Belt: warn when the purge tower is enabled but the project has no tower object

The purge tower is a model object the GUI creates and sizes, and libslic3r
only purges into one that exists. A multi-filament belt project sliced from
the CLI without it changed filament with nowhere to purge, silently.

Raised in Hanif Koh's review of #14394.
This commit is contained in:
harrierpigeon
2026-10-02 01:11:59 -05:00
parent 556569c0e3
commit 360a68e078
2 changed files with 45 additions and 0 deletions
+15
View File
@@ -1553,6 +1553,21 @@ StringObjectException Print::validate(std::vector<StringObjectException> *warnin
add_warning(warningtemp);
}
// The purge tower is a model object the GUI creates and sizes; libslic3r only purges
// into one that exists. A project sliced without it (the CLI on a project saved before
// the tower was generated) changes filament with nowhere to purge.
if (m_config.belt_printer.value && m_config.enable_belt_purge_tower.value
&& m_config.print_sequence != PrintSequence::ByObject
&& ! m_config.spiral_mode.value && extruders.size() > 1 && ! this->has_belt_purge_tower()) {
StringObjectException warningtemp;
warningtemp.string = L("The belt purge tower is enabled but the project has no purge tower object; "
"filament changes will not be purged. Open the project in the application "
"to generate the tower.");
warningtemp.opt_key = "enable_belt_purge_tower";
warningtemp.is_warning = true;
add_warning(warningtemp);
}
if (m_config.belt_printer.value && m_config.enable_belt_purge_tower.value) {
const size_t prism_count = std::count_if(m_objects.begin(), m_objects.end(), [](const PrintObject *object) {
return object->config().belt_purge_tower_object.value;
+30
View File
@@ -336,6 +336,36 @@ TEST_CASE("Belt purge planning requires its managed purge object", "[Print][Purg
CHECK_FALSE(print.has_wipe_tower());
}
// The GUI creates the purge tower object; a project sliced without one (the CLI) must say
// that its filament changes go unpurged.
TEST_CASE("Belt purge tower enabled without a tower object warns", "[Print][PurgeTower][belt]")
{
DynamicPrintConfig config = multifilament_config(2, {
{ "belt_printer", 1 },
{ "enable_belt_purge_tower", 1 },
{ "layer_change_gcode", "G92 E0\n" }
});
auto purge_warnings = [](Print &print) {
std::vector<StringObjectException> warnings;
print.validate(&warnings);
return std::count_if(warnings.begin(), warnings.end(), [](const StringObjectException &w) {
return w.opt_key == "enable_belt_purge_tower";
});
};
Model model;
Print print;
build_cubes(model, print, config, /*n=*/2, /*overlap=*/false);
model.objects[1]->config.set_key_value("extruder", new ConfigOptionInt(2));
print.apply(model, config);
REQUIRE(print.extruders().size() > 1);
CHECK(purge_warnings(print) == 1);
model.objects.front()->config.set_key_value("belt_purge_tower_object", new ConfigOptionBool(true));
print.apply(model, config);
CHECK(purge_warnings(print) == 0);
}
TEST_CASE("Belt purge rejects multiple managed purge objects", "[Print][PurgeTower][Regression]")
{
DynamicPrintConfig config = multifilament_config(2, {