Refactor infill rotation (#10587)

* 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

* update based on feedback
This commit is contained in:
SoftFever
2025-09-02 22:53:56 +08:00
committed by GitHub
parent c0a69793d8
commit d7b9e65da0
8 changed files with 300 additions and 253 deletions

View File

@@ -21,6 +21,197 @@
namespace Slic3r {
// Calculate infill rotation angle (in radians) for a given layer from a rotation template.
// Grammar subset handled (rotation only):
// [±]α[*Z or !][joint][-][N|B|T][length][* or !]
// [±]α* sets an initial angle only (no layer processed)
// Where:
// - α: angle in degrees. Without a sign it's absolute; with +/ it's relative. α% means a percentage of 360°.
// - Runtime: *Z repeats the instruction Z times; bare * is a no-op used for initialization; ! runs once globally and then stops.
// - Solid signs (D,S,O,M,R) are not processed here; if present they are treated as invalid/non-rotation characters.
// - Joint signs (shape of the turn across a range):
// / linear;
// N,n vertical sinus (n = lazy/half amplitude);
// Z,z horizontal sinus (z = lazy/half amplitude);
// $ arcsin; L quarter circle H→V; l quarter circle V→H;
// U,u squared; Q,q cubic; ~ random; ^ pseudorandom; | middle step; # vertical step at end.
// - Counting / range length:
// After the joint (or after α) a count determines duration of the turn:
// N = layer count, B = bottom_shell_layers, T = top_shell_layers.
// Prefix '-' flips the joint (swap initial/final orientation).
// - Length modifiers convert the count to a Z range instead of a pure layer count:
// mm, cm, m, ' (feet), " (inches), # (standard height of N layers), % (percent of model height).
//
// Behavior:
// - The template string is tokenized by commas/whitespace and evaluated cyclically with one or more "ranges" per token.
// - Absolute α resets the accumulated angle at the start of its range; relative α accumulates.
// - *Z and ! control repetition and one-time execution of tokens across layers.
// - If the template contains no metalanguage symbols, it is treated as a simple comma-separated list of angles repeated by modulo.
// - Returns angle in radians for the requested layer_id. 0° aligns with +X; fillers may internally rotate as needed.
double calculate_infill_rotation_angle(const PrintObject* object,
size_t layer_id,
const double& fixed_infill_angle,
const std::string& template_string)
{
if (template_string.empty()) {
return Geometry::deg2rad(fixed_infill_angle);
}
double angle = 0.0;
ConfigOptionFloats rotate_angles;
const std::string search_string = "/NnZz$LlUuQq~^|#";
if (regex_search(template_string, std::regex("[+\\-%*@\'\"cm" + search_string + "]"))) { // template metalanguage of rotating infill
std::regex del("[\\s,]+");
std::sregex_token_iterator it(template_string.begin(), template_string.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_add = 0;
double angle_steps = 1;
double angle_start = 0;
double limit_fill_z = object->get_layer(0)->bottom_z();
double start_fill_z = limit_fill_z;
bool _noop = false;
auto fill_form = std::string::npos;
bool _absolute = false;
bool _negative = false;
std::vector<bool> stop(tk.size(), false);
for (int i = 0; i <= layer_id; i++) {
double fill_z = object->get_layer(i)->bottom_z();
if (limit_fill_z < 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 = object->get_layer(i)->print_z;
// Solid handling removed: this function only computes rotation.
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 signs (D,S,O,M,R) are not handled here; if present they behave as invalid characters.
if (cs[0] == 'B') {
angle_steps = object->print()->default_region_config().bottom_shell_layers.value;
} else if (cs[0] == 'T') {
angle_steps = 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 * object->height() * 1e-8;
else if (cs[0] == '#') // value in the feet
limit_fill_z = angle_steps * 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{
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) object->layers().size());
idx = std::min(idx, (int) object->layers().size() - 1);
limit_fill_z = object->get_layer(idx)->print_z + sdx * 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 = 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 = Geometry::deg2rad(angle_start + angle_add * negvalue);
}
} else {
rotate_angles.deserialize(template_string);
auto rotate_angle_idx = layer_id % rotate_angles.size();
angle = Geometry::deg2rad(rotate_angles.values[rotate_angle_idx]);
}
return angle;
}
struct SurfaceFillParams
{
// Zero based extruder ID.
@@ -35,6 +226,8 @@ struct SurfaceFillParams
coordf_t overlap = 0.;
// Angle as provided by the region config, in radians.
float angle = 0.f;
// Orca: is_using_template_angle
bool is_using_template_angle = false;
// Is bridging used for this fill? Bridging parameters may be used even if this->flow.bridge() is not set.
bool bridge;
// Non-negative for a bridge.
@@ -90,6 +283,7 @@ struct SurfaceFillParams
RETURN_COMPARE_NON_EQUAL(spacing);
RETURN_COMPARE_NON_EQUAL(overlap);
RETURN_COMPARE_NON_EQUAL(angle);
RETURN_COMPARE_NON_EQUAL(is_using_template_angle);
RETURN_COMPARE_NON_EQUAL(density);
RETURN_COMPARE_NON_EQUAL(multiline);
// RETURN_COMPARE_NON_EQUAL_TYPED(unsigned, dont_adjust);
@@ -118,6 +312,7 @@ struct SurfaceFillParams
this->spacing == rhs.spacing &&
this->overlap == rhs.overlap &&
this->angle == rhs.angle &&
this->is_using_template_angle == rhs.is_using_template_angle &&
this->bridge == rhs.bridge &&
this->bridge_angle == rhs.bridge_angle &&
this->density == rhs.density &&
@@ -627,7 +822,6 @@ std::vector<SurfaceFill> group_fills(const Layer &layer, LockRegionParam &lock_p
flow_params.insert({flow, {exp}});
else
it->second.push_back(exp);
it++;
};
auto append_density_param = [](std::map<float, ExPolygons> &density_params, float density, const ExPolygon &exp) {
@@ -636,7 +830,6 @@ std::vector<SurfaceFill> group_fills(const Layer &layer, LockRegionParam &lock_p
density_params.insert({density, {exp}});
else
it->second.push_back(exp);
it++;
};
for (size_t region_id = 0; region_id < layer.regions().size(); ++ region_id) {
@@ -656,7 +849,6 @@ std::vector<SurfaceFill> group_fills(const Layer &layer, LockRegionParam &lock_p
params.lateral_lattice_angle_1 = region_config.lateral_lattice_angle_1;
params.lateral_lattice_angle_2 = region_config.lateral_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);
@@ -704,17 +896,21 @@ std::vector<SurfaceFill> group_fills(const Layer &layer, LockRegionParam &lock_p
params.extrusion_role = erSolidInfill;
}
}
if (params.extrusion_role == erInternalInfill) {
params.angle = calculate_infill_rotation_angle(layer.object(), layer.id(), region_config.infill_direction.value,
region_config.sparse_infill_rotate_template.value);
params.is_using_template_angle = !region_config.sparse_infill_rotate_template.value.empty();
} else {
params.angle = calculate_infill_rotation_angle(layer.object(), layer.id(), region_config.solid_infill_direction.value,
region_config.solid_infill_rotate_template.value);
params.is_using_template_angle = !region_config.solid_infill_rotate_template.value.empty();
}
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));
} else {
params.angle += float(Geometry::deg2rad(region_config.solid_infill_direction.value));
}
// Calculate the actual flow we'll be using for this infill.
params.bridge = is_bridge || Fill::use_bridge_flow(params.pattern);
@@ -892,8 +1088,12 @@ std::vector<SurfaceFill> group_fills(const Layer &layer, LockRegionParam &lock_p
params.pattern = ipRectilinear;
params.density = 100.f;
params.extrusion_role = erSolidInfill;
params.angle = float(Geometry::deg2rad(layerm.region().config().solid_infill_direction.value));
// calculate the actual flow we'll be using for this infill
const PrintRegionConfig &region_config = layerm.region().config();
params.angle = calculate_infill_rotation_angle(layer.object(), layer.id(), region_config.solid_infill_direction.value,
region_config.solid_infill_rotate_template.value);
params.is_using_template_angle = !region_config.solid_infill_rotate_template.value.empty();
// calculate the actual flow we'll be using for this infill
params.flow = layerm.flow(frSolidInfill);
params.spacing = params.flow.spacing();
surface_fills.emplace_back(params);
@@ -996,6 +1196,7 @@ void Layer::make_fills(FillAdaptive::Octree* adaptive_fill_octree, FillAdaptive:
f->layer_id = this->id();
f->z = this->print_z;
f->angle = surface_fill.params.angle;
f->is_using_template_angle = surface_fill.params.is_using_template_angle;
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();
@@ -1054,184 +1255,7 @@ void Layer::make_fills(FillAdaptive::Octree* adaptive_fill_octree, FillAdaptive:
params.config = &region_config;
params.pattern = surface_fill.params.pattern;
ConfigOptionFloats rotate_angles;
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]);
}
if( surface_fill.params.pattern == ipLockedZag ) {
if( surface_fill.params.pattern == ipLockedZag ) {
params.locked_zag = true;
params.infill_lock_depth = surface_fill.params.infill_lock_depth;
params.skin_infill_depth = surface_fill.params.skin_infill_depth;
@@ -1293,7 +1317,23 @@ void Layer::make_fills(FillAdaptive::Octree* adaptive_fill_octree, FillAdaptive:
assert(dynamic_cast<ExtrusionEntityCollection*>(layerm->fills.entities[i]) != nullptr);
#endif
}
/**
* Generate sparse-infill polylines for anchoring/analysis purposes.
*
* This produces the geometric polylines of internal sparse infill for the current
* layer (using the same infill pattern, angle, rotation template, and spacing that
* normal slicing would use), but it does not create extrusion entities.
*
* The returned polylines are consumed by internal-bridge detection on the next
* layer to derive anchor lines and compute the bridge direction over sparse infill.
*
* Notes:
* - Only `stInternal` surfaces are considered.
* - Rotation templates (e.g. `sparse_infill_rotate_template`) are applied so the
* anchors reflect the actual infill orientation.
* - For lightning/adaptive patterns, the respective generators are wired so their
* polylines match the final infill layout.
*/
Polylines Layer::generate_sparse_infill_polylines_for_anchoring(FillAdaptive::Octree* adaptive_fill_octree, FillAdaptive::Octree* support_fill_octree, FillLightning::Generator* lightning_generator) const
{
LockRegionParam skin_inner_param;
@@ -1346,6 +1386,7 @@ Polylines Layer::generate_sparse_infill_polylines_for_anchoring(FillAdaptive::Oc
f->layer_id = this->id() - this->object()->get_layer(0)->id(); // We need to subtract raft layers.
f->z = this->print_z;
f->angle = surface_fill.params.angle;
f->is_using_template_angle = surface_fill.params.is_using_template_angle;
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();

View File

@@ -306,7 +306,9 @@ std::pair<float, Point> Fill::_infill_direction(const Surface *surface) const
out_angle = float(surface->bridge_angle);
} else if (this->layer_id != size_t(-1)) {
// alternate fill direction
out_angle += this->_layer_angle(this->layer_id / surface->thickness_layers);
//Orca: if template angle is not empty, don't apply layer angle
if(!is_using_template_angle)
out_angle += this->_layer_angle(this->layer_id / surface->thickness_layers);
} else {
// printf("Layer_ID undefined!\n");
}

View File

@@ -119,8 +119,8 @@ public:
coordf_t overlap;
// in radians, ccw, 0 = East
float angle;
// Orca: enable angle shifting for layer change
float rotate_angle{ M_PI/180.0 };
// Orca: is_using_template_angle
bool is_using_template_angle{false};
// In scaled coordinates. Maximum lenght of a perimeter segment connecting two infill lines.
// Used by the FillRectilinear2, FillGrid2, FillTriangles, FillStars and FillCubic.
// If left to zero, the links will not be limited.
@@ -182,7 +182,6 @@ protected:
overlap(0.),
// Initial angle is undefined.
angle(FLT_MAX),
rotate_angle(M_PI/180.0),
link_max_length(0),
loop_clipping(0),
// The initial bounding box is empty, therefore undefined.
@@ -204,7 +203,7 @@ protected:
ExPolygon expolygon,
ThickPolylines& thick_polylines_out) {}
virtual float _layer_angle(size_t idx) const { return rotate_angle; }
virtual float _layer_angle(size_t idx) const { return is_using_template_angle ? 0.f : (idx & 1) ? float(M_PI/2.) : 0.f; }
virtual std::pair<float, Point> _infill_direction(const Surface *surface) const;

View File

@@ -3184,13 +3184,16 @@ 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("This parameter adds a rotation of sparse infill direction to each layer according to the specified template. "
"The template is a comma-separated list of angles in degrees, e.g. '0,90'. "
"The first angle is applied to the first layer, the second angle to the second layer, and so on. "
"If there are more layers than angles, the angles will be repeated. Note that not all sparse infill patterns support rotation.");
def->sidetext = "°"; // degrees, don't need translation
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("0,90"));
def->set_default_value(new ConfigOptionString(""));
//Orca
def = this->add("solid_infill_rotate_template", coString);
@@ -3202,7 +3205,7 @@ void PrintConfigDef::init_fff_params()
"If there are more layers than angles, the angles will be repeated. Note that not all solid infill patterns support rotation.");
def->sidetext = "°"; // degrees, don't need translation
def->mode = comAdvanced;
def->set_default_value(new ConfigOptionString("0,90"));
def->set_default_value(new ConfigOptionString(""));
def = this->add("skeleton_infill_density", coPercent);
def->label = L("Skeleton infill density");

View File

@@ -879,20 +879,9 @@ void ConfigManipulation::toggle_print_fff_options(DynamicPrintConfig *config, co
for (auto el : { "lateral_lattice_angle_1", "lateral_lattice_angle_2"})
toggle_line(el, lattice_options);
//Orca: hide rotate template for solid infill if not support
const auto _sparse_infill_pattern = config->option<ConfigOptionEnum<InfillPattern>>("sparse_infill_pattern")->value;
bool show_sparse_infill_rotate_template = _sparse_infill_pattern == ipRectilinear || _sparse_infill_pattern == ipLine ||
_sparse_infill_pattern == ipZigZag || _sparse_infill_pattern == ipCrossZag ||
_sparse_infill_pattern == ipLockedZag;
toggle_line("sparse_infill_rotate_template", show_sparse_infill_rotate_template);
//Orca: hide rotate template for solid infill if not support
const auto _solid_infill_pattern = config->option<ConfigOptionEnum<InfillPattern>>("internal_solid_infill_pattern")->value;
bool show_solid_infill_rotate_template = _solid_infill_pattern == ipRectilinear || _solid_infill_pattern == ipMonotonic ||
_solid_infill_pattern == ipMonotonicLine || _solid_infill_pattern == ipAlignedRectilinear;
toggle_line("solid_infill_rotate_template", show_solid_infill_rotate_template);
//Orca: disable infill_direction/solid_infill_direction if sparse_infill_rotate_template/solid_infill_rotate_template is not empty value
toggle_field("infill_direction", config->opt_string("sparse_infill_rotate_template") == "");
toggle_field("solid_infill_direction", config->opt_string("solid_infill_rotate_template") == "");
toggle_line("infill_overhang_angle", config->opt_enum<InfillPattern>("sparse_infill_pattern") == InfillPattern::ipLateralHoneycomb);

View File

@@ -436,15 +436,9 @@ void Field::get_value_by_opt_type(wxString& str, const bool check_value/* = true
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"[BT][!]?|[#][\\d]+[!]?|[+\\-]?[\\d.]+[%]?[*]?[\\d]*[/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();

View File

@@ -1633,6 +1633,35 @@ void Tab::on_value_change(const std::string& opt_key, const boost::any& value)
}
}
if (opt_key == "sparse_infill_rotate_template") {
// Orca: show warning dialog if rotate template for solid infill if not support
const auto _sparse_infill_pattern = m_config->option<ConfigOptionEnum<InfillPattern>>("sparse_infill_pattern")->value;
bool is_safe_to_rotate = _sparse_infill_pattern == ipRectilinear || _sparse_infill_pattern == ipLine ||
_sparse_infill_pattern == ipZigZag || _sparse_infill_pattern == ipCrossZag ||
_sparse_infill_pattern == ipLockedZag;
auto new_value = boost::any_cast<std::string>(value);
is_safe_to_rotate = is_safe_to_rotate || new_value.empty();
if (!is_safe_to_rotate) {
wxString msg_text = _(
L("Infill patterns are typically designed to handle rotation automatically to ensure proper printing and achieve their "
"intended effects (e.g., Gyroid, Cubic). Rotating the current sparse infill pattern may lead to insufficient support. "
"Please proceed with caution and thoroughly check for any potential printing issues."
"Are you sure you want to enable this option?"));
msg_text += "\n\n" + _(L("Are you sure you want to enable this option?"));
MessageDialog dialog(wxGetApp().plater(), msg_text, "", wxICON_WARNING | wxYES | wxNO);
dialog.SetButtonLabel(wxID_YES, _L("Enable"));
dialog.SetButtonLabel(wxID_NO, _L("Cancel"));
if (dialog.ShowModal() == wxID_NO) {
DynamicPrintConfig new_conf = *m_config;
new_conf.set_key_value("sparse_infill_rotate_template", new ConfigOptionString(""));
m_config_manipulation.apply(m_config, &new_conf);
wxGetApp().plater()->update();
}
}
}
if(opt_key=="layer_height"){
auto min_layer_height_from_nozzle=wxGetApp().preset_bundle->full_config().option<ConfigOptionFloats>("min_layer_height")->values;
auto max_layer_height_from_nozzle=wxGetApp().preset_bundle->full_config().option<ConfigOptionFloats>("max_layer_height")->values;
@@ -2211,7 +2240,7 @@ void TabPrint::build()
optgroup->append_single_option_line("fill_multiline", "strength_settings_infill#fill-multiline");
optgroup->append_single_option_line("sparse_infill_pattern", "strength_settings_infill#sparse-infill-pattern");
optgroup->append_single_option_line("infill_direction", "strength_settings_infill#direction");
optgroup->append_single_option_line("sparse_infill_rotate_template", "strength_settings_infill#rotation");
optgroup->append_single_option_line("sparse_infill_rotate_template", "strength_settings_infill_rotation_template_metalanguage");
optgroup->append_single_option_line("skin_infill_density", "strength_settings_patterns#locked-zag");
optgroup->append_single_option_line("skeleton_infill_density", "strength_settings_patterns#locked-zag");
optgroup->append_single_option_line("infill_lock_depth", "strength_settings_patterns#locked-zag");
@@ -2227,7 +2256,7 @@ void TabPrint::build()
optgroup->append_single_option_line("infill_anchor", "strength_settings_infill#anchor");
optgroup->append_single_option_line("internal_solid_infill_pattern", "strength_settings_infill#internal-solid-infill");
optgroup->append_single_option_line("solid_infill_direction", "strength_settings_infill#direction");
optgroup->append_single_option_line("solid_infill_rotate_template", "strength_settings_infill#rotation");
optgroup->append_single_option_line("solid_infill_rotate_template", "strength_settings_infill_rotation_template_metalanguage");
optgroup->append_single_option_line("gap_fill_target", "strength_settings_infill#apply-gap-fill");
optgroup->append_single_option_line("filter_out_gap_fill", "strength_settings_infill#filter-out-tiny-gaps");
optgroup->append_single_option_line("infill_wall_overlap", "strength_settings_infill#infill-wall-overlap");