From 6ba61fe39cd232a9647696f110ddeb7d1743138a Mon Sep 17 00:00:00 2001 From: supermerill Date: Fri, 10 Aug 2018 11:01:56 +0200 Subject: [PATCH 01/25] Thin wall improvements - now keep the bit with 0 width to merge them instead of just delete them - merging algorithm re-coded (extensively) - change behavior of thick polyline to only 1 width value per point instead of 2 per pair of adjacent points. - do not post-process the voronoi diagram in polylines, now it's don in expolygon.medial_axis. - failsafes in perimeter_generator (too many splits, too small) - some new tests. --- t/thin.t | 24 +- xs/src/libslic3r/ExPolygon.cpp | 526 +++++++++++++++++++++--- xs/src/libslic3r/ExPolygon.hpp | 2 +- xs/src/libslic3r/Geometry.cpp | 65 +-- xs/src/libslic3r/MultiPoint.cpp | 24 ++ xs/src/libslic3r/MultiPoint.hpp | 1 + xs/src/libslic3r/PerimeterGenerator.cpp | 87 ++-- xs/src/libslic3r/Polyline.cpp | 98 ++++- xs/src/libslic3r/Polyline.hpp | 11 + 9 files changed, 714 insertions(+), 124 deletions(-) diff --git a/t/thin.t b/t/thin.t index 2d256d2864..e7140e1518 100644 --- a/t/thin.t +++ b/t/thin.t @@ -1,4 +1,4 @@ -use Test::More tests => 23; +use Test::More tests => 28; use strict; use warnings; @@ -108,6 +108,28 @@ if (0) { 'all medial axis segments of a semicircumference have the same orientation'; } +{ + my $expolygon = Slic3r::ExPolygon->new(Slic3r::Polygon->new_scale( + [4.3, 4], [4.3, 0], [4,0], [4,4], [0,4], [0,4.5], [4,4.5], [4,10], [4.3,10], [4.3, 4.5], + [6, 4.5], [6,10], [6.2,10], [6.2,4.5], [10,4.5], [10,4], [6.2,4], [6.2,0], [6, 0], [6, 4], + )); + my $res = $expolygon->medial_axis(scale 0.55, scale 0.25); + is scalar(@$res), 2, 'medial axis of a (bit too narrow) french cross is two lines'; + ok unscale($res->[0]->length) >= (9.9) - epsilon, 'medial axis has reasonable length'; + ok unscale($res->[1]->length) >= (9.9) - epsilon, 'medial axis has reasonable length'; +} + +{ + my $expolygon = Slic3r::ExPolygon->new(Slic3r::Polygon->new_scale( + [0.86526705,1.4509841], [0.57696039,1.8637021], [0.4502297,2.5569978], [0.45626199,3.2965596], [1.1218851,3.3049455], [0.96681072,2.8243202], [0.86328971,2.2056997], [0.85367905,1.7790778], + )); + my $res = $expolygon->medial_axis(scale 1, scale 0.25); + is scalar(@$res), 1, 'medial axis of a (bit too narrow) french cross is two lines'; + ok unscale($res->[0]->length) >= (1.4) - epsilon, 'medial axis has reasonable length'; + # TODO: check if min width is < 0.3 and max width is > 0.6 (min($res->[0]->width.front, $res->[0]->width.back) # problem: can't have access to width + +} + { my $expolygon = Slic3r::ExPolygon->new(Slic3r::Polygon->new_scale( [100, 100], diff --git a/xs/src/libslic3r/ExPolygon.cpp b/xs/src/libslic3r/ExPolygon.cpp index 0e7a50e498..3ac4f5f90d 100644 --- a/xs/src/libslic3r/ExPolygon.cpp +++ b/xs/src/libslic3r/ExPolygon.cpp @@ -184,8 +184,104 @@ ExPolygon::simplify(double tolerance, ExPolygons* expolygons) const expolygons->insert(expolygons->end(), ep.begin(), ep.end()); } +/// remove point that are at SCALED_EPSILON * 2 distance. void -ExPolygon::medial_axis(const ExPolygon &bounds, double max_width, double min_width, ThickPolylines* polylines) const +remove_point_too_near(ThickPolyline* to_reduce) { + const int32_t smallest = SCALED_EPSILON * 2; + uint32_t id = 1; + while (id < to_reduce->points.size() - 2) { + uint32_t newdist = min(to_reduce->points[id].distance_to(to_reduce->points[id - 1]) + , to_reduce->points[id].distance_to(to_reduce->points[id + 1])); + if (newdist < smallest) { + to_reduce->points.erase(to_reduce->points.begin() + id); + to_reduce->width.erase(to_reduce->width.begin() + id); + } else { + ++id; + } + } +} + +/// add points from pattern to to_modify at the same % of the length +/// so not add if an other point is present at the correct position +void +add_point_same_percent(ThickPolyline* pattern, ThickPolyline* to_modify) { + const double to_modify_length = to_modify->length(); + const double percent_epsilon = SCALED_EPSILON / to_modify_length; + const double pattern_length = pattern->length(); + + double percent_length = 0; + for (uint32_t idx_point = 1; idx_point < pattern->points.size() - 1; ++idx_point) { + percent_length += pattern->points[idx_point-1].distance_to(pattern->points[idx_point]) / pattern_length; + //find position + uint32_t idx_other = 1; + double percent_length_other_before = 0; + double percent_length_other = 0; + while (idx_other < to_modify->points.size()) { + percent_length_other_before = percent_length_other; + percent_length_other += to_modify->points[idx_other-1].distance_to(to_modify->points[idx_other]) + / to_modify_length; + if (percent_length_other > percent_length - percent_epsilon) { + //if higher (we have gone over it) + break; + } + ++idx_other; + } + if (percent_length_other > percent_length + percent_epsilon) { + //insert a new point before the position + double percent_dist = (percent_length - percent_length_other_before) / (percent_length_other - percent_length_other_before); + coordf_t new_width = to_modify->width[idx_other - 1] * (1 - percent_dist); + new_width += to_modify->width[idx_other] * (percent_dist); + Point new_point; + new_point.x = (coord_t)((double)(to_modify->points[idx_other - 1].x) * (1 - percent_dist)); + new_point.x += (coord_t)((double)(to_modify->points[idx_other].x) * (percent_dist)); + new_point.y = (coord_t)((double)(to_modify->points[idx_other - 1].y) * (1 - percent_dist)); + new_point.y += (coord_t)((double)(to_modify->points[idx_other].y) * (percent_dist)); + to_modify->width.insert(to_modify->width.begin() + idx_other, new_width); + to_modify->points.insert(to_modify->points.begin() + idx_other, new_point); + } + } +} + +/// find the nearest angle in the contour (or 2 nearest if it's difficult to choose) +/// return 1 for an angle of 90° and 0 for an angle of 0° or 180° +double +get_coeff_from_angle_countour(Point &point, const ExPolygon &contour) { + double nearestDist = point.distance_to(contour.contour.points.front()); + Point nearest = contour.contour.points.front(); + uint32_t id_nearest = 0; + double nearDist = nearestDist; + Point near = nearest; + uint32_t id_near=0; + for (uint32_t id_point = 1; id_point < contour.contour.points.size(); ++id_point) { + if (nearestDist > point.distance_to(contour.contour.points[id_point])) { + nearestDist = point.distance_to(contour.contour.points[id_point]); + near = nearest; + nearest = contour.contour.points[id_point]; + id_near = id_nearest; + id_nearest = id_point; + } + } + double angle = 0; + Point point_before = id_nearest == 0 ? contour.contour.points.back() : contour.contour.points[id_nearest - 1]; + Point point_after = id_nearest == contour.contour.points.size()-1 ? contour.contour.points.front() : contour.contour.points[id_nearest + 1]; + //compute angle + angle = min(nearest.ccw_angle(point_before, point_after), nearest.ccw_angle(point_after, point_before)); + //compute the diff from 90° + angle = abs(angle - PI / 2); + if (near.x != nearest.x && near.y != nearest.y && max(nearestDist, nearDist) + SCALED_EPSILON < nearest.distance_to(near)) { + //not only nearest + Point point_before = id_near == 0 ? contour.contour.points.back() : contour.contour.points[id_near - 1]; + Point point_after = id_near == contour.contour.points.size() - 1 ? contour.contour.points.front() : contour.contour.points[id_near + 1]; + double angle2 = min(nearest.ccw_angle(point_before, point_after), nearest.ccw_angle(point_after, point_before)); + angle2 = abs(angle - PI / 2); + angle = (angle + angle2) / 2; + } + + return 1-(angle/(PI/2)); +} + +void +ExPolygon::medial_axis(const ExPolygon &bounds, double max_width, double min_width, ThickPolylines* polylines, double height) const { // init helper object Slic3r::Geometry::MedialAxis ma(max_width, min_width, this); @@ -195,65 +291,166 @@ ExPolygon::medial_axis(const ExPolygon &bounds, double max_width, double min_wid ThickPolylines pp; ma.build(&pp); - /* // Commented out debug code - SVG svg("medial_axis.svg"); - svg.draw(*this); - svg.draw(pp); - svg.Close(); - */ + + //{ + // stringstream stri; + // stri << "medial_axis" << id << ".svg"; + // SVG svg(stri.str()); + // svg.draw(bounds); + // 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. 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())); - - - // 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. + concatThickPolylines(pp); + //reoder pp by length (ascending) It's really important to do that to avoid building the line from the width insteand of the length + std::sort(pp.begin(), pp.end(), [](const ThickPolyline & a, const ThickPolyline & b) { return a.length() < b.length(); }); + + // 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. + // + int id_f = 0; bool changes = true; while (changes) { changes = false; for (size_t i = 0; i < pp.size(); ++i) { ThickPolyline& polyline = pp[i]; + + //simple check to see if i can be fusionned + if (!polyline.endpoints.first && !polyline.endpoints.second) continue; ThickPolyline* best_candidate = nullptr; float best_dot = -1; int best_idx = 0; - + double dot_poly_branch = 0; + double dot_candidate_branch = 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())) { + } 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())) { + } else if (polyline.first_point().coincides_with(other.first_point())) { + } else if (polyline.last_point().coincides_with(other.first_point())) { polyline.reverse(); } else { continue; } + //std::cout << " try : " << i << ":" << j << " : " << + // (polyline.points.size() < 2 && other.points.size() < 2) << + // (!polyline.endpoints.second || !other.endpoints.second) << + // ((polyline.points.back().distance_to(other.points.back()) + // + (polyline.width.back() + other.width.back()) / 4) + // > max_width*1.05) << + // (abs(polyline.length() - other.length()) > max_width / 2) << "\n"; - //only consider the other if the next point is near us + //// mergeable tests 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; + // test if the new width will not be too big if a fusion occur + //note that this isn't the real calcul. It's just to avoid merging lines too far apart. + if ( + ((polyline.points.back().distance_to(other.points.back()) + + (polyline.width.back() + other.width.back()) / 4) + > max_width*1.05)) + continue; + // test if the lines are not too different in length. + if (abs(polyline.length() - other.length()) > max_width / 2) continue; + //test if we don't merge with something too different and without any relevance. + double coeffSizePolyI = 1; + if (polyline.width.back() == 0) { + coeffSizePolyI = 0.1 + 0.9*get_coeff_from_angle_countour(polyline.points.back(), *this); + } + double coeffSizeOtherJ = 1; + if (other.width.back() == 0) { + coeffSizeOtherJ = 0.1+0.9*get_coeff_from_angle_countour(other.points.back(), *this); + } + if (abs(polyline.length()*coeffSizePolyI - other.length()*coeffSizeOtherJ) > max_width / 2) continue; + + //compute angle to see if it's better than previous ones (straighter = better). 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; + + // Get the branch/line in wich we may merge, if possible + // with that, we can decide what is important, and how we can merge that. + // angle_poly - angle_candi =90° => one is useless + // both angle are equal => both are useful with same strength + // ex: Y => | both are useful to crete a nice line + // ex2: TTTTT => ----- these 90° useless lines should be discarded + bool find_main_branch = false; + int biggest_main_branch_id = 0; + int biggest_main_branch_length = 0; + for (size_t k = 0; k < pp.size(); ++k) { + //std::cout << "try to find main : " << k << " ? " << i << " " << j << " "; + if (k == i | k == j) continue; + ThickPolyline& main = pp[k]; + if (polyline.first_point().coincides_with(main.last_point())) { + main.reverse(); + if (!main.endpoints.second) + find_main_branch = true; + else if (biggest_main_branch_length < main.length()) { + biggest_main_branch_id = k; + biggest_main_branch_length = main.length(); + } + } else if (polyline.first_point().coincides_with(main.first_point())) { + if (!main.endpoints.second) + find_main_branch = true; + else if (biggest_main_branch_length < main.length()) { + biggest_main_branch_id = k; + biggest_main_branch_length = main.length(); + } + } + if (find_main_branch) { + //use this variable to store the good index and break to compute it + biggest_main_branch_id = k; + break; + } + } + if (!find_main_branch && biggest_main_branch_length == 0) { + // nothing -> it's impossible! + dot_poly_branch = 0.707; + dot_candidate_branch = 0.707; + //std::cout << "no main branch... impossible!!\n"; + } else if (!find_main_branch && + (pp[biggest_main_branch_id].length() < polyline.length() || pp[biggest_main_branch_id].length() < other.length()) ){ + //the main branch should have no endpoint or be bigger! + //here, it have an endpoint, and is not the biggest -> bad! + continue; + } else { + //compute the dot (biggest_main_branch_id) + 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_candid(other.lines().front().vector().x, other.lines().front().vector().y); + v_candid.scale(1 / std::sqrt(v_candid.x*v_candid.x + v_candid.y*v_candid.y)); + Pointf v_branch(-pp[biggest_main_branch_id].lines().front().vector().x, -pp[biggest_main_branch_id].lines().front().vector().y); + v_branch.scale(1 / std::sqrt(v_branch.x*v_branch.x + v_branch.y*v_branch.y)); + dot_poly_branch = v_poly.x*v_branch.x + v_poly.y*v_branch.y; + dot_candidate_branch = v_candid.x*v_branch.x + v_candid.y*v_branch.y; + if (dot_poly_branch < 0) dot_poly_branch = 0; + if (dot_candidate_branch < 0) dot_candidate_branch = 0; + } + //test if it's useful to merge or not + //ie, don't merge 'T' but ok for 'Y', merge only lines of not disproportionate different length (ratio max: 4) + if (dot_poly_branch < 0.1 || dot_candidate_branch < 0.1 || + (polyline.length()>other.length() ? polyline.length() / other.length() : other.length() / polyline.length()) > 4) { + continue; + } if (other_dot > best_dot) { best_candidate = &other; best_idx = j; @@ -261,41 +458,91 @@ ExPolygon::medial_axis(const ExPolygon &bounds, double max_width, double min_wid } } if (best_candidate != nullptr) { + // delete very near points + remove_point_too_near(&polyline); + remove_point_too_near(best_candidate); - //assert polyline.size == best_candidate->size (see selection loop, an 'if' takes care of that) + // add point at the same pos than the other line to have a nicer fusion + add_point_same_percent(&polyline, best_candidate); + add_point_same_percent(best_candidate, &polyline); + //get the angle of the nearest points of the contour to see : _| (good) \_ (average) __(bad) + //sqrt because the result are nicer this way: don't over-penalize /_ angles + //TODO: try if we can achieve a better result if we use a different algo if the angle is <90° + const double coeff_angle_poly = (get_coeff_from_angle_countour(polyline.points.back(), *this)); + const double coeff_angle_candi = (get_coeff_from_angle_countour(best_candidate->points.back(), *this)); + + //this will encourage to follow the curve, a little, because it's shorter near the center + //without that, it tends to go to the outter rim. + double weight_poly = 2 - polyline.length() / max(polyline.length(), best_candidate->length()); + double weight_candi = 2 - best_candidate->length() / max(polyline.length(), best_candidate->length()); + weight_poly *= coeff_angle_poly; + weight_candi *= coeff_angle_candi; + const double coeff_poly = (dot_poly_branch * weight_poly) / (dot_poly_branch * weight_poly + dot_candidate_branch * weight_candi); + const double coeff_candi = 1.0 - coeff_poly; //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) { + while (idx_point < min(polyline.points.size(), best_candidate->points.size())) { //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]; + polyline.points[idx_point].x = polyline.points[idx_point].x * coeff_poly + best_candidate->points[idx_point].x * coeff_candi; + polyline.points[idx_point].y = polyline.points[idx_point].y * coeff_poly + best_candidate->points[idx_point].y * coeff_candi; + + // The width decrease with distance from the centerline. + // This formula is what works the best, even if it's not perfect (created empirically). 0->3% error on a gap fill on some tests. + //If someone find an other formula based on the properties of the voronoi algorithm used here, and it works better, please use it. + //or maybe just use the distance to nearest edge in bounds... + double value_from_current_width = 0.5*polyline.width[idx_point] * dot_poly_branch / max(dot_poly_branch, dot_candidate_branch); + value_from_current_width += 0.5*best_candidate->width[idx_point] * dot_candidate_branch / max(dot_poly_branch, dot_candidate_branch); + double value_from_dist = 2 * polyline.points[idx_point].distance_to(best_candidate->points[idx_point]); + value_from_dist *= sqrt(min(dot_poly_branch, dot_candidate_branch) / max(dot_poly_branch, dot_candidate_branch)); + polyline.width[idx_point] = value_from_current_width + value_from_dist; + //failsafe + if (polyline.width[idx_point] > max_width) polyline.width[idx_point] = max_width; + ++idx_point; } - //select if an end occur - polyline.endpoints.second &= best_candidate->endpoints.second; + if (idx_point < best_candidate->points.size()) { + if (idx_point + 1 < best_candidate->points.size()) { + //create a new polyline + pp.emplace_back(); + pp.back().endpoints.first = true; + pp.back().endpoints.second = best_candidate->endpoints.second; + for (int idx_point_new_line = idx_point; idx_point_new_line < best_candidate->points.size(); ++idx_point_new_line) { + pp.back().points.push_back(best_candidate->points[idx_point_new_line]); + pp.back().width.push_back(best_candidate->width[idx_point_new_line]); + } + } else { + //Add last point + polyline.points.push_back(best_candidate->points[idx_point]); + polyline.width.push_back(best_candidate->width[idx_point]); + //select if an end opccur + polyline.endpoints.second &= best_candidate->endpoints.second; + } + } else { + //select if an end opccur + 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 (polyline.points[idx_point - 1].distance_to(polyline.points[idx_point]) < SCALED_EPSILON) { if (idx_point < polyline.points.size() -1) { polyline.points.erase(polyline.points.begin() + idx_point); + polyline.width.erase(polyline.width.begin() + idx_point); } else { - polyline.points.erase(polyline.points.begin() + idx_point -1); + polyline.points.erase(polyline.points.begin() + idx_point - 1); + polyline.width.erase(polyline.width.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); + polyline.width.erase(polyline.width.begin() + idx_point); --idx_point; } } @@ -308,13 +555,73 @@ ExPolygon::medial_axis(const ExPolygon &bounds, double max_width, double min_wid pp.erase(pp.begin() + best_idx); changes = true; + break; } } + if (changes) { + concatThickPolylines(pp); + ///reorder, in case of change + std::sort(pp.begin(), pp.end(), [](const ThickPolyline & a, const ThickPolyline & b) { return a.length() < b.length(); }); + } } - + + // remove too small extrusion at start & end of polylines + changes = false; + for (size_t i = 0; i < pp.size(); ++i) { + ThickPolyline& polyline = pp[i]; + // remove bits with too small extrusion + while (polyline.points.size() > 1 && polyline.width.front() < min_width && polyline.endpoints.first) { + //try to split if possible + if (polyline.width[1] > min_width) { + double percent_can_keep = (min_width - polyline.width[0]) / (polyline.width[1] - polyline.width[0]); + if (polyline.points.front().distance_to(polyline.points[1]) * percent_can_keep > max_width / 2 + && polyline.points.front().distance_to(polyline.points[1])* (1 - percent_can_keep) > max_width / 2) { + //Can split => move the first point and assign a new weight. + //the update of endpoints wil be performed in concatThickPolylines + polyline.points.front().x = polyline.points.front().x + + (coord_t)((polyline.points[1].x - polyline.points.front().x) * percent_can_keep); + polyline.points.front().y = polyline.points.front().y + + (coord_t)((polyline.points[1].y - polyline.points.front().y) * percent_can_keep); + polyline.width.front() = min_width; + changes = true; + break; + } + } + polyline.points.erase(polyline.points.begin()); + polyline.width.erase(polyline.width.begin()); + changes = true; + } + while (polyline.points.size() > 1 && polyline.width.back() < min_width && polyline.endpoints.second) { + //try to split if possible + if (polyline.width[polyline.points.size()-2] > min_width) { + double percent_can_keep = (min_width - polyline.width.back()) / (polyline.width[polyline.points.size() - 2] - polyline.width.back()); + if (polyline.points.back().distance_to(polyline.points[polyline.points.size() - 2]) * percent_can_keep > max_width / 2 + && polyline.points.back().distance_to(polyline.points[polyline.points.size() - 2]) * (1-percent_can_keep) > max_width / 2) { + //Can split => move the first point and assign a new weight. + //the update of endpoints wil be performed in concatThickPolylines + polyline.points.back().x = polyline.points.back().x + + (coord_t)((polyline.points[polyline.points.size() - 2].x - polyline.points.back().x) * percent_can_keep); + polyline.points.back().y = polyline.points.back().y + + (coord_t)((polyline.points[polyline.points.size() - 2].y - polyline.points.back().y) * percent_can_keep); + polyline.width.back() = min_width; + changes = true; + break; + } + } + polyline.points.erase(polyline.points.end()-1); + polyline.width.erase(polyline.width.end() - 1); + changes = true; + } + if (polyline.points.size() < 2) { + //remove self if too small + pp.erase(pp.begin() + i); + --i; + } + } + if (changes) concatThickPolylines(pp); + // 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]; @@ -325,13 +632,13 @@ ExPolygon::medial_axis(const ExPolygon &bounds, double max_width, double min_wid Point new_front = polyline.points.front(); Point new_back = polyline.points.back(); if (polyline.endpoints.first && !bounds.has_boundary_point(new_front)) { - Line line(polyline.points.front(), polyline.points[1]); + Line line(polyline.points[1], polyline.points.front()); // prevent the line from touching on the other side, otherwise intersection() might return that solution - if (polyline.points.size() == 2) line.b = line.midpoint(); + if (polyline.points.size() == 2) line.a = line.midpoint(); - line.extend_start(max_width); - (void)bounds.contour.intersection(line, &new_front); + line.extend_end(max_width); + (void)bounds.contour.first_intersection(line, &new_front); } if (polyline.endpoints.second && !bounds.has_boundary_point(new_back)) { Line line( @@ -343,12 +650,14 @@ ExPolygon::medial_axis(const ExPolygon &bounds, double max_width, double min_wid if (polyline.points.size() == 2) line.a = line.midpoint(); line.extend_end(max_width); - (void)bounds.contour.intersection(line, &new_back); + (void)bounds.contour.first_intersection(line, &new_back); } polyline.points.front() = new_front; polyline.points.back() = new_back; } - + + + // concatenate, but even where multiple thickpolyline join, to create nice long strait polylines // 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 @@ -359,6 +668,7 @@ ExPolygon::medial_axis(const ExPolygon &bounds, double max_width, double min_wid // 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. + changes = false; for (size_t i = 0; i < pp.size(); ++i) { ThickPolyline& polyline = pp[i]; if (polyline.endpoints.first && polyline.endpoints.second) continue; // optimization @@ -395,32 +705,130 @@ ExPolygon::medial_axis(const ExPolygon &bounds, double max_width, double min_wid 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.width.insert(polyline.width.end(), best_candidate->width.begin() + 1, best_candidate->width.end()); polyline.endpoints.second = best_candidate->endpoints.second; - assert(polyline.width.size() == polyline.points.size()*2 - 2); - + assert(polyline.width.size() == polyline.points.size()); + changes = true; pp.erase(pp.begin() + best_idx); } } - + if (changes) concatThickPolylines(pp); + + //remove too thin polylines points (inside a polyline : split it) 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) { - pp.erase(pp.begin() + i); - --i; - removed = true; - continue; + // remove bits with too small extrusion + size_t idx_point = 0; + while (idx_point polyline.length()) { + shortest_size = polyline.length(); + shortest_idx = i; + } + + } + } + if (shortest_idx >= 0 && shortest_idx < pp.size()) { + pp.erase(pp.begin() + shortest_idx); + changes = true; + } + if (changes) concatThickPolylines(pp); + } + + //TODO: reduce the flow at the intersection ( + ) points ? + + //ensure the volume extruded is correct for what we have been asked + // => don't over-extrude + double surface = 0; + double volume = 0; + for (ThickPolyline& polyline : pp) { + for (ThickLine l : polyline.thicklines()) { + surface += l.length() * (l.a_width + l.b_width) / 2; + double width_mean = (l.a_width + l.b_width) / 2; + volume += height * (width_mean - height * (1. - 0.25 * PI)) * l.length(); + } } + // compute bounds volume + double boundsVolume = 0; + boundsVolume += height*bounds.area(); + // add external "perimeter gap" + double perimeterRoundGap = bounds.contour.length() * height * (1 - 0.25*PI) * 0.5; + // add holes "perimeter gaps" + double holesGaps = 0; + for (auto hole = bounds.holes.begin(); hole != bounds.holes.end(); ++hole) { + holesGaps += hole->length() * height * (1 - 0.25*PI) * 0.5; + } + boundsVolume += perimeterRoundGap + holesGaps; + if (boundsVolume < volume) { + //reduce width + double reduce_by = boundsVolume / volume; + for (ThickPolyline& polyline : pp) { + for (ThickLine l : polyline.thicklines()) { + l.a_width *= reduce_by; + l.b_width *= reduce_by; + } + } + } polylines->insert(polylines->end(), pp.begin(), pp.end()); } @@ -428,7 +836,7 @@ void ExPolygon::medial_axis(double max_width, double min_width, Polylines* polylines) const { ThickPolylines tp; - this->medial_axis(*this, max_width, min_width, &tp); + this->medial_axis(*this, max_width, min_width, &tp, max_width/2.0); polylines->insert(polylines->end(), tp.begin(), tp.end()); } diff --git a/xs/src/libslic3r/ExPolygon.hpp b/xs/src/libslic3r/ExPolygon.hpp index fcdabc2723..7f636705b5 100644 --- a/xs/src/libslic3r/ExPolygon.hpp +++ b/xs/src/libslic3r/ExPolygon.hpp @@ -42,7 +42,7 @@ class ExPolygon Polygons simplify_p(double tolerance) const; ExPolygons simplify(double tolerance) const; void simplify(double tolerance, ExPolygons* expolygons) const; - void medial_axis(const ExPolygon &bounds, double max_width, double min_width, ThickPolylines* polylines) const; + void medial_axis(const ExPolygon &bounds, double max_width, double min_width, ThickPolylines* polylines, double height) 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; diff --git a/xs/src/libslic3r/Geometry.cpp b/xs/src/libslic3r/Geometry.cpp index b972e7e176..77b9b7f376 100644 --- a/xs/src/libslic3r/Geometry.cpp +++ b/xs/src/libslic3r/Geometry.cpp @@ -724,7 +724,7 @@ MedialAxis::build(ThickPolylines* polylines) polyline.endpoints.first = rpolyline.endpoints.second; } - assert(polyline.width.size() == polyline.points.size()*2 - 2); + assert(polyline.width.size() == polyline.points.size()); // prevent loop endpoints from being extended if (polyline.first_point().coincides_with(polyline.last_point())) { @@ -770,8 +770,8 @@ MedialAxis::process_edge_neighbors(const VD::edge_type* edge, ThickPolyline* pol Point new_point(neighbor->vertex1()->x(), neighbor->vertex1()->y()); polyline->points.push_back(new_point); - polyline->width.push_back(this->thickness[neighbor].first); polyline->width.push_back(this->thickness[neighbor].second); + (void)this->edges.erase(neighbor); (void)this->edges.erase(neighbor->twin()); edge = neighbor; @@ -851,34 +851,39 @@ MedialAxis::validate_edge(const VD::edge_type* edge) ? line.b.distance_to(segment_l)*2 : line.b.distance_to(this->retrieve_endpoint(cell_l))*2; - if (cell_l->contains_segment() && cell_r->contains_segment()) { - // calculate the relative angle between the two boundary segments - double angle = fabs(segment_r.orientation() - segment_l.orientation()); - if (angle > PI) angle = 2*PI - angle; - assert(angle >= 0 && angle <= PI); - - // fabs(angle) ranges from 0 (collinear, same direction) to PI (collinear, opposite direction) - // we're interested only in segments close to the second case (facing segments) - // so we allow some tolerance. - // this filter ensures that we're dealing with a narrow/oriented area (longer than thick) - // we don't run it on edges not generated by two segments (thus generated by one segment - // and the endpoint of another segment), since their orientation would not be meaningful - if (PI - angle > PI/8) { - // angle is not narrow enough - - // only apply this filter to segments that are not too short otherwise their - // angle could possibly be not meaningful - if (w0 < SCALED_EPSILON || w1 < SCALED_EPSILON || line.length() >= this->min_width) - return false; - } - } else { - if (w0 < SCALED_EPSILON || w1 < SCALED_EPSILON) - return false; - } - - if (w0 < this->min_width && w1 < this->min_width) - return false; - + //don't remove the line that goes to the intersection of the contour + // we use them to create nicer thin wall lines + //if (cell_l->contains_segment() && cell_r->contains_segment()) { + // // calculate the relative angle between the two boundary segments + // double angle = fabs(segment_r.orientation() - segment_l.orientation()); + // if (angle > PI) angle = 2*PI - angle; + // assert(angle >= 0 && angle <= PI); + // + // // fabs(angle) ranges from 0 (collinear, same direction) to PI (collinear, opposite direction) + // // we're interested only in segments close to the second case (facing segments) + // // so we allow some tolerance. + // // this filter ensures that we're dealing with a narrow/oriented area (longer than thick) + // // we don't run it on edges not generated by two segments (thus generated by one segment + // // and the endpoint of another segment), since their orientation would not be meaningful + // if (PI - angle > PI/8) { + // // angle is not narrow enough + // + // // only apply this filter to segments that are not too short otherwise their + // // angle could possibly be not meaningful + // if (w0 < SCALED_EPSILON || w1 < SCALED_EPSILON || line.length() >= this->min_width) + // return false; + // } + //} else { + // if (w0 < SCALED_EPSILON || w1 < SCALED_EPSILON) + // return false; + //} + + // don't do that before we try to fusion them + //if (w0 < this->min_width && w1 < this->min_width) + // return false; + // + + //shouldn't occur if perimeter_generator is well made if (w0 > this->max_width && w1 > this->max_width) return false; diff --git a/xs/src/libslic3r/MultiPoint.cpp b/xs/src/libslic3r/MultiPoint.cpp index d8ee1d09d6..4625bcffd2 100644 --- a/xs/src/libslic3r/MultiPoint.cpp +++ b/xs/src/libslic3r/MultiPoint.cpp @@ -158,6 +158,30 @@ MultiPoint::intersection(const Line& line, Point* intersection) const return false; } +bool +MultiPoint::first_intersection(const Line& line, Point* intersection) const +{ + bool found = false; + double dmin = 0.; + for (const Line &l : this->lines()) { + Point ip; + if (l.intersection(line, &ip)) { + if (! found) { + found = true; + dmin = ip.distance_to(line.a); + *intersection = ip; + } else { + double d = ip.distance_to(line.a); + if (d < dmin) { + dmin = d; + *intersection = ip; + } + } + } + } + return found; +} + std::string MultiPoint::dump_perl() const { diff --git a/xs/src/libslic3r/MultiPoint.hpp b/xs/src/libslic3r/MultiPoint.hpp index acb409b205..9f67ca9525 100644 --- a/xs/src/libslic3r/MultiPoint.hpp +++ b/xs/src/libslic3r/MultiPoint.hpp @@ -43,6 +43,7 @@ class MultiPoint void append(const Points &points); void append(const Points::const_iterator &begin, const Points::const_iterator &end); bool intersection(const Line& line, Point* intersection) const; + bool first_intersection(const Line& line, Point* intersection) const; std::string dump_perl() const; static Points _douglas_peucker(const Points &points, const double tolerance); diff --git a/xs/src/libslic3r/PerimeterGenerator.cpp b/xs/src/libslic3r/PerimeterGenerator.cpp index e789a94dce..2ae788c824 100644 --- a/xs/src/libslic3r/PerimeterGenerator.cpp +++ b/xs/src/libslic3r/PerimeterGenerator.cpp @@ -85,44 +85,65 @@ PerimeterGenerator::process() for (int i = 0; i <= loop_number+1; ++i) { // outer loop is 0 Polygons offsets; if (i == 0) { - // the minimum thickness of a single loop is: - // ext_width/2 + ext_spacing/2 + spacing/2 + width/2 + // compute next onion, without taking care of thin_walls : destroy too thin areas. + if (!this->config->thin_walls) + offsets = offset(last, -(float)(ext_pwidth / 2)); + + + // look for thin walls if (this->config->thin_walls) { + // the minimum thickness of a single loop is: + // ext_width/2 + ext_spacing/2 + spacing/2 + width/2 offsets = offset2( last, -(ext_pwidth/2 + ext_min_spacing/2 - 1), - +(ext_min_spacing/2 - 1) - ); - } else { - offsets = offset(last, -ext_pwidth/2); - } - - // look for thin walls - if (this->config->thin_walls) { - Polygons no_thin_zone = offset(offsets, +ext_pwidth/2); - Polygons diffpp = diff( - last, - no_thin_zone, - true // medial axis requires non-overlapping geometry - ); + +(ext_min_spacing/2 - 1)); + + // detect edge case where a curve can be split in multiple small chunks. + Polygons no_thin_onion = offset(last, -(float)(ext_pwidth / 2)); + if (no_thin_onion.size()>0 && offsets.size() > 3 * no_thin_onion.size()) { + //use a sightly smaller spacing to try to drastically improve the split + Polygons next_onion_secondTry = offset2( + last, + -(float)(ext_pwidth / 2 + ext_min_spacing / 2.5 - 1), + +(float)(ext_min_spacing / 2.5 - 1)); + if (abs(((int32_t)offsets.size()) - ((int32_t)no_thin_onion.size())) > + 2*abs(((int32_t)next_onion_secondTry.size()) - ((int32_t)no_thin_onion.size()))) { + offsets = next_onion_secondTry; + } + } // 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); - 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) { - ExPolygons bounds = _clipper_ex(ClipperLib::ctUnion, (Polygons)*ex, to_polygons(anchor), true); - //search our bound + Polygons no_thin_zone = offset(offsets, +ext_pwidth/2); + // medial axis requires non-overlapping geometry + Polygons thin_zones = diff(last, no_thin_zone, true); + //don't use offset2_ex, because we don't want to merge the zones that have been separated. + Polygons expp = offset(thin_zones, (float)(-min_width / 2)); + //we push the bits removed and put them into what we will use as our anchor + if (expp.size() > 0) { + no_thin_zone = diff(last, offset(expp, (float)(min_width / 2)), true); + } + // compute a bit of overlap to anchor thin walls inside the print. + for (Polygon &ex : expp) { + //growing back the polygon + //a vary little bit of overlap can be created here with other thin polygon, but it's more useful than worisome. + ExPolygons ex_bigger = offset_ex(ex, (float)(min_width / 2)); + if (ex_bigger.size() != 1) continue; // impossible error, growing a single polygon can't create multiple or 0. + ExPolygons anchor = intersection_ex(offset(ex, (float)(ext_pwidth / 2), + CLIPPER_OFFSET_SCALE, jtSquare, 3), no_thin_zone, true); + ExPolygons bounds = _clipper_ex(ClipperLib::ctUnion, to_polygons(ex_bigger), to_polygons(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 - ex->medial_axis(bound, ext_pwidth + ext_pspacing2, min_width, &thin_walls); - continue; + if (!intersection_ex(ex_bigger[0], bound).empty()) { + //be sure it's not too small to extrude reliably + if (ex_bigger[0].area() > min_width*(ext_pwidth + ext_pspacing2)) { + // the maximum thickness of our thin wall area is equal to the minimum thickness of a single loop + ex_bigger[0].medial_axis(bound, ext_pwidth + ext_pspacing2, min_width, + &thin_walls, this->layer_height); + } + break; } } } @@ -292,9 +313,13 @@ PerimeterGenerator::process() ); ThickPolylines polylines; - for (ExPolygons::const_iterator ex = gaps_ex.begin(); ex != gaps_ex.end(); ++ex) - ex->medial_axis(*ex, max, min, &polylines); - + for (const ExPolygon &ex : gaps_ex) { + //remove too small gaps that are too hard to fill. + //ie one that are smaller than an extrusion with width of min and a length of max. + if (ex.area() > min*max) { + ex.medial_axis(ex, max, min, &polylines, this->layer_height); + } + } if (!polylines.empty()) { ExtrusionEntityCollection gap_fill = this->_variable_width(polylines, erGapFill, this->solid_infill_flow); diff --git a/xs/src/libslic3r/Polyline.cpp b/xs/src/libslic3r/Polyline.cpp index b80335073f..a75485ed63 100644 --- a/xs/src/libslic3r/Polyline.cpp +++ b/xs/src/libslic3r/Polyline.cpp @@ -6,6 +6,7 @@ #include "ClipperUtils.hpp" #include #include +#include namespace Slic3r { @@ -235,8 +236,8 @@ ThickPolyline::thicklines() const lines.reserve(this->points.size() - 1); for (size_t i = 0; i < this->points.size()-1; ++i) { ThickLine line(this->points[i], this->points[i+1]); - line.a_width = this->width[2*i]; - line.b_width = this->width[2*i+1]; + line.a_width = this->width[i]; + line.b_width = this->width[i + 1]; lines.push_back(line); } } @@ -251,4 +252,97 @@ ThickPolyline::reverse() std::swap(this->endpoints.first, this->endpoints.second); } +void +concatThickPolylines(ThickPolylines& pp) { + bool changes = true; + while (changes){ + changes = false; + //concat polyline if only 2 polyline at a point + for (size_t i = 0; i < pp.size(); ++i) { + ThickPolyline *polyline = &pp[i]; + + int32_t id_candidate_first_point = -1; + int32_t id_candidate_last_point = -1; + int32_t nbCandidate_first_point = 0; + int32_t nbCandidate_last_point = 0; + // find another polyline starting here + for (size_t j = 0; j < pp.size(); ++j) { + if (j == i) continue; + ThickPolyline *other = &pp[j]; + if (polyline->last_point().coincides_with(other->last_point())) { + other->reverse(); + id_candidate_last_point = j; + nbCandidate_last_point++; + } else if (polyline->first_point().coincides_with(other->last_point())) { + id_candidate_first_point = j; + nbCandidate_first_point++; + } else if (polyline->first_point().coincides_with(other->first_point())) { + id_candidate_first_point = j; + nbCandidate_first_point++; + other->reverse(); + } else if (polyline->last_point().coincides_with(other->first_point())) { + id_candidate_last_point = j; + nbCandidate_last_point++; + } else { + continue; + } + } + if (id_candidate_last_point == id_candidate_first_point && nbCandidate_first_point == 1 && nbCandidate_last_point == 1) { + // it's a trap! it's a loop! + if (pp[id_candidate_first_point].points.size() > 2) { + polyline->points.insert(polyline->points.begin(), pp[id_candidate_first_point].points.begin() + 1, pp[id_candidate_first_point].points.end() - 1); + polyline->width.insert(polyline->width.begin(), pp[id_candidate_first_point].width.begin() + 1, pp[id_candidate_first_point].width.end() - 1); + } + pp.erase(pp.begin() + id_candidate_first_point); + changes = true; + polyline->endpoints.first = false; + polyline->endpoints.second = false; + continue; + } + + if (nbCandidate_first_point == 1) { + //concat at front + polyline->width[0] = std::max(polyline->width.front(), pp[id_candidate_first_point].width.back()); + polyline->points.insert(polyline->points.begin(), pp[id_candidate_first_point].points.begin(), pp[id_candidate_first_point].points.end() - 1); + polyline->width.insert(polyline->width.begin(), pp[id_candidate_first_point].width.begin(), pp[id_candidate_first_point].width.end() - 1); + polyline->endpoints.first = pp[id_candidate_first_point].endpoints.first; + pp.erase(pp.begin() + id_candidate_first_point); + changes = true; + if (id_candidate_first_point < i) { + i--; + polyline = &pp[i]; + } + if (id_candidate_last_point > id_candidate_first_point) { + id_candidate_last_point--; + } + } else if (nbCandidate_first_point == 0 && !polyline->endpoints.first && !polyline->first_point().coincides_with(polyline->last_point())) { + //update endpoint + polyline->endpoints.first = true; + } + if (nbCandidate_last_point == 1) { + //concat at back + polyline->width[polyline->width.size() - 1] = std::max(polyline->width.back(), pp[id_candidate_last_point].width.front()); + polyline->points.insert(polyline->points.end(), pp[id_candidate_last_point].points.begin() + 1, pp[id_candidate_last_point].points.end()); + polyline->width.insert(polyline->width.end(), pp[id_candidate_last_point].width.begin() + 1, pp[id_candidate_last_point].width.end()); + polyline->endpoints.second = pp[id_candidate_last_point].endpoints.second; + pp.erase(pp.begin() + id_candidate_last_point); + changes = true; + if (id_candidate_last_point < i) { + i--; + polyline = &pp[i]; + } + } else if (nbCandidate_last_point == 0 && !polyline->endpoints.second && !polyline->first_point().coincides_with(polyline->last_point())) { + //update endpoint + polyline->endpoints.second = true; + } + + if (polyline->last_point().coincides_with(polyline->first_point())) { + //the concat has created a loop : update endpoints + polyline->endpoints.first = false; + polyline->endpoints.second = false; + } + } + } +} + } diff --git a/xs/src/libslic3r/Polyline.hpp b/xs/src/libslic3r/Polyline.hpp index 49e261bdf7..c17c06ba91 100644 --- a/xs/src/libslic3r/Polyline.hpp +++ b/xs/src/libslic3r/Polyline.hpp @@ -36,15 +36,26 @@ class Polyline : public MultiPoint { Polygons grow(double delta, double scale = CLIPPER_OFFSET_SCALE, ClipperLib::JoinType joinType = ClipperLib::jtSquare, double miterLimit = 3.0) const; }; + +/// ThickPolyline : a polyline with a width for each point +/// This calss has a vector of coordf_t, it must be the same size than points. +/// it's used to store the size of the line at this point. +/// Also, the endpoint let us know if the front() and back() of the polyline +/// join something or is a dead-end. class ThickPolyline : public Polyline { public: + /// width size must be == point size std::vector width; + /// if true => it's an endpoint, if false it join an other ThickPolyline. first is at front(), second is at back() std::pair endpoints; ThickPolyline() : endpoints(std::make_pair(false, false)) {}; ThickLines thicklines() const; void reverse(); }; +/// concatenate poylines if possible and refresh the endpoints +void concatThickPolylines(ThickPolylines &polylines); + inline Polylines to_polylines(const Polygons &polygons) { From 2841809ef8b1515557e16bd6676cd584db0fb7fd Mon Sep 17 00:00:00 2001 From: supermerill Date: Tue, 25 Sep 2018 10:52:29 +0200 Subject: [PATCH 02/25] thin wall : medial axis: * corrections from review * more functions for a clearer code * now simplify the frontier with the anchor to avoid weird edge-cases. * new post-process: remove "curve edge" before merging * new post-process: cube corner: the small bit that go from the voronoi corner to the cube corner is now a "pulling string" that pull the voronoi corner a bit to make a nicer cube. * _variable_width : reduce the threshold for creating a new extrusion by half vs threshold to create segments (if not, it doesn't create enough) --- xs/src/libslic3r/ExPolygon.cpp | 661 +----------------------- xs/src/libslic3r/ExPolygon.hpp | 1 + xs/src/libslic3r/Geometry.cpp | 261 ---------- xs/src/libslic3r/Geometry.hpp | 26 +- xs/src/libslic3r/PerimeterGenerator.cpp | 11 +- xs/src/libslic3r/Polyline.cpp | 8 +- 6 files changed, 37 insertions(+), 931 deletions(-) diff --git a/xs/src/libslic3r/ExPolygon.cpp b/xs/src/libslic3r/ExPolygon.cpp index 3ac4f5f90d..8eef314e0d 100644 --- a/xs/src/libslic3r/ExPolygon.cpp +++ b/xs/src/libslic3r/ExPolygon.cpp @@ -1,5 +1,6 @@ #include "BoundingBox.hpp" #include "ExPolygon.hpp" +#include "MedialAxis.hpp" #include "Geometry.hpp" #include "Polygon.hpp" #include "Line.hpp" @@ -185,651 +186,37 @@ ExPolygon::simplify(double tolerance, ExPolygons* expolygons) const } /// remove point that are at SCALED_EPSILON * 2 distance. +//simplier than simplify void -remove_point_too_near(ThickPolyline* to_reduce) { - const int32_t smallest = SCALED_EPSILON * 2; - uint32_t id = 1; - while (id < to_reduce->points.size() - 2) { - uint32_t newdist = min(to_reduce->points[id].distance_to(to_reduce->points[id - 1]) - , to_reduce->points[id].distance_to(to_reduce->points[id + 1])); - if (newdist < smallest) { - to_reduce->points.erase(to_reduce->points.begin() + id); - to_reduce->width.erase(to_reduce->width.begin() + id); - } else { - ++id; - } +ExPolygon::remove_point_too_near(const coord_t tolerance) { + size_t id = 1; + while (id < this->contour.points.size() - 1) { + size_t newdist = min(this->contour.points[id].distance_to(this->contour.points[id - 1]) + , this->contour.points[id].distance_to(this->contour.points[id + 1])); + if (newdist < tolerance) { + this->contour.points.erase(this->contour.points.begin() + id); + newdist = this->contour.points[id].distance_to(this->contour.points[id - 1]); } -} - -/// add points from pattern to to_modify at the same % of the length -/// so not add if an other point is present at the correct position -void -add_point_same_percent(ThickPolyline* pattern, ThickPolyline* to_modify) { - const double to_modify_length = to_modify->length(); - const double percent_epsilon = SCALED_EPSILON / to_modify_length; - const double pattern_length = pattern->length(); - - double percent_length = 0; - for (uint32_t idx_point = 1; idx_point < pattern->points.size() - 1; ++idx_point) { - percent_length += pattern->points[idx_point-1].distance_to(pattern->points[idx_point]) / pattern_length; - //find position - uint32_t idx_other = 1; - double percent_length_other_before = 0; - double percent_length_other = 0; - while (idx_other < to_modify->points.size()) { - percent_length_other_before = percent_length_other; - percent_length_other += to_modify->points[idx_other-1].distance_to(to_modify->points[idx_other]) - / to_modify_length; - if (percent_length_other > percent_length - percent_epsilon) { - //if higher (we have gone over it) - break; - } - ++idx_other; - } - if (percent_length_other > percent_length + percent_epsilon) { - //insert a new point before the position - double percent_dist = (percent_length - percent_length_other_before) / (percent_length_other - percent_length_other_before); - coordf_t new_width = to_modify->width[idx_other - 1] * (1 - percent_dist); - new_width += to_modify->width[idx_other] * (percent_dist); - Point new_point; - new_point.x = (coord_t)((double)(to_modify->points[idx_other - 1].x) * (1 - percent_dist)); - new_point.x += (coord_t)((double)(to_modify->points[idx_other].x) * (percent_dist)); - new_point.y = (coord_t)((double)(to_modify->points[idx_other - 1].y) * (1 - percent_dist)); - new_point.y += (coord_t)((double)(to_modify->points[idx_other].y) * (percent_dist)); - to_modify->width.insert(to_modify->width.begin() + idx_other, new_width); - to_modify->points.insert(to_modify->points.begin() + idx_other, new_point); + //go to next one + //if you removed a point, it check if the next one isn't too near from the previous one. + // if not, it byepass it. + if (newdist > tolerance) { + ++id; } } -} - -/// find the nearest angle in the contour (or 2 nearest if it's difficult to choose) -/// return 1 for an angle of 90° and 0 for an angle of 0° or 180° -double -get_coeff_from_angle_countour(Point &point, const ExPolygon &contour) { - double nearestDist = point.distance_to(contour.contour.points.front()); - Point nearest = contour.contour.points.front(); - uint32_t id_nearest = 0; - double nearDist = nearestDist; - Point near = nearest; - uint32_t id_near=0; - for (uint32_t id_point = 1; id_point < contour.contour.points.size(); ++id_point) { - if (nearestDist > point.distance_to(contour.contour.points[id_point])) { - nearestDist = point.distance_to(contour.contour.points[id_point]); - near = nearest; - nearest = contour.contour.points[id_point]; - id_near = id_nearest; - id_nearest = id_point; - } + if (this->contour.points.front().distance_to(this->contour.points.back()) < tolerance) { + this->contour.points.erase(this->contour.points.end() -1); } - double angle = 0; - Point point_before = id_nearest == 0 ? contour.contour.points.back() : contour.contour.points[id_nearest - 1]; - Point point_after = id_nearest == contour.contour.points.size()-1 ? contour.contour.points.front() : contour.contour.points[id_nearest + 1]; - //compute angle - angle = min(nearest.ccw_angle(point_before, point_after), nearest.ccw_angle(point_after, point_before)); - //compute the diff from 90° - angle = abs(angle - PI / 2); - if (near.x != nearest.x && near.y != nearest.y && max(nearestDist, nearDist) + SCALED_EPSILON < nearest.distance_to(near)) { - //not only nearest - Point point_before = id_near == 0 ? contour.contour.points.back() : contour.contour.points[id_near - 1]; - Point point_after = id_near == contour.contour.points.size() - 1 ? contour.contour.points.front() : contour.contour.points[id_near + 1]; - double angle2 = min(nearest.ccw_angle(point_before, point_after), nearest.ccw_angle(point_after, point_before)); - angle2 = abs(angle - PI / 2); - angle = (angle + angle2) / 2; - } - - return 1-(angle/(PI/2)); } void -ExPolygon::medial_axis(const ExPolygon &bounds, double max_width, double min_width, ThickPolylines* polylines, double height) const -{ - // init helper object - Slic3r::Geometry::MedialAxis ma(max_width, min_width, this); - ma.lines = this->lines(); - - // compute the Voronoi diagram and extract medial axis polylines - ThickPolylines pp; - ma.build(&pp); - - - //{ - // stringstream stri; - // stri << "medial_axis" << id << ".svg"; - // SVG svg(stri.str()); - // svg.draw(bounds); - // 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. - 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())); - - concatThickPolylines(pp); - //reoder pp by length (ascending) It's really important to do that to avoid building the line from the width insteand of the length - std::sort(pp.begin(), pp.end(), [](const ThickPolyline & a, const ThickPolyline & b) { return a.length() < b.length(); }); - - // 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. - // - int id_f = 0; - bool changes = true; - while (changes) { - changes = false; - for (size_t i = 0; i < pp.size(); ++i) { - ThickPolyline& polyline = pp[i]; - - //simple check to see if i can be fusionned - if (!polyline.endpoints.first && !polyline.endpoints.second) continue; - - ThickPolyline* best_candidate = nullptr; - float best_dot = -1; - int best_idx = 0; - double dot_poly_branch = 0; - double dot_candidate_branch = 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; - } - //std::cout << " try : " << i << ":" << j << " : " << - // (polyline.points.size() < 2 && other.points.size() < 2) << - // (!polyline.endpoints.second || !other.endpoints.second) << - // ((polyline.points.back().distance_to(other.points.back()) - // + (polyline.width.back() + other.width.back()) / 4) - // > max_width*1.05) << - // (abs(polyline.length() - other.length()) > max_width / 2) << "\n"; - - //// mergeable tests - if (polyline.points.size() < 2 && other.points.size() < 2) continue; - if (!polyline.endpoints.second || !other.endpoints.second) continue; - // test if the new width will not be too big if a fusion occur - //note that this isn't the real calcul. It's just to avoid merging lines too far apart. - if ( - ((polyline.points.back().distance_to(other.points.back()) - + (polyline.width.back() + other.width.back()) / 4) - > max_width*1.05)) - continue; - // test if the lines are not too different in length. - if (abs(polyline.length() - other.length()) > max_width / 2) continue; - - - //test if we don't merge with something too different and without any relevance. - double coeffSizePolyI = 1; - if (polyline.width.back() == 0) { - coeffSizePolyI = 0.1 + 0.9*get_coeff_from_angle_countour(polyline.points.back(), *this); - } - double coeffSizeOtherJ = 1; - if (other.width.back() == 0) { - coeffSizeOtherJ = 0.1+0.9*get_coeff_from_angle_countour(other.points.back(), *this); - } - if (abs(polyline.length()*coeffSizePolyI - other.length()*coeffSizeOtherJ) > max_width / 2) continue; - - //compute angle to see if it's better than previous ones (straighter = better). - 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; - - // Get the branch/line in wich we may merge, if possible - // with that, we can decide what is important, and how we can merge that. - // angle_poly - angle_candi =90° => one is useless - // both angle are equal => both are useful with same strength - // ex: Y => | both are useful to crete a nice line - // ex2: TTTTT => ----- these 90° useless lines should be discarded - bool find_main_branch = false; - int biggest_main_branch_id = 0; - int biggest_main_branch_length = 0; - for (size_t k = 0; k < pp.size(); ++k) { - //std::cout << "try to find main : " << k << " ? " << i << " " << j << " "; - if (k == i | k == j) continue; - ThickPolyline& main = pp[k]; - if (polyline.first_point().coincides_with(main.last_point())) { - main.reverse(); - if (!main.endpoints.second) - find_main_branch = true; - else if (biggest_main_branch_length < main.length()) { - biggest_main_branch_id = k; - biggest_main_branch_length = main.length(); - } - } else if (polyline.first_point().coincides_with(main.first_point())) { - if (!main.endpoints.second) - find_main_branch = true; - else if (biggest_main_branch_length < main.length()) { - biggest_main_branch_id = k; - biggest_main_branch_length = main.length(); - } - } - if (find_main_branch) { - //use this variable to store the good index and break to compute it - biggest_main_branch_id = k; - break; - } - } - if (!find_main_branch && biggest_main_branch_length == 0) { - // nothing -> it's impossible! - dot_poly_branch = 0.707; - dot_candidate_branch = 0.707; - //std::cout << "no main branch... impossible!!\n"; - } else if (!find_main_branch && - (pp[biggest_main_branch_id].length() < polyline.length() || pp[biggest_main_branch_id].length() < other.length()) ){ - //the main branch should have no endpoint or be bigger! - //here, it have an endpoint, and is not the biggest -> bad! - continue; - } else { - //compute the dot (biggest_main_branch_id) - 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_candid(other.lines().front().vector().x, other.lines().front().vector().y); - v_candid.scale(1 / std::sqrt(v_candid.x*v_candid.x + v_candid.y*v_candid.y)); - Pointf v_branch(-pp[biggest_main_branch_id].lines().front().vector().x, -pp[biggest_main_branch_id].lines().front().vector().y); - v_branch.scale(1 / std::sqrt(v_branch.x*v_branch.x + v_branch.y*v_branch.y)); - dot_poly_branch = v_poly.x*v_branch.x + v_poly.y*v_branch.y; - dot_candidate_branch = v_candid.x*v_branch.x + v_candid.y*v_branch.y; - if (dot_poly_branch < 0) dot_poly_branch = 0; - if (dot_candidate_branch < 0) dot_candidate_branch = 0; - } - //test if it's useful to merge or not - //ie, don't merge 'T' but ok for 'Y', merge only lines of not disproportionate different length (ratio max: 4) - if (dot_poly_branch < 0.1 || dot_candidate_branch < 0.1 || - (polyline.length()>other.length() ? polyline.length() / other.length() : other.length() / polyline.length()) > 4) { - continue; - } - if (other_dot > best_dot) { - best_candidate = &other; - best_idx = j; - best_dot = other_dot; - } - } - if (best_candidate != nullptr) { - // delete very near points - remove_point_too_near(&polyline); - remove_point_too_near(best_candidate); - - // add point at the same pos than the other line to have a nicer fusion - add_point_same_percent(&polyline, best_candidate); - add_point_same_percent(best_candidate, &polyline); - - //get the angle of the nearest points of the contour to see : _| (good) \_ (average) __(bad) - //sqrt because the result are nicer this way: don't over-penalize /_ angles - //TODO: try if we can achieve a better result if we use a different algo if the angle is <90° - const double coeff_angle_poly = (get_coeff_from_angle_countour(polyline.points.back(), *this)); - const double coeff_angle_candi = (get_coeff_from_angle_countour(best_candidate->points.back(), *this)); - - //this will encourage to follow the curve, a little, because it's shorter near the center - //without that, it tends to go to the outter rim. - double weight_poly = 2 - polyline.length() / max(polyline.length(), best_candidate->length()); - double weight_candi = 2 - best_candidate->length() / max(polyline.length(), best_candidate->length()); - weight_poly *= coeff_angle_poly; - weight_candi *= coeff_angle_candi; - const double coeff_poly = (dot_poly_branch * weight_poly) / (dot_poly_branch * weight_poly + dot_candidate_branch * weight_candi); - const double coeff_candi = 1.0 - coeff_poly; - //iterate the points - // as voronoi should create symetric thing, we can iterate synchonously - unsigned int idx_point = 1; - while (idx_point < min(polyline.points.size(), best_candidate->points.size())) { - //fusion - polyline.points[idx_point].x = polyline.points[idx_point].x * coeff_poly + best_candidate->points[idx_point].x * coeff_candi; - polyline.points[idx_point].y = polyline.points[idx_point].y * coeff_poly + best_candidate->points[idx_point].y * coeff_candi; - - // The width decrease with distance from the centerline. - // This formula is what works the best, even if it's not perfect (created empirically). 0->3% error on a gap fill on some tests. - //If someone find an other formula based on the properties of the voronoi algorithm used here, and it works better, please use it. - //or maybe just use the distance to nearest edge in bounds... - double value_from_current_width = 0.5*polyline.width[idx_point] * dot_poly_branch / max(dot_poly_branch, dot_candidate_branch); - value_from_current_width += 0.5*best_candidate->width[idx_point] * dot_candidate_branch / max(dot_poly_branch, dot_candidate_branch); - double value_from_dist = 2 * polyline.points[idx_point].distance_to(best_candidate->points[idx_point]); - value_from_dist *= sqrt(min(dot_poly_branch, dot_candidate_branch) / max(dot_poly_branch, dot_candidate_branch)); - polyline.width[idx_point] = value_from_current_width + value_from_dist; - //failsafe - if (polyline.width[idx_point] > max_width) polyline.width[idx_point] = max_width; - - ++idx_point; - } - if (idx_point < best_candidate->points.size()) { - if (idx_point + 1 < best_candidate->points.size()) { - //create a new polyline - pp.emplace_back(); - pp.back().endpoints.first = true; - pp.back().endpoints.second = best_candidate->endpoints.second; - for (int idx_point_new_line = idx_point; idx_point_new_line < best_candidate->points.size(); ++idx_point_new_line) { - pp.back().points.push_back(best_candidate->points[idx_point_new_line]); - pp.back().width.push_back(best_candidate->width[idx_point_new_line]); - } - } else { - //Add last point - polyline.points.push_back(best_candidate->points[idx_point]); - polyline.width.push_back(best_candidate->width[idx_point]); - //select if an end opccur - polyline.endpoints.second &= best_candidate->endpoints.second; - } - - } else { - //select if an end opccur - 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) { - if (polyline.points[idx_point - 1].distance_to(polyline.points[idx_point]) < SCALED_EPSILON) { - if (idx_point < polyline.points.size() -1) { - polyline.points.erase(polyline.points.begin() + idx_point); - polyline.width.erase(polyline.width.begin() + idx_point); - } else { - polyline.points.erase(polyline.points.begin() + idx_point - 1); - polyline.width.erase(polyline.width.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) { - if (!bounds.contains_b(polyline.points[idx_point])) { - polyline.points.erase(polyline.points.begin() + idx_point); - polyline.width.erase(polyline.width.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; - break; - } - } - if (changes) { - concatThickPolylines(pp); - ///reorder, in case of change - std::sort(pp.begin(), pp.end(), [](const ThickPolyline & a, const ThickPolyline & b) { return a.length() < b.length(); }); - } - } - - // remove too small extrusion at start & end of polylines - changes = false; - for (size_t i = 0; i < pp.size(); ++i) { - ThickPolyline& polyline = pp[i]; - // remove bits with too small extrusion - while (polyline.points.size() > 1 && polyline.width.front() < min_width && polyline.endpoints.first) { - //try to split if possible - if (polyline.width[1] > min_width) { - double percent_can_keep = (min_width - polyline.width[0]) / (polyline.width[1] - polyline.width[0]); - if (polyline.points.front().distance_to(polyline.points[1]) * percent_can_keep > max_width / 2 - && polyline.points.front().distance_to(polyline.points[1])* (1 - percent_can_keep) > max_width / 2) { - //Can split => move the first point and assign a new weight. - //the update of endpoints wil be performed in concatThickPolylines - polyline.points.front().x = polyline.points.front().x + - (coord_t)((polyline.points[1].x - polyline.points.front().x) * percent_can_keep); - polyline.points.front().y = polyline.points.front().y + - (coord_t)((polyline.points[1].y - polyline.points.front().y) * percent_can_keep); - polyline.width.front() = min_width; - changes = true; - break; - } - } - polyline.points.erase(polyline.points.begin()); - polyline.width.erase(polyline.width.begin()); - changes = true; - } - while (polyline.points.size() > 1 && polyline.width.back() < min_width && polyline.endpoints.second) { - //try to split if possible - if (polyline.width[polyline.points.size()-2] > min_width) { - double percent_can_keep = (min_width - polyline.width.back()) / (polyline.width[polyline.points.size() - 2] - polyline.width.back()); - if (polyline.points.back().distance_to(polyline.points[polyline.points.size() - 2]) * percent_can_keep > max_width / 2 - && polyline.points.back().distance_to(polyline.points[polyline.points.size() - 2]) * (1-percent_can_keep) > max_width / 2) { - //Can split => move the first point and assign a new weight. - //the update of endpoints wil be performed in concatThickPolylines - polyline.points.back().x = polyline.points.back().x + - (coord_t)((polyline.points[polyline.points.size() - 2].x - polyline.points.back().x) * percent_can_keep); - polyline.points.back().y = polyline.points.back().y + - (coord_t)((polyline.points[polyline.points.size() - 2].y - polyline.points.back().y) * percent_can_keep); - polyline.width.back() = min_width; - changes = true; - break; - } - } - polyline.points.erase(polyline.points.end()-1); - polyline.width.erase(polyline.width.end() - 1); - changes = true; - } - if (polyline.points.size() < 2) { - //remove self if too small - pp.erase(pp.begin() + i); - --i; - } - } - if (changes) concatThickPolylines(pp); - - // Loop through all returned polylines in order to extend their endpoints to the - // expolygon boundaries - 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 - // 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 && !bounds.has_boundary_point(new_front)) { - Line line(polyline.points[1], polyline.points.front()); - - // prevent the line from touching on the other side, otherwise intersection() might return that solution - if (polyline.points.size() == 2) line.a = line.midpoint(); - - line.extend_end(max_width); - (void)bounds.contour.first_intersection(line, &new_front); - } - if (polyline.endpoints.second && !bounds.has_boundary_point(new_back)) { - Line line( - *(polyline.points.end() - 2), - polyline.points.back() - ); - - // prevent the line from touching on the other side, otherwise intersection() might return that solution - if (polyline.points.size() == 2) line.a = line.midpoint(); - line.extend_end(max_width); - - (void)bounds.contour.first_intersection(line, &new_back); - } - polyline.points.front() = new_front; - polyline.points.back() = new_back; - } - - - // concatenate, but even where multiple thickpolyline join, to create nice long strait polylines - // 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. - - changes = false; - 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() + 1, best_candidate->width.end()); - polyline.endpoints.second = best_candidate->endpoints.second; - assert(polyline.width.size() == polyline.points.size()); - changes = true; - pp.erase(pp.begin() + best_idx); - } - } - if (changes) concatThickPolylines(pp); - - //remove too thin polylines points (inside a polyline : split it) - for (size_t i = 0; i < pp.size(); ++i) { - ThickPolyline& polyline = pp[i]; - - // remove bits with too small extrusion - size_t idx_point = 0; - while (idx_point polyline.length()) { - shortest_size = polyline.length(); - shortest_idx = i; - } - - } - } - if (shortest_idx >= 0 && shortest_idx < pp.size()) { - pp.erase(pp.begin() + shortest_idx); - changes = true; - } - if (changes) concatThickPolylines(pp); - } - - //TODO: reduce the flow at the intersection ( + ) points ? - - //ensure the volume extruded is correct for what we have been asked - // => don't over-extrude - double surface = 0; - double volume = 0; - for (ThickPolyline& polyline : pp) { - for (ThickLine l : polyline.thicklines()) { - surface += l.length() * (l.a_width + l.b_width) / 2; - double width_mean = (l.a_width + l.b_width) / 2; - volume += height * (width_mean - height * (1. - 0.25 * PI)) * l.length(); - } - } - - // compute bounds volume - double boundsVolume = 0; - boundsVolume += height*bounds.area(); - // add external "perimeter gap" - double perimeterRoundGap = bounds.contour.length() * height * (1 - 0.25*PI) * 0.5; - // add holes "perimeter gaps" - double holesGaps = 0; - for (auto hole = bounds.holes.begin(); hole != bounds.holes.end(); ++hole) { - holesGaps += hole->length() * height * (1 - 0.25*PI) * 0.5; - } - boundsVolume += perimeterRoundGap + holesGaps; - - if (boundsVolume < volume) { - //reduce width - double reduce_by = boundsVolume / volume; - for (ThickPolyline& polyline : pp) { - for (ThickLine l : polyline.thicklines()) { - l.a_width *= reduce_by; - l.b_width *= reduce_by; - } - } - } - polylines->insert(polylines->end(), pp.begin(), pp.end()); +ExPolygon::medial_axis(const ExPolygon &bounds, double max_width, double min_width, ThickPolylines* polylines, double height) const { + ExPolygon simplifiedBounds = bounds; + simplifiedBounds.remove_point_too_near(SCALED_RESOLUTION); + ExPolygon simplifiedPolygon = *this; + simplifiedPolygon.remove_point_too_near(SCALED_RESOLUTION); + Slic3r::MedialAxis ma(simplifiedPolygon, simplifiedBounds, max_width, min_width, height); + ma.build(polylines); } void diff --git a/xs/src/libslic3r/ExPolygon.hpp b/xs/src/libslic3r/ExPolygon.hpp index 7f636705b5..92610cc567 100644 --- a/xs/src/libslic3r/ExPolygon.hpp +++ b/xs/src/libslic3r/ExPolygon.hpp @@ -42,6 +42,7 @@ class ExPolygon Polygons simplify_p(double tolerance) const; ExPolygons simplify(double tolerance) const; void simplify(double tolerance, ExPolygons* expolygons) const; + void remove_point_too_near(const coord_t tolerance); void medial_axis(const ExPolygon &bounds, double max_width, double min_width, ThickPolylines* polylines, double height) const; void medial_axis(double max_width, double min_width, Polylines* polylines) const; void get_trapezoids(Polygons* polygons) const; diff --git a/xs/src/libslic3r/Geometry.cpp b/xs/src/libslic3r/Geometry.cpp index 77b9b7f376..c71bc4bb6a 100644 --- a/xs/src/libslic3r/Geometry.cpp +++ b/xs/src/libslic3r/Geometry.cpp @@ -648,266 +648,5 @@ arrange(size_t total_parts, const Pointf &part_size, coordf_t dist, const Boundi return true; } -void -MedialAxis::build(ThickPolylines* polylines) -{ - construct_voronoi(this->lines.begin(), this->lines.end(), &this->vd); - - /* - // DEBUG: dump all Voronoi edges - { - SVG svg("voronoi.svg"); - svg.draw(*this->expolygon); - for (VD::const_edge_iterator edge = this->vd.edges().begin(); edge != this->vd.edges().end(); ++edge) { - if (edge->is_infinite()) continue; - - ThickPolyline polyline; - polyline.points.push_back(Point( edge->vertex0()->x(), edge->vertex0()->y() )); - polyline.points.push_back(Point( edge->vertex1()->x(), edge->vertex1()->y() )); - polyline.width.push_back(this->max_width); - polyline.width.push_back(this->max_width); - polylines->push_back(polyline); - - svg.draw(polyline); - } - svg.Close(); - return; - } - */ - - // collect valid edges (i.e. prune those not belonging to MAT) - // note: this keeps twins, so it inserts twice the number of the valid edges - this->valid_edges.clear(); - { - std::set seen_edges; - for (VD::const_edge_iterator edge = this->vd.edges().begin(); edge != this->vd.edges().end(); ++edge) { - // if we only process segments representing closed loops, none if the - // infinite edges (if any) would be part of our MAT anyway - if (edge->is_secondary() || edge->is_infinite()) continue; - - // don't re-validate twins - if (seen_edges.find(&*edge) != seen_edges.end()) continue; // TODO: is this needed? - seen_edges.insert(&*edge); - seen_edges.insert(edge->twin()); - - if (!this->validate_edge(&*edge)) continue; - this->valid_edges.insert(&*edge); - this->valid_edges.insert(edge->twin()); - } - } - this->edges = this->valid_edges; - - // iterate through the valid edges to build polylines - while (!this->edges.empty()) { - const VD::edge_type* edge = *this->edges.begin(); - - // start a polyline - ThickPolyline polyline; - polyline.points.push_back(Point( edge->vertex0()->x(), edge->vertex0()->y() )); - polyline.points.push_back(Point( edge->vertex1()->x(), edge->vertex1()->y() )); - polyline.width.push_back(this->thickness[edge].first); - polyline.width.push_back(this->thickness[edge].second); - - // remove this edge and its twin from the available edges - (void)this->edges.erase(edge); - (void)this->edges.erase(edge->twin()); - - // get next points - this->process_edge_neighbors(edge, &polyline); - - // get previous points - { - ThickPolyline rpolyline; - this->process_edge_neighbors(edge->twin(), &rpolyline); - polyline.points.insert(polyline.points.begin(), rpolyline.points.rbegin(), rpolyline.points.rend()); - polyline.width.insert(polyline.width.begin(), rpolyline.width.rbegin(), rpolyline.width.rend()); - polyline.endpoints.first = rpolyline.endpoints.second; - } - - assert(polyline.width.size() == polyline.points.size()); - - // prevent loop endpoints from being extended - if (polyline.first_point().coincides_with(polyline.last_point())) { - polyline.endpoints.first = false; - polyline.endpoints.second = false; - } - - // append polyline to result - polylines->push_back(polyline); - } -} - -void -MedialAxis::build(Polylines* polylines) -{ - ThickPolylines tp; - this->build(&tp); - polylines->insert(polylines->end(), tp.begin(), tp.end()); -} - -void -MedialAxis::process_edge_neighbors(const VD::edge_type* edge, ThickPolyline* polyline) -{ - while (true) { - // Since rot_next() works on the edge starting point but we want - // to find neighbors on the ending point, we just swap edge with - // its twin. - const VD::edge_type* twin = edge->twin(); - - // count neighbors for this edge - std::vector neighbors; - for (const VD::edge_type* neighbor = twin->rot_next(); neighbor != twin; - neighbor = neighbor->rot_next()) { - if (this->valid_edges.count(neighbor) > 0) neighbors.push_back(neighbor); - } - - // if we have a single neighbor then we can continue recursively - if (neighbors.size() == 1) { - const VD::edge_type* neighbor = neighbors.front(); - - // break if this is a closed loop - if (this->edges.count(neighbor) == 0) return; - - Point new_point(neighbor->vertex1()->x(), neighbor->vertex1()->y()); - polyline->points.push_back(new_point); - polyline->width.push_back(this->thickness[neighbor].second); - - (void)this->edges.erase(neighbor); - (void)this->edges.erase(neighbor->twin()); - edge = neighbor; - } else if (neighbors.size() == 0) { - polyline->endpoints.second = true; - return; - } else { - // T-shaped or star-shaped joint - return; - } - } -} - -bool -MedialAxis::validate_edge(const VD::edge_type* edge) -{ - // prevent overflows and detect almost-infinite edges - if (std::abs(edge->vertex0()->x()) > (double)MAX_COORD - || std::abs(edge->vertex0()->y()) > (double)MAX_COORD - || std::abs(edge->vertex1()->x()) > (double)MAX_COORD - || std::abs(edge->vertex1()->y()) > (double)MAX_COORD) - return false; - - // construct the line representing this edge of the Voronoi diagram - const Line line( - Point( edge->vertex0()->x(), edge->vertex0()->y() ), - Point( edge->vertex1()->x(), edge->vertex1()->y() ) - ); - - // discard edge if it lies outside the supplied shape - // this could maybe be optimized (checking inclusion of the endpoints - // might give false positives as they might belong to the contour itself) - if (this->expolygon != NULL) { - if (line.a.coincides_with(line.b)) { - // in this case, contains(line) returns a false positive - if (!this->expolygon->contains(line.a)) return false; - } else { - if (!this->expolygon->contains(line)) return false; - } - } - - // retrieve the original line segments which generated the edge we're checking - const VD::cell_type* cell_l = edge->cell(); - const VD::cell_type* cell_r = edge->twin()->cell(); - const Line &segment_l = this->retrieve_segment(cell_l); - const Line &segment_r = this->retrieve_segment(cell_r); - - /* - SVG svg("edge.svg"); - svg.draw(*this->expolygon); - svg.draw(line); - svg.draw(segment_l, "red"); - svg.draw(segment_r, "blue"); - svg.Close(); - */ - /* Calculate thickness of the cross-section at both the endpoints of this edge. - Our Voronoi edge is part of a CCW sequence going around its Voronoi cell - located on the left side. (segment_l). - This edge's twin goes around segment_r. Thus, segment_r is - oriented in the same direction as our main edge, and segment_l is oriented - in the same direction as our twin edge. - We used to only consider the (half-)distances to segment_r, and that works - whenever segment_l and segment_r are almost specular and facing. However, - at curves they are staggered and they only face for a very little length - (our very short edge represents such visibility). - Both w0 and w1 can be calculated either towards cell_l or cell_r with equal - results by Voronoi definition. - When cell_l or cell_r don't refer to the segment but only to an endpoint, we - calculate the distance to that endpoint instead. */ - - coordf_t w0 = cell_r->contains_segment() - ? line.a.distance_to(segment_r)*2 - : line.a.distance_to(this->retrieve_endpoint(cell_r))*2; - - coordf_t w1 = cell_l->contains_segment() - ? line.b.distance_to(segment_l)*2 - : line.b.distance_to(this->retrieve_endpoint(cell_l))*2; - - //don't remove the line that goes to the intersection of the contour - // we use them to create nicer thin wall lines - //if (cell_l->contains_segment() && cell_r->contains_segment()) { - // // calculate the relative angle between the two boundary segments - // double angle = fabs(segment_r.orientation() - segment_l.orientation()); - // if (angle > PI) angle = 2*PI - angle; - // assert(angle >= 0 && angle <= PI); - // - // // fabs(angle) ranges from 0 (collinear, same direction) to PI (collinear, opposite direction) - // // we're interested only in segments close to the second case (facing segments) - // // so we allow some tolerance. - // // this filter ensures that we're dealing with a narrow/oriented area (longer than thick) - // // we don't run it on edges not generated by two segments (thus generated by one segment - // // and the endpoint of another segment), since their orientation would not be meaningful - // if (PI - angle > PI/8) { - // // angle is not narrow enough - // - // // only apply this filter to segments that are not too short otherwise their - // // angle could possibly be not meaningful - // if (w0 < SCALED_EPSILON || w1 < SCALED_EPSILON || line.length() >= this->min_width) - // return false; - // } - //} else { - // if (w0 < SCALED_EPSILON || w1 < SCALED_EPSILON) - // return false; - //} - - // don't do that before we try to fusion them - //if (w0 < this->min_width && w1 < this->min_width) - // return false; - // - - //shouldn't occur if perimeter_generator is well made - if (w0 > this->max_width && w1 > this->max_width) - return false; - - this->thickness[edge] = std::make_pair(w0, w1); - this->thickness[edge->twin()] = std::make_pair(w1, w0); - - return true; -} - -const Line& -MedialAxis::retrieve_segment(const VD::cell_type* cell) const -{ - return this->lines[cell->source_index()]; -} - -const Point& -MedialAxis::retrieve_endpoint(const VD::cell_type* cell) const -{ - const Line& line = this->retrieve_segment(cell); - if (cell->source_category() == SOURCE_CATEGORY_SEGMENT_START_POINT) { - return line.a; - } else { - return line.b; - } -} - } } diff --git a/xs/src/libslic3r/Geometry.hpp b/xs/src/libslic3r/Geometry.hpp index c2c0d66da2..5b1ddcadae 100644 --- a/xs/src/libslic3r/Geometry.hpp +++ b/xs/src/libslic3r/Geometry.hpp @@ -3,13 +3,11 @@ #include "libslic3r.h" #include "BoundingBox.hpp" +#include "MedialAxis.hpp" #include "ExPolygon.hpp" #include "Polygon.hpp" #include "Polyline.hpp" -#include "boost/polygon/voronoi.hpp" -using boost::polygon::voronoi_builder; -using boost::polygon::voronoi_diagram; namespace Slic3r { namespace Geometry { @@ -44,28 +42,6 @@ bool arrange( // output Pointfs &positions); -class MedialAxis { - public: - Lines lines; - const ExPolygon* expolygon; - double max_width; - double min_width; - MedialAxis(double _max_width, double _min_width, const ExPolygon* _expolygon = NULL) - : expolygon(_expolygon), max_width(_max_width), min_width(_min_width) {}; - void build(ThickPolylines* polylines); - void build(Polylines* polylines); - - private: - typedef voronoi_diagram VD; - VD vd; - std::set edges, valid_edges; - std::map > thickness; - void process_edge_neighbors(const VD::edge_type* edge, ThickPolyline* polyline); - bool validate_edge(const VD::edge_type* edge); - const Line& retrieve_segment(const VD::cell_type* cell) const; - const Point& retrieve_endpoint(const VD::cell_type* cell) const; -}; - } } #endif diff --git a/xs/src/libslic3r/PerimeterGenerator.cpp b/xs/src/libslic3r/PerimeterGenerator.cpp index 2ae788c824..165708929a 100644 --- a/xs/src/libslic3r/PerimeterGenerator.cpp +++ b/xs/src/libslic3r/PerimeterGenerator.cpp @@ -117,7 +117,7 @@ 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); - Polygons no_thin_zone = offset(offsets, +ext_pwidth/2); + Polygons no_thin_zone = offset(offsets, +ext_pwidth/2, jtSquare); // medial axis requires non-overlapping geometry Polygons thin_zones = diff(last, no_thin_zone, true); //don't use offset2_ex, because we don't want to merge the zones that have been separated. @@ -129,7 +129,8 @@ PerimeterGenerator::process() // compute a bit of overlap to anchor thin walls inside the print. for (Polygon &ex : expp) { //growing back the polygon - //a vary little bit of overlap can be created here with other thin polygon, but it's more useful than worisome. + //a very little bit of overlap can be created here with other thin polygons, but it's more useful than worisome. + ex.remove_point_too_near(SCALED_RESOLUTION); ExPolygons ex_bigger = offset_ex(ex, (float)(min_width / 2)); if (ex_bigger.size() != 1) continue; // impossible error, growing a single polygon can't create multiple or 0. ExPolygons anchor = intersection_ex(offset(ex, (float)(ext_pwidth / 2), @@ -140,7 +141,7 @@ PerimeterGenerator::process() //be sure it's not too small to extrude reliably if (ex_bigger[0].area() > min_width*(ext_pwidth + ext_pspacing2)) { // the maximum thickness of our thin wall area is equal to the minimum thickness of a single loop - ex_bigger[0].medial_axis(bound, ext_pwidth + ext_pspacing2, min_width, + ex_bigger[0].medial_axis(bound, ext_pwidth + ext_pspacing2, min_width, &thin_walls, this->layer_height); } break; @@ -496,8 +497,10 @@ PerimeterGenerator::_variable_width(const ThickPolylines &polylines, ExtrusionRo // of segments, and any pruning shall be performed before we apply this tolerance const double tolerance = scale_(0.05); + int id_line = 0; ExtrusionEntityCollection coll; for (ThickPolylines::const_iterator p = polylines.begin(); p != polylines.end(); ++p) { + id_line++; ExtrusionPaths paths; ExtrusionPath path(role); ThickLines lines = p->thicklines(); @@ -563,7 +566,7 @@ PerimeterGenerator::_variable_width(const ThickPolylines &polylines, ExtrusionRo path.polyline.append(line.b); } else { thickness_delta = fabs(scale_(flow.width) - w); - if (thickness_delta <= tolerance) { + if (thickness_delta <= tolerance/2) { // the width difference between this line and the current flow width is // within the accepted tolerance diff --git a/xs/src/libslic3r/Polyline.cpp b/xs/src/libslic3r/Polyline.cpp index a75485ed63..0afd229514 100644 --- a/xs/src/libslic3r/Polyline.cpp +++ b/xs/src/libslic3r/Polyline.cpp @@ -261,10 +261,10 @@ concatThickPolylines(ThickPolylines& pp) { for (size_t i = 0; i < pp.size(); ++i) { ThickPolyline *polyline = &pp[i]; - int32_t id_candidate_first_point = -1; - int32_t id_candidate_last_point = -1; - int32_t nbCandidate_first_point = 0; - int32_t nbCandidate_last_point = 0; + size_t id_candidate_first_point = -1; + size_t id_candidate_last_point = -1; + size_t nbCandidate_first_point = 0; + size_t nbCandidate_last_point = 0; // find another polyline starting here for (size_t j = 0; j < pp.size(); ++j) { if (j == i) continue; From e4484c40273c539375a4d86961344a3196780f96 Mon Sep 17 00:00:00 2001 From: supermerill Date: Mon, 1 Oct 2018 19:16:04 +0200 Subject: [PATCH 03/25] forgot medial_axis.hpp/cpp files --- xs/src/libslic3r/MedialAxis.cpp | 1413 +++++++++++++++++++++++++++++++ xs/src/libslic3r/MedialAxis.hpp | 63 ++ 2 files changed, 1476 insertions(+) create mode 100644 xs/src/libslic3r/MedialAxis.cpp create mode 100644 xs/src/libslic3r/MedialAxis.hpp diff --git a/xs/src/libslic3r/MedialAxis.cpp b/xs/src/libslic3r/MedialAxis.cpp new file mode 100644 index 0000000000..27b77e197a --- /dev/null +++ b/xs/src/libslic3r/MedialAxis.cpp @@ -0,0 +1,1413 @@ +#include "BoundingBox.hpp" +#include "ExPolygon.hpp" +#include "Geometry.hpp" +#include "Polygon.hpp" +#include "Line.hpp" +#include "ClipperUtils.hpp" +#include "SVG.hpp" +#include "polypartition.h" +#include "poly2tri/poly2tri.h" +#include +#include +#include + +namespace Slic3r { + +int MedialAxis::id = 0; + +void +MedialAxis::build(Polylines* polylines) +{ + ThickPolylines tp; + this->build(&tp); + polylines->insert(polylines->end(), tp.begin(), tp.end()); +} + +void +MedialAxis::polyline_from_voronoi(const Lines& voronoi_edges, ThickPolylines* polylines) +{ + this->lines = voronoi_edges; + construct_voronoi(lines.begin(), lines.end(), &this->vd); + + /* + // DEBUG: dump all Voronoi edges + { + for (VD::const_edge_iterator edge = this->vd.edges().begin(); edge != this->vd.edges().end(); ++edge) { + if (edge->is_infinite()) continue; + + ThickPolyline polyline; + polyline.points.push_back(Point( edge->vertex0()->x(), edge->vertex0()->y() )); + polyline.points.push_back(Point( edge->vertex1()->x(), edge->vertex1()->y() )); + polylines->push_back(polyline); + } + return; + } + */ + + typedef const VD::vertex_type vert_t; + typedef const VD::edge_type edge_t; + + // collect valid edges (i.e. prune those not belonging to MAT) + // note: this keeps twins, so it inserts twice the number of the valid edges + this->valid_edges.clear(); + { + std::set seen_edges; + for (VD::const_edge_iterator edge = this->vd.edges().begin(); edge != this->vd.edges().end(); ++edge) { + // if we only process segments representing closed loops, none if the + // infinite edges (if any) would be part of our MAT anyway + if (edge->is_secondary() || edge->is_infinite()) continue; + + // don't re-validate twins + if (seen_edges.find(&*edge) != seen_edges.end()) continue; // TODO: is this needed? + seen_edges.insert(&*edge); + seen_edges.insert(edge->twin()); + + if (!this->validate_edge(&*edge)) continue; + this->valid_edges.insert(&*edge); + this->valid_edges.insert(edge->twin()); + } + } + this->edges = this->valid_edges; + + // iterate through the valid edges to build polylines + while (!this->edges.empty()) { + const edge_t* edge = *this->edges.begin(); + + // start a polyline + ThickPolyline polyline; + polyline.points.push_back(Point( edge->vertex0()->x(), edge->vertex0()->y() )); + polyline.points.push_back(Point( edge->vertex1()->x(), edge->vertex1()->y() )); + polyline.width.push_back(this->thickness[edge].first); + polyline.width.push_back(this->thickness[edge].second); + + // remove this edge and its twin from the available edges + (void)this->edges.erase(edge); + (void)this->edges.erase(edge->twin()); + + // get next points + this->process_edge_neighbors(edge, &polyline); + + // get previous points + { + ThickPolyline rpolyline; + this->process_edge_neighbors(edge->twin(), &rpolyline); + polyline.points.insert(polyline.points.begin(), rpolyline.points.rbegin(), rpolyline.points.rend()); + polyline.width.insert(polyline.width.begin(), rpolyline.width.rbegin(), rpolyline.width.rend()); + polyline.endpoints.first = rpolyline.endpoints.second; + } + + assert(polyline.width.size() == polyline.points.size()); + + // prevent loop endpoints from being extended + if (polyline.first_point().coincides_with(polyline.last_point())) { + polyline.endpoints.first = false; + polyline.endpoints.second = false; + } + + // append polyline to result + polylines->push_back(polyline); + } + + #ifdef SLIC3R_DEBUG + { + static int iRun = 0; + dump_voronoi_to_svg(this->lines, this->vd, polylines, debug_out_path("MedialAxis-%d.svg", iRun ++).c_str()); + printf("Thick lines: "); + for (ThickPolylines::const_iterator it = polylines->begin(); it != polylines->end(); ++ it) { + ThickLines lines = it->thicklines(); + for (ThickLines::const_iterator it2 = lines.begin(); it2 != lines.end(); ++ it2) { + printf("%f,%f ", it2->a_width, it2->b_width); + } + } + printf("\n"); + } + #endif /* SLIC3R_DEBUG */ +} + +void +MedialAxis::process_edge_neighbors(const VD::edge_type* edge, ThickPolyline* polyline) +{ + while (true) { + // Since rot_next() works on the edge starting point but we want + // to find neighbors on the ending point, we just swap edge with + // its twin. + const VD::edge_type* twin = edge->twin(); + + // count neighbors for this edge + std::vector neighbors; + for (const VD::edge_type* neighbor = twin->rot_next(); neighbor != twin; + neighbor = neighbor->rot_next()) { + if (this->valid_edges.count(neighbor) > 0) neighbors.push_back(neighbor); + } + + // if we have a single neighbor then we can continue recursively + if (neighbors.size() == 1) { + const VD::edge_type* neighbor = neighbors.front(); + + // break if this is a closed loop + if (this->edges.count(neighbor) == 0) return; + + Point new_point(neighbor->vertex1()->x(), neighbor->vertex1()->y()); + polyline->points.push_back(new_point); + polyline->width.push_back(this->thickness[neighbor].second); + + (void)this->edges.erase(neighbor); + (void)this->edges.erase(neighbor->twin()); + edge = neighbor; + } else if (neighbors.size() == 0) { + polyline->endpoints.second = true; + return; + } else { + // T-shaped or star-shaped joint + return; + } + } +} + +bool +MedialAxis::validate_edge(const VD::edge_type* edge) +{ + // prevent overflows and detect almost-infinite edges + if (std::abs(edge->vertex0()->x()) > double(CLIPPER_MAX_COORD_UNSCALED) || + std::abs(edge->vertex0()->y()) > double(CLIPPER_MAX_COORD_UNSCALED) || + std::abs(edge->vertex1()->x()) > double(CLIPPER_MAX_COORD_UNSCALED) || + std::abs(edge->vertex1()->y()) > double(CLIPPER_MAX_COORD_UNSCALED)) + return false; + + // construct the line representing this edge of the Voronoi diagram + const Line line( + Point( edge->vertex0()->x(), edge->vertex0()->y() ), + Point( edge->vertex1()->x(), edge->vertex1()->y() ) + ); + + // discard edge if it lies outside the supplied shape + // this could maybe be optimized (checking inclusion of the endpoints + // might give false positives as they might belong to the contour itself) + if (line.a.coincides_with(line.b)) { + // in this case, contains(line) returns a false positive + if (!this->expolygon.contains(line.a)) return false; + } else { + if (!this->expolygon.contains(line)) return false; + } + + // retrieve the original line segments which generated the edge we're checking + const VD::cell_type* cell_l = edge->cell(); + const VD::cell_type* cell_r = edge->twin()->cell(); + const Line &segment_l = this->retrieve_segment(cell_l); + const Line &segment_r = this->retrieve_segment(cell_r); + + + //SVG svg("edge.svg"); + //svg.draw(this->expolygon.expolygon); + //svg.draw(line); + //svg.draw(segment_l, "red"); + //svg.draw(segment_r, "blue"); + //svg.Close(); + // + + /* Calculate thickness of the cross-section at both the endpoints of this edge. + Our Voronoi edge is part of a CCW sequence going around its Voronoi cell + located on the left side. (segment_l). + This edge's twin goes around segment_r. Thus, segment_r is + oriented in the same direction as our main edge, and segment_l is oriented + in the same direction as our twin edge. + We used to only consider the (half-)distances to segment_r, and that works + whenever segment_l and segment_r are almost specular and facing. However, + at curves they are staggered and they only face for a very little length + (our very short edge represents such visibility). + Both w0 and w1 can be calculated either towards cell_l or cell_r with equal + results by Voronoi definition. + When cell_l or cell_r don't refer to the segment but only to an endpoint, we + calculate the distance to that endpoint instead. */ + + coordf_t w0 = cell_r->contains_segment() + ? line.a.distance_to(segment_r)*2 + : line.a.distance_to(this->retrieve_endpoint(cell_r))*2; + + coordf_t w1 = cell_l->contains_segment() + ? line.b.distance_to(segment_l)*2 + : line.b.distance_to(this->retrieve_endpoint(cell_l))*2; + + //don't remove the line that goes to the intersection of the contour + // we use them to create nicer thin wall lines + //if (cell_l->contains_segment() && cell_r->contains_segment()) { + // // calculate the relative angle between the two boundary segments + // double angle = fabs(segment_r.orientation() - segment_l.orientation()); + // if (angle > PI) angle = 2*PI - angle; + // assert(angle >= 0 && angle <= PI); + // + // // fabs(angle) ranges from 0 (collinear, same direction) to PI (collinear, opposite direction) + // // we're interested only in segments close to the second case (facing segments) + // // so we allow some tolerance. + // // this filter ensures that we're dealing with a narrow/oriented area (longer than thick) + // // we don't run it on edges not generated by two segments (thus generated by one segment + // // and the endpoint of another segment), since their orientation would not be meaningful + // if (PI - angle > PI/8) { + // // angle is not narrow enough + // + // // only apply this filter to segments that are not too short otherwise their + // // angle could possibly be not meaningful + // if (w0 < SCALED_EPSILON || w1 < SCALED_EPSILON || line.length() >= this->min_width) + // return false; + // } + //} else { + // if (w0 < SCALED_EPSILON || w1 < SCALED_EPSILON) + // return false; + //} + + // don't do that before we try to fusion them + //if (w0 < this->min_width && w1 < this->min_width) + // return false; + // + + //shouldn't occur if perimeter_generator is well made + if (w0 > this->max_width && w1 > this->max_width) + return false; + + this->thickness[edge] = std::make_pair(w0, w1); + this->thickness[edge->twin()] = std::make_pair(w1, w0); + + return true; +} + +const Line& +MedialAxis::retrieve_segment(const VD::cell_type* cell) const +{ + return lines[cell->source_index()]; +} + +const Point& +MedialAxis::retrieve_endpoint(const VD::cell_type* cell) const +{ + const Line& line = this->retrieve_segment(cell); + if (cell->source_category() == boost::polygon::SOURCE_CATEGORY_SEGMENT_START_POINT) { + return line.a; + } else { + return line.b; + } +} + + +/// remove point that are at SCALED_EPSILON * 2 distance. +void +remove_point_too_near(ThickPolyline* to_reduce) +{ + const coord_t smallest = SCALED_EPSILON * 2; + size_t id = 1; + while (id < to_reduce->points.size() - 1) { + size_t newdist = min(to_reduce->points[id].distance_to(to_reduce->points[id - 1]) + , to_reduce->points[id].distance_to(to_reduce->points[id + 1])); + if (newdist < smallest) { + to_reduce->points.erase(to_reduce->points.begin() + id); + to_reduce->width.erase(to_reduce->width.begin() + id); + newdist = to_reduce->points[id].distance_to(to_reduce->points[id - 1]); + } + //go to next one + //if you removed a point, it check if the next one isn't too near from the previous one. + // if not, it byepass it. + if (newdist > smallest) { + ++id; + } + } +} + +/// add points from pattern to to_modify at the same % of the length +/// so not add if an other point is present at the correct position +void +add_point_same_percent(ThickPolyline* pattern, ThickPolyline* to_modify) +{ + const double to_modify_length = to_modify->length(); + const double percent_epsilon = SCALED_EPSILON / to_modify_length; + const double pattern_length = pattern->length(); + + double percent_length = 0; + for (size_t idx_point = 1; idx_point < pattern->points.size() - 1; ++idx_point) { + percent_length += pattern->points[idx_point-1].distance_to(pattern->points[idx_point]) / pattern_length; + //find position + size_t idx_other = 1; + double percent_length_other_before = 0; + double percent_length_other = 0; + while (idx_other < to_modify->points.size()) { + percent_length_other_before = percent_length_other; + percent_length_other += to_modify->points[idx_other-1].distance_to(to_modify->points[idx_other]) + / to_modify_length; + if (percent_length_other > percent_length - percent_epsilon) { + //if higher (we have gone over it) + break; + } + ++idx_other; + } + if (percent_length_other > percent_length + percent_epsilon) { + //insert a new point before the position + double percent_dist = (percent_length - percent_length_other_before) / (percent_length_other - percent_length_other_before); + coordf_t new_width = to_modify->width[idx_other - 1] * (1 - percent_dist); + new_width += to_modify->width[idx_other] * (percent_dist); + Point new_point; + new_point.x = (coord_t)((double)(to_modify->points[idx_other - 1].x) * (1 - percent_dist)); + new_point.x += (coord_t)((double)(to_modify->points[idx_other].x) * (percent_dist)); + new_point.y = (coord_t)((double)(to_modify->points[idx_other - 1].y) * (1 - percent_dist)); + new_point.y += (coord_t)((double)(to_modify->points[idx_other].y) * (percent_dist)); + to_modify->width.insert(to_modify->width.begin() + idx_other, new_width); + to_modify->points.insert(to_modify->points.begin() + idx_other, new_point); + } + } +} + +/// find the nearest angle in the contour (or 2 nearest if it's difficult to choose) +/// return 1 for an angle of 90° and 0 for an angle of 0° or 180° +/// find the nearest angle in the contour (or 2 nearest if it's difficult to choose) +/// return 1 for an angle of 90° and 0 for an angle of 0° or 180° +double +get_coeff_from_angle_countour(Point &point, const ExPolygon &contour, coord_t min_dist_between_point) { + double nearestDist = point.distance_to(contour.contour.points.front()); + Point nearest = contour.contour.points.front(); + size_t id_nearest = 0; + double nearDist = nearestDist; + Point near = nearest; + size_t id_near = 0; + for (size_t id_point = 1; id_point < contour.contour.points.size(); ++id_point) { + if (nearestDist > point.distance_to(contour.contour.points[id_point])) { + //update near + id_near = id_nearest; + near = nearest; + nearDist = nearestDist; + //update nearest + nearestDist = point.distance_to(contour.contour.points[id_point]); + nearest = contour.contour.points[id_point]; + id_nearest = id_point; + } + } + double angle = 0; + size_t id_before = id_nearest == 0 ? contour.contour.points.size() - 1 : id_nearest - 1; + Point point_before = id_nearest == 0 ? contour.contour.points.back() : contour.contour.points[id_nearest - 1]; + //Search one point far enough to be relevant + while (nearest.distance_to(point_before) < min_dist_between_point) { + point_before = id_before == 0 ? contour.contour.points.back() : contour.contour.points[id_before - 1]; + id_before = id_before == 0 ? contour.contour.points.size() - 1 : id_before - 1; + //don't loop + if (id_before == id_nearest) { + id_before = id_nearest == 0 ? contour.contour.points.size() - 1 : id_nearest - 1; + point_before = id_nearest == 0 ? contour.contour.points.back() : contour.contour.points[id_nearest - 1]; + break; + } + } + size_t id_after = id_nearest == contour.contour.points.size() - 1 ? 0 : id_nearest + 1; + Point point_after = id_nearest == contour.contour.points.size() - 1 ? contour.contour.points.front() : contour.contour.points[id_nearest + 1]; + //Search one point far enough to be relevant + while (nearest.distance_to(point_after) < min_dist_between_point) { + point_after = id_after == contour.contour.points.size() - 1 ? contour.contour.points.front() : contour.contour.points[id_after + 1]; + id_after = id_after == contour.contour.points.size() - 1 ? 0 : id_after + 1; + //don't loop + if (id_after == id_nearest) { + id_after = id_nearest == contour.contour.points.size() - 1 ? 0 : id_nearest + 1; + point_after = id_nearest == contour.contour.points.size() - 1 ? contour.contour.points.front() : contour.contour.points[id_nearest + 1]; + break; + } + } + //compute angle + angle = nearest.ccw_angle(point_before, point_after); + if (angle >= PI) angle = 2 * PI - angle; // smaller angle + //compute the diff from 90° + angle = abs(angle - PI / 2); + if (near.coincides_with(nearest) && max(nearestDist, nearDist) + SCALED_EPSILON < nearest.distance_to(near)) { + //not only nearest + Point point_before = id_near == 0 ? contour.contour.points.back() : contour.contour.points[id_near - 1]; + Point point_after = id_near == contour.contour.points.size() - 1 ? contour.contour.points.front() : contour.contour.points[id_near + 1]; + double angle2 = min(nearest.ccw_angle(point_before, point_after), nearest.ccw_angle(point_after, point_before)); + angle2 = abs(angle - PI / 2); + angle = (angle + angle2) / 2; + } + + return 1 - (angle / (PI / 2)); +} + +double +dot(Line l1, Line l2) +{ + Vectorf v_1 = normalize(Vectorf(l1.b.x - l1.a.x, l1.b.y - l1.a.y)); + Vectorf v_2 = normalize(Vectorf(l2.b.x - l2.a.x, l2.b.y - l2.a.y)); + return v_1.x*v_2.x + v_1.y*v_2.y; +} + +void +MedialAxis::fusion_curve(ThickPolylines &pp) +{ + //fusion Y with only 1 '0' value => the "0" branch "pull" the cross-point + bool changes = false; + for (size_t i = 0; i < pp.size(); ++i) { + ThickPolyline& polyline = pp[i]; + // only consider 2-point polyline with endpoint + if (polyline.points.size() != 2) continue; + if (polyline.endpoints.first) polyline.reverse(); + else if (!polyline.endpoints.second) continue; + if (polyline.width.back() > EPSILON) continue; + + //check my length is small + coord_t length = (coord_t)polyline.length(); + if (length > max_width) continue; + + size_t closest_point_idx = this->expolygon.contour.closest_point_index(polyline.points.back()); + + //check the 0-wodth point is on the contour. + if (closest_point_idx == (size_t)-1) continue; + + size_t prev_idx = closest_point_idx == 0 ? this->expolygon.contour.points.size() - 1 : closest_point_idx - 1; + size_t next_idx = closest_point_idx == this->expolygon.contour.points.size() - 1 ? 0 : closest_point_idx + 1; + double mindot = 1; + mindot = min(mindot, abs(dot(Line(polyline.points[polyline.points.size() - 1], polyline.points[polyline.points.size() - 2]), + (Line(this->expolygon.contour.points[closest_point_idx], this->expolygon.contour.points[prev_idx]))))); + mindot = min(mindot, abs(dot(Line(polyline.points[polyline.points.size() - 1], polyline.points[polyline.points.size() - 2]), + (Line(this->expolygon.contour.points[closest_point_idx], this->expolygon.contour.points[next_idx]))))); + + //compute angle + double coeff_contour_angle = this->expolygon.contour.points[closest_point_idx].ccw_angle(this->expolygon.contour.points[prev_idx], this->expolygon.contour.points[next_idx]); + if (coeff_contour_angle >= PI) coeff_contour_angle = 2 * PI - coeff_contour_angle; // smaller angle + //compute the diff from 90° + coeff_contour_angle = abs(coeff_contour_angle - PI / 2); + + + // look if other end is a cross point with almost 90° angle + double sum_dot = 0; + double min_dot = 0; + // look if other end is a cross point with multiple other branch + vector crosspoint; + for (size_t j = 0; j < pp.size(); ++j) { + if (j == i) continue; + ThickPolyline& other = pp[j]; + if (polyline.first_point().coincides_with(other.last_point())) { + other.reverse(); + crosspoint.push_back(j); + double dot_temp = dot(Line(polyline.points[0], polyline.points[1]), (Line(other.points[0], other.points[1]))); + min_dot = min(min_dot, abs(dot_temp)); + sum_dot += dot_temp; + } else if (polyline.first_point().coincides_with(other.first_point())) { + crosspoint.push_back(j); + double dot_temp = dot(Line(polyline.points[0], polyline.points[1]), (Line(other.points[0], other.points[1]))); + min_dot = min(min_dot, abs(dot_temp)); + sum_dot += dot_temp; + } + } + //only consider very shallow angle for contour + if (mindot > 0.15 && + (1 - (coeff_contour_angle / (PI / 2))) > 0.2) continue; + + //check if it's a line that we can pull + if (crosspoint.size() != 2) continue; + if (sum_dot > 0.2) continue; + if (min_dot > 0.5) continue; + + //don't pull, it distords the line if there are too many points. + //// pull it a bit, depends on my size, the dot?, and the coeff at my 0-end (~14% for a square, almost 0 for a gentle curve) + //coord_t length_pull = polyline.length(); + //length_pull *= 0.144 * get_coeff_from_angle_countour(polyline.points.back(), this->expolygon, min(min_width, polyline.length() / 2)); + + ////compute dir + //Vectorf pull_direction(polyline.points[1].x - polyline.points[0].x, polyline.points[1].y - polyline.points[0].y); + //pull_direction = normalize(pull_direction); + //pull_direction.x *= length_pull; + //pull_direction.y *= length_pull; + + ////pull the points + //Point &p1 = pp[crosspoint[0]].points[0]; + //p1.x = p1.x + (coord_t)pull_direction.x; + //p1.y = p1.y + (coord_t)pull_direction.y; + + //Point &p2 = pp[crosspoint[1]].points[0]; + //p2.x = p2.x + (coord_t)pull_direction.x; + //p2.y = p2.y + (coord_t)pull_direction.y; + + //delete the now unused polyline + pp.erase(pp.begin() + i); + --i; + changes = true; + } + if (changes) { + concatThickPolylines(pp); + ///reorder, in case of change + std::sort(pp.begin(), pp.end(), [](const ThickPolyline & a, const ThickPolyline & b) { return a.length() < b.length(); }); + } +} + +void +MedialAxis::fusion_corners(ThickPolylines &pp) +{ + + //fusion Y with only 1 '0' value => the "0" branch "pull" the cross-point + bool changes = false; + for (size_t i = 0; i < pp.size(); ++i) { + ThickPolyline& polyline = pp[i]; + // only consider polyline with 0-end + if (polyline.points.size() != 2) continue; + if (polyline.endpoints.first) polyline.reverse(); + else if (!polyline.endpoints.second) continue; + if (polyline.width.back() > 0) continue; + + //check my length is small + coord_t length = polyline.length(); + if (length > max_width) continue; + + // look if other end is a cross point with multiple other branch + vector crosspoint; + for (size_t j = 0; j < pp.size(); ++j) { + if (j == i) continue; + ThickPolyline& other = pp[j]; + if (polyline.first_point().coincides_with(other.last_point())) { + other.reverse(); + crosspoint.push_back(j); + } else if (polyline.first_point().coincides_with(other.first_point())) { + crosspoint.push_back(j); + } + } + //check if it's a line that we can pull + if (crosspoint.size() != 2) continue; + + // check if i am at the external side of a curve + double angle1 = polyline.points[0].ccw_angle(polyline.points[1], pp[crosspoint[0]].points[1]); + if (angle1 >= PI) angle1 = 2 * PI - angle1; // smaller angle + double angle2 = polyline.points[0].ccw_angle(polyline.points[1], pp[crosspoint[1]].points[1]); + if (angle2 >= PI) angle2 = 2 * PI - angle2; // smaller angle + if (angle1 + angle2 < PI) continue; + + //check if is smaller or the other ones are not endpoits + if (pp[crosspoint[0]].endpoints.second && length > pp[crosspoint[0]].length()) continue; + if (pp[crosspoint[1]].endpoints.second && length > pp[crosspoint[1]].length()) continue; + + //FIXME: also pull (a bit less) points that are near to this one. + // if true, pull it a bit, depends on my size, the dot?, and the coeff at my 0-end (~14% for a square, almost 0 for a gentle curve) + coord_t length_pull = polyline.length(); + length_pull *= 0.144 * get_coeff_from_angle_countour(polyline.points.back(), this->expolygon, min(min_width, polyline.length() / 2)); + + //compute dir + Vectorf pull_direction(polyline.points[1].x - polyline.points[0].x, polyline.points[1].y - polyline.points[0].y); + pull_direction = normalize(pull_direction); + pull_direction.x *= length_pull; + pull_direction.y *= length_pull; + + //pull the points + Point &p1 = pp[crosspoint[0]].points[0]; + p1.x = p1.x + pull_direction.x; + p1.y = p1.y + pull_direction.y; + + Point &p2 = pp[crosspoint[1]].points[0]; + p2.x = p2.x + pull_direction.x; + p2.y = p2.y + pull_direction.y; + + //delete the now unused polyline + pp.erase(pp.begin() + i); + --i; + changes = true; + } + if (changes) { + concatThickPolylines(pp); + ///reorder, in case of change + std::sort(pp.begin(), pp.end(), [](const ThickPolyline & a, const ThickPolyline & b) { return a.length() < b.length(); }); + } +} + + +void +MedialAxis::extends_line(ThickPolyline& polyline, const ExPolygons& anchors, const coord_t join_width) +{ + // 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 + if (polyline.endpoints.second && !bounds.has_boundary_point(polyline.points.back())) { + Line line(*(polyline.points.end() - 2), polyline.points.back()); + + // prevent the line from touching on the other side, otherwise intersection() might return that solution + if (polyline.points.size() == 2) line.a = line.midpoint(); + + line.extend_end(max_width); + Point new_back; + if (this->expolygon.contour.has_boundary_point(polyline.points.back())) { + new_back = polyline.points.back(); + } else { + (void)this->expolygon.contour.first_intersection(line, &new_back); + polyline.points.push_back(new_back); + polyline.width.push_back(polyline.width.back()); + } + Point new_bound; + (void)bounds.contour.first_intersection(line, &new_bound); + /* if (new_bound.coincides_with_epsilon(new_back)) { + return; + }*/ + // find anchor + Point best_anchor; + double shortest_dist = max_width; + for (const ExPolygon& a : anchors) { + Point p_maybe_inside = a.contour.centroid(); + double test_dist = new_bound.distance_to(p_maybe_inside) + new_back.distance_to(p_maybe_inside); + //if (test_dist < max_width / 2 && (test_dist < shortest_dist || shortest_dist < 0)) { + double angle_test = new_back.ccw_angle(p_maybe_inside, line.a); + if (angle_test > PI) angle_test = 2 * PI - angle_test; + if (test_dist < max_width && test_dist PI / 2) { + shortest_dist = test_dist; + best_anchor = p_maybe_inside; + } + } + if (best_anchor.x != 0 && best_anchor.y != 0) { + Point p_obj = best_anchor + new_bound; + p_obj.x /= 2; + p_obj.y /= 2; + Line l2 = Line(new_back, p_obj); + l2.extend_end(max_width); + (void)bounds.contour.first_intersection(l2, &new_bound); + } + if (new_bound.coincides_with_epsilon(new_back)) { + return; + } + polyline.points.push_back(new_bound); + //polyline.width.push_back(join_width); + //it thickens the line a bit too early, imo + polyline.width.push_back(polyline.width.back()); + } +} + +void +MedialAxis::main_fusion(ThickPolylines& pp) +{ + //int idf = 0; + //reoder pp by length (ascending) It's really important to do that to avoid building the line from the width insteand of the length + std::sort(pp.begin(), pp.end(), [](const ThickPolyline & a, const ThickPolyline & b) { + bool ahas0 = a.width.front() == 0 || a.width.back() == 0; + bool bhas0 = b.width.front() == 0 || b.width.back() == 0; + if (ahas0 && !bhas0) return true; + if (!ahas0 && bhas0) return false; + return a.length() < b.length(); + }); + + bool changes = true; + map coeff_angle_cache; + while (changes) { + changes = false; + for (size_t i = 0; i < pp.size(); ++i) { + ThickPolyline& polyline = pp[i]; + + //simple check to see if i can be fusionned + if (!polyline.endpoints.first && !polyline.endpoints.second) continue; + + + ThickPolyline* best_candidate = nullptr; + float best_dot = -1; + size_t best_idx = 0; + double dot_poly_branch = 0; + double dot_candidate_branch = 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; + } + //std::cout << " try : " << i << ":" << j << " : " << + // (polyline.points.size() < 2 && other.points.size() < 2) << + // (!polyline.endpoints.second || !other.endpoints.second) << + // ((polyline.points.back().distance_to(other.points.back()) + // + (polyline.width.back() + other.width.back()) / 4) + // > max_width*1.05) << + // (abs(polyline.length() - other.length()) > max_width) << "\n"; + + //// mergeable tests + if (polyline.points.size() < 2 && other.points.size() < 2) continue; + if (!polyline.endpoints.second || !other.endpoints.second) continue; + // test if the new width will not be too big if a fusion occur + //note that this isn't the real calcul. It's just to avoid merging lines too far apart. + if ( + ((polyline.points.back().distance_to(other.points.back()) + + (polyline.width.back() + other.width.back()) / 4) + > max_width*1.05)) + continue; + // test if the lines are not too different in length. + if (abs(polyline.length() - other.length()) > max_width) continue; + + + //test if we don't merge with something too different and without any relevance. + double coeffSizePolyI = 1; + if (polyline.width.back() == 0) { + coeffSizePolyI = 0.1 + 0.9*get_coeff_from_angle_countour(polyline.points.back(), this->expolygon, min(min_width, polyline.length() / 2)); + } + double coeffSizeOtherJ = 1; + if (other.width.back() == 0) { + coeffSizeOtherJ = 0.1 + 0.9*get_coeff_from_angle_countour(other.points.back(), this->expolygon, min(min_width, polyline.length() / 2)); + } + //std::cout << " try2 : " << i << ":" << j << " : " + // << (abs(polyline.length()*coeffSizePolyI - other.length()*coeffSizeOtherJ) > max_width / 2) + // << (abs(polyline.length()*coeffSizePolyI - other.length()*coeffSizeOtherJ) > max_width) + // << "\n"; + if (abs(polyline.length()*coeffSizePolyI - other.length()*coeffSizeOtherJ) > max_width / 2) continue; + + + //compute angle to see if it's better than previous ones (straighter = better). + //we need to add how strait we are from our main. + float test_dot = dot(polyline.lines().front(), other.lines().front()); + + // Get the branch/line in wich we may merge, if possible + // with that, we can decide what is important, and how we can merge that. + // angle_poly - angle_candi =90° => one is useless + // both angle are equal => both are useful with same strength + // ex: Y => | both are useful to crete a nice line + // ex2: TTTTT => ----- these 90° useless lines should be discarded + bool find_main_branch = false; + size_t biggest_main_branch_id = 0; + coord_t biggest_main_branch_length = 0; + for (size_t k = 0; k < pp.size(); ++k) { + //std::cout << "try to find main : " << k << " ? " << i << " " << j << " "; + if (k == i | k == j) continue; + ThickPolyline& main = pp[k]; + if (polyline.first_point().coincides_with(main.last_point())) { + main.reverse(); + if (!main.endpoints.second) + find_main_branch = true; + else if (biggest_main_branch_length < main.length()) { + biggest_main_branch_id = k; + biggest_main_branch_length = main.length(); + } + } else if (polyline.first_point().coincides_with(main.first_point())) { + if (!main.endpoints.second) + find_main_branch = true; + else if (biggest_main_branch_length < main.length()) { + biggest_main_branch_id = k; + biggest_main_branch_length = main.length(); + } + } + if (find_main_branch) { + //use this variable to store the good index and break to compute it + biggest_main_branch_id = k; + break; + } + } + if (!find_main_branch && biggest_main_branch_length == 0) { + // nothing -> it's impossible! + dot_poly_branch = 0.707; + dot_candidate_branch = 0.707; + //std::cout << "no main branch... impossible!!\n"; + } else if (!find_main_branch && ( + (pp[biggest_main_branch_id].length() < polyline.length() && (polyline.width.back() != 0 || pp[biggest_main_branch_id].width.back() ==0)) + || (pp[biggest_main_branch_id].length() < other.length() && (other.width.back() != 0 || pp[biggest_main_branch_id].width.back() == 0)))) { + //the main branch should have no endpoint or be bigger! + //here, it have an endpoint, and is not the biggest -> bad! + continue; + } else { + //compute the dot (biggest_main_branch_id) + dot_poly_branch = -dot(Line(polyline.points[0], polyline.points[1]), Line(pp[biggest_main_branch_id].points[0], pp[biggest_main_branch_id].points[1])); + dot_candidate_branch = -dot(Line(other.points[0], other.points[1]), Line(pp[biggest_main_branch_id].points[0], pp[biggest_main_branch_id].points[1])); + if (dot_poly_branch < 0) dot_poly_branch = 0; + if (dot_candidate_branch < 0) dot_candidate_branch = 0; + if (pp[biggest_main_branch_id].width.back()>0) + test_dot += 2 * dot_poly_branch ; + } + //test if it's useful to merge or not + //ie, don't merge 'T' but ok for 'Y', merge only lines of not disproportionate different length (ratio max: 4) (or they are both with 0-width end) + if (dot_poly_branch < 0.1 || dot_candidate_branch < 0.1 || + ( + ((polyline.length()>other.length() ? polyline.length() / other.length() : other.length() / polyline.length()) > 4) + && !(polyline.width.back() == 0 && other.width.back()==0) + ) ){ + continue; + } + if (test_dot > best_dot) { + best_candidate = &other; + best_idx = j; + best_dot = test_dot; + } + } + if (best_candidate != nullptr) { + //idf++; + //std::cout << " == fusion " << id <<" : "<< idf << " ==\n"; + // delete very near points + remove_point_too_near(&polyline); + remove_point_too_near(best_candidate); + + // add point at the same pos than the other line to have a nicer fusion + add_point_same_percent(&polyline, best_candidate); + add_point_same_percent(best_candidate, &polyline); + + //get the angle of the nearest points of the contour to see : _| (good) \_ (average) __(bad) + //sqrt because the result are nicer this way: don't over-penalize /_ angles + //TODO: try if we can achieve a better result if we use a different algo if the angle is <90° + const double coeff_angle_poly = (coeff_angle_cache.find(polyline.points.back()) != coeff_angle_cache.end()) + ? coeff_angle_cache[polyline.points.back()] + : (get_coeff_from_angle_countour(polyline.points.back(), this->expolygon, min(min_width, polyline.length() / 2))); + const double coeff_angle_candi = (coeff_angle_cache.find(best_candidate->points.back()) != coeff_angle_cache.end()) + ? coeff_angle_cache[best_candidate->points.back()] + : (get_coeff_from_angle_countour(best_candidate->points.back(), this->expolygon, min(min_width, best_candidate->length() / 2))); + + //this will encourage to follow the curve, a little, because it's shorter near the center + //without that, it tends to go to the outter rim. + //std::cout << " max(polyline.length(), best_candidate->length())=" << max(polyline.length(), best_candidate->length()) + // << ", polyline.length()=" << polyline.length() + // << ", best_candidate->length()=" << best_candidate->length() + // << ", polyline.length() / max=" << (polyline.length() / max(polyline.length(), best_candidate->length())) + // << ", best_candidate->length() / max=" << (best_candidate->length() / max(polyline.length(), best_candidate->length())) + // << "\n"; + double weight_poly = 2 - (polyline.length() / max(polyline.length(), best_candidate->length())); + double weight_candi = 2 - (best_candidate->length() / max(polyline.length(), best_candidate->length())); + weight_poly *= coeff_angle_poly; + weight_candi *= coeff_angle_candi; + const double coeff_poly = (dot_poly_branch * weight_poly) / (dot_poly_branch * weight_poly + dot_candidate_branch * weight_candi); + const double coeff_candi = 1.0 - coeff_poly; + //std::cout << "coeff_angle_poly=" << coeff_angle_poly + // << ", coeff_angle_candi=" << coeff_angle_candi + // << ", weight_poly=" << (2 - (polyline.length() / max(polyline.length(), best_candidate->length()))) + // << ", weight_candi=" << (2 - (best_candidate->length() / max(polyline.length(), best_candidate->length()))) + // << ", sumpoly=" << weight_poly + // << ", sumcandi=" << weight_candi + // << ", dot_poly_branch=" << dot_poly_branch + // << ", dot_candidate_branch=" << dot_candidate_branch + // << ", coeff_poly=" << coeff_poly + // << ", coeff_candi=" << coeff_candi + // << "\n"; + //iterate the points + // as voronoi should create symetric thing, we can iterate synchonously + size_t idx_point = 1; + while (idx_point < min(polyline.points.size(), best_candidate->points.size())) { + //fusion + polyline.points[idx_point].x = polyline.points[idx_point].x * coeff_poly + best_candidate->points[idx_point].x * coeff_candi; + polyline.points[idx_point].y = polyline.points[idx_point].y * coeff_poly + best_candidate->points[idx_point].y * coeff_candi; + + // The width decrease with distance from the centerline. + // This formula is what works the best, even if it's not perfect (created empirically). 0->3% error on a gap fill on some tests. + //If someone find an other formula based on the properties of the voronoi algorithm used here, and it works better, please use it. + //or maybe just use the distance to nearest edge in bounds... + double value_from_current_width = 0.5*polyline.width[idx_point] * dot_poly_branch / max(dot_poly_branch, dot_candidate_branch); + value_from_current_width += 0.5*best_candidate->width[idx_point] * dot_candidate_branch / max(dot_poly_branch, dot_candidate_branch); + double value_from_dist = 2 * polyline.points[idx_point].distance_to(best_candidate->points[idx_point]); + value_from_dist *= sqrt(min(dot_poly_branch, dot_candidate_branch) / max(dot_poly_branch, dot_candidate_branch)); + polyline.width[idx_point] = value_from_current_width + value_from_dist; + //std::cout << "width:" << polyline.width[idx_point] << " = " << value_from_current_width << " + " << value_from_dist + // << " (<" << max_width << " && " << (bounds.contour.closest_point(polyline.points[idx_point])->distance_to(polyline.points[idx_point]) * 2.1)<<")\n"; + //failsafes + if (polyline.width[idx_point] > max_width) + polyline.width[idx_point] = max_width; + const coord_t max_width_contour = bounds.contour.closest_point(polyline.points[idx_point])->distance_to(polyline.points[idx_point]) * 2.1; + if (polyline.width[idx_point] > max_width_contour) + polyline.width[idx_point] = max_width_contour; + + ++idx_point; + } + if (idx_point < best_candidate->points.size()) { + if (idx_point + 1 < best_candidate->points.size()) { + //create a new polyline + pp.emplace_back(); + pp.back().endpoints.first = true; + pp.back().endpoints.second = best_candidate->endpoints.second; + for (size_t idx_point_new_line = idx_point; idx_point_new_line < best_candidate->points.size(); ++idx_point_new_line) { + pp.back().points.push_back(best_candidate->points[idx_point_new_line]); + pp.back().width.push_back(best_candidate->width[idx_point_new_line]); + } + } else { + //Add last point + polyline.points.push_back(best_candidate->points[idx_point]); + polyline.width.push_back(best_candidate->width[idx_point]); + //select if an end opccur + polyline.endpoints.second &= best_candidate->endpoints.second; + } + + } else { + //select if an end opccur + polyline.endpoints.second &= best_candidate->endpoints.second; + } + + //remove points that are the same or too close each other, ie simplify + for (size_t idx_point = 1; idx_point < polyline.points.size(); ++idx_point) { + if (polyline.points[idx_point - 1].distance_to(polyline.points[idx_point]) < SCALED_EPSILON) { + if (idx_point < polyline.points.size() - 1) { + polyline.points.erase(polyline.points.begin() + idx_point); + polyline.width.erase(polyline.width.begin() + idx_point); + } else { + polyline.points.erase(polyline.points.begin() + idx_point - 1); + polyline.width.erase(polyline.width.begin() + idx_point - 1); + } + --idx_point; + } + } + //remove points that are outside of the geometry + for (size_t idx_point = 0; idx_point < polyline.points.size(); ++idx_point) { + if (!bounds.contains_b(polyline.points[idx_point])) { + polyline.points.erase(polyline.points.begin() + idx_point); + polyline.width.erase(polyline.width.begin() + idx_point); + --idx_point; + } + } + + //update cache + coeff_angle_cache[polyline.points.back()] = coeff_angle_poly * coeff_poly + coeff_angle_candi * coeff_candi; + + + if (polyline.points.size() < 2) { + //remove self + pp.erase(pp.begin() + i); + --i; + --best_idx; + } + + pp.erase(pp.begin() + best_idx); + //{ + // stringstream stri; + // stri << "medial_axis_2.0_aft_fus_" << id << "_" << idf << ".svg"; + // SVG svg(stri.str()); + // svg.draw(bounds); + // svg.draw(this->expolygon); + // svg.draw(pp); + // svg.Close(); + //} + changes = true; + break; + } + } + if (changes) { + concatThickPolylines(pp); + ///reorder, in case of change + std::sort(pp.begin(), pp.end(), [](const ThickPolyline & a, const ThickPolyline & b) { + bool ahas0 = a.width.front() == 0 || a.width.back() == 0; + bool bhas0 = b.width.front() == 0 || b.width.back() == 0; + if (ahas0 && !bhas0) return true; + if (!ahas0 && bhas0) return false; + return a.length() < b.length(); + }); + } + } +} + +void +MedialAxis::remove_too_thin_extrusion(ThickPolylines& pp) +{ + // remove too thin extrusion at start & end of polylines + bool changes = false; + for (size_t i = 0; i < pp.size(); ++i) { + ThickPolyline& polyline = pp[i]; + // remove bits with too small extrusion + while (polyline.points.size() > 1 && polyline.width.front() < this->min_width && polyline.endpoints.first) { + //try to split if possible + if (polyline.width[1] > min_width) { + double percent_can_keep = (min_width - polyline.width[0]) / (polyline.width[1] - polyline.width[0]); + if (polyline.points.front().distance_to(polyline.points[1]) * percent_can_keep > this->max_width / 2 + && polyline.points.front().distance_to(polyline.points[1])* (1 - percent_can_keep) > this->max_width / 2) { + //Can split => move the first point and assign a new weight. + //the update of endpoints wil be performed in concatThickPolylines + polyline.points.front().x = polyline.points.front().x + + (coord_t)((polyline.points[1].x - polyline.points.front().x) * percent_can_keep); + polyline.points.front().y = polyline.points.front().y + + (coord_t)((polyline.points[1].y - polyline.points.front().y) * percent_can_keep); + polyline.width.front() = min_width; + changes = true; + break; + } + } + polyline.points.erase(polyline.points.begin()); + polyline.width.erase(polyline.width.begin()); + changes = true; + } + while (polyline.points.size() > 1 && polyline.width.back() < this->min_width && polyline.endpoints.second) { + //try to split if possible + if (polyline.width[polyline.points.size() - 2] > min_width) { + double percent_can_keep = (min_width - polyline.width.back()) / (polyline.width[polyline.points.size() - 2] - polyline.width.back()); + if (polyline.points.back().distance_to(polyline.points[polyline.points.size() - 2]) * percent_can_keep > this->max_width / 2 + && polyline.points.back().distance_to(polyline.points[polyline.points.size() - 2]) * (1 - percent_can_keep) > this->max_width / 2) { + //Can split => move the first point and assign a new weight. + //the update of endpoints wil be performed in concatThickPolylines + polyline.points.back().x = polyline.points.back().x + + (coord_t)((polyline.points[polyline.points.size() - 2].x - polyline.points.back().x) * percent_can_keep); + polyline.points.back().y = polyline.points.back().y + + (coord_t)((polyline.points[polyline.points.size() - 2].y - polyline.points.back().y) * percent_can_keep); + polyline.width.back() = min_width; + changes = true; + break; + } + } + polyline.points.erase(polyline.points.end() - 1); + polyline.width.erase(polyline.width.end() - 1); + changes = true; + } + if (polyline.points.size() < 2) { + //remove self if too small + pp.erase(pp.begin() + i); + --i; + } + } + if (changes) concatThickPolylines(pp); +} + +void +MedialAxis::concatenate_polylines_with_crossing(ThickPolylines& pp) +{ + + // concatenate, but even where multiple thickpolyline join, to create nice long strait polylines + /* 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 : now we select the most "strait line" choice + when we merge with an other line at a point with more than two meet. + */ + bool changes = false; + 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; + size_t 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() + 1, best_candidate->width.end()); + polyline.endpoints.second = best_candidate->endpoints.second; + assert(polyline.width.size() == polyline.points.size()); + changes = true; + pp.erase(pp.begin() + best_idx); + } + } + if (changes) concatThickPolylines(pp); +} + +void +MedialAxis::remove_too_thin_points(ThickPolylines& pp) +{ + //remove too thin polylines points (inside a polyline : split it) + for (size_t i = 0; i < pp.size(); ++i) { + ThickPolyline& polyline = pp[i]; + + // remove bits with too small extrusion + size_t idx_point = 0; + while (idx_point polyline.length()) { + shortest_size = polyline.length(); + shortest_idx = i; + } + + } + } + if (shortest_idx >= 0 && shortest_idx < pp.size()) { + pp.erase(pp.begin() + shortest_idx); + changes = true; + } + if (changes) concatThickPolylines(pp); + } +} + +void +MedialAxis::ensure_not_overextrude(ThickPolylines& pp) +{ + //ensure the volume extruded is correct for what we have been asked + // => don't over-extrude + double surface = 0; + double volume = 0; + for (ThickPolyline& polyline : pp) { + for (ThickLine l : polyline.thicklines()) { + surface += l.length() * (l.a_width + l.b_width) / 2; + double width_mean = (l.a_width + l.b_width) / 2; + volume += height * (width_mean - height * (1. - 0.25 * PI)) * l.length(); + } + } + + // compute bounds volume + double boundsVolume = 0; + boundsVolume += height*bounds.area(); + // add external "perimeter gap" + double perimeterRoundGap = bounds.contour.length() * height * (1 - 0.25*PI) * 0.5; + // add holes "perimeter gaps" + double holesGaps = 0; + for (auto hole = bounds.holes.begin(); hole != bounds.holes.end(); ++hole) { + holesGaps += hole->length() * height * (1 - 0.25*PI) * 0.5; + } + boundsVolume += perimeterRoundGap + holesGaps; + + if (boundsVolume < volume) { + //reduce width + double reduce_by = boundsVolume / volume; + for (ThickPolyline& polyline : pp) { + for (ThickLine l : polyline.thicklines()) { + l.a_width *= reduce_by; + l.b_width *= reduce_by; + } + } + } +} + +ExPolygon +MedialAxis::simplify_polygon_frontier() +{ + + //simplify the boundary between us and the bounds. + //int firstIdx = 0; + //while (firstIdx < contour.points.size() && bounds.contour.contains(contour.points[firstIdx])) firstIdx++; + ExPolygon simplified_poly = this->surface; + if (&this->surface != &this->bounds) { + bool need_intersect = false; + for (size_t i = 0; i < simplified_poly.contour.points.size(); i++) { + Point &p_check = simplified_poly.contour.points[i]; + //if (!find) { + if (!bounds.has_boundary_point(p_check)) { + //check if we put it at a bound point instead of delete it + size_t prev_i = i == 0 ? simplified_poly.contour.points.size() - 1 : (i - 1); + size_t next_i = i == simplified_poly.contour.points.size() - 1 ? 0 : (i + 1); + const Point* closest = bounds.contour.closest_point(p_check); + if (closest != nullptr && closest->distance_to(p_check) + SCALED_EPSILON + < min(p_check.distance_to(simplified_poly.contour.points[prev_i]), p_check.distance_to(simplified_poly.contour.points[next_i])) / 2) { + p_check.x = closest->x; + p_check.y = closest->y; + need_intersect = true; + } else { + simplified_poly.contour.points.erase(simplified_poly.contour.points.begin() + i); + i--; + } + } + } + if (need_intersect) { + ExPolygons simplified_polys = intersection_ex(simplified_poly, bounds); + if (simplified_polys.size() == 1) { + simplified_poly = simplified_polys[0]; + } else { + simplified_poly = this->surface; + } + } + } + + simplified_poly.remove_point_too_near(SCALED_RESOLUTION); + return simplified_poly; +} + +void +MedialAxis::build(ThickPolylines* polylines_out) +{ + this->id++; + + this->expolygon = simplify_polygon_frontier(); + + + // compute the Voronoi diagram and extract medial axis polylines + ThickPolylines pp; + this->polyline_from_voronoi(this->expolygon.lines(), &pp); + + + //{ + // stringstream stri; + // stri << "medial_axis_1_voronoi_" << id << ".svg"; + // SVG svg(stri.str()); + // svg.draw(bounds); + // svg.draw(this->expolygon); + // svg.draw(pp); + // svg.Close(); + //} + + /* 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())); + + + fusion_curve(pp); + //{ + // stringstream stri; + // stri << "medial_axis_2_curve_" << id << ".svg"; + // SVG svg(stri.str()); + // svg.draw(bounds); + // svg.draw(this->expolygon); + // svg.draw(pp); + // svg.Close(); + //} + + concatThickPolylines(pp); + + // 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. + // + main_fusion(pp); + //{ + // stringstream stri; + // stri << "medial_axis_3_fusion_" << id << ".svg"; + // SVG svg(stri.str()); + // svg.draw(bounds); + // svg.draw(this->expolygon); + // svg.draw(pp); + // svg.Close(); + //} + + //fusion right-angle corners. + fusion_corners(pp); + + //reduce extrusion when it's too thin to be printable + remove_too_thin_extrusion(pp); + //{ + // stringstream stri; + // stri << "medial_axis_4_thinok_" << id << ".svg"; + // SVG svg(stri.str()); + // svg.draw(bounds); + // svg.draw(this->expolygon); + // svg.draw(pp); + // svg.Close(); + //} + + remove_too_thin_points(pp); + //{ + // stringstream stri; + // stri << "medial_axis_5.0_thuinner_" << id << ".svg"; + // SVG svg(stri.str()); + // svg.draw(bounds); + // svg.draw(this->expolygon); + // svg.draw(pp); + // svg.Close(); + //} + + // Loop through all returned polylines in order to extend their endpoints to the + // expolygon boundaries + const ExPolygons anchors = offset2_ex(diff_ex(this->bounds, this->expolygon), -SCALED_RESOLUTION, SCALED_RESOLUTION); + for (size_t i = 0; i < pp.size(); ++i) { + ThickPolyline& polyline = pp[i]; + extends_line(polyline, anchors, min_width); + polyline.reverse(); + extends_line(polyline, anchors, min_width); + } + //{ + // stringstream stri; + // stri << "medial_axis_5_expand_" << id << ".svg"; + // SVG svg(stri.str()); + // svg.draw(bounds); + // svg.draw(this->expolygon); + // svg.draw(pp); + // svg.Close(); + //} + + concatenate_polylines_with_crossing(pp); + //{ + // stringstream stri; + // stri << "medial_axis_6_concat_" << id << ".svg"; + // SVG svg(stri.str()); + // svg.draw(bounds); + // svg.draw(this->expolygon); + // svg.draw(pp); + // svg.Close(); + //} + + remove_too_short_polylines(pp, max_w * 2); + //{ + // stringstream stri; + // stri << "medial_axis_8_tooshort_" << id << ".svg"; + // SVG svg(stri.str()); + // svg.draw(bounds); + // svg.draw(this->expolygon); + // svg.draw(pp); + // svg.Close(); + //} + + //TODO: reduce the flow at the intersection ( + ) points ? + ensure_not_overextrude(pp); + //{ + // stringstream stri; + // stri << "medial_axis_9_endn_" << id << ".svg"; + // SVG svg(stri.str()); + // svg.draw(bounds); + // svg.draw(this->expolygon); + // svg.draw(pp); + // svg.Close(); + //} + + polylines_out->insert(polylines_out->end(), pp.begin(), pp.end()); + +} + +} // namespace Slic3r diff --git a/xs/src/libslic3r/MedialAxis.hpp b/xs/src/libslic3r/MedialAxis.hpp new file mode 100644 index 0000000000..c65cbdb84b --- /dev/null +++ b/xs/src/libslic3r/MedialAxis.hpp @@ -0,0 +1,63 @@ +#ifndef slic3r_MedialAxis_hpp_ +#define slic3r_MedialAxis_hpp_ + +#include "libslic3r.h" +#include "ExPolygon.hpp" +#include "Polyline.hpp" +#include "Geometry.hpp" +#include + +#include "boost/polygon/voronoi.hpp" +using boost::polygon::voronoi_builder; +using boost::polygon::voronoi_diagram; + +namespace Slic3r { + + class MedialAxis { + public: + Lines lines; //lines is here only to avoid appassing it in argument of amny method. Initialized in polyline_from_voronoi. + ExPolygon expolygon; + const ExPolygon& bounds; + const ExPolygon& surface; + const double max_width; + const double min_width; + const double height; + MedialAxis(const ExPolygon &_expolygon, const ExPolygon &_bounds, const double _max_width, const double _min_width, const double _height) + : surface(_expolygon), bounds(_bounds), max_width(_max_width), min_width(_min_width), height(_height) { + }; + void build(ThickPolylines* polylines_out); + void build(Polylines* polylines); + + private: + static int id; + class VD : public voronoi_diagram { + public: + typedef double coord_type; + typedef boost::polygon::point_data point_type; + typedef boost::polygon::segment_data segment_type; + typedef boost::polygon::rectangle_data rect_type; + }; + VD vd; + std::set edges, valid_edges; + std::map > thickness; + void process_edge_neighbors(const VD::edge_type* edge, ThickPolyline* polyline); + bool validate_edge(const VD::edge_type* edge); + const Line& retrieve_segment(const VD::cell_type* cell) const; + const Point& retrieve_endpoint(const VD::cell_type* cell) const; + void polyline_from_voronoi(const Lines& voronoi_edges, ThickPolylines* polylines_out); + + ExPolygon simplify_polygon_frontier(); + void fusion_curve(ThickPolylines &pp); + void main_fusion(ThickPolylines& pp); + void fusion_corners(ThickPolylines &pp); + void extends_line(ThickPolyline& polyline, const ExPolygons& anchors, const coord_t join_width); + void remove_too_thin_extrusion(ThickPolylines& pp); + void concatenate_polylines_with_crossing(ThickPolylines& pp); + void remove_too_thin_points(ThickPolylines& pp); + void remove_too_short_polylines(ThickPolylines& pp, const coord_t min_size); + void ensure_not_overextrude(ThickPolylines& pp); + }; +} + + +#endif From 2235c876d2ad6228b680de65bd79b94c1a82447a Mon Sep 17 00:00:00 2001 From: supermerill Date: Mon, 3 Dec 2018 14:09:21 +0100 Subject: [PATCH 04/25] correct error with Polygon / ExPolygon add to cmakelist add needed function (mostly copy from slic3rPE) --- src/CMakeLists.txt | 1 + xs/MANIFEST | 2 ++ xs/src/libslic3r/ExPolygon.cpp | 6 +++--- xs/src/libslic3r/MedialAxis.cpp | 12 ++++++------ xs/src/libslic3r/MedialAxis.hpp | 3 ++- xs/src/libslic3r/MultiPoint.hpp | 19 +++++++++++++++++++ xs/src/libslic3r/PerimeterGenerator.cpp | 11 ++++++----- xs/src/libslic3r/Point.cpp | 8 ++++++++ xs/src/libslic3r/Point.hpp | 16 ++++++++++++++-- 9 files changed, 61 insertions(+), 17 deletions(-) diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 996b15c1c6..b8c3b88dee 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -199,6 +199,7 @@ add_library(libslic3r STATIC ${LIBDIR}/libslic3r/LayerHeightSpline.cpp ${LIBDIR}/libslic3r/Line.cpp ${LIBDIR}/libslic3r/Log.cpp + ${LIBDIR}/libslic3r/MedialAxis.cpp ${LIBDIR}/libslic3r/Model.cpp ${LIBDIR}/libslic3r/MotionPlanner.cpp ${LIBDIR}/libslic3r/MultiPoint.cpp diff --git a/xs/MANIFEST b/xs/MANIFEST index 4d20158719..3295cbd2c2 100644 --- a/xs/MANIFEST +++ b/xs/MANIFEST @@ -121,6 +121,8 @@ src/libslic3r/libslic3r.h src/libslic3r/Line.cpp src/libslic3r/Line.hpp src/libslic3r/Log.hpp +src/libslic3r/MedialAxis.cpp +src/libslic3r/MedialAxis.hpp src/libslic3r/Model.cpp src/libslic3r/Model.hpp src/libslic3r/MotionPlanner.cpp diff --git a/xs/src/libslic3r/ExPolygon.cpp b/xs/src/libslic3r/ExPolygon.cpp index 8eef314e0d..87c119a04f 100644 --- a/xs/src/libslic3r/ExPolygon.cpp +++ b/xs/src/libslic3r/ExPolygon.cpp @@ -191,16 +191,16 @@ void ExPolygon::remove_point_too_near(const coord_t tolerance) { size_t id = 1; while (id < this->contour.points.size() - 1) { - size_t newdist = min(this->contour.points[id].distance_to(this->contour.points[id - 1]) + double newdist = min(this->contour.points[id].distance_to(this->contour.points[id - 1]) , this->contour.points[id].distance_to(this->contour.points[id + 1])); - if (newdist < tolerance) { + if (newdist < (double)tolerance) { this->contour.points.erase(this->contour.points.begin() + id); newdist = this->contour.points[id].distance_to(this->contour.points[id - 1]); } //go to next one //if you removed a point, it check if the next one isn't too near from the previous one. // if not, it byepass it. - if (newdist > tolerance) { + if (newdist > (double)tolerance) { ++id; } } diff --git a/xs/src/libslic3r/MedialAxis.cpp b/xs/src/libslic3r/MedialAxis.cpp index 27b77e197a..9c5e3d37c5 100644 --- a/xs/src/libslic3r/MedialAxis.cpp +++ b/xs/src/libslic3r/MedialAxis.cpp @@ -44,7 +44,7 @@ MedialAxis::polyline_from_voronoi(const Lines& voronoi_edges, ThickPolylines* po } */ - typedef const VD::vertex_type vert_t; + // typedef const VD::vertex_type vert_t; typedef const VD::edge_type edge_t; // collect valid edges (i.e. prune those not belonging to MAT) @@ -168,10 +168,10 @@ bool MedialAxis::validate_edge(const VD::edge_type* edge) { // prevent overflows and detect almost-infinite edges - if (std::abs(edge->vertex0()->x()) > double(CLIPPER_MAX_COORD_UNSCALED) || - std::abs(edge->vertex0()->y()) > double(CLIPPER_MAX_COORD_UNSCALED) || - std::abs(edge->vertex1()->x()) > double(CLIPPER_MAX_COORD_UNSCALED) || - std::abs(edge->vertex1()->y()) > double(CLIPPER_MAX_COORD_UNSCALED)) + if (std::abs(edge->vertex0()->x()) > double(MAX_COORD) || + std::abs(edge->vertex0()->y()) > double(MAX_COORD) || + std::abs(edge->vertex1()->x()) > double(MAX_COORD) || + std::abs(edge->vertex1()->y()) > double(MAX_COORD)) return false; // construct the line representing this edge of the Voronoi diagram @@ -1355,7 +1355,7 @@ MedialAxis::build(ThickPolylines* polylines_out) // Loop through all returned polylines in order to extend their endpoints to the // expolygon boundaries - const ExPolygons anchors = offset2_ex(diff_ex(this->bounds, this->expolygon), -SCALED_RESOLUTION, SCALED_RESOLUTION); + const ExPolygons anchors = offset2_ex(to_polygons(diff_ex(this->bounds, this->expolygon)), -SCALED_RESOLUTION, SCALED_RESOLUTION); for (size_t i = 0; i < pp.size(); ++i) { ThickPolyline& polyline = pp[i]; extends_line(polyline, anchors, min_width); diff --git a/xs/src/libslic3r/MedialAxis.hpp b/xs/src/libslic3r/MedialAxis.hpp index c65cbdb84b..93f6725db2 100644 --- a/xs/src/libslic3r/MedialAxis.hpp +++ b/xs/src/libslic3r/MedialAxis.hpp @@ -17,8 +17,9 @@ namespace Slic3r { public: Lines lines; //lines is here only to avoid appassing it in argument of amny method. Initialized in polyline_from_voronoi. ExPolygon expolygon; - const ExPolygon& bounds; + const ExPolygon& surface; + const ExPolygon& bounds; const double max_width; const double min_width; const double height; diff --git a/xs/src/libslic3r/MultiPoint.hpp b/xs/src/libslic3r/MultiPoint.hpp index 9f67ca9525..791d440f93 100644 --- a/xs/src/libslic3r/MultiPoint.hpp +++ b/xs/src/libslic3r/MultiPoint.hpp @@ -31,6 +31,25 @@ class MultiPoint int find_point(const Point &point) const; bool has_boundary_point(const Point &point) const; + /// return the index of the closest point in this polygon in relation with "point" + /// \param point the point to compare with. + /// \return the index of the closest point in the points vector. + size_t closest_point_index(const Point &point) const { + size_t idx = -1; + if (! this->points.empty()) { + idx = 0; + double dist_min = this->points.front().distance_to_sq(point); + for (size_t i = 1; i < this->points.size(); ++ i) { + double d = this->points[i].distance_to_sq(point); + if (d < dist_min) { + dist_min = d; + idx = i; + } + } + } + return idx; + } + const Point* closest_point(const Point &point) const { return this->points.empty() ? nullptr : &this->points[this->closest_point_index(point)]; } BoundingBox bounding_box() const; // Return true if there are exact duplicates. diff --git a/xs/src/libslic3r/PerimeterGenerator.cpp b/xs/src/libslic3r/PerimeterGenerator.cpp index 165708929a..31ac2eaf2f 100644 --- a/xs/src/libslic3r/PerimeterGenerator.cpp +++ b/xs/src/libslic3r/PerimeterGenerator.cpp @@ -121,20 +121,21 @@ PerimeterGenerator::process() // medial axis requires non-overlapping geometry Polygons thin_zones = diff(last, no_thin_zone, true); //don't use offset2_ex, because we don't want to merge the zones that have been separated. - Polygons expp = offset(thin_zones, (float)(-min_width / 2)); + ExPolygons expp = offset_ex(thin_zones, (float)(-min_width / 2)); //we push the bits removed and put them into what we will use as our anchor if (expp.size() > 0) { - no_thin_zone = diff(last, offset(expp, (float)(min_width / 2)), true); + no_thin_zone = diff(last, to_polygons(offset_ex(expp, (float)(min_width / 2))), true); } // compute a bit of overlap to anchor thin walls inside the print. - for (Polygon &ex : expp) { + for (ExPolygon &ex : expp) { //growing back the polygon //a very little bit of overlap can be created here with other thin polygons, but it's more useful than worisome. ex.remove_point_too_near(SCALED_RESOLUTION); ExPolygons ex_bigger = offset_ex(ex, (float)(min_width / 2)); if (ex_bigger.size() != 1) continue; // impossible error, growing a single polygon can't create multiple or 0. - ExPolygons anchor = intersection_ex(offset(ex, (float)(ext_pwidth / 2), - CLIPPER_OFFSET_SCALE, jtSquare, 3), no_thin_zone, true); + ExPolygons anchor = intersection_ex( + to_polygons(offset_ex(ex, (float)(ext_pwidth / 2), CLIPPER_OFFSET_SCALE, jtSquare, 3)), + no_thin_zone, true); ExPolygons bounds = _clipper_ex(ClipperLib::ctUnion, to_polygons(ex_bigger), to_polygons(anchor), true); for (ExPolygon &bound : bounds) { if (!intersection_ex(ex_bigger[0], bound).empty()) { diff --git a/xs/src/libslic3r/Point.cpp b/xs/src/libslic3r/Point.cpp index c67dadb3bf..bf5dcd13a4 100644 --- a/xs/src/libslic3r/Point.cpp +++ b/xs/src/libslic3r/Point.cpp @@ -187,6 +187,14 @@ Point::distance_to(const Point &point) const return sqrt(dx*dx + dy*dy); } +double +Point::distance_to_sq(const Point &point) const +{ + double dx = ((double)point.x - this->x); + double dy = ((double)point.y - this->y); + return dx*dx + dy*dy; +} + /* distance to the closest point of line */ double Point::distance_to(const Line &line) const diff --git a/xs/src/libslic3r/Point.hpp b/xs/src/libslic3r/Point.hpp index d52e31aaf9..922a55bffc 100644 --- a/xs/src/libslic3r/Point.hpp +++ b/xs/src/libslic3r/Point.hpp @@ -47,6 +47,7 @@ class Point static Point new_scale(Pointf p); bool operator==(const Point& rhs) const; bool operator!=(const Point& rhs) const { return !(*this == rhs); } + bool operator< (const Point& rhs) const { return this->x < rhs.x || (this->x == rhs.x && this->y < rhs.y); } std::string wkt() const; std::string dump_perl() const; void scale(double factor); @@ -73,6 +74,7 @@ class Point bool nearest_point(const Points &points, Point* point) const; bool nearest_waypoint(const Points &points, const Point &dest, Point* point) const; double distance_to(const Point &point) const; + double distance_to_sq(const Point &point) const; double distance_to(const Line &line) const; double perp_distance_to(const Line &line) const; double ccw(const Point &p1, const Point &p2) const; @@ -128,6 +130,8 @@ class Pointf bool operator==(const Pointf& rhs) const; bool coincides_with_epsilon(const Pointf& rhs) const; Pointf& operator/=(const double& scalar); + bool operator!=(const Pointf &rhs) const { return ! (*this == rhs); } + bool operator< (const Pointf& rhs) const { return this->x < rhs.x || (this->x == rhs.x && this->y < rhs.y); } std::string wkt() const; std::string dump_perl() const; @@ -138,12 +142,18 @@ class Pointf void rotate(double angle, const Pointf ¢er); Pointf negative() const; Vectorf vector_to(const Pointf &point) const; + }; Pointf operator+(const Pointf& point1, const Pointf& point2); Pointf operator/(const Pointf& point1, const double& scalar); - -std::ostream& operator<<(std::ostream &stm, const Pointf3 &pointf3); +inline Pointf operator*(double scalar, const Pointf& p) { return Pointf(scalar * p.x, scalar * p.y); } +inline Pointf operator*(const Pointf& p, double scalar) { return Pointf(scalar * p.x, scalar * p.y); } +inline Vectorf normalize(const Vectorf& v) +{ + coordf_t len = sqrt((v.x*v.x) + (v.y*v.y)); + return (len != 0.0) ? 1.0 / len * v : Vectorf(0.0, 0.0); +} class Pointf3 : public Pointf { @@ -161,6 +171,8 @@ class Pointf3 : public Pointf Vectorf3 vector_to(const Pointf3 &point) const; }; +std::ostream& operator<<(std::ostream &stm, const Pointf3 &pointf3); + template inline Points to_points(const std::vector &items) From 97c226af146d456ba6e2e55dbcc276a7ac266222 Mon Sep 17 00:00:00 2001 From: supermerill Date: Mon, 3 Dec 2018 20:06:43 +0100 Subject: [PATCH 05/25] Correct thin_wall bugs discovered with disc.stl from lordofhyphens --- xs/src/libslic3r/MedialAxis.cpp | 55 +++++++++++++++++++++------------ xs/src/libslic3r/Point.hpp | 2 +- 2 files changed, 36 insertions(+), 21 deletions(-) diff --git a/xs/src/libslic3r/MedialAxis.cpp b/xs/src/libslic3r/MedialAxis.cpp index 9c5e3d37c5..e30e370521 100644 --- a/xs/src/libslic3r/MedialAxis.cpp +++ b/xs/src/libslic3r/MedialAxis.cpp @@ -613,8 +613,12 @@ MedialAxis::extends_line(ThickPolyline& polyline, const ExPolygons& anchors, con // 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 if (polyline.endpoints.second && !bounds.has_boundary_point(polyline.points.back())) { - Line line(*(polyline.points.end() - 2), polyline.points.back()); - + size_t first_idx = polyline.points.size() - 2; + Line line(*(polyline.points.begin() + first_idx), polyline.points.back()); + while (line.length() < SCALED_RESOLUTION && first_idx>0) { + first_idx--; + line.a = *(polyline.points.begin() + first_idx); + } // prevent the line from touching on the other side, otherwise intersection() might return that solution if (polyline.points.size() == 2) line.a = line.midpoint(); @@ -624,14 +628,18 @@ MedialAxis::extends_line(ThickPolyline& polyline, const ExPolygons& anchors, con new_back = polyline.points.back(); } else { (void)this->expolygon.contour.first_intersection(line, &new_back); + // safety check if no intersection + if (new_back.x == 0 && new_back.y == 0) new_back = line.b; polyline.points.push_back(new_back); polyline.width.push_back(polyline.width.back()); } Point new_bound; (void)bounds.contour.first_intersection(line, &new_bound); - /* if (new_bound.coincides_with_epsilon(new_back)) { - return; - }*/ + // safety check if no intersection + if (new_bound.x == 0 && new_bound.y == 0) { + if (line.b.coincides_with_epsilon(polyline.points.back())) return; + new_bound = line.b; + } // find anchor Point best_anchor; double shortest_dist = max_width; @@ -761,7 +769,7 @@ MedialAxis::main_fusion(ThickPolylines& pp) coord_t biggest_main_branch_length = 0; for (size_t k = 0; k < pp.size(); ++k) { //std::cout << "try to find main : " << k << " ? " << i << " " << j << " "; - if (k == i | k == j) continue; + if (k == i || k == j) continue; ThickPolyline& main = pp[k]; if (polyline.first_point().coincides_with(main.last_point())) { main.reverse(); @@ -989,19 +997,23 @@ MedialAxis::remove_too_thin_extrusion(ThickPolylines& pp) while (polyline.points.size() > 1 && polyline.width.front() < this->min_width && polyline.endpoints.first) { //try to split if possible if (polyline.width[1] > min_width) { - double percent_can_keep = (min_width - polyline.width[0]) / (polyline.width[1] - polyline.width[0]); - if (polyline.points.front().distance_to(polyline.points[1]) * percent_can_keep > this->max_width / 2 - && polyline.points.front().distance_to(polyline.points[1])* (1 - percent_can_keep) > this->max_width / 2) { + double percent_can_keep = 1 - (min_width - polyline.width[0]) / (polyline.width[1] - polyline.width[0]); + if (polyline.points.front().distance_to(polyline.points[1]) * percent_can_keep > SCALED_RESOLUTION) { //Can split => move the first point and assign a new weight. //the update of endpoints wil be performed in concatThickPolylines polyline.points.front().x = polyline.points.front().x + - (coord_t)((polyline.points[1].x - polyline.points.front().x) * percent_can_keep); + (coord_t)((polyline.points[1].x - polyline.points.front().x) * (1 - percent_can_keep)); polyline.points.front().y = polyline.points.front().y + - (coord_t)((polyline.points[1].y - polyline.points.front().y) * percent_can_keep); + (coord_t)((polyline.points[1].y - polyline.points.front().y) * (1 - percent_can_keep)); polyline.width.front() = min_width; - changes = true; - break; + } else { + /// almost 0-length, Remove + std::cout << id << " remove the point"<<"\n"; + polyline.points.erase(polyline.points.begin()); + polyline.width.erase(polyline.width.begin()); } + changes = true; + break; } polyline.points.erase(polyline.points.begin()); polyline.width.erase(polyline.width.begin()); @@ -1010,19 +1022,22 @@ MedialAxis::remove_too_thin_extrusion(ThickPolylines& pp) while (polyline.points.size() > 1 && polyline.width.back() < this->min_width && polyline.endpoints.second) { //try to split if possible if (polyline.width[polyline.points.size() - 2] > min_width) { - double percent_can_keep = (min_width - polyline.width.back()) / (polyline.width[polyline.points.size() - 2] - polyline.width.back()); - if (polyline.points.back().distance_to(polyline.points[polyline.points.size() - 2]) * percent_can_keep > this->max_width / 2 - && polyline.points.back().distance_to(polyline.points[polyline.points.size() - 2]) * (1 - percent_can_keep) > this->max_width / 2) { + double percent_can_keep = 1 - (min_width - polyline.width.back()) / (polyline.width[polyline.points.size() - 2] - polyline.width.back()); + if (polyline.points.back().distance_to(polyline.points[polyline.points.size() - 2]) * percent_can_keep > SCALED_RESOLUTION) { //Can split => move the first point and assign a new weight. //the update of endpoints wil be performed in concatThickPolylines polyline.points.back().x = polyline.points.back().x + - (coord_t)((polyline.points[polyline.points.size() - 2].x - polyline.points.back().x) * percent_can_keep); + (coord_t)((polyline.points[polyline.points.size() - 2].x - polyline.points.back().x) * (1 - percent_can_keep)); polyline.points.back().y = polyline.points.back().y + - (coord_t)((polyline.points[polyline.points.size() - 2].y - polyline.points.back().y) * percent_can_keep); + (coord_t)((polyline.points[polyline.points.size() - 2].y - polyline.points.back().y) * (1 - percent_can_keep)); polyline.width.back() = min_width; - changes = true; - break; + } else { + /// almost 0-length, Remove + polyline.points.erase(polyline.points.end() - 1); + polyline.width.erase(polyline.width.end() - 1); } + changes = true; + break; } polyline.points.erase(polyline.points.end() - 1); polyline.width.erase(polyline.width.end() - 1); diff --git a/xs/src/libslic3r/Point.hpp b/xs/src/libslic3r/Point.hpp index 922a55bffc..bc8ce488a8 100644 --- a/xs/src/libslic3r/Point.hpp +++ b/xs/src/libslic3r/Point.hpp @@ -47,7 +47,7 @@ class Point static Point new_scale(Pointf p); bool operator==(const Point& rhs) const; bool operator!=(const Point& rhs) const { return !(*this == rhs); } - bool operator< (const Point& rhs) const { return this->x < rhs.x || (this->x == rhs.x && this->y < rhs.y); } + bool operator<(const Point& rhs) const { return this->x < rhs.x || (this->x == rhs.x && this->y < rhs.y); } std::string wkt() const; std::string dump_perl() const; void scale(double factor); From 1c1451cfaaa9d3a237c67f643fea29927d9ccd90 Mon Sep 17 00:00:00 2001 From: supermerill Date: Tue, 4 Dec 2018 16:03:34 +0100 Subject: [PATCH 06/25] some modifs for a small bug (a hole that appeared between thin zone and perimeter) --- xs/src/libslic3r/MedialAxis.cpp | 1 - xs/src/libslic3r/PerimeterGenerator.cpp | 33 ++++++++++++++----------- 2 files changed, 18 insertions(+), 16 deletions(-) diff --git a/xs/src/libslic3r/MedialAxis.cpp b/xs/src/libslic3r/MedialAxis.cpp index e30e370521..d1d4698d65 100644 --- a/xs/src/libslic3r/MedialAxis.cpp +++ b/xs/src/libslic3r/MedialAxis.cpp @@ -1008,7 +1008,6 @@ MedialAxis::remove_too_thin_extrusion(ThickPolylines& pp) polyline.width.front() = min_width; } else { /// almost 0-length, Remove - std::cout << id << " remove the point"<<"\n"; polyline.points.erase(polyline.points.begin()); polyline.width.erase(polyline.width.begin()); } diff --git a/xs/src/libslic3r/PerimeterGenerator.cpp b/xs/src/libslic3r/PerimeterGenerator.cpp index 31ac2eaf2f..d2957871b3 100644 --- a/xs/src/libslic3r/PerimeterGenerator.cpp +++ b/xs/src/libslic3r/PerimeterGenerator.cpp @@ -102,7 +102,7 @@ PerimeterGenerator::process() // detect edge case where a curve can be split in multiple small chunks. Polygons no_thin_onion = offset(last, -(float)(ext_pwidth / 2)); if (no_thin_onion.size()>0 && offsets.size() > 3 * no_thin_onion.size()) { - //use a sightly smaller spacing to try to drastically improve the split + //use a sightly smaller offset2 spacing to try to drastically improve the split Polygons next_onion_secondTry = offset2( last, -(float)(ext_pwidth / 2 + ext_min_spacing / 2.5 - 1), @@ -117,32 +117,35 @@ 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); - Polygons no_thin_zone = offset(offsets, +ext_pwidth/2, jtSquare); + Polygons no_thin_zone = offset(offsets, ext_pwidth/2, jtSquare); // medial axis requires non-overlapping geometry Polygons thin_zones = diff(last, no_thin_zone, true); //don't use offset2_ex, because we don't want to merge the zones that have been separated. - ExPolygons expp = offset_ex(thin_zones, (float)(-min_width / 2)); + //a very little bit of overlap can be created here with other thin polygons, but it's more useful than worisome. + ExPolygons half_thins = offset_ex(thin_zones, (float)(-min_width / 2)); + //simplify them + for (ExPolygon &half_thin : half_thins) { + half_thin.remove_point_too_near(SCALED_RESOLUTION); + } //we push the bits removed and put them into what we will use as our anchor - if (expp.size() > 0) { - no_thin_zone = diff(last, to_polygons(offset_ex(expp, (float)(min_width / 2))), true); + if (half_thins.size() > 0) { + no_thin_zone = diff(last, to_polygons(offset_ex(half_thins, (float)(min_width / 2) - SCALED_EPSILON)), true); } // compute a bit of overlap to anchor thin walls inside the print. - for (ExPolygon &ex : expp) { + for (ExPolygon &half_thin : half_thins) { //growing back the polygon - //a very little bit of overlap can be created here with other thin polygons, but it's more useful than worisome. - ex.remove_point_too_near(SCALED_RESOLUTION); - ExPolygons ex_bigger = offset_ex(ex, (float)(min_width / 2)); - if (ex_bigger.size() != 1) continue; // impossible error, growing a single polygon can't create multiple or 0. + ExPolygons thin = offset_ex(half_thin, (float)(min_width / 2)); + if (thin.size() != 1) continue; // impossible error, growing a single polygon can't create multiple or 0. ExPolygons anchor = intersection_ex( - to_polygons(offset_ex(ex, (float)(ext_pwidth / 2), CLIPPER_OFFSET_SCALE, jtSquare, 3)), + to_polygons(offset_ex(half_thin, (float)(min_width / 2 + ext_pwidth / 2), CLIPPER_OFFSET_SCALE, jtSquare, 3)), no_thin_zone, true); - ExPolygons bounds = _clipper_ex(ClipperLib::ctUnion, to_polygons(ex_bigger), to_polygons(anchor), true); + ExPolygons bounds = _clipper_ex(ClipperLib::ctUnion, to_polygons(thin), to_polygons(anchor), true); for (ExPolygon &bound : bounds) { - if (!intersection_ex(ex_bigger[0], bound).empty()) { + if (!intersection_ex(thin[0], bound).empty()) { //be sure it's not too small to extrude reliably - if (ex_bigger[0].area() > min_width*(ext_pwidth + ext_pspacing2)) { + if (thin[0].area() > min_width*(ext_pwidth + ext_pspacing2)) { // the maximum thickness of our thin wall area is equal to the minimum thickness of a single loop - ex_bigger[0].medial_axis(bound, ext_pwidth + ext_pspacing2, min_width, + thin[0].medial_axis(bound, ext_pwidth + ext_pspacing2, min_width, &thin_walls, this->layer_height); } break; From 155a68bdf9e381aeddfede566f40222a6b79dd9d Mon Sep 17 00:00:00 2001 From: supermerill Date: Tue, 4 Dec 2018 16:34:03 +0100 Subject: [PATCH 07/25] change the all 'medial axis segments of a semicircumference have the same orientation' to 'all medial axis segments of a semicircumference have the same orientation (but the 2 end points)' --- t/thin.t | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/t/thin.t b/t/thin.t index e7140e1518..65ac203533 100644 --- a/t/thin.t +++ b/t/thin.t @@ -103,7 +103,7 @@ if (0) { # check whether turns are all CCW or all CW my @lines = @{$res->[0]->lines}; - my @angles = map { $lines[$_-1]->ccw($lines[$_]->b) } 1..$#lines; + my @angles = map { $lines[$_-2]->ccw($lines[$_-1]->b) } 3..$#lines; ok !!(none { $_ < 0 } @angles) || (none { $_ > 0 } @angles), 'all medial axis segments of a semicircumference have the same orientation'; } From b6edcc4deb9a1e989f6cf645ba13abeb54e502f7 Mon Sep 17 00:00:00 2001 From: supermerill Date: Tue, 4 Dec 2018 17:42:33 +0100 Subject: [PATCH 08/25] rename variable "near" as appveyor seems to not like it. correct a bug with offset parameters. --- xs/src/libslic3r/MedialAxis.cpp | 28 ++++++++++++------------- xs/src/libslic3r/PerimeterGenerator.cpp | 2 +- 2 files changed, 15 insertions(+), 15 deletions(-) diff --git a/xs/src/libslic3r/MedialAxis.cpp b/xs/src/libslic3r/MedialAxis.cpp index d1d4698d65..6c2f6c17b9 100644 --- a/xs/src/libslic3r/MedialAxis.cpp +++ b/xs/src/libslic3r/MedialAxis.cpp @@ -359,21 +359,21 @@ add_point_same_percent(ThickPolyline* pattern, ThickPolyline* to_modify) /// return 1 for an angle of 90° and 0 for an angle of 0° or 180° double get_coeff_from_angle_countour(Point &point, const ExPolygon &contour, coord_t min_dist_between_point) { - double nearestDist = point.distance_to(contour.contour.points.front()); - Point nearest = contour.contour.points.front(); + double nearest_dist = point.distance_to(contour.contour.points.front()); + Point point_nearest = contour.contour.points.front(); size_t id_nearest = 0; - double nearDist = nearestDist; - Point near = nearest; + double near_dist = nearest_dist; + Point point_near = point_nearest; size_t id_near = 0; for (size_t id_point = 1; id_point < contour.contour.points.size(); ++id_point) { - if (nearestDist > point.distance_to(contour.contour.points[id_point])) { + if (nearest_dist > point.distance_to(contour.contour.points[id_point])) { //update near id_near = id_nearest; - near = nearest; - nearDist = nearestDist; + point_near = point_nearest; + near_dist = nearest_dist; //update nearest - nearestDist = point.distance_to(contour.contour.points[id_point]); - nearest = contour.contour.points[id_point]; + nearest_dist = point.distance_to(contour.contour.points[id_point]); + point_nearest = contour.contour.points[id_point]; id_nearest = id_point; } } @@ -381,7 +381,7 @@ get_coeff_from_angle_countour(Point &point, const ExPolygon &contour, coord_t mi size_t id_before = id_nearest == 0 ? contour.contour.points.size() - 1 : id_nearest - 1; Point point_before = id_nearest == 0 ? contour.contour.points.back() : contour.contour.points[id_nearest - 1]; //Search one point far enough to be relevant - while (nearest.distance_to(point_before) < min_dist_between_point) { + while (point_nearest.distance_to(point_before) < min_dist_between_point) { point_before = id_before == 0 ? contour.contour.points.back() : contour.contour.points[id_before - 1]; id_before = id_before == 0 ? contour.contour.points.size() - 1 : id_before - 1; //don't loop @@ -394,7 +394,7 @@ get_coeff_from_angle_countour(Point &point, const ExPolygon &contour, coord_t mi size_t id_after = id_nearest == contour.contour.points.size() - 1 ? 0 : id_nearest + 1; Point point_after = id_nearest == contour.contour.points.size() - 1 ? contour.contour.points.front() : contour.contour.points[id_nearest + 1]; //Search one point far enough to be relevant - while (nearest.distance_to(point_after) < min_dist_between_point) { + while (point_nearest.distance_to(point_after) < min_dist_between_point) { point_after = id_after == contour.contour.points.size() - 1 ? contour.contour.points.front() : contour.contour.points[id_after + 1]; id_after = id_after == contour.contour.points.size() - 1 ? 0 : id_after + 1; //don't loop @@ -405,15 +405,15 @@ get_coeff_from_angle_countour(Point &point, const ExPolygon &contour, coord_t mi } } //compute angle - angle = nearest.ccw_angle(point_before, point_after); + angle = point_nearest.ccw_angle(point_before, point_after); if (angle >= PI) angle = 2 * PI - angle; // smaller angle //compute the diff from 90° angle = abs(angle - PI / 2); - if (near.coincides_with(nearest) && max(nearestDist, nearDist) + SCALED_EPSILON < nearest.distance_to(near)) { + if (point_near.coincides_with(point_nearest) && max(nearest_dist, near_dist) + SCALED_EPSILON < point_nearest.distance_to(point_near)) { //not only nearest Point point_before = id_near == 0 ? contour.contour.points.back() : contour.contour.points[id_near - 1]; Point point_after = id_near == contour.contour.points.size() - 1 ? contour.contour.points.front() : contour.contour.points[id_near + 1]; - double angle2 = min(nearest.ccw_angle(point_before, point_after), nearest.ccw_angle(point_after, point_before)); + double angle2 = min(point_nearest.ccw_angle(point_before, point_after), point_nearest.ccw_angle(point_after, point_before)); angle2 = abs(angle - PI / 2); angle = (angle + angle2) / 2; } diff --git a/xs/src/libslic3r/PerimeterGenerator.cpp b/xs/src/libslic3r/PerimeterGenerator.cpp index d2957871b3..dc4e5797b4 100644 --- a/xs/src/libslic3r/PerimeterGenerator.cpp +++ b/xs/src/libslic3r/PerimeterGenerator.cpp @@ -117,7 +117,7 @@ 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); - Polygons no_thin_zone = offset(offsets, ext_pwidth/2, jtSquare); + Polygons no_thin_zone = offset(offsets, ext_pwidth/2, CLIPPER_OFFSET_SCALE, jtSquare, 3); // medial axis requires non-overlapping geometry Polygons thin_zones = diff(last, no_thin_zone, true); //don't use offset2_ex, because we don't want to merge the zones that have been separated. From d0f5bd75e7cfcc942bbcaffb620e9cedbfed364f Mon Sep 17 00:00:00 2001 From: supermerill Date: Wed, 5 Dec 2018 15:51:38 +0100 Subject: [PATCH 09/25] one more test, reworked the thin semi-circle test. some bugfix on the algo of thin_walls. --- t/thin.t | 18 ++++++++++++++---- xs/src/libslic3r/MedialAxis.cpp | 12 ++++++------ 2 files changed, 20 insertions(+), 10 deletions(-) diff --git a/t/thin.t b/t/thin.t index 65ac203533..1597e0dec2 100644 --- a/t/thin.t +++ b/t/thin.t @@ -1,4 +1,4 @@ -use Test::More tests => 28; +use Test::More tests => 29; use strict; use warnings; @@ -102,10 +102,12 @@ if (0) { is scalar(@$res), 1, 'medial axis of a semicircumference is a single line'; # check whether turns are all CCW or all CW - my @lines = @{$res->[0]->lines}; - my @angles = map { $lines[$_-2]->ccw($lines[$_-1]->b) } 3..$#lines; + my @all_lines = @{$res->[0]->lines}; + # remove lines that are near the end. + my @lines = grep($_->a->y >= 1578184 || $_->b->y >= 1578184, @all_lines); + my @angles = map { $lines[$_-1]->ccw($lines[$_]->b) } 1..$#lines; ok !!(none { $_ < 0 } @angles) || (none { $_ > 0 } @angles), - 'all medial axis segments of a semicircumference have the same orientation'; + 'all medial axis segments of a semicircumference have the same orientation (but the 2 end points)'; } { @@ -117,6 +119,14 @@ if (0) { is scalar(@$res), 2, 'medial axis of a (bit too narrow) french cross is two lines'; ok unscale($res->[0]->length) >= (9.9) - epsilon, 'medial axis has reasonable length'; ok unscale($res->[1]->length) >= (9.9) - epsilon, 'medial axis has reasonable length'; + + my @lines1 = @{$res->[0]->lines}; + my @angles1 = map { $lines1[$_-1]->ccw($lines1[$_]->b) } 1..$#lines1; + my @lines2 = @{$res->[1]->lines}; + my @angles2 = map { $lines2[$_-1]->ccw($lines2[$_]->b) } 1..$#lines2; + my @angles = (@angles1, @angles2); + ok !!(none { $_ != 0 } @angles), + 'medial axis of a (bit too narrow) french cross is two lines has only strait lines'; } { diff --git a/xs/src/libslic3r/MedialAxis.cpp b/xs/src/libslic3r/MedialAxis.cpp index 6c2f6c17b9..6fbb809fae 100644 --- a/xs/src/libslic3r/MedialAxis.cpp +++ b/xs/src/libslic3r/MedialAxis.cpp @@ -1042,7 +1042,7 @@ MedialAxis::remove_too_thin_extrusion(ThickPolylines& pp) polyline.width.erase(polyline.width.end() - 1); changes = true; } - if (polyline.points.size() < 2) { + if (polyline.points.size() < 2 || polyline.length() < max_width) { //remove self if too small pp.erase(pp.begin() + i); --i; @@ -1066,7 +1066,6 @@ MedialAxis::concatenate_polylines_with_crossing(ThickPolylines& pp) Optimisation of the old algorithm : now we select the most "strait line" choice when we merge with an other line at a point with more than two meet. */ - bool changes = false; for (size_t i = 0; i < pp.size(); ++i) { ThickPolyline& polyline = pp[i]; if (polyline.endpoints.first && polyline.endpoints.second) continue; // optimization @@ -1076,8 +1075,11 @@ MedialAxis::concatenate_polylines_with_crossing(ThickPolylines& pp) size_t best_idx = 0; // find another polyline starting here - for (size_t j = i + 1; j < pp.size(); ++j) { + for (size_t j = 0; j < pp.size(); ++j) { + if (j == i) continue; ThickPolyline& other = pp[j]; + if (other.endpoints.first && other.endpoints.second) continue; + if (polyline.last_point().coincides_with(other.last_point())) { other.reverse(); } else if (polyline.first_point().coincides_with(other.last_point())) { @@ -1101,16 +1103,14 @@ MedialAxis::concatenate_polylines_with_crossing(ThickPolylines& pp) } } 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() + 1, best_candidate->width.end()); polyline.endpoints.second = best_candidate->endpoints.second; assert(polyline.width.size() == polyline.points.size()); - changes = true; + if (best_idx < i) i--; pp.erase(pp.begin() + best_idx); } } - if (changes) concatThickPolylines(pp); } void From 32e9d9d619afc0904938cfb6535a6719cc40d018 Mon Sep 17 00:00:00 2001 From: supermerill Date: Wed, 5 Dec 2018 19:25:35 +0100 Subject: [PATCH 10/25] bugfix polylines & try to avoid many periemter splits. --- xs/src/libslic3r/MedialAxis.cpp | 6 +- xs/src/libslic3r/PerimeterGenerator.cpp | 16 ++-- xs/src/libslic3r/Polyline.cpp | 112 +++++++++++++----------- 3 files changed, 75 insertions(+), 59 deletions(-) diff --git a/xs/src/libslic3r/MedialAxis.cpp b/xs/src/libslic3r/MedialAxis.cpp index 6fbb809fae..46c37e3be9 100644 --- a/xs/src/libslic3r/MedialAxis.cpp +++ b/xs/src/libslic3r/MedialAxis.cpp @@ -1042,7 +1042,8 @@ MedialAxis::remove_too_thin_extrusion(ThickPolylines& pp) polyline.width.erase(polyline.width.end() - 1); changes = true; } - if (polyline.points.size() < 2 || polyline.length() < max_width) { + //remove empty lines and bits that comes from a "main line" + if (polyline.points.size() < 2 || (changes && polyline.length() < max_width && polyline.points.size() ==2)) { //remove self if too small pp.erase(pp.begin() + i); --i; @@ -1288,6 +1289,9 @@ MedialAxis::build(ThickPolylines* polylines_out) this->id++; this->expolygon = simplify_polygon_frontier(); + //safety check + if (this->expolygon.area() < this->max_width * this->min_width) this->expolygon = this->surface; + if (this->expolygon.area() < this->max_width * this->min_width) return; // compute the Voronoi diagram and extract medial axis polylines diff --git a/xs/src/libslic3r/PerimeterGenerator.cpp b/xs/src/libslic3r/PerimeterGenerator.cpp index dc4e5797b4..681a10f451 100644 --- a/xs/src/libslic3r/PerimeterGenerator.cpp +++ b/xs/src/libslic3r/PerimeterGenerator.cpp @@ -101,16 +101,18 @@ PerimeterGenerator::process() // detect edge case where a curve can be split in multiple small chunks. Polygons no_thin_onion = offset(last, -(float)(ext_pwidth / 2)); - if (no_thin_onion.size()>0 && offsets.size() > 3 * no_thin_onion.size()) { - //use a sightly smaller offset2 spacing to try to drastically improve the split + float div = 2; + while (no_thin_onion.size()>0 && offsets.size() > no_thin_onion.size() && no_thin_onion.size() + offsets.size() > 3) { + div += 0.5; + //use a sightly smaller offset2 spacing to try to improve the split Polygons next_onion_secondTry = offset2( last, - -(float)(ext_pwidth / 2 + ext_min_spacing / 2.5 - 1), - +(float)(ext_min_spacing / 2.5 - 1)); - if (abs(((int32_t)offsets.size()) - ((int32_t)no_thin_onion.size())) > - 2*abs(((int32_t)next_onion_secondTry.size()) - ((int32_t)no_thin_onion.size()))) { + -(float)(ext_pwidth / 2 + ext_min_spacing / div - 1), + +(float)(ext_min_spacing / div - 1)); + if (offsets.size() > next_onion_secondTry.size()) { offsets = next_onion_secondTry; } + if (div > 3) break; } // the following offset2 ensures almost nothing in @thin_walls is narrower than $min_width @@ -143,7 +145,9 @@ PerimeterGenerator::process() for (ExPolygon &bound : bounds) { if (!intersection_ex(thin[0], bound).empty()) { //be sure it's not too small to extrude reliably + thin[0].remove_point_too_near(SCALED_RESOLUTION); if (thin[0].area() > min_width*(ext_pwidth + ext_pspacing2)) { + bound.remove_point_too_near(SCALED_RESOLUTION); // the maximum thickness of our thin wall area is equal to the minimum thickness of a single loop thin[0].medial_axis(bound, ext_pwidth + ext_pspacing2, min_width, &thin_walls, this->layer_height); diff --git a/xs/src/libslic3r/Polyline.cpp b/xs/src/libslic3r/Polyline.cpp index 0afd229514..e5d8b440fd 100644 --- a/xs/src/libslic3r/Polyline.cpp +++ b/xs/src/libslic3r/Polyline.cpp @@ -260,6 +260,11 @@ concatThickPolylines(ThickPolylines& pp) { //concat polyline if only 2 polyline at a point for (size_t i = 0; i < pp.size(); ++i) { ThickPolyline *polyline = &pp[i]; + if (polyline->first_point().coincides_with(polyline->last_point())) { + polyline->endpoints.first = false; + polyline->endpoints.second = false; + continue; + } size_t id_candidate_first_point = -1; size_t id_candidate_last_point = -1; @@ -269,77 +274,80 @@ concatThickPolylines(ThickPolylines& pp) { for (size_t j = 0; j < pp.size(); ++j) { if (j == i) continue; ThickPolyline *other = &pp[j]; + if (other->first_point().coincides_with(other->last_point())) continue; if (polyline->last_point().coincides_with(other->last_point())) { - other->reverse(); + //other->reverse(); + id_candidate_last_point = j; + nbCandidate_last_point++; + } + if (polyline->last_point().coincides_with(other->first_point())) { id_candidate_last_point = j; nbCandidate_last_point++; - } else if (polyline->first_point().coincides_with(other->last_point())) { + } + if (polyline->first_point().coincides_with(other->last_point())) { id_candidate_first_point = j; nbCandidate_first_point++; - } else if (polyline->first_point().coincides_with(other->first_point())) { + } + if (polyline->first_point().coincides_with(other->first_point())) { id_candidate_first_point = j; nbCandidate_first_point++; - other->reverse(); - } else if (polyline->last_point().coincides_with(other->first_point())) { - id_candidate_last_point = j; - nbCandidate_last_point++; - } else { - continue; + //other->reverse(); } } if (id_candidate_last_point == id_candidate_first_point && nbCandidate_first_point == 1 && nbCandidate_last_point == 1) { + if (polyline->first_point().coincides_with(pp[id_candidate_first_point].first_point())) pp[id_candidate_first_point].reverse(); // it's a trap! it's a loop! - if (pp[id_candidate_first_point].points.size() > 2) { - polyline->points.insert(polyline->points.begin(), pp[id_candidate_first_point].points.begin() + 1, pp[id_candidate_first_point].points.end() - 1); - polyline->width.insert(polyline->width.begin(), pp[id_candidate_first_point].width.begin() + 1, pp[id_candidate_first_point].width.end() - 1); - } + polyline->points.insert(polyline->points.end(), pp[id_candidate_first_point].points.begin() + 1, pp[id_candidate_first_point].points.end()); + polyline->width.insert(polyline->width.end(), pp[id_candidate_first_point].width.begin() + 1, pp[id_candidate_first_point].width.end()); pp.erase(pp.begin() + id_candidate_first_point); changes = true; polyline->endpoints.first = false; polyline->endpoints.second = false; - continue; - } + } else { - if (nbCandidate_first_point == 1) { - //concat at front - polyline->width[0] = std::max(polyline->width.front(), pp[id_candidate_first_point].width.back()); - polyline->points.insert(polyline->points.begin(), pp[id_candidate_first_point].points.begin(), pp[id_candidate_first_point].points.end() - 1); - polyline->width.insert(polyline->width.begin(), pp[id_candidate_first_point].width.begin(), pp[id_candidate_first_point].width.end() - 1); - polyline->endpoints.first = pp[id_candidate_first_point].endpoints.first; - pp.erase(pp.begin() + id_candidate_first_point); - changes = true; - if (id_candidate_first_point < i) { - i--; - polyline = &pp[i]; + if (nbCandidate_first_point == 1) { + if (polyline->first_point().coincides_with(pp[id_candidate_first_point].first_point())) pp[id_candidate_first_point].reverse(); + //concat at front + polyline->width[0] = std::max(polyline->width.front(), pp[id_candidate_first_point].width.back()); + polyline->points.insert(polyline->points.begin(), pp[id_candidate_first_point].points.begin(), pp[id_candidate_first_point].points.end() - 1); + polyline->width.insert(polyline->width.begin(), pp[id_candidate_first_point].width.begin(), pp[id_candidate_first_point].width.end() - 1); + polyline->endpoints.first = pp[id_candidate_first_point].endpoints.first; + pp.erase(pp.begin() + id_candidate_first_point); + changes = true; + if (id_candidate_first_point < i) { + i--; + polyline = &pp[i]; + } + if (id_candidate_last_point > id_candidate_first_point) { + id_candidate_last_point--; + } + } else if (nbCandidate_first_point == 0) { + //update endpoint + polyline->endpoints.first = true; } - if (id_candidate_last_point > id_candidate_first_point) { - id_candidate_last_point--; + if (nbCandidate_last_point == 1) { + if (polyline->last_point().coincides_with(pp[id_candidate_last_point].last_point())) pp[id_candidate_last_point].reverse(); + //concat at back + polyline->width[polyline->width.size() - 1] = std::max(polyline->width.back(), pp[id_candidate_last_point].width.front()); + polyline->points.insert(polyline->points.end(), pp[id_candidate_last_point].points.begin() + 1, pp[id_candidate_last_point].points.end()); + polyline->width.insert(polyline->width.end(), pp[id_candidate_last_point].width.begin() + 1, pp[id_candidate_last_point].width.end()); + polyline->endpoints.second = pp[id_candidate_last_point].endpoints.second; + pp.erase(pp.begin() + id_candidate_last_point); + changes = true; + if (id_candidate_last_point < i) { + i--; + polyline = &pp[i]; + } + } else if (nbCandidate_last_point == 0) { + //update endpoint + polyline->endpoints.second = true; } - } else if (nbCandidate_first_point == 0 && !polyline->endpoints.first && !polyline->first_point().coincides_with(polyline->last_point())) { - //update endpoint - polyline->endpoints.first = true; - } - if (nbCandidate_last_point == 1) { - //concat at back - polyline->width[polyline->width.size() - 1] = std::max(polyline->width.back(), pp[id_candidate_last_point].width.front()); - polyline->points.insert(polyline->points.end(), pp[id_candidate_last_point].points.begin() + 1, pp[id_candidate_last_point].points.end()); - polyline->width.insert(polyline->width.end(), pp[id_candidate_last_point].width.begin() + 1, pp[id_candidate_last_point].width.end()); - polyline->endpoints.second = pp[id_candidate_last_point].endpoints.second; - pp.erase(pp.begin() + id_candidate_last_point); - changes = true; - if (id_candidate_last_point < i) { - i--; - polyline = &pp[i]; - } - } else if (nbCandidate_last_point == 0 && !polyline->endpoints.second && !polyline->first_point().coincides_with(polyline->last_point())) { - //update endpoint - polyline->endpoints.second = true; - } - if (polyline->last_point().coincides_with(polyline->first_point())) { - //the concat has created a loop : update endpoints - polyline->endpoints.first = false; - polyline->endpoints.second = false; + if (polyline->last_point().coincides_with(polyline->first_point())) { + //the concat has created a loop : update endpoints + polyline->endpoints.first = false; + polyline->endpoints.second = false; + } } } } From 90df3a5dd16154929b1cdc53d54e9c18acefefb1 Mon Sep 17 00:00:00 2001 From: supermerill Date: Thu, 6 Dec 2018 14:00:25 +0100 Subject: [PATCH 11/25] debug test, relax min area for medial_axis --- t/thin.t | 2 ++ xs/src/libslic3r/MedialAxis.cpp | 4 ++-- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/t/thin.t b/t/thin.t index 1597e0dec2..3bacbdfe06 100644 --- a/t/thin.t +++ b/t/thin.t @@ -115,6 +115,7 @@ if (0) { [4.3, 4], [4.3, 0], [4,0], [4,4], [0,4], [0,4.5], [4,4.5], [4,10], [4.3,10], [4.3, 4.5], [6, 4.5], [6,10], [6.2,10], [6.2,4.5], [10,4.5], [10,4], [6.2,4], [6.2,0], [6, 0], [6, 4], )); + $expolygon->contour->make_counter_clockwise(); my $res = $expolygon->medial_axis(scale 0.55, scale 0.25); is scalar(@$res), 2, 'medial axis of a (bit too narrow) french cross is two lines'; ok unscale($res->[0]->length) >= (9.9) - epsilon, 'medial axis has reasonable length'; @@ -133,6 +134,7 @@ if (0) { my $expolygon = Slic3r::ExPolygon->new(Slic3r::Polygon->new_scale( [0.86526705,1.4509841], [0.57696039,1.8637021], [0.4502297,2.5569978], [0.45626199,3.2965596], [1.1218851,3.3049455], [0.96681072,2.8243202], [0.86328971,2.2056997], [0.85367905,1.7790778], )); + $expolygon->contour->make_counter_clockwise(); my $res = $expolygon->medial_axis(scale 1, scale 0.25); is scalar(@$res), 1, 'medial axis of a (bit too narrow) french cross is two lines'; ok unscale($res->[0]->length) >= (1.4) - epsilon, 'medial axis has reasonable length'; diff --git a/xs/src/libslic3r/MedialAxis.cpp b/xs/src/libslic3r/MedialAxis.cpp index 46c37e3be9..3c379ee0e4 100644 --- a/xs/src/libslic3r/MedialAxis.cpp +++ b/xs/src/libslic3r/MedialAxis.cpp @@ -1290,8 +1290,8 @@ MedialAxis::build(ThickPolylines* polylines_out) this->expolygon = simplify_polygon_frontier(); //safety check - if (this->expolygon.area() < this->max_width * this->min_width) this->expolygon = this->surface; - if (this->expolygon.area() < this->max_width * this->min_width) return; + if (this->expolygon.area() < this->min_width * this->min_width) this->expolygon = this->surface; + if (this->expolygon.area() < this->min_width * this->min_width) return; // compute the Voronoi diagram and extract medial axis polylines From ca9f3f7e29662c25a0bd5c3ed00232ea8ed68d2a Mon Sep 17 00:00:00 2001 From: supermerill Date: Thu, 6 Dec 2018 14:12:27 +0100 Subject: [PATCH 12/25] edge-case bugfix (a perimeter inside a thin_wall area) --- xs/src/libslic3r/MedialAxis.cpp | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/xs/src/libslic3r/MedialAxis.cpp b/xs/src/libslic3r/MedialAxis.cpp index 3c379ee0e4..ba3e5fdfdd 100644 --- a/xs/src/libslic3r/MedialAxis.cpp +++ b/xs/src/libslic3r/MedialAxis.cpp @@ -367,7 +367,7 @@ get_coeff_from_angle_countour(Point &point, const ExPolygon &contour, coord_t mi size_t id_near = 0; for (size_t id_point = 1; id_point < contour.contour.points.size(); ++id_point) { if (nearest_dist > point.distance_to(contour.contour.points[id_point])) { - //update near + //update point_near id_near = id_nearest; point_near = point_nearest; near_dist = nearest_dist; @@ -627,17 +627,30 @@ MedialAxis::extends_line(ThickPolyline& polyline, const ExPolygons& anchors, con if (this->expolygon.contour.has_boundary_point(polyline.points.back())) { new_back = polyline.points.back(); } else { + //TODO: verify also for holes. (void)this->expolygon.contour.first_intersection(line, &new_back); // safety check if no intersection if (new_back.x == 0 && new_back.y == 0) new_back = line.b; + polyline.points.push_back(new_back); polyline.width.push_back(polyline.width.back()); } Point new_bound; + //TODO: verify also for holes. (void)bounds.contour.first_intersection(line, &new_bound); // safety check if no intersection if (new_bound.x == 0 && new_bound.y == 0) { if (line.b.coincides_with_epsilon(polyline.points.back())) return; + //check if we don't over-shoot inside us + bool is_in_anchor = false; + for (const ExPolygon& a : anchors) { + if (a.contains(line.b)) { + is_in_anchor = true; + break; + } + } + if (!is_in_anchor) std::cout << "not in anchor:\n"; + if (!is_in_anchor) return; new_bound = line.b; } // find anchor @@ -1298,6 +1311,7 @@ MedialAxis::build(ThickPolylines* polylines_out) ThickPolylines pp; this->polyline_from_voronoi(this->expolygon.lines(), &pp); + concatThickPolylines(pp); //{ // stringstream stri; @@ -1327,8 +1341,6 @@ MedialAxis::build(ThickPolylines* polylines_out) // svg.Close(); //} - concatThickPolylines(pp); - // 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. From 6643d7876930e7c6ec7df71bfc7c2b5f97a2a047 Mon Sep 17 00:00:00 2001 From: supermerill Date: Fri, 7 Dec 2018 13:25:23 +0100 Subject: [PATCH 13/25] stop_at_min_width : do not extends the thin wall if it's over the min_width (can be toggled when calling expolygon.medial_axis). --- xs/src/libslic3r/ExPolygon.cpp | 9 +++------ xs/src/libslic3r/ExPolygon.hpp | 2 +- xs/src/libslic3r/MedialAxis.cpp | 28 +++++++++++++++++++++------- xs/src/libslic3r/MedialAxis.hpp | 1 + 4 files changed, 26 insertions(+), 14 deletions(-) diff --git a/xs/src/libslic3r/ExPolygon.cpp b/xs/src/libslic3r/ExPolygon.cpp index 87c119a04f..bfc3d5077a 100644 --- a/xs/src/libslic3r/ExPolygon.cpp +++ b/xs/src/libslic3r/ExPolygon.cpp @@ -210,12 +210,9 @@ ExPolygon::remove_point_too_near(const coord_t tolerance) { } void -ExPolygon::medial_axis(const ExPolygon &bounds, double max_width, double min_width, ThickPolylines* polylines, double height) const { - ExPolygon simplifiedBounds = bounds; - simplifiedBounds.remove_point_too_near(SCALED_RESOLUTION); - ExPolygon simplifiedPolygon = *this; - simplifiedPolygon.remove_point_too_near(SCALED_RESOLUTION); - Slic3r::MedialAxis ma(simplifiedPolygon, simplifiedBounds, max_width, min_width, height); +ExPolygon::medial_axis(const ExPolygon &bounds, double max_width, double min_width, ThickPolylines* polylines, double height, bool stop_at_min_width) const { + Slic3r::MedialAxis ma(*this, bounds, max_width, min_width, height); + ma.stop_at_min_width = stop_at_min_width; ma.build(polylines); } diff --git a/xs/src/libslic3r/ExPolygon.hpp b/xs/src/libslic3r/ExPolygon.hpp index 92610cc567..cd3ab9ea7e 100644 --- a/xs/src/libslic3r/ExPolygon.hpp +++ b/xs/src/libslic3r/ExPolygon.hpp @@ -43,7 +43,7 @@ class ExPolygon ExPolygons simplify(double tolerance) const; void simplify(double tolerance, ExPolygons* expolygons) const; void remove_point_too_near(const coord_t tolerance); - void medial_axis(const ExPolygon &bounds, double max_width, double min_width, ThickPolylines* polylines, double height) const; + void medial_axis(const ExPolygon &bounds, double max_width, double min_width, ThickPolylines* polylines, double height, bool stop_at_min_width = true) 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; diff --git a/xs/src/libslic3r/MedialAxis.cpp b/xs/src/libslic3r/MedialAxis.cpp index ba3e5fdfdd..2cd602db1f 100644 --- a/xs/src/libslic3r/MedialAxis.cpp +++ b/xs/src/libslic3r/MedialAxis.cpp @@ -1360,6 +1360,18 @@ MedialAxis::build(ThickPolylines* polylines_out) //fusion right-angle corners. fusion_corners(pp); + // Loop through all returned polylines in order to extend their endpoints to the + // expolygon boundaries (if done here, it may be cut later if not thick enough) + if (stop_at_min_width) { + const ExPolygons anchors = offset2_ex(to_polygons(diff_ex(this->bounds, this->expolygon)), -SCALED_RESOLUTION, SCALED_RESOLUTION); + for (size_t i = 0; i < pp.size(); ++i) { + ThickPolyline& polyline = pp[i]; + extends_line(polyline, anchors, min_width); + polyline.reverse(); + extends_line(polyline, anchors, min_width); + } + } + //reduce extrusion when it's too thin to be printable remove_too_thin_extrusion(pp); //{ @@ -1385,13 +1397,15 @@ MedialAxis::build(ThickPolylines* polylines_out) // Loop through all returned polylines in order to extend their endpoints to the // expolygon boundaries - const ExPolygons anchors = offset2_ex(to_polygons(diff_ex(this->bounds, this->expolygon)), -SCALED_RESOLUTION, SCALED_RESOLUTION); - for (size_t i = 0; i < pp.size(); ++i) { - ThickPolyline& polyline = pp[i]; - extends_line(polyline, anchors, min_width); - polyline.reverse(); - extends_line(polyline, anchors, min_width); - } + if (!stop_at_min_width) { + const ExPolygons anchors = offset2_ex(to_polygons(diff_ex(this->bounds, this->expolygon)), -SCALED_RESOLUTION, SCALED_RESOLUTION); + for (size_t i = 0; i < pp.size(); ++i) { + ThickPolyline& polyline = pp[i]; + extends_line(polyline, anchors, min_width); + polyline.reverse(); + extends_line(polyline, anchors, min_width); + } + } //{ // stringstream stri; // stri << "medial_axis_5_expand_" << id << ".svg"; diff --git a/xs/src/libslic3r/MedialAxis.hpp b/xs/src/libslic3r/MedialAxis.hpp index 93f6725db2..3ec9d467d2 100644 --- a/xs/src/libslic3r/MedialAxis.hpp +++ b/xs/src/libslic3r/MedialAxis.hpp @@ -23,6 +23,7 @@ namespace Slic3r { const double max_width; const double min_width; const double height; + bool stop_at_min_width = true; MedialAxis(const ExPolygon &_expolygon, const ExPolygon &_bounds, const double _max_width, const double _min_width, const double _height) : surface(_expolygon), bounds(_bounds), max_width(_max_width), min_width(_min_width), height(_height) { }; From 137c99cbe173d28b3d6e0c83f68ace512ea612c8 Mon Sep 17 00:00:00 2001 From: supermerill Date: Mon, 10 Dec 2018 14:40:36 +0100 Subject: [PATCH 14/25] bugfix, remove \t, #4640 --- xs/src/libslic3r/ExPolygon.cpp | 2 +- xs/src/libslic3r/MedialAxis.cpp | 60 ++++++++++++++++--------- xs/src/libslic3r/MedialAxis.hpp | 2 +- xs/src/libslic3r/PerimeterGenerator.cpp | 8 +++- 4 files changed, 48 insertions(+), 24 deletions(-) diff --git a/xs/src/libslic3r/ExPolygon.cpp b/xs/src/libslic3r/ExPolygon.cpp index bfc3d5077a..d1cc295611 100644 --- a/xs/src/libslic3r/ExPolygon.cpp +++ b/xs/src/libslic3r/ExPolygon.cpp @@ -212,7 +212,7 @@ ExPolygon::remove_point_too_near(const coord_t tolerance) { void ExPolygon::medial_axis(const ExPolygon &bounds, double max_width, double min_width, ThickPolylines* polylines, double height, bool stop_at_min_width) const { Slic3r::MedialAxis ma(*this, bounds, max_width, min_width, height); - ma.stop_at_min_width = stop_at_min_width; + ma.stop_at_min_width = stop_at_min_width; ma.build(polylines); } diff --git a/xs/src/libslic3r/MedialAxis.cpp b/xs/src/libslic3r/MedialAxis.cpp index 2cd602db1f..f5de2b49df 100644 --- a/xs/src/libslic3r/MedialAxis.cpp +++ b/xs/src/libslic3r/MedialAxis.cpp @@ -301,13 +301,14 @@ remove_point_too_near(ThickPolyline* to_reduce) to_reduce->points.erase(to_reduce->points.begin() + id); to_reduce->width.erase(to_reduce->width.begin() + id); newdist = to_reduce->points[id].distance_to(to_reduce->points[id - 1]); + //if you removed a point, it check if the next one isn't too near from the previous one. + // if not, it bypass it. + if (newdist > smallest) { + ++id; + } } //go to next one - //if you removed a point, it check if the next one isn't too near from the previous one. - // if not, it byepass it. - if (newdist > smallest) { - ++id; - } + else ++id; } } @@ -620,7 +621,7 @@ MedialAxis::extends_line(ThickPolyline& polyline, const ExPolygons& anchors, con line.a = *(polyline.points.begin() + first_idx); } // prevent the line from touching on the other side, otherwise intersection() might return that solution - if (polyline.points.size() == 2) line.a = line.midpoint(); + if (polyline.points.size() == 2 && this->expolygon.contains(line.midpoint())) line.a = line.midpoint(); line.extend_end(max_width); Point new_back; @@ -628,18 +629,37 @@ MedialAxis::extends_line(ThickPolyline& polyline, const ExPolygons& anchors, con new_back = polyline.points.back(); } else { //TODO: verify also for holes. - (void)this->expolygon.contour.first_intersection(line, &new_back); + bool finded = this->expolygon.contour.first_intersection(line, &new_back); + //verify also for holes. + Point new_back_temp; + for (Polygon hole : this->expolygon.holes) { + if (hole.first_intersection(line, &new_back_temp)) { + if (!finded || line.a.distance_to(new_back_temp) < line.a.distance_to(new_back)) { + finded = true; + new_back = new_back_temp; + } + } + } // safety check if no intersection - if (new_back.x == 0 && new_back.y == 0) new_back = line.b; + if (!finded) new_back = line.b; polyline.points.push_back(new_back); polyline.width.push_back(polyline.width.back()); } Point new_bound; - //TODO: verify also for holes. - (void)bounds.contour.first_intersection(line, &new_bound); + bool finded = bounds.contour.first_intersection(line, &new_bound); + //verify also for holes. + Point new_bound_temp; + for (Polygon hole : bounds.holes) { + if (hole.first_intersection(line, &new_bound_temp)) { + if (!finded || line.a.distance_to(new_bound_temp) < line.a.distance_to(new_bound)) { + finded = true; + new_bound = new_bound_temp; + } + } + } // safety check if no intersection - if (new_bound.x == 0 && new_bound.y == 0) { + if (!finded) { if (line.b.coincides_with_epsilon(polyline.points.back())) return; //check if we don't over-shoot inside us bool is_in_anchor = false; @@ -1363,7 +1383,7 @@ MedialAxis::build(ThickPolylines* polylines_out) // Loop through all returned polylines in order to extend their endpoints to the // expolygon boundaries (if done here, it may be cut later if not thick enough) if (stop_at_min_width) { - const ExPolygons anchors = offset2_ex(to_polygons(diff_ex(this->bounds, this->expolygon)), -SCALED_RESOLUTION, SCALED_RESOLUTION); + const ExPolygons anchors = offset2_ex(to_polygons(diff_ex(this->bounds, this->expolygon)), -SCALED_RESOLUTION, SCALED_RESOLUTION); for (size_t i = 0; i < pp.size(); ++i) { ThickPolyline& polyline = pp[i]; extends_line(polyline, anchors, min_width); @@ -1398,14 +1418,14 @@ MedialAxis::build(ThickPolylines* polylines_out) // Loop through all returned polylines in order to extend their endpoints to the // expolygon boundaries if (!stop_at_min_width) { - const ExPolygons anchors = offset2_ex(to_polygons(diff_ex(this->bounds, this->expolygon)), -SCALED_RESOLUTION, SCALED_RESOLUTION); - for (size_t i = 0; i < pp.size(); ++i) { - ThickPolyline& polyline = pp[i]; - extends_line(polyline, anchors, min_width); - polyline.reverse(); - extends_line(polyline, anchors, min_width); - } - } + const ExPolygons anchors = offset2_ex(to_polygons(diff_ex(this->bounds, this->expolygon)), -SCALED_RESOLUTION, SCALED_RESOLUTION); + for (size_t i = 0; i < pp.size(); ++i) { + ThickPolyline& polyline = pp[i]; + extends_line(polyline, anchors, min_width); + polyline.reverse(); + extends_line(polyline, anchors, min_width); + } + } //{ // stringstream stri; // stri << "medial_axis_5_expand_" << id << ".svg"; diff --git a/xs/src/libslic3r/MedialAxis.hpp b/xs/src/libslic3r/MedialAxis.hpp index 3ec9d467d2..25a2c15f78 100644 --- a/xs/src/libslic3r/MedialAxis.hpp +++ b/xs/src/libslic3r/MedialAxis.hpp @@ -17,7 +17,7 @@ namespace Slic3r { public: Lines lines; //lines is here only to avoid appassing it in argument of amny method. Initialized in polyline_from_voronoi. ExPolygon expolygon; - + const ExPolygon& surface; const ExPolygon& bounds; const double max_width; diff --git a/xs/src/libslic3r/PerimeterGenerator.cpp b/xs/src/libslic3r/PerimeterGenerator.cpp index 681a10f451..5c82bc81a9 100644 --- a/xs/src/libslic3r/PerimeterGenerator.cpp +++ b/xs/src/libslic3r/PerimeterGenerator.cpp @@ -133,6 +133,7 @@ PerimeterGenerator::process() if (half_thins.size() > 0) { no_thin_zone = diff(last, to_polygons(offset_ex(half_thins, (float)(min_width / 2) - SCALED_EPSILON)), true); } + ExPolygons thin_zones_extruded; // compute a bit of overlap to anchor thin walls inside the print. for (ExPolygon &half_thin : half_thins) { //growing back the polygon @@ -145,17 +146,20 @@ PerimeterGenerator::process() for (ExPolygon &bound : bounds) { if (!intersection_ex(thin[0], bound).empty()) { //be sure it's not too small to extrude reliably - thin[0].remove_point_too_near(SCALED_RESOLUTION); + thin[0].remove_point_too_near(SCALED_RESOLUTION); if (thin[0].area() > min_width*(ext_pwidth + ext_pspacing2)) { - bound.remove_point_too_near(SCALED_RESOLUTION); + bound.remove_point_too_near(SCALED_RESOLUTION); // the maximum thickness of our thin wall area is equal to the minimum thickness of a single loop thin[0].medial_axis(bound, ext_pwidth + ext_pspacing2, min_width, &thin_walls, this->layer_height); + thin_zones_extruded.emplace_back(thin[0]); } break; } } } + // recompute the next onion, to be sure to not miss any small areas that can't be extruded by thin_walls + offsets = to_polygons(diff_ex(offset_ex(last, -(float)(ext_pwidth / 2)), thin_zones_extruded, true)); #ifdef DEBUG printf(" %zu thin walls detected\n", thin_walls.size()); #endif From 4534c5eeeb849f5b46b21816be54e802008559f2 Mon Sep 17 00:00:00 2001 From: supermerill Date: Tue, 11 Dec 2018 18:11:06 +0100 Subject: [PATCH 15/25] Review corrections --- xs/src/libslic3r/MedialAxis.cpp | 41 +++++++++---------------- xs/src/libslic3r/MedialAxis.hpp | 2 +- xs/src/libslic3r/MultiPoint.hpp | 6 ++-- xs/src/libslic3r/PerimeterGenerator.cpp | 2 +- xs/src/libslic3r/Polyline.hpp | 2 +- 5 files changed, 21 insertions(+), 32 deletions(-) diff --git a/xs/src/libslic3r/MedialAxis.cpp b/xs/src/libslic3r/MedialAxis.cpp index f5de2b49df..67aa03d62e 100644 --- a/xs/src/libslic3r/MedialAxis.cpp +++ b/xs/src/libslic3r/MedialAxis.cpp @@ -708,19 +708,19 @@ MedialAxis::extends_line(ThickPolyline& polyline, const ExPolygons& anchors, con void MedialAxis::main_fusion(ThickPolylines& pp) { - //int idf = 0; - //reoder pp by length (ascending) It's really important to do that to avoid building the line from the width insteand of the length - std::sort(pp.begin(), pp.end(), [](const ThickPolyline & a, const ThickPolyline & b) { - bool ahas0 = a.width.front() == 0 || a.width.back() == 0; - bool bhas0 = b.width.front() == 0 || b.width.back() == 0; - if (ahas0 && !bhas0) return true; - if (!ahas0 && bhas0) return false; - return a.length() < b.length(); - }); bool changes = true; map coeff_angle_cache; while (changes) { + concatThickPolylines(pp); + //reoder pp by length (ascending) It's really important to do that to avoid building the line from the width insteand of the length + std::sort(pp.begin(), pp.end(), [](const ThickPolyline & a, const ThickPolyline & b) { + bool ahas0 = a.width.front() == 0 || a.width.back() == 0; + bool bhas0 = b.width.front() == 0 || b.width.back() == 0; + if (ahas0 && !bhas0) return true; + if (!ahas0 && bhas0) return false; + return a.length() < b.length(); + }); changes = false; for (size_t i = 0; i < pp.size(); ++i) { ThickPolyline& polyline = pp[i]; @@ -949,12 +949,12 @@ MedialAxis::main_fusion(ThickPolylines& pp) //Add last point polyline.points.push_back(best_candidate->points[idx_point]); polyline.width.push_back(best_candidate->width[idx_point]); - //select if an end opccur + //select if an end occur polyline.endpoints.second &= best_candidate->endpoints.second; } } else { - //select if an end opccur + //select if an end occur polyline.endpoints.second &= best_candidate->endpoints.second; } @@ -1005,18 +1005,8 @@ MedialAxis::main_fusion(ThickPolylines& pp) break; } } - if (changes) { - concatThickPolylines(pp); - ///reorder, in case of change - std::sort(pp.begin(), pp.end(), [](const ThickPolyline & a, const ThickPolyline & b) { - bool ahas0 = a.width.front() == 0 || a.width.back() == 0; - bool bhas0 = b.width.front() == 0 || b.width.back() == 0; - if (ahas0 && !bhas0) return true; - if (!ahas0 && bhas0) return false; - return a.length() < b.length(); - }); - } } + if (changes) concatThickPolylines(pp); } void @@ -1226,7 +1216,7 @@ MedialAxis::remove_too_short_polylines(ThickPolylines& pp, const coord_t min_siz } } - if (shortest_idx >= 0 && shortest_idx < pp.size()) { + if (shortest_idx < pp.size()) { pp.erase(pp.begin() + shortest_idx); changes = true; } @@ -1265,7 +1255,7 @@ MedialAxis::ensure_not_overextrude(ThickPolylines& pp) //reduce width double reduce_by = boundsVolume / volume; for (ThickPolyline& polyline : pp) { - for (ThickLine l : polyline.thicklines()) { + for (ThickLine &l : polyline.thicklines()) { l.a_width *= reduce_by; l.b_width *= reduce_by; } @@ -1278,8 +1268,7 @@ MedialAxis::simplify_polygon_frontier() { //simplify the boundary between us and the bounds. - //int firstIdx = 0; - //while (firstIdx < contour.points.size() && bounds.contour.contains(contour.points[firstIdx])) firstIdx++; + //it will remove every point in the surface contour that aren't on the bounds contour ExPolygon simplified_poly = this->surface; if (&this->surface != &this->bounds) { bool need_intersect = false; diff --git a/xs/src/libslic3r/MedialAxis.hpp b/xs/src/libslic3r/MedialAxis.hpp index 25a2c15f78..f8319ffcab 100644 --- a/xs/src/libslic3r/MedialAxis.hpp +++ b/xs/src/libslic3r/MedialAxis.hpp @@ -15,7 +15,7 @@ namespace Slic3r { class MedialAxis { public: - Lines lines; //lines is here only to avoid appassing it in argument of amny method. Initialized in polyline_from_voronoi. + Lines lines; //lines is here only to avoid passing it in argument of many methods. Initialized in polyline_from_voronoi. ExPolygon expolygon; const ExPolygon& surface; diff --git a/xs/src/libslic3r/MultiPoint.hpp b/xs/src/libslic3r/MultiPoint.hpp index 791d440f93..07abe553ac 100644 --- a/xs/src/libslic3r/MultiPoint.hpp +++ b/xs/src/libslic3r/MultiPoint.hpp @@ -31,9 +31,9 @@ class MultiPoint int find_point(const Point &point) const; bool has_boundary_point(const Point &point) const; - /// return the index of the closest point in this polygon in relation with "point" - /// \param point the point to compare with. - /// \return the index of the closest point in the points vector. + /// return the index of the closest point in this polygon in relation with "point" + /// \param point the point to compare with. + /// \return the index of the closest point in the points vector, or max(size_t) if points is empty. size_t closest_point_index(const Point &point) const { size_t idx = -1; if (! this->points.empty()) { diff --git a/xs/src/libslic3r/PerimeterGenerator.cpp b/xs/src/libslic3r/PerimeterGenerator.cpp index 5c82bc81a9..51f152ebb7 100644 --- a/xs/src/libslic3r/PerimeterGenerator.cpp +++ b/xs/src/libslic3r/PerimeterGenerator.cpp @@ -138,7 +138,7 @@ PerimeterGenerator::process() for (ExPolygon &half_thin : half_thins) { //growing back the polygon ExPolygons thin = offset_ex(half_thin, (float)(min_width / 2)); - if (thin.size() != 1) continue; // impossible error, growing a single polygon can't create multiple or 0. + assert(thin.size() == 1); ExPolygons anchor = intersection_ex( to_polygons(offset_ex(half_thin, (float)(min_width / 2 + ext_pwidth / 2), CLIPPER_OFFSET_SCALE, jtSquare, 3)), no_thin_zone, true); diff --git a/xs/src/libslic3r/Polyline.hpp b/xs/src/libslic3r/Polyline.hpp index c17c06ba91..db4111db3b 100644 --- a/xs/src/libslic3r/Polyline.hpp +++ b/xs/src/libslic3r/Polyline.hpp @@ -38,7 +38,7 @@ class Polyline : public MultiPoint { /// ThickPolyline : a polyline with a width for each point -/// This calss has a vector of coordf_t, it must be the same size than points. +/// This class has a vector of coordf_t, it must be the same size as points. /// it's used to store the size of the line at this point. /// Also, the endpoint let us know if the front() and back() of the polyline /// join something or is a dead-end. From 767fc8622b79df52e217550c9bfaf24b04467eb3 Mon Sep 17 00:00:00 2001 From: supermerill Date: Mon, 17 Dec 2018 12:38:03 +0100 Subject: [PATCH 16/25] Medial axis: avoid duplication + bugfix --- xs/src/libslic3r/MedialAxis.cpp | 31 ++++++++++++++----------------- xs/src/libslic3r/MedialAxis.hpp | 1 + 2 files changed, 15 insertions(+), 17 deletions(-) diff --git a/xs/src/libslic3r/MedialAxis.cpp b/xs/src/libslic3r/MedialAxis.cpp index 67aa03d62e..073e14d253 100644 --- a/xs/src/libslic3r/MedialAxis.cpp +++ b/xs/src/libslic3r/MedialAxis.cpp @@ -605,6 +605,16 @@ MedialAxis::fusion_corners(ThickPolylines &pp) } } +void +MedialAxis::extends_line_both_side(ThickPolylines& pp) { + const ExPolygons anchors = offset2_ex(diff_ex(this->bounds, this->expolygon), -SCALED_RESOLUTION, SCALED_RESOLUTION); + for (size_t i = 0; i < pp.size(); ++i) { + ThickPolyline& polyline = pp[i]; + this->extends_line(polyline, anchors, this->min_width); + polyline.reverse(); + this->extends_line(polyline, anchors, this->min_width); + } +} void MedialAxis::extends_line(ThickPolyline& polyline, const ExPolygons& anchors, const coord_t join_width) @@ -1246,7 +1256,7 @@ MedialAxis::ensure_not_overextrude(ThickPolylines& pp) double perimeterRoundGap = bounds.contour.length() * height * (1 - 0.25*PI) * 0.5; // add holes "perimeter gaps" double holesGaps = 0; - for (auto hole = bounds.holes.begin(); hole != bounds.holes.end(); ++hole) { + for (const Polygon &hole : bounds.holes) { holesGaps += hole->length() * height * (1 - 0.25*PI) * 0.5; } boundsVolume += perimeterRoundGap + holesGaps; @@ -1300,8 +1310,7 @@ MedialAxis::simplify_polygon_frontier() } } } - - simplified_poly.remove_point_too_near(SCALED_RESOLUTION); + if (!simplified_poly.contour.points.empty()) simplified_poly.remove_point_too_near(SCALED_RESOLUTION); return simplified_poly; } @@ -1372,13 +1381,7 @@ MedialAxis::build(ThickPolylines* polylines_out) // Loop through all returned polylines in order to extend their endpoints to the // expolygon boundaries (if done here, it may be cut later if not thick enough) if (stop_at_min_width) { - const ExPolygons anchors = offset2_ex(to_polygons(diff_ex(this->bounds, this->expolygon)), -SCALED_RESOLUTION, SCALED_RESOLUTION); - for (size_t i = 0; i < pp.size(); ++i) { - ThickPolyline& polyline = pp[i]; - extends_line(polyline, anchors, min_width); - polyline.reverse(); - extends_line(polyline, anchors, min_width); - } + extends_line_both_side(pp); } //reduce extrusion when it's too thin to be printable @@ -1407,13 +1410,7 @@ MedialAxis::build(ThickPolylines* polylines_out) // Loop through all returned polylines in order to extend their endpoints to the // expolygon boundaries if (!stop_at_min_width) { - const ExPolygons anchors = offset2_ex(to_polygons(diff_ex(this->bounds, this->expolygon)), -SCALED_RESOLUTION, SCALED_RESOLUTION); - for (size_t i = 0; i < pp.size(); ++i) { - ThickPolyline& polyline = pp[i]; - extends_line(polyline, anchors, min_width); - polyline.reverse(); - extends_line(polyline, anchors, min_width); - } + extends_line_both_side(pp); } //{ // stringstream stri; diff --git a/xs/src/libslic3r/MedialAxis.hpp b/xs/src/libslic3r/MedialAxis.hpp index f8319ffcab..a23f7a4387 100644 --- a/xs/src/libslic3r/MedialAxis.hpp +++ b/xs/src/libslic3r/MedialAxis.hpp @@ -52,6 +52,7 @@ namespace Slic3r { void fusion_curve(ThickPolylines &pp); void main_fusion(ThickPolylines& pp); void fusion_corners(ThickPolylines &pp); + void extends_line_both_side(ThickPolylines& pp); void extends_line(ThickPolyline& polyline, const ExPolygons& anchors, const coord_t join_width); void remove_too_thin_extrusion(ThickPolylines& pp); void concatenate_polylines_with_crossing(ThickPolylines& pp); From 44e7ec855e1706fe1af6dddaff5835e018d0b69d Mon Sep 17 00:00:00 2001 From: supermerill Date: Tue, 18 Dec 2018 11:38:29 +0100 Subject: [PATCH 17/25] bugfix ensure_not_overextrude --- xs/src/libslic3r/MedialAxis.cpp | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/xs/src/libslic3r/MedialAxis.cpp b/xs/src/libslic3r/MedialAxis.cpp index 073e14d253..5ba5e9f2f6 100644 --- a/xs/src/libslic3r/MedialAxis.cpp +++ b/xs/src/libslic3r/MedialAxis.cpp @@ -607,7 +607,7 @@ MedialAxis::fusion_corners(ThickPolylines &pp) void MedialAxis::extends_line_both_side(ThickPolylines& pp) { - const ExPolygons anchors = offset2_ex(diff_ex(this->bounds, this->expolygon), -SCALED_RESOLUTION, SCALED_RESOLUTION); + const ExPolygons anchors = offset2_ex(to_polygons(diff_ex(this->bounds, this->expolygon)), -SCALED_RESOLUTION, SCALED_RESOLUTION); for (size_t i = 0; i < pp.size(); ++i) { ThickPolyline& polyline = pp[i]; this->extends_line(polyline, anchors, this->min_width); @@ -1257,7 +1257,7 @@ MedialAxis::ensure_not_overextrude(ThickPolylines& pp) // add holes "perimeter gaps" double holesGaps = 0; for (const Polygon &hole : bounds.holes) { - holesGaps += hole->length() * height * (1 - 0.25*PI) * 0.5; + holesGaps += hole.length() * height * (1 - 0.25*PI) * 0.5; } boundsVolume += perimeterRoundGap + holesGaps; @@ -1265,9 +1265,8 @@ MedialAxis::ensure_not_overextrude(ThickPolylines& pp) //reduce width double reduce_by = boundsVolume / volume; for (ThickPolyline& polyline : pp) { - for (ThickLine &l : polyline.thicklines()) { - l.a_width *= reduce_by; - l.b_width *= reduce_by; + for (coordf_t &width : polyline.width) { + width *= reduce_by; } } } From fff41c28649d3e4b996b41e733bf9a6cd7eea4b4 Mon Sep 17 00:00:00 2001 From: supermerill Date: Fri, 4 Jan 2019 17:53:36 +0100 Subject: [PATCH 18/25] thin_walls_min_width & min size of thin wall is the nozzle diameter --- lib/Slic3r/GUI/PresetEditor.pm | 17 ++++++++++---- src/GUI/Dialogs/PresetEditor.hpp | 2 +- xs/src/libslic3r/Layer.cpp | 1 + xs/src/libslic3r/MedialAxis.cpp | 30 ++++++++++++++++++++----- xs/src/libslic3r/MedialAxis.hpp | 11 +++++---- xs/src/libslic3r/PerimeterGenerator.cpp | 7 +++--- xs/src/libslic3r/PrintConfig.cpp | 10 ++++++++- xs/src/libslic3r/PrintConfig.hpp | 2 ++ xs/src/libslic3r/PrintRegion.cpp | 1 + 9 files changed, 63 insertions(+), 18 deletions(-) diff --git a/lib/Slic3r/GUI/PresetEditor.pm b/lib/Slic3r/GUI/PresetEditor.pm index 2563940429..23b4db5d22 100644 --- a/lib/Slic3r/GUI/PresetEditor.pm +++ b/lib/Slic3r/GUI/PresetEditor.pm @@ -439,7 +439,7 @@ sub options { adaptive_slicing adaptive_slicing_quality match_horizontal_surfaces perimeters spiral_vase top_solid_layers min_shell_thickness min_top_bottom_shell_thickness bottom_solid_layers - extra_perimeters avoid_crossing_perimeters thin_walls overhangs + extra_perimeters avoid_crossing_perimeters thin_walls thin_walls_min_width overhangs seam_position external_perimeters_first fill_density fill_pattern top_infill_pattern bottom_infill_pattern fill_gaps infill_every_layers infill_only_where_needed @@ -540,7 +540,12 @@ sub build { my $optgroup = $page->new_optgroup('Quality (slower slicing)'); $optgroup->append_single_option_line('extra_perimeters'); $optgroup->append_single_option_line('avoid_crossing_perimeters'); - $optgroup->append_single_option_line('thin_walls'); + my $line = Slic3r::GUI::OptionsGroup::Line->new( + label => 'Detect thin walls', + ); + $line->append_option($optgroup->get_option('thin_walls')); + $line->append_option($optgroup->get_option('thin_walls_min_width')); + $optgroup->append_line($line); $optgroup->append_single_option_line('overhangs'); } { @@ -886,8 +891,8 @@ sub _update { my $have_perimeters = ($config->perimeters > 0) || ($config->min_shell_thickness > 0); if (any { /$opt_key/ } qw(all_keys perimeters)) { $self->get_field($_)->toggle($have_perimeters) - for qw(extra_perimeters thin_walls overhangs seam_position external_perimeters_first - external_perimeter_extrusion_width + for qw(extra_perimeters thin_walls thin_walls_min_width overhangs seam_position + external_perimeters_first external_perimeter_extrusion_width perimeter_speed small_perimeter_speed external_perimeter_speed); } @@ -970,6 +975,10 @@ sub _update { $self->get_field($_)->toggle($have_support_material && $have_support_interface) for qw(support_material_interface_spacing support_material_interface_extruder support_material_interface_speed); + + # thin walls settigns only when thins walls is activated + $self->get_field($_)->toggle($config->thin_walls) + for qw(thin_walls_min_width); $self->get_field('perimeter_extrusion_width')->toggle($have_perimeters || $have_skirt || $have_brim); $self->get_field('support_material_extruder')->toggle($have_support_material || $have_skirt); diff --git a/src/GUI/Dialogs/PresetEditor.hpp b/src/GUI/Dialogs/PresetEditor.hpp index a6046c10bb..1d07c2cfce 100644 --- a/src/GUI/Dialogs/PresetEditor.hpp +++ b/src/GUI/Dialogs/PresetEditor.hpp @@ -143,7 +143,7 @@ class PrintEditor : public PresetEditor { "adaptive_slicing"s, "adaptive_slicing_quality"s, "match_horizontal_surfaces"s, "perimeters"s, "spiral_vase"s, "top_solid_layers"s, "bottom_solid_layers"s, - "extra_perimeters"s, "avoid_crossing_perimeters"s, "thin_walls"s, "overhangs"s, + "extra_perimeters"s, "avoid_crossing_perimeters"s, "thin_walls"s, "thin_walls_min_width"s, "overhangs"s, "seam_position"s, "external_perimeters_first"s, "fill_density"s, "fill_pattern"s, "top_infill_pattern"s, "bottom_infill_pattern"s, "fill_gaps"s, "infill_every_layers"s, "infill_only_where_needed"s, diff --git a/xs/src/libslic3r/Layer.cpp b/xs/src/libslic3r/Layer.cpp index 3e627b1644..ddf09ccbd1 100644 --- a/xs/src/libslic3r/Layer.cpp +++ b/xs/src/libslic3r/Layer.cpp @@ -189,6 +189,7 @@ Layer::make_perimeters() && config.overhangs == other_config.overhangs && config.serialize("perimeter_extrusion_width").compare(other_config.serialize("perimeter_extrusion_width")) == 0 && config.thin_walls == other_config.thin_walls + && config.thin_walls_min_width == other_config.thin_walls_min_width && config.external_perimeters_first == other_config.external_perimeters_first) { layerms.push_back(other_layerm); done.insert(it - this->regions.begin()); diff --git a/xs/src/libslic3r/MedialAxis.cpp b/xs/src/libslic3r/MedialAxis.cpp index 5ba5e9f2f6..4055a840e3 100644 --- a/xs/src/libslic3r/MedialAxis.cpp +++ b/xs/src/libslic3r/MedialAxis.cpp @@ -576,7 +576,7 @@ MedialAxis::fusion_corners(ThickPolylines &pp) //FIXME: also pull (a bit less) points that are near to this one. // if true, pull it a bit, depends on my size, the dot?, and the coeff at my 0-end (~14% for a square, almost 0 for a gentle curve) coord_t length_pull = polyline.length(); - length_pull *= 0.144 * get_coeff_from_angle_countour(polyline.points.back(), this->expolygon, min(min_width, polyline.length() / 2)); + length_pull *= 0.144 * get_coeff_from_angle_countour(polyline.points.back(), this->expolygon, min(min_width, (coord_t)(polyline.length() / 2))); //compute dir Vectorf pull_direction(polyline.points[1].x - polyline.points[0].x, polyline.points[1].y - polyline.points[0].y); @@ -784,11 +784,11 @@ MedialAxis::main_fusion(ThickPolylines& pp) //test if we don't merge with something too different and without any relevance. double coeffSizePolyI = 1; if (polyline.width.back() == 0) { - coeffSizePolyI = 0.1 + 0.9*get_coeff_from_angle_countour(polyline.points.back(), this->expolygon, min(min_width, polyline.length() / 2)); + coeffSizePolyI = 0.1 + 0.9*get_coeff_from_angle_countour(polyline.points.back(), this->expolygon, min(min_width, (coord_t)(polyline.length() / 2))); } double coeffSizeOtherJ = 1; if (other.width.back() == 0) { - coeffSizeOtherJ = 0.1 + 0.9*get_coeff_from_angle_countour(other.points.back(), this->expolygon, min(min_width, polyline.length() / 2)); + coeffSizeOtherJ = 0.1 + 0.9*get_coeff_from_angle_countour(other.points.back(), this->expolygon, min(min_width, (coord_t)(polyline.length() / 2))); } //std::cout << " try2 : " << i << ":" << j << " : " // << (abs(polyline.length()*coeffSizePolyI - other.length()*coeffSizeOtherJ) > max_width / 2) @@ -887,10 +887,10 @@ MedialAxis::main_fusion(ThickPolylines& pp) //TODO: try if we can achieve a better result if we use a different algo if the angle is <90° const double coeff_angle_poly = (coeff_angle_cache.find(polyline.points.back()) != coeff_angle_cache.end()) ? coeff_angle_cache[polyline.points.back()] - : (get_coeff_from_angle_countour(polyline.points.back(), this->expolygon, min(min_width, polyline.length() / 2))); + : (get_coeff_from_angle_countour(polyline.points.back(), this->expolygon, min(min_width, (coord_t)(polyline.length() / 2)))); const double coeff_angle_candi = (coeff_angle_cache.find(best_candidate->points.back()) != coeff_angle_cache.end()) ? coeff_angle_cache[best_candidate->points.back()] - : (get_coeff_from_angle_countour(best_candidate->points.back(), this->expolygon, min(min_width, best_candidate->length() / 2))); + : (get_coeff_from_angle_countour(best_candidate->points.back(), this->expolygon, min(min_width, (coord_t)(best_candidate->length() / 2)))); //this will encourage to follow the curve, a little, because it's shorter near the center //without that, it tends to go to the outter rim. @@ -1313,6 +1313,23 @@ MedialAxis::simplify_polygon_frontier() return simplified_poly; } +void +MedialAxis::grow_to_nozzle_diameter(ThickPolylines& pp, const ExPolygons& anchors) { + //ensure the width is not lower than 0.4. + for (ThickPolyline& polyline : pp) { + for (int i = 0; i < polyline.points.size(); ++i) { + bool is_anchored = false; + for (const ExPolygon &poly : anchors) { + if (poly.contains(polyline.points[i])) { + is_anchored = true; + break; + } + } + if (!is_anchored && polyline.width[i]bounds, this->expolygon)); + polylines_out->insert(polylines_out->end(), pp.begin(), pp.end()); } diff --git a/xs/src/libslic3r/MedialAxis.hpp b/xs/src/libslic3r/MedialAxis.hpp index a23f7a4387..9453fb4829 100644 --- a/xs/src/libslic3r/MedialAxis.hpp +++ b/xs/src/libslic3r/MedialAxis.hpp @@ -20,12 +20,14 @@ namespace Slic3r { const ExPolygon& surface; const ExPolygon& bounds; - const double max_width; - const double min_width; - const double height; + const coord_t max_width; + const coord_t min_width; + const coord_t height; + coord_t nozzle_diameter; bool stop_at_min_width = true; - MedialAxis(const ExPolygon &_expolygon, const ExPolygon &_bounds, const double _max_width, const double _min_width, const double _height) + MedialAxis(const ExPolygon &_expolygon, const ExPolygon &_bounds, const coord_t _max_width, const coord_t _min_width, const coord_t _height) : surface(_expolygon), bounds(_bounds), max_width(_max_width), min_width(_min_width), height(_height) { + nozzle_diameter = _min_width; }; void build(ThickPolylines* polylines_out); void build(Polylines* polylines); @@ -59,6 +61,7 @@ namespace Slic3r { void remove_too_thin_points(ThickPolylines& pp); void remove_too_short_polylines(ThickPolylines& pp, const coord_t min_size); void ensure_not_overextrude(ThickPolylines& pp); + void grow_to_nozzle_diameter(ThickPolylines& pp, const ExPolygons& anchors); }; } diff --git a/xs/src/libslic3r/PerimeterGenerator.cpp b/xs/src/libslic3r/PerimeterGenerator.cpp index 51f152ebb7..d1bba92b97 100644 --- a/xs/src/libslic3r/PerimeterGenerator.cpp +++ b/xs/src/libslic3r/PerimeterGenerator.cpp @@ -117,7 +117,7 @@ PerimeterGenerator::process() // 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 = scale_(this->config->thin_walls_min_width.get_abs_value(this->ext_perimeter_flow.nozzle_diameter)); Polygons no_thin_zone = offset(offsets, ext_pwidth/2, CLIPPER_OFFSET_SCALE, jtSquare, 3); // medial axis requires non-overlapping geometry @@ -150,8 +150,9 @@ PerimeterGenerator::process() if (thin[0].area() > min_width*(ext_pwidth + ext_pspacing2)) { bound.remove_point_too_near(SCALED_RESOLUTION); // the maximum thickness of our thin wall area is equal to the minimum thickness of a single loop - thin[0].medial_axis(bound, ext_pwidth + ext_pspacing2, min_width, - &thin_walls, this->layer_height); + Slic3r::MedialAxis ma(thin[0], bound, ext_pwidth + ext_pspacing2, min_width, this->layer_height); + ma.nozzle_diameter = (coord_t)scale_(this->ext_perimeter_flow.nozzle_diameter); + ma.build(&thin_walls); thin_zones_extruded.emplace_back(thin[0]); } break; diff --git a/xs/src/libslic3r/PrintConfig.cpp b/xs/src/libslic3r/PrintConfig.cpp index d0113b5029..ccdf08977b 100644 --- a/xs/src/libslic3r/PrintConfig.cpp +++ b/xs/src/libslic3r/PrintConfig.cpp @@ -1596,12 +1596,20 @@ PrintConfigDef::PrintConfigDef() } def = this->add("thin_walls", coBool); - def->label = __TRANS("Detect thin walls"); + def->label = __TRANS(""); def->category = __TRANS("Layers and Perimeters"); def->tooltip = __TRANS("Detect single-width walls (parts where two extrusions don't fit and we need to collapse them into a single trace)."); def->cli = "thin-walls!"; def->default_value = new ConfigOptionBool(true); + def = this->add("thin_walls_min_width", coFloatOrPercent); + def->label = __TRANS("min width"); + def->category = __TRANS("Layers and Perimeters"); + def->tooltip = __TRANS("Minimum width for the extrusion to be extruded (widths lower than the nozzle diameter will be over-extruded at the nozzle diameter). Can be percent of the nozzle size."); + def->cli = "thin-walls-min-width=s"; + def->min = 0; + def->default_value = new ConfigOptionFloatOrPercent(33,true); + def = this->add("threads", coInt); def->label = __TRANS("Threads"); def->tooltip = __TRANS("Threads are used to parallelize long-running tasks. Optimal threads number is slightly above the number of available cores/processors."); diff --git a/xs/src/libslic3r/PrintConfig.hpp b/xs/src/libslic3r/PrintConfig.hpp index 8176446934..52c3a1f9d4 100644 --- a/xs/src/libslic3r/PrintConfig.hpp +++ b/xs/src/libslic3r/PrintConfig.hpp @@ -269,6 +269,7 @@ class PrintRegionConfig : public virtual StaticPrintConfig ConfigOptionInt solid_infill_every_layers; ConfigOptionFloatOrPercent solid_infill_speed; ConfigOptionBool thin_walls; + ConfigOptionFloatOrPercent thin_walls_min_width; ConfigOptionFloatOrPercent top_infill_extrusion_width; ConfigOptionEnum top_infill_pattern; ConfigOptionInt top_solid_layers; @@ -311,6 +312,7 @@ class PrintRegionConfig : public virtual StaticPrintConfig OPT_PTR(solid_infill_every_layers); OPT_PTR(solid_infill_speed); OPT_PTR(thin_walls); + OPT_PTR(thin_walls_min_width); OPT_PTR(top_infill_extrusion_width); OPT_PTR(top_infill_pattern); OPT_PTR(top_solid_infill_speed); diff --git a/xs/src/libslic3r/PrintRegion.cpp b/xs/src/libslic3r/PrintRegion.cpp index 6c5c6ed467..ce2c249209 100644 --- a/xs/src/libslic3r/PrintRegion.cpp +++ b/xs/src/libslic3r/PrintRegion.cpp @@ -82,6 +82,7 @@ PrintRegion::invalidate_state_by_config(const PrintConfigBase &config) || opt_key == "first_layer_extrusion_width" || opt_key == "perimeter_extrusion_width" || opt_key == "thin_walls" + || opt_key == "thin_walls_min_width" || opt_key == "external_perimeters_first") { steps.insert(posPerimeters); } else if (opt_key == "first_layer_extrusion_width") { From 69aea3145c77f68c13fffdf1ccf4c0f8e5ae4d34 Mon Sep 17 00:00:00 2001 From: supermerill Date: Fri, 4 Jan 2019 18:29:12 +0100 Subject: [PATCH 19/25] taper ends of thin walls lines --- xs/src/libslic3r/MedialAxis.cpp | 31 ++++++++++++++++++++++++++++++- xs/src/libslic3r/MedialAxis.hpp | 1 + 2 files changed, 31 insertions(+), 1 deletion(-) diff --git a/xs/src/libslic3r/MedialAxis.cpp b/xs/src/libslic3r/MedialAxis.cpp index 4055a840e3..998878da8d 100644 --- a/xs/src/libslic3r/MedialAxis.cpp +++ b/xs/src/libslic3r/MedialAxis.cpp @@ -1330,6 +1330,33 @@ MedialAxis::grow_to_nozzle_diameter(ThickPolylines& pp, const ExPolygons& anchor } } +void +MedialAxis::tapper_ends(ThickPolylines& pp) { + //ensure the width is not lower than 0.4. + for (ThickPolyline& polyline : pp) { + if (polyline.length() < nozzle_diameter * 2) continue; + if (polyline.endpoints.first) { + polyline.width[0] = min_width; + coord_t current_dist = min_width; + for (size_t i = 1; i polyline.width[i]) break; + polyline.width[i] = current_dist; + } + } + if (polyline.endpoints.second) { + size_t last_idx = polyline.width.size() - 1; + polyline.width[last_idx] = min_width; + coord_t current_dist = min_width; + for (size_t i = 1; i polyline.width[last_idx - i]) break; + polyline.width[last_idx - i] = current_dist; + } + } + } +} + void MedialAxis::build(ThickPolylines* polylines_out) { @@ -1472,8 +1499,10 @@ MedialAxis::build(ThickPolylines* polylines_out) // svg.Close(); //} - if (nozzle_diameter != min_width) + if (nozzle_diameter != min_width) { grow_to_nozzle_diameter(pp, diff_ex(this->bounds, this->expolygon)); + tapper_ends(pp); + } polylines_out->insert(polylines_out->end(), pp.begin(), pp.end()); diff --git a/xs/src/libslic3r/MedialAxis.hpp b/xs/src/libslic3r/MedialAxis.hpp index 9453fb4829..79fb5fc08e 100644 --- a/xs/src/libslic3r/MedialAxis.hpp +++ b/xs/src/libslic3r/MedialAxis.hpp @@ -62,6 +62,7 @@ namespace Slic3r { void remove_too_short_polylines(ThickPolylines& pp, const coord_t min_size); void ensure_not_overextrude(ThickPolylines& pp); void grow_to_nozzle_diameter(ThickPolylines& pp, const ExPolygons& anchors); + void tapper_ends(ThickPolylines& pp); }; } From e34e75a7ecb0cf24aa5db4e68b6582b2a97c5dfa Mon Sep 17 00:00:00 2001 From: supermerill Date: Fri, 4 Jan 2019 19:00:10 +0100 Subject: [PATCH 20/25] typo --- xs/src/libslic3r/MedialAxis.cpp | 4 ++-- xs/src/libslic3r/MedialAxis.hpp | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/xs/src/libslic3r/MedialAxis.cpp b/xs/src/libslic3r/MedialAxis.cpp index 998878da8d..e12e617efa 100644 --- a/xs/src/libslic3r/MedialAxis.cpp +++ b/xs/src/libslic3r/MedialAxis.cpp @@ -1331,7 +1331,7 @@ MedialAxis::grow_to_nozzle_diameter(ThickPolylines& pp, const ExPolygons& anchor } void -MedialAxis::tapper_ends(ThickPolylines& pp) { +MedialAxis::taper_ends(ThickPolylines& pp) { //ensure the width is not lower than 0.4. for (ThickPolyline& polyline : pp) { if (polyline.length() < nozzle_diameter * 2) continue; @@ -1501,7 +1501,7 @@ MedialAxis::build(ThickPolylines* polylines_out) if (nozzle_diameter != min_width) { grow_to_nozzle_diameter(pp, diff_ex(this->bounds, this->expolygon)); - tapper_ends(pp); + taper_ends(pp); } polylines_out->insert(polylines_out->end(), pp.begin(), pp.end()); diff --git a/xs/src/libslic3r/MedialAxis.hpp b/xs/src/libslic3r/MedialAxis.hpp index 79fb5fc08e..eee9cd511b 100644 --- a/xs/src/libslic3r/MedialAxis.hpp +++ b/xs/src/libslic3r/MedialAxis.hpp @@ -62,7 +62,7 @@ namespace Slic3r { void remove_too_short_polylines(ThickPolylines& pp, const coord_t min_size); void ensure_not_overextrude(ThickPolylines& pp); void grow_to_nozzle_diameter(ThickPolylines& pp, const ExPolygons& anchors); - void tapper_ends(ThickPolylines& pp); + void taper_ends(ThickPolylines& pp); }; } From fc46316191537b4c029ab81711148331aa514e54 Mon Sep 17 00:00:00 2001 From: supermerill Date: Thu, 7 Feb 2019 13:39:30 +0100 Subject: [PATCH 21/25] Thin_wall / medial axis: - reworked thin_variable_width (discretize into segments of constant width) - bugfix taper_ends - add setting thin_walls_overlap to control the perimeter/thin wall overlap - add point.interpolate(%,point) : point method --- lib/Slic3r/GUI/PresetEditor.pm | 9 +- src/GUI/Dialogs/PresetEditor.hpp | 3 +- xs/src/libslic3r/Geometry.hpp | 1 - xs/src/libslic3r/Layer.cpp | 1 + xs/src/libslic3r/MedialAxis.cpp | 190 ++++++++++++++++++++---- xs/src/libslic3r/MedialAxis.hpp | 6 + xs/src/libslic3r/PerimeterGenerator.cpp | 126 ++-------------- xs/src/libslic3r/PerimeterGenerator.hpp | 2 - xs/src/libslic3r/Point.cpp | 13 ++ xs/src/libslic3r/Point.hpp | 1 + xs/src/libslic3r/PrintConfig.cpp | 10 +- xs/src/libslic3r/PrintConfig.hpp | 2 + xs/src/libslic3r/PrintRegion.cpp | 1 + 13 files changed, 210 insertions(+), 155 deletions(-) diff --git a/lib/Slic3r/GUI/PresetEditor.pm b/lib/Slic3r/GUI/PresetEditor.pm index 23b4db5d22..085ac9cbd7 100644 --- a/lib/Slic3r/GUI/PresetEditor.pm +++ b/lib/Slic3r/GUI/PresetEditor.pm @@ -439,7 +439,8 @@ sub options { adaptive_slicing adaptive_slicing_quality match_horizontal_surfaces perimeters spiral_vase top_solid_layers min_shell_thickness min_top_bottom_shell_thickness bottom_solid_layers - extra_perimeters avoid_crossing_perimeters thin_walls thin_walls_min_width overhangs + extra_perimeters avoid_crossing_perimeters + thin_walls thin_walls_min_width thin_walls_overlap overhangs seam_position external_perimeters_first fill_density fill_pattern top_infill_pattern bottom_infill_pattern fill_gaps infill_every_layers infill_only_where_needed @@ -545,6 +546,7 @@ sub build { ); $line->append_option($optgroup->get_option('thin_walls')); $line->append_option($optgroup->get_option('thin_walls_min_width')); + $line->append_option($optgroup->get_option('thin_walls_overlap')); $optgroup->append_line($line); $optgroup->append_single_option_line('overhangs'); } @@ -891,7 +893,8 @@ sub _update { my $have_perimeters = ($config->perimeters > 0) || ($config->min_shell_thickness > 0); if (any { /$opt_key/ } qw(all_keys perimeters)) { $self->get_field($_)->toggle($have_perimeters) - for qw(extra_perimeters thin_walls thin_walls_min_width overhangs seam_position + for qw(extra_perimeters thin_walls thin_walls_min_width thin_walls_overlap + overhangs seam_position external_perimeters_first external_perimeter_extrusion_width perimeter_speed small_perimeter_speed external_perimeter_speed); } @@ -978,7 +981,7 @@ sub _update { # thin walls settigns only when thins walls is activated $self->get_field($_)->toggle($config->thin_walls) - for qw(thin_walls_min_width); + for qw(thin_walls_min_width thin_walls_overlap); $self->get_field('perimeter_extrusion_width')->toggle($have_perimeters || $have_skirt || $have_brim); $self->get_field('support_material_extruder')->toggle($have_support_material || $have_skirt); diff --git a/src/GUI/Dialogs/PresetEditor.hpp b/src/GUI/Dialogs/PresetEditor.hpp index 1d07c2cfce..9a7e52983b 100644 --- a/src/GUI/Dialogs/PresetEditor.hpp +++ b/src/GUI/Dialogs/PresetEditor.hpp @@ -143,7 +143,8 @@ class PrintEditor : public PresetEditor { "adaptive_slicing"s, "adaptive_slicing_quality"s, "match_horizontal_surfaces"s, "perimeters"s, "spiral_vase"s, "top_solid_layers"s, "bottom_solid_layers"s, - "extra_perimeters"s, "avoid_crossing_perimeters"s, "thin_walls"s, "thin_walls_min_width"s, "overhangs"s, + "extra_perimeters"s, "avoid_crossing_perimeters"s, + "thin_walls"s, "thin_walls_min_width"s, "thin_walls_overlap"s, "overhangs"s, "seam_position"s, "external_perimeters_first"s, "fill_density"s, "fill_pattern"s, "top_infill_pattern"s, "bottom_infill_pattern"s, "fill_gaps"s, "infill_every_layers"s, "infill_only_where_needed"s, diff --git a/xs/src/libslic3r/Geometry.hpp b/xs/src/libslic3r/Geometry.hpp index f18e16e403..8dce56a2fb 100644 --- a/xs/src/libslic3r/Geometry.hpp +++ b/xs/src/libslic3r/Geometry.hpp @@ -3,7 +3,6 @@ #include "libslic3r.h" #include "BoundingBox.hpp" -#include "MedialAxis.hpp" #include "ExPolygon.hpp" #include "Polygon.hpp" #include "Polyline.hpp" diff --git a/xs/src/libslic3r/Layer.cpp b/xs/src/libslic3r/Layer.cpp index ddf09ccbd1..35cf9b566a 100644 --- a/xs/src/libslic3r/Layer.cpp +++ b/xs/src/libslic3r/Layer.cpp @@ -190,6 +190,7 @@ Layer::make_perimeters() && config.serialize("perimeter_extrusion_width").compare(other_config.serialize("perimeter_extrusion_width")) == 0 && config.thin_walls == other_config.thin_walls && config.thin_walls_min_width == other_config.thin_walls_min_width + && config.thin_walls_overlap == other_config.thin_walls_overlap && config.external_perimeters_first == other_config.external_perimeters_first) { layerms.push_back(other_layerm); done.insert(it - this->regions.begin()); diff --git a/xs/src/libslic3r/MedialAxis.cpp b/xs/src/libslic3r/MedialAxis.cpp index e12e617efa..4f7b025656 100644 --- a/xs/src/libslic3r/MedialAxis.cpp +++ b/xs/src/libslic3r/MedialAxis.cpp @@ -1,3 +1,4 @@ +#include "MedialAxis.hpp" #include "BoundingBox.hpp" #include "ExPolygon.hpp" #include "Geometry.hpp" @@ -343,13 +344,9 @@ add_point_same_percent(ThickPolyline* pattern, ThickPolyline* to_modify) double percent_dist = (percent_length - percent_length_other_before) / (percent_length_other - percent_length_other_before); coordf_t new_width = to_modify->width[idx_other - 1] * (1 - percent_dist); new_width += to_modify->width[idx_other] * (percent_dist); - Point new_point; - new_point.x = (coord_t)((double)(to_modify->points[idx_other - 1].x) * (1 - percent_dist)); - new_point.x += (coord_t)((double)(to_modify->points[idx_other].x) * (percent_dist)); - new_point.y = (coord_t)((double)(to_modify->points[idx_other - 1].y) * (1 - percent_dist)); - new_point.y += (coord_t)((double)(to_modify->points[idx_other].y) * (percent_dist)); to_modify->width.insert(to_modify->width.begin() + idx_other, new_width); - to_modify->points.insert(to_modify->points.begin() + idx_other, new_point); + to_modify->points.insert(to_modify->points.begin() + idx_other, + to_modify->points[idx_other - 1].interpolate(percent_dist, to_modify->points[idx_other])); } } } @@ -1030,14 +1027,11 @@ MedialAxis::remove_too_thin_extrusion(ThickPolylines& pp) while (polyline.points.size() > 1 && polyline.width.front() < this->min_width && polyline.endpoints.first) { //try to split if possible if (polyline.width[1] > min_width) { - double percent_can_keep = 1 - (min_width - polyline.width[0]) / (polyline.width[1] - polyline.width[0]); - if (polyline.points.front().distance_to(polyline.points[1]) * percent_can_keep > SCALED_RESOLUTION) { + double percent_can_keep = (min_width - polyline.width[0]) / (polyline.width[1] - polyline.width[0]); + if (polyline.points.front().distance_to(polyline.points[1]) * (1 - percent_can_keep) > SCALED_RESOLUTION) { //Can split => move the first point and assign a new weight. //the update of endpoints wil be performed in concatThickPolylines - polyline.points.front().x = polyline.points.front().x + - (coord_t)((polyline.points[1].x - polyline.points.front().x) * (1 - percent_can_keep)); - polyline.points.front().y = polyline.points.front().y + - (coord_t)((polyline.points[1].y - polyline.points.front().y) * (1 - percent_can_keep)); + polyline.points.front() = polyline.points.front().interpolate(percent_can_keep, polyline.points[1]); polyline.width.front() = min_width; } else { /// almost 0-length, Remove @@ -1054,14 +1048,11 @@ MedialAxis::remove_too_thin_extrusion(ThickPolylines& pp) while (polyline.points.size() > 1 && polyline.width.back() < this->min_width && polyline.endpoints.second) { //try to split if possible if (polyline.width[polyline.points.size() - 2] > min_width) { - double percent_can_keep = 1 - (min_width - polyline.width.back()) / (polyline.width[polyline.points.size() - 2] - polyline.width.back()); - if (polyline.points.back().distance_to(polyline.points[polyline.points.size() - 2]) * percent_can_keep > SCALED_RESOLUTION) { + double percent_can_keep = (min_width - polyline.width.back()) / (polyline.width[polyline.points.size() - 2] - polyline.width.back()); + if (polyline.points.back().distance_to(polyline.points[polyline.points.size() - 2]) * (1 - percent_can_keep) > SCALED_RESOLUTION) { //Can split => move the first point and assign a new weight. //the update of endpoints wil be performed in concatThickPolylines - polyline.points.back().x = polyline.points.back().x + - (coord_t)((polyline.points[polyline.points.size() - 2].x - polyline.points.back().x) * (1 - percent_can_keep)); - polyline.points.back().y = polyline.points.back().y + - (coord_t)((polyline.points[polyline.points.size() - 2].y - polyline.points.back().y) * (1 - percent_can_keep)); + polyline.points.back() = polyline.points.back().interpolate(percent_can_keep, polyline.points[polyline.points.size() - 2]); polyline.width.back() = min_width; } else { /// almost 0-length, Remove @@ -1137,6 +1128,12 @@ MedialAxis::concatenate_polylines_with_crossing(ThickPolylines& pp) } } if (best_candidate != nullptr) { + //intersections may create ever-ertusion because the included circle can be a bit larger. We have to make it short again if needed. + if (polyline.points.size() > 1 && best_candidate->points.size() > 1 + && polyline.width.back() > polyline.width[polyline.width.size() - 2] + && polyline.width.back() > best_candidate->width[1]) { + polyline.width.back() = std::min(polyline.width[polyline.width.size() - 2], best_candidate->width[1]); + } 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() + 1, best_candidate->width.end()); polyline.endpoints.second = best_candidate->endpoints.second; @@ -1317,7 +1314,7 @@ void MedialAxis::grow_to_nozzle_diameter(ThickPolylines& pp, const ExPolygons& anchors) { //ensure the width is not lower than 0.4. for (ThickPolyline& polyline : pp) { - for (int i = 0; i < polyline.points.size(); ++i) { + for (size_t i = 0; i < polyline.points.size(); ++i) { bool is_anchored = false; for (const ExPolygon &poly : anchors) { if (poly.contains(polyline.points[i])) { @@ -1332,26 +1329,49 @@ MedialAxis::grow_to_nozzle_diameter(ThickPolylines& pp, const ExPolygons& anchor void MedialAxis::taper_ends(ThickPolylines& pp) { + const coord_t min_size = this->nozzle_diameter * 0.1; + const coordf_t length = std::min(this->anchor_size, (this->nozzle_diameter - min_size) / 2); + if (length <= SCALED_RESOLUTION) return; //ensure the width is not lower than 0.4. for (ThickPolyline& polyline : pp) { - if (polyline.length() < nozzle_diameter * 2) continue; + if (polyline.length() < length * 2.2) continue; if (polyline.endpoints.first) { - polyline.width[0] = min_width; - coord_t current_dist = min_width; + polyline.width[0] = min_size; + coord_t current_dist = min_size; + coord_t last_dist = min_size; for (size_t i = 1; i polyline.width[i]) break; - polyline.width[i] = current_dist; + if (current_dist > length) { + //create a new point if not near enough + if (current_dist > polyline.width[i] + SCALED_RESOLUTION) { + coordf_t percent_dist = (polyline.width[i] - polyline.width[i - 1]) / (current_dist - last_dist); + polyline.points.insert(polyline.points.begin() + i, polyline.points[i - 1].interpolate(percent_dist, polyline.points[i])); + polyline.width.insert(polyline.width.begin() + i, polyline.width[i]); + } + break; + } + polyline.width[i] = std::max((coordf_t)min_size, min_size + (polyline.width[i] - min_size) * current_dist / length); + last_dist = current_dist; } } if (polyline.endpoints.second) { - size_t last_idx = polyline.width.size() - 1; - polyline.width[last_idx] = min_width; - coord_t current_dist = min_width; + const size_t back_idx = polyline.width.size() - 1; + polyline.width[back_idx] = min_size; + coord_t current_dist = min_size; + coord_t last_dist = min_size; for (size_t i = 1; i polyline.width[last_idx - i]) break; - polyline.width[last_idx - i] = current_dist; + current_dist += polyline.points[back_idx - i + 1].distance_to(polyline.points[back_idx - i]); + if (current_dist > length) { + //create new point if not near enough + if (current_dist > polyline.width[back_idx - i] + SCALED_RESOLUTION) { + coordf_t percent_dist = (polyline.width[back_idx - i] - polyline.width[back_idx - i + 1]) / (current_dist - last_dist); + polyline.points.insert(polyline.points.begin() + back_idx - i + 1, polyline.points[back_idx - i + 1].interpolate(percent_dist, polyline.points[back_idx - i])); + polyline.width.insert(polyline.width.begin() + back_idx - i + 1, polyline.width[back_idx - i]); + } + break; + } + polyline.width[back_idx - i] = std::max((coordf_t)min_size, min_size + (polyline.width[back_idx - i] - min_size) * current_dist / length); + last_dist = current_dist; } } } @@ -1508,4 +1528,114 @@ MedialAxis::build(ThickPolylines* polylines_out) } + +ExtrusionEntityCollection +discretize_variable_width(const ThickPolylines &polylines, ExtrusionRole role, Flow flow) +{ + // this value determines granularity of adaptive width, as G-code does not allow + // variable extrusion within a single move; this value shall only affect the amount + // of segments, and any pruning shall be performed before we apply this tolerance + const double tolerance = 4*SCALED_RESOLUTION;//scale_(0.05); + + int id_line = 0; + ExtrusionEntityCollection coll; + for (ThickPolylines::const_iterator p = polylines.begin(); p != polylines.end(); ++p) { + id_line++; + ExtrusionPaths paths; + ExtrusionPath path(role); + ThickLines lines = p->thicklines(); + + for (int i = 0; i < (int)lines.size(); ++i) { + ThickLine& line = lines[i]; + + const coordf_t line_len = line.length(); + if (line_len < SCALED_EPSILON) continue; + + double thickness_delta = fabs(line.a_width - line.b_width); + if (thickness_delta > tolerance && ceil(thickness_delta / tolerance) > 2) { + const size_t segments = 1 + ceil(thickness_delta / tolerance); + Points pp; + std::vector width; + { + for (size_t j = 0; j < segments; ++j) { + pp.push_back(line.a.interpolate(((double)j) / segments, line.b)); + double percent_width = ((double)j) / (segments-1); + width.push_back(line.a_width*(1 - percent_width) + line.b_width*percent_width); + } + pp.push_back(line.b); + width.push_back(line.b_width); + + assert(pp.size() == segments + 1); + assert(width.size() == segments); + } + + // delete this line and insert new ones + lines.erase(lines.begin() + i); + for (size_t j = 0; j < segments; ++j) { + ThickLine new_line(pp[j], pp[j+1]); + new_line.a_width = width[j]; + new_line.b_width = width[j]; + lines.insert(lines.begin() + i + j, new_line); + } + + --i; + continue; + } else if (thickness_delta > 0) { + //create a middle point + ThickLine new_line(line.a.interpolate(0.5, line.b), line.b); + new_line.a_width = line.b_width; + new_line.b_width = line.b_width; + line.b = new_line.a; + line.b_width = line.a_width; + lines.insert(lines.begin() + i + 1, new_line); + + --i; + continue; + } + + if (path.polyline.points.empty()) { + flow.width = unscale(line.a_width) + flow.height * (1. - 0.25 * PI); + #ifdef SLIC3R_DEBUG + printf(" filling %f gap\n", flow.width); + #endif + + // make sure we don't include too thin segments which + // may cause even slightly negative mm3_per_mm because of floating point math + path.mm3_per_mm = flow.mm3_per_mm(); + if (path.mm3_per_mm < EPSILON) continue; + + path.width = flow.width; + path.height = flow.height; + path.polyline.append(line.a); + path.polyline.append(line.b); + } else { + thickness_delta = fabs(flow.scaled_spacing() - line.a_width); + if (thickness_delta <= tolerance/2) { + // the width difference between this line and the current flow width is + // within the accepted tolerance + + path.polyline.append(line.b); + } else { + // we need to initialize a new line + paths.push_back(path); + path = ExtrusionPath(role); + --i; + } + } + } + if (path.polyline.is_valid()) + paths.push_back(path); + + // append paths to collection + if (!paths.empty()) { + if (paths.front().first_point().coincides_with(paths.back().last_point())) { + coll.append(ExtrusionLoop(paths)); + } else { + coll.append(paths); + } + } + } + + return coll; +} } // namespace Slic3r diff --git a/xs/src/libslic3r/MedialAxis.hpp b/xs/src/libslic3r/MedialAxis.hpp index eee9cd511b..635f68f2c8 100644 --- a/xs/src/libslic3r/MedialAxis.hpp +++ b/xs/src/libslic3r/MedialAxis.hpp @@ -5,6 +5,8 @@ #include "ExPolygon.hpp" #include "Polyline.hpp" #include "Geometry.hpp" +#include "ExtrusionEntityCollection.hpp" +#include "Flow.hpp" #include #include "boost/polygon/voronoi.hpp" @@ -24,10 +26,12 @@ namespace Slic3r { const coord_t min_width; const coord_t height; coord_t nozzle_diameter; + coord_t anchor_size; bool stop_at_min_width = true; MedialAxis(const ExPolygon &_expolygon, const ExPolygon &_bounds, const coord_t _max_width, const coord_t _min_width, const coord_t _height) : surface(_expolygon), bounds(_bounds), max_width(_max_width), min_width(_min_width), height(_height) { nozzle_diameter = _min_width; + anchor_size = 0; }; void build(ThickPolylines* polylines_out); void build(Polylines* polylines); @@ -64,6 +68,8 @@ namespace Slic3r { void grow_to_nozzle_diameter(ThickPolylines& pp, const ExPolygons& anchors); void taper_ends(ThickPolylines& pp); }; + + ExtrusionEntityCollection discretize_variable_width(const ThickPolylines &polylines, ExtrusionRole role, Flow flow); } diff --git a/xs/src/libslic3r/PerimeterGenerator.cpp b/xs/src/libslic3r/PerimeterGenerator.cpp index d1bba92b97..2243c133d0 100644 --- a/xs/src/libslic3r/PerimeterGenerator.cpp +++ b/xs/src/libslic3r/PerimeterGenerator.cpp @@ -1,6 +1,7 @@ #include "PerimeterGenerator.hpp" #include "ClipperUtils.hpp" #include "ExtrusionEntityCollection.hpp" +#include "MedialAxis.hpp" #include #include @@ -94,6 +95,7 @@ PerimeterGenerator::process() if (this->config->thin_walls) { // the minimum thickness of a single loop is: // ext_width/2 + ext_spacing/2 + spacing/2 + width/2 + //here, we shrink & grow by ext_min_spacing to remove areas where the current loop can't be extruded offsets = offset2( last, -(ext_pwidth/2 + ext_min_spacing/2 - 1), @@ -104,12 +106,12 @@ PerimeterGenerator::process() float div = 2; while (no_thin_onion.size()>0 && offsets.size() > no_thin_onion.size() && no_thin_onion.size() + offsets.size() > 3) { div += 0.5; - //use a sightly smaller offset2 spacing to try to improve the split + //use a sightly smaller offset2 spacing to try to improve the split, but with a little bit of over-extrusion Polygons next_onion_secondTry = offset2( last, -(float)(ext_pwidth / 2 + ext_min_spacing / div - 1), +(float)(ext_min_spacing / div - 1)); - if (offsets.size() > next_onion_secondTry.size()) { + if (offsets.size() > next_onion_secondTry.size() * 1.1) { offsets = next_onion_secondTry; } if (div > 3) break; @@ -133,14 +135,14 @@ PerimeterGenerator::process() if (half_thins.size() > 0) { no_thin_zone = diff(last, to_polygons(offset_ex(half_thins, (float)(min_width / 2) - SCALED_EPSILON)), true); } - ExPolygons thin_zones_extruded; // compute a bit of overlap to anchor thin walls inside the print. for (ExPolygon &half_thin : half_thins) { //growing back the polygon ExPolygons thin = offset_ex(half_thin, (float)(min_width / 2)); assert(thin.size() == 1); + double overlap = (coord_t)scale_(this->config->thin_walls_overlap.get_abs_value(this->ext_perimeter_flow.nozzle_diameter)); ExPolygons anchor = intersection_ex( - to_polygons(offset_ex(half_thin, (float)(min_width / 2 + ext_pwidth / 2), CLIPPER_OFFSET_SCALE, jtSquare, 3)), + to_polygons(offset_ex(half_thin, (float)(min_width / 2 + overlap), CLIPPER_OFFSET_SCALE, jtSquare, 3)), no_thin_zone, true); ExPolygons bounds = _clipper_ex(ClipperLib::ctUnion, to_polygons(thin), to_polygons(anchor), true); for (ExPolygon &bound : bounds) { @@ -152,15 +154,13 @@ PerimeterGenerator::process() // the maximum thickness of our thin wall area is equal to the minimum thickness of a single loop Slic3r::MedialAxis ma(thin[0], bound, ext_pwidth + ext_pspacing2, min_width, this->layer_height); ma.nozzle_diameter = (coord_t)scale_(this->ext_perimeter_flow.nozzle_diameter); + ma.anchor_size = overlap; ma.build(&thin_walls); - thin_zones_extruded.emplace_back(thin[0]); } break; } } } - // recompute the next onion, to be sure to not miss any small areas that can't be extruded by thin_walls - offsets = to_polygons(diff_ex(offset_ex(last, -(float)(ext_pwidth / 2)), thin_zones_extruded, true)); #ifdef DEBUG printf(" %zu thin walls detected\n", thin_walls.size()); #endif @@ -335,8 +335,7 @@ PerimeterGenerator::process() } } if (!polylines.empty()) { - ExtrusionEntityCollection gap_fill = this->_variable_width(polylines, - erGapFill, this->solid_infill_flow); + ExtrusionEntityCollection gap_fill = discretize_variable_width(polylines, erGapFill, this->solid_infill_flow); this->gap_fill->append(gap_fill.entities); @@ -460,8 +459,7 @@ PerimeterGenerator::_traverse_loops(const PerimeterGeneratorLoops &loops, // append thin walls to the nearest-neighbor search (only for first iteration) if (!thin_walls.empty()) { - ExtrusionEntityCollection tw = this->_variable_width - (thin_walls, erExternalPerimeter, this->ext_perimeter_flow); + ExtrusionEntityCollection tw = discretize_variable_width(thin_walls, erExternalPerimeter, this->ext_perimeter_flow); coll.append(tw.entities); thin_walls.clear(); @@ -502,112 +500,6 @@ PerimeterGenerator::_traverse_loops(const PerimeterGeneratorLoops &loops, return entities; } -ExtrusionEntityCollection -PerimeterGenerator::_variable_width(const ThickPolylines &polylines, ExtrusionRole role, Flow flow) const -{ - // this value determines granularity of adaptive width, as G-code does not allow - // variable extrusion within a single move; this value shall only affect the amount - // of segments, and any pruning shall be performed before we apply this tolerance - const double tolerance = scale_(0.05); - - int id_line = 0; - ExtrusionEntityCollection coll; - for (ThickPolylines::const_iterator p = polylines.begin(); p != polylines.end(); ++p) { - id_line++; - ExtrusionPaths paths; - ExtrusionPath path(role); - ThickLines lines = p->thicklines(); - - for (int i = 0; i < (int)lines.size(); ++i) { - const ThickLine& line = lines[i]; - - const coordf_t line_len = line.length(); - if (line_len < SCALED_EPSILON) continue; - - double thickness_delta = fabs(line.a_width - line.b_width); - if (thickness_delta > tolerance) { - const size_t segments = ceil(thickness_delta / tolerance); - const coordf_t seg_len = line_len / segments; - Points pp; - std::vector width; - { - pp.push_back(line.a); - width.push_back(line.a_width); - for (size_t j = 1; j < segments; ++j) { - pp.push_back(line.point_at(j*seg_len)); - - coordf_t w = line.a_width + (j*seg_len) * (line.b_width-line.a_width) / line_len; - width.push_back(w); - width.push_back(w); - } - pp.push_back(line.b); - width.push_back(line.b_width); - - assert(pp.size() == segments + 1); - assert(width.size() == segments*2); - } - - // delete this line and insert new ones - lines.erase(lines.begin() + i); - for (size_t j = 0; j < segments; ++j) { - ThickLine new_line(pp[j], pp[j+1]); - new_line.a_width = width[2*j]; - new_line.b_width = width[2*j+1]; - lines.insert(lines.begin() + i + j, new_line); - } - - --i; - continue; - } - - const double w = fmax(line.a_width, line.b_width); - - if (path.polyline.points.empty()) { - flow.width = unscale(w); - #ifdef SLIC3R_DEBUG - printf(" filling %f gap\n", flow.width); - #endif - - // make sure we don't include too thin segments which - // may cause even slightly negative mm3_per_mm because of floating point math - path.mm3_per_mm = flow.mm3_per_mm(); - if (path.mm3_per_mm < EPSILON) continue; - - path.width = flow.width; - path.height = flow.height; - path.polyline.append(line.a); - path.polyline.append(line.b); - } else { - thickness_delta = fabs(scale_(flow.width) - w); - if (thickness_delta <= tolerance/2) { - // the width difference between this line and the current flow width is - // within the accepted tolerance - - path.polyline.append(line.b); - } else { - // we need to initialize a new line - paths.push_back(path); - path = ExtrusionPath(role); - --i; - } - } - } - if (path.polyline.is_valid()) - paths.push_back(path); - - // append paths to collection - if (!paths.empty()) { - if (paths.front().first_point().coincides_with(paths.back().last_point())) { - coll.append(ExtrusionLoop(paths)); - } else { - coll.append(paths); - } - } - } - - return coll; -} - bool PerimeterGeneratorLoop::is_internal_contour() const { diff --git a/xs/src/libslic3r/PerimeterGenerator.hpp b/xs/src/libslic3r/PerimeterGenerator.hpp index 0e7fbd3e4b..4bd7802c3b 100644 --- a/xs/src/libslic3r/PerimeterGenerator.hpp +++ b/xs/src/libslic3r/PerimeterGenerator.hpp @@ -86,8 +86,6 @@ class PerimeterGenerator { ExtrusionEntityCollection _traverse_loops(const PerimeterGeneratorLoops &loops, ThickPolylines &thin_walls) const; - ExtrusionEntityCollection _variable_width - (const ThickPolylines &polylines, ExtrusionRole role, Flow flow) const; }; } diff --git a/xs/src/libslic3r/Point.cpp b/xs/src/libslic3r/Point.cpp index bf5dcd13a4..28749c7ea8 100644 --- a/xs/src/libslic3r/Point.cpp +++ b/xs/src/libslic3r/Point.cpp @@ -305,6 +305,19 @@ Point::projection_onto(const Line &line) const } } +/// This method create a new point on the line defined by this and p2. +/// The new point is place at position defined by |p2-this| * percent, starting from this +/// \param percent the proportion of the segment length to place the point +/// \param p2 the second point, forming a segment with this +/// \return a new point, == this if percent is 0 and == p2 if percent is 1 +Point Point::interpolate(const double percent, const Point &p2) const +{ + Point p_out; + p_out.x = this->x*(1 - percent) + p2.x*(percent); + p_out.y = this->y*(1 - percent) + p2.y*(percent); + return p_out; +} + Point Point::negative() const { diff --git a/xs/src/libslic3r/Point.hpp b/xs/src/libslic3r/Point.hpp index bc8ce488a8..671e7775ba 100644 --- a/xs/src/libslic3r/Point.hpp +++ b/xs/src/libslic3r/Point.hpp @@ -82,6 +82,7 @@ class Point double ccw_angle(const Point &p1, const Point &p2) const; Point projection_onto(const MultiPoint &poly) const; Point projection_onto(const Line &line) const; + Point interpolate(const double percent, const Point &p) const; Point negative() const; Vector vector_to(const Point &point) const; void align_to_grid(const Point &spacing, const Point &base = Point(0,0)); diff --git a/xs/src/libslic3r/PrintConfig.cpp b/xs/src/libslic3r/PrintConfig.cpp index ccdf08977b..e94bcc0094 100644 --- a/xs/src/libslic3r/PrintConfig.cpp +++ b/xs/src/libslic3r/PrintConfig.cpp @@ -1608,7 +1608,15 @@ PrintConfigDef::PrintConfigDef() def->tooltip = __TRANS("Minimum width for the extrusion to be extruded (widths lower than the nozzle diameter will be over-extruded at the nozzle diameter). Can be percent of the nozzle size."); def->cli = "thin-walls-min-width=s"; def->min = 0; - def->default_value = new ConfigOptionFloatOrPercent(33,true); + def->default_value = new ConfigOptionFloatOrPercent(33, true); + + def = this->add("thin_walls_overlap", coFloatOrPercent); + def->label = __TRANS("overlap"); + def->category = __TRANS("Layers and Perimeters"); + def->tooltip = __TRANS("Overlap between the thin walls and the perimeters. Can be a % of the external perimeter width (default 50%)"); + def->cli = "thin-walls-overlap=s"; + def->min = 0; + def->default_value = new ConfigOptionFloatOrPercent(50, true); def = this->add("threads", coInt); def->label = __TRANS("Threads"); diff --git a/xs/src/libslic3r/PrintConfig.hpp b/xs/src/libslic3r/PrintConfig.hpp index 52c3a1f9d4..726012d7b7 100644 --- a/xs/src/libslic3r/PrintConfig.hpp +++ b/xs/src/libslic3r/PrintConfig.hpp @@ -270,6 +270,7 @@ class PrintRegionConfig : public virtual StaticPrintConfig ConfigOptionFloatOrPercent solid_infill_speed; ConfigOptionBool thin_walls; ConfigOptionFloatOrPercent thin_walls_min_width; + ConfigOptionFloatOrPercent thin_walls_overlap; ConfigOptionFloatOrPercent top_infill_extrusion_width; ConfigOptionEnum top_infill_pattern; ConfigOptionInt top_solid_layers; @@ -313,6 +314,7 @@ class PrintRegionConfig : public virtual StaticPrintConfig OPT_PTR(solid_infill_speed); OPT_PTR(thin_walls); OPT_PTR(thin_walls_min_width); + OPT_PTR(thin_walls_overlap); OPT_PTR(top_infill_extrusion_width); OPT_PTR(top_infill_pattern); OPT_PTR(top_solid_infill_speed); diff --git a/xs/src/libslic3r/PrintRegion.cpp b/xs/src/libslic3r/PrintRegion.cpp index ce2c249209..bda491383c 100644 --- a/xs/src/libslic3r/PrintRegion.cpp +++ b/xs/src/libslic3r/PrintRegion.cpp @@ -83,6 +83,7 @@ PrintRegion::invalidate_state_by_config(const PrintConfigBase &config) || opt_key == "perimeter_extrusion_width" || opt_key == "thin_walls" || opt_key == "thin_walls_min_width" + || opt_key == "thin_walls_overlap" || opt_key == "external_perimeters_first") { steps.insert(posPerimeters); } else if (opt_key == "first_layer_extrusion_width") { From f6ae12fdd84c8d60e99ba271932e8c499edcc60e Mon Sep 17 00:00:00 2001 From: supermerill Date: Fri, 8 Feb 2019 13:40:10 +0100 Subject: [PATCH 22/25] bugfix thin wall / gapfill width --- t/adaptive_width.t | 1 + xs/src/libslic3r/MedialAxis.cpp | 71 ++++++--------------------------- 2 files changed, 14 insertions(+), 58 deletions(-) diff --git a/t/adaptive_width.t b/t/adaptive_width.t index 7a0baa7527..14ffd3dd4c 100644 --- a/t/adaptive_width.t +++ b/t/adaptive_width.t @@ -32,6 +32,7 @@ sub scale_points (@) { map [scale $_->[X], scale $_->[Y]], @_ } expolygon => $expolygon, )); my $config = Slic3r::Config::Full->new; + $config->set('thin_walls_overlap',0); my $loops = Slic3r::ExtrusionPath::Collection->new; my $gap_fill = Slic3r::ExtrusionPath::Collection->new; my $fill_surfaces = Slic3r::Surface::Collection->new; diff --git a/xs/src/libslic3r/MedialAxis.cpp b/xs/src/libslic3r/MedialAxis.cpp index 4f7b025656..60b00701c9 100644 --- a/xs/src/libslic3r/MedialAxis.cpp +++ b/xs/src/libslic3r/MedialAxis.cpp @@ -346,15 +346,15 @@ add_point_same_percent(ThickPolyline* pattern, ThickPolyline* to_modify) new_width += to_modify->width[idx_other] * (percent_dist); to_modify->width.insert(to_modify->width.begin() + idx_other, new_width); to_modify->points.insert(to_modify->points.begin() + idx_other, - to_modify->points[idx_other - 1].interpolate(percent_dist, to_modify->points[idx_other])); + to_modify->points[idx_other - 1].interpolate(percent_dist, to_modify->points[idx_other])); } } } /// find the nearest angle in the contour (or 2 nearest if it's difficult to choose) -/// return 1 for an angle of 90° and 0 for an angle of 0° or 180° +/// return 1 for an angle of 90° and 0 for an angle of 0° or 180° /// find the nearest angle in the contour (or 2 nearest if it's difficult to choose) -/// return 1 for an angle of 90° and 0 for an angle of 0° or 180° +/// return 1 for an angle of 90° and 0 for an angle of 0° or 180° double get_coeff_from_angle_countour(Point &point, const ExPolygon &contour, coord_t min_dist_between_point) { double nearest_dist = point.distance_to(contour.contour.points.front()); @@ -405,7 +405,7 @@ get_coeff_from_angle_countour(Point &point, const ExPolygon &contour, coord_t mi //compute angle angle = point_nearest.ccw_angle(point_before, point_after); if (angle >= PI) angle = 2 * PI - angle; // smaller angle - //compute the diff from 90° + //compute the diff from 90° angle = abs(angle - PI / 2); if (point_near.coincides_with(point_nearest) && max(nearest_dist, near_dist) + SCALED_EPSILON < point_nearest.distance_to(point_near)) { //not only nearest @@ -460,11 +460,11 @@ MedialAxis::fusion_curve(ThickPolylines &pp) //compute angle double coeff_contour_angle = this->expolygon.contour.points[closest_point_idx].ccw_angle(this->expolygon.contour.points[prev_idx], this->expolygon.contour.points[next_idx]); if (coeff_contour_angle >= PI) coeff_contour_angle = 2 * PI - coeff_contour_angle; // smaller angle - //compute the diff from 90° + //compute the diff from 90° coeff_contour_angle = abs(coeff_contour_angle - PI / 2); - // look if other end is a cross point with almost 90° angle + // look if other end is a cross point with almost 90° angle double sum_dot = 0; double min_dot = 0; // look if other end is a cross point with multiple other branch @@ -676,7 +676,7 @@ MedialAxis::extends_line(ThickPolyline& polyline, const ExPolygons& anchors, con break; } } - if (!is_in_anchor) std::cout << "not in anchor:\n"; + //if (!is_in_anchor) std::cout << "not in anchor:\n"; if (!is_in_anchor) return; new_bound = line.b; } @@ -756,14 +756,7 @@ MedialAxis::main_fusion(ThickPolylines& pp) } else { continue; } - //std::cout << " try : " << i << ":" << j << " : " << - // (polyline.points.size() < 2 && other.points.size() < 2) << - // (!polyline.endpoints.second || !other.endpoints.second) << - // ((polyline.points.back().distance_to(other.points.back()) - // + (polyline.width.back() + other.width.back()) / 4) - // > max_width*1.05) << - // (abs(polyline.length() - other.length()) > max_width) << "\n"; - + //// mergeable tests if (polyline.points.size() < 2 && other.points.size() < 2) continue; if (!polyline.endpoints.second || !other.endpoints.second) continue; @@ -787,10 +780,6 @@ MedialAxis::main_fusion(ThickPolylines& pp) if (other.width.back() == 0) { coeffSizeOtherJ = 0.1 + 0.9*get_coeff_from_angle_countour(other.points.back(), this->expolygon, min(min_width, (coord_t)(polyline.length() / 2))); } - //std::cout << " try2 : " << i << ":" << j << " : " - // << (abs(polyline.length()*coeffSizePolyI - other.length()*coeffSizeOtherJ) > max_width / 2) - // << (abs(polyline.length()*coeffSizePolyI - other.length()*coeffSizeOtherJ) > max_width) - // << "\n"; if (abs(polyline.length()*coeffSizePolyI - other.length()*coeffSizeOtherJ) > max_width / 2) continue; @@ -800,15 +789,14 @@ MedialAxis::main_fusion(ThickPolylines& pp) // Get the branch/line in wich we may merge, if possible // with that, we can decide what is important, and how we can merge that. - // angle_poly - angle_candi =90° => one is useless + // angle_poly - angle_candi =90° => one is useless // both angle are equal => both are useful with same strength // ex: Y => | both are useful to crete a nice line - // ex2: TTTTT => ----- these 90° useless lines should be discarded + // ex2: TTTTT => ----- these 90° useless lines should be discarded bool find_main_branch = false; size_t biggest_main_branch_id = 0; coord_t biggest_main_branch_length = 0; for (size_t k = 0; k < pp.size(); ++k) { - //std::cout << "try to find main : " << k << " ? " << i << " " << j << " "; if (k == i || k == j) continue; ThickPolyline& main = pp[k]; if (polyline.first_point().coincides_with(main.last_point())) { @@ -837,7 +825,6 @@ MedialAxis::main_fusion(ThickPolylines& pp) // nothing -> it's impossible! dot_poly_branch = 0.707; dot_candidate_branch = 0.707; - //std::cout << "no main branch... impossible!!\n"; } else if (!find_main_branch && ( (pp[biggest_main_branch_id].length() < polyline.length() && (polyline.width.back() != 0 || pp[biggest_main_branch_id].width.back() ==0)) || (pp[biggest_main_branch_id].length() < other.length() && (other.width.back() != 0 || pp[biggest_main_branch_id].width.back() == 0)))) { @@ -869,8 +856,6 @@ MedialAxis::main_fusion(ThickPolylines& pp) } } if (best_candidate != nullptr) { - //idf++; - //std::cout << " == fusion " << id <<" : "<< idf << " ==\n"; // delete very near points remove_point_too_near(&polyline); remove_point_too_near(best_candidate); @@ -881,7 +866,7 @@ MedialAxis::main_fusion(ThickPolylines& pp) //get the angle of the nearest points of the contour to see : _| (good) \_ (average) __(bad) //sqrt because the result are nicer this way: don't over-penalize /_ angles - //TODO: try if we can achieve a better result if we use a different algo if the angle is <90° + //TODO: try if we can achieve a better result if we use a different algo if the angle is <90° const double coeff_angle_poly = (coeff_angle_cache.find(polyline.points.back()) != coeff_angle_cache.end()) ? coeff_angle_cache[polyline.points.back()] : (get_coeff_from_angle_countour(polyline.points.back(), this->expolygon, min(min_width, (coord_t)(polyline.length() / 2)))); @@ -891,29 +876,12 @@ MedialAxis::main_fusion(ThickPolylines& pp) //this will encourage to follow the curve, a little, because it's shorter near the center //without that, it tends to go to the outter rim. - //std::cout << " max(polyline.length(), best_candidate->length())=" << max(polyline.length(), best_candidate->length()) - // << ", polyline.length()=" << polyline.length() - // << ", best_candidate->length()=" << best_candidate->length() - // << ", polyline.length() / max=" << (polyline.length() / max(polyline.length(), best_candidate->length())) - // << ", best_candidate->length() / max=" << (best_candidate->length() / max(polyline.length(), best_candidate->length())) - // << "\n"; double weight_poly = 2 - (polyline.length() / max(polyline.length(), best_candidate->length())); double weight_candi = 2 - (best_candidate->length() / max(polyline.length(), best_candidate->length())); weight_poly *= coeff_angle_poly; weight_candi *= coeff_angle_candi; const double coeff_poly = (dot_poly_branch * weight_poly) / (dot_poly_branch * weight_poly + dot_candidate_branch * weight_candi); const double coeff_candi = 1.0 - coeff_poly; - //std::cout << "coeff_angle_poly=" << coeff_angle_poly - // << ", coeff_angle_candi=" << coeff_angle_candi - // << ", weight_poly=" << (2 - (polyline.length() / max(polyline.length(), best_candidate->length()))) - // << ", weight_candi=" << (2 - (best_candidate->length() / max(polyline.length(), best_candidate->length()))) - // << ", sumpoly=" << weight_poly - // << ", sumcandi=" << weight_candi - // << ", dot_poly_branch=" << dot_poly_branch - // << ", dot_candidate_branch=" << dot_candidate_branch - // << ", coeff_poly=" << coeff_poly - // << ", coeff_candi=" << coeff_candi - // << "\n"; //iterate the points // as voronoi should create symetric thing, we can iterate synchonously size_t idx_point = 1; @@ -931,8 +899,6 @@ MedialAxis::main_fusion(ThickPolylines& pp) double value_from_dist = 2 * polyline.points[idx_point].distance_to(best_candidate->points[idx_point]); value_from_dist *= sqrt(min(dot_poly_branch, dot_candidate_branch) / max(dot_poly_branch, dot_candidate_branch)); polyline.width[idx_point] = value_from_current_width + value_from_dist; - //std::cout << "width:" << polyline.width[idx_point] << " = " << value_from_current_width << " + " << value_from_dist - // << " (<" << max_width << " && " << (bounds.contour.closest_point(polyline.points[idx_point])->distance_to(polyline.points[idx_point]) * 2.1)<<")\n"; //failsafes if (polyline.width[idx_point] > max_width) polyline.width[idx_point] = max_width; @@ -999,15 +965,6 @@ MedialAxis::main_fusion(ThickPolylines& pp) } pp.erase(pp.begin() + best_idx); - //{ - // stringstream stri; - // stri << "medial_axis_2.0_aft_fus_" << id << "_" << idf << ".svg"; - // SVG svg(stri.str()); - // svg.draw(bounds); - // svg.draw(this->expolygon); - // svg.draw(pp); - // svg.Close(); - //} changes = true; break; } @@ -1537,10 +1494,8 @@ discretize_variable_width(const ThickPolylines &polylines, ExtrusionRole role, F // of segments, and any pruning shall be performed before we apply this tolerance const double tolerance = 4*SCALED_RESOLUTION;//scale_(0.05); - int id_line = 0; ExtrusionEntityCollection coll; for (ThickPolylines::const_iterator p = polylines.begin(); p != polylines.end(); ++p) { - id_line++; ExtrusionPaths paths; ExtrusionPath path(role); ThickLines lines = p->thicklines(); @@ -1594,7 +1549,7 @@ discretize_variable_width(const ThickPolylines &polylines, ExtrusionRole role, F } if (path.polyline.points.empty()) { - flow.width = unscale(line.a_width) + flow.height * (1. - 0.25 * PI); + flow.width = unscale(line.a_width); #ifdef SLIC3R_DEBUG printf(" filling %f gap\n", flow.width); #endif @@ -1609,7 +1564,7 @@ discretize_variable_width(const ThickPolylines &polylines, ExtrusionRole role, F path.polyline.append(line.a); path.polyline.append(line.b); } else { - thickness_delta = fabs(flow.scaled_spacing() - line.a_width); + thickness_delta = fabs(flow.scaled_width() - line.a_width); if (thickness_delta <= tolerance/2) { // the width difference between this line and the current flow width is // within the accepted tolerance From f06491cda7c9f62e727893ad5ac62f93f1730cd1 Mon Sep 17 00:00:00 2001 From: supermerill Date: Mon, 18 Feb 2019 12:02:45 +0100 Subject: [PATCH 23/25] medial axis: debug * concatenate_polylines_with_crossing: now connected the good end with the other good end * discretize_variable_width: now use TP.width->flow.spacing for gap-fill and TP.width->flow.width if possible for thin-walls. * bugfix thin wall (medial axis): use correct temp variables for main_fusion. --- xs/src/libslic3r/MedialAxis.cpp | 223 +++++++++++++++++++------------- 1 file changed, 130 insertions(+), 93 deletions(-) diff --git a/xs/src/libslic3r/MedialAxis.cpp b/xs/src/libslic3r/MedialAxis.cpp index 60b00701c9..88885912c3 100644 --- a/xs/src/libslic3r/MedialAxis.cpp +++ b/xs/src/libslic3r/MedialAxis.cpp @@ -45,7 +45,6 @@ MedialAxis::polyline_from_voronoi(const Lines& voronoi_edges, ThickPolylines* po } */ - // typedef const VD::vertex_type vert_t; typedef const VD::edge_type edge_t; // collect valid edges (i.e. prune those not belonging to MAT) @@ -296,12 +295,12 @@ remove_point_too_near(ThickPolyline* to_reduce) const coord_t smallest = SCALED_EPSILON * 2; size_t id = 1; while (id < to_reduce->points.size() - 1) { - size_t newdist = min(to_reduce->points[id].distance_to(to_reduce->points[id - 1]) + coord_t newdist = (coord_t)std::min(to_reduce->points[id].distance_to(to_reduce->points[id - 1]) , to_reduce->points[id].distance_to(to_reduce->points[id + 1])); if (newdist < smallest) { to_reduce->points.erase(to_reduce->points.begin() + id); to_reduce->width.erase(to_reduce->width.begin() + id); - newdist = to_reduce->points[id].distance_to(to_reduce->points[id - 1]); + newdist = (coord_t)to_reduce->points[id].distance_to(to_reduce->points[id - 1]); //if you removed a point, it check if the next one isn't too near from the previous one. // if not, it bypass it. if (newdist > smallest) { @@ -352,9 +351,9 @@ add_point_same_percent(ThickPolyline* pattern, ThickPolyline* to_modify) } /// find the nearest angle in the contour (or 2 nearest if it's difficult to choose) -/// return 1 for an angle of 90° and 0 for an angle of 0° or 180° +/// return 1 for an angle of 90° and 0 for an angle of 0° or 180° /// find the nearest angle in the contour (or 2 nearest if it's difficult to choose) -/// return 1 for an angle of 90° and 0 for an angle of 0° or 180° +/// return 1 for an angle of 90° and 0 for an angle of 0° or 180° double get_coeff_from_angle_countour(Point &point, const ExPolygon &contour, coord_t min_dist_between_point) { double nearest_dist = point.distance_to(contour.contour.points.front()); @@ -405,13 +404,13 @@ get_coeff_from_angle_countour(Point &point, const ExPolygon &contour, coord_t mi //compute angle angle = point_nearest.ccw_angle(point_before, point_after); if (angle >= PI) angle = 2 * PI - angle; // smaller angle - //compute the diff from 90° + //compute the diff from 90° angle = abs(angle - PI / 2); - if (point_near.coincides_with(point_nearest) && max(nearest_dist, near_dist) + SCALED_EPSILON < point_nearest.distance_to(point_near)) { + if (point_near.coincides_with(point_nearest) && std::max(nearest_dist, near_dist) + SCALED_EPSILON < point_nearest.distance_to(point_near)) { //not only nearest Point point_before = id_near == 0 ? contour.contour.points.back() : contour.contour.points[id_near - 1]; Point point_after = id_near == contour.contour.points.size() - 1 ? contour.contour.points.front() : contour.contour.points[id_near + 1]; - double angle2 = min(point_nearest.ccw_angle(point_before, point_after), point_nearest.ccw_angle(point_after, point_before)); + double angle2 = std::min(point_nearest.ccw_angle(point_before, point_after), point_nearest.ccw_angle(point_after, point_before)); angle2 = abs(angle - PI / 2); angle = (angle + angle2) / 2; } @@ -452,23 +451,23 @@ MedialAxis::fusion_curve(ThickPolylines &pp) size_t prev_idx = closest_point_idx == 0 ? this->expolygon.contour.points.size() - 1 : closest_point_idx - 1; size_t next_idx = closest_point_idx == this->expolygon.contour.points.size() - 1 ? 0 : closest_point_idx + 1; double mindot = 1; - mindot = min(mindot, abs(dot(Line(polyline.points[polyline.points.size() - 1], polyline.points[polyline.points.size() - 2]), + mindot = std::min(mindot, abs(dot(Line(polyline.points[polyline.points.size() - 1], polyline.points[polyline.points.size() - 2]), (Line(this->expolygon.contour.points[closest_point_idx], this->expolygon.contour.points[prev_idx]))))); - mindot = min(mindot, abs(dot(Line(polyline.points[polyline.points.size() - 1], polyline.points[polyline.points.size() - 2]), + mindot = std::min(mindot, abs(dot(Line(polyline.points[polyline.points.size() - 1], polyline.points[polyline.points.size() - 2]), (Line(this->expolygon.contour.points[closest_point_idx], this->expolygon.contour.points[next_idx]))))); //compute angle double coeff_contour_angle = this->expolygon.contour.points[closest_point_idx].ccw_angle(this->expolygon.contour.points[prev_idx], this->expolygon.contour.points[next_idx]); if (coeff_contour_angle >= PI) coeff_contour_angle = 2 * PI - coeff_contour_angle; // smaller angle - //compute the diff from 90° + //compute the diff from 90° coeff_contour_angle = abs(coeff_contour_angle - PI / 2); - // look if other end is a cross point with almost 90° angle + // look if other end is a cross point with almost 90° angle double sum_dot = 0; double min_dot = 0; // look if other end is a cross point with multiple other branch - vector crosspoint; + std::vector crosspoint; for (size_t j = 0; j < pp.size(); ++j) { if (j == i) continue; ThickPolyline& other = pp[j]; @@ -476,12 +475,12 @@ MedialAxis::fusion_curve(ThickPolylines &pp) other.reverse(); crosspoint.push_back(j); double dot_temp = dot(Line(polyline.points[0], polyline.points[1]), (Line(other.points[0], other.points[1]))); - min_dot = min(min_dot, abs(dot_temp)); + min_dot = std::min(min_dot, abs(dot_temp)); sum_dot += dot_temp; } else if (polyline.first_point().coincides_with(other.first_point())) { crosspoint.push_back(j); double dot_temp = dot(Line(polyline.points[0], polyline.points[1]), (Line(other.points[0], other.points[1]))); - min_dot = min(min_dot, abs(dot_temp)); + min_dot = std::min(min_dot, abs(dot_temp)); sum_dot += dot_temp; } } @@ -541,11 +540,11 @@ MedialAxis::fusion_corners(ThickPolylines &pp) if (polyline.width.back() > 0) continue; //check my length is small - coord_t length = polyline.length(); + coord_t length = (coord_t)polyline.length(); if (length > max_width) continue; // look if other end is a cross point with multiple other branch - vector crosspoint; + std::vector crosspoint; for (size_t j = 0; j < pp.size(); ++j) { if (j == i) continue; ThickPolyline& other = pp[j]; @@ -572,8 +571,11 @@ MedialAxis::fusion_corners(ThickPolylines &pp) //FIXME: also pull (a bit less) points that are near to this one. // if true, pull it a bit, depends on my size, the dot?, and the coeff at my 0-end (~14% for a square, almost 0 for a gentle curve) - coord_t length_pull = polyline.length(); - length_pull *= 0.144 * get_coeff_from_angle_countour(polyline.points.back(), this->expolygon, min(min_width, (coord_t)(polyline.length() / 2))); + coord_t length_pull = (coord_t)polyline.length(); + length_pull *= (coord_t)( 0.144 * get_coeff_from_angle_countour( + polyline.points.back(), + this->expolygon, + std::min(min_width, (coord_t)(polyline.length() / 2)))); //compute dir Vectorf pull_direction(polyline.points[1].x - polyline.points[0].x, polyline.points[1].y - polyline.points[0].y); @@ -676,7 +678,6 @@ MedialAxis::extends_line(ThickPolyline& polyline, const ExPolygons& anchors, con break; } } - //if (!is_in_anchor) std::cout << "not in anchor:\n"; if (!is_in_anchor) return; new_bound = line.b; } @@ -717,7 +718,7 @@ MedialAxis::main_fusion(ThickPolylines& pp) { bool changes = true; - map coeff_angle_cache; + std::map coeff_angle_cache; while (changes) { concatThickPolylines(pp); //reoder pp by length (ascending) It's really important to do that to avoid building the line from the width insteand of the length @@ -774,25 +775,25 @@ MedialAxis::main_fusion(ThickPolylines& pp) //test if we don't merge with something too different and without any relevance. double coeffSizePolyI = 1; if (polyline.width.back() == 0) { - coeffSizePolyI = 0.1 + 0.9*get_coeff_from_angle_countour(polyline.points.back(), this->expolygon, min(min_width, (coord_t)(polyline.length() / 2))); + coeffSizePolyI = 0.1 + 0.9*get_coeff_from_angle_countour(polyline.points.back(), this->expolygon, std::min(min_width, (coord_t)(polyline.length() / 2))); } double coeffSizeOtherJ = 1; if (other.width.back() == 0) { - coeffSizeOtherJ = 0.1 + 0.9*get_coeff_from_angle_countour(other.points.back(), this->expolygon, min(min_width, (coord_t)(polyline.length() / 2))); + coeffSizeOtherJ = 0.1 + 0.9*get_coeff_from_angle_countour(other.points.back(), this->expolygon, std::min(min_width, (coord_t)(polyline.length() / 2))); } if (abs(polyline.length()*coeffSizePolyI - other.length()*coeffSizeOtherJ) > max_width / 2) continue; //compute angle to see if it's better than previous ones (straighter = better). //we need to add how strait we are from our main. - float test_dot = dot(polyline.lines().front(), other.lines().front()); + float test_dot = (float)(dot(polyline.lines().front(), other.lines().front())); // Get the branch/line in wich we may merge, if possible // with that, we can decide what is important, and how we can merge that. - // angle_poly - angle_candi =90° => one is useless + // angle_poly - angle_candi =90° => one is useless // both angle are equal => both are useful with same strength // ex: Y => | both are useful to crete a nice line - // ex2: TTTTT => ----- these 90° useless lines should be discarded + // ex2: TTTTT => ----- these 90° useless lines should be discarded bool find_main_branch = false; size_t biggest_main_branch_id = 0; coord_t biggest_main_branch_length = 0; @@ -805,14 +806,14 @@ MedialAxis::main_fusion(ThickPolylines& pp) find_main_branch = true; else if (biggest_main_branch_length < main.length()) { biggest_main_branch_id = k; - biggest_main_branch_length = main.length(); + biggest_main_branch_length = (coord_t)main.length(); } } else if (polyline.first_point().coincides_with(main.first_point())) { if (!main.endpoints.second) find_main_branch = true; else if (biggest_main_branch_length < main.length()) { biggest_main_branch_id = k; - biggest_main_branch_length = main.length(); + biggest_main_branch_length = (coord_t)main.length(); } } if (find_main_branch) { @@ -821,38 +822,44 @@ MedialAxis::main_fusion(ThickPolylines& pp) break; } } + double dot_poly_branch_test = 0.707; + double dot_candidate_branch_test = 0.707; if (!find_main_branch && biggest_main_branch_length == 0) { // nothing -> it's impossible! - dot_poly_branch = 0.707; - dot_candidate_branch = 0.707; + dot_poly_branch_test = 0.707; + dot_candidate_branch_test = 0.707; } else if (!find_main_branch && ( (pp[biggest_main_branch_id].length() < polyline.length() && (polyline.width.back() != 0 || pp[biggest_main_branch_id].width.back() ==0)) || (pp[biggest_main_branch_id].length() < other.length() && (other.width.back() != 0 || pp[biggest_main_branch_id].width.back() == 0)))) { //the main branch should have no endpoint or be bigger! //here, it have an endpoint, and is not the biggest -> bad! + //std::cout << "he main branch should have no endpoint or be bigger! here, it have an endpoint, and is not the biggest -> bad!\n"; continue; } else { //compute the dot (biggest_main_branch_id) - dot_poly_branch = -dot(Line(polyline.points[0], polyline.points[1]), Line(pp[biggest_main_branch_id].points[0], pp[biggest_main_branch_id].points[1])); - dot_candidate_branch = -dot(Line(other.points[0], other.points[1]), Line(pp[biggest_main_branch_id].points[0], pp[biggest_main_branch_id].points[1])); - if (dot_poly_branch < 0) dot_poly_branch = 0; - if (dot_candidate_branch < 0) dot_candidate_branch = 0; + dot_poly_branch_test = -dot(Line(polyline.points[0], polyline.points[1]), Line(pp[biggest_main_branch_id].points[0], pp[biggest_main_branch_id].points[1])); + dot_candidate_branch_test = -dot(Line(other.points[0], other.points[1]), Line(pp[biggest_main_branch_id].points[0], pp[biggest_main_branch_id].points[1])); + if (dot_poly_branch_test < 0) dot_poly_branch_test = 0; + if (dot_candidate_branch_test < 0) dot_candidate_branch_test = 0; if (pp[biggest_main_branch_id].width.back()>0) - test_dot += 2 * dot_poly_branch ; + test_dot += 2 * (float)dot_poly_branch; } //test if it's useful to merge or not //ie, don't merge 'T' but ok for 'Y', merge only lines of not disproportionate different length (ratio max: 4) (or they are both with 0-width end) - if (dot_poly_branch < 0.1 || dot_candidate_branch < 0.1 || + if (dot_poly_branch_test < 0.1 || dot_candidate_branch_test < 0.1 || ( ((polyline.length()>other.length() ? polyline.length() / other.length() : other.length() / polyline.length()) > 4) && !(polyline.width.back() == 0 && other.width.back()==0) - ) ){ + )) { + //std::cout << "not useful to merge\n"; continue; } if (test_dot > best_dot) { best_candidate = &other; best_idx = j; best_dot = test_dot; + dot_poly_branch = dot_poly_branch_test; + dot_candidate_branch = dot_candidate_branch_test; } } if (best_candidate != nullptr) { @@ -866,18 +873,18 @@ MedialAxis::main_fusion(ThickPolylines& pp) //get the angle of the nearest points of the contour to see : _| (good) \_ (average) __(bad) //sqrt because the result are nicer this way: don't over-penalize /_ angles - //TODO: try if we can achieve a better result if we use a different algo if the angle is <90° + //TODO: try if we can achieve a better result if we use a different algo if the angle is <90° const double coeff_angle_poly = (coeff_angle_cache.find(polyline.points.back()) != coeff_angle_cache.end()) ? coeff_angle_cache[polyline.points.back()] - : (get_coeff_from_angle_countour(polyline.points.back(), this->expolygon, min(min_width, (coord_t)(polyline.length() / 2)))); + : (get_coeff_from_angle_countour(polyline.points.back(), this->expolygon, std::min(min_width, (coord_t)(polyline.length() / 2)))); const double coeff_angle_candi = (coeff_angle_cache.find(best_candidate->points.back()) != coeff_angle_cache.end()) ? coeff_angle_cache[best_candidate->points.back()] - : (get_coeff_from_angle_countour(best_candidate->points.back(), this->expolygon, min(min_width, (coord_t)(best_candidate->length() / 2)))); + : (get_coeff_from_angle_countour(best_candidate->points.back(), this->expolygon, std::min(min_width, (coord_t)(best_candidate->length() / 2)))); //this will encourage to follow the curve, a little, because it's shorter near the center //without that, it tends to go to the outter rim. - double weight_poly = 2 - (polyline.length() / max(polyline.length(), best_candidate->length())); - double weight_candi = 2 - (best_candidate->length() / max(polyline.length(), best_candidate->length())); + double weight_poly = 2 - (polyline.length() / std::max(polyline.length(), best_candidate->length())); + double weight_candi = 2 - (best_candidate->length() / std::max(polyline.length(), best_candidate->length())); weight_poly *= coeff_angle_poly; weight_candi *= coeff_angle_candi; const double coeff_poly = (dot_poly_branch * weight_poly) / (dot_poly_branch * weight_poly + dot_candidate_branch * weight_candi); @@ -885,7 +892,7 @@ MedialAxis::main_fusion(ThickPolylines& pp) //iterate the points // as voronoi should create symetric thing, we can iterate synchonously size_t idx_point = 1; - while (idx_point < min(polyline.points.size(), best_candidate->points.size())) { + while (idx_point < std::min(polyline.points.size(), best_candidate->points.size())) { //fusion polyline.points[idx_point].x = polyline.points[idx_point].x * coeff_poly + best_candidate->points[idx_point].x * coeff_candi; polyline.points[idx_point].y = polyline.points[idx_point].y * coeff_poly + best_candidate->points[idx_point].y * coeff_candi; @@ -894,15 +901,15 @@ MedialAxis::main_fusion(ThickPolylines& pp) // This formula is what works the best, even if it's not perfect (created empirically). 0->3% error on a gap fill on some tests. //If someone find an other formula based on the properties of the voronoi algorithm used here, and it works better, please use it. //or maybe just use the distance to nearest edge in bounds... - double value_from_current_width = 0.5*polyline.width[idx_point] * dot_poly_branch / max(dot_poly_branch, dot_candidate_branch); - value_from_current_width += 0.5*best_candidate->width[idx_point] * dot_candidate_branch / max(dot_poly_branch, dot_candidate_branch); + double value_from_current_width = 0.5*polyline.width[idx_point] * dot_poly_branch / std::max(dot_poly_branch, dot_candidate_branch); + value_from_current_width += 0.5*best_candidate->width[idx_point] * dot_candidate_branch / std::max(dot_poly_branch, dot_candidate_branch); double value_from_dist = 2 * polyline.points[idx_point].distance_to(best_candidate->points[idx_point]); - value_from_dist *= sqrt(min(dot_poly_branch, dot_candidate_branch) / max(dot_poly_branch, dot_candidate_branch)); + value_from_dist *= sqrt(std::min(dot_poly_branch, dot_candidate_branch) / std::max(dot_poly_branch, dot_candidate_branch)); polyline.width[idx_point] = value_from_current_width + value_from_dist; //failsafes if (polyline.width[idx_point] > max_width) polyline.width[idx_point] = max_width; - const coord_t max_width_contour = bounds.contour.closest_point(polyline.points[idx_point])->distance_to(polyline.points[idx_point]) * 2.1; + const coord_t max_width_contour = (coord_t) bounds.contour.closest_point(polyline.points[idx_point])->distance_to(polyline.points[idx_point]) * 2.1; if (polyline.width[idx_point] > max_width_contour) polyline.width[idx_point] = max_width_contour; @@ -970,7 +977,6 @@ MedialAxis::main_fusion(ThickPolylines& pp) } } } - if (changes) concatThickPolylines(pp); } void @@ -1023,7 +1029,7 @@ MedialAxis::remove_too_thin_extrusion(ThickPolylines& pp) polyline.width.erase(polyline.width.end() - 1); changes = true; } - //remove empty lines and bits that comes from a "main line" + //remove points and bits that comes from a "main line" if (polyline.points.size() < 2 || (changes && polyline.length() < max_width && polyline.points.size() ==2)) { //remove self if too small pp.erase(pp.begin() + i); @@ -1061,21 +1067,24 @@ MedialAxis::concatenate_polylines_with_crossing(ThickPolylines& pp) if (j == i) continue; ThickPolyline& other = pp[j]; if (other.endpoints.first && other.endpoints.second) continue; - + bool me_reverse = false; + bool other_reverse = false; if (polyline.last_point().coincides_with(other.last_point())) { - other.reverse(); + other_reverse = true; } else if (polyline.first_point().coincides_with(other.last_point())) { - polyline.reverse(); - other.reverse(); + me_reverse = true; + other_reverse = true; } else if (polyline.first_point().coincides_with(other.first_point())) { - polyline.reverse(); + me_reverse = true; } else if (!polyline.last_point().coincides_with(other.first_point())) { continue; } - Pointf v_poly(polyline.lines().back().vector().x, polyline.lines().back().vector().y); + Pointf v_poly(me_reverse ? polyline.lines().front().vector().x : polyline.lines().back().vector().x, + me_reverse ? polyline.lines().front().vector().y : 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); + Pointf v_other(other_reverse ? other.lines().back().vector().x : other.lines().front().vector().x, + other_reverse ? other.lines().back().vector().y : 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) { @@ -1084,8 +1093,16 @@ MedialAxis::concatenate_polylines_with_crossing(ThickPolylines& pp) best_dot = other_dot; } } - if (best_candidate != nullptr) { - //intersections may create ever-ertusion because the included circle can be a bit larger. We have to make it short again if needed. + if (best_candidate != nullptr && best_candidate->points.size() > 1) { + if (polyline.last_point().coincides_with(best_candidate->last_point())) { + best_candidate->reverse(); + } else if (polyline.first_point().coincides_with(best_candidate->last_point())) { + polyline.reverse(); + best_candidate->reverse(); + } else if (polyline.first_point().coincides_with(best_candidate->first_point())) { + polyline.reverse(); + } + //intersections may create over-extrusion because the included circle can be a bit larger. We have to make it short again if needed. if (polyline.points.size() > 1 && best_candidate->points.size() > 1 && polyline.width.back() > polyline.width[polyline.width.size() - 2] && polyline.width.back() > best_candidate->width[1]) { @@ -1196,7 +1213,7 @@ MedialAxis::ensure_not_overextrude(ThickPolylines& pp) double surface = 0; double volume = 0; for (ThickPolyline& polyline : pp) { - for (ThickLine l : polyline.thicklines()) { + for (ThickLine &l : polyline.thicklines()) { surface += l.length() * (l.a_width + l.b_width) / 2; double width_mean = (l.a_width + l.b_width) / 2; volume += height * (width_mean - height * (1. - 0.25 * PI)) * l.length(); @@ -1244,7 +1261,7 @@ MedialAxis::simplify_polygon_frontier() size_t next_i = i == simplified_poly.contour.points.size() - 1 ? 0 : (i + 1); const Point* closest = bounds.contour.closest_point(p_check); if (closest != nullptr && closest->distance_to(p_check) + SCALED_EPSILON - < min(p_check.distance_to(simplified_poly.contour.points[prev_i]), p_check.distance_to(simplified_poly.contour.points[next_i])) / 2) { + < std::min(p_check.distance_to(simplified_poly.contour.points[prev_i]), p_check.distance_to(simplified_poly.contour.points[next_i])) / 2) { p_check.x = closest->x; p_check.y = closest->y; need_intersect = true; @@ -1263,15 +1280,19 @@ MedialAxis::simplify_polygon_frontier() } } } - if (!simplified_poly.contour.points.empty()) simplified_poly.remove_point_too_near(SCALED_RESOLUTION); + + if (!simplified_poly.contour.points.empty()) + simplified_poly.remove_point_too_near((coord_t)SCALED_RESOLUTION); return simplified_poly; } +/// Grow the extrusion to at least nozzle_diameter*1.05 (lowest safe extrusion width) +/// Do not grow points inside the anchor. void MedialAxis::grow_to_nozzle_diameter(ThickPolylines& pp, const ExPolygons& anchors) { //ensure the width is not lower than 0.4. for (ThickPolyline& polyline : pp) { - for (size_t i = 0; i < polyline.points.size(); ++i) { + for (int i = 0; i < polyline.points.size(); ++i) { bool is_anchored = false; for (const ExPolygon &poly : anchors) { if (poly.contains(polyline.points[i])) { @@ -1279,14 +1300,16 @@ MedialAxis::grow_to_nozzle_diameter(ThickPolylines& pp, const ExPolygons& anchor break; } } - if (!is_anchored && polyline.width[i]nozzle_diameter * 0.1; + // minimum size of the taper: be sure to extrude at least the "round edges" of the extrusion (0-spacing extrusion). + const coord_t min_size = std::max(this->nozzle_diameter * 0.1, this->height * (1. - 0.25 * PI)); const coordf_t length = std::min(this->anchor_size, (this->nozzle_diameter - min_size) / 2); if (length <= SCALED_RESOLUTION) return; //ensure the width is not lower than 0.4. @@ -1297,7 +1320,7 @@ MedialAxis::taper_ends(ThickPolylines& pp) { coord_t current_dist = min_size; coord_t last_dist = min_size; for (size_t i = 1; i length) { //create a new point if not near enough if (current_dist > polyline.width[i] + SCALED_RESOLUTION) { @@ -1317,7 +1340,7 @@ MedialAxis::taper_ends(ThickPolylines& pp) { coord_t current_dist = min_size; coord_t last_dist = min_size; for (size_t i = 1; i length) { //create new point if not near enough if (current_dist > polyline.width[back_idx - i] + SCALED_RESOLUTION) { @@ -1485,7 +1508,6 @@ MedialAxis::build(ThickPolylines* polylines_out) } - ExtrusionEntityCollection discretize_variable_width(const ThickPolylines &polylines, ExtrusionRole role, Flow flow) { @@ -1495,10 +1517,10 @@ discretize_variable_width(const ThickPolylines &polylines, ExtrusionRole role, F const double tolerance = 4*SCALED_RESOLUTION;//scale_(0.05); ExtrusionEntityCollection coll; - for (ThickPolylines::const_iterator p = polylines.begin(); p != polylines.end(); ++p) { + for (const ThickPolyline &p : polylines) { ExtrusionPaths paths; ExtrusionPath path(role); - ThickLines lines = p->thicklines(); + ThickLines lines = p.thicklines(); for (int i = 0; i < (int)lines.size(); ++i) { ThickLine& line = lines[i]; @@ -1506,9 +1528,11 @@ discretize_variable_width(const ThickPolylines &polylines, ExtrusionRole role, F const coordf_t line_len = line.length(); if (line_len < SCALED_EPSILON) continue; + assert(line.a_width >= 0); + assert(line.b_width >= 0); double thickness_delta = fabs(line.a_width - line.b_width); if (thickness_delta > tolerance && ceil(thickness_delta / tolerance) > 2) { - const size_t segments = 1 + ceil(thickness_delta / tolerance); + const size_t segments = 1 + std::min(16000.0, ceil(thickness_delta / tolerance)); Points pp; std::vector width; { @@ -1518,8 +1542,7 @@ discretize_variable_width(const ThickPolylines &polylines, ExtrusionRole role, F width.push_back(line.a_width*(1 - percent_width) + line.b_width*percent_width); } pp.push_back(line.b); - width.push_back(line.b_width); - + assert(pp.size() == segments + 1); assert(width.size() == segments); } @@ -1527,7 +1550,7 @@ discretize_variable_width(const ThickPolylines &polylines, ExtrusionRole role, F // delete this line and insert new ones lines.erase(lines.begin() + i); for (size_t j = 0; j < segments; ++j) { - ThickLine new_line(pp[j], pp[j+1]); + ThickLine new_line(pp[j], pp[j + 1]); new_line.a_width = width[j]; new_line.b_width = width[j]; lines.insert(lines.begin() + i + j, new_line); @@ -1547,45 +1570,58 @@ discretize_variable_width(const ThickPolylines &polylines, ExtrusionRole role, F --i; continue; } - + //gapfill : we want to be able to fill the voids (touching the perimeters), so the spacing is what we want. + //thinwall: we want the extrusion to not go out of the polygon, so the width is what we want. + // but we can't extrude with a negative spacing, so we have to gradually fall back to spacing if the width is too small. + + // default: extrude a thin wall that doesn't go outside of the specified width. + coordf_t wanted_width = unscale(line.a_width); + if (role == erGapFill) { + // Convert from spacing to extrusion width based on the extrusion model + // of a square extrusion ended with semi circles. + wanted_width = unscale(line.a_width) + flow.height * (1. - 0.25 * PI); + } else if (unscale(line.a_width) < 2 * flow.height * (1. - 0.25 * PI)) { + //width (too) small, be sure to not extrude with negative spacing. + //we began to fall back to spacing gradually even before the spacing go into the negative + // to make extrusion1 < extrusion2 if width1 < width2 even if width2 is too small. + wanted_width = unscale(line.a_width)*0.35 + 1.3 * flow.height * (1. - 0.25 * PI); + } + if (path.polyline.points.empty()) { - flow.width = unscale(line.a_width); - #ifdef SLIC3R_DEBUG - printf(" filling %f gap\n", flow.width); - #endif - - // make sure we don't include too thin segments which - // may cause even slightly negative mm3_per_mm because of floating point math - path.mm3_per_mm = flow.mm3_per_mm(); - if (path.mm3_per_mm < EPSILON) continue; - - path.width = flow.width; - path.height = flow.height; + flow.width = wanted_width; path.polyline.append(line.a); path.polyline.append(line.b); + path.mm3_per_mm = flow.mm3_per_mm(); + path.width = flow.width; + path.height = flow.height; } else { - thickness_delta = fabs(flow.scaled_width() - line.a_width); - if (thickness_delta <= tolerance/2) { + thickness_delta = scale_(fabs(flow.width - wanted_width)); + if (thickness_delta <= tolerance / 2) { // the width difference between this line and the current flow width is // within the accepted tolerance - path.polyline.append(line.b); } else { // we need to initialize a new line - paths.push_back(path); + paths.emplace_back(std::move(path)); path = ExtrusionPath(role); --i; } } } if (path.polyline.is_valid()) - paths.push_back(path); - - // append paths to collection + paths.emplace_back(std::move(path)); + // Append paths to collection. if (!paths.empty()) { if (paths.front().first_point().coincides_with(paths.back().last_point())) { coll.append(ExtrusionLoop(paths)); } else { + //TODO: add them to an unsortable collection, to be able to keep the order anchor->outside + // and to keep the order of extrusion (do not stop extruding at intersection to turn => bad) + // BUT this need the ironing code to be able to have multiple collection inside each other + // BUT this need the ironing code to be able to have the no_sort flag to be useful + //ExtrusionEntityCollection unsortable_coll(paths); + //unsortable_coll.no_sort = true; + //coll.append(unsortable_coll); coll.append(paths); } } @@ -1593,4 +1629,5 @@ discretize_variable_width(const ThickPolylines &polylines, ExtrusionRole role, F return coll; } + } // namespace Slic3r From 1687c5c6f4afe0dd702c1909adb3478bf2d696ed Mon Sep 17 00:00:00 2001 From: supermerill Date: Sat, 6 Apr 2019 23:26:46 +0200 Subject: [PATCH 24/25] little bugfix for thinwall : concatenate_polylines_with_crossing --- xs/src/libslic3r/MedialAxis.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/xs/src/libslic3r/MedialAxis.cpp b/xs/src/libslic3r/MedialAxis.cpp index 88885912c3..59fa73da57 100644 --- a/xs/src/libslic3r/MedialAxis.cpp +++ b/xs/src/libslic3r/MedialAxis.cpp @@ -1086,7 +1086,7 @@ MedialAxis::concatenate_polylines_with_crossing(ThickPolylines& pp) Pointf v_other(other_reverse ? other.lines().back().vector().x : other.lines().front().vector().x, other_reverse ? other.lines().back().vector().y : 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; + float other_dot = std::abs(float(v_poly.x*v_other.x + v_poly.y*v_other.y)); if (other_dot > best_dot) { best_candidate = &other; best_idx = j; From 1c4504533c450cb08d6a108b86b82bdd1938415c Mon Sep 17 00:00:00 2001 From: supermerill Date: Sun, 7 Apr 2019 14:39:37 +0200 Subject: [PATCH 25/25] Add sanity check for gapfill's min width. --- xs/src/libslic3r/PerimeterGenerator.cpp | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/xs/src/libslic3r/PerimeterGenerator.cpp b/xs/src/libslic3r/PerimeterGenerator.cpp index 2243c133d0..e86a429edc 100644 --- a/xs/src/libslic3r/PerimeterGenerator.cpp +++ b/xs/src/libslic3r/PerimeterGenerator.cpp @@ -27,6 +27,9 @@ PerimeterGenerator::process() // solid infill coord_t ispacing = this->solid_infill_flow.scaled_spacing(); + //nozzle diameter + const double nozzle_diameter = this->print_config->nozzle_diameter.get_at(this->config->perimeter_extruder-1); + // Calculate the minimum required spacing between two adjacent traces. // This should be equal to the nominal flow spacing but we experiment // with some tolerance in order to avoid triggering medial axis when @@ -48,8 +51,6 @@ PerimeterGenerator::process() // We consider overhang any part where the entire nozzle diameter is not supported by the // lower layer, so we take lower slices and offset them by half the nozzle diameter used // in the current layer - double nozzle_diameter = this->print_config->nozzle_diameter.get_at(this->config->perimeter_extruder-1); - this->_lower_slices_p = offset(*this->lower_slices, scale_(+nozzle_diameter/2)); } @@ -319,6 +320,8 @@ PerimeterGenerator::process() // collapse double min = 0.2*pwidth * (1 - INSET_OVERLAP_TOLERANCE); + //be sure we don't gapfill where the perimeters are already touching each other (negative spacing). + min = std::max(min, double(Flow::new_from_spacing(EPSILON, nozzle_diameter, this->layer_height, false).scaled_width())); double max = 2*pspacing; ExPolygons gaps_ex = diff_ex( offset2(gaps, -min/2, +min/2),