diff --git a/doc/images/fill/extra-solid-infill.gif b/doc/images/fill/extra-solid-infill.gif new file mode 100644 index 0000000000..2ce6b62c01 Binary files /dev/null and b/doc/images/fill/extra-solid-infill.gif differ diff --git a/doc/print_settings/strength/strength_settings_infill.md b/doc/print_settings/strength/strength_settings_infill.md index 36850e5391..6ff90f7f5f 100644 --- a/doc/print_settings/strength/strength_settings_infill.md +++ b/doc/print_settings/strength/strength_settings_infill.md @@ -13,6 +13,7 @@ Infill is the internal structure of a 3D print, providing strength and support. - [Filter out tiny gaps](#filter-out-tiny-gaps) - [Anchor](#anchor) - [Internal Solid Infill](#internal-solid-infill) +- [Extra Solid Infill](#extra-solid-infill) - [Sparse Infill Pattern](#sparse-infill-pattern) - [Credits](#credits) @@ -153,11 +154,56 @@ OrcaSlicer tries to connect two close infill lines to a short perimeter segment. - **Anchor On** ![InfillAnchorOn](https://github.com/SoftFever/OrcaSlicer/blob/main/doc/images/fill/InfillAnchorOn.png?raw=true) - ## Internal Solid Infill Line pattern of internal solid infill. If the [detect narrow internal solid infill](strength_settings_advanced#detect-narrow-internal-solid-infill) be enabled, the [concentric pattern](strength_settings_patterns#concentric) will be used for the small area. + +## Extra Solid Infill + +Insert extra solid infills at specific layers to add strength at critical points in your print. This feature allows you to strategically reinforce your part without changing the overall sparse infill density. + +![extra-solid-infill](https://github.com/SoftFever/OrcaSlicer/blob/main/doc/images/fill/extra-solid-infill.gif?raw=true) + +The pattern supports two formats: + +### Interval Pattern +- **Simple interval**: `N` - Insert 1 solid layer every N layers, equal to `N#1` +- **Multiple layers**: `N#K` - Insert K consecutive solid layers every N layers +- **Optional K**: `N#` - Shorthand for `N#1` + +Examples: +``` +5 or 5#1 # Insert 1 solid layer every 5 layers +5# # Same as 5#1 +10#2 # Insert 2 consecutive solid layers every 10 layers +``` + +### Explicit Layer List +Specify exact layer numbers (1-based) using comma-separated values. Each entry may be a single layer `N` or a range `N#K` to insert K consecutive solid layers starting at layer N: + +``` +1,7,9 # Insert solid layers at layers 1, 7, and 9 +5,15,25 # Insert solid layers at layers 5, 15, and 25 +5,9#2,18 # Insert at 5; at 9 and 10 (because #2); and at 18 +``` + +> [!NOTE] +> - Layer numbers are 1-based (first layer is layer 1) +> - `#K` is optional in both interval and explicit list entries (`N#` equals `N#1`) +> - Solid layers are inserted in addition to the normal sparse infill pattern + +> [!TIP] +> Use this feature to: +> - Add strength at stress concentration points +> - Reinforce mounting holes or attachment points +> - Create internal structure for functional parts +> - Add periodic reinforcement for tall prints +> - Insert a single solid layer at a specific height by using an explicit list with a leading 0, which will be ignored because layer indices are 1-based. Example: `0,15` inserts a solid layer only at layer 15. + +> [!WARNING] +> Layers that include solid infill can take significantly longer than surrounding layers. This time differential may lead to z-banding-like bulges. Consider adjusting cooling or speeds if you observe artifacts. + ## Sparse Infill Pattern > [!TIP] diff --git a/src/libslic3r/Preset.cpp b/src/libslic3r/Preset.cpp index 6dc2544ddc..6718e0e809 100644 --- a/src/libslic3r/Preset.cpp +++ b/src/libslic3r/Preset.cpp @@ -794,7 +794,7 @@ static std::vector s_Preset_print_options { "extra_perimeters_on_overhangs", "ensure_vertical_shell_thickness", "reduce_crossing_wall", "detect_thin_wall", "detect_overhang_wall", "overhang_reverse", "overhang_reverse_threshold","overhang_reverse_internal_only", "wall_direction", "seam_position", "staggered_inner_seams", "wall_sequence", "is_infill_first", "sparse_infill_density","fill_multiline", "sparse_infill_pattern", "lateral_lattice_angle_1", "lateral_lattice_angle_2", "infill_overhang_angle", "top_surface_pattern", "bottom_surface_pattern", "infill_direction", "solid_infill_direction", "counterbore_hole_bridging","infill_shift_step", "sparse_infill_rotate_template", "solid_infill_rotate_template", "symmetric_infill_y_axis","skeleton_infill_density", "infill_lock_depth", "skin_infill_depth", "skin_infill_density", - "align_infill_direction_to_model", + "align_infill_direction_to_model", "extra_solid_infills", "minimum_sparse_infill_area", "reduce_infill_retraction","internal_solid_infill_pattern","gap_fill_target", "ironing_type", "ironing_pattern", "ironing_flow", "ironing_speed", "ironing_spacing", "ironing_angle", "ironing_inset", "support_ironing", "support_ironing_pattern", "support_ironing_flow", "support_ironing_spacing", diff --git a/src/libslic3r/PrintConfig.cpp b/src/libslic3r/PrintConfig.cpp index 755bf573ce..17a207165f 100644 --- a/src/libslic3r/PrintConfig.cpp +++ b/src/libslic3r/PrintConfig.cpp @@ -2384,6 +2384,13 @@ void PrintConfigDef::init_fff_params() def->mode = comAdvanced; def->set_default_value(new ConfigOptionBool(false)); + def = this->add("extra_solid_infills", coString); + def->label = L("Insert solid layers"); + def->category = L("Strength"); + def->tooltip = L("Insert solid infill at specific layers. Use N to insert every Nth layer, N#K to insert K consecutive solid layers every N layers (K is optional, e.g. '5#' equals '5#1'), or a comma-separated list (e.g. 1,7,9) to insert at explicit layers. Layers are 1-based."); + def->mode = comAdvanced; + def->set_default_value(new ConfigOptionString()); + // Infill multiline def = this->add("fill_multiline", coInt); @@ -3184,13 +3191,12 @@ void PrintConfigDef::init_fff_params() def = this->add("sparse_infill_rotate_template", coString); def->label = L("Sparse infill rotatation template"); def->category = L("Strength"); - def->tooltip = L( - "Rotate the sparse infill direction per layer using a template of angles. " - "Enter comma-separated degrees (e.g., '0,30,60,90'). " - "Angles are applied in order by layer and repeat when the list ends. " - "Advanced syntax is supported: '+5' rotates +5° every layer; '+5#5' rotates +5° every 5 layers. See the Wiki for details. " - "When a template is set, the standard infill direction setting is ignored. " - "Note: some infill patterns (e.g., Gyroid) control rotation themselves; use with care."); + def->tooltip = L("Rotate the sparse infill direction per layer using a template of angles. " + "Enter comma-separated degrees (e.g., '0,30,60,90'). " + "Angles are applied in order by layer and repeat when the list ends. " + "Advanced syntax is supported: '+5' rotates +5° every layer; '+5#5' rotates +5° every 5 layers. See the Wiki for details. " + "When a template is set, the standard infill direction setting is ignored. " + "Note: some infill patterns (e.g., Gyroid) control rotation themselves; use with care."); def->sidetext = L("°"); def->mode = comAdvanced; def->set_default_value(new ConfigOptionString("")); diff --git a/src/libslic3r/PrintConfig.hpp b/src/libslic3r/PrintConfig.hpp index d9fbd57944..1e927a1a13 100644 --- a/src/libslic3r/PrintConfig.hpp +++ b/src/libslic3r/PrintConfig.hpp @@ -985,6 +985,7 @@ PRINT_CONFIG_CLASS_DEFINE( ((ConfigOptionFloat, lateral_lattice_angle_2)) ((ConfigOptionFloat, infill_overhang_angle)) ((ConfigOptionBool, align_infill_direction_to_model)) + ((ConfigOptionString, extra_solid_infills)) ((ConfigOptionEnum, fuzzy_skin)) ((ConfigOptionFloat, fuzzy_skin_thickness)) ((ConfigOptionFloat, fuzzy_skin_point_distance)) diff --git a/src/libslic3r/PrintObject.cpp b/src/libslic3r/PrintObject.cpp index bf56528e98..1c0e8b95cc 100644 --- a/src/libslic3r/PrintObject.cpp +++ b/src/libslic3r/PrintObject.cpp @@ -1079,6 +1079,7 @@ bool PrintObject::invalidate_state_by_config_options( || opt_key == "infill_direction" || opt_key == "solid_infill_direction" || opt_key == "align_infill_direction_to_model" + || opt_key == "extra_solid_infills" || opt_key == "ensure_vertical_shell_thickness" || opt_key == "bridge_angle" || opt_key == "internal_bridge_angle" // ORCA: Internal bridge angle override @@ -3489,16 +3490,14 @@ void PrintObject::discover_horizontal_shells() Layer *layer = m_layers[i]; LayerRegion *layerm = layer->regions()[region_id]; const PrintRegionConfig ®ion_config = layerm->region().config(); -#if 0 - if (region_config.solid_infill_every_layers.value > 0 && region_config.sparse_infill_density.value > 0 && - (i % region_config.solid_infill_every_layers) == 0) { - // Insert a solid internal layer. Mark stInternal surfaces as stInternalSolid or stInternalBridge. - SurfaceType type = (region_config.sparse_infill_density == 100 || region_config.solid_infill_every_layers == 1) ? stInternalSolid : stInternalBridge; - for (Surface &surface : layerm->fill_surfaces.surfaces) + + if (!region_config.extra_solid_infills.value.empty() && + check_layer_id_pattern(region_config.extra_solid_infills.value, i)) { + // Insert a solid internal layer. Mark stInternal surfaces as stInternalSolid. + for (Surface& surface : layerm->fill_surfaces.surfaces) if (surface.surface_type == stInternal) - surface.surface_type = type; + surface.surface_type = stInternalSolid; } -#endif // If ensure_vertical_shell_thickness, then the rest has already been performed by discover_vertical_shells(). if (region_config.ensure_vertical_shell_thickness.value == evstAll) diff --git a/src/libslic3r/Utils.hpp b/src/libslic3r/Utils.hpp index e9408e1a02..bf291d2f50 100644 --- a/src/libslic3r/Utils.hpp +++ b/src/libslic3r/Utils.hpp @@ -681,6 +681,8 @@ void copy_directory_recursively(const boost::filesystem::path &source, const boo void save_string_file(const boost::filesystem::path& p, const std::string& str); void load_string_file(const boost::filesystem::path& p, std::string& str); +bool check_layer_id_pattern(const std::string& pattern, int layer_id); + } // namespace Slic3r #if WIN32 diff --git a/src/libslic3r/utils.cpp b/src/libslic3r/utils.cpp index 744eb98311..a493f6ebd1 100644 --- a/src/libslic3r/utils.cpp +++ b/src/libslic3r/utils.cpp @@ -1566,4 +1566,91 @@ void load_string_file(const boost::filesystem::path& p, std::string& str) file.read(&str[0], sz); } +// pattern string supprt these pattern: " +// 1. 5#1, insert 1 solid layer every 5 layers. this can be simplified to 5 +// 2."1,7,9", explicitly insert solid layer at layer 1, 7, 9 +bool check_layer_id_pattern(const std::string& pattern, int layer_id){ + if (pattern.empty() || layer_id < 0) + return false; + + // layer_id is 0-based, so we need to add 1 to make it 1-based + layer_id++; + + // Remove whitespace and surrounding quotes. + std::string p; p.reserve(pattern.size()); + for (char c : pattern) { + if (c == ' ' || c == '\t' || c == '\n' || c == '\r') + continue; + p.push_back(c); + } + if (!p.empty() && (p.front() == '"' || p.front() == '\'')) + p.erase(p.begin()); + if (!p.empty() && (p.back() == '"' || p.back() == '\'')) + p.pop_back(); + if (p.empty()) + return false; + + // Explicit list form: "1,7,9" or with counts per entry: "5,9#2,18" + if (p.find(',') != std::string::npos) { + size_t start = 0; + while (start < p.size()) { + size_t end = p.find(',', start); + std::string token = p.substr(start, (end == std::string::npos) ? std::string::npos : end - start); + if (!token.empty()) { + try { + size_t hash_pos_token = token.find('#'); + if (hash_pos_token == std::string::npos) { + int value = std::stoi(token); + if (value == layer_id) + return true; + } else { + int base_layer = std::stoi(token.substr(0, hash_pos_token)); + std::string count_str = token.substr(hash_pos_token + 1); + int local_count = 1; + if (!count_str.empty()) + local_count = std::stoi(count_str); + if (base_layer > 0 && local_count > 0) { + if (layer_id >= base_layer && layer_id < base_layer + local_count) + return true; + } + } + } catch (...) { + // Ignore invalid tokens + } + } + if (end == std::string::npos) + break; + start = end + 1; + } + return false; + } + + // Interval form: "N#K" or simplified "N" (equals to N#1) + int interval = 0; + int count = 1; + size_t hash_pos = p.find('#'); + try { + if (hash_pos == std::string::npos) { + interval = std::stoi(p); + } else { + interval = std::stoi(p.substr(0, hash_pos)); + std::string count_str = p.substr(hash_pos + 1); + if (!count_str.empty()) + count = std::stoi(count_str); + } + } catch (...) { + return false; + } + + if (interval <= 0 || count <= 0) + return false; + + // Layers are 1-based. Match layers interval, interval+1, ..., interval+count-1, then repeat every interval. + if (layer_id < interval) + return false; + int mod = layer_id % interval; // For multiples, mod == 0 + return mod >= 0 && mod < count; +} + + }; // namespace Slic3r diff --git a/src/slic3r/GUI/Field.cpp b/src/slic3r/GUI/Field.cpp index 7fb97d798c..03c9f0c9bb 100644 --- a/src/slic3r/GUI/Field.cpp +++ b/src/slic3r/GUI/Field.cpp @@ -451,13 +451,27 @@ void Field::get_value_by_opt_type(wxString& str, const bool check_value/* = true show_error(m_parent, format_wxstr(_L("This parameter expects a valid template."))); wxString old_value(boost::any_cast(m_value)); this->set_value(old_value, true); // Revert to previous value - throw; } } else { // Valid string, so update m_value with the new string from the control. m_value = into_u8(str); } break; + } else if (m_opt.opt_key == "extra_solid_infills") { + string ustr(str.utf8_string()); + // New rule: accept either interval form (N or N#K) or explicit list (e.g. 1,7,9), with optional quotes. + const std::regex rx_interval(u8R"(^\s*['"]?\s*\d+\s*(?:#\s*\d*)?\s*['"]?\s*$)"); + // List entries may be plain numbers or number with optional #K count, e.g., 5, 9#2, 18 + const std::regex rx_list(u8R"(^\s*['"]?\s*\d+(?:\s*#\s*\d*)?(?:\s*,\s*\d+(?:\s*#\s*\d*)?)*\s*['"]?\s*$)"); + bool is_valid = ustr.empty() || std::regex_match(ustr, rx_interval) || std::regex_match(ustr, rx_list); + if (!is_valid) { + show_error(m_parent, format_wxstr(_L("Invalid pattern. Use N, N#K, or a comma-separated list with optional #K per entry. Examples: 5, 5#2, 1,7,9, 5,9#2,18."))); + wxString old_value(boost::any_cast(m_value)); + this->set_value(old_value, true); // Revert to previous value + } + // Valid string or empty, so update m_value with the new string from the control. + m_value = into_u8(str); + break; } m_value = into_u8(str); diff --git a/src/slic3r/GUI/GUI_Factories.cpp b/src/slic3r/GUI/GUI_Factories.cpp index 1e3f5b9693..ac9ce3b4b4 100644 --- a/src/slic3r/GUI/GUI_Factories.cpp +++ b/src/slic3r/GUI/GUI_Factories.cpp @@ -108,6 +108,7 @@ std::map> SettingsFactory::PART_CAT {"bottom_shell_layers", L("Bottom Solid Layers"),1}, {"bottom_shell_thickness", L("Bottom Minimum Shell Thickness"),1},{"bottom_surface_density", L("Bottom Surface Density"),1}, {"sparse_infill_density", "",1},{"sparse_infill_pattern", "",1},{"lateral_lattice_angle_1", "",1},{"lateral_lattice_angle_2", "",1},{"infill_overhang_angle", "",1},{"infill_anchor", "",1},{"infill_anchor_max", "",1},{"top_surface_pattern", "",1},{"bottom_surface_pattern", "",1}, {"internal_solid_infill_pattern", "",1}, {"align_infill_direction_to_model", "", 1}, + {"extra_solid_infills", "", 1}, {"infill_combination", "",1}, {"infill_combination_max_layer_height", "",1}, {"infill_wall_overlap", "",1},{"top_bottom_infill_wall_overlap", "",1}, {"solid_infill_direction", "",1}, {"infill_direction", "",1}, {"bridge_angle", "",1}, {"internal_bridge_angle", "",1}, {"minimum_sparse_infill_area", "",1} }}, { L("Speed"), {{"outer_wall_speed", "",1},{"inner_wall_speed", "",2},{"sparse_infill_speed", "",3},{"top_surface_speed", "",4}, {"internal_solid_infill_speed", "",5}, diff --git a/src/slic3r/GUI/Tab.cpp b/src/slic3r/GUI/Tab.cpp index 692e106bd6..d1272d25ef 100644 --- a/src/slic3r/GUI/Tab.cpp +++ b/src/slic3r/GUI/Tab.cpp @@ -2263,6 +2263,7 @@ void TabPrint::build() optgroup = page->new_optgroup(L("Advanced"), L"param_advanced"); optgroup->append_single_option_line("align_infill_direction_to_model", "strength_settings_advanced#align-infill-direction-to-model"); + optgroup->append_single_option_line("extra_solid_infills", "strength_settings_infill#extra-solid-infill"); optgroup->append_single_option_line("bridge_angle", "strength_settings_advanced#bridge-infill-direction"); optgroup->append_single_option_line("internal_bridge_angle", "strength_settings_advanced#bridge-infill-direction"); // ORCA: Internal bridge angle override optgroup->append_single_option_line("minimum_sparse_infill_area", "strength_settings_advanced#minimum-sparse-infill-threshold");