// #include "libslic3r/GCodeSender.hpp" #include "ConfigManipulation.hpp" #include "I18N.hpp" #include "GUI_App.hpp" #include "Plater.hpp" #include "PartPlate.hpp" #include "format.hpp" #include "libslic3r/Model.hpp" #include "libslic3r/PresetBundle.hpp" #include "MsgDialog.hpp" #include #include #include namespace Slic3r { namespace GUI { void ConfigManipulation::apply(DynamicPrintConfig* config, DynamicPrintConfig* new_config) { bool modified = false; m_applying_keys = config->diff(*new_config); for (auto opt_key : m_applying_keys) { config->set_key_value(opt_key, new_config->option(opt_key)->clone()); modified = true; } if (modified && load_config != nullptr) load_config(); m_applying_keys.clear(); } bool ConfigManipulation::is_applying() const { return is_msg_dlg_already_exist; } t_config_option_keys const &ConfigManipulation::applying_keys() const { return m_applying_keys; } void ConfigManipulation::toggle_field(const std::string &opt_key, const bool toggle, int opt_index /* = -1*/) { if (local_config) { if (local_config->option(opt_key) == nullptr) return; } cb_toggle_field(opt_key, toggle, opt_index); } void ConfigManipulation::toggle_line(const std::string& opt_key, const bool toggle, int opt_index) { if (local_config) { if (local_config->option(opt_key) == nullptr) return; } if (cb_toggle_line) cb_toggle_line(opt_key, toggle, opt_index); } void ConfigManipulation::check_nozzle_recommended_temperature_range(DynamicPrintConfig *config) { if (is_msg_dlg_already_exist) return; int temperature_range_low, temperature_range_high; if (!get_temperature_range(config, temperature_range_low, temperature_range_high)) return; wxString msg_text; bool need_check = false; if (temperature_range_low < 190 || temperature_range_high > 300) { msg_text += _L("The recommended minimum temperature is less than 190 degree or the recommended maximum temperature is greater than 300 degree.\n"); need_check = true; } if (temperature_range_low > temperature_range_high) { msg_text += _L("The recommended minimum temperature cannot be higher than the recommended maximum temperature.\n"); need_check = true; } if (need_check) { msg_text += _L("Please check.\n"); MessageDialog dialog(m_msg_dlg_parent, msg_text, "", wxICON_WARNING | wxOK); is_msg_dlg_already_exist = true; dialog.ShowModal(); is_msg_dlg_already_exist = false; } } void ConfigManipulation::check_nozzle_temperature_range(DynamicPrintConfig *config) { if (is_msg_dlg_already_exist) return; int temperature_range_low, temperature_range_high; if (!get_temperature_range(config, temperature_range_low, temperature_range_high)) return; if (config->has("nozzle_temperature")) { if (config->opt_int_nullable("nozzle_temperature", 0) < temperature_range_low || config->opt_int_nullable("nozzle_temperature", 0) > temperature_range_high) { wxString msg_text = _(L("Nozzle may be blocked when the temperature is out of recommended range.\n" "Please confirm whether to use the temperature for printing.\n\n")); msg_text += wxString::Format(_L("Recommended nozzle temperature of this filament type is [%d, %d] degree centigrade"), temperature_range_low, temperature_range_high); MessageDialog dialog(m_msg_dlg_parent, msg_text, "", wxICON_WARNING | wxOK); is_msg_dlg_already_exist = true; dialog.ShowModal(); is_msg_dlg_already_exist = false; } } } void ConfigManipulation::check_nozzle_temperature_initial_layer_range(DynamicPrintConfig* config) { if (is_msg_dlg_already_exist) return; int temperature_range_low, temperature_range_high; if (!get_temperature_range(config, temperature_range_low, temperature_range_high)) return; if (config->has("nozzle_temperature_initial_layer")) { if (config->opt_int_nullable("nozzle_temperature_initial_layer", 0) < temperature_range_low || config->opt_int_nullable("nozzle_temperature_initial_layer", 0) > temperature_range_high) { wxString msg_text = _(L("Nozzle may be blocked when the temperature is out of recommended range.\n" "Please confirm whether to use the temperature for printing.\n\n")); msg_text += wxString::Format(_L("Recommended nozzle temperature of this filament type is [%d, %d] degree centigrade"), temperature_range_low, temperature_range_high); MessageDialog dialog(m_msg_dlg_parent, msg_text, "", wxICON_WARNING | wxOK); is_msg_dlg_already_exist = true; dialog.ShowModal(); is_msg_dlg_already_exist = false; } } } void ConfigManipulation::check_filament_max_volumetric_speed(DynamicPrintConfig *config) { //if (is_msg_dlg_already_exist) return; //float max_volumetric_speed = config->opt_float("filament_max_volumetric_speed"); float max_volumetric_speed = config->has("filament_max_volumetric_speed") ? config->opt_float_nullable("filament_max_volumetric_speed", (float) 0.5) : 0.5; // BBS: limite the min max_volumetric_speed if (max_volumetric_speed < 0.5 && wxGetApp().initialized()) { const wxString msg_text = _(L("Too small max volumetric speed.\nReset to 0.5")); MessageDialog dialog(nullptr, msg_text, "", wxICON_WARNING | wxOK); DynamicPrintConfig new_conf = *config; is_msg_dlg_already_exist = true; dialog.ShowModal(); new_conf.set_key_value("filament_max_volumetric_speed", new ConfigOptionFloatsNullable({0.5})); apply(config, &new_conf); is_msg_dlg_already_exist = false; } } void ConfigManipulation::check_filament_scarf_setting(DynamicPrintConfig *config) { bool post_warning = false; float layer_height = wxGetApp().preset_bundle->prints.get_selected_preset().config.opt_float("layer_height"); DynamicPrintConfig new_conf = *config; //std::vector new_data = config->option("filament_scarf_height")->values; for (size_t i = 0; i < config->option("filament_scarf_height")->size(); i++) { double value = config->option("filament_scarf_height")->values[i].get_abs_value(1); bool reset = false; if (config->option("filament_scarf_height")->values[i].percent) { if (value >= 1) reset = true; } else if (value > layer_height) reset = true; if (reset && wxGetApp().initialized()) { post_warning = true; new_conf.set_key_value("filament_scarf_height", new ConfigOptionFloatsOrPercents{FloatOrPercent(10, true)}); } } if (post_warning) { const wxString msg_text = _(L("Should not large than 100%.\nReset to default")); MessageDialog dialog(nullptr, msg_text, "", wxICON_WARNING | wxOK); is_msg_dlg_already_exist = true; dialog.ShowModal(); apply(config, &new_conf); is_msg_dlg_already_exist = false; } } // The top/bottom shell keeps growing until both the layer count and the thickness criteria are // satisfied (see PrintObject::discover_vertical_shells), so the shell that really gets printed is // the larger of the two. A zero thickness disables the thickness criterion. static int effective_shell_layers(const DynamicPrintConfig *config, const char *layers_key, const char *thickness_key) { const int shell_layers = config->opt_int(layers_key); if (shell_layers <= 0) return 0; const double thickness = config->opt_float(thickness_key); const double layer_height = config->opt_float("layer_height"); // Mirrors the "print_z difference < thickness - EPSILON" test the slicer uses to stop growing. const int layers_from_thickness = (thickness > EPSILON && layer_height > EPSILON) ? int(std::ceil((thickness - EPSILON) / layer_height)) : 0; return std::max(shell_layers, layers_from_thickness); } // Painted top/bottom color is projected into the shell layer by layer, so a penetration deeper // than the printed shell would color the sparse infill and fail to print. // Use the actual printed shell depth, which is the larger of the configured layer count and the // thickness-derived layer count. void ConfigManipulation::check_color_penetration_layers(DynamicPrintConfig *config, const std::string &edited_key) { struct ShellSide { const char *penetration_key; const char *layers_key; const char *thickness_key; }; static const ShellSide shell_sides[] = { {"top_color_penetration_layers", "top_shell_layers", "top_shell_thickness"}, {"bottom_color_penetration_layers", "bottom_shell_layers", "bottom_shell_thickness"}, }; DynamicPrintConfig new_conf = *config; bool clamped = false; bool show_dlg = false; int dlg_max_layers = 0; wxString dlg_label; for (const ShellSide &side : shell_sides) { if (!config->has(side.penetration_key) || !config->has(side.layers_key)) continue; const int max_layers = effective_shell_layers(config, side.layers_key, side.thickness_key); // An open top/bottom has no solid shell to penetrate into, leave the value alone. if (max_layers <= 0 || config->opt_int(side.penetration_key) <= max_layers) continue; new_conf.set_key_value(side.penetration_key, new ConfigOptionInt(max_layers)); clamped = true; // Warn on any user edit that breaks the pair, including shrinking the shell. if (edited_key == side.penetration_key || edited_key == side.layers_key || edited_key == side.thickness_key || edited_key == "layer_height") { show_dlg = true; dlg_max_layers = max_layers; dlg_label = _(print_config_def.get(side.penetration_key)->label); } } if (clamped) apply(config, &new_conf); if (show_dlg) show_error(m_msg_dlg_parent, _L("Value is out of range.") + "\n" + GUI::format_wxstr(_L("%1% cannot exceed the shell layers (%2%), otherwise the painted color would " "reach the sparse infill."), dlg_label, dlg_max_layers)); } void ConfigManipulation::update_print_fff_config(DynamicPrintConfig* config, const bool is_global_config, const bool is_plate_config) { // #ys_FIXME_to_delete //! Temporary workaround for the correct updates of the TextCtrl (like "layer_height"): // KillFocus() for the wxSpinCtrl use CallAfter function. So, // to except the duplicate call of the update() after dialog->ShowModal(), // let check if this process is already started. if (is_msg_dlg_already_exist) return; bool is_object_config = (!is_global_config && !is_plate_config); // layer_height shouldn't be equal to zero if (config->opt_float("layer_height") < EPSILON) { const wxString msg_text = _(L("Too small layer height.\nReset to 0.2")); MessageDialog dialog(m_msg_dlg_parent, msg_text,"", wxICON_WARNING | wxOK); DynamicPrintConfig new_conf = *config; is_msg_dlg_already_exist = true; dialog.ShowModal(); new_conf.set_key_value("layer_height", new ConfigOptionFloat(0.2)); apply(config, &new_conf); is_msg_dlg_already_exist = false; } //BBS: limite the max layer_herght if (config->opt_float("layer_height") > 0.6 + EPSILON) { const wxString msg_text = _(L("Too large layer height.\nReset to 0.2")); if (wxGetApp().app_config->get("developer_mode") == "true") { MessageDialog dialog(nullptr, msg_text, "", wxICON_WARNING | wxYES_NO); is_msg_dlg_already_exist = true; if (dialog.ShowModal() == wxID_YES) { DynamicPrintConfig new_conf = *config; new_conf.set_key_value("layer_height", new ConfigOptionFloat(0.2)); apply(config, &new_conf); } is_msg_dlg_already_exist = false; } else { MessageDialog dialog(nullptr, msg_text, "", wxICON_WARNING | wxOK); DynamicPrintConfig new_conf = *config; is_msg_dlg_already_exist = true; dialog.ShowModal(); new_conf.set_key_value("layer_height", new ConfigOptionFloat(0.2)); apply(config, &new_conf); is_msg_dlg_already_exist = false; } } //limit scarf start height double seam_slope_start_height = config->option("seam_slope_start_height")->get_abs_value(1); bool reset_slope_start_height = false; if (config->option("seam_slope_start_height")->percent) { if (seam_slope_start_height >= 1) reset_slope_start_height = true; } else { if (seam_slope_start_height >= config->opt_float("layer_height")) reset_slope_start_height = true; } if (reset_slope_start_height) { const wxString msg_text = _(L("Should not be larger than layer height.\nReset to 10%")); MessageDialog dialog(nullptr, msg_text, "", wxICON_WARNING | wxOK); DynamicPrintConfig new_conf = *config; is_msg_dlg_already_exist = true; dialog.ShowModal(); new_conf.set_key_value("seam_slope_start_height", new ConfigOptionFloatOrPercent{10, true}); apply(config, &new_conf); is_msg_dlg_already_exist = false; } //BBS: top_area_threshold showed if the top one wall function be applyed bool top_one_wall_apply = config->opt_enum("top_one_wall_type") == TopOneWallType::None; toggle_line("top_area_threshold", !top_one_wall_apply); //BBS: ironing_spacing shouldn't be too small or equal to zero if (config->opt_float("ironing_spacing") < 0.05) { const wxString msg_text = _(L("Too small ironing spacing.\nReset to 0.1")); MessageDialog dialog(nullptr, msg_text, "", wxICON_WARNING | wxOK); DynamicPrintConfig new_conf = *config; is_msg_dlg_already_exist = true; dialog.ShowModal(); new_conf.set_key_value("ironing_spacing", new ConfigOptionFloat(0.1)); apply(config, &new_conf); is_msg_dlg_already_exist = false; } if (config->opt_float("support_ironing_spacing") < 0.05) { const wxString msg_text = _(L("Too small support ironing spacing.\nReset to 0.1")); MessageDialog dialog(nullptr, msg_text, "", wxICON_WARNING | wxOK); DynamicPrintConfig new_conf = *config; is_msg_dlg_already_exist = true; dialog.ShowModal(); new_conf.set_key_value("support_ironing_spacing", new ConfigOptionFloat(0.1)); apply(config, &new_conf); is_msg_dlg_already_exist = false; } // the ironing only works with solid interface bool has_ironing_support = config->opt_int("raft_layers") > 1 || (config->opt_bool("enable_support") && config->opt_int("support_interface_top_layers") > 0); bool can_ironing_support = config->opt_float("support_interface_spacing") < EPSILON; can_ironing_support &= // where the grid alwasy was sparse, not works with ironing! config->opt_enum("support_interface_pattern") != SupportMaterialInterfacePattern::smipGrid; if (has_ironing_support) if (!can_ironing_support && config->opt_bool("enable_support_ironing")) { const wxString msg_text = _(L("Support ironing will not work with sparse support interface.\n" "Do you want to adjust the support interface config to use ironing?\n" "Yes: make the support interface solid.\n" "No: disable the support ironing.")); MessageDialog dialog(nullptr, msg_text, "", wxICON_WARNING | wxYES_NO); DynamicPrintConfig new_conf = *config; is_msg_dlg_already_exist = true; if (dialog.ShowModal() == wxID_YES) { new_conf.set_key_value("support_interface_spacing", new ConfigOptionFloat(0.0f)); if (config->opt_enum("support_interface_pattern") == SupportMaterialInterfacePattern::smipGrid) new_conf.set_key_value("support_interface_pattern", new ConfigOptionEnum(SupportMaterialInterfacePattern::smipAuto)); } else { new_conf.set_key_value("enable_support_ironing", new ConfigOptionBool(false)); } apply(config, &new_conf); is_msg_dlg_already_exist = false; } if (config->option("initial_layer_print_height")->value < EPSILON) { const wxString msg_text = _(L("Zero initial layer height is invalid.\n\nThe first layer height will be reset to 0.2.")); MessageDialog dialog(m_msg_dlg_parent, msg_text, "", wxICON_WARNING | wxOK); DynamicPrintConfig new_conf = *config; is_msg_dlg_already_exist = true; dialog.ShowModal(); new_conf.set_key_value("initial_layer_print_height", new ConfigOptionFloat(0.2)); apply(config, &new_conf); is_msg_dlg_already_exist = false; } if (abs(config->option("xy_hole_compensation")->value) > 2) { const wxString msg_text = _(L("This setting is only used for model size tuning with small value in some cases.\n" "For example, when the model size has slight errors and is difficult be assembled.\n" "For large size tuning, please use model scaling function.\n\n" "The value will be reset to 0.")); MessageDialog dialog(m_msg_dlg_parent, msg_text, "", wxICON_WARNING | wxOK); DynamicPrintConfig new_conf = *config; is_msg_dlg_already_exist = true; dialog.ShowModal(); new_conf.set_key_value("xy_hole_compensation", new ConfigOptionFloat(0)); apply(config, &new_conf); is_msg_dlg_already_exist = false; } if (abs(config->option("xy_contour_compensation")->value) > 2) { const wxString msg_text = _(L("This setting is only used for model size tuning with small value in some cases.\n" "For example, when the model size has slight errors and is difficult be assembled.\n" "For large size tuning, please use model scaling function.\n\n" "The value will be reset to 0.")); MessageDialog dialog(m_msg_dlg_parent, msg_text, "", wxICON_WARNING | wxOK); DynamicPrintConfig new_conf = *config; is_msg_dlg_already_exist = true; dialog.ShowModal(); new_conf.set_key_value("xy_contour_compensation", new ConfigOptionFloat(0)); apply(config, &new_conf); is_msg_dlg_already_exist = false; } //if enable auto hole and contour compensation, disable the manner if (config->opt_bool("enable_circle_compensation")) { DynamicPrintConfig new_conf = *config; is_msg_dlg_already_exist = true; new_conf.set_key_value("xy_contour_compensation", new ConfigOptionFloat(0)); new_conf.set_key_value("xy_hole_compensation", new ConfigOptionFloat(0)); apply(config, &new_conf); is_msg_dlg_already_exist = false; } if (config->option("elefant_foot_compensation")->value > 1) { const wxString msg_text = _(L("Too large elephant foot compensation is unreasonable.\n" "If really have serious elephant foot effect, please check other settings.\n" "For example, whether bed temperature is too high.\n\n" "The value will be reset to 0.")); MessageDialog dialog(m_msg_dlg_parent, msg_text, "", wxICON_WARNING | wxOK); DynamicPrintConfig new_conf = *config; is_msg_dlg_already_exist = true; dialog.ShowModal(); new_conf.set_key_value("elefant_foot_compensation", new ConfigOptionFloat(0)); apply(config, &new_conf); is_msg_dlg_already_exist = false; } // Interlocking beam width must be > 0 when beam interlocking is enabled (zero causes divide-by-zero in backend). if (config->opt_bool("interlocking_beam") && config->opt_float("interlocking_beam_width") <= 0.) { const wxString msg_text = _(L("Interlocking beam width can't be zero when beam interlocking is enabled.\nReset to 0.01 mm.")); MessageDialog dialog(m_msg_dlg_parent, msg_text, "", wxICON_WARNING | wxOK); DynamicPrintConfig new_conf = *config; is_msg_dlg_already_exist = true; dialog.ShowModal(); new_conf.set_key_value("interlocking_beam_width", new ConfigOptionFloat(0.01)); apply(config, &new_conf); is_msg_dlg_already_exist = false; } if (config->option("enable_wrapping_detection")->value) { std::string printer_type = wxGetApp().preset_bundle->printers.get_edited_preset().get_printer_type(wxGetApp().preset_bundle); if (!DevPrinterConfigUtil::support_wrapping_detection(printer_type)) { DynamicPrintConfig new_conf = *config; new_conf.set_key_value("enable_wrapping_detection", new ConfigOptionBool(false)); apply(config, &new_conf); } } double sparse_infill_density = config->option("sparse_infill_density")->value; int fill_multiline = config->option("fill_multiline")->value; auto timelapse_type = config->opt_enum("timelapse_type"); DynamicPrintConfig *global_config = &wxGetApp().preset_bundle->prints.get_edited_preset().config; bool object_spiral_mode = false; if (is_global_config) { auto plates = wxGetApp().plater()->get_partplate_list().get_plate_list(); for (auto& plate : plates) { if (plate->config()->has("spiral_mode") && plate->config()->opt_bool("spiral_mode")) object_spiral_mode = true; } } bool adjust_spiral_mode_params = false; if (config->opt_bool("spiral_mode") || object_spiral_mode) { adjust_spiral_mode_params = (config->opt_enum("timelapse_type") != TimelapseType::tlTraditional || config->opt_bool("z_direction_outwall_speed_continuous") || config->opt_bool("enable_wrapping_detection")); } if (config->opt_bool("spiral_mode") && !adjust_spiral_mode_params) { adjust_spiral_mode_params = (config->opt_int("wall_loops") != 1 || config->opt_int("top_shell_layers") != 0 || sparse_infill_density != 0 || config->opt_bool("enable_support") || config->opt_int("enforce_support_layers") != 0 || config->opt_bool("detect_thin_wall")); if (!is_global_config && !adjust_spiral_mode_params) { adjust_spiral_mode_params = (global_config->opt_enum("timelapse_type") != TimelapseType::tlTraditional || global_config->opt_bool("z_direction_outwall_speed_continuous") || global_config->opt_bool("enable_wrapping_detection")); } } if (!is_plate_config && adjust_spiral_mode_params) { DynamicPrintConfig new_conf = *config; auto answer = show_spiral_mode_settings_dialog(is_object_config); bool support = true; if (answer == wxID_YES) { if (!is_global_config) { global_config->set_key_value("timelapse_type", new ConfigOptionEnum(tlTraditional)); global_config->set_key_value("z_direction_outwall_speed_continuous", new ConfigOptionBool(false)); global_config->set_key_value("enable_wrapping_detection", new ConfigOptionBool(false)); } new_conf.set_key_value("timelapse_type", new ConfigOptionEnum(tlTraditional)); new_conf.set_key_value("z_direction_outwall_speed_continuous", new ConfigOptionBool(false)); new_conf.set_key_value("enable_wrapping_detection", new ConfigOptionBool(false)); if (config->opt_bool("spiral_mode")) { new_conf.set_key_value("wall_loops", new ConfigOptionInt(1)); new_conf.set_key_value("top_shell_layers", new ConfigOptionInt(0)); new_conf.set_key_value("sparse_infill_density", new ConfigOptionPercent(0)); new_conf.set_key_value("enable_support", new ConfigOptionBool(false)); new_conf.set_key_value("enforce_support_layers", new ConfigOptionInt(0)); new_conf.set_key_value("ensure_vertical_shell_thickness", new ConfigOptionEnum(EnsureVerticalThicknessLevel::evtEnabled)); new_conf.set_key_value("detect_thin_wall", new ConfigOptionBool(false)); new_conf.set_key_value("alternate_extra_wall", new ConfigOptionBool(false)); new_conf.set_key_value("periodic_modifier", new ConfigOptionBool(false)); } timelapse_type = TimelapseType::tlTraditional; if (new_conf.has("sparse_infill_density") && new_conf.option("sparse_infill_density") == 0) sparse_infill_density = 0; if (new_conf.has("enable_support") && !new_conf.opt_bool("enable_support")) support = false; } else { new_conf.set_key_value("spiral_mode", new ConfigOptionBool(false)); if (object_spiral_mode) { auto plates = wxGetApp().plater()->get_partplate_list().get_plate_list(); for (auto &plate : plates) { if (plate->config()->has("spiral_mode") && plate->config()->opt_bool("spiral_mode")) plate->config()->set_key_value("spiral_mode", new ConfigOptionBool(false)); } } } apply(config, &new_conf); if (cb_value_change) { cb_value_change("sparse_infill_density", sparse_infill_density); int timelapse_type_int = (int)timelapse_type; cb_value_change("timelapse_type", timelapse_type_int); if (!support) cb_value_change("enable_support", false); } is_msg_dlg_already_exist = false; } if (is_global_config && (config->opt_int("wall_filament") || config->opt_int("sparse_infill_filament") || config->opt_int("solid_infill_filament") )) { DynamicPrintConfig new_conf = *config; new_conf.set_key_value("wall_filament", new ConfigOptionInt(0)); new_conf.set_key_value("sparse_infill_filament", new ConfigOptionInt(0)); new_conf.set_key_value("solid_infill_filament", new ConfigOptionInt(0)); apply(config, &new_conf); } //BBS //if (config->opt_enum("wall_generator") == PerimeterGeneratorType::Arachne && // config->opt_bool("enable_overhang_speed")) //{ // wxString msg_text = _(L("Arachne engine only works when overhang slowing down is disabled.\n" // "This may cause decline in the quality of overhang surface when print fastly")) + "\n"; // if (is_global_config) // msg_text += "\n" + _(L("Disable overhang slowing down automatically? \n" // "Yes - Enable arachne and disable overhang slowing down\n" // "No - Give up using arachne this time")); // MessageDialog dialog(m_msg_dlg_parent, msg_text, "", // wxICON_WARNING | (is_global_config ? wxYES | wxNO : wxOK)); // DynamicPrintConfig new_conf = *config; // is_msg_dlg_already_exist = true; // auto answer = dialog.ShowModal(); // bool enable_overhang_slow_down = true; // if (!is_global_config || answer == wxID_YES) { // new_conf.set_key_value("enable_overhang_speed", new ConfigOptionBool(false)); // enable_overhang_slow_down = false; // } // else { // new_conf.set_key_value("wall_generator", new ConfigOptionEnum(PerimeterGeneratorType::Classic)); // } // apply(config, &new_conf); // if (cb_value_change) { // if (!enable_overhang_slow_down) // cb_value_change("enable_overhang_speed", false); // } // is_msg_dlg_already_exist = false; //} // Fuzzy skin Extrusion/Combined modes require Arachne wall generator if (config->opt_enum("fuzzy_skin") != FuzzySkinType::Disabled_fuzzy && config->opt_enum("wall_generator") == PerimeterGeneratorType::Classic && (config->opt_enum("fuzzy_skin_mode") == FuzzySkinMode::Extrusion || config->opt_enum("fuzzy_skin_mode") == FuzzySkinMode::Combined)) { const wxString msg_text = _(L("Fuzzy skin [Extrusion] and [Combined] modes require the Arachne wall generator.\n\n" "Do you want to change these settings automatically?\n" "Yes - Enable Arachne wall generator\n" "No - Keep current wall generator and set fuzzy skin to [Displacement] mode")); MessageDialog dialog(m_msg_dlg_parent, msg_text, "", wxICON_WARNING | wxYES_NO); DynamicPrintConfig new_conf = *config; is_msg_dlg_already_exist = true; if (dialog.ShowModal() == wxID_YES) { new_conf.set_key_value("wall_generator", new ConfigOptionEnum(PerimeterGeneratorType::Arachne)); } else { new_conf.set_key_value("fuzzy_skin_mode", new ConfigOptionEnum(FuzzySkinMode::Displacement)); } apply(config, &new_conf); is_msg_dlg_already_exist = false; } // BBS int filament_cnt = wxGetApp().preset_bundle->filament_presets.size(); #if 0 bool has_wipe_tower = filament_cnt > 1 && config->opt_bool("enable_prime_tower"); if (has_wipe_tower && (config->opt_bool("adaptive_layer_height") || config->opt_bool("independent_support_layer_height"))) { wxString msg_text; if (config->opt_bool("adaptive_layer_height") && config->opt_bool("independent_support_layer_height")) { msg_text = _(L("Prime tower does not work when Adaptive Layer Height or Independent Support Layer Height is on.\n" "Which do you want to keep?\n" "YES - Keep Prime Tower\n" "NO - Keep Adaptive Layer Height and Independent Support Layer Height")); } else if (config->opt_bool("adaptive_layer_height")) { msg_text = _(L("Prime tower does not work when Adaptive Layer Height is on.\n" "Which do you want to keep?\n" "YES - Keep Prime Tower\n" "NO - Keep Adaptive Layer Height")); } else { msg_text = _(L("Prime tower does not work when Independent Support Layer Height is on.\n" "Which do you want to keep?\n" "YES - Keep Prime Tower\n" "NO - Keep Independent Support Layer Height")); } MessageDialog dialog(m_msg_dlg_parent, msg_text, "", wxICON_WARNING | wxYES | wxNO); is_msg_dlg_already_exist = true; auto answer = dialog.ShowModal(); DynamicPrintConfig new_conf = *config; if (answer == wxID_YES) { if (config->opt_bool("adaptive_layer_height")) new_conf.set_key_value("adaptive_layer_height", new ConfigOptionBool(false)); if (config->opt_bool("independent_support_layer_height")) new_conf.set_key_value("independent_support_layer_height", new ConfigOptionBool(false)); } else new_conf.set_key_value("enable_prime_tower", new ConfigOptionBool(false)); apply(config, &new_conf); is_msg_dlg_already_exist = false; } // BBS if (has_wipe_tower && config->opt_bool("enable_support") && !config->opt_bool("independent_support_layer_height")) { double layer_height = config->opt_float("layer_height"); double top_gap_raw = config->opt_float("support_top_z_distance"); //double bottom_gap_raw = config->opt_float("support_bottom_z_distance"); double top_gap = std::round(top_gap_raw / layer_height) * layer_height; //double bottom_gap = std::round(bottom_gap_raw / layer_height) * layer_height; if (top_gap != top_gap_raw /* || bottom_gap != bottom_gap_raw*/) { DynamicPrintConfig new_conf = *config; new_conf.set_key_value("support_top_z_distance", new ConfigOptionFloat(top_gap)); //new_conf.set_key_value("support_bottom_z_distance", new ConfigOptionFloat(bottom_gap)); apply(config, &new_conf); //wxMessageBox(_L("Support top/bottom Z distance is automatically changed to multiple of layer height.")); } } #endif { static bool s_mixed_sublayer_warned = false; bool sublayer_on = config->opt_bool("enable_mixed_color_sublayer"); if (sublayer_on && !s_mixed_sublayer_warned && wxGetApp().app_config->get("no_warn_mixed_sublayer_variable_layer") != "1") { bool has_variable_layer = false; for (const auto* obj : wxGetApp().model().objects) { if (obj->layer_height_profile.get().size() > 4) { has_variable_layer = true; break; } } if (has_variable_layer) { MessageDialog dialog(m_msg_dlg_parent, _L("Using variable layer height together with mixed color sublayer may result in poor color mixing quality."), "", wxICON_WARNING | wxOK); dialog.show_dsa_button(); is_msg_dlg_already_exist = true; dialog.ShowModal(); is_msg_dlg_already_exist = false; if (dialog.get_checkbox_state()) wxGetApp().app_config->set("no_warn_mixed_sublayer_variable_layer", "1"); s_mixed_sublayer_warned = true; } } if (!sublayer_on) s_mixed_sublayer_warned = false; } // Removed: legacy "develop mode" auto-restore that forced detect_overhang_wall back // to true whenever enable_support was on. It silently overrode user intent across // project save/reopen (e.g. system preset + enable_support => detect_overhang_wall // came back checked after reopening the 3MF). User toggles for detect_overhang_wall // must be respected. if (config->opt_bool("enable_support")) { auto support_type = config->opt_enum("support_type"); auto support_style = config->opt_enum("support_style"); std::set enum_set_normal = { smsDefault, smsGrid, smsSnug }; std::set enum_set_tree = { smsDefault, smsTreeSlim, smsTreeStrong, smsTreeHybrid, smsTreeOrganic }; auto & set = is_tree(support_type) ? enum_set_tree : enum_set_normal; if (set.find(support_style) == set.end()) { DynamicPrintConfig new_conf = *config; new_conf.set_key_value("support_style", new ConfigOptionEnum(smsDefault)); apply(config, &new_conf); } } if (config->option("sparse_infill_density")->value == 100) { std::string sparse_infill_pattern = config->option>("sparse_infill_pattern")->serialize(); const auto &top_fill_pattern_values = config->def()->get("top_surface_pattern")->enum_values; bool correct_100p_fill = std::find(top_fill_pattern_values.begin(), top_fill_pattern_values.end(), sparse_infill_pattern) != top_fill_pattern_values.end(); if (!correct_100p_fill) { // get sparse_infill_pattern name from enum_labels for using this one at dialog_msg const ConfigOptionDef *fill_pattern_def = config->def()->get("sparse_infill_pattern"); assert(fill_pattern_def != nullptr); auto it_pattern = std::find(fill_pattern_def->enum_values.begin(), fill_pattern_def->enum_values.end(), sparse_infill_pattern); assert(it_pattern != fill_pattern_def->enum_values.end()); if (it_pattern != fill_pattern_def->enum_values.end()) { wxString msg_text = GUI::format_wxstr(_L("%1% infill pattern doesn't support 100%% density."), _(fill_pattern_def->enum_labels[it_pattern - fill_pattern_def->enum_values.begin()])); if (is_global_config) msg_text += "\n" + _L("Switch to rectilinear pattern?\n" "Yes - switch to rectilinear pattern automatically\n" "No - reset density to default non 100% value automatically") + "\n"; MessageDialog dialog(m_msg_dlg_parent, msg_text, "", wxICON_WARNING | (is_global_config ? wxYES | wxNO : wxOK) ); DynamicPrintConfig new_conf = *config; is_msg_dlg_already_exist = true; auto answer = dialog.ShowModal(); if (is_object_config || answer == wxID_YES) { new_conf.set_key_value("sparse_infill_pattern", new ConfigOptionEnum(ipRectilinear)); sparse_infill_density = 100; } else sparse_infill_density = wxGetApp().preset_bundle->prints.get_selected_preset().config.option("sparse_infill_density")->value; new_conf.set_key_value("sparse_infill_density", new ConfigOptionPercent(sparse_infill_density)); apply(config, &new_conf); if (cb_value_change) cb_value_change("sparse_infill_density", sparse_infill_density); is_msg_dlg_already_exist = false; } } } // BBS static const char* filament_slot_keys[] = { "support_filament", "support_interface_filament", "wall_filament", "sparse_infill_filament", "solid_infill_filament"}; for (int i = 0; i < sizeof(filament_slot_keys) / sizeof(filament_slot_keys[0]); i++) { std::string key = std::string(filament_slot_keys[i]); auto* opt = dynamic_cast(config->option(key, false)); if (opt != nullptr) { int val = opt->getInt(); bool out_of_range = val > filament_cnt; bool is_mixed = (val > 0 && val <= filament_cnt && wxGetApp().preset_bundle->is_mixed_filament(val - 1)); if (out_of_range || is_mixed) { DynamicPrintConfig new_conf = *config; int new_value = 0; if (out_of_range) { const DynamicPrintConfig *conf_temp = wxGetApp().plater()->config(); if (conf_temp != nullptr && conf_temp->has(key)) new_value = conf_temp->opt_int(key); } new_conf.set_key_value(key, new ConfigOptionInt(new_value)); apply(config, &new_conf); } } } if (config->opt_enum("print_sequence") == PrintSequence::ByObject && config->opt_int("skirt_height") > 1 && config->opt_int("skirt_loops") > 0) { const wxString msg_text = _(L("While printing by Object, the extruder may collide skirt.\nThus, reset the skirt layer to 1 to avoid that.")); MessageDialog dialog(m_msg_dlg_parent, msg_text, "", wxICON_WARNING | wxOK); DynamicPrintConfig new_conf = *config; is_msg_dlg_already_exist = true; dialog.ShowModal(); new_conf.set_key_value("skirt_height", new ConfigOptionInt(1)); apply(config, &new_conf); is_msg_dlg_already_exist = false; } // layer_height shouldn't be equal to zero float skin_depth = config->opt_float("skin_infill_depth"); if (config->opt_float("infill_lock_depth") > skin_depth) { // xgettext:no-c-format, no-boost-format const wxString msg_text = _(L("lock depth should smaller than skin depth.\nReset to 50% of skin depth")); MessageDialog dialog(m_msg_dlg_parent, msg_text, "", wxICON_WARNING | wxOK); DynamicPrintConfig new_conf = *config; is_msg_dlg_already_exist = true; dialog.ShowModal(); new_conf.set_key_value("infill_lock_depth", new ConfigOptionFloat(skin_depth / 2)); apply(config, &new_conf); is_msg_dlg_already_exist = false; } if (config->opt_bool("alternate_extra_wall") && config->opt_bool("spiral_mode") && applying_keys().empty()) { const wxString msg_text = _L("Alternate extra wall is incompatible with spiral vase mode. To enable alternate extra wall, the following adjustments are recommended:") + wxString("\n - ") + _L("Set ensure vertical shell thickness to Partial.") + wxString("\n - ") + wxString::Format(_L("Set wall loops to %d."), 2) + wxString("\n - ") + wxString::Format(_L("Set sparse infill density to %d%%."), 15) + wxString("\n - ") + _L("Disable spiral vase mode.") + "\n\n" + _L("Change these settings automatically?\n" "Yes - Apply and keep alternate extra wall enabled\n" "No - Don't use alternate extra wall"); MessageDialog dialog(wxGetApp().plater(), msg_text, "", wxICON_WARNING | wxYES | wxNO); is_msg_dlg_already_exist = true; auto answer = dialog.ShowModal(); is_msg_dlg_already_exist = false; if (answer == wxID_YES) { DynamicPrintConfig new_conf = *config; new_conf.set_key_value("ensure_vertical_shell_thickness", new ConfigOptionEnum(EnsureVerticalThicknessLevel::evtPartial)); new_conf.set_key_value("wall_loops", new ConfigOptionInt(2)); new_conf.set_key_value("sparse_infill_density", new ConfigOptionPercent(15)); new_conf.set_key_value("spiral_mode", new ConfigOptionBool(false)); apply(config, &new_conf); } else { DynamicPrintConfig new_conf = *config; new_conf.set_key_value("alternate_extra_wall", new ConfigOptionBool(false)); apply(config, &new_conf); } } if (config->has("periodic_modifier") && config->opt_bool("periodic_modifier") && config->opt_bool("spiral_mode") && applying_keys().empty()) { const wxString msg_text = _L("Periodic modifier is incompatible with spiral vase mode. To enable periodic modifier, the following adjustments are recommended:") + wxString("\n - ") + _L("Disable spiral vase mode.") + "\n\n" + _L("Change these settings automatically?\n" "Yes - Apply and keep periodic modifier enabled\n" "No - Don't use periodic modifier"); MessageDialog dialog(wxGetApp().plater(), msg_text, "", wxICON_WARNING | wxYES | wxNO); is_msg_dlg_already_exist = true; auto answer = dialog.ShowModal(); is_msg_dlg_already_exist = false; if (answer == wxID_YES) { DynamicPrintConfig new_conf = *config; new_conf.set_key_value("spiral_mode", new ConfigOptionBool(false)); apply(config, &new_conf); } else { DynamicPrintConfig new_conf = *config; new_conf.set_key_value("periodic_modifier", new ConfigOptionBool(false)); apply(config, &new_conf); } } // Single consolidated prompt for non-optimal companion settings when alternate extra wall // is on: EVT == Enabled, wall_loops != 2, sparse_infill_density == 0. Skip during apply() // cascades so density sync (Tab::on_value_change skeleton/skin) does not double-prompt. { const bool alt_on = config->opt_bool("alternate_extra_wall"); const auto evt = config->opt_enum("ensure_vertical_shell_thickness"); const int wall_loops = config->opt_int("wall_loops"); const int density = config->option("sparse_infill_density")->value; const bool evt_enabled = evt == EnsureVerticalThicknessLevel::evtEnabled; const bool walls_not_rec = wall_loops != 2; const bool density_zero = density == 0; const bool suboptimal = evt_enabled || walls_not_rec || density_zero; if (!alt_on || !suboptimal) m_alt_suboptimal_acknowledged = false; if (alt_on && !config->opt_bool("spiral_mode") && suboptimal && !m_alt_suboptimal_acknowledged && applying_keys().empty()) { wxString adjustments; if (evt_enabled) adjustments += wxString("\n - ") + _L("Set ensure vertical shell thickness to Partial."); if (walls_not_rec) adjustments += "\n - " + wxString::Format(_L("Set wall loops to %d."), 2); if (density_zero) adjustments += "\n - " + wxString::Format(_L("Set sparse infill density to %d%%."), 15); const wxString msg_text = _L("For alternate extra wall to work properly, the following adjustments are recommended:") + adjustments + "\n\n" + _L("Apply these adjustments?\n" "Yes - Apply and keep alternate extra wall enabled\n" "No - Keep current settings and alternate extra wall enabled"); MessageDialog dialog(wxGetApp().plater(), msg_text, "", wxICON_WARNING | wxYES | wxNO); is_msg_dlg_already_exist = true; auto answer = dialog.ShowModal(); is_msg_dlg_already_exist = false; if (answer == wxID_YES) { DynamicPrintConfig new_conf = *config; if (evt_enabled) new_conf.set_key_value("ensure_vertical_shell_thickness", new ConfigOptionEnum(EnsureVerticalThicknessLevel::evtPartial)); if (walls_not_rec) new_conf.set_key_value("wall_loops", new ConfigOptionInt(2)); if (density_zero) new_conf.set_key_value("sparse_infill_density", new ConfigOptionPercent(15)); apply(config, &new_conf); } else { m_alt_suboptimal_acknowledged = true; } } } } void ConfigManipulation::apply_null_fff_config(DynamicPrintConfig *config, std::vector const &keys, std::map const &configs) { for (auto &k : keys) { if (/*k == "adaptive_layer_height" || */k == "independent_support_layer_height" || k == "enable_support" || k == "detect_thin_wall") config->set_key_value(k, new ConfigOptionBool(true)); else if (k == "wall_loops") config->set_key_value(k, new ConfigOptionInt(0)); else if (k == "top_shell_layers" || k == "enforce_support_layers") config->set_key_value(k, new ConfigOptionInt(1)); else if (k == "sparse_infill_density") { double v = config->option(k)->value; for (auto &c : configs) { auto o = c.second->get().option(k); if (o && o->value > v) v = o->value; } config->set_key_value(k, new ConfigOptionPercent(v)); // sparse_infill_pattern } else if (k == "detect_overhang_wall") config->set_key_value(k, new ConfigOptionBool(false)); else if (k == "sparse_infill_pattern") config->set_key_value(k, new ConfigOptionEnum(ipGrid)); } } void ConfigManipulation::toggle_print_fff_options(DynamicPrintConfig *config, int variant_index, const bool is_global_config) { bool have_perimeters = config->opt_int("wall_loops") > 0; for (auto el : { "ensure_vertical_shell_thickness", "detect_thin_wall", "detect_overhang_wall", "seam_position","seam_placement_away_from_overhangs","seam_gap","wipe_speed", "wall_sequence", "outer_wall_line_width", "inner_wall_speed", "outer_wall_speed","small_perimeter_speed", "small_perimeter_threshold" }) toggle_field(el, have_perimeters); SeamPosition seam_pos = config->option>("seam_position")->value; toggle_line("seam_placement_away_from_overhangs", seam_pos == SeamPosition::spAligned || seam_pos == SeamPosition::spRear); bool have_infill = config->option("sparse_infill_density")->value > 0; // sparse_infill_filament uses the same logic as in Print::extruders() for (auto el : {"sparse_infill_pattern", "sparse_infill_anchor_max", "infill_combination", "minimum_sparse_infill_area", "sparse_infill_filament", "infill_shift_step", "infill_rotate_step", "symmetric_infill_y_axis", "sparse_infill_lattice_angle_1", "sparse_infill_lattice_angle_2"}) toggle_line(el, have_infill); // Determine if the selected infill pattern supports multiline infill. InfillPattern pattern = config->opt_enum("sparse_infill_pattern"); bool support_multiline_infill = pattern == ipCubic || pattern == ipGrid || pattern == ipRectilinear || pattern == ipStars || pattern == ipAlignedRectilinear || pattern == ipGyroid || pattern == ipHoneycomb || pattern == ipLightning || pattern == ip3DHoneycomb || pattern == ipAdaptiveCubic || pattern == ipSupportCubic; toggle_line("fill_multiline", have_infill && support_multiline_infill); // Only allow configuration of open anchors if the anchoring is enabled. bool has_infill_anchors = have_infill && config->option("sparse_infill_anchor_max")->value > 0; toggle_line("sparse_infill_anchor", has_infill_anchors); //cross zag bool is_cross_zag = have_infill && config->option>("sparse_infill_pattern")->value == InfillPattern::ipCrossZag; bool is_locked_zig = have_infill && config->option>("sparse_infill_pattern")->value == InfillPattern::ipLockedZag; bool is_conformal_pattern = have_infill && (pattern == InfillPattern::ipZigZag || pattern == InfillPattern::ipCrossZag || pattern == InfillPattern::ipLockedZag || pattern == InfillPattern::ipRectilinear || pattern == InfillPattern::ipAlignedRectilinear); toggle_line("conformal_infill", is_conformal_pattern); bool conformal_on = is_conformal_pattern && config->option("conformal_infill") && config->option("conformal_infill")->value; toggle_line("conformal_stagger", conformal_on); toggle_line("conformal_link_keep_layers", conformal_on); toggle_line("conformal_link_flip_layers", conformal_on); toggle_line("conformal_pole", conformal_on); toggle_line("conformal_ray_count", conformal_on); toggle_line("conformal_hub_radius", conformal_on); for (auto el : {"infill_instead_top_bottom_surfaces","skeleton_infill_density", "skin_infill_density", "infill_lock_depth", "skin_infill_depth", "skin_infill_line_width", "skeleton_infill_line_width", "locked_skin_infill_pattern", "locked_skeleton_infill_pattern"}) toggle_line(el, is_locked_zig); bool is_zig_zag = have_infill && config->option>("sparse_infill_pattern")->value == InfillPattern::ipZigZag; toggle_line("infill_rotate_step", is_zig_zag); toggle_line("infill_shift_step", is_cross_zag || is_locked_zig); toggle_line("symmetric_infill_y_axis", is_zig_zag || is_cross_zag || is_locked_zig); bool lattice_options = have_infill && config->option>("sparse_infill_pattern")->value == InfillPattern::ip2DLattice; for (auto el : {"sparse_infill_lattice_angle_1", "sparse_infill_lattice_angle_2"}) toggle_line(el, lattice_options); bool have_periodic_modifier = config->has("periodic_modifier") && config->opt_bool("periodic_modifier"); for (auto el : { "periodic_modifier_skip_layers", "periodic_modifier_apply_layers" }) toggle_line(el, have_periodic_modifier); bool has_spiral_vase = config->opt_bool("spiral_mode"); toggle_line("spiral_mode_smooth", has_spiral_vase); toggle_line("spiral_mode_max_xy_smoothing", config->opt_bool("spiral_mode_smooth")); toggle_field("z_direction_outwall_speed_continuous", !has_spiral_vase); bool has_top_solid_infill = config->opt_int("top_shell_layers") > 0; bool has_bottom_solid_infill = config->opt_int("bottom_shell_layers") > 0; bool has_solid_infill = has_top_solid_infill || has_bottom_solid_infill; // solid_infill_filament uses the same logic as in Print::extruders() for (auto el : {"top_surface_pattern", "bottom_surface_pattern", "top_surface_density", "bottom_surface_density", "internal_solid_infill_pattern", "sub_top_surface_pattern", "solid_infill_filament"}) toggle_field(el, has_solid_infill); for (auto el : { "infill_direction", "sparse_infill_line_width", "bridge_angle", "sparse_infill_speed", "bridge_speed" }) toggle_field(el, have_infill || has_solid_infill); toggle_field("top_shell_thickness", ! has_spiral_vase && has_top_solid_infill); toggle_field("bottom_shell_thickness", ! has_spiral_vase && has_bottom_solid_infill); // Gap fill is newly allowed in between perimeter lines even for empty infill (see GH #1476). toggle_field("gap_infill_speed", have_perimeters); for (auto el : { "top_surface_line_width", "top_surface_speed" }) toggle_field(el, has_top_solid_infill || (has_spiral_vase && has_bottom_solid_infill)); bool have_default_acceleration = config->opt_float_nullable("default_acceleration", variant_index) > 0; //BBS for (auto el : { "initial_layer_acceleration", "outer_wall_acceleration", "top_surface_acceleration", "inner_wall_acceleration", "sparse_infill_acceleration" }) toggle_field(el, have_default_acceleration); if (is_BBL_Printer) { for (auto el : {"default_jerk", "outer_wall_jerk", "inner_wall_jerk", "infill_jerk", "top_surface_jerk", "initial_layer_jerk", "travel_jerk"}) toggle_line(el, false); } else { for (auto el : {"default_jerk", "outer_wall_jerk", "inner_wall_jerk", "infill_jerk", "top_surface_jerk", "initial_layer_jerk", "travel_jerk"}) toggle_line(el, true); bool quality_default_jerk = config->opt_float("default_jerk") > 0; for (auto el : {"outer_wall_jerk", "inner_wall_jerk", "infill_jerk", "top_surface_jerk", "initial_layer_jerk", "travel_jerk"}) toggle_field(el, quality_default_jerk); } bool have_skirt = config->opt_int("skirt_loops") > 0; toggle_field("skirt_height", have_skirt && config->opt_enum("draft_shield") != dsEnabled); for (auto el : { "skirt_distance", "draft_shield"}) toggle_field(el, have_skirt); bool have_brim = (config->opt_enum("brim_type") != btNoBrim); toggle_field("brim_object_gap", have_brim); bool have_brim_width = (config->opt_enum("brim_type") != btNoBrim) && config->opt_enum("brim_type") != btAutoBrim && config->opt_enum("brim_type") != btBrimEars; toggle_field("brim_width", have_brim_width); // wall_filament uses the same logic as in Print::extruders() toggle_field("wall_filament", have_perimeters || have_brim); bool have_raft = config->opt_int("raft_layers") > 0; bool have_support_material = config->opt_bool("enable_support") || have_raft; // BBS SupportType support_type = config->opt_enum("support_type"); bool have_support_interface = config->opt_int("support_interface_top_layers") > 0 || config->opt_int("support_interface_bottom_layers") > 0; bool have_support_soluble = have_support_material && config->opt_float("support_top_z_distance") == 0; auto support_style = config->opt_enum("support_style"); for (auto el : { "support_style", "support_base_pattern", "support_base_pattern_spacing", "support_expansion", "support_angle", "support_interface_pattern", "support_interface_top_layers", "bridge_no_support", "max_bridge_length", "support_top_z_distance", "support_bottom_z_distance", //BBS: add more support params to dependent of enable_support "support_type", "support_on_build_plate_only", "support_remove_small_overhang","support_interface_not_for_body", "support_object_xy_distance","support_object_first_layer_gap"/*, "independent_support_layer_height"*/}) toggle_field(el, have_support_material); toggle_field("support_threshold_angle", have_support_material && is_auto(support_type)); //toggle_field("support_closing_radius", have_support_material && support_style == smsSnug); bool support_is_tree = config->opt_bool("enable_support") && is_tree(support_type); for (auto el : {"tree_support_branch_angle", "tree_support_branch_distance", "tree_support_branch_diameter", "tree_support_branch_diameter_angle"}) toggle_field(el, support_is_tree); // hide tree support settings when normal is selected for (auto el : {"tree_support_branch_angle", "tree_support_branch_distance", "tree_support_branch_diameter", "tree_support_branch_diameter_angle", "max_bridge_length"}) toggle_line(el, support_is_tree); toggle_line("support_critical_regions_only", is_auto(support_type) && support_is_tree); toggle_line("detect_floating_vertical_shell", config->option("detect_narrow_internal_solid_infill")->value); toggle_line("vertical_shell_speed", config->option("detect_narrow_internal_solid_infill")->value, variant_index); // tree support use max_bridge_length instead of bridge_no_support toggle_line("bridge_no_support", !support_is_tree); // only normal support has bottom interfaces toggle_line("support_bottom_interface_spacing", !support_is_tree); toggle_line("support_interface_bottom_layers", !support_is_tree); for (auto el : {"support_interface_spacing", "support_interface_filament", "support_interface_loop_pattern"}) toggle_field(el, have_support_material && have_support_interface); //BBS bool have_skirt_height = have_skirt && (config->opt_int("skirt_height") > 1 || config->opt_enum("draft_shield") != dsEnabled); toggle_line("support_speed", have_support_material || have_skirt_height); toggle_line("support_interface_speed", have_support_material && have_support_interface); // BBS //toggle_field("support_material_synchronize_layers", have_support_soluble); toggle_field("inner_wall_line_width", have_perimeters || have_skirt || have_brim); toggle_field("support_filament", have_support_material || have_skirt); toggle_line("raft_contact_distance", have_raft && !have_support_soluble); bool has_ironing = (config->opt_enum("ironing_type") != IroningType::NoIroning); for (auto el : { "ironing_pattern","ironing_speed", "ironing_flow", "ironing_spacing", "ironing_direction", "ironing_inset"}) toggle_line(el, has_ironing); // has ironingable support interface bool has_ironing_support = config->opt_int("raft_layers") > 1 || (config->opt_bool("enable_support") && config->opt_int("support_interface_top_layers") > 0); toggle_field("enable_support_ironing", has_ironing_support); for (auto el : {"support_ironing_pattern", "support_ironing_speed", "support_ironing_flow", "support_ironing_spacing", "support_ironing_direction", "support_ironing_inset"}) toggle_line(el, config->opt_bool("enable_support_ironing") && has_ironing_support); // bool have_sequential_printing = (config->opt_enum("print_sequence") == PrintSequence::ByObject); // for (auto el : { "extruder_clearance_dist_to_rod", "extruder_clearance_height_to_rod", "extruder_clearance_height_to_lid" }) // toggle_field(el, have_sequential_printing); bool have_ooze_prevention = config->opt_bool("ooze_prevention"); toggle_field("standby_temperature_delta", have_ooze_prevention); bool have_prime_tower = config->opt_bool("enable_prime_tower"); for (auto el : {"prime_tower_width", "prime_tower_brim_width", "prime_tower_skip_points", "prime_tower_rib_wall", "prime_tower_infill_gap", "prime_tower_enable_framework", "prime_tower_max_speed"}) toggle_line(el, have_prime_tower); { std::string printer_model = wxGetApp().preset_bundle->printers.get_edited_preset().config.opt_string("printer_model"); bool is_tower_interface_supported = (printer_model.find("H2C") != std::string::npos || printer_model.find("H2D") != std::string::npos || printer_model.find("X2D") != std::string::npos); toggle_line("enable_tower_interface_features", have_prime_tower && is_tower_interface_supported); } bool have_rib_wall = config->opt_bool("prime_tower_rib_wall")&&have_prime_tower; for (auto el : {"prime_tower_extra_rib_length", "prime_tower_rib_width", "prime_tower_fillet_wall"}) toggle_line(el, have_rib_wall); toggle_field("prime_tower_width", !have_rib_wall); for (auto el : {"flush_into_infill", "flush_into_support", "flush_into_objects"}) toggle_field(el, have_prime_tower); bool have_avoid_crossing_perimeters = config->opt_bool("reduce_crossing_wall"); toggle_line("max_travel_detour_distance", have_avoid_crossing_perimeters); toggle_line("avoid_crossing_wall_includes_support", have_avoid_crossing_perimeters); bool has_overhang_speed = config->opt_bool_nullable("enable_overhang_speed", variant_index); for (auto el : { "overhang_1_4_speed", "overhang_2_4_speed", "overhang_3_4_speed", "overhang_4_4_speed"}) toggle_line(el, has_overhang_speed, variant_index); bool has_height_slowdown = config->opt_bool("enable_height_slowdown", variant_index); for (auto el : { "slowdown_start_height", "slowdown_start_speed", "slowdown_start_acc", "slowdown_end_height", "slowdown_end_speed", "slowdown_end_acc" }) toggle_line(el, has_height_slowdown, variant_index); toggle_line("flush_into_objects", !is_global_config); toggle_line("print_flow_ratio", !is_global_config); toggle_line("wall_filament", !is_global_config); toggle_line("solid_infill_filament", !is_global_config); toggle_line("sparse_infill_filament", !is_global_config); toggle_line("support_interface_not_for_body",config->opt_int("support_interface_filament")&&!config->opt_int("support_filament")); bool has_fuzzy_skin = (config->opt_enum("fuzzy_skin") != FuzzySkinType::Disabled_fuzzy); for (auto el : { "fuzzy_skin_thickness", "fuzzy_skin_point_distance", "fuzzy_skin_first_layer", "fuzzy_skin_noise_type", "fuzzy_skin_mode" }) toggle_line(el, has_fuzzy_skin); NoiseType fuzzy_skin_noise_type = config->opt_enum("fuzzy_skin_noise_type"); toggle_line("fuzzy_skin_scale", fuzzy_skin_noise_type != NoiseType::Classic && has_fuzzy_skin); toggle_line("fuzzy_skin_octaves", fuzzy_skin_noise_type != NoiseType::Classic && fuzzy_skin_noise_type != NoiseType::Voronoi && has_fuzzy_skin); toggle_line("fuzzy_skin_persistence", (fuzzy_skin_noise_type == NoiseType::Perlin || fuzzy_skin_noise_type == NoiseType::Billow) && has_fuzzy_skin); bool have_arachne = config->opt_enum("wall_generator") == PerimeterGeneratorType::Arachne; for (auto el : { "wall_transition_length", "wall_transition_filter_deviation", "wall_transition_angle", "min_feature_size", "min_bead_width", "wall_distribution_count" }) toggle_line(el, have_arachne); toggle_field("detect_thin_wall", !have_arachne); PresetBundle *preset_bundle = wxGetApp().preset_bundle; // OrcaSlicer auto gcflavor = preset_bundle->printers.get_edited_preset().config.option>("gcode_flavor")->value; if( gcflavor != gcfKlipper ) { for (auto el : {"accel_to_decel_enable", "accel_to_decel_factor"}) toggle_line(el, false); } else { for (auto el : {"accel_to_decel_enable", "accel_to_decel_factor"}) toggle_line(el, true); toggle_field("accel_to_decel_factor", config->opt_bool("accel_to_decel_enable")); } toggle_line("exclude_object", gcflavor == gcfKlipper); bool use_beam_interlocking = config->opt_bool("interlocking_beam"); toggle_line("mmu_segmented_region_interlocking_depth", !use_beam_interlocking); toggle_line("interlocking_beam_width", use_beam_interlocking); toggle_line("interlocking_orientation", use_beam_interlocking); toggle_line("interlocking_beam_layer_count", use_beam_interlocking); toggle_line("interlocking_depth", use_beam_interlocking); toggle_line("interlocking_boundary_avoidance", use_beam_interlocking); bool enable_auto_hole_and_contour_compensation = config->opt_bool("enable_circle_compensation"); toggle_field("xy_hole_compensation", !enable_auto_hole_and_contour_compensation); toggle_field("xy_contour_compensation", !enable_auto_hole_and_contour_compensation); toggle_line("circle_compensation_manual_offset", enable_auto_hole_and_contour_compensation); // override filament scarf seam settings bool override_filament_scarf_seam_settings = config->opt_bool("override_filament_scarf_seam_setting"); for (auto el : {"seam_slope_type", "seam_slope_start_height", "seam_slope_gap", "seam_slope_min_length"}) toggle_line(el, override_filament_scarf_seam_settings); std::string printer_type = wxGetApp().preset_bundle->printers.get_edited_preset().get_printer_type(wxGetApp().preset_bundle); toggle_line("enable_wrapping_detection", DevPrinterConfigUtil::support_wrapping_detection(printer_type)); } void ConfigManipulation::update_print_sla_config(DynamicPrintConfig* config, const bool is_global_config/* = false*/) { double head_penetration = config->opt_float("support_head_penetration"); double head_width = config->opt_float("support_head_width"); if (head_penetration > head_width) { //wxString msg_text = _(L("Head penetration should not be greater than the head width.")); wxString msg_text = "Head penetration should not be greater than the head width."; //MessageDialog dialog(m_msg_dlg_parent, msg_text, _(L("Invalid Head penetration")), wxICON_WARNING | wxOK); MessageDialog dialog(m_msg_dlg_parent, msg_text, "Invalid Head penetration", wxICON_WARNING | wxOK); DynamicPrintConfig new_conf = *config; if (dialog.ShowModal() == wxID_OK) { new_conf.set_key_value("support_head_penetration", new ConfigOptionFloat(head_width)); apply(config, &new_conf); } } double pinhead_d = config->opt_float("support_head_front_diameter"); double pillar_d = config->opt_float("support_pillar_diameter"); if (pinhead_d > pillar_d) { //wxString msg_text = _(L("Pinhead diameter should be smaller than the pillar diameter.")); wxString msg_text = "Pinhead diameter should be smaller than the pillar diameter."; //MessageDialog dialog(m_msg_dlg_parent, msg_text, _(L("Invalid pinhead diameter")), wxICON_WARNING | wxOK); MessageDialog dialog(m_msg_dlg_parent, msg_text, "Invalid pinhead diameter", wxICON_WARNING | wxOK); DynamicPrintConfig new_conf = *config; if (dialog.ShowModal() == wxID_OK) { new_conf.set_key_value("support_head_front_diameter", new ConfigOptionFloat(pillar_d / 2.0)); apply(config, &new_conf); } } } void ConfigManipulation::toggle_print_sla_options(DynamicPrintConfig* config) { bool supports_en = config->opt_bool("supports_enable"); toggle_field("support_head_front_diameter", supports_en); toggle_field("support_head_penetration", supports_en); toggle_field("support_head_width", supports_en); toggle_field("support_pillar_diameter", supports_en); toggle_field("support_small_pillar_diameter_percent", supports_en); toggle_field("support_max_bridges_on_pillar", supports_en); toggle_field("support_pillar_connection_mode", supports_en); toggle_field("support_buildplate_only", supports_en); toggle_field("support_base_diameter", supports_en); toggle_field("support_base_height", supports_en); toggle_field("support_base_safety_distance", supports_en); toggle_field("support_critical_angle", supports_en); toggle_field("support_max_bridge_length", supports_en); toggle_field("support_max_pillar_link_distance", supports_en); toggle_field("support_points_density_relative", supports_en); toggle_field("support_points_minimal_distance", supports_en); bool pad_en = config->opt_bool("pad_enable"); toggle_field("pad_wall_thickness", pad_en); toggle_field("pad_wall_height", pad_en); toggle_field("pad_brim_size", pad_en); toggle_field("pad_max_merge_distance", pad_en); // toggle_field("pad_edge_radius", supports_en); toggle_field("pad_wall_slope", pad_en); toggle_field("pad_around_object", pad_en); toggle_field("pad_around_object_everywhere", pad_en); bool zero_elev = config->opt_bool("pad_around_object") && pad_en; toggle_field("support_object_elevation", supports_en && !zero_elev); toggle_field("pad_object_gap", zero_elev); toggle_field("pad_around_object_everywhere", zero_elev); toggle_field("pad_object_connector_stride", zero_elev); toggle_field("pad_object_connector_width", zero_elev); toggle_field("pad_object_connector_penetration", zero_elev); } int ConfigManipulation::show_spiral_mode_settings_dialog(bool is_object_config) { wxString msg_text = _(L("Spiral mode only works when wall loops is 1, support is disabled, clumping detection by probing is disabled, top shell layers is 0, sparse infill density is 0, timelapse type is instant, smoothing wall speed in z direction is false, alternate extra wall is disabled and periodic modifier is disabled.")); auto printer_structure_opt = wxGetApp().preset_bundle->printers.get_edited_preset().config.option>("printer_structure"); if (printer_structure_opt && printer_structure_opt->value == PrinterStructure::psI3) { msg_text += _(L(" But machines with I3 structure will not generate timelapse videos.")); } if (!is_object_config) msg_text += "\n\n" + _(L("Change these settings automatically? \n" "Yes - Change these settings and enable spiral mode automatically\n" "No - Give up using spiral mode this time")); MessageDialog dialog(wxGetApp().plater(), msg_text, "", wxICON_WARNING | (!is_object_config ? wxYES | wxNO : wxOK)); is_msg_dlg_already_exist = true; auto answer = dialog.ShowModal(); is_msg_dlg_already_exist = false; if (is_object_config) answer = wxID_YES; return answer; } bool ConfigManipulation::get_temperature_range(DynamicPrintConfig *config, int &range_low, int &range_high) { bool range_low_exist = false, range_high_exist = false; if (config->has("nozzle_temperature_range_low")) { range_low = config->opt_int("nozzle_temperature_range_low", (unsigned int) 0); range_low_exist = true; } if (config->has("nozzle_temperature_range_high")) { range_high = config->opt_int("nozzle_temperature_range_high", (unsigned int) 0); range_high_exist = true; } return range_low_exist && range_high_exist; } // 根据用户已选择的支撑料和模型主体料,查询是否有推荐参数 bool query_support_recommended_params_for_combination(int support_filament_index, const std::string& model_material_type, const std::string& model_material_name, DynamicPrintConfig& out_config) { auto &filament_presets = Slic3r::GUI::wxGetApp().preset_bundle->filament_presets; auto &filaments = Slic3r::GUI::wxGetApp().preset_bundle->filaments; if (support_filament_index < 0 || support_filament_index >= static_cast(filament_presets.size())) return false; Slic3r::Preset* support_preset = filaments.find_preset(filament_presets[support_filament_index]); if (!support_preset) return false; std::string support_type = support_preset->config.option("filament_type")->values[0]; std::string support_name = support_preset->alias; // 按优先级尝试多种 key 组合查询推荐参数 // support_recommended_params_map 的 key 格式为 "support_material|model_material" // 其中 support_material 可能是 name 或 type,model_material 也可能是 name 或 type // model_material_name 或 model_material_type 可能为空(当盘内模型不满足同名/同类型条件时) struct QueryPair { std::string support_key; std::string model_key; }; std::vector queries; if (!model_material_name.empty()) { queries.push_back({support_name, model_material_name}); // name + name(最精确) queries.push_back({support_type, model_material_name}); // type + name } if (!model_material_type.empty()) { queries.push_back({support_name, model_material_type}); // name + type queries.push_back({support_type, model_material_type}); // type + type(最宽泛) } for (const auto& q : queries) { if (build_support_recommended_config(q.support_key, q.model_key, out_config)) { return true; } } return false; } // 根据支撑材料和主体材料,构建推荐配置到 DynamicPrintConfig bool build_support_recommended_config(const std::string& support_material, const std::string& model_material, DynamicPrintConfig& out_config) { auto rec_params_opt = Slic3r::GUI::wxGetApp().preset_bundle->get_support_recommended_params(support_material, model_material); if (!rec_params_opt.has_value() || !rec_params_opt->params.hasRecommendedParams()) { return false; } const auto& rec_params = rec_params_opt.value(); for (const auto &[param_key, value] : rec_params.params.params) { auto opt_def_it = print_config_def.options.find(param_key); if (opt_def_it == print_config_def.options.end()) { BOOST_LOG_TRIVIAL(warning) << "Unknown config option in support recommended params: " << param_key; continue; } const ConfigOptionDef& opt_def = opt_def_it->second; std::string serialized = std::visit([](auto &&val) -> std::string { using T = std::decay_t; if constexpr (std::is_same_v) { return val; } else if constexpr (std::is_same_v) { return val ? "1" : "0"; } else if constexpr (std::is_same_v>) { std::ostringstream ss; for (size_t i = 0; i < val.size(); ++i) { if (i > 0) ss << ","; ss << val[i]; } return ss.str(); } else { std::ostringstream ss; ss << val; return ss.str(); } }, value); ConfigOption *opt = opt_def.create_default_option(); if (opt && opt->deserialize(serialized)) { out_config.set_key_value(param_key, opt); }else { delete opt; BOOST_LOG_TRIVIAL(warning) << "Failed to deserialize recommended param " << param_key << ": " << serialized; } } return true; } } // GUI } // Slic3r