Expand mixed slots in by-object filament bookkeeping

This commit is contained in:
SoftFever
2026-08-22 21:14:08 +08:00
parent 2131ef0560
commit 0c3d7c6ed1
2 changed files with 99 additions and 6 deletions

View File

@@ -1,6 +1,7 @@
#include <catch2/catch_all.hpp>
#include "libslic3r/GCode/ToolOrdering.hpp"
#include "libslic3r/MultiNozzleUtils.hpp"
#include "libslic3r/Print.hpp"
#include "test_helpers.hpp"
@@ -135,3 +136,78 @@ TEST_CASE("Whole-layer mixing emits only the nominal layer height", "[MixedFilam
CHECK(gc.find(";HEIGHT:0.12") == std::string::npos);
CHECK(gc.find(";HEIGHT:0.08") == std::string::npos);
}
TEST_CASE("By-object prints without mixed filaments keep their used-filament set", "[MixedFilament]")
{
// Regression guard for the mixed gate: with no mixed slot the by-object bookkeeping must
// be untouched by this change. Object 2 prints with filament 2, so both filaments are used
// and no mixed filament is reported.
DynamicPrintConfig config = multifilament_config(2, {{"print_sequence", "by object"}});
const std::vector<std::vector<ConfigBase::SetDeserializeItem>> overrides{ {}, { {"extruder", "2"} } };
Print print;
Model model;
init_print(std::vector<TriangleMesh>{cube(20), cube(20)}, print, model, config, &overrides);
REQUIRE(print.objects().size() == 2);
print.process();
CHECK(print.get_slice_used_filaments(false) == std::vector<unsigned int>{0, 1});
CHECK(print.get_slice_used_filaments(true) == std::vector<unsigned int>{0, 1});
CHECK(print.get_slice_used_mixed_filaments().empty());
}
TEST_CASE("By-layer prints record a mixed slot's components and the slot itself", "[MixedFilament]")
{
// Control for the by-object case below: the by-layer path publishes the physical
// components (0-based 0 and 1) as used filaments and the mixed slot (config index 2) as
// a used mixed filament. By-object prints must report exactly the same.
Print print;
Model model;
init_print({cube(20)}, print, model, mixed_config(false));
print.process();
CHECK(print.get_slice_used_filaments(false) == std::vector<unsigned int>{0, 1});
CHECK(print.get_slice_used_mixed_filaments() == std::vector<unsigned int>{2});
}
TEST_CASE("By-object prints expand a mixed slot to its components in the slice bookkeeping", "[MixedFilament]")
{
// Sequential prints build their filament lists from unsorted per-object orderings, which
// still carry the virtual slot (config index 2). The slice-used sets and the published
// grouping result must see the physical components 0 and 1 instead, and the slot itself
// must still be reported as a used mixed filament — exactly what the by-layer path yields.
DynamicPrintConfig config = mixed_config(false);
config.set_deserialize_strict({{"print_sequence", "by object"}});
Print print;
Model model;
init_print({cube(20), cube(20)}, print, model, config);
REQUIRE(print.objects().size() == 2);
print.process();
const std::vector<unsigned int> components{0, 1};
CHECK(print.get_slice_used_filaments(false) == components);
CHECK(print.get_slice_used_filaments(true) == components);
CHECK(print.get_slice_used_mixed_filaments() == std::vector<unsigned int>{2});
auto group_result = print.get_layered_nozzle_group_result();
REQUIRE(group_result != nullptr);
CHECK(group_result->get_used_filaments() == components);
}
TEST_CASE("By-object G-code lists a mixed slot's components in the filament header", "[MixedFilament]")
{
DynamicPrintConfig config = mixed_config(false);
config.set_deserialize_strict({{"print_sequence", "by object"}});
Print print;
Model model;
init_print({cube(20), cube(20)}, print, model, config);
const std::string gc = Slic3r::Test::gcode(print);
REQUIRE(!gc.empty());
// The header names the filaments that must be loaded (components 1 and 2, 1-based),
// never the virtual slot 3.
CHECK(gc.find("; filament: 1,2\n") != std::string::npos);
CHECK(gc.find("; filament: 3") == std::string::npos);
}