mirror of
https://github.com/OrcaSlicer/OrcaSlicer.git
synced 2026-05-14 00:52:04 +00:00
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:
@@ -3,9 +3,9 @@
|
||||
This metalanguage provides a way to define the [direction and rotation](strength_settings_infill#direction-and-rotation) of [patterns](strength_settings_patterns) in 3D printing.
|
||||
|
||||
- [Basic instructions](#basic-instructions)
|
||||
- [Quick examples](#quick-examples)
|
||||
- [Defined angle](#defined-angle)
|
||||
- [Runtime instructions](#runtime-instructions)
|
||||
- [Solid sign](#solid-sign)
|
||||
- [Joint sign](#joint-sign)
|
||||
- [Counting](#counting)
|
||||
- [Length modifier](#length-modifier)
|
||||
@@ -15,21 +15,29 @@ This metalanguage provides a way to define the [direction and rotation](strength
|
||||
- [Repetitive, adjusting and one-time instructions](#repetitive-adjusting-and-one-time-instructions)
|
||||
- [Range instructions](#range-instructions)
|
||||
- [Constant layer number instructions](#constant-layer-number-instructions)
|
||||
- [Solid layers into sparse infill instructions](#solid-layers-into-sparse-infill-instructions)
|
||||
- [Complex template examples](#complex-template-examples)
|
||||
- [Credits](#credits)
|
||||
|
||||
## Basic instructions
|
||||
|
||||
`[±]α[*ℤ or !][solid or joint sign, or its combinations][-][ℕ, B or T][length modifier][* or !]` - full length template instruction for the **sparse** infill
|
||||
## Quick examples
|
||||
|
||||
`[±]α[*ℤ or !][joint sign][-][ℕ][* or !]` - full length template instruction for the **solid** infill
|
||||
- `0` - fixed direction at 0° (X-axis)
|
||||
- `0, 90` - alternate 0° and 90° each layer
|
||||
- `0, 15, 30` - alternate 0°, 15° and 30° each layer
|
||||
- `+90` - rotate 90° each layer (sequence 90°, 180°, 270°, 0° ...)
|
||||
- `+45` - rotate 45° each layer for higher dispersion
|
||||
- `+30/50%` - linearly rotate by 30° over the next 50% of model height
|
||||
- `+45/10#` - linearly rotate by 45° across 10 standard layers
|
||||
- `+15#10` - keep the same angle for 10 layers, then rotate +15°; repeats every 10 layers
|
||||
- `B!, +30` - skip the first bottom shell layers from rotation, then rotate 30° per layer
|
||||
- `0, +30, +90` - use a repeating sequence of 0°, +30°, +90°
|
||||
|
||||
`[±]α[*ℤ or !][joint sign, or its combinations][-][ℕ, B or T][length modifier][* or !]` - full length template instruction for the **sparse** infill
|
||||
|
||||
`[±]α*` - just setting an initial rotation angle
|
||||
|
||||
`[solid or joint sign]ℕ[!]` - putting the solid layers ℕ times without them rotating
|
||||
|
||||
`[B or T][!]` - putting the solid layers according to the number of bottom or top shell layers without them rotating
|
||||
|
||||
|
||||
> [!NOTE]
|
||||
> `[...]` - values in square brackets are optional
|
||||
@@ -50,15 +58,7 @@ This metalanguage provides a way to define the [direction and rotation](strength
|
||||
- `*ℤ` - repeat the instruction ℤ times
|
||||
- `!` - the one-time running instruction
|
||||
|
||||
### Solid sign
|
||||
|
||||
`[solid sign]` - the mark for inserting a solid layer:
|
||||
|
||||
- `D` - insert native sparse patterned layer but with 100% density
|
||||
- `S` - insert user-defined solid layer
|
||||
- `O` - insert Concentric solid layer
|
||||
- `M` - insert Monotonic solid layer
|
||||
- `R` - insert Rectilinear solid layer
|
||||
|
||||
|
||||
### Joint sign
|
||||
|
||||
@@ -132,7 +132,7 @@ For more complex instructions, autoformatting is used to make the template easie
|
||||
|
||||
### Simple absolute instructions
|
||||
|
||||
They include a simple definition of the angle for each layer. Note that the initial setting of this angle is also affected by the value in the sparse or solid infill angle field.
|
||||
They include a simple definition of the angle for each layer. Note that the initial setting of this angle is also affected by the value in the infill angle field.
|
||||
|
||||
- `0`, `15`, `45.5`, `256.5605`... - just fill at the existing angle. The initial direction starts at the X-axis, and the acceptable range of values is from 0 to 360
|
||||
- `0` as well as `+0`, `-0` or just empty template
|
||||
@@ -190,22 +190,12 @@ It is important to know that this will not be the exact length, but will be tied
|
||||
|
||||
### Constant layer number instructions
|
||||
|
||||
There are 2 letter signs `T` and `B` that can determine the number of shell layers on the top and bottom of the model. It is useful for calculating skipping this amount to align the fill, or inserting the required number of horizontal solid bulkheads.
|
||||
There are 2 letter signs `T` and `B` that can determine the number of shell layers on the top and bottom of the model. It is useful for calculating skipping this amount to align the fill.
|
||||
|
||||
- `B!, +30` - skip the first shell layers from rotation, then fill with 30 degree turn each layer
|
||||
- `+30/1cm, T` - rotate one centimeter of infill linearly at a 30 degree angle, then skip the number of layers equal to the count of the upper shell layers without rotation.
|
||||
|
||||
### Solid layers into sparse infill instructions
|
||||
|
||||
The following instructions allow you to embed solid layers in a sparse fill. The following commands are available `D` `S` `O` `M` `R`. For their purpose, see [solid sign](#solid-sign).
|
||||
|
||||
It is possible to combine them with the rotation method and layer number constant - `DT` `S/` `M#` `OB`...
|
||||
|
||||
- `#14, +15R` - put 14 layers of sparse infill then put one rectilinear layer of solid infill with 15 degree turn
|
||||

|
||||
- `B!, 240M3, #25` - skip the first shell layers from rotation, fill model with 3 solid monotonic layers at 240 degrees, then put 25 sparse layers at the same angle
|
||||
- `+30/1cm, ST` - rotate one centimeter of infill linearly at 30 degrees, then put solid layers equal to the count of the top shell layers
|
||||
- `+30M3` or `+90M/3` - fill whole model with solid infill with 30 degree turn at each layer
|
||||
|
||||
|
||||
## Complex template examples
|
||||
|
||||
|
||||
@@ -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 ®ion_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 = ®ion_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();
|
||||
|
||||
@@ -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");
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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");
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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");
|
||||
|
||||
Reference in New Issue
Block a user