mirror of
https://git.mirrors.martin98.com/https://github.com/slic3r/Slic3r.git
synced 2025-08-13 18:35:56 +08:00
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:
parent
586b38f0b2
commit
18f926cfc4
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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);
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
|
@ -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) {
|
||||
|
@ -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 "
|
||||
|
@ -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);
|
||||
|
@ -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"
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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",
|
||||
|
Loading…
x
Reference in New Issue
Block a user