mirror of
https://git.mirrors.martin98.com/https://github.com/slic3r/Slic3r.git
synced 2025-08-14 10:06:08 +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;
|
return angles;
|
||||||
}
|
}
|
||||||
|
|
||||||
Polygons BridgeDetector::coverage(double angle) const
|
Polygons BridgeDetector::coverage(double angle, bool precise) const {
|
||||||
{
|
|
||||||
if (angle == -1)
|
if (angle == -1)
|
||||||
angle = this->angle;
|
angle = this->angle;
|
||||||
|
|
||||||
@ -213,52 +213,92 @@ Polygons BridgeDetector::coverage(double angle) const
|
|||||||
if (angle != -1) {
|
if (angle != -1) {
|
||||||
// Get anchors, convert them to Polygons and rotate them.
|
// Get anchors, convert them to Polygons and rotate them.
|
||||||
Polygons anchors = to_polygons(this->_anchor_regions);
|
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) {
|
for (ExPolygon expolygon : this->expolygons) {
|
||||||
// Clone our expolygon and rotate it so that we work with vertical lines.
|
// 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;
|
// 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
|
// 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.
|
// are inside the anchors and not on their contours leading to false negatives.
|
||||||
for (ExPolygon &expoly : offset_ex(expolygon, 0.5f * float(this->spacing))) {
|
for (ExPolygon &expoly : offset_ex(expolygon, 0.5f * float(this->spacing))) {
|
||||||
// Compute trapezoids according to a vertical orientation
|
// Compute trapezoids according to a vertical orientation
|
||||||
Polygons trapezoids;
|
Polygons trapezoids;
|
||||||
expoly.get_trapezoids2(&trapezoids, PI/2.0);
|
if (!precise) expoly.get_trapezoids2(&trapezoids, PI / 2);
|
||||||
for (const Polygon &trapezoid : trapezoids) {
|
else expoly.get_trapezoids3_half(&trapezoids, float(this->spacing));
|
||||||
|
for (Polygon &trapezoid : trapezoids) {
|
||||||
// not nice, we need a more robust non-numeric check
|
// 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;
|
size_t n_supported = 0;
|
||||||
for (const Line &supported_line : intersection_ln(trapezoid.lines(), anchors))
|
if (!precise) {
|
||||||
if (supported_line.length() >= this->spacing)
|
for (const Line &supported_line : intersection_ln(trapezoid.lines(), anchors))
|
||||||
++ n_supported;
|
if (supported_line.length() >= this->spacing)
|
||||||
if (n_supported >= 2)
|
++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));
|
covered.push_back(std::move(trapezoid));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Unite the trapezoids before rotation, as the rotation creates tiny gaps and intersections between the trapezoids
|
// Unite the trapezoids before rotation, as the rotation creates tiny gaps and intersections between the trapezoids
|
||||||
// instead of exact overlaps.
|
// instead of exact overlaps.
|
||||||
covered = union_(covered);
|
covered = union_(covered);
|
||||||
// Intersect trapezoids with actual bridge area to remove extra margins and append it to result.
|
// Intersect trapezoids with actual bridge area to remove extra margins and append it to result.
|
||||||
polygons_rotate(covered, -(PI/2.0 - angle));
|
polygons_rotate(covered, -(PI/2.0 - angle));
|
||||||
covered = intersection(covered, to_polygons(this->expolygons));
|
covered = intersection(covered, to_polygons(this->expolygons));
|
||||||
#if 0
|
#if 0
|
||||||
{
|
{
|
||||||
my @lines = map @{$_->lines}, @$trapezoids;
|
my @lines = map @{$_->lines}, @$trapezoids;
|
||||||
$_->rotate(-(PI/2 - $angle), [0,0]) for @lines;
|
$_->rotate(-(PI/2 - $angle), [0,0]) for @lines;
|
||||||
|
|
||||||
require "Slic3r/SVG.pm";
|
require "Slic3r/SVG.pm";
|
||||||
Slic3r::SVG::output(
|
Slic3r::SVG::output(
|
||||||
"coverage_" . rad2deg($angle) . ".svg",
|
"coverage_" . rad2deg($angle) . ".svg",
|
||||||
expolygons => [$self->expolygon],
|
expolygons => [$self->expolygon],
|
||||||
green_expolygons => $self->_anchor_regions,
|
green_expolygons => $self->_anchor_regions,
|
||||||
red_expolygons => $coverage,
|
red_expolygons => $coverage,
|
||||||
lines => \@lines,
|
lines => \@lines,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
return covered;
|
return covered;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -33,7 +33,7 @@ public:
|
|||||||
BridgeDetector(const ExPolygons &_expolygons, const ExPolygonCollection &_lower_slices, coord_t _extrusion_width);
|
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.
|
// If bridge_direction_override != 0, then the angle is used instead of auto-detect.
|
||||||
bool detect_angle(double bridge_direction_override = 0.);
|
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;
|
void unsupported_edges(double angle, Polylines* unsupported) const;
|
||||||
Polylines unsupported_edges(double angle = -1) const;
|
Polylines unsupported_edges(double angle = -1) const;
|
||||||
|
|
||||||
@ -54,7 +54,7 @@ private:
|
|||||||
double coverage;
|
double coverage;
|
||||||
double max_length;
|
double max_length;
|
||||||
};
|
};
|
||||||
|
public:
|
||||||
// Get possible briging direction candidates.
|
// Get possible briging direction candidates.
|
||||||
std::vector<double> bridge_direction_candidates() const;
|
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_);
|
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);
|
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);
|
Slic3r::Polygons union_pt_chained(const Slic3r::Polygons &subject, bool safety_offset_ = false);
|
||||||
|
@ -539,13 +539,59 @@ ExPolygon::get_trapezoids2(Polygons* polygons) const
|
|||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
ExPolygon::get_trapezoids2(Polygons* polygons, double angle) const
|
ExPolygon::get_trapezoids2(Polygons* polygons, double angle) const {
|
||||||
{
|
|
||||||
ExPolygon clone = *this;
|
ExPolygon clone = *this;
|
||||||
clone.rotate(PI/2 - angle, Point(0,0));
|
clone.rotate(PI / 2 - angle, Point(0, 0));
|
||||||
clone.get_trapezoids2(polygons);
|
clone.get_trapezoids2(polygons);
|
||||||
for (Polygons::iterator polygon = polygons->begin(); polygon != polygons->end(); ++polygon)
|
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
|
// 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_trapezoids(Polygons* polygons, double angle) const;
|
||||||
void get_trapezoids2(Polygons* polygons) const;
|
void get_trapezoids2(Polygons* polygons) const;
|
||||||
void get_trapezoids2(Polygons* polygons, double angle) 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(Polygons* polygons) const;
|
||||||
void triangulate_pp(Polygons* polygons) const;
|
void triangulate_pp(Polygons* polygons) const;
|
||||||
void triangulate_p2t(Polygons* polygons) const;
|
void triangulate_p2t(Polygons* polygons) const;
|
||||||
|
@ -1,6 +1,8 @@
|
|||||||
#include "PerimeterGenerator.hpp"
|
#include "PerimeterGenerator.hpp"
|
||||||
#include "ClipperUtils.hpp"
|
#include "ClipperUtils.hpp"
|
||||||
#include "ExtrusionEntityCollection.hpp"
|
#include "ExtrusionEntityCollection.hpp"
|
||||||
|
#include "BridgeDetector.hpp"
|
||||||
|
#include "Geometry.hpp"
|
||||||
#include <cmath>
|
#include <cmath>
|
||||||
#include <cassert>
|
#include <cassert>
|
||||||
|
|
||||||
@ -49,16 +51,18 @@ void PerimeterGenerator::process()
|
|||||||
|
|
||||||
// we need to process each island separately because we might have different
|
// we need to process each island separately because we might have different
|
||||||
// extra perimeters for each one
|
// extra perimeters for each one
|
||||||
|
int surface_idx = 0;
|
||||||
for (const Surface &surface : this->slices->surfaces) {
|
for (const Surface &surface : this->slices->surfaces) {
|
||||||
// detect how many perimeters must be generated for this island
|
// detect how many perimeters must be generated for this island
|
||||||
int loop_number = this->config->perimeters + surface.extra_perimeters - 1; // 0-indexed loops
|
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){
|
if (this->config->only_one_perimeter_top && this->upper_slices == NULL){
|
||||||
loop_number = 0;
|
loop_number = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
ExPolygons gaps;
|
ExPolygons gaps;
|
||||||
|
//this var store infill surface removed from last to not add any more perimeters to it.
|
||||||
ExPolygons stored;
|
ExPolygons stored;
|
||||||
ExPolygons last = union_ex(surface.expolygon.simplify_p(SCALED_RESOLUTION));
|
ExPolygons last = union_ex(surface.expolygon.simplify_p(SCALED_RESOLUTION));
|
||||||
if (loop_number >= 0) {
|
if (loop_number >= 0) {
|
||||||
@ -68,6 +72,83 @@ void PerimeterGenerator::process()
|
|||||||
ThickPolylines thin_walls;
|
ThickPolylines thin_walls;
|
||||||
// we loop one time more than needed in order to find gaps after the last perimeter was applied
|
// 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
|
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
|
// We can add more perimeters if there are uncovered overhangs
|
||||||
// improvement for future: find a way to add perimeters only where it's needed.
|
// 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.
|
// It's hard to do, so here is a simple version.
|
||||||
@ -92,23 +173,25 @@ void PerimeterGenerator::process()
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Calculate next onion shell of perimeters.
|
// Calculate next onion shell of perimeters.
|
||||||
ExPolygons offsets;
|
//this variable stored the nexyt onion
|
||||||
|
ExPolygons next_onion;
|
||||||
if (i == 0) {
|
if (i == 0) {
|
||||||
// the minimum thickness of a single loop is:
|
// the minimum thickness of a single loop is:
|
||||||
// ext_width/2 + ext_spacing/2 + spacing/2 + width/2
|
// ext_width/2 + ext_spacing/2 + spacing/2 + width/2
|
||||||
offsets = this->config->thin_walls ?
|
next_onion = this->config->thin_walls ?
|
||||||
offset2_ex(
|
offset2_ex(
|
||||||
last,
|
last,
|
||||||
-(ext_perimeter_width / 2 + ext_min_spacing / 2 - 1),
|
-(float)(ext_perimeter_width / 2 + ext_min_spacing / 2 - 1),
|
||||||
+(ext_min_spacing / 2 - 1)) :
|
+(float)(ext_min_spacing / 2 - 1)) :
|
||||||
offset_ex(last, - ext_perimeter_width / 2);
|
offset_ex(last, -(float)(ext_perimeter_width / 2));
|
||||||
|
|
||||||
// look for thin walls
|
// look for thin walls
|
||||||
if (this->config->thin_walls) {
|
if (this->config->thin_walls) {
|
||||||
// the following offset2 ensures almost nothing in @thin_walls is narrower than $min_width
|
// 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)
|
// (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(
|
ExPolygons expp = offset2_ex(
|
||||||
// medial axis requires non-overlapping geometry
|
// medial axis requires non-overlapping geometry
|
||||||
diff_ex(to_polygons(last),
|
diff_ex(to_polygons(last),
|
||||||
@ -118,7 +201,7 @@ void PerimeterGenerator::process()
|
|||||||
// compute a bit of overlap to anchor thin walls inside the print.
|
// 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);
|
ExPolygons anchor = intersection_ex(to_polygons(offset_ex(expp, (float)(ext_perimeter_width / 2))), no_thin_zone, true);
|
||||||
for (ExPolygon &ex : expp) {
|
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) {
|
for (ExPolygon &bound : bounds) {
|
||||||
if (!intersection_ex(ex, bound).empty()) {
|
if (!intersection_ex(ex, bound).empty()) {
|
||||||
// the maximum thickness of our thin wall area is equal to the minimum thickness of a single loop
|
// 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
|
//FIXME Is this offset correct if the line width of the inner perimeters differs
|
||||||
// from the line width of the infill?
|
// from the line width of the infill?
|
||||||
coord_t distance = (i == 1) ? ext_perimeter_spacing2 : perimeter_spacing;
|
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
|
// This path will ensure, that the perimeters do not overfill, as in
|
||||||
// prusa3d/Slic3r GH #32, but with the cost of rounding the perimeters
|
// 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
|
// excessively, creating gaps, which then need to be filled in by the not very
|
||||||
// reliable gap fill algorithm.
|
// reliable gap fill algorithm.
|
||||||
// Also the offset2(perimeter, -x, x) may sometimes lead to a perimeter, which is larger than
|
// Also the offset2(perimeter, -x, x) may sometimes lead to a perimeter, which is larger than
|
||||||
// the original.
|
// the original.
|
||||||
offset2_ex(last,
|
next_onion = offset2_ex(last,
|
||||||
-(distance + min_spacing/2 - 1),
|
-(float)(distance + min_spacing / 2 - 1),
|
||||||
min_spacing / 2 - 1) :
|
+(float)(min_spacing / 2 - 1));
|
||||||
|
} else {
|
||||||
// If "detect thin walls" is not enabled, this paths will be entered, which
|
// If "detect thin walls" is not enabled, this paths will be entered, which
|
||||||
// leads to overflows, as in prusa3d/Slic3r GH #32
|
// leads to overflows, as in prusa3d/Slic3r GH #32
|
||||||
offset_ex(last, - distance);
|
next_onion = offset_ex(last, -(float)(distance));
|
||||||
|
}
|
||||||
// look for gaps
|
// look for gaps
|
||||||
if (this->config->gap_fill_speed.value > 0 && this->config->fill_density.value > 0)
|
if (this->config->gap_fill_speed.value > 0 && this->config->fill_density.value > 0)
|
||||||
// not using safety offset here would "detect" very narrow gaps
|
// not using safety offset here would "detect" very narrow gaps
|
||||||
// (but still long enough to escape the area threshold) that gap fill
|
// (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
|
// won't be able to fill but we'd still remove from infill area
|
||||||
append(gaps, diff_ex(
|
append(gaps, diff_ex(
|
||||||
offset(last, -0.5*distance),
|
offset(last, -0.5f*distance),
|
||||||
offset(offsets, 0.5 * distance + 10))); // safety offset
|
offset(next_onion, 0.5f * distance + 10))); // safety offset
|
||||||
}
|
}
|
||||||
if (offsets.empty()) {
|
|
||||||
|
if (next_onion.empty()) {
|
||||||
// Store the number of loops actually generated.
|
// Store the number of loops actually generated.
|
||||||
loop_number = i - 1;
|
loop_number = i - 1;
|
||||||
// No region left to be filled in.
|
// No region left to be filled in.
|
||||||
@ -170,7 +256,8 @@ void PerimeterGenerator::process()
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for (const ExPolygon &expolygon : offsets) {
|
|
||||||
|
for (const ExPolygon &expolygon : next_onion) {
|
||||||
contours[i].emplace_back(PerimeterGeneratorLoop(expolygon.contour, i, true));
|
contours[i].emplace_back(PerimeterGeneratorLoop(expolygon.contour, i, true));
|
||||||
if (! expolygon.holes.empty()) {
|
if (! expolygon.holes.empty()) {
|
||||||
holes[i].reserve(holes[i].size() + expolygon.holes.size());
|
holes[i].reserve(holes[i].size() + expolygon.holes.size());
|
||||||
@ -178,23 +265,25 @@ void PerimeterGenerator::process()
|
|||||||
holes[i].emplace_back(PerimeterGeneratorLoop(hole, i, false));
|
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
|
//split the polygons with top/not_top
|
||||||
ExPolygons upper_polygons(this->upper_slices->expolygons);
|
ExPolygons upper_polygons(this->upper_slices->expolygons);
|
||||||
ExPolygons inner_polygons = diff_ex(last, (upper_polygons), true);
|
ExPolygons top_polygons = diff_ex(last, (upper_polygons), true);
|
||||||
ExPolygons top_polygons = diff_ex(last, inner_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.
|
// increase a bit the inner space to fill the frontier between last and stored.
|
||||||
stored = _clipper_ex(ClipperLib::ctUnion, to_polygons(stored),
|
stored = union_ex(stored, intersection_ex(offset_ex(top_polygons, (float)(perimeter_spacing / 2)), last));
|
||||||
to_polygons(intersection_ex(offset_ex(inner_polygons, perimeter_spacing / 2), last)), false);
|
last = intersection_ex(offset_ex(inner_polygons, (float)(perimeter_spacing / 2)), last);
|
||||||
last = intersection_ex(offset_ex(top_polygons, perimeter_spacing / 2), last);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// add stored polygons
|
// re-add stored polygons
|
||||||
last = _clipper_ex(ClipperLib::ctUnion, to_polygons(last), to_polygons(stored), false);
|
last = union_ex(last, stored);
|
||||||
|
|
||||||
// nest loops: holes first
|
// nest loops: holes first
|
||||||
for (int d = 0; d <= loop_number; ++d) {
|
for (int d = 0; d <= loop_number; ++d) {
|
||||||
|
@ -1147,10 +1147,32 @@ PrintConfigDef::PrintConfigDef()
|
|||||||
def->label = L("Detect bridging perimeters");
|
def->label = L("Detect bridging perimeters");
|
||||||
def->category = L("Layers and Perimeters");
|
def->category = L("Layers and Perimeters");
|
||||||
def->tooltip = L("Experimental option to adjust flow for overhangs (bridge flow will be used), "
|
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->cli = "overhangs!";
|
||||||
def->default_value = new ConfigOptionBool(true);
|
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 = this->add("parking_pos_retraction", coFloat);
|
||||||
def->label = L("Filament parking position");
|
def->label = L("Filament parking position");
|
||||||
def->tooltip = L("Distance of the extruder tip from the position where the filament is parked "
|
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;
|
ConfigOptionInt infill_dense_layers;
|
||||||
ConfigOptionPercent infill_dense_density;
|
ConfigOptionPercent infill_dense_density;
|
||||||
ConfigOptionBool overhangs;
|
ConfigOptionBool overhangs;
|
||||||
|
ConfigOptionBool no_perimeter_unsupported;
|
||||||
|
ConfigOptionInt min_perimeter_unsupported;
|
||||||
|
ConfigOptionBool noperi_bridge_only;
|
||||||
ConfigOptionInt perimeter_extruder;
|
ConfigOptionInt perimeter_extruder;
|
||||||
ConfigOptionFloatOrPercent perimeter_extrusion_width;
|
ConfigOptionFloatOrPercent perimeter_extrusion_width;
|
||||||
ConfigOptionFloat perimeter_speed;
|
ConfigOptionFloat perimeter_speed;
|
||||||
@ -457,6 +460,9 @@ protected:
|
|||||||
OPT_PTR(infill_dense_layers);
|
OPT_PTR(infill_dense_layers);
|
||||||
OPT_PTR(infill_dense_density);
|
OPT_PTR(infill_dense_density);
|
||||||
OPT_PTR(overhangs);
|
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_extruder);
|
||||||
OPT_PTR(perimeter_extrusion_width);
|
OPT_PTR(perimeter_extrusion_width);
|
||||||
OPT_PTR(perimeter_speed);
|
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 == "perimeter_extrusion_width"
|
||||||
|| opt_key == "infill_overlap"
|
|| opt_key == "infill_overlap"
|
||||||
|| opt_key == "thin_walls"
|
|| 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);
|
steps.emplace_back(posPerimeters);
|
||||||
} else if (
|
} else if (
|
||||||
opt_key == "layer_height"
|
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",
|
"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",
|
"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",
|
"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;
|
return s_opts;
|
||||||
}
|
}
|
||||||
|
@ -807,6 +807,11 @@ void TabPrint::build()
|
|||||||
optgroup->append_single_option_line("avoid_crossing_perimeters");
|
optgroup->append_single_option_line("avoid_crossing_perimeters");
|
||||||
optgroup->append_single_option_line("thin_walls");
|
optgroup->append_single_option_line("thin_walls");
|
||||||
optgroup->append_single_option_line("overhangs");
|
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 = page->new_optgroup(_(L("Advanced")));
|
||||||
optgroup->append_single_option_line("seam_position");
|
optgroup->append_single_option_line("seam_position");
|
||||||
@ -1158,6 +1163,10 @@ void TabPrint::update()
|
|||||||
"perimeter_speed", "small_perimeter_speed", "external_perimeter_speed" })
|
"perimeter_speed", "small_perimeter_speed", "external_perimeter_speed" })
|
||||||
get_field(el)->toggle(have_perimeters);
|
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;
|
bool have_infill = m_config->option<ConfigOptionPercent>("fill_density")->value > 0;
|
||||||
// infill_extruder uses the same logic as in Print::extruders()
|
// infill_extruder uses the same logic as in Print::extruders()
|
||||||
for (auto el : {"fill_pattern", "infill_every_layers", "infill_only_where_needed",
|
for (auto el : {"fill_pattern", "infill_every_layers", "infill_only_where_needed",
|
||||||
|
Loading…
x
Reference in New Issue
Block a user