Add template metalanguage support for infill rotation template (#9996)
Some checks failed
Build all / Build All (push) Waiting to run
Build all / Flatpak (push) Waiting to run
Publish docs to Wiki / Publish docs to Wiki (push) Has been cancelled

* 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 <softfeverever@gmail.com>
This commit is contained in:
π² 2025-07-25 15:29:08 +03:00 committed by GitHub
parent 3d16c7f4c8
commit 1ef427f661
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
9 changed files with 230 additions and 14 deletions

View file

@ -655,6 +655,7 @@ std::vector<SurfaceFill> 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<SurfaceFill> 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<SurfaceFill> 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<FillConcentricInternal *>(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 &region_config = layerm->region().config();
params.config = &region_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<std::string> 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<bool> 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>(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 = &region_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;

View file

@ -787,6 +787,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", "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",

View file

@ -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);

View file

@ -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<FuzzySkinType>, fuzzy_skin))
((ConfigOptionFloat, fuzzy_skin_thickness))
((ConfigOptionFloat, fuzzy_skin_point_distance))

View file

@ -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

View file

@ -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<std::string>(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<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);

View file

@ -107,6 +107,7 @@ std::map<std::string, std::vector<SimpleSettingData>> 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},

View file

@ -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>(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));

View file

@ -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");