Perimeter loop:

- change seam function, old behavior for nearest is now on "hiding", nearest now prioritize the nearest heavily.
 - add lopping_peri seam option : rear => it put the externals ones on -y and the internal ones on +y, if possible.
 - debug looping_peri max_width (from 0.8 to 1.4)
This commit is contained in:
supermerill 2018-11-14 20:03:22 +01:00
parent b8dca4cb9d
commit c1d46264ba
12 changed files with 189 additions and 72 deletions

View File

@ -1934,7 +1934,7 @@ std::string GCode::extrude_loop(ExtrusionLoop loop, std::string description, dou
Point last_pos = this->last_pos();
if (m_config.spiral_vase) {
loop.split_at(last_pos, false);
} else if (seam_position == spNearest || seam_position == spAligned || seam_position == spRear) {
} else if (seam_position == spNearest || seam_position == spAligned || seam_position == spRear || seam_position == spHidden) {
Polygon polygon = loop.polygon();
const coordf_t nozzle_dmr = EXTRUDER_CONFIG(nozzle_diameter);
const coord_t nozzle_r = coord_t(scale_(0.5 * nozzle_dmr) + 0.5);
@ -1949,11 +1949,18 @@ std::string GCode::extrude_loop(ExtrusionLoop loop, std::string description, dou
last_pos_weight = 1.f;
}
break;
case spNearest:
last_pos_weight = 5.f;
break;
case spRear:
last_pos = m_layer->object()->bounding_box().center();
last_pos.y += coord_t(3. * m_layer->object()->bounding_box().radius());
last_pos_weight = 5.f;
break;
case spHidden:
last_pos_weight = 0.1f;
break;
}
// Insert a projection of last_pos into the polygon.
@ -1975,7 +1982,16 @@ std::string GCode::extrude_loop(ExtrusionLoop loop, std::string description, dou
const float penaltySeam = 1.3f;
const float penaltyOverhangHalf = 10.f;
// Penalty for visible seams.
float dist_max = 0.1f * lengths.back();// 5.f * nozzle_dmr
if (this->config().seam_travel) {
dist_max = 0;
for (size_t i = 0; i < polygon.points.size(); ++i) {
dist_max = std::max(dist_max, (float)polygon.points[i].distance_to(last_pos_proj));
}
}
//TODO: ignore the angle penalty if the new point is not in an external path (bot/top/ext_peri)
for (size_t i = 0; i < polygon.points.size(); ++i) {
//std::cout << "check point @" << unscale(polygon.points[i].x) << ":" << unscale(polygon.points[i].y);
float ccwAngle = penalties[i];
if (was_clockwise)
ccwAngle = - ccwAngle;
@ -1996,13 +2012,15 @@ std::string GCode::extrude_loop(ExtrusionLoop loop, std::string description, dou
// Interpolate penalty between maximum and the penalty for a convex vertex.
penalty = penaltyConvexVertex + (penaltyFlatSurface - penaltyConvexVertex) * bspline_kernel(ccwAngle * float(PI * 2. / 3.));
}
if (this->config().seam_travel) {
penalty += last_pos_weight * polygon.points[i].distance_to(last_pos_proj) / dist_max;
}else{
// Give a negative penalty for points close to the last point or the prefered seam location.
//float dist_to_last_pos_proj = last_pos_proj.distance_to(polygon.points[i]);
float dist_to_last_pos_proj = (i < last_pos_proj_idx) ?
std::min(lengths[last_pos_proj_idx] - lengths[i], lengths.back() - lengths[last_pos_proj_idx] + lengths[i]) :
std::min(lengths[i] - lengths[last_pos_proj_idx], lengths.back() - lengths[i] + lengths[last_pos_proj_idx]);
float dist_max = 0.1f * lengths.back(); // 5.f * nozzle_dmr
penalty -= last_pos_weight * bspline_kernel(dist_to_last_pos_proj / dist_max);
}
penalties[i] = std::max(0.f, penalty);
}

View File

@ -45,9 +45,9 @@ public:
int idx = -1;
if (! this->points.empty()) {
idx = 0;
double dist_min = this->points.front().distance_to(point);
double dist_min = this->points.front().distance_to_sq(point);
for (int i = 1; i < int(this->points.size()); ++ i) {
double d = this->points[i].distance_to(point);
double d = this->points[i].distance_to_sq(point);
if (d < dist_min) {
dist_min = d;
idx = i;

View File

@ -644,10 +644,10 @@ ExtrusionEntityCollection PerimeterGenerator::_traverse_loops(
}
PerimeterIntersectionPoint
get_nearest_point(const PerimeterGeneratorLoops &children, ExtrusionLoop &myPolylines, const coord_t dist_cut, const coord_t max_dist) {
PerimeterGenerator::_get_nearest_point(const PerimeterGeneratorLoops &children, ExtrusionLoop &myPolylines, const coord_t dist_cut, const coord_t max_dist) const {
//find best points of intersections
PerimeterIntersectionPoint intersect;
intersect.distance = 0x7FFFFFFF;
intersect.distance = 0x7FFFFFFF; // ! assumption on intersect type & max value
intersect.idx_polyline_outter = -1;
intersect.idx_children = -1;
for (size_t idx_child = 0; idx_child < children.size(); idx_child++) {
@ -656,24 +656,55 @@ get_nearest_point(const PerimeterGeneratorLoops &children, ExtrusionLoop &myPoly
if (myPolylines.paths[idx_poly].extruder_id == (unsigned int)-1) continue;
if (myPolylines.paths[idx_poly].length() < dist_cut + SCALED_RESOLUTION) continue;
if ((myPolylines.paths[idx_poly].role() == erExternalPerimeter || child.is_external() )
&& this->object_config->seam_position.value != SeamPosition::spRandom) {
//first, try to find 2 point near enough
for (size_t idx_point = 0; idx_point < myPolylines.paths[idx_poly].polyline.points.size(); idx_point++) {
const Point &p = myPolylines.paths[idx_poly].polyline.points[idx_point];
const Point &nearest_p = *child.polygon.closest_point(p);
const coord_t dist = (coord_t)nearest_p.distance_to(p);
if (dist + SCALED_EPSILON / 2 < intersect.distance) {
const double dist = nearest_p.distance_to(p);
//Try to find a point in the far side, aligning them
if (dist + dist_cut / 20 < intersect.distance ||
(config->perimeter_loop_seam.value == spRear && (intersect.idx_polyline_outter <0 || p.y > intersect.outter_best.y)
&& dist <= max_dist && intersect.distance + dist_cut / 20)) {
//ok, copy the idx
intersect.distance = dist;
intersect.distance = (coord_t)nearest_p.distance_to(p);
intersect.idx_children = idx_child;
intersect.idx_polyline_outter = idx_poly;
intersect.outter_best = p;
intersect.child_best = nearest_p;
}
}
} else {
//first, try to find 2 point near enough
for (size_t idx_point = 0; idx_point < myPolylines.paths[idx_poly].polyline.points.size(); idx_point++) {
const Point &p = myPolylines.paths[idx_poly].polyline.points[idx_point];
const Point &nearest_p = *child.polygon.closest_point(p);
const double dist = nearest_p.distance_to(p);
if (dist + SCALED_EPSILON < intersect.distance ||
(config->perimeter_loop_seam.value == spRear && (intersect.idx_polyline_outter<0 || p.y < intersect.outter_best.y)
&& dist <= max_dist && intersect.distance + dist_cut / 20)) {
//ok, copy the idx
intersect.distance = (coord_t)nearest_p.distance_to(p);
intersect.idx_children = idx_child;
intersect.idx_polyline_outter = idx_poly;
intersect.outter_best = p;
intersect.child_best = nearest_p;
}
}
}
}
}
if (intersect.distance <= max_dist) {
return intersect;
}
for (size_t idx_child = 0; idx_child < children.size(); idx_child++) {
const PerimeterGeneratorLoop &child = children[idx_child];
for (size_t idx_poly = 0; idx_poly < myPolylines.paths.size(); idx_poly++) {
if (myPolylines.paths[idx_poly].extruder_id == (unsigned int)-1) continue;
if (myPolylines.paths[idx_poly].length() < dist_cut + SCALED_RESOLUTION) continue;
//second, try to check from one of my points
//don't check the last point, as it's used to go outter, can't use it to go inner.
for (size_t idx_point = 1; idx_point < myPolylines.paths[idx_poly].polyline.points.size()-1; idx_point++) {
@ -692,9 +723,18 @@ get_nearest_point(const PerimeterGeneratorLoops &children, ExtrusionLoop &myPoly
intersect.child_best = nearest_p;
}
}
}
}
if (intersect.distance <= max_dist) {
return intersect;
}
for (size_t idx_child = 0; idx_child < children.size(); idx_child++) {
const PerimeterGeneratorLoop &child = children[idx_child];
for (size_t idx_poly = 0; idx_poly < myPolylines.paths.size(); idx_poly++) {
if (myPolylines.paths[idx_poly].extruder_id == (unsigned int)-1) continue;
if (myPolylines.paths[idx_poly].length() < dist_cut + SCALED_RESOLUTION) continue;
//lastly, try to check from one of his points
for (size_t idx_point = 0; idx_point < child.polygon.points.size(); idx_point++) {
const Point &p = child.polygon.points[idx_point];
@ -898,17 +938,14 @@ PerimeterGenerator::_traverse_and_join_loops(const PerimeterGeneratorLoop &loop,
//TODO change this->external_perimeter_flow.scaled_width() if it's the first one!
const coord_t max_width_extrusion = this->perimeter_flow.scaled_width();
ExtrusionLoop my_loop = _extrude_and_cut_loop(loop, entry_point);
vector<bool> path_is_ccw;
for (size_t idx_poly = 0; idx_poly < my_loop.paths.size(); idx_poly++) {
path_is_ccw.push_back(true);
}
int child_idx = 0;
//Polylines myPolylines = { myPolyline };
//iterate on each point ot find the best place to go into the child
vector<PerimeterGeneratorLoop> childs = children;
while (!childs.empty()) {
PerimeterIntersectionPoint nearest = get_nearest_point(childs, my_loop, this->perimeter_flow.scaled_width(), this->perimeter_flow.scaled_width()* 0.8);
child_idx++;
PerimeterIntersectionPoint nearest = this->_get_nearest_point(childs, my_loop, this->perimeter_flow.scaled_width(), this->perimeter_flow.scaled_width()* 1.42);
if (nearest.idx_children == (size_t)-1) {
//return ExtrusionEntityCollection();
break;
@ -920,9 +957,7 @@ PerimeterGenerator::_traverse_and_join_loops(const PerimeterGeneratorLoop &loop,
//PerimeterGeneratorLoops less_childs = childs;
//less_childs.erase(less_childs.begin() + nearest.idx_children);
//create new node with recursive ask for the inner perimeter & COPY of the points, ready to be cut
const bool cut_path_is_ccw = path_is_ccw[nearest.idx_polyline_outter];
my_loop.paths.insert(my_loop.paths.begin() + nearest.idx_polyline_outter + 1, my_loop.paths[nearest.idx_polyline_outter]);
path_is_ccw.insert(path_is_ccw.begin() + nearest.idx_polyline_outter + 1, cut_path_is_ccw);
ExtrusionPath *outer_start = &my_loop.paths[nearest.idx_polyline_outter];
ExtrusionPath *outer_end = &my_loop.paths[nearest.idx_polyline_outter + 1];
@ -981,7 +1016,6 @@ PerimeterGenerator::_traverse_and_join_loops(const PerimeterGeneratorLoop &loop,
const size_t child_paths_size = child_loop.paths.size();
if (child_paths_size == 0) continue;
my_loop.paths.insert(my_loop.paths.begin() + nearest.idx_polyline_outter + 1, child_loop.paths.begin(), child_loop.paths.end());
for (size_t i = 0; i < child_paths_size; i++) path_is_ccw.insert(path_is_ccw.begin() + nearest.idx_polyline_outter + 1, !cut_path_is_ccw);
//add paths into my_loop => need to re-get the refs
outer_start = &my_loop.paths[nearest.idx_polyline_outter];
@ -1005,8 +1039,8 @@ PerimeterGenerator::_traverse_and_join_loops(const PerimeterGeneratorLoop &loop,
else
inner_start->polyline.clip_start(inner_start->polyline.length()/2);
} else {
coord_t length_poly_1 = outer_start->polyline.length();
coord_t length_poly_2 = outer_end->polyline.length();
double length_poly_1 = outer_start->polyline.length();
double length_poly_2 = outer_end->polyline.length();
coord_t length_trim_1 = outer_start_spacing / 2;
coord_t length_trim_2 = outer_end_spacing / 2;
if (length_poly_1 < length_trim_1) {
@ -1065,9 +1099,9 @@ PerimeterGenerator::_traverse_and_join_loops(const PerimeterGeneratorLoop &loop,
my_loop.paths[idx].reverse();
}
outer_start = &my_loop.paths[nearest.idx_polyline_outter];
outer_end = &my_loop.paths[nearest.idx_polyline_outter + child_paths_size + 1];
inner_start = &my_loop.paths[nearest.idx_polyline_outter + 1];
inner_end = &my_loop.paths[nearest.idx_polyline_outter + child_paths_size];
outer_end = &my_loop.paths[nearest.idx_polyline_outter + child_paths_size + 1];
}
}
@ -1152,7 +1186,8 @@ PerimeterGenerator::_traverse_and_join_loops(const PerimeterGeneratorLoop &loop,
travel_path_end[0].polyline.append(outer_end->polyline.points.front());
}
//check if we add path or reuse bits
if (outer_start->polyline.points.size() == 1) {
//FIXME
/*if (outer_start->polyline.points.size() == 1) {
outer_start->polyline = travel_path_begin.front().polyline;
travel_path_begin.erase(travel_path_begin.begin());
outer_start->extruder_id = -1;
@ -1160,15 +1195,13 @@ PerimeterGenerator::_traverse_and_join_loops(const PerimeterGeneratorLoop &loop,
outer_end->polyline = travel_path_end.back().polyline;
travel_path_end.erase(travel_path_end.end() - 1);
outer_end->extruder_id = -1;
}
}*/
//add paths into my_loop => after that all ref are wrong!
for (int i = travel_path_end.size() - 1; i >= 0; i--) {
my_loop.paths.insert(my_loop.paths.begin() + nearest.idx_polyline_outter + child_paths_size + 1, travel_path_end[i]);
path_is_ccw.insert(path_is_ccw.begin() + nearest.idx_polyline_outter + child_paths_size + 1, cut_path_is_ccw);
}
for (int i = travel_path_begin.size() - 1; i >= 0; i--) {
my_loop.paths.insert(my_loop.paths.begin() + nearest.idx_polyline_outter + 1, travel_path_begin[i]);
path_is_ccw.insert(path_is_ccw.begin() + nearest.idx_polyline_outter + 1, cut_path_is_ccw);
}
}

View File

@ -103,6 +103,7 @@ private:
ThickPolylines &thin_walls) const;
ExtrusionLoop _traverse_and_join_loops(const PerimeterGeneratorLoop &loop, const PerimeterGeneratorLoops &childs, const Point entryPoint) const;
ExtrusionLoop _extrude_and_cut_loop(const PerimeterGeneratorLoop &loop, const Point entryPoint, const Line &direction = Line(Point(0,0),Point(0,0))) const;
PerimeterIntersectionPoint _get_nearest_point(const PerimeterGeneratorLoops &children, ExtrusionLoop &myPolylines, const coord_t dist_cut, const coord_t max_dist) const;
ExtrusionEntityCollection _variable_width
(const ThickPolylines &polylines, ExtrusionRole role, Flow flow) const;
};

View File

@ -400,13 +400,25 @@ PrintConfigDef::PrintConfigDef()
def->default_value = new ConfigOptionBool(false);
def = this->add("perimeter_loop", coBool);
def->label = L("Looping perimeters");
def->label = L(" ");
def->category = L("Layers and Perimeters");
def->tooltip = L("Join the perimeters to create only one continuous extrusion without any z-hop."
" Long inside travel (from external to holes) are not extruded to give some place to the infill.");
def->cli = "loop-perimeter!";
def->default_value = new ConfigOptionBool(false);
def = this->add("perimeter_loop_seam", coEnum);
def->label = L("Seam position");
def->category = L("Layers and Perimeters");
def->tooltip = L("Position of perimeters starting points.");
def->cli = "perimeter-seam-position=s";
def->enum_keys_map = &ConfigOptionEnum<SeamPosition>::get_enum_values();
def->enum_values.push_back("nearest");
def->enum_values.push_back("rear");
def->enum_labels.push_back(L("Nearest"));
def->enum_labels.push_back(L("Rear"));
def->default_value = new ConfigOptionEnum<SeamPosition>(spRear);
def = this->add("extra_perimeters", coBool);
def->label = L("Extra perimeters if needed");
def->category = L("Layers and Perimeters");
@ -1600,12 +1612,21 @@ PrintConfigDef::PrintConfigDef()
def->enum_values.push_back("nearest");
def->enum_values.push_back("aligned");
def->enum_values.push_back("rear");
def->enum_values.push_back("hidden");
def->enum_labels.push_back(L("Random"));
def->enum_labels.push_back(L("Nearest"));
def->enum_labels.push_back(L("Aligned"));
def->enum_labels.push_back(L("Rear"));
def->enum_labels.push_back(L("Hidden"));
def->default_value = new ConfigOptionEnum<SeamPosition>(spAligned);
def = this->add("seam_travel", coBool);
def->label = L("Travel move reduced");
def->category = L("Layers and Perimeters");
def->tooltip = L("Add a big cost to travel paths when possible (when going into a loop), so it will prefer a less optimal seam posistion if it's nearer.");
def->cli = "seam-travel!";
def->default_value = new ConfigOptionBool(false);
#if 0
def = this->add("seam_preferred_direction", coFloat);
// def->gui_type = "slider";

View File

@ -42,7 +42,7 @@ enum SupportMaterialPattern {
};
enum SeamPosition {
spRandom, spNearest, spAligned, spRear
spRandom, spNearest, spAligned, spRear, spHidden
};
enum FilamentType {
@ -120,6 +120,7 @@ template<> inline t_config_enum_values& ConfigOptionEnum<SeamPosition>::get_enum
keys_map["nearest"] = spNearest;
keys_map["aligned"] = spAligned;
keys_map["rear"] = spRear;
keys_map["hidden"] = spHidden;
}
return keys_map;
}
@ -350,6 +351,7 @@ public:
ConfigOptionFloat layer_height;
ConfigOptionInt raft_layers;
ConfigOptionEnum<SeamPosition> seam_position;
ConfigOptionBool seam_travel;
// ConfigOptionFloat seam_preferred_direction;
// ConfigOptionFloat seam_preferred_direction_jitter;
ConfigOptionBool support_material;
@ -395,6 +397,7 @@ protected:
OPT_PTR(layer_height);
OPT_PTR(raft_layers);
OPT_PTR(seam_position);
OPT_PTR(seam_travel);
// OPT_PTR(seam_preferred_direction);
// OPT_PTR(seam_preferred_direction_jitter);
OPT_PTR(support_material);
@ -444,6 +447,7 @@ public:
ConfigOptionFloatOrPercent external_perimeter_speed;
ConfigOptionBool external_perimeters_first;
ConfigOptionBool perimeter_loop;
ConfigOptionEnum<SeamPosition> perimeter_loop_seam;
ConfigOptionBool extra_perimeters;
ConfigOptionBool only_one_perimeter_top;
ConfigOptionFloat fill_angle;
@ -499,6 +503,7 @@ protected:
OPT_PTR(external_perimeter_speed);
OPT_PTR(external_perimeters_first);
OPT_PTR(perimeter_loop);
OPT_PTR(perimeter_loop_seam);
OPT_PTR(extra_perimeters);
OPT_PTR(only_one_perimeter_top);
OPT_PTR(fill_angle);

View File

@ -157,6 +157,7 @@ bool PrintObject::invalidate_state_by_config_options(const std::vector<t_config_
|| opt_key == "thin_walls"
|| opt_key == "external_perimeters_first"
|| opt_key == "perimeter_loop"
|| opt_key == "perimeter_loop_seam"
|| opt_key == "no_perimeter_unsupported"
|| opt_key == "min_perimeter_unsupported"
|| opt_key == "noperi_bridge_only") {
@ -241,6 +242,7 @@ bool PrintObject::invalidate_state_by_config_options(const std::vector<t_config_
steps.emplace_back(posInfill);
} else if (
opt_key == "seam_position"
|| opt_key == "seam_travel"
|| opt_key == "seam_preferred_direction"
|| opt_key == "seam_preferred_direction_jitter"
|| opt_key == "support_material_speed"

View File

@ -527,6 +527,27 @@ void Choice::set_value(const boost::any& value, bool change_event)
}
else
val = 0;
} else if (m_opt_id.compare("perimeter_loop_seam") == 0) {
if (!m_opt.enum_values.empty()) {
std::string key;
t_config_enum_values map_names = ConfigOptionEnum<SeamPosition>::get_enum_values();
for (auto it : map_names) {
if (val == it.second) {
key = it.first;
break;
}
}
size_t idx = 0;
for (auto el : m_opt.enum_values) {
if (el.compare(key) == 0)
break;
++idx;
}
val = idx == m_opt.enum_values.size() ? 0 : idx;
} else
val = 3;
}
dynamic_cast<wxComboBox*>(window)->SetSelection(val);
break;
@ -591,7 +612,15 @@ boost::any& Choice::get_value()
m_value = static_cast<SupportMaterialPattern>(ret_enum);
else if (m_opt_id.compare("seam_position") == 0)
m_value = static_cast<SeamPosition>(ret_enum);
else if (m_opt_id.compare("host_type") == 0)
else if (m_opt_id.compare("perimeter_loop_seam") == 0) {
if (!m_opt.enum_values.empty()) {
std::string key = m_opt.enum_values[ret_enum];
t_config_enum_values map_names = ConfigOptionEnum<SeamPosition>::get_enum_values();
int value = map_names.at(key);
m_value = static_cast<SeamPosition>(value);
} else
m_value = static_cast<SeamPosition>(3);
} else if (m_opt_id.compare("host_type") == 0)
m_value = static_cast<PrintHostType>(ret_enum);
else if (m_opt_id.compare("infill_dense_algo") == 0)
m_value = static_cast<DenseInfillAlgo>(ret_enum);

View File

@ -603,7 +603,7 @@ void change_opt_value(DynamicPrintConfig& config, const t_config_option_key& opt
config.set_key_value(opt_key, new ConfigOptionEnum<GCodeFlavor>(boost::any_cast<GCodeFlavor>(value)));
else if (opt_key.compare("support_material_pattern") == 0)
config.set_key_value(opt_key, new ConfigOptionEnum<SupportMaterialPattern>(boost::any_cast<SupportMaterialPattern>(value)));
else if (opt_key.compare("seam_position") == 0)
else if (opt_key.compare("seam_position") == 0 || opt_key.compare("perimeter_loop_seam") == 0)
config.set_key_value(opt_key, new ConfigOptionEnum<SeamPosition>(boost::any_cast<SeamPosition>(value)));
else if (opt_key.compare("host_type") == 0)
config.set_key_value(opt_key, new ConfigOptionEnum<PrintHostType>(boost::any_cast<PrintHostType>(value)));

View File

@ -459,7 +459,7 @@ boost::any ConfigOptionsGroup::get_config_value(const DynamicPrintConfig& config
else if (opt_key.compare("support_material_pattern") == 0){
ret = static_cast<int>(config.option<ConfigOptionEnum<SupportMaterialPattern>>(opt_key)->value);
}
else if (opt_key.compare("seam_position") == 0){
else if (opt_key.compare("seam_position") == 0 || opt_key.compare("perimeter_loop_seam") == 0) {
ret = static_cast<int>(config.option<ConfigOptionEnum<SeamPosition>>(opt_key)->value);
}
else if (opt_key.compare("host_type") == 0){

View File

@ -307,7 +307,7 @@ const std::vector<std::string>& Preset::print_options()
"wipe_tower", "wipe_tower_x", "wipe_tower_y", "wipe_tower_width", "wipe_tower_rotation_angle", "wipe_tower_bridging",
"only_one_perimeter_top", "single_extruder_multi_material_priming", "compatible_printers", "compatible_printers_condition", "inherits",
"infill_dense", "infill_dense_algo", "no_perimeter_unsupported", "min_perimeter_unsupported", "noperi_bridge_only",
"support_material_solid_first_layer", "perimeter_loop"
"support_material_solid_first_layer", "perimeter_loop", "perimeter_loop_seam", "seam_travel"
};
return s_opts;
}

View File

@ -819,9 +819,15 @@ void TabPrint::build()
optgroup->append_line(line);
optgroup = page->new_optgroup(_(L("Advanced")));
optgroup->append_single_option_line("seam_position");
line = { _(L("Avoid unsupported perimeters")), "" };
line.append_option(optgroup->get_option("seam_position"));
line.append_option(optgroup->get_option("seam_travel"));
optgroup->append_line(line);
optgroup->append_single_option_line("external_perimeters_first");
optgroup->append_single_option_line("perimeter_loop");
line = { _(L("Looping perimeter")), "" };
line.append_option(optgroup->get_option("perimeter_loop"));
line.append_option(optgroup->get_option("perimeter_loop_seam"));
optgroup->append_line(line);
page = add_options_page(_(L("Infill")), "infill.png");
optgroup = page->new_optgroup(_(L("Infill")));
@ -1170,9 +1176,11 @@ void TabPrint::update()
bool have_perimeters = m_config->opt_int("perimeters") > 0;
for (auto el : { "extra_perimeters", "only_one_perimeter_top", "ensure_vertical_shell_thickness", "thin_walls", "overhangs",
"seam_position", "external_perimeters_first", "external_perimeter_extrusion_width",
"perimeter_speed", "small_perimeter_speed", "external_perimeter_speed", "perimeter_loop" })
"perimeter_speed", "small_perimeter_speed", "external_perimeter_speed", "perimeter_loop", "perimeter_loop_seam" })
get_field(el)->toggle(have_perimeters);
get_field("perimeter_loop_seam")->toggle(m_config->opt_bool("perimeter_loop"));
bool have_no_perimeter_unsupported = have_perimeters && m_config->opt_bool("no_perimeter_unsupported");
for (auto el : { "min_perimeter_unsupported", "noperi_bridge_only" })
get_field(el)->toggle(have_no_perimeter_unsupported);