Extract Layer::choose_ironing_extruder for unit-testable ironing routing (#13467)

* Extract Layer::choose_ironing_extruder for unit-testable ironing routing

The ironing extruder selection in make_ironing() was a 5-line nested
conditional inlined at the top of the loop, with no isolated test
coverage. Pull the gating into a static helper so the routing decision
is unit-testable without spinning up the slicing pipeline.

Pure refactor: the helper preserves the original logic bit-for-bit
(NoIroning -> -1; AllSolid always enabled; TopSurfaces and TopmostOnly
require some top shells or, in spiral mode, more than one bottom shell;
TopmostOnly additionally requires being on the topmost layer; enabled
ironing routes to solid_infill_filament).

Add tests/fff_print/test_choose_ironing_extruder.cpp covering:
- AllSolid regardless of layer position
- TopSurfaces with top_shell_layers > 0
- TopSurfaces with top_shell_layers=0 + spiral mode + bottom_shell_layers>1
- TopmostOnly + topmost layer
- NoIroning short-circuit
- TopSurfaces with top_shell_layers=0 (and not spiral) -> disabled
- TopSurfaces, spiral, but bottom_shell_layers=1 -> disabled
- TopmostOnly on a non-topmost layer -> disabled

* Move ironing routing test into the Fill subsystem file

Rename the test to tests/libslic3r/test_fill.cpp and tag it [Fill] to
match the subsystem it covers, use flat behavioral test cases with
GENERATE for the parameterized ones, and drop the history narration from
the code comments.

* tests: move ironing routing tests into fff_print/test_fill.cpp

Keeps the Fill tests in one file, alongside the existing ironing
rotation-template test.
This commit is contained in:
Daniel Williams
2026-09-14 20:37:04 +08:00
committed by GitHub
parent ecbe1b1b90
commit 70247ad298
3 changed files with 97 additions and 13 deletions

View File

@@ -1595,6 +1595,25 @@ Polylines Layer::generate_sparse_infill_polylines_for_anchoring(FillAdaptive::Oc
return sparse_infill_polylines; return sparse_infill_polylines;
} }
// Returns the filament id (1-based) the region is ironed with, or -1 when the
// region is not ironed. AllSolid always irons. TopSurfaces and TopmostOnly need
// either some top shells or, in spiral mode, more than one bottom shell, and
// TopmostOnly additionally needs the layer to be the topmost one.
int Layer::choose_ironing_extruder(const PrintRegionConfig &cfg,
bool spiral_mode,
bool is_topmost_layer)
{
if (cfg.ironing_type == IroningType::NoIroning)
return -1;
const bool gate = (cfg.ironing_type == IroningType::AllSolid)
|| ((cfg.top_shell_layers > 0 || (spiral_mode && cfg.bottom_shell_layers > 1))
&& (cfg.ironing_type == IroningType::TopSurfaces
|| (cfg.ironing_type == IroningType::TopmostOnly && is_topmost_layer)));
if (!gate)
return -1;
return cfg.top_surface_filament_id;
}
// Create ironing extrusions over top surfaces. // Create ironing extrusions over top surfaces.
void Layer::make_ironing() void Layer::make_ironing()
{ {
@@ -1664,19 +1683,10 @@ void Layer::make_ironing()
if (! layerm->slices.empty()) { if (! layerm->slices.empty()) {
IroningParams ironing_params; IroningParams ironing_params;
const PrintRegionConfig &config = layerm->region().config(); const PrintRegionConfig &config = layerm->region().config();
if (config.ironing_type != IroningType::NoIroning && ironing_params.extruder = Layer::choose_ironing_extruder(
(config.ironing_type == IroningType::AllSolid || config,
((config.top_shell_layers > 0 || (this->object()->print()->config().spiral_mode && config.bottom_shell_layers > 1)) && /*spiral_mode=*/this->object()->print()->config().spiral_mode,
(config.ironing_type == IroningType::TopSurfaces || /*is_topmost_layer=*/layerm->layer()->upper_layer == nullptr);
(config.ironing_type == IroningType::TopmostOnly && layerm->layer()->upper_layer == nullptr))))) {
if (config.outer_wall_filament_id == config.top_surface_filament_id || config.wall_loops == 0) {
// Iron the whole face.
ironing_params.extruder = config.top_surface_filament_id;
} else {
// Iron just the infill.
ironing_params.extruder = config.top_surface_filament_id;
}
}
if (ironing_params.extruder != -1) { if (ironing_params.extruder != -1) {
//TODO just_infill is currently not used. //TODO just_infill is currently not used.
ironing_params.just_infill = false; ironing_params.just_infill = false;

View File

@@ -16,6 +16,7 @@ using LayerPtrs = std::vector<Layer*>;
class LayerRegion; class LayerRegion;
using LayerRegionPtrs = std::vector<LayerRegion*>; using LayerRegionPtrs = std::vector<LayerRegion*>;
class PrintRegion; class PrintRegion;
class PrintRegionConfig;
class PrintObject; class PrintObject;
class Print; class Print;
@@ -200,6 +201,11 @@ public:
FillAdaptive::Octree *support_fill_octree, FillAdaptive::Octree *support_fill_octree,
FillLightning::Generator* lightning_generator) const; FillLightning::Generator* lightning_generator) const;
void make_ironing(); void make_ironing();
// Returns the filament id (1-based) the region is ironed with, or -1 when the
// region is not ironed.
static int choose_ironing_extruder(const PrintRegionConfig &cfg,
bool spiral_mode,
bool is_topmost_layer);
void make_contour_z(const sla::IndexedMesh &mesh); void make_contour_z(const sla::IndexedMesh &mesh);
void export_region_slices_to_svg(const char *path) const; void export_region_slices_to_svg(const char *path) const;

View File

@@ -15,6 +15,7 @@
#include "libslic3r/Geometry.hpp" #include "libslic3r/Geometry.hpp"
#include "libslic3r/Layer.hpp" #include "libslic3r/Layer.hpp"
#include "libslic3r/Print.hpp" #include "libslic3r/Print.hpp"
#include "libslic3r/PrintConfig.hpp"
#include "libslic3r/SVG.hpp" #include "libslic3r/SVG.hpp"
#include "libslic3r/libslic3r.h" #include "libslic3r/libslic3r.h"
@@ -676,6 +677,73 @@ TEST_CASE("Ironing follows the solid infill rotation template", "[Fill]")
REQUIRE(compared > int(ironing.size()) / 2); REQUIRE(compared > int(ironing.size()) / 2);
} }
namespace {
PrintRegionConfig ironing_config(IroningType type,
int top_surface_filament_id = 1,
int top_shell_layers = 3,
int bottom_shell_layers = 1)
{
PrintRegionConfig cfg;
cfg.ironing_type.value = type;
cfg.top_surface_filament_id.value = top_surface_filament_id;
cfg.top_shell_layers.value = top_shell_layers;
cfg.bottom_shell_layers.value = bottom_shell_layers;
cfg.outer_wall_filament_id.value = 1;
cfg.wall_loops.value = 2;
return cfg;
}
} // namespace
TEST_CASE("Ironing an all-solid region uses the top surface filament on every layer", "[Fill]")
{
const PrintRegionConfig cfg = ironing_config(IroningType::AllSolid, /*top_surface_filament_id=*/2);
const bool is_topmost_layer = GENERATE(false, true);
CAPTURE(is_topmost_layer);
REQUIRE(Layer::choose_ironing_extruder(cfg, /*spiral_mode=*/false, is_topmost_layer) == 2);
}
TEST_CASE("Ironing top surfaces uses the top surface filament when the region has top shells", "[Fill]")
{
const PrintRegionConfig cfg = ironing_config(IroningType::TopSurfaces,
/*top_surface_filament_id=*/3,
/*top_shell_layers=*/2);
REQUIRE(Layer::choose_ironing_extruder(cfg, /*spiral_mode=*/false, /*is_topmost_layer=*/false) == 3);
}
TEST_CASE("Ironing top surfaces without top shells needs spiral mode and more than one bottom shell", "[Fill]")
{
const PrintRegionConfig one_bottom_shell = ironing_config(IroningType::TopSurfaces,
/*top_surface_filament_id=*/1,
/*top_shell_layers=*/0,
/*bottom_shell_layers=*/1);
const PrintRegionConfig two_bottom_shells = ironing_config(IroningType::TopSurfaces,
/*top_surface_filament_id=*/1,
/*top_shell_layers=*/0,
/*bottom_shell_layers=*/2);
REQUIRE(Layer::choose_ironing_extruder(two_bottom_shells, /*spiral_mode=*/true, /*is_topmost_layer=*/false) == 1);
REQUIRE(Layer::choose_ironing_extruder(one_bottom_shell, /*spiral_mode=*/true, /*is_topmost_layer=*/false) == -1);
REQUIRE(Layer::choose_ironing_extruder(two_bottom_shells, /*spiral_mode=*/false, /*is_topmost_layer=*/false) == -1);
}
TEST_CASE("Ironing the topmost surface only applies to the topmost layer", "[Fill]")
{
const PrintRegionConfig cfg = ironing_config(IroningType::TopmostOnly, /*top_surface_filament_id=*/4);
REQUIRE(Layer::choose_ironing_extruder(cfg, /*spiral_mode=*/false, /*is_topmost_layer=*/true) == 4);
REQUIRE(Layer::choose_ironing_extruder(cfg, /*spiral_mode=*/false, /*is_topmost_layer=*/false) == -1);
}
TEST_CASE("A region with ironing turned off is never ironed", "[Fill]")
{
const PrintRegionConfig cfg = ironing_config(IroningType::NoIroning);
const bool spiral_mode = GENERATE(false, true);
CAPTURE(spiral_mode);
REQUIRE(Layer::choose_ironing_extruder(cfg, spiral_mode, /*is_topmost_layer=*/true) == -1);
}
TEST_CASE("Solid infill direction offsets every layer when no template is set", "[Fill]") TEST_CASE("Solid infill direction offsets every layer when no template is set", "[Fill]")
{ {
auto angles_for = [](int direction) { auto angles_for = [](int direction) {