diff --git a/resources/profiles/Creality.json b/resources/profiles/Creality.json index b3786f6eb34..068cb4ca82c 100644 --- a/resources/profiles/Creality.json +++ b/resources/profiles/Creality.json @@ -24,6 +24,10 @@ "name": "Creality CR-6 Max", "sub_path": "machine/Creality CR-6 Max.json" }, + { + "name": "Creality CR-M4", + "sub_path": "machine/Creality CR-M4.json" + }, { "name": "Creality Ender-3 V2", "sub_path": "machine/Creality Ender-3 V2.json" @@ -107,6 +111,14 @@ { "name": "Creality K1 Max", "sub_path": "machine/Creality K1 Max.json" + }, + { + "name": "Creality K1 SE", + "sub_path": "machine/Creality K1 SE.json" + }, + { + "name": "Creality K2 Plus", + "sub_path": "machine/Creality K2 Plus.json" } ], "process_list": [ @@ -246,6 +258,14 @@ "name": "0.08mm SuperDetail @Creality Ender5Pro (2019) 0.3", "sub_path": "process/0.08mm SuperDetail @Creality Ender5Pro (2019) 0.3.json" }, + { + "name": "0.08mm SuperDetail @Creality K2 Plus 0.2 nozzle", + "sub_path": "process/0.08mm SuperDetail @Creality K2 Plus 0.2 nozzle.json" + }, + { + "name": "0.08mm SuperDetail @Creality K2 Plus 0.4 nozzle", + "sub_path": "process/0.08mm SuperDetail @Creality K2 Plus 0.4 nozzle.json" + }, { "name": "0.10mm HighDetail @Creality CR-6 0.4.json", "sub_path": "process/0.10mm HighDetail @Creality CR-6 0.4.json" @@ -262,6 +282,14 @@ "name": "0.10mm HighDetail @Creality Ender5Pro (2019) 0.3", "sub_path": "process/0.10mm HighDetail @Creality Ender5Pro (2019) 0.3.json" }, + { + "name": "0.10mm HighDetail @Creality CR-M4", + "sub_path": "process/0.10mm HighDetail @Creality CR-M4.json" + }, + { + "name": "0.10mm HighDetail @Creality K2 Plus 0.2 nozzle", + "sub_path": "process/0.10mm HighDetail @Creality K2 Plus 0.2 nozzle.json" + }, { "name": "0.12mm Detail @Creality Ender3 0.2", "sub_path": "process/0.12mm Fine @Creality Ender3 0.2.json" @@ -374,6 +402,26 @@ "name": "0.12mm Detail @Creality Ender5Pro (2019) 0.5", "sub_path": "process/0.12mm Detail @Creality Ender5Pro (2019) 0.5.json" }, + { + "name": "0.15mm Detail @Creality CR-M4", + "sub_path": "process/0.15mm Detail @Creality CR-M4.json" + }, + { + "name": "0.12mm Detail @Creality K2 Plus 0.2 nozzle", + "sub_path": "process/0.12mm Detail @Creality K2 Plus 0.2 nozzle.json" + }, + { + "name": "0.12mm Detail @Creality K2 Plus 0.4 nozzle", + "sub_path": "process/0.12mm Detail @Creality K2 Plus 0.4 nozzle.json" + }, + { + "name": "0.18mm Detail @Creality K2 Plus 0.6 nozzle", + "sub_path": "process/0.18mm Detail @Creality K2 Plus 0.6 nozzle.json" + }, + { + "name": "0.24mm Detail @Creality K2 Plus 0.8 nozzle", + "sub_path": "process/0.24mm Detail @Creality K2 Plus 0.8 nozzle.json" + }, { "name": "0.16mm Optimal @Creality CR10V2", "sub_path": "process/0.16mm Optimal @Creality CR10V2.json" @@ -530,6 +578,22 @@ "name": "0.16mm Optimal @Creality K1Max (0.4 nozzle)", "sub_path": "process/0.16mm Optimal @Creality K1Max (0.4 nozzle).json" }, + { + "name": "0.14mm Optimal @Creality K2 Plus 0.2 nozzle", + "sub_path": "process/0.14mm Optimal @Creality K2 Plus 0.2 nozzle.json" + }, + { + "name": "0.16mm Optimal @Creality K2 Plus 0.4 nozzle", + "sub_path": "process/0.16mm Optimal @Creality K2 Plus 0.4 nozzle.json" + }, + { + "name": "0.24mm Optimal @Creality K2 Plus 0.6 nozzle", + "sub_path": "process/0.24mm Optimal @Creality K2 Plus 0.6 nozzle.json" + }, + { + "name": "0.32mm Optimal @Creality K2 Plus 0.8 nozzle", + "sub_path": "process/0.32mm Optimal @Creality K2 Plus 0.8 nozzle.json" + }, { "name": "0.20mm Standard @Creality CR10V2", "sub_path": "process/0.20mm Standard @Creality CR10V2.json" @@ -686,6 +750,10 @@ "name": "0.20mm Standard @Creality Ender6", "sub_path": "process/0.20mm Standard @Creality Ender6.json" }, + { + "name": "0.20mm Standard @Creality CR-M4", + "sub_path": "process/0.20mm Standard @Creality CR-M4.json" + }, { "name": "0.20mm Standard @Creality K1 (0.4 nozzle)", "sub_path": "process/0.20mm Standard @Creality K1 (0.4 nozzle).json" @@ -698,6 +766,22 @@ "name": "0.20mm Standard @Creality K1Max (0.4 nozzle)", "sub_path": "process/0.20mm Standard @Creality K1Max (0.4 nozzle).json" }, + { + "name": "0.20mm Standard @Creality K2 Plus 0.4 nozzle", + "sub_path": "process/0.20mm Standard @Creality K2 Plus 0.4 nozzle.json" + }, + { + "name": "0.30mm Standard @Creality K2 Plus 0.6 nozzle", + "sub_path": "process/0.30mm Standard @Creality K2 Plus 0.6 nozzle.json" + }, + { + "name": "0.40mm Standard @Creality K2 Plus 0.8 nozzle", + "sub_path": "process/0.40mm Standard @Creality K2 Plus 0.8 nozzle.json" + }, + { + "name": "0.20mm Fast @Creality K1 SE 0.4", + "sub_path": "process/0.20mm Fast @Creality K1 SE 0.4.json" + }, { "name": "0.24mm Draft @Creality Ender3 0.2", "sub_path": "process/0.24mm Draft @Creality Ender3 0.2.json" @@ -826,6 +910,18 @@ "name": "0.24mm Draft @Creality Ender5Pro (2019) 0.8", "sub_path": "process/0.24mm Draft @Creality Ender5Pro (2019) 0.8.json" }, + { + "name": "0.24mm Draft @Creality K2 Plus 0.4 nozzle", + "sub_path": "process/0.24mm Draft @Creality K2 Plus 0.4 nozzle.json" + }, + { + "name": "0.36mm Draft @Creality K2 Plus 0.6 nozzle", + "sub_path": "process/0.36mm Draft @Creality K2 Plus 0.6 nozzle.json" + }, + { + "name": "0.48mm Draft @Creality K2 Plus 0.8 nozzle", + "sub_path": "process/0.48mm Draft @Creality K2 Plus 0.8 nozzle.json" + }, { "name": "0.28mm SuperDraft @Creality Ender3 0.2", "sub_path": "process/0.28mm SuperDraft @Creality Ender3 0.2.json" @@ -882,6 +978,18 @@ "name": "0.28mm SuperDraft @Creality Ender5Pro (2019) 1.0", "sub_path": "process/0.28mm SuperDraft @Creality Ender5Pro (2019) 1.0.json" }, + { + "name": "0.28mm SuperDraft @Creality K2 Plus 0.4 nozzle", + "sub_path": "process/0.28mm SuperDraft @Creality K2 Plus 0.4 nozzle.json" + }, + { + "name": "0.42mm SuperDraft @Creality K2 Plus 0.6 nozzle", + "sub_path": "process/0.42mm SuperDraft @Creality K2 Plus 0.6 nozzle.json" + }, + { + "name": "0.56mm SuperDraft @Creality K2 Plus 0.8 nozzle", + "sub_path": "process/0.56mm SuperDraft @Creality K2 Plus 0.8 nozzle.json" + }, { "name": "0.32mm Chunky @Creality CR-6 0.6", "sub_path": "process/0.32mm Chunky @Creality CR-6 0.6.json" @@ -1179,6 +1287,46 @@ { "name": "Creality Generic TPU @K1-all", "sub_path": "filament/Creality Generic TPU @K1-all.json" + }, + { + "name": "Creality Generic ABS @K2-all", + "sub_path": "filament/Creality Generic ABS @K2-all.json" + }, + { + "name": "Creality Generic ASA @K2-all", + "sub_path": "filament/Creality Generic ASA @K2-all.json" + }, + { + "name": "Creality Generic PETG @K2-all", + "sub_path": "filament/Creality Generic PETG @K2-all.json" + }, + { + "name": "Creality Generic PLA @K2-all", + "sub_path": "filament/Creality Generic PLA @K2-all.json" + }, + { + "name": "Creality Generic PLA High Speed @K2-all", + "sub_path": "filament/Creality Generic PLA High Speed @K2-all.json" + }, + { + "name": "Creality Generic PLA Matte @K2-all", + "sub_path": "filament/Creality Generic PLA Matte @K2-all.json" + }, + { + "name": "Creality Generic PLA Silk @K2-all", + "sub_path": "filament/Creality Generic PLA Silk @K2-all.json" + }, + { + "name": "Creality Generic TPU @K2-all", + "sub_path": "filament/Creality Generic TPU @K2-all.json" + }, + { + "name": "Creality Generic PLA-CF @K2-all", + "sub_path": "filament/Creality Generic PLA-CF @K2-all.json" + }, + { + "name": "Creality Generic PA-CF @K2-all", + "sub_path": "filament/Creality Generic PA-CF @K2-all.json" } ], "machine_list": [ @@ -1250,6 +1398,10 @@ "name": "Creality CR-6 Max 0.8 nozzle", "sub_path": "machine/Creality CR-6 Max 0.8 nozzle.json" }, + { + "name": "Creality CR-M4 0.4 nozzle", + "sub_path": "machine/Creality CR-M4 0.4 nozzle.json" + }, { "name": "Creality Ender-3 V2 0.4 nozzle", "sub_path": "machine/Creality Ender-3 V2 0.4 nozzle.json" @@ -1426,6 +1578,10 @@ "name": "Creality K1C 0.8 nozzle", "sub_path": "machine/Creality K1C 0.8 nozzle.json" }, + { + "name": "Creality K1 SE 0.4 nozzle", + "sub_path": "machine/Creality K1 SE 0.4 nozzle.json" + }, { "name": "Creality K1 Max (0.4 nozzle)", "sub_path": "machine/Creality K1 Max (0.4 nozzle).json" @@ -1437,6 +1593,22 @@ { "name": "Creality K1 Max (0.8 nozzle)", "sub_path": "machine/Creality K1 Max (0.8 nozzle).json" + }, + { + "name": "Creality K2 Plus 0.2 nozzle", + "sub_path": "machine/Creality K2 Plus 0.2 nozzle.json" + }, + { + "name": "Creality K2 Plus 0.4 nozzle", + "sub_path": "machine/Creality K2 Plus 0.4 nozzle.json" + }, + { + "name": "Creality K2 Plus 0.6 nozzle", + "sub_path": "machine/Creality K2 Plus 0.6 nozzle.json" + }, + { + "name": "Creality K2 Plus 0.8 nozzle", + "sub_path": "machine/Creality K2 Plus 0.8 nozzle.json" } ] } \ No newline at end of file diff --git a/resources/profiles/Creality/Creality CR-M4_cover.png b/resources/profiles/Creality/Creality CR-M4_cover.png new file mode 100644 index 00000000000..482a2577aba Binary files /dev/null and b/resources/profiles/Creality/Creality CR-M4_cover.png differ diff --git a/resources/profiles/Creality/Creality K1 SE_cover.png b/resources/profiles/Creality/Creality K1 SE_cover.png new file mode 100644 index 00000000000..b855c0b0f57 Binary files /dev/null and b/resources/profiles/Creality/Creality K1 SE_cover.png differ diff --git a/resources/profiles/Creality/Creality K2 Plus_cover.png b/resources/profiles/Creality/Creality K2 Plus_cover.png new file mode 100644 index 00000000000..fc381e2bd81 Binary files /dev/null and b/resources/profiles/Creality/Creality K2 Plus_cover.png differ diff --git a/resources/profiles/Creality/creality_crm4_buildplate_model.stl b/resources/profiles/Creality/creality_crm4_buildplate_model.stl new file mode 100644 index 00000000000..fabd185e34f Binary files /dev/null and b/resources/profiles/Creality/creality_crm4_buildplate_model.stl differ diff --git a/resources/profiles/Creality/creality_crm4_buildplate_texture.png b/resources/profiles/Creality/creality_crm4_buildplate_texture.png new file mode 100644 index 00000000000..c87a116ff59 Binary files /dev/null and b/resources/profiles/Creality/creality_crm4_buildplate_texture.png differ diff --git a/resources/profiles/Creality/creality_k1se_buildplate_model.stl b/resources/profiles/Creality/creality_k1se_buildplate_model.stl new file mode 100644 index 00000000000..ef159f264b7 Binary files /dev/null and b/resources/profiles/Creality/creality_k1se_buildplate_model.stl differ diff --git a/resources/profiles/Creality/creality_k1se_buildplate_texture.png b/resources/profiles/Creality/creality_k1se_buildplate_texture.png new file mode 100644 index 00000000000..cde02380781 Binary files /dev/null and b/resources/profiles/Creality/creality_k1se_buildplate_texture.png differ diff --git a/resources/profiles/Creality/creality_k2plus_buildplate_model.stl b/resources/profiles/Creality/creality_k2plus_buildplate_model.stl new file mode 100644 index 00000000000..ea2d8b07e82 Binary files /dev/null and b/resources/profiles/Creality/creality_k2plus_buildplate_model.stl differ diff --git a/resources/profiles/Creality/creality_k2plus_buildplate_texture.png b/resources/profiles/Creality/creality_k2plus_buildplate_texture.png new file mode 100644 index 00000000000..4dcf16e604f Binary files /dev/null and b/resources/profiles/Creality/creality_k2plus_buildplate_texture.png differ diff --git a/resources/profiles/Creality/filament/Creality Generic ABS @K1-all.json b/resources/profiles/Creality/filament/Creality Generic ABS @K1-all.json index ccc7f3c526f..f5bc4e3b507 100644 --- a/resources/profiles/Creality/filament/Creality Generic ABS @K1-all.json +++ b/resources/profiles/Creality/filament/Creality Generic ABS @K1-all.json @@ -17,6 +17,7 @@ "compatible_printers": [ "Creality K1C 0.4 nozzle", "Creality K1C 0.6 nozzle", - "Creality K1C 0.8 nozzle" + "Creality K1C 0.8 nozzle", + "Creality K1 SE 0.4 nozzle" ] } \ No newline at end of file diff --git a/resources/profiles/Creality/filament/Creality Generic ABS @K2-all.json b/resources/profiles/Creality/filament/Creality Generic ABS @K2-all.json new file mode 100644 index 00000000000..4a033b3780c --- /dev/null +++ b/resources/profiles/Creality/filament/Creality Generic ABS @K2-all.json @@ -0,0 +1,26 @@ +{ + "type": "filament", + "setting_id": "GFSA04_CREALITY_00", + "name": "Creality Generic ABS @K2-all", + "from": "system", + "instantiation": "true", + "inherits": "Creality Generic ABS", + "filament_flow_ratio": [ + "0.95" + ], + "filament_max_volumetric_speed": [ + "18" + ], + "slow_down_layer_time": [ + "12" + ], + "slow_down_min_speed": [ + "20" + ], + "compatible_printers": [ + "Creality K2 Plus 0.2 nozzle", + "Creality K2 Plus 0.4 nozzle", + "Creality K2 Plus 0.6 nozzle", + "Creality K2 Plus 0.8 nozzle" + ] +} \ No newline at end of file diff --git a/resources/profiles/Creality/filament/Creality Generic ABS.json b/resources/profiles/Creality/filament/Creality Generic ABS.json index 288cb5dbada..518de656c86 100644 --- a/resources/profiles/Creality/filament/Creality Generic ABS.json +++ b/resources/profiles/Creality/filament/Creality Generic ABS.json @@ -58,6 +58,7 @@ "Creality CR-10 SE 0.2 nozzle", "Creality CR-10 SE 0.4 nozzle", "Creality CR-10 SE 0.6 nozzle", - "Creality CR-10 SE 0.8 nozzle" + "Creality CR-10 SE 0.8 nozzle", + "Creality CR-M4 0.4 nozzle" ] } diff --git a/resources/profiles/Creality/filament/Creality Generic ASA @K1-all.json b/resources/profiles/Creality/filament/Creality Generic ASA @K1-all.json index 038fd88d960..6f24db9b0ba 100644 --- a/resources/profiles/Creality/filament/Creality Generic ASA @K1-all.json +++ b/resources/profiles/Creality/filament/Creality Generic ASA @K1-all.json @@ -17,6 +17,7 @@ "compatible_printers": [ "Creality K1C 0.4 nozzle", "Creality K1C 0.6 nozzle", - "Creality K1C 0.8 nozzle" + "Creality K1C 0.8 nozzle", + "Creality K1 SE 0.4 nozzle" ] } \ No newline at end of file diff --git a/resources/profiles/Creality/filament/Creality Generic ASA @K2-all.json b/resources/profiles/Creality/filament/Creality Generic ASA @K2-all.json new file mode 100644 index 00000000000..8cb657bdd04 --- /dev/null +++ b/resources/profiles/Creality/filament/Creality Generic ASA @K2-all.json @@ -0,0 +1,26 @@ +{ + "type": "filament", + "setting_id": "GFSA04_00", + "name": "Creality Generic ASA @K2-all", + "from": "system", + "instantiation": "true", + "inherits": "Creality Generic ASA", + "filament_flow_ratio": [ + "0.95" + ], + "filament_max_volumetric_speed": [ + "14" + ], + "slow_down_layer_time": [ + "10" + ], + "slow_down_min_speed": [ + "20" + ], + "compatible_printers": [ + "Creality K2 Plus 0.2 nozzle", + "Creality K2 Plus 0.4 nozzle", + "Creality K2 Plus 0.6 nozzle", + "Creality K2 Plus 0.8 nozzle" + ] +} \ No newline at end of file diff --git a/resources/profiles/Creality/filament/Creality Generic PA-CF @K1-all.json b/resources/profiles/Creality/filament/Creality Generic PA-CF @K1-all.json index b94656297f8..cdf6f7accb2 100644 --- a/resources/profiles/Creality/filament/Creality Generic PA-CF @K1-all.json +++ b/resources/profiles/Creality/filament/Creality Generic PA-CF @K1-all.json @@ -12,6 +12,7 @@ "Creality K1C 0.4 nozzle", "Creality K1C 0.6 nozzle", "Creality K1C 0.8 nozzle", + "Creality K1 SE 0.4 nozzle", "Creality K1 Max (0.4 nozzle)", "Creality K1 Max (0.6 nozzle)", "Creality K1 Max (0.8 nozzle)" diff --git a/resources/profiles/Creality/filament/Creality Generic PA-CF @K2-all.json b/resources/profiles/Creality/filament/Creality Generic PA-CF @K2-all.json new file mode 100644 index 00000000000..3e0963cc490 --- /dev/null +++ b/resources/profiles/Creality/filament/Creality Generic PA-CF @K2-all.json @@ -0,0 +1,23 @@ +{ + "type": "filament", + "name": "Creality Generic PA-CF @K2-all", + "inherits": "Creality Generic PA-CF", + "from": "system", + "setting_id": "GFSN99_01", + "instantiation": "true", + "nozzle_temperature_initial_layer": [ + "280" + ], + "nozzle_temperature": [ + "280" + ], + "fan_min_speed": [ + "30" + ], + "compatible_printers": [ + "Creality K2 Plus 0.2 nozzle", + "Creality K2 Plus 0.4 nozzle", + "Creality K2 Plus 0.6 nozzle", + "Creality K2 Plus 0.8 nozzle" + ] +} \ No newline at end of file diff --git a/resources/profiles/Creality/filament/Creality Generic PC @K1-all.json b/resources/profiles/Creality/filament/Creality Generic PC @K1-all.json index 34a99503189..dd9b382e801 100644 --- a/resources/profiles/Creality/filament/Creality Generic PC @K1-all.json +++ b/resources/profiles/Creality/filament/Creality Generic PC @K1-all.json @@ -15,6 +15,7 @@ "Creality K1C 0.4 nozzle", "Creality K1C 0.6 nozzle", "Creality K1C 0.8 nozzle", + "Creality K1 SE 0.4 nozzle", "Creality K1 Max (0.4 nozzle)", "Creality K1 Max (0.6 nozzle)", "Creality K1 Max (0.8 nozzle)" diff --git a/resources/profiles/Creality/filament/Creality Generic PETG @K1-all.json b/resources/profiles/Creality/filament/Creality Generic PETG @K1-all.json index 9528472e4c6..22600d80db4 100644 --- a/resources/profiles/Creality/filament/Creality Generic PETG @K1-all.json +++ b/resources/profiles/Creality/filament/Creality Generic PETG @K1-all.json @@ -50,6 +50,7 @@ "compatible_printers": [ "Creality K1C 0.4 nozzle", "Creality K1C 0.6 nozzle", - "Creality K1C 0.8 nozzle" + "Creality K1C 0.8 nozzle", + "Creality K1 SE 0.4 nozzle" ] } \ No newline at end of file diff --git a/resources/profiles/Creality/filament/Creality Generic PETG @K2-all.json b/resources/profiles/Creality/filament/Creality Generic PETG @K2-all.json new file mode 100644 index 00000000000..efe3f28bb7f --- /dev/null +++ b/resources/profiles/Creality/filament/Creality Generic PETG @K2-all.json @@ -0,0 +1,59 @@ +{ + "type": "filament", + "setting_id": "GFSG99_00", + "name": "Creality Generic PETG @K2-all", + "from": "system", + "instantiation": "true", + "inherits": "Creality Generic PETG", + "filament_max_volumetric_speed": [ + "18" + ], + "slow_down_layer_time": [ + "8" + ], + "slow_down_min_speed": [ + "20" + ], + "cool_plate_temp": [ + "70" + ], + "eng_plate_temp": [ + "50" + ], + "hot_plate_temp": [ + "70" + ], + "textured_plate_temp": [ + "70" + ], + "cool_plate_temp_initial_layer": [ + "70" + ], + "eng_plate_temp_initial_layer": [ + "0" + ], + "hot_plate_temp_initial_layer": [ + "70" + ], + "textured_plate_temp_initial_layer": [ + "70" + ], + "nozzle_temperature_initial_layer": [ + "250" + ], + "nozzle_temperature": [ + "250" + ], + "fan_max_speed": [ + "80" + ], + "reduce_fan_stop_start_freq": [ + "1" + ], + "compatible_printers": [ + "Creality K2 Plus 0.2 nozzle", + "Creality K2 Plus 0.4 nozzle", + "Creality K2 Plus 0.6 nozzle", + "Creality K2 Plus 0.8 nozzle" + ] +} \ No newline at end of file diff --git a/resources/profiles/Creality/filament/Creality Generic PETG.json b/resources/profiles/Creality/filament/Creality Generic PETG.json index fc48c8a1041..365e513d0e8 100644 --- a/resources/profiles/Creality/filament/Creality Generic PETG.json +++ b/resources/profiles/Creality/filament/Creality Generic PETG.json @@ -68,6 +68,7 @@ "Creality CR-10 SE 0.2 nozzle", "Creality CR-10 SE 0.4 nozzle", "Creality CR-10 SE 0.6 nozzle", - "Creality CR-10 SE 0.8 nozzle" + "Creality CR-10 SE 0.8 nozzle", + "Creality CR-M4 0.4 nozzle" ] } diff --git a/resources/profiles/Creality/filament/Creality Generic PLA @K1-all.json b/resources/profiles/Creality/filament/Creality Generic PLA @K1-all.json index 50ed25372e8..458e578b455 100644 --- a/resources/profiles/Creality/filament/Creality Generic PLA @K1-all.json +++ b/resources/profiles/Creality/filament/Creality Generic PLA @K1-all.json @@ -41,6 +41,7 @@ "compatible_printers": [ "Creality K1C 0.4 nozzle", "Creality K1C 0.6 nozzle", - "Creality K1C 0.8 nozzle" + "Creality K1C 0.8 nozzle", + "Creality K1 SE 0.4 nozzle" ] } \ No newline at end of file diff --git a/resources/profiles/Creality/filament/Creality Generic PLA @K2-all.json b/resources/profiles/Creality/filament/Creality Generic PLA @K2-all.json new file mode 100644 index 00000000000..472eef92212 --- /dev/null +++ b/resources/profiles/Creality/filament/Creality Generic PLA @K2-all.json @@ -0,0 +1,53 @@ +{ + "type": "filament", + "setting_id": "GFSL99_00", + "name": "Creality Generic PLA @K2-all", + "from": "system", + "instantiation": "true", + "inherits": "Creality Generic PLA", + "filament_flow_ratio": [ + "0.95" + ], + "filament_max_volumetric_speed": [ + "18" + ], + "slow_down_min_speed": [ + "20" + ], + "slow_down_layer_time": [ + "4" + ], + "cool_plate_temp": [ + "50" + ], + "eng_plate_temp": [ + "50" + ], + "hot_plate_temp": [ + "50" + ], + "textured_plate_temp": [ + "50" + ], + "cool_plate_temp_initial_layer": [ + "50" + ], + "eng_plate_temp_initial_layer": [ + "50" + ], + "hot_plate_temp_initial_layer": [ + "50" + ], + "textured_plate_temp_initial_layer": [ + "50" + ], + "reduce_fan_stop_start_freq": [ + "1" + ], + "compatible_printers": [ + "Creality K2 Plus 0.2 nozzle", + "Creality K2 Plus 0.4 nozzle", + "Creality K2 Plus 0.6 nozzle", + "Creality K2 Plus 0.8 nozzle" + ] +} \ No newline at end of file diff --git a/resources/profiles/Creality/filament/Creality Generic PLA High Speed @K1-all.json b/resources/profiles/Creality/filament/Creality Generic PLA High Speed @K1-all.json index 083b3b30b73..1f4bc1e05fd 100644 --- a/resources/profiles/Creality/filament/Creality Generic PLA High Speed @K1-all.json +++ b/resources/profiles/Creality/filament/Creality Generic PLA High Speed @K1-all.json @@ -15,6 +15,7 @@ "Creality K1C 0.4 nozzle", "Creality K1C 0.6 nozzle", "Creality K1C 0.8 nozzle", + "Creality K1 SE 0.4 nozzle", "Creality K1 Max (0.4 nozzle)", "Creality K1 Max (0.6 nozzle)", "Creality K1 Max (0.8 nozzle)" diff --git a/resources/profiles/Creality/filament/Creality Generic PLA High Speed @K2-all.json b/resources/profiles/Creality/filament/Creality Generic PLA High Speed @K2-all.json new file mode 100644 index 00000000000..e6572ec13d0 --- /dev/null +++ b/resources/profiles/Creality/filament/Creality Generic PLA High Speed @K2-all.json @@ -0,0 +1,17 @@ +{ + "type": "filament", + "setting_id": "GFSL95_00", + "name": "Creality Generic PLA High Speed @K2-all", + "from": "system", + "instantiation": "true", + "inherits": "Creality Generic PLA @K2-all", + "filament_max_volumetric_speed": [ + "23" + ], + "compatible_printers": [ + "Creality K2 Plus 0.2 nozzle", + "Creality K2 Plus 0.4 nozzle", + "Creality K2 Plus 0.6 nozzle", + "Creality K2 Plus 0.8 nozzle" + ] +} \ No newline at end of file diff --git a/resources/profiles/Creality/filament/Creality Generic PLA Matte @K1-all.json b/resources/profiles/Creality/filament/Creality Generic PLA Matte @K1-all.json index 4dfcea7c892..ce0da80e9fb 100644 --- a/resources/profiles/Creality/filament/Creality Generic PLA Matte @K1-all.json +++ b/resources/profiles/Creality/filament/Creality Generic PLA Matte @K1-all.json @@ -15,6 +15,7 @@ "Creality K1C 0.4 nozzle", "Creality K1C 0.6 nozzle", "Creality K1C 0.8 nozzle", + "Creality K1 SE 0.4 nozzle", "Creality K1 Max (0.4 nozzle)", "Creality K1 Max (0.6 nozzle)", "Creality K1 Max (0.8 nozzle)" diff --git a/resources/profiles/Creality/filament/Creality Generic PLA Matte @K2-all.json b/resources/profiles/Creality/filament/Creality Generic PLA Matte @K2-all.json new file mode 100644 index 00000000000..354200f89dd --- /dev/null +++ b/resources/profiles/Creality/filament/Creality Generic PLA Matte @K2-all.json @@ -0,0 +1,17 @@ +{ + "type": "filament", + "setting_id": "GFSL05_00", + "name": "Creality Generic PLA Matte @K2-all", + "from": "system", + "instantiation": "true", + "inherits": "Creality Generic PLA @K2-all", + "filament_max_volumetric_speed": [ + "18" + ], + "compatible_printers": [ + "Creality K2 Plus 0.2 nozzle", + "Creality K2 Plus 0.4 nozzle", + "Creality K2 Plus 0.6 nozzle", + "Creality K2 Plus 0.8 nozzle" + ] +} \ No newline at end of file diff --git a/resources/profiles/Creality/filament/Creality Generic PLA Silk @K1-all.json b/resources/profiles/Creality/filament/Creality Generic PLA Silk @K1-all.json index c8170220ac7..2c4c8480ed7 100644 --- a/resources/profiles/Creality/filament/Creality Generic PLA Silk @K1-all.json +++ b/resources/profiles/Creality/filament/Creality Generic PLA Silk @K1-all.json @@ -15,6 +15,7 @@ "Creality K1C 0.4 nozzle", "Creality K1C 0.6 nozzle", "Creality K1C 0.8 nozzle", + "Creality K1 SE 0.4 nozzle", "Creality K1 Max (0.4 nozzle)", "Creality K1 Max (0.6 nozzle)", "Creality K1 Max (0.8 nozzle)" diff --git a/resources/profiles/Creality/filament/Creality Generic PLA Silk @K2-all.json b/resources/profiles/Creality/filament/Creality Generic PLA Silk @K2-all.json new file mode 100644 index 00000000000..4f3c3567fab --- /dev/null +++ b/resources/profiles/Creality/filament/Creality Generic PLA Silk @K2-all.json @@ -0,0 +1,17 @@ +{ + "type": "filament", + "setting_id": "GFSL96_00", + "name": "Creality Generic PLA Silk @K2-all", + "from": "system", + "instantiation": "true", + "inherits": "Creality Generic PLA @K2-all", + "filament_max_volumetric_speed": [ + "10" + ], + "compatible_printers": [ + "Creality K2 Plus 0.2 nozzle", + "Creality K2 Plus 0.4 nozzle", + "Creality K2 Plus 0.6 nozzle", + "Creality K2 Plus 0.8 nozzle" + ] +} \ No newline at end of file diff --git a/resources/profiles/Creality/filament/Creality Generic PLA-CF @K1-all.json b/resources/profiles/Creality/filament/Creality Generic PLA-CF @K1-all.json index 642ca517afe..d620e43a8b7 100644 --- a/resources/profiles/Creality/filament/Creality Generic PLA-CF @K1-all.json +++ b/resources/profiles/Creality/filament/Creality Generic PLA-CF @K1-all.json @@ -15,6 +15,7 @@ "Creality K1C 0.4 nozzle", "Creality K1C 0.6 nozzle", "Creality K1C 0.8 nozzle", + "Creality K1 SE 0.4 nozzle", "Creality K1 Max (0.4 nozzle)", "Creality K1 Max (0.6 nozzle)", "Creality K1 Max (0.8 nozzle)" diff --git a/resources/profiles/Creality/filament/Creality Generic PLA-CF @K2-all.json b/resources/profiles/Creality/filament/Creality Generic PLA-CF @K2-all.json new file mode 100644 index 00000000000..e76726af3f7 --- /dev/null +++ b/resources/profiles/Creality/filament/Creality Generic PLA-CF @K2-all.json @@ -0,0 +1,23 @@ +{ + "type": "filament", + "setting_id": "GFSL96_00", + "name": "Creality Generic PLA-CF @K2-all", + "from": "system", + "instantiation": "true", + "inherits": "Creality Generic PLA-CF", + "filament_max_volumetric_speed": [ + "18" + ], + "slow_down_layer_time": [ + "8" + ], + "additional_cooling_fan_speed": [ + "0" + ], + "compatible_printers": [ + "Creality K2 Plus 0.2 nozzle", + "Creality K2 Plus 0.4 nozzle", + "Creality K2 Plus 0.6 nozzle", + "Creality K2 Plus 0.8 nozzle" + ] +} \ No newline at end of file diff --git a/resources/profiles/Creality/filament/Creality Generic PLA.json b/resources/profiles/Creality/filament/Creality Generic PLA.json index e0383bfc989..a81b71ab1ea 100644 --- a/resources/profiles/Creality/filament/Creality Generic PLA.json +++ b/resources/profiles/Creality/filament/Creality Generic PLA.json @@ -59,6 +59,7 @@ "Creality CR-10 SE 0.2 nozzle", "Creality CR-10 SE 0.4 nozzle", "Creality CR-10 SE 0.6 nozzle", - "Creality CR-10 SE 0.8 nozzle" + "Creality CR-10 SE 0.8 nozzle", + "Creality CR-M4 0.4 nozzle" ] } diff --git a/resources/profiles/Creality/filament/Creality Generic TPU @K1-all.json b/resources/profiles/Creality/filament/Creality Generic TPU @K1-all.json index a19eec76efd..48c4d40b116 100644 --- a/resources/profiles/Creality/filament/Creality Generic TPU @K1-all.json +++ b/resources/profiles/Creality/filament/Creality Generic TPU @K1-all.json @@ -35,6 +35,7 @@ "compatible_printers": [ "Creality K1C 0.4 nozzle", "Creality K1C 0.6 nozzle", - "Creality K1C 0.8 nozzle" + "Creality K1C 0.8 nozzle", + "Creality K1 SE 0.4 nozzle" ] } \ No newline at end of file diff --git a/resources/profiles/Creality/filament/Creality Generic TPU @K2-all.json b/resources/profiles/Creality/filament/Creality Generic TPU @K2-all.json new file mode 100644 index 00000000000..21b4e547a35 --- /dev/null +++ b/resources/profiles/Creality/filament/Creality Generic TPU @K2-all.json @@ -0,0 +1,41 @@ +{ + "type": "filament", + "setting_id": "GFU99_CREALITY_00", + "name": "Creality Generic TPU @K2-all", + "from": "system", + "instantiation": "true", + "inherits": "Creality Generic TPU", + "hot_plate_temp": [ + "40" + ], + "hot_plate_temp_initial_layer": [ + "40" + ], + "textured_plate_temp": [ + "40" + ], + "textured_plate_temp_initial_layer": [ + "40" + ], + "nozzle_temperature_initial_layer": [ + "220" + ], + "nozzle_temperature": [ + "220" + ], + "filament_max_volumetric_speed": [ + "3" + ], + "slow_down_layer_time": [ + "8" + ], + "reduce_fan_stop_start_freq": [ + "1" + ], + "compatible_printers": [ + "Creality K2 Plus 0.2 nozzle", + "Creality K2 Plus 0.4 nozzle", + "Creality K2 Plus 0.6 nozzle", + "Creality K2 Plus 0.8 nozzle" + ] +} \ No newline at end of file diff --git a/resources/profiles/Creality/filament/Creality Generic TPU.json b/resources/profiles/Creality/filament/Creality Generic TPU.json index ac48f2d0320..ff93417668e 100644 --- a/resources/profiles/Creality/filament/Creality Generic TPU.json +++ b/resources/profiles/Creality/filament/Creality Generic TPU.json @@ -16,7 +16,7 @@ "Creality K1 Max (0.4 nozzle)", "Creality K1 Max (0.6 nozzle)", "Creality K1 Max (0.8 nozzle)", - "Creality Sermoon V1 0.4 nozzle", + "Creality Sermoon V1 0.4 nozzle", "Creality CR-10 SE 0.2 nozzle", "Creality CR-10 SE 0.4 nozzle", "Creality CR-10 SE 0.6 nozzle", diff --git a/resources/profiles/Creality/machine/Creality CR-M4 0.4 nozzle.json b/resources/profiles/Creality/machine/Creality CR-M4 0.4 nozzle.json new file mode 100644 index 00000000000..e552611f8ba --- /dev/null +++ b/resources/profiles/Creality/machine/Creality CR-M4 0.4 nozzle.json @@ -0,0 +1,54 @@ +{ + "type": "machine", + "setting_id": "GM001", + "name": "Creality CR-M4 0.4 nozzle", + "from": "system", + "instantiation": "true", + "inherits": "fdm_creality_common", + "printer_model": "Creality CR-M4", + "gcode_flavor": "marlin", + "printer_structure": "i3", + "default_filament_profile": [ + "Creality Generic PLA" + ], + "default_print_profile": "0.20mm Standard @Creality CR-M4", + "nozzle_diameter": [ + "0.4" + ], + "printable_height": "470", + "printable_area": [ + "0x0", + "450x0", + "450x450", + "0x450" + ], + "machine_max_speed_e": [ + "50", + "50" + ], + "machine_max_acceleration_z": [ + "100", + "100" + ], + "machine_max_jerk_x": [ + "10", + "10" + ], + "machine_max_jerk_y": [ + "10", + "10" + ], + "retraction_minimum_travel": [ + "0.8" + ], + "retraction_length": [ + "0.8" + ], + "retraction_speed": [ + "40" + ], + "nozzle_type": "undefine", + "auxiliary_fan": "0", + "machine_start_gcode": "M220 S100 ;Reset Feedrate\nM221 S100 ;Reset Flowrate\nG28 ;Home\nG92 E0 ;Reset Extruder\nG1 Z2.0 F3000 ;Move Z Axis up\nG1 X10.1 Y20 Z0.28 F5000.0 ;Move to start position\nM109 S[material_print_temperature_layer_0]\nG1 X10.1 Y145.0 Z0.28 F1500.0 E15 ;Draw the first line\nG1 X10.4 Y145.0 Z0.28 F5000.0 ;Move to side a little\nG1 X10.4 Y20 Z0.28 F1500.0 E30 ;Draw the second line\nG92 E0 ;Reset Extruder\nG1 E-1.0000 F1800 ;Retract a bit\nG1 Z2.0 F3000 ;Move Z Axis up\nG1 E0.0000 F1800 ", + "machine_end_gcode": "G91 ;Relative positionning\nG1 E-2 F2700 ;Retract a bit\nG1 E-2 Z0.2 F2400 ;Retract and raise Z\nG1 X5 Y5 F3000 ;Wipe out\nG1 Z10 ;Raise Z more\nG90 ;Absolute positionning\nG1 X0 Y0 ;Present print\nM106 S0 ;Turn-off fan\nM104 S0 ;Turn-off hotend\nM140 S0 ;Turn-off bed\nM84 X Y E ;Disable all steppers but Z" +} diff --git a/resources/profiles/Creality/machine/Creality CR-M4.json b/resources/profiles/Creality/machine/Creality CR-M4.json new file mode 100644 index 00000000000..889591f6eb6 --- /dev/null +++ b/resources/profiles/Creality/machine/Creality CR-M4.json @@ -0,0 +1,12 @@ +{ + "type": "machine_model", + "name": "Creality CR-M4", + "model_id": "Creality-CR-M4", + "nozzle_diameter": "0.4", + "machine_tech": "FFF", + "family": "Creality", + "bed_model": "creality_crm4_buildplate_model.stl", + "bed_texture": "creality_crm4_buildplate_texture.png", + "hotend_model": "", + "default_materials": "Creality Generic PLA;Creality Generic PETG;Creality Generic ABS" +} diff --git a/resources/profiles/Creality/machine/Creality K1 SE 0.4 nozzle.json b/resources/profiles/Creality/machine/Creality K1 SE 0.4 nozzle.json new file mode 100644 index 00000000000..f1e812eb68c --- /dev/null +++ b/resources/profiles/Creality/machine/Creality K1 SE 0.4 nozzle.json @@ -0,0 +1,134 @@ +{ + "type": "machine", + "setting_id": "GM001", + "name": "Creality K1 SE 0.4 nozzle", + "from": "system", + "instantiation": "true", + "inherits": "fdm_creality_common", + "printer_model": "Creality K1 SE", + "gcode_flavor": "klipper", + "default_print_profile": "0.20mm Fast @Creality K1 SE 0.4", + "nozzle_diameter": [ + "0.4" + ], + "printer_variant": "0.4", + "printable_area": [ + "0x0", + "220x0", + "220x220", + "0x220" + ], + "printable_height": "250", + "nozzle_type": "hardened_steel", + "auxiliary_fan": "0", + "support_air_filtration": "0", + "support_multi_bed_types": "0", + "machine_max_acceleration_e": [ + "5000", + "5000" + ], + "machine_max_acceleration_extruding": [ + "20000", + "20000" + ], + "machine_max_acceleration_retracting": [ + "5000", + "5000" + ], + "machine_max_acceleration_travel": [ + "20000", + "20000" + ], + "machine_max_acceleration_x": [ + "20000", + "20000" + ], + "machine_max_acceleration_y": [ + "20000", + "20000" + ], + "machine_max_acceleration_z": [ + "500", + "500" + ], + "machine_max_speed_e": [ + "100", + "100" + ], + "machine_max_speed_x": [ + "800", + "800" + ], + "machine_max_speed_y": [ + "800", + "800" + ], + "machine_max_speed_z": [ + "30", + "30" + ], + "machine_max_jerk_e": [ + "10", + "10" + ], + "machine_max_jerk_x": [ + "20", + "20" + ], + "machine_max_jerk_y": [ + "20", + "20" + ], + "machine_max_jerk_z": [ + "5", + "5" + ], + "max_layer_height": [ + "0.32" + ], + "min_layer_height": [ + "0.08" + ], + "printer_settings_id": "Creality", + "retraction_minimum_travel": [ + "1" + ], + "retract_before_wipe": [ + "70%" + ], + "retraction_length": [ + "0.8" + ], + "retract_length_toolchange": [ + "0" + ], + "retraction_speed": [ + "40" + ], + "deretraction_speed": [ + "40" + ], + "extruder_clearance_height_to_lid": "101", + "extruder_clearance_height_to_rod": "45", + "extruder_clearance_radius": "45", + "z_hop": [ + "0.4" + ], + "wipe_distance": [ + "2" + ], + "single_extruder_multi_material": "1", + "manual_filament_change": "0", + "machine_pause_gcode": "PAUSE", + "default_filament_profile": [ + "Creality Generic PLA @K1-all" + ], + "machine_start_gcode": "M140 S0\nM104 S0 \nSTART_PRINT EXTRUDER_TEMP=[nozzle_temperature_initial_layer] BED_TEMP=[bed_temperature_initial_layer_single]", + "machine_end_gcode": "END_PRINT", + "scan_first_layer": "0", + "thumbnails_format": "PNG", + "thumbnails": [ + "300x300", + "96x96" + ] +} \ No newline at end of file diff --git a/resources/profiles/Creality/machine/Creality K1 SE.json b/resources/profiles/Creality/machine/Creality K1 SE.json new file mode 100644 index 00000000000..e8a6d9829f0 --- /dev/null +++ b/resources/profiles/Creality/machine/Creality K1 SE.json @@ -0,0 +1,12 @@ +{ + "type": "machine_model", + "name": "Creality K1 SE", + "model_id": "Creality-K1-SE", + "nozzle_diameter": "0.4", + "machine_tech": "FFF", + "family": "Creality", + "bed_model": "creality_k1se_buildplate_model.stl", + "bed_texture": "creality_k1se_buildplate_texture.png", + "hotend_model": "", + "default_materials": "Creality Generic ABS @K1-all;Creality Generic ASA @K1-all;Creality Generic PA-CF @K1-all;Creality Generic PC @K1-all;Creality Generic PETG @K1-all;Creality Generic PLA @K1-all;Creality Generic PLA High Speed @K1-all;Creality Generic PLA Matte @K1-all;Creality Generic PLA Silk @K1-all;Creality Generic PLA-CF @K1-all;Creality Generic TPU @K1-all" +} \ No newline at end of file diff --git a/resources/profiles/Creality/machine/Creality K2 Plus 0.2 nozzle.json b/resources/profiles/Creality/machine/Creality K2 Plus 0.2 nozzle.json new file mode 100644 index 00000000000..b88686c8c2a --- /dev/null +++ b/resources/profiles/Creality/machine/Creality K2 Plus 0.2 nozzle.json @@ -0,0 +1,134 @@ +{ + "type": "machine", + "setting_id": "GM001", + "name": "Creality K2 Plus 0.2 nozzle", + "from": "system", + "instantiation": "true", + "inherits": "fdm_creality_common", + "printer_model": "Creality K2 Plus", + "gcode_flavor": "klipper", + "default_print_profile": "0.14mm Optimal @Creality K2 Plus 0.2 nozzle", + "nozzle_diameter": [ + "0.2" + ], + "printer_variant": "0.2", + "printable_area": [ + "0x0", + "350x0", + "350x350", + "0x350" + ], + "printable_height": "350", + "nozzle_type": "hardened_steel", + "auxiliary_fan": "1", + "support_air_filtration": "1", + "support_multi_bed_types": "0", + "machine_max_acceleration_e": [ + "5000", + "5000" + ], + "machine_max_acceleration_extruding": [ + "30000", + "30000" + ], + "machine_max_acceleration_retracting": [ + "5000", + "5000" + ], + "machine_max_acceleration_travel": [ + "30000", + "30000" + ], + "machine_max_acceleration_x": [ + "30000", + "30000" + ], + "machine_max_acceleration_y": [ + "30000", + "30000" + ], + "machine_max_acceleration_z": [ + "5000", + "5000" + ], + "machine_max_speed_e": [ + "50", + "50" + ], + "machine_max_speed_x": [ + "800", + "800" + ], + "machine_max_speed_y": [ + "800", + "800" + ], + "machine_max_speed_z": [ + "10", + "10" + ], + "machine_max_jerk_e": [ + "10", + "10" + ], + "machine_max_jerk_x": [ + "20", + "20" + ], + "machine_max_jerk_y": [ + "20", + "20" + ], + "machine_max_jerk_z": [ + "5", + "5" + ], + "max_layer_height": [ + "0.3" + ], + "min_layer_height": [ + "0.08" + ], + "printer_settings_id": "Creality", + "retraction_minimum_travel": [ + "2" + ], + "retract_before_wipe": [ + "70%" + ], + "retraction_length": [ + "0.5" + ], + "retract_length_toolchange": [ + "0" + ], + "retraction_speed": [ + "40" + ], + "deretraction_speed": [ + "40" + ], + "extruder_clearance_height_to_lid": "118", + "extruder_clearance_height_to_rod": "24", + "extruder_clearance_radius": "64", + "z_hop": [ + "0.4" + ], + "wipe_distance": [ + "1" + ], + "single_extruder_multi_material": "1", + "manual_filament_change": "0", + "default_filament_profile": [ + "Creality Generic PLA @K2-all" + ], + "machine_start_gcode":"M140 S0\nM104 S0 \nSTART_PRINT EXTRUDER_TEMP=[nozzle_temperature_initial_layer] BED_TEMP=[bed_temperature_initial_layer_single]\nT[initial_no_support_extruder]\nM204 S2000\nG1 Z3 F600\nM83\nG1 Y150 F12000\nG1 X0 F12000\nG1 Z0.2 F600\nG1 X0 Y150 F6000\nG1 X0 Y0 E15 F1500\nG1 X150 Y0 E15 F1500\nG92 E0\nG1 Z1 F600", + "machine_end_gcode": "END_PRINT", + "machine_pause_gcode": "PAUSE", + "scan_first_layer": "0", + "thumbnails_format":"PNG", + "thumbnails": [ + "300x300", + "96x96" + ] +} \ No newline at end of file diff --git a/resources/profiles/Creality/machine/Creality K2 Plus 0.4 nozzle.json b/resources/profiles/Creality/machine/Creality K2 Plus 0.4 nozzle.json new file mode 100644 index 00000000000..68417eebeb4 --- /dev/null +++ b/resources/profiles/Creality/machine/Creality K2 Plus 0.4 nozzle.json @@ -0,0 +1,134 @@ +{ + "type": "machine", + "setting_id": "GM001", + "name": "Creality K2 Plus 0.4 nozzle", + "from": "system", + "instantiation": "true", + "inherits": "fdm_creality_common", + "printer_model": "Creality K2 Plus", + "gcode_flavor": "klipper", + "default_print_profile": "0.16mm Optimal @Creality K2 Plus 0.4 nozzle", + "nozzle_diameter": [ + "0.4" + ], + "printer_variant": "0.4", + "printable_area": [ + "0x0", + "350x0", + "350x350", + "0x350" + ], + "printable_height": "350", + "nozzle_type": "hardened_steel", + "auxiliary_fan": "1", + "support_air_filtration": "1", + "support_multi_bed_types": "0", + "machine_max_acceleration_e": [ + "5000", + "5000" + ], + "machine_max_acceleration_extruding": [ + "30000", + "30000" + ], + "machine_max_acceleration_retracting": [ + "5000", + "5000" + ], + "machine_max_acceleration_travel": [ + "30000", + "30000" + ], + "machine_max_acceleration_x": [ + "30000", + "30000" + ], + "machine_max_acceleration_y": [ + "30000", + "30000" + ], + "machine_max_acceleration_z": [ + "5000", + "5000" + ], + "machine_max_speed_e": [ + "50", + "50" + ], + "machine_max_speed_x": [ + "800", + "800" + ], + "machine_max_speed_y": [ + "800", + "800" + ], + "machine_max_speed_z": [ + "10", + "10" + ], + "machine_max_jerk_e": [ + "10", + "10" + ], + "machine_max_jerk_x": [ + "20", + "20" + ], + "machine_max_jerk_y": [ + "20", + "20" + ], + "machine_max_jerk_z": [ + "5", + "5" + ], + "max_layer_height": [ + "0.3" + ], + "min_layer_height": [ + "0.08" + ], + "printer_settings_id": "Creality", + "retraction_minimum_travel": [ + "2" + ], + "retract_before_wipe": [ + "70%" + ], + "retraction_length": [ + "0.5" + ], + "retract_length_toolchange": [ + "0" + ], + "retraction_speed": [ + "40" + ], + "deretraction_speed": [ + "40" + ], + "extruder_clearance_height_to_lid": "118", + "extruder_clearance_height_to_rod": "24", + "extruder_clearance_radius": "64", + "z_hop": [ + "0.4" + ], + "wipe_distance": [ + "1" + ], + "single_extruder_multi_material": "1", + "manual_filament_change": "0", + "default_filament_profile": [ + "Creality Generic PLA @K2-all" + ], + "machine_start_gcode":"M140 S0\nM104 S0 \nSTART_PRINT EXTRUDER_TEMP=[nozzle_temperature_initial_layer] BED_TEMP=[bed_temperature_initial_layer_single]\nT[initial_no_support_extruder]\nM204 S2000\nG1 Z3 F600\nM83\nG1 Y150 F12000\nG1 X0 F12000\nG1 Z0.2 F600\nG1 X0 Y150 F6000\nG1 X0 Y0 E15 F1500\nG1 X150 Y0 E15 F1500\nG92 E0\nG1 Z1 F600", + "machine_end_gcode": "END_PRINT", + "machine_pause_gcode": "PAUSE", + "scan_first_layer": "0", + "thumbnails_format":"PNG", + "thumbnails": [ + "300x300", + "96x96" + ] +} \ No newline at end of file diff --git a/resources/profiles/Creality/machine/Creality K2 Plus 0.6 nozzle.json b/resources/profiles/Creality/machine/Creality K2 Plus 0.6 nozzle.json new file mode 100644 index 00000000000..2ad329e24e7 --- /dev/null +++ b/resources/profiles/Creality/machine/Creality K2 Plus 0.6 nozzle.json @@ -0,0 +1,134 @@ +{ + "type": "machine", + "setting_id": "GM001", + "name": "Creality K2 Plus 0.6 nozzle", + "from": "system", + "instantiation": "true", + "inherits": "fdm_creality_common", + "printer_model": "Creality K2 Plus", + "gcode_flavor": "klipper", + "default_print_profile": "0.24mm Optimal @Creality K2 Plus 0.6 nozzle", + "nozzle_diameter": [ + "0.6" + ], + "printer_variant": "0.6", + "printable_area": [ + "0x0", + "350x0", + "350x350", + "0x350" + ], + "printable_height": "350", + "nozzle_type": "hardened_steel", + "auxiliary_fan": "1", + "support_air_filtration": "1", + "support_multi_bed_types": "0", + "machine_max_acceleration_e": [ + "5000", + "5000" + ], + "machine_max_acceleration_extruding": [ + "30000", + "30000" + ], + "machine_max_acceleration_retracting": [ + "5000", + "5000" + ], + "machine_max_acceleration_travel": [ + "30000", + "30000" + ], + "machine_max_acceleration_x": [ + "30000", + "30000" + ], + "machine_max_acceleration_y": [ + "30000", + "30000" + ], + "machine_max_acceleration_z": [ + "5000", + "5000" + ], + "machine_max_speed_e": [ + "50", + "50" + ], + "machine_max_speed_x": [ + "800", + "800" + ], + "machine_max_speed_y": [ + "800", + "800" + ], + "machine_max_speed_z": [ + "10", + "10" + ], + "machine_max_jerk_e": [ + "10", + "10" + ], + "machine_max_jerk_x": [ + "20", + "20" + ], + "machine_max_jerk_y": [ + "20", + "20" + ], + "machine_max_jerk_z": [ + "5", + "5" + ], + "max_layer_height": [ + "0.42" + ], + "min_layer_height": [ + "0.08" + ], + "printer_settings_id": "Creality", + "retraction_minimum_travel": [ + "2" + ], + "retract_before_wipe": [ + "70%" + ], + "retraction_length": [ + "0.5" + ], + "retract_length_toolchange": [ + "0" + ], + "retraction_speed": [ + "40" + ], + "deretraction_speed": [ + "40" + ], + "extruder_clearance_height_to_lid": "118", + "extruder_clearance_height_to_rod": "24", + "extruder_clearance_radius": "64", + "z_hop": [ + "0.4" + ], + "wipe_distance": [ + "1" + ], + "single_extruder_multi_material": "1", + "manual_filament_change": "0", + "default_filament_profile": [ + "Creality Generic PLA @K2-all" + ], + "machine_start_gcode":"M140 S0\nM104 S0 \nSTART_PRINT EXTRUDER_TEMP=[nozzle_temperature_initial_layer] BED_TEMP=[bed_temperature_initial_layer_single]\nT[initial_no_support_extruder]\nM204 S2000\nG1 Z3 F600\nM83\nG1 Y150 F12000\nG1 X0 F12000\nG1 Z0.2 F600\nG1 X0 Y150 F6000\nG1 X0 Y0 E15 F1500\nG1 X150 Y0 E15 F1500\nG92 E0\nG1 Z1 F600", + "machine_end_gcode": "END_PRINT", + "machine_pause_gcode": "PAUSE", + "scan_first_layer": "0", + "thumbnails_format": "PNG", + "thumbnails": [ + "300x300", + "96x96" + ] +} diff --git a/resources/profiles/Creality/machine/Creality K2 Plus 0.8 nozzle.json b/resources/profiles/Creality/machine/Creality K2 Plus 0.8 nozzle.json new file mode 100644 index 00000000000..bdd7bb0473c --- /dev/null +++ b/resources/profiles/Creality/machine/Creality K2 Plus 0.8 nozzle.json @@ -0,0 +1,134 @@ +{ + "type": "machine", + "setting_id": "GM001", + "name": "Creality K2 Plus 0.8 nozzle", + "from": "system", + "instantiation": "true", + "inherits": "fdm_creality_common", + "printer_model": "Creality K2 Plus", + "gcode_flavor": "klipper", + "default_print_profile": "0.32mm Optimal @Creality K2 Plus 0.8 nozzle", + "nozzle_diameter": [ + "0.8" + ], + "printer_variant": "0.8", + "printable_area": [ + "0x0", + "350x0", + "350x350", + "0x350" + ], + "printable_height": "350", + "nozzle_type": "hardened_steel", + "auxiliary_fan": "1", + "support_air_filtration": "1", + "support_multi_bed_types": "0", + "machine_max_acceleration_e": [ + "5000", + "5000" + ], + "machine_max_acceleration_extruding": [ + "30000", + "30000" + ], + "machine_max_acceleration_retracting": [ + "5000", + "5000" + ], + "machine_max_acceleration_travel": [ + "30000", + "30000" + ], + "machine_max_acceleration_x": [ + "30000", + "30000" + ], + "machine_max_acceleration_y": [ + "30000", + "30000" + ], + "machine_max_acceleration_z": [ + "5000", + "5000" + ], + "machine_max_speed_e": [ + "50", + "50" + ], + "machine_max_speed_x": [ + "800", + "800" + ], + "machine_max_speed_y": [ + "800", + "800" + ], + "machine_max_speed_z": [ + "10", + "10" + ], + "machine_max_jerk_e": [ + "10", + "10" + ], + "machine_max_jerk_x": [ + "20", + "20" + ], + "machine_max_jerk_y": [ + "20", + "20" + ], + "machine_max_jerk_z": [ + "5", + "5" + ], + "max_layer_height": [ + "0.56" + ], + "min_layer_height": [ + "0.08" + ], + "printer_settings_id": "Creality", + "retraction_minimum_travel": [ + "2" + ], + "retract_before_wipe": [ + "70%" + ], + "retraction_length": [ + "0.5" + ], + "retract_length_toolchange": [ + "0" + ], + "retraction_speed": [ + "40" + ], + "deretraction_speed": [ + "40" + ], + "extruder_clearance_height_to_lid": "118", + "extruder_clearance_height_to_rod": "24", + "extruder_clearance_radius": "64", + "z_hop": [ + "0.4" + ], + "wipe_distance": [ + "1" + ], + "single_extruder_multi_material": "1", + "manual_filament_change": "0", + "default_filament_profile": [ + "Creality Generic PLA @K2-all" + ], + "machine_start_gcode":"M140 S0\nM104 S0 \nSTART_PRINT EXTRUDER_TEMP=[nozzle_temperature_initial_layer] BED_TEMP=[bed_temperature_initial_layer_single]\nT[initial_no_support_extruder]\nM204 S2000\nG1 Z3 F600\nM83\nG1 Y150 F12000\nG1 X0 F12000\nG1 Z0.2 F600\nG1 X0 Y150 F6000\nG1 X0 Y0 E15 F1500\nG1 X150 Y0 E15 F1500\nG92 E0\nG1 Z1 F600", + "machine_end_gcode": "END_PRINT", + "machine_pause_gcode": "PAUSE", + "scan_first_layer": "0", + "thumbnails_format":"PNG", + "thumbnails": [ + "300x300", + "96x96" + ] +} diff --git a/resources/profiles/Creality/machine/Creality K2 Plus.json b/resources/profiles/Creality/machine/Creality K2 Plus.json new file mode 100644 index 00000000000..7cb68b5a44f --- /dev/null +++ b/resources/profiles/Creality/machine/Creality K2 Plus.json @@ -0,0 +1,12 @@ +{ + "type": "machine_model", + "name": "Creality K2 Plus", + "model_id": "Creality-K2-Plus", + "nozzle_diameter": "0.2;0.4;0.6;0.8", + "machine_tech": "FFF", + "family": "Creality", + "bed_model": "creality_k2plus_buildplate_model.stl", + "bed_texture": "creality_k2plus_buildplate_texture.png", + "hotend_model": "", + "default_materials": "Creality Generic ABS @K2-all;Creality Generic ASA @K2-all;Creality Generic PETG @K2-all;Creality Generic PLA @K2-all;Creality Generic PLA High Speed @K2-all;Creality Generic PLA Matte @K2-all;Creality Generic PLA Silk @K2-all" +} \ No newline at end of file diff --git a/resources/profiles/Creality/machine/fdm_machine_common.json b/resources/profiles/Creality/machine/fdm_machine_common.json index f24b0ac3131..6bf00c74184 100644 --- a/resources/profiles/Creality/machine/fdm_machine_common.json +++ b/resources/profiles/Creality/machine/fdm_machine_common.json @@ -113,8 +113,6 @@ "1" ], "z_hop_types": "Normal Lift", - "support_air_filtration": "0", - "support_chamber_temp_control": "0", "before_layer_change_gcode": ";BEFORE_LAYER_CHANGE\n;[layer_z]\nG92 E0\n", "default_print_profile": "0.16mm Optimal @Bambu Lab X1 Carbon 0.4 nozzle", "machine_start_gcode": "G0 Z20 F9000\nG92 E0; G1 E-10 F1200\nG28\nM970 Q1 A10 B10 C130 K0\nM970 Q1 A10 B131 C250 K1\nM974 Q1 S1 P0\nM970 Q0 A10 B10 C130 H20 K0\nM970 Q0 A10 B131 C250 K1\nM974 Q0 S1 P0\nM220 S100 ;Reset Feedrate\nM221 S100 ;Reset Flowrate\nG29 ;Home\nG90;\nG92 E0 ;Reset Extruder \nG1 Z2.0 F3000 ;Move Z Axis up \nG1 X10.1 Y20 Z0.28 F5000.0 ;Move to start position\nM109 S205;\nG1 X10.1 Y200.0 Z0.28 F1500.0 E15 ;Draw the first line\nG1 X10.4 Y200.0 Z0.28 F5000.0 ;Move to side a little\nG1 X10.4 Y20 Z0.28 F1500.0 E30 ;Draw the second line\nG92 E0 ;Reset Extruder \nG1 X110 Y110 Z2.0 F3000 ;Move Z Axis up", diff --git a/resources/profiles/Creality/process/0.08mm SuperDetail @Creality K2 Plus 0.2 nozzle.json b/resources/profiles/Creality/process/0.08mm SuperDetail @Creality K2 Plus 0.2 nozzle.json new file mode 100644 index 00000000000..37987434961 --- /dev/null +++ b/resources/profiles/Creality/process/0.08mm SuperDetail @Creality K2 Plus 0.2 nozzle.json @@ -0,0 +1,109 @@ +{ + "type": "process", + "setting_id": "GP004", + "name": "0.08mm SuperDetail @Creality K2 Plus 0.2 nozzle", + "from": "system", + "inherits": "fdm_process_common_klipper", + "instantiation": "true", + "max_travel_detour_distance": "0", + "bottom_surface_pattern": "monotonic", + "bottom_shell_layers": "5", + "bottom_shell_thickness": "0", + "bridge_flow": "1", + "bridge_speed": "25", + "internal_bridge_speed": "70", + "brim_width": "5", + "brim_object_gap": "0.1", + "compatible_printers": [ + "Creality K2 Plus 0.2 nozzle" + ], + "default_acceleration": "12000", + "bridge_no_support": "0", + "draft_shield": "disabled", + "elefant_foot_compensation": "0.15", + "outer_wall_line_width": "0.22", + "outer_wall_speed": "100", + "outer_wall_acceleration": "5000", + "inner_wall_acceleration": "5000", + "wall_infill_order": "inner wall/outer wall/infill", + "line_width": "0.22", + "infill_direction": "45", + "sparse_infill_density": "15%", + "sparse_infill_pattern": "crosshatch", + "internal_bridge_support_thickness": "0.8", + "initial_layer_acceleration": "500", + "initial_layer_line_width": "0.25", + "initial_layer_print_height": "0.1", + "initial_layer_speed": "40", + "gap_infill_speed": "50", + "infill_combination": "0", + "sparse_infill_line_width": "0.25", + "infill_wall_overlap": "30", + "sparse_infill_speed": "120", + "interface_shells": "0", + "ironing_flow": "10%", + "ironing_spacing": "0.15", + "ironing_speed": "30", + "ironing_type": "no ironing", + "layer_height": "0.08", + "reduce_infill_retraction": "1", + "filename_format": "{input_filename_base}_{filament_type[initial_tool]}_{print_time}.gcode", + "detect_overhang_wall": "1", + "overhang_1_4_speed": "0", + "overhang_2_4_speed": "50", + "overhang_3_4_speed": "30", + "overhang_4_4_speed": "10", + "only_one_wall_top": "1", + "inner_wall_line_width": "0.25", + "inner_wall_speed": "150", + "wall_loops": "4", + "raft_layers": "0", + "seam_position": "aligned", + "skirt_distance": "2", + "skirt_height": "1", + "skirt_loops": "0", + "minimum_sparse_infill_area": "15", + "internal_solid_infill_line_width": "0.22", + "internal_solid_infill_speed": "150", + "initial_layer_infill_speed": "60", + "standby_temperature_delta": "-5", + "enable_support": "0", + "resolution": "0.012", + "support_type": "normal(auto)", + "support_style": "default", + "support_on_build_plate_only": "0", + "support_top_z_distance": "0.2", + "support_bottom_z_distance": "0.2", + "support_filament": "0", + "support_line_width": "0.2", + "support_interface_loop_pattern": "0", + "support_interface_filament": "0", + "support_interface_top_layers": "2", + "support_interface_bottom_layers": "2", + "support_interface_spacing": "0.5", + "support_expansion": "0", + "support_interface_speed": "80", + "support_base_pattern": "default", + "support_base_pattern_spacing": "2.5", + "support_speed": "150", + "support_threshold_angle": "30", + "support_object_xy_distance": "0.35", + "tree_support_branch_diameter": "2", + "tree_support_branch_angle": "45", + "tree_support_wall_count": "1", + "detect_thin_wall": "0", + "top_surface_pattern": "monotonicline", + "top_surface_line_width": "0.22", + "top_surface_acceleration": "2000", + "top_surface_speed": "100", + "top_shell_layers": "7", + "top_shell_thickness": "0.8", + "travel_acceleration": "12000", + "travel_speed": "500", + "enable_prime_tower": "0", + "wipe_tower_no_sparse_layers": "0", + "prime_tower_width": "60", + "xy_hole_compensation": "0", + "xy_contour_compensation": "0", + "gcode_label_objects": "0" +} \ No newline at end of file diff --git a/resources/profiles/Creality/process/0.08mm SuperDetail @Creality K2 Plus 0.4 nozzle.json b/resources/profiles/Creality/process/0.08mm SuperDetail @Creality K2 Plus 0.4 nozzle.json new file mode 100644 index 00000000000..7304c173ae0 --- /dev/null +++ b/resources/profiles/Creality/process/0.08mm SuperDetail @Creality K2 Plus 0.4 nozzle.json @@ -0,0 +1,109 @@ +{ + "type": "process", + "setting_id": "GP004", + "name": "0.08mm SuperDetail @Creality K2 Plus 0.4 nozzle", + "from": "system", + "inherits": "fdm_process_common_klipper", + "instantiation": "true", + "max_travel_detour_distance": "0", + "bottom_surface_pattern": "monotonic", + "bottom_shell_layers": "7", + "bottom_shell_thickness": "0", + "bridge_flow": "1", + "bridge_speed": "50", + "internal_bridge_speed": "150%", + "brim_width": "5", + "brim_object_gap": "0.1", + "compatible_printers": [ + "Creality K2 Plus 0.4 nozzle" + ], + "default_acceleration": "12000", + "bridge_no_support": "0", + "draft_shield": "disabled", + "elefant_foot_compensation": "0.15", + "outer_wall_line_width": "0.42", + "outer_wall_speed": "200", + "outer_wall_acceleration": "5000", + "inner_wall_acceleration": "5000", + "wall_infill_order": "inner wall/outer wall/infill", + "line_width": "0.42", + "infill_direction": "45", + "sparse_infill_density": "15%", + "sparse_infill_pattern": "crosshatch", + "internal_bridge_support_thickness": "0.8", + "initial_layer_acceleration": "2000", + "initial_layer_line_width": "0.5", + "initial_layer_print_height": "0.2", + "initial_layer_speed": "60", + "gap_infill_speed": "250", + "infill_combination": "0", + "sparse_infill_line_width": "0.45", + "infill_wall_overlap": "30%", + "sparse_infill_speed": "350", + "interface_shells": "0", + "ironing_flow": "8%", + "ironing_spacing": "0.15", + "ironing_speed": "30", + "ironing_type": "no ironing", + "layer_height": "0.08", + "reduce_infill_retraction": "1", + "filename_format": "{input_filename_base}_{filament_type[initial_tool]}_{print_time}.gcode", + "detect_overhang_wall": "1", + "overhang_1_4_speed": "0", + "overhang_2_4_speed": "50", + "overhang_3_4_speed": "30", + "overhang_4_4_speed": "10", + "only_one_wall_top": "1", + "inner_wall_line_width": "0.45", + "inner_wall_speed": "350", + "wall_loops": "2", + "raft_layers": "0", + "seam_position": "aligned", + "skirt_distance": "2", + "skirt_height": "1", + "skirt_loops": "0", + "minimum_sparse_infill_area": "15", + "internal_solid_infill_line_width": "0.42", + "internal_solid_infill_speed": "250", + "initial_layer_infill_speed": "105", + "standby_temperature_delta": "-5", + "enable_support": "0", + "resolution": "0.012", + "support_type": "normal(auto)", + "support_style": "default", + "support_on_build_plate_only": "0", + "support_top_z_distance": "0.2", + "support_bottom_z_distance": "0.2", + "support_filament": "0", + "support_line_width": "0.4", + "support_interface_loop_pattern": "0", + "support_interface_filament": "0", + "support_interface_top_layers": "2", + "support_interface_bottom_layers": "2", + "support_interface_spacing": "0.5", + "support_expansion": "0", + "support_interface_speed": "80", + "support_base_pattern": "default", + "support_base_pattern_spacing": "2.5", + "support_speed": "150", + "support_threshold_angle": "30", + "support_object_xy_distance": "0.35", + "tree_support_branch_diameter": "2", + "tree_support_branch_angle": "45", + "tree_support_wall_count": "1", + "detect_thin_wall": "0", + "top_surface_pattern": "monotonicline", + "top_surface_line_width": "0.42", + "top_surface_acceleration": "5000", + "top_surface_speed": "200", + "top_shell_layers": "9", + "top_shell_thickness": "0.8", + "travel_acceleration": "12000", + "travel_speed": "500", + "enable_prime_tower": "1", + "wipe_tower_no_sparse_layers": "0", + "prime_tower_width": "40", + "xy_hole_compensation": "0", + "xy_contour_compensation": "0", + "gcode_label_objects": "0" +} \ No newline at end of file diff --git a/resources/profiles/Creality/process/0.10mm HighDetail @Creality CR-M4.json b/resources/profiles/Creality/process/0.10mm HighDetail @Creality CR-M4.json new file mode 100644 index 00000000000..fe86b26a368 --- /dev/null +++ b/resources/profiles/Creality/process/0.10mm HighDetail @Creality CR-M4.json @@ -0,0 +1,103 @@ +{ + "type": "process", + "setting_id": "GP004", + "name": "0.10mm HighDetail @Creality CR-M4", + "from": "system", + "inherits": "fdm_process_creality_common", + "instantiation": "true", + "bottom_surface_pattern": "monotonic", + "bottom_shell_layers": "4", + "bottom_shell_thickness": "0", + "bridge_flow": "1", + "bridge_speed": "15", + "brim_width": ".4", + "brim_object_gap": "0.1", + "default_acceleration": "500", + "top_surface_acceleration": "500", + "bridge_no_support": "0", + "draft_shield": "disabled", + "elefant_foot_compensation": "0", + "enable_arc_fitting": "0", + "outer_wall_line_width": "0.4", + "wall_infill_order": "inner wall/outer wall/infill", + "line_width": "0.4", + "infill_direction": "45", + "sparse_infill_density": "15%", + "sparse_infill_pattern": "crosshatch", + "initial_layer_acceleration": "500", + "travel_acceleration": "700", + "inner_wall_acceleration": "500", + "initial_layer_line_width": "0.44", + "initial_layer_print_height": "0.1", + "infill_combination": "0", + "sparse_infill_line_width": "0.4", + "infill_wall_overlap": "30%", + "interface_shells": "0", + "ironing_flow": "10%", + "ironing_spacing": "0.1", + "ironing_speed": "20", + "ironing_type": "no ironing", + "layer_height": "0.1", + "reduce_infill_retraction": "1", + "filename_format": "{input_filename_base}_{filament_type[initial_tool]}_{print_time}.gcode", + "detect_overhang_wall": "1", + "overhang_1_4_speed": "0", + "overhang_2_4_speed": "50", + "overhang_3_4_speed": "35", + "overhang_4_4_speed": "10", + "inner_wall_line_width": "0.4", + "wall_loops": "2", + "raft_layers": "0", + "seam_position": "nearest", + "skirt_distance": "3", + "skirt_height": "1", + "skirt_loops": "1", + "minimum_sparse_infill_area": "15", + "internal_solid_infill_line_width": "0.4", + "spiral_mode": "0", + "standby_temperature_delta": "-5", + "enable_support": "0", + "resolution": "0.012", + "support_type": "normal(auto)", + "support_style": "default", + "support_on_build_plate_only": "0", + "support_top_z_distance": "0.2", + "support_filament": "0", + "support_line_width": "0.4", + "support_interface_loop_pattern": "0", + "support_interface_filament": "0", + "support_interface_top_layers": "2", + "support_interface_bottom_layers": "2", + "support_interface_spacing": "1.33", + "support_interface_speed": "25", + "support_base_pattern": "rectilinear", + "support_base_pattern_spacing": "2.5", + "support_speed": "60", + "support_threshold_angle": "60", + "support_object_xy_distance": "0.8", + "tree_support_branch_angle": "45", + "tree_support_wall_count": "0", + "detect_thin_wall": "0", + "top_surface_pattern": "monotonicline", + "top_surface_line_width": "0.4", + "top_shell_layers": "4", + "top_shell_thickness": "0.8", + "initial_layer_speed": "15", + "inital_travel_speed": "25", + "initial_layer_infill_speed": "15", + "outer_wall_speed": "25", + "inner_wall_speed": "25", + "internal_solid_infill_speed": "50", + "top_surface_speed": "30", + "gap_infill_speed": "30", + "sparse_infill_speed": "50", + "travel_speed": "100", + "enable_prime_tower": "0", + "wipe_tower_no_sparse_layers": "0", + "prime_tower_width": "60", + "xy_hole_compensation": "0", + "xy_contour_compensation": "0", + "compatible_printers": [ + "Creality CR-M4 0.4 nozzle" + ] +} diff --git a/resources/profiles/Creality/process/0.10mm HighDetail @Creality K2 Plus 0.2 nozzle.json b/resources/profiles/Creality/process/0.10mm HighDetail @Creality K2 Plus 0.2 nozzle.json new file mode 100644 index 00000000000..bffb09ca7c9 --- /dev/null +++ b/resources/profiles/Creality/process/0.10mm HighDetail @Creality K2 Plus 0.2 nozzle.json @@ -0,0 +1,109 @@ +{ + "type": "process", + "setting_id": "GP004", + "name": "0.10mm HighDetail @Creality K2 Plus 0.2 nozzle", + "from": "system", + "inherits": "fdm_process_common_klipper", + "instantiation": "true", + "max_travel_detour_distance": "0", + "bottom_surface_pattern": "monotonic", + "bottom_shell_layers": "5", + "bottom_shell_thickness": "0", + "bridge_flow": "1", + "bridge_speed": "25", + "internal_bridge_speed": "70", + "brim_width": "5", + "brim_object_gap": "0.1", + "compatible_printers": [ + "Creality K2 Plus 0.2 nozzle" + ], + "default_acceleration": "12000", + "bridge_no_support": "0", + "draft_shield": "disabled", + "elefant_foot_compensation": "0.15", + "outer_wall_line_width": "0.22", + "outer_wall_speed": "100", + "outer_wall_acceleration": "5000", + "inner_wall_acceleration": "5000", + "wall_infill_order": "inner wall/outer wall/infill", + "line_width": "0.22", + "infill_direction": "45", + "sparse_infill_density": "15%", + "sparse_infill_pattern": "crosshatch", + "internal_bridge_support_thickness": "0.8", + "initial_layer_acceleration": "500", + "initial_layer_line_width": "0.25", + "initial_layer_print_height": "0.1", + "initial_layer_speed": "40", + "gap_infill_speed": "50", + "infill_combination": "0", + "sparse_infill_line_width": "0.25", + "infill_wall_overlap": "30", + "sparse_infill_speed": "120", + "interface_shells": "0", + "ironing_flow": "10%", + "ironing_spacing": "0.15", + "ironing_speed": "30", + "ironing_type": "no ironing", + "layer_height": "0.1", + "reduce_infill_retraction": "1", + "filename_format": "{input_filename_base}_{filament_type[initial_tool]}_{print_time}.gcode", + "detect_overhang_wall": "1", + "overhang_1_4_speed": "0", + "overhang_2_4_speed": "50", + "overhang_3_4_speed": "30", + "overhang_4_4_speed": "10", + "only_one_wall_top": "1", + "inner_wall_line_width": "0.25", + "inner_wall_speed": "150", + "wall_loops": "4", + "raft_layers": "0", + "seam_position": "aligned", + "skirt_distance": "2", + "skirt_height": "1", + "skirt_loops": "0", + "minimum_sparse_infill_area": "15", + "internal_solid_infill_line_width": "0.22", + "internal_solid_infill_speed": "150", + "initial_layer_infill_speed": "60", + "standby_temperature_delta": "-5", + "enable_support": "0", + "resolution": "0.012", + "support_type": "normal(auto)", + "support_style": "default", + "support_on_build_plate_only": "0", + "support_top_z_distance": "0.2", + "support_bottom_z_distance": "0.2", + "support_filament": "0", + "support_line_width": "0.2", + "support_interface_loop_pattern": "0", + "support_interface_filament": "0", + "support_interface_top_layers": "2", + "support_interface_bottom_layers": "2", + "support_interface_spacing": "0.5", + "support_expansion": "0", + "support_interface_speed": "80", + "support_base_pattern": "default", + "support_base_pattern_spacing": "2.5", + "support_speed": "150", + "support_threshold_angle": "30", + "support_object_xy_distance": "0.35", + "tree_support_branch_diameter": "2", + "tree_support_branch_angle": "45", + "tree_support_wall_count": "1", + "detect_thin_wall": "0", + "top_surface_pattern": "monotonicline", + "top_surface_line_width": "0.22", + "top_surface_acceleration": "2000", + "top_surface_speed": "100", + "top_shell_layers": "7", + "top_shell_thickness": "0.8", + "travel_acceleration": "12000", + "travel_speed": "500", + "enable_prime_tower": "0", + "wipe_tower_no_sparse_layers": "0", + "prime_tower_width": "60", + "xy_hole_compensation": "0", + "xy_contour_compensation": "0", + "gcode_label_objects": "0" +} \ No newline at end of file diff --git a/resources/profiles/Creality/process/0.12mm Detail @Creality K2 Plus 0.2 nozzle.json b/resources/profiles/Creality/process/0.12mm Detail @Creality K2 Plus 0.2 nozzle.json new file mode 100644 index 00000000000..437d4c16e5d --- /dev/null +++ b/resources/profiles/Creality/process/0.12mm Detail @Creality K2 Plus 0.2 nozzle.json @@ -0,0 +1,109 @@ +{ + "type": "process", + "setting_id": "GP004", + "name": "0.12mm Detail @Creality K2 Plus 0.2 nozzle", + "from": "system", + "inherits": "fdm_process_common_klipper", + "instantiation": "true", + "max_travel_detour_distance": "0", + "bottom_surface_pattern": "monotonic", + "bottom_shell_layers": "5", + "bottom_shell_thickness": "0", + "bridge_flow": "1", + "bridge_speed": "25", + "internal_bridge_speed": "70", + "brim_width": "5", + "brim_object_gap": "0.1", + "compatible_printers": [ + "Creality K2 Plus 0.2 nozzle" + ], + "default_acceleration": "12000", + "bridge_no_support": "0", + "draft_shield": "disabled", + "elefant_foot_compensation": "0.15", + "outer_wall_line_width": "0.22", + "outer_wall_speed": "100", + "outer_wall_acceleration": "5000", + "inner_wall_acceleration": "5000", + "wall_infill_order": "inner wall/outer wall/infill", + "line_width": "0.22", + "infill_direction": "45", + "sparse_infill_density": "15%", + "sparse_infill_pattern": "crosshatch", + "internal_bridge_support_thickness": "0.8", + "initial_layer_acceleration": "500", + "initial_layer_line_width": "0.25", + "initial_layer_print_height": "0.1", + "initial_layer_speed": "40", + "gap_infill_speed": "50", + "infill_combination": "0", + "sparse_infill_line_width": "0.25", + "infill_wall_overlap": "30", + "sparse_infill_speed": "120", + "interface_shells": "0", + "ironing_flow": "10%", + "ironing_spacing": "0.15", + "ironing_speed": "30", + "ironing_type": "no ironing", + "layer_height": "0.12", + "reduce_infill_retraction": "1", + "filename_format": "{input_filename_base}_{filament_type[initial_tool]}_{print_time}.gcode", + "detect_overhang_wall": "1", + "overhang_1_4_speed": "0", + "overhang_2_4_speed": "50", + "overhang_3_4_speed": "30", + "overhang_4_4_speed": "10", + "only_one_wall_top": "1", + "inner_wall_line_width": "0.25", + "inner_wall_speed": "150", + "wall_loops": "4", + "raft_layers": "0", + "seam_position": "aligned", + "skirt_distance": "2", + "skirt_height": "1", + "skirt_loops": "0", + "minimum_sparse_infill_area": "15", + "internal_solid_infill_line_width": "0.22", + "internal_solid_infill_speed": "150", + "initial_layer_infill_speed": "60", + "standby_temperature_delta": "-5", + "enable_support": "0", + "resolution": "0.012", + "support_type": "normal(auto)", + "support_style": "default", + "support_on_build_plate_only": "0", + "support_top_z_distance": "0.2", + "support_bottom_z_distance": "0.2", + "support_filament": "0", + "support_line_width": "0.2", + "support_interface_loop_pattern": "0", + "support_interface_filament": "0", + "support_interface_top_layers": "2", + "support_interface_bottom_layers": "2", + "support_interface_spacing": "0.5", + "support_expansion": "0", + "support_interface_speed": "80", + "support_base_pattern": "default", + "support_base_pattern_spacing": "2.5", + "support_speed": "150", + "support_threshold_angle": "30", + "support_object_xy_distance": "0.35", + "tree_support_branch_diameter": "2", + "tree_support_branch_angle": "45", + "tree_support_wall_count": "1", + "detect_thin_wall": "0", + "top_surface_pattern": "monotonicline", + "top_surface_line_width": "0.22", + "top_surface_acceleration": "2000", + "top_surface_speed": "100", + "top_shell_layers": "7", + "top_shell_thickness": "0.8", + "travel_acceleration": "12000", + "travel_speed": "500", + "enable_prime_tower": "0", + "wipe_tower_no_sparse_layers": "0", + "prime_tower_width": "60", + "xy_hole_compensation": "0", + "xy_contour_compensation": "0", + "gcode_label_objects": "0" +} \ No newline at end of file diff --git a/resources/profiles/Creality/process/0.12mm Detail @Creality K2 Plus 0.4 nozzle.json b/resources/profiles/Creality/process/0.12mm Detail @Creality K2 Plus 0.4 nozzle.json new file mode 100644 index 00000000000..2f262bfaeaf --- /dev/null +++ b/resources/profiles/Creality/process/0.12mm Detail @Creality K2 Plus 0.4 nozzle.json @@ -0,0 +1,109 @@ +{ + "type": "process", + "setting_id": "GP004", + "name": "0.12mm Detail @Creality K2 Plus 0.4 nozzle", + "from": "system", + "inherits": "fdm_process_common_klipper", + "instantiation": "true", + "max_travel_detour_distance": "0", + "bottom_surface_pattern": "monotonic", + "bottom_shell_layers": "5", + "bottom_shell_thickness": "0", + "bridge_flow": "1", + "bridge_speed": "50", + "internal_bridge_speed": "150%", + "brim_width": "5", + "brim_object_gap": "0.1", + "compatible_printers": [ + "Creality K2 Plus 0.4 nozzle" + ], + "default_acceleration": "12000", + "bridge_no_support": "0", + "draft_shield": "disabled", + "elefant_foot_compensation": "0.15", + "outer_wall_line_width": "0.42", + "outer_wall_speed": "200", + "outer_wall_acceleration": "5000", + "inner_wall_acceleration": "5000", + "wall_infill_order": "inner wall/outer wall/infill", + "line_width": "0.42", + "infill_direction": "45", + "sparse_infill_density": "15%", + "sparse_infill_pattern": "crosshatch", + "internal_bridge_support_thickness": "0.8", + "initial_layer_acceleration": "2000", + "initial_layer_line_width": "0.5", + "initial_layer_print_height": "0.2", + "initial_layer_speed": "60", + "gap_infill_speed": "250", + "infill_combination": "0", + "sparse_infill_line_width": "0.45", + "infill_wall_overlap": "30%", + "sparse_infill_speed": "300", + "interface_shells": "0", + "ironing_flow": "10%", + "ironing_spacing": "0.15", + "ironing_speed": "30", + "ironing_type": "no ironing", + "layer_height": "0.12", + "reduce_infill_retraction": "1", + "filename_format": "{input_filename_base}_{filament_type[initial_tool]}_{print_time}.gcode", + "detect_overhang_wall": "1", + "overhang_1_4_speed": "0", + "overhang_2_4_speed": "50", + "overhang_3_4_speed": "30", + "overhang_4_4_speed": "10", + "only_one_wall_top": "1", + "inner_wall_line_width": "0.45", + "inner_wall_speed": "300", + "wall_loops": "2", + "raft_layers": "0", + "seam_position": "aligned", + "skirt_distance": "2", + "skirt_height": "1", + "skirt_loops": "0", + "minimum_sparse_infill_area": "15", + "internal_solid_infill_line_width": "0.42", + "internal_solid_infill_speed": "250", + "initial_layer_infill_speed": "105", + "standby_temperature_delta": "-5", + "enable_support": "0", + "resolution": "0.012", + "support_type": "normal(auto)", + "support_style": "default", + "support_on_build_plate_only": "0", + "support_top_z_distance": "0.2", + "support_bottom_z_distance": "0.2", + "support_filament": "0", + "support_line_width": "0.4", + "support_interface_loop_pattern": "0", + "support_interface_filament": "0", + "support_interface_top_layers": "2", + "support_interface_bottom_layers": "2", + "support_interface_spacing": "0.5", + "support_expansion": "0", + "support_interface_speed": "80", + "support_base_pattern": "default", + "support_base_pattern_spacing": "2.5", + "support_speed": "150", + "support_threshold_angle": "30", + "support_object_xy_distance": "0.35", + "tree_support_branch_diameter": "2", + "tree_support_branch_angle": "45", + "tree_support_wall_count": "1", + "detect_thin_wall": "0", + "top_surface_pattern": "monotonicline", + "top_surface_line_width": "0.42", + "top_surface_acceleration": "5000", + "top_surface_speed": "200", + "top_shell_layers": "6", + "top_shell_thickness": "0.8", + "travel_acceleration": "12000", + "travel_speed": "500", + "enable_prime_tower": "1", + "wipe_tower_no_sparse_layers": "0", + "prime_tower_width": "40", + "xy_hole_compensation": "0", + "xy_contour_compensation": "0", + "gcode_label_objects": "0" +} \ No newline at end of file diff --git a/resources/profiles/Creality/process/0.14mm Optimal @Creality K2 Plus 0.2 nozzle.json b/resources/profiles/Creality/process/0.14mm Optimal @Creality K2 Plus 0.2 nozzle.json new file mode 100644 index 00000000000..1525dd94f67 --- /dev/null +++ b/resources/profiles/Creality/process/0.14mm Optimal @Creality K2 Plus 0.2 nozzle.json @@ -0,0 +1,109 @@ +{ + "type": "process", + "setting_id": "GP004", + "name": "0.14mm Optimal @Creality K2 Plus 0.2 nozzle", + "from": "system", + "inherits": "fdm_process_common_klipper", + "instantiation": "true", + "max_travel_detour_distance": "0", + "bottom_surface_pattern": "monotonic", + "bottom_shell_layers": "5", + "bottom_shell_thickness": "0", + "bridge_flow": "1", + "bridge_speed": "25", + "internal_bridge_speed": "70", + "brim_width": "5", + "brim_object_gap": "0.1", + "compatible_printers": [ + "Creality K2 Plus 0.2 nozzle" + ], + "default_acceleration": "12000", + "bridge_no_support": "0", + "draft_shield": "disabled", + "elefant_foot_compensation": "0.15", + "outer_wall_line_width": "0.22", + "outer_wall_speed": "100", + "outer_wall_acceleration": "5000", + "inner_wall_acceleration": "5000", + "wall_infill_order": "inner wall/outer wall/infill", + "line_width": "0.22", + "infill_direction": "45", + "sparse_infill_density": "15%", + "sparse_infill_pattern": "crosshatch", + "internal_bridge_support_thickness": "0.8", + "initial_layer_acceleration": "500", + "initial_layer_line_width": "0.25", + "initial_layer_print_height": "0.1", + "initial_layer_speed": "40", + "gap_infill_speed": "50", + "infill_combination": "0", + "sparse_infill_line_width": "0.25", + "infill_wall_overlap": "30", + "sparse_infill_speed": "120", + "interface_shells": "0", + "ironing_flow": "10%", + "ironing_spacing": "0.15", + "ironing_speed": "30", + "ironing_type": "no ironing", + "layer_height": "0.14", + "reduce_infill_retraction": "1", + "filename_format": "{input_filename_base}_{filament_type[initial_tool]}_{print_time}.gcode", + "detect_overhang_wall": "1", + "overhang_1_4_speed": "0", + "overhang_2_4_speed": "50", + "overhang_3_4_speed": "30", + "overhang_4_4_speed": "10", + "only_one_wall_top": "1", + "inner_wall_line_width": "0.25", + "inner_wall_speed": "150", + "wall_loops": "4", + "raft_layers": "0", + "seam_position": "aligned", + "skirt_distance": "2", + "skirt_height": "1", + "skirt_loops": "0", + "minimum_sparse_infill_area": "15", + "internal_solid_infill_line_width": "0.22", + "internal_solid_infill_speed": "150", + "initial_layer_infill_speed": "60", + "standby_temperature_delta": "-5", + "enable_support": "0", + "resolution": "0.012", + "support_type": "normal(auto)", + "support_style": "default", + "support_on_build_plate_only": "0", + "support_top_z_distance": "0.2", + "support_bottom_z_distance": "0.2", + "support_filament": "0", + "support_line_width": "0.2", + "support_interface_loop_pattern": "0", + "support_interface_filament": "0", + "support_interface_top_layers": "2", + "support_interface_bottom_layers": "2", + "support_interface_spacing": "0.5", + "support_expansion": "0", + "support_interface_speed": "80", + "support_base_pattern": "default", + "support_base_pattern_spacing": "2.5", + "support_speed": "150", + "support_threshold_angle": "30", + "support_object_xy_distance": "0.35", + "tree_support_branch_diameter": "2", + "tree_support_branch_angle": "45", + "tree_support_wall_count": "1", + "detect_thin_wall": "0", + "top_surface_pattern": "monotonicline", + "top_surface_line_width": "0.22", + "top_surface_acceleration": "2000", + "top_surface_speed": "100", + "top_shell_layers": "7", + "top_shell_thickness": "0.8", + "travel_acceleration": "12000", + "travel_speed": "500", + "enable_prime_tower": "0", + "wipe_tower_no_sparse_layers": "0", + "prime_tower_width": "60", + "xy_hole_compensation": "0", + "xy_contour_compensation": "0", + "gcode_label_objects": "0" +} \ No newline at end of file diff --git a/resources/profiles/Creality/process/0.15mm Detail @Creality CR-M4.json b/resources/profiles/Creality/process/0.15mm Detail @Creality CR-M4.json new file mode 100644 index 00000000000..10711b00cfa --- /dev/null +++ b/resources/profiles/Creality/process/0.15mm Detail @Creality CR-M4.json @@ -0,0 +1,103 @@ +{ + "type": "process", + "setting_id": "GP004", + "name": "0.15mm Detail @Creality CR-M4", + "from": "system", + "inherits": "fdm_process_creality_common", + "instantiation": "true", + "bottom_surface_pattern": "monotonic", + "bottom_shell_layers": "4", + "bottom_shell_thickness": "0", + "bridge_flow": "1", + "bridge_speed": "15", + "brim_width": ".4", + "brim_object_gap": "0.1", + "default_acceleration": "500", + "top_surface_acceleration": "500", + "bridge_no_support": "0", + "draft_shield": "disabled", + "elefant_foot_compensation": "0", + "enable_arc_fitting": "0", + "outer_wall_line_width": "0.4", + "wall_infill_order": "inner wall/outer wall/infill", + "line_width": "0.4", + "infill_direction": "45", + "sparse_infill_density": "15%", + "sparse_infill_pattern": "crosshatch", + "initial_layer_acceleration": "500", + "travel_acceleration": "700", + "inner_wall_acceleration": "500", + "initial_layer_line_width": "0.44", + "initial_layer_print_height": "0.15", + "infill_combination": "0", + "sparse_infill_line_width": "0.4", + "infill_wall_overlap": "30%", + "interface_shells": "0", + "ironing_flow": "10%", + "ironing_spacing": "0.1", + "ironing_speed": "20", + "ironing_type": "no ironing", + "layer_height": "0.15", + "reduce_infill_retraction": "1", + "filename_format": "{input_filename_base}_{filament_type[initial_tool]}_{print_time}.gcode", + "detect_overhang_wall": "1", + "overhang_1_4_speed": "0", + "overhang_2_4_speed": "50", + "overhang_3_4_speed": "35", + "overhang_4_4_speed": "10", + "inner_wall_line_width": "0.4", + "wall_loops": "2", + "raft_layers": "0", + "seam_position": "nearest", + "skirt_distance": "3", + "skirt_height": "1", + "skirt_loops": "1", + "minimum_sparse_infill_area": "15", + "internal_solid_infill_line_width": "0.4", + "spiral_mode": "0", + "standby_temperature_delta": "-5", + "enable_support": "0", + "resolution": "0.012", + "support_type": "normal(auto)", + "support_style": "default", + "support_on_build_plate_only": "0", + "support_top_z_distance": "0.2", + "support_filament": "0", + "support_line_width": "0.4", + "support_interface_loop_pattern": "0", + "support_interface_filament": "0", + "support_interface_top_layers": "2", + "support_interface_bottom_layers": "2", + "support_interface_spacing": "1.33", + "support_interface_speed": "25", + "support_base_pattern": "rectilinear", + "support_base_pattern_spacing": "2.5", + "support_speed": "60", + "support_threshold_angle": "60", + "support_object_xy_distance": "0.8", + "tree_support_branch_angle": "45", + "tree_support_wall_count": "0", + "detect_thin_wall": "0", + "top_surface_pattern": "monotonicline", + "top_surface_line_width": "0.4", + "top_shell_layers": "4", + "top_shell_thickness": "0.8", + "initial_layer_speed": "15", + "inital_travel_speed": "25", + "initial_layer_infill_speed": "15", + "outer_wall_speed": "25", + "inner_wall_speed": "25", + "internal_solid_infill_speed": "50", + "top_surface_speed": "30", + "gap_infill_speed": "30", + "sparse_infill_speed": "50", + "travel_speed": "100", + "enable_prime_tower": "0", + "wipe_tower_no_sparse_layers": "0", + "prime_tower_width": "60", + "xy_hole_compensation": "0", + "xy_contour_compensation": "0", + "compatible_printers": [ + "Creality CR-M4 0.4 nozzle" + ] +} diff --git a/resources/profiles/Creality/process/0.16mm Optimal @Creality K2 Plus 0.4 nozzle.json b/resources/profiles/Creality/process/0.16mm Optimal @Creality K2 Plus 0.4 nozzle.json new file mode 100644 index 00000000000..981862606fd --- /dev/null +++ b/resources/profiles/Creality/process/0.16mm Optimal @Creality K2 Plus 0.4 nozzle.json @@ -0,0 +1,109 @@ +{ + "type": "process", + "setting_id": "GP004", + "name": "0.16mm Optimal @Creality K2 Plus 0.4 nozzle", + "from": "system", + "inherits": "fdm_process_common_klipper", + "instantiation": "true", + "max_travel_detour_distance": "0", + "bottom_surface_pattern": "monotonic", + "bottom_shell_layers": "4", + "bottom_shell_thickness": "0", + "bridge_flow": "1", + "bridge_speed": "50", + "internal_bridge_speed": "150%", + "brim_width": "5", + "brim_object_gap": "0.1", + "compatible_printers": [ + "Creality K2 Plus 0.4 nozzle" + ], + "default_acceleration": "12000", + "bridge_no_support": "0", + "draft_shield": "disabled", + "elefant_foot_compensation": "0.15", + "outer_wall_line_width": "0.42", + "outer_wall_speed": "200", + "outer_wall_acceleration": "5000", + "inner_wall_acceleration": "5000", + "wall_infill_order": "inner wall/outer wall/infill", + "line_width": "0.42", + "infill_direction": "45", + "sparse_infill_density": "15%", + "sparse_infill_pattern": "crosshatch", + "internal_bridge_support_thickness": "0.8", + "initial_layer_acceleration": "2000", + "initial_layer_line_width": "0.5", + "initial_layer_print_height": "0.2", + "initial_layer_speed": "60", + "gap_infill_speed": "250", + "infill_combination": "0", + "sparse_infill_line_width": "0.45", + "infill_wall_overlap": "30%", + "sparse_infill_speed": "300", + "interface_shells": "0", + "ironing_flow": "10%", + "ironing_spacing": "0.15", + "ironing_speed": "30", + "ironing_type": "no ironing", + "layer_height": "0.16", + "reduce_infill_retraction": "1", + "filename_format": "{input_filename_base}_{filament_type[initial_tool]}_{print_time}.gcode", + "detect_overhang_wall": "1", + "overhang_1_4_speed": "0", + "overhang_2_4_speed": "50", + "overhang_3_4_speed": "30", + "overhang_4_4_speed": "10", + "only_one_wall_top": "1", + "inner_wall_line_width": "0.45", + "inner_wall_speed": "300", + "wall_loops": "2", + "raft_layers": "0", + "seam_position": "aligned", + "skirt_distance": "2", + "skirt_height": "1", + "skirt_loops": "0", + "minimum_sparse_infill_area": "15", + "internal_solid_infill_line_width": "0.42", + "internal_solid_infill_speed": "250", + "initial_layer_infill_speed": "105", + "standby_temperature_delta": "-5", + "enable_support": "0", + "resolution": "0.012", + "support_type": "normal(auto)", + "support_style": "default", + "support_on_build_plate_only": "0", + "support_top_z_distance": "0.2", + "support_bottom_z_distance": "0.2", + "support_filament": "0", + "support_line_width": "0.4", + "support_interface_loop_pattern": "0", + "support_interface_filament": "0", + "support_interface_top_layers": "2", + "support_interface_bottom_layers": "2", + "support_interface_spacing": "0.5", + "support_expansion": "0", + "support_interface_speed": "80", + "support_base_pattern": "default", + "support_base_pattern_spacing": "2.5", + "support_speed": "150", + "support_threshold_angle": "30", + "support_object_xy_distance": "0.35", + "tree_support_branch_diameter": "2", + "tree_support_branch_angle": "45", + "tree_support_wall_count": "1", + "detect_thin_wall": "0", + "top_surface_pattern": "monotonicline", + "top_surface_line_width": "0.42", + "top_surface_acceleration": "5000", + "top_surface_speed": "200", + "top_shell_layers": "5", + "top_shell_thickness": "0.8", + "travel_acceleration": "12000", + "travel_speed": "500", + "enable_prime_tower": "1", + "wipe_tower_no_sparse_layers": "0", + "prime_tower_width": "40", + "xy_hole_compensation": "0", + "xy_contour_compensation": "0", + "gcode_label_objects": "0" +} \ No newline at end of file diff --git a/resources/profiles/Creality/process/0.18mm Detail @Creality K2 Plus 0.6 nozzle.json b/resources/profiles/Creality/process/0.18mm Detail @Creality K2 Plus 0.6 nozzle.json new file mode 100644 index 00000000000..4d42a50cbab --- /dev/null +++ b/resources/profiles/Creality/process/0.18mm Detail @Creality K2 Plus 0.6 nozzle.json @@ -0,0 +1,109 @@ +{ + "type": "process", + "setting_id": "GP004", + "name": "0.18mm Detail @Creality K2 Plus 0.6 nozzle", + "from": "system", + "inherits": "fdm_process_common_klipper", + "instantiation": "true", + "max_travel_detour_distance": "0", + "bottom_surface_pattern": "monotonic", + "bottom_shell_layers": "3", + "bottom_shell_thickness": "0", + "bridge_flow": "1", + "bridge_speed": "25", + "internal_bridge_speed": "70", + "brim_width": "5", + "brim_object_gap": "0.1", + "compatible_printers": [ + "Creality K2 Plus 0.6 nozzle" + ], + "default_acceleration": "12000", + "bridge_no_support": "0", + "draft_shield": "disabled", + "elefant_foot_compensation": "0.15", + "outer_wall_line_width": "0.62", + "outer_wall_speed": "100", + "outer_wall_acceleration": "5000", + "inner_wall_acceleration": "5000", + "wall_infill_order": "inner wall/outer wall/infill", + "line_width": "0.62", + "infill_direction": "45", + "sparse_infill_density": "15%", + "sparse_infill_pattern": "crosshatch", + "internal_bridge_support_thickness": "0.8", + "initial_layer_acceleration": "500", + "initial_layer_line_width": "0.65", + "initial_layer_print_height": "0.3", + "initial_layer_speed": "40", + "gap_infill_speed": "50", + "infill_combination": "0", + "sparse_infill_line_width": "0.65", + "infill_wall_overlap": "30%", + "sparse_infill_speed": "120", + "interface_shells": "0", + "ironing_flow": "10%", + "ironing_spacing": "0.15", + "ironing_speed": "30", + "ironing_type": "no ironing", + "layer_height": "0.18", + "reduce_infill_retraction": "1", + "filename_format": "{input_filename_base}_{filament_type[initial_tool]}_{print_time}.gcode", + "detect_overhang_wall": "1", + "overhang_1_4_speed": "0", + "overhang_2_4_speed": "50", + "overhang_3_4_speed": "20", + "overhang_4_4_speed": "10", + "only_one_wall_top": "1", + "inner_wall_line_width": "0.65", + "inner_wall_speed": "150", + "wall_loops": "2", + "raft_layers": "0", + "seam_position": "aligned", + "skirt_distance": "2", + "skirt_height": "1", + "skirt_loops": "0", + "minimum_sparse_infill_area": "15", + "internal_solid_infill_line_width": "0.62", + "internal_solid_infill_speed": "150", + "initial_layer_infill_speed": "60", + "standby_temperature_delta": "-5", + "enable_support": "0", + "resolution": "0.012", + "support_type": "normal(auto)", + "support_style": "default", + "support_on_build_plate_only": "0", + "support_top_z_distance": "0.2", + "support_bottom_z_distance": "0.2", + "support_filament": "0", + "support_line_width": "0.6", + "support_interface_loop_pattern": "0", + "support_interface_filament": "0", + "support_interface_top_layers": "2", + "support_interface_bottom_layers": "2", + "support_interface_spacing": "0.5", + "support_expansion": "0", + "support_interface_speed": "80", + "support_base_pattern": "default", + "support_base_pattern_spacing": "2.5", + "support_speed": "150", + "support_threshold_angle": "30", + "support_object_xy_distance": "0.35", + "tree_support_branch_diameter": "2", + "tree_support_branch_angle": "45", + "tree_support_wall_count": "1", + "detect_thin_wall": "0", + "top_surface_pattern": "monotonicline", + "top_surface_line_width": "0.62", + "top_surface_acceleration": "2000", + "top_surface_speed": "100", + "top_shell_layers": "4", + "top_shell_thickness": "0.8", + "travel_acceleration": "12000", + "travel_speed": "500", + "enable_prime_tower": "0", + "wipe_tower_no_sparse_layers": "0", + "prime_tower_width": "60", + "xy_hole_compensation": "0", + "xy_contour_compensation": "0", + "gcode_label_objects": "0" +} \ No newline at end of file diff --git a/resources/profiles/Creality/process/0.20mm Fast @Creality K1 SE 0.4.json b/resources/profiles/Creality/process/0.20mm Fast @Creality K1 SE 0.4.json new file mode 100644 index 00000000000..fcc01eec404 --- /dev/null +++ b/resources/profiles/Creality/process/0.20mm Fast @Creality K1 SE 0.4.json @@ -0,0 +1,110 @@ +{ + "type": "process", + "setting_id": "GP004", + "name": "0.20mm Fast @Creality K1 SE 0.4", + "from": "system", + "inherits": "fdm_process_common_klipper", + "instantiation": "true", + "max_travel_detour_distance": "0", + "bottom_surface_pattern": "monotonic", + "bottom_shell_layers": "3", + "bottom_shell_thickness": "0", + "bridge_flow": "1", + "bridge_speed": "100", + "internal_bridge_speed": "70", + "brim_width": "5", + "brim_object_gap": "0.1", + "compatible_printers": [ + "Creality K1 SE 0.4 nozzle" + ], + "default_acceleration": "12000", + "bridge_no_support": "0", + "draft_shield": "disabled", + "elefant_foot_compensation": "0.15", + "outer_wall_line_width": "0.42", + "outer_wall_speed": "200", + "outer_wall_acceleration": "5000", + "inner_wall_acceleration": "5000", + "wall_infill_order": "inner wall/outer wall/infill", + "line_width": "0.42", + "infill_direction": "45", + "sparse_infill_density": "15%", + "sparse_infill_pattern": "crosshatch", + "internal_bridge_support_thickness": "0.8", + "initial_layer_acceleration": "1000", + "initial_layer_line_width": "0.5", + "initial_layer_print_height": "0.2", + "initial_layer_speed": "60", + "gap_infill_speed": "200", + "infill_combination": "0", + "sparse_infill_line_width": "0.45", + "infill_wall_overlap": "15%", + "sparse_infill_speed": "250", + "interface_shells": "0", + "ironing_flow": "10%", + "ironing_spacing": "0.15", + "ironing_speed": "100", + "ironing_type": "no ironing", + "layer_height": "0.2", + "reduce_infill_retraction": "1", + "filename_format": "{input_filename_base}_{filament_type[initial_tool]}_{print_time}.gcode", + "detect_overhang_wall": "1", + "overhang_1_4_speed": "0", + "overhang_2_4_speed": "50", + "overhang_3_4_speed": "25", + "overhang_4_4_speed": "10", + "only_one_wall_top": "1", + "inner_wall_line_width": "0.45", + "inner_wall_speed": "300", + "wall_loops": "2", + "raft_layers": "0", + "seam_position": "aligned", + "skirt_distance": "2", + "skirt_height": "1", + "skirt_loops": "0", + "minimum_sparse_infill_area": "15", + "internal_solid_infill_line_width": "0.42", + "internal_solid_infill_speed": "250", + "initial_layer_infill_speed": "105", + "standby_temperature_delta": "-5", + "enable_support": "0", + "resolution": "0.012", + "support_type": "normal(auto)", + "support_style": "default", + "support_on_build_plate_only": "0", + "support_top_z_distance": "0.2", + "support_bottom_z_distance": "0.2", + "support_filament": "0", + "support_line_width": "0.42", + "support_interface_loop_pattern": "0", + "support_interface_filament": "0", + "support_interface_top_layers": "2", + "support_interface_bottom_layers": "2", + "support_interface_spacing": "0.5", + "support_expansion": "0", + "support_interface_speed": "80", + "support_base_pattern": "default", + "support_base_pattern_spacing": "2.5", + "support_speed": "150", + "support_threshold_angle": "30", + "support_object_xy_distance": "0.35", + "tree_support_branch_diameter": "2", + "tree_support_branch_angle": "45", + "tree_support_wall_count": "1", + "detect_thin_wall": "0", + "top_surface_pattern": "monotonicline", + "top_surface_line_width": "0.42", + "top_surface_acceleration": "2000", + "top_surface_speed": "150", + "top_shell_layers": "4", + "top_shell_thickness": "0.6", + "travel_acceleration": "12000", + "initial_layer_travel_speed": "100%", + "travel_speed": "500", + "enable_prime_tower": "0", + "wipe_tower_no_sparse_layers": "0", + "prime_tower_width": "60", + "xy_hole_compensation": "0", + "xy_contour_compensation": "0", + "gcode_label_objects": "0" +} \ No newline at end of file diff --git a/resources/profiles/Creality/process/0.20mm Standard @Creality CR-M4.json b/resources/profiles/Creality/process/0.20mm Standard @Creality CR-M4.json new file mode 100644 index 00000000000..bf0484a2c9d --- /dev/null +++ b/resources/profiles/Creality/process/0.20mm Standard @Creality CR-M4.json @@ -0,0 +1,103 @@ +{ + "type": "process", + "setting_id": "GP004", + "name": "0.20mm Standard @Creality CR-M4", + "from": "system", + "inherits": "fdm_process_creality_common", + "instantiation": "true", + "bottom_surface_pattern": "monotonic", + "bottom_shell_layers": "3", + "bottom_shell_thickness": "0", + "bridge_flow": "1", + "bridge_speed": "15", + "brim_width": ".4", + "brim_object_gap": "0.1", + "default_acceleration": "500", + "top_surface_acceleration": "500", + "bridge_no_support": "0", + "draft_shield": "disabled", + "elefant_foot_compensation": "0", + "enable_arc_fitting": "0", + "outer_wall_line_width": "0.4", + "wall_infill_order": "inner wall/outer wall/infill", + "line_width": "0.4", + "infill_direction": "45", + "sparse_infill_density": "15%", + "sparse_infill_pattern": "crosshatch", + "initial_layer_acceleration": "500", + "travel_acceleration": "700", + "inner_wall_acceleration": "500", + "initial_layer_line_width": "0.44", + "initial_layer_print_height": "0.2", + "infill_combination": "0", + "sparse_infill_line_width": "0.4", + "infill_wall_overlap": "30%", + "interface_shells": "0", + "ironing_flow": "10%", + "ironing_spacing": "0.1", + "ironing_speed": "20", + "ironing_type": "no ironing", + "layer_height": "0.2", + "reduce_infill_retraction": "1", + "filename_format": "{input_filename_base}_{filament_type[initial_tool]}_{print_time}.gcode", + "detect_overhang_wall": "1", + "overhang_1_4_speed": "0", + "overhang_2_4_speed": "50", + "overhang_3_4_speed": "35", + "overhang_4_4_speed": "10", + "inner_wall_line_width": "0.4", + "wall_loops": "2", + "raft_layers": "0", + "seam_position": "nearest", + "skirt_distance": "3", + "skirt_height": "1", + "skirt_loops": "1", + "minimum_sparse_infill_area": "15", + "internal_solid_infill_line_width": "0.4", + "spiral_mode": "0", + "standby_temperature_delta": "-5", + "enable_support": "0", + "resolution": "0.012", + "support_type": "normal(auto)", + "support_style": "default", + "support_on_build_plate_only": "0", + "support_top_z_distance": "0.2", + "support_filament": "0", + "support_line_width": "0.4", + "support_interface_loop_pattern": "0", + "support_interface_filament": "0", + "support_interface_top_layers": "2", + "support_interface_bottom_layers": "2", + "support_interface_spacing": "1.33", + "support_interface_speed": "25", + "support_base_pattern": "rectilinear", + "support_base_pattern_spacing": "2.5", + "support_speed": "60", + "support_threshold_angle": "60", + "support_object_xy_distance": "0.8", + "tree_support_branch_angle": "45", + "tree_support_wall_count": "0", + "detect_thin_wall": "0", + "top_surface_pattern": "monotonicline", + "top_surface_line_width": "0.4", + "top_shell_layers": "3", + "top_shell_thickness": "0.8", + "initial_layer_speed": "15", + "inital_travel_speed": "25", + "initial_layer_infill_speed": "15", + "outer_wall_speed": "25", + "inner_wall_speed": "25", + "internal_solid_infill_speed": "50", + "top_surface_speed": "30", + "gap_infill_speed": "30", + "sparse_infill_speed": "50", + "travel_speed": "100", + "enable_prime_tower": "0", + "wipe_tower_no_sparse_layers": "0", + "prime_tower_width": "60", + "xy_hole_compensation": "0", + "xy_contour_compensation": "0", + "compatible_printers": [ + "Creality CR-M4 0.4 nozzle" + ] +} diff --git a/resources/profiles/Creality/process/0.20mm Standard @Creality K2 Plus 0.4 nozzle.json b/resources/profiles/Creality/process/0.20mm Standard @Creality K2 Plus 0.4 nozzle.json new file mode 100644 index 00000000000..b90fab60f87 --- /dev/null +++ b/resources/profiles/Creality/process/0.20mm Standard @Creality K2 Plus 0.4 nozzle.json @@ -0,0 +1,109 @@ +{ + "type": "process", + "setting_id": "GP004", + "name": "0.20mm Standard @Creality K2 Plus 0.4 nozzle", + "from": "system", + "inherits": "fdm_process_common_klipper", + "instantiation": "true", + "max_travel_detour_distance": "0", + "bottom_surface_pattern": "monotonic", + "bottom_shell_layers": "3", + "bottom_shell_thickness": "0", + "bridge_flow": "1", + "bridge_speed": "50", + "internal_bridge_speed": "150%", + "brim_width": "5", + "brim_object_gap": "0.1", + "compatible_printers": [ + "Creality K2 Plus 0.4 nozzle" + ], + "default_acceleration": "12000", + "bridge_no_support": "0", + "draft_shield": "disabled", + "elefant_foot_compensation": "0.15", + "outer_wall_line_width": "0.42", + "outer_wall_speed": "200", + "outer_wall_acceleration": "5000", + "inner_wall_acceleration": "5000", + "wall_infill_order": "inner wall/outer wall/infill", + "line_width": "0.42", + "infill_direction": "45", + "sparse_infill_density": "15%", + "sparse_infill_pattern": "crosshatch", + "internal_bridge_support_thickness": "0.8", + "initial_layer_acceleration": "2000", + "initial_layer_line_width": "0.5", + "initial_layer_print_height": "0.2", + "initial_layer_speed": "60", + "gap_infill_speed": "250", + "infill_combination": "0", + "sparse_infill_line_width": "0.45", + "infill_wall_overlap": "30%", + "sparse_infill_speed": "270", + "interface_shells": "0", + "ironing_flow": "10%", + "ironing_spacing": "0.15", + "ironing_speed": "30", + "ironing_type": "no ironing", + "layer_height": "0.2", + "reduce_infill_retraction": "1", + "filename_format": "{input_filename_base}_{filament_type[initial_tool]}_{print_time}.gcode", + "detect_overhang_wall": "1", + "overhang_1_4_speed": "0", + "overhang_2_4_speed": "50", + "overhang_3_4_speed": "30", + "overhang_4_4_speed": "10", + "only_one_wall_top": "1", + "inner_wall_line_width": "0.45", + "inner_wall_speed": "300", + "wall_loops": "2", + "raft_layers": "0", + "seam_position": "aligned", + "skirt_distance": "2", + "skirt_height": "1", + "skirt_loops": "0", + "minimum_sparse_infill_area": "15", + "internal_solid_infill_line_width": "0.42", + "internal_solid_infill_speed": "250", + "initial_layer_infill_speed": "105", + "standby_temperature_delta": "-5", + "enable_support": "0", + "resolution": "0.012", + "support_type": "normal(auto)", + "support_style": "default", + "support_on_build_plate_only": "0", + "support_top_z_distance": "0.2", + "support_bottom_z_distance": "0.2", + "support_filament": "0", + "support_line_width": "0.4", + "support_interface_loop_pattern": "0", + "support_interface_filament": "0", + "support_interface_top_layers": "2", + "support_interface_bottom_layers": "2", + "support_interface_spacing": "0.5", + "support_expansion": "0", + "support_interface_speed": "80", + "support_base_pattern": "default", + "support_base_pattern_spacing": "2.5", + "support_speed": "150", + "support_threshold_angle": "30", + "support_object_xy_distance": "0.35", + "tree_support_branch_diameter": "2", + "tree_support_branch_angle": "45", + "tree_support_wall_count": "1", + "detect_thin_wall": "0", + "top_surface_pattern": "monotonicline", + "top_surface_line_width": "0.42", + "top_surface_acceleration": "5000", + "top_surface_speed": "200", + "top_shell_layers": "5", + "top_shell_thickness": "0.8", + "travel_acceleration": "12000", + "travel_speed": "500", + "enable_prime_tower": "1", + "wipe_tower_no_sparse_layers": "0", + "prime_tower_width": "40", + "xy_hole_compensation": "0", + "xy_contour_compensation": "0", + "gcode_label_objects": "0" +} \ No newline at end of file diff --git a/resources/profiles/Creality/process/0.24mm Detail @Creality K2 Plus 0.8 nozzle.json b/resources/profiles/Creality/process/0.24mm Detail @Creality K2 Plus 0.8 nozzle.json new file mode 100644 index 00000000000..fc3ecfd1038 --- /dev/null +++ b/resources/profiles/Creality/process/0.24mm Detail @Creality K2 Plus 0.8 nozzle.json @@ -0,0 +1,109 @@ +{ + "type": "process", + "setting_id": "GP004", + "name": "0.24mm Detail @Creality K2 Plus 0.8 nozzle", + "from": "system", + "inherits": "fdm_process_common_klipper", + "instantiation": "true", + "max_travel_detour_distance": "0", + "bottom_surface_pattern": "monotonic", + "bottom_shell_layers": "3", + "bottom_shell_thickness": "0", + "bridge_flow": "1", + "bridge_speed": "25", + "internal_bridge_speed": "70", + "brim_width": "5", + "brim_object_gap": "0.1", + "compatible_printers": [ + "Creality K2 Plus 0.8 nozzle" + ], + "default_acceleration": "12000", + "bridge_no_support": "0", + "draft_shield": "disabled", + "elefant_foot_compensation": "0.15", + "outer_wall_line_width": "0.82", + "outer_wall_speed": "100", + "outer_wall_acceleration": "5000", + "inner_wall_acceleration": "5000", + "wall_infill_order": "inner wall/outer wall/infill", + "line_width": "0.82", + "infill_direction": "45", + "sparse_infill_density": "15%", + "sparse_infill_pattern": "crosshatch", + "internal_bridge_support_thickness": "0.8", + "initial_layer_acceleration": "500", + "initial_layer_line_width": "0.82", + "initial_layer_print_height": "0.4", + "initial_layer_speed": "40", + "gap_infill_speed": "50", + "infill_combination": "0", + "sparse_infill_line_width": "0.82", + "infill_wall_overlap": "30%", + "sparse_infill_speed": "120", + "interface_shells": "0", + "ironing_flow": "10%", + "ironing_spacing": "0.15", + "ironing_speed": "30", + "ironing_type": "no ironing", + "layer_height": "0.24", + "reduce_infill_retraction": "1", + "filename_format": "{input_filename_base}_{filament_type[initial_tool]}_{print_time}.gcode", + "detect_overhang_wall": "1", + "overhang_1_4_speed": "0", + "overhang_2_4_speed": "50", + "overhang_3_4_speed": "20", + "overhang_4_4_speed": "10", + "only_one_wall_top": "1", + "inner_wall_line_width": "0.82", + "inner_wall_speed": "150", + "wall_loops": "2", + "raft_layers": "0", + "seam_position": "aligned", + "skirt_distance": "2", + "skirt_height": "1", + "skirt_loops": "0", + "minimum_sparse_infill_area": "15", + "internal_solid_infill_line_width": "0.82", + "internal_solid_infill_speed": "150", + "initial_layer_infill_speed": "60", + "standby_temperature_delta": "-5", + "enable_support": "0", + "resolution": "0.012", + "support_type": "normal(auto)", + "support_style": "default", + "support_on_build_plate_only": "0", + "support_top_z_distance": "0.2", + "support_bottom_z_distance": "0.2", + "support_filament": "0", + "support_line_width": "0.6", + "support_interface_loop_pattern": "0", + "support_interface_filament": "0", + "support_interface_top_layers": "2", + "support_interface_bottom_layers": "2", + "support_interface_spacing": "0.3", + "support_expansion": "0", + "support_interface_speed": "80", + "support_base_pattern": "default", + "support_base_pattern_spacing": "2.5", + "support_speed": "150", + "support_threshold_angle": "30", + "support_object_xy_distance": "0.35", + "tree_support_branch_diameter": "2", + "tree_support_branch_angle": "45", + "tree_support_wall_count": "1", + "detect_thin_wall": "0", + "top_surface_pattern": "monotonicline", + "top_surface_line_width": "0.82", + "top_surface_acceleration": "2000", + "top_surface_speed": "100", + "top_shell_layers": "4", + "top_shell_thickness": "0.8", + "travel_acceleration": "12000", + "travel_speed": "500", + "enable_prime_tower": "0", + "wipe_tower_no_sparse_layers": "0", + "prime_tower_width": "60", + "xy_hole_compensation": "0", + "xy_contour_compensation": "0", + "gcode_label_objects": "0" +} \ No newline at end of file diff --git a/resources/profiles/Creality/process/0.24mm Draft @Creality K2 Plus 0.4 nozzle.json b/resources/profiles/Creality/process/0.24mm Draft @Creality K2 Plus 0.4 nozzle.json new file mode 100644 index 00000000000..55e401882c4 --- /dev/null +++ b/resources/profiles/Creality/process/0.24mm Draft @Creality K2 Plus 0.4 nozzle.json @@ -0,0 +1,109 @@ +{ + "type": "process", + "setting_id": "GP004", + "name": "0.24mm Draft @Creality K2 Plus 0.4 nozzle", + "from": "system", + "inherits": "fdm_process_common_klipper", + "instantiation": "true", + "max_travel_detour_distance": "0", + "bottom_surface_pattern": "monotonic", + "bottom_shell_layers": "3", + "bottom_shell_thickness": "0", + "bridge_flow": "1", + "bridge_speed": "50", + "internal_bridge_speed": "150%", + "brim_width": "5", + "brim_object_gap": "0.1", + "compatible_printers": [ + "Creality K2 Plus 0.4 nozzle" + ], + "default_acceleration": "12000", + "bridge_no_support": "0", + "draft_shield": "disabled", + "elefant_foot_compensation": "0.15", + "outer_wall_line_width": "0.42", + "outer_wall_speed": "200", + "outer_wall_acceleration": "5000", + "inner_wall_acceleration": "5000", + "wall_infill_order": "inner wall/outer wall/infill", + "line_width": "0.42", + "infill_direction": "45", + "sparse_infill_density": "15%", + "sparse_infill_pattern": "crosshatch", + "internal_bridge_support_thickness": "0.8", + "initial_layer_acceleration": "2000", + "initial_layer_line_width": "0.5", + "initial_layer_print_height": "0.2", + "initial_layer_speed": "60", + "gap_infill_speed": "250", + "infill_combination": "0", + "sparse_infill_line_width": "0.45", + "infill_wall_overlap": "30%", + "sparse_infill_speed": "250", + "interface_shells": "0", + "ironing_flow": "10%", + "ironing_spacing": "0.15", + "ironing_speed": "30", + "ironing_type": "no ironing", + "layer_height": "0.24", + "reduce_infill_retraction": "1", + "filename_format": "{input_filename_base}_{filament_type[initial_tool]}_{print_time}.gcode", + "detect_overhang_wall": "1", + "overhang_1_4_speed": "0", + "overhang_2_4_speed": "50", + "overhang_3_4_speed": "30", + "overhang_4_4_speed": "10", + "only_one_wall_top": "1", + "inner_wall_line_width": "0.45", + "inner_wall_speed": "300", + "wall_loops": "2", + "raft_layers": "0", + "seam_position": "aligned", + "skirt_distance": "2", + "skirt_height": "1", + "skirt_loops": "0", + "minimum_sparse_infill_area": "15", + "internal_solid_infill_line_width": "0.42", + "internal_solid_infill_speed": "250", + "initial_layer_infill_speed": "105", + "standby_temperature_delta": "-5", + "enable_support": "0", + "resolution": "0.012", + "support_type": "normal(auto)", + "support_style": "default", + "support_on_build_plate_only": "0", + "support_top_z_distance": "0.2", + "support_bottom_z_distance": "0.2", + "support_filament": "0", + "support_line_width": "0.4", + "support_interface_loop_pattern": "0", + "support_interface_filament": "0", + "support_interface_top_layers": "2", + "support_interface_bottom_layers": "2", + "support_interface_spacing": "0.5", + "support_expansion": "0", + "support_interface_speed": "80", + "support_base_pattern": "default", + "support_base_pattern_spacing": "2.5", + "support_speed": "150", + "support_threshold_angle": "30", + "support_object_xy_distance": "0.35", + "tree_support_branch_diameter": "2", + "tree_support_branch_angle": "45", + "tree_support_wall_count": "1", + "detect_thin_wall": "0", + "top_surface_pattern": "monotonicline", + "top_surface_line_width": "0.42", + "top_surface_acceleration": "5000", + "top_surface_speed": "200", + "top_shell_layers": "5", + "top_shell_thickness": "0.8", + "travel_acceleration": "12000", + "travel_speed": "500", + "enable_prime_tower": "1", + "wipe_tower_no_sparse_layers": "0", + "prime_tower_width": "40", + "xy_hole_compensation": "0", + "xy_contour_compensation": "0", + "gcode_label_objects": "0" +} \ No newline at end of file diff --git a/resources/profiles/Creality/process/0.24mm Optimal @Creality K2 Plus 0.6 nozzle.json b/resources/profiles/Creality/process/0.24mm Optimal @Creality K2 Plus 0.6 nozzle.json new file mode 100644 index 00000000000..c15220e405f --- /dev/null +++ b/resources/profiles/Creality/process/0.24mm Optimal @Creality K2 Plus 0.6 nozzle.json @@ -0,0 +1,109 @@ +{ + "type": "process", + "setting_id": "GP004", + "name": "0.24mm Optimal @Creality K2 Plus 0.6 nozzle", + "from": "system", + "inherits": "fdm_process_common_klipper", + "instantiation": "true", + "max_travel_detour_distance": "0", + "bottom_surface_pattern": "monotonic", + "bottom_shell_layers": "3", + "bottom_shell_thickness": "0", + "bridge_flow": "1", + "bridge_speed": "25", + "internal_bridge_speed": "70", + "brim_width": "5", + "brim_object_gap": "0.1", + "compatible_printers": [ + "Creality K2 Plus 0.6 nozzle" + ], + "default_acceleration": "12000", + "bridge_no_support": "0", + "draft_shield": "disabled", + "elefant_foot_compensation": "0.15", + "outer_wall_line_width": "0.62", + "outer_wall_speed": "100", + "outer_wall_acceleration": "5000", + "inner_wall_acceleration": "5000", + "wall_infill_order": "inner wall/outer wall/infill", + "line_width": "0.62", + "infill_direction": "45", + "sparse_infill_density": "15%", + "sparse_infill_pattern": "crosshatch", + "internal_bridge_support_thickness": "0.8", + "initial_layer_acceleration": "500", + "initial_layer_line_width": "0.65", + "initial_layer_print_height": "0.3", + "initial_layer_speed": "40", + "gap_infill_speed": "50", + "infill_combination": "0", + "sparse_infill_line_width": "0.65", + "infill_wall_overlap": "30%", + "sparse_infill_speed": "120", + "interface_shells": "0", + "ironing_flow": "10%", + "ironing_spacing": "0.15", + "ironing_speed": "30", + "ironing_type": "no ironing", + "layer_height": "0.24", + "reduce_infill_retraction": "1", + "filename_format": "{input_filename_base}_{filament_type[initial_tool]}_{print_time}.gcode", + "detect_overhang_wall": "1", + "overhang_1_4_speed": "0", + "overhang_2_4_speed": "50", + "overhang_3_4_speed": "20", + "overhang_4_4_speed": "10", + "only_one_wall_top": "1", + "inner_wall_line_width": "0.65", + "inner_wall_speed": "150", + "wall_loops": "2", + "raft_layers": "0", + "seam_position": "aligned", + "skirt_distance": "2", + "skirt_height": "1", + "skirt_loops": "0", + "minimum_sparse_infill_area": "15", + "internal_solid_infill_line_width": "0.62", + "internal_solid_infill_speed": "150", + "initial_layer_infill_speed": "60", + "standby_temperature_delta": "-5", + "enable_support": "0", + "resolution": "0.012", + "support_type": "normal(auto)", + "support_style": "default", + "support_on_build_plate_only": "0", + "support_top_z_distance": "0.2", + "support_bottom_z_distance": "0.2", + "support_filament": "0", + "support_line_width": "0.6", + "support_interface_loop_pattern": "0", + "support_interface_filament": "0", + "support_interface_top_layers": "2", + "support_interface_bottom_layers": "2", + "support_interface_spacing": "0.5", + "support_expansion": "0", + "support_interface_speed": "80", + "support_base_pattern": "default", + "support_base_pattern_spacing": "2.5", + "support_speed": "150", + "support_threshold_angle": "30", + "support_object_xy_distance": "0.35", + "tree_support_branch_diameter": "2", + "tree_support_branch_angle": "45", + "tree_support_wall_count": "1", + "detect_thin_wall": "0", + "top_surface_pattern": "monotonicline", + "top_surface_line_width": "0.62", + "top_surface_acceleration": "2000", + "top_surface_speed": "100", + "top_shell_layers": "4", + "top_shell_thickness": "0.8", + "travel_acceleration": "12000", + "travel_speed": "500", + "enable_prime_tower": "0", + "wipe_tower_no_sparse_layers": "0", + "prime_tower_width": "60", + "xy_hole_compensation": "0", + "xy_contour_compensation": "0", + "gcode_label_objects": "0" +} \ No newline at end of file diff --git a/resources/profiles/Creality/process/0.28mm SuperDraft @Creality K2 Plus 0.4 nozzle.json b/resources/profiles/Creality/process/0.28mm SuperDraft @Creality K2 Plus 0.4 nozzle.json new file mode 100644 index 00000000000..9a2f65b23c7 --- /dev/null +++ b/resources/profiles/Creality/process/0.28mm SuperDraft @Creality K2 Plus 0.4 nozzle.json @@ -0,0 +1,109 @@ +{ + "type": "process", + "setting_id": "GP004", + "name": "0.28mm SuperDraft @Creality K2 Plus 0.4 nozzle", + "from": "system", + "inherits": "fdm_process_common_klipper", + "instantiation": "true", + "max_travel_detour_distance": "0", + "bottom_surface_pattern": "monotonic", + "bottom_shell_layers": "3", + "bottom_shell_thickness": "0", + "bridge_flow": "1", + "bridge_speed": "50", + "internal_bridge_speed": "150%", + "brim_width": "5", + "brim_object_gap": "0.1", + "compatible_printers": [ + "Creality K2 Plus 0.4 nozzle" + ], + "default_acceleration": "12000", + "bridge_no_support": "0", + "draft_shield": "disabled", + "elefant_foot_compensation": "0.15", + "outer_wall_line_width": "0.42", + "outer_wall_speed": "200", + "outer_wall_acceleration": "5000", + "inner_wall_acceleration": "5000", + "wall_infill_order": "inner wall/outer wall/infill", + "line_width": "0.42", + "infill_direction": "45", + "sparse_infill_density": "15%", + "sparse_infill_pattern": "crosshatch", + "internal_bridge_support_thickness": "0.8", + "initial_layer_acceleration": "2000", + "initial_layer_line_width": "0.5", + "initial_layer_print_height": "0.2", + "initial_layer_speed": "60", + "gap_infill_speed": "250", + "infill_combination": "0", + "sparse_infill_line_width": "0.45", + "infill_wall_overlap": "30%", + "sparse_infill_speed": "200", + "interface_shells": "0", + "ironing_flow": "10%", + "ironing_spacing": "0.15", + "ironing_speed": "30", + "ironing_type": "no ironing", + "layer_height": "0.28", + "reduce_infill_retraction": "1", + "filename_format": "{input_filename_base}_{filament_type[initial_tool]}_{print_time}.gcode", + "detect_overhang_wall": "1", + "overhang_1_4_speed": "0", + "overhang_2_4_speed": "50", + "overhang_3_4_speed": "30", + "overhang_4_4_speed": "10", + "only_one_wall_top": "1", + "inner_wall_line_width": "0.45", + "inner_wall_speed": "200", + "wall_loops": "2", + "raft_layers": "0", + "seam_position": "aligned", + "skirt_distance": "2", + "skirt_height": "1", + "skirt_loops": "0", + "minimum_sparse_infill_area": "15", + "internal_solid_infill_line_width": "0.42", + "internal_solid_infill_speed": "250", + "initial_layer_infill_speed": "105", + "standby_temperature_delta": "-5", + "enable_support": "0", + "resolution": "0.012", + "support_type": "normal(auto)", + "support_style": "default", + "support_on_build_plate_only": "0", + "support_top_z_distance": "0.2", + "support_bottom_z_distance": "0.2", + "support_filament": "0", + "support_line_width": "0.4", + "support_interface_loop_pattern": "0", + "support_interface_filament": "0", + "support_interface_top_layers": "2", + "support_interface_bottom_layers": "2", + "support_interface_spacing": "0.5", + "support_expansion": "0", + "support_interface_speed": "80", + "support_base_pattern": "default", + "support_base_pattern_spacing": "2.5", + "support_speed": "150", + "support_threshold_angle": "30", + "support_object_xy_distance": "0.35", + "tree_support_branch_diameter": "2", + "tree_support_branch_angle": "45", + "tree_support_wall_count": "1", + "detect_thin_wall": "0", + "top_surface_pattern": "monotonicline", + "top_surface_line_width": "0.42", + "top_surface_acceleration": "5000", + "top_surface_speed": "200", + "top_shell_layers": "5", + "top_shell_thickness": "0.8", + "travel_acceleration": "12000", + "travel_speed": "500", + "enable_prime_tower": "1", + "wipe_tower_no_sparse_layers": "0", + "prime_tower_width": "40", + "xy_hole_compensation": "0", + "xy_contour_compensation": "0", + "gcode_label_objects": "0" +} \ No newline at end of file diff --git a/resources/profiles/Creality/process/0.30mm Standard @Creality K2 Plus 0.6 nozzle.json b/resources/profiles/Creality/process/0.30mm Standard @Creality K2 Plus 0.6 nozzle.json new file mode 100644 index 00000000000..9efc66438c4 --- /dev/null +++ b/resources/profiles/Creality/process/0.30mm Standard @Creality K2 Plus 0.6 nozzle.json @@ -0,0 +1,109 @@ +{ + "type": "process", + "setting_id": "GP004", + "name": "0.30mm Standard @Creality K2 Plus 0.6 nozzle", + "from": "system", + "inherits": "fdm_process_common_klipper", + "instantiation": "true", + "max_travel_detour_distance": "0", + "bottom_surface_pattern": "monotonic", + "bottom_shell_layers": "3", + "bottom_shell_thickness": "0", + "bridge_flow": "1", + "bridge_speed": "25", + "internal_bridge_speed": "70", + "brim_width": "5", + "brim_object_gap": "0.1", + "compatible_printers": [ + "Creality K2 Plus 0.6 nozzle" + ], + "default_acceleration": "12000", + "bridge_no_support": "0", + "draft_shield": "disabled", + "elefant_foot_compensation": "0.15", + "outer_wall_line_width": "0.62", + "outer_wall_speed": "100", + "outer_wall_acceleration": "5000", + "inner_wall_acceleration": "5000", + "wall_infill_order": "inner wall/outer wall/infill", + "line_width": "0.62", + "infill_direction": "45", + "sparse_infill_density": "15%", + "sparse_infill_pattern": "crosshatch", + "internal_bridge_support_thickness": "0.8", + "initial_layer_acceleration": "500", + "initial_layer_line_width": "0.65", + "initial_layer_print_height": "0.3", + "initial_layer_speed": "40", + "gap_infill_speed": "50", + "infill_combination": "0", + "sparse_infill_line_width": "0.65", + "infill_wall_overlap": "30%", + "sparse_infill_speed": "120", + "interface_shells": "0", + "ironing_flow": "10%", + "ironing_spacing": "0.15", + "ironing_speed": "30", + "ironing_type": "no ironing", + "layer_height": "0.3", + "reduce_infill_retraction": "1", + "filename_format": "{input_filename_base}_{filament_type[initial_tool]}_{print_time}.gcode", + "detect_overhang_wall": "1", + "overhang_1_4_speed": "0", + "overhang_2_4_speed": "50", + "overhang_3_4_speed": "20", + "overhang_4_4_speed": "10", + "only_one_wall_top": "1", + "inner_wall_line_width": "0.65", + "inner_wall_speed": "150", + "wall_loops": "2", + "raft_layers": "0", + "seam_position": "aligned", + "skirt_distance": "2", + "skirt_height": "1", + "skirt_loops": "0", + "minimum_sparse_infill_area": "15", + "internal_solid_infill_line_width": "0.62", + "internal_solid_infill_speed": "150", + "initial_layer_infill_speed": "60", + "standby_temperature_delta": "-5", + "enable_support": "0", + "resolution": "0.012", + "support_type": "normal(auto)", + "support_style": "default", + "support_on_build_plate_only": "0", + "support_top_z_distance": "0.2", + "support_bottom_z_distance": "0.2", + "support_filament": "0", + "support_line_width": "0.6", + "support_interface_loop_pattern": "0", + "support_interface_filament": "0", + "support_interface_top_layers": "2", + "support_interface_bottom_layers": "2", + "support_interface_spacing": "0.5", + "support_expansion": "0", + "support_interface_speed": "80", + "support_base_pattern": "default", + "support_base_pattern_spacing": "2.5", + "support_speed": "150", + "support_threshold_angle": "30", + "support_object_xy_distance": "0.35", + "tree_support_branch_diameter": "2", + "tree_support_branch_angle": "45", + "tree_support_wall_count": "1", + "detect_thin_wall": "0", + "top_surface_pattern": "monotonicline", + "top_surface_line_width": "0.62", + "top_surface_acceleration": "2000", + "top_surface_speed": "100", + "top_shell_layers": "4", + "top_shell_thickness": "0.8", + "travel_acceleration": "12000", + "travel_speed": "500", + "enable_prime_tower": "0", + "wipe_tower_no_sparse_layers": "0", + "prime_tower_width": "60", + "xy_hole_compensation": "0", + "xy_contour_compensation": "0", + "gcode_label_objects": "0" +} \ No newline at end of file diff --git a/resources/profiles/Creality/process/0.32mm Optimal @Creality K2 Plus 0.8 nozzle.json b/resources/profiles/Creality/process/0.32mm Optimal @Creality K2 Plus 0.8 nozzle.json new file mode 100644 index 00000000000..02f244ad2b6 --- /dev/null +++ b/resources/profiles/Creality/process/0.32mm Optimal @Creality K2 Plus 0.8 nozzle.json @@ -0,0 +1,109 @@ +{ + "type": "process", + "setting_id": "GP004", + "name": "0.32mm Optimal @Creality K2 Plus 0.8 nozzle", + "from": "system", + "inherits": "fdm_process_common_klipper", + "instantiation": "true", + "max_travel_detour_distance": "0", + "bottom_surface_pattern": "monotonic", + "bottom_shell_layers": "3", + "bottom_shell_thickness": "0", + "bridge_flow": "1", + "bridge_speed": "25", + "internal_bridge_speed": "70", + "brim_width": "5", + "brim_object_gap": "0.1", + "compatible_printers": [ + "Creality K2 Plus 0.8 nozzle" + ], + "default_acceleration": "12000", + "bridge_no_support": "0", + "draft_shield": "disabled", + "elefant_foot_compensation": "0.15", + "outer_wall_line_width": "0.82", + "outer_wall_speed": "100", + "outer_wall_acceleration": "5000", + "inner_wall_acceleration": "5000", + "wall_infill_order": "inner wall/outer wall/infill", + "line_width": "0.82", + "infill_direction": "45", + "sparse_infill_density": "15%", + "sparse_infill_pattern": "crosshatch", + "internal_bridge_support_thickness": "0.8", + "initial_layer_acceleration": "500", + "initial_layer_line_width": "0.82", + "initial_layer_print_height": "0.4", + "initial_layer_speed": "40", + "gap_infill_speed": "50", + "infill_combination": "0", + "sparse_infill_line_width": "0.82", + "infill_wall_overlap": "30%", + "sparse_infill_speed": "120", + "interface_shells": "0", + "ironing_flow": "10%", + "ironing_spacing": "0.15", + "ironing_speed": "30", + "ironing_type": "no ironing", + "layer_height": "0.32", + "reduce_infill_retraction": "1", + "filename_format": "{input_filename_base}_{filament_type[initial_tool]}_{print_time}.gcode", + "detect_overhang_wall": "1", + "overhang_1_4_speed": "0", + "overhang_2_4_speed": "50", + "overhang_3_4_speed": "20", + "overhang_4_4_speed": "10", + "only_one_wall_top": "1", + "inner_wall_line_width": "0.82", + "inner_wall_speed": "150", + "wall_loops": "2", + "raft_layers": "0", + "seam_position": "aligned", + "skirt_distance": "2", + "skirt_height": "1", + "skirt_loops": "0", + "minimum_sparse_infill_area": "15", + "internal_solid_infill_line_width": "0.82", + "internal_solid_infill_speed": "150", + "initial_layer_infill_speed": "60", + "standby_temperature_delta": "-5", + "enable_support": "0", + "resolution": "0.012", + "support_type": "normal(auto)", + "support_style": "default", + "support_on_build_plate_only": "0", + "support_top_z_distance": "0.2", + "support_bottom_z_distance": "0.2", + "support_filament": "0", + "support_line_width": "0.6", + "support_interface_loop_pattern": "0", + "support_interface_filament": "0", + "support_interface_top_layers": "2", + "support_interface_bottom_layers": "2", + "support_interface_spacing": "0.3", + "support_expansion": "0", + "support_interface_speed": "80", + "support_base_pattern": "default", + "support_base_pattern_spacing": "2.5", + "support_speed": "150", + "support_threshold_angle": "30", + "support_object_xy_distance": "0.35", + "tree_support_branch_diameter": "2", + "tree_support_branch_angle": "45", + "tree_support_wall_count": "1", + "detect_thin_wall": "0", + "top_surface_pattern": "monotonicline", + "top_surface_line_width": "0.82", + "top_surface_acceleration": "2000", + "top_surface_speed": "100", + "top_shell_layers": "4", + "top_shell_thickness": "0.8", + "travel_acceleration": "12000", + "travel_speed": "500", + "enable_prime_tower": "0", + "wipe_tower_no_sparse_layers": "0", + "prime_tower_width": "60", + "xy_hole_compensation": "0", + "xy_contour_compensation": "0", + "gcode_label_objects": "0" +} \ No newline at end of file diff --git a/resources/profiles/Creality/process/0.36mm Draft @Creality K2 Plus 0.6 nozzle.json b/resources/profiles/Creality/process/0.36mm Draft @Creality K2 Plus 0.6 nozzle.json new file mode 100644 index 00000000000..e112eb39c0e --- /dev/null +++ b/resources/profiles/Creality/process/0.36mm Draft @Creality K2 Plus 0.6 nozzle.json @@ -0,0 +1,109 @@ +{ + "type": "process", + "setting_id": "GP004", + "name": "0.36mm Draft @Creality K2 Plus 0.6 nozzle", + "from": "system", + "inherits": "fdm_process_common_klipper", + "instantiation": "true", + "max_travel_detour_distance": "0", + "bottom_surface_pattern": "monotonic", + "bottom_shell_layers": "3", + "bottom_shell_thickness": "0", + "bridge_flow": "1", + "bridge_speed": "25", + "internal_bridge_speed": "70", + "brim_width": "5", + "brim_object_gap": "0.1", + "compatible_printers": [ + "Creality K2 Plus 0.6 nozzle" + ], + "default_acceleration": "12000", + "bridge_no_support": "0", + "draft_shield": "disabled", + "elefant_foot_compensation": "0.15", + "outer_wall_line_width": "0.62", + "outer_wall_speed": "100", + "outer_wall_acceleration": "5000", + "inner_wall_acceleration": "5000", + "wall_infill_order": "inner wall/outer wall/infill", + "line_width": "0.62", + "infill_direction": "45", + "sparse_infill_density": "15%", + "sparse_infill_pattern": "crosshatch", + "internal_bridge_support_thickness": "0.8", + "initial_layer_acceleration": "500", + "initial_layer_line_width": "0.65", + "initial_layer_print_height": "0.3", + "initial_layer_speed": "40", + "gap_infill_speed": "50", + "infill_combination": "0", + "sparse_infill_line_width": "0.65", + "infill_wall_overlap": "30%", + "sparse_infill_speed": "120", + "interface_shells": "0", + "ironing_flow": "10%", + "ironing_spacing": "0.15", + "ironing_speed": "30", + "ironing_type": "no ironing", + "layer_height": "0.36", + "reduce_infill_retraction": "1", + "filename_format": "{input_filename_base}_{filament_type[initial_tool]}_{print_time}.gcode", + "detect_overhang_wall": "1", + "overhang_1_4_speed": "0", + "overhang_2_4_speed": "50", + "overhang_3_4_speed": "20", + "overhang_4_4_speed": "10", + "only_one_wall_top": "1", + "inner_wall_line_width": "0.65", + "inner_wall_speed": "150", + "wall_loops": "2", + "raft_layers": "0", + "seam_position": "aligned", + "skirt_distance": "2", + "skirt_height": "1", + "skirt_loops": "0", + "minimum_sparse_infill_area": "15", + "internal_solid_infill_line_width": "0.62", + "internal_solid_infill_speed": "150", + "initial_layer_infill_speed": "60", + "standby_temperature_delta": "-5", + "enable_support": "0", + "resolution": "0.012", + "support_type": "normal(auto)", + "support_style": "default", + "support_on_build_plate_only": "0", + "support_top_z_distance": "0.2", + "support_bottom_z_distance": "0.2", + "support_filament": "0", + "support_line_width": "0.6", + "support_interface_loop_pattern": "0", + "support_interface_filament": "0", + "support_interface_top_layers": "2", + "support_interface_bottom_layers": "2", + "support_interface_spacing": "0.5", + "support_expansion": "0", + "support_interface_speed": "80", + "support_base_pattern": "default", + "support_base_pattern_spacing": "2.5", + "support_speed": "150", + "support_threshold_angle": "30", + "support_object_xy_distance": "0.35", + "tree_support_branch_diameter": "2", + "tree_support_branch_angle": "45", + "tree_support_wall_count": "1", + "detect_thin_wall": "0", + "top_surface_pattern": "monotonicline", + "top_surface_line_width": "0.62", + "top_surface_acceleration": "2000", + "top_surface_speed": "100", + "top_shell_layers": "4", + "top_shell_thickness": "0.8", + "travel_acceleration": "12000", + "travel_speed": "500", + "enable_prime_tower": "0", + "wipe_tower_no_sparse_layers": "0", + "prime_tower_width": "60", + "xy_hole_compensation": "0", + "xy_contour_compensation": "0", + "gcode_label_objects": "0" +} \ No newline at end of file diff --git a/resources/profiles/Creality/process/0.40mm Standard @Creality K2 Plus 0.8 nozzle.json b/resources/profiles/Creality/process/0.40mm Standard @Creality K2 Plus 0.8 nozzle.json new file mode 100644 index 00000000000..26477340b04 --- /dev/null +++ b/resources/profiles/Creality/process/0.40mm Standard @Creality K2 Plus 0.8 nozzle.json @@ -0,0 +1,109 @@ +{ + "type": "process", + "setting_id": "GP004", + "name": "0.40mm Standard @Creality K2 Plus 0.8 nozzle", + "from": "system", + "inherits": "fdm_process_common_klipper", + "instantiation": "true", + "max_travel_detour_distance": "0", + "bottom_surface_pattern": "monotonic", + "bottom_shell_layers": "3", + "bottom_shell_thickness": "0", + "bridge_flow": "1", + "bridge_speed": "25", + "internal_bridge_speed": "70", + "brim_width": "5", + "brim_object_gap": "0.1", + "compatible_printers": [ + "Creality K2 Plus 0.8 nozzle" + ], + "default_acceleration": "12000", + "bridge_no_support": "0", + "draft_shield": "disabled", + "elefant_foot_compensation": "0.15", + "outer_wall_line_width": "0.82", + "outer_wall_speed": "100", + "outer_wall_acceleration": "5000", + "inner_wall_acceleration": "5000", + "wall_infill_order": "inner wall/outer wall/infill", + "line_width": "0.82", + "infill_direction": "45", + "sparse_infill_density": "15%", + "sparse_infill_pattern": "crosshatch", + "internal_bridge_support_thickness": "0.8", + "initial_layer_acceleration": "500", + "initial_layer_line_width": "0.82", + "initial_layer_print_height": "0.4", + "initial_layer_speed": "40", + "gap_infill_speed": "50", + "infill_combination": "0", + "sparse_infill_line_width": "0.82", + "infill_wall_overlap": "30%", + "sparse_infill_speed": "120", + "interface_shells": "0", + "ironing_flow": "10%", + "ironing_spacing": "0.15", + "ironing_speed": "30", + "ironing_type": "no ironing", + "layer_height": "0.4", + "reduce_infill_retraction": "1", + "filename_format": "{input_filename_base}_{filament_type[initial_tool]}_{print_time}.gcode", + "detect_overhang_wall": "1", + "overhang_1_4_speed": "0", + "overhang_2_4_speed": "50", + "overhang_3_4_speed": "20", + "overhang_4_4_speed": "10", + "only_one_wall_top": "1", + "inner_wall_line_width": "0.82", + "inner_wall_speed": "150", + "wall_loops": "2", + "raft_layers": "0", + "seam_position": "aligned", + "skirt_distance": "2", + "skirt_height": "1", + "skirt_loops": "0", + "minimum_sparse_infill_area": "15", + "internal_solid_infill_line_width": "0.82", + "internal_solid_infill_speed": "150", + "initial_layer_infill_speed": "60", + "standby_temperature_delta": "-5", + "enable_support": "0", + "resolution": "0.012", + "support_type": "normal(auto)", + "support_style": "default", + "support_on_build_plate_only": "0", + "support_top_z_distance": "0.2", + "support_bottom_z_distance": "0.2", + "support_filament": "0", + "support_line_width": "0.6", + "support_interface_loop_pattern": "0", + "support_interface_filament": "0", + "support_interface_top_layers": "2", + "support_interface_bottom_layers": "2", + "support_interface_spacing": "0.3", + "support_expansion": "0", + "support_interface_speed": "80", + "support_base_pattern": "default", + "support_base_pattern_spacing": "2.5", + "support_speed": "150", + "support_threshold_angle": "30", + "support_object_xy_distance": "0.35", + "tree_support_branch_diameter": "2", + "tree_support_branch_angle": "45", + "tree_support_wall_count": "1", + "detect_thin_wall": "0", + "top_surface_pattern": "monotonicline", + "top_surface_line_width": "0.82", + "top_surface_acceleration": "2000", + "top_surface_speed": "100", + "top_shell_layers": "4", + "top_shell_thickness": "0.8", + "travel_acceleration": "12000", + "travel_speed": "500", + "enable_prime_tower": "0", + "wipe_tower_no_sparse_layers": "0", + "prime_tower_width": "60", + "xy_hole_compensation": "0", + "xy_contour_compensation": "0", + "gcode_label_objects": "0" +} \ No newline at end of file diff --git a/resources/profiles/Creality/process/0.42mm SuperDraft @Creality K2 Plus 0.6 nozzle.json b/resources/profiles/Creality/process/0.42mm SuperDraft @Creality K2 Plus 0.6 nozzle.json new file mode 100644 index 00000000000..4e9594ba20c --- /dev/null +++ b/resources/profiles/Creality/process/0.42mm SuperDraft @Creality K2 Plus 0.6 nozzle.json @@ -0,0 +1,109 @@ +{ + "type": "process", + "setting_id": "GP004", + "name": "0.42mm SuperDraft @Creality K2 Plus 0.6 nozzle", + "from": "system", + "inherits": "fdm_process_common_klipper", + "instantiation": "true", + "max_travel_detour_distance": "0", + "bottom_surface_pattern": "monotonic", + "bottom_shell_layers": "3", + "bottom_shell_thickness": "0", + "bridge_flow": "1", + "bridge_speed": "25", + "internal_bridge_speed": "70", + "brim_width": "5", + "brim_object_gap": "0.1", + "compatible_printers": [ + "Creality K2 Plus 0.6 nozzle" + ], + "default_acceleration": "12000", + "bridge_no_support": "0", + "draft_shield": "disabled", + "elefant_foot_compensation": "0.15", + "outer_wall_line_width": "0.62", + "outer_wall_speed": "100", + "outer_wall_acceleration": "5000", + "inner_wall_acceleration": "5000", + "wall_infill_order": "inner wall/outer wall/infill", + "line_width": "0.62", + "infill_direction": "45", + "sparse_infill_density": "15%", + "sparse_infill_pattern": "crosshatch", + "internal_bridge_support_thickness": "0.8", + "initial_layer_acceleration": "500", + "initial_layer_line_width": "0.65", + "initial_layer_print_height": "0.3", + "initial_layer_speed": "40", + "gap_infill_speed": "50", + "infill_combination": "0", + "sparse_infill_line_width": "0.65", + "infill_wall_overlap": "30%", + "sparse_infill_speed": "120", + "interface_shells": "0", + "ironing_flow": "10%", + "ironing_spacing": "0.15", + "ironing_speed": "30", + "ironing_type": "no ironing", + "layer_height": "0.42", + "reduce_infill_retraction": "1", + "filename_format": "{input_filename_base}_{filament_type[initial_tool]}_{print_time}.gcode", + "detect_overhang_wall": "1", + "overhang_1_4_speed": "0", + "overhang_2_4_speed": "50", + "overhang_3_4_speed": "20", + "overhang_4_4_speed": "10", + "only_one_wall_top": "1", + "inner_wall_line_width": "0.65", + "inner_wall_speed": "150", + "wall_loops": "2", + "raft_layers": "0", + "seam_position": "aligned", + "skirt_distance": "2", + "skirt_height": "1", + "skirt_loops": "0", + "minimum_sparse_infill_area": "15", + "internal_solid_infill_line_width": "0.62", + "internal_solid_infill_speed": "150", + "initial_layer_infill_speed": "60", + "standby_temperature_delta": "-5", + "enable_support": "0", + "resolution": "0.012", + "support_type": "normal(auto)", + "support_style": "default", + "support_on_build_plate_only": "0", + "support_top_z_distance": "0.2", + "support_bottom_z_distance": "0.2", + "support_filament": "0", + "support_line_width": "0.6", + "support_interface_loop_pattern": "0", + "support_interface_filament": "0", + "support_interface_top_layers": "2", + "support_interface_bottom_layers": "2", + "support_interface_spacing": "0.5", + "support_expansion": "0", + "support_interface_speed": "80", + "support_base_pattern": "default", + "support_base_pattern_spacing": "2.5", + "support_speed": "150", + "support_threshold_angle": "30", + "support_object_xy_distance": "0.35", + "tree_support_branch_diameter": "2", + "tree_support_branch_angle": "45", + "tree_support_wall_count": "1", + "detect_thin_wall": "0", + "top_surface_pattern": "monotonicline", + "top_surface_line_width": "0.62", + "top_surface_acceleration": "2000", + "top_surface_speed": "100", + "top_shell_layers": "4", + "top_shell_thickness": "0.8", + "travel_acceleration": "12000", + "travel_speed": "500", + "enable_prime_tower": "0", + "wipe_tower_no_sparse_layers": "0", + "prime_tower_width": "60", + "xy_hole_compensation": "0", + "xy_contour_compensation": "0", + "gcode_label_objects": "0" +} \ No newline at end of file diff --git a/resources/profiles/Creality/process/0.48mm Draft @Creality K2 Plus 0.8 nozzle.json b/resources/profiles/Creality/process/0.48mm Draft @Creality K2 Plus 0.8 nozzle.json new file mode 100644 index 00000000000..c420d26892b --- /dev/null +++ b/resources/profiles/Creality/process/0.48mm Draft @Creality K2 Plus 0.8 nozzle.json @@ -0,0 +1,109 @@ +{ + "type": "process", + "setting_id": "GP004", + "name": "0.48mm Draft @Creality K2 Plus 0.8 nozzle", + "from": "system", + "inherits": "fdm_process_common_klipper", + "instantiation": "true", + "max_travel_detour_distance": "0", + "bottom_surface_pattern": "monotonic", + "bottom_shell_layers": "3", + "bottom_shell_thickness": "0", + "bridge_flow": "1", + "bridge_speed": "25", + "internal_bridge_speed": "70", + "brim_width": "5", + "brim_object_gap": "0.1", + "compatible_printers": [ + "Creality K2 Plus 0.8 nozzle" + ], + "default_acceleration": "12000", + "bridge_no_support": "0", + "draft_shield": "disabled", + "elefant_foot_compensation": "0.15", + "outer_wall_line_width": "0.82", + "outer_wall_speed": "100", + "outer_wall_acceleration": "5000", + "inner_wall_acceleration": "5000", + "wall_infill_order": "inner wall/outer wall/infill", + "line_width": "0.82", + "infill_direction": "45", + "sparse_infill_density": "15%", + "sparse_infill_pattern": "crosshatch", + "internal_bridge_support_thickness": "0.8", + "initial_layer_acceleration": "500", + "initial_layer_line_width": "0.82", + "initial_layer_print_height": "0.4", + "initial_layer_speed": "40", + "gap_infill_speed": "50", + "infill_combination": "0", + "sparse_infill_line_width": "0.82", + "infill_wall_overlap": "30%", + "sparse_infill_speed": "120", + "interface_shells": "0", + "ironing_flow": "10%", + "ironing_spacing": "0.15", + "ironing_speed": "30", + "ironing_type": "no ironing", + "layer_height": "0.48", + "reduce_infill_retraction": "1", + "filename_format": "{input_filename_base}_{filament_type[initial_tool]}_{print_time}.gcode", + "detect_overhang_wall": "1", + "overhang_1_4_speed": "0", + "overhang_2_4_speed": "50", + "overhang_3_4_speed": "20", + "overhang_4_4_speed": "10", + "only_one_wall_top": "1", + "inner_wall_line_width": "0.82", + "inner_wall_speed": "150", + "wall_loops": "2", + "raft_layers": "0", + "seam_position": "aligned", + "skirt_distance": "2", + "skirt_height": "1", + "skirt_loops": "0", + "minimum_sparse_infill_area": "15", + "internal_solid_infill_line_width": "0.82", + "internal_solid_infill_speed": "150", + "initial_layer_infill_speed": "60", + "standby_temperature_delta": "-5", + "enable_support": "0", + "resolution": "0.012", + "support_type": "normal(auto)", + "support_style": "default", + "support_on_build_plate_only": "0", + "support_top_z_distance": "0.2", + "support_bottom_z_distance": "0.2", + "support_filament": "0", + "support_line_width": "0.6", + "support_interface_loop_pattern": "0", + "support_interface_filament": "0", + "support_interface_top_layers": "2", + "support_interface_bottom_layers": "2", + "support_interface_spacing": "0.3", + "support_expansion": "0", + "support_interface_speed": "80", + "support_base_pattern": "default", + "support_base_pattern_spacing": "2.5", + "support_speed": "150", + "support_threshold_angle": "30", + "support_object_xy_distance": "0.35", + "tree_support_branch_diameter": "2", + "tree_support_branch_angle": "45", + "tree_support_wall_count": "1", + "detect_thin_wall": "0", + "top_surface_pattern": "monotonicline", + "top_surface_line_width": "0.82", + "top_surface_acceleration": "2000", + "top_surface_speed": "100", + "top_shell_layers": "4", + "top_shell_thickness": "0.8", + "travel_acceleration": "12000", + "travel_speed": "500", + "enable_prime_tower": "0", + "wipe_tower_no_sparse_layers": "0", + "prime_tower_width": "60", + "xy_hole_compensation": "0", + "xy_contour_compensation": "0", + "gcode_label_objects": "0" +} \ No newline at end of file diff --git a/resources/profiles/Creality/process/0.56mm SuperDraft @Creality K2 Plus 0.8 nozzle.json b/resources/profiles/Creality/process/0.56mm SuperDraft @Creality K2 Plus 0.8 nozzle.json new file mode 100644 index 00000000000..a463fef9692 --- /dev/null +++ b/resources/profiles/Creality/process/0.56mm SuperDraft @Creality K2 Plus 0.8 nozzle.json @@ -0,0 +1,109 @@ +{ + "type": "process", + "setting_id": "GP004", + "name": "0.56mm SuperDraft @Creality K2 Plus 0.8 nozzle", + "from": "system", + "inherits": "fdm_process_common_klipper", + "instantiation": "true", + "max_travel_detour_distance": "0", + "bottom_surface_pattern": "monotonic", + "bottom_shell_layers": "3", + "bottom_shell_thickness": "0", + "bridge_flow": "1", + "bridge_speed": "25", + "internal_bridge_speed": "70", + "brim_width": "5", + "brim_object_gap": "0.1", + "compatible_printers": [ + "Creality K2 Plus 0.8 nozzle" + ], + "default_acceleration": "12000", + "bridge_no_support": "0", + "draft_shield": "disabled", + "elefant_foot_compensation": "0.15", + "outer_wall_line_width": "0.82", + "outer_wall_speed": "100", + "outer_wall_acceleration": "5000", + "inner_wall_acceleration": "5000", + "wall_infill_order": "inner wall/outer wall/infill", + "line_width": "0.82", + "infill_direction": "45", + "sparse_infill_density": "15%", + "sparse_infill_pattern": "crosshatch", + "internal_bridge_support_thickness": "0.8", + "initial_layer_acceleration": "500", + "initial_layer_line_width": "0.82", + "initial_layer_print_height": "0.4", + "initial_layer_speed": "40", + "gap_infill_speed": "50", + "infill_combination": "0", + "sparse_infill_line_width": "0.82", + "infill_wall_overlap": "30%", + "sparse_infill_speed": "120", + "interface_shells": "0", + "ironing_flow": "10%", + "ironing_spacing": "0.15", + "ironing_speed": "30", + "ironing_type": "no ironing", + "layer_height": "0.56", + "reduce_infill_retraction": "1", + "filename_format": "{input_filename_base}_{filament_type[initial_tool]}_{print_time}.gcode", + "detect_overhang_wall": "1", + "overhang_1_4_speed": "0", + "overhang_2_4_speed": "50", + "overhang_3_4_speed": "20", + "overhang_4_4_speed": "10", + "only_one_wall_top": "1", + "inner_wall_line_width": "0.82", + "inner_wall_speed": "150", + "wall_loops": "2", + "raft_layers": "0", + "seam_position": "aligned", + "skirt_distance": "2", + "skirt_height": "1", + "skirt_loops": "0", + "minimum_sparse_infill_area": "15", + "internal_solid_infill_line_width": "0.82", + "internal_solid_infill_speed": "150", + "initial_layer_infill_speed": "60", + "standby_temperature_delta": "-5", + "enable_support": "0", + "resolution": "0.012", + "support_type": "normal(auto)", + "support_style": "default", + "support_on_build_plate_only": "0", + "support_top_z_distance": "0.2", + "support_bottom_z_distance": "0.2", + "support_filament": "0", + "support_line_width": "0.6", + "support_interface_loop_pattern": "0", + "support_interface_filament": "0", + "support_interface_top_layers": "2", + "support_interface_bottom_layers": "2", + "support_interface_spacing": "0.3", + "support_expansion": "0", + "support_interface_speed": "80", + "support_base_pattern": "default", + "support_base_pattern_spacing": "2.5", + "support_speed": "150", + "support_threshold_angle": "30", + "support_object_xy_distance": "0.35", + "tree_support_branch_diameter": "2", + "tree_support_branch_angle": "45", + "tree_support_wall_count": "1", + "detect_thin_wall": "0", + "top_surface_pattern": "monotonicline", + "top_surface_line_width": "0.82", + "top_surface_acceleration": "2000", + "top_surface_speed": "100", + "top_shell_layers": "4", + "top_shell_thickness": "0.8", + "travel_acceleration": "12000", + "travel_speed": "500", + "enable_prime_tower": "0", + "wipe_tower_no_sparse_layers": "0", + "prime_tower_width": "60", + "xy_hole_compensation": "0", + "xy_contour_compensation": "0", + "gcode_label_objects": "0" +} \ No newline at end of file diff --git a/resources/profiles/Ratrig.json b/resources/profiles/Ratrig.json index 3519738f1ec..a3e6fe1fa31 100644 --- a/resources/profiles/Ratrig.json +++ b/resources/profiles/Ratrig.json @@ -235,6 +235,18 @@ { "name": "RatRig Generic PA-CF", "sub_path": "filament/RatRig Generic PA-CF.json" + }, + { + "name": "RatRig PunkFil ABS", + "sub_path": "filament/RatRig PunkFil ABS.json" + }, + { + "name": "RatRig PunkFil PETG CF", + "sub_path": "filament/RatRig PunkFil PETG CF.json" + }, + { + "name": "RatRig PunkFil PETG", + "sub_path": "filament/RatRig PunkFil PETG.json" } ], "machine_list": [ diff --git a/resources/profiles/Ratrig/RatRig V-Core 4 300_cover.png b/resources/profiles/Ratrig/RatRig V-Core 4 300_cover.png index 8490bf74a0f..d1ed3b3437d 100644 Binary files a/resources/profiles/Ratrig/RatRig V-Core 4 300_cover.png and b/resources/profiles/Ratrig/RatRig V-Core 4 300_cover.png differ diff --git a/resources/profiles/Ratrig/RatRig V-Core 4 400_cover.png b/resources/profiles/Ratrig/RatRig V-Core 4 400_cover.png index 8490bf74a0f..d1ed3b3437d 100644 Binary files a/resources/profiles/Ratrig/RatRig V-Core 4 400_cover.png and b/resources/profiles/Ratrig/RatRig V-Core 4 400_cover.png differ diff --git a/resources/profiles/Ratrig/RatRig V-Core 4 500_cover.png b/resources/profiles/Ratrig/RatRig V-Core 4 500_cover.png index 8490bf74a0f..d1ed3b3437d 100644 Binary files a/resources/profiles/Ratrig/RatRig V-Core 4 500_cover.png and b/resources/profiles/Ratrig/RatRig V-Core 4 500_cover.png differ diff --git a/resources/profiles/Ratrig/RatRig V-Core 4 HYBRID 300_cover.png b/resources/profiles/Ratrig/RatRig V-Core 4 HYBRID 300_cover.png index 8490bf74a0f..d4a3fb40154 100644 Binary files a/resources/profiles/Ratrig/RatRig V-Core 4 HYBRID 300_cover.png and b/resources/profiles/Ratrig/RatRig V-Core 4 HYBRID 300_cover.png differ diff --git a/resources/profiles/Ratrig/RatRig V-Core 4 HYBRID 400_cover.png b/resources/profiles/Ratrig/RatRig V-Core 4 HYBRID 400_cover.png index 8490bf74a0f..d4a3fb40154 100644 Binary files a/resources/profiles/Ratrig/RatRig V-Core 4 HYBRID 400_cover.png and b/resources/profiles/Ratrig/RatRig V-Core 4 HYBRID 400_cover.png differ diff --git a/resources/profiles/Ratrig/RatRig V-Core 4 HYBRID 500_cover.png b/resources/profiles/Ratrig/RatRig V-Core 4 HYBRID 500_cover.png index 8490bf74a0f..d4a3fb40154 100644 Binary files a/resources/profiles/Ratrig/RatRig V-Core 4 HYBRID 500_cover.png and b/resources/profiles/Ratrig/RatRig V-Core 4 HYBRID 500_cover.png differ diff --git a/resources/profiles/Ratrig/filament/RatRig PunkFil ABS.json b/resources/profiles/Ratrig/filament/RatRig PunkFil ABS.json new file mode 100644 index 00000000000..c8affa0ab3f --- /dev/null +++ b/resources/profiles/Ratrig/filament/RatRig PunkFil ABS.json @@ -0,0 +1,96 @@ +{ + "type": "filament", + "filament_id": "GFB99", + "setting_id": "GFSA04", + "name": "RatRig PunkFil ABS", + "from": "system", + "instantiation": "true", + "inherits": "fdm_filament_abs", + "filament_flow_ratio": [ + "0.92" + ], + "filament_max_volumetric_speed": [ + "40" + ], + "filament_z_hop": [ + "nil" + ], + "enable_pressure_advance": [ + "1" + ], + "pressure_advance": [ + "0.022" + ], + "hot_plate_temp_initial_layer" : [ + "110" + ], + "hot_plate_temp" : [ + "110" + ], + "nozzle_temperature_initial_layer": [ + "260" + ], + "nozzle_temperature": [ + "260" + ], + "close_fan_the_first_x_layers": [ + "2" + ], + "fan_cooling_layer_time": [ + "7" + ], + "fan_max_speed": [ + "60" + ], + "fan_min_speed": [ + "30" + ], + "overhang_fan_speed": [ + "60" + ], + "overhang_fan_threshold": [ + "25%" + ], + "slow_down_min_speed": [ + "50" + ], + "slow_down_layer_time": [ + "2" + ], + "slow_down_for_layer_cooling": [ + "1" + ], + "filament_cost": [ + "25.5" + ], + "filament_vendor": [ + "RatRig" + ], + "compatible_printers": [ + "RatRig V-Core 3 200 0.4 nozzle", + "RatRig V-Core 3 300 0.4 nozzle", + "RatRig V-Core 3 400 0.4 nozzle", + "RatRig V-Core 3 500 0.4 nozzle", + "RatRig V-Minion 0.4 nozzle", + "RatRig V-Cast 0.4 nozzle", + "RatRig V-Cast 0.6 nozzle", + "RatRig V-Core 4 300 0.4 nozzle", + "RatRig V-Core 4 400 0.4 nozzle", + "RatRig V-Core 4 500 0.4 nozzle", + "RatRig V-Core 4 HYBRID 300 0.4 nozzle", + "RatRig V-Core 4 HYBRID 400 0.4 nozzle", + "RatRig V-Core 4 HYBRID 500 0.4 nozzle", + "RatRig V-Core 4 300 0.5 nozzle", + "RatRig V-Core 4 400 0.5 nozzle", + "RatRig V-Core 4 500 0.5 nozzle", + "RatRig V-Core 4 HYBRID 300 0.5 nozzle", + "RatRig V-Core 4 HYBRID 400 0.5 nozzle", + "RatRig V-Core 4 HYBRID 500 0.5 nozzle", + "RatRig V-Core 4 300 0.6 nozzle", + "RatRig V-Core 4 400 0.6 nozzle", + "RatRig V-Core 4 500 0.6 nozzle", + "RatRig V-Core 4 HYBRID 300 0.6 nozzle", + "RatRig V-Core 4 HYBRID 400 0.6 nozzle", + "RatRig V-Core 4 HYBRID 500 0.6 nozzle" + ] +} diff --git a/resources/profiles/Ratrig/filament/RatRig PunkFil PETG CF.json b/resources/profiles/Ratrig/filament/RatRig PunkFil PETG CF.json new file mode 100644 index 00000000000..8150ef9d539 --- /dev/null +++ b/resources/profiles/Ratrig/filament/RatRig PunkFil PETG CF.json @@ -0,0 +1,99 @@ +{ + "type": "filament", + "filament_id": "GFB99", + "setting_id": "GFSA04", + "name": "RatRig PunkFil PETG CF", + "from": "system", + "instantiation": "true", + "inherits": "fdm_filament_pet", + "filament_flow_ratio": [ + "0.93" + ], + "filament_max_volumetric_speed": [ + "20" + ], + "filament_z_hop": [ + "nil" + ], + "enable_pressure_advance": [ + "1" + ], + "pressure_advance": [ + "0.038" + ], + "hot_plate_temp_initial_layer" : [ + "80" + ], + "hot_plate_temp" : [ + "80" + ], + "nozzle_temperature_initial_layer": [ + "230" + ], + "nozzle_temperature": [ + "230" + ], + "close_fan_the_first_x_layers": [ + "2" + ], + "fan_cooling_layer_time": [ + "10" + ], + "fan_max_speed": [ + "30" + ], + "fan_min_speed": [ + "0" + ], + "overhang_fan_speed": [ + "40" + ], + "overhang_fan_threshold": [ + "50%" + ], + "slow_down_min_speed": [ + "30" + ], + "slow_down_layer_time": [ + "8" + ], + "slow_down_for_layer_cooling": [ + "1" + ], + "filament_cost": [ + "48" + ], + "filament_type": [ + "PETG-CF10" + ], + "filament_vendor": [ + "RatRig" + ], + "compatible_printers": [ + "RatRig V-Core 3 200 0.4 nozzle", + "RatRig V-Core 3 300 0.4 nozzle", + "RatRig V-Core 3 400 0.4 nozzle", + "RatRig V-Core 3 500 0.4 nozzle", + "RatRig V-Minion 0.4 nozzle", + "RatRig V-Cast 0.4 nozzle", + "RatRig V-Cast 0.6 nozzle", + "RatRig V-Core 4 300 0.4 nozzle", + "RatRig V-Core 4 400 0.4 nozzle", + "RatRig V-Core 4 500 0.4 nozzle", + "RatRig V-Core 4 HYBRID 300 0.4 nozzle", + "RatRig V-Core 4 HYBRID 400 0.4 nozzle", + "RatRig V-Core 4 HYBRID 500 0.4 nozzle", + "RatRig V-Core 4 300 0.5 nozzle", + "RatRig V-Core 4 400 0.5 nozzle", + "RatRig V-Core 4 500 0.5 nozzle", + "RatRig V-Core 4 HYBRID 300 0.5 nozzle", + "RatRig V-Core 4 HYBRID 400 0.5 nozzle", + "RatRig V-Core 4 HYBRID 500 0.5 nozzle", + "RatRig V-Core 4 300 0.6 nozzle", + "RatRig V-Core 4 400 0.6 nozzle", + "RatRig V-Core 4 500 0.6 nozzle", + "RatRig V-Core 4 HYBRID 300 0.6 nozzle", + "RatRig V-Core 4 HYBRID 400 0.6 nozzle", + "RatRig V-Core 4 HYBRID 500 0.6 nozzle" + ] +} diff --git a/resources/profiles/Ratrig/filament/RatRig PunkFil PETG.json b/resources/profiles/Ratrig/filament/RatRig PunkFil PETG.json new file mode 100644 index 00000000000..aff5fa4c6d3 --- /dev/null +++ b/resources/profiles/Ratrig/filament/RatRig PunkFil PETG.json @@ -0,0 +1,96 @@ +{ + "type": "filament", + "filament_id": "GFB99", + "setting_id": "GFSA04", + "name": "RatRig PunkFil PETG", + "from": "system", + "instantiation": "true", + "inherits": "fdm_filament_pet", + "filament_flow_ratio": [ + "0.93" + ], + "filament_max_volumetric_speed": [ + "40" + ], + "filament_z_hop": [ + "nil" + ], + "enable_pressure_advance": [ + "1" + ], + "pressure_advance": [ + "0.025" + ], + "hot_plate_temp_initial_layer" : [ + "80" + ], + "hot_plate_temp" : [ + "80" + ], + "nozzle_temperature_initial_layer": [ + "235" + ], + "nozzle_temperature": [ + "235" + ], + "close_fan_the_first_x_layers": [ + "2" + ], + "fan_cooling_layer_time": [ + "8" + ], + "fan_max_speed": [ + "60" + ], + "fan_min_speed": [ + "30" + ], + "overhang_fan_speed": [ + "50" + ], + "overhang_fan_threshold": [ + "50%" + ], + "slow_down_min_speed": [ + "50" + ], + "slow_down_layer_time": [ + "2" + ], + "slow_down_for_layer_cooling": [ + "1" + ], + "filament_cost": [ + "24.5" + ], + "filament_vendor": [ + "RatRig" + ], + "compatible_printers": [ + "RatRig V-Core 3 200 0.4 nozzle", + "RatRig V-Core 3 300 0.4 nozzle", + "RatRig V-Core 3 400 0.4 nozzle", + "RatRig V-Core 3 500 0.4 nozzle", + "RatRig V-Minion 0.4 nozzle", + "RatRig V-Cast 0.4 nozzle", + "RatRig V-Cast 0.6 nozzle", + "RatRig V-Core 4 300 0.4 nozzle", + "RatRig V-Core 4 400 0.4 nozzle", + "RatRig V-Core 4 500 0.4 nozzle", + "RatRig V-Core 4 HYBRID 300 0.4 nozzle", + "RatRig V-Core 4 HYBRID 400 0.4 nozzle", + "RatRig V-Core 4 HYBRID 500 0.4 nozzle", + "RatRig V-Core 4 300 0.5 nozzle", + "RatRig V-Core 4 400 0.5 nozzle", + "RatRig V-Core 4 500 0.5 nozzle", + "RatRig V-Core 4 HYBRID 300 0.5 nozzle", + "RatRig V-Core 4 HYBRID 400 0.5 nozzle", + "RatRig V-Core 4 HYBRID 500 0.5 nozzle", + "RatRig V-Core 4 300 0.6 nozzle", + "RatRig V-Core 4 400 0.6 nozzle", + "RatRig V-Core 4 500 0.6 nozzle", + "RatRig V-Core 4 HYBRID 300 0.6 nozzle", + "RatRig V-Core 4 HYBRID 400 0.6 nozzle", + "RatRig V-Core 4 HYBRID 500 0.6 nozzle" + ] +} diff --git a/resources/profiles/Ratrig/machine/RatRig V-Core 4 300 0.4 nozzle.json b/resources/profiles/Ratrig/machine/RatRig V-Core 4 300 0.4 nozzle.json index bee5029db67..9aa5e17c45c 100644 --- a/resources/profiles/Ratrig/machine/RatRig V-Core 4 300 0.4 nozzle.json +++ b/resources/profiles/Ratrig/machine/RatRig V-Core 4 300 0.4 nozzle.json @@ -18,7 +18,7 @@ ], "machine_max_speed_x": ["400", "400"], "machine_max_speed_y": ["400", "400"], - "machine_max_speed_z": ["50", "50"], + "machine_max_speed_z": ["200", "200"], "machine_max_speed_e": ["120", "120"], "machine_max_acceleration_x": ["10000", "10000"], "machine_max_acceleration_y": ["10000", "10000"], @@ -26,23 +26,28 @@ "machine_max_acceleration_e": ["5000", "5000"], "machine_max_acceleration_extruding": ["10000", "10000"], "machine_max_acceleration_retracting": ["10000", "10000"], + "machine_max_acceleration_travel": ["10000", "10000"], "machine_max_jerk_x": ["5", "5"], "machine_max_jerk_y": ["5", "5"], "machine_max_jerk_z": ["0.4", "0.4"], "machine_max_jerk_e": ["5", "5"], "min_layer_height": ["0.06"], "max_layer_height": ["0.3"], + "extruder_clearance_radius": "45", + "extruder_clearance_height_to_rod": "25", + "extruder_clearance_height_to_lid": "140", "retract_lift_below": ["0.2"], "retraction_length": ["0.8"], "retraction_speed": ["120"], "deretraction_speed": ["120"], - "wipe": ["0"], - "retract_before_wipe": ["0%"], + "wipe": ["1"], + "retract_before_wipe": ["70%"], + "z_hop": ["0.2"], "machine_start_gcode": "START_PRINT EXTRUDER_TEMP=[nozzle_temperature_initial_layer] BED_TEMP=[bed_temperature_initial_layer_single] TOTAL_LAYER_COUNT={total_layer_count} X0={first_layer_print_min[0]} Y0={first_layer_print_min[1]} X1={first_layer_print_max[0]} Y1={first_layer_print_max[1]}", - "layer_change_gcode": ";AFTER_LAYER_CHANGE\n;[layer_z]\nG92 E0\n_ON_LAYER_CHANGE LAYER={layer_num + 1}", - "before_layer_change_gcode": ";[layer_z]", + "layer_change_gcode": ";AFTER_LAYER_CHANGE\n;[layer_z]", + "before_layer_change_gcode": ";BEFORE_LAYER_CHANGE\n;[layer_z]\nG92 E0\n_ON_LAYER_CHANGE LAYER={layer_num + 1}", "change_filament_gcode": "M600", - "machine_pause_gcode": "M601", + "machine_pause_gcode": "PAUSE", "printing_by_object_gcode": ";BETWEEN_OBJECTS\nG92 E0", "printable_height": "300", "thumbnails": [ diff --git a/resources/profiles/Ratrig/machine/RatRig V-Core 4 300 0.5 nozzle.json b/resources/profiles/Ratrig/machine/RatRig V-Core 4 300 0.5 nozzle.json index 94c11a1bbe9..4a69198bb7e 100644 --- a/resources/profiles/Ratrig/machine/RatRig V-Core 4 300 0.5 nozzle.json +++ b/resources/profiles/Ratrig/machine/RatRig V-Core 4 300 0.5 nozzle.json @@ -18,7 +18,7 @@ ], "machine_max_speed_x": ["400", "400"], "machine_max_speed_y": ["400", "400"], - "machine_max_speed_z": ["50", "50"], + "machine_max_speed_z": ["200", "200"], "machine_max_speed_e": ["120", "120"], "machine_max_acceleration_x": ["10000", "10000"], "machine_max_acceleration_y": ["10000", "10000"], @@ -26,23 +26,28 @@ "machine_max_acceleration_e": ["5000", "5000"], "machine_max_acceleration_extruding": ["10000", "10000"], "machine_max_acceleration_retracting": ["10000", "10000"], + "machine_max_acceleration_travel": ["10000", "10000"], "machine_max_jerk_x": ["5", "5"], "machine_max_jerk_y": ["5", "5"], "machine_max_jerk_z": ["0.4", "0.4"], "machine_max_jerk_e": ["5", "5"], "min_layer_height": ["0.07"], "max_layer_height": ["0.32"], + "extruder_clearance_radius": "45", + "extruder_clearance_height_to_rod": "25", + "extruder_clearance_height_to_lid": "140", "retract_lift_below": ["0.2"], "retraction_length": ["0.8"], "retraction_speed": ["120"], "deretraction_speed": ["120"], - "wipe": ["0"], - "retract_before_wipe": ["0%"], + "wipe": ["1"], + "retract_before_wipe": ["70%"], + "z_hop": ["0.2"], "machine_start_gcode": "START_PRINT EXTRUDER_TEMP=[nozzle_temperature_initial_layer] BED_TEMP=[bed_temperature_initial_layer_single] TOTAL_LAYER_COUNT={total_layer_count} X0={first_layer_print_min[0]} Y0={first_layer_print_min[1]} X1={first_layer_print_max[0]} Y1={first_layer_print_max[1]}", - "layer_change_gcode": ";AFTER_LAYER_CHANGE\n;[layer_z]\nG92 E0\n_ON_LAYER_CHANGE LAYER={layer_num + 1}", - "before_layer_change_gcode": ";[layer_z]", + "layer_change_gcode": ";AFTER_LAYER_CHANGE\n;[layer_z]", + "before_layer_change_gcode": ";BEFORE_LAYER_CHANGE\n;[layer_z]\nG92 E0\n_ON_LAYER_CHANGE LAYER={layer_num + 1}", "change_filament_gcode": "M600", - "machine_pause_gcode": "M601", + "machine_pause_gcode": "PAUSE", "printing_by_object_gcode": ";BETWEEN_OBJECTS\nG92 E0", "printable_height": "300", "thumbnails": [ diff --git a/resources/profiles/Ratrig/machine/RatRig V-Core 4 300 0.6 nozzle.json b/resources/profiles/Ratrig/machine/RatRig V-Core 4 300 0.6 nozzle.json index b6033678690..bf8af7c54b0 100644 --- a/resources/profiles/Ratrig/machine/RatRig V-Core 4 300 0.6 nozzle.json +++ b/resources/profiles/Ratrig/machine/RatRig V-Core 4 300 0.6 nozzle.json @@ -18,7 +18,7 @@ ], "machine_max_speed_x": ["400", "400"], "machine_max_speed_y": ["400", "400"], - "machine_max_speed_z": ["50", "50"], + "machine_max_speed_z": ["200", "200"], "machine_max_speed_e": ["120", "120"], "machine_max_acceleration_x": ["10000", "10000"], "machine_max_acceleration_y": ["10000", "10000"], @@ -26,23 +26,28 @@ "machine_max_acceleration_e": ["5000", "5000"], "machine_max_acceleration_extruding": ["10000", "10000"], "machine_max_acceleration_retracting": ["10000", "10000"], + "machine_max_acceleration_travel": ["10000", "10000"], "machine_max_jerk_x": ["5", "5"], "machine_max_jerk_y": ["5", "5"], "machine_max_jerk_z": ["0.4", "0.4"], "machine_max_jerk_e": ["5", "5"], "min_layer_height": ["0.1"], "max_layer_height": ["0.4"], + "extruder_clearance_radius": "45", + "extruder_clearance_height_to_rod": "25", + "extruder_clearance_height_to_lid": "140", "retract_lift_below": ["0.3"], "retraction_length": ["0.8"], "retraction_speed": ["120"], "deretraction_speed": ["120"], - "wipe": ["0"], - "retract_before_wipe": ["0%"], + "wipe": ["1"], + "retract_before_wipe": ["70%"], + "z_hop": ["0.2"], "machine_start_gcode": "START_PRINT EXTRUDER_TEMP=[nozzle_temperature_initial_layer] BED_TEMP=[bed_temperature_initial_layer_single] TOTAL_LAYER_COUNT={total_layer_count} X0={first_layer_print_min[0]} Y0={first_layer_print_min[1]} X1={first_layer_print_max[0]} Y1={first_layer_print_max[1]}", - "layer_change_gcode": ";AFTER_LAYER_CHANGE\n;[layer_z]\nG92 E0\n_ON_LAYER_CHANGE LAYER={layer_num + 1}", - "before_layer_change_gcode": ";[layer_z]", + "layer_change_gcode": ";AFTER_LAYER_CHANGE\n;[layer_z]", + "before_layer_change_gcode": ";BEFORE_LAYER_CHANGE\n;[layer_z]\nG92 E0\n_ON_LAYER_CHANGE LAYER={layer_num + 1}", "change_filament_gcode": "M600", - "machine_pause_gcode": "M601", + "machine_pause_gcode": "PAUSE", "printing_by_object_gcode": ";BETWEEN_OBJECTS\nG92 E0", "printable_height": "300", "thumbnails": [ diff --git a/resources/profiles/Ratrig/machine/RatRig V-Core 4 400 0.4 nozzle.json b/resources/profiles/Ratrig/machine/RatRig V-Core 4 400 0.4 nozzle.json index f01c8a7c823..f95218ddb2d 100644 --- a/resources/profiles/Ratrig/machine/RatRig V-Core 4 400 0.4 nozzle.json +++ b/resources/profiles/Ratrig/machine/RatRig V-Core 4 400 0.4 nozzle.json @@ -18,7 +18,7 @@ ], "machine_max_speed_x": ["400", "400"], "machine_max_speed_y": ["400", "400"], - "machine_max_speed_z": ["50", "50"], + "machine_max_speed_z": ["200", "200"], "machine_max_speed_e": ["120", "120"], "machine_max_acceleration_x": ["10000", "10000"], "machine_max_acceleration_y": ["10000", "10000"], @@ -26,25 +26,30 @@ "machine_max_acceleration_e": ["5000", "5000"], "machine_max_acceleration_extruding": ["10000", "10000"], "machine_max_acceleration_retracting": ["10000", "10000"], + "machine_max_acceleration_travel": ["10000", "10000"], "machine_max_jerk_x": ["5", "5"], "machine_max_jerk_y": ["5", "5"], "machine_max_jerk_z": ["0.4", "0.4"], "machine_max_jerk_e": ["5", "5"], "min_layer_height": ["0.06"], "max_layer_height": ["0.3"], + "extruder_clearance_radius": "45", + "extruder_clearance_height_to_rod": "25", + "extruder_clearance_height_to_lid": "140", "retract_lift_below": ["0.2"], "retraction_length": ["0.8"], "retraction_speed": ["120"], "deretraction_speed": ["120"], - "wipe": ["0"], - "retract_before_wipe": ["0%"], + "wipe": ["1"], + "retract_before_wipe": ["70%"], + "z_hop": ["0.2"], "machine_start_gcode": "START_PRINT EXTRUDER_TEMP=[nozzle_temperature_initial_layer] BED_TEMP=[bed_temperature_initial_layer_single] TOTAL_LAYER_COUNT={total_layer_count} X0={first_layer_print_min[0]} Y0={first_layer_print_min[1]} X1={first_layer_print_max[0]} Y1={first_layer_print_max[1]}", - "layer_change_gcode": ";AFTER_LAYER_CHANGE\n;[layer_z]\nG92 E0\n_ON_LAYER_CHANGE LAYER={layer_num + 1}", - "before_layer_change_gcode": ";[layer_z]", + "layer_change_gcode": ";AFTER_LAYER_CHANGE\n;[layer_z]", + "before_layer_change_gcode": ";BEFORE_LAYER_CHANGE\n;[layer_z]\nG92 E0\n_ON_LAYER_CHANGE LAYER={layer_num + 1}", "change_filament_gcode": "M600", - "machine_pause_gcode": "M601", + "machine_pause_gcode": "PAUSE", "printing_by_object_gcode": ";BETWEEN_OBJECTS\nG92 E0", - "printable_height": "400", + "printable_height": "300", "thumbnails": [ "64x64/PNG", "400x300/PNG" diff --git a/resources/profiles/Ratrig/machine/RatRig V-Core 4 400 0.5 nozzle.json b/resources/profiles/Ratrig/machine/RatRig V-Core 4 400 0.5 nozzle.json index a43e8c82d52..7a69713f1b4 100644 --- a/resources/profiles/Ratrig/machine/RatRig V-Core 4 400 0.5 nozzle.json +++ b/resources/profiles/Ratrig/machine/RatRig V-Core 4 400 0.5 nozzle.json @@ -18,7 +18,7 @@ ], "machine_max_speed_x": ["400", "400"], "machine_max_speed_y": ["400", "400"], - "machine_max_speed_z": ["50", "50"], + "machine_max_speed_z": ["200", "200"], "machine_max_speed_e": ["120", "120"], "machine_max_acceleration_x": ["10000", "10000"], "machine_max_acceleration_y": ["10000", "10000"], @@ -26,25 +26,30 @@ "machine_max_acceleration_e": ["5000", "5000"], "machine_max_acceleration_extruding": ["10000", "10000"], "machine_max_acceleration_retracting": ["10000", "10000"], + "machine_max_acceleration_travel": ["10000", "10000"], "machine_max_jerk_x": ["5", "5"], "machine_max_jerk_y": ["5", "5"], "machine_max_jerk_z": ["0.4", "0.4"], "machine_max_jerk_e": ["5", "5"], "min_layer_height": ["0.07"], "max_layer_height": ["0.32"], + "extruder_clearance_radius": "45", + "extruder_clearance_height_to_rod": "25", + "extruder_clearance_height_to_lid": "140", "retract_lift_below": ["0.2"], "retraction_length": ["0.8"], "retraction_speed": ["120"], "deretraction_speed": ["120"], - "wipe": ["0"], - "retract_before_wipe": ["0%"], + "wipe": ["1"], + "retract_before_wipe": ["70%"], + "z_hop": ["0.2"], "machine_start_gcode": "START_PRINT EXTRUDER_TEMP=[nozzle_temperature_initial_layer] BED_TEMP=[bed_temperature_initial_layer_single] TOTAL_LAYER_COUNT={total_layer_count} X0={first_layer_print_min[0]} Y0={first_layer_print_min[1]} X1={first_layer_print_max[0]} Y1={first_layer_print_max[1]}", - "layer_change_gcode": ";AFTER_LAYER_CHANGE\n;[layer_z]\nG92 E0\n_ON_LAYER_CHANGE LAYER={layer_num + 1}", - "before_layer_change_gcode": ";[layer_z]", + "layer_change_gcode": ";AFTER_LAYER_CHANGE\n;[layer_z]", + "before_layer_change_gcode": ";BEFORE_LAYER_CHANGE\n;[layer_z]\nG92 E0\n_ON_LAYER_CHANGE LAYER={layer_num + 1}", "change_filament_gcode": "M600", - "machine_pause_gcode": "M601", + "machine_pause_gcode": "PAUSE", "printing_by_object_gcode": ";BETWEEN_OBJECTS\nG92 E0", - "printable_height": "400", + "printable_height": "300", "thumbnails": [ "64x64/PNG", "400x300/PNG" diff --git a/resources/profiles/Ratrig/machine/RatRig V-Core 4 400 0.6 nozzle.json b/resources/profiles/Ratrig/machine/RatRig V-Core 4 400 0.6 nozzle.json index b47ecf033b3..8a2c557b01c 100644 --- a/resources/profiles/Ratrig/machine/RatRig V-Core 4 400 0.6 nozzle.json +++ b/resources/profiles/Ratrig/machine/RatRig V-Core 4 400 0.6 nozzle.json @@ -18,7 +18,7 @@ ], "machine_max_speed_x": ["400", "400"], "machine_max_speed_y": ["400", "400"], - "machine_max_speed_z": ["50", "50"], + "machine_max_speed_z": ["200", "200"], "machine_max_speed_e": ["120", "120"], "machine_max_acceleration_x": ["10000", "10000"], "machine_max_acceleration_y": ["10000", "10000"], @@ -26,26 +26,30 @@ "machine_max_acceleration_e": ["5000", "5000"], "machine_max_acceleration_extruding": ["10000", "10000"], "machine_max_acceleration_retracting": ["10000", "10000"], + "machine_max_acceleration_travel": ["10000", "10000"], "machine_max_jerk_x": ["5", "5"], "machine_max_jerk_y": ["5", "5"], "machine_max_jerk_z": ["0.4", "0.4"], "machine_max_jerk_e": ["5", "5"], "min_layer_height": ["0.1"], "max_layer_height": ["0.4"], + "extruder_clearance_radius": "45", + "extruder_clearance_height_to_rod": "25", + "extruder_clearance_height_to_lid": "140", "retract_lift_below": ["0.3"], "retraction_length": ["2"], "retraction_speed": ["40"], - "deretraction_speed": ["0"], - "retraction_minimum_travel": ["2"], - "wipe": ["0"], - "retract_before_wipe": ["0%"], + "deretraction_speed": ["120"], + "wipe": ["1"], + "retract_before_wipe": ["70%"], + "z_hop": ["0.2"], "machine_start_gcode": "START_PRINT EXTRUDER_TEMP=[nozzle_temperature_initial_layer] BED_TEMP=[bed_temperature_initial_layer_single] TOTAL_LAYER_COUNT={total_layer_count} X0={first_layer_print_min[0]} Y0={first_layer_print_min[1]} X1={first_layer_print_max[0]} Y1={first_layer_print_max[1]}", - "layer_change_gcode": ";AFTER_LAYER_CHANGE\n;[layer_z]\nG92 E0\n_ON_LAYER_CHANGE LAYER={layer_num + 1}", - "before_layer_change_gcode": ";[layer_z]", + "layer_change_gcode": ";AFTER_LAYER_CHANGE\n;[layer_z]", + "before_layer_change_gcode": ";BEFORE_LAYER_CHANGE\n;[layer_z]\nG92 E0\n_ON_LAYER_CHANGE LAYER={layer_num + 1}", "change_filament_gcode": "M600", - "machine_pause_gcode": "M601", + "machine_pause_gcode": "PAUSE", "printing_by_object_gcode": ";BETWEEN_OBJECTS\nG92 E0", - "printable_height": "400", + "printable_height": "300", "thumbnails": [ "64x64/PNG", "400x300/PNG" diff --git a/resources/profiles/Ratrig/machine/RatRig V-Core 4 500 0.4 nozzle.json b/resources/profiles/Ratrig/machine/RatRig V-Core 4 500 0.4 nozzle.json index b1d3c11ff3a..d89467a3b42 100644 --- a/resources/profiles/Ratrig/machine/RatRig V-Core 4 500 0.4 nozzle.json +++ b/resources/profiles/Ratrig/machine/RatRig V-Core 4 500 0.4 nozzle.json @@ -18,7 +18,7 @@ ], "machine_max_speed_x": ["400", "400"], "machine_max_speed_y": ["400", "400"], - "machine_max_speed_z": ["50", "50"], + "machine_max_speed_z": ["200", "200"], "machine_max_speed_e": ["120", "120"], "machine_max_acceleration_x": ["10000", "10000"], "machine_max_acceleration_y": ["10000", "10000"], @@ -26,25 +26,30 @@ "machine_max_acceleration_e": ["5000", "5000"], "machine_max_acceleration_extruding": ["10000", "10000"], "machine_max_acceleration_retracting": ["10000", "10000"], + "machine_max_acceleration_travel": ["10000", "10000"], "machine_max_jerk_x": ["5", "5"], "machine_max_jerk_y": ["5", "5"], "machine_max_jerk_z": ["0.4", "0.4"], "machine_max_jerk_e": ["5", "5"], "min_layer_height": ["0.06"], "max_layer_height": ["0.3"], + "extruder_clearance_radius": "45", + "extruder_clearance_height_to_rod": "25", + "extruder_clearance_height_to_lid": "140", "retract_lift_below": ["0.2"], "retraction_length": ["0.8"], "retraction_speed": ["120"], "deretraction_speed": ["120"], - "wipe": ["0"], - "retract_before_wipe": ["0%"], + "wipe": ["1"], + "retract_before_wipe": ["70%"], + "z_hop": ["0.2"], "machine_start_gcode": "START_PRINT EXTRUDER_TEMP=[nozzle_temperature_initial_layer] BED_TEMP=[bed_temperature_initial_layer_single] TOTAL_LAYER_COUNT={total_layer_count} X0={first_layer_print_min[0]} Y0={first_layer_print_min[1]} X1={first_layer_print_max[0]} Y1={first_layer_print_max[1]}", - "layer_change_gcode": ";AFTER_LAYER_CHANGE\n;[layer_z]\nG92 E0\n_ON_LAYER_CHANGE LAYER={layer_num + 1}", - "before_layer_change_gcode": ";[layer_z]", + "layer_change_gcode": ";AFTER_LAYER_CHANGE\n;[layer_z]", + "before_layer_change_gcode": ";BEFORE_LAYER_CHANGE\n;[layer_z]\nG92 E0\n_ON_LAYER_CHANGE LAYER={layer_num + 1}", "change_filament_gcode": "M600", - "machine_pause_gcode": "M601", + "machine_pause_gcode": "PAUSE", "printing_by_object_gcode": ";BETWEEN_OBJECTS\nG92 E0", - "printable_height": "500", + "printable_height": "300", "thumbnails": [ "64x64/PNG", "400x300/PNG" diff --git a/resources/profiles/Ratrig/machine/RatRig V-Core 4 500 0.5 nozzle.json b/resources/profiles/Ratrig/machine/RatRig V-Core 4 500 0.5 nozzle.json index 46643c68c8c..96618aa218a 100644 --- a/resources/profiles/Ratrig/machine/RatRig V-Core 4 500 0.5 nozzle.json +++ b/resources/profiles/Ratrig/machine/RatRig V-Core 4 500 0.5 nozzle.json @@ -18,7 +18,7 @@ ], "machine_max_speed_x": ["400", "400"], "machine_max_speed_y": ["400", "400"], - "machine_max_speed_z": ["50", "50"], + "machine_max_speed_z": ["200", "200"], "machine_max_speed_e": ["120", "120"], "machine_max_acceleration_x": ["10000", "10000"], "machine_max_acceleration_y": ["10000", "10000"], @@ -26,25 +26,30 @@ "machine_max_acceleration_e": ["5000", "5000"], "machine_max_acceleration_extruding": ["10000", "10000"], "machine_max_acceleration_retracting": ["10000", "10000"], + "machine_max_acceleration_travel": ["10000", "10000"], "machine_max_jerk_x": ["5", "5"], "machine_max_jerk_y": ["5", "5"], "machine_max_jerk_z": ["0.4", "0.4"], "machine_max_jerk_e": ["5", "5"], "min_layer_height": ["0.07"], "max_layer_height": ["0.32"], + "extruder_clearance_radius": "45", + "extruder_clearance_height_to_rod": "25", + "extruder_clearance_height_to_lid": "140", "retract_lift_below": ["0.25"], "retraction_length": ["0.8"], "retraction_speed": ["120"], "deretraction_speed": ["120"], - "wipe": ["0"], - "retract_before_wipe": ["0%"], + "wipe": ["1"], + "retract_before_wipe": ["70%"], + "z_hop": ["0.2"], "machine_start_gcode": "START_PRINT EXTRUDER_TEMP=[nozzle_temperature_initial_layer] BED_TEMP=[bed_temperature_initial_layer_single] TOTAL_LAYER_COUNT={total_layer_count} X0={first_layer_print_min[0]} Y0={first_layer_print_min[1]} X1={first_layer_print_max[0]} Y1={first_layer_print_max[1]}", - "layer_change_gcode": ";AFTER_LAYER_CHANGE\n;[layer_z]\nG92 E0\n_ON_LAYER_CHANGE LAYER={layer_num + 1}", - "before_layer_change_gcode": ";[layer_z]", + "layer_change_gcode": ";AFTER_LAYER_CHANGE\n;[layer_z]", + "before_layer_change_gcode": ";BEFORE_LAYER_CHANGE\n;[layer_z]\nG92 E0\n_ON_LAYER_CHANGE LAYER={layer_num + 1}", "change_filament_gcode": "M600", - "machine_pause_gcode": "M601", + "machine_pause_gcode": "PAUSE", "printing_by_object_gcode": ";BETWEEN_OBJECTS\nG92 E0", - "printable_height": "500", + "printable_height": "300", "thumbnails": [ "64x64/PNG", "400x300/PNG" diff --git a/resources/profiles/Ratrig/machine/RatRig V-Core 4 500 0.6 nozzle.json b/resources/profiles/Ratrig/machine/RatRig V-Core 4 500 0.6 nozzle.json index aef0823eaca..adb004f782a 100644 --- a/resources/profiles/Ratrig/machine/RatRig V-Core 4 500 0.6 nozzle.json +++ b/resources/profiles/Ratrig/machine/RatRig V-Core 4 500 0.6 nozzle.json @@ -18,7 +18,7 @@ ], "machine_max_speed_x": ["400", "400"], "machine_max_speed_y": ["400", "400"], - "machine_max_speed_z": ["50", "50"], + "machine_max_speed_z": ["200", "200"], "machine_max_speed_e": ["120", "120"], "machine_max_acceleration_x": ["10000", "10000"], "machine_max_acceleration_y": ["10000", "10000"], @@ -26,25 +26,30 @@ "machine_max_acceleration_e": ["5000", "5000"], "machine_max_acceleration_extruding": ["10000", "10000"], "machine_max_acceleration_retracting": ["10000", "10000"], + "machine_max_acceleration_travel": ["10000", "10000"], "machine_max_jerk_x": ["5", "5"], "machine_max_jerk_y": ["5", "5"], "machine_max_jerk_z": ["0.4", "0.4"], "machine_max_jerk_e": ["5", "5"], "min_layer_height": ["0.1"], "max_layer_height": ["0.4"], + "extruder_clearance_radius": "45", + "extruder_clearance_height_to_rod": "25", + "extruder_clearance_height_to_lid": "140", "retract_lift_below": ["0.3"], "retraction_length": ["0.8"], "retraction_speed": ["120"], "deretraction_speed": ["120"], - "wipe": ["0"], - "retract_before_wipe": ["0%"], + "wipe": ["1"], + "retract_before_wipe": ["70%"], + "z_hop": ["0.2"], "machine_start_gcode": "START_PRINT EXTRUDER_TEMP=[nozzle_temperature_initial_layer] BED_TEMP=[bed_temperature_initial_layer_single] TOTAL_LAYER_COUNT={total_layer_count} X0={first_layer_print_min[0]} Y0={first_layer_print_min[1]} X1={first_layer_print_max[0]} Y1={first_layer_print_max[1]}", - "layer_change_gcode": ";AFTER_LAYER_CHANGE\n;[layer_z]\nG92 E0\n_ON_LAYER_CHANGE LAYER={layer_num + 1}", - "before_layer_change_gcode": ";[layer_z]", + "layer_change_gcode": ";AFTER_LAYER_CHANGE\n;[layer_z]", + "before_layer_change_gcode": ";BEFORE_LAYER_CHANGE\n;[layer_z]\nG92 E0\n_ON_LAYER_CHANGE LAYER={layer_num + 1}", "change_filament_gcode": "M600", - "machine_pause_gcode": "M601", + "machine_pause_gcode": "PAUSE", "printing_by_object_gcode": ";BETWEEN_OBJECTS\nG92 E0", - "printable_height": "500", + "printable_height": "300", "thumbnails": [ "64x64/PNG", "400x300/PNG" diff --git a/resources/profiles/Ratrig/machine/RatRig V-Core 4 HYBRID 300 0.4 nozzle.json b/resources/profiles/Ratrig/machine/RatRig V-Core 4 HYBRID 300 0.4 nozzle.json index a5687013718..19592514fd1 100644 --- a/resources/profiles/Ratrig/machine/RatRig V-Core 4 HYBRID 300 0.4 nozzle.json +++ b/resources/profiles/Ratrig/machine/RatRig V-Core 4 HYBRID 300 0.4 nozzle.json @@ -18,31 +18,36 @@ ], "machine_max_speed_x": ["500", "500"], "machine_max_speed_y": ["500", "500"], - "machine_max_speed_z": ["50", "50"], + "machine_max_speed_z": ["200", "200"], "machine_max_speed_e": ["120", "120"], "machine_max_acceleration_x": ["20000", "20000"], "machine_max_acceleration_y": ["20000", "20000"], "machine_max_acceleration_z": ["200", "200"], - "machine_max_acceleration_e": ["20000", "20000"], + "machine_max_acceleration_e": ["5000", "5000"], "machine_max_acceleration_extruding": ["20000", "20000"], "machine_max_acceleration_retracting": ["20000", "20000"], + "machine_max_acceleration_travel": ["20000", "20000"], "machine_max_jerk_x": ["5", "5"], "machine_max_jerk_y": ["5", "5"], "machine_max_jerk_z": ["0.4", "0.4"], "machine_max_jerk_e": ["5", "5"], "min_layer_height": ["0.06"], "max_layer_height": ["0.3"], + "extruder_clearance_radius": "45", + "extruder_clearance_height_to_rod": "25", + "extruder_clearance_height_to_lid": "140", "retract_lift_below": ["0.2"], "retraction_length": ["0.8"], "retraction_speed": ["120"], "deretraction_speed": ["120"], - "wipe": ["0"], - "retract_before_wipe": ["0%"], + "wipe": ["1"], + "retract_before_wipe": ["70%"], + "z_hop": ["0.2"], "machine_start_gcode": "START_PRINT EXTRUDER_TEMP=[nozzle_temperature_initial_layer] BED_TEMP=[bed_temperature_initial_layer_single] TOTAL_LAYER_COUNT={total_layer_count} X0={first_layer_print_min[0]} Y0={first_layer_print_min[1]} X1={first_layer_print_max[0]} Y1={first_layer_print_max[1]}", - "layer_change_gcode": ";AFTER_LAYER_CHANGE\n;[layer_z]\nG92 E0\n_ON_LAYER_CHANGE LAYER={layer_num + 1}", - "before_layer_change_gcode": ";[layer_z]", + "layer_change_gcode": ";AFTER_LAYER_CHANGE\n;[layer_z]", + "before_layer_change_gcode": ";BEFORE_LAYER_CHANGE\n;[layer_z]\nG92 E0\n_ON_LAYER_CHANGE LAYER={layer_num + 1}", "change_filament_gcode": "M600", - "machine_pause_gcode": "M601", + "machine_pause_gcode": "PAUSE", "printing_by_object_gcode": ";BETWEEN_OBJECTS\nG92 E0", "printable_height": "300", "thumbnails": [ diff --git a/resources/profiles/Ratrig/machine/RatRig V-Core 4 HYBRID 300 0.5 nozzle.json b/resources/profiles/Ratrig/machine/RatRig V-Core 4 HYBRID 300 0.5 nozzle.json index d4277f77614..5f3449a306d 100644 --- a/resources/profiles/Ratrig/machine/RatRig V-Core 4 HYBRID 300 0.5 nozzle.json +++ b/resources/profiles/Ratrig/machine/RatRig V-Core 4 HYBRID 300 0.5 nozzle.json @@ -18,31 +18,36 @@ ], "machine_max_speed_x": ["500", "500"], "machine_max_speed_y": ["500", "500"], - "machine_max_speed_z": ["50", "50"], + "machine_max_speed_z": ["200", "200"], "machine_max_speed_e": ["120", "120"], "machine_max_acceleration_x": ["20000", "20000"], "machine_max_acceleration_y": ["20000", "20000"], "machine_max_acceleration_z": ["200", "200"], - "machine_max_acceleration_e": ["20000", "20000"], + "machine_max_acceleration_e": ["5000", "5000"], "machine_max_acceleration_extruding": ["20000", "20000"], "machine_max_acceleration_retracting": ["20000", "20000"], + "machine_max_acceleration_travel": ["20000", "20000"], "machine_max_jerk_x": ["5", "5"], "machine_max_jerk_y": ["5", "5"], "machine_max_jerk_z": ["0.4", "0.4"], "machine_max_jerk_e": ["5", "5"], "min_layer_height": ["0.07"], "max_layer_height": ["0.32"], + "extruder_clearance_radius": "45", + "extruder_clearance_height_to_rod": "25", + "extruder_clearance_height_to_lid": "140", "retract_lift_below": ["0.2"], "retraction_length": ["0.8"], "retraction_speed": ["120"], "deretraction_speed": ["120"], - "wipe": ["0"], - "retract_before_wipe": ["0%"], + "wipe": ["1"], + "retract_before_wipe": ["70%"], + "z_hop": ["0.2"], "machine_start_gcode": "START_PRINT EXTRUDER_TEMP=[nozzle_temperature_initial_layer] BED_TEMP=[bed_temperature_initial_layer_single] TOTAL_LAYER_COUNT={total_layer_count} X0={first_layer_print_min[0]} Y0={first_layer_print_min[1]} X1={first_layer_print_max[0]} Y1={first_layer_print_max[1]}", - "layer_change_gcode": ";AFTER_LAYER_CHANGE\n;[layer_z]\nG92 E0\n_ON_LAYER_CHANGE LAYER={layer_num + 1}", - "before_layer_change_gcode": ";[layer_z]", + "layer_change_gcode": ";AFTER_LAYER_CHANGE\n;[layer_z]", + "before_layer_change_gcode": ";BEFORE_LAYER_CHANGE\n;[layer_z]\nG92 E0\n_ON_LAYER_CHANGE LAYER={layer_num + 1}", "change_filament_gcode": "M600", - "machine_pause_gcode": "M601", + "machine_pause_gcode": "PAUSE", "printing_by_object_gcode": ";BETWEEN_OBJECTS\nG92 E0", "printable_height": "300", "thumbnails": [ diff --git a/resources/profiles/Ratrig/machine/RatRig V-Core 4 HYBRID 300 0.6 nozzle.json b/resources/profiles/Ratrig/machine/RatRig V-Core 4 HYBRID 300 0.6 nozzle.json index 9f5d69299af..951606077f2 100644 --- a/resources/profiles/Ratrig/machine/RatRig V-Core 4 HYBRID 300 0.6 nozzle.json +++ b/resources/profiles/Ratrig/machine/RatRig V-Core 4 HYBRID 300 0.6 nozzle.json @@ -18,31 +18,36 @@ ], "machine_max_speed_x": ["500", "500"], "machine_max_speed_y": ["500", "500"], - "machine_max_speed_z": ["50", "50"], + "machine_max_speed_z": ["200", "200"], "machine_max_speed_e": ["120", "120"], "machine_max_acceleration_x": ["20000", "20000"], "machine_max_acceleration_y": ["20000", "20000"], "machine_max_acceleration_z": ["200", "200"], - "machine_max_acceleration_e": ["20000", "20000"], + "machine_max_acceleration_e": ["5000", "5000"], "machine_max_acceleration_extruding": ["20000", "20000"], "machine_max_acceleration_retracting": ["20000", "20000"], + "machine_max_acceleration_travel": ["20000", "20000"], "machine_max_jerk_x": ["5", "5"], "machine_max_jerk_y": ["5", "5"], "machine_max_jerk_z": ["0.4", "0.4"], "machine_max_jerk_e": ["5", "5"], "min_layer_height": ["0.1"], "max_layer_height": ["0.4"], + "extruder_clearance_radius": "45", + "extruder_clearance_height_to_rod": "25", + "extruder_clearance_height_to_lid": "140", "retract_lift_below": ["0.3"], "retraction_length": ["0.8"], "retraction_speed": ["120"], "deretraction_speed": ["120"], - "wipe": ["0"], - "retract_before_wipe": ["0%"], + "wipe": ["1"], + "retract_before_wipe": ["70%"], + "z_hop": ["0.2"], "machine_start_gcode": "START_PRINT EXTRUDER_TEMP=[nozzle_temperature_initial_layer] BED_TEMP=[bed_temperature_initial_layer_single] TOTAL_LAYER_COUNT={total_layer_count} X0={first_layer_print_min[0]} Y0={first_layer_print_min[1]} X1={first_layer_print_max[0]} Y1={first_layer_print_max[1]}", - "layer_change_gcode": ";AFTER_LAYER_CHANGE\n;[layer_z]\nG92 E0\n_ON_LAYER_CHANGE LAYER={layer_num + 1}", - "before_layer_change_gcode": ";[layer_z]", + "layer_change_gcode": ";AFTER_LAYER_CHANGE\n;[layer_z]", + "before_layer_change_gcode": ";BEFORE_LAYER_CHANGE\n;[layer_z]\nG92 E0\n_ON_LAYER_CHANGE LAYER={layer_num + 1}", "change_filament_gcode": "M600", - "machine_pause_gcode": "M601", + "machine_pause_gcode": "PAUSE", "printing_by_object_gcode": ";BETWEEN_OBJECTS\nG92 E0", "printable_height": "300", "thumbnails": [ diff --git a/resources/profiles/Ratrig/machine/RatRig V-Core 4 HYBRID 400 0.4 nozzle.json b/resources/profiles/Ratrig/machine/RatRig V-Core 4 HYBRID 400 0.4 nozzle.json index 40c56a1db0f..84f7796e4d7 100644 --- a/resources/profiles/Ratrig/machine/RatRig V-Core 4 HYBRID 400 0.4 nozzle.json +++ b/resources/profiles/Ratrig/machine/RatRig V-Core 4 HYBRID 400 0.4 nozzle.json @@ -18,31 +18,36 @@ ], "machine_max_speed_x": ["500", "500"], "machine_max_speed_y": ["500", "500"], - "machine_max_speed_z": ["50", "50"], + "machine_max_speed_z": ["200", "200"], "machine_max_speed_e": ["120", "120"], "machine_max_acceleration_x": ["20000", "20000"], "machine_max_acceleration_y": ["20000", "20000"], "machine_max_acceleration_z": ["200", "200"], - "machine_max_acceleration_e": ["20000", "20000"], + "machine_max_acceleration_e": ["5000", "5000"], "machine_max_acceleration_extruding": ["20000", "20000"], "machine_max_acceleration_retracting": ["20000", "20000"], + "machine_max_acceleration_travel": ["20000", "20000"], "machine_max_jerk_x": ["5", "5"], "machine_max_jerk_y": ["5", "5"], "machine_max_jerk_z": ["0.4", "0.4"], "machine_max_jerk_e": ["5", "5"], "min_layer_height": ["0.06"], "max_layer_height": ["0.3"], + "extruder_clearance_radius": "45", + "extruder_clearance_height_to_rod": "25", + "extruder_clearance_height_to_lid": "140", "retract_lift_below": ["0.2"], "retraction_length": ["0.8"], "retraction_speed": ["120"], "deretraction_speed": ["120"], - "wipe": ["0"], - "retract_before_wipe": ["0%"], + "wipe": ["1"], + "retract_before_wipe": ["70%"], + "z_hop": ["0.2"], "machine_start_gcode": "START_PRINT EXTRUDER_TEMP=[nozzle_temperature_initial_layer] BED_TEMP=[bed_temperature_initial_layer_single] TOTAL_LAYER_COUNT={total_layer_count} X0={first_layer_print_min[0]} Y0={first_layer_print_min[1]} X1={first_layer_print_max[0]} Y1={first_layer_print_max[1]}", - "layer_change_gcode": ";AFTER_LAYER_CHANGE\n;[layer_z]\nG92 E0\n_ON_LAYER_CHANGE LAYER={layer_num + 1}", - "before_layer_change_gcode": ";[layer_z]", + "layer_change_gcode": ";AFTER_LAYER_CHANGE\n;[layer_z]", + "before_layer_change_gcode": ";BEFORE_LAYER_CHANGE\n;[layer_z]\nG92 E0\n_ON_LAYER_CHANGE LAYER={layer_num + 1}", "change_filament_gcode": "M600", - "machine_pause_gcode": "M601", + "machine_pause_gcode": "PAUSE", "printing_by_object_gcode": ";BETWEEN_OBJECTS\nG92 E0", "printable_height": "400", "thumbnails": [ diff --git a/resources/profiles/Ratrig/machine/RatRig V-Core 4 HYBRID 400 0.5 nozzle.json b/resources/profiles/Ratrig/machine/RatRig V-Core 4 HYBRID 400 0.5 nozzle.json index ca973935020..9adc92b1e0e 100644 --- a/resources/profiles/Ratrig/machine/RatRig V-Core 4 HYBRID 400 0.5 nozzle.json +++ b/resources/profiles/Ratrig/machine/RatRig V-Core 4 HYBRID 400 0.5 nozzle.json @@ -18,31 +18,36 @@ ], "machine_max_speed_x": ["500", "500"], "machine_max_speed_y": ["500", "500"], - "machine_max_speed_z": ["50", "50"], + "machine_max_speed_z": ["200", "200"], "machine_max_speed_e": ["120", "120"], "machine_max_acceleration_x": ["20000", "20000"], "machine_max_acceleration_y": ["20000", "20000"], "machine_max_acceleration_z": ["200", "200"], - "machine_max_acceleration_e": ["20000", "20000"], + "machine_max_acceleration_e": ["5000", "5000"], "machine_max_acceleration_extruding": ["20000", "20000"], "machine_max_acceleration_retracting": ["20000", "20000"], + "machine_max_acceleration_travel": ["20000", "20000"], "machine_max_jerk_x": ["5", "5"], "machine_max_jerk_y": ["5", "5"], "machine_max_jerk_z": ["0.4", "0.4"], "machine_max_jerk_e": ["5", "5"], "min_layer_height": ["0.07"], "max_layer_height": ["0.32"], + "extruder_clearance_radius": "45", + "extruder_clearance_height_to_rod": "25", + "extruder_clearance_height_to_lid": "140", "retract_lift_below": ["0.2"], "retraction_length": ["0.8"], "retraction_speed": ["120"], "deretraction_speed": ["120"], - "wipe": ["0"], - "retract_before_wipe": ["0%"], + "wipe": ["1"], + "retract_before_wipe": ["70%"], + "z_hop": ["0.2"], "machine_start_gcode": "START_PRINT EXTRUDER_TEMP=[nozzle_temperature_initial_layer] BED_TEMP=[bed_temperature_initial_layer_single] TOTAL_LAYER_COUNT={total_layer_count} X0={first_layer_print_min[0]} Y0={first_layer_print_min[1]} X1={first_layer_print_max[0]} Y1={first_layer_print_max[1]}", - "layer_change_gcode": ";AFTER_LAYER_CHANGE\n;[layer_z]\nG92 E0\n_ON_LAYER_CHANGE LAYER={layer_num + 1}", - "before_layer_change_gcode": ";[layer_z]", + "layer_change_gcode": ";AFTER_LAYER_CHANGE\n;[layer_z]", + "before_layer_change_gcode": ";BEFORE_LAYER_CHANGE\n;[layer_z]\nG92 E0\n_ON_LAYER_CHANGE LAYER={layer_num + 1}", "change_filament_gcode": "M600", - "machine_pause_gcode": "M601", + "machine_pause_gcode": "PAUSE", "printing_by_object_gcode": ";BETWEEN_OBJECTS\nG92 E0", "printable_height": "400", "thumbnails": [ diff --git a/resources/profiles/Ratrig/machine/RatRig V-Core 4 HYBRID 400 0.6 nozzle.json b/resources/profiles/Ratrig/machine/RatRig V-Core 4 HYBRID 400 0.6 nozzle.json index f26750c4f48..7af4004b0d9 100644 --- a/resources/profiles/Ratrig/machine/RatRig V-Core 4 HYBRID 400 0.6 nozzle.json +++ b/resources/profiles/Ratrig/machine/RatRig V-Core 4 HYBRID 400 0.6 nozzle.json @@ -18,31 +18,36 @@ ], "machine_max_speed_x": ["500", "500"], "machine_max_speed_y": ["500", "500"], - "machine_max_speed_z": ["50", "50"], + "machine_max_speed_z": ["200", "200"], "machine_max_speed_e": ["120", "120"], "machine_max_acceleration_x": ["20000", "20000"], "machine_max_acceleration_y": ["20000", "20000"], "machine_max_acceleration_z": ["200", "200"], - "machine_max_acceleration_e": ["20000", "20000"], + "machine_max_acceleration_e": ["5000", "5000"], "machine_max_acceleration_extruding": ["20000", "20000"], "machine_max_acceleration_retracting": ["20000", "20000"], + "machine_max_acceleration_travel": ["20000", "20000"], "machine_max_jerk_x": ["5", "5"], "machine_max_jerk_y": ["5", "5"], "machine_max_jerk_z": ["0.4", "0.4"], "machine_max_jerk_e": ["5", "5"], "min_layer_height": ["0.1"], "max_layer_height": ["0.4"], + "extruder_clearance_radius": "45", + "extruder_clearance_height_to_rod": "25", + "extruder_clearance_height_to_lid": "140", "retract_lift_below": ["0.3"], "retraction_length": ["0.8"], "retraction_speed": ["120"], "deretraction_speed": ["120"], - "wipe": ["0"], - "retract_before_wipe": ["0%"], + "wipe": ["1"], + "retract_before_wipe": ["70%"], + "z_hop": ["0.2"], "machine_start_gcode": "START_PRINT EXTRUDER_TEMP=[nozzle_temperature_initial_layer] BED_TEMP=[bed_temperature_initial_layer_single] TOTAL_LAYER_COUNT={total_layer_count} X0={first_layer_print_min[0]} Y0={first_layer_print_min[1]} X1={first_layer_print_max[0]} Y1={first_layer_print_max[1]}", - "layer_change_gcode": ";AFTER_LAYER_CHANGE\n;[layer_z]\nG92 E0\n_ON_LAYER_CHANGE LAYER={layer_num + 1}", - "before_layer_change_gcode": ";[layer_z]", + "layer_change_gcode": ";AFTER_LAYER_CHANGE\n;[layer_z]", + "before_layer_change_gcode": ";BEFORE_LAYER_CHANGE\n;[layer_z]\nG92 E0\n_ON_LAYER_CHANGE LAYER={layer_num + 1}", "change_filament_gcode": "M600", - "machine_pause_gcode": "M601", + "machine_pause_gcode": "PAUSE", "printing_by_object_gcode": ";BETWEEN_OBJECTS\nG92 E0", "printable_height": "400", "thumbnails": [ diff --git a/resources/profiles/Ratrig/machine/RatRig V-Core 4 HYBRID 500 0.4 nozzle.json b/resources/profiles/Ratrig/machine/RatRig V-Core 4 HYBRID 500 0.4 nozzle.json index 08eae27e320..845752dee6a 100644 --- a/resources/profiles/Ratrig/machine/RatRig V-Core 4 HYBRID 500 0.4 nozzle.json +++ b/resources/profiles/Ratrig/machine/RatRig V-Core 4 HYBRID 500 0.4 nozzle.json @@ -18,31 +18,36 @@ ], "machine_max_speed_x": ["500", "500"], "machine_max_speed_y": ["500", "500"], - "machine_max_speed_z": ["50", "50"], + "machine_max_speed_z": ["200", "200"], "machine_max_speed_e": ["120", "120"], "machine_max_acceleration_x": ["20000", "20000"], "machine_max_acceleration_y": ["20000", "20000"], "machine_max_acceleration_z": ["200", "200"], - "machine_max_acceleration_e": ["20000", "20000"], + "machine_max_acceleration_e": ["5000", "5000"], "machine_max_acceleration_extruding": ["20000", "20000"], "machine_max_acceleration_retracting": ["20000", "20000"], + "machine_max_acceleration_travel": ["20000", "20000"], "machine_max_jerk_x": ["5", "5"], "machine_max_jerk_y": ["5", "5"], "machine_max_jerk_z": ["0.4", "0.4"], "machine_max_jerk_e": ["5", "5"], "min_layer_height": ["0.06"], "max_layer_height": ["0.3"], + "extruder_clearance_radius": "45", + "extruder_clearance_height_to_rod": "25", + "extruder_clearance_height_to_lid": "140", "retract_lift_below": ["0.2"], "retraction_length": ["0.8"], "retraction_speed": ["120"], "deretraction_speed": ["120"], - "wipe": ["0"], - "retract_before_wipe": ["0%"], + "wipe": ["1"], + "retract_before_wipe": ["70%"], + "z_hop": ["0.2"], "machine_start_gcode": "START_PRINT EXTRUDER_TEMP=[nozzle_temperature_initial_layer] BED_TEMP=[bed_temperature_initial_layer_single] TOTAL_LAYER_COUNT={total_layer_count} X0={first_layer_print_min[0]} Y0={first_layer_print_min[1]} X1={first_layer_print_max[0]} Y1={first_layer_print_max[1]}", - "layer_change_gcode": ";AFTER_LAYER_CHANGE\n;[layer_z]\nG92 E0\n_ON_LAYER_CHANGE LAYER={layer_num + 1}", - "before_layer_change_gcode": ";[layer_z]", + "layer_change_gcode": ";AFTER_LAYER_CHANGE\n;[layer_z]", + "before_layer_change_gcode": ";BEFORE_LAYER_CHANGE\n;[layer_z]\nG92 E0\n_ON_LAYER_CHANGE LAYER={layer_num + 1}", "change_filament_gcode": "M600", - "machine_pause_gcode": "M601", + "machine_pause_gcode": "PAUSE", "printing_by_object_gcode": ";BETWEEN_OBJECTS\nG92 E0", "printable_height": "500", "thumbnails": [ diff --git a/resources/profiles/Ratrig/machine/RatRig V-Core 4 HYBRID 500 0.5 nozzle.json b/resources/profiles/Ratrig/machine/RatRig V-Core 4 HYBRID 500 0.5 nozzle.json index 58a7ef937ec..9eb0716da33 100644 --- a/resources/profiles/Ratrig/machine/RatRig V-Core 4 HYBRID 500 0.5 nozzle.json +++ b/resources/profiles/Ratrig/machine/RatRig V-Core 4 HYBRID 500 0.5 nozzle.json @@ -18,31 +18,36 @@ ], "machine_max_speed_x": ["500", "500"], "machine_max_speed_y": ["500", "500"], - "machine_max_speed_z": ["50", "50"], + "machine_max_speed_z": ["200", "200"], "machine_max_speed_e": ["120", "120"], "machine_max_acceleration_x": ["20000", "20000"], "machine_max_acceleration_y": ["20000", "20000"], "machine_max_acceleration_z": ["200", "200"], - "machine_max_acceleration_e": ["20000", "20000"], + "machine_max_acceleration_e": ["5000", "5000"], "machine_max_acceleration_extruding": ["20000", "20000"], "machine_max_acceleration_retracting": ["20000", "20000"], + "machine_max_acceleration_travel": ["20000", "20000"], "machine_max_jerk_x": ["5", "5"], "machine_max_jerk_y": ["5", "5"], "machine_max_jerk_z": ["0.4", "0.4"], "machine_max_jerk_e": ["5", "5"], "min_layer_height": ["0.07"], "max_layer_height": ["0.32"], + "extruder_clearance_radius": "45", + "extruder_clearance_height_to_rod": "25", + "extruder_clearance_height_to_lid": "140", "retract_lift_below": ["0.25"], "retraction_length": ["0.8"], "retraction_speed": ["120"], "deretraction_speed": ["120"], - "wipe": ["0"], - "retract_before_wipe": ["0%"], + "wipe": ["1"], + "retract_before_wipe": ["70%"], + "z_hop": ["0.2"], "machine_start_gcode": "START_PRINT EXTRUDER_TEMP=[nozzle_temperature_initial_layer] BED_TEMP=[bed_temperature_initial_layer_single] TOTAL_LAYER_COUNT={total_layer_count} X0={first_layer_print_min[0]} Y0={first_layer_print_min[1]} X1={first_layer_print_max[0]} Y1={first_layer_print_max[1]}", - "layer_change_gcode": ";AFTER_LAYER_CHANGE\n;[layer_z]\nG92 E0\n_ON_LAYER_CHANGE LAYER={layer_num + 1}", - "before_layer_change_gcode": ";[layer_z]", + "layer_change_gcode": ";AFTER_LAYER_CHANGE\n;[layer_z]", + "before_layer_change_gcode": ";BEFORE_LAYER_CHANGE\n;[layer_z]\nG92 E0\n_ON_LAYER_CHANGE LAYER={layer_num + 1}", "change_filament_gcode": "M600", - "machine_pause_gcode": "M601", + "machine_pause_gcode": "PAUSE", "printing_by_object_gcode": ";BETWEEN_OBJECTS\nG92 E0", "printable_height": "500", "thumbnails": [ diff --git a/resources/profiles/Ratrig/machine/RatRig V-Core 4 HYBRID 500 0.6 nozzle.json b/resources/profiles/Ratrig/machine/RatRig V-Core 4 HYBRID 500 0.6 nozzle.json index 05c5c9c075b..d606c82b540 100644 --- a/resources/profiles/Ratrig/machine/RatRig V-Core 4 HYBRID 500 0.6 nozzle.json +++ b/resources/profiles/Ratrig/machine/RatRig V-Core 4 HYBRID 500 0.6 nozzle.json @@ -18,31 +18,36 @@ ], "machine_max_speed_x": ["500", "500"], "machine_max_speed_y": ["500", "500"], - "machine_max_speed_z": ["50", "50"], + "machine_max_speed_z": ["200", "200"], "machine_max_speed_e": ["120", "120"], "machine_max_acceleration_x": ["20000", "20000"], "machine_max_acceleration_y": ["20000", "20000"], "machine_max_acceleration_z": ["200", "200"], - "machine_max_acceleration_e": ["20000", "20000"], + "machine_max_acceleration_e": ["5000", "5000"], "machine_max_acceleration_extruding": ["20000", "20000"], "machine_max_acceleration_retracting": ["20000", "20000"], + "machine_max_acceleration_travel": ["20000", "20000"], "machine_max_jerk_x": ["5", "5"], "machine_max_jerk_y": ["5", "5"], "machine_max_jerk_z": ["0.4", "0.4"], "machine_max_jerk_e": ["5", "5"], "min_layer_height": ["0.1"], "max_layer_height": ["0.4"], + "extruder_clearance_radius": "45", + "extruder_clearance_height_to_rod": "25", + "extruder_clearance_height_to_lid": "140", "retract_lift_below": ["0.3"], "retraction_length": ["0.8"], "retraction_speed": ["120"], "deretraction_speed": ["120"], - "wipe": ["0"], - "retract_before_wipe": ["0%"], + "wipe": ["1"], + "retract_before_wipe": ["70%"], + "z_hop": ["0.2"], "machine_start_gcode": "START_PRINT EXTRUDER_TEMP=[nozzle_temperature_initial_layer] BED_TEMP=[bed_temperature_initial_layer_single] TOTAL_LAYER_COUNT={total_layer_count} X0={first_layer_print_min[0]} Y0={first_layer_print_min[1]} X1={first_layer_print_max[0]} Y1={first_layer_print_max[1]}", - "layer_change_gcode": ";AFTER_LAYER_CHANGE\n;[layer_z]\nG92 E0\n_ON_LAYER_CHANGE LAYER={layer_num + 1}", - "before_layer_change_gcode": ";[layer_z]", + "layer_change_gcode": ";AFTER_LAYER_CHANGE\n;[layer_z]", + "before_layer_change_gcode": ";BEFORE_LAYER_CHANGE\n;[layer_z]\nG92 E0\n_ON_LAYER_CHANGE LAYER={layer_num + 1}", "change_filament_gcode": "M600", - "machine_pause_gcode": "M601", + "machine_pause_gcode": "PAUSE", "printing_by_object_gcode": ";BETWEEN_OBJECTS\nG92 E0", "printable_height": "500", "thumbnails": [ diff --git a/resources/profiles/Snapmaker.json b/resources/profiles/Snapmaker.json index 0fd90b48db3..1e782b61b8d 100644 --- a/resources/profiles/Snapmaker.json +++ b/resources/profiles/Snapmaker.json @@ -1,6 +1,6 @@ { "name": "Snapmaker", - "version": "02.02.00.01", + "version": "02.02.02.02", "force_update": "0", "description": "Snapmaker configurations", "machine_model_list": [ @@ -400,6 +400,10 @@ "name": "fdm_filament_pva", "sub_path": "filament/fdm_filament_pva.json" }, + { + "name": "fdm_filament_breakaway", + "sub_path": "filament/fdm_filament_breakaway.json" + }, { "name": "fdm_filament_tpu", "sub_path": "filament/fdm_filament_tpu.json" @@ -724,6 +728,18 @@ "name": "Snapmaker J1 PVA @0.2 nozzle", "sub_path": "filament/Snapmaker J1 PVA @0.2 nozzle.json" }, + { + "name": "Snapmaker J1 Breakaway @base", + "sub_path": "filament/Snapmaker J1 Breakaway @base.json" + }, + { + "name": "Snapmaker J1 Breakaway", + "sub_path": "filament/Snapmaker J1 Breakaway.json" + }, + { + "name": "Snapmaker J1 Breakaway @0.2 nozzle", + "sub_path": "filament/Snapmaker J1 Breakaway @0.2 nozzle.json" + }, { "name": "Snapmaker J1 TPU @base", "sub_path": "filament/Snapmaker J1 TPU @base.json" @@ -920,6 +936,18 @@ "name": "Snapmaker Dual PVA @0.2 nozzle", "sub_path": "filament/Snapmaker Dual PVA @0.2 nozzle.json" }, + { + "name": "Snapmaker Dual Breakaway @base", + "sub_path": "filament/Snapmaker Dual Breakaway @base.json" + }, + { + "name": "Snapmaker Dual Breakaway", + "sub_path": "filament/Snapmaker Dual Breakaway.json" + }, + { + "name": "Snapmaker Dual Breakaway @0.2 nozzle", + "sub_path": "filament/Snapmaker Dual Breakaway @0.2 nozzle.json" + }, { "name": "Snapmaker Dual TPU @base", "sub_path": "filament/Snapmaker Dual TPU @base.json" diff --git a/resources/profiles/Snapmaker/filament/Snapmaker Dual ABS @0.2 nozzle.json b/resources/profiles/Snapmaker/filament/Snapmaker Dual ABS @0.2 nozzle.json index 6db75b2afc5..1bf8f4af610 100644 --- a/resources/profiles/Snapmaker/filament/Snapmaker Dual ABS @0.2 nozzle.json +++ b/resources/profiles/Snapmaker/filament/Snapmaker Dual ABS @0.2 nozzle.json @@ -16,9 +16,6 @@ "Snapmaker A350 Dual QSKit (0.2 nozzle)", "Snapmaker Artisan (0.2 nozzle)" ], - "filament_end_gcode": [ - "" - ], "hot_plate_temp": [ "90" ], diff --git a/resources/profiles/Snapmaker/filament/Snapmaker Dual ABS @base.json b/resources/profiles/Snapmaker/filament/Snapmaker Dual ABS @base.json index f4cb4f76cab..5f102698d1e 100644 --- a/resources/profiles/Snapmaker/filament/Snapmaker Dual ABS @base.json +++ b/resources/profiles/Snapmaker/filament/Snapmaker Dual ABS @base.json @@ -5,9 +5,6 @@ "name": "Snapmaker Dual ABS @base", "filament_id": "168223792", "inherits": "fdm_filament_abs", - "filament_end_gcode": [ - "M104 S{temperature_vitrification[current_extruder]} ;standby T{current_extruder}" - ], "hot_plate_temp": [ "110" ], diff --git a/resources/profiles/Snapmaker/filament/Snapmaker Dual ASA @0.2 nozzle.json b/resources/profiles/Snapmaker/filament/Snapmaker Dual ASA @0.2 nozzle.json index b2b5588eb2f..ddfa0696f95 100644 --- a/resources/profiles/Snapmaker/filament/Snapmaker Dual ASA @0.2 nozzle.json +++ b/resources/profiles/Snapmaker/filament/Snapmaker Dual ASA @0.2 nozzle.json @@ -16,9 +16,6 @@ "Snapmaker A350 Dual QSKit (0.2 nozzle)", "Snapmaker Artisan (0.2 nozzle)" ], - "filament_end_gcode": [ - "" - ], "hot_plate_temp": [ "90" ], diff --git a/resources/profiles/Snapmaker/filament/Snapmaker Dual ASA @base.json b/resources/profiles/Snapmaker/filament/Snapmaker Dual ASA @base.json index 1da233088ab..4b82c16926c 100644 --- a/resources/profiles/Snapmaker/filament/Snapmaker Dual ASA @base.json +++ b/resources/profiles/Snapmaker/filament/Snapmaker Dual ASA @base.json @@ -5,9 +5,6 @@ "name": "Snapmaker Dual ASA @base", "filament_id": "1247172706", "inherits": "fdm_filament_asa", - "filament_end_gcode": [ - "M104 S{temperature_vitrification[current_extruder]} ;standby T{current_extruder}" - ], "hot_plate_temp": [ "110" ], diff --git a/resources/profiles/Snapmaker/filament/Snapmaker Dual Breakaway @0.2 nozzle.json b/resources/profiles/Snapmaker/filament/Snapmaker Dual Breakaway @0.2 nozzle.json new file mode 100644 index 00000000000..15d4ec43ec9 --- /dev/null +++ b/resources/profiles/Snapmaker/filament/Snapmaker Dual Breakaway @0.2 nozzle.json @@ -0,0 +1,22 @@ +{ + "type": "filament", + "from": "system", + "instantiation": "true", + "name": "Snapmaker Dual Breakaway @0.2 nozzle", + "setting_id": "98433967", + "inherits": "Snapmaker Dual Breakaway @base", + "compatible_printers": [ + "Snapmaker A250 Dual (0.2 nozzle)", + "Snapmaker A250 Dual BKit (0.2 nozzle)", + "Snapmaker A250 Dual QS+B Kit (0.2 nozzle)", + "Snapmaker A250 Dual QSKit (0.2 nozzle)", + "Snapmaker A350 Dual (0.2 nozzle)", + "Snapmaker A350 Dual BKit (0.2 nozzle)", + "Snapmaker A350 Dual QS+B Kit (0.2 nozzle)", + "Snapmaker A350 Dual QSKit (0.2 nozzle)", + "Snapmaker Artisan (0.2 nozzle)" + ], + "filament_max_volumetric_speed": [ + "1.4" + ] +} \ No newline at end of file diff --git a/resources/profiles/Snapmaker/filament/Snapmaker Dual Breakaway @base.json b/resources/profiles/Snapmaker/filament/Snapmaker Dual Breakaway @base.json new file mode 100644 index 00000000000..d2b92c92617 --- /dev/null +++ b/resources/profiles/Snapmaker/filament/Snapmaker Dual Breakaway @base.json @@ -0,0 +1,35 @@ +{ + "type": "filament", + "from": "system", + "instantiation": "false", + "name": "Snapmaker Dual Breakaway @base", + "filament_id": "1207881278", + "inherits": "fdm_filament_breakaway", + "filament_loading_speed_start": [ + "35" + ], + "filament_loading_speed": [ + "35" + ], + "filament_unloading_speed_start": [ + "35" + ], + "filament_unloading_speed": [ + "35" + ], + "filament_load_time": [ + "2" + ], + "filament_unload_time": [ + "2" + ], + "filament_cooling_moves": [ + "2" + ], + "filament_cooling_initial_speed": [ + "35" + ], + "filament_cooling_final_speed": [ + "60" + ] +} \ No newline at end of file diff --git a/resources/profiles/Snapmaker/filament/Snapmaker Dual Breakaway.json b/resources/profiles/Snapmaker/filament/Snapmaker Dual Breakaway.json new file mode 100644 index 00000000000..daf06114e53 --- /dev/null +++ b/resources/profiles/Snapmaker/filament/Snapmaker Dual Breakaway.json @@ -0,0 +1,37 @@ +{ + "type": "filament", + "from": "system", + "instantiation": "true", + "name": "Snapmaker Dual Breakaway", + "setting_id": "1762361484", + "inherits": "Snapmaker Dual Breakaway @base", + "compatible_printers": [ + "Snapmaker A250 Dual (0.4 nozzle)", + "Snapmaker A250 Dual (0.6 nozzle)", + "Snapmaker A250 Dual (0.8 nozzle)", + "Snapmaker A250 Dual BKit (0.4 nozzle)", + "Snapmaker A250 Dual BKit (0.6 nozzle)", + "Snapmaker A250 Dual BKit (0.8 nozzle)", + "Snapmaker A250 Dual QS+B Kit (0.4 nozzle)", + "Snapmaker A250 Dual QS+B Kit (0.6 nozzle)", + "Snapmaker A250 Dual QS+B Kit (0.8 nozzle)", + "Snapmaker A250 Dual QSKit (0.4 nozzle)", + "Snapmaker A250 Dual QSKit (0.6 nozzle)", + "Snapmaker A250 Dual QSKit (0.8 nozzle)", + "Snapmaker A350 Dual (0.4 nozzle)", + "Snapmaker A350 Dual (0.6 nozzle)", + "Snapmaker A350 Dual (0.8 nozzle)", + "Snapmaker A350 Dual BKit (0.4 nozzle)", + "Snapmaker A350 Dual BKit (0.6 nozzle)", + "Snapmaker A350 Dual BKit (0.8 nozzle)", + "Snapmaker A350 Dual QS+B Kit (0.4 nozzle)", + "Snapmaker A350 Dual QS+B Kit (0.6 nozzle)", + "Snapmaker A350 Dual QS+B Kit (0.8 nozzle)", + "Snapmaker A350 Dual QSKit (0.4 nozzle)", + "Snapmaker A350 Dual QSKit (0.6 nozzle)", + "Snapmaker A350 Dual QSKit (0.8 nozzle)", + "Snapmaker Artisan (0.4 nozzle)", + "Snapmaker Artisan (0.6 nozzle)", + "Snapmaker Artisan (0.8 nozzle)" + ] +} \ No newline at end of file diff --git a/resources/profiles/Snapmaker/filament/Snapmaker Dual PA-CF @base.json b/resources/profiles/Snapmaker/filament/Snapmaker Dual PA-CF @base.json index 514b9009a5c..a3583fcd81d 100644 --- a/resources/profiles/Snapmaker/filament/Snapmaker Dual PA-CF @base.json +++ b/resources/profiles/Snapmaker/filament/Snapmaker Dual PA-CF @base.json @@ -5,9 +5,6 @@ "name": "Snapmaker Dual PA-CF @base", "filament_id": "3493177425", "inherits": "fdm_filament_pa", - "filament_end_gcode": [ - "M104 S{temperature_vitrification[current_extruder]} ;standby T{current_extruder}" - ], "hot_plate_temp": [ "100" ], diff --git a/resources/profiles/Snapmaker/filament/Snapmaker Dual PET @base.json b/resources/profiles/Snapmaker/filament/Snapmaker Dual PET @base.json index 67bca9c4e87..bf642524275 100644 --- a/resources/profiles/Snapmaker/filament/Snapmaker Dual PET @base.json +++ b/resources/profiles/Snapmaker/filament/Snapmaker Dual PET @base.json @@ -5,9 +5,6 @@ "name": "Snapmaker Dual PET @base", "filament_id": "2128577941", "inherits": "fdm_filament_pet", - "filament_end_gcode": [ - "M104 S{temperature_vitrification[current_extruder]} ;standby T{current_extruder}" - ], "overhang_fan_speed": [ "40" ], diff --git a/resources/profiles/Snapmaker/filament/Snapmaker Dual PETG @base.json b/resources/profiles/Snapmaker/filament/Snapmaker Dual PETG @base.json index 6dfe8db49b6..6920156a95f 100644 --- a/resources/profiles/Snapmaker/filament/Snapmaker Dual PETG @base.json +++ b/resources/profiles/Snapmaker/filament/Snapmaker Dual PETG @base.json @@ -5,9 +5,6 @@ "name": "Snapmaker Dual PETG @base", "filament_id": "2209001062", "inherits": "fdm_filament_petg", - "filament_end_gcode": [ - "M104 S{temperature_vitrification[current_extruder]} ;standby T{current_extruder}" - ], "overhang_fan_speed": [ "25" ], diff --git a/resources/profiles/Snapmaker/filament/Snapmaker Dual PETG-CF @base.json b/resources/profiles/Snapmaker/filament/Snapmaker Dual PETG-CF @base.json index c1603ece8a8..86a223a7149 100644 --- a/resources/profiles/Snapmaker/filament/Snapmaker Dual PETG-CF @base.json +++ b/resources/profiles/Snapmaker/filament/Snapmaker Dual PETG-CF @base.json @@ -5,9 +5,6 @@ "name": "Snapmaker Dual PETG-CF @base", "filament_id": "1042511226", "inherits": "fdm_filament_petg", - "filament_end_gcode": [ - "M104 S{temperature_vitrification[current_extruder]} ;standby T{current_extruder}" - ], "required_nozzle_HRC": [ "40" ], @@ -83,6 +80,9 @@ "enable_pressure_advance": [ "0" ], + "pressure_advance": [ + "0.04" + ], "filament_type": [ "PETG-CF" ], diff --git a/resources/profiles/Snapmaker/filament/Snapmaker Dual PLA @base.json b/resources/profiles/Snapmaker/filament/Snapmaker Dual PLA @base.json index cb9e12eb4a3..99967358610 100644 --- a/resources/profiles/Snapmaker/filament/Snapmaker Dual PLA @base.json +++ b/resources/profiles/Snapmaker/filament/Snapmaker Dual PLA @base.json @@ -5,9 +5,6 @@ "name": "Snapmaker Dual PLA @base", "filament_id": "1417031127", "inherits": "fdm_filament_pla", - "filament_end_gcode": [ - "M104 S{temperature_vitrification[current_extruder]} ;standby T{current_extruder}" - ], "filament_retraction_length": [ "nil" ], diff --git a/resources/profiles/Snapmaker/filament/Snapmaker Dual PLA Eco @base.json b/resources/profiles/Snapmaker/filament/Snapmaker Dual PLA Eco @base.json index 0604c192119..77d46c7bf5a 100644 --- a/resources/profiles/Snapmaker/filament/Snapmaker Dual PLA Eco @base.json +++ b/resources/profiles/Snapmaker/filament/Snapmaker Dual PLA Eco @base.json @@ -5,9 +5,6 @@ "name": "Snapmaker Dual PLA Eco @base", "filament_id": "200803790", "inherits": "fdm_filament_pla_eco", - "filament_end_gcode": [ - "M104 S{temperature_vitrification[current_extruder]} ;standby T{current_extruder}" - ], "filament_density": [ "1.26" ], diff --git a/resources/profiles/Snapmaker/filament/Snapmaker Dual PLA Matte @base.json b/resources/profiles/Snapmaker/filament/Snapmaker Dual PLA Matte @base.json index 455345c14e5..5b99cb36f32 100644 --- a/resources/profiles/Snapmaker/filament/Snapmaker Dual PLA Matte @base.json +++ b/resources/profiles/Snapmaker/filament/Snapmaker Dual PLA Matte @base.json @@ -5,9 +5,6 @@ "name": "Snapmaker Dual PLA Matte @base", "filament_id": "3503790988", "inherits": "fdm_filament_pla", - "filament_end_gcode": [ - "M104 S{temperature_vitrification[current_extruder]} ;standby T{current_extruder}" - ], "filament_density": [ "1.32" ], diff --git a/resources/profiles/Snapmaker/filament/Snapmaker Dual PLA Metal @base.json b/resources/profiles/Snapmaker/filament/Snapmaker Dual PLA Metal @base.json index f5bbb7250d6..3ef20715869 100644 --- a/resources/profiles/Snapmaker/filament/Snapmaker Dual PLA Metal @base.json +++ b/resources/profiles/Snapmaker/filament/Snapmaker Dual PLA Metal @base.json @@ -5,9 +5,6 @@ "name": "Snapmaker Dual PLA Metal @base", "filament_id": "2029994346", "inherits": "fdm_filament_pla", - "filament_end_gcode": [ - "M104 S{temperature_vitrification[current_extruder]} ;standby T{current_extruder}" - ], "filament_cost": [ "90" ], diff --git a/resources/profiles/Snapmaker/filament/Snapmaker Dual PLA Silk @base.json b/resources/profiles/Snapmaker/filament/Snapmaker Dual PLA Silk @base.json index 9e54ccb9cf2..c2ddf06aa3e 100644 --- a/resources/profiles/Snapmaker/filament/Snapmaker Dual PLA Silk @base.json +++ b/resources/profiles/Snapmaker/filament/Snapmaker Dual PLA Silk @base.json @@ -5,9 +5,6 @@ "name": "Snapmaker Dual PLA Silk @base", "filament_id": "1181363872", "inherits": "fdm_filament_pla", - "filament_end_gcode": [ - "M104 S{temperature_vitrification[current_extruder]} ;standby T{current_extruder}" - ], "hot_plate_temp_initial_layer": [ "65" ], diff --git a/resources/profiles/Snapmaker/filament/Snapmaker Dual PLA-CF @base.json b/resources/profiles/Snapmaker/filament/Snapmaker Dual PLA-CF @base.json index c5568ad7977..644105f3df9 100644 --- a/resources/profiles/Snapmaker/filament/Snapmaker Dual PLA-CF @base.json +++ b/resources/profiles/Snapmaker/filament/Snapmaker Dual PLA-CF @base.json @@ -5,9 +5,6 @@ "name": "Snapmaker Dual PLA-CF @base", "filament_id": "1702147325", "inherits": "fdm_filament_pla", - "filament_end_gcode": [ - "M104 S{temperature_vitrification[current_extruder]} ;standby T{current_extruder}" - ], "required_nozzle_HRC": [ "40" ], diff --git a/resources/profiles/Snapmaker/filament/Snapmaker Dual PVA @base.json b/resources/profiles/Snapmaker/filament/Snapmaker Dual PVA @base.json index 6083795b147..e5abdc7d823 100644 --- a/resources/profiles/Snapmaker/filament/Snapmaker Dual PVA @base.json +++ b/resources/profiles/Snapmaker/filament/Snapmaker Dual PVA @base.json @@ -5,9 +5,6 @@ "name": "Snapmaker Dual PVA @base", "filament_id": "3104636980", "inherits": "fdm_filament_pva", - "filament_end_gcode": [ - "M104 S{temperature_vitrification[current_extruder]} ;standby T{current_extruder}" - ], "filament_loading_speed_start": [ "35" ], diff --git a/resources/profiles/Snapmaker/filament/Snapmaker Dual TPE.json b/resources/profiles/Snapmaker/filament/Snapmaker Dual TPE.json index b93954f9b2e..dc50110b2ed 100644 --- a/resources/profiles/Snapmaker/filament/Snapmaker Dual TPE.json +++ b/resources/profiles/Snapmaker/filament/Snapmaker Dual TPE.json @@ -37,9 +37,6 @@ "hot_plate_temp": [ "45" ], - "hot_plate_temp_initial_layer": [ - "45" - ], "overhang_fan_speed": [ "65" ], diff --git a/resources/profiles/Snapmaker/filament/Snapmaker Dual TPU @base.json b/resources/profiles/Snapmaker/filament/Snapmaker Dual TPU @base.json index f343d1fb250..199797509ef 100644 --- a/resources/profiles/Snapmaker/filament/Snapmaker Dual TPU @base.json +++ b/resources/profiles/Snapmaker/filament/Snapmaker Dual TPU @base.json @@ -5,9 +5,6 @@ "name": "Snapmaker Dual TPU @base", "filament_id": "2971656290", "inherits": "fdm_filament_tpu", - "filament_end_gcode": [ - "M104 S{temperature_vitrification[current_extruder]} ;standby T{current_extruder}" - ], "filament_loading_speed_start": [ "35" ], @@ -34,5 +31,11 @@ ], "filament_cooling_final_speed": [ "60" + ], + "nozzle_temperature_initial_layer": [ + "240" + ], + "nozzle_temperature": [ + "240" ] } \ No newline at end of file diff --git a/resources/profiles/Snapmaker/filament/Snapmaker J1 ABS @0.2 nozzle.json b/resources/profiles/Snapmaker/filament/Snapmaker J1 ABS @0.2 nozzle.json index ca0d592a7c0..41f0ae99e2e 100644 --- a/resources/profiles/Snapmaker/filament/Snapmaker J1 ABS @0.2 nozzle.json +++ b/resources/profiles/Snapmaker/filament/Snapmaker J1 ABS @0.2 nozzle.json @@ -8,9 +8,6 @@ "compatible_printers": [ "Snapmaker J1 (0.2 nozzle)" ], - "filament_end_gcode": [ - "" - ], "hot_plate_temp": [ "90" ], diff --git a/resources/profiles/Snapmaker/filament/Snapmaker J1 ABS @base.json b/resources/profiles/Snapmaker/filament/Snapmaker J1 ABS @base.json index 2674a15d111..0de3893a527 100644 --- a/resources/profiles/Snapmaker/filament/Snapmaker J1 ABS @base.json +++ b/resources/profiles/Snapmaker/filament/Snapmaker J1 ABS @base.json @@ -5,9 +5,6 @@ "name": "Snapmaker J1 ABS @base", "filament_id": "1223824394", "inherits": "fdm_filament_abs", - "filament_end_gcode": [ - "M104 S{temperature_vitrification[current_extruder]} ;standby T{current_extruder}" - ], "hot_plate_temp": [ "110" ], diff --git a/resources/profiles/Snapmaker/filament/Snapmaker J1 Breakaway @0.2 nozzle.json b/resources/profiles/Snapmaker/filament/Snapmaker J1 Breakaway @0.2 nozzle.json new file mode 100644 index 00000000000..4cb11cd1e36 --- /dev/null +++ b/resources/profiles/Snapmaker/filament/Snapmaker J1 Breakaway @0.2 nozzle.json @@ -0,0 +1,14 @@ +{ + "type": "filament", + "from": "system", + "instantiation": "true", + "name": "Snapmaker J1 Breakaway @0.2 nozzle", + "setting_id": "2613265860", + "inherits": "Snapmaker J1 Breakaway @base", + "compatible_printers": [ + "Snapmaker J1 (0.2 nozzle)" + ], + "filament_max_volumetric_speed": [ + "1.4" + ] +} \ No newline at end of file diff --git a/resources/profiles/Snapmaker/filament/Snapmaker J1 Breakaway @base.json b/resources/profiles/Snapmaker/filament/Snapmaker J1 Breakaway @base.json new file mode 100644 index 00000000000..7d1d244cc31 --- /dev/null +++ b/resources/profiles/Snapmaker/filament/Snapmaker J1 Breakaway @base.json @@ -0,0 +1,8 @@ +{ + "type": "filament", + "from": "system", + "instantiation": "false", + "name": "Snapmaker J1 Breakaway @base", + "filament_id": "3492897526", + "inherits": "fdm_filament_breakaway" +} \ No newline at end of file diff --git a/resources/profiles/Snapmaker/filament/Snapmaker J1 Breakaway.json b/resources/profiles/Snapmaker/filament/Snapmaker J1 Breakaway.json new file mode 100644 index 00000000000..bb0936c48fb --- /dev/null +++ b/resources/profiles/Snapmaker/filament/Snapmaker J1 Breakaway.json @@ -0,0 +1,13 @@ +{ + "type": "filament", + "from": "system", + "instantiation": "true", + "name": "Snapmaker J1 Breakaway", + "setting_id": "738160822", + "inherits": "Snapmaker J1 Breakaway @base", + "compatible_printers": [ + "Snapmaker J1 (0.4 nozzle)", + "Snapmaker J1 (0.6 nozzle)", + "Snapmaker J1 (0.8 nozzle)" + ] +} \ No newline at end of file diff --git a/resources/profiles/Snapmaker/filament/Snapmaker J1 PA-CF @base.json b/resources/profiles/Snapmaker/filament/Snapmaker J1 PA-CF @base.json index 1514c32a42c..b7bda1fadc6 100644 --- a/resources/profiles/Snapmaker/filament/Snapmaker J1 PA-CF @base.json +++ b/resources/profiles/Snapmaker/filament/Snapmaker J1 PA-CF @base.json @@ -5,9 +5,6 @@ "name": "Snapmaker J1 PA-CF @base", "filament_id": "1210173120", "inherits": "fdm_filament_pa", - "filament_end_gcode": [ - "M104 S{temperature_vitrification[current_extruder]} ;standby T{current_extruder}" - ], "overhang_fan_speed": [ "55" ], diff --git a/resources/profiles/Snapmaker/filament/Snapmaker J1 PET @base.json b/resources/profiles/Snapmaker/filament/Snapmaker J1 PET @base.json index fdd3a613bf4..a65db99924b 100644 --- a/resources/profiles/Snapmaker/filament/Snapmaker J1 PET @base.json +++ b/resources/profiles/Snapmaker/filament/Snapmaker J1 PET @base.json @@ -5,9 +5,6 @@ "name": "Snapmaker J1 PET @base", "filament_id": "1009481135", "inherits": "fdm_filament_pet", - "filament_end_gcode": [ - "M104 S{temperature_vitrification[current_extruder]} ;standby T{current_extruder}" - ], "overhang_fan_speed": [ "40" ], diff --git a/resources/profiles/Snapmaker/filament/Snapmaker J1 PETG @base.json b/resources/profiles/Snapmaker/filament/Snapmaker J1 PETG @base.json index ad69c9f1874..add2de2157f 100644 --- a/resources/profiles/Snapmaker/filament/Snapmaker J1 PETG @base.json +++ b/resources/profiles/Snapmaker/filament/Snapmaker J1 PETG @base.json @@ -5,9 +5,6 @@ "name": "Snapmaker J1 PETG @base", "filament_id": "1172603684", "inherits": "fdm_filament_petg", - "filament_end_gcode": [ - "M104 S{temperature_vitrification[current_extruder]} ;standby T{current_extruder}" - ], "overhang_fan_speed": [ "35" ], diff --git a/resources/profiles/Snapmaker/filament/Snapmaker J1 PETG-CF @base.json b/resources/profiles/Snapmaker/filament/Snapmaker J1 PETG-CF @base.json index 7a8f9f949ea..b979f6bdea9 100644 --- a/resources/profiles/Snapmaker/filament/Snapmaker J1 PETG-CF @base.json +++ b/resources/profiles/Snapmaker/filament/Snapmaker J1 PETG-CF @base.json @@ -5,9 +5,6 @@ "name": "Snapmaker J1 PETG-CF @base", "filament_id": "4235401834", "inherits": "fdm_filament_petg", - "filament_end_gcode": [ - "M104 S{temperature_vitrification[current_extruder]} ;standby T{current_extruder}" - ], "required_nozzle_HRC": [ "40" ], @@ -56,6 +53,9 @@ "enable_pressure_advance": [ "0" ], + "pressure_advance": [ + "0.04" + ], "filament_type": [ "PETG-CF" ], diff --git a/resources/profiles/Snapmaker/filament/Snapmaker J1 PLA @base.json b/resources/profiles/Snapmaker/filament/Snapmaker J1 PLA @base.json index e95ea7a1125..abd1c423891 100644 --- a/resources/profiles/Snapmaker/filament/Snapmaker J1 PLA @base.json +++ b/resources/profiles/Snapmaker/filament/Snapmaker J1 PLA @base.json @@ -5,9 +5,6 @@ "name": "Snapmaker J1 PLA @base", "filament_id": "377675245", "inherits": "fdm_filament_pla", - "filament_end_gcode": [ - "M104 S{temperature_vitrification[current_extruder]} ;standby T{current_extruder}" - ], "filament_retraction_length": [ "nil" ], diff --git a/resources/profiles/Snapmaker/filament/Snapmaker J1 PLA Eco @base.json b/resources/profiles/Snapmaker/filament/Snapmaker J1 PLA Eco @base.json index 4e2f8954b6f..f5f24aa8c0e 100644 --- a/resources/profiles/Snapmaker/filament/Snapmaker J1 PLA Eco @base.json +++ b/resources/profiles/Snapmaker/filament/Snapmaker J1 PLA Eco @base.json @@ -5,9 +5,6 @@ "name": "Snapmaker J1 PLA Eco @base", "filament_id": "3383257822", "inherits": "fdm_filament_pla_eco", - "filament_end_gcode": [ - "M104 S{temperature_vitrification[current_extruder]} ;standby T{current_extruder}" - ], "filament_density": [ "1.26" ], diff --git a/resources/profiles/Snapmaker/filament/Snapmaker J1 PLA Matte @base.json b/resources/profiles/Snapmaker/filament/Snapmaker J1 PLA Matte @base.json index 1f9759f5620..fdbd3790705 100644 --- a/resources/profiles/Snapmaker/filament/Snapmaker J1 PLA Matte @base.json +++ b/resources/profiles/Snapmaker/filament/Snapmaker J1 PLA Matte @base.json @@ -5,9 +5,6 @@ "name": "Snapmaker J1 PLA Matte @base", "filament_id": "1192769348", "inherits": "fdm_filament_pla", - "filament_end_gcode": [ - "M104 S{temperature_vitrification[current_extruder]} ;standby T{current_extruder}" - ], "filament_density": [ "1.32" ], diff --git a/resources/profiles/Snapmaker/filament/Snapmaker J1 PLA Metal @base.json b/resources/profiles/Snapmaker/filament/Snapmaker J1 PLA Metal @base.json index ceac6636484..3d75258f561 100644 --- a/resources/profiles/Snapmaker/filament/Snapmaker J1 PLA Metal @base.json +++ b/resources/profiles/Snapmaker/filament/Snapmaker J1 PLA Metal @base.json @@ -5,9 +5,6 @@ "name": "Snapmaker J1 PLA Metal @base", "filament_id": "4012961186", "inherits": "fdm_filament_pla", - "filament_end_gcode": [ - "M104 S{temperature_vitrification[current_extruder]} ;standby T{current_extruder}" - ], "filament_cost": [ "90" ], diff --git a/resources/profiles/Snapmaker/filament/Snapmaker J1 PLA Silk @base.json b/resources/profiles/Snapmaker/filament/Snapmaker J1 PLA Silk @base.json index 780b647fca0..e9fc62b8fc3 100644 --- a/resources/profiles/Snapmaker/filament/Snapmaker J1 PLA Silk @base.json +++ b/resources/profiles/Snapmaker/filament/Snapmaker J1 PLA Silk @base.json @@ -5,9 +5,6 @@ "name": "Snapmaker J1 PLA Silk @base", "filament_id": "1528786603", "inherits": "fdm_filament_pla", - "filament_end_gcode": [ - "M104 S{temperature_vitrification[current_extruder]} ;standby T{current_extruder}" - ], "hot_plate_temp_initial_layer": [ "65" ], diff --git a/resources/profiles/Snapmaker/filament/Snapmaker J1 PLA-CF @base.json b/resources/profiles/Snapmaker/filament/Snapmaker J1 PLA-CF @base.json index 5b5ffb42be3..aed0f937c68 100644 --- a/resources/profiles/Snapmaker/filament/Snapmaker J1 PLA-CF @base.json +++ b/resources/profiles/Snapmaker/filament/Snapmaker J1 PLA-CF @base.json @@ -5,9 +5,6 @@ "name": "Snapmaker J1 PLA-CF @base", "filament_id": "3806593857", "inherits": "fdm_filament_pla", - "filament_end_gcode": [ - "M104 S{temperature_vitrification[current_extruder]} ;standby T{current_extruder}" - ], "required_nozzle_HRC": [ "40" ], diff --git a/resources/profiles/Snapmaker/filament/Snapmaker J1 PVA @base.json b/resources/profiles/Snapmaker/filament/Snapmaker J1 PVA @base.json index e74dfa665ff..e7e03c48e1a 100644 --- a/resources/profiles/Snapmaker/filament/Snapmaker J1 PVA @base.json +++ b/resources/profiles/Snapmaker/filament/Snapmaker J1 PVA @base.json @@ -5,9 +5,6 @@ "name": "Snapmaker J1 PVA @base", "filament_id": "4227461134", "inherits": "fdm_filament_pva", - "filament_end_gcode": [ - "M104 S{temperature_vitrification[current_extruder]} ;standby T{current_extruder}" - ], "slow_down_layer_time": [ "8" ] diff --git a/resources/profiles/Snapmaker/filament/Snapmaker J1 TPE.json b/resources/profiles/Snapmaker/filament/Snapmaker J1 TPE.json index c2ae2b9c6a4..e1768a743bd 100644 --- a/resources/profiles/Snapmaker/filament/Snapmaker J1 TPE.json +++ b/resources/profiles/Snapmaker/filament/Snapmaker J1 TPE.json @@ -13,9 +13,6 @@ "hot_plate_temp": [ "45" ], - "hot_plate_temp_initial_layer": [ - "45" - ], "overhang_fan_speed": [ "65" ], diff --git a/resources/profiles/Snapmaker/filament/Snapmaker J1 TPU @base.json b/resources/profiles/Snapmaker/filament/Snapmaker J1 TPU @base.json index ae11ea5b292..a811ac33411 100644 --- a/resources/profiles/Snapmaker/filament/Snapmaker J1 TPU @base.json +++ b/resources/profiles/Snapmaker/filament/Snapmaker J1 TPU @base.json @@ -5,7 +5,10 @@ "name": "Snapmaker J1 TPU @base", "filament_id": "4092268632", "inherits": "fdm_filament_tpu", - "filament_end_gcode": [ - "M104 S{temperature_vitrification[current_extruder]} ;standby T{current_extruder}" + "nozzle_temperature_initial_layer": [ + "240" + ], + "nozzle_temperature": [ + "240" ] } \ No newline at end of file diff --git a/resources/profiles/Snapmaker/filament/Snapmaker PETG-CF @base.json b/resources/profiles/Snapmaker/filament/Snapmaker PETG-CF @base.json index 06600fd9e89..6b63709c017 100644 --- a/resources/profiles/Snapmaker/filament/Snapmaker PETG-CF @base.json +++ b/resources/profiles/Snapmaker/filament/Snapmaker PETG-CF @base.json @@ -53,6 +53,9 @@ "enable_pressure_advance": [ "0" ], + "pressure_advance": [ + "0.04" + ], "filament_type": [ "PETG-CF" ], diff --git a/resources/profiles/Snapmaker/filament/Snapmaker TPE.json b/resources/profiles/Snapmaker/filament/Snapmaker TPE.json index ac388de2231..fa38a7a6247 100644 --- a/resources/profiles/Snapmaker/filament/Snapmaker TPE.json +++ b/resources/profiles/Snapmaker/filament/Snapmaker TPE.json @@ -34,9 +34,6 @@ "hot_plate_temp": [ "45" ], - "hot_plate_temp_initial_layer": [ - "45" - ], "overhang_fan_speed": [ "100" ], @@ -46,6 +43,12 @@ "filament_max_volumetric_speed": [ "7.2" ], + "nozzle_temperature_initial_layer": [ + "240" + ], + "nozzle_temperature": [ + "240" + ], "fan_max_speed": [ "100" ], diff --git a/resources/profiles/Snapmaker/filament/fdm_filament_breakaway.json b/resources/profiles/Snapmaker/filament/fdm_filament_breakaway.json new file mode 100644 index 00000000000..73addc11186 --- /dev/null +++ b/resources/profiles/Snapmaker/filament/fdm_filament_breakaway.json @@ -0,0 +1,52 @@ +{ + "type": "filament", + "from": "system", + "instantiation": "false", + "name": "fdm_filament_breakaway", + "inherits": "fdm_filament_common", + "filament_is_support": [ + "1" + ], + "hot_plate_temp": [ + "65" + ], + "hot_plate_temp_initial_layer": [ + "70" + ], + "filament_flow_ratio": [ + "0.98" + ], + "filament_cost": [ + "100" + ], + "filament_density": [ + "1.32" + ], + "filament_max_volumetric_speed": [ + "12" + ], + "filament_retraction_length": [ + "1.8" + ], + "nozzle_temperature_initial_layer": [ + "230" + ], + "nozzle_temperature": [ + "220" + ], + "temperature_vitrification": [ + "154" + ], + "default_filament_colour": [ + "#ffffff" + ], + "filament_type": [ + "Breakaway Support" + ], + "nozzle_temperature_range_high": [ + "240" + ], + "nozzle_temperature_range_low": [ + "200" + ] +} \ No newline at end of file diff --git a/resources/profiles/Snapmaker/filament/fdm_filament_petg.json b/resources/profiles/Snapmaker/filament/fdm_filament_petg.json index 407d8d3258e..81bbe9eef81 100644 --- a/resources/profiles/Snapmaker/filament/fdm_filament_petg.json +++ b/resources/profiles/Snapmaker/filament/fdm_filament_petg.json @@ -61,6 +61,9 @@ "enable_pressure_advance": [ "1" ], + "pressure_advance": [ + "0.03" + ], "default_filament_colour": [ "#DF6734" ], diff --git a/resources/profiles/Snapmaker/filament/fdm_filament_pla.json b/resources/profiles/Snapmaker/filament/fdm_filament_pla.json index 7a6824530e1..f8c982cc3b4 100644 --- a/resources/profiles/Snapmaker/filament/fdm_filament_pla.json +++ b/resources/profiles/Snapmaker/filament/fdm_filament_pla.json @@ -31,6 +31,9 @@ "filament_retraction_length": [ "1.2" ], + "filament_retraction_speed": [ + "60" + ], "nozzle_temperature_initial_layer": [ "220" ], @@ -49,6 +52,9 @@ "enable_pressure_advance": [ "1" ], + "pressure_advance": [ + "0.02" + ], "default_filament_colour": [ "#F8C827" ], diff --git a/resources/profiles/Snapmaker/filament/fdm_filament_pva.json b/resources/profiles/Snapmaker/filament/fdm_filament_pva.json index 9f34b6ed576..d4c778d1c1c 100644 --- a/resources/profiles/Snapmaker/filament/fdm_filament_pva.json +++ b/resources/profiles/Snapmaker/filament/fdm_filament_pva.json @@ -7,12 +7,6 @@ "filament_is_support": [ "1" ], - "hot_plate_temp": [ - "50" - ], - "hot_plate_temp_initial_layer": [ - "50" - ], "overhang_fan_threshold": [ "50%" ], @@ -32,7 +26,7 @@ "15" ], "filament_max_volumetric_speed": [ - "2.4" + "6" ], "filament_retraction_length": [ "2" diff --git a/resources/profiles/Snapmaker/filament/fdm_filament_tpu.json b/resources/profiles/Snapmaker/filament/fdm_filament_tpu.json index 86075bf3801..689c2ab0dd1 100644 --- a/resources/profiles/Snapmaker/filament/fdm_filament_tpu.json +++ b/resources/profiles/Snapmaker/filament/fdm_filament_tpu.json @@ -8,7 +8,7 @@ "40" ], "hot_plate_temp_initial_layer": [ - "40" + "45" ], "overhang_fan_speed": [ "70" @@ -53,10 +53,10 @@ "25" ], "nozzle_temperature_initial_layer": [ - "240" + "230" ], "nozzle_temperature": [ - "240" + "225" ], "temperature_vitrification": [ "138" @@ -67,6 +67,9 @@ "fan_min_speed": [ "70" ], + "pressure_advance": [ + "0.01" + ], "default_filament_colour": [ "#302730" ], diff --git a/resources/profiles/Snapmaker/machine/fdm_a250.json b/resources/profiles/Snapmaker/machine/fdm_a250.json index 7b3915a8801..4a177a83bae 100644 --- a/resources/profiles/Snapmaker/machine/fdm_a250.json +++ b/resources/profiles/Snapmaker/machine/fdm_a250.json @@ -14,7 +14,7 @@ "230x250", "0x250" ], - "machine_start_gcode": "; Model: Snapmaker A250\n; Update: 20240428\n; Maintained by https://github.com/macdylan/3dp-configs\n; Printer : [printer_preset]\n; Profile : [print_preset]\n; Plate : [plate_name]\n\nT[initial_extruder]\n\nM140 S{first_layer_bed_temperature[initial_extruder]}\n\n; you can clean the nozzle\nM104 S165\nM204 S100\nG28\nG0 Z153 F960.0\nG0 Y125.0 F3420.0\nG0 X115.0\n\nM190 R{first_layer_bed_temperature[initial_extruder]}\n\nG28\n{if 1==1}\n G0 X0\n G0 Z0.2 F960.0\n G0 Y0 F3420.0\n G0 X230\n G0 Y250\n G0 X0\n G0 Y0\n{endif}\n\nM83\n{if 1==1 && max(hot_plate_temp_initial_layer[initial_extruder], hot_plate_temp[initial_extruder]) >= 90}\nG0 Z0.06\nG92 Z0 ;reset z\n{endif}\n\n; flush initial nozzle\nT[initial_extruder]\nM104 S{max(250, min(290, nozzle_temperature_initial_layer[(initial_extruder % 2 == 0 ? min(initial_extruder + initial_extruder, 63) : max(initial_extruder - (1-initial_extruder), 0))] + 15))}; common flush temp\nG0 Z1.6 F960.0\nG0 X{( initial_extruder % 2 == 0 ? 100.0 : 130.0 )} F3420.0\nG0 Y0 F3420.0\n\nM109 S{max(250, min(290, nozzle_temperature_initial_layer[(initial_extruder % 2 == 0 ? min(initial_extruder + initial_extruder, 63) : max(initial_extruder - (1-initial_extruder), 0))] + 15))} C2 W1\nG1 E15 F80.0\nG92 E0\n\nM106 S{min(255, (fan_max_speed[(initial_extruder % 2 == 0 ? min(initial_extruder + initial_extruder, 63) : max(initial_extruder - (1-initial_extruder), 0))] + 10) * 2.55)}\n\nM104 S{nozzle_temperature_initial_layer[(initial_extruder % 2 == 0 ? min(initial_extruder + initial_extruder, 63) : max(initial_extruder - (1-initial_extruder), 0))] + 5}\n\nG1 E9.0 F200\nG92 E0\nG1 E6.0 Z4.6 F200\nG92 E0\n\nG0 Z5.6 F200\nM107\n\nG0 X{( initial_extruder % 2 == 0 ? 55.0 : 175.0 )} F3420.0\nG0 Z0.3 F960.0\nM109 S{nozzle_temperature_initial_layer[(initial_extruder % 2 == 0 ? min(initial_extruder + initial_extruder, 63) : max(initial_extruder - (1-initial_extruder), 0))]} C3 W1\nG1 E3 F200\nG92 E0\nG1 X{( initial_extruder % 2 == 0 ? 0 : 230 )} E3.42995 F3420.0\nG92 E0\n\nG1 E-{retraction_length[initial_extruder]} F200\nG92 E0\nG0 Y20 F3420.0\n\n; ready [plate_name]", + "machine_start_gcode": "; Model: Snapmaker A250\n; Update: 20240922\n; Maintained by https://github.com/macdylan/3dp-configs\n; Printer : [printer_preset]\n; Profile : [print_preset]\n; Plate : [plate_name]\n\nT[initial_extruder]\n\nM140 S{first_layer_bed_temperature[initial_extruder]}\n\n; you can clean the nozzle\nM104 S165\nM204 S100\nG28\nG0 Z153 F960.0\nG0 Y125.0 F3420.0\nG0 X115.0\n\nM190 R{first_layer_bed_temperature[initial_extruder]}\n\nG28\n{if 0==1} ; boundary check(for dual/quick swap kit), not recommanded if there are any clamps \n G0 X0\n G0 Z0.2 F960.0\n G0 Y0 F3420.0\n G0 X230\n G0 Y250\n G0 X0\n G0 Y0\n{endif}\n\nM83\n{if 1==1 && max(hot_plate_temp_initial_layer[initial_extruder], hot_plate_temp[initial_extruder]) >= 90}\nG0 Z0.06\nG92 Z0 ;reset z\n{endif}\n\n; flush initial nozzle\nT[initial_extruder]\nM104 S{max(250, min(290, nozzle_temperature_initial_layer[(initial_extruder % 2 == 0 ? min(initial_extruder + initial_extruder, 63) : max(initial_extruder - (1-initial_extruder), 0))] + 15))}; common flush temp\nG0 Z1.6 F960.0\nG0 X{( initial_extruder % 2 == 0 ? 100.0 : 130.0 )} F3420.0\nG0 Y0 F3420.0\n\nM109 S{max(250, min(290, nozzle_temperature_initial_layer[(initial_extruder % 2 == 0 ? min(initial_extruder + initial_extruder, 63) : max(initial_extruder - (1-initial_extruder), 0))] + 15))} C2 W1\nG1 E15 F80.0\nG92 E0\n\nM106 S{min(255, (fan_max_speed[(initial_extruder % 2 == 0 ? min(initial_extruder + initial_extruder, 63) : max(initial_extruder - (1-initial_extruder), 0))] + 10) * 2.55)}\n\nM104 S{nozzle_temperature_initial_layer[(initial_extruder % 2 == 0 ? min(initial_extruder + initial_extruder, 63) : max(initial_extruder - (1-initial_extruder), 0))] + 5}\n\nG1 E9.0 F200\nG92 E0\nG1 E6.0 Z4.6 F200\nG92 E0\n\nG0 Z5.6 F200\nM107\n\nG0 X{( initial_extruder % 2 == 0 ? 55.0 : 175.0 )} F3420.0\nG0 Z0.3 F960.0\nM109 S{nozzle_temperature_initial_layer[(initial_extruder % 2 == 0 ? min(initial_extruder + initial_extruder, 63) : max(initial_extruder - (1-initial_extruder), 0))]} C3 W1\nG1 E3 F200\nG92 E0\nG1 X{( initial_extruder % 2 == 0 ? 0 : 230 )} E3.42995 F3420.0\nG92 E0\n\nG1 E-{retraction_length[initial_extruder]} F200\nG92 E0\nG0 Y20 F3420.0\n\n; ready [plate_name]", "machine_end_gcode": "G92 E0\n\nG0 Z{max_layer_z + 2.0} F600\n; retract the filament to make it easier to replace\nG0 E-5 F200\nG28\n\n M104 S0\nM140 S0\nM107\nM220 S100\nM84\n\n;\n; DON'T REMOVE these lines if you're using the smfix (https://github.com/macdylan/SMFix)\n; min_x = [first_layer_print_min_0]\n; min_y = [first_layer_print_min_1]\n; max_x = [first_layer_print_max_0]\n; max_y = [first_layer_print_max_1]\n; max_z = [max_layer_z]\n; total_layer_number = [layer_num]\n;", "before_layer_change_gcode": "; layer_num: [layer_num]\nG92 E0" } \ No newline at end of file diff --git a/resources/profiles/Snapmaker/machine/fdm_a250_dual.json b/resources/profiles/Snapmaker/machine/fdm_a250_dual.json index 7425a37bf18..09be312c71a 100644 --- a/resources/profiles/Snapmaker/machine/fdm_a250_dual.json +++ b/resources/profiles/Snapmaker/machine/fdm_a250_dual.json @@ -20,8 +20,8 @@ "230x250", "0x250" ], - "machine_start_gcode": "; Model: Snapmaker A250 Dual ({nozzle_diameter[0]}/{nozzle_diameter[1]})\n; Update: 20240428\n; Maintained by https://github.com/macdylan/3dp-configs\n; Printer : [printer_preset]\n; Profile : [print_preset]\n; Plate : [plate_name]\n; --- initial_extruder: [initial_extruder]\n; --- has_wipe_tower: [has_wipe_tower]\n; --- total_toolchanges: [total_toolchanges]\n; --- T0: {is_extruder_used[(initial_extruder % 2 == 0 ? min(initial_extruder + 0, 63) : max(initial_extruder - 1, 0))]}\n; --- T1: {is_extruder_used[(initial_extruder % 2 == 0 ? min(initial_extruder + 1, 63) : max(initial_extruder - 0, 0))]}\n\nT[initial_extruder]\n\nM140 S{first_layer_bed_temperature[initial_extruder]}\n\n; you can clean the nozzle\n{if is_extruder_used[(initial_extruder % 2 == 0 ? min(initial_extruder + 0, 63) : max(initial_extruder - (1-0), 0))]}\n M104 T{(initial_extruder % 2 == 0 ? min(initial_extruder + 0, 63) : max(initial_extruder - (1-0), 0))} S165\n {endif}\n{if is_extruder_used[(initial_extruder % 2 == 0 ? min(initial_extruder + 1, 63) : max(initial_extruder - (1-1), 0))]}\n M104 T{(initial_extruder % 2 == 0 ? min(initial_extruder + 1, 63) : max(initial_extruder - (1-1), 0))} S165\n {endif}\nM204 S100\nG28\nG0 Z126 F960.0\nG0 Y125.0 F3420.0\nG0 X115.0\n\nM190 R{first_layer_bed_temperature[initial_extruder]}\n\nG28\n{if 1==1}\n G0 X0\n G0 Z0.2 F960.0\n G0 Y0 F3420.0\n G0 X230\n G0 Y250\n G0 X0\n G0 Y0\n{endif}\n\nM83\n{if 1==1 && max(hot_plate_temp_initial_layer[initial_extruder], hot_plate_temp[initial_extruder]) >= 90}\nG0 Z0.06\nG92 Z0 ;reset z\n{endif}\n\n{if is_extruder_used[(initial_extruder % 2 == 0 ? min(initial_extruder + 0, 63) : max(initial_extruder - (1-0), 0))]}\n; preheat 0\nM104 T{(initial_extruder % 2 == 0 ? min(initial_extruder + 0, 63) : max(initial_extruder - (1-0), 0))} S{max(250, min(290, nozzle_temperature_initial_layer[(initial_extruder % 2 == 0 ? min(initial_extruder + 0, 63) : max(initial_extruder - (1-0), 0))] + 15))}\n {endif}\n{if is_extruder_used[(initial_extruder % 2 == 0 ? min(initial_extruder + 1, 63) : max(initial_extruder - (1-1), 0))]}\n; preheat 1\nM104 T{(initial_extruder % 2 == 0 ? min(initial_extruder + 1, 63) : max(initial_extruder - (1-1), 0))} S{max(250, min(290, nozzle_temperature_initial_layer[(initial_extruder % 2 == 0 ? min(initial_extruder + 1, 63) : max(initial_extruder - (1-1), 0))] + 15))}\n {endif}\n\n{if is_extruder_used[(initial_extruder % 2 == 0 ? min(initial_extruder + 0, 63) : max(initial_extruder - (1-0), 0))] and (initial_extruder % 2) != 0}\n; flush nozzle 0\nT{(initial_extruder % 2 == 0 ? min(initial_extruder + 0, 63) : max(initial_extruder - (1-0), 0))}\nM104 S{max(250, min(290, nozzle_temperature_initial_layer[(initial_extruder % 2 == 0 ? min(initial_extruder + 0, 63) : max(initial_extruder - (1-0), 0))] + 15))}; common flush temp\nG0 Z1.6 F960.0\nG0 X{( 0 % 2 == 0 ? 100.0 : 130.0 )} F3420.0\nG0 Y0 F3420.0\n\nM109 S{max(250, min(290, nozzle_temperature_initial_layer[(initial_extruder % 2 == 0 ? min(initial_extruder + 0, 63) : max(initial_extruder - (1-0), 0))] + 15))} C2 W1\nG1 E20 F80.0\nG92 E0\n\nM106 S{min(255, (fan_max_speed[(initial_extruder % 2 == 0 ? min(initial_extruder + 0, 63) : max(initial_extruder - (1-0), 0))] + 10) * 2.55)}\n\nM104 S{nozzle_temperature_initial_layer[(initial_extruder % 2 == 0 ? min(initial_extruder + 0, 63) : max(initial_extruder - (1-0), 0))] + 5}\n\nG1 E12.0 F200\nG92 E0\nG1 E8.0 Z4.6 F200\nG92 E0\n\nG0 Z5.6 F200\nM107\n\nG0 X{( 0 % 2 == 0 ? 55.0 : 175.0 )} F3420.0\nG0 Z0.3 F960.0\nM109 S{nozzle_temperature_initial_layer[(initial_extruder % 2 == 0 ? min(initial_extruder + 0, 63) : max(initial_extruder - (1-0), 0))]} C3 W1\nG1 E3 F200\nG92 E0\nG1 X{( 0 % 2 == 0 ? 0 : 230 )} E3.42995 F3420.0\nG92 E0\n\nG1 E-{retract_length_toolchange[0]} F200\nG92 E0\nG0 Y20 F3420.0\nM104 S{temperature_vitrification[(initial_extruder % 2 == 0 ? min(initial_extruder + 0, 63) : max(initial_extruder - (1-0), 0))]}\n {endif}\n\n{if is_extruder_used[(initial_extruder % 2 == 0 ? min(initial_extruder + 1, 63) : max(initial_extruder - (1-1), 0))] and (initial_extruder % 2) != 1}\n; flush nozzle 1\nT{(initial_extruder % 2 == 0 ? min(initial_extruder + 1, 63) : max(initial_extruder - (1-1), 0))}\nM104 S{max(250, min(290, nozzle_temperature_initial_layer[(initial_extruder % 2 == 0 ? min(initial_extruder + 1, 63) : max(initial_extruder - (1-1), 0))] + 15))}; common flush temp\nG0 Z1.6 F960.0\nG0 X{( 1 % 2 == 0 ? 100.0 : 130.0 )} F3420.0\nG0 Y0 F3420.0\n\nM109 S{max(250, min(290, nozzle_temperature_initial_layer[(initial_extruder % 2 == 0 ? min(initial_extruder + 1, 63) : max(initial_extruder - (1-1), 0))] + 15))} C2 W1\nG1 E20 F80.0\nG92 E0\n\nM106 S{min(255, (fan_max_speed[(initial_extruder % 2 == 0 ? min(initial_extruder + 1, 63) : max(initial_extruder - (1-1), 0))] + 10) * 2.55)}\n\nM104 S{nozzle_temperature_initial_layer[(initial_extruder % 2 == 0 ? min(initial_extruder + 1, 63) : max(initial_extruder - (1-1), 0))] + 5}\n\nG1 E12.0 F200\nG92 E0\nG1 E8.0 Z4.6 F200\nG92 E0\n\nG0 Z5.6 F200\nM107\n\nG0 X{( 1 % 2 == 0 ? 55.0 : 175.0 )} F3420.0\nG0 Z0.3 F960.0\nM109 S{nozzle_temperature_initial_layer[(initial_extruder % 2 == 0 ? min(initial_extruder + 1, 63) : max(initial_extruder - (1-1), 0))]} C3 W1\nG1 E3 F200\nG92 E0\nG1 X{( 1 % 2 == 0 ? 0 : 230 )} E3.42995 F3420.0\nG92 E0\n\nG1 E-{retract_length_toolchange[1]} F200\nG92 E0\nG0 Y20 F3420.0\nM104 S{temperature_vitrification[(initial_extruder % 2 == 0 ? min(initial_extruder + 1, 63) : max(initial_extruder - (1-1), 0))]}\n {endif}\n\n; flush initial nozzle\nT[initial_extruder]\nM104 S{max(250, min(290, nozzle_temperature_initial_layer[(initial_extruder % 2 == 0 ? min(initial_extruder + initial_extruder, 63) : max(initial_extruder - (1-initial_extruder), 0))] + 15))}; common flush temp\nG0 Z1.6 F960.0\nG0 X{( initial_extruder % 2 == 0 ? 100.0 : 130.0 )} F3420.0\nG0 Y0 F3420.0\n\nM109 S{max(250, min(290, nozzle_temperature_initial_layer[(initial_extruder % 2 == 0 ? min(initial_extruder + initial_extruder, 63) : max(initial_extruder - (1-initial_extruder), 0))] + 15))} C2 W1\nG1 E20 F80.0\nG92 E0\n\nM106 S{min(255, (fan_max_speed[(initial_extruder % 2 == 0 ? min(initial_extruder + initial_extruder, 63) : max(initial_extruder - (1-initial_extruder), 0))] + 10) * 2.55)}\n\nM104 S{nozzle_temperature_initial_layer[(initial_extruder % 2 == 0 ? min(initial_extruder + initial_extruder, 63) : max(initial_extruder - (1-initial_extruder), 0))] + 5}\n\nG1 E12.0 F200\nG92 E0\nG1 E8.0 Z4.6 F200\nG92 E0\n\nG0 Z5.6 F200\nM107\n\nG0 X{( initial_extruder % 2 == 0 ? 55.0 : 175.0 )} F3420.0\nG0 Z0.3 F960.0\nM109 S{nozzle_temperature_initial_layer[(initial_extruder % 2 == 0 ? min(initial_extruder + initial_extruder, 63) : max(initial_extruder - (1-initial_extruder), 0))]} C3 W1\nG1 E3 F200\nG92 E0\nG1 X{( initial_extruder % 2 == 0 ? 0 : 230 )} E3.42995 F3420.0\nG92 E0\n\nG1 E-{retraction_length[initial_extruder]} F200\nG92 E0\nG0 Y20 F3420.0\n\n; ready [plate_name]", + "machine_start_gcode": "; Model: Snapmaker A250 Dual ({nozzle_diameter[0]}/{nozzle_diameter[1]})\n; Update: 20240922\n; Maintained by https://github.com/macdylan/3dp-configs\n; Printer : [printer_preset]\n; Profile : [print_preset]\n; Plate : [plate_name]\n; --- initial_extruder: [initial_extruder]\n; --- has_wipe_tower: [has_wipe_tower]\n; --- total_toolchanges: [total_toolchanges]\n; --- T0: {is_extruder_used[(initial_extruder % 2 == 0 ? min(initial_extruder + 0, 63) : max(initial_extruder - 1, 0))]}\n; --- T1: {is_extruder_used[(initial_extruder % 2 == 0 ? min(initial_extruder + 1, 63) : max(initial_extruder - 0, 0))]}\n\nT[initial_extruder]\n\nM140 S{first_layer_bed_temperature[initial_extruder]}\n\n; you can clean the nozzle\n{if is_extruder_used[(initial_extruder % 2 == 0 ? min(initial_extruder + 0, 63) : max(initial_extruder - (1-0), 0))]}\n M104 T{(initial_extruder % 2 == 0 ? min(initial_extruder + 0, 63) : max(initial_extruder - (1-0), 0))} S165\n {endif}\n{if is_extruder_used[(initial_extruder % 2 == 0 ? min(initial_extruder + 1, 63) : max(initial_extruder - (1-1), 0))]}\n M104 T{(initial_extruder % 2 == 0 ? min(initial_extruder + 1, 63) : max(initial_extruder - (1-1), 0))} S165\n {endif}\nM204 S100\nG28\nG0 Z126 F960.0\nG0 Y125.0 F3420.0\nG0 X115.0\n\nM190 R{first_layer_bed_temperature[initial_extruder]}\n\nG28\n{if 0==1} ; boundary check(for dual/quick swap kit), not recommanded if there are any clamps \n G0 X0\n G0 Z0.2 F960.0\n G0 Y0 F3420.0\n G0 X230\n G0 Y250\n G0 X0\n G0 Y0\n{endif}\n\nM83\n{if 1==1 && max(hot_plate_temp_initial_layer[initial_extruder], hot_plate_temp[initial_extruder]) >= 90}\nG0 Z0.06\nG92 Z0 ;reset z\n{endif}\n\n{if is_extruder_used[(initial_extruder % 2 == 0 ? min(initial_extruder + 0, 63) : max(initial_extruder - (1-0), 0))]}\n; preheat 0\nM104 T{(initial_extruder % 2 == 0 ? min(initial_extruder + 0, 63) : max(initial_extruder - (1-0), 0))} S{max(250, min(290, nozzle_temperature_initial_layer[(initial_extruder % 2 == 0 ? min(initial_extruder + 0, 63) : max(initial_extruder - (1-0), 0))] + 15))}\n {endif}\n{if is_extruder_used[(initial_extruder % 2 == 0 ? min(initial_extruder + 1, 63) : max(initial_extruder - (1-1), 0))]}\n; preheat 1\nM104 T{(initial_extruder % 2 == 0 ? min(initial_extruder + 1, 63) : max(initial_extruder - (1-1), 0))} S{max(250, min(290, nozzle_temperature_initial_layer[(initial_extruder % 2 == 0 ? min(initial_extruder + 1, 63) : max(initial_extruder - (1-1), 0))] + 15))}\n {endif}\n\n {if 1==1}\n{if is_extruder_used[(initial_extruder % 2 == 0 ? min(initial_extruder + 0, 63) : max(initial_extruder - (1-0), 0))] and (initial_extruder % 2) != 0}\n; flush nozzle 0\nT{(initial_extruder % 2 == 0 ? min(initial_extruder + 0, 63) : max(initial_extruder - (1-0), 0))}\nM104 S{max(250, min(290, nozzle_temperature_initial_layer[(initial_extruder % 2 == 0 ? min(initial_extruder + 0, 63) : max(initial_extruder - (1-0), 0))] + 15))}; common flush temp\nG0 Z1.6 F960.0\nG0 X{( 0 % 2 == 0 ? 100.0 : 130.0 )} F3420.0\nG0 Y0 F3420.0\n\nM109 S{max(250, min(290, nozzle_temperature_initial_layer[(initial_extruder % 2 == 0 ? min(initial_extruder + 0, 63) : max(initial_extruder - (1-0), 0))] + 15))} C2 W1\nG1 E20 F80.0\nG92 E0\n\nM106 S{min(255, (fan_max_speed[(initial_extruder % 2 == 0 ? min(initial_extruder + 0, 63) : max(initial_extruder - (1-0), 0))] + 10) * 2.55)}\n\nM104 S{nozzle_temperature_initial_layer[(initial_extruder % 2 == 0 ? min(initial_extruder + 0, 63) : max(initial_extruder - (1-0), 0))] + 5}\n\nG1 E12.0 F200\nG92 E0\nG1 E8.0 Z4.6 F200\nG92 E0\n\nG0 Z5.6 F200\nM107\n\nG0 X{( 0 % 2 == 0 ? 55.0 : 175.0 )} F3420.0\nG0 Z0.3 F960.0\nM109 S{nozzle_temperature_initial_layer[(initial_extruder % 2 == 0 ? min(initial_extruder + 0, 63) : max(initial_extruder - (1-0), 0))]} C3 W1\nG1 E3 F200\nG92 E0\nG1 X{( 0 % 2 == 0 ? 0 : 230 )} E3.42995 F3420.0\nG92 E0\n\nG1 E-{retract_length_toolchange[0]} F200\nG92 E0\nG0 Y20 F3420.0\n\nM104 S{temperature_vitrification[(initial_extruder % 2 == 0 ? min(initial_extruder + 0, 63) : max(initial_extruder - (1-0), 0))]}\n {endif}\n\n{if is_extruder_used[(initial_extruder % 2 == 0 ? min(initial_extruder + 1, 63) : max(initial_extruder - (1-1), 0))] and (initial_extruder % 2) != 1}\n; flush nozzle 1\nT{(initial_extruder % 2 == 0 ? min(initial_extruder + 1, 63) : max(initial_extruder - (1-1), 0))}\nM104 S{max(250, min(290, nozzle_temperature_initial_layer[(initial_extruder % 2 == 0 ? min(initial_extruder + 1, 63) : max(initial_extruder - (1-1), 0))] + 15))}; common flush temp\nG0 Z1.6 F960.0\nG0 X{( 1 % 2 == 0 ? 100.0 : 130.0 )} F3420.0\nG0 Y0 F3420.0\n\nM109 S{max(250, min(290, nozzle_temperature_initial_layer[(initial_extruder % 2 == 0 ? min(initial_extruder + 1, 63) : max(initial_extruder - (1-1), 0))] + 15))} C2 W1\nG1 E20 F80.0\nG92 E0\n\nM106 S{min(255, (fan_max_speed[(initial_extruder % 2 == 0 ? min(initial_extruder + 1, 63) : max(initial_extruder - (1-1), 0))] + 10) * 2.55)}\n\nM104 S{nozzle_temperature_initial_layer[(initial_extruder % 2 == 0 ? min(initial_extruder + 1, 63) : max(initial_extruder - (1-1), 0))] + 5}\n\nG1 E12.0 F200\nG92 E0\nG1 E8.0 Z4.6 F200\nG92 E0\n\nG0 Z5.6 F200\nM107\n\nG0 X{( 1 % 2 == 0 ? 55.0 : 175.0 )} F3420.0\nG0 Z0.3 F960.0\nM109 S{nozzle_temperature_initial_layer[(initial_extruder % 2 == 0 ? min(initial_extruder + 1, 63) : max(initial_extruder - (1-1), 0))]} C3 W1\nG1 E3 F200\nG92 E0\nG1 X{( 1 % 2 == 0 ? 0 : 230 )} E3.42995 F3420.0\nG92 E0\n\nG1 E-{retract_length_toolchange[1]} F200\nG92 E0\nG0 Y20 F3420.0\n\nM104 S{temperature_vitrification[(initial_extruder % 2 == 0 ? min(initial_extruder + 1, 63) : max(initial_extruder - (1-1), 0))]}\n {endif}\n\n {endif}\n; flush initial nozzle\nT[initial_extruder]\nM104 S{max(250, min(290, nozzle_temperature_initial_layer[(initial_extruder % 2 == 0 ? min(initial_extruder + initial_extruder, 63) : max(initial_extruder - (1-initial_extruder), 0))] + 15))}; common flush temp\nG0 Z1.6 F960.0\nG0 X{( initial_extruder % 2 == 0 ? 100.0 : 130.0 )} F3420.0\nG0 Y0 F3420.0\n\nM109 S{max(250, min(290, nozzle_temperature_initial_layer[(initial_extruder % 2 == 0 ? min(initial_extruder + initial_extruder, 63) : max(initial_extruder - (1-initial_extruder), 0))] + 15))} C2 W1\nG1 E20 F80.0\nG92 E0\n\nM106 S{min(255, (fan_max_speed[(initial_extruder % 2 == 0 ? min(initial_extruder + initial_extruder, 63) : max(initial_extruder - (1-initial_extruder), 0))] + 10) * 2.55)}\n\nM104 S{nozzle_temperature_initial_layer[(initial_extruder % 2 == 0 ? min(initial_extruder + initial_extruder, 63) : max(initial_extruder - (1-initial_extruder), 0))] + 5}\n\nG1 E12.0 F200\nG92 E0\nG1 E8.0 Z4.6 F200\nG92 E0\n\nG0 Z5.6 F200\nM107\n\nG0 X{( initial_extruder % 2 == 0 ? 55.0 : 175.0 )} F3420.0\nG0 Z0.3 F960.0\nM109 S{nozzle_temperature_initial_layer[(initial_extruder % 2 == 0 ? min(initial_extruder + initial_extruder, 63) : max(initial_extruder - (1-initial_extruder), 0))]} C3 W1\nG1 E3 F200\nG92 E0\nG1 X{( initial_extruder % 2 == 0 ? 0 : 230 )} E3.42995 F3420.0\nG92 E0\n\nG1 E-{retraction_length[initial_extruder]} F200\nG92 E0\nG0 Y20 F3420.0\n\n; ready [plate_name]", "machine_end_gcode": "G92 E0\n\nG0 Z{max_layer_z + 2.0} F600\n; retract the filament to make it easier to replace\nG0 E-10 F200\nG28\n\n{if is_extruder_used[(initial_extruder % 2 == 0 ? min(initial_extruder + 0, 63) : max(initial_extruder - (1-0), 0))]}\nM104 T{(initial_extruder % 2 == 0 ? min(initial_extruder + 0, 63) : max(initial_extruder - (1-0), 0))} S0\n {endif}\n{if is_extruder_used[(initial_extruder % 2 == 0 ? min(initial_extruder + 1, 63) : max(initial_extruder - (1-1), 0))]}\nM104 T{(initial_extruder % 2 == 0 ? min(initial_extruder + 1, 63) : max(initial_extruder - (1-1), 0))} S0\n {endif}\nM140 S0\nM107\nM220 S100\nM84\n\n;\n; DON'T REMOVE these lines if you're using the smfix (https://github.com/macdylan/SMFix)\n; min_x = [first_layer_print_min_0]\n; min_y = [first_layer_print_min_1]\n; max_x = [first_layer_print_max_0]\n; max_y = [first_layer_print_max_1]\n; max_z = [max_layer_z]\n; total_layer_number = [layer_num]\n;", - "change_filament_gcode": ";***** Update: 20230923\n{if current_extruder != next_extruder}\n; Change T[current_extruder] -> T[next_extruder] (layer [layer_num]\n; layer\nT{next_extruder}\n\nM107 P[current_extruder] ;fan off T[current_extruder]\nM104 T[current_extruder] S{temperature_vitrification[current_extruder]} ;standby T[current_extruder]\n\n{if layer_num == 1 &&\n ((filament_type[current_extruder] == \"PLA\" || filament_type[current_extruder] == \"TPU\")\n || (filament_type[next_extruder] == \"PLA\" || filament_type[next_extruder] == \"TPU\"))\n}\n ; set bed temp: {filament_type[current_extruder]}({bed_temperature[current_extruder]}) -> {filament_type[next_extruder]}({bed_temperature[next_extruder]})\n M140 S{min(bed_temperature[current_extruder], bed_temperature[next_extruder])}\n{endif}\n\nM109 T[next_extruder] S{if layer_num < 1}[nozzle_temperature_initial_layer]{else}[nozzle_temperature]{endif} C3 W1 ;wait T[next_extruder]\n{if layer_num == 1}\n G1 E{retraction_length[next_extruder]} F200;deretract\n{endif}\n\n{if layer_z > first_layer_height && layer_num >= close_fan_the_first_x_layers[next_extruder]}\n; M106 P[next_extruder] S{fan_min_speed[next_extruder] * 255.0 / 100.0} ;restore fan speed for T[next_extruder]\n{endif}\n; End Toolchange\n{endif}", + "change_filament_gcode": ";***** Update: 20240823\n{if current_extruder != next_extruder}\n; Change T[current_extruder] -> T[next_extruder] (layer [layer_num]\n; layer\nT{next_extruder}\n\n{if layer_num == 1 &&\n ((filament_type[current_extruder] == \"PLA\" || filament_type[current_extruder] == \"TPU\")\n || (filament_type[next_extruder] == \"PLA\" || filament_type[next_extruder] == \"TPU\"))\n}\n ; set bed temp: {filament_type[current_extruder]}({bed_temperature[current_extruder]}) -> {filament_type[next_extruder]}({bed_temperature[next_extruder]})\n M140 S{min(bed_temperature[current_extruder], bed_temperature[next_extruder])}\n{endif}\n\nM109 T[next_extruder] S{if layer_num < 1}[nozzle_temperature_initial_layer]{else}[nozzle_temperature]{endif} C3 W1 ;wait T[next_extruder]\n{if layer_num == 1}\n G1 E{retraction_length[next_extruder]} F200;deretract\n{endif}\n\n{if layer_z > first_layer_height && layer_num >= close_fan_the_first_x_layers[next_extruder]}\n; M106 P[next_extruder] S{fan_min_speed[next_extruder] * 255.0 / 100.0} ;restore fan speed for T[next_extruder]\n{endif}\n; End Toolchange\n{endif}", "before_layer_change_gcode": "; layer_num: [layer_num]\nG92 E0" } \ No newline at end of file diff --git a/resources/profiles/Snapmaker/machine/fdm_a350.json b/resources/profiles/Snapmaker/machine/fdm_a350.json index 1ae94e4af74..dea0f9cdad7 100644 --- a/resources/profiles/Snapmaker/machine/fdm_a350.json +++ b/resources/profiles/Snapmaker/machine/fdm_a350.json @@ -14,7 +14,7 @@ "320x350", "0x350" ], - "machine_start_gcode": "; Model: Snapmaker A350\n; Update: 20240428\n; Maintained by https://github.com/macdylan/3dp-configs\n; Printer : [printer_preset]\n; Profile : [print_preset]\n; Plate : [plate_name]\n\nT[initial_extruder]\n\nM140 S{first_layer_bed_temperature[initial_extruder]}\n\n; you can clean the nozzle\nM104 S165\nM204 S100\nG28\nG0 Z216 F960.0\nG0 Y175.0 F3420.0\nG0 X160.0\n\nM190 R{first_layer_bed_temperature[initial_extruder]}\n\nG28\n{if 1==1}\n G0 X0\n G0 Z0.2 F960.0\n G0 Y0 F3420.0\n G0 X320\n G0 Y350\n G0 X0\n G0 Y0\n{endif}\n\nM83\n{if 1==1 && max(hot_plate_temp_initial_layer[initial_extruder], hot_plate_temp[initial_extruder]) >= 90}\nG0 Z0.06\nG92 Z0 ;reset z\n{endif}\n\n; flush initial nozzle\nT[initial_extruder]\nM104 S{max(250, min(290, nozzle_temperature_initial_layer[(initial_extruder % 2 == 0 ? min(initial_extruder + initial_extruder, 63) : max(initial_extruder - (1-initial_extruder), 0))] + 15))}; common flush temp\nG0 Z1.6 F960.0\nG0 X{( initial_extruder % 2 == 0 ? 145.0 : 175.0 )} F3420.0\nG0 Y0 F3420.0\n\nM109 S{max(250, min(290, nozzle_temperature_initial_layer[(initial_extruder % 2 == 0 ? min(initial_extruder + initial_extruder, 63) : max(initial_extruder - (1-initial_extruder), 0))] + 15))} C2 W1\nG1 E15 F80.0\nG92 E0\n\nM106 S{min(255, (fan_max_speed[(initial_extruder % 2 == 0 ? min(initial_extruder + initial_extruder, 63) : max(initial_extruder - (1-initial_extruder), 0))] + 10) * 2.55)}\n\nM104 S{nozzle_temperature_initial_layer[(initial_extruder % 2 == 0 ? min(initial_extruder + initial_extruder, 63) : max(initial_extruder - (1-initial_extruder), 0))] + 5}\n\nG1 E9.0 F200\nG92 E0\nG1 E6.0 Z4.6 F200\nG92 E0\n\nG0 Z5.6 F200\nM107\n\nG0 X{( initial_extruder % 2 == 0 ? 100.0 : 220.0 )} F3420.0\nG0 Z0.3 F960.0\nM109 S{nozzle_temperature_initial_layer[(initial_extruder % 2 == 0 ? min(initial_extruder + initial_extruder, 63) : max(initial_extruder - (1-initial_extruder), 0))]} C3 W1\nG1 E3 F200\nG92 E0\nG1 X{( initial_extruder % 2 == 0 ? 0 : 320 )} E6.23628 F3420.0\nG92 E0\n\nG1 E-{retraction_length[initial_extruder]} F200\nG92 E0\nG0 Y20 F3420.0\n\n; ready [plate_name]", + "machine_start_gcode": "; Model: Snapmaker A350\n; Update: 20240922\n; Maintained by https://github.com/macdylan/3dp-configs\n; Printer : [printer_preset]\n; Profile : [print_preset]\n; Plate : [plate_name]\n\nT[initial_extruder]\n\nM140 S{first_layer_bed_temperature[initial_extruder]}\n\n; you can clean the nozzle\nM104 S165\nM204 S100\nG28\nG0 Z216 F960.0\nG0 Y175.0 F3420.0\nG0 X160.0\n\nM190 R{first_layer_bed_temperature[initial_extruder]}\n\nG28\n{if 0==1} ; boundary check(for dual/quick swap kit), not recommanded if there are any clamps \n G0 X0\n G0 Z0.2 F960.0\n G0 Y0 F3420.0\n G0 X320\n G0 Y350\n G0 X0\n G0 Y0\n{endif}\n\nM83\n{if 1==1 && max(hot_plate_temp_initial_layer[initial_extruder], hot_plate_temp[initial_extruder]) >= 90}\nG0 Z0.06\nG92 Z0 ;reset z\n{endif}\n\n; flush initial nozzle\nT[initial_extruder]\nM104 S{max(250, min(290, nozzle_temperature_initial_layer[(initial_extruder % 2 == 0 ? min(initial_extruder + initial_extruder, 63) : max(initial_extruder - (1-initial_extruder), 0))] + 15))}; common flush temp\nG0 Z1.6 F960.0\nG0 X{( initial_extruder % 2 == 0 ? 145.0 : 175.0 )} F3420.0\nG0 Y0 F3420.0\n\nM109 S{max(250, min(290, nozzle_temperature_initial_layer[(initial_extruder % 2 == 0 ? min(initial_extruder + initial_extruder, 63) : max(initial_extruder - (1-initial_extruder), 0))] + 15))} C2 W1\nG1 E15 F80.0\nG92 E0\n\nM106 S{min(255, (fan_max_speed[(initial_extruder % 2 == 0 ? min(initial_extruder + initial_extruder, 63) : max(initial_extruder - (1-initial_extruder), 0))] + 10) * 2.55)}\n\nM104 S{nozzle_temperature_initial_layer[(initial_extruder % 2 == 0 ? min(initial_extruder + initial_extruder, 63) : max(initial_extruder - (1-initial_extruder), 0))] + 5}\n\nG1 E9.0 F200\nG92 E0\nG1 E6.0 Z4.6 F200\nG92 E0\n\nG0 Z5.6 F200\nM107\n\nG0 X{( initial_extruder % 2 == 0 ? 100.0 : 220.0 )} F3420.0\nG0 Z0.3 F960.0\nM109 S{nozzle_temperature_initial_layer[(initial_extruder % 2 == 0 ? min(initial_extruder + initial_extruder, 63) : max(initial_extruder - (1-initial_extruder), 0))]} C3 W1\nG1 E3 F200\nG92 E0\nG1 X{( initial_extruder % 2 == 0 ? 0 : 320 )} E6.23628 F3420.0\nG92 E0\n\nG1 E-{retraction_length[initial_extruder]} F200\nG92 E0\nG0 Y20 F3420.0\n\n; ready [plate_name]", "machine_end_gcode": "G92 E0\n\nG0 Z{max_layer_z + 2.0} F600\n; retract the filament to make it easier to replace\nG0 E-5 F200\nG28\n\n M104 S0\nM140 S0\nM107\nM220 S100\nM84\n\n;\n; DON'T REMOVE these lines if you're using the smfix (https://github.com/macdylan/SMFix)\n; min_x = [first_layer_print_min_0]\n; min_y = [first_layer_print_min_1]\n; max_x = [first_layer_print_max_0]\n; max_y = [first_layer_print_max_1]\n; max_z = [max_layer_z]\n; total_layer_number = [layer_num]\n;", "before_layer_change_gcode": "; layer_num: [layer_num]\nG92 E0" } \ No newline at end of file diff --git a/resources/profiles/Snapmaker/machine/fdm_a350_dual.json b/resources/profiles/Snapmaker/machine/fdm_a350_dual.json index a2e3a405890..83448eaa908 100644 --- a/resources/profiles/Snapmaker/machine/fdm_a350_dual.json +++ b/resources/profiles/Snapmaker/machine/fdm_a350_dual.json @@ -20,8 +20,8 @@ "320x350", "0x350" ], - "machine_start_gcode": "; Model: Snapmaker A350 Dual ({nozzle_diameter[0]}/{nozzle_diameter[1]})\n; Update: 20240428\n; Maintained by https://github.com/macdylan/3dp-configs\n; Printer : [printer_preset]\n; Profile : [print_preset]\n; Plate : [plate_name]\n; --- initial_extruder: [initial_extruder]\n; --- has_wipe_tower: [has_wipe_tower]\n; --- total_toolchanges: [total_toolchanges]\n; --- T0: {is_extruder_used[(initial_extruder % 2 == 0 ? min(initial_extruder + 0, 63) : max(initial_extruder - 1, 0))]}\n; --- T1: {is_extruder_used[(initial_extruder % 2 == 0 ? min(initial_extruder + 1, 63) : max(initial_extruder - 0, 0))]}\n\nT[initial_extruder]\n\nM140 S{first_layer_bed_temperature[initial_extruder]}\n\n; you can clean the nozzle\n{if is_extruder_used[(initial_extruder % 2 == 0 ? min(initial_extruder + 0, 63) : max(initial_extruder - (1-0), 0))]}\n M104 T{(initial_extruder % 2 == 0 ? min(initial_extruder + 0, 63) : max(initial_extruder - (1-0), 0))} S165\n {endif}\n{if is_extruder_used[(initial_extruder % 2 == 0 ? min(initial_extruder + 1, 63) : max(initial_extruder - (1-1), 0))]}\n M104 T{(initial_extruder % 2 == 0 ? min(initial_extruder + 1, 63) : max(initial_extruder - (1-1), 0))} S165\n {endif}\nM204 S100\nG28\nG0 Z190 F960.0\nG0 Y175.0 F3420.0\nG0 X160.0\n\nM190 R{first_layer_bed_temperature[initial_extruder]}\n\nG28\n{if 1==1}\n G0 X0\n G0 Z0.2 F960.0\n G0 Y0 F3420.0\n G0 X320\n G0 Y350\n G0 X0\n G0 Y0\n{endif}\n\nM83\n{if 1==1 && max(hot_plate_temp_initial_layer[initial_extruder], hot_plate_temp[initial_extruder]) >= 90}\nG0 Z0.06\nG92 Z0 ;reset z\n{endif}\n\n{if is_extruder_used[(initial_extruder % 2 == 0 ? min(initial_extruder + 0, 63) : max(initial_extruder - (1-0), 0))]}\n; preheat 0\nM104 T{(initial_extruder % 2 == 0 ? min(initial_extruder + 0, 63) : max(initial_extruder - (1-0), 0))} S{max(250, min(290, nozzle_temperature_initial_layer[(initial_extruder % 2 == 0 ? min(initial_extruder + 0, 63) : max(initial_extruder - (1-0), 0))] + 15))}\n {endif}\n{if is_extruder_used[(initial_extruder % 2 == 0 ? min(initial_extruder + 1, 63) : max(initial_extruder - (1-1), 0))]}\n; preheat 1\nM104 T{(initial_extruder % 2 == 0 ? min(initial_extruder + 1, 63) : max(initial_extruder - (1-1), 0))} S{max(250, min(290, nozzle_temperature_initial_layer[(initial_extruder % 2 == 0 ? min(initial_extruder + 1, 63) : max(initial_extruder - (1-1), 0))] + 15))}\n {endif}\n\n{if is_extruder_used[(initial_extruder % 2 == 0 ? min(initial_extruder + 0, 63) : max(initial_extruder - (1-0), 0))] and (initial_extruder % 2) != 0}\n; flush nozzle 0\nT{(initial_extruder % 2 == 0 ? min(initial_extruder + 0, 63) : max(initial_extruder - (1-0), 0))}\nM104 S{max(250, min(290, nozzle_temperature_initial_layer[(initial_extruder % 2 == 0 ? min(initial_extruder + 0, 63) : max(initial_extruder - (1-0), 0))] + 15))}; common flush temp\nG0 Z1.6 F960.0\nG0 X{( 0 % 2 == 0 ? 145.0 : 175.0 )} F3420.0\nG0 Y0 F3420.0\n\nM109 S{max(250, min(290, nozzle_temperature_initial_layer[(initial_extruder % 2 == 0 ? min(initial_extruder + 0, 63) : max(initial_extruder - (1-0), 0))] + 15))} C2 W1\nG1 E20 F80.0\nG92 E0\n\nM106 S{min(255, (fan_max_speed[(initial_extruder % 2 == 0 ? min(initial_extruder + 0, 63) : max(initial_extruder - (1-0), 0))] + 10) * 2.55)}\n\nM104 S{nozzle_temperature_initial_layer[(initial_extruder % 2 == 0 ? min(initial_extruder + 0, 63) : max(initial_extruder - (1-0), 0))] + 5}\n\nG1 E12.0 F200\nG92 E0\nG1 E8.0 Z4.6 F200\nG92 E0\n\nG0 Z5.6 F200\nM107\n\nG0 X{( 0 % 2 == 0 ? 100.0 : 220.0 )} F3420.0\nG0 Z0.3 F960.0\nM109 S{nozzle_temperature_initial_layer[(initial_extruder % 2 == 0 ? min(initial_extruder + 0, 63) : max(initial_extruder - (1-0), 0))]} C3 W1\nG1 E3 F200\nG92 E0\nG1 X{( 0 % 2 == 0 ? 0 : 320 )} E6.23628 F3420.0\nG92 E0\n\nG1 E-{retract_length_toolchange[0]} F200\nG92 E0\nG0 Y20 F3420.0\nM104 S{temperature_vitrification[(initial_extruder % 2 == 0 ? min(initial_extruder + 0, 63) : max(initial_extruder - (1-0), 0))]}\n {endif}\n\n{if is_extruder_used[(initial_extruder % 2 == 0 ? min(initial_extruder + 1, 63) : max(initial_extruder - (1-1), 0))] and (initial_extruder % 2) != 1}\n; flush nozzle 1\nT{(initial_extruder % 2 == 0 ? min(initial_extruder + 1, 63) : max(initial_extruder - (1-1), 0))}\nM104 S{max(250, min(290, nozzle_temperature_initial_layer[(initial_extruder % 2 == 0 ? min(initial_extruder + 1, 63) : max(initial_extruder - (1-1), 0))] + 15))}; common flush temp\nG0 Z1.6 F960.0\nG0 X{( 1 % 2 == 0 ? 145.0 : 175.0 )} F3420.0\nG0 Y0 F3420.0\n\nM109 S{max(250, min(290, nozzle_temperature_initial_layer[(initial_extruder % 2 == 0 ? min(initial_extruder + 1, 63) : max(initial_extruder - (1-1), 0))] + 15))} C2 W1\nG1 E20 F80.0\nG92 E0\n\nM106 S{min(255, (fan_max_speed[(initial_extruder % 2 == 0 ? min(initial_extruder + 1, 63) : max(initial_extruder - (1-1), 0))] + 10) * 2.55)}\n\nM104 S{nozzle_temperature_initial_layer[(initial_extruder % 2 == 0 ? min(initial_extruder + 1, 63) : max(initial_extruder - (1-1), 0))] + 5}\n\nG1 E12.0 F200\nG92 E0\nG1 E8.0 Z4.6 F200\nG92 E0\n\nG0 Z5.6 F200\nM107\n\nG0 X{( 1 % 2 == 0 ? 100.0 : 220.0 )} F3420.0\nG0 Z0.3 F960.0\nM109 S{nozzle_temperature_initial_layer[(initial_extruder % 2 == 0 ? min(initial_extruder + 1, 63) : max(initial_extruder - (1-1), 0))]} C3 W1\nG1 E3 F200\nG92 E0\nG1 X{( 1 % 2 == 0 ? 0 : 320 )} E6.23628 F3420.0\nG92 E0\n\nG1 E-{retract_length_toolchange[1]} F200\nG92 E0\nG0 Y20 F3420.0\nM104 S{temperature_vitrification[(initial_extruder % 2 == 0 ? min(initial_extruder + 1, 63) : max(initial_extruder - (1-1), 0))]}\n {endif}\n\n; flush initial nozzle\nT[initial_extruder]\nM104 S{max(250, min(290, nozzle_temperature_initial_layer[(initial_extruder % 2 == 0 ? min(initial_extruder + initial_extruder, 63) : max(initial_extruder - (1-initial_extruder), 0))] + 15))}; common flush temp\nG0 Z1.6 F960.0\nG0 X{( initial_extruder % 2 == 0 ? 145.0 : 175.0 )} F3420.0\nG0 Y0 F3420.0\n\nM109 S{max(250, min(290, nozzle_temperature_initial_layer[(initial_extruder % 2 == 0 ? min(initial_extruder + initial_extruder, 63) : max(initial_extruder - (1-initial_extruder), 0))] + 15))} C2 W1\nG1 E20 F80.0\nG92 E0\n\nM106 S{min(255, (fan_max_speed[(initial_extruder % 2 == 0 ? min(initial_extruder + initial_extruder, 63) : max(initial_extruder - (1-initial_extruder), 0))] + 10) * 2.55)}\n\nM104 S{nozzle_temperature_initial_layer[(initial_extruder % 2 == 0 ? min(initial_extruder + initial_extruder, 63) : max(initial_extruder - (1-initial_extruder), 0))] + 5}\n\nG1 E12.0 F200\nG92 E0\nG1 E8.0 Z4.6 F200\nG92 E0\n\nG0 Z5.6 F200\nM107\n\nG0 X{( initial_extruder % 2 == 0 ? 100.0 : 220.0 )} F3420.0\nG0 Z0.3 F960.0\nM109 S{nozzle_temperature_initial_layer[(initial_extruder % 2 == 0 ? min(initial_extruder + initial_extruder, 63) : max(initial_extruder - (1-initial_extruder), 0))]} C3 W1\nG1 E3 F200\nG92 E0\nG1 X{( initial_extruder % 2 == 0 ? 0 : 320 )} E6.23628 F3420.0\nG92 E0\n\nG1 E-{retraction_length[initial_extruder]} F200\nG92 E0\nG0 Y20 F3420.0\n\n; ready [plate_name]", + "machine_start_gcode": "; Model: Snapmaker A350 Dual ({nozzle_diameter[0]}/{nozzle_diameter[1]})\n; Update: 20240922\n; Maintained by https://github.com/macdylan/3dp-configs\n; Printer : [printer_preset]\n; Profile : [print_preset]\n; Plate : [plate_name]\n; --- initial_extruder: [initial_extruder]\n; --- has_wipe_tower: [has_wipe_tower]\n; --- total_toolchanges: [total_toolchanges]\n; --- T0: {is_extruder_used[(initial_extruder % 2 == 0 ? min(initial_extruder + 0, 63) : max(initial_extruder - 1, 0))]}\n; --- T1: {is_extruder_used[(initial_extruder % 2 == 0 ? min(initial_extruder + 1, 63) : max(initial_extruder - 0, 0))]}\n\nT[initial_extruder]\n\nM140 S{first_layer_bed_temperature[initial_extruder]}\n\n; you can clean the nozzle\n{if is_extruder_used[(initial_extruder % 2 == 0 ? min(initial_extruder + 0, 63) : max(initial_extruder - (1-0), 0))]}\n M104 T{(initial_extruder % 2 == 0 ? min(initial_extruder + 0, 63) : max(initial_extruder - (1-0), 0))} S165\n {endif}\n{if is_extruder_used[(initial_extruder % 2 == 0 ? min(initial_extruder + 1, 63) : max(initial_extruder - (1-1), 0))]}\n M104 T{(initial_extruder % 2 == 0 ? min(initial_extruder + 1, 63) : max(initial_extruder - (1-1), 0))} S165\n {endif}\nM204 S100\nG28\nG0 Z190 F960.0\nG0 Y175.0 F3420.0\nG0 X160.0\n\nM190 R{first_layer_bed_temperature[initial_extruder]}\n\nG28\n{if 0==1} ; boundary check(for dual/quick swap kit), not recommanded if there are any clamps \n G0 X0\n G0 Z0.2 F960.0\n G0 Y0 F3420.0\n G0 X320\n G0 Y350\n G0 X0\n G0 Y0\n{endif}\n\nM83\n{if 1==1 && max(hot_plate_temp_initial_layer[initial_extruder], hot_plate_temp[initial_extruder]) >= 90}\nG0 Z0.06\nG92 Z0 ;reset z\n{endif}\n\n{if is_extruder_used[(initial_extruder % 2 == 0 ? min(initial_extruder + 0, 63) : max(initial_extruder - (1-0), 0))]}\n; preheat 0\nM104 T{(initial_extruder % 2 == 0 ? min(initial_extruder + 0, 63) : max(initial_extruder - (1-0), 0))} S{max(250, min(290, nozzle_temperature_initial_layer[(initial_extruder % 2 == 0 ? min(initial_extruder + 0, 63) : max(initial_extruder - (1-0), 0))] + 15))}\n {endif}\n{if is_extruder_used[(initial_extruder % 2 == 0 ? min(initial_extruder + 1, 63) : max(initial_extruder - (1-1), 0))]}\n; preheat 1\nM104 T{(initial_extruder % 2 == 0 ? min(initial_extruder + 1, 63) : max(initial_extruder - (1-1), 0))} S{max(250, min(290, nozzle_temperature_initial_layer[(initial_extruder % 2 == 0 ? min(initial_extruder + 1, 63) : max(initial_extruder - (1-1), 0))] + 15))}\n {endif}\n\n {if 1==1}\n{if is_extruder_used[(initial_extruder % 2 == 0 ? min(initial_extruder + 0, 63) : max(initial_extruder - (1-0), 0))] and (initial_extruder % 2) != 0}\n; flush nozzle 0\nT{(initial_extruder % 2 == 0 ? min(initial_extruder + 0, 63) : max(initial_extruder - (1-0), 0))}\nM104 S{max(250, min(290, nozzle_temperature_initial_layer[(initial_extruder % 2 == 0 ? min(initial_extruder + 0, 63) : max(initial_extruder - (1-0), 0))] + 15))}; common flush temp\nG0 Z1.6 F960.0\nG0 X{( 0 % 2 == 0 ? 145.0 : 175.0 )} F3420.0\nG0 Y0 F3420.0\n\nM109 S{max(250, min(290, nozzle_temperature_initial_layer[(initial_extruder % 2 == 0 ? min(initial_extruder + 0, 63) : max(initial_extruder - (1-0), 0))] + 15))} C2 W1\nG1 E20 F80.0\nG92 E0\n\nM106 S{min(255, (fan_max_speed[(initial_extruder % 2 == 0 ? min(initial_extruder + 0, 63) : max(initial_extruder - (1-0), 0))] + 10) * 2.55)}\n\nM104 S{nozzle_temperature_initial_layer[(initial_extruder % 2 == 0 ? min(initial_extruder + 0, 63) : max(initial_extruder - (1-0), 0))] + 5}\n\nG1 E12.0 F200\nG92 E0\nG1 E8.0 Z4.6 F200\nG92 E0\n\nG0 Z5.6 F200\nM107\n\nG0 X{( 0 % 2 == 0 ? 100.0 : 220.0 )} F3420.0\nG0 Z0.3 F960.0\nM109 S{nozzle_temperature_initial_layer[(initial_extruder % 2 == 0 ? min(initial_extruder + 0, 63) : max(initial_extruder - (1-0), 0))]} C3 W1\nG1 E3 F200\nG92 E0\nG1 X{( 0 % 2 == 0 ? 0 : 320 )} E6.23628 F3420.0\nG92 E0\n\nG1 E-{retract_length_toolchange[0]} F200\nG92 E0\nG0 Y20 F3420.0\n\nM104 S{temperature_vitrification[(initial_extruder % 2 == 0 ? min(initial_extruder + 0, 63) : max(initial_extruder - (1-0), 0))]}\n {endif}\n\n{if is_extruder_used[(initial_extruder % 2 == 0 ? min(initial_extruder + 1, 63) : max(initial_extruder - (1-1), 0))] and (initial_extruder % 2) != 1}\n; flush nozzle 1\nT{(initial_extruder % 2 == 0 ? min(initial_extruder + 1, 63) : max(initial_extruder - (1-1), 0))}\nM104 S{max(250, min(290, nozzle_temperature_initial_layer[(initial_extruder % 2 == 0 ? min(initial_extruder + 1, 63) : max(initial_extruder - (1-1), 0))] + 15))}; common flush temp\nG0 Z1.6 F960.0\nG0 X{( 1 % 2 == 0 ? 145.0 : 175.0 )} F3420.0\nG0 Y0 F3420.0\n\nM109 S{max(250, min(290, nozzle_temperature_initial_layer[(initial_extruder % 2 == 0 ? min(initial_extruder + 1, 63) : max(initial_extruder - (1-1), 0))] + 15))} C2 W1\nG1 E20 F80.0\nG92 E0\n\nM106 S{min(255, (fan_max_speed[(initial_extruder % 2 == 0 ? min(initial_extruder + 1, 63) : max(initial_extruder - (1-1), 0))] + 10) * 2.55)}\n\nM104 S{nozzle_temperature_initial_layer[(initial_extruder % 2 == 0 ? min(initial_extruder + 1, 63) : max(initial_extruder - (1-1), 0))] + 5}\n\nG1 E12.0 F200\nG92 E0\nG1 E8.0 Z4.6 F200\nG92 E0\n\nG0 Z5.6 F200\nM107\n\nG0 X{( 1 % 2 == 0 ? 100.0 : 220.0 )} F3420.0\nG0 Z0.3 F960.0\nM109 S{nozzle_temperature_initial_layer[(initial_extruder % 2 == 0 ? min(initial_extruder + 1, 63) : max(initial_extruder - (1-1), 0))]} C3 W1\nG1 E3 F200\nG92 E0\nG1 X{( 1 % 2 == 0 ? 0 : 320 )} E6.23628 F3420.0\nG92 E0\n\nG1 E-{retract_length_toolchange[1]} F200\nG92 E0\nG0 Y20 F3420.0\n\nM104 S{temperature_vitrification[(initial_extruder % 2 == 0 ? min(initial_extruder + 1, 63) : max(initial_extruder - (1-1), 0))]}\n {endif}\n\n {endif}\n; flush initial nozzle\nT[initial_extruder]\nM104 S{max(250, min(290, nozzle_temperature_initial_layer[(initial_extruder % 2 == 0 ? min(initial_extruder + initial_extruder, 63) : max(initial_extruder - (1-initial_extruder), 0))] + 15))}; common flush temp\nG0 Z1.6 F960.0\nG0 X{( initial_extruder % 2 == 0 ? 145.0 : 175.0 )} F3420.0\nG0 Y0 F3420.0\n\nM109 S{max(250, min(290, nozzle_temperature_initial_layer[(initial_extruder % 2 == 0 ? min(initial_extruder + initial_extruder, 63) : max(initial_extruder - (1-initial_extruder), 0))] + 15))} C2 W1\nG1 E20 F80.0\nG92 E0\n\nM106 S{min(255, (fan_max_speed[(initial_extruder % 2 == 0 ? min(initial_extruder + initial_extruder, 63) : max(initial_extruder - (1-initial_extruder), 0))] + 10) * 2.55)}\n\nM104 S{nozzle_temperature_initial_layer[(initial_extruder % 2 == 0 ? min(initial_extruder + initial_extruder, 63) : max(initial_extruder - (1-initial_extruder), 0))] + 5}\n\nG1 E12.0 F200\nG92 E0\nG1 E8.0 Z4.6 F200\nG92 E0\n\nG0 Z5.6 F200\nM107\n\nG0 X{( initial_extruder % 2 == 0 ? 100.0 : 220.0 )} F3420.0\nG0 Z0.3 F960.0\nM109 S{nozzle_temperature_initial_layer[(initial_extruder % 2 == 0 ? min(initial_extruder + initial_extruder, 63) : max(initial_extruder - (1-initial_extruder), 0))]} C3 W1\nG1 E3 F200\nG92 E0\nG1 X{( initial_extruder % 2 == 0 ? 0 : 320 )} E6.23628 F3420.0\nG92 E0\n\nG1 E-{retraction_length[initial_extruder]} F200\nG92 E0\nG0 Y20 F3420.0\n\n; ready [plate_name]", "machine_end_gcode": "G92 E0\n\nG0 Z{max_layer_z + 2.0} F600\n; retract the filament to make it easier to replace\nG0 E-10 F200\nG28\n\n{if is_extruder_used[(initial_extruder % 2 == 0 ? min(initial_extruder + 0, 63) : max(initial_extruder - (1-0), 0))]}\nM104 T{(initial_extruder % 2 == 0 ? min(initial_extruder + 0, 63) : max(initial_extruder - (1-0), 0))} S0\n {endif}\n{if is_extruder_used[(initial_extruder % 2 == 0 ? min(initial_extruder + 1, 63) : max(initial_extruder - (1-1), 0))]}\nM104 T{(initial_extruder % 2 == 0 ? min(initial_extruder + 1, 63) : max(initial_extruder - (1-1), 0))} S0\n {endif}\nM140 S0\nM107\nM220 S100\nM84\n\n;\n; DON'T REMOVE these lines if you're using the smfix (https://github.com/macdylan/SMFix)\n; min_x = [first_layer_print_min_0]\n; min_y = [first_layer_print_min_1]\n; max_x = [first_layer_print_max_0]\n; max_y = [first_layer_print_max_1]\n; max_z = [max_layer_z]\n; total_layer_number = [layer_num]\n;", - "change_filament_gcode": ";***** Update: 20230923\n{if current_extruder != next_extruder}\n; Change T[current_extruder] -> T[next_extruder] (layer [layer_num]\n; layer\nT{next_extruder}\n\nM107 P[current_extruder] ;fan off T[current_extruder]\nM104 T[current_extruder] S{temperature_vitrification[current_extruder]} ;standby T[current_extruder]\n\n{if layer_num == 1 &&\n ((filament_type[current_extruder] == \"PLA\" || filament_type[current_extruder] == \"TPU\")\n || (filament_type[next_extruder] == \"PLA\" || filament_type[next_extruder] == \"TPU\"))\n}\n ; set bed temp: {filament_type[current_extruder]}({bed_temperature[current_extruder]}) -> {filament_type[next_extruder]}({bed_temperature[next_extruder]})\n M140 S{min(bed_temperature[current_extruder], bed_temperature[next_extruder])}\n{endif}\n\nM109 T[next_extruder] S{if layer_num < 1}[nozzle_temperature_initial_layer]{else}[nozzle_temperature]{endif} C3 W1 ;wait T[next_extruder]\n{if layer_num == 1}\n G1 E{retraction_length[next_extruder]} F200;deretract\n{endif}\n\n{if layer_z > first_layer_height && layer_num >= close_fan_the_first_x_layers[next_extruder]}\n; M106 P[next_extruder] S{fan_min_speed[next_extruder] * 255.0 / 100.0} ;restore fan speed for T[next_extruder]\n{endif}\n; End Toolchange\n{endif}", + "change_filament_gcode": ";***** Update: 20240823\n{if current_extruder != next_extruder}\n; Change T[current_extruder] -> T[next_extruder] (layer [layer_num]\n; layer\nT{next_extruder}\n\n{if layer_num == 1 &&\n ((filament_type[current_extruder] == \"PLA\" || filament_type[current_extruder] == \"TPU\")\n || (filament_type[next_extruder] == \"PLA\" || filament_type[next_extruder] == \"TPU\"))\n}\n ; set bed temp: {filament_type[current_extruder]}({bed_temperature[current_extruder]}) -> {filament_type[next_extruder]}({bed_temperature[next_extruder]})\n M140 S{min(bed_temperature[current_extruder], bed_temperature[next_extruder])}\n{endif}\n\nM109 T[next_extruder] S{if layer_num < 1}[nozzle_temperature_initial_layer]{else}[nozzle_temperature]{endif} C3 W1 ;wait T[next_extruder]\n{if layer_num == 1}\n G1 E{retraction_length[next_extruder]} F200;deretract\n{endif}\n\n{if layer_z > first_layer_height && layer_num >= close_fan_the_first_x_layers[next_extruder]}\n; M106 P[next_extruder] S{fan_min_speed[next_extruder] * 255.0 / 100.0} ;restore fan speed for T[next_extruder]\n{endif}\n; End Toolchange\n{endif}", "before_layer_change_gcode": "; layer_num: [layer_num]\nG92 E0" } \ No newline at end of file diff --git a/resources/profiles/Snapmaker/machine/fdm_a400.json b/resources/profiles/Snapmaker/machine/fdm_a400.json index 4bfd754b692..f21783f25a9 100644 --- a/resources/profiles/Snapmaker/machine/fdm_a400.json +++ b/resources/profiles/Snapmaker/machine/fdm_a400.json @@ -52,8 +52,8 @@ "400x400", "0x400" ], - "machine_start_gcode": "; Model: Snapmaker Artisan ({nozzle_diameter[0]}/{nozzle_diameter[1]})\n; Update: 20240428\n; Maintained by https://github.com/macdylan/3dp-configs\n; Printer : [printer_preset]\n; Profile : [print_preset]\n; Plate : [plate_name]\n; --- initial_extruder: [initial_extruder]\n; --- has_wipe_tower: [has_wipe_tower]\n; --- total_toolchanges: [total_toolchanges]\n; --- T0: {is_extruder_used[(initial_extruder % 2 == 0 ? min(initial_extruder + 0, 63) : max(initial_extruder - 1, 0))]}\n; --- T1: {is_extruder_used[(initial_extruder % 2 == 0 ? min(initial_extruder + 1, 63) : max(initial_extruder - 0, 0))]}\n\nT[initial_extruder]\n\nM205 V[machine_max_jerk_x] ;Junction Deviation (mm)\n\nM140 S{first_layer_bed_temperature[initial_extruder]}\n\n; you can clean the nozzle\n{if is_extruder_used[(initial_extruder % 2 == 0 ? min(initial_extruder + 0, 63) : max(initial_extruder - (1-0), 0))]}\n M104 T{(initial_extruder % 2 == 0 ? min(initial_extruder + 0, 63) : max(initial_extruder - (1-0), 0))} S165\n {endif}\n{if is_extruder_used[(initial_extruder % 2 == 0 ? min(initial_extruder + 1, 63) : max(initial_extruder - (1-1), 0))]}\n M104 T{(initial_extruder % 2 == 0 ? min(initial_extruder + 1, 63) : max(initial_extruder - (1-1), 0))} S165\n {endif}\nM204 S100\nG28\nG0 Z266 F960.0\nG0 Y200.0 F6840.0\nG0 X200.0\n\n{if first_layer_print_min[0] >= 70 && first_layer_print_max[0] <= 330 && first_layer_print_min[1] >= 70 && first_layer_print_max[1] <= 330}\nM190 P0 R{first_layer_bed_temperature[initial_extruder]} ;only inner part of the bed\n{else}\nM190 R{first_layer_bed_temperature[initial_extruder]}\n{endif}\n\nG28\n{if 1==1}\n G0 X0\n G0 Z0.2 F960.0\n G0 Y0 F6840.0\n G0 X400\n G0 Y400\n G0 X0\n G0 Y0\n{endif}\n\nM83\n{if 1==1 && max(hot_plate_temp_initial_layer[initial_extruder], hot_plate_temp[initial_extruder]) >= 90}\nG0 Z0.06\nG92 Z0 ;reset z\n{endif}\n\n{if is_extruder_used[(initial_extruder % 2 == 0 ? min(initial_extruder + 0, 63) : max(initial_extruder - (1-0), 0))]}\n; preheat 0\nM104 T{(initial_extruder % 2 == 0 ? min(initial_extruder + 0, 63) : max(initial_extruder - (1-0), 0))} S{max(250, min(290, nozzle_temperature_initial_layer[(initial_extruder % 2 == 0 ? min(initial_extruder + 0, 63) : max(initial_extruder - (1-0), 0))] + 15))}\n {endif}\n{if is_extruder_used[(initial_extruder % 2 == 0 ? min(initial_extruder + 1, 63) : max(initial_extruder - (1-1), 0))]}\n; preheat 1\nM104 T{(initial_extruder % 2 == 0 ? min(initial_extruder + 1, 63) : max(initial_extruder - (1-1), 0))} S{max(250, min(290, nozzle_temperature_initial_layer[(initial_extruder % 2 == 0 ? min(initial_extruder + 1, 63) : max(initial_extruder - (1-1), 0))] + 15))}\n {endif}\n\n{if is_extruder_used[(initial_extruder % 2 == 0 ? min(initial_extruder + 0, 63) : max(initial_extruder - (1-0), 0))] and (initial_extruder % 2) != 0}\n; flush nozzle 0\nT{(initial_extruder % 2 == 0 ? min(initial_extruder + 0, 63) : max(initial_extruder - (1-0), 0))}\nM104 S{max(250, min(290, nozzle_temperature_initial_layer[(initial_extruder % 2 == 0 ? min(initial_extruder + 0, 63) : max(initial_extruder - (1-0), 0))] + 15))}; common flush temp\nG0 Z1.6 F960.0\nG0 X{( 0 % 2 == 0 ? 185.0 : 215.0 )} F6840.0\nG0 Y0 F6840.0\n\nM109 S{max(250, min(290, nozzle_temperature_initial_layer[(initial_extruder % 2 == 0 ? min(initial_extruder + 0, 63) : max(initial_extruder - (1-0), 0))] + 15))} C2 W1\nG1 E20 F80.0\nG92 E0\n\nM106 S{min(255, (fan_max_speed[(initial_extruder % 2 == 0 ? min(initial_extruder + 0, 63) : max(initial_extruder - (1-0), 0))] + 10) * 2.55)}\n\nM104 S{nozzle_temperature_initial_layer[(initial_extruder % 2 == 0 ? min(initial_extruder + 0, 63) : max(initial_extruder - (1-0), 0))] + 5}\n\nG1 E12.0 F200\nG92 E0\nG1 E8.0 Z4.6 F200\nG92 E0\n\nG0 Z5.6 F200\nM107\n\nG0 X{( 0 % 2 == 0 ? 140.0 : 260.0 )} F6840.0\nG0 Z0.3 F960.0\nM109 S{nozzle_temperature_initial_layer[(initial_extruder % 2 == 0 ? min(initial_extruder + 0, 63) : max(initial_extruder - (1-0), 0))]} C3 W1\nG1 E3 F200\nG92 E0\nG1 X{( 0 % 2 == 0 ? 0 : 400 )} E8.73079 F6840.0\nG92 E0\n\nG1 E-{retract_length_toolchange[0]} F200\nG92 E0\nG0 Y20 F6840.0\nM104 S{temperature_vitrification[(initial_extruder % 2 == 0 ? min(initial_extruder + 0, 63) : max(initial_extruder - (1-0), 0))]}\n {endif}\n\n{if is_extruder_used[(initial_extruder % 2 == 0 ? min(initial_extruder + 1, 63) : max(initial_extruder - (1-1), 0))] and (initial_extruder % 2) != 1}\n; flush nozzle 1\nT{(initial_extruder % 2 == 0 ? min(initial_extruder + 1, 63) : max(initial_extruder - (1-1), 0))}\nM104 S{max(250, min(290, nozzle_temperature_initial_layer[(initial_extruder % 2 == 0 ? min(initial_extruder + 1, 63) : max(initial_extruder - (1-1), 0))] + 15))}; common flush temp\nG0 Z1.6 F960.0\nG0 X{( 1 % 2 == 0 ? 185.0 : 215.0 )} F6840.0\nG0 Y0 F6840.0\n\nM109 S{max(250, min(290, nozzle_temperature_initial_layer[(initial_extruder % 2 == 0 ? min(initial_extruder + 1, 63) : max(initial_extruder - (1-1), 0))] + 15))} C2 W1\nG1 E20 F80.0\nG92 E0\n\nM106 S{min(255, (fan_max_speed[(initial_extruder % 2 == 0 ? min(initial_extruder + 1, 63) : max(initial_extruder - (1-1), 0))] + 10) * 2.55)}\n\nM104 S{nozzle_temperature_initial_layer[(initial_extruder % 2 == 0 ? min(initial_extruder + 1, 63) : max(initial_extruder - (1-1), 0))] + 5}\n\nG1 E12.0 F200\nG92 E0\nG1 E8.0 Z4.6 F200\nG92 E0\n\nG0 Z5.6 F200\nM107\n\nG0 X{( 1 % 2 == 0 ? 140.0 : 260.0 )} F6840.0\nG0 Z0.3 F960.0\nM109 S{nozzle_temperature_initial_layer[(initial_extruder % 2 == 0 ? min(initial_extruder + 1, 63) : max(initial_extruder - (1-1), 0))]} C3 W1\nG1 E3 F200\nG92 E0\nG1 X{( 1 % 2 == 0 ? 0 : 400 )} E8.73079 F6840.0\nG92 E0\n\nG1 E-{retract_length_toolchange[1]} F200\nG92 E0\nG0 Y20 F6840.0\nM104 S{temperature_vitrification[(initial_extruder % 2 == 0 ? min(initial_extruder + 1, 63) : max(initial_extruder - (1-1), 0))]}\n {endif}\n\n; flush initial nozzle\nT[initial_extruder]\nM104 S{max(250, min(290, nozzle_temperature_initial_layer[(initial_extruder % 2 == 0 ? min(initial_extruder + initial_extruder, 63) : max(initial_extruder - (1-initial_extruder), 0))] + 15))}; common flush temp\nG0 Z1.6 F960.0\nG0 X{( initial_extruder % 2 == 0 ? 185.0 : 215.0 )} F6840.0\nG0 Y0 F6840.0\n\nM109 S{max(250, min(290, nozzle_temperature_initial_layer[(initial_extruder % 2 == 0 ? min(initial_extruder + initial_extruder, 63) : max(initial_extruder - (1-initial_extruder), 0))] + 15))} C2 W1\nG1 E20 F80.0\nG92 E0\n\nM106 S{min(255, (fan_max_speed[(initial_extruder % 2 == 0 ? min(initial_extruder + initial_extruder, 63) : max(initial_extruder - (1-initial_extruder), 0))] + 10) * 2.55)}\n\nM104 S{nozzle_temperature_initial_layer[(initial_extruder % 2 == 0 ? min(initial_extruder + initial_extruder, 63) : max(initial_extruder - (1-initial_extruder), 0))] + 5}\n\nG1 E12.0 F200\nG92 E0\nG1 E8.0 Z4.6 F200\nG92 E0\n\nG0 Z5.6 F200\nM107\n\nG0 X{( initial_extruder % 2 == 0 ? 140.0 : 260.0 )} F6840.0\nG0 Z0.3 F960.0\nM109 S{nozzle_temperature_initial_layer[(initial_extruder % 2 == 0 ? min(initial_extruder + initial_extruder, 63) : max(initial_extruder - (1-initial_extruder), 0))]} C3 W1\nG1 E3 F200\nG92 E0\nG1 X{( initial_extruder % 2 == 0 ? 0 : 400 )} E8.73079 F6840.0\nG92 E0\n\nG1 E-{retraction_length[initial_extruder]} F200\nG92 E0\nG0 Y20 F6840.0\n\n; ready [plate_name]", + "machine_start_gcode": "; Model: Snapmaker Artisan ({nozzle_diameter[0]}/{nozzle_diameter[1]})\n; Update: 20240922\n; Maintained by https://github.com/macdylan/3dp-configs\n; Printer : [printer_preset]\n; Profile : [print_preset]\n; Plate : [plate_name]\n; --- initial_extruder: [initial_extruder]\n; --- has_wipe_tower: [has_wipe_tower]\n; --- total_toolchanges: [total_toolchanges]\n; --- T0: {is_extruder_used[(initial_extruder % 2 == 0 ? min(initial_extruder + 0, 63) : max(initial_extruder - 1, 0))]}\n; --- T1: {is_extruder_used[(initial_extruder % 2 == 0 ? min(initial_extruder + 1, 63) : max(initial_extruder - 0, 0))]}\n\nT[initial_extruder]\n\nM205 V[machine_max_jerk_x] ;Junction Deviation (mm)\n\nM140 S{first_layer_bed_temperature[initial_extruder]}\n\n; you can clean the nozzle\n{if is_extruder_used[(initial_extruder % 2 == 0 ? min(initial_extruder + 0, 63) : max(initial_extruder - (1-0), 0))]}\n M104 T{(initial_extruder % 2 == 0 ? min(initial_extruder + 0, 63) : max(initial_extruder - (1-0), 0))} S165\n {endif}\n{if is_extruder_used[(initial_extruder % 2 == 0 ? min(initial_extruder + 1, 63) : max(initial_extruder - (1-1), 0))]}\n M104 T{(initial_extruder % 2 == 0 ? min(initial_extruder + 1, 63) : max(initial_extruder - (1-1), 0))} S165\n {endif}\nM204 S100\nG28\nG0 Z266 F960.0\nG0 Y200.0 F6840.0\nG0 X200.0\n\n{if first_layer_print_min[0] >= 70 && first_layer_print_max[0] <= 330 && first_layer_print_min[1] >= 70 && first_layer_print_max[1] <= 330}\nM190 P0 R{first_layer_bed_temperature[initial_extruder]} ;only inner part of the bed\n{else}\nM190 R{first_layer_bed_temperature[initial_extruder]}\n{endif}\n\nG28\n{if 0==1} ; boundary check(for dual/quick swap kit), not recommanded if there are any clamps \n G0 X0\n G0 Z0.2 F960.0\n G0 Y0 F6840.0\n G0 X400\n G0 Y400\n G0 X0\n G0 Y0\n{endif}\n\nM83\n{if 1==1 && max(hot_plate_temp_initial_layer[initial_extruder], hot_plate_temp[initial_extruder]) >= 90}\nG0 Z0.06\nG92 Z0 ;reset z\n{endif}\n\n{if is_extruder_used[(initial_extruder % 2 == 0 ? min(initial_extruder + 0, 63) : max(initial_extruder - (1-0), 0))]}\n; preheat 0\nM104 T{(initial_extruder % 2 == 0 ? min(initial_extruder + 0, 63) : max(initial_extruder - (1-0), 0))} S{max(250, min(290, nozzle_temperature_initial_layer[(initial_extruder % 2 == 0 ? min(initial_extruder + 0, 63) : max(initial_extruder - (1-0), 0))] + 15))}\n {endif}\n{if is_extruder_used[(initial_extruder % 2 == 0 ? min(initial_extruder + 1, 63) : max(initial_extruder - (1-1), 0))]}\n; preheat 1\nM104 T{(initial_extruder % 2 == 0 ? min(initial_extruder + 1, 63) : max(initial_extruder - (1-1), 0))} S{max(250, min(290, nozzle_temperature_initial_layer[(initial_extruder % 2 == 0 ? min(initial_extruder + 1, 63) : max(initial_extruder - (1-1), 0))] + 15))}\n {endif}\n\n {if 1==1}\n{if is_extruder_used[(initial_extruder % 2 == 0 ? min(initial_extruder + 0, 63) : max(initial_extruder - (1-0), 0))] and (initial_extruder % 2) != 0}\n; flush nozzle 0\nT{(initial_extruder % 2 == 0 ? min(initial_extruder + 0, 63) : max(initial_extruder - (1-0), 0))}\nM104 S{max(250, min(290, nozzle_temperature_initial_layer[(initial_extruder % 2 == 0 ? min(initial_extruder + 0, 63) : max(initial_extruder - (1-0), 0))] + 15))}; common flush temp\nG0 Z1.6 F960.0\nG0 X{( 0 % 2 == 0 ? 185.0 : 215.0 )} F6840.0\nG0 Y0 F6840.0\n\nM109 S{max(250, min(290, nozzle_temperature_initial_layer[(initial_extruder % 2 == 0 ? min(initial_extruder + 0, 63) : max(initial_extruder - (1-0), 0))] + 15))} C2 W1\nG1 E20 F80.0\nG92 E0\n\nM106 S{min(255, (fan_max_speed[(initial_extruder % 2 == 0 ? min(initial_extruder + 0, 63) : max(initial_extruder - (1-0), 0))] + 10) * 2.55)}\n\nM104 S{nozzle_temperature_initial_layer[(initial_extruder % 2 == 0 ? min(initial_extruder + 0, 63) : max(initial_extruder - (1-0), 0))] + 5}\n\nG1 E12.0 F200\nG92 E0\nG1 E8.0 Z4.6 F200\nG92 E0\n\nG0 Z5.6 F200\nM107\n\nG0 X{( 0 % 2 == 0 ? 140.0 : 260.0 )} F6840.0\nG0 Z0.3 F960.0\nM109 S{nozzle_temperature_initial_layer[(initial_extruder % 2 == 0 ? min(initial_extruder + 0, 63) : max(initial_extruder - (1-0), 0))]} C3 W1\nG1 E3 F200\nG92 E0\nG1 X{( 0 % 2 == 0 ? 0 : 400 )} E8.73079 F6840.0\nG92 E0\n\nG1 E-{retract_length_toolchange[0]} F200\nG92 E0\nG0 Y20 F6840.0\n\nM104 S{temperature_vitrification[(initial_extruder % 2 == 0 ? min(initial_extruder + 0, 63) : max(initial_extruder - (1-0), 0))]}\n {endif}\n\n{if is_extruder_used[(initial_extruder % 2 == 0 ? min(initial_extruder + 1, 63) : max(initial_extruder - (1-1), 0))] and (initial_extruder % 2) != 1}\n; flush nozzle 1\nT{(initial_extruder % 2 == 0 ? min(initial_extruder + 1, 63) : max(initial_extruder - (1-1), 0))}\nM104 S{max(250, min(290, nozzle_temperature_initial_layer[(initial_extruder % 2 == 0 ? min(initial_extruder + 1, 63) : max(initial_extruder - (1-1), 0))] + 15))}; common flush temp\nG0 Z1.6 F960.0\nG0 X{( 1 % 2 == 0 ? 185.0 : 215.0 )} F6840.0\nG0 Y0 F6840.0\n\nM109 S{max(250, min(290, nozzle_temperature_initial_layer[(initial_extruder % 2 == 0 ? min(initial_extruder + 1, 63) : max(initial_extruder - (1-1), 0))] + 15))} C2 W1\nG1 E20 F80.0\nG92 E0\n\nM106 S{min(255, (fan_max_speed[(initial_extruder % 2 == 0 ? min(initial_extruder + 1, 63) : max(initial_extruder - (1-1), 0))] + 10) * 2.55)}\n\nM104 S{nozzle_temperature_initial_layer[(initial_extruder % 2 == 0 ? min(initial_extruder + 1, 63) : max(initial_extruder - (1-1), 0))] + 5}\n\nG1 E12.0 F200\nG92 E0\nG1 E8.0 Z4.6 F200\nG92 E0\n\nG0 Z5.6 F200\nM107\n\nG0 X{( 1 % 2 == 0 ? 140.0 : 260.0 )} F6840.0\nG0 Z0.3 F960.0\nM109 S{nozzle_temperature_initial_layer[(initial_extruder % 2 == 0 ? min(initial_extruder + 1, 63) : max(initial_extruder - (1-1), 0))]} C3 W1\nG1 E3 F200\nG92 E0\nG1 X{( 1 % 2 == 0 ? 0 : 400 )} E8.73079 F6840.0\nG92 E0\n\nG1 E-{retract_length_toolchange[1]} F200\nG92 E0\nG0 Y20 F6840.0\n\nM104 S{temperature_vitrification[(initial_extruder % 2 == 0 ? min(initial_extruder + 1, 63) : max(initial_extruder - (1-1), 0))]}\n {endif}\n\n {endif}\n; flush initial nozzle\nT[initial_extruder]\nM104 S{max(250, min(290, nozzle_temperature_initial_layer[(initial_extruder % 2 == 0 ? min(initial_extruder + initial_extruder, 63) : max(initial_extruder - (1-initial_extruder), 0))] + 15))}; common flush temp\nG0 Z1.6 F960.0\nG0 X{( initial_extruder % 2 == 0 ? 185.0 : 215.0 )} F6840.0\nG0 Y0 F6840.0\n\nM109 S{max(250, min(290, nozzle_temperature_initial_layer[(initial_extruder % 2 == 0 ? min(initial_extruder + initial_extruder, 63) : max(initial_extruder - (1-initial_extruder), 0))] + 15))} C2 W1\nG1 E20 F80.0\nG92 E0\n\nM106 S{min(255, (fan_max_speed[(initial_extruder % 2 == 0 ? min(initial_extruder + initial_extruder, 63) : max(initial_extruder - (1-initial_extruder), 0))] + 10) * 2.55)}\n\nM104 S{nozzle_temperature_initial_layer[(initial_extruder % 2 == 0 ? min(initial_extruder + initial_extruder, 63) : max(initial_extruder - (1-initial_extruder), 0))] + 5}\n\nG1 E12.0 F200\nG92 E0\nG1 E8.0 Z4.6 F200\nG92 E0\n\nG0 Z5.6 F200\nM107\n\nG0 X{( initial_extruder % 2 == 0 ? 140.0 : 260.0 )} F6840.0\nG0 Z0.3 F960.0\nM109 S{nozzle_temperature_initial_layer[(initial_extruder % 2 == 0 ? min(initial_extruder + initial_extruder, 63) : max(initial_extruder - (1-initial_extruder), 0))]} C3 W1\nG1 E3 F200\nG92 E0\nG1 X{( initial_extruder % 2 == 0 ? 0 : 400 )} E8.73079 F6840.0\nG92 E0\n\nG1 E-{retraction_length[initial_extruder]} F200\nG92 E0\nG0 Y20 F6840.0\n\n; ready [plate_name]", "machine_end_gcode": "G92 E0\n\nG0 Z{max_layer_z + 2.0} F600\n; retract the filament to make it easier to replace\nG0 E-10 F200\nG28\n\n{if is_extruder_used[(initial_extruder % 2 == 0 ? min(initial_extruder + 0, 63) : max(initial_extruder - (1-0), 0))]}\nM104 T{(initial_extruder % 2 == 0 ? min(initial_extruder + 0, 63) : max(initial_extruder - (1-0), 0))} S0\n {endif}\n{if is_extruder_used[(initial_extruder % 2 == 0 ? min(initial_extruder + 1, 63) : max(initial_extruder - (1-1), 0))]}\nM104 T{(initial_extruder % 2 == 0 ? min(initial_extruder + 1, 63) : max(initial_extruder - (1-1), 0))} S0\n {endif}\nM140 S0\nM107\nM220 S100\nM84\n\n;\n; DON'T REMOVE these lines if you're using the smfix (https://github.com/macdylan/SMFix)\n; min_x = [first_layer_print_min_0]\n; min_y = [first_layer_print_min_1]\n; max_x = [first_layer_print_max_0]\n; max_y = [first_layer_print_max_1]\n; max_z = [max_layer_z]\n; total_layer_number = [layer_num]\n;", - "change_filament_gcode": ";***** Update: 20230923\n{if current_extruder != next_extruder}\n; Change T[current_extruder] -> T[next_extruder] (layer [layer_num]\n; layer\nT{next_extruder}\n\nM107 P[current_extruder] ;fan off T[current_extruder]\nM104 T[current_extruder] S{temperature_vitrification[current_extruder]} ;standby T[current_extruder]\n\n{if layer_num == 1 &&\n ((filament_type[current_extruder] == \"PLA\" || filament_type[current_extruder] == \"TPU\")\n || (filament_type[next_extruder] == \"PLA\" || filament_type[next_extruder] == \"TPU\"))\n}\n ; set bed temp: {filament_type[current_extruder]}({bed_temperature[current_extruder]}) -> {filament_type[next_extruder]}({bed_temperature[next_extruder]})\n M140 S{min(bed_temperature[current_extruder], bed_temperature[next_extruder])}\n{endif}\n\nM109 T[next_extruder] S{if layer_num < 1}[nozzle_temperature_initial_layer]{else}[nozzle_temperature]{endif} C3 W1 ;wait T[next_extruder]\n{if layer_num == 1}\n G1 E{retraction_length[next_extruder]} F200;deretract\n{endif}\n\n{if layer_z > first_layer_height && layer_num >= close_fan_the_first_x_layers[next_extruder]}\n; M106 P[next_extruder] S{fan_min_speed[next_extruder] * 255.0 / 100.0} ;restore fan speed for T[next_extruder]\n{endif}\n; End Toolchange\n{endif}", + "change_filament_gcode": ";***** Update: 20240823\n{if current_extruder != next_extruder}\n; Change T[current_extruder] -> T[next_extruder] (layer [layer_num]\n; layer\nT{next_extruder}\n\n{if layer_num == 1 &&\n ((filament_type[current_extruder] == \"PLA\" || filament_type[current_extruder] == \"TPU\")\n || (filament_type[next_extruder] == \"PLA\" || filament_type[next_extruder] == \"TPU\"))\n}\n ; set bed temp: {filament_type[current_extruder]}({bed_temperature[current_extruder]}) -> {filament_type[next_extruder]}({bed_temperature[next_extruder]})\n M140 S{min(bed_temperature[current_extruder], bed_temperature[next_extruder])}\n{endif}\n\nM109 T[next_extruder] S{if layer_num < 1}[nozzle_temperature_initial_layer]{else}[nozzle_temperature]{endif} C3 W1 ;wait T[next_extruder]\n{if layer_num == 1}\n G1 E{retraction_length[next_extruder]} F200;deretract\n{endif}\n\n{if layer_z > first_layer_height && layer_num >= close_fan_the_first_x_layers[next_extruder]}\n; M106 P[next_extruder] S{fan_min_speed[next_extruder] * 255.0 / 100.0} ;restore fan speed for T[next_extruder]\n{endif}\n; End Toolchange\n{endif}", "before_layer_change_gcode": "; layer_num: [layer_num]\nG92 E0" } \ No newline at end of file diff --git a/resources/profiles/Snapmaker/machine/fdm_common.json b/resources/profiles/Snapmaker/machine/fdm_common.json index 9dde4953b3e..6066188b5c1 100644 --- a/resources/profiles/Snapmaker/machine/fdm_common.json +++ b/resources/profiles/Snapmaker/machine/fdm_common.json @@ -11,7 +11,7 @@ "auxiliary_fan": "0", "remaining_times": "1", "single_extruder_multi_material": "0", - "purge_in_prime_tower": "1", + "purge_in_prime_tower": "0", "enable_filament_ramming": "0", "nozzle_volume": "0", "cooling_tube_retraction": "0", diff --git a/resources/profiles/Snapmaker/machine/fdm_idex.json b/resources/profiles/Snapmaker/machine/fdm_idex.json index 28176df2d5d..62000328f79 100644 --- a/resources/profiles/Snapmaker/machine/fdm_idex.json +++ b/resources/profiles/Snapmaker/machine/fdm_idex.json @@ -100,8 +100,8 @@ "Snapmaker J1 PLA", "Snapmaker J1 PETG" ], - "machine_start_gcode": "; Model: Snapmaker J1 ({nozzle_diameter[0]}/{nozzle_diameter[1]})\n; Update: 20240428\n; Maintained by https://github.com/macdylan/3dp-configs\n; Printer : [printer_preset]\n; Profile : [print_preset]\n; Plate : [plate_name]\n; --- initial_extruder: [initial_extruder]\n; --- has_wipe_tower: [has_wipe_tower]\n; --- total_toolchanges: [total_toolchanges]\n; --- T0: {is_extruder_used[(initial_extruder % 2 == 0 ? min(initial_extruder + 0, 63) : max(initial_extruder - 1, 0))]}\n; --- T1: {is_extruder_used[(initial_extruder % 2 == 0 ? min(initial_extruder + 1, 63) : max(initial_extruder - 0, 0))]}\n\nT[initial_extruder]\n\nM205 V[machine_max_jerk_x] ;Junction Deviation (mm)\n\n{if plate_name =~/.*IDEXDupl.*/ || plate_name =~/.*IDEXCopy.*/ }\n M605 S2 X162 R0 ;IDEX Duplication\n{elsif plate_name =~/.*IDEXMirr.*/}\n M605 S3 ;IDEX Mirror\n{elsif plate_name =~/.*IDEXBack.*/}\n M605 S4 ;IDEX Backup\n{endif}\n\nM140 S{first_layer_bed_temperature[initial_extruder]}\n\n; you can clean the nozzle\n{if is_extruder_used[(initial_extruder % 2 == 0 ? min(initial_extruder + 0, 63) : max(initial_extruder - (1-0), 0))]}\n M104 T{(initial_extruder % 2 == 0 ? min(initial_extruder + 0, 63) : max(initial_extruder - (1-0), 0))} S165\n {endif}\n{if is_extruder_used[(initial_extruder % 2 == 0 ? min(initial_extruder + 1, 63) : max(initial_extruder - (1-1), 0))]}\n M104 T{(initial_extruder % 2 == 0 ? min(initial_extruder + 1, 63) : max(initial_extruder - (1-1), 0))} S165\n {endif}\nM204 S100\nG28\n\nG0 Z100.0\n{if is_extruder_used[(initial_extruder % 2 == 0 ? min(initial_extruder + 0, 63) : max(initial_extruder - (1-0), 0))]}\n T{(initial_extruder % 2 == 0 ? min(initial_extruder + 0, 63) : max(initial_extruder - (1-0), 0))}\n G0 X{if 0 == 0}80{else}240{endif} Y0 F6840.0\n {endif}\n{if is_extruder_used[(initial_extruder % 2 == 0 ? min(initial_extruder + 1, 63) : max(initial_extruder - (1-1), 0))]}\n T{(initial_extruder % 2 == 0 ? min(initial_extruder + 1, 63) : max(initial_extruder - (1-1), 0))}\n G0 X{if 1 == 0}80{else}240{endif} Y0 F6840.0\n {endif}\n\nM190 R{first_layer_bed_temperature[initial_extruder]}\n\n{if 1==1}; LED\n M355 S1 P64\n G4 P100\n M355 S1 P128\n G4 P100\n M355 S1 P64\n G4 P200\n M355 S1 P255\n G4 P100\n{endif}\nG28 X Y\n\nM83\n{if 1==1 && max(hot_plate_temp_initial_layer[initial_extruder], hot_plate_temp[initial_extruder]) >= 90}\nG0 Z0.06\nG92 Z0 ;reset z\n{endif}\n\n{if is_extruder_used[(initial_extruder % 2 == 0 ? min(initial_extruder + 0, 63) : max(initial_extruder - (1-0), 0))]}\n; preheat 0\nM104 T{(initial_extruder % 2 == 0 ? min(initial_extruder + 0, 63) : max(initial_extruder - (1-0), 0))} S{max(250, min(290, nozzle_temperature_initial_layer[(initial_extruder % 2 == 0 ? min(initial_extruder + 0, 63) : max(initial_extruder - (1-0), 0))] + 15))}\n {endif}\n{if is_extruder_used[(initial_extruder % 2 == 0 ? min(initial_extruder + 1, 63) : max(initial_extruder - (1-1), 0))]}\n; preheat 1\nM104 T{(initial_extruder % 2 == 0 ? min(initial_extruder + 1, 63) : max(initial_extruder - (1-1), 0))} S{max(250, min(290, nozzle_temperature_initial_layer[(initial_extruder % 2 == 0 ? min(initial_extruder + 1, 63) : max(initial_extruder - (1-1), 0))] + 15))}\n {endif}\n\n{if is_extruder_used[(initial_extruder % 2 == 0 ? min(initial_extruder + 0, 63) : max(initial_extruder - (1-0), 0))] and (initial_extruder % 2) != 0}\n; flush nozzle 0\nT{(initial_extruder % 2 == 0 ? min(initial_extruder + 0, 63) : max(initial_extruder - (1-0), 0))}\nM104 S{max(250, min(290, nozzle_temperature_initial_layer[(initial_extruder % 2 == 0 ? min(initial_extruder + 0, 63) : max(initial_extruder - (1-0), 0))] + 15))}; common flush temp\nG0 Z1.6 F240.0\nG0 X{( 0 % 2 == 0 ? 147.0 : 177.0 )} F6840.0\nG0 Y0 F6840.0\n\nM109 S{max(250, min(290, nozzle_temperature_initial_layer[(initial_extruder % 2 == 0 ? min(initial_extruder + 0, 63) : max(initial_extruder - (1-0), 0))] + 15))} C2 W1\nG1 E20 F80.0\nG92 E0\n\nM106 S{min(255, (fan_max_speed[(initial_extruder % 2 == 0 ? min(initial_extruder + 0, 63) : max(initial_extruder - (1-0), 0))] + 10) * 2.55)}\n\nM104 S{nozzle_temperature_initial_layer[(initial_extruder % 2 == 0 ? min(initial_extruder + 0, 63) : max(initial_extruder - (1-0), 0))] + 5}\n\nG1 E12.0 F200\nG92 E0\nG1 E8.0 Z4.6 F200\nG92 E0\n\nG0 Z5.6 F200\nM107\n\nG0 X{( 0 % 2 == 0 ? 137.0 : 187.0 )} F6840.0\nG0 Z0.3 F240.0\nM109 S{nozzle_temperature_initial_layer[(initial_extruder % 2 == 0 ? min(initial_extruder + 0, 63) : max(initial_extruder - (1-0), 0))]} C3 W1\nG1 E3 F200\nG92 E0\nG1 X{( 0 % 2 == 0 ? 0 : 324 )} E8.5437 F6840.0\nG92 E0\n\nG1 E-{retract_length_toolchange[0]} F200\nG92 E0\nG0 Y20 F6840.0\nM104 S{temperature_vitrification[(initial_extruder % 2 == 0 ? min(initial_extruder + 0, 63) : max(initial_extruder - (1-0), 0))]}\n {endif}\n\n{if is_extruder_used[(initial_extruder % 2 == 0 ? min(initial_extruder + 1, 63) : max(initial_extruder - (1-1), 0))] and (initial_extruder % 2) != 1}\n; flush nozzle 1\nT{(initial_extruder % 2 == 0 ? min(initial_extruder + 1, 63) : max(initial_extruder - (1-1), 0))}\nM104 S{max(250, min(290, nozzle_temperature_initial_layer[(initial_extruder % 2 == 0 ? min(initial_extruder + 1, 63) : max(initial_extruder - (1-1), 0))] + 15))}; common flush temp\nG0 Z1.6 F240.0\nG0 X{( 1 % 2 == 0 ? 147.0 : 177.0 )} F6840.0\nG0 Y0 F6840.0\n\nM109 S{max(250, min(290, nozzle_temperature_initial_layer[(initial_extruder % 2 == 0 ? min(initial_extruder + 1, 63) : max(initial_extruder - (1-1), 0))] + 15))} C2 W1\nG1 E20 F80.0\nG92 E0\n\nM106 S{min(255, (fan_max_speed[(initial_extruder % 2 == 0 ? min(initial_extruder + 1, 63) : max(initial_extruder - (1-1), 0))] + 10) * 2.55)}\n\nM104 S{nozzle_temperature_initial_layer[(initial_extruder % 2 == 0 ? min(initial_extruder + 1, 63) : max(initial_extruder - (1-1), 0))] + 5}\n\nG1 E12.0 F200\nG92 E0\nG1 E8.0 Z4.6 F200\nG92 E0\n\nG0 Z5.6 F200\nM107\n\nG0 X{( 1 % 2 == 0 ? 137.0 : 187.0 )} F6840.0\nG0 Z0.3 F240.0\nM109 S{nozzle_temperature_initial_layer[(initial_extruder % 2 == 0 ? min(initial_extruder + 1, 63) : max(initial_extruder - (1-1), 0))]} C3 W1\nG1 E3 F200\nG92 E0\nG1 X{( 1 % 2 == 0 ? 0 : 324 )} E8.5437 F6840.0\nG92 E0\n\nG1 E-{retract_length_toolchange[1]} F200\nG92 E0\nG0 Y20 F6840.0\nM104 S{temperature_vitrification[(initial_extruder % 2 == 0 ? min(initial_extruder + 1, 63) : max(initial_extruder - (1-1), 0))]}\n {endif}\n\n; flush initial nozzle\nT[initial_extruder]\nM104 S{max(250, min(290, nozzle_temperature_initial_layer[(initial_extruder % 2 == 0 ? min(initial_extruder + initial_extruder, 63) : max(initial_extruder - (1-initial_extruder), 0))] + 15))}; common flush temp\nG0 Z1.6 F240.0\nG0 X{( initial_extruder % 2 == 0 ? 147.0 : 177.0 )} F6840.0\nG0 Y0 F6840.0\n\nM109 S{max(250, min(290, nozzle_temperature_initial_layer[(initial_extruder % 2 == 0 ? min(initial_extruder + initial_extruder, 63) : max(initial_extruder - (1-initial_extruder), 0))] + 15))} C2 W1\nG1 E20 F80.0\nG92 E0\n\nM106 S{min(255, (fan_max_speed[(initial_extruder % 2 == 0 ? min(initial_extruder + initial_extruder, 63) : max(initial_extruder - (1-initial_extruder), 0))] + 10) * 2.55)}\n\nM104 S{nozzle_temperature_initial_layer[(initial_extruder % 2 == 0 ? min(initial_extruder + initial_extruder, 63) : max(initial_extruder - (1-initial_extruder), 0))] + 5}\n\nG1 E12.0 F200\nG92 E0\nG1 E8.0 Z4.6 F200\nG92 E0\n\nG0 Z5.6 F200\nM107\n\nG0 X{( initial_extruder % 2 == 0 ? 137.0 : 187.0 )} F6840.0\nG0 Z0.3 F240.0\nM109 S{nozzle_temperature_initial_layer[(initial_extruder % 2 == 0 ? min(initial_extruder + initial_extruder, 63) : max(initial_extruder - (1-initial_extruder), 0))]} C3 W1\nG1 E3 F200\nG92 E0\nG1 X{( initial_extruder % 2 == 0 ? 0 : 324 )} E8.5437 F6840.0\nG92 E0\n\nG1 E-{retraction_length[initial_extruder]} F200\nG92 E0\nG0 Y20 F6840.0\n\n; ready [plate_name]", + "machine_start_gcode": "; Model: Snapmaker J1 ({nozzle_diameter[0]}/{nozzle_diameter[1]})\n; Update: 20240922\n; Maintained by https://github.com/macdylan/3dp-configs\n; Printer : [printer_preset]\n; Profile : [print_preset]\n; Plate : [plate_name]\n; --- initial_extruder: [initial_extruder]\n; --- has_wipe_tower: [has_wipe_tower]\n; --- total_toolchanges: [total_toolchanges]\n; --- T0: {is_extruder_used[(initial_extruder % 2 == 0 ? min(initial_extruder + 0, 63) : max(initial_extruder - 1, 0))]}\n; --- T1: {is_extruder_used[(initial_extruder % 2 == 0 ? min(initial_extruder + 1, 63) : max(initial_extruder - 0, 0))]}\n\nT[initial_extruder]\n\nM205 V[machine_max_jerk_x] ;Junction Deviation (mm)\n\n{if plate_name =~/.*IDEXDupl.*/ || plate_name =~/.*IDEXCopy.*/ }\n M605 S2 X162 R0 ;IDEX Duplication\n{elsif plate_name =~/.*IDEXMirr.*/}\n M605 S3 ;IDEX Mirror\n{elsif plate_name =~/.*IDEXBack.*/}\n M605 S4 ;IDEX Backup\n{endif}\n\nM140 S{first_layer_bed_temperature[initial_extruder]}\n\n; you can clean the nozzle\n{if is_extruder_used[(initial_extruder % 2 == 0 ? min(initial_extruder + 0, 63) : max(initial_extruder - (1-0), 0))]}\n M104 T{(initial_extruder % 2 == 0 ? min(initial_extruder + 0, 63) : max(initial_extruder - (1-0), 0))} S165\n {endif}\n{if is_extruder_used[(initial_extruder % 2 == 0 ? min(initial_extruder + 1, 63) : max(initial_extruder - (1-1), 0))]}\n M104 T{(initial_extruder % 2 == 0 ? min(initial_extruder + 1, 63) : max(initial_extruder - (1-1), 0))} S165\n {endif}\nM204 S100\nG28\n\nG0 Z100.0\n{if is_extruder_used[(initial_extruder % 2 == 0 ? min(initial_extruder + 0, 63) : max(initial_extruder - (1-0), 0))]}\n T{(initial_extruder % 2 == 0 ? min(initial_extruder + 0, 63) : max(initial_extruder - (1-0), 0))}\n G0 X{if 0 == 0}80{else}240{endif} Y0 F6840.0\n {endif}\n{if is_extruder_used[(initial_extruder % 2 == 0 ? min(initial_extruder + 1, 63) : max(initial_extruder - (1-1), 0))]}\n T{(initial_extruder % 2 == 0 ? min(initial_extruder + 1, 63) : max(initial_extruder - (1-1), 0))}\n G0 X{if 1 == 0}80{else}240{endif} Y0 F6840.0\n {endif}\n\nM190 R{first_layer_bed_temperature[initial_extruder]}\n\n{if 1==1}; LED\n M355 S1 P64\n G4 P100\n M355 S1 P128\n G4 P100\n M355 S1 P64\n G4 P200\n M355 S1 P255\n G4 P100\n{endif}\nG28 X Y\n\nM83\n{if 1==1 && max(hot_plate_temp_initial_layer[initial_extruder], hot_plate_temp[initial_extruder]) >= 90}\nG0 Z0.06\nG92 Z0 ;reset z\n{endif}\n\n{if is_extruder_used[(initial_extruder % 2 == 0 ? min(initial_extruder + 0, 63) : max(initial_extruder - (1-0), 0))]}\n; preheat 0\nM104 T{(initial_extruder % 2 == 0 ? min(initial_extruder + 0, 63) : max(initial_extruder - (1-0), 0))} S{max(250, min(290, nozzle_temperature_initial_layer[(initial_extruder % 2 == 0 ? min(initial_extruder + 0, 63) : max(initial_extruder - (1-0), 0))] + 15))}\n {endif}\n{if is_extruder_used[(initial_extruder % 2 == 0 ? min(initial_extruder + 1, 63) : max(initial_extruder - (1-1), 0))]}\n; preheat 1\nM104 T{(initial_extruder % 2 == 0 ? min(initial_extruder + 1, 63) : max(initial_extruder - (1-1), 0))} S{max(250, min(290, nozzle_temperature_initial_layer[(initial_extruder % 2 == 0 ? min(initial_extruder + 1, 63) : max(initial_extruder - (1-1), 0))] + 15))}\n {endif}\n\n {if 1==1}\n{if is_extruder_used[(initial_extruder % 2 == 0 ? min(initial_extruder + 0, 63) : max(initial_extruder - (1-0), 0))] and (initial_extruder % 2) != 0}\n; flush nozzle 0\nT{(initial_extruder % 2 == 0 ? min(initial_extruder + 0, 63) : max(initial_extruder - (1-0), 0))}\nM104 S{max(250, min(290, nozzle_temperature_initial_layer[(initial_extruder % 2 == 0 ? min(initial_extruder + 0, 63) : max(initial_extruder - (1-0), 0))] + 15))}; common flush temp\nG0 Z1.6 F240.0\nG0 X{( 0 % 2 == 0 ? 147.0 : 177.0 )} F6840.0\nG0 Y0 F6840.0\n\nM109 S{max(250, min(290, nozzle_temperature_initial_layer[(initial_extruder % 2 == 0 ? min(initial_extruder + 0, 63) : max(initial_extruder - (1-0), 0))] + 15))} C2 W1\nG1 E20 F80.0\nG92 E0\n\nM106 S{min(255, (fan_max_speed[(initial_extruder % 2 == 0 ? min(initial_extruder + 0, 63) : max(initial_extruder - (1-0), 0))] + 10) * 2.55)}\n\nM104 S{nozzle_temperature_initial_layer[(initial_extruder % 2 == 0 ? min(initial_extruder + 0, 63) : max(initial_extruder - (1-0), 0))] + 5}\n\nG1 E12.0 F200\nG92 E0\nG1 E8.0 Z4.6 F200\nG92 E0\n\nG0 Z5.6 F200\nM107\n\nG0 X{( 0 % 2 == 0 ? 137.0 : 187.0 )} F6840.0\nG0 Z0.3 F240.0\nM109 S{nozzle_temperature_initial_layer[(initial_extruder % 2 == 0 ? min(initial_extruder + 0, 63) : max(initial_extruder - (1-0), 0))]} C3 W1\nG1 E3 F200\nG92 E0\nG1 X{( 0 % 2 == 0 ? 0 : 324 )} E8.5437 F6840.0\nG92 E0\n\nG1 E-{retract_length_toolchange[0]} F200\nG92 E0\nG0 Y20 F6840.0\nG28 X F6840.0\n\nM104 S{temperature_vitrification[(initial_extruder % 2 == 0 ? min(initial_extruder + 0, 63) : max(initial_extruder - (1-0), 0))]}\n {endif}\n\n{if is_extruder_used[(initial_extruder % 2 == 0 ? min(initial_extruder + 1, 63) : max(initial_extruder - (1-1), 0))] and (initial_extruder % 2) != 1}\n; flush nozzle 1\nT{(initial_extruder % 2 == 0 ? min(initial_extruder + 1, 63) : max(initial_extruder - (1-1), 0))}\nM104 S{max(250, min(290, nozzle_temperature_initial_layer[(initial_extruder % 2 == 0 ? min(initial_extruder + 1, 63) : max(initial_extruder - (1-1), 0))] + 15))}; common flush temp\nG0 Z1.6 F240.0\nG0 X{( 1 % 2 == 0 ? 147.0 : 177.0 )} F6840.0\nG0 Y0 F6840.0\n\nM109 S{max(250, min(290, nozzle_temperature_initial_layer[(initial_extruder % 2 == 0 ? min(initial_extruder + 1, 63) : max(initial_extruder - (1-1), 0))] + 15))} C2 W1\nG1 E20 F80.0\nG92 E0\n\nM106 S{min(255, (fan_max_speed[(initial_extruder % 2 == 0 ? min(initial_extruder + 1, 63) : max(initial_extruder - (1-1), 0))] + 10) * 2.55)}\n\nM104 S{nozzle_temperature_initial_layer[(initial_extruder % 2 == 0 ? min(initial_extruder + 1, 63) : max(initial_extruder - (1-1), 0))] + 5}\n\nG1 E12.0 F200\nG92 E0\nG1 E8.0 Z4.6 F200\nG92 E0\n\nG0 Z5.6 F200\nM107\n\nG0 X{( 1 % 2 == 0 ? 137.0 : 187.0 )} F6840.0\nG0 Z0.3 F240.0\nM109 S{nozzle_temperature_initial_layer[(initial_extruder % 2 == 0 ? min(initial_extruder + 1, 63) : max(initial_extruder - (1-1), 0))]} C3 W1\nG1 E3 F200\nG92 E0\nG1 X{( 1 % 2 == 0 ? 0 : 324 )} E8.5437 F6840.0\nG92 E0\n\nG1 E-{retract_length_toolchange[1]} F200\nG92 E0\nG0 Y20 F6840.0\nG28 X F6840.0\n\nM104 S{temperature_vitrification[(initial_extruder % 2 == 0 ? min(initial_extruder + 1, 63) : max(initial_extruder - (1-1), 0))]}\n {endif}\n\n {endif}\n; flush initial nozzle\nT[initial_extruder]\nM104 S{max(250, min(290, nozzle_temperature_initial_layer[(initial_extruder % 2 == 0 ? min(initial_extruder + initial_extruder, 63) : max(initial_extruder - (1-initial_extruder), 0))] + 15))}; common flush temp\nG0 Z1.6 F240.0\nG0 X{( initial_extruder % 2 == 0 ? 147.0 : 177.0 )} F6840.0\nG0 Y0 F6840.0\n\nM109 S{max(250, min(290, nozzle_temperature_initial_layer[(initial_extruder % 2 == 0 ? min(initial_extruder + initial_extruder, 63) : max(initial_extruder - (1-initial_extruder), 0))] + 15))} C2 W1\nG1 E20 F80.0\nG92 E0\n\nM106 S{min(255, (fan_max_speed[(initial_extruder % 2 == 0 ? min(initial_extruder + initial_extruder, 63) : max(initial_extruder - (1-initial_extruder), 0))] + 10) * 2.55)}\n\nM104 S{nozzle_temperature_initial_layer[(initial_extruder % 2 == 0 ? min(initial_extruder + initial_extruder, 63) : max(initial_extruder - (1-initial_extruder), 0))] + 5}\n\nG1 E12.0 F200\nG92 E0\nG1 E8.0 Z4.6 F200\nG92 E0\n\nG0 Z5.6 F200\nM107\n\nG0 X{( initial_extruder % 2 == 0 ? 137.0 : 187.0 )} F6840.0\nG0 Z0.3 F240.0\nM109 S{nozzle_temperature_initial_layer[(initial_extruder % 2 == 0 ? min(initial_extruder + initial_extruder, 63) : max(initial_extruder - (1-initial_extruder), 0))]} C3 W1\nG1 E3 F200\nG92 E0\nG1 X{( initial_extruder % 2 == 0 ? 0 : 324 )} E8.5437 F6840.0\nG92 E0\n\nG1 E-{retraction_length[initial_extruder]} F200\nG92 E0\nG0 Y20 F6840.0\n\n; ready [plate_name]", "machine_end_gcode": "G92 E0\n\nG0 Z{max_layer_z + 2.0} F600\n; retract the filament to make it easier to replace\nG0 E-10 F200\nG28\n\n{if is_extruder_used[(initial_extruder % 2 == 0 ? min(initial_extruder + 0, 63) : max(initial_extruder - (1-0), 0))]}\nM104 T{(initial_extruder % 2 == 0 ? min(initial_extruder + 0, 63) : max(initial_extruder - (1-0), 0))} S0\n {endif}\n{if is_extruder_used[(initial_extruder % 2 == 0 ? min(initial_extruder + 1, 63) : max(initial_extruder - (1-1), 0))]}\nM104 T{(initial_extruder % 2 == 0 ? min(initial_extruder + 1, 63) : max(initial_extruder - (1-1), 0))} S0\n {endif}\nM140 S0\nM107\nM220 S100\nM84\n\n;\n; DON'T REMOVE these lines if you're using the smfix (https://github.com/macdylan/SMFix)\n; min_x = [first_layer_print_min_0]\n; min_y = [first_layer_print_min_1]\n; max_x = [first_layer_print_max_0]\n; max_y = [first_layer_print_max_1]\n; max_z = [max_layer_z]\n; total_layer_number = [layer_num]\n;", - "change_filament_gcode": ";***** Update: 20230730\n{if current_extruder != next_extruder}\n; Change T[current_extruder] -> T[next_extruder] (layer [layer_num] [toolchange_count]/[total_toolchanges])\n; layer [layer_num] at [layer_z]mm\nT[next_extruder]\n\nM107 P[current_extruder] ;fan off T[current_extruder]\nM104 T[current_extruder] S{temperature_vitrification[current_extruder]} ;standby T[current_extruder]\n\n{if layer_num == 1 &&\n ((filament_type[current_extruder] == \"PLA\" || filament_type[current_extruder] == \"TPU\")\n || (filament_type[next_extruder] == \"PLA\" || filament_type[next_extruder] == \"TPU\"))\n}\n; set bed temp: {filament_type[current_extruder]}({bed_temperature[current_extruder]}) -> {filament_type[next_extruder]}({bed_temperature[next_extruder]})\nM140 S{min(bed_temperature[current_extruder], bed_temperature[next_extruder])}\n{endif}\n\nM2000 S200 V[travel_speed] A[travel_acceleration] ;quick switch extruders, S:200 mode/V:speed/A:acceleration\nM109 T[next_extruder] S{if layer_num < 1}[nozzle_temperature_initial_layer]{else}[nozzle_temperature]{endif} C3 W1 ;wait T[next_extruder]\n{if layer_z > first_layer_height && layer_num >= close_fan_the_first_x_layers[next_extruder]}\n M106 P[next_extruder] S{fan_min_speed[next_extruder] * 255.0 / 100.0} ;restore fan speed for T[next_extruder]\n{endif}\n{endif}", + "change_filament_gcode": ";***** Update: 20240823\n{if current_extruder != next_extruder}\n; Change T[current_extruder] -> T[next_extruder] (layer [layer_num] [toolchange_count]/[total_toolchanges])\n; layer [layer_num] at [layer_z]mm\nT[next_extruder]\n\n M107 P[current_extruder] ;fan off T[current_extruder]\n\n{if layer_num == 1 &&\n ((filament_type[current_extruder] == \"PLA\" || filament_type[current_extruder] == \"TPU\")\n || (filament_type[next_extruder] == \"PLA\" || filament_type[next_extruder] == \"TPU\"))\n}\n; set bed temp: {filament_type[current_extruder]}({bed_temperature[current_extruder]}) -> {filament_type[next_extruder]}({bed_temperature[next_extruder]})\nM140 S{min(bed_temperature[current_extruder], bed_temperature[next_extruder])}\n{endif}\n\nM2000 S200 V[travel_speed] A[travel_acceleration] ;quick switch extruders, S:200 mode/V:speed/A:acceleration\nM109 T[next_extruder] S{if layer_num < 1}[nozzle_temperature_initial_layer]{else}[nozzle_temperature]{endif} C3 W1 ;wait T[next_extruder]\n{if layer_z > first_layer_height && layer_num >= close_fan_the_first_x_layers[next_extruder]}\n M106 P[next_extruder] S{fan_min_speed[next_extruder] * 255.0 / 100.0} ;restore fan speed for T[next_extruder]\n{endif}\n{endif}", "before_layer_change_gcode": "; layer_num: [layer_num]\nG92 E0" } \ No newline at end of file diff --git a/resources/profiles/Snapmaker/machine/fdm_linear2_dual.json b/resources/profiles/Snapmaker/machine/fdm_linear2_dual.json index fcd3e851540..7595508dc5f 100644 --- a/resources/profiles/Snapmaker/machine/fdm_linear2_dual.json +++ b/resources/profiles/Snapmaker/machine/fdm_linear2_dual.json @@ -21,5 +21,9 @@ "default_filament_profile": [ "Snapmaker PLA", "Snapmaker PETG" + ], + "extruder_offset": [ + "0x0", + "0x0" ] } \ No newline at end of file diff --git a/resources/profiles/Snapmaker/process/fdm_process_a400.json b/resources/profiles/Snapmaker/process/fdm_process_a400.json index 90c851f1163..8989333a913 100644 --- a/resources/profiles/Snapmaker/process/fdm_process_a400.json +++ b/resources/profiles/Snapmaker/process/fdm_process_a400.json @@ -8,7 +8,7 @@ "initial_layer_print_height": "0.2", "enable_arc_fitting": "1", "initial_layer_infill_speed": "75", - "outer_wall_speed": "145", + "outer_wall_speed": "100", "inner_wall_speed": "160", "sparse_infill_speed": "160", "internal_solid_infill_speed": "160", diff --git a/resources/profiles/Snapmaker/process/fdm_process_common.json b/resources/profiles/Snapmaker/process/fdm_process_common.json index f35f39ffb1d..e5b21d091e9 100644 --- a/resources/profiles/Snapmaker/process/fdm_process_common.json +++ b/resources/profiles/Snapmaker/process/fdm_process_common.json @@ -49,6 +49,7 @@ "reduce_crossing_wall": "1", "max_travel_detour_distance": "0", "wipe_tower_no_sparse_layers": "0", + "wipe_tower_max_purge_speed": "55", "draft_shield": "disabled", "independent_support_layer_height": "1", "interface_shells": "0", @@ -152,9 +153,15 @@ "brim_object_gap": "0", "enable_prime_tower": "1", "prime_tower_width": "25", - "prime_volume": "7.3", + "prime_volume": "55", "prime_tower_brim_width": "5", - "wipe_tower_cone_angle": "40", + "wipe_tower_cone_angle": "25", + "wipe_tower_extra_spacing": "150%", + "wipe_tower_rotation_angle": "90", + "ooze_prevention": "1", + "standby_temperature_delta": "-70", + "preheat_time": "80", + "preheat_steps": "1", "flush_into_infill": "0", "flush_into_support": "1", "flush_into_objects": "0", @@ -172,7 +179,6 @@ "post_process": "", "enforce_support_layers": "0", "exclude_object": "0", - "standby_temperature_delta": "-45", "timelapse_type": "0", "gap_fill_enabled": "0", "single_extruder_multi_material_priming": "0" diff --git a/resources/profiles/Snapmaker/process/fdm_process_idex.json b/resources/profiles/Snapmaker/process/fdm_process_idex.json index 7682a1e12db..26ceab17697 100644 --- a/resources/profiles/Snapmaker/process/fdm_process_idex.json +++ b/resources/profiles/Snapmaker/process/fdm_process_idex.json @@ -8,7 +8,7 @@ "initial_layer_print_height": "0.2", "enable_arc_fitting": "1", "initial_layer_infill_speed": "75", - "outer_wall_speed": "145", + "outer_wall_speed": "120", "inner_wall_speed": "250", "sparse_infill_speed": "250", "internal_solid_infill_speed": "250", @@ -36,6 +36,7 @@ "support_bottom_z_distance": "0", "support_interface_top_layers": "3", "support_interface_pattern": "rectilinear", + "preheat_time": "90", "filename_format": "{input_filename_base}_{layer_height}mm_{print_time}_J1.gcode", "small_perimeter_threshold": "0", "support_material_synchronize_layers": "1" diff --git a/src/OrcaSlicer.cpp b/src/OrcaSlicer.cpp index e93533166e2..dc140e26295 100644 --- a/src/OrcaSlicer.cpp +++ b/src/OrcaSlicer.cpp @@ -3575,10 +3575,16 @@ int CLI::run(int argc, char **argv) // this affects volumes: o->rotate(Geometry::deg2rad(m_config.opt_float(opt_key)), Y); } else if (opt_key == "scale") { + float ratio = m_config.opt_float(opt_key); + if (ratio <= 0.f) { + BOOST_LOG_TRIVIAL(error) << boost::format("Invalid params:invalid scale ratio %1%")%ratio; + record_exit_reson(outfile_dir, CLI_INVALID_PARAMS, 0, cli_errors[CLI_INVALID_PARAMS], sliced_info); + flush_and_exit(CLI_INVALID_PARAMS); + } for (auto &model : m_models) for (auto &o : model.objects) // this affects volumes: - o->scale(m_config.get_abs_value(opt_key, 1)); + o->scale(ratio); } else if (opt_key == "scale_to_fit") { const Vec3d &opt = m_config.opt(opt_key)->value; if (opt.x() <= 0 || opt.y() <= 0 || opt.z() <= 0) { diff --git a/src/libslic3r/CMakeLists.txt b/src/libslic3r/CMakeLists.txt index d3c08bacc67..2c362dba23e 100644 --- a/src/libslic3r/CMakeLists.txt +++ b/src/libslic3r/CMakeLists.txt @@ -307,29 +307,21 @@ set(lisbslic3r_sources SlicingAdaptive.hpp Support/SupportCommon.cpp Support/SupportCommon.hpp - Support/SupportDebug.cpp - Support/SupportDebug.hpp Support/SupportLayer.hpp - # Support/SupportMaterial.cpp - # Support/SupportMaterial.hpp - Support/SupportParameters.cpp + Support/SupportMaterial.cpp + Support/SupportMaterial.hpp Support/SupportParameters.hpp - Support/OrganicSupport.cpp - Support/OrganicSupport.hpp - Support/TreeSupport.cpp + Support/SupportSpotsGenerator.cpp + Support/SupportSpotsGenerator.hpp Support/TreeSupport.hpp - Support/TreeSupportCommon.cpp + Support/TreeSupport.cpp + Support/TreeSupport3D.cpp + Support/TreeSupport3D.hpp Support/TreeSupportCommon.hpp Support/TreeModelVolumes.cpp Support/TreeModelVolumes.hpp - SupportMaterial.cpp - SupportMaterial.hpp PrincipalComponents2D.cpp PrincipalComponents2D.hpp - SupportSpotsGenerator.cpp - SupportSpotsGenerator.hpp - TreeSupport.hpp - TreeSupport.cpp MinimumSpanningTree.hpp MinimumSpanningTree.cpp Surface.cpp diff --git a/src/libslic3r/CSGMesh/CSGMesh.hpp b/src/libslic3r/CSGMesh/CSGMesh.hpp index 177d3432965..ea16eae52ab 100644 --- a/src/libslic3r/CSGMesh/CSGMesh.hpp +++ b/src/libslic3r/CSGMesh/CSGMesh.hpp @@ -82,6 +82,40 @@ struct CSGPart { {} }; +//Prusa +// Check if there are only positive parts (Union) within the collection. +template bool is_all_positive(const Cont &csgmesh) +{ + bool is_all_pos = + std::all_of(csgmesh.begin(), + csgmesh.end(), + [](auto &part) { + return csg::get_operation(part) == csg::CSGType::Union; + }); + + return is_all_pos; +} + +//Prusa +// Merge all the positive parts of the collection into a single triangle mesh without performing +// any booleans. +template +indexed_triangle_set csgmesh_merge_positive_parts(const Cont &csgmesh) +{ + indexed_triangle_set m; + for (auto &csgpart : csgmesh) { + auto op = csg::get_operation(csgpart); + const indexed_triangle_set * pmesh = csg::get_mesh(csgpart); + if (pmesh && op == csg::CSGType::Union) { + indexed_triangle_set mcpy = *pmesh; + its_transform(mcpy, csg::get_transform(csgpart), true); + its_merge(m, mcpy); + } + } + + return m; +} + }} // namespace Slic3r::csg #endif // CSGMESH_HPP diff --git a/src/libslic3r/CSGMesh/PerformCSGMeshBooleans.hpp b/src/libslic3r/CSGMesh/PerformCSGMeshBooleans.hpp index a9a48b48c02..df4a6033827 100644 --- a/src/libslic3r/CSGMesh/PerformCSGMeshBooleans.hpp +++ b/src/libslic3r/CSGMesh/PerformCSGMeshBooleans.hpp @@ -257,7 +257,7 @@ void perform_csgmesh_booleans_mcut(MeshBoolean::mcut::McutMeshPtr& mcutm, template -std::tuple check_csgmesh_booleans(const Range &csgrange, Visitor &&vfn) +std::tuple check_csgmesh_booleans(const Range &csgrange, Visitor &&vfn) { using namespace detail_cgal; BooleanFailReason fail_reason = BooleanFailReason::OK; @@ -304,23 +304,23 @@ std::tuple check_csgmesh_booleans(const Range }; execution::for_each(ex_tbb, size_t(0), csgrange.size(), check_part); - //It ret = csgrange.end(); - //for (size_t i = 0; i < csgrange.size(); ++i) { - // if (!cgalmeshes[i]) { - // auto it = csgrange.begin(); - // std::advance(it, i); - // vfn(it); + It ret = csgrange.end(); + for (size_t i = 0; i < csgrange.size(); ++i) { + if (!cgalmeshes[i]) { + auto it = csgrange.begin(); + std::advance(it, i); + vfn(it); - // if (ret == csgrange.end()) - // ret = it; - // } - //} + if (ret == csgrange.end()) + ret = it; + } + } - return { fail_reason,fail_part_name }; + return { fail_reason,fail_part_name, ret}; } template -std::tuple check_csgmesh_booleans(const Range &csgrange, bool use_mcut=false) +std::tuple check_csgmesh_booleans(const Range &csgrange, bool use_mcut=false) { if(!use_mcut) return check_csgmesh_booleans(csgrange, [](auto &) {}); @@ -354,7 +354,7 @@ std::tuple check_csgmesh_booleans(const Range get_path_of_change_filament(const Print& print) gcode += gcodegen.writer().unlift(); // Make sure there is no z-hop (in most cases, there isn't). double current_z = gcodegen.writer().get_position().z(); + + if (z == -1.) // in case no specific z was provided, print at current_z pos z = current_z; @@ -741,7 +743,7 @@ static std::vector get_path_of_change_filament(const Print& print) || !needs_toolchange // this is just finishing the tower with no toolchange || is_ramming); - if (should_travel_to_tower) { + if (should_travel_to_tower || gcodegen.m_need_change_layer_lift_z) { // FIXME: It would be better if the wipe tower set the force_travel flag for all toolchanges, // then we could simplify the condition and make it more readable. gcode += gcodegen.retract(); @@ -767,10 +769,10 @@ static std::vector get_path_of_change_filament(const Print& print) toolchange_gcode_str = gcodegen.set_extruder(new_extruder_id, tcr.print_z); // TODO: toolchange_z vs print_z if (gcodegen.config().enable_prime_tower) { deretraction_str += gcodegen.writer().travel_to_z(z, "restore layer Z"); - Vec3d position{gcodegen.writer().get_position()}; - position.z() = z; - gcodegen.writer().set_position(position); - deretraction_str += gcodegen.unretract(); + Vec3d position{gcodegen.writer().get_position()}; + position.z() = z; + gcodegen.writer().set_position(position); + deretraction_str += gcodegen.unretract(); } } @@ -4516,12 +4518,11 @@ std::string GCode::change_layer(coordf_t print_z) comment << "move to next layer (" << m_layer_index << ")"; gcode += m_writer.travel_to_z(z, comment.str()); } - else { - //BBS: set m_need_change_layer_lift_z to be true so that z lift can be done in travel_to() function - m_need_change_layer_lift_z = true; - } + + m_need_change_layer_lift_z = true; m_nominal_z = z; + m_writer.get_position().z() = z; // forget last wiping path as wiping after raising Z is pointless // BBS. Dont forget wiping path to reduce stringing. @@ -6008,14 +6009,15 @@ std::string GCode::travel_to(const Point& point, ExtrusionRole role, std::string if (m_spiral_vase) { // No lazy z lift for spiral vase mode for (size_t i = 1; i < travel.size(); ++i) { - gcode += m_writer.travel_to_xy(this->point_to_gcode(travel.points[i]), comment + " travel_to_xy"); + gcode += m_writer.travel_to_xy(this->point_to_gcode(travel.points[i]), comment); } } else { if (travel.size() == 2) { // No extra movements emitted by avoid_crossing_perimeters, simply move to the end point with z change const auto& dest2d = this->point_to_gcode(travel.points.back()); Vec3d dest3d(dest2d(0), dest2d(1), z == DBL_MAX ? m_nominal_z : z); - gcode += m_writer.travel_to_xyz(dest3d, comment + " travel_to_xyz"); + gcode += m_writer.travel_to_xyz(dest3d, comment, m_need_change_layer_lift_z); + m_need_change_layer_lift_z = false; } else { // Extra movements emitted by avoid_crossing_perimeters, lift the z to normal height at the beginning, then apply the z // ratio at the last point @@ -6024,21 +6026,23 @@ std::string GCode::travel_to(const Point& point, ExtrusionRole role, std::string // Lift to normal z at beginning Vec2d dest2d = this->point_to_gcode(travel.points[i]); Vec3d dest3d(dest2d(0), dest2d(1), m_nominal_z); - gcode += m_writer.travel_to_xyz(dest3d, comment + " travel_to_xyz"); + gcode += m_writer.travel_to_xyz(dest3d, comment, m_need_change_layer_lift_z); + m_need_change_layer_lift_z = false; } else if (z != DBL_MAX && i == travel.size() - 1) { // Apply z_ratio for the very last point Vec2d dest2d = this->point_to_gcode(travel.points[i]); Vec3d dest3d(dest2d(0), dest2d(1), z); - gcode += m_writer.travel_to_xyz(dest3d, comment + " travel_to_xyz"); + gcode += m_writer.travel_to_xyz(dest3d, comment); } else { // For all points in between, no z change - gcode += m_writer.travel_to_xy(this->point_to_gcode(travel.points[i]), comment + " travel_to_xy"); + gcode += m_writer.travel_to_xy(this->point_to_gcode(travel.points[i]), comment); } } } } this->set_last_pos(travel.points.back()); } + return gcode; } diff --git a/src/libslic3r/GCode/WipeTower2.cpp b/src/libslic3r/GCode/WipeTower2.cpp index 67ffb9eb513..9e9e8ea22e8 100644 --- a/src/libslic3r/GCode/WipeTower2.cpp +++ b/src/libslic3r/GCode/WipeTower2.cpp @@ -1127,7 +1127,7 @@ void WipeTower2::toolchange_Load( WipeTowerWriter2 &writer, const WipeTower::box_coordinates &cleaning_box) { - if (m_semm && (m_parking_pos_retraction != 0 || m_extra_loading_move != 0)) { + if (m_semm && m_enable_filament_ramming && (m_parking_pos_retraction != 0 || m_extra_loading_move != 0)) { float xl = cleaning_box.ld.x() + m_perimeter_width * 0.75f; float xr = cleaning_box.rd.x() - m_perimeter_width * 0.75f; float oldx = writer.x(); // the nozzle is in place to do the first wiping moves, we will remember the position diff --git a/src/libslic3r/GCodeWriter.cpp b/src/libslic3r/GCodeWriter.cpp index ad3ad640c04..d17527d1158 100644 --- a/src/libslic3r/GCodeWriter.cpp +++ b/src/libslic3r/GCodeWriter.cpp @@ -440,7 +440,7 @@ std::string GCodeWriter::travel_to_xy(const Vec2d &point, const std::string &com return w.string(); } -std::string GCodeWriter::travel_to_xyz(const Vec3d &point, const std::string &comment) +std::string GCodeWriter::travel_to_xyz(const Vec3d &point, const std::string &comment, bool force_z) { // FIXME: This function was not being used when travel_speed_z was separated (bd6badf). // Calculation of feedrate was not updated accordingly. If you want to use @@ -526,7 +526,7 @@ std::string GCodeWriter::travel_to_xyz(const Vec3d &point, const std::string &co this->set_current_position_clear(true); return slop_move + xy_z_move; } - else if (!this->will_move_z(point(2))) { + else if (!force_z && !this->will_move_z(point(2))) { double nominal_z = m_pos(2) - m_lifted; m_lifted -= (point(2) - nominal_z); // In case that z_hop == layer_height we could end up with almost zero in_m_lifted diff --git a/src/libslic3r/GCodeWriter.hpp b/src/libslic3r/GCodeWriter.hpp index 28569e909de..038325b446a 100644 --- a/src/libslic3r/GCodeWriter.hpp +++ b/src/libslic3r/GCodeWriter.hpp @@ -69,7 +69,7 @@ class GCodeWriter { // SoftFever NOTE: the returned speed is mm/minute double get_current_speed() const { return m_current_speed;} std::string travel_to_xy(const Vec2d &point, const std::string &comment = std::string()); - std::string travel_to_xyz(const Vec3d &point, const std::string &comment = std::string()); + std::string travel_to_xyz(const Vec3d &point, const std::string &comment = std::string(), bool force_z = false); std::string travel_to_z(double z, const std::string &comment = std::string()); bool will_move_z(double z) const; std::string extrude_to_xy(const Vec2d &point, double dE, const std::string &comment = std::string(), bool force_no_extrusion = false); @@ -81,7 +81,8 @@ class GCodeWriter { std::string unretract(); std::string lift(LiftType lift_type = LiftType::NormalLift, bool spiral_vase = false); std::string unlift(); - Vec3d get_position() const { return m_pos; } + const Vec3d& get_position() const { return m_pos; } + Vec3d& get_position() { return m_pos; } void set_position(const Vec3d& in) { m_pos = in; } double get_zhop() const { return m_lifted; } diff --git a/src/libslic3r/PerimeterGenerator.cpp b/src/libslic3r/PerimeterGenerator.cpp index 6cae4756a3b..3b9bc6daaa7 100644 --- a/src/libslic3r/PerimeterGenerator.cpp +++ b/src/libslic3r/PerimeterGenerator.cpp @@ -735,6 +735,12 @@ static ExtrusionEntityCollection traverse_loops(const PerimeterGenerator &perime if(paths.empty()) continue; chain_and_reorder_extrusion_paths(paths, &paths.front().first_point()); } else { + if (overhangs_reverse && perimeter_generator.layer_id > perimeter_generator.object_config->raft_layers) { + // Always reverse if detect overhang wall is not enabled + steep_overhang_contour = true; + steep_overhang_hole = true; + } + ExtrusionPath path(role); //BBS. path.polyline = polygon.split_at_first_point(); @@ -1219,6 +1225,12 @@ static ExtrusionEntityCollection traverse_extrusions(const PerimeterGenerator& p } } else { + if (overhangs_reverse && perimeter_generator.layer_id > perimeter_generator.object_config->raft_layers) { + // Always reverse if detect overhang wall is not enabled + steep_overhang_contour = true; + steep_overhang_hole = true; + } + extrusion_paths_append(paths, *extrusion, role, is_external ? perimeter_generator.ext_perimeter_flow : perimeter_generator.perimeter_flow); } diff --git a/src/libslic3r/Print.cpp b/src/libslic3r/Print.cpp index 09737086da9..bdce8e008c4 100644 --- a/src/libslic3r/Print.cpp +++ b/src/libslic3r/Print.cpp @@ -9,7 +9,6 @@ #include "Geometry/ConvexHull.hpp" #include "I18N.hpp" #include "ShortestPath.hpp" -#include "Support/SupportMaterial.hpp" #include "Thread.hpp" #include "Time.hpp" #include "GCode.hpp" diff --git a/src/libslic3r/PrintConfig.cpp b/src/libslic3r/PrintConfig.cpp index 94fdcfe5fe5..98822805182 100644 --- a/src/libslic3r/PrintConfig.cpp +++ b/src/libslic3r/PrintConfig.cpp @@ -1056,7 +1056,8 @@ void PrintConfigDef::init_fff_params() def->category = L("Quality"); // xgettext:no-c-format, no-boost-format def->tooltip = L("Number of mm the overhang need to be for the reversal to be considered useful. Can be a % of the perimeter width." - "\nValue 0 enables reversal on every even layers regardless."); + "\nValue 0 enables reversal on every even layers regardless." + "\nWhen Detect overhang wall is not enabled, this option is ignored and reversal happens on every even layers regardless."); def->sidetext = L("mm or %"); def->ratio_over = "line_width"; def->min = 0; @@ -2124,6 +2125,7 @@ void PrintConfigDef::init_fff_params() def->enum_values.push_back("PET-CF"); def->enum_values.push_back("PETG"); def->enum_values.push_back("PETG-CF"); + def->enum_values.push_back("PETG-CF10"); def->enum_values.push_back("PHA"); def->enum_values.push_back("PLA"); def->enum_values.push_back("PLA-AERO"); diff --git a/src/libslic3r/PrintObject.cpp b/src/libslic3r/PrintObject.cpp index fc83b1a8d97..528a72e7086 100644 --- a/src/libslic3r/PrintObject.cpp +++ b/src/libslic3r/PrintObject.cpp @@ -8,8 +8,8 @@ #include "Layer.hpp" #include "MutablePolygon.hpp" #include "PrintConfig.hpp" -#include "SupportMaterial.hpp" -#include "SupportSpotsGenerator.hpp" +#include "Support/SupportMaterial.hpp" +#include "Support/SupportSpotsGenerator.hpp" #include "Support/TreeSupport.hpp" #include "Surface.hpp" #include "Slicing.hpp" @@ -19,7 +19,6 @@ #include "Fill/FillAdaptive.hpp" #include "Fill/FillLightning.hpp" #include "Format/STL.hpp" -#include "TreeSupport.hpp" #include "format.hpp" #include @@ -3521,18 +3520,14 @@ void PrintObject::combine_infill() void PrintObject::_generate_support_material() { - PrintObjectSupportMaterial support_material(this, m_slicing_params); - support_material.generate(*this); - - if (this->config().enable_support.value && is_tree(this->config().support_type.value)) { - if (this->config().support_style.value == smsOrganic || - // Orca: use organic as default - this->config().support_style.value == smsDefault) { - fff_tree_support_generate(*this, std::function([this]() { this->throw_if_canceled(); })); - } else { - TreeSupport tree_support(*this, m_slicing_params); - tree_support.generate(); - } + if (is_tree(m_config.support_type.value)) { + TreeSupport tree_support(*this, m_slicing_params); + tree_support.throw_on_cancel = [this]() { this->throw_if_canceled(); }; + tree_support.generate(); + } + else { + PrintObjectSupportMaterial support_material(this, m_slicing_params); + support_material.generate(*this); } } diff --git a/src/libslic3r/Support/OrganicSupport.cpp b/src/libslic3r/Support/OrganicSupport.cpp deleted file mode 100644 index 05e515d94ec..00000000000 --- a/src/libslic3r/Support/OrganicSupport.cpp +++ /dev/null @@ -1,1420 +0,0 @@ -#include "OrganicSupport.hpp" -#include "SupportCommon.hpp" - -#include "../AABBTreeLines.hpp" -#include "../ClipperUtils.hpp" -#include "../Polygon.hpp" -#include "../Polyline.hpp" -#include "../MutablePolygon.hpp" -#include "../TriangleMeshSlicer.hpp" - -#include - -#include - -#define TREE_SUPPORT_ORGANIC_NUDGE_NEW 1 - -#ifndef TREE_SUPPORT_ORGANIC_NUDGE_NEW - // Old version using OpenVDB, works but it is extremely slow for complex meshes. - #include "../OpenVDBUtilsLegacy.hpp" - #include -#endif // TREE_SUPPORT_ORGANIC_NUDGE_NEW - -namespace Slic3r -{ - -namespace FFFTreeSupport -{ - -// Single slice through a single branch or trough a number of branches. -struct Slice -{ - // All polygons collected for this slice. - Polygons polygons; - // All bottom contacts collected for this slice. - Polygons bottom_contacts; - // How many branches were merged in this slice? Used to decide whether ClipperLib union is needed. - size_t num_branches{ 0 }; -}; - -struct Element -{ - // Current position of the centerline including the Z coordinate, unscaled. - Vec3f position; - float radius; - - // Index of this layer, including the raft layers. - LayerIndex layer_idx; - - // Limits where the centerline could be placed at the current layer Z. - Polygons influence_area; - - // Locked node should not be moved. Locked nodes are at the top of an object or at the tips of branches. - bool locked; - - // Previous position, for Laplacian smoothing, unscaled. - Vec3f prev_position; - - // For sphere tracing and other collision detection optimizations. - Vec3f last_collision; - double last_collision_depth; - - struct CollisionSphere { - // Minimum Z for which the sphere collision will be evaluated. - // Limited by the minimum sloping angle and by the bottom of the tree. - float min_z{ -std::numeric_limits::max() }; - // Maximum Z for which the sphere collision will be evaluated. - // Limited by the minimum sloping angle and by the tip of the current branch. - float max_z{ std::numeric_limits::max() }; - // Span of layers to test collision of this sphere against. - uint32_t layer_begin; - uint32_t layer_end; - }; - - CollisionSphere collision_sphere; -}; - -struct Branch; - -struct Bifurcation -{ - Branch *branch; - double area; -}; - -// Single branch of a tree. -struct Branch -{ - std::vector path; - - using Bifurcations = -#ifdef NDEBUG - // To reduce memory allocation in release mode. - boost::container::small_vector; -#else // NDEBUG - // To ease debugging. - std::vector; -#endif // NDEBUG - - Bifurcations up; - Bifurcation down; - - // How many of the thick up branches are considered continuation of the trunk? - // These will be smoothed out together. - size_t num_up_trunk; - - bool has_root() const { return this->down.branch == nullptr; } - bool has_tip() const { return this->up.empty(); } -}; - -struct Tree -{ - // Branches: Store of all branches. - // The first branch is the root of the tree. - Slic3r::deque branches; - - Branch& root() { return branches.front(); } - const Branch& root() const { return branches.front(); } - - // Result of slicing the branches. - std::vector slices; - // First layer index of the first slice in the vector above. - LayerIndex first_layer_id{ -1 }; -}; - -using Forest = std::vector; -using Trees = std::vector; - -Element to_tree_element(const TreeSupportSettings &config, const SlicingParameters &slicing_params, SupportElement &element, bool is_root) -{ - Element out; - out.position = to_3d(unscaled(element.state.result_on_layer), float(layer_z(slicing_params, config, element.state.layer_idx))); - out.radius = support_element_radius(config, element); - out.layer_idx = element.state.layer_idx; - out.influence_area = std::move(element.influence_area); - out.locked = (is_root && element.state.layer_idx > 0) || element.state.locked(); - return out; -} - -// Convert move bounds into a forest of trees, each tree made of a graph of branches and bifurcation points. -// Destroys move_bounds. -Forest make_forest(const TreeSupportSettings &config, const SlicingParameters &slicing_params, std::vector &&move_bounds) -{ - struct TreeVisitor { - void visit_recursive(std::vector &move_bounds, SupportElement &start_element, Branch *parent_branch, Tree &out) const { - assert(! start_element.state.marked && ! start_element.parents.empty()); - // Collect elements up to a bifurcation above. - start_element.state.marked = true; - // For each branch bifurcating from this point: -// SupportElements &layer = move_bounds[start_element.state.layer_idx]; - SupportElements &layer_above = move_bounds[start_element.state.layer_idx + 1]; - for (size_t parent_idx = 0; parent_idx < start_element.parents.size(); ++ parent_idx) { - Branch branch; - if (parent_branch) - // Duplicate the last element of the trunk below. - // If this branch has a smaller diameter than the trunk below, its centerline will not be aligned with the centerline of the trunk. - branch.path.emplace_back(parent_branch->path.back()); - branch.path.emplace_back(to_tree_element(config, slicing_params, start_element, parent_branch == nullptr)); - // Traverse each branch until it branches again. - SupportElement &first_parent = layer_above[start_element.parents[parent_idx]]; - assert(! first_parent.state.marked); - assert(branch.path.back().layer_idx + 1 == first_parent.state.layer_idx); - branch.path.emplace_back(to_tree_element(config, slicing_params, first_parent, false)); - if (first_parent.parents.size() < 2) - first_parent.state.marked = true; - SupportElement *next_branch = nullptr; - if (first_parent.parents.size() == 1) { - for (SupportElement *parent = &first_parent;;) { - assert(parent->state.marked); - SupportElement &next_parent = move_bounds[parent->state.layer_idx + 1][parent->parents.front()]; - assert(! next_parent.state.marked); - assert(branch.path.back().layer_idx + 1 == next_parent.state.layer_idx); - branch.path.emplace_back(to_tree_element(config, slicing_params, next_parent, false)); - if (next_parent.parents.size() > 1) { - // Branching point was reached. - next_branch = &next_parent; - break; - } - next_parent.state.marked = true; - if (next_parent.parents.size() == 0) - // Tip is reached. - break; - parent = &next_parent; - } - } else if (first_parent.parents.size() > 1) - // Branching point was reached. - next_branch = &first_parent; - assert(branch.path.size() >= 2); - assert(next_branch == nullptr || ! next_branch->state.marked); - out.branches.emplace_back(std::move(branch)); - Branch *pbranch = &out.branches.back(); - if (parent_branch) { - parent_branch->up.push_back({ pbranch }); - pbranch->down = { parent_branch }; - } - if (next_branch) - this->visit_recursive(move_bounds, *next_branch, pbranch, out); - } - - if (parent_branch) { - // Update initial radii of thin branches merging with a trunk. - auto it_up_max_r = std::max_element(parent_branch->up.begin(), parent_branch->up.end(), - [](const Bifurcation &l, const Bifurcation &r){ return l.branch->path[1].radius < r.branch->path[1].radius; }); - const float r1 = it_up_max_r->branch->path[1].radius; - const float radius_increment = unscaled(config.branch_radius_increase_per_layer); - for (auto it = parent_branch->up.begin(); it != parent_branch->up.end(); ++ it) - if (it != it_up_max_r) { - Element &el = it->branch->path.front(); - Element &el2 = it->branch->path[1]; - if (! is_approx(r1, el2.radius)) - el.radius = std::min(el.radius, el2.radius + radius_increment); - } - // Sort children of parent_branch by decreasing radius. - std::sort(parent_branch->up.begin(), parent_branch->up.end(), - [](const Bifurcation &l, const Bifurcation &r){ return l.branch->path.front().radius > r.branch->path.front().radius; }); - // Update number of branches to be considered a continuation of the trunk during smoothing. - { - const float r_trunk = 0.75 * it_up_max_r->branch->path.front().radius; - parent_branch->num_up_trunk = 0; - for (const Bifurcation& up : parent_branch->up) - if (up.branch->path.front().radius < r_trunk) - break; - else - ++ parent_branch->num_up_trunk; - } - } - } - - const TreeSupportSettings &config; - const SlicingParameters &slicing_params; - }; - - TreeVisitor visitor{ config, slicing_params }; - - for (SupportElements &elements : move_bounds) - for (SupportElement &el : elements) - el.state.marked = false; - - Trees trees; - for (LayerIndex layer_idx = 0; layer_idx + 1 < LayerIndex(move_bounds.size()); ++ layer_idx) { - for (SupportElement &start_element : move_bounds[layer_idx]) { - if (! start_element.state.marked && ! start_element.parents.empty()) { -#if 0 - { - // Verify that this node is a root, such that there is no element in the layer below - // that points to it. - int ielement = &start_element - move_bounds.data(); - int found = 0; - if (layer_idx > 0) { - for (auto &el : move_bounds[layer_idx - 1]) { - for (auto iparent : el.parents) - if (iparent == ielement) - ++ found; - } - if (found != 0) - printf("Found: %d\n", found); - } - } -#endif - trees.push_back({}); - visitor.visit_recursive(move_bounds, start_element, nullptr, trees.back()); - assert(! trees.back().branches.empty()); - assert(! trees.back().branches.front().path.empty()); -#if 0 - // Debugging: Only build trees with specific properties. - if (start_element.state.lost) { - } - else if (start_element.state.verylost) { - } - else - trees.pop_back(); -#endif - } - } - } - -#if 1 - move_bounds.clear(); -#else - for (SupportElements &elements : move_bounds) - for (SupportElement &el : elements) - el.state.marked = false; -#endif - - return trees; -} - -// Move bounds were propagated top to bottom. At each joint of branches the move bounds were reduced significantly. -// Now reflect the reduction of tree space by propagating the reduction of tree centerline space -// bottom-up starting with the bottom-most joint. -void trim_influence_areas_bottom_up(Forest &forest, const float dxy_dlayer) -{ - struct Trimmer { - static void trim_recursive(Branch &branch, const float delta_r, const float dxy_dlayer) { - assert(delta_r >= 0); - if (delta_r > 0) - branch.path.front().influence_area = offset(branch.path.front().influence_area, delta_r); - for (size_t i = 1; i < branch.path.size(); ++ i) - branch.path[i].influence_area = intersection(branch.path[i].influence_area, offset(branch.path[i - 1].influence_area, dxy_dlayer)); - const float r0 = branch.path.back().radius; - for (Bifurcation &up : branch.up) { - up.branch->path.front().influence_area = branch.path.back().influence_area; - trim_recursive(*up.branch, r0 - up.branch->path.front().radius, dxy_dlayer); - } - } - }; - - for (Tree &tree : forest) { - Branch &root = tree.root(); - const float r0 = root.path.back().radius; - for (Bifurcation &up : root.up) - Trimmer::trim_recursive(*up.branch, r0 - up.branch->path.front().radius, dxy_dlayer); - } -} - -// Straighten up and smooth centerlines inside their influence areas. -void smooth_trees_inside_influence_areas(Branch &root, bool is_root) -{ - // Smooth the subtree: - // - // Apply laplacian and bilaplacian smoothing inside a branch, - // apply laplacian smoothing only at a bifurcation point. - // - // Applying a bilaplacian smoothing inside a branch should ensure curvature of the brach to be lower - // than the radius at each particular point of the centerline, - // while omitting bilaplacian smoothing at bifurcation points will create sharp bifurcations. - // Sharp bifurcations have a smaller volume, but just a tiny bit larger surfaces than smooth bifurcations - // where each continuation of the trunk satifies the path radius > centerline element radius. - const size_t num_iterations = 100; - struct StackElement { - Branch &branch; - size_t idx_up; - }; - std::vector stack; - - auto adjust_position = [](Element &el, Vec2f new_pos) { - Point new_pos_scaled = scaled(new_pos); - if (! contains(el.influence_area, new_pos_scaled)) { - int64_t min_dist = std::numeric_limits::max(); - Point min_proj_scaled; - for (const Polygon& polygon : el.influence_area) { - Point proj_scaled = polygon.point_projection(new_pos_scaled); - if (int64_t dist = (proj_scaled - new_pos_scaled).cast().squaredNorm(); dist < min_dist) { - min_dist = dist; - min_proj_scaled = proj_scaled; - } - } - new_pos = unscaled(min_proj_scaled); - } - el.position.head<2>() = new_pos; - }; - - for (size_t iter = 0; iter < num_iterations; ++ iter) { - // 1) Back-up the current positions. - stack.push_back({ root, 0 }); - while (! stack.empty()) { - StackElement &state = stack.back(); - if (state.idx_up == state.branch.num_up_trunk) { - // Process this path. - for (auto &el : state.branch.path) - el.prev_position = el.position; - stack.pop_back(); - } else { - // Open another up node of this branch. - stack.push_back({ *state.branch.up[state.idx_up].branch, 0 }); - ++ state.idx_up; - } - } - // 2) Calculate new position. - stack.push_back({ root, 0 }); - while (! stack.empty()) { - StackElement &state = stack.back(); - if (state.idx_up == state.branch.num_up_trunk) { - // Process this path. - for (size_t i = 1; i + 1 < state.branch.path.size(); ++ i) - if (auto &el = state.branch.path[i]; ! el.locked) { - // Laplacian smoothing with 0.5 weight. - const Vec3f &p0 = state.branch.path[i - 1].prev_position; - const Vec3f &p1 = el.prev_position; - const Vec3f &p2 = state.branch.path[i + 1].prev_position; - adjust_position(el, 0.5 * p1.head<2>() + 0.25 * (p0.head<2>() + p2.head<2>())); -#if 0 - // Only apply bilaplacian smoothing if the current curvature is smaller than el.radius. - // Interpolate p0, p1, p2 with a circle. - // First project p0, p1, p2 into a common plane. - const Vec3f n = (p1 - p0).cross(p2 - p1); - const Vec3f y = Vec3f(n.y(), n.x(), 0).normalized(); - const Vec2f q0{ p0.z(), p0.dot(y) }; - const Vec2f q1{ p1.z(), p1.dot(y) }; - const Vec2f q2{ p2.z(), p2.dot(y) }; - // Interpolate q0, q1, q2 with a circle, calculate its radius. - Vec2f b = q1 - q0; - Vec2f c = q2 - q0; - float lb = b.squaredNorm(); - float lc = c.squaredNorm(); - if (float d = b.x() * c.y() - b.y() * c.x(); std::abs(d) > EPSILON) { - Vec2f v = lc * b - lb * c; - float r2 = 0.25f * v.squaredNorm() / sqr(d); - if (r2 ) - } -#endif - } - { - // Laplacian smoothing with 0.5 weight, branching point. - float weight = 0; - Vec2f new_pos = Vec2f::Zero(); - for (size_t i = 0; i < state.branch.num_up_trunk; ++i) { - const Element &el = state.branch.up[i].branch->path.front(); - new_pos += el.prev_position.head<2>(); - weight += el.radius; - } - { - const Element &el = state.branch.path[state.branch.path.size() - 2]; - new_pos += el.prev_position.head<2>(); - weight *= 2.f; - } - adjust_position(state.branch.path.back(), 0.5f * state.branch.path.back().prev_position.head<2>() + 0.5f * weight * new_pos); - } - stack.pop_back(); - } else { - // Open another up node of this branch. - stack.push_back({ *state.branch.up[state.idx_up].branch, 0 }); - ++ state.idx_up; - } - } - } - // Also smoothen start of the path. - if (Element &first = root.path.front(); ! first.locked) { - Element &second = root.path[1]; - Vec2f new_pos = 0.75f * first.prev_position.head<2>() + 0.25f * second.prev_position.head<2>(); - if (is_root) - // Let the root of the tree float inside its influence area. - adjust_position(first, new_pos); - else { - // Keep the start of a thin branch inside the trunk. - const Element &trunk = root.down.branch->path.back(); - const float rdif = trunk.radius - root.path.front().radius; - assert(rdif >= 0); - Vec2f vdif = new_pos - trunk.prev_position.head<2>(); - float ldif = vdif.squaredNorm(); - if (ldif > sqr(rdif)) - // Clamp new position. - new_pos = trunk.prev_position.head<2>() + vdif * rdif / sqrt(ldif); - first.position.head<2>() = new_pos; - } - } -} - -void smooth_trees_inside_influence_areas(Forest &forest) -{ - // Parallel for! - for (Tree &tree : forest) - smooth_trees_inside_influence_areas(tree.root(), true); -} - -#if 0 -// Test whether two circles, each on its own plane in 3D intersect. -// Circles are considered intersecting, if the lowest point on one circle is below the other circle's plane. -// Assumption: The two planes are oriented the same way. -static bool circles_intersect( - const Vec3d &p1, const Vec3d &n1, const double r1, - const Vec3d &p2, const Vec3d &n2, const double r2) -{ - assert(n1.dot(n2) >= 0); - - const Vec3d z = n1.cross(n2); - const Vec3d dir1 = z.cross(n1); - const Vec3d lowest_point1 = p1 + dir1 * (r1 / dir1.norm()); - assert(n2.dot(p1) >= n2.dot(lowest_point1)); - if (n2.dot(lowest_point1) <= 0) - return true; - const Vec3d dir2 = z.cross(n2); - const Vec3d lowest_point2 = p2 + dir2 * (r2 / dir2.norm()); - assert(n1.dot(p2) >= n1.dot(lowest_point2)); - return n1.dot(lowest_point2) <= 0; -} -#endif - -template -void triangulate_fan(indexed_triangle_set &its, int ifan, int ibegin, int iend) -{ - // at least 3 vertices, increasing order. - assert(ibegin + 3 <= iend); - assert(ibegin >= 0 && iend <= its.vertices.size()); - assert(ifan >= 0 && ifan < its.vertices.size()); - int num_faces = iend - ibegin; - its.indices.reserve(its.indices.size() + num_faces * 3); - for (int v = ibegin, u = iend - 1; v < iend; u = v ++) { - if (flip_normals) - its.indices.push_back({ ifan, u, v }); - else - its.indices.push_back({ ifan, v, u }); - } -} - -static void triangulate_strip(indexed_triangle_set &its, int ibegin1, int iend1, int ibegin2, int iend2) -{ - // at least 3 vertices, increasing order. - assert(ibegin1 + 3 <= iend1); - assert(ibegin1 >= 0 && iend1 <= its.vertices.size()); - assert(ibegin2 + 3 <= iend2); - assert(ibegin2 >= 0 && iend2 <= its.vertices.size()); - int n1 = iend1 - ibegin1; - int n2 = iend2 - ibegin2; - its.indices.reserve(its.indices.size() + (n1 + n2) * 3); - - // For the first vertex of 1st strip, find the closest vertex on the 2nd strip. - int istart2 = ibegin2; - { - const Vec3f &p1 = its.vertices[ibegin1]; - auto d2min = std::numeric_limits::max(); - for (int i = ibegin2; i < iend2; ++ i) { - const Vec3f &p2 = its.vertices[i]; - const float d2 = (p2 - p1).squaredNorm(); - if (d2 < d2min) { - d2min = d2; - istart2 = i; - } - } - } - - // Now triangulate the strip zig-zag fashion taking always the shortest connection if possible. - for (int u = ibegin1, v = istart2; n1 > 0 || n2 > 0;) { - bool take_first; - int u2, v2; - auto update_u2 = [&u2, u, ibegin1, iend1]() { - u2 = u; - if (++ u2 == iend1) - u2 = ibegin1; - }; - auto update_v2 = [&v2, v, ibegin2, iend2]() { - v2 = v; - if (++ v2 == iend2) - v2 = ibegin2; - }; - if (n1 == 0) { - take_first = false; - update_v2(); - } else if (n2 == 0) { - take_first = true; - update_u2(); - } else { - update_u2(); - update_v2(); - float l1 = (its.vertices[u2] - its.vertices[v]).squaredNorm(); - float l2 = (its.vertices[v2] - its.vertices[u]).squaredNorm(); - take_first = l1 < l2; - } - if (take_first) { - its.indices.push_back({ u, u2, v }); - -- n1; - u = u2; - } else { - its.indices.push_back({ u, v2, v }); - -- n2; - v = v2; - } - } -} - -// Discretize 3D circle, append to output vector, return ranges of indices of the points added. -static std::pair discretize_circle(const Vec3f ¢er, const Vec3f &normal, const float radius, const float eps, std::vector &pts) -{ - // Calculate discretization step and number of steps. - float angle_step = 2. * acos(1. - eps / radius); - auto nsteps = int(ceil(2 * M_PI / angle_step)); - angle_step = 2 * M_PI / nsteps; - - // Prepare coordinate system for the circle plane. - Vec3f x = normal.cross(Vec3f(0.f, -1.f, 0.f)).normalized(); - Vec3f y = normal.cross(x).normalized(); - assert(std::abs(x.cross(y).dot(normal) - 1.f) < EPSILON); - - // Discretize the circle. - int begin = int(pts.size()); - pts.reserve(pts.size() + nsteps); - float angle = 0; - x *= radius; - y *= radius; - for (int i = 0; i < nsteps; ++ i) { - pts.emplace_back(center + x * cos(angle) + y * sin(angle)); - angle += angle_step; - } - return { begin, int(pts.size()) }; -} - -// Returns Z span of the generated mesh. -static std::pair extrude_branch( - const std::vector &path, - const TreeSupportSettings &config, - const SlicingParameters &slicing_params, - const std::vector &move_bounds, - indexed_triangle_set &result) -{ - Vec3d p1, p2, p3; - Vec3d v1, v2; - Vec3d nprev; - Vec3d ncurrent; - assert(path.size() >= 2); - static constexpr const float eps = 0.015f; - std::pair prev_strip; - -// char fname[2048]; -// static int irun = 0; - - float zmin = 0; - float zmax = 0; - - for (size_t ipath = 1; ipath < path.size(); ++ ipath) { - const SupportElement &prev = *path[ipath - 1]; - const SupportElement ¤t = *path[ipath]; - assert(prev.state.layer_idx + 1 == current.state.layer_idx); - p1 = to_3d(unscaled(prev .state.result_on_layer), layer_z(slicing_params, config, prev .state.layer_idx)); - p2 = to_3d(unscaled(current.state.result_on_layer), layer_z(slicing_params, config, current.state.layer_idx)); - v1 = (p2 - p1).normalized(); - if (ipath == 1) { - nprev = v1; - // Extrude the bottom half sphere. - float radius = unscaled(support_element_radius(config, prev)); - float angle_step = 2. * acos(1. - eps / radius); - auto nsteps = int(ceil(M_PI / (2. * angle_step))); - angle_step = M_PI / (2. * nsteps); - int ifan = int(result.vertices.size()); - result.vertices.emplace_back((p1 - nprev * radius).cast()); - zmin = result.vertices.back().z(); - float angle = angle_step; - for (int i = 1; i < nsteps; ++ i, angle += angle_step) { - std::pair strip = discretize_circle((p1 - nprev * radius * cos(angle)).cast(), nprev.cast(), radius * sin(angle), eps, result.vertices); - if (i == 1) - triangulate_fan(result, ifan, strip.first, strip.second); - else - triangulate_strip(result, prev_strip.first, prev_strip.second, strip.first, strip.second); -// sprintf(fname, "d:\\temp\\meshes\\tree-partial-%d.obj", ++ irun); -// its_write_obj(result, fname); - prev_strip = strip; - } - } - if (ipath + 1 == path.size()) { - // End of the tube. - ncurrent = v1; - // Extrude the top half sphere. - float radius = unscaled(support_element_radius(config, current)); - float angle_step = 2. * acos(1. - eps / radius); - auto nsteps = int(ceil(M_PI / (2. * angle_step))); - angle_step = M_PI / (2. * nsteps); - auto angle = float(M_PI / 2.); - for (int i = 0; i < nsteps; ++ i, angle -= angle_step) { - std::pair strip = discretize_circle((p2 + ncurrent * radius * cos(angle)).cast(), ncurrent.cast(), radius * sin(angle), eps, result.vertices); - triangulate_strip(result, prev_strip.first, prev_strip.second, strip.first, strip.second); -// sprintf(fname, "d:\\temp\\meshes\\tree-partial-%d.obj", ++ irun); -// its_write_obj(result, fname); - prev_strip = strip; - } - int ifan = int(result.vertices.size()); - result.vertices.emplace_back((p2 + ncurrent * radius).cast()); - zmax = result.vertices.back().z(); - triangulate_fan(result, ifan, prev_strip.first, prev_strip.second); -// sprintf(fname, "d:\\temp\\meshes\\tree-partial-%d.obj", ++ irun); -// its_write_obj(result, fname); - } else { - const SupportElement &next = *path[ipath + 1]; - assert(current.state.layer_idx + 1 == next.state.layer_idx); - p3 = to_3d(unscaled(next.state.result_on_layer), layer_z(slicing_params, config, next.state.layer_idx)); - v2 = (p3 - p2).normalized(); - ncurrent = (v1 + v2).normalized(); - float radius = unscaled(support_element_radius(config, current)); - std::pair strip = discretize_circle(p2.cast(), ncurrent.cast(), radius, eps, result.vertices); - triangulate_strip(result, prev_strip.first, prev_strip.second, strip.first, strip.second); - prev_strip = strip; -// sprintf(fname, "d:\\temp\\meshes\\tree-partial-%d.obj", ++irun); -// its_write_obj(result, fname); - } -#if 0 - if (circles_intersect(p1, nprev, support_element_radius(settings, prev), p2, ncurrent, support_element_radius(settings, current))) { - // Cannot connect previous and current slice using a simple zig-zag triangulation, - // because the two circles intersect. - - } else { - // Continue with chaining. - - } -#endif - } - - return std::make_pair(zmin, zmax); -} - -#ifdef TREE_SUPPORT_ORGANIC_NUDGE_NEW - -// New version using per layer AABB trees of lines for nudging spheres away from an object. -static void organic_smooth_branches_avoid_collisions( - const PrintObject &print_object, - const TreeModelVolumes &volumes, - const TreeSupportSettings &config, - std::vector &move_bounds, - const std::vector> &elements_with_link_down, - const std::vector &linear_data_layers, - std::function throw_on_cancel) -{ - struct LayerCollisionCache { - coord_t min_element_radius{ std::numeric_limits::max() }; - bool min_element_radius_known() const { return this->min_element_radius != std::numeric_limits::max(); } - coord_t collision_radius{ 0 }; - std::vector lines; - AABBTreeIndirect::Tree<2, double> aabbtree_lines; - bool empty() const { return this->lines.empty(); } - }; - std::vector layer_collision_cache; - layer_collision_cache.reserve(1024); - const SlicingParameters &slicing_params = print_object.slicing_parameters(); - for (const std::pair& element : elements_with_link_down) { - LayerIndex layer_idx = element.first->state.layer_idx; - if (size_t num_layers = layer_idx + 1; num_layers > layer_collision_cache.size()) { - if (num_layers > layer_collision_cache.capacity()) - reserve_power_of_2(layer_collision_cache, num_layers); - layer_collision_cache.resize(num_layers, {}); - } - auto& l = layer_collision_cache[layer_idx]; - l.min_element_radius = std::min(l.min_element_radius, support_element_radius(config, *element.first)); - } - - throw_on_cancel(); - - for (LayerIndex layer_idx = 0; layer_idx < LayerIndex(layer_collision_cache.size()); ++layer_idx) - if (LayerCollisionCache& l = layer_collision_cache[layer_idx]; !l.min_element_radius_known()) - l.min_element_radius = 0; - else { - //FIXME - l.min_element_radius = 0; - std::optional>> res = volumes.get_collision_lower_bound_area(layer_idx, l.min_element_radius); - assert(res.has_value()); - l.collision_radius = res->first; - Lines alines = to_lines(res->second.get()); - l.lines.reserve(alines.size()); - for (const Line &line : alines) - l.lines.push_back({ unscaled(line.a), unscaled(line.b) }); - l.aabbtree_lines = AABBTreeLines::build_aabb_tree_over_indexed_lines(l.lines); - throw_on_cancel(); - } - - struct CollisionSphere { - const SupportElement& element; - int element_below_id; - const bool locked; - float radius; - // Current position, when nudged away from the collision. - Vec3f position; - // Previous position, for Laplacian smoothing. - Vec3f prev_position; - // - Vec3f last_collision; - double last_collision_depth; - // Minimum Z for which the sphere collision will be evaluated. - // Limited by the minimum sloping angle and by the bottom of the tree. - float min_z{ -std::numeric_limits::max() }; - // Maximum Z for which the sphere collision will be evaluated. - // Limited by the minimum sloping angle and by the tip of the current branch. - float max_z{ std::numeric_limits::max() }; - uint32_t layer_begin; - uint32_t layer_end; - }; - - std::vector collision_spheres; - collision_spheres.reserve(elements_with_link_down.size()); - for (const std::pair &element_with_link : elements_with_link_down) { - const SupportElement &element = *element_with_link.first; - const int link_down = element_with_link.second; - collision_spheres.push_back({ - element, - link_down, - // locked - element.parents.empty() || (link_down == -1 && element.state.layer_idx > 0), - unscaled(support_element_radius(config, element)), - // 3D position - to_3d(unscaled(element.state.result_on_layer), float(layer_z(slicing_params, config, element.state.layer_idx))) - }); - // Update min_z coordinate to min_z of the tree below. - CollisionSphere &collision_sphere = collision_spheres.back(); - if (link_down != -1) { - const size_t offset_below = linear_data_layers[element.state.layer_idx - 1]; - collision_sphere.min_z = collision_spheres[offset_below + link_down].min_z; - } else - collision_sphere.min_z = collision_sphere.position.z(); - } - // Update max_z by propagating max_z from the tips of the branches. - for (int collision_sphere_id = int(collision_spheres.size()) - 1; collision_sphere_id >= 0; -- collision_sphere_id) { - CollisionSphere &collision_sphere = collision_spheres[collision_sphere_id]; - if (collision_sphere.element.parents.empty()) - // Tip - collision_sphere.max_z = collision_sphere.position.z(); - else { - // Below tip - const size_t offset_above = linear_data_layers[collision_sphere.element.state.layer_idx + 1]; - for (auto iparent : collision_sphere.element.parents) { - float parent_z = collision_spheres[offset_above + iparent].max_z; -// collision_sphere.max_z = collision_sphere.max_z == std::numeric_limits::max() ? parent_z : std::max(collision_sphere.max_z, parent_z); - collision_sphere.max_z = std::min(collision_sphere.max_z, parent_z); - } - } - } - // Update min_z / max_z to limit the search Z span of a given sphere for collision detection. - for (CollisionSphere &collision_sphere : collision_spheres) { - //FIXME limit the collision span by the tree slope. - collision_sphere.min_z = std::max(collision_sphere.min_z, collision_sphere.position.z() - collision_sphere.radius); - collision_sphere.max_z = std::min(collision_sphere.max_z, collision_sphere.position.z() + collision_sphere.radius); - collision_sphere.layer_begin = std::min(collision_sphere.element.state.layer_idx, layer_idx_ceil(slicing_params, config, collision_sphere.min_z)); - assert(collision_sphere.layer_begin < layer_collision_cache.size()); - collision_sphere.layer_end = std::min(LayerIndex(layer_collision_cache.size()), std::max(collision_sphere.element.state.layer_idx, layer_idx_floor(slicing_params, config, collision_sphere.max_z)) + 1); - } - - throw_on_cancel(); - - static constexpr const double collision_extra_gap = 0.1; - static constexpr const double max_nudge_collision_avoidance = 0.5; - static constexpr const double max_nudge_smoothing = 0.2; - static constexpr const size_t num_iter = 100; // 1000; - for (size_t iter = 0; iter < num_iter; ++ iter) { - // Back up prev position before Laplacian smoothing. - for (CollisionSphere &collision_sphere : collision_spheres) - collision_sphere.prev_position = collision_sphere.position; - std::atomic num_moved{ 0 }; - tbb::parallel_for(tbb::blocked_range(0, collision_spheres.size()), - [&collision_spheres, &layer_collision_cache, &slicing_params, &config, &linear_data_layers, &num_moved, &throw_on_cancel](const tbb::blocked_range range) { - for (size_t collision_sphere_id = range.begin(); collision_sphere_id < range.end(); ++ collision_sphere_id) - if (CollisionSphere &collision_sphere = collision_spheres[collision_sphere_id]; ! collision_sphere.locked) { - // Calculate collision of multiple 2D layers against a collision sphere. - collision_sphere.last_collision_depth = - std::numeric_limits::max(); - for (uint32_t layer_id = collision_sphere.layer_begin; layer_id != collision_sphere.layer_end; ++ layer_id) { - double dz = (layer_id - collision_sphere.element.state.layer_idx) * slicing_params.layer_height; - if (double r2 = sqr(collision_sphere.radius) - sqr(dz); r2 > 0) { - if (const LayerCollisionCache &layer_collision_cache_item = layer_collision_cache[layer_id]; ! layer_collision_cache_item.empty()) { - size_t hit_idx_out; - Vec2d hit_point_out; - if (double dist = sqrt(AABBTreeLines::squared_distance_to_indexed_lines( - layer_collision_cache_item.lines, layer_collision_cache_item.aabbtree_lines, Vec2d(to_2d(collision_sphere.position).cast()), - hit_idx_out, hit_point_out, r2)); dist >= 0.) { - double collision_depth = sqrt(r2) - dist; - if (collision_depth > collision_sphere.last_collision_depth) { - collision_sphere.last_collision_depth = collision_depth; - collision_sphere.last_collision = to_3d(hit_point_out.cast(), float(layer_z(slicing_params, config, layer_id))); - } - } - } - } - } - if (collision_sphere.last_collision_depth > 0) { - // Collision detected to be removed. - // Nudge the circle center away from the collision. - if (collision_sphere.last_collision_depth > EPSILON) - // a little bit of hysteresis to detect end of - ++ num_moved; - // Shift by maximum 2mm. - double nudge_dist = std::min(std::max(0., collision_sphere.last_collision_depth + collision_extra_gap), max_nudge_collision_avoidance); - Vec2d nudge_vector = (to_2d(collision_sphere.position) - to_2d(collision_sphere.last_collision)).cast().normalized() * nudge_dist; - collision_sphere.position.head<2>() += (nudge_vector * nudge_dist).cast(); - } - // Laplacian smoothing - Vec2d avg{ 0, 0 }; - //const SupportElements &above = move_bounds[collision_sphere.element.state.layer_idx + 1]; - const size_t offset_above = linear_data_layers[collision_sphere.element.state.layer_idx + 1]; - double weight = 0.; - for (auto iparent : collision_sphere.element.parents) { - double w = collision_sphere.radius; - avg += w * to_2d(collision_spheres[offset_above + iparent].prev_position.cast()); - weight += w; - } - if (collision_sphere.element_below_id != -1) { - const size_t offset_below = linear_data_layers[collision_sphere.element.state.layer_idx - 1]; - const double w = weight; // support_element_radius(config, move_bounds[element.state.layer_idx - 1][below]); - avg += w * to_2d(collision_spheres[offset_below + collision_sphere.element_below_id].prev_position.cast()); - weight += w; - } - avg /= weight; - static constexpr const double smoothing_factor = 0.5; - Vec2d old_pos = to_2d(collision_sphere.position).cast(); - Vec2d new_pos = (1. - smoothing_factor) * old_pos + smoothing_factor * avg; - Vec2d shift = new_pos - old_pos; - double nudge_dist_max = shift.norm(); - // Shift by maximum 1mm, less than the collision avoidance factor. - double nudge_dist = std::min(std::max(0., nudge_dist_max), max_nudge_smoothing); - collision_sphere.position.head<2>() += (shift.normalized() * nudge_dist).cast(); - - throw_on_cancel(); - } - }); -#if 0 - std::vector stat; - for (CollisionSphere& collision_sphere : collision_spheres) - if (!collision_sphere.locked) - stat.emplace_back(collision_sphere.last_collision_depth); - std::sort(stat.begin(), stat.end()); - printf("iteration: %d, moved: %d, collision depth: min %lf, max %lf, median %lf\n", int(iter), int(num_moved), stat.front(), stat.back(), stat[stat.size() / 2]); -#endif - if (num_moved == 0) - break; - } - - for (size_t i = 0; i < collision_spheres.size(); ++ i) - elements_with_link_down[i].first->state.result_on_layer = scaled(to_2d(collision_spheres[i].position)); -} -#else // TREE_SUPPORT_ORGANIC_NUDGE_NEW -// Old version using OpenVDB, works but it is extremely slow for complex meshes. -static void organic_smooth_branches_avoid_collisions( - const PrintObject &print_object, - const TreeModelVolumes &volumes, - const TreeSupportSettings &config, - std::vector &move_bounds, - const std::vector> &elements_with_link_down, - const std::vector &linear_data_layers, - std::function throw_on_cancel) -{ - TriangleMesh mesh = print_object.model_object()->raw_mesh(); - mesh.transform(print_object.trafo_centered()); - double scale = 10.; - openvdb::FloatGrid::Ptr grid = mesh_to_grid(mesh.its, openvdb::math::Transform{}, scale, 0., 0.); - std::unique_ptr> closest_surface_point = openvdb::tools::ClosestSurfacePoint::create(*grid); - std::vector pts, prev, projections; - std::vector distances; - for (const std::pair& element : elements_with_link_down) { - Vec3d pt = to_3d(unscaled(element.first->state.result_on_layer), layer_z(print_object.slicing_parameters(), config, element.first->state.layer_idx)) * scale; - pts.push_back({ pt.x(), pt.y(), pt.z() }); - } - - const double collision_extra_gap = 1. * scale; - const double max_nudge_collision_avoidance = 2. * scale; - const double max_nudge_smoothing = 1. * scale; - - static constexpr const size_t num_iter = 100; // 1000; - for (size_t iter = 0; iter < num_iter; ++ iter) { - prev = pts; - projections = pts; - distances.assign(pts.size(), std::numeric_limits::max()); - closest_surface_point->searchAndReplace(projections, distances); - size_t num_moved = 0; - for (size_t i = 0; i < projections.size(); ++ i) { - const SupportElement &element = *elements_with_link_down[i].first; - const int below = elements_with_link_down[i].second; - const bool locked = (below == -1 && element.state.layer_idx > 0) || element.state.locked(); - if (! locked && pts[i] != projections[i]) { - // Nudge the circle center away from the collision. - Vec3d v{ projections[i].x() - pts[i].x(), projections[i].y() - pts[i].y(), projections[i].z() - pts[i].z() }; - double depth = v.norm(); - assert(std::abs(distances[i] - depth) < EPSILON); - double radius = unscaled(support_element_radius(config, element)) * scale; - if (depth < radius) { - // Collision detected to be removed. - ++ num_moved; - double dxy = sqrt(sqr(radius) - sqr(v.z())); - double nudge_dist_max = dxy - std::hypot(v.x(), v.y()) - //FIXME 1mm gap - + collision_extra_gap; - // Shift by maximum 2mm. - double nudge_dist = std::min(std::max(0., nudge_dist_max), max_nudge_collision_avoidance); - Vec2d nudge_v = to_2d(v).normalized() * (- nudge_dist); - pts[i].x() += nudge_v.x(); - pts[i].y() += nudge_v.y(); - } - } - // Laplacian smoothing - if (! locked && ! element.parents.empty()) { - Vec2d avg{ 0, 0 }; - const SupportElements &above = move_bounds[element.state.layer_idx + 1]; - const size_t offset_above = linear_data_layers[element.state.layer_idx + 1]; - double weight = 0.; - for (auto iparent : element.parents) { - double w = support_element_radius(config, above[iparent]); - avg.x() += w * prev[offset_above + iparent].x(); - avg.y() += w * prev[offset_above + iparent].y(); - weight += w; - } - size_t cnt = element.parents.size(); - if (below != -1) { - const size_t offset_below = linear_data_layers[element.state.layer_idx - 1]; - const double w = weight; // support_element_radius(config, move_bounds[element.state.layer_idx - 1][below]); - avg.x() += w * prev[offset_below + below].x(); - avg.y() += w * prev[offset_below + below].y(); - ++ cnt; - weight += w; - } - //avg /= double(cnt); - avg /= weight; - static constexpr const double smoothing_factor = 0.5; - Vec2d old_pos{ pts[i].x(), pts[i].y() }; - Vec2d new_pos = (1. - smoothing_factor) * old_pos + smoothing_factor * avg; - Vec2d shift = new_pos - old_pos; - double nudge_dist_max = shift.norm(); - // Shift by maximum 1mm, less than the collision avoidance factor. - double nudge_dist = std::min(std::max(0., nudge_dist_max), max_nudge_smoothing); - Vec2d nudge_v = shift.normalized() * nudge_dist; - pts[i].x() += nudge_v.x(); - pts[i].y() += nudge_v.y(); - } - } -// printf("iteration: %d, moved: %d\n", int(iter), int(num_moved)); - if (num_moved == 0) - break; - } - - for (size_t i = 0; i < projections.size(); ++ i) { - elements_with_link_down[i].first->state.result_on_layer.x() = scaled(pts[i].x()) / scale; - elements_with_link_down[i].first->state.result_on_layer.y() = scaled(pts[i].y()) / scale; - } -} -#endif // TREE_SUPPORT_ORGANIC_NUDGE_NEW - -// Organic specific: Smooth branches and produce one cummulative mesh to be sliced. -void organic_draw_branches( - PrintObject &print_object, - TreeModelVolumes &volumes, - const TreeSupportSettings &config, - std::vector &move_bounds, - - // I/O: - SupportGeneratorLayersPtr &bottom_contacts, - SupportGeneratorLayersPtr &top_contacts, - InterfacePlacer &interface_placer, - - // Output: - SupportGeneratorLayersPtr &intermediate_layers, - SupportGeneratorLayerStorage &layer_storage, - - std::function throw_on_cancel) -{ - // All SupportElements are put into a layer independent storage to improve parallelization. - std::vector> elements_with_link_down; - std::vector linear_data_layers; - { - std::vector> map_downwards_old; - std::vector> map_downwards_new; - linear_data_layers.emplace_back(0); - for (LayerIndex layer_idx = 0; layer_idx < LayerIndex(move_bounds.size()); ++ layer_idx) { - SupportElements *layer_above = layer_idx + 1 < LayerIndex(move_bounds.size()) ? &move_bounds[layer_idx + 1] : nullptr; - map_downwards_new.clear(); - std::sort(map_downwards_old.begin(), map_downwards_old.end(), [](auto& l, auto& r) { return l.first < r.first; }); - SupportElements &layer = move_bounds[layer_idx]; - for (size_t elem_idx = 0; elem_idx < layer.size(); ++ elem_idx) { - SupportElement &elem = layer[elem_idx]; - int child = -1; - if (layer_idx > 0) { - auto it = std::lower_bound(map_downwards_old.begin(), map_downwards_old.end(), &elem, [](auto& l, const SupportElement* r) { return l.first < r; }); - if (it != map_downwards_old.end() && it->first == &elem) { - child = it->second; - // Only one link points to a node above from below. - assert(!(++it != map_downwards_old.end() && it->first == &elem)); - } -#ifndef NDEBUG - { - const SupportElement *pchild = child == -1 ? nullptr : &move_bounds[layer_idx - 1][child]; - assert(pchild ? pchild->state.result_on_layer_is_set() : elem.state.target_height > layer_idx); - } -#endif // NDEBUG - } - for (int32_t parent_idx : elem.parents) { - SupportElement &parent = (*layer_above)[parent_idx]; - if (parent.state.result_on_layer_is_set()) - map_downwards_new.emplace_back(&parent, elem_idx); - } - - elements_with_link_down.push_back({ &elem, int(child) }); - } - std::swap(map_downwards_old, map_downwards_new); - linear_data_layers.emplace_back(elements_with_link_down.size()); - } - } - - throw_on_cancel(); - - organic_smooth_branches_avoid_collisions(print_object, volumes, config, move_bounds, elements_with_link_down, linear_data_layers, throw_on_cancel); - - // Reduce memory footprint. After this point only finalize_interface_and_support_areas() will use volumes and from that only collisions with zero radius will be used. - volumes.clear_all_but_object_collision(); - - // Unmark all nodes. - for (SupportElements &elements : move_bounds) - for (SupportElement &element : elements) - element.state.marked = false; - - // Traverse all nodes, generate tubes. - // Traversal stack with nodes and their current parent - - struct Branch { - std::vector path; - bool has_root{ false }; - bool has_tip { false }; - }; - - struct Slice { - Polygons polygons; - Polygons bottom_contacts; - size_t num_branches{ 0 }; - }; - - struct Tree { - std::vector branches; - - std::vector slices; - LayerIndex first_layer_id{ -1 }; - }; - - std::vector trees; - - struct TreeVisitor { - static void visit_recursive(std::vector &move_bounds, SupportElement &start_element, Tree &out) { - assert(! start_element.state.marked && ! start_element.parents.empty()); - // Collect elements up to a bifurcation above. - start_element.state.marked = true; - // For each branch bifurcating from this point: - //SupportElements &layer = move_bounds[start_element.state.layer_idx]; - SupportElements &layer_above = move_bounds[start_element.state.layer_idx + 1]; - bool root = out.branches.empty(); - for (size_t parent_idx = 0; parent_idx < start_element.parents.size(); ++ parent_idx) { - Branch branch; - branch.path.emplace_back(&start_element); - // Traverse each branch until it branches again. - SupportElement &first_parent = layer_above[start_element.parents[parent_idx]]; - assert(! first_parent.state.marked); - assert(branch.path.back()->state.layer_idx + 1 == first_parent.state.layer_idx); - branch.path.emplace_back(&first_parent); - if (first_parent.parents.size() < 2) - first_parent.state.marked = true; - SupportElement *next_branch = nullptr; - if (first_parent.parents.size() == 1) { - for (SupportElement *parent = &first_parent;;) { - assert(parent->state.marked); - SupportElement &next_parent = move_bounds[parent->state.layer_idx + 1][parent->parents.front()]; - assert(! next_parent.state.marked); - assert(branch.path.back()->state.layer_idx + 1 == next_parent.state.layer_idx); - branch.path.emplace_back(&next_parent); - if (next_parent.parents.size() > 1) { - // Branching point was reached. - next_branch = &next_parent; - break; - } - next_parent.state.marked = true; - if (next_parent.parents.size() == 0) - // Tip is reached. - break; - parent = &next_parent; - } - } else if (first_parent.parents.size() > 1) - // Branching point was reached. - next_branch = &first_parent; - assert(branch.path.size() >= 2); - assert(next_branch == nullptr || ! next_branch->state.marked); - branch.has_root = root; - branch.has_tip = ! next_branch; - out.branches.emplace_back(std::move(branch)); - if (next_branch) - visit_recursive(move_bounds, *next_branch, out); - } - } - }; - - for (LayerIndex layer_idx = 0; layer_idx + 1 < LayerIndex(move_bounds.size()); ++ layer_idx) { -// int ielement; - for (SupportElement& start_element : move_bounds[layer_idx]) { - if (!start_element.state.marked && !start_element.parents.empty()) { -#if 0 - int found = 0; - if (layer_idx > 0) { - for (auto& el : move_bounds[layer_idx - 1]) { - for (auto iparent : el.parents) - if (iparent == ielement) - ++found; - } - if (found != 0) - printf("Found: %d\n", found); - } -#endif - trees.push_back({}); - TreeVisitor::visit_recursive(move_bounds, start_element, trees.back()); - assert(!trees.back().branches.empty()); - //FIXME debugging -#if 0 - if (start_element.state.lost) { - } - else if (start_element.state.verylost) { - } else - trees.pop_back(); -#endif - } -// ++ ielement; - } - } - - const SlicingParameters &slicing_params = print_object.slicing_parameters(); - MeshSlicingParams mesh_slicing_params; - mesh_slicing_params.mode = MeshSlicingParams::SlicingMode::Positive; - - tbb::parallel_for(tbb::blocked_range(0, trees.size(), 1), - [&trees, &volumes, &config, &slicing_params, &move_bounds, &mesh_slicing_params, &throw_on_cancel](const tbb::blocked_range &range) { - indexed_triangle_set partial_mesh; - std::vector slice_z; - std::vector bottom_contacts; - for (size_t tree_id = range.begin(); tree_id < range.end(); ++ tree_id) { - Tree &tree = trees[tree_id]; - for (const Branch &branch : tree.branches) { - // Triangulate the tube. - partial_mesh.clear(); - std::pair zspan = extrude_branch(branch.path, config, slicing_params, move_bounds, partial_mesh); - LayerIndex layer_begin = branch.has_root ? - branch.path.front()->state.layer_idx : - std::min(branch.path.front()->state.layer_idx, layer_idx_ceil(slicing_params, config, zspan.first)); - LayerIndex layer_end = (branch.has_tip ? - branch.path.back()->state.layer_idx : - std::max(branch.path.back()->state.layer_idx, layer_idx_floor(slicing_params, config, zspan.second))) + 1; - slice_z.clear(); - for (LayerIndex layer_idx = layer_begin; layer_idx < layer_end; ++ layer_idx) { - const double print_z = layer_z(slicing_params, config, layer_idx); - const double bottom_z = layer_idx > 0 ? layer_z(slicing_params, config, layer_idx - 1) : 0.; - slice_z.emplace_back(float(0.5 * (bottom_z + print_z))); - } - std::vector slices = slice_mesh(partial_mesh, slice_z, mesh_slicing_params, throw_on_cancel); - bottom_contacts.clear(); - //FIXME parallelize? - for (LayerIndex i = 0; i < LayerIndex(slices.size()); ++ i) - slices[i] = diff_clipped(slices[i], volumes.getCollision(0, layer_begin + i, true)); //FIXME parent_uses_min || draw_area.element->state.use_min_xy_dist); - - size_t num_empty = 0; - if (slices.front().empty()) { - // Some of the initial layers are empty. - num_empty = std::find_if(slices.begin(), slices.end(), [](auto &s) { return !s.empty(); }) - slices.begin(); - } else { - if (branch.has_root) { - if (branch.path.front()->state.to_model_gracious) { - if (config.settings.support_floor_layers > 0) - //FIXME one may just take the whole tree slice as bottom interface. - bottom_contacts.emplace_back(intersection_clipped(slices.front(), volumes.getPlaceableAreas(0, layer_begin, [] {}))); - } else if (layer_begin > 0) { - // Drop down areas that do rest non - gracefully on the model to ensure the branch actually rests on something. - struct BottomExtraSlice { - Polygons polygons; - double area; - }; - std::vector bottom_extra_slices; - Polygons rest_support; - coord_t bottom_radius = support_element_radius(config, *branch.path.front()); - // Don't propagate further than 1.5 * bottom radius. - //LayerIndex layers_propagate_max = 2 * bottom_radius / config.layer_height; - LayerIndex layers_propagate_max = 5 * bottom_radius / config.layer_height; - LayerIndex layer_bottommost = branch.path.front()->state.verylost ? - // If the tree bottom is hanging in the air, bring it down to some surface. - 0 : - //FIXME the "verylost" branches should stop when crossing another support. - std::max(0, layer_begin - layers_propagate_max); - double support_area_min_radius = M_PI * sqr(double(config.branch_radius)); - double support_area_stop = std::max(0.2 * M_PI * sqr(double(bottom_radius)), 0.5 * support_area_min_radius); - // Only propagate until the rest area is smaller than this threshold. - //double support_area_min = 0.1 * support_area_min_radius; - for (LayerIndex layer_idx = layer_begin - 1; layer_idx >= layer_bottommost; -- layer_idx) { - rest_support = diff_clipped(rest_support.empty() ? slices.front() : rest_support, volumes.getCollision(0, layer_idx, false)); - double rest_support_area = area(rest_support); - if (rest_support_area < support_area_stop) - // Don't propagate a fraction of the tree contact surface. - break; - bottom_extra_slices.push_back({ rest_support, rest_support_area }); - } - // Now remove those bottom slices that are not supported at all. -#if 0 - while (! bottom_extra_slices.empty()) { - Polygons this_bottom_contacts = intersection_clipped( - bottom_extra_slices.back().polygons, volumes.getPlaceableAreas(0, layer_begin - LayerIndex(bottom_extra_slices.size()), [] {})); - if (area(this_bottom_contacts) < support_area_min) - bottom_extra_slices.pop_back(); - else { - // At least a fraction of the tree bottom is considered to be supported. - if (config.settings.support_floor_layers > 0) - // Turn this fraction of the tree bottom into a contact layer. - bottom_contacts.emplace_back(std::move(this_bottom_contacts)); - break; - } - } -#endif - if (config.settings.support_floor_layers > 0) - for (int i = int(bottom_extra_slices.size()) - 2; i >= 0; -- i) - bottom_contacts.emplace_back( - intersection_clipped(bottom_extra_slices[i].polygons, volumes.getPlaceableAreas(0, layer_begin - i - 1, [] {}))); - layer_begin -= LayerIndex(bottom_extra_slices.size()); - slices.insert(slices.begin(), bottom_extra_slices.size(), {}); - auto it_dst = slices.begin(); - for (auto it_src = bottom_extra_slices.rbegin(); it_src != bottom_extra_slices.rend(); ++ it_src) - *it_dst ++ = std::move(it_src->polygons); - } - } - -#if 0 - //FIXME branch.has_tip seems to not be reliable. - if (branch.has_tip && interface_placer.support_parameters.has_top_contacts) - // Add top slices to top contacts / interfaces / base interfaces. - for (int i = int(branch.path.size()) - 1; i >= 0; -- i) { - const SupportElement &el = *branch.path[i]; - if (el.state.missing_roof_layers == 0) - break; - //FIXME Move or not? - interface_placer.add_roof(std::move(slices[int(slices.size()) - i - 1]), el.state.layer_idx, - interface_placer.support_parameters.num_top_interface_layers + 1 - el.state.missing_roof_layers); - } -#endif - } - - layer_begin += LayerIndex(num_empty); - while (! slices.empty() && slices.back().empty()) { - slices.pop_back(); - -- layer_end; - } - if (layer_begin < layer_end) { - LayerIndex new_begin = tree.first_layer_id == -1 ? layer_begin : std::min(tree.first_layer_id, layer_begin); - LayerIndex new_end = tree.first_layer_id == -1 ? layer_end : std::max(tree.first_layer_id + LayerIndex(tree.slices.size()), layer_end); - size_t new_size = size_t(new_end - new_begin); - if (tree.first_layer_id == -1) { - } else if (tree.slices.capacity() < new_size) { - std::vector new_slices; - new_slices.reserve(new_size); - if (LayerIndex dif = tree.first_layer_id - new_begin; dif > 0) - new_slices.insert(new_slices.end(), dif, {}); - append(new_slices, std::move(tree.slices)); - tree.slices.swap(new_slices); - } else if (LayerIndex dif = tree.first_layer_id - new_begin; dif > 0) - tree.slices.insert(tree.slices.begin(), tree.first_layer_id - new_begin, {}); - tree.slices.insert(tree.slices.end(), new_size - tree.slices.size(), {}); - layer_begin -= LayerIndex(num_empty); - for (LayerIndex i = layer_begin; i != layer_end; ++ i) { - int j = i - layer_begin; - if (Polygons &src = slices[j]; ! src.empty()) { - Slice &dst = tree.slices[i - new_begin]; - if (++ dst.num_branches > 1) { - append(dst.polygons, std::move(src)); - if (j < int(bottom_contacts.size())) - append(dst.bottom_contacts, std::move(bottom_contacts[j])); - } else { - dst.polygons = std::move(std::move(src)); - if (j < int(bottom_contacts.size())) - dst.bottom_contacts = std::move(bottom_contacts[j]); - } - } - } - tree.first_layer_id = new_begin; - } - } - } - }, tbb::simple_partitioner()); - - tbb::parallel_for(tbb::blocked_range(0, trees.size(), 1), - [&trees, &throw_on_cancel](const tbb::blocked_range &range) { - for (size_t tree_id = range.begin(); tree_id < range.end(); ++ tree_id) { - Tree &tree = trees[tree_id]; - for (Slice &slice : tree.slices) - if (slice.num_branches > 1) { - slice.polygons = union_(slice.polygons); - slice.bottom_contacts = union_(slice.bottom_contacts); - slice.num_branches = 1; - } - throw_on_cancel(); - } - }, tbb::simple_partitioner()); - - size_t num_layers = 0; - for (Tree &tree : trees) - if (tree.first_layer_id >= 0) - num_layers = std::max(num_layers, size_t(tree.first_layer_id + tree.slices.size())); - - std::vector slices(num_layers, Slice{}); - for (Tree &tree : trees) - if (tree.first_layer_id >= 0) { - for (LayerIndex i = tree.first_layer_id; i != tree.first_layer_id + LayerIndex(tree.slices.size()); ++ i) - if (Slice &src = tree.slices[i - tree.first_layer_id]; ! src.polygons.empty()) { - Slice &dst = slices[i]; - if (++ dst.num_branches > 1) { - append(dst.polygons, std::move(src.polygons)); - append(dst.bottom_contacts, std::move(src.bottom_contacts)); - } else { - dst.polygons = std::move(src.polygons); - dst.bottom_contacts = std::move(src.bottom_contacts); - } - } - } - - tbb::parallel_for(tbb::blocked_range(0, std::min(move_bounds.size(), slices.size()), 1), - [&print_object, &config, &slices, &bottom_contacts, &top_contacts, &intermediate_layers, &layer_storage, &throw_on_cancel](const tbb::blocked_range &range) { - for (size_t layer_idx = range.begin(); layer_idx < range.end(); ++layer_idx) { - Slice &slice = slices[layer_idx]; - assert(intermediate_layers[layer_idx] == nullptr); - Polygons base_layer_polygons = slice.num_branches > 1 ? union_(slice.polygons) : std::move(slice.polygons); - Polygons bottom_contact_polygons = slice.num_branches > 1 ? union_(slice.bottom_contacts) : std::move(slice.bottom_contacts); - - if (! base_layer_polygons.empty()) { - // Most of the time in this function is this union call. Can take 300+ ms when a lot of areas are to be unioned. - base_layer_polygons = smooth_outward(union_(base_layer_polygons), config.support_line_width); //FIXME was .smooth(50); - //smooth_outward(closing(std::move(bottom), closing_distance + minimum_island_radius, closing_distance, SUPPORT_SURFACES_OFFSET_PARAMETERS), smoothing_distance) : - // simplify a bit, to ensure the output does not contain outrageous amounts of vertices. Should not be necessary, just a precaution. - base_layer_polygons = polygons_simplify(base_layer_polygons, std::min(scaled(0.03), double(config.resolution)), polygons_strictly_simple); - } - - // Subtract top contact layer polygons from support base. - SupportGeneratorLayer *top_contact_layer = top_contacts.empty() ? nullptr : top_contacts[layer_idx]; - if (top_contact_layer && ! top_contact_layer->polygons.empty() && ! base_layer_polygons.empty()) { - base_layer_polygons = diff(base_layer_polygons, top_contact_layer->polygons); - if (! bottom_contact_polygons.empty()) - //FIXME it may be better to clip bottom contacts with top contacts first after they are propagated to produce interface layers. - bottom_contact_polygons = diff(bottom_contact_polygons, top_contact_layer->polygons); - } - if (! bottom_contact_polygons.empty()) { - base_layer_polygons = diff(base_layer_polygons, bottom_contact_polygons); - SupportGeneratorLayer *bottom_contact_layer = bottom_contacts[layer_idx] = &layer_allocate( - layer_storage, SupporLayerType::BottomContact, print_object.slicing_parameters(), config, layer_idx); - bottom_contact_layer->polygons = std::move(bottom_contact_polygons); - } - if (! base_layer_polygons.empty()) { - SupportGeneratorLayer *base_layer = intermediate_layers[layer_idx] = &layer_allocate( - layer_storage, SupporLayerType::Base, print_object.slicing_parameters(), config, layer_idx); - base_layer->polygons = union_(base_layer_polygons); - } - - throw_on_cancel(); - } - }, tbb::simple_partitioner()); -} - -} // namespace FFFTreeSupport - -} // namespace Slic3r diff --git a/src/libslic3r/Support/OrganicSupport.hpp b/src/libslic3r/Support/OrganicSupport.hpp deleted file mode 100644 index f86caabe99a..00000000000 --- a/src/libslic3r/Support/OrganicSupport.hpp +++ /dev/null @@ -1,39 +0,0 @@ -#ifndef slic3r_OrganicSupport_hpp -#define slic3r_OrganicSupport_hpp - -#include "SupportCommon.hpp" -#include "TreeSupport.hpp" - -namespace Slic3r -{ - -class PrintObject; - -namespace FFFTreeSupport -{ - -class TreeModelVolumes; - -// Organic specific: Smooth branches and produce one cummulative mesh to be sliced. -void organic_draw_branches( - PrintObject &print_object, - TreeModelVolumes &volumes, - const TreeSupportSettings &config, - std::vector &move_bounds, - - // I/O: - SupportGeneratorLayersPtr &bottom_contacts, - SupportGeneratorLayersPtr &top_contacts, - InterfacePlacer &interface_placer, - - // Output: - SupportGeneratorLayersPtr &intermediate_layers, - SupportGeneratorLayerStorage &layer_storage, - - std::function throw_on_cancel); - -} // namespace FFFTreeSupport - -} // namespace Slic3r - -#endif // slic3r_OrganicSupport_hpp \ No newline at end of file diff --git a/src/libslic3r/Support/SupportCommon.cpp b/src/libslic3r/Support/SupportCommon.cpp index f51e992256f..d37324084cb 100644 --- a/src/libslic3r/Support/SupportCommon.cpp +++ b/src/libslic3r/Support/SupportCommon.cpp @@ -32,7 +32,7 @@ #include -namespace Slic3r::FFFSupport { +namespace Slic3r { // how much we extend support around the actual contact area //FIXME this should be dependent on the nozzle diameter! diff --git a/src/libslic3r/Support/SupportCommon.hpp b/src/libslic3r/Support/SupportCommon.hpp index 753665e5963..cdaa43c5dbf 100644 --- a/src/libslic3r/Support/SupportCommon.hpp +++ b/src/libslic3r/Support/SupportCommon.hpp @@ -12,8 +12,6 @@ namespace Slic3r { class PrintObject; class SupportLayer; -namespace FFFSupport { - // Remove bridges from support contact areas. // To be called if PrintObjectConfig::dont_support_bridges. void remove_bridges_from_contacts( @@ -150,8 +148,6 @@ int idx_lower_or_equal(const std::vector &vec, int idx, FN_LOWER_EQUAL fn_lo return idx_lower_or_equal(vec.begin(), vec.end(), idx, fn_lower_equal); } -} // namespace FFFSupport - } // namespace Slic3r #endif /* slic3r_SupportCommon_hpp_ */ diff --git a/src/libslic3r/Support/SupportDebug.cpp b/src/libslic3r/Support/SupportDebug.cpp deleted file mode 100644 index 5c18bc76947..00000000000 --- a/src/libslic3r/Support/SupportDebug.cpp +++ /dev/null @@ -1,108 +0,0 @@ -#if 1 //#ifdef SLIC3R_DEBUG - -#include "../ClipperUtils.hpp" -#include "../SVG.hpp" -#include "../Layer.hpp" - -#include "SupportLayer.hpp" - -namespace Slic3r::FFFSupport { - -const char* support_surface_type_to_color_name(const SupporLayerType surface_type) -{ - switch (surface_type) { - case SupporLayerType::TopContact: return "rgb(255,0,0)"; // "red"; - case SupporLayerType::TopInterface: return "rgb(0,255,0)"; // "green"; - case SupporLayerType::Base: return "rgb(0,0,255)"; // "blue"; - case SupporLayerType::BottomInterface:return "rgb(255,255,128)"; // yellow - case SupporLayerType::BottomContact: return "rgb(255,0,255)"; // magenta - case SupporLayerType::RaftInterface: return "rgb(0,255,255)"; - case SupporLayerType::RaftBase: return "rgb(128,128,128)"; - case SupporLayerType::Unknown: return "rgb(128,0,0)"; // maroon - default: return "rgb(64,64,64)"; - }; -} - -Point export_support_surface_type_legend_to_svg_box_size() -{ - return Point(scale_(1.+10.*8.), scale_(3.)); -} - -void export_support_surface_type_legend_to_svg(SVG &svg, const Point &pos) -{ - // 1st row - coord_t pos_x0 = pos(0) + scale_(1.); - coord_t pos_x = pos_x0; - coord_t pos_y = pos(1) + scale_(1.5); - coord_t step_x = scale_(10.); - svg.draw_legend(Point(pos_x, pos_y), "top contact" , support_surface_type_to_color_name(SupporLayerType::TopContact)); - pos_x += step_x; - svg.draw_legend(Point(pos_x, pos_y), "top iface" , support_surface_type_to_color_name(SupporLayerType::TopInterface)); - pos_x += step_x; - svg.draw_legend(Point(pos_x, pos_y), "base" , support_surface_type_to_color_name(SupporLayerType::Base)); - pos_x += step_x; - svg.draw_legend(Point(pos_x, pos_y), "bottom iface" , support_surface_type_to_color_name(SupporLayerType::BottomInterface)); - pos_x += step_x; - svg.draw_legend(Point(pos_x, pos_y), "bottom contact" , support_surface_type_to_color_name(SupporLayerType::BottomContact)); - // 2nd row - pos_x = pos_x0; - pos_y = pos(1)+scale_(2.8); - svg.draw_legend(Point(pos_x, pos_y), "raft interface" , support_surface_type_to_color_name(SupporLayerType::RaftInterface)); - pos_x += step_x; - svg.draw_legend(Point(pos_x, pos_y), "raft base" , support_surface_type_to_color_name(SupporLayerType::RaftBase)); - pos_x += step_x; - svg.draw_legend(Point(pos_x, pos_y), "unknown" , support_surface_type_to_color_name(SupporLayerType::Unknown)); - pos_x += step_x; - svg.draw_legend(Point(pos_x, pos_y), "intermediate" , support_surface_type_to_color_name(SupporLayerType::Intermediate)); -} - -void export_print_z_polygons_to_svg(const char *path, SupportGeneratorLayer ** const layers, int n_layers) -{ - BoundingBox bbox; - for (int i = 0; i < n_layers; ++ i) - bbox.merge(get_extents(layers[i]->polygons)); - Point legend_size = export_support_surface_type_legend_to_svg_box_size(); - Point legend_pos(bbox.min(0), bbox.max(1)); - bbox.merge(Point(std::max(bbox.min(0) + legend_size(0), bbox.max(0)), bbox.max(1) + legend_size(1))); - SVG svg(path, bbox); - const float transparency = 0.5f; - for (int i = 0; i < n_layers; ++ i) - svg.draw(union_ex(layers[i]->polygons), support_surface_type_to_color_name(layers[i]->layer_type), transparency); - for (int i = 0; i < n_layers; ++ i) - svg.draw(to_polylines(layers[i]->polygons), support_surface_type_to_color_name(layers[i]->layer_type)); - export_support_surface_type_legend_to_svg(svg, legend_pos); - svg.Close(); -} - -void export_print_z_polygons_and_extrusions_to_svg( - const char *path, - SupportGeneratorLayer ** const layers, - int n_layers, - SupportLayer &support_layer) -{ - BoundingBox bbox; - for (int i = 0; i < n_layers; ++ i) - bbox.merge(get_extents(layers[i]->polygons)); - Point legend_size = export_support_surface_type_legend_to_svg_box_size(); - Point legend_pos(bbox.min(0), bbox.max(1)); - bbox.merge(Point(std::max(bbox.min(0) + legend_size(0), bbox.max(0)), bbox.max(1) + legend_size(1))); - SVG svg(path, bbox); - const float transparency = 0.5f; - for (int i = 0; i < n_layers; ++ i) - svg.draw(union_ex(layers[i]->polygons), support_surface_type_to_color_name(layers[i]->layer_type), transparency); - for (int i = 0; i < n_layers; ++ i) - svg.draw(to_polylines(layers[i]->polygons), support_surface_type_to_color_name(layers[i]->layer_type)); - - Polygons polygons_support, polygons_interface; - support_layer.support_fills.polygons_covered_by_width(polygons_support, float(SCALED_EPSILON)); -// support_layer.support_interface_fills.polygons_covered_by_width(polygons_interface, SCALED_EPSILON); - svg.draw(union_ex(polygons_support), "brown"); - svg.draw(union_ex(polygons_interface), "black"); - - export_support_surface_type_legend_to_svg(svg, legend_pos); - svg.Close(); -} - -} // namespace Slic3r - -#endif /* SLIC3R_DEBUG */ diff --git a/src/libslic3r/Support/SupportDebug.hpp b/src/libslic3r/Support/SupportDebug.hpp deleted file mode 100644 index 22a43bc4ea7..00000000000 --- a/src/libslic3r/Support/SupportDebug.hpp +++ /dev/null @@ -1,18 +0,0 @@ -#ifndef slic3r_SupportCommon_hpp_ -#define slic3r_SupportCommon_hpp_ - -namespace Slic3r { - -class SupportGeneratorLayer; -class SupportLayer; - -namespace FFFSupport { - -void export_print_z_polygons_to_svg(const char *path, SupportGeneratorLayer ** const layers, size_t n_layers); -void export_print_z_polygons_and_extrusions_to_svg(const char *path, SupportGeneratorLayer ** const layers, size_t n_layers, SupportLayer& support_layer); - -} // namespace FFFSupport - -} // namespace Slic3r - -#endif /* slic3r_SupportCommon_hpp_ */ diff --git a/src/libslic3r/Support/SupportLayer.hpp b/src/libslic3r/Support/SupportLayer.hpp index a3c60b9fd82..64e3be99e7a 100644 --- a/src/libslic3r/Support/SupportLayer.hpp +++ b/src/libslic3r/Support/SupportLayer.hpp @@ -8,7 +8,7 @@ #include "../ClipperUtils.hpp" #include "../Polygon.hpp" -namespace Slic3r::FFFSupport { +namespace Slic3r { // Support layer type to be used by SupportGeneratorLayer. This type carries a much more detailed information // about the support layer type than the final support layers stored in a PrintObject. diff --git a/src/libslic3r/Support/SupportMaterial.cpp b/src/libslic3r/Support/SupportMaterial.cpp index c6d250e4208..b022607bf75 100644 --- a/src/libslic3r/Support/SupportMaterial.cpp +++ b/src/libslic3r/Support/SupportMaterial.cpp @@ -1,16 +1,13 @@ -#include "../ClipperUtils.hpp" -#include "../ExtrusionEntityCollection.hpp" -#include "../Layer.hpp" -#include "../Print.hpp" -#include "../Fill/FillBase.hpp" -#include "../Geometry.hpp" -#include "../Point.hpp" -#include "../MutablePolygon.hpp" - -#include "Support/SupportCommon.hpp" +#include "ClipperUtils.hpp" +#include "ExtrusionEntity.hpp" +#include "ExtrusionEntityCollection.hpp" +#include "Layer.hpp" +#include "Print.hpp" #include "SupportMaterial.hpp" - -#include +#include "Fill/FillBase.hpp" +#include "Geometry.hpp" +#include "Point.hpp" +#include "MutablePolygon.hpp" #include #include @@ -18,6 +15,7 @@ #include #include +#include #include #define SUPPORT_USE_AGG_RASTERIZER @@ -34,25 +32,29 @@ #endif // SUPPORT_USE_AGG_RASTERIZER // #define SLIC3R_DEBUG - +// #define SUPPORT_TREE_DEBUG_TO_SVG // Make assert active if SLIC3R_DEBUG -#ifdef SLIC3R_DEBUG +#if defined(SLIC3R_DEBUG) || defined(SUPPORT_TREE_DEBUG_TO_SVG) #define DEBUG #define _DEBUG #undef NDEBUG - #include "../utils.hpp" - #include "../SVG.hpp" + #include "utils.hpp" + #include "SVG.hpp" #endif -#include +#ifndef SQ +#define SQ(x) ((x)*(x)) +#endif -using namespace Slic3r::FFFSupport; +// #undef NDEBUG +#include namespace Slic3r { // how much we extend support around the actual contact area //FIXME this should be dependent on the nozzle diameter! -#define SUPPORT_MATERIAL_MARGIN 1.5 +// BBS: change from 1.5 to 1.2 +#define SUPPORT_MATERIAL_MARGIN 1.2 // Increment used to reach MARGIN in steps to avoid trespassing thin objects #define NUM_MARGIN_STEPS 3 @@ -65,6 +67,105 @@ namespace Slic3r { //#define SUPPORT_SURFACES_OFFSET_PARAMETERS ClipperLib::jtMiter, 1.5 #define SUPPORT_SURFACES_OFFSET_PARAMETERS ClipperLib::jtSquare, 0. +static constexpr bool support_with_sheath = false; + +#ifdef SLIC3R_DEBUG +const char* support_surface_type_to_color_name(const PrintObjectSupportMaterial::SupporLayerType surface_type) +{ + switch (surface_type) { + case PrintObjectSupportMaterial::sltTopContact: return "rgb(255,0,0)"; // "red"; + case PrintObjectSupportMaterial::sltTopInterface: return "rgb(0,255,0)"; // "green"; + case PrintObjectSupportMaterial::sltBase: return "rgb(0,0,255)"; // "blue"; + case PrintObjectSupportMaterial::sltBottomInterface:return "rgb(255,255,128)"; // yellow + case PrintObjectSupportMaterial::sltBottomContact: return "rgb(255,0,255)"; // magenta + case PrintObjectSupportMaterial::sltRaftInterface: return "rgb(0,255,255)"; + case PrintObjectSupportMaterial::sltRaftBase: return "rgb(128,128,128)"; + case PrintObjectSupportMaterial::sltUnknown: return "rgb(128,0,0)"; // maroon + default: return "rgb(64,64,64)"; + }; +} + +Point export_support_surface_type_legend_to_svg_box_size() +{ + return Point(scale_(1.+10.*8.), scale_(3.)); +} + +void export_support_surface_type_legend_to_svg(SVG &svg, const Point &pos) +{ + // 1st row + coord_t pos_x0 = pos(0) + scale_(1.); + coord_t pos_x = pos_x0; + coord_t pos_y = pos(1) + scale_(1.5); + coord_t step_x = scale_(10.); + svg.draw_legend(Point(pos_x, pos_y), "top contact" , support_surface_type_to_color_name(PrintObjectSupportMaterial::sltTopContact)); + pos_x += step_x; + svg.draw_legend(Point(pos_x, pos_y), "top iface" , support_surface_type_to_color_name(PrintObjectSupportMaterial::sltTopInterface)); + pos_x += step_x; + svg.draw_legend(Point(pos_x, pos_y), "base" , support_surface_type_to_color_name(PrintObjectSupportMaterial::sltBase)); + pos_x += step_x; + svg.draw_legend(Point(pos_x, pos_y), "bottom iface" , support_surface_type_to_color_name(PrintObjectSupportMaterial::sltBottomInterface)); + pos_x += step_x; + svg.draw_legend(Point(pos_x, pos_y), "bottom contact" , support_surface_type_to_color_name(PrintObjectSupportMaterial::sltBottomContact)); + // 2nd row + pos_x = pos_x0; + pos_y = pos(1)+scale_(2.8); + svg.draw_legend(Point(pos_x, pos_y), "raft interface" , support_surface_type_to_color_name(PrintObjectSupportMaterial::sltRaftInterface)); + pos_x += step_x; + svg.draw_legend(Point(pos_x, pos_y), "raft base" , support_surface_type_to_color_name(PrintObjectSupportMaterial::sltRaftBase)); + pos_x += step_x; + svg.draw_legend(Point(pos_x, pos_y), "unknown" , support_surface_type_to_color_name(PrintObjectSupportMaterial::sltUnknown)); + pos_x += step_x; + svg.draw_legend(Point(pos_x, pos_y), "intermediate" , support_surface_type_to_color_name(PrintObjectSupportMaterial::sltIntermediate)); +} + +void export_print_z_polygons_to_svg(const char *path, PrintObjectSupportMaterial::MyLayer ** const layers, size_t n_layers) +{ + BoundingBox bbox; + for (int i = 0; i < n_layers; ++ i) + bbox.merge(get_extents(layers[i]->polygons)); + Point legend_size = export_support_surface_type_legend_to_svg_box_size(); + Point legend_pos(bbox.min(0), bbox.max(1)); + bbox.merge(Point(std::max(bbox.min(0) + legend_size(0), bbox.max(0)), bbox.max(1) + legend_size(1))); + SVG svg(path, bbox); + const float transparency = 0.5f; + for (int i = 0; i < n_layers; ++ i) + svg.draw(union_ex(layers[i]->polygons), support_surface_type_to_color_name(layers[i]->layer_type), transparency); + for (int i = 0; i < n_layers; ++ i) + svg.draw(to_polylines(layers[i]->polygons), support_surface_type_to_color_name(layers[i]->layer_type)); + export_support_surface_type_legend_to_svg(svg, legend_pos); + svg.Close(); +} + +void export_print_z_polygons_and_extrusions_to_svg( + const char *path, + PrintObjectSupportMaterial::MyLayer ** const layers, + size_t n_layers, + SupportLayer &support_layer) +{ + BoundingBox bbox; + for (int i = 0; i < n_layers; ++ i) + bbox.merge(get_extents(layers[i]->polygons)); + Point legend_size = export_support_surface_type_legend_to_svg_box_size(); + Point legend_pos(bbox.min(0), bbox.max(1)); + bbox.merge(Point(std::max(bbox.min(0) + legend_size(0), bbox.max(0)), bbox.max(1) + legend_size(1))); + SVG svg(path, bbox); + const float transparency = 0.5f; + for (int i = 0; i < n_layers; ++ i) + svg.draw(union_ex(layers[i]->polygons), support_surface_type_to_color_name(layers[i]->layer_type), transparency); + for (int i = 0; i < n_layers; ++ i) + svg.draw(to_polylines(layers[i]->polygons), support_surface_type_to_color_name(layers[i]->layer_type)); + + Polygons polygons_support, polygons_interface; + support_layer.support_fills.polygons_covered_by_width(polygons_support, float(SCALED_EPSILON)); +// support_layer.support_interface_fills.polygons_covered_by_width(polygons_interface, SCALED_EPSILON); + svg.draw(union_ex(polygons_support), "brown"); + svg.draw(union_ex(polygons_interface), "black"); + + export_support_surface_type_legend_to_svg(svg, legend_pos); + svg.Close(); +} +#endif /* SLIC3R_DEBUG */ + #ifdef SUPPORT_USE_AGG_RASTERIZER static std::vector rasterize_polygons(const Vec2i32 &grid_size, const double pixel_size, const Point &left_bottom, const Polygons &polygons) { @@ -228,14 +329,135 @@ static Polygons contours_simplified(const Vec2i32 &grid_size, const double pixel } #endif // SUPPORT_USE_AGG_RASTERIZER +static std::string get_svg_filename(std::string layer_nr_or_z, std::string tag = "bbl_ts") +{ + static bool rand_init = false; + + if (!rand_init) { + srand(time(NULL)); + rand_init = true; + } + + int rand_num = rand() % 1000000; + //makedir("./SVG"); + std::string prefix = "./SVG/"; + std::string suffix = ".svg"; + return prefix + tag + "_" + layer_nr_or_z /*+ "_" + std::to_string(rand_num)*/ + suffix; +} + PrintObjectSupportMaterial::PrintObjectSupportMaterial(const PrintObject *object, const SlicingParameters &slicing_params) : + m_object (object), m_print_config (&object->print()->config()), m_object_config (&object->config()), - m_slicing_params (slicing_params), - m_support_params (*object) + m_slicing_params (slicing_params) +{ + m_support_params.first_layer_flow = support_material_1st_layer_flow(object, float(slicing_params.first_print_layer_height)); + m_support_params.support_material_flow = support_material_flow(object, float(slicing_params.layer_height)); + m_support_params.support_material_interface_flow = support_material_interface_flow(object, float(slicing_params.layer_height)); + m_support_params.support_layer_height_min = 0.01; + + // Calculate a minimum support layer height as a minimum over all extruders, but not smaller than 10um. + m_support_params.support_layer_height_min = 1000000.; + for (auto lh : m_print_config->min_layer_height.values) + m_support_params.support_layer_height_min = std::min(m_support_params.support_layer_height_min, std::max(0.01, lh)); + for (auto layer : m_object->layers()) + m_support_params.support_layer_height_min = std::min(m_support_params.support_layer_height_min, std::max(0.01, layer->height)); + + if (m_object_config->support_interface_top_layers.value == 0) { + // No interface layers allowed, print everything with the base support pattern. + m_support_params.support_material_interface_flow = m_support_params.support_material_flow; + } + + // Evaluate the XY gap between the object outer perimeters and the support structures. + // Evaluate the XY gap between the object outer perimeters and the support structures. + coordf_t external_perimeter_width = 0.; + coordf_t bridge_flow = 0; + for (size_t region_id = 0; region_id < object->num_printing_regions(); ++ region_id) { + const PrintRegion ®ion = object->printing_region(region_id); + external_perimeter_width = std::max(external_perimeter_width, coordf_t(region.flow(*object, frExternalPerimeter, slicing_params.layer_height).width())); + bridge_flow += region.config().bridge_flow; + } + m_support_params.gap_xy = m_object_config->support_object_xy_distance.value; + bridge_flow /= object->num_printing_regions(); + + m_support_params.support_material_bottom_interface_flow = m_slicing_params.soluble_interface || ! m_object_config->thick_bridges ? + m_support_params.support_material_interface_flow.with_flow_ratio(bridge_flow) : + Flow::bridging_flow(bridge_flow * m_support_params.support_material_interface_flow.nozzle_diameter(), m_support_params.support_material_interface_flow.nozzle_diameter()); + + m_support_params.can_merge_support_regions = m_object_config->support_filament.value == m_object_config->support_interface_filament.value; + if (!m_support_params.can_merge_support_regions && (m_object_config->support_filament.value == 0 || m_object_config->support_interface_filament.value == 0)) { + // One of the support extruders is of "don't care" type. + auto object_extruders = m_object->object_extruders(); + if (object_extruders.size() == 1 && + *object_extruders.begin() == std::max(m_object_config->support_filament.value, m_object_config->support_interface_filament.value)) + // Object is printed with the same extruder as the support. + m_support_params.can_merge_support_regions = true; + } + + + m_support_params.base_angle = Geometry::deg2rad(float(m_object_config->support_angle.value)); + m_support_params.interface_angle = Geometry::deg2rad(float(m_object_config->support_angle.value + 90.)); + m_support_params.interface_spacing = m_object_config->support_interface_spacing.value + m_support_params.support_material_interface_flow.spacing(); + m_support_params.interface_density = std::min(1., m_support_params.support_material_interface_flow.spacing() / m_support_params.interface_spacing); + m_support_params.support_spacing = m_object_config->support_base_pattern_spacing.value + m_support_params.support_material_flow.spacing(); + m_support_params.support_density = std::min(1., m_support_params.support_material_flow.spacing() / m_support_params.support_spacing); + if (m_object_config->support_interface_top_layers.value == 0) { + // No interface layers allowed, print everything with the base support pattern. + m_support_params.interface_spacing = m_support_params.support_spacing; + m_support_params.interface_density = m_support_params.support_density; + } + + SupportMaterialPattern support_pattern = m_object_config->support_base_pattern; + m_support_params.with_sheath = support_with_sheath; + m_support_params.base_fill_pattern = + support_pattern == smpHoneycomb ? ipHoneycomb : + m_support_params.support_density > 0.95 || m_support_params.with_sheath ? ipRectilinear : ipSupportBase; + m_support_params.interface_fill_pattern = (m_support_params.interface_density > 0.95 ? ipRectilinear : ipSupportBase); + if (m_object_config->support_interface_pattern == smipGrid) + m_support_params.contact_fill_pattern = ipGrid; + else if (m_object_config->support_interface_pattern == smipRectilinearInterlaced) + m_support_params.contact_fill_pattern = ipRectilinear; + else + m_support_params.contact_fill_pattern = + (m_object_config->support_interface_pattern == smipAuto && m_slicing_params.soluble_interface) || + m_object_config->support_interface_pattern == smipConcentric ? + ipConcentric : + (m_support_params.interface_density > 0.95 ? ipRectilinear : ipSupportBase); +} + +// Using the std::deque as an allocator. +inline PrintObjectSupportMaterial::MyLayer& layer_allocate( + std::deque &layer_storage, + PrintObjectSupportMaterial::SupporLayerType layer_type) +{ + layer_storage.push_back(PrintObjectSupportMaterial::MyLayer()); + layer_storage.back().layer_type = layer_type; + return layer_storage.back(); +} + +inline PrintObjectSupportMaterial::MyLayer& layer_allocate( + std::deque &layer_storage, + tbb::spin_mutex &layer_storage_mutex, + PrintObjectSupportMaterial::SupporLayerType layer_type) +{ + layer_storage_mutex.lock(); + layer_storage.push_back(PrintObjectSupportMaterial::MyLayer()); + PrintObjectSupportMaterial::MyLayer *layer_new = &layer_storage.back(); + layer_storage_mutex.unlock(); + layer_new->layer_type = layer_type; + return *layer_new; +} + +inline void layers_append(PrintObjectSupportMaterial::MyLayersPtr &dst, const PrintObjectSupportMaterial::MyLayersPtr &src) { + dst.insert(dst.end(), src.begin(), src.end()); } +// Support layer that is covered by some form of dense interface. +static constexpr const std::initializer_list support_types_interface { + PrintObjectSupportMaterial::sltRaftInterface, PrintObjectSupportMaterial::sltBottomContact, PrintObjectSupportMaterial::sltBottomInterface, PrintObjectSupportMaterial::sltTopContact, PrintObjectSupportMaterial::sltTopInterface +}; + void PrintObjectSupportMaterial::generate(PrintObject &object) { BOOST_LOG_TRIVIAL(info) << "Support generator - Start"; @@ -246,7 +468,7 @@ void PrintObjectSupportMaterial::generate(PrintObject &object) // Layer instances will be allocated by std::deque and they will be kept until the end of this function call. // The layers will be referenced by various LayersPtr (of type std::vector) - SupportGeneratorLayerStorage layer_storage; + MyLayerStorage layer_storage; BOOST_LOG_TRIVIAL(info) << "Support generator - Creating top contacts"; @@ -259,15 +481,18 @@ void PrintObjectSupportMaterial::generate(PrintObject &object) // should the support material expose to the object in order to guarantee // that it will be effective, regardless of how it's built below. // If raft is to be generated, the 1st top_contact layer will contain the 1st object layer silhouette without holes. - SupportGeneratorLayersPtr top_contacts = this->top_contact_layers(object, buildplate_covered, layer_storage); + MyLayersPtr top_contacts = this->top_contact_layers(object, buildplate_covered, layer_storage); if (top_contacts.empty()) // Nothing is supported, no supports are generated. return; + if (object.print()->canceled()) + return; + #ifdef SLIC3R_DEBUG static int iRun = 0; iRun ++; - for (const SupportGeneratorLayer *layer : top_contacts) + for (const MyLayer *layer : top_contacts) Slic3r::SVG::export_expolygons( debug_out_path("support-top-contacts-%d-%lf.svg", iRun, layer->print_z), union_ex(layer->polygons)); @@ -280,10 +505,13 @@ void PrintObjectSupportMaterial::generate(PrintObject &object) // layer_support_areas contains the per object layer support areas. These per object layer support areas // may get merged and trimmed by this->generate_base_layers() if the support layers are not synchronized with object layers. std::vector layer_support_areas; - SupportGeneratorLayersPtr bottom_contacts = this->bottom_contact_layers_and_layer_support_areas( + MyLayersPtr bottom_contacts = this->bottom_contact_layers_and_layer_support_areas( object, top_contacts, buildplate_covered, layer_storage, layer_support_areas); + if (object.print()->canceled()) + return; + #ifdef SLIC3R_DEBUG for (size_t layer_id = 0; layer_id < object.layers().size(); ++ layer_id) Slic3r::SVG::export_expolygons( @@ -298,13 +526,13 @@ void PrintObjectSupportMaterial::generate(PrintObject &object) // The layers may or may not be synchronized with the object layers, depending on the configuration. // For example, a single nozzle multi material printing will need to generate a waste tower, which in turn // wastes less material, if there are as little tool changes as possible. - SupportGeneratorLayersPtr intermediate_layers = this->raft_and_intermediate_support_layers( + MyLayersPtr intermediate_layers = this->raft_and_intermediate_support_layers( object, bottom_contacts, top_contacts, layer_storage); this->trim_support_layers_by_object(object, top_contacts, m_slicing_params.gap_support_object, m_slicing_params.gap_object_support, m_support_params.gap_xy); #ifdef SLIC3R_DEBUG - for (const SupportGeneratorLayer *layer : top_contacts) + for (const MyLayer *layer : top_contacts) Slic3r::SVG::export_expolygons( debug_out_path("support-top-contacts-trimmed-by-object-%d-%lf.svg", iRun, layer->print_z), union_ex(layer->polygons)); @@ -316,7 +544,7 @@ void PrintObjectSupportMaterial::generate(PrintObject &object) this->generate_base_layers(object, bottom_contacts, top_contacts, intermediate_layers, layer_support_areas); #ifdef SLIC3R_DEBUG - for (SupportGeneratorLayersPtr::const_iterator it = intermediate_layers.begin(); it != intermediate_layers.end(); ++ it) + for (MyLayersPtr::const_iterator it = intermediate_layers.begin(); it != intermediate_layers.end(); ++ it) Slic3r::SVG::export_expolygons( debug_out_path("support-base-layers-%d-%lf.svg", iRun, (*it)->print_z), union_ex((*it)->polygons)); @@ -335,23 +563,24 @@ void PrintObjectSupportMaterial::generate(PrintObject &object) // Propagate top / bottom contact layers to generate interface layers // and base interface layers (for soluble interface / non souble base only) - SupportGeneratorLayersPtr empty_layers; - auto [interface_layers, base_interface_layers] = FFFSupport::generate_interface_layers( - *m_object_config, m_support_params, bottom_contacts, top_contacts, empty_layers, empty_layers, intermediate_layers, layer_storage); + auto [interface_layers, base_interface_layers] = this->generate_interface_layers(bottom_contacts, top_contacts, intermediate_layers, layer_storage); BOOST_LOG_TRIVIAL(info) << "Support generator - Creating raft"; // If raft is to be generated, the 1st top_contact layer will contain the 1st object layer silhouette with holes filled. // There is also a 1st intermediate layer containing bases of support columns. // Inflate the bases of the support columns and create the raft base under the object. - SupportGeneratorLayersPtr raft_layers = FFFSupport::generate_raft_base(object, m_support_params, m_slicing_params, top_contacts, interface_layers, base_interface_layers, intermediate_layers, layer_storage); + MyLayersPtr raft_layers = this->generate_raft_base(object, top_contacts, interface_layers, base_interface_layers, intermediate_layers, layer_storage); + + if (object.print()->canceled()) + return; #ifdef SLIC3R_DEBUG - for (const SupportGeneratorLayer *l : interface_layers) + for (const MyLayer *l : interface_layers) Slic3r::SVG::export_expolygons( debug_out_path("support-interface-layers-%d-%lf.svg", iRun, l->print_z), union_ex(l->polygons)); - for (const SupportGeneratorLayer *l : base_interface_layers) + for (const MyLayer *l : base_interface_layers) Slic3r::SVG::export_expolygons( debug_out_path("support-base-interface-layers-%d-%lf.svg", iRun, l->print_z), union_ex(l->polygons)); @@ -374,10 +603,94 @@ void PrintObjectSupportMaterial::generate(PrintObject &object) // intermediate_layers.clear(); // interface_layers.clear(); -#ifdef SLIC3R_DEBUG - SupportGeneratorLayersPtr layers_sorted = -#endif // SLIC3R_DEBUG - generate_support_layers(object, raft_layers, bottom_contacts, top_contacts, intermediate_layers, interface_layers, base_interface_layers); + // Install support layers into the object. + // A support layer installed on a PrintObject has a unique print_z. + MyLayersPtr layers_sorted; + layers_sorted.reserve(raft_layers.size() + bottom_contacts.size() + top_contacts.size() + intermediate_layers.size() + interface_layers.size() + base_interface_layers.size()); + layers_append(layers_sorted, raft_layers); + layers_append(layers_sorted, bottom_contacts); + layers_append(layers_sorted, top_contacts); + layers_append(layers_sorted, intermediate_layers); + layers_append(layers_sorted, interface_layers); + layers_append(layers_sorted, base_interface_layers); + // Sort the layers lexicographically by a raising print_z and a decreasing height. + std::sort(layers_sorted.begin(), layers_sorted.end(), [](auto *l1, auto *l2) { return *l1 < *l2; }); + + // BBS: MusangKing - erase mini layer heights (< 0.08mm) arised by top/bottom_z_distance & top_contacts under variable layer height + if (this->synchronize_layers() && !object.slicing_parameters().soluble_interface) { + auto thres = m_support_params.support_layer_height_min - EPSILON; + for (size_t i = 1; i < layers_sorted.size() - 1; ++i) { + auto& lowr = layers_sorted[i - 1]; + auto& curr = layers_sorted[i]; + auto& higr = layers_sorted[i + 1]; + // "Rounding" suspicious top/bottom contacts + if (curr->layer_type == sltTopContact || curr->layer_type == sltBottomContact) { + // Check adjacent-layer print_z diffs + coordf_t height_low = curr->print_z - lowr->print_z; + coordf_t height_high = higr->print_z - curr->print_z; + if (height_low < thres || height_high < thres) { + // Mark to-be-deleted layer as Unknown type + curr->layer_type = sltUnknown; + } + } + } + // Retains the order + layers_sorted.erase(std::remove_if(layers_sorted.begin(), layers_sorted.end(), [](MyLayer* l) {return l->layer_type == sltUnknown; }), layers_sorted.end()); + } + + int layer_id = 0; + int layer_id_interface = 0; + assert(object.support_layers().empty()); + for (size_t i = 0; i < layers_sorted.size();) { + // Find the last layer with roughly the same print_z, find the minimum layer height of all. + // Due to the floating point inaccuracies, the print_z may not be the same even if in theory they should. + size_t j = i + 1; + coordf_t zmax = layers_sorted[i]->print_z + EPSILON; + for (; j < layers_sorted.size() && layers_sorted[j]->print_z <= zmax; ++j) ; + // Assign an average print_z to the set of layers with nearly equal print_z. + coordf_t zavg = 0.5 * (layers_sorted[i]->print_z + layers_sorted[j - 1]->print_z); + coordf_t height_min = layers_sorted[i]->height; + bool empty = true; + // For snug supports, layers where the direction of the support interface shall change are accounted for. + size_t num_interfaces = 0; + size_t num_top_contacts = 0; + double top_contact_bottom_z = 0; + for (size_t u = i; u < j; ++u) { + MyLayer &layer = *layers_sorted[u]; + if (! layer.polygons.empty()) { + empty = false; + num_interfaces += one_of(layer.layer_type, support_types_interface); + if (layer.layer_type == sltTopContact) { + ++ num_top_contacts; + assert(num_top_contacts <= 1); + // All top contact layers sharing this print_z shall also share bottom_z. + //assert(num_top_contacts == 1 || (top_contact_bottom_z - layer.bottom_z) < EPSILON); + top_contact_bottom_z = layer.bottom_z; + } + } + layer.print_z = zavg; + height_min = std::min(height_min, layer.height); + } + if (! empty) { + // Here the upper_layer and lower_layer pointers are left to null at the support layers, + // as they are never used. These pointers are candidates for removal. + bool this_layer_contacts_only = num_top_contacts > 0 && num_top_contacts == num_interfaces; + size_t this_layer_id_interface = layer_id_interface; + if (this_layer_contacts_only) { + // Find a supporting layer for its interface ID. + for (auto it = object.support_layers().rbegin(); it != object.support_layers().rend(); ++ it) + if (const SupportLayer &other_layer = **it; std::abs(other_layer.print_z - top_contact_bottom_z) < EPSILON) { + // other_layer supports this top contact layer. Assign a different support interface direction to this layer + // from the layer that supports it. + this_layer_id_interface = other_layer.interface_id() + 1; + } + } + object.add_support_layer(layer_id ++, this_layer_id_interface, height_min, zavg); + if (num_interfaces && ! this_layer_contacts_only) + ++ layer_id_interface; + } + i = j; + } BOOST_LOG_TRIVIAL(info) << "Support generator - Generating tool paths"; @@ -389,7 +702,7 @@ void PrintObjectSupportMaterial::generate(PrintObject &object) // Due to the floating point inaccuracies, the print_z may not be the same even if in theory they should. int j = i + 1; coordf_t zmax = layers_sorted[i]->print_z + EPSILON; - bool empty = layers_sorted[i]->polygons.empty(); + bool empty = true; for (; j < layers_sorted.size() && layers_sorted[j]->print_z <= zmax; ++j) if (!layers_sorted[j]->polygons.empty()) empty = false; @@ -408,8 +721,20 @@ void PrintObjectSupportMaterial::generate(PrintObject &object) } #endif /* SLIC3R_DEBUG */ +#if 0 // #ifdef SLIC3R_DEBUG + // check bounds + std::ofstream out; + out.open("./SVG/ns_support_layers.txt"); + if (out.is_open()) { + out << "### Support Layers ###" << std::endl; + for (auto& i : object.support_layers()) { + out << i->print_z << std::endl; + } + } +#endif /* SLIC3R_DEBUG */ + // Generate the actual toolpaths and save them into each layer. - generate_support_toolpaths(object.support_layers(), *m_object_config, m_support_params, m_slicing_params, raft_layers, bottom_contacts, top_contacts, intermediate_layers, interface_layers, base_interface_layers); + this->generate_toolpaths(object.support_layers(), raft_layers, bottom_contacts, top_contacts, intermediate_layers, interface_layers, base_interface_layers); #ifdef SLIC3R_DEBUG { @@ -419,7 +744,7 @@ void PrintObjectSupportMaterial::generate(PrintObject &object) // Due to the floating point inaccuracies, the print_z may not be the same even if in theory they should. int j = i + 1; coordf_t zmax = layers_sorted[i]->print_z + EPSILON; - bool empty = layers_sorted[i]->polygons.empty(); + bool empty = true; for (; j < layers_sorted.size() && layers_sorted[j]->print_z <= zmax; ++j) if (! layers_sorted[j]->polygons.empty()) empty = false; @@ -477,8 +802,8 @@ struct SupportGridParams { grid_resolution(object_config.support_base_pattern_spacing.value + support_material_flow.spacing()), support_angle(Geometry::deg2rad(object_config.support_angle.value)), extrusion_width(support_material_flow.spacing()), - // support_material_closing_radius(object_config.support_material_closing_radius.value), - support_material_closing_radius(2.0), + //support_closing_radius(object_config.support_closing_radius.value), + support_closing_radius(2.0), expansion_to_slice(coord_t(support_material_flow.scaled_spacing() / 2 + 5)), expansion_to_propagate(-3) {} @@ -486,7 +811,7 @@ struct SupportGridParams { double grid_resolution; double support_angle; double extrusion_width; - double support_material_closing_radius; + double support_closing_radius; coord_t expansion_to_slice; coord_t expansion_to_propagate; }; @@ -504,8 +829,9 @@ class SupportGridPattern m_support_polygons(support_polygons), m_trimming_polygons(trimming_polygons), m_support_spacing(params.grid_resolution), m_support_angle(params.support_angle), m_extrusion_width(params.extrusion_width), - m_support_material_closing_radius(params.support_material_closing_radius) + m_support_material_closing_radius(params.support_closing_radius) { + if (m_style != smsSnug) m_style = smsGrid; switch (m_style) { case smsGrid: { @@ -671,31 +997,24 @@ class SupportGridPattern if (!support_polygons_simplified.empty()) bbox.merge(get_extents(support_polygons_simplified)); SVG svg(debug_out_path("extract_support_from_grid_trimmed-%s-%d-%d-%lf.svg", step_name, iRun, layer_id, print_z).c_str(), bbox); - svg.draw(union_ex(support_polygons_simplified), "gray", 0.25f); - svg.draw(islands, "red", 0.5f); - svg.draw(union_ex(out), "green", 0.5f); - svg.draw(union_ex(*m_support_polygons), "blue", 0.5f); - svg.draw_outline(islands, "red", "red", scale_(0.05)); - svg.draw_outline(union_ex(out), "green", "green", scale_(0.05)); - svg.draw_outline(union_ex(*m_support_polygons), "blue", "blue", scale_(0.05)); - for (const Point &pt : samples) - svg.draw(pt, "black", coord_t(scale_(0.15))); - svg.Close(); + if (svg.is_opened()) { + svg.draw(union_ex(support_polygons_simplified), "gray", 0.25f); + svg.draw(islands, "red", 0.5f); + svg.draw(union_ex(out), "green", 0.5f); + svg.draw(union_ex(*m_support_polygons), "blue", 0.5f); + svg.draw_outline(islands, "red", "red", scale_(0.05)); + svg.draw_outline(union_ex(out), "green", "green", scale_(0.05)); + svg.draw_outline(union_ex(*m_support_polygons), "blue", "blue", scale_(0.05)); + for (const Point& pt : samples) + svg.draw(pt, "black", coord_t(scale_(0.15))); + svg.Close(); + } #endif /* SLIC3R_DEBUG */ if (m_support_angle != 0.) polygons_rotate(out, m_support_angle); return out; } - case smsTreeSlim: - case smsTreeStrong: - case smsTreeHybrid: - - // Orca: use organic as default - case smsDefault: - case smsOrganic: -// assert(false); - [[fallthrough]]; case smsSnug: // Merge the support polygons by applying morphological closing and inwards smoothing. auto closing_distance = scaled(m_support_material_closing_radius); @@ -981,7 +1300,7 @@ namespace SupportMaterialInternal { static inline bool has_bridging_perimeters(const ExtrusionLoop &loop) { for (const ExtrusionPath &ep : loop.paths) - if (ep.role() == ExtrusionRole::erOverhangPerimeter && ! ep.polyline.empty()) + if (ep.role() == erOverhangPerimeter && ! ep.polyline.empty()) return int(ep.size()) >= (ep.is_closed() ? 3 : 2); return false; } @@ -1007,7 +1326,7 @@ namespace SupportMaterialInternal { for (const ExtrusionEntity *ee2 : static_cast(ee)->entities) { assert(! ee2->is_collection()); assert(! ee2->is_loop()); - if (ee2->role() == ExtrusionRole::erBridgeInfill) + if (ee2->role() == erBridgeInfill || ee2->role() == erInternalBridgeInfill) return true; } } @@ -1028,7 +1347,7 @@ namespace SupportMaterialInternal { { assert(expansion_scaled >= 0.f); for (const ExtrusionPath &ep : loop.paths) - if (ep.role() == ExtrusionRole::erOverhangPerimeter && ! ep.polyline.empty()) { + if (ep.role() == erOverhangPerimeter && ! ep.polyline.empty()) { float exp = 0.5f * (float)scale_(ep.width) + expansion_scaled; if (ep.is_closed()) { if (ep.size() >= 3) { @@ -1063,6 +1382,89 @@ namespace SupportMaterialInternal { collect_bridging_perimeter_areas(*static_cast(ee), expansion_scaled, out); } } + + static void remove_bridges_from_contacts( + const PrintConfig &print_config, + const Layer &lower_layer, + const Polygons &lower_layer_polygons, + const LayerRegion &layerm, + float fw, + Polygons &contact_polygons) + { + // compute the area of bridging perimeters + Polygons bridges; + { + // Surface supporting this layer, expanded by 0.5 * nozzle_diameter, as we consider this kind of overhang to be sufficiently supported. + Polygons lower_grown_slices = expand(lower_layer_polygons, + //FIXME to mimic the decision in the perimeter generator, we should use half the external perimeter width. + 0.5f * float(scale_(print_config.nozzle_diameter.get_at(layerm.region().config().wall_filament-1))), + SUPPORT_SURFACES_OFFSET_PARAMETERS); + // Collect perimeters of this layer. + //FIXME split_at_first_point() could split a bridge mid-way + #if 0 + Polylines overhang_perimeters = layerm.perimeters.as_polylines(); + // workaround for Clipper bug, see Slic3r::Polygon::clip_as_polyline() + for (Polyline &polyline : overhang_perimeters) + polyline.points[0].x += 1; + // Trim the perimeters of this layer by the lower layer to get the unsupported pieces of perimeters. + overhang_perimeters = diff_pl(overhang_perimeters, lower_grown_slices); + #else + Polylines overhang_perimeters = diff_pl(layerm.perimeters.as_polylines(), lower_grown_slices); + #endif + + // only consider straight overhangs + // only consider overhangs having endpoints inside layer's slices + // convert bridging polylines into polygons by inflating them with their thickness + // since we're dealing with bridges, we can't assume width is larger than spacing, + // so we take the largest value and also apply safety offset to be ensure no gaps + // are left in between + // BBS + const PrintObjectConfig& object_config = layerm.layer()->object()->config(); + Flow perimeter_bridge_flow = layerm.bridging_flow(frPerimeter, object_config.thick_bridges); + //FIXME one may want to use a maximum of bridging flow width and normal flow width, as the perimeters are calculated using the normal flow + // and then turned to bridging flow, thus their centerlines are derived from non-bridging flow and expanding them by a bridging flow + // may not expand them to the edge of their respective islands. + const float w = float(0.5 * std::max(perimeter_bridge_flow.scaled_width(), perimeter_bridge_flow.scaled_spacing())) + scaled(0.001); + for (Polyline &polyline : overhang_perimeters) + if (polyline.is_straight()) { + // This is a bridge + polyline.extend_start(fw); + polyline.extend_end(fw); + // Is the straight perimeter segment supported at both sides? + Point pts[2] = { polyline.first_point(), polyline.last_point() }; + bool supported[2] = { false, false }; + for (size_t i = 0; i < lower_layer.lslices.size() && ! (supported[0] && supported[1]); ++ i) + for (int j = 0; j < 2; ++ j) + if (! supported[j] && lower_layer.lslices_bboxes[i].contains(pts[j]) && lower_layer.lslices[i].contains(pts[j])) + supported[j] = true; + if (supported[0] && supported[1]) + // Offset a polyline into a thick line. + polygons_append(bridges, offset(polyline, w)); + } + bridges = union_(bridges); + } + // remove the entire bridges and only support the unsupported edges + //FIXME the brided regions are already collected as layerm.bridged. Use it? + for (const Surface &surface : layerm.fill_surfaces.surfaces) + if (surface.surface_type == stBottomBridge && surface.bridge_angle >= 0.0) + polygons_append(bridges, surface.expolygon); + //FIXME add the gap filled areas. Extrude the gaps with a bridge flow? + // Remove the unsupported ends of the bridges from the bridged areas. + //FIXME add supports at regular intervals to support long bridges! + bridges = diff(bridges, + // Offset unsupported edges into polygons. + offset(layerm.unsupported_bridge_edges, scale_(SUPPORT_MATERIAL_MARGIN), SUPPORT_SURFACES_OFFSET_PARAMETERS)); + // Remove bridged areas from the supported areas. + contact_polygons = diff(contact_polygons, bridges, ApplySafetyOffset::Yes); + + #ifdef SLIC3R_DEBUG + static int iRun = 0; + SVG::export_expolygons(debug_out_path("support-top-contacts-remove-bridges-run%d.svg", iRun ++), + { { { union_ex(offset(layerm.unsupported_bridge_edges, scale_(SUPPORT_MATERIAL_MARGIN), SUPPORT_SURFACES_OFFSET_PARAMETERS)) }, { "unsupported_bridge_edges", "orange", 0.5f } }, + { { union_ex(contact_polygons) }, { "contact_polygons", "blue", 0.5f } }, + { { union_ex(bridges) }, { "bridges", "red", "black", "", scaled(0.1f), 0.5f } } }); + #endif /* SLIC3R_DEBUG */ + } } std::vector PrintObjectSupportMaterial::buildplate_covered(const PrintObject &object) const @@ -1118,16 +1520,22 @@ struct SlicesMarginCache Polygons all_polygons; }; +// BBS +static const double length_thresh_well_supported = scale_(6); // min: 6mm +static const double area_thresh_well_supported = SQ(length_thresh_well_supported); // min: 6x6=36mm^2 +static const double sharp_tail_xy_gap = 0.2f; +static const double no_overlap_xy_gap = 0.2f; +static const double sharp_tail_max_support_height = 16.f; + // Tuple: overhang_polygons, contact_polygons, enforcer_polygons, no_interface_offset // no_interface_offset: minimum of external perimeter widths -static inline std::tuple detect_overhangs( +static inline ExPolygons detect_overhangs( const Layer &layer, const size_t layer_id, - const Polygons &lower_layer_polygons, + Polygons &lower_layer_polygons, const PrintConfig &print_config, const PrintObjectConfig &object_config, SupportAnnotations &annotations, - SlicesMarginCache &slices_margin, const double gap_xy #ifdef SLIC3R_DEBUG , size_t iRun @@ -1136,63 +1544,50 @@ static inline std::tuple detect_overhangs( { // Snug overhang polygons. Polygons overhang_polygons; - // Expanded for stability, trimmed by gap_xy. - Polygons contact_polygons; - // Enforcers projected to overhangs, trimmed - Polygons enforcer_polygons; - const bool support_auto = object_config.enable_support.value && is_auto(object_config.support_type.value); + // BBS. + const bool auto_normal_support = object_config.support_type.value == stNormalAuto; const bool buildplate_only = ! annotations.buildplate_covered.empty(); // If user specified a custom angle threshold, convert it to radians. // Zero means automatic overhang detection. // +1 makes the threshold inclusive double thresh_angle = object_config.support_threshold_angle.value > 0 ? object_config.support_threshold_angle.value + 1 : 0; thresh_angle = std::min(thresh_angle, 89.); // BBS should be smaller than 90 - const double threshold_rad = Geometry::deg2rad(thresh_angle); - float no_interface_offset = 0.f; + const double threshold_rad = Geometry::deg2rad(thresh_angle); + const coordf_t max_bridge_length = scale_(object_config.max_bridge_length.value); + const bool bridge_no_support = object_config.bridge_no_support.value; + const coordf_t xy_expansion = scale_(object_config.support_expansion.value); - if (layer_id == 0) + if (layer_id == 0) { - // This is the first object layer, so the object is being printed on a raft and - // we're here just to get the object footprint for the raft. -#if 0 - // The following line was filling excessive holes in the raft, see GH #430 - overhang_polygons = collect_slices_outer(layer); -#else // Don't fill in the holes. The user may apply a higher raft_expansion if one wants a better 1st layer adhesion. overhang_polygons = to_polygons(layer.lslices); -#endif - // Expand for better stability. - contact_polygons = object_config.raft_expansion.value > 0 ? expand(overhang_polygons, scaled(object_config.raft_expansion.value)) : overhang_polygons; + + for (auto& slice : layer.lslices) { + auto bbox_size = get_extents(slice).size(); + if (g_config_support_sharp_tails && + !(bbox_size.x() > length_thresh_well_supported && bbox_size.y() > length_thresh_well_supported)) + { + layer.sharp_tails.push_back(slice); + layer.sharp_tails_height.insert({ &slice, layer.height }); + } + } } else if (! layer.regions().empty()) { // Generate overhang / contact_polygons for non-raft layers. const Layer &lower_layer = *layer.lower_layer; - const bool has_enforcer = ! annotations.enforcers_layers.empty() && ! annotations.enforcers_layers[layer_id].empty(); - - // Cache support trimming polygons derived from lower layer polygons, possible merged with "on build plate only" trimming polygons. - auto slices_margin_update = - [&slices_margin, &lower_layer, &lower_layer_polygons, buildplate_only, has_enforcer, &annotations, layer_id] - (float slices_margin_offset, float no_interface_offset) { - if (slices_margin.offset != slices_margin_offset) { - slices_margin.offset = slices_margin_offset; - slices_margin.polygons = (slices_margin_offset == 0.f) ? - lower_layer_polygons : - // What is the purpose of no_interface_offset? Likely to not trim the contact layer by lower layer regions that are too thin to extrude? - offset2(lower_layer.lslices, -no_interface_offset * 0.5f, slices_margin_offset + no_interface_offset * 0.5f, SUPPORT_SURFACES_OFFSET_PARAMETERS); - if (buildplate_only && !annotations.buildplate_covered[layer_id].empty()) { - if (has_enforcer) - // Make a backup of trimming polygons before enforcing "on build plate only". - slices_margin.all_polygons = slices_margin.polygons; - // Trim the inflated contact surfaces by the top surfaces as well. - slices_margin.polygons = union_(slices_margin.polygons, annotations.buildplate_covered[layer_id]); - } + const bool has_enforcer = !annotations.enforcers_layers.empty() && !annotations.enforcers_layers[layer_id].empty(); + // Can't directly use lower_layer.lslices, or we'll miss some very sharp tails. + // Filter out areas whose diameter that is smaller than extrusion_width. Do not use offset2() for this purpose! + // FIXME if there are multiple regions with different extrusion width, the following code may not be right. + float fw = float(layer.regions().front()->flow(frExternalPerimeter).scaled_width()); + ExPolygons lower_layer_expolys; + for (const ExPolygon& expoly : lower_layer.lslices) { + if (!offset_ex(expoly, -fw / 2).empty()) { + lower_layer_expolys.emplace_back(expoly); } - }; - - no_interface_offset = std::accumulate(layer.regions().begin(), layer.regions().end(), FLT_MAX, - [](float acc, const LayerRegion *layerm) { return std::min(acc, float(layerm->flow(frExternalPerimeter).scaled_width())); }); + } float lower_layer_offset = 0; for (LayerRegion *layerm : layer.regions()) { @@ -1219,26 +1614,12 @@ static inline std::tuple detect_overhangs( // This step is done before the contact surface is calculated by growing the overhang region. diff_polygons = diff(diff_polygons, annotations.buildplate_covered[layer_id]); } - } else if (support_auto) { + } else if (auto_normal_support) { // Get the regions needing a suport, collapse very tiny spots. //FIXME cache the lower layer offset if this layer has multiple regions. -#if 0 - //FIXME this solution will trigger stupid supports for sharp corners, see GH #4874 - diff_polygons = opening( - diff(layerm_polygons, - // Likely filtering out thin regions from the lower layer, that will not be covered by perimeters, thus they - // are not supporting this layer. - // However this may lead to a situation where regions at the current layer that are narrow thus not extrudable will generate unnecessary supports. - // For example, see GH issue #3094 - opening(lower_layer_polygons, 0.5f * fw, lower_layer_offset + 0.5f * fw, SUPPORT_SURFACES_OFFSET_PARAMETERS)), - //FIXME This opening is targeted to reduce very thin regions to support, but it may lead to - // no support at all for not so steep overhangs. - 0.1f * fw); -#else diff_polygons = diff(layerm_polygons, expand(lower_layer_polygons, lower_layer_offset, SUPPORT_SURFACES_OFFSET_PARAMETERS)); -#endif if (buildplate_only && ! annotations.buildplate_covered[layer_id].empty()) { // Don't support overhangs above the top surfaces. // This step is done before the contact surface is calculated by growing the overhang region. @@ -1248,58 +1629,160 @@ static inline std::tuple detect_overhangs( // Offset the support regions back to a full overhang, restrict them to the full overhang. // This is done to increase size of the supporting columns below, as they are calculated by // propagating these contact surfaces downwards. - diff_polygons = diff( - intersection(expand(diff_polygons, lower_layer_offset, SUPPORT_SURFACES_OFFSET_PARAMETERS), layerm_polygons), - lower_layer_polygons); + diff_polygons = diff(intersection(expand(diff_polygons, lower_layer_offset, SUPPORT_SURFACES_OFFSET_PARAMETERS), layerm_polygons), lower_layer_polygons); + if (xy_expansion != 0) { diff_polygons = expand(diff_polygons, xy_expansion, SUPPORT_SURFACES_OFFSET_PARAMETERS); } } //FIXME add user defined filtering here based on minimal area or minimum radius or whatever. + + // BBS + if (g_config_support_sharp_tails) { + for (ExPolygon& expoly : layerm->raw_slices) { + if (offset_ex(expoly, -0.5 * fw).empty()) continue; + bool is_sharp_tail = false; + float accum_height = layer.height; + + // 1. nothing below + // Check whether this is a sharp tail region. + // Should use lower_layer_expolys without any offset. Otherwise, it may missing sharp tails near the main body. + if (!overlaps(offset_ex(expoly, 0.5 * fw), lower_layer_expolys)) { + is_sharp_tail = expoly.area() < area_thresh_well_supported && !offset_ex(expoly, -0.1 * fw).empty(); + } + + if (is_sharp_tail) { + ExPolygons overhang = diff_ex({ expoly }, lower_layer_expolys); + layer.sharp_tails.push_back(expoly); + layer.sharp_tails_height.insert({ &expoly, accum_height }); + overhang = offset_ex(overhang, 0.05 * fw); + polygons_append(diff_polygons, to_polygons(overhang)); + } + } + } } if (diff_polygons.empty()) continue; // Apply the "support blockers". - if (! annotations.blockers_layers.empty() && ! annotations.blockers_layers[layer_id].empty()) { + if (!annotations.blockers_layers.empty() && !annotations.blockers_layers[layer_id].empty()) { // Expand the blocker a bit. Custom blockers produce strips // spanning just the projection between the two slices. // Subtracting them as they are may leave unwanted narrow // residues of diff_polygons that would then be supported. diff_polygons = diff(diff_polygons, - expand(union_(annotations.blockers_layers[layer_id]), float(1000.*SCALED_EPSILON))); + expand(union_(annotations.blockers_layers[layer_id]), float(1000. * SCALED_EPSILON))); } - #ifdef SLIC3R_DEBUG - { - ::Slic3r::SVG svg(debug_out_path("support-top-contacts-raw-run%d-layer%d-region%d.svg", - iRun, layer_id, - std::find_if(layer.regions().begin(), layer.regions().end(), [layerm](const LayerRegion* other){return other == layerm;}) - layer.regions().begin()), - get_extents(diff_polygons)); - Slic3r::ExPolygons expolys = union_ex(diff_polygons); - svg.draw(expolys); + if (bridge_no_support) { + //FIXME Expensive, potentially not precise enough. Misses gap fill extrusions, which bridge. + SupportMaterialInternal::remove_bridges_from_contacts( + print_config, lower_layer, lower_layer_polygons, *layerm, fw, diff_polygons); + } + + if (diff_polygons.empty() || offset(diff_polygons, -0.1 * fw).empty()) + continue; + + polygons_append(overhang_polygons, diff_polygons); + } // for each layer.region + } + + ExPolygons overhang_areas = union_ex(overhang_polygons); + // check cantilever + if (layer.lower_layer) { + for (ExPolygon& poly : overhang_areas) { + float fw = float(layer.regions().front()->flow(frExternalPerimeter).scaled_width()); + auto cluster_boundary_ex = intersection_ex(poly, offset_ex(layer.lower_layer->lslices, scale_(0.5))); + Polygons cluster_boundary = to_polygons(cluster_boundary_ex); + if (cluster_boundary.empty()) continue; + double dist_max = 0; + for (auto& pt : poly.contour.points) { + double dist_pt = std::numeric_limits::max(); + for (auto& ply : cluster_boundary) { + double d = ply.distance_to(pt); + dist_pt = std::min(dist_pt, d); + } + dist_max = std::max(dist_max, dist_pt); + } + if (dist_max > scale_(3)) { // is cantilever if the farmost point is larger than 3mm away from base + layer.cantilevers.emplace_back(poly); + } + } + } + + return overhang_areas; +} + +// Tuple: overhang_polygons, contact_polygons, enforcer_polygons, no_interface_offset +// no_interface_offset: minimum of external perimeter widths +static inline std::tuple detect_contacts( + const Layer& layer, + const size_t layer_id, + Polygons& overhang_polygons, + Polygons& lower_layer_polygons, + const PrintConfig& print_config, + const PrintObjectConfig& object_config, + SupportAnnotations& annotations, + SlicesMarginCache& slices_margin, + const double gap_xy +#ifdef SLIC3R_DEBUG + , size_t iRun +#endif // SLIC3R_DEBUG +) +{ + // Expanded for stability, trimmed by gap_xy. + Polygons contact_polygons; + // Enforcers projected to overhangs, trimmed + Polygons enforcer_polygons; + + // BBS. + const bool auto_normal_support = object_config.support_type.value == stNormalAuto; + const bool buildplate_only = !annotations.buildplate_covered.empty(); + float no_interface_offset = 0.f; + + if (layer_id == 0) + { + // Expand for better stability. + contact_polygons = object_config.raft_expansion.value > 0 ? expand(overhang_polygons, scaled(object_config.raft_expansion.value)) : overhang_polygons; + } + else if (!layer.regions().empty()) + { + // Generate overhang / contact_polygons for non-raft layers. + const Layer& lower_layer = *layer.lower_layer; + const bool has_enforcer = !annotations.enforcers_layers.empty() && !annotations.enforcers_layers[layer_id].empty(); + const ExPolygons& lower_layer_expolys = lower_layer.lslices; + const ExPolygons& lower_layer_sharptails = lower_layer.sharp_tails; + + // Cache support trimming polygons derived from lower layer polygons, possible merged with "on build plate only" trimming polygons. + auto slices_margin_update = + [&slices_margin, &layer, &lower_layer, &lower_layer_polygons, buildplate_only, has_enforcer, &annotations, layer_id] + (float slices_margin_offset, float no_interface_offset) { + if (slices_margin.offset != slices_margin_offset) { + slices_margin.offset = slices_margin_offset; + slices_margin.polygons = (slices_margin_offset == 0.f) ? + lower_layer_polygons : + // What is the purpose of no_interface_offset? Likely to not trim the contact layer by lower layer regions that are too thin to extrude? + offset2(lower_layer.lslices, -no_interface_offset * 0.5f, slices_margin_offset + no_interface_offset * 0.5f, SUPPORT_SURFACES_OFFSET_PARAMETERS); + if (buildplate_only && !annotations.buildplate_covered[layer_id].empty()) { + if (has_enforcer) + // Make a backup of trimming polygons before enforcing "on build plate only". + slices_margin.all_polygons = slices_margin.polygons; + // Trim the inflated contact surfaces by the top surfaces as well. + slices_margin.polygons = union_(slices_margin.polygons, annotations.buildplate_covered[layer_id]); + } } - #endif /* SLIC3R_DEBUG */ + }; + + no_interface_offset = std::accumulate(layer.regions().begin(), layer.regions().end(), FLT_MAX, + [](float acc, const LayerRegion* layerm) { return std::min(acc, float(layerm->flow(frExternalPerimeter).scaled_width())); }); - // if (object_config.dont_support_bridges) - // //FIXME Expensive, potentially not precise enough. Misses gap fill extrusions, which bridge. - // remove_bridges_from_contacts(print_config, lower_layer, *layerm, fw, diff_polygons); + float lower_layer_offset = 0; + for (LayerRegion* layerm : layer.regions()) { + Polygons layerm_polygons = to_polygons(layerm->slices.surfaces); + // Overhang polygons for this layer and region. + Polygons diff_polygons = intersection(overhang_polygons, layerm_polygons); if (diff_polygons.empty()) continue; - #ifdef SLIC3R_DEBUG - Slic3r::SVG::export_expolygons( - debug_out_path("support-top-contacts-filtered-run%d-layer%d-region%d-z%f.svg", - iRun, layer_id, - std::find_if(layer.regions().begin(), layer.regions().end(), [layerm](const LayerRegion* other){return other == layerm;}) - layer.regions().begin(), - layer.print_z), - union_ex(diff_polygons)); - #endif /* SLIC3R_DEBUG */ - - //FIXME the overhang_polygons are used to construct the support towers as well. - //if (this->has_contact_loops()) - // Store the exact contour of the overhang for the contact loops. - polygons_append(overhang_polygons, diff_polygons); - // Let's define the required contact area by using a max gap of half the upper // extrusion width and extending the area according to the configured margin. // We increment the area in steps because we don't want our support to overflow @@ -1330,44 +1813,45 @@ static inline std::tuple detect_overhangs( } // for each layer.region if (has_enforcer) - if (const Polygons &enforcer_polygons_src = annotations.enforcers_layers[layer_id]; ! enforcer_polygons_src.empty()) { + if (const Polygons& enforcer_polygons_src = annotations.enforcers_layers[layer_id]; !enforcer_polygons_src.empty()) { // Enforce supports (as if with 90 degrees of slope) for the regions covered by the enforcer meshes. - #ifdef SLIC3R_DEBUG +#ifdef SLIC3R_DEBUG ExPolygons enforcers_united = union_ex(enforcer_polygons_src); - #endif // SLIC3R_DEBUG +#endif // SLIC3R_DEBUG enforcer_polygons = diff(intersection(layer.lslices, enforcer_polygons_src), // Inflate just a tiny bit to avoid intersection of the overhang areas with the object. expand(lower_layer_polygons, 0.05f * no_interface_offset, SUPPORT_SURFACES_OFFSET_PARAMETERS)); - #ifdef SLIC3R_DEBUG +#ifdef SLIC3R_DEBUG SVG::export_expolygons(debug_out_path("support-top-contacts-enforcers-run%d-layer%d-z%f.svg", iRun, layer_id, layer.print_z), { { layer.lslices, { "layer.lslices", "gray", 0.2f } }, { { union_ex(lower_layer_polygons) }, { "lower_layer_polygons", "green", 0.5f } }, { enforcers_united, { "enforcers", "blue", 0.5f } }, { { union_safety_offset_ex(enforcer_polygons) }, { "new_contacts", "red", "black", "", scaled(0.1f), 0.5f } } }); - #endif /* SLIC3R_DEBUG */ - if (! enforcer_polygons.empty()) { +#endif /* SLIC3R_DEBUG */ + if (!enforcer_polygons.empty()) { polygons_append(overhang_polygons, enforcer_polygons); slices_margin_update(std::min(lower_layer_offset, float(scale_(gap_xy))), no_interface_offset); polygons_append(contact_polygons, diff(enforcer_polygons, slices_margin.all_polygons.empty() ? slices_margin.polygons : slices_margin.all_polygons)); } } - } + } - return std::make_tuple(std::move(overhang_polygons), std::move(contact_polygons), std::move(enforcer_polygons), no_interface_offset); + return std::make_tuple(std::move(contact_polygons), std::move(enforcer_polygons), no_interface_offset); } // Allocate one, possibly two support contact layers. // For "thick" overhangs, one support layer will be generated to support normal extrusions, the other to support the "thick" extrusions. -static inline std::pair new_contact_layer( +static inline std::pair new_contact_layer( const PrintConfig &print_config, const PrintObjectConfig &object_config, const SlicingParameters &slicing_params, const coordf_t support_layer_height_min, const Layer &layer, - SupportGeneratorLayerStorage &layer_storage) + std::deque &layer_storage, + tbb::spin_mutex &layer_storage_mutex) { double print_z, bottom_z, height; - SupportGeneratorLayer* bridging_layer = nullptr; + PrintObjectSupportMaterial::MyLayer* bridging_layer = nullptr; assert(layer.id() >= slicing_params.raft_layers()); size_t layer_id = layer.id() - slicing_params.raft_layers(); @@ -1385,15 +1869,15 @@ static inline std::pair new_cont bottom_z = (layer_id == 1) ? slicing_params.object_print_z_min : layer.lower_layer->lower_layer->print_z; } else { print_z = layer.bottom_z() - slicing_params.gap_support_object; - bottom_z = print_z; - height = 0.; + height = print_config.independent_support_layer_height ? 0. : layer.lower_layer->height/*object_config.layer_height*/; // BBS: need to consider adaptive layer heights + bottom_z = print_z - height; // Ignore this contact area if it's too low. // Don't want to print a layer below the first layer height as it may not stick well. //FIXME there may be a need for a single layer support, then one may decide to print it either as a bottom contact or a top contact // and it may actually make sense to do it with a thinner layer than the first layer height. if (print_z < slicing_params.first_print_layer_height - EPSILON) { // This contact layer is below the first layer height, therefore not printable. Don't support this surface. - return std::pair(nullptr, nullptr); + return std::pair(nullptr, nullptr); } const bool has_raft = slicing_params.raft_layers() > 1; const coordf_t min_print_z = has_raft ? slicing_params.raft_contact_top_z : slicing_params.first_print_layer_height; @@ -1415,6 +1899,9 @@ static inline std::pair new_cont for (const LayerRegion* region : layer.regions()) bridging_height += region->region().bridging_height_avg(print_config); bridging_height /= coordf_t(layer.regions().size()); + // BBS: align bridging height + if (!print_config.independent_support_layer_height) + bridging_height = std::ceil(bridging_height / object_config.layer_height - EPSILON) * object_config.layer_height; coordf_t bridging_print_z = layer.print_z - bridging_height - slicing_params.gap_support_object; if (bridging_print_z >= min_print_z) { // Not below the first layer height means this layer is printable. @@ -1424,23 +1911,25 @@ static inline std::pair new_cont } if (bridging_print_z < print_z - EPSILON) { // Allocate the new layer. - bridging_layer = &layer_storage.allocate(SupporLayerType::TopContact); + bridging_layer = &layer_allocate(layer_storage, layer_storage_mutex, PrintObjectSupportMaterial::sltTopContact); bridging_layer->idx_object_layer_above = layer_id; bridging_layer->print_z = bridging_print_z; if (bridging_print_z == slicing_params.first_print_layer_height) { bridging_layer->bottom_z = 0; bridging_layer->height = slicing_params.first_print_layer_height; } else { + // BBS: if independent_support_layer_height is not enabled, the support layer_height should be the same as layer height. + // Note that for this case, adaptive layer height must be disabled. + bridging_layer->height = print_config.independent_support_layer_height ? 0. : object_config.layer_height; // Don't know the height yet. - bridging_layer->bottom_z = bridging_print_z; - bridging_layer->height = 0; + bridging_layer->bottom_z = bridging_print_z - bridging_layer->height; } } } } } - SupportGeneratorLayer &new_layer = layer_storage.allocate(SupporLayerType::TopContact); + PrintObjectSupportMaterial::MyLayer &new_layer = layer_allocate(layer_storage, layer_storage_mutex, PrintObjectSupportMaterial::sltTopContact); new_layer.idx_object_layer_above = layer_id; new_layer.print_z = print_z; new_layer.bottom_z = bottom_z; @@ -1449,7 +1938,7 @@ static inline std::pair new_cont } static inline void fill_contact_layer( - SupportGeneratorLayer &new_layer, + PrintObjectSupportMaterial::MyLayer &new_layer, size_t layer_id, const SlicingParameters &slicing_params, const PrintObjectConfig &object_config, @@ -1486,7 +1975,7 @@ static inline void fill_contact_layer( #endif // SLIC3R_DEBUG )); // 2) infill polygons, expand them by half the extrusion width + a tiny bit of extra. - bool reduce_interfaces = object_config.support_style.value == smsGrid && layer_id > 0 && !slicing_params.soluble_interface; + bool reduce_interfaces = object_config.support_style.value != smsSnug && layer_id > 0 && !slicing_params.soluble_interface; if (reduce_interfaces) { // Reduce the amount of dense interfaces: Do not generate dense interfaces below overhangs with 60% overhang of the extrusions. Polygons dense_interface_polygons = diff(overhang_polygons, lower_layer_polygons_for_dense_interface()); @@ -1601,10 +2090,10 @@ static inline void fill_contact_layer( // Merge close contact layers conservatively: If two layers are closer than the minimum allowed print layer height (the min_layer_height parameter), // the top contact layer is merged into the bottom contact layer. -static void merge_contact_layers(const SlicingParameters &slicing_params, double support_layer_height_min, SupportGeneratorLayersPtr &layers) +static void merge_contact_layers(const SlicingParameters &slicing_params, double support_layer_height_min, PrintObjectSupportMaterial::MyLayersPtr &layers) { // Sort the layers, as one layer may produce bridging and non-bridging contact layers with different print_z. - std::sort(layers.begin(), layers.end(), [](const SupportGeneratorLayer *l1, const SupportGeneratorLayer *l2) { return l1->print_z < l2->print_z; }); + std::sort(layers.begin(), layers.end(), [](const PrintObjectSupportMaterial::MyLayer *l1, const PrintObjectSupportMaterial::MyLayer *l2) { return l1->print_z < l2->print_z; }); int i = 0; int k = 0; @@ -1614,7 +2103,7 @@ static void merge_contact_layers(const SlicingParameters &slicing_params, double for (; j < (int)layers.size() && layers[j]->print_z < slicing_params.first_print_layer_height + support_layer_height_min - EPSILON; ++ j); if (j > 0) { // Merge the layers layers (0) to (j - 1) into the layers[0]. - SupportGeneratorLayer &dst = *layers.front(); + PrintObjectSupportMaterial::MyLayer &dst = *layers.front(); for (int u = 1; u < j; ++ u) dst.merge(std::move(*layers[u])); // Snap the first layer to the 1st layer height. @@ -1632,7 +2121,7 @@ static void merge_contact_layers(const SlicingParameters &slicing_params, double for (; j < (int)layers.size() && layers[j]->print_z < zmax; ++ j) ; if (i + 1 < j) { // Merge the layers layers (i + 1) to (j - 1) into the layers[i]. - SupportGeneratorLayer &dst = *layers[i]; + PrintObjectSupportMaterial::MyLayer &dst = *layers[i]; for (int u = i + 1; u < j; ++ u) dst.merge(std::move(*layers[u])); } @@ -1644,11 +2133,80 @@ static void merge_contact_layers(const SlicingParameters &slicing_params, double layers.erase(layers.begin() + k, layers.end()); } + +struct OverhangCluster { + std::map> layer_overhangs; + ExPolygons merged_overhangs_dilated; + int min_layer = 1e7; + int max_layer = 0; + coordf_t offset_scaled = 0; + bool is_cantilever = false; + bool is_sharp_tail = false; + bool is_small_overhang = false; + + OverhangCluster(ExPolygon* overhang, int layer_nr, coordf_t offset_scaled) { + this->offset_scaled = offset_scaled; + insert(overhang, layer_nr); + } + + void insert(ExPolygon* overhang_new, int layer_nr) { + if (layer_overhangs.find(layer_nr) != layer_overhangs.end()) { + layer_overhangs[layer_nr].push_back(overhang_new); + } + else { + layer_overhangs.emplace(layer_nr, std::vector{ overhang_new }); + } + ExPolygons overhang_dilated = offset_scaled > EPSILON ? offset_ex(*overhang_new, offset_scaled) : ExPolygons{ *overhang_new }; + if (!overhang_dilated.empty()) + merged_overhangs_dilated = union_ex(merged_overhangs_dilated, overhang_dilated); + min_layer = std::min(min_layer, layer_nr); + max_layer = std::max(max_layer, layer_nr); + } + + int height() { + return max_layer - min_layer + 1; + } + + bool intersects(const ExPolygon& overhang_new, int layer_nr) { + if (layer_nr < 1) + return false; + + //auto it = layer_overhangs.find(layer_nr - 1); + //if (it == layer_overhangs.end()) + // return false; + //ExPolygons overhangs_lower; + //for (ExPolygon* poly : it->second) { + // overhangs_lower.push_back(*poly); + //} + if (layer_nrmax_layer + 1) + return false; + const ExPolygons overhang_dilated = offset_ex(overhang_new, offset_scaled); + return overlaps(overhang_dilated, merged_overhangs_dilated); + } +}; + +static OverhangCluster* add_overhang(std::vector& clusters, ExPolygon* overhang, int layer_nr, coordf_t offset_scaled) { + OverhangCluster* cluster = nullptr; + bool found = false; + for (int i = 0; i < clusters.size(); i++) { + auto cluster_i = &clusters[i]; + if (cluster_i->intersects(*overhang, layer_nr)) { + cluster_i->insert(overhang, layer_nr); + cluster = cluster_i; + break; + } + } + if (!cluster) { + cluster = &clusters.emplace_back(overhang, layer_nr, offset_scaled); + } + return cluster; +}; + // Generate top contact layers supporting overhangs. // For a soluble interface material synchronize the layer heights with the object, otherwise leave the layer height undefined. // If supports over bed surface only are requested, don't generate contact layers over an object. -SupportGeneratorLayersPtr PrintObjectSupportMaterial::top_contact_layers( - const PrintObject &object, const std::vector &buildplate_covered, SupportGeneratorLayerStorage &layer_storage) const +PrintObjectSupportMaterial::MyLayersPtr PrintObjectSupportMaterial::top_contact_layers( + const PrintObject &object, const std::vector &buildplate_covered, MyLayerStorage &layer_storage) const { #ifdef SLIC3R_DEBUG static int iRun = 0; @@ -1656,11 +2214,17 @@ SupportGeneratorLayersPtr PrintObjectSupportMaterial::top_contact_layers( #define SLIC3R_IRUN , iRun #endif /* SLIC3R_DEBUG */ + // BBS: tree support is selected so normal supports need not be generated. + // Note we still need to go through the following steps if support is disabled but raft is enabled. + if (m_object_config->enable_support.value && (m_object_config->support_type.value != stNormalAuto && m_object_config->support_type.value != stNormal)) { + return MyLayersPtr(); + } + // Slice support enforcers / support blockers. SupportAnnotations annotations(object, buildplate_covered); // Output layers, sorted by top Z. - SupportGeneratorLayersPtr contact_out; + MyLayersPtr contact_out; BOOST_LOG_TRIVIAL(debug) << "PrintObjectSupportMaterial::top_contact_layers() in parallel - start"; // Determine top contact areas. @@ -1672,76 +2236,261 @@ SupportGeneratorLayersPtr PrintObjectSupportMaterial::top_contact_layers( // For each overhang layer, two supporting layers may be generated: One for the overhangs extruded with a bridging flow, // and the other for the overhangs extruded with a normal flow. contact_out.assign(num_layers * 2, nullptr); - tbb::parallel_for(tbb::blocked_range(this->has_raft() ? 0 : 1, num_layers), - [this, &object, &annotations, &layer_storage, &contact_out] - (const tbb::blocked_range& range) { - for (size_t layer_id = range.begin(); layer_id < range.end(); ++ layer_id) - { - const Layer &layer = *object.layers()[layer_id]; + tbb::spin_mutex layer_storage_mutex; + + std::vector overhangs_per_layers(num_layers); + size_t layer_id_start = this->has_raft() ? 0 : 1; + // main part of overhang detection can be parallel + tbb::parallel_for(tbb::blocked_range(layer_id_start, num_layers), + [&](const tbb::blocked_range& range) { + for (size_t layer_id = range.begin(); layer_id < range.end(); layer_id++) { + const Layer& layer = *object.layers()[layer_id]; Polygons lower_layer_polygons = (layer_id == 0) ? Polygons() : to_polygons(object.layers()[layer_id - 1]->lslices); - SlicesMarginCache slices_margin; - - auto [overhang_polygons, contact_polygons, enforcer_polygons, no_interface_offset] = - detect_overhangs(layer, layer_id, lower_layer_polygons, *m_print_config, *m_object_config, annotations, slices_margin, m_support_params.gap_xy - #ifdef SLIC3R_DEBUG - , iRun - #endif // SLIC3R_DEBUG - ); - - // Now apply the contact areas to the layer where they need to be made. - if (! contact_polygons.empty() || ! overhang_polygons.empty()) { - // Allocate the two empty layers. - auto [new_layer, bridging_layer] = new_contact_layer(*m_print_config, *m_object_config, m_slicing_params, m_support_params.support_layer_height_min, layer, layer_storage); - if (new_layer) { - // Fill the non-bridging layer with polygons. - fill_contact_layer(*new_layer, layer_id, m_slicing_params, - *m_object_config, slices_margin, overhang_polygons, contact_polygons, enforcer_polygons, lower_layer_polygons, - m_support_params.support_material_flow, no_interface_offset - #ifdef SLIC3R_DEBUG - , iRun, layer - #endif // SLIC3R_DEBUG - ); - // Insert new layer even if there is no interface generated: Likely the support angle is not steep enough to require dense interface, - // however generating a sparse support will be useful for the object stability. - // if (! new_layer->polygons.empty()) - contact_out[layer_id * 2] = new_layer; - if (bridging_layer != nullptr) { - bridging_layer->polygons = new_layer->polygons; - bridging_layer->contact_polygons = std::make_unique(*new_layer->contact_polygons); - bridging_layer->overhang_polygons = std::make_unique(*new_layer->overhang_polygons); - if (new_layer->enforcer_polygons) - bridging_layer->enforcer_polygons = std::make_unique(*new_layer->enforcer_polygons); - contact_out[layer_id * 2 + 1] = bridging_layer; - } - } - } - } - }); - // Compress contact_out, remove the nullptr items. - remove_nulls(contact_out); + overhangs_per_layers[layer_id] = detect_overhangs(layer, layer_id, lower_layer_polygons, *m_print_config, *m_object_config, annotations, m_support_params.gap_xy +#ifdef SLIC3R_DEBUG + , iRun +#endif // SLIC3R_DEBUG + ); - // Merge close contact layers conservatively: If two layers are closer than the minimum allowed print layer height (the min_layer_height parameter), - // the top contact layer is merged into the bottom contact layer. - merge_contact_layers(m_slicing_params, m_support_params.support_layer_height_min, contact_out); + if (object.print()->canceled()) + break; + } + } + ); // end tbb::parallel_for + + if (object.print()->canceled()) + return MyLayersPtr(); + + // check if the sharp tails should be extended higher + bool detect_first_sharp_tail_only = false; + const coordf_t extrusion_width = m_object_config->line_width.get_abs_value(object.print()->config().nozzle_diameter.get_at(object.config().support_interface_filament-1)); + const coordf_t extrusion_width_scaled = scale_(extrusion_width); + if (is_auto(m_object_config->support_type.value) && g_config_support_sharp_tails && !detect_first_sharp_tail_only) { + for (size_t layer_nr = layer_id_start; layer_nr < num_layers; layer_nr++) { + if (object.print()->canceled()) + break; - BOOST_LOG_TRIVIAL(debug) << "PrintObjectSupportMaterial::top_contact_layers() in parallel - end"; + const Layer* layer = object.get_layer(layer_nr); + const Layer* lower_layer = layer->lower_layer; + if (!lower_layer) + continue; - return contact_out; -} + // BBS detect sharp tail + const ExPolygons& lower_layer_sharptails = lower_layer->sharp_tails; + const auto& lower_layer_sharptails_height = lower_layer->sharp_tails_height; + for (const ExPolygon& expoly : layer->lslices) { + bool is_sharp_tail = false; + float accum_height = layer->height; + do { + // 2. something below + // check whether this is above a sharp tail region. + + // 2.1 If no sharp tail below, this is considered as common region. + ExPolygons supported_by_lower = intersection_ex({ expoly }, lower_layer_sharptails); + if (supported_by_lower.empty()) { + is_sharp_tail = false; + break; + } -// Find the bottom contact layers above the top surfaces of this layer. -static inline SupportGeneratorLayer* detect_bottom_contacts( - const SlicingParameters &slicing_params, - const SupportParameters &support_params, - const PrintObject &object, - const Layer &layer, - // Existing top contact layers, to which this newly created bottom contact layer will be snapped to guarantee a minimum layer height. - const SupportGeneratorLayersPtr &top_contacts, - // First top contact layer index overlapping with this new bottom interface layer. - size_t contact_idx, + // 2.2 If sharp tail below, check whether it support this region enough. +#if 0 + // judge by area isn't reliable, failure cases include 45 degree rotated cube + float supported_area = area(supported_by_lower); + if (supported_area > area_thresh_well_supported) { + is_sharp_tail = false; + break; + } +#endif + BoundingBox bbox = get_extents(supported_by_lower); + if (bbox.size().x() > length_thresh_well_supported && bbox.size().y() > length_thresh_well_supported) { + is_sharp_tail = false; + break; + } + + // 2.3 check whether sharp tail exceed the max height + for (const auto& lower_sharp_tail_height : lower_layer_sharptails_height) { + if (lower_sharp_tail_height.first->overlaps(expoly)) { + accum_height += lower_sharp_tail_height.second; + break; + } + } + if (accum_height > sharp_tail_max_support_height) { + is_sharp_tail = false; + break; + } + + // 2.4 if the area grows fast than threshold, it get connected to other part or + // it has a sharp slop and will be auto supported. + ExPolygons new_overhang_expolys = diff_ex({ expoly }, lower_layer_sharptails); + Point size_diff = get_extents(new_overhang_expolys).size() - get_extents(lower_layer_sharptails).size(); + if (size_diff.both_comp(Point(scale_(5), scale_(5)), ">") || !offset_ex(new_overhang_expolys, -5.0 * extrusion_width_scaled).empty()) { + is_sharp_tail = false; + break; + } + + // 2.5 mark the expoly as sharptail + is_sharp_tail = true; + } while (0); + + if (is_sharp_tail) { + ExPolygons overhang = diff_ex({ expoly }, lower_layer->lslices); + layer->sharp_tails.push_back(expoly); + layer->sharp_tails_height.insert({ &expoly, accum_height }); + append(overhangs_per_layers[layer_nr], overhang); +#ifdef SUPPORT_TREE_DEBUG_TO_SVG + SVG svg(get_svg_filename(std::to_string(layer->print_z), "sharp_tail"), object.bounding_box()); + if (svg.is_opened()) svg.draw(overhang, "yellow"); +#endif + } + + } + } + } + + if (object.print()->canceled()) + return MyLayersPtr(); + + // BBS group overhang clusters + const bool config_remove_small_overhangs = m_object_config->support_remove_small_overhang.value; + if (config_remove_small_overhangs) { + std::vector clusters; + double fw_scaled = scale_(extrusion_width); + std::set removed_overhang; + + for (size_t layer_id = layer_id_start; layer_id < num_layers; layer_id++) { + const Layer* layer = object.get_layer(layer_id); + for (auto& overhang : overhangs_per_layers[layer_id]) { + OverhangCluster* cluster = add_overhang(clusters, &overhang, layer_id, fw_scaled); + if (overlaps({ overhang }, layer->cantilevers)) + cluster->is_cantilever = true; + } + } + + for (OverhangCluster& cluster : clusters) { + // 3. check whether the small overhang is sharp tail + cluster.is_sharp_tail = false; + for (size_t layer_id = cluster.min_layer; layer_id <= cluster.max_layer; layer_id++) { + const Layer* layer = object.get_layer(layer_id); + if (overlaps(layer->sharp_tails, cluster.merged_overhangs_dilated)) { + cluster.is_sharp_tail = true; + break; + } + } + + if (!cluster.is_sharp_tail && !cluster.is_cantilever) { + // 2. check overhang cluster size is small + cluster.is_small_overhang = false; + auto erode1 = offset_ex(cluster.merged_overhangs_dilated, -1.0 * fw_scaled); + Point bbox_sz = get_extents(erode1).size(); + if (bbox_sz.x() < 2 * fw_scaled || bbox_sz.y() < 2 * fw_scaled) { + cluster.is_small_overhang = true; + } + } + +#ifdef SUPPORT_TREE_DEBUG_TO_SVG + const Layer* layer1 = object.get_layer(cluster.min_layer); + BoundingBox bbox = get_extents(cluster.merged_overhangs_dilated); + bbox.merge(get_extents(layer1->lslices)); + SVG svg(format("SVG/overhangCluster_%s_%s_tail=%s_cantilever=%s_small=%s.svg", cluster.min_layer, layer1->print_z, cluster.is_sharp_tail, cluster.is_cantilever, cluster.is_small_overhang), bbox); + if (svg.is_opened()) { + svg.draw(layer1->lslices, "red"); + svg.draw(cluster.merged_overhangs_dilated, "blue"); + } +#endif + + // 5. remove small overhangs + if (cluster.is_small_overhang) { + for (auto overhangs : cluster.layer_overhangs) { + for (auto* poly : overhangs.second) + removed_overhang.insert(poly); + } + } + } + + for (size_t layer_id = layer_id_start; layer_id < num_layers; layer_id++) { + auto& layer_overhangs = overhangs_per_layers[layer_id]; + if (layer_overhangs.empty()) + continue; + + for (int poly_idx = 0; poly_idx < layer_overhangs.size(); poly_idx++) { + auto* overhang = &layer_overhangs[poly_idx]; + if (removed_overhang.find(overhang) != removed_overhang.end()) { + overhang->clear(); + } + } + } + } + + if (object.print()->canceled()) + return MyLayersPtr(); + + for (size_t layer_id = layer_id_start; layer_id < num_layers; layer_id++) { + const Layer& layer = *object.layers()[layer_id]; + Polygons overhang_polygons = to_polygons(overhangs_per_layers[layer_id]); + Polygons lower_layer_polygons = (layer_id == 0) ? Polygons() : to_polygons(object.layers()[layer_id - 1]->lslices); + SlicesMarginCache slices_margin; + + auto [contact_polygons, enforcer_polygons, no_interface_offset] = + detect_contacts(layer, layer_id, overhang_polygons, lower_layer_polygons, *m_print_config, *m_object_config, annotations, slices_margin, m_support_params.gap_xy +#ifdef SLIC3R_DEBUG + , iRun +#endif // SLIC3R_DEBUG + ); + + // Now apply the contact areas to the layer where they need to be made. + if (!contact_polygons.empty() || !overhang_polygons.empty()) { + // Allocate the two empty layers. + auto [new_layer, bridging_layer] = new_contact_layer(*m_print_config, *m_object_config, m_slicing_params, m_support_params.support_layer_height_min, layer, layer_storage, layer_storage_mutex); + if (new_layer) { + // Fill the non-bridging layer with polygons. + fill_contact_layer(*new_layer, layer_id, m_slicing_params, + *m_object_config, slices_margin, overhang_polygons, contact_polygons, enforcer_polygons, lower_layer_polygons, + m_support_params.support_material_flow, no_interface_offset +#ifdef SLIC3R_DEBUG + , iRun, layer +#endif // SLIC3R_DEBUG + ); + // Insert new layer even if there is no interface generated: Likely the support angle is not steep enough to require dense interface, + // however generating a sparse support will be useful for the object stability. + // if (! new_layer->polygons.empty()) + contact_out[layer_id * 2] = new_layer; + if (bridging_layer != nullptr) { + bridging_layer->polygons = new_layer->polygons; + bridging_layer->contact_polygons = std::make_unique(*new_layer->contact_polygons); + bridging_layer->overhang_polygons = std::make_unique(*new_layer->overhang_polygons); + if (new_layer->enforcer_polygons) + bridging_layer->enforcer_polygons = std::make_unique(*new_layer->enforcer_polygons); + contact_out[layer_id * 2 + 1] = bridging_layer; + } + } + } + } + + // Compress contact_out, remove the nullptr items. + remove_nulls(contact_out); + + // Merge close contact layers conservatively: If two layers are closer than the minimum allowed print layer height (the min_layer_height parameter), + // the top contact layer is merged into the bottom contact layer. + merge_contact_layers(m_slicing_params, m_support_params.support_layer_height_min, contact_out); + + BOOST_LOG_TRIVIAL(debug) << "PrintObjectSupportMaterial::top_contact_layers() in parallel - end"; + + return contact_out; +} + +// Find the bottom contact layers above the top surfaces of this layer. +static inline PrintObjectSupportMaterial::MyLayer* detect_bottom_contacts( + const SlicingParameters &slicing_params, + const PrintObjectSupportMaterial::SupportParams &support_params, + const PrintObject &object, + const Layer &layer, + // Existing top contact layers, to which this newly created bottom contact layer will be snapped to guarantee a minimum layer height. + const PrintObjectSupportMaterial::MyLayersPtr &top_contacts, + // First top contact layer index overlapping with this new bottom interface layer. + size_t contact_idx, // To allocate a new layer from. - SupportGeneratorLayerStorage &layer_storage, + std::deque &layer_storage, // To trim the support areas above this bottom interface layer with this newly created bottom interface layer. std::vector &layer_support_areas, // Support areas projected from top to bottom, starting with top support interfaces. @@ -1776,13 +2525,13 @@ static inline SupportGeneratorLayer* detect_bottom_contacts( size_t layer_id = layer.id() - slicing_params.raft_layers(); // Allocate a new bottom contact layer. - SupportGeneratorLayer &layer_new = layer_storage.allocate_unguarded(SupporLayerType::BottomContact); + PrintObjectSupportMaterial::MyLayer &layer_new = layer_allocate(layer_storage, PrintObjectSupportMaterial::sltBottomContact); // Grow top surfaces so that interface and support generation are generated // with some spacing from object - it looks we don't need the actual // top shapes so this can be done here //FIXME calculate layer height based on the actual thickness of the layer: // If the layer is extruded with no bridging flow, support just the normal extrusions. - layer_new.height = slicing_params.soluble_interface ? + layer_new.height = slicing_params.soluble_interface || !object.print()->config().independent_support_layer_height ? // Align the interface layer with the object's layer height. layer.upper_layer->height : // Place a bridge flow interface layer or the normal flow interface layer over the top surface. @@ -1805,12 +2554,18 @@ static inline SupportGeneratorLayer* detect_bottom_contacts( // A top layer has been found, which is close to the new bottom layer. coordf_t diff = layer_new.print_z - top_contacts[top_idx]->print_z; assert(std::abs(diff) <= support_params.support_layer_height_min + EPSILON); - if (diff > 0.) { - // The top contact layer is below this layer. Make the bridging layer thinner to align with the existing top layer. - assert(diff < layer_new.height + EPSILON); - assert(layer_new.height - diff >= support_params.support_layer_height_min - EPSILON); - layer_new.print_z = top_contacts[top_idx]->print_z; - layer_new.height -= diff; + if (diff > 0.F) { + if (layer_new.height - diff > support_params.support_layer_height_min) { + // The top contact layer is below this layer. Make the bridging layer thinner to align with the existing top layer. + assert(diff < layer_new.height + EPSILON); + assert(layer_new.height - diff >= support_params.support_layer_height_min - EPSILON); + layer_new.print_z = top_contacts[top_idx]->print_z; + layer_new.height -= diff; + } + else { + // BBS: The trimmed layer height is smaller than support_layer_height_min. Walk to the next top contact layer. + continue; + } } else { // The top contact layer is above this layer. One may either make this layer thicker or thinner. @@ -1938,12 +2693,12 @@ static inline std::pair project_support_to_grid(const Layer // Generate bottom contact layers supporting the top contact layers. // For a soluble interface material synchronize the layer heights with the object, // otherwise set the layer height to a bridging flow of a support interface nozzle. -SupportGeneratorLayersPtr PrintObjectSupportMaterial::bottom_contact_layers_and_layer_support_areas( - const PrintObject &object, const SupportGeneratorLayersPtr &top_contacts, std::vector &buildplate_covered, - SupportGeneratorLayerStorage &layer_storage, std::vector &layer_support_areas) const +PrintObjectSupportMaterial::MyLayersPtr PrintObjectSupportMaterial::bottom_contact_layers_and_layer_support_areas( + const PrintObject &object, const MyLayersPtr &top_contacts, std::vector &buildplate_covered, + MyLayerStorage &layer_storage, std::vector &layer_support_areas) const { if (top_contacts.empty()) - return SupportGeneratorLayersPtr(); + return MyLayersPtr(); #ifdef SLIC3R_DEBUG static size_t s_iRun = 0; @@ -1960,7 +2715,7 @@ SupportGeneratorLayersPtr PrintObjectSupportMaterial::bottom_contact_layers_and_ // find object top surfaces // we'll use them to clip our support and detect where does it stick - SupportGeneratorLayersPtr bottom_contacts; + MyLayersPtr bottom_contacts; // There is some support to be built, if there are non-empty top surfaces detected. // Sum of unsupported contact areas above the current layer.print_z. @@ -1979,7 +2734,7 @@ SupportGeneratorLayersPtr PrintObjectSupportMaterial::bottom_contact_layers_and_ Polygons enforcers_new; #endif // SLIC3R_DEBUG for (; contact_idx >= 0 && top_contacts[contact_idx]->print_z > layer.print_z - EPSILON; -- contact_idx) { - SupportGeneratorLayer &top_contact = *top_contacts[contact_idx]; + MyLayer &top_contact = *top_contacts[contact_idx]; #ifndef SLIC3R_DEBUG Polygons polygons_new; Polygons enforcers_new; @@ -2021,7 +2776,7 @@ SupportGeneratorLayersPtr PrintObjectSupportMaterial::bottom_contact_layers_and_ #endif // SLIC3R_DEBUG ] { // Find the bottom contact layers above the top surfaces of this layer. - SupportGeneratorLayer *layer_new = detect_bottom_contacts( + MyLayer *layer_new = detect_bottom_contacts( m_slicing_params, m_support_params, object, layer, top_contacts, contact_idx, layer_storage, layer_support_areas, overhangs_for_bottom_contacts #ifdef SLIC3R_DEBUG , iRun, polygons_new @@ -2080,21 +2835,64 @@ SupportGeneratorLayersPtr PrintObjectSupportMaterial::bottom_contact_layers_and_ return bottom_contacts; } +template +IndexType idx_higher_or_equal(const std::vector& vec, IndexType idx, FN_HIGHER_EQUAL fn_higher_equal) +{ + return Layer::idx_higher_or_equal(vec.begin(), vec.end(), idx, fn_higher_equal); +} + +// FN_LOWER_EQUAL: the provided object pointer has a Z value <= of an internal threshold. +// Find the first item with Z value <= of an internal threshold of fn_lower_equal. +// If no vec item with Z value <= of an internal threshold of fn_lower_equal is found, return -1. +// If the initial idx is < -1, then use binary search. +// Otherwise search linearly downwards. +template +int idx_lower_or_equal(IT begin, IT end, int idx, FN_LOWER_EQUAL fn_lower_equal) +{ + auto size = int(end - begin); + if (size == 0) { + idx = -1; + } else if (idx < -1) { + // First of the batch of layers per thread pool invocation. Use binary search. + int idx_low = 0; + int idx_high = std::max(0, size - 1); + while (idx_low + 1 < idx_high) { + int idx_mid = (idx_low + idx_high) / 2; + if (fn_lower_equal(begin[idx_mid])) + idx_low = idx_mid; + else + idx_high = idx_mid; + } + idx = fn_lower_equal(begin[idx_high]) ? idx_high : + (fn_lower_equal(begin[idx_low ]) ? idx_low : -1); + } else { + // For the other layers of this batch of layers, search incrementally, which is cheaper than the binary search. + while (idx >= 0 && ! fn_lower_equal(begin[idx])) + -- idx; + } + return idx; +} +template +int idx_lower_or_equal(const std::vector &vec, int idx, FN_LOWER_EQUAL fn_lower_equal) +{ + return idx_lower_or_equal(vec.begin(), vec.end(), idx, fn_lower_equal); +} + // Trim the top_contacts layers with the bottom_contacts layers if they overlap, so there would not be enough vertical space for both of them. void PrintObjectSupportMaterial::trim_top_contacts_by_bottom_contacts( - const PrintObject &object, const SupportGeneratorLayersPtr &bottom_contacts, SupportGeneratorLayersPtr &top_contacts) const + const PrintObject &object, const MyLayersPtr &bottom_contacts, MyLayersPtr &top_contacts) const { tbb::parallel_for(tbb::blocked_range(0, int(top_contacts.size())), [&bottom_contacts, &top_contacts](const tbb::blocked_range& range) { int idx_bottom_overlapping_first = -2; // For all top contact layers, counting downwards due to the way idx_higher_or_equal caches the last index to avoid repeated binary search. for (int idx_top = range.end() - 1; idx_top >= range.begin(); -- idx_top) { - SupportGeneratorLayer &layer_top = *top_contacts[idx_top]; + MyLayer &layer_top = *top_contacts[idx_top]; // Find the first bottom layer overlapping with layer_top. - idx_bottom_overlapping_first = idx_lower_or_equal(bottom_contacts, idx_bottom_overlapping_first, [&layer_top](const SupportGeneratorLayer *layer_bottom){ return layer_bottom->bottom_print_z() - EPSILON <= layer_top.bottom_z; }); + idx_bottom_overlapping_first = idx_lower_or_equal(bottom_contacts, idx_bottom_overlapping_first, [&layer_top](const MyLayer *layer_bottom){ return layer_bottom->bottom_print_z() - EPSILON <= layer_top.bottom_z; }); // For all top contact layers overlapping with the thick bottom contact layer: for (int idx_bottom_overlapping = idx_bottom_overlapping_first; idx_bottom_overlapping >= 0; -- idx_bottom_overlapping) { - const SupportGeneratorLayer &layer_bottom = *bottom_contacts[idx_bottom_overlapping]; + const MyLayer &layer_bottom = *bottom_contacts[idx_bottom_overlapping]; assert(layer_bottom.bottom_print_z() - EPSILON <= layer_top.bottom_z); if (layer_top.print_z < layer_bottom.print_z + EPSILON) { // Layers overlap. Trim layer_top with layer_bottom. @@ -2106,16 +2904,16 @@ void PrintObjectSupportMaterial::trim_top_contacts_by_bottom_contacts( }); } -SupportGeneratorLayersPtr PrintObjectSupportMaterial::raft_and_intermediate_support_layers( +PrintObjectSupportMaterial::MyLayersPtr PrintObjectSupportMaterial::raft_and_intermediate_support_layers( const PrintObject &object, - const SupportGeneratorLayersPtr &bottom_contacts, - const SupportGeneratorLayersPtr &top_contacts, - SupportGeneratorLayerStorage &layer_storage) const + const MyLayersPtr &bottom_contacts, + const MyLayersPtr &top_contacts, + MyLayerStorage &layer_storage) const { - SupportGeneratorLayersPtr intermediate_layers; + MyLayersPtr intermediate_layers; // Collect and sort the extremes (bottoms of the top contacts and tops of the bottom contacts). - SupportGeneratorLayersPtr extremes; + MyLayersPtr extremes; extremes.reserve(top_contacts.size() + bottom_contacts.size()); for (size_t i = 0; i < top_contacts.size(); ++ i) // Bottoms of the top contact layers. In case of non-soluble supports, @@ -2127,18 +2925,18 @@ SupportGeneratorLayersPtr PrintObjectSupportMaterial::raft_and_intermediate_supp if (extremes.empty()) return intermediate_layers; - auto layer_extreme_lower = [](const SupportGeneratorLayer *l1, const SupportGeneratorLayer *l2) { + auto layer_extreme_lower = [](const MyLayer *l1, const MyLayer *l2) { coordf_t z1 = l1->extreme_z(); coordf_t z2 = l2->extreme_z(); // If the layers are aligned, return the top contact surface first. - return z1 < z2 || (z1 == z2 && l1->layer_type == SupporLayerType::TopContact && l2->layer_type == SupporLayerType::BottomContact); + return z1 < z2 || (z1 == z2 && l1->layer_type == PrintObjectSupportMaterial::sltTopContact && l2->layer_type == PrintObjectSupportMaterial::sltBottomContact); }; std::sort(extremes.begin(), extremes.end(), layer_extreme_lower); assert(extremes.empty() || (extremes.front()->extreme_z() > m_slicing_params.raft_interface_top_z - EPSILON && (m_slicing_params.raft_layers() == 1 || // only raft contact layer - extremes.front()->layer_type == SupporLayerType::TopContact || // first extreme is a top contact layer + extremes.front()->layer_type == sltTopContact || // first extreme is a top contact layer extremes.front()->extreme_z() > m_slicing_params.first_print_layer_height - EPSILON))); bool synchronize = this->synchronize_layers(); @@ -2150,32 +2948,32 @@ SupportGeneratorLayersPtr PrintObjectSupportMaterial::raft_and_intermediate_supp extremes[i]->extreme_z() - extremes[i-1]->extreme_z() > m_support_params.support_layer_height_min - EPSILON); assert(extremes[i]->extreme_z() - extremes[i-1]->extreme_z() > 0. || extremes[i]->layer_type == extremes[i-1]->layer_type || - (extremes[i]->layer_type == SupporLayerType::BottomContact && extremes[i - 1]->layer_type == SupporLayerType::TopContact)); + (extremes[i]->layer_type == sltBottomContact && extremes[i - 1]->layer_type == sltTopContact)); } #endif // Generate intermediate layers. // The first intermediate layer is the same as the 1st layer if there is no raft, // or the bottom of the first intermediate layer is aligned with the bottom of the raft contact layer. - // Intermediate layers are always printed with a normal etrusion flow (non-bridging). + // Intermediate layers are always printed with a normal extrusion flow (non-bridging). size_t idx_layer_object = 0; size_t idx_extreme_first = 0; if (! extremes.empty() && std::abs(extremes.front()->extreme_z() - m_slicing_params.raft_interface_top_z) < EPSILON) { // This is a raft contact layer, its height has been decided in this->top_contact_layers(). // Ignore this layer when calculating the intermediate support layers. - assert(extremes.front()->layer_type == SupporLayerType::TopContact); + assert(extremes.front()->layer_type == sltTopContact); ++ idx_extreme_first; } for (size_t idx_extreme = idx_extreme_first; idx_extreme < extremes.size(); ++ idx_extreme) { - SupportGeneratorLayer *extr2 = extremes[idx_extreme]; + MyLayer *extr2 = extremes[idx_extreme]; coordf_t extr2z = extr2->extreme_z(); if (std::abs(extr2z - m_slicing_params.first_print_layer_height) < EPSILON) { // This is a bottom of a synchronized (or soluble) top contact layer, its height has been decided in this->top_contact_layers(). - assert(extr2->layer_type == SupporLayerType::TopContact); - assert(extr2->bottom_z == m_slicing_params.first_print_layer_height); + assert(extr2->layer_type == sltTopContact); + assert(std::abs(extr2->bottom_z - m_slicing_params.first_print_layer_height) < EPSILON); assert(extr2->print_z >= m_slicing_params.first_print_layer_height + m_support_params.support_layer_height_min - EPSILON); if (intermediate_layers.empty() || intermediate_layers.back()->print_z < m_slicing_params.first_print_layer_height) { - SupportGeneratorLayer &layer_new = layer_storage.allocate_unguarded(SupporLayerType::Intermediate); + MyLayer &layer_new = layer_allocate(layer_storage, sltIntermediate); layer_new.bottom_z = 0.; layer_new.print_z = m_slicing_params.first_print_layer_height; layer_new.height = m_slicing_params.first_print_layer_height; @@ -2185,11 +2983,11 @@ SupportGeneratorLayersPtr PrintObjectSupportMaterial::raft_and_intermediate_supp } assert(extr2z >= m_slicing_params.raft_interface_top_z + EPSILON); assert(extr2z >= m_slicing_params.first_print_layer_height + EPSILON); - SupportGeneratorLayer *extr1 = (idx_extreme == idx_extreme_first) ? nullptr : extremes[idx_extreme - 1]; + MyLayer *extr1 = (idx_extreme == idx_extreme_first) ? nullptr : extremes[idx_extreme - 1]; // Fuse a support layer firmly to the raft top interface (not to the raft contacts). coordf_t extr1z = (extr1 == nullptr) ? m_slicing_params.raft_interface_top_z : extr1->extreme_z(); assert(extr2z >= extr1z); - assert(extr2z > extr1z || (extr1 != nullptr && extr2->layer_type == SupporLayerType::BottomContact)); + assert(extr2z > extr1z || (extr1 != nullptr && extr2->layer_type == sltBottomContact)); if (std::abs(extr1z) < EPSILON) { // This layer interval starts with the 1st layer. Print the 1st layer using the prescribed 1st layer thickness. // assert(! m_slicing_params.has_raft()); RaftingEdition: unclear where the issue is: assert fails with 1-layer raft & base supports @@ -2197,7 +2995,7 @@ SupportGeneratorLayersPtr PrintObjectSupportMaterial::raft_and_intermediate_supp // At this point only layers above first_print_layer_heigth + EPSILON are expected as the other cases were captured earlier. assert(extr2z >= m_slicing_params.first_print_layer_height + EPSILON); // Generate a new intermediate layer. - SupportGeneratorLayer &layer_new = layer_storage.allocate_unguarded(SupporLayerType::Intermediate); + MyLayer &layer_new = layer_allocate(layer_storage, sltIntermediate); layer_new.bottom_z = 0.; layer_new.print_z = extr1z = m_slicing_params.first_print_layer_height; layer_new.height = extr1z; @@ -2217,7 +3015,7 @@ SupportGeneratorLayersPtr PrintObjectSupportMaterial::raft_and_intermediate_supp ++ idx_layer_object; if (idx_layer_object == 0 && extr1z == m_slicing_params.raft_interface_top_z) { // Insert one base support layer below the object. - SupportGeneratorLayer &layer_new = layer_storage.allocate_unguarded(SupporLayerType::Intermediate); + MyLayer &layer_new = layer_allocate(layer_storage, sltIntermediate); layer_new.print_z = m_slicing_params.object_print_z_min; layer_new.bottom_z = m_slicing_params.raft_interface_top_z; layer_new.height = layer_new.print_z - layer_new.bottom_z; @@ -2225,7 +3023,7 @@ SupportGeneratorLayersPtr PrintObjectSupportMaterial::raft_and_intermediate_supp } // Emit all intermediate support layers synchronized with object layers up to extr2z. for (; idx_layer_object < object.layers().size() && object.layers()[idx_layer_object]->print_z < extr2z + EPSILON; ++ idx_layer_object) { - SupportGeneratorLayer &layer_new = layer_storage.allocate_unguarded(SupporLayerType::Intermediate); + MyLayer &layer_new = layer_allocate(layer_storage, sltIntermediate); layer_new.print_z = object.layers()[idx_layer_object]->print_z; layer_new.height = object.layers()[idx_layer_object]->height; layer_new.bottom_z = (idx_layer_object > 0) ? object.layers()[idx_layer_object - 1]->print_z : (layer_new.print_z - layer_new.height); @@ -2237,13 +3035,13 @@ SupportGeneratorLayersPtr PrintObjectSupportMaterial::raft_and_intermediate_supp size_t n_layers_extra = size_t(ceil(dist / m_slicing_params.max_suport_layer_height)); assert(n_layers_extra > 0); coordf_t step = dist / coordf_t(n_layers_extra); - if (extr1 != nullptr && extr1->layer_type == SupporLayerType::TopContact && + if (extr1 != nullptr && extr1->layer_type == sltTopContact && extr1->print_z + m_support_params.support_layer_height_min > extr1->bottom_z + step) { // The bottom extreme is a bottom of a top surface. Ensure that the gap // between the 1st intermediate layer print_z and extr1->print_z is not too small. assert(extr1->bottom_z + m_support_params.support_layer_height_min < extr1->print_z + EPSILON); // Generate the first intermediate layer. - SupportGeneratorLayer &layer_new = layer_storage.allocate_unguarded(SupporLayerType::Intermediate); + MyLayer &layer_new = layer_allocate(layer_storage, sltIntermediate); layer_new.bottom_z = extr1->bottom_z; layer_new.print_z = extr1z = extr1->print_z; layer_new.height = extr1->height; @@ -2255,7 +3053,7 @@ SupportGeneratorLayersPtr PrintObjectSupportMaterial::raft_and_intermediate_supp // Continue printing the other layers up to extr2z. step = dist / coordf_t(n_layers_extra); } - if (! m_slicing_params.soluble_interface && extr2->layer_type == SupporLayerType::TopContact) { + if (! m_slicing_params.soluble_interface && extr2->layer_type == sltTopContact) { // This is a top interface layer, which does not have a height assigned yet. Do it now. assert(extr2->height == 0.); assert(extr1z > m_slicing_params.first_print_layer_height - EPSILON); @@ -2267,7 +3065,7 @@ SupportGeneratorLayersPtr PrintObjectSupportMaterial::raft_and_intermediate_supp coordf_t extr2z_large_steps = extr2z; // Take the largest allowed step in the Z axis until extr2z_large_steps is reached. for (size_t i = 0; i < n_layers_extra; ++ i) { - SupportGeneratorLayer &layer_new = layer_storage.allocate_unguarded(SupporLayerType::Intermediate); + MyLayer &layer_new = layer_allocate(layer_storage, sltIntermediate); if (i + 1 == n_layers_extra) { // Last intermediate layer added. Align the last entered layer with extr2z_large_steps exactly. layer_new.bottom_z = (i == 0) ? extr1z : intermediate_layers.back()->print_z; @@ -2291,6 +3089,36 @@ SupportGeneratorLayersPtr PrintObjectSupportMaterial::raft_and_intermediate_supp assert(top_contacts[i]->height > 0.); #endif /* _DEBUG */ +#if 0 // #ifdef SLIC3R_DEBUG + // check bounds + std::ofstream out; + out.open("./SVG/ns_bounds.txt"); + if (out.is_open()) { + if (!top_contacts.empty()) { + out << "### Top Contacts ###" << std::endl; + for (auto& t : top_contacts) { + out << t->print_z << std::endl; + } + } + if (!bottom_contacts.empty()) { + out << "### Bottome Contacts ###" << std::endl; + for (auto& b : bottom_contacts) { + out << b->print_z << std::endl; + } + } + if (!intermediate_layers.empty()) { + out << "### Intermediate Layers ###" << std::endl; + for (auto& i : intermediate_layers) { + out << i->print_z << std::endl; + } + } + out << "### Slice Layers ###" << std::endl; + for (size_t j = 0; j < object.layers().size(); ++j) { + out << object.layers()[j]->print_z << std::endl; + } + } +#endif /* SLIC3R_DEBUG */ + return intermediate_layers; } @@ -2298,9 +3126,9 @@ SupportGeneratorLayersPtr PrintObjectSupportMaterial::raft_and_intermediate_supp // Also the bottom/top_contacts shall have a layer thickness assigned already. void PrintObjectSupportMaterial::generate_base_layers( const PrintObject &object, - const SupportGeneratorLayersPtr &bottom_contacts, - const SupportGeneratorLayersPtr &top_contacts, - SupportGeneratorLayersPtr &intermediate_layers, + const MyLayersPtr &bottom_contacts, + const MyLayersPtr &top_contacts, + MyLayersPtr &intermediate_layers, const std::vector &layer_support_areas) const { #ifdef SLIC3R_DEBUG @@ -2324,7 +3152,7 @@ void PrintObjectSupportMaterial::generate_base_layers( { BOOST_LOG_TRIVIAL(trace) << "Support generator - generate_base_layers - creating layer " << idx_intermediate << " of " << intermediate_layers.size(); - SupportGeneratorLayer &layer_intermediate = *intermediate_layers[idx_intermediate]; + MyLayer &layer_intermediate = *intermediate_layers[idx_intermediate]; // Layers must be sorted by print_z. assert(idx_intermediate == 0 || layer_intermediate.print_z >= intermediate_layers[idx_intermediate - 1]->print_z); @@ -2347,10 +3175,10 @@ void PrintObjectSupportMaterial::generate_base_layers( // 4) base.print_z > top.bottom_z && base.bottom_z < top.bottom_z -> Base overlaps with top.bottom_z. This must not happen. // 5) base.print_z <= top.print_z && base.bottom_z >= top.bottom_z -> Base is fully inside top. Trim base by top. idx_top_contact_above = idx_lower_or_equal(top_contacts, idx_top_contact_above, - [&layer_intermediate](const SupportGeneratorLayer *layer){ return layer->bottom_z <= layer_intermediate.print_z - EPSILON; }); + [&layer_intermediate](const MyLayer *layer){ return layer->bottom_z <= layer_intermediate.print_z - EPSILON; }); // Collect all the top_contact layer intersecting with this layer. for (int idx_top_contact_overlapping = idx_top_contact_above; idx_top_contact_overlapping >= 0; -- idx_top_contact_overlapping) { - SupportGeneratorLayer &layer_top_overlapping = *top_contacts[idx_top_contact_overlapping]; + MyLayer &layer_top_overlapping = *top_contacts[idx_top_contact_overlapping]; if (layer_top_overlapping.print_z < layer_intermediate.bottom_z + EPSILON) break; // Base must not overlap with top.bottom_z. @@ -2367,7 +3195,7 @@ void PrintObjectSupportMaterial::generate_base_layers( polygons_new = layer_support_areas.front(); double first_layer_z = object.layers().front()->print_z; for (int i = idx_top_contact_above + 1; i < int(top_contacts.size()); ++ i) { - SupportGeneratorLayer &contacts = *top_contacts[i]; + MyLayer &contacts = *top_contacts[i]; if (contacts.print_z > first_layer_z + EPSILON) break; assert(contacts.bottom_z > layer_intermediate.print_z - EPSILON); @@ -2384,10 +3212,10 @@ void PrintObjectSupportMaterial::generate_base_layers( // 4) base.print_z > bottom.print_z && base.bottom_z >= bottom.print_z -> Base overlaps with bottom.print_z. This must not happen. // 5) base.print_z <= bottom.print_z && base.bottom_z >= bottom.bottom_z -> Base is fully inside top. Trim base by top. idx_bottom_contact_overlapping = idx_lower_or_equal(bottom_contacts, idx_bottom_contact_overlapping, - [&layer_intermediate](const SupportGeneratorLayer *layer){ return layer->bottom_print_z() <= layer_intermediate.print_z - EPSILON; }); + [&layer_intermediate](const MyLayer *layer){ return layer->bottom_print_z() <= layer_intermediate.print_z - EPSILON; }); // Collect all the bottom_contacts layer intersecting with this layer. for (int i = idx_bottom_contact_overlapping; i >= 0; -- i) { - SupportGeneratorLayer &layer_bottom_overlapping = *bottom_contacts[i]; + MyLayer &layer_bottom_overlapping = *bottom_contacts[i]; if (layer_bottom_overlapping.print_z < layer_intermediate.bottom_print_z() + EPSILON) break; // Base must not overlap with bottom.top_z. @@ -2417,10 +3245,10 @@ void PrintObjectSupportMaterial::generate_base_layers( polygons_new, polygons_trimming, ApplySafetyOffset::Yes); // safety offset to merge the touching source polygons - layer_intermediate.layer_type = SupporLayerType::Base; + layer_intermediate.layer_type = sltBase; #if 0 - // coordf_t fillet_radius_scaled = scale_(m_object_config->support_material_spacing); + // coordf_t fillet_radius_scaled = scale_(m_object_config->support_base_pattern_spacing); // Fillet the base polygons and trim them again with the top, interface and contact layers. $base->{$i} = diff( offset2( @@ -2439,7 +3267,7 @@ void PrintObjectSupportMaterial::generate_base_layers( BOOST_LOG_TRIVIAL(debug) << "PrintObjectSupportMaterial::generate_base_layers() in parallel - end"; #ifdef SLIC3R_DEBUG - for (SupportGeneratorLayersPtr::const_iterator it = intermediate_layers.begin(); it != intermediate_layers.end(); ++it) + for (MyLayersPtr::const_iterator it = intermediate_layers.begin(); it != intermediate_layers.end(); ++it) ::Slic3r::SVG::export_expolygons( debug_out_path("support-intermediate-layers-untrimmed-%d-%lf.svg", iRun, (*it)->print_z), union_ex((*it)->polygons)); @@ -2451,7 +3279,7 @@ void PrintObjectSupportMaterial::generate_base_layers( void PrintObjectSupportMaterial::trim_support_layers_by_object( const PrintObject &object, - SupportGeneratorLayersPtr &support_layers, + MyLayersPtr &support_layers, const coordf_t gap_extra_above, const coordf_t gap_extra_below, const coordf_t gap_xy) const @@ -2460,10 +3288,10 @@ void PrintObjectSupportMaterial::trim_support_layers_by_object( // Collect non-empty layers to be processed in parallel. // This is a good idea as pulling a thread from a thread pool for an empty task is expensive. - SupportGeneratorLayersPtr nonempty_layers; + MyLayersPtr nonempty_layers; nonempty_layers.reserve(support_layers.size()); for (size_t idx_layer = 0; idx_layer < support_layers.size(); ++ idx_layer) { - SupportGeneratorLayer *support_layer = support_layers[idx_layer]; + MyLayer *support_layer = support_layers[idx_layer]; if (! support_layer->polygons.empty() && support_layer->print_z >= m_slicing_params.raft_contact_top_z + EPSILON) // Non-empty support layer and not a raft layer. nonempty_layers.push_back(support_layer); @@ -2475,13 +3303,27 @@ void PrintObjectSupportMaterial::trim_support_layers_by_object( tbb::blocked_range(0, nonempty_layers.size()), [this, &object, &nonempty_layers, gap_extra_above, gap_extra_below, gap_xy_scaled](const tbb::blocked_range& range) { size_t idx_object_layer_overlapping = size_t(-1); + + auto is_layers_overlap = [](const MyLayer& support_layer, const Layer& object_layer, coordf_t bridging_height = 0.f) -> bool { + if (std::abs(support_layer.print_z - object_layer.print_z) < EPSILON) + return true; + + coordf_t object_lh = bridging_height > EPSILON ? bridging_height : object_layer.height; + if (support_layer.print_z < object_layer.print_z && support_layer.print_z > object_layer.print_z - object_lh) + return true; + + if (support_layer.print_z > object_layer.print_z && support_layer.bottom_z < object_layer.print_z - EPSILON) + return true; + + return false; + }; for (size_t idx_layer = range.begin(); idx_layer < range.end(); ++ idx_layer) { - SupportGeneratorLayer &support_layer = *nonempty_layers[idx_layer]; + MyLayer &support_layer = *nonempty_layers[idx_layer]; // BOOST_LOG_TRIVIAL(trace) << "Support generator - trim_support_layers_by_object - trimmming non-empty layer " << idx_layer << " of " << nonempty_layers.size(); assert(! support_layer.polygons.empty() && support_layer.print_z >= m_slicing_params.raft_contact_top_z + EPSILON); // Find the overlapping object layers including the extra above / below gap. coordf_t z_threshold = support_layer.bottom_print_z() - gap_extra_below + EPSILON; - idx_object_layer_overlapping = idx_higher_or_equal( + idx_object_layer_overlapping = Layer::idx_higher_or_equal( object.layers().begin(), object.layers().end(), idx_object_layer_overlapping, [z_threshold](const Layer *layer){ return layer->print_z >= z_threshold; }); // Collect all the object layers intersecting with this layer. @@ -2491,7 +3333,16 @@ void PrintObjectSupportMaterial::trim_support_layers_by_object( const Layer &object_layer = *object.layers()[i]; if (object_layer.bottom_z() > support_layer.print_z + gap_extra_above - EPSILON) break; - polygons_append(polygons_trimming, offset(object_layer.lslices, gap_xy_scaled, SUPPORT_SURFACES_OFFSET_PARAMETERS)); + + bool is_overlap = is_layers_overlap(support_layer, object_layer); + for (const ExPolygon& expoly : object_layer.lslices) { + // BBS + bool is_sharptail = !intersection_ex({ expoly }, object_layer.sharp_tails).empty(); + coordf_t trimming_offset = is_sharptail ? scale_(sharp_tail_xy_gap) : + is_overlap ? gap_xy_scaled : + scale_(no_overlap_xy_gap); + polygons_append(polygons_trimming, offset({ expoly }, trimming_offset, SUPPORT_SURFACES_OFFSET_PARAMETERS)); + } } if (! m_slicing_params.soluble_interface && m_object_config->thick_bridges) { // Collect all bottom surfaces, which will be extruded with a bridging flow. @@ -2503,8 +3354,11 @@ void PrintObjectSupportMaterial::trim_support_layers_by_object( if (object_layer.print_z - bridging_height > support_layer.print_z + gap_extra_above - EPSILON) break; some_region_overlaps = true; + + bool is_overlap = is_layers_overlap(support_layer, object_layer, bridging_height); + coordf_t trimming_offset = is_overlap ? gap_xy_scaled : scale_(no_overlap_xy_gap); polygons_append(polygons_trimming, - offset(region->fill_surfaces.filter_by_type(stBottomBridge), gap_xy_scaled, SUPPORT_SURFACES_OFFSET_PARAMETERS)); + offset(region->fill_surfaces.filter_by_type(stBottomBridge), trimming_offset, SUPPORT_SURFACES_OFFSET_PARAMETERS)); if (region->region().config().detect_overhang_wall.value) // Add bridging perimeters. SupportMaterialInternal::collect_bridging_perimeter_areas(region->perimeters, gap_xy_scaled, polygons_trimming); @@ -2513,6 +3367,7 @@ void PrintObjectSupportMaterial::trim_support_layers_by_object( break; } } + // $layer->slices contains the full shape of layer, thus including // perimeter's width. $support contains the full shape of support // material, thus including the width of its foremost extrusion. @@ -2523,6 +3378,1483 @@ void PrintObjectSupportMaterial::trim_support_layers_by_object( BOOST_LOG_TRIVIAL(debug) << "PrintObjectSupportMaterial::trim_support_layers_by_object() in parallel - end"; } +PrintObjectSupportMaterial::MyLayersPtr PrintObjectSupportMaterial::generate_raft_base( + const PrintObject &object, + const MyLayersPtr &top_contacts, + const MyLayersPtr &interface_layers, + const MyLayersPtr &base_interface_layers, + const MyLayersPtr &base_layers, + MyLayerStorage &layer_storage) const +{ + // If there is brim to be generated, calculate the trimming regions. + Polygons brim; + if (object.has_brim()) { + // Calculate the area covered by the brim. + const BrimType brim_type = object.config().brim_type; + const bool brim_outer = brim_type == btOuterOnly || brim_type == btOuterAndInner; + const bool brim_inner = brim_type == btInnerOnly || brim_type == btOuterAndInner; + // BBS: the pattern of raft and brim are the same, thus the brim can be serpated by support raft. + const auto brim_object_gap = scaled(object.config().brim_object_gap.value); + //const auto brim_object_gap = scaled(object.config().brim_object_gap.value + object.config().brim_width.value); + for (const ExPolygon &ex : object.layers().front()->lslices) { + if (brim_outer && brim_inner) + polygons_append(brim, offset(ex, brim_object_gap)); + else { + if (brim_outer) + polygons_append(brim, offset(ex.contour, brim_object_gap, ClipperLib::jtRound, float(scale_(0.1)))); + else + brim.emplace_back(ex.contour); + if (brim_inner) { + Polygons holes = ex.holes; + polygons_reverse(holes); + holes = shrink(holes, brim_object_gap, ClipperLib::jtRound, float(scale_(0.1))); + polygons_reverse(holes); + polygons_append(brim, std::move(holes)); + } else + polygons_append(brim, ex.holes); + } + } + brim = union_(brim); + } + + // How much to inflate the support columns to be stable. This also applies to the 1st layer, if no raft layers are to be printed. + const float inflate_factor_fine = float(scale_((m_slicing_params.raft_layers() > 1) ? 0.5 : EPSILON)); + const float inflate_factor_1st_layer = std::max(0.f, float(scale_(object.config().raft_first_layer_expansion)) - inflate_factor_fine); + MyLayer *contacts = top_contacts .empty() ? nullptr : top_contacts .front(); + MyLayer *interfaces = interface_layers .empty() ? nullptr : interface_layers .front(); + MyLayer *base_interfaces = base_interface_layers.empty() ? nullptr : base_interface_layers.front(); + MyLayer *columns_base = base_layers .empty() ? nullptr : base_layers .front(); + if (contacts != nullptr && contacts->print_z > std::max(m_slicing_params.first_print_layer_height, m_slicing_params.raft_contact_top_z) + EPSILON) + // This is not the raft contact layer. + contacts = nullptr; + if (interfaces != nullptr && interfaces->bottom_print_z() > m_slicing_params.raft_interface_top_z + EPSILON) + // This is not the raft column base layer. + interfaces = nullptr; + if (base_interfaces != nullptr && base_interfaces->bottom_print_z() > m_slicing_params.raft_interface_top_z + EPSILON) + // This is not the raft column base layer. + base_interfaces = nullptr; + if (columns_base != nullptr && columns_base->bottom_print_z() > m_slicing_params.raft_interface_top_z + EPSILON) + // This is not the raft interface layer. + columns_base = nullptr; + + Polygons interface_polygons; + if (contacts != nullptr && ! contacts->polygons.empty()) + polygons_append(interface_polygons, expand(contacts->polygons, inflate_factor_fine, SUPPORT_SURFACES_OFFSET_PARAMETERS)); + if (interfaces != nullptr && ! interfaces->polygons.empty()) + polygons_append(interface_polygons, expand(interfaces->polygons, inflate_factor_fine, SUPPORT_SURFACES_OFFSET_PARAMETERS)); + if (base_interfaces != nullptr && ! base_interfaces->polygons.empty()) + polygons_append(interface_polygons, expand(base_interfaces->polygons, inflate_factor_fine, SUPPORT_SURFACES_OFFSET_PARAMETERS)); + + // Output vector. + MyLayersPtr raft_layers; + + if (m_slicing_params.raft_layers() > 1) { + Polygons base; + Polygons columns; + if (columns_base != nullptr) { + base = columns_base->polygons; + columns = base; + if (! interface_polygons.empty()) + // Trim the 1st layer columns with the inflated interface polygons. + columns = diff(columns, interface_polygons); + } + if (! interface_polygons.empty()) { + // Merge the untrimmed columns base with the expanded raft interface, to be used for the support base and interface. + base = union_(base, interface_polygons); + } + // Do not add the raft contact layer, only add the raft layers below the contact layer. + // Insert the 1st layer. + { + MyLayer &new_layer = layer_allocate(layer_storage, (m_slicing_params.base_raft_layers > 0) ? sltRaftBase : sltRaftInterface); + raft_layers.push_back(&new_layer); + new_layer.print_z = m_slicing_params.first_print_layer_height; + new_layer.height = m_slicing_params.first_print_layer_height; + new_layer.bottom_z = 0.; + new_layer.polygons = inflate_factor_1st_layer > 0 ? expand(base, inflate_factor_1st_layer) : base; + } + // Insert the base layers. + for (size_t i = 1; i < m_slicing_params.base_raft_layers; ++ i) { + coordf_t print_z = raft_layers.back()->print_z; + MyLayer &new_layer = layer_allocate(layer_storage, sltRaftBase); + raft_layers.push_back(&new_layer); + new_layer.print_z = print_z + m_slicing_params.base_raft_layer_height; + new_layer.height = m_slicing_params.base_raft_layer_height; + new_layer.bottom_z = print_z; + new_layer.polygons = base; + } + // Insert the interface layers. + for (size_t i = 1; i < m_slicing_params.interface_raft_layers; ++ i) { + coordf_t print_z = raft_layers.back()->print_z; + MyLayer &new_layer = layer_allocate(layer_storage, sltRaftInterface); + raft_layers.push_back(&new_layer); + new_layer.print_z = print_z + m_slicing_params.interface_raft_layer_height; + new_layer.height = m_slicing_params.interface_raft_layer_height; + new_layer.bottom_z = print_z; + new_layer.polygons = interface_polygons; + //FIXME misusing contact_polygons for support columns. + new_layer.contact_polygons = std::make_unique(columns); + } + } else { + if (columns_base != nullptr) { + // Expand the bases of the support columns in the 1st layer. + Polygons &raft = columns_base->polygons; + Polygons trimming; + // BBS: if first layer of support is intersected with object island, it must have the same function as brim unless in nobrim mode. + if (object.has_brim()) + trimming = offset(m_object->layers().front()->lslices, (float)scale_(object.config().brim_object_gap.value), SUPPORT_SURFACES_OFFSET_PARAMETERS); + else + trimming = offset(m_object->layers().front()->lslices, (float)scale_(m_support_params.gap_xy), SUPPORT_SURFACES_OFFSET_PARAMETERS); + if (inflate_factor_1st_layer > SCALED_EPSILON) { + // Inflate in multiple steps to avoid leaking of the support 1st layer through object walls. + auto nsteps = std::max(5, int(ceil(inflate_factor_1st_layer / m_support_params.first_layer_flow.scaled_width()))); + float step = inflate_factor_1st_layer / nsteps; + for (int i = 0; i < nsteps; ++ i) + raft = diff(expand(raft, step), trimming); + } else + raft = diff(raft, trimming); + if (! interface_polygons.empty()) + columns_base->polygons = diff(columns_base->polygons, interface_polygons); + } + if (! brim.empty()) { + if (columns_base) + columns_base->polygons = diff(columns_base->polygons, brim); + if (contacts) + contacts->polygons = diff(contacts->polygons, brim); + if (interfaces) + interfaces->polygons = diff(interfaces->polygons, brim); + if (base_interfaces) + base_interfaces->polygons = diff(base_interfaces->polygons, brim); + } + } + + return raft_layers; +} + +// Convert some of the intermediate layers into top/bottom interface layers as well as base interface layers. +std::pair PrintObjectSupportMaterial::generate_interface_layers( + const MyLayersPtr &bottom_contacts, + const MyLayersPtr &top_contacts, + MyLayersPtr &intermediate_layers, + MyLayerStorage &layer_storage) const +{ +// my $area_threshold = $self->interface_flow->scaled_spacing ** 2; + + std::pair base_and_interface_layers; + MyLayersPtr &interface_layers = base_and_interface_layers.first; + MyLayersPtr &base_interface_layers = base_and_interface_layers.second; + + // distinguish between interface and base interface layers + // Contact layer is considered an interface layer, therefore run the following block only if support_interface_top_layers > 1. + // Contact layer needs a base_interface layer, therefore run the following block if support_interface_top_layers > 0, has soluble support and extruders are different. + bool soluble_interface_non_soluble_base = + // Zero z-gap between the overhangs and the support interface. + m_slicing_params.soluble_interface && + // Interface extruder soluble. + m_object_config->support_interface_filament.value > 0 && m_print_config->filament_soluble.get_at(m_object_config->support_interface_filament.value - 1) && + // Base extruder: Either "print with active extruder" not soluble. + (m_object_config->support_filament.value == 0 || ! m_print_config->filament_soluble.get_at(m_object_config->support_filament.value - 1)); + bool snug_supports = m_object_config->support_style.value == smsSnug; + // BBS: if support interface and support base do not use the same filament, add a base layer to improve their adhesion + bool differnt_support_interface_filament = m_object_config->support_interface_filament.value != m_object_config->support_filament.value; + int num_base_interface_layers_top = differnt_support_interface_filament ? 1 : 0; + int num_base_interface_layers_bottom = differnt_support_interface_filament ? 1 : 0; + int num_interface_layers_top = m_object_config->support_interface_top_layers + num_base_interface_layers_top; + int num_interface_layers_bottom = m_object_config->support_interface_bottom_layers + num_base_interface_layers_bottom; + if (num_interface_layers_bottom < 0) + num_interface_layers_bottom = num_interface_layers_top; + + if (! intermediate_layers.empty() && (num_interface_layers_top > 1 || num_interface_layers_bottom > 1)) { + // For all intermediate layers, collect top contact surfaces, which are not further than support_interface_top_layers. + BOOST_LOG_TRIVIAL(debug) << "PrintObjectSupportMaterial::generate_interface_layers() in parallel - start"; + // Since the intermediate layer index starts at zero the number of interface layer needs to be reduced by 1. + -- num_interface_layers_top; + -- num_interface_layers_bottom; + int num_interface_layers_only_top = num_interface_layers_top - num_base_interface_layers_top; + int num_interface_layers_only_bottom = num_interface_layers_bottom - num_base_interface_layers_bottom; + interface_layers.assign(intermediate_layers.size(), nullptr); + if (num_base_interface_layers_top || num_base_interface_layers_bottom) + base_interface_layers.assign(intermediate_layers.size(), nullptr); + auto smoothing_distance = m_support_params.support_material_interface_flow.scaled_spacing() * 1.5; + auto minimum_island_radius = m_support_params.support_material_interface_flow.scaled_spacing() / m_support_params.interface_density; + auto closing_distance = smoothing_distance; // scaled(m_object_config->support_closing_radius.value); + tbb::spin_mutex layer_storage_mutex; + // Insert a new layer into base_interface_layers, if intersection with base exists. + auto insert_layer = [&layer_storage, &layer_storage_mutex, snug_supports, closing_distance, smoothing_distance, minimum_island_radius]( + MyLayer &intermediate_layer, Polygons &bottom, Polygons &&top, const Polygons *subtract, SupporLayerType type) -> MyLayer* { + assert(! bottom.empty() || ! top.empty()); + // Merge top into bottom, unite them with a safety offset. + append(bottom, std::move(top)); + // Merge top / bottom interfaces. For snug supports, merge using closing distance and regularize (close concave corners). + bottom = intersection( + snug_supports ? + smooth_outward(closing(std::move(bottom), closing_distance + minimum_island_radius, closing_distance, SUPPORT_SURFACES_OFFSET_PARAMETERS), smoothing_distance) : + union_safety_offset(std::move(bottom)), + intermediate_layer.polygons); + if (! bottom.empty()) { + //FIXME Remove non-printable tiny islands, let them be printed using the base support. + //bottom = opening(std::move(bottom), minimum_island_radius); + if (! bottom.empty()) { + MyLayer &layer_new = layer_allocate(layer_storage, layer_storage_mutex, type); + layer_new.polygons = std::move(bottom); + layer_new.print_z = intermediate_layer.print_z; + layer_new.bottom_z = intermediate_layer.bottom_z; + layer_new.height = intermediate_layer.height; + layer_new.bridging = intermediate_layer.bridging; + // Subtract the interface from the base regions. + intermediate_layer.polygons = diff(intermediate_layer.polygons, layer_new.polygons); + if (subtract) + // Trim the base interface layer with the interface layer. + layer_new.polygons = diff(std::move(layer_new.polygons), *subtract); + //FIXME filter layer_new.polygons islands by a minimum area? + // $interface_area = [ grep abs($_->area) >= $area_threshold, @$interface_area ]; + return &layer_new; + } + } + return nullptr; + }; + tbb::parallel_for(tbb::blocked_range(0, int(intermediate_layers.size())), + [&bottom_contacts, &top_contacts, &intermediate_layers, &insert_layer, + num_interface_layers_top, num_interface_layers_bottom, num_base_interface_layers_top, num_base_interface_layers_bottom, num_interface_layers_only_top, num_interface_layers_only_bottom, + snug_supports, &interface_layers, &base_interface_layers](const tbb::blocked_range& range) { + // Gather the top / bottom contact layers intersecting with num_interface_layers resp. num_interface_layers_only intermediate layers above / below + // this intermediate layer. + // Index of the first top contact layer intersecting the current intermediate layer. + auto idx_top_contact_first = -1; + // Index of the first bottom contact layer intersecting the current intermediate layer. + auto idx_bottom_contact_first = -1; + auto num_intermediate = int(intermediate_layers.size()); + for (int idx_intermediate_layer = range.begin(); idx_intermediate_layer < range.end(); ++ idx_intermediate_layer) { + MyLayer &intermediate_layer = *intermediate_layers[idx_intermediate_layer]; + Polygons polygons_top_contact_projected_interface; + Polygons polygons_top_contact_projected_base; + Polygons polygons_bottom_contact_projected_interface; + Polygons polygons_bottom_contact_projected_base; + if (num_interface_layers_top > 0) { + // Top Z coordinate of a slab, over which we are collecting the top / bottom contact surfaces + coordf_t top_z = intermediate_layers[std::min(num_intermediate - 1, idx_intermediate_layer + num_interface_layers_top - 1)]->print_z; + coordf_t top_inteface_z = std::numeric_limits::max(); + if (num_base_interface_layers_top > 0) + // Some top base interface layers will be generated. + top_inteface_z = num_interface_layers_only_top == 0 ? + // Only base interface layers to generate. + - std::numeric_limits::max() : + intermediate_layers[std::min(num_intermediate - 1, idx_intermediate_layer + num_interface_layers_only_top - 1)]->print_z; + // Move idx_top_contact_first up until above the current print_z. + idx_top_contact_first = idx_higher_or_equal(top_contacts, idx_top_contact_first, [&intermediate_layer](const MyLayer *layer){ return layer->print_z >= intermediate_layer.print_z; }); // - EPSILON + // Collect the top contact areas above this intermediate layer, below top_z. + for (int idx_top_contact = idx_top_contact_first; idx_top_contact < int(top_contacts.size()); ++ idx_top_contact) { + const MyLayer &top_contact_layer = *top_contacts[idx_top_contact]; + //FIXME maybe this adds one interface layer in excess? + if (top_contact_layer.bottom_z - EPSILON > top_z) + break; + polygons_append(top_contact_layer.bottom_z - EPSILON > top_inteface_z ? polygons_top_contact_projected_base : polygons_top_contact_projected_interface, + // For snug supports, project the overhang polygons covering the whole overhang, so that they will merge without a gap with support polygons of the other layers. + // For grid supports, merging of support regions will be performed by the projection into grid. + snug_supports ? *top_contact_layer.overhang_polygons : top_contact_layer.polygons); + } + } + if (num_interface_layers_bottom > 0) { + // Bottom Z coordinate of a slab, over which we are collecting the top / bottom contact surfaces + coordf_t bottom_z = intermediate_layers[std::max(0, idx_intermediate_layer - num_interface_layers_bottom + 1)]->bottom_z; + coordf_t bottom_interface_z = - std::numeric_limits::max(); + if (num_base_interface_layers_bottom > 0) + // Some bottom base interface layers will be generated. + bottom_interface_z = num_interface_layers_only_bottom == 0 ? + // Only base interface layers to generate. + std::numeric_limits::max() : + intermediate_layers[std::max(0, idx_intermediate_layer - num_interface_layers_only_bottom)]->bottom_z; + // Move idx_bottom_contact_first up until touching bottom_z. + idx_bottom_contact_first = idx_higher_or_equal(bottom_contacts, idx_bottom_contact_first, [bottom_z](const MyLayer *layer){ return layer->print_z >= bottom_z - EPSILON; }); + // Collect the top contact areas above this intermediate layer, below top_z. + for (int idx_bottom_contact = idx_bottom_contact_first; idx_bottom_contact < int(bottom_contacts.size()); ++ idx_bottom_contact) { + const MyLayer &bottom_contact_layer = *bottom_contacts[idx_bottom_contact]; + if (bottom_contact_layer.print_z - EPSILON > intermediate_layer.bottom_z) + break; + polygons_append(bottom_contact_layer.print_z - EPSILON > bottom_interface_z ? polygons_bottom_contact_projected_interface : polygons_bottom_contact_projected_base, bottom_contact_layer.polygons); + } + } + MyLayer *interface_layer = nullptr; + if (! polygons_bottom_contact_projected_interface.empty() || ! polygons_top_contact_projected_interface.empty()) { + interface_layer = insert_layer( + intermediate_layer, polygons_bottom_contact_projected_interface, std::move(polygons_top_contact_projected_interface), nullptr, + polygons_top_contact_projected_interface.empty() ? sltBottomInterface : sltTopInterface); + interface_layers[idx_intermediate_layer] = interface_layer; + } + if (! polygons_bottom_contact_projected_base.empty() || ! polygons_top_contact_projected_base.empty()) + base_interface_layers[idx_intermediate_layer] = insert_layer( + intermediate_layer, polygons_bottom_contact_projected_base, std::move(polygons_top_contact_projected_base), + interface_layer ? &interface_layer->polygons : nullptr, sltBase); + } + }); + + // Compress contact_out, remove the nullptr items. + remove_nulls(interface_layers); + remove_nulls(base_interface_layers); + BOOST_LOG_TRIVIAL(debug) << "PrintObjectSupportMaterial::generate_interface_layers() in parallel - end"; + } + + return base_and_interface_layers; +} + +static inline void fill_expolygon_generate_paths( + ExtrusionEntitiesPtr &dst, + ExPolygon &&expolygon, + Fill *filler, + const FillParams &fill_params, + ExtrusionRole role, + const Flow &flow) +{ + Surface surface(stInternal, std::move(expolygon)); + Polylines polylines; + try { + polylines = filler->fill_surface(&surface, fill_params); + } catch (InfillFailedException &) { + } + extrusion_entities_append_paths( + dst, + std::move(polylines), + role, + flow.mm3_per_mm(), flow.width(), flow.height()); +} + +static inline void fill_expolygons_generate_paths( + ExtrusionEntitiesPtr &dst, + ExPolygons &&expolygons, + Fill *filler, + const FillParams &fill_params, + ExtrusionRole role, + const Flow &flow) +{ + for (ExPolygon &expoly : expolygons) + fill_expolygon_generate_paths(dst, std::move(expoly), filler, fill_params, role, flow); +} + +static inline void fill_expolygons_generate_paths( + ExtrusionEntitiesPtr &dst, + ExPolygons &&expolygons, + Fill *filler, + float density, + ExtrusionRole role, + const Flow &flow) +{ + FillParams fill_params; + fill_params.density = density; + fill_params.dont_adjust = true; + fill_expolygons_generate_paths(dst, std::move(expolygons), filler, fill_params, role, flow); +} + +static inline void fill_expolygons_with_sheath_generate_paths( + ExtrusionEntitiesPtr &dst, + const Polygons &polygons, + Fill *filler, + float density, + ExtrusionRole role, + const Flow &flow, + bool with_sheath, + bool no_sort) +{ + if (polygons.empty()) + return; + + if (! with_sheath) { + fill_expolygons_generate_paths(dst, closing_ex(polygons, float(SCALED_EPSILON)), filler, density, role, flow); + return; + } + + FillParams fill_params; + fill_params.density = density; + fill_params.dont_adjust = true; + + double spacing = flow.scaled_spacing(); + // Clip the sheath path to avoid the extruder to get exactly on the first point of the loop. + double clip_length = spacing * 0.15; + + for (ExPolygon &expoly : closing_ex(polygons, float(SCALED_EPSILON), float(SCALED_EPSILON + 0.5*flow.scaled_width()))) { + // Don't reorder the skirt and its infills. + std::unique_ptr eec; + if (no_sort) { + eec = std::make_unique(); + eec->no_sort = true; + } + ExtrusionEntitiesPtr &out = no_sort ? eec->entities : dst; + // Draw the perimeters. + Polylines polylines; + polylines.reserve(expoly.holes.size() + 1); + for (size_t i = 0; i <= expoly.holes.size(); ++ i) { + Polyline pl(i == 0 ? expoly.contour.points : expoly.holes[i - 1].points); + pl.points.emplace_back(pl.points.front()); + pl.clip_end(clip_length); + polylines.emplace_back(std::move(pl)); + } + extrusion_entities_append_paths(out, polylines, erSupportMaterial, flow.mm3_per_mm(), flow.width(), flow.height()); + // Fill in the rest. + fill_expolygons_generate_paths(out, offset_ex(expoly, float(-0.4 * spacing)), filler, fill_params, role, flow); + if (no_sort && ! eec->empty()) + dst.emplace_back(eec.release()); + } +} + +// Support layers, partially processed. +struct MyLayerExtruded +{ + MyLayerExtruded& operator=(MyLayerExtruded &&rhs) { + this->layer = rhs.layer; + this->extrusions = std::move(rhs.extrusions); + m_polygons_to_extrude = std::move(rhs.m_polygons_to_extrude); + rhs.layer = nullptr; + return *this; + } + + bool empty() const { + return layer == nullptr || layer->polygons.empty(); + } + + void set_polygons_to_extrude(Polygons &&polygons) { + if (m_polygons_to_extrude == nullptr) + m_polygons_to_extrude = std::make_unique(std::move(polygons)); + else + *m_polygons_to_extrude = std::move(polygons); + } + Polygons& polygons_to_extrude() { return (m_polygons_to_extrude == nullptr) ? layer->polygons : *m_polygons_to_extrude; } + const Polygons& polygons_to_extrude() const { return (m_polygons_to_extrude == nullptr) ? layer->polygons : *m_polygons_to_extrude; } + + bool could_merge(const MyLayerExtruded &other) const { + return ! this->empty() && ! other.empty() && + std::abs(this->layer->height - other.layer->height) < EPSILON && + this->layer->bridging == other.layer->bridging; + } + + // Merge regions, perform boolean union over the merged polygons. + void merge(MyLayerExtruded &&other) { + assert(this->could_merge(other)); + // 1) Merge the rest polygons to extrude, if there are any. + if (other.m_polygons_to_extrude != nullptr) { + if (m_polygons_to_extrude == nullptr) { + // This layer has no extrusions generated yet, if it has no m_polygons_to_extrude (its area to extrude was not reduced yet). + assert(this->extrusions.empty()); + m_polygons_to_extrude = std::make_unique(this->layer->polygons); + } + Slic3r::polygons_append(*m_polygons_to_extrude, std::move(*other.m_polygons_to_extrude)); + *m_polygons_to_extrude = union_safety_offset(*m_polygons_to_extrude); + other.m_polygons_to_extrude.reset(); + } else if (m_polygons_to_extrude != nullptr) { + assert(other.m_polygons_to_extrude == nullptr); + // The other layer has no extrusions generated yet, if it has no m_polygons_to_extrude (its area to extrude was not reduced yet). + assert(other.extrusions.empty()); + Slic3r::polygons_append(*m_polygons_to_extrude, other.layer->polygons); + *m_polygons_to_extrude = union_safety_offset(*m_polygons_to_extrude); + } + // 2) Merge the extrusions. + this->extrusions.insert(this->extrusions.end(), other.extrusions.begin(), other.extrusions.end()); + other.extrusions.clear(); + // 3) Merge the infill polygons. + Slic3r::polygons_append(this->layer->polygons, std::move(other.layer->polygons)); + this->layer->polygons = union_safety_offset(this->layer->polygons); + other.layer->polygons.clear(); + } + + void polygons_append(Polygons &dst) const { + if (layer != NULL && ! layer->polygons.empty()) + Slic3r::polygons_append(dst, layer->polygons); + } + + // The source layer. It carries the height and extrusion type (bridging / non bridging, extrusion height). + PrintObjectSupportMaterial::MyLayer *layer { nullptr }; + // Collect extrusions. They will be exported sorted by the bottom height. + ExtrusionEntitiesPtr extrusions; + +private: + // In case the extrusions are non-empty, m_polygons_to_extrude may contain the rest areas yet to be filled by additional support. + // This is useful mainly for the loop interfaces, which are generated before the zig-zag infills. + std::unique_ptr m_polygons_to_extrude; +}; + +typedef std::vector MyLayerExtrudedPtrs; + +struct LoopInterfaceProcessor +{ + LoopInterfaceProcessor(coordf_t circle_r) : + n_contact_loops(0), + circle_radius(circle_r), + circle_distance(circle_r * 3.) + { + // Shape of the top contact area. + circle.points.reserve(6); + for (size_t i = 0; i < 6; ++ i) { + double angle = double(i) * M_PI / 3.; + circle.points.push_back(Point(circle_radius * cos(angle), circle_radius * sin(angle))); + } + } + + // Generate loop contacts at the top_contact_layer, + // trim the top_contact_layer->polygons with the areas covered by the loops. + void generate(MyLayerExtruded &top_contact_layer, const Flow &interface_flow_src) const; + + int n_contact_loops; + coordf_t circle_radius; + coordf_t circle_distance; + Polygon circle; +}; + +void LoopInterfaceProcessor::generate(MyLayerExtruded &top_contact_layer, const Flow &interface_flow_src) const +{ + if (n_contact_loops == 0 || top_contact_layer.empty()) + return; + + Flow flow = interface_flow_src.with_height(top_contact_layer.layer->height); + + Polygons overhang_polygons; + if (top_contact_layer.layer->overhang_polygons != nullptr) + overhang_polygons = std::move(*top_contact_layer.layer->overhang_polygons); + + // Generate the outermost loop. + // Find centerline of the external loop (or any other kind of extrusions should the loop be skipped) + ExPolygons top_contact_expolygons = offset_ex(union_ex(top_contact_layer.layer->polygons), - 0.5f * flow.scaled_width()); + + // Grid size and bit shifts for quick and exact to/from grid coordinates manipulation. + coord_t circle_grid_resolution = 1; + coord_t circle_grid_powerof2 = 0; + { + // epsilon to account for rounding errors + coord_t circle_grid_resolution_non_powerof2 = coord_t(2. * circle_distance + 3.); + while (circle_grid_resolution < circle_grid_resolution_non_powerof2) { + circle_grid_resolution <<= 1; + ++ circle_grid_powerof2; + } + } + + struct PointAccessor { + const Point* operator()(const Point &pt) const { return &pt; } + }; + typedef ClosestPointInRadiusLookup ClosestPointLookupType; + + Polygons loops0; + { + // find centerline of the external loop of the contours + // Only consider the loops facing the overhang. + Polygons external_loops; + // Holes in the external loops. + Polygons circles; + Polygons overhang_with_margin = offset(union_ex(overhang_polygons), 0.5f * flow.scaled_width()); + for (ExPolygons::iterator it_contact_expoly = top_contact_expolygons.begin(); it_contact_expoly != top_contact_expolygons.end(); ++ it_contact_expoly) { + // Store the circle centers placed for an expolygon into a regular grid, hashed by the circle centers. + ClosestPointLookupType circle_centers_lookup(coord_t(circle_distance - SCALED_EPSILON)); + Points circle_centers; + Point center_last; + // For each contour of the expolygon, start with the outer contour, continue with the holes. + for (size_t i_contour = 0; i_contour <= it_contact_expoly->holes.size(); ++ i_contour) { + Polygon &contour = (i_contour == 0) ? it_contact_expoly->contour : it_contact_expoly->holes[i_contour - 1]; + const Point *seg_current_pt = nullptr; + coordf_t seg_current_t = 0.; + if (! intersection_pl(contour.split_at_first_point(), overhang_with_margin).empty()) { + // The contour is below the overhang at least to some extent. + //FIXME ideally one would place the circles below the overhang only. + // Walk around the contour and place circles so their centers are not closer than circle_distance from each other. + if (circle_centers.empty()) { + // Place the first circle. + seg_current_pt = &contour.points.front(); + seg_current_t = 0.; + center_last = *seg_current_pt; + circle_centers_lookup.insert(center_last); + circle_centers.push_back(center_last); + } + for (Points::const_iterator it = contour.points.begin() + 1; it != contour.points.end(); ++it) { + // Is it possible to place a circle on this segment? Is it not too close to any of the circles already placed on this contour? + const Point &p1 = *(it-1); + const Point &p2 = *it; + // Intersection of a ray (p1, p2) with a circle placed at center_last, with radius of circle_distance. + const Vec2d v_seg(coordf_t(p2(0)) - coordf_t(p1(0)), coordf_t(p2(1)) - coordf_t(p1(1))); + const Vec2d v_cntr(coordf_t(p1(0) - center_last(0)), coordf_t(p1(1) - center_last(1))); + coordf_t a = v_seg.squaredNorm(); + coordf_t b = 2. * v_seg.dot(v_cntr); + coordf_t c = v_cntr.squaredNorm() - circle_distance * circle_distance; + coordf_t disc = b * b - 4. * a * c; + if (disc > 0.) { + // The circle intersects a ray. Avoid the parts of the segment inside the circle. + coordf_t t1 = (-b - sqrt(disc)) / (2. * a); + coordf_t t2 = (-b + sqrt(disc)) / (2. * a); + coordf_t t0 = (seg_current_pt == &p1) ? seg_current_t : 0.; + // Take the lowest t in , excluding . + coordf_t t; + if (t0 <= t1) + t = t0; + else if (t2 <= 1.) + t = t2; + else { + // Try the following segment. + seg_current_pt = nullptr; + continue; + } + seg_current_pt = &p1; + seg_current_t = t; + center_last = Point(p1(0) + coord_t(v_seg(0) * t), p1(1) + coord_t(v_seg(1) * t)); + // It has been verified that the new point is far enough from center_last. + // Ensure, that it is far enough from all the centers. + std::pair circle_closest = circle_centers_lookup.find(center_last); + if (circle_closest.first != nullptr) { + -- it; + continue; + } + } else { + // All of the segment is outside the circle. Take the first point. + seg_current_pt = &p1; + seg_current_t = 0.; + center_last = p1; + } + // Place the first circle. + circle_centers_lookup.insert(center_last); + circle_centers.push_back(center_last); + } + external_loops.push_back(std::move(contour)); + for (const Point ¢er : circle_centers) { + circles.push_back(circle); + circles.back().translate(center); + } + } + } + } + // Apply a pattern to the external loops. + loops0 = diff(external_loops, circles); + } + + Polylines loop_lines; + { + // make more loops + Polygons loop_polygons = loops0; + for (int i = 1; i < n_contact_loops; ++ i) + polygons_append(loop_polygons, + opening( + loops0, + i * flow.scaled_spacing() + 0.5f * flow.scaled_spacing(), + 0.5f * flow.scaled_spacing())); + // Clip such loops to the side oriented towards the object. + // Collect split points, so they will be recognized after the clipping. + // At the split points the clipped pieces will be stitched back together. + loop_lines.reserve(loop_polygons.size()); + std::unordered_map map_split_points; + for (Polygons::const_iterator it = loop_polygons.begin(); it != loop_polygons.end(); ++ it) { + assert(map_split_points.find(it->first_point()) == map_split_points.end()); + map_split_points[it->first_point()] = -1; + loop_lines.push_back(it->split_at_first_point()); + } + loop_lines = intersection_pl(loop_lines, expand(overhang_polygons, scale_(SUPPORT_MATERIAL_MARGIN))); + // Because a closed loop has been split to a line, loop_lines may contain continuous segments split to 2 pieces. + // Try to connect them. + for (int i_line = 0; i_line < int(loop_lines.size()); ++ i_line) { + Polyline &polyline = loop_lines[i_line]; + auto it = map_split_points.find(polyline.first_point()); + if (it != map_split_points.end()) { + // This is a stitching point. + // If this assert triggers, multiple source polygons likely intersected at this point. + assert(it->second != -2); + if (it->second < 0) { + // First occurence. + it->second = i_line; + } else { + // Second occurence. Join the lines. + Polyline &polyline_1st = loop_lines[it->second]; + assert(polyline_1st.first_point() == it->first || polyline_1st.last_point() == it->first); + if (polyline_1st.first_point() == it->first) + polyline_1st.reverse(); + polyline_1st.append(std::move(polyline)); + it->second = -2; + } + continue; + } + it = map_split_points.find(polyline.last_point()); + if (it != map_split_points.end()) { + // This is a stitching point. + // If this assert triggers, multiple source polygons likely intersected at this point. + assert(it->second != -2); + if (it->second < 0) { + // First occurence. + it->second = i_line; + } else { + // Second occurence. Join the lines. + Polyline &polyline_1st = loop_lines[it->second]; + assert(polyline_1st.first_point() == it->first || polyline_1st.last_point() == it->first); + if (polyline_1st.first_point() == it->first) + polyline_1st.reverse(); + polyline.reverse(); + polyline_1st.append(std::move(polyline)); + it->second = -2; + } + } + } + // Remove empty lines. + remove_degenerate(loop_lines); + } + + // add the contact infill area to the interface area + // note that growing loops by $circle_radius ensures no tiny + // extrusions are left inside the circles; however it creates + // a very large gap between loops and contact_infill_polygons, so maybe another + // solution should be found to achieve both goals + // Store the trimmed polygons into a separate polygon set, so the original infill area remains intact for + // "modulate by layer thickness". + top_contact_layer.set_polygons_to_extrude(diff(top_contact_layer.layer->polygons, offset(loop_lines, float(circle_radius * 1.1)))); + + // Transform loops into ExtrusionPath objects. + extrusion_entities_append_paths( + top_contact_layer.extrusions, + std::move(loop_lines), + erSupportMaterialInterface, flow.mm3_per_mm(), flow.width(), flow.height()); +} + +#ifdef SLIC3R_DEBUG +static std::string dbg_index_to_color(int idx) +{ + if (idx < 0) + return "yellow"; + idx = idx % 3; + switch (idx) { + case 0: return "red"; + case 1: return "green"; + default: return "blue"; + } +} +#endif /* SLIC3R_DEBUG */ + +// When extruding a bottom interface layer over an object, the bottom interface layer is extruded in a thin air, therefore +// it is being extruded with a bridging flow to not shrink excessively (the die swell effect). +// Tiny extrusions are better avoided and it is always better to anchor the thread to an existing support structure if possible. +// Therefore the bottom interface spots are expanded a bit. The expanded regions may overlap with another bottom interface layers, +// leading to over extrusion, where they overlap. The over extrusion is better avoided as it often makes the interface layers +// to stick too firmly to the object. +// +// Modulate thickness (increase bottom_z) of extrusions_in_out generated for this_layer +// if they overlap with overlapping_layers, whose print_z is above this_layer.bottom_z() and below this_layer.print_z. +void modulate_extrusion_by_overlapping_layers( + // Extrusions generated for this_layer. + ExtrusionEntitiesPtr &extrusions_in_out, + const PrintObjectSupportMaterial::MyLayer &this_layer, + // Multiple layers overlapping with this_layer, sorted bottom up. + const PrintObjectSupportMaterial::MyLayersPtr &overlapping_layers) +{ + size_t n_overlapping_layers = overlapping_layers.size(); + if (n_overlapping_layers == 0 || extrusions_in_out.empty()) + // The extrusions do not overlap with any other extrusion. + return; + + // Get the initial extrusion parameters. + ExtrusionPath *extrusion_path_template = dynamic_cast(extrusions_in_out.front()); + assert(extrusion_path_template != nullptr); + ExtrusionRole extrusion_role = extrusion_path_template->role(); + float extrusion_width = extrusion_path_template->width; + + struct ExtrusionPathFragment + { + ExtrusionPathFragment() : mm3_per_mm(-1), width(-1), height(-1) {}; + ExtrusionPathFragment(double mm3_per_mm, float width, float height) : mm3_per_mm(mm3_per_mm), width(width), height(height) {}; + + Polylines polylines; + double mm3_per_mm; + float width; + float height; + }; + + // Split the extrusions by the overlapping layers, reduce their extrusion rate. + // The last path_fragment is from this_layer. + std::vector path_fragments( + n_overlapping_layers + 1, + ExtrusionPathFragment(extrusion_path_template->mm3_per_mm, extrusion_path_template->width, extrusion_path_template->height)); + // Don't use it, it will be released. + extrusion_path_template = nullptr; + +#ifdef SLIC3R_DEBUG + static int iRun = 0; + ++ iRun; + BoundingBox bbox; + for (size_t i_overlapping_layer = 0; i_overlapping_layer < n_overlapping_layers; ++ i_overlapping_layer) { + const PrintObjectSupportMaterial::MyLayer &overlapping_layer = *overlapping_layers[i_overlapping_layer]; + bbox.merge(get_extents(overlapping_layer.polygons)); + } + for (ExtrusionEntitiesPtr::const_iterator it = extrusions_in_out.begin(); it != extrusions_in_out.end(); ++ it) { + ExtrusionPath *path = dynamic_cast(*it); + assert(path != nullptr); + bbox.merge(get_extents(path->polyline)); + } + SVG svg(debug_out_path("support-fragments-%d-%lf.svg", iRun, this_layer.print_z).c_str(), bbox); + const float transparency = 0.5f; + // Filled polygons for the overlapping regions. + svg.draw(union_ex(this_layer.polygons), dbg_index_to_color(-1), transparency); + for (size_t i_overlapping_layer = 0; i_overlapping_layer < n_overlapping_layers; ++ i_overlapping_layer) { + const PrintObjectSupportMaterial::MyLayer &overlapping_layer = *overlapping_layers[i_overlapping_layer]; + svg.draw(union_ex(overlapping_layer.polygons), dbg_index_to_color(int(i_overlapping_layer)), transparency); + } + // Contours of the overlapping regions. + svg.draw(to_polylines(this_layer.polygons), dbg_index_to_color(-1), scale_(0.2)); + for (size_t i_overlapping_layer = 0; i_overlapping_layer < n_overlapping_layers; ++ i_overlapping_layer) { + const PrintObjectSupportMaterial::MyLayer &overlapping_layer = *overlapping_layers[i_overlapping_layer]; + svg.draw(to_polylines(overlapping_layer.polygons), dbg_index_to_color(int(i_overlapping_layer)), scale_(0.1)); + } + // Fill extrusion, the source. + for (ExtrusionEntitiesPtr::const_iterator it = extrusions_in_out.begin(); it != extrusions_in_out.end(); ++ it) { + ExtrusionPath *path = dynamic_cast(*it); + std::string color_name; + switch ((it - extrusions_in_out.begin()) % 9) { + case 0: color_name = "magenta"; break; + case 1: color_name = "deepskyblue"; break; + case 2: color_name = "coral"; break; + case 3: color_name = "goldenrod"; break; + case 4: color_name = "orange"; break; + case 5: color_name = "olivedrab"; break; + case 6: color_name = "blueviolet"; break; + case 7: color_name = "brown"; break; + default: color_name = "orchid"; break; + } + svg.draw(path->polyline, color_name, scale_(0.2)); + } +#endif /* SLIC3R_DEBUG */ + + // End points of the original paths. + std::vector> path_ends; + // Collect the paths of this_layer. + { + Polylines &polylines = path_fragments.back().polylines; + for (ExtrusionEntity *ee : extrusions_in_out) { + ExtrusionPath *path = dynamic_cast(ee); + assert(path != nullptr); + polylines.emplace_back(Polyline(std::move(path->polyline))); + path_ends.emplace_back(std::pair(polylines.back().points.front(), polylines.back().points.back())); + } + } + // Destroy the original extrusion paths, their polylines were moved to path_fragments already. + // This will be the destination for the new paths. + extrusions_in_out.clear(); + + // Fragment the path segments by overlapping layers. The overlapping layers are sorted by an increasing print_z. + // Trim by the highest overlapping layer first. + for (int i_overlapping_layer = int(n_overlapping_layers) - 1; i_overlapping_layer >= 0; -- i_overlapping_layer) { + const PrintObjectSupportMaterial::MyLayer &overlapping_layer = *overlapping_layers[i_overlapping_layer]; + ExtrusionPathFragment &frag = path_fragments[i_overlapping_layer]; + Polygons polygons_trimming = offset(union_ex(overlapping_layer.polygons), float(scale_(0.5*extrusion_width))); + frag.polylines = intersection_pl(path_fragments.back().polylines, polygons_trimming); + path_fragments.back().polylines = diff_pl(path_fragments.back().polylines, polygons_trimming); + // Adjust the extrusion parameters for a reduced layer height and a non-bridging flow (nozzle_dmr = -1, does not matter). + assert(this_layer.print_z > overlapping_layer.print_z); + frag.height = float(this_layer.print_z - overlapping_layer.print_z); + frag.mm3_per_mm = Flow(frag.width, frag.height, -1.f).mm3_per_mm(); +#ifdef SLIC3R_DEBUG + svg.draw(frag.polylines, dbg_index_to_color(i_overlapping_layer), scale_(0.1)); +#endif /* SLIC3R_DEBUG */ + } + +#ifdef SLIC3R_DEBUG + svg.draw(path_fragments.back().polylines, dbg_index_to_color(-1), scale_(0.1)); + svg.Close(); +#endif /* SLIC3R_DEBUG */ + + // Now chain the split segments using hashing and a nearly exact match, maintaining the order of segments. + // Create a single ExtrusionPath or ExtrusionEntityCollection per source ExtrusionPath. + // Map of fragment start/end points to a pair of + // Because a non-exact matching is used for the end points, a multi-map is used. + // As the clipper library may reverse the order of some clipped paths, store both ends into the map. + struct ExtrusionPathFragmentEnd + { + ExtrusionPathFragmentEnd(size_t alayer_idx, size_t apolyline_idx, bool ais_start) : + layer_idx(alayer_idx), polyline_idx(apolyline_idx), is_start(ais_start) {} + size_t layer_idx; + size_t polyline_idx; + bool is_start; + }; + class ExtrusionPathFragmentEndPointAccessor { + public: + ExtrusionPathFragmentEndPointAccessor(const std::vector &path_fragments) : m_path_fragments(path_fragments) {} + // Return an end point of a fragment, or nullptr if the fragment has been consumed already. + const Point* operator()(const ExtrusionPathFragmentEnd &fragment_end) const { + const Polyline &polyline = m_path_fragments[fragment_end.layer_idx].polylines[fragment_end.polyline_idx]; + return polyline.points.empty() ? nullptr : + (fragment_end.is_start ? &polyline.points.front() : &polyline.points.back()); + } + private: + ExtrusionPathFragmentEndPointAccessor& operator=(const ExtrusionPathFragmentEndPointAccessor&) { + return *this; + } + + const std::vector &m_path_fragments; + }; + const coord_t search_radius = 7; + ClosestPointInRadiusLookup map_fragment_starts( + search_radius, ExtrusionPathFragmentEndPointAccessor(path_fragments)); + for (size_t i_overlapping_layer = 0; i_overlapping_layer <= n_overlapping_layers; ++ i_overlapping_layer) { + const Polylines &polylines = path_fragments[i_overlapping_layer].polylines; + for (size_t i_polyline = 0; i_polyline < polylines.size(); ++ i_polyline) { + // Map a starting point of a polyline to a pair of + if (polylines[i_polyline].points.size() >= 2) { + map_fragment_starts.insert(ExtrusionPathFragmentEnd(i_overlapping_layer, i_polyline, true)); + map_fragment_starts.insert(ExtrusionPathFragmentEnd(i_overlapping_layer, i_polyline, false)); + } + } + } + + // For each source path: + for (size_t i_path = 0; i_path < path_ends.size(); ++ i_path) { + const Point &pt_start = path_ends[i_path].first; + const Point &pt_end = path_ends[i_path].second; + Point pt_current = pt_start; + // Find a chain of fragments with the original / reduced print height. + ExtrusionMultiPath multipath; + for (;;) { + // Find a closest end point to pt_current. + std::pair end_and_dist2 = map_fragment_starts.find(pt_current); + // There may be a bug in Clipper flipping the order of two last points in a fragment? + // assert(end_and_dist2.first != nullptr); + assert(end_and_dist2.first == nullptr || end_and_dist2.second < search_radius * search_radius); + if (end_and_dist2.first == nullptr) { + // New fragment connecting to pt_current was not found. + // Verify that the last point found is close to the original end point of the unfragmented path. + //const double d2 = (pt_end - pt_current).cast.squaredNorm(); + //assert(d2 < coordf_t(search_radius * search_radius)); + // End of the path. + break; + } + const ExtrusionPathFragmentEnd &fragment_end_min = *end_and_dist2.first; + // Fragment to consume. + ExtrusionPathFragment &frag = path_fragments[fragment_end_min.layer_idx]; + Polyline &frag_polyline = frag.polylines[fragment_end_min.polyline_idx]; + // Path to append the fragment to. + ExtrusionPath *path = multipath.paths.empty() ? nullptr : &multipath.paths.back(); + if (path != nullptr) { + // Verify whether the path is compatible with the current fragment. + assert(this_layer.layer_type == PrintObjectSupportMaterial::sltBottomContact || path->height != frag.height || path->mm3_per_mm != frag.mm3_per_mm); + if (path->height != frag.height || path->mm3_per_mm != frag.mm3_per_mm) { + path = nullptr; + } + // Merging with the previous path. This can only happen if the current layer was reduced by a base layer, which was split into a base and interface layer. + } + if (path == nullptr) { + // Allocate a new path. + multipath.paths.push_back(ExtrusionPath(extrusion_role, frag.mm3_per_mm, frag.width, frag.height)); + path = &multipath.paths.back(); + } + // The Clipper library may flip the order of the clipped polylines arbitrarily. + // Reverse the source polyline, if connecting to the end. + if (! fragment_end_min.is_start) + frag_polyline.reverse(); + // Enforce exact overlap of the end points of successive fragments. + assert(frag_polyline.points.front() == pt_current); + frag_polyline.points.front() = pt_current; + // Don't repeat the first point. + if (! path->polyline.points.empty()) + path->polyline.points.pop_back(); + // Consume the fragment's polyline, remove it from the input fragments, so it will be ignored the next time. + path->polyline.append(std::move(frag_polyline)); + frag_polyline.points.clear(); + pt_current = path->polyline.points.back(); + if (pt_current == pt_end) { + // End of the path. + break; + } + } + if (!multipath.paths.empty()) { + if (multipath.paths.size() == 1) { + // This path was not fragmented. + extrusions_in_out.push_back(new ExtrusionPath(std::move(multipath.paths.front()))); + } else { + // This path was fragmented. Copy the collection as a whole object, so the order inside the collection will not be changed + // during the chaining of extrusions_in_out. + extrusions_in_out.push_back(new ExtrusionMultiPath(std::move(multipath))); + } + } + } + // If there are any non-consumed fragments, add them separately. + //FIXME this shall not happen, if the Clipper works as expected and all paths split to fragments could be re-connected. + for (auto it_fragment = path_fragments.begin(); it_fragment != path_fragments.end(); ++ it_fragment) + extrusion_entities_append_paths(extrusions_in_out, std::move(it_fragment->polylines), extrusion_role, it_fragment->mm3_per_mm, it_fragment->width, it_fragment->height); +} + +void PrintObjectSupportMaterial::generate_toolpaths( + SupportLayerPtrs &support_layers, + const MyLayersPtr &raft_layers, + const MyLayersPtr &bottom_contacts, + const MyLayersPtr &top_contacts, + const MyLayersPtr &intermediate_layers, + const MyLayersPtr &interface_layers, + const MyLayersPtr &base_interface_layers) const +{ + // loop_interface_processor with a given circle radius. + LoopInterfaceProcessor loop_interface_processor(1.5 * m_support_params.support_material_interface_flow.scaled_width()); + loop_interface_processor.n_contact_loops = this->has_contact_loops() ? 1 : 0; + + std::vector angles { m_support_params.base_angle }; + if (m_object_config->support_base_pattern == smpRectilinearGrid) + angles.push_back(m_support_params.interface_angle); + + BoundingBox bbox_object(Point(-scale_(1.), -scale_(1.0)), Point(scale_(1.), scale_(1.))); + +// const coordf_t link_max_length_factor = 3.; + const coordf_t link_max_length_factor = 0.; + + float raft_angle_1st_layer = 0.f; + float raft_angle_base = 0.f; + float raft_angle_interface = 0.f; + if (m_slicing_params.base_raft_layers > 1) { + // There are all raft layer types (1st layer, base, interface & contact layers) available. + raft_angle_1st_layer = m_support_params.interface_angle; + raft_angle_base = m_support_params.base_angle; + raft_angle_interface = m_support_params.interface_angle; + } else if (m_slicing_params.base_raft_layers == 1 || m_slicing_params.interface_raft_layers > 1) { + // 1st layer, interface & contact layers available. + raft_angle_1st_layer = m_support_params.base_angle; + if (this->has_support()) + // Print 1st layer at 45 degrees from both the interface and base angles as both can land on the 1st layer. + raft_angle_1st_layer += 0.7854f; + raft_angle_interface = m_support_params.interface_angle; + } else if (m_slicing_params.interface_raft_layers == 1) { + // Only the contact raft layer is non-empty, which will be printed as the 1st layer. + assert(m_slicing_params.base_raft_layers == 0); + assert(m_slicing_params.interface_raft_layers == 1); + assert(m_slicing_params.raft_layers() == 1 && raft_layers.size() == 0); + } else { + // No raft. + assert(m_slicing_params.base_raft_layers == 0); + assert(m_slicing_params.interface_raft_layers == 0); + assert(m_slicing_params.raft_layers() == 0 && raft_layers.size() == 0); + } + + // Insert the raft base layers. + size_t n_raft_layers = size_t(std::max(0, int(m_slicing_params.raft_layers()) - 1)); + tbb::parallel_for(tbb::blocked_range(0, n_raft_layers), + [this, &support_layers, &raft_layers, + &bbox_object, raft_angle_1st_layer, raft_angle_base, raft_angle_interface, link_max_length_factor] + (const tbb::blocked_range& range) { + for (size_t support_layer_id = range.begin(); support_layer_id < range.end(); ++ support_layer_id) + { + assert(support_layer_id < raft_layers.size()); + SupportLayer &support_layer = *support_layers[support_layer_id]; + assert(support_layer.support_fills.entities.empty()); + MyLayer &raft_layer = *raft_layers[support_layer_id]; + + std::unique_ptr filler_interface = std::unique_ptr(Fill::new_from_type(m_support_params.interface_fill_pattern)); + std::unique_ptr filler_support = std::unique_ptr(Fill::new_from_type(m_support_params.base_fill_pattern)); + filler_interface->set_bounding_box(bbox_object); + filler_support->set_bounding_box(bbox_object); + + // Print the support base below the support columns, or the support base for the support columns plus the contacts. + if (support_layer_id > 0) { + const Polygons &to_infill_polygons = (support_layer_id < m_slicing_params.base_raft_layers) ? + raft_layer.polygons : + //FIXME misusing contact_polygons for support columns. + ((raft_layer.contact_polygons == nullptr) ? Polygons() : *raft_layer.contact_polygons); + if (! to_infill_polygons.empty()) { + assert(! raft_layer.bridging); + Flow flow(float(m_support_params.support_material_flow.width()), float(raft_layer.height), m_support_params.support_material_flow.nozzle_diameter()); + Fill * filler = filler_support.get(); + filler->angle = raft_angle_base; + filler->spacing = m_support_params.support_material_flow.spacing(); + filler->link_max_length = coord_t(scale_(filler->spacing * link_max_length_factor / m_support_params.support_density)); + fill_expolygons_with_sheath_generate_paths( + // Destination + support_layer.support_fills.entities, + // Regions to fill + to_infill_polygons, + // Filler and its parameters + filler, float(m_support_params.support_density), + // Extrusion parameters + erSupportMaterial, flow, + m_support_params.with_sheath, false); + } + } + + Fill *filler = filler_interface.get(); + Flow flow = m_support_params.first_layer_flow; + float density = 0.f; + if (support_layer_id == 0) { + // Base flange. + filler->angle = raft_angle_1st_layer; + filler->spacing = m_support_params.first_layer_flow.spacing(); + density = float(m_object_config->raft_first_layer_density.value * 0.01); + } else if (support_layer_id >= m_slicing_params.base_raft_layers) { + filler->angle = raft_angle_interface; + // We don't use $base_flow->spacing because we need a constant spacing + // value that guarantees that all layers are correctly aligned. + filler->spacing = m_support_params.support_material_flow.spacing(); + assert(! raft_layer.bridging); + flow = Flow(float(m_support_params.support_material_interface_flow.width()), float(raft_layer.height), m_support_params.support_material_flow.nozzle_diameter()); + density = float(m_support_params.interface_density); + } else + continue; + filler->link_max_length = coord_t(scale_(filler->spacing * link_max_length_factor / density)); + fill_expolygons_with_sheath_generate_paths( + // Destination + support_layer.support_fills.entities, + // Regions to fill + raft_layer.polygons, + // Filler and its parameters + filler, density, + // Extrusion parameters + (support_layer_id < m_slicing_params.base_raft_layers) ? erSupportMaterial : erSupportMaterialInterface, flow, + // sheath at first layer + support_layer_id == 0, support_layer_id == 0); + } + }); + + struct LayerCacheItem { + LayerCacheItem(MyLayerExtruded *layer_extruded = nullptr) : layer_extruded(layer_extruded) {} + MyLayerExtruded *layer_extruded; + std::vector overlapping; + }; + struct LayerCache { + MyLayerExtruded bottom_contact_layer; + MyLayerExtruded top_contact_layer; + MyLayerExtruded base_layer; + MyLayerExtruded interface_layer; + MyLayerExtruded base_interface_layer; + boost::container::static_vector nonempty; + + void add_nonempty_and_sort() { + for (MyLayerExtruded *item : { &bottom_contact_layer, &top_contact_layer, &interface_layer, &base_interface_layer, &base_layer }) + if (! item->empty()) + this->nonempty.emplace_back(item); + // Sort the layers with the same print_z coordinate by their heights, thickest first. + std::stable_sort(this->nonempty.begin(), this->nonempty.end(), [](const LayerCacheItem &lc1, const LayerCacheItem &lc2) { return lc1.layer_extruded->layer->height > lc2.layer_extruded->layer->height; }); + } + }; + std::vector layer_caches(support_layers.size()); + + tbb::parallel_for(tbb::blocked_range(n_raft_layers, support_layers.size()), + [this, &support_layers, &bottom_contacts, &top_contacts, &intermediate_layers, &interface_layers, &base_interface_layers, &layer_caches, &loop_interface_processor, + &bbox_object, &angles, link_max_length_factor] + (const tbb::blocked_range& range) { + // Indices of the 1st layer in their respective container at the support layer height. + size_t idx_layer_bottom_contact = size_t(-1); + size_t idx_layer_top_contact = size_t(-1); + size_t idx_layer_intermediate = size_t(-1); + size_t idx_layer_interface = size_t(-1); + size_t idx_layer_base_interface = size_t(-1); + // BBS + const auto fill_type_first_layer = ipConcentric; + auto filler_interface = std::unique_ptr(Fill::new_from_type(m_support_params.contact_fill_pattern)); + // Filler for the 1st layer interface, if different from filler_interface. + auto filler_first_layer_ptr = std::unique_ptr(range.begin() == 0 && m_support_params.contact_fill_pattern != fill_type_first_layer ? Fill::new_from_type(fill_type_first_layer) : nullptr); + // Pointer to the 1st layer interface filler. + auto filler_first_layer = filler_first_layer_ptr ? filler_first_layer_ptr.get() : filler_interface.get(); + // Filler for the base interface (to be used for soluble interface / non soluble base, to produce non soluble interface layer below soluble interface layer). + auto filler_base_interface = std::unique_ptr(base_interface_layers.empty() ? nullptr : + Fill::new_from_type(m_support_params.interface_density > 0.95 || m_support_params.with_sheath ? ipRectilinear : ipSupportBase)); + auto filler_support = std::unique_ptr(Fill::new_from_type(m_support_params.base_fill_pattern)); + filler_interface->set_bounding_box(bbox_object); + if (filler_first_layer_ptr) + filler_first_layer_ptr->set_bounding_box(bbox_object); + if (filler_base_interface) + filler_base_interface->set_bounding_box(bbox_object); + filler_support->set_bounding_box(bbox_object); + for (size_t support_layer_id = range.begin(); support_layer_id < range.end(); ++ support_layer_id) + { + SupportLayer &support_layer = *support_layers[support_layer_id]; + LayerCache &layer_cache = layer_caches[support_layer_id]; + float interface_angle_delta = m_object_config->support_style.value == smsSnug ? + (support_layer.interface_id() & 1) ? float(- M_PI / 4.) : float(+ M_PI / 4.) : + 0; + + // Find polygons with the same print_z. + MyLayerExtruded &bottom_contact_layer = layer_cache.bottom_contact_layer; + MyLayerExtruded &top_contact_layer = layer_cache.top_contact_layer; + MyLayerExtruded &base_layer = layer_cache.base_layer; + MyLayerExtruded &interface_layer = layer_cache.interface_layer; + MyLayerExtruded &base_interface_layer = layer_cache.base_interface_layer; + // Increment the layer indices to find a layer at support_layer.print_z. + { + auto fun = [&support_layer](const MyLayer *l){ return l->print_z >= support_layer.print_z - EPSILON; }; + idx_layer_bottom_contact = idx_higher_or_equal(bottom_contacts, idx_layer_bottom_contact, fun); + idx_layer_top_contact = idx_higher_or_equal(top_contacts, idx_layer_top_contact, fun); + idx_layer_intermediate = idx_higher_or_equal(intermediate_layers, idx_layer_intermediate, fun); + idx_layer_interface = idx_higher_or_equal(interface_layers, idx_layer_interface, fun); + idx_layer_base_interface = idx_higher_or_equal(base_interface_layers, idx_layer_base_interface,fun); + } + // Copy polygons from the layers. + if (idx_layer_bottom_contact < bottom_contacts.size() && bottom_contacts[idx_layer_bottom_contact]->print_z < support_layer.print_z + EPSILON) + bottom_contact_layer.layer = bottom_contacts[idx_layer_bottom_contact]; + if (idx_layer_top_contact < top_contacts.size() && top_contacts[idx_layer_top_contact]->print_z < support_layer.print_z + EPSILON) + top_contact_layer.layer = top_contacts[idx_layer_top_contact]; + if (idx_layer_interface < interface_layers.size() && interface_layers[idx_layer_interface]->print_z < support_layer.print_z + EPSILON) + interface_layer.layer = interface_layers[idx_layer_interface]; + if (idx_layer_base_interface < base_interface_layers.size() && base_interface_layers[idx_layer_base_interface]->print_z < support_layer.print_z + EPSILON) + base_interface_layer.layer = base_interface_layers[idx_layer_base_interface]; + if (idx_layer_intermediate < intermediate_layers.size() && intermediate_layers[idx_layer_intermediate]->print_z < support_layer.print_z + EPSILON) + base_layer.layer = intermediate_layers[idx_layer_intermediate]; + + if (m_object_config->support_interface_top_layers == 0) { + // If no top interface layers were requested, we treat the contact layer exactly as a generic base layer. + if (m_support_params.can_merge_support_regions) { + if (base_layer.could_merge(top_contact_layer)) + base_layer.merge(std::move(top_contact_layer)); + else if (base_layer.empty()) + base_layer = std::move(top_contact_layer); + } + } else { + loop_interface_processor.generate(top_contact_layer, m_support_params.support_material_interface_flow); + // If no loops are allowed, we treat the contact layer exactly as a generic interface layer. + // Merge interface_layer into top_contact_layer, as the top_contact_layer is not synchronized and therefore it will be used + // to trim other layers. + if (top_contact_layer.could_merge(interface_layer)) + top_contact_layer.merge(std::move(interface_layer)); + } + if ((m_object_config->support_interface_top_layers == 0 || m_object_config->support_interface_bottom_layers == 0) && m_support_params.can_merge_support_regions) { + if (base_layer.could_merge(bottom_contact_layer)) + base_layer.merge(std::move(bottom_contact_layer)); + else if (base_layer.empty() && ! bottom_contact_layer.empty() && ! bottom_contact_layer.layer->bridging) + base_layer = std::move(bottom_contact_layer); + } else if (bottom_contact_layer.could_merge(top_contact_layer)) + top_contact_layer.merge(std::move(bottom_contact_layer)); + else if (bottom_contact_layer.could_merge(interface_layer)) + bottom_contact_layer.merge(std::move(interface_layer)); + +#if 0 + if ( ! interface_layer.empty() && ! base_layer.empty()) { + // turn base support into interface when it's contained in our holes + // (this way we get wider interface anchoring) + //FIXME The intention of the code below is unclear. One likely wanted to just merge small islands of base layers filling in the holes + // inside interface layers, but the code below fills just too much, see GH #4570 + Polygons islands = top_level_islands(interface_layer.layer->polygons); + polygons_append(interface_layer.layer->polygons, intersection(base_layer.layer->polygons, islands)); + base_layer.layer->polygons = diff(base_layer.layer->polygons, islands); + } +#endif + + // Calculate top interface angle + float angle_of_biggest_bridge = -1.f; + do + { + // Currently only works when thick_bridges is off + if (m_object->config().thick_bridges) + break; + + coordf_t object_layer_bottom_z = support_layer.print_z + m_slicing_params.gap_support_object; + const Layer* object_layer = m_object->get_layer_at_bottomz(object_layer_bottom_z, 10.0 * EPSILON); + if (object_layer == nullptr) + break; + + if (object_layer != nullptr) { + float biggest_bridge_area = 0.f; + const Polygons& top_contact_polys = top_contact_layer.polygons_to_extrude(); + for (auto layerm : object_layer->regions()) { + for (auto bridge_surface : layerm->fill_surfaces.filter_by_type(stBottomBridge)) { + float bs_area = bridge_surface->area(); + if (bs_area <= biggest_bridge_area || bridge_surface->bridge_angle < 0.f) + continue; + + angle_of_biggest_bridge = bridge_surface->bridge_angle; + biggest_bridge_area = bs_area; + } + } + } + } while (0); + + auto calc_included_angle_degree = [](int degree_a, int degree_b) { + int iad = std::abs(degree_b - degree_a); + return std::min(iad, 180 - iad); + }; + + // Top and bottom contacts, interface layers. + for (size_t i = 0; i < 3; ++ i) { + MyLayerExtruded &layer_ex = (i == 0) ? top_contact_layer : (i == 1 ? bottom_contact_layer : interface_layer); + if (layer_ex.empty() || layer_ex.polygons_to_extrude().empty()) + continue; + bool interface_as_base = m_object_config->support_interface_top_layers.value == 0 || + (m_object_config->support_interface_bottom_layers == 0 && &layer_ex == &bottom_contact_layer); + //FIXME Bottom interfaces are extruded with the briding flow. Some bridging layers have its height slightly reduced, therefore + // the bridging flow does not quite apply. Reduce the flow to area of an ellipse? (A = pi * a * b) + Flow interface_flow; + if (layer_ex.layer->bridging) + interface_flow = Flow::bridging_flow(layer_ex.layer->height, m_support_params.support_material_bottom_interface_flow.nozzle_diameter()); + else if (layer_ex.layer->bottom_z < EPSILON) { + interface_flow = m_support_params.first_layer_flow; + }else + interface_flow = (interface_as_base ? &m_support_params.support_material_flow : &m_support_params.support_material_interface_flow)->with_height(float(layer_ex.layer->height)); + filler_interface->angle = interface_as_base ? + // If zero interface layers are configured, use the same angle as for the base layers. + angles[support_layer_id % angles.size()] : + // Use interface angle for the interface layers. + m_support_params.interface_angle + interface_angle_delta; + + // BBS + bool can_adjust_top_interface_angle = (m_object_config->support_interface_top_layers.value > 1 && &layer_ex == &top_contact_layer); + if (can_adjust_top_interface_angle && angle_of_biggest_bridge >= 0.f) { + int bridge_degree = (int)Geometry::rad2deg(angle_of_biggest_bridge); + int support_intf_degree = (int)Geometry::rad2deg(filler_interface->angle); + int max_included_degree = 0; + int step = 90; + for (int add_on_degree = 0; add_on_degree < 180; add_on_degree += step) { + int degree_to_try = support_intf_degree + add_on_degree; + int included_degree = calc_included_angle_degree(bridge_degree, degree_to_try); + if (included_degree > max_included_degree) { + max_included_degree = included_degree; + filler_interface->angle = Geometry::deg2rad((float)degree_to_try); + } + } + } + double density = interface_as_base ? m_support_params.support_density : m_support_params.interface_density; + filler_interface->spacing = interface_as_base ? m_support_params.support_material_flow.spacing() : m_support_params.support_material_interface_flow.spacing(); + filler_interface->link_max_length = coord_t(scale_(filler_interface->spacing * link_max_length_factor / density)); + // BBS support more interface patterns + FillParams fill_params; + fill_params.density = density; + fill_params.dont_adjust = true; + if (m_object_config->support_interface_pattern == smipGrid) { + filler_interface->angle = Geometry::deg2rad(m_support_params.base_angle); + fill_params.dont_sort = true; + } + if (m_object_config->support_interface_pattern == smipRectilinearInterlaced) + filler_interface->layer_id = support_layer.interface_id(); + fill_expolygons_generate_paths( + // Destination + layer_ex.extrusions, + // Regions to fill + union_safety_offset_ex(layer_ex.polygons_to_extrude()), + // Filler and its parameters + filler_interface.get(), fill_params, + // Extrusion parameters + erSupportMaterialInterface, interface_flow); + } + + // Base interface layers under soluble interfaces + if ( ! base_interface_layer.empty() && ! base_interface_layer.polygons_to_extrude().empty()) { + Fill *filler = filler_base_interface.get(); + //FIXME Bottom interfaces are extruded with the briding flow. Some bridging layers have its height slightly reduced, therefore + // the bridging flow does not quite apply. Reduce the flow to area of an ellipse? (A = pi * a * b) + assert(! base_interface_layer.layer->bridging); + Flow interface_flow = m_support_params.support_material_flow.with_height(float(base_interface_layer.layer->height)); + filler->angle = m_support_params.interface_angle + interface_angle_delta; + filler->spacing = m_support_params.support_material_interface_flow.spacing(); + filler->link_max_length = coord_t(scale_(filler->spacing * link_max_length_factor / m_support_params.interface_density)); + fill_expolygons_generate_paths( + // Destination + base_interface_layer.extrusions, + //base_layer_interface.extrusions, + // Regions to fill + union_safety_offset_ex(base_interface_layer.polygons_to_extrude()), + // Filler and its parameters + filler, float(m_support_params.interface_density), + // Extrusion parameters + erSupportMaterial, interface_flow); + } + + // Base support or flange. + if (! base_layer.empty() && ! base_layer.polygons_to_extrude().empty()) { + Fill *filler = filler_support.get(); + filler->angle = angles[support_layer_id % angles.size()]; + // We don't use $base_flow->spacing because we need a constant spacing + // value that guarantees that all layers are correctly aligned. + assert(! base_layer.layer->bridging); + auto flow = m_support_params.support_material_flow.with_height(float(base_layer.layer->height)); + filler->spacing = m_support_params.support_material_flow.spacing(); + filler->link_max_length = coord_t(scale_(filler->spacing * link_max_length_factor / m_support_params.support_density)); + float density = float(m_support_params.support_density); + bool sheath = m_support_params.with_sheath; + bool no_sort = false; + if (base_layer.layer->bottom_z < EPSILON) { + // Base flange (the 1st layer). + filler = filler_first_layer; + // BBS: the 1st layer use the same fill direction as other layers(in rectilinear) to avoid + // that 2nd layer detaches from the 1st layer. + //filler->angle = Geometry::deg2rad(float(m_object_config->support_angle.value + 90.)); + density = float(m_object_config->raft_first_layer_density.value * 0.01); + flow = m_support_params.first_layer_flow; + // use the proper spacing for first layer as we don't need to align + // its pattern to the other layers + //FIXME When paralellizing, each thread shall have its own copy of the fillers. + filler->spacing = flow.spacing(); + filler->link_max_length = coord_t(scale_(filler->spacing * link_max_length_factor / density)); + sheath = true; + no_sort = true; + } + fill_expolygons_with_sheath_generate_paths( + // Destination + base_layer.extrusions, + // Regions to fill + base_layer.polygons_to_extrude(), + // Filler and its parameters + filler, density, + // Extrusion parameters + erSupportMaterial, flow, + sheath, no_sort); + + } + + // Merge base_interface_layers to base_layers to avoid unneccessary retractions + if (! base_layer.empty() && ! base_interface_layer.empty() && ! base_layer.polygons_to_extrude().empty() && ! base_interface_layer.polygons_to_extrude().empty() && + base_layer.could_merge(base_interface_layer)) + base_layer.merge(std::move(base_interface_layer)); + + layer_cache.add_nonempty_and_sort(); + + // Collect the support areas with this print_z into islands, as there is no need + // for retraction over these islands. + Polygons polys; + // Collect the extrusions, sorted by the bottom extrusion height. + for (LayerCacheItem &layer_cache_item : layer_cache.nonempty) { + // Collect islands to polys. + layer_cache_item.layer_extruded->polygons_append(polys); + // The print_z of the top contact surfaces and bottom_z of the bottom contact surfaces are "free" + // in a sense that they are not synchronized with other support layers. As the top and bottom contact surfaces + // are inflated to achieve a better anchoring, it may happen, that these surfaces will at least partially + // overlap in Z with another support layers, leading to over-extrusion. + // Mitigate the over-extrusion by modulating the extrusion rate over these regions. + // The print head will follow the same print_z, but the layer thickness will be reduced + // where it overlaps with another support layer. + //FIXME When printing a briging path, what is an equivalent height of the squished extrudate of the same width? + // Collect overlapping top/bottom surfaces. + layer_cache_item.overlapping.reserve(20); + coordf_t bottom_z = layer_cache_item.layer_extruded->layer->bottom_print_z() + EPSILON; + auto add_overlapping = [&layer_cache_item, bottom_z](const MyLayersPtr &layers, size_t idx_top) { + for (int i = int(idx_top) - 1; i >= 0 && layers[i]->print_z > bottom_z; -- i) + layer_cache_item.overlapping.push_back(layers[i]); + }; + add_overlapping(top_contacts, idx_layer_top_contact); + if (layer_cache_item.layer_extruded->layer->layer_type == sltBottomContact) { + // Bottom contact layer may overlap with a base layer, which may be changed to interface layer. + add_overlapping(intermediate_layers, idx_layer_intermediate); + add_overlapping(interface_layers, idx_layer_interface); + add_overlapping(base_interface_layers, idx_layer_base_interface); + } + // Order the layers by lexicographically by an increasing print_z and a decreasing layer height. + std::stable_sort(layer_cache_item.overlapping.begin(), layer_cache_item.overlapping.end(), [](auto *l1, auto *l2) { return *l1 < *l2; }); + } + if (! polys.empty()) + expolygons_append(support_layer.support_islands, union_ex(polys)); + } // for each support_layer_id + }); + + // Now modulate the support layer height in parallel. + tbb::parallel_for(tbb::blocked_range(n_raft_layers, support_layers.size()), + [&support_layers, &layer_caches] + (const tbb::blocked_range& range) { + for (size_t support_layer_id = range.begin(); support_layer_id < range.end(); ++ support_layer_id) { + SupportLayer &support_layer = *support_layers[support_layer_id]; + LayerCache &layer_cache = layer_caches[support_layer_id]; + // For all extrusion types at this print_z, ordered by decreasing layer height: + for (LayerCacheItem &layer_cache_item : layer_cache.nonempty) { + // Trim the extrusion height from the bottom by the overlapping layers. + modulate_extrusion_by_overlapping_layers(layer_cache_item.layer_extruded->extrusions, *layer_cache_item.layer_extruded->layer, layer_cache_item.overlapping); + support_layer.support_fills.append(std::move(layer_cache_item.layer_extruded->extrusions)); + } + } + }); + +#ifndef NDEBUG + struct Test { + static bool verify_nonempty(const ExtrusionEntityCollection *collection) { + for (const ExtrusionEntity *ee : collection->entities) { + if (const ExtrusionPath *path = dynamic_cast(ee)) + assert(! path->empty()); + else if (const ExtrusionMultiPath *multipath = dynamic_cast(ee)) + assert(! multipath->empty()); + else if (const ExtrusionEntityCollection *eecol = dynamic_cast(ee)) { + assert(! eecol->empty()); + return verify_nonempty(eecol); + } else + assert(false); + } + return true; + } + }; + for (const SupportLayer *support_layer : support_layers) + assert(Test::verify_nonempty(&support_layer->support_fills)); +#endif // NDEBUG +} + /* void PrintObjectSupportMaterial::clip_by_pillars( const PrintObject &object, diff --git a/src/libslic3r/Support/SupportMaterial.hpp b/src/libslic3r/Support/SupportMaterial.hpp index f626150f04d..861126fcbdc 100644 --- a/src/libslic3r/Support/SupportMaterial.hpp +++ b/src/libslic3r/Support/SupportMaterial.hpp @@ -1,16 +1,15 @@ #ifndef slic3r_SupportMaterial_hpp_ #define slic3r_SupportMaterial_hpp_ -#include "../Flow.hpp" -#include "../PrintConfig.hpp" -#include "../Slicing.hpp" - -#include "SupportLayer.hpp" -#include "SupportParameters.hpp" +#include "Flow.hpp" +#include "PrintConfig.hpp" +#include "Slicing.hpp" namespace Slic3r { class PrintObject; +class PrintConfig; +class PrintObjectConfig; // This class manages raft and supports for a single PrintObject. // Instantiated by Slic3r::Print::Object->_support_material() @@ -18,6 +17,142 @@ class PrintObject; // the parameters of the raft to determine the 1st layer height and thickness. class PrintObjectSupportMaterial { +public: + // Support layer type to be used by MyLayer. This type carries a much more detailed information + // about the support layer type than the final support layers stored in a PrintObject. + enum SupporLayerType { + sltUnknown = 0, + // Ratft base layer, to be printed with the support material. + sltRaftBase, + // Raft interface layer, to be printed with the support interface material. + sltRaftInterface, + // Bottom contact layer placed over a top surface of an object. To be printed with a support interface material. + sltBottomContact, + // Dense interface layer, to be printed with the support interface material. + // This layer is separated from an object by an sltBottomContact layer. + sltBottomInterface, + // Sparse base support layer, to be printed with a support material. + sltBase, + // Dense interface layer, to be printed with the support interface material. + // This layer is separated from an object with sltTopContact layer. + sltTopInterface, + // Top contact layer directly supporting an overhang. To be printed with a support interface material. + sltTopContact, + // Some undecided type yet. It will turn into sltBase first, then it may turn into sltBottomInterface or sltTopInterface. + sltIntermediate, + }; + + // A support layer type used internally by the SupportMaterial class. This class carries a much more detailed + // information about the support layer than the layers stored in the PrintObject, mainly + // the MyLayer is aware of the bridging flow and the interface gaps between the object and the support. + class MyLayer + { + public: + void reset() { + *this = MyLayer(); + } + + bool operator==(const MyLayer &layer2) const { + return print_z == layer2.print_z && height == layer2.height && bridging == layer2.bridging; + } + + // Order the layers by lexicographically by an increasing print_z and a decreasing layer height. + bool operator<(const MyLayer &layer2) const { + if (print_z < layer2.print_z) { + return true; + } else if (print_z == layer2.print_z) { + if (height > layer2.height) + return true; + else if (height == layer2.height) { + // Bridging layers first. + return bridging && ! layer2.bridging; + } else + return false; + } else + return false; + } + + void merge(MyLayer &&rhs) { + // The union_() does not support move semantic yet, but maybe one day it will. + this->polygons = union_(this->polygons, std::move(rhs.polygons)); + auto merge = [](std::unique_ptr &dst, std::unique_ptr &src) { + if (! dst || dst->empty()) + dst = std::move(src); + else if (src && ! src->empty()) + *dst = union_(*dst, std::move(*src)); + }; + merge(this->contact_polygons, rhs.contact_polygons); + merge(this->overhang_polygons, rhs.overhang_polygons); + merge(this->enforcer_polygons, rhs.enforcer_polygons); + rhs.reset(); + } + + // For the bridging flow, bottom_print_z will be above bottom_z to account for the vertical separation. + // For the non-bridging flow, bottom_print_z will be equal to bottom_z. + coordf_t bottom_print_z() const { return print_z - height; } + + // To sort the extremes of top / bottom interface layers. + coordf_t extreme_z() const { return (this->layer_type == sltTopContact) ? this->bottom_z : this->print_z; } + + SupporLayerType layer_type { sltUnknown }; + // Z used for printing, in unscaled coordinates. + coordf_t print_z { 0 }; + // Bottom Z of this layer. For soluble layers, bottom_z + height = print_z, + // otherwise bottom_z + gap + height = print_z. + coordf_t bottom_z { 0 }; + // Layer height in unscaled coordinates. + coordf_t height { 0 }; + // Index of a PrintObject layer_id supported by this layer. This will be set for top contact layers. + // If this is not a contact layer, it will be set to size_t(-1). + size_t idx_object_layer_above { size_t(-1) }; + // Index of a PrintObject layer_id, which supports this layer. This will be set for bottom contact layers. + // If this is not a contact layer, it will be set to size_t(-1). + size_t idx_object_layer_below { size_t(-1) }; + // Use a bridging flow when printing this support layer. + bool bridging { false }; + + // Polygons to be filled by the support pattern. + Polygons polygons; + // Currently for the contact layers only. + std::unique_ptr contact_polygons; + std::unique_ptr overhang_polygons; + // Enforcers need to be propagated independently in case the "support on build plate only" option is enabled. + std::unique_ptr enforcer_polygons; + }; + + struct SupportParams { + Flow first_layer_flow; + Flow support_material_flow; + Flow support_material_interface_flow; + Flow support_material_bottom_interface_flow; + // Is merging of regions allowed? Could the interface & base support regions be printed with the same extruder? + bool can_merge_support_regions; + + coordf_t support_layer_height_min; + // coordf_t support_layer_height_max; + + coordf_t gap_xy; + + float base_angle; + float interface_angle; + coordf_t interface_spacing; + coordf_t support_expansion; + coordf_t interface_density; + coordf_t support_spacing; + coordf_t support_density; + + InfillPattern base_fill_pattern; + InfillPattern interface_fill_pattern; + InfillPattern contact_fill_pattern; + bool with_sheath; + }; + + // Layers are allocated and owned by a deque. Once a layer is allocated, it is maintained + // up to the end of a generate() method. The layer storage may be replaced by an allocator class in the future, + // which would allocate layers by multiple chunks. + typedef std::deque MyLayerStorage; + typedef std::vector MyLayersPtr; + public: PrintObjectSupportMaterial(const PrintObject *object, const SlicingParameters &slicing_params); @@ -26,8 +161,8 @@ class PrintObjectSupportMaterial // Has any support? bool has_support() const { return m_object_config->enable_support.value || m_object_config->enforce_support_layers; } bool build_plate_only() const { return this->has_support() && m_object_config->support_on_build_plate_only.value; } - - bool synchronize_layers() const { return m_slicing_params.soluble_interface && m_print_config->independent_support_layer_height.value; } + // BBS + bool synchronize_layers() const { return /*m_slicing_params.soluble_interface && */!m_print_config->independent_support_layer_height.value; } bool has_contact_loops() const { return m_object_config->support_interface_loop_pattern.value; } // Generate support material for the object. @@ -36,47 +171,63 @@ class PrintObjectSupportMaterial void generate(PrintObject &object); private: - using SupportGeneratorLayersPtr = FFFSupport::SupportGeneratorLayersPtr; - using SupportGeneratorLayerStorage = FFFSupport::SupportGeneratorLayerStorage; - using SupportParameters = FFFSupport::SupportParameters; - std::vector buildplate_covered(const PrintObject &object) const; // Generate top contact layers supporting overhangs. // For a soluble interface material synchronize the layer heights with the object, otherwise leave the layer height undefined. // If supports over bed surface only are requested, don't generate contact layers over an object. - SupportGeneratorLayersPtr top_contact_layers(const PrintObject &object, const std::vector &buildplate_covered, SupportGeneratorLayerStorage &layer_storage) const; + MyLayersPtr top_contact_layers(const PrintObject &object, const std::vector &buildplate_covered, MyLayerStorage &layer_storage) const; // Generate bottom contact layers supporting the top contact layers. // For a soluble interface material synchronize the layer heights with the object, // otherwise set the layer height to a bridging flow of a support interface nozzle. - SupportGeneratorLayersPtr bottom_contact_layers_and_layer_support_areas( - const PrintObject &object, const SupportGeneratorLayersPtr &top_contacts, std::vector &buildplate_covered, - SupportGeneratorLayerStorage &layer_storage, std::vector &layer_support_areas) const; + MyLayersPtr bottom_contact_layers_and_layer_support_areas( + const PrintObject &object, const MyLayersPtr &top_contacts, std::vector &buildplate_covered, + MyLayerStorage &layer_storage, std::vector &layer_support_areas) const; // Trim the top_contacts layers with the bottom_contacts layers if they overlap, so there would not be enough vertical space for both of them. - void trim_top_contacts_by_bottom_contacts(const PrintObject &object, const SupportGeneratorLayersPtr &bottom_contacts, SupportGeneratorLayersPtr &top_contacts) const; + void trim_top_contacts_by_bottom_contacts(const PrintObject &object, const MyLayersPtr &bottom_contacts, MyLayersPtr &top_contacts) const; // Generate raft layers and the intermediate support layers between the bottom contact and top contact surfaces. - SupportGeneratorLayersPtr raft_and_intermediate_support_layers( + MyLayersPtr raft_and_intermediate_support_layers( const PrintObject &object, - const SupportGeneratorLayersPtr &bottom_contacts, - const SupportGeneratorLayersPtr &top_contacts, - SupportGeneratorLayerStorage &layer_storage) const; + const MyLayersPtr &bottom_contacts, + const MyLayersPtr &top_contacts, + MyLayerStorage &layer_storage) const; // Fill in the base layers with polygons. void generate_base_layers( const PrintObject &object, - const SupportGeneratorLayersPtr &bottom_contacts, - const SupportGeneratorLayersPtr &top_contacts, - SupportGeneratorLayersPtr &intermediate_layers, + const MyLayersPtr &bottom_contacts, + const MyLayersPtr &top_contacts, + MyLayersPtr &intermediate_layers, const std::vector &layer_support_areas) const; + // Generate raft layers, also expand the 1st support layer + // in case there is no raft layer to improve support adhesion. + MyLayersPtr generate_raft_base( + const PrintObject &object, + const MyLayersPtr &top_contacts, + const MyLayersPtr &interface_layers, + const MyLayersPtr &base_interface_layers, + const MyLayersPtr &base_layers, + MyLayerStorage &layer_storage) const; + + // Turn some of the base layers into base interface layers. + // For soluble interfaces with non-soluble bases, print maximum two first interface layers with the base + // extruder to improve adhesion of the soluble filament to the base. + std::pair generate_interface_layers( + const MyLayersPtr &bottom_contacts, + const MyLayersPtr &top_contacts, + MyLayersPtr &intermediate_layers, + MyLayerStorage &layer_storage) const; + + // Trim support layers by an object to leave a defined gap between // the support volume and the object. void trim_support_layers_by_object( const PrintObject &object, - SupportGeneratorLayersPtr &support_layers, + MyLayersPtr &support_layers, const coordf_t gap_extra_above, const coordf_t gap_extra_below, const coordf_t gap_xy) const; @@ -86,14 +237,25 @@ class PrintObjectSupportMaterial void clip_with_shape(); */ + // Produce the actual G-code. + void generate_toolpaths( + SupportLayerPtrs &support_layers, + const MyLayersPtr &raft_layers, + const MyLayersPtr &bottom_contacts, + const MyLayersPtr &top_contacts, + const MyLayersPtr &intermediate_layers, + const MyLayersPtr &interface_layers, + const MyLayersPtr &base_interface_layers) const; + // Following objects are not owned by SupportMaterial class. + const PrintObject *m_object; const PrintConfig *m_print_config; const PrintObjectConfig *m_object_config; // Pre-calculated parameters shared between the object slicer and the support generator, // carrying information on a raft, 1st layer height, 1st object layer height, gap between the raft and object etc. SlicingParameters m_slicing_params; // Various precomputed support parameters to be shared with external functions. - SupportParameters m_support_params; + SupportParams m_support_params; }; } // namespace Slic3r diff --git a/src/libslic3r/Support/SupportParameters.cpp b/src/libslic3r/Support/SupportParameters.cpp deleted file mode 100644 index 8508206cb9f..00000000000 --- a/src/libslic3r/Support/SupportParameters.cpp +++ /dev/null @@ -1,144 +0,0 @@ -#include "../Print.hpp" -#include "../PrintConfig.hpp" -#include "../Slicing.hpp" -#include "SupportParameters.hpp" - -namespace Slic3r::FFFSupport { - -SupportParameters::SupportParameters(const PrintObject &object) -{ - const PrintConfig &print_config = object.print()->config(); - const PrintObjectConfig &object_config = object.config(); - const SlicingParameters &slicing_params = object.slicing_parameters(); - - this->soluble_interface = slicing_params.soluble_interface; - this->soluble_interface_non_soluble_base = - // Zero z-gap between the overhangs and the support interface. - slicing_params.soluble_interface && - // Interface extruder soluble. - object_config.support_interface_filament.value > 0 && print_config.filament_soluble.get_at(object_config.support_interface_filament.value - 1) && - // Base extruder: Either "print with active extruder" not soluble. - (object_config.support_filament.value == 0 || ! print_config.filament_soluble.get_at(object_config.support_filament.value - 1)); - - { - int num_top_interface_layers = std::max(0, object_config.support_interface_top_layers.value); - int num_bottom_interface_layers = object_config.support_interface_bottom_layers < 0 ? - num_top_interface_layers : object_config.support_interface_bottom_layers; - this->has_top_contacts = num_top_interface_layers > 0; - this->has_bottom_contacts = num_bottom_interface_layers > 0; - this->num_top_interface_layers = this->has_top_contacts ? size_t(num_top_interface_layers - 1) : 0; - this->num_bottom_interface_layers = this->has_bottom_contacts ? size_t(num_bottom_interface_layers - 1) : 0; - if (this->soluble_interface_non_soluble_base) { - // Try to support soluble dense interfaces with non-soluble dense interfaces. - this->num_top_base_interface_layers = size_t(std::min(num_top_interface_layers / 2, 2)); - this->num_bottom_base_interface_layers = size_t(std::min(num_bottom_interface_layers / 2, 2)); - } else { - this->num_top_base_interface_layers = 0; - this->num_bottom_base_interface_layers = 0; - } - } - - this->first_layer_flow = Slic3r::support_material_1st_layer_flow(&object, float(slicing_params.first_print_layer_height)); - this->support_material_flow = Slic3r::support_material_flow(&object, float(slicing_params.layer_height)); - this->support_material_interface_flow = Slic3r::support_material_interface_flow(&object, float(slicing_params.layer_height)); - this->raft_interface_flow = support_material_interface_flow; - - // Calculate a minimum support layer height as a minimum over all extruders, but not smaller than 10um. - this->support_layer_height_min = scaled(0.01); - for (auto lh : print_config.min_layer_height.values) - this->support_layer_height_min = std::min(this->support_layer_height_min, std::max(0.01, lh)); - for (auto layer : object.layers()) - this->support_layer_height_min = std::min(this->support_layer_height_min, std::max(0.01, layer->height)); - - if (object_config.support_interface_top_layers.value == 0) { - // No interface layers allowed, print everything with the base support pattern. - this->support_material_interface_flow = this->support_material_flow; - } - - // Evaluate the XY gap between the object outer perimeters and the support structures. - // Evaluate the XY gap between the object outer perimeters and the support structures. - coordf_t external_perimeter_width = 0.; - coordf_t bridge_flow_ratio = 0; - for (size_t region_id = 0; region_id < object.num_printing_regions(); ++ region_id) { - const PrintRegion ®ion = object.printing_region(region_id); - external_perimeter_width = std::max(external_perimeter_width, coordf_t(region.flow(object, frExternalPerimeter, slicing_params.layer_height).width())); - bridge_flow_ratio += region.config().bridge_flow; - } - this->gap_xy = object_config.support_object_xy_distance;//.get_abs_value(external_perimeter_width); - bridge_flow_ratio /= object.num_printing_regions(); - - this->support_material_bottom_interface_flow = slicing_params.soluble_interface || ! object_config.thick_bridges ? - this->support_material_interface_flow.with_flow_ratio(bridge_flow_ratio) : - Flow::bridging_flow(bridge_flow_ratio * this->support_material_interface_flow.nozzle_diameter(), this->support_material_interface_flow.nozzle_diameter()); - - this->can_merge_support_regions = object_config.support_filament.value == object_config.support_interface_filament.value; - if (!this->can_merge_support_regions && (object_config.support_filament.value == 0 || object_config.support_interface_filament.value == 0)) { - // One of the support extruders is of "don't care" type. - auto object_extruders = object.object_extruders(); - if (object_extruders.size() == 1 && - *object_extruders.begin() == std::max(object_config.support_filament.value, object_config.support_interface_filament.value)) - // Object is printed with the same extruder as the support. - this->can_merge_support_regions = true; - } - - double interface_spacing = object_config.support_interface_spacing.value + this->support_material_interface_flow.spacing(); - this->interface_density = std::min(1., this->support_material_interface_flow.spacing() / interface_spacing); - double raft_interface_spacing = object_config.support_interface_spacing.value + this->raft_interface_flow.spacing(); - this->raft_interface_density = std::min(1., this->raft_interface_flow.spacing() / raft_interface_spacing); - double support_spacing = object_config.support_base_pattern_spacing.value + this->support_material_flow.spacing(); - this->support_density = std::min(1., this->support_material_flow.spacing() / support_spacing); - if (object_config.support_interface_top_layers.value == 0) { - // No interface layers allowed, print everything with the base support pattern. - this->interface_density = this->support_density; - } - - SupportMaterialPattern support_pattern = object_config.support_base_pattern; - this->with_sheath = false;//object_config.support_material_with_sheath; - this->base_fill_pattern = - support_pattern == smpHoneycomb ? ipHoneycomb : - this->support_density > 0.95 || this->with_sheath ? ipRectilinear : ipSupportBase; - this->interface_fill_pattern = (this->interface_density > 0.95 ? ipRectilinear : ipSupportBase); - this->raft_interface_fill_pattern = this->raft_interface_density > 0.95 ? ipRectilinear : ipSupportBase; - this->contact_fill_pattern = - (object_config.support_interface_pattern == smipAuto && slicing_params.soluble_interface) || - object_config.support_interface_pattern == smipConcentric ? - ipConcentric : - (this->interface_density > 0.95 ? ipRectilinear : ipSupportBase); - - this->base_angle = Geometry::deg2rad(float(object_config.support_angle.value)); - this->interface_angle = Geometry::deg2rad(float(object_config.support_angle.value + 90.)); - this->raft_angle_1st_layer = 0.f; - this->raft_angle_base = 0.f; - this->raft_angle_interface = 0.f; - if (slicing_params.base_raft_layers > 1) { - assert(slicing_params.raft_layers() >= 4); - // There are all raft layer types (1st layer, base, interface & contact layers) available. - this->raft_angle_1st_layer = this->interface_angle; - this->raft_angle_base = this->base_angle; - this->raft_angle_interface = this->interface_angle; - if ((slicing_params.interface_raft_layers & 1) == 0) - // Allign the 1st raft interface layer so that the object 1st layer is hatched perpendicularly to the raft contact interface. - this->raft_angle_interface += float(0.5 * M_PI); - } else if (slicing_params.base_raft_layers == 1 || slicing_params.interface_raft_layers > 1) { - assert(slicing_params.raft_layers() == 2 || slicing_params.raft_layers() == 3); - // 1st layer, interface & contact layers available. - this->raft_angle_1st_layer = this->base_angle; - this->raft_angle_interface = this->interface_angle + 0.5 * M_PI; - } else if (slicing_params.interface_raft_layers == 1) { - // Only the contact raft layer is non-empty, which will be printed as the 1st layer. - assert(slicing_params.base_raft_layers == 0); - assert(slicing_params.interface_raft_layers == 1); - assert(slicing_params.raft_layers() == 1); - this->raft_angle_1st_layer = float(0.5 * M_PI); - this->raft_angle_interface = this->raft_angle_1st_layer; - } else { - // No raft. - assert(slicing_params.base_raft_layers == 0); - assert(slicing_params.interface_raft_layers == 0); - assert(slicing_params.raft_layers() == 0); - } - - this->tree_branch_diameter_double_wall_area_scaled = 0.25 * sqr(scaled(object_config.tree_support_branch_diameter_double_wall.value)) * M_PI; -} - -} // namespace Slic3r diff --git a/src/libslic3r/Support/SupportParameters.hpp b/src/libslic3r/Support/SupportParameters.hpp index 8a63d9f3f02..e240504c23c 100644 --- a/src/libslic3r/Support/SupportParameters.hpp +++ b/src/libslic3r/Support/SupportParameters.hpp @@ -9,10 +9,142 @@ namespace Slic3r { class PrintObject; enum InfillPattern : int; -namespace FFFSupport { - struct SupportParameters { - SupportParameters(const PrintObject &object); + SupportParameters(const PrintObject &object) + { + const PrintConfig &print_config = object.print()->config(); + const PrintObjectConfig &object_config = object.config(); + const SlicingParameters &slicing_params = object.slicing_parameters(); + + this->soluble_interface = slicing_params.soluble_interface; + this->soluble_interface_non_soluble_base = + // Zero z-gap between the overhangs and the support interface. + slicing_params.soluble_interface && + // Interface extruder soluble. + object_config.support_interface_filament.value > 0 && print_config.filament_soluble.get_at(object_config.support_interface_filament.value - 1) && + // Base extruder: Either "print with active extruder" not soluble. + (object_config.support_filament.value == 0 || ! print_config.filament_soluble.get_at(object_config.support_filament.value - 1)); + + { + int num_top_interface_layers = std::max(0, object_config.support_interface_top_layers.value); + int num_bottom_interface_layers = object_config.support_interface_bottom_layers < 0 ? + num_top_interface_layers : object_config.support_interface_bottom_layers; + this->has_top_contacts = num_top_interface_layers > 0; + this->has_bottom_contacts = num_bottom_interface_layers > 0; + this->num_top_interface_layers = this->has_top_contacts ? size_t(num_top_interface_layers - 1) : 0; + this->num_bottom_interface_layers = this->has_bottom_contacts ? size_t(num_bottom_interface_layers - 1) : 0; + if (this->soluble_interface_non_soluble_base) { + // Try to support soluble dense interfaces with non-soluble dense interfaces. + this->num_top_base_interface_layers = size_t(std::min(num_top_interface_layers / 2, 2)); + this->num_bottom_base_interface_layers = size_t(std::min(num_bottom_interface_layers / 2, 2)); + } else { + this->num_top_base_interface_layers = 0; + this->num_bottom_base_interface_layers = 0; + } + } + + this->first_layer_flow = Slic3r::support_material_1st_layer_flow(&object, float(slicing_params.first_print_layer_height)); + this->support_material_flow = Slic3r::support_material_flow(&object, float(slicing_params.layer_height)); + this->support_material_interface_flow = Slic3r::support_material_interface_flow(&object, float(slicing_params.layer_height)); + this->raft_interface_flow = support_material_interface_flow; + + // Calculate a minimum support layer height as a minimum over all extruders, but not smaller than 10um. + this->support_layer_height_min = scaled(0.01); + for (auto lh : print_config.min_layer_height.values) + this->support_layer_height_min = std::min(this->support_layer_height_min, std::max(0.01, lh)); + for (auto layer : object.layers()) + this->support_layer_height_min = std::min(this->support_layer_height_min, std::max(0.01, layer->height)); + + if (object_config.support_interface_top_layers.value == 0) { + // No interface layers allowed, print everything with the base support pattern. + this->support_material_interface_flow = this->support_material_flow; + } + + // Evaluate the XY gap between the object outer perimeters and the support structures. + // Evaluate the XY gap between the object outer perimeters and the support structures. + coordf_t external_perimeter_width = 0.; + coordf_t bridge_flow_ratio = 0; + for (size_t region_id = 0; region_id < object.num_printing_regions(); ++ region_id) { + const PrintRegion ®ion = object.printing_region(region_id); + external_perimeter_width = std::max(external_perimeter_width, coordf_t(region.flow(object, frExternalPerimeter, slicing_params.layer_height).width())); + bridge_flow_ratio += region.config().bridge_flow; + } + this->gap_xy = object_config.support_object_xy_distance;//.get_abs_value(external_perimeter_width); + bridge_flow_ratio /= object.num_printing_regions(); + + this->support_material_bottom_interface_flow = slicing_params.soluble_interface || ! object_config.thick_bridges ? + this->support_material_interface_flow.with_flow_ratio(bridge_flow_ratio) : + Flow::bridging_flow(bridge_flow_ratio * this->support_material_interface_flow.nozzle_diameter(), this->support_material_interface_flow.nozzle_diameter()); + + this->can_merge_support_regions = object_config.support_filament.value == object_config.support_interface_filament.value; + if (!this->can_merge_support_regions && (object_config.support_filament.value == 0 || object_config.support_interface_filament.value == 0)) { + // One of the support extruders is of "don't care" type. + auto object_extruders = object.object_extruders(); + if (object_extruders.size() == 1 && + *object_extruders.begin() == std::max(object_config.support_filament.value, object_config.support_interface_filament.value)) + // Object is printed with the same extruder as the support. + this->can_merge_support_regions = true; + } + + double interface_spacing = object_config.support_interface_spacing.value + this->support_material_interface_flow.spacing(); + this->interface_density = std::min(1., this->support_material_interface_flow.spacing() / interface_spacing); + double raft_interface_spacing = object_config.support_interface_spacing.value + this->raft_interface_flow.spacing(); + this->raft_interface_density = std::min(1., this->raft_interface_flow.spacing() / raft_interface_spacing); + double support_spacing = object_config.support_base_pattern_spacing.value + this->support_material_flow.spacing(); + this->support_density = std::min(1., this->support_material_flow.spacing() / support_spacing); + if (object_config.support_interface_top_layers.value == 0) { + // No interface layers allowed, print everything with the base support pattern. + this->interface_density = this->support_density; + } + + SupportMaterialPattern support_pattern = object_config.support_base_pattern; + this->with_sheath = false;//object_config.support_material_with_sheath; + this->base_fill_pattern = + support_pattern == smpHoneycomb ? ipHoneycomb : + this->support_density > 0.95 || this->with_sheath ? ipRectilinear : ipSupportBase; + this->interface_fill_pattern = (this->interface_density > 0.95 ? ipRectilinear : ipSupportBase); + this->raft_interface_fill_pattern = this->raft_interface_density > 0.95 ? ipRectilinear : ipSupportBase; + this->contact_fill_pattern = + (object_config.support_interface_pattern == smipAuto && slicing_params.soluble_interface) || + object_config.support_interface_pattern == smipConcentric ? + ipConcentric : + (this->interface_density > 0.95 ? ipRectilinear : ipSupportBase); + + this->base_angle = Geometry::deg2rad(float(object_config.support_angle.value)); + this->interface_angle = Geometry::deg2rad(float(object_config.support_angle.value + 90.)); + this->raft_angle_1st_layer = 0.f; + this->raft_angle_base = 0.f; + this->raft_angle_interface = 0.f; + if (slicing_params.base_raft_layers > 1) { + assert(slicing_params.raft_layers() >= 4); + // There are all raft layer types (1st layer, base, interface & contact layers) available. + this->raft_angle_1st_layer = this->interface_angle; + this->raft_angle_base = this->base_angle; + this->raft_angle_interface = this->interface_angle; + if ((slicing_params.interface_raft_layers & 1) == 0) + // Allign the 1st raft interface layer so that the object 1st layer is hatched perpendicularly to the raft contact interface. + this->raft_angle_interface += float(0.5 * M_PI); + } else if (slicing_params.base_raft_layers == 1 || slicing_params.interface_raft_layers > 1) { + assert(slicing_params.raft_layers() == 2 || slicing_params.raft_layers() == 3); + // 1st layer, interface & contact layers available. + this->raft_angle_1st_layer = this->base_angle; + this->raft_angle_interface = this->interface_angle + 0.5 * M_PI; + } else if (slicing_params.interface_raft_layers == 1) { + // Only the contact raft layer is non-empty, which will be printed as the 1st layer. + assert(slicing_params.base_raft_layers == 0); + assert(slicing_params.interface_raft_layers == 1); + assert(slicing_params.raft_layers() == 1); + this->raft_angle_1st_layer = float(0.5 * M_PI); + this->raft_angle_interface = this->raft_angle_1st_layer; + } else { + // No raft. + assert(slicing_params.base_raft_layers == 0); + assert(slicing_params.interface_raft_layers == 0); + assert(slicing_params.raft_layers() == 0); + } + + this->tree_branch_diameter_double_wall_area_scaled = 0.25 * sqr(scaled(object_config.tree_support_branch_diameter_double_wall.value)) * M_PI; + } // Both top / bottom contacts and interfaces are soluble. bool soluble_interface; @@ -89,8 +221,6 @@ struct SupportParameters { { return this->raft_angle_interface + ((interface_id & 1) ? float(- M_PI / 4.) : float(+ M_PI / 4.)); } }; -} // namespace FFFSupport - } // namespace Slic3r #endif /* slic3r_SupportParameters_hpp_ */ diff --git a/src/libslic3r/SupportSpotsGenerator.cpp b/src/libslic3r/Support/SupportSpotsGenerator.cpp similarity index 100% rename from src/libslic3r/SupportSpotsGenerator.cpp rename to src/libslic3r/Support/SupportSpotsGenerator.cpp diff --git a/src/libslic3r/SupportSpotsGenerator.hpp b/src/libslic3r/Support/SupportSpotsGenerator.hpp similarity index 100% rename from src/libslic3r/SupportSpotsGenerator.hpp rename to src/libslic3r/Support/SupportSpotsGenerator.hpp diff --git a/src/libslic3r/Support/TreeModelVolumes.cpp b/src/libslic3r/Support/TreeModelVolumes.cpp index d77400309b1..f082115ea30 100644 --- a/src/libslic3r/Support/TreeModelVolumes.cpp +++ b/src/libslic3r/Support/TreeModelVolumes.cpp @@ -26,7 +26,7 @@ #include #include -namespace Slic3r::FFFTreeSupport +namespace Slic3r::TreeSupport3D { using namespace std::literals; @@ -871,4 +871,4 @@ std::vector +#include "MinimumSpanningTree.hpp" #include "TreeSupport.hpp" -#include "TreeSupportCommon.hpp" -#include "SupportCommon.hpp" -#include "OrganicSupport.hpp" - -#include "../AABBTreeIndirect.hpp" -#include "../BuildVolume.hpp" -#include "../ClipperUtils.hpp" -#include "../EdgeGrid.hpp" -#include "../Fill/Fill.hpp" -#include "../Layer.hpp" -#include "../Print.hpp" -#include "../MultiPoint.hpp" -#include "../Polygon.hpp" -#include "../Polyline.hpp" -#include "../MutablePolygon.hpp" -#include "libslic3r.h" - -#include -#include -#include -#include -#include -#include -#include +#include "Print.hpp" +#include "Layer.hpp" +#include "Fill/FillBase.hpp" +#include "Fill/FillConcentric.hpp" +#include "CurveAnalyzer.hpp" +#include "SVG.hpp" +#include "ShortestPath.hpp" +#include "I18N.hpp" +#include +#include "TreeSupport3D.hpp" #include - +#include #include -// #define TREESUPPORT_DEBUG_SVG +#define _L(s) Slic3r::I18N::translate(s) -using namespace Slic3r::FFFSupport; +#define USE_PLAN_LAYER_HEIGHTS 1 -namespace Slic3r -{ +#ifndef M_PI +#define M_PI 3.1415926535897932384626433832795 +#endif +#ifndef SIGN +#define SIGN(x) (x>=0?1:-1) +#endif +#define TAU (2.0 * M_PI) +#define NO_INDEX (std::numeric_limits::max()) -namespace FFFTreeSupport -{ +// #define SUPPORT_TREE_DEBUG_TO_SVG -enum class LineStatus +#ifdef SUPPORT_TREE_DEBUG_TO_SVG +#include "nlohmann/json.hpp" +#endif +namespace Slic3r { - INVALID, - TO_MODEL, - TO_MODEL_GRACIOUS, - TO_MODEL_GRACIOUS_SAFE, - TO_BP, - TO_BP_SAFE -}; - -using LineInformation = std::vector>; -using LineInformations = std::vector; -using namespace std::literals; +#define unscale_(val) ((val) * SCALING_FACTOR) -static inline void validate_range(const Point &pt) +inline unsigned int round_divide(unsigned int dividend, unsigned int divisor) //!< Return dividend divided by divisor rounded to the nearest integer { - static constexpr const int32_t hi = 65536 * 16384; - if (pt.x() > hi || pt.y() > hi || -pt.x() > hi || -pt.y() > hi) - throw ClipperLib::clipperException("Coordinate outside allowed range"); + return (dividend + divisor / 2) / divisor; } - -static inline void validate_range(const Points &points) +inline unsigned int round_up_divide(unsigned int dividend, unsigned int divisor) //!< Return dividend divided by divisor rounded to the nearest integer { - for (const Point &p : points) - validate_range(p); + return (dividend + divisor - 1) / divisor; } -static inline void validate_range(const MultiPoint &mp) +inline double dot_with_unscale(const Point a, const Point b) { - validate_range(mp.points); + return unscale_(a(0)) * unscale_(b(0)) + unscale_(a(1)) * unscale_(b(1)); } -static inline void validate_range(const Polygons &polygons) +inline double vsize2_with_unscale(const Point pt) { - for (const Polygon &p : polygons) - validate_range(p); + return dot_with_unscale(pt, pt); } -static inline void validate_range(const Polylines &polylines) +inline Point turn90_ccw(const Point pt) { - for (const Polyline &p : polylines) - validate_range(p); -} + Point ret; -static inline void validate_range(const LineInformation &lines) -{ - for (const auto& p : lines) - validate_range(p.first); + ret(0) = -pt(1); + ret(1) = pt(0); + return ret; } -static inline void validate_range(const LineInformations &lines) +inline Point normal(Point pt, double scale) { - for (const LineInformation &l : lines) - validate_range(l); -} + double length = scale_(sqrt(vsize2_with_unscale(pt))); -static inline void check_self_intersections(const Polygons &polygons, const std::string_view message) -{ -#ifdef TREE_SUPPORT_SHOW_ERRORS_WIN32 - if (!intersecting_edges(polygons).empty()) - ::MessageBoxA(nullptr, (std::string("TreeSupport infill self intersections: ") + std::string(message)).c_str(), "Bug detected!", MB_OK | MB_SYSTEMMODAL | MB_SETFOREGROUND | MB_ICONWARNING); -#endif // TREE_SUPPORT_SHOW_ERRORS_WIN32 -} -static inline void check_self_intersections(const ExPolygon &expoly, const std::string_view message) -{ -#ifdef TREE_SUPPORT_SHOW_ERRORS_WIN32 - check_self_intersections(to_polygons(expoly), message); -#endif // TREE_SUPPORT_SHOW_ERRORS_WIN32 + return pt * (scale / length); } -static std::vector>> group_meshes(const Print &print, const std::vector &print_object_ids) +enum TreeSupportStage { + STAGE_DETECT_OVERHANGS, + STAGE_GENERATE_CONTACT_NODES, + STAGE_DROP_DOWN_NODES, + STAGE_DRAW_CIRCLES, + STAGE_GENERATE_TOOLPATHS, + STAGE_MinimumSpanningTree, + STAGE_GET_AVOIDANCE, + STAGE_projection_onto_ex, + STAGE_get_collision, + STAGE_intersection_ln, + STAGE_total, + NUM_STAGES +}; + +class TreeSupportProfiler { - std::vector>> grouped_meshes; +public: + uint32_t stage_durations[NUM_STAGES]; + uint32_t stage_index = 0; + boost::posix_time::ptime tic_time; + boost::posix_time::ptime toc_time; - //FIXME this is ugly, it does not belong here. - for (size_t object_id : print_object_ids) { - const PrintObject &print_object = *print.get_object(object_id); - const PrintObjectConfig &object_config = print_object.config(); - if (object_config.support_top_z_distance < EPSILON) - // || min_feature_size < scaled(0.1) that is the minimum line width - TreeSupportSettings::soluble = true; + TreeSupportProfiler() + { + for (uint32_t& item : stage_durations) { + item = 0; + } } - size_t largest_printed_mesh_idx = 0; + void stage_start(TreeSupportStage stage) + { + if (stage > NUM_STAGES) + return; - // Group all meshes that can be processed together. NOTE this is different from mesh-groups! Only one setting object is needed per group, - // as different settings in the same group may only occur in the tip, which uses the original settings objects from the meshes. - for (size_t object_id : print_object_ids) { - const PrintObject &print_object = *print.get_object(object_id); + m_stage_start_times[stage] = boost::posix_time::microsec_clock::local_time(); + } - bool found_existing_group = false; - TreeSupportSettings next_settings{ TreeSupportMeshGroupSettings{ print_object }, print_object.slicing_parameters() }; - //FIXME for now only a single object per group is enabled. -#if 0 - for (size_t idx = 0; idx < grouped_meshes.size(); ++ idx) - if (next_settings == grouped_meshes[idx].first) { - found_existing_group = true; - grouped_meshes[idx].second.emplace_back(object_id); - // handle some settings that are only used for performance reasons. This ensures that a horrible set setting intended to improve performance can not reduce it drastically. - grouped_meshes[idx].first.performance_interface_skip_layers = std::min(grouped_meshes[idx].first.performance_interface_skip_layers, next_settings.performance_interface_skip_layers); - } -#endif - if (! found_existing_group) - grouped_meshes.emplace_back(next_settings, std::vector{ object_id }); + void stage_finish(TreeSupportStage stage) + { + if (stage > NUM_STAGES) + return; - // no need to do this per mesh group as adaptive layers and raft setting are not setable per mesh. - if (print.get_object(largest_printed_mesh_idx)->layers().back()->print_z < print_object.layers().back()->print_z) - largest_printed_mesh_idx = object_id; + boost::posix_time::ptime time = boost::posix_time::microsec_clock::local_time(); + stage_durations[stage] = (time - m_stage_start_times[stage]).total_milliseconds(); } -#if 0 + void tic() { tic_time = boost::posix_time::microsec_clock::local_time(); } + void toc() { toc_time = boost::posix_time::microsec_clock::local_time(); } + void stage_add(TreeSupportStage stage, bool do_toc = false) { - std::vector known_z(storage.meshes[largest_printed_mesh_idx].layers.size()); - for (size_t z = 0; z < storage.meshes[largest_printed_mesh_idx].layers.size(); z++) - known_z[z] = storage.meshes[largest_printed_mesh_idx].layers[z].printZ; - for (size_t idx = 0; idx < grouped_meshes.size(); ++ idx) - grouped_meshes[idx].first.setActualZ(known_z); + if (stage > NUM_STAGES) + return; + if(do_toc) + toc_time = boost::posix_time::microsec_clock::local_time(); + stage_durations[stage] += (toc_time - tic_time).total_milliseconds(); } -#endif - - return grouped_meshes; -} -#if 0 -// todo remove as only for debugging relevant -[[nodiscard]] static std::string getPolygonAsString(const Polygons& poly) -{ - std::string ret; - for (auto path : poly) - for (Point p : path) { - if (ret != "") - ret += ","; - ret += "(" + std::to_string(p.x()) + "," + std::to_string(p.y()) + ")"; - } - return ret; -} -#endif + std::string report() + { + std::stringstream ss; + ss << "total overhange cost: " << stage_durations[STAGE_total] + << "; STAGE_DETECT_OVERHANGS: " << stage_durations[STAGE_DETECT_OVERHANGS] + << "; STAGE_GENERATE_CONTACT_NODES: " << stage_durations[STAGE_GENERATE_CONTACT_NODES] + << "; STAGE_DROP_DOWN_NODES: " << stage_durations[STAGE_DROP_DOWN_NODES] + << "; STAGE_DRAW_CIRCLES: " << stage_durations[STAGE_DRAW_CIRCLES] + << "; STAGE_GENERATE_TOOLPATHS: " << stage_durations[STAGE_GENERATE_TOOLPATHS] + << "; STAGE_MinimumSpanningTree: " << stage_durations[STAGE_MinimumSpanningTree] + << "; STAGE_GET_AVOIDANCE: " << stage_durations[STAGE_GET_AVOIDANCE] + << "; STAGE_projection_onto_ex: " << stage_durations[STAGE_projection_onto_ex] + << "; STAGE_get_collision: " << stage_durations[STAGE_get_collision] + << "; STAGE_intersection_ln: " << stage_durations[STAGE_intersection_ln]; + + return ss.str(); + } +private: + boost::posix_time::ptime m_stage_start_times[NUM_STAGES]; +}; +TreeSupportProfiler profiler; -[[nodiscard]] static const std::vector generate_overhangs(const TreeSupportSettings &settings, const PrintObject &print_object, std::function throw_on_cancel) +Lines spanning_tree_to_lines(const std::vector& spanning_trees) { - const size_t num_raft_layers = settings.raft_layers.size(); - const size_t num_object_layers = print_object.layer_count(); - const size_t num_layers = num_object_layers + num_raft_layers; - std::vector out(num_layers, Polygons{}); - - const PrintConfig &print_config = print_object.print()->config(); - const PrintObjectConfig &config = print_object.config(); - const bool support_auto = config.enable_support.value && is_auto(config.support_type.value); - const int support_enforce_layers = config.enforce_support_layers.value; - std::vector enforcers_layers{ print_object.slice_support_enforcers() }; - std::vector blockers_layers{ print_object.slice_support_blockers() }; - print_object.project_and_append_custom_facets(false, EnforcerBlockerType::ENFORCER, enforcers_layers); - print_object.project_and_append_custom_facets(false, EnforcerBlockerType::BLOCKER, blockers_layers); - const int support_threshold = config.support_threshold_angle.value; - const bool support_threshold_auto = support_threshold == 0; - // +1 makes the threshold inclusive - double tan_threshold = support_threshold_auto ? 0. : tan(M_PI * double(support_threshold + 1) / 180.); - //FIXME this is a fudge constant! - auto enforcer_overhang_offset = scaled(config.tree_support_tip_diameter.value); - - size_t num_overhang_layers = support_auto ? num_object_layers : std::min(num_object_layers, std::max(size_t(support_enforce_layers), enforcers_layers.size())); - tbb::parallel_for(tbb::blocked_range(1, num_overhang_layers), - [&print_object, &config, &print_config, &enforcers_layers, &blockers_layers, - support_auto, support_enforce_layers, support_threshold_auto, tan_threshold, enforcer_overhang_offset, num_raft_layers, &throw_on_cancel, &out] - (const tbb::blocked_range &range) { - for (LayerIndex layer_id = range.begin(); layer_id < range.end(); ++ layer_id) { - const Layer ¤t_layer = *print_object.get_layer(layer_id); - const Layer &lower_layer = *print_object.get_layer(layer_id - 1); - // Full overhangs with zero lower_layer_offset and no blockers applied. - Polygons raw_overhangs; - bool raw_overhangs_calculated = false; - // Final overhangs. - Polygons overhangs; - // For how many layers full overhangs shall be supported. - const bool enforced_layer = layer_id < support_enforce_layers; - if (support_auto || enforced_layer) { - float lower_layer_offset; - if (enforced_layer) - lower_layer_offset = 0; - else if (support_threshold_auto) { - float external_perimeter_width = 0; - for (const LayerRegion *layerm : lower_layer.regions()) - external_perimeter_width += layerm->flow(frExternalPerimeter).scaled_width(); - external_perimeter_width /= lower_layer.region_count(); - lower_layer_offset = float(0.5 * external_perimeter_width); - } else - lower_layer_offset = scaled(lower_layer.height / tan_threshold); - overhangs = lower_layer_offset == 0 ? - diff(current_layer.lslices, lower_layer.lslices) : - diff(current_layer.lslices, offset(lower_layer.lslices, lower_layer_offset)); - if (lower_layer_offset == 0) { - raw_overhangs = overhangs; - raw_overhangs_calculated = true; - } - if (! (enforced_layer || blockers_layers.empty() || blockers_layers[layer_id].empty())) { - Polygons &blocker = blockers_layers[layer_id]; - // Arthur: union_ is a must because after mirroring, the blocker polygons are in left-hand coordinates, ie clockwise, - // which are not valid polygons, and will be removed by offset. union_ can make these polygons right. - overhangs = diff(overhangs, offset(union_(blocker), scale_(g_config_tree_support_collision_resolution)), ApplySafetyOffset::Yes); - } - if (config.bridge_no_support) { - for (const LayerRegion *layerm : current_layer.regions()) - remove_bridges_from_contacts(print_config, lower_layer, *layerm, - float(layerm->flow(frExternalPerimeter).scaled_width()), overhangs); - } - } - //check_self_intersections(overhangs, "generate_overhangs1"); - if (! enforcers_layers.empty() && ! enforcers_layers[layer_id].empty()) { - // Has some support enforcers at this layer, apply them to the overhangs, don't apply the support threshold angle. - //enforcers_layers[layer_id] = union_(enforcers_layers[layer_id]); - //check_self_intersections(enforcers_layers[layer_id], "generate_overhangs - enforcers"); - //check_self_intersections(to_polygons(lower_layer.lslices), "generate_overhangs - lowerlayers"); - if (Polygons enforced_overhangs = intersection(raw_overhangs_calculated ? raw_overhangs : diff(current_layer.lslices, lower_layer.lslices), enforcers_layers[layer_id] /*, ApplySafetyOffset::Yes */); - ! enforced_overhangs.empty()) { - //FIXME this is a hack to make enforcers work on steep overhangs. - //check_self_intersections(enforced_overhangs, "generate_overhangs - enforced overhangs1"); - //Polygons enforced_overhangs_prev = enforced_overhangs; - //check_self_intersections(to_polygons(union_ex(enforced_overhangs)), "generate_overhangs - enforced overhangs11"); - //check_self_intersections(offset(union_ex(enforced_overhangs), - //FIXME enforcer_overhang_offset is a fudge constant! - enforced_overhangs = diff(offset(union_ex(enforced_overhangs), enforcer_overhang_offset), - lower_layer.lslices); -#ifdef TREESUPPORT_DEBUG_SVG -// if (! intersecting_edges(enforced_overhangs).empty()) - { - static int irun = 0; - SVG::export_expolygons(debug_out_path("treesupport-self-intersections-%d.svg", ++irun), - { { { current_layer.lslices }, { "current_layer.lslices", "yellow", 0.5f } }, - { { lower_layer.lslices }, { "lower_layer.lslices", "gray", 0.5f } }, - { { union_ex(enforced_overhangs) }, { "enforced_overhangs", "red", "black", "", scaled(0.1f), 0.5f } } }); - } -#endif // TREESUPPORT_DEBUG_SVG - //check_self_intersections(enforced_overhangs, "generate_overhangs - enforced overhangs2"); - overhangs = overhangs.empty() ? std::move(enforced_overhangs) : union_(overhangs, enforced_overhangs); - //check_self_intersections(overhangs, "generate_overhangs - enforcers"); - } - } - out[layer_id + num_raft_layers] = std::move(overhangs); - throw_on_cancel(); - } - }); - -#if 0 - if (num_raft_layers > 0) { - const Layer &first_layer = *print_object.get_layer(0); - // Final overhangs. - Polygons overhangs = - // Don't apply blockes on raft layer. - //(! blockers_layers.empty() && ! blockers_layers[layer_id].empty() ? - // diff(first_layer.lslices, blockers_layers[layer_id], ApplySafetyOffset::Yes) : - to_polygons(first_layer.lslices); -#if 0 - if (! enforcers_layers.empty() && ! enforcers_layers[layer_id].empty()) { - if (Polygons enforced_overhangs = intersection(first_layer.lslices, enforcers_layers[layer_id] /*, ApplySafetyOffset::Yes */); - ! enforced_overhangs.empty()) { - //FIXME this is a hack to make enforcers work on steep overhangs. - //FIXME enforcer_overhang_offset is a fudge constant! - enforced_overhangs = offset(union_ex(enforced_overhangs), enforcer_overhang_offset); - overhangs = overhangs.empty() ? std::move(enforced_overhangs) : union_(overhangs, enforced_overhangs); - } - } -#endif - out[num_raft_layers] = std::move(overhangs); - throw_on_cancel(); - } -#endif + Lines polylines; + for (const MinimumSpanningTree& mst : spanning_trees) { + std::vector points = mst.vertices(); + std::unordered_set to_ignore; + for (Point pt1 : points) { + if (to_ignore.find(pt1) != to_ignore.end()) + continue; - return out; -} + const std::vector& neighbours = mst.adjacent_nodes(pt1); + if (neighbours.empty()) + continue; -/*! - * \brief Precalculates all avoidances, that could be required. - * - * \param storage[in] Background storage to access meshes. - * \param currently_processing_meshes[in] Indexes of all meshes that are processed in this iteration - */ -[[nodiscard]] static LayerIndex precalculate(const Print &print, const std::vector &overhangs, const TreeSupportSettings &config, const std::vector &object_ids, TreeModelVolumes &volumes, std::function throw_on_cancel) -{ - // calculate top most layer that is relevant for support - LayerIndex max_layer = 0; - for (size_t object_id : object_ids) { - const PrintObject &print_object = *print.get_object(object_id); - const int num_raft_layers = int(config.raft_layers.size()); - const int num_layers = int(print_object.layer_count()) + num_raft_layers; - int max_support_layer_id = 0; - for (int layer_id = std::max(num_raft_layers, 1); layer_id < num_layers; ++ layer_id) - if (! overhangs[layer_id].empty()) - max_support_layer_id = layer_id; - max_layer = std::max(max_support_layer_id - int(config.z_distance_top_layers), 0); - } - if (max_layer > 0) - // The actual precalculation happens in TreeModelVolumes. - volumes.precalculate(*print.get_object(object_ids.front()), max_layer, throw_on_cancel); - return max_layer; -} + for (Point pt2 : neighbours) { + if (to_ignore.find(pt2) != to_ignore.end()) + continue; -/*! - * \brief Converts a Polygons object representing a line into the internal format. - * - * \param polylines[in] The Polyline that will be converted. - * \param layer_idx[in] The current layer. - * \return All lines of the \p polylines object, with information for each point regarding in which avoidance it is currently valid in. - */ -// Called by generate_initial_areas() -[[nodiscard]] static LineInformations convert_lines_to_internal( - const TreeModelVolumes &volumes, const TreeSupportSettings &config, - const Polylines &polylines, LayerIndex layer_idx) -{ - const bool min_xy_dist = config.xy_distance > config.xy_min_distance; - - LineInformations result; - // Also checks if the position is valid, if it is NOT, it deletes that point - for (const Polyline &line : polylines) { - LineInformation res_line; - for (Point p : line) { - if (! contains(volumes.getAvoidance(config.getRadius(0), layer_idx, TreeModelVolumes::AvoidanceType::FastSafe, false, min_xy_dist), p)) - res_line.emplace_back(p, LineStatus::TO_BP_SAFE); - else if (! contains(volumes.getAvoidance(config.getRadius(0), layer_idx, TreeModelVolumes::AvoidanceType::Fast, false, min_xy_dist), p)) - res_line.emplace_back(p, LineStatus::TO_BP); - else if (config.support_rests_on_model && ! contains(volumes.getAvoidance(config.getRadius(0), layer_idx, TreeModelVolumes::AvoidanceType::FastSafe, true, min_xy_dist), p)) - res_line.emplace_back(p, LineStatus::TO_MODEL_GRACIOUS_SAFE); - else if (config.support_rests_on_model && ! contains(volumes.getAvoidance(config.getRadius(0), layer_idx, TreeModelVolumes::AvoidanceType::Fast, true, min_xy_dist), p)) - res_line.emplace_back(p, LineStatus::TO_MODEL_GRACIOUS); - else if (config.support_rests_on_model && ! contains(volumes.getCollision(config.getRadius(0), layer_idx, min_xy_dist), p)) - res_line.emplace_back(p, LineStatus::TO_MODEL); - else if (!res_line.empty()) { - result.emplace_back(res_line); - res_line.clear(); + Line line(pt1, pt2); + polylines.push_back(line); } - } - if (!res_line.empty()) { - result.emplace_back(res_line); - res_line.clear(); + + to_ignore.insert(pt1); } } - - validate_range(result); - return result; + return polylines; } -#if 0 -/*! - * \brief Converts lines in internal format into a Polygons object representing these lines. - * - * \param lines[in] The lines that will be converted. - * \return All lines of the \p lines object as a Polygons object. - */ -[[nodiscard]] static Polylines convert_internal_to_lines(LineInformations lines) -{ - Polylines result; - for (LineInformation line : lines) { - Polyline path; - for (auto point_data : line) - path.points.emplace_back(point_data.first); - result.emplace_back(std::move(path)); - } - validate_range(result); - return result; -} -#endif -/*! - * \brief Evaluates if a point has to be added now. Required for a split_lines call in generate_initial_areas(). - * - * \param current_layer[in] The layer on which the point lies, point and its status. - * \return whether the point is valid. - */ -[[nodiscard]] static bool evaluate_point_for_next_layer_function( - const TreeModelVolumes &volumes, const TreeSupportSettings &config, - size_t current_layer, const std::pair &p) +#ifdef SUPPORT_TREE_DEBUG_TO_SVG +static std::string get_svg_filename(std::string layer_nr_or_z, std::string tag = "bbl_ts") { - using AvoidanceType = TreeModelVolumes::AvoidanceType; - const bool min_xy_dist = config.xy_distance > config.xy_min_distance; - if (! contains(volumes.getAvoidance(config.getRadius(0), current_layer - 1, p.second == LineStatus::TO_BP_SAFE ? AvoidanceType::FastSafe : AvoidanceType::Fast, false, min_xy_dist), p.first)) - return true; - if (config.support_rests_on_model && (p.second != LineStatus::TO_BP && p.second != LineStatus::TO_BP_SAFE)) - return ! contains( - p.second == LineStatus::TO_MODEL_GRACIOUS || p.second == LineStatus::TO_MODEL_GRACIOUS_SAFE ? - volumes.getAvoidance(config.getRadius(0), current_layer - 1, p.second == LineStatus::TO_MODEL_GRACIOUS_SAFE ? AvoidanceType::FastSafe : AvoidanceType::Fast, true, min_xy_dist) : - volumes.getCollision(config.getRadius(0), current_layer - 1, min_xy_dist), - p.first); - return false; -} + static bool rand_init = false; -/*! - * \brief Evaluates which points of some lines are not valid one layer below and which are. Assumes all points are valid on the current layer. Validity is evaluated using supplied lambda. - * - * \param lines[in] The lines that have to be evaluated. - * \param evaluatePoint[in] The function used to evaluate the points. - * \return A pair with which points are still valid in the first slot and which are not in the second slot. - */ -template -[[nodiscard]] static std::pair split_lines(const LineInformations &lines, EvaluatePointFn evaluatePoint) -{ - // assumes all Points on the current line are valid - - LineInformations keep; - LineInformations set_free; - for (const std::vector> &line : lines) { - bool current_keep = true; - LineInformation resulting_line; - for (const std::pair &me : line) { - if (evaluatePoint(me) != current_keep) { - if (! resulting_line.empty()) - (current_keep ? &keep : &set_free)->emplace_back(std::move(resulting_line)); - current_keep = !current_keep; - } - resulting_line.emplace_back(me); - } - if (! resulting_line.empty()) - (current_keep ? &keep : &set_free)->emplace_back(std::move(resulting_line)); + if (!rand_init) { + srand(time(NULL)); + rand_init = true; } - validate_range(keep); - validate_range(set_free); - return std::pair>>, std::vector>>>(keep, set_free); + + int rand_num = rand() % 1000000; + //makedir("./SVG"); + std::string prefix = "./SVG/"; + std::string suffix = ".svg"; + return prefix + tag + "_" + layer_nr_or_z /*+ "_" + std::to_string(rand_num)*/ + suffix; } -// Ported from CURA's PolygonUtils::getNextPointWithDistance() -// Sample a next point at distance "dist" from start_pt on polyline segment (start_idx, start_idx + 1). -// Returns sample point and start index of its segment on polyline if such sample exists. -static std::optional> polyline_sample_next_point_at_distance(const Points &polyline, const Point &start_pt, size_t start_idx, double dist) +static void draw_contours_and_nodes_to_svg +( + std::string layer_nr_or_z, + const ExPolygons &overhangs, + const ExPolygons &overhangs_after_offset, + const ExPolygons &outlines_below, + const std::vector &layer_nodes, + const std::vector &lower_layer_nodes, + std::string name_prefix, + std::vector legends = { "overhang","avoid","outlines" }, std::vector colors = { "blue","red","yellow" } +) { - const double dist2 = sqr(dist); - const auto dist2i = int64_t(dist2); - const auto eps = scaled(0.01); - - for (size_t i = start_idx + 1; i < polyline.size(); ++ i) { - const Point p1 = polyline[i]; - if ((p1 - start_pt).cast().squaredNorm() >= dist2i) { - // The end point is outside the circle with center "start_pt" and radius "dist". - const Point p0 = polyline[i - 1]; - Vec2d v = (p1 - p0).cast(); - double l2v = v.squaredNorm(); - if (l2v < sqr(eps)) { - // Very short segment. - Point c = (p0 + p1) / 2; - if (std::abs((start_pt - c).cast().norm() - dist) < eps) - return std::pair{ c, i - 1 }; - else - continue; - } - Vec2d p0f = (start_pt - p0).cast(); - // Foot point of start_pt into v. - Vec2d foot_pt = v * (p0f.dot(v) / l2v); - // Vector from foot point of "start_pt" to "start_pt". - Vec2d xf = p0f - foot_pt; - // Squared distance of "start_pt" from the ray (p0, p1). - double l2_from_line = xf.squaredNorm(); - // Squared distance of an intersection point of a circle with center at the foot point. - if (double l2_intersection = dist2 - l2_from_line; - l2_intersection > - SCALED_EPSILON) { - // The ray (p0, p1) touches or intersects a circle centered at "start_pt" with radius "dist". - // Distance of the circle intersection point from the foot point. - l2_intersection = std::max(l2_intersection, 0.); - if ((v - foot_pt).cast().squaredNorm() >= l2_intersection) { - // Intersection of the circle with the segment (p0, p1) is on the right side (close to p1) from the foot point. - Point p = p0 + (foot_pt + v * sqrt(l2_intersection / l2v)).cast(); - validate_range(p); - return std::pair{ p, i - 1 }; - } - } - } + BoundingBox bbox = get_extents(overhangs); + bbox.merge(get_extents(overhangs_after_offset)); + bbox.merge(get_extents(outlines_below)); + Points layer_pts; + for (TreeSupport::Node* node : layer_nodes) { + layer_pts.push_back(node->position); } - return {}; -} + bbox.merge(get_extents(layer_pts)); + bbox.inflated(scale_(1)); + bbox.max.x() = std::max(bbox.max.x(), (coord_t)scale_(10)); + bbox.max.y() = std::max(bbox.max.y(), (coord_t)scale_(10)); + + SVG svg; + if(!layer_nr_or_z.empty()) + svg.open(get_svg_filename(layer_nr_or_z, name_prefix), bbox); + else + svg.open(name_prefix, bbox); + if (!svg.is_opened()) return; -/*! - * \brief Eensures that every line segment is about distance in length. The resulting lines may differ from the original but all points are on the original - * - * \param input[in] The lines on which evenly spaced points should be placed. - * \param distance[in] The distance the points should be from each other. - * \param min_points[in] The amount of points that have to be placed. If not enough can be placed the distance will be reduced to place this many points. - * \return A Polygons object containing the evenly spaced points. Does not represent an area, more a collection of points on lines. - */ -[[nodiscard]] static Polylines ensure_maximum_distance_polyline(const Polylines &input, double distance, size_t min_points) -{ - Polylines result; - for (Polyline part : input) { - if (part.empty()) - continue; + // draw grid + svg.draw_grid(bbox, "gray", coord_t(scale_(0.05))); - double len = length(part.points); - Polyline line; - double current_distance = std::max(distance, scaled(0.1)); - if (len < 2 * distance && min_points <= 1) - { - // Insert the opposite point of the first one. - //FIXME pretty expensive - Polyline pl(part); - pl.clip_end(len / 2); - line.points.emplace_back(pl.points.back()); - } - else - { - size_t optimal_end_index = part.size() - 1; - - if (part.front() == part.back()) { - size_t optimal_start_index = 0; - // If the polyline was a polygon, there is a high chance it was an overhang. Overhangs that are <60� tend to be very thin areas, so lets get the beginning and end of them and ensure that they are supported. - // The first point of the line will always be supported, so rotate the order of points in this polyline that one of the two corresponding points that are furthest from each other is in the beginning. - // The other will be manually added (optimal_end_index) - coord_t max_dist2_between_vertecies = 0; - for (size_t idx = 0; idx < part.size() - 1; ++ idx) { - for (size_t inner_idx = 0; inner_idx < part.size() - 1; inner_idx++) { - if ((part[idx] - part[inner_idx]).cast().squaredNorm() > max_dist2_between_vertecies) { - optimal_start_index = idx; - optimal_end_index = inner_idx; - max_dist2_between_vertecies = (part[idx] - part[inner_idx]).cast().squaredNorm(); - } - } - } - std::rotate(part.begin(), part.begin() + optimal_start_index, part.end() - 1); - part[part.size() - 1] = part[0]; // restore that property that this polyline ends where it started. - optimal_end_index = (part.size() + optimal_end_index - optimal_start_index - 1) % (part.size() - 1); - } + // draw overhang areas + svg.draw_outline(union_ex(overhangs), colors[0]); + svg.draw_outline(union_ex(overhangs_after_offset), colors[1]); + svg.draw_outline(outlines_below, colors[2]); - while (line.size() < min_points && current_distance >= scaled(0.1)) - { - line.clear(); - Point current_point = part[0]; - line.points.emplace_back(part[0]); - if (min_points > 1 || (part[0] - part[optimal_end_index]).cast().norm() > current_distance) - line.points.emplace_back(part[optimal_end_index]); - size_t current_index = 0; - std::optional> next_point; - double next_distance = current_distance; - // Get points so that at least min_points are added and they each are current_distance away from each other. If that is impossible, decrease current_distance a bit. - // The input are lines, that means that the line from the last to the first vertex does not have to exist, so exclude all points that are on this line! - while ((next_point = polyline_sample_next_point_at_distance(part.points, current_point, current_index, next_distance))) { - // Not every point that is distance away, is valid, as it may be much closer to another point. This is especially the case when the overhang is very thin. - // So this ensures that the points are actually a certain distance from each other. - // This assurance is only made on a per polygon basis, as different but close polygon may not be able to use support below the other polygon. - double min_distance_to_existing_point = std::numeric_limits::max(); - for (Point p : line) - min_distance_to_existing_point = std::min(min_distance_to_existing_point, (p - next_point->first).cast().norm()); - if (min_distance_to_existing_point >= current_distance) { - // viable point was found. Add to possible result. - line.points.emplace_back(next_point->first); - current_point = next_point->first; - current_index = next_point->second; - next_distance = current_distance; - } else { - if (current_point == next_point->first) { - // In case a fixpoint is encountered, better aggressively overcompensate so the code does not become stuck here... - BOOST_LOG_TRIVIAL(warning) << "Tree Support: Encountered a fixpoint in polyline_sample_next_point_at_distance. This is expected to happen if the distance (currently " << next_distance << - ") is smaller than 100"; - tree_supports_show_error("Encountered issue while placing tips. Some tips may be missing."sv, true); - if (next_distance > 2 * current_distance) - // This case should never happen, but better safe than sorry. - break; - next_distance += current_distance; - continue; - } - // if the point was too close, the next possible viable point is at least distance-min_distance_to_existing_point away from the one that was just checked. - next_distance = std::max(current_distance - min_distance_to_existing_point, scaled(0.1)); - current_point = next_point->first; - current_index = next_point->second; - } - } - current_distance *= 0.9; - } - } - result.emplace_back(std::move(line)); + // draw legend + if (!lower_layer_nodes.empty()) { + svg.draw_text(bbox.min + Point(scale_(0), scale_(0)), format("nPoints: %1%->%2%",layer_nodes.size(), lower_layer_nodes.size()).c_str(), "green", 2); } - validate_range(result); - return result; -} + else { + svg.draw_text(bbox.min + Point(scale_(0), scale_(0)), ("nPoints: " + std::to_string(layer_nodes.size())).c_str(), "green", 2); + } + svg.draw_text(bbox.min + Point(scale_(0), scale_(2)), legends[0].c_str(), colors[0].c_str(), 2); + svg.draw_text(bbox.min + Point(scale_(0), scale_(4)), legends[1].c_str(), colors[1].c_str(), 2); + svg.draw_text(bbox.min + Point(scale_(0), scale_(6)), legends[2].c_str(), colors[2].c_str(), 2); -/*! - * \brief Returns Polylines representing the (infill) lines that will result in slicing the given area - * - * \param area[in] The area that has to be filled with infill. - * \param roof[in] Whether the roofing or regular support settings should be used. - * \param layer_idx[in] The current layer index. - * \param support_infill_distance[in] The distance that should be between the infill lines. - * - * \return A Polygons object that represents the resulting infill lines. - */ -[[nodiscard]] static Polylines generate_support_infill_lines( - // Polygon to fill in with a zig-zag pattern supporting an overhang. - const Polygons &polygon, - const SupportParameters &support_params, - bool roof, LayerIndex layer_idx, coord_t support_infill_distance) -{ + // draw layer nodes + svg.draw(layer_pts, "green", coord_t(scale_(0.1))); #if 0 - Polygons gaps; - // as we effectivly use lines to place our supportPoints we may use the Infill class for it, while not made for it it works perfect - - const EFillMethod pattern = roof ? config.roof_pattern : config.support_pattern; - -// const bool zig_zaggify_infill = roof ? pattern == EFillMethod::ZIG_ZAG : config.zig_zaggify_support; - const bool connect_polygons = false; - constexpr coord_t support_roof_overlap = 0; - constexpr size_t infill_multiplier = 1; - constexpr coord_t outline_offset = 0; - const int support_shift = roof ? 0 : support_infill_distance / 2; - const size_t wall_line_count = include_walls && !roof ? config.support_wall_count : 0; - const Point infill_origin; - constexpr Polygons* perimeter_gaps = nullptr; - constexpr bool use_endpieces = true; - const bool connected_zigzags = roof ? false : config.connect_zigzags; - const size_t zag_skip_count = roof ? 0 : config.zag_skip_count; - constexpr coord_t pocket_size = 0; - std::vector angles = roof ? config.support_roof_angles : config.support_infill_angles; - std::vector toolpaths; - - const coord_t z = config.getActualZ(layer_idx); - int divisor = static_cast(angles.size()); - int index = ((layer_idx % divisor) + divisor) % divisor; - const AngleRadians fill_angle = angles[index]; - Infill roof_computation(pattern, true /* zig_zaggify_infill */, connect_polygons, polygon, - roof ? config.support_roof_line_width : config.support_line_width, support_infill_distance, support_roof_overlap, infill_multiplier, - fill_angle, z, support_shift, config.resolution, wall_line_count, infill_origin, - perimeter_gaps, connected_zigzags, use_endpieces, false /* skip_some_zags */, zag_skip_count, pocket_size); - Polygons polygons; - Polygons lines; - roof_computation.generate(toolpaths, polygons, lines, config.settings); - append(lines, to_polylines(polygons)); - return lines; -#else -// const bool connected_zigzags = roof ? false : config.connect_zigzags; -// const int support_shift = roof ? 0 : support_infill_distance / 2; - - const Flow &flow = roof ? support_params.support_material_interface_flow : support_params.support_material_flow; - std::unique_ptr filler = std::unique_ptr(Fill::new_from_type(roof ? support_params.interface_fill_pattern : support_params.base_fill_pattern)); - FillParams fill_params; - - filler->layer_id = layer_idx; - filler->spacing = flow.spacing(); - filler->angle = roof ? - //fixme support_layer.interface_id() instead of layer_idx - (support_params.interface_angle + (layer_idx & 1) ? float(- M_PI / 4.) : float(+ M_PI / 4.)) : - support_params.base_angle; - - fill_params.density = float(roof ? support_params.interface_density : scaled(filler->spacing) / (scaled(filler->spacing) + float(support_infill_distance))); - fill_params.dont_adjust = true; + // lower layer points + layer_pts.clear(); + for (TreeSupport::Node *node : lower_layer_nodes) { + layer_pts.push_back(node->position); + } + svg.draw(layer_pts, "black", coord_t(scale_(0.1))); - Polylines out; - for (ExPolygon &expoly : union_ex(polygon)) { - // The surface type does not matter. - assert(area(expoly) > 0.); -#ifdef TREE_SUPPORT_SHOW_ERRORS_WIN32 - if (area(expoly) <= 0.) - ::MessageBoxA(nullptr, "TreeSupport infill negative area", "Bug detected!", MB_OK | MB_SYSTEMMODAL | MB_SETFOREGROUND | MB_ICONWARNING); -#endif // TREE_SUPPORT_SHOW_ERRORS_WIN32 - assert(intersecting_edges(to_polygons(expoly)).empty()); - check_self_intersections(expoly, "generate_support_infill_lines"); - Surface surface(stInternal, std::move(expoly)); - try { - Polylines pl = filler->fill_surface(&surface, fill_params); - assert(pl.empty() || get_extents(surface.expolygon).inflated(SCALED_EPSILON).contains(get_extents(pl))); -#ifdef TREE_SUPPORT_SHOW_ERRORS_WIN32 - if (! pl.empty() && ! get_extents(surface.expolygon).inflated(SCALED_EPSILON).contains(get_extents(pl))) - ::MessageBoxA(nullptr, "TreeSupport infill failure", "Bug detected!", MB_OK | MB_SYSTEMMODAL | MB_SETFOREGROUND | MB_ICONWARNING); -#endif // TREE_SUPPORT_SHOW_ERRORS_WIN32 - append(out, std::move(pl)); - } catch (InfillFailedException &) { - } - } - validate_range(out); - return out; + // higher layer points + layer_pts.clear(); + for (TreeSupport::Node* node : layer_nodes) { + if(node->parent) + layer_pts.push_back(node->parent->position); + } + svg.draw(layer_pts, "blue", coord_t(scale_(0.1))); #endif } -/*! - * \brief Unions two Polygons. Ensures that if the input is non empty that the output also will be non empty. - * \param first[in] The first Polygon. - * \param second[in] The second Polygon. - * \return The union of both Polygons - */ -[[nodiscard]] static Polygons safe_union(const Polygons first, const Polygons second = {}) +static void draw_layer_mst +(const std::string &layer_nr_or_z, + const std::vector &spanning_trees, + const ExPolygons& outline +) { - // unionPolygons can slowly remove Polygons under certain circumstances, because of rounding issues (Polygons that have a thin area). - // This does not cause a problem when actually using it on large areas, but as influence areas (representing centerpoints) can be very thin, this does occur so this ugly workaround is needed - // Here is an example of a Polygons object that will loose vertices when unioning, and will be gone after a few times unionPolygons was called: - /* - Polygons example; - Polygon exampleInner; - exampleInner.add(Point(120410,83599));//A - exampleInner.add(Point(120384,83643));//B - exampleInner.add(Point(120399,83618));//C - exampleInner.add(Point(120414,83591));//D - exampleInner.add(Point(120423,83570));//E - exampleInner.add(Point(120419,83580));//F - example.add(exampleInner); - for(int i=0;i<10;i++){ - log("Iteration %d Example area: %f\n",i,area(example)); - example=example.unionPolygons(); - } -*/ - - Polygons result; - if (! first.empty() || ! second.empty()) { - result = union_(first, second); - if (result.empty()) { - BOOST_LOG_TRIVIAL(debug) << "Caught an area destroying union, enlarging areas a bit."; - // just take the few lines we have, and offset them a tiny bit. Needs to be offsetPolylines, as offset may aleady have problems with the area. - result = union_(offset(to_polylines(first), scaled(0.002), jtMiter, 1.2), offset(to_polylines(second), scaled(0.002), jtMiter, 1.2)); - } + auto lines = spanning_tree_to_lines(spanning_trees); + BoundingBox bbox = get_extents(lines); + for (auto& poly : outline) + { + BoundingBox bb = poly.contour.bounding_box(); + bbox.merge(bb); } - - return result; -} - -/*! - * \brief Offsets (increases the area of) a polygons object in multiple steps to ensure that it does not lag through over a given obstacle. - * \param me[in] Polygons object that has to be offset. - * \param distance[in] The distance by which me should be offset. Expects values >=0. - * \param collision[in] The area representing obstacles. - * \param last_step_offset_without_check[in] The most it is allowed to offset in one step. - * \param min_amount_offset[in] How many steps have to be done at least. As this uses round offset this increases the amount of vertices, which may be required if Polygons get very small. Required as arcTolerance is not exposed in offset, which should result with a similar result. - * \return The resulting Polygons object. - */ -[[nodiscard]] static Polygons safe_offset_inc(const Polygons& me, coord_t distance, const Polygons& collision, coord_t safe_step_size, coord_t last_step_offset_without_check, size_t min_amount_offset) -{ - bool do_final_difference = last_step_offset_without_check == 0; - Polygons ret = safe_union(me); // ensure sane input - - // Trim the collision polygons with the region of interest for diff() efficiency. - Polygons collision_trimmed_buffer; - auto collision_trimmed = [&collision_trimmed_buffer, &collision, &ret, distance]() -> const Polygons& { - if (collision_trimmed_buffer.empty() && ! collision.empty()) - collision_trimmed_buffer = ClipperUtils::clip_clipper_polygons_with_subject_bbox(collision, get_extents(ret).inflated(std::max((coord_t)0, distance) + SCALED_EPSILON)); - return collision_trimmed_buffer; - }; - if (distance == 0) - return do_final_difference ? diff(ret, collision_trimmed()) : union_(ret); - if (safe_step_size < 0 || last_step_offset_without_check < 0) { - BOOST_LOG_TRIVIAL(error) << "Offset increase got invalid parameter!"; - tree_supports_show_error("Negative offset distance... How did you manage this ?"sv, true); - return do_final_difference ? diff(ret, collision_trimmed()) : union_(ret); - } + SVG svg(get_svg_filename(layer_nr_or_z, "mstree").c_str(), bbox); + if (!svg.is_opened()) return; - coord_t step_size = safe_step_size; - int steps = distance > last_step_offset_without_check ? (distance - last_step_offset_without_check) / step_size : 0; - if (distance - steps * step_size > last_step_offset_without_check) { - if ((steps + 1) * step_size <= distance) - // This will be the case when last_step_offset_without_check >= safe_step_size - ++ steps; - else - do_final_difference = true; - } - if (steps + (distance < last_step_offset_without_check || (distance % step_size) != 0) < int(min_amount_offset) && min_amount_offset > 1) { - // yes one can add a bool as the standard specifies that a result from compare operators has to be 0 or 1 - // reduce the stepsize to ensure it is offset the required amount of times - step_size = distance / min_amount_offset; - if (step_size >= safe_step_size) { - // effectivly reduce last_step_offset_without_check - step_size = safe_step_size; - steps = min_amount_offset; - } else - steps = distance / step_size; - } - // offset in steps - for (int i = 0; i < steps; ++ i) { - ret = diff(offset(ret, step_size, ClipperLib::jtRound, scaled(0.01)), collision_trimmed()); - // ensure that if many offsets are done the performance does not suffer extremely by the new vertices of jtRound. - if (i % 10 == 7) - ret = polygons_simplify(ret, scaled(0.015), polygons_strictly_simple); - } - // offset the remainder - float last_offset = distance - steps * step_size; - if (last_offset > SCALED_EPSILON) - ret = offset(ret, distance - steps * step_size, ClipperLib::jtRound, scaled(0.01)); - ret = polygons_simplify(ret, scaled(0.015), polygons_strictly_simple); - - if (do_final_difference) - ret = diff(ret, collision_trimmed()); - return union_(ret); + svg.draw(lines, "blue", coord_t(scale_(0.05))); + svg.draw_outline(outline, "yellow"); + for (auto &spanning_tree : spanning_trees) + svg.draw(spanning_tree.vertices(), "black", coord_t(scale_(0.1))); } -class RichInterfacePlacer : public InterfacePlacer { -public: - RichInterfacePlacer( - const InterfacePlacer &interface_placer, - const TreeModelVolumes &volumes, - bool force_tip_to_roof, - size_t num_support_layers, - std::vector &move_bounds) - : - InterfacePlacer(interface_placer), - volumes(volumes), force_tip_to_roof(force_tip_to_roof), move_bounds(move_bounds) - { - m_already_inserted.assign(num_support_layers, {}); - this->min_xy_dist = this->config.xy_distance > this->config.xy_min_distance; - m_base_radius = scaled(0.01); - m_base_circle = Polygon{ make_circle(m_base_radius, SUPPORT_TREE_CIRCLE_RESOLUTION) }; - - } - const TreeModelVolumes &volumes; - // Radius of the tree tip is large enough to be covered by an interface. - const bool force_tip_to_roof; - bool min_xy_dist; +static void draw_two_overhangs_to_svg(SupportLayer* ts_layer, const ExPolygons& overhangs1, const ExPolygons& overhangs2) +{ + if (overhangs1.empty() && overhangs2.empty()) + return; + BoundingBox bbox1 = get_extents(overhangs1); + BoundingBox bbox2 = get_extents(overhangs2); + bbox1.merge(bbox2); -public: - // called by sample_overhang_area() - void add_points_along_lines( - // Insert points (tree tips or top contact interfaces) along these lines. - LineInformations lines, - // Start at this layer. - LayerIndex insert_layer_idx, - // Insert this number of interface layers. - size_t roof_tip_layers, - // True if an interface is already generated above these lines. - size_t supports_roof_layers, - // The element tries to not move until this dtt is reached. - size_t dont_move_until) - { - validate_range(lines); - // Add tip area as roof (happens when minimum roof area > minimum tip area) if possible - size_t dtt_roof_tip; - for (dtt_roof_tip = 0; dtt_roof_tip < roof_tip_layers && insert_layer_idx - dtt_roof_tip >= 1; ++ dtt_roof_tip) { - size_t this_layer_idx = insert_layer_idx - dtt_roof_tip; - auto evaluateRoofWillGenerate = [&](const std::pair &p) { - //FIXME Vojtech: The circle is just shifted, it has a known size, the infill should fit all the time! - #if 0 - Polygon roof_circle; - for (Point corner : base_circle) - roof_circle.points.emplace_back(p.first + corner * config.min_radius); - return !generate_support_infill_lines({ roof_circle }, config, true, insert_layer_idx - dtt_roof_tip, config.support_roof_line_distance).empty(); - #else - return true; - #endif - }; + SVG svg(get_svg_filename(std::to_string(ts_layer->print_z), "two_overhangs"), bbox1); + if (!svg.is_opened()) return; - { - std::pair split = - // keep all lines that are still valid on the next layer - split_lines(lines, [this, this_layer_idx](const std::pair &p) - { return evaluate_point_for_next_layer_function(volumes, config, this_layer_idx, p); }); - LineInformations points = std::move(split.second); - // Not all roofs are guaranteed to actually generate lines, so filter these out and add them as points. - split = split_lines(split.first, evaluateRoofWillGenerate); - lines = std::move(split.first); - append(points, split.second); - // add all points that would not be valid - for (const LineInformation &line : points) - for (const std::pair &point_data : line) - add_point_as_influence_area(point_data, this_layer_idx, - // don't move until - roof_tip_layers - dtt_roof_tip, - // supports roof - dtt_roof_tip + supports_roof_layers > 0, - // disable ovalization - false); - } + svg.draw(union_ex(overhangs1), "blue"); + svg.draw(union_ex(overhangs2), "red"); +} - // add all tips as roof to the roof storage - Polygons new_roofs; - for (const LineInformation &line : lines) - //FIXME sweep the tip radius along the line? - for (const std::pair &p : line) { - Polygon roof_circle{ m_base_circle }; - roof_circle.scale(config.min_radius / m_base_radius); - roof_circle.translate(p.first); - new_roofs.emplace_back(std::move(roof_circle)); - } - this->add_roof(std::move(new_roofs), this_layer_idx, dtt_roof_tip + supports_roof_layers); - } +static void draw_polylines(SupportLayer* ts_layer, Polylines& polylines) +{ + if (polylines.empty()) + return; + BoundingBox bbox = get_extents(polylines); - for (const LineInformation &line : lines) { - // If a line consists of enough tips, the assumption is that it is not a single tip, but part of a simulated support pattern. - // Ovalisation should be disabled for these to improve the quality of the lines when tip_diameter=line_width - bool disable_ovalistation = config.min_radius < 3 * config.support_line_width && roof_tip_layers == 0 && dtt_roof_tip == 0 && line.size() > 5; - for (const std::pair &point_data : line) - add_point_as_influence_area(point_data, insert_layer_idx - dtt_roof_tip, - // don't move until - dont_move_until > dtt_roof_tip ? dont_move_until - dtt_roof_tip : 0, - // supports roof - dtt_roof_tip + supports_roof_layers > 0, - disable_ovalistation); - } - } + SVG svg(get_svg_filename(std::to_string(ts_layer->print_z), "lightnings"), bbox); + if (!svg.is_opened()) return; -private: - // called by this->add_points_along_lines() - void add_point_as_influence_area(std::pair p, LayerIndex insert_layer, size_t dont_move_until, bool roof, bool skip_ovalisation) + int id = 0; + for (Polyline& pline : polylines) { - bool to_bp = p.second == LineStatus::TO_BP || p.second == LineStatus::TO_BP_SAFE; - bool gracious = to_bp || p.second == LineStatus::TO_MODEL_GRACIOUS || p.second == LineStatus::TO_MODEL_GRACIOUS_SAFE; - bool safe_radius = p.second == LineStatus::TO_BP_SAFE || p.second == LineStatus::TO_MODEL_GRACIOUS_SAFE; - if (! config.support_rests_on_model && ! to_bp) { - BOOST_LOG_TRIVIAL(warning) << "Tried to add an invalid support point"; - tree_supports_show_error("Unable to add tip. Some overhang may not be supported correctly."sv, true); - return; - } - Polygons circle{ m_base_circle }; - circle.front().translate(p.first); + int i1, i2; + for (size_t i = 0; i < pline.size() - 1; i++) { - Point hash_pos = p.first / ((config.min_radius + 1) / 10); - std::lock_guard critical_section_movebounds(m_mutex_movebounds); - if (!m_already_inserted[insert_layer].count(hash_pos)) { - // normalize the point a bit to also catch points which are so close that inserting it would achieve nothing - m_already_inserted[insert_layer].emplace(hash_pos); - static constexpr const size_t dtt = 0; - SupportElementState state; - state.target_height = insert_layer; - state.target_position = p.first; - state.next_position = p.first; - state.layer_idx = insert_layer; - state.effective_radius_height = dtt; - state.to_buildplate = to_bp; - state.distance_to_top = dtt; - state.result_on_layer = p.first; - assert(state.result_on_layer_is_set()); - state.increased_to_model_radius = 0; - state.to_model_gracious = gracious; - state.elephant_foot_increases = 0; - state.use_min_xy_dist = min_xy_dist; - state.supports_roof = roof; - state.dont_move_until = dont_move_until; - state.can_use_safe_radius = safe_radius; - state.missing_roof_layers = force_tip_to_roof ? dont_move_until : 0; - state.skip_ovalisation = skip_ovalisation; - move_bounds[insert_layer].emplace_back(state, std::move(circle)); - } - } - } - - // Outputs - std::vector &move_bounds; - - // Temps - coord_t m_base_radius; - Polygon m_base_circle; - - // Mutexes, guards - std::mutex m_mutex_movebounds; - std::vector> m_already_inserted; -}; - -int generate_raft_contact( - const PrintObject &print_object, - const TreeSupportSettings &config, - InterfacePlacer &interface_placer) -{ - int raft_contact_layer_idx = -1; - if (print_object.has_raft() && print_object.layer_count() > 0) { - // Produce raft contact layer outside of the tree support loop, so that no trees will be generated for the raft contact layer. - // Raft layers supporting raft contact interface will be produced by the classic raft generator. - // Find the raft contact layer. - raft_contact_layer_idx = int(config.raft_layers.size()) - 1; - while (raft_contact_layer_idx > 0 && config.raft_layers[raft_contact_layer_idx] > print_object.slicing_parameters().raft_contact_top_z + EPSILON) - -- raft_contact_layer_idx; - // Create the raft contact layer. - const ExPolygons &lslices = print_object.get_layer(0)->lslices; - double expansion = print_object.config().raft_expansion.value; - interface_placer.add_roof_unguarded(expansion > 0 ? expand(lslices, scaled(expansion)) : to_polygons(lslices), raft_contact_layer_idx, 0); - } - return raft_contact_layer_idx; -} - -void finalize_raft_contact( - const PrintObject &print_object, - const int raft_contact_layer_idx, - SupportGeneratorLayersPtr &top_contacts, - std::vector &move_bounds) -{ - if (raft_contact_layer_idx >= 0) { - const size_t first_tree_layer = print_object.slicing_parameters().raft_layers() - 1; - // Remove tree tips that start below the raft contact, - // remove interface layers below the raft contact. - for (size_t i = 0; i < first_tree_layer; ++i) { - top_contacts[i] = nullptr; - move_bounds[i].clear(); - } - if (raft_contact_layer_idx >= 0 && print_object.config().raft_expansion.value > 0) { - // If any tips at first_tree_layer now are completely inside the expanded raft layer, remove them as well before they are propagated to the ground. - Polygons &raft_polygons = top_contacts[raft_contact_layer_idx]->polygons; - EdgeGrid::Grid grid(get_extents(raft_polygons).inflated(SCALED_EPSILON)); - grid.create(raft_polygons, Polylines{}, coord_t(scale_(10.))); - SupportElements &first_layer_move_bounds = move_bounds[first_tree_layer]; - double threshold = scaled(print_object.config().raft_expansion.value) * 2.; - first_layer_move_bounds.erase(std::remove_if(first_layer_move_bounds.begin(), first_layer_move_bounds.end(), - [&grid, threshold](const SupportElement &el) { - coordf_t dist; - if (grid.signed_distance_edges(el.state.result_on_layer, threshold, dist)) { - assert(std::abs(dist) < threshold + SCALED_EPSILON); - // Support point is inside the expanded raft, remove it. - return dist < - 0.; - } - return false; - }), first_layer_move_bounds.end()); - #if 0 - // Remove the remaining tips from the raft: Closing operation on tip circles. - if (! first_layer_move_bounds.empty()) { - const double eps = 0.1; - // All tips supporting this layer are expected to have the same radius. - double radius = support_element_radius(config, first_layer_move_bounds.front()); - // Connect the tips with the following closing radius. - double closing_distance = radius; - Polygon circle = make_circle(radius + closing_distance, eps); - Polygons circles; - circles.reserve(first_layer_move_bounds.size()); - for (const SupportElement &el : first_layer_move_bounds) { - circles.emplace_back(circle); - circles.back().translate(el.state.result_on_layer); - } - raft_polygons = diff(raft_polygons, offset(union_(circles), - closing_distance)); - } - #endif + i1 = i; + i2 = i + 1; + svg.draw(Line(pline.points[i1], pline.points[i2]), "blue"); + svg.draw(pline.points[i1], "red"); + id++; + svg.draw_text(pline.points[i1], std::to_string(id).c_str(), "black", 1); } + svg.draw(pline.points[i2], "red"); + id++; + svg.draw_text(pline.points[i2], std::to_string(id).c_str(), "black", 1); } } +#endif -// Called by generate_initial_areas(), used in parallel by multiple layers. -// Produce -// 1) Maximum num_support_roof_layers roof (top interface & contact) layers. -// 2) Tree tips supporting either the roof layers or the object itself. -// num_support_roof_layers should always be respected: -// If num_support_roof_layers contact layers could not be produced, then the tree tip -// is augmented with SupportElementState::missing_roof_layers -// and the top "missing_roof_layers" of such particular tree tips are supposed to be coverted to -// roofs aka interface layers by the tool path generator. -void sample_overhang_area( - // Area to support - Polygons &&overhang_area, - // If true, then the overhang_area is likely large and wide, thus it is worth to try - // to cover it with continuous interfaces supported by zig-zag patterned tree tips. - const bool large_horizontal_roof, - // Index of the top suport layer generated by this function. - const size_t layer_idx, - // Maximum number of roof (contact, interface) layers between the overhang and tree tips to be generated. - const size_t num_support_roof_layers, - // - const coord_t connect_length, - // Configuration classes - const TreeSupportMeshGroupSettings &mesh_group_settings, - // Configuration & Output - RichInterfacePlacer &interface_placer) +// Move point from inside polygon if distance>0, outside if distance<0. +// Special case: distance=0 means find the nearest point of from on the polygon contour. +// The max move distance should not excceed max_move_distance. +static unsigned int move_inside_expoly(const ExPolygon &polygon, Point& from, double distance = 0, double max_move_distance = std::numeric_limits::max()) { - // Assumption is that roof will support roof further up to avoid a lot of unnecessary branches. Each layer down it is checked whether the roof area - // is still large enough to be a roof and aborted as soon as it is not. This part was already reworked a few times, and there could be an argument - // made to change it again if there are actual issues encountered regarding supporting roofs. - // Main problem is that some patterns change each layer, so just calculating points and checking if they are still valid an layer below is not useful, - // as the pattern may be different one layer below. Same with calculating which points are now no longer being generated as result from - // a decreasing roof, as there is no guarantee that a line will be above these points. Implementing a separate roof support behavior - // for each pattern harms maintainability as it very well could be >100 LOC - auto generate_roof_lines = [&interface_placer, &mesh_group_settings](const Polygons &area, LayerIndex layer_idx) -> Polylines { - return generate_support_infill_lines(area, interface_placer.support_parameters, true, layer_idx, mesh_group_settings.support_roof_line_distance); - }; + //TODO: This is copied from the moveInside of Polygons. + /* + We'd like to use this function as subroutine in moveInside(Polygons...), but + then we'd need to recompute the distance of the point to the polygon, which + is expensive. Or we need to return the distance. We need the distance there + to compare with the distance to other polygons. + */ + Point ret = from; + double bestDist2 = std::numeric_limits::max(); + bool is_already_on_correct_side_of_boundary = false; // whether [from] is already on the right side of the boundary + const Polygon &contour = polygon.contour; - LineInformations overhang_lines; - // Track how many top contact / interface layers were already generated. - size_t dtt_roof = 0; - size_t layer_generation_dtt = 0; - - if (large_horizontal_roof) { - assert(num_support_roof_layers > 0); - // Sometimes roofs could be empty as the pattern does not generate lines if the area is narrow enough (i am looking at you, concentric infill). - // To catch these cases the added roofs are saved to be evaluated later. - std::vector added_roofs(num_support_roof_layers); - Polygons last_overhang = overhang_area; - for (dtt_roof = 0; dtt_roof < num_support_roof_layers && layer_idx - dtt_roof >= 1; ++ dtt_roof) { - // here the roof is handled. If roof can not be added the branches will try to not move instead - Polygons forbidden_next; - { - const bool min_xy_dist = interface_placer.config.xy_distance > interface_placer.config.xy_min_distance; - const Polygons &forbidden_next_raw = interface_placer.config.support_rests_on_model ? - interface_placer.volumes.getCollision(interface_placer.config.getRadius(0), layer_idx - (dtt_roof + 1), min_xy_dist) : - interface_placer.volumes.getAvoidance(interface_placer.config.getRadius(0), layer_idx - (dtt_roof + 1), TreeModelVolumes::AvoidanceType::Fast, false, min_xy_dist); - // prevent rounding errors down the line - //FIXME maybe use SafetyOffset::Yes at the following diff() instead? - forbidden_next = offset(union_ex(forbidden_next_raw), scaled(0.005), jtMiter, 1.2); - } - Polygons overhang_area_next = diff(overhang_area, forbidden_next); - if (area(overhang_area_next) < mesh_group_settings.minimum_roof_area) { - // Next layer down the roof area would be to small so we have to insert our roof support here. - if (dtt_roof > 0) { - size_t dtt_before = dtt_roof - 1; - // Produce support head points supporting an interface layer: First produce the interface lines, then sample them. - overhang_lines = split_lines( - convert_lines_to_internal(interface_placer.volumes, interface_placer.config, - ensure_maximum_distance_polyline(generate_roof_lines(last_overhang, layer_idx - dtt_before), connect_length, 1), layer_idx - dtt_before), - [&interface_placer, layer_idx, dtt_before](const std::pair &p) - { return evaluate_point_for_next_layer_function(interface_placer.volumes, interface_placer.config, layer_idx - dtt_before, p); }) - .first; - } - break; - } - added_roofs[dtt_roof] = overhang_area; - last_overhang = std::move(overhang_area); - overhang_area = std::move(overhang_area_next); - } - - layer_generation_dtt = std::max(dtt_roof, size_t(1)) - 1; // 1 inside max and -1 outside to avoid underflow. layer_generation_dtt=dtt_roof-1 if dtt_roof!=0; - // if the roof should be valid, check that the area does generate lines. This is NOT guaranteed. - if (overhang_lines.empty() && dtt_roof != 0 && generate_roof_lines(overhang_area, layer_idx - layer_generation_dtt).empty()) - for (size_t idx = 0; idx < dtt_roof; idx++) { - // check for every roof area that it has resulting lines. Remember idx 1 means the 2. layer of roof => higher idx == lower layer - if (generate_roof_lines(added_roofs[idx], layer_idx - idx).empty()) { - dtt_roof = idx; - layer_generation_dtt = std::max(dtt_roof, size_t(1)) - 1; - break; - } - } - added_roofs.erase(added_roofs.begin() + dtt_roof, added_roofs.end()); - interface_placer.add_roofs(std::move(added_roofs), layer_idx); - } - - if (overhang_lines.empty()) { - // support_line_width to form a line here as otherwise most will be unsupported. Technically this violates branch distance, but not only is this the only reasonable choice, - // but it ensures consistant behaviour as some infill patterns generate each line segment as its own polyline part causing a similar line forming behaviour. - // This is not doen when a roof is above as the roof will support the model and the trees only need to support the roof - bool supports_roof = dtt_roof > 0; - bool continuous_tips = ! supports_roof && large_horizontal_roof; - Polylines polylines = ensure_maximum_distance_polyline( - generate_support_infill_lines(overhang_area, interface_placer.support_parameters, supports_roof, layer_idx - layer_generation_dtt, - supports_roof ? mesh_group_settings.support_roof_line_distance : mesh_group_settings.support_tree_branch_distance), - continuous_tips ? interface_placer.config.min_radius / 2 : connect_length, 1); - size_t point_count = 0; - for (const Polyline &poly : polylines) - point_count += poly.size(); - const size_t min_support_points = std::max(coord_t(1), std::min(coord_t(3), coord_t(total_length(overhang_area) / connect_length))); - if (point_count <= min_support_points) { - // add the outer wall (of the overhang) to ensure it is correct supported instead. Try placing the support points in a way that they fully support the outer wall, instead of just the with half of the the support line width. - // I assume that even small overhangs are over one line width wide, so lets try to place the support points in a way that the full support area generated from them - // will support the overhang (if this is not done it may only be half). This WILL NOT be the case when supporting an angle of about < 60� so there is a fallback, - // as some support is better than none. - Polygons reduced_overhang_area = offset(union_ex(overhang_area), - interface_placer.config.support_line_width / 2.2, jtMiter, 1.2); - polylines = ensure_maximum_distance_polyline( - to_polylines( - ! reduced_overhang_area.empty() && - area(offset(diff_ex(overhang_area, reduced_overhang_area), std::max(interface_placer.config.support_line_width, connect_length), jtMiter, 1.2)) < sqr(scaled(0.001)) ? - reduced_overhang_area : - overhang_area), - connect_length, min_support_points); - } - overhang_lines = convert_lines_to_internal(interface_placer.volumes, interface_placer.config, polylines, layer_idx - dtt_roof); - } - - assert(dtt_roof <= layer_idx); - if (dtt_roof >= layer_idx && large_horizontal_roof) - // Reached buildplate when generating contact, interface and base interface layers. - interface_placer.add_roof_build_plate(std::move(overhang_area), dtt_roof); - else { - // normal trees have to be generated - const bool roof_enabled = num_support_roof_layers > 0; - interface_placer.add_points_along_lines( - // Sample along these lines - overhang_lines, - // First layer index to insert the tree tips or interfaces. - layer_idx - dtt_roof, - // Remaining roof tip layers. - interface_placer.force_tip_to_roof ? num_support_roof_layers - dtt_roof : 0, - // Supports roof already? How many roof layers were already produced above these tips? - dtt_roof, - // Don't move until the following distance to top is reached. - roof_enabled ? num_support_roof_layers - dtt_roof : 0); + if (contour.points.size() < 2) + { + return 0; } -} - -/*! - * \brief Creates the initial influence areas (that can later be propagated down) by placing them below the overhang. - * - * Generates Points where the Model should be supported and creates the areas where these points have to be placed. - * - * \param mesh[in] The mesh that is currently processed. - * \param move_bounds[out] Storage for the influence areas. - * \param storage[in] Background storage, required for adding roofs. - */ -static void generate_initial_areas( - const PrintObject &print_object, - const TreeModelVolumes &volumes, - const TreeSupportSettings &config, - const std::vector &overhangs, - std::vector &move_bounds, - InterfacePlacer &interface_placer, - std::function throw_on_cancel) -{ - using AvoidanceType = TreeModelVolumes::AvoidanceType; - TreeSupportMeshGroupSettings mesh_group_settings(print_object); - - // To ensure z_distance_top_layers are left empty between the overhang (zeroth empty layer), the support has to be added z_distance_top_layers+1 layers below - const size_t z_distance_delta = config.z_distance_top_layers + 1; - - const bool min_xy_dist = config.xy_distance > config.xy_min_distance; - -#if 0 - if (mesh.overhang_areas.size() <= z_distance_delta) - return; -#endif - - const coord_t connect_length = (config.support_line_width * 100. / mesh_group_settings.support_tree_top_rate) + std::max(2. * config.min_radius - 1.0 * config.support_line_width, 0.0); - // As r*r=x*x+y*y (circle equation): If a circle with center at (0,0) the top most point is at (0,r) as in y=r. - // This calculates how far one has to move on the x-axis so that y=r-support_line_width/2. - // In other words how far does one need to move on the x-axis to be support_line_width/2 away from the circle line. - // As a circle is round this length is identical for every axis as long as the 90 degrees angle between both remains. - const coord_t circle_length_to_half_linewidth_change = config.min_radius < config.support_line_width ? - config.min_radius / 2 : - scale_(sqrt(sqr(unscale(config.min_radius)) - sqr(unscale(config.min_radius - config.support_line_width / 2)))); - // Extra support offset to compensate for larger tip radiis. Also outset a bit more when z overwrites xy, because supporting something with a part of a support line is better than not supporting it at all. - //FIXME Vojtech: This is not sufficient for support enforcers to work. - //FIXME There is no account for the support overhang angle. - //FIXME There is no account for the width of the collision regions. - const coord_t extra_outset = std::max(coord_t(0), config.min_radius - config.support_line_width / 2) + (min_xy_dist ? config.support_line_width / 2 : 0) - //FIXME this is a heuristic value for support enforcers to work. -// + 10 * config.support_line_width; - ; - const size_t num_support_roof_layers = mesh_group_settings.support_roof_layers; - const bool roof_enabled = num_support_roof_layers > 0; - const bool force_tip_to_roof = roof_enabled && (interface_placer.support_parameters.soluble_interface || sqr(config.min_radius) * M_PI > mesh_group_settings.minimum_roof_area); - // cap for how much layer below the overhang a new support point may be added, as other than with regular support every new inserted point - // may cause extra material and time cost. Could also be an user setting or differently calculated. Idea is that if an overhang - // does not turn valid in double the amount of layers a slope of support angle would take to travel xy_distance, nothing reasonable will come from it. - // The 2*z_distance_delta is only a catch for when the support angle is very high. - // Used only if not min_xy_dist. - coord_t max_overhang_insert_lag = 0; - if (config.z_distance_top_layers > 0) { - max_overhang_insert_lag = 2 * config.z_distance_top_layers; - if (mesh_group_settings.support_angle > EPSILON && mesh_group_settings.support_angle < 0.5 * M_PI - EPSILON) { - //FIXME mesh_group_settings.support_angle does not apply to enforcers and also it does not apply to automatic support angle (by half the external perimeter width). - //used by max_overhang_insert_lag, only if not min_xy_dist. - const auto max_overhang_speed = coord_t(tan(mesh_group_settings.support_angle) * config.layer_height); - max_overhang_insert_lag = std::max(max_overhang_insert_lag, round_up_divide(config.xy_distance, max_overhang_speed / 2)); - } - } - - size_t num_support_layers; - int raft_contact_layer_idx; - // Layers with their overhang regions. - std::vector> raw_overhangs; - + Point p0 = contour.points[polygon.contour.size() - 2]; + Point p1 = contour.points.back(); + // because we compare with vsize2_with_unscale here (no division by zero), we also need to compare by vsize2_with_unscale inside the loop + // to avoid integer rounding edge cases + bool projected_p_beyond_prev_segment = dot_with_unscale(p1 - p0, from - p0) >= vsize2_with_unscale(p1 - p0); + for(const Point& p2 : polygon.contour.points) { - const size_t num_raft_layers = config.raft_layers.size(); - const size_t first_support_layer = std::max(int(num_raft_layers) - int(z_distance_delta), 1); - num_support_layers = size_t(std::max(0, int(print_object.layer_count()) + int(num_raft_layers) - int(z_distance_delta))); - raft_contact_layer_idx = generate_raft_contact(print_object, config, interface_placer); - // Enumerate layers for which the support tips may be generated from overhangs above. - raw_overhangs.reserve(num_support_layers - first_support_layer); - for (size_t layer_idx = first_support_layer; layer_idx < num_support_layers; ++ layer_idx) - if (const size_t overhang_idx = layer_idx + z_distance_delta; ! overhangs[overhang_idx].empty()) - raw_overhangs.push_back({ layer_idx, &overhangs[overhang_idx] }); - } - - RichInterfacePlacer rich_interface_placer{ interface_placer, volumes, force_tip_to_roof, num_support_layers, move_bounds }; - - tbb::parallel_for(tbb::blocked_range(0, raw_overhangs.size()), - [&volumes, &config, &raw_overhangs, &mesh_group_settings, - min_xy_dist, roof_enabled, num_support_roof_layers, extra_outset, circle_length_to_half_linewidth_change, connect_length, - &rich_interface_placer, &throw_on_cancel](const tbb::blocked_range &range) { - for (size_t raw_overhang_idx = range.begin(); raw_overhang_idx < range.end(); ++ raw_overhang_idx) { - size_t layer_idx = raw_overhangs[raw_overhang_idx].first; - const Polygons &overhang_raw = *raw_overhangs[raw_overhang_idx].second; - - // take the least restrictive avoidance possible - Polygons relevant_forbidden; - { - const Polygons &relevant_forbidden_raw = config.support_rests_on_model ? - volumes.getCollision(config.getRadius(0), layer_idx, min_xy_dist) : - volumes.getAvoidance(config.getRadius(0), layer_idx, AvoidanceType::Fast, false, min_xy_dist); - // prevent rounding errors down the line, points placed directly on the line of the forbidden area may not be added otherwise. - relevant_forbidden = offset(union_ex(relevant_forbidden_raw), scaled(0.005), jtMiter, 1.2); - } + // X = A + Normal(B-A) * (((B-A) dot_with_unscale (P-A)) / VSize(B-A)); + // = A + (B-A) * ((B-A) dot_with_unscale (P-A)) / VSize2(B-A); + // X = P projected on AB + const Point& a = p1; + const Point& b = p2; + const Point& p = from; + Point ab = b - a; + Point ap = p - a; + double ab_length2 = vsize2_with_unscale(ab); + if(ab_length2 <= 0) //A = B, i.e. the input polygon had two adjacent points on top of each other. + { + p1 = p2; //Skip only one of the points. + continue; + } + double dot_prod = dot_with_unscale(ab, ap); + if (dot_prod <= 0) // x is projected to before ab + { + if (projected_p_beyond_prev_segment) + { // case which looks like: > . + projected_p_beyond_prev_segment = false; + Point& x = p1; - // every overhang has saved if a roof should be generated for it. This can NOT be done in the for loop as an area may NOT have a roof - // even if it is larger than the minimum_roof_area when it is only larger because of the support horizontal expansion and - // it would not have a roof if the overhang is offset by support roof horizontal expansion instead. (At least this is the current behavior of the regular support) - Polygons overhang_regular; - { - // When support_offset = 0 safe_offset_inc will only be the difference between overhang_raw and relevant_forbidden, that has to be calculated anyway. - overhang_regular = safe_offset_inc(overhang_raw, mesh_group_settings.support_offset, relevant_forbidden, config.min_radius * 1.75 + config.xy_min_distance, 0, 1); - //check_self_intersections(overhang_regular, "overhang_regular1"); - - // offset ensures that areas that could be supported by a part of a support line, are not considered unsupported overhang - Polygons remaining_overhang = intersection( - diff(mesh_group_settings.support_offset == 0 ? - overhang_raw : - offset(union_ex(overhang_raw), mesh_group_settings.support_offset, jtMiter, 1.2), - offset(union_ex(overhang_regular), config.support_line_width * 0.5, jtMiter, 1.2)), - relevant_forbidden); - - // Offset the area to compensate for large tip radiis. Offset happens in multiple steps to ensure the tip is as close to the original overhang as possible. - //+config.support_line_width / 80 to avoid calculating very small (useless) offsets because of rounding errors. - //FIXME likely a better approach would be to find correspondences between the full overhang and the trimmed overhang - // and if there is no correspondence, project the missing points to the clipping curve. - for (coord_t extra_total_offset_acc = 0; ! remaining_overhang.empty() && extra_total_offset_acc + config.support_line_width / 8 < extra_outset; ) { - const coord_t offset_current_step = std::min( - extra_total_offset_acc + 2 * config.support_line_width > config.min_radius ? - config.support_line_width / 8 : - circle_length_to_half_linewidth_change, - extra_outset - extra_total_offset_acc); - extra_total_offset_acc += offset_current_step; - const Polygons &raw_collision = volumes.getCollision(0, layer_idx, true); - const coord_t offset_step = config.xy_min_distance + config.support_line_width; - // Reducing the remaining overhang by the areas already supported. - //FIXME 1.5 * extra_total_offset_acc seems to be too much, it may remove some remaining overhang without being supported at all. - remaining_overhang = diff(remaining_overhang, safe_offset_inc(overhang_regular, 1.5 * extra_total_offset_acc, raw_collision, offset_step, 0, 1)); - // Extending the overhangs by the inflated remaining overhangs. - overhang_regular = union_(overhang_regular, diff(safe_offset_inc(remaining_overhang, extra_total_offset_acc, raw_collision, offset_step, 0, 1), relevant_forbidden)); - //check_self_intersections(overhang_regular, "overhang_regular2"); - } -#if 0 - // If the xy distance overrides the z distance, some support needs to be inserted further down. - //=> Analyze which support points do not fit on this layer and check if they will fit a few layers down (while adding them an infinite amount of layers down would technically be closer the the setting description, it would not produce reasonable results. ) - if (! min_xy_dist) { - LineInformations overhang_lines; + double dist2 = vsize2_with_unscale(x - p); + if (dist2 < bestDist2) + { + bestDist2 = dist2; + if (distance == 0) { - //Vojtech: Generate support heads at support_tree_branch_distance spacing by producing a zig-zag infill at support_tree_branch_distance spacing, - // which is then resmapled - // support_line_width to form a line here as otherwise most will be unsupported. Technically this violates branch distance, - // mbut not only is this the only reasonable choice, but it ensures consistent behavior as some infill patterns generate - // each line segment as its own polyline part causing a similar line forming behavior. Also it is assumed that - // the area that is valid a layer below is to small for support roof. - Polylines polylines = ensure_maximum_distance_polyline( - generate_support_infill_lines(remaining_overhang, support_params, false, layer_idx, mesh_group_settings.support_tree_branch_distance), - config.min_radius, 1); - if (polylines.size() <= 3) - // add the outer wall to ensure it is correct supported instead - polylines = ensure_maximum_distance_polyline(to_polylines(remaining_overhang), connect_length, 3); - for (const auto &line : polylines) { - LineInformation res_line; - for (Point p : line) - res_line.emplace_back(p, LineStatus::INVALID); - overhang_lines.emplace_back(res_line); - } - validate_range(overhang_lines); + ret = x; } - for (size_t lag_ctr = 1; lag_ctr <= max_overhang_insert_lag && !overhang_lines.empty() && layer_idx - coord_t(lag_ctr) >= 1; lag_ctr++) { - // get least restricted avoidance for layer_idx-lag_ctr - const Polygons &relevant_forbidden_below = config.support_rests_on_model ? - volumes.getCollision(config.getRadius(0), layer_idx - lag_ctr, min_xy_dist) : - volumes.getAvoidance(config.getRadius(0), layer_idx - lag_ctr, AvoidanceType::Fast, false, min_xy_dist); - // it is not required to offset the forbidden area here as the points wont change: If points here are not inside the forbidden area neither will they be later when placing these points, as these are the same points. - auto evaluatePoint = [&](std::pair p) { return contains(relevant_forbidden_below, p.first); }; - - std::pair split = split_lines(overhang_lines, evaluatePoint); // keep all lines that are invalid - overhang_lines = split.first; - // Set all now valid lines to their correct LineStatus. Easiest way is to just discard Avoidance information for each point and evaluate them again. - LineInformations fresh_valid_points = convert_lines_to_internal(volumes, config, convert_internal_to_lines(split.second), layer_idx - lag_ctr); - validate_range(fresh_valid_points); - - rich_interface_placer.add_points_along_lines(fresh_valid_points, (force_tip_to_roof && lag_ctr <= num_support_roof_layers) ? num_support_roof_layers : 0, layer_idx - lag_ctr, false, roof_enabled ? num_support_roof_layers : 0); + else + { + // TODO: check whether it needs scale_() + Point inward_dir = turn90_ccw(normal(ab, 10.0) + normal(p1 - p0, 10.0)); // inward direction irrespective of sign of [distance] + // MM2INT(10.0) to retain precision for the eventual normalization + ret = x + normal(inward_dir, scale_(distance)); + is_already_on_correct_side_of_boundary = dot_with_unscale(inward_dir, p - x) * distance >= 0; } } -#endif } + else + { + projected_p_beyond_prev_segment = false; + p0 = p1; + p1 = p2; + continue; + } + } + else if (dot_prod >= ab_length2) // x is projected to beyond ab + { + projected_p_beyond_prev_segment = true; + p0 = p1; + p1 = p2; + continue; + } + else + { // x is projected to a point properly on the line segment (not onto a vertex). The case which looks like | . + projected_p_beyond_prev_segment = false; + Point x = a + ab * (dot_prod / ab_length2); - throw_on_cancel(); - - if (roof_enabled) { - // Try to support the overhangs by dense interfaces for num_support_roof_layers, cover the bottom most interface with tree tips. - static constexpr const coord_t support_roof_offset = 0; - Polygons overhang_roofs = safe_offset_inc(overhang_raw, support_roof_offset, relevant_forbidden, config.min_radius * 2 + config.xy_min_distance, 0, 1); - if (mesh_group_settings.minimum_support_area > 0) - remove_small(overhang_roofs, mesh_group_settings.minimum_roof_area); - overhang_regular = diff(overhang_regular, overhang_roofs, ApplySafetyOffset::Yes); - //check_self_intersections(overhang_regular, "overhang_regular3"); - for (ExPolygon &roof_part : union_ex(overhang_roofs)) { - sample_overhang_area(to_polygons(std::move(roof_part)), true, layer_idx, num_support_roof_layers, connect_length, - mesh_group_settings, rich_interface_placer); - throw_on_cancel(); + double dist2 = vsize2_with_unscale(p - x); + if (dist2 < bestDist2) + { + bestDist2 = dist2; + if (distance == 0) + { + ret = x; + } + else + { + Point inward_dir = turn90_ccw(normal(ab, scale_(distance))); // inward or outward depending on the sign of [distance] + ret = x + inward_dir; + is_already_on_correct_side_of_boundary = dot_with_unscale(inward_dir, p - x) >= 0; } - } - // Either the roof is not enabled, then these are all the overhangs to be supported, - // or roof is enabled and these are the thin overhangs at object slopes (not horizontal overhangs). - if (mesh_group_settings.minimum_support_area > 0) - remove_small(overhang_regular, mesh_group_settings.minimum_support_area); - for (ExPolygon &support_part : union_ex(overhang_regular)) { - sample_overhang_area(to_polygons(std::move(support_part)), - false, layer_idx, num_support_roof_layers, connect_length, - mesh_group_settings, rich_interface_placer); - throw_on_cancel(); } } - }); - finalize_raft_contact(print_object, raft_contact_layer_idx, interface_placer.top_contacts_mutable(), move_bounds); + p0 = p1; + p1 = p2; + } + + if (is_already_on_correct_side_of_boundary) // when the best point is already inside and we're moving inside, or when the best point is already outside and we're moving outside + { + // BBS. Remove this condition. + if (bestDist2 < distance * distance) + { + from = ret; + } + } + else if (bestDist2 < max_move_distance * max_move_distance) + { + from = ret; + } + return 0; } -static unsigned int move_inside(const Polygons &polygons, Point &from, int distance = 0, int64_t maxDist2 = std::numeric_limits::max()) +/* + * Implementation assumes moving inside, but moving outside should just as well be possible. + */ +static bool move_inside_expolys(const ExPolygons& polygons, Point& from, double distance, double max_move_distance) { - Point ret = from; + Point from0 = from; + Point ret = from; + std::vector valid_pts; double bestDist2 = std::numeric_limits::max(); - auto bestPoly = static_cast(-1); - bool is_already_on_correct_side_of_boundary = false; // whether [from] is already on the right side of the boundary - for (unsigned int poly_idx = 0; poly_idx < polygons.size(); ++ poly_idx) { - const Polygon &poly = polygons[poly_idx]; - if (poly.size() < 2) + unsigned int bestPoly = NO_INDEX; + bool is_already_on_correct_side_of_boundary = false; // whether [from] is already on the right side of the boundary + Point inward_dir; + for (unsigned int poly_idx = 0; poly_idx < polygons.size(); poly_idx++) + { + const ExPolygon poly = polygons[poly_idx]; + if (poly.contour.size() < 2) continue; - Point p0 = poly[poly.size() - 2]; - Point p1 = poly.back(); - // because we compare with vSize2 here (no division by zero), we also need to compare by vSize2 inside the loop + Point p0 = poly.contour[poly.contour.size()-2]; + Point p1 = poly.contour.points.back(); + // because we compare with vsize2_with_unscale here (no division by zero), we also need to compare by vsize2_with_unscale inside the loop // to avoid integer rounding edge cases - bool projected_p_beyond_prev_segment = (p1 - p0).cast().dot((from - p0).cast()) >= (p1 - p0).cast().squaredNorm(); - for (const Point& p2 : poly) { - // X = A + Normal(B-A) * (((B-A) dot (P-A)) / VSize(B-A)); - // = A + (B-A) * ((B-A) dot (P-A)) / VSize2(B-A); + bool projected_p_beyond_prev_segment = dot_with_unscale(p1 - p0, from - p0) >= vsize2_with_unscale(p1 - p0); + for(const Point& p2 : poly.contour.points) + { + // X = A + Normal(B-A) * (((B-A) dot_with_unscale (P-A)) / VSize(B-A)); + // = A + (B-A) * ((B-A) dot_with_unscale (P-A)) / VSize2(B-A); // X = P projected on AB - const Point& a = p1; - const Point& b = p2; - const Point& p = from; - auto ab = (b - a).cast(); - auto ap = (p - a).cast(); - int64_t ab_length2 = ab.squaredNorm(); - if (ab_length2 <= 0) { //A = B, i.e. the input polygon had two adjacent points on top of each other. + Point a = p1; + Point b = p2; + Point p = from; + Point ab = b - a; + Point ap = p - a; + double ab_length2 = vsize2_with_unscale(ab); + if(ab_length2 <= 0) //A = B, i.e. the input polygon had two adjacent points on top of each other. + { p1 = p2; //Skip only one of the points. continue; } - int64_t dot_prod = ab.dot(ap); - if (dot_prod <= 0) { // x is projected to before ab - if (projected_p_beyond_prev_segment) { - // case which looks like: > . + double dot_prod = dot_with_unscale(ab, ap); + if (dot_prod <= 0) // x is projected to before ab + { + if (projected_p_beyond_prev_segment) + { // case which looks like: > . projected_p_beyond_prev_segment = false; Point& x = p1; - auto dist2 = (x - p).cast().squaredNorm(); - if (dist2 < bestDist2) { + double dist2 = vsize2_with_unscale(x - p); + if (dist2 < bestDist2) + { bestDist2 = dist2; bestPoly = poly_idx; - if (distance == 0) - ret = x; - else { - Vec2d abd = ab.cast(); - Vec2d p1p2 = (p1 - p0).cast(); - double lab = abd.norm(); - double lp1p2 = p1p2.norm(); - // inward direction irrespective of sign of [distance] - auto inward_dir = perp(abd * (scaled(10.0) / lab) + p1p2 * (scaled(10.0) / lp1p2)); + if (distance == 0) { ret = x; } + else + { + inward_dir = turn90_ccw(normal(ab, 10.0) + normal(p1 - p0, 10.0)); // inward direction irrespective of sign of [distance] // MM2INT(10.0) to retain precision for the eventual normalization - ret = x + (inward_dir * (distance / inward_dir.norm())).cast(); - is_already_on_correct_side_of_boundary = inward_dir.dot((p - x).cast()) * distance >= 0; + ret = x + normal(inward_dir, scale_(distance)); + is_already_on_correct_side_of_boundary = dot_with_unscale(inward_dir, p - x) * distance >= 0; + if (is_already_on_correct_side_of_boundary && dist2 < distance * distance) + valid_pts.push_back(ret-from0); } } - } else { - projected_p_beyond_prev_segment = false; + } + else + { + projected_p_beyond_prev_segment = false; p0 = p1; p1 = p2; continue; } - } else if (dot_prod >= ab_length2) { - // x is projected to beyond ab + } + else if (dot_prod >= ab_length2) // x is projected to beyond ab + { projected_p_beyond_prev_segment = true; p0 = p1; p1 = p2; continue; - } else { - // x is projected to a point properly on the line segment (not onto a vertex). The case which looks like | . + } + else + { // x is projected to a point properly on the line segment (not onto a vertex). The case which looks like | . projected_p_beyond_prev_segment = false; - Point x = a + (ab.cast() * (double(dot_prod) / double(ab_length2))).cast(); - auto dist2 = (p - x).cast().squaredNorm(); - if (dist2 < bestDist2) { + Point x = a + ab * (dot_prod / ab_length2); + + double dist2 = vsize2_with_unscale(p - x); + if (dist2 < bestDist2) + { bestDist2 = dist2; bestPoly = poly_idx; - if (distance == 0) - ret = x; - else { - Vec2d abd = ab.cast(); - Vec2d inward_dir = perp(abd * (distance / abd.norm())); // inward or outward depending on the sign of [distance] - ret = x + inward_dir.cast(); - is_already_on_correct_side_of_boundary = inward_dir.dot((p - x).cast()) >= 0; + if (distance == 0) { ret = x; } + else + { + inward_dir = turn90_ccw(normal(ab, scale_(distance))); // inward or outward depending on the sign of [distance] + ret = x + inward_dir; + is_already_on_correct_side_of_boundary = dot_with_unscale(inward_dir, p - x) >= 0; + if (is_already_on_correct_side_of_boundary && dist2 1) { + // std::sort(valid_pts.begin(), valid_pts.end()); + // Point v_combine = valid_pts[0] + valid_pts[1]; + // if(vsize2_with_unscale(v_combine)::max()) -{ - if (! contains(polygons, from)) - move_inside(polygons, from); - return from; + return false; } -/*! - * \brief Checks if an influence area contains a valid subsection and returns the corresponding metadata and the new Influence area. - * - * Calculates an influence areas of the layer below, based on the influence area of one element on the current layer. - * Increases every influence area by maximum_move_distance_slow. If this is not enough, as in we would change our gracious or to_buildplate status the influence areas are instead increased by maximum_move_distance_slow. - * Also ensures that increasing the radius of a branch, does not cause it to change its status (like to_buildplate ). If this were the case, the radius is not increased instead. - * - * Warning: The used format inside this is different as the SupportElement does not have a valid area member. Instead this area is saved as value of the dictionary. This was done to avoid not needed heap allocations. - * - * \param settings[in] Which settings have to be used to check validity. - * \param layer_idx[in] Number of the current layer. - * \param parent[in] The metadata of the parents influence area. - * \param relevant_offset[in] The maximal possible influence area. No guarantee regarding validity with current layer collision required, as it is ensured in-function! - * \param to_bp_data[out] The part of the Influence area that can reach the buildplate. - * \param to_model_data[out] The part of the Influence area that do not have to reach the buildplate. This has overlap with new_layer_data. - * \param increased[out] Area than can reach all further up support points. No assurance is made that the buildplate or the model can be reached in accordance to the user-supplied settings. - * \param overspeed[in] How much should the already offset area be offset again. Usually this is 0. - * \param mergelayer[in] Will the merge method be called on this layer. This information is required as some calculation can be avoided if they are not required for merging. - * \return A valid support element for the next layer regarding the calculated influence areas. Empty if no influence are can be created using the supplied influence area and settings. - */ -[[nodiscard]] static std::optional increase_single_area( - const TreeModelVolumes &volumes, - const TreeSupportSettings &config, - const AreaIncreaseSettings &settings, - const LayerIndex layer_idx, - const SupportElement &parent, - const Polygons &relevant_offset, - Polygons &to_bp_data, - Polygons &to_model_data, - Polygons &increased, - const coord_t overspeed, - const bool mergelayer) +static Point find_closest_ex(Point from, const ExPolygons& polygons) { - SupportElementState current_elem{ SupportElementState::propagate_down(parent.state) }; - Polygons check_layer_data; - if (settings.increase_radius) - current_elem.effective_radius_height += 1; - coord_t radius = support_element_collision_radius(config, current_elem); - - const auto _tiny_area_threshold = tiny_area_threshold(); - if (settings.move) { - increased = relevant_offset; - if (overspeed > 0) { - const coord_t safe_movement_distance = - (current_elem.use_min_xy_dist ? config.xy_min_distance : config.xy_distance) + - (std::min(config.z_distance_top_layers, config.z_distance_bottom_layers) > 0 ? config.min_feature_size : 0); - // The difference to ensure that the result not only conforms to wall_restriction, but collision/avoidance is done later. - // The higher last_safe_step_movement_distance comes exactly from the fact that the collision will be subtracted later. - increased = safe_offset_inc(increased, overspeed, volumes.getWallRestriction(support_element_collision_radius(config, parent.state), layer_idx, parent.state.use_min_xy_dist), - safe_movement_distance, safe_movement_distance + radius, 1); - } - if (settings.no_error && settings.move) - // as ClipperLib::jtRound has to be used for offsets this simplify is VERY important for performance. - polygons_simplify(increased, scaled(0.025), polygons_strictly_simple); - } else - // if no movement is done the areas keep parent area as no move == offset(0) - increased = parent.influence_area; - - if (mergelayer || current_elem.to_buildplate) { - to_bp_data = safe_union(diff_clipped(increased, volumes.getAvoidance(radius, layer_idx - 1, settings.type, false, settings.use_min_distance))); - if (! current_elem.to_buildplate && area(to_bp_data) > _tiny_area_threshold) { - // mostly happening in the tip, but with merges one should check every time, just to be sure. - current_elem.to_buildplate = true; // sometimes nodes that can reach the buildplate are marked as cant reach, tainting subtrees. This corrects it. - BOOST_LOG_TRIVIAL(debug) << "Corrected taint leading to a wrong to model value on layer " << layer_idx - 1 << " targeting " << - current_elem.target_height << " with radius " << radius; - } - } - if (config.support_rests_on_model) { - if (mergelayer || current_elem.to_model_gracious) - to_model_data = safe_union(diff_clipped(increased, volumes.getAvoidance(radius, layer_idx - 1, settings.type, true, settings.use_min_distance))); - - if (!current_elem.to_model_gracious) { - if (mergelayer && area(to_model_data) >= _tiny_area_threshold) { - current_elem.to_model_gracious = true; - BOOST_LOG_TRIVIAL(debug) << "Corrected taint leading to a wrong non gracious value on layer " << layer_idx - 1 << " targeting " << - current_elem.target_height << " with radius " << radius; - } else - // Cannot route to gracious areas. Push the tree away from object and route it down anyways. - to_model_data = safe_union(diff_clipped(increased, volumes.getCollision(radius, layer_idx - 1, settings.use_min_distance))); - } - } - - check_layer_data = current_elem.to_buildplate ? to_bp_data : to_model_data; - - if (settings.increase_radius && area(check_layer_data) > _tiny_area_threshold) { - auto validWithRadius = [&](coord_t next_radius) { - if (volumes.ceilRadius(next_radius, settings.use_min_distance) <= volumes.ceilRadius(radius, settings.use_min_distance)) - return true; - - Polygons to_bp_data_2; - if (current_elem.to_buildplate) - // regular union as output will not be used later => this area should always be a subset of the safe_union one (i think) - to_bp_data_2 = diff_clipped(increased, volumes.getAvoidance(next_radius, layer_idx - 1, settings.type, false, settings.use_min_distance)); - Polygons to_model_data_2; - if (config.support_rests_on_model && !current_elem.to_buildplate) - to_model_data_2 = diff_clipped(increased, - current_elem.to_model_gracious ? - volumes.getAvoidance(next_radius, layer_idx - 1, settings.type, true, settings.use_min_distance) : - volumes.getCollision(next_radius, layer_idx - 1, settings.use_min_distance)); - Polygons check_layer_data_2 = current_elem.to_buildplate ? to_bp_data_2 : to_model_data_2; - return area(check_layer_data_2) > _tiny_area_threshold; - }; - coord_t ceil_radius_before = volumes.ceilRadius(radius, settings.use_min_distance); - - if (support_element_collision_radius(config, current_elem) < config.increase_radius_until_radius && support_element_collision_radius(config, current_elem) < support_element_radius(config, current_elem)) { - coord_t target_radius = std::min(support_element_radius(config, current_elem), config.increase_radius_until_radius); - coord_t current_ceil_radius = volumes.getRadiusNextCeil(radius, settings.use_min_distance); - - while (current_ceil_radius < target_radius && validWithRadius(volumes.getRadiusNextCeil(current_ceil_radius + 1, settings.use_min_distance))) - current_ceil_radius = volumes.getRadiusNextCeil(current_ceil_radius + 1, settings.use_min_distance); - size_t resulting_eff_dtt = current_elem.effective_radius_height; - while (resulting_eff_dtt + 1 < current_elem.distance_to_top && - config.getRadius(resulting_eff_dtt + 1, current_elem.elephant_foot_increases) <= current_ceil_radius && - config.getRadius(resulting_eff_dtt + 1, current_elem.elephant_foot_increases) <= support_element_radius(config, current_elem)) - ++ resulting_eff_dtt; - current_elem.effective_radius_height = resulting_eff_dtt; - } - radius = support_element_collision_radius(config, current_elem); - - const coord_t foot_radius_increase = std::max(config.bp_radius_increase_per_layer - config.branch_radius_increase_per_layer, 0.0); - // Is nearly all of the time 1, but sometimes an increase of 1 could cause the radius to become bigger than recommendedMinRadius, - // which could cause the radius to become bigger than precalculated. - double planned_foot_increase = std::min(1.0, double(config.recommendedMinRadius(layer_idx - 1) - support_element_radius(config, current_elem)) / foot_radius_increase); -//FIXME - bool increase_bp_foot = planned_foot_increase > 0 && current_elem.to_buildplate; -// bool increase_bp_foot = false; - - if (increase_bp_foot && support_element_radius(config, current_elem) >= config.branch_radius && support_element_radius(config, current_elem) >= config.increase_radius_until_radius) - if (validWithRadius(config.getRadius(current_elem.effective_radius_height, current_elem.elephant_foot_increases + planned_foot_increase))) { - current_elem.elephant_foot_increases += planned_foot_increase; - radius = support_element_collision_radius(config, current_elem); - } - - if (ceil_radius_before != volumes.ceilRadius(radius, settings.use_min_distance)) { - if (current_elem.to_buildplate) - to_bp_data = safe_union(diff_clipped(increased, volumes.getAvoidance(radius, layer_idx - 1, settings.type, false, settings.use_min_distance))); - if (config.support_rests_on_model && (!current_elem.to_buildplate || mergelayer)) - to_model_data = safe_union(diff_clipped(increased, - current_elem.to_model_gracious ? - volumes.getAvoidance(radius, layer_idx - 1, settings.type, true, settings.use_min_distance) : - volumes.getCollision(radius, layer_idx - 1, settings.use_min_distance) - )); - check_layer_data = current_elem.to_buildplate ? to_bp_data : to_model_data; - if (area(check_layer_data) < _tiny_area_threshold) { - BOOST_LOG_TRIVIAL(error) << "Lost area by doing catch up from " << ceil_radius_before << " to radius " << - volumes.ceilRadius(support_element_collision_radius(config, current_elem), settings.use_min_distance); - tree_supports_show_error("Area lost catching up radius. May not cause visible malformation."sv, true); + Point closest_pt; + double min_dist2 = std::numeric_limits::max(); + + for (const ExPolygon &poly : polygons) { + for (int i = 0; i < poly.num_contours(); i++) { + const Point* candidate = poly.contour_or_hole(i).closest_point(from); + double dist2 = vsize2_with_unscale(*candidate - from); + if (dist2 < min_dist2) { + closest_pt = *candidate; + min_dist2 = dist2; } } } - return area(check_layer_data) > _tiny_area_threshold ? std::optional(current_elem) : std::optional(); + return closest_pt; } -struct SupportElementInfluenceAreas { - // All influence areas: both to build plate and model. - Polygons influence_areas; - // Influence areas just to build plate. - Polygons to_bp_areas; - // Influence areas just to model. - Polygons to_model_areas; +static bool move_outside_expolys(const ExPolygons& polygons, Point& from, double distance, double max_move_distance) +{ + return move_inside_expolys(polygons, from, -distance, -max_move_distance); +} + +static bool is_inside_ex(const ExPolygon &polygon, const Point &pt) +{ + if (!get_extents(polygon).contains(pt)) + return false; + + return polygon.contains(pt); +} - void clear() { - this->influence_areas.clear(); - this->to_bp_areas.clear(); - this->to_model_areas.clear(); +static bool is_inside_ex(const ExPolygons &polygons, const Point &pt) +{ + for (const ExPolygon &poly : polygons) { + if (is_inside_ex(poly, pt)) + return true; } -}; -struct SupportElementMerging { - SupportElementState state; - /*! - * \brief All elements in the layer above the current one that are supported by this element - */ - SupportElement::ParentIndices parents; - - SupportElementInfluenceAreas areas; - // Bounding box of all influence areas. - Eigen::AlignedBox bbox_data; - - const Eigen::AlignedBox& bbox() const { return bbox_data;} - const Point centroid() const { return (bbox_data.min() + bbox_data.max()) / 2; } - void set_bbox(const BoundingBox& abbox) - { Point eps { coord_t(SCALED_EPSILON), coord_t(SCALED_EPSILON) }; bbox_data = { abbox.min - eps, abbox.max + eps }; } - - // Called by the AABBTree builder to get an index into the vector of source elements. - // Not needed, thus zero is returned. - static size_t idx() { return 0; } -}; + return false; +} -/*! - * \brief Increases influence areas as far as required. - * - * Calculates influence areas of the layer below, based on the influence areas of the current layer. - * Increases every influence area by maximum_move_distance_slow. If this is not enough, as in it would change the gracious or to_buildplate status, the influence areas are instead increased by maximum_move_distance. - * Also ensures that increasing the radius of a branch, does not cause it to change its status (like to_buildplate ). If this were the case, the radius is not increased instead. - * - * Warning: The used format inside this is different as the SupportElement does not have a valid area member. Instead this area is saved as value of the dictionary. This was done to avoid not needed heap allocations. - * - * \param to_bp_areas[out] Influence areas that can reach the buildplate - * \param to_model_areas[out] Influence areas that do not have to reach the buildplate. This has overlap with new_layer_data, as areas that can reach the buildplate are also considered valid areas to the model. - * This redundancy is required if a to_buildplate influence area is allowed to merge with a to model influence area. - * \param influence_areas[out] Area than can reach all further up support points. No assurance is made that the buildplate or the model can be reached in accordance to the user-supplied settings. - * \param bypass_merge_areas[out] Influence areas ready to be added to the layer below that do not need merging. - * \param last_layer[in] Influence areas of the current layer. - * \param layer_idx[in] Number of the current layer. - * \param mergelayer[in] Will the merge method be called on this layer. This information is required as some calculation can be avoided if they are not required for merging. - */ -static void increase_areas_one_layer( - const TreeModelVolumes &volumes, - const TreeSupportSettings &config, - // New areas at the layer below layer_idx - std::vector &merging_areas, - // Layer above merging_areas. - const LayerIndex layer_idx, - // Layer elements above merging_areas. - SupportElements &layer_elements, - // If false, the merging_areas will not be merged for performance reasons. - const bool mergelayer, - std::function throw_on_cancel) +static bool move_out_expolys(const ExPolygons& polygons, Point& from, double distance, double max_move_distance) { - using AvoidanceType = TreeModelVolumes::AvoidanceType; - - tbb::parallel_for(tbb::blocked_range(0, merging_areas.size(), 1), - [&](const tbb::blocked_range &range) { - for (size_t merging_area_idx = range.begin(); merging_area_idx < range.end(); ++ merging_area_idx) { - SupportElementMerging &merging_area = merging_areas[merging_area_idx]; - assert(merging_area.parents.size() == 1); - SupportElement &parent = layer_elements[merging_area.parents.front()]; - SupportElementState elem = SupportElementState::propagate_down(parent.state); - const Polygons &wall_restriction = - // Abstract representation of the model outline. If an influence area would move through it, it could teleport through a wall. - volumes.getWallRestriction(support_element_collision_radius(config, parent.state), layer_idx, parent.state.use_min_xy_dist); - -#ifdef TREESUPPORT_DEBUG_SVG - SVG::export_expolygons(debug_out_path("treesupport-increase_areas_one_layer-%d-%ld.svg", layer_idx, int(merging_area_idx)), - { { { union_ex(wall_restriction) }, { "wall_restricrictions", "gray", 0.5f } }, - { { union_ex(parent.influence_area) }, { "parent", "red", "black", "", scaled(0.1f), 0.5f } } }); -#endif // TREESUPPORT_DEBUG_SVG - - Polygons to_bp_data, to_model_data; - coord_t radius = support_element_collision_radius(config, elem); - - // When the radius increases, the outer "support wall" of the branch will have been moved farther away from the center (as this is the definition of radius). - // As it is not specified that the support_tree_angle has to be one of the center of the branch, it is here seen as the smaller angle of the outer wall of the branch, to the outer wall of the same branch one layer above. - // As the branch may have become larger the distance between these 2 walls is smaller than the distance of the center points. - // These extra distance is added to the movement distance possible for this layer. - - coord_t extra_speed = 5; // The extra speed is added to both movement distances. Also move 5 microns faster than allowed to avoid rounding errors, this may cause issues at VERY VERY small layer heights. - coord_t extra_slow_speed = 0; // Only added to the slow movement distance. - const coord_t ceiled_parent_radius = volumes.ceilRadius(support_element_collision_radius(config, parent.state), parent.state.use_min_xy_dist); - coord_t projected_radius_increased = config.getRadius(parent.state.effective_radius_height + 1, parent.state.elephant_foot_increases); - coord_t projected_radius_delta = projected_radius_increased - support_element_collision_radius(config, parent.state); - - // When z distance is more than one layer up and down the Collision used to calculate the wall restriction will always include the wall (and not just the xy_min_distance) of the layer above and below like this (d = blocked area because of z distance): - /* - * layer z+1:dddddiiiiiioooo - * layer z+0:xxxxxdddddddddd - * layer z-1:dddddxxxxxxxxxx - * For more detailed visualisation see calculateWallRestrictions - */ - const coord_t safe_movement_distance = - (elem.use_min_xy_dist ? config.xy_min_distance : config.xy_distance) + - (std::min(config.z_distance_top_layers, config.z_distance_bottom_layers) > 0 ? config.min_feature_size : 0); - if (ceiled_parent_radius == volumes.ceilRadius(projected_radius_increased, parent.state.use_min_xy_dist) || - projected_radius_increased < config.increase_radius_until_radius) - // If it is guaranteed possible to increase the radius, the maximum movement speed can be increased, as it is assumed that the maximum movement speed is the one of the slower moving wall - extra_speed += projected_radius_delta; - else - // if a guaranteed radius increase is not possible, only increase the slow speed - // Ensure that the slow movement distance can not become larger than the fast one. - extra_slow_speed += std::min(projected_radius_delta, (config.maximum_move_distance + extra_speed) - (config.maximum_move_distance_slow + extra_slow_speed)); - - if (config.layer_start_bp_radius > layer_idx && - config.recommendedMinRadius(layer_idx - 1) < config.getRadius(elem.effective_radius_height + 1, elem.elephant_foot_increases)) { - // can guarantee elephant foot radius increase - if (ceiled_parent_radius == volumes.ceilRadius(config.getRadius(parent.state.effective_radius_height + 1, parent.state.elephant_foot_increases + 1), parent.state.use_min_xy_dist)) - extra_speed += config.bp_radius_increase_per_layer; - else - extra_slow_speed += std::min(coord_t(config.bp_radius_increase_per_layer), - config.maximum_move_distance - (config.maximum_move_distance_slow + extra_slow_speed)); - } + Point from0 = from; + ExPolygons polys_dilated = union_ex(offset_ex(polygons, scale_(distance))); + Point pt = projection_onto(polys_dilated, from);// find_closest_ex(from, polys_dilated); + Point outward_dir = pt - from; + Point pt_max = from + normal(outward_dir, scale_(max_move_distance)); + double dist2 = vsize2_with_unscale(outward_dir); + if (dist2 > SQ(max_move_distance)) + pt = pt_max; + // case 5: already outside and far enough, no need to move + if (!is_inside_ex(polys_dilated, from)) + return true; + else if (!is_inside_ex(polygons, from)) { + // case 4: already outside but not far enough + from = pt; + return true; + } + else { + bool pt_max_in_poly = is_inside_ex(polygons, pt_max); + if (!pt_max_in_poly) { + from = pt_max; + return true; + } + else { + return false; + } + } +} + +static Point bounding_box_middle(const BoundingBox &bbox) +{ + return (bbox.max + bbox.min) / 2; +} - const coord_t fast_speed = config.maximum_move_distance + extra_speed; - const coord_t slow_speed = config.maximum_move_distance_slow + extra_speed + extra_slow_speed; +TreeSupport::TreeSupport(PrintObject& object, const SlicingParameters &slicing_params) + : m_object(&object), m_slicing_params(slicing_params), m_object_config(&object.config()) +{ + m_raft_layers = slicing_params.base_raft_layers + slicing_params.interface_raft_layers; + support_type = m_object_config->support_type; + support_style = m_object_config->support_style; + if (support_style == smsDefault) + // Orca: use organic as default + support_style = smsOrganic; + SupportMaterialPattern support_pattern = m_object_config->support_base_pattern; + if (support_style == smsTreeHybrid && support_pattern == smpDefault) + support_pattern = smpRectilinear; + m_support_params.base_fill_pattern = + support_pattern == smpLightning ? ipLightning : + support_pattern == smpHoneycomb ? ipHoneycomb : + m_support_params.support_density > 0.95 || m_support_params.with_sheath ? ipRectilinear : ipSupportBase; + + m_support_params.interface_fill_pattern = (m_support_params.interface_density > 0.95 ? ipRectilinear : ipSupportBase); + if (m_object_config->support_interface_pattern == smipGrid) + m_support_params.contact_fill_pattern = ipGrid; + else if (m_object_config->support_interface_pattern == smipRectilinearInterlaced) + m_support_params.contact_fill_pattern = ipRectilinear; + else + m_support_params.contact_fill_pattern = (m_object_config->support_interface_pattern == smipAuto && m_slicing_params.soluble_interface) || + m_object_config->support_interface_pattern == smipConcentric ? + ipConcentric : + (m_support_params.interface_density > 0.95 ? ipRectilinear : ipSupportBase); + + const auto nozzle_diameter = object.print()->config().nozzle_diameter.get_at(object.config().support_interface_filament-1); + const coordf_t extrusion_width = m_object_config->line_width.get_abs_value(nozzle_diameter); + const coordf_t support_extrusion_width = m_object_config->support_line_width.get_abs_value(nozzle_diameter); + + m_support_params.support_extrusion_width = support_extrusion_width > 0 ? support_extrusion_width : extrusion_width; + is_slim = is_tree_slim(support_type, support_style); + is_strong = is_tree(support_type) && support_style == smsTreeStrong; + MAX_BRANCH_RADIUS = 10.0; + tree_support_branch_diameter_angle = 5.0;//is_slim ? 10.0 : 5.0; + // by default tree support needs no infill, unless it's tree hybrid which contains normal nodes. + with_infill = support_pattern != smpNone && support_pattern != smpDefault; + const PrintConfig& print_config = m_object->print()->config(); + m_machine_border.contour = get_bed_shape_with_excluded_area(print_config); + Vec3d plate_offset = m_object->print()->get_plate_origin(); + // align with the centered object in current plate (may not be the 1st plate, so need to add the plate offset) + m_machine_border.translate(Point(scale_(plate_offset(0)), scale_(plate_offset(1))) - m_object->instances().front().shift); +#ifdef SUPPORT_TREE_DEBUG_TO_SVG + SVG svg("SVG/machine_boarder.svg", m_object->bounding_box()); + if (svg.is_opened()) svg.draw(m_machine_border, "yellow"); +#endif +} - Polygons offset_slow, offset_fast; - bool add = false; - bool bypass_merge = false; - constexpr bool increase_radius = true, no_error = true, use_min_radius = true, move = true; // aliases for better readability +#define SUPPORT_SURFACES_OFFSET_PARAMETERS ClipperLib::jtSquare, 0. +void TreeSupport::detect_overhangs(bool detect_first_sharp_tail_only) +{ + // overhangs are already detected + if (m_object->support_layer_count() >= m_object->layer_count()) + return; - // Determine in which order configurations are checked if they result in a valid influence area. Check will stop if a valid area is found - std::vector order; - auto insertSetting = [&](AreaIncreaseSettings settings, bool back) { - if (std::find(order.begin(), order.end(), settings) == order.end()) { - if (back) - order.emplace_back(settings); - else - order.insert(order.begin(), settings); - } - }; - - const bool parent_moved_slow = elem.last_area_increase.increase_speed < config.maximum_move_distance; - const bool avoidance_speed_mismatch = parent_moved_slow && elem.last_area_increase.type != AvoidanceType::Slow; - if (elem.last_area_increase.move && elem.last_area_increase.no_error && elem.can_use_safe_radius && !mergelayer && - !avoidance_speed_mismatch && (elem.distance_to_top >= config.tip_layers || parent_moved_slow)) { - // assume that the avoidance type that was best for the parent is best for me. Makes this function about 7% faster. - insertSetting({ elem.last_area_increase.type, elem.last_area_increase.increase_speed < config.maximum_move_distance ? slow_speed : fast_speed, - increase_radius, elem.last_area_increase.no_error, !use_min_radius, elem.last_area_increase.move }, true); - insertSetting({ elem.last_area_increase.type, elem.last_area_increase.increase_speed < config.maximum_move_distance ? slow_speed : fast_speed, - !increase_radius, elem.last_area_increase.no_error, !use_min_radius, elem.last_area_increase.move }, true); + // Clear and create Tree Support Layers + m_object->clear_support_layers(); + m_object->clear_tree_support_preview_cache(); + create_tree_support_layers(); + + const PrintObjectConfig& config = m_object->config(); + SupportType stype = support_type; + const coordf_t radius_sample_resolution = g_config_tree_support_collision_resolution; + const double nozzle_diameter = m_object->print()->config().nozzle_diameter.get_at(0); + const coordf_t extrusion_width = config.get_abs_value("line_width", nozzle_diameter); + const coordf_t extrusion_width_scaled = scale_(extrusion_width); + const coordf_t max_bridge_length = scale_(config.max_bridge_length.value); + const bool bridge_no_support = max_bridge_length > 0; + const bool support_critical_regions_only = config.support_critical_regions_only.value; + const bool config_remove_small_overhangs = config.support_remove_small_overhang.value; + const int enforce_support_layers = config.enforce_support_layers.value; + const double area_thresh_well_supported = SQ(scale_(6)); + const double length_thresh_well_supported = scale_(6); + static const double sharp_tail_max_support_height = 16.f; + // a region is considered well supported if the number of layers below it exceeds this threshold + const int thresh_layers_below = 10 / config.layer_height; + double obj_height = m_object->size().z(); + // +1 makes the threshold inclusive + double thresh_angle = config.support_threshold_angle.value > EPSILON ? config.support_threshold_angle.value + 1 : 30; + thresh_angle = std::min(thresh_angle, 89.); // should be smaller than 90 + const double threshold_rad = Geometry::deg2rad(thresh_angle); + + // for small overhang removal + struct OverhangCluster { + std::map layer_overhangs; + ExPolygons merged_poly; + BoundingBox merged_bbox; + int min_layer = 1e7; + int max_layer = 0; + coordf_t offset = 0; + bool is_cantilever = false; + bool is_sharp_tail = false; + bool is_small_overhang = false; + OverhangCluster(const ExPolygon* expoly, int layer_nr) { + push_back(expoly, layer_nr); + } + void push_back(const ExPolygon* expoly, int layer_nr) { + layer_overhangs.emplace(layer_nr, expoly); + auto dilate1 = offset_ex(*expoly, offset); + if (!dilate1.empty()) + merged_poly = union_ex(merged_poly, dilate1); + min_layer = std::min(min_layer, layer_nr); + max_layer = std::max(max_layer, layer_nr); + merged_bbox.merge(get_extents(dilate1)); + } + int height() { + return max_layer - min_layer + 1; + } + bool intersects(const ExPolygon& region, int layer_nr, coordf_t offset) { + if (layer_nr < 1) return false; + auto it = layer_overhangs.find(layer_nr - 1); + if (it == layer_overhangs.end()) return false; + const ExPolygon* overhang = it->second; + + this->offset = offset; + auto dilate1 = offset_ex(region, offset); + BoundingBox bbox = get_extents(dilate1); + if (!merged_bbox.overlap(bbox)) + return false; + return overlaps({ *overhang }, dilate1); + } + // it's basically the combination of push_back and intersects, but saves an offset_ex + bool push_back_if_intersects(const ExPolygon& region, int layer_nr, coordf_t offset) { + bool is_intersect = false; + ExPolygons dilate1; + BoundingBox bbox; + do { + if (layer_nr < 1) break; + auto it = layer_overhangs.find(layer_nr - 1); + if (it == layer_overhangs.end()) break; + const ExPolygon* overhang = it->second; + + this->offset = offset; + dilate1 = offset_ex(region, offset); + if (dilate1.empty()) break; + bbox = get_extents(dilate1); + if (!merged_bbox.overlap(bbox)) + break; + is_intersect = overlaps({ *overhang }, dilate1); + } while (0); + if (is_intersect) { + layer_overhangs.emplace(layer_nr, ®ion); + merged_poly = union_ex(merged_poly, dilate1); + min_layer = std::min(min_layer, layer_nr); + max_layer = std::max(max_layer, layer_nr); + merged_bbox.merge(bbox); } - // branch may still go though a hole, so a check has to be done whether the hole was already passed, and the regular avoidance can be used. - if (!elem.can_use_safe_radius) { - // if the radius until which it is always increased can not be guaranteed, move fast. This is to avoid holes smaller than the real branch radius. - // This does not guarantee the avoidance of such holes, but ensures they are avoided if possible. - // order.emplace_back(AvoidanceType::Slow,!increase_radius,no_error,!use_min_radius,move); - insertSetting({ AvoidanceType::Slow, slow_speed, increase_radius, no_error, !use_min_radius, !move }, true); // did we go through the hole - // in many cases the definition of hole is overly restrictive, so to avoid unnecessary fast movement in the tip, it is ignored there for a bit. - // This CAN cause a branch to go though a hole it otherwise may have avoided. - if (elem.distance_to_top < round_up_divide(config.tip_layers, size_t(2))) - insertSetting({ AvoidanceType::Fast, slow_speed, increase_radius, no_error, !use_min_radius, !move }, true); - insertSetting({ AvoidanceType::FastSafe, fast_speed, increase_radius, no_error, !use_min_radius, !move }, true); // did we manage to avoid the hole - insertSetting({ AvoidanceType::FastSafe, fast_speed, !increase_radius, no_error, !use_min_radius, move }, true); - insertSetting({ AvoidanceType::Fast, fast_speed, !increase_radius, no_error, !use_min_radius, move }, true); - } else { - insertSetting({ AvoidanceType::Slow, slow_speed, increase_radius, no_error, !use_min_radius, move }, true); - // while moving fast to be able to increase the radius (b) may seems preferable (over a) this can cause the a sudden skip in movement, - // which looks similar to a layer shift and can reduce stability. - // as such idx have chosen to only use the user setting for radius increases as a friendly recommendation. - insertSetting({ AvoidanceType::Slow, slow_speed, !increase_radius, no_error, !use_min_radius, move }, true); // a - if (elem.distance_to_top < config.tip_layers) - insertSetting({ AvoidanceType::FastSafe, slow_speed, increase_radius, no_error, !use_min_radius, move }, true); - insertSetting({ AvoidanceType::FastSafe, fast_speed, increase_radius, no_error, !use_min_radius, move }, true); // b - insertSetting({ AvoidanceType::FastSafe, fast_speed, !increase_radius, no_error, !use_min_radius, move }, true); + return is_intersect; + } + }; + std::vector overhangClusters; + + auto find_and_insert_cluster = [](auto ®ionClusters, const ExPolygon ®ion, int layer_nr, coordf_t offset) { + OverhangCluster *cluster = nullptr; + for (int i = 0; i < regionClusters.size(); i++) { + auto cluster_i = ®ionClusters[i]; + if (cluster_i->push_back_if_intersects(region, layer_nr, offset)) { + cluster = cluster_i; + break; } + } + if (!cluster) { + cluster = ®ionClusters.emplace_back(®ion, layer_nr); + } + return cluster; + }; + + if (!is_tree(stype)) return; + + max_cantilever_dist = 0; + + // main part of overhang detection can be parallel + tbb::parallel_for(tbb::blocked_range(0, m_object->layer_count()), + [&](const tbb::blocked_range& range) { + for (size_t layer_nr = range.begin(); layer_nr < range.end(); layer_nr++) { + if (m_object->print()->canceled()) + break; + + if (!is_auto(stype) && layer_nr > enforce_support_layers) + continue; + + Layer* layer = m_object->get_layer(layer_nr); - if (elem.use_min_xy_dist) { - std::vector new_order; - // if the branch currently has to use min_xy_dist check if the configuration would also be valid - // with the regular xy_distance before checking with use_min_radius (Only happens when Support Distance priority is z overrides xy ) - for (AreaIncreaseSettings settings : order) { - new_order.emplace_back(settings); - new_order.push_back({ settings.type, settings.increase_speed, settings.increase_radius, settings.no_error, use_min_radius, settings.move }); + if (layer->lower_layer == nullptr) { + for (auto& slice : layer->lslices) { + auto bbox_size = get_extents(slice).size(); + if (!((bbox_size.x() > length_thresh_well_supported && bbox_size.y() > length_thresh_well_supported)) + && g_config_support_sharp_tails) { + layer->sharp_tails.push_back(slice); + layer->sharp_tails_height.insert({ &slice, layer->height }); + } + } + continue; } - order = new_order; - } - if (elem.to_buildplate || (elem.to_model_gracious && intersection(parent.influence_area, volumes.getPlaceableAreas(radius, layer_idx, throw_on_cancel)).empty())) { - // error case - // it is normal that we wont be able to find a new area at some point in time if we wont be able to reach layer 0 aka have to connect with the model - insertSetting({ AvoidanceType::Fast, fast_speed, !increase_radius, !no_error, elem.use_min_xy_dist, move }, true); - } - if (elem.distance_to_top < elem.dont_move_until && elem.can_use_safe_radius) // only do not move when holes would be avoided in every case. - // Only do not move when already in a no hole avoidance with the regular xy distance. - insertSetting({ AvoidanceType::Slow, 0, increase_radius, no_error, !use_min_radius, !move }, false); - - Polygons inc_wo_collision; - // Check whether it is faster to calculate the area increased with the fast speed independently from the slow area, or time could be saved by reusing the slow area to calculate the fast one. - // Calculated by comparing the steps saved when calcualting idependently with the saved steps when not. - bool offset_independant_faster = radius / safe_movement_distance - int(config.maximum_move_distance + extra_speed < radius + safe_movement_distance) > - round_up_divide((extra_speed + extra_slow_speed + config.maximum_move_distance_slow), safe_movement_distance); - for (const AreaIncreaseSettings &settings : order) { - if (settings.move) { - if (offset_slow.empty() && (settings.increase_speed == slow_speed || ! offset_independant_faster)) { - // offsetting in 2 steps makes our offsetted area rounder preventing (rounding) errors created by to pointy areas. At this point one can see that the Polygons class - // was never made for precision in the single digit micron range. - offset_slow = safe_offset_inc(parent.influence_area, extra_speed + extra_slow_speed + config.maximum_move_distance_slow, - wall_restriction, safe_movement_distance, offset_independant_faster ? safe_movement_distance + radius : 0, 2); -#ifdef TREESUPPORT_DEBUG_SVG - SVG::export_expolygons(debug_out_path("treesupport-increase_areas_one_layer-slow-%d-%ld.svg", layer_idx, int(merging_area_idx)), - { { { union_ex(wall_restriction) }, { "wall_restricrictions", "gray", 0.5f } }, - { { union_ex(offset_slow) }, { "offset_slow", "red", "black", "", scaled(0.1f), 0.5f } } }); -#endif // TREESUPPORT_DEBUG_SVG + + Layer* lower_layer = layer->lower_layer; + coordf_t lower_layer_offset = layer_nr < enforce_support_layers ? -0.15 * extrusion_width : (float)lower_layer->height / tan(threshold_rad); + coordf_t support_offset_scaled = scale_(lower_layer_offset); + // Filter out areas whose diameter that is smaller than extrusion_width. Do not use offset2() for this purpose! + ExPolygons lower_polys; + for (const ExPolygon& expoly : lower_layer->lslices) { + if (!offset_ex(expoly, -extrusion_width_scaled / 2).empty()) { + lower_polys.emplace_back(expoly); } - if (offset_fast.empty() && settings.increase_speed != slow_speed) { - if (offset_independant_faster) - offset_fast = safe_offset_inc(parent.influence_area, extra_speed + config.maximum_move_distance, - wall_restriction, safe_movement_distance, offset_independant_faster ? safe_movement_distance + radius : 0, 1); - else { - const coord_t delta_slow_fast = config.maximum_move_distance - (config.maximum_move_distance_slow + extra_slow_speed); - offset_fast = safe_offset_inc(offset_slow, delta_slow_fast, wall_restriction, safe_movement_distance, safe_movement_distance + radius, offset_independant_faster ? 2 : 1); + } + ExPolygons curr_polys; + std::vector curr_poly_ptrs; + for (const ExPolygon& expoly : layer->lslices) { + if (!offset_ex(expoly, -extrusion_width_scaled / 2).empty()) { + curr_polys.emplace_back(expoly); + curr_poly_ptrs.emplace_back(&expoly); + } + } + + // normal overhang + ExPolygons lower_layer_offseted = offset_ex(lower_polys, support_offset_scaled, SUPPORT_SURFACES_OFFSET_PARAMETERS); + ExPolygons overhang_areas = diff_ex(curr_polys, lower_layer_offseted); + + overhang_areas.erase(std::remove_if(overhang_areas.begin(), overhang_areas.end(), + [extrusion_width_scaled](ExPolygon& area) { return offset_ex(area, -0.1 * extrusion_width_scaled).empty(); }), + overhang_areas.end()); + + + if (is_auto(stype) && g_config_support_sharp_tails) + { + // BBS detect sharp tail + for (const ExPolygon* expoly : curr_poly_ptrs) { + bool is_sharp_tail = false; + // 1. nothing below + // this is a sharp tail region if it's small but non-ignorable + if (!overlaps(offset_ex(*expoly, 0.5 * extrusion_width_scaled), lower_polys)) { + is_sharp_tail = expoly->area() < area_thresh_well_supported && !offset_ex(*expoly, -0.1 * extrusion_width_scaled).empty(); } -#ifdef TREESUPPORT_DEBUG_SVG - SVG::export_expolygons(debug_out_path("treesupport-increase_areas_one_layer-fast-%d-%ld.svg", layer_idx, int(merging_area_idx)), - { { { union_ex(wall_restriction) }, { "wall_restricrictions", "gray", 0.5f } }, - { { union_ex(offset_fast) }, { "offset_fast", "red", "black", "", scaled(0.1f), 0.5f } } }); -#endif // TREESUPPORT_DEBUG_SVG + + if (is_sharp_tail) { + ExPolygons overhang = diff_ex({ *expoly }, lower_polys); + layer->sharp_tails.push_back(*expoly); + layer->sharp_tails_height.insert({ expoly, layer->height }); + append(overhang_areas, overhang); + + if (!overhang.empty()) { + has_sharp_tails = true; +#ifdef SUPPORT_TREE_DEBUG_TO_SVG + SVG svg(format("SVG/sharp_tail_orig_%.02f.svg", layer->print_z), m_object->bounding_box()); + if (svg.is_opened()) svg.draw(overhang, "red"); +#endif + } + } } } - std::optional result; - inc_wo_collision.clear(); - if (!settings.no_error) { - // ERROR CASE - // if the area becomes for whatever reason something that clipper sees as a line, offset would stop working, so ensure that even if if wrongly would be a line, it still actually has an area that can be increased - Polygons lines_offset = offset(to_polylines(parent.influence_area), scaled(0.005), jtMiter, 1.2); - Polygons base_error_area = union_(parent.influence_area, lines_offset); - result = increase_single_area(volumes, config, settings, layer_idx, parent, - base_error_area, to_bp_data, to_model_data, inc_wo_collision, (config.maximum_move_distance + extra_speed) * 1.5, mergelayer); -#ifdef TREE_SUPPORT_SHOW_ERRORS - BOOST_LOG_TRIVIAL(error) -#else // TREE_SUPPORT_SHOW_ERRORS - BOOST_LOG_TRIVIAL(warning) -#endif // TREE_SUPPORT_SHOW_ERRORS - << "Influence area could not be increased! Data about the Influence area: " - "Radius: " << radius << " at layer: " << layer_idx - 1 << " NextTarget: " << elem.layer_idx << " Distance to top: " << elem.distance_to_top << - " Elephant foot increases " << elem.elephant_foot_increases << " use_min_xy_dist " << elem.use_min_xy_dist << " to buildplate " << elem.to_buildplate << - " gracious " << elem.to_model_gracious << " safe " << elem.can_use_safe_radius << " until move " << elem.dont_move_until << " \n " - "Parent " << &parent << ": Radius: " << support_element_collision_radius(config, parent.state) << " at layer: " << layer_idx << " NextTarget: " << parent.state.layer_idx << - " Distance to top: " << parent.state.distance_to_top << " Elephant foot increases " << parent.state.elephant_foot_increases << " use_min_xy_dist " << parent.state.use_min_xy_dist << - " to buildplate " << parent.state.to_buildplate << " gracious " << parent.state.to_model_gracious << " safe " << parent.state.can_use_safe_radius << " until move " << parent.state.dont_move_until; - tree_supports_show_error("Potentially lost branch!"sv, true); -#ifdef TREE_SUPPORTS_TRACK_LOST - if (result) - result->lost = true; -#endif // TREE_SUPPORTS_TRACK_LOST - } else - result = increase_single_area(volumes, config, settings, layer_idx, parent, - settings.increase_speed == slow_speed ? offset_slow : offset_fast, to_bp_data, to_model_data, inc_wo_collision, 0, mergelayer); - - if (result) { - elem = *result; - radius = support_element_collision_radius(config, elem); - elem.last_area_increase = settings; - add = true; - // do not merge if the branch should not move or the priority has to be to get farther away from the model. - bypass_merge = !settings.move || (settings.use_min_distance && elem.distance_to_top < config.tip_layers); - if (settings.move) - elem.dont_move_until = 0; - else - elem.result_on_layer = parent.state.result_on_layer; - - elem.can_use_safe_radius = settings.type != AvoidanceType::Fast; - - if (!settings.use_min_distance) - elem.use_min_xy_dist = false; - if (!settings.no_error) -#ifdef TREE_SUPPORT_SHOW_ERRORS - BOOST_LOG_TRIVIAL(error) -#else // TREE_SUPPORT_SHOW_ERRORS - BOOST_LOG_TRIVIAL(info) -#endif // TREE_SUPPORT_SHOW_ERRORS - << "Trying to keep area by moving faster than intended: Success"; - break; - } else if (!settings.no_error) - BOOST_LOG_TRIVIAL(error) << "Trying to keep area by moving faster than intended: FAILURE! WRONG BRANCHES LIKLY!"; - } - if (add) { - // Union seems useless, but some rounding errors somewhere can cause to_bp_data to be slightly bigger than it should be. - assert(! inc_wo_collision.empty() || ! to_bp_data.empty() || ! to_model_data.empty()); - Polygons max_influence_area = safe_union( - diff_clipped(inc_wo_collision, volumes.getCollision(radius, layer_idx - 1, elem.use_min_xy_dist)), - safe_union(to_bp_data, to_model_data)); - merging_area.state = elem; - assert(!max_influence_area.empty()); - merging_area.set_bbox(get_extents(max_influence_area)); - merging_area.areas.influence_areas = std::move(max_influence_area); - if (! bypass_merge) { - if (elem.to_buildplate) - merging_area.areas.to_bp_areas = std::move(to_bp_data); - if (config.support_rests_on_model) - merging_area.areas.to_model_areas = std::move(to_model_data); + SupportLayer* ts_layer = m_object->get_support_layer(layer_nr + m_raft_layers); + for (ExPolygon& poly : overhang_areas) { + if (offset_ex(poly, -0.1 * extrusion_width_scaled).empty()) continue; + ts_layer->overhang_areas.emplace_back(poly); + + // check cantilever + { + auto cluster_boundary_ex = intersection_ex(poly, offset_ex(lower_layer->lslices, scale_(0.5))); + Polygons cluster_boundary = to_polygons(cluster_boundary_ex); + if (cluster_boundary.empty()) continue; + double dist_max = 0; + for (auto& pt : poly.contour.points) { + double dist_pt = std::numeric_limits::max(); + for (auto& ply : cluster_boundary) { + double d = ply.distance_to(pt); + dist_pt = std::min(dist_pt, d); + } + dist_max = std::max(dist_max, dist_pt); + } + if (dist_max > scale_(3)) { // is cantilever if the farmost point is larger than 3mm away from base + max_cantilever_dist = std::max(max_cantilever_dist, dist_max); + layer->cantilevers.emplace_back(poly); + BOOST_LOG_TRIVIAL(debug) << "found a cantilever cluster. layer_nr=" << layer_nr << dist_max; + has_cantilever = true; + } + } } - } else { - // If the bottom most point of a branch is set, later functions will assume that the position is valid, and ignore it. - // But as branches connecting with the model that are to small have to be culled, the bottom most point has to be not set. - // A point can be set on the top most tip layer (maybe more if it should not move for a few layers). - parent.state.result_on_layer_reset(); - parent.state.to_model_gracious = false; -#ifdef TREE_SUPPORTS_TRACK_LOST - parent.state.verylost = true; -#endif // TREE_SUPPORTS_TRACK_LOST } - - throw_on_cancel(); } - }, tbb::simple_partitioner()); -} + ); // end tbb::parallel_for -[[nodiscard]] static SupportElementState merge_support_element_states( - const SupportElementState &first, const SupportElementState &second, const Point &next_position, const coord_t layer_idx, - const TreeSupportSettings &config) -{ - SupportElementState out; - out.next_position = next_position; - out.layer_idx = layer_idx; - out.use_min_xy_dist = first.use_min_xy_dist || second.use_min_xy_dist; - out.supports_roof = first.supports_roof || second.supports_roof; - out.dont_move_until = std::max(first.dont_move_until, second.dont_move_until); - out.can_use_safe_radius = first.can_use_safe_radius || second.can_use_safe_radius; - out.missing_roof_layers = std::min(first.missing_roof_layers, second.missing_roof_layers); - out.skip_ovalisation = false; - if (first.target_height > second.target_height) { - out.target_height = first.target_height; - out.target_position = first.target_position; - } else { - out.target_height = second.target_height; - out.target_position = second.target_position; - } - out.effective_radius_height = std::max(first.effective_radius_height, second.effective_radius_height); - out.distance_to_top = std::max(first.distance_to_top, second.distance_to_top); + BOOST_LOG_TRIVIAL(info) << "max_cantilever_dist=" << max_cantilever_dist; + + // check if the sharp tails should be extended higher + if (is_auto(stype) && g_config_support_sharp_tails && !detect_first_sharp_tail_only) { + for (size_t layer_nr = 0; layer_nr < m_object->layer_count(); layer_nr++) { + if (m_object->print()->canceled()) + break; - out.to_buildplate = first.to_buildplate && second.to_buildplate; - out.to_model_gracious = first.to_model_gracious && second.to_model_gracious; // valid as we do not merge non-gracious with gracious + Layer* layer = m_object->get_layer(layer_nr); + SupportLayer* ts_layer = m_object->get_support_layer(layer_nr + m_raft_layers); + Layer* lower_layer = layer->lower_layer; + if (!lower_layer) + continue; - out.elephant_foot_increases = 0; - if (config.bp_radius_increase_per_layer > 0) { - coord_t foot_increase_radius = std::abs(std::max(support_element_collision_radius(config, second), support_element_collision_radius(config, first)) - support_element_collision_radius(config, out)); - // elephant_foot_increases has to be recalculated, as when a smaller tree with a larger elephant_foot_increases merge with a larger branch - // the elephant_foot_increases may have to be lower as otherwise the radius suddenly increases. This results often in a non integer value. - out.elephant_foot_increases = foot_increase_radius / (config.bp_radius_increase_per_layer - config.branch_radius_increase_per_layer); - } + // BBS detect sharp tail + const ExPolygons& lower_layer_sharptails = lower_layer->sharp_tails; + const auto& lower_layer_sharptails_height = lower_layer->sharp_tails_height; + for (ExPolygon& expoly : layer->lslices) { + bool is_sharp_tail = false; + float accum_height = layer->height; + do { + // 2. something below + // check whether this is above a sharp tail region. + + // 2.1 If no sharp tail below, this is considered as common region. + ExPolygons supported_by_lower = intersection_ex({ expoly }, lower_layer_sharptails); + if (supported_by_lower.empty()) { + is_sharp_tail = false; + break; + } - // set last settings to the best out of both parents. If this is wrong, it will only cause a small performance penalty instead of weird behavior. - out.last_area_increase = { - std::min(first.last_area_increase.type, second.last_area_increase.type), - std::min(first.last_area_increase.increase_speed, second.last_area_increase.increase_speed), - first.last_area_increase.increase_radius || second.last_area_increase.increase_radius, - first.last_area_increase.no_error || second.last_area_increase.no_error, - first.last_area_increase.use_min_distance && second.last_area_increase.use_min_distance, - first.last_area_increase.move || second.last_area_increase.move }; + // 2.2 If sharp tail below, check whether it support this region enough. +#if 0 + // judge by area isn't reliable, failure cases include 45 degree rotated cube + float supported_area = area(supported_by_lower); + if (supported_area > area_thresh_well_supported) { + is_sharp_tail = false; + break; + } +#endif + BoundingBox bbox = get_extents(supported_by_lower); + if (bbox.size().x() > length_thresh_well_supported && bbox.size().y() > length_thresh_well_supported) { + is_sharp_tail = false; + break; + } - return out; -} + // 2.3 check whether sharp tail exceed the max height + for (const auto& lower_sharp_tail_height : lower_layer_sharptails_height) { + if (lower_sharp_tail_height.first->overlaps(expoly)) { + accum_height += lower_sharp_tail_height.second; + break; + } + } + if (accum_height > sharp_tail_max_support_height) { + is_sharp_tail = false; + break; + } -static bool merge_influence_areas_two_elements( - const TreeModelVolumes &volumes, const TreeSupportSettings &config, const LayerIndex layer_idx, - SupportElementMerging &dst, SupportElementMerging &src) -{ - // Don't merge gracious with a non gracious area as bad placement could negatively impact reliability of the whole subtree. - const bool merging_gracious_and_non_gracious = dst.state.to_model_gracious != src.state.to_model_gracious; - // Could cause some issues with the increase of one area, as it is assumed that if the smaller is increased - // by the delta to the larger it is engulfed by it already. But because a different collision - // may be removed from the in draw_area() generated circles, this assumption could be wrong. - const bool merging_min_and_regular_xy = dst.state.use_min_xy_dist != src.state.use_min_xy_dist; - - if (merging_gracious_and_non_gracious || merging_min_and_regular_xy) - return false; + // 2.4 if the area grows fast than threshold, it get connected to other part or + // it has a sharp slop and will be auto supported. + ExPolygons new_overhang_expolys = diff_ex({ expoly }, lower_layer_sharptails); + if ((get_extents(new_overhang_expolys).size() - get_extents(lower_layer_sharptails).size()).both_comp(Point(scale_(5), scale_(5)), ">") || !offset_ex(new_overhang_expolys, -5.0 * extrusion_width_scaled).empty()) { + is_sharp_tail = false; + break; + } - const bool dst_radius_bigger = support_element_collision_radius(config, dst.state) > support_element_collision_radius(config, src.state); - const SupportElementMerging &smaller_rad = dst_radius_bigger ? src : dst; - const SupportElementMerging &bigger_rad = dst_radius_bigger ? dst : src; - const coord_t real_radius_delta = std::abs(support_element_radius(config, bigger_rad.state) - support_element_radius(config, smaller_rad.state)); - { - // Testing intersection of bounding boxes. - // Expand the smaller radius branch bounding box to match the lambda intersect_small_with_bigger() below. - // Because the lambda intersect_small_with_bigger() applies a rounded offset, a snug offset of the bounding box - // is sufficient. On the other side, if a mitered offset was used by the lambda, - // the bounding box expansion would have to account for the mitered extension of the sharp corners. - Eigen::AlignedBox smaller_bbox = smaller_rad.bbox(); - smaller_bbox.min() -= Point{ real_radius_delta, real_radius_delta }; - smaller_bbox.max() += Point{ real_radius_delta, real_radius_delta }; - if (! smaller_bbox.intersects(bigger_rad.bbox())) - return false; - } + // 2.5 mark the expoly as sharptail + is_sharp_tail = true; + } while (0); + + if (is_sharp_tail) { + ExPolygons overhang = diff_ex({ expoly }, lower_layer->lslices); + layer->sharp_tails.push_back(expoly); + layer->sharp_tails_height.insert({ &expoly, accum_height }); + append(ts_layer->overhang_areas, overhang); + + if (!overhang.empty()) + has_sharp_tails = true; +#ifdef SUPPORT_TREE_DEBUG_TO_SVG + SVG svg(format("SVG/sharp_tail_%.02f.svg",layer->print_z), m_object->bounding_box()); + if (svg.is_opened()) svg.draw(overhang, "red"); +#endif + } - // Accumulator of a radius increase of a "to model" branch by merging in a "to build plate" branch. - coord_t increased_to_model_radius = 0; - const bool merging_to_bp = dst.state.to_buildplate && src.state.to_buildplate; - if (! merging_to_bp) { - // Get the real radius increase as the user does not care for the collision model. - if (dst.state.to_buildplate != src.state.to_buildplate) { - // Merging a "to build plate" branch with a "to model" branch. - // Don't allow merging a thick "to build plate" branch into a thinner "to model" branch. - const coord_t rdst = support_element_radius(config, dst.state); - const coord_t rsrc = support_element_radius(config, src.state); - if (dst.state.to_buildplate) { - if (rsrc < rdst) - increased_to_model_radius = src.state.increased_to_model_radius + rdst - rsrc; - } else { - if (rsrc > rdst) - increased_to_model_radius = dst.state.increased_to_model_radius + rsrc - rdst; - } - if (increased_to_model_radius > config.max_to_model_radius_increase) - return false; + } + } + } + + // group overhang clusters + for (size_t layer_nr = 0; layer_nr < m_object->layer_count(); layer_nr++) { + if (m_object->print()->canceled()) + break; + SupportLayer* ts_layer = m_object->get_support_layer(layer_nr + m_raft_layers); + Layer* layer = m_object->get_layer(layer_nr); + for (auto& overhang : ts_layer->overhang_areas) { + OverhangCluster* cluster = find_and_insert_cluster(overhangClusters, overhang, layer_nr, extrusion_width_scaled); + if (overlaps({ overhang },layer->cantilevers)) + cluster->is_cantilever = true; } - // if a merge could place a stable branch on unstable ground, would be increasing the radius further - // than allowed to when merging to model and to_bp trees or would merge to model before it is known - // they will even been drawn the merge is skipped - if (! dst.state.supports_roof && ! src.state.supports_roof && - std::max(src.state.distance_to_top, dst.state.distance_to_top) < config.min_dtt_to_model) - return false; } - // Area of the bigger radius is used to ensure correct placement regarding the relevant avoidance, - // so if that would change an invalid area may be created. - if (! bigger_rad.state.can_use_safe_radius && smaller_rad.state.can_use_safe_radius) - return false; + auto enforcers = m_object->slice_support_enforcers(); + auto blockers = m_object->slice_support_blockers(); + m_object->project_and_append_custom_facets(false, EnforcerBlockerType::ENFORCER, enforcers); + m_object->project_and_append_custom_facets(false, EnforcerBlockerType::BLOCKER, blockers); + if (is_auto(stype) && config_remove_small_overhangs) { + if (blockers.size() < m_object->layer_count()) + blockers.resize(m_object->layer_count()); + for (auto& cluster : overhangClusters) { + // 3. check whether the small overhang is sharp tail + cluster.is_sharp_tail = false; + for (size_t layer_id = cluster.min_layer; layer_id <= cluster.max_layer; layer_id++) { + Layer* layer = m_object->get_layer(layer_id); + if (overlaps(layer->sharp_tails, cluster.merged_poly)) { + cluster.is_sharp_tail = true; + break; + } + } - // the bigger radius is used to verify that the area is still valid after the increase with the delta. - // If there were a point where the big influence area could be valid with can_use_safe_radius - // the element would already be can_use_safe_radius. - // the smaller radius, which gets increased by delta may reach into the area where use_min_xy_dist is no longer required. - const bool use_min_radius = bigger_rad.state.use_min_xy_dist && smaller_rad.state.use_min_xy_dist; - - // The idea is that the influence area with the smaller collision radius is increased by the radius difference. - // If this area has any intersections with the influence area of the larger collision radius, a branch (of the larger collision radius) placed in this intersection, has already engulfed the branch of the smaller collision radius. - // Because of this a merge may happen even if the influence areas (that represent possible center points of branches) do not intersect yet. - // Remember that collision radius <= real radius as otherwise this assumption would be false. - const coord_t smaller_collision_radius = support_element_collision_radius(config, smaller_rad.state); - const Polygons &collision = volumes.getCollision(smaller_collision_radius, layer_idx - 1, use_min_radius); - auto intersect_small_with_bigger = [real_radius_delta, smaller_collision_radius, &collision, &config](const Polygons &small, const Polygons &bigger) { - return intersection( - safe_offset_inc( - small, real_radius_delta, collision, - // -3 avoids possible rounding errors - 2 * (config.xy_distance + smaller_collision_radius - 3), 0, 0), - bigger); - }; -//#define TREES_MERGE_RATHER_LATER - Polygons intersect = -#ifdef TREES_MERGE_RATHER_LATER - intersection( -#else - intersect_small_with_bigger( + if (!cluster.is_sharp_tail && !cluster.is_cantilever) { + // 2. check overhang cluster size is smaller than 3.0 * fw_scaled + auto erode1 = offset_ex(cluster.merged_poly, -1 * extrusion_width_scaled); + Point bbox_sz = get_extents(erode1).size(); + if (bbox_sz.x() < 2 * extrusion_width_scaled || bbox_sz.y() < 2 * extrusion_width_scaled) { + cluster.is_small_overhang = true; + } + } + +#ifdef SUPPORT_TREE_DEBUG_TO_SVG + const Layer* layer1 = m_object->get_layer(cluster.min_layer); + BoundingBox bbox = cluster.merged_bbox; + bbox.merge(get_extents(layer1->lslices)); + SVG svg(format("SVG/overhangCluster_%s-%s_%s-%s_tail=%s_cantilever=%s_small=%s.svg", + cluster.min_layer, cluster.max_layer, layer1->print_z, m_object->get_layer(cluster.max_layer)->print_z, + cluster.is_sharp_tail, cluster.is_cantilever, cluster.is_small_overhang), bbox); + if (svg.is_opened()) { + svg.draw(layer1->lslices, "red"); + svg.draw(cluster.merged_poly, "blue"); + svg.draw_text(bbox.min + Point(scale_(0), scale_(2)), "lslices", "red", 2); + svg.draw_text(bbox.min + Point(scale_(0), scale_(2)), "overhang", "blue", 2); + } #endif - merging_to_bp ? smaller_rad.areas.to_bp_areas : smaller_rad.areas.to_model_areas, - merging_to_bp ? bigger_rad.areas.to_bp_areas : bigger_rad.areas.to_model_areas); - const auto _tiny_area_threshold = tiny_area_threshold(); - // dont use empty as a line is not empty, but for this use-case it very well may be (and would be one layer down as union does not keep lines) - // check if the overlap is large enough (Small ares tend to attract rounding errors in clipper). - if (area(intersect) <= _tiny_area_threshold) - return false; + if (!cluster.is_small_overhang) + continue; - // While 0.025 was guessed as enough, i did not have reason to change it. - if (area(offset(intersect, scaled(-0.025), jtMiter, 1.2)) <= _tiny_area_threshold) - return false; + for (auto it = cluster.layer_overhangs.begin(); it != cluster.layer_overhangs.end(); it++) { + int layer_nr = it->first; + auto p_overhang = it->second; + blockers[layer_nr].push_back(p_overhang->contour); + } + } + } -#ifdef TREES_MERGE_RATHER_LATER - intersect = - intersect_small_with_bigger( - merging_to_bp ? smaller_rad.areas.to_bp_areas : smaller_rad.areas.to_model_areas, - merging_to_bp ? bigger_rad.areas.to_bp_areas : bigger_rad.areas.to_model_areas); -#endif + has_overhangs = false; + for (int layer_nr = 0; layer_nr < m_object->layer_count(); layer_nr++) { + if (m_object->print()->canceled()) + break; - // Do the actual merge now that the branches are confirmed to be able to intersect. - // calculate which point is closest to the point of the last merge (or tip center if no merge above it has happened) - // used at the end to estimate where to best place the branch on the bottom most layer - // could be replaced with a random point inside the new area - Point new_pos = move_inside_if_outside(intersect, dst.state.next_position); - - SupportElementState new_state = merge_support_element_states(dst.state, src.state, new_pos, layer_idx - 1, config); - new_state.increased_to_model_radius = increased_to_model_radius == 0 ? - // increased_to_model_radius was not set yet. Propagate maximum. - std::max(dst.state.increased_to_model_radius, src.state.increased_to_model_radius) : - increased_to_model_radius; - - // Rather unioning with "intersect" due to some rounding errors. - Polygons influence_areas = safe_union( - intersect_small_with_bigger(smaller_rad.areas.influence_areas, bigger_rad.areas.influence_areas), - intersect); - - Polygons to_model_areas; - if (merging_to_bp && config.support_rests_on_model) - to_model_areas = new_state.to_model_gracious ? - // Rather unioning with "intersect" due to some rounding errors. - safe_union( - intersect_small_with_bigger(smaller_rad.areas.to_model_areas, bigger_rad.areas.to_model_areas), - intersect) : - influence_areas; - - dst.parents.insert(dst.parents.end(), src.parents.begin(), src.parents.end()); - dst.state = new_state; - dst.areas.influence_areas = std::move(influence_areas); - dst.areas.to_bp_areas.clear(); - dst.areas.to_model_areas.clear(); - if (merging_to_bp) { - dst.areas.to_bp_areas = std::move(intersect); - if (config.support_rests_on_model) - dst.areas.to_model_areas = std::move(to_model_areas); - } else - dst.areas.to_model_areas = std::move(intersect); - // Update the bounding box. - BoundingBox bbox(get_extents(dst.areas.influence_areas)); - bbox.merge(get_extents(dst.areas.to_bp_areas)); - bbox.merge(get_extents(dst.areas.to_model_areas)); - dst.set_bbox(bbox); - // Clear the source data. - src.areas.clear(); - src.parents.clear(); - return true; -} + SupportLayer* ts_layer = m_object->get_support_layer(layer_nr + m_raft_layers); + auto layer = m_object->get_layer(layer_nr); + auto lower_layer = layer->lower_layer; + if (support_critical_regions_only && is_auto(stype)) { + ts_layer->overhang_areas.clear(); + if (lower_layer == nullptr) + ts_layer->overhang_areas = layer->sharp_tails; + else + ts_layer->overhang_areas = diff_ex(layer->sharp_tails, lower_layer->lslices); -/*! - * \brief Merges Influence Areas if possible. - * - * Branches which do overlap have to be merged. This helper merges all elements in input with the elements into reduced_new_layer. - * Elements in input_aabb are merged together if possible, while elements reduced_new_layer_aabb are not checked against each other. - * - * \param reduced_aabb[in,out] The already processed elements. - * \param input_aabb[in] Not yet processed elements - * \param to_bp_areas[in] The Elements of the current Layer that will reach the buildplate. Value is the influence area where the center of a circle of support may be placed. - * \param to_model_areas[in] The Elements of the current Layer that do not have to reach the buildplate. Also contains main as every element that can reach the buildplate is not forced to. - * Value is the influence area where the center of a circle of support may be placed. - * \param influence_areas[in] The influence areas without avoidance removed. - * \param insert_bp_areas[out] Elements to be inserted into the main dictionary after the Helper terminates. - * \param insert_model_areas[out] Elements to be inserted into the secondary dictionary after the Helper terminates. - * \param insert_influence[out] Elements to be inserted into the dictionary containing the largest possibly valid influence area (ignoring if the area may not be there because of avoidance) - * \param erase[out] Elements that should be deleted from the above dictionaries. - * \param layer_idx[in] The Index of the current Layer. - */ + append(ts_layer->overhang_areas, layer->cantilevers); + } -static SupportElementMerging* merge_influence_areas_leaves( - const TreeModelVolumes &volumes, const TreeSupportSettings &config, const LayerIndex layer_idx, - SupportElementMerging * const dst_begin, SupportElementMerging *dst_end) -{ - // Merging at the lowest level of the AABB tree. Checking one against each other, O(n^2). - assert(dst_begin < dst_end); - for (SupportElementMerging *i = dst_begin; i + 1 < dst_end;) { - for (SupportElementMerging *j = i + 1; j != dst_end;) - if (merge_influence_areas_two_elements(volumes, config, layer_idx, *i, *j)) { - // i was merged with j, j is empty. - if (j != -- dst_end) - *j = std::move(*dst_end); - goto merged; - } else - ++ j; - // not merged - ++ i; - merged: - ; - } - return dst_end; -} + if (layer_nr < blockers.size()) { + Polygons& blocker = blockers[layer_nr]; + // Arthur: union_ is a must because after mirroring, the blocker polygons are in left-hand coordinates, ie clockwise, + // which are not valid polygons, and will be removed by offset_ex. union_ can make these polygons right. + ts_layer->overhang_areas = diff_ex(ts_layer->overhang_areas, offset_ex(union_(blocker), scale_(radius_sample_resolution))); + } -static SupportElementMerging* merge_influence_areas_two_sets( - const TreeModelVolumes &volumes, const TreeSupportSettings &config, const LayerIndex layer_idx, - SupportElementMerging * const dst_begin, SupportElementMerging * dst_end, - SupportElementMerging * src_begin, SupportElementMerging * const src_end) -{ - // Merging src into dst. - // Areas of src should not overlap with areas of another elements of src. - // Areas of dst should not overlap with areas of another elements of dst. - // The memory from dst_begin to src_end is reserved for the merging operation, - // src follows dst. - assert(src_begin < src_end); - assert(dst_begin < dst_end); - assert(dst_end <= src_begin); - for (SupportElementMerging *src = src_begin; src != src_end; ++ src) { - SupportElementMerging *dst = dst_begin; - SupportElementMerging *merged = nullptr; - for (; dst != dst_end; ++ dst) - if (merge_influence_areas_two_elements(volumes, config, layer_idx, *dst, *src)) { - merged = dst ++; - if (src != src_begin) - // Compactify src. - *src = std::move(*src_begin); - ++ src_begin; - break; + if (max_bridge_length > 0 && ts_layer->overhang_areas.size() > 0 && lower_layer) { + // do not break bridge for normal part in TreeHybrid + bool break_bridge = !(support_style == smsTreeHybrid && area(ts_layer->overhang_areas) > m_support_params.thresh_big_overhang); + m_object->remove_bridges_from_contacts(lower_layer, layer, extrusion_width_scaled, &ts_layer->overhang_areas, max_bridge_length, break_bridge); + } + + for (auto &area : ts_layer->overhang_areas) { + ts_layer->overhang_types.emplace(&area, SupportLayer::Detected); + } + // enforcers now follow same logic as normal support. See STUDIO-3692 + if (layer_nr < enforcers.size() && lower_layer) { + float no_interface_offset = std::accumulate(layer->regions().begin(), layer->regions().end(), FLT_MAX, + [](float acc, const LayerRegion* layerm) { return std::min(acc, float(layerm->flow(frExternalPerimeter).scaled_width())); }); + Polygons lower_layer_polygons = (layer_nr == 0) ? Polygons() : to_polygons(lower_layer->lslices); + Polygons& enforcer = enforcers[layer_nr]; + if (!enforcer.empty()) { + ExPolygons enforcer_polygons = diff_ex(intersection_ex(layer->lslices, enforcer), + // Inflate just a tiny bit to avoid intersection of the overhang areas with the object. + expand(lower_layer_polygons, 0.05f * no_interface_offset, SUPPORT_SURFACES_OFFSET_PARAMETERS)); + append(ts_layer->overhang_areas, enforcer_polygons); + ts_layer->overhang_types.emplace(&ts_layer->overhang_areas.back(), SupportLayer::Enforced); } - for (; dst != dst_end;) - if (merge_influence_areas_two_elements(volumes, config, layer_idx, *merged, *dst)) { - // Compactify dst. - if (dst != -- dst_end) - *dst = std::move(*dst_end); - } else - ++ dst; - } - // Compactify src elements that were not merged with dst to the end of dst. - assert(dst_end <= src_begin); - if (dst_end == src_begin) - dst_end = src_end; - else - while (src_begin != src_end) - *dst_end ++ = std::move(*src_begin ++); + } - return dst_end; -} + if (!ts_layer->overhang_areas.empty()) has_overhangs = true; + if (!layer->cantilevers.empty()) has_cantilever = true; + } -/*! - * \brief Merges Influence Areas at one layer if possible. - * - * Branches which do overlap have to be merged. This manages the helper and uses a divide and conquer approach to parallelize this problem. This parallelization can at most accelerate the merging by a factor of 2. - * - * \param to_bp_areas[in] The Elements of the current Layer that will reach the buildplate. - * Value is the influence area where the center of a circle of support may be placed. - * \param to_model_areas[in] The Elements of the current Layer that do not have to reach the buildplate. Also contains main as every element that can reach the buildplate is not forced to. - * Value is the influence area where the center of a circle of support may be placed. - * \param influence_areas[in] The Elements of the current Layer without avoidances removed. This is the largest possible influence area for this layer. - * Value is the influence area where the center of a circle of support may be placed. - * \param layer_idx[in] The current layer. - */ -static void merge_influence_areas( - const TreeModelVolumes &volumes, - const TreeSupportSettings &config, - const LayerIndex layer_idx, - std::vector &influence_areas, - std::function throw_on_cancel) -{ - const size_t input_size = influence_areas.size(); - if (input_size == 0) - return; +#ifdef SUPPORT_TREE_DEBUG_TO_SVG + for (const SupportLayer* layer : m_object->support_layers()) { + if (layer->overhang_areas.empty() && (blockers.size()<=layer->id() || blockers[layer->id()].empty())) + continue; - // Merging by divide & conquer. - // The majority of time is consumed by Clipper polygon operations, intersection is accelerated by bounding boxes. - // Sorting input into an AABB tree helps to perform most of the intersections at first iterations, - // thus reducing computation when merging larger subtrees. - // The actual merge logic is found in merge_influence_areas_two_sets. - - // Build an AABB tree over the influence areas. - //FIXME A full tree does not need to be built, the lowest level branches will be always bucketed. - // However the additional time consumed is negligible. - AABBTreeIndirect::Tree<2, coord_t> tree; - // Sort influence_areas in place. - tree.build_modify_input(influence_areas); - - throw_on_cancel(); - - // Prepare the initial buckets as ranges of influence areas. The initial buckets contain power of 2 influence areas to follow - // the branching of the AABB tree. - // Vectors of ranges of influence areas, following the branching of the AABB tree: - std::vector> buckets; - // Initial number of buckets for 1st round of merging. - size_t num_buckets_initial; - { - // How many buckets per first merge iteration? - const size_t num_threads = tbb::this_task_arena::max_concurrency(); - // 4 buckets per thread if possible, - const size_t num_buckets_min = (input_size + 2) / 4; - // 2 buckets per thread otherwise. - const size_t num_buckets_max = input_size / 2; - num_buckets_initial = num_buckets_min >= num_threads ? num_buckets_min : num_buckets_max; - const size_t bucket_size = num_buckets_min >= num_threads ? 4 : 2; - // Fill in the buckets. - SupportElementMerging *it = influence_areas.data(); - // Reserve one more bucket to keep a single influence area which will not be merged in the first iteration. - buckets.reserve(num_buckets_initial + 1); - for (size_t i = 0; i < num_buckets_initial; ++ i, it += bucket_size) - buckets.emplace_back(std::make_pair(it, it + bucket_size)); - SupportElementMerging *it_end = influence_areas.data() + influence_areas.size(); - if (buckets.back().second >= it_end) { - // Last bucket is less than size 4, but bigger than size 1. - buckets.back().second = std::min(buckets.back().second, it_end); - } else { - // Last bucket is size 1, it will not be merged in the first iteration. - assert(it + 1 == it_end); - buckets.emplace_back(std::make_pair(it, it_end)); - } - } - - // 1st merge iteration, merge one with each other. - tbb::parallel_for(tbb::blocked_range(0, num_buckets_initial), - [&](const tbb::blocked_range &range) { - for (size_t idx = range.begin(); idx < range.end(); ++ idx) { - // Merge bucket_count adjacent to each other, merging uneven bucket numbers into even buckets - buckets[idx].second = merge_influence_areas_leaves(volumes, config, layer_idx, buckets[idx].first, buckets[idx].second); - throw_on_cancel(); - } - }); - - // Further merge iterations, merging one AABB subtree with another one, hopefully minimizing intersections between the elements - // of each of the subtree. - while (buckets.size() > 1) { - tbb::parallel_for(tbb::blocked_range(0, buckets.size() / 2), - [&](const tbb::blocked_range &range) { - for (size_t idx = range.begin(); idx < range.end(); ++ idx) { - const size_t bucket_pair_idx = idx * 2; - // Merge bucket_count adjacent to each other, merging uneven bucket numbers into even buckets - buckets[bucket_pair_idx].second = merge_influence_areas_two_sets(volumes, config, layer_idx, - buckets[bucket_pair_idx].first, buckets[bucket_pair_idx].second, - buckets[bucket_pair_idx + 1].first, buckets[bucket_pair_idx + 1].second); - throw_on_cancel(); + SVG svg(format("SVG/overhang_areas_%s.svg", layer->print_z), m_object->bounding_box()); + if (svg.is_opened()) { + svg.draw_outline(m_object->get_layer(layer->id())->lslices, "yellow"); + svg.draw(layer->overhang_areas, "orange"); + if (blockers.size() > layer->id()) + svg.draw(blockers[layer->id()], "red"); + } + if (enforcers.size() > layer->id()) { + SVG svg(format("SVG/enforcer_%s.svg", layer->print_z), m_object->bounding_box()); + if (svg.is_opened()) { + svg.draw_outline(m_object->get_layer(layer->id())->lslices, "yellow"); + svg.draw(enforcers[layer->id()], "red"); } - }); - // Remove odd buckets, which were merged into even buckets. - size_t new_size = (buckets.size() + 1) / 2; - for (size_t i = 1; i < new_size; ++ i) - buckets[i] = std::move(buckets[i * 2]); - buckets.erase(buckets.begin() + new_size, buckets.end()); + } + if (blockers.size() > layer->id()) { + SVG svg(format("SVG/blocker_%s.svg", layer->print_z), m_object->bounding_box()); + if (svg.is_opened()) { + svg.draw_outline(m_object->get_layer(layer->id())->lslices, "yellow"); + svg.draw(blockers[layer->id()], "red"); + } + } } +#endif } -/*! - * \brief Propagates influence downwards, and merges overlapping ones. - * - * \param move_bounds[in,out] All currently existing influence areas - */ -static void create_layer_pathing(const TreeModelVolumes &volumes, const TreeSupportSettings &config, std::vector &move_bounds, std::function throw_on_cancel) +void TreeSupport::create_tree_support_layers() { -#ifdef SLIC3R_TREESUPPORTS_PROGRESS - const double data_size_inverse = 1 / double(move_bounds.size()); - double progress_total = TREE_PROGRESS_PRECALC_AVO + TREE_PROGRESS_PRECALC_COLL + TREE_PROGRESS_GENERATE_NODES; -#endif // SLIC3R_TREESUPPORTS_PROGRESS - - auto dur_inc = std::chrono::duration_values::zero(); - auto dur_total = std::chrono::duration_values::zero(); - - LayerIndex last_merge_layer_idx = move_bounds.size(); - bool new_element = false; - const auto _tiny_area_threshold = tiny_area_threshold(); - - // Ensures at least one merge operation per 3mm height, 50 layers, 1 mm movement of slow speed or 5mm movement of fast speed (whatever is lowest). Values were guessed. - size_t max_merge_every_x_layers = std::min(std::min(5000 / (std::max(config.maximum_move_distance, coord_t(100))), 1000 / std::max(config.maximum_move_distance_slow, coord_t(20))), 3000 / config.layer_height); - size_t merge_every_x_layers = 1; - // Calculate the influence areas for each layer below (Top down) - // This is done by first increasing the influence area by the allowed movement distance, and merging them with other influence areas if possible - for (int layer_idx = int(move_bounds.size()) - 1; layer_idx > 0; -- layer_idx) - if (SupportElements &prev_layer = move_bounds[layer_idx]; ! prev_layer.empty()) { - // merging is expensive and only parallelized to a max speedup of 2. As such it may be useful in some cases to only merge every few layers to improve performance. - bool had_new_element = new_element; - const bool merge_this_layer = had_new_element || size_t(last_merge_layer_idx - layer_idx) >= merge_every_x_layers; - if (had_new_element) - merge_every_x_layers = 1; - const auto ta = std::chrono::high_resolution_clock::now(); - - // ### Increase the influence areas by the allowed movement distance - std::vector influence_areas; - influence_areas.reserve(prev_layer.size()); - for (int32_t element_idx = 0; element_idx < int32_t(prev_layer.size()); ++ element_idx) { - SupportElement &el = prev_layer[element_idx]; - assert(!el.influence_area.empty()); - SupportElement::ParentIndices parents; - parents.emplace_back(element_idx); - influence_areas.push_back({ el.state, parents }); - } - increase_areas_one_layer(volumes, config, influence_areas, layer_idx, prev_layer, merge_this_layer, throw_on_cancel); - - // Place already fully constructed elements to the output, remove them from influence_areas. - SupportElements &this_layer = move_bounds[layer_idx - 1]; - influence_areas.erase(std::remove_if(influence_areas.begin(), influence_areas.end(), - [&this_layer, &_tiny_area_threshold, layer_idx](SupportElementMerging &elem) { - if (elem.areas.influence_areas.empty()) - // This area was removed completely due to collisions. - return true; - if (elem.areas.to_bp_areas.empty() && elem.areas.to_model_areas.empty()) { - if (area(elem.areas.influence_areas) < _tiny_area_threshold) { - BOOST_LOG_TRIVIAL(error) << "Insert Error of Influence area bypass on layer " << layer_idx - 1; - tree_supports_show_error("Insert error of area after bypassing merge.\n"sv, true); - } - // Move the area to output. - this_layer.emplace_back(elem.state, std::move(elem.parents), std::move(elem.areas.influence_areas)); - return true; - } - // Keep the area. - return false; - }), - influence_areas.end()); - - dur_inc += std::chrono::high_resolution_clock::now() - ta; - new_element = ! move_bounds[layer_idx - 1].empty(); - if (merge_this_layer) { - bool reduced_by_merging = false; - if (size_t count_before_merge = influence_areas.size(); count_before_merge > 1) { - // ### Calculate which influence areas overlap, and merge them into a new influence area (simplified: an intersection of influence areas that have such an intersection) - merge_influence_areas(volumes, config, layer_idx, influence_areas, throw_on_cancel); - reduced_by_merging = count_before_merge > influence_areas.size(); - } - last_merge_layer_idx = layer_idx; - if (! reduced_by_merging && ! had_new_element) - merge_every_x_layers = std::min(max_merge_every_x_layers, merge_every_x_layers + 1); - } - - dur_total += std::chrono::high_resolution_clock::now() - ta; + int layer_id = 0; + coordf_t raft_print_z = 0.f; + coordf_t raft_slice_z = 0.f; + for (; layer_id < m_slicing_params.base_raft_layers; layer_id++) { + raft_print_z += m_slicing_params.base_raft_layer_height; + raft_slice_z = raft_print_z - m_slicing_params.base_raft_layer_height / 2; + m_object->add_tree_support_layer(layer_id, m_slicing_params.base_raft_layer_height, raft_print_z, raft_slice_z); + } - // Save calculated elements to output, and allocate Polygons on heap, as they will not be changed again. - for (SupportElementMerging &elem : influence_areas) - if (! elem.areas.influence_areas.empty()) { - Polygons new_area = safe_union(elem.areas.influence_areas); - if (area(new_area) < _tiny_area_threshold) { - BOOST_LOG_TRIVIAL(error) << "Insert Error of Influence area on layer " << layer_idx - 1 << ". Origin of " << elem.parents.size() << " areas. Was to bp " << elem.state.to_buildplate; - tree_supports_show_error("Insert error of area after merge.\n"sv, true); - } - this_layer.emplace_back(elem.state, std::move(elem.parents), std::move(new_area)); - } + for (; layer_id < m_slicing_params.base_raft_layers + m_slicing_params.interface_raft_layers; layer_id++) { + raft_print_z += m_slicing_params.interface_raft_layer_height; + raft_slice_z = raft_print_z - m_slicing_params.interface_raft_layer_height / 2; + m_object->add_tree_support_layer(layer_id, m_slicing_params.base_raft_layer_height, raft_print_z, raft_slice_z); + } - #ifdef SLIC3R_TREESUPPORTS_PROGRESS - progress_total += data_size_inverse * TREE_PROGRESS_AREA_CALC; - Progress::messageProgress(Progress::Stage::SUPPORT, progress_total * m_progress_multiplier + m_progress_offset, TREE_PROGRESS_TOTAL); - #endif - throw_on_cancel(); + for (Layer *layer : m_object->layers()) { + SupportLayer* ts_layer = m_object->add_tree_support_layer(layer->id(), layer->height, layer->print_z, layer->slice_z); + if (ts_layer->id() > m_raft_layers) { + SupportLayer* lower_layer = m_object->get_support_layer(ts_layer->id() - 1); + lower_layer->upper_layer = ts_layer; + ts_layer->lower_layer = lower_layer; } - - BOOST_LOG_TRIVIAL(info) << "Time spent with creating influence areas' subtasks: Increasing areas " << dur_inc.count() / 1000000 << - " ms merging areas: " << (dur_total - dur_inc).count() / 1000000 << " ms"; + } } -/*! - * \brief Sets the result_on_layer for all parents based on the SupportElement supplied. - * - * \param elem[in] The SupportElements, which parent's position should be determined. - */ -static void set_points_on_areas(const SupportElement &elem, SupportElements *layer_above) +static inline BoundingBox fill_expolygon_generate_paths( + ExtrusionEntitiesPtr &dst, + ExPolygon &&expolygon, + Fill *filler, + const FillParams &fill_params, + ExtrusionRole role, + const Flow &flow) { - assert(!elem.state.deleted); - assert(layer_above != nullptr || elem.parents.empty()); + Surface surface(stInternal, std::move(expolygon)); + Polylines polylines; + try { + polylines = filler->fill_surface(&surface, fill_params); + } catch (InfillFailedException &) { + } - // Based on the branch center point of the current layer, the point on the next (further up) layer is calculated. - if (! elem.state.result_on_layer_is_set()) { - BOOST_LOG_TRIVIAL(error) << "Uninitialized support element"; - tree_supports_show_error("Uninitialized support element. A branch may be missing.\n"sv, true); - return; + BoundingBox fill_bbox; + if (!polylines.empty()) { + fill_bbox = polylines[0].bounding_box(); + for (auto& polyline : polylines) + fill_bbox.merge(polyline.bounding_box()); } - if (layer_above) - for (int32_t next_elem_idx : elem.parents) { - assert(next_elem_idx >= 0); - SupportElement &next_elem = (*layer_above)[next_elem_idx]; - assert(! next_elem.state.deleted); - // if the value was set somewhere else it it kept. This happens when a branch tries not to move after being unable to create a roof. - if (! next_elem.state.result_on_layer_is_set()) { - // Move inside has edgecases (see tests) so DONT use Polygons.inside to confirm correct move, Error with distance 0 is <= 1 - // it is not required to check if how far this move moved a point as is can be larger than maximum_movement_distance. - // While this seems like a problem it may for example occur after merges. - next_elem.state.result_on_layer = move_inside_if_outside(next_elem.influence_area, elem.state.result_on_layer); - // do not call recursive because then amount of layers would be restricted by the stack size - } - // Mark the parent element as accessed from a valid child element. - next_elem.state.marked = true; - } + extrusion_entities_append_paths(dst, std::move(polylines), role, flow.mm3_per_mm(), flow.width(), flow.height()); + + return fill_bbox; } -static void set_to_model_contact_simple(SupportElement &elem) +static inline std::vector fill_expolygons_generate_paths( + ExtrusionEntitiesPtr &dst, + ExPolygons &&expolygons, + Fill *filler, + const FillParams &fill_params, + ExtrusionRole role, + const Flow &flow) { - const Point best = move_inside_if_outside(elem.influence_area, elem.state.next_position); - elem.state.result_on_layer = best; - BOOST_LOG_TRIVIAL(debug) << "Added NON gracious Support On Model Point (" << best.x() << "," << best.y() << "). The current layer is " << elem.state.layer_idx; + std::vector fill_boxes; + for (ExPolygon& expoly : expolygons) { + auto box = fill_expolygon_generate_paths(dst, std::move(expoly), filler, fill_params, role, flow); + fill_boxes.emplace_back(box); + } + return fill_boxes; } -/*! - * \brief Get the best point to connect to the model and set the result_on_layer of the relevant SupportElement accordingly. - * - * \param move_bounds[in,out] All currently existing influence areas - * \param first_elem[in,out] SupportElement that did not have its result_on_layer set meaning that it does not have a child element. - * \param layer_idx[in] The current layer. - */ -static void set_to_model_contact_to_model_gracious( - const TreeModelVolumes &volumes, - const TreeSupportSettings &config, - std::vector &move_bounds, - SupportElement &first_elem, - std::function throw_on_cancel) +static void _make_loops(ExtrusionEntitiesPtr& loops_entities, ExPolygons &support_area, ExtrusionRole role, size_t wall_count, const Flow &flow) { - SupportElement *last_successfull_layer = nullptr; + Polygons loops; + std::map depth_per_expoly; + std::list expoly_list; - // check for every layer upwards, up to the point where this influence area was created (either by initial insert or merge) if the branch could be placed on it, and highest up layer index. - { - SupportElement *elem = &first_elem; - for (LayerIndex layer_check = elem->state.layer_idx; - ! intersection(elem->influence_area, volumes.getPlaceableAreas(support_element_collision_radius(config, elem->state), layer_check, throw_on_cancel)).empty(); - elem = &move_bounds[++ layer_check][elem->parents.front()]) { - assert(elem->state.layer_idx == layer_check); - assert(! elem->state.deleted); - assert(elem->state.to_model_gracious); - last_successfull_layer = elem; - if (elem->parents.size() != 1) - // Reached merge point. - break; - } + for (ExPolygon &expoly : support_area) { + expoly_list.emplace_back(std::move(expoly)); + depth_per_expoly.insert({&expoly_list.back(), 0}); } + if (expoly_list.empty()) return; - // Could not find valid placement, even though it should exist => error handling - if (last_successfull_layer == nullptr) { - BOOST_LOG_TRIVIAL(warning) << "No valid placement found for to model gracious element on layer " << first_elem.state.layer_idx; - tree_supports_show_error("Could not fine valid placement on model! Just placing it down anyway. Could cause floating branches."sv, true); - first_elem.state.to_model_gracious = false; - set_to_model_contact_simple(first_elem); - } else { - // Found a gracious area above first_elem. Remove all below last_successfull_layer. - { - LayerIndex parent_layer_idx = first_elem.state.layer_idx; - for (SupportElement *elem = &first_elem; elem != last_successfull_layer; elem = &move_bounds[++ parent_layer_idx][elem->parents.front()]) { - assert(! elem->state.deleted); - elem->state.deleted = true; + while (!expoly_list.empty()) { + polygons_append(loops, to_polygons(expoly_list.front())); + + auto first_iter = expoly_list.begin(); + auto depth_iter = depth_per_expoly.find(&expoly_list.front()); + if (depth_iter->second + 1 < wall_count) { + //ExPolygons expolys_new = offset_ex(expoly_list.front(), -float(flow.scaled_spacing()), jtSquare); + // shrink and then dilate to prevent overlapping and overflow + ExPolygons expolys_new = offset2_ex({expoly_list.front()}, -1.4 * float(flow.scaled_spacing()), .4 * float(flow.scaled_spacing())); + + for (ExPolygon &expoly : expolys_new) { + auto new_iter = expoly_list.insert(expoly_list.begin(), expoly); + depth_per_expoly.insert({&*new_iter, depth_iter->second + 1}); } } - // Guess a point inside the influence area, in which the branch will be placed in. - const Point best = move_inside_if_outside(last_successfull_layer->influence_area, last_successfull_layer->state.next_position); - last_successfull_layer->state.result_on_layer = best; - BOOST_LOG_TRIVIAL(debug) << "Added gracious Support On Model Point (" << best.x() << "," << best.y() << "). The current layer is " << last_successfull_layer; + depth_per_expoly.erase(depth_iter); + expoly_list.erase(first_iter); } -} -// Remove elements marked as "deleted", update indices to parents. -static void remove_deleted_elements(std::vector &move_bounds) -{ - std::vector map_parents; - std::vector map_current; - for (LayerIndex layer_idx = LayerIndex(move_bounds.size()) - 1; layer_idx >= 0; -- layer_idx) { - SupportElements &layer = move_bounds[layer_idx]; - map_current.clear(); - for (int32_t i = 0; i < int32_t(layer.size());) { - SupportElement &element = layer[i]; - if (element.state.deleted) { - if (map_current.empty()) { - // Initialize with identity map. - map_current.assign(layer.size(), 0); - std::iota(map_current.begin(), map_current.end(), 0); - } - // Delete all "deleted" elements from the end of the layer vector. - while (i < int32_t(layer.size()) && layer.back().state.deleted) { - layer.pop_back(); - // Mark as deleted in the map. - map_current[layer.size()] = -1; - } - assert(i == layer.size() || i + 1 < layer.size()); - if (i + 1 < int32_t(layer.size())) { - element = std::move(layer.back()); - layer.pop_back(); - // Mark the current element as deleted. - map_current[i] = -1; - // Mark the moved element as moved to index i. - map_current[layer.size()] = i; - } + // draw connected loops + if (/*wall_count > 1 && wall_count<5*/0) { + // TODO this method may drop some contours + wall_count = std::min(wall_count, loops.size()); + Polylines polylines; + polylines.push_back(Polyline()); + Polyline& polyline = polylines.back(); + Point end_pt; + Point end_dir; + for (int wall_idx = 0; wall_idx < wall_count; wall_idx++) { + Polygon &loop = loops[wall_idx]; + if (loop.size()<3) continue; + // break the closed loop if this is not the last loop, so the next loop can attach to the end_pt + //if (wall_idx != wall_count - 1 && loop.first_point() == loop.last_point()) + // loop.points.pop_back(); + + if (wall_idx == 0) { + polyline.append(loop.points); } else { - // Current element is not deleted. Update its parent indices. - if (! map_parents.empty()) - for (int32_t &parent_idx : element.parents) - parent_idx = map_parents[parent_idx]; - ++ i; + double d = loop.distance_to(end_pt); + if (d < scale_(2)) { // if current loop is close to the previous one + polyline.append(end_pt); + ExtrusionPath expath; + expath.polyline.append(loop.points); + ExtrusionLoop extru_loop(expath); + extru_loop.split_at(end_pt, false); + polyline.append(extru_loop.as_polyline()); + }else{// create a new polylie if they are far away + polylines.push_back(Polyline()); + polyline = polylines.back(); + polyline.append(loop.points); + } } + end_pt = polyline.points.back(); + end_dir = end_pt - polyline.points[polyline.points.size() - 2]; + Point perpendicular_dir = turn90_ccw(end_dir); + end_pt = end_pt + normal(perpendicular_dir, flow.scaled_spacing()); } - std::swap(map_current, map_parents); + + extrusion_entities_append_paths(loops_entities, polylines, role, float(flow.mm3_per_mm()), float(flow.width()), float(flow.height())); + } else { + extrusion_entities_append_loops(loops_entities, std::move(loops), role, float(flow.mm3_per_mm()), float(flow.width()), float(flow.height())); + } } + +static void make_perimeter_and_inner_brim(ExtrusionEntitiesPtr &dst, const ExPolygon &support_area, size_t wall_count, const Flow &flow, ExtrusionRole role) +{ + Polygons loops; + ExPolygons support_area_new = offset_ex(support_area, -0.5f * float(flow.scaled_spacing()), jtSquare); + _make_loops(dst, support_area_new, role, wall_count, flow); } -/*! - * \brief Set the result_on_layer point for all influence areas - * - * \param move_bounds[in,out] All currently existing influence areas - */ -static void create_nodes_from_area( - const TreeModelVolumes &volumes, - const TreeSupportSettings &config, - std::vector &move_bounds, - std::function throw_on_cancel) +static void make_perimeter_and_infill(ExtrusionEntitiesPtr& dst, const Print& print, const ExPolygon& support_area, size_t wall_count, const Flow& flow, ExtrusionRole role, Fill* filler_support, double support_density, bool infill_first=true) { - // Initialize points on layer 0, with a "random" point in the influence area. - // Point is chosen based on an inaccurate estimate where the branches will split into two, but every point inside the influence area would produce a valid result. - { - SupportElements *layer_above = move_bounds.size() > 1 ? &move_bounds[1] : nullptr; - if (layer_above) { - for (SupportElement &elem : *layer_above) - elem.state.marked = false; - } - for (SupportElement &init : move_bounds.front()) { - init.state.result_on_layer = move_inside_if_outside(init.influence_area, init.state.next_position); - // Also set the parent nodes, as these will be required for the first iteration of the loop below and mark the parent nodes. - set_points_on_areas(init, layer_above); - } - } - - throw_on_cancel(); - - for (LayerIndex layer_idx = 1; layer_idx < LayerIndex(move_bounds.size()); ++ layer_idx) { - auto &layer = move_bounds[layer_idx]; - auto *layer_above = layer_idx + 1 < LayerIndex(move_bounds.size()) ? &move_bounds[layer_idx + 1] : nullptr; - if (layer_above) - for (SupportElement &elem : *layer_above) - elem.state.marked = false; - for (SupportElement &elem : layer) { - assert(! elem.state.deleted); - assert(elem.state.layer_idx == layer_idx); - // check if the resulting center point is not yet set - if (! elem.state.result_on_layer_is_set()) { - if (elem.state.to_buildplate || (elem.state.distance_to_top < config.min_dtt_to_model && ! elem.state.supports_roof)) { - if (elem.state.to_buildplate) { - BOOST_LOG_TRIVIAL(error) << "Uninitialized Influence area targeting " << elem.state.target_position.x() << "," << elem.state.target_position.y() << ") " - "at target_height: " << elem.state.target_height << " layer: " << layer_idx; - tree_supports_show_error("Uninitialized support element! A branch could be missing or exist partially."sv, true); - } - // we dont need to remove yet the parents as they will have a lower dtt and also no result_on_layer set - elem.state.deleted = true; - } else { - // set the point where the branch will be placed on the model - if (elem.state.to_model_gracious) - set_to_model_contact_to_model_gracious(volumes, config, move_bounds, elem, throw_on_cancel); - else - set_to_model_contact_simple(elem); - } - } - if (! elem.state.deleted && ! elem.state.marked && elem.state.target_height == layer_idx) - // Just a tip surface with no supporting element. - elem.state.deleted = true; - if (elem.state.deleted) { - for (int32_t parent_idx : elem.parents) - // When the roof was not able to generate downwards enough, the top elements may have not moved, and have result_on_layer already set. - // As this branch needs to be removed => all parents result_on_layer have to be invalidated. - (*layer_above)[parent_idx].state.result_on_layer_reset(); + Polygons loops; + ExPolygons support_area_new = offset_ex(support_area, -0.5f * float(flow.scaled_spacing()), jtSquare); + + // draw infill + FillParams fill_params; + fill_params.density = support_density; + fill_params.dont_adjust = true; + ExPolygons to_infill = support_area_new; + std::vector fill_boxes = fill_expolygons_generate_paths(dst, std::move(to_infill), filler_support, fill_params, role, flow); + + // allow wall_count to be zero, which means only draw infill + if (wall_count == 0) { + for (auto fill_bbox : fill_boxes) + { + // extend bounding box on x-axis + if (cos(filler_support->angle)>=sin(filler_support->angle)) { + fill_bbox.min[0] -= scale_(10); + fill_bbox.max[0] += scale_(10); } - if (! elem.state.deleted) { - // Element is valid now setting points in the layer above and mark the parent nodes. - set_points_on_areas(elem, layer_above); + else { + fill_bbox.min[1] -= scale_(10); + fill_bbox.max[1] += scale_(10); } + support_area_new = diff_ex(support_area_new, offset_ex(to_expolygons({ fill_bbox.polygon() }), 0.5*flow.scaled_width())); } - throw_on_cancel(); - } - -#ifndef NDEBUG - // Verify the tree connectivity including the branch slopes. - for (LayerIndex layer_idx = 0; layer_idx + 1 < LayerIndex(move_bounds.size()); ++ layer_idx) { - auto &layer = move_bounds[layer_idx]; - auto &above = move_bounds[layer_idx + 1]; - for (SupportElement &elem : layer) - if (! elem.state.deleted) { - for (int32_t iparent : elem.parents) { - SupportElement &parent = above[iparent]; - assert(! parent.state.deleted); - assert(elem.state.result_on_layer_is_set() == parent.state.result_on_layer_is_set()); - if (elem.state.result_on_layer_is_set()) { - double radius_increase = support_element_radius(config, elem) - support_element_radius(config, parent); - assert(radius_increase >= 0); - double shift = (elem.state.result_on_layer - parent.state.result_on_layer).cast().norm(); - //FIXME this assert fails a lot. Is it correct? -// assert(shift < radius_increase + 2. * config.maximum_move_distance_slow); - } - } - } + // filter out small areas + for (auto it = support_area_new.begin(); it != support_area_new.end(); ) { + if (offset_ex(*it, -flow.scaled_width()).empty()) + it = support_area_new.erase(it); + else + it++; } -#endif // NDEBUG - - remove_deleted_elements(move_bounds); - -#ifndef NDEBUG - // Verify the tree connectivity including the branch slopes. - for (LayerIndex layer_idx = 0; layer_idx + 1 < LayerIndex(move_bounds.size()); ++ layer_idx) { - auto &layer = move_bounds[layer_idx]; - auto &above = move_bounds[layer_idx + 1]; - for (SupportElement &elem : layer) { - assert(! elem.state.deleted); - for (int32_t iparent : elem.parents) { - SupportElement &parent = above[iparent]; - assert(! parent.state.deleted); - assert(elem.state.result_on_layer_is_set() == parent.state.result_on_layer_is_set()); - if (elem.state.result_on_layer_is_set()) { - double radius_increase = support_element_radius(config, elem) - support_element_radius(config, parent); - assert(radius_increase >= 0); - double shift = (elem.state.result_on_layer - parent.state.result_on_layer).cast().norm(); - //FIXME this assert fails a lot. Is it correct? -// assert(shift < radius_increase + 2. * config.maximum_move_distance_slow); - } - } + } + + { // draw loops + ExtrusionEntitiesPtr loops_entities; + _make_loops(loops_entities, support_area_new, role, wall_count, flow); + + if (infill_first) + dst.insert(dst.end(), loops_entities.begin(), loops_entities.end()); + else { // loops first + loops_entities.insert(loops_entities.end(), dst.begin(), dst.end()); + dst = std::move(loops_entities); + } + } + if (infill_first) { + // sort regions to reduce travel + Points ordering_points; + for (const auto& area : dst) + ordering_points.push_back(area->first_point()); + std::vector order = chain_points(ordering_points); + ExtrusionEntitiesPtr new_dst; + new_dst.reserve(ordering_points.size()); + for (size_t i : order) + new_dst.emplace_back(dst[i]); + dst = new_dst; + } +} + +void TreeSupport::generate_toolpaths() +{ + const PrintConfig &print_config = m_object->print()->config(); + const PrintObjectConfig &object_config = m_object->config(); + coordf_t support_extrusion_width = m_support_params.support_extrusion_width; + coordf_t nozzle_diameter = print_config.nozzle_diameter.get_at(object_config.support_filament - 1); + coordf_t layer_height = object_config.layer_height.value; + const size_t wall_count = object_config.tree_support_wall_count.value; + + // Check if set to zero, use default if so. + if (support_extrusion_width <= 0.0) + support_extrusion_width = Flow::auto_extrusion_width(FlowRole::frSupportMaterial, (float)nozzle_diameter); + + // coconut: use same intensity settings as SupportMaterial.cpp + auto m_support_material_interface_flow = support_material_interface_flow(m_object, float(m_slicing_params.layer_height)); + coordf_t interface_spacing = object_config.support_interface_spacing.value + m_support_material_interface_flow.spacing(); + coordf_t bottom_interface_spacing = object_config.support_bottom_interface_spacing.value + m_support_material_interface_flow.spacing(); + coordf_t interface_density = std::min(1., m_support_material_interface_flow.spacing() / interface_spacing); + coordf_t bottom_interface_density = std::min(1., m_support_material_interface_flow.spacing() / bottom_interface_spacing); + + const coordf_t branch_radius = object_config.tree_support_branch_diameter.value / 2; + const coordf_t branch_radius_scaled = scale_(branch_radius); + + if (m_object->support_layers().empty()) + return; + + // calculate fill areas for raft layers + ExPolygons raft_areas; + if (m_object->layer_count() > 0) { + const Layer *layer = m_object->layers().front(); + for (const ExPolygon &expoly : layer->lslices) { + raft_areas.push_back(expoly); + } + } + + if (m_object->support_layer_count() > m_raft_layers) { + const SupportLayer *ts_layer = m_object->get_support_layer(m_raft_layers); + for (const ExPolygon& expoly : ts_layer->floor_areas) + raft_areas.push_back(expoly); + for (const ExPolygon& expoly : ts_layer->roof_areas) + raft_areas.push_back(expoly); + for (const ExPolygon& expoly : ts_layer->base_areas) + raft_areas.push_back(expoly); + } + + raft_areas = std::move(offset_ex(raft_areas, scale_(object_config.raft_first_layer_expansion))); + + // generate raft tool path + if (m_raft_layers > 0) + { + ExtrusionRole raft_contour_er = m_slicing_params.base_raft_layers > 0 ? erSupportMaterial : erSupportMaterialInterface; + SupportLayer *ts_layer = m_object->support_layers().front(); + Flow flow = m_object->print()->brim_flow(); + + Polygons loops; + for (const ExPolygon& expoly : raft_areas) { + loops.push_back(expoly.contour); + loops.insert(loops.end(), expoly.holes.begin(), expoly.holes.end()); + } + extrusion_entities_append_loops(ts_layer->support_fills.entities, std::move(loops), raft_contour_er, + float(flow.mm3_per_mm()), float(flow.width()), float(flow.height())); + raft_areas = offset_ex(raft_areas, -flow.scaled_spacing() / 2.); + } + + for (size_t layer_nr = 0; layer_nr < m_slicing_params.base_raft_layers; layer_nr++) { + SupportLayer *ts_layer = m_object->get_support_layer(layer_nr); + coordf_t expand_offset = (layer_nr == 0 ? 0. : -1.); + + Flow support_flow = layer_nr == 0 ? m_object->print()->brim_flow() : Flow(support_extrusion_width, ts_layer->height, nozzle_diameter); + Fill* filler_interface = Fill::new_from_type(ipRectilinear); + filler_interface->angle = layer_nr == 0 ? 90 : 0; + filler_interface->spacing = support_extrusion_width; + + FillParams fill_params; + fill_params.density = object_config.raft_first_layer_density * 0.01; + fill_params.dont_adjust = true; + + fill_expolygons_generate_paths(ts_layer->support_fills.entities, std::move(offset_ex(raft_areas, scale_(expand_offset))), + filler_interface, fill_params, erSupportMaterial, support_flow); + } + + for (size_t layer_nr = m_slicing_params.base_raft_layers; + layer_nr < m_slicing_params.base_raft_layers + m_slicing_params.interface_raft_layers; + layer_nr++) + { + SupportLayer *ts_layer = m_object->get_support_layer(layer_nr); + coordf_t expand_offset = (layer_nr == 0 ? 0. : -1.); + + Flow support_flow(support_extrusion_width, ts_layer->height, nozzle_diameter); + Fill* filler_interface = Fill::new_from_type(ipRectilinear); + filler_interface->angle = 0; + filler_interface->spacing = support_extrusion_width; + + FillParams fill_params; + fill_params.density = interface_density; + fill_params.dont_adjust = true; + + fill_expolygons_generate_paths(ts_layer->support_fills.entities, std::move(offset_ex(raft_areas, scale_(expand_offset))), + filler_interface, fill_params, erSupportMaterialInterface, support_flow); + } + + BoundingBox bbox_object(Point(-scale_(1.), -scale_(1.0)), Point(scale_(1.), scale_(1.))); + + std::shared_ptr filler_interface = std::shared_ptr(Fill::new_from_type(m_support_params.contact_fill_pattern)); + std::shared_ptr filler_Roof1stLayer = std::shared_ptr(Fill::new_from_type(ipRectilinear)); + filler_interface->set_bounding_box(bbox_object); + filler_Roof1stLayer->set_bounding_box(bbox_object); + filler_interface->angle = Geometry::deg2rad(object_config.support_angle.value + 90.); + filler_Roof1stLayer->angle = Geometry::deg2rad(object_config.support_angle.value + 90.); + + // generate tree support tool paths + tbb::parallel_for( + tbb::blocked_range(m_raft_layers, m_object->support_layer_count()), + [&](const tbb::blocked_range& range) + { + for (size_t layer_id = range.begin(); layer_id < range.end(); layer_id++) { + if (m_object->print()->canceled()) + break; + + m_object->print()->set_status(70, (boost::format(_L("Support: generate toolpath at layer %d")) % layer_id).str()); + + SupportLayer* ts_layer = m_object->get_support_layer(layer_id); + Flow support_flow(support_extrusion_width, ts_layer->height, nozzle_diameter); + coordf_t support_spacing = object_config.support_base_pattern_spacing.value + support_flow.spacing(); + coordf_t support_density = std::min(1., support_flow.spacing() / support_spacing); + ts_layer->support_fills.no_sort = false; + + for (auto& area_group : ts_layer->area_groups) { + ExPolygon& poly = *area_group.area; + ExPolygons polys; + FillParams fill_params; + if (area_group.type != SupportLayer::BaseType) { + // interface + if (layer_id == 0) { + Flow flow = m_raft_layers == 0 ? m_object->print()->brim_flow() : support_flow; + make_perimeter_and_inner_brim(ts_layer->support_fills.entities, poly, wall_count, flow, + area_group.type == SupportLayer::RoofType ? erSupportMaterialInterface : erSupportMaterial); + polys = std::move(offset_ex(poly, -flow.scaled_spacing())); + } else if (area_group.type == SupportLayer::Roof1stLayer) { + polys = std::move(offset_ex(poly, 0.5*support_flow.scaled_width())); + } + else { + polys.push_back(poly); + } + fill_params.density = interface_density; + fill_params.dont_adjust = true; + } + if (area_group.type == SupportLayer::Roof1stLayer) { + // roof_1st_layer + fill_params.density = interface_density; + // Note: spacing means the separation between two lines as if they are tightly extruded + filler_Roof1stLayer->spacing = m_support_material_interface_flow.spacing(); + // generate a perimeter first to support interface better + ExtrusionEntityCollection* temp_support_fills = new ExtrusionEntityCollection(); + make_perimeter_and_infill(temp_support_fills->entities, *m_object->print(), poly, 1, m_support_material_interface_flow, erSupportMaterial, + filler_Roof1stLayer.get(), interface_density, false); + temp_support_fills->no_sort = true; // make sure loops are first + if (!temp_support_fills->entities.empty()) + ts_layer->support_fills.entities.push_back(temp_support_fills); + else + delete temp_support_fills; + } else if (area_group.type == SupportLayer::FloorType) { + // floor_areas + fill_params.density = bottom_interface_density; + filler_interface->spacing = m_support_material_interface_flow.spacing(); + fill_expolygons_generate_paths(ts_layer->support_fills.entities, std::move(polys), + filler_interface.get(), fill_params, erSupportMaterialInterface, m_support_material_interface_flow); + } else if (area_group.type == SupportLayer::RoofType) { + // roof_areas + fill_params.density = interface_density; + filler_interface->spacing = m_support_material_interface_flow.spacing(); + if (m_object_config->support_interface_pattern == smipGrid) { + filler_interface->angle = Geometry::deg2rad(object_config.support_angle.value); + fill_params.dont_sort = true; + } + if (m_object_config->support_interface_pattern == smipRectilinearInterlaced) + filler_interface->layer_id = round(area_group.dist_to_top / ts_layer->height); + fill_expolygons_generate_paths(ts_layer->support_fills.entities, std::move(polys), filler_interface.get(), fill_params, erSupportMaterialInterface, + m_support_material_interface_flow); + } + else { + // base_areas + Flow flow = (layer_id == 0 && m_raft_layers == 0) ? m_object->print()->brim_flow() : support_flow; + bool need_infill = with_infill; + if(m_object_config->support_base_pattern==smpDefault) + need_infill &= area_group.need_infill; + if (layer_id>0 && area_group.dist_to_top < 10 && !need_infill && support_style!=smsTreeHybrid) { + if (area_group.dist_to_top < 5) // 1 wall at the top <5mm + make_perimeter_and_inner_brim(ts_layer->support_fills.entities, poly, 1, flow, erSupportMaterial); + else // at least 2 walls for range [5,10) + make_perimeter_and_inner_brim(ts_layer->support_fills.entities, poly, std::max(wall_count, size_t(2)), flow, erSupportMaterial); + } + else if (layer_id > 0 && need_infill && m_support_params.base_fill_pattern != ipLightning) { + std::shared_ptr filler_support = std::shared_ptr(Fill::new_from_type(m_support_params.base_fill_pattern)); + filler_support->set_bounding_box(bbox_object); + filler_support->spacing = object_config.support_base_pattern_spacing.value * support_density;// constant spacing to align support infill lines + filler_support->angle = Geometry::deg2rad(object_config.support_angle.value); + + // allow infill-only mode if support is thick enough (so min_wall_count is 0); + // otherwise must draw 1 wall + size_t min_wall_count = offset(poly, -scale_(support_spacing * 1.5)).empty() ? 1 : 0; + make_perimeter_and_infill(ts_layer->support_fills.entities, *m_object->print(), poly, std::max(min_wall_count, wall_count), flow, + erSupportMaterial, filler_support.get(), support_density); + } + else { + make_perimeter_and_inner_brim(ts_layer->support_fills.entities, poly, + layer_id > 0 ? wall_count : std::numeric_limits::max(), flow, erSupportMaterial); + } + + } + } + if (m_support_params.base_fill_pattern == ipLightning) + { + double print_z = ts_layer->print_z; + if (printZ_to_lightninglayer.find(print_z) == printZ_to_lightninglayer.end()) + continue; + //TODO: + //1.the second parameter of convertToLines seems to decide how long the lightning should be trimmed from its root, so that the root wont overlap/detach the support contour. + // whether current value works correctly remained to be tested + //2.related to previous one, that lightning roots need to be trimed more when support has multiple walls + //3.function connect_infill() and variable 'params' helps create connection pattern along contours between two lightning roots, + // strengthen lightnings while it may make support harder. decide to enable it or not. if yes, proper values for params are remained to be tested + auto& lightning_layer = generator->getTreesForLayer(printZ_to_lightninglayer[print_z]); + + Flow flow = (layer_id == 0 && m_raft_layers == 0) ? m_object->print()->brim_flow() :support_flow; + ExPolygons areas = offset_ex(ts_layer->base_areas, -flow.scaled_spacing()); + + for (auto& area : areas) + { + Polylines polylines = lightning_layer.convertToLines(to_polygons(area), 0); + for (auto itr = polylines.begin(); itr != polylines.end();) + { + if (itr->length() < scale_(1.0)) + itr = polylines.erase(itr); + else + itr++; + } + Polylines opt_polylines; +#if 1 + //this wont create connection patterns along contours + append(opt_polylines, chain_polylines(std::move(polylines))); +#else + //this will create connection patterns along contours + FillParams params; + params.anchor_length = float(Fill::infill_anchor * 0.01 * flow.spacing()); + params.anchor_length_max = Fill::infill_anchor_max; + params.anchor_length = std::min(params.anchor_length, params.anchor_length_max); + Fill::connect_infill(std::move(polylines), area, opt_polylines, flow.spacing(), params); +#endif + extrusion_entities_append_paths(ts_layer->support_fills.entities, opt_polylines, erSupportMaterial, + float(flow.mm3_per_mm()), float(flow.width()), float(flow.height())); + +#ifdef SUPPORT_TREE_DEBUG_TO_SVG + std::string name = "./SVG/trees_polyline_" + std::to_string(ts_layer->print_z) /*+ "_" + std::to_string(rand_num)*/ + ".svg"; + BoundingBox bbox = get_extents(ts_layer->base_areas); + SVG svg(name, bbox); + if (svg.is_opened()) { + svg.draw(ts_layer->base_areas, "blue"); + svg.draw(generator->Overhangs()[printZ_to_lightninglayer[print_z]], "red"); + for (auto &line : opt_polylines) svg.draw(line, "yellow"); + } +#endif + } + } + + // sort extrusions to reduce travel, also make sure walls go before infills + if(ts_layer->support_fills.no_sort==false) + chain_and_reorder_extrusion_entities(ts_layer->support_fills.entities); + } + } + ); +} + +Polygons TreeSupport::spanning_tree_to_polygon(const std::vector& spanning_trees, Polygons layer_contours, int layer_nr) +{ + Polygons polys; + auto& mst_line_x_layer_contour_cache = m_mst_line_x_layer_contour_caches[layer_nr]; + for (MinimumSpanningTree mst : spanning_trees) { + std::vector points = mst.vertices(); + if (points.size() == 0) + continue; + std::map visited; + for (int i=0;i to_ignore; + for (int i = 0; i < points.size(); i++) { + if (visited[points[i]] == true) + continue; + + Polygon poly; + bool has_next = true; + Point pt1 = points[i]; + poly.points.push_back(pt1); + visited[pt1] = true; + + while (has_next) { + const std::vector& neighbours = mst.adjacent_nodes(pt1); + if (neighbours.empty()) + { + break; + } + + double min_ccw = std::numeric_limits::max(); + Point pt_selected = neighbours[0]; + has_next = false; + for (Point pt2 : neighbours) { + if (to_ignore.find(Line(pt1, pt2)) == to_ignore.end()) { + auto iter = mst_line_x_layer_contour_cache.find({ pt1,pt2 }); + if (iter != mst_line_x_layer_contour_cache.end()) { + if (iter->second) + continue; + } + else { + Polylines pls; + pls.emplace_back(pt1, pt2); + Polylines pls_intersect = intersection_pl(pls, layer_contours); + mst_line_x_layer_contour_cache.insert({ {pt1, pt2}, !pls_intersect.empty() }); + mst_line_x_layer_contour_cache.insert({ {pt2, pt1}, !pls_intersect.empty() }); + if (!pls_intersect.empty()) + continue; + } + + if (poly.points.size() < 2 || visited[pt2]==false) + { + pt_selected = pt2; + has_next = true; + break; + } + double curr_ccw = pt2.ccw(pt1, poly.points.back()); + if (curr_ccw < min_ccw) + { + min_ccw = curr_ccw; + pt_selected = pt2; + has_next = true; + } + } + } + if (has_next) { + poly.points.push_back(pt_selected); + to_ignore.insert(Line(pt1, pt_selected)); + visited[pt_selected] = true; + pt1 = pt_selected; + } + } + polys.emplace_back(std::move(poly)); + } + } + return polys; +} + +Polygons TreeSupport::contact_nodes_to_polygon(const std::vector& contact_nodes, Polygons layer_contours, int layer_nr, std::vector& radiis, std::vector& is_interface) +{ + Polygons polys; + std::vector spanning_trees; + std::vector radiis_mtree; + std::vector is_interface_mtree; + // generate minimum spanning trees + { + std::map visited; + for (int i = 0; i < contact_nodes.size(); i++) + visited.emplace(contact_nodes[i], false); + std::unordered_set to_ignore; + + // generate minimum spaning trees + for (int i = 0; i < contact_nodes.size(); i++) { + Node* node = contact_nodes[i]; + if (visited[node]) + continue; + + std::vector points_to_mstree; + double radius = 0; + Point pt1 = node->position; + points_to_mstree.push_back(pt1); + visited[node] = true; + radius += node->radius; + + for (int j = i + 1; j < contact_nodes.size(); j++) { + Node* node2 = contact_nodes[j]; + Point pt2 = node2->position; + // connect to this neighbor if: + // 1) both are interface or both are not + // 3) not readly added + // 4) won't cross perimeters: this is not right since we need to check all possible connections + if ((node->support_roof_layers_below > 0) == (node2->support_roof_layers_below > 0) + && to_ignore.find(Line(pt1, pt2)) == to_ignore.end()) + { + points_to_mstree.emplace_back(pt2); + visited[node2] = true; + radius += node2->radius; + } + } + + spanning_trees.emplace_back(points_to_mstree); + radiis_mtree.push_back(radius / points_to_mstree.size()); + is_interface_mtree.push_back(node->support_roof_layers_below > 0); + } + } + auto lines = spanning_tree_to_lines(spanning_trees); +#if 1 + // convert mtree to polygon + for (int k = 0; k < spanning_trees.size(); k++) { + auto& mst_line_x_layer_contour_cache = m_mst_line_x_layer_contour_caches[layer_nr]; + MinimumSpanningTree mst = spanning_trees[k]; + std::vector points = mst.vertices(); + std::map visited; + for (int i = 0; i < points.size(); i++) + visited.emplace(points[i], false); + + std::unordered_set to_ignore; + for (int i = 0; i < points.size(); i++) { + if (visited[points[i]]) + continue; + + Polygon poly; + Point pt1 = points[i]; + poly.points.push_back(pt1); + visited[pt1] = true; + + bool has_next = true; + while (has_next) + { + const std::vector& neighbours = mst.adjacent_nodes(pt1); + double min_ccw = -std::numeric_limits::max(); + Point pt_selected; + has_next = false; + for (Point pt2 : neighbours) { + if (to_ignore.find(Line(pt1, pt2)) == to_ignore.end()) { + auto iter = mst_line_x_layer_contour_cache.find({ pt1,pt2 }); + if (iter != mst_line_x_layer_contour_cache.end()) { + if (iter->second) + continue; + } + else { + Polylines pls; + pls.emplace_back(pt1, pt2); + Polylines pls_intersect = intersection_pl(pls, layer_contours); + mst_line_x_layer_contour_cache.insert({ {pt1, pt2}, !pls_intersect.empty() }); + mst_line_x_layer_contour_cache.insert({ {pt2, pt1}, !pls_intersect.empty() }); + if (!pls_intersect.empty()) + continue; + } + if (poly.points.size() < 2) + { + pt_selected = pt2; + has_next = true; + break; + } + double curr_ccw = pt2.ccw(pt1, poly.points.rbegin()[1]); + if (curr_ccw > min_ccw) + { + has_next = true; + min_ccw = curr_ccw; + pt_selected = pt2; + } + } + } + if (!has_next) + break; + + poly.points.push_back(pt_selected); + to_ignore.insert(Line(pt1, pt_selected)); + visited[pt_selected] = true; + pt1 = pt_selected; + } + polys.emplace_back(std::move(poly)); + radiis.push_back(radiis_mtree[k]); + is_interface.push_back(is_interface_mtree[k]); + } + } +#else + polys = spanning_tree_to_polygon(spanning_trees, layer_contours, layer_nr, radiis); +#endif + return polys; +} + + +void TreeSupport::generate() +{ + if (support_style == smsOrganic) { + generate_tree_support_3D(*m_object, this->throw_on_cancel); + return; + } + + std::vector> contact_nodes(m_object->layers().size()); + + profiler.stage_start(STAGE_total); + + // Generate overhang areas + profiler.stage_start(STAGE_DETECT_OVERHANGS); + m_object->print()->set_status(55, _L("Support: detect overhangs")); + detect_overhangs(); + profiler.stage_finish(STAGE_DETECT_OVERHANGS); + + if (!has_overhangs) return; + + m_ts_data = m_object->alloc_tree_support_preview_cache(); + m_ts_data->is_slim = is_slim; + + // Generate contact points of tree support + profiler.stage_start(STAGE_GENERATE_CONTACT_NODES); + m_object->print()->set_status(56, _L("Support: generate contact points")); + generate_contact_points(contact_nodes); + profiler.stage_finish(STAGE_GENERATE_CONTACT_NODES); + + //Drop nodes to lower layers. + profiler.stage_start(STAGE_DROP_DOWN_NODES); + m_object->print()->set_status(60, _L("Support: propagate branches")); + drop_nodes(contact_nodes); + profiler.stage_finish(STAGE_DROP_DOWN_NODES); + + smooth_nodes(contact_nodes); + +if (!m_object->config().tree_support_adaptive_layer_height) + // Adjust support layer heights + adjust_layer_heights(contact_nodes); + + + //Generate support areas. + profiler.stage_start(STAGE_DRAW_CIRCLES); + m_object->print()->set_status(65, _L("Support: draw polygons")); + draw_circles(contact_nodes); + profiler.stage_finish(STAGE_DRAW_CIRCLES); + + for (auto& layer : contact_nodes) + { + for (Node* p_node : layer) + { + delete p_node; + } + layer.clear(); + } + contact_nodes.clear(); + + profiler.stage_start(STAGE_GENERATE_TOOLPATHS); + m_object->print()->set_status(69, _L("Support: generate toolpath")); + generate_toolpaths(); + profiler.stage_finish(STAGE_GENERATE_TOOLPATHS); + + profiler.stage_finish(STAGE_total); + BOOST_LOG_TRIVIAL(info) << "tree support time " << profiler.report(); +} + +coordf_t TreeSupport::calc_branch_radius(coordf_t base_radius, size_t layers_to_top, size_t tip_layers, double diameter_angle_scale_factor) +{ + double radius; + if (!is_slim) { + if ((layers_to_top + 1) > tip_layers) { + radius = base_radius + base_radius * (layers_to_top + 1) * diameter_angle_scale_factor; + } else { + radius = base_radius * (layers_to_top + 1) / tip_layers; + } + } else { + if ((layers_to_top + 1) > tip_layers * 2) { + radius = base_radius + base_radius * (layers_to_top + 1) * diameter_angle_scale_factor; + } else { + radius = base_radius * (layers_to_top + 1) / (tip_layers * 2); + } + radius = std::max(radius, MIN_BRANCH_RADIUS); + } + radius = std::min(radius, MAX_BRANCH_RADIUS); + return radius; +} + +coordf_t TreeSupport::calc_branch_radius(coordf_t base_radius, coordf_t mm_to_top, double diameter_angle_scale_factor) +{ + double radius; + coordf_t tip_height = base_radius;// this is a 45 degree tip + if (mm_to_top > tip_height) + { + radius = base_radius + (mm_to_top-tip_height) * diameter_angle_scale_factor; + } + else + { + radius = mm_to_top;// this is a 45 degree tip + } + + radius = std::max(radius, MIN_BRANCH_RADIUS); + radius = std::min(radius, MAX_BRANCH_RADIUS); + // if have interface layers, radius should be larger + if (m_object_config->support_interface_top_layers.value > 0) + radius = std::max(radius, base_radius); + return radius; +} + +template // RegionType could be ExPolygons or Polygons +ExPolygons avoid_object_remove_extra_small_parts(ExPolygons &expolys, const RegionType&avoid_region) { + ExPolygons expolys_out; + for (auto expoly : expolys) { + auto expolys_avoid = diff_ex(expoly, avoid_region); + int idx_max_area = -1; + float max_area = 0; + for (int i = 0; i < expolys_avoid.size(); ++i) { + auto a = expolys_avoid[i].area(); + if (a > max_area) { + max_area = a; + idx_max_area = i; + } + } + if (idx_max_area >= 0) expolys_out.emplace_back(std::move(expolys_avoid[idx_max_area])); + } + return expolys_out; +} + +Polygons TreeSupport::get_trim_support_regions( + const PrintObject& object, + SupportLayer* support_layer_ptr, + const coordf_t gap_extra_above, + const coordf_t gap_extra_below, + const coordf_t gap_xy) +{ + static const double sharp_tail_xy_gap = 0.2f; + static const double no_overlap_xy_gap = 0.2f; + double gap_xy_scaled = scale_(gap_xy); + SupportLayer& support_layer = *support_layer_ptr; + auto m_print_config = object.print()->config(); + + size_t idx_object_layer_overlapping = size_t(-1); + + auto is_layers_overlap = [](const SupportLayer& support_layer, const Layer& object_layer, coordf_t bridging_height = 0.f) -> bool { + if (std::abs(support_layer.print_z - object_layer.print_z) < EPSILON) + return true; + + coordf_t object_lh = bridging_height > EPSILON ? bridging_height : object_layer.height; + if (support_layer.print_z < object_layer.print_z && support_layer.print_z > object_layer.print_z - object_lh) + return true; + + if (support_layer.print_z > object_layer.print_z && support_layer.bottom_z() < object_layer.print_z - EPSILON) + return true; + + return false; + }; + + // Find the overlapping object layers including the extra above / below gap. + coordf_t z_threshold = support_layer.bottom_z() - gap_extra_below + EPSILON; + idx_object_layer_overlapping = Layer::idx_higher_or_equal( + object.layers().begin(), object.layers().end(), idx_object_layer_overlapping, + [z_threshold](const Layer* layer) { return layer->print_z >= z_threshold; }); + // Collect all the object layers intersecting with this layer. + Polygons polygons_trimming; + size_t i = idx_object_layer_overlapping; + for (; i < object.layers().size(); ++i) { + const Layer& object_layer = *object.layers()[i]; + if (object_layer.bottom_z() > support_layer.print_z + gap_extra_above - EPSILON) + break; + + bool is_overlap = is_layers_overlap(support_layer, object_layer); + for (const ExPolygon& expoly : object_layer.lslices) { + // BBS + bool is_sharptail = !intersection_ex({ expoly }, object_layer.sharp_tails).empty(); + coordf_t trimming_offset = is_sharptail ? scale_(sharp_tail_xy_gap) : + is_overlap ? gap_xy_scaled : + scale_(no_overlap_xy_gap); + polygons_append(polygons_trimming, offset({ expoly }, trimming_offset, SUPPORT_SURFACES_OFFSET_PARAMETERS)); + } + } + if (!m_slicing_params.soluble_interface && m_object_config->thick_bridges) { + // Collect all bottom surfaces, which will be extruded with a bridging flow. + for (; i < object.layers().size(); ++i) { + const Layer& object_layer = *object.layers()[i]; + bool some_region_overlaps = false; + for (LayerRegion* region : object_layer.regions()) { + coordf_t bridging_height = region->region().bridging_height_avg(m_print_config); + if (object_layer.print_z - bridging_height > support_layer.print_z + gap_extra_above - EPSILON) + break; + some_region_overlaps = true; + + bool is_overlap = is_layers_overlap(support_layer, object_layer, bridging_height); + coordf_t trimming_offset = is_overlap ? gap_xy_scaled : scale_(no_overlap_xy_gap); + polygons_append(polygons_trimming, + offset(region->fill_surfaces.filter_by_type(stBottomBridge), trimming_offset, SUPPORT_SURFACES_OFFSET_PARAMETERS)); + } + if (!some_region_overlaps) + break; + } } + + return polygons_trimming; +} + +void TreeSupport::draw_circles(const std::vector>& contact_nodes) +{ + const PrintObjectConfig &config = m_object->config(); + const Print* print = m_object->print(); + bool has_brim = print->has_brim(); + int bottom_gap_layers = round(m_slicing_params.gap_object_support / m_slicing_params.layer_height); + const coordf_t branch_radius = config.tree_support_branch_diameter.value / 2; + const coordf_t branch_radius_scaled = scale_(branch_radius); + bool on_buildplate_only = config.support_on_build_plate_only.value; + Polygon branch_circle; //Pre-generate a circle with correct diameter so that we don't have to recompute those (co)sines every time. + + // Use square support if there are too many nodes per layer because circle support needs much longer time to compute + // Hower circle support can be printed faster, so we prefer circle for fewer nodes case. + const bool SQUARE_SUPPORT = avg_node_per_layer > 200; + const int CIRCLE_RESOLUTION = SQUARE_SUPPORT ? 4 : 100; // The number of vertices in each circle. + + + for (int i = 0; i < CIRCLE_RESOLUTION; i++) + { + double angle; + if (SQUARE_SUPPORT) + angle = (double) i / CIRCLE_RESOLUTION * TAU + PI / 4.0 + nodes_angle; + else + angle = (double) i / CIRCLE_RESOLUTION * TAU; + branch_circle.append(Point(cos(angle) * branch_radius_scaled, sin(angle) * branch_radius_scaled)); + } + + // Performance optimization. Only generate lslices for brim and skirt. + size_t brim_skirt_layers = has_brim ? 1 : 0; + const PrintConfig& print_config = print->config(); + for (const PrintObject* object : print->objects()) + { + size_t skirt_layers = print->has_infinite_skirt() ? object->layer_count() : std::min(size_t(print_config.skirt_height.value), object->layer_count()); + brim_skirt_layers = std::max(brim_skirt_layers, skirt_layers); + } + + // generate areas + const coordf_t layer_height = config.layer_height.value; + const size_t top_interface_layers = config.support_interface_top_layers.value; + const size_t bottom_interface_layers = config.support_interface_bottom_layers.value; + const double diameter_angle_scale_factor = tan(tree_support_branch_diameter_angle * M_PI / 180.);// * layer_height / branch_radius; //Scale factor per layer to produce the desired angle. + const double nozzle_diameter = m_object->print()->config().nozzle_diameter.get_at(0); + const coordf_t line_width = config.get_abs_value("support_line_width", nozzle_diameter); + const coordf_t line_width_scaled = scale_(line_width); + + const bool with_lightning_infill = m_support_params.base_fill_pattern == ipLightning; + coordf_t support_extrusion_width = m_support_params.support_extrusion_width; + const size_t wall_count = config.tree_support_wall_count.value; + + const PrintObjectConfig& object_config = m_object->config(); + BOOST_LOG_TRIVIAL(info) << "draw_circles for object: " << m_object->model_object()->name; + + // coconut: previously std::unordered_map in m_collision_cache is not multi-thread safe which may cause programs stuck, here we change to tbb::concurrent_unordered_map + tbb::parallel_for( + tbb::blocked_range(0, m_object->layer_count()), + [&](const tbb::blocked_range& range) + { + for (size_t layer_nr = range.begin(); layer_nr < range.end(); layer_nr++) + { + if (print->canceled()) + break; + + const std::vector& curr_layer_nodes = contact_nodes[layer_nr]; + SupportLayer* ts_layer = m_object->get_support_layer(layer_nr + m_raft_layers); + assert(ts_layer != nullptr); + + // skip if current layer has no points. This fixes potential crash in get_collision (see jira BBL001-355) + if (curr_layer_nodes.empty()) { + ts_layer->print_z = 0.0; + ts_layer->height = 0.0; + continue; + } + + Node* first_node = curr_layer_nodes.front(); + ts_layer->print_z = first_node->print_z; + ts_layer->height = first_node->height; + if (ts_layer->height < EPSILON) { + continue; + } + + ExPolygons& base_areas = ts_layer->base_areas; + ExPolygons& roof_areas = ts_layer->roof_areas; + ExPolygons& roof_1st_layer = ts_layer->roof_1st_layer; + ExPolygons& floor_areas = ts_layer->floor_areas; + ExPolygons& roof_gap_areas = ts_layer->roof_gap_areas; + coordf_t max_layers_above_base = 0; + coordf_t max_layers_above_roof = 0; + coordf_t max_layers_above_roof1 = 0; + bool has_polygon_node = false; + bool has_circle_node = false; + + BOOST_LOG_TRIVIAL(debug) << "circles at layer " << layer_nr << " contact nodes size=" << contact_nodes[layer_nr].size(); + //Draw the support areas and add the roofs appropriately to the support roof instead of normal areas. + ts_layer->lslices.reserve(contact_nodes[layer_nr].size()); + for (const Node* p_node : contact_nodes[layer_nr]) + { + if (print->canceled()) + break; + + const Node& node = *p_node; + ExPolygons area; + // Generate directly from overhang polygon if one of the following is true: + // 1) node is a normal part of hybrid support + // 2) node is virtual + if (node.type == ePolygon || node.distance_to_top<0) { + if (node.overhang->contour.size() > 100 || node.overhang->holes.size()>1) + area.emplace_back(*node.overhang); + else { + area = offset_ex({ *node.overhang }, scale_(m_ts_data->m_xy_distance)); + } + if (node.type == ePolygon) + has_polygon_node = true; + } + else { + Polygon circle; + size_t layers_to_top = node.distance_to_top; + double scale = calc_branch_radius(branch_radius, node.dist_mm_to_top, diameter_angle_scale_factor) / branch_radius; + + if (/*is_slim*/1) { // draw ellipse along movement direction + double moveX = node.movement.x() / (scale * branch_radius_scaled); + double moveY = node.movement.y() / (scale * branch_radius_scaled); + const double vsize_inv = 0.5 / (0.01 + std::sqrt(moveX * moveX + moveY * moveY)); + double matrix[2*2] = { + scale * (1 + moveX * moveX * vsize_inv),scale * (0 + moveX * moveY * vsize_inv), + scale * (0 + moveX * moveY * vsize_inv),scale * (1 + moveY * moveY * vsize_inv), + }; + for (auto vertex: branch_circle.points) { + vertex = Point(matrix[0] * vertex.x() + matrix[1] * vertex.y(), matrix[2] * vertex.x() + matrix[3] * vertex.y()); + circle.append(node.position + vertex); + } + } else { + for (auto iter = branch_circle.points.begin(); iter != branch_circle.points.end(); iter++) { + Point corner = (*iter) * scale; + circle.append(node.position + corner); + } + } + if (layer_nr == 0 && m_raft_layers == 0) { + double brim_width = + config.tree_support_auto_brim + ? layers_to_top * layer_height / + (scale * branch_radius) * 0.5 + : config.tree_support_brim_width; + circle = offset(circle, scale_(brim_width))[0]; + } + area.emplace_back(ExPolygon(circle)); + // merge overhang to get a smoother interface surface + // Do not merge when buildplate_only is on, because some underneath nodes may have been deleted. + if (top_interface_layers > 0 && node.support_roof_layers_below > 0 && !on_buildplate_only) { + ExPolygons overhang_expanded; + if (node.overhang->contour.size() > 100 || node.overhang->holes.size()>1) + overhang_expanded.emplace_back(*node.overhang); + else { + // 对于有缺陷的模型,overhang膨胀以后可能是空的! + overhang_expanded = offset_ex({ *node.overhang }, scale_(m_ts_data->m_xy_distance)); + } + append(area, overhang_expanded); + } + has_circle_node = true; + } + + if (node.distance_to_top < 0) + append(roof_gap_areas, area); + else if (node.support_roof_layers_below == 1) + { + append(roof_1st_layer, area); + max_layers_above_roof1 = std::max(max_layers_above_roof1, node.dist_mm_to_top); + } + else if (node.support_roof_layers_below > 0) + { + append(roof_areas, area); + max_layers_above_roof = std::max(max_layers_above_roof, node.dist_mm_to_top); + } + else + { + append(base_areas, area); + max_layers_above_base = std::max(max_layers_above_base, node.dist_mm_to_top); + } + + if (layer_nr < brim_skirt_layers) + append(ts_layer->lslices, area); + } + + ts_layer->lslices = std::move(union_ex(ts_layer->lslices)); + + //Must update bounding box which is used in avoid crossing perimeter + ts_layer->lslices_bboxes.clear(); + ts_layer->lslices_bboxes.reserve(ts_layer->lslices.size()); + for (const ExPolygon &expoly : ts_layer->lslices) + ts_layer->lslices_bboxes.emplace_back(get_extents(expoly)); + ts_layer->backup_untyped_slices(); + + m_object->print()->set_status(65, (boost::format( _L("Support: generate polygons at layer %d")) % layer_nr).str()); + + // join roof segments + double contact_dist_scaled = scale_(0.5);// scale_(m_slicing_params.gap_support_object); + roof_areas = std::move(offset2_ex(roof_areas, contact_dist_scaled, -contact_dist_scaled)); + roof_1st_layer = std::move(offset2_ex(roof_1st_layer, contact_dist_scaled, -contact_dist_scaled)); + + // avoid object + //ExPolygons avoid_region_interface = m_ts_data->get_collision(m_ts_data->m_xy_distance, layer_nr); + Polygons avoid_region_interface = get_trim_support_regions(*m_object, ts_layer, m_slicing_params.gap_object_support, m_slicing_params.gap_support_object, m_ts_data->m_xy_distance); + if (has_circle_node) { + roof_areas = avoid_object_remove_extra_small_parts(roof_areas, avoid_region_interface); + roof_1st_layer = avoid_object_remove_extra_small_parts(roof_1st_layer, avoid_region_interface); + } + else { + roof_areas = std::move(diff_ex(roof_areas, avoid_region_interface)); + roof_1st_layer = std::move(diff_ex(roof_1st_layer, avoid_region_interface)); + } + roof_areas = intersection_ex(roof_areas, m_machine_border); + + // roof_1st_layer and roof_areas may intersect, so need to subtract roof_areas from roof_1st_layer + roof_1st_layer = std::move(diff_ex(roof_1st_layer, roof_areas)); + roof_1st_layer = intersection_ex(roof_1st_layer, m_machine_border); + + // let supports touch objects when brim is on + auto avoid_region = m_ts_data->get_collision((layer_nr == 0 && has_brim) ? config.brim_object_gap : m_ts_data->m_xy_distance, layer_nr); + base_areas = avoid_object_remove_extra_small_parts(base_areas, avoid_region); + base_areas = std::move(diff_ex(base_areas, roof_areas)); + base_areas = std::move(diff_ex(base_areas, roof_1st_layer)); + base_areas = std::move(diff_ex(base_areas, roof_gap_areas)); + base_areas = intersection_ex(base_areas, m_machine_border); + + if (SQUARE_SUPPORT) { + // simplify support contours + ExPolygons base_areas_simplified; + for (auto &area : base_areas) { area.simplify(scale_(line_width / 2), &base_areas_simplified); } + base_areas = std::move(base_areas_simplified); + } + //Subtract support floors. We can only compute floor_areas here instead of with roof_areas, + // or we'll get much wider floor than necessary. + if (bottom_interface_layers + bottom_gap_layers > 0) + { + if (layer_nr >= bottom_interface_layers + bottom_gap_layers) + { + for (size_t i = 0; i <= bottom_gap_layers; i++) + { + const Layer* below_layer = m_object->get_layer(layer_nr - bottom_interface_layers - i); + ExPolygons bottom_interface = intersection_ex(base_areas, below_layer->lslices); + floor_areas.insert(floor_areas.end(), bottom_interface.begin(), bottom_interface.end()); + } + } + if (floor_areas.empty() == false) { + floor_areas = std::move(diff_ex(floor_areas, avoid_region_interface)); + floor_areas = std::move(offset2_ex(floor_areas, contact_dist_scaled, -contact_dist_scaled)); + base_areas = std::move(diff_ex(base_areas, offset_ex(floor_areas, 10))); + } + } + if (bottom_gap_layers > 0 && layer_nr > bottom_gap_layers) { + const Layer* below_layer = m_object->get_layer(layer_nr - bottom_gap_layers); + ExPolygons bottom_gap_area = intersection_ex(floor_areas, below_layer->lslices); + if (!bottom_gap_area.empty()) { + floor_areas = std::move(diff_ex(floor_areas, bottom_gap_area)); + } + } + auto &area_groups = ts_layer->area_groups; + for (auto& area : ts_layer->base_areas) { + area_groups.emplace_back(&area, SupportLayer::BaseType, max_layers_above_base); + area_groups.back().need_infill = has_polygon_node; + } + for (auto &area : ts_layer->roof_areas) area_groups.emplace_back(&area, SupportLayer::RoofType, max_layers_above_roof); + for (auto &area : ts_layer->floor_areas) area_groups.emplace_back(&area, SupportLayer::FloorType, 10000); + for (auto &area : ts_layer->roof_1st_layer) area_groups.emplace_back(&area, SupportLayer::Roof1stLayer, max_layers_above_roof1); + + for (auto &area_group : area_groups) { + auto& expoly = area_group.area; + expoly->holes.erase(std::remove_if(expoly->holes.begin(), expoly->holes.end(), + [](auto &hole) { + auto bbox_size = get_extents(hole).size(); + return bbox_size[0] < scale_(2) && bbox_size[1] < scale_(2); + }), + expoly->holes.end()); + } + + } + }); + + + if (with_lightning_infill) + { + const bool global_lightning_infill = true; + + std::vector contours; + std::vector overhangs; + for (int layer_nr = 1; layer_nr < m_object->layer_count(); layer_nr++) { + if (print->canceled()) break; + const std::vector& curr_layer_nodes = contact_nodes[layer_nr]; + SupportLayer* ts_layer = m_object->get_support_layer(layer_nr + m_raft_layers); + assert(ts_layer != nullptr); + + // skip if current layer has no points. This fixes potential crash in get_collision (see jira BBL001-355) + if (curr_layer_nodes.empty()) continue; + if (ts_layer->height < EPSILON) continue; + if (ts_layer->area_groups.empty()) continue; + + ExPolygons& base_areas = ts_layer->base_areas; + + int layer_nr_lower = layer_nr - 1; + for (layer_nr_lower; layer_nr_lower >= 0; layer_nr_lower--) { + if (!m_object->get_support_layer(layer_nr_lower + m_raft_layers)->area_groups.empty()) break; + } + if (layer_nr_lower <= 0) continue; + + SupportLayer* lower_layer = m_object->get_support_layer(layer_nr_lower + m_raft_layers); + ExPolygons& base_areas_lower = lower_layer->base_areas; + + ExPolygons overhang; + if (global_lightning_infill) + { + //search overhangs globally + overhang = std::move(diff_ex(offset_ex(base_areas_lower, -2.0 * scale_(support_extrusion_width)), base_areas)); + } + else + { + //search overhangs only on floating islands + for (auto& base_area : base_areas) + for (auto& hole : base_area.holes) + { + Polygon rev_hole = hole; + rev_hole.make_counter_clockwise(); + ExPolygons ex_hole; + ex_hole.emplace_back(std::move(ExPolygon(rev_hole))); + for (auto& other_area : base_areas) + //if (&other_area != &base_area) + ex_hole = std::move(diff_ex(ex_hole, other_area)); + overhang = std::move(union_ex(overhang, ex_hole)); + } + overhang = std::move(intersection_ex(overhang, offset_ex(base_areas_lower, -0.5 * scale_(support_extrusion_width)))); + } + + overhangs.emplace_back(to_polygons(overhang)); + contours.emplace_back(to_polygons(base_areas_lower)); + printZ_to_lightninglayer[lower_layer->print_z] = overhangs.size() - 1; + +#ifdef SUPPORT_TREE_DEBUG_TO_SVG + draw_two_overhangs_to_svg(m_object->get_support_layer(layer_nr_lower + m_raft_layers), base_areas_lower, to_expolygons(overhangs.back())); +#endif + } + + + auto m_support_material_flow = support_material_flow(m_object, m_slicing_params.layer_height); + coordf_t support_spacing = object_config.support_base_pattern_spacing.value + m_support_material_flow.spacing(); + coordf_t support_density = std::min(1., m_support_material_flow.spacing() / support_spacing * 2); // for lightning infill the density is defined differently, so need to double it + generator = std::make_unique(m_object, contours, overhangs, []() {}, support_density); + } + + else if (!with_infill) { + // move the holes to contour so they can be well supported + + // check if poly's contour intersects with expoly's contour + auto intersects_contour = [](Polygon poly, ExPolygon expoly, Point& pt_on_poly, Point& pt_on_expoly, Point& pt_far_on_poly, float dist_thresh = 0.01) { + float min_dist = std::numeric_limits::max(); + float max_dist = 0; + for (auto from : poly.points) { + for (int i = 0; i < expoly.num_contours(); i++) { + const Point* candidate = expoly.contour_or_hole(i).closest_point(from); + double dist2 = vsize2_with_unscale(*candidate - from); + if (dist2 < min_dist) { + min_dist = dist2; + pt_on_poly = from; + pt_on_expoly = *candidate; + } + if (dist2 > max_dist) { + max_dist = dist2; + pt_far_on_poly = from; + } + if (dist2 < dist_thresh) { return true; } + } + } + return false; + }; + + // polygon pointer: depth, direction, farPoint + std::map> holePropagationInfos; + for (int layer_nr = m_object->layer_count() - 1; layer_nr > 0; layer_nr--) { + if (print->canceled()) break; + m_object->print()->set_status(66, (boost::format(_L("Support: fix holes at layer %d")) % layer_nr).str()); + + const std::vector& curr_layer_nodes = contact_nodes[layer_nr]; + SupportLayer* ts_layer = m_object->get_support_layer(layer_nr + m_raft_layers); + assert(ts_layer != nullptr); + + // skip if current layer has no points. This fixes potential crash in get_collision (see jira BBL001-355) + if (curr_layer_nodes.empty()) continue; + if (ts_layer->height < EPSILON) continue; + if (ts_layer->area_groups.empty()) continue; + + int layer_nr_lower = layer_nr - 1; + for (layer_nr_lower; layer_nr_lower >= 0; layer_nr_lower--) { + if (!m_object->get_support_layer(layer_nr_lower + m_raft_layers)->area_groups.empty()) break; + } + if (layer_nr_lower < 0) continue; + auto& area_groups_lower = m_object->get_support_layer(layer_nr_lower + m_raft_layers)->area_groups; + + for (const auto& area_group : ts_layer->area_groups) { + if (area_group.type != SupportLayer::BaseType) continue; + const auto& area = area_group.area; + for (const auto& hole : area->holes) { + // auto hole_bbox = get_extents(hole).polygon(); + for (auto& area_group_lower : area_groups_lower) { + if (area_group.type != SupportLayer::BaseType) continue; + auto& base_area_lower = *area_group_lower.area; + Point pt_on_poly, pt_on_expoly, pt_far_on_poly; + // if a hole doesn't intersect with lower layer's contours, add a hole to lower layer and move it slightly to the contour + if (base_area_lower.contour.contains(hole.points.front()) && !intersects_contour(hole, base_area_lower, pt_on_poly, pt_on_expoly, pt_far_on_poly)) { + Polygon hole_lower = hole; + Point direction = normal(pt_on_expoly - pt_on_poly, line_width_scaled / 2); + hole_lower.translate(direction); + // note to expand a hole, we need to do negative offset + auto hole_expanded = offset(hole_lower, -line_width_scaled / 4, ClipperLib::JoinType::jtSquare); + if (!hole_expanded.empty()) { + base_area_lower.holes.push_back(std::move(hole_expanded[0])); + holePropagationInfos.insert({ &base_area_lower.holes.back(), {25, direction, pt_far_on_poly} }); + } + break; + } + else if (holePropagationInfos.find(&hole) != holePropagationInfos.end() && std::get<0>(holePropagationInfos[&hole]) > 0 && + base_area_lower.contour.contains(std::get<2>(holePropagationInfos[&hole]))) { + Polygon hole_lower = hole; + auto&& direction = std::get<1>(holePropagationInfos[&hole]); + hole_lower.translate(direction); + // note to shrink a hole, we need to do positive offset + auto hole_expanded = offset(hole_lower, line_width_scaled / 2, ClipperLib::JoinType::jtSquare); + Point farPoint = std::get<2>(holePropagationInfos[&hole]) + direction * 2; + if (!hole_expanded.empty()) { + base_area_lower.holes.push_back(std::move(hole_expanded[0])); + holePropagationInfos.insert({ &base_area_lower.holes.back(), {std::get<0>(holePropagationInfos[&hole]) - 1, direction, farPoint} }); + } + break; + } + } + { + // if roof1 interface is inside a hole, need to expand the interface + for (auto& roof1 : ts_layer->roof_1st_layer) { + //if (hole.contains(roof1.contour.points.front()) && hole.contains(roof1.contour.bounding_box().center())) + bool is_inside_hole = std::all_of(roof1.contour.points.begin(), roof1.contour.points.end(), [&hole](Point& pt) { return hole.contains(pt); }); + if (is_inside_hole) { + Polygon hole_reoriented = hole; + if (roof1.contour.is_counter_clockwise()) + hole_reoriented.make_counter_clockwise(); + else if (roof1.contour.is_clockwise()) + hole_reoriented.make_clockwise(); + auto tmp = union_({ roof1.contour }, { hole_reoriented }); + if (!tmp.empty()) roof1.contour = tmp[0]; + + // make sure 1) roof1 and object 2) roof1 and roof, won't intersect + // Note: We can't replace roof1 directly, as we have recorded its address. + // So instead we need to replace its members one by one. + auto tmp1 = diff_ex(roof1, m_ts_data->get_collision((layer_nr == 0 && has_brim) ? config.brim_object_gap : m_ts_data->m_xy_distance, layer_nr)); + tmp1 = diff_ex(tmp1, ts_layer->roof_areas); + if (!tmp1.empty()) { + roof1.contour = std::move(tmp1[0].contour); + roof1.holes = std::move(tmp1[0].holes); + } + break; + } + } + } + } + } + } + } + + +#ifdef SUPPORT_TREE_DEBUG_TO_SVG + for (int layer_nr = m_object->layer_count() - 1; layer_nr >= 0; layer_nr--) { + SupportLayer* ts_layer = m_object->get_support_layer(layer_nr + m_raft_layers); + ExPolygons& base_areas = ts_layer->base_areas; + ExPolygons& roof_areas = ts_layer->roof_areas; + ExPolygons& roof_1st_layer = ts_layer->roof_1st_layer; + ExPolygons& floor_areas = ts_layer->floor_areas; + if (base_areas.empty() && roof_areas.empty() && roof_1st_layer.empty()) continue; + char fname[10]; sprintf(fname, "%d_%.2f", layer_nr, ts_layer->print_z); + draw_contours_and_nodes_to_svg("", base_areas, roof_areas, roof_1st_layer, {}, {}, get_svg_filename(fname, "circles"), {"base", "roof", "roof1st"}); + } +#endif // SUPPORT_TREE_DEBUG_TO_SVG + + SupportLayerPtrs& ts_layers = m_object->support_layers(); + auto iter = std::remove_if(ts_layers.begin(), ts_layers.end(), [](SupportLayer* ts_layer) { return ts_layer->height < EPSILON; }); + ts_layers.erase(iter, ts_layers.end()); + for (int layer_nr = 0; layer_nr < ts_layers.size(); layer_nr++) { + ts_layers[layer_nr]->upper_layer = layer_nr != ts_layers.size() - 1 ? ts_layers[layer_nr + 1] : nullptr; + ts_layers[layer_nr]->lower_layer = layer_nr > 0 ? ts_layers[layer_nr - 1] : nullptr; } -#endif // NDEBUG } -// For producing circular / elliptical areas from SupportElements (one DrawArea per one SupportElement) -// and for smoothing those areas along the tree branches. -struct DrawArea -{ - // Element to be processed. - SupportElement *element; - // Element below, if there is such an element. nullptr if element is a root of a tree. - SupportElement *child_element; - // Polygons to be extruded for this element. - Polygons polygons; -}; +void TreeSupport::drop_nodes(std::vector>& contact_nodes) +{ + const PrintObjectConfig &config = m_object->config(); + // Use Minimum Spanning Tree to connect the points on each layer and move them while dropping them down. + const coordf_t support_extrusion_width = m_support_params.support_extrusion_width; + const coordf_t layer_height = config.layer_height.value; + const double angle = config.tree_support_branch_angle.value * M_PI / 180.; + const int wall_count = std::max(1, config.tree_support_wall_count.value); + double tan_angle = tan(angle); // when nodes are thick, they can move further. this is the max angle + const coordf_t max_move_distance = (angle < M_PI / 2) ? (coordf_t)(tan_angle * layer_height)*wall_count : std::numeric_limits::max(); + const double max_move_distance2 = max_move_distance * max_move_distance; + const coordf_t branch_radius = config.tree_support_branch_diameter.value / 2; + const size_t tip_layers = branch_radius / layer_height; //The number of layers to be shrinking the circle to create a tip. This produces a 45 degree angle. + const double diameter_angle_scale_factor = tan(tree_support_branch_diameter_angle * M_PI / 180.);//*layer_height / branch_radius; // Scale factor per layer to produce the desired angle. + const coordf_t radius_sample_resolution = m_ts_data->m_radius_sample_resolution; + const bool support_on_buildplate_only = config.support_on_build_plate_only.value; + const size_t bottom_interface_layers = config.support_interface_bottom_layers.value; + const size_t top_interface_layers = config.support_interface_top_layers.value; + float DO_NOT_MOVER_UNDER_MM = is_slim ? 0 : 5; // do not move contact points under 5mm + const auto nozzle_diameter = m_object->print()->config().nozzle_diameter.get_at(m_object->config().support_interface_filament-1); + const auto support_line_width = config.support_line_width.get_abs_value(nozzle_diameter); + + auto get_branch_angle = [this,&config](coordf_t radius) { + if (config.tree_support_branch_angle.value < 30.0) return config.tree_support_branch_angle.value; + return (radius - MIN_BRANCH_RADIUS) / (MAX_BRANCH_RADIUS - MIN_BRANCH_RADIUS) * (config.tree_support_branch_angle.value - 30.0) + 30.0; + }; + auto get_max_move_dist = [this, &config, branch_radius, tip_layers, diameter_angle_scale_factor, wall_count, support_extrusion_width, support_line_width](const Node *node, int power = 1) { + double move_dist = node->max_move_dist; + if (node->max_move_dist == 0) { + if (node->radius == 0) node->radius = calc_branch_radius(branch_radius, node->dist_mm_to_top, diameter_angle_scale_factor); + double angle = config.tree_support_branch_angle.value; + if (angle > 30.0 && node->radius > MIN_BRANCH_RADIUS) + angle = (node->radius - MIN_BRANCH_RADIUS) / (MAX_BRANCH_RADIUS - MIN_BRANCH_RADIUS) * (config.tree_support_branch_angle.value - 30.0) + 30.0; + double tan_angle = tan(angle * M_PI / 180); + int wall_count_ = node->radius > 2 * support_line_width ? wall_count : 1; + node->max_move_dist = (angle < 90) ? (coordf_t) (tan_angle * node->height) * wall_count_ : std::numeric_limits::max(); + node->max_move_dist = std::min(node->max_move_dist, support_extrusion_width); + move_dist = node->max_move_dist; + } + if (power == 2) move_dist = SQ(move_dist); + return move_dist; + }; + + m_ts_data->layer_heights = plan_layer_heights(contact_nodes); + std::vector &layer_heights = m_ts_data->layer_heights; + if (layer_heights.empty()) return; + + std::unordered_set to_free_node_set; + m_spanning_trees.resize(contact_nodes.size()); + //m_mst_line_x_layer_contour_caches.resize(contact_nodes.size()); + + if (0) + {// get outlines below and avoidance area using tbb + // This part only takes very little time, so we disable it. + typedef std::chrono::high_resolution_clock clock_; + typedef std::chrono::duration > second_; + std::chrono::time_point t0{ clock_::now() }; + + // get all the possible radiis + std::vector > all_layer_radius(m_highest_overhang_layer+1); + std::vector> all_layer_node_dist(m_highest_overhang_layer + 1); + for (size_t layer_nr = m_highest_overhang_layer; layer_nr > 0; layer_nr--) + { + if (layer_heights[layer_nr].height < EPSILON) continue; + auto& layer_radius = all_layer_radius[layer_nr]; + auto& layer_node_dist = all_layer_node_dist[layer_nr]; + for (Node *p_node : contact_nodes[layer_nr]) { + layer_node_dist.emplace(p_node->dist_mm_to_top); + } + size_t layer_nr_next = layer_heights[layer_nr].next_layer_nr; + if (layer_nr_next <= m_highest_overhang_layer && layer_nr_next>0) { + for (auto node_dist : layer_node_dist) + all_layer_node_dist[layer_nr_next].emplace(node_dist + layer_heights[layer_nr].height); + } + for (auto node_dist : layer_node_dist) { + layer_radius.emplace(calc_branch_radius(branch_radius, node_dist, diameter_angle_scale_factor)); + } + } + // parallel pre-compute avoidance + //tbb::parallel_for(tbb::blocked_range(1, m_highest_overhang_layer), [&](const tbb::blocked_range &range) { + //for (size_t layer_nr = range.begin(); layer_nr < range.end(); layer_nr++) { + for (size_t layer_nr = 0; layer_nr < all_layer_radius.size(); layer_nr++) { + BOOST_LOG_TRIVIAL(debug) << "pre calculate_avoidance layer=" << layer_nr; + for (auto node_radius : all_layer_radius[layer_nr]) { + m_ts_data->get_avoidance(0, layer_nr); + m_ts_data->get_avoidance(node_radius, layer_nr); + } + } + //}); + + double duration{ std::chrono::duration_cast(clock_::now() - t0).count() }; + BOOST_LOG_TRIVIAL(debug) << "before m_avoidance_cache.size()=" << m_ts_data->m_avoidance_cache.size() + << ", takes " << duration << " secs."; + } + + for (size_t layer_nr = contact_nodes.size() - 1; layer_nr > 0; layer_nr--) // Skip layer 0, since we can't drop down the vertices there. + { + if (m_object->print()->canceled()) + break; + + auto& layer_contact_nodes = contact_nodes[layer_nr]; + if (layer_contact_nodes.empty()) + continue; + + int layer_nr_next = layer_heights[layer_nr].next_layer_nr; + coordf_t print_z_next = layer_heights[layer_nr_next].print_z; + coordf_t height_next = layer_heights[layer_nr_next].height; + + std::deque> unsupported_branch_leaves; // All nodes that are leaves on this layer that would result in unsupported ('mid-air') branches. + const Layer* ts_layer = m_object->get_support_layer(layer_nr); + + m_object->print()->set_status(60, (boost::format(_L("Support: propagate branches at layer %d")) % layer_nr).str()); + + Polygons layer_contours = m_ts_data->get_contours_with_holes(layer_nr); + //std::unordered_map& mst_line_x_layer_contour_cache = m_mst_line_x_layer_contour_caches[layer_nr]; + std::unordered_map mst_line_x_layer_contour_cache; + auto is_line_cut_by_contour = [&mst_line_x_layer_contour_cache,&layer_contours](Point a, Point b) + { + auto iter = mst_line_x_layer_contour_cache.find({ a, b }); + if (iter != mst_line_x_layer_contour_cache.end()) { + if (iter->second) + return true; + } + else { + profiler.tic(); + Line ln(b, a); + Lines pls_intersect = intersection_ln(ln, layer_contours); + mst_line_x_layer_contour_cache.insert({ {a, b}, !pls_intersect.empty() }); + mst_line_x_layer_contour_cache.insert({ ln, !pls_intersect.empty() }); + profiler.stage_add(STAGE_intersection_ln, true); + if (!pls_intersect.empty()) + return true; + } + return false; + }; + + //Group together all nodes for each part. + const ExPolygons& parts = m_ts_data->get_avoidance(0, layer_nr); + std::vector> nodes_per_part(1 + parts.size()); //All nodes that aren't inside a part get grouped together in the 0th part. + for (Node* p_node : layer_contact_nodes) + { + const Node& node = *p_node; + + if (node.distance_to_top < 0) { + // gap nodes do not merge or move + Node* next_node = new Node(p_node->position, p_node->distance_to_top + 1, layer_nr_next, p_node->support_roof_layers_below - 1, p_node->to_buildplate, p_node, + print_z_next, height_next); + get_max_move_dist(next_node); + next_node->is_merged = false; + contact_nodes[layer_nr_next].emplace_back(next_node); + continue; + } + if (support_on_buildplate_only && !node.to_buildplate) //Can't rest on model and unable to reach the build plate. Then we must drop the node and leave parts unsupported. + { + unsupported_branch_leaves.push_front({ layer_nr, p_node }); + continue; + } + if (node.to_buildplate || parts.empty()) //It's outside, so make it go towards the build plate. + { + nodes_per_part[0][node.position] = p_node; + continue; + } + + /* Find which part this node is located in and group the nodes in + * the same part together. Since nodes have a radius and the + * avoidance areas are offset by that radius, the set of parts may + * be different per node. Here we consider a node to be inside the + * part that is closest. The node may be inside a bigger part that + * is actually two parts merged together due to an offset. In that + * case we may incorrectly keep two nodes separate, but at least + * every node falls into some group. + */ + coordf_t closest_part_distance2 = std::numeric_limits::max(); + size_t closest_part = -1; + for (size_t part_index = 0; part_index < parts.size(); part_index++) + { + //constexpr bool border_result = true; + if (is_inside_ex(parts[part_index], node.position)) //If it's inside, the distance is 0 and this part is considered the best. + { + closest_part = part_index; + closest_part_distance2 = 0; + break; + } + + Point closest_point = *parts[part_index].contour.closest_point(node.position); + const coordf_t distance2 = vsize2_with_unscale(node.position - closest_point); + if (distance2 < closest_part_distance2) + { + closest_part_distance2 = distance2; + closest_part = part_index; + } + } + //Put it in the best one. + nodes_per_part[closest_part + 1][node.position] = p_node; //Index + 1 because the 0th index is the outside part. + } + + //Create a MST for every part. + profiler.tic(); + //std::vector& spanning_trees = m_spanning_trees[layer_nr]; + std::vector spanning_trees; + for (const std::unordered_map& group : nodes_per_part) + { + std::vector points_to_buildplate; + for (const std::pair& entry : group) + { + points_to_buildplate.emplace_back(entry.first); //Just the position of the node. + } + spanning_trees.emplace_back(points_to_buildplate); + } + profiler.stage_add(STAGE_MinimumSpanningTree,true); -/*! - * \brief Draws circles around result_on_layer points of the influence areas - * - * \param linear_data[in] All currently existing influence areas with the layer they are on - * \param layer_tree_polygons[out] Resulting branch areas with the layerindex they appear on. layer_tree_polygons.size() has to be at least linear_data.size() as each Influence area in linear_data will save have at least one (that's why it's a vector) corresponding branch area in layer_tree_polygons. - * \param inverse_tree_order[in] A mapping that returns the child of every influence area. - */ -static void generate_branch_areas( - const TreeModelVolumes &volumes, - const TreeSupportSettings &config, - const std::vector &move_bounds, - std::vector &linear_data, - std::function throw_on_cancel) -{ -#ifdef SLIC3R_TREESUPPORTS_PROGRESS - double progress_total = TREE_PROGRESS_PRECALC_AVO + TREE_PROGRESS_PRECALC_COLL + TREE_PROGRESS_GENERATE_NODES + TREE_PROGRESS_AREA_CALC; - constexpr int progress_report_steps = 10; - const size_t progress_inserts_check_interval = linear_data.size() / progress_report_steps; - std::mutex critical_sections; -#endif // SLIC3R_TREESUPPORTS_PROGRESS - - // Pre-generate a circle with correct diameter so that we don't have to recompute those (co)sines every time. - const Polygon branch_circle = make_circle(config.branch_radius, SUPPORT_TREE_CIRCLE_RESOLUTION); - - tbb::parallel_for(tbb::blocked_range(0, linear_data.size()), - [&volumes, &config, &move_bounds, &linear_data, &branch_circle, &throw_on_cancel](const tbb::blocked_range &range) { - for (size_t idx = range.begin(); idx < range.end(); ++ idx) { - DrawArea &draw_area = linear_data[idx]; - const LayerIndex layer_idx = draw_area.element->state.layer_idx; - const coord_t radius = support_element_radius(config, *draw_area.element); - bool parent_uses_min = false; - - // Calculate multiple ovalized circles, to connect with every parent and child. Also generate regular circle for the current layer. Merge all these into one area. - std::vector> movement_directions{ std::pair(Point(0, 0), radius) }; - if (! draw_area.element->state.skip_ovalisation) { - if (draw_area.child_element != nullptr) { - const Point movement = draw_area.child_element->state.result_on_layer - draw_area.element->state.result_on_layer; - movement_directions.emplace_back(movement, radius); - } - const SupportElements *layer_above = layer_idx + 1 < LayerIndex(move_bounds.size()) ? &move_bounds[layer_idx + 1] : nullptr; - for (int32_t parent_idx : draw_area.element->parents) { - const SupportElement &parent = (*layer_above)[parent_idx]; - const Point movement = parent.state.result_on_layer - draw_area.element->state.result_on_layer; - //FIXME why max(..., config.support_line_width)? - movement_directions.emplace_back(movement, std::max(support_element_radius(config, parent), config.support_line_width)); - parent_uses_min |= parent.state.use_min_xy_dist; +#ifdef SUPPORT_TREE_DEBUG_TO_SVG + coordf_t branch_radius_temp = 0; + coordf_t max_y = std::numeric_limits::min(); + draw_layer_mst(std::to_string(ts_layer->print_z), spanning_trees, m_object->get_layer(layer_nr)->lslices); +#endif + for (size_t group_index = 0; group_index < nodes_per_part.size(); group_index++) + { + const MinimumSpanningTree& mst = spanning_trees[group_index]; + //In the first pass, merge all nodes that are close together. + std::unordered_set to_delete; + for (const std::pair& entry : nodes_per_part[group_index]) + { + Node* p_node = entry.second; + Node& node = *p_node; + if (to_delete.find(p_node) != to_delete.end()) + { + continue; //Delete this node (don't create a new node for it on the next layer). } - } + const std::vector& neighbours = mst.adjacent_nodes(node.position); + if (node.type == ePolygon) { + // Remove all neighbours that are completely inside the polygon and merge them into this node. + for (const Point &neighbour : neighbours) { + Node * neighbour_node = nodes_per_part[group_index][neighbour]; + coord_t neighbour_radius = scale_(calc_branch_radius(branch_radius, neighbour_node->dist_mm_to_top, diameter_angle_scale_factor)); + Point pt_north = neighbour + Point(0, neighbour_radius), pt_south = neighbour - Point(0, neighbour_radius), + pt_west = neighbour - Point(neighbour_radius, 0), pt_east = neighbour + Point(neighbour_radius, 0); + if (is_inside_ex(*node.overhang, neighbour) && is_inside_ex(*node.overhang, pt_north) && is_inside_ex(*node.overhang, pt_south) + && is_inside_ex(*node.overhang, pt_west) && is_inside_ex(*node.overhang, pt_east)){ + node.distance_to_top = std::max(node.distance_to_top, neighbour_node->distance_to_top); + node.support_roof_layers_below = std::max(node.support_roof_layers_below, neighbour_node->support_roof_layers_below); + node.dist_mm_to_top = std::max(node.dist_mm_to_top, neighbour_node->dist_mm_to_top); + node.merged_neighbours.push_front(neighbour_node); + node.merged_neighbours.insert(node.merged_neighbours.end(), neighbour_node->merged_neighbours.begin(), neighbour_node->merged_neighbours.end()); + node.is_merged = true; + to_delete.insert(neighbour_node); + } + } + } + else if (neighbours.size() == 1 && vsize2_with_unscale(neighbours[0] - node.position) < max_move_distance2 && mst.adjacent_nodes(neighbours[0]).size() == 1 && + nodes_per_part[group_index][neighbours[0]]->type!=ePolygon) // We have just two nodes left, and they're very close, and the only neighbor is not ePolygon + { + //Insert a completely new node and let both original nodes fade. + Point next_position = (node.position + neighbours[0]) / 2; //Average position of the two nodes. - const Polygons &collision = volumes.getCollision(0, layer_idx, parent_uses_min || draw_area.element->state.use_min_xy_dist); - auto generateArea = [&collision, &draw_area, &branch_circle, branch_radius = config.branch_radius, support_line_width = config.support_line_width, &movement_directions] - (coord_t aoffset, double &max_speed) { - Polygons poly; - max_speed = 0; - for (std::pair movement : movement_directions) { - max_speed = std::max(max_speed, movement.first.cast().norm()); - - // Visualization: https://jsfiddle.net/0zvcq39L/2/ - // Ovalizes the circle to an ellipse, that contains both old center and new target position. - double used_scale = (movement.second + aoffset) / (1.0 * branch_radius); - Point center_position = draw_area.element->state.result_on_layer + movement.first / 2; - const double moveX = movement.first.x() / (used_scale * branch_radius); - const double moveY = movement.first.y() / (used_scale * branch_radius); - const double vsize_inv = 0.5 / (0.01 + std::sqrt(moveX * moveX + moveY * moveY)); - - double matrix[] = { - used_scale * (1 + moveX * moveX * vsize_inv), - used_scale * (0 + moveX * moveY * vsize_inv), - used_scale * (0 + moveX * moveY * vsize_inv), - used_scale * (1 + moveY * moveY * vsize_inv), - }; - Polygon circle; - for (Point vertex : branch_circle) - circle.points.emplace_back(center_position + Point(matrix[0] * vertex.x() + matrix[1] * vertex.y(), matrix[2] * vertex.x() + matrix[3] * vertex.y())); - poly.emplace_back(std::move(circle)); - } - - // There seem to be some rounding errors, causing a branch to be a tiny bit further away from the model that it has to be. - // This can cause the tip to be slightly further away front the overhang (x/y wise) than optimal. This fixes it, and for every other part, 0.05mm will not be noticed. - poly = diff_clipped(offset(union_(poly), std::min(coord_t(50), support_line_width / 4), jtMiter, 1.2), collision); - return poly; - }; - - // Ensure branch area will not overlap with model/collision. This can happen because of e.g. ovalization or increase_until_radius. - double max_speed; - Polygons polygons = generateArea(0, max_speed); - const bool fast_relative_movement = max_speed > radius * 0.75; - - if (fast_relative_movement || support_element_radius(config, *draw_area.element) - support_element_collision_radius(config, draw_area.element->state) > config.support_line_width) { - // Simulate the path the nozzle will take on the outermost wall. - // If multiple parts exist, the outer line will not go all around the support part potentially causing support material to be printed mid air. - ExPolygons nozzle_path = offset_ex(polygons, - config.support_line_width / 2); - if (nozzle_path.size() > 1) { - // Just try to make the area a tiny bit larger. - polygons = generateArea(config.support_line_width / 2, max_speed); - nozzle_path = offset_ex(polygons, -config.support_line_width / 2); - // If larger area did not fix the problem, all parts off the nozzle path that do not contain the center point are removed, hoping for the best. - if (nozzle_path.size() > 1) { - ExPolygons polygons_with_correct_center; - for (ExPolygon &part : nozzle_path) { - bool drop = false; - if (! part.contains(draw_area.element->state.result_on_layer)) { - // try a fuzzy inside as sometimes the point should be on the border, but is not because of rounding errors... - Point pt = draw_area.element->state.result_on_layer; - move_inside(to_polygons(part), pt, 0); - drop = (draw_area.element->state.result_on_layer - pt).cast().norm() >= scaled(0.025); - } - if (! drop) - polygons_with_correct_center.emplace_back(std::move(part)); + const coordf_t branch_radius_node = calc_branch_radius(branch_radius, node.dist_mm_to_top, diameter_angle_scale_factor); + + auto avoid_layer = m_ts_data->get_avoidance(branch_radius_node, layer_nr_next); + if (group_index == 0) + { + //Avoid collisions. + const coordf_t max_move_between_samples = max_move_distance + radius_sample_resolution + EPSILON; //100 micron extra for rounding errors. + move_out_expolys(avoid_layer, next_position, radius_sample_resolution + EPSILON, max_move_between_samples); + } + + Node* neighbour = nodes_per_part[group_index][neighbours[0]]; + Node* node_; + if (p_node->parent && neighbour->parent) + node_ = (node.dist_mm_to_top >= neighbour->dist_mm_to_top && p_node->parent) ? p_node : neighbour; + else + node_ = p_node->parent ? p_node : neighbour; + // Make sure the next pass doesn't drop down either of these (since that already happened). + node_->merged_neighbours.push_front(node_ == p_node ? neighbour : p_node); + const bool to_buildplate = !is_inside_ex(m_ts_data->get_avoidance(0, layer_nr_next), next_position); + Node * next_node = new Node(next_position, node_->distance_to_top + 1, layer_nr_next, node_->support_roof_layers_below-1, to_buildplate, node_, + print_z_next, height_next); + next_node->movement = next_position - node.position; + get_max_move_dist(next_node); + next_node->is_merged = true; + contact_nodes[layer_nr_next].push_back(next_node); + + + to_delete.insert(neighbour); + to_delete.insert(p_node); + } + else if (neighbours.size() > 1) //Don't merge leaf nodes because we would then incur movement greater than the maximum move distance. + { + //Remove all neighbours that are too close and merge them into this node. + for (const Point& neighbour : neighbours) + { + if (vsize2_with_unscale(neighbour - node.position) < /*max_move_distance2*/get_max_move_dist(&node,2)) + { + Node* neighbour_node = nodes_per_part[group_index][neighbour]; + if (neighbour_node->type == ePolygon) continue; + + node.distance_to_top = std::max(node.distance_to_top, neighbour_node->distance_to_top); + node.support_roof_layers_below = std::max(node.support_roof_layers_below, neighbour_node->support_roof_layers_below); + node.dist_mm_to_top = std::max(node.dist_mm_to_top, neighbour_node->dist_mm_to_top); + node.merged_neighbours.push_front(neighbour_node); + node.merged_neighbours.insert(node.merged_neighbours.end(), neighbour_node->merged_neighbours.begin(), neighbour_node->merged_neighbours.end()); + node.is_merged = true; + to_delete.insert(neighbour_node); } - // Increase the area again, to ensure the nozzle path when calculated later is very similar to the one assumed above. - assert(contains(polygons, draw_area.element->state.result_on_layer)); - polygons = diff_clipped(offset(polygons_with_correct_center, config.support_line_width / 2, jtMiter, 1.2), - //FIXME Vojtech: Clipping may split the region into multiple pieces again, reversing the fixing effort. - collision); } } } - draw_area.polygons = std::move(polygons); + //In the second pass, move all middle nodes. + for (const std::pair& entry : nodes_per_part[group_index]) + { + Node* p_node = entry.second; + const Node& node = *p_node; + if (to_delete.find(p_node) != to_delete.end()) + { + continue; + } + if (node.type == ePolygon) { + // polygon node do not merge or move + const bool to_buildplate = !is_inside_ex(m_ts_data->m_layer_outlines[layer_nr], p_node->position); + Node * next_node = new Node(p_node->position, p_node->distance_to_top + 1, layer_nr_next, p_node->support_roof_layers_below - 1, to_buildplate, + p_node, print_z_next, height_next); + next_node->max_move_dist = 0; + next_node->is_merged = false; + contact_nodes[layer_nr_next].emplace_back(next_node); + continue; + } -#ifdef SLIC3R_TREESUPPORTS_PROGRESS - if (idx % progress_inserts_check_interval == 0) { - std::lock_guard critical_section_progress(critical_sections); - progress_total += TREE_PROGRESS_GENERATE_BRANCH_AREAS / progress_report_steps; - Progress::messageProgress(Progress::Stage::SUPPORT, progress_total * m_progress_multiplier + m_progress_offset, TREE_PROGRESS_TOTAL); - } -#endif - throw_on_cancel(); - } - }); -} + //If the branch falls completely inside a collision area (the entire branch would be removed by the X/Y offset), delete it. + if (group_index > 0 && is_inside_ex(m_ts_data->get_collision(m_ts_data->m_xy_distance, layer_nr), node.position)) + { + const coordf_t branch_radius_node = calc_branch_radius(branch_radius, node.dist_mm_to_top, diameter_angle_scale_factor); + Point to_outside = projection_onto(m_ts_data->get_collision(m_ts_data->m_xy_distance, layer_nr), node.position); + double dist2_to_outside = vsize2_with_unscale(node.position - to_outside); + if (dist2_to_outside >= branch_radius_node * branch_radius_node) //Too far inside. + { + if (support_on_buildplate_only) + { + unsupported_branch_leaves.push_front({ layer_nr, p_node }); + } + else { + Node* pn = p_node; + for (int i = 0; i <= bottom_interface_layers && pn; i++, pn = pn->parent) + pn->support_floor_layers_above = bottom_interface_layers - i + 1; // +1 so the parent node has support_floor_layers_above=2 + to_delete.insert(p_node); + } + continue; + } + // if the link between parent and current is cut by contours, mark current as bottom contact node + if (p_node->parent && intersection_ln({p_node->position, p_node->parent->position}, layer_contours).empty()==false) + { + Node* pn = p_node->parent; + for (int i = 0; i <= bottom_interface_layers && pn; i++, pn = pn->parent) + pn->support_floor_layers_above = bottom_interface_layers - i + 1; + to_delete.insert(p_node); + continue; + } + } + Point next_layer_vertex = node.position; + Point move_to_neighbor_center; + std::vector moves; + std::vector weights; + const std::vector neighbours = mst.adjacent_nodes(node.position); + // 1. do not merge neighbors under 5mm + // 2. Only merge node with single neighbor in distance between [max_move_distance, 10mm/layer_height] + float dist2_to_first_neighbor = neighbours.empty() ? 0 : vsize2_with_unscale(neighbours[0] - node.position); + if (ts_layer->print_z > DO_NOT_MOVER_UNDER_MM && + (neighbours.size() > 1 || (neighbours.size() == 1 && dist2_to_first_neighbor >= max_move_distance2))) // Only nodes that aren't about to collapse. + { + // Move towards the average position of all neighbours. + Point sum_direction(0, 0); + for (const Point &neighbour : neighbours) { + // do not move to the neighbor to be deleted + Node *neighbour_node = nodes_per_part[group_index][neighbour]; + if (to_delete.find(neighbour_node) != to_delete.end()) continue; + + Point direction = neighbour - node.position; + // do not move to neighbor that's too far away (即使以最大速度移动,在接触热床之前都无法汇聚) + float dist2_to_neighbor = vsize2_with_unscale(direction); + + coordf_t branch_bottom_radius = calc_branch_radius(branch_radius, node.dist_mm_to_top + node.print_z, diameter_angle_scale_factor); + coordf_t neighbour_bottom_radius = calc_branch_radius(branch_radius, neighbour_node->dist_mm_to_top + neighbour_node->print_z, diameter_angle_scale_factor); + double max_converge_distance = tan_angle * (ts_layer->print_z - DO_NOT_MOVER_UNDER_MM) + std::max(branch_bottom_radius, neighbour_bottom_radius); + if (dist2_to_neighbor > max_converge_distance * max_converge_distance) continue; + + if (is_line_cut_by_contour(node.position, neighbour)) continue; + + if (!is_strong) + sum_direction += direction * (1 / dist2_to_neighbor); + else + sum_direction += direction; + } -/*! - * \brief Applies some smoothing to the outer wall, intended to smooth out sudden jumps as they can happen when a branch moves though a hole. - * - * \param layer_tree_polygons[in,out] Resulting branch areas with the layerindex they appear on. - */ -static void smooth_branch_areas( - const TreeSupportSettings &config, - std::vector &move_bounds, - std::vector &linear_data, - const std::vector &linear_data_layers, - std::function throw_on_cancel) -{ -#ifdef SLIC3R_TREESUPPORTS_PROGRESS - double progress_total = TREE_PROGRESS_PRECALC_AVO + TREE_PROGRESS_PRECALC_COLL + TREE_PROGRESS_GENERATE_NODES + TREE_PROGRESS_AREA_CALC + TREE_PROGRESS_GENERATE_BRANCH_AREAS; -#endif // SLIC3R_TREESUPPORTS_PROGRESS - - const coord_t max_radius_change_per_layer = 1 + config.support_line_width / 2; // this is the upper limit a radius may change per layer. +1 to avoid rounding errors - - // smooth upwards - for (LayerIndex layer_idx = 0; layer_idx < LayerIndex(move_bounds.size()) - 1; ++ layer_idx) { - const size_t processing_base = linear_data_layers[layer_idx]; - const size_t processing_base_above = linear_data_layers[layer_idx + 1]; - const SupportElements &layer_above = move_bounds[layer_idx + 1]; - tbb::parallel_for(tbb::blocked_range(0, processing_base_above - processing_base), - [&](const tbb::blocked_range &range) { - for (size_t processing_idx = range.begin(); processing_idx < range.end(); ++ processing_idx) { - DrawArea &draw_area = linear_data[processing_base + processing_idx]; - assert(draw_area.element->state.layer_idx == layer_idx); - double max_outer_wall_distance = 0; - bool do_something = false; - for (int32_t parent_idx : draw_area.element->parents) { - const SupportElement &parent = layer_above[parent_idx]; - assert(parent.state.layer_idx == layer_idx + 1); - if (support_element_radius(config, parent) != support_element_collision_radius(config, parent)) { - do_something = true; - max_outer_wall_distance = std::max(max_outer_wall_distance, - (draw_area.element->state.result_on_layer - parent.state.result_on_layer).cast().norm() - (support_element_radius(config, *draw_area.element) - support_element_radius(config, parent))); + if (!is_strong) + move_to_neighbor_center = sum_direction; + else { + if (vsize2_with_unscale(sum_direction) <= max_move_distance2) { + move_to_neighbor_center = sum_direction; + } else { + move_to_neighbor_center = normal(sum_direction, scale_(get_max_move_dist(&node))); + } } } - max_outer_wall_distance += max_radius_change_per_layer; // As this change is a bit larger than what usually appears, lost radius can be slowly reclaimed over the layers. - if (do_something) { - assert(contains(draw_area.polygons, draw_area.element->state.result_on_layer)); - Polygons max_allowed_area = offset(draw_area.polygons, float(max_outer_wall_distance), jtMiter, 1.2); - for (int32_t parent_idx : draw_area.element->parents) { - const SupportElement &parent = layer_above[parent_idx]; -#ifndef NDEBUG - assert(parent.state.layer_idx == layer_idx + 1); - assert(contains(linear_data[processing_base_above + parent_idx].polygons, parent.state.result_on_layer)); - double radius_increase = support_element_radius(config, *draw_area.element) - support_element_radius(config, parent); - assert(radius_increase >= 0); - double shift = (draw_area.element->state.result_on_layer - parent.state.result_on_layer).cast().norm(); - assert(shift < radius_increase + 2. * config.maximum_move_distance_slow); -#endif // NDEBUG - if (support_element_radius(config, parent) != support_element_collision_radius(config, parent)) { - // No other element on this layer than the current one may be connected to &parent, - // thus it is safe to update parent's DrawArea directly. - Polygons &dst = linear_data[processing_base_above + parent_idx].polygons; -// Polygons orig = dst; - if (! dst.empty()) { - dst = intersection(dst, max_allowed_area); -#if 0 - if (dst.empty()) { - static int irun = 0; - SVG::export_expolygons(debug_out_path("treesupport-extrude_areas-smooth-error-%d.svg", irun ++), - { { { union_ex(max_allowed_area) }, { "max_allowed_area", "yellow", 0.5f } }, - { { union_ex(orig) }, { "orig", "red", "black", "", scaled(0.1f), 0.5f } } }); - ::MessageBoxA(nullptr, "TreeSupport smoothing bug", "Bug detected!", MB_OK | MB_SYSTEMMODAL | MB_SETFOREGROUND | MB_ICONWARNING); - } + + const coordf_t branch_radius_node = calc_branch_radius(branch_radius, node.dist_mm_to_top/*+node.print_z*/, diameter_angle_scale_factor); +#ifdef SUPPORT_TREE_DEBUG_TO_SVG + if (node.position(1) > max_y) { + max_y = node.position(1); + branch_radius_temp = branch_radius_node; + } #endif - } - } + auto avoid_layer = m_ts_data->get_avoidance(branch_radius_node, layer_nr_next); + + Point to_outside = projection_onto(avoid_layer, node.position); + Point direction_to_outer = to_outside - node.position; + double dist2_to_outer = vsize2_with_unscale(direction_to_outer); + // don't move if + // 1) line of node and to_outside is cut by contour (means supports may intersect with object) + // 2) it's impossible to move to build plate + if (is_line_cut_by_contour(node.position, to_outside) || dist2_to_outer > max_move_distance2 * SQ(layer_nr) || + !is_inside_ex(avoid_layer, node.position)) { + // try move to outside of lower layer instead + Point candidate_vertex = node.position; + const coordf_t max_move_between_samples = max_move_distance + radius_sample_resolution + EPSILON; // 100 micron extra for rounding errors. + bool is_outside = move_out_expolys(avoid_layer, candidate_vertex, max_move_between_samples, max_move_between_samples); + if (is_outside) { + direction_to_outer = candidate_vertex - node.position; + dist2_to_outer = vsize2_with_unscale(direction_to_outer); + } else { + direction_to_outer = Point(0, 0); + dist2_to_outer = 0; + } + } + // move to the averaged direction of neighbor center and contour edge if they are roughly same direction + Point movement; + if (!is_strong) + movement = move_to_neighbor_center*2 + (dist2_to_outer > EPSILON ? direction_to_outer * (1 / dist2_to_outer) : Point(0, 0)); + else { + if (movement.dot(move_to_neighbor_center) >= 0.2 || move_to_neighbor_center == Point(0, 0)) + movement = direction_to_outer + move_to_neighbor_center; + else + movement = move_to_neighbor_center; // otherwise move to neighbor center first + } + + if (vsize2_with_unscale(movement) > get_max_move_dist(&node,2)) + movement = normal(movement, scale_(get_max_move_dist(&node))); + + // add momentum to force smooth movement + //movement = movement * 0.5 + p_node->movement * 0.5; + + next_layer_vertex += movement; + + if (group_index == 0) { + // Avoid collisions. + const coordf_t max_move_between_samples = get_max_move_dist(&node, 1) + radius_sample_resolution + EPSILON; // 100 micron extra for rounding errors. + bool is_outside = move_out_expolys(avoid_layer, next_layer_vertex, radius_sample_resolution + EPSILON, max_move_between_samples); + if (!is_outside) { + Point candidate_vertex = node.position; + is_outside = move_out_expolys(avoid_layer, candidate_vertex, radius_sample_resolution + EPSILON, max_move_between_samples); + if (is_outside) { next_layer_vertex = candidate_vertex; } } } - throw_on_cancel(); + + const bool to_buildplate = !is_inside_ex(m_ts_data->m_layer_outlines[layer_nr], next_layer_vertex);// !is_inside_ex(m_ts_data->get_avoidance(m_ts_data->m_xy_distance, layer_nr - 1), next_layer_vertex); + Node * next_node = new Node(next_layer_vertex, node.distance_to_top + 1, layer_nr_next, node.support_roof_layers_below - 1, to_buildplate, p_node, + print_z_next, height_next); + next_node->movement = movement; + get_max_move_dist(next_node); + next_node->is_merged = false; + contact_nodes[layer_nr_next].push_back(next_node); } - }); - } + } -#ifdef SLIC3R_TREESUPPORTS_PROGRESS - progress_total += TREE_PROGRESS_SMOOTH_BRANCH_AREAS / 2; - Progress::messageProgress(Progress::Stage::SUPPORT, progress_total * m_progress_multiplier + m_progress_offset, TREE_PROGRESS_TOTAL); // It is just assumed that both smoothing loops together are one third of the time spent in this function. This was guessed. As the whole function is only 10%, and the smoothing is hard to predict a progress report in the loop may be not useful. +#ifdef SUPPORT_TREE_DEBUG_TO_SVG + if (contact_nodes[layer_nr].empty() == false) { + draw_contours_and_nodes_to_svg((boost::format("%.2f") % contact_nodes[layer_nr][0]->print_z).str(), m_ts_data->get_avoidance(0, layer_nr), + m_ts_data->get_avoidance(branch_radius_temp, layer_nr), + m_ts_data->m_layer_outlines_below[layer_nr], + contact_nodes[layer_nr], contact_nodes[layer_nr_next], "contact_points", { "overhang","avoid","outline" }, { "blue","red","yellow" }); + + BOOST_LOG_TRIVIAL(debug) << "drop_nodes layer " << layer_nr << ", print_z=" << ts_layer->print_z; + for (size_t i = 0; i < std::min(size_t(5), contact_nodes[layer_nr].size()); i++) { + auto &node = contact_nodes[layer_nr][i]; + BOOST_LOG_TRIVIAL(debug) << "\t node " << i << ", pos=" << node->position << ", move = " << node->movement << ", is_merged=" << node->is_merged; + } + } #endif - // smooth downwards - for (auto& element : move_bounds.back()) - element.state.marked = false; - for (int layer_idx = int(move_bounds.size()) - 2; layer_idx >= 0; -- layer_idx) { - const size_t processing_base = linear_data_layers[layer_idx]; - const size_t processing_base_above = linear_data_layers[layer_idx + 1]; - const SupportElements &layer_above = move_bounds[layer_idx + 1]; - tbb::parallel_for(tbb::blocked_range(0, processing_base_above - processing_base), - [&](const tbb::blocked_range &range) { - for (size_t processing_idx = range.begin(); processing_idx < range.end(); ++ processing_idx) { - DrawArea &draw_area = linear_data[processing_base + processing_idx]; - bool do_something = false; - Polygons max_allowed_area; - for (int32_t parent_idx : draw_area.element->parents) { - const SupportElement &parent = layer_above[parent_idx]; - coord_t max_outer_line_increase = max_radius_change_per_layer; - Polygons result = offset(linear_data[processing_base_above + parent_idx].polygons, max_outer_line_increase, jtMiter, 1.2); - Point direction = draw_area.element->state.result_on_layer - parent.state.result_on_layer; - // move the polygons object - for (auto &outer : result) - for (Point& p : outer) - p += direction; - append(max_allowed_area, std::move(result)); - do_something = do_something || parent.state.marked || support_element_collision_radius(config, parent) != support_element_radius(config, parent); - } - if (do_something) { - // Trim the current drawing areas with max_allowed_area. - Polygons result = intersection(max_allowed_area, draw_area.polygons); - if (area(result) < area(draw_area.polygons)) { - // Mark parent as modified to propagate down. - draw_area.element->state.marked = true; - draw_area.polygons = std::move(result); + // Prune all branches that couldn't find support on either the model or the buildplate (resulting in 'mid-air' branches). + for (;! unsupported_branch_leaves.empty(); unsupported_branch_leaves.pop_back()) + { + const auto& entry = unsupported_branch_leaves.back(); + Node* i_node = entry.second; + for (; i_node != nullptr; i_node = i_node->parent) + { + size_t i_layer = i_node->obj_layer_nr; + std::vector::iterator to_erase = std::find(contact_nodes[i_layer].begin(), contact_nodes[i_layer].end(), i_node); + if (to_erase != contact_nodes[i_layer].end()) + { + // update the parent-child chain + if(i_node->parent) + i_node->parent->child = i_node->child; + if(i_node->child) + i_node->child->parent = i_node->parent; + contact_nodes[i_layer].erase(to_erase); + to_free_node_set.insert(i_node); + + for (Node* neighbour : i_node->merged_neighbours) + { + unsupported_branch_leaves.push_front({ i_layer, neighbour }); } } - throw_on_cancel(); } - }); + } } + + BOOST_LOG_TRIVIAL(debug) << "after m_avoidance_cache.size()=" << m_ts_data->m_avoidance_cache.size(); -#ifdef SLIC3R_TREESUPPORTS_PROGRESS - progress_total += TREE_PROGRESS_SMOOTH_BRANCH_AREAS / 2; - Progress::messageProgress(Progress::Stage::SUPPORT, progress_total * m_progress_multiplier + m_progress_offset, TREE_PROGRESS_TOTAL); -#endif + for (Node *node : to_free_node_set) + { + delete node; + } + to_free_node_set.clear(); } -/*! - * \brief Drop down areas that do rest non-gracefully on the model to ensure the branch actually rests on something. - * - * \param layer_tree_polygons[in] Resulting branch areas with the layerindex they appear on. - * \param linear_data[in] All currently existing influence areas with the layer they are on - * \param dropped_down_areas[out] Areas that have to be added to support all non-graceful areas. - * \param inverse_tree_order[in] A mapping that returns the child of every influence area. - */ -static void drop_non_gracious_areas( - const TreeModelVolumes &volumes, - const std::vector &linear_data, - std::vector &support_layer_storage, - std::function throw_on_cancel) +void TreeSupport::smooth_nodes(std::vector> &contact_nodes) { - const auto _tiny_area_threshold = tiny_area_threshold(); - std::vector>> dropped_down_areas(linear_data.size()); - tbb::parallel_for(tbb::blocked_range(0, linear_data.size()), - [&](const tbb::blocked_range &range) { - for (size_t idx = range.begin(); idx < range.end(); ++ idx) { - // If a element has no child, it connects to whatever is below as no support further down for it will exist. - if (const DrawArea &draw_element = linear_data[idx]; ! draw_element.element->state.to_model_gracious && draw_element.child_element == nullptr) { - Polygons rest_support; - const LayerIndex layer_idx_first = draw_element.element->state.layer_idx - 1; - for (LayerIndex layer_idx = layer_idx_first; area(rest_support) > _tiny_area_threshold && layer_idx >= 0; -- layer_idx) { - rest_support = diff_clipped(layer_idx == layer_idx_first ? draw_element.polygons : rest_support, volumes.getCollision(0, layer_idx, false)); - dropped_down_areas[idx].emplace_back(layer_idx, rest_support); + for (int layer_nr = 0; layer_nr < contact_nodes.size(); layer_nr++) { + std::vector &curr_layer_nodes = contact_nodes[layer_nr]; + if (curr_layer_nodes.empty()) continue; + for (Node *node : curr_layer_nodes) { + node->is_processed = false; + if (layer_nr == 0) node->is_merged = true; // nodes on plate are also merged nodes + } + } + + for (int layer_nr = 0; layer_nr< contact_nodes.size(); layer_nr++) { + std::vector &curr_layer_nodes = contact_nodes[layer_nr]; + if (curr_layer_nodes.empty()) continue; + for (Node *node : curr_layer_nodes) { + if (!node->is_processed) { + std::vector pts; + std::vector branch; + Node * p_node = node; + // add a fixed head + if (node->child) { + pts.push_back(p_node->child->position); + branch.push_back(p_node->child); + } + do { + pts.push_back(p_node->position); + branch.push_back(p_node); + p_node = p_node->parent; + } while (p_node && !p_node->is_processed); + if (pts.size() < 3) continue; + + std::vector pts1 = pts; + // TODO here we assume layer height gap is constant. If not true, need to consider height jump + const int iterations = 100; + for (size_t k = 0; k < iterations; k++) { + for (size_t i = 1; i < pts.size() - 1; i++) { + size_t i2 = i >= 2 ? i - 2 : 0; + size_t i3 = i < pts.size() - 2 ? i + 2 : pts.size() - 1; + Point pt = (pts[i2] + pts[i - 1] + pts[i] + pts[i + 1] + pts[i3]) / 5; + pts1[i] = pt; + if (k == iterations - 1) { + branch[i]->position = pt; + branch[i]->movement = (pts[i + 1] - pts[i - 1]) / 2; + branch[i]->is_processed = true; + } + } + if (k < iterations - 1) + std::swap(pts, pts1); } } - throw_on_cancel(); } - }); - - for (coord_t i = 0; i < static_cast(dropped_down_areas.size()); i++) - for (std::pair &pair : dropped_down_areas[i]) - append(support_layer_storage[pair.first], std::move(pair.second)); + } + // save tree structure for viewing in python + auto& tree_nodes = m_ts_data->tree_nodes; + std::map ptr2idx; + std::map idx2ptr; + for (int layer_nr = 0; layer_nr < contact_nodes.size(); layer_nr++) { + std::vector& curr_layer_nodes = contact_nodes[layer_nr]; + for (Node* node : curr_layer_nodes) { + ptr2idx.emplace(node, tree_nodes.size()); + idx2ptr.emplace(tree_nodes.size(), node); + tree_nodes.emplace_back(node->position, node->print_z); + } + } + for (size_t i = 0; i < tree_nodes.size(); i++) { + TreeNode& tree_node = tree_nodes[i]; + Node* p_node = idx2ptr[i]; + if (p_node->child) + tree_node.children.push_back(ptr2idx[p_node->child]); + if(p_node->parent) + tree_node.parents.push_back(ptr2idx[p_node->parent]); + } +#ifdef SUPPORT_TREE_DEBUG_TO_SVG + nlohmann::json jj; + for (size_t i = 0; i < tree_nodes.size(); i++) { + nlohmann::json j; + j["pos"] = tree_nodes[i].pos; + j["children"] = tree_nodes[i].children; + j["linked"] = !(tree_nodes[i].pos.z() > 0.205 && tree_nodes[i].children.empty()); + jj.push_back(j); + } + + std::ofstream ofs("tree_nodes.json"); + ofs << jj.dump(); + ofs.close(); +#endif } -/*! - * \brief Generates Support Floor, ensures Support Roof can not cut of branches, and saves the branches as support to storage - * - * \param support_layer_storage[in] Areas where support should be generated. - * \param support_roof_storage[in] Areas where support was replaced with roof. - * \param storage[in,out] The storage where the support should be stored. - */ -static void finalize_interface_and_support_areas( - const PrintObject &print_object, - const TreeModelVolumes &volumes, - const TreeSupportSettings &config, - const std::vector &overhangs, - std::vector &support_layer_storage, - std::vector &support_roof_storage, - - SupportGeneratorLayersPtr &bottom_contacts, - SupportGeneratorLayersPtr &top_contacts, - SupportGeneratorLayersPtr &intermediate_layers, - SupportGeneratorLayerStorage &layer_storage, - - std::function throw_on_cancel) +void TreeSupport::adjust_layer_heights(std::vector>& contact_nodes) { - assert(std::all_of(bottom_contacts.begin(), bottom_contacts.end(), [](auto *p) { return p == nullptr; })); -// assert(std::all_of(top_contacts.begin(), top_contacts.end(), [](auto* p) { return p == nullptr; })); - assert(std::all_of(intermediate_layers.begin(), intermediate_layers.end(), [](auto* p) { return p == nullptr; })); - - InterfacePreference interface_pref = config.interface_preference; // InterfacePreference::InterfaceAreaOverwritesSupport; - -#ifdef SLIC3R_TREESUPPORTS_PROGRESS - double progress_total = TREE_PROGRESS_PRECALC_AVO + TREE_PROGRESS_PRECALC_COLL + TREE_PROGRESS_GENERATE_NODES + TREE_PROGRESS_AREA_CALC + TREE_PROGRESS_GENERATE_BRANCH_AREAS + TREE_PROGRESS_SMOOTH_BRANCH_AREAS; -#endif // SLIC3R_TREESUPPORTS_PROGRESS - - // Iterate over the generated circles in parallel and clean them up. Also add support floor. - tbb::parallel_for(tbb::blocked_range(0, support_layer_storage.size()), - [&](const tbb::blocked_range &range) { - for (size_t layer_idx = range.begin(); layer_idx < range.end(); ++ layer_idx) { - // Subtract support lines of the branches from the roof - SupportGeneratorLayer *support_roof = top_contacts[layer_idx]; - Polygons support_roof_polygons; - - if (Polygons &src = support_roof_storage[layer_idx]; ! src.empty()) { - if (support_roof != nullptr && ! support_roof->polygons.empty()) { - support_roof_polygons = union_(src, support_roof->polygons); - support_roof->polygons.clear(); - } else - support_roof_polygons = std::move(src); - } else if (support_roof != nullptr) { - support_roof_polygons = std::move(support_roof->polygons); - support_roof->polygons.clear(); + if (contact_nodes.empty()) + return; + + const PrintConfig& print_config = m_object->print()->config(); + const PrintObjectConfig& config = m_object->config(); + // don't merge layers for Vine support, or the branches will be unsmooth + // TODO can we merge layers in a way that guaranttees smoothness? + if (!print_config.independent_support_layer_height || is_slim) { + for (int layer_nr = 0; layer_nr < contact_nodes.size(); layer_nr++) { + std::vector& curr_layer_nodes = contact_nodes[layer_nr]; + for (Node* node : curr_layer_nodes) { + node->print_z = m_object->get_layer(layer_nr)->print_z; + node->height = m_object->get_layer(layer_nr)->height; } + } + return; + } - assert(intermediate_layers[layer_idx] == nullptr); - Polygons base_layer_polygons = std::move(support_layer_storage[layer_idx]); + // extreme layer_id + std::vector extremes; + const coordf_t layer_height = config.layer_height.value; + const coordf_t max_layer_height = m_slicing_params.max_layer_height; + const size_t bot_intf_layers = config.support_interface_bottom_layers.value; + const size_t top_intf_layers = config.support_interface_top_layers.value; - if (! base_layer_polygons.empty()) { - // Most of the time in this function is this union call. Can take 300+ ms when a lot of areas are to be unioned. - base_layer_polygons = smooth_outward(union_(base_layer_polygons), config.support_line_width); //FIXME was .smooth(50); - //smooth_outward(closing(std::move(bottom), closing_distance + minimum_island_radius, closing_distance, SUPPORT_SURFACES_OFFSET_PARAMETERS), smoothing_distance) : - // simplify a bit, to ensure the output does not contain outrageous amounts of vertices. Should not be necessary, just a precaution. - base_layer_polygons = polygons_simplify(base_layer_polygons, std::min(scaled(0.03), double(config.resolution)), polygons_strictly_simple); - } + // if already using max layer height, no need to adjust + if (layer_height == max_layer_height) return; - if (! support_roof_polygons.empty() && ! base_layer_polygons.empty()) { -// if (area(intersection(base_layer_polygons, support_roof_polygons)) > tiny_area_threshold) - { - switch (interface_pref) { - case InterfacePreference::InterfaceAreaOverwritesSupport: - base_layer_polygons = diff(base_layer_polygons, support_roof_polygons); - break; - case InterfacePreference::SupportAreaOverwritesInterface: - support_roof_polygons = diff(support_roof_polygons, base_layer_polygons); - break; - //FIXME - #if 1 - case InterfacePreference::InterfaceLinesOverwriteSupport: - case InterfacePreference::SupportLinesOverwriteInterface: - assert(false); - [[fallthrough]]; - #else - case InterfacePreference::InterfaceLinesOverwriteSupport: - { - // Hatch the support roof interfaces, offset them by their line width and subtract them from support base. - Polygons interface_lines = offset(to_polylines( - generate_support_infill_lines(support_roof->polygons, true, layer_idx, config.support_roof_line_distance)), - config.support_roof_line_width / 2); - base_layer_polygons = diff(base_layer_polygons, interface_lines); - break; - } - case InterfacePreference::SupportLinesOverwriteInterface: - { - // Hatch the support roof interfaces, offset them by their line width and subtract them from support base. - Polygons tree_lines = union_(offset(to_polylines( - generate_support_infill_lines(base_layer_polygons, false, layer_idx, config.support_line_distance, true)), - config.support_line_width / 2)); - // do not draw roof where the tree is. I prefer it this way as otherwise the roof may cut of a branch from its support below. - support_roof->polygons = diff(support_roof->polygons, tree_lines); - break; - } - #endif - case InterfacePreference::Nothing: - break; - } - } - } + extremes.push_back(0); + for (Node* node : contact_nodes[0]) { + node->print_z = m_object->get_layer(0)->print_z; + node->height = m_object->get_layer(0)->height; + } - // Subtract support floors from the support area and add them to the support floor instead. - if (config.support_bottom_layers > 0 && ! base_layer_polygons.empty()) { - SupportGeneratorLayer*& support_bottom = bottom_contacts[layer_idx]; - Polygons layer_outset = diff_clipped( - config.support_bottom_offset > 0 ? offset(base_layer_polygons, config.support_bottom_offset, jtMiter, 1.2) : base_layer_polygons, - volumes.getCollision(0, layer_idx, false)); - Polygons floor_layer; - size_t layers_below = 0; - while (layers_below <= config.support_bottom_layers) { - // one sample at 0 layers below, another at config.support_bottom_layers. In-between samples at config.performance_interface_skip_layers distance from each other. - const size_t sample_layer = static_cast(std::max(0, (static_cast(layer_idx) - static_cast(layers_below)) - static_cast(config.z_distance_bottom_layers))); - //FIXME subtract the wipe tower - append(floor_layer, intersection(layer_outset, overhangs[sample_layer])); - if (layers_below < config.support_bottom_layers) - layers_below = std::min(layers_below + 1, config.support_bottom_layers); - else - break; - } - if (! floor_layer.empty()) { - if (support_bottom == nullptr) - support_bottom = &layer_allocate(layer_storage, SupporLayerType::BottomContact, print_object.slicing_parameters(), config, layer_idx); - support_bottom->polygons = union_(floor_layer, support_bottom->polygons); - base_layer_polygons = diff_clipped(base_layer_polygons, offset(support_bottom->polygons, scaled(0.01), jtMiter, 1.2)); // Subtract the support floor from the normal support. - } + for (int layer_nr = 1; layer_nr < contact_nodes.size(); layer_nr++) { + std::vector& curr_layer_nodes = contact_nodes[layer_nr]; + for (Node* node : curr_layer_nodes) { + if (node->support_roof_layers_below >0 || node->support_floor_layers_above == bot_intf_layers) { + extremes.push_back(layer_nr); + break; } + } - if (! support_roof_polygons.empty()) { - if (support_roof == nullptr) - support_roof = top_contacts[layer_idx] = &layer_allocate(layer_storage, SupporLayerType::TopContact, print_object.slicing_parameters(), config, layer_idx); - support_roof->polygons = union_(support_roof_polygons); - } - if (! base_layer_polygons.empty()) { - SupportGeneratorLayer *base_layer = intermediate_layers[layer_idx] = &layer_allocate(layer_storage, SupporLayerType::Base, print_object.slicing_parameters(), config, layer_idx); - base_layer->polygons = union_(base_layer_polygons); + if (extremes.back() == layer_nr) { + // contact layer use the same print_z and layer height with object layer + for (Node* node : curr_layer_nodes) { + node->print_z = m_object->get_layer(layer_nr)->print_z; + node->height = m_object->get_layer(layer_nr)->height; } + } + } -#ifdef SLIC3R_TREESUPPORTS_PROGRESS - { - std::lock_guard critical_section_progress(critical_sections); - progress_total += TREE_PROGRESS_FINALIZE_BRANCH_AREAS / support_layer_storage.size(); - Progress::messageProgress(Progress::Stage::SUPPORT, progress_total * m_progress_multiplier + m_progress_offset, TREE_PROGRESS_TOTAL); + // schedule new layer heights and print_z + for (size_t idx_extreme = 0; idx_extreme < extremes.size(); idx_extreme++) { + int extr2_layer_nr = extremes[idx_extreme]; + coordf_t extr2z = m_object->get_layer(extr2_layer_nr)->bottom_z(); + int extr1_layer_nr = idx_extreme == 0 ? -1 : extremes[idx_extreme - 1]; + coordf_t extr1z = idx_extreme == 0 ? 0.f : m_object->get_layer(extr1_layer_nr)->print_z; + coordf_t dist = extr2z - extr1z; + + // Insert intermediate layers. + size_t n_layers_extra = size_t(ceil(dist / m_slicing_params.max_suport_layer_height)); + if (n_layers_extra <= 1) + continue; + + coordf_t step = dist / coordf_t(n_layers_extra); + coordf_t print_z = extr1z + step; + assert(step >= layer_height - EPSILON); + for (int layer_nr = extr1_layer_nr + 1; layer_nr < extr2_layer_nr; layer_nr++) { + std::vector& curr_layer_nodes = contact_nodes[layer_nr]; + if (curr_layer_nodes.empty()) continue; + + if (std::abs(print_z - curr_layer_nodes[0]->print_z) < step / 2 + EPSILON) { + for (Node* node : curr_layer_nodes) { + node->print_z = print_z; + node->height = step; + } + print_z += step; } -#endif -#if 0 - { - std::lock_guard lock(critical_sections); - if (!storage.support.supportLayers[layer_idx].support_infill_parts.empty() || !storage.support.supportLayers[layer_idx].support_roof.empty()) - storage.support.layer_nr_max_filled_layer = std::max(storage.support.layer_nr_max_filled_layer, static_cast(layer_idx)); + else { + // can't clear curr_layer_nodes, or the model will have empty layers + for (Node* node : curr_layer_nodes) { + node->print_z = 0.0; + node->height = 0.0; + } } -#endif - throw_on_cancel(); } - }); + } } -/*! - * \brief Draws circles around result_on_layer points of the influence areas and applies some post processing. - * - * \param move_bounds[in] All currently existing influence areas - * \param storage[in,out] The storage where the support should be stored. - */ -static void draw_areas( - PrintObject &print_object, - const TreeModelVolumes &volumes, - const TreeSupportSettings &config, - const std::vector &overhangs, - std::vector &move_bounds, - - SupportGeneratorLayersPtr &bottom_contacts, - SupportGeneratorLayersPtr &top_contacts, - SupportGeneratorLayersPtr &intermediate_layers, - SupportGeneratorLayerStorage &layer_storage, - std::function throw_on_cancel) +std::vector TreeSupport::plan_layer_heights(std::vector> &contact_nodes) { - std::vector support_layer_storage(move_bounds.size()); - std::vector support_roof_storage(move_bounds.size()); - // All SupportElements are put into a layer independent storage to improve parallelization. - std::vector linear_data; - std::vector linear_data_layers; - { - std::vector> map_downwards_old; - std::vector> map_downwards_new; - for (LayerIndex layer_idx = 0; layer_idx < LayerIndex(move_bounds.size()); ++ layer_idx) { - SupportElements *layer_above = layer_idx + 1 < LayerIndex(move_bounds.size()) ? &move_bounds[layer_idx + 1] : nullptr; - map_downwards_new.clear(); - linear_data_layers.emplace_back(linear_data.size()); - std::sort(map_downwards_old.begin(), map_downwards_old.end(), [](auto &l, auto &r) { return l.first < r.first; }); - for (SupportElement &elem : move_bounds[layer_idx]) { - SupportElement *child = nullptr; - if (layer_idx > 0) { - auto it = std::lower_bound(map_downwards_old.begin(), map_downwards_old.end(), &elem, [](auto &l, const SupportElement *r) { return l.first < r; }); - if (it != map_downwards_old.end() && it->first == &elem) { - child = it->second; - // Only one link points to a node above from below. - assert(! (++ it != map_downwards_old.end() && it->first == &elem)); - } - assert(child ? child->state.result_on_layer_is_set() : elem.state.target_height > layer_idx); - } - for (int32_t parent_idx : elem.parents) { - SupportElement &parent = (*layer_above)[parent_idx]; - if (parent.state.result_on_layer_is_set()) - map_downwards_new.emplace_back(&parent, &elem); - } - linear_data.push_back({ &elem, child }); - } - std::swap(map_downwards_old, map_downwards_new); + const PrintObjectConfig& config = m_object->config(); + const PrintConfig & print_config = m_object->print()->config(); + const coordf_t max_layer_height = m_slicing_params.max_layer_height; + const coordf_t layer_height = config.layer_height.value; + coordf_t z_distance_top = m_slicing_params.gap_support_object; + // BBS: add extra distance if thick bridge is enabled + // Note: normal support uses print_z, but tree support uses integer layers, so we need to subtract layer_height + if (!m_slicing_params.soluble_interface && m_object_config->thick_bridges) { + z_distance_top += m_object->layers()[0]->regions()[0]->region().bridging_height_avg(m_object->print()->config()) - layer_height; + } + const size_t support_roof_layers = config.support_interface_top_layers.value; + const int z_distance_top_layers = round_up_divide(scale_(z_distance_top), scale_(layer_height)) + 1; + std::vector layer_heights(contact_nodes.size()); + std::vector bounds; + + if (!config.tree_support_adaptive_layer_height || layer_height == max_layer_height || !print_config.independent_support_layer_height) { + for (int layer_nr = 0; layer_nr < contact_nodes.size(); layer_nr++) { + layer_heights[layer_nr] = {m_object->get_layer(layer_nr)->print_z, m_object->get_layer(layer_nr)->height, layer_nr > 0 ? size_t(layer_nr - 1) : 0}; } - linear_data_layers.emplace_back(linear_data.size()); + return layer_heights; } - throw_on_cancel(); + bounds.push_back(0); + // Keep first layer still + layer_heights[0] = {m_object->get_layer(0)->print_z, m_object->get_layer(0)->height, 0}; + // Collect top contact layers + for (int layer_nr = 1; layer_nr < contact_nodes.size(); layer_nr++) + { + if (!contact_nodes[layer_nr].empty()) + for (int i = 0; i < support_roof_layers + z_distance_top_layers + 1; i++) { + if (layer_nr - i > 0) { + bounds.push_back(layer_nr - i); + layer_heights[layer_nr - i].print_z = m_object->get_layer(layer_nr - i)->print_z; + layer_heights[layer_nr - i].height = m_object->get_layer(layer_nr - i)->height; + } + else { + break; + } -#ifndef NDEBUG - for (size_t i = 0; i < move_bounds.size(); ++ i) { - size_t begin = linear_data_layers[i]; - size_t end = linear_data_layers[i + 1]; - for (size_t j = begin; j < end; ++ j) - assert(linear_data[j].element == &move_bounds[i][j - begin]); + } } -#endif // NDEBUG - - auto t_start = std::chrono::high_resolution_clock::now(); - // Generate the circles that will be the branches. - generate_branch_areas(volumes, config, move_bounds, linear_data, throw_on_cancel); + std::set s(bounds.begin(), bounds.end()); + bounds.assign(s.begin(), s.end()); + + for (size_t idx_extreme = 0; idx_extreme < bounds.size(); idx_extreme++) { + int extr2_layer_nr = bounds[idx_extreme]; + coordf_t extr2z = m_object->get_layer(extr2_layer_nr)->bottom_z(); + int extr1_layer_nr = idx_extreme == 0 ? -1 : bounds[idx_extreme - 1]; + coordf_t extr1z = idx_extreme == 0 ? 0.f : m_object->get_layer(extr1_layer_nr)->print_z; + coordf_t dist = extr2z - extr1z; + + // Insert intermediate layers. + size_t n_layers_extra = size_t(ceil(dist / (m_slicing_params.max_suport_layer_height + EPSILON))); + int actual_internel_layers = extr2_layer_nr - extr1_layer_nr - 1; + int extr_layers_left = extr2_layer_nr - extr1_layer_nr - n_layers_extra - 1; + if (n_layers_extra < 1) + continue; -#if 0 - assert(linear_data_layers.size() == move_bounds.size() + 1); - for (const auto &draw_area : linear_data) - assert(contains(draw_area.polygons, draw_area.element->state.result_on_layer)); - for (size_t i = 0; i < move_bounds.size(); ++ i) { - size_t begin = linear_data_layers[i]; - size_t end = linear_data_layers[i + 1]; - for (size_t j = begin; j < end; ++ j) { - const auto &draw_area = linear_data[j]; - assert(draw_area.element == &move_bounds[i][j - begin]); - assert(contains(draw_area.polygons, draw_area.element->state.result_on_layer)); + coordf_t step = dist / coordf_t(n_layers_extra); + coordf_t print_z = extr1z + step; + assert(step >= layer_height - EPSILON); + for (int layer_nr = extr1_layer_nr + 1; layer_nr < extr2_layer_nr; layer_nr++) { + // if (curr_layer_nodes.empty()) continue; + if (std::abs(print_z - m_object->get_layer(layer_nr)->print_z) < step / 2 + EPSILON || extr_layers_left < 1) { + layer_heights[layer_nr].print_z = print_z; + layer_heights[layer_nr].height = step; + print_z += step; + } + else { + // can't clear curr_layer_nodes, or the model will have empty layers + layer_heights[layer_nr].print_z = 0.0; + layer_heights[layer_nr].height = 0.0; + extr_layers_left--; + } } } -#endif -#if 0 - for (size_t area_layer_idx = 0; area_layer_idx + 1 < linear_data_layers.size(); ++ area_layer_idx) { - size_t begin = linear_data_layers[area_layer_idx]; - size_t end = linear_data_layers[area_layer_idx + 1]; - Polygons polygons; - for (size_t area_idx = begin; area_idx < end; ++ area_idx) { - DrawArea &area = linear_data[area_idx]; - append(polygons, area.polygons); + // fill in next_layer_nr + int i = layer_heights.size() - 1, j = i; + for (; j >= 0; i = j) { + if (layer_heights[i].height < EPSILON) { + j--; + continue; } - SVG::export_expolygons(debug_out_path("treesupport-extrude_areas-raw-%d.svg", area_layer_idx), - { { { union_ex(polygons) }, { "parent", "red", "black", "", scaled(0.1f), 0.5f } } }); + for (j = i - 1; j >= 0; j--) { + if (layer_heights[j].height > EPSILON) { + layer_heights[i].next_layer_nr = j; + break; + } + } + BOOST_LOG_TRIVIAL(trace) << "plan_layer_heights print_z, height, layer_nr->next_layer_nr: " << layer_heights[i].print_z << " " << layer_heights[i].height << " " + << i << "->" << layer_heights[i].next_layer_nr << std::endl; } -#endif - auto t_generate = std::chrono::high_resolution_clock::now(); - // In some edgecases a branch may go though a hole, where the regular radius does not fit. This can result in an apparent jump in branch radius. As such this cases need to be caught and smoothed out. - smooth_branch_areas(config, move_bounds, linear_data, linear_data_layers, throw_on_cancel); + return layer_heights; +} -#if 0 - for (size_t area_layer_idx = 0; area_layer_idx + 1 < linear_data_layers.size(); ++area_layer_idx) { - size_t begin = linear_data_layers[area_layer_idx]; - size_t end = linear_data_layers[area_layer_idx + 1]; - Polygons polygons; - for (size_t area_idx = begin; area_idx < end; ++area_idx) { - DrawArea& area = linear_data[area_idx]; - append(polygons, area.polygons); +void TreeSupport::generate_contact_points(std::vector>& contact_nodes) +{ + const PrintObjectConfig &config = m_object->config(); + const coordf_t point_spread = scale_(config.tree_support_branch_distance.value); + + //First generate grid points to cover the entire area of the print. + BoundingBox bounding_box = m_object->bounding_box(); + const Point bounding_box_size = bounding_box.max - bounding_box.min; + constexpr double rotate_angle = 22.0 / 180.0 * M_PI; + + const auto center = bounding_box_middle(bounding_box); + const auto sin_angle = std::sin(rotate_angle); + const auto cos_angle = std::cos(rotate_angle); + const Point rotated_dims = Point( + bounding_box_size(0) * cos_angle + bounding_box_size(1) * sin_angle, + bounding_box_size(0) * sin_angle + bounding_box_size(1) * cos_angle) / 2; + + std::vector grid_points; + for (auto x = -rotated_dims(0); x < rotated_dims(0); x += point_spread) { + for (auto y = -rotated_dims(1); y < rotated_dims(1); y += point_spread) { + Point pt(x, y); + pt.rotate(cos_angle, sin_angle); + pt += center; + if (bounding_box.contains(pt)) { + grid_points.push_back(pt); + } } - SVG::export_expolygons(debug_out_path("treesupport-extrude_areas-smooth-%d.svg", area_layer_idx), - { { { union_ex(polygons) }, { "parent", "red", "black", "", scaled(0.1f), 0.5f } } }); } -#endif - auto t_smooth = std::chrono::high_resolution_clock::now(); - // drop down all trees that connect non gracefully with the model - drop_non_gracious_areas(volumes, linear_data, support_layer_storage, throw_on_cancel); - auto t_drop = std::chrono::high_resolution_clock::now(); + const coordf_t layer_height = config.layer_height.value; + coordf_t z_distance_top = m_slicing_params.gap_support_object; + // BBS: add extra distance if thick bridge is enabled + // Note: normal support uses print_z, but tree support uses integer layers, so we need to subtract layer_height + if (!m_slicing_params.soluble_interface && m_object_config->thick_bridges) { + z_distance_top += m_object->layers()[0]->regions()[0]->region().bridging_height_avg(m_object->print()->config()) - layer_height; + } + const int z_distance_top_layers = round_up_divide(scale_(z_distance_top), scale_(layer_height)) + 1; //Support must always be 1 layer below overhang. - // Single threaded combining all support areas to the right layers. - { - auto begin = linear_data.begin(); - for (LayerIndex layer_idx = 0; layer_idx < LayerIndex(move_bounds.size()); ++ layer_idx) { - size_t cnt_roofs = 0; - size_t cnt_layers = 0; - auto end = begin; - for (; end != linear_data.end() && end->element->state.layer_idx == layer_idx; ++ end) - ++ (end->element->state.missing_roof_layers > end->element->state.distance_to_top ? cnt_roofs : cnt_layers); - auto &this_roofs = support_roof_storage[layer_idx]; - auto &this_layers = support_layer_storage[layer_idx]; - this_roofs.reserve(this_roofs.size() + cnt_roofs); - this_layers.reserve(this_layers.size() + cnt_layers); - for (auto it = begin; it != end; ++ it) - std::move(std::begin(it->polygons), std::end(it->polygons), std::back_inserter(it->element->state.missing_roof_layers > it->element->state.distance_to_top ? this_roofs : this_layers)); - begin = end; - } - } - - finalize_interface_and_support_areas(print_object, volumes, config, overhangs, support_layer_storage, support_roof_storage, - bottom_contacts, top_contacts, intermediate_layers, layer_storage, throw_on_cancel); - auto t_end = std::chrono::high_resolution_clock::now(); - - auto dur_gen_tips = 0.001 * std::chrono::duration_cast(t_generate - t_start).count(); - auto dur_smooth = 0.001 * std::chrono::duration_cast(t_smooth - t_generate).count(); - auto dur_drop = 0.001 * std::chrono::duration_cast(t_drop - t_smooth).count(); - auto dur_finalize = 0.001 * std::chrono::duration_cast(t_end - t_drop).count(); - - BOOST_LOG_TRIVIAL(info) << - "Time used for drawing subfuctions: generate_branch_areas: " << dur_gen_tips << " ms " - "smooth_branch_areas: " << dur_smooth << " ms " - "drop_non_gracious_areas: " << dur_drop << " ms " - "finalize_interface_and_support_areas " << dur_finalize << " ms"; -} + size_t support_roof_layers = config.support_interface_top_layers.value; + if (support_roof_layers > 0) + support_roof_layers += 1; // BBS: add a normal support layer below interface (if we have interface) + coordf_t thresh_angle = std::min(89.f, config.support_threshold_angle.value < EPSILON ? 30.f : config.support_threshold_angle.value); + coordf_t half_overhang_distance = scale_(tan(thresh_angle * M_PI / 180.0) * layer_height / 2); -extern bool g_showed_critical_error; -extern bool g_showed_performance_warning; + // fix bug of generating support for very thin objects + if (m_object->layers().size() <= z_distance_top_layers + 1) + return; -/*! - * \brief Create the areas that need support. - * - * These areas are stored inside the given SliceDataStorage object. - * \param storage The data storage where the mesh data is gotten from and - * where the resulting support areas are stored. - */ -static void generate_support_areas(Print &print, const BuildVolume &build_volume, const std::vector &print_object_ids, std::function throw_on_cancel) -{ - g_showed_critical_error = false; - g_showed_performance_warning = false; + m_highest_overhang_layer = 0; + int nonempty_layers = 0; + std::vector all_nodes; + for (size_t layer_nr = 1; layer_nr < m_object->layers().size(); layer_nr++) + { + if (m_object->print()->canceled()) + break; + auto ts_layer = m_object->get_support_layer(layer_nr + m_raft_layers); + const ExPolygons &overhang = ts_layer->overhang_areas; + auto & curr_nodes = contact_nodes[layer_nr]; + if (overhang.empty()) + continue; - // Settings with the indexes of meshes that use these settings. - std::vector>> grouped_meshes = group_meshes(print, print_object_ids); - if (grouped_meshes.empty()) - return; + m_highest_overhang_layer = std::max(m_highest_overhang_layer, layer_nr); + auto print_z = m_object->get_layer(layer_nr)->print_z; + auto height = m_object->get_layer(layer_nr)->height; - size_t counter = 0; + for (const ExPolygon &overhang_part : overhang) + { + BoundingBox overhang_bounds = get_extents(overhang_part); + if (support_style==smsTreeHybrid && overhang_part.area() > m_support_params.thresh_big_overhang) { + Point candidate = overhang_bounds.center(); + if (!overhang_part.contains(candidate)) + move_inside_expoly(overhang_part, candidate); + if (!(config.support_on_build_plate_only && is_inside_ex(m_ts_data->m_layer_outlines_below[layer_nr], candidate))) { + Node* contact_node = new Node(candidate, -z_distance_top_layers, layer_nr, support_roof_layers + z_distance_top_layers, true, Node::NO_PARENT, print_z, + height, z_distance_top); + contact_node->type = ePolygon; + contact_node->overhang = &overhang_part; + curr_nodes.emplace_back(contact_node); + continue; + } + } - // Process every mesh group. These groups can not be processed parallel, as the processing in each group is parallelized, and nested parallelization is disables and slow. - for (std::pair> &processing : grouped_meshes) - { - // process each combination of meshes - // this struct is used to easy retrieve setting. No other function except those in TreeModelVolumes and generate_initial_areas() have knowledge of the existence of multiple meshes being processed. - //FIXME this is a copy - // Contains config settings to avoid loading them in every function. This was done to improve readability of the code. - const TreeSupportSettings &config = processing.first; - BOOST_LOG_TRIVIAL(info) << "Processing support tree mesh group " << counter + 1 << " of " << grouped_meshes.size() << " containing " << grouped_meshes[counter].second.size() << " meshes."; - auto t_start = std::chrono::high_resolution_clock::now(); -#if 0 - std::vector exclude(num_support_layers); - // get all already existing support areas and exclude them - tbb::parallel_for(tbb::blocked_range(0, num_support_layers), - [&](const tbb::blocked_range &range) { - for (size_t layer_idx = range.begin(); layer_idx < range.end(); ++ layer_idx) { - Polygons exlude_at_layer; - append(exlude_at_layer, storage.support.supportLayers[layer_idx].support_bottom); - append(exlude_at_layer, storage.support.supportLayers[layer_idx].support_roof); - for (auto part : storage.support.supportLayers[layer_idx].support_infill_parts) - append(exlude_at_layer, part.outline); - exclude[layer_idx] = union_(exlude_at_layer); + overhang_bounds.inflated(half_overhang_distance); + bool added = false; //Did we add a point this way? + for (Point candidate : grid_points) + { + if (overhang_bounds.contains(candidate)) + { + // BBS: move_inside_expoly shouldn't be used if candidate is already inside, as it moves point to boundary and the inside is not well supported! + bool is_inside = is_inside_ex(overhang_part, candidate); + if (!is_inside) { + constexpr coordf_t distance_inside = 0; // Move point towards the border of the polygon if it is closer than half the overhang distance: Catch points that + // fall between overhang areas on constant surfaces. + move_inside_expoly(overhang_part, candidate, distance_inside, half_overhang_distance); + is_inside = is_inside_ex(overhang_part, candidate); + } + if (is_inside) + { + // collision radius has to be 0 or the supports are too few at curved slopes + //if (!is_inside_ex(m_ts_data->get_collision(0, layer_nr), candidate)) + { + constexpr bool to_buildplate = true; + Node * contact_node = new Node(candidate, -z_distance_top_layers, layer_nr, support_roof_layers + z_distance_top_layers, to_buildplate, + Node::NO_PARENT, print_z, height, z_distance_top); + contact_node->overhang = &overhang_part; + curr_nodes.emplace_back(contact_node); + added = true; + } + } + } } - }); -#endif -#ifdef SLIC3R_TREESUPPORTS_PROGRESS - m_progress_multiplier = 1.0 / double(grouped_meshes.size()); - m_progress_offset = counter == 0 ? 0 : TREE_PROGRESS_TOTAL * (double(counter) * m_progress_multiplier); -#endif // SLIC3R_TREESUPPORT_PROGRESS - PrintObject &print_object = *print.get_object(processing.second.front()); - // Generator for model collision, avoidance and internal guide volumes. - TreeModelVolumes volumes{ print_object, build_volume, config.maximum_move_distance, config.maximum_move_distance_slow, processing.second.front(), -#ifdef SLIC3R_TREESUPPORTS_PROGRESS - m_progress_multiplier, m_progress_offset, -#endif // SLIC3R_TREESUPPORTS_PROGRESS - /* additional_excluded_areas */{} }; - - //FIXME generating overhangs just for the furst mesh of the group. - assert(processing.second.size() == 1); - std::vector overhangs = generate_overhangs(config, *print.get_object(processing.second.front()), throw_on_cancel); - - // ### Precalculate avoidances, collision etc. - size_t num_support_layers = precalculate(print, overhangs, processing.first, processing.second, volumes, throw_on_cancel); - bool has_support = num_support_layers > 0; - bool has_raft = config.raft_layers.size() > 0; - num_support_layers = std::max(num_support_layers, config.raft_layers.size()); - - SupportParameters support_params(print_object); - support_params.with_sheath = true; -// Don't override the support density of tree supports, as the support density is used for raft. -// The trees will have the density zeroed in tree_supports_generate_paths() -// support_params.support_density = 0; - - SupportGeneratorLayerStorage layer_storage; - SupportGeneratorLayersPtr top_contacts; - SupportGeneratorLayersPtr bottom_contacts; - SupportGeneratorLayersPtr interface_layers; - SupportGeneratorLayersPtr base_interface_layers; - SupportGeneratorLayersPtr intermediate_layers(num_support_layers, nullptr); - if (support_params.has_top_contacts || has_raft) - top_contacts.assign(num_support_layers, nullptr); - if (support_params.has_bottom_contacts) - bottom_contacts.assign(num_support_layers, nullptr); - if (support_params.has_interfaces() || has_raft) - interface_layers.assign(num_support_layers, nullptr); - if (support_params.has_base_interfaces() || has_raft) - base_interface_layers.assign(num_support_layers, nullptr); - - auto remove_undefined_layers = [&bottom_contacts, &top_contacts, &interface_layers, &base_interface_layers, &intermediate_layers]() { - auto doit = [](SupportGeneratorLayersPtr& layers) { - layers.erase(std::remove_if(layers.begin(), layers.end(), [](const SupportGeneratorLayer* ptr) { return ptr == nullptr; }), layers.end()); - }; - doit(bottom_contacts); - doit(top_contacts); - doit(interface_layers); - doit(base_interface_layers); - doit(intermediate_layers); - }; - InterfacePlacer interface_placer{ - print_object.slicing_parameters(), support_params, config, - // Outputs - layer_storage, top_contacts, interface_layers, base_interface_layers }; - - if (has_support) { - auto t_precalc = std::chrono::high_resolution_clock::now(); - - // value is the area where support may be placed. As this is calculated in CreateLayerPathing it is saved and reused in draw_areas - std::vector move_bounds(num_support_layers); - - // ### Place tips of the support tree - for (size_t mesh_idx : processing.second) - generate_initial_areas(*print.get_object(mesh_idx), volumes, config, overhangs, - move_bounds, interface_placer, throw_on_cancel); - auto t_gen = std::chrono::high_resolution_clock::now(); - - #ifdef TREESUPPORT_DEBUG_SVG - for (size_t layer_idx = 0; layer_idx < move_bounds.size(); ++layer_idx) { - Polygons polys; - for (auto& area : move_bounds[layer_idx]) - append(polys, area.influence_area); - if (auto begin = move_bounds[layer_idx].begin(); begin != move_bounds[layer_idx].end()) - SVG::export_expolygons(debug_out_path("treesupport-initial_areas-%d.svg", layer_idx), - { { { union_ex(volumes.getWallRestriction(support_element_collision_radius(config, begin->state), layer_idx, begin->state.use_min_xy_dist)) }, - { "wall_restricrictions", "gray", 0.5f } }, - { { union_ex(polys) }, { "parent", "red", "black", "", scaled(0.1f), 0.5f } } }); + if (!added) //If we didn't add any points due to bad luck, we want to add one anyway such that loose parts are also supported. + { + auto bbox = overhang_part.contour.bounding_box(); + Points candidates; + if (ts_layer->overhang_types[&overhang_part] == SupportLayer::Detected) + candidates = {bbox.min, bounding_box_middle(bbox), bbox.max}; + else + candidates = {bounding_box_middle(bbox)}; + + for (Point candidate : candidates) { + if (!overhang_part.contains(candidate)) + move_inside_expoly(overhang_part, candidate); + constexpr bool to_buildplate = true; + Node *contact_node = new Node(candidate, -z_distance_top_layers, layer_nr, support_roof_layers + z_distance_top_layers, to_buildplate, Node::NO_PARENT, + print_z, height, z_distance_top); + contact_node->overhang = &overhang_part; + curr_nodes.emplace_back(contact_node); + } + } + // add supports at corners for both auto and manual overhangs, github #2008 + if (/*ts_layer->overhang_types[&overhang_part] == SupportLayer::Detected*/1) { + // add points at corners + auto &points = overhang_part.contour.points; + int nSize = points.size(); + for (int i = 0; i < nSize; i++) { + auto pt = points[i]; + auto v1 = (pt - points[(i - 1 + nSize) % nSize]).cast().normalized(); + auto v2 = (pt - points[(i + 1) % nSize]).cast().normalized(); + if (v1.dot(v2) > -0.7) { // angle smaller than 135 degrees + Node *contact_node = new Node(pt, -z_distance_top_layers, layer_nr, support_roof_layers + z_distance_top_layers, true, Node::NO_PARENT, print_z, + height, z_distance_top); + contact_node->overhang = &overhang_part; + contact_node->is_corner = true; + curr_nodes.emplace_back(contact_node); + } + } + } + if(ts_layer->overhang_types[&overhang_part] == SupportLayer::Enforced || is_slim){ + // remove close points in Enforcers + // auto above_nodes = contact_nodes[layer_nr - 1]; + if (!curr_nodes.empty() /*&& !above_nodes.empty()*/) { + for (auto it = curr_nodes.begin(); it != curr_nodes.end();) { + bool is_duplicate = false; + if (!(*it)->is_corner) { + Slic3r::Vec3f curr_pt((*it)->position(0), (*it)->position(1), scale_((*it)->print_z)); + for (auto &pt : all_nodes) { + auto dif = curr_pt - pt; + if (dif.norm() < point_spread / 2) { + delete (*it); + it = curr_nodes.erase(it); + is_duplicate = true; + break; + } + } + } + if (!is_duplicate) it++; + } + } } - #endif // TREESUPPORT_DEBUG_SVG + } + if (!curr_nodes.empty()) nonempty_layers++; + for (auto node : curr_nodes) { all_nodes.emplace_back(node->position(0), node->position(1), scale_(node->print_z)); } +#ifdef SUPPORT_TREE_DEBUG_TO_SVG + draw_contours_and_nodes_to_svg(std::to_string(print_z), overhang, m_ts_data->m_layer_outlines_below[layer_nr], {}, + contact_nodes[layer_nr], {}, "init_contact_points", { "overhang","outlines","" }); +#endif + } + int nNodes = all_nodes.size(); + avg_node_per_layer = nodes_angle = 0; + if (nNodes > 0) { + avg_node_per_layer = nNodes / nonempty_layers; + // get orientation of nodes by line fitting + // line: y=kx+b, where + // k=tan(nodes_angle)=(n\sum{xy}-\sum{x}\sum{y})/(n\sum{x^2}-\sum{x}^2) + float mx = 0, my = 0, mxy = 0, mx2 = 0; + for (auto &pt : all_nodes) { + float x = unscale_(pt(0)); + float y = unscale_(pt(1)); + mx += x; + my += y; + mxy += x * y; + mx2 += x * x; + } + nodes_angle = atan2(nNodes * mxy - mx * my, nNodes * mx2 - SQ(mx)); + + BOOST_LOG_TRIVIAL(info) << "avg_node_per_layer=" << avg_node_per_layer << ", nodes_angle=" << nodes_angle; + } +} - // ### Propagate the influence areas downwards. This is an inherently serial operation. - create_layer_pathing(volumes, config, move_bounds, throw_on_cancel); - auto t_path = std::chrono::high_resolution_clock::now(); +void TreeSupport::insert_dropped_node(std::vector& nodes_layer, Node* p_node) +{ + std::vector::iterator conflicting_node_it = std::find(nodes_layer.begin(), nodes_layer.end(), p_node); + if (conflicting_node_it == nodes_layer.end()) //No conflict. + { + nodes_layer.emplace_back(p_node); + return; + } - // ### Set a point in each influence area - create_nodes_from_area(volumes, config, move_bounds, throw_on_cancel); - auto t_place = std::chrono::high_resolution_clock::now(); + Node* conflicting_node = *conflicting_node_it; + conflicting_node->distance_to_top = std::max(conflicting_node->distance_to_top, p_node->distance_to_top); + conflicting_node->support_roof_layers_below = std::max(conflicting_node->support_roof_layers_below, p_node->support_roof_layers_below); +} - // ### draw these points as circles +TreeSupportData::TreeSupportData(const PrintObject &object, coordf_t xy_distance, coordf_t max_move, coordf_t radius_sample_resolution) + : m_xy_distance(xy_distance), m_max_move(max_move), m_radius_sample_resolution(radius_sample_resolution) +{ + for (std::size_t layer_nr = 0; layer_nr < object.layers().size(); ++layer_nr) + { + const Layer* layer = object.get_layer(layer_nr); + m_layer_outlines.push_back(ExPolygons()); + ExPolygons& outline = m_layer_outlines.back(); + for (const ExPolygon& poly : layer->lslices) { + poly.simplify(scale_(m_radius_sample_resolution), &outline); + } - if (print_object.config().support_style.value != smsOrganic && - // Orca: use organic as default - print_object.config().support_style.value != smsDefault) { - draw_areas(*print.get_object(processing.second.front()), volumes, config, overhangs, move_bounds, - bottom_contacts, top_contacts, intermediate_layers, layer_storage, throw_on_cancel); - } else { - organic_draw_branches( - *print.get_object(processing.second.front()), volumes, config, move_bounds, - bottom_contacts, top_contacts, interface_placer, intermediate_layers, layer_storage, - throw_on_cancel); - } + if (layer_nr == 0) + m_layer_outlines_below.push_back(outline); + else + m_layer_outlines_below.push_back(union_ex(m_layer_outlines_below.end()[-1], outline)); + } +} - remove_undefined_layers(); - - std::tie(interface_layers, base_interface_layers) = generate_interface_layers(print_object.config(), support_params, - bottom_contacts, top_contacts, interface_layers, base_interface_layers, intermediate_layers, layer_storage); - - auto t_draw = std::chrono::high_resolution_clock::now(); - auto dur_pre_gen = 0.001 * std::chrono::duration_cast(t_precalc - t_start).count(); - auto dur_gen = 0.001 * std::chrono::duration_cast(t_gen - t_precalc).count(); - auto dur_path = 0.001 * std::chrono::duration_cast(t_path - t_gen).count(); - auto dur_place = 0.001 * std::chrono::duration_cast(t_place - t_path).count(); - auto dur_draw = 0.001 * std::chrono::duration_cast(t_draw - t_place).count(); - auto dur_total = 0.001 * std::chrono::duration_cast(t_draw - t_start).count(); - BOOST_LOG_TRIVIAL(info) << - "Total time used creating Tree support for the currently grouped meshes: " << dur_total << " ms. " - "Different subtasks:\nCalculating Avoidance: " << dur_pre_gen << " ms " - "Creating inital influence areas: " << dur_gen << " ms " - "Influence area creation: " << dur_path << "ms " - "Placement of Points in InfluenceAreas: " << dur_place << "ms " - "Drawing result as support " << dur_draw << " ms"; - // if (config.branch_radius==2121) - // BOOST_LOG_TRIVIAL(error) << "Why ask questions when you already know the answer twice.\n (This is not a real bug, please dont report it.)"; - - move_bounds.clear(); - } else if (generate_raft_contact(print_object, config, interface_placer) >= 0) { - remove_undefined_layers(); - } else - // No raft. - continue; +const ExPolygons& TreeSupportData::get_collision(coordf_t radius, size_t layer_nr) const +{ + profiler.tic(); + radius = ceil_radius(radius); + RadiusLayerPair key{radius, layer_nr}; + const auto it = m_collision_cache.find(key); + const ExPolygons& collision = it != m_collision_cache.end() ? it->second : calculate_collision(key); + profiler.stage_add(STAGE_get_collision, true); + return collision; +} - // Produce the support G-code. - // Used by both classic and tree supports. - SupportGeneratorLayersPtr raft_layers = generate_raft_base(print_object, support_params, print_object.slicing_parameters(), - top_contacts, interface_layers, base_interface_layers, intermediate_layers, layer_storage); -#if 1 //#ifdef SLIC3R_DEBUG - SupportGeneratorLayersPtr layers_sorted = -#endif // SLIC3R_DEBUG - generate_support_layers(print_object, raft_layers, bottom_contacts, top_contacts, intermediate_layers, interface_layers, base_interface_layers); - // Don't fill in the tree supports, make them hollow with just a single sheath line. - generate_support_toolpaths(print_object.support_layers(), print_object.config(), support_params, print_object.slicing_parameters(), - raft_layers, bottom_contacts, top_contacts, intermediate_layers, interface_layers, base_interface_layers); - - #if 0 -//#ifdef SLIC3R_DEBUG - { - static int iRun = 0; - ++ iRun; - size_t layer_id = 0; - for (int i = 0; i < int(layers_sorted.size());) { - // Find the last layer with roughly the same print_z, find the minimum layer height of all. - // Due to the floating point inaccuracies, the print_z may not be the same even if in theory they should. - int j = i + 1; - coordf_t zmax = layers_sorted[i]->print_z + EPSILON; - bool empty = layers_sorted[i]->polygons.empty(); - for (; j < layers_sorted.size() && layers_sorted[j]->print_z <= zmax; ++j) - if (!layers_sorted[j]->polygons.empty()) - empty = false; - if (!empty) { - export_print_z_polygons_to_svg( - debug_out_path("support-%d-%lf.svg", iRun, layers_sorted[i]->print_z).c_str(), - layers_sorted.data() + i, j - i); - export_print_z_polygons_and_extrusions_to_svg( - debug_out_path("support-w-fills-%d-%lf.svg", iRun, layers_sorted[i]->print_z).c_str(), - layers_sorted.data() + i, j - i, - *print_object.support_layers()[layer_id]); - ++layer_id; - } - i = j; - } - } -#endif /* SLIC3R_DEBUG */ +const ExPolygons& TreeSupportData::get_avoidance(coordf_t radius, size_t layer_nr, int recursions) const +{ + profiler.tic(); + radius = ceil_radius(radius); + RadiusLayerPair key{radius, layer_nr, recursions }; + const auto it = m_avoidance_cache.find(key); + const ExPolygons& avoidance = it != m_avoidance_cache.end() ? it->second : calculate_avoidance(key); + + profiler.stage_add(STAGE_GET_AVOIDANCE, true); + return avoidance; +} - ++ counter; +Polygons TreeSupportData::get_contours(size_t layer_nr) const +{ + Polygons contours; + for (const ExPolygon& expoly : m_layer_outlines[layer_nr]) { + contours.push_back(expoly.contour); } -// storage.support.generated = true; + return contours; } -} // namespace FFFTreeSupport +Polygons TreeSupportData::get_contours_with_holes(size_t layer_nr) const +{ + Polygons contours; + for (const ExPolygon& expoly : m_layer_outlines[layer_nr]) { + for(int i=0;i throw_on_cancel) +coordf_t TreeSupportData::ceil_radius(coordf_t radius) const { - size_t idx = 0; - for (const PrintObject *po : print_object.print()->objects()) { - if (po == &print_object) - break; - ++idx; +#if 1 + size_t factor = (size_t)(radius / m_radius_sample_resolution); + coordf_t remains = radius - m_radius_sample_resolution * factor; + if (remains > EPSILON) { + return radius + m_radius_sample_resolution - remains; + } + else { + return radius; + } +#else + coordf_t resolution = m_radius_sample_resolution; + return ceil(radius / resolution) * resolution; +#endif +} + +const ExPolygons& TreeSupportData::calculate_collision(const RadiusLayerPair& key) const +{ + assert(key.layer_nr < m_layer_outlines.size()); + + ExPolygons collision_areas = offset_ex(m_layer_outlines[key.layer_nr], scale_(key.radius)); + const auto ret = m_collision_cache.insert({ key, std::move(collision_areas) }); + return ret.first->second; +} + +const ExPolygons& TreeSupportData::calculate_avoidance(const RadiusLayerPair& key) const +{ + const auto& radius = key.radius; + const auto& layer_nr = key.layer_nr; + BOOST_LOG_TRIVIAL(debug) << "calculate_avoidance on radius=" << radius << ", layer=" << layer_nr<<", recursion="< 0; layers_below++) { layer_nr_next = layer_heights[layer_nr_next].next_layer_nr; } + // Check if we would exceed the recursion limit by trying to process this layer + if (layers_below >= max_recursion_depth && m_avoidance_cache.find({radius, layer_nr_next}) == m_avoidance_cache.end()) { + // Force the calculation of the layer `max_recursion_depth` below our current one, ignoring the result. + get_avoidance(radius, layer_nr_next, key.recursions + 1); + } + + layer_nr_next = layer_heights[layer_nr].next_layer_nr; + ExPolygons avoidance_areas = offset_ex(get_avoidance(radius, layer_nr_next, key.recursions+1), scale_(-m_max_move)); + const ExPolygons &collision = get_collision(radius, layer_nr); + avoidance_areas.insert(avoidance_areas.end(), collision.begin(), collision.end()); + avoidance_areas = std::move(union_ex(avoidance_areas)); + auto ret = m_avoidance_cache.insert({ key, std::move(avoidance_areas) }); + //assert(ret.second); + return ret.first->second; + } else { + ExPolygons avoidance_areas = offset_ex(m_layer_outlines_below[layer_nr], scale_(m_xy_distance + radius)); + auto ret = m_avoidance_cache.insert({ key, std::move(avoidance_areas) }); + assert(ret.second); + return ret.first->second; } - FFFTreeSupport::generate_support_areas(*print_object.print(), - BuildVolume(Pointfs{ Vec2d{ -300., -300. }, Vec2d{ -300., +300. }, Vec2d{ +300., +300. }, Vec2d{ +300., -300. } }, 0.), { idx }, - throw_on_cancel); } -} // namespace Slic3r +} //namespace Slic3r diff --git a/src/libslic3r/Support/TreeSupport.hpp b/src/libslic3r/Support/TreeSupport.hpp index c79f1d4a3af..17e146ddcd6 100644 --- a/src/libslic3r/Support/TreeSupport.hpp +++ b/src/libslic3r/Support/TreeSupport.hpp @@ -1,299 +1,513 @@ -// Tree supports by Thomas Rahm, losely based on Tree Supports by CuraEngine. -// Original source of Thomas Rahm's tree supports: -// https://github.com/ThomasRahm/CuraEngine -// -// Original CuraEngine copyright: -// Copyright (c) 2021 Ultimaker B.V. -// CuraEngine is released under the terms of the AGPLv3 or higher. - -#ifndef slic3r_TreeSupport_hpp -#define slic3r_TreeSupport_hpp - -#include "SupportLayer.hpp" -#include "TreeModelVolumes.hpp" -#include "TreeSupportCommon.hpp" - -#include "../BoundingBox.hpp" -#include "../Point.hpp" -#include "../Utils.hpp" - -#include - - -// #define TREE_SUPPORT_SHOW_ERRORS - -#ifdef SLIC3R_TREESUPPORTS_PROGRESS - // The various stages of the process can be weighted differently in the progress bar. - // These weights are obtained experimentally using a small sample size. Sensible weights can differ drastically based on the assumed default settings and model. - #define TREE_PROGRESS_TOTAL 10000 - #define TREE_PROGRESS_PRECALC_COLL TREE_PROGRESS_TOTAL * 0.1 - #define TREE_PROGRESS_PRECALC_AVO TREE_PROGRESS_TOTAL * 0.4 - #define TREE_PROGRESS_GENERATE_NODES TREE_PROGRESS_TOTAL * 0.1 - #define TREE_PROGRESS_AREA_CALC TREE_PROGRESS_TOTAL * 0.3 - #define TREE_PROGRESS_DRAW_AREAS TREE_PROGRESS_TOTAL * 0.1 - #define TREE_PROGRESS_GENERATE_BRANCH_AREAS TREE_PROGRESS_DRAW_AREAS / 3 - #define TREE_PROGRESS_SMOOTH_BRANCH_AREAS TREE_PROGRESS_DRAW_AREAS / 3 - #define TREE_PROGRESS_FINALIZE_BRANCH_AREAS TREE_PROGRESS_DRAW_AREAS / 3 -#endif // SLIC3R_TREESUPPORTS_PROGRESS +#ifndef TREESUPPORT_H +#define TREESUPPORT_H + +#include +#include +#include "ExPolygon.hpp" +#include "Point.hpp" +#include "Slicing.hpp" +#include "MinimumSpanningTree.hpp" +#include "tbb/concurrent_unordered_map.h" +#include "Flow.hpp" +#include "PrintConfig.hpp" +#include "Fill/Lightning/Generator.hpp" + +#ifndef SQ +#define SQ(x) ((x)*(x)) +#endif namespace Slic3r { - -// Forward declarations -class Print; class PrintObject; -struct SlicingParameters; +class TreeSupport; +class SupportLayer; -namespace FFFTreeSupport +struct LayerHeightData { + coordf_t print_z = 0; + coordf_t height = 0; + size_t next_layer_nr = 0; + LayerHeightData() = default; + LayerHeightData(coordf_t z, coordf_t h, size_t next_layer) : print_z(z), height(h), next_layer_nr(next_layer) {} +}; -// The number of vertices in each circle. -static constexpr const size_t SUPPORT_TREE_CIRCLE_RESOLUTION = 25; - -struct AreaIncreaseSettings -{ - AreaIncreaseSettings( - TreeModelVolumes::AvoidanceType type = TreeModelVolumes::AvoidanceType::Fast, coord_t increase_speed = 0, - bool increase_radius = false, bool no_error = false, bool use_min_distance = false, bool move = false) : - increase_speed{ increase_speed }, type{ type }, increase_radius{ increase_radius }, no_error{ no_error }, use_min_distance{ use_min_distance }, move{ move } {} - - coord_t increase_speed; - // Packing for smaller memory footprint of SupportElementState && SupportElementMerging - TreeModelVolumes::AvoidanceType type; - bool increase_radius : 1; - bool no_error : 1; - bool use_min_distance : 1; - bool move : 1; - bool operator==(const AreaIncreaseSettings& other) const - { - return type == other.type && - increase_speed == other.increase_speed && - increase_radius == other.increase_radius && - no_error == other.no_error && - use_min_distance == other.use_min_distance && - move == other.move; +struct TreeNode { + Vec3f pos; + std::vector children; // index of children in the storing vector + std::vector parents; // index of parents in the storing vector + TreeNode(Point pt, float z) { + pos = { float(unscale_(pt.x())),float(unscale_(pt.y())),z }; } }; -#define TREE_SUPPORTS_TRACK_LOST - -// C++17 does not support in place initializers of bit values, thus a constructor zeroing the bits is provided. -struct SupportElementStateBits { - SupportElementStateBits() : - to_buildplate(false), - to_model_gracious(false), - use_min_xy_dist(false), - supports_roof(false), - can_use_safe_radius(false), - skip_ovalisation(false), -#ifdef TREE_SUPPORTS_TRACK_LOST - lost(false), - verylost(false), -#endif // TREE_SUPPORTS_TRACK_LOST - deleted(false), - marked(false) - {} - +/*! + * \brief Lazily generates tree guidance volumes. + * + * \warning This class is not currently thread-safe and should not be accessed in OpenMP blocks + */ +class TreeSupportData +{ +public: + TreeSupportData() = default; /*! - * \brief The element trys to reach the buildplate + * \brief Construct the TreeSupportData object + * + * \param xy_distance The required clearance between the model and the + * tree branches. + * \param max_move The maximum allowable movement between nodes on + * adjacent layers + * \param radius_sample_resolution Sample size used to round requested node radii. + * \param collision_resolution */ - bool to_buildplate : 1; + TreeSupportData(const PrintObject& object, coordf_t max_move, coordf_t radius_sample_resolution, coordf_t collision_resolution); + + TreeSupportData(TreeSupportData&&) = default; + TreeSupportData& operator=(TreeSupportData&&) = default; + + TreeSupportData(const TreeSupportData&) = delete; + TreeSupportData& operator=(const TreeSupportData&) = delete; /*! - * \brief Will the branch be able to rest completely on a flat surface, be it buildplate or model ? + * \brief Creates the areas that have to be avoided by the tree's branches. + * + * The result is a 2D area that would cause nodes of radius \p radius to + * collide with the model. + * + * \param radius The radius of the node of interest + * \param layer The layer of interest + * \return Polygons object */ - bool to_model_gracious : 1; + const ExPolygons& get_collision(coordf_t radius, size_t layer_idx) const; /*! - * \brief Whether the min_xy_distance can be used to get avoidance or similar. Will only be true if support_xy_overrides_z=Z overrides X/Y. + * \brief Creates the areas that have to be avoided by the tree's branches + * in order to reach the build plate. + * + * The result is a 2D area that would cause nodes of radius \p radius to + * collide with the model or be unable to reach the build platform. + * + * The input collision areas are inset by the maximum move distance and + * propagated upwards. + * + * \param radius The radius of the node of interest + * \param layer The layer of interest + * \return Polygons object */ - bool use_min_xy_dist : 1; + const ExPolygons& get_avoidance(coordf_t radius, size_t layer_idx, int recursions=0) const; + Polygons get_contours(size_t layer_nr) const; + Polygons get_contours_with_holes(size_t layer_nr) const; + + std::vector layer_heights; + + std::vector tree_nodes; + +private: /*! - * \brief True if this Element or any parent (element above) provides support to a support roof. + * \brief Convenience typedef for the keys to the caches */ - bool supports_roof : 1; + struct RadiusLayerPair { + coordf_t radius; + size_t layer_nr; + int recursions; + + }; + struct RadiusLayerPairEquality { + constexpr bool operator()(const RadiusLayerPair& _Left, const RadiusLayerPair& _Right) const { + return _Left.radius == _Right.radius && _Left.layer_nr == _Right.layer_nr; + } + }; + struct RadiusLayerPairHash { + size_t operator()(const RadiusLayerPair& elem) const { + return std::hash()(elem.radius) ^ std::hash()(elem.layer_nr * 7919); + } + }; /*! - * \brief An influence area is considered safe when it can use the holefree avoidance <=> It will not have to encounter holes on its way downward. + * \brief Round \p radius upwards to a multiple of m_radius_sample_resolution + * + * \param radius The radius of the node of interest */ - bool can_use_safe_radius : 1; + coordf_t ceil_radius(coordf_t radius) const; /*! - * \brief Skip the ovalisation to parent and children when generating the final circles. + * \brief Calculate the collision areas at the radius and layer indicated + * by \p key. + * + * \param key The radius and layer of the node of interest */ - bool skip_ovalisation : 1; - -#ifdef TREE_SUPPORTS_TRACK_LOST - // Likely a lost branch, debugging information. - bool lost : 1; - bool verylost : 1; -#endif // TREE_SUPPORTS_TRACK_LOST + const ExPolygons& calculate_collision(const RadiusLayerPair& key) const; - // Not valid anymore, to be deleted. - bool deleted : 1; + /*! + * \brief Calculate the avoidance areas at the radius and layer indicated + * by \p key. + * + * \param key The radius and layer of the node of interest + */ + const ExPolygons& calculate_avoidance(const RadiusLayerPair& key) const; - // General purpose flag marking a visited element. - bool marked : 1; -}; -struct SupportElementState : public SupportElementStateBits -{ +public: + bool is_slim = false; /*! - * \brief The layer this support elements wants reach + * \brief The required clearance between the model and the tree branches */ - LayerIndex target_height; + coordf_t m_xy_distance; /*! - * \brief The position this support elements wants to support on layer=target_height + * \brief The maximum distance that the centrepoint of a tree branch may + * move in consequtive layers */ - Point target_position; + coordf_t m_max_move; /*! - * \brief The next position this support elements wants to reach. NOTE: This is mainly a suggestion regarding direction inside the influence area. + * \brief Sample resolution for radius values. + * + * The radius will be rounded (upwards) to multiples of this value before + * calculations are done when collision, avoidance and internal model + * Polygons are requested. */ - Point next_position; + coordf_t m_radius_sample_resolution; /*! - * \brief The next height this support elements wants to reach + * \brief Storage for layer outlines of the meshes. */ - LayerIndex layer_idx; + std::vector m_layer_outlines; + + // union contours of all layers below + std::vector m_layer_outlines_below; /*! - * \brief The Effective distance to top of this element regarding radius increases and collision calculations. + * \brief Caches for the collision, avoidance and internal model polygons + * at given radius and layer indices. + * + * These are mutable to allow modification from const function. This is + * generally considered OK as the functions are still logically const + * (ie there is no difference in behaviour for the user betweeen + * calculating the values each time vs caching the results). + * + * coconut: previously stl::unordered_map is used which seems problematic with tbb::parallel_for. + * So we change to tbb::concurrent_unordered_map */ - uint32_t effective_radius_height; + mutable tbb::concurrent_unordered_map m_collision_cache; + mutable tbb::concurrent_unordered_map m_avoidance_cache; + + friend TreeSupport; +}; +struct LineHash { + size_t operator()(const Line& line) const { + return (std::hash()(line.a(0)) ^ std::hash()(line.b(1))) * 102 + + (std::hash()(line.a(1)) ^ std::hash()(line.b(0))) * 10222; + } +}; + +/*! + * \brief Generates a tree structure to support your models. + */ +class TreeSupport +{ +public: /*! - * \brief The amount of layers this element is below the topmost layer of this branch. + * \brief Creates an instance of the tree support generator. + * + * \param storage The data storage to get global settings from. */ - uint32_t distance_to_top; + TreeSupport(PrintObject& object, const SlicingParameters &slicing_params); /*! - * \brief The resulting center point around which a circle will be drawn later. - * Will be set by setPointsOnAreas + * \brief Create the areas that need support. + * + * These areas are stored inside the given SliceDataStorage object. + * \param storage The data storage where the mesh data is gotten from and + * where the resulting support areas are stored. */ - Point result_on_layer { std::numeric_limits::max(), std::numeric_limits::max() }; - bool result_on_layer_is_set() const { return this->result_on_layer != Point{ std::numeric_limits::max(), std::numeric_limits::max() }; } - void result_on_layer_reset() { this->result_on_layer = Point{ std::numeric_limits::max(), std::numeric_limits::max() }; } + void generate(); + + void detect_overhangs(bool detect_first_sharp_tail_only=false); + + enum NodeType { + eCircle, + eSquare, + ePolygon + }; + /*! - * \brief The amount of extra radius we got from merging branches that could have reached the buildplate, but merged with ones that can not. + * \brief Represents the metadata of a node in the tree. */ - coord_t increased_to_model_radius; // how much to model we increased only relevant for merging + struct Node + { + static constexpr Node* NO_PARENT = nullptr; + + Node() + : distance_to_top(0) + , position(Point(0, 0)) + , obj_layer_nr(0) + , support_roof_layers_below(0) + , support_floor_layers_above(0) + , to_buildplate(true) + , parent(nullptr) + , print_z(0.0) + , height(0.0) + {} + // when dist_mm_to_top_==0, new node's dist_mm_to_top=parent->dist_mm_to_top + parent->height; + Node(const Point position, const int distance_to_top, const int obj_layer_nr, const int support_roof_layers_below, const bool to_buildplate, Node* parent, + coordf_t print_z_, coordf_t height_, coordf_t dist_mm_to_top_=0) + : distance_to_top(distance_to_top) + , position(position) + , obj_layer_nr(obj_layer_nr) + , support_roof_layers_below(support_roof_layers_below) + , support_floor_layers_above(0) + , to_buildplate(to_buildplate) + , parent(parent) + , print_z(print_z_) + , height(height_) + , dist_mm_to_top(dist_mm_to_top_) + { + if (parent) { + type = parent->type; + overhang = parent->overhang; + if (dist_mm_to_top==0) + dist_mm_to_top = parent->dist_mm_to_top + parent->height; + parent->child = this; + for (auto& neighbor : parent->merged_neighbours) + neighbor->child = this; + } + } + +#ifdef DEBUG // Clear the delete node's data so if there's invalid access after, we may get a clue by inspecting that node. + ~Node() + { + parent = nullptr; + merged_neighbours.clear(); + } +#endif // DEBUG + + /*! + * \brief The number of layers to go to the top of this branch. + * Negative value means it's a virtual node between support and overhang, which doesn't need to be extruded. + */ + int distance_to_top; + coordf_t dist_mm_to_top = 0; // dist to bottom contact in mm + + /*! + * \brief The position of this node on the layer. + */ + Point position; + Point movement; // movement towards neighbor center or outline + mutable double radius = 0.0; + mutable double max_move_dist = 0.0; + NodeType type = eCircle; + bool is_merged = false; // this node is generated by merging upper nodes + bool is_corner = false; + bool is_processed = false; + const ExPolygon *overhang = nullptr; // when type==ePolygon, set this value to get original overhang area + + /*! + * \brief The direction of the skin lines above the tip of the branch. + * + * This determines in which direction we should reduce the width of the + * branch. + */ + bool skin_direction; + + /*! + * \brief The number of support roof layers below this one. + * + * When a contact point is created, it is determined whether the mesh + * needs to be supported with support roof or not, since that is a + * per-mesh setting. This is stored in this variable in order to track + * how far we need to extend that support roof downwards. + */ + int support_roof_layers_below; + int support_floor_layers_above; + int obj_layer_nr; + + /*! + * \brief Whether to try to go towards the build plate. + * + * If the node is inside the collision areas, it has no choice but to go + * towards the model. If it is not inside the collision areas, it must + * go towards the build plate to prevent a scar on the surface. + */ + bool to_buildplate; + + /*! + * \brief The originating node for this one, one layer higher. + * + * In order to prune branches that can't have any support (because they + * can't be on the model and the path to the buildplate isn't clear), + * the entire branch needs to be known. + */ + Node *parent; + Node *child = nullptr; + + /*! + * \brief All neighbours (on the same layer) that where merged into this node. + * + * In order to prune branches that can't have any support (because they + * can't be on the model and the path to the buildplate isn't clear), + * the entire branch needs to be known. + */ + std::list merged_neighbours; + + coordf_t print_z; + coordf_t height; + + bool operator==(const Node& other) const + { + return position == other.position; + } + }; + + struct SupportParams + { + Flow first_layer_flow; + Flow support_material_flow; + Flow support_material_interface_flow; + Flow support_material_bottom_interface_flow; + coordf_t support_extrusion_width; + // Is merging of regions allowed? Could the interface & base support regions be printed with the same extruder? + bool can_merge_support_regions; + + coordf_t support_layer_height_min; + // coordf_t support_layer_height_max; + + coordf_t gap_xy; + + float base_angle; + float interface_angle; + coordf_t interface_spacing; + coordf_t interface_density; + coordf_t support_spacing; + coordf_t support_density; + + InfillPattern base_fill_pattern; + InfillPattern interface_fill_pattern; + InfillPattern contact_fill_pattern; + bool with_sheath; + const double thresh_big_overhang = SQ(scale_(10)); + }; + + int avg_node_per_layer = 0; + float nodes_angle = 0; + bool has_overhangs = false; + bool has_sharp_tails = false; + bool has_cantilever = false; + double max_cantilever_dist = 0; + SupportType support_type; + SupportMaterialStyle support_style; + + std::unique_ptr generator; + std::unordered_map printZ_to_lightninglayer; + + std::function throw_on_cancel; +private: /*! - * \brief Counter about the times the elephant foot was increased. Can be fractions for merge reasons. + * \brief Generator for model collision, avoidance and internal guide volumes + * + * Lazily computes volumes as needed. + * \warning This class is NOT currently thread-safe and should not be accessed in OpenMP blocks */ - double elephant_foot_increases; + std::shared_ptr m_ts_data; + PrintObject *m_object; + const PrintObjectConfig *m_object_config; + SlicingParameters m_slicing_params; + // Various precomputed support parameters to be shared with external functions. + SupportParams m_support_params; + size_t m_raft_layers = 0; + size_t m_highest_overhang_layer = 0; + std::vector> m_spanning_trees; + std::vector< std::unordered_map> m_mst_line_x_layer_contour_caches; + coordf_t MAX_BRANCH_RADIUS = 10.0; + coordf_t MAX_BRANCH_RADIUS_FIRST_LAYER = 12.0; + coordf_t MIN_BRANCH_RADIUS = 0.5; + float tree_support_branch_diameter_angle = 5.0; + bool is_strong = false; + bool is_slim = false; + bool with_infill = false; + /*! - * \brief The element tries to not move until this dtt is reached, is set to 0 if the element had to move. + * \brief Polygons representing the limits of the printable area of the + * machine */ - uint32_t dont_move_until; + ExPolygon m_machine_border; /*! - * \brief Settings used to increase the influence area to its current state. + * \brief Draws circles around each node of the tree into the final support. + * + * This also handles the areas that have to become support roof, support + * bottom, the Z distances, etc. + * + * \param storage[in, out] The settings storage to get settings from and to + * save the resulting support polygons to. + * \param contact_nodes The nodes to draw as support. */ - AreaIncreaseSettings last_area_increase; + void draw_circles(const std::vector>& contact_nodes); /*! - * \brief Amount of roof layers that were not yet added, because the branch needed to move. + * \brief Drops down the nodes of the tree support towards the build plate. + * + * This is where the cleverness of tree support comes in: The nodes stay on + * their 2D layers but on the next layer they are slightly shifted. This + * causes them to move towards each other as they are copied to lower layers + * which ultimately results in a 3D tree. + * + * \param contact_nodes[in, out] The nodes in the space that need to be + * dropped down. The nodes are dropped to lower layers inside the same + * vector of layers. */ - uint32_t missing_roof_layers; - - // called by increase_single_area() and increaseAreas() - [[nodiscard]] static SupportElementState propagate_down(const SupportElementState &src) - { - SupportElementState dst{ src }; - ++ dst.distance_to_top; - -- dst.layer_idx; - // set to invalid as we are a new node on a new layer - dst.result_on_layer_reset(); - dst.skip_ovalisation = false; - return dst; - } + void drop_nodes(std::vector> &contact_nodes); - [[nodiscard]] bool locked() const { return this->distance_to_top < this->dont_move_until; } -}; + void smooth_nodes(std::vector> &contact_nodes); -/*! - * \brief Get the Distance to top regarding the real radius this part will have. This is different from distance_to_top, which is can be used to calculate the top most layer of the branch. - * \param elem[in] The SupportElement one wants to know the effectiveDTT - * \return The Effective DTT. - */ -[[nodiscard]] inline size_t getEffectiveDTT(const TreeSupportSettings &settings, const SupportElementState &elem) -{ - return elem.effective_radius_height < settings.increase_radius_until_layer ? - (elem.distance_to_top < settings.increase_radius_until_layer ? elem.distance_to_top : settings.increase_radius_until_layer) : - elem.effective_radius_height; -} + void adjust_layer_heights(std::vector>& contact_nodes); -/*! - * \brief Get the Radius, that this element will have. - * \param elem[in] The Element. - * \return The radius the element has. - */ -[[nodiscard]] inline coord_t support_element_radius(const TreeSupportSettings &settings, const SupportElementState &elem) -{ - return settings.getRadius(getEffectiveDTT(settings, elem), elem.elephant_foot_increases); -} - -/*! - * \brief Get the collision Radius of this Element. This can be smaller then the actual radius, as the drawAreas will cut off areas that may collide with the model. - * \param elem[in] The Element. - * \return The collision radius the element has. - */ -[[nodiscard]] inline coord_t support_element_collision_radius(const TreeSupportSettings &settings, const SupportElementState &elem) -{ - return settings.getRadius(elem.effective_radius_height, elem.elephant_foot_increases); -} - -struct SupportElement -{ - using ParentIndices = -#ifdef NDEBUG - // To reduce memory allocation in release mode. - boost::container::small_vector; -#else // NDEBUG - // To ease debugging. - std::vector; -#endif // NDEBUG - -// SupportElement(const SupportElementState &state) : SupportElementState(state) {} - SupportElement(const SupportElementState &state, Polygons &&influence_area) : state(state), influence_area(std::move(influence_area)) {} - SupportElement(const SupportElementState &state, ParentIndices &&parents, Polygons &&influence_area) : - state(state), parents(std::move(parents)), influence_area(std::move(influence_area)) {} - - SupportElementState state; + /*! BBS: MusangKing: maximum layer height + * \brief Optimize the generation of tree support by pre-planning the layer_heights + * + */ + std::vector plan_layer_heights(std::vector> &contact_nodes); /*! - * \brief All elements in the layer above the current one that are supported by this element + * \brief Creates points where support contacts the model. + * + * A set of points is created for each layer. + * \param mesh The mesh to get the overhang areas to support of. + * \param contact_nodes[out] A vector of mappings from contact points to + * their tree nodes. + * \param collision_areas For every layer, the areas where a generated + * contact point would immediately collide with the model due to the X/Y + * distance. + * \return For each layer, a list of points where the tree should connect + * with the model. */ - ParentIndices parents; + void generate_contact_points(std::vector>& contact_nodes); /*! - * \brief The resulting influence area. - * Will only be set in the results of createLayerPathing, and will be nullptr inside! + * \brief Add a node to the next layer. + * + * If a node is already at that position in the layer, the nodes are merged. */ - Polygons influence_area; + void insert_dropped_node(std::vector& nodes_layer, Node* node); + void create_tree_support_layers(); + void generate_toolpaths(); + Polygons spanning_tree_to_polygon(const std::vector& spanning_trees, Polygons layer_contours, int layer_nr); + Polygons contact_nodes_to_polygon(const std::vector& contact_nodes, Polygons layer_contours, int layer_nr, std::vector& radiis, std::vector& is_interface); + coordf_t calc_branch_radius(coordf_t base_radius, size_t layers_to_top, size_t tip_layers, double diameter_angle_scale_factor); + coordf_t calc_branch_radius(coordf_t base_radius, coordf_t mm_to_top, double diameter_angle_scale_factor); + + // similar to SupportMaterial::trim_support_layers_by_object + Polygons get_trim_support_regions( + const PrintObject& object, + SupportLayer* support_layer_ptr, + const coordf_t gap_extra_above, + const coordf_t gap_extra_below, + const coordf_t gap_xy); }; -using SupportElements = std::deque; - -[[nodiscard]] inline coord_t support_element_radius(const TreeSupportSettings &settings, const SupportElement &elem) -{ - return support_element_radius(settings, elem.state); -} - -[[nodiscard]] inline coord_t support_element_collision_radius(const TreeSupportSettings &settings, const SupportElement &elem) -{ - return support_element_collision_radius(settings, elem.state); } -} // namespace FFFTreeSupport - -void fff_tree_support_generate(PrintObject &print_object, std::function throw_on_cancel = []{}); - -} // namespace Slic3r - -#endif /* slic3r_TreeSupport_hpp */ +#endif /* TREESUPPORT_H */ diff --git a/src/libslic3r/Support/TreeSupport3D.cpp b/src/libslic3r/Support/TreeSupport3D.cpp index ba3b1810ce9..c96801e008d 100644 --- a/src/libslic3r/Support/TreeSupport3D.cpp +++ b/src/libslic3r/Support/TreeSupport3D.cpp @@ -7,22 +7,21 @@ // CuraEngine is released under the terms of the AGPLv3 or higher. #include "TreeSupport3D.hpp" -#include "AABBTreeIndirect.hpp" -#include "AABBTreeLines.hpp" -#include "BuildVolume.hpp" -#include "ClipperUtils.hpp" -#include "EdgeGrid.hpp" -#include "Fill/Fill.hpp" -#include "Layer.hpp" -#include "Print.hpp" -#include "MultiPoint.hpp" -#include "Polygon.hpp" -#include "Polyline.hpp" -#include "MutablePolygon.hpp" -#include "SupportMaterial.hpp" -#include "TriangleMeshSlicer.hpp" -#include "TreeSupport.hpp" -#include "I18N.hpp" +#include "TreeSupportCommon.hpp" +#include "SupportCommon.hpp" + +#include "../AABBTreeIndirect.hpp" +#include "../BuildVolume.hpp" +#include "../ClipperUtils.hpp" +#include "../EdgeGrid.hpp" +#include "../Fill/Fill.hpp" +#include "../Layer.hpp" +#include "../Print.hpp" +#include "../MultiPoint.hpp" +#include "../Polygon.hpp" +#include "../Polyline.hpp" +#include "../MutablePolygon.hpp" +#include "libslic3r.h" #include #include @@ -34,10 +33,7 @@ #include -#define TBB_PREVIEW_GLOBAL_CONTROL 1 -#include #include -#include #if defined(TREE_SUPPORT_SHOW_ERRORS) && defined(_WIN32) #define TREE_SUPPORT_SHOW_ERRORS_WIN32 @@ -46,16 +42,12 @@ #define TREE_SUPPORT_ORGANIC_NUDGE_NEW 1 #ifndef TREE_SUPPORT_ORGANIC_NUDGE_NEW - // Old version using OpenVDB, works but it is extremely slow for complex meshes. - #include "OpenVDBUtilsLegacy.hpp" - #include +// Old version using OpenVDB, works but it is extremely slow for complex meshes. +#include "../OpenVDBUtilsLegacy.hpp" +#include #endif // TREE_SUPPORT_ORGANIC_NUDGE_NEW -#ifndef _L -#define _L(s) Slic3r::I18N::translate(s) -#endif - - //#define TREESUPPORT_DEBUG_SVG +// #define TREESUPPORT_DEBUG_SVG namespace Slic3r { @@ -133,8 +125,6 @@ static inline void check_self_intersections(const ExPolygon &expoly, const std:: #endif // TREE_SUPPORT_SHOW_ERRORS_WIN32 } -// static constexpr const auto tiny_area_threshold = sqr(scaled(0.001)); - static std::vector>> group_meshes(const Print &print, const std::vector &print_object_ids) { std::vector>> grouped_meshes; @@ -154,12 +144,6 @@ static std::vector>> group_me // as different settings in the same group may only occur in the tip, which uses the original settings objects from the meshes. for (size_t object_id : print_object_ids) { const PrintObject &print_object = *print.get_object(object_id); -#ifndef NDEBUG - const PrintObjectConfig &object_config = print_object.config(); -#endif // NDEBUG - // Support must be enabled and set to Tree style. - //assert(object_config.support_material); - //assert(object_config.support_material_style == smsTree || object_config.support_material_style == smsOrganic); bool found_existing_group = false; TreeSupportSettings next_settings{ TreeSupportMeshGroupSettings{ print_object }, print_object.slicing_parameters() }; @@ -218,7 +202,7 @@ static std::vector>> group_me const PrintConfig &print_config = print_object.print()->config(); const PrintObjectConfig &config = print_object.config(); - const bool support_auto = is_auto(config.support_type.value); + const bool support_auto = config.enable_support.value && is_auto(config.support_type.value); const int support_enforce_layers = config.enforce_support_layers.value; std::vector enforcers_layers{ print_object.slice_support_enforcers() }; std::vector blockers_layers{ print_object.slice_support_blockers() }; @@ -229,8 +213,7 @@ static std::vector>> group_me // +1 makes the threshold inclusive double tan_threshold = support_threshold_auto ? 0. : tan(M_PI * double(support_threshold + 1) / 180.); //FIXME this is a fudge constant! - double support_tree_tip_diameter = 0.8; - auto enforcer_overhang_offset = scaled(support_tree_tip_diameter); + auto enforcer_overhang_offset = scaled(config.tree_support_tip_diameter.value); size_t num_overhang_layers = support_auto ? num_object_layers : std::min(num_object_layers, std::max(size_t(support_enforce_layers), enforcers_layers.size())); tbb::parallel_for(tbb::blocked_range(1, num_overhang_layers), @@ -266,13 +249,17 @@ static std::vector>> group_me raw_overhangs = overhangs; raw_overhangs_calculated = true; } - if (! (enforced_layer || blockers_layers.empty() || blockers_layers[layer_id].empty())) - overhangs = diff(overhangs, blockers_layers[layer_id], ApplySafetyOffset::Yes); - //if (config.bridge_no_support) { - // for (const LayerRegion *layerm : current_layer.regions()) - // remove_bridges_from_contacts(print_config, lower_layer, *layerm, - // float(layerm->flow(frExternalPerimeter).scaled_width()), overhangs); - //} + if (! (enforced_layer || blockers_layers.empty() || blockers_layers[layer_id].empty())) { + Polygons &blocker = blockers_layers[layer_id]; + // Arthur: union_ is a must because after mirroring, the blocker polygons are in left-hand coordinates, ie clockwise, + // which are not valid polygons, and will be removed by offset. union_ can make these polygons right. + overhangs = diff(overhangs, offset(union_(blocker), scale_(g_config_tree_support_collision_resolution)), ApplySafetyOffset::Yes); + } + if (config.bridge_no_support) { + for (const LayerRegion *layerm : current_layer.regions()) + remove_bridges_from_contacts(print_config, lower_layer, *layerm, + float(layerm->flow(frExternalPerimeter).scaled_width()), overhangs); + } } //check_self_intersections(overhangs, "generate_overhangs1"); if (! enforcers_layers.empty() && ! enforcers_layers[layer_id].empty()) { @@ -310,21 +297,49 @@ static std::vector>> group_me } }); +#if 0 + if (num_raft_layers > 0) { + const Layer &first_layer = *print_object.get_layer(0); + // Final overhangs. + Polygons overhangs = + // Don't apply blockes on raft layer. + //(! blockers_layers.empty() && ! blockers_layers[layer_id].empty() ? + // diff(first_layer.lslices, blockers_layers[layer_id], ApplySafetyOffset::Yes) : + to_polygons(first_layer.lslices); +#if 0 + if (! enforcers_layers.empty() && ! enforcers_layers[layer_id].empty()) { + if (Polygons enforced_overhangs = intersection(first_layer.lslices, enforcers_layers[layer_id] /*, ApplySafetyOffset::Yes */); + ! enforced_overhangs.empty()) { + //FIXME this is a hack to make enforcers work on steep overhangs. + //FIXME enforcer_overhang_offset is a fudge constant! + enforced_overhangs = offset(union_ex(enforced_overhangs), enforcer_overhang_offset); + overhangs = overhangs.empty() ? std::move(enforced_overhangs) : union_(overhangs, enforced_overhangs); + } + } +#endif + out[num_raft_layers] = std::move(overhangs); + throw_on_cancel(); + } +#endif + return out; } /*! * \brief Precalculates all avoidances, that could be required. + * + * \param storage[in] Background storage to access meshes. + * \param currently_processing_meshes[in] Indexes of all meshes that are processed in this iteration */ [[nodiscard]] static LayerIndex precalculate(const Print &print, const std::vector &overhangs, const TreeSupportSettings &config, const std::vector &object_ids, TreeModelVolumes &volumes, std::function throw_on_cancel) { // calculate top most layer that is relevant for support LayerIndex max_layer = 0; for (size_t object_id : object_ids) { - const PrintObject &print_object = *print.get_object(object_id); + const PrintObject &print_object = *print.get_object(object_id); const int num_raft_layers = int(config.raft_layers.size()); const int num_layers = int(print_object.layer_count()) + num_raft_layers; - int max_support_layer_id = 0; + int max_support_layer_id = 0; for (int layer_id = std::max(num_raft_layers, 1); layer_id < num_layers; ++ layer_id) if (! overhangs[layer_id].empty()) max_support_layer_id = layer_id; @@ -464,7 +479,7 @@ static std::optional> polyline_sample_next_point_at_dis { const double dist2 = sqr(dist); const auto dist2i = int64_t(dist2); - static constexpr const auto eps = scaled(0.01); + const auto eps = scaled(0.01); for (size_t i = start_idx + 1; i < polyline.size(); ++ i) { const Point p1 = polyline[i]; @@ -619,6 +634,7 @@ static std::optional> polyline_sample_next_point_at_dis * \return A Polygons object that represents the resulting infill lines. */ [[nodiscard]] static Polylines generate_support_infill_lines( + // Polygon to fill in with a zig-zag pattern supporting an overhang. const Polygons &polygon, const SupportParameters &support_params, bool roof, LayerIndex layer_idx, coord_t support_infill_distance) @@ -659,6 +675,9 @@ static std::optional> polyline_sample_next_point_at_dis append(lines, to_polylines(polygons)); return lines; #else +// const bool connected_zigzags = roof ? false : config.connect_zigzags; +// const int support_shift = roof ? 0 : support_infill_distance / 2; + const Flow &flow = roof ? support_params.support_material_interface_flow : support_params.support_material_flow; std::unique_ptr filler = std::unique_ptr(Fill::new_from_type(roof ? support_params.interface_fill_pattern : support_params.base_fill_pattern)); FillParams fill_params; @@ -758,7 +777,7 @@ static std::optional> polyline_sample_next_point_at_dis Polygons collision_trimmed_buffer; auto collision_trimmed = [&collision_trimmed_buffer, &collision, &ret, distance]() -> const Polygons& { if (collision_trimmed_buffer.empty() && ! collision.empty()) - collision_trimmed_buffer = ClipperUtils::clip_clipper_polygons_with_subject_bbox(collision, get_extents(ret).inflated(std::max(0, distance) + SCALED_EPSILON)); + collision_trimmed_buffer = ClipperUtils::clip_clipper_polygons_with_subject_bbox(collision, get_extents(ret).inflated(std::max((coord_t)0, distance) + SCALED_EPSILON)); return collision_trimmed_buffer; }; @@ -795,13 +814,13 @@ static std::optional> polyline_sample_next_point_at_dis ret = diff(offset(ret, step_size, ClipperLib::jtRound, scaled(0.01)), collision_trimmed()); // ensure that if many offsets are done the performance does not suffer extremely by the new vertices of jtRound. if (i % 10 == 7) - ret = polygons_simplify(ret, scaled(0.015)); + ret = polygons_simplify(ret, scaled(0.015), polygons_strictly_simple); } // offset the remainder float last_offset = distance - steps * step_size; if (last_offset > SCALED_EPSILON) ret = offset(ret, distance - steps * step_size, ClipperLib::jtRound, scaled(0.01)); - ret = polygons_simplify(ret, scaled(0.015)); + ret = polygons_simplify(ret, scaled(0.015), polygons_strictly_simple); if (do_final_difference) ret = diff(ret, collision_trimmed()); @@ -822,6 +841,9 @@ class RichInterfacePlacer : public InterfacePlacer { { m_already_inserted.assign(num_support_layers, {}); this->min_xy_dist = this->config.xy_distance > this->config.xy_min_distance; + m_base_radius = scaled(0.01); + m_base_circle = Polygon{ make_circle(m_base_radius, SUPPORT_TREE_CIRCLE_RESOLUTION) }; + } const TreeModelVolumes &volumes; // Radius of the tree tip is large enough to be covered by an interface. @@ -957,15 +979,14 @@ class RichInterfacePlacer : public InterfacePlacer { std::vector &move_bounds; // Temps - static constexpr const auto m_base_radius = scaled(0.01); - const Polygon m_base_circle { make_circle(m_base_radius, SUPPORT_TREE_CIRCLE_RESOLUTION) }; + coord_t m_base_radius; + Polygon m_base_circle; // Mutexes, guards std::mutex m_mutex_movebounds; std::vector> m_already_inserted; }; - int generate_raft_contact( const PrintObject &print_object, const TreeSupportSettings &config, @@ -982,7 +1003,7 @@ int generate_raft_contact( // Create the raft contact layer. const ExPolygons &lslices = print_object.get_layer(0)->lslices; double expansion = print_object.config().raft_expansion.value; - interface_placer.add_roof_unguarded(expansion > 0 ? offset(lslices, scaled(expansion)) : to_polygons(lslices), raft_contact_layer_idx, 0); + interface_placer.add_roof_unguarded(expansion > 0 ? expand(lslices, scaled(expansion)) : to_polygons(lslices), raft_contact_layer_idx, 0); } return raft_contact_layer_idx; } @@ -1051,7 +1072,7 @@ void finalize_raft_contact( // roofs aka interface layers by the tool path generator. void sample_overhang_area( // Area to support - Polygons&& overhang_area, + Polygons &&overhang_area, // If true, then the overhang_area is likely large and wide, thus it is worth to try // to cover it with continuous interfaces supported by zig-zag patterned tree tips. const bool large_horizontal_roof, @@ -1062,9 +1083,9 @@ void sample_overhang_area( // const coord_t connect_length, // Configuration classes - const TreeSupportMeshGroupSettings& mesh_group_settings, + const TreeSupportMeshGroupSettings &mesh_group_settings, // Configuration & Output - RichInterfacePlacer& interface_placer) + RichInterfacePlacer &interface_placer) { // Assumption is that roof will support roof further up to avoid a lot of unnecessary branches. Each layer down it is checked whether the roof area // is still large enough to be a roof and aborted as soon as it is not. This part was already reworked a few times, and there could be an argument @@ -1073,13 +1094,13 @@ void sample_overhang_area( // as the pattern may be different one layer below. Same with calculating which points are now no longer being generated as result from // a decreasing roof, as there is no guarantee that a line will be above these points. Implementing a separate roof support behavior // for each pattern harms maintainability as it very well could be >100 LOC - auto generate_roof_lines = [&interface_placer, &mesh_group_settings](const Polygons& area, LayerIndex layer_idx) -> Polylines { + auto generate_roof_lines = [&interface_placer, &mesh_group_settings](const Polygons &area, LayerIndex layer_idx) -> Polylines { return generate_support_infill_lines(area, interface_placer.support_parameters, true, layer_idx, mesh_group_settings.support_roof_line_distance); - }; + }; LineInformations overhang_lines; // Track how many top contact / interface layers were already generated. - size_t dtt_roof = 0; + size_t dtt_roof = 0; size_t layer_generation_dtt = 0; if (large_horizontal_roof) { @@ -1088,12 +1109,12 @@ void sample_overhang_area( // To catch these cases the added roofs are saved to be evaluated later. std::vector added_roofs(num_support_roof_layers); Polygons last_overhang = overhang_area; - for (dtt_roof = 0; dtt_roof < num_support_roof_layers && layer_idx - dtt_roof >= 1; ++dtt_roof) { + for (dtt_roof = 0; dtt_roof < num_support_roof_layers && layer_idx - dtt_roof >= 1; ++ dtt_roof) { // here the roof is handled. If roof can not be added the branches will try to not move instead Polygons forbidden_next; { const bool min_xy_dist = interface_placer.config.xy_distance > interface_placer.config.xy_min_distance; - const Polygons& forbidden_next_raw = interface_placer.config.support_rests_on_model ? + const Polygons &forbidden_next_raw = interface_placer.config.support_rests_on_model ? interface_placer.volumes.getCollision(interface_placer.config.getRadius(0), layer_idx - (dtt_roof + 1), min_xy_dist) : interface_placer.volumes.getAvoidance(interface_placer.config.getRadius(0), layer_idx - (dtt_roof + 1), TreeModelVolumes::AvoidanceType::Fast, false, min_xy_dist); // prevent rounding errors down the line @@ -1109,14 +1130,14 @@ void sample_overhang_area( overhang_lines = split_lines( convert_lines_to_internal(interface_placer.volumes, interface_placer.config, ensure_maximum_distance_polyline(generate_roof_lines(last_overhang, layer_idx - dtt_before), connect_length, 1), layer_idx - dtt_before), - [&interface_placer, layer_idx, dtt_before](const std::pair& p) - { return evaluate_point_for_next_layer_function(interface_placer.volumes, interface_placer.config, layer_idx - dtt_before, p); }) + [&interface_placer, layer_idx, dtt_before](const std::pair &p) + { return evaluate_point_for_next_layer_function(interface_placer.volumes, interface_placer.config, layer_idx - dtt_before, p); }) .first; } break; } added_roofs[dtt_roof] = overhang_area; - last_overhang = std::move(overhang_area); + last_overhang = std::move(overhang_area); overhang_area = std::move(overhang_area_next); } @@ -1139,26 +1160,26 @@ void sample_overhang_area( // support_line_width to form a line here as otherwise most will be unsupported. Technically this violates branch distance, but not only is this the only reasonable choice, // but it ensures consistant behaviour as some infill patterns generate each line segment as its own polyline part causing a similar line forming behaviour. // This is not doen when a roof is above as the roof will support the model and the trees only need to support the roof - bool supports_roof = dtt_roof > 0; - bool continuous_tips = !supports_roof && large_horizontal_roof; + bool supports_roof = dtt_roof > 0; + bool continuous_tips = ! supports_roof && large_horizontal_roof; Polylines polylines = ensure_maximum_distance_polyline( generate_support_infill_lines(overhang_area, interface_placer.support_parameters, supports_roof, layer_idx - layer_generation_dtt, supports_roof ? mesh_group_settings.support_roof_line_distance : mesh_group_settings.support_tree_branch_distance), continuous_tips ? interface_placer.config.min_radius / 2 : connect_length, 1); size_t point_count = 0; - for (const Polyline& poly : polylines) + for (const Polyline &poly : polylines) point_count += poly.size(); const size_t min_support_points = std::max(coord_t(1), std::min(coord_t(3), coord_t(total_length(overhang_area) / connect_length))); if (point_count <= min_support_points) { // add the outer wall (of the overhang) to ensure it is correct supported instead. Try placing the support points in a way that they fully support the outer wall, instead of just the with half of the the support line width. // I assume that even small overhangs are over one line width wide, so lets try to place the support points in a way that the full support area generated from them - // will support the overhang (if this is not done it may only be half). This WILL NOT be the case when supporting an angle of about < 60 degrees so there is a fallback, + // will support the overhang (if this is not done it may only be half). This WILL NOT be the case when supporting an angle of about < 60� so there is a fallback, // as some support is better than none. - Polygons reduced_overhang_area = offset(union_ex(overhang_area), -interface_placer.config.support_line_width / 2.2, jtMiter, 1.2); + Polygons reduced_overhang_area = offset(union_ex(overhang_area), - interface_placer.config.support_line_width / 2.2, jtMiter, 1.2); polylines = ensure_maximum_distance_polyline( to_polylines( - !reduced_overhang_area.empty() && - area(offset(diff_ex(overhang_area, reduced_overhang_area), std::max(interface_placer.config.support_line_width, connect_length), jtMiter, 1.2)) < sqr(scaled(0.001)) ? + ! reduced_overhang_area.empty() && + area(offset(diff_ex(overhang_area, reduced_overhang_area), std::max(interface_placer.config.support_line_width, connect_length), jtMiter, 1.2)) < sqr(scaled(0.001)) ? reduced_overhang_area : overhang_area), connect_length, min_support_points); @@ -1187,16 +1208,6 @@ void sample_overhang_area( } } -inline SupportGeneratorLayer& layer_allocate( - SupportGeneratorLayerStorage& layer_storage, - SupporLayerType layer_type, - const SlicingParameters &slicing_params, - size_t layer_idx) -{ - auto& layer = layer_storage.allocate(layer_type); - return layer_initialize(layer, layer_type, slicing_params, layer_idx); -} - /*! * \brief Creates the initial influence areas (that can later be propagated down) by placing them below the overhang. * @@ -1206,7 +1217,7 @@ inline SupportGeneratorLayer& layer_allocate( * \param move_bounds[out] Storage for the influence areas. * \param storage[in] Background storage, required for adding roofs. */ -void generate_initial_areas( +static void generate_initial_areas( const PrintObject &print_object, const TreeModelVolumes &volumes, const TreeSupportSettings &config, @@ -1218,6 +1229,7 @@ void generate_initial_areas( using AvoidanceType = TreeModelVolumes::AvoidanceType; TreeSupportMeshGroupSettings mesh_group_settings(print_object); + // To ensure z_distance_top_layers are left empty between the overhang (zeroth empty layer), the support has to be added z_distance_top_layers+1 layers below const size_t z_distance_delta = config.z_distance_top_layers + 1; const bool min_xy_dist = config.xy_distance > config.xy_min_distance; @@ -1241,7 +1253,7 @@ void generate_initial_areas( //FIXME There is no account for the width of the collision regions. const coord_t extra_outset = std::max(coord_t(0), config.min_radius - config.support_line_width / 2) + (min_xy_dist ? config.support_line_width / 2 : 0) //FIXME this is a heuristic value for support enforcers to work. -// + 10 * mesh_config.support_line_width; +// + 10 * config.support_line_width; ; const size_t num_support_roof_layers = mesh_group_settings.support_roof_layers; const bool roof_enabled = num_support_roof_layers > 0; @@ -1254,15 +1266,13 @@ void generate_initial_areas( coord_t max_overhang_insert_lag = 0; if (config.z_distance_top_layers > 0) { max_overhang_insert_lag = 2 * config.z_distance_top_layers; - - //FIXME if (mesh_group_settings.support_angle > EPSILON && mesh_group_settings.support_angle < 0.5 * M_PI - EPSILON) { //FIXME mesh_group_settings.support_angle does not apply to enforcers and also it does not apply to automatic support angle (by half the external perimeter width). - // take the least restrictive avoidance possible + //used by max_overhang_insert_lag, only if not min_xy_dist. const auto max_overhang_speed = coord_t(tan(mesh_group_settings.support_angle) * config.layer_height); max_overhang_insert_lag = std::max(max_overhang_insert_lag, round_up_divide(config.xy_distance, max_overhang_speed / 2)); - } } + } size_t num_support_layers; int raft_contact_layer_idx; @@ -1387,6 +1397,7 @@ void generate_initial_areas( throw_on_cancel(); if (roof_enabled) { + // Try to support the overhangs by dense interfaces for num_support_roof_layers, cover the bottom most interface with tree tips. static constexpr const coord_t support_roof_offset = 0; Polygons overhang_roofs = safe_offset_inc(overhang_raw, support_roof_offset, relevant_forbidden, config.min_radius * 2 + config.xy_min_distance, 0, 1); if (mesh_group_settings.minimum_support_area > 0) @@ -1403,7 +1414,6 @@ void generate_initial_areas( // or roof is enabled and these are the thin overhangs at object slopes (not horizontal overhangs). if (mesh_group_settings.minimum_support_area > 0) remove_small(overhang_regular, mesh_group_settings.minimum_support_area); - for (ExPolygon &support_part : union_ex(overhang_regular)) { sample_overhang_area(to_polygons(std::move(support_part)), false, layer_idx, num_support_roof_layers, connect_length, @@ -1565,6 +1575,7 @@ static Point move_inside_if_outside(const Polygons &polygons, Point from, int di current_elem.effective_radius_height += 1; coord_t radius = support_element_collision_radius(config, current_elem); + const auto _tiny_area_threshold = tiny_area_threshold(); if (settings.move) { increased = relevant_offset; if (overspeed > 0) { @@ -1578,14 +1589,14 @@ static Point move_inside_if_outside(const Polygons &polygons, Point from, int di } if (settings.no_error && settings.move) // as ClipperLib::jtRound has to be used for offsets this simplify is VERY important for performance. - polygons_simplify(increased, scaled(0.025)); + polygons_simplify(increased, scaled(0.025), polygons_strictly_simple); } else // if no movement is done the areas keep parent area as no move == offset(0) increased = parent.influence_area; if (mergelayer || current_elem.to_buildplate) { to_bp_data = safe_union(diff_clipped(increased, volumes.getAvoidance(radius, layer_idx - 1, settings.type, false, settings.use_min_distance))); - if (! current_elem.to_buildplate && area(to_bp_data) > tiny_area_threshold) { + if (! current_elem.to_buildplate && area(to_bp_data) > _tiny_area_threshold) { // mostly happening in the tip, but with merges one should check every time, just to be sure. current_elem.to_buildplate = true; // sometimes nodes that can reach the buildplate are marked as cant reach, tainting subtrees. This corrects it. BOOST_LOG_TRIVIAL(debug) << "Corrected taint leading to a wrong to model value on layer " << layer_idx - 1 << " targeting " << @@ -1597,18 +1608,19 @@ static Point move_inside_if_outside(const Polygons &polygons, Point from, int di to_model_data = safe_union(diff_clipped(increased, volumes.getAvoidance(radius, layer_idx - 1, settings.type, true, settings.use_min_distance))); if (!current_elem.to_model_gracious) { - if (mergelayer && area(to_model_data) >= tiny_area_threshold) { + if (mergelayer && area(to_model_data) >= _tiny_area_threshold) { current_elem.to_model_gracious = true; BOOST_LOG_TRIVIAL(debug) << "Corrected taint leading to a wrong non gracious value on layer " << layer_idx - 1 << " targeting " << current_elem.target_height << " with radius " << radius; } else + // Cannot route to gracious areas. Push the tree away from object and route it down anyways. to_model_data = safe_union(diff_clipped(increased, volumes.getCollision(radius, layer_idx - 1, settings.use_min_distance))); } } check_layer_data = current_elem.to_buildplate ? to_bp_data : to_model_data; - if (settings.increase_radius && area(check_layer_data) > tiny_area_threshold) { + if (settings.increase_radius && area(check_layer_data) > _tiny_area_threshold) { auto validWithRadius = [&](coord_t next_radius) { if (volumes.ceilRadius(next_radius, settings.use_min_distance) <= volumes.ceilRadius(radius, settings.use_min_distance)) return true; @@ -1624,7 +1636,7 @@ static Point move_inside_if_outside(const Polygons &polygons, Point from, int di volumes.getAvoidance(next_radius, layer_idx - 1, settings.type, true, settings.use_min_distance) : volumes.getCollision(next_radius, layer_idx - 1, settings.use_min_distance)); Polygons check_layer_data_2 = current_elem.to_buildplate ? to_bp_data_2 : to_model_data_2; - return area(check_layer_data_2) > tiny_area_threshold; + return area(check_layer_data_2) > _tiny_area_threshold; }; coord_t ceil_radius_before = volumes.ceilRadius(radius, settings.use_min_distance); @@ -1667,7 +1679,7 @@ static Point move_inside_if_outside(const Polygons &polygons, Point from, int di volumes.getCollision(radius, layer_idx - 1, settings.use_min_distance) )); check_layer_data = current_elem.to_buildplate ? to_bp_data : to_model_data; - if (area(check_layer_data) < tiny_area_threshold) { + if (area(check_layer_data) < _tiny_area_threshold) { BOOST_LOG_TRIVIAL(error) << "Lost area by doing catch up from " << ceil_radius_before << " to radius " << volumes.ceilRadius(support_element_collision_radius(config, current_elem), settings.use_min_distance); tree_supports_show_error("Area lost catching up radius. May not cause visible malformation."sv, true); @@ -1675,7 +1687,7 @@ static Point move_inside_if_outside(const Polygons &polygons, Point from, int di } } - return area(check_layer_data) > tiny_area_threshold ? std::optional(current_elem) : std::optional(); + return area(check_layer_data) > _tiny_area_threshold ? std::optional(current_elem) : std::optional(); } struct SupportElementInfluenceAreas { @@ -1936,6 +1948,10 @@ static void increase_areas_one_layer( " Distance to top: " << parent.state.distance_to_top << " Elephant foot increases " << parent.state.elephant_foot_increases << " use_min_xy_dist " << parent.state.use_min_xy_dist << " to buildplate " << parent.state.to_buildplate << " gracious " << parent.state.to_model_gracious << " safe " << parent.state.can_use_safe_radius << " until move " << parent.state.dont_move_until; tree_supports_show_error("Potentially lost branch!"sv, true); +#ifdef TREE_SUPPORTS_TRACK_LOST + if (result) + result->lost = true; +#endif // TREE_SUPPORTS_TRACK_LOST } else result = increase_single_area(volumes, config, settings, layer_idx, parent, settings.increase_speed == slow_speed ? offset_slow : offset_fast, to_bp_data, to_model_data, inc_wo_collision, 0, mergelayer); @@ -1990,7 +2006,11 @@ static void increase_areas_one_layer( // A point can be set on the top most tip layer (maybe more if it should not move for a few layers). parent.state.result_on_layer_reset(); parent.state.to_model_gracious = false; +#ifdef TREE_SUPPORTS_TRACK_LOST + parent.state.verylost = true; +#endif // TREE_SUPPORTS_TRACK_LOST } + throw_on_cancel(); } }, tbb::simple_partitioner()); @@ -2126,19 +2146,33 @@ static bool merge_influence_areas_two_elements( 2 * (config.xy_distance + smaller_collision_radius - 3), 0, 0), bigger); }; - Polygons intersect = intersect_small_with_bigger( +//#define TREES_MERGE_RATHER_LATER + Polygons intersect = +#ifdef TREES_MERGE_RATHER_LATER + intersection( +#else + intersect_small_with_bigger( +#endif merging_to_bp ? smaller_rad.areas.to_bp_areas : smaller_rad.areas.to_model_areas, merging_to_bp ? bigger_rad.areas.to_bp_areas : bigger_rad.areas.to_model_areas); + const auto _tiny_area_threshold = tiny_area_threshold(); // dont use empty as a line is not empty, but for this use-case it very well may be (and would be one layer down as union does not keep lines) // check if the overlap is large enough (Small ares tend to attract rounding errors in clipper). - if (area(intersect) <= tiny_area_threshold) + if (area(intersect) <= _tiny_area_threshold) return false; // While 0.025 was guessed as enough, i did not have reason to change it. - if (area(offset(intersect, scaled(-0.025), jtMiter, 1.2)) <= tiny_area_threshold) + if (area(offset(intersect, scaled(-0.025), jtMiter, 1.2)) <= _tiny_area_threshold) return false; +#ifdef TREES_MERGE_RATHER_LATER + intersect = + intersect_small_with_bigger( + merging_to_bp ? smaller_rad.areas.to_bp_areas : smaller_rad.areas.to_model_areas, + merging_to_bp ? bigger_rad.areas.to_bp_areas : bigger_rad.areas.to_model_areas); +#endif + // Do the actual merge now that the branches are confirmed to be able to intersect. // calculate which point is closest to the point of the last merge (or tip center if no merge above it has happened) // used at the end to estimate where to best place the branch on the bottom most layer @@ -2381,7 +2415,7 @@ static void merge_influence_areas( * * \param move_bounds[in,out] All currently existing influence areas */ -void create_layer_pathing(const TreeModelVolumes &volumes, const TreeSupportSettings &config, std::vector &move_bounds, std::function throw_on_cancel) +static void create_layer_pathing(const TreeModelVolumes &volumes, const TreeSupportSettings &config, std::vector &move_bounds, std::function throw_on_cancel) { #ifdef SLIC3R_TREESUPPORTS_PROGRESS const double data_size_inverse = 1 / double(move_bounds.size()); @@ -2393,6 +2427,7 @@ void create_layer_pathing(const TreeModelVolumes &volumes, const TreeSupportSett LayerIndex last_merge_layer_idx = move_bounds.size(); bool new_element = false; + const auto _tiny_area_threshold = tiny_area_threshold(); // Ensures at least one merge operation per 3mm height, 50 layers, 1 mm movement of slow speed or 5mm movement of fast speed (whatever is lowest). Values were guessed. size_t max_merge_every_x_layers = std::min(std::min(5000 / (std::max(config.maximum_move_distance, coord_t(100))), 1000 / std::max(config.maximum_move_distance_slow, coord_t(20))), 3000 / config.layer_height); @@ -2423,12 +2458,12 @@ void create_layer_pathing(const TreeModelVolumes &volumes, const TreeSupportSett // Place already fully constructed elements to the output, remove them from influence_areas. SupportElements &this_layer = move_bounds[layer_idx - 1]; influence_areas.erase(std::remove_if(influence_areas.begin(), influence_areas.end(), - [&this_layer, layer_idx](SupportElementMerging &elem) { + [&this_layer, &_tiny_area_threshold, layer_idx](SupportElementMerging &elem) { if (elem.areas.influence_areas.empty()) // This area was removed completely due to collisions. return true; if (elem.areas.to_bp_areas.empty() && elem.areas.to_model_areas.empty()) { - if (area(elem.areas.influence_areas) < tiny_area_threshold) { + if (area(elem.areas.influence_areas) < _tiny_area_threshold) { BOOST_LOG_TRIVIAL(error) << "Insert Error of Influence area bypass on layer " << layer_idx - 1; tree_supports_show_error("Insert error of area after bypassing merge.\n"sv, true); } @@ -2461,7 +2496,7 @@ void create_layer_pathing(const TreeModelVolumes &volumes, const TreeSupportSett for (SupportElementMerging &elem : influence_areas) if (! elem.areas.influence_areas.empty()) { Polygons new_area = safe_union(elem.areas.influence_areas); - if (area(new_area) < tiny_area_threshold) { + if (area(new_area) < _tiny_area_threshold) { BOOST_LOG_TRIVIAL(error) << "Insert Error of Influence area on layer " << layer_idx - 1 << ". Origin of " << elem.parents.size() << " areas. Was to bp " << elem.state.to_buildplate; tree_supports_show_error("Insert error of area after merge.\n"sv, true); } @@ -2623,7 +2658,7 @@ static void remove_deleted_elements(std::vector &move_bounds) * * \param move_bounds[in,out] All currently existing influence areas */ -void create_nodes_from_area( +static void create_nodes_from_area( const TreeModelVolumes &volumes, const TreeSupportSettings &config, std::vector &move_bounds, @@ -2634,8 +2669,8 @@ void create_nodes_from_area( { SupportElements *layer_above = move_bounds.size() > 1 ? &move_bounds[1] : nullptr; if (layer_above) { - for (SupportElement &elem : *layer_above) - elem.state.marked = false; + for (SupportElement &elem : *layer_above) + elem.state.marked = false; } for (SupportElement &init : move_bounds.front()) { init.state.result_on_layer = move_inside_if_outside(init.influence_area, init.state.next_position); @@ -2706,7 +2741,7 @@ void create_nodes_from_area( assert(radius_increase >= 0); double shift = (elem.state.result_on_layer - parent.state.result_on_layer).cast().norm(); //FIXME this assert fails a lot. Is it correct? - //assert(shift < radius_increase + 2. * config.maximum_move_distance_slow); +// assert(shift < radius_increase + 2. * config.maximum_move_distance_slow); } } } @@ -2731,7 +2766,7 @@ void create_nodes_from_area( assert(radius_increase >= 0); double shift = (elem.state.result_on_layer - parent.state.result_on_layer).cast().norm(); //FIXME this assert fails a lot. Is it correct? - //assert(shift < radius_increase + 2. * config.maximum_move_distance_slow); +// assert(shift < radius_increase + 2. * config.maximum_move_distance_slow); } } } @@ -3026,6 +3061,7 @@ static void drop_non_gracious_areas( std::vector &support_layer_storage, std::function throw_on_cancel) { + const auto _tiny_area_threshold = tiny_area_threshold(); std::vector>> dropped_down_areas(linear_data.size()); tbb::parallel_for(tbb::blocked_range(0, linear_data.size()), [&](const tbb::blocked_range &range) { @@ -3034,7 +3070,7 @@ static void drop_non_gracious_areas( if (const DrawArea &draw_element = linear_data[idx]; ! draw_element.element->state.to_model_gracious && draw_element.child_element == nullptr) { Polygons rest_support; const LayerIndex layer_idx_first = draw_element.element->state.layer_idx - 1; - for (LayerIndex layer_idx = layer_idx_first; area(rest_support) > tiny_area_threshold && layer_idx >= 0; -- layer_idx) { + for (LayerIndex layer_idx = layer_idx_first; area(rest_support) > _tiny_area_threshold && layer_idx >= 0; -- layer_idx) { rest_support = diff_clipped(layer_idx == layer_idx_first ? draw_element.polygons : rest_support, volumes.getCollision(0, layer_idx, false)); dropped_down_areas[idx].emplace_back(layer_idx, rest_support); } @@ -3071,8 +3107,10 @@ static void finalize_interface_and_support_areas( std::function throw_on_cancel) { assert(std::all_of(bottom_contacts.begin(), bottom_contacts.end(), [](auto *p) { return p == nullptr; })); +// assert(std::all_of(top_contacts.begin(), top_contacts.end(), [](auto* p) { return p == nullptr; })); assert(std::all_of(intermediate_layers.begin(), intermediate_layers.end(), [](auto* p) { return p == nullptr; })); - InterfacePreference interface_pref = config.interface_preference; // InterfacePreference::SupportLinesOverwriteInterface; + + InterfacePreference interface_pref = config.interface_preference; // InterfacePreference::InterfaceAreaOverwritesSupport; #ifdef SLIC3R_TREESUPPORTS_PROGRESS double progress_total = TREE_PROGRESS_PRECALC_AVO + TREE_PROGRESS_PRECALC_COLL + TREE_PROGRESS_GENERATE_NODES + TREE_PROGRESS_AREA_CALC + TREE_PROGRESS_GENERATE_BRANCH_AREAS + TREE_PROGRESS_SMOOTH_BRANCH_AREAS; @@ -3083,8 +3121,9 @@ static void finalize_interface_and_support_areas( [&](const tbb::blocked_range &range) { for (size_t layer_idx = range.begin(); layer_idx < range.end(); ++ layer_idx) { // Subtract support lines of the branches from the roof - SupportGeneratorLayer* support_roof = layer_idx < top_contacts.size() ? top_contacts[layer_idx] : nullptr; + SupportGeneratorLayer *support_roof = top_contacts[layer_idx]; Polygons support_roof_polygons; + if (Polygons &src = support_roof_storage[layer_idx]; ! src.empty()) { if (support_roof != nullptr && ! support_roof->polygons.empty()) { support_roof_polygons = union_(src, support_roof->polygons); @@ -3096,7 +3135,7 @@ static void finalize_interface_and_support_areas( support_roof->polygons.clear(); } - //assert(intermediate_layers[layer_idx] == nullptr); + assert(intermediate_layers[layer_idx] == nullptr); Polygons base_layer_polygons = std::move(support_layer_storage[layer_idx]); if (! base_layer_polygons.empty()) { @@ -3104,8 +3143,9 @@ static void finalize_interface_and_support_areas( base_layer_polygons = smooth_outward(union_(base_layer_polygons), config.support_line_width); //FIXME was .smooth(50); //smooth_outward(closing(std::move(bottom), closing_distance + minimum_island_radius, closing_distance, SUPPORT_SURFACES_OFFSET_PARAMETERS), smoothing_distance) : // simplify a bit, to ensure the output does not contain outrageous amounts of vertices. Should not be necessary, just a precaution. - base_layer_polygons = polygons_simplify(base_layer_polygons, std::min(scaled(0.03), double(config.resolution))); + base_layer_polygons = polygons_simplify(base_layer_polygons, std::min(scaled(0.03), double(config.resolution)), polygons_strictly_simple); } + if (! support_roof_polygons.empty() && ! base_layer_polygons.empty()) { // if (area(intersection(base_layer_polygons, support_roof_polygons)) > tiny_area_threshold) { @@ -3169,7 +3209,7 @@ static void finalize_interface_and_support_areas( } if (! floor_layer.empty()) { if (support_bottom == nullptr) - support_bottom = &layer_allocate(layer_storage, SupporLayerType::sltBottomContact, print_object.slicing_parameters(), config, layer_idx); + support_bottom = &layer_allocate(layer_storage, SupporLayerType::BottomContact, print_object.slicing_parameters(), config, layer_idx); support_bottom->polygons = union_(floor_layer, support_bottom->polygons); base_layer_polygons = diff_clipped(base_layer_polygons, offset(support_bottom->polygons, scaled(0.01), jtMiter, 1.2)); // Subtract the support floor from the normal support. } @@ -3177,11 +3217,11 @@ static void finalize_interface_and_support_areas( if (! support_roof_polygons.empty()) { if (support_roof == nullptr) - support_roof = top_contacts[layer_idx] = &layer_allocate(layer_storage, SupporLayerType::sltTopContact, print_object.slicing_parameters(), config, layer_idx); + support_roof = top_contacts[layer_idx] = &layer_allocate(layer_storage, SupporLayerType::TopContact, print_object.slicing_parameters(), config, layer_idx); support_roof->polygons = union_(support_roof_polygons); } if (! base_layer_polygons.empty()) { - SupportGeneratorLayer *base_layer = intermediate_layers[layer_idx] = &layer_allocate(layer_storage, SupporLayerType::sltBase, print_object.slicing_parameters(), config, layer_idx); + SupportGeneratorLayer *base_layer = intermediate_layers[layer_idx] = &layer_allocate(layer_storage, SupporLayerType::Base, print_object.slicing_parameters(), config, layer_idx); base_layer->polygons = union_(base_layer_polygons); } @@ -3361,28 +3401,6 @@ static void draw_areas( "finalize_interface_and_support_areas " << dur_finalize << " ms"; } -#if 1 -// Test whether two circles, each on its own plane in 3D intersect. -// Circles are considered intersecting, if the lowest point on one circle is below the other circle's plane. -// Assumption: The two planes are oriented the same way. -static bool circles_intersect( - const Vec3d &p1, const Vec3d &n1, const double r1, - const Vec3d &p2, const Vec3d &n2, const double r2) -{ - assert(n1.dot(n2) >= 0); - - const Vec3d z = n1.cross(n2); - const Vec3d dir1 = z.cross(n1); - const Vec3d lowest_point1 = p1 + dir1 * (r1 / dir1.norm()); - assert(n2.dot(p1) >= n2.dot(lowest_point1)); - if (n2.dot(lowest_point1) <= 0) - return true; - const Vec3d dir2 = z.cross(n2); - const Vec3d lowest_point2 = p2 + dir2 * (r2 / dir2.norm()); - assert(n1.dot(p2) >= n1.dot(lowest_point2)); - return n1.dot(lowest_point2) <= 0; -} - template void triangulate_fan(indexed_triangle_set &its, int ifan, int ibegin, int iend) { @@ -3491,28 +3509,13 @@ static std::pair discretize_circle(const Vec3f ¢er, const Vec3f &n return { begin, int(pts.size()) }; } -// Discretize polygon, append to output vector, return ranges of indices of the points added. -static std::pair discretize_polygon(const Vec3f& center, const Polygons& polys, std::vector& pts) -{ - const Polygon& poly = polys.front(); - size_t nsteps = poly.size(); - // Discretize the circle. - int begin = int(pts.size()); - pts.reserve(pts.size() + nsteps); - for (int i = 0; i < nsteps; ++i) { - Vec3f pt(poly.points[i].x(), poly.points[i].y(), center.z()); - pts.emplace_back(pt); - } - return { begin, int(pts.size()) }; -} - // Returns Z span of the generated mesh. static std::pair extrude_branch( - const std::vector&path, - const TreeSupportSettings &config, - const SlicingParameters &slicing_params, - const std::vector &move_bounds, - indexed_triangle_set &result) + const std::vector &path, + const TreeSupportSettings &config, + const SlicingParameters &slicing_params, + const std::vector &move_bounds, + indexed_triangle_set &result) { Vec3d p1, p2, p3; Vec3d v1, v2; @@ -3521,6 +3524,10 @@ static std::pair extrude_branch( assert(path.size() >= 2); static constexpr const float eps = 0.015f; std::pair prev_strip; + +// char fname[2048]; +// static int irun = 0; + float zmin = 0; float zmax = 0; @@ -3542,23 +3549,15 @@ static std::pair extrude_branch( result.vertices.emplace_back((p1 - nprev * radius).cast()); zmin = result.vertices.back().z(); float angle = angle_step; - std::pair strip; - if (current.state.type == TreeSupport::NodeType::ePolygon) { - strip = discretize_polygon(p1.cast(), current.influence_area, result.vertices); + for (int i = 1; i < nsteps; ++ i, angle += angle_step) { + std::pair strip = discretize_circle((p1 - nprev * radius * cos(angle)).cast(), nprev.cast(), radius * sin(angle), eps, result.vertices); + if (i == 1) + triangulate_fan(result, ifan, strip.first, strip.second); + else + triangulate_strip(result, prev_strip.first, prev_strip.second, strip.first, strip.second); +// sprintf(fname, "d:\\temp\\meshes\\tree-partial-%d.obj", ++ irun); +// its_write_obj(result, fname); prev_strip = strip; - strip = discretize_polygon(p2.cast(), current.influence_area, result.vertices); - } - else { - for (int i = 1; i < nsteps; ++i, angle += angle_step) { - strip = discretize_circle((p1 - nprev * radius * cos(angle)).cast(), nprev.cast(), radius * sin(angle), eps, result.vertices); - if (i == 1) - triangulate_fan(result, ifan, strip.first, strip.second); - else - triangulate_strip(result, prev_strip.first, prev_strip.second, strip.first, strip.second); - // sprintf(fname, "d:\\temp\\meshes\\tree-partial-%d.obj", ++ irun); - // its_write_obj(result, fname); - prev_strip = strip; - } } } if (ipath + 1 == path.size()) { @@ -3570,25 +3569,19 @@ static std::pair extrude_branch( auto nsteps = int(ceil(M_PI / (2. * angle_step))); angle_step = M_PI / (2. * nsteps); auto angle = float(M_PI / 2.); - std::pair strip; - if (current.state.type == TreeSupport::NodeType::ePolygon) { - strip = discretize_polygon(p2.cast(), current.influence_area, result.vertices); - } - else { - for (int i = 0; i < nsteps; ++i, angle -= angle_step) { - strip = discretize_circle((p2 + ncurrent * radius * cos(angle)).cast(), ncurrent.cast(), radius * sin(angle), eps, result.vertices); - triangulate_strip(result, prev_strip.first, prev_strip.second, strip.first, strip.second); - // sprintf(fname, "d:\\temp\\meshes\\tree-partial-%d.obj", ++ irun); - // its_write_obj(result, fname); - prev_strip = strip; - } - int ifan = int(result.vertices.size()); - result.vertices.emplace_back((p2 + ncurrent * radius).cast()); - zmax = result.vertices.back().z(); - triangulate_fan(result, ifan, prev_strip.first, prev_strip.second); - // sprintf(fname, "d:\\temp\\meshes\\tree-partial-%d.obj", ++ irun); - // its_write_obj(result, fname); + for (int i = 0; i < nsteps; ++ i, angle -= angle_step) { + std::pair strip = discretize_circle((p2 + ncurrent * radius * cos(angle)).cast(), ncurrent.cast(), radius * sin(angle), eps, result.vertices); + triangulate_strip(result, prev_strip.first, prev_strip.second, strip.first, strip.second); +// sprintf(fname, "d:\\temp\\meshes\\tree-partial-%d.obj", ++ irun); +// its_write_obj(result, fname); + prev_strip = strip; } + int ifan = int(result.vertices.size()); + result.vertices.emplace_back((p2 + ncurrent * radius).cast()); + zmax = result.vertices.back().z(); + triangulate_fan(result, ifan, prev_strip.first, prev_strip.second); +// sprintf(fname, "d:\\temp\\meshes\\tree-partial-%d.obj", ++ irun); +// its_write_obj(result, fname); } else { const SupportElement &next = *path[ipath + 1]; assert(current.state.layer_idx + 1 == next.state.layer_idx); @@ -3596,13 +3589,7 @@ static std::pair extrude_branch( v2 = (p3 - p2).normalized(); ncurrent = (v1 + v2).normalized(); float radius = unscaled(support_element_radius(config, current)); - std::pair strip; - if (current.state.type == TreeSupport::NodeType::ePolygon) { - strip = discretize_polygon(p2.cast(), current.influence_area, result.vertices); - } - else { - strip = discretize_circle(p2.cast(), ncurrent.cast(), radius, eps, result.vertices); - } + std::pair strip = discretize_circle(p2.cast(), ncurrent.cast(), radius, eps, result.vertices); triangulate_strip(result, prev_strip.first, prev_strip.second, strip.first, strip.second); prev_strip = strip; // sprintf(fname, "d:\\temp\\meshes\\tree-partial-%d.obj", ++irun); @@ -3622,14 +3609,16 @@ static std::pair extrude_branch( return std::make_pair(zmin, zmax); } -#endif + #ifdef TREE_SUPPORT_ORGANIC_NUDGE_NEW + // New version using per layer AABB trees of lines for nudging spheres away from an object. -void organic_smooth_branches_avoid_collisions( +static void organic_smooth_branches_avoid_collisions( const PrintObject &print_object, const TreeModelVolumes &volumes, const TreeSupportSettings &config, + std::vector &move_bounds, const std::vector> &elements_with_link_down, const std::vector &linear_data_layers, std::function throw_on_cancel) @@ -3649,7 +3638,7 @@ void organic_smooth_branches_avoid_collisions( LayerIndex layer_idx = element.first->state.layer_idx; if (size_t num_layers = layer_idx + 1; num_layers > layer_collision_cache.size()) { if (num_layers > layer_collision_cache.capacity()) - layer_collision_cache.reserve(next_highest_power_of_2(num_layers)); + reserve_power_of_2(layer_collision_cache, num_layers); layer_collision_cache.resize(num_layers, {}); } auto& l = layer_collision_cache[layer_idx]; @@ -3763,8 +3752,6 @@ void organic_smooth_branches_avoid_collisions( // Calculate collision of multiple 2D layers against a collision sphere. collision_sphere.last_collision_depth = - std::numeric_limits::max(); for (uint32_t layer_id = collision_sphere.layer_begin; layer_id != collision_sphere.layer_end; ++ layer_id) { - if(layer_id>= layer_collision_cache.size()) - continue; double dz = (layer_id - collision_sphere.element.state.layer_idx) * slicing_params.layer_height; if (double r2 = sqr(collision_sphere.radius) - sqr(dz); r2 > 0) { if (const LayerCollisionCache &layer_collision_cache_item = layer_collision_cache[layer_id]; ! layer_collision_cache_item.empty()) { @@ -3795,6 +3782,7 @@ void organic_smooth_branches_avoid_collisions( } // Laplacian smoothing Vec2d avg{ 0, 0 }; + //const SupportElements &above = move_bounds[collision_sphere.element.state.layer_idx + 1]; const size_t offset_above = linear_data_layers[collision_sphere.element.state.layer_idx + 1]; double weight = 0.; for (auto iparent : collision_sphere.element.parents) { @@ -3804,7 +3792,7 @@ void organic_smooth_branches_avoid_collisions( } if (collision_sphere.element_below_id != -1) { const size_t offset_below = linear_data_layers[collision_sphere.element.state.layer_idx - 1]; - const double w = weight; + const double w = weight; // support_element_radius(config, move_bounds[element.state.layer_idx - 1][below]); avg += w * to_2d(collision_spheres[offset_below + collision_sphere.element_below_id].prev_position.cast()); weight += w; } @@ -3909,7 +3897,7 @@ static void organic_smooth_branches_avoid_collisions( size_t cnt = element.parents.size(); if (below != -1) { const size_t offset_below = linear_data_layers[element.state.layer_idx - 1]; - const double w = weight; // config.getRadius(move_bounds[element.state.layer_idx - 1][below].state); + const double w = weight; // support_element_radius(config, move_bounds[element.state.layer_idx - 1][below]); avg.x() += w * prev[offset_below + below].x(); avg.y() += w * prev[offset_below + below].y(); ++ cnt; @@ -3941,189 +3929,6 @@ static void organic_smooth_branches_avoid_collisions( } #endif // TREE_SUPPORT_ORGANIC_NUDGE_NEW -// Organic specific: Smooth branches and produce one cummulative mesh to be sliced. -indexed_triangle_set draw_branches( - PrintObject &print_object, - const TreeModelVolumes &volumes, - const TreeSupportSettings &config, - std::vector &move_bounds, - std::function throw_on_cancel) -{ - static int irun = 0; - - // All SupportElements are put into a layer independent storage to improve parallelization. - std::vector> elements_with_link_down; - std::vector linear_data_layers; - { - std::vector> map_downwards_old; - std::vector> map_downwards_new; - linear_data_layers.emplace_back(0); - for (LayerIndex layer_idx = 0; layer_idx < LayerIndex(move_bounds.size()); ++ layer_idx) { - SupportElements *layer_above = layer_idx + 1 < move_bounds.size() ? &move_bounds[layer_idx + 1] : nullptr; - map_downwards_new.clear(); - std::sort(map_downwards_old.begin(), map_downwards_old.end(), [](auto& l, auto& r) { return l.first < r.first; }); - SupportElements &layer = move_bounds[layer_idx]; - for (size_t elem_idx = 0; elem_idx < layer.size(); ++ elem_idx) { - SupportElement &elem = layer[elem_idx]; - int child = -1; - if (layer_idx > 0) { - auto it = std::lower_bound(map_downwards_old.begin(), map_downwards_old.end(), &elem, [](auto& l, const SupportElement* r) { return l.first < r; }); - if (it != map_downwards_old.end() && it->first == &elem) { - child = it->second; - // Only one link points to a node above from below. - assert(!(++it != map_downwards_old.end() && it->first == &elem)); - } - const SupportElement *pchild = child == -1 ? nullptr : &move_bounds[layer_idx - 1][child]; - assert(pchild ? pchild->state.result_on_layer_is_set() : elem.state.target_height > layer_idx); - } - for (int32_t parent_idx : elem.parents) { - SupportElement &parent = (*layer_above)[parent_idx]; - if (parent.state.result_on_layer_is_set()) - map_downwards_new.emplace_back(&parent, elem_idx); - } - - elements_with_link_down.push_back({ &elem, int(child) }); - } - std::swap(map_downwards_old, map_downwards_new); - linear_data_layers.emplace_back(elements_with_link_down.size()); - } - } - - throw_on_cancel(); - - organic_smooth_branches_avoid_collisions(print_object, volumes, config, elements_with_link_down, linear_data_layers, throw_on_cancel); - - // Unmark all nodes. - for (SupportElements &elements : move_bounds) - for (SupportElement &element : elements) - element.state.marked = false; - - // Traverse all nodes, generate tubes. - // Traversal stack with nodes and thier current parent - const SlicingParameters &slicing_params = print_object.slicing_parameters(); - std::vector path; - indexed_triangle_set cummulative_mesh; - indexed_triangle_set partial_mesh; - indexed_triangle_set temp_mesh; - for (LayerIndex layer_idx = 0; layer_idx + 1 < LayerIndex(move_bounds.size()); ++ layer_idx) { - SupportElements &layer = move_bounds[layer_idx]; - SupportElements &layer_above = move_bounds[layer_idx + 1]; - - for (SupportElement &start_element : layer) - if (! start_element.state.marked && ! start_element.parents.empty()) { - // Collect elements up to a bifurcation above. - start_element.state.marked = true; - for (size_t parent_idx = 0; parent_idx < start_element.parents.size(); ++ parent_idx) { - path.clear(); - path.emplace_back(&start_element); - // Traverse each branch until it branches again. - SupportElement &first_parent = layer_above[start_element.parents[parent_idx]]; - assert(path.back()->state.layer_idx + 1 == first_parent.state.layer_idx); - path.emplace_back(&first_parent); - if (first_parent.parents.size() < 2) - first_parent.state.marked = true; - if (first_parent.parents.size() == 1) { - for (SupportElement *parent = &first_parent;;) { - SupportElement &next_parent = move_bounds[parent->state.layer_idx + 1][parent->parents.front()]; - assert(path.back()->state.layer_idx + 1 == next_parent.state.layer_idx); - path.emplace_back(&next_parent); - if (next_parent.parents.size() > 1) - break; - next_parent.state.marked = true; - if (next_parent.parents.size() == 0) - break; - parent = &next_parent; - } - } - // Triangulate the tube. - partial_mesh.clear(); - extrude_branch(path, config, slicing_params, move_bounds, partial_mesh); -#if 1 - char fname[2048]; - sprintf(fname, "SVG\\tree-raw-%d.obj", ++ irun); - its_write_obj(partial_mesh, fname); -#if 0 - temp_mesh.clear(); - cut_mesh(partial_mesh, layer_z(slicing_params, path.back()->state.layer_idx) + EPSILON, nullptr, &temp_mesh, false); - sprintf(fname, "d:\\temp\\meshes\\tree-trimmed1-%d.obj", irun); - its_write_obj(temp_mesh, fname); - partial_mesh.clear(); - cut_mesh(temp_mesh, layer_z(slicing_params, path.front()->state.layer_idx) - EPSILON, &partial_mesh, nullptr, false); - sprintf(fname, "d:\\temp\\meshes\\tree-trimmed2-%d.obj", irun); - its_write_obj(partial_mesh, fname); -#endif -#endif - its_merge(cummulative_mesh, partial_mesh); - } - throw_on_cancel(); - } - } - return cummulative_mesh; -} - -// Organic specific: Slice the cummulative mesh produced by draw_branches(). -void slice_branches( - PrintObject &print_object, - const TreeModelVolumes &volumes, - const TreeSupportSettings &config, - const std::vector &overhangs, - std::vector &move_bounds, - const indexed_triangle_set &cummulative_mesh, - - SupportGeneratorLayersPtr &bottom_contacts, - SupportGeneratorLayersPtr &top_contacts, - SupportGeneratorLayersPtr &intermediate_layers, - SupportGeneratorLayerStorage &layer_storage, - - std::function throw_on_cancel) -{ - const SlicingParameters &slicing_params = print_object.slicing_parameters(); - std::vector slice_z; - for (size_t layer_idx = 0; layer_idx < move_bounds.size(); ++ layer_idx) { - double print_z = slicing_params.object_print_z_min + slicing_params.first_object_layer_height + layer_idx * slicing_params.layer_height; - double layer_height = layer_idx == 0 ? slicing_params.first_object_layer_height : slicing_params.layer_height; - slice_z.emplace_back(float(print_z - layer_height * 0.5)); - } - // Remove the trailing slices. - while (! slice_z.empty()) - if (move_bounds[slice_z.size() - 1].empty()) - slice_z.pop_back(); - else - break; - -#if 0 - its_write_obj(cummulative_mesh, "d:\\temp\\meshes\\tree.obj"); -#endif - - MeshSlicingParamsEx params; - params.closing_radius = float(print_object.config().slice_closing_radius.value); - params.mode = MeshSlicingParams::SlicingMode::Positive; - std::vector slices = slice_mesh_ex(cummulative_mesh, slice_z, params, throw_on_cancel); - for (size_t layer_idx = 0; layer_idx < slice_z.size(); ++ layer_idx) - if (! slices[layer_idx].empty()) { - SupportGeneratorLayer *&l = intermediate_layers[layer_idx]; - if (l == nullptr) - l = &layer_allocate(layer_storage, SupporLayerType::sltBase, slicing_params, layer_idx); - append(l->polygons, to_polygons(std::move(slices[layer_idx]))); - } - - // Trim the slices. - tbb::parallel_for(tbb::blocked_range(0, intermediate_layers.size()), - [&](const tbb::blocked_range &range) { - for (size_t layer_idx = range.begin(); layer_idx < range.end(); ++ layer_idx) - if (SupportGeneratorLayer *layer = intermediate_layers[layer_idx]; layer) { - Polygons &poly = intermediate_layers[layer_idx]->polygons; - poly = diff_clipped(poly, volumes.getCollision(0, layer_idx, true)); - poly = intersection(poly, volumes.m_bed_area); - } - }); - - std::vector support_layer_storage(move_bounds.size()); - std::vector support_roof_storage(move_bounds.size()); - finalize_interface_and_support_areas(print_object, volumes, config, overhangs, support_layer_storage, support_roof_storage, - bottom_contacts, top_contacts, intermediate_layers, layer_storage, throw_on_cancel); -} - /*! * \brief Create the areas that need support. * @@ -4131,7 +3936,7 @@ void slice_branches( * \param storage The data storage where the mesh data is gotten from and * where the resulting support areas are stored. */ -static void generate_support_areas(Print &print, TreeSupport* tree_support, const BuildVolume &build_volume, const std::vector &print_object_ids, std::function throw_on_cancel) +static void generate_support_areas(Print &print, const BuildVolume &build_volume, const std::vector &print_object_ids, std::function throw_on_cancel) { // Settings with the indexes of meshes that use these settings. std::vector>> grouped_meshes = group_meshes(print, print_object_ids); @@ -4147,10 +3952,24 @@ static void generate_support_areas(Print &print, TreeSupport* tree_support, cons // this struct is used to easy retrieve setting. No other function except those in TreeModelVolumes and generate_initial_areas() have knowledge of the existence of multiple meshes being processed. //FIXME this is a copy // Contains config settings to avoid loading them in every function. This was done to improve readability of the code. - TreeSupportSettings &config = processing.first; + const TreeSupportSettings &config = processing.first; BOOST_LOG_TRIVIAL(info) << "Processing support tree mesh group " << counter + 1 << " of " << grouped_meshes.size() << " containing " << grouped_meshes[counter].second.size() << " meshes."; auto t_start = std::chrono::high_resolution_clock::now(); - +#if 0 + std::vector exclude(num_support_layers); + // get all already existing support areas and exclude them + tbb::parallel_for(tbb::blocked_range(0, num_support_layers), + [&](const tbb::blocked_range &range) { + for (size_t layer_idx = range.begin(); layer_idx < range.end(); ++ layer_idx) { + Polygons exlude_at_layer; + append(exlude_at_layer, storage.support.supportLayers[layer_idx].support_bottom); + append(exlude_at_layer, storage.support.supportLayers[layer_idx].support_roof); + for (auto part : storage.support.supportLayers[layer_idx].support_infill_parts) + append(exlude_at_layer, part.outline); + exclude[layer_idx] = union_(exlude_at_layer); + } + }); +#endif #ifdef SLIC3R_TREESUPPORTS_PROGRESS m_progress_multiplier = 1.0 / double(grouped_meshes.size()); m_progress_offset = counter == 0 ? 0 : TREE_PROGRESS_TOTAL * (double(counter) * m_progress_multiplier); @@ -4163,41 +3982,21 @@ static void generate_support_areas(Print &print, TreeSupport* tree_support, cons #endif // SLIC3R_TREESUPPORTS_PROGRESS /* additional_excluded_areas */{} }; - //FIXME generating overhangs just for the first mesh of the group. + //FIXME generating overhangs just for the furst mesh of the group. assert(processing.second.size() == 1); - - print.set_status(55, _L("Support: detect overhangs")); -#if 0 - std::vector overhangs; - tree_support->detect_overhangs(); - const int num_raft_layers = int(config.raft_layers.size()); - const int num_layers = int(print_object.layer_count()) + num_raft_layers; - overhangs.resize(num_layers); - for (size_t i = 0; i < print_object.layer_count(); i++) - { - overhangs[i + num_raft_layers] = to_polygons(print_object.get_support_layer(i)->overhang_areas); - } - print_object.clear_support_layers(); -#else std::vector overhangs = generate_overhangs(config, *print.get_object(processing.second.front()), throw_on_cancel); -#endif + // ### Precalculate avoidances, collision etc. size_t num_support_layers = precalculate(print, overhangs, processing.first, processing.second, volumes, throw_on_cancel); - bool has_support = num_support_layers > 0; - bool has_raft = config.raft_layers.size() > 0; + bool has_raft = config.raft_layers.size() > 0; num_support_layers = std::max(num_support_layers, config.raft_layers.size()); - if (num_support_layers == 0) - continue; - SupportParameters support_params(print_object); support_params.with_sheath = true; - // organic support default pattern is none. - if (config.support_pattern == smpDefault) { - config.support_pattern = smpNone; - support_params.support_density = 0; - } +// Don't override the support density of tree supports, as the support density is used for raft. +// The trees will have the density zeroed in tree_supports_generate_paths() +// support_params.support_density = 0; SupportGeneratorLayerStorage layer_storage; SupportGeneratorLayersPtr top_contacts; @@ -4217,36 +4016,32 @@ static void generate_support_areas(Print &print, TreeSupport* tree_support, cons auto remove_undefined_layers = [&bottom_contacts, &top_contacts, &interface_layers, &base_interface_layers, &intermediate_layers]() { auto doit = [](SupportGeneratorLayersPtr& layers) { layers.erase(std::remove_if(layers.begin(), layers.end(), [](const SupportGeneratorLayer* ptr) { return ptr == nullptr; }), layers.end()); - }; + }; doit(bottom_contacts); doit(top_contacts); doit(interface_layers); doit(base_interface_layers); doit(intermediate_layers); - }; + }; InterfacePlacer interface_placer{ print_object.slicing_parameters(), support_params, config, // Outputs layer_storage, top_contacts, interface_layers, base_interface_layers }; - if (has_support) { auto t_precalc = std::chrono::high_resolution_clock::now(); + // value is the area where support may be placed. As this is calculated in CreateLayerPathing it is saved and reused in draw_areas std::vector move_bounds(num_support_layers); - + // ### Place tips of the support tree for (size_t mesh_idx : processing.second) - generate_initial_areas(*print.get_object(mesh_idx), volumes, config, overhangs, + generate_initial_areas(*print.get_object(mesh_idx), volumes, config, overhangs, move_bounds, interface_placer, throw_on_cancel); auto t_gen = std::chrono::high_resolution_clock::now(); - // save num of points to log - for (size_t i = 0; i < move_bounds.size(); i++) - BOOST_LOG_TRIVIAL(info) << "Number of points in move_bound: " << move_bounds[i].size() << " in layer " << i; - -#ifdef TREESUPPORT_DEBUG_SVG + #ifdef TREESUPPORT_DEBUG_SVG for (size_t layer_idx = 0; layer_idx < move_bounds.size(); ++layer_idx) { Polygons polys; for (auto& area : move_bounds[layer_idx]) @@ -4254,13 +4049,12 @@ static void generate_support_areas(Print &print, TreeSupport* tree_support, cons if (auto begin = move_bounds[layer_idx].begin(); begin != move_bounds[layer_idx].end()) SVG::export_expolygons(debug_out_path("treesupport-initial_areas-%d.svg", layer_idx), { { { union_ex(volumes.getWallRestriction(support_element_collision_radius(config, begin->state), layer_idx, begin->state.use_min_xy_dist)) }, - { "wall_restricrictions", "gray", 0.5f } }, - { { union_ex(polys) }, { "parent", "red", "black", "", scaled(0.1f), 0.5f } } }); + { "wall_restricrictions", "gray", 0.5f } }, + { { union_ex(polys) }, { "parent", "red", "black", "", scaled(0.1f), 0.5f } } }); } -#endif // TREESUPPORT_DEBUG_SVG + #endif // TREESUPPORT_DEBUG_SVG // ### Propagate the influence areas downwards. This is an inherently serial operation. - print.set_status(60, _L("Support: propagate branches")); create_layer_pathing(volumes, config, move_bounds, throw_on_cancel); auto t_path = std::chrono::high_resolution_clock::now(); @@ -4269,18 +4063,18 @@ static void generate_support_areas(Print &print, TreeSupport* tree_support, cons auto t_place = std::chrono::high_resolution_clock::now(); // ### draw these points as circles - indexed_triangle_set branches = draw_branches(*print.get_object(processing.second.front()), volumes, config, move_bounds, throw_on_cancel); - // Reduce memory footprint. After this point only slice_branches() will use volumes and from that only collisions with zero radius will be used. - volumes.clear_all_but_object_collision(); - slice_branches(*print.get_object(processing.second.front()), volumes, config, overhangs, move_bounds, branches, - bottom_contacts, top_contacts, intermediate_layers, layer_storage, throw_on_cancel); - - // this new function may cause bad_function_call exception - //organic_draw_branches( - // *print.get_object(processing.second.front()), volumes, config, move_bounds, - // bottom_contacts, top_contacts, interface_placer, intermediate_layers, layer_storage, - // throw_on_cancel); - + + if (print_object.config().support_style.value != smsOrganic && + // Orca: use organic as default + print_object.config().support_style.value != smsDefault) { + draw_areas(*print.get_object(processing.second.front()), volumes, config, overhangs, move_bounds, + bottom_contacts, top_contacts, intermediate_layers, layer_storage, throw_on_cancel); + } else { + organic_draw_branches( + *print.get_object(processing.second.front()), volumes, config, move_bounds, + bottom_contacts, top_contacts, interface_placer, intermediate_layers, layer_storage, + throw_on_cancel); + } remove_undefined_layers(); @@ -4301,23 +4095,26 @@ static void generate_support_areas(Print &print, TreeSupport* tree_support, cons "Influence area creation: " << dur_path << "ms " "Placement of Points in InfluenceAreas: " << dur_place << "ms " "Drawing result as support " << dur_draw << " ms"; - + // if (config.branch_radius==2121) + // BOOST_LOG_TRIVIAL(error) << "Why ask questions when you already know the answer twice.\n (This is not a real bug, please dont report it.)"; + move_bounds.clear(); - } - else if (generate_raft_contact(print_object, config, interface_placer) >= 0) { + } else if (generate_raft_contact(print_object, config, interface_placer) >= 0) { remove_undefined_layers(); - } - else + } else // No raft. continue; // Produce the support G-code. - SupportGeneratorLayersPtr raft_layers = generate_raft_base(print_object, support_params, print_object.slicing_parameters(), top_contacts, interface_layers, base_interface_layers, intermediate_layers, layer_storage); - SupportGeneratorLayersPtr layers_sorted = generate_support_layers(print_object, raft_layers, bottom_contacts, top_contacts, intermediate_layers, interface_layers, base_interface_layers); - + // Used by both classic and tree supports. + SupportGeneratorLayersPtr raft_layers = generate_raft_base(print_object, support_params, print_object.slicing_parameters(), + top_contacts, interface_layers, base_interface_layers, intermediate_layers, layer_storage); +#if 1 //#ifdef SLIC3R_DEBUG + SupportGeneratorLayersPtr layers_sorted = +#endif // SLIC3R_DEBUG + generate_support_layers(print_object, raft_layers, bottom_contacts, top_contacts, intermediate_layers, interface_layers, base_interface_layers); // Don't fill in the tree supports, make them hollow with just a single sheath line. - print.set_status(69, _L("Support: generate toolpath")); - generate_support_toolpaths(print_object, print_object.support_layers(), print_object.config(), support_params, print_object.slicing_parameters(), + generate_support_toolpaths(print_object.support_layers(), print_object.config(), support_params, print_object.slicing_parameters(), raft_layers, bottom_contacts, top_contacts, intermediate_layers, interface_layers, base_interface_layers); #if 0 @@ -4358,19 +4155,19 @@ static void generate_support_areas(Print &print, TreeSupport* tree_support, cons // Organic specific: Smooth branches and produce one cummulative mesh to be sliced. void organic_draw_branches( - PrintObject& print_object, - TreeModelVolumes& volumes, - const TreeSupportSettings& config, - std::vector& move_bounds, + PrintObject &print_object, + TreeModelVolumes &volumes, + const TreeSupportSettings &config, + std::vector &move_bounds, // I/O: - SupportGeneratorLayersPtr& bottom_contacts, - SupportGeneratorLayersPtr& top_contacts, - InterfacePlacer& interface_placer, + SupportGeneratorLayersPtr &bottom_contacts, + SupportGeneratorLayersPtr &top_contacts, + InterfacePlacer &interface_placer, // Output: - SupportGeneratorLayersPtr& intermediate_layers, - SupportGeneratorLayerStorage& layer_storage, + SupportGeneratorLayersPtr &intermediate_layers, + SupportGeneratorLayerStorage &layer_storage, std::function throw_on_cancel) { @@ -4381,13 +4178,13 @@ void organic_draw_branches( std::vector> map_downwards_old; std::vector> map_downwards_new; linear_data_layers.emplace_back(0); - for (LayerIndex layer_idx = 0; layer_idx < LayerIndex(move_bounds.size()); ++layer_idx) { - SupportElements* layer_above = layer_idx + 1 < LayerIndex(move_bounds.size()) ? &move_bounds[layer_idx + 1] : nullptr; + for (LayerIndex layer_idx = 0; layer_idx < LayerIndex(move_bounds.size()); ++ layer_idx) { + SupportElements *layer_above = layer_idx + 1 < LayerIndex(move_bounds.size()) ? &move_bounds[layer_idx + 1] : nullptr; map_downwards_new.clear(); std::sort(map_downwards_old.begin(), map_downwards_old.end(), [](auto& l, auto& r) { return l.first < r.first; }); - SupportElements& layer = move_bounds[layer_idx]; - for (size_t elem_idx = 0; elem_idx < layer.size(); ++elem_idx) { - SupportElement& elem = layer[elem_idx]; + SupportElements &layer = move_bounds[layer_idx]; + for (size_t elem_idx = 0; elem_idx < layer.size(); ++ elem_idx) { + SupportElement &elem = layer[elem_idx]; int child = -1; if (layer_idx > 0) { auto it = std::lower_bound(map_downwards_old.begin(), map_downwards_old.end(), &elem, [](auto& l, const SupportElement* r) { return l.first < r; }); @@ -4398,13 +4195,13 @@ void organic_draw_branches( } #ifndef NDEBUG { - const SupportElement* pchild = child == -1 ? nullptr : &move_bounds[layer_idx - 1][child]; + const SupportElement *pchild = child == -1 ? nullptr : &move_bounds[layer_idx - 1][child]; assert(pchild ? pchild->state.result_on_layer_is_set() : elem.state.target_height > layer_idx); } #endif // NDEBUG } for (int32_t parent_idx : elem.parents) { - SupportElement& parent = (*layer_above)[parent_idx]; + SupportElement &parent = (*layer_above)[parent_idx]; if (parent.state.result_on_layer_is_set()) map_downwards_new.emplace_back(&parent, elem_idx); } @@ -4418,14 +4215,14 @@ void organic_draw_branches( throw_on_cancel(); - organic_smooth_branches_avoid_collisions(print_object, volumes, config, elements_with_link_down, linear_data_layers, throw_on_cancel); + organic_smooth_branches_avoid_collisions(print_object, volumes, config, move_bounds, elements_with_link_down, linear_data_layers, throw_on_cancel); // Reduce memory footprint. After this point only finalize_interface_and_support_areas() will use volumes and from that only collisions with zero radius will be used. volumes.clear_all_but_object_collision(); // Unmark all nodes. - for (SupportElements& elements : move_bounds) - for (SupportElement& element : elements) + for (SupportElements &elements : move_bounds) + for (SupportElement &element : elements) element.state.marked = false; // Traverse all nodes, generate tubes. @@ -4434,7 +4231,7 @@ void organic_draw_branches( struct Branch { std::vector path; bool has_root{ false }; - bool has_tip{ false }; + bool has_tip { false }; }; struct Slice { @@ -4453,30 +4250,30 @@ void organic_draw_branches( std::vector trees; struct TreeVisitor { - static void visit_recursive(std::vector& move_bounds, SupportElement& start_element, Tree& out) { - assert(!start_element.state.marked && !start_element.parents.empty()); + static void visit_recursive(std::vector &move_bounds, SupportElement &start_element, Tree &out) { + assert(! start_element.state.marked && ! start_element.parents.empty()); // Collect elements up to a bifurcation above. start_element.state.marked = true; // For each branch bifurcating from this point: //SupportElements &layer = move_bounds[start_element.state.layer_idx]; - SupportElements& layer_above = move_bounds[start_element.state.layer_idx + 1]; + SupportElements &layer_above = move_bounds[start_element.state.layer_idx + 1]; bool root = out.branches.empty(); - for (size_t parent_idx = 0; parent_idx < start_element.parents.size(); ++parent_idx) { + for (size_t parent_idx = 0; parent_idx < start_element.parents.size(); ++ parent_idx) { Branch branch; branch.path.emplace_back(&start_element); // Traverse each branch until it branches again. - SupportElement& first_parent = layer_above[start_element.parents[parent_idx]]; - assert(!first_parent.state.marked); + SupportElement &first_parent = layer_above[start_element.parents[parent_idx]]; + assert(! first_parent.state.marked); assert(branch.path.back()->state.layer_idx + 1 == first_parent.state.layer_idx); branch.path.emplace_back(&first_parent); if (first_parent.parents.size() < 2) first_parent.state.marked = true; - SupportElement* next_branch = nullptr; + SupportElement *next_branch = nullptr; if (first_parent.parents.size() == 1) { - for (SupportElement* parent = &first_parent;;) { + for (SupportElement *parent = &first_parent;;) { assert(parent->state.marked); - SupportElement& next_parent = move_bounds[parent->state.layer_idx + 1][parent->parents.front()]; - assert(!next_parent.state.marked); + SupportElement &next_parent = move_bounds[parent->state.layer_idx + 1][parent->parents.front()]; + assert(! next_parent.state.marked); assert(branch.path.back()->state.layer_idx + 1 == next_parent.state.layer_idx); branch.path.emplace_back(&next_parent); if (next_parent.parents.size() > 1) { @@ -4490,14 +4287,13 @@ void organic_draw_branches( break; parent = &next_parent; } - } - else if (first_parent.parents.size() > 1) + } else if (first_parent.parents.size() > 1) // Branching point was reached. next_branch = &first_parent; assert(branch.path.size() >= 2); - assert(next_branch == nullptr || !next_branch->state.marked); + assert(next_branch == nullptr || ! next_branch->state.marked); branch.has_root = root; - branch.has_tip = !next_branch; + branch.has_tip = ! next_branch; out.branches.emplace_back(std::move(branch)); if (next_branch) visit_recursive(move_bounds, *next_branch, out); @@ -4505,8 +4301,8 @@ void organic_draw_branches( } }; - for (LayerIndex layer_idx = 0; layer_idx + 1 < LayerIndex(move_bounds.size()); ++layer_idx) { - // int ielement; + for (LayerIndex layer_idx = 0; layer_idx + 1 < LayerIndex(move_bounds.size()); ++ layer_idx) { +// int ielement; for (SupportElement& start_element : move_bounds[layer_idx]) { if (!start_element.state.marked && !start_element.parents.empty()) { #if 0 @@ -4529,61 +4325,58 @@ void organic_draw_branches( if (start_element.state.lost) { } else if (start_element.state.verylost) { - } - else + } else trees.pop_back(); #endif } - // ++ ielement; +// ++ ielement; } } - const SlicingParameters& slicing_params = print_object.slicing_parameters(); + const SlicingParameters &slicing_params = print_object.slicing_parameters(); MeshSlicingParams mesh_slicing_params; mesh_slicing_params.mode = MeshSlicingParams::SlicingMode::Positive; - tbb::parallel_for(tbb::blocked_range(0, trees.size(), trees.size()), - [&trees, &volumes, &config, &slicing_params, &move_bounds, &mesh_slicing_params, &throw_on_cancel](const tbb::blocked_range& range) { + tbb::parallel_for(tbb::blocked_range(0, trees.size(), 1), + [&trees, &volumes, &config, &slicing_params, &move_bounds, &mesh_slicing_params, &throw_on_cancel](const tbb::blocked_range &range) { indexed_triangle_set partial_mesh; std::vector slice_z; std::vector bottom_contacts; - for (size_t tree_id = range.begin(); tree_id < range.end(); ++tree_id) { - Tree& tree = trees[tree_id]; - for (const Branch& branch : tree.branches) { + for (size_t tree_id = range.begin(); tree_id < range.end(); ++ tree_id) { + Tree &tree = trees[tree_id]; + for (const Branch &branch : tree.branches) { // Triangulate the tube. partial_mesh.clear(); std::pair zspan = extrude_branch(branch.path, config, slicing_params, move_bounds, partial_mesh); LayerIndex layer_begin = branch.has_root ? - branch.path.front()->state.layer_idx : + branch.path.front()->state.layer_idx : std::min(branch.path.front()->state.layer_idx, layer_idx_ceil(slicing_params, config, zspan.first)); - LayerIndex layer_end = (branch.has_tip ? + LayerIndex layer_end = (branch.has_tip ? branch.path.back()->state.layer_idx : std::max(branch.path.back()->state.layer_idx, layer_idx_floor(slicing_params, config, zspan.second))) + 1; slice_z.clear(); - for (LayerIndex layer_idx = layer_begin; layer_idx < layer_end; ++layer_idx) { - const double print_z = layer_z(slicing_params, config, layer_idx); + for (LayerIndex layer_idx = layer_begin; layer_idx < layer_end; ++ layer_idx) { + const double print_z = layer_z(slicing_params, config, layer_idx); const double bottom_z = layer_idx > 0 ? layer_z(slicing_params, config, layer_idx - 1) : 0.; slice_z.emplace_back(float(0.5 * (bottom_z + print_z))); } std::vector slices = slice_mesh(partial_mesh, slice_z, mesh_slicing_params, throw_on_cancel); bottom_contacts.clear(); //FIXME parallelize? - for (LayerIndex i = 0; i < LayerIndex(slices.size()); ++i) + for (LayerIndex i = 0; i < LayerIndex(slices.size()); ++ i) slices[i] = diff_clipped(slices[i], volumes.getCollision(0, layer_begin + i, true)); //FIXME parent_uses_min || draw_area.element->state.use_min_xy_dist); size_t num_empty = 0; if (slices.front().empty()) { // Some of the initial layers are empty. - num_empty = std::find_if(slices.begin(), slices.end(), [](auto& s) { return !s.empty(); }) - slices.begin(); - } - else { + num_empty = std::find_if(slices.begin(), slices.end(), [](auto &s) { return !s.empty(); }) - slices.begin(); + } else { if (branch.has_root) { if (branch.path.front()->state.to_model_gracious) { if (config.settings.support_floor_layers > 0) //FIXME one may just take the whole tree slice as bottom interface. bottom_contacts.emplace_back(intersection_clipped(slices.front(), volumes.getPlaceableAreas(0, layer_begin, [] {}))); - } - else if (layer_begin > 0) { + } else if (layer_begin > 0) { // Drop down areas that do rest non - gracefully on the model to ensure the branch actually rests on something. struct BottomExtraSlice { Polygons polygons; @@ -4595,16 +4388,16 @@ void organic_draw_branches( // Don't propagate further than 1.5 * bottom radius. //LayerIndex layers_propagate_max = 2 * bottom_radius / config.layer_height; LayerIndex layers_propagate_max = 5 * bottom_radius / config.layer_height; - LayerIndex layer_bottommost = branch.path.front()->state.verylost ? + LayerIndex layer_bottommost = branch.path.front()->state.verylost ? // If the tree bottom is hanging in the air, bring it down to some surface. - 0 : + 0 : //FIXME the "verylost" branches should stop when crossing another support. std::max(0, layer_begin - layers_propagate_max); double support_area_min_radius = M_PI * sqr(double(config.branch_radius)); double support_area_stop = std::max(0.2 * M_PI * sqr(double(bottom_radius)), 0.5 * support_area_min_radius); - // Only propagate until the rest area is smaller than this threshold. - //double support_area_min = 0.1 * support_area_min_radius; - for (LayerIndex layer_idx = layer_begin - 1; layer_idx >= layer_bottommost; --layer_idx) { + // Only propagate until the rest area is smaller than this threshold. + //double support_area_min = 0.1 * support_area_min_radius; + for (LayerIndex layer_idx = layer_begin - 1; layer_idx >= layer_bottommost; -- layer_idx) { rest_support = diff_clipped(rest_support.empty() ? slices.front() : rest_support, volumes.getCollision(0, layer_idx, false)); double rest_support_area = area(rest_support); if (rest_support_area < support_area_stop) @@ -4614,7 +4407,7 @@ void organic_draw_branches( } // Now remove those bottom slices that are not supported at all. #if 0 - while (!bottom_extra_slices.empty()) { + while (! bottom_extra_slices.empty()) { Polygons this_bottom_contacts = intersection_clipped( bottom_extra_slices.back().polygons, volumes.getPlaceableAreas(0, layer_begin - LayerIndex(bottom_extra_slices.size()), [] {})); if (area(this_bottom_contacts) < support_area_min) @@ -4629,23 +4422,23 @@ void organic_draw_branches( } #endif if (config.settings.support_floor_layers > 0) - for (int i = int(bottom_extra_slices.size()) - 2; i >= 0; --i) + for (int i = int(bottom_extra_slices.size()) - 2; i >= 0; -- i) bottom_contacts.emplace_back( intersection_clipped(bottom_extra_slices[i].polygons, volumes.getPlaceableAreas(0, layer_begin - i - 1, [] {}))); layer_begin -= LayerIndex(bottom_extra_slices.size()); slices.insert(slices.begin(), bottom_extra_slices.size(), {}); auto it_dst = slices.begin(); - for (auto it_src = bottom_extra_slices.rbegin(); it_src != bottom_extra_slices.rend(); ++it_src) - *it_dst++ = std::move(it_src->polygons); + for (auto it_src = bottom_extra_slices.rbegin(); it_src != bottom_extra_slices.rend(); ++ it_src) + *it_dst ++ = std::move(it_src->polygons); } } - + #if 0 //FIXME branch.has_tip seems to not be reliable. if (branch.has_tip && interface_placer.support_parameters.has_top_contacts) // Add top slices to top contacts / interfaces / base interfaces. - for (int i = int(branch.path.size()) - 1; i >= 0; --i) { - const SupportElement& el = *branch.path[i]; + for (int i = int(branch.path.size()) - 1; i >= 0; -- i) { + const SupportElement &el = *branch.path[i]; if (el.state.missing_roof_layers == 0) break; //FIXME Move or not? @@ -4656,38 +4449,35 @@ void organic_draw_branches( } layer_begin += LayerIndex(num_empty); - while (!slices.empty() && slices.back().empty()) { + while (! slices.empty() && slices.back().empty()) { slices.pop_back(); - --layer_end; + -- layer_end; } if (layer_begin < layer_end) { LayerIndex new_begin = tree.first_layer_id == -1 ? layer_begin : std::min(tree.first_layer_id, layer_begin); - LayerIndex new_end = tree.first_layer_id == -1 ? layer_end : std::max(tree.first_layer_id + LayerIndex(tree.slices.size()), layer_end); - size_t new_size = size_t(new_end - new_begin); + LayerIndex new_end = tree.first_layer_id == -1 ? layer_end : std::max(tree.first_layer_id + LayerIndex(tree.slices.size()), layer_end); + size_t new_size = size_t(new_end - new_begin); if (tree.first_layer_id == -1) { - } - else if (tree.slices.capacity() < new_size) { + } else if (tree.slices.capacity() < new_size) { std::vector new_slices; new_slices.reserve(new_size); if (LayerIndex dif = tree.first_layer_id - new_begin; dif > 0) new_slices.insert(new_slices.end(), dif, {}); append(new_slices, std::move(tree.slices)); tree.slices.swap(new_slices); - } - else if (LayerIndex dif = tree.first_layer_id - new_begin; dif > 0) + } else if (LayerIndex dif = tree.first_layer_id - new_begin; dif > 0) tree.slices.insert(tree.slices.begin(), tree.first_layer_id - new_begin, {}); tree.slices.insert(tree.slices.end(), new_size - tree.slices.size(), {}); layer_begin -= LayerIndex(num_empty); - for (LayerIndex i = layer_begin; i != layer_end; ++i) { + for (LayerIndex i = layer_begin; i != layer_end; ++ i) { int j = i - layer_begin; - if (Polygons& src = slices[j]; !src.empty()) { - Slice& dst = tree.slices[i - new_begin]; - if (++dst.num_branches > 1) { + if (Polygons &src = slices[j]; ! src.empty()) { + Slice &dst = tree.slices[i - new_begin]; + if (++ dst.num_branches > 1) { append(dst.polygons, std::move(src)); if (j < int(bottom_contacts.size())) append(dst.bottom_contacts, std::move(bottom_contacts[j])); - } - else { + } else { dst.polygons = std::move(std::move(src)); if (j < int(bottom_contacts.size())) dst.bottom_contacts = std::move(bottom_contacts[j]); @@ -4701,86 +4491,84 @@ void organic_draw_branches( }, tbb::simple_partitioner()); tbb::parallel_for(tbb::blocked_range(0, trees.size(), 1), - [&trees, &throw_on_cancel](const tbb::blocked_range& range) { - for (size_t tree_id = range.begin(); tree_id < range.end(); ++tree_id) { - Tree& tree = trees[tree_id]; - for (Slice& slice : tree.slices) - if (slice.num_branches > 1) { - slice.polygons = union_(slice.polygons); - slice.bottom_contacts = union_(slice.bottom_contacts); - slice.num_branches = 1; - } - throw_on_cancel(); - } - }, tbb::simple_partitioner()); + [&trees, &throw_on_cancel](const tbb::blocked_range &range) { + for (size_t tree_id = range.begin(); tree_id < range.end(); ++ tree_id) { + Tree &tree = trees[tree_id]; + for (Slice &slice : tree.slices) + if (slice.num_branches > 1) { + slice.polygons = union_(slice.polygons); + slice.bottom_contacts = union_(slice.bottom_contacts); + slice.num_branches = 1; + } + throw_on_cancel(); + } + }, tbb::simple_partitioner()); size_t num_layers = 0; - for (Tree& tree : trees) + for (Tree &tree : trees) if (tree.first_layer_id >= 0) num_layers = std::max(num_layers, size_t(tree.first_layer_id + tree.slices.size())); std::vector slices(num_layers, Slice{}); - for (Tree& tree : trees) + for (Tree &tree : trees) if (tree.first_layer_id >= 0) { - for (LayerIndex i = tree.first_layer_id; i != tree.first_layer_id + LayerIndex(tree.slices.size()); ++i) - if (Slice& src = tree.slices[i - tree.first_layer_id]; !src.polygons.empty()) { - Slice& dst = slices[i]; - if (++dst.num_branches > 1) { - append(dst.polygons, std::move(src.polygons)); + for (LayerIndex i = tree.first_layer_id; i != tree.first_layer_id + LayerIndex(tree.slices.size()); ++ i) + if (Slice &src = tree.slices[i - tree.first_layer_id]; ! src.polygons.empty()) { + Slice &dst = slices[i]; + if (++ dst.num_branches > 1) { + append(dst.polygons, std::move(src.polygons)); append(dst.bottom_contacts, std::move(src.bottom_contacts)); - } - else { - dst.polygons = std::move(src.polygons); + } else { + dst.polygons = std::move(src.polygons); dst.bottom_contacts = std::move(src.bottom_contacts); } } } tbb::parallel_for(tbb::blocked_range(0, std::min(move_bounds.size(), slices.size()), 1), - [&print_object, &config, &slices, &bottom_contacts, &top_contacts, &intermediate_layers, &layer_storage, &throw_on_cancel](const tbb::blocked_range& range) { - for (size_t layer_idx = range.begin(); layer_idx < range.end(); ++layer_idx) { - Slice& slice = slices[layer_idx]; - assert(intermediate_layers[layer_idx] == nullptr); - Polygons base_layer_polygons = slice.num_branches > 1 ? union_(slice.polygons) : std::move(slice.polygons); - Polygons bottom_contact_polygons = slice.num_branches > 1 ? union_(slice.bottom_contacts) : std::move(slice.bottom_contacts); - - if (!base_layer_polygons.empty()) { - // Most of the time in this function is this union call. Can take 300+ ms when a lot of areas are to be unioned. - base_layer_polygons = smooth_outward(union_(base_layer_polygons), config.support_line_width); //FIXME was .smooth(50); - //smooth_outward(closing(std::move(bottom), closing_distance + minimum_island_radius, closing_distance, SUPPORT_SURFACES_OFFSET_PARAMETERS), smoothing_distance) : - // simplify a bit, to ensure the output does not contain outrageous amounts of vertices. Should not be necessary, just a precaution. - base_layer_polygons = polygons_simplify(base_layer_polygons, std::min(scaled(0.03), double(config.resolution))); - } + [&print_object, &config, &slices, &bottom_contacts, &top_contacts, &intermediate_layers, &layer_storage, &throw_on_cancel](const tbb::blocked_range &range) { + for (size_t layer_idx = range.begin(); layer_idx < range.end(); ++layer_idx) { + Slice &slice = slices[layer_idx]; + assert(intermediate_layers[layer_idx] == nullptr); + Polygons base_layer_polygons = slice.num_branches > 1 ? union_(slice.polygons) : std::move(slice.polygons); + Polygons bottom_contact_polygons = slice.num_branches > 1 ? union_(slice.bottom_contacts) : std::move(slice.bottom_contacts); - // Subtract top contact layer polygons from support base. - SupportGeneratorLayer* top_contact_layer = top_contacts.empty() ? nullptr : top_contacts[layer_idx]; - if (top_contact_layer && !top_contact_layer->polygons.empty() && !base_layer_polygons.empty()) { - base_layer_polygons = diff(base_layer_polygons, top_contact_layer->polygons); - if (!bottom_contact_polygons.empty()) - //FIXME it may be better to clip bottom contacts with top contacts first after they are propagated to produce interface layers. - bottom_contact_polygons = diff(bottom_contact_polygons, top_contact_layer->polygons); - } - if (!bottom_contact_polygons.empty()) { - base_layer_polygons = diff(base_layer_polygons, bottom_contact_polygons); - SupportGeneratorLayer* bottom_contact_layer = bottom_contacts[layer_idx] = &layer_allocate( - layer_storage, SupporLayerType::sltBottomContact, print_object.slicing_parameters(), config, layer_idx); - bottom_contact_layer->polygons = std::move(bottom_contact_polygons); - } - if (!base_layer_polygons.empty()) { - SupportGeneratorLayer* base_layer = intermediate_layers[layer_idx] = &layer_allocate( - layer_storage, SupporLayerType::sltBase, print_object.slicing_parameters(), config, layer_idx); - base_layer->polygons = union_(base_layer_polygons); - } + if (! base_layer_polygons.empty()) { + // Most of the time in this function is this union call. Can take 300+ ms when a lot of areas are to be unioned. + base_layer_polygons = smooth_outward(union_(base_layer_polygons), config.support_line_width); //FIXME was .smooth(50); + //smooth_outward(closing(std::move(bottom), closing_distance + minimum_island_radius, closing_distance, SUPPORT_SURFACES_OFFSET_PARAMETERS), smoothing_distance) : + // simplify a bit, to ensure the output does not contain outrageous amounts of vertices. Should not be necessary, just a precaution. + base_layer_polygons = polygons_simplify(base_layer_polygons, std::min(scaled(0.03), double(config.resolution)), polygons_strictly_simple); + } - throw_on_cancel(); + // Subtract top contact layer polygons from support base. + SupportGeneratorLayer *top_contact_layer = top_contacts.empty() ? nullptr : top_contacts[layer_idx]; + if (top_contact_layer && ! top_contact_layer->polygons.empty() && ! base_layer_polygons.empty()) { + base_layer_polygons = diff(base_layer_polygons, top_contact_layer->polygons); + if (! bottom_contact_polygons.empty()) + //FIXME it may be better to clip bottom contacts with top contacts first after they are propagated to produce interface layers. + bottom_contact_polygons = diff(bottom_contact_polygons, top_contact_layer->polygons); + } + if (! bottom_contact_polygons.empty()) { + base_layer_polygons = diff(base_layer_polygons, bottom_contact_polygons); + SupportGeneratorLayer *bottom_contact_layer = bottom_contacts[layer_idx] = &layer_allocate( + layer_storage, SupporLayerType::BottomContact, print_object.slicing_parameters(), config, layer_idx); + bottom_contact_layer->polygons = std::move(bottom_contact_polygons); + } + if (! base_layer_polygons.empty()) { + SupportGeneratorLayer *base_layer = intermediate_layers[layer_idx] = &layer_allocate( + layer_storage, SupporLayerType::Base, print_object.slicing_parameters(), config, layer_idx); + base_layer->polygons = union_(base_layer_polygons); } - }, tbb::simple_partitioner()); -} + throw_on_cancel(); + } + }, tbb::simple_partitioner()); +} } // namespace TreeSupport3D -void generate_tree_support_3D(PrintObject &print_object, TreeSupport* tree_support, std::function throw_on_cancel) +void generate_tree_support_3D(PrintObject &print_object, std::function throw_on_cancel) { size_t idx = 0; for (const PrintObject *po : print_object.print()->objects()) { @@ -4788,11 +4576,9 @@ void generate_tree_support_3D(PrintObject &print_object, TreeSupport* tree_suppo break; ++idx; } - - Points bedpts = tree_support->m_machine_border.contour.points; - BuildVolume build_volume{ Pointfs{ unscaled(bedpts[0]), unscaled(bedpts[1]),unscaled(bedpts[2]),unscaled(bedpts[3])}, tree_support->m_print_config->printable_height }; - - TreeSupport3D::generate_support_areas(*print_object.print(), tree_support, build_volume, { idx }, throw_on_cancel); + TreeSupport3D::generate_support_areas(*print_object.print(), + BuildVolume(Pointfs{ Vec2d{ -300., -300. }, Vec2d{ -300., +300. }, Vec2d{ +300., +300. }, Vec2d{ +300., -300. } }, 0.), { idx }, + throw_on_cancel); } } // namespace Slic3r diff --git a/src/libslic3r/Support/TreeSupport3D.hpp b/src/libslic3r/Support/TreeSupport3D.hpp index 7a6d342ff3e..46e81412317 100644 --- a/src/libslic3r/Support/TreeSupport3D.hpp +++ b/src/libslic3r/Support/TreeSupport3D.hpp @@ -9,13 +9,17 @@ #ifndef slic3r_TreeSupport_hpp #define slic3r_TreeSupport_hpp -#include -#include "../Point.hpp" -#include "../BoundingBox.hpp" -#include "../Utils.hpp" +#include "SupportLayer.hpp" #include "TreeModelVolumes.hpp" #include "TreeSupportCommon.hpp" +#include "../BoundingBox.hpp" +#include "../Point.hpp" +#include "../Utils.hpp" + +#include + + // #define TREE_SUPPORT_SHOW_ERRORS #ifdef SLIC3R_TREESUPPORTS_PROGRESS @@ -36,11 +40,9 @@ namespace Slic3r { // Forward declarations -class TreeSupport; class Print; class PrintObject; -class SupportGeneratorLayer; -using SupportGeneratorLayersPtr = std::vector; +struct SlicingParameters; namespace TreeSupport3D { @@ -90,7 +92,7 @@ struct SupportElementStateBits { #endif // TREE_SUPPORTS_TRACK_LOST deleted(false), marked(false) - {} + {} /*! * \brief The element trys to reach the buildplate @@ -108,7 +110,7 @@ struct SupportElementStateBits { bool use_min_xy_dist : 1; /*! - * \brief True if this Element or any parent provides support to a support roof. + * \brief True if this Element or any parent (element above) provides support to a support roof. */ bool supports_roof : 1; @@ -137,10 +139,6 @@ struct SupportElementStateBits { struct SupportElementState : public SupportElementStateBits { - int type = 0; - coordf_t radius = 0; - float print_z = 0; - /*! * \brief The layer this support elements wants reach */ @@ -175,7 +173,7 @@ struct SupportElementState : public SupportElementStateBits * \brief The resulting center point around which a circle will be drawn later. * Will be set by setPointsOnAreas */ - Point result_on_layer{ std::numeric_limits::max(), std::numeric_limits::max() }; + Point result_on_layer { std::numeric_limits::max(), std::numeric_limits::max() }; bool result_on_layer_is_set() const { return this->result_on_layer != Point{ std::numeric_limits::max(), std::numeric_limits::max() }; } void result_on_layer_reset() { this->result_on_layer = Point{ std::numeric_limits::max(), std::numeric_limits::max() }; } /*! @@ -189,7 +187,7 @@ struct SupportElementState : public SupportElementStateBits double elephant_foot_increases; /*! - * \brief The element trys not to move until this dtt is reached, is set to 0 if the element had to move. + * \brief The element tries to not move until this dtt is reached, is set to 0 if the element had to move. */ uint32_t dont_move_until; @@ -204,18 +202,19 @@ struct SupportElementState : public SupportElementStateBits uint32_t missing_roof_layers; // called by increase_single_area() and increaseAreas() - [[nodiscard]] static SupportElementState propagate_down(const SupportElementState& src) + [[nodiscard]] static SupportElementState propagate_down(const SupportElementState &src) { SupportElementState dst{ src }; - ++dst.distance_to_top; - --dst.layer_idx; + ++ dst.distance_to_top; + -- dst.layer_idx; // set to invalid as we are a new node on a new layer dst.result_on_layer_reset(); dst.skip_ovalisation = false; return dst; } -}; + [[nodiscard]] bool locked() const { return this->distance_to_top < this->dont_move_until; } +}; /*! * \brief Get the Distance to top regarding the real radius this part will have. This is different from distance_to_top, which is can be used to calculate the top most layer of the branch. @@ -279,8 +278,6 @@ struct SupportElement Polygons influence_area; }; -void tree_supports_show_error(std::string_view message, bool critical); - using SupportElements = std::deque; [[nodiscard]] inline coord_t support_element_radius(const TreeSupportSettings &settings, const SupportElement &elem) @@ -293,39 +290,27 @@ using SupportElements = std::deque; return support_element_collision_radius(settings, elem.state); } -void create_layer_pathing(const TreeModelVolumes& volumes, const TreeSupportSettings& config, std::vector& move_bounds, std::function throw_on_cancel); - -void create_nodes_from_area(const TreeModelVolumes& volumes, const TreeSupportSettings& config, std::vector& move_bounds, std::function throw_on_cancel); - -void organic_smooth_branches_avoid_collisions(const PrintObject& print_object, const TreeModelVolumes& volumes, const TreeSupportSettings& config, const std::vector>& elements_with_link_down, const std::vector& linear_data_layers, std::function throw_on_cancel); - -indexed_triangle_set draw_branches(PrintObject& print_object, const TreeModelVolumes& volumes, const TreeSupportSettings& config, std::vector& move_bounds, std::function throw_on_cancel); - -void slice_branches(PrintObject& print_object, const TreeModelVolumes& volumes, const TreeSupportSettings& config, const std::vector& overhangs, std::vector& move_bounds, const indexed_triangle_set& cummulative_mesh, SupportGeneratorLayersPtr& bottom_contacts, SupportGeneratorLayersPtr& top_contacts, SupportGeneratorLayersPtr& intermediate_layers, SupportGeneratorLayerStorage& layer_storage, std::function throw_on_cancel); - -void generate_initial_areas(const PrintObject& print_object, const TreeModelVolumes& volumes, const TreeSupportSettings& config, const std::vector& overhangs, std::vector& move_bounds, InterfacePlacer& interface_placer, std::function throw_on_cancel); - // Organic specific: Smooth branches and produce one cummulative mesh to be sliced. void organic_draw_branches( - PrintObject& print_object, - TreeModelVolumes& volumes, - const TreeSupportSettings& config, - std::vector& move_bounds, + PrintObject &print_object, + TreeModelVolumes &volumes, + const TreeSupportSettings &config, + std::vector &move_bounds, // I/O: - SupportGeneratorLayersPtr& bottom_contacts, - SupportGeneratorLayersPtr& top_contacts, - InterfacePlacer& interface_placer, + SupportGeneratorLayersPtr &bottom_contacts, + SupportGeneratorLayersPtr &top_contacts, + InterfacePlacer &interface_placer, // Output: - SupportGeneratorLayersPtr& intermediate_layers, - SupportGeneratorLayerStorage& layer_storage, + SupportGeneratorLayersPtr &intermediate_layers, + SupportGeneratorLayerStorage &layer_storage, std::function throw_on_cancel); } // namespace TreeSupport3D -void generate_tree_support_3D(PrintObject &print_object, TreeSupport* tree_support, std::function throw_on_cancel = []{}); +void generate_tree_support_3D(PrintObject &print_object, std::function throw_on_cancel = []{}); } // namespace Slic3r diff --git a/src/libslic3r/Support/TreeSupportCommon.cpp b/src/libslic3r/Support/TreeSupportCommon.cpp deleted file mode 100644 index 98796784b15..00000000000 --- a/src/libslic3r/Support/TreeSupportCommon.cpp +++ /dev/null @@ -1,212 +0,0 @@ -// Tree supports by Thomas Rahm, losely based on Tree Supports by CuraEngine. -// Original source of Thomas Rahm's tree supports: -// https://github.com/ThomasRahm/CuraEngine -// -// Original CuraEngine copyright: -// Copyright (c) 2021 Ultimaker B.V. -// CuraEngine is released under the terms of the AGPLv3 or higher. - -#include "TreeSupportCommon.hpp" - -namespace Slic3r::FFFTreeSupport { - -TreeSupportMeshGroupSettings::TreeSupportMeshGroupSettings(const PrintObject &print_object) -{ - const PrintConfig &print_config = print_object.print()->config(); - const PrintObjectConfig &config = print_object.config(); - const SlicingParameters &slicing_params = print_object.slicing_parameters(); -// const std::vector printing_extruders = print_object.object_extruders(); - - // Support must be enabled and set to Tree style. - assert(config.enable_support || config.enforce_support_layers > 0); - assert(is_tree(config.support_type)); - - // Calculate maximum external perimeter width over all printing regions, taking into account the default layer height. - coordf_t external_perimeter_width = 0.; - for (size_t region_id = 0; region_id < print_object.num_printing_regions(); ++ region_id) { - const PrintRegion ®ion = print_object.printing_region(region_id); - external_perimeter_width = std::max(external_perimeter_width, region.flow(print_object, frExternalPerimeter, config.layer_height).width()); - } - - this->layer_height = scaled(config.layer_height.value); - this->resolution = scaled(print_config.resolution.value); - // Arache feature - this->min_feature_size = scaled(config.min_feature_size.value); - // +1 makes the threshold inclusive - this->support_angle = 0.5 * M_PI - std::clamp((config.support_threshold_angle + 1) * M_PI / 180., 0., 0.5 * M_PI); - this->support_line_width = support_material_flow(&print_object, config.layer_height).scaled_width(); - this->support_roof_line_width = support_material_interface_flow(&print_object, config.layer_height).scaled_width(); - //FIXME add it to SlicingParameters and reuse in both tree and normal supports? - this->support_bottom_enable = config.support_interface_top_layers.value > 0 && config.support_interface_bottom_layers.value != 0; - this->support_bottom_height = this->support_bottom_enable ? - (config.support_interface_bottom_layers.value > 0 ? - config.support_interface_bottom_layers.value : - config.support_interface_top_layers.value) * this->layer_height : - 0; - this->support_material_buildplate_only = config.support_on_build_plate_only; - this->support_xy_distance = scaled(config.support_object_xy_distance.value); - // Separation of interfaces, it is likely smaller than support_xy_distance. - this->support_xy_distance_overhang = std::min(this->support_xy_distance, scaled(0.5 * external_perimeter_width)); - this->support_top_distance = scaled(slicing_params.gap_support_object); - this->support_bottom_distance = scaled(slicing_params.gap_object_support); -// this->support_interface_skip_height = -// this->support_infill_angles = - this->support_roof_enable = config.support_interface_top_layers.value > 0; - this->support_roof_layers = this->support_roof_enable ? config.support_interface_top_layers.value : 0; - this->support_floor_enable = config.support_interface_top_layers.value > 0 && config.support_interface_bottom_layers.value > 0; - this->support_floor_layers = this->support_floor_enable ? config.support_interface_bottom_layers.value : 0; -// this->minimum_roof_area = -// this->support_roof_angles = - this->support_roof_pattern = config.support_interface_pattern; - this->support_pattern = config.support_base_pattern; - this->support_line_spacing = scaled(config.support_base_pattern_spacing.value); -// this->support_bottom_offset = -// this->support_wall_count = config.support_material_with_sheath ? 1 : 0; - this->support_wall_count = 1; - this->support_roof_line_distance = scaled(config.support_interface_spacing.value) + this->support_roof_line_width; -// this->minimum_support_area = -// this->minimum_bottom_area = -// this->support_offset = - this->support_tree_branch_distance = scaled(config.tree_support_branch_distance_organic.value); - this->support_tree_angle = std::clamp(config.tree_support_branch_angle_organic * M_PI / 180., 0., 0.5 * M_PI - EPSILON); - this->support_tree_angle_slow = std::clamp(config.tree_support_angle_slow * M_PI / 180., 0., this->support_tree_angle - EPSILON); - this->support_tree_branch_diameter = scaled(config.tree_support_branch_diameter_organic.value); - this->support_tree_branch_diameter_angle = std::clamp(config.tree_support_branch_diameter_angle * M_PI / 180., 0., 0.5 * M_PI - EPSILON); - this->support_tree_top_rate = config.tree_support_top_rate.value; // percent -// this->support_tree_tip_diameter = this->support_line_width; - this->support_tree_tip_diameter = std::clamp(scaled(config.tree_support_tip_diameter.value), (coord_t)0, this->support_tree_branch_diameter); - - std::cout << "\n---------------\n" - << "layer_height: " << layer_height << "\nresolution: " << resolution << "\nmin_feature_size: " << min_feature_size - << "\nsupport_angle: " << support_angle << "\nconfig.support_threshold_angle: " << config.support_threshold_angle << "\nsupport_line_width: " << support_line_width - << "\nsupport_roof_line_width: " << support_roof_line_width << "\nsupport_bottom_enable: " << support_bottom_enable - << "\nsupport_bottom_height: " << support_bottom_height - << "\nsupport_material_buildplate_only: " << support_material_buildplate_only - << "\nsupport_xy_distance: " << support_xy_distance << "\nsupport_xy_distance_overhang: " << support_xy_distance_overhang - << "\nsupport_top_distance: " << support_top_distance << "\nsupport_bottom_distance: " << support_bottom_distance - << "\nsupport_roof_enable: " << support_roof_enable << "\nsupport_roof_layers: " << support_roof_layers - << "\nsupport_floor_enable: " << support_floor_enable << "\nsupport_floor_layers: " << support_floor_layers - << "\nsupport_roof_pattern: " << support_roof_pattern << "\nsupport_pattern: " << support_pattern - << "\nsupport_line_spacing: " << support_line_spacing << "\nsupport_wall_count: " << support_wall_count - << "\nsupport_roof_line_distance: " << support_roof_line_distance - << "\nsupport_tree_branch_distance: " << support_tree_branch_distance - << "\nsupport_tree_angle_slow: " << support_tree_angle_slow - << "\nsupport_tree_branch_diameter: " << support_tree_branch_diameter - << "\nsupport_tree_branch_diameter_angle: " << support_tree_branch_diameter_angle - << "\nsupport_tree_top_rate: " << support_tree_top_rate << "\nsupport_tree_tip_diameter: " << support_tree_tip_diameter - << "\n---------------\n"; -} - -TreeSupportSettings::TreeSupportSettings(const TreeSupportMeshGroupSettings &mesh_group_settings, const SlicingParameters &slicing_params) - : support_line_width(mesh_group_settings.support_line_width), - layer_height(mesh_group_settings.layer_height), - branch_radius(mesh_group_settings.support_tree_branch_diameter / 2), - min_radius(mesh_group_settings.support_tree_tip_diameter / 2), // The actual radius is 50 microns larger as the resulting branches will be increased by 50 microns to avoid rounding errors effectively increasing the xydistance - maximum_move_distance((mesh_group_settings.support_tree_angle < M_PI / 2.) ? (coord_t)(tan(mesh_group_settings.support_tree_angle) * layer_height) : std::numeric_limits::max()), - maximum_move_distance_slow((mesh_group_settings.support_tree_angle_slow < M_PI / 2.) ? (coord_t)(tan(mesh_group_settings.support_tree_angle_slow) * layer_height) : std::numeric_limits::max()), - support_bottom_layers(mesh_group_settings.support_bottom_enable ? (mesh_group_settings.support_bottom_height + layer_height / 2) / layer_height : 0), - // Ensure lines always stack nicely even if layer height is large. - tip_layers(std::max((branch_radius - min_radius) / (support_line_width / 3), branch_radius / layer_height)), - branch_radius_increase_per_layer(tan(mesh_group_settings.support_tree_branch_diameter_angle) * layer_height), - max_to_model_radius_increase(mesh_group_settings.support_tree_max_diameter_increase_by_merges_when_support_to_model / 2), - min_dtt_to_model(round_up_divide(mesh_group_settings.support_tree_min_height_to_model, layer_height)), - increase_radius_until_radius(mesh_group_settings.support_tree_branch_diameter / 2), - increase_radius_until_layer(increase_radius_until_radius <= branch_radius ? tip_layers * (increase_radius_until_radius / branch_radius) : (increase_radius_until_radius - branch_radius) / branch_radius_increase_per_layer), - support_rests_on_model(! mesh_group_settings.support_material_buildplate_only), - xy_distance(mesh_group_settings.support_xy_distance), - xy_min_distance(std::min(mesh_group_settings.support_xy_distance, mesh_group_settings.support_xy_distance_overhang)), - bp_radius(mesh_group_settings.support_tree_bp_diameter / 2), - // Increase by half a line overlap, but not faster than 40 degrees angle (0 degrees means zero increase in radius). - bp_radius_increase_per_layer(std::min(tan(0.7) * layer_height, 0.5 * support_line_width)), - z_distance_bottom_layers(size_t(round(double(mesh_group_settings.support_bottom_distance) / double(layer_height)))), - z_distance_top_layers(size_t(round(double(mesh_group_settings.support_top_distance) / double(layer_height)))), -// support_infill_angles(mesh_group_settings.support_infill_angles), - support_roof_angles(mesh_group_settings.support_roof_angles), - roof_pattern(mesh_group_settings.support_roof_pattern), - support_pattern(mesh_group_settings.support_pattern), - support_roof_line_width(mesh_group_settings.support_roof_line_width), - support_line_spacing(mesh_group_settings.support_line_spacing), - support_bottom_offset(mesh_group_settings.support_bottom_offset), - support_wall_count(mesh_group_settings.support_wall_count), - resolution(mesh_group_settings.resolution), - support_roof_line_distance(mesh_group_settings.support_roof_line_distance), // in the end the actual infill has to be calculated to subtract interface from support areas according to interface_preference. - settings(mesh_group_settings), - min_feature_size(mesh_group_settings.min_feature_size) -{ - // At least one tip layer must be defined. - assert(tip_layers > 0); - - layer_start_bp_radius = (bp_radius - branch_radius) / bp_radius_increase_per_layer; - - if (TreeSupportSettings::soluble) { - // safeOffsetInc can only work in steps of the size xy_min_distance in the worst case => xy_min_distance has to be a bit larger than 0 in this worst case and should be large enough for performance to not suffer extremely - // When for all meshes the z bottom and top distance is more than one layer though the worst case is xy_min_distance + min_feature_size - // This is not the best solution, but the only one to ensure areas can not lag though walls at high maximum_move_distance. - xy_min_distance = std::max(xy_min_distance, scaled(0.1)); - xy_distance = std::max(xy_distance, xy_min_distance); - } - -// const std::unordered_map interface_map = { { "support_area_overwrite_interface_area", InterfacePreference::SupportAreaOverwritesInterface }, { "interface_area_overwrite_support_area", InterfacePreference::InterfaceAreaOverwritesSupport }, { "support_lines_overwrite_interface_area", InterfacePreference::SupportLinesOverwriteInterface }, { "interface_lines_overwrite_support_area", InterfacePreference::InterfaceLinesOverwriteSupport }, { "nothing", InterfacePreference::Nothing } }; -// interface_preference = interface_map.at(mesh_group_settings.get("support_interface_priority")); -//FIXME this was the default -// interface_preference = InterfacePreference::SupportLinesOverwriteInterface; - //interface_preference = InterfacePreference::SupportAreaOverwritesInterface; - interface_preference = InterfacePreference::InterfaceAreaOverwritesSupport; - - if (slicing_params.raft_layers() > 0) { - // Fill in raft_layers with the heights of the layers below the first object layer. - // First layer - double z = slicing_params.first_print_layer_height; - this->raft_layers.emplace_back(z); - // Raft base layers - for (size_t i = 1; i < slicing_params.base_raft_layers; ++ i) { - z += slicing_params.base_raft_layer_height; - this->raft_layers.emplace_back(z); - } - // Raft interface layers - for (size_t i = 0; i + 1 < slicing_params.interface_raft_layers; ++ i) { - z += slicing_params.interface_raft_layer_height; - this->raft_layers.emplace_back(z); - } - // Raft contact layer - if (slicing_params.raft_layers() > 1) { - z = slicing_params.raft_contact_top_z; - this->raft_layers.emplace_back(z); - } - if (double dist_to_go = slicing_params.object_print_z_min - z; dist_to_go > EPSILON) { - // Layers between the raft contacts and bottom of the object. - auto nsteps = int(ceil(dist_to_go / slicing_params.max_suport_layer_height)); - double step = dist_to_go / nsteps; - for (int i = 0; i < nsteps; ++ i) { - z += step; - this->raft_layers.emplace_back(z); - } - } - } -} - -#if defined(TREE_SUPPORT_SHOW_ERRORS) && defined(_WIN32) - #define TREE_SUPPORT_SHOW_ERRORS_WIN32 - #include -#endif - -// Shared with generate_support_areas() -bool g_showed_critical_error = false; -bool g_showed_performance_warning = false; - -void tree_supports_show_error(std::string_view message, bool critical) -{ // todo Remove! ONLY FOR PUBLIC BETA!! - printf("Error: %s, critical: %d\n", message.data(), int(critical)); -#ifdef TREE_SUPPORT_SHOW_ERRORS_WIN32 - static bool showed_critical = false; - static bool showed_performance = false; - auto bugtype = std::string(critical ? " This is a critical bug. It may cause missing or malformed branches.\n" : "This bug should only decrease performance.\n"); - bool show = (critical && !g_showed_critical_error) || (!critical && !g_showed_performance_warning); - (critical ? g_showed_critical_error : g_showed_performance_warning) = true; - if (show) - MessageBoxA(nullptr, std::string("TreeSupport_2 MOD detected an error while generating the tree support.\nPlease report this back to me with profile and model.\nRevision 5.0\n" + std::string(message) + "\n" + bugtype).c_str(), - "Bug detected!", MB_OK | MB_SYSTEMMODAL | MB_SETFOREGROUND | MB_ICONWARNING); -#endif // TREE_SUPPORT_SHOW_ERRORS_WIN32 -} - -} // namespace Slic3r::FFFTreeSupport diff --git a/src/libslic3r/Support/TreeSupportCommon.hpp b/src/libslic3r/Support/TreeSupportCommon.hpp index ee7d4b33388..50ca626fa3f 100644 --- a/src/libslic3r/Support/TreeSupportCommon.hpp +++ b/src/libslic3r/Support/TreeSupportCommon.hpp @@ -15,12 +15,10 @@ #include -using namespace Slic3r::FFFSupport; - namespace Slic3r { -namespace FFFTreeSupport +namespace TreeSupport3D { using LayerIndex = int; @@ -36,7 +34,92 @@ enum class InterfacePreference struct TreeSupportMeshGroupSettings { TreeSupportMeshGroupSettings() = default; - explicit TreeSupportMeshGroupSettings(const PrintObject &print_object); + explicit TreeSupportMeshGroupSettings(const PrintObject &print_object) + { + const PrintConfig &print_config = print_object.print()->config(); + const PrintObjectConfig &config = print_object.config(); + const SlicingParameters &slicing_params = print_object.slicing_parameters(); + // const std::vector printing_extruders = print_object.object_extruders(); + + // Support must be enabled and set to Tree style. + assert(config.enable_support || config.enforce_support_layers > 0); + assert(is_tree(config.support_type)); + + // Calculate maximum external perimeter width over all printing regions, taking into account the default layer height. + coordf_t external_perimeter_width = 0.; + for (size_t region_id = 0; region_id < print_object.num_printing_regions(); ++ region_id) { + const PrintRegion ®ion = print_object.printing_region(region_id); + external_perimeter_width = std::max(external_perimeter_width, region.flow(print_object, frExternalPerimeter, config.layer_height).width()); + } + + this->layer_height = scaled(config.layer_height.value); + this->resolution = scaled(print_config.resolution.value); + // Arache feature + this->min_feature_size = scaled(config.min_feature_size.value); + // +1 makes the threshold inclusive + this->support_angle = 0.5 * M_PI - std::clamp((config.support_threshold_angle + 1) * M_PI / 180., 0., 0.5 * M_PI); + this->support_line_width = support_material_flow(&print_object, config.layer_height).scaled_width(); + this->support_roof_line_width = support_material_interface_flow(&print_object, config.layer_height).scaled_width(); + //FIXME add it to SlicingParameters and reuse in both tree and normal supports? + this->support_bottom_enable = config.support_interface_top_layers.value > 0 && config.support_interface_bottom_layers.value != 0; + this->support_bottom_height = this->support_bottom_enable ? + (config.support_interface_bottom_layers.value > 0 ? + config.support_interface_bottom_layers.value : + config.support_interface_top_layers.value) * this->layer_height : + 0; + this->support_material_buildplate_only = config.support_on_build_plate_only; + this->support_xy_distance = scaled(config.support_object_xy_distance.value); + // Separation of interfaces, it is likely smaller than support_xy_distance. + this->support_xy_distance_overhang = std::min(this->support_xy_distance, scaled(0.5 * external_perimeter_width)); + this->support_top_distance = scaled(slicing_params.gap_support_object); + this->support_bottom_distance = scaled(slicing_params.gap_object_support); + // this->support_interface_skip_height = + // this->support_infill_angles = + this->support_roof_enable = config.support_interface_top_layers.value > 0; + this->support_roof_layers = this->support_roof_enable ? config.support_interface_top_layers.value : 0; + this->support_floor_enable = config.support_interface_top_layers.value > 0 && config.support_interface_bottom_layers.value > 0; + this->support_floor_layers = this->support_floor_enable ? config.support_interface_bottom_layers.value : 0; + // this->minimum_roof_area = + // this->support_roof_angles = + this->support_roof_pattern = config.support_interface_pattern; + this->support_pattern = config.support_base_pattern; + this->support_line_spacing = scaled(config.support_base_pattern_spacing.value); + // this->support_bottom_offset = + // this->support_wall_count = config.support_material_with_sheath ? 1 : 0; + this->support_wall_count = 1; + this->support_roof_line_distance = scaled(config.support_interface_spacing.value) + this->support_roof_line_width; + // this->minimum_support_area = + // this->minimum_bottom_area = + // this->support_offset = + this->support_tree_branch_distance = scaled(config.tree_support_branch_distance_organic.value); + this->support_tree_angle = std::clamp(config.tree_support_branch_angle_organic * M_PI / 180., 0., 0.5 * M_PI - EPSILON); + this->support_tree_angle_slow = std::clamp(config.tree_support_angle_slow * M_PI / 180., 0., this->support_tree_angle - EPSILON); + this->support_tree_branch_diameter = scaled(config.tree_support_branch_diameter_organic.value); + this->support_tree_branch_diameter_angle = std::clamp(config.tree_support_branch_diameter_angle * M_PI / 180., 0., 0.5 * M_PI - EPSILON); + this->support_tree_top_rate = config.tree_support_top_rate.value; // percent + // this->support_tree_tip_diameter = this->support_line_width; + this->support_tree_tip_diameter = std::clamp(scaled(config.tree_support_tip_diameter.value), (coord_t)0, this->support_tree_branch_diameter); + + std::cout << "\n---------------\n" + << "layer_height: " << layer_height << "\nresolution: " << resolution << "\nmin_feature_size: " << min_feature_size + << "\nsupport_angle: " << support_angle << "\nconfig.support_threshold_angle: " << config.support_threshold_angle << "\nsupport_line_width: " << support_line_width + << "\nsupport_roof_line_width: " << support_roof_line_width << "\nsupport_bottom_enable: " << support_bottom_enable + << "\nsupport_bottom_height: " << support_bottom_height + << "\nsupport_material_buildplate_only: " << support_material_buildplate_only + << "\nsupport_xy_distance: " << support_xy_distance << "\nsupport_xy_distance_overhang: " << support_xy_distance_overhang + << "\nsupport_top_distance: " << support_top_distance << "\nsupport_bottom_distance: " << support_bottom_distance + << "\nsupport_roof_enable: " << support_roof_enable << "\nsupport_roof_layers: " << support_roof_layers + << "\nsupport_floor_enable: " << support_floor_enable << "\nsupport_floor_layers: " << support_floor_layers + << "\nsupport_roof_pattern: " << support_roof_pattern << "\nsupport_pattern: " << support_pattern + << "\nsupport_line_spacing: " << support_line_spacing << "\nsupport_wall_count: " << support_wall_count + << "\nsupport_roof_line_distance: " << support_roof_line_distance + << "\nsupport_tree_branch_distance: " << support_tree_branch_distance + << "\nsupport_tree_angle_slow: " << support_tree_angle_slow + << "\nsupport_tree_branch_diameter: " << support_tree_branch_diameter + << "\nsupport_tree_branch_diameter_angle: " << support_tree_branch_diameter_angle + << "\nsupport_tree_top_rate: " << support_tree_top_rate << "\nsupport_tree_tip_diameter: " << support_tree_tip_diameter + << "\n---------------\n"; + } /*********************************************************************/ /* Print parameters, not support specific: */ @@ -209,7 +292,93 @@ struct TreeSupportSettings { public: TreeSupportSettings() = default; // required for the definition of the config variable in the TreeSupportGenerator class. - explicit TreeSupportSettings(const TreeSupportMeshGroupSettings &mesh_group_settings, const SlicingParameters &slicing_params); + explicit TreeSupportSettings(const TreeSupportMeshGroupSettings &mesh_group_settings, const SlicingParameters &slicing_params) + : support_line_width(mesh_group_settings.support_line_width), + layer_height(mesh_group_settings.layer_height), + branch_radius(mesh_group_settings.support_tree_branch_diameter / 2), + min_radius(mesh_group_settings.support_tree_tip_diameter / 2), // The actual radius is 50 microns larger as the resulting branches will be increased by 50 microns to avoid rounding errors effectively increasing the xydistance + maximum_move_distance((mesh_group_settings.support_tree_angle < M_PI / 2.) ? (coord_t)(tan(mesh_group_settings.support_tree_angle) * layer_height) : std::numeric_limits::max()), + maximum_move_distance_slow((mesh_group_settings.support_tree_angle_slow < M_PI / 2.) ? (coord_t)(tan(mesh_group_settings.support_tree_angle_slow) * layer_height) : std::numeric_limits::max()), + support_bottom_layers(mesh_group_settings.support_bottom_enable ? (mesh_group_settings.support_bottom_height + layer_height / 2) / layer_height : 0), + // Ensure lines always stack nicely even if layer height is large. + tip_layers(std::max((branch_radius - min_radius) / (support_line_width / 3), branch_radius / layer_height)), + branch_radius_increase_per_layer(tan(mesh_group_settings.support_tree_branch_diameter_angle) * layer_height), + max_to_model_radius_increase(mesh_group_settings.support_tree_max_diameter_increase_by_merges_when_support_to_model / 2), + min_dtt_to_model(round_up_divide(mesh_group_settings.support_tree_min_height_to_model, layer_height)), + increase_radius_until_radius(mesh_group_settings.support_tree_branch_diameter / 2), + increase_radius_until_layer(increase_radius_until_radius <= branch_radius ? tip_layers * (increase_radius_until_radius / branch_radius) : (increase_radius_until_radius - branch_radius) / branch_radius_increase_per_layer), + support_rests_on_model(! mesh_group_settings.support_material_buildplate_only), + xy_distance(mesh_group_settings.support_xy_distance), + xy_min_distance(std::min(mesh_group_settings.support_xy_distance, mesh_group_settings.support_xy_distance_overhang)), + bp_radius(mesh_group_settings.support_tree_bp_diameter / 2), + // Increase by half a line overlap, but not faster than 40 degrees angle (0 degrees means zero increase in radius). + bp_radius_increase_per_layer(std::min(tan(0.7) * layer_height, 0.5 * support_line_width)), + z_distance_bottom_layers(size_t(round(double(mesh_group_settings.support_bottom_distance) / double(layer_height)))), + z_distance_top_layers(size_t(round(double(mesh_group_settings.support_top_distance) / double(layer_height)))), + // support_infill_angles(mesh_group_settings.support_infill_angles), + support_roof_angles(mesh_group_settings.support_roof_angles), + roof_pattern(mesh_group_settings.support_roof_pattern), + support_pattern(mesh_group_settings.support_pattern), + support_roof_line_width(mesh_group_settings.support_roof_line_width), + support_line_spacing(mesh_group_settings.support_line_spacing), + support_bottom_offset(mesh_group_settings.support_bottom_offset), + support_wall_count(mesh_group_settings.support_wall_count), + resolution(mesh_group_settings.resolution), + support_roof_line_distance(mesh_group_settings.support_roof_line_distance), // in the end the actual infill has to be calculated to subtract interface from support areas according to interface_preference. + settings(mesh_group_settings), + min_feature_size(mesh_group_settings.min_feature_size) + { + // At least one tip layer must be defined. + assert(tip_layers > 0); + + layer_start_bp_radius = (bp_radius - branch_radius) / bp_radius_increase_per_layer; + + if (TreeSupportSettings::soluble) { + // safeOffsetInc can only work in steps of the size xy_min_distance in the worst case => xy_min_distance has to be a bit larger than 0 in this worst case and should be large enough for performance to not suffer extremely + // When for all meshes the z bottom and top distance is more than one layer though the worst case is xy_min_distance + min_feature_size + // This is not the best solution, but the only one to ensure areas can not lag though walls at high maximum_move_distance. + xy_min_distance = std::max(xy_min_distance, scaled(0.1)); + xy_distance = std::max(xy_distance, xy_min_distance); + } + + // const std::unordered_map interface_map = { { "support_area_overwrite_interface_area", InterfacePreference::SupportAreaOverwritesInterface }, { "interface_area_overwrite_support_area", InterfacePreference::InterfaceAreaOverwritesSupport }, { "support_lines_overwrite_interface_area", InterfacePreference::SupportLinesOverwriteInterface }, { "interface_lines_overwrite_support_area", InterfacePreference::InterfaceLinesOverwriteSupport }, { "nothing", InterfacePreference::Nothing } }; + // interface_preference = interface_map.at(mesh_group_settings.get("support_interface_priority")); + //FIXME this was the default + // interface_preference = InterfacePreference::SupportLinesOverwriteInterface; + //interface_preference = InterfacePreference::SupportAreaOverwritesInterface; + interface_preference = InterfacePreference::InterfaceAreaOverwritesSupport; + + if (slicing_params.raft_layers() > 0) { + // Fill in raft_layers with the heights of the layers below the first object layer. + // First layer + double z = slicing_params.first_print_layer_height; + this->raft_layers.emplace_back(z); + // Raft base layers + for (size_t i = 1; i < slicing_params.base_raft_layers; ++ i) { + z += slicing_params.base_raft_layer_height; + this->raft_layers.emplace_back(z); + } + // Raft interface layers + for (size_t i = 0; i + 1 < slicing_params.interface_raft_layers; ++ i) { + z += slicing_params.interface_raft_layer_height; + this->raft_layers.emplace_back(z); + } + // Raft contact layer + if (slicing_params.raft_layers() > 1) { + z = slicing_params.raft_contact_top_z; + this->raft_layers.emplace_back(z); + } + if (double dist_to_go = slicing_params.object_print_z_min - z; dist_to_go > EPSILON) { + // Layers between the raft contacts and bottom of the object. + auto nsteps = int(ceil(dist_to_go / slicing_params.max_suport_layer_height)); + double step = dist_to_go / nsteps; + for (int i = 0; i < nsteps; ++ i) { + z += step; + this->raft_layers.emplace_back(z); + } + } + } + } // some static variables dependent on other meshes that are not currently processed. // Has to be static because TreeSupportConfig will be used in TreeModelVolumes as this reduces redundancy. @@ -450,7 +619,20 @@ static constexpr const bool polygons_strictly_simple = false; inline double tiny_area_threshold() { return sqr(scaled(0.001)); } -void tree_supports_show_error(std::string_view message, bool critical); +inline void tree_supports_show_error(std::string_view message, bool critical) +{ // todo Remove! ONLY FOR PUBLIC BETA!! + printf("Error: %s, critical: %d\n", message.data(), int(critical)); +#ifdef TREE_SUPPORT_SHOW_ERRORS_WIN32 + static bool g_showed_critical_error = false; + static bool g_showed_performance_warning = false; + auto bugtype = std::string(critical ? " This is a critical bug. It may cause missing or malformed branches.\n" : "This bug should only decrease performance.\n"); + bool show = (critical && !g_showed_critical_error) || (!critical && !g_showed_performance_warning); + (critical ? g_showed_critical_error : g_showed_performance_warning) = true; + if (show) + MessageBoxA(nullptr, std::string("TreeSupport_2 MOD detected an error while generating the tree support.\nPlease report this back to me with profile and model.\nRevision 5.0\n" + std::string(message) + "\n" + bugtype).c_str(), + "Bug detected!", MB_OK | MB_SYSTEMMODAL | MB_SETFOREGROUND | MB_ICONWARNING); +#endif // TREE_SUPPORT_SHOW_ERRORS_WIN32 +} inline double layer_z(const SlicingParameters &slicing_params, const TreeSupportSettings &config, const size_t layer_idx) { @@ -584,7 +766,7 @@ class InterfacePlacer { std::mutex m_mutex_layer_storage; }; -} // namespace FFFTreeSupport +} // namespace TreeSupport3D } // namespace Slic3r diff --git a/src/libslic3r/SupportMaterial.cpp b/src/libslic3r/SupportMaterial.cpp deleted file mode 100644 index b022607bf75..00000000000 --- a/src/libslic3r/SupportMaterial.cpp +++ /dev/null @@ -1,4960 +0,0 @@ -#include "ClipperUtils.hpp" -#include "ExtrusionEntity.hpp" -#include "ExtrusionEntityCollection.hpp" -#include "Layer.hpp" -#include "Print.hpp" -#include "SupportMaterial.hpp" -#include "Fill/FillBase.hpp" -#include "Geometry.hpp" -#include "Point.hpp" -#include "MutablePolygon.hpp" - -#include -#include -#include -#include - -#include -#include -#include - -#define SUPPORT_USE_AGG_RASTERIZER - -#ifdef SUPPORT_USE_AGG_RASTERIZER - #include - #include - #include - #include - #include - #include "PNGReadWrite.hpp" -#else - #include "EdgeGrid.hpp" -#endif // SUPPORT_USE_AGG_RASTERIZER - -// #define SLIC3R_DEBUG -// #define SUPPORT_TREE_DEBUG_TO_SVG -// Make assert active if SLIC3R_DEBUG -#if defined(SLIC3R_DEBUG) || defined(SUPPORT_TREE_DEBUG_TO_SVG) - #define DEBUG - #define _DEBUG - #undef NDEBUG - #include "utils.hpp" - #include "SVG.hpp" -#endif - -#ifndef SQ -#define SQ(x) ((x)*(x)) -#endif - -// #undef NDEBUG -#include - -namespace Slic3r { - -// how much we extend support around the actual contact area -//FIXME this should be dependent on the nozzle diameter! -// BBS: change from 1.5 to 1.2 -#define SUPPORT_MATERIAL_MARGIN 1.2 - -// Increment used to reach MARGIN in steps to avoid trespassing thin objects -#define NUM_MARGIN_STEPS 3 - -// Dimensions of a tree-like structure to save material -#define PILLAR_SIZE (2.5) -#define PILLAR_SPACING 10 - -//#define SUPPORT_SURFACES_OFFSET_PARAMETERS ClipperLib::jtMiter, 3. -//#define SUPPORT_SURFACES_OFFSET_PARAMETERS ClipperLib::jtMiter, 1.5 -#define SUPPORT_SURFACES_OFFSET_PARAMETERS ClipperLib::jtSquare, 0. - -static constexpr bool support_with_sheath = false; - -#ifdef SLIC3R_DEBUG -const char* support_surface_type_to_color_name(const PrintObjectSupportMaterial::SupporLayerType surface_type) -{ - switch (surface_type) { - case PrintObjectSupportMaterial::sltTopContact: return "rgb(255,0,0)"; // "red"; - case PrintObjectSupportMaterial::sltTopInterface: return "rgb(0,255,0)"; // "green"; - case PrintObjectSupportMaterial::sltBase: return "rgb(0,0,255)"; // "blue"; - case PrintObjectSupportMaterial::sltBottomInterface:return "rgb(255,255,128)"; // yellow - case PrintObjectSupportMaterial::sltBottomContact: return "rgb(255,0,255)"; // magenta - case PrintObjectSupportMaterial::sltRaftInterface: return "rgb(0,255,255)"; - case PrintObjectSupportMaterial::sltRaftBase: return "rgb(128,128,128)"; - case PrintObjectSupportMaterial::sltUnknown: return "rgb(128,0,0)"; // maroon - default: return "rgb(64,64,64)"; - }; -} - -Point export_support_surface_type_legend_to_svg_box_size() -{ - return Point(scale_(1.+10.*8.), scale_(3.)); -} - -void export_support_surface_type_legend_to_svg(SVG &svg, const Point &pos) -{ - // 1st row - coord_t pos_x0 = pos(0) + scale_(1.); - coord_t pos_x = pos_x0; - coord_t pos_y = pos(1) + scale_(1.5); - coord_t step_x = scale_(10.); - svg.draw_legend(Point(pos_x, pos_y), "top contact" , support_surface_type_to_color_name(PrintObjectSupportMaterial::sltTopContact)); - pos_x += step_x; - svg.draw_legend(Point(pos_x, pos_y), "top iface" , support_surface_type_to_color_name(PrintObjectSupportMaterial::sltTopInterface)); - pos_x += step_x; - svg.draw_legend(Point(pos_x, pos_y), "base" , support_surface_type_to_color_name(PrintObjectSupportMaterial::sltBase)); - pos_x += step_x; - svg.draw_legend(Point(pos_x, pos_y), "bottom iface" , support_surface_type_to_color_name(PrintObjectSupportMaterial::sltBottomInterface)); - pos_x += step_x; - svg.draw_legend(Point(pos_x, pos_y), "bottom contact" , support_surface_type_to_color_name(PrintObjectSupportMaterial::sltBottomContact)); - // 2nd row - pos_x = pos_x0; - pos_y = pos(1)+scale_(2.8); - svg.draw_legend(Point(pos_x, pos_y), "raft interface" , support_surface_type_to_color_name(PrintObjectSupportMaterial::sltRaftInterface)); - pos_x += step_x; - svg.draw_legend(Point(pos_x, pos_y), "raft base" , support_surface_type_to_color_name(PrintObjectSupportMaterial::sltRaftBase)); - pos_x += step_x; - svg.draw_legend(Point(pos_x, pos_y), "unknown" , support_surface_type_to_color_name(PrintObjectSupportMaterial::sltUnknown)); - pos_x += step_x; - svg.draw_legend(Point(pos_x, pos_y), "intermediate" , support_surface_type_to_color_name(PrintObjectSupportMaterial::sltIntermediate)); -} - -void export_print_z_polygons_to_svg(const char *path, PrintObjectSupportMaterial::MyLayer ** const layers, size_t n_layers) -{ - BoundingBox bbox; - for (int i = 0; i < n_layers; ++ i) - bbox.merge(get_extents(layers[i]->polygons)); - Point legend_size = export_support_surface_type_legend_to_svg_box_size(); - Point legend_pos(bbox.min(0), bbox.max(1)); - bbox.merge(Point(std::max(bbox.min(0) + legend_size(0), bbox.max(0)), bbox.max(1) + legend_size(1))); - SVG svg(path, bbox); - const float transparency = 0.5f; - for (int i = 0; i < n_layers; ++ i) - svg.draw(union_ex(layers[i]->polygons), support_surface_type_to_color_name(layers[i]->layer_type), transparency); - for (int i = 0; i < n_layers; ++ i) - svg.draw(to_polylines(layers[i]->polygons), support_surface_type_to_color_name(layers[i]->layer_type)); - export_support_surface_type_legend_to_svg(svg, legend_pos); - svg.Close(); -} - -void export_print_z_polygons_and_extrusions_to_svg( - const char *path, - PrintObjectSupportMaterial::MyLayer ** const layers, - size_t n_layers, - SupportLayer &support_layer) -{ - BoundingBox bbox; - for (int i = 0; i < n_layers; ++ i) - bbox.merge(get_extents(layers[i]->polygons)); - Point legend_size = export_support_surface_type_legend_to_svg_box_size(); - Point legend_pos(bbox.min(0), bbox.max(1)); - bbox.merge(Point(std::max(bbox.min(0) + legend_size(0), bbox.max(0)), bbox.max(1) + legend_size(1))); - SVG svg(path, bbox); - const float transparency = 0.5f; - for (int i = 0; i < n_layers; ++ i) - svg.draw(union_ex(layers[i]->polygons), support_surface_type_to_color_name(layers[i]->layer_type), transparency); - for (int i = 0; i < n_layers; ++ i) - svg.draw(to_polylines(layers[i]->polygons), support_surface_type_to_color_name(layers[i]->layer_type)); - - Polygons polygons_support, polygons_interface; - support_layer.support_fills.polygons_covered_by_width(polygons_support, float(SCALED_EPSILON)); -// support_layer.support_interface_fills.polygons_covered_by_width(polygons_interface, SCALED_EPSILON); - svg.draw(union_ex(polygons_support), "brown"); - svg.draw(union_ex(polygons_interface), "black"); - - export_support_surface_type_legend_to_svg(svg, legend_pos); - svg.Close(); -} -#endif /* SLIC3R_DEBUG */ - -#ifdef SUPPORT_USE_AGG_RASTERIZER -static std::vector rasterize_polygons(const Vec2i32 &grid_size, const double pixel_size, const Point &left_bottom, const Polygons &polygons) -{ - std::vector data(grid_size.x() * grid_size.y()); - agg::rendering_buffer rendering_buffer(data.data(), unsigned(grid_size.x()), unsigned(grid_size.y()), grid_size.x()); - agg::pixfmt_gray8 pixel_renderer(rendering_buffer); - agg::renderer_base raw_renderer(pixel_renderer); - agg::renderer_scanline_aa_solid> renderer(raw_renderer); - - renderer.color(agg::pixfmt_gray8::color_type(255)); - raw_renderer.clear(agg::pixfmt_gray8::color_type(0)); - - agg::scanline_p8 scanline; - agg::rasterizer_scanline_aa<> rasterizer; - - auto convert_pt = [left_bottom, pixel_size](const Point &pt) { - return Vec2d((pt.x() - left_bottom.x()) / pixel_size, (pt.y() - left_bottom.y()) / pixel_size); - }; - rasterizer.reset(); - for (const Polygon &polygon : polygons) { - agg::path_storage path; - auto it = polygon.points.begin(); - Vec2d pt_front = convert_pt(*it); - path.move_to(pt_front.x(), pt_front.y()); - while (++ it != polygon.points.end()) { - Vec2d pt = convert_pt(*it); - path.line_to(pt.x(), pt.y()); - } - path.line_to(pt_front.x(), pt_front.y()); - rasterizer.add_path(std::move(path)); - } - agg::render_scanlines(rasterizer, scanline, renderer); - return data; -} -// Grid has to have the boundary pixels unset. -static Polygons contours_simplified(const Vec2i32 &grid_size, const double pixel_size, Point left_bottom, const std::vector &grid, coord_t offset, bool fill_holes) -{ - assert(std::abs(2 * offset) < pixel_size - 10); - - // Fill in empty cells, which have a left / right neighbor filled. - // Fill in empty cells, which have the top / bottom neighbor filled. - std::vector cell_inside_data; - const std::vector &cell_inside = fill_holes ? cell_inside_data : grid; - if (fill_holes) { - cell_inside_data = grid; - for (int r = 1; r + 1 < grid_size.y(); ++ r) { - for (int c = 1; c + 1 < grid_size.x(); ++ c) { - int addr = r * grid_size.x() + c; - if ((grid[addr - 1] != 0 && grid[addr + 1] != 0) || - (grid[addr - grid_size.x()] != 0 && grid[addr + grid_size.x()] != 0)) - cell_inside_data[addr] = true; - } - } - } - - // 1) Collect the lines. - std::vector lines; - std::vector> start_point_to_line_idx; - for (int r = 1; r < grid_size.y(); ++ r) { - for (int c = 1; c < grid_size.x(); ++ c) { - int addr = r * grid_size.x() + c; - bool left = cell_inside[addr - 1] != 0; - bool top = cell_inside[addr - grid_size.x()] != 0; - bool current = cell_inside[addr] != 0; - if (left != current) { - lines.push_back( - left ? - Line(Point(c, r+1), Point(c, r )) : - Line(Point(c, r ), Point(c, r+1))); - start_point_to_line_idx.emplace_back(lines.back().a, int(lines.size()) - 1); - } - if (top != current) { - lines.push_back( - top ? - Line(Point(c , r), Point(c+1, r)) : - Line(Point(c+1, r), Point(c , r))); - start_point_to_line_idx.emplace_back(lines.back().a, int(lines.size()) - 1); - } - } - } - std::sort(start_point_to_line_idx.begin(), start_point_to_line_idx.end(), [](const auto &l, const auto &r){ return l.first < r.first; }); - - // 2) Chain the lines. - std::vector line_processed(lines.size(), false); - Polygons out; - for (int i_candidate = 0; i_candidate < int(lines.size()); ++ i_candidate) { - if (line_processed[i_candidate]) - continue; - Polygon poly; - line_processed[i_candidate] = true; - poly.points.push_back(lines[i_candidate].b); - int i_line_current = i_candidate; - for (;;) { - auto line_range = std::equal_range(std::begin(start_point_to_line_idx), std::end(start_point_to_line_idx), - std::make_pair(lines[i_line_current].b, 0), [](const auto& l, const auto& r) { return l.first < r.first; }); - // The interval has to be non empty, there shall be at least one line continuing the current one. - assert(line_range.first != line_range.second); - int i_next = -1; - for (auto it = line_range.first; it != line_range.second; ++ it) { - if (it->second == i_candidate) { - // closing the loop. - goto end_of_poly; - } - if (line_processed[it->second]) - continue; - if (i_next == -1) { - i_next = it->second; - } else { - // This is a corner, where two lines meet exactly. Pick the line, which encloses a smallest angle with - // the current edge. - const Line &line_current = lines[i_line_current]; - const Line &line_next = lines[it->second]; - const Vector v1 = line_current.vector(); - const Vector v2 = line_next.vector(); - int64_t cross = int64_t(v1(0)) * int64_t(v2(1)) - int64_t(v2(0)) * int64_t(v1(1)); - if (cross > 0) { - // This has to be a convex right angle. There is no better next line. - i_next = it->second; - break; - } - } - } - line_processed[i_next] = true; - i_line_current = i_next; - poly.points.push_back(lines[i_line_current].b); - } - end_of_poly: - out.push_back(std::move(poly)); - } - - // 3) Scale the polygons back into world, shrink slightly and remove collinear points. - for (Polygon &poly : out) { - for (Point &p : poly.points) { -#if 0 - p.x() = (p.x() + 1) * pixel_size + left_bottom.x(); - p.y() = (p.y() + 1) * pixel_size + left_bottom.y(); -#else - p *= pixel_size; - p += left_bottom; -#endif - } - // Shrink the contour slightly, so if the same contour gets discretized and simplified again, one will get the same result. - // Remove collinear points. - Points pts; - pts.reserve(poly.points.size()); - for (size_t j = 0; j < poly.points.size(); ++ j) { - size_t j0 = (j == 0) ? poly.points.size() - 1 : j - 1; - size_t j2 = (j + 1 == poly.points.size()) ? 0 : j + 1; - Point v = poly.points[j2] - poly.points[j0]; - if (v(0) != 0 && v(1) != 0) { - // This is a corner point. Copy it to the output contour. - Point p = poly.points[j]; - p(1) += (v(0) < 0) ? - offset : offset; - p(0) += (v(1) > 0) ? - offset : offset; - pts.push_back(p); - } - } - poly.points = std::move(pts); - } - return out; -} -#endif // SUPPORT_USE_AGG_RASTERIZER - -static std::string get_svg_filename(std::string layer_nr_or_z, std::string tag = "bbl_ts") -{ - static bool rand_init = false; - - if (!rand_init) { - srand(time(NULL)); - rand_init = true; - } - - int rand_num = rand() % 1000000; - //makedir("./SVG"); - std::string prefix = "./SVG/"; - std::string suffix = ".svg"; - return prefix + tag + "_" + layer_nr_or_z /*+ "_" + std::to_string(rand_num)*/ + suffix; -} - -PrintObjectSupportMaterial::PrintObjectSupportMaterial(const PrintObject *object, const SlicingParameters &slicing_params) : - m_object (object), - m_print_config (&object->print()->config()), - m_object_config (&object->config()), - m_slicing_params (slicing_params) -{ - m_support_params.first_layer_flow = support_material_1st_layer_flow(object, float(slicing_params.first_print_layer_height)); - m_support_params.support_material_flow = support_material_flow(object, float(slicing_params.layer_height)); - m_support_params.support_material_interface_flow = support_material_interface_flow(object, float(slicing_params.layer_height)); - m_support_params.support_layer_height_min = 0.01; - - // Calculate a minimum support layer height as a minimum over all extruders, but not smaller than 10um. - m_support_params.support_layer_height_min = 1000000.; - for (auto lh : m_print_config->min_layer_height.values) - m_support_params.support_layer_height_min = std::min(m_support_params.support_layer_height_min, std::max(0.01, lh)); - for (auto layer : m_object->layers()) - m_support_params.support_layer_height_min = std::min(m_support_params.support_layer_height_min, std::max(0.01, layer->height)); - - if (m_object_config->support_interface_top_layers.value == 0) { - // No interface layers allowed, print everything with the base support pattern. - m_support_params.support_material_interface_flow = m_support_params.support_material_flow; - } - - // Evaluate the XY gap between the object outer perimeters and the support structures. - // Evaluate the XY gap between the object outer perimeters and the support structures. - coordf_t external_perimeter_width = 0.; - coordf_t bridge_flow = 0; - for (size_t region_id = 0; region_id < object->num_printing_regions(); ++ region_id) { - const PrintRegion ®ion = object->printing_region(region_id); - external_perimeter_width = std::max(external_perimeter_width, coordf_t(region.flow(*object, frExternalPerimeter, slicing_params.layer_height).width())); - bridge_flow += region.config().bridge_flow; - } - m_support_params.gap_xy = m_object_config->support_object_xy_distance.value; - bridge_flow /= object->num_printing_regions(); - - m_support_params.support_material_bottom_interface_flow = m_slicing_params.soluble_interface || ! m_object_config->thick_bridges ? - m_support_params.support_material_interface_flow.with_flow_ratio(bridge_flow) : - Flow::bridging_flow(bridge_flow * m_support_params.support_material_interface_flow.nozzle_diameter(), m_support_params.support_material_interface_flow.nozzle_diameter()); - - m_support_params.can_merge_support_regions = m_object_config->support_filament.value == m_object_config->support_interface_filament.value; - if (!m_support_params.can_merge_support_regions && (m_object_config->support_filament.value == 0 || m_object_config->support_interface_filament.value == 0)) { - // One of the support extruders is of "don't care" type. - auto object_extruders = m_object->object_extruders(); - if (object_extruders.size() == 1 && - *object_extruders.begin() == std::max(m_object_config->support_filament.value, m_object_config->support_interface_filament.value)) - // Object is printed with the same extruder as the support. - m_support_params.can_merge_support_regions = true; - } - - - m_support_params.base_angle = Geometry::deg2rad(float(m_object_config->support_angle.value)); - m_support_params.interface_angle = Geometry::deg2rad(float(m_object_config->support_angle.value + 90.)); - m_support_params.interface_spacing = m_object_config->support_interface_spacing.value + m_support_params.support_material_interface_flow.spacing(); - m_support_params.interface_density = std::min(1., m_support_params.support_material_interface_flow.spacing() / m_support_params.interface_spacing); - m_support_params.support_spacing = m_object_config->support_base_pattern_spacing.value + m_support_params.support_material_flow.spacing(); - m_support_params.support_density = std::min(1., m_support_params.support_material_flow.spacing() / m_support_params.support_spacing); - if (m_object_config->support_interface_top_layers.value == 0) { - // No interface layers allowed, print everything with the base support pattern. - m_support_params.interface_spacing = m_support_params.support_spacing; - m_support_params.interface_density = m_support_params.support_density; - } - - SupportMaterialPattern support_pattern = m_object_config->support_base_pattern; - m_support_params.with_sheath = support_with_sheath; - m_support_params.base_fill_pattern = - support_pattern == smpHoneycomb ? ipHoneycomb : - m_support_params.support_density > 0.95 || m_support_params.with_sheath ? ipRectilinear : ipSupportBase; - m_support_params.interface_fill_pattern = (m_support_params.interface_density > 0.95 ? ipRectilinear : ipSupportBase); - if (m_object_config->support_interface_pattern == smipGrid) - m_support_params.contact_fill_pattern = ipGrid; - else if (m_object_config->support_interface_pattern == smipRectilinearInterlaced) - m_support_params.contact_fill_pattern = ipRectilinear; - else - m_support_params.contact_fill_pattern = - (m_object_config->support_interface_pattern == smipAuto && m_slicing_params.soluble_interface) || - m_object_config->support_interface_pattern == smipConcentric ? - ipConcentric : - (m_support_params.interface_density > 0.95 ? ipRectilinear : ipSupportBase); -} - -// Using the std::deque as an allocator. -inline PrintObjectSupportMaterial::MyLayer& layer_allocate( - std::deque &layer_storage, - PrintObjectSupportMaterial::SupporLayerType layer_type) -{ - layer_storage.push_back(PrintObjectSupportMaterial::MyLayer()); - layer_storage.back().layer_type = layer_type; - return layer_storage.back(); -} - -inline PrintObjectSupportMaterial::MyLayer& layer_allocate( - std::deque &layer_storage, - tbb::spin_mutex &layer_storage_mutex, - PrintObjectSupportMaterial::SupporLayerType layer_type) -{ - layer_storage_mutex.lock(); - layer_storage.push_back(PrintObjectSupportMaterial::MyLayer()); - PrintObjectSupportMaterial::MyLayer *layer_new = &layer_storage.back(); - layer_storage_mutex.unlock(); - layer_new->layer_type = layer_type; - return *layer_new; -} - -inline void layers_append(PrintObjectSupportMaterial::MyLayersPtr &dst, const PrintObjectSupportMaterial::MyLayersPtr &src) -{ - dst.insert(dst.end(), src.begin(), src.end()); -} - -// Support layer that is covered by some form of dense interface. -static constexpr const std::initializer_list support_types_interface { - PrintObjectSupportMaterial::sltRaftInterface, PrintObjectSupportMaterial::sltBottomContact, PrintObjectSupportMaterial::sltBottomInterface, PrintObjectSupportMaterial::sltTopContact, PrintObjectSupportMaterial::sltTopInterface -}; - -void PrintObjectSupportMaterial::generate(PrintObject &object) -{ - BOOST_LOG_TRIVIAL(info) << "Support generator - Start"; - - coordf_t max_object_layer_height = 0.; - for (size_t i = 0; i < object.layer_count(); ++ i) - max_object_layer_height = std::max(max_object_layer_height, object.layers()[i]->height); - - // Layer instances will be allocated by std::deque and they will be kept until the end of this function call. - // The layers will be referenced by various LayersPtr (of type std::vector) - MyLayerStorage layer_storage; - - BOOST_LOG_TRIVIAL(info) << "Support generator - Creating top contacts"; - - // Per object layer projection of the object below the layer into print bed. - std::vector buildplate_covered = this->buildplate_covered(object); - - // Determine the top contact surfaces of the support, defined as: - // contact = overhangs - clearance + margin - // This method is responsible for identifying what contact surfaces - // should the support material expose to the object in order to guarantee - // that it will be effective, regardless of how it's built below. - // If raft is to be generated, the 1st top_contact layer will contain the 1st object layer silhouette without holes. - MyLayersPtr top_contacts = this->top_contact_layers(object, buildplate_covered, layer_storage); - if (top_contacts.empty()) - // Nothing is supported, no supports are generated. - return; - - if (object.print()->canceled()) - return; - -#ifdef SLIC3R_DEBUG - static int iRun = 0; - iRun ++; - for (const MyLayer *layer : top_contacts) - Slic3r::SVG::export_expolygons( - debug_out_path("support-top-contacts-%d-%lf.svg", iRun, layer->print_z), - union_ex(layer->polygons)); -#endif /* SLIC3R_DEBUG */ - - BOOST_LOG_TRIVIAL(info) << "Support generator - Creating bottom contacts"; - - // Determine the bottom contact surfaces of the supports over the top surfaces of the object. - // Depending on whether the support is soluble or not, the contact layer thickness is decided. - // layer_support_areas contains the per object layer support areas. These per object layer support areas - // may get merged and trimmed by this->generate_base_layers() if the support layers are not synchronized with object layers. - std::vector layer_support_areas; - MyLayersPtr bottom_contacts = this->bottom_contact_layers_and_layer_support_areas( - object, top_contacts, buildplate_covered, - layer_storage, layer_support_areas); - - if (object.print()->canceled()) - return; - -#ifdef SLIC3R_DEBUG - for (size_t layer_id = 0; layer_id < object.layers().size(); ++ layer_id) - Slic3r::SVG::export_expolygons( - debug_out_path("support-areas-%d-%lf.svg", iRun, object.layers()[layer_id]->print_z), - union_ex(layer_support_areas[layer_id])); -#endif /* SLIC3R_DEBUG */ - - BOOST_LOG_TRIVIAL(info) << "Support generator - Creating intermediate layers - indices"; - - // Allocate empty layers between the top / bottom support contact layers - // as placeholders for the base and intermediate support layers. - // The layers may or may not be synchronized with the object layers, depending on the configuration. - // For example, a single nozzle multi material printing will need to generate a waste tower, which in turn - // wastes less material, if there are as little tool changes as possible. - MyLayersPtr intermediate_layers = this->raft_and_intermediate_support_layers( - object, bottom_contacts, top_contacts, layer_storage); - - this->trim_support_layers_by_object(object, top_contacts, m_slicing_params.gap_support_object, m_slicing_params.gap_object_support, m_support_params.gap_xy); - -#ifdef SLIC3R_DEBUG - for (const MyLayer *layer : top_contacts) - Slic3r::SVG::export_expolygons( - debug_out_path("support-top-contacts-trimmed-by-object-%d-%lf.svg", iRun, layer->print_z), - union_ex(layer->polygons)); -#endif - - BOOST_LOG_TRIVIAL(info) << "Support generator - Creating base layers"; - - // Fill in intermediate layers between the top / bottom support contact layers, trim them by the object. - this->generate_base_layers(object, bottom_contacts, top_contacts, intermediate_layers, layer_support_areas); - -#ifdef SLIC3R_DEBUG - for (MyLayersPtr::const_iterator it = intermediate_layers.begin(); it != intermediate_layers.end(); ++ it) - Slic3r::SVG::export_expolygons( - debug_out_path("support-base-layers-%d-%lf.svg", iRun, (*it)->print_z), - union_ex((*it)->polygons)); -#endif /* SLIC3R_DEBUG */ - - BOOST_LOG_TRIVIAL(info) << "Support generator - Trimming top contacts by bottom contacts"; - - // Because the top and bottom contacts are thick slabs, they may overlap causing over extrusion - // and unwanted strong bonds to the object. - // Rather trim the top contacts by their overlapping bottom contacts to leave a gap instead of over extruding - // top contacts over the bottom contacts. - this->trim_top_contacts_by_bottom_contacts(object, bottom_contacts, top_contacts); - - - BOOST_LOG_TRIVIAL(info) << "Support generator - Creating interfaces"; - - // Propagate top / bottom contact layers to generate interface layers - // and base interface layers (for soluble interface / non souble base only) - auto [interface_layers, base_interface_layers] = this->generate_interface_layers(bottom_contacts, top_contacts, intermediate_layers, layer_storage); - - BOOST_LOG_TRIVIAL(info) << "Support generator - Creating raft"; - - // If raft is to be generated, the 1st top_contact layer will contain the 1st object layer silhouette with holes filled. - // There is also a 1st intermediate layer containing bases of support columns. - // Inflate the bases of the support columns and create the raft base under the object. - MyLayersPtr raft_layers = this->generate_raft_base(object, top_contacts, interface_layers, base_interface_layers, intermediate_layers, layer_storage); - - if (object.print()->canceled()) - return; - -#ifdef SLIC3R_DEBUG - for (const MyLayer *l : interface_layers) - Slic3r::SVG::export_expolygons( - debug_out_path("support-interface-layers-%d-%lf.svg", iRun, l->print_z), - union_ex(l->polygons)); - for (const MyLayer *l : base_interface_layers) - Slic3r::SVG::export_expolygons( - debug_out_path("support-base-interface-layers-%d-%lf.svg", iRun, l->print_z), - union_ex(l->polygons)); -#endif // SLIC3R_DEBUG - -/* - // Clip with the pillars. - if (! shape.empty()) { - this->clip_with_shape(interface, shape); - this->clip_with_shape(base, shape); - } -*/ - - BOOST_LOG_TRIVIAL(info) << "Support generator - Creating layers"; - -// For debugging purposes, one may want to show only some of the support extrusions. -// raft_layers.clear(); -// bottom_contacts.clear(); -// top_contacts.clear(); -// intermediate_layers.clear(); -// interface_layers.clear(); - - // Install support layers into the object. - // A support layer installed on a PrintObject has a unique print_z. - MyLayersPtr layers_sorted; - layers_sorted.reserve(raft_layers.size() + bottom_contacts.size() + top_contacts.size() + intermediate_layers.size() + interface_layers.size() + base_interface_layers.size()); - layers_append(layers_sorted, raft_layers); - layers_append(layers_sorted, bottom_contacts); - layers_append(layers_sorted, top_contacts); - layers_append(layers_sorted, intermediate_layers); - layers_append(layers_sorted, interface_layers); - layers_append(layers_sorted, base_interface_layers); - // Sort the layers lexicographically by a raising print_z and a decreasing height. - std::sort(layers_sorted.begin(), layers_sorted.end(), [](auto *l1, auto *l2) { return *l1 < *l2; }); - - // BBS: MusangKing - erase mini layer heights (< 0.08mm) arised by top/bottom_z_distance & top_contacts under variable layer height - if (this->synchronize_layers() && !object.slicing_parameters().soluble_interface) { - auto thres = m_support_params.support_layer_height_min - EPSILON; - for (size_t i = 1; i < layers_sorted.size() - 1; ++i) { - auto& lowr = layers_sorted[i - 1]; - auto& curr = layers_sorted[i]; - auto& higr = layers_sorted[i + 1]; - // "Rounding" suspicious top/bottom contacts - if (curr->layer_type == sltTopContact || curr->layer_type == sltBottomContact) { - // Check adjacent-layer print_z diffs - coordf_t height_low = curr->print_z - lowr->print_z; - coordf_t height_high = higr->print_z - curr->print_z; - if (height_low < thres || height_high < thres) { - // Mark to-be-deleted layer as Unknown type - curr->layer_type = sltUnknown; - } - } - } - // Retains the order - layers_sorted.erase(std::remove_if(layers_sorted.begin(), layers_sorted.end(), [](MyLayer* l) {return l->layer_type == sltUnknown; }), layers_sorted.end()); - } - - int layer_id = 0; - int layer_id_interface = 0; - assert(object.support_layers().empty()); - for (size_t i = 0; i < layers_sorted.size();) { - // Find the last layer with roughly the same print_z, find the minimum layer height of all. - // Due to the floating point inaccuracies, the print_z may not be the same even if in theory they should. - size_t j = i + 1; - coordf_t zmax = layers_sorted[i]->print_z + EPSILON; - for (; j < layers_sorted.size() && layers_sorted[j]->print_z <= zmax; ++j) ; - // Assign an average print_z to the set of layers with nearly equal print_z. - coordf_t zavg = 0.5 * (layers_sorted[i]->print_z + layers_sorted[j - 1]->print_z); - coordf_t height_min = layers_sorted[i]->height; - bool empty = true; - // For snug supports, layers where the direction of the support interface shall change are accounted for. - size_t num_interfaces = 0; - size_t num_top_contacts = 0; - double top_contact_bottom_z = 0; - for (size_t u = i; u < j; ++u) { - MyLayer &layer = *layers_sorted[u]; - if (! layer.polygons.empty()) { - empty = false; - num_interfaces += one_of(layer.layer_type, support_types_interface); - if (layer.layer_type == sltTopContact) { - ++ num_top_contacts; - assert(num_top_contacts <= 1); - // All top contact layers sharing this print_z shall also share bottom_z. - //assert(num_top_contacts == 1 || (top_contact_bottom_z - layer.bottom_z) < EPSILON); - top_contact_bottom_z = layer.bottom_z; - } - } - layer.print_z = zavg; - height_min = std::min(height_min, layer.height); - } - if (! empty) { - // Here the upper_layer and lower_layer pointers are left to null at the support layers, - // as they are never used. These pointers are candidates for removal. - bool this_layer_contacts_only = num_top_contacts > 0 && num_top_contacts == num_interfaces; - size_t this_layer_id_interface = layer_id_interface; - if (this_layer_contacts_only) { - // Find a supporting layer for its interface ID. - for (auto it = object.support_layers().rbegin(); it != object.support_layers().rend(); ++ it) - if (const SupportLayer &other_layer = **it; std::abs(other_layer.print_z - top_contact_bottom_z) < EPSILON) { - // other_layer supports this top contact layer. Assign a different support interface direction to this layer - // from the layer that supports it. - this_layer_id_interface = other_layer.interface_id() + 1; - } - } - object.add_support_layer(layer_id ++, this_layer_id_interface, height_min, zavg); - if (num_interfaces && ! this_layer_contacts_only) - ++ layer_id_interface; - } - i = j; - } - - BOOST_LOG_TRIVIAL(info) << "Support generator - Generating tool paths"; - -#if 0 // #ifdef SLIC3R_DEBUG - { - size_t layer_id = 0; - for (int i = 0; i < int(layers_sorted.size());) { - // Find the last layer with roughly the same print_z, find the minimum layer height of all. - // Due to the floating point inaccuracies, the print_z may not be the same even if in theory they should. - int j = i + 1; - coordf_t zmax = layers_sorted[i]->print_z + EPSILON; - bool empty = true; - for (; j < layers_sorted.size() && layers_sorted[j]->print_z <= zmax; ++j) - if (!layers_sorted[j]->polygons.empty()) - empty = false; - if (!empty) { - export_print_z_polygons_to_svg( - debug_out_path("support-%d-%lf-before.svg", iRun, layers_sorted[i]->print_z).c_str(), - layers_sorted.data() + i, j - i); - export_print_z_polygons_and_extrusions_to_svg( - debug_out_path("support-w-fills-%d-%lf-before.svg", iRun, layers_sorted[i]->print_z).c_str(), - layers_sorted.data() + i, j - i, - *object.support_layers()[layer_id]); - ++layer_id; - } - i = j; - } - } -#endif /* SLIC3R_DEBUG */ - -#if 0 // #ifdef SLIC3R_DEBUG - // check bounds - std::ofstream out; - out.open("./SVG/ns_support_layers.txt"); - if (out.is_open()) { - out << "### Support Layers ###" << std::endl; - for (auto& i : object.support_layers()) { - out << i->print_z << std::endl; - } - } -#endif /* SLIC3R_DEBUG */ - - // Generate the actual toolpaths and save them into each layer. - this->generate_toolpaths(object.support_layers(), raft_layers, bottom_contacts, top_contacts, intermediate_layers, interface_layers, base_interface_layers); - -#ifdef SLIC3R_DEBUG - { - size_t layer_id = 0; - for (int i = 0; i < int(layers_sorted.size());) { - // Find the last layer with roughly the same print_z, find the minimum layer height of all. - // Due to the floating point inaccuracies, the print_z may not be the same even if in theory they should. - int j = i + 1; - coordf_t zmax = layers_sorted[i]->print_z + EPSILON; - bool empty = true; - for (; j < layers_sorted.size() && layers_sorted[j]->print_z <= zmax; ++j) - if (! layers_sorted[j]->polygons.empty()) - empty = false; - if (! empty) { - export_print_z_polygons_to_svg( - debug_out_path("support-%d-%lf.svg", iRun, layers_sorted[i]->print_z).c_str(), - layers_sorted.data() + i, j - i); - export_print_z_polygons_and_extrusions_to_svg( - debug_out_path("support-w-fills-%d-%lf.svg", iRun, layers_sorted[i]->print_z).c_str(), - layers_sorted.data() + i, j - i, - *object.support_layers()[layer_id]); - ++layer_id; - } - i = j; - } - } -#endif /* SLIC3R_DEBUG */ - - BOOST_LOG_TRIVIAL(info) << "Support generator - End"; -} - -// Collect all polygons of all regions in a layer with a given surface type. -Polygons collect_region_slices_by_type(const Layer &layer, SurfaceType surface_type) -{ - // 1) Count the new polygons first. - size_t n_polygons_new = 0; - for (const LayerRegion *region : layer.regions()) - for (const Surface &surface : region->slices.surfaces) - if (surface.surface_type == surface_type) - n_polygons_new += surface.expolygon.holes.size() + 1; - // 2) Collect the new polygons. - Polygons out; - out.reserve(n_polygons_new); - for (const LayerRegion *region : layer.regions()) - for (const Surface &surface : region->slices.surfaces) - if (surface.surface_type == surface_type) - polygons_append(out, surface.expolygon); - return out; -} - -// Collect outer contours of all slices of this layer. -// This is useful for calculating the support base with holes filled. -Polygons collect_slices_outer(const Layer &layer) -{ - Polygons out; - out.reserve(out.size() + layer.lslices.size()); - for (const ExPolygon &expoly : layer.lslices) - out.emplace_back(expoly.contour); - return out; -} - -struct SupportGridParams { - SupportGridParams(const PrintObjectConfig &object_config, const Flow &support_material_flow) : - style(object_config.support_style.value), - grid_resolution(object_config.support_base_pattern_spacing.value + support_material_flow.spacing()), - support_angle(Geometry::deg2rad(object_config.support_angle.value)), - extrusion_width(support_material_flow.spacing()), - //support_closing_radius(object_config.support_closing_radius.value), - support_closing_radius(2.0), - expansion_to_slice(coord_t(support_material_flow.scaled_spacing() / 2 + 5)), - expansion_to_propagate(-3) {} - - SupportMaterialStyle style; - double grid_resolution; - double support_angle; - double extrusion_width; - double support_closing_radius; - coord_t expansion_to_slice; - coord_t expansion_to_propagate; -}; - -class SupportGridPattern -{ -public: - SupportGridPattern( - // Support islands, to be stretched into a grid. Already trimmed with min(lower_layer_offset, m_gap_xy) - const Polygons *support_polygons, - // Trimming polygons, to trim the stretched support islands. support_polygons were already trimmed with trimming_polygons. - const Polygons *trimming_polygons, - const SupportGridParams ¶ms) : - m_style(params.style), - m_support_polygons(support_polygons), m_trimming_polygons(trimming_polygons), - m_support_spacing(params.grid_resolution), m_support_angle(params.support_angle), - m_extrusion_width(params.extrusion_width), - m_support_material_closing_radius(params.support_closing_radius) - { - if (m_style != smsSnug) m_style = smsGrid; - switch (m_style) { - case smsGrid: - { - // Prepare the grid data, it will be reused when extracting support structures. - if (m_support_angle != 0.) { - // Create a copy of the rotated contours. - m_support_polygons_rotated = *support_polygons; - m_trimming_polygons_rotated = *trimming_polygons; - m_support_polygons = &m_support_polygons_rotated; - m_trimming_polygons = &m_trimming_polygons_rotated; - polygons_rotate(m_support_polygons_rotated, - params.support_angle); - polygons_rotate(m_trimming_polygons_rotated, - params.support_angle); - } - - // Resolution of the sparse support grid. - coord_t grid_resolution = coord_t(scale_(m_support_spacing)); - BoundingBox bbox = get_extents(*m_support_polygons); - bbox.offset(20); - // Align the bounding box with the sparse support grid. - bbox.align_to_grid(grid_resolution); - - #ifdef SUPPORT_USE_AGG_RASTERIZER - m_bbox = bbox; - // Oversample the grid to avoid leaking of supports through or around the object walls. - int extrusion_width_scaled = scale_(params.extrusion_width); - int oversampling = std::clamp(int(scale_(m_support_spacing) / (extrusion_width_scaled + 100)), 1, 8); - m_pixel_size = std::max(extrusion_width_scaled + 21, scale_(m_support_spacing / oversampling)); - // Add one empty column / row boundaries. - m_bbox.offset(m_pixel_size); - // Grid size fitting the support polygons plus one pixel boundary around the polygons. - Vec2i32 grid_size_raw(int(ceil((m_bbox.max.x() - m_bbox.min.x()) / m_pixel_size)), - int(ceil((m_bbox.max.y() - m_bbox.min.y()) / m_pixel_size))); - // Overlay macro blocks of (oversampling x oversampling) over the grid. - Vec2i32 grid_blocks((grid_size_raw.x() + oversampling - 1 - 2) / oversampling, - (grid_size_raw.y() + oversampling - 1 - 2) / oversampling); - // and resize the grid to fit the macro blocks + one pixel boundary. - m_grid_size = grid_blocks * oversampling + Vec2i32(2, 2); - assert(m_grid_size.x() >= grid_size_raw.x()); - assert(m_grid_size.y() >= grid_size_raw.y()); - m_grid2 = rasterize_polygons(m_grid_size, m_pixel_size, m_bbox.min, *m_support_polygons); - - seed_fill_block(m_grid2, m_grid_size, - dilate_trimming_region(rasterize_polygons(m_grid_size, m_pixel_size, m_bbox.min, *m_trimming_polygons), m_grid_size), - grid_blocks, oversampling); - - #ifdef SLIC3R_DEBUG - { - static int irun; - Slic3r::png::write_gray_to_file_scaled(debug_out_path("support-rasterizer-%d.png", irun++), m_grid_size.x(), m_grid_size.y(), m_grid2.data(), 4); - } - #endif // SLIC3R_DEBUG - - #else // SUPPORT_USE_AGG_RASTERIZER - // Create an EdgeGrid, initialize it with projection, initialize signed distance field. - m_grid.set_bbox(bbox); - m_grid.create(*m_support_polygons, grid_resolution); - #if 0 - if (m_grid.has_intersecting_edges()) { - // EdgeGrid fails to produce valid signed distance function for self-intersecting polygons. - m_support_polygons_rotated = simplify_polygons(*m_support_polygons); - m_support_polygons = &m_support_polygons_rotated; - m_grid.set_bbox(bbox); - m_grid.create(*m_support_polygons, grid_resolution); - // assert(! m_grid.has_intersecting_edges()); - printf("SupportGridPattern: fixing polygons with intersection %s\n", - m_grid.has_intersecting_edges() ? "FAILED" : "SUCCEEDED"); - } - #endif - m_grid.calculate_sdf(); - #endif // SUPPORT_USE_AGG_RASTERIZER - break; - } - - case smsSnug: - default: - // nothing to prepare - break; - } - } - - // Extract polygons from the grid, offsetted by offset_in_grid, - // and trim the extracted polygons by trimming_polygons. - // Trimming by the trimming_polygons may split the extracted polygons into pieces. - // Remove all the pieces, which do not contain any of the island_samples. - Polygons extract_support(const coord_t offset_in_grid, bool fill_holes -#ifdef SLIC3R_DEBUG - , const char *step_name, int iRun, size_t layer_id, double print_z -#endif - ) - { - switch (m_style) { - case smsGrid: - { - #ifdef SUPPORT_USE_AGG_RASTERIZER - Polygons support_polygons_simplified = contours_simplified(m_grid_size, m_pixel_size, m_bbox.min, m_grid2, offset_in_grid, fill_holes); - #else // SUPPORT_USE_AGG_RASTERIZER - // Generate islands, so each island may be tested for overlap with island_samples. - assert(std::abs(2 * offset_in_grid) < m_grid.resolution()); - Polygons support_polygons_simplified = m_grid.contours_simplified(offset_in_grid, fill_holes); - #endif // SUPPORT_USE_AGG_RASTERIZER - - ExPolygons islands = diff_ex(support_polygons_simplified, *m_trimming_polygons); - - // Extract polygons, which contain some of the island_samples. - Polygons out; - - // Sample a single point per input support polygon, keep it as a reference to maintain corresponding - // polygons if ever these polygons get split into parts by the trimming polygons. - // As offset_in_grid may be negative, m_support_polygons may stick slightly outside of islands. - // Trim ti with islands. - Points samples = island_samples( - offset_in_grid > 0 ? - // Expanding, thus m_support_polygons are all inside islands. - union_ex(*m_support_polygons) : - // Shrinking, thus m_support_polygons may be trimmed a tiny bit by islands. - intersection_ex(*m_support_polygons, islands)); - - std::vector> samples_inside; - for (ExPolygon &island : islands) { - BoundingBox bbox = get_extents(island.contour); - // Samples are sorted lexicographically. - auto it_lower = std::lower_bound(samples.begin(), samples.end(), Point(bbox.min - Point(1, 1))); - auto it_upper = std::upper_bound(samples.begin(), samples.end(), Point(bbox.max + Point(1, 1))); - samples_inside.clear(); - for (auto it = it_lower; it != it_upper; ++ it) - if (bbox.contains(*it)) - samples_inside.push_back(std::make_pair(*it, false)); - if (! samples_inside.empty()) { - // For all samples_inside count the boundary crossing. - for (size_t i_contour = 0; i_contour <= island.holes.size(); ++ i_contour) { - Polygon &contour = (i_contour == 0) ? island.contour : island.holes[i_contour - 1]; - Points::const_iterator i = contour.points.begin(); - Points::const_iterator j = contour.points.end() - 1; - for (; i != contour.points.end(); j = i ++) { - //FIXME this test is not numerically robust. Particularly, it does not handle horizontal segments at y == point(1) well. - // Does the ray with y == point(1) intersect this line segment? - for (auto &sample_inside : samples_inside) { - if (((*i)(1) > sample_inside.first(1)) != ((*j)(1) > sample_inside.first(1))) { - double x1 = (double)sample_inside.first(0); - double x2 = (double)(*i)(0) + (double)((*j)(0) - (*i)(0)) * (double)(sample_inside.first(1) - (*i)(1)) / (double)((*j)(1) - (*i)(1)); - if (x1 < x2) - sample_inside.second = !sample_inside.second; - } - } - } - } - // If any of the sample is inside this island, add this island to the output. - for (auto &sample_inside : samples_inside) - if (sample_inside.second) { - polygons_append(out, std::move(island)); - island.clear(); - break; - } - } - } - - #ifdef SLIC3R_DEBUG - BoundingBox bbox = get_extents(*m_trimming_polygons); - if (! islands.empty()) - bbox.merge(get_extents(islands)); - if (!out.empty()) - bbox.merge(get_extents(out)); - if (!support_polygons_simplified.empty()) - bbox.merge(get_extents(support_polygons_simplified)); - SVG svg(debug_out_path("extract_support_from_grid_trimmed-%s-%d-%d-%lf.svg", step_name, iRun, layer_id, print_z).c_str(), bbox); - if (svg.is_opened()) { - svg.draw(union_ex(support_polygons_simplified), "gray", 0.25f); - svg.draw(islands, "red", 0.5f); - svg.draw(union_ex(out), "green", 0.5f); - svg.draw(union_ex(*m_support_polygons), "blue", 0.5f); - svg.draw_outline(islands, "red", "red", scale_(0.05)); - svg.draw_outline(union_ex(out), "green", "green", scale_(0.05)); - svg.draw_outline(union_ex(*m_support_polygons), "blue", "blue", scale_(0.05)); - for (const Point& pt : samples) - svg.draw(pt, "black", coord_t(scale_(0.15))); - svg.Close(); - } - #endif /* SLIC3R_DEBUG */ - - if (m_support_angle != 0.) - polygons_rotate(out, m_support_angle); - return out; - } - case smsSnug: - // Merge the support polygons by applying morphological closing and inwards smoothing. - auto closing_distance = scaled(m_support_material_closing_radius); - auto smoothing_distance = scaled(m_extrusion_width); -#ifdef SLIC3R_DEBUG - SVG::export_expolygons(debug_out_path("extract_support_from_grid_trimmed-%s-%d-%d-%lf.svg", step_name, iRun, layer_id, print_z), - { { { diff_ex(expand(*m_support_polygons, closing_distance), closing(*m_support_polygons, closing_distance, SUPPORT_SURFACES_OFFSET_PARAMETERS)) }, { "closed", "blue", 0.5f } }, - { { union_ex(smooth_outward(closing(*m_support_polygons, closing_distance, SUPPORT_SURFACES_OFFSET_PARAMETERS), smoothing_distance)) }, { "regularized", "red", "black", "", scaled(0.1f), 0.5f } }, - { { union_ex(*m_support_polygons) }, { "src", "green", 0.5f } }, - }); -#endif /* SLIC3R_DEBUG */ - //FIXME do we want to trim with the object here? On one side the columns will be thinner, on the other side support interfaces may disappear for snug supports. - // return diff(smooth_outward(closing(*m_support_polygons, closing_distance, SUPPORT_SURFACES_OFFSET_PARAMETERS), smoothing_distance), *m_trimming_polygons); - return smooth_outward(closing(*m_support_polygons, closing_distance, SUPPORT_SURFACES_OFFSET_PARAMETERS), smoothing_distance); - } - assert(false); - return Polygons(); - } - -#if defined(SLIC3R_DEBUG) && ! defined(SUPPORT_USE_AGG_RASTERIZER) - void serialize(const std::string &path) - { - FILE *file = ::fopen(path.c_str(), "wb"); - ::fwrite(&m_support_spacing, 8, 1, file); - ::fwrite(&m_support_angle, 8, 1, file); - uint32_t n_polygons = m_support_polygons->size(); - ::fwrite(&n_polygons, 4, 1, file); - for (uint32_t i = 0; i < n_polygons; ++ i) { - const Polygon &poly = (*m_support_polygons)[i]; - uint32_t n_points = poly.size(); - ::fwrite(&n_points, 4, 1, file); - for (uint32_t j = 0; j < n_points; ++ j) { - const Point &pt = poly.points[j]; - ::fwrite(&pt.x(), sizeof(coord_t), 1, file); - ::fwrite(&pt.y(), sizeof(coord_t), 1, file); - } - } - n_polygons = m_trimming_polygons->size(); - ::fwrite(&n_polygons, 4, 1, file); - for (uint32_t i = 0; i < n_polygons; ++ i) { - const Polygon &poly = (*m_trimming_polygons)[i]; - uint32_t n_points = poly.size(); - ::fwrite(&n_points, 4, 1, file); - for (uint32_t j = 0; j < n_points; ++ j) { - const Point &pt = poly.points[j]; - ::fwrite(&pt.x(), sizeof(coord_t), 1, file); - ::fwrite(&pt.y(), sizeof(coord_t), 1, file); - } - } - ::fclose(file); - } - - static SupportGridPattern deserialize(const std::string &path, int which = -1) - { - SupportGridPattern out; - out.deserialize_(path, which); - return out; - } - - // Deserialization constructor - bool deserialize_(const std::string &path, int which = -1) - { - FILE *file = ::fopen(path.c_str(), "rb"); - if (file == nullptr) - return false; - - m_support_polygons = &m_support_polygons_deserialized; - m_trimming_polygons = &m_trimming_polygons_deserialized; - - ::fread(&m_support_spacing, 8, 1, file); - ::fread(&m_support_angle, 8, 1, file); - //FIXME - //m_support_spacing *= 0.01 / 2; - uint32_t n_polygons; - ::fread(&n_polygons, 4, 1, file); - m_support_polygons_deserialized.reserve(n_polygons); - int32_t scale = 1; - for (uint32_t i = 0; i < n_polygons; ++ i) { - Polygon poly; - uint32_t n_points; - ::fread(&n_points, 4, 1, file); - poly.points.reserve(n_points); - for (uint32_t j = 0; j < n_points; ++ j) { - coord_t x, y; - ::fread(&x, sizeof(coord_t), 1, file); - ::fread(&y, sizeof(coord_t), 1, file); - poly.points.emplace_back(Point(x * scale, y * scale)); - } - if (which == -1 || which == i) - m_support_polygons_deserialized.emplace_back(std::move(poly)); - printf("Polygon %d, area: %lf\n", i, area(poly.points)); - } - ::fread(&n_polygons, 4, 1, file); - m_trimming_polygons_deserialized.reserve(n_polygons); - for (uint32_t i = 0; i < n_polygons; ++ i) { - Polygon poly; - uint32_t n_points; - ::fread(&n_points, 4, 1, file); - poly.points.reserve(n_points); - for (uint32_t j = 0; j < n_points; ++ j) { - coord_t x, y; - ::fread(&x, sizeof(coord_t), 1, file); - ::fread(&y, sizeof(coord_t), 1, file); - poly.points.emplace_back(Point(x * scale, y * scale)); - } - m_trimming_polygons_deserialized.emplace_back(std::move(poly)); - } - ::fclose(file); - - m_support_polygons_deserialized = simplify_polygons(m_support_polygons_deserialized, false); - //m_support_polygons_deserialized = to_polygons(union_ex(m_support_polygons_deserialized, false)); - - // Create an EdgeGrid, initialize it with projection, initialize signed distance field. - coord_t grid_resolution = coord_t(scale_(m_support_spacing)); - BoundingBox bbox = get_extents(*m_support_polygons); - bbox.offset(20); - bbox.align_to_grid(grid_resolution); - m_grid.set_bbox(bbox); - m_grid.create(*m_support_polygons, grid_resolution); - m_grid.calculate_sdf(); - return true; - } - - const Polygons& support_polygons() const { return *m_support_polygons; } - const Polygons& trimming_polygons() const { return *m_trimming_polygons; } - const EdgeGrid::Grid& grid() const { return m_grid; } - -#endif // defined(SLIC3R_DEBUG) && ! defined(SUPPORT_USE_AGG_RASTERIZER) - -private: - SupportGridPattern() {} - SupportGridPattern& operator=(const SupportGridPattern &rhs); - -#ifdef SUPPORT_USE_AGG_RASTERIZER - // Dilate the trimming region (unmask the boundary pixels). - static std::vector dilate_trimming_region(const std::vector &trimming, const Vec2i32 &grid_size) - { - std::vector dilated(trimming.size(), 0); - for (int r = 1; r + 1 < grid_size.y(); ++ r) - for (int c = 1; c + 1 < grid_size.x(); ++ c) { - //int addr = c + r * m_grid_size.x(); - // 4-neighborhood is not sufficient. - // dilated[addr] = trimming[addr] != 0 && trimming[addr - 1] != 0 && trimming[addr + 1] != 0 && trimming[addr - m_grid_size.x()] != 0 && trimming[addr + m_grid_size.x()] != 0; - // 8-neighborhood - int addr = c + (r - 1) * grid_size.x(); - bool b = trimming[addr - 1] != 0 && trimming[addr] != 0 && trimming[addr + 1] != 0; - addr += grid_size.x(); - b = b && trimming[addr - 1] != 0 && trimming[addr] != 0 && trimming[addr + 1] != 0; - addr += grid_size.x(); - b = b && trimming[addr - 1] != 0 && trimming[addr] != 0 && trimming[addr + 1] != 0; - dilated[addr - grid_size.x()] = b; - } - return dilated; - } - - // Seed fill each of the (oversampling x oversampling) block up to the dilated trimming region. - static void seed_fill_block(std::vector &grid, Vec2i32 grid_size, const std::vector &trimming,const Vec2i32 &grid_blocks, int oversampling) - { - int size = oversampling; - int stride = grid_size.x(); - for (int block_r = 0; block_r < grid_blocks.y(); ++ block_r) - for (int block_c = 0; block_c < grid_blocks.x(); ++ block_c) { - // Propagate the support pixels over the macro cell up to the trimming mask. - int addr = block_c * size + 1 + (block_r * size + 1) * stride; - unsigned char *grid_data = grid.data() + addr; - const unsigned char *mask_data = trimming.data() + addr; - // Top to bottom propagation. - #define PROPAGATION_STEP(offset) \ - do { \ - int addr = r * stride + c; \ - int addr2 = addr + offset; \ - if (grid_data[addr2] && ! mask_data[addr] && ! mask_data[addr2]) \ - grid_data[addr] = 1; \ - } while (0); - for (int r = 0; r < size; ++ r) { - if (r > 0) - for (int c = 0; c < size; ++ c) - PROPAGATION_STEP(- stride); - for (int c = 1; c < size; ++ c) - PROPAGATION_STEP(- 1); - for (int c = size - 2; c >= 0; -- c) - PROPAGATION_STEP(+ 1); - } - // Bottom to top propagation. - for (int r = size - 2; r >= 0; -- r) { - for (int c = 0; c < size; ++ c) - PROPAGATION_STEP(+ stride); - for (int c = 1; c < size; ++ c) - PROPAGATION_STEP(- 1); - for (int c = size - 2; c >= 0; -- c) - PROPAGATION_STEP(+ 1); - } - #undef PROPAGATION_STEP - } - } -#endif // SUPPORT_USE_AGG_RASTERIZER - -#if 0 - // Get some internal point of an expolygon, to be used as a representative - // sample to test, whether this island is inside another island. - //FIXME this was quick, but not sufficiently robust. - static Point island_sample(const ExPolygon &expoly) - { - // Find the lowest point lexicographically. - const Point *pt_min = &expoly.contour.points.front(); - for (size_t i = 1; i < expoly.contour.points.size(); ++ i) - if (expoly.contour.points[i] < *pt_min) - pt_min = &expoly.contour.points[i]; - - // Lowest corner will always be convex, in worst case denegenerate with zero angle. - const Point &p1 = (pt_min == &expoly.contour.points.front()) ? expoly.contour.points.back() : *(pt_min - 1); - const Point &p2 = *pt_min; - const Point &p3 = (pt_min == &expoly.contour.points.back()) ? expoly.contour.points.front() : *(pt_min + 1); - - Vector v = (p3 - p2) + (p1 - p2); - double l2 = double(v(0))*double(v(0))+double(v(1))*double(v(1)); - if (l2 == 0.) - return p2; - double coef = 20. / sqrt(l2); - return Point(p2(0) + coef * v(0), p2(1) + coef * v(1)); - } -#endif - - // Sample one internal point per expolygon. - // FIXME this is quite an overkill to calculate a complete offset just to get a single point, but at least it is robust. - static Points island_samples(const ExPolygons &expolygons) - { - Points pts; - pts.reserve(expolygons.size()); - for (const ExPolygon &expoly : expolygons) - if (expoly.contour.points.size() > 2) { - #if 0 - pts.push_back(island_sample(expoly)); - #else - Polygons polygons = offset(expoly, - 20.f); - for (const Polygon &poly : polygons) - if (! poly.points.empty()) { - // Take a small fixed number of samples of this polygon for robustness. - int num_points = int(poly.points.size()); - int num_samples = std::min(num_points, 4); - int stride = num_points / num_samples; - for (int i = 0; i < num_points; i += stride) - pts.push_back(poly.points[i]); - break; - } - #endif - } - // Sort the points lexicographically, so a binary search could be used to locate points inside a bounding box. - std::sort(pts.begin(), pts.end()); - return pts; - } - - SupportMaterialStyle m_style; - const Polygons *m_support_polygons; - const Polygons *m_trimming_polygons; - Polygons m_support_polygons_rotated; - Polygons m_trimming_polygons_rotated; - // Angle in radians, by which the whole support is rotated. - coordf_t m_support_angle; - // X spacing of the support lines parallel with the Y axis. - coordf_t m_support_spacing; - coordf_t m_extrusion_width; - // For snug supports: Morphological closing of support areas. - coordf_t m_support_material_closing_radius; - -#ifdef SUPPORT_USE_AGG_RASTERIZER - Vec2i32 m_grid_size; - double m_pixel_size; - BoundingBox m_bbox; - std::vector m_grid2; -#else // SUPPORT_USE_AGG_RASTERIZER - Slic3r::EdgeGrid::Grid m_grid; -#endif // SUPPORT_USE_AGG_RASTERIZER - -#ifdef SLIC3R_DEBUG - // support for deserialization of m_support_polygons, m_trimming_polygons - Polygons m_support_polygons_deserialized; - Polygons m_trimming_polygons_deserialized; -#endif /* SLIC3R_DEBUG */ -}; - -namespace SupportMaterialInternal { - static inline bool has_bridging_perimeters(const ExtrusionLoop &loop) - { - for (const ExtrusionPath &ep : loop.paths) - if (ep.role() == erOverhangPerimeter && ! ep.polyline.empty()) - return int(ep.size()) >= (ep.is_closed() ? 3 : 2); - return false; - } - static bool has_bridging_perimeters(const ExtrusionEntityCollection &perimeters) - { - for (const ExtrusionEntity *ee : perimeters.entities) { - if (ee->is_collection()) { - for (const ExtrusionEntity *ee2 : static_cast(ee)->entities) { - assert(! ee2->is_collection()); - if (ee2->is_loop()) - if (has_bridging_perimeters(*static_cast(ee2))) - return true; - } - } else if (ee->is_loop() && has_bridging_perimeters(*static_cast(ee))) - return true; - } - return false; - } - static bool has_bridging_fills(const ExtrusionEntityCollection &fills) - { - for (const ExtrusionEntity *ee : fills.entities) { - assert(ee->is_collection()); - for (const ExtrusionEntity *ee2 : static_cast(ee)->entities) { - assert(! ee2->is_collection()); - assert(! ee2->is_loop()); - if (ee2->role() == erBridgeInfill || ee2->role() == erInternalBridgeInfill) - return true; - } - } - return false; - } - static bool has_bridging_extrusions(const Layer &layer) - { - for (const LayerRegion *region : layer.regions()) { - if (SupportMaterialInternal::has_bridging_perimeters(region->perimeters)) - return true; - if (region->fill_surfaces.has(stBottomBridge) && has_bridging_fills(region->fills)) - return true; - } - return false; - } - - static inline void collect_bridging_perimeter_areas(const ExtrusionLoop &loop, const float expansion_scaled, Polygons &out) - { - assert(expansion_scaled >= 0.f); - for (const ExtrusionPath &ep : loop.paths) - if (ep.role() == erOverhangPerimeter && ! ep.polyline.empty()) { - float exp = 0.5f * (float)scale_(ep.width) + expansion_scaled; - if (ep.is_closed()) { - if (ep.size() >= 3) { - // This is a complete loop. - // Add the outer contour first. - Polygon poly; - poly.points = ep.polyline.points; - poly.points.pop_back(); - if (poly.area() < 0) - poly.reverse(); - polygons_append(out, offset(poly, exp, SUPPORT_SURFACES_OFFSET_PARAMETERS)); - Polygons holes = offset(poly, - exp, SUPPORT_SURFACES_OFFSET_PARAMETERS); - polygons_reverse(holes); - polygons_append(out, holes); - } - } else if (ep.size() >= 2) { - // Offset the polyline. - polygons_append(out, offset(ep.polyline, exp, SUPPORT_SURFACES_OFFSET_PARAMETERS)); - } - } - } - static void collect_bridging_perimeter_areas(const ExtrusionEntityCollection &perimeters, const float expansion_scaled, Polygons &out) - { - for (const ExtrusionEntity *ee : perimeters.entities) { - if (ee->is_collection()) { - for (const ExtrusionEntity *ee2 : static_cast(ee)->entities) { - assert(! ee2->is_collection()); - if (ee2->is_loop()) - collect_bridging_perimeter_areas(*static_cast(ee2), expansion_scaled, out); - } - } else if (ee->is_loop()) - collect_bridging_perimeter_areas(*static_cast(ee), expansion_scaled, out); - } - } - - static void remove_bridges_from_contacts( - const PrintConfig &print_config, - const Layer &lower_layer, - const Polygons &lower_layer_polygons, - const LayerRegion &layerm, - float fw, - Polygons &contact_polygons) - { - // compute the area of bridging perimeters - Polygons bridges; - { - // Surface supporting this layer, expanded by 0.5 * nozzle_diameter, as we consider this kind of overhang to be sufficiently supported. - Polygons lower_grown_slices = expand(lower_layer_polygons, - //FIXME to mimic the decision in the perimeter generator, we should use half the external perimeter width. - 0.5f * float(scale_(print_config.nozzle_diameter.get_at(layerm.region().config().wall_filament-1))), - SUPPORT_SURFACES_OFFSET_PARAMETERS); - // Collect perimeters of this layer. - //FIXME split_at_first_point() could split a bridge mid-way - #if 0 - Polylines overhang_perimeters = layerm.perimeters.as_polylines(); - // workaround for Clipper bug, see Slic3r::Polygon::clip_as_polyline() - for (Polyline &polyline : overhang_perimeters) - polyline.points[0].x += 1; - // Trim the perimeters of this layer by the lower layer to get the unsupported pieces of perimeters. - overhang_perimeters = diff_pl(overhang_perimeters, lower_grown_slices); - #else - Polylines overhang_perimeters = diff_pl(layerm.perimeters.as_polylines(), lower_grown_slices); - #endif - - // only consider straight overhangs - // only consider overhangs having endpoints inside layer's slices - // convert bridging polylines into polygons by inflating them with their thickness - // since we're dealing with bridges, we can't assume width is larger than spacing, - // so we take the largest value and also apply safety offset to be ensure no gaps - // are left in between - // BBS - const PrintObjectConfig& object_config = layerm.layer()->object()->config(); - Flow perimeter_bridge_flow = layerm.bridging_flow(frPerimeter, object_config.thick_bridges); - //FIXME one may want to use a maximum of bridging flow width and normal flow width, as the perimeters are calculated using the normal flow - // and then turned to bridging flow, thus their centerlines are derived from non-bridging flow and expanding them by a bridging flow - // may not expand them to the edge of their respective islands. - const float w = float(0.5 * std::max(perimeter_bridge_flow.scaled_width(), perimeter_bridge_flow.scaled_spacing())) + scaled(0.001); - for (Polyline &polyline : overhang_perimeters) - if (polyline.is_straight()) { - // This is a bridge - polyline.extend_start(fw); - polyline.extend_end(fw); - // Is the straight perimeter segment supported at both sides? - Point pts[2] = { polyline.first_point(), polyline.last_point() }; - bool supported[2] = { false, false }; - for (size_t i = 0; i < lower_layer.lslices.size() && ! (supported[0] && supported[1]); ++ i) - for (int j = 0; j < 2; ++ j) - if (! supported[j] && lower_layer.lslices_bboxes[i].contains(pts[j]) && lower_layer.lslices[i].contains(pts[j])) - supported[j] = true; - if (supported[0] && supported[1]) - // Offset a polyline into a thick line. - polygons_append(bridges, offset(polyline, w)); - } - bridges = union_(bridges); - } - // remove the entire bridges and only support the unsupported edges - //FIXME the brided regions are already collected as layerm.bridged. Use it? - for (const Surface &surface : layerm.fill_surfaces.surfaces) - if (surface.surface_type == stBottomBridge && surface.bridge_angle >= 0.0) - polygons_append(bridges, surface.expolygon); - //FIXME add the gap filled areas. Extrude the gaps with a bridge flow? - // Remove the unsupported ends of the bridges from the bridged areas. - //FIXME add supports at regular intervals to support long bridges! - bridges = diff(bridges, - // Offset unsupported edges into polygons. - offset(layerm.unsupported_bridge_edges, scale_(SUPPORT_MATERIAL_MARGIN), SUPPORT_SURFACES_OFFSET_PARAMETERS)); - // Remove bridged areas from the supported areas. - contact_polygons = diff(contact_polygons, bridges, ApplySafetyOffset::Yes); - - #ifdef SLIC3R_DEBUG - static int iRun = 0; - SVG::export_expolygons(debug_out_path("support-top-contacts-remove-bridges-run%d.svg", iRun ++), - { { { union_ex(offset(layerm.unsupported_bridge_edges, scale_(SUPPORT_MATERIAL_MARGIN), SUPPORT_SURFACES_OFFSET_PARAMETERS)) }, { "unsupported_bridge_edges", "orange", 0.5f } }, - { { union_ex(contact_polygons) }, { "contact_polygons", "blue", 0.5f } }, - { { union_ex(bridges) }, { "bridges", "red", "black", "", scaled(0.1f), 0.5f } } }); - #endif /* SLIC3R_DEBUG */ - } -} - -std::vector PrintObjectSupportMaterial::buildplate_covered(const PrintObject &object) const -{ - // Build support on a build plate only? If so, then collect and union all the surfaces below the current layer. - // Unfortunately this is an inherently serial process. - const bool buildplate_only = this->build_plate_only(); - std::vector buildplate_covered; - if (buildplate_only) { - BOOST_LOG_TRIVIAL(debug) << "PrintObjectSupportMaterial::buildplate_covered() - start"; - buildplate_covered.assign(object.layers().size(), Polygons()); - //FIXME prefix sum algorithm, parallelize it! Parallelization will also likely be more numerically stable. - for (size_t layer_id = 1; layer_id < object.layers().size(); ++ layer_id) { - const Layer &lower_layer = *object.layers()[layer_id-1]; - // Merge the new slices with the preceding slices. - // Apply the safety offset to the newly added polygons, so they will connect - // with the polygons collected before, - // but don't apply the safety offset during the union operation as it would - // inflate the polygons over and over. - Polygons &covered = buildplate_covered[layer_id]; - covered = buildplate_covered[layer_id - 1]; - polygons_append(covered, offset(lower_layer.lslices, scale_(0.01))); - covered = union_(covered); - } - BOOST_LOG_TRIVIAL(debug) << "PrintObjectSupportMaterial::buildplate_covered() - end"; - } - return buildplate_covered; -} - -struct SupportAnnotations -{ - SupportAnnotations(const PrintObject &object, const std::vector &buildplate_covered) : - enforcers_layers(object.slice_support_enforcers()), - blockers_layers(object.slice_support_blockers()), - buildplate_covered(buildplate_covered) - { - // Append custom supports. - object.project_and_append_custom_facets(false, EnforcerBlockerType::ENFORCER, enforcers_layers); - object.project_and_append_custom_facets(false, EnforcerBlockerType::BLOCKER, blockers_layers); - } - - std::vector enforcers_layers; - std::vector blockers_layers; - const std::vector& buildplate_covered; -}; - -struct SlicesMarginCache -{ - float offset { -1 }; - // Trimming polygons, including possibly the "build plate only" mask. - Polygons polygons; - // Trimming polygons, without the "build plate only" mask. If empty, use polygons. - Polygons all_polygons; -}; - -// BBS -static const double length_thresh_well_supported = scale_(6); // min: 6mm -static const double area_thresh_well_supported = SQ(length_thresh_well_supported); // min: 6x6=36mm^2 -static const double sharp_tail_xy_gap = 0.2f; -static const double no_overlap_xy_gap = 0.2f; -static const double sharp_tail_max_support_height = 16.f; - -// Tuple: overhang_polygons, contact_polygons, enforcer_polygons, no_interface_offset -// no_interface_offset: minimum of external perimeter widths -static inline ExPolygons detect_overhangs( - const Layer &layer, - const size_t layer_id, - Polygons &lower_layer_polygons, - const PrintConfig &print_config, - const PrintObjectConfig &object_config, - SupportAnnotations &annotations, - const double gap_xy -#ifdef SLIC3R_DEBUG - , size_t iRun -#endif // SLIC3R_DEBUG - ) -{ - // Snug overhang polygons. - Polygons overhang_polygons; - - // BBS. - const bool auto_normal_support = object_config.support_type.value == stNormalAuto; - const bool buildplate_only = ! annotations.buildplate_covered.empty(); - // If user specified a custom angle threshold, convert it to radians. - // Zero means automatic overhang detection. - // +1 makes the threshold inclusive - double thresh_angle = object_config.support_threshold_angle.value > 0 ? object_config.support_threshold_angle.value + 1 : 0; - thresh_angle = std::min(thresh_angle, 89.); // BBS should be smaller than 90 - const double threshold_rad = Geometry::deg2rad(thresh_angle); - const coordf_t max_bridge_length = scale_(object_config.max_bridge_length.value); - const bool bridge_no_support = object_config.bridge_no_support.value; - const coordf_t xy_expansion = scale_(object_config.support_expansion.value); - - if (layer_id == 0) - { - // Don't fill in the holes. The user may apply a higher raft_expansion if one wants a better 1st layer adhesion. - overhang_polygons = to_polygons(layer.lslices); - - for (auto& slice : layer.lslices) { - auto bbox_size = get_extents(slice).size(); - if (g_config_support_sharp_tails && - !(bbox_size.x() > length_thresh_well_supported && bbox_size.y() > length_thresh_well_supported)) - { - layer.sharp_tails.push_back(slice); - layer.sharp_tails_height.insert({ &slice, layer.height }); - } - } - } - else if (! layer.regions().empty()) - { - // Generate overhang / contact_polygons for non-raft layers. - const Layer &lower_layer = *layer.lower_layer; - const bool has_enforcer = !annotations.enforcers_layers.empty() && !annotations.enforcers_layers[layer_id].empty(); - // Can't directly use lower_layer.lslices, or we'll miss some very sharp tails. - // Filter out areas whose diameter that is smaller than extrusion_width. Do not use offset2() for this purpose! - // FIXME if there are multiple regions with different extrusion width, the following code may not be right. - float fw = float(layer.regions().front()->flow(frExternalPerimeter).scaled_width()); - ExPolygons lower_layer_expolys; - for (const ExPolygon& expoly : lower_layer.lslices) { - if (!offset_ex(expoly, -fw / 2).empty()) { - lower_layer_expolys.emplace_back(expoly); - } - } - - float lower_layer_offset = 0; - for (LayerRegion *layerm : layer.regions()) { - // Extrusion width accounts for the roundings of the extrudates. - // It is the maximum widh of the extrudate. - float fw = float(layerm->flow(frExternalPerimeter).scaled_width()); - lower_layer_offset = - (layer_id < (size_t)object_config.enforce_support_layers.value) ? - // Enforce a full possible support, ignore the overhang angle. - 0.f : - (threshold_rad > 0. ? - // Overhang defined by an angle. - float(scale_(lower_layer.height / tan(threshold_rad))) : - // Overhang defined by half the extrusion width. - 0.5f * fw); - // Overhang polygons for this layer and region. - Polygons diff_polygons; - Polygons layerm_polygons = to_polygons(layerm->slices.surfaces); - if (lower_layer_offset == 0.f) { - // Support everything. - diff_polygons = diff(layerm_polygons, lower_layer_polygons); - if (buildplate_only) { - // Don't support overhangs above the top surfaces. - // This step is done before the contact surface is calculated by growing the overhang region. - diff_polygons = diff(diff_polygons, annotations.buildplate_covered[layer_id]); - } - } else if (auto_normal_support) { - // Get the regions needing a suport, collapse very tiny spots. - //FIXME cache the lower layer offset if this layer has multiple regions. - diff_polygons = - diff(layerm_polygons, - expand(lower_layer_polygons, lower_layer_offset, SUPPORT_SURFACES_OFFSET_PARAMETERS)); - if (buildplate_only && ! annotations.buildplate_covered[layer_id].empty()) { - // Don't support overhangs above the top surfaces. - // This step is done before the contact surface is calculated by growing the overhang region. - diff_polygons = diff(diff_polygons, annotations.buildplate_covered[layer_id]); - } - if (! diff_polygons.empty()) { - // Offset the support regions back to a full overhang, restrict them to the full overhang. - // This is done to increase size of the supporting columns below, as they are calculated by - // propagating these contact surfaces downwards. - diff_polygons = diff(intersection(expand(diff_polygons, lower_layer_offset, SUPPORT_SURFACES_OFFSET_PARAMETERS), layerm_polygons), lower_layer_polygons); - if (xy_expansion != 0) { diff_polygons = expand(diff_polygons, xy_expansion, SUPPORT_SURFACES_OFFSET_PARAMETERS); } - } - //FIXME add user defined filtering here based on minimal area or minimum radius or whatever. - - // BBS - if (g_config_support_sharp_tails) { - for (ExPolygon& expoly : layerm->raw_slices) { - if (offset_ex(expoly, -0.5 * fw).empty()) continue; - bool is_sharp_tail = false; - float accum_height = layer.height; - - // 1. nothing below - // Check whether this is a sharp tail region. - // Should use lower_layer_expolys without any offset. Otherwise, it may missing sharp tails near the main body. - if (!overlaps(offset_ex(expoly, 0.5 * fw), lower_layer_expolys)) { - is_sharp_tail = expoly.area() < area_thresh_well_supported && !offset_ex(expoly, -0.1 * fw).empty(); - } - - if (is_sharp_tail) { - ExPolygons overhang = diff_ex({ expoly }, lower_layer_expolys); - layer.sharp_tails.push_back(expoly); - layer.sharp_tails_height.insert({ &expoly, accum_height }); - overhang = offset_ex(overhang, 0.05 * fw); - polygons_append(diff_polygons, to_polygons(overhang)); - } - } - } - } - - if (diff_polygons.empty()) - continue; - - // Apply the "support blockers". - if (!annotations.blockers_layers.empty() && !annotations.blockers_layers[layer_id].empty()) { - // Expand the blocker a bit. Custom blockers produce strips - // spanning just the projection between the two slices. - // Subtracting them as they are may leave unwanted narrow - // residues of diff_polygons that would then be supported. - diff_polygons = diff(diff_polygons, - expand(union_(annotations.blockers_layers[layer_id]), float(1000. * SCALED_EPSILON))); - } - - if (bridge_no_support) { - //FIXME Expensive, potentially not precise enough. Misses gap fill extrusions, which bridge. - SupportMaterialInternal::remove_bridges_from_contacts( - print_config, lower_layer, lower_layer_polygons, *layerm, fw, diff_polygons); - } - - if (diff_polygons.empty() || offset(diff_polygons, -0.1 * fw).empty()) - continue; - - polygons_append(overhang_polygons, diff_polygons); - } // for each layer.region - } - - ExPolygons overhang_areas = union_ex(overhang_polygons); - // check cantilever - if (layer.lower_layer) { - for (ExPolygon& poly : overhang_areas) { - float fw = float(layer.regions().front()->flow(frExternalPerimeter).scaled_width()); - auto cluster_boundary_ex = intersection_ex(poly, offset_ex(layer.lower_layer->lslices, scale_(0.5))); - Polygons cluster_boundary = to_polygons(cluster_boundary_ex); - if (cluster_boundary.empty()) continue; - double dist_max = 0; - for (auto& pt : poly.contour.points) { - double dist_pt = std::numeric_limits::max(); - for (auto& ply : cluster_boundary) { - double d = ply.distance_to(pt); - dist_pt = std::min(dist_pt, d); - } - dist_max = std::max(dist_max, dist_pt); - } - if (dist_max > scale_(3)) { // is cantilever if the farmost point is larger than 3mm away from base - layer.cantilevers.emplace_back(poly); - } - } - } - - return overhang_areas; -} - -// Tuple: overhang_polygons, contact_polygons, enforcer_polygons, no_interface_offset -// no_interface_offset: minimum of external perimeter widths -static inline std::tuple detect_contacts( - const Layer& layer, - const size_t layer_id, - Polygons& overhang_polygons, - Polygons& lower_layer_polygons, - const PrintConfig& print_config, - const PrintObjectConfig& object_config, - SupportAnnotations& annotations, - SlicesMarginCache& slices_margin, - const double gap_xy -#ifdef SLIC3R_DEBUG - , size_t iRun -#endif // SLIC3R_DEBUG -) -{ - // Expanded for stability, trimmed by gap_xy. - Polygons contact_polygons; - // Enforcers projected to overhangs, trimmed - Polygons enforcer_polygons; - - // BBS. - const bool auto_normal_support = object_config.support_type.value == stNormalAuto; - const bool buildplate_only = !annotations.buildplate_covered.empty(); - float no_interface_offset = 0.f; - - if (layer_id == 0) - { - // Expand for better stability. - contact_polygons = object_config.raft_expansion.value > 0 ? expand(overhang_polygons, scaled(object_config.raft_expansion.value)) : overhang_polygons; - } - else if (!layer.regions().empty()) - { - // Generate overhang / contact_polygons for non-raft layers. - const Layer& lower_layer = *layer.lower_layer; - const bool has_enforcer = !annotations.enforcers_layers.empty() && !annotations.enforcers_layers[layer_id].empty(); - const ExPolygons& lower_layer_expolys = lower_layer.lslices; - const ExPolygons& lower_layer_sharptails = lower_layer.sharp_tails; - - // Cache support trimming polygons derived from lower layer polygons, possible merged with "on build plate only" trimming polygons. - auto slices_margin_update = - [&slices_margin, &layer, &lower_layer, &lower_layer_polygons, buildplate_only, has_enforcer, &annotations, layer_id] - (float slices_margin_offset, float no_interface_offset) { - if (slices_margin.offset != slices_margin_offset) { - slices_margin.offset = slices_margin_offset; - slices_margin.polygons = (slices_margin_offset == 0.f) ? - lower_layer_polygons : - // What is the purpose of no_interface_offset? Likely to not trim the contact layer by lower layer regions that are too thin to extrude? - offset2(lower_layer.lslices, -no_interface_offset * 0.5f, slices_margin_offset + no_interface_offset * 0.5f, SUPPORT_SURFACES_OFFSET_PARAMETERS); - if (buildplate_only && !annotations.buildplate_covered[layer_id].empty()) { - if (has_enforcer) - // Make a backup of trimming polygons before enforcing "on build plate only". - slices_margin.all_polygons = slices_margin.polygons; - // Trim the inflated contact surfaces by the top surfaces as well. - slices_margin.polygons = union_(slices_margin.polygons, annotations.buildplate_covered[layer_id]); - } - } - }; - - no_interface_offset = std::accumulate(layer.regions().begin(), layer.regions().end(), FLT_MAX, - [](float acc, const LayerRegion* layerm) { return std::min(acc, float(layerm->flow(frExternalPerimeter).scaled_width())); }); - - float lower_layer_offset = 0; - for (LayerRegion* layerm : layer.regions()) { - Polygons layerm_polygons = to_polygons(layerm->slices.surfaces); - - // Overhang polygons for this layer and region. - Polygons diff_polygons = intersection(overhang_polygons, layerm_polygons); - if (diff_polygons.empty()) - continue; - - // Let's define the required contact area by using a max gap of half the upper - // extrusion width and extending the area according to the configured margin. - // We increment the area in steps because we don't want our support to overflow - // on the other side of the object (if it's very thin). - { - //FIMXE 1) Make the offset configurable, 2) Make the Z span configurable. - //FIXME one should trim with the layer span colliding with the support layer, this layer - // may be lower than lower_layer, so the support area needed may need to be actually bigger! - // For the same reason, the non-bridging support area may be smaller than the bridging support area! - slices_margin_update(std::min(lower_layer_offset, float(scale_(gap_xy))), no_interface_offset); - // Offset the contact polygons outside. -#if 0 - for (size_t i = 0; i < NUM_MARGIN_STEPS; ++ i) { - diff_polygons = diff( - offset( - diff_polygons, - scaled(SUPPORT_MATERIAL_MARGIN / NUM_MARGIN_STEPS), - ClipperLib::jtRound, - // round mitter limit - scale_(0.05)), - slices_margin.polygons); - } -#else - diff_polygons = diff(diff_polygons, slices_margin.polygons); -#endif - } - polygons_append(contact_polygons, diff_polygons); - } // for each layer.region - - if (has_enforcer) - if (const Polygons& enforcer_polygons_src = annotations.enforcers_layers[layer_id]; !enforcer_polygons_src.empty()) { - // Enforce supports (as if with 90 degrees of slope) for the regions covered by the enforcer meshes. -#ifdef SLIC3R_DEBUG - ExPolygons enforcers_united = union_ex(enforcer_polygons_src); -#endif // SLIC3R_DEBUG - enforcer_polygons = diff(intersection(layer.lslices, enforcer_polygons_src), - // Inflate just a tiny bit to avoid intersection of the overhang areas with the object. - expand(lower_layer_polygons, 0.05f * no_interface_offset, SUPPORT_SURFACES_OFFSET_PARAMETERS)); -#ifdef SLIC3R_DEBUG - SVG::export_expolygons(debug_out_path("support-top-contacts-enforcers-run%d-layer%d-z%f.svg", iRun, layer_id, layer.print_z), - { { layer.lslices, { "layer.lslices", "gray", 0.2f } }, - { { union_ex(lower_layer_polygons) }, { "lower_layer_polygons", "green", 0.5f } }, - { enforcers_united, { "enforcers", "blue", 0.5f } }, - { { union_safety_offset_ex(enforcer_polygons) }, { "new_contacts", "red", "black", "", scaled(0.1f), 0.5f } } }); -#endif /* SLIC3R_DEBUG */ - if (!enforcer_polygons.empty()) { - polygons_append(overhang_polygons, enforcer_polygons); - slices_margin_update(std::min(lower_layer_offset, float(scale_(gap_xy))), no_interface_offset); - polygons_append(contact_polygons, diff(enforcer_polygons, slices_margin.all_polygons.empty() ? slices_margin.polygons : slices_margin.all_polygons)); - } - } - } - - return std::make_tuple(std::move(contact_polygons), std::move(enforcer_polygons), no_interface_offset); -} - -// Allocate one, possibly two support contact layers. -// For "thick" overhangs, one support layer will be generated to support normal extrusions, the other to support the "thick" extrusions. -static inline std::pair new_contact_layer( - const PrintConfig &print_config, - const PrintObjectConfig &object_config, - const SlicingParameters &slicing_params, - const coordf_t support_layer_height_min, - const Layer &layer, - std::deque &layer_storage, - tbb::spin_mutex &layer_storage_mutex) -{ - double print_z, bottom_z, height; - PrintObjectSupportMaterial::MyLayer* bridging_layer = nullptr; - assert(layer.id() >= slicing_params.raft_layers()); - size_t layer_id = layer.id() - slicing_params.raft_layers(); - - if (layer_id == 0) { - // This is a raft contact layer sitting directly on the print bed. - assert(slicing_params.has_raft()); - print_z = slicing_params.raft_contact_top_z; - bottom_z = slicing_params.raft_interface_top_z; - height = slicing_params.contact_raft_layer_height; - } else if (slicing_params.soluble_interface) { - // Align the contact surface height with a layer immediately below the supported layer. - // Interface layer will be synchronized with the object. - print_z = layer.bottom_z(); - height = layer.lower_layer->height; - bottom_z = (layer_id == 1) ? slicing_params.object_print_z_min : layer.lower_layer->lower_layer->print_z; - } else { - print_z = layer.bottom_z() - slicing_params.gap_support_object; - height = print_config.independent_support_layer_height ? 0. : layer.lower_layer->height/*object_config.layer_height*/; // BBS: need to consider adaptive layer heights - bottom_z = print_z - height; - // Ignore this contact area if it's too low. - // Don't want to print a layer below the first layer height as it may not stick well. - //FIXME there may be a need for a single layer support, then one may decide to print it either as a bottom contact or a top contact - // and it may actually make sense to do it with a thinner layer than the first layer height. - if (print_z < slicing_params.first_print_layer_height - EPSILON) { - // This contact layer is below the first layer height, therefore not printable. Don't support this surface. - return std::pair(nullptr, nullptr); - } - const bool has_raft = slicing_params.raft_layers() > 1; - const coordf_t min_print_z = has_raft ? slicing_params.raft_contact_top_z : slicing_params.first_print_layer_height; - if (print_z < min_print_z + support_layer_height_min) { - // Align the layer with the 1st layer height or the raft contact layer. - // With raft active, any contact layer below the raft_contact_top_z will be brought to raft_contact_top_z to extend the raft area. - print_z = min_print_z; - bottom_z = has_raft ? slicing_params.raft_interface_top_z : 0; - height = has_raft ? slicing_params.contact_raft_layer_height : min_print_z; - } else { - // Don't know the height of the top contact layer yet. The top contact layer is printed with a normal flow and - // its height will be set adaptively later on. - } - - // Contact layer will be printed with a normal flow, but - // it will support layers printed with a bridging flow. - if (object_config.thick_bridges && SupportMaterialInternal::has_bridging_extrusions(layer)) { - coordf_t bridging_height = 0.; - for (const LayerRegion* region : layer.regions()) - bridging_height += region->region().bridging_height_avg(print_config); - bridging_height /= coordf_t(layer.regions().size()); - // BBS: align bridging height - if (!print_config.independent_support_layer_height) - bridging_height = std::ceil(bridging_height / object_config.layer_height - EPSILON) * object_config.layer_height; - coordf_t bridging_print_z = layer.print_z - bridging_height - slicing_params.gap_support_object; - if (bridging_print_z >= min_print_z) { - // Not below the first layer height means this layer is printable. - if (print_z < min_print_z + support_layer_height_min) { - // Align the layer with the 1st layer height or the raft contact layer. - bridging_print_z = min_print_z; - } - if (bridging_print_z < print_z - EPSILON) { - // Allocate the new layer. - bridging_layer = &layer_allocate(layer_storage, layer_storage_mutex, PrintObjectSupportMaterial::sltTopContact); - bridging_layer->idx_object_layer_above = layer_id; - bridging_layer->print_z = bridging_print_z; - if (bridging_print_z == slicing_params.first_print_layer_height) { - bridging_layer->bottom_z = 0; - bridging_layer->height = slicing_params.first_print_layer_height; - } else { - // BBS: if independent_support_layer_height is not enabled, the support layer_height should be the same as layer height. - // Note that for this case, adaptive layer height must be disabled. - bridging_layer->height = print_config.independent_support_layer_height ? 0. : object_config.layer_height; - // Don't know the height yet. - bridging_layer->bottom_z = bridging_print_z - bridging_layer->height; - } - } - } - } - } - - PrintObjectSupportMaterial::MyLayer &new_layer = layer_allocate(layer_storage, layer_storage_mutex, PrintObjectSupportMaterial::sltTopContact); - new_layer.idx_object_layer_above = layer_id; - new_layer.print_z = print_z; - new_layer.bottom_z = bottom_z; - new_layer.height = height; - return std::make_pair(&new_layer, bridging_layer); -} - -static inline void fill_contact_layer( - PrintObjectSupportMaterial::MyLayer &new_layer, - size_t layer_id, - const SlicingParameters &slicing_params, - const PrintObjectConfig &object_config, - const SlicesMarginCache &slices_margin, - const Polygons &overhang_polygons, - const Polygons &contact_polygons, - const Polygons &enforcer_polygons, - const Polygons &lower_layer_polygons, - const Flow &support_material_flow, - float no_interface_offset -#ifdef SLIC3R_DEBUG - , size_t iRun, - const Layer &layer -#endif // SLIC3R_DEBUG - ) -{ - const SupportGridParams grid_params(object_config, support_material_flow); - - Polygons lower_layer_polygons_for_dense_interface_cache; - auto lower_layer_polygons_for_dense_interface = [&lower_layer_polygons_for_dense_interface_cache, &lower_layer_polygons, no_interface_offset]() -> const Polygons& { - if (lower_layer_polygons_for_dense_interface_cache.empty()) - lower_layer_polygons_for_dense_interface_cache = - //FIXME no_interface_offset * 0.6f offset is not quite correct, one shall derive it based on an angle thus depending on layer height. - opening(lower_layer_polygons, no_interface_offset * 0.5f, no_interface_offset * (0.6f + 0.5f), SUPPORT_SURFACES_OFFSET_PARAMETERS); - return lower_layer_polygons_for_dense_interface_cache; - }; - - // Stretch support islands into a grid, trim them. - SupportGridPattern support_grid_pattern(&contact_polygons, &slices_margin.polygons, grid_params); - // 1) Contact polygons will be projected down. To keep the interface and base layers from growing, return a contour a tiny bit smaller than the grid cells. - new_layer.contact_polygons = std::make_unique(support_grid_pattern.extract_support(grid_params.expansion_to_propagate, true -#ifdef SLIC3R_DEBUG - , "top_contact_polygons", iRun, layer_id, layer.print_z -#endif // SLIC3R_DEBUG - )); - // 2) infill polygons, expand them by half the extrusion width + a tiny bit of extra. - bool reduce_interfaces = object_config.support_style.value != smsSnug && layer_id > 0 && !slicing_params.soluble_interface; - if (reduce_interfaces) { - // Reduce the amount of dense interfaces: Do not generate dense interfaces below overhangs with 60% overhang of the extrusions. - Polygons dense_interface_polygons = diff(overhang_polygons, lower_layer_polygons_for_dense_interface()); - if (! dense_interface_polygons.empty()) { - dense_interface_polygons = - diff( - // Regularize the contour. - expand(dense_interface_polygons, no_interface_offset * 0.1f), - slices_margin.polygons); - // Support islands, to be stretched into a grid. - //FIXME The regularization of dense_interface_polygons above may stretch dense_interface_polygons outside of the contact polygons, - // thus some dense interface areas may not get supported. Trim the excess with contact_polygons at the following line. - // See for example GH #4874. - Polygons dense_interface_polygons_trimmed = intersection(dense_interface_polygons, *new_layer.contact_polygons); - // Stretch support islands into a grid, trim them. - SupportGridPattern support_grid_pattern(&dense_interface_polygons_trimmed, &slices_margin.polygons, grid_params); - new_layer.polygons = support_grid_pattern.extract_support(grid_params.expansion_to_slice, false -#ifdef SLIC3R_DEBUG - , "top_contact_polygons2", iRun, layer_id, layer.print_z -#endif // SLIC3R_DEBUG - ); - #ifdef SLIC3R_DEBUG - SVG::export_expolygons(debug_out_path("support-top-contacts-final1-run%d-layer%d-z%f.svg", iRun, layer_id, layer.print_z), - { { { union_ex(lower_layer_polygons) }, { "lower_layer_polygons", "gray", 0.2f } }, - { { union_ex(*new_layer.contact_polygons) }, { "new_layer.contact_polygons", "yellow", 0.5f } }, - { { union_ex(slices_margin.polygons) }, { "slices_margin_cached", "blue", 0.5f } }, - { { union_ex(dense_interface_polygons) }, { "dense_interface_polygons", "green", 0.5f } }, - { { union_safety_offset_ex(new_layer.polygons) }, { "new_layer.polygons", "red", "black", "", scaled(0.1f), 0.5f } } }); - //support_grid_pattern.serialize(debug_out_path("support-top-contacts-final-run%d-layer%d-z%f.bin", iRun, layer_id, layer.print_z)); - SVG::export_expolygons(debug_out_path("support-top-contacts-final2-run%d-layer%d-z%f.svg", iRun, layer_id, layer.print_z), - { { { union_ex(lower_layer_polygons) }, { "lower_layer_polygons", "gray", 0.2f } }, - { { union_ex(*new_layer.contact_polygons) }, { "new_layer.contact_polygons", "yellow", 0.5f } }, - { { union_ex(contact_polygons) }, { "contact_polygons", "blue", 0.5f } }, - { { union_ex(dense_interface_polygons) }, { "dense_interface_polygons", "green", 0.5f } }, - { { union_safety_offset_ex(new_layer.polygons) }, { "new_layer.polygons", "red", "black", "", scaled(0.1f), 0.5f } } }); - #endif /* SLIC3R_DEBUG */ - } - } else { - new_layer.polygons = support_grid_pattern.extract_support(grid_params.expansion_to_slice, true -#ifdef SLIC3R_DEBUG - , "top_contact_polygons3", iRun, layer_id, layer.print_z -#endif // SLIC3R_DEBUG - ); - } - - if (! enforcer_polygons.empty() && ! slices_margin.all_polygons.empty() && layer_id > 0) { - // Support enforcers used together with support enforcers. The support enforcers need to be handled separately from the rest of the support. - - SupportGridPattern support_grid_pattern(&enforcer_polygons, &slices_margin.all_polygons, grid_params); - // 1) Contact polygons will be projected down. To keep the interface and base layers from growing, return a contour a tiny bit smaller than the grid cells. - new_layer.enforcer_polygons = std::make_unique(support_grid_pattern.extract_support(grid_params.expansion_to_propagate, true -#ifdef SLIC3R_DEBUG - , "top_contact_polygons4", iRun, layer_id, layer.print_z -#endif // SLIC3R_DEBUG - )); - Polygons new_polygons; - bool needs_union = ! new_layer.polygons.empty(); - if (reduce_interfaces) { - // 2) infill polygons, expand them by half the extrusion width + a tiny bit of extra. - // Reduce the amount of dense interfaces: Do not generate dense interfaces below overhangs with 60% overhang of the extrusions. - Polygons dense_interface_polygons = diff(enforcer_polygons, lower_layer_polygons_for_dense_interface()); - if (! dense_interface_polygons.empty()) { - dense_interface_polygons = - diff( - // Regularize the contour. - expand(dense_interface_polygons, no_interface_offset * 0.1f), - slices_margin.all_polygons); - // Support islands, to be stretched into a grid. - //FIXME The regularization of dense_interface_polygons above may stretch dense_interface_polygons outside of the contact polygons, - // thus some dense interface areas may not get supported. Trim the excess with contact_polygons at the following line. - // See for example GH #4874. - Polygons dense_interface_polygons_trimmed = intersection(dense_interface_polygons, *new_layer.enforcer_polygons); - SupportGridPattern support_grid_pattern(&dense_interface_polygons_trimmed, &slices_margin.all_polygons, grid_params); - // Extend the polygons to extrude with the contact polygons of support enforcers. - new_polygons = support_grid_pattern.extract_support(grid_params.expansion_to_slice, false - #ifdef SLIC3R_DEBUG - , "top_contact_polygons5", iRun, layer_id, layer.print_z - #endif // SLIC3R_DEBUG - ); - } - } else { - new_polygons = support_grid_pattern.extract_support(grid_params.expansion_to_slice, true - #ifdef SLIC3R_DEBUG - , "top_contact_polygons6", iRun, layer_id, layer.print_z - #endif // SLIC3R_DEBUG - ); - } - append(new_layer.polygons, std::move(new_polygons)); - if (needs_union) - new_layer.polygons = union_(new_layer.polygons); - } - -#ifdef SLIC3R_DEBUG - SVG::export_expolygons(debug_out_path("support-top-contacts-final0-run%d-layer%d-z%f.svg", iRun, layer_id, layer.print_z), - { { { union_ex(lower_layer_polygons) }, { "lower_layer_polygons", "gray", 0.2f } }, - { { union_ex(*new_layer.contact_polygons) }, { "new_layer.contact_polygons", "yellow", 0.5f } }, - { { union_ex(contact_polygons) }, { "contact_polygons", "blue", 0.5f } }, - { { union_ex(overhang_polygons) }, { "overhang_polygons", "green", 0.5f } }, - { { union_safety_offset_ex(new_layer.polygons) }, { "new_layer.polygons", "red", "black", "", scaled(0.1f), 0.5f } } }); -#endif /* SLIC3R_DEBUG */ - - // Even after the contact layer was expanded into a grid, some of the contact islands may be too tiny to be extruded. - // Remove those tiny islands from new_layer.polygons and new_layer.contact_polygons. - - // Store the overhang polygons. - // The overhang polygons are used in the path generator for planning of the contact loops. - // if (this->has_contact_loops()). Compared to "polygons", "overhang_polygons" are snug. - new_layer.overhang_polygons = std::make_unique(std::move(overhang_polygons)); - if (! enforcer_polygons.empty()) - new_layer.enforcer_polygons = std::make_unique(std::move(enforcer_polygons)); -} - -// Merge close contact layers conservatively: If two layers are closer than the minimum allowed print layer height (the min_layer_height parameter), -// the top contact layer is merged into the bottom contact layer. -static void merge_contact_layers(const SlicingParameters &slicing_params, double support_layer_height_min, PrintObjectSupportMaterial::MyLayersPtr &layers) -{ - // Sort the layers, as one layer may produce bridging and non-bridging contact layers with different print_z. - std::sort(layers.begin(), layers.end(), [](const PrintObjectSupportMaterial::MyLayer *l1, const PrintObjectSupportMaterial::MyLayer *l2) { return l1->print_z < l2->print_z; }); - - int i = 0; - int k = 0; - { - // Find the span of layers, which are to be printed at the first layer height. - int j = 0; - for (; j < (int)layers.size() && layers[j]->print_z < slicing_params.first_print_layer_height + support_layer_height_min - EPSILON; ++ j); - if (j > 0) { - // Merge the layers layers (0) to (j - 1) into the layers[0]. - PrintObjectSupportMaterial::MyLayer &dst = *layers.front(); - for (int u = 1; u < j; ++ u) - dst.merge(std::move(*layers[u])); - // Snap the first layer to the 1st layer height. - dst.print_z = slicing_params.first_print_layer_height; - dst.height = slicing_params.first_print_layer_height; - dst.bottom_z = 0; - ++ k; - } - i = j; - } - for (; i < int(layers.size()); ++ k) { - // Find the span of layers closer than m_support_layer_height_min. - int j = i + 1; - coordf_t zmax = layers[i]->print_z + support_layer_height_min + EPSILON; - for (; j < (int)layers.size() && layers[j]->print_z < zmax; ++ j) ; - if (i + 1 < j) { - // Merge the layers layers (i + 1) to (j - 1) into the layers[i]. - PrintObjectSupportMaterial::MyLayer &dst = *layers[i]; - for (int u = i + 1; u < j; ++ u) - dst.merge(std::move(*layers[u])); - } - if (k < i) - layers[k] = layers[i]; - i = j; - } - if (k < (int)layers.size()) - layers.erase(layers.begin() + k, layers.end()); -} - - -struct OverhangCluster { - std::map> layer_overhangs; - ExPolygons merged_overhangs_dilated; - int min_layer = 1e7; - int max_layer = 0; - coordf_t offset_scaled = 0; - bool is_cantilever = false; - bool is_sharp_tail = false; - bool is_small_overhang = false; - - OverhangCluster(ExPolygon* overhang, int layer_nr, coordf_t offset_scaled) { - this->offset_scaled = offset_scaled; - insert(overhang, layer_nr); - } - - void insert(ExPolygon* overhang_new, int layer_nr) { - if (layer_overhangs.find(layer_nr) != layer_overhangs.end()) { - layer_overhangs[layer_nr].push_back(overhang_new); - } - else { - layer_overhangs.emplace(layer_nr, std::vector{ overhang_new }); - } - ExPolygons overhang_dilated = offset_scaled > EPSILON ? offset_ex(*overhang_new, offset_scaled) : ExPolygons{ *overhang_new }; - if (!overhang_dilated.empty()) - merged_overhangs_dilated = union_ex(merged_overhangs_dilated, overhang_dilated); - min_layer = std::min(min_layer, layer_nr); - max_layer = std::max(max_layer, layer_nr); - } - - int height() { - return max_layer - min_layer + 1; - } - - bool intersects(const ExPolygon& overhang_new, int layer_nr) { - if (layer_nr < 1) - return false; - - //auto it = layer_overhangs.find(layer_nr - 1); - //if (it == layer_overhangs.end()) - // return false; - //ExPolygons overhangs_lower; - //for (ExPolygon* poly : it->second) { - // overhangs_lower.push_back(*poly); - //} - if (layer_nrmax_layer + 1) - return false; - const ExPolygons overhang_dilated = offset_ex(overhang_new, offset_scaled); - return overlaps(overhang_dilated, merged_overhangs_dilated); - } -}; - -static OverhangCluster* add_overhang(std::vector& clusters, ExPolygon* overhang, int layer_nr, coordf_t offset_scaled) { - OverhangCluster* cluster = nullptr; - bool found = false; - for (int i = 0; i < clusters.size(); i++) { - auto cluster_i = &clusters[i]; - if (cluster_i->intersects(*overhang, layer_nr)) { - cluster_i->insert(overhang, layer_nr); - cluster = cluster_i; - break; - } - } - if (!cluster) { - cluster = &clusters.emplace_back(overhang, layer_nr, offset_scaled); - } - return cluster; -}; - -// Generate top contact layers supporting overhangs. -// For a soluble interface material synchronize the layer heights with the object, otherwise leave the layer height undefined. -// If supports over bed surface only are requested, don't generate contact layers over an object. -PrintObjectSupportMaterial::MyLayersPtr PrintObjectSupportMaterial::top_contact_layers( - const PrintObject &object, const std::vector &buildplate_covered, MyLayerStorage &layer_storage) const -{ -#ifdef SLIC3R_DEBUG - static int iRun = 0; - ++ iRun; - #define SLIC3R_IRUN , iRun -#endif /* SLIC3R_DEBUG */ - - // BBS: tree support is selected so normal supports need not be generated. - // Note we still need to go through the following steps if support is disabled but raft is enabled. - if (m_object_config->enable_support.value && (m_object_config->support_type.value != stNormalAuto && m_object_config->support_type.value != stNormal)) { - return MyLayersPtr(); - } - - // Slice support enforcers / support blockers. - SupportAnnotations annotations(object, buildplate_covered); - - // Output layers, sorted by top Z. - MyLayersPtr contact_out; - - BOOST_LOG_TRIVIAL(debug) << "PrintObjectSupportMaterial::top_contact_layers() in parallel - start"; - // Determine top contact areas. - // If generating raft only (no support), only calculate top contact areas for the 0th layer. - // If having a raft, start with 0th layer, otherwise with 1st layer. - // Note that layer_id < layer->id when raft_layers > 0 as the layer->id incorporates the raft layers. - // So layer_id == 0 means first object layer and layer->id == 0 means first print layer if there are no explicit raft layers. - size_t num_layers = this->has_support() ? object.layer_count() : 1; - // For each overhang layer, two supporting layers may be generated: One for the overhangs extruded with a bridging flow, - // and the other for the overhangs extruded with a normal flow. - contact_out.assign(num_layers * 2, nullptr); - tbb::spin_mutex layer_storage_mutex; - - std::vector overhangs_per_layers(num_layers); - size_t layer_id_start = this->has_raft() ? 0 : 1; - // main part of overhang detection can be parallel - tbb::parallel_for(tbb::blocked_range(layer_id_start, num_layers), - [&](const tbb::blocked_range& range) { - for (size_t layer_id = range.begin(); layer_id < range.end(); layer_id++) { - const Layer& layer = *object.layers()[layer_id]; - Polygons lower_layer_polygons = (layer_id == 0) ? Polygons() : to_polygons(object.layers()[layer_id - 1]->lslices); - - overhangs_per_layers[layer_id] = detect_overhangs(layer, layer_id, lower_layer_polygons, *m_print_config, *m_object_config, annotations, m_support_params.gap_xy -#ifdef SLIC3R_DEBUG - , iRun -#endif // SLIC3R_DEBUG - ); - - if (object.print()->canceled()) - break; - } - } - ); // end tbb::parallel_for - - if (object.print()->canceled()) - return MyLayersPtr(); - - // check if the sharp tails should be extended higher - bool detect_first_sharp_tail_only = false; - const coordf_t extrusion_width = m_object_config->line_width.get_abs_value(object.print()->config().nozzle_diameter.get_at(object.config().support_interface_filament-1)); - const coordf_t extrusion_width_scaled = scale_(extrusion_width); - if (is_auto(m_object_config->support_type.value) && g_config_support_sharp_tails && !detect_first_sharp_tail_only) { - for (size_t layer_nr = layer_id_start; layer_nr < num_layers; layer_nr++) { - if (object.print()->canceled()) - break; - - const Layer* layer = object.get_layer(layer_nr); - const Layer* lower_layer = layer->lower_layer; - if (!lower_layer) - continue; - - // BBS detect sharp tail - const ExPolygons& lower_layer_sharptails = lower_layer->sharp_tails; - const auto& lower_layer_sharptails_height = lower_layer->sharp_tails_height; - for (const ExPolygon& expoly : layer->lslices) { - bool is_sharp_tail = false; - float accum_height = layer->height; - do { - // 2. something below - // check whether this is above a sharp tail region. - - // 2.1 If no sharp tail below, this is considered as common region. - ExPolygons supported_by_lower = intersection_ex({ expoly }, lower_layer_sharptails); - if (supported_by_lower.empty()) { - is_sharp_tail = false; - break; - } - - // 2.2 If sharp tail below, check whether it support this region enough. -#if 0 - // judge by area isn't reliable, failure cases include 45 degree rotated cube - float supported_area = area(supported_by_lower); - if (supported_area > area_thresh_well_supported) { - is_sharp_tail = false; - break; - } -#endif - BoundingBox bbox = get_extents(supported_by_lower); - if (bbox.size().x() > length_thresh_well_supported && bbox.size().y() > length_thresh_well_supported) { - is_sharp_tail = false; - break; - } - - // 2.3 check whether sharp tail exceed the max height - for (const auto& lower_sharp_tail_height : lower_layer_sharptails_height) { - if (lower_sharp_tail_height.first->overlaps(expoly)) { - accum_height += lower_sharp_tail_height.second; - break; - } - } - if (accum_height > sharp_tail_max_support_height) { - is_sharp_tail = false; - break; - } - - // 2.4 if the area grows fast than threshold, it get connected to other part or - // it has a sharp slop and will be auto supported. - ExPolygons new_overhang_expolys = diff_ex({ expoly }, lower_layer_sharptails); - Point size_diff = get_extents(new_overhang_expolys).size() - get_extents(lower_layer_sharptails).size(); - if (size_diff.both_comp(Point(scale_(5), scale_(5)), ">") || !offset_ex(new_overhang_expolys, -5.0 * extrusion_width_scaled).empty()) { - is_sharp_tail = false; - break; - } - - // 2.5 mark the expoly as sharptail - is_sharp_tail = true; - } while (0); - - if (is_sharp_tail) { - ExPolygons overhang = diff_ex({ expoly }, lower_layer->lslices); - layer->sharp_tails.push_back(expoly); - layer->sharp_tails_height.insert({ &expoly, accum_height }); - append(overhangs_per_layers[layer_nr], overhang); -#ifdef SUPPORT_TREE_DEBUG_TO_SVG - SVG svg(get_svg_filename(std::to_string(layer->print_z), "sharp_tail"), object.bounding_box()); - if (svg.is_opened()) svg.draw(overhang, "yellow"); -#endif - } - - } - } - } - - if (object.print()->canceled()) - return MyLayersPtr(); - - // BBS group overhang clusters - const bool config_remove_small_overhangs = m_object_config->support_remove_small_overhang.value; - if (config_remove_small_overhangs) { - std::vector clusters; - double fw_scaled = scale_(extrusion_width); - std::set removed_overhang; - - for (size_t layer_id = layer_id_start; layer_id < num_layers; layer_id++) { - const Layer* layer = object.get_layer(layer_id); - for (auto& overhang : overhangs_per_layers[layer_id]) { - OverhangCluster* cluster = add_overhang(clusters, &overhang, layer_id, fw_scaled); - if (overlaps({ overhang }, layer->cantilevers)) - cluster->is_cantilever = true; - } - } - - for (OverhangCluster& cluster : clusters) { - // 3. check whether the small overhang is sharp tail - cluster.is_sharp_tail = false; - for (size_t layer_id = cluster.min_layer; layer_id <= cluster.max_layer; layer_id++) { - const Layer* layer = object.get_layer(layer_id); - if (overlaps(layer->sharp_tails, cluster.merged_overhangs_dilated)) { - cluster.is_sharp_tail = true; - break; - } - } - - if (!cluster.is_sharp_tail && !cluster.is_cantilever) { - // 2. check overhang cluster size is small - cluster.is_small_overhang = false; - auto erode1 = offset_ex(cluster.merged_overhangs_dilated, -1.0 * fw_scaled); - Point bbox_sz = get_extents(erode1).size(); - if (bbox_sz.x() < 2 * fw_scaled || bbox_sz.y() < 2 * fw_scaled) { - cluster.is_small_overhang = true; - } - } - -#ifdef SUPPORT_TREE_DEBUG_TO_SVG - const Layer* layer1 = object.get_layer(cluster.min_layer); - BoundingBox bbox = get_extents(cluster.merged_overhangs_dilated); - bbox.merge(get_extents(layer1->lslices)); - SVG svg(format("SVG/overhangCluster_%s_%s_tail=%s_cantilever=%s_small=%s.svg", cluster.min_layer, layer1->print_z, cluster.is_sharp_tail, cluster.is_cantilever, cluster.is_small_overhang), bbox); - if (svg.is_opened()) { - svg.draw(layer1->lslices, "red"); - svg.draw(cluster.merged_overhangs_dilated, "blue"); - } -#endif - - // 5. remove small overhangs - if (cluster.is_small_overhang) { - for (auto overhangs : cluster.layer_overhangs) { - for (auto* poly : overhangs.second) - removed_overhang.insert(poly); - } - } - } - - for (size_t layer_id = layer_id_start; layer_id < num_layers; layer_id++) { - auto& layer_overhangs = overhangs_per_layers[layer_id]; - if (layer_overhangs.empty()) - continue; - - for (int poly_idx = 0; poly_idx < layer_overhangs.size(); poly_idx++) { - auto* overhang = &layer_overhangs[poly_idx]; - if (removed_overhang.find(overhang) != removed_overhang.end()) { - overhang->clear(); - } - } - } - } - - if (object.print()->canceled()) - return MyLayersPtr(); - - for (size_t layer_id = layer_id_start; layer_id < num_layers; layer_id++) { - const Layer& layer = *object.layers()[layer_id]; - Polygons overhang_polygons = to_polygons(overhangs_per_layers[layer_id]); - Polygons lower_layer_polygons = (layer_id == 0) ? Polygons() : to_polygons(object.layers()[layer_id - 1]->lslices); - SlicesMarginCache slices_margin; - - auto [contact_polygons, enforcer_polygons, no_interface_offset] = - detect_contacts(layer, layer_id, overhang_polygons, lower_layer_polygons, *m_print_config, *m_object_config, annotations, slices_margin, m_support_params.gap_xy -#ifdef SLIC3R_DEBUG - , iRun -#endif // SLIC3R_DEBUG - ); - - // Now apply the contact areas to the layer where they need to be made. - if (!contact_polygons.empty() || !overhang_polygons.empty()) { - // Allocate the two empty layers. - auto [new_layer, bridging_layer] = new_contact_layer(*m_print_config, *m_object_config, m_slicing_params, m_support_params.support_layer_height_min, layer, layer_storage, layer_storage_mutex); - if (new_layer) { - // Fill the non-bridging layer with polygons. - fill_contact_layer(*new_layer, layer_id, m_slicing_params, - *m_object_config, slices_margin, overhang_polygons, contact_polygons, enforcer_polygons, lower_layer_polygons, - m_support_params.support_material_flow, no_interface_offset -#ifdef SLIC3R_DEBUG - , iRun, layer -#endif // SLIC3R_DEBUG - ); - // Insert new layer even if there is no interface generated: Likely the support angle is not steep enough to require dense interface, - // however generating a sparse support will be useful for the object stability. - // if (! new_layer->polygons.empty()) - contact_out[layer_id * 2] = new_layer; - if (bridging_layer != nullptr) { - bridging_layer->polygons = new_layer->polygons; - bridging_layer->contact_polygons = std::make_unique(*new_layer->contact_polygons); - bridging_layer->overhang_polygons = std::make_unique(*new_layer->overhang_polygons); - if (new_layer->enforcer_polygons) - bridging_layer->enforcer_polygons = std::make_unique(*new_layer->enforcer_polygons); - contact_out[layer_id * 2 + 1] = bridging_layer; - } - } - } - } - - // Compress contact_out, remove the nullptr items. - remove_nulls(contact_out); - - // Merge close contact layers conservatively: If two layers are closer than the minimum allowed print layer height (the min_layer_height parameter), - // the top contact layer is merged into the bottom contact layer. - merge_contact_layers(m_slicing_params, m_support_params.support_layer_height_min, contact_out); - - BOOST_LOG_TRIVIAL(debug) << "PrintObjectSupportMaterial::top_contact_layers() in parallel - end"; - - return contact_out; -} - -// Find the bottom contact layers above the top surfaces of this layer. -static inline PrintObjectSupportMaterial::MyLayer* detect_bottom_contacts( - const SlicingParameters &slicing_params, - const PrintObjectSupportMaterial::SupportParams &support_params, - const PrintObject &object, - const Layer &layer, - // Existing top contact layers, to which this newly created bottom contact layer will be snapped to guarantee a minimum layer height. - const PrintObjectSupportMaterial::MyLayersPtr &top_contacts, - // First top contact layer index overlapping with this new bottom interface layer. - size_t contact_idx, - // To allocate a new layer from. - std::deque &layer_storage, - // To trim the support areas above this bottom interface layer with this newly created bottom interface layer. - std::vector &layer_support_areas, - // Support areas projected from top to bottom, starting with top support interfaces. - const Polygons &supports_projected -#ifdef SLIC3R_DEBUG - , size_t iRun - , const Polygons &polygons_new -#endif // SLIC3R_DEBUG - ) -{ - Polygons top = collect_region_slices_by_type(layer, stTop); -#ifdef SLIC3R_DEBUG - SVG::export_expolygons(debug_out_path("support-bottom-layers-raw-%d-%lf.svg", iRun, layer.print_z), - { { { union_ex(top) }, { "top", "blue", 0.5f } }, - { { union_safety_offset_ex(supports_projected) }, { "overhangs", "magenta", 0.5f } }, - { layer.lslices, { "layer.lslices", "green", 0.5f } }, - { { union_safety_offset_ex(polygons_new) }, { "polygons_new", "red", "black", "", scaled(0.1f), 0.5f } } }); -#endif /* SLIC3R_DEBUG */ - - // Now find whether any projection of the contact surfaces above layer.print_z not yet supported by any - // top surfaces above layer.print_z falls onto this top surface. - // Touching are the contact surfaces supported exclusively by this top surfaces. - // Don't use a safety offset as it has been applied during insertion of polygons. - if (top.empty()) - return nullptr; - - Polygons touching = intersection(top, supports_projected); - if (touching.empty()) - return nullptr; - - assert(layer.id() >= slicing_params.raft_layers()); - size_t layer_id = layer.id() - slicing_params.raft_layers(); - - // Allocate a new bottom contact layer. - PrintObjectSupportMaterial::MyLayer &layer_new = layer_allocate(layer_storage, PrintObjectSupportMaterial::sltBottomContact); - // Grow top surfaces so that interface and support generation are generated - // with some spacing from object - it looks we don't need the actual - // top shapes so this can be done here - //FIXME calculate layer height based on the actual thickness of the layer: - // If the layer is extruded with no bridging flow, support just the normal extrusions. - layer_new.height = slicing_params.soluble_interface || !object.print()->config().independent_support_layer_height ? - // Align the interface layer with the object's layer height. - layer.upper_layer->height : - // Place a bridge flow interface layer or the normal flow interface layer over the top surface. - support_params.support_material_bottom_interface_flow.height(); - layer_new.print_z = slicing_params.soluble_interface ? layer.upper_layer->print_z : - layer.print_z + layer_new.height + slicing_params.gap_object_support; - layer_new.bottom_z = layer.print_z; - layer_new.idx_object_layer_below = layer_id; - layer_new.bridging = !slicing_params.soluble_interface && object.config().thick_bridges; - //FIXME how much to inflate the bottom surface, as it is being extruded with a bridging flow? The following line uses a normal flow. - layer_new.polygons = expand(touching, float(support_params.support_material_flow.scaled_width()), SUPPORT_SURFACES_OFFSET_PARAMETERS); - - if (! slicing_params.soluble_interface) { - // Walk the top surfaces, snap the top of the new bottom surface to the closest top of the top surface, - // so there will be no support surfaces generated with thickness lower than m_support_layer_height_min. - for (size_t top_idx = size_t(std::max(0, contact_idx)); - top_idx < top_contacts.size() && top_contacts[top_idx]->print_z < layer_new.print_z + support_params.support_layer_height_min + EPSILON; - ++ top_idx) { - if (top_contacts[top_idx]->print_z > layer_new.print_z - support_params.support_layer_height_min - EPSILON) { - // A top layer has been found, which is close to the new bottom layer. - coordf_t diff = layer_new.print_z - top_contacts[top_idx]->print_z; - assert(std::abs(diff) <= support_params.support_layer_height_min + EPSILON); - if (diff > 0.F) { - if (layer_new.height - diff > support_params.support_layer_height_min) { - // The top contact layer is below this layer. Make the bridging layer thinner to align with the existing top layer. - assert(diff < layer_new.height + EPSILON); - assert(layer_new.height - diff >= support_params.support_layer_height_min - EPSILON); - layer_new.print_z = top_contacts[top_idx]->print_z; - layer_new.height -= diff; - } - else { - // BBS: The trimmed layer height is smaller than support_layer_height_min. Walk to the next top contact layer. - continue; - } - } - else { - // The top contact layer is above this layer. One may either make this layer thicker or thinner. - // By making the layer thicker, one will decrease the number of discrete layers with the price of extruding a bit too thick bridges. - // By making the layer thinner, one adds one more discrete layer. - layer_new.print_z = top_contacts[top_idx]->print_z; - layer_new.height -= diff; - } - break; - } - } - } - -#ifdef SLIC3R_DEBUG - Slic3r::SVG::export_expolygons( - debug_out_path("support-bottom-contacts-%d-%lf.svg", iRun, layer_new.print_z), - union_ex(layer_new.polygons)); -#endif /* SLIC3R_DEBUG */ - - // Trim the already created base layers above the current layer intersecting with the new bottom contacts layer. - //FIXME Maybe this is no more needed, as the overlapping base layers are trimmed by the bottom layers at the final stage? - touching = expand(touching, float(SCALED_EPSILON)); - for (int layer_id_above = layer_id + 1; layer_id_above < int(object.total_layer_count()); ++ layer_id_above) { - const Layer &layer_above = *object.layers()[layer_id_above]; - if (layer_above.print_z > layer_new.print_z - EPSILON) - break; - if (Polygons &above = layer_support_areas[layer_id_above]; ! above.empty()) { -#ifdef SLIC3R_DEBUG - SVG::export_expolygons(debug_out_path("support-support-areas-raw-before-trimming-%d-with-%f-%lf.svg", iRun, layer.print_z, layer_above.print_z), - { { { union_ex(touching) }, { "touching", "blue", 0.5f } }, - { { union_safety_offset_ex(above) }, { "above", "red", "black", "", scaled(0.1f), 0.5f } } }); -#endif /* SLIC3R_DEBUG */ - above = diff(above, touching); -#ifdef SLIC3R_DEBUG - Slic3r::SVG::export_expolygons( - debug_out_path("support-support-areas-raw-after-trimming-%d-with-%f-%lf.svg", iRun, layer.print_z, layer_above.print_z), - union_ex(above)); -#endif /* SLIC3R_DEBUG */ - } - } - - return &layer_new; -} - -// Returns polygons to print + polygons to propagate downwards. -// Called twice: First for normal supports, possibly trimmed by "on build plate only", second for support enforcers not trimmed by "on build plate only". -static inline std::pair project_support_to_grid(const Layer &layer, const SupportGridParams &grid_params, const Polygons &overhangs, Polygons *layer_buildplate_covered -#ifdef SLIC3R_DEBUG - , size_t iRun, size_t layer_id, const char *debug_name -#endif /* SLIC3R_DEBUG */ -) -{ - // Remove the areas that touched from the projection that will continue on next, lower, top surfaces. -// Polygons trimming = union_(to_polygons(layer.slices), touching, true); - Polygons trimming = layer_buildplate_covered ? std::move(*layer_buildplate_covered) : offset(layer.lslices, float(SCALED_EPSILON)); - Polygons overhangs_projection = diff(overhangs, trimming); - -#ifdef SLIC3R_DEBUG - SVG::export_expolygons(debug_out_path("support-support-areas-%s-raw-%d-%lf.svg", debug_name, iRun, layer.print_z), - { { { union_ex(trimming) }, { "trimming", "blue", 0.5f } }, - { { union_safety_offset_ex(overhangs_projection) }, { "overhangs_projection", "red", "black", "", scaled(0.1f), 0.5f } } }); -#endif /* SLIC3R_DEBUG */ - - remove_sticks(overhangs_projection); - remove_degenerate(overhangs_projection); - -#ifdef SLIC3R_DEBUG - SVG::export_expolygons(debug_out_path("support-support-areas-%s-raw-cleaned-%d-%lf.svg", debug_name, iRun, layer.print_z), - { { { union_ex(trimming) }, { "trimming", "blue", 0.5f } }, - { { union_ex(overhangs_projection) }, { "overhangs_projection", "red", "black", "", scaled(0.1f), 0.5f } } }); -#endif /* SLIC3R_DEBUG */ - - SupportGridPattern support_grid_pattern(&overhangs_projection, &trimming, grid_params); - tbb::task_group task_group_inner; - - std::pair out; - - // 1) Cache the slice of a support volume. The support volume is expanded by 1/2 of support material flow spacing - // to allow a placement of suppot zig-zag snake along the grid lines. - task_group_inner.run([&grid_params, &support_grid_pattern, &out -#ifdef SLIC3R_DEBUG - , &layer, layer_id, iRun, debug_name -#endif /* SLIC3R_DEBUG */ - ] { - out.first = support_grid_pattern.extract_support(grid_params.expansion_to_slice, true -#ifdef SLIC3R_DEBUG - , (std::string(debug_name) + "_support_area").c_str(), iRun, layer_id, layer.print_z -#endif // SLIC3R_DEBUG - ); -#ifdef SLIC3R_DEBUG - Slic3r::SVG::export_expolygons( - debug_out_path("support-layer_support_area-gridded-%s-%d-%lf.svg", debug_name, iRun, layer.print_z), - union_ex(out.first)); -#endif /* SLIC3R_DEBUG */ - }); - - // 2) Support polygons will be projected down. To keep the interface and base layers from growing, return a contour a tiny bit smaller than the grid cells. - task_group_inner.run([&grid_params, &support_grid_pattern, &out -#ifdef SLIC3R_DEBUG - , &layer, layer_id, &overhangs_projection, &trimming, iRun, debug_name -#endif /* SLIC3R_DEBUG */ - ] { - out.second = support_grid_pattern.extract_support(grid_params.expansion_to_propagate, true -#ifdef SLIC3R_DEBUG - , "support_projection", iRun, layer_id, layer.print_z -#endif // SLIC3R_DEBUG - ); -#ifdef SLIC3R_DEBUG - Slic3r::SVG::export_expolygons( - debug_out_path("support-projection_new-gridded-%d-%lf.svg", iRun, layer.print_z), - union_ex(out.second)); -#endif /* SLIC3R_DEBUG */ -#ifdef SLIC3R_DEBUG - SVG::export_expolygons(debug_out_path("support-projection_new-gridded-%d-%lf.svg", iRun, layer.print_z), - { { { union_ex(trimming) }, { "trimming", "gray", 0.5f } }, - { { union_safety_offset_ex(overhangs_projection) }, { "overhangs_projection", "blue", 0.5f } }, - { { union_safety_offset_ex(out.second) }, { "projection_new", "red", "black", "", scaled(0.1f), 0.5f } } }); -#endif /* SLIC3R_DEBUG */ - }); - - task_group_inner.wait(); - return out; -} - -// Generate bottom contact layers supporting the top contact layers. -// For a soluble interface material synchronize the layer heights with the object, -// otherwise set the layer height to a bridging flow of a support interface nozzle. -PrintObjectSupportMaterial::MyLayersPtr PrintObjectSupportMaterial::bottom_contact_layers_and_layer_support_areas( - const PrintObject &object, const MyLayersPtr &top_contacts, std::vector &buildplate_covered, - MyLayerStorage &layer_storage, std::vector &layer_support_areas) const -{ - if (top_contacts.empty()) - return MyLayersPtr(); - -#ifdef SLIC3R_DEBUG - static size_t s_iRun = 0; - size_t iRun = s_iRun ++; -#endif /* SLIC3R_DEBUG */ - - //FIXME higher expansion_to_slice here? why? - //const auto expansion_to_slice = m_support_material_flow.scaled_spacing() / 2 + 25; - const SupportGridParams grid_params(*m_object_config, m_support_params.support_material_flow); - const bool buildplate_only = ! buildplate_covered.empty(); - - // Allocate empty surface areas, one per object layer. - layer_support_areas.assign(object.total_layer_count(), Polygons()); - - // find object top surfaces - // we'll use them to clip our support and detect where does it stick - MyLayersPtr bottom_contacts; - - // There is some support to be built, if there are non-empty top surfaces detected. - // Sum of unsupported contact areas above the current layer.print_z. - Polygons overhangs_projection; - // Sum of unsupported enforcer contact areas above the current layer.print_z. - // Only used if "supports on build plate only" is enabled and both automatic and support enforcers are enabled. - Polygons enforcers_projection; - // Last top contact layer visited when collecting the projection of contact areas. - int contact_idx = int(top_contacts.size()) - 1; - for (int layer_id = int(object.total_layer_count()) - 2; layer_id >= 0; -- layer_id) { - BOOST_LOG_TRIVIAL(trace) << "Support generator - bottom_contact_layers - layer " << layer_id; - const Layer &layer = *object.get_layer(layer_id); - // Collect projections of all contact areas above or at the same level as this top surface. -#ifdef SLIC3R_DEBUG - Polygons polygons_new; - Polygons enforcers_new; -#endif // SLIC3R_DEBUG - for (; contact_idx >= 0 && top_contacts[contact_idx]->print_z > layer.print_z - EPSILON; -- contact_idx) { - MyLayer &top_contact = *top_contacts[contact_idx]; -#ifndef SLIC3R_DEBUG - Polygons polygons_new; - Polygons enforcers_new; -#endif // SLIC3R_DEBUG - // Contact surfaces are expanded away from the object, trimmed by the object. - // Use a slight positive offset to overlap the touching regions. -#if 0 - // Merge and collect the contact polygons. The contact polygons are inflated, but not extended into a grid form. - polygons_append(polygons_new, offset(*top_contact.contact_polygons, SCALED_EPSILON)); - if (top_contact.enforcer_polygons) - polygons_append(enforcers_new, offset(*top_contact.enforcer_polygons, SCALED_EPSILON)); -#else - // Consume the contact_polygons. The contact polygons are already expanded into a grid form, and they are a tiny bit smaller - // than the grid cells. - polygons_append(polygons_new, std::move(*top_contact.contact_polygons)); - if (top_contact.enforcer_polygons) - polygons_append(enforcers_new, std::move(*top_contact.enforcer_polygons)); -#endif - // These are the overhang surfaces. They are touching the object and they are not expanded away from the object. - // Use a slight positive offset to overlap the touching regions. - polygons_append(polygons_new, expand(*top_contact.overhang_polygons, float(SCALED_EPSILON))); - polygons_append(overhangs_projection, union_(polygons_new)); - polygons_append(enforcers_projection, enforcers_new); - } - if (overhangs_projection.empty() && enforcers_projection.empty()) - continue; - - // Overhangs_projection will be filled in asynchronously, move it away. - Polygons overhangs_projection_raw = union_(std::move(overhangs_projection)); - Polygons enforcers_projection_raw = union_(std::move(enforcers_projection)); - - tbb::task_group task_group; - const Polygons &overhangs_for_bottom_contacts = buildplate_only ? enforcers_projection_raw : overhangs_projection_raw; - if (! overhangs_for_bottom_contacts.empty()) - // Find the bottom contact layers above the top surfaces of this layer. - task_group.run([this, &object, &layer, &top_contacts, contact_idx, &layer_storage, &layer_support_areas, &bottom_contacts, &overhangs_for_bottom_contacts - #ifdef SLIC3R_DEBUG - , iRun, &polygons_new - #endif // SLIC3R_DEBUG - ] { - // Find the bottom contact layers above the top surfaces of this layer. - MyLayer *layer_new = detect_bottom_contacts( - m_slicing_params, m_support_params, object, layer, top_contacts, contact_idx, layer_storage, layer_support_areas, overhangs_for_bottom_contacts -#ifdef SLIC3R_DEBUG - , iRun, polygons_new -#endif // SLIC3R_DEBUG - ); - if (layer_new) - bottom_contacts.push_back(layer_new); - }); - - Polygons &layer_support_area = layer_support_areas[layer_id]; - Polygons *layer_buildplate_covered = buildplate_covered.empty() ? nullptr : &buildplate_covered[layer_id]; - // Filtering the propagated support columns to two extrusions, overlapping by maximum 20%. -// float column_propagation_filtering_radius = scaled(0.8 * 0.5 * (m_support_params.support_material_flow.spacing() + m_support_params.support_material_flow.width())); - task_group.run([&grid_params, &overhangs_projection, &overhangs_projection_raw, &layer, &layer_support_area, layer_buildplate_covered /* , column_propagation_filtering_radius */ -#ifdef SLIC3R_DEBUG - , iRun, layer_id -#endif /* SLIC3R_DEBUG */ - ] { - // buildplate_covered[layer_id] will be consumed here. - std::tie(layer_support_area, overhangs_projection) = project_support_to_grid(layer, grid_params, overhangs_projection_raw, layer_buildplate_covered -#ifdef SLIC3R_DEBUG - , iRun, layer_id, "general" -#endif /* SLIC3R_DEBUG */ - ); - // When propagating support areas downwards, stop propagating the support column if it becomes too thin to be printable. - //overhangs_projection = opening(overhangs_projection, column_propagation_filtering_radius); - }); - - Polygons layer_support_area_enforcers; - if (! enforcers_projection.empty()) - // Project the enforcers polygons downwards, don't trim them with the "buildplate only" polygons. - task_group.run([&grid_params, &enforcers_projection, &enforcers_projection_raw, &layer, &layer_support_area_enforcers -#ifdef SLIC3R_DEBUG - , iRun, layer_id -#endif /* SLIC3R_DEBUG */ - ]{ - std::tie(layer_support_area_enforcers, enforcers_projection) = project_support_to_grid(layer, grid_params, enforcers_projection_raw, nullptr -#ifdef SLIC3R_DEBUG - , iRun, layer_id, "enforcers" -#endif /* SLIC3R_DEBUG */ - ); - }); - - task_group.wait(); - - if (! layer_support_area_enforcers.empty()) { - if (layer_support_area.empty()) - layer_support_area = std::move(layer_support_area_enforcers); - else - layer_support_area = union_(layer_support_area, layer_support_area_enforcers); - } - } // over all layers downwards - - std::reverse(bottom_contacts.begin(), bottom_contacts.end()); - trim_support_layers_by_object(object, bottom_contacts, m_slicing_params.gap_support_object, m_slicing_params.gap_object_support, m_support_params.gap_xy); - return bottom_contacts; -} - -template -IndexType idx_higher_or_equal(const std::vector& vec, IndexType idx, FN_HIGHER_EQUAL fn_higher_equal) -{ - return Layer::idx_higher_or_equal(vec.begin(), vec.end(), idx, fn_higher_equal); -} - -// FN_LOWER_EQUAL: the provided object pointer has a Z value <= of an internal threshold. -// Find the first item with Z value <= of an internal threshold of fn_lower_equal. -// If no vec item with Z value <= of an internal threshold of fn_lower_equal is found, return -1. -// If the initial idx is < -1, then use binary search. -// Otherwise search linearly downwards. -template -int idx_lower_or_equal(IT begin, IT end, int idx, FN_LOWER_EQUAL fn_lower_equal) -{ - auto size = int(end - begin); - if (size == 0) { - idx = -1; - } else if (idx < -1) { - // First of the batch of layers per thread pool invocation. Use binary search. - int idx_low = 0; - int idx_high = std::max(0, size - 1); - while (idx_low + 1 < idx_high) { - int idx_mid = (idx_low + idx_high) / 2; - if (fn_lower_equal(begin[idx_mid])) - idx_low = idx_mid; - else - idx_high = idx_mid; - } - idx = fn_lower_equal(begin[idx_high]) ? idx_high : - (fn_lower_equal(begin[idx_low ]) ? idx_low : -1); - } else { - // For the other layers of this batch of layers, search incrementally, which is cheaper than the binary search. - while (idx >= 0 && ! fn_lower_equal(begin[idx])) - -- idx; - } - return idx; -} -template -int idx_lower_or_equal(const std::vector &vec, int idx, FN_LOWER_EQUAL fn_lower_equal) -{ - return idx_lower_or_equal(vec.begin(), vec.end(), idx, fn_lower_equal); -} - -// Trim the top_contacts layers with the bottom_contacts layers if they overlap, so there would not be enough vertical space for both of them. -void PrintObjectSupportMaterial::trim_top_contacts_by_bottom_contacts( - const PrintObject &object, const MyLayersPtr &bottom_contacts, MyLayersPtr &top_contacts) const -{ - tbb::parallel_for(tbb::blocked_range(0, int(top_contacts.size())), - [&bottom_contacts, &top_contacts](const tbb::blocked_range& range) { - int idx_bottom_overlapping_first = -2; - // For all top contact layers, counting downwards due to the way idx_higher_or_equal caches the last index to avoid repeated binary search. - for (int idx_top = range.end() - 1; idx_top >= range.begin(); -- idx_top) { - MyLayer &layer_top = *top_contacts[idx_top]; - // Find the first bottom layer overlapping with layer_top. - idx_bottom_overlapping_first = idx_lower_or_equal(bottom_contacts, idx_bottom_overlapping_first, [&layer_top](const MyLayer *layer_bottom){ return layer_bottom->bottom_print_z() - EPSILON <= layer_top.bottom_z; }); - // For all top contact layers overlapping with the thick bottom contact layer: - for (int idx_bottom_overlapping = idx_bottom_overlapping_first; idx_bottom_overlapping >= 0; -- idx_bottom_overlapping) { - const MyLayer &layer_bottom = *bottom_contacts[idx_bottom_overlapping]; - assert(layer_bottom.bottom_print_z() - EPSILON <= layer_top.bottom_z); - if (layer_top.print_z < layer_bottom.print_z + EPSILON) { - // Layers overlap. Trim layer_top with layer_bottom. - layer_top.polygons = diff(layer_top.polygons, layer_bottom.polygons); - } else - break; - } - } - }); -} - -PrintObjectSupportMaterial::MyLayersPtr PrintObjectSupportMaterial::raft_and_intermediate_support_layers( - const PrintObject &object, - const MyLayersPtr &bottom_contacts, - const MyLayersPtr &top_contacts, - MyLayerStorage &layer_storage) const -{ - MyLayersPtr intermediate_layers; - - // Collect and sort the extremes (bottoms of the top contacts and tops of the bottom contacts). - MyLayersPtr extremes; - extremes.reserve(top_contacts.size() + bottom_contacts.size()); - for (size_t i = 0; i < top_contacts.size(); ++ i) - // Bottoms of the top contact layers. In case of non-soluble supports, - // the top contact layer thickness is not known yet. - extremes.push_back(top_contacts[i]); - for (size_t i = 0; i < bottom_contacts.size(); ++ i) - // Tops of the bottom contact layers. - extremes.push_back(bottom_contacts[i]); - if (extremes.empty()) - return intermediate_layers; - - auto layer_extreme_lower = [](const MyLayer *l1, const MyLayer *l2) { - coordf_t z1 = l1->extreme_z(); - coordf_t z2 = l2->extreme_z(); - // If the layers are aligned, return the top contact surface first. - return z1 < z2 || (z1 == z2 && l1->layer_type == PrintObjectSupportMaterial::sltTopContact && l2->layer_type == PrintObjectSupportMaterial::sltBottomContact); - }; - std::sort(extremes.begin(), extremes.end(), layer_extreme_lower); - - assert(extremes.empty() || - (extremes.front()->extreme_z() > m_slicing_params.raft_interface_top_z - EPSILON && - (m_slicing_params.raft_layers() == 1 || // only raft contact layer - extremes.front()->layer_type == sltTopContact || // first extreme is a top contact layer - extremes.front()->extreme_z() > m_slicing_params.first_print_layer_height - EPSILON))); - - bool synchronize = this->synchronize_layers(); - -#ifdef _DEBUG - // Verify that the extremes are separated by m_support_layer_height_min. - for (size_t i = 1; i < extremes.size(); ++ i) { - assert(extremes[i]->extreme_z() - extremes[i-1]->extreme_z() == 0. || - extremes[i]->extreme_z() - extremes[i-1]->extreme_z() > m_support_params.support_layer_height_min - EPSILON); - assert(extremes[i]->extreme_z() - extremes[i-1]->extreme_z() > 0. || - extremes[i]->layer_type == extremes[i-1]->layer_type || - (extremes[i]->layer_type == sltBottomContact && extremes[i - 1]->layer_type == sltTopContact)); - } -#endif - - // Generate intermediate layers. - // The first intermediate layer is the same as the 1st layer if there is no raft, - // or the bottom of the first intermediate layer is aligned with the bottom of the raft contact layer. - // Intermediate layers are always printed with a normal extrusion flow (non-bridging). - size_t idx_layer_object = 0; - size_t idx_extreme_first = 0; - if (! extremes.empty() && std::abs(extremes.front()->extreme_z() - m_slicing_params.raft_interface_top_z) < EPSILON) { - // This is a raft contact layer, its height has been decided in this->top_contact_layers(). - // Ignore this layer when calculating the intermediate support layers. - assert(extremes.front()->layer_type == sltTopContact); - ++ idx_extreme_first; - } - for (size_t idx_extreme = idx_extreme_first; idx_extreme < extremes.size(); ++ idx_extreme) { - MyLayer *extr2 = extremes[idx_extreme]; - coordf_t extr2z = extr2->extreme_z(); - if (std::abs(extr2z - m_slicing_params.first_print_layer_height) < EPSILON) { - // This is a bottom of a synchronized (or soluble) top contact layer, its height has been decided in this->top_contact_layers(). - assert(extr2->layer_type == sltTopContact); - assert(std::abs(extr2->bottom_z - m_slicing_params.first_print_layer_height) < EPSILON); - assert(extr2->print_z >= m_slicing_params.first_print_layer_height + m_support_params.support_layer_height_min - EPSILON); - if (intermediate_layers.empty() || intermediate_layers.back()->print_z < m_slicing_params.first_print_layer_height) { - MyLayer &layer_new = layer_allocate(layer_storage, sltIntermediate); - layer_new.bottom_z = 0.; - layer_new.print_z = m_slicing_params.first_print_layer_height; - layer_new.height = m_slicing_params.first_print_layer_height; - intermediate_layers.push_back(&layer_new); - } - continue; - } - assert(extr2z >= m_slicing_params.raft_interface_top_z + EPSILON); - assert(extr2z >= m_slicing_params.first_print_layer_height + EPSILON); - MyLayer *extr1 = (idx_extreme == idx_extreme_first) ? nullptr : extremes[idx_extreme - 1]; - // Fuse a support layer firmly to the raft top interface (not to the raft contacts). - coordf_t extr1z = (extr1 == nullptr) ? m_slicing_params.raft_interface_top_z : extr1->extreme_z(); - assert(extr2z >= extr1z); - assert(extr2z > extr1z || (extr1 != nullptr && extr2->layer_type == sltBottomContact)); - if (std::abs(extr1z) < EPSILON) { - // This layer interval starts with the 1st layer. Print the 1st layer using the prescribed 1st layer thickness. - // assert(! m_slicing_params.has_raft()); RaftingEdition: unclear where the issue is: assert fails with 1-layer raft & base supports - assert(intermediate_layers.empty() || intermediate_layers.back()->print_z <= m_slicing_params.first_print_layer_height); - // At this point only layers above first_print_layer_heigth + EPSILON are expected as the other cases were captured earlier. - assert(extr2z >= m_slicing_params.first_print_layer_height + EPSILON); - // Generate a new intermediate layer. - MyLayer &layer_new = layer_allocate(layer_storage, sltIntermediate); - layer_new.bottom_z = 0.; - layer_new.print_z = extr1z = m_slicing_params.first_print_layer_height; - layer_new.height = extr1z; - intermediate_layers.push_back(&layer_new); - // Continue printing the other layers up to extr2z. - } - coordf_t dist = extr2z - extr1z; - assert(dist >= 0.); - if (dist == 0.) - continue; - // The new layers shall be at least m_support_layer_height_min thick. - assert(dist >= m_support_params.support_layer_height_min - EPSILON); - if (synchronize) { - // Emit support layers synchronized with the object layers. - // Find the first object layer, which has its print_z in this support Z range. - while (idx_layer_object < object.layers().size() && object.layers()[idx_layer_object]->print_z < extr1z + EPSILON) - ++ idx_layer_object; - if (idx_layer_object == 0 && extr1z == m_slicing_params.raft_interface_top_z) { - // Insert one base support layer below the object. - MyLayer &layer_new = layer_allocate(layer_storage, sltIntermediate); - layer_new.print_z = m_slicing_params.object_print_z_min; - layer_new.bottom_z = m_slicing_params.raft_interface_top_z; - layer_new.height = layer_new.print_z - layer_new.bottom_z; - intermediate_layers.push_back(&layer_new); - } - // Emit all intermediate support layers synchronized with object layers up to extr2z. - for (; idx_layer_object < object.layers().size() && object.layers()[idx_layer_object]->print_z < extr2z + EPSILON; ++ idx_layer_object) { - MyLayer &layer_new = layer_allocate(layer_storage, sltIntermediate); - layer_new.print_z = object.layers()[idx_layer_object]->print_z; - layer_new.height = object.layers()[idx_layer_object]->height; - layer_new.bottom_z = (idx_layer_object > 0) ? object.layers()[idx_layer_object - 1]->print_z : (layer_new.print_z - layer_new.height); - assert(intermediate_layers.empty() || intermediate_layers.back()->print_z < layer_new.print_z + EPSILON); - intermediate_layers.push_back(&layer_new); - } - } else { - // Insert intermediate layers. - size_t n_layers_extra = size_t(ceil(dist / m_slicing_params.max_suport_layer_height)); - assert(n_layers_extra > 0); - coordf_t step = dist / coordf_t(n_layers_extra); - if (extr1 != nullptr && extr1->layer_type == sltTopContact && - extr1->print_z + m_support_params.support_layer_height_min > extr1->bottom_z + step) { - // The bottom extreme is a bottom of a top surface. Ensure that the gap - // between the 1st intermediate layer print_z and extr1->print_z is not too small. - assert(extr1->bottom_z + m_support_params.support_layer_height_min < extr1->print_z + EPSILON); - // Generate the first intermediate layer. - MyLayer &layer_new = layer_allocate(layer_storage, sltIntermediate); - layer_new.bottom_z = extr1->bottom_z; - layer_new.print_z = extr1z = extr1->print_z; - layer_new.height = extr1->height; - intermediate_layers.push_back(&layer_new); - dist = extr2z - extr1z; - n_layers_extra = size_t(ceil(dist / m_slicing_params.max_suport_layer_height)); - if (n_layers_extra == 0) - continue; - // Continue printing the other layers up to extr2z. - step = dist / coordf_t(n_layers_extra); - } - if (! m_slicing_params.soluble_interface && extr2->layer_type == sltTopContact) { - // This is a top interface layer, which does not have a height assigned yet. Do it now. - assert(extr2->height == 0.); - assert(extr1z > m_slicing_params.first_print_layer_height - EPSILON); - extr2->height = step; - extr2->bottom_z = extr2z = extr2->print_z - step; - if (-- n_layers_extra == 0) - continue; - } - coordf_t extr2z_large_steps = extr2z; - // Take the largest allowed step in the Z axis until extr2z_large_steps is reached. - for (size_t i = 0; i < n_layers_extra; ++ i) { - MyLayer &layer_new = layer_allocate(layer_storage, sltIntermediate); - if (i + 1 == n_layers_extra) { - // Last intermediate layer added. Align the last entered layer with extr2z_large_steps exactly. - layer_new.bottom_z = (i == 0) ? extr1z : intermediate_layers.back()->print_z; - layer_new.print_z = extr2z_large_steps; - layer_new.height = layer_new.print_z - layer_new.bottom_z; - } - else { - // Intermediate layer, not the last added. - layer_new.height = step; - layer_new.bottom_z = extr1z + i * step; - layer_new.print_z = layer_new.bottom_z + step; - } - assert(intermediate_layers.empty() || intermediate_layers.back()->print_z <= layer_new.print_z); - intermediate_layers.push_back(&layer_new); - } - } - } - -#ifdef _DEBUG - for (size_t i = 0; i < top_contacts.size(); ++i) - assert(top_contacts[i]->height > 0.); -#endif /* _DEBUG */ - -#if 0 // #ifdef SLIC3R_DEBUG - // check bounds - std::ofstream out; - out.open("./SVG/ns_bounds.txt"); - if (out.is_open()) { - if (!top_contacts.empty()) { - out << "### Top Contacts ###" << std::endl; - for (auto& t : top_contacts) { - out << t->print_z << std::endl; - } - } - if (!bottom_contacts.empty()) { - out << "### Bottome Contacts ###" << std::endl; - for (auto& b : bottom_contacts) { - out << b->print_z << std::endl; - } - } - if (!intermediate_layers.empty()) { - out << "### Intermediate Layers ###" << std::endl; - for (auto& i : intermediate_layers) { - out << i->print_z << std::endl; - } - } - out << "### Slice Layers ###" << std::endl; - for (size_t j = 0; j < object.layers().size(); ++j) { - out << object.layers()[j]->print_z << std::endl; - } - } -#endif /* SLIC3R_DEBUG */ - - return intermediate_layers; -} - -// At this stage there shall be intermediate_layers allocated between bottom_contacts and top_contacts, but they have no polygons assigned. -// Also the bottom/top_contacts shall have a layer thickness assigned already. -void PrintObjectSupportMaterial::generate_base_layers( - const PrintObject &object, - const MyLayersPtr &bottom_contacts, - const MyLayersPtr &top_contacts, - MyLayersPtr &intermediate_layers, - const std::vector &layer_support_areas) const -{ -#ifdef SLIC3R_DEBUG - static int iRun = 0; -#endif /* SLIC3R_DEBUG */ - - if (top_contacts.empty()) - // No top contacts -> no intermediate layers will be produced. - return; - - BOOST_LOG_TRIVIAL(debug) << "PrintObjectSupportMaterial::generate_base_layers() in parallel - start"; - tbb::parallel_for( - tbb::blocked_range(0, intermediate_layers.size()), - [&object, &bottom_contacts, &top_contacts, &intermediate_layers, &layer_support_areas](const tbb::blocked_range& range) { - // index -2 means not initialized yet, -1 means intialized and decremented to 0 and then -1. - int idx_top_contact_above = -2; - int idx_bottom_contact_overlapping = -2; - int idx_object_layer_above = -2; - // Counting down due to the way idx_lower_or_equal caches indices to avoid repeated binary search over the complete sequence. - for (int idx_intermediate = int(range.end()) - 1; idx_intermediate >= int(range.begin()); -- idx_intermediate) - { - BOOST_LOG_TRIVIAL(trace) << "Support generator - generate_base_layers - creating layer " << - idx_intermediate << " of " << intermediate_layers.size(); - MyLayer &layer_intermediate = *intermediate_layers[idx_intermediate]; - // Layers must be sorted by print_z. - assert(idx_intermediate == 0 || layer_intermediate.print_z >= intermediate_layers[idx_intermediate - 1]->print_z); - - // Find a top_contact layer touching the layer_intermediate from above, if any, and collect its polygons into polygons_new. - // New polygons for layer_intermediate. - Polygons polygons_new; - - // Use the precomputed layer_support_areas. "idx_object_layer_above": above means above since the last iteration, not above after this call. - idx_object_layer_above = idx_lower_or_equal(object.layers().begin(), object.layers().end(), idx_object_layer_above, - [&layer_intermediate](const Layer* layer) { return layer->print_z <= layer_intermediate.print_z + EPSILON; }); - - // Polygons to trim polygons_new. - Polygons polygons_trimming; - - // Trimming the base layer with any overlapping top layer. - // Following cases are recognized: - // 1) top.bottom_z >= base.top_z -> No overlap, no trimming needed. - // 2) base.bottom_z >= top.print_z -> No overlap, no trimming needed. - // 3) base.print_z > top.print_z && base.bottom_z >= top.bottom_z -> Overlap, which will be solved inside generate_toolpaths() by reducing the base layer height where it overlaps the top layer. No trimming needed here. - // 4) base.print_z > top.bottom_z && base.bottom_z < top.bottom_z -> Base overlaps with top.bottom_z. This must not happen. - // 5) base.print_z <= top.print_z && base.bottom_z >= top.bottom_z -> Base is fully inside top. Trim base by top. - idx_top_contact_above = idx_lower_or_equal(top_contacts, idx_top_contact_above, - [&layer_intermediate](const MyLayer *layer){ return layer->bottom_z <= layer_intermediate.print_z - EPSILON; }); - // Collect all the top_contact layer intersecting with this layer. - for (int idx_top_contact_overlapping = idx_top_contact_above; idx_top_contact_overlapping >= 0; -- idx_top_contact_overlapping) { - MyLayer &layer_top_overlapping = *top_contacts[idx_top_contact_overlapping]; - if (layer_top_overlapping.print_z < layer_intermediate.bottom_z + EPSILON) - break; - // Base must not overlap with top.bottom_z. - assert(! (layer_intermediate.print_z > layer_top_overlapping.bottom_z + EPSILON && layer_intermediate.bottom_z < layer_top_overlapping.bottom_z - EPSILON)); - if (layer_intermediate.print_z <= layer_top_overlapping.print_z + EPSILON && layer_intermediate.bottom_z >= layer_top_overlapping.bottom_z - EPSILON) - // Base is fully inside top. Trim base by top. - polygons_append(polygons_trimming, layer_top_overlapping.polygons); - } - - if (idx_object_layer_above < 0) { - // layer_support_areas are synchronized with object layers and they contain projections of the contact layers above them. - // This intermediate layer is not above any object layer, thus there is no information in layer_support_areas about - // towers supporting contact layers intersecting the first object layer. Project these contact layers now. - polygons_new = layer_support_areas.front(); - double first_layer_z = object.layers().front()->print_z; - for (int i = idx_top_contact_above + 1; i < int(top_contacts.size()); ++ i) { - MyLayer &contacts = *top_contacts[i]; - if (contacts.print_z > first_layer_z + EPSILON) - break; - assert(contacts.bottom_z > layer_intermediate.print_z - EPSILON); - polygons_append(polygons_new, contacts.polygons); - } - } else - polygons_new = layer_support_areas[idx_object_layer_above]; - - // Trimming the base layer with any overlapping bottom layer. - // Following cases are recognized: - // 1) bottom.bottom_z >= base.top_z -> No overlap, no trimming needed. - // 2) base.bottom_z >= bottom.print_z -> No overlap, no trimming needed. - // 3) base.print_z > bottom.bottom_z && base.bottom_z < bottom.bottom_z -> Overlap, which will be solved inside generate_toolpaths() by reducing the bottom layer height where it overlaps the base layer. No trimming needed here. - // 4) base.print_z > bottom.print_z && base.bottom_z >= bottom.print_z -> Base overlaps with bottom.print_z. This must not happen. - // 5) base.print_z <= bottom.print_z && base.bottom_z >= bottom.bottom_z -> Base is fully inside top. Trim base by top. - idx_bottom_contact_overlapping = idx_lower_or_equal(bottom_contacts, idx_bottom_contact_overlapping, - [&layer_intermediate](const MyLayer *layer){ return layer->bottom_print_z() <= layer_intermediate.print_z - EPSILON; }); - // Collect all the bottom_contacts layer intersecting with this layer. - for (int i = idx_bottom_contact_overlapping; i >= 0; -- i) { - MyLayer &layer_bottom_overlapping = *bottom_contacts[i]; - if (layer_bottom_overlapping.print_z < layer_intermediate.bottom_print_z() + EPSILON) - break; - // Base must not overlap with bottom.top_z. - assert(! (layer_intermediate.print_z > layer_bottom_overlapping.print_z + EPSILON && layer_intermediate.bottom_z < layer_bottom_overlapping.print_z - EPSILON)); - if (layer_intermediate.print_z <= layer_bottom_overlapping.print_z + EPSILON && layer_intermediate.bottom_z >= layer_bottom_overlapping.bottom_print_z() - EPSILON) - // Base is fully inside bottom. Trim base by bottom. - polygons_append(polygons_trimming, layer_bottom_overlapping.polygons); - } - - #ifdef SLIC3R_DEBUG - { - BoundingBox bbox = get_extents(polygons_new); - bbox.merge(get_extents(polygons_trimming)); - ::Slic3r::SVG svg(debug_out_path("support-intermediate-layers-raw-%d-%lf.svg", iRun, layer_intermediate.print_z), bbox); - svg.draw(union_ex(polygons_new), "blue", 0.5f); - svg.draw(to_polylines(polygons_new), "blue"); - svg.draw(union_safety_offset_ex(polygons_trimming), "red", 0.5f); - svg.draw(to_polylines(polygons_trimming), "red"); - } - #endif /* SLIC3R_DEBUG */ - - // Trim the polygons, store them. - if (polygons_trimming.empty()) - layer_intermediate.polygons = std::move(polygons_new); - else - layer_intermediate.polygons = diff( - polygons_new, - polygons_trimming, - ApplySafetyOffset::Yes); // safety offset to merge the touching source polygons - layer_intermediate.layer_type = sltBase; - - #if 0 - // coordf_t fillet_radius_scaled = scale_(m_object_config->support_base_pattern_spacing); - // Fillet the base polygons and trim them again with the top, interface and contact layers. - $base->{$i} = diff( - offset2( - $base->{$i}, - $fillet_radius_scaled, - -$fillet_radius_scaled, - # Use a geometric offsetting for filleting. - JT_ROUND, - 0.2*$fillet_radius_scaled), - $trim_polygons, - false); // don't apply the safety offset. - } - #endif - } - }); - BOOST_LOG_TRIVIAL(debug) << "PrintObjectSupportMaterial::generate_base_layers() in parallel - end"; - -#ifdef SLIC3R_DEBUG - for (MyLayersPtr::const_iterator it = intermediate_layers.begin(); it != intermediate_layers.end(); ++it) - ::Slic3r::SVG::export_expolygons( - debug_out_path("support-intermediate-layers-untrimmed-%d-%lf.svg", iRun, (*it)->print_z), - union_ex((*it)->polygons)); - ++ iRun; -#endif /* SLIC3R_DEBUG */ - - this->trim_support_layers_by_object(object, intermediate_layers, m_slicing_params.gap_support_object, m_slicing_params.gap_object_support, m_support_params.gap_xy); -} - -void PrintObjectSupportMaterial::trim_support_layers_by_object( - const PrintObject &object, - MyLayersPtr &support_layers, - const coordf_t gap_extra_above, - const coordf_t gap_extra_below, - const coordf_t gap_xy) const -{ - const float gap_xy_scaled = float(scale_(gap_xy)); - - // Collect non-empty layers to be processed in parallel. - // This is a good idea as pulling a thread from a thread pool for an empty task is expensive. - MyLayersPtr nonempty_layers; - nonempty_layers.reserve(support_layers.size()); - for (size_t idx_layer = 0; idx_layer < support_layers.size(); ++ idx_layer) { - MyLayer *support_layer = support_layers[idx_layer]; - if (! support_layer->polygons.empty() && support_layer->print_z >= m_slicing_params.raft_contact_top_z + EPSILON) - // Non-empty support layer and not a raft layer. - nonempty_layers.push_back(support_layer); - } - - // For all intermediate support layers: - BOOST_LOG_TRIVIAL(debug) << "PrintObjectSupportMaterial::trim_support_layers_by_object() in parallel - start"; - tbb::parallel_for( - tbb::blocked_range(0, nonempty_layers.size()), - [this, &object, &nonempty_layers, gap_extra_above, gap_extra_below, gap_xy_scaled](const tbb::blocked_range& range) { - size_t idx_object_layer_overlapping = size_t(-1); - - auto is_layers_overlap = [](const MyLayer& support_layer, const Layer& object_layer, coordf_t bridging_height = 0.f) -> bool { - if (std::abs(support_layer.print_z - object_layer.print_z) < EPSILON) - return true; - - coordf_t object_lh = bridging_height > EPSILON ? bridging_height : object_layer.height; - if (support_layer.print_z < object_layer.print_z && support_layer.print_z > object_layer.print_z - object_lh) - return true; - - if (support_layer.print_z > object_layer.print_z && support_layer.bottom_z < object_layer.print_z - EPSILON) - return true; - - return false; - }; - for (size_t idx_layer = range.begin(); idx_layer < range.end(); ++ idx_layer) { - MyLayer &support_layer = *nonempty_layers[idx_layer]; - // BOOST_LOG_TRIVIAL(trace) << "Support generator - trim_support_layers_by_object - trimmming non-empty layer " << idx_layer << " of " << nonempty_layers.size(); - assert(! support_layer.polygons.empty() && support_layer.print_z >= m_slicing_params.raft_contact_top_z + EPSILON); - // Find the overlapping object layers including the extra above / below gap. - coordf_t z_threshold = support_layer.bottom_print_z() - gap_extra_below + EPSILON; - idx_object_layer_overlapping = Layer::idx_higher_or_equal( - object.layers().begin(), object.layers().end(), idx_object_layer_overlapping, - [z_threshold](const Layer *layer){ return layer->print_z >= z_threshold; }); - // Collect all the object layers intersecting with this layer. - Polygons polygons_trimming; - size_t i = idx_object_layer_overlapping; - for (; i < object.layers().size(); ++ i) { - const Layer &object_layer = *object.layers()[i]; - if (object_layer.bottom_z() > support_layer.print_z + gap_extra_above - EPSILON) - break; - - bool is_overlap = is_layers_overlap(support_layer, object_layer); - for (const ExPolygon& expoly : object_layer.lslices) { - // BBS - bool is_sharptail = !intersection_ex({ expoly }, object_layer.sharp_tails).empty(); - coordf_t trimming_offset = is_sharptail ? scale_(sharp_tail_xy_gap) : - is_overlap ? gap_xy_scaled : - scale_(no_overlap_xy_gap); - polygons_append(polygons_trimming, offset({ expoly }, trimming_offset, SUPPORT_SURFACES_OFFSET_PARAMETERS)); - } - } - if (! m_slicing_params.soluble_interface && m_object_config->thick_bridges) { - // Collect all bottom surfaces, which will be extruded with a bridging flow. - for (; i < object.layers().size(); ++ i) { - const Layer &object_layer = *object.layers()[i]; - bool some_region_overlaps = false; - for (LayerRegion *region : object_layer.regions()) { - coordf_t bridging_height = region->region().bridging_height_avg(*m_print_config); - if (object_layer.print_z - bridging_height > support_layer.print_z + gap_extra_above - EPSILON) - break; - some_region_overlaps = true; - - bool is_overlap = is_layers_overlap(support_layer, object_layer, bridging_height); - coordf_t trimming_offset = is_overlap ? gap_xy_scaled : scale_(no_overlap_xy_gap); - polygons_append(polygons_trimming, - offset(region->fill_surfaces.filter_by_type(stBottomBridge), trimming_offset, SUPPORT_SURFACES_OFFSET_PARAMETERS)); - if (region->region().config().detect_overhang_wall.value) - // Add bridging perimeters. - SupportMaterialInternal::collect_bridging_perimeter_areas(region->perimeters, gap_xy_scaled, polygons_trimming); - } - if (! some_region_overlaps) - break; - } - } - - // $layer->slices contains the full shape of layer, thus including - // perimeter's width. $support contains the full shape of support - // material, thus including the width of its foremost extrusion. - // We leave a gap equal to a full extrusion width. - support_layer.polygons = diff(support_layer.polygons, polygons_trimming); - } - }); - BOOST_LOG_TRIVIAL(debug) << "PrintObjectSupportMaterial::trim_support_layers_by_object() in parallel - end"; -} - -PrintObjectSupportMaterial::MyLayersPtr PrintObjectSupportMaterial::generate_raft_base( - const PrintObject &object, - const MyLayersPtr &top_contacts, - const MyLayersPtr &interface_layers, - const MyLayersPtr &base_interface_layers, - const MyLayersPtr &base_layers, - MyLayerStorage &layer_storage) const -{ - // If there is brim to be generated, calculate the trimming regions. - Polygons brim; - if (object.has_brim()) { - // Calculate the area covered by the brim. - const BrimType brim_type = object.config().brim_type; - const bool brim_outer = brim_type == btOuterOnly || brim_type == btOuterAndInner; - const bool brim_inner = brim_type == btInnerOnly || brim_type == btOuterAndInner; - // BBS: the pattern of raft and brim are the same, thus the brim can be serpated by support raft. - const auto brim_object_gap = scaled(object.config().brim_object_gap.value); - //const auto brim_object_gap = scaled(object.config().brim_object_gap.value + object.config().brim_width.value); - for (const ExPolygon &ex : object.layers().front()->lslices) { - if (brim_outer && brim_inner) - polygons_append(brim, offset(ex, brim_object_gap)); - else { - if (brim_outer) - polygons_append(brim, offset(ex.contour, brim_object_gap, ClipperLib::jtRound, float(scale_(0.1)))); - else - brim.emplace_back(ex.contour); - if (brim_inner) { - Polygons holes = ex.holes; - polygons_reverse(holes); - holes = shrink(holes, brim_object_gap, ClipperLib::jtRound, float(scale_(0.1))); - polygons_reverse(holes); - polygons_append(brim, std::move(holes)); - } else - polygons_append(brim, ex.holes); - } - } - brim = union_(brim); - } - - // How much to inflate the support columns to be stable. This also applies to the 1st layer, if no raft layers are to be printed. - const float inflate_factor_fine = float(scale_((m_slicing_params.raft_layers() > 1) ? 0.5 : EPSILON)); - const float inflate_factor_1st_layer = std::max(0.f, float(scale_(object.config().raft_first_layer_expansion)) - inflate_factor_fine); - MyLayer *contacts = top_contacts .empty() ? nullptr : top_contacts .front(); - MyLayer *interfaces = interface_layers .empty() ? nullptr : interface_layers .front(); - MyLayer *base_interfaces = base_interface_layers.empty() ? nullptr : base_interface_layers.front(); - MyLayer *columns_base = base_layers .empty() ? nullptr : base_layers .front(); - if (contacts != nullptr && contacts->print_z > std::max(m_slicing_params.first_print_layer_height, m_slicing_params.raft_contact_top_z) + EPSILON) - // This is not the raft contact layer. - contacts = nullptr; - if (interfaces != nullptr && interfaces->bottom_print_z() > m_slicing_params.raft_interface_top_z + EPSILON) - // This is not the raft column base layer. - interfaces = nullptr; - if (base_interfaces != nullptr && base_interfaces->bottom_print_z() > m_slicing_params.raft_interface_top_z + EPSILON) - // This is not the raft column base layer. - base_interfaces = nullptr; - if (columns_base != nullptr && columns_base->bottom_print_z() > m_slicing_params.raft_interface_top_z + EPSILON) - // This is not the raft interface layer. - columns_base = nullptr; - - Polygons interface_polygons; - if (contacts != nullptr && ! contacts->polygons.empty()) - polygons_append(interface_polygons, expand(contacts->polygons, inflate_factor_fine, SUPPORT_SURFACES_OFFSET_PARAMETERS)); - if (interfaces != nullptr && ! interfaces->polygons.empty()) - polygons_append(interface_polygons, expand(interfaces->polygons, inflate_factor_fine, SUPPORT_SURFACES_OFFSET_PARAMETERS)); - if (base_interfaces != nullptr && ! base_interfaces->polygons.empty()) - polygons_append(interface_polygons, expand(base_interfaces->polygons, inflate_factor_fine, SUPPORT_SURFACES_OFFSET_PARAMETERS)); - - // Output vector. - MyLayersPtr raft_layers; - - if (m_slicing_params.raft_layers() > 1) { - Polygons base; - Polygons columns; - if (columns_base != nullptr) { - base = columns_base->polygons; - columns = base; - if (! interface_polygons.empty()) - // Trim the 1st layer columns with the inflated interface polygons. - columns = diff(columns, interface_polygons); - } - if (! interface_polygons.empty()) { - // Merge the untrimmed columns base with the expanded raft interface, to be used for the support base and interface. - base = union_(base, interface_polygons); - } - // Do not add the raft contact layer, only add the raft layers below the contact layer. - // Insert the 1st layer. - { - MyLayer &new_layer = layer_allocate(layer_storage, (m_slicing_params.base_raft_layers > 0) ? sltRaftBase : sltRaftInterface); - raft_layers.push_back(&new_layer); - new_layer.print_z = m_slicing_params.first_print_layer_height; - new_layer.height = m_slicing_params.first_print_layer_height; - new_layer.bottom_z = 0.; - new_layer.polygons = inflate_factor_1st_layer > 0 ? expand(base, inflate_factor_1st_layer) : base; - } - // Insert the base layers. - for (size_t i = 1; i < m_slicing_params.base_raft_layers; ++ i) { - coordf_t print_z = raft_layers.back()->print_z; - MyLayer &new_layer = layer_allocate(layer_storage, sltRaftBase); - raft_layers.push_back(&new_layer); - new_layer.print_z = print_z + m_slicing_params.base_raft_layer_height; - new_layer.height = m_slicing_params.base_raft_layer_height; - new_layer.bottom_z = print_z; - new_layer.polygons = base; - } - // Insert the interface layers. - for (size_t i = 1; i < m_slicing_params.interface_raft_layers; ++ i) { - coordf_t print_z = raft_layers.back()->print_z; - MyLayer &new_layer = layer_allocate(layer_storage, sltRaftInterface); - raft_layers.push_back(&new_layer); - new_layer.print_z = print_z + m_slicing_params.interface_raft_layer_height; - new_layer.height = m_slicing_params.interface_raft_layer_height; - new_layer.bottom_z = print_z; - new_layer.polygons = interface_polygons; - //FIXME misusing contact_polygons for support columns. - new_layer.contact_polygons = std::make_unique(columns); - } - } else { - if (columns_base != nullptr) { - // Expand the bases of the support columns in the 1st layer. - Polygons &raft = columns_base->polygons; - Polygons trimming; - // BBS: if first layer of support is intersected with object island, it must have the same function as brim unless in nobrim mode. - if (object.has_brim()) - trimming = offset(m_object->layers().front()->lslices, (float)scale_(object.config().brim_object_gap.value), SUPPORT_SURFACES_OFFSET_PARAMETERS); - else - trimming = offset(m_object->layers().front()->lslices, (float)scale_(m_support_params.gap_xy), SUPPORT_SURFACES_OFFSET_PARAMETERS); - if (inflate_factor_1st_layer > SCALED_EPSILON) { - // Inflate in multiple steps to avoid leaking of the support 1st layer through object walls. - auto nsteps = std::max(5, int(ceil(inflate_factor_1st_layer / m_support_params.first_layer_flow.scaled_width()))); - float step = inflate_factor_1st_layer / nsteps; - for (int i = 0; i < nsteps; ++ i) - raft = diff(expand(raft, step), trimming); - } else - raft = diff(raft, trimming); - if (! interface_polygons.empty()) - columns_base->polygons = diff(columns_base->polygons, interface_polygons); - } - if (! brim.empty()) { - if (columns_base) - columns_base->polygons = diff(columns_base->polygons, brim); - if (contacts) - contacts->polygons = diff(contacts->polygons, brim); - if (interfaces) - interfaces->polygons = diff(interfaces->polygons, brim); - if (base_interfaces) - base_interfaces->polygons = diff(base_interfaces->polygons, brim); - } - } - - return raft_layers; -} - -// Convert some of the intermediate layers into top/bottom interface layers as well as base interface layers. -std::pair PrintObjectSupportMaterial::generate_interface_layers( - const MyLayersPtr &bottom_contacts, - const MyLayersPtr &top_contacts, - MyLayersPtr &intermediate_layers, - MyLayerStorage &layer_storage) const -{ -// my $area_threshold = $self->interface_flow->scaled_spacing ** 2; - - std::pair base_and_interface_layers; - MyLayersPtr &interface_layers = base_and_interface_layers.first; - MyLayersPtr &base_interface_layers = base_and_interface_layers.second; - - // distinguish between interface and base interface layers - // Contact layer is considered an interface layer, therefore run the following block only if support_interface_top_layers > 1. - // Contact layer needs a base_interface layer, therefore run the following block if support_interface_top_layers > 0, has soluble support and extruders are different. - bool soluble_interface_non_soluble_base = - // Zero z-gap between the overhangs and the support interface. - m_slicing_params.soluble_interface && - // Interface extruder soluble. - m_object_config->support_interface_filament.value > 0 && m_print_config->filament_soluble.get_at(m_object_config->support_interface_filament.value - 1) && - // Base extruder: Either "print with active extruder" not soluble. - (m_object_config->support_filament.value == 0 || ! m_print_config->filament_soluble.get_at(m_object_config->support_filament.value - 1)); - bool snug_supports = m_object_config->support_style.value == smsSnug; - // BBS: if support interface and support base do not use the same filament, add a base layer to improve their adhesion - bool differnt_support_interface_filament = m_object_config->support_interface_filament.value != m_object_config->support_filament.value; - int num_base_interface_layers_top = differnt_support_interface_filament ? 1 : 0; - int num_base_interface_layers_bottom = differnt_support_interface_filament ? 1 : 0; - int num_interface_layers_top = m_object_config->support_interface_top_layers + num_base_interface_layers_top; - int num_interface_layers_bottom = m_object_config->support_interface_bottom_layers + num_base_interface_layers_bottom; - if (num_interface_layers_bottom < 0) - num_interface_layers_bottom = num_interface_layers_top; - - if (! intermediate_layers.empty() && (num_interface_layers_top > 1 || num_interface_layers_bottom > 1)) { - // For all intermediate layers, collect top contact surfaces, which are not further than support_interface_top_layers. - BOOST_LOG_TRIVIAL(debug) << "PrintObjectSupportMaterial::generate_interface_layers() in parallel - start"; - // Since the intermediate layer index starts at zero the number of interface layer needs to be reduced by 1. - -- num_interface_layers_top; - -- num_interface_layers_bottom; - int num_interface_layers_only_top = num_interface_layers_top - num_base_interface_layers_top; - int num_interface_layers_only_bottom = num_interface_layers_bottom - num_base_interface_layers_bottom; - interface_layers.assign(intermediate_layers.size(), nullptr); - if (num_base_interface_layers_top || num_base_interface_layers_bottom) - base_interface_layers.assign(intermediate_layers.size(), nullptr); - auto smoothing_distance = m_support_params.support_material_interface_flow.scaled_spacing() * 1.5; - auto minimum_island_radius = m_support_params.support_material_interface_flow.scaled_spacing() / m_support_params.interface_density; - auto closing_distance = smoothing_distance; // scaled(m_object_config->support_closing_radius.value); - tbb::spin_mutex layer_storage_mutex; - // Insert a new layer into base_interface_layers, if intersection with base exists. - auto insert_layer = [&layer_storage, &layer_storage_mutex, snug_supports, closing_distance, smoothing_distance, minimum_island_radius]( - MyLayer &intermediate_layer, Polygons &bottom, Polygons &&top, const Polygons *subtract, SupporLayerType type) -> MyLayer* { - assert(! bottom.empty() || ! top.empty()); - // Merge top into bottom, unite them with a safety offset. - append(bottom, std::move(top)); - // Merge top / bottom interfaces. For snug supports, merge using closing distance and regularize (close concave corners). - bottom = intersection( - snug_supports ? - smooth_outward(closing(std::move(bottom), closing_distance + minimum_island_radius, closing_distance, SUPPORT_SURFACES_OFFSET_PARAMETERS), smoothing_distance) : - union_safety_offset(std::move(bottom)), - intermediate_layer.polygons); - if (! bottom.empty()) { - //FIXME Remove non-printable tiny islands, let them be printed using the base support. - //bottom = opening(std::move(bottom), minimum_island_radius); - if (! bottom.empty()) { - MyLayer &layer_new = layer_allocate(layer_storage, layer_storage_mutex, type); - layer_new.polygons = std::move(bottom); - layer_new.print_z = intermediate_layer.print_z; - layer_new.bottom_z = intermediate_layer.bottom_z; - layer_new.height = intermediate_layer.height; - layer_new.bridging = intermediate_layer.bridging; - // Subtract the interface from the base regions. - intermediate_layer.polygons = diff(intermediate_layer.polygons, layer_new.polygons); - if (subtract) - // Trim the base interface layer with the interface layer. - layer_new.polygons = diff(std::move(layer_new.polygons), *subtract); - //FIXME filter layer_new.polygons islands by a minimum area? - // $interface_area = [ grep abs($_->area) >= $area_threshold, @$interface_area ]; - return &layer_new; - } - } - return nullptr; - }; - tbb::parallel_for(tbb::blocked_range(0, int(intermediate_layers.size())), - [&bottom_contacts, &top_contacts, &intermediate_layers, &insert_layer, - num_interface_layers_top, num_interface_layers_bottom, num_base_interface_layers_top, num_base_interface_layers_bottom, num_interface_layers_only_top, num_interface_layers_only_bottom, - snug_supports, &interface_layers, &base_interface_layers](const tbb::blocked_range& range) { - // Gather the top / bottom contact layers intersecting with num_interface_layers resp. num_interface_layers_only intermediate layers above / below - // this intermediate layer. - // Index of the first top contact layer intersecting the current intermediate layer. - auto idx_top_contact_first = -1; - // Index of the first bottom contact layer intersecting the current intermediate layer. - auto idx_bottom_contact_first = -1; - auto num_intermediate = int(intermediate_layers.size()); - for (int idx_intermediate_layer = range.begin(); idx_intermediate_layer < range.end(); ++ idx_intermediate_layer) { - MyLayer &intermediate_layer = *intermediate_layers[idx_intermediate_layer]; - Polygons polygons_top_contact_projected_interface; - Polygons polygons_top_contact_projected_base; - Polygons polygons_bottom_contact_projected_interface; - Polygons polygons_bottom_contact_projected_base; - if (num_interface_layers_top > 0) { - // Top Z coordinate of a slab, over which we are collecting the top / bottom contact surfaces - coordf_t top_z = intermediate_layers[std::min(num_intermediate - 1, idx_intermediate_layer + num_interface_layers_top - 1)]->print_z; - coordf_t top_inteface_z = std::numeric_limits::max(); - if (num_base_interface_layers_top > 0) - // Some top base interface layers will be generated. - top_inteface_z = num_interface_layers_only_top == 0 ? - // Only base interface layers to generate. - - std::numeric_limits::max() : - intermediate_layers[std::min(num_intermediate - 1, idx_intermediate_layer + num_interface_layers_only_top - 1)]->print_z; - // Move idx_top_contact_first up until above the current print_z. - idx_top_contact_first = idx_higher_or_equal(top_contacts, idx_top_contact_first, [&intermediate_layer](const MyLayer *layer){ return layer->print_z >= intermediate_layer.print_z; }); // - EPSILON - // Collect the top contact areas above this intermediate layer, below top_z. - for (int idx_top_contact = idx_top_contact_first; idx_top_contact < int(top_contacts.size()); ++ idx_top_contact) { - const MyLayer &top_contact_layer = *top_contacts[idx_top_contact]; - //FIXME maybe this adds one interface layer in excess? - if (top_contact_layer.bottom_z - EPSILON > top_z) - break; - polygons_append(top_contact_layer.bottom_z - EPSILON > top_inteface_z ? polygons_top_contact_projected_base : polygons_top_contact_projected_interface, - // For snug supports, project the overhang polygons covering the whole overhang, so that they will merge without a gap with support polygons of the other layers. - // For grid supports, merging of support regions will be performed by the projection into grid. - snug_supports ? *top_contact_layer.overhang_polygons : top_contact_layer.polygons); - } - } - if (num_interface_layers_bottom > 0) { - // Bottom Z coordinate of a slab, over which we are collecting the top / bottom contact surfaces - coordf_t bottom_z = intermediate_layers[std::max(0, idx_intermediate_layer - num_interface_layers_bottom + 1)]->bottom_z; - coordf_t bottom_interface_z = - std::numeric_limits::max(); - if (num_base_interface_layers_bottom > 0) - // Some bottom base interface layers will be generated. - bottom_interface_z = num_interface_layers_only_bottom == 0 ? - // Only base interface layers to generate. - std::numeric_limits::max() : - intermediate_layers[std::max(0, idx_intermediate_layer - num_interface_layers_only_bottom)]->bottom_z; - // Move idx_bottom_contact_first up until touching bottom_z. - idx_bottom_contact_first = idx_higher_or_equal(bottom_contacts, idx_bottom_contact_first, [bottom_z](const MyLayer *layer){ return layer->print_z >= bottom_z - EPSILON; }); - // Collect the top contact areas above this intermediate layer, below top_z. - for (int idx_bottom_contact = idx_bottom_contact_first; idx_bottom_contact < int(bottom_contacts.size()); ++ idx_bottom_contact) { - const MyLayer &bottom_contact_layer = *bottom_contacts[idx_bottom_contact]; - if (bottom_contact_layer.print_z - EPSILON > intermediate_layer.bottom_z) - break; - polygons_append(bottom_contact_layer.print_z - EPSILON > bottom_interface_z ? polygons_bottom_contact_projected_interface : polygons_bottom_contact_projected_base, bottom_contact_layer.polygons); - } - } - MyLayer *interface_layer = nullptr; - if (! polygons_bottom_contact_projected_interface.empty() || ! polygons_top_contact_projected_interface.empty()) { - interface_layer = insert_layer( - intermediate_layer, polygons_bottom_contact_projected_interface, std::move(polygons_top_contact_projected_interface), nullptr, - polygons_top_contact_projected_interface.empty() ? sltBottomInterface : sltTopInterface); - interface_layers[idx_intermediate_layer] = interface_layer; - } - if (! polygons_bottom_contact_projected_base.empty() || ! polygons_top_contact_projected_base.empty()) - base_interface_layers[idx_intermediate_layer] = insert_layer( - intermediate_layer, polygons_bottom_contact_projected_base, std::move(polygons_top_contact_projected_base), - interface_layer ? &interface_layer->polygons : nullptr, sltBase); - } - }); - - // Compress contact_out, remove the nullptr items. - remove_nulls(interface_layers); - remove_nulls(base_interface_layers); - BOOST_LOG_TRIVIAL(debug) << "PrintObjectSupportMaterial::generate_interface_layers() in parallel - end"; - } - - return base_and_interface_layers; -} - -static inline void fill_expolygon_generate_paths( - ExtrusionEntitiesPtr &dst, - ExPolygon &&expolygon, - Fill *filler, - const FillParams &fill_params, - ExtrusionRole role, - const Flow &flow) -{ - Surface surface(stInternal, std::move(expolygon)); - Polylines polylines; - try { - polylines = filler->fill_surface(&surface, fill_params); - } catch (InfillFailedException &) { - } - extrusion_entities_append_paths( - dst, - std::move(polylines), - role, - flow.mm3_per_mm(), flow.width(), flow.height()); -} - -static inline void fill_expolygons_generate_paths( - ExtrusionEntitiesPtr &dst, - ExPolygons &&expolygons, - Fill *filler, - const FillParams &fill_params, - ExtrusionRole role, - const Flow &flow) -{ - for (ExPolygon &expoly : expolygons) - fill_expolygon_generate_paths(dst, std::move(expoly), filler, fill_params, role, flow); -} - -static inline void fill_expolygons_generate_paths( - ExtrusionEntitiesPtr &dst, - ExPolygons &&expolygons, - Fill *filler, - float density, - ExtrusionRole role, - const Flow &flow) -{ - FillParams fill_params; - fill_params.density = density; - fill_params.dont_adjust = true; - fill_expolygons_generate_paths(dst, std::move(expolygons), filler, fill_params, role, flow); -} - -static inline void fill_expolygons_with_sheath_generate_paths( - ExtrusionEntitiesPtr &dst, - const Polygons &polygons, - Fill *filler, - float density, - ExtrusionRole role, - const Flow &flow, - bool with_sheath, - bool no_sort) -{ - if (polygons.empty()) - return; - - if (! with_sheath) { - fill_expolygons_generate_paths(dst, closing_ex(polygons, float(SCALED_EPSILON)), filler, density, role, flow); - return; - } - - FillParams fill_params; - fill_params.density = density; - fill_params.dont_adjust = true; - - double spacing = flow.scaled_spacing(); - // Clip the sheath path to avoid the extruder to get exactly on the first point of the loop. - double clip_length = spacing * 0.15; - - for (ExPolygon &expoly : closing_ex(polygons, float(SCALED_EPSILON), float(SCALED_EPSILON + 0.5*flow.scaled_width()))) { - // Don't reorder the skirt and its infills. - std::unique_ptr eec; - if (no_sort) { - eec = std::make_unique(); - eec->no_sort = true; - } - ExtrusionEntitiesPtr &out = no_sort ? eec->entities : dst; - // Draw the perimeters. - Polylines polylines; - polylines.reserve(expoly.holes.size() + 1); - for (size_t i = 0; i <= expoly.holes.size(); ++ i) { - Polyline pl(i == 0 ? expoly.contour.points : expoly.holes[i - 1].points); - pl.points.emplace_back(pl.points.front()); - pl.clip_end(clip_length); - polylines.emplace_back(std::move(pl)); - } - extrusion_entities_append_paths(out, polylines, erSupportMaterial, flow.mm3_per_mm(), flow.width(), flow.height()); - // Fill in the rest. - fill_expolygons_generate_paths(out, offset_ex(expoly, float(-0.4 * spacing)), filler, fill_params, role, flow); - if (no_sort && ! eec->empty()) - dst.emplace_back(eec.release()); - } -} - -// Support layers, partially processed. -struct MyLayerExtruded -{ - MyLayerExtruded& operator=(MyLayerExtruded &&rhs) { - this->layer = rhs.layer; - this->extrusions = std::move(rhs.extrusions); - m_polygons_to_extrude = std::move(rhs.m_polygons_to_extrude); - rhs.layer = nullptr; - return *this; - } - - bool empty() const { - return layer == nullptr || layer->polygons.empty(); - } - - void set_polygons_to_extrude(Polygons &&polygons) { - if (m_polygons_to_extrude == nullptr) - m_polygons_to_extrude = std::make_unique(std::move(polygons)); - else - *m_polygons_to_extrude = std::move(polygons); - } - Polygons& polygons_to_extrude() { return (m_polygons_to_extrude == nullptr) ? layer->polygons : *m_polygons_to_extrude; } - const Polygons& polygons_to_extrude() const { return (m_polygons_to_extrude == nullptr) ? layer->polygons : *m_polygons_to_extrude; } - - bool could_merge(const MyLayerExtruded &other) const { - return ! this->empty() && ! other.empty() && - std::abs(this->layer->height - other.layer->height) < EPSILON && - this->layer->bridging == other.layer->bridging; - } - - // Merge regions, perform boolean union over the merged polygons. - void merge(MyLayerExtruded &&other) { - assert(this->could_merge(other)); - // 1) Merge the rest polygons to extrude, if there are any. - if (other.m_polygons_to_extrude != nullptr) { - if (m_polygons_to_extrude == nullptr) { - // This layer has no extrusions generated yet, if it has no m_polygons_to_extrude (its area to extrude was not reduced yet). - assert(this->extrusions.empty()); - m_polygons_to_extrude = std::make_unique(this->layer->polygons); - } - Slic3r::polygons_append(*m_polygons_to_extrude, std::move(*other.m_polygons_to_extrude)); - *m_polygons_to_extrude = union_safety_offset(*m_polygons_to_extrude); - other.m_polygons_to_extrude.reset(); - } else if (m_polygons_to_extrude != nullptr) { - assert(other.m_polygons_to_extrude == nullptr); - // The other layer has no extrusions generated yet, if it has no m_polygons_to_extrude (its area to extrude was not reduced yet). - assert(other.extrusions.empty()); - Slic3r::polygons_append(*m_polygons_to_extrude, other.layer->polygons); - *m_polygons_to_extrude = union_safety_offset(*m_polygons_to_extrude); - } - // 2) Merge the extrusions. - this->extrusions.insert(this->extrusions.end(), other.extrusions.begin(), other.extrusions.end()); - other.extrusions.clear(); - // 3) Merge the infill polygons. - Slic3r::polygons_append(this->layer->polygons, std::move(other.layer->polygons)); - this->layer->polygons = union_safety_offset(this->layer->polygons); - other.layer->polygons.clear(); - } - - void polygons_append(Polygons &dst) const { - if (layer != NULL && ! layer->polygons.empty()) - Slic3r::polygons_append(dst, layer->polygons); - } - - // The source layer. It carries the height and extrusion type (bridging / non bridging, extrusion height). - PrintObjectSupportMaterial::MyLayer *layer { nullptr }; - // Collect extrusions. They will be exported sorted by the bottom height. - ExtrusionEntitiesPtr extrusions; - -private: - // In case the extrusions are non-empty, m_polygons_to_extrude may contain the rest areas yet to be filled by additional support. - // This is useful mainly for the loop interfaces, which are generated before the zig-zag infills. - std::unique_ptr m_polygons_to_extrude; -}; - -typedef std::vector MyLayerExtrudedPtrs; - -struct LoopInterfaceProcessor -{ - LoopInterfaceProcessor(coordf_t circle_r) : - n_contact_loops(0), - circle_radius(circle_r), - circle_distance(circle_r * 3.) - { - // Shape of the top contact area. - circle.points.reserve(6); - for (size_t i = 0; i < 6; ++ i) { - double angle = double(i) * M_PI / 3.; - circle.points.push_back(Point(circle_radius * cos(angle), circle_radius * sin(angle))); - } - } - - // Generate loop contacts at the top_contact_layer, - // trim the top_contact_layer->polygons with the areas covered by the loops. - void generate(MyLayerExtruded &top_contact_layer, const Flow &interface_flow_src) const; - - int n_contact_loops; - coordf_t circle_radius; - coordf_t circle_distance; - Polygon circle; -}; - -void LoopInterfaceProcessor::generate(MyLayerExtruded &top_contact_layer, const Flow &interface_flow_src) const -{ - if (n_contact_loops == 0 || top_contact_layer.empty()) - return; - - Flow flow = interface_flow_src.with_height(top_contact_layer.layer->height); - - Polygons overhang_polygons; - if (top_contact_layer.layer->overhang_polygons != nullptr) - overhang_polygons = std::move(*top_contact_layer.layer->overhang_polygons); - - // Generate the outermost loop. - // Find centerline of the external loop (or any other kind of extrusions should the loop be skipped) - ExPolygons top_contact_expolygons = offset_ex(union_ex(top_contact_layer.layer->polygons), - 0.5f * flow.scaled_width()); - - // Grid size and bit shifts for quick and exact to/from grid coordinates manipulation. - coord_t circle_grid_resolution = 1; - coord_t circle_grid_powerof2 = 0; - { - // epsilon to account for rounding errors - coord_t circle_grid_resolution_non_powerof2 = coord_t(2. * circle_distance + 3.); - while (circle_grid_resolution < circle_grid_resolution_non_powerof2) { - circle_grid_resolution <<= 1; - ++ circle_grid_powerof2; - } - } - - struct PointAccessor { - const Point* operator()(const Point &pt) const { return &pt; } - }; - typedef ClosestPointInRadiusLookup ClosestPointLookupType; - - Polygons loops0; - { - // find centerline of the external loop of the contours - // Only consider the loops facing the overhang. - Polygons external_loops; - // Holes in the external loops. - Polygons circles; - Polygons overhang_with_margin = offset(union_ex(overhang_polygons), 0.5f * flow.scaled_width()); - for (ExPolygons::iterator it_contact_expoly = top_contact_expolygons.begin(); it_contact_expoly != top_contact_expolygons.end(); ++ it_contact_expoly) { - // Store the circle centers placed for an expolygon into a regular grid, hashed by the circle centers. - ClosestPointLookupType circle_centers_lookup(coord_t(circle_distance - SCALED_EPSILON)); - Points circle_centers; - Point center_last; - // For each contour of the expolygon, start with the outer contour, continue with the holes. - for (size_t i_contour = 0; i_contour <= it_contact_expoly->holes.size(); ++ i_contour) { - Polygon &contour = (i_contour == 0) ? it_contact_expoly->contour : it_contact_expoly->holes[i_contour - 1]; - const Point *seg_current_pt = nullptr; - coordf_t seg_current_t = 0.; - if (! intersection_pl(contour.split_at_first_point(), overhang_with_margin).empty()) { - // The contour is below the overhang at least to some extent. - //FIXME ideally one would place the circles below the overhang only. - // Walk around the contour and place circles so their centers are not closer than circle_distance from each other. - if (circle_centers.empty()) { - // Place the first circle. - seg_current_pt = &contour.points.front(); - seg_current_t = 0.; - center_last = *seg_current_pt; - circle_centers_lookup.insert(center_last); - circle_centers.push_back(center_last); - } - for (Points::const_iterator it = contour.points.begin() + 1; it != contour.points.end(); ++it) { - // Is it possible to place a circle on this segment? Is it not too close to any of the circles already placed on this contour? - const Point &p1 = *(it-1); - const Point &p2 = *it; - // Intersection of a ray (p1, p2) with a circle placed at center_last, with radius of circle_distance. - const Vec2d v_seg(coordf_t(p2(0)) - coordf_t(p1(0)), coordf_t(p2(1)) - coordf_t(p1(1))); - const Vec2d v_cntr(coordf_t(p1(0) - center_last(0)), coordf_t(p1(1) - center_last(1))); - coordf_t a = v_seg.squaredNorm(); - coordf_t b = 2. * v_seg.dot(v_cntr); - coordf_t c = v_cntr.squaredNorm() - circle_distance * circle_distance; - coordf_t disc = b * b - 4. * a * c; - if (disc > 0.) { - // The circle intersects a ray. Avoid the parts of the segment inside the circle. - coordf_t t1 = (-b - sqrt(disc)) / (2. * a); - coordf_t t2 = (-b + sqrt(disc)) / (2. * a); - coordf_t t0 = (seg_current_pt == &p1) ? seg_current_t : 0.; - // Take the lowest t in , excluding . - coordf_t t; - if (t0 <= t1) - t = t0; - else if (t2 <= 1.) - t = t2; - else { - // Try the following segment. - seg_current_pt = nullptr; - continue; - } - seg_current_pt = &p1; - seg_current_t = t; - center_last = Point(p1(0) + coord_t(v_seg(0) * t), p1(1) + coord_t(v_seg(1) * t)); - // It has been verified that the new point is far enough from center_last. - // Ensure, that it is far enough from all the centers. - std::pair circle_closest = circle_centers_lookup.find(center_last); - if (circle_closest.first != nullptr) { - -- it; - continue; - } - } else { - // All of the segment is outside the circle. Take the first point. - seg_current_pt = &p1; - seg_current_t = 0.; - center_last = p1; - } - // Place the first circle. - circle_centers_lookup.insert(center_last); - circle_centers.push_back(center_last); - } - external_loops.push_back(std::move(contour)); - for (const Point ¢er : circle_centers) { - circles.push_back(circle); - circles.back().translate(center); - } - } - } - } - // Apply a pattern to the external loops. - loops0 = diff(external_loops, circles); - } - - Polylines loop_lines; - { - // make more loops - Polygons loop_polygons = loops0; - for (int i = 1; i < n_contact_loops; ++ i) - polygons_append(loop_polygons, - opening( - loops0, - i * flow.scaled_spacing() + 0.5f * flow.scaled_spacing(), - 0.5f * flow.scaled_spacing())); - // Clip such loops to the side oriented towards the object. - // Collect split points, so they will be recognized after the clipping. - // At the split points the clipped pieces will be stitched back together. - loop_lines.reserve(loop_polygons.size()); - std::unordered_map map_split_points; - for (Polygons::const_iterator it = loop_polygons.begin(); it != loop_polygons.end(); ++ it) { - assert(map_split_points.find(it->first_point()) == map_split_points.end()); - map_split_points[it->first_point()] = -1; - loop_lines.push_back(it->split_at_first_point()); - } - loop_lines = intersection_pl(loop_lines, expand(overhang_polygons, scale_(SUPPORT_MATERIAL_MARGIN))); - // Because a closed loop has been split to a line, loop_lines may contain continuous segments split to 2 pieces. - // Try to connect them. - for (int i_line = 0; i_line < int(loop_lines.size()); ++ i_line) { - Polyline &polyline = loop_lines[i_line]; - auto it = map_split_points.find(polyline.first_point()); - if (it != map_split_points.end()) { - // This is a stitching point. - // If this assert triggers, multiple source polygons likely intersected at this point. - assert(it->second != -2); - if (it->second < 0) { - // First occurence. - it->second = i_line; - } else { - // Second occurence. Join the lines. - Polyline &polyline_1st = loop_lines[it->second]; - assert(polyline_1st.first_point() == it->first || polyline_1st.last_point() == it->first); - if (polyline_1st.first_point() == it->first) - polyline_1st.reverse(); - polyline_1st.append(std::move(polyline)); - it->second = -2; - } - continue; - } - it = map_split_points.find(polyline.last_point()); - if (it != map_split_points.end()) { - // This is a stitching point. - // If this assert triggers, multiple source polygons likely intersected at this point. - assert(it->second != -2); - if (it->second < 0) { - // First occurence. - it->second = i_line; - } else { - // Second occurence. Join the lines. - Polyline &polyline_1st = loop_lines[it->second]; - assert(polyline_1st.first_point() == it->first || polyline_1st.last_point() == it->first); - if (polyline_1st.first_point() == it->first) - polyline_1st.reverse(); - polyline.reverse(); - polyline_1st.append(std::move(polyline)); - it->second = -2; - } - } - } - // Remove empty lines. - remove_degenerate(loop_lines); - } - - // add the contact infill area to the interface area - // note that growing loops by $circle_radius ensures no tiny - // extrusions are left inside the circles; however it creates - // a very large gap between loops and contact_infill_polygons, so maybe another - // solution should be found to achieve both goals - // Store the trimmed polygons into a separate polygon set, so the original infill area remains intact for - // "modulate by layer thickness". - top_contact_layer.set_polygons_to_extrude(diff(top_contact_layer.layer->polygons, offset(loop_lines, float(circle_radius * 1.1)))); - - // Transform loops into ExtrusionPath objects. - extrusion_entities_append_paths( - top_contact_layer.extrusions, - std::move(loop_lines), - erSupportMaterialInterface, flow.mm3_per_mm(), flow.width(), flow.height()); -} - -#ifdef SLIC3R_DEBUG -static std::string dbg_index_to_color(int idx) -{ - if (idx < 0) - return "yellow"; - idx = idx % 3; - switch (idx) { - case 0: return "red"; - case 1: return "green"; - default: return "blue"; - } -} -#endif /* SLIC3R_DEBUG */ - -// When extruding a bottom interface layer over an object, the bottom interface layer is extruded in a thin air, therefore -// it is being extruded with a bridging flow to not shrink excessively (the die swell effect). -// Tiny extrusions are better avoided and it is always better to anchor the thread to an existing support structure if possible. -// Therefore the bottom interface spots are expanded a bit. The expanded regions may overlap with another bottom interface layers, -// leading to over extrusion, where they overlap. The over extrusion is better avoided as it often makes the interface layers -// to stick too firmly to the object. -// -// Modulate thickness (increase bottom_z) of extrusions_in_out generated for this_layer -// if they overlap with overlapping_layers, whose print_z is above this_layer.bottom_z() and below this_layer.print_z. -void modulate_extrusion_by_overlapping_layers( - // Extrusions generated for this_layer. - ExtrusionEntitiesPtr &extrusions_in_out, - const PrintObjectSupportMaterial::MyLayer &this_layer, - // Multiple layers overlapping with this_layer, sorted bottom up. - const PrintObjectSupportMaterial::MyLayersPtr &overlapping_layers) -{ - size_t n_overlapping_layers = overlapping_layers.size(); - if (n_overlapping_layers == 0 || extrusions_in_out.empty()) - // The extrusions do not overlap with any other extrusion. - return; - - // Get the initial extrusion parameters. - ExtrusionPath *extrusion_path_template = dynamic_cast(extrusions_in_out.front()); - assert(extrusion_path_template != nullptr); - ExtrusionRole extrusion_role = extrusion_path_template->role(); - float extrusion_width = extrusion_path_template->width; - - struct ExtrusionPathFragment - { - ExtrusionPathFragment() : mm3_per_mm(-1), width(-1), height(-1) {}; - ExtrusionPathFragment(double mm3_per_mm, float width, float height) : mm3_per_mm(mm3_per_mm), width(width), height(height) {}; - - Polylines polylines; - double mm3_per_mm; - float width; - float height; - }; - - // Split the extrusions by the overlapping layers, reduce their extrusion rate. - // The last path_fragment is from this_layer. - std::vector path_fragments( - n_overlapping_layers + 1, - ExtrusionPathFragment(extrusion_path_template->mm3_per_mm, extrusion_path_template->width, extrusion_path_template->height)); - // Don't use it, it will be released. - extrusion_path_template = nullptr; - -#ifdef SLIC3R_DEBUG - static int iRun = 0; - ++ iRun; - BoundingBox bbox; - for (size_t i_overlapping_layer = 0; i_overlapping_layer < n_overlapping_layers; ++ i_overlapping_layer) { - const PrintObjectSupportMaterial::MyLayer &overlapping_layer = *overlapping_layers[i_overlapping_layer]; - bbox.merge(get_extents(overlapping_layer.polygons)); - } - for (ExtrusionEntitiesPtr::const_iterator it = extrusions_in_out.begin(); it != extrusions_in_out.end(); ++ it) { - ExtrusionPath *path = dynamic_cast(*it); - assert(path != nullptr); - bbox.merge(get_extents(path->polyline)); - } - SVG svg(debug_out_path("support-fragments-%d-%lf.svg", iRun, this_layer.print_z).c_str(), bbox); - const float transparency = 0.5f; - // Filled polygons for the overlapping regions. - svg.draw(union_ex(this_layer.polygons), dbg_index_to_color(-1), transparency); - for (size_t i_overlapping_layer = 0; i_overlapping_layer < n_overlapping_layers; ++ i_overlapping_layer) { - const PrintObjectSupportMaterial::MyLayer &overlapping_layer = *overlapping_layers[i_overlapping_layer]; - svg.draw(union_ex(overlapping_layer.polygons), dbg_index_to_color(int(i_overlapping_layer)), transparency); - } - // Contours of the overlapping regions. - svg.draw(to_polylines(this_layer.polygons), dbg_index_to_color(-1), scale_(0.2)); - for (size_t i_overlapping_layer = 0; i_overlapping_layer < n_overlapping_layers; ++ i_overlapping_layer) { - const PrintObjectSupportMaterial::MyLayer &overlapping_layer = *overlapping_layers[i_overlapping_layer]; - svg.draw(to_polylines(overlapping_layer.polygons), dbg_index_to_color(int(i_overlapping_layer)), scale_(0.1)); - } - // Fill extrusion, the source. - for (ExtrusionEntitiesPtr::const_iterator it = extrusions_in_out.begin(); it != extrusions_in_out.end(); ++ it) { - ExtrusionPath *path = dynamic_cast(*it); - std::string color_name; - switch ((it - extrusions_in_out.begin()) % 9) { - case 0: color_name = "magenta"; break; - case 1: color_name = "deepskyblue"; break; - case 2: color_name = "coral"; break; - case 3: color_name = "goldenrod"; break; - case 4: color_name = "orange"; break; - case 5: color_name = "olivedrab"; break; - case 6: color_name = "blueviolet"; break; - case 7: color_name = "brown"; break; - default: color_name = "orchid"; break; - } - svg.draw(path->polyline, color_name, scale_(0.2)); - } -#endif /* SLIC3R_DEBUG */ - - // End points of the original paths. - std::vector> path_ends; - // Collect the paths of this_layer. - { - Polylines &polylines = path_fragments.back().polylines; - for (ExtrusionEntity *ee : extrusions_in_out) { - ExtrusionPath *path = dynamic_cast(ee); - assert(path != nullptr); - polylines.emplace_back(Polyline(std::move(path->polyline))); - path_ends.emplace_back(std::pair(polylines.back().points.front(), polylines.back().points.back())); - } - } - // Destroy the original extrusion paths, their polylines were moved to path_fragments already. - // This will be the destination for the new paths. - extrusions_in_out.clear(); - - // Fragment the path segments by overlapping layers. The overlapping layers are sorted by an increasing print_z. - // Trim by the highest overlapping layer first. - for (int i_overlapping_layer = int(n_overlapping_layers) - 1; i_overlapping_layer >= 0; -- i_overlapping_layer) { - const PrintObjectSupportMaterial::MyLayer &overlapping_layer = *overlapping_layers[i_overlapping_layer]; - ExtrusionPathFragment &frag = path_fragments[i_overlapping_layer]; - Polygons polygons_trimming = offset(union_ex(overlapping_layer.polygons), float(scale_(0.5*extrusion_width))); - frag.polylines = intersection_pl(path_fragments.back().polylines, polygons_trimming); - path_fragments.back().polylines = diff_pl(path_fragments.back().polylines, polygons_trimming); - // Adjust the extrusion parameters for a reduced layer height and a non-bridging flow (nozzle_dmr = -1, does not matter). - assert(this_layer.print_z > overlapping_layer.print_z); - frag.height = float(this_layer.print_z - overlapping_layer.print_z); - frag.mm3_per_mm = Flow(frag.width, frag.height, -1.f).mm3_per_mm(); -#ifdef SLIC3R_DEBUG - svg.draw(frag.polylines, dbg_index_to_color(i_overlapping_layer), scale_(0.1)); -#endif /* SLIC3R_DEBUG */ - } - -#ifdef SLIC3R_DEBUG - svg.draw(path_fragments.back().polylines, dbg_index_to_color(-1), scale_(0.1)); - svg.Close(); -#endif /* SLIC3R_DEBUG */ - - // Now chain the split segments using hashing and a nearly exact match, maintaining the order of segments. - // Create a single ExtrusionPath or ExtrusionEntityCollection per source ExtrusionPath. - // Map of fragment start/end points to a pair of - // Because a non-exact matching is used for the end points, a multi-map is used. - // As the clipper library may reverse the order of some clipped paths, store both ends into the map. - struct ExtrusionPathFragmentEnd - { - ExtrusionPathFragmentEnd(size_t alayer_idx, size_t apolyline_idx, bool ais_start) : - layer_idx(alayer_idx), polyline_idx(apolyline_idx), is_start(ais_start) {} - size_t layer_idx; - size_t polyline_idx; - bool is_start; - }; - class ExtrusionPathFragmentEndPointAccessor { - public: - ExtrusionPathFragmentEndPointAccessor(const std::vector &path_fragments) : m_path_fragments(path_fragments) {} - // Return an end point of a fragment, or nullptr if the fragment has been consumed already. - const Point* operator()(const ExtrusionPathFragmentEnd &fragment_end) const { - const Polyline &polyline = m_path_fragments[fragment_end.layer_idx].polylines[fragment_end.polyline_idx]; - return polyline.points.empty() ? nullptr : - (fragment_end.is_start ? &polyline.points.front() : &polyline.points.back()); - } - private: - ExtrusionPathFragmentEndPointAccessor& operator=(const ExtrusionPathFragmentEndPointAccessor&) { - return *this; - } - - const std::vector &m_path_fragments; - }; - const coord_t search_radius = 7; - ClosestPointInRadiusLookup map_fragment_starts( - search_radius, ExtrusionPathFragmentEndPointAccessor(path_fragments)); - for (size_t i_overlapping_layer = 0; i_overlapping_layer <= n_overlapping_layers; ++ i_overlapping_layer) { - const Polylines &polylines = path_fragments[i_overlapping_layer].polylines; - for (size_t i_polyline = 0; i_polyline < polylines.size(); ++ i_polyline) { - // Map a starting point of a polyline to a pair of - if (polylines[i_polyline].points.size() >= 2) { - map_fragment_starts.insert(ExtrusionPathFragmentEnd(i_overlapping_layer, i_polyline, true)); - map_fragment_starts.insert(ExtrusionPathFragmentEnd(i_overlapping_layer, i_polyline, false)); - } - } - } - - // For each source path: - for (size_t i_path = 0; i_path < path_ends.size(); ++ i_path) { - const Point &pt_start = path_ends[i_path].first; - const Point &pt_end = path_ends[i_path].second; - Point pt_current = pt_start; - // Find a chain of fragments with the original / reduced print height. - ExtrusionMultiPath multipath; - for (;;) { - // Find a closest end point to pt_current. - std::pair end_and_dist2 = map_fragment_starts.find(pt_current); - // There may be a bug in Clipper flipping the order of two last points in a fragment? - // assert(end_and_dist2.first != nullptr); - assert(end_and_dist2.first == nullptr || end_and_dist2.second < search_radius * search_radius); - if (end_and_dist2.first == nullptr) { - // New fragment connecting to pt_current was not found. - // Verify that the last point found is close to the original end point of the unfragmented path. - //const double d2 = (pt_end - pt_current).cast.squaredNorm(); - //assert(d2 < coordf_t(search_radius * search_radius)); - // End of the path. - break; - } - const ExtrusionPathFragmentEnd &fragment_end_min = *end_and_dist2.first; - // Fragment to consume. - ExtrusionPathFragment &frag = path_fragments[fragment_end_min.layer_idx]; - Polyline &frag_polyline = frag.polylines[fragment_end_min.polyline_idx]; - // Path to append the fragment to. - ExtrusionPath *path = multipath.paths.empty() ? nullptr : &multipath.paths.back(); - if (path != nullptr) { - // Verify whether the path is compatible with the current fragment. - assert(this_layer.layer_type == PrintObjectSupportMaterial::sltBottomContact || path->height != frag.height || path->mm3_per_mm != frag.mm3_per_mm); - if (path->height != frag.height || path->mm3_per_mm != frag.mm3_per_mm) { - path = nullptr; - } - // Merging with the previous path. This can only happen if the current layer was reduced by a base layer, which was split into a base and interface layer. - } - if (path == nullptr) { - // Allocate a new path. - multipath.paths.push_back(ExtrusionPath(extrusion_role, frag.mm3_per_mm, frag.width, frag.height)); - path = &multipath.paths.back(); - } - // The Clipper library may flip the order of the clipped polylines arbitrarily. - // Reverse the source polyline, if connecting to the end. - if (! fragment_end_min.is_start) - frag_polyline.reverse(); - // Enforce exact overlap of the end points of successive fragments. - assert(frag_polyline.points.front() == pt_current); - frag_polyline.points.front() = pt_current; - // Don't repeat the first point. - if (! path->polyline.points.empty()) - path->polyline.points.pop_back(); - // Consume the fragment's polyline, remove it from the input fragments, so it will be ignored the next time. - path->polyline.append(std::move(frag_polyline)); - frag_polyline.points.clear(); - pt_current = path->polyline.points.back(); - if (pt_current == pt_end) { - // End of the path. - break; - } - } - if (!multipath.paths.empty()) { - if (multipath.paths.size() == 1) { - // This path was not fragmented. - extrusions_in_out.push_back(new ExtrusionPath(std::move(multipath.paths.front()))); - } else { - // This path was fragmented. Copy the collection as a whole object, so the order inside the collection will not be changed - // during the chaining of extrusions_in_out. - extrusions_in_out.push_back(new ExtrusionMultiPath(std::move(multipath))); - } - } - } - // If there are any non-consumed fragments, add them separately. - //FIXME this shall not happen, if the Clipper works as expected and all paths split to fragments could be re-connected. - for (auto it_fragment = path_fragments.begin(); it_fragment != path_fragments.end(); ++ it_fragment) - extrusion_entities_append_paths(extrusions_in_out, std::move(it_fragment->polylines), extrusion_role, it_fragment->mm3_per_mm, it_fragment->width, it_fragment->height); -} - -void PrintObjectSupportMaterial::generate_toolpaths( - SupportLayerPtrs &support_layers, - const MyLayersPtr &raft_layers, - const MyLayersPtr &bottom_contacts, - const MyLayersPtr &top_contacts, - const MyLayersPtr &intermediate_layers, - const MyLayersPtr &interface_layers, - const MyLayersPtr &base_interface_layers) const -{ - // loop_interface_processor with a given circle radius. - LoopInterfaceProcessor loop_interface_processor(1.5 * m_support_params.support_material_interface_flow.scaled_width()); - loop_interface_processor.n_contact_loops = this->has_contact_loops() ? 1 : 0; - - std::vector angles { m_support_params.base_angle }; - if (m_object_config->support_base_pattern == smpRectilinearGrid) - angles.push_back(m_support_params.interface_angle); - - BoundingBox bbox_object(Point(-scale_(1.), -scale_(1.0)), Point(scale_(1.), scale_(1.))); - -// const coordf_t link_max_length_factor = 3.; - const coordf_t link_max_length_factor = 0.; - - float raft_angle_1st_layer = 0.f; - float raft_angle_base = 0.f; - float raft_angle_interface = 0.f; - if (m_slicing_params.base_raft_layers > 1) { - // There are all raft layer types (1st layer, base, interface & contact layers) available. - raft_angle_1st_layer = m_support_params.interface_angle; - raft_angle_base = m_support_params.base_angle; - raft_angle_interface = m_support_params.interface_angle; - } else if (m_slicing_params.base_raft_layers == 1 || m_slicing_params.interface_raft_layers > 1) { - // 1st layer, interface & contact layers available. - raft_angle_1st_layer = m_support_params.base_angle; - if (this->has_support()) - // Print 1st layer at 45 degrees from both the interface and base angles as both can land on the 1st layer. - raft_angle_1st_layer += 0.7854f; - raft_angle_interface = m_support_params.interface_angle; - } else if (m_slicing_params.interface_raft_layers == 1) { - // Only the contact raft layer is non-empty, which will be printed as the 1st layer. - assert(m_slicing_params.base_raft_layers == 0); - assert(m_slicing_params.interface_raft_layers == 1); - assert(m_slicing_params.raft_layers() == 1 && raft_layers.size() == 0); - } else { - // No raft. - assert(m_slicing_params.base_raft_layers == 0); - assert(m_slicing_params.interface_raft_layers == 0); - assert(m_slicing_params.raft_layers() == 0 && raft_layers.size() == 0); - } - - // Insert the raft base layers. - size_t n_raft_layers = size_t(std::max(0, int(m_slicing_params.raft_layers()) - 1)); - tbb::parallel_for(tbb::blocked_range(0, n_raft_layers), - [this, &support_layers, &raft_layers, - &bbox_object, raft_angle_1st_layer, raft_angle_base, raft_angle_interface, link_max_length_factor] - (const tbb::blocked_range& range) { - for (size_t support_layer_id = range.begin(); support_layer_id < range.end(); ++ support_layer_id) - { - assert(support_layer_id < raft_layers.size()); - SupportLayer &support_layer = *support_layers[support_layer_id]; - assert(support_layer.support_fills.entities.empty()); - MyLayer &raft_layer = *raft_layers[support_layer_id]; - - std::unique_ptr filler_interface = std::unique_ptr(Fill::new_from_type(m_support_params.interface_fill_pattern)); - std::unique_ptr filler_support = std::unique_ptr(Fill::new_from_type(m_support_params.base_fill_pattern)); - filler_interface->set_bounding_box(bbox_object); - filler_support->set_bounding_box(bbox_object); - - // Print the support base below the support columns, or the support base for the support columns plus the contacts. - if (support_layer_id > 0) { - const Polygons &to_infill_polygons = (support_layer_id < m_slicing_params.base_raft_layers) ? - raft_layer.polygons : - //FIXME misusing contact_polygons for support columns. - ((raft_layer.contact_polygons == nullptr) ? Polygons() : *raft_layer.contact_polygons); - if (! to_infill_polygons.empty()) { - assert(! raft_layer.bridging); - Flow flow(float(m_support_params.support_material_flow.width()), float(raft_layer.height), m_support_params.support_material_flow.nozzle_diameter()); - Fill * filler = filler_support.get(); - filler->angle = raft_angle_base; - filler->spacing = m_support_params.support_material_flow.spacing(); - filler->link_max_length = coord_t(scale_(filler->spacing * link_max_length_factor / m_support_params.support_density)); - fill_expolygons_with_sheath_generate_paths( - // Destination - support_layer.support_fills.entities, - // Regions to fill - to_infill_polygons, - // Filler and its parameters - filler, float(m_support_params.support_density), - // Extrusion parameters - erSupportMaterial, flow, - m_support_params.with_sheath, false); - } - } - - Fill *filler = filler_interface.get(); - Flow flow = m_support_params.first_layer_flow; - float density = 0.f; - if (support_layer_id == 0) { - // Base flange. - filler->angle = raft_angle_1st_layer; - filler->spacing = m_support_params.first_layer_flow.spacing(); - density = float(m_object_config->raft_first_layer_density.value * 0.01); - } else if (support_layer_id >= m_slicing_params.base_raft_layers) { - filler->angle = raft_angle_interface; - // We don't use $base_flow->spacing because we need a constant spacing - // value that guarantees that all layers are correctly aligned. - filler->spacing = m_support_params.support_material_flow.spacing(); - assert(! raft_layer.bridging); - flow = Flow(float(m_support_params.support_material_interface_flow.width()), float(raft_layer.height), m_support_params.support_material_flow.nozzle_diameter()); - density = float(m_support_params.interface_density); - } else - continue; - filler->link_max_length = coord_t(scale_(filler->spacing * link_max_length_factor / density)); - fill_expolygons_with_sheath_generate_paths( - // Destination - support_layer.support_fills.entities, - // Regions to fill - raft_layer.polygons, - // Filler and its parameters - filler, density, - // Extrusion parameters - (support_layer_id < m_slicing_params.base_raft_layers) ? erSupportMaterial : erSupportMaterialInterface, flow, - // sheath at first layer - support_layer_id == 0, support_layer_id == 0); - } - }); - - struct LayerCacheItem { - LayerCacheItem(MyLayerExtruded *layer_extruded = nullptr) : layer_extruded(layer_extruded) {} - MyLayerExtruded *layer_extruded; - std::vector overlapping; - }; - struct LayerCache { - MyLayerExtruded bottom_contact_layer; - MyLayerExtruded top_contact_layer; - MyLayerExtruded base_layer; - MyLayerExtruded interface_layer; - MyLayerExtruded base_interface_layer; - boost::container::static_vector nonempty; - - void add_nonempty_and_sort() { - for (MyLayerExtruded *item : { &bottom_contact_layer, &top_contact_layer, &interface_layer, &base_interface_layer, &base_layer }) - if (! item->empty()) - this->nonempty.emplace_back(item); - // Sort the layers with the same print_z coordinate by their heights, thickest first. - std::stable_sort(this->nonempty.begin(), this->nonempty.end(), [](const LayerCacheItem &lc1, const LayerCacheItem &lc2) { return lc1.layer_extruded->layer->height > lc2.layer_extruded->layer->height; }); - } - }; - std::vector layer_caches(support_layers.size()); - - tbb::parallel_for(tbb::blocked_range(n_raft_layers, support_layers.size()), - [this, &support_layers, &bottom_contacts, &top_contacts, &intermediate_layers, &interface_layers, &base_interface_layers, &layer_caches, &loop_interface_processor, - &bbox_object, &angles, link_max_length_factor] - (const tbb::blocked_range& range) { - // Indices of the 1st layer in their respective container at the support layer height. - size_t idx_layer_bottom_contact = size_t(-1); - size_t idx_layer_top_contact = size_t(-1); - size_t idx_layer_intermediate = size_t(-1); - size_t idx_layer_interface = size_t(-1); - size_t idx_layer_base_interface = size_t(-1); - // BBS - const auto fill_type_first_layer = ipConcentric; - auto filler_interface = std::unique_ptr(Fill::new_from_type(m_support_params.contact_fill_pattern)); - // Filler for the 1st layer interface, if different from filler_interface. - auto filler_first_layer_ptr = std::unique_ptr(range.begin() == 0 && m_support_params.contact_fill_pattern != fill_type_first_layer ? Fill::new_from_type(fill_type_first_layer) : nullptr); - // Pointer to the 1st layer interface filler. - auto filler_first_layer = filler_first_layer_ptr ? filler_first_layer_ptr.get() : filler_interface.get(); - // Filler for the base interface (to be used for soluble interface / non soluble base, to produce non soluble interface layer below soluble interface layer). - auto filler_base_interface = std::unique_ptr(base_interface_layers.empty() ? nullptr : - Fill::new_from_type(m_support_params.interface_density > 0.95 || m_support_params.with_sheath ? ipRectilinear : ipSupportBase)); - auto filler_support = std::unique_ptr(Fill::new_from_type(m_support_params.base_fill_pattern)); - filler_interface->set_bounding_box(bbox_object); - if (filler_first_layer_ptr) - filler_first_layer_ptr->set_bounding_box(bbox_object); - if (filler_base_interface) - filler_base_interface->set_bounding_box(bbox_object); - filler_support->set_bounding_box(bbox_object); - for (size_t support_layer_id = range.begin(); support_layer_id < range.end(); ++ support_layer_id) - { - SupportLayer &support_layer = *support_layers[support_layer_id]; - LayerCache &layer_cache = layer_caches[support_layer_id]; - float interface_angle_delta = m_object_config->support_style.value == smsSnug ? - (support_layer.interface_id() & 1) ? float(- M_PI / 4.) : float(+ M_PI / 4.) : - 0; - - // Find polygons with the same print_z. - MyLayerExtruded &bottom_contact_layer = layer_cache.bottom_contact_layer; - MyLayerExtruded &top_contact_layer = layer_cache.top_contact_layer; - MyLayerExtruded &base_layer = layer_cache.base_layer; - MyLayerExtruded &interface_layer = layer_cache.interface_layer; - MyLayerExtruded &base_interface_layer = layer_cache.base_interface_layer; - // Increment the layer indices to find a layer at support_layer.print_z. - { - auto fun = [&support_layer](const MyLayer *l){ return l->print_z >= support_layer.print_z - EPSILON; }; - idx_layer_bottom_contact = idx_higher_or_equal(bottom_contacts, idx_layer_bottom_contact, fun); - idx_layer_top_contact = idx_higher_or_equal(top_contacts, idx_layer_top_contact, fun); - idx_layer_intermediate = idx_higher_or_equal(intermediate_layers, idx_layer_intermediate, fun); - idx_layer_interface = idx_higher_or_equal(interface_layers, idx_layer_interface, fun); - idx_layer_base_interface = idx_higher_or_equal(base_interface_layers, idx_layer_base_interface,fun); - } - // Copy polygons from the layers. - if (idx_layer_bottom_contact < bottom_contacts.size() && bottom_contacts[idx_layer_bottom_contact]->print_z < support_layer.print_z + EPSILON) - bottom_contact_layer.layer = bottom_contacts[idx_layer_bottom_contact]; - if (idx_layer_top_contact < top_contacts.size() && top_contacts[idx_layer_top_contact]->print_z < support_layer.print_z + EPSILON) - top_contact_layer.layer = top_contacts[idx_layer_top_contact]; - if (idx_layer_interface < interface_layers.size() && interface_layers[idx_layer_interface]->print_z < support_layer.print_z + EPSILON) - interface_layer.layer = interface_layers[idx_layer_interface]; - if (idx_layer_base_interface < base_interface_layers.size() && base_interface_layers[idx_layer_base_interface]->print_z < support_layer.print_z + EPSILON) - base_interface_layer.layer = base_interface_layers[idx_layer_base_interface]; - if (idx_layer_intermediate < intermediate_layers.size() && intermediate_layers[idx_layer_intermediate]->print_z < support_layer.print_z + EPSILON) - base_layer.layer = intermediate_layers[idx_layer_intermediate]; - - if (m_object_config->support_interface_top_layers == 0) { - // If no top interface layers were requested, we treat the contact layer exactly as a generic base layer. - if (m_support_params.can_merge_support_regions) { - if (base_layer.could_merge(top_contact_layer)) - base_layer.merge(std::move(top_contact_layer)); - else if (base_layer.empty()) - base_layer = std::move(top_contact_layer); - } - } else { - loop_interface_processor.generate(top_contact_layer, m_support_params.support_material_interface_flow); - // If no loops are allowed, we treat the contact layer exactly as a generic interface layer. - // Merge interface_layer into top_contact_layer, as the top_contact_layer is not synchronized and therefore it will be used - // to trim other layers. - if (top_contact_layer.could_merge(interface_layer)) - top_contact_layer.merge(std::move(interface_layer)); - } - if ((m_object_config->support_interface_top_layers == 0 || m_object_config->support_interface_bottom_layers == 0) && m_support_params.can_merge_support_regions) { - if (base_layer.could_merge(bottom_contact_layer)) - base_layer.merge(std::move(bottom_contact_layer)); - else if (base_layer.empty() && ! bottom_contact_layer.empty() && ! bottom_contact_layer.layer->bridging) - base_layer = std::move(bottom_contact_layer); - } else if (bottom_contact_layer.could_merge(top_contact_layer)) - top_contact_layer.merge(std::move(bottom_contact_layer)); - else if (bottom_contact_layer.could_merge(interface_layer)) - bottom_contact_layer.merge(std::move(interface_layer)); - -#if 0 - if ( ! interface_layer.empty() && ! base_layer.empty()) { - // turn base support into interface when it's contained in our holes - // (this way we get wider interface anchoring) - //FIXME The intention of the code below is unclear. One likely wanted to just merge small islands of base layers filling in the holes - // inside interface layers, but the code below fills just too much, see GH #4570 - Polygons islands = top_level_islands(interface_layer.layer->polygons); - polygons_append(interface_layer.layer->polygons, intersection(base_layer.layer->polygons, islands)); - base_layer.layer->polygons = diff(base_layer.layer->polygons, islands); - } -#endif - - // Calculate top interface angle - float angle_of_biggest_bridge = -1.f; - do - { - // Currently only works when thick_bridges is off - if (m_object->config().thick_bridges) - break; - - coordf_t object_layer_bottom_z = support_layer.print_z + m_slicing_params.gap_support_object; - const Layer* object_layer = m_object->get_layer_at_bottomz(object_layer_bottom_z, 10.0 * EPSILON); - if (object_layer == nullptr) - break; - - if (object_layer != nullptr) { - float biggest_bridge_area = 0.f; - const Polygons& top_contact_polys = top_contact_layer.polygons_to_extrude(); - for (auto layerm : object_layer->regions()) { - for (auto bridge_surface : layerm->fill_surfaces.filter_by_type(stBottomBridge)) { - float bs_area = bridge_surface->area(); - if (bs_area <= biggest_bridge_area || bridge_surface->bridge_angle < 0.f) - continue; - - angle_of_biggest_bridge = bridge_surface->bridge_angle; - biggest_bridge_area = bs_area; - } - } - } - } while (0); - - auto calc_included_angle_degree = [](int degree_a, int degree_b) { - int iad = std::abs(degree_b - degree_a); - return std::min(iad, 180 - iad); - }; - - // Top and bottom contacts, interface layers. - for (size_t i = 0; i < 3; ++ i) { - MyLayerExtruded &layer_ex = (i == 0) ? top_contact_layer : (i == 1 ? bottom_contact_layer : interface_layer); - if (layer_ex.empty() || layer_ex.polygons_to_extrude().empty()) - continue; - bool interface_as_base = m_object_config->support_interface_top_layers.value == 0 || - (m_object_config->support_interface_bottom_layers == 0 && &layer_ex == &bottom_contact_layer); - //FIXME Bottom interfaces are extruded with the briding flow. Some bridging layers have its height slightly reduced, therefore - // the bridging flow does not quite apply. Reduce the flow to area of an ellipse? (A = pi * a * b) - Flow interface_flow; - if (layer_ex.layer->bridging) - interface_flow = Flow::bridging_flow(layer_ex.layer->height, m_support_params.support_material_bottom_interface_flow.nozzle_diameter()); - else if (layer_ex.layer->bottom_z < EPSILON) { - interface_flow = m_support_params.first_layer_flow; - }else - interface_flow = (interface_as_base ? &m_support_params.support_material_flow : &m_support_params.support_material_interface_flow)->with_height(float(layer_ex.layer->height)); - filler_interface->angle = interface_as_base ? - // If zero interface layers are configured, use the same angle as for the base layers. - angles[support_layer_id % angles.size()] : - // Use interface angle for the interface layers. - m_support_params.interface_angle + interface_angle_delta; - - // BBS - bool can_adjust_top_interface_angle = (m_object_config->support_interface_top_layers.value > 1 && &layer_ex == &top_contact_layer); - if (can_adjust_top_interface_angle && angle_of_biggest_bridge >= 0.f) { - int bridge_degree = (int)Geometry::rad2deg(angle_of_biggest_bridge); - int support_intf_degree = (int)Geometry::rad2deg(filler_interface->angle); - int max_included_degree = 0; - int step = 90; - for (int add_on_degree = 0; add_on_degree < 180; add_on_degree += step) { - int degree_to_try = support_intf_degree + add_on_degree; - int included_degree = calc_included_angle_degree(bridge_degree, degree_to_try); - if (included_degree > max_included_degree) { - max_included_degree = included_degree; - filler_interface->angle = Geometry::deg2rad((float)degree_to_try); - } - } - } - double density = interface_as_base ? m_support_params.support_density : m_support_params.interface_density; - filler_interface->spacing = interface_as_base ? m_support_params.support_material_flow.spacing() : m_support_params.support_material_interface_flow.spacing(); - filler_interface->link_max_length = coord_t(scale_(filler_interface->spacing * link_max_length_factor / density)); - // BBS support more interface patterns - FillParams fill_params; - fill_params.density = density; - fill_params.dont_adjust = true; - if (m_object_config->support_interface_pattern == smipGrid) { - filler_interface->angle = Geometry::deg2rad(m_support_params.base_angle); - fill_params.dont_sort = true; - } - if (m_object_config->support_interface_pattern == smipRectilinearInterlaced) - filler_interface->layer_id = support_layer.interface_id(); - fill_expolygons_generate_paths( - // Destination - layer_ex.extrusions, - // Regions to fill - union_safety_offset_ex(layer_ex.polygons_to_extrude()), - // Filler and its parameters - filler_interface.get(), fill_params, - // Extrusion parameters - erSupportMaterialInterface, interface_flow); - } - - // Base interface layers under soluble interfaces - if ( ! base_interface_layer.empty() && ! base_interface_layer.polygons_to_extrude().empty()) { - Fill *filler = filler_base_interface.get(); - //FIXME Bottom interfaces are extruded with the briding flow. Some bridging layers have its height slightly reduced, therefore - // the bridging flow does not quite apply. Reduce the flow to area of an ellipse? (A = pi * a * b) - assert(! base_interface_layer.layer->bridging); - Flow interface_flow = m_support_params.support_material_flow.with_height(float(base_interface_layer.layer->height)); - filler->angle = m_support_params.interface_angle + interface_angle_delta; - filler->spacing = m_support_params.support_material_interface_flow.spacing(); - filler->link_max_length = coord_t(scale_(filler->spacing * link_max_length_factor / m_support_params.interface_density)); - fill_expolygons_generate_paths( - // Destination - base_interface_layer.extrusions, - //base_layer_interface.extrusions, - // Regions to fill - union_safety_offset_ex(base_interface_layer.polygons_to_extrude()), - // Filler and its parameters - filler, float(m_support_params.interface_density), - // Extrusion parameters - erSupportMaterial, interface_flow); - } - - // Base support or flange. - if (! base_layer.empty() && ! base_layer.polygons_to_extrude().empty()) { - Fill *filler = filler_support.get(); - filler->angle = angles[support_layer_id % angles.size()]; - // We don't use $base_flow->spacing because we need a constant spacing - // value that guarantees that all layers are correctly aligned. - assert(! base_layer.layer->bridging); - auto flow = m_support_params.support_material_flow.with_height(float(base_layer.layer->height)); - filler->spacing = m_support_params.support_material_flow.spacing(); - filler->link_max_length = coord_t(scale_(filler->spacing * link_max_length_factor / m_support_params.support_density)); - float density = float(m_support_params.support_density); - bool sheath = m_support_params.with_sheath; - bool no_sort = false; - if (base_layer.layer->bottom_z < EPSILON) { - // Base flange (the 1st layer). - filler = filler_first_layer; - // BBS: the 1st layer use the same fill direction as other layers(in rectilinear) to avoid - // that 2nd layer detaches from the 1st layer. - //filler->angle = Geometry::deg2rad(float(m_object_config->support_angle.value + 90.)); - density = float(m_object_config->raft_first_layer_density.value * 0.01); - flow = m_support_params.first_layer_flow; - // use the proper spacing for first layer as we don't need to align - // its pattern to the other layers - //FIXME When paralellizing, each thread shall have its own copy of the fillers. - filler->spacing = flow.spacing(); - filler->link_max_length = coord_t(scale_(filler->spacing * link_max_length_factor / density)); - sheath = true; - no_sort = true; - } - fill_expolygons_with_sheath_generate_paths( - // Destination - base_layer.extrusions, - // Regions to fill - base_layer.polygons_to_extrude(), - // Filler and its parameters - filler, density, - // Extrusion parameters - erSupportMaterial, flow, - sheath, no_sort); - - } - - // Merge base_interface_layers to base_layers to avoid unneccessary retractions - if (! base_layer.empty() && ! base_interface_layer.empty() && ! base_layer.polygons_to_extrude().empty() && ! base_interface_layer.polygons_to_extrude().empty() && - base_layer.could_merge(base_interface_layer)) - base_layer.merge(std::move(base_interface_layer)); - - layer_cache.add_nonempty_and_sort(); - - // Collect the support areas with this print_z into islands, as there is no need - // for retraction over these islands. - Polygons polys; - // Collect the extrusions, sorted by the bottom extrusion height. - for (LayerCacheItem &layer_cache_item : layer_cache.nonempty) { - // Collect islands to polys. - layer_cache_item.layer_extruded->polygons_append(polys); - // The print_z of the top contact surfaces and bottom_z of the bottom contact surfaces are "free" - // in a sense that they are not synchronized with other support layers. As the top and bottom contact surfaces - // are inflated to achieve a better anchoring, it may happen, that these surfaces will at least partially - // overlap in Z with another support layers, leading to over-extrusion. - // Mitigate the over-extrusion by modulating the extrusion rate over these regions. - // The print head will follow the same print_z, but the layer thickness will be reduced - // where it overlaps with another support layer. - //FIXME When printing a briging path, what is an equivalent height of the squished extrudate of the same width? - // Collect overlapping top/bottom surfaces. - layer_cache_item.overlapping.reserve(20); - coordf_t bottom_z = layer_cache_item.layer_extruded->layer->bottom_print_z() + EPSILON; - auto add_overlapping = [&layer_cache_item, bottom_z](const MyLayersPtr &layers, size_t idx_top) { - for (int i = int(idx_top) - 1; i >= 0 && layers[i]->print_z > bottom_z; -- i) - layer_cache_item.overlapping.push_back(layers[i]); - }; - add_overlapping(top_contacts, idx_layer_top_contact); - if (layer_cache_item.layer_extruded->layer->layer_type == sltBottomContact) { - // Bottom contact layer may overlap with a base layer, which may be changed to interface layer. - add_overlapping(intermediate_layers, idx_layer_intermediate); - add_overlapping(interface_layers, idx_layer_interface); - add_overlapping(base_interface_layers, idx_layer_base_interface); - } - // Order the layers by lexicographically by an increasing print_z and a decreasing layer height. - std::stable_sort(layer_cache_item.overlapping.begin(), layer_cache_item.overlapping.end(), [](auto *l1, auto *l2) { return *l1 < *l2; }); - } - if (! polys.empty()) - expolygons_append(support_layer.support_islands, union_ex(polys)); - } // for each support_layer_id - }); - - // Now modulate the support layer height in parallel. - tbb::parallel_for(tbb::blocked_range(n_raft_layers, support_layers.size()), - [&support_layers, &layer_caches] - (const tbb::blocked_range& range) { - for (size_t support_layer_id = range.begin(); support_layer_id < range.end(); ++ support_layer_id) { - SupportLayer &support_layer = *support_layers[support_layer_id]; - LayerCache &layer_cache = layer_caches[support_layer_id]; - // For all extrusion types at this print_z, ordered by decreasing layer height: - for (LayerCacheItem &layer_cache_item : layer_cache.nonempty) { - // Trim the extrusion height from the bottom by the overlapping layers. - modulate_extrusion_by_overlapping_layers(layer_cache_item.layer_extruded->extrusions, *layer_cache_item.layer_extruded->layer, layer_cache_item.overlapping); - support_layer.support_fills.append(std::move(layer_cache_item.layer_extruded->extrusions)); - } - } - }); - -#ifndef NDEBUG - struct Test { - static bool verify_nonempty(const ExtrusionEntityCollection *collection) { - for (const ExtrusionEntity *ee : collection->entities) { - if (const ExtrusionPath *path = dynamic_cast(ee)) - assert(! path->empty()); - else if (const ExtrusionMultiPath *multipath = dynamic_cast(ee)) - assert(! multipath->empty()); - else if (const ExtrusionEntityCollection *eecol = dynamic_cast(ee)) { - assert(! eecol->empty()); - return verify_nonempty(eecol); - } else - assert(false); - } - return true; - } - }; - for (const SupportLayer *support_layer : support_layers) - assert(Test::verify_nonempty(&support_layer->support_fills)); -#endif // NDEBUG -} - -/* -void PrintObjectSupportMaterial::clip_by_pillars( - const PrintObject &object, - LayersPtr &bottom_contacts, - LayersPtr &top_contacts, - LayersPtr &intermediate_contacts); - -{ - // this prevents supplying an empty point set to BoundingBox constructor - if (top_contacts.empty()) - return; - - coord_t pillar_size = scale_(PILLAR_SIZE); - coord_t pillar_spacing = scale_(PILLAR_SPACING); - - // A regular grid of pillars, filling the 2D bounding box. - Polygons grid; - { - // Rectangle with a side of 2.5x2.5mm. - Polygon pillar; - pillar.points.push_back(Point(0, 0)); - pillar.points.push_back(Point(pillar_size, 0)); - pillar.points.push_back(Point(pillar_size, pillar_size)); - pillar.points.push_back(Point(0, pillar_size)); - - // 2D bounding box of the projection of all contact polygons. - BoundingBox bbox; - for (LayersPtr::const_iterator it = top_contacts.begin(); it != top_contacts.end(); ++ it) - bbox.merge(get_extents((*it)->polygons)); - grid.reserve(size_t(ceil(bb.size()(0) / pillar_spacing)) * size_t(ceil(bb.size()(1) / pillar_spacing))); - for (coord_t x = bb.min(0); x <= bb.max(0) - pillar_size; x += pillar_spacing) { - for (coord_t y = bb.min(1); y <= bb.max(1) - pillar_size; y += pillar_spacing) { - grid.push_back(pillar); - for (size_t i = 0; i < pillar.points.size(); ++ i) - grid.back().points[i].translate(Point(x, y)); - } - } - } - - // add pillars to every layer - for my $i (0..n_support_z) { - $shape->[$i] = [ @$grid ]; - } - - // build capitals - for my $i (0..n_support_z) { - my $z = $support_z->[$i]; - - my $capitals = intersection( - $grid, - $contact->{$z} // [], - ); - - // work on one pillar at time (if any) to prevent the capitals from being merged - // but store the contact area supported by the capital because we need to make - // sure nothing is left - my $contact_supported_by_capitals = []; - foreach my $capital (@$capitals) { - // enlarge capital tops - $capital = offset([$capital], +($pillar_spacing - $pillar_size)/2); - push @$contact_supported_by_capitals, @$capital; - - for (my $j = $i-1; $j >= 0; $j--) { - my $jz = $support_z->[$j]; - $capital = offset($capital, -$self->interface_flow->scaled_width/2); - last if !@$capitals; - push @{ $shape->[$j] }, @$capital; - } - } - - // Capitals will not generally cover the whole contact area because there will be - // remainders. For now we handle this situation by projecting such unsupported - // areas to the ground, just like we would do with a normal support. - my $contact_not_supported_by_capitals = diff( - $contact->{$z} // [], - $contact_supported_by_capitals, - ); - if (@$contact_not_supported_by_capitals) { - for (my $j = $i-1; $j >= 0; $j--) { - push @{ $shape->[$j] }, @$contact_not_supported_by_capitals; - } - } - } -} - -sub clip_with_shape { - my ($self, $support, $shape) = @_; - - foreach my $i (keys %$support) { - // don't clip bottom layer with shape so that we - // can generate a continuous base flange - // also don't clip raft layers - next if $i == 0; - next if $i < $self->object_config->raft_layers; - $support->{$i} = intersection( - $support->{$i}, - $shape->[$i], - ); - } -} -*/ - -} // namespace Slic3r diff --git a/src/libslic3r/SupportMaterial.hpp b/src/libslic3r/SupportMaterial.hpp deleted file mode 100644 index 861126fcbdc..00000000000 --- a/src/libslic3r/SupportMaterial.hpp +++ /dev/null @@ -1,263 +0,0 @@ -#ifndef slic3r_SupportMaterial_hpp_ -#define slic3r_SupportMaterial_hpp_ - -#include "Flow.hpp" -#include "PrintConfig.hpp" -#include "Slicing.hpp" - -namespace Slic3r { - -class PrintObject; -class PrintConfig; -class PrintObjectConfig; - -// This class manages raft and supports for a single PrintObject. -// Instantiated by Slic3r::Print::Object->_support_material() -// This class is instantiated before the slicing starts as Object.pm will query -// the parameters of the raft to determine the 1st layer height and thickness. -class PrintObjectSupportMaterial -{ -public: - // Support layer type to be used by MyLayer. This type carries a much more detailed information - // about the support layer type than the final support layers stored in a PrintObject. - enum SupporLayerType { - sltUnknown = 0, - // Ratft base layer, to be printed with the support material. - sltRaftBase, - // Raft interface layer, to be printed with the support interface material. - sltRaftInterface, - // Bottom contact layer placed over a top surface of an object. To be printed with a support interface material. - sltBottomContact, - // Dense interface layer, to be printed with the support interface material. - // This layer is separated from an object by an sltBottomContact layer. - sltBottomInterface, - // Sparse base support layer, to be printed with a support material. - sltBase, - // Dense interface layer, to be printed with the support interface material. - // This layer is separated from an object with sltTopContact layer. - sltTopInterface, - // Top contact layer directly supporting an overhang. To be printed with a support interface material. - sltTopContact, - // Some undecided type yet. It will turn into sltBase first, then it may turn into sltBottomInterface or sltTopInterface. - sltIntermediate, - }; - - // A support layer type used internally by the SupportMaterial class. This class carries a much more detailed - // information about the support layer than the layers stored in the PrintObject, mainly - // the MyLayer is aware of the bridging flow and the interface gaps between the object and the support. - class MyLayer - { - public: - void reset() { - *this = MyLayer(); - } - - bool operator==(const MyLayer &layer2) const { - return print_z == layer2.print_z && height == layer2.height && bridging == layer2.bridging; - } - - // Order the layers by lexicographically by an increasing print_z and a decreasing layer height. - bool operator<(const MyLayer &layer2) const { - if (print_z < layer2.print_z) { - return true; - } else if (print_z == layer2.print_z) { - if (height > layer2.height) - return true; - else if (height == layer2.height) { - // Bridging layers first. - return bridging && ! layer2.bridging; - } else - return false; - } else - return false; - } - - void merge(MyLayer &&rhs) { - // The union_() does not support move semantic yet, but maybe one day it will. - this->polygons = union_(this->polygons, std::move(rhs.polygons)); - auto merge = [](std::unique_ptr &dst, std::unique_ptr &src) { - if (! dst || dst->empty()) - dst = std::move(src); - else if (src && ! src->empty()) - *dst = union_(*dst, std::move(*src)); - }; - merge(this->contact_polygons, rhs.contact_polygons); - merge(this->overhang_polygons, rhs.overhang_polygons); - merge(this->enforcer_polygons, rhs.enforcer_polygons); - rhs.reset(); - } - - // For the bridging flow, bottom_print_z will be above bottom_z to account for the vertical separation. - // For the non-bridging flow, bottom_print_z will be equal to bottom_z. - coordf_t bottom_print_z() const { return print_z - height; } - - // To sort the extremes of top / bottom interface layers. - coordf_t extreme_z() const { return (this->layer_type == sltTopContact) ? this->bottom_z : this->print_z; } - - SupporLayerType layer_type { sltUnknown }; - // Z used for printing, in unscaled coordinates. - coordf_t print_z { 0 }; - // Bottom Z of this layer. For soluble layers, bottom_z + height = print_z, - // otherwise bottom_z + gap + height = print_z. - coordf_t bottom_z { 0 }; - // Layer height in unscaled coordinates. - coordf_t height { 0 }; - // Index of a PrintObject layer_id supported by this layer. This will be set for top contact layers. - // If this is not a contact layer, it will be set to size_t(-1). - size_t idx_object_layer_above { size_t(-1) }; - // Index of a PrintObject layer_id, which supports this layer. This will be set for bottom contact layers. - // If this is not a contact layer, it will be set to size_t(-1). - size_t idx_object_layer_below { size_t(-1) }; - // Use a bridging flow when printing this support layer. - bool bridging { false }; - - // Polygons to be filled by the support pattern. - Polygons polygons; - // Currently for the contact layers only. - std::unique_ptr contact_polygons; - std::unique_ptr overhang_polygons; - // Enforcers need to be propagated independently in case the "support on build plate only" option is enabled. - std::unique_ptr enforcer_polygons; - }; - - struct SupportParams { - Flow first_layer_flow; - Flow support_material_flow; - Flow support_material_interface_flow; - Flow support_material_bottom_interface_flow; - // Is merging of regions allowed? Could the interface & base support regions be printed with the same extruder? - bool can_merge_support_regions; - - coordf_t support_layer_height_min; - // coordf_t support_layer_height_max; - - coordf_t gap_xy; - - float base_angle; - float interface_angle; - coordf_t interface_spacing; - coordf_t support_expansion; - coordf_t interface_density; - coordf_t support_spacing; - coordf_t support_density; - - InfillPattern base_fill_pattern; - InfillPattern interface_fill_pattern; - InfillPattern contact_fill_pattern; - bool with_sheath; - }; - - // Layers are allocated and owned by a deque. Once a layer is allocated, it is maintained - // up to the end of a generate() method. The layer storage may be replaced by an allocator class in the future, - // which would allocate layers by multiple chunks. - typedef std::deque MyLayerStorage; - typedef std::vector MyLayersPtr; - -public: - PrintObjectSupportMaterial(const PrintObject *object, const SlicingParameters &slicing_params); - - // Is raft enabled? - bool has_raft() const { return m_slicing_params.has_raft(); } - // Has any support? - bool has_support() const { return m_object_config->enable_support.value || m_object_config->enforce_support_layers; } - bool build_plate_only() const { return this->has_support() && m_object_config->support_on_build_plate_only.value; } - // BBS - bool synchronize_layers() const { return /*m_slicing_params.soluble_interface && */!m_print_config->independent_support_layer_height.value; } - bool has_contact_loops() const { return m_object_config->support_interface_loop_pattern.value; } - - // Generate support material for the object. - // New support layers will be added to the object, - // with extrusion paths and islands filled in for each support layer. - void generate(PrintObject &object); - -private: - std::vector buildplate_covered(const PrintObject &object) const; - - // Generate top contact layers supporting overhangs. - // For a soluble interface material synchronize the layer heights with the object, otherwise leave the layer height undefined. - // If supports over bed surface only are requested, don't generate contact layers over an object. - MyLayersPtr top_contact_layers(const PrintObject &object, const std::vector &buildplate_covered, MyLayerStorage &layer_storage) const; - - // Generate bottom contact layers supporting the top contact layers. - // For a soluble interface material synchronize the layer heights with the object, - // otherwise set the layer height to a bridging flow of a support interface nozzle. - MyLayersPtr bottom_contact_layers_and_layer_support_areas( - const PrintObject &object, const MyLayersPtr &top_contacts, std::vector &buildplate_covered, - MyLayerStorage &layer_storage, std::vector &layer_support_areas) const; - - // Trim the top_contacts layers with the bottom_contacts layers if they overlap, so there would not be enough vertical space for both of them. - void trim_top_contacts_by_bottom_contacts(const PrintObject &object, const MyLayersPtr &bottom_contacts, MyLayersPtr &top_contacts) const; - - // Generate raft layers and the intermediate support layers between the bottom contact and top contact surfaces. - MyLayersPtr raft_and_intermediate_support_layers( - const PrintObject &object, - const MyLayersPtr &bottom_contacts, - const MyLayersPtr &top_contacts, - MyLayerStorage &layer_storage) const; - - // Fill in the base layers with polygons. - void generate_base_layers( - const PrintObject &object, - const MyLayersPtr &bottom_contacts, - const MyLayersPtr &top_contacts, - MyLayersPtr &intermediate_layers, - const std::vector &layer_support_areas) const; - - // Generate raft layers, also expand the 1st support layer - // in case there is no raft layer to improve support adhesion. - MyLayersPtr generate_raft_base( - const PrintObject &object, - const MyLayersPtr &top_contacts, - const MyLayersPtr &interface_layers, - const MyLayersPtr &base_interface_layers, - const MyLayersPtr &base_layers, - MyLayerStorage &layer_storage) const; - - // Turn some of the base layers into base interface layers. - // For soluble interfaces with non-soluble bases, print maximum two first interface layers with the base - // extruder to improve adhesion of the soluble filament to the base. - std::pair generate_interface_layers( - const MyLayersPtr &bottom_contacts, - const MyLayersPtr &top_contacts, - MyLayersPtr &intermediate_layers, - MyLayerStorage &layer_storage) const; - - - // Trim support layers by an object to leave a defined gap between - // the support volume and the object. - void trim_support_layers_by_object( - const PrintObject &object, - MyLayersPtr &support_layers, - const coordf_t gap_extra_above, - const coordf_t gap_extra_below, - const coordf_t gap_xy) const; - -/* - void generate_pillars_shape(); - void clip_with_shape(); -*/ - - // Produce the actual G-code. - void generate_toolpaths( - SupportLayerPtrs &support_layers, - const MyLayersPtr &raft_layers, - const MyLayersPtr &bottom_contacts, - const MyLayersPtr &top_contacts, - const MyLayersPtr &intermediate_layers, - const MyLayersPtr &interface_layers, - const MyLayersPtr &base_interface_layers) const; - - // Following objects are not owned by SupportMaterial class. - const PrintObject *m_object; - const PrintConfig *m_print_config; - const PrintObjectConfig *m_object_config; - // Pre-calculated parameters shared between the object slicer and the support generator, - // carrying information on a raft, 1st layer height, 1st object layer height, gap between the raft and object etc. - SlicingParameters m_slicing_params; - // Various precomputed support parameters to be shared with external functions. - SupportParams m_support_params; -}; - -} // namespace Slic3r - -#endif /* slic3r_SupportMaterial_hpp_ */ diff --git a/src/libslic3r/TreeSupport.cpp b/src/libslic3r/TreeSupport.cpp deleted file mode 100644 index dc4f9998ff0..00000000000 --- a/src/libslic3r/TreeSupport.cpp +++ /dev/null @@ -1,3716 +0,0 @@ -#include - -#include "MinimumSpanningTree.hpp" -#include "TreeSupport.hpp" -#include "Print.hpp" -#include "Layer.hpp" -#include "Fill/FillBase.hpp" -#include "Fill/FillConcentric.hpp" -#include "CurveAnalyzer.hpp" -#include "SVG.hpp" -#include "ShortestPath.hpp" -#include "I18N.hpp" -#include - -#include -#include -#include - -#define _L(s) Slic3r::I18N::translate(s) - -#define USE_PLAN_LAYER_HEIGHTS 1 - -#ifndef M_PI -#define M_PI 3.1415926535897932384626433832795 -#endif -#ifndef SIGN -#define SIGN(x) (x>=0?1:-1) -#endif -#define TAU (2.0 * M_PI) -#define NO_INDEX (std::numeric_limits::max()) - -// #define SUPPORT_TREE_DEBUG_TO_SVG - -#ifdef SUPPORT_TREE_DEBUG_TO_SVG -#include "nlohmann/json.hpp" -#endif -namespace Slic3r -{ -#define unscale_(val) ((val) * SCALING_FACTOR) - -inline unsigned int round_divide(unsigned int dividend, unsigned int divisor) //!< Return dividend divided by divisor rounded to the nearest integer -{ - return (dividend + divisor / 2) / divisor; -} -inline unsigned int round_up_divide(unsigned int dividend, unsigned int divisor) //!< Return dividend divided by divisor rounded to the nearest integer -{ - return (dividend + divisor - 1) / divisor; -} - -inline double dot_with_unscale(const Point a, const Point b) -{ - return unscale_(a(0)) * unscale_(b(0)) + unscale_(a(1)) * unscale_(b(1)); -} - -inline double vsize2_with_unscale(const Point pt) -{ - return dot_with_unscale(pt, pt); -} - -inline Point turn90_ccw(const Point pt) -{ - Point ret; - - ret(0) = -pt(1); - ret(1) = pt(0); - return ret; -} - -inline Point normal(Point pt, double scale) -{ - double length = scale_(sqrt(vsize2_with_unscale(pt))); - - return pt * (scale / length); -} - -enum TreeSupportStage { - STAGE_DETECT_OVERHANGS, - STAGE_GENERATE_CONTACT_NODES, - STAGE_DROP_DOWN_NODES, - STAGE_DRAW_CIRCLES, - STAGE_GENERATE_TOOLPATHS, - STAGE_MinimumSpanningTree, - STAGE_GET_AVOIDANCE, - STAGE_projection_onto_ex, - STAGE_get_collision, - STAGE_intersection_ln, - STAGE_total, - NUM_STAGES -}; - -class TreeSupportProfiler -{ -public: - uint32_t stage_durations[NUM_STAGES]; - uint32_t stage_index = 0; - boost::posix_time::ptime tic_time; - boost::posix_time::ptime toc_time; - - TreeSupportProfiler() - { - for (uint32_t& item : stage_durations) { - item = 0; - } - } - - void stage_start(TreeSupportStage stage) - { - if (stage > NUM_STAGES) - return; - - m_stage_start_times[stage] = boost::posix_time::microsec_clock::local_time(); - } - - void stage_finish(TreeSupportStage stage) - { - if (stage > NUM_STAGES) - return; - - boost::posix_time::ptime time = boost::posix_time::microsec_clock::local_time(); - stage_durations[stage] = (time - m_stage_start_times[stage]).total_milliseconds(); - } - - void tic() { tic_time = boost::posix_time::microsec_clock::local_time(); } - void toc() { toc_time = boost::posix_time::microsec_clock::local_time(); } - void stage_add(TreeSupportStage stage, bool do_toc = false) - { - if (stage > NUM_STAGES) - return; - if(do_toc) - toc_time = boost::posix_time::microsec_clock::local_time(); - stage_durations[stage] += (toc_time - tic_time).total_milliseconds(); - } - - std::string report() - { - std::stringstream ss; - ss << "total overhange cost: " << stage_durations[STAGE_total] - << "; STAGE_DETECT_OVERHANGS: " << stage_durations[STAGE_DETECT_OVERHANGS] - << "; STAGE_GENERATE_CONTACT_NODES: " << stage_durations[STAGE_GENERATE_CONTACT_NODES] - << "; STAGE_DROP_DOWN_NODES: " << stage_durations[STAGE_DROP_DOWN_NODES] - << "; STAGE_DRAW_CIRCLES: " << stage_durations[STAGE_DRAW_CIRCLES] - << "; STAGE_GENERATE_TOOLPATHS: " << stage_durations[STAGE_GENERATE_TOOLPATHS] - << "; STAGE_MinimumSpanningTree: " << stage_durations[STAGE_MinimumSpanningTree] - << "; STAGE_GET_AVOIDANCE: " << stage_durations[STAGE_GET_AVOIDANCE] - << "; STAGE_projection_onto_ex: " << stage_durations[STAGE_projection_onto_ex] - << "; STAGE_get_collision: " << stage_durations[STAGE_get_collision] - << "; STAGE_intersection_ln: " << stage_durations[STAGE_intersection_ln]; - - return ss.str(); - } -private: - boost::posix_time::ptime m_stage_start_times[NUM_STAGES]; -}; -TreeSupportProfiler profiler; - -Lines spanning_tree_to_lines(const std::vector& spanning_trees) -{ - Lines polylines; - for (const MinimumSpanningTree& mst : spanning_trees) { - std::vector points = mst.vertices(); - std::unordered_set to_ignore; - for (Point pt1 : points) { - if (to_ignore.find(pt1) != to_ignore.end()) - continue; - - const std::vector& neighbours = mst.adjacent_nodes(pt1); - if (neighbours.empty()) - continue; - - for (Point pt2 : neighbours) { - if (to_ignore.find(pt2) != to_ignore.end()) - continue; - - Line line(pt1, pt2); - polylines.push_back(line); - } - - to_ignore.insert(pt1); - } - } - return polylines; -} - - -#ifdef SUPPORT_TREE_DEBUG_TO_SVG -static std::string get_svg_filename(std::string layer_nr_or_z, std::string tag = "bbl_ts") -{ - static bool rand_init = false; - - if (!rand_init) { - srand(time(NULL)); - rand_init = true; - } - - int rand_num = rand() % 1000000; - //makedir("./SVG"); - std::string prefix = "./SVG/"; - std::string suffix = ".svg"; - return prefix + tag + "_" + layer_nr_or_z /*+ "_" + std::to_string(rand_num)*/ + suffix; -} - -static void draw_contours_and_nodes_to_svg -( - std::string layer_nr_or_z, - const ExPolygons &overhangs, - const ExPolygons &overhangs_after_offset, - const ExPolygons &outlines_below, - const std::vector &layer_nodes, - const std::vector &lower_layer_nodes, - std::string name_prefix, - std::vector legends = { "overhang","avoid","outlines" }, std::vector colors = { "blue","red","yellow" } -) -{ - BoundingBox bbox = get_extents(overhangs); - bbox.merge(get_extents(overhangs_after_offset)); - bbox.merge(get_extents(outlines_below)); - Points layer_pts; - for (TreeSupport::Node* node : layer_nodes) { - layer_pts.push_back(node->position); - } - bbox.merge(get_extents(layer_pts)); - bbox.inflated(scale_(1)); - bbox.max.x() = std::max(bbox.max.x(), (coord_t)scale_(10)); - bbox.max.y() = std::max(bbox.max.y(), (coord_t)scale_(10)); - - SVG svg; - if(!layer_nr_or_z.empty()) - svg.open(get_svg_filename(layer_nr_or_z, name_prefix), bbox); - else - svg.open(name_prefix, bbox); - if (!svg.is_opened()) return; - - // draw grid - svg.draw_grid(bbox, "gray", coord_t(scale_(0.05))); - - // draw overhang areas - svg.draw_outline(union_ex(overhangs), colors[0]); - svg.draw_outline(union_ex(overhangs_after_offset), colors[1]); - svg.draw_outline(outlines_below, colors[2]); - - // draw legend - if (!lower_layer_nodes.empty()) { - svg.draw_text(bbox.min + Point(scale_(0), scale_(0)), format("nPoints: %1%->%2%",layer_nodes.size(), lower_layer_nodes.size()).c_str(), "green", 2); - } - else { - svg.draw_text(bbox.min + Point(scale_(0), scale_(0)), ("nPoints: " + std::to_string(layer_nodes.size())).c_str(), "green", 2); - } - svg.draw_text(bbox.min + Point(scale_(0), scale_(2)), legends[0].c_str(), colors[0].c_str(), 2); - svg.draw_text(bbox.min + Point(scale_(0), scale_(4)), legends[1].c_str(), colors[1].c_str(), 2); - svg.draw_text(bbox.min + Point(scale_(0), scale_(6)), legends[2].c_str(), colors[2].c_str(), 2); - - // draw layer nodes - svg.draw(layer_pts, "green", coord_t(scale_(0.1))); -#if 0 - // lower layer points - layer_pts.clear(); - for (TreeSupport::Node *node : lower_layer_nodes) { - layer_pts.push_back(node->position); - } - svg.draw(layer_pts, "black", coord_t(scale_(0.1))); - - // higher layer points - layer_pts.clear(); - for (TreeSupport::Node* node : layer_nodes) { - if(node->parent) - layer_pts.push_back(node->parent->position); - } - svg.draw(layer_pts, "blue", coord_t(scale_(0.1))); -#endif -} - -static void draw_layer_mst -(const std::string &layer_nr_or_z, - const std::vector &spanning_trees, - const ExPolygons& outline -) -{ - auto lines = spanning_tree_to_lines(spanning_trees); - BoundingBox bbox = get_extents(lines); - for (auto& poly : outline) - { - BoundingBox bb = poly.contour.bounding_box(); - bbox.merge(bb); - } - - SVG svg(get_svg_filename(layer_nr_or_z, "mstree").c_str(), bbox); - if (!svg.is_opened()) return; - - svg.draw(lines, "blue", coord_t(scale_(0.05))); - svg.draw_outline(outline, "yellow"); - for (auto &spanning_tree : spanning_trees) - svg.draw(spanning_tree.vertices(), "black", coord_t(scale_(0.1))); -} - -static void draw_two_overhangs_to_svg(SupportLayer* ts_layer, const ExPolygons& overhangs1, const ExPolygons& overhangs2) -{ - if (overhangs1.empty() && overhangs2.empty()) - return; - BoundingBox bbox1 = get_extents(overhangs1); - BoundingBox bbox2 = get_extents(overhangs2); - bbox1.merge(bbox2); - - SVG svg(get_svg_filename(std::to_string(ts_layer->print_z), "two_overhangs"), bbox1); - if (!svg.is_opened()) return; - - svg.draw(union_ex(overhangs1), "blue"); - svg.draw(union_ex(overhangs2), "red"); -} - -static void draw_polylines(SupportLayer* ts_layer, Polylines& polylines) -{ - if (polylines.empty()) - return; - BoundingBox bbox = get_extents(polylines); - - SVG svg(get_svg_filename(std::to_string(ts_layer->print_z), "lightnings"), bbox); - if (!svg.is_opened()) return; - - int id = 0; - for (Polyline& pline : polylines) - { - int i1, i2; - for (size_t i = 0; i < pline.size() - 1; i++) - { - i1 = i; - i2 = i + 1; - svg.draw(Line(pline.points[i1], pline.points[i2]), "blue"); - svg.draw(pline.points[i1], "red"); - id++; - svg.draw_text(pline.points[i1], std::to_string(id).c_str(), "black", 1); - } - svg.draw(pline.points[i2], "red"); - id++; - svg.draw_text(pline.points[i2], std::to_string(id).c_str(), "black", 1); - } -} -#endif - -// Move point from inside polygon if distance>0, outside if distance<0. -// Special case: distance=0 means find the nearest point of from on the polygon contour. -// The max move distance should not excceed max_move_distance. -static unsigned int move_inside_expoly(const ExPolygon &polygon, Point& from, double distance = 0, double max_move_distance = std::numeric_limits::max()) -{ - //TODO: This is copied from the moveInside of Polygons. - /* - We'd like to use this function as subroutine in moveInside(Polygons...), but - then we'd need to recompute the distance of the point to the polygon, which - is expensive. Or we need to return the distance. We need the distance there - to compare with the distance to other polygons. - */ - Point ret = from; - double bestDist2 = std::numeric_limits::max(); - bool is_already_on_correct_side_of_boundary = false; // whether [from] is already on the right side of the boundary - const Polygon &contour = polygon.contour; - - if (contour.points.size() < 2) - { - return 0; - } - Point p0 = contour.points[polygon.contour.size() - 2]; - Point p1 = contour.points.back(); - // because we compare with vsize2_with_unscale here (no division by zero), we also need to compare by vsize2_with_unscale inside the loop - // to avoid integer rounding edge cases - bool projected_p_beyond_prev_segment = dot_with_unscale(p1 - p0, from - p0) >= vsize2_with_unscale(p1 - p0); - for(const Point& p2 : polygon.contour.points) - { - // X = A + Normal(B-A) * (((B-A) dot_with_unscale (P-A)) / VSize(B-A)); - // = A + (B-A) * ((B-A) dot_with_unscale (P-A)) / VSize2(B-A); - // X = P projected on AB - const Point& a = p1; - const Point& b = p2; - const Point& p = from; - Point ab = b - a; - Point ap = p - a; - double ab_length2 = vsize2_with_unscale(ab); - if(ab_length2 <= 0) //A = B, i.e. the input polygon had two adjacent points on top of each other. - { - p1 = p2; //Skip only one of the points. - continue; - } - double dot_prod = dot_with_unscale(ab, ap); - if (dot_prod <= 0) // x is projected to before ab - { - if (projected_p_beyond_prev_segment) - { // case which looks like: > . - projected_p_beyond_prev_segment = false; - Point& x = p1; - - double dist2 = vsize2_with_unscale(x - p); - if (dist2 < bestDist2) - { - bestDist2 = dist2; - if (distance == 0) - { - ret = x; - } - else - { - // TODO: check whether it needs scale_() - Point inward_dir = turn90_ccw(normal(ab, 10.0) + normal(p1 - p0, 10.0)); // inward direction irrespective of sign of [distance] - // MM2INT(10.0) to retain precision for the eventual normalization - ret = x + normal(inward_dir, scale_(distance)); - is_already_on_correct_side_of_boundary = dot_with_unscale(inward_dir, p - x) * distance >= 0; - } - } - } - else - { - projected_p_beyond_prev_segment = false; - p0 = p1; - p1 = p2; - continue; - } - } - else if (dot_prod >= ab_length2) // x is projected to beyond ab - { - projected_p_beyond_prev_segment = true; - p0 = p1; - p1 = p2; - continue; - } - else - { // x is projected to a point properly on the line segment (not onto a vertex). The case which looks like | . - projected_p_beyond_prev_segment = false; - Point x = a + ab * (dot_prod / ab_length2); - - double dist2 = vsize2_with_unscale(p - x); - if (dist2 < bestDist2) - { - bestDist2 = dist2; - if (distance == 0) - { - ret = x; - } - else - { - Point inward_dir = turn90_ccw(normal(ab, scale_(distance))); // inward or outward depending on the sign of [distance] - ret = x + inward_dir; - is_already_on_correct_side_of_boundary = dot_with_unscale(inward_dir, p - x) >= 0; - } - } - } - - p0 = p1; - p1 = p2; - } - - if (is_already_on_correct_side_of_boundary) // when the best point is already inside and we're moving inside, or when the best point is already outside and we're moving outside - { - // BBS. Remove this condition. - if (bestDist2 < distance * distance) - { - from = ret; - } - } - else if (bestDist2 < max_move_distance * max_move_distance) - { - from = ret; - } - return 0; -} - -/* - * Implementation assumes moving inside, but moving outside should just as well be possible. - */ -static bool move_inside_expolys(const ExPolygons& polygons, Point& from, double distance, double max_move_distance) -{ - Point from0 = from; - Point ret = from; - std::vector valid_pts; - double bestDist2 = std::numeric_limits::max(); - unsigned int bestPoly = NO_INDEX; - bool is_already_on_correct_side_of_boundary = false; // whether [from] is already on the right side of the boundary - Point inward_dir; - for (unsigned int poly_idx = 0; poly_idx < polygons.size(); poly_idx++) - { - const ExPolygon poly = polygons[poly_idx]; - if (poly.contour.size() < 2) - continue; - Point p0 = poly.contour[poly.contour.size()-2]; - Point p1 = poly.contour.points.back(); - // because we compare with vsize2_with_unscale here (no division by zero), we also need to compare by vsize2_with_unscale inside the loop - // to avoid integer rounding edge cases - bool projected_p_beyond_prev_segment = dot_with_unscale(p1 - p0, from - p0) >= vsize2_with_unscale(p1 - p0); - for(const Point& p2 : poly.contour.points) - { - // X = A + Normal(B-A) * (((B-A) dot_with_unscale (P-A)) / VSize(B-A)); - // = A + (B-A) * ((B-A) dot_with_unscale (P-A)) / VSize2(B-A); - // X = P projected on AB - Point a = p1; - Point b = p2; - Point p = from; - Point ab = b - a; - Point ap = p - a; - double ab_length2 = vsize2_with_unscale(ab); - if(ab_length2 <= 0) //A = B, i.e. the input polygon had two adjacent points on top of each other. - { - p1 = p2; //Skip only one of the points. - continue; - } - double dot_prod = dot_with_unscale(ab, ap); - if (dot_prod <= 0) // x is projected to before ab - { - if (projected_p_beyond_prev_segment) - { // case which looks like: > . - projected_p_beyond_prev_segment = false; - Point& x = p1; - - double dist2 = vsize2_with_unscale(x - p); - if (dist2 < bestDist2) - { - bestDist2 = dist2; - bestPoly = poly_idx; - if (distance == 0) { ret = x; } - else - { - inward_dir = turn90_ccw(normal(ab, 10.0) + normal(p1 - p0, 10.0)); // inward direction irrespective of sign of [distance] - // MM2INT(10.0) to retain precision for the eventual normalization - ret = x + normal(inward_dir, scale_(distance)); - is_already_on_correct_side_of_boundary = dot_with_unscale(inward_dir, p - x) * distance >= 0; - if (is_already_on_correct_side_of_boundary && dist2 < distance * distance) - valid_pts.push_back(ret-from0); - } - } - } - else - { - projected_p_beyond_prev_segment = false; - p0 = p1; - p1 = p2; - continue; - } - } - else if (dot_prod >= ab_length2) // x is projected to beyond ab - { - projected_p_beyond_prev_segment = true; - p0 = p1; - p1 = p2; - continue; - } - else - { // x is projected to a point properly on the line segment (not onto a vertex). The case which looks like | . - projected_p_beyond_prev_segment = false; - Point x = a + ab * (dot_prod / ab_length2); - - double dist2 = vsize2_with_unscale(p - x); - if (dist2 < bestDist2) - { - bestDist2 = dist2; - bestPoly = poly_idx; - if (distance == 0) { ret = x; } - else - { - inward_dir = turn90_ccw(normal(ab, scale_(distance))); // inward or outward depending on the sign of [distance] - ret = x + inward_dir; - is_already_on_correct_side_of_boundary = dot_with_unscale(inward_dir, p - x) >= 0; - if (is_already_on_correct_side_of_boundary && dist2 1) { - // std::sort(valid_pts.begin(), valid_pts.end()); - // Point v_combine = valid_pts[0] + valid_pts[1]; - // if(vsize2_with_unscale(v_combine)::max(); - - for (const ExPolygon &poly : polygons) { - for (int i = 0; i < poly.num_contours(); i++) { - const Point* candidate = poly.contour_or_hole(i).closest_point(from); - double dist2 = vsize2_with_unscale(*candidate - from); - if (dist2 < min_dist2) { - closest_pt = *candidate; - min_dist2 = dist2; - } - } - } - - return closest_pt; -} - -static bool move_outside_expolys(const ExPolygons& polygons, Point& from, double distance, double max_move_distance) -{ - return move_inside_expolys(polygons, from, -distance, -max_move_distance); -} - -static bool is_inside_ex(const ExPolygon &polygon, const Point &pt) -{ - if (!get_extents(polygon).contains(pt)) - return false; - - return polygon.contains(pt); -} - -static bool is_inside_ex(const ExPolygons &polygons, const Point &pt) -{ - for (const ExPolygon &poly : polygons) { - if (is_inside_ex(poly, pt)) - return true; - } - - return false; -} - -static bool move_out_expolys(const ExPolygons& polygons, Point& from, double distance, double max_move_distance) -{ - Point from0 = from; - ExPolygons polys_dilated = union_ex(offset_ex(polygons, scale_(distance))); - Point pt = projection_onto(polys_dilated, from);// find_closest_ex(from, polys_dilated); - Point outward_dir = pt - from; - Point pt_max = from + normal(outward_dir, scale_(max_move_distance)); - double dist2 = vsize2_with_unscale(outward_dir); - if (dist2 > SQ(max_move_distance)) - pt = pt_max; - // case 5: already outside and far enough, no need to move - if (!is_inside_ex(polys_dilated, from)) - return true; - else if (!is_inside_ex(polygons, from)) { - // case 4: already outside but not far enough - from = pt; - return true; - } - else { - bool pt_max_in_poly = is_inside_ex(polygons, pt_max); - if (!pt_max_in_poly) { - from = pt_max; - return true; - } - else { - return false; - } - } -} - -static Point bounding_box_middle(const BoundingBox &bbox) -{ - return (bbox.max + bbox.min) / 2; -} - -TreeSupport::TreeSupport(PrintObject& object, const SlicingParameters &slicing_params) - : m_object(&object), m_slicing_params(slicing_params), m_object_config(&object.config()) -{ - m_raft_layers = slicing_params.base_raft_layers + slicing_params.interface_raft_layers; - support_type = m_object_config->support_type; - support_style = m_object_config->support_style; - if (support_style == smsDefault) - support_style = smsTreeHybrid; - SupportMaterialPattern support_pattern = m_object_config->support_base_pattern; - if (support_style == smsTreeHybrid && support_pattern == smpDefault) support_pattern = smpRectilinear; - m_support_params.base_fill_pattern = - support_pattern == smpLightning ? ipLightning : - support_pattern == smpHoneycomb ? ipHoneycomb : - m_support_params.support_density > 0.95 || m_support_params.with_sheath ? ipRectilinear : - ipSupportBase; - - m_support_params.interface_fill_pattern = (m_support_params.interface_density > 0.95 ? ipRectilinear : ipSupportBase); - if (m_object_config->support_interface_pattern == smipGrid) - m_support_params.contact_fill_pattern = ipGrid; - else if (m_object_config->support_interface_pattern == smipRectilinearInterlaced) - m_support_params.contact_fill_pattern = ipRectilinear; - else - m_support_params.contact_fill_pattern = (m_object_config->support_interface_pattern == smipAuto && m_slicing_params.soluble_interface) || - m_object_config->support_interface_pattern == smipConcentric ? - ipConcentric : - (m_support_params.interface_density > 0.95 ? ipRectilinear : ipSupportBase); - - const auto nozzle_diameter = object.print()->config().nozzle_diameter.get_at(object.config().support_interface_filament-1); - const coordf_t extrusion_width = m_object_config->line_width.get_abs_value(nozzle_diameter); - const coordf_t support_extrusion_width = m_object_config->support_line_width.get_abs_value(nozzle_diameter); - - m_support_params.support_extrusion_width = support_extrusion_width > 0 ? support_extrusion_width : extrusion_width; - is_slim = is_tree_slim(support_type, support_style); - is_strong = is_tree(support_type) && support_style == smsTreeStrong; - MAX_BRANCH_RADIUS = 10.0; - tree_support_branch_diameter_angle = 5.0;//is_slim ? 10.0 : 5.0; - // by default tree support needs no infill, unless it's tree hybrid which contains normal nodes. - with_infill = support_pattern != smpNone && support_pattern != smpDefault; - const PrintConfig& print_config = m_object->print()->config(); - m_machine_border.contour = get_bed_shape_with_excluded_area(print_config); - Vec3d plate_offset = m_object->print()->get_plate_origin(); - // align with the centered object in current plate (may not be the 1st plate, so need to add the plate offset) - m_machine_border.translate(Point(scale_(plate_offset(0)), scale_(plate_offset(1))) - m_object->instances().front().shift); -#ifdef SUPPORT_TREE_DEBUG_TO_SVG - SVG svg("SVG/machine_boarder.svg", m_object->bounding_box()); - if (svg.is_opened()) svg.draw(m_machine_border, "yellow"); -#endif -} - - -#define SUPPORT_SURFACES_OFFSET_PARAMETERS ClipperLib::jtSquare, 0. -void TreeSupport::detect_overhangs(bool detect_first_sharp_tail_only) -{ - // overhangs are already detected - if (m_object->support_layer_count() >= m_object->layer_count()) - return; - - // Clear and create Tree Support Layers - m_object->clear_support_layers(); - m_object->clear_tree_support_preview_cache(); - create_tree_support_layers(); - - const PrintObjectConfig& config = m_object->config(); - SupportType stype = support_type; - const coordf_t radius_sample_resolution = g_config_tree_support_collision_resolution; - const double nozzle_diameter = m_object->print()->config().nozzle_diameter.get_at(0); - const coordf_t extrusion_width = config.get_abs_value("line_width", nozzle_diameter); - const coordf_t extrusion_width_scaled = scale_(extrusion_width); - const coordf_t max_bridge_length = scale_(config.max_bridge_length.value); - const bool bridge_no_support = max_bridge_length > 0; - const bool support_critical_regions_only = config.support_critical_regions_only.value; - const bool config_remove_small_overhangs = config.support_remove_small_overhang.value; - const int enforce_support_layers = config.enforce_support_layers.value; - const double area_thresh_well_supported = SQ(scale_(6)); - const double length_thresh_well_supported = scale_(6); - static const double sharp_tail_max_support_height = 16.f; - // a region is considered well supported if the number of layers below it exceeds this threshold - const int thresh_layers_below = 10 / config.layer_height; - double obj_height = m_object->size().z(); - // +1 makes the threshold inclusive - double thresh_angle = config.support_threshold_angle.value > EPSILON ? config.support_threshold_angle.value + 1 : 30; - thresh_angle = std::min(thresh_angle, 89.); // should be smaller than 90 - const double threshold_rad = Geometry::deg2rad(thresh_angle); - - // for small overhang removal - struct OverhangCluster { - std::map layer_overhangs; - ExPolygons merged_poly; - BoundingBox merged_bbox; - int min_layer = 1e7; - int max_layer = 0; - coordf_t offset = 0; - bool is_cantilever = false; - bool is_sharp_tail = false; - bool is_small_overhang = false; - OverhangCluster(const ExPolygon* expoly, int layer_nr) { - push_back(expoly, layer_nr); - } - void push_back(const ExPolygon* expoly, int layer_nr) { - layer_overhangs.emplace(layer_nr, expoly); - auto dilate1 = offset_ex(*expoly, offset); - if (!dilate1.empty()) - merged_poly = union_ex(merged_poly, dilate1); - min_layer = std::min(min_layer, layer_nr); - max_layer = std::max(max_layer, layer_nr); - merged_bbox.merge(get_extents(dilate1)); - } - int height() { - return max_layer - min_layer + 1; - } - bool intersects(const ExPolygon& region, int layer_nr, coordf_t offset) { - if (layer_nr < 1) return false; - auto it = layer_overhangs.find(layer_nr - 1); - if (it == layer_overhangs.end()) return false; - const ExPolygon* overhang = it->second; - - this->offset = offset; - auto dilate1 = offset_ex(region, offset); - BoundingBox bbox = get_extents(dilate1); - if (!merged_bbox.overlap(bbox)) - return false; - return overlaps({ *overhang }, dilate1); - } - // it's basically the combination of push_back and intersects, but saves an offset_ex - bool push_back_if_intersects(const ExPolygon& region, int layer_nr, coordf_t offset) { - bool is_intersect = false; - ExPolygons dilate1; - BoundingBox bbox; - do { - if (layer_nr < 1) break; - auto it = layer_overhangs.find(layer_nr - 1); - if (it == layer_overhangs.end()) break; - const ExPolygon* overhang = it->second; - - this->offset = offset; - dilate1 = offset_ex(region, offset); - if (dilate1.empty()) break; - bbox = get_extents(dilate1); - if (!merged_bbox.overlap(bbox)) - break; - is_intersect = overlaps({ *overhang }, dilate1); - } while (0); - if (is_intersect) { - layer_overhangs.emplace(layer_nr, ®ion); - merged_poly = union_ex(merged_poly, dilate1); - min_layer = std::min(min_layer, layer_nr); - max_layer = std::max(max_layer, layer_nr); - merged_bbox.merge(bbox); - } - return is_intersect; - } - }; - std::vector overhangClusters; - - auto find_and_insert_cluster = [](auto ®ionClusters, const ExPolygon ®ion, int layer_nr, coordf_t offset) { - OverhangCluster *cluster = nullptr; - for (int i = 0; i < regionClusters.size(); i++) { - auto cluster_i = ®ionClusters[i]; - if (cluster_i->push_back_if_intersects(region, layer_nr, offset)) { - cluster = cluster_i; - break; - } - } - if (!cluster) { - cluster = ®ionClusters.emplace_back(®ion, layer_nr); - } - return cluster; - }; - - if (!is_tree(stype)) return; - - max_cantilever_dist = 0; - - // main part of overhang detection can be parallel - tbb::parallel_for(tbb::blocked_range(0, m_object->layer_count()), - [&](const tbb::blocked_range& range) { - for (size_t layer_nr = range.begin(); layer_nr < range.end(); layer_nr++) { - if (m_object->print()->canceled()) - break; - - if (!is_auto(stype) && layer_nr > enforce_support_layers) - continue; - - Layer* layer = m_object->get_layer(layer_nr); - - if (layer->lower_layer == nullptr) { - for (auto& slice : layer->lslices) { - auto bbox_size = get_extents(slice).size(); - if (!((bbox_size.x() > length_thresh_well_supported && bbox_size.y() > length_thresh_well_supported)) - && g_config_support_sharp_tails) { - layer->sharp_tails.push_back(slice); - layer->sharp_tails_height.insert({ &slice, layer->height }); - } - } - continue; - } - - Layer* lower_layer = layer->lower_layer; - coordf_t lower_layer_offset = layer_nr < enforce_support_layers ? -0.15 * extrusion_width : (float)lower_layer->height / tan(threshold_rad); - coordf_t support_offset_scaled = scale_(lower_layer_offset); - // Filter out areas whose diameter that is smaller than extrusion_width. Do not use offset2() for this purpose! - ExPolygons lower_polys; - for (const ExPolygon& expoly : lower_layer->lslices) { - if (!offset_ex(expoly, -extrusion_width_scaled / 2).empty()) { - lower_polys.emplace_back(expoly); - } - } - ExPolygons curr_polys; - std::vector curr_poly_ptrs; - for (const ExPolygon& expoly : layer->lslices) { - if (!offset_ex(expoly, -extrusion_width_scaled / 2).empty()) { - curr_polys.emplace_back(expoly); - curr_poly_ptrs.emplace_back(&expoly); - } - } - - // normal overhang - ExPolygons lower_layer_offseted = offset_ex(lower_polys, support_offset_scaled, SUPPORT_SURFACES_OFFSET_PARAMETERS); - ExPolygons overhang_areas = diff_ex(curr_polys, lower_layer_offseted); - - overhang_areas.erase(std::remove_if(overhang_areas.begin(), overhang_areas.end(), - [extrusion_width_scaled](ExPolygon& area) { return offset_ex(area, -0.1 * extrusion_width_scaled).empty(); }), - overhang_areas.end()); - - - if (is_auto(stype) && g_config_support_sharp_tails) - { - // BBS detect sharp tail - for (const ExPolygon* expoly : curr_poly_ptrs) { - bool is_sharp_tail = false; - // 1. nothing below - // this is a sharp tail region if it's small but non-ignorable - if (!overlaps(offset_ex(*expoly, 0.5 * extrusion_width_scaled), lower_polys)) { - is_sharp_tail = expoly->area() < area_thresh_well_supported && !offset_ex(*expoly, -0.1 * extrusion_width_scaled).empty(); - } - - if (is_sharp_tail) { - ExPolygons overhang = diff_ex({ *expoly }, lower_polys); - layer->sharp_tails.push_back(*expoly); - layer->sharp_tails_height.insert({ expoly, layer->height }); - append(overhang_areas, overhang); - - if (!overhang.empty()) { - has_sharp_tails = true; -#ifdef SUPPORT_TREE_DEBUG_TO_SVG - SVG svg(format("SVG/sharp_tail_orig_%.02f.svg", layer->print_z), m_object->bounding_box()); - if (svg.is_opened()) svg.draw(overhang, "red"); -#endif - } - } - } - } - - SupportLayer* ts_layer = m_object->get_support_layer(layer_nr + m_raft_layers); - for (ExPolygon& poly : overhang_areas) { - if (offset_ex(poly, -0.1 * extrusion_width_scaled).empty()) continue; - ts_layer->overhang_areas.emplace_back(poly); - - // check cantilever - { - auto cluster_boundary_ex = intersection_ex(poly, offset_ex(lower_layer->lslices, scale_(0.5))); - Polygons cluster_boundary = to_polygons(cluster_boundary_ex); - if (cluster_boundary.empty()) continue; - double dist_max = 0; - for (auto& pt : poly.contour.points) { - double dist_pt = std::numeric_limits::max(); - for (auto& ply : cluster_boundary) { - double d = ply.distance_to(pt); - dist_pt = std::min(dist_pt, d); - } - dist_max = std::max(dist_max, dist_pt); - } - if (dist_max > scale_(3)) { // is cantilever if the farmost point is larger than 3mm away from base - max_cantilever_dist = std::max(max_cantilever_dist, dist_max); - layer->cantilevers.emplace_back(poly); - BOOST_LOG_TRIVIAL(debug) << "found a cantilever cluster. layer_nr=" << layer_nr << dist_max; - has_cantilever = true; - } - } - } - } - } - ); // end tbb::parallel_for - - BOOST_LOG_TRIVIAL(info) << "max_cantilever_dist=" << max_cantilever_dist; - - // check if the sharp tails should be extended higher - if (is_auto(stype) && g_config_support_sharp_tails && !detect_first_sharp_tail_only) { - for (size_t layer_nr = 0; layer_nr < m_object->layer_count(); layer_nr++) { - if (m_object->print()->canceled()) - break; - - Layer* layer = m_object->get_layer(layer_nr); - SupportLayer* ts_layer = m_object->get_support_layer(layer_nr + m_raft_layers); - Layer* lower_layer = layer->lower_layer; - if (!lower_layer) - continue; - - // BBS detect sharp tail - const ExPolygons& lower_layer_sharptails = lower_layer->sharp_tails; - const auto& lower_layer_sharptails_height = lower_layer->sharp_tails_height; - for (ExPolygon& expoly : layer->lslices) { - bool is_sharp_tail = false; - float accum_height = layer->height; - do { - // 2. something below - // check whether this is above a sharp tail region. - - // 2.1 If no sharp tail below, this is considered as common region. - ExPolygons supported_by_lower = intersection_ex({ expoly }, lower_layer_sharptails); - if (supported_by_lower.empty()) { - is_sharp_tail = false; - break; - } - - // 2.2 If sharp tail below, check whether it support this region enough. -#if 0 - // judge by area isn't reliable, failure cases include 45 degree rotated cube - float supported_area = area(supported_by_lower); - if (supported_area > area_thresh_well_supported) { - is_sharp_tail = false; - break; - } -#endif - BoundingBox bbox = get_extents(supported_by_lower); - if (bbox.size().x() > length_thresh_well_supported && bbox.size().y() > length_thresh_well_supported) { - is_sharp_tail = false; - break; - } - - // 2.3 check whether sharp tail exceed the max height - for (const auto& lower_sharp_tail_height : lower_layer_sharptails_height) { - if (lower_sharp_tail_height.first->overlaps(expoly)) { - accum_height += lower_sharp_tail_height.second; - break; - } - } - if (accum_height > sharp_tail_max_support_height) { - is_sharp_tail = false; - break; - } - - // 2.4 if the area grows fast than threshold, it get connected to other part or - // it has a sharp slop and will be auto supported. - ExPolygons new_overhang_expolys = diff_ex({ expoly }, lower_layer_sharptails); - if ((get_extents(new_overhang_expolys).size() - get_extents(lower_layer_sharptails).size()).both_comp(Point(scale_(5), scale_(5)), ">") || !offset_ex(new_overhang_expolys, -5.0 * extrusion_width_scaled).empty()) { - is_sharp_tail = false; - break; - } - - // 2.5 mark the expoly as sharptail - is_sharp_tail = true; - } while (0); - - if (is_sharp_tail) { - ExPolygons overhang = diff_ex({ expoly }, lower_layer->lslices); - layer->sharp_tails.push_back(expoly); - layer->sharp_tails_height.insert({ &expoly, accum_height }); - append(ts_layer->overhang_areas, overhang); - - if (!overhang.empty()) - has_sharp_tails = true; -#ifdef SUPPORT_TREE_DEBUG_TO_SVG - SVG svg(format("SVG/sharp_tail_%.02f.svg",layer->print_z), m_object->bounding_box()); - if (svg.is_opened()) svg.draw(overhang, "red"); -#endif - } - - } - } - } - - // group overhang clusters - for (size_t layer_nr = 0; layer_nr < m_object->layer_count(); layer_nr++) { - if (m_object->print()->canceled()) - break; - SupportLayer* ts_layer = m_object->get_support_layer(layer_nr + m_raft_layers); - Layer* layer = m_object->get_layer(layer_nr); - for (auto& overhang : ts_layer->overhang_areas) { - OverhangCluster* cluster = find_and_insert_cluster(overhangClusters, overhang, layer_nr, extrusion_width_scaled); - if (overlaps({ overhang },layer->cantilevers)) - cluster->is_cantilever = true; - } - } - - auto enforcers = m_object->slice_support_enforcers(); - auto blockers = m_object->slice_support_blockers(); - m_object->project_and_append_custom_facets(false, EnforcerBlockerType::ENFORCER, enforcers); - m_object->project_and_append_custom_facets(false, EnforcerBlockerType::BLOCKER, blockers); - if (is_auto(stype) && config_remove_small_overhangs) { - if (blockers.size() < m_object->layer_count()) - blockers.resize(m_object->layer_count()); - for (auto& cluster : overhangClusters) { - // 3. check whether the small overhang is sharp tail - cluster.is_sharp_tail = false; - for (size_t layer_id = cluster.min_layer; layer_id <= cluster.max_layer; layer_id++) { - Layer* layer = m_object->get_layer(layer_id); - if (overlaps(layer->sharp_tails, cluster.merged_poly)) { - cluster.is_sharp_tail = true; - break; - } - } - - if (!cluster.is_sharp_tail && !cluster.is_cantilever) { - // 2. check overhang cluster size is smaller than 3.0 * fw_scaled - auto erode1 = offset_ex(cluster.merged_poly, -1 * extrusion_width_scaled); - Point bbox_sz = get_extents(erode1).size(); - if (bbox_sz.x() < 2 * extrusion_width_scaled || bbox_sz.y() < 2 * extrusion_width_scaled) { - cluster.is_small_overhang = true; - } - } - -#ifdef SUPPORT_TREE_DEBUG_TO_SVG - const Layer* layer1 = m_object->get_layer(cluster.min_layer); - BoundingBox bbox = cluster.merged_bbox; - bbox.merge(get_extents(layer1->lslices)); - SVG svg(format("SVG/overhangCluster_%s-%s_%s-%s_tail=%s_cantilever=%s_small=%s.svg", - cluster.min_layer, cluster.max_layer, layer1->print_z, m_object->get_layer(cluster.max_layer)->print_z, - cluster.is_sharp_tail, cluster.is_cantilever, cluster.is_small_overhang), bbox); - if (svg.is_opened()) { - svg.draw(layer1->lslices, "red"); - svg.draw(cluster.merged_poly, "blue"); - svg.draw_text(bbox.min + Point(scale_(0), scale_(2)), "lslices", "red", 2); - svg.draw_text(bbox.min + Point(scale_(0), scale_(2)), "overhang", "blue", 2); - } -#endif - - if (!cluster.is_small_overhang) - continue; - - for (auto it = cluster.layer_overhangs.begin(); it != cluster.layer_overhangs.end(); it++) { - int layer_nr = it->first; - auto p_overhang = it->second; - blockers[layer_nr].push_back(p_overhang->contour); - } - } - } - - has_overhangs = false; - for (int layer_nr = 0; layer_nr < m_object->layer_count(); layer_nr++) { - if (m_object->print()->canceled()) - break; - - SupportLayer* ts_layer = m_object->get_support_layer(layer_nr + m_raft_layers); - auto layer = m_object->get_layer(layer_nr); - auto lower_layer = layer->lower_layer; - if (support_critical_regions_only && is_auto(stype)) { - ts_layer->overhang_areas.clear(); - if (lower_layer == nullptr) - ts_layer->overhang_areas = layer->sharp_tails; - else - ts_layer->overhang_areas = diff_ex(layer->sharp_tails, lower_layer->lslices); - - append(ts_layer->overhang_areas, layer->cantilevers); - } - - if (layer_nr < blockers.size()) { - Polygons& blocker = blockers[layer_nr]; - // Arthur: union_ is a must because after mirroring, the blocker polygons are in left-hand coordinates, ie clockwise, - // which are not valid polygons, and will be removed by offset_ex. union_ can make these polygons right. - ts_layer->overhang_areas = diff_ex(ts_layer->overhang_areas, offset_ex(union_(blocker), scale_(radius_sample_resolution))); - } - - if (max_bridge_length > 0 && ts_layer->overhang_areas.size() > 0 && lower_layer) { - // do not break bridge for normal part in TreeHybrid - bool break_bridge = !(support_style == smsTreeHybrid && area(ts_layer->overhang_areas) > m_support_params.thresh_big_overhang); - m_object->remove_bridges_from_contacts(lower_layer, layer, extrusion_width_scaled, &ts_layer->overhang_areas, max_bridge_length, break_bridge); - } - - for (auto &area : ts_layer->overhang_areas) { - ts_layer->overhang_types.emplace(&area, SupportLayer::Detected); - } - // enforcers now follow same logic as normal support. See STUDIO-3692 - if (layer_nr < enforcers.size() && lower_layer) { - float no_interface_offset = std::accumulate(layer->regions().begin(), layer->regions().end(), FLT_MAX, - [](float acc, const LayerRegion* layerm) { return std::min(acc, float(layerm->flow(frExternalPerimeter).scaled_width())); }); - Polygons lower_layer_polygons = (layer_nr == 0) ? Polygons() : to_polygons(lower_layer->lslices); - Polygons& enforcer = enforcers[layer_nr]; - if (!enforcer.empty()) { - ExPolygons enforcer_polygons = diff_ex(intersection_ex(layer->lslices, enforcer), - // Inflate just a tiny bit to avoid intersection of the overhang areas with the object. - expand(lower_layer_polygons, 0.05f * no_interface_offset, SUPPORT_SURFACES_OFFSET_PARAMETERS)); - append(ts_layer->overhang_areas, enforcer_polygons); - ts_layer->overhang_types.emplace(&ts_layer->overhang_areas.back(), SupportLayer::Enforced); - } - } - - if (!ts_layer->overhang_areas.empty()) has_overhangs = true; - if (!layer->cantilevers.empty()) has_cantilever = true; - } - -#ifdef SUPPORT_TREE_DEBUG_TO_SVG - for (const SupportLayer* layer : m_object->support_layers()) { - if (layer->overhang_areas.empty() && (blockers.size()<=layer->id() || blockers[layer->id()].empty())) - continue; - - SVG svg(format("SVG/overhang_areas_%s.svg", layer->print_z), m_object->bounding_box()); - if (svg.is_opened()) { - svg.draw_outline(m_object->get_layer(layer->id())->lslices, "yellow"); - svg.draw(layer->overhang_areas, "orange"); - if (blockers.size() > layer->id()) - svg.draw(blockers[layer->id()], "red"); - } - if (enforcers.size() > layer->id()) { - SVG svg(format("SVG/enforcer_%s.svg", layer->print_z), m_object->bounding_box()); - if (svg.is_opened()) { - svg.draw_outline(m_object->get_layer(layer->id())->lslices, "yellow"); - svg.draw(enforcers[layer->id()], "red"); - } - } - if (blockers.size() > layer->id()) { - SVG svg(format("SVG/blocker_%s.svg", layer->print_z), m_object->bounding_box()); - if (svg.is_opened()) { - svg.draw_outline(m_object->get_layer(layer->id())->lslices, "yellow"); - svg.draw(blockers[layer->id()], "red"); - } - } - } -#endif -} - -void TreeSupport::create_tree_support_layers() -{ - int layer_id = 0; - coordf_t raft_print_z = 0.f; - coordf_t raft_slice_z = 0.f; - for (; layer_id < m_slicing_params.base_raft_layers; layer_id++) { - raft_print_z += m_slicing_params.base_raft_layer_height; - raft_slice_z = raft_print_z - m_slicing_params.base_raft_layer_height / 2; - m_object->add_tree_support_layer(layer_id, m_slicing_params.base_raft_layer_height, raft_print_z, raft_slice_z); - } - - for (; layer_id < m_slicing_params.base_raft_layers + m_slicing_params.interface_raft_layers; layer_id++) { - raft_print_z += m_slicing_params.interface_raft_layer_height; - raft_slice_z = raft_print_z - m_slicing_params.interface_raft_layer_height / 2; - m_object->add_tree_support_layer(layer_id, m_slicing_params.base_raft_layer_height, raft_print_z, raft_slice_z); - } - - for (Layer *layer : m_object->layers()) { - SupportLayer* ts_layer = m_object->add_tree_support_layer(layer->id(), layer->height, layer->print_z, layer->slice_z); - if (ts_layer->id() > m_raft_layers) { - SupportLayer* lower_layer = m_object->get_support_layer(ts_layer->id() - 1); - lower_layer->upper_layer = ts_layer; - ts_layer->lower_layer = lower_layer; - } - } -} - -static inline BoundingBox fill_expolygon_generate_paths( - ExtrusionEntitiesPtr &dst, - ExPolygon &&expolygon, - Fill *filler, - const FillParams &fill_params, - ExtrusionRole role, - const Flow &flow) -{ - Surface surface(stInternal, std::move(expolygon)); - Polylines polylines; - try { - polylines = filler->fill_surface(&surface, fill_params); - } catch (InfillFailedException &) { - } - - BoundingBox fill_bbox; - if (!polylines.empty()) { - fill_bbox = polylines[0].bounding_box(); - for (auto& polyline : polylines) - fill_bbox.merge(polyline.bounding_box()); - } - - extrusion_entities_append_paths(dst, std::move(polylines), role, flow.mm3_per_mm(), flow.width(), flow.height()); - - return fill_bbox; -} - -static inline std::vector fill_expolygons_generate_paths( - ExtrusionEntitiesPtr &dst, - ExPolygons &&expolygons, - Fill *filler, - const FillParams &fill_params, - ExtrusionRole role, - const Flow &flow) -{ - std::vector fill_boxes; - for (ExPolygon& expoly : expolygons) { - auto box = fill_expolygon_generate_paths(dst, std::move(expoly), filler, fill_params, role, flow); - fill_boxes.emplace_back(box); - } - return fill_boxes; -} - -static void _make_loops(ExtrusionEntitiesPtr& loops_entities, ExPolygons &support_area, ExtrusionRole role, size_t wall_count, const Flow &flow) -{ - Polygons loops; - std::map depth_per_expoly; - std::list expoly_list; - - for (ExPolygon &expoly : support_area) { - expoly_list.emplace_back(std::move(expoly)); - depth_per_expoly.insert({&expoly_list.back(), 0}); - } - if (expoly_list.empty()) return; - - while (!expoly_list.empty()) { - polygons_append(loops, to_polygons(expoly_list.front())); - - auto first_iter = expoly_list.begin(); - auto depth_iter = depth_per_expoly.find(&expoly_list.front()); - if (depth_iter->second + 1 < wall_count) { - //ExPolygons expolys_new = offset_ex(expoly_list.front(), -float(flow.scaled_spacing()), jtSquare); - // shrink and then dilate to prevent overlapping and overflow - ExPolygons expolys_new = offset2_ex({expoly_list.front()}, -1.4 * float(flow.scaled_spacing()), .4 * float(flow.scaled_spacing())); - - for (ExPolygon &expoly : expolys_new) { - auto new_iter = expoly_list.insert(expoly_list.begin(), expoly); - depth_per_expoly.insert({&*new_iter, depth_iter->second + 1}); - } - } - depth_per_expoly.erase(depth_iter); - expoly_list.erase(first_iter); - } - - // draw connected loops - if (/*wall_count > 1 && wall_count<5*/0) { - // TODO this method may drop some contours - wall_count = std::min(wall_count, loops.size()); - Polylines polylines; - polylines.push_back(Polyline()); - Polyline& polyline = polylines.back(); - Point end_pt; - Point end_dir; - for (int wall_idx = 0; wall_idx < wall_count; wall_idx++) { - Polygon &loop = loops[wall_idx]; - if (loop.size()<3) continue; - // break the closed loop if this is not the last loop, so the next loop can attach to the end_pt - //if (wall_idx != wall_count - 1 && loop.first_point() == loop.last_point()) - // loop.points.pop_back(); - - if (wall_idx == 0) { - polyline.append(loop.points); - } else { - double d = loop.distance_to(end_pt); - if (d < scale_(2)) { // if current loop is close to the previous one - polyline.append(end_pt); - ExtrusionPath expath; - expath.polyline.append(loop.points); - ExtrusionLoop extru_loop(expath); - extru_loop.split_at(end_pt, false); - polyline.append(extru_loop.as_polyline()); - }else{// create a new polylie if they are far away - polylines.push_back(Polyline()); - polyline = polylines.back(); - polyline.append(loop.points); - } - } - end_pt = polyline.points.back(); - end_dir = end_pt - polyline.points[polyline.points.size() - 2]; - Point perpendicular_dir = turn90_ccw(end_dir); - end_pt = end_pt + normal(perpendicular_dir, flow.scaled_spacing()); - } - - extrusion_entities_append_paths(loops_entities, polylines, role, float(flow.mm3_per_mm()), float(flow.width()), float(flow.height())); - } else { - extrusion_entities_append_loops(loops_entities, std::move(loops), role, float(flow.mm3_per_mm()), float(flow.width()), float(flow.height())); - } - } - -static void make_perimeter_and_inner_brim(ExtrusionEntitiesPtr &dst, const ExPolygon &support_area, size_t wall_count, const Flow &flow, ExtrusionRole role) -{ - Polygons loops; - ExPolygons support_area_new = offset_ex(support_area, -0.5f * float(flow.scaled_spacing()), jtSquare); - _make_loops(dst, support_area_new, role, wall_count, flow); -} - -static void make_perimeter_and_infill(ExtrusionEntitiesPtr& dst, const Print& print, const ExPolygon& support_area, size_t wall_count, const Flow& flow, ExtrusionRole role, Fill* filler_support, double support_density, bool infill_first=true) -{ - Polygons loops; - ExPolygons support_area_new = offset_ex(support_area, -0.5f * float(flow.scaled_spacing()), jtSquare); - - // draw infill - FillParams fill_params; - fill_params.density = support_density; - fill_params.dont_adjust = true; - ExPolygons to_infill = support_area_new; - std::vector fill_boxes = fill_expolygons_generate_paths(dst, std::move(to_infill), filler_support, fill_params, role, flow); - - // allow wall_count to be zero, which means only draw infill - if (wall_count == 0) { - for (auto fill_bbox : fill_boxes) - { - // extend bounding box on x-axis - if (cos(filler_support->angle)>=sin(filler_support->angle)) { - fill_bbox.min[0] -= scale_(10); - fill_bbox.max[0] += scale_(10); - } - else { - fill_bbox.min[1] -= scale_(10); - fill_bbox.max[1] += scale_(10); - } - support_area_new = diff_ex(support_area_new, offset_ex(to_expolygons({ fill_bbox.polygon() }), 0.5*flow.scaled_width())); - } - // filter out small areas - for (auto it = support_area_new.begin(); it != support_area_new.end(); ) { - if (offset_ex(*it, -flow.scaled_width()).empty()) - it = support_area_new.erase(it); - else - it++; - } - } - - { // draw loops - ExtrusionEntitiesPtr loops_entities; - _make_loops(loops_entities, support_area_new, role, wall_count, flow); - - if (infill_first) - dst.insert(dst.end(), loops_entities.begin(), loops_entities.end()); - else { // loops first - loops_entities.insert(loops_entities.end(), dst.begin(), dst.end()); - dst = std::move(loops_entities); - } - } - if (infill_first) { - // sort regions to reduce travel - Points ordering_points; - for (const auto& area : dst) - ordering_points.push_back(area->first_point()); - std::vector order = chain_points(ordering_points); - ExtrusionEntitiesPtr new_dst; - new_dst.reserve(ordering_points.size()); - for (size_t i : order) - new_dst.emplace_back(dst[i]); - dst = new_dst; - } -} - -void TreeSupport::generate_toolpaths() -{ - const PrintConfig &print_config = m_object->print()->config(); - const PrintObjectConfig &object_config = m_object->config(); - coordf_t support_extrusion_width = m_support_params.support_extrusion_width; - coordf_t nozzle_diameter = print_config.nozzle_diameter.get_at(object_config.support_filament - 1); - coordf_t layer_height = object_config.layer_height.value; - const size_t wall_count = object_config.tree_support_wall_count.value; - - // Check if set to zero, use default if so. - if (support_extrusion_width <= 0.0) - support_extrusion_width = Flow::auto_extrusion_width(FlowRole::frSupportMaterial, (float)nozzle_diameter); - - // coconut: use same intensity settings as SupportMaterial.cpp - auto m_support_material_interface_flow = support_material_interface_flow(m_object, float(m_slicing_params.layer_height)); - coordf_t interface_spacing = object_config.support_interface_spacing.value + m_support_material_interface_flow.spacing(); - coordf_t bottom_interface_spacing = object_config.support_bottom_interface_spacing.value + m_support_material_interface_flow.spacing(); - coordf_t interface_density = std::min(1., m_support_material_interface_flow.spacing() / interface_spacing); - coordf_t bottom_interface_density = std::min(1., m_support_material_interface_flow.spacing() / bottom_interface_spacing); - - const coordf_t branch_radius = object_config.tree_support_branch_diameter.value / 2; - const coordf_t branch_radius_scaled = scale_(branch_radius); - - if (m_object->support_layers().empty()) - return; - - // calculate fill areas for raft layers - ExPolygons raft_areas; - if (m_object->layer_count() > 0) { - const Layer *layer = m_object->layers().front(); - for (const ExPolygon &expoly : layer->lslices) { - raft_areas.push_back(expoly); - } - } - - if (m_object->support_layer_count() > m_raft_layers) { - const SupportLayer *ts_layer = m_object->get_support_layer(m_raft_layers); - for (const ExPolygon& expoly : ts_layer->floor_areas) - raft_areas.push_back(expoly); - for (const ExPolygon& expoly : ts_layer->roof_areas) - raft_areas.push_back(expoly); - for (const ExPolygon& expoly : ts_layer->base_areas) - raft_areas.push_back(expoly); - } - - raft_areas = std::move(offset_ex(raft_areas, scale_(object_config.raft_first_layer_expansion))); - - // generate raft tool path - if (m_raft_layers > 0) - { - ExtrusionRole raft_contour_er = m_slicing_params.base_raft_layers > 0 ? erSupportMaterial : erSupportMaterialInterface; - SupportLayer *ts_layer = m_object->support_layers().front(); - Flow flow = m_object->print()->brim_flow(); - - Polygons loops; - for (const ExPolygon& expoly : raft_areas) { - loops.push_back(expoly.contour); - loops.insert(loops.end(), expoly.holes.begin(), expoly.holes.end()); - } - extrusion_entities_append_loops(ts_layer->support_fills.entities, std::move(loops), raft_contour_er, - float(flow.mm3_per_mm()), float(flow.width()), float(flow.height())); - raft_areas = offset_ex(raft_areas, -flow.scaled_spacing() / 2.); - } - - for (size_t layer_nr = 0; layer_nr < m_slicing_params.base_raft_layers; layer_nr++) { - SupportLayer *ts_layer = m_object->get_support_layer(layer_nr); - coordf_t expand_offset = (layer_nr == 0 ? 0. : -1.); - - Flow support_flow = layer_nr == 0 ? m_object->print()->brim_flow() : Flow(support_extrusion_width, ts_layer->height, nozzle_diameter); - Fill* filler_interface = Fill::new_from_type(ipRectilinear); - filler_interface->angle = layer_nr == 0 ? 90 : 0; - filler_interface->spacing = support_extrusion_width; - - FillParams fill_params; - fill_params.density = object_config.raft_first_layer_density * 0.01; - fill_params.dont_adjust = true; - - fill_expolygons_generate_paths(ts_layer->support_fills.entities, std::move(offset_ex(raft_areas, scale_(expand_offset))), - filler_interface, fill_params, erSupportMaterial, support_flow); - } - - for (size_t layer_nr = m_slicing_params.base_raft_layers; - layer_nr < m_slicing_params.base_raft_layers + m_slicing_params.interface_raft_layers; - layer_nr++) - { - SupportLayer *ts_layer = m_object->get_support_layer(layer_nr); - coordf_t expand_offset = (layer_nr == 0 ? 0. : -1.); - - Flow support_flow(support_extrusion_width, ts_layer->height, nozzle_diameter); - Fill* filler_interface = Fill::new_from_type(ipRectilinear); - filler_interface->angle = 0; - filler_interface->spacing = support_extrusion_width; - - FillParams fill_params; - fill_params.density = interface_density; - fill_params.dont_adjust = true; - - fill_expolygons_generate_paths(ts_layer->support_fills.entities, std::move(offset_ex(raft_areas, scale_(expand_offset))), - filler_interface, fill_params, erSupportMaterialInterface, support_flow); - } - - BoundingBox bbox_object(Point(-scale_(1.), -scale_(1.0)), Point(scale_(1.), scale_(1.))); - - std::shared_ptr filler_interface = std::shared_ptr(Fill::new_from_type(m_support_params.contact_fill_pattern)); - std::shared_ptr filler_Roof1stLayer = std::shared_ptr(Fill::new_from_type(ipRectilinear)); - filler_interface->set_bounding_box(bbox_object); - filler_Roof1stLayer->set_bounding_box(bbox_object); - filler_interface->angle = Geometry::deg2rad(object_config.support_angle.value + 90.); - filler_Roof1stLayer->angle = Geometry::deg2rad(object_config.support_angle.value + 90.); - - // generate tree support tool paths - tbb::parallel_for( - tbb::blocked_range(m_raft_layers, m_object->support_layer_count()), - [&](const tbb::blocked_range& range) - { - for (size_t layer_id = range.begin(); layer_id < range.end(); layer_id++) { - if (m_object->print()->canceled()) - break; - - m_object->print()->set_status(70, (boost::format(_L("Support: generate toolpath at layer %d")) % layer_id).str()); - - SupportLayer* ts_layer = m_object->get_support_layer(layer_id); - Flow support_flow(support_extrusion_width, ts_layer->height, nozzle_diameter); - coordf_t support_spacing = object_config.support_base_pattern_spacing.value + support_flow.spacing(); - coordf_t support_density = std::min(1., support_flow.spacing() / support_spacing); - ts_layer->support_fills.no_sort = false; - - for (auto& area_group : ts_layer->area_groups) { - ExPolygon& poly = *area_group.area; - ExPolygons polys; - FillParams fill_params; - if (area_group.type != SupportLayer::BaseType) { - // interface - if (layer_id == 0) { - Flow flow = m_raft_layers == 0 ? m_object->print()->brim_flow() : support_flow; - make_perimeter_and_inner_brim(ts_layer->support_fills.entities, poly, wall_count, flow, - area_group.type == SupportLayer::RoofType ? erSupportMaterialInterface : erSupportMaterial); - polys = std::move(offset_ex(poly, -flow.scaled_spacing())); - } else if (area_group.type == SupportLayer::Roof1stLayer) { - polys = std::move(offset_ex(poly, 0.5*support_flow.scaled_width())); - } - else { - polys.push_back(poly); - } - fill_params.density = interface_density; - fill_params.dont_adjust = true; - } - if (area_group.type == SupportLayer::Roof1stLayer) { - // roof_1st_layer - fill_params.density = interface_density; - // Note: spacing means the separation between two lines as if they are tightly extruded - filler_Roof1stLayer->spacing = m_support_material_interface_flow.spacing(); - // generate a perimeter first to support interface better - ExtrusionEntityCollection* temp_support_fills = new ExtrusionEntityCollection(); - make_perimeter_and_infill(temp_support_fills->entities, *m_object->print(), poly, 1, m_support_material_interface_flow, erSupportMaterial, - filler_Roof1stLayer.get(), interface_density, false); - temp_support_fills->no_sort = true; // make sure loops are first - if (!temp_support_fills->entities.empty()) - ts_layer->support_fills.entities.push_back(temp_support_fills); - else - delete temp_support_fills; - } else if (area_group.type == SupportLayer::FloorType) { - // floor_areas - fill_params.density = bottom_interface_density; - filler_interface->spacing = m_support_material_interface_flow.spacing(); - fill_expolygons_generate_paths(ts_layer->support_fills.entities, std::move(polys), - filler_interface.get(), fill_params, erSupportMaterialInterface, m_support_material_interface_flow); - } else if (area_group.type == SupportLayer::RoofType) { - // roof_areas - fill_params.density = interface_density; - filler_interface->spacing = m_support_material_interface_flow.spacing(); - if (m_object_config->support_interface_pattern == smipGrid) { - filler_interface->angle = Geometry::deg2rad(object_config.support_angle.value); - fill_params.dont_sort = true; - } - if (m_object_config->support_interface_pattern == smipRectilinearInterlaced) - filler_interface->layer_id = round(area_group.dist_to_top / ts_layer->height); - fill_expolygons_generate_paths(ts_layer->support_fills.entities, std::move(polys), filler_interface.get(), fill_params, erSupportMaterialInterface, - m_support_material_interface_flow); - } - else { - // base_areas - Flow flow = (layer_id == 0 && m_raft_layers == 0) ? m_object->print()->brim_flow() : support_flow; - bool need_infill = with_infill; - if(m_object_config->support_base_pattern==smpDefault) - need_infill &= area_group.need_infill; - if (layer_id>0 && area_group.dist_to_top < 10 && !need_infill && support_style!=smsTreeHybrid) { - if (area_group.dist_to_top < 5) // 1 wall at the top <5mm - make_perimeter_and_inner_brim(ts_layer->support_fills.entities, poly, 1, flow, erSupportMaterial); - else // at least 2 walls for range [5,10) - make_perimeter_and_inner_brim(ts_layer->support_fills.entities, poly, std::max(wall_count, size_t(2)), flow, erSupportMaterial); - } - else if (layer_id > 0 && need_infill && m_support_params.base_fill_pattern != ipLightning) { - std::shared_ptr filler_support = std::shared_ptr(Fill::new_from_type(m_support_params.base_fill_pattern)); - filler_support->set_bounding_box(bbox_object); - filler_support->spacing = object_config.support_base_pattern_spacing.value * support_density;// constant spacing to align support infill lines - filler_support->angle = Geometry::deg2rad(object_config.support_angle.value); - - // allow infill-only mode if support is thick enough (so min_wall_count is 0); - // otherwise must draw 1 wall - size_t min_wall_count = offset(poly, -scale_(support_spacing * 1.5)).empty() ? 1 : 0; - make_perimeter_and_infill(ts_layer->support_fills.entities, *m_object->print(), poly, std::max(min_wall_count, wall_count), flow, - erSupportMaterial, filler_support.get(), support_density); - } - else { - make_perimeter_and_inner_brim(ts_layer->support_fills.entities, poly, - layer_id > 0 ? wall_count : std::numeric_limits::max(), flow, erSupportMaterial); - } - - } - } - if (m_support_params.base_fill_pattern == ipLightning) - { - double print_z = ts_layer->print_z; - if (printZ_to_lightninglayer.find(print_z) == printZ_to_lightninglayer.end()) - continue; - //TODO: - //1.the second parameter of convertToLines seems to decide how long the lightning should be trimmed from its root, so that the root wont overlap/detach the support contour. - // whether current value works correctly remained to be tested - //2.related to previous one, that lightning roots need to be trimed more when support has multiple walls - //3.function connect_infill() and variable 'params' helps create connection pattern along contours between two lightning roots, - // strengthen lightnings while it may make support harder. decide to enable it or not. if yes, proper values for params are remained to be tested - auto& lightning_layer = generator->getTreesForLayer(printZ_to_lightninglayer[print_z]); - - Flow flow = (layer_id == 0 && m_raft_layers == 0) ? m_object->print()->brim_flow() :support_flow; - ExPolygons areas = offset_ex(ts_layer->base_areas, -flow.scaled_spacing()); - - for (auto& area : areas) - { - Polylines polylines = lightning_layer.convertToLines(to_polygons(area), 0); - for (auto itr = polylines.begin(); itr != polylines.end();) - { - if (itr->length() < scale_(1.0)) - itr = polylines.erase(itr); - else - itr++; - } - Polylines opt_polylines; -#if 1 - //this wont create connection patterns along contours - append(opt_polylines, chain_polylines(std::move(polylines))); -#else - //this will create connection patterns along contours - FillParams params; - params.anchor_length = float(Fill::infill_anchor * 0.01 * flow.spacing()); - params.anchor_length_max = Fill::infill_anchor_max; - params.anchor_length = std::min(params.anchor_length, params.anchor_length_max); - Fill::connect_infill(std::move(polylines), area, opt_polylines, flow.spacing(), params); -#endif - extrusion_entities_append_paths(ts_layer->support_fills.entities, opt_polylines, erSupportMaterial, - float(flow.mm3_per_mm()), float(flow.width()), float(flow.height())); - -#ifdef SUPPORT_TREE_DEBUG_TO_SVG - std::string name = "./SVG/trees_polyline_" + std::to_string(ts_layer->print_z) /*+ "_" + std::to_string(rand_num)*/ + ".svg"; - BoundingBox bbox = get_extents(ts_layer->base_areas); - SVG svg(name, bbox); - if (svg.is_opened()) { - svg.draw(ts_layer->base_areas, "blue"); - svg.draw(generator->Overhangs()[printZ_to_lightninglayer[print_z]], "red"); - for (auto &line : opt_polylines) svg.draw(line, "yellow"); - } -#endif - } - } - - // sort extrusions to reduce travel, also make sure walls go before infills - if(ts_layer->support_fills.no_sort==false) - chain_and_reorder_extrusion_entities(ts_layer->support_fills.entities); - } - } - ); -} - -Polygons TreeSupport::spanning_tree_to_polygon(const std::vector& spanning_trees, Polygons layer_contours, int layer_nr) -{ - Polygons polys; - auto& mst_line_x_layer_contour_cache = m_mst_line_x_layer_contour_caches[layer_nr]; - for (MinimumSpanningTree mst : spanning_trees) { - std::vector points = mst.vertices(); - if (points.size() == 0) - continue; - std::map visited; - for (int i=0;i to_ignore; - for (int i = 0; i < points.size(); i++) { - if (visited[points[i]] == true) - continue; - - Polygon poly; - bool has_next = true; - Point pt1 = points[i]; - poly.points.push_back(pt1); - visited[pt1] = true; - - while (has_next) { - const std::vector& neighbours = mst.adjacent_nodes(pt1); - if (neighbours.empty()) - { - break; - } - - double min_ccw = std::numeric_limits::max(); - Point pt_selected = neighbours[0]; - has_next = false; - for (Point pt2 : neighbours) { - if (to_ignore.find(Line(pt1, pt2)) == to_ignore.end()) { - auto iter = mst_line_x_layer_contour_cache.find({ pt1,pt2 }); - if (iter != mst_line_x_layer_contour_cache.end()) { - if (iter->second) - continue; - } - else { - Polylines pls; - pls.emplace_back(pt1, pt2); - Polylines pls_intersect = intersection_pl(pls, layer_contours); - mst_line_x_layer_contour_cache.insert({ {pt1, pt2}, !pls_intersect.empty() }); - mst_line_x_layer_contour_cache.insert({ {pt2, pt1}, !pls_intersect.empty() }); - if (!pls_intersect.empty()) - continue; - } - - if (poly.points.size() < 2 || visited[pt2]==false) - { - pt_selected = pt2; - has_next = true; - break; - } - double curr_ccw = pt2.ccw(pt1, poly.points.back()); - if (curr_ccw < min_ccw) - { - min_ccw = curr_ccw; - pt_selected = pt2; - has_next = true; - } - } - } - if (has_next) { - poly.points.push_back(pt_selected); - to_ignore.insert(Line(pt1, pt_selected)); - visited[pt_selected] = true; - pt1 = pt_selected; - } - } - polys.emplace_back(std::move(poly)); - } - } - return polys; -} - -Polygons TreeSupport::contact_nodes_to_polygon(const std::vector& contact_nodes, Polygons layer_contours, int layer_nr, std::vector& radiis, std::vector& is_interface) -{ - Polygons polys; - std::vector spanning_trees; - std::vector radiis_mtree; - std::vector is_interface_mtree; - // generate minimum spanning trees - { - std::map visited; - for (int i = 0; i < contact_nodes.size(); i++) - visited.emplace(contact_nodes[i], false); - std::unordered_set to_ignore; - - // generate minimum spaning trees - for (int i = 0; i < contact_nodes.size(); i++) { - Node* node = contact_nodes[i]; - if (visited[node]) - continue; - - std::vector points_to_mstree; - double radius = 0; - Point pt1 = node->position; - points_to_mstree.push_back(pt1); - visited[node] = true; - radius += node->radius; - - for (int j = i + 1; j < contact_nodes.size(); j++) { - Node* node2 = contact_nodes[j]; - Point pt2 = node2->position; - // connect to this neighbor if: - // 1) both are interface or both are not - // 3) not readly added - // 4) won't cross perimeters: this is not right since we need to check all possible connections - if ((node->support_roof_layers_below > 0) == (node2->support_roof_layers_below > 0) - && to_ignore.find(Line(pt1, pt2)) == to_ignore.end()) - { - points_to_mstree.emplace_back(pt2); - visited[node2] = true; - radius += node2->radius; - } - } - - spanning_trees.emplace_back(points_to_mstree); - radiis_mtree.push_back(radius / points_to_mstree.size()); - is_interface_mtree.push_back(node->support_roof_layers_below > 0); - } - } - auto lines = spanning_tree_to_lines(spanning_trees); -#if 1 - // convert mtree to polygon - for (int k = 0; k < spanning_trees.size(); k++) { - auto& mst_line_x_layer_contour_cache = m_mst_line_x_layer_contour_caches[layer_nr]; - MinimumSpanningTree mst = spanning_trees[k]; - std::vector points = mst.vertices(); - std::map visited; - for (int i = 0; i < points.size(); i++) - visited.emplace(points[i], false); - - std::unordered_set to_ignore; - for (int i = 0; i < points.size(); i++) { - if (visited[points[i]]) - continue; - - Polygon poly; - Point pt1 = points[i]; - poly.points.push_back(pt1); - visited[pt1] = true; - - bool has_next = true; - while (has_next) - { - const std::vector& neighbours = mst.adjacent_nodes(pt1); - double min_ccw = -std::numeric_limits::max(); - Point pt_selected; - has_next = false; - for (Point pt2 : neighbours) { - if (to_ignore.find(Line(pt1, pt2)) == to_ignore.end()) { - auto iter = mst_line_x_layer_contour_cache.find({ pt1,pt2 }); - if (iter != mst_line_x_layer_contour_cache.end()) { - if (iter->second) - continue; - } - else { - Polylines pls; - pls.emplace_back(pt1, pt2); - Polylines pls_intersect = intersection_pl(pls, layer_contours); - mst_line_x_layer_contour_cache.insert({ {pt1, pt2}, !pls_intersect.empty() }); - mst_line_x_layer_contour_cache.insert({ {pt2, pt1}, !pls_intersect.empty() }); - if (!pls_intersect.empty()) - continue; - } - if (poly.points.size() < 2) - { - pt_selected = pt2; - has_next = true; - break; - } - double curr_ccw = pt2.ccw(pt1, poly.points.rbegin()[1]); - if (curr_ccw > min_ccw) - { - has_next = true; - min_ccw = curr_ccw; - pt_selected = pt2; - } - } - } - if (!has_next) - break; - - poly.points.push_back(pt_selected); - to_ignore.insert(Line(pt1, pt_selected)); - visited[pt_selected] = true; - pt1 = pt_selected; - } - polys.emplace_back(std::move(poly)); - radiis.push_back(radiis_mtree[k]); - is_interface.push_back(is_interface_mtree[k]); - } - } -#else - polys = spanning_tree_to_polygon(spanning_trees, layer_contours, layer_nr, radiis); -#endif - return polys; -} - - -void TreeSupport::generate() -{ - bool tree_support_enable = m_object_config->enable_support.value && is_tree(m_object_config->support_type.value); - if (!tree_support_enable) - return; - - std::vector> contact_nodes(m_object->layers().size()); - - profiler.stage_start(STAGE_total); - - // Generate overhang areas - profiler.stage_start(STAGE_DETECT_OVERHANGS); - m_object->print()->set_status(55, _L("Support: detect overhangs")); - detect_overhangs(); - profiler.stage_finish(STAGE_DETECT_OVERHANGS); - - if (!has_overhangs) return; - - m_ts_data = m_object->alloc_tree_support_preview_cache(); - m_ts_data->is_slim = is_slim; - - // Generate contact points of tree support - profiler.stage_start(STAGE_GENERATE_CONTACT_NODES); - m_object->print()->set_status(56, _L("Support: generate contact points")); - generate_contact_points(contact_nodes); - profiler.stage_finish(STAGE_GENERATE_CONTACT_NODES); - - //Drop nodes to lower layers. - profiler.stage_start(STAGE_DROP_DOWN_NODES); - m_object->print()->set_status(60, _L("Support: propagate branches")); - drop_nodes(contact_nodes); - profiler.stage_finish(STAGE_DROP_DOWN_NODES); - - smooth_nodes(contact_nodes); - -if (!m_object->config().tree_support_adaptive_layer_height) - // Adjust support layer heights - adjust_layer_heights(contact_nodes); - - - //Generate support areas. - profiler.stage_start(STAGE_DRAW_CIRCLES); - m_object->print()->set_status(65, _L("Support: draw polygons")); - draw_circles(contact_nodes); - profiler.stage_finish(STAGE_DRAW_CIRCLES); - - for (auto& layer : contact_nodes) - { - for (Node* p_node : layer) - { - delete p_node; - } - layer.clear(); - } - contact_nodes.clear(); - - profiler.stage_start(STAGE_GENERATE_TOOLPATHS); - m_object->print()->set_status(69, _L("Support: generate toolpath")); - generate_toolpaths(); - profiler.stage_finish(STAGE_GENERATE_TOOLPATHS); - - profiler.stage_finish(STAGE_total); - BOOST_LOG_TRIVIAL(info) << "tree support time " << profiler.report(); -} - -coordf_t TreeSupport::calc_branch_radius(coordf_t base_radius, size_t layers_to_top, size_t tip_layers, double diameter_angle_scale_factor) -{ - double radius; - if (!is_slim) { - if ((layers_to_top + 1) > tip_layers) { - radius = base_radius + base_radius * (layers_to_top + 1) * diameter_angle_scale_factor; - } else { - radius = base_radius * (layers_to_top + 1) / tip_layers; - } - } else { - if ((layers_to_top + 1) > tip_layers * 2) { - radius = base_radius + base_radius * (layers_to_top + 1) * diameter_angle_scale_factor; - } else { - radius = base_radius * (layers_to_top + 1) / (tip_layers * 2); - } - radius = std::max(radius, MIN_BRANCH_RADIUS); - } - radius = std::min(radius, MAX_BRANCH_RADIUS); - return radius; -} - -coordf_t TreeSupport::calc_branch_radius(coordf_t base_radius, coordf_t mm_to_top, double diameter_angle_scale_factor) -{ - double radius; - coordf_t tip_height = base_radius;// this is a 45 degree tip - if (mm_to_top > tip_height) - { - radius = base_radius + (mm_to_top-tip_height) * diameter_angle_scale_factor; - } - else - { - radius = mm_to_top;// this is a 45 degree tip - } - - radius = std::max(radius, MIN_BRANCH_RADIUS); - radius = std::min(radius, MAX_BRANCH_RADIUS); - // if have interface layers, radius should be larger - if (m_object_config->support_interface_top_layers.value > 0) - radius = std::max(radius, base_radius); - return radius; -} - -template // RegionType could be ExPolygons or Polygons -ExPolygons avoid_object_remove_extra_small_parts(ExPolygons &expolys, const RegionType&avoid_region) { - ExPolygons expolys_out; - for (auto expoly : expolys) { - auto expolys_avoid = diff_ex(expoly, avoid_region); - int idx_max_area = -1; - float max_area = 0; - for (int i = 0; i < expolys_avoid.size(); ++i) { - auto a = expolys_avoid[i].area(); - if (a > max_area) { - max_area = a; - idx_max_area = i; - } - } - if (idx_max_area >= 0) expolys_out.emplace_back(std::move(expolys_avoid[idx_max_area])); - } - return expolys_out; -} - -Polygons TreeSupport::get_trim_support_regions( - const PrintObject& object, - SupportLayer* support_layer_ptr, - const coordf_t gap_extra_above, - const coordf_t gap_extra_below, - const coordf_t gap_xy) -{ - static const double sharp_tail_xy_gap = 0.2f; - static const double no_overlap_xy_gap = 0.2f; - double gap_xy_scaled = scale_(gap_xy); - SupportLayer& support_layer = *support_layer_ptr; - auto m_print_config = object.print()->config(); - - size_t idx_object_layer_overlapping = size_t(-1); - - auto is_layers_overlap = [](const SupportLayer& support_layer, const Layer& object_layer, coordf_t bridging_height = 0.f) -> bool { - if (std::abs(support_layer.print_z - object_layer.print_z) < EPSILON) - return true; - - coordf_t object_lh = bridging_height > EPSILON ? bridging_height : object_layer.height; - if (support_layer.print_z < object_layer.print_z && support_layer.print_z > object_layer.print_z - object_lh) - return true; - - if (support_layer.print_z > object_layer.print_z && support_layer.bottom_z() < object_layer.print_z - EPSILON) - return true; - - return false; - }; - - // Find the overlapping object layers including the extra above / below gap. - coordf_t z_threshold = support_layer.bottom_z() - gap_extra_below + EPSILON; - idx_object_layer_overlapping = Layer::idx_higher_or_equal( - object.layers().begin(), object.layers().end(), idx_object_layer_overlapping, - [z_threshold](const Layer* layer) { return layer->print_z >= z_threshold; }); - // Collect all the object layers intersecting with this layer. - Polygons polygons_trimming; - size_t i = idx_object_layer_overlapping; - for (; i < object.layers().size(); ++i) { - const Layer& object_layer = *object.layers()[i]; - if (object_layer.bottom_z() > support_layer.print_z + gap_extra_above - EPSILON) - break; - - bool is_overlap = is_layers_overlap(support_layer, object_layer); - for (const ExPolygon& expoly : object_layer.lslices) { - // BBS - bool is_sharptail = !intersection_ex({ expoly }, object_layer.sharp_tails).empty(); - coordf_t trimming_offset = is_sharptail ? scale_(sharp_tail_xy_gap) : - is_overlap ? gap_xy_scaled : - scale_(no_overlap_xy_gap); - polygons_append(polygons_trimming, offset({ expoly }, trimming_offset, SUPPORT_SURFACES_OFFSET_PARAMETERS)); - } - } - if (!m_slicing_params.soluble_interface && m_object_config->thick_bridges) { - // Collect all bottom surfaces, which will be extruded with a bridging flow. - for (; i < object.layers().size(); ++i) { - const Layer& object_layer = *object.layers()[i]; - bool some_region_overlaps = false; - for (LayerRegion* region : object_layer.regions()) { - coordf_t bridging_height = region->region().bridging_height_avg(m_print_config); - if (object_layer.print_z - bridging_height > support_layer.print_z + gap_extra_above - EPSILON) - break; - some_region_overlaps = true; - - bool is_overlap = is_layers_overlap(support_layer, object_layer, bridging_height); - coordf_t trimming_offset = is_overlap ? gap_xy_scaled : scale_(no_overlap_xy_gap); - polygons_append(polygons_trimming, - offset(region->fill_surfaces.filter_by_type(stBottomBridge), trimming_offset, SUPPORT_SURFACES_OFFSET_PARAMETERS)); - } - if (!some_region_overlaps) - break; - } - } - - return polygons_trimming; -} - -void TreeSupport::draw_circles(const std::vector>& contact_nodes) -{ - const PrintObjectConfig &config = m_object->config(); - const Print* print = m_object->print(); - bool has_brim = print->has_brim(); - int bottom_gap_layers = round(m_slicing_params.gap_object_support / m_slicing_params.layer_height); - const coordf_t branch_radius = config.tree_support_branch_diameter.value / 2; - const coordf_t branch_radius_scaled = scale_(branch_radius); - bool on_buildplate_only = config.support_on_build_plate_only.value; - Polygon branch_circle; //Pre-generate a circle with correct diameter so that we don't have to recompute those (co)sines every time. - - // Use square support if there are too many nodes per layer because circle support needs much longer time to compute - // Hower circle support can be printed faster, so we prefer circle for fewer nodes case. - const bool SQUARE_SUPPORT = avg_node_per_layer > 200; - const int CIRCLE_RESOLUTION = SQUARE_SUPPORT ? 4 : 100; // The number of vertices in each circle. - - - for (int i = 0; i < CIRCLE_RESOLUTION; i++) - { - double angle; - if (SQUARE_SUPPORT) - angle = (double) i / CIRCLE_RESOLUTION * TAU + PI / 4.0 + nodes_angle; - else - angle = (double) i / CIRCLE_RESOLUTION * TAU; - branch_circle.append(Point(cos(angle) * branch_radius_scaled, sin(angle) * branch_radius_scaled)); - } - - // Performance optimization. Only generate lslices for brim and skirt. - size_t brim_skirt_layers = has_brim ? 1 : 0; - const PrintConfig& print_config = print->config(); - for (const PrintObject* object : print->objects()) - { - size_t skirt_layers = print->has_infinite_skirt() ? object->layer_count() : std::min(size_t(print_config.skirt_height.value), object->layer_count()); - brim_skirt_layers = std::max(brim_skirt_layers, skirt_layers); - } - - // generate areas - const coordf_t layer_height = config.layer_height.value; - const size_t top_interface_layers = config.support_interface_top_layers.value; - const size_t bottom_interface_layers = config.support_interface_bottom_layers.value; - const double diameter_angle_scale_factor = tan(tree_support_branch_diameter_angle * M_PI / 180.);// * layer_height / branch_radius; //Scale factor per layer to produce the desired angle. - const double nozzle_diameter = m_object->print()->config().nozzle_diameter.get_at(0); - const coordf_t line_width = config.get_abs_value("support_line_width", nozzle_diameter); - const coordf_t line_width_scaled = scale_(line_width); - - const bool with_lightning_infill = m_support_params.base_fill_pattern == ipLightning; - coordf_t support_extrusion_width = m_support_params.support_extrusion_width; - const size_t wall_count = config.tree_support_wall_count.value; - - const PrintObjectConfig& object_config = m_object->config(); - BOOST_LOG_TRIVIAL(info) << "draw_circles for object: " << m_object->model_object()->name; - - // coconut: previously std::unordered_map in m_collision_cache is not multi-thread safe which may cause programs stuck, here we change to tbb::concurrent_unordered_map - tbb::parallel_for( - tbb::blocked_range(0, m_object->layer_count()), - [&](const tbb::blocked_range& range) - { - for (size_t layer_nr = range.begin(); layer_nr < range.end(); layer_nr++) - { - if (print->canceled()) - break; - - const std::vector& curr_layer_nodes = contact_nodes[layer_nr]; - SupportLayer* ts_layer = m_object->get_support_layer(layer_nr + m_raft_layers); - assert(ts_layer != nullptr); - - // skip if current layer has no points. This fixes potential crash in get_collision (see jira BBL001-355) - if (curr_layer_nodes.empty()) { - ts_layer->print_z = 0.0; - ts_layer->height = 0.0; - continue; - } - - Node* first_node = curr_layer_nodes.front(); - ts_layer->print_z = first_node->print_z; - ts_layer->height = first_node->height; - if (ts_layer->height < EPSILON) { - continue; - } - - ExPolygons& base_areas = ts_layer->base_areas; - ExPolygons& roof_areas = ts_layer->roof_areas; - ExPolygons& roof_1st_layer = ts_layer->roof_1st_layer; - ExPolygons& floor_areas = ts_layer->floor_areas; - ExPolygons& roof_gap_areas = ts_layer->roof_gap_areas; - coordf_t max_layers_above_base = 0; - coordf_t max_layers_above_roof = 0; - coordf_t max_layers_above_roof1 = 0; - bool has_polygon_node = false; - bool has_circle_node = false; - - BOOST_LOG_TRIVIAL(debug) << "circles at layer " << layer_nr << " contact nodes size=" << contact_nodes[layer_nr].size(); - //Draw the support areas and add the roofs appropriately to the support roof instead of normal areas. - ts_layer->lslices.reserve(contact_nodes[layer_nr].size()); - for (const Node* p_node : contact_nodes[layer_nr]) - { - if (print->canceled()) - break; - - const Node& node = *p_node; - ExPolygons area; - // Generate directly from overhang polygon if one of the following is true: - // 1) node is a normal part of hybrid support - // 2) node is virtual - if (node.type == ePolygon || node.distance_to_top<0) { - if (node.overhang->contour.size() > 100 || node.overhang->holes.size()>1) - area.emplace_back(*node.overhang); - else { - area = offset_ex({ *node.overhang }, scale_(m_ts_data->m_xy_distance)); - } - if (node.type == ePolygon) - has_polygon_node = true; - } - else { - Polygon circle; - size_t layers_to_top = node.distance_to_top; - double scale = calc_branch_radius(branch_radius, node.dist_mm_to_top, diameter_angle_scale_factor) / branch_radius; - - if (/*is_slim*/1) { // draw ellipse along movement direction - double moveX = node.movement.x() / (scale * branch_radius_scaled); - double moveY = node.movement.y() / (scale * branch_radius_scaled); - const double vsize_inv = 0.5 / (0.01 + std::sqrt(moveX * moveX + moveY * moveY)); - double matrix[2*2] = { - scale * (1 + moveX * moveX * vsize_inv),scale * (0 + moveX * moveY * vsize_inv), - scale * (0 + moveX * moveY * vsize_inv),scale * (1 + moveY * moveY * vsize_inv), - }; - for (auto vertex: branch_circle.points) { - vertex = Point(matrix[0] * vertex.x() + matrix[1] * vertex.y(), matrix[2] * vertex.x() + matrix[3] * vertex.y()); - circle.append(node.position + vertex); - } - } else { - for (auto iter = branch_circle.points.begin(); iter != branch_circle.points.end(); iter++) { - Point corner = (*iter) * scale; - circle.append(node.position + corner); - } - } - if (layer_nr == 0 && m_raft_layers == 0) { - double brim_width = - config.tree_support_auto_brim - ? layers_to_top * layer_height / - (scale * branch_radius) * 0.5 - : config.tree_support_brim_width; - circle = offset(circle, scale_(brim_width))[0]; - } - area.emplace_back(ExPolygon(circle)); - // merge overhang to get a smoother interface surface - // Do not merge when buildplate_only is on, because some underneath nodes may have been deleted. - if (top_interface_layers > 0 && node.support_roof_layers_below > 0 && !on_buildplate_only) { - ExPolygons overhang_expanded; - if (node.overhang->contour.size() > 100 || node.overhang->holes.size()>1) - overhang_expanded.emplace_back(*node.overhang); - else { - // 对于有缺陷的模型,overhang膨胀以后可能是空的! - overhang_expanded = offset_ex({ *node.overhang }, scale_(m_ts_data->m_xy_distance)); - } - append(area, overhang_expanded); - } - has_circle_node = true; - } - - if (node.distance_to_top < 0) - append(roof_gap_areas, area); - else if (node.support_roof_layers_below == 1) - { - append(roof_1st_layer, area); - max_layers_above_roof1 = std::max(max_layers_above_roof1, node.dist_mm_to_top); - } - else if (node.support_roof_layers_below > 0) - { - append(roof_areas, area); - max_layers_above_roof = std::max(max_layers_above_roof, node.dist_mm_to_top); - } - else - { - append(base_areas, area); - max_layers_above_base = std::max(max_layers_above_base, node.dist_mm_to_top); - } - - if (layer_nr < brim_skirt_layers) - append(ts_layer->lslices, area); - } - - ts_layer->lslices = std::move(union_ex(ts_layer->lslices)); - - //Must update bounding box which is used in avoid crossing perimeter - ts_layer->lslices_bboxes.clear(); - ts_layer->lslices_bboxes.reserve(ts_layer->lslices.size()); - for (const ExPolygon &expoly : ts_layer->lslices) - ts_layer->lslices_bboxes.emplace_back(get_extents(expoly)); - ts_layer->backup_untyped_slices(); - - m_object->print()->set_status(65, (boost::format( _L("Support: generate polygons at layer %d")) % layer_nr).str()); - - // join roof segments - double contact_dist_scaled = scale_(0.5);// scale_(m_slicing_params.gap_support_object); - roof_areas = std::move(offset2_ex(roof_areas, contact_dist_scaled, -contact_dist_scaled)); - roof_1st_layer = std::move(offset2_ex(roof_1st_layer, contact_dist_scaled, -contact_dist_scaled)); - - // avoid object - //ExPolygons avoid_region_interface = m_ts_data->get_collision(m_ts_data->m_xy_distance, layer_nr); - Polygons avoid_region_interface = get_trim_support_regions(*m_object, ts_layer, m_slicing_params.gap_object_support, m_slicing_params.gap_support_object, m_ts_data->m_xy_distance); - if (has_circle_node) { - roof_areas = avoid_object_remove_extra_small_parts(roof_areas, avoid_region_interface); - roof_1st_layer = avoid_object_remove_extra_small_parts(roof_1st_layer, avoid_region_interface); - } - else { - roof_areas = std::move(diff_ex(roof_areas, avoid_region_interface)); - roof_1st_layer = std::move(diff_ex(roof_1st_layer, avoid_region_interface)); - } - roof_areas = intersection_ex(roof_areas, m_machine_border); - - // roof_1st_layer and roof_areas may intersect, so need to subtract roof_areas from roof_1st_layer - roof_1st_layer = std::move(diff_ex(roof_1st_layer, roof_areas)); - roof_1st_layer = intersection_ex(roof_1st_layer, m_machine_border); - - // let supports touch objects when brim is on - auto avoid_region = m_ts_data->get_collision((layer_nr == 0 && has_brim) ? config.brim_object_gap : m_ts_data->m_xy_distance, layer_nr); - base_areas = avoid_object_remove_extra_small_parts(base_areas, avoid_region); - base_areas = std::move(diff_ex(base_areas, roof_areas)); - base_areas = std::move(diff_ex(base_areas, roof_1st_layer)); - base_areas = std::move(diff_ex(base_areas, roof_gap_areas)); - base_areas = intersection_ex(base_areas, m_machine_border); - - if (SQUARE_SUPPORT) { - // simplify support contours - ExPolygons base_areas_simplified; - for (auto &area : base_areas) { area.simplify(scale_(line_width / 2), &base_areas_simplified); } - base_areas = std::move(base_areas_simplified); - } - //Subtract support floors. We can only compute floor_areas here instead of with roof_areas, - // or we'll get much wider floor than necessary. - if (bottom_interface_layers + bottom_gap_layers > 0) - { - if (layer_nr >= bottom_interface_layers + bottom_gap_layers) - { - for (size_t i = 0; i <= bottom_gap_layers; i++) - { - const Layer* below_layer = m_object->get_layer(layer_nr - bottom_interface_layers - i); - ExPolygons bottom_interface = intersection_ex(base_areas, below_layer->lslices); - floor_areas.insert(floor_areas.end(), bottom_interface.begin(), bottom_interface.end()); - } - } - if (floor_areas.empty() == false) { - floor_areas = std::move(diff_ex(floor_areas, avoid_region_interface)); - floor_areas = std::move(offset2_ex(floor_areas, contact_dist_scaled, -contact_dist_scaled)); - base_areas = std::move(diff_ex(base_areas, offset_ex(floor_areas, 10))); - } - } - if (bottom_gap_layers > 0 && layer_nr > bottom_gap_layers) { - const Layer* below_layer = m_object->get_layer(layer_nr - bottom_gap_layers); - ExPolygons bottom_gap_area = intersection_ex(floor_areas, below_layer->lslices); - if (!bottom_gap_area.empty()) { - floor_areas = std::move(diff_ex(floor_areas, bottom_gap_area)); - } - } - auto &area_groups = ts_layer->area_groups; - for (auto& area : ts_layer->base_areas) { - area_groups.emplace_back(&area, SupportLayer::BaseType, max_layers_above_base); - area_groups.back().need_infill = has_polygon_node; - } - for (auto &area : ts_layer->roof_areas) area_groups.emplace_back(&area, SupportLayer::RoofType, max_layers_above_roof); - for (auto &area : ts_layer->floor_areas) area_groups.emplace_back(&area, SupportLayer::FloorType, 10000); - for (auto &area : ts_layer->roof_1st_layer) area_groups.emplace_back(&area, SupportLayer::Roof1stLayer, max_layers_above_roof1); - - for (auto &area_group : area_groups) { - auto& expoly = area_group.area; - expoly->holes.erase(std::remove_if(expoly->holes.begin(), expoly->holes.end(), - [](auto &hole) { - auto bbox_size = get_extents(hole).size(); - return bbox_size[0] < scale_(2) && bbox_size[1] < scale_(2); - }), - expoly->holes.end()); - } - - } - }); - - - if (with_lightning_infill) - { - const bool global_lightning_infill = true; - - std::vector contours; - std::vector overhangs; - for (int layer_nr = 1; layer_nr < m_object->layer_count(); layer_nr++) { - if (print->canceled()) break; - const std::vector& curr_layer_nodes = contact_nodes[layer_nr]; - SupportLayer* ts_layer = m_object->get_support_layer(layer_nr + m_raft_layers); - assert(ts_layer != nullptr); - - // skip if current layer has no points. This fixes potential crash in get_collision (see jira BBL001-355) - if (curr_layer_nodes.empty()) continue; - if (ts_layer->height < EPSILON) continue; - if (ts_layer->area_groups.empty()) continue; - - ExPolygons& base_areas = ts_layer->base_areas; - - int layer_nr_lower = layer_nr - 1; - for (layer_nr_lower; layer_nr_lower >= 0; layer_nr_lower--) { - if (!m_object->get_support_layer(layer_nr_lower + m_raft_layers)->area_groups.empty()) break; - } - if (layer_nr_lower <= 0) continue; - - SupportLayer* lower_layer = m_object->get_support_layer(layer_nr_lower + m_raft_layers); - ExPolygons& base_areas_lower = lower_layer->base_areas; - - ExPolygons overhang; - if (global_lightning_infill) - { - //search overhangs globally - overhang = std::move(diff_ex(offset_ex(base_areas_lower, -2.0 * scale_(support_extrusion_width)), base_areas)); - } - else - { - //search overhangs only on floating islands - for (auto& base_area : base_areas) - for (auto& hole : base_area.holes) - { - Polygon rev_hole = hole; - rev_hole.make_counter_clockwise(); - ExPolygons ex_hole; - ex_hole.emplace_back(std::move(ExPolygon(rev_hole))); - for (auto& other_area : base_areas) - //if (&other_area != &base_area) - ex_hole = std::move(diff_ex(ex_hole, other_area)); - overhang = std::move(union_ex(overhang, ex_hole)); - } - overhang = std::move(intersection_ex(overhang, offset_ex(base_areas_lower, -0.5 * scale_(support_extrusion_width)))); - } - - overhangs.emplace_back(to_polygons(overhang)); - contours.emplace_back(to_polygons(base_areas_lower)); - printZ_to_lightninglayer[lower_layer->print_z] = overhangs.size() - 1; - -#ifdef SUPPORT_TREE_DEBUG_TO_SVG - draw_two_overhangs_to_svg(m_object->get_support_layer(layer_nr_lower + m_raft_layers), base_areas_lower, to_expolygons(overhangs.back())); -#endif - } - - - auto m_support_material_flow = support_material_flow(m_object, m_slicing_params.layer_height); - coordf_t support_spacing = object_config.support_base_pattern_spacing.value + m_support_material_flow.spacing(); - coordf_t support_density = std::min(1., m_support_material_flow.spacing() / support_spacing * 2); // for lightning infill the density is defined differently, so need to double it - generator = std::make_unique(m_object, contours, overhangs, []() {}, support_density); - } - - else if (!with_infill) { - // move the holes to contour so they can be well supported - - // check if poly's contour intersects with expoly's contour - auto intersects_contour = [](Polygon poly, ExPolygon expoly, Point& pt_on_poly, Point& pt_on_expoly, Point& pt_far_on_poly, float dist_thresh = 0.01) { - float min_dist = std::numeric_limits::max(); - float max_dist = 0; - for (auto from : poly.points) { - for (int i = 0; i < expoly.num_contours(); i++) { - const Point* candidate = expoly.contour_or_hole(i).closest_point(from); - double dist2 = vsize2_with_unscale(*candidate - from); - if (dist2 < min_dist) { - min_dist = dist2; - pt_on_poly = from; - pt_on_expoly = *candidate; - } - if (dist2 > max_dist) { - max_dist = dist2; - pt_far_on_poly = from; - } - if (dist2 < dist_thresh) { return true; } - } - } - return false; - }; - - // polygon pointer: depth, direction, farPoint - std::map> holePropagationInfos; - for (int layer_nr = m_object->layer_count() - 1; layer_nr > 0; layer_nr--) { - if (print->canceled()) break; - m_object->print()->set_status(66, (boost::format(_L("Support: fix holes at layer %d")) % layer_nr).str()); - - const std::vector& curr_layer_nodes = contact_nodes[layer_nr]; - SupportLayer* ts_layer = m_object->get_support_layer(layer_nr + m_raft_layers); - assert(ts_layer != nullptr); - - // skip if current layer has no points. This fixes potential crash in get_collision (see jira BBL001-355) - if (curr_layer_nodes.empty()) continue; - if (ts_layer->height < EPSILON) continue; - if (ts_layer->area_groups.empty()) continue; - - int layer_nr_lower = layer_nr - 1; - for (layer_nr_lower; layer_nr_lower >= 0; layer_nr_lower--) { - if (!m_object->get_support_layer(layer_nr_lower + m_raft_layers)->area_groups.empty()) break; - } - if (layer_nr_lower < 0) continue; - auto& area_groups_lower = m_object->get_support_layer(layer_nr_lower + m_raft_layers)->area_groups; - - for (const auto& area_group : ts_layer->area_groups) { - if (area_group.type != SupportLayer::BaseType) continue; - const auto& area = area_group.area; - for (const auto& hole : area->holes) { - // auto hole_bbox = get_extents(hole).polygon(); - for (auto& area_group_lower : area_groups_lower) { - if (area_group.type != SupportLayer::BaseType) continue; - auto& base_area_lower = *area_group_lower.area; - Point pt_on_poly, pt_on_expoly, pt_far_on_poly; - // if a hole doesn't intersect with lower layer's contours, add a hole to lower layer and move it slightly to the contour - if (base_area_lower.contour.contains(hole.points.front()) && !intersects_contour(hole, base_area_lower, pt_on_poly, pt_on_expoly, pt_far_on_poly)) { - Polygon hole_lower = hole; - Point direction = normal(pt_on_expoly - pt_on_poly, line_width_scaled / 2); - hole_lower.translate(direction); - // note to expand a hole, we need to do negative offset - auto hole_expanded = offset(hole_lower, -line_width_scaled / 4, ClipperLib::JoinType::jtSquare); - if (!hole_expanded.empty()) { - base_area_lower.holes.push_back(std::move(hole_expanded[0])); - holePropagationInfos.insert({ &base_area_lower.holes.back(), {25, direction, pt_far_on_poly} }); - } - break; - } - else if (holePropagationInfos.find(&hole) != holePropagationInfos.end() && std::get<0>(holePropagationInfos[&hole]) > 0 && - base_area_lower.contour.contains(std::get<2>(holePropagationInfos[&hole]))) { - Polygon hole_lower = hole; - auto&& direction = std::get<1>(holePropagationInfos[&hole]); - hole_lower.translate(direction); - // note to shrink a hole, we need to do positive offset - auto hole_expanded = offset(hole_lower, line_width_scaled / 2, ClipperLib::JoinType::jtSquare); - Point farPoint = std::get<2>(holePropagationInfos[&hole]) + direction * 2; - if (!hole_expanded.empty()) { - base_area_lower.holes.push_back(std::move(hole_expanded[0])); - holePropagationInfos.insert({ &base_area_lower.holes.back(), {std::get<0>(holePropagationInfos[&hole]) - 1, direction, farPoint} }); - } - break; - } - } - { - // if roof1 interface is inside a hole, need to expand the interface - for (auto& roof1 : ts_layer->roof_1st_layer) { - //if (hole.contains(roof1.contour.points.front()) && hole.contains(roof1.contour.bounding_box().center())) - bool is_inside_hole = std::all_of(roof1.contour.points.begin(), roof1.contour.points.end(), [&hole](Point& pt) { return hole.contains(pt); }); - if (is_inside_hole) { - Polygon hole_reoriented = hole; - if (roof1.contour.is_counter_clockwise()) - hole_reoriented.make_counter_clockwise(); - else if (roof1.contour.is_clockwise()) - hole_reoriented.make_clockwise(); - auto tmp = union_({ roof1.contour }, { hole_reoriented }); - if (!tmp.empty()) roof1.contour = tmp[0]; - - // make sure 1) roof1 and object 2) roof1 and roof, won't intersect - // Note: We can't replace roof1 directly, as we have recorded its address. - // So instead we need to replace its members one by one. - auto tmp1 = diff_ex(roof1, m_ts_data->get_collision((layer_nr == 0 && has_brim) ? config.brim_object_gap : m_ts_data->m_xy_distance, layer_nr)); - tmp1 = diff_ex(tmp1, ts_layer->roof_areas); - if (!tmp1.empty()) { - roof1.contour = std::move(tmp1[0].contour); - roof1.holes = std::move(tmp1[0].holes); - } - break; - } - } - } - } - } - } - } - - -#ifdef SUPPORT_TREE_DEBUG_TO_SVG - for (int layer_nr = m_object->layer_count() - 1; layer_nr >= 0; layer_nr--) { - SupportLayer* ts_layer = m_object->get_support_layer(layer_nr + m_raft_layers); - ExPolygons& base_areas = ts_layer->base_areas; - ExPolygons& roof_areas = ts_layer->roof_areas; - ExPolygons& roof_1st_layer = ts_layer->roof_1st_layer; - ExPolygons& floor_areas = ts_layer->floor_areas; - if (base_areas.empty() && roof_areas.empty() && roof_1st_layer.empty()) continue; - char fname[10]; sprintf(fname, "%d_%.2f", layer_nr, ts_layer->print_z); - draw_contours_and_nodes_to_svg("", base_areas, roof_areas, roof_1st_layer, {}, {}, get_svg_filename(fname, "circles"), {"base", "roof", "roof1st"}); - } -#endif // SUPPORT_TREE_DEBUG_TO_SVG - - SupportLayerPtrs& ts_layers = m_object->support_layers(); - auto iter = std::remove_if(ts_layers.begin(), ts_layers.end(), [](SupportLayer* ts_layer) { return ts_layer->height < EPSILON; }); - ts_layers.erase(iter, ts_layers.end()); - for (int layer_nr = 0; layer_nr < ts_layers.size(); layer_nr++) { - ts_layers[layer_nr]->upper_layer = layer_nr != ts_layers.size() - 1 ? ts_layers[layer_nr + 1] : nullptr; - ts_layers[layer_nr]->lower_layer = layer_nr > 0 ? ts_layers[layer_nr - 1] : nullptr; - } -} - -void TreeSupport::drop_nodes(std::vector>& contact_nodes) -{ - const PrintObjectConfig &config = m_object->config(); - // Use Minimum Spanning Tree to connect the points on each layer and move them while dropping them down. - const coordf_t support_extrusion_width = m_support_params.support_extrusion_width; - const coordf_t layer_height = config.layer_height.value; - const double angle = config.tree_support_branch_angle.value * M_PI / 180.; - const int wall_count = std::max(1, config.tree_support_wall_count.value); - double tan_angle = tan(angle); // when nodes are thick, they can move further. this is the max angle - const coordf_t max_move_distance = (angle < M_PI / 2) ? (coordf_t)(tan_angle * layer_height)*wall_count : std::numeric_limits::max(); - const double max_move_distance2 = max_move_distance * max_move_distance; - const coordf_t branch_radius = config.tree_support_branch_diameter.value / 2; - const size_t tip_layers = branch_radius / layer_height; //The number of layers to be shrinking the circle to create a tip. This produces a 45 degree angle. - const double diameter_angle_scale_factor = tan(tree_support_branch_diameter_angle * M_PI / 180.);//*layer_height / branch_radius; // Scale factor per layer to produce the desired angle. - const coordf_t radius_sample_resolution = m_ts_data->m_radius_sample_resolution; - const bool support_on_buildplate_only = config.support_on_build_plate_only.value; - const size_t bottom_interface_layers = config.support_interface_bottom_layers.value; - const size_t top_interface_layers = config.support_interface_top_layers.value; - float DO_NOT_MOVER_UNDER_MM = is_slim ? 0 : 5; // do not move contact points under 5mm - const auto nozzle_diameter = m_object->print()->config().nozzle_diameter.get_at(m_object->config().support_interface_filament-1); - const auto support_line_width = config.support_line_width.get_abs_value(nozzle_diameter); - - auto get_branch_angle = [this,&config](coordf_t radius) { - if (config.tree_support_branch_angle.value < 30.0) return config.tree_support_branch_angle.value; - return (radius - MIN_BRANCH_RADIUS) / (MAX_BRANCH_RADIUS - MIN_BRANCH_RADIUS) * (config.tree_support_branch_angle.value - 30.0) + 30.0; - }; - auto get_max_move_dist = [this, &config, branch_radius, tip_layers, diameter_angle_scale_factor, wall_count, support_extrusion_width, support_line_width](const Node *node, int power = 1) { - double move_dist = node->max_move_dist; - if (node->max_move_dist == 0) { - if (node->radius == 0) node->radius = calc_branch_radius(branch_radius, node->dist_mm_to_top, diameter_angle_scale_factor); - double angle = config.tree_support_branch_angle.value; - if (angle > 30.0 && node->radius > MIN_BRANCH_RADIUS) - angle = (node->radius - MIN_BRANCH_RADIUS) / (MAX_BRANCH_RADIUS - MIN_BRANCH_RADIUS) * (config.tree_support_branch_angle.value - 30.0) + 30.0; - double tan_angle = tan(angle * M_PI / 180); - int wall_count_ = node->radius > 2 * support_line_width ? wall_count : 1; - node->max_move_dist = (angle < 90) ? (coordf_t) (tan_angle * node->height) * wall_count_ : std::numeric_limits::max(); - node->max_move_dist = std::min(node->max_move_dist, support_extrusion_width); - move_dist = node->max_move_dist; - } - if (power == 2) move_dist = SQ(move_dist); - return move_dist; - }; - - m_ts_data->layer_heights = plan_layer_heights(contact_nodes); - std::vector &layer_heights = m_ts_data->layer_heights; - if (layer_heights.empty()) return; - - std::unordered_set to_free_node_set; - m_spanning_trees.resize(contact_nodes.size()); - //m_mst_line_x_layer_contour_caches.resize(contact_nodes.size()); - - if (0) - {// get outlines below and avoidance area using tbb - // This part only takes very little time, so we disable it. - typedef std::chrono::high_resolution_clock clock_; - typedef std::chrono::duration > second_; - std::chrono::time_point t0{ clock_::now() }; - - // get all the possible radiis - std::vector > all_layer_radius(m_highest_overhang_layer+1); - std::vector> all_layer_node_dist(m_highest_overhang_layer + 1); - for (size_t layer_nr = m_highest_overhang_layer; layer_nr > 0; layer_nr--) - { - if (layer_heights[layer_nr].height < EPSILON) continue; - auto& layer_radius = all_layer_radius[layer_nr]; - auto& layer_node_dist = all_layer_node_dist[layer_nr]; - for (Node *p_node : contact_nodes[layer_nr]) { - layer_node_dist.emplace(p_node->dist_mm_to_top); - } - size_t layer_nr_next = layer_heights[layer_nr].next_layer_nr; - if (layer_nr_next <= m_highest_overhang_layer && layer_nr_next>0) { - for (auto node_dist : layer_node_dist) - all_layer_node_dist[layer_nr_next].emplace(node_dist + layer_heights[layer_nr].height); - } - for (auto node_dist : layer_node_dist) { - layer_radius.emplace(calc_branch_radius(branch_radius, node_dist, diameter_angle_scale_factor)); - } - } - // parallel pre-compute avoidance - //tbb::parallel_for(tbb::blocked_range(1, m_highest_overhang_layer), [&](const tbb::blocked_range &range) { - //for (size_t layer_nr = range.begin(); layer_nr < range.end(); layer_nr++) { - for (size_t layer_nr = 0; layer_nr < all_layer_radius.size(); layer_nr++) { - BOOST_LOG_TRIVIAL(debug) << "pre calculate_avoidance layer=" << layer_nr; - for (auto node_radius : all_layer_radius[layer_nr]) { - m_ts_data->get_avoidance(0, layer_nr); - m_ts_data->get_avoidance(node_radius, layer_nr); - } - } - //}); - - double duration{ std::chrono::duration_cast(clock_::now() - t0).count() }; - BOOST_LOG_TRIVIAL(debug) << "before m_avoidance_cache.size()=" << m_ts_data->m_avoidance_cache.size() - << ", takes " << duration << " secs."; - } - - for (size_t layer_nr = contact_nodes.size() - 1; layer_nr > 0; layer_nr--) // Skip layer 0, since we can't drop down the vertices there. - { - if (m_object->print()->canceled()) - break; - - auto& layer_contact_nodes = contact_nodes[layer_nr]; - if (layer_contact_nodes.empty()) - continue; - - int layer_nr_next = layer_heights[layer_nr].next_layer_nr; - coordf_t print_z_next = layer_heights[layer_nr_next].print_z; - coordf_t height_next = layer_heights[layer_nr_next].height; - - std::deque> unsupported_branch_leaves; // All nodes that are leaves on this layer that would result in unsupported ('mid-air') branches. - const Layer* ts_layer = m_object->get_support_layer(layer_nr); - - m_object->print()->set_status(60, (boost::format(_L("Support: propagate branches at layer %d")) % layer_nr).str()); - - Polygons layer_contours = m_ts_data->get_contours_with_holes(layer_nr); - //std::unordered_map& mst_line_x_layer_contour_cache = m_mst_line_x_layer_contour_caches[layer_nr]; - std::unordered_map mst_line_x_layer_contour_cache; - auto is_line_cut_by_contour = [&mst_line_x_layer_contour_cache,&layer_contours](Point a, Point b) - { - auto iter = mst_line_x_layer_contour_cache.find({ a, b }); - if (iter != mst_line_x_layer_contour_cache.end()) { - if (iter->second) - return true; - } - else { - profiler.tic(); - Line ln(b, a); - Lines pls_intersect = intersection_ln(ln, layer_contours); - mst_line_x_layer_contour_cache.insert({ {a, b}, !pls_intersect.empty() }); - mst_line_x_layer_contour_cache.insert({ ln, !pls_intersect.empty() }); - profiler.stage_add(STAGE_intersection_ln, true); - if (!pls_intersect.empty()) - return true; - } - return false; - }; - - //Group together all nodes for each part. - const ExPolygons& parts = m_ts_data->get_avoidance(0, layer_nr); - std::vector> nodes_per_part(1 + parts.size()); //All nodes that aren't inside a part get grouped together in the 0th part. - for (Node* p_node : layer_contact_nodes) - { - const Node& node = *p_node; - - if (node.distance_to_top < 0) { - // gap nodes do not merge or move - Node* next_node = new Node(p_node->position, p_node->distance_to_top + 1, layer_nr_next, p_node->support_roof_layers_below - 1, p_node->to_buildplate, p_node, - print_z_next, height_next); - get_max_move_dist(next_node); - next_node->is_merged = false; - contact_nodes[layer_nr_next].emplace_back(next_node); - continue; - } - if (support_on_buildplate_only && !node.to_buildplate) //Can't rest on model and unable to reach the build plate. Then we must drop the node and leave parts unsupported. - { - unsupported_branch_leaves.push_front({ layer_nr, p_node }); - continue; - } - if (node.to_buildplate || parts.empty()) //It's outside, so make it go towards the build plate. - { - nodes_per_part[0][node.position] = p_node; - continue; - } - - /* Find which part this node is located in and group the nodes in - * the same part together. Since nodes have a radius and the - * avoidance areas are offset by that radius, the set of parts may - * be different per node. Here we consider a node to be inside the - * part that is closest. The node may be inside a bigger part that - * is actually two parts merged together due to an offset. In that - * case we may incorrectly keep two nodes separate, but at least - * every node falls into some group. - */ - coordf_t closest_part_distance2 = std::numeric_limits::max(); - size_t closest_part = -1; - for (size_t part_index = 0; part_index < parts.size(); part_index++) - { - //constexpr bool border_result = true; - if (is_inside_ex(parts[part_index], node.position)) //If it's inside, the distance is 0 and this part is considered the best. - { - closest_part = part_index; - closest_part_distance2 = 0; - break; - } - - Point closest_point = *parts[part_index].contour.closest_point(node.position); - const coordf_t distance2 = vsize2_with_unscale(node.position - closest_point); - if (distance2 < closest_part_distance2) - { - closest_part_distance2 = distance2; - closest_part = part_index; - } - } - //Put it in the best one. - nodes_per_part[closest_part + 1][node.position] = p_node; //Index + 1 because the 0th index is the outside part. - } - - //Create a MST for every part. - profiler.tic(); - //std::vector& spanning_trees = m_spanning_trees[layer_nr]; - std::vector spanning_trees; - for (const std::unordered_map& group : nodes_per_part) - { - std::vector points_to_buildplate; - for (const std::pair& entry : group) - { - points_to_buildplate.emplace_back(entry.first); //Just the position of the node. - } - spanning_trees.emplace_back(points_to_buildplate); - } - profiler.stage_add(STAGE_MinimumSpanningTree,true); - -#ifdef SUPPORT_TREE_DEBUG_TO_SVG - coordf_t branch_radius_temp = 0; - coordf_t max_y = std::numeric_limits::min(); - draw_layer_mst(std::to_string(ts_layer->print_z), spanning_trees, m_object->get_layer(layer_nr)->lslices); -#endif - for (size_t group_index = 0; group_index < nodes_per_part.size(); group_index++) - { - const MinimumSpanningTree& mst = spanning_trees[group_index]; - //In the first pass, merge all nodes that are close together. - std::unordered_set to_delete; - for (const std::pair& entry : nodes_per_part[group_index]) - { - Node* p_node = entry.second; - Node& node = *p_node; - if (to_delete.find(p_node) != to_delete.end()) - { - continue; //Delete this node (don't create a new node for it on the next layer). - } - const std::vector& neighbours = mst.adjacent_nodes(node.position); - if (node.type == ePolygon) { - // Remove all neighbours that are completely inside the polygon and merge them into this node. - for (const Point &neighbour : neighbours) { - Node * neighbour_node = nodes_per_part[group_index][neighbour]; - coord_t neighbour_radius = scale_(calc_branch_radius(branch_radius, neighbour_node->dist_mm_to_top, diameter_angle_scale_factor)); - Point pt_north = neighbour + Point(0, neighbour_radius), pt_south = neighbour - Point(0, neighbour_radius), - pt_west = neighbour - Point(neighbour_radius, 0), pt_east = neighbour + Point(neighbour_radius, 0); - if (is_inside_ex(*node.overhang, neighbour) && is_inside_ex(*node.overhang, pt_north) && is_inside_ex(*node.overhang, pt_south) - && is_inside_ex(*node.overhang, pt_west) && is_inside_ex(*node.overhang, pt_east)){ - node.distance_to_top = std::max(node.distance_to_top, neighbour_node->distance_to_top); - node.support_roof_layers_below = std::max(node.support_roof_layers_below, neighbour_node->support_roof_layers_below); - node.dist_mm_to_top = std::max(node.dist_mm_to_top, neighbour_node->dist_mm_to_top); - node.merged_neighbours.push_front(neighbour_node); - node.merged_neighbours.insert(node.merged_neighbours.end(), neighbour_node->merged_neighbours.begin(), neighbour_node->merged_neighbours.end()); - node.is_merged = true; - to_delete.insert(neighbour_node); - } - } - } - else if (neighbours.size() == 1 && vsize2_with_unscale(neighbours[0] - node.position) < max_move_distance2 && mst.adjacent_nodes(neighbours[0]).size() == 1 && - nodes_per_part[group_index][neighbours[0]]->type!=ePolygon) // We have just two nodes left, and they're very close, and the only neighbor is not ePolygon - { - //Insert a completely new node and let both original nodes fade. - Point next_position = (node.position + neighbours[0]) / 2; //Average position of the two nodes. - - const coordf_t branch_radius_node = calc_branch_radius(branch_radius, node.dist_mm_to_top, diameter_angle_scale_factor); - - auto avoid_layer = m_ts_data->get_avoidance(branch_radius_node, layer_nr_next); - if (group_index == 0) - { - //Avoid collisions. - const coordf_t max_move_between_samples = max_move_distance + radius_sample_resolution + EPSILON; //100 micron extra for rounding errors. - move_out_expolys(avoid_layer, next_position, radius_sample_resolution + EPSILON, max_move_between_samples); - } - - Node* neighbour = nodes_per_part[group_index][neighbours[0]]; - Node* node_; - if (p_node->parent && neighbour->parent) - node_ = (node.dist_mm_to_top >= neighbour->dist_mm_to_top && p_node->parent) ? p_node : neighbour; - else - node_ = p_node->parent ? p_node : neighbour; - // Make sure the next pass doesn't drop down either of these (since that already happened). - node_->merged_neighbours.push_front(node_ == p_node ? neighbour : p_node); - const bool to_buildplate = !is_inside_ex(m_ts_data->get_avoidance(0, layer_nr_next), next_position); - Node * next_node = new Node(next_position, node_->distance_to_top + 1, layer_nr_next, node_->support_roof_layers_below-1, to_buildplate, node_, - print_z_next, height_next); - next_node->movement = next_position - node.position; - get_max_move_dist(next_node); - next_node->is_merged = true; - contact_nodes[layer_nr_next].push_back(next_node); - - - to_delete.insert(neighbour); - to_delete.insert(p_node); - } - else if (neighbours.size() > 1) //Don't merge leaf nodes because we would then incur movement greater than the maximum move distance. - { - //Remove all neighbours that are too close and merge them into this node. - for (const Point& neighbour : neighbours) - { - if (vsize2_with_unscale(neighbour - node.position) < /*max_move_distance2*/get_max_move_dist(&node,2)) - { - Node* neighbour_node = nodes_per_part[group_index][neighbour]; - if (neighbour_node->type == ePolygon) continue; - - node.distance_to_top = std::max(node.distance_to_top, neighbour_node->distance_to_top); - node.support_roof_layers_below = std::max(node.support_roof_layers_below, neighbour_node->support_roof_layers_below); - node.dist_mm_to_top = std::max(node.dist_mm_to_top, neighbour_node->dist_mm_to_top); - node.merged_neighbours.push_front(neighbour_node); - node.merged_neighbours.insert(node.merged_neighbours.end(), neighbour_node->merged_neighbours.begin(), neighbour_node->merged_neighbours.end()); - node.is_merged = true; - to_delete.insert(neighbour_node); - } - } - } - } - - //In the second pass, move all middle nodes. - for (const std::pair& entry : nodes_per_part[group_index]) - { - Node* p_node = entry.second; - const Node& node = *p_node; - if (to_delete.find(p_node) != to_delete.end()) - { - continue; - } - if (node.type == ePolygon) { - // polygon node do not merge or move - const bool to_buildplate = !is_inside_ex(m_ts_data->m_layer_outlines[layer_nr], p_node->position); - Node * next_node = new Node(p_node->position, p_node->distance_to_top + 1, layer_nr_next, p_node->support_roof_layers_below - 1, to_buildplate, - p_node, print_z_next, height_next); - next_node->max_move_dist = 0; - next_node->is_merged = false; - contact_nodes[layer_nr_next].emplace_back(next_node); - continue; - } - - //If the branch falls completely inside a collision area (the entire branch would be removed by the X/Y offset), delete it. - if (group_index > 0 && is_inside_ex(m_ts_data->get_collision(m_ts_data->m_xy_distance, layer_nr), node.position)) - { - const coordf_t branch_radius_node = calc_branch_radius(branch_radius, node.dist_mm_to_top, diameter_angle_scale_factor); - Point to_outside = projection_onto(m_ts_data->get_collision(m_ts_data->m_xy_distance, layer_nr), node.position); - double dist2_to_outside = vsize2_with_unscale(node.position - to_outside); - if (dist2_to_outside >= branch_radius_node * branch_radius_node) //Too far inside. - { - if (support_on_buildplate_only) - { - unsupported_branch_leaves.push_front({ layer_nr, p_node }); - } - else { - Node* pn = p_node; - for (int i = 0; i <= bottom_interface_layers && pn; i++, pn = pn->parent) - pn->support_floor_layers_above = bottom_interface_layers - i + 1; // +1 so the parent node has support_floor_layers_above=2 - to_delete.insert(p_node); - } - continue; - } - // if the link between parent and current is cut by contours, mark current as bottom contact node - if (p_node->parent && intersection_ln({p_node->position, p_node->parent->position}, layer_contours).empty()==false) - { - Node* pn = p_node->parent; - for (int i = 0; i <= bottom_interface_layers && pn; i++, pn = pn->parent) - pn->support_floor_layers_above = bottom_interface_layers - i + 1; - to_delete.insert(p_node); - continue; - } - } - Point next_layer_vertex = node.position; - Point move_to_neighbor_center; - std::vector moves; - std::vector weights; - const std::vector neighbours = mst.adjacent_nodes(node.position); - // 1. do not merge neighbors under 5mm - // 2. Only merge node with single neighbor in distance between [max_move_distance, 10mm/layer_height] - float dist2_to_first_neighbor = neighbours.empty() ? 0 : vsize2_with_unscale(neighbours[0] - node.position); - if (ts_layer->print_z > DO_NOT_MOVER_UNDER_MM && - (neighbours.size() > 1 || (neighbours.size() == 1 && dist2_to_first_neighbor >= max_move_distance2))) // Only nodes that aren't about to collapse. - { - // Move towards the average position of all neighbours. - Point sum_direction(0, 0); - for (const Point &neighbour : neighbours) { - // do not move to the neighbor to be deleted - Node *neighbour_node = nodes_per_part[group_index][neighbour]; - if (to_delete.find(neighbour_node) != to_delete.end()) continue; - - Point direction = neighbour - node.position; - // do not move to neighbor that's too far away (即使以最大速度移动,在接触热床之前都无法汇聚) - float dist2_to_neighbor = vsize2_with_unscale(direction); - - coordf_t branch_bottom_radius = calc_branch_radius(branch_radius, node.dist_mm_to_top + node.print_z, diameter_angle_scale_factor); - coordf_t neighbour_bottom_radius = calc_branch_radius(branch_radius, neighbour_node->dist_mm_to_top + neighbour_node->print_z, diameter_angle_scale_factor); - double max_converge_distance = tan_angle * (ts_layer->print_z - DO_NOT_MOVER_UNDER_MM) + std::max(branch_bottom_radius, neighbour_bottom_radius); - if (dist2_to_neighbor > max_converge_distance * max_converge_distance) continue; - - if (is_line_cut_by_contour(node.position, neighbour)) continue; - - if (!is_strong) - sum_direction += direction * (1 / dist2_to_neighbor); - else - sum_direction += direction; - } - - if (!is_strong) - move_to_neighbor_center = sum_direction; - else { - if (vsize2_with_unscale(sum_direction) <= max_move_distance2) { - move_to_neighbor_center = sum_direction; - } else { - move_to_neighbor_center = normal(sum_direction, scale_(get_max_move_dist(&node))); - } - } - } - - const coordf_t branch_radius_node = calc_branch_radius(branch_radius, node.dist_mm_to_top/*+node.print_z*/, diameter_angle_scale_factor); -#ifdef SUPPORT_TREE_DEBUG_TO_SVG - if (node.position(1) > max_y) { - max_y = node.position(1); - branch_radius_temp = branch_radius_node; - } -#endif - auto avoid_layer = m_ts_data->get_avoidance(branch_radius_node, layer_nr_next); - - Point to_outside = projection_onto(avoid_layer, node.position); - Point direction_to_outer = to_outside - node.position; - double dist2_to_outer = vsize2_with_unscale(direction_to_outer); - // don't move if - // 1) line of node and to_outside is cut by contour (means supports may intersect with object) - // 2) it's impossible to move to build plate - if (is_line_cut_by_contour(node.position, to_outside) || dist2_to_outer > max_move_distance2 * SQ(layer_nr) || - !is_inside_ex(avoid_layer, node.position)) { - // try move to outside of lower layer instead - Point candidate_vertex = node.position; - const coordf_t max_move_between_samples = max_move_distance + radius_sample_resolution + EPSILON; // 100 micron extra for rounding errors. - bool is_outside = move_out_expolys(avoid_layer, candidate_vertex, max_move_between_samples, max_move_between_samples); - if (is_outside) { - direction_to_outer = candidate_vertex - node.position; - dist2_to_outer = vsize2_with_unscale(direction_to_outer); - } else { - direction_to_outer = Point(0, 0); - dist2_to_outer = 0; - } - } - // move to the averaged direction of neighbor center and contour edge if they are roughly same direction - Point movement; - if (!is_strong) - movement = move_to_neighbor_center*2 + (dist2_to_outer > EPSILON ? direction_to_outer * (1 / dist2_to_outer) : Point(0, 0)); - else { - if (movement.dot(move_to_neighbor_center) >= 0.2 || move_to_neighbor_center == Point(0, 0)) - movement = direction_to_outer + move_to_neighbor_center; - else - movement = move_to_neighbor_center; // otherwise move to neighbor center first - } - - if (vsize2_with_unscale(movement) > get_max_move_dist(&node,2)) - movement = normal(movement, scale_(get_max_move_dist(&node))); - - // add momentum to force smooth movement - //movement = movement * 0.5 + p_node->movement * 0.5; - - next_layer_vertex += movement; - - if (group_index == 0) { - // Avoid collisions. - const coordf_t max_move_between_samples = get_max_move_dist(&node, 1) + radius_sample_resolution + EPSILON; // 100 micron extra for rounding errors. - bool is_outside = move_out_expolys(avoid_layer, next_layer_vertex, radius_sample_resolution + EPSILON, max_move_between_samples); - if (!is_outside) { - Point candidate_vertex = node.position; - is_outside = move_out_expolys(avoid_layer, candidate_vertex, radius_sample_resolution + EPSILON, max_move_between_samples); - if (is_outside) { next_layer_vertex = candidate_vertex; } - } - } - - const bool to_buildplate = !is_inside_ex(m_ts_data->m_layer_outlines[layer_nr], next_layer_vertex);// !is_inside_ex(m_ts_data->get_avoidance(m_ts_data->m_xy_distance, layer_nr - 1), next_layer_vertex); - Node * next_node = new Node(next_layer_vertex, node.distance_to_top + 1, layer_nr_next, node.support_roof_layers_below - 1, to_buildplate, p_node, - print_z_next, height_next); - next_node->movement = movement; - get_max_move_dist(next_node); - next_node->is_merged = false; - contact_nodes[layer_nr_next].push_back(next_node); - } - } - -#ifdef SUPPORT_TREE_DEBUG_TO_SVG - if (contact_nodes[layer_nr].empty() == false) { - draw_contours_and_nodes_to_svg((boost::format("%.2f") % contact_nodes[layer_nr][0]->print_z).str(), m_ts_data->get_avoidance(0, layer_nr), - m_ts_data->get_avoidance(branch_radius_temp, layer_nr), - m_ts_data->m_layer_outlines_below[layer_nr], - contact_nodes[layer_nr], contact_nodes[layer_nr_next], "contact_points", { "overhang","avoid","outline" }, { "blue","red","yellow" }); - - BOOST_LOG_TRIVIAL(debug) << "drop_nodes layer " << layer_nr << ", print_z=" << ts_layer->print_z; - for (size_t i = 0; i < std::min(size_t(5), contact_nodes[layer_nr].size()); i++) { - auto &node = contact_nodes[layer_nr][i]; - BOOST_LOG_TRIVIAL(debug) << "\t node " << i << ", pos=" << node->position << ", move = " << node->movement << ", is_merged=" << node->is_merged; - } - } -#endif - - // Prune all branches that couldn't find support on either the model or the buildplate (resulting in 'mid-air' branches). - for (;! unsupported_branch_leaves.empty(); unsupported_branch_leaves.pop_back()) - { - const auto& entry = unsupported_branch_leaves.back(); - Node* i_node = entry.second; - for (; i_node != nullptr; i_node = i_node->parent) - { - size_t i_layer = i_node->obj_layer_nr; - std::vector::iterator to_erase = std::find(contact_nodes[i_layer].begin(), contact_nodes[i_layer].end(), i_node); - if (to_erase != contact_nodes[i_layer].end()) - { - // update the parent-child chain - if(i_node->parent) - i_node->parent->child = i_node->child; - if(i_node->child) - i_node->child->parent = i_node->parent; - contact_nodes[i_layer].erase(to_erase); - to_free_node_set.insert(i_node); - - for (Node* neighbour : i_node->merged_neighbours) - { - unsupported_branch_leaves.push_front({ i_layer, neighbour }); - } - } - } - } - } - - BOOST_LOG_TRIVIAL(debug) << "after m_avoidance_cache.size()=" << m_ts_data->m_avoidance_cache.size(); - - for (Node *node : to_free_node_set) - { - delete node; - } - to_free_node_set.clear(); -} - -void TreeSupport::smooth_nodes(std::vector> &contact_nodes) -{ - for (int layer_nr = 0; layer_nr < contact_nodes.size(); layer_nr++) { - std::vector &curr_layer_nodes = contact_nodes[layer_nr]; - if (curr_layer_nodes.empty()) continue; - for (Node *node : curr_layer_nodes) { - node->is_processed = false; - if (layer_nr == 0) node->is_merged = true; // nodes on plate are also merged nodes - } - } - - for (int layer_nr = 0; layer_nr< contact_nodes.size(); layer_nr++) { - std::vector &curr_layer_nodes = contact_nodes[layer_nr]; - if (curr_layer_nodes.empty()) continue; - for (Node *node : curr_layer_nodes) { - if (!node->is_processed) { - std::vector pts; - std::vector branch; - Node * p_node = node; - // add a fixed head - if (node->child) { - pts.push_back(p_node->child->position); - branch.push_back(p_node->child); - } - do { - pts.push_back(p_node->position); - branch.push_back(p_node); - p_node = p_node->parent; - } while (p_node && !p_node->is_processed); - if (pts.size() < 3) continue; - - std::vector pts1 = pts; - // TODO here we assume layer height gap is constant. If not true, need to consider height jump - const int iterations = 100; - for (size_t k = 0; k < iterations; k++) { - for (size_t i = 1; i < pts.size() - 1; i++) { - size_t i2 = i >= 2 ? i - 2 : 0; - size_t i3 = i < pts.size() - 2 ? i + 2 : pts.size() - 1; - Point pt = (pts[i2] + pts[i - 1] + pts[i] + pts[i + 1] + pts[i3]) / 5; - pts1[i] = pt; - if (k == iterations - 1) { - branch[i]->position = pt; - branch[i]->movement = (pts[i + 1] - pts[i - 1]) / 2; - branch[i]->is_processed = true; - } - } - if (k < iterations - 1) - std::swap(pts, pts1); - } - } - } - } - // save tree structure for viewing in python - auto& tree_nodes = m_ts_data->tree_nodes; - std::map ptr2idx; - std::map idx2ptr; - for (int layer_nr = 0; layer_nr < contact_nodes.size(); layer_nr++) { - std::vector& curr_layer_nodes = contact_nodes[layer_nr]; - for (Node* node : curr_layer_nodes) { - ptr2idx.emplace(node, tree_nodes.size()); - idx2ptr.emplace(tree_nodes.size(), node); - tree_nodes.emplace_back(node->position, node->print_z); - } - } - for (size_t i = 0; i < tree_nodes.size(); i++) { - TreeNode& tree_node = tree_nodes[i]; - Node* p_node = idx2ptr[i]; - if (p_node->child) - tree_node.children.push_back(ptr2idx[p_node->child]); - if(p_node->parent) - tree_node.parents.push_back(ptr2idx[p_node->parent]); - } -#ifdef SUPPORT_TREE_DEBUG_TO_SVG - nlohmann::json jj; - for (size_t i = 0; i < tree_nodes.size(); i++) { - nlohmann::json j; - j["pos"] = tree_nodes[i].pos; - j["children"] = tree_nodes[i].children; - j["linked"] = !(tree_nodes[i].pos.z() > 0.205 && tree_nodes[i].children.empty()); - jj.push_back(j); - } - - std::ofstream ofs("tree_nodes.json"); - ofs << jj.dump(); - ofs.close(); -#endif -} - -void TreeSupport::adjust_layer_heights(std::vector>& contact_nodes) -{ - if (contact_nodes.empty()) - return; - - const PrintConfig& print_config = m_object->print()->config(); - const PrintObjectConfig& config = m_object->config(); - // don't merge layers for Vine support, or the branches will be unsmooth - // TODO can we merge layers in a way that guaranttees smoothness? - if (!print_config.independent_support_layer_height || is_slim) { - for (int layer_nr = 0; layer_nr < contact_nodes.size(); layer_nr++) { - std::vector& curr_layer_nodes = contact_nodes[layer_nr]; - for (Node* node : curr_layer_nodes) { - node->print_z = m_object->get_layer(layer_nr)->print_z; - node->height = m_object->get_layer(layer_nr)->height; - } - } - return; - } - - // extreme layer_id - std::vector extremes; - const coordf_t layer_height = config.layer_height.value; - const coordf_t max_layer_height = m_slicing_params.max_layer_height; - const size_t bot_intf_layers = config.support_interface_bottom_layers.value; - const size_t top_intf_layers = config.support_interface_top_layers.value; - - // if already using max layer height, no need to adjust - if (layer_height == max_layer_height) return; - - extremes.push_back(0); - for (Node* node : contact_nodes[0]) { - node->print_z = m_object->get_layer(0)->print_z; - node->height = m_object->get_layer(0)->height; - } - - for (int layer_nr = 1; layer_nr < contact_nodes.size(); layer_nr++) { - std::vector& curr_layer_nodes = contact_nodes[layer_nr]; - for (Node* node : curr_layer_nodes) { - if (node->support_roof_layers_below >0 || node->support_floor_layers_above == bot_intf_layers) { - extremes.push_back(layer_nr); - break; - } - } - - if (extremes.back() == layer_nr) { - // contact layer use the same print_z and layer height with object layer - for (Node* node : curr_layer_nodes) { - node->print_z = m_object->get_layer(layer_nr)->print_z; - node->height = m_object->get_layer(layer_nr)->height; - } - } - } - - // schedule new layer heights and print_z - for (size_t idx_extreme = 0; idx_extreme < extremes.size(); idx_extreme++) { - int extr2_layer_nr = extremes[idx_extreme]; - coordf_t extr2z = m_object->get_layer(extr2_layer_nr)->bottom_z(); - int extr1_layer_nr = idx_extreme == 0 ? -1 : extremes[idx_extreme - 1]; - coordf_t extr1z = idx_extreme == 0 ? 0.f : m_object->get_layer(extr1_layer_nr)->print_z; - coordf_t dist = extr2z - extr1z; - - // Insert intermediate layers. - size_t n_layers_extra = size_t(ceil(dist / m_slicing_params.max_suport_layer_height)); - if (n_layers_extra <= 1) - continue; - - coordf_t step = dist / coordf_t(n_layers_extra); - coordf_t print_z = extr1z + step; - assert(step >= layer_height - EPSILON); - for (int layer_nr = extr1_layer_nr + 1; layer_nr < extr2_layer_nr; layer_nr++) { - std::vector& curr_layer_nodes = contact_nodes[layer_nr]; - if (curr_layer_nodes.empty()) continue; - - if (std::abs(print_z - curr_layer_nodes[0]->print_z) < step / 2 + EPSILON) { - for (Node* node : curr_layer_nodes) { - node->print_z = print_z; - node->height = step; - } - print_z += step; - } - else { - // can't clear curr_layer_nodes, or the model will have empty layers - for (Node* node : curr_layer_nodes) { - node->print_z = 0.0; - node->height = 0.0; - } - } - } - } -} - -std::vector TreeSupport::plan_layer_heights(std::vector> &contact_nodes) -{ - const PrintObjectConfig& config = m_object->config(); - const PrintConfig & print_config = m_object->print()->config(); - const coordf_t max_layer_height = m_slicing_params.max_layer_height; - const coordf_t layer_height = config.layer_height.value; - coordf_t z_distance_top = m_slicing_params.gap_support_object; - // BBS: add extra distance if thick bridge is enabled - // Note: normal support uses print_z, but tree support uses integer layers, so we need to subtract layer_height - if (!m_slicing_params.soluble_interface && m_object_config->thick_bridges) { - z_distance_top += m_object->layers()[0]->regions()[0]->region().bridging_height_avg(m_object->print()->config()) - layer_height; - } - const size_t support_roof_layers = config.support_interface_top_layers.value; - const int z_distance_top_layers = round_up_divide(scale_(z_distance_top), scale_(layer_height)) + 1; - std::vector layer_heights(contact_nodes.size()); - std::vector bounds; - - if (!config.tree_support_adaptive_layer_height || layer_height == max_layer_height || !print_config.independent_support_layer_height) { - for (int layer_nr = 0; layer_nr < contact_nodes.size(); layer_nr++) { - layer_heights[layer_nr] = {m_object->get_layer(layer_nr)->print_z, m_object->get_layer(layer_nr)->height, layer_nr > 0 ? size_t(layer_nr - 1) : 0}; - } - return layer_heights; - } - - bounds.push_back(0); - // Keep first layer still - layer_heights[0] = {m_object->get_layer(0)->print_z, m_object->get_layer(0)->height, 0}; - // Collect top contact layers - for (int layer_nr = 1; layer_nr < contact_nodes.size(); layer_nr++) - { - if (!contact_nodes[layer_nr].empty()) - for (int i = 0; i < support_roof_layers + z_distance_top_layers + 1; i++) { - if (layer_nr - i > 0) { - bounds.push_back(layer_nr - i); - layer_heights[layer_nr - i].print_z = m_object->get_layer(layer_nr - i)->print_z; - layer_heights[layer_nr - i].height = m_object->get_layer(layer_nr - i)->height; - } - else { - break; - } - - } - } - std::set s(bounds.begin(), bounds.end()); - bounds.assign(s.begin(), s.end()); - - for (size_t idx_extreme = 0; idx_extreme < bounds.size(); idx_extreme++) { - int extr2_layer_nr = bounds[idx_extreme]; - coordf_t extr2z = m_object->get_layer(extr2_layer_nr)->bottom_z(); - int extr1_layer_nr = idx_extreme == 0 ? -1 : bounds[idx_extreme - 1]; - coordf_t extr1z = idx_extreme == 0 ? 0.f : m_object->get_layer(extr1_layer_nr)->print_z; - coordf_t dist = extr2z - extr1z; - - // Insert intermediate layers. - size_t n_layers_extra = size_t(ceil(dist / (m_slicing_params.max_suport_layer_height + EPSILON))); - int actual_internel_layers = extr2_layer_nr - extr1_layer_nr - 1; - int extr_layers_left = extr2_layer_nr - extr1_layer_nr - n_layers_extra - 1; - if (n_layers_extra < 1) - continue; - - coordf_t step = dist / coordf_t(n_layers_extra); - coordf_t print_z = extr1z + step; - assert(step >= layer_height - EPSILON); - for (int layer_nr = extr1_layer_nr + 1; layer_nr < extr2_layer_nr; layer_nr++) { - // if (curr_layer_nodes.empty()) continue; - if (std::abs(print_z - m_object->get_layer(layer_nr)->print_z) < step / 2 + EPSILON || extr_layers_left < 1) { - layer_heights[layer_nr].print_z = print_z; - layer_heights[layer_nr].height = step; - print_z += step; - } - else { - // can't clear curr_layer_nodes, or the model will have empty layers - layer_heights[layer_nr].print_z = 0.0; - layer_heights[layer_nr].height = 0.0; - extr_layers_left--; - } - } - } - - // fill in next_layer_nr - int i = layer_heights.size() - 1, j = i; - for (; j >= 0; i = j) { - if (layer_heights[i].height < EPSILON) { - j--; - continue; - } - for (j = i - 1; j >= 0; j--) { - if (layer_heights[j].height > EPSILON) { - layer_heights[i].next_layer_nr = j; - break; - } - } - BOOST_LOG_TRIVIAL(trace) << "plan_layer_heights print_z, height, layer_nr->next_layer_nr: " << layer_heights[i].print_z << " " << layer_heights[i].height << " " - << i << "->" << layer_heights[i].next_layer_nr << std::endl; - } - - return layer_heights; -} - -void TreeSupport::generate_contact_points(std::vector>& contact_nodes) -{ - const PrintObjectConfig &config = m_object->config(); - const coordf_t point_spread = scale_(config.tree_support_branch_distance.value); - - //First generate grid points to cover the entire area of the print. - BoundingBox bounding_box = m_object->bounding_box(); - const Point bounding_box_size = bounding_box.max - bounding_box.min; - constexpr double rotate_angle = 22.0 / 180.0 * M_PI; - - const auto center = bounding_box_middle(bounding_box); - const auto sin_angle = std::sin(rotate_angle); - const auto cos_angle = std::cos(rotate_angle); - const Point rotated_dims = Point( - bounding_box_size(0) * cos_angle + bounding_box_size(1) * sin_angle, - bounding_box_size(0) * sin_angle + bounding_box_size(1) * cos_angle) / 2; - - std::vector grid_points; - for (auto x = -rotated_dims(0); x < rotated_dims(0); x += point_spread) { - for (auto y = -rotated_dims(1); y < rotated_dims(1); y += point_spread) { - Point pt(x, y); - pt.rotate(cos_angle, sin_angle); - pt += center; - if (bounding_box.contains(pt)) { - grid_points.push_back(pt); - } - } - } - - const coordf_t layer_height = config.layer_height.value; - coordf_t z_distance_top = m_slicing_params.gap_support_object; - // BBS: add extra distance if thick bridge is enabled - // Note: normal support uses print_z, but tree support uses integer layers, so we need to subtract layer_height - if (!m_slicing_params.soluble_interface && m_object_config->thick_bridges) { - z_distance_top += m_object->layers()[0]->regions()[0]->region().bridging_height_avg(m_object->print()->config()) - layer_height; - } - const int z_distance_top_layers = round_up_divide(scale_(z_distance_top), scale_(layer_height)) + 1; //Support must always be 1 layer below overhang. - - size_t support_roof_layers = config.support_interface_top_layers.value; - if (support_roof_layers > 0) - support_roof_layers += 1; // BBS: add a normal support layer below interface (if we have interface) - coordf_t thresh_angle = std::min(89.f, config.support_threshold_angle.value < EPSILON ? 30.f : config.support_threshold_angle.value); - coordf_t half_overhang_distance = scale_(tan(thresh_angle * M_PI / 180.0) * layer_height / 2); - - // fix bug of generating support for very thin objects - if (m_object->layers().size() <= z_distance_top_layers + 1) - return; - - m_highest_overhang_layer = 0; - int nonempty_layers = 0; - std::vector all_nodes; - for (size_t layer_nr = 1; layer_nr < m_object->layers().size(); layer_nr++) - { - if (m_object->print()->canceled()) - break; - auto ts_layer = m_object->get_support_layer(layer_nr + m_raft_layers); - const ExPolygons &overhang = ts_layer->overhang_areas; - auto & curr_nodes = contact_nodes[layer_nr]; - if (overhang.empty()) - continue; - - m_highest_overhang_layer = std::max(m_highest_overhang_layer, layer_nr); - auto print_z = m_object->get_layer(layer_nr)->print_z; - auto height = m_object->get_layer(layer_nr)->height; - - for (const ExPolygon &overhang_part : overhang) - { - BoundingBox overhang_bounds = get_extents(overhang_part); - if (support_style==smsTreeHybrid && overhang_part.area() > m_support_params.thresh_big_overhang) { - Point candidate = overhang_bounds.center(); - if (!overhang_part.contains(candidate)) - move_inside_expoly(overhang_part, candidate); - if (!(config.support_on_build_plate_only && is_inside_ex(m_ts_data->m_layer_outlines_below[layer_nr], candidate))) { - Node* contact_node = new Node(candidate, -z_distance_top_layers, layer_nr, support_roof_layers + z_distance_top_layers, true, Node::NO_PARENT, print_z, - height, z_distance_top); - contact_node->type = ePolygon; - contact_node->overhang = &overhang_part; - curr_nodes.emplace_back(contact_node); - continue; - } - } - - overhang_bounds.inflated(half_overhang_distance); - bool added = false; //Did we add a point this way? - for (Point candidate : grid_points) - { - if (overhang_bounds.contains(candidate)) - { - // BBS: move_inside_expoly shouldn't be used if candidate is already inside, as it moves point to boundary and the inside is not well supported! - bool is_inside = is_inside_ex(overhang_part, candidate); - if (!is_inside) { - constexpr coordf_t distance_inside = 0; // Move point towards the border of the polygon if it is closer than half the overhang distance: Catch points that - // fall between overhang areas on constant surfaces. - move_inside_expoly(overhang_part, candidate, distance_inside, half_overhang_distance); - is_inside = is_inside_ex(overhang_part, candidate); - } - if (is_inside) - { - // collision radius has to be 0 or the supports are too few at curved slopes - //if (!is_inside_ex(m_ts_data->get_collision(0, layer_nr), candidate)) - { - constexpr bool to_buildplate = true; - Node * contact_node = new Node(candidate, -z_distance_top_layers, layer_nr, support_roof_layers + z_distance_top_layers, to_buildplate, - Node::NO_PARENT, print_z, height, z_distance_top); - contact_node->overhang = &overhang_part; - curr_nodes.emplace_back(contact_node); - added = true; - } - } - } - } - - if (!added) //If we didn't add any points due to bad luck, we want to add one anyway such that loose parts are also supported. - { - auto bbox = overhang_part.contour.bounding_box(); - Points candidates; - if (ts_layer->overhang_types[&overhang_part] == SupportLayer::Detected) - candidates = {bbox.min, bounding_box_middle(bbox), bbox.max}; - else - candidates = {bounding_box_middle(bbox)}; - - for (Point candidate : candidates) { - if (!overhang_part.contains(candidate)) - move_inside_expoly(overhang_part, candidate); - constexpr bool to_buildplate = true; - Node *contact_node = new Node(candidate, -z_distance_top_layers, layer_nr, support_roof_layers + z_distance_top_layers, to_buildplate, Node::NO_PARENT, - print_z, height, z_distance_top); - contact_node->overhang = &overhang_part; - curr_nodes.emplace_back(contact_node); - } - } - // add supports at corners for both auto and manual overhangs, github #2008 - if (/*ts_layer->overhang_types[&overhang_part] == SupportLayer::Detected*/1) { - // add points at corners - auto &points = overhang_part.contour.points; - int nSize = points.size(); - for (int i = 0; i < nSize; i++) { - auto pt = points[i]; - auto v1 = (pt - points[(i - 1 + nSize) % nSize]).cast().normalized(); - auto v2 = (pt - points[(i + 1) % nSize]).cast().normalized(); - if (v1.dot(v2) > -0.7) { // angle smaller than 135 degrees - Node *contact_node = new Node(pt, -z_distance_top_layers, layer_nr, support_roof_layers + z_distance_top_layers, true, Node::NO_PARENT, print_z, - height, z_distance_top); - contact_node->overhang = &overhang_part; - contact_node->is_corner = true; - curr_nodes.emplace_back(contact_node); - } - } - } - if(ts_layer->overhang_types[&overhang_part] == SupportLayer::Enforced || is_slim){ - // remove close points in Enforcers - // auto above_nodes = contact_nodes[layer_nr - 1]; - if (!curr_nodes.empty() /*&& !above_nodes.empty()*/) { - for (auto it = curr_nodes.begin(); it != curr_nodes.end();) { - bool is_duplicate = false; - if (!(*it)->is_corner) { - Slic3r::Vec3f curr_pt((*it)->position(0), (*it)->position(1), scale_((*it)->print_z)); - for (auto &pt : all_nodes) { - auto dif = curr_pt - pt; - if (dif.norm() < point_spread / 2) { - delete (*it); - it = curr_nodes.erase(it); - is_duplicate = true; - break; - } - } - } - if (!is_duplicate) it++; - } - } - } - } - if (!curr_nodes.empty()) nonempty_layers++; - for (auto node : curr_nodes) { all_nodes.emplace_back(node->position(0), node->position(1), scale_(node->print_z)); } -#ifdef SUPPORT_TREE_DEBUG_TO_SVG - draw_contours_and_nodes_to_svg(std::to_string(print_z), overhang, m_ts_data->m_layer_outlines_below[layer_nr], {}, - contact_nodes[layer_nr], {}, "init_contact_points", { "overhang","outlines","" }); -#endif - } - int nNodes = all_nodes.size(); - avg_node_per_layer = nodes_angle = 0; - if (nNodes > 0) { - avg_node_per_layer = nNodes / nonempty_layers; - // get orientation of nodes by line fitting - // line: y=kx+b, where - // k=tan(nodes_angle)=(n\sum{xy}-\sum{x}\sum{y})/(n\sum{x^2}-\sum{x}^2) - float mx = 0, my = 0, mxy = 0, mx2 = 0; - for (auto &pt : all_nodes) { - float x = unscale_(pt(0)); - float y = unscale_(pt(1)); - mx += x; - my += y; - mxy += x * y; - mx2 += x * x; - } - nodes_angle = atan2(nNodes * mxy - mx * my, nNodes * mx2 - SQ(mx)); - - BOOST_LOG_TRIVIAL(info) << "avg_node_per_layer=" << avg_node_per_layer << ", nodes_angle=" << nodes_angle; - } -} - -void TreeSupport::insert_dropped_node(std::vector& nodes_layer, Node* p_node) -{ - std::vector::iterator conflicting_node_it = std::find(nodes_layer.begin(), nodes_layer.end(), p_node); - if (conflicting_node_it == nodes_layer.end()) //No conflict. - { - nodes_layer.emplace_back(p_node); - return; - } - - Node* conflicting_node = *conflicting_node_it; - conflicting_node->distance_to_top = std::max(conflicting_node->distance_to_top, p_node->distance_to_top); - conflicting_node->support_roof_layers_below = std::max(conflicting_node->support_roof_layers_below, p_node->support_roof_layers_below); -} - -TreeSupportData::TreeSupportData(const PrintObject &object, coordf_t xy_distance, coordf_t max_move, coordf_t radius_sample_resolution) - : m_xy_distance(xy_distance), m_max_move(max_move), m_radius_sample_resolution(radius_sample_resolution) -{ - for (std::size_t layer_nr = 0; layer_nr < object.layers().size(); ++layer_nr) - { - const Layer* layer = object.get_layer(layer_nr); - m_layer_outlines.push_back(ExPolygons()); - ExPolygons& outline = m_layer_outlines.back(); - for (const ExPolygon& poly : layer->lslices) { - poly.simplify(scale_(m_radius_sample_resolution), &outline); - } - - if (layer_nr == 0) - m_layer_outlines_below.push_back(outline); - else - m_layer_outlines_below.push_back(union_ex(m_layer_outlines_below.end()[-1], outline)); - } -} - -const ExPolygons& TreeSupportData::get_collision(coordf_t radius, size_t layer_nr) const -{ - profiler.tic(); - radius = ceil_radius(radius); - RadiusLayerPair key{radius, layer_nr}; - const auto it = m_collision_cache.find(key); - const ExPolygons& collision = it != m_collision_cache.end() ? it->second : calculate_collision(key); - profiler.stage_add(STAGE_get_collision, true); - return collision; -} - -const ExPolygons& TreeSupportData::get_avoidance(coordf_t radius, size_t layer_nr, int recursions) const -{ - profiler.tic(); - radius = ceil_radius(radius); - RadiusLayerPair key{radius, layer_nr, recursions }; - const auto it = m_avoidance_cache.find(key); - const ExPolygons& avoidance = it != m_avoidance_cache.end() ? it->second : calculate_avoidance(key); - - profiler.stage_add(STAGE_GET_AVOIDANCE, true); - return avoidance; -} - -Polygons TreeSupportData::get_contours(size_t layer_nr) const -{ - Polygons contours; - for (const ExPolygon& expoly : m_layer_outlines[layer_nr]) { - contours.push_back(expoly.contour); - } - - return contours; -} - -Polygons TreeSupportData::get_contours_with_holes(size_t layer_nr) const -{ - Polygons contours; - for (const ExPolygon& expoly : m_layer_outlines[layer_nr]) { - for(int i=0;i EPSILON) { - return radius + m_radius_sample_resolution - remains; - } - else { - return radius; - } -#else - coordf_t resolution = m_radius_sample_resolution; - return ceil(radius / resolution) * resolution; -#endif -} - -const ExPolygons& TreeSupportData::calculate_collision(const RadiusLayerPair& key) const -{ - assert(key.layer_nr < m_layer_outlines.size()); - - ExPolygons collision_areas = offset_ex(m_layer_outlines[key.layer_nr], scale_(key.radius)); - const auto ret = m_collision_cache.insert({ key, std::move(collision_areas) }); - return ret.first->second; -} - -const ExPolygons& TreeSupportData::calculate_avoidance(const RadiusLayerPair& key) const -{ - const auto& radius = key.radius; - const auto& layer_nr = key.layer_nr; - BOOST_LOG_TRIVIAL(debug) << "calculate_avoidance on radius=" << radius << ", layer=" << layer_nr<<", recursion="< 0; layers_below++) { layer_nr_next = layer_heights[layer_nr_next].next_layer_nr; } - // Check if we would exceed the recursion limit by trying to process this layer - if (layers_below >= max_recursion_depth && m_avoidance_cache.find({radius, layer_nr_next}) == m_avoidance_cache.end()) { - // Force the calculation of the layer `max_recursion_depth` below our current one, ignoring the result. - get_avoidance(radius, layer_nr_next, key.recursions + 1); - } - - layer_nr_next = layer_heights[layer_nr].next_layer_nr; - ExPolygons avoidance_areas = offset_ex(get_avoidance(radius, layer_nr_next, key.recursions+1), scale_(-m_max_move)); - const ExPolygons &collision = get_collision(radius, layer_nr); - avoidance_areas.insert(avoidance_areas.end(), collision.begin(), collision.end()); - avoidance_areas = std::move(union_ex(avoidance_areas)); - auto ret = m_avoidance_cache.insert({ key, std::move(avoidance_areas) }); - //assert(ret.second); - return ret.first->second; - } else { - ExPolygons avoidance_areas = offset_ex(m_layer_outlines_below[layer_nr], scale_(m_xy_distance + radius)); - auto ret = m_avoidance_cache.insert({ key, std::move(avoidance_areas) }); - assert(ret.second); - return ret.first->second; - } -} - -} //namespace Slic3r diff --git a/src/libslic3r/TreeSupport.hpp b/src/libslic3r/TreeSupport.hpp deleted file mode 100644 index 2f3546ed234..00000000000 --- a/src/libslic3r/TreeSupport.hpp +++ /dev/null @@ -1,511 +0,0 @@ -#ifndef TREESUPPORT_H -#define TREESUPPORT_H - -#include -#include -#include "ExPolygon.hpp" -#include "Point.hpp" -#include "Slicing.hpp" -#include "MinimumSpanningTree.hpp" -#include "tbb/concurrent_unordered_map.h" -#include "Flow.hpp" -#include "PrintConfig.hpp" -#include "Fill/Lightning/Generator.hpp" - -#ifndef SQ -#define SQ(x) ((x)*(x)) -#endif - -namespace Slic3r -{ -class PrintObject; -class TreeSupport; -class SupportLayer; - -struct LayerHeightData -{ - coordf_t print_z = 0; - coordf_t height = 0; - size_t next_layer_nr = 0; - LayerHeightData() = default; - LayerHeightData(coordf_t z, coordf_t h, size_t next_layer) : print_z(z), height(h), next_layer_nr(next_layer) {} -}; - -struct TreeNode { - Vec3f pos; - std::vector children; // index of children in the storing vector - std::vector parents; // index of parents in the storing vector - TreeNode(Point pt, float z) { - pos = { float(unscale_(pt.x())),float(unscale_(pt.y())),z }; - } -}; - -/*! - * \brief Lazily generates tree guidance volumes. - * - * \warning This class is not currently thread-safe and should not be accessed in OpenMP blocks - */ -class TreeSupportData -{ -public: - TreeSupportData() = default; - /*! - * \brief Construct the TreeSupportData object - * - * \param xy_distance The required clearance between the model and the - * tree branches. - * \param max_move The maximum allowable movement between nodes on - * adjacent layers - * \param radius_sample_resolution Sample size used to round requested node radii. - * \param collision_resolution - */ - TreeSupportData(const PrintObject& object, coordf_t max_move, coordf_t radius_sample_resolution, coordf_t collision_resolution); - - TreeSupportData(TreeSupportData&&) = default; - TreeSupportData& operator=(TreeSupportData&&) = default; - - TreeSupportData(const TreeSupportData&) = delete; - TreeSupportData& operator=(const TreeSupportData&) = delete; - - /*! - * \brief Creates the areas that have to be avoided by the tree's branches. - * - * The result is a 2D area that would cause nodes of radius \p radius to - * collide with the model. - * - * \param radius The radius of the node of interest - * \param layer The layer of interest - * \return Polygons object - */ - const ExPolygons& get_collision(coordf_t radius, size_t layer_idx) const; - - /*! - * \brief Creates the areas that have to be avoided by the tree's branches - * in order to reach the build plate. - * - * The result is a 2D area that would cause nodes of radius \p radius to - * collide with the model or be unable to reach the build platform. - * - * The input collision areas are inset by the maximum move distance and - * propagated upwards. - * - * \param radius The radius of the node of interest - * \param layer The layer of interest - * \return Polygons object - */ - const ExPolygons& get_avoidance(coordf_t radius, size_t layer_idx, int recursions=0) const; - - Polygons get_contours(size_t layer_nr) const; - Polygons get_contours_with_holes(size_t layer_nr) const; - - std::vector layer_heights; - - std::vector tree_nodes; - -private: - /*! - * \brief Convenience typedef for the keys to the caches - */ - struct RadiusLayerPair { - coordf_t radius; - size_t layer_nr; - int recursions; - - }; - struct RadiusLayerPairEquality { - constexpr bool operator()(const RadiusLayerPair& _Left, const RadiusLayerPair& _Right) const { - return _Left.radius == _Right.radius && _Left.layer_nr == _Right.layer_nr; - } - }; - struct RadiusLayerPairHash { - size_t operator()(const RadiusLayerPair& elem) const { - return std::hash()(elem.radius) ^ std::hash()(elem.layer_nr * 7919); - } - }; - - /*! - * \brief Round \p radius upwards to a multiple of m_radius_sample_resolution - * - * \param radius The radius of the node of interest - */ - coordf_t ceil_radius(coordf_t radius) const; - - /*! - * \brief Calculate the collision areas at the radius and layer indicated - * by \p key. - * - * \param key The radius and layer of the node of interest - */ - const ExPolygons& calculate_collision(const RadiusLayerPair& key) const; - - /*! - * \brief Calculate the avoidance areas at the radius and layer indicated - * by \p key. - * - * \param key The radius and layer of the node of interest - */ - const ExPolygons& calculate_avoidance(const RadiusLayerPair& key) const; - - -public: - bool is_slim = false; - /*! - * \brief The required clearance between the model and the tree branches - */ - coordf_t m_xy_distance; - - /*! - * \brief The maximum distance that the centrepoint of a tree branch may - * move in consequtive layers - */ - coordf_t m_max_move; - - /*! - * \brief Sample resolution for radius values. - * - * The radius will be rounded (upwards) to multiples of this value before - * calculations are done when collision, avoidance and internal model - * Polygons are requested. - */ - coordf_t m_radius_sample_resolution; - - /*! - * \brief Storage for layer outlines of the meshes. - */ - std::vector m_layer_outlines; - - // union contours of all layers below - std::vector m_layer_outlines_below; - - /*! - * \brief Caches for the collision, avoidance and internal model polygons - * at given radius and layer indices. - * - * These are mutable to allow modification from const function. This is - * generally considered OK as the functions are still logically const - * (ie there is no difference in behaviour for the user betweeen - * calculating the values each time vs caching the results). - * - * coconut: previously stl::unordered_map is used which seems problematic with tbb::parallel_for. - * So we change to tbb::concurrent_unordered_map - */ - mutable tbb::concurrent_unordered_map m_collision_cache; - mutable tbb::concurrent_unordered_map m_avoidance_cache; - - friend TreeSupport; -}; - -struct LineHash { - size_t operator()(const Line& line) const { - return (std::hash()(line.a(0)) ^ std::hash()(line.b(1))) * 102 + - (std::hash()(line.a(1)) ^ std::hash()(line.b(0))) * 10222; - } -}; - -/*! - * \brief Generates a tree structure to support your models. - */ -class TreeSupport -{ -public: - /*! - * \brief Creates an instance of the tree support generator. - * - * \param storage The data storage to get global settings from. - */ - TreeSupport(PrintObject& object, const SlicingParameters &slicing_params); - - /*! - * \brief Create the areas that need support. - * - * These areas are stored inside the given SliceDataStorage object. - * \param storage The data storage where the mesh data is gotten from and - * where the resulting support areas are stored. - */ - void generate(); - - void detect_overhangs(bool detect_first_sharp_tail_only=false); - - enum NodeType { - eCircle, - eSquare, - ePolygon - }; - - /*! - * \brief Represents the metadata of a node in the tree. - */ - struct Node - { - static constexpr Node* NO_PARENT = nullptr; - - Node() - : distance_to_top(0) - , position(Point(0, 0)) - , obj_layer_nr(0) - , support_roof_layers_below(0) - , support_floor_layers_above(0) - , to_buildplate(true) - , parent(nullptr) - , print_z(0.0) - , height(0.0) - {} - - // when dist_mm_to_top_==0, new node's dist_mm_to_top=parent->dist_mm_to_top + parent->height; - Node(const Point position, const int distance_to_top, const int obj_layer_nr, const int support_roof_layers_below, const bool to_buildplate, Node* parent, - coordf_t print_z_, coordf_t height_, coordf_t dist_mm_to_top_=0) - : distance_to_top(distance_to_top) - , position(position) - , obj_layer_nr(obj_layer_nr) - , support_roof_layers_below(support_roof_layers_below) - , support_floor_layers_above(0) - , to_buildplate(to_buildplate) - , parent(parent) - , print_z(print_z_) - , height(height_) - , dist_mm_to_top(dist_mm_to_top_) - { - if (parent) { - type = parent->type; - overhang = parent->overhang; - if (dist_mm_to_top==0) - dist_mm_to_top = parent->dist_mm_to_top + parent->height; - parent->child = this; - for (auto& neighbor : parent->merged_neighbours) - neighbor->child = this; - } - } - -#ifdef DEBUG // Clear the delete node's data so if there's invalid access after, we may get a clue by inspecting that node. - ~Node() - { - parent = nullptr; - merged_neighbours.clear(); - } -#endif // DEBUG - - /*! - * \brief The number of layers to go to the top of this branch. - * Negative value means it's a virtual node between support and overhang, which doesn't need to be extruded. - */ - int distance_to_top; - coordf_t dist_mm_to_top = 0; // dist to bottom contact in mm - - /*! - * \brief The position of this node on the layer. - */ - Point position; - Point movement; // movement towards neighbor center or outline - mutable double radius = 0.0; - mutable double max_move_dist = 0.0; - NodeType type = eCircle; - bool is_merged = false; // this node is generated by merging upper nodes - bool is_corner = false; - bool is_processed = false; - const ExPolygon *overhang = nullptr; // when type==ePolygon, set this value to get original overhang area - - /*! - * \brief The direction of the skin lines above the tip of the branch. - * - * This determines in which direction we should reduce the width of the - * branch. - */ - bool skin_direction; - - /*! - * \brief The number of support roof layers below this one. - * - * When a contact point is created, it is determined whether the mesh - * needs to be supported with support roof or not, since that is a - * per-mesh setting. This is stored in this variable in order to track - * how far we need to extend that support roof downwards. - */ - int support_roof_layers_below; - int support_floor_layers_above; - int obj_layer_nr; - - /*! - * \brief Whether to try to go towards the build plate. - * - * If the node is inside the collision areas, it has no choice but to go - * towards the model. If it is not inside the collision areas, it must - * go towards the build plate to prevent a scar on the surface. - */ - bool to_buildplate; - - /*! - * \brief The originating node for this one, one layer higher. - * - * In order to prune branches that can't have any support (because they - * can't be on the model and the path to the buildplate isn't clear), - * the entire branch needs to be known. - */ - Node *parent; - Node *child = nullptr; - - /*! - * \brief All neighbours (on the same layer) that where merged into this node. - * - * In order to prune branches that can't have any support (because they - * can't be on the model and the path to the buildplate isn't clear), - * the entire branch needs to be known. - */ - std::list merged_neighbours; - - coordf_t print_z; - coordf_t height; - - bool operator==(const Node& other) const - { - return position == other.position; - } - }; - - struct SupportParams - { - Flow first_layer_flow; - Flow support_material_flow; - Flow support_material_interface_flow; - Flow support_material_bottom_interface_flow; - coordf_t support_extrusion_width; - // Is merging of regions allowed? Could the interface & base support regions be printed with the same extruder? - bool can_merge_support_regions; - - coordf_t support_layer_height_min; - // coordf_t support_layer_height_max; - - coordf_t gap_xy; - - float base_angle; - float interface_angle; - coordf_t interface_spacing; - coordf_t interface_density; - coordf_t support_spacing; - coordf_t support_density; - - InfillPattern base_fill_pattern; - InfillPattern interface_fill_pattern; - InfillPattern contact_fill_pattern; - bool with_sheath; - const double thresh_big_overhang = SQ(scale_(10)); - }; - - int avg_node_per_layer = 0; - float nodes_angle = 0; - bool has_overhangs = false; - bool has_sharp_tails = false; - bool has_cantilever = false; - double max_cantilever_dist = 0; - SupportType support_type; - SupportMaterialStyle support_style; - - std::unique_ptr generator; - std::unordered_map printZ_to_lightninglayer; -private: - /*! - * \brief Generator for model collision, avoidance and internal guide volumes - * - * Lazily computes volumes as needed. - * \warning This class is NOT currently thread-safe and should not be accessed in OpenMP blocks - */ - std::shared_ptr m_ts_data; - PrintObject *m_object; - const PrintObjectConfig *m_object_config; - SlicingParameters m_slicing_params; - // Various precomputed support parameters to be shared with external functions. - SupportParams m_support_params; - size_t m_raft_layers = 0; - size_t m_highest_overhang_layer = 0; - std::vector> m_spanning_trees; - std::vector< std::unordered_map> m_mst_line_x_layer_contour_caches; - coordf_t MAX_BRANCH_RADIUS = 10.0; - coordf_t MAX_BRANCH_RADIUS_FIRST_LAYER = 12.0; - coordf_t MIN_BRANCH_RADIUS = 0.5; - float tree_support_branch_diameter_angle = 5.0; - bool is_strong = false; - bool is_slim = false; - bool with_infill = false; - - - /*! - * \brief Polygons representing the limits of the printable area of the - * machine - */ - ExPolygon m_machine_border; - - /*! - * \brief Draws circles around each node of the tree into the final support. - * - * This also handles the areas that have to become support roof, support - * bottom, the Z distances, etc. - * - * \param storage[in, out] The settings storage to get settings from and to - * save the resulting support polygons to. - * \param contact_nodes The nodes to draw as support. - */ - void draw_circles(const std::vector>& contact_nodes); - - /*! - * \brief Drops down the nodes of the tree support towards the build plate. - * - * This is where the cleverness of tree support comes in: The nodes stay on - * their 2D layers but on the next layer they are slightly shifted. This - * causes them to move towards each other as they are copied to lower layers - * which ultimately results in a 3D tree. - * - * \param contact_nodes[in, out] The nodes in the space that need to be - * dropped down. The nodes are dropped to lower layers inside the same - * vector of layers. - */ - void drop_nodes(std::vector> &contact_nodes); - - void smooth_nodes(std::vector> &contact_nodes); - - void adjust_layer_heights(std::vector>& contact_nodes); - - /*! BBS: MusangKing: maximum layer height - * \brief Optimize the generation of tree support by pre-planning the layer_heights - * - */ - - std::vector plan_layer_heights(std::vector> &contact_nodes); - /*! - * \brief Creates points where support contacts the model. - * - * A set of points is created for each layer. - * \param mesh The mesh to get the overhang areas to support of. - * \param contact_nodes[out] A vector of mappings from contact points to - * their tree nodes. - * \param collision_areas For every layer, the areas where a generated - * contact point would immediately collide with the model due to the X/Y - * distance. - * \return For each layer, a list of points where the tree should connect - * with the model. - */ - void generate_contact_points(std::vector>& contact_nodes); - - /*! - * \brief Add a node to the next layer. - * - * If a node is already at that position in the layer, the nodes are merged. - */ - void insert_dropped_node(std::vector& nodes_layer, Node* node); - void create_tree_support_layers(); - void generate_toolpaths(); - Polygons spanning_tree_to_polygon(const std::vector& spanning_trees, Polygons layer_contours, int layer_nr); - Polygons contact_nodes_to_polygon(const std::vector& contact_nodes, Polygons layer_contours, int layer_nr, std::vector& radiis, std::vector& is_interface); - coordf_t calc_branch_radius(coordf_t base_radius, size_t layers_to_top, size_t tip_layers, double diameter_angle_scale_factor); - coordf_t calc_branch_radius(coordf_t base_radius, coordf_t mm_to_top, double diameter_angle_scale_factor); - - // similar to SupportMaterial::trim_support_layers_by_object - Polygons get_trim_support_regions( - const PrintObject& object, - SupportLayer* support_layer_ptr, - const coordf_t gap_extra_above, - const coordf_t gap_extra_below, - const coordf_t gap_xy); -}; - -} - -#endif /* TREESUPPORT_H */ diff --git a/src/slic3r/GUI/ConfigManipulation.cpp b/src/slic3r/GUI/ConfigManipulation.cpp index 3f4399d68e3..704fdf0dac1 100644 --- a/src/slic3r/GUI/ConfigManipulation.cpp +++ b/src/slic3r/GUI/ConfigManipulation.cpp @@ -736,8 +736,9 @@ void ConfigManipulation::toggle_print_fff_options(DynamicPrintConfig *config, co bool has_detect_overhang_wall = config->opt_bool("detect_overhang_wall"); bool has_overhang_reverse = config->opt_bool("overhang_reverse"); bool force_wall_direction = config->opt_enum("wall_direction") != WallDirection::Auto; - bool allow_overhang_reverse = has_detect_overhang_wall && !has_spiral_vase && !force_wall_direction; + bool allow_overhang_reverse = !has_spiral_vase && !force_wall_direction; toggle_field("overhang_reverse", allow_overhang_reverse); + toggle_field("overhang_reverse_threshold", has_detect_overhang_wall); toggle_line("overhang_reverse_threshold", allow_overhang_reverse && has_overhang_reverse); toggle_line("overhang_reverse_internal_only", allow_overhang_reverse && has_overhang_reverse); bool has_overhang_reverse_internal_only = config->opt_bool("overhang_reverse_internal_only"); diff --git a/src/slic3r/GUI/CreatePresetsDialog.cpp b/src/slic3r/GUI/CreatePresetsDialog.cpp index bb784e567d1..6606d5f5f52 100644 --- a/src/slic3r/GUI/CreatePresetsDialog.cpp +++ b/src/slic3r/GUI/CreatePresetsDialog.cpp @@ -47,20 +47,20 @@ static const std::vector filament_vendors = "Duramic", "ELEGOO", "Eryone", "Essentium", "eSUN", "Extrudr", "Fiberforce", "Fiberlogy", "FilaCube", "Filamentive", "Fillamentum", "FLASHFORGE", "Formfutura", "Francofil", "FilamentOne", - "Fil X", "GEEETECH", "Giantarm", "Gizmo Dorks", "GreenGate3D", + "Fil X", "GEEETECH", "Giantarm", "Gizmo Dorks", "GreenGate3D", "HATCHBOX", "Hello3D", "IC3D", "IEMAI", "IIID Max", "INLAND", "iProspect", "iSANMATE", "Justmaker", "Keene Village Plastics", "Kexcelled", "MakerBot", "MatterHackers", "MIKA3D", "NinjaTek", "Nobufil", "Novamaker", "OVERTURE", "OVVNYXE", "Polymaker", "Priline", "Printed Solid", "Protopasta", "Prusament", "Push Plastic", - "R3D", "Re-pet3D", "Recreus", "Regen", "Sain SMART", - "SliceWorx", "Snapmaker", "SnoLabs", "Spectrum", "SUNLU", - "TTYT3D", "Tianse", "UltiMaker", "Valment", "Verbatim", - "VO3D", "Voxelab", "VOXELPLA", "YOOPAI", "Yousu", - "Ziro", "Zyltech"}; + "R3D", "Re-pet3D", "Recreus", "Regen", "RatRig", + "Sain SMART", "SliceWorx", "Snapmaker", "SnoLabs", "Spectrum", + "SUNLU", "TTYT3D", "Tianse", "UltiMaker", "Valment", + "Verbatim", "VO3D", "Voxelab", "VOXELPLA", "YOOPAI", + "Yousu", "Ziro", "Zyltech"}; static const std::vector filament_types = {"PLA", "rPLA", "PLA+", "PLA Tough", "PETG", "ABS", "ASA", "FLEX", "HIPS", "PA", "PACF", - "NYLON", "PVA", "PVB", "PC", "PCABS", "PCTG", "PCCF", "PHA", "PP", "PEI", "PET", "PETG", + "NYLON", "PVA", "PVB", "PC", "PCABS", "PCTG", "PCCF", "PHA", "PP", "PEI", "PET", "PETGCF", "PTBA", "PTBA90A", "PEEK", "TPU93A", "TPU75D", "TPU", "TPU92A", "TPU98A", "Misc", "TPE", "GLAZE", "Nylon", "CPE", "METAL", "ABST", "Carbon Fiber", "SBS"}; diff --git a/src/slic3r/GUI/Field.cpp b/src/slic3r/GUI/Field.cpp index 4347e18cc37..7846949071e 100644 --- a/src/slic3r/GUI/Field.cpp +++ b/src/slic3r/GUI/Field.cpp @@ -248,8 +248,6 @@ bool Field::is_matched(const std::string& string, const std::string& pattern) return std::regex_match(string, regex_pattern); } -static wxString na_value() { return _(L("N/A")); } - void Field::get_value_by_opt_type(wxString& str, const bool check_value/* = true*/) { switch (m_opt.type) { @@ -287,7 +285,7 @@ void Field::get_value_by_opt_type(wxString& str, const bool check_value/* = true } double val; - bool is_na_value = m_opt.nullable && str == na_value(); + bool is_na_value = m_opt.nullable && str == m_na_value; const char dec_sep = is_decimal_separator_point() ? '.' : ','; const char dec_sep_alt = dec_sep == '.' ? ',' : '.'; @@ -827,7 +825,6 @@ bool TextCtrl::value_was_changed() void TextCtrl::propagate_value() { - if (!is_defined_input_value(text_ctrl(), m_opt.type)) { // BBS // on_kill_focus() cause a call of OptionsGroup::reload_config(), // Thus, do it only when it's really needed (when undefined value was input) @@ -840,11 +837,10 @@ void TextCtrl::propagate_value() void TextCtrl::set_value(const boost::any& value, bool change_event/* = false*/) { m_disable_change_event = !change_event; if (m_opt.nullable) { - const bool m_is_na_val = boost::any_cast(value) == na_value(); - if (!m_is_na_val) + if (boost::any_cast(value) != _(L("N/A"))) m_last_meaningful_value = value; - text_ctrl()->SetValue(m_is_na_val ? na_value() : - boost::any_cast(value)); // BBS + + text_ctrl()->SetValue(boost::any_cast(value)); // BBS } else text_ctrl()->SetValue(value.empty() ? "" : boost::any_cast(value)); // BBS // BBS: null value @@ -866,9 +862,14 @@ void TextCtrl::set_last_meaningful_value() propagate_value(); } +void TextCtrl::update_na_value(const boost::any& value) +{ + m_na_value = boost::any_cast(value); +} + void TextCtrl::set_na_value() { - text_ctrl()->SetValue(na_value()); // BBS + text_ctrl()->SetValue(m_na_value); // BBS propagate_value(); } @@ -978,10 +979,16 @@ void CheckBox::set_value(const boost::any& value, bool change_event) { m_disable_change_event = !change_event; if (m_opt.nullable) { - m_is_na_val = boost::any_cast(value) == ConfigOptionBoolsNullable::nil_value(); + const bool is_value_unsigned_char = value.type() == typeid(unsigned char); + m_is_na_val = is_value_unsigned_char && + boost::any_cast(value) == ConfigOptionBoolsNullable::nil_value(); if (!m_is_na_val) - m_last_meaningful_value = value; - dynamic_cast<::CheckBox*>(window)->SetValue(m_is_na_val ? false : boost::any_cast(value) != 0); // BBS + m_last_meaningful_value = is_value_unsigned_char ? value : static_cast(boost::any_cast(value)); + + const auto bool_value = is_value_unsigned_char ? + boost::any_cast(value) != 0 : + boost::any_cast(value); + dynamic_cast<::CheckBox*>(window)->SetValue(m_is_na_val ? false : bool_value); // BBS } else if (!value.empty()) // BBS: null value dynamic_cast<::CheckBox*>(window)->SetValue(boost::any_cast(value)); // BBS diff --git a/src/slic3r/GUI/Field.hpp b/src/slic3r/GUI/Field.hpp index 3b974b7ad0c..2cc89f240a8 100644 --- a/src/slic3r/GUI/Field.hpp +++ b/src/slic3r/GUI/Field.hpp @@ -10,6 +10,7 @@ #include #include #include +#include "I18N.hpp" #include #include @@ -225,6 +226,8 @@ class Field : public UndoValueUIManager { virtual void set_last_meaningful_value() {} virtual void set_na_value() {} + virtual void update_na_value(const boost::any& value) {} + /// Gets a boost::any representing this control. /// subclasses should overload with a specific version virtual boost::any& get_value() = 0; @@ -283,6 +286,7 @@ class Field : public UndoValueUIManager { bool bEnterPressed = false; + wxString m_na_value = _(L("N/A")); friend class OptionsGroup; }; @@ -324,6 +328,8 @@ class TextCtrl : public Field { void set_last_meaningful_value() override; void set_na_value() override; + void update_na_value(const boost::any& value) override; + boost::any& get_value() override; void msw_rescale() override; diff --git a/src/slic3r/GUI/GUI_App.cpp b/src/slic3r/GUI/GUI_App.cpp index 937d1d9b91b..4415118af5d 100644 --- a/src/slic3r/GUI/GUI_App.cpp +++ b/src/slic3r/GUI/GUI_App.cpp @@ -1908,9 +1908,18 @@ void GUI_App::init_app_config() // Mac : "~/Library/Application Support/Slic3r" if (data_dir().empty()) { - // Orca: check if data_dir folder exists in application folder - // use it if it exists - boost::filesystem::path app_data_dir_path = boost::filesystem::current_path() / "data_dir"; + // Orca: check if data_dir folder exists in application folder use it if it exists + // Note:wxStandardPaths::Get().GetExecutablePath() return following paths + // Unix: /usr/local/bin/exename + // Windows: "C:\Programs\AppFolder\exename.exe" + // Mac: /Applications/exename.app/Contents/MacOS/exename + // TODO: have no idea what to do with Linux bundles + auto _app_folder = boost::filesystem::path(wxStandardPaths::Get().GetExecutablePath().ToUTF8().data()).parent_path(); +#ifdef __APPLE__ + // On macOS, the executable is inside the .app bundle. + _app_folder = _app_folder.parent_path().parent_path().parent_path(); +#endif + boost::filesystem::path app_data_dir_path = _app_folder / "data_dir"; if (boost::filesystem::exists(app_data_dir_path)) { set_data_dir(app_data_dir_path.string()); } diff --git a/src/slic3r/GUI/PartPlate.cpp b/src/slic3r/GUI/PartPlate.cpp index 5617700105e..db8b4856a52 100644 --- a/src/slic3r/GUI/PartPlate.cpp +++ b/src/slic3r/GUI/PartPlate.cpp @@ -2541,59 +2541,9 @@ void PartPlate::generate_print_polygon(ExPolygon &print_polygon) } }; - int points_count = 8; - if (m_shape.size() == 4) - { - //rectangle case - for (int i = 0; i < 4; i++) - { - const Vec2d& p = m_shape[i]; - Vec2d center; - double start_angle, stop_angle, radius_x, radius_y, radius; - switch (i) { - case 0: - radius = 5.f; - center(0) = p(0) + radius; - center(1) = p(1) + radius; - start_angle = PI; - stop_angle = 1.5 * PI; - compute_points(center, radius, start_angle, stop_angle, points_count); - break; - case 1: - print_polygon.contour.append({ scale_(p(0)), scale_(p(1)) }); - break; - case 2: - radius_x = (int)(p(0)) % 10; - radius_y = (int)(p(1)) % 10; - radius = (radius_x > radius_y)?radius_y: radius_x; - if (radius < 5.0) - radius = 5.f; - center(0) = p(0) - radius; - center(1) = p(1) - radius; - start_angle = 0; - stop_angle = 0.5 * PI; - compute_points(center, radius, start_angle, stop_angle, points_count); - break; - case 3: - radius_x = (int)(p(0)) % 10; - radius_y = (int)(p(1)) % 10; - radius = (radius_x > radius_y)?radius_y: radius_x; - if (radius < 5.0) - radius = 5.f; - center(0) = p(0) + radius; - center(1) = p(1) - radius; - start_angle = 0.5 * PI; - stop_angle = PI; - compute_points(center, radius, start_angle, stop_angle, points_count); - break; - } - } - } - else { - for (const Vec2d& p : m_shape) { - print_polygon.contour.append({ scale_(p(0)), scale_(p(1)) }); + for (const Vec2d& p : m_shape) { + print_polygon.contour.append({scale_(p(0)), scale_(p(1))}); } - } } void PartPlate::generate_exclude_polygon(ExPolygon &exclude_polygon) diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index 874dd0fba55..b51937e43cb 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -11686,14 +11686,37 @@ void Plater::export_stl(bool extended, bool selection_only, bool multi_stls) } // Following lambda generates a combined mesh for export with normals pointing outwards. - auto mesh_to_export_fff_no_boolean = [](const ModelObject &mo, int instance_id) { + auto mesh_to_export_fff_no_boolean = [this](const ModelObject &mo, int instance_id) { TriangleMesh mesh; - for (const ModelVolume *v : mo.volumes) - if (v->is_model_part()) { - TriangleMesh vol_mesh(v->mesh()); - vol_mesh.transform(v->get_matrix(), true); - mesh.merge(vol_mesh); - } + + //Prusa export negative parts + std::vector csgmesh; + csgmesh.reserve(2 * mo.volumes.size()); + csg::model_to_csgmesh(mo, Transform3d::Identity(), std::back_inserter(csgmesh), + csg::mpartsPositive | csg::mpartsNegative | csg::mpartsDoSplits); + + auto csgrange = range(csgmesh); + if (csg::is_all_positive(csgrange)) { + mesh = TriangleMesh{csg::csgmesh_merge_positive_parts(csgrange)}; + } else if (std::get<2>(csg::check_csgmesh_booleans(csgrange)) == csgrange.end()) { + try { + auto cgalm = csg::perform_csgmesh_booleans(csgrange); + mesh = MeshBoolean::cgal::cgal_to_triangle_mesh(*cgalm); + } catch (...) {} + } + + if (mesh.empty()) { + get_notification_manager()->push_plater_error_notification( + _u8L("Unable to perform boolean operation on model meshes. " + "Only positive parts will be exported.")); + + for (const ModelVolume* v : mo.volumes) + if (v->is_model_part()) { + TriangleMesh vol_mesh(v->mesh()); + vol_mesh.transform(v->get_matrix(), true); + mesh.merge(vol_mesh); + } + } if (instance_id == -1) { TriangleMesh vols_mesh(mesh); mesh = TriangleMesh(); diff --git a/src/slic3r/GUI/SlicingProgressNotification.cpp b/src/slic3r/GUI/SlicingProgressNotification.cpp index bedcbc2eb7c..e6ea1a312b7 100644 --- a/src/slic3r/GUI/SlicingProgressNotification.cpp +++ b/src/slic3r/GUI/SlicingProgressNotification.cpp @@ -31,14 +31,13 @@ void NotificationManager::SlicingProgressNotification::init() m_endlines.push_back(0); } if (m_lines_count >= 2) { - m_lines_count = 3; + m_lines_count = std::min((size_t)3, m_lines_count); m_multiline = true; - while (m_endlines.size() < 3) + while (m_endlines.size() < m_lines_count) m_endlines.push_back(m_endlines.back()); } else { - m_lines_count = 2; - m_endlines.push_back(m_endlines.back()); + m_lines_count = 1; m_multiline = false; } if (m_state == EState::Shown) @@ -222,7 +221,7 @@ void NotificationManager::SlicingProgressNotification::render(GLCanvas3D& canvas const ImVec2 dailytips_child_window_padding = m_dailytips_panel->is_expanded() ? ImVec2(15.f, 10.f) * scale : ImVec2(15.f, 0.f) * scale; const ImVec2 bottom_padding = ImVec2(0.f, 0.f) * scale; const float progress_panel_width = (m_window_width - 2 * progress_child_window_padding.x); - const float progress_panel_height = (58.0f * scale); + const float progress_panel_height = (58.0f * scale) + (m_lines_count - 1) * m_line_height; const float dailytips_panel_width = (m_window_width - 2 * dailytips_child_window_padding.x); const float gcodeviewer_height = wxGetApp().plater()->get_preview_canvas3D()->get_gcode_viewer().get_legend_height(); //const float dailytips_panel_height = std::min(380.0f * scale, std::max(90.0f, (cnv_size.get_height() - gcodeviewer_height - progress_panel_height - dailytips_child_window_padding.y - initial_y - m_line_height * 4))); @@ -272,11 +271,12 @@ void NotificationManager::SlicingProgressNotification::render(GLCanvas3D& canvas if (ImGui::BeginChild(child_name.c_str(), ImVec2(progress_panel_width, progress_panel_height), false, child_window_flags)) { ImVec2 child_window_pos = ImGui::GetWindowPos(); ImVec2 button_size = ImVec2(38.f, 38.f) * scale; - ImVec2 button_pos = child_window_pos + ImVec2(progress_panel_width - button_size.x, (progress_panel_height - button_size.y) / 2.0f); float margin_x = 8.0f * scale; - ImVec2 progress_bar_pos = child_window_pos + ImVec2(0, progress_panel_height / 2.0f); ImVec2 progress_bar_size = ImVec2(progress_panel_width - button_size.x - margin_x, 4.0f * scale); - ImVec2 text_pos = ImVec2(progress_bar_pos.x, progress_bar_pos.y - m_line_height * 1.2f); + float text_bottom = progress_bar_size.y + m_line_height * 1.2f + 7.f * scale; + ImVec2 progress_bar_pos = child_window_pos + ImVec2(0, progress_panel_height - text_bottom); + ImVec2 button_pos = child_window_pos + ImVec2(progress_panel_width - button_size.x, progress_panel_height - text_bottom - button_size.y / 2.0f); + ImVec2 text_pos = ImVec2(progress_bar_pos.x, progress_bar_pos.y - m_line_height * (1.2f + m_lines_count - 1)); render_text(text_pos); render_close_button(button_pos, button_size); @@ -353,9 +353,13 @@ void Slic3r::GUI::NotificationManager::SlicingProgressNotification::render_text( imgui.pop_bold_font(); } if(m_sp_state == SlicingProgressState::SP_PROGRESS) { - //one line text - ImGui::SetCursorScreenPos(pos); - imgui.text(m_text1.substr(0, m_endlines[0]).c_str()); + // multi-line text + int last_end = 0; + for (auto i = 0; i < m_lines_count; i++) { + ImGui::SetCursorScreenPos(pos + ImVec2(0, i * m_line_height)); + imgui.text(m_text1.substr(last_end, m_endlines[i] - last_end).c_str()); + last_end = m_endlines[i]; + } } } diff --git a/src/slic3r/GUI/Tab.cpp b/src/slic3r/GUI/Tab.cpp index ad4e49b899e..b87545acc4d 100644 --- a/src/slic3r/GUI/Tab.cpp +++ b/src/slic3r/GUI/Tab.cpp @@ -3119,15 +3119,25 @@ void TabFilament::add_filament_overrides_page() line.near_label_widget = [this, optgroup_wk = ConfigOptionsGroupWkp(optgroup), opt_key, opt_index](wxWindow* parent) { wxCheckBox* check_box = new wxCheckBox(parent, wxID_ANY, ""); - check_box->Bind(wxEVT_CHECKBOX, [optgroup_wk, opt_key, opt_index](wxCommandEvent& evt) { + check_box->Bind( + wxEVT_CHECKBOX, + [this, optgroup_wk, opt_key, opt_index](wxCommandEvent& evt) { const bool is_checked = evt.IsChecked(); if (auto optgroup_sh = optgroup_wk.lock(); optgroup_sh) { if (Field *field = optgroup_sh->get_fieldc(opt_key, opt_index); field != nullptr) { field->toggle(is_checked); - if (is_checked) + + if (is_checked) { + field->update_na_value(_(L("N/A"))); field->set_last_meaningful_value(); - else + } + else { + const std::string printer_opt_key = opt_key.substr(strlen("filament_")); + const auto printer_config = m_preset_bundle->printers.get_edited_preset().config; + const boost::any printer_config_value = optgroup_sh->get_config_value(printer_config, printer_opt_key, opt_index); + field->update_na_value(printer_config_value); field->set_na_value(); + } } } }, check_box->GetId()); @@ -3164,7 +3174,7 @@ void TabFilament::add_filament_overrides_page() append_single_option_line(opt_key, extruder_idx); } -void TabFilament::update_filament_overrides_page() +void TabFilament::update_filament_overrides_page(const DynamicPrintConfig* printers_config) { if (!m_active_page || m_active_page->title() != "Setting Overrides") return; @@ -3215,22 +3225,30 @@ void TabFilament::update_filament_overrides_page() m_overrides_options[opt_key]->SetValue(is_checked); Field* field = optgroup->get_fieldc(opt_key, extruder_idx); - if (field != nullptr) { - if (opt_key == "filament_long_retractions_when_cut") { - int machine_enabled_level = wxGetApp().preset_bundle->printers.get_edited_preset().config.option("enable_long_retraction_when_cut")->value; - bool machine_enabled = machine_enabled_level == LongRectrationLevel::EnableFilament; - toggle_line(opt_key, machine_enabled); - field->toggle(is_checked && machine_enabled); - } - else if (opt_key == "filament_retraction_distances_when_cut") { - int machine_enabled_level = wxGetApp().preset_bundle->printers.get_edited_preset().config.option("enable_long_retraction_when_cut")->value; - bool machine_enabled = machine_enabled_level == LongRectrationLevel::EnableFilament; - bool filament_enabled = m_config->option("filament_long_retractions_when_cut")->values[extruder_idx] == 1; - toggle_line(opt_key, filament_enabled && machine_enabled); - field->toggle(is_checked && filament_enabled && machine_enabled); + if (field == nullptr) continue; + + if (opt_key == "filament_long_retractions_when_cut") { + int machine_enabled_level = printers_config->option( + "enable_long_retraction_when_cut")->value; + bool machine_enabled = machine_enabled_level == LongRectrationLevel::EnableFilament; + toggle_line(opt_key, machine_enabled); + field->toggle(is_checked && machine_enabled); + } else if (opt_key == "filament_retraction_distances_when_cut") { + int machine_enabled_level = printers_config->option( + "enable_long_retraction_when_cut")->value; + bool machine_enabled = machine_enabled_level == LongRectrationLevel::EnableFilament; + bool filament_enabled = m_config->option("filament_long_retractions_when_cut")->values[extruder_idx] == 1; + toggle_line(opt_key, filament_enabled && machine_enabled); + field->toggle(is_checked && filament_enabled && machine_enabled); + } else { + if (!is_checked) { + const std::string printer_opt_key = opt_key.substr(strlen("filament_")); + boost::any printer_config_value = optgroup->get_config_value(*printers_config, printer_opt_key, extruder_idx); + field->update_na_value(printer_config_value); + field->set_value(printer_config_value, false); } - else - field->toggle(is_checked); + + field->toggle(is_checked); } } } @@ -3604,7 +3622,7 @@ void TabFilament::toggle_options() toggle_line("filament_diameter", !is_pellet_printer); } if (m_active_page->title() == L("Setting Overrides")) - update_filament_overrides_page(); + update_filament_overrides_page(&cfg); if (m_active_page->title() == L("Multimaterial")) { // Orca: hide specific settings for BBL printers diff --git a/src/slic3r/GUI/Tab.hpp b/src/slic3r/GUI/Tab.hpp index 0550c6291d8..cab9990bb46 100644 --- a/src/slic3r/GUI/Tab.hpp +++ b/src/slic3r/GUI/Tab.hpp @@ -554,7 +554,7 @@ class TabFilament : public Tab ogStaticText* m_cooling_description_line {nullptr}; void add_filament_overrides_page(); - void update_filament_overrides_page(); + void update_filament_overrides_page(const DynamicPrintConfig* printers_config); void update_volumetric_flow_preset_hints(); std::map m_overrides_options; diff --git a/version.inc b/version.inc index 07b3c348624..983f3250b79 100644 --- a/version.inc +++ b/version.inc @@ -10,7 +10,7 @@ endif() if(NOT DEFINED BBL_INTERNAL_TESTING) set(BBL_INTERNAL_TESTING "0") endif() -set(SoftFever_VERSION "2.2.0-beta2") +set(SoftFever_VERSION "2.2.0-rc") string(REGEX MATCH "^([0-9]+)\\.([0-9]+)\\.([0-9]+)" SoftFever_VERSION_MATCH ${SoftFever_VERSION}) set(ORCA_VERSION_MAJOR ${CMAKE_MATCH_1})