From ae138090f1defcb132b32d7241b1abd7804eef48 Mon Sep 17 00:00:00 2001 From: Ioannis Giannakas <59056762+igiannakas@users.noreply.github.com> Date: Wed, 14 Oct 2026 16:51:57 +0100 Subject: [PATCH] Fix uninitialised object ids in "; printing object" comments (#16115) Object and copy ids were only assigned in set_object_info(), which runs only for Klipper, Marlin and RRF with Exclude objects on. Every other printer, Bambu included, printed an uninitialized object id and labeled every copy as copy 0. The numbering now runs on every export, so the labels are stable and unique on all printers. Output for Klipper, Marlin and RRF with Exclude objects on is unchanged. The label test covers Marlin, Klipper and Repetier, plus a BBL row that checks each copy gets its own M624/M625 skip block. Co-authored-by: raistlin7447 --- src/libslic3r/GCode.cpp | 29 ++++++++++----- src/libslic3r/GCode.hpp | 3 +- src/libslic3r/Print.hpp | 6 +-- tests/fff_print/test_gcode.cpp | 37 +++++++++++++++++++ .../fff_print/test_slicing_pipeline_hook.cpp | 4 +- 5 files changed, 64 insertions(+), 15 deletions(-) diff --git a/src/libslic3r/GCode.cpp b/src/libslic3r/GCode.cpp index ee7f6cabc6..dd17cc4fdd 100644 --- a/src/libslic3r/GCode.cpp +++ b/src/libslic3r/GCode.cpp @@ -4201,6 +4201,8 @@ void GCode::_do_export(Print& print, GCodeOutputStream &file, ThumbnailsGenerato file.write_format("; EXECUTABLE_BLOCK_START\n"); + // Set for every printer, as the object label comments use these ids too. + assign_object_label_ids(print); // SoftFever if( m_enable_exclude_object) file.write(set_object_info(&print)); @@ -11055,15 +11057,30 @@ inline std::string polygon_to_string(const Polygon &polygon, Print *print, bool gcode << "]"; return gcode.str(); } -// this function iterator PrintObject and assign a seqential id to each object. -// this id is used to generate unique object id for each object. +// Numbers the objects, and their instances both within each object (id) and across the print +// (unique_id). The "; printing object" comments and the exclude-object commands label objects with these ids. +void GCode::assign_object_label_ids(Print &print) +{ + m_instance_names.clear(); + size_t object_id = 0; + size_t unique_id = 0; + for (PrintObject *object : print.objects()) { + object->set_id(object_id++); + size_t inst_id = 0; + for (PrintInstance &inst : object->instances()) { + inst.id = inst_id++; + inst.unique_id = unique_id++; + } + } +} + +// Writes the exclude-object definitions for the ids assign_object_label_ids() set. std::string GCode::set_object_info(Print *print) { const auto gflavor = print->config().gcode_flavor.value; if (print->is_BBL_printer() || (gflavor != gcfKlipper && gflavor != gcfMarlinLegacy && gflavor != gcfMarlinFirmware && gflavor != gcfRepRapFirmware)) return ""; std::ostringstream gcode; - size_t object_id = 0; // Orca: check if we are in pa calib mode if (print->calib_mode() == CalibMode::Calib_PA_Pattern) { BoundingBoxf bbox_bed(print->config().printable_area.values); @@ -11079,14 +11096,8 @@ std::string GCode::set_object_info(Print *print) { } else if (print->calib_mode() == CalibMode::Calib_PA_Line) { // PA_Line has only one object, no EXCLUDE_OBJECT_DEFINE needed } else { - size_t unique_id = 0; - m_instance_names.clear(); for (PrintObject* object : print->objects()) { - object->set_id(object_id++); - size_t inst_id = 0; for (PrintInstance& inst : object->instances()) { - inst.unique_id = unique_id++; - inst.id = inst_id++; // Outlines are in plate coordinates. On a belt printer that is the frame after // the slicing rotation has been undone and before the G-code axis remap and // machine-frame shear: where the object stands on the belt, which is what an diff --git a/src/libslic3r/GCode.hpp b/src/libslic3r/GCode.hpp index c89b646ae2..08894574ed 100644 --- a/src/libslic3r/GCode.hpp +++ b/src/libslic3r/GCode.hpp @@ -319,6 +319,7 @@ public: bool is_BBL_Printer(); WipeTowerType wipe_tower_type(); + void assign_object_label_ids(Print &print); // SoftFever std::string set_object_info(Print* print); @@ -794,7 +795,7 @@ protected: bool m_enable_exclude_object; std::vector m_label_objects_ids; - // Object label names by instance, built on first use from the ids set_object_info() assigns. + // Object label names by instance, built on first use from the ids assign_object_label_ids() assigns. std::unordered_map m_instance_names; const std::string& instance_name(const PrintInstance &instance); std::string _encode_label_ids_to_base64(std::vector ids); diff --git a/src/libslic3r/Print.hpp b/src/libslic3r/Print.hpp index 070e4f4ddc..11de1d80e4 100644 --- a/src/libslic3r/Print.hpp +++ b/src/libslic3r/Print.hpp @@ -249,9 +249,9 @@ struct PrintInstance // SoftFever // // instance id - size_t id; + size_t id{0}; // Orca: unique id used by marlin/rrf cancel object feature - size_t unique_id; + size_t unique_id{0}; //BBS: instance_shift is too large because of multi-plate, apply without plate offset. Point shift_without_plate_offset() const; @@ -738,7 +738,7 @@ private: // SoftFever // // object id - size_t m_id; + size_t m_id{0}; void apply_conical_overhang(); public: diff --git a/tests/fff_print/test_gcode.cpp b/tests/fff_print/test_gcode.cpp index f63e30a07f..71fe6e16bd 100644 --- a/tests/fff_print/test_gcode.cpp +++ b/tests/fff_print/test_gcode.cpp @@ -50,6 +50,43 @@ TEST_CASE("Klipper object labels name each copy without the characters Klipper c } } +TEST_CASE("Object label comments number each object and copy with or without exclude object", "[GCode]") +{ + // repetier has no exclude-object commands, so set_object_info() writes nothing for it. + // BBL printers write M624/M625 skip blocks whatever exclude_object is set to. + const auto [flavor, exclude_object, bbl] = GENERATE(table({ + {"marlin2", "0", false}, + {"klipper", "0", false}, + {"klipper", "1", false}, + {"repetier", "1", false}, + {"marlin2", "0", true}, + })); + DynamicPrintConfig config = DynamicPrintConfig::full_print_config(); + config.set_deserialize_strict({{"gcode_flavor", flavor}, {"exclude_object", exclude_object}, {"gcode_label_objects", "1"}}); + Print print; + print.is_BBL_printer() = bbl; + Model model; + Test::init_print(std::vector{Test::cube(20.), Test::cube(20.)}, print, model, config, nullptr, false, 2); + arrange_objects(model, BoundingBox{Point::new_scale(0., 0.), Point::new_scale(500., 500.)}, + ArrangeParams{scaled(min_object_distance(config))}); + print.apply(model, config); + + const std::string gcode = Test::gcode(print); + INFO(flavor << ", exclude_object " << exclude_object << ", BBL " << bbl); + for (const char *label : {"id:0 copy 0", "id:0 copy 1", "id:1 copy 0", "id:1 copy 1"}) { + INFO(label); + CHECK(gcode.find("; printing object object.stl " + std::string(label) + "\n") != std::string::npos); + } + if (bbl) + for (const PrintObject *object : print.objects()) + for (const PrintInstance &instance : object->instances()) { + const std::string label_id = std::to_string(instance.model_instance->get_labeled_id()); + INFO("label id " << label_id); + CHECK(gcode.find("; start printing object, unique label id: " + label_id + "\nM624 ") != std::string::npos); + CHECK(gcode.find("; stop printing object, unique label id: " + label_id + "\nM625\n") != std::string::npos); + } +} + TEST_CASE("An exported G-code's settings load back as the settings it was sliced with", "[GCode]") { // Two filaments, so the config block carries the flush matrix already multiplied by flush_multiplier. diff --git a/tests/fff_print/test_slicing_pipeline_hook.cpp b/tests/fff_print/test_slicing_pipeline_hook.cpp index 128f05d81e..500cace206 100644 --- a/tests/fff_print/test_slicing_pipeline_hook.cpp +++ b/tests/fff_print/test_slicing_pipeline_hook.cpp @@ -87,8 +87,8 @@ static std::string strip_nondeterministic_gcode_lines(const std::string& gcode) while (std::getline(in, line)) { if (line.compare(0, 15, "; generated by ") == 0) continue; // wall-clock timestamp if (line.compare(0, 18, "; model label id: ") == 0) continue; // ObjectID-derived - // "; [stop] printing object id:N copy M" / "... unique label id: N" (ObjectID-derived): - if (line.find("printing object") != std::string::npos && line.find(" id:") != std::string::npos) continue; + // "; start/stop printing object, unique label id: N" (ObjectID-derived): + if (line.find("printing object, unique label id: ") != std::string::npos) continue; if (line.find("slicing_pipeline_plugin") != std::string::npos) continue; // config-dump plugin name out += line; out += '\n'; }