From 8180bea83511e69653d88680c24648da0a19a330 Mon Sep 17 00:00:00 2001 From: Lukas Matena Date: Thu, 13 Feb 2025 01:35:00 +0100 Subject: [PATCH] Forbid seq arrange for a single bed in cases where it would reorder objects unexpectedly --- src/libslic3r/ArrangeHelper.cpp | 47 ++++++++++++++++++++++++++- src/libslic3r/ArrangeHelper.hpp | 4 ++- src/slic3r/GUI/Jobs/SeqArrangeJob.cpp | 20 ++++++++++-- src/slic3r/GUI/Plater.cpp | 10 ++++-- 4 files changed, 74 insertions(+), 7 deletions(-) diff --git a/src/libslic3r/ArrangeHelper.cpp b/src/libslic3r/ArrangeHelper.cpp index e1c1b909b7..a18a565183 100644 --- a/src/libslic3r/ArrangeHelper.cpp +++ b/src/libslic3r/ArrangeHelper.cpp @@ -17,6 +17,29 @@ namespace Slic3r { + +static bool can_arrange_selected_bed(const Model& model, int bed_idx) +{ + // When arranging a single bed, all instances of each object present must be on the same bed. + // Otherwise, the resulting order may not be possible to apply without messing up order + // on the other beds. + const auto map = s_multiple_beds.get_inst_map(); + for (const ModelObject* mo : model.objects) { + std::map used_beds; + bool mo_on_this_bed = false; + for (const ModelInstance* mi : mo->instances) { + int id = -1; + if (auto it = map.find(mi->id()); it != map.end()) + id = it->second; + if (id == bed_idx) + mo_on_this_bed = true; + used_beds[id] = true; + } + if (mo_on_this_bed && used_beds.size() != 1) + return false; + } + return true; +} static Sequential::PrinterGeometry get_printer_geometry(const ConfigBase& config) { @@ -187,6 +210,9 @@ void arrange_model_sequential(Model& model, const ConfigBase& config, bool curre SeqArrange::SeqArrange(const Model& model, const ConfigBase& config, bool current_bed_only) { m_selected_bed = current_bed_only ? s_multiple_beds.get_active_bed() : -1; + if (m_selected_bed != -1 && ! can_arrange_selected_bed(model, m_selected_bed)) + throw ExceptionCannotAttemptSeqArrange(); + m_printer_geometry = get_printer_geometry(config); m_solver_configuration = get_solver_config(m_printer_geometry); m_objects = get_objects_to_print(model, m_printer_geometry, m_selected_bed); @@ -201,12 +227,31 @@ void SeqArrange::process_seq_arrange(std::function progress_fn) m_solver_configuration, m_printer_geometry, m_objects, progress_fn); + + // If this was arrangement of a single bed, check that all instances of a single object + // ended up on the same bed. Otherwise we cannot apply the result (instances of a single + // object always follow one another in the object list and therefore the print). + if (m_selected_bed != -1 && s_multiple_beds.get_number_of_beds() > 1) { + int expected_plate = -1; + for (const Sequential::ObjectToPrint& otp : m_objects) { + auto it = std::find_if(m_plates.begin(), m_plates.end(), [&otp](const auto& plate) + { return std::any_of(plate.scheduled_objects.begin(), plate.scheduled_objects.end(), + [&otp](const auto& obj) { return otp.id == obj.id; + }); + }); + assert(it != m_plates.end()); + size_t plate_id = it - m_plates.begin(); + if (expected_plate != -1 && expected_plate != plate_id) + throw ExceptionCannotApplySeqArrange(); + expected_plate = otp.glued_to_next ? plate_id : -1; + } + } } // Extract the result and move the objects in Model accordingly. void SeqArrange::apply_seq_arrange(Model& model) const -{ +{ struct MoveData { Sequential::ScheduledObject scheduled_object; size_t bed_idx; diff --git a/src/libslic3r/ArrangeHelper.hpp b/src/libslic3r/ArrangeHelper.hpp index 1136829f8a..4e600d834b 100644 --- a/src/libslic3r/ArrangeHelper.hpp +++ b/src/libslic3r/ArrangeHelper.hpp @@ -10,10 +10,12 @@ namespace Slic3r { class Model; class ConfigBase; + class ExceptionCannotAttemptSeqArrange : public std::exception {}; + class ExceptionCannotApplySeqArrange : public std::exception {}; + void arrange_model_sequential(Model& model, const ConfigBase& config); bool check_seq_printability(const Model& model, const ConfigBase& config); - // This is just a helper class to collect data for seq. arrangement, running the arrangement // and applying the results to model. It is here so the processing itself can be offloaded // into a separate thread without copying the Model or sharing it with UI thread. diff --git a/src/slic3r/GUI/Jobs/SeqArrangeJob.cpp b/src/slic3r/GUI/Jobs/SeqArrangeJob.cpp index 2fb0f0d952..fb64c55130 100644 --- a/src/slic3r/GUI/Jobs/SeqArrangeJob.cpp +++ b/src/slic3r/GUI/Jobs/SeqArrangeJob.cpp @@ -7,6 +7,7 @@ #include "slic3r/GUI/GUI_ObjectList.hpp" #include "slic3r/GUI/I18N.hpp" #include "slic3r/GUI/Plater.hpp" +#include "slic3r/GUI/MsgDialog.hpp" @@ -39,11 +40,24 @@ void SeqArrangeJob::process(Ctl& ctl) -void SeqArrangeJob::finalize(bool canceled, std::exception_ptr&) +void SeqArrangeJob::finalize(bool canceled, std::exception_ptr& eptr) { // If the task was cancelled, the stopping exception was already caught - // in 'process' function. Let any other exception propagate further. - if (! canceled) { + // in 'process' function. Any other exception propagates through here. + bool error = false; + if (eptr) { + try { + std::rethrow_exception(eptr); + } catch (const ExceptionCannotApplySeqArrange&) { + ErrorDialog dlg(wxGetApp().plater(), _L("The result of the single-bed arrange would scatter instances of a single object between several beds, " + "possibly affecting order of printing of the non-selected beds. Consider using global arrange across all beds."), false); + dlg.ShowModal(); + error = true; + eptr = nullptr; // The exception is handled. + } + } + + if (! canceled && ! error) { Plater::TakeSnapshot snapshot(wxGetApp().plater(), _u8L("Arrange for sequential print")); m_seq_arrange->apply_seq_arrange(wxGetApp().model()); wxGetApp().plater()->canvas3D()->reload_scene(true, true); diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index ae5add653d..400eac81d1 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -7122,8 +7122,14 @@ void Plater::arrange(bool current_bed_only) const bool sequential = p->config->has("complete_objects") && p->config->opt_bool("complete_objects"); if (p->can_arrange()) { - if (sequential) - replace_job(this->get_ui_job_worker(), std::make_unique(this->model(), *p->config, current_bed_only)); + if (sequential) { + try { + replace_job(this->get_ui_job_worker(), std::make_unique(this->model(), *p->config, current_bed_only)); + } catch (const ExceptionCannotAttemptSeqArrange&) { + ErrorDialog dlg(this, _L("Sequential arrange for a single bed is only allowed when all instances of the affected objects are on the same bed."), false); + dlg.ShowModal(); + } + } else { auto& w = get_ui_job_worker(); arrange(w, mode);