mirror of
https://github.com/OrcaSlicer/OrcaSlicer.git
synced 2026-10-10 09:11:08 +00:00
Fix garbled G-code preview when a post-processing script is used (#15005)
* Rebuild the G-code line offsets after post-processing scripts run in place * Clamp the G-code window reads to the mapped file size * Add tests for rebuilding the G-code line offsets * Include <mutex>, <ios> and boost/filesystem/operations.hpp where they are used * Keep the preview's G-code lines and highlight in step with post-processing scripts --------- Co-authored-by: SoftFever <103989404+SoftFever@users.noreply.github.com> Co-authored-by: SoftFever <softfeverever@gmail.com>
This commit is contained in:
co-authored by
SoftFever
SoftFever
parent
eb28daf0fe
commit
e0b35f9ec9
@@ -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<std::mutex> lock(result->result_mutex);
|
||||
result->lines_ends.clear();
|
||||
return;
|
||||
}
|
||||
|
||||
GCodeProcessorResult& reloaded = processor.result();
|
||||
std::lock_guard<std::mutex> lock(result->result_mutex);
|
||||
result->moves = std::move(reloaded.moves);
|
||||
result->lines_ends = std::move(reloaded.lines_ends);
|
||||
}
|
||||
|
||||
std::tuple<float, float> Print::object_skirt_offset(double margin_height) const
|
||||
{
|
||||
if (config().skirt_loops == 0 || config().skirt_type != stPerObject || m_objects.empty())
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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");
|
||||
|
||||
@@ -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<Line> 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);
|
||||
|
||||
@@ -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<size_t> 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);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user