From 7c71ac087093a404141c9b338149db4c444928fa Mon Sep 17 00:00:00 2001 From: Hanif Koh Date: Mon, 28 Sep 2026 06:08:17 +0800 Subject: [PATCH] Clamp Ironing Line Spacing to a Usable Minimum An ironing line spacing of 0 reached the fillers from a 3MF, the CLI or the per-filament override, which has no GUI guard. Concentric ironing then never finished slicing, because a zero inset never shrinks the region, and rectilinear ironing was silently dropped. Tiny positive values produced an unprintable number of lines. Top surface and support ironing now clamp the spacing to the 0.05 mm floor the process GUI guard already enforces, so these configurations iron at that spacing. Spacings at or above the floor, including every shipped profile, are unchanged. The concentric filler also returns early on a non-positive step so no other caller can hang it, and the filament settings page now resets a too-small override the same way the process page does. --- src/libslic3r/Fill/Fill.cpp | 4 +- src/libslic3r/Fill/FillConcentric.cpp | 5 ++ src/libslic3r/PrintConfig.hpp | 4 ++ src/libslic3r/Support/SupportParameters.hpp | 2 +- src/slic3r/GUI/ConfigManipulation.cpp | 28 ++++++++++- src/slic3r/GUI/ConfigManipulation.hpp | 1 + src/slic3r/GUI/Tab.cpp | 1 + tests/fff_print/test_fill.cpp | 55 +++++++++++++++++++++ 8 files changed, 95 insertions(+), 5 deletions(-) diff --git a/src/libslic3r/Fill/Fill.cpp b/src/libslic3r/Fill/Fill.cpp index 28fabed8af..a09c69fc5e 100644 --- a/src/libslic3r/Fill/Fill.cpp +++ b/src/libslic3r/Fill/Fill.cpp @@ -1692,9 +1692,9 @@ void Layer::make_ironing() ironing_params.just_infill = false; // ORCA: Get filament-specific overrides if configured, otherwise use process values size_t extruder_idx = ironing_params.extruder - 1; - ironing_params.line_spacing = (!config.filament_ironing_spacing.is_nil(extruder_idx) + ironing_params.line_spacing = std::max(IRONING_SPACING_MIN, !config.filament_ironing_spacing.is_nil(extruder_idx) ? config.filament_ironing_spacing.get_at(extruder_idx) - : config.ironing_spacing); + : config.ironing_spacing.value); ironing_params.inset = (!config.filament_ironing_inset.is_nil(extruder_idx) ? config.filament_ironing_inset.get_at(extruder_idx) : config.ironing_inset); diff --git a/src/libslic3r/Fill/FillConcentric.cpp b/src/libslic3r/Fill/FillConcentric.cpp index 1882f7a656..044fee042e 100644 --- a/src/libslic3r/Fill/FillConcentric.cpp +++ b/src/libslic3r/Fill/FillConcentric.cpp @@ -22,6 +22,9 @@ void FillConcentric::_fill_surface_single( coord_t min_spacing = scale_(this->spacing) * params.multiline; coord_t distance = coord_t(min_spacing / params.density); + // A non-positive step never shrinks the region, so the inset loop below would not end. + if (min_spacing <= 0 || distance <= 0) + return; if (params.density > 0.9999f && !params.dont_adjust) { distance = this->_adjust_solid_spacing(bounding_box.size()(0), distance); @@ -108,6 +111,8 @@ void FillConcentric::_fill_surface_single(const FillParams& params, // no rotation is supported for this infill pattern Point bbox_size = expolygon.contour.bounding_box().size(); coord_t min_spacing = scaled(this->spacing); + if (min_spacing <= 0) + return; if (params.density > 0.9999f && !params.dont_adjust) { coord_t loops_count = std::max(bbox_size.x(), bbox_size.y()) / min_spacing + 1; diff --git a/src/libslic3r/PrintConfig.hpp b/src/libslic3r/PrintConfig.hpp index 679811d699..6284e5bc7a 100644 --- a/src/libslic3r/PrintConfig.hpp +++ b/src/libslic3r/PrintConfig.hpp @@ -178,6 +178,10 @@ enum class IroningType { Count, }; +// Smallest usable ironing line spacing. Anything tighter yields an unprintable number of lines, +// and zero stops the fillers from making progress. +constexpr double IRONING_SPACING_MIN = 0.05; + //BBS enum class WallInfillOrder { InnerOuterInfill, diff --git a/src/libslic3r/Support/SupportParameters.hpp b/src/libslic3r/Support/SupportParameters.hpp index 8d43edb444..e9151c0c95 100644 --- a/src/libslic3r/Support/SupportParameters.hpp +++ b/src/libslic3r/Support/SupportParameters.hpp @@ -64,7 +64,7 @@ struct SupportParameters { this->ironing = object_config.support_ironing; this->ironing_flow = support_material_interface_flow.with_height(support_material_interface_flow.height() * 0.01 * object_config.support_ironing_flow.value); - this->ironing_spacing = object_config.support_ironing_spacing; + this->ironing_spacing = std::max(IRONING_SPACING_MIN, object_config.support_ironing_spacing.value); this->ironing_pattern = object_config.support_ironing_pattern; // Calculate a minimum support layer height as a minimum over all extruders, but not smaller than 10um. diff --git a/src/slic3r/GUI/ConfigManipulation.cpp b/src/slic3r/GUI/ConfigManipulation.cpp index 5a58ec7b6c..6bb73281a5 100644 --- a/src/slic3r/GUI/ConfigManipulation.cpp +++ b/src/slic3r/GUI/ConfigManipulation.cpp @@ -210,6 +210,30 @@ void ConfigManipulation::check_filament_max_volumetric_speed(DynamicPrintConfig } +void ConfigManipulation::check_filament_ironing_spacing(DynamicPrintConfig *config) +{ + const auto *opt = config->option("filament_ironing_spacing"); + if (opt == nullptr) + return; + std::vector values = opt->values; + bool reset = false; + for (size_t i = 0; i < values.size(); ++i) + if (!opt->is_nil(i) && values[i] < IRONING_SPACING_MIN) { + values[i] = 0.1; + reset = true; + } + if (!reset) + return; + const wxString msg_text = _(L("Ironing spacing too small\nIt has been reset to 0.1")); + MessageDialog dialog(nullptr, msg_text, "", wxICON_WARNING | wxOK); + DynamicPrintConfig new_conf = *config; + is_msg_dlg_already_exist = true; + dialog.ShowModal(); + new_conf.set_key_value("filament_ironing_spacing", new ConfigOptionFloatsNullable(values)); + apply(config, &new_conf); + is_msg_dlg_already_exist = false; +} + void ConfigManipulation::check_chamber_temperature(DynamicPrintConfig* config) { bool support_chamber_temp_control=GUI::wxGetApp().preset_bundle->printers.get_selected_preset().config.opt_bool("support_chamber_temp_control"); @@ -332,7 +356,7 @@ void ConfigManipulation::update_print_fff_config(DynamicPrintConfig* config, con } //BBS: ironing_spacing shouldn't be too small or equal to zero - if (config->opt_float("ironing_spacing") < 0.05) + if (config->opt_float("ironing_spacing") < IRONING_SPACING_MIN) { const wxString msg_text = _(L("Ironing spacing too small\nIt has been reset to 0.1")); MessageDialog dialog(nullptr, msg_text, "", wxICON_WARNING | wxOK); @@ -343,7 +367,7 @@ void ConfigManipulation::update_print_fff_config(DynamicPrintConfig* config, con apply(config, &new_conf); is_msg_dlg_already_exist = false; } - if (config->opt_float("support_ironing_spacing") < 0.05) + if (config->opt_float("support_ironing_spacing") < IRONING_SPACING_MIN) { const wxString msg_text = _(L("Ironing spacing too small\nIt has been reset to 0.1")); MessageDialog dialog(nullptr, msg_text, "", wxICON_WARNING | wxOK); diff --git a/src/slic3r/GUI/ConfigManipulation.hpp b/src/slic3r/GUI/ConfigManipulation.hpp index ac53ffb4bb..92b76643f2 100644 --- a/src/slic3r/GUI/ConfigManipulation.hpp +++ b/src/slic3r/GUI/ConfigManipulation.hpp @@ -84,6 +84,7 @@ public: void check_nozzle_temperature_initial_layer_range(DynamicPrintConfig* config); void check_adaptive_pressure_advance_model(DynamicPrintConfig* config); void check_filament_max_volumetric_speed(DynamicPrintConfig *config); + void check_filament_ironing_spacing(DynamicPrintConfig *config); void check_chamber_temperature(DynamicPrintConfig* config); void check_chamber_minimal_temperature(DynamicPrintConfig* config); bool check_layer_height(DynamicPrintConfig* config); diff --git a/src/slic3r/GUI/Tab.cpp b/src/slic3r/GUI/Tab.cpp index bf348f856f..72974e8bac 100644 --- a/src/slic3r/GUI/Tab.cpp +++ b/src/slic3r/GUI/Tab.cpp @@ -4905,6 +4905,7 @@ void TabFilament::update() return; // ys_FIXME m_config_manipulation.check_filament_max_volumetric_speed(m_config); + m_config_manipulation.check_filament_ironing_spacing(m_config); m_update_cnt++; diff --git a/tests/fff_print/test_fill.cpp b/tests/fff_print/test_fill.cpp index 0ff7e65e5d..1a85cb2727 100644 --- a/tests/fff_print/test_fill.cpp +++ b/tests/fff_print/test_fill.cpp @@ -747,6 +747,61 @@ TEST_CASE("A region with ironing turned off is never ironed", "[Fill]") REQUIRE(Layer::choose_ironing_extruder(cfg, spiral_mode, /*is_topmost_layer=*/true) == -1); } +// Ironing path count and total length in mm, over the whole object. +static std::pair ironing_extent(const Print &print) +{ + size_t paths = 0; + double length = 0.; + for (const Layer *layer : print.objects().front()->layers()) + for (const LayerRegion *region : layer->regions()) + for (const ExtrusionEntity *entity : region->fills.flatten().entities) + if (ironing_role(entity->role())) { + ++paths; + length += unscale(entity->length()); + } + return {paths, length}; +} + +TEST_CASE("Ironing spacing below the minimum irons at the minimum spacing", "[Fill]") +{ + const std::string pattern = GENERATE("rectilinear", "concentric"); + const bool via_filament = GENERATE(false, true); + const double spacing = GENERATE(0., 0.001); + CAPTURE(pattern, via_filament, spacing); + + auto ironing_for = [&pattern, via_filament](double spacing) { + DynamicPrintConfig config = DynamicPrintConfig::full_print_config(); + config.set_deserialize_strict({{"ironing_type", "top"}, + {"ironing_pattern", pattern}, + {"layer_height", 0.2}}); + // The filament override replaces the process spacing, which stays at a usable value. + if (via_filament) + config.set_deserialize_strict({{"ironing_spacing", 0.1}, {"filament_ironing_spacing", spacing}}); + else + config.set_deserialize_strict({{"ironing_spacing", spacing}}); + Print print; + Slic3r::Test::init_and_process_print({Slic3r::Test::cube(20)}, print, config); + return ironing_extent(print); + }; + + const std::pair clamped = ironing_for(spacing); + const std::pair minimum = ironing_for(IRONING_SPACING_MIN); + REQUIRE(minimum.first > 0); + CHECK(clamped.first == minimum.first); + CHECK_THAT(clamped.second, Catch::Matchers::WithinRel(minimum.second, 1e-9)); +} + +TEST_CASE("Concentric fill at zero spacing returns without paths", "[Fill]") +{ + std::unique_ptr filler(Fill::new_from_type(ipConcentric)); + filler->spacing = 0.; + filler->bounding_box = BoundingBox(Point(0, 0), Point::new_scale(10, 10)); + FillParams params; + params.density = 1.f; + Surface surface(stTop, ExPolygon({Point(0, 0), Point::new_scale(10, 0), Point::new_scale(10, 10), Point::new_scale(0, 10)})); + CHECK(filler->fill_surface(&surface, params).empty()); +} + TEST_CASE("Solid infill direction offsets every layer when no template is set", "[Fill]") { auto angles_for = [](int direction) {