1 // #include "libslic3r/GCodeSender.hpp"
2 #include "ConfigManipulation.hpp"
3 #include "I18N.hpp"
4 #include "GUI_App.hpp"
5 #include "format.hpp"
6 #include "libslic3r/Model.hpp"
7 #include "libslic3r/PresetBundle.hpp"
8 
9 #include <wx/msgdlg.h>
10 
11 namespace Slic3r {
12 namespace GUI {
13 
apply(DynamicPrintConfig * config,DynamicPrintConfig * new_config)14 void ConfigManipulation::apply(DynamicPrintConfig* config, DynamicPrintConfig* new_config)
15 {
16     bool modified = false;
17     for (auto opt_key : config->diff(*new_config)) {
18         config->set_key_value(opt_key, new_config->option(opt_key)->clone());
19         modified = true;
20     }
21 
22     if (modified && load_config != nullptr)
23         load_config();
24 }
25 
toggle_field(const std::string & opt_key,const bool toggle,int opt_index)26 void ConfigManipulation::toggle_field(const std::string& opt_key, const bool toggle, int opt_index/* = -1*/)
27 {
28     if (local_config) {
29         if (local_config->option(opt_key) == nullptr)
30             return;
31     }
32     cb_toggle_field(opt_key, toggle, opt_index);
33 }
34 
update_print_fff_config(DynamicPrintConfig * config,const bool is_global_config)35 void ConfigManipulation::update_print_fff_config(DynamicPrintConfig* config, const bool is_global_config)
36 {
37     // #ys_FIXME_to_delete
38     //! Temporary workaround for the correct updates of the TextCtrl (like "layer_height"):
39     // KillFocus() for the wxSpinCtrl use CallAfter function. So,
40     // to except the duplicate call of the update() after dialog->ShowModal(),
41     // let check if this process is already started.
42     if (is_msg_dlg_already_exist)
43         return;
44 
45     // layer_height shouldn't be equal to zero
46     if (config->opt_float("layer_height") < EPSILON)
47     {
48         const wxString msg_text = _(L("Zero layer height is not valid.\n\nThe layer height will be reset to 0.01."));
49         wxMessageDialog dialog(nullptr, msg_text, _(L("Layer height")), wxICON_WARNING | wxOK);
50         DynamicPrintConfig new_conf = *config;
51         is_msg_dlg_already_exist = true;
52         dialog.ShowModal();
53         new_conf.set_key_value("layer_height", new ConfigOptionFloat(0.01));
54         apply(config, &new_conf);
55         is_msg_dlg_already_exist = false;
56     }
57 
58     if (fabs(config->option<ConfigOptionFloatOrPercent>("first_layer_height")->value - 0) < EPSILON)
59     {
60         const wxString msg_text = _(L("Zero first layer height is not valid.\n\nThe first layer height will be reset to 0.01."));
61         wxMessageDialog dialog(nullptr, msg_text, _(L("First layer height")), wxICON_WARNING | wxOK);
62         DynamicPrintConfig new_conf = *config;
63         is_msg_dlg_already_exist = true;
64         dialog.ShowModal();
65         new_conf.set_key_value("first_layer_height", new ConfigOptionFloatOrPercent(0.01, false));
66         apply(config, &new_conf);
67         is_msg_dlg_already_exist = false;
68     }
69 
70     double fill_density = config->option<ConfigOptionPercent>("fill_density")->value;
71 
72     if (config->opt_bool("spiral_vase") &&
73         ! (config->opt_int("perimeters") == 1 &&
74            config->opt_int("top_solid_layers") == 0 &&
75            fill_density == 0 &&
76            ! config->opt_bool("support_material") &&
77            config->opt_int("support_material_enforce_layers") == 0 &&
78            config->opt_bool("ensure_vertical_shell_thickness") &&
79            ! config->opt_bool("thin_walls")))
80     {
81         wxString msg_text = _(L("The Spiral Vase mode requires:\n"
82                                 "- one perimeter\n"
83                                 "- no top solid layers\n"
84                                 "- 0% fill density\n"
85                                 "- no support material\n"
86                                 "- Ensure vertical shell thickness enabled\n"
87                					"- Detect thin walls disabled"));
88         if (is_global_config)
89             msg_text += "\n\n" + _(L("Shall I adjust those settings in order to enable Spiral Vase?"));
90         wxMessageDialog dialog(nullptr, msg_text, _(L("Spiral Vase")),
91                                wxICON_WARNING | (is_global_config ? wxYES | wxNO : wxOK));
92         DynamicPrintConfig new_conf = *config;
93         auto answer = dialog.ShowModal();
94         if (!is_global_config || answer == wxID_YES) {
95             new_conf.set_key_value("perimeters", new ConfigOptionInt(1));
96             new_conf.set_key_value("top_solid_layers", new ConfigOptionInt(0));
97             new_conf.set_key_value("fill_density", new ConfigOptionPercent(0));
98             new_conf.set_key_value("support_material", new ConfigOptionBool(false));
99             new_conf.set_key_value("support_material_enforce_layers", new ConfigOptionInt(0));
100             new_conf.set_key_value("ensure_vertical_shell_thickness", new ConfigOptionBool(true));
101             new_conf.set_key_value("thin_walls", new ConfigOptionBool(false));
102             fill_density = 0;
103         }
104         else {
105             new_conf.set_key_value("spiral_vase", new ConfigOptionBool(false));
106         }
107         apply(config, &new_conf);
108         if (cb_value_change)
109             cb_value_change("fill_density", fill_density);
110     }
111 
112     if (config->opt_bool("wipe_tower") && config->opt_bool("support_material") &&
113         config->opt_float("support_material_contact_distance") > 0. &&
114         (config->opt_int("support_material_extruder") != 0 || config->opt_int("support_material_interface_extruder") != 0)) {
115         wxString msg_text = _(L("The Wipe Tower currently supports the non-soluble supports only\n"
116                                 "if they are printed with the current extruder without triggering a tool change.\n"
117                                 "(both support_material_extruder and support_material_interface_extruder need to be set to 0)."));
118         if (is_global_config)
119             msg_text += "\n\n" + _(L("Shall I adjust those settings in order to enable the Wipe Tower?"));
120         wxMessageDialog dialog (nullptr, msg_text, _(L("Wipe Tower")),
121                                 wxICON_WARNING | (is_global_config ? wxYES | wxNO : wxOK));
122         DynamicPrintConfig new_conf = *config;
123         auto answer = dialog.ShowModal();
124         if (!is_global_config || answer == wxID_YES) {
125             new_conf.set_key_value("support_material_extruder", new ConfigOptionInt(0));
126             new_conf.set_key_value("support_material_interface_extruder", new ConfigOptionInt(0));
127         }
128         else
129             new_conf.set_key_value("wipe_tower", new ConfigOptionBool(false));
130         apply(config, &new_conf);
131     }
132 
133     if (config->opt_bool("wipe_tower") && config->opt_bool("support_material") &&
134         config->opt_float("support_material_contact_distance") == 0 &&
135         !config->opt_bool("support_material_synchronize_layers")) {
136         wxString msg_text = _(L("For the Wipe Tower to work with the soluble supports, the support layers\n"
137                                 "need to be synchronized with the object layers."));
138         if (is_global_config)
139             msg_text += "\n\n" + _(L("Shall I synchronize support layers in order to enable the Wipe Tower?"));
140         wxMessageDialog dialog(nullptr, msg_text, _(L("Wipe Tower")),
141                                wxICON_WARNING | (is_global_config ? wxYES | wxNO : wxOK));
142         DynamicPrintConfig new_conf = *config;
143         auto answer = dialog.ShowModal();
144         if (!is_global_config || answer == wxID_YES) {
145             new_conf.set_key_value("support_material_synchronize_layers", new ConfigOptionBool(true));
146         }
147         else
148             new_conf.set_key_value("wipe_tower", new ConfigOptionBool(false));
149         apply(config, &new_conf);
150     }
151 
152     static bool support_material_overhangs_queried = false;
153 
154     if (config->opt_bool("support_material")) {
155         // Ask only once.
156         if (!support_material_overhangs_queried) {
157             support_material_overhangs_queried = true;
158             if (!config->opt_bool("overhangs")/* != 1*/) {
159                 wxString msg_text = _(L("Supports work better, if the following feature is enabled:\n"
160                                         "- Detect bridging perimeters"));
161                 if (is_global_config)
162                     msg_text += "\n\n" + _(L("Shall I adjust those settings for supports?"));
163                 wxMessageDialog dialog(nullptr, msg_text, _(L("Support Generator")),
164                                        wxICON_WARNING | (is_global_config ? wxYES | wxNO | wxCANCEL : wxOK));
165                 DynamicPrintConfig new_conf = *config;
166                 auto answer = dialog.ShowModal();
167                 if (!is_global_config || answer == wxID_YES) {
168                     // Enable "detect bridging perimeters".
169                     new_conf.set_key_value("overhangs", new ConfigOptionBool(true));
170                 }
171                 else if (answer == wxID_NO) {
172                     // Do nothing, leave supports on and "detect bridging perimeters" off.
173                 }
174                 else if (answer == wxID_CANCEL) {
175                     // Disable supports.
176                     new_conf.set_key_value("support_material", new ConfigOptionBool(false));
177                     support_material_overhangs_queried = false;
178                 }
179                 apply(config, &new_conf);
180             }
181         }
182     }
183     else {
184         support_material_overhangs_queried = false;
185     }
186 
187     if (config->option<ConfigOptionPercent>("fill_density")->value == 100) {
188         std::string  fill_pattern            = config->option<ConfigOptionEnum<InfillPattern>>("fill_pattern")->serialize();
189         const auto  &top_fill_pattern_values = config->def()->get("top_fill_pattern")->enum_values;
190         bool correct_100p_fill = std::find(top_fill_pattern_values.begin(), top_fill_pattern_values.end(), fill_pattern) != top_fill_pattern_values.end();
191         if (!correct_100p_fill) {
192             // get fill_pattern name from enum_labels for using this one at dialog_msg
193             const ConfigOptionDef *fill_pattern_def = config->def()->get("fill_pattern");
194             assert(fill_pattern_def != nullptr);
195             auto it_pattern = std::find(fill_pattern_def->enum_values.begin(), fill_pattern_def->enum_values.end(), fill_pattern);
196             assert(it_pattern != fill_pattern_def->enum_values.end());
197             if (it_pattern != fill_pattern_def->enum_values.end()) {
198                 wxString msg_text = GUI::format_wxstr(_L("The %1% infill pattern is not supposed to work at 100%% density."),
199                     _(fill_pattern_def->enum_labels[it_pattern - fill_pattern_def->enum_values.begin()]));
200                 if (is_global_config)
201                     msg_text += "\n\n" + _L("Shall I switch to rectilinear fill pattern?");
202                 wxMessageDialog dialog(nullptr, msg_text, _L("Infill"),
203                                                   wxICON_WARNING | (is_global_config ? wxYES | wxNO : wxOK) );
204                 DynamicPrintConfig new_conf = *config;
205                 auto answer = dialog.ShowModal();
206                 if (!is_global_config || answer == wxID_YES) {
207                     new_conf.set_key_value("fill_pattern", new ConfigOptionEnum<InfillPattern>(ipRectilinear));
208                     fill_density = 100;
209                 }
210                 else
211                     fill_density = wxGetApp().preset_bundle->prints.get_selected_preset().config.option<ConfigOptionPercent>("fill_density")->value;
212                 new_conf.set_key_value("fill_density", new ConfigOptionPercent(fill_density));
213                 apply(config, &new_conf);
214                 if (cb_value_change)
215                     cb_value_change("fill_density", fill_density);
216             }
217         }
218     }
219 }
220 
toggle_print_fff_options(DynamicPrintConfig * config)221 void ConfigManipulation::toggle_print_fff_options(DynamicPrintConfig* config)
222 {
223     bool have_perimeters = config->opt_int("perimeters") > 0;
224     for (auto el : { "extra_perimeters", "ensure_vertical_shell_thickness", "thin_walls", "overhangs",
225                     "seam_position", "external_perimeters_first", "external_perimeter_extrusion_width",
226                     "perimeter_speed", "small_perimeter_speed", "external_perimeter_speed" })
227         toggle_field(el, have_perimeters);
228 
229     bool have_infill = config->option<ConfigOptionPercent>("fill_density")->value > 0;
230     // infill_extruder uses the same logic as in Print::extruders()
231     for (auto el : { "fill_pattern", "infill_every_layers", "infill_only_where_needed",
232                     "solid_infill_every_layers", "solid_infill_below_area", "infill_extruder", "infill_anchor_max" })
233         toggle_field(el, have_infill);
234     // Only allow configuration of open anchors if the anchoring is enabled.
235     bool has_infill_anchors = have_infill && config->option<ConfigOptionFloatOrPercent>("infill_anchor_max")->value > 0;
236     toggle_field("infill_anchor", has_infill_anchors);
237 
238     bool has_spiral_vase         = config->opt_bool("spiral_vase");
239     bool has_top_solid_infill 	 = config->opt_int("top_solid_layers") > 0;
240     bool has_bottom_solid_infill = config->opt_int("bottom_solid_layers") > 0;
241     bool has_solid_infill 		 = has_top_solid_infill || has_bottom_solid_infill;
242     // solid_infill_extruder uses the same logic as in Print::extruders()
243     for (auto el : { "top_fill_pattern", "bottom_fill_pattern", "infill_first", "solid_infill_extruder",
244                     "solid_infill_extrusion_width", "solid_infill_speed" })
245         toggle_field(el, has_solid_infill);
246 
247     for (auto el : { "fill_angle", "bridge_angle", "infill_extrusion_width",
248                     "infill_speed", "bridge_speed" })
249         toggle_field(el, have_infill || has_solid_infill);
250 
251     toggle_field("top_solid_min_thickness", ! has_spiral_vase && has_top_solid_infill);
252     toggle_field("bottom_solid_min_thickness", ! has_spiral_vase && has_bottom_solid_infill);
253 
254     // Gap fill is newly allowed in between perimeter lines even for empty infill (see GH #1476).
255     toggle_field("gap_fill_speed", have_perimeters);
256 
257     for (auto el : { "top_infill_extrusion_width", "top_solid_infill_speed" })
258         toggle_field(el, has_top_solid_infill);
259 
260     bool have_default_acceleration = config->opt_float("default_acceleration") > 0;
261     for (auto el : { "perimeter_acceleration", "infill_acceleration",
262                     "bridge_acceleration", "first_layer_acceleration" })
263         toggle_field(el, have_default_acceleration);
264 
265     bool have_skirt = config->opt_int("skirts") > 0;
266     toggle_field("skirt_height", have_skirt && !config->opt_bool("draft_shield"));
267     for (auto el : { "skirt_distance", "draft_shield", "min_skirt_length" })
268         toggle_field(el, have_skirt);
269 
270     bool have_brim = config->opt_float("brim_width") > 0;
271     // perimeter_extruder uses the same logic as in Print::extruders()
272     toggle_field("perimeter_extruder", have_perimeters || have_brim);
273 
274     bool have_raft = config->opt_int("raft_layers") > 0;
275     bool have_support_material = config->opt_bool("support_material") || have_raft;
276     bool have_support_material_auto = have_support_material && config->opt_bool("support_material_auto");
277     bool have_support_interface = config->opt_int("support_material_interface_layers") > 0;
278     bool have_support_soluble = have_support_material && config->opt_float("support_material_contact_distance") == 0;
279     for (auto el : { "support_material_pattern", "support_material_with_sheath",
280                     "support_material_spacing", "support_material_angle", "support_material_interface_layers",
281                     "dont_support_bridges", "support_material_extrusion_width", "support_material_contact_distance",
282                     "support_material_xy_spacing" })
283         toggle_field(el, have_support_material);
284     toggle_field("support_material_threshold", have_support_material_auto);
285 
286     for (auto el : { "support_material_interface_spacing", "support_material_interface_extruder",
287                     "support_material_interface_speed", "support_material_interface_contact_loops" })
288         toggle_field(el, have_support_material && have_support_interface);
289     toggle_field("support_material_synchronize_layers", have_support_soluble);
290 
291     toggle_field("perimeter_extrusion_width", have_perimeters || have_skirt || have_brim);
292     toggle_field("support_material_extruder", have_support_material || have_skirt);
293     toggle_field("support_material_speed", have_support_material || have_brim || have_skirt);
294 
295     bool has_ironing = config->opt_bool("ironing");
296     for (auto el : { "ironing_type", "ironing_flowrate", "ironing_spacing", "ironing_speed" })
297     	toggle_field(el, has_ironing);
298 
299     bool have_sequential_printing = config->opt_bool("complete_objects");
300     for (auto el : { "extruder_clearance_radius", "extruder_clearance_height" })
301         toggle_field(el, have_sequential_printing);
302 
303     bool have_ooze_prevention = config->opt_bool("ooze_prevention");
304     toggle_field("standby_temperature_delta", have_ooze_prevention);
305 
306     bool have_wipe_tower = config->opt_bool("wipe_tower");
307     for (auto el : { "wipe_tower_x", "wipe_tower_y", "wipe_tower_width", "wipe_tower_rotation_angle",
308                      "wipe_tower_bridging", "wipe_tower_no_sparse_layers", "single_extruder_multi_material_priming" })
309         toggle_field(el, have_wipe_tower);
310 
311     bool have_avoid_crossing_perimeters = config->opt_bool("avoid_crossing_perimeters");
312     toggle_field("avoid_crossing_perimeters_max_detour", have_avoid_crossing_perimeters);
313 }
314 
update_print_sla_config(DynamicPrintConfig * config,const bool is_global_config)315 void ConfigManipulation::update_print_sla_config(DynamicPrintConfig* config, const bool is_global_config/* = false*/)
316 {
317     double head_penetration = config->opt_float("support_head_penetration");
318     double head_width = config->opt_float("support_head_width");
319     if (head_penetration > head_width) {
320         wxString msg_text = _(L("Head penetration should not be greater than the head width."));
321 
322         wxMessageDialog dialog(nullptr, msg_text, _(L("Invalid Head penetration")), wxICON_WARNING | wxOK);
323         DynamicPrintConfig new_conf = *config;
324         if (dialog.ShowModal() == wxID_OK) {
325             new_conf.set_key_value("support_head_penetration", new ConfigOptionFloat(head_width));
326             apply(config, &new_conf);
327         }
328     }
329 
330     double pinhead_d = config->opt_float("support_head_front_diameter");
331     double pillar_d = config->opt_float("support_pillar_diameter");
332     if (pinhead_d > pillar_d) {
333         wxString msg_text = _(L("Pinhead diameter should be smaller than the pillar diameter."));
334 
335         wxMessageDialog dialog(nullptr, msg_text, _(L("Invalid pinhead diameter")), wxICON_WARNING | wxOK);
336 
337         DynamicPrintConfig new_conf = *config;
338         if (dialog.ShowModal() == wxID_OK) {
339             new_conf.set_key_value("support_head_front_diameter", new ConfigOptionFloat(pillar_d / 2.0));
340             apply(config, &new_conf);
341         }
342     }
343 }
344 
toggle_print_sla_options(DynamicPrintConfig * config)345 void ConfigManipulation::toggle_print_sla_options(DynamicPrintConfig* config)
346 {
347     bool supports_en = config->opt_bool("supports_enable");
348 
349     toggle_field("support_head_front_diameter", supports_en);
350     toggle_field("support_head_penetration", supports_en);
351     toggle_field("support_head_width", supports_en);
352     toggle_field("support_pillar_diameter", supports_en);
353     toggle_field("support_small_pillar_diameter_percent", supports_en);
354     toggle_field("support_max_bridges_on_pillar", supports_en);
355     toggle_field("support_pillar_connection_mode", supports_en);
356     toggle_field("support_buildplate_only", supports_en);
357     toggle_field("support_base_diameter", supports_en);
358     toggle_field("support_base_height", supports_en);
359     toggle_field("support_base_safety_distance", supports_en);
360     toggle_field("support_critical_angle", supports_en);
361     toggle_field("support_max_bridge_length", supports_en);
362     toggle_field("support_max_pillar_link_distance", supports_en);
363     toggle_field("support_points_density_relative", supports_en);
364     toggle_field("support_points_minimal_distance", supports_en);
365 
366     bool pad_en = config->opt_bool("pad_enable");
367 
368     toggle_field("pad_wall_thickness", pad_en);
369     toggle_field("pad_wall_height", pad_en);
370     toggle_field("pad_brim_size", pad_en);
371     toggle_field("pad_max_merge_distance", pad_en);
372  // toggle_field("pad_edge_radius", supports_en);
373     toggle_field("pad_wall_slope", pad_en);
374     toggle_field("pad_around_object", pad_en);
375     toggle_field("pad_around_object_everywhere", pad_en);
376 
377     bool zero_elev = config->opt_bool("pad_around_object") && pad_en;
378 
379     toggle_field("support_object_elevation", supports_en && !zero_elev);
380     toggle_field("pad_object_gap", zero_elev);
381     toggle_field("pad_around_object_everywhere", zero_elev);
382     toggle_field("pad_object_connector_stride", zero_elev);
383     toggle_field("pad_object_connector_width", zero_elev);
384     toggle_field("pad_object_connector_penetration", zero_elev);
385 }
386 
387 
388 } // GUI
389 } // Slic3r
390