diff --git a/src/libslic3r/Print.cpp b/src/libslic3r/Print.cpp index 6d810a922d..5603716064 100644 --- a/src/libslic3r/Print.cpp +++ b/src/libslic3r/Print.cpp @@ -5340,6 +5340,31 @@ void Print::export_gcode_from_previous_file(const std::string& file, GCodeProces } } +void Print::reload_gcode_moves(GCodeProcessorResult* result) const +{ + GCodeProcessor processor; + GCodeProcessor::s_IsBBLPrinter = is_BBL_printer(); + const Vec3d origin = this->get_plate_origin(); + processor.set_xy_offset(origin(0), origin(1)); + // Estimate the per-move times with the same nozzle-grouping slot context as the export. + if (result->nozzle_group_result) + processor.initialize_from_context(result->nozzle_group_result); + try { + processor.process_file(result->filename); + } catch (const std::exception& ex) { + // The edited file is what gets printed, so failing to preview it must not fail the slice. + BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << ": cannot re-read the G-code file " << result->filename << ": " << ex.what(); + std::lock_guard lock(result->result_mutex); + result->lines_ends.clear(); + return; + } + + GCodeProcessorResult& reloaded = processor.result(); + std::lock_guard lock(result->result_mutex); + result->moves = std::move(reloaded.moves); + result->lines_ends = std::move(reloaded.lines_ends); +} + std::tuple Print::object_skirt_offset(double margin_height) const { if (config().skirt_loops == 0 || config().skirt_type != stPerObject || m_objects.empty()) diff --git a/src/libslic3r/Print.hpp b/src/libslic3r/Print.hpp index 062a8eb3a3..b536d73e8f 100644 --- a/src/libslic3r/Print.hpp +++ b/src/libslic3r/Print.hpp @@ -1287,6 +1287,11 @@ public: void set_gcode_file_ready(); void set_gcode_file_invalidated(); void export_gcode_from_previous_file(const std::string& file, GCodeProcessorResult* result, ThumbnailsGeneratorCallback thumbnail_cb = nullptr); + // Re-reads the moves and line offsets of `result` from its G-code file after the file was rewritten in + // place (post-processing scripts or plugins), so the preview and its G-code window follow the file on + // disk. Everything else in `result` was computed while slicing and is kept. If the file cannot be + // re-read, the moves are kept and the line offsets are cleared, which hides the G-code window. + void reload_gcode_moves(GCodeProcessorResult* result) const; //BBS: add modify_count logic int get_modified_count() const {return m_modified_count;} //BBS: add status for whether support used diff --git a/src/slic3r/GUI/BackgroundSlicingProcess.cpp b/src/slic3r/GUI/BackgroundSlicingProcess.cpp index 50c69f40dc..ba205887ec 100644 --- a/src/slic3r/GUI/BackgroundSlicingProcess.cpp +++ b/src/slic3r/GUI/BackgroundSlicingProcess.cpp @@ -283,11 +283,12 @@ void BackgroundSlicingProcess::process_fff() m_temp_output_path = this->get_current_plate()->get_tmp_gcode_path(); m_fff_print->export_gcode(m_temp_output_path, m_gcode_result, [this](const ThumbnailsParams& params) { return this->render_thumbnails(params); }); - // Orca: BBL printers post-process the g-code in place here and never re-parse it into a fresh - // GCodeProcessorResult, so m_gcode_result->nozzle_group_result (consumed by the H2C print-dispatch - // nozzle mapping) survives post-processing. No preservation guard is needed on this path. + // Orca: BBL printers post-process the g-code in place here, in the file the G-code viewer maps, so + // the preview re-reads its moves and line offsets from the edited file. The rest of m_gcode_result, + // including nozzle_group_result (consumed by the H2C print-dispatch nozzle mapping), is kept. if (m_fff_print->is_BBL_printer()) { - run_post_process_scripts(m_temp_output_path, false, "File", m_temp_output_path, m_fff_print->full_print_config()); + if (run_post_process_scripts(m_temp_output_path, false, "File", m_temp_output_path, m_fff_print->full_print_config())) + m_fff_print->reload_gcode_moves(m_gcode_result); } BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format(": export gcode finished"); diff --git a/src/slic3r/GUI/GCodeViewer.cpp b/src/slic3r/GUI/GCodeViewer.cpp index aa4b3ca2bb..03bef82d18 100644 --- a/src/slic3r/GUI/GCodeViewer.cpp +++ b/src/slic3r/GUI/GCodeViewer.cpp @@ -895,10 +895,16 @@ void GCodeViewer::SequentialView::GCodeWindow::render(float top, float bottom, f auto update_lines = [this](uint64_t start_id, uint64_t end_id) { std::vector ret; ret.reserve(end_id - start_id + 1); + // Orca: m_lines_ends indexes into a memory mapping, so it must be clamped to the mapping. If the + // file was modified behind our back (an in-place post-processing script that shrank it), an + // unchecked read is an access violation, which the caller's try/catch cannot catch on Windows. + const size_t file_size = m_file.size(); for (uint64_t id = start_id; id <= end_id; ++id) { // read line from file - const size_t start = id == 1 ? 0 : m_lines_ends[id - 2]; - const size_t original_len = m_lines_ends[id - 1] - start; + // Keep one entry per id: render() indexes m_lines by (id - start_id). + const size_t start = id == 1 ? 0 : std::min(m_lines_ends[id - 2], file_size); + const size_t end = std::min(m_lines_ends[id - 1], file_size); + const size_t original_len = end > start ? end - start : 0; // A character is four bytes at most, so 55 of them always fit in 220. const size_t len = std::min(original_len, (size_t) 55 * 4); std::string gline(m_file.data() + start, len); diff --git a/tests/fff_print/test_gcodeprocessor.cpp b/tests/fff_print/test_gcodeprocessor.cpp index 2d6bafd0a9..4bdc416002 100644 --- a/tests/fff_print/test_gcodeprocessor.cpp +++ b/tests/fff_print/test_gcodeprocessor.cpp @@ -9,6 +9,7 @@ #include "libslic3r/Config.hpp" #include "libslic3r/GCode/GCodeProcessor.hpp" #include "libslic3r/Model.hpp" +#include "libslic3r/Print.hpp" #include "libslic3r/Utils.hpp" #include "test_helpers.hpp" @@ -221,3 +222,62 @@ TEST_CASE("Line ends of the exported G-code mark every newline in the file", "[G INFO("first difference at line " << difference.first - result.lines_ends.begin() + 1); CHECK(difference.first == result.lines_ends.end()); } + +TEST_CASE("Reloaded moves name their lines in G-code a script rewrote in place", "[GCodeProcessor]") +{ + Print print; + Model model; + Test::init_print({ Test::cube(20) }, print, model); + GCodeProcessorResult result; + const std::string gcode = Test::gcode(print, &result); + const auto exported_moves = result.moves; + + // A script that prepends one comment and, writing in text mode on Windows, turns every LF into CRLF. + const std::string prepended = ";EDITED\r\n"; + std::string edited = prepended; + for (const char c : gcode) { + if (c == '\n') + edited += '\r'; + edited += c; + } + ScopedTemporaryFile temp(".gcode"); + save_string_file(temp.path(), edited); + result.filename = temp.string(); + print.reload_gcode_moves(&result); + + std::vector newline_ends; + for (size_t i = edited.find('\n'); i != std::string::npos; i = edited.find('\n', i + 1)) + newline_ends.push_back(i + 1); + CHECK(result.lines_ends == newline_ends); + + // Every move that came from a line now names the same line one further down. + REQUIRE(result.moves.size() == exported_moves.size()); + const auto difference = std::mismatch(exported_moves.begin(), exported_moves.end(), result.moves.begin(), + [](const auto &exported, const auto &reloaded) { + return reloaded.gcode_id == (exported.gcode_id == 0 ? 0 : exported.gcode_id + 1); + }); + INFO("first difference at move " << difference.first - exported_moves.begin()); + CHECK(difference.first == exported_moves.end()); +} + +TEST_CASE("Rewritten G-code that cannot be re-read keeps the moves and hides the G-code window", "[GCodeProcessor]") +{ + Print print; + Model model; + Test::init_print({ Test::cube(20) }, print, model); + GCodeProcessorResult result; + const std::string gcode = Test::gcode(print, &result); + const auto exported_moves = result.moves; + + // A script that strips the trailing config block, which the G-code reader needs. + const size_t config_block = gcode.find("; CONFIG_BLOCK_START"); + REQUIRE(config_block != std::string::npos); + ScopedTemporaryFile temp(".gcode"); + save_string_file(temp.path(), gcode.substr(0, config_block)); + result.filename = temp.string(); + print.reload_gcode_moves(&result); + + CHECK(result.lines_ends.empty()); + REQUIRE(result.moves.size() == exported_moves.size()); + CHECK(result.moves.back().gcode_id == exported_moves.back().gcode_id); +}