From 99b267166c0187fe2d0a6ba119a169b4af2cca10 Mon Sep 17 00:00:00 2001 From: Epsylon Date: Sun, 30 Aug 2026 10:14:22 +0300 Subject: [PATCH 1/3] Improved consistency between classic and arachne perimeter generators --- src/libslic3r/Arachne/PerimeterOrder.cpp | 19 ++++++++++++++----- src/libslic3r/Arachne/PerimeterOrder.hpp | 5 ++++- src/libslic3r/PerimeterGenerator.cpp | 3 ++- 3 files changed, 20 insertions(+), 7 deletions(-) diff --git a/src/libslic3r/Arachne/PerimeterOrder.cpp b/src/libslic3r/Arachne/PerimeterOrder.cpp index a117e396234..ab3560fd777 100644 --- a/src/libslic3r/Arachne/PerimeterOrder.cpp +++ b/src/libslic3r/Arachne/PerimeterOrder.cpp @@ -215,7 +215,10 @@ static std::vector order_of_grouped_perimeter_extrusions_to_minimize_dis return grouped_extrusions_order; } -static PerimeterExtrusions extract_ordered_perimeter_extrusions(const PerimeterExtrusions &sorted_perimeter_extrusions, const bool external_perimeters_first) { +static PerimeterExtrusions extract_ordered_perimeter_extrusions( + const PerimeterExtrusions &sorted_perimeter_extrusions, + const PerimeterGenerator::Parameters ¶ms +) { // Extrusions are ordered inside each group. std::vector grouped_extrusions; @@ -260,8 +263,12 @@ static PerimeterExtrusions extract_ordered_perimeter_extrusions(const PerimeterE } } - if (!external_perimeters_first) - std::reverse(grouped_extrusions.back().extrusions.begin(), grouped_extrusions.back().extrusions.end()); + if (!params.config.external_perimeters_first && + !(params.layer_id == 0 && params.object_config.brim_width.value > 0)) + std::reverse( + grouped_extrusions.back().extrusions.begin(), + grouped_extrusions.back().extrusions.end() + ); } const std::vector grouped_extrusion_order = order_of_grouped_perimeter_extrusions_to_minimize_distances(grouped_extrusions, Point::Zero()); @@ -277,11 +284,13 @@ static PerimeterExtrusions extract_ordered_perimeter_extrusions(const PerimeterE // FIXME: From the point of better patch planning, it should be better to do ordering when we have generated all extrusions (for now, when G-Code is exported). // FIXME: It would be better to extract the adjacency graph of extrusions from the SkeletalTrapezoidation graph. -PerimeterExtrusions ordered_perimeter_extrusions(const Perimeters &perimeters, const bool external_perimeters_first) { +PerimeterExtrusions ordered_perimeter_extrusions( + const Perimeters &perimeters, const PerimeterGenerator::Parameters ¶ms +) { PerimeterExtrusions sorted_perimeter_extrusions = get_sorted_perimeter_extrusions_by_area(perimeters); construct_perimeter_extrusions_adjacency_graph(sorted_perimeter_extrusions); assign_nearest_external_perimeter(sorted_perimeter_extrusions); - return extract_ordered_perimeter_extrusions(sorted_perimeter_extrusions, external_perimeters_first); + return extract_ordered_perimeter_extrusions(sorted_perimeter_extrusions, params); } } // namespace Slic3r::Arachne::PerimeterOrder diff --git a/src/libslic3r/Arachne/PerimeterOrder.hpp b/src/libslic3r/Arachne/PerimeterOrder.hpp index f8469d917f1..38320b17cee 100644 --- a/src/libslic3r/Arachne/PerimeterOrder.hpp +++ b/src/libslic3r/Arachne/PerimeterOrder.hpp @@ -6,6 +6,7 @@ #include #include +#include "libslic3r/PerimeterGenerator.hpp" #include "libslic3r/Arachne/utils/ExtrusionLine.hpp" #include "libslic3r/BoundingBox.hpp" #include "libslic3r/Polygon.hpp" @@ -44,7 +45,9 @@ struct PerimeterExtrusion using PerimeterExtrusions = std::vector; -PerimeterExtrusions ordered_perimeter_extrusions(const Perimeters &perimeters, bool external_perimeters_first); +PerimeterExtrusions ordered_perimeter_extrusions( + const Perimeters &perimeters, const PerimeterGenerator::Parameters ¶ms +); } // namespace Slic3r::Arachne::PerimeterOrder diff --git a/src/libslic3r/PerimeterGenerator.cpp b/src/libslic3r/PerimeterGenerator.cpp index 0adc99edfee..2d801a859bf 100644 --- a/src/libslic3r/PerimeterGenerator.cpp +++ b/src/libslic3r/PerimeterGenerator.cpp @@ -1121,7 +1121,8 @@ void PerimeterGenerator::process_arachne( return true; }()); - Arachne::PerimeterOrder::PerimeterExtrusions ordered_extrusions = Arachne::PerimeterOrder::ordered_perimeter_extrusions(perimeters, params.config.external_perimeters_first); + Arachne::PerimeterOrder::PerimeterExtrusions ordered_extrusions = + Arachne::PerimeterOrder::ordered_perimeter_extrusions(perimeters, params); if (ExtrusionEntityCollection extrusion_coll = traverse_extrusions(params, lower_slices_polygons_cache, ordered_extrusions); !extrusion_coll.empty()) out_loops.append(extrusion_coll); From 19cbe9f6c8ad74a08310164058580de6dbf8929e Mon Sep 17 00:00:00 2001 From: Epsylon Date: Sun, 30 Aug 2026 15:40:47 +0300 Subject: [PATCH 2/3] Replaced external_perimeters_first bool with perimeters_order enum; Added InnerOuterInner perimeters order --- src/libslic3r/Arachne/PerimeterOrder.cpp | 20 +++++++++++++-- src/libslic3r/GCode/SeamPlacer.cpp | 2 +- src/libslic3r/Layer.cpp | 2 +- src/libslic3r/PerimeterGenerator.cpp | 10 ++++++-- src/libslic3r/Preset.cpp | 2 +- src/libslic3r/PrintConfig.cpp | 31 ++++++++++++++++++++---- src/libslic3r/PrintConfig.hpp | 9 ++++++- src/libslic3r/PrintObject.cpp | 2 +- src/slic3r/GUI/ConfigManipulation.cpp | 2 +- src/slic3r/GUI/Tab.cpp | 2 +- 10 files changed, 66 insertions(+), 16 deletions(-) diff --git a/src/libslic3r/Arachne/PerimeterOrder.cpp b/src/libslic3r/Arachne/PerimeterOrder.cpp index ab3560fd777..9a44df97176 100644 --- a/src/libslic3r/Arachne/PerimeterOrder.cpp +++ b/src/libslic3r/Arachne/PerimeterOrder.cpp @@ -263,12 +263,28 @@ static PerimeterExtrusions extract_ordered_perimeter_extrusions( } } - if (!params.config.external_perimeters_first && - !(params.layer_id == 0 && params.object_config.brim_width.value > 0)) + // Make the order inner->outer by default. + // This causes a potential double reverse, but this way the conditions are consistent with + // the classic perimeter generator + std::reverse( + grouped_extrusions.back().extrusions.begin(), grouped_extrusions.back().extrusions.end() + ); + // if brim will be printed, reverse the order of perimeters so that + // we continue inwards after having finished the brim + const bool first_layer_with_brim = params.layer_id == 0 && + params.object_config.brim_width.value > 0 && + params.object_config.brim_separation == 0 && params.object_config.brim_type != btNoBrim; + if (params.config.perimeters_order == PerimetersOrder::OuterInner || first_layer_with_brim) std::reverse( grouped_extrusions.back().extrusions.begin(), grouped_extrusions.back().extrusions.end() ); + if (params.config.perimeters_order == PerimetersOrder::InnerOuterInner && + !first_layer_with_brim && grouped_extrusions.back().extrusions.size() > 1) + std::swap( + *std::prev(grouped_extrusions.back().extrusions.end(), 1), + *std::prev(grouped_extrusions.back().extrusions.end(), 2) + ); } const std::vector grouped_extrusion_order = order_of_grouped_perimeter_extrusions_to_minimize_distances(grouped_extrusions, Point::Zero()); diff --git a/src/libslic3r/GCode/SeamPlacer.cpp b/src/libslic3r/GCode/SeamPlacer.cpp index b3a16c8500c..bdd1a7c417b 100644 --- a/src/libslic3r/GCode/SeamPlacer.cpp +++ b/src/libslic3r/GCode/SeamPlacer.cpp @@ -397,7 +397,7 @@ boost::variant finalize_seam_position( *outter_scarf_start_point }; - if (region->config().external_perimeters_first.value) { + if (region->config().perimeters_order.value != PerimetersOrder::InnerOuter) { const auto external_first_offset_direction{ offset_direction == Geometry::Direction1D::forward ? Geometry::Direction1D::backward : diff --git a/src/libslic3r/Layer.cpp b/src/libslic3r/Layer.cpp index 1cf9e1882d1..8b3671fce7b 100644 --- a/src/libslic3r/Layer.cpp +++ b/src/libslic3r/Layer.cpp @@ -658,7 +658,7 @@ inline bool has_compatible_layer_regions(const PrintRegionConfig &config, const config.overhangs == other_config.overhangs && config.opt_serialize("perimeter_extrusion_width") == other_config.opt_serialize("perimeter_extrusion_width") && config.thin_walls == other_config.thin_walls && - config.external_perimeters_first == other_config.external_perimeters_first && + config.perimeters_order == other_config.perimeters_order && config.infill_overlap == other_config.infill_overlap && has_compatible_dynamic_overhang_speed(config, other_config); } diff --git a/src/libslic3r/PerimeterGenerator.cpp b/src/libslic3r/PerimeterGenerator.cpp index 2d801a859bf..f21d0ad9009 100644 --- a/src/libslic3r/PerimeterGenerator.cpp +++ b/src/libslic3r/PerimeterGenerator.cpp @@ -1453,9 +1453,15 @@ void PerimeterGenerator::process_classic( // if brim will be printed, reverse the order of perimeters so that // we continue inwards after having finished the brim // TODO: add test for perimeter order - if (params.config.external_perimeters_first || - (params.layer_id == 0 && params.object_config.brim_width.value > 0)) + const bool first_layer_with_brim = params.layer_id == 0 && + params.object_config.brim_width.value > 0 && + params.object_config.brim_separation == 0 && params.object_config.brim_type != btNoBrim; + if (params.config.perimeters_order == PerimetersOrder::OuterInner || first_layer_with_brim) entities.reverse(); + if (params.config.perimeters_order == PerimetersOrder::InnerOuterInner && + !first_layer_with_brim && entities.size() > 1) + std::swap(*std::prev(entities.end(), 1), *std::prev(entities.end(), 2)); + // append perimeters for this slice as a collection if (! entities.empty()) out_loops.append(entities); diff --git a/src/libslic3r/Preset.cpp b/src/libslic3r/Preset.cpp index 555d9ce40a9..8e8e5399f29 100644 --- a/src/libslic3r/Preset.cpp +++ b/src/libslic3r/Preset.cpp @@ -474,7 +474,7 @@ static std::vector s_Preset_print_options { "ensure_vertical_shell_thickness", "extra_perimeters", "extra_perimeters_on_overhangs", "avoid_crossing_curled_overhangs", "avoid_crossing_perimeters", "thin_walls", "overhangs", "seam_position", "staggered_inner_seams", "seam_gap_distance", - "external_perimeters_first", "fill_density", "fill_pattern", "top_fill_pattern", "bottom_fill_pattern", + "perimeters_order", "fill_density", "fill_pattern", "top_fill_pattern", "bottom_fill_pattern", "scarf_seam_placement", "scarf_seam_only_on_smooth", "scarf_seam_start_height", "scarf_seam_entire_loop", "scarf_seam_length", "scarf_seam_max_segment_length", "scarf_seam_on_inner_perimeters", "infill_every_layers", /*"infill_only_where_needed",*/ "solid_infill_every_layers", "fill_angle", "bridge_angle", "solid_infill_below_area", "only_retract_when_crossing_perimeters", "infill_first", diff --git a/src/libslic3r/PrintConfig.cpp b/src/libslic3r/PrintConfig.cpp index 5462830a23e..6c86d1c900f 100644 --- a/src/libslic3r/PrintConfig.cpp +++ b/src/libslic3r/PrintConfig.cpp @@ -267,6 +267,13 @@ static const t_config_enum_values s_keys_map_ForwardCompatibilitySubstitutionRul }; CONFIG_OPTION_ENUM_DEFINE_STATIC_MAPS(ForwardCompatibilitySubstitutionRule) +static t_config_enum_values s_keys_map_PerimetersOrder { + {"inner_outer", int(PerimetersOrder::InnerOuter)}, + {"outer_inner", int(PerimetersOrder::OuterInner)}, + {"inner_outer_inner", int(PerimetersOrder::InnerOuterInner)} +}; +CONFIG_OPTION_ENUM_DEFINE_STATIC_MAPS(PerimetersOrder) + static t_config_enum_values s_keys_map_PerimeterGeneratorType { { "classic", int(PerimeterGeneratorType::Classic) }, { "arachne", int(PerimeterGeneratorType::Arachne) } @@ -1157,13 +1164,20 @@ void PrintConfigDef::init_fff_params() def->mode = comAdvanced; def->set_default_value(new ConfigOptionFloatOrPercent(50, true)); - def = this->add("external_perimeters_first", coBool); - def->label = L("External perimeters first"); + def = this->add("perimeters_order", coEnum); + def->label = L("Perimeters order"); def->category = L("Layers and Perimeters"); - def->tooltip = L("Print contour perimeters from the outermost one to the innermost one " - "instead of the default inverse order."); + def->tooltip = L( + "Determines printing order of the contour perimeters. If the number of perimeters is less " + "than three, the Inner>Outer>Inner option behaves like Outer>Inner" + ); + def->set_enum( + {{"inner_outer", L("Inner>Outer")}, + {"outer_inner", L("Outer>Inner")}, + {"inner_outer_inner", L("Inner>Outer>Inner")}} + ); def->mode = comExpert; - def->set_default_value(new ConfigOptionBool(false)); + def->set_default_value(new ConfigOptionEnum(PerimetersOrder::InnerOuter)); def = this->add("extra_perimeters", coBool); def->label = L("Extra perimeters if needed"); @@ -5363,6 +5377,13 @@ void PrintConfigDef::handle_legacy(t_config_option_key &opt_key, std::string &va // Values other than 0/1 are replaced with "partial" for handling values from different slicers. value = "partial"; } + } else if (opt_key == "external_perimeters_first") { + opt_key = "perimeters_order"; + if (value == "0") { + value = "inner_outer"; + } else { + value = "outer_inner"; + } } // In PrusaSlicer 2.3.0-alpha0 the "monotonous" infill was introduced, which was later renamed to "monotonic". diff --git a/src/libslic3r/PrintConfig.hpp b/src/libslic3r/PrintConfig.hpp index 8cb41093150..1bfbff3411e 100644 --- a/src/libslic3r/PrintConfig.hpp +++ b/src/libslic3r/PrintConfig.hpp @@ -180,6 +180,13 @@ enum class LabelObjectsStyle { Disabled, Octoprint, Firmware }; +enum class PerimetersOrder +{ + InnerOuter, + OuterInner, + InnerOuterInner +}; + enum class PerimeterGeneratorType { // Classic perimeter generator using Clipper offsets with constant extrusion width. @@ -754,7 +761,7 @@ PRINT_CONFIG_CLASS_DEFINE( ((ConfigOptionFloatOrPercent, overhang_speed_1)) ((ConfigOptionFloatOrPercent, overhang_speed_2)) ((ConfigOptionFloatOrPercent, overhang_speed_3)) - ((ConfigOptionBool, external_perimeters_first)) + ((ConfigOptionEnum, perimeters_order)) ((ConfigOptionBool, extra_perimeters)) ((ConfigOptionBool, extra_perimeters_on_overhangs)) ((ConfigOptionFloat, fill_angle)) diff --git a/src/libslic3r/PrintObject.cpp b/src/libslic3r/PrintObject.cpp index 9aa02e6acb1..ae42c389f57 100644 --- a/src/libslic3r/PrintObject.cpp +++ b/src/libslic3r/PrintObject.cpp @@ -730,7 +730,7 @@ bool PrintObject::invalidate_state_by_config_options( || opt_key == "first_layer_extrusion_width" || opt_key == "perimeter_extrusion_width" || opt_key == "infill_overlap" - || opt_key == "external_perimeters_first" + || opt_key == "perimeters_order" || opt_key == "arc_fitting" || opt_key == "top_one_perimeter_type" || opt_key == "only_one_perimeter_first_layer") { diff --git a/src/slic3r/GUI/ConfigManipulation.cpp b/src/slic3r/GUI/ConfigManipulation.cpp index b838a267d83..1c46356c9de 100644 --- a/src/slic3r/GUI/ConfigManipulation.cpp +++ b/src/slic3r/GUI/ConfigManipulation.cpp @@ -284,7 +284,7 @@ void ConfigManipulation::toggle_print_fff_options(DynamicPrintConfig* config) { bool have_perimeters = config->opt_int("perimeters") > 0; for (auto el : { "extra_perimeters","extra_perimeters_on_overhangs", "thin_walls", "overhangs", - "seam_position","staggered_inner_seams", "external_perimeters_first", "external_perimeter_extrusion_width", + "seam_position","staggered_inner_seams", "perimeters_order", "external_perimeter_extrusion_width", "perimeter_speed", "small_perimeter_speed", "external_perimeter_speed", "enable_dynamic_overhang_speeds"}) toggle_field(el, have_perimeters); diff --git a/src/slic3r/GUI/Tab.cpp b/src/slic3r/GUI/Tab.cpp index 118bb2fc376..272a8262f21 100644 --- a/src/slic3r/GUI/Tab.cpp +++ b/src/slic3r/GUI/Tab.cpp @@ -1488,7 +1488,7 @@ void TabPrint::build() optgroup->append_single_option_line("scarf_seam_max_segment_length", scarf_seam_path + "max-scarf-joint-segment-length"); optgroup->append_single_option_line("scarf_seam_on_inner_perimeters", scarf_seam_path + "scarf-joint-on-inner-perimeters"); - optgroup->append_single_option_line("external_perimeters_first", category_path + "external-perimeters-first"); + optgroup->append_single_option_line("perimeters_order", category_path + "perimeters-order"); optgroup->append_single_option_line("gap_fill_enabled", category_path + "fill-gaps"); optgroup->append_single_option_line("perimeter_generator"); From 12de3e470ec032000e66b33415ccd4de331dc758 Mon Sep 17 00:00:00 2001 From: Epsylon Date: Sun, 30 Aug 2026 17:22:24 +0300 Subject: [PATCH 3/3] Invalidate perimeters when brim changes --- src/libslic3r/PrintObject.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/libslic3r/PrintObject.cpp b/src/libslic3r/PrintObject.cpp index ae42c389f57..ac56f689eb9 100644 --- a/src/libslic3r/PrintObject.cpp +++ b/src/libslic3r/PrintObject.cpp @@ -720,6 +720,8 @@ bool PrintObject::invalidate_state_by_config_options( if ( opt_key == "brim_width" || opt_key == "brim_separation" || opt_key == "brim_type") { + // Brim can affect the first layer perimeters order so they have to be invalidated + steps.emplace_back(posPerimeters); steps.emplace_back(posSupportSpotsSearch); // Brim is printed below supports, support invalidates brim and skirt. steps.emplace_back(posSupportMaterial);