#include #include "libslic3r/GCodeReader.hpp" #include "test_helpers.hpp" #include #include #include using namespace Slic3r; using namespace Slic3r::Test; // 0-based tool indices used by extrusions whose role comment contains `role` (needs gcode_comments). static std::set tools_for_role(const std::string& gcode, const std::string& role) { std::set tools; int current_tool = 0; GCodeReader reader; reader.parse_buffer(gcode, [&](GCodeReader& self, const GCodeReader::GCodeLine& line) { const std::string cmd(line.cmd()); if (cmd.size() >= 2 && cmd[0] == 'T' && std::isdigit((unsigned char)cmd[1])) current_tool = std::stoi(cmd.substr(1)); else if (line.extruding(self) && std::string(line.comment()).find(role) != std::string::npos) tools.insert(current_tool); }); return tools; } // Tool index = filament id - 1; brim and skirt follow the wall filament. TEST_CASE("Each feature prints with its assigned filament", "[MultiFilament]") { auto [infill_filament, wall_filament] = GENERATE(table({ {1, 1}, {1, 2}, {2, 1}, {2, 2} })); DYNAMIC_SECTION("infill filament " << infill_filament << ", wall filament " << wall_filament) { const std::string gcode = slice({ cube(20) }, multifilament_config(2, { { "sparse_infill_filament_id", infill_filament }, { "internal_solid_filament_id", infill_filament }, { "top_surface_filament_id", infill_filament }, { "bottom_surface_filament_id", infill_filament }, { "outer_wall_filament_id", wall_filament }, { "inner_wall_filament_id", wall_filament }, { "skirt_loops", 1 }, { "brim_type", "outer_only" }, { "brim_width", 5 }, })); const std::set wall_tool{ wall_filament - 1 }; const std::set infill_tool{ infill_filament - 1 }; CHECK(tools_for_role(gcode, "perimeter") == wall_tool); CHECK(tools_for_role(gcode, "infill") == infill_tool); // sparse + solid + top/bottom CHECK(tools_for_role(gcode, "brim") == wall_tool); CHECK(tools_for_role(gcode, "skirt") == wall_tool); } } TEST_CASE("Each feature prints with its assigned filament (three filaments)", "[MultiFilament]") { const std::string gcode = slice({ cube(20) }, multifilament_config(3, { { "sparse_infill_filament_id", 2 }, { "internal_solid_filament_id", 2 }, { "top_surface_filament_id", 2 }, { "bottom_surface_filament_id", 2 }, { "outer_wall_filament_id", 3 }, { "inner_wall_filament_id", 3 }, { "skirt_loops", 0 }, { "brim_type", "no_brim" }, })); CHECK(tools_for_role(gcode, "perimeter") == std::set{ 2 }); // filament 3 CHECK(tools_for_role(gcode, "infill") == std::set{ 1 }); // filament 2 } // The override must survive tool ordering: object 1's walls print on their filament's // tool, object 0 stays on the first. If dropped, every wall prints on tool 0. TEST_CASE("Per-object wall filament override is honored", "[MultiFilament]") { const std::string gcode = slice_with_object_overrides( { cube(20), cube(20) }, multifilament_config(2, { { "skirt_loops", 0 }, { "brim_type", "no_brim" }, { "print_sequence", "by object" }, }), { {}, { { "outer_wall_filament_id", 2 }, { "inner_wall_filament_id", 2 } } }); CHECK(tools_for_role(gcode, "perimeter") == std::set{ 0, 1 }); CHECK(tools_for_role(gcode, "infill") == std::set{ 0 }); // infill not overridden: stays on F1 }