Don't print totally unsupported perimeters.

- remove outer overhangs (via a intersection with convection hull)
 - add bridge-able area detection (reuse the BridgeDetector but it's a bit wonky with overhangs and not very smooth)
 - add gui switchs
This commit is contained in:
supermerill 2018-07-09 20:01:40 +02:00
parent 586b38f0b2
commit 18f926cfc4
11 changed files with 291 additions and 66 deletions

View File

@ -203,8 +203,8 @@ std::vector<double> BridgeDetector::bridge_direction_candidates() const
return angles;
}
Polygons BridgeDetector::coverage(double angle) const
{
Polygons BridgeDetector::coverage(double angle, bool precise) const {
if (angle == -1)
angle = this->angle;
@ -213,52 +213,92 @@ Polygons BridgeDetector::coverage(double angle) const
if (angle != -1) {
// Get anchors, convert them to Polygons and rotate them.
Polygons anchors = to_polygons(this->_anchor_regions);
polygons_rotate(anchors, PI/2.0 - angle);
polygons_rotate(anchors, PI / 2.0 - angle);
//same for region which do not need bridging
//Polygons supported_area = diff(this->lower_slices.expolygons, this->_anchor_regions, true);
//polygons_rotate(anchors, PI / 2.0 - angle);
for (ExPolygon expolygon : this->expolygons) {
// Clone our expolygon and rotate it so that we work with vertical lines.
expolygon.rotate(PI/2.0 - angle);
expolygon.rotate(PI / 2.0 - angle);
// Outset the bridge expolygon by half the amount we used for detecting anchors;
// we'll use this one to generate our trapezoids and be sure that their vertices
// are inside the anchors and not on their contours leading to false negatives.
for (ExPolygon &expoly : offset_ex(expolygon, 0.5f * float(this->spacing))) {
// Compute trapezoids according to a vertical orientation
Polygons trapezoids;
expoly.get_trapezoids2(&trapezoids, PI/2.0);
for (const Polygon &trapezoid : trapezoids) {
if (!precise) expoly.get_trapezoids2(&trapezoids, PI / 2);
else expoly.get_trapezoids3_half(&trapezoids, float(this->spacing));
for (Polygon &trapezoid : trapezoids) {
// not nice, we need a more robust non-numeric check
// imporvment 1: take into account when we go in the supported area.
size_t n_supported = 0;
for (const Line &supported_line : intersection_ln(trapezoid.lines(), anchors))
if (supported_line.length() >= this->spacing)
++ n_supported;
if (n_supported >= 2)
if (!precise) {
for (const Line &supported_line : intersection_ln(trapezoid.lines(), anchors))
if (supported_line.length() >= this->spacing)
++n_supported;
} else {
Polygons intersects = intersection(trapezoid, anchors);
n_supported = intersects.size();
if (n_supported >= 2) {
// trim it to not allow to go outside of the intersections
BoundingBox center_bound = intersects[0].bounding_box();
coord_t min_y = center_bound.center().y, max_y = center_bound.center().y;
for (Polygon &poly_bound : intersects) {
center_bound = poly_bound.bounding_box();
if (min_y > center_bound.center().y) min_y = center_bound.center().y;
if (max_y < center_bound.center().y) max_y = center_bound.center().y;
}
coord_t min_x = trapezoid[0].x, max_x = trapezoid[0].x;
for (Point &p : trapezoid.points) {
if (min_x > p.x) min_x = p.x;
if (max_x < p.x) max_x = p.x;
}
//add what get_trapezoids3 has removed (+EPSILON)
min_x -= (this->spacing / 4 + 1);
max_x += (this->spacing / 4 + 1);
coord_t mid_x = (min_x + max_x) / 2;
for (Point &p : trapezoid.points) {
if (p.y < min_y) p.y = min_y;
if (p.y > max_y) p.y = max_y;
if (p.x > min_x && p.x < mid_x) p.x = min_x;
if (p.x < max_x && p.x > mid_x) p.x = max_x;
}
}
}
if (n_supported >= 2) {
//add it
covered.push_back(std::move(trapezoid));
}
}
}
}
// Unite the trapezoids before rotation, as the rotation creates tiny gaps and intersections between the trapezoids
// instead of exact overlaps.
covered = union_(covered);
// Intersect trapezoids with actual bridge area to remove extra margins and append it to result.
polygons_rotate(covered, -(PI/2.0 - angle));
covered = intersection(covered, to_polygons(this->expolygons));
// Unite the trapezoids before rotation, as the rotation creates tiny gaps and intersections between the trapezoids
// instead of exact overlaps.
covered = union_(covered);
// Intersect trapezoids with actual bridge area to remove extra margins and append it to result.
polygons_rotate(covered, -(PI/2.0 - angle));
covered = intersection(covered, to_polygons(this->expolygons));
#if 0
{
my @lines = map @{$_->lines}, @$trapezoids;
$_->rotate(-(PI/2 - $angle), [0,0]) for @lines;
require "Slic3r/SVG.pm";
Slic3r::SVG::output(
"coverage_" . rad2deg($angle) . ".svg",
expolygons => [$self->expolygon],
green_expolygons => $self->_anchor_regions,
red_expolygons => $coverage,
lines => \@lines,
);
{
my @lines = map @{$_->lines}, @$trapezoids;
$_->rotate(-(PI/2 - $angle), [0,0]) for @lines;
require "Slic3r/SVG.pm";
Slic3r::SVG::output(
"coverage_" . rad2deg($angle) . ".svg",
expolygons => [$self->expolygon],
green_expolygons => $self->_anchor_regions,
red_expolygons => $coverage,
lines => \@lines,
);
}
#endif
}
return covered;
}

View File

@ -33,7 +33,7 @@ public:
BridgeDetector(const ExPolygons &_expolygons, const ExPolygonCollection &_lower_slices, coord_t _extrusion_width);
// If bridge_direction_override != 0, then the angle is used instead of auto-detect.
bool detect_angle(double bridge_direction_override = 0.);
Polygons coverage(double angle = -1) const;
Polygons coverage(double angle = -1, bool precise = false) const;
void unsupported_edges(double angle, Polylines* unsupported) const;
Polylines unsupported_edges(double angle = -1) const;
@ -54,7 +54,7 @@ private:
double coverage;
double max_length;
};
public:
// Get possible briging direction candidates.
std::vector<double> bridge_direction_candidates() const;

View File

@ -213,6 +213,14 @@ inline Slic3r::ExPolygons union_ex(const Slic3r::Surfaces &subject, bool safety_
return _clipper_ex(ClipperLib::ctUnion, to_polygons(subject), Slic3r::Polygons(), safety_offset_);
}
inline Slic3r::ExPolygons union_ex(const Slic3r::ExPolygons &subject1, const Slic3r::ExPolygons &subject2, bool safety_offset_ = false) {
Polygons poly_union;
polygons_append(poly_union, to_polygons(subject1));
polygons_append(poly_union, to_polygons(subject2));
return _clipper_ex(ClipperLib::ctUnion, poly_union, Slic3r::Polygons(), safety_offset_);
//OR that, i don't know what is the best
//return _clipper_ex(ClipperLib::ctUnion, to_polygons(subject1), to_polygons(subject2), safety_offset_);
}
ClipperLib::PolyTree union_pt(const Slic3r::Polygons &subject, bool safety_offset_ = false);
Slic3r::Polygons union_pt_chained(const Slic3r::Polygons &subject, bool safety_offset_ = false);

View File

@ -539,13 +539,59 @@ ExPolygon::get_trapezoids2(Polygons* polygons) const
}
void
ExPolygon::get_trapezoids2(Polygons* polygons, double angle) const
{
ExPolygon::get_trapezoids2(Polygons* polygons, double angle) const {
ExPolygon clone = *this;
clone.rotate(PI/2 - angle, Point(0,0));
clone.rotate(PI / 2 - angle, Point(0, 0));
clone.get_trapezoids2(polygons);
for (Polygons::iterator polygon = polygons->begin(); polygon != polygons->end(); ++polygon)
polygon->rotate(-(PI/2 - angle), Point(0,0));
polygon->rotate(-(PI / 2 - angle), Point(0, 0));
}
void
ExPolygon::get_trapezoids3_half(Polygons* polygons, float spacing) const {
// get all points of this ExPolygon
Points pp = *this;
if (pp.empty()) return;
// build our bounding box
BoundingBox bb(pp);
// get all x coordinates
int min_x = pp[0].x, max_x = pp[0].x;
std::vector<coord_t> xx;
for (Points::const_iterator p = pp.begin(); p != pp.end(); ++p) {
if (min_x > p->x) min_x = p->x;
if (max_x < p->x) max_x = p->x;
}
for (int x = min_x; x < max_x-spacing/2; x += spacing) {
xx.push_back(x);
}
xx.push_back(max_x);
//std::sort(xx.begin(), xx.end());
// find trapezoids by looping from first to next-to-last coordinate
for (std::vector<coord_t>::const_iterator x = xx.begin(); x != xx.end() - 1; ++x) {
coord_t next_x = *(x + 1);
if (*x == next_x) continue;
// build rectangle
Polygon poly;
poly.points.resize(4);
poly[0].x = *x +spacing / 4;
poly[0].y = bb.min.y;
poly[1].x = next_x -spacing / 4;
poly[1].y = bb.min.y;
poly[2].x = next_x -spacing / 4;
poly[2].y = bb.max.y;
poly[3].x = *x +spacing / 4;
poly[3].y = bb.max.y;
// intersect with this expolygon
// append results to return value
polygons_append(*polygons, intersection(poly, to_polygons(*this)));
}
}
// While this triangulates successfully, it's NOT a constrained triangulation

View File

@ -59,6 +59,7 @@ public:
void get_trapezoids(Polygons* polygons, double angle) const;
void get_trapezoids2(Polygons* polygons) const;
void get_trapezoids2(Polygons* polygons, double angle) const;
void get_trapezoids3_half(Polygons* polygons, float spacing) const;
void triangulate(Polygons* polygons) const;
void triangulate_pp(Polygons* polygons) const;
void triangulate_p2t(Polygons* polygons) const;

View File

@ -1,6 +1,8 @@
#include "PerimeterGenerator.hpp"
#include "ClipperUtils.hpp"
#include "ExtrusionEntityCollection.hpp"
#include "BridgeDetector.hpp"
#include "Geometry.hpp"
#include <cmath>
#include <cassert>
@ -49,16 +51,18 @@ void PerimeterGenerator::process()
// we need to process each island separately because we might have different
// extra perimeters for each one
int surface_idx = 0;
for (const Surface &surface : this->slices->surfaces) {
// detect how many perimeters must be generated for this island
int loop_number = this->config->perimeters + surface.extra_perimeters - 1; // 0-indexed loops
surface_idx++;
if (this->config->only_one_perimeter_top && this->upper_slices == NULL){
loop_number = 0;
}
ExPolygons gaps;
//this var store infill surface removed from last to not add any more perimeters to it.
ExPolygons stored;
ExPolygons last = union_ex(surface.expolygon.simplify_p(SCALED_RESOLUTION));
if (loop_number >= 0) {
@ -68,6 +72,83 @@ void PerimeterGenerator::process()
ThickPolylines thin_walls;
// we loop one time more than needed in order to find gaps after the last perimeter was applied
for (int i = 0;; ++ i) { // outer loop is 0
//store surface for bridge infill to avoid unsupported perimeters (but the first one, this one is always good)
if (this->config->no_perimeter_unsupported && i == this->config->min_perimeter_unsupported
&& this->lower_slices != NULL && !this->lower_slices->expolygons.empty()) {
//note: i don't know where to use the safety offset or not, so if you know, please modify the block.
//compute our unsupported surface
ExPolygons unsupported = diff_ex(last, this->lower_slices->expolygons, true);
if (!unsupported.empty()) {
ExPolygons to_draw;
//remove small overhangs
ExPolygons unsupported_filtered = offset2_ex(unsupported, -(float)(perimeter_spacing), (float)(perimeter_spacing));
if (!unsupported_filtered.empty()) {
//to_draw.insert(to_draw.end(), last.begin(), last.end());
//extract only the useful part of the lower layer. The safety offset is really needed here.
ExPolygons support = diff_ex(last, unsupported, true);
if (this->config->noperi_bridge_only && !unsupported.empty()) {
//only consider the part that can be bridged (really, by the bridge algorithm)
//first, separate into islands (ie, each ExPlolygon)
int numploy = 0;
//only consider the bottom layer that intersect unsupported, to be sure it's only on our island.
ExPolygonCollection lower_island(support);
BridgeDetector detector(unsupported_filtered,
lower_island,
perimeter_spacing);
if (detector.detect_angle(Geometry::deg2rad(this->config->bridge_angle.value))) {
ExPolygons bridgeable = union_ex(detector.coverage(-1, true));
if (!bridgeable.empty()) {
//simplify to avoid most of artefacts from printing lines.
ExPolygons bridgeable_simplified;
for (ExPolygon &poly : bridgeable) {
poly.simplify(perimeter_spacing/4, &bridgeable_simplified);
}
//offset by perimeter spacing because the simplify may have reduced it a bit.
//it's not dangerous as it will be intersected by 'unsupported' later
to_draw.insert(to_draw.end(), bridgeable.begin(), bridgeable.end());
// add overlap (perimeter_spacing/4 was good in test, ie 25%)
coord_t overlap = scale_(this->config->get_abs_value("infill_overlap", perimeter_spacing));
unsupported_filtered = intersection_ex(unsupported_filtered, offset_ex(bridgeable_simplified, overlap));
} else {
unsupported_filtered.clear();
}
} else {
unsupported_filtered.clear();
}
} else {
//only consider the part that can be 'bridged' (inside the convex hull)
// it's not as precise as the bridge detector, but it's better than nothing, and quicker.
ExPolygonCollection coll_last(support);
ExPolygon hull;
hull.contour = coll_last.convex_hull();
unsupported_filtered = intersection_ex(unsupported_filtered, ExPolygons() = { hull });
}
if (!unsupported_filtered.empty()) {
//to_draw.insert(to_draw.end(), detector._anchor_regions.begin(), detector._anchor_regions.end());
//and we want at least 1 perimeter of overlap
ExPolygons bridge = unsupported_filtered;
unsupported_filtered = intersection_ex(offset_ex(unsupported_filtered, (float)(perimeter_spacing)), last);
// unsupported need to be offset_ex by -(float)(perimeter_spacing/2) for the hole to be flush
ExPolygons supported = diff_ex(last, unsupported_filtered); //offset_ex(unsupported_filtered, -(float)(perimeter_spacing / 2)), true);
ExPolygons bridge_and_support = union_ex(bridge, support);
//to_draw.insert(to_draw.end(), support.begin(), support.end());
// make him flush with perimeter area
unsupported_filtered = intersection_ex(offset_ex(unsupported_filtered, (float)(perimeter_spacing / 2)), bridge_and_support);
// store the results
last = supported;
//add this directly to the infill list.
// this will avoid to throw wrong offsets into a good polygons
this->fill_surfaces->append(
unsupported_filtered,
stInternal);
}
}
}
}
// We can add more perimeters if there are uncovered overhangs
// improvement for future: find a way to add perimeters only where it's needed.
// It's hard to do, so here is a simple version.
@ -92,23 +173,25 @@ void PerimeterGenerator::process()
}
// Calculate next onion shell of perimeters.
ExPolygons offsets;
//this variable stored the nexyt onion
ExPolygons next_onion;
if (i == 0) {
// the minimum thickness of a single loop is:
// ext_width/2 + ext_spacing/2 + spacing/2 + width/2
offsets = this->config->thin_walls ?
next_onion = this->config->thin_walls ?
offset2_ex(
last,
-(ext_perimeter_width / 2 + ext_min_spacing / 2 - 1),
+(ext_min_spacing / 2 - 1)) :
offset_ex(last, - ext_perimeter_width / 2);
-(float)(ext_perimeter_width / 2 + ext_min_spacing / 2 - 1),
+(float)(ext_min_spacing / 2 - 1)) :
offset_ex(last, -(float)(ext_perimeter_width / 2));
// look for thin walls
if (this->config->thin_walls) {
// the following offset2 ensures almost nothing in @thin_walls is narrower than $min_width
// (actually, something larger than that still may exist due to mitering or other causes)
coord_t min_width = scale_(this->ext_perimeter_flow.nozzle_diameter / 3);
coord_t min_width = (coord_t)scale_(this->ext_perimeter_flow.nozzle_diameter / 3);
Polygons no_thin_zone = offset(offsets, (float)(ext_perimeter_width / 2));
Polygons no_thin_zone = offset(next_onion, (float)(ext_perimeter_width / 2));
ExPolygons expp = offset2_ex(
// medial axis requires non-overlapping geometry
diff_ex(to_polygons(last),
@ -118,7 +201,7 @@ void PerimeterGenerator::process()
// compute a bit of overlap to anchor thin walls inside the print.
ExPolygons anchor = intersection_ex(to_polygons(offset_ex(expp, (float)(ext_perimeter_width / 2))), no_thin_zone, true);
for (ExPolygon &ex : expp) {
ExPolygons bounds = _clipper_ex(ClipperLib::ctUnion, to_polygons(ex), to_polygons(anchor), true);
ExPolygons bounds = union_ex(ExPolygons() = { ex }, anchor, true);
for (ExPolygon &bound : bounds) {
if (!intersection_ex(ex, bound).empty()) {
// the maximum thickness of our thin wall area is equal to the minimum thickness of a single loop
@ -132,29 +215,32 @@ void PerimeterGenerator::process()
//FIXME Is this offset correct if the line width of the inner perimeters differs
// from the line width of the infill?
coord_t distance = (i == 1) ? ext_perimeter_spacing2 : perimeter_spacing;
offsets = this->config->thin_walls ?
if (this->config->thin_walls){
// This path will ensure, that the perimeters do not overfill, as in
// prusa3d/Slic3r GH #32, but with the cost of rounding the perimeters
// excessively, creating gaps, which then need to be filled in by the not very
// reliable gap fill algorithm.
// Also the offset2(perimeter, -x, x) may sometimes lead to a perimeter, which is larger than
// the original.
offset2_ex(last,
-(distance + min_spacing/2 - 1),
min_spacing / 2 - 1) :
next_onion = offset2_ex(last,
-(float)(distance + min_spacing / 2 - 1),
+(float)(min_spacing / 2 - 1));
} else {
// If "detect thin walls" is not enabled, this paths will be entered, which
// leads to overflows, as in prusa3d/Slic3r GH #32
offset_ex(last, - distance);
next_onion = offset_ex(last, -(float)(distance));
}
// look for gaps
if (this->config->gap_fill_speed.value > 0 && this->config->fill_density.value > 0)
// not using safety offset here would "detect" very narrow gaps
// (but still long enough to escape the area threshold) that gap fill
// won't be able to fill but we'd still remove from infill area
append(gaps, diff_ex(
offset(last, -0.5*distance),
offset(offsets, 0.5 * distance + 10))); // safety offset
}
if (offsets.empty()) {
offset(last, -0.5f*distance),
offset(next_onion, 0.5f * distance + 10))); // safety offset
}
if (next_onion.empty()) {
// Store the number of loops actually generated.
loop_number = i - 1;
// No region left to be filled in.
@ -170,7 +256,8 @@ void PerimeterGenerator::process()
break;
}
}
for (const ExPolygon &expolygon : offsets) {
for (const ExPolygon &expolygon : next_onion) {
contours[i].emplace_back(PerimeterGeneratorLoop(expolygon.contour, i, true));
if (! expolygon.holes.empty()) {
holes[i].reserve(holes[i].size() + expolygon.holes.size());
@ -178,23 +265,25 @@ void PerimeterGenerator::process()
holes[i].emplace_back(PerimeterGeneratorLoop(hole, i, false));
}
}
last = std::move(offsets);
last = std::move(next_onion);
if(i==0 && config->only_one_perimeter_top && this->upper_slices != NULL) {
//store surface for top infill if only_one_perimeter_top
if(i==0 && config->only_one_perimeter_top && this->upper_slices != NULL){
//split the polygons with top/not_top
ExPolygons upper_polygons(this->upper_slices->expolygons);
ExPolygons inner_polygons = diff_ex(last, (upper_polygons), true);
ExPolygons top_polygons = diff_ex(last, inner_polygons, true);
ExPolygons top_polygons = diff_ex(last, (upper_polygons), true);
ExPolygons inner_polygons = diff_ex(last, top_polygons, true);
// increase a bit the inner space to fill the frontier between last and stored.
stored = _clipper_ex(ClipperLib::ctUnion, to_polygons(stored),
to_polygons(intersection_ex(offset_ex(inner_polygons, perimeter_spacing / 2), last)), false);
last = intersection_ex(offset_ex(top_polygons, perimeter_spacing / 2), last);
stored = union_ex(stored, intersection_ex(offset_ex(top_polygons, (float)(perimeter_spacing / 2)), last));
last = intersection_ex(offset_ex(inner_polygons, (float)(perimeter_spacing / 2)), last);
}
}
// add stored polygons
last = _clipper_ex(ClipperLib::ctUnion, to_polygons(last), to_polygons(stored), false);
// re-add stored polygons
last = union_ex(last, stored);
// nest loops: holes first
for (int d = 0; d <= loop_number; ++d) {

View File

@ -1147,10 +1147,32 @@ PrintConfigDef::PrintConfigDef()
def->label = L("Detect bridging perimeters");
def->category = L("Layers and Perimeters");
def->tooltip = L("Experimental option to adjust flow for overhangs (bridge flow will be used), "
"to apply bridge speed to them and enable fan.");
"to apply bridge speed to them and enable fan.");
def->cli = "overhangs!";
def->default_value = new ConfigOptionBool(true);
def = this->add("no_perimeter_unsupported", coBool);
def->label = L("");
def->category = L("Layers and Perimeters");
def->tooltip = L("Experimental option to remove perimeters where there are nothing under and a bridged infill should be better.");
def->cli = "no-perimeter-unsupported!";
def->default_value = new ConfigOptionBool(true);
def = this->add("min_perimeter_unsupported", coInt);
def->label = L("Minimum perimeters");
def->category = L("Layers and Perimeters");
def->tooltip = L("Number of permieter exluded from this option.");
def->cli = "min-perimeter-unsupported=i";
def->min = 0;
def->default_value = new ConfigOptionInt(0);
def = this->add("noperi_bridge_only", coBool);
def->label = L("Only on briged area");
def->category = L("Layers and Perimeters");
def->tooltip = L("Only remove perimeters over area marked as 'bridge'. Can be useful to let perimeter run over overhangs, but it's not very reliable.");
def->cli = "noperi-bridge-only!";
def->default_value = new ConfigOptionBool(true);
def = this->add("parking_pos_retraction", coFloat);
def->label = L("Filament parking position");
def->tooltip = L("Distance of the extruder tip from the position where the filament is parked "

View File

@ -411,6 +411,9 @@ public:
ConfigOptionInt infill_dense_layers;
ConfigOptionPercent infill_dense_density;
ConfigOptionBool overhangs;
ConfigOptionBool no_perimeter_unsupported;
ConfigOptionInt min_perimeter_unsupported;
ConfigOptionBool noperi_bridge_only;
ConfigOptionInt perimeter_extruder;
ConfigOptionFloatOrPercent perimeter_extrusion_width;
ConfigOptionFloat perimeter_speed;
@ -457,6 +460,9 @@ protected:
OPT_PTR(infill_dense_layers);
OPT_PTR(infill_dense_density);
OPT_PTR(overhangs);
OPT_PTR(no_perimeter_unsupported);
OPT_PTR(min_perimeter_unsupported);
OPT_PTR(noperi_bridge_only);
OPT_PTR(perimeter_extruder);
OPT_PTR(perimeter_extrusion_width);
OPT_PTR(perimeter_speed);

View File

@ -149,7 +149,10 @@ bool PrintObject::invalidate_state_by_config_options(const std::vector<t_config_
|| opt_key == "perimeter_extrusion_width"
|| opt_key == "infill_overlap"
|| opt_key == "thin_walls"
|| opt_key == "external_perimeters_first") {
|| opt_key == "external_perimeters_first"
|| opt_key == "no_perimeter_unsupported"
|| opt_key == "min_perimeter_unsupported"
|| opt_key == "noperi_bridge_only") {
steps.emplace_back(posPerimeters);
} else if (
opt_key == "layer_height"

View File

@ -300,7 +300,8 @@ const std::vector<std::string>& Preset::print_options()
"over_bridge_flow_ratio", "clip_multipart_objects", "enforce_full_fill_volume", "external_infill_margin", "bridged_infill_margin",
"elefant_foot_compensation", "xy_size_compensation", "threads", "resolution", "wipe_tower", "wipe_tower_x", "wipe_tower_y",
"wipe_tower_width", "wipe_tower_rotation_angle", "wipe_tower_bridging", "only_one_perimeter_top", "compatible_printers",
"compatible_printers_condition", "inherits", "infill_dense_layers", "infill_dense_density"
"compatible_printers_condition", "inherits", "infill_dense_layers", "infill_dense_density", "no_perimeter_unsupported",
"min_perimeter_unsupported", "noperi_bridge_only"
};
return s_opts;
}

View File

@ -807,6 +807,11 @@ void TabPrint::build()
optgroup->append_single_option_line("avoid_crossing_perimeters");
optgroup->append_single_option_line("thin_walls");
optgroup->append_single_option_line("overhangs");
line = { _(L("Avoid unsupported perimeters")), "" };
line.append_option(optgroup->get_option("no_perimeter_unsupported"));
line.append_option(optgroup->get_option("min_perimeter_unsupported"));
line.append_option(optgroup->get_option("noperi_bridge_only"));
optgroup->append_line(line);
optgroup = page->new_optgroup(_(L("Advanced")));
optgroup->append_single_option_line("seam_position");
@ -1158,6 +1163,10 @@ void TabPrint::update()
"perimeter_speed", "small_perimeter_speed", "external_perimeter_speed" })
get_field(el)->toggle(have_perimeters);
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);
bool have_infill = m_config->option<ConfigOptionPercent>("fill_density")->value > 0;
// infill_extruder uses the same logic as in Print::extruders()
for (auto el : {"fill_pattern", "infill_every_layers", "infill_only_where_needed",