From 9ed459e132baee9d82a926bb4b47b930aa4eacb1 Mon Sep 17 00:00:00 2001 From: Ian Chua Date: Fri, 25 Sep 2026 13:25:45 +0800 Subject: [PATCH 1/5] fix: slicing event name and ID --- src/libslic3r/GCode.cpp | 15 ++++++++++----- src/libslic3r/Print.cpp | 15 ++++++++++----- src/slic3r/GUI/Plater.cpp | 8 ++++---- 3 files changed, 24 insertions(+), 14 deletions(-) diff --git a/src/libslic3r/GCode.cpp b/src/libslic3r/GCode.cpp index 1a1d1bcbff..8bbd7a0344 100644 --- a/src/libslic3r/GCode.cpp +++ b/src/libslic3r/GCode.cpp @@ -2479,7 +2479,8 @@ void GCode::do_export(Print* print, const char* path, GCodeProcessorResult* resu { LifecycleEventContext ctx; - ctx.name = std::to_string(print->model().id().id); + ctx.id = std::to_string(print->model().id().id); + ctx.name = print->get_model_name(); ctx.code = LifecycleEvtCode::Ok; ctx.msg = path; ctx.cancellation_check = [print]() { return print->canceled(); }; @@ -2531,7 +2532,8 @@ void GCode::do_export(Print* print, const char* path, GCodeProcessorResult* resu } { LifecycleEventContext ctx; - ctx.name = std::to_string(print->model().id().id); + ctx.id = std::to_string(print->model().id().id); + ctx.name = print->get_model_name(); ctx.code = LifecycleEvtCode::Error; ctx.msg = std::string(path) + "\n" + err_msg; ctx.cancellation_check = [print]() { return print->canceled(); }; @@ -2555,7 +2557,8 @@ void GCode::do_export(Print* print, const char* path, GCodeProcessorResult* resu boost::nowide::remove(path_tmp.c_str()); { LifecycleEventContext ctx; - ctx.name = std::to_string(print->model().id().id); + ctx.id = std::to_string(print->model().id().id); + ctx.name = print->get_model_name(); ctx.code = LifecycleEvtCode::Error; ctx.msg = std::string(path) + "\n" + ex.what(); ctx.cancellation_check = [print]() { return print->canceled(); }; @@ -2668,7 +2671,8 @@ void GCode::do_export(Print* print, const char* path, GCodeProcessorResult* resu if (ret) { { LifecycleEventContext ctx; - ctx.name = std::to_string(print->model().id().id); + ctx.id = std::to_string(print->model().id().id); + ctx.name = print->get_model_name(); ctx.code = LifecycleEvtCode::Error; ctx.msg = std::string(path) + "\nFailed to rename the output G-code file: " + ret.message(); ctx.cancellation_check = [print]() { return print->canceled(); }; @@ -2687,7 +2691,8 @@ void GCode::do_export(Print* print, const char* path, GCodeProcessorResult* resu { LifecycleEventContext ctx; - ctx.name = std::to_string(print->model().id().id); + ctx.id = std::to_string(print->model().id().id); + ctx.name = print->get_model_name(); ctx.code = LifecycleEvtCode::Ok; ctx.msg = path; ctx.cancellation_check = [print]() { return print->canceled(); }; diff --git a/src/libslic3r/Print.cpp b/src/libslic3r/Print.cpp index 477c2c74ca..850761339f 100644 --- a/src/libslic3r/Print.cpp +++ b/src/libslic3r/Print.cpp @@ -2716,7 +2716,8 @@ void Print::process(long long *time_cost_with_cache, bool use_cache) { LifecycleEventContext ctx; - ctx.name = std::to_string(m_model.id().id); + ctx.id = std::to_string(m_model.id().id); + ctx.name = get_model_name(); ctx.code = LifecycleEvtCode::Ok; ctx.cancellation_check = [this]() { return canceled(); }; fire_lifecycle_event(LifecycleEvent::SliceStarted, ctx); @@ -3343,7 +3344,8 @@ void Print::process(long long *time_cost_with_cache, bool use_cache) { LifecycleEventContext ctx; - ctx.name = std::to_string(m_model.id().id); + ctx.id = std::to_string(m_model.id().id); + ctx.name = get_model_name(); ctx.code = LifecycleEvtCode::Ok; ctx.cancellation_check = [this]() { return canceled(); }; fire_lifecycle_event(LifecycleEvent::SliceGeometryFinished, ctx); @@ -4949,7 +4951,8 @@ void Print::export_gcode_from_previous_file(const std::string& file, GCodeProces { { LifecycleEventContext ctx; - ctx.name = std::to_string(m_model.id().id); + ctx.id = std::to_string(m_model.id().id); + ctx.name = get_model_name(); ctx.code = LifecycleEvtCode::Ok; ctx.msg = file; ctx.cancellation_check = [this]() { return canceled(); }; @@ -4979,7 +4982,8 @@ void Print::export_gcode_from_previous_file(const std::string& file, GCodeProces BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << boost::format(": found errors when process gcode file %1%") %file.c_str(); { LifecycleEventContext ctx; - ctx.name = std::to_string(m_model.id().id); + ctx.id = std::to_string(m_model.id().id); + ctx.name = get_model_name(); ctx.code = LifecycleEvtCode::Error; ctx.msg = file + "\n" + ex.what(); ctx.cancellation_check = [this]() { return canceled(); }; @@ -4993,7 +4997,8 @@ void Print::export_gcode_from_previous_file(const std::string& file, GCodeProces { LifecycleEventContext ctx; - ctx.name = std::to_string(m_model.id().id); + ctx.id = std::to_string(m_model.id().id); + ctx.name = get_model_name(); ctx.code = LifecycleEvtCode::Ok; ctx.msg = file; ctx.cancellation_check = [this]() { return canceled(); }; diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index 5eb36f20f9..04318dcbb5 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -12801,9 +12801,6 @@ void Plater::priv::on_process_completed(SlicingProcessCompletedEvent &evt) this->background_process.stop(); notification_manager->set_slicing_progress_export_possible(); - // Reset the "export G-code path" name, so that the automatic background processing will be enabled again. - const std::string lifecycle_job_name = this->background_process.fff_print() ? - this->background_process.fff_print()->output_filename() : std::string(); this->background_process.reset_export(); // This bool stops showing export finished notification even when process_completed_with_error is false bool has_error = false; @@ -12850,7 +12847,10 @@ void Plater::priv::on_process_completed(SlicingProcessCompletedEvent &evt) { Slic3r::LifecycleEventContext ctx; - ctx.name = lifecycle_job_name; + if (const Print* print = this->background_process.fff_print()) { + ctx.id = std::to_string(print->model().id().id); + ctx.name = print->get_model_name(); + } ctx.code = evt.cancelled() ? Slic3r::LifecycleEvtCode::Warn : (has_error ? Slic3r::LifecycleEvtCode::Error : Slic3r::LifecycleEvtCode::Ok); ctx.msg = evt.cancelled() ? "cancelled" : (has_error ? lifecycle_error_msg : std::string()); Slic3r::fire_lifecycle_event(Slic3r::LifecycleEvent::SlicingJobComplete, ctx); From 04204ec0d3e740f80ecc1e526ad2f03e1943500a Mon Sep 17 00:00:00 2001 From: peachismomo Date: Sun, 27 Sep 2026 17:25:56 +0800 Subject: [PATCH 2/5] test: cover slicing lifecycle event context --- tests/fff_print/test_print.cpp | 66 ++++++++++++++++++++++++++++++++++ 1 file changed, 66 insertions(+) diff --git a/tests/fff_print/test_print.cpp b/tests/fff_print/test_print.cpp index 383a7165d5..83dbfa0945 100644 --- a/tests/fff_print/test_print.cpp +++ b/tests/fff_print/test_print.cpp @@ -15,6 +15,9 @@ #include "libslic3r/Layer.hpp" #include "libslic3r/Model.hpp" #include "libslic3r/GCodeReader.hpp" +#include "libslic3r/GCode/GCodeProcessor.hpp" +#include "libslic3r/Exception.hpp" +#include "libslic3r/LifecycleEvents.hpp" #include "test_helpers.hpp" #include "test_utils.hpp" @@ -22,7 +25,10 @@ #include #include #include +#include #include +#include +#include using namespace Slic3r; using namespace Slic3r::Test; @@ -224,8 +230,68 @@ std::string resolved_output_name(Model& model, const std::string& format, const return print.output_filename(filename_base); } +struct ScopedLifecycleHook +{ + explicit ScopedLifecycleHook(LifecycleHookFn hook) { set_lifecycle_hook_fn(std::move(hook)); } + ~ScopedLifecycleHook() { set_lifecycle_hook_fn(nullptr); } +}; + } // namespace +TEST_CASE("Slicing lifecycle events identify the model", "[Print][LifecycleEvents]") +{ + struct ObservedEvent { + LifecycleEvent event; + std::string id; + std::string name; + }; + std::vector events; + ScopedLifecycleHook hook([&](LifecycleEvent event, const LifecycleEventContext& ctx) { + events.push_back({ event, ctx.id, ctx.name }); + }); + + Print print; + Model model; + ModelInfo info; + info.model_name = "Lifecycle test model"; + model.model_info = std::make_shared(std::move(info)); + init_print({cube(20)}, print, model); + + print.process(); + ScopedTemporaryFile temp(".gcode"); + print.export_gcode(temp.string(), nullptr, nullptr); + GCodeProcessorResult result; + print.export_gcode_from_previous_file(temp.string(), &result); + + const std::string expected_id = std::to_string(print.model().id().id); + const std::vector expected_events = { + LifecycleEvent::SliceStarted, + LifecycleEvent::SliceGeometryFinished, + LifecycleEvent::GCodeExportStarted, + LifecycleEvent::GCodeExportFinished, + LifecycleEvent::GCodeExportStarted, + LifecycleEvent::GCodeExportFinished, + }; + REQUIRE(events.size() == expected_events.size()); + for (size_t i = 0; i < expected_events.size(); ++i) { + CHECK(events[i].event == expected_events[i]); + CHECK(events[i].id == expected_id); + CHECK(events[i].name == "Lifecycle test model"); + } +} + +TEST_CASE("Output filenames with numeric statistics fail before slicing finishes", "[Print][Regression]") +{ + DynamicPrintConfig config = DynamicPrintConfig::full_print_config(); + config.set_key_value("filename_format", new ConfigOptionString("{int(total_weight*10) / 10.0}")); + + Print print; + Model model; + init_print({cube(20)}, print, model, config); + + CHECK_THROWS_AS(print.output_filename(), PlaceholderParserError); +} + TEST_CASE("Print: {first_object_name} names the first printable object on the plate", "[Print]") { Model model; From 58842bab05d0ce9208239da97f886a27233e4fe2 Mon Sep 17 00:00:00 2001 From: peachismomo Date: Sun, 27 Sep 2026 17:29:01 +0800 Subject: [PATCH 3/5] test: test for SliceStarted ensuring stable model ID is presetn when model name is absent --- tests/fff_print/test_print.cpp | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/tests/fff_print/test_print.cpp b/tests/fff_print/test_print.cpp index 83dbfa0945..32cd608b43 100644 --- a/tests/fff_print/test_print.cpp +++ b/tests/fff_print/test_print.cpp @@ -280,6 +280,26 @@ TEST_CASE("Slicing lifecycle events identify the model", "[Print][LifecycleEvent } } +TEST_CASE("Slicing lifecycle event name is empty without model metadata", "[Print][LifecycleEvents]") +{ + std::string event_id; + std::string event_name = "unset"; + ScopedLifecycleHook hook([&](LifecycleEvent event, const LifecycleEventContext& ctx) { + if (event == LifecycleEvent::SliceStarted) { + event_id = ctx.id; + event_name = ctx.name; + } + }); + + Print print; + Model model; + init_print({cube(20)}, print, model); + print.process(); + + CHECK(event_id == std::to_string(print.model().id().id)); + CHECK(event_name.empty()); +} + TEST_CASE("Output filenames with numeric statistics fail before slicing finishes", "[Print][Regression]") { DynamicPrintConfig config = DynamicPrintConfig::full_print_config(); From 9e16cdb23b710d533a0abc186a84ee830f6b5d28 Mon Sep 17 00:00:00 2001 From: peachismomo Date: Sun, 27 Sep 2026 17:29:25 +0800 Subject: [PATCH 4/5] fix: comment above reset_export --- src/slic3r/GUI/Plater.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index 04318dcbb5..0555a43776 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -12801,6 +12801,7 @@ void Plater::priv::on_process_completed(SlicingProcessCompletedEvent &evt) this->background_process.stop(); notification_manager->set_slicing_progress_export_possible(); + // Reset the "export G-code path" name, so that the automatic background processing will be enabled again. this->background_process.reset_export(); // This bool stops showing export finished notification even when process_completed_with_error is false bool has_error = false; From e340a13c186e024ece6b3b4873c34137836aabe8 Mon Sep 17 00:00:00 2001 From: peachismomo Date: Sun, 27 Sep 2026 17:53:41 +0800 Subject: [PATCH 5/5] fix: use current print for slicing lifecycle identity --- src/slic3r/GUI/Plater.cpp | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index 0555a43776..a7023c6966 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -12848,11 +12848,18 @@ void Plater::priv::on_process_completed(SlicingProcessCompletedEvent &evt) { Slic3r::LifecycleEventContext ctx; - if (const Print* print = this->background_process.fff_print()) { - ctx.id = std::to_string(print->model().id().id); - ctx.name = print->get_model_name(); + if (const PrintBase* print = this->background_process.current_print()) { + const Model& model = print->model(); + ctx.id = std::to_string(model.id().id); + if (model.model_info) + ctx.name = model.model_info->model_name; + } else { + // Realistically Printbase* print will never be null because select_technology already asserts an active print + // and the worker thread asserts it before processing. + BOOST_LOG_TRIVIAL(warning) << __FUNCTION__ << ": slicing completed without an active print; lifecycle event has no model ID"; } - ctx.code = evt.cancelled() ? Slic3r::LifecycleEvtCode::Warn : (has_error ? Slic3r::LifecycleEvtCode::Error : Slic3r::LifecycleEvtCode::Ok); + ctx.code = evt.cancelled() ? Slic3r::LifecycleEvtCode::Warn : + (has_error ? Slic3r::LifecycleEvtCode::Error : Slic3r::LifecycleEvtCode::Ok); ctx.msg = evt.cancelled() ? "cancelled" : (has_error ? lifecycle_error_msg : std::string()); Slic3r::fire_lifecycle_event(Slic3r::LifecycleEvent::SlicingJobComplete, ctx); }