From 1ef427f661b4ab0442fdeb90f5437cf6f5511e1f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=CF=80=C2=B2?= Date: Fri, 25 Jul 2025 15:29:08 +0300 Subject: [PATCH] Add template metalanguage support for infill rotation template (#9996) * Add some new non-overlapping functions for rotation surfaces/infills I can't post the entire package of changes yet, but this is just the beginning. These features do not affect the latest changes to the pattern rotation system. They are merely adding new functionality. * Added relative rotation of the infill according to the template. * Update PrintConfig.cpp * Update PrintConfig.cpp * Update PrintConfig.cpp * Add height limitation * Both sparse and solid. +one-time instructions * implementation v3 need for clean code in future * + Multiply Instructions * Add solid layers into sparse infill * Update Layer.hpp * Update PrintObject.cpp * Update Tab.cpp * Remove some bugs and increase quality * rename apply_model_direction to align_infill_direction_to_model * Change the data type of top_surface_direction and bottom_surface_direction to float so that they are consistent with other infill direction parameters. * remove top_surface_direction and bottom surface_direction options * clean code --------- Co-authored-by: SoftFever --- src/libslic3r/Fill/Fill.cpp | 197 +++++++++++++++++++++++++++++-- src/libslic3r/Preset.cpp | 1 + src/libslic3r/PrintConfig.cpp | 8 ++ src/libslic3r/PrintConfig.hpp | 1 + src/libslic3r/PrintObject.cpp | 1 + src/slic3r/GUI/Field.cpp | 32 ++++- src/slic3r/GUI/GUI_Factories.cpp | 1 + src/slic3r/GUI/Plater.cpp | 1 + src/slic3r/GUI/Tab.cpp | 2 + 9 files changed, 230 insertions(+), 14 deletions(-) diff --git a/src/libslic3r/Fill/Fill.cpp b/src/libslic3r/Fill/Fill.cpp index 2b0f53a368..2e83d0829f 100644 --- a/src/libslic3r/Fill/Fill.cpp +++ b/src/libslic3r/Fill/Fill.cpp @@ -655,6 +655,7 @@ std::vector group_fills(const Layer &layer, LockRegionParam &lock_p params.lattice_angle_1 = region_config.lattice_angle_1; params.lattice_angle_2 = region_config.lattice_angle_2; params.infill_overhang_angle = region_config.infill_overhang_angle; + params.angle = 0.; if (params.pattern == ipLockedZag) { params.infill_lock_depth = scale_(region_config.infill_lock_depth); params.skin_infill_depth = scale_(region_config.skin_infill_depth); @@ -703,10 +704,15 @@ std::vector group_fills(const Layer &layer, LockRegionParam &lock_p } } params.bridge_angle = float(surface.bridge_angle); + + if (region_config.align_infill_direction_to_model) { + auto m = layer.object()->trafo().matrix(); + params.angle += atan2((float) m(1, 0), (float) m(0, 0)); + } if (params.extrusion_role == erInternalInfill) { - params.angle = float(Geometry::deg2rad(region_config.infill_direction.value)); + params.angle += float(Geometry::deg2rad(region_config.infill_direction.value)); } else { - params.angle = float(Geometry::deg2rad(region_config.solid_infill_direction.value)); + params.angle += float(Geometry::deg2rad(region_config.solid_infill_direction.value)); } // Calculate the actual flow we'll be using for this infill. @@ -720,9 +726,9 @@ std::vector group_fills(const Layer &layer, LockRegionParam &lock_p if (!params.bridge) { if (params.extrusion_role == erInternalInfill) params.sparse_infill_speed = region_config.sparse_infill_speed; - else if (params.extrusion_role == erTopSolidInfill) + else if (params.extrusion_role == erTopSolidInfill) { params.top_surface_speed = region_config.top_surface_speed; - else if (params.extrusion_role == erSolidInfill) + } else if (params.extrusion_role == erSolidInfill) params.solid_infill_speed = region_config.internal_solid_infill_speed; } // Calculate flow spacing for infill pattern generation. @@ -992,7 +998,6 @@ void Layer::make_fills(FillAdaptive::Octree* adaptive_fill_octree, FillAdaptive: f->adapt_fill_octree = (surface_fill.params.pattern == ipSupportCubic) ? support_fill_octree : adaptive_fill_octree; f->print_config = &this->object()->print()->config(); f->print_object_config = &this->object()->config(); - f->adapt_fill_octree = (surface_fill.params.pattern == ipSupportCubic) ? support_fill_octree : adaptive_fill_octree; if (surface_fill.params.pattern == ipConcentricInternal) { FillConcentricInternal *fill_concentric = dynamic_cast(f.get()); assert(fill_concentric != nullptr); @@ -1045,14 +1050,186 @@ void Layer::make_fills(FillAdaptive::Octree* adaptive_fill_octree, FillAdaptive: params.using_internal_flow = using_internal_flow; params.no_extrusion_overlap = surface_fill.params.overlap; auto ®ion_config = layerm->region().config(); + params.config = ®ion_config; + params.pattern = surface_fill.params.pattern; ConfigOptionFloats rotate_angles; - rotate_angles.deserialize( surface_fill.params.extrusion_role == erInternalInfill ? region_config.sparse_infill_rotate_template.value : region_config.solid_infill_rotate_template.value); - auto rotate_angle_idx = f->layer_id % rotate_angles.size(); - f->rotate_angle = Geometry::deg2rad(rotate_angles.values[rotate_angle_idx]); + const std::string search_string = "/NnZz$LlUuQq~^|#"; + std::string v(params.extrusion_role == erInternalInfill ? region_config.sparse_infill_rotate_template.value : + region_config.solid_infill_rotate_template.value); + if (regex_search(v, std::regex("[+\\-%*@\'\"cmSODMR" + search_string + "]"))) { // template metalanguage of rotating infill + std::regex del("[\\s,]+"); + std::sregex_token_iterator it(v.begin(), v.end(), del, -1); + std::vector tk; + std::sregex_token_iterator end; + while (it != end) { + tk.push_back(*it++); + } + int t = 0; + int repeats = 0; + double angle = 0; + double angle_add = 0; + double angle_steps = 1; + double angle_start = 0; + double limit_fill_z = this->object()->get_layer(0)->bottom_z(); + double start_fill_z = limit_fill_z; + bool _noop = false; + auto solid = std::string::npos; // -1 - sparse, 0 - native (D), 1 - internal solid (S), 2 - concentric (O), 3 - monotonic (M), 4 - rectilinear (R) + auto fill_form = std::string::npos; + bool _absolute = false; + bool _negative = false; + std::vector stop(tk.size(), false); + + for (int i = 0; i <= this->id(); i++) { + double fill_z = this->object()->get_layer(i)->bottom_z(); + + if (limit_fill_z < this->object()->get_layer(i)->slice_z) { + if (repeats) { // if repeats >0 then restore parameters for new iteration + limit_fill_z += limit_fill_z - start_fill_z; + start_fill_z = fill_z; + repeats--; + } else { + start_fill_z = fill_z; + limit_fill_z = this->object()->get_layer(i)->print_z; + solid = std::string::npos; + fill_form = std::string::npos; + do { + if (!stop[t]) { + _noop = false; + _absolute = false; + _negative = false; + angle_start += angle_add; + angle_add = 0; + angle_steps = 1; + repeats = 1; + if (tk[t].find('!') != std::string::npos) // this is an one-time instruction + stop[t] = true; + + char* cs = &tk[t][0]; + + if ((cs[0] >= '0' && cs[0] <= '9') && !(cs[0] == '+' || cs[0] == '-')) // absolute/relative + _absolute = true; + + angle_add = strtod(cs, &cs); // read angle parameter + + if (cs[0] == '%') { // percentage of angles + angle_add *= 3.6; + cs = &cs[1]; + } + + int tit = tk[t].find('*'); + if (tit != std::string::npos) // overall angle_cycles + repeats = strtol(&tk[t][tit + 1], &cs, 0); + + if (repeats) { // run if overall cycles greater than 0 + solid = std::string("DSOMR").find(cs[0]); // solid infill + if (solid != std::string::npos) + cs = &cs[1]; + + if (cs[0] == 'B') { + angle_steps = this->object()->print()->default_region_config().bottom_shell_layers.value; + } else if (cs[0] == 'T') { + angle_steps = this->object()->print()->default_region_config().top_shell_layers.value; + } else { + fill_form = search_string.find(cs[0]); + if (fill_form != std::string::npos) + cs = &cs[1]; + + _negative = (cs[0] == '-'); // negative parameter + angle_steps = abs(strtod(cs, &cs)); + + if (angle_steps && cs[0] != '\0' && cs[0] != '!') { + if (cs[0] == '%') // value in the percents of fill_z + limit_fill_z = angle_steps * this->object()->height() * 1e-8; + else if (cs[0] == '#') // value in the feet + limit_fill_z = angle_steps * this->object()->config().layer_height; + else if (cs[0] == '\'') // value in the feet + limit_fill_z = angle_steps * 12 * 25.4; + else if (cs[0] == '\"') // value in the inches + limit_fill_z = angle_steps * 25.4; + else if (cs[0] == 'c') // value in centimeters + limit_fill_z = angle_steps * 10.; + else if (cs[0] == 'm') + if (cs[1] == 'm') { // value in the millimeters + limit_fill_z = angle_steps * 1.; + } else // value in the meters + limit_fill_z = angle_steps * 1000.; + limit_fill_z += fill_z; + angle_steps = 0; // limit_fill_z has already count + } + } + if (angle_steps) { // if limit_fill_z does not setting by lenght method. Get count the layer id above model height + if (fill_form == std::string::npos && !_absolute) + angle_add *= (int) angle_steps; + int idx = i + std::max(angle_steps - 1, 0.); + int sdx = std::max(0, idx - (int) this->object()->layers().size()); + idx = std::min(idx, (int) this->object()->layers().size() - 1); + limit_fill_z = this->object()->get_layer(idx)->print_z + sdx * this->object()->config().layer_height; + } + repeats = std::max(--repeats, 0); + } else + _noop = true; // set the dumb cycle + if (_absolute) { // is absolute + angle_start = angle_add; + angle_add = 0; + } + } + if (++t >= tk.size()) + t = 0; + } while (std::all_of(stop.begin(), stop.end(), [](bool v) { return v; }) ? false : + (t ? _noop : false) || stop[t]); // if this is a dumb instruction which never reaprated twice + } + } + double top_z = this->object()->get_layer(i)->print_z; + double negvalue = (_negative ? limit_fill_z - top_z : top_z - start_fill_z) / (limit_fill_z - start_fill_z); + + switch (fill_form) { + case 0: break; // /-joint, linear + case 1: negvalue -= sin(negvalue * PI * 2.) / (PI * 2.); break; // N-joint, sinus, vertical start + case 2: negvalue -= sin(negvalue * PI * 2.) / (PI * 4.); break; // n-joint, sinus, vertical start, lazy + case 3: negvalue += sin(negvalue * PI * 2.) / (PI * 2.); break; // Z-joint, sinus, horizontal start + case 4: negvalue += sin(negvalue * PI * 2.) / (PI * 4.); break; // z-joint, sinus, horizontal start, lazy + case 5: negvalue = asin(negvalue * 2. - 1.) / PI + 0.5; break; // $-joint, arcsin + case 6: negvalue = sin(negvalue * PI / 2.); break; // L-joint, quarter of circle, horizontal start + case 7: negvalue = 1. - cos(negvalue * PI / 2.); break; // l-joint, quarter of circle, vertical start + case 8: negvalue = 1. - pow(1. - negvalue, 2); break; // U-joint, squared, x2 + case 9: negvalue = pow(1 - negvalue, 2); break; // u-joint, squared, x2 inverse + case 10: negvalue = 1. - pow(1. - negvalue, 3); break; // Q-joint, cubic, x3 + case 11: negvalue = pow(1. - negvalue, 3); break; // q-joint, cubic, x3 inverse + case 12: negvalue = (double) rand() / RAND_MAX; break; // ~-joint, random, fill the whole angle + case 13: negvalue += (double) rand() / RAND_MAX - 0.5; break; // ^-joint, pseudorandom, disperse at middle line + case 14: negvalue = 0.5; break; // |-joint, like #-joint but placed at middle angle + case 15: negvalue = _negative ? 0. : 1.; break; // #-joint, vertical at the end angle + } + angle = angle_start + angle_add * negvalue; + } + if (solid != std::string::npos) { + switch (solid) { + case 1: params.pattern = region_config.internal_solid_infill_pattern.value; break; // selected solid pattern + case 2: params.pattern = ipConcentric; break; // concentric pattern + case 3: params.pattern = ipMonotonic; break; // monotonic pattern + case 4: params.pattern = ipRectilinear; // rectilinear pattern + } // or else use native pattern + params.extrusion_role = erSolidInfill; + params.density = 1.; + surface_fill.params.pattern = params.pattern; + + f = std::unique_ptr(Fill::new_from_type(params.pattern)); // reinitialize surface + f->set_bounding_box(bbox); + f->layer_id = this->id(); + f->z = this->print_z; + f->angle = surface_fill.params.angle; + f->print_config = &this->object()->print()->config(); + f->print_object_config = &this->object()->config(); + params.use_arachne = surface_fill.params.pattern == ipConcentric || surface_fill.params.pattern == ipConcentricInternal; + } + f->rotate_angle = Geometry::deg2rad(angle); + } else { + rotate_angles.deserialize(v); + auto rotate_angle_idx = f->layer_id % rotate_angles.size(); + f->rotate_angle = Geometry::deg2rad(rotate_angles.values[rotate_angle_idx]); + } - params.config = ®ion_config; - params.pattern = surface_fill.params.pattern; if( surface_fill.params.pattern == ipLockedZag ) { params.locked_zag = true; params.infill_lock_depth = surface_fill.params.infill_lock_depth; diff --git a/src/libslic3r/Preset.cpp b/src/libslic3r/Preset.cpp index 5cc4c23150..bc95d25012 100644 --- a/src/libslic3r/Preset.cpp +++ b/src/libslic3r/Preset.cpp @@ -787,6 +787,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", "lattice_angle_1", "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", "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 7dcb83dd66..24e29459ed 100644 --- a/src/libslic3r/PrintConfig.cpp +++ b/src/libslic3r/PrintConfig.cpp @@ -2366,6 +2366,14 @@ void PrintConfigDef::init_fff_params() def->min = 0; def->max = 100; def->set_default_value(new ConfigOptionPercent(20)); + + def = this->add("align_infill_direction_to_model", coBool); + def->label = L("Align infill direction to model"); + def->category = L("Strength"); + def->tooltip = L("Aligns infill and surface fill directions to follow the model's orientation on the build plate. When enabled, fill directions rotate with the model to maintain optimal strength characteristics."); + def->mode = comAdvanced; + def->set_default_value(new ConfigOptionBool(false)); + // Infill multiline def = this->add("fill_multiline", coInt); diff --git a/src/libslic3r/PrintConfig.hpp b/src/libslic3r/PrintConfig.hpp index bea60fc749..c2f38ae0b0 100644 --- a/src/libslic3r/PrintConfig.hpp +++ b/src/libslic3r/PrintConfig.hpp @@ -983,6 +983,7 @@ PRINT_CONFIG_CLASS_DEFINE( ((ConfigOptionFloat, lattice_angle_1)) ((ConfigOptionFloat, lattice_angle_2)) ((ConfigOptionFloat, infill_overhang_angle)) + ((ConfigOptionBool, align_infill_direction_to_model)) ((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 c84a265610..fbc56dceed 100644 --- a/src/libslic3r/PrintObject.cpp +++ b/src/libslic3r/PrintObject.cpp @@ -1078,6 +1078,7 @@ bool PrintObject::invalidate_state_by_config_options( || opt_key == "skeleton_infill_line_width" || opt_key == "infill_direction" || opt_key == "solid_infill_direction" + || opt_key == "align_infill_direction_to_model" || opt_key == "ensure_vertical_shell_thickness" || opt_key == "bridge_angle" || opt_key == "internal_bridge_angle" // ORCA: Internal bridge angle override diff --git a/src/slic3r/GUI/Field.cpp b/src/slic3r/GUI/Field.cpp index d7c9408103..094d3e7be6 100644 --- a/src/slic3r/GUI/Field.cpp +++ b/src/slic3r/GUI/Field.cpp @@ -431,10 +431,34 @@ void Field::get_value_by_opt_type(wxString& str, const bool check_value/* = true set_value(str, true); } } else if (m_opt.opt_key == "sparse_infill_rotate_template" || m_opt.opt_key == "solid_infill_rotate_template") { - if (!ConfigOptionFloats::validate_string(str.utf8_string())) { - show_error(m_parent, format_wxstr(_L("This parameter expects a comma-delimited list of numbers. E.g, \"0,90\"."))); - wxString old_value(boost::any_cast(m_value)); - this->set_value(old_value, true); // Revert to previous value + string ustr(str.utf8_string()); + if (!ConfigOptionFloats::validate_string(ustr)) { + string v; + std::smatch match; + string ps = (m_opt.opt_key == "sparse_infill_rotate_template") ? + u8"[SODMR]?[BT][!]?|[SODMR]?[#][\\d]+[!]?|[+\\-]?[\\d.]+[%]?[*]?[\\d]*[SODMR]?[/NnZz$LlUuQq~^|#]?[+\\-]?[\\d.]*[%#\'\"cm]?[m]?[BT]?[!*]?" : + u8"[#][\\d]+[!]?|[+\\-]?[\\d.]+[%]?[*]?[\\d]*[/NnZz$LlUuQq~^|#]?[+\\-]?[\\d.]*[%#\'\"cm]?[m]?[!*]?"; + + //if (m_opt.opt_key == "sparse_infill_rotate_template") { + //string ps = u8"[#][\\d]+[!]?|[+\\-]?[\\d.]+[%]?[*]?[\\d]*[SODMR]?[/NnZz$LlUuQq~^|#]?[+\\-]?[\\d.]*[%#\'\"cm]?[m]?["; + //if (m_opt.opt_key == "sparse_infill_rotate_template") { + // ps = u8"[BT][!]?|" + ps ; + //} + //ps += u8"BT]?[!*]?"; + while (std::regex_search(ustr, match, std::regex(ps))) { + for (auto x : match) v += x.str() + ", "; + ustr = match.suffix().str(); + } + v = v.substr(0, v.length() - 2); + try { + this->set_value(from_u8(v), true); + m_value = into_u8(v); + } catch (...) { + 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); diff --git a/src/slic3r/GUI/GUI_Factories.cpp b/src/slic3r/GUI/GUI_Factories.cpp index e23f57991c..5acd8cadbc 100644 --- a/src/slic3r/GUI/GUI_Factories.cpp +++ b/src/slic3r/GUI/GUI_Factories.cpp @@ -107,6 +107,7 @@ std::map> SettingsFactory::PART_CAT { L("Strength"), {{"wall_loops", "",1},{"top_shell_layers", L("Top Solid Layers"),1},{"top_shell_thickness", L("Top Minimum Shell Thickness"),1},{"top_surface_density", L("Top Surface Density"),1}, {"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},{"lattice_angle_1", "",1},{"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}, {"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/Plater.cpp b/src/slic3r/GUI/Plater.cpp index eba57e60d6..ef069ab337 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -9902,6 +9902,7 @@ void adjust_settings_for_flowrate_calib(ModelObjectPtrs& objects, bool linear, i _obj->config.set_key_value("top_solid_infill_flow_ratio", new ConfigOptionFloat(1.0f)); _obj->config.set_key_value("infill_direction", new ConfigOptionFloat(45)); _obj->config.set_key_value("solid_infill_direction", new ConfigOptionFloat(135)); + _obj->config.set_key_value("align_infill_direction_to_model", new ConfigOptionBool(true)); _obj->config.set_key_value("ironing_type", new ConfigOptionEnum(IroningType::NoIroning)); _obj->config.set_key_value("internal_solid_infill_speed", new ConfigOptionFloat(internal_solid_speed)); _obj->config.set_key_value("top_surface_speed", new ConfigOptionFloat(top_surface_speed)); diff --git a/src/slic3r/GUI/Tab.cpp b/src/slic3r/GUI/Tab.cpp index a6532c6850..c909258d4b 100644 --- a/src/slic3r/GUI/Tab.cpp +++ b/src/slic3r/GUI/Tab.cpp @@ -2195,6 +2195,7 @@ void TabPrint::build() optgroup->append_single_option_line("detect_thin_wall", "strength_settings_walls#detect-thin-wall"); optgroup = page->new_optgroup(L("Top/bottom shells"), L"param_shell"); + optgroup->append_single_option_line("top_shell_layers", "strength_settings_top_bottom_shells#shells-layers"); optgroup->append_single_option_line("top_shell_thickness", "strength_settings_top_bottom_shells#shell-thickness"); optgroup->append_single_option_line("top_surface_density", "strength_settings_top_bottom_shells#surface-density"); @@ -2233,6 +2234,7 @@ void TabPrint::build() optgroup->append_single_option_line("infill_wall_overlap", "strength_settings_infill#infill-wall-overlap"); optgroup = page->new_optgroup(L("Advanced"), L"param_advanced"); + optgroup->append_single_option_line("align_infill_direction_to_model"); 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");