thinwall improvements (#4472)

* First step of thin wall improvements for alexrj branch.

* bugfix

* fix

* remove tabs, remove dead code

* Convert comments to C++ instead of C-style.

Converting comments to C++ style, fixed typos in comments.
C-style comments are normally reserved for commenting out code blocks, not descriptions.
This commit is contained in:
Merill 2018-07-06 01:39:55 +02:00 committed by Joseph Lenox
parent 55a4e95b94
commit cc42bb411c
3 changed files with 201 additions and 62 deletions

View File

@ -185,7 +185,7 @@ ExPolygon::simplify(double tolerance, ExPolygons* expolygons) const
}
void
ExPolygon::medial_axis(double max_width, double min_width, ThickPolylines* polylines) const
ExPolygon::medial_axis(const ExPolygon &bounds, double max_width, double min_width, ThickPolylines* polylines) const
{
// init helper object
Slic3r::Geometry::MedialAxis ma(max_width, min_width, this);
@ -195,41 +195,145 @@ ExPolygon::medial_axis(double max_width, double min_width, ThickPolylines* polyl
ThickPolylines pp;
ma.build(&pp);
/*
/* // Commented out debug code
SVG svg("medial_axis.svg");
svg.draw(*this);
svg.draw(pp);
svg.Close();
*/
/* Find the maximum width returned; we're going to use this for validating and
filtering the output segments. */
// Find the maximum width returned; we're going to use this for validating and
// filtering the output segments.
double max_w = 0;
for (ThickPolylines::const_iterator it = pp.begin(); it != pp.end(); ++it)
max_w = fmaxf(max_w, *std::max_element(it->width.begin(), it->width.end()));
/* Loop through all returned polylines in order to extend their endpoints to the
expolygon boundaries */
// Aligned fusion: Fusion the bits at the end of lines by "increasing thickness"
// For that, we have to find other lines,
// and with a next point no more distant than the max width.
// Then, we can merge the bit from the first point to the second by following the mean.
bool changes = true;
while (changes) {
changes = false;
for (size_t i = 0; i < pp.size(); ++i) {
ThickPolyline& polyline = pp[i];
ThickPolyline* best_candidate = nullptr;
float best_dot = -1;
int best_idx = 0;
// find another polyline starting here
for (size_t j = i + 1; j < pp.size(); ++j) {
ThickPolyline& other = pp[j];
if (polyline.last_point().coincides_with(other.last_point())) {
polyline.reverse();
other.reverse();
}
else if (polyline.first_point().coincides_with(other.last_point())) {
other.reverse();
}
else if (polyline.first_point().coincides_with(other.first_point())) {
}
else if (polyline.last_point().coincides_with(other.first_point())) {
polyline.reverse();
} else {
continue;
}
//only consider the other if the next point is near us
if (polyline.points.size() < 2 && other.points.size() < 2) continue;
if (!polyline.endpoints.second || !other.endpoints.second) continue;
if (polyline.points.back().distance_to(other.points.back()) > max_width) continue;
if (polyline.points.size() != other.points.size()) continue;
Pointf v_poly(polyline.lines().front().vector().x, polyline.lines().front().vector().y);
v_poly.scale(1 / std::sqrt(v_poly.x*v_poly.x + v_poly.y*v_poly.y));
Pointf v_other(other.lines().front().vector().x, other.lines().front().vector().y);
v_other.scale(1 / std::sqrt(v_other.x*v_other.x + v_other.y*v_other.y));
float other_dot = v_poly.x*v_other.x + v_poly.y*v_other.y;
if (other_dot > best_dot) {
best_candidate = &other;
best_idx = j;
best_dot = other_dot;
}
}
if (best_candidate != nullptr) {
//assert polyline.size == best_candidate->size (see selection loop, an 'if' takes care of that)
//iterate the points
// as voronoi should create symetric thing, we can iterate synchonously
unsigned int idx_point = 1;
while (idx_point < polyline.points.size() && polyline.points[idx_point].distance_to(best_candidate->points[idx_point]) < max_width) {
//fusion
polyline.points[idx_point].x += best_candidate->points[idx_point].x;
polyline.points[idx_point].x /= 2;
polyline.points[idx_point].y += best_candidate->points[idx_point].y;
polyline.points[idx_point].y /= 2;
polyline.width[idx_point] += best_candidate->width[idx_point];
++idx_point;
}
//select if an end occur
polyline.endpoints.second &= best_candidate->endpoints.second;
//remove points that are the same or too close each other, ie simplify
for (unsigned int idx_point = 1; idx_point < polyline.points.size(); ++idx_point) {
//distance of 1 is on the sclaed coordinates, so it correspond to SCALE_FACTOR, so it's very small
if (polyline.points[idx_point - 1].distance_to(polyline.points[idx_point]) < 1) {
if (idx_point < polyline.points.size() -1) {
polyline.points.erase(polyline.points.begin() + idx_point);
} else {
polyline.points.erase(polyline.points.begin() + idx_point -1);
}
--idx_point;
}
}
//remove points that are outside of the geometry
for (unsigned int idx_point = 0; idx_point < polyline.points.size(); ++idx_point) {
//distance of 1 is on the sclaed coordinates, so it correspond to SCALE_FACTOR, so it's very small
if (!bounds.contains_b(polyline.points[idx_point])) {
polyline.points.erase(polyline.points.begin() + idx_point);
--idx_point;
}
}
if (polyline.points.size() < 2) {
//remove self
pp.erase(pp.begin() + i);
--i;
--best_idx;
}
pp.erase(pp.begin() + best_idx);
changes = true;
}
}
}
// Loop through all returned polylines in order to extend their endpoints to the
// expolygon boundaries
bool removed = false;
for (size_t i = 0; i < pp.size(); ++i) {
ThickPolyline& polyline = pp[i];
// extend initial and final segments of each polyline if they're actual endpoints
/* We assign new endpoints to temporary variables because in case of a single-line
polyline, after we extend the start point it will be caught by the intersection()
call, so we keep the inner point until we perform the second intersection() as well */
// Assign new endpoints to temporary variables because in case of a single-line
// polyline. After the start point is extended it will be caught by the intersection()
// call, so keep the inner point until the second intersection() is performed.
Point new_front = polyline.points.front();
Point new_back = polyline.points.back();
if (polyline.endpoints.first && !this->has_boundary_point(new_front)) {
if (polyline.endpoints.first && !bounds.has_boundary_point(new_front)) {
Line line(polyline.points.front(), polyline.points[1]);
// prevent the line from touching on the other side, otherwise intersection() might return that solution
if (polyline.points.size() == 2) line.b = line.midpoint();
line.extend_start(max_width);
(void)this->contour.intersection(line, &new_front);
(void)bounds.contour.intersection(line, &new_front);
}
if (polyline.endpoints.second && !this->has_boundary_point(new_back)) {
if (polyline.endpoints.second && !bounds.has_boundary_point(new_back)) {
Line line(
*(polyline.points.end() - 2),
polyline.points.back()
@ -239,60 +343,83 @@ ExPolygon::medial_axis(double max_width, double min_width, ThickPolylines* polyl
if (polyline.points.size() == 2) line.a = line.midpoint();
line.extend_end(max_width);
(void)this->contour.intersection(line, &new_back);
(void)bounds.contour.intersection(line, &new_back);
}
polyline.points.front() = new_front;
polyline.points.back() = new_back;
}
// If we removed any short polylines, we now try to connect consecutive polylines
// in order to allow loop detection. Note that this algorithm is greedier than
// MedialAxis::process_edge_neighbors(), as it will connect random pairs of
// polylines even when more than two start from the same point. This has no
// drawbacks since we optimize later using nearest-neighbor which would do the
// same, but should we use a more sophisticated optimization algorithm.
// We should not connect polylines when more than two meet.
// Optimisation of the old algorithm : Select the most "straight line" choice
// when we merge with an other line at a point with more than two meet.
/* remove too short polylines
(we can't do this check before endpoints extension and clipping because we don't
know how long will the endpoints be extended since it depends on polygon thickness
which is variable - extension will be <= max_width/2 on each side) */
for (size_t i = 0; i < pp.size(); ++i) {
ThickPolyline& polyline = pp[i];
if (polyline.endpoints.first && polyline.endpoints.second) continue; // optimization
ThickPolyline* best_candidate = nullptr;
float best_dot = -1;
int best_idx = 0;
// find another polyline starting here
for (size_t j = i+1; j < pp.size(); ++j) {
ThickPolyline& other = pp[j];
if (polyline.last_point().coincides_with(other.last_point())) {
other.reverse();
} else if (polyline.first_point().coincides_with(other.last_point())) {
polyline.reverse();
other.reverse();
} else if (polyline.first_point().coincides_with(other.first_point())) {
polyline.reverse();
} else if (!polyline.last_point().coincides_with(other.first_point())) {
continue;
}
Pointf v_poly(polyline.lines().back().vector().x, polyline.lines().back().vector().y);
v_poly.scale(1 / std::sqrt(v_poly.x*v_poly.x + v_poly.y*v_poly.y));
Pointf v_other(other.lines().front().vector().x, other.lines().front().vector().y);
v_other.scale(1 / std::sqrt(v_other.x*v_other.x + v_other.y*v_other.y));
float other_dot = v_poly.x*v_other.x + v_poly.y*v_other.y;
if (other_dot > best_dot) {
best_candidate = &other;
best_idx = j;
best_dot = other_dot;
}
}
if (best_candidate != nullptr) {
polyline.points.insert(polyline.points.end(), best_candidate->points.begin() + 1, best_candidate->points.end());
polyline.width.insert(polyline.width.end(), best_candidate->width.begin(), best_candidate->width.end());
polyline.endpoints.second = best_candidate->endpoints.second;
assert(polyline.width.size() == polyline.points.size()*2 - 2);
pp.erase(pp.begin() + best_idx);
}
}
for (size_t i = 0; i < pp.size(); ++i) {
ThickPolyline& polyline = pp[i];
// remove too short polylines
// (we can't do this check before endpoints extension and clipping because we don't
// know how long will the endpoints be extended since it depends on polygon thickness
// which is variable - extension will be <= max_width/2 on each side)
if ((polyline.endpoints.first || polyline.endpoints.second)
&& polyline.length() < max_w*2) {
&& polyline.length() < max_w * 2) {
pp.erase(pp.begin() + i);
--i;
removed = true;
continue;
}
}
/* If we removed any short polylines we now try to connect consecutive polylines
in order to allow loop detection. Note that this algorithm is greedier than
MedialAxis::process_edge_neighbors() as it will connect random pairs of
polylines even when more than two start from the same point. This has no
drawbacks since we optimize later using nearest-neighbor which would do the
same, but should we use a more sophisticated optimization algorithm we should
not connect polylines when more than two meet. */
if (removed) {
for (size_t i = 0; i < pp.size(); ++i) {
ThickPolyline& polyline = pp[i];
if (polyline.endpoints.first && polyline.endpoints.second) continue; // optimization
// find another polyline starting here
for (size_t j = i+1; j < pp.size(); ++j) {
ThickPolyline& other = pp[j];
if (polyline.last_point().coincides_with(other.last_point())) {
other.reverse();
} else if (polyline.first_point().coincides_with(other.last_point())) {
polyline.reverse();
other.reverse();
} else if (polyline.first_point().coincides_with(other.first_point())) {
polyline.reverse();
} else if (!polyline.last_point().coincides_with(other.first_point())) {
continue;
}
polyline.points.insert(polyline.points.end(), other.points.begin() + 1, other.points.end());
polyline.width.insert(polyline.width.end(), other.width.begin(), other.width.end());
polyline.endpoints.second = other.endpoints.second;
assert(polyline.width.size() == polyline.points.size()*2 - 2);
pp.erase(pp.begin() + j);
j = i; // restart search from i+1
}
}
}
polylines->insert(polylines->end(), pp.begin(), pp.end());
}
@ -301,7 +428,7 @@ void
ExPolygon::medial_axis(double max_width, double min_width, Polylines* polylines) const
{
ThickPolylines tp;
this->medial_axis(max_width, min_width, &tp);
this->medial_axis(*this, max_width, min_width, &tp);
polylines->insert(polylines->end(), tp.begin(), tp.end());
}

View File

@ -39,7 +39,7 @@ class ExPolygon
Polygons simplify_p(double tolerance) const;
ExPolygons simplify(double tolerance) const;
void simplify(double tolerance, ExPolygons* expolygons) const;
void medial_axis(double max_width, double min_width, ThickPolylines* polylines) const;
void medial_axis(const ExPolygon &bounds, double max_width, double min_width, ThickPolylines* polylines) const;
void medial_axis(double max_width, double min_width, Polylines* polylines) const;
void get_trapezoids(Polygons* polygons) const;
void get_trapezoids(Polygons* polygons, double angle) const;

View File

@ -99,9 +99,10 @@ PerimeterGenerator::process()
// look for thin walls
if (this->config->thin_walls) {
Polygons no_thin_zone = offset(offsets, +ext_pwidth/2);
Polygons diffpp = diff(
last,
offset(offsets, +ext_pwidth/2),
no_thin_zone,
true // medial axis requires non-overlapping geometry
);
@ -109,11 +110,22 @@ PerimeterGenerator::process()
// (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);
ExPolygons expp = offset2_ex(diffpp, -min_width/2, +min_width/2);
// compute a bit of overlap to anchor thin walls inside the print.
ExPolygons anchor = intersection_ex(to_polygons(offset_ex(expp, (float)(ext_pwidth / 2))), no_thin_zone, true);
// the maximum thickness of our thin wall area is equal to the minimum thickness of a single loop
for (ExPolygons::const_iterator ex = expp.begin(); ex != expp.end(); ++ex)
ex->medial_axis(ext_pwidth + ext_pspacing2, min_width, &thin_walls);
for (ExPolygons::const_iterator ex = expp.begin(); ex != expp.end(); ++ex) {
ExPolygons bounds = _clipper_ex(ClipperLib::ctUnion, (Polygons)*ex, to_polygons(anchor), true);
//search our bound
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
ex->medial_axis(bound, ext_pwidth + ext_pspacing2, min_width, &thin_walls);
continue;
}
}
}
#ifdef DEBUG
printf(" %zu thin walls detected\n", thin_walls.size());
#endif
@ -281,7 +293,7 @@ PerimeterGenerator::process()
ThickPolylines polylines;
for (ExPolygons::const_iterator ex = gaps_ex.begin(); ex != gaps_ex.end(); ++ex)
ex->medial_axis(max, min, &polylines);
ex->medial_axis(*ex, max, min, &polylines);
if (!polylines.empty()) {
ExtrusionEntityCollection gap_fill = this->_variable_width(polylines,