diff --git a/src/libslic3r/AppConfig.cpp b/src/libslic3r/AppConfig.cpp index e592155d14..16f6a4a606 100644 --- a/src/libslic3r/AppConfig.cpp +++ b/src/libslic3r/AppConfig.cpp @@ -111,6 +111,11 @@ void AppConfig::set_defaults() if (get("background_processing").empty()) set_bool("background_processing", false); #endif + if (get("auto_slice_after_change").empty()) + set_bool("auto_slice_after_change", false); + + if (get("auto_slice_change_delay_seconds").empty()) + set("auto_slice_change_delay_seconds", "1"); if (get("drop_project_action").empty()) set_bool("drop_project_action", true); diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index b994c375d1..baa3c6d6fb 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -5,6 +5,7 @@ #include #include #include +#include #include #include #include @@ -4104,6 +4105,8 @@ struct Plater::priv bool m_ignore_event{false}; bool m_slice_all{false}; bool m_is_slicing {false}; + bool auto_reslice_pending {false}; + bool auto_reslice_after_cancel {false}; bool m_is_publishing {false}; int m_is_RightClickInLeftUI{-1}; int m_cur_slice_plate; @@ -4148,6 +4151,7 @@ struct Plater::priv std::string delayed_error_message; wxTimer background_process_timer; + wxTimer auto_reslice_timer; std::string label_btn_export; std::string label_btn_send; @@ -4357,6 +4361,9 @@ struct Plater::priv std::vector> get_extruder_filament_info(); void update_print_volume_state(); void schedule_background_process(); + void schedule_auto_reslice_if_needed(); + void trigger_auto_reslice_now(); + int auto_slice_delay_seconds() const; // Update background processing thread from the current config and Model. enum UpdateBackgroundProcessReturnState { // update_background_process() reports, that the Print / SLAPrint was updated in a way, @@ -4742,10 +4749,18 @@ Plater::priv::priv(Plater *q, MainFrame *main_frame) panels.push_back(assemble_view); this->background_process_timer.SetOwner(this->q, 0); + this->auto_reslice_timer.SetOwner(this->q, 0); this->q->Bind(wxEVT_TIMER, [this](wxTimerEvent &evt) { - if (!this->suppressed_backround_processing_update) - this->update_restart_background_process(false, false); + if (&evt.GetTimer() == &this->background_process_timer) { + if (!this->suppressed_backround_processing_update) + this->update_restart_background_process(false, false); + } else if (&evt.GetTimer() == &this->auto_reslice_timer) { + this->auto_reslice_timer.Stop(); + this->trigger_auto_reslice_now(); + } else { + evt.Skip(); + } }); update(); @@ -7285,6 +7300,90 @@ void Plater::priv::schedule_background_process() this->view3D->get_canvas3d()->set_config(this->config); } +void Plater::priv::schedule_auto_reslice_if_needed() +{ + AppConfig* cfg = wxGetApp().app_config; + if (cfg == nullptr || !cfg->get_bool("auto_slice_after_change")) + return; + + if (model.objects.empty()) + return; + + PartPlate* plate = partplate_list.get_curr_plate(); + if (plate == nullptr || !plate->has_printable_instances()) + return; + + if (background_process.running() || m_is_slicing) { + // Remember to restart once the current slice stops and cancel it now. + auto_reslice_after_cancel = true; + background_process.stop(); + return; + } + + const int delay_seconds = auto_slice_delay_seconds(); + if (delay_seconds > 0) { + auto_reslice_pending = true; + auto_reslice_timer.Stop(); + auto_reslice_timer.Start(delay_seconds * 1000, wxTIMER_ONE_SHOT); + return; + } + + if (auto_reslice_pending) + return; + + auto_reslice_pending = true; + auto_reslice_timer.Stop(); + wxGetApp().CallAfter([this]() { this->trigger_auto_reslice_now(); }); +} + +void Plater::priv::trigger_auto_reslice_now() +{ + this->auto_reslice_pending = false; + + AppConfig* cfg = wxGetApp().app_config; + if (cfg == nullptr || !cfg->get_bool("auto_slice_after_change")) + return; + + if (this->model.objects.empty()) + return; + + if (this->background_process.running() || this->m_is_slicing) + return; + + PartPlate* plate = this->partplate_list.get_curr_plate(); + if (plate == nullptr || !plate->has_printable_instances()) + return; + + this->q->reslice(); +} + +int Plater::priv::auto_slice_delay_seconds() const +{ + AppConfig* cfg = wxGetApp().app_config; + if (cfg == nullptr) + return 0; + + std::string delay_str = cfg->get("auto_slice_change_delay_seconds"); + if (delay_str.empty()) + return 0; + + long delay_seconds = 0; + try { + delay_seconds = std::stol(delay_str); + } catch (...) { + delay_seconds = 0; + } + + if (delay_seconds < 0) + delay_seconds = 0; + + const long max_seconds = std::numeric_limits::max() / 1000; + if (delay_seconds > max_seconds) + delay_seconds = max_seconds; + + return static_cast(delay_seconds); +} + std::vector> Plater::priv::get_extruder_filament_info() { std::vector> filament_infos; @@ -9446,6 +9545,11 @@ void Plater::priv::on_process_completed(SlicingProcessCompletedEvent &evt) } } } + if (auto_reslice_after_cancel) { + auto_reslice_after_cancel = false; + schedule_auto_reslice_if_needed(); + } + BOOST_LOG_TRIVIAL(debug) << __FUNCTION__ << boost::format(", exit."); } @@ -15942,6 +16046,7 @@ void Plater::on_config_change(const DynamicPrintConfig &config) if (p->main_frame->is_loaded()) { this->p->schedule_background_process(); update_title_dirty_status(); + p->schedule_auto_reslice_if_needed(); } } diff --git a/src/slic3r/GUI/Preferences.cpp b/src/slic3r/GUI/Preferences.cpp index d5520162e4..db330f84c8 100644 --- a/src/slic3r/GUI/Preferences.cpp +++ b/src/slic3r/GUI/Preferences.cpp @@ -677,6 +677,74 @@ wxBoxSizer *PreferencesDialog::create_item_backup(wxString title, wxString toolt return m_sizer_input; } +wxBoxSizer *PreferencesDialog::create_item_auto_reslice(wxString title, wxString checkbox_tooltip, wxString delay_tooltip) +{ + wxBoxSizer *sizer_row = new wxBoxSizer(wxHORIZONTAL); + + sizer_row->AddSpacer(FromDIP(DESIGN_LEFT_MARGIN)); + + auto checkbox_title = new wxStaticText(m_parent, wxID_ANY, title, wxDefaultPosition, DESIGN_TITLE_SIZE, wxST_NO_AUTORESIZE); + checkbox_title->SetForegroundColour(DESIGN_GRAY900_COLOR); + checkbox_title->SetFont(::Label::Body_14); + checkbox_title->Wrap(DESIGN_TITLE_SIZE.x); + checkbox_title->SetToolTip(checkbox_tooltip); + + auto checkbox = new ::CheckBox(m_parent); + checkbox->SetValue(app_config->get_bool("auto_slice_after_change")); + checkbox->SetToolTip(checkbox_tooltip); + + wxString delay_value = app_config->get("auto_slice_change_delay_seconds"); + if (delay_value.empty()) + delay_value = "0"; + + auto input = new ::TextInput(m_parent, wxEmptyString, _L("sec"), wxEmptyString, wxDefaultPosition, wxSize(FromDIP(97), -1), wxTE_PROCESS_ENTER); + StateColor input_bg(std::pair(wxColour("#F0F0F1"), StateColor::Disabled), std::pair(*wxWHITE, StateColor::Enabled)); + input->SetBackgroundColor(input_bg); + input->GetTextCtrl()->SetValue(delay_value); + wxTextValidator validator(wxFILTER_DIGITS); + input->SetToolTip(delay_tooltip); + input->GetTextCtrl()->SetValidator(validator); + + sizer_row->Add(checkbox_title, 0, wxALIGN_CENTER | wxTOP | wxBOTTOM, FromDIP(3)); + sizer_row->Add(checkbox, 0, wxALIGN_CENTER | wxLEFT | wxRIGHT, FromDIP(5)); + sizer_row->Add(input, 0, wxALIGN_CENTER_VERTICAL); + + auto commit_delay = [this, input]() { + wxString value = input->GetTextCtrl()->GetValue(); + long seconds = 0; + if (!value.ToLong(&seconds) || seconds < 0) + seconds = 0; + wxString sanitized = wxString::Format("%ld", seconds); + input->GetTextCtrl()->SetValue(sanitized); + app_config->set("auto_slice_change_delay_seconds", std::string(sanitized.mb_str())); + app_config->save(); + }; + + input->GetTextCtrl()->Bind(wxEVT_TEXT_ENTER, [commit_delay](wxCommandEvent &e) { + commit_delay(); + e.Skip(); + }); + + input->GetTextCtrl()->Bind(wxEVT_KILL_FOCUS, [commit_delay](wxFocusEvent &e) { + commit_delay(); + e.Skip(); + }); + + checkbox->Bind(wxEVT_TOGGLEBUTTON, [this, checkbox, input](wxCommandEvent &e) { + const bool enabled = checkbox->GetValue(); + app_config->set_bool("auto_slice_after_change", enabled); + app_config->save(); + input->Enable(enabled); + input->Refresh(); + e.Skip(); + }); + + input->Enable(checkbox->GetValue()); + input->Refresh(); + + return sizer_row; +} + wxBoxSizer* PreferencesDialog::create_item_darkmode(wxString title,wxString tooltip, std::string param) { wxBoxSizer* m_sizer_checkbox = new wxBoxSizer(wxHORIZONTAL); @@ -1256,6 +1324,12 @@ void PreferencesDialog::create_items() auto item_auto_arrange = create_item_checkbox(_L("Auto arrange plate after cloning"), "", "auto_arrange"); g_sizer->Add(item_auto_arrange); + + auto item_auto_reslice = create_item_auto_reslice( + _L("Auto slice after changes"), + _L("If enabled, OrcaSlicer will re-slice automatically whenever slicing-related settings change."), + _L("Delay in seconds before auto slicing starts, allowing multiple edits to be grouped. Use 0 to slice immediately.")); + g_sizer->Add(item_auto_reslice); //// CONTROL > Camera g_sizer->Add(create_item_title(_L("Camera")), 1, wxEXPAND); diff --git a/src/slic3r/GUI/Preferences.hpp b/src/slic3r/GUI/Preferences.hpp index c6fa92a498..ecd591e089 100644 --- a/src/slic3r/GUI/Preferences.hpp +++ b/src/slic3r/GUI/Preferences.hpp @@ -90,6 +90,7 @@ public: wxBoxSizer *create_item_input(wxString title, wxString title2, wxString tooltip, std::string param, std::function onchange = {}); wxBoxSizer *create_camera_orbit_mult_input(wxString title, wxString tooltip); wxBoxSizer *create_item_backup(wxString title, wxString tooltip); + wxBoxSizer *create_item_auto_reslice(wxString title, wxString checkbox_tooltip, wxString delay_tooltip); wxBoxSizer *create_item_multiple_combobox(wxString title, wxString tooltip, std::string parama, std::vector vlista, std::vector vlistb); #ifdef WIN32 wxBoxSizer *create_item_link_association(wxString url_prefix, wxString website_name);