From a3190d94d0e08e492cffff62977e117f97bc64b6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luk=C3=A1=C5=A1=20Hejl?= Date: Mon, 26 Feb 2024 16:01:21 +0100 Subject: [PATCH] SPE-1804: Single perimeter for top and bottom layers working with both perimeter generators. Co-authored-by: supermerill Co-authored-by: Morton Jonuschat Co-authored-by: Vovodroid Co-authored-by: qing.zhang Co-authored-by: lane.wei --- src/libslic3r/ClipperUtils.cpp | 12 ++ src/libslic3r/ClipperUtils.hpp | 1 + src/libslic3r/LayerRegion.cpp | 3 + src/libslic3r/PerimeterGenerator.cpp | 170 +++++++++++++++++++++++++-- src/libslic3r/PerimeterGenerator.hpp | 2 + src/libslic3r/Preset.cpp | 3 +- src/libslic3r/PrintConfig.cpp | 26 ++++ src/libslic3r/PrintConfig.hpp | 13 +- src/libslic3r/PrintObject.cpp | 4 +- src/slic3r/GUI/Tab.cpp | 4 + tests/fff_print/test_perimeters.cpp | 1 + 11 files changed, 224 insertions(+), 15 deletions(-) diff --git a/src/libslic3r/ClipperUtils.cpp b/src/libslic3r/ClipperUtils.cpp index 1c2e1b9600b..d8d3b1d5afc 100644 --- a/src/libslic3r/ClipperUtils.cpp +++ b/src/libslic3r/ClipperUtils.cpp @@ -183,6 +183,18 @@ namespace ClipperUtils { out.end()); return out; } + [[nodiscard]] Polygons clip_clipper_polygons_with_subject_bbox(const ExPolygons &src, const BoundingBox &bbox) + { + Polygons out; + out.reserve(number_polygons(src)); + for (const ExPolygon &p : src) { + Polygons temp = clip_clipper_polygons_with_subject_bbox(p, bbox); + out.insert(out.end(), temp.begin(), temp.end()); + } + + out.erase(std::remove_if(out.begin(), out.end(), [](const Polygon &polygon) {return polygon.empty(); }), out.end()); + return out; + } } static ExPolygons PolyTreeToExPolygons(ClipperLib::PolyTree &&polytree) diff --git a/src/libslic3r/ClipperUtils.hpp b/src/libslic3r/ClipperUtils.hpp index 454ceb74c7f..f399fb6e3b5 100644 --- a/src/libslic3r/ClipperUtils.hpp +++ b/src/libslic3r/ClipperUtils.hpp @@ -335,6 +335,7 @@ namespace ClipperUtils { [[nodiscard]] Polygon clip_clipper_polygon_with_subject_bbox(const Polygon &src, const BoundingBox &bbox); [[nodiscard]] Polygons clip_clipper_polygons_with_subject_bbox(const Polygons &src, const BoundingBox &bbox); [[nodiscard]] Polygons clip_clipper_polygons_with_subject_bbox(const ExPolygon &src, const BoundingBox &bbox); + [[nodiscard]] Polygons clip_clipper_polygons_with_subject_bbox(const ExPolygons &src, const BoundingBox &bbox); } // offset Polygons diff --git a/src/libslic3r/LayerRegion.cpp b/src/libslic3r/LayerRegion.cpp index fc547bba638..b5ae16b8faa 100644 --- a/src/libslic3r/LayerRegion.cpp +++ b/src/libslic3r/LayerRegion.cpp @@ -111,6 +111,7 @@ void LayerRegion::make_perimeters( // Cummulative sum of polygons over all the regions. const ExPolygons *lower_slices = this->layer()->lower_layer ? &this->layer()->lower_layer->lslices : nullptr; + const ExPolygons *upper_slices = this->layer()->upper_layer ? &this->layer()->upper_layer->lslices : nullptr; // Cache for offsetted lower_slices Polygons lower_layer_polygons_cache; @@ -124,6 +125,7 @@ void LayerRegion::make_perimeters( params, surface, lower_slices, + upper_slices, lower_layer_polygons_cache, // output: m_perimeters, @@ -135,6 +137,7 @@ void LayerRegion::make_perimeters( params, surface, lower_slices, + upper_slices, lower_layer_polygons_cache, // output: m_perimeters, diff --git a/src/libslic3r/PerimeterGenerator.cpp b/src/libslic3r/PerimeterGenerator.cpp index 9f9647b37f0..fff8be41b32 100644 --- a/src/libslic3r/PerimeterGenerator.cpp +++ b/src/libslic3r/PerimeterGenerator.cpp @@ -1083,6 +1083,7 @@ void PerimeterGenerator::process_arachne( const Parameters ¶ms, const Surface &surface, const ExPolygons *lower_slices, + const ExPolygons *upper_slices, // Cache: Polygons &lower_slices_polygons_cache, // Output: @@ -1094,7 +1095,8 @@ void PerimeterGenerator::process_arachne( ExPolygons &out_fill_expolygons) { // other perimeters - coord_t perimeter_spacing = params.perimeter_flow.scaled_spacing(); + coord_t perimeter_width = params.perimeter_flow.scaled_width(); + coord_t perimeter_spacing = params.perimeter_flow.scaled_spacing(); // external perimeters coord_t ext_perimeter_width = params.ext_perimeter_flow.scaled_width(); coord_t ext_perimeter_spacing = params.ext_perimeter_flow.scaled_spacing(); @@ -1114,12 +1116,82 @@ void PerimeterGenerator::process_arachne( // we need to process each island separately because we might have different // extra perimeters for each one // detect how many perimeters must be generated for this island - int loop_number = params.config.perimeters + surface.extra_perimeters - 1; // 0-indexed loops - ExPolygons last = offset_ex(surface.expolygon.simplify_p(params.scaled_resolution), - float(ext_perimeter_width / 2. - ext_perimeter_spacing / 2.)); - Polygons last_p = to_polygons(last); + int loop_number = params.config.perimeters + surface.extra_perimeters - 1; // 0-indexed loops + if (loop_number > 0 && ((params.config.top_one_perimeter_type == TopOnePerimeterType::TopmostOnly && upper_slices == nullptr) || (params.config.only_one_perimeter_first_layer && params.layer_id == 0))) + loop_number = 0; + + // Calculate how many inner loops remain when TopSurfaces is selected. + const int inner_loop_number = (params.config.top_one_perimeter_type == TopOnePerimeterType::TopSurfaces && upper_slices != nullptr) ? loop_number - 1 : -1; + + // Set one perimeter when TopSurfaces is selected. + if (params.config.top_one_perimeter_type == TopOnePerimeterType::TopSurfaces) + loop_number = 0; + + ExPolygons last = offset_ex(surface.expolygon.simplify_p(params.scaled_resolution), - float(ext_perimeter_width / 2. - ext_perimeter_spacing / 2.)); + Polygons last_p = to_polygons(last); + Arachne::WallToolPaths wall_tool_paths(last_p, ext_perimeter_spacing, perimeter_spacing, coord_t(loop_number + 1), 0, params.layer_height, params.object_config, params.print_config); + std::vector perimeters = wall_tool_paths.getToolPaths(); + ExPolygons infill_contour = union_ex(wall_tool_paths.getInnerContour()); + + // Check if there are some remaining perimeters to generate (the number of perimeters + // is greater than one together with enabled the single perimeter on top surface feature). + if (inner_loop_number >= 0) { + assert(upper_slices != nullptr); + + // Infill contour bounding box. + BoundingBox infill_contour_bbox = get_extents(infill_contour); + infill_contour_bbox.offset(SCALED_EPSILON); + + // Get top ExPolygons from current infill contour. + const Polygons upper_slices_clipped = ClipperUtils::clip_clipper_polygons_with_subject_bbox(*upper_slices, infill_contour_bbox); + ExPolygons top_expolygons = diff_ex(infill_contour, upper_slices_clipped); + + if (!top_expolygons.empty()) { + if (lower_slices != nullptr) { + const float bridge_offset = float(std::max(ext_perimeter_spacing, perimeter_width)); + const Polygons lower_slices_clipped = ClipperUtils::clip_clipper_polygons_with_subject_bbox(*lower_slices, infill_contour_bbox); + const ExPolygons current_slices_bridges = offset_ex(diff_ex(top_expolygons, lower_slices_clipped), bridge_offset); + + // Remove bridges from top surface polygons. + top_expolygons = diff_ex(top_expolygons, current_slices_bridges); + } + + // Filter out areas that are too thin and expand top surface polygons a bit to hide the wall line. + const float top_surface_min_width = std::max(float(ext_perimeter_spacing) / 4.f + scaled(0.00001), float(perimeter_width) / 4.f); + top_expolygons = offset2_ex(top_expolygons, -top_surface_min_width, top_surface_min_width + float(perimeter_width)); + + // Get the not-top ExPolygons (including bridges) from current slices and expanded real top ExPolygons (without bridges). + const ExPolygons not_top_expolygons = diff_ex(infill_contour, top_expolygons); + + // Get final top ExPolygons. + top_expolygons = intersection_ex(top_expolygons, infill_contour); + + const Polygons not_top_polygons = to_polygons(not_top_expolygons); + Arachne::WallToolPaths inner_wall_tool_paths(not_top_polygons, perimeter_spacing, perimeter_spacing, coord_t(inner_loop_number + 1), 0, params.layer_height, params.object_config, params.print_config); + std::vector inner_perimeters = inner_wall_tool_paths.getToolPaths(); + + // Recalculate indexes of inner perimeters before merging them. + if (!perimeters.empty()) { + for (Arachne::VariableWidthLines &inner_perimeter : inner_perimeters) { + if (inner_perimeter.empty()) + continue; + + for (Arachne::ExtrusionLine &el : inner_perimeter) + ++el.inset_idx; + } + } + + perimeters.insert(perimeters.end(), inner_perimeters.begin(), inner_perimeters.end()); + infill_contour = union_ex(top_expolygons, inner_wall_tool_paths.getInnerContour()); + } else { + // There is no top surface ExPolygon, so we call Arachne again with parameters + // like when the single perimeter feature is disabled. + Arachne::WallToolPaths no_single_perimeter_tool_paths(last_p, ext_perimeter_spacing, perimeter_spacing, coord_t(inner_loop_number + 2), 0, params.layer_height, params.object_config, params.print_config); + perimeters = no_single_perimeter_tool_paths.getToolPaths(); + infill_contour = union_ex(no_single_perimeter_tool_paths.getInnerContour()); + } + } - Arachne::WallToolPaths wallToolPaths(last_p, ext_perimeter_spacing, perimeter_spacing, coord_t(loop_number + 1), 0, params.layer_height, params.object_config, params.print_config); - std::vector perimeters = wallToolPaths.getToolPaths(); loop_number = int(perimeters.size()) - 1; #ifdef ARACHNE_DEBUG @@ -1272,8 +1344,7 @@ void PerimeterGenerator::process_arachne( if (ExtrusionEntityCollection extrusion_coll = traverse_extrusions(params, lower_slices_polygons_cache, ordered_extrusions); !extrusion_coll.empty()) out_loops.append(extrusion_coll); - ExPolygons infill_contour = union_ex(wallToolPaths.getInnerContour()); - const coord_t spacing = (perimeters.size() == 1) ? ext_perimeter_spacing2 : perimeter_spacing; + const coord_t spacing = (perimeters.size() == 1) ? ext_perimeter_spacing2 : perimeter_spacing; if (offset_ex(infill_contour, -float(spacing / 2.)).empty()) infill_contour.clear(); // Infill region is too small, so let's filter it out. @@ -1329,6 +1400,7 @@ void PerimeterGenerator::process_classic( const Parameters ¶ms, const Surface &surface, const ExPolygons *lower_slices, + const ExPolygons *upper_slices, // Cache: Polygons &lower_slices_polygons_cache, // Output: @@ -1376,8 +1448,15 @@ void PerimeterGenerator::process_classic( // extra perimeters for each one // detect how many perimeters must be generated for this island int loop_number = params.config.perimeters + surface.extra_perimeters - 1; // 0-indexed loops - ExPolygons last = union_ex(surface.expolygon.simplify_p(params.scaled_resolution)); + + // Set the topmost layer to be one perimeter. + if (loop_number > 0 && ((params.config.top_one_perimeter_type != TopOnePerimeterType::None && upper_slices == nullptr) || (params.config.only_one_perimeter_first_layer && params.layer_id == 0))) + loop_number = 0; + + ExPolygons last = union_ex(surface.expolygon.simplify_p(params.scaled_resolution)); ExPolygons gaps; + ExPolygons top_fills; + ExPolygons fill_clip; if (loop_number >= 0) { // In case no perimeters are to be generated, loop_number will equal to -1. std::vector contours(loop_number+1); // depth => loops @@ -1468,6 +1547,65 @@ void PerimeterGenerator::process_classic( } } last = std::move(offsets); + + // Store surface for top infill if top_one_perimeter_type is set to TopSurfaces. + if (i == 0 && i != loop_number && params.config.top_one_perimeter_type == TopOnePerimeterType::TopSurfaces && upper_slices != nullptr) { + // Split the polygons with top/not_top. + + // Get the offset from solid surface anchor. + const coordf_t total_perimeter_spacing = coordf_t(perimeter_spacing * (params.config.perimeters.value - 1)); + const coordf_t top_surface_offset_threshold = params.config.perimeters.value <= 1 ? 0. : 0.9 * total_perimeter_spacing; + coordf_t top_surface_offset = params.config.perimeters.value == 0 ? 0. : 1.5 * coordf_t(ext_perimeter_width + total_perimeter_spacing); + + // If possible, try to not push the extra perimeters inside the sparse infill. + if (top_surface_offset > top_surface_offset_threshold) { + top_surface_offset -= top_surface_offset_threshold; + } else + top_surface_offset = 0.; + + // Don't take into account too thin areas. + const float top_surface_min_width = std::max(float(ext_perimeter_spacing) / 2.f + scaled(0.00001), float(perimeter_width)); + + // Current slices bounding box. + BoundingBox current_perimeters_bbox = get_extents(last); + current_perimeters_bbox.offset(SCALED_EPSILON); + + ExPolygons current_slices_without_bridges; + if (lower_slices != nullptr) { + const float bridge_offset = 1.5f * float(std::max(ext_perimeter_spacing, perimeter_width)); + const Polygons lower_slices_clipped = ClipperUtils::clip_clipper_polygons_with_subject_bbox(*lower_slices, current_perimeters_bbox); + const ExPolygons current_slices_bridges = offset_ex(diff_ex(last, lower_slices_clipped, ApplySafetyOffset::Yes), bridge_offset); + current_slices_without_bridges = diff_ex(last, current_slices_bridges, ApplySafetyOffset::Yes); + } else { + current_slices_without_bridges = last; + } + + // Get top ExPolygons (including external perimeters) from current slices without bridges. + const Polygons upper_slices_clipped = expand(ClipperUtils::clip_clipper_polygons_with_subject_bbox(*upper_slices, current_perimeters_bbox), top_surface_min_width); + const ExPolygons top_polygons = diff_ex(current_slices_without_bridges, upper_slices_clipped, ApplySafetyOffset::Yes); + + if (!top_polygons.empty()) { + // Set the clip to a virtual second perimeter. + fill_clip = offset_ex(last, -float(ext_perimeter_spacing)); + + // Get the not-top ExPolygons (including bridges) from current slices and expanded real top ExPolygons (without bridges). + const ExPolygons not_top_polygons = diff_ex(last, offset_ex(top_polygons, float(top_surface_offset) + top_surface_min_width - (float(ext_perimeter_spacing) / 2.f)), ApplySafetyOffset::Yes); + + // Get difference between top ExPolygons without bridges and the area defined by the virtual second perimeter. + const ExPolygons top_gap = diff_ex(top_polygons, fill_clip); + + // Get top infill surface ExPolygons (without bridges) using the difference between the area defined by the virtual second perimeter and non-top ExPolygons. + top_fills = diff_ex(fill_clip, not_top_polygons, ApplySafetyOffset::Yes); + + // Set the clip to the external perimeter but go back inside by infill_extrusion_width/2 to ensure the extrusion won't go outside even with a 100% overlap. + fill_clip = offset_ex(last, float((coordf_t(ext_perimeter_spacing) / 2.) - params.config.infill_extrusion_width.get_abs_value(params.solid_infill_flow.nozzle_diameter()) / 2.)); + last = intersection_ex(not_top_polygons, last); + + if (has_gap_fill) + last = union_ex(last, top_gap); + } + } + if (i == loop_number && (! has_gap_fill || params.config.fill_density.value == 0)) { // The last run of this loop is executed to collect gaps for gap fill. // As the gap fill is either disabled or not @@ -1581,9 +1719,11 @@ void PerimeterGenerator::process_classic( ext_perimeter_spacing / 2 : // two or more loops? perimeter_spacing / 2; - // only apply infill overlap if we actually have one perimeter - if (inset > 0) - inset -= coord_t(scale_(params.config.get_abs_value("infill_overlap", unscale(inset + solid_infill_spacing / 2)))); + + // Only apply infill overlap if we actually have one perimeter. + const coord_t infill_perimeter_overlap = (inset > 0) ? coord_t(params.config.get_abs_value("infill_overlap", coordf_t(inset + solid_infill_spacing / 2.))) : 0; + inset -= infill_perimeter_overlap; + // simplify infill contours according to resolution Polygons pp; for (ExPolygon &ex : last) @@ -1597,6 +1737,12 @@ void PerimeterGenerator::process_classic( float(- inset - min_perimeter_infill_spacing / 2.), float(min_perimeter_infill_spacing / 2.)); + // Apply single perimeter feature. + if (!top_fills.empty()) { + const ExPolygons top_infill_areas = intersection_ex(fill_clip, offset_ex(top_fills, float(ext_perimeter_spacing) / 2.f)); + infill_areas = union_ex(infill_areas, offset_ex(top_infill_areas, float(infill_perimeter_overlap))); + } + if (lower_slices != nullptr && params.config.overhangs && params.config.extra_perimeters_on_overhangs && params.config.perimeters > 0 && params.layer_id > params.object_config.raft_layers) { // Generate extra perimeters on overhang areas, and cut them to these parts only, to save print time and material diff --git a/src/libslic3r/PerimeterGenerator.hpp b/src/libslic3r/PerimeterGenerator.hpp index 0555327b79b..3c41bc1688d 100644 --- a/src/libslic3r/PerimeterGenerator.hpp +++ b/src/libslic3r/PerimeterGenerator.hpp @@ -76,6 +76,7 @@ void process_classic( const Parameters ¶ms, const Surface &surface, const ExPolygons *lower_slices, + const ExPolygons *upper_slices, // Cache: Polygons &lower_slices_polygons_cache, // Output: @@ -91,6 +92,7 @@ void process_arachne( const Parameters ¶ms, const Surface &surface, const ExPolygons *lower_slices, + const ExPolygons *upper_slices, // Cache: Polygons &lower_slices_polygons_cache, // Output: diff --git a/src/libslic3r/Preset.cpp b/src/libslic3r/Preset.cpp index e08cc5890fd..a7361c01306 100644 --- a/src/libslic3r/Preset.cpp +++ b/src/libslic3r/Preset.cpp @@ -471,7 +471,8 @@ static std::vector s_Preset_print_options { "wipe_tower_width", "wipe_tower_cone_angle", "wipe_tower_rotation_angle", "wipe_tower_brim_width", "wipe_tower_bridging", "single_extruder_multi_material_priming", "mmu_segmented_region_max_width", "mmu_segmented_region_interlocking_depth", "wipe_tower_extruder", "wipe_tower_no_sparse_layers", "wipe_tower_extra_flow", "wipe_tower_extra_spacing", "compatible_printers", "compatible_printers_condition", "inherits", "perimeter_generator", "wall_transition_length", "wall_transition_filter_deviation", "wall_transition_angle", - "wall_distribution_count", "min_feature_size", "min_bead_width" + "wall_distribution_count", "min_feature_size", "min_bead_width", + "top_one_perimeter_type", "only_one_perimeter_first_layer", }; static std::vector s_Preset_filament_options { diff --git a/src/libslic3r/PrintConfig.cpp b/src/libslic3r/PrintConfig.cpp index 186069f148a..92092fc30f8 100644 --- a/src/libslic3r/PrintConfig.cpp +++ b/src/libslic3r/PrintConfig.cpp @@ -257,6 +257,13 @@ static t_config_enum_values s_keys_map_PerimeterGeneratorType { }; CONFIG_OPTION_ENUM_DEFINE_STATIC_MAPS(PerimeterGeneratorType) +static t_config_enum_values s_keys_map_TopOnePerimeterType { + { "none", int(TopOnePerimeterType::None) }, + { "top", int(TopOnePerimeterType::TopSurfaces) }, + { "topmost", int(TopOnePerimeterType::TopmostOnly) } +}; +CONFIG_OPTION_ENUM_DEFINE_STATIC_MAPS(TopOnePerimeterType) + static void assign_printer_technology_to_unknown(t_optiondef_map &options, PrinterTechnology printer_technology) { for (std::pair &kvp : options) @@ -569,6 +576,25 @@ void PrintConfigDef::init_fff_params() def->mode = comAdvanced; def->set_default_value(new ConfigOptionFloat(1)); + def = this->add("top_one_perimeter_type", coEnum); + def->label = L("Only one perimeter type"); + def->category = L("Layers and Perimeters"); + def->tooltip = L("Use only one perimeter on flat top surface, to give more space to the top infill pattern. Could be applied on topmost surface or all top surface."); + def->mode = comExpert; + def->set_enum({ + { "none", L("None surfaces") }, + { "top", L("All top surfaces") }, + { "topmost", L("Topmost surface only") } + }); + def->set_default_value(new ConfigOptionEnum(TopOnePerimeterType::None)); + + def = this->add("only_one_perimeter_first_layer", coBool); + def->label = L("Only one perimeter on first layer"); + def->category = L("Layers and Perimeters"); + def->tooltip = L("Use only one perimeter on the first layer of model."); + def->mode = comExpert; + def->set_default_value(new ConfigOptionBool(false)); + def = this->add("bridge_speed", coFloat); def->label = L("Bridges"); def->category = L("Speed"); diff --git a/src/libslic3r/PrintConfig.hpp b/src/libslic3r/PrintConfig.hpp index 1c54ce66746..067f8fefdaa 100644 --- a/src/libslic3r/PrintConfig.hpp +++ b/src/libslic3r/PrintConfig.hpp @@ -159,6 +159,14 @@ enum class PerimeterGeneratorType Arachne }; +enum class TopOnePerimeterType +{ + None, + TopSurfaces, + TopmostOnly, + Count +}; + enum class GCodeThumbnailsFormat { PNG, JPG, QOI }; @@ -190,7 +198,7 @@ CONFIG_OPTION_ENUM_DECLARE_STATIC_MAPS(LabelObjectsStyle) CONFIG_OPTION_ENUM_DECLARE_STATIC_MAPS(GCodeThumbnailsFormat) CONFIG_OPTION_ENUM_DECLARE_STATIC_MAPS(ForwardCompatibilitySubstitutionRule) CONFIG_OPTION_ENUM_DECLARE_STATIC_MAPS(PerimeterGeneratorType) - +CONFIG_OPTION_ENUM_DECLARE_STATIC_MAPS(TopOnePerimeterType) #undef CONFIG_OPTION_ENUM_DECLARE_STATIC_MAPS @@ -662,6 +670,9 @@ PRINT_CONFIG_CLASS_DEFINE( ((ConfigOptionFloat, top_solid_min_thickness)) ((ConfigOptionFloatOrPercent, top_solid_infill_speed)) ((ConfigOptionBool, wipe_into_infill)) + // Single perimeter. + ((ConfigOptionEnum, top_one_perimeter_type)) + ((ConfigOptionBool, only_one_perimeter_first_layer)) ) PRINT_CONFIG_CLASS_DEFINE( diff --git a/src/libslic3r/PrintObject.cpp b/src/libslic3r/PrintObject.cpp index dc5a86fc3bc..d5ad93de013 100644 --- a/src/libslic3r/PrintObject.cpp +++ b/src/libslic3r/PrintObject.cpp @@ -715,7 +715,9 @@ bool PrintObject::invalidate_state_by_config_options( || opt_key == "perimeter_extrusion_width" || opt_key == "infill_overlap" || opt_key == "external_perimeters_first" - || opt_key == "arc_fitting") { + || opt_key == "arc_fitting" + || opt_key == "top_one_perimeter_type" + || opt_key == "only_one_perimeter_first_layer") { steps.emplace_back(posPerimeters); } else if ( opt_key == "gap_fill_enabled" diff --git a/src/slic3r/GUI/Tab.cpp b/src/slic3r/GUI/Tab.cpp index 643cd52f2b8..42a1329c584 100644 --- a/src/slic3r/GUI/Tab.cpp +++ b/src/slic3r/GUI/Tab.cpp @@ -1467,6 +1467,10 @@ void TabPrint::build() optgroup->append_single_option_line("fuzzy_skin_thickness", category_path + "fuzzy-skin-thickness"); optgroup->append_single_option_line("fuzzy_skin_point_dist", category_path + "fuzzy-skin-point-distance"); + optgroup = page->new_optgroup(L("Only one perimeter")); + optgroup->append_single_option_line("top_one_perimeter_type", category_path + "top-one-perimeter-type"); + optgroup->append_single_option_line("only_one_perimeter_first_layer", category_path + "only-one-perimeter-first-layer"); + page = add_options_page(L("Infill"), "infill"); category_path = "infill_42#"; optgroup = page->new_optgroup(L("Infill")); diff --git a/tests/fff_print/test_perimeters.cpp b/tests/fff_print/test_perimeters.cpp index 4fa344d86cc..c154f5a0c20 100644 --- a/tests/fff_print/test_perimeters.cpp +++ b/tests/fff_print/test_perimeters.cpp @@ -65,6 +65,7 @@ SCENARIO("Perimeter nesting", "[Perimeters]") perimeter_generator_params, surface, nullptr, + nullptr, // cache: lower_layer_polygons_cache, // output: