diff --git a/src/libslic3r/GCode/CoolingBuffer.cpp b/src/libslic3r/GCode/CoolingBuffer.cpp index 6815ea73a0..07ab197f27 100644 --- a/src/libslic3r/GCode/CoolingBuffer.cpp +++ b/src/libslic3r/GCode/CoolingBuffer.cpp @@ -682,7 +682,8 @@ std::string CoolingBuffer::apply_layer_cooldown( #define EXTRUDER_CONFIG(OPT) config.OPT.get_at(m_current_extruder) int min_fan_speed = EXTRUDER_CONFIG(min_fan_speed); int fan_speed_new = EXTRUDER_CONFIG(fan_always_on) ? min_fan_speed : 0; - if (layer_id >= (size_t)EXTRUDER_CONFIG(disable_fan_first_layers)) { + int disable_fan_first_layers = EXTRUDER_CONFIG(disable_fan_first_layers); + if (int(layer_id) >= disable_fan_first_layers) { int max_fan_speed = EXTRUDER_CONFIG(max_fan_speed); float slowdown_below_layer_time = float(EXTRUDER_CONFIG(slowdown_below_layer_time)); float fan_below_layer_time = float(EXTRUDER_CONFIG(fan_below_layer_time)); @@ -698,6 +699,17 @@ std::string CoolingBuffer::apply_layer_cooldown( } } bridge_fan_speed = EXTRUDER_CONFIG(bridge_fan_speed); + // Is the fan speed ramp enabled? + int full_fan_speed_layer = EXTRUDER_CONFIG(full_fan_speed_layer); + // When ramping up fan speed from disable_fan_first_layers to full_fan_speed_layer, force disable_fan_first_layers above zero, + // so there will be a zero fan speed at least at the 1st layer. + disable_fan_first_layers = std::max(disable_fan_first_layers, 1); + if (int(layer_id) >= disable_fan_first_layers && int(layer_id) + 1 < full_fan_speed_layer) { + // Ramp up the fan speed from disable_fan_first_layers to full_fan_speed_layer. + float factor = float(int(layer_id + 1) - disable_fan_first_layers) / float(full_fan_speed_layer - disable_fan_first_layers); + fan_speed_new = clamp(0, 255, int(float(fan_speed_new ) * factor + 0.5f)); + bridge_fan_speed = clamp(0, 255, int(float(bridge_fan_speed) * factor + 0.5f)); + } #undef EXTRUDER_CONFIG bridge_fan_control = bridge_fan_speed > fan_speed_new; } else { diff --git a/src/libslic3r/Preset.cpp b/src/libslic3r/Preset.cpp index c7e0c50409..bd146202d9 100644 --- a/src/libslic3r/Preset.cpp +++ b/src/libslic3r/Preset.cpp @@ -443,7 +443,7 @@ const std::vector& Preset::filament_options() "filament_unloading_speed", "filament_unloading_speed_start", "filament_unload_time", "filament_toolchange_delay", "filament_cooling_moves", "filament_cooling_initial_speed", "filament_cooling_final_speed", "filament_ramming_parameters", "filament_minimal_purge_on_wipe_tower", "temperature", "first_layer_temperature", "bed_temperature", "first_layer_bed_temperature", "fan_always_on", "cooling", "min_fan_speed", - "max_fan_speed", "bridge_fan_speed", "disable_fan_first_layers", "fan_below_layer_time", "slowdown_below_layer_time", "min_print_speed", + "max_fan_speed", "bridge_fan_speed", "disable_fan_first_layers", "full_fan_speed_layer", "fan_below_layer_time", "slowdown_below_layer_time", "min_print_speed", "start_filament_gcode", "end_filament_gcode", // Retract overrides "filament_retract_length", "filament_retract_lift", "filament_retract_lift_above", "filament_retract_lift_below", "filament_retract_speed", "filament_deretract_speed", "filament_retract_restart_extra", "filament_retract_before_travel", @@ -731,8 +731,9 @@ static bool profile_print_params_same(const DynamicPrintConfig &cfg_old, const D // Load a preset from an already parsed config file, insert it into the sorted sequence of presets // and select it, losing previous modifications. -// In case -Preset& PresetCollection::load_external_preset( +// Only a single profile could be edited at at the same time, which introduces complexity when loading +// filament profiles for multi-extruder printers. +std::pair PresetCollection::load_external_preset( // Path to the profile source file (a G-code, an AMF or 3MF file, a config file) const std::string &path, // Name of the profile, derived from the source file name. @@ -742,14 +743,23 @@ Preset& PresetCollection::load_external_preset( // Config to initialize the preset from. const DynamicPrintConfig &config, // Select the preset after loading? - bool select) + LoadAndSelect select) { // Load the preset over a default preset, so that the missing fields are filled in from the default preset. DynamicPrintConfig cfg(this->default_preset_for(config).config); cfg.apply_only(config, cfg.keys(), true); + std::string &inherits = Preset::inherits(cfg); + if (select == LoadAndSelect::Never) { + // Some filament profile has been selected and modified already. + // Check whether this profile is equal to the modified edited profile. + const Preset &edited = this->get_edited_preset(); + if ((edited.name == original_name || edited.name == inherits) && profile_print_params_same(edited.config, cfg)) + // Just point to that already selected and edited profile. + return std::make_pair(&(*this->find_preset_internal(edited.name)), false); + } // Is there a preset already loaded with the name stored inside the config? - std::deque::iterator it = this->find_preset_internal(original_name); - bool found = it != m_presets.end() && it->name == original_name; + std::deque::iterator it = this->find_preset_internal(original_name); + bool found = it != m_presets.end() && it->name == original_name; if (! found) { // Try to match the original_name against the "renamed_from" profile names of loaded system profiles. it = this->find_preset_renamed(original_name); @@ -757,19 +767,40 @@ Preset& PresetCollection::load_external_preset( } if (found && profile_print_params_same(it->config, cfg)) { // The preset exists and it matches the values stored inside config. - if (select) + if (select == LoadAndSelect::Always) this->select_preset(it - m_presets.begin()); - return *it; + return std::make_pair(&(*it), false); } - // Update the "inherits" field. - std::string &inherits = Preset::inherits(cfg); - if (found && inherits.empty()) { - // There is a profile with the same name already loaded. Should we update the "inherits" field? - if (it->vendor == nullptr) - inherits = it->inherits(); - else - inherits = it->name; + if (! found && select != LoadAndSelect::Never && ! inherits.empty()) { + // Try to use a system profile as a base to select the system profile + // and override its settings with the loaded ones. + assert(it == m_presets.end()); + it = this->find_preset_internal(inherits); + found = it != m_presets.end() && it->name == inherits; + if (found && profile_print_params_same(it->config, cfg)) { + // The system preset exists and it matches the values stored inside config. + if (select == LoadAndSelect::Always) + this->select_preset(it - m_presets.begin()); + return std::make_pair(&(*it), false); + } } + if (found) { + if (select != LoadAndSelect::Never) { + // Select the existing preset and override it with new values, so that + // the differences will be shown in the preset editor against the referenced profile. + this->select_preset(it - m_presets.begin()); + this->get_edited_preset().config.apply(config); + this->update_dirty(); + assert(this->get_edited_preset().is_dirty); + return std::make_pair(&(*it), this->get_edited_preset().is_dirty); + } + if (inherits.empty()) { + // Update the "inherits" field. + // There is a profile with the same name already loaded. Should we update the "inherits" field? + inherits = it->vendor ? it->name : it->inherits(); + } + } + // The external preset does not match an internal preset, load the external preset. std::string new_name; for (size_t idx = 0;; ++ idx) { @@ -790,19 +821,19 @@ Preset& PresetCollection::load_external_preset( break; if (profile_print_params_same(it->config, cfg)) { // The preset exists and it matches the values stored inside config. - if (select) + if (select == LoadAndSelect::Always) this->select_preset(it - m_presets.begin()); - return *it; + return std::make_pair(&(*it), false); } // Form another profile name. } // Insert a new profile. - Preset &preset = this->load_preset(path, new_name, std::move(cfg), select); + Preset &preset = this->load_preset(path, new_name, std::move(cfg), select == LoadAndSelect::Always); preset.is_external = true; if (&this->get_selected_preset() == &preset) this->get_edited_preset().is_external = true; - return preset; + return std::make_pair(&preset, false); } Preset& PresetCollection::load_preset(const std::string &path, const std::string &name, DynamicPrintConfig &&config, bool select) diff --git a/src/libslic3r/Preset.hpp b/src/libslic3r/Preset.hpp index 6409eee7ca..c54dd3fd87 100644 --- a/src/libslic3r/Preset.hpp +++ b/src/libslic3r/Preset.hpp @@ -287,7 +287,18 @@ public: Preset& load_preset(const std::string &path, const std::string &name, const DynamicPrintConfig &config, bool select = true); Preset& load_preset(const std::string &path, const std::string &name, DynamicPrintConfig &&config, bool select = true); - Preset& load_external_preset( + // Returns a loaded preset, returns true if an existing preset was selected AND modified from config. + // In that case the successive filament loaded for a multi material printer should not be modified, but + // an external preset should be created instead. + enum class LoadAndSelect { + // Never select + Never, + // Always select + Always, + // Select a profile only if it was modified. + OnlyIfModified, + }; + std::pair load_external_preset( // Path to the profile source file (a G-code, an AMF or 3MF file, a config file) const std::string &path, // Name of the profile, derived from the source file name. @@ -297,7 +308,7 @@ public: // Config to initialize the preset from. const DynamicPrintConfig &config, // Select the preset after loading? - bool select = true); + LoadAndSelect select = LoadAndSelect::Always); // Save the preset under a new name. If the name is different from the old one, // a new preset is stored into the list of presets. diff --git a/src/libslic3r/PresetBundle.cpp b/src/libslic3r/PresetBundle.cpp index 44df000c3b..b020f874ed 100644 --- a/src/libslic3r/PresetBundle.cpp +++ b/src/libslic3r/PresetBundle.cpp @@ -49,7 +49,7 @@ PresetBundle::PresetBundle() : // initialized based on PrintConfigDef(), but to empty values (zeros, empty vectors, empty strings). // // "compatible_printers", "compatible_printers_condition", "inherits", - // "print_settings_id", "filament_settings_id", "printer_settings_id", + // "print_settings_id", "filament_settings_id", "printer_settings_id", "printer_settings_id" // "printer_vendor", "printer_model", "printer_variant", "default_print_profile", "default_filament_profile" // Create the ID config keys, as they are not part of the Static print config classes. @@ -586,6 +586,7 @@ DynamicPrintConfig PresetBundle::full_fff_config() const out.option("print_settings_id", true)->value = this->prints.get_selected_preset_name(); out.option("filament_settings_id", true)->values = this->filament_presets; out.option("printer_settings_id", true)->value = this->printers.get_selected_preset_name(); + out.option("physical_printer_settings_id", true)->value = this->physical_printers.get_selected_printer_name(); // Serialize the collected "compatible_printers_condition" and "inherits" fields. // There will be 1 + num_exturders fields for "inherits" and 2 + num_extruders for "compatible_printers_condition" stored. @@ -637,6 +638,7 @@ DynamicPrintConfig PresetBundle::full_sla_config() const out.option("sla_print_settings_id", true)->value = this->sla_prints.get_selected_preset_name(); out.option("sla_material_settings_id", true)->value = this->sla_materials.get_selected_preset_name(); out.option("printer_settings_id", true)->value = this->printers.get_selected_preset_name(); + out.option("physical_printer_settings_id", true)->value = this->physical_printers.get_selected_printer_name(); // Serialize the collected "compatible_printers_condition" and "inherits" fields. // There will be 1 + num_exturders fields for "inherits" and 2 + num_extruders for "compatible_printers_condition" stored. @@ -712,6 +714,7 @@ void PresetBundle::load_config_file(const std::string &path) } // Load a config file from a boost property_tree. This is a private method called from load_config_file. +// is_external == false on if called from ConfigWizard void PresetBundle::load_config_file_config(const std::string &name_or_path, bool is_external, DynamicPrintConfig &&config) { PrinterTechnology printer_technology = Preset::printer_technology(config); @@ -798,14 +801,17 @@ void PresetBundle::load_config_file_config(const std::string &name_or_path, bool compatible_prints_condition = compatible_prints_condition_values.front(); Preset *loaded = nullptr; if (is_external) { - loaded = &this->filaments.load_external_preset(name_or_path, name, old_filament_profile_names->values.front(), config); + auto [aloaded, modified] = this->filaments.load_external_preset(name_or_path, name, old_filament_profile_names->values.front(), config); + loaded = aloaded; } else { - loaded = &this->filaments.load_preset(this->filaments.path_from_name(name), name, config); + // called from Config Wizard. + loaded= &this->filaments.load_preset(this->filaments.path_from_name(name), name, config); loaded->save(); } this->filament_presets.clear(); this->filament_presets.emplace_back(loaded->name); } else { + assert(is_external); // Split the filament presets, load each of them separately. std::vector configs(num_extruders, this->filaments.default_preset().config); // loop through options and scatter them into configs. @@ -826,6 +832,7 @@ void PresetBundle::load_config_file_config(const std::string &name_or_path, bool // To avoid incorrect selection of the first filament preset (means a value of Preset->m_idx_selected) // in a case when next added preset take a place of previosly selected preset, // we should add presets from last to first + bool any_modified = false; for (int i = (int)configs.size()-1; i >= 0; i--) { DynamicPrintConfig &cfg = configs[i]; // Split the "compatible_printers_condition" and "inherits" from the cummulative vectors to separate filament presets. @@ -833,24 +840,15 @@ void PresetBundle::load_config_file_config(const std::string &name_or_path, bool cfg.opt_string("compatible_prints_condition", true) = compatible_prints_condition_values[i]; cfg.opt_string("inherits", true) = inherits_values[i + 1]; // Load all filament presets, but only select the first one in the preset dialog. - Preset *loaded = nullptr; - if (is_external) - loaded = &this->filaments.load_external_preset(name_or_path, name, - (i < int(old_filament_profile_names->values.size())) ? old_filament_profile_names->values[i] : "", - std::move(cfg), i == 0); - else { - // Used by the config wizard when creating a custom setup. - // Therefore this block should only be called for a single extruder. - char suffix[64]; - if (i == 0) - suffix[0] = 0; - else - sprintf(suffix, "%d", (int)i); - std::string new_name = name + suffix; - loaded = &this->filaments.load_preset(this->filaments.path_from_name(new_name), - new_name, std::move(cfg), i == 0); - loaded->save(); - } + auto [loaded, modified] = this->filaments.load_external_preset(name_or_path, name, + (i < int(old_filament_profile_names->values.size())) ? old_filament_profile_names->values[i] : "", + std::move(cfg), + i == 0 ? + PresetCollection::LoadAndSelect::Always : + any_modified ? + PresetCollection::LoadAndSelect::Never : + PresetCollection::LoadAndSelect::OnlyIfModified); + any_modified |= modified; this->filament_presets[i] = loaded->name; } } @@ -864,10 +862,23 @@ void PresetBundle::load_config_file_config(const std::string &name_or_path, bool load_preset(this->sla_materials, 1, "sla_material_settings_id"); load_preset(this->printers, 2, "printer_settings_id"); break; - default: break; + default: + break; } this->update_compatible(PresetSelectCompatibleType::Never); + + const std::string &physical_printer = config.option("physical_printer_settings_id", true)->value; + if (this->printers.get_edited_preset().is_external || physical_printer.empty()) { + this->physical_printers.unselect_printer(); + } else { + // Activate the physical printer profile if possible. + PhysicalPrinter *pp = this->physical_printers.find_printer(physical_printer, true); + if (pp != nullptr && std::find(pp->preset_names.begin(), pp->preset_names.end(), this->printers.get_edited_preset().name) != pp->preset_names.end()) + this->physical_printers.select_printer(*pp); + else + this->physical_printers.unselect_printer(); + } } // Load the active configuration of a config bundle from a boost property_tree. This is a private method called from load_config_file. diff --git a/src/libslic3r/PresetBundle.hpp b/src/libslic3r/PresetBundle.hpp index 609e25e2c2..5d7cc84ba2 100644 --- a/src/libslic3r/PresetBundle.hpp +++ b/src/libslic3r/PresetBundle.hpp @@ -70,7 +70,7 @@ public: // Load user configuration and store it into the user profiles. // This method is called by the configuration wizard. - void load_config(const std::string &name, DynamicPrintConfig config) + void load_config_from_wizard(const std::string &name, DynamicPrintConfig config) { this->load_config_file_config(name, false, std::move(config)); } // Load configuration that comes from a model file containing configuration, such as 3MF et al. diff --git a/src/libslic3r/Print.cpp b/src/libslic3r/Print.cpp index a8d4b2ea05..3215e376d7 100644 --- a/src/libslic3r/Print.cpp +++ b/src/libslic3r/Print.cpp @@ -93,6 +93,7 @@ bool Print::invalidate_state_by_config_options(const std::vectorinvalidate_step(psGCodeExport)); // Set the profile aliases for the PrintBase::output_filename() - m_placeholder_parser.set("print_preset", new_full_config.option("print_settings_id")->clone()); - m_placeholder_parser.set("filament_preset", new_full_config.option("filament_settings_id")->clone()); - m_placeholder_parser.set("printer_preset", new_full_config.option("printer_settings_id")->clone()); + m_placeholder_parser.set("print_preset", new_full_config.option("print_settings_id")->clone()); + m_placeholder_parser.set("filament_preset", new_full_config.option("filament_settings_id")->clone()); + m_placeholder_parser.set("printer_preset", new_full_config.option("printer_settings_id")->clone()); + m_placeholder_parser.set("physical_printer_preset", new_full_config.option("physical_printer_settings_id")->clone()); // We want the filament overrides to be applied over their respective extruder parameters by the PlaceholderParser. // see "Placeholders do not respect filament overrides." GH issue #3649 m_placeholder_parser.apply_config(filament_overrides); diff --git a/src/libslic3r/PrintConfig.cpp b/src/libslic3r/PrintConfig.cpp index fe64e8aa62..0e21abdf33 100644 --- a/src/libslic3r/PrintConfig.cpp +++ b/src/libslic3r/PrintConfig.cpp @@ -981,6 +981,17 @@ void PrintConfigDef::init_fff_params() def->max = max_temp; def->set_default_value(new ConfigOptionInts { 200 }); + def = this->add("full_fan_speed_layer", coInts); + def->label = L("Full fan speed at layer"); + def->tooltip = L("Fan speed will be ramped up linearly from zero at layer \"disable_fan_first_layers\" " + "to maximum at layer \"full_fan_speed_layer\". " + "\"full_fan_speed_layer\" will be ignored if lower than \"disable_fan_first_layers\", in which case " + "the fan will be running at maximum allowed speed at layer \"disable_fan_first_layers\" + 1."); + def->min = 0; + def->max = 1000; + def->mode = comExpert; + def->set_default_value(new ConfigOptionInts { 0 }); + def = this->add("gap_fill_speed", coFloat); def->label = L("Gap fill"); def->category = L("Speed"); @@ -1680,6 +1691,10 @@ void PrintConfigDef::init_fff_params() def->set_default_value(new ConfigOptionString("")); def->cli = ConfigOptionDef::nocli; + def = this->add("physical_printer_settings_id", coString); + def->set_default_value(new ConfigOptionString("")); + def->cli = ConfigOptionDef::nocli; + def = this->add("raft_layers", coInt); def->label = L("Raft layers"); def->category = L("Support material"); diff --git a/src/libslic3r/PrintConfig.hpp b/src/libslic3r/PrintConfig.hpp index aa7b159d03..9eb79d5fa2 100644 --- a/src/libslic3r/PrintConfig.hpp +++ b/src/libslic3r/PrintConfig.hpp @@ -852,6 +852,7 @@ public: ConfigOptionFloatOrPercent first_layer_extrusion_width; ConfigOptionFloatOrPercent first_layer_speed; ConfigOptionInts first_layer_temperature; + ConfigOptionInts full_fan_speed_layer; ConfigOptionFloat infill_acceleration; ConfigOptionBool infill_first; ConfigOptionInts max_fan_speed; @@ -925,6 +926,7 @@ protected: OPT_PTR(first_layer_extrusion_width); OPT_PTR(first_layer_speed); OPT_PTR(first_layer_temperature); + OPT_PTR(full_fan_speed_layer); OPT_PTR(infill_acceleration); OPT_PTR(infill_first); OPT_PTR(max_fan_speed); diff --git a/src/libslic3r/SLAPrint.cpp b/src/libslic3r/SLAPrint.cpp index f36e48aa66..65fac73f34 100644 --- a/src/libslic3r/SLAPrint.cpp +++ b/src/libslic3r/SLAPrint.cpp @@ -193,9 +193,10 @@ SLAPrint::ApplyStatus SLAPrint::apply(const Model &model, DynamicPrintConfig con #endif /* _DEBUG */ // Normalize the config. - config.option("sla_print_settings_id", true); - config.option("sla_material_settings_id", true); - config.option("printer_settings_id", true); + config.option("sla_print_settings_id", true); + config.option("sla_material_settings_id", true); + config.option("printer_settings_id", true); + config.option("physical_printer_settings_id", true); // Collect changes to print config. t_config_option_keys print_diff = m_print_config.diff(config); t_config_option_keys printer_diff = m_printer_config.diff(config); @@ -228,9 +229,10 @@ SLAPrint::ApplyStatus SLAPrint::apply(const Model &model, DynamicPrintConfig con // update_apply_status(this->invalidate_step(slapsRasterize)); m_placeholder_parser.apply_config(config); // Set the profile aliases for the PrintBase::output_filename() - m_placeholder_parser.set("print_preset", config.option("sla_print_settings_id")->clone()); - m_placeholder_parser.set("material_preset", config.option("sla_material_settings_id")->clone()); - m_placeholder_parser.set("printer_preset", config.option("printer_settings_id")->clone()); + m_placeholder_parser.set("print_preset", config.option("sla_print_settings_id")->clone()); + m_placeholder_parser.set("material_preset", config.option("sla_material_settings_id")->clone()); + m_placeholder_parser.set("printer_preset", config.option("printer_settings_id")->clone()); + m_placeholder_parser.set("physical_printer_preset", config.option("physical_printer_settings_id")->clone()); } // It is also safe to change m_config now after this->invalidate_state_by_config_options() call. diff --git a/src/libslic3r/Technologies.hpp b/src/libslic3r/Technologies.hpp index e9195e39aa..4cda8f0085 100644 --- a/src/libslic3r/Technologies.hpp +++ b/src/libslic3r/Technologies.hpp @@ -89,6 +89,7 @@ #define ENABLE_2_3_0_BETA2 1 #define ENABLE_ARROW_KEYS_WITH_SLIDERS (1 && ENABLE_2_3_0_BETA2) +#define ENABLE_NEW_NOTIFICATIONS_FADE_OUT (1 && ENABLE_2_3_0_BETA2) #define ENABLE_PREVIEW_TYPE_CHANGE (1 && ENABLE_2_3_0_BETA2) diff --git a/src/slic3r/GUI/ConfigWizard.cpp b/src/slic3r/GUI/ConfigWizard.cpp index 18e89f1146..c0bfbe1412 100644 --- a/src/slic3r/GUI/ConfigWizard.cpp +++ b/src/slic3r/GUI/ConfigWizard.cpp @@ -2457,7 +2457,7 @@ void ConfigWizard::priv::apply_config(AppConfig *app_config, PresetBundle *prese page_temps->apply_custom_config(*custom_config); const std::string profile_name = page_custom->profile_name(); - preset_bundle->load_config(profile_name, *custom_config); + preset_bundle->load_config_from_wizard(profile_name, *custom_config); } // Update the selections from the compatibilty. diff --git a/src/slic3r/GUI/GLCanvas3D.cpp b/src/slic3r/GUI/GLCanvas3D.cpp index b337813e6c..640e757eda 100644 --- a/src/slic3r/GUI/GLCanvas3D.cpp +++ b/src/slic3r/GUI/GLCanvas3D.cpp @@ -636,9 +636,9 @@ void GLCanvas3D::WarningTexture::activate(WarningTexture::Warning warning, bool auto ¬ification_manager = *wxGetApp().plater()->get_notification_manager(); if (state) { if(error) - notification_manager.push_plater_error_notification(text,*(wxGetApp().plater()->get_current_canvas3D())); + notification_manager.push_plater_error_notification(text); else - notification_manager.push_plater_warning_notification(text, *(wxGetApp().plater()->get_current_canvas3D())); + notification_manager.push_plater_warning_notification(text); } else { if (error) notification_manager.close_plater_error_notification(text); @@ -1728,8 +1728,7 @@ void GLCanvas3D::render() m_tooltip.render(m_mouse.position, *this); wxGetApp().plater()->get_mouse3d_controller().render_settings_dialog(*this); - - wxGetApp().plater()->get_notification_manager()->render_notifications(*this, get_overlay_window_width()); + wxGetApp().plater()->get_notification_manager()->render_notifications(get_overlay_window_width()); wxGetApp().imgui()->render(); @@ -2384,6 +2383,14 @@ void GLCanvas3D::on_idle(wxIdleEvent& evt) if (!m_initialized) return; +#if ENABLE_NEW_NOTIFICATIONS_FADE_OUT + NotificationManager* notification_mgr = wxGetApp().plater()->get_notification_manager(); + if (notification_mgr->requires_update()) + notification_mgr->update_notifications(); + + m_dirty |= notification_mgr->requires_render(); +#endif // ENABLE_NEW_NOTIFICATIONS_FADE_OUT + m_dirty |= m_main_toolbar.update_items_state(); m_dirty |= m_undoredo_toolbar.update_items_state(); m_dirty |= wxGetApp().plater()->get_view_toolbar().update_items_state(); @@ -2391,12 +2398,24 @@ void GLCanvas3D::on_idle(wxIdleEvent& evt) bool mouse3d_controller_applied = wxGetApp().plater()->get_mouse3d_controller().apply(wxGetApp().plater()->get_camera()); m_dirty |= mouse3d_controller_applied; +#if ENABLE_NEW_NOTIFICATIONS_FADE_OUT + if (!m_dirty) { + if (notification_mgr->requires_update()) + evt.RequestMore(); + return; + } +#else if (!m_dirty) return; +#endif // ENABLE_NEW_NOTIFICATIONS_FADE_OUT _refresh_if_shown_on_screen(); +#if ENABLE_NEW_NOTIFICATIONS_FADE_OUT + if (m_extra_frame_requested || mouse3d_controller_applied || notification_mgr->requires_update()) { +#else if (m_extra_frame_requested || mouse3d_controller_applied) { +#endif // ENABLE_NEW_NOTIFICATIONS_FADE_OUT m_dirty = true; m_extra_frame_requested = false; evt.RequestMore(); @@ -2531,7 +2550,7 @@ void GLCanvas3D::on_char(wxKeyEvent& evt) case WXK_BACK: case WXK_DELETE: post_event(SimpleEvent(EVT_GLTOOLBAR_DELETE_ALL)); break; - default: evt.Skip(); + default: evt.Skip(); } } else if ((evt.GetModifiers() & shiftMask) != 0) { diff --git a/src/slic3r/GUI/GUI_App.cpp b/src/slic3r/GUI/GUI_App.cpp index 2be866ce78..5472676d99 100644 --- a/src/slic3r/GUI/GUI_App.cpp +++ b/src/slic3r/GUI/GUI_App.cpp @@ -820,7 +820,7 @@ bool GUI_App::on_init_inner() app_config->save(); if (this->plater_ != nullptr) { if (*Semver::parse(SLIC3R_VERSION) < *Semver::parse(into_u8(evt.GetString()))) { - this->plater_->get_notification_manager()->push_notification(NotificationType::NewAppAvailable, *(this->plater_->get_current_canvas3D())); + this->plater_->get_notification_manager()->push_notification(NotificationType::NewAppAvailable); } } }); diff --git a/src/slic3r/GUI/Mouse3DController.cpp b/src/slic3r/GUI/Mouse3DController.cpp index 24da199dbd..57748af6f0 100644 --- a/src/slic3r/GUI/Mouse3DController.cpp +++ b/src/slic3r/GUI/Mouse3DController.cpp @@ -602,7 +602,7 @@ void Mouse3DController::disconnected() m_params_by_device[m_device_str] = m_params_ui; m_device_str.clear(); m_connected = false; - wxGetApp().plater()->get_notification_manager()->push_notification(NotificationType::Mouse3dDisconnected, *(wxGetApp().plater()->get_current_canvas3D())); + wxGetApp().plater()->get_notification_manager()->push_notification(NotificationType::Mouse3dDisconnected); wxGetApp().plater()->CallAfter([]() { Plater *plater = wxGetApp().plater(); diff --git a/src/slic3r/GUI/NotificationManager.cpp b/src/slic3r/GUI/NotificationManager.cpp index e59d61d0a0..bf4142b9c5 100644 --- a/src/slic3r/GUI/NotificationManager.cpp +++ b/src/slic3r/GUI/NotificationManager.cpp @@ -18,6 +18,9 @@ static constexpr float GAP_WIDTH = 10.0f; static constexpr float SPACE_RIGHT_PANEL = 10.0f; +#if ENABLE_NEW_NOTIFICATIONS_FADE_OUT +static constexpr float FADING_OUT_DURATION = 2.0f; +#endif // ENABLE_NEW_NOTIFICATIONS_FADE_OUT namespace Slic3r { namespace GUI { @@ -134,6 +137,96 @@ NotificationManager::PopNotification::PopNotification(const NotificationData &n, { //init(); } +#if ENABLE_NEW_NOTIFICATIONS_FADE_OUT +void NotificationManager::PopNotification::render(GLCanvas3D& canvas, float initial_y, bool move_from_overlay, float overlay_width) +{ + if (m_hidden) { + m_top_y = initial_y - GAP_WIDTH; + return; + } + + Size cnv_size = canvas.get_canvas_size(); + ImGuiWrapper& imgui = *wxGetApp().imgui(); + ImVec2 mouse_pos = ImGui::GetMousePos(); + float right_gap = SPACE_RIGHT_PANEL + (move_from_overlay ? overlay_width + m_line_height * 5 : 0); + + if (m_line_height != ImGui::CalcTextSize("A").y) + init(); + + set_next_window_size(imgui); + + // top y of window + m_top_y = initial_y + m_window_height; + + ImVec2 win_pos(1.0f * (float)cnv_size.get_width() - right_gap, 1.0f * (float)cnv_size.get_height() - m_top_y); + imgui.set_next_window_pos(win_pos.x, win_pos.y, ImGuiCond_Always, 1.0f, 0.0f); + imgui.set_next_window_size(m_window_width, m_window_height, ImGuiCond_Always); + + // find if hovered + m_hovered = false; + if (mouse_pos.x < win_pos.x && mouse_pos.x > win_pos.x - m_window_width && mouse_pos.y > win_pos.y && mouse_pos.y < win_pos.y + m_window_height) { + ImGui::SetNextWindowFocus(); + m_hovered = true; + } + + // color change based on fading out + bool fading_pop = false; + if (m_fading_out) { + Notifications_Internal::push_style_color(ImGuiCol_WindowBg, ImGui::GetStyleColorVec4(ImGuiCol_WindowBg), m_fading_out, m_current_fade_opacity); + Notifications_Internal::push_style_color(ImGuiCol_Text, ImGui::GetStyleColorVec4(ImGuiCol_Text), m_fading_out, m_current_fade_opacity); + fading_pop = true; + } + + // background color + if (m_is_gray) { + ImVec4 backcolor(0.7f, 0.7f, 0.7f, 0.5f); + Notifications_Internal::push_style_color(ImGuiCol_WindowBg, backcolor, m_fading_out, m_current_fade_opacity); + } + else if (m_data.level == NotificationLevel::ErrorNotification) { + ImVec4 backcolor = ImGui::GetStyleColorVec4(ImGuiCol_WindowBg); + backcolor.x += 0.3f; + Notifications_Internal::push_style_color(ImGuiCol_WindowBg, backcolor, m_fading_out, m_current_fade_opacity); + } + else if (m_data.level == NotificationLevel::WarningNotification) { + ImVec4 backcolor = ImGui::GetStyleColorVec4(ImGuiCol_WindowBg); + backcolor.x += 0.3f; + backcolor.y += 0.15f; + Notifications_Internal::push_style_color(ImGuiCol_WindowBg, backcolor, m_fading_out, m_current_fade_opacity); + } + + // name of window - probably indentifies window and is shown so last_end add whitespaces according to id + if (m_id == 0) + m_id = m_id_provider.allocate_id(); + std::string name = "!!Ntfctn" + std::to_string(m_id); + if (imgui.begin(name, ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoScrollbar)) { + ImVec2 win_size = ImGui::GetWindowSize(); + + //FIXME: dont forget to us this for texts + //GUI::format(_utf8(L())); + + /* + //countdown numbers + ImGui::SetCursorPosX(15); + ImGui::SetCursorPosY(15); + imgui.text(std::to_string(m_remaining_time).c_str()); + */ + + render_left_sign(imgui); + render_text(imgui, win_size.x, win_size.y, win_pos.x, win_pos.y); + render_close_button(imgui, win_size.x, win_size.y, win_pos.x, win_pos.y); + m_minimize_b_visible = false; + if (m_multiline && m_lines_count > 3) + render_minimize_button(imgui, win_pos.x, win_pos.y); + } + imgui.end(); + + if (m_is_gray || m_data.level == NotificationLevel::ErrorNotification || m_data.level == NotificationLevel::WarningNotification) + ImGui::PopStyleColor(); + + if (fading_pop) + ImGui::PopStyleColor(2); +} +#else NotificationManager::PopNotification::RenderResult NotificationManager::PopNotification::render(GLCanvas3D& canvas, const float& initial_y, bool move_from_overlay, float overlay_width) { if (!m_initialized) { @@ -268,6 +361,7 @@ NotificationManager::PopNotification::RenderResult NotificationManager::PopNotif ImGui::PopStyleColor(); return ret_val; } +#endif // ENABLE_NEW_NOTIFICATIONS_FADE_OUT void NotificationManager::PopNotification::count_spaces() { //determine line width @@ -528,6 +622,7 @@ void NotificationManager::PopNotification::render_close_button(ImGuiWrapper& img ImGui::PopStyleColor(); ImGui::PopStyleColor(); } +#if !ENABLE_NEW_NOTIFICATIONS_FADE_OUT void NotificationManager::PopNotification::render_countdown(ImGuiWrapper& imgui, const float win_size_x, const float win_size_y, const float win_pos_x, const float win_pos_y) { /* @@ -575,6 +670,7 @@ void NotificationManager::PopNotification::render_countdown(ImGuiWrapper& imgui, m_countdown_frame++; */ } +#endif // !ENABLE_NEW_NOTIFICATIONS_FADE_OUT void NotificationManager::PopNotification::render_left_sign(ImGuiWrapper& imgui) { if (m_data.level == NotificationLevel::ErrorNotification || m_data.level == NotificationLevel::WarningNotification) { @@ -643,6 +739,52 @@ bool NotificationManager::PopNotification::compare_text(const std::string& text) return false; } +#if ENABLE_NEW_NOTIFICATIONS_FADE_OUT +void NotificationManager::PopNotification::update_state() +{ + if (!m_initialized) + init(); + + if (m_hidden) { + m_state = EState::Static; + return; + } + + if (m_hovered) { + // reset fading + m_fading_out = false; + m_current_fade_opacity = 1.0f; + m_remaining_time = m_data.duration; + } + + if (m_counting_down) { + if (m_fading_out && m_current_fade_opacity <= 0.0f) + m_finished = true; + else if (!m_fading_out && m_remaining_time == 0) { + m_fading_out = true; + m_fading_start = wxGetLocalTimeMillis(); + } + } + + if (m_finished) { + m_state = EState::Finished; + return; + } + if (m_close_pending) { + m_finished = true; + m_state = EState::ClosePending; + return; + } + if (m_fading_out) { + if (!m_paused) { + wxMilliClock_t curr_time = wxGetLocalTimeMillis() - m_fading_start; + m_current_fade_opacity = std::clamp(1.0f - 0.001f * static_cast(curr_time.GetValue()) / FADING_OUT_DURATION, 0.0f, 1.0f); + } + m_state = EState::FadingOut; + } +} +#endif // ENABLE_NEW_NOTIFICATIONS_FADE_OUT + NotificationManager::SlicingCompleteLargeNotification::SlicingCompleteLargeNotification(const NotificationData& n, NotificationIDProvider& id_provider, wxEvtHandler* evt_handler, bool large) : NotificationManager::PopNotification(n, id_provider, evt_handler) { @@ -849,19 +991,19 @@ NotificationManager::NotificationManager(wxEvtHandler* evt_handler) : m_evt_handler(evt_handler) { } -void NotificationManager::push_notification(const NotificationType type, GLCanvas3D& canvas, int timestamp) +void NotificationManager::push_notification(const NotificationType type, int timestamp) { auto it = std::find_if(basic_notifications.begin(), basic_notifications.end(), boost::bind(&NotificationData::type, boost::placeholders::_1) == type); assert(it != basic_notifications.end()); if (it != basic_notifications.end()) - push_notification_data( *it, canvas, timestamp); + push_notification_data(*it, timestamp); } -void NotificationManager::push_notification(const std::string& text, GLCanvas3D& canvas, int timestamp) +void NotificationManager::push_notification(const std::string& text, int timestamp) { - push_notification_data({ NotificationType::CustomNotification, NotificationLevel::RegularNotification, 10, text }, canvas, timestamp ); + push_notification_data({ NotificationType::CustomNotification, NotificationLevel::RegularNotification, 10, text }, timestamp); } -void NotificationManager::push_notification(const std::string& text, NotificationManager::NotificationLevel level, GLCanvas3D& canvas, int timestamp) +void NotificationManager::push_notification(const std::string& text, NotificationManager::NotificationLevel level, int timestamp) { int duration = 0; switch (level) { @@ -872,32 +1014,32 @@ void NotificationManager::push_notification(const std::string& text, Notificatio assert(false); return; } - push_notification_data({ NotificationType::CustomNotification, level, duration, text }, canvas, timestamp); + push_notification_data({ NotificationType::CustomNotification, level, duration, text }, timestamp); } -void NotificationManager::push_slicing_error_notification(const std::string& text, GLCanvas3D& canvas) +void NotificationManager::push_slicing_error_notification(const std::string& text) { set_all_slicing_errors_gray(false); - push_notification_data({ NotificationType::SlicingError, NotificationLevel::ErrorNotification, 0, _u8L("ERROR:") + "\n" + text }, canvas, 0); + push_notification_data({ NotificationType::SlicingError, NotificationLevel::ErrorNotification, 0, _u8L("ERROR:") + "\n" + text }, 0); close_notification_of_type(NotificationType::SlicingComplete); } -void NotificationManager::push_slicing_warning_notification(const std::string& text, bool gray, GLCanvas3D& canvas, ObjectID oid, int warning_step) +void NotificationManager::push_slicing_warning_notification(const std::string& text, bool gray, ObjectID oid, int warning_step) { NotificationData data { NotificationType::SlicingWarning, NotificationLevel::WarningNotification, 0, _u8L("WARNING:") + "\n" + text }; auto notification = std::make_unique(data, m_id_provider, m_evt_handler); notification->object_id = oid; notification->warning_step = warning_step; - if (push_notification_data(std::move(notification), canvas, 0)) { + if (push_notification_data(std::move(notification), 0)) { m_pop_notifications.back()->set_gray(gray); } } -void NotificationManager::push_plater_error_notification(const std::string& text, GLCanvas3D& canvas) +void NotificationManager::push_plater_error_notification(const std::string& text) { - push_notification_data({ NotificationType::PlaterError, NotificationLevel::ErrorNotification, 0, _u8L("ERROR:") + "\n" + text }, canvas, 0); + push_notification_data({ NotificationType::PlaterError, NotificationLevel::ErrorNotification, 0, _u8L("ERROR:") + "\n" + text }, 0); } -void NotificationManager::push_plater_warning_notification(const std::string& text, GLCanvas3D& canvas) +void NotificationManager::push_plater_warning_notification(const std::string& text) { - push_notification_data({ NotificationType::PlaterWarning, NotificationLevel::WarningNotification, 0, _u8L("WARNING:") + "\n" + text }, canvas, 0); + push_notification_data({ NotificationType::PlaterWarning, NotificationLevel::WarningNotification, 0, _u8L("WARNING:") + "\n" + text }, 0); // dissaper if in preview set_in_preview(m_in_preview); } @@ -951,7 +1093,7 @@ void NotificationManager::close_slicing_errors_and_warnings() } } } -void NotificationManager::push_slicing_complete_notification(GLCanvas3D& canvas, int timestamp, bool large) +void NotificationManager::push_slicing_complete_notification(int timestamp, bool large) { std::string hypertext; int time = 10; @@ -963,8 +1105,7 @@ void NotificationManager::push_slicing_complete_notification(GLCanvas3D& canvas, } NotificationData data{ NotificationType::SlicingComplete, NotificationLevel::RegularNotification, time, _u8L("Slicing finished."), hypertext, [](wxEvtHandler* evnthndlr){ if (evnthndlr != nullptr) wxPostEvent(evnthndlr, ExportGcodeNotificationClickedEvent(EVT_EXPORT_GCODE_NOTIFICAION_CLICKED)); return true; } }; - push_notification_data(std::make_unique(data, m_id_provider, m_evt_handler, large), - canvas, timestamp); + push_notification_data(std::make_unique(data, m_id_provider, m_evt_handler, large), timestamp); } void NotificationManager::set_slicing_complete_print_time(const std::string &info) { @@ -1001,38 +1142,41 @@ void NotificationManager::remove_slicing_warnings_of_released_objects(const std: notification->close(); } } -void NotificationManager::push_exporting_finished_notification(GLCanvas3D& canvas, std::string path, std::string dir_path, bool on_removable) +void NotificationManager::push_exporting_finished_notification(const std::string& path, const std::string& dir_path, bool on_removable) { close_notification_of_type(NotificationType::ExportFinished); - NotificationData data{ NotificationType::ExportFinished, NotificationLevel::RegularNotification, 0, _u8L("Exporting finished.") +"\n"+ path }; - push_notification_data(std::make_unique(data, m_id_provider, m_evt_handler, on_removable, path, dir_path), - canvas, 0); + NotificationData data{ NotificationType::ExportFinished, NotificationLevel::RegularNotification, 0, _u8L("Exporting finished.") + "\n" + path }; + push_notification_data(std::make_unique(data, m_id_provider, m_evt_handler, on_removable, path, dir_path), 0); } -void NotificationManager::push_progress_bar_notification(const std::string& text, GLCanvas3D& canvas, float percentage) +void NotificationManager::push_progress_bar_notification(const std::string& text, float percentage) { NotificationData data{ NotificationType::ProgressBar, NotificationLevel::ProgressBarNotification, 0, text }; - push_notification_data(std::make_unique(data, m_id_provider, m_evt_handler, 0),canvas, 0); + push_notification_data(std::make_unique(data, m_id_provider, m_evt_handler, 0), 0); } -void NotificationManager::set_progress_bar_percentage(const std::string& text, float percentage, GLCanvas3D& canvas) +void NotificationManager::set_progress_bar_percentage(const std::string& text, float percentage) { bool found = false; for (std::unique_ptr& notification : m_pop_notifications) { if (notification->get_type() == NotificationType::ProgressBar && notification->compare_text(text)) { dynamic_cast(notification.get())->set_percentage(percentage); - canvas.request_extra_frame(); + wxGetApp().plater()->get_current_canvas3D()->request_extra_frame(); found = true; } } if (!found) { - push_progress_bar_notification(text, canvas, percentage); + push_progress_bar_notification(text, percentage); } } -bool NotificationManager::push_notification_data(const NotificationData ¬ification_data, GLCanvas3D& canvas, int timestamp) +bool NotificationManager::push_notification_data(const NotificationData& notification_data, int timestamp) { - return push_notification_data(std::make_unique(notification_data, m_id_provider, m_evt_handler), canvas, timestamp); + return push_notification_data(std::make_unique(notification_data, m_id_provider, m_evt_handler), timestamp); } -bool NotificationManager::push_notification_data(std::unique_ptr notification, GLCanvas3D& canvas, int timestamp) +bool NotificationManager::push_notification_data(std::unique_ptr notification, int timestamp) { +#if ENABLE_NEW_NOTIFICATIONS_FADE_OUT + m_requires_update = true; +#endif // ENABLE_NEW_NOTIFICATIONS_FADE_OUT + // if timestamped notif, push only new one if (timestamp != 0) { if (m_used_timestamps.find(timestamp) == m_used_timestamps.end()) { @@ -1041,6 +1185,9 @@ bool NotificationManager::push_notification_data(std::unique_ptrget_current_canvas3D(); + if (this->activate_existing(notification.get())) { m_pop_notifications.back()->update(notification->get_data()); canvas.request_extra_frame(); @@ -1051,7 +1198,22 @@ bool NotificationManager::push_notification_data(std::unique_ptrget_current_canvas3D(); + float last_y = 0.0f; + + for (const auto& notification : m_pop_notifications) { + notification->render(canvas, last_y, m_move_from_overlay && !m_in_preview, overlay_width); + if (notification->get_state() != PopNotification::EState::Finished) + last_y = notification->get_top() + GAP_WIDTH; + } +} +#else +void NotificationManager::render_notifications(float overlay_width) { float last_x = 0.0f; float current_height = 0.0f; @@ -1059,9 +1221,12 @@ void NotificationManager::render_notifications(GLCanvas3D& canvas, float overlay bool render_main = false; bool hovered = false; sort_notifications(); - // iterate thru notifications and render them / erease them + + GLCanvas3D& canvas = *wxGetApp().plater()->get_current_canvas3D(); + + // iterate thru notifications and render them / erase them for (auto it = m_pop_notifications.begin(); it != m_pop_notifications.end();) { - if ((*it)->get_finished()) { + if ((*it)->is_finished()) { it = m_pop_notifications.erase(it); } else { (*it)->set_paused(m_hovered); @@ -1111,6 +1276,7 @@ void NotificationManager::render_notifications(GLCanvas3D& canvas, float overlay // If any of the notifications is fading out, 100% of the CPU/GPU is consumed. canvas.request_extra_frame(); } +#endif // ENABLE_NEW_NOTIFICATIONS_FADE_OUT void NotificationManager::sort_notifications() { @@ -1118,7 +1284,7 @@ void NotificationManager::sort_notifications() std::stable_sort(m_pop_notifications.begin(), m_pop_notifications.end(), [](const std::unique_ptr &n1, const std::unique_ptr &n2) { int n1l = (int)n1->get_data().level; int n2l = (int)n2->get_data().level; - if (n1l == n2l && n1->get_is_gray() && !n2->get_is_gray()) + if (n1l == n2l && n1->is_gray() && !n2->is_gray()) return true; return (n1l < n2l); }); @@ -1129,7 +1295,7 @@ bool NotificationManager::activate_existing(const NotificationManager::PopNotifi NotificationType new_type = notification->get_type(); const std::string &new_text = notification->get_data().text1; for (auto it = m_pop_notifications.begin(); it != m_pop_notifications.end(); ++it) { - if ((*it)->get_type() == new_type && !(*it)->get_finished()) { + if ((*it)->get_type() == new_type && !(*it)->is_finished()) { if (new_type == NotificationType::CustomNotification || new_type == NotificationType::PlaterWarning) { if (!(*it)->compare_text(new_text)) continue; @@ -1162,6 +1328,78 @@ void NotificationManager::set_in_preview(bool preview) } } +#if ENABLE_NEW_NOTIFICATIONS_FADE_OUT +void NotificationManager::update_notifications() +{ + static size_t last_size = 0; + + for (auto it = m_pop_notifications.begin(); it != m_pop_notifications.end();) { + std::unique_ptr& notification = *it; + if (notification->get_state() == PopNotification::EState::Finished) + it = m_pop_notifications.erase(it); + else { + notification->set_paused(m_hovered); + notification->update_state(); + ++it; + } + } + + m_requires_update = false; + for (const std::unique_ptr& notification : m_pop_notifications) { + if (notification->requires_update()) { + m_requires_update = true; + break; + } + } + + // update hovering state + m_hovered = false; + for (const std::unique_ptr& notification : m_pop_notifications) { + if (notification->is_hovered()) { + m_hovered = true; + break; + } + } + + size_t curr_size = m_pop_notifications.size(); + m_requires_render = m_hovered || (last_size != curr_size); + last_size = curr_size; + + if (!m_requires_render) { + for (const std::unique_ptr& notification : m_pop_notifications) { + if (notification->requires_render()) { + m_requires_render = true; + break; + } + } + } + + // actualizate timers + wxWindow* p = dynamic_cast(wxGetApp().plater()); + while (p->GetParent() != nullptr) + p = p->GetParent(); + wxTopLevelWindow* top_level_wnd = dynamic_cast(p); + if (!top_level_wnd->IsActive()) + return; + + { + // Control the fade-out. + // time in seconds + long now = wxGetLocalTime(); + // Pausing fade-out when the mouse is over some notification. + if (!m_hovered && m_last_time < now) { + if (now - m_last_time >= 1) { + for (auto& notification : m_pop_notifications) { + if (notification->get_state() != PopNotification::EState::Static) + notification->substract_remaining_time(); + } + } + m_last_time = now; + } + } +} +#endif // ENABLE_NEW_NOTIFICATIONS_FADE_OUT + bool NotificationManager::has_slicing_error_notification() { return std::any_of(m_pop_notifications.begin(), m_pop_notifications.end(), [](auto &n) { diff --git a/src/slic3r/GUI/NotificationManager.hpp b/src/slic3r/GUI/NotificationManager.hpp index da544391d7..81c57eccb3 100644 --- a/src/slic3r/GUI/NotificationManager.hpp +++ b/src/slic3r/GUI/NotificationManager.hpp @@ -87,16 +87,16 @@ public: NotificationManager(wxEvtHandler* evt_handler); // Push a prefabricated notification from basic_notifications (see the table at the end of this file). - void push_notification(const NotificationType type, GLCanvas3D& canvas, int timestamp = 0); + void push_notification(const NotificationType type, int timestamp = 0); // Push a NotificationType::CustomNotification with NotificationLevel::RegularNotification and 10s fade out interval. - void push_notification(const std::string& text, GLCanvas3D& canvas, int timestamp = 0); + void push_notification(const std::string& text, int timestamp = 0); // Push a NotificationType::CustomNotification with provided notification level and 10s for RegularNotification. // ErrorNotification and ImportantNotification are never faded out. - void push_notification(const std::string& text, NotificationLevel level, GLCanvas3D& canvas, int timestamp = 0); + void push_notification(const std::string& text, NotificationLevel level, int timestamp = 0); // Creates Slicing Error notification with a custom text and no fade out. - void push_slicing_error_notification(const std::string& text, GLCanvas3D& canvas); + void push_slicing_error_notification(const std::string& text); // Creates Slicing Warning notification with a custom text and no fade out. - void push_slicing_warning_notification(const std::string& text, bool gray, GLCanvas3D& canvas, ObjectID oid, int warning_step); + void push_slicing_warning_notification(const std::string& text, bool gray, ObjectID oid, int warning_step); // marks slicing errors as gray void set_all_slicing_errors_gray(bool g); // marks slicing warings as gray @@ -108,39 +108,45 @@ public: // living_oids is expected to be sorted. void remove_slicing_warnings_of_released_objects(const std::vector& living_oids); // Object partially outside of the printer working space, cannot print. No fade out. - void push_plater_error_notification(const std::string& text, GLCanvas3D& canvas); + void push_plater_error_notification(const std::string& text); // Object fully out of the printer working space and such. No fade out. - void push_plater_warning_notification(const std::string& text, GLCanvas3D& canvas); + void push_plater_warning_notification(const std::string& text); // Closes error or warning of the same text void close_plater_error_notification(const std::string& text); void close_plater_warning_notification(const std::string& text); // Creates special notification slicing complete. // If large = true (Plater side bar is closed), then printing time and export button is shown // at the notification and fade-out is disabled. Otherwise the fade out time is set to 10s. - void push_slicing_complete_notification(GLCanvas3D& canvas, int timestamp, bool large); + void push_slicing_complete_notification(int timestamp, bool large); // Add a print time estimate to an existing SlicingComplete notification. void set_slicing_complete_print_time(const std::string &info); // Called when the side bar changes its visibility, as the "slicing complete" notification supplements // the "slicing info" normally shown at the side bar. void set_slicing_complete_large(bool large); // Exporting finished, show this information with path, button to open containing folder and if ejectable - eject button - void push_exporting_finished_notification(GLCanvas3D& canvas, std::string path, std::string dir_path, bool on_removable); + void push_exporting_finished_notification(const std::string& path, const std::string& dir_path, bool on_removable); // notification with progress bar - void push_progress_bar_notification(const std::string& text, GLCanvas3D& canvas, float percentage = 0); - void set_progress_bar_percentage(const std::string& text, float percentage, GLCanvas3D& canvas); + void push_progress_bar_notification(const std::string& text, float percentage = 0); + void set_progress_bar_percentage(const std::string& text, float percentage); // Close old notification ExportFinished. void new_export_began(bool on_removable); // finds ExportFinished notification and closes it if it was to removable device void device_ejected(); // renders notifications in queue and deletes expired ones - void render_notifications(GLCanvas3D& canvas, float overlay_width); + void render_notifications(float overlay_width); // finds and closes all notifications of given type void close_notification_of_type(const NotificationType type); // Which view is active? Plater or G-code preview? Hide warnings in G-code preview. void set_in_preview(bool preview); // Move to left to avoid colision with variable layer height gizmo. void set_move_from_overlay(bool move) { m_move_from_overlay = move; } - + +#if ENABLE_NEW_NOTIFICATIONS_FADE_OUT + void update_notifications(); + bool requires_update() const { return m_requires_update; } + bool requires_render() const { return m_requires_render; } +#endif // ENABLE_NEW_NOTIFICATIONS_FADE_OUT + private: // duration 0 means not disapearing struct NotificationData { @@ -175,6 +181,17 @@ private: class PopNotification { public: +#if ENABLE_NEW_NOTIFICATIONS_FADE_OUT + enum class EState + { + Unknown, + Static, + Countdown, + FadingOut, + ClosePending, + Finished + }; +#else enum class RenderResult { Finished, @@ -183,27 +200,41 @@ private: Countdown, Hovered }; +#endif // ENABLE_NEW_NOTIFICATIONS_FADE_OUT PopNotification(const NotificationData &n, NotificationIDProvider &id_provider, wxEvtHandler* evt_handler); virtual ~PopNotification() { if (m_id) m_id_provider.release_id(m_id); } +#if ENABLE_NEW_NOTIFICATIONS_FADE_OUT + void render(GLCanvas3D& canvas, float initial_y, bool move_from_overlay, float overlay_width); +#else RenderResult render(GLCanvas3D& canvas, const float& initial_y, bool move_from_overlay, float overlay_width); +#endif // ENABLE_NEW_NOTIFICATIONS_FADE_OUT // close will dissapear notification on next render void close() { m_close_pending = true; } // data from newer notification of same type void update(const NotificationData& n); - bool get_finished() const { return m_finished || m_close_pending; } + bool is_finished() const { return m_finished || m_close_pending; } +#if ENABLE_NEW_NOTIFICATIONS_FADE_OUT + bool is_hovered() const { return m_hovered; } +#endif // ENABLE_NEW_NOTIFICATIONS_FADE_OUT // returns top after movement float get_top() const { return m_top_y; } //returns top in actual frame float get_current_top() const { return m_top_y; } const NotificationType get_type() const { return m_data.type; } - const NotificationData get_data() const { return m_data; } - const bool get_is_gray() const { return m_is_gray; } + const NotificationData get_data() const { return m_data; } + const bool is_gray() const { return m_is_gray; } // Call equals one second down void substract_remaining_time() { m_remaining_time--; } void set_gray(bool g) { m_is_gray = g; } void set_paused(bool p) { m_paused = p; } bool compare_text(const std::string& text); void hide(bool h) { m_hidden = h; } +#if ENABLE_NEW_NOTIFICATIONS_FADE_OUT + void update_state(); + bool requires_render() const { return m_fading_out || m_close_pending || m_finished; } + bool requires_update() const { return m_state != EState::Static; } + EState get_state() const { return m_state; } +#endif // ENABLE_NEW_NOTIFICATIONS_FADE_OUT protected: // Call after every size change @@ -218,9 +249,11 @@ private: virtual void render_close_button(ImGuiWrapper& imgui, const float win_size_x, const float win_size_y, const float win_pos_x , const float win_pos_y); +#if !ENABLE_NEW_NOTIFICATIONS_FADE_OUT void render_countdown(ImGuiWrapper& imgui, const float win_size_x, const float win_size_y, const float win_pos_x , const float win_pos_y); +#endif // !ENABLE_NEW_NOTIFICATIONS_FADE_OUT virtual void render_hypertext(ImGuiWrapper& imgui, const float text_x, const float text_y, const std::string text, @@ -237,7 +270,10 @@ private: // For reusing ImGUI windows. NotificationIDProvider &m_id_provider; - int m_id { 0 }; +#if ENABLE_NEW_NOTIFICATIONS_FADE_OUT + EState m_state { EState::Unknown }; +#endif // ENABLE_NEW_NOTIFICATIONS_FADE_OUT + int m_id { 0 }; bool m_initialized { false }; // Main text std::string m_text1; @@ -252,15 +288,22 @@ private: bool m_paused { false }; int m_countdown_frame { 0 }; bool m_fading_out { false }; +#if ENABLE_NEW_NOTIFICATIONS_FADE_OUT + wxMilliClock_t m_fading_start { 0LL }; +#else // total time left when fading beggins - float m_fading_time { 0.0f }; - float m_current_fade_opacity { 1.f }; + float m_fading_time{ 0.0f }; +#endif // ENABLE_NEW_NOTIFICATIONS_FADE_OUT + float m_current_fade_opacity { 1.0f }; // If hidden the notif is alive but not visible to user bool m_hidden { false }; // m_finished = true - does not render, marked to delete bool m_finished { false }; // Will go to m_finished next render bool m_close_pending { false }; +#if ENABLE_NEW_NOTIFICATIONS_FADE_OUT + bool m_hovered { false }; +#endif // ENABLE_NEW_NOTIFICATIONS_FADE_OUT // variables to count positions correctly // all space without text float m_window_width_offset; @@ -366,8 +409,8 @@ private: //pushes notification into the queue of notifications that are rendered //can be used to create custom notification - bool push_notification_data(const NotificationData& notification_data, GLCanvas3D& canvas, int timestamp); - bool push_notification_data(std::unique_ptr notification, GLCanvas3D& canvas, int timestamp); + bool push_notification_data(const NotificationData& notification_data, int timestamp); + bool push_notification_data(std::unique_ptr notification, int timestamp); //finds older notification of same type and moves it to the end of queue. returns true if found bool activate_existing(const NotificationManager::PopNotification* notification); // Put the more important notifications to the bottom of the list. @@ -390,6 +433,10 @@ private: bool m_in_preview { false }; // True if the layer editing is enabled in Plater, so that the notifications are shifted left of it. bool m_move_from_overlay { false }; +#if ENABLE_NEW_NOTIFICATIONS_FADE_OUT + bool m_requires_update{ false }; + bool m_requires_render{ false }; +#endif // ENABLE_NEW_NOTIFICATIONS_FADE_OUT //prepared (basic) notifications const std::vector basic_notifications = { diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index 91bf11992a..c3b278333d 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -2115,12 +2115,12 @@ Plater::priv::priv(Plater *q, MainFrame *main_frame) if (evt.data.second) { this->show_action_buttons(this->ready_to_slice); notification_manager->close_notification_of_type(NotificationType::ExportFinished); - notification_manager->push_notification(format(_L("Successfully unmounted. The device %s(%s) can now be safely removed from the computer."),evt.data.first.name, evt.data.first.path), - NotificationManager::NotificationLevel::RegularNotification, *q->get_current_canvas3D()); - } else { - notification_manager->push_notification(format(_L("Ejecting of device %s(%s) has failed."), evt.data.first.name, evt.data.first.path), - NotificationManager::NotificationLevel::ErrorNotification, *q->get_current_canvas3D()); - } + notification_manager->push_notification(format(_L("Successfully unmounted. The device %s(%s) can now be safely removed from the computer."), evt.data.first.name, evt.data.first.path), + NotificationManager::NotificationLevel::RegularNotification); + } else { + notification_manager->push_notification(format(_L("Ejecting of device %s(%s) has failed."), evt.data.first.name, evt.data.first.path), + NotificationManager::NotificationLevel::ErrorNotification); + } }); this->q->Bind(EVT_REMOVABLE_DRIVES_CHANGED, [this, q](RemovableDrivesChangedEvent &) { this->show_action_buttons(this->ready_to_slice); @@ -2948,7 +2948,7 @@ unsigned int Plater::priv::update_background_process(bool force_validation, bool } else { // The print is not valid. // Show error as notification. - notification_manager->push_slicing_error_notification(err, *q->get_current_canvas3D()); + notification_manager->push_slicing_error_notification(err); return_state |= UPDATE_BACKGROUND_PROCESS_INVALID; } } else if (! this->delayed_error_message.empty()) { @@ -3509,7 +3509,7 @@ void Plater::priv::on_slicing_update(SlicingStatusEvent &evt) this->statusbar()->set_progress(evt.status.percent); this->statusbar()->set_status_text(_(evt.status.text) + wxString::FromUTF8("…")); - //notification_manager->set_progress_bar_percentage("Slicing progress", (float)evt.status.percent / 100.0f, *q->get_current_canvas3D()); + //notification_manager->set_progress_bar_percentage("Slicing progress", (float)evt.status.percent / 100.0f); } if (evt.status.flags & (PrintBase::SlicingStatus::RELOAD_SCENE | PrintBase::SlicingStatus::RELOAD_SLA_SUPPORT_POINTS)) { switch (this->printer_technology) { @@ -3551,8 +3551,8 @@ void Plater::priv::on_slicing_update(SlicingStatusEvent &evt) // Now process state.warnings. for (auto const& warning : state.warnings) { if (warning.current) { - notification_manager->push_slicing_warning_notification(warning.message, false, *q->get_current_canvas3D(), object_id, warning_step); - add_warning(warning, object_id.id); + notification_manager->push_slicing_warning_notification(warning.message, false, object_id, warning_step); + add_warning(warning, object_id.id); } } } @@ -3560,7 +3560,7 @@ void Plater::priv::on_slicing_update(SlicingStatusEvent &evt) void Plater::priv::on_slicing_completed(wxCommandEvent & evt) { - notification_manager->push_slicing_complete_notification(*q->get_current_canvas3D(), evt.GetInt(), is_sidebar_collapsed()); + notification_manager->push_slicing_complete_notification(evt.GetInt(), is_sidebar_collapsed()); switch (this->printer_technology) { case ptFFF: this->update_fff_scene(); @@ -3655,7 +3655,7 @@ void Plater::priv::on_process_completed(SlicingProcessCompletedEvent &evt) else show_error(q, message.first, message.second); } else - notification_manager->push_slicing_error_notification(message.first, *q->get_current_canvas3D()); + notification_manager->push_slicing_error_notification(message.first); this->statusbar()->set_status_text(from_u8(message.first)); if (evt.invalidate_plater()) { @@ -3701,10 +3701,10 @@ void Plater::priv::on_process_completed(SlicingProcessCompletedEvent &evt) // If writing to removable drive was scheduled, show notification with eject button if (exporting_status == ExportingStatus::EXPORTING_TO_REMOVABLE && !has_error) { show_action_buttons(false); - notification_manager->push_exporting_finished_notification(*q->get_current_canvas3D(), last_output_path, last_output_dir_path, true); + notification_manager->push_exporting_finished_notification(last_output_path, last_output_dir_path, true); wxGetApp().removable_drive_manager()->set_exporting_finished(true); }else if (exporting_status == ExportingStatus::EXPORTING_TO_LOCAL && !has_error) - notification_manager->push_exporting_finished_notification(*q->get_current_canvas3D(), last_output_path, last_output_dir_path, false); + notification_manager->push_exporting_finished_notification(last_output_path, last_output_dir_path, false); } exporting_status = ExportingStatus::NOT_EXPORTING; } diff --git a/src/slic3r/GUI/PresetHints.cpp b/src/slic3r/GUI/PresetHints.cpp index c40c4c6acb..9f40ffe5bd 100644 --- a/src/slic3r/GUI/PresetHints.cpp +++ b/src/slic3r/GUI/PresetHints.cpp @@ -18,40 +18,43 @@ std::string PresetHints::cooling_description(const Preset &preset) { std::string out; - if (preset.config.opt_bool("cooling", 0)) { + bool cooling = preset.config.opt_bool("cooling", 0); + int fan_below_layer_time = preset.config.opt_int("fan_below_layer_time", 0); + int full_fan_speed_layer = preset.config.opt_int("full_fan_speed_layer", 0); + + if (cooling) { int slowdown_below_layer_time = preset.config.opt_int("slowdown_below_layer_time", 0); int min_fan_speed = preset.config.opt_int("min_fan_speed", 0); int max_fan_speed = preset.config.opt_int("max_fan_speed", 0); int min_print_speed = int(preset.config.opt_float("min_print_speed", 0) + 0.5); - int fan_below_layer_time = preset.config.opt_int("fan_below_layer_time", 0); - out += (boost::format(_utf8(L("If estimated layer time is below ~%1%s, " - "fan will run at %2%%% and print speed will be reduced " - "so that no less than %3%s are spent on that layer " - "(however, speed will never be reduced below %4%mm/s)."))) - % slowdown_below_layer_time % max_fan_speed % slowdown_below_layer_time % min_print_speed).str(); - - if (fan_below_layer_time > slowdown_below_layer_time) { - out += "\n" + (boost::format(_utf8(L("If estimated layer time is greater, but still below ~%1%s, " - "fan will run at a proportionally decreasing speed between %2%%% and %3%%%."))) - % fan_below_layer_time % max_fan_speed % min_fan_speed).str(); - } - out += "\n" + _utf8(L("During the other layers, fan")) + " "; - } else { - out = _utf8(L("Fan")) + " "; + out += GUI::format(_L("If estimated layer time is below ~%1%s, " + "fan will run at %2%%% and print speed will be reduced " + "so that no less than %3%s are spent on that layer " + "(however, speed will never be reduced below %4%mm/s)."), + slowdown_below_layer_time, max_fan_speed, slowdown_below_layer_time, min_print_speed); + if (fan_below_layer_time > slowdown_below_layer_time) + out += "\n" + + GUI::format(_L("If estimated layer time is greater, but still below ~%1%s, " + "fan will run at a proportionally decreasing speed between %2%%% and %3%%%."), + fan_below_layer_time, max_fan_speed, min_fan_speed); + out += "\n"; } if (preset.config.opt_bool("fan_always_on", 0)) { int disable_fan_first_layers = preset.config.opt_int("disable_fan_first_layers", 0); int min_fan_speed = preset.config.opt_int("min_fan_speed", 0); - out += (boost::format(_utf8(L("will always run at %1%%%"))) % min_fan_speed).str() + " "; - - if (disable_fan_first_layers > 1) - out += (boost::format(_utf8(L("except for the first %1% layers."))) % disable_fan_first_layers).str(); - else if (disable_fan_first_layers == 1) - out += _utf8(L("except for the first layer.")); + if (full_fan_speed_layer > fan_below_layer_time + 1) + out += GUI::format(_L("Fan speed will be ramped from zero at layer %1% to %2%%% at layer %3%."), disable_fan_first_layers, min_fan_speed, full_fan_speed_layer); + else { + out += GUI::format(cooling ? _L("During the other layers, fan will always run at %1%%%") : _L("Fan will always run at %1%%%"), min_fan_speed) + " "; + if (disable_fan_first_layers > 1) + out += GUI::format(_L("except for the first %1% layers."), disable_fan_first_layers); + else if (disable_fan_first_layers == 1) + out += GUI::format(_L("except for the first layer.")); + } } else - out += _utf8(L("will be turned off.")); + out += cooling ? _u8L("During the other layers, fan will be turned off.") : _u8L("Fan will be turned off."); return out; } diff --git a/src/slic3r/GUI/Tab.cpp b/src/slic3r/GUI/Tab.cpp index d125f8da68..57883e909a 100644 --- a/src/slic3r/GUI/Tab.cpp +++ b/src/slic3r/GUI/Tab.cpp @@ -1847,6 +1847,7 @@ void TabFilament::build() optgroup->append_single_option_line("bridge_fan_speed", category_path + "fan-settings"); optgroup->append_single_option_line("disable_fan_first_layers", category_path + "fan-settings"); + optgroup->append_single_option_line("full_fan_speed_layer", category_path + "fan-settings"); optgroup = page->new_optgroup(L("Cooling thresholds"), 25); optgroup->append_single_option_line("fan_below_layer_time", category_path + "cooling-thresholds"); @@ -1999,7 +2000,7 @@ void TabFilament::toggle_options() for (auto el : { "max_fan_speed", "fan_below_layer_time", "slowdown_below_layer_time", "min_print_speed" }) toggle_option(el, cooling); - for (auto el : { "min_fan_speed", "disable_fan_first_layers" }) + for (auto el : { "min_fan_speed", "disable_fan_first_layers", "full_fan_speed_layer" }) toggle_option(el, fan_always_on); } diff --git a/src/slic3r/Utils/PresetUpdater.cpp b/src/slic3r/Utils/PresetUpdater.cpp index 10de8b0efe..60dfe05c78 100644 --- a/src/slic3r/Utils/PresetUpdater.cpp +++ b/src/slic3r/Utils/PresetUpdater.cpp @@ -827,7 +827,7 @@ PresetUpdater::UpdateResult PresetUpdater::config_update(const Semver& old_slic3 } } else { p->set_waiting_updates(updates); - GUI::wxGetApp().plater()->get_notification_manager()->push_notification(GUI::NotificationType::PresetUpdateAvailable, *(GUI::wxGetApp().plater()->get_current_canvas3D())); + GUI::wxGetApp().plater()->get_notification_manager()->push_notification(GUI::NotificationType::PresetUpdateAvailable); } // MsgUpdateConfig will show after the notificaation is clicked