mirror of
https://github.com/OrcaSlicer/OrcaSlicer.git
synced 2026-10-15 03:41:28 +00:00
Write the first layer temperatures of the printed filaments into the G-code header (#16422)
The `; first_layer_bed_temperature` and `; first_layer_temperature` compatibility lines were always taken from filament 1. Anycubic's GoKlipper reads the bed line to heat the bed, so a plate printed only with another filament got the wrong temperature. The bed line now follows `bed_temperature_formula` (highest used filament or first printing filament). The nozzle line uses the first printing filament. Both values are computed before the by-object loop.
This commit is contained in:
@@ -16,6 +16,7 @@
|
||||
|
||||
#include "libslic3r/TriangleMesh.hpp"
|
||||
#include "libslic3r/Point.hpp"
|
||||
#include <cstddef>
|
||||
#include <fstream>
|
||||
#include <ios>
|
||||
#include <string>
|
||||
@@ -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.
|
||||
|
||||
Reference in New Issue
Block a user