diff --git a/src/libslic3r/GCode.cpp b/src/libslic3r/GCode.cpp index 74d0e38496..ce2be2db58 100644 --- a/src/libslic3r/GCode.cpp +++ b/src/libslic3r/GCode.cpp @@ -3570,6 +3570,13 @@ void GCode::_do_export(Print& print, GCodeOutputStream &file, ThumbnailsGenerato print.throw_if_canceled(); + // Taken before the object loop, which reassigns initial_extruder_id when printing by object. + const int compat_first_layer_bed_temp = m_config.bed_temperature_formula == BedTempFormula::btfHighestTemp ? + get_highest_bed_temperature(true, print) : + get_bed_temperature(initial_extruder_id, true, print.config().curr_bed_type); + const int compat_first_layer_nozzle_temp = + print.config().nozzle_temperature_initial_layer.get_at(get_filament_config_index((int) initial_extruder_id)); + int extruder_id = get_extruder_id(initial_extruder_id); m_cooling_buffer = make_unique(*this); @@ -4160,12 +4167,8 @@ void GCode::_do_export(Print& print, GCodeOutputStream &file, ThumbnailsGenerato file.write(full_config); // SoftFever: write compatiple image - int first_layer_bed_temperature = get_bed_temperature(0, true, print.config().curr_bed_type); - file.write_format("; first_layer_bed_temperature = %d\n", - first_layer_bed_temperature); - file.write_format( - "; first_layer_temperature = %d\n", - print.config().nozzle_temperature_initial_layer.get_at(0)); + file.write_format("; first_layer_bed_temperature = %d\n", compat_first_layer_bed_temp); + file.write_format("; first_layer_temperature = %d\n", compat_first_layer_nozzle_temp); file.write("; CONFIG_BLOCK_END\n\n"); } else if (thumbnail_cb != nullptr) { // generate the thumbnails @@ -4750,10 +4753,9 @@ void GCode::_do_export(Print& print, GCodeOutputStream &file, ThumbnailsGenerato file.write(full_config); // SoftFever: write compatiple info - int first_layer_bed_temperature = get_bed_temperature(0, true, print.config().curr_bed_type); - file.write_format("; first_layer_bed_temperature = %d\n", first_layer_bed_temperature); + file.write_format("; first_layer_bed_temperature = %d\n", compat_first_layer_bed_temp); file.write_format("; bed_shape = %s\n", print.full_print_config().opt_serialize("printable_area").c_str()); - file.write_format("; first_layer_temperature = %d\n", print.config().nozzle_temperature_initial_layer.get_at(0)); + file.write_format("; first_layer_temperature = %d\n", compat_first_layer_nozzle_temp); file.write_format("; first_layer_height = %.3f\n", print.config().initial_layer_print_height.value); //SF TODO diff --git a/tests/fff_print/test_gcode.cpp b/tests/fff_print/test_gcode.cpp index 71fe6e16bd..07091eddef 100644 --- a/tests/fff_print/test_gcode.cpp +++ b/tests/fff_print/test_gcode.cpp @@ -16,6 +16,7 @@ #include "libslic3r/TriangleMesh.hpp" #include "libslic3r/Point.hpp" +#include #include #include #include @@ -50,6 +51,93 @@ TEST_CASE("Klipper object labels name each copy without the characters Klipper c } } +// Firmwares read the compatibility header lines (Anycubic's GoKlipper heats the bed with +// first_layer_bed_temperature), so they must describe the filament the plate prints, not filament 1. +TEST_CASE("The G-code header names the first layer temperatures of the filament the plate prints", "[GCode]") +{ + const std::string formula = GENERATE("by_first_filament", "by_highest_temp"); + DYNAMIC_SECTION("bed_temperature_formula " << formula) { + // Filament 1: bed 60, nozzle 200. Filament 2: bed 100, nozzle 250. Everything prints with filament 2. + DynamicPrintConfig config = Test::multifilament_config(2, { + {"bed_temperature_formula", formula}, + // Authored 1..n as PresetBundle writes it; the all-1s default collapses every per-filament value + // to filament 1. + {"filament_self_index", "1,2"}, + {"filament_extruder_variant", "Direct Drive Standard;Direct Drive Standard"}, + {"enable_prime_tower", 0}, + {"nozzle_temperature_initial_layer", "200,250"}, + {"nozzle_temperature", "200,250"}, + {"supertack_plate_temp_initial_layer", "60,100"}, + {"cool_plate_temp_initial_layer", "60,100"}, + {"textured_cool_plate_temp_initial_layer", "60,100"}, + {"eng_plate_temp_initial_layer", "60,100"}, + {"hot_plate_temp_initial_layer", "60,100"}, + {"textured_plate_temp_initial_layer", "60,100"}, + {"sparse_infill_filament_id", 2}, + {"internal_solid_filament_id", 2}, + {"top_surface_filament_id", 2}, + {"bottom_surface_filament_id", 2}, + {"outer_wall_filament_id", 2}, + {"inner_wall_filament_id", 2}, + {"skirt_loops", 0}, + {"brim_type", "no_brim"}, + }); + const std::string gcode = Test::slice({Test::cube(20)}, config); + + const auto header_value = [&gcode](const std::string& key) { + const std::string token = "; " + key + " = "; + const size_t pos = gcode.find(token); + REQUIRE(pos != std::string::npos); + return std::stoi(gcode.substr(pos + token.size())); + }; + CHECK(header_value("first_layer_bed_temperature") == 100); + CHECK(header_value("first_layer_temperature") == 250); + } +} + +// Printing by object reassigns the initial filament to each object's first filament while it emits the objects, so +// the header must still name the filament the print starts with: the firmware heats the bed for the first object. +TEST_CASE("The G-code header names the first object's filament when printing by object", "[GCode]") +{ + // Filament 1: bed 60, nozzle 200. Filament 2: bed 100, nozzle 250. Object 1 prints with filament 2, object 2 + // with filament 1. + DynamicPrintConfig config = Test::multifilament_config(2, { + {"print_sequence", "by object"}, + {"bed_temperature_formula", "by_first_filament"}, + {"filament_self_index", "1,2"}, + {"filament_extruder_variant", "Direct Drive Standard;Direct Drive Standard"}, + {"enable_prime_tower", 0}, + {"nozzle_temperature_initial_layer", "200,250"}, + {"nozzle_temperature", "200,250"}, + {"supertack_plate_temp_initial_layer", "60,100"}, + {"cool_plate_temp_initial_layer", "60,100"}, + {"textured_cool_plate_temp_initial_layer", "60,100"}, + {"eng_plate_temp_initial_layer", "60,100"}, + {"hot_plate_temp_initial_layer", "60,100"}, + {"textured_plate_temp_initial_layer", "60,100"}, + {"skirt_loops", 0}, + {"brim_type", "no_brim"}, + }); + const std::string gcode = Test::slice_with_object_overrides({Test::cube(20), Test::cube(20)}, config, + {{{"extruder", "2"}}, {{"extruder", "1"}}}); + + // Filament 2 starts and filament 1 follows, so the last object's filament differs from the first one's. + const size_t first_t1 = gcode.find("\nT1"); + const size_t first_t0 = gcode.find("\nT0"); + REQUIRE(first_t1 != std::string::npos); + REQUIRE(first_t0 != std::string::npos); + REQUIRE(first_t1 < first_t0); + + const auto header_value = [&gcode](const std::string& key) { + const std::string token = "; " + key + " = "; + const size_t pos = gcode.find(token); + REQUIRE(pos != std::string::npos); + return std::stoi(gcode.substr(pos + token.size())); + }; + CHECK(header_value("first_layer_bed_temperature") == 100); + CHECK(header_value("first_layer_temperature") == 250); +} + 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.