mirror of
https://github.com/OrcaSlicer/OrcaSlicer.git
synced 2026-07-29 05:42:33 +00:00
[Feature] Add a new feature that allow user to insert extra solid infills (#10611)
* refactor Infill rotation template * clean up comments * set default solid_infill_rotate_template to empty * Fix an issue that infill_direction solid_infill_direction not working as expected * Add Extra Solid Infill Feature Introduced a new feature to insert extra solid infills at specific layers for enhanced strength in 3D prints. * fix doc error * fix image name * support "#K" for Explicit Layer List * update wiki
This commit is contained in:
@@ -794,7 +794,7 @@ static std::vector<std::string> 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",
|
||||
|
||||
@@ -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(""));
|
||||
|
||||
@@ -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<FuzzySkinType>, fuzzy_skin))
|
||||
((ConfigOptionFloat, fuzzy_skin_thickness))
|
||||
((ConfigOptionFloat, fuzzy_skin_point_distance))
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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<std::string>(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<std::string>(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);
|
||||
|
||||
@@ -108,6 +108,7 @@ std::map<std::string, std::vector<SimpleSettingData>> 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},
|
||||
|
||||
@@ -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");
|
||||
|
||||
Reference in New Issue
Block a user