diff --git a/src/libslic3r/PrintBase.cpp b/src/libslic3r/PrintBase.cpp index 4448c12c76..99ac989d14 100644 --- a/src/libslic3r/PrintBase.cpp +++ b/src/libslic3r/PrintBase.cpp @@ -19,7 +19,7 @@ void PrintTryCancel::operator()() m_print->throw_if_canceled(); } -size_t PrintStateBase::g_last_timestamp = 0; +std::atomic PrintStateBase::g_last_timestamp{0}; // Update "scale", "input_filename", "input_filename_base", "first_object_name" placeholders from the current m_objects. void PrintBase::update_object_placeholders(DynamicConfig &config, const std::string &default_ext) const @@ -107,11 +107,26 @@ std::string PrintBase::output_filepath(const std::string &path, const std::strin return path; } +void PrintBase::set_status_callback(status_callback_type cb) +{ + std::scoped_lock lock(m_status_callback_mutex); + m_status_callback = std::move(cb); +} + +// Returns a copy, so that the callback is invoked with m_status_callback_mutex released: the callback +// may block on the UI thread, which in turn may be assigning a new callback. +PrintBase::status_callback_type PrintBase::status_callback() const +{ + std::scoped_lock lock(m_status_callback_mutex); + return m_status_callback; +} + //BBS: move set_status from hpp to cpp void PrintBase::set_status(int percent, const std::string &message, unsigned int flags, int warning_step) const { - if (m_status_callback) - m_status_callback(SlicingStatus(percent, message, flags, warning_step)); + status_callback_type status_callback = this->status_callback(); + if (status_callback) + status_callback(SlicingStatus(percent, message, flags, warning_step)); else BOOST_LOG_TRIVIAL(debug) <m_status_callback) { + status_callback_type status_callback = this->status_callback(); + if (status_callback) { auto status = print_object ? SlicingStatus(*print_object, step, message, message_id, warning_level) : SlicingStatus(*this, step, message, message_id, warning_level); - m_status_callback(status); + status_callback(status); } else if (! message.empty()) BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format(", Print warning: %1%\n")% message.c_str(); @@ -132,8 +148,9 @@ void PrintBase::status_update_warnings(int step, PrintStateBase::WarningLevel wa const std::string& message, PrintObjectBase &object, PrintStateBase::SlicingNotificationType message_id) { //BBS: add object it into slicing status - if (this->m_status_callback) { - m_status_callback(SlicingStatus(object, step, message, message_id, warning_level)); + status_callback_type status_callback = this->status_callback(); + if (status_callback) { + status_callback(SlicingStatus(object, step, message, message_id, warning_level)); } else if (!message.empty()) BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format(", PrintObject warning: %1%\n")% message.c_str(); diff --git a/src/libslic3r/PrintBase.hpp b/src/libslic3r/PrintBase.hpp index f1b158e51f..76b4e92605 100644 --- a/src/libslic3r/PrintBase.hpp +++ b/src/libslic3r/PrintBase.hpp @@ -99,10 +99,9 @@ public: }; protected: - //FIXME last timestamp is shared between Print & SLAPrint, - // and if multiple Print or SLAPrint instances are executed in parallel, modification of g_last_timestamp - // is not synchronized! - static size_t g_last_timestamp; + // The last timestamp is shared between all the Print & SLAPrint instances, and Orca keeps one Print + // per PartPlate, so it is incremented under different state mutexes: it has to be atomic. + static std::atomic g_last_timestamp; }; // To be instantiated over PrintStep or PrintObjectStep enums. @@ -473,11 +472,12 @@ public: }; typedef std::function status_callback_type; // Default status console print out in the form of percent => message. - void set_status_default() { m_status_callback = nullptr; } + void set_status_default() { this->set_status_callback(nullptr); } // No status output or callback whatsoever, useful mostly for automatic tests. - void set_status_silent() { m_status_callback = [](const SlicingStatus&){}; } - // Register a custom status callback. - void set_status_callback(status_callback_type cb) { m_status_callback = cb; } + void set_status_silent() { this->set_status_callback([](const SlicingStatus&){}); } + // Register a custom status callback. Called from the UI thread while the worker thread may be + // invoking the previous callback, therefore guarded by m_status_callback_mutex. + void set_status_callback(status_callback_type cb); // Calls a registered callback to update the status, or print out the default message. void set_status(int percent, const std::string &message, unsigned int flags = SlicingStatus::DEFAULT, int warning_step = -1) const; @@ -563,7 +563,10 @@ protected: std::string m_plate_name; // Callback to be evoked regularly to update state of the UI thread. + // Guarded by m_status_callback_mutex, always invoke the copy returned by status_callback(). status_callback_type m_status_callback; + mutable std::mutex m_status_callback_mutex; + status_callback_type status_callback() const; private: std::atomic m_cancel_status; diff --git a/src/slic3r/GUI/GUI_ObjectList.cpp b/src/slic3r/GUI/GUI_ObjectList.cpp index 99c023e4a1..f22f6b519b 100644 --- a/src/slic3r/GUI/GUI_ObjectList.cpp +++ b/src/slic3r/GUI/GUI_ObjectList.cpp @@ -6693,6 +6693,11 @@ void ObjectList::OnEditingStarted(wxDataViewEvent &event) void ObjectList::OnEditingDone(wxDataViewEvent &event) { + // ~wxDataViewCtrl ends the in-place editing, so this handler runs while ~Plater is already tearing + // the Plater down. Nothing below may touch the Plater or the plates any more. + if (wxGetApp().is_closing()) + return; + if (event.GetColumn() != colName) return; diff --git a/src/slic3r/GUI/PartPlate.cpp b/src/slic3r/GUI/PartPlate.cpp index 8e2d6ba37b..1a9f04dfac 100644 --- a/src/slic3r/GUI/PartPlate.cpp +++ b/src/slic3r/GUI/PartPlate.cpp @@ -3572,6 +3572,8 @@ void PartPlate::update_slice_result_valid_state(bool valid) //update current slice context into backgroud slicing process void PartPlate::update_slice_context(BackgroundSlicingProcess & process) { + //this callback outlives the call, so it is dropped again in PartPlateList::clear() and + //PartPlateList::delete_plate() before the plate is destroyed auto statuscb = [this](const Slic3r::PrintBase::SlicingStatus& status) { Slic3r::SlicingStatusEvent *event = new Slic3r::SlicingStatusEvent(EVT_SLICING_UPDATE, 0, status); //BBS: GUI refactor: add plate info befor message @@ -4543,7 +4545,14 @@ void PartPlateList::clear(bool delete_plates, bool release_print_list, bool exce else plate->clear(); if (delete_plates) + { + //the slicing status callback installed by update_slice_context() captures the plate, so drop it + //while the Print is still alive: the prints are only released below, after this loop, and are + //not released at all when release_print_list is false. + if (Print* print = plate->fff_print()) + print->set_status_default(); delete plate; + } } if (delete_plates) @@ -4886,6 +4895,10 @@ int PartPlateList::delete_plate(int index) //destroy the print object int print_index; plate->get_print(nullptr, nullptr, &print_index); + //the slicing status callback installed by update_slice_context() captures the plate, and destroy_print() + //frees the Print, so drop the callback here, the last point where both are still alive. + if (Print* print = plate->fff_print()) + print->set_status_default(); destroy_print(print_index); delete plate; diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index 2622fdbc28..9913c39cbc 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -103,7 +103,6 @@ #include "Selection.hpp" #include "GLToolbar.hpp" #include "GUI_Preview.hpp" -#include "UVEditorCanvas.hpp" #include "3DBed.hpp" #include "PartPlate.hpp" #include "Camera.hpp" @@ -1798,14 +1797,11 @@ bool Sidebar::priv::switch_diameter_to(const wxString &diameter) Preset& printer_preset = wxGetApp().preset_bundle->printers.get_edited_preset(); // The combo lists printer variants, and the variant of a mixed-nozzle machine ("0.4+0.6") is no // single extruder's diameter, so the preset's own variant answers first. - const std::string &printer_variant = printer_preset.config.opt_string("printer_variant"); - if (printer_variant == diameter.ToStdString()) { + if (printer_preset.config.opt_string("printer_variant") == diameter.ToStdString()) { return true; } - // A named variant ("0.4 High Flow") shares its diameter with the standard profile, which selecting - // the plain diameter switches back to, so only a preset naming no variant is kept by its diameter. auto* nozzle_diameter = dynamic_cast(printer_preset.config.option("nozzle_diameter")); - if (printer_variant.empty() && nozzle_diameter && nozzle_diameter->size() > 0) { + if (nozzle_diameter && nozzle_diameter->size() > 0) { auto current_nozzle_dia = get_diameter_string(nozzle_diameter->values[0]); // If the selected diameter is the same as current nozzle, don't switch profiles if (current_nozzle_dia == diameter.ToStdString()) { @@ -2240,14 +2236,12 @@ bool Sidebar::priv::sync_extruder_list(bool &only_external_material, bool is_man std::string machine_print_name = obj->get_show_printer_type(); PresetBundle *preset_bundle = wxGetApp().preset_bundle; std::string target_model_id = preset_bundle->printers.get_selected_preset().get_printer_type(preset_bundle); - const bool optional_printer_model = DevPrinterConfigUtil::is_optional_printer_model_id(obj->printer_type); - const bool optional_target_model = DevPrinterConfigUtil::is_optional_printer_model_id(target_model_id); - Preset* machine_preset = optional_printer_model ? nullptr : get_printer_preset(obj); - if (!optional_printer_model && !optional_target_model && !machine_preset) { + Preset* machine_preset = get_printer_preset(obj); + if (!machine_preset) { BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << __LINE__ << "check error: machine_preset empty"; return false; } - if (!optional_printer_model && !optional_target_model && machine_print_name != target_model_id) { + if (machine_print_name != target_model_id) { MessageDialog dlg(this->plater, _L("The currently selected machine preset is inconsistent with the connected printer type.\n" "Are you sure to continue syncing?"), _L("Sync printer information"), wxICON_WARNING | wxYES | wxNO); if (dlg.ShowModal() == wxID_NO) { @@ -2439,11 +2433,6 @@ void Sidebar::priv::update_sync_status(const MachineObject *obj) return; } - if (DevPrinterConfigUtil::is_optional_printer_model_id(obj->printer_type)) { - clear_all_sync_status(); - return; - } - bool printer_synced = false; // 1. update printer status const Preset &cur_preset = wxGetApp().preset_bundle->printers.get_edited_preset(); @@ -3937,18 +3926,13 @@ void Sidebar::update_presets(Preset::Type preset_type) combo_flow->Show(combo_flow->GetCount() > 0); }; - auto update_extruder_diameter = [&diameters, &nozzle_diameter, &diameter](int extruder_index,ExtruderGroup & extruder) { + auto update_extruder_diameter = [&diameters, &nozzle_diameter](int extruder_index,ExtruderGroup & extruder) { extruder.combo_diameter->Clear(); if (extruder_index >= int(nozzle_diameter->values.size())) return; int select = -1; // ORCA get the actual nozzle diameter from printer config auto nozzle_dia = get_diameter_string(nozzle_diameter->values[extruder_index]); - // Named variants such as "0.4HS" and "0.4 High Flow" share a physical diameter. - // Retain the variant selection unless the diameter was customized. - const bool keep_variant = diameter.substr(0, diameter.find_first_not_of("0123456789.")) == nozzle_dia && - std::find(diameters.begin(), diameters.end(), diameter) != diameters.end(); - const std::string &selected_variant = keep_variant ? diameter : nozzle_dia; // ORCA try to add nozzle diameter from config if list is empty. fixes blank nozzle combo box when preset has no alias if(!diameters.empty() && diameters[0].empty() && !nozzle_dia.empty()){ diameters[0] = nozzle_dia; @@ -3958,7 +3942,7 @@ void Sidebar::update_presets(Preset::Type preset_type) diameters.push_back(nozzle_dia); } for (size_t i = 0; i < diameters.size(); ++i) { - if (diameters[i] == selected_variant) + if (diameters[i] == nozzle_dia) select = extruder.combo_diameter->GetCount(); extruder.combo_diameter->Append(diameters[i], {}); } @@ -6082,30 +6066,11 @@ void Sidebar::load_ams_list(MachineObject* obj) filament_ams_list = build_filament_ams_list(obj); } + bool device_change = false; const std::string& device = obj ? obj->get_dev_id() : ""; - const bool same_device = p->ams_list_device == device; - - // Keep sync metadata out of the device payload, but preserve it across a - // subscription refresh when the physical filament in a slot is unchanged. - // Otherwise the refreshed configs differ only by the missing - // filament_changed key, causing combo boxes to rebuild and lose their - // transient post-sync badges. - auto &previous_filament_ams_list = wxGetApp().preset_bundle->filament_ams_list; - for (auto &entry : filament_ams_list) { - auto previous = previous_filament_ams_list.find(entry.first); - const auto *previous_changed = previous == previous_filament_ams_list.end() ? nullptr : - dynamic_cast(previous->second.option("filament_changed")); - if (!same_device || previous_changed == nullptr || - previous->second.opt_string("filament_id", 0u) != entry.second.opt_string("filament_id", 0u)) { - continue; - } - entry.second.set_key_value("filament_changed", - new ConfigOptionBool{previous_changed->value}); - } - - bool device_change = !same_device; - if (device_change) { + if (p->ams_list_device != device) { p->ams_list_device = device; + device_change = true; } BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format(": %1% items") % filament_ams_list.size(); if (wxGetApp().preset_bundle->filament_ams_list == filament_ams_list && !device_change) @@ -6115,27 +6080,9 @@ void Sidebar::load_ams_list(MachineObject* obj) wxGetApp().preset_bundle->filament_ams_list = filament_ams_list; for (auto c : p->combos_filament){ - c->set_sync_badge(false); c->update(); - } - - if (!device_change) { - size_t combo_index = 0; - for (const auto &entry : filament_ams_list) { - const auto &tray = entry.second; - const bool has_filament = !tray.opt_string("filament_id", 0u).empty(); - const bool is_placeholder = tray.has("filament_slot_placeholder") && - tray.opt_bool("filament_slot_placeholder", 0u); - if (!has_filament && !is_placeholder) { - continue; - } - if (combo_index >= p->combos_filament.size()) { - break; - } - const auto *filament_changed = dynamic_cast(tray.option("filament_changed")); - p->combos_filament[combo_index]->set_sync_badge( - has_filament && !is_placeholder && filament_changed != nullptr && filament_changed->value); - ++combo_index; + if (device_change) { + c->ShowBadge(false);//change printer,then clear badge } } @@ -6309,32 +6256,18 @@ void Sidebar::sync_ams_list(bool is_from_big_sync_btn) auto tip = sync_color_only ? _L("Only filament color information has been synchronized from printer.") : _L("Filament type and color information have been synchronized, but slot information is not included."); c->SetToolTip(tip); - c->set_sync_badge(true); + c->ShowBadge(true); }; { // badge ams filament clear_combos_filament_badge(); if (sync_result.direct_sync) { - // A placeholder contributes a preserved project filament to the - // overwrite result, but it is not AMS-sourced and must not get a - // sync badge. Non-placeholder empty trays are omitted entirely. - size_t combo_index = 0; - for (const auto &entry : wxGetApp().preset_bundle->filament_ams_list) { - const auto &tray = entry.second; - const bool has_filament = !tray.opt_string("filament_id", 0u).empty(); - const bool is_placeholder = tray.has("filament_slot_placeholder") && - tray.opt_bool("filament_slot_placeholder", 0u); - if (!has_filament && !is_placeholder) { - continue; - } - if (combo_index >= p->combos_filament.size()) { - break; - } - if (is_placeholder) { - p->combos_filament[combo_index]->set_sync_badge(false); - } else { - badge_combox_filament(p->combos_filament[combo_index]); - } - ++combo_index; + // Orca: PresetBundle::sync_ams_list rebuilds combos_filament + // 1:1 from the AMS trays that produce a combo (loaded trays + placeholders; non-placeholder + // empty trays are skipped), so every resulting combo is AMS-sourced and gets a badge. The + // previous per-tray index walked the full filament_ams_list (including the skipped empties), + // so an empty slot before a loaded one dropped the badge for the trailing filaments. + for (auto &c : p->combos_filament) { + badge_combox_filament(c); } } } @@ -6602,11 +6535,6 @@ template void setup_dialog_position(T& info) void Sidebar::pop_sync_nozzle_and_ams_dialog() { BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << " begin pop_sync_nozzle_and_ams_dialog"; - auto agent = wxGetApp().getAgent(); - if (!agent || agent->get_filament_sync_mode() == FilamentSyncMode::none) { - BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << " filament synchronization is not supported; skipping dialog"; - return; - } wxTheApp->CallAfter([this]() { SyncNozzleAndAmsDialog::InputInfo temp_na_info; wxPoint big_btn_pt; @@ -6738,14 +6666,17 @@ void Sidebar::clear_combos_filament_badge() { auto &combos_filament = p->combos_filament; for (auto &c : combos_filament) { // clear flag - c->set_sync_badge(false); + c->ShowBadge(false); } } void Sidebar::udpate_combos_filament_badge() { auto &combos_filament = p->combos_filament; for (auto &c : combos_filament) { - c->update_badge_according_flag(); + auto selection = c->GetSelection(); + auto select_flag = c->GetFlag(selection); + auto ok = select_flag == (int) PresetComboBox::FilamentAMSType::FROM_AMS; + c->ShowBadge(ok); } } @@ -7117,13 +7048,6 @@ struct Plater::priv GLToolbar collapse_toolbar; Preview *preview; AssembleView* assemble_view { nullptr }; - // Docked/resizable 2D pane showing GLGizmoTextureDisplacement's LSCM unwrap of a painted - // patch; a sibling AUI pane alongside "sidebar"/"main", not part of the view3D/preview/ - // assemble_view sizer - see its registration below and Plater::get_uv_editor_canvas(). The - // pane hosts the panel (toolbar + canvas + status line); uv_editor_canvas is its inner canvas, - // cached so the gizmo can reach it directly. - UVEditorPanel* uv_editor_panel { nullptr }; - UVEditorCanvas* uv_editor_canvas { nullptr }; bool first_enter_assemble{ true }; std::unique_ptr notification_manager; @@ -7373,8 +7297,6 @@ struct Plater::priv void undo(); void redo(); - // True, and tells the user, while a background job is working on the model - see the definition. - bool undo_redo_blocked_by_job(); void undo_redo_to(size_t time_to_load); // BBS: backup @@ -7828,26 +7750,6 @@ Plater::priv::priv(Plater *q, MainFrame *main_frame) .BottomDockable(false) .BestSize(wxSize(39 * wxGetApp().em_unit(), 90 * wxGetApp().em_unit()))); - // UV editor pane for GLGizmoTextureDisplacement's LSCM unwrap preview - a resizable/dockable - // sibling of "sidebar"/"main" like everything else registered on this same AUI manager, not a - // change to the view3D/preview/assemble_view sizer above. Hidden by default: only relevant - // while that gizmo is active with a layer using the "Unwrap (LSCM)" projection method (see - // Plater::show_uv_editor()), so it stays out of the way of everyone else's window layout. - uv_editor_panel = new UVEditorPanel(q); - uv_editor_canvas = uv_editor_panel->canvas(); - m_aui_mgr.AddPane(uv_editor_panel, wxAuiPaneInfo() - .Name("uv_editor") - .Caption(_L("UV Editor")) - .Right() - .Hide() - .BestSize(wxSize(40 * wxGetApp().em_unit(), 40 * wxGetApp().em_unit()))); - // Closing the pane with its own X has to reach the gizmo, or its next update would simply show the pane again. - q->Bind(wxEVT_AUI_PANE_CLOSE, [this](wxAuiManagerEvent &evt) { - evt.Skip(); - if (evt.GetPane() != nullptr && evt.GetPane()->window == uv_editor_panel && uv_editor_canvas != nullptr) - uv_editor_canvas->run_command(UVEditorCanvas::Command::PaneClosed); - }); - auto* panel_sizer = new wxBoxSizer(wxHORIZONTAL); panel_sizer->Add(view3D, 1, wxEXPAND | wxALL, 0); panel_sizer->Add(preview, 1, wxEXPAND | wxALL, 0); @@ -7889,13 +7791,6 @@ Plater::priv::priv(Plater *q, MainFrame *main_frame) BOOST_LOG_TRIVIAL(info) << "Removed floating AUI state from saved window layout for Wayland"; } - // The UV editor is a transient, gizmo-driven pane (see show_uv_editor()); a saved layout - // from a session that happened to close with it open would otherwise restore it visible on - // startup, with nothing painted in it. Force it hidden here so it only ever appears when the - // texture-displacement gizmo asks for it. - if (wxAuiPaneInfo &uv_pane = m_aui_mgr.GetPane("uv_editor"); uv_pane.IsOk()) - uv_pane.Hide(); - sidebar_layout.is_collapsed = !sidebar.IsShown(); } @@ -10744,13 +10639,15 @@ void Plater::priv::reset(bool apply_presets_change, bool reload_presets) m_worker.cancel_all(); + // Stop and reset the Print content. m_worker.cancel_all() only stops the UI jobs, so this has to + // happen before reinit() deletes the plates together with the Print the slicing thread may still + // be working on. + this->background_process.reset(); + //BBS: clear the partplate list's object before object cleared partplate_list.reinit(); partplate_list.update_slice_context_to_current_plate(background_process); preview->update_gcode_result(partplate_list.get_current_slice_result()); - - // Stop and reset the Print content. - this->background_process.reset(); model.clear_objects(); // clear_objects() only drops the ModelObjects; the CAD recipe is Model-level state and would // otherwise be written into every project saved for the rest of the session. @@ -12766,7 +12663,7 @@ void Plater::priv::on_select_preset(wxCommandEvent &evt) sidebar->auto_calc_flushing_volumes(idx); } auto select_flag = combo->GetFlag(selection); - combo->set_sync_badge(select_flag == (int)PresetComboBox::FilamentAMSType::FROM_AMS); + combo->ShowBadge(select_flag == (int)PresetComboBox::FilamentAMSType::FROM_AMS); q->on_filament_change(idx); } bool select_preset = !combo->selection_is_changed_according_to_physical_printers(); @@ -15038,25 +14935,8 @@ void Plater::priv::take_snapshot(const std::string& snapshot_name, const UndoRed BOOST_LOG_TRIVIAL(info) << "Undo / Redo snapshot taken: " << snapshot_name << ", Undo / Redo stack memory: " << Slic3r::format_memsize_MB(this->undo_redo_stack().memsize()) << log_memory_info(); } -// A background job holds the model it is working on: the texture displacement bake, for one, hands its -// result to the volume when it finishes, and it was queued against the geometry as it was at the time. -// Undoing while it runs restores an older state under it - a different transform, a different mesh - -// and the result then lands on geometry it was never computed for. Undo and redo therefore wait for -// the job, and say so rather than doing nothing. -bool Plater::priv::undo_redo_blocked_by_job() -{ - if (m_worker.is_idle()) - return false; - notification_manager->push_notification(NotificationType::CustomNotification, - NotificationManager::NotificationLevel::RegularNotificationLevel, - _u8L("Cannot undo or redo while an operation is running. Stop it first.")); - return true; -} - void Plater::priv::undo() { - if (this->undo_redo_blocked_by_job()) - return; const std::vector &snapshots = this->undo_redo_stack().snapshots(); auto it_current = std::lower_bound(snapshots.begin(), snapshots.end(), UndoRedo::Snapshot(this->undo_redo_stack().active_snapshot_time())); // BBS: undo-redo until modify record @@ -15074,8 +14954,6 @@ void Plater::priv::undo() void Plater::priv::redo() { - if (this->undo_redo_blocked_by_job()) - return; const std::vector &snapshots = this->undo_redo_stack().snapshots(); auto it_current = std::lower_bound(snapshots.begin(), snapshots.end(), UndoRedo::Snapshot(this->undo_redo_stack().active_snapshot_time())); // BBS: undo-redo until modify record @@ -15114,6 +14992,11 @@ void Plater::priv::undo_redo_to(std::vector::const_iterator // Make sure that no updating function calls take_snapshot until we are done. SuppressSnapshots snapshot_supressor(q); + // Loading a snapshot deletes every PartPlate, which the slicing thread keeps dereferencing (its + // current plate and the status callback). Cancel it and wait for it to finish before the jump, + // update_after_undo_redo() re-applies the background process to the rebuilt plates afterwards. + this->background_process.stop(); + bool temp_snapshot_was_taken = this->undo_redo_stack().temp_snapshot_active(); PrinterTechnology new_printer_technology = it_snapshot->snapshot_data.printer_technology; bool printer_technology_changed = this->printer_technology != new_printer_technology; @@ -16721,7 +16604,7 @@ void adjust_settings_for_flowrate_calib(ModelObjectPtrs& objects, bool linear, i auto printer_config = &wxGetApp().preset_bundle->printers.get_edited_preset().config; auto filament_config = &wxGetApp().preset_bundle->filaments.get_edited_preset().config; - /// -- scale -- + /// --- scale --- // model is created for a 0.4 nozzle, scale z with nozzle size. const ConfigOptionFloats* nozzle_diameter_config = printer_config->option("nozzle_diameter"); std::vector extruder_types = printer_config->option("extruder_type")->values; @@ -21198,33 +21081,6 @@ GLCanvas3D* Plater::get_assmeble_canvas3D() return nullptr; } -UVEditorCanvas* Plater::get_uv_editor_canvas() -{ - return p->uv_editor_canvas; -} - -void Plater::show_uv_editor(bool show) -{ - if (p->uv_editor_panel == nullptr) - return; - const wxAuiPaneInfo &pane = p->m_aui_mgr.GetPane(p->uv_editor_panel); - if (!pane.IsOk() || pane.IsShown() == show) - return; - - // Deferred, because GLGizmoTextureDisplacement calls this from its ImGui panel - that is, from - // the middle of the 3D canvas's GL frame. Showing an AUI pane re-lays out the window and - // delivers the resulting size/paint events synchronously, and the UV canvas painting itself - // makes its own surface current in the app's *shared* GL context, which mid-frame is the one - // the 3D canvas is drawing into. Doing the layout once the frame is over avoids that entirely. - CallAfter([this, show]() { - wxAuiPaneInfo &deferred_pane = p->m_aui_mgr.GetPane(p->uv_editor_panel); - if (!deferred_pane.IsOk() || deferred_pane.IsShown() == show) - return; - deferred_pane.Show(show); - p->m_aui_mgr.Update(); - }); -} - GLCanvas3D* Plater::get_current_canvas3D(bool exclude_preview) { return p->get_current_canvas3D(exclude_preview); @@ -21486,14 +21342,9 @@ bool Plater::is_same_printer_for_connected_and_selected(bool popup_warning) } if (!check_printer_initialized(obj, true, popup_warning)) return false; - const std::string machine_model = obj->printer_type; - PresetBundle *preset_bundle = wxGetApp().preset_bundle; - const std::string selected_model = preset_bundle ? preset_bundle->printers.get_edited_preset().get_printer_type(preset_bundle) : std::string(); - if (!DevPrinterConfigUtil::is_optional_printer_model_id(machine_model) && - !DevPrinterConfigUtil::is_optional_printer_model_id(selected_model) && - !get_printer_preset(obj)) { + Preset * machine_preset = get_printer_preset(obj); + if (!machine_preset) return false; - } if (wxGetApp().is_blocking_printing()) { if (popup_warning) { @@ -22578,6 +22429,11 @@ int Plater::delete_plate(int plate_index) if (plate_index == -1) index = p->partplate_list.get_curr_plate_index(); + // Orca: delete_plate() destroys the plate's Print and GCodeResult, which the slicing thread is + // still working on, so it has to be stopped first. can_delete_plate() also refuses while slicing, + // but the plate grabber in the 3D scene does not go through it. + p->background_process.stop(); + take_snapshot("delete partplate"); ret = p->partplate_list.delete_plate(index); @@ -22907,7 +22763,7 @@ bool Plater::can_delete() const { return p->can_delete(); } bool Plater::can_delete_all() const { return p->can_delete_all(); } bool Plater::can_add_model() const { return !is_background_process_slicing(); } bool Plater::can_add_plate() const { return !is_background_process_slicing() && p->can_add_plate(); } -bool Plater::can_delete_plate() const { return p->can_delete_plate(); } +bool Plater::can_delete_plate() const { return !is_background_process_slicing() && p->can_delete_plate(); } bool Plater::can_increase_instances() const { return p->can_increase_instances(); } bool Plater::can_decrease_instances() const { return p->can_decrease_instances(); } bool Plater::can_set_instance_to_object() const { return p->can_set_instance_to_object(); } @@ -22964,9 +22820,8 @@ bool Plater::can_copy_to_clipboard() const return true; } -// The job check keeps the buttons in step with priv::undo()/redo(), which refuse while one runs. -bool Plater::can_undo() const { return IsShown() && p->is_view3D_shown() && p->m_worker.is_idle() && p->undo_redo_stack().has_undo_snapshot(); } -bool Plater::can_redo() const { return IsShown() && p->is_view3D_shown() && p->m_worker.is_idle() && p->undo_redo_stack().has_redo_snapshot(); } +bool Plater::can_undo() const { return IsShown() && p->is_view3D_shown() && p->undo_redo_stack().has_undo_snapshot(); } +bool Plater::can_redo() const { return IsShown() && p->is_view3D_shown() && p->undo_redo_stack().has_redo_snapshot(); } bool Plater::can_reload_from_disk() const { return p->can_reload_from_disk(); } //BBS bool Plater::can_fillcolor() const { return p->can_fillcolor(); } diff --git a/src/slic3r/Utils/UndoRedo.cpp b/src/slic3r/Utils/UndoRedo.cpp index 8e7bd703c3..2073dba863 100644 --- a/src/slic3r/Utils/UndoRedo.cpp +++ b/src/slic3r/Utils/UndoRedo.cpp @@ -1025,6 +1025,8 @@ void StackImpl::load_snapshot(size_t timestamp, Slic3r::Model& model, Slic3r::GU std::vector previous_gcode_paths; plate_list.get_sliced_result(previous_slice_result, previous_gcode_paths); + // The plates are dereferenced by the slicing thread, which the caller + // (Plater::priv::undo_redo_to) has stopped before loading the snapshot. plate_list.reset(false); this->load_mutable_object(plate_list.id(), plate_list); plate_list.rebuild_plates_after_deserialize(previous_slice_result, previous_gcode_paths);