From b8cfa32a41bc3250b89e7a0cb2b0289ae7f9d541 Mon Sep 17 00:00:00 2001 From: Kris Austin Date: Wed, 22 Jul 2026 14:49:37 -0500 Subject: [PATCH] fix: multi-material slicing crash with line width 0 (#14455) --- src/libslic3r/MultiMaterialSegmentation.cpp | 15 ++++- src/libslic3r/MultiMaterialSegmentation.hpp | 6 ++ tests/libslic3r/CMakeLists.txt | 1 + .../test_multimaterial_segmentation.cpp | 57 +++++++++++++++++++ 4 files changed, 77 insertions(+), 2 deletions(-) create mode 100644 tests/libslic3r/test_multimaterial_segmentation.cpp diff --git a/src/libslic3r/MultiMaterialSegmentation.cpp b/src/libslic3r/MultiMaterialSegmentation.cpp index 9e465715d5..6f80f7b759 100644 --- a/src/libslic3r/MultiMaterialSegmentation.cpp +++ b/src/libslic3r/MultiMaterialSegmentation.cpp @@ -1177,6 +1177,18 @@ static bool is_volume_sinking(const indexed_triangle_set &its, const Transform3d //#define MMU_SEGMENTATION_DEBUG_TOP_BOTTOM +double resolve_outer_wall_line_width(const PrintRegionConfig ®ion_config, const PrintObjectConfig &object_config, const PrintConfig &print_config) +{ + // A filament id of 0 underflows, and get_at() then falls back to the first nozzle. + const double nozzle_diameter = print_config.nozzle_diameter.get_at(region_config.outer_wall_filament_id - 1); + ConfigOptionFloatOrPercent width = region_config.outer_wall_line_width; + if (width.value == 0) + width = object_config.line_width; + if (!width.percent && width.value <= 0.) + return Flow::auto_extrusion_width(frExternalPerimeter, float(nozzle_diameter)); + return width.get_abs_value(nozzle_diameter); +} + // Returns segmentation of top and bottom layers based on painting in segmentation gizmos. static inline std::vector> segmentation_top_and_bottom_layers(const PrintObject &print_object, const std::vector &input_expolygons, @@ -1347,8 +1359,7 @@ static inline std::vector> segmentation_top_and_bottom_l // As this region may split existing regions, we collect statistics over all regions for color_idx == 0. color_idx == 0 || config.outer_wall_filament_id == int(color_idx)) { //BBS: the extrusion line width is outer wall rather than inner wall - const double nozzle_diameter = print_object.print()->config().nozzle_diameter.get_at(0); - double outer_wall_line_width = config.get_abs_value("outer_wall_line_width", nozzle_diameter); + double outer_wall_line_width = resolve_outer_wall_line_width(config, print_object.config(), print_object.print()->config()); out.extrusion_width = std::max(out.extrusion_width, outer_wall_line_width); out.top_shell_layers = std::max(out.top_shell_layers, config.top_shell_layers); out.bottom_shell_layers = std::max(out.bottom_shell_layers, config.bottom_shell_layers); diff --git a/src/libslic3r/MultiMaterialSegmentation.hpp b/src/libslic3r/MultiMaterialSegmentation.hpp index fad97e4cf2..ee7b07c0d8 100644 --- a/src/libslic3r/MultiMaterialSegmentation.hpp +++ b/src/libslic3r/MultiMaterialSegmentation.hpp @@ -9,6 +9,9 @@ namespace Slic3r { class ExPolygon; class ModelVolume; class PrintObject; +class PrintConfig; +class PrintObjectConfig; +class PrintRegionConfig; class FacetsAnnotation; using ExPolygons = std::vector; @@ -52,6 +55,9 @@ std::vector> multi_material_segmentation_by_painting(con // Returns fuzzy skin segmentation based on painting in fuzzy skin segmentation gizmo std::vector> fuzzy_skin_segmentation_by_painting(const PrintObject &print_object, const std::function &throw_on_cancel_callback); +// Effective outer-wall line width for a region, resolved against its own nozzle with PrintRegion::flow's fallback. +double resolve_outer_wall_line_width(const PrintRegionConfig ®ion_config, const PrintObjectConfig &object_config, const PrintConfig &print_config); + } // namespace Slic3r namespace boost::polygon { diff --git a/tests/libslic3r/CMakeLists.txt b/tests/libslic3r/CMakeLists.txt index 23e0d9c0e0..97d3576974 100644 --- a/tests/libslic3r/CMakeLists.txt +++ b/tests/libslic3r/CMakeLists.txt @@ -19,6 +19,7 @@ add_executable(${_TEST_NAME}_tests test_preset_diff.cpp test_elephant_foot_compensation.cpp test_geometry.cpp + test_multimaterial_segmentation.cpp test_placeholder_parser.cpp test_polygon.cpp test_mutable_polygon.cpp diff --git a/tests/libslic3r/test_multimaterial_segmentation.cpp b/tests/libslic3r/test_multimaterial_segmentation.cpp new file mode 100644 index 0000000000..271b9d5660 --- /dev/null +++ b/tests/libslic3r/test_multimaterial_segmentation.cpp @@ -0,0 +1,57 @@ +#include + +// MultiMaterialSegmentation.hpp declares boost::polygon traits for ColoredLine, so its +// geometry/boost dependencies must be included first. +#include +#include "libslic3r/Line.hpp" +#include "libslic3r/Flow.hpp" +#include "libslic3r/MultiMaterialSegmentation.hpp" +#include "libslic3r/PrintConfig.hpp" + +using namespace Slic3r; + +TEST_CASE("Multi-material segmentation resolves the outer-wall line width", "[MultiMaterialSegmentation][Regression]") +{ + struct Case + { + std::string description; + double outer_value; + bool outer_percent; + double line_value; + bool line_percent; + std::vector nozzle_diameters; + int outer_wall_filament_id; + double expected; + }; + + auto c = GENERATE(values({ + {"absolute outer-wall width is used as-is", 0.6, false, 0.42, false, {0.4}, 1, 0.6}, + {"percent outer-wall width uses the nozzle", 120, true, 0.42, false, {0.5}, 1, 0.6}, + {"zero outer-wall width uses the line width", 0, false, 0.5, false, {0.4}, 1, 0.5}, + {"zero outer-wall width uses a percent line", 0, false, 100, true, {0.5}, 1, 0.5}, + {"zero width falls back to auto", 0, false, 0, false, {0.4}, 1, Flow::auto_extrusion_width(frExternalPerimeter, 0.4)}, + {"the auto fallback scales with the nozzle", 0, false, 0, false, {0.6}, 1, Flow::auto_extrusion_width(frExternalPerimeter, 0.6)}, + {"a percent width uses the outer wall's nozzle", 120, true, 0.42, false, {0.4, 0.8}, 2, 0.96}, + {"the auto width uses the outer wall's nozzle", 0, false, 0, false, {0.4, 0.8}, 2, Flow::auto_extrusion_width(frExternalPerimeter, 0.8)}, + {"an absolute width ignores the nozzle", 0.6, false, 0.42, false, {0.4, 0.8}, 2, 0.6}, + {"a zero percent width uses the line width", 0, true, 0.5, false, {0.4}, 1, 0.5}, + {"an unset filament id uses the first nozzle", 0, false, 0, false, {0.4, 0.8}, 0, Flow::auto_extrusion_width(frExternalPerimeter, 0.4)}, + {"an out-of-range filament id uses nozzle 1", 0, false, 0, false, {0.4, 0.8}, 5, Flow::auto_extrusion_width(frExternalPerimeter, 0.4)}, + })); + + DYNAMIC_SECTION(c.description) + { + PrintConfig print_config; + print_config.nozzle_diameter.values = c.nozzle_diameters; + + PrintObjectConfig object_config; + object_config.line_width = ConfigOptionFloatOrPercent(c.line_value, c.line_percent); + + PrintRegionConfig region_config; + region_config.outer_wall_line_width = ConfigOptionFloatOrPercent(c.outer_value, c.outer_percent); + region_config.outer_wall_filament_id.value = c.outer_wall_filament_id; + + REQUIRE_THAT(resolve_outer_wall_line_width(region_config, object_config, print_config), + Catch::Matchers::WithinAbs(c.expected, 1e-9)); + } +}