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 <kris.austin@gmail.com>
This commit is contained in:
Ioannis Giannakas
2026-10-14 10:51:57 -05:00
committed by GitHub
co-authored by raistlin7447
parent e028476d2a
commit ae138090f1
5 changed files with 64 additions and 15 deletions
+20 -9
View File
@@ -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
+2 -1
View File
@@ -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<size_t> 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<const PrintInstance*, std::string> m_instance_names;
const std::string& instance_name(const PrintInstance &instance);
std::string _encode_label_ids_to_base64(std::vector<size_t> ids);
+3 -3
View File
@@ -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:
+37
View File
@@ -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<std::string, std::string, bool>({
{"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<TriangleMesh>{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.
@@ -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 <name> 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';
}