diff --git a/src/libslic3r/Arachne/WallToolPaths.cpp b/src/libslic3r/Arachne/WallToolPaths.cpp index a147e3f36d..bb01050c10 100644 --- a/src/libslic3r/Arachne/WallToolPaths.cpp +++ b/src/libslic3r/Arachne/WallToolPaths.cpp @@ -31,6 +31,11 @@ WallToolPathsParams make_paths_params(const int layer_id, const PrintObjectConfi if (const auto &min_feature_size_opt = print_object_config.min_feature_size) input_params.min_feature_size = min_feature_size_opt.value * 0.01 * min_nozzle_diameter; + if (const auto &min_wall_length_factor_opt = print_object_config.min_length_factor) + input_params.min_length_factor = min_wall_length_factor_opt.value; + else + input_params.min_length_factor = 0.5f; + if (layer_id == 0) { if (const auto &initial_layer_min_bead_width_opt = print_object_config.initial_layer_min_bead_width) input_params.min_bead_width = initial_layer_min_bead_width_opt.value * 0.01 * min_nozzle_diameter; @@ -47,6 +52,8 @@ WallToolPathsParams make_paths_params(const int layer_id, const PrintObjectConfi input_params.wall_transition_angle = print_object_config.wall_transition_angle.value; input_params.wall_distribution_count = print_object_config.wall_distribution_count.value; + + input_params.is_top_or_bottom_layer = false; // Set to default value } return input_params; @@ -671,7 +678,8 @@ void WallToolPaths::removeSmallLines(std::vector &toolpaths) coord_t min_width = std::numeric_limits::max(); for (const ExtrusionJunction &j : line) min_width = std::min(min_width, j.w); - if (line.is_odd && !line.is_closed && shorterThan(line, min_width / 2)) { // remove line + // Only use min_length_factor for non-topmost, to prevent top gaps. Otherwise use default value. + if (line.is_odd && !line.is_closed && shorterThan(line, m_params.is_top_or_bottom_layer ? (min_width / 2) : (min_width * m_params.min_length_factor))) { // remove line line = std::move(inset.back()); inset.erase(--inset.end()); line_idx--; // reconsider the current position diff --git a/src/libslic3r/Arachne/WallToolPaths.hpp b/src/libslic3r/Arachne/WallToolPaths.hpp index 02e780ff35..09e2ae5508 100644 --- a/src/libslic3r/Arachne/WallToolPaths.hpp +++ b/src/libslic3r/Arachne/WallToolPaths.hpp @@ -25,10 +25,12 @@ class WallToolPathsParams public: float min_bead_width; float min_feature_size; + float min_length_factor; float wall_transition_length; float wall_transition_angle; float wall_transition_filter_deviation; int wall_distribution_count; + bool is_top_or_bottom_layer; }; WallToolPathsParams make_paths_params(const int layer_id, const PrintObjectConfig &print_object_config, const PrintConfig &print_config); @@ -109,7 +111,7 @@ protected: /*! * Remove polylines shorter than half the smallest line width along that polyline. */ - static void removeSmallLines(std::vector &toolpaths); + void removeSmallLines(std::vector &toolpaths); /*! * Simplifies the variable-width toolpaths by calling the simplify on every line in the toolpath using the provided diff --git a/src/libslic3r/PerimeterGenerator.cpp b/src/libslic3r/PerimeterGenerator.cpp index b9ab63cf86..02586fbcb2 100644 --- a/src/libslic3r/PerimeterGenerator.cpp +++ b/src/libslic3r/PerimeterGenerator.cpp @@ -1941,10 +1941,15 @@ void PerimeterGenerator::process_arachne() int loop_number = this->config->wall_loops + surface.extra_perimeters - 1; // 0-indexed loops if (this->config->alternate_extra_wall && this->layer_id % 2 == 1 && !m_spiral_vase) // add alternating extra wall loop_number++; - if (this->layer_id == 0 && this->config->only_one_wall_first_layer) + + // Set the bottommost layer to be one wall + const bool is_bottom_layer = (this->layer_id == 0) ? true : false; + if (is_bottom_layer && this->config->only_one_wall_first_layer) loop_number = 0; + // Orca: set the topmost layer to be one wall according to the config - if (loop_number > 0 && config->only_one_wall_top && this->upper_slices == nullptr) + const bool is_topmost_layer = (this->upper_slices == nullptr) ? true : false; + if (is_topmost_layer && loop_number > 0 && config->only_one_wall_top) loop_number = 0; // Orca: properly adjust offset for the outer wall if precise_outer_wall is enabled. ExPolygons last = offset_ex(surface.expolygon.simplify_p(surface_simplify_resolution), @@ -1952,6 +1957,9 @@ void PerimeterGenerator::process_arachne() : -float(ext_perimeter_width / 2. - ext_perimeter_spacing / 2.)); Arachne::WallToolPathsParams input_params = Arachne::make_paths_params(this->layer_id, *object_config, *print_config); + // Set params is_top_or_bottom_layer for adjusting short-wall removal sensitivity. + input_params.is_top_or_bottom_layer = (is_bottom_layer || is_topmost_layer) ? true : false; + coord_t wall_0_inset = 0; if (config->precise_outer_wall) wall_0_inset = -coord_t(ext_perimeter_width / 2 - ext_perimeter_spacing / 2); @@ -1959,29 +1967,45 @@ void PerimeterGenerator::process_arachne() std::vector out_shell; ExPolygons top_fills; ExPolygons fill_clip; - if (loop_number > 0 && config->only_one_wall_top && !surface.is_bridge() && this->upper_slices != nullptr) { - // Check if current layer has surfaces that are not covered by upper layer (i.e., top surfaces) - ExPolygons non_top_polygons; - this->split_top_surfaces(last, top_fills, non_top_polygons, fill_clip); - if (top_fills.empty()) { + // Check if we're on a top surface, and make adjustments where needed + if (!surface.is_bridge() && !is_topmost_layer) { + ExPolygons non_top_polygons; + // Temporary storage, in the event all we need to do is set is_top_or_bottom_layer + ExPolygons top_fills_tmp; + ExPolygons fill_clip_tmp; + // Check if current layer has surfaces that are not covered by upper layer (i.e., top surfaces) + this->split_top_surfaces(last, top_fills_tmp, non_top_polygons, fill_clip_tmp); + + if (top_fills_tmp.empty()) { // No top surfaces, no special handling needed } else { - // First we slice the outer shell - Polygons last_p = to_polygons(last); - Arachne::WallToolPaths wallToolPaths(last_p, bead_width_0, perimeter_spacing, coord_t(1), - wall_0_inset, layer_height, input_params); - out_shell = wallToolPaths.getToolPaths(); - // Make sure infill not overlap with wall - top_fills = intersection_ex(top_fills, wallToolPaths.getInnerContour()); + // Use single-wall on top-surfaces if configured + if (loop_number > 0 && config->only_one_wall_top) { + // Adjust arachne input params to prevent removal of larger short walls, which could lead to gaps + Arachne::WallToolPathsParams input_params_tmp = input_params; + input_params_tmp.is_top_or_bottom_layer = true; + + // Swap in the temporary storage + top_fills.swap(top_fills_tmp); + fill_clip.swap(fill_clip_tmp); + + // First we slice the outer shell + Polygons last_p = to_polygons(last); + Arachne::WallToolPaths wallToolPaths(last_p, bead_width_0, perimeter_spacing, coord_t(1), + wall_0_inset, layer_height, input_params_tmp); + out_shell = wallToolPaths.getToolPaths(); + // Make sure infill not overlap with wall + top_fills = intersection_ex(top_fills, wallToolPaths.getInnerContour()); - if (!top_fills.empty()) { - // Then get the inner part that needs more walls - last = intersection_ex(non_top_polygons, wallToolPaths.getInnerContour()); - loop_number--; - } else { - // Give up the outer shell because we don't have any meaningful top surface - out_shell.clear(); + if (!top_fills.empty()) { + // Then get the inner part that needs more walls + last = intersection_ex(non_top_polygons, wallToolPaths.getInnerContour()); + loop_number--; + } else { + // Give up the outer shell because we don't have any meaningful top surface + out_shell.clear(); + } } } } diff --git a/src/libslic3r/Preset.cpp b/src/libslic3r/Preset.cpp index dba5cf05e5..519b7ee090 100644 --- a/src/libslic3r/Preset.cpp +++ b/src/libslic3r/Preset.cpp @@ -804,7 +804,7 @@ static std::vector s_Preset_print_options { "initial_layer_infill_speed", "only_one_wall_top", "timelapse_type", "wall_generator", "wall_transition_length", "wall_transition_filter_deviation", "wall_transition_angle", - "wall_distribution_count", "min_feature_size", "min_bead_width", "post_process", + "wall_distribution_count", "min_feature_size", "min_bead_width", "post_process", "min_length_factor", "small_perimeter_speed", "small_perimeter_threshold","bridge_angle", "filter_out_gap_fill", "travel_acceleration","inner_wall_acceleration", "min_width_top_surface", "default_jerk", "outer_wall_jerk", "inner_wall_jerk", "infill_jerk", "top_surface_jerk", "initial_layer_jerk","travel_jerk", "top_solid_infill_flow_ratio","bottom_solid_infill_flow_ratio","only_one_wall_first_layer", "print_flow_ratio", "seam_gap", diff --git a/src/libslic3r/PrintConfig.cpp b/src/libslic3r/PrintConfig.cpp index 1073c0d20b..a8ed821aea 100644 --- a/src/libslic3r/PrintConfig.cpp +++ b/src/libslic3r/PrintConfig.cpp @@ -4540,6 +4540,20 @@ def = this->add("filament_loading_speed", coFloats); def->min = 0; def->set_default_value(new ConfigOptionPercent(25)); + def = this->add("min_length_factor", coFloat); + def->label = L("Minimum wall length"); + def->category = L("Quality"); + def->tooltip = L("Adjust this value to prevent short, unclosed walls from being printed, which could increase print time. " + "Higher values remove more and longer walls.\n\n" + "NOTE: Bottom and top surfaces will not be affected by this value to prevent visual gaps on the ouside of the model. " + "Adjust 'One wall threshold' in the Advanced settings below to adjust the sensitivity of what is considered a top-surface. " + "'One wall threshold' is only visibile if this setting is set above the default value of 0.5, or if single-wall top surfaces is enabled."); + def->sidetext = L(""); + def->mode = comAdvanced; + def->min = 0.0; + def->max = 25.0; + def->set_default_value(new ConfigOptionFloat(0.5)); + def = this->add("initial_layer_min_bead_width", coPercent); def->label = L("First layer minimum wall width"); def->category = L("Quality"); diff --git a/src/libslic3r/PrintConfig.hpp b/src/libslic3r/PrintConfig.hpp index 4b1ddc4947..3fd07bf93b 100644 --- a/src/libslic3r/PrintConfig.hpp +++ b/src/libslic3r/PrintConfig.hpp @@ -784,6 +784,7 @@ PRINT_CONFIG_CLASS_DEFINE( ((ConfigOptionPercent, tree_support_top_rate)) ((ConfigOptionFloat, tree_support_branch_diameter_organic)) ((ConfigOptionFloat, tree_support_branch_angle_organic)) + ((ConfigOptionFloat, min_length_factor)) // Move all acceleration and jerk settings to object ((ConfigOptionFloat, default_acceleration)) diff --git a/src/libslic3r/PrintObject.cpp b/src/libslic3r/PrintObject.cpp index 28c8c45cc4..9af5df80b6 100644 --- a/src/libslic3r/PrintObject.cpp +++ b/src/libslic3r/PrintObject.cpp @@ -1127,6 +1127,7 @@ bool PrintObject::invalidate_state_by_config_options( || opt_key == "wall_transition_angle" || opt_key == "wall_distribution_count" || opt_key == "min_feature_size" + || opt_key == "min_length_factor" || opt_key == "min_bead_width") { steps.emplace_back(posSlice); } else if ( diff --git a/src/slic3r/GUI/ConfigManipulation.cpp b/src/slic3r/GUI/ConfigManipulation.cpp index 79ba0dc7b7..3a0b5fe390 100644 --- a/src/slic3r/GUI/ConfigManipulation.cpp +++ b/src/slic3r/GUI/ConfigManipulation.cpp @@ -695,7 +695,7 @@ void ConfigManipulation::toggle_print_fff_options(DynamicPrintConfig *config, co bool have_arachne = config->opt_enum("wall_generator") == PerimeterGeneratorType::Arachne; for (auto el : { "wall_transition_length", "wall_transition_filter_deviation", "wall_transition_angle", - "min_feature_size", "min_bead_width", "wall_distribution_count", "initial_layer_min_bead_width"}) + "min_feature_size", "min_length_factor", "min_bead_width", "wall_distribution_count", "initial_layer_min_bead_width"}) toggle_line(el, have_arachne); toggle_field("detect_thin_wall", !have_arachne); @@ -712,8 +712,8 @@ void ConfigManipulation::toggle_print_fff_options(DynamicPrintConfig *config, co toggle_line("make_overhang_printable_angle", have_make_overhang_printable); toggle_line("make_overhang_printable_hole_size", have_make_overhang_printable); - toggle_line("min_width_top_surface",config->opt_bool("only_one_wall_top")); - + toggle_line("min_width_top_surface", config->opt_bool("only_one_wall_top") || ((config->opt_float("min_length_factor") > 0.5f) && have_arachne)); // 0.5 is default value + for (auto el : { "hole_to_polyhole_threshold", "hole_to_polyhole_twisted" }) toggle_line(el, config->opt_bool("hole_to_polyhole")); diff --git a/src/slic3r/GUI/Tab.cpp b/src/slic3r/GUI/Tab.cpp index bdff812112..6c8cdd0f02 100644 --- a/src/slic3r/GUI/Tab.cpp +++ b/src/slic3r/GUI/Tab.cpp @@ -1957,6 +1957,7 @@ void TabPrint::build() optgroup->append_single_option_line("initial_layer_min_bead_width"); optgroup->append_single_option_line("min_bead_width"); optgroup->append_single_option_line("min_feature_size"); + optgroup->append_single_option_line("min_length_factor"); optgroup = page->new_optgroup(L("Walls and surfaces"), L"param_advanced"); optgroup->append_single_option_line("wall_sequence"); @@ -1964,8 +1965,8 @@ void TabPrint::build() optgroup->append_single_option_line("print_flow_ratio"); optgroup->append_single_option_line("top_solid_infill_flow_ratio"); optgroup->append_single_option_line("bottom_solid_infill_flow_ratio"); - optgroup->append_single_option_line("only_one_wall_top"); optgroup->append_single_option_line("min_width_top_surface"); + optgroup->append_single_option_line("only_one_wall_top"); optgroup->append_single_option_line("only_one_wall_first_layer"); optgroup->append_single_option_line("reduce_crossing_wall"); optgroup->append_single_option_line("max_travel_detour_distance");