From 6ba61fe39cd232a9647696f110ddeb7d1743138a Mon Sep 17 00:00:00 2001 From: supermerill Date: Fri, 10 Aug 2018 11:01:56 +0200 Subject: [PATCH 01/31] 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/31] 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/31] 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/31] 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/31] 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/31] 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/31] 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/31] 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/31] 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/31] 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/31] 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/31] 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/31] 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/31] 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/31] 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/31] 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/31] 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/31] 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/31] 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/31] 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/31] 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/31] 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/31] 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/31] 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/31] 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), From d1a5fe276f4bedf53684ae70422e6cac273aafba Mon Sep 17 00:00:00 2001 From: supermerill Date: Tue, 9 Apr 2019 19:02:26 +0200 Subject: [PATCH 26/31] medial axis: fixes - taper_ends fix - empty_vector.back() bugfix - more accurate min width from nozzle diameter & layer height - add epsilon offset when trying to see if points are inside a polygon - failsafe not safe (do not fusion with a too high width). - add remove_collinear_points() in the simplify function. --- xs/src/libslic3r/MedialAxis.cpp | 68 ++++++++++++++++++++++----------- 1 file changed, 45 insertions(+), 23 deletions(-) diff --git a/xs/src/libslic3r/MedialAxis.cpp b/xs/src/libslic3r/MedialAxis.cpp index 59fa73da57..cdeeef0f52 100644 --- a/xs/src/libslic3r/MedialAxis.cpp +++ b/xs/src/libslic3r/MedialAxis.cpp @@ -187,7 +187,20 @@ MedialAxis::validate_edge(const VD::edge_type* edge) // 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; + //test if (!expolygon.contains(line)) + Polyline line_a_b; + line_a_b.append(line.a); + line_a_b.append(line.b); + Polylines external_bits = diff_pl(Polylines{ line_a_b }, expolygon); + if (!external_bits.empty()){ + //check if the bits that are not inside are under epsilon length + coordf_t max_length = 0; + for (Polyline &poly : external_bits){ + max_length = std::max(max_length, poly.length()); + } + if (max_length > SCALED_EPSILON) + return false; + } } // retrieve the original line segments which generated the edge we're checking @@ -743,6 +756,10 @@ MedialAxis::main_fusion(ThickPolylines& pp) double dot_poly_branch = 0; double dot_candidate_branch = 0; + bool find_main_branch = false; + size_t biggest_main_branch_id = 0; + coord_t biggest_main_branch_length = 0; + // find another polyline starting here for (size_t j = i + 1; j < pp.size(); ++j) { ThickPolyline& other = pp[j]; @@ -794,9 +811,9 @@ MedialAxis::main_fusion(ThickPolylines& pp) // 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; + find_main_branch = false; + biggest_main_branch_id = 0; + biggest_main_branch_length = 0; for (size_t k = 0; k < pp.size(); ++k) { if (k == i || k == j) continue; ThickPolyline& main = pp[k]; @@ -909,9 +926,9 @@ MedialAxis::main_fusion(ThickPolylines& pp) //failsafes if (polyline.width[idx_point] > max_width) polyline.width[idx_point] = max_width; - 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; + //failsafe: try to not go out of the radius of the section, take the width of the merging point for that. (and with some offset) + if (find_main_branch && polyline.width[idx_point] > pp[biggest_main_branch_id].width.front() * 1.1) + polyline.width[idx_point] = pp[biggest_main_branch_id].width.front() * 1.1; ++idx_point; } @@ -960,15 +977,14 @@ MedialAxis::main_fusion(ThickPolylines& pp) } } - //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; + } else { + //update cache + coeff_angle_cache[polyline.points.back()] = coeff_angle_poly * coeff_poly + coeff_angle_candi * coeff_candi; } pp.erase(pp.begin() + best_idx); @@ -1250,6 +1266,9 @@ MedialAxis::simplify_polygon_frontier() //simplify the boundary between us and the bounds. //it will remove every point in the surface contour that aren't on the bounds contour ExPolygon simplified_poly = this->surface; + simplified_poly.contour.remove_collinear_points(); + for (Polygon &hole : simplified_poly.holes) + hole.remove_collinear_points(); if (&this->surface != &this->bounds) { bool need_intersect = false; for (size_t i = 0; i < simplified_poly.contour.points.size(); i++) { @@ -1290,6 +1309,9 @@ MedialAxis::simplify_polygon_frontier() /// Do not grow points inside the anchor. void MedialAxis::grow_to_nozzle_diameter(ThickPolylines& pp, const ExPolygons& anchors) { + coord_t min_width = this->nozzle_diameter * 1.05; + if (this->height > 0)min_width = Flow::new_from_spacing(float(unscale(this->nozzle_diameter)), + float(unscale(this->nozzle_diameter)), float(unscale(this->height)), false).scaled_width(); //ensure the width is not lower than 0.4. for (ThickPolyline& polyline : pp) { for (int i = 0; i < polyline.points.size(); ++i) { @@ -1300,8 +1322,8 @@ MedialAxis::grow_to_nozzle_diameter(ThickPolylines& pp, const ExPolygons& anchor break; } } - if (!is_anchored && polyline.width[i] < nozzle_diameter * 1.05) - polyline.width[i] = nozzle_diameter * 1.05; + if (!is_anchored && polyline.width[i] < min_width) + polyline.width[i] = min_width; } } } @@ -1324,7 +1346,7 @@ MedialAxis::taper_ends(ThickPolylines& pp) { 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); + coordf_t percent_dist = (length - last_dist) / (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]); } @@ -1335,22 +1357,22 @@ MedialAxis::taper_ends(ThickPolylines& pp) { } } if (polyline.endpoints.second) { - const size_t back_idx = polyline.width.size() - 1; - polyline.width[back_idx] = min_size; + polyline.width[polyline.width.size() - 1] = min_size; coord_t current_dist = min_size; coord_t last_dist = min_size; - for (size_t i = 1; i 0; --i) { + current_dist += (coord_t)polyline.points[i].distance_to(polyline.points[i - 1]); + std::cout << "2current_dist= " << current_dist << "\n"; 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]); + if (current_dist > length + SCALED_RESOLUTION) { + coordf_t percent_dist = (length - last_dist) / (current_dist - last_dist); + polyline.points.insert(polyline.points.begin() + i, polyline.points[i].interpolate(percent_dist, polyline.points[i - 1])); + polyline.width.insert(polyline.width.begin() + i, polyline.width[i - 1]); } break; } - polyline.width[back_idx - i] = std::max((coordf_t)min_size, min_size + (polyline.width[back_idx - i] - min_size) * current_dist / length); + polyline.width[i - 1] = std::max((coordf_t)min_size, min_size + (polyline.width[i - 1] - min_size) * current_dist / length); last_dist = current_dist; } } From 45a5630388a68c5706370bbc1e7733f3b3424bd1 Mon Sep 17 00:00:00 2001 From: supermerill Date: Thu, 18 Apr 2019 18:21:13 +0200 Subject: [PATCH 27/31] Cleaning pull request adding comments --- lib/Slic3r/GUI/PresetEditor.pm | 2 +- src/GUI/Dialogs/PresetEditor.hpp | 2 +- src/test/libslic3r/test_flow.cpp | 16 +-- t/thin.t | 18 +-- xs/src/libslic3r/ExPolygon.cpp | 9 +- xs/src/libslic3r/ExPolygon.hpp | 2 +- xs/src/libslic3r/LayerHeightSpline.cpp | 4 +- xs/src/libslic3r/MedialAxis.cpp | 173 ++++++------------------ xs/src/libslic3r/MedialAxis.hpp | 84 +++++++++--- xs/src/libslic3r/PerimeterGenerator.cpp | 17 +-- xs/src/libslic3r/Point.cpp | 4 +- xs/src/libslic3r/Point.hpp | 1 - xs/src/libslic3r/Polyline.cpp | 6 +- xs/src/libslic3r/PrintConfig.cpp | 20 +-- 14 files changed, 156 insertions(+), 202 deletions(-) diff --git a/lib/Slic3r/GUI/PresetEditor.pm b/lib/Slic3r/GUI/PresetEditor.pm index f92032d098..f32a5666fb 100644 --- a/lib/Slic3r/GUI/PresetEditor.pm +++ b/lib/Slic3r/GUI/PresetEditor.pm @@ -440,7 +440,7 @@ sub options { 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 thin_walls_overlap overhangs + 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 diff --git a/src/GUI/Dialogs/PresetEditor.hpp b/src/GUI/Dialogs/PresetEditor.hpp index 61980e9afe..7287d7fc51 100644 --- a/src/GUI/Dialogs/PresetEditor.hpp +++ b/src/GUI/Dialogs/PresetEditor.hpp @@ -144,7 +144,7 @@ class PrintEditor : public PresetEditor { "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, "thin_walls_overlap"s, "overhangs"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/src/test/libslic3r/test_flow.cpp b/src/test/libslic3r/test_flow.cpp index 1db8cd5fdf..8db6f462bf 100644 --- a/src/test/libslic3r/test_flow.cpp +++ b/src/test/libslic3r/test_flow.cpp @@ -93,10 +93,10 @@ SCENARIO(" Bridge flow specifics.", "[!mayfail]") { SCENARIO("Flow: Flow math for non-bridges", "[!mayfail]") { GIVEN("Nozzle Diameter of 0.4, a desired width of 1mm and layer height of 0.5") { auto width {ConfigOptionFloatOrPercent(1.0, false)}; - float spacing {0.4}; - float nozzle_diameter {0.4}; - float bridge_flow {1.0}; - float layer_height {0.5}; + float spacing {0.4f}; + float nozzle_diameter {0.4f}; + float bridge_flow {1.0f}; + float layer_height {0.5f}; // Spacing for non-bridges is has some overlap THEN("External perimeter flow has spacing fixed to 1.1*nozzle_diameter") { @@ -117,10 +117,10 @@ SCENARIO("Flow: Flow math for non-bridges", "[!mayfail]") { } /// Check the min/max GIVEN("Nozzle Diameter of 0.25") { - float spacing {0.4}; - float nozzle_diameter {0.25}; - float bridge_flow {0.0}; - float layer_height {0.5}; + float spacing {0.4f}; + float nozzle_diameter {0.25f}; + float bridge_flow {0.0f}; + float layer_height {0.5f}; WHEN("layer height is set to 0.2") { layer_height = 0.15f; THEN("Max width is set.") { diff --git a/t/thin.t b/t/thin.t index 3bacbdfe06..9bd4e9aa0f 100644 --- a/t/thin.t +++ b/t/thin.t @@ -103,8 +103,8 @@ if (0) { # check whether turns are all CCW or all CW my @all_lines = @{$res->[0]->lines}; - # remove lines that are near the end. - my @lines = grep($_->a->y >= 1578184 || $_->b->y >= 1578184, @all_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 (but the 2 end points)'; @@ -115,15 +115,15 @@ 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(); + $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'; ok unscale($res->[1]->length) >= (9.9) - epsilon, 'medial axis has reasonable length'; - - my @lines1 = @{$res->[0]->lines}; + + my @lines1 = @{$res->[0]->lines}; my @angles1 = map { $lines1[$_-1]->ccw($lines1[$_]->b) } 1..$#lines1; - my @lines2 = @{$res->[1]->lines}; + my @lines2 = @{$res->[1]->lines}; my @angles2 = map { $lines2[$_-1]->ccw($lines2[$_]->b) } 1..$#lines2; my @angles = (@angles1, @angles2); ok !!(none { $_ != 0 } @angles), @@ -134,12 +134,12 @@ 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(); + $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'; - # 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 - + # 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 + } { diff --git a/xs/src/libslic3r/ExPolygon.cpp b/xs/src/libslic3r/ExPolygon.cpp index b1a321fc24..a444e6226e 100644 --- a/xs/src/libslic3r/ExPolygon.cpp +++ b/xs/src/libslic3r/ExPolygon.cpp @@ -217,18 +217,11 @@ 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.build(polylines); -} - void ExPolygon::medial_axis(double max_width, double min_width, Polylines* polylines) const { ThickPolylines tp; - this->medial_axis(*this, max_width, min_width, &tp, max_width/2.0); + MedialAxis{*this, coord_t(max_width), coord_t(min_width), coord_t(max_width / 2.0)}.build(tp); polylines->insert(polylines->end(), tp.begin(), tp.end()); } diff --git a/xs/src/libslic3r/ExPolygon.hpp b/xs/src/libslic3r/ExPolygon.hpp index f9072e7d29..01e83fc52c 100644 --- a/xs/src/libslic3r/ExPolygon.hpp +++ b/xs/src/libslic3r/ExPolygon.hpp @@ -45,7 +45,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, bool stop_at_min_width = true) const; + ///TODO: remove that function. It's kept only because of perl tests. 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/LayerHeightSpline.cpp b/xs/src/libslic3r/LayerHeightSpline.cpp index 0fadf35136..7fe114b2bb 100644 --- a/xs/src/libslic3r/LayerHeightSpline.cpp +++ b/xs/src/libslic3r/LayerHeightSpline.cpp @@ -84,7 +84,7 @@ bool LayerHeightSpline::updateLayerHeights(std::vector heights) this->_layer_heights = heights; result = this->_updateBSpline(); }else{ - std::cerr << "Unable to update layer heights. You provided " << heights.size() << " layers, but " << this->_layers.size()-1 << " expected" << std::endl; + std::cout << "Unable to update layer heights. You provided " << heights.size() << " layers, but " << this->_layers.size()-1 << " expected" << std::endl; } this->_layers_updated = false; @@ -193,7 +193,7 @@ bool LayerHeightSpline::_updateBSpline() result = true; } else { result = false; - std::cerr << "Spline setup failed." << std::endl; + std::cout << "Spline setup failed." << std::endl; } this->_is_valid = result; diff --git a/xs/src/libslic3r/MedialAxis.cpp b/xs/src/libslic3r/MedialAxis.cpp index cdeeef0f52..18f26eca35 100644 --- a/xs/src/libslic3r/MedialAxis.cpp +++ b/xs/src/libslic3r/MedialAxis.cpp @@ -13,15 +13,13 @@ #include namespace Slic3r { - -int MedialAxis::id = 0; void -MedialAxis::build(Polylines* polylines) +MedialAxis::build(Polylines &polylines) { ThickPolylines tp; - this->build(&tp); - polylines->insert(polylines->end(), tp.begin(), tp.end()); + this->build(tp); + polylines.insert(polylines.end(), tp.begin(), tp.end()); } void @@ -458,7 +456,7 @@ MedialAxis::fusion_curve(ThickPolylines &pp) size_t closest_point_idx = this->expolygon.contour.closest_point_index(polyline.points.back()); - //check the 0-wodth point is on the contour. + //check the 0-width 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; @@ -619,7 +617,7 @@ MedialAxis::fusion_corners(ThickPolylines &pp) void MedialAxis::extends_line_both_side(ThickPolylines& pp) { - 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]; this->extends_line(polyline, anchors, this->min_width); @@ -635,7 +633,7 @@ MedialAxis::extends_line(ThickPolyline& polyline, const ExPolygons& anchors, con // 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())) { + if (polyline.endpoints.second && !bounds->has_boundary_point(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) { @@ -669,10 +667,10 @@ MedialAxis::extends_line(ThickPolyline& polyline, const ExPolygons& anchors, con polyline.width.push_back(polyline.width.back()); } Point new_bound; - bool finded = 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) { + 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; @@ -714,7 +712,7 @@ MedialAxis::extends_line(ThickPolyline& polyline, const ExPolygons& anchors, con p_obj.y /= 2; Line l2 = Line(new_back, p_obj); l2.extend_end(max_width); - (void)bounds.contour.first_intersection(l2, &new_bound); + (void)bounds->contour.first_intersection(l2, &new_bound); } if (new_bound.coincides_with_epsilon(new_back)) { return; @@ -850,7 +848,6 @@ MedialAxis::main_fusion(ThickPolylines& pp) || (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) @@ -868,7 +865,6 @@ MedialAxis::main_fusion(ThickPolylines& pp) ((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) { @@ -917,7 +913,7 @@ MedialAxis::main_fusion(ThickPolylines& pp) // 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... + //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 / 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]); @@ -970,7 +966,7 @@ MedialAxis::main_fusion(ThickPolylines& pp) } //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])) { + 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; @@ -1238,12 +1234,12 @@ MedialAxis::ensure_not_overextrude(ThickPolylines& pp) // compute bounds volume double boundsVolume = 0; - boundsVolume += height*bounds.area(); + boundsVolume += height*bounds->area(); // add external "perimeter gap" - double perimeterRoundGap = bounds.contour.length() * height * (1 - 0.25*PI) * 0.5; + double perimeterRoundGap = bounds->contour.length() * height * (1 - 0.25*PI) * 0.5; // add holes "perimeter gaps" double holesGaps = 0; - for (const Polygon &hole : bounds.holes) { + for (const Polygon &hole : bounds->holes) { holesGaps += hole.length() * height * (1 - 0.25*PI) * 0.5; } boundsVolume += perimeterRoundGap + holesGaps; @@ -1259,50 +1255,53 @@ MedialAxis::ensure_not_overextrude(ThickPolylines& pp) } } -ExPolygon +void MedialAxis::simplify_polygon_frontier() { - //simplify the boundary between us and the bounds. + //simplify the boundary between us and the bounds-> //it will remove every point in the surface contour that aren't on the bounds contour - ExPolygon simplified_poly = this->surface; - simplified_poly.contour.remove_collinear_points(); - for (Polygon &hole : simplified_poly.holes) + this->expolygon = this->surface; + this->expolygon.contour.remove_collinear_points(); + for (Polygon &hole : this->expolygon.holes) hole.remove_collinear_points(); - if (&this->surface != &this->bounds) { + 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]; + for (size_t i = 0; i < this->expolygon.contour.points.size(); i++) { + Point &p_check = this->expolygon.contour.points[i]; //if (!find) { - if (!bounds.has_boundary_point(p_check)) { + 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); + size_t prev_i = i == 0 ? this->expolygon.contour.points.size() - 1 : (i - 1); + size_t next_i = i == this->expolygon.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 - < std::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(this->expolygon.contour.points[prev_i]), p_check.distance_to(this->expolygon.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); + this->expolygon.contour.points.erase(this->expolygon.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]; + ExPolygons simplified_polygons = intersection_ex(this->expolygon, *bounds); + if (simplified_polygons.size() == 1) { + this->expolygon = simplified_polygons[0]; } else { - simplified_poly = this->surface; + //can't simply that much, reuse the given one + this->expolygon = this->surface; + this->expolygon.contour.remove_collinear_points(); + for (Polygon &hole : this->expolygon.holes) + hole.remove_collinear_points(); } } } - if (!simplified_poly.contour.points.empty()) - simplified_poly.remove_point_too_near((coord_t)SCALED_RESOLUTION); - return simplified_poly; + if (!this->expolygon.contour.points.empty()) + this->expolygon.remove_point_too_near((coord_t)SCALED_RESOLUTION); } /// Grow the extrusion to at least nozzle_diameter*1.05 (lowest safe extrusion width) @@ -1332,7 +1331,7 @@ void MedialAxis::taper_ends(ThickPolylines& pp) { // 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); + const coordf_t length = std::min(this->taper_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) { @@ -1362,7 +1361,6 @@ MedialAxis::taper_ends(ThickPolylines& pp) { coord_t last_dist = min_size; for (size_t i = polyline.width.size()-1; i > 0; --i) { current_dist += (coord_t)polyline.points[i].distance_to(polyline.points[i - 1]); - std::cout << "2current_dist= " << current_dist << "\n"; if (current_dist > length) { //create new point if not near enough if (current_dist > length + SCALED_RESOLUTION) { @@ -1380,11 +1378,10 @@ MedialAxis::taper_ends(ThickPolylines& pp) { } void -MedialAxis::build(ThickPolylines* polylines_out) +MedialAxis::build(ThickPolylines &polylines_out) { - this->id++; - this->expolygon = simplify_polygon_frontier(); + simplify_polygon_frontier(); //safety check 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; @@ -1395,16 +1392,6 @@ MedialAxis::build(ThickPolylines* polylines_out) this->polyline_from_voronoi(this->expolygon.lines(), &pp); concatThickPolylines(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. */ @@ -1414,15 +1401,6 @@ MedialAxis::build(ThickPolylines* polylines_out) 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(); - //} // Aligned fusion: Fusion the bits at the end of lines by "increasing thickness" // For that, we have to find other lines, @@ -1430,15 +1408,6 @@ MedialAxis::build(ThickPolylines* polylines_out) // 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); @@ -1451,82 +1420,30 @@ MedialAxis::build(ThickPolylines* polylines_out) //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 if (!stop_at_min_width) { extends_line_both_side(pp); } - //{ - // 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(); - //} if (nozzle_diameter != min_width) { - grow_to_nozzle_diameter(pp, diff_ex(this->bounds, this->expolygon)); + grow_to_nozzle_diameter(pp, diff_ex(*this->bounds, this->expolygon)); + } + if(this->taper_size != 0){ taper_ends(pp); } - polylines_out->insert(polylines_out->end(), pp.begin(), pp.end()); + 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 635f68f2c8..b7a0c67978 100644 --- a/xs/src/libslic3r/MedialAxis.hpp +++ b/xs/src/libslic3r/MedialAxis.hpp @@ -15,29 +15,57 @@ using boost::polygon::voronoi_diagram; namespace Slic3r { - class MedialAxis { +/// This class is used to create single-line extrusion pattern with variable width to cover a ExPolygon. +/// The ends can enter a boundary area if neded, and can have a taper at each end. +/// The constructor initialize the mandatory variable. +/// you must use the setter to add the opptional settings before calling build(). +class MedialAxis { public: - Lines lines; //lines is here only to avoid passing it in argument of many methods. Initialized in polyline_from_voronoi. - ExPolygon expolygon; - + /// _expolygon: the polygon to fill + /// _max_width : maximum width of the extrusion. _expolygon shouldn't have a spot where a circle diameter is higher than that (or almost). + /// _min_width : minimum width of the extrusion, every spot where a circle diameter is lower than that will be ignored (unless it's the tip of the extrusion) + /// _height: height of the extrusion, used to compute the diufference between width and spacing. + MedialAxis(const ExPolygon &_expolygon, const coord_t _max_width, const coord_t _min_width, const coord_t _height) + : surface(_expolygon), max_width(_max_width), min_width(_min_width), height(_height), + bounds(&_expolygon), nozzle_diameter(_min_width), taper_size(0), stop_at_min_width(true){}; + + /// create the polylines_out collection of variable-width polyline to extrude. + void build(ThickPolylines &polylines_out); + /// You shouldn't use this method as it doesn't give you the variable width. Can be useful for debugging. + void build(Polylines &polylines); + + /// optional parameter: anchor area in which the extrusion should extends into. Default : expolygon (no bound) + MedialAxis& use_bounds(const ExPolygon & _bounds) { this->bounds = &_bounds; return *this; } + /// optional parameter: the real minimum width : it will grow the width of every extrusion that has a width lower than that. Default : min_width (same min) + MedialAxis& use_min_real_width(const coord_t nozzle_diameter) { this->nozzle_diameter = nozzle_diameter; return *this; } + /// optional parameter: create a taper of this length at each end (inside a bound or not). Default : 0 (no taper) + MedialAxis& use_tapers(const coord_t taper_size) { this->taper_size = taper_size; return *this; } + /// optional parameter: if true, the entension inside the bounds can be cut if the width is too small. Default : true + MedialAxis& set_stop_at_min_width(const bool stop_at_min_width) { this->stop_at_min_width = stop_at_min_width; return *this; } + + private: + /// Cache value: lines is here only to avoid passing it in argument of many methods. Initialized in polyline_from_voronoi. + Lines lines; + + /// input polygon to fill const ExPolygon& surface; - const ExPolygon& bounds; + /// the copied expolygon from surface, it's modified in build() to simplify it. It's then used to create the voronopi diagram. + ExPolygon expolygon; + const ExPolygon* bounds; + /// maximum width of the extrusion. _expolygon shouldn't have a spot where a circle diameter is higher than that (or almost). const coord_t max_width; + /// minimum width of the extrusion, every spot where a circle diameter is lower than that will be ignored (unless it's the tip of the extrusion) const coord_t min_width; + /// height of the extrusion, used to compute the diufference between width and spacing. const coord_t height; + /// Used to compute the real minimum width we can extrude. if != min_width, it activate grow_to_nozzle_diameter(). 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); - - private: - static int id; + /// if != , it activates taper_ends(). Can use nozzle_diameter. + coord_t taper_size; + //if true, remove_too_* can shorten the bits created by extends_line. + bool stop_at_min_width; + + //voronoi stuff class VD : public voronoi_diagram { public: typedef double coord_type; @@ -54,21 +82,37 @@ namespace Slic3r { 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(); + // functions called by build: + + /// create a simplied version of surface, store it in expolygon + void simplify_polygon_frontier(); + /// fusion little polylines created (by voronoi) on the external side of a curve inside the main polyline. void fusion_curve(ThickPolylines &pp); + /// fusion polylines created by voronoi, where needed. void main_fusion(ThickPolylines& pp); + /// like fusion_curve but for sharp angles like a square corner. void fusion_corners(ThickPolylines &pp); + /// extends the polylines inside bounds, use extends_line on both end void extends_line_both_side(ThickPolylines& pp); + /// extends the polylines inside bounds (anchors) void extends_line(ThickPolyline& polyline, const ExPolygons& anchors, const coord_t join_width); + /// remove too thin bits at start & end of polylines void remove_too_thin_extrusion(ThickPolylines& pp); + /// instead of keeping polyline split at each corssing, we try to create long strait polylines that can cross each other. void concatenate_polylines_with_crossing(ThickPolylines& pp); + /// remove bits around points that are too thin (can be inside the polyline) void remove_too_thin_points(ThickPolylines& pp); + /// delete polylines that are too short void remove_too_short_polylines(ThickPolylines& pp, const coord_t min_size); + /// be sure we didn't try to push more plastic than the volume defined by surface * height can receive. If overextruded, reduce all widths by the correct %. void ensure_not_overextrude(ThickPolylines& pp); + /// if nozzle_diameter > min_width, grow bits that are < width(nozzle_diameter) to width(nozzle_diameter) (don't activate that for gapfill) void grow_to_nozzle_diameter(ThickPolylines& pp, const ExPolygons& anchors); + /// taper the ends of polylines (don't activate that for gapfill) void taper_ends(ThickPolylines& pp); - }; - +}; + + /// create a ExtrusionEntityCollection from ThickPolylines, discretizing the variable width into little sections (of 4*SCALED_RESOLUTION length) where needed. 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 e86a429edc..9f3e1ab2c1 100644 --- a/xs/src/libslic3r/PerimeterGenerator.cpp +++ b/xs/src/libslic3r/PerimeterGenerator.cpp @@ -96,7 +96,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 + //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), @@ -153,10 +153,11 @@ 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 - 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); + Slic3r::MedialAxis ma{ thin[0], ext_pwidth + ext_pspacing2, min_width, coord_t(this->layer_height) }; + ma.use_bounds(bound) + .use_min_real_width((coord_t)scale_(this->ext_perimeter_flow.nozzle_diameter)) + .use_tapers(overlap) + .build(thin_walls); } break; } @@ -320,8 +321,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())); + //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), @@ -334,7 +335,7 @@ PerimeterGenerator::process() //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); + MedialAxis{ex, coord_t(max),coord_t(min), coord_t(this->layer_height)}.build(polylines); } } if (!polylines.empty()) { diff --git a/xs/src/libslic3r/Point.cpp b/xs/src/libslic3r/Point.cpp index 57d8bf1c0a..7381f9e832 100644 --- a/xs/src/libslic3r/Point.cpp +++ b/xs/src/libslic3r/Point.cpp @@ -276,8 +276,8 @@ Point::projection_onto(const MultiPoint &poly) const for (Lines::const_iterator line = lines.begin(); line != lines.end(); ++line) { Point point_temp = this->projection_onto(*line); if (this->distance_to(point_temp) < running_min) { - running_projection = point_temp; - running_min = this->distance_to(running_projection); + running_projection = point_temp; + running_min = this->distance_to(running_projection); } } return running_projection; diff --git a/xs/src/libslic3r/Point.hpp b/xs/src/libslic3r/Point.hpp index e2e6dc7c2d..a02349f400 100644 --- a/xs/src/libslic3r/Point.hpp +++ b/xs/src/libslic3r/Point.hpp @@ -144,7 +144,6 @@ 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); diff --git a/xs/src/libslic3r/Polyline.cpp b/xs/src/libslic3r/Polyline.cpp index e5d8b440fd..f0f33dc55c 100644 --- a/xs/src/libslic3r/Polyline.cpp +++ b/xs/src/libslic3r/Polyline.cpp @@ -171,9 +171,9 @@ Polyline::split_at(const Point &point, Polyline* p1, Polyline* p2) const for (Lines::const_iterator line = lines.begin(); line != lines.end(); ++line) { Point p_tmp = point.projection_onto(*line); if (point.distance_to(p_tmp) < min) { - p = p_tmp; - min = point.distance_to(p); - line_idx = line - lines.begin(); + p = p_tmp; + min = point.distance_to(p); + line_idx = line - lines.begin(); } } diff --git a/xs/src/libslic3r/PrintConfig.cpp b/xs/src/libslic3r/PrintConfig.cpp index 3b3539d7ec..697508eb9a 100644 --- a/xs/src/libslic3r/PrintConfig.cpp +++ b/xs/src/libslic3r/PrintConfig.cpp @@ -815,16 +815,16 @@ PrintConfigDef::PrintConfigDef() def->default_value = new ConfigOptionInt(100); def = this->add("max_layer_height", coFloats); - def->label = "Max"; - def->tooltip = "This is the highest printable layer height for this extruder and limits the resolution for adaptive slicing. Typical values are slightly smaller than nozzle_diameter."; - def->sidetext = "mm"; - def->cli = "max-layer-height=f@"; - def->min = 0; - { - ConfigOptionFloats* opt = new ConfigOptionFloats(); - opt->values.push_back(0.3); - def->default_value = opt; - } + def->label = "Max"; + def->tooltip = "This is the highest printable layer height for this extruder and limits the resolution for adaptive slicing. Typical values are slightly smaller than nozzle_diameter."; + def->sidetext = "mm"; + def->cli = "max-layer-height=f@"; + def->min = 0; + { + ConfigOptionFloats* opt = new ConfigOptionFloats(); + opt->values.push_back(0.3); + def->default_value = opt; + } def = this->add("max_print_speed", coFloat); def->label = __TRANS("Max print speed"); From 6273f0d18e9e162dcc1a90de752a1febce356686 Mon Sep 17 00:00:00 2001 From: supermerill Date: Thu, 27 Jun 2019 14:22:59 +0200 Subject: [PATCH 28/31] bugfix to test_fill --- src/test/libslic3r/test_fill.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/test/libslic3r/test_fill.cpp b/src/test/libslic3r/test_fill.cpp index a1ce98b4c7..0fe4751d63 100644 --- a/src/test/libslic3r/test_fill.cpp +++ b/src/test/libslic3r/test_fill.cpp @@ -421,6 +421,7 @@ bool test_if_solid_surface_filled(const ExPolygon& expolygon, double flow_spacin Flow flow(flow_spacing, 0.4, flow_spacing); filler->min_spacing = flow.spacing(); + filler->density = density; Polylines paths {filler->fill_surface(surface)}; From d7357cc26bd48f43ebdcab27f41fb5b52a7b3192 Mon Sep 17 00:00:00 2001 From: supermerill Date: Tue, 22 Oct 2019 19:08:02 +0200 Subject: [PATCH 29/31] some bugfix for medial axis add cp medial axis tests --- src/CMakeLists.txt | 1 + src/test/libslic3r/test_thin.cpp | 470 +++++++++++++++++++++++++++++++ xs/src/libslic3r/MedialAxis.cpp | 77 +++-- xs/src/libslic3r/MedialAxis.hpp | 4 +- 4 files changed, 528 insertions(+), 24 deletions(-) create mode 100644 src/test/libslic3r/test_thin.cpp diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index f43d56bd9d..7f7a541303 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -315,6 +315,7 @@ set(SLIC3R_TEST_SOURCES ${TESTDIR}/libslic3r/test_printobject.cpp ${TESTDIR}/libslic3r/test_skirt_brim.cpp ${TESTDIR}/libslic3r/test_test_data.cpp + ${TESTDIR}/libslic3r/test_thin.cpp ${TESTDIR}/libslic3r/test_trianglemesh.cpp ${TESTDIR}/libslic3r/test_extrusion_entity.cpp ${TESTDIR}/libslic3r/test_3mf.cpp diff --git a/src/test/libslic3r/test_thin.cpp b/src/test/libslic3r/test_thin.cpp new file mode 100644 index 0000000000..ab32db5ff4 --- /dev/null +++ b/src/test/libslic3r/test_thin.cpp @@ -0,0 +1,470 @@ + +//#define CATCH_CONFIG_DISABLE + +#include +#include "../test_data.hpp" +#include "../../libslic3r/ClipperUtils.hpp" +#include "../../libslic3r/MedialAxis.hpp" +#include "../../libslic3r/SVG.hpp" + +using namespace Slic3r; +using namespace Slic3r::Geometry; + + +SCENARIO("thin walls: ") +{ + + + GIVEN("Square") + { + Points test_set; + test_set.reserve(4); + Points square {Point::new_scale(100, 100), + Point::new_scale(200, 100), + Point::new_scale(200, 200), + Point::new_scale(100, 200)}; + Slic3r::Polygon hole_in_square{ Points{ + Point::new_scale(140, 140), + Point::new_scale(140, 160), + Point::new_scale(160, 160), + Point::new_scale(160, 140) } }; + ExPolygon expolygon; + expolygon.contour = Slic3r::Polygon{ square }; + expolygon.holes = Slic3r::Polygons{ hole_in_square }; + WHEN("creating the medial axis"){ + Polylines res; + expolygon.medial_axis(scale_(40), scale_(0.5), &res); + + THEN("medial axis of a square shape is a single path"){ + REQUIRE(res.size() == 1); + } + THEN("polyline forms a closed loop"){ + REQUIRE(res[0].first_point().coincides_with(res[0].last_point()) == true); + } + THEN("medial axis loop has reasonable length"){ + REQUIRE(res[0].length() > expolygon.holes[0].length()); + REQUIRE(res[0].length() < expolygon.contour.length()); + } + } + } + + GIVEN("narrow rectangle"){ + ExPolygon expolygon; + expolygon.contour = Slic3r::Polygon{ Points{ + Point::new_scale(100, 100), + Point::new_scale(120, 100), + Point::new_scale(120, 200), + Point::new_scale(100, 200) } }; + Polylines res; + expolygon.medial_axis(scale_(20), scale_(0.5), &res); + + ExPolygon expolygon2; + expolygon2.contour = Slic3r::Polygon{ Points{ + Point::new_scale(100, 100), + Point::new_scale(120, 100), + Point::new_scale(120, 200), + Point::new_scale(105, 200), // extra point in the short side + Point::new_scale(100, 200) } }; + Polylines res2; + expolygon.medial_axis(scale_(20), scale_(0.5), &res2); + WHEN("creating the medial axis"){ + + THEN("medial axis of a narrow rectangle is a single line"){ + REQUIRE(res.size() == 1); + THEN("medial axis has reasonable length") { + REQUIRE(res[0].length() >= scale_(200 - 100 - (120 - 100)) - SCALED_EPSILON); + } + } + THEN("medial axis of a narrow rectangle with an extra vertex is still a single line"){ + REQUIRE(res2.size() == 1); + THEN("medial axis of a narrow rectangle with an extra vertex has reasonable length") { + REQUIRE(res2[0].length() >= scale_(200 - 100 - (120 - 100)) - SCALED_EPSILON); + } + THEN("extra vertices don't influence medial axis") { + REQUIRE(res2[0].length() - res[0].length() < SCALED_EPSILON); + } + } + } + } + + //TODO: compare with mainline slic3r + GIVEN("semicicumference") { + ExPolygon expolygon; + expolygon.contour = Slic3r::Polygon{ Points{ + Point{ 1185881, 829367 }, Point{ 1421988, 1578184 }, Point{ 1722442, 2303558 }, Point{ 2084981, 2999998 }, Point{ 2506843, 3662186 }, Point{ 2984809, 4285086 }, Point{ 3515250, 4863959 }, Point{ 4094122, 5394400 }, Point{ 4717018, 5872368 }, + Point{ 5379210, 6294226 }, Point{ 6075653, 6656769 }, Point{ 6801033, 6957229 }, Point{ 7549842, 7193328 }, Point{ 8316383, 7363266 }, Point{ 9094809, 7465751 }, Point{ 9879211, 7500000 }, Point{ 10663611, 7465750 }, Point{ 11442038, 7363265 }, + Point{ 12208580, 7193327 }, Point{ 12957389, 6957228 }, Point{ 13682769, 6656768 }, Point{ 14379209, 6294227 }, Point{ 15041405, 5872366 }, Point{ 15664297, 5394401 }, Point{ 16243171, 4863960 }, Point{ 16758641, 4301424 }, Point{ 17251579, 3662185 }, + Point{ 17673439, 3000000 }, Point{ 18035980, 2303556 }, Point{ 18336441, 1578177 }, Point{ 18572539, 829368 }, Point{ 18750748, 0 }, Point{ 19758422, 0 }, Point{ 19727293, 236479 }, Point{ 19538467, 1088188 }, Point{ 19276136, 1920196 }, + Point{ 18942292, 2726179 }, Point{ 18539460, 3499999 }, Point{ 18070731, 4235755 }, Point{ 17539650, 4927877 }, Point{ 16950279, 5571067 }, Point{ 16307090, 6160437 }, Point{ 15614974, 6691519 }, Point{ 14879209, 7160248 }, Point{ 14105392, 7563079 }, + Point{ 13299407, 7896927 }, Point{ 12467399, 8159255 }, Point{ 11615691, 8348082 }, Point{ 10750769, 8461952 }, Point{ 9879211, 8500000 }, Point{ 9007652, 8461952 }, Point{ 8142729, 8348082 }, Point{ 7291022, 8159255 }, Point{ 6459015, 7896927 }, + Point{ 5653029, 7563079 }, Point{ 4879210, 7160247 }, Point{ 4143447, 6691519 }, Point{ 3451331, 6160437 }, Point{ 2808141, 5571066 }, Point{ 2218773, 4927878 }, Point{ 1687689, 4235755 }, Point{ 1218962, 3499999 }, Point{ 827499, 2748020 }, + Point{ 482284, 1920196 }, Point{ 219954, 1088186 }, Point{ 31126, 236479 }, Point{ 0, 0 }, Point{ 1005754, 0 } + } }; + + WHEN("creating the medial axis") { + Polylines res; + expolygon.medial_axis(scale_(1.324888), scale_(0.25), &res); + + THEN("medial axis of a semicircumference is a single line") { + REQUIRE(res.size() == 1); + } + THEN("all medial axis segments of a semicircumference have the same orientation (but the 2 end points)") { + Lines lines = res[0].lines(); + double min_angle = 1, max_angle = -1; + //std::cout << "first angle=" << lines[0].ccw(lines[1].b) << "\n"; + for (int idx = 1; idx < lines.size() - 1; idx++) { + double angle = lines[idx - 1].ccw(lines[idx].b); + if (std::abs(angle) - EPSILON < 0) angle = 0; + //if (angle < 0) std::cout << unscale_(lines[idx - 1].a.x()) << ":" << unscale_(lines[idx - 1].a.y()) << " -> " << unscale_(lines[idx - 1].b.x()) << ":" << unscale_(lines[idx - 1].b.y()) << " -> " << unscale_(lines[idx].b.x()) << ":" << unscale_(lines[idx].b.y()) << "\n"; + std::cout << "angle=" << 180 * lines[idx].a.ccw_angle(lines[idx - 1].a, lines[idx].b) / PI << "\n"; + min_angle = std::min(min_angle, angle); + max_angle = std::max(max_angle, angle); + } + //std::cout << "last angle=" << lines[lines.size() - 2].ccw(lines[lines.size() - 1].b) << "\n"; + // check whether turns are all CCW or all CW + bool allccw = (min_angle <= 0 && max_angle <= 0); + bool allcw = (min_angle >= 0 && max_angle >= 0); + bool allsame_orientation = allccw || allcw; + REQUIRE(allsame_orientation); + } + } + } + + + GIVEN("round with large and very small distance between points"){ + ExPolygon expolygon; + expolygon.contour = Slic3r::Polygon{ Points{ + Point::new_scale(15.181601,-2.389639), Point::new_scale(15.112616,-1.320034), Point::new_scale(14.024491,-0.644338), Point::new_scale(13.978982,-0.624495), Point::new_scale(9.993299,0.855584), Point::new_scale(9.941970,0.871195), Point::new_scale(5.796743,1.872643), + Point::new_scale(5.743826,1.882168), Point::new_scale(1.509170,2.386464), Point::new_scale(1.455460,2.389639), Point::new_scale(-2.809359,2.389639), Point::new_scale(-2.862805,2.386464), Point::new_scale(-7.097726,1.882168), Point::new_scale(-7.150378,1.872643), Point::new_scale(-11.286344,0.873576), + Point::new_scale(-11.335028,0.858759), Point::new_scale(-14.348632,-0.237938), Point::new_scale(-14.360538,-0.242436), Point::new_scale(-15.181601,-0.737570), Point::new_scale(-15.171309,-2.388509) + } }; + expolygon.holes.push_back(Slic3r::Polygon{ Points{ + Point::new_scale( -11.023311,-1.034226 ), Point::new_scale( -6.920984,-0.042941 ), Point::new_scale( -2.768613,0.463207 ), Point::new_scale( 1.414714,0.463207 ), Point::new_scale( 5.567085,-0.042941 ), Point::new_scale( 9.627910,-1.047563 ) + } }); + + WHEN("creating the medial axis"){ + Polylines res; + expolygon.medial_axis(scale_(2.5), scale_(0.5), &res); + + THEN("medial axis of it is two line"){ + REQUIRE(res.size() == 2); + } + } + } + + GIVEN("french cross") + { + ExPolygon expolygon; + expolygon.contour = Slic3r::Polygon{ Points{ + Point::new_scale(4.3, 4), Point::new_scale(4.3, 0), Point::new_scale(4, 0), Point::new_scale(4, 4), Point::new_scale(0, 4), Point::new_scale(0, 4.5), Point::new_scale(4, 4.5), Point::new_scale(4, 10), Point::new_scale(4.3, 10), Point::new_scale(4.3, 4.5), + Point::new_scale(6, 4.5), Point::new_scale(6, 10), Point::new_scale(6.2, 10), Point::new_scale(6.2, 4.5), Point::new_scale(10, 4.5), Point::new_scale(10, 4), Point::new_scale(6.2, 4), Point::new_scale(6.2, 0), Point::new_scale(6, 0), Point::new_scale(6, 4), + } }; + expolygon.contour.make_counter_clockwise(); + + WHEN("creating the medial axis"){ + Polylines res; + expolygon.medial_axis(scale_(0.55), scale_(0.25), &res); + + THEN("medial axis of a (bit too narrow) french cross is two lines"){ + REQUIRE(res.size() == 2); + } + THEN("medial axis has reasonable length"){ + REQUIRE(res[0].length() >= scale_(9.9) - SCALED_EPSILON); + REQUIRE(res[1].length() >= scale_(9.9) - SCALED_EPSILON); + } + + THEN("medial axis of a (bit too narrow) french cross is two lines has only strait lines (first line)"){ + Lines lines = res[0].lines(); + double min_angle = 1, max_angle = -1; + for (int idx = 1; idx < lines.size(); idx++){ + double angle = lines[idx - 1].ccw(lines[idx].b); + min_angle = std::min(min_angle, angle); + max_angle = std::max(max_angle, angle); + } + REQUIRE(min_angle == max_angle); + REQUIRE(min_angle == 0); + } + THEN("medial axis of a (bit too narrow) french cross is two lines has only strait lines (second line)"){ + Lines lines = res[1].lines(); + double min_angle = 1, max_angle = -1; + for (int idx = 1; idx < lines.size(); idx++){ + double angle = lines[idx - 1].ccw(lines[idx].b); + min_angle = std::min(min_angle, angle); + max_angle = std::max(max_angle, angle); + } + REQUIRE(min_angle == max_angle); + REQUIRE(min_angle == 0); + } + } + + } + + + //TODO: compare with mainline slic3r + //GIVEN("tooth") + //{ + // ExPolygon expolygon; + // expolygon.contour = Slic3r::Polygon{ Points{ + // Point::new_scale(0.86526705, 1.4509841), Point::new_scale(0.57696039, 1.8637021), + // Point::new_scale(0.4502297, 2.5569978), Point::new_scale(0.45626199, 3.2965596), + // Point::new_scale(1.1218851, 3.3049455), Point::new_scale(0.96681072, 2.8243202), + // Point::new_scale(0.86328971, 2.2056997), Point::new_scale(0.85367905, 1.7790778) + // } }; + // expolygon.contour.make_counter_clockwise(); + // WHEN("creating the medial axis"){ + // Polylines res; + // expolygon.medial_axis(scale_(1), scale_(0.25), &res); + // THEN("medial axis of a tooth is two lines"){ + // REQUIRE(res.size() == 2); + // THEN("medial axis has reasonable length") { + // REQUIRE(res[0].length() >= scale_(1.4) - SCALED_EPSILON); + // REQUIRE(res[1].length() >= scale_(1.4) - SCALED_EPSILON); + // // 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 + // //TODO: now i have access! correct it! + // } + // } + // } + //} + + GIVEN("Anchor & Tapers") + { + ExPolygon tooth; + tooth.contour = Slic3r::Polygon{ Points{ + Point::new_scale(0,0), Point::new_scale(10,0), Point::new_scale(10,1.2), Point::new_scale(0,1.2) + } }; + tooth.contour.make_counter_clockwise(); + ExPolygon base_part; + base_part.contour = Slic3r::Polygon{ Points{ + Point::new_scale(0,-3), Point::new_scale(0,3), Point::new_scale(-2,3), Point::new_scale(-2,-3) + } }; + base_part.contour.make_counter_clockwise(); + //expolygon.contour = Slic3r::Polygon{ Points{ + // //Point::new_scale(0, 13), Point::new_scale(-1, 13), Point::new_scale(-1, 0), Point::new_scale(0.0,0.0), + // Point::new_scale(0,0.2), Point::new_scale(3,0.2), Point::new_scale(3,0.4), Point::new_scale(0,0.4), + // Point::new_scale(0,1), Point::new_scale(3,1), Point::new_scale(3,1.3), Point::new_scale(0,1.3), + // Point::new_scale(0,2), Point::new_scale(3,2), Point::new_scale(3,2.4), Point::new_scale(0,2.4), + // Point::new_scale(0,3), Point::new_scale(3,3), Point::new_scale(3,3.5), Point::new_scale(0,3.5), + // Point::new_scale(0,4), Point::new_scale(3,4), Point::new_scale(3,4.6), Point::new_scale(0,4.6), + // Point::new_scale(0,5), Point::new_scale(3,5), Point::new_scale(3,5.7), Point::new_scale(0,5.7), + // Point::new_scale(0,6), Point::new_scale(3,6), Point::new_scale(3,6.8), Point::new_scale(0,6.8), + // Point::new_scale(0,7.5), Point::new_scale(3,7.5), Point::new_scale(3,8.4), Point::new_scale(0,8.4), + // Point::new_scale(0,9), Point::new_scale(3,9), Point::new_scale(3,10), Point::new_scale(0,10), + // Point::new_scale(0,11), Point::new_scale(3,11), Point::new_scale(3,12.2), Point::new_scale(0,12.2), + //} }; + WHEN("1 nozzle, 0.2 layer height") { + const coord_t nozzle_diam = scale_(1); + ExPolygon anchor = union_ex(ExPolygons{ tooth }, intersection_ex(ExPolygons{ base_part }, offset_ex(tooth, nozzle_diam / 2)), true)[0]; + ThickPolylines res; + //expolygon.medial_axis(scale_(1), scale_(0.5), &res); + Slic3r::MedialAxis ma(tooth, nozzle_diam * 2, nozzle_diam/3, scale_(0.2)); + ma.use_bounds(anchor) + .use_min_real_width(nozzle_diam) + .use_tapers(0.25*nozzle_diam); + ma.build(res); + THEN("medial axis of a simple line is one line") { + REQUIRE(res.size() == 1); + THEN("medial axis has the length of the line + the length of the anchor") { + std::cout << res[0].length() << "\n"; + REQUIRE(std::abs(res[0].length() - scale_(10.5)) < SCALED_EPSILON); + } + THEN("medial axis has the line width as max width") { + double max_width = 0; + for (coordf_t width : res[0].width) max_width = std::max(max_width, width); + REQUIRE(std::abs(max_width - scale_(1.2)) < SCALED_EPSILON); + } + //compute the length of the tapers + THEN("medial axis has good tapers length") { + int l1 = 0; + for (size_t idx = 0; idx < res[0].width.size() - 1 && res[0].width[idx] - nozzle_diam < SCALED_EPSILON; ++idx) + l1 += res[0].lines()[idx].length(); + int l2 = 0; + for (size_t idx = res[0].width.size() - 1; idx > 0 && res[0].width[idx] - nozzle_diam < SCALED_EPSILON; --idx) + l2 += res[0].lines()[idx - 1].length(); + REQUIRE(std::abs(l1 - l2) < SCALED_EPSILON); + REQUIRE(std::abs(l1 - scale_(0.25 - 0.1)) < SCALED_EPSILON); + } + } + } + + WHEN("1.2 nozzle, 0.6 layer height") { + const coord_t nozzle_diam = scale_(1.2); + ExPolygon anchor = union_ex(ExPolygons{ tooth }, intersection_ex(ExPolygons{ base_part }, offset_ex(tooth, nozzle_diam / 4)), true)[0]; + ThickPolylines res; + //expolygon.medial_axis(scale_(1), scale_(0.5), &res); + Slic3r::MedialAxis ma(tooth, nozzle_diam * 2, nozzle_diam/3, scale_(0.6)); + ma.use_bounds(anchor) + .use_min_real_width(nozzle_diam) + .use_tapers(1.0*nozzle_diam); + ma.build(res); + THEN("medial axis of a simple line is one line") { + REQUIRE(res.size() == 1); + THEN("medial axis has the length of the line + the length of the anchor") { + //0.3 because it's offseted by nozzle_diam / 4 + REQUIRE(std::abs(res[0].length() - scale_(10.3)) < SCALED_EPSILON); + } + THEN("medial axis can'ty have a line width below Flow::new_from_spacing(nozzle_diam).width") { + double max_width = 0; + for (coordf_t width : res[0].width) max_width = std::max(max_width, width); + double min_width = Flow::new_from_spacing(float(unscale_(nozzle_diam)), float(unscale_(nozzle_diam)), 0.6f, false).scaled_width(); + REQUIRE(std::abs(max_width - min_width) < SCALED_EPSILON); + REQUIRE(std::abs(max_width - nozzle_diam) > SCALED_EPSILON); + } + //compute the length of the tapers + THEN("medial axis has a 45� taper and a shorter one") { + int l1 = 0; + for (size_t idx = 0; idx < res[0].width.size() - 1 && res[0].width[idx] - scale_(1.2) < SCALED_EPSILON; ++idx) + l1 += res[0].lines()[idx].length(); + int l2 = 0; + for (size_t idx = res[0].width.size() - 1; idx > 0 && res[0].width[idx] - scale_(1.2) < SCALED_EPSILON; --idx) + l2 += res[0].lines()[idx - 1].length(); + //here the taper is limited by the 0-width spacing + double min_width = Flow::new_from_spacing(float(unscale_(nozzle_diam)), float(unscale_(nozzle_diam)), 0.6f, false).scaled_width(); + REQUIRE(std::abs(l1 - l2) < SCALED_EPSILON); + REQUIRE(l1 < scale_(0.6)); + REQUIRE(l1 > scale_(0.4)); + } + } + } + + } + + GIVEN("1� rotated tooths") + { + + } + + GIVEN("narrow trapezoid") + { + ExPolygon expolygon; + expolygon.contour = Slic3r::Polygon{ Points{ + Point::new_scale(100, 100), + Point::new_scale(120, 100), + Point::new_scale(112, 200), + Point::new_scale(108, 200) + } }; + WHEN("creating the medial axis"){ + Polylines res; + expolygon.medial_axis(scale_(20), scale_(0.5), &res); + THEN("medial axis of a narrow trapezoid is a single line"){ + REQUIRE(res.size() == 1); + THEN("medial axis has reasonable length") { + REQUIRE(res[0].length() >= scale_(200 - 100 - (120 - 100)) - SCALED_EPSILON); + } + } + } + } + + GIVEN("L shape") + { + ExPolygon expolygon; + expolygon.contour = Slic3r::Polygon{ Points{ + Point::new_scale(100, 100), + Point::new_scale(120, 100), + Point::new_scale(120, 180), + Point::new_scale(200, 180), + Point::new_scale(200, 200), + Point::new_scale(100, 200) + } }; + WHEN("creating the medial axis"){ + Polylines res; + expolygon.medial_axis(scale_(20), scale_(0.5), &res); + THEN("medial axis of a L shape is a single line"){ + REQUIRE(res.size() == 1); + THEN("medial axis has reasonable length") { + // 20 is the thickness of the expolygon, which is subtracted from the ends + REQUIRE(res[0].length() + 20 > scale_(80 * 2) - SCALED_EPSILON); + REQUIRE(res[0].length() + 20 < scale_(100 * 2) + SCALED_EPSILON); + } + } + } + } + + GIVEN("shape"){ + ExPolygon expolygon; + expolygon.contour = Slic3r::Polygon{ Points{ + Point{ -203064906, -51459966 }, Point{ -219312231, -51459966 }, Point{ -219335477, -51459962 }, Point{ -219376095, -51459962 }, Point{ -219412047, -51459966 }, + Point{ -219572094, -51459966 }, Point{ -219624814, -51459962 }, Point{ -219642183, -51459962 }, Point{ -219656665, -51459966 }, Point{ -220815482, -51459966 }, + Point{ -220815482, -37738966 }, Point{ -221117540, -37738966 }, Point{ -221117540, -51762024 }, Point{ -203064906, -51762024 }, + } }; + WHEN("creating the medial axis"){ + Polylines polylines; + expolygon.medial_axis(819998, 102499.75, &polylines); + double perimeter_len = expolygon.contour.split_at_first_point().length(); + THEN("medial axis has reasonable length"){ + double polyline_length = 0; + for (Slic3r::Polyline &poly : polylines) polyline_length += poly.length(); + REQUIRE(polyline_length > perimeter_len * 3. / 8. - SCALED_EPSILON); + } + } + } + + GIVEN("narrow triangle") + { + ExPolygon expolygon; + expolygon.contour = Slic3r::Polygon{ Points{ + Point::new_scale(50, 100), + Point::new_scale(1000, 102), + Point::new_scale(50, 104) + } }; + + WHEN("creating the medial axis"){ + Polylines res; + expolygon.medial_axis(scale_(4), scale_(0.5), &res); + THEN("medial axis of a narrow triangle is a single line"){ + REQUIRE(res.size() == 1); + THEN("medial axis has reasonable length") { + REQUIRE(res[0].length() > scale_(200 - 100 - (120 - 100)) - SCALED_EPSILON); + } + } + } + } + + GIVEN("GH #2474") + { + ExPolygon expolygon; + expolygon.contour = Slic3r::Polygon{ Points{ + Point{91294454, 31032190}, + Point{11294481, 31032190}, + Point{11294481, 29967810}, + Point{44969182, 29967810}, + Point{89909960, 29967808}, + Point{91294454, 29967808} + } }; + WHEN("creating the medial axis") { + Polylines res; + expolygon.medial_axis(1871238, 500000, &res); + THEN("medial axis is a single polyline") { + REQUIRE(res.size() == 1); + Slic3r::Polyline polyline = res[0]; + THEN("medial axis is horizontal and is centered") { + double sum = 0; + for (Line &l : polyline.lines()) sum += std::abs(l.b.y() - l.a.y()); + coord_t expected_y = expolygon.contour.bounding_box().center().y(); + REQUIRE((sum / polyline.size()) - expected_y < SCALED_EPSILON); + } + + // order polyline from left to right + if (polyline.first_point().x() > polyline.last_point().x()) polyline.reverse(); + + THEN("expected x_min & x_max") { + BoundingBox polyline_bb = polyline.bounding_box(); + REQUIRE(polyline.first_point().x() == polyline_bb.min.x()); + REQUIRE(polyline.last_point().x() == polyline_bb.max.x()); + } + + THEN("medial axis is not self-overlapping") { + //TODO + //REQUIRE(polyline.first_point().x() == polyline_bb.x_min()); + std::vector all_x; + for (Point &p : polyline.points) all_x.push_back(p.x()); + std::vector sorted_x{ all_x }; + std::sort(sorted_x.begin(), sorted_x.end()); + for (size_t i = 0; i < all_x.size(); i++) { + REQUIRE(all_x[i] == sorted_x[i]); + } + } + } + } + } + +} diff --git a/xs/src/libslic3r/MedialAxis.cpp b/xs/src/libslic3r/MedialAxis.cpp index 18f26eca35..9656918315 100644 --- a/xs/src/libslic3r/MedialAxis.cpp +++ b/xs/src/libslic3r/MedialAxis.cpp @@ -303,7 +303,7 @@ MedialAxis::retrieve_endpoint(const VD::cell_type* cell) const void remove_point_too_near(ThickPolyline* to_reduce) { - const coord_t smallest = SCALED_EPSILON * 2; + const coord_t smallest = (coord_t)SCALED_EPSILON * 2; size_t id = 1; while (id < to_reduce->points.size() - 1) { coord_t newdist = (coord_t)std::min(to_reduce->points[id].distance_to(to_reduce->points[id - 1]) @@ -355,16 +355,17 @@ add_point_same_percent(ThickPolyline* pattern, ThickPolyline* to_modify) coordf_t new_width = to_modify->width[idx_other - 1] * (1 - percent_dist); 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.insert( + to_modify->points.begin() + 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()); @@ -415,7 +416,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) && std::max(nearest_dist, near_dist) + SCALED_EPSILON < point_nearest.distance_to(point_near)) { //not only nearest @@ -445,7 +446,7 @@ MedialAxis::fusion_curve(ThickPolylines &pp) 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.points.size() != 2) continue; // too restrictive. if (polyline.endpoints.first) polyline.reverse(); else if (!polyline.endpoints.second) continue; if (polyline.width.back() > EPSILON) continue; @@ -470,11 +471,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 @@ -495,6 +496,8 @@ MedialAxis::fusion_curve(ThickPolylines &pp) sum_dot += dot_temp; } } + sum_dot = abs(sum_dot); + //only consider very shallow angle for contour if (mindot > 0.15 && (1 - (coeff_contour_angle / (PI / 2))) > 0.2) continue; @@ -503,6 +506,8 @@ MedialAxis::fusion_curve(ThickPolylines &pp) if (crosspoint.size() != 2) continue; if (sum_dot > 0.2) continue; if (min_dot > 0.5) continue; + //don't remove useful bits. TODO: use the mindot to know by how much to multiply (1 when 90°, 1.42 when 45+, 1 when 0°) + if (polyline.length() > polyline.width.front()*1.42) 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) @@ -533,6 +538,8 @@ MedialAxis::fusion_curve(ThickPolylines &pp) concatThickPolylines(pp); ///reorder, in case of change std::sort(pp.begin(), pp.end(), [](const ThickPolyline & a, const ThickPolyline & b) { return a.length() < b.length(); }); + //have to redo it to remove multi-branch bits. + fusion_curve(pp); } } @@ -648,7 +655,6 @@ 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. bool finded = this->expolygon.contour.first_intersection(line, &new_back); //verify also for holes. Point new_back_temp; @@ -661,7 +667,15 @@ MedialAxis::extends_line(ThickPolyline& polyline, const ExPolygons& anchors, con } } // safety check if no intersection - if (!finded) new_back = line.b; + if (!finded){ + if (!this->expolygon.contains(line.b)) { + //it's not possible to print that + polyline.points.clear(); + polyline.width.clear(); + return; + } + new_back = line.b; + } polyline.points.push_back(new_back); polyline.width.push_back(polyline.width.back()); @@ -805,10 +819,10 @@ 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 find_main_branch = false; biggest_main_branch_id = 0; biggest_main_branch_length = 0; @@ -886,7 +900,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, std::min(min_width, (coord_t)(polyline.length() / 2)))); @@ -920,9 +934,14 @@ MedialAxis::main_fusion(ThickPolylines& pp) 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) + if (polyline.width[idx_point] > max_width) polyline.width[idx_point] = max_width; //failsafe: try to not go out of the radius of the section, take the width of the merging point for that. (and with some offset) + coord_t main_branch_width = pp[biggest_main_branch_id].width.front(); + coord_t main_branch_dist = pp[biggest_main_branch_id].points.front().distance_to(polyline.points[idx_point]); + coord_t max_width_from_main = std::sqrt(main_branch_width*main_branch_width + main_branch_dist*main_branch_dist); + if (find_main_branch && polyline.width[idx_point] > max_width_from_main) + polyline.width[idx_point] = max_width_from_main; if (find_main_branch && polyline.width[idx_point] > pp[biggest_main_branch_id].width.front() * 1.1) polyline.width[idx_point] = pp[biggest_main_branch_id].width.front() * 1.1; @@ -1307,11 +1326,15 @@ MedialAxis::simplify_polygon_frontier() /// 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) { - coord_t min_width = this->nozzle_diameter * 1.05; - if (this->height > 0)min_width = Flow::new_from_spacing(float(unscale(this->nozzle_diameter)), - float(unscale(this->nozzle_diameter)), float(unscale(this->height)), false).scaled_width(); - //ensure the width is not lower than 0.4. +MedialAxis::grow_to_nozzle_diameter(ThickPolylines& pp, const ExPolygons& anchors) +{ + //compute the min width + coord_t min_width = this->nozzle_diameter; + if (this->height > 0) min_width = Flow::new_from_spacing( + float(unscale(this->nozzle_diameter)), + float(unscale(this->nozzle_diameter)), + float(unscale(this->height)), false).scaled_width(); + //ensure the width is not lower than min_width. for (ThickPolyline& polyline : pp) { for (int i = 0; i < polyline.points.size(); ++i) { bool is_anchored = false; @@ -1328,12 +1351,13 @@ MedialAxis::grow_to_nozzle_diameter(ThickPolylines& pp, const ExPolygons& anchor } void -MedialAxis::taper_ends(ThickPolylines& pp) { +MedialAxis::taper_ends(ThickPolylines& pp) +{ // 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->taper_size, (this->nozzle_diameter - min_size) / 2); if (length <= SCALED_RESOLUTION) return; - //ensure the width is not lower than 0.4. + //ensure the width is not lower than min_size. for (ThickPolyline& polyline : pp) { if (polyline.length() < length * 2.2) continue; if (polyline.endpoints.first) { @@ -1344,7 +1368,7 @@ MedialAxis::taper_ends(ThickPolylines& pp) { current_dist += (coord_t) polyline.points[i - 1].distance_to(polyline.points[i]); if (current_dist > length) { //create a new point if not near enough - if (current_dist > polyline.width[i] + SCALED_RESOLUTION) { + if (current_dist > length + SCALED_RESOLUTION) { coordf_t percent_dist = (length - last_dist) / (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]); @@ -1390,6 +1414,15 @@ MedialAxis::build(ThickPolylines &polylines_out) // compute the Voronoi diagram and extract medial axis polylines ThickPolylines pp; this->polyline_from_voronoi(this->expolygon.lines(), &pp); + + //sanity check, as the voronoi can return (abeit very rarely) randomly high values. + for (ThickPolyline &tp : pp) { + for (int i = 0; i < tp.width.size(); i++) { + if (tp.width[i] > this->max_width) { + tp.width[i] = this->max_width; + } + } + } concatThickPolylines(pp); diff --git a/xs/src/libslic3r/MedialAxis.hpp b/xs/src/libslic3r/MedialAxis.hpp index b7a0c67978..36efb49cae 100644 --- a/xs/src/libslic3r/MedialAxis.hpp +++ b/xs/src/libslic3r/MedialAxis.hpp @@ -24,7 +24,7 @@ class MedialAxis { /// _expolygon: the polygon to fill /// _max_width : maximum width of the extrusion. _expolygon shouldn't have a spot where a circle diameter is higher than that (or almost). /// _min_width : minimum width of the extrusion, every spot where a circle diameter is lower than that will be ignored (unless it's the tip of the extrusion) - /// _height: height of the extrusion, used to compute the diufference between width and spacing. + /// _height: height of the extrusion, used to compute the difference between width and spacing. MedialAxis(const ExPolygon &_expolygon, const coord_t _max_width, const coord_t _min_width, const coord_t _height) : surface(_expolygon), max_width(_max_width), min_width(_min_width), height(_height), bounds(&_expolygon), nozzle_diameter(_min_width), taper_size(0), stop_at_min_width(true){}; @@ -49,7 +49,7 @@ class MedialAxis { /// input polygon to fill const ExPolygon& surface; - /// the copied expolygon from surface, it's modified in build() to simplify it. It's then used to create the voronopi diagram. + /// the copied expolygon from surface, it's modified in build() to simplify it. It's then used to create the voronoi diagram. ExPolygon expolygon; const ExPolygon* bounds; /// maximum width of the extrusion. _expolygon shouldn't have a spot where a circle diameter is higher than that (or almost). From 79db85d226547f3b894ee0f137ad54c5d61e9201 Mon Sep 17 00:00:00 2001 From: supermerill Date: Mon, 24 Feb 2020 11:30:19 +0100 Subject: [PATCH 30/31] fix for edge-cases --- xs/src/libslic3r/MedialAxis.cpp | 16 +++++++++++----- xs/src/libslic3r/PerimeterGenerator.cpp | 7 ++++--- 2 files changed, 15 insertions(+), 8 deletions(-) diff --git a/xs/src/libslic3r/MedialAxis.cpp b/xs/src/libslic3r/MedialAxis.cpp index 9656918315..2e18046279 100644 --- a/xs/src/libslic3r/MedialAxis.cpp +++ b/xs/src/libslic3r/MedialAxis.cpp @@ -95,7 +95,7 @@ MedialAxis::polyline_from_voronoi(const Lines& voronoi_edges, ThickPolylines* po } assert(polyline.width.size() == polyline.points.size()); - + // if loop, set endpoints to false // prevent loop endpoints from being extended if (polyline.first_point().coincides_with(polyline.last_point())) { polyline.endpoints.first = false; @@ -271,8 +271,8 @@ MedialAxis::validate_edge(const VD::edge_type* edge) // return false; // - //shouldn't occur if perimeter_generator is well made - if (w0 > this->max_width && w1 > this->max_width) + //shouldn't occur if perimeter_generator is well made. *1.05 for a little wiggle room + if (w0 > this->max_width*1.05 && w1 > this->max_width*1.05) return false; this->thickness[edge] = std::make_pair(w0, w1); @@ -628,8 +628,14 @@ MedialAxis::extends_line_both_side(ThickPolylines& pp) { 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); + if (!polyline.points.empty()) { + polyline.reverse(); + this->extends_line(polyline, anchors, this->min_width); + } + if (polyline.points.empty()) { + pp.erase(pp.begin() + i); + --i; + } } } diff --git a/xs/src/libslic3r/PerimeterGenerator.cpp b/xs/src/libslic3r/PerimeterGenerator.cpp index 9f3e1ab2c1..57e1fe4513 100644 --- a/xs/src/libslic3r/PerimeterGenerator.cpp +++ b/xs/src/libslic3r/PerimeterGenerator.cpp @@ -140,7 +140,8 @@ PerimeterGenerator::process() for (ExPolygon &half_thin : half_thins) { //growing back the polygon ExPolygons thin = offset_ex(half_thin, (float)(min_width / 2)); - assert(thin.size() == 1); + assert(thin.size() <= 1); + if (thin.empty()) continue; 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 + overlap), CLIPPER_OFFSET_SCALE, jtSquare, 3)), @@ -152,8 +153,8 @@ PerimeterGenerator::process() 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 - Slic3r::MedialAxis ma{ thin[0], ext_pwidth + ext_pspacing2, min_width, coord_t(this->layer_height) }; + // the maximum thickness of our thin wall area is equal to the minimum thickness of a single loop (*1.1 because of circles approx.) + Slic3r::MedialAxis ma{ thin[0], (ext_pwidth + ext_pspacing2)*1.1, min_width, coord_t(this->layer_height) }; ma.use_bounds(bound) .use_min_real_width((coord_t)scale_(this->ext_perimeter_flow.nozzle_diameter)) .use_tapers(overlap) From da90a83440096da52e8c56aa7a03c804c4bf2efb Mon Sep 17 00:00:00 2001 From: Joseph Lenox Date: Sun, 23 Aug 2020 15:14:56 -0500 Subject: [PATCH 31/31] Change merge to prefer @supermerill implementation --- xs/src/libslic3r/ExPolygon.cpp | 219 --------------------------------- xs/src/libslic3r/ExPolygon.hpp | 1 + 2 files changed, 1 insertion(+), 219 deletions(-) diff --git a/xs/src/libslic3r/ExPolygon.cpp b/xs/src/libslic3r/ExPolygon.cpp index 3767c6adad..a444e6226e 100644 --- a/xs/src/libslic3r/ExPolygon.cpp +++ b/xs/src/libslic3r/ExPolygon.cpp @@ -217,225 +217,6 @@ ExPolygon::remove_point_too_near(const coord_t tolerance) { } } -void -ExPolygon::medial_axis(const ExPolygon &bounds, double max_width, double min_width, ThickPolylines* polylines) const -{ - // init helper object - Slic3r::Geometry::MedialAxis ma(max_width, min_width, this); - ma.lines = this->lines(); - - // compute the Voronoi diagram and extract medial axis polylines - ThickPolylines pp; - ma.build(&pp); - - /* // Commented out debug code - SVG svg("medial_axis.svg"); - svg.draw(*this); - svg.draw(pp); - svg.Close(); - */ - - // Find the maximum width returned; we're going to use this for validating and - // filtering the output segments. - 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. - - bool changes = true; - while (changes) { - changes = false; - for (size_t i = 0; i < pp.size(); ++i) { - ThickPolyline& polyline = pp[i]; - - ThickPolyline* best_candidate = nullptr; - float best_dot = -1; - int best_idx = 0; - - // find another polyline starting here - for (size_t j = i + 1; j < pp.size(); ++j) { - ThickPolyline& other = pp[j]; - if (polyline.last_point().coincides_with(other.last_point())) { - polyline.reverse(); - other.reverse(); - } - else if (polyline.first_point().coincides_with(other.last_point())) { - other.reverse(); - } - else if (polyline.first_point().coincides_with(other.first_point())) { - } - else if (polyline.last_point().coincides_with(other.first_point())) { - polyline.reverse(); - } else { - continue; - } - - //only consider the other if the next point is near us - if (polyline.points.size() < 2 && other.points.size() < 2) continue; - if (!polyline.endpoints.second || !other.endpoints.second) continue; - if (polyline.points.back().distance_to(other.points.back()) > max_width) continue; - if (polyline.points.size() != other.points.size()) continue; - - - Pointf v_poly(polyline.lines().front().vector().x, polyline.lines().front().vector().y); - v_poly.scale(1 / std::sqrt(v_poly.x*v_poly.x + v_poly.y*v_poly.y)); - Pointf v_other(other.lines().front().vector().x, other.lines().front().vector().y); - v_other.scale(1 / std::sqrt(v_other.x*v_other.x + v_other.y*v_other.y)); - float other_dot = v_poly.x*v_other.x + v_poly.y*v_other.y; - if (other_dot > best_dot) { - best_candidate = &other; - best_idx = j; - best_dot = other_dot; - } - } - if (best_candidate != nullptr) { - - //assert polyline.size == best_candidate->size (see selection loop, an 'if' takes care of that) - - //iterate the points - // as voronoi should create symmetric thing, we can iterate synchonously - unsigned int idx_point = 1; - while (idx_point < polyline.points.size() && polyline.points[idx_point].distance_to(best_candidate->points[idx_point]) < max_width) { - //fusion - polyline.points[idx_point].x += best_candidate->points[idx_point].x; - polyline.points[idx_point].x /= 2; - polyline.points[idx_point].y += best_candidate->points[idx_point].y; - polyline.points[idx_point].y /= 2; - polyline.width[idx_point] += best_candidate->width[idx_point]; - ++idx_point; - } - //select if an end occur - polyline.endpoints.second &= best_candidate->endpoints.second; - - //remove points that are the same or too close each other, ie simplify - for (unsigned int idx_point = 1; idx_point < polyline.points.size(); ++idx_point) { - //distance of 1 is on the sclaed coordinates, so it correspond to SCALE_FACTOR, so it's very small - if (polyline.points[idx_point - 1].distance_to(polyline.points[idx_point]) < 1) { - if (idx_point < polyline.points.size() -1) { - polyline.points.erase(polyline.points.begin() + idx_point); - } else { - polyline.points.erase(polyline.points.begin() + idx_point -1); - } - --idx_point; - } - } - //remove points that are outside of the geometry - for (unsigned int idx_point = 0; idx_point < polyline.points.size(); ++idx_point) { - //distance of 1 is on the sclaed coordinates, so it correspond to SCALE_FACTOR, so it's very small - if (!bounds.contains_b(polyline.points[idx_point])) { - polyline.points.erase(polyline.points.begin() + idx_point); - --idx_point; - } - } - if (polyline.points.size() < 2) { - //remove self - pp.erase(pp.begin() + i); - --i; - --best_idx; - } - - pp.erase(pp.begin() + best_idx); - changes = true; - } - } - } - - // Loop through all returned polylines in order to extend their endpoints to the - // expolygon boundaries - 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.front(), polyline.points[1]); - - // prevent the line from touching on the other side, otherwise intersection() might return that solution - if (polyline.points.size() == 2) line.b = line.midpoint(); - - line.extend_start(max_width); - (void)bounds.contour.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.intersection(line, &new_back); - } - polyline.points.front() = new_front; - polyline.points.back() = new_back; - } - - // If we removed any short polylines, we now try to connect consecutive polylines - // in order to allow loop detection. Note that this algorithm is greedier than - // MedialAxis::process_edge_neighbors(), as it will connect random pairs of - // polylines even when more than two start from the same point. This has no - // drawbacks since we optimize later using nearest-neighbor which would do the - // same, but should we use a more sophisticated optimization algorithm. - // We should not connect polylines when more than two meet. - // Optimisation of the old algorithm : Select the most "straight line" choice - // when we merge with an other line at a point with more than two meet. - - 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; - } - } - //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 > (double)tolerance) { - ++id; - } - } - if (this->contour.points.front().distance_to(this->contour.points.back()) < tolerance) { - this->contour.points.erase(this->contour.points.end() -1); - } -} - void ExPolygon::medial_axis(double max_width, double min_width, Polylines* polylines) const { diff --git a/xs/src/libslic3r/ExPolygon.hpp b/xs/src/libslic3r/ExPolygon.hpp index 2a2ae1a682..ae7f64be9e 100644 --- a/xs/src/libslic3r/ExPolygon.hpp +++ b/xs/src/libslic3r/ExPolygon.hpp @@ -47,6 +47,7 @@ class ExPolygon void remove_point_too_near(const coord_t tolerance); ///TODO: remove that function. It's kept only because of perl tests. void medial_axis(double max_width, double min_width, Polylines* polylines) const; + void medial_axis(const ExPolygon &bounds, double max_width, double min_width, ThickPolylines* polylines) const; void get_trapezoids(Polygons* polygons) const; void get_trapezoids(Polygons* polygons, double angle) const; void get_trapezoids2(Polygons* polygons) const;