diff --git a/src/libslic3r/Fill/FillAdaptive.cpp b/src/libslic3r/Fill/FillAdaptive.cpp index b6750d4f8..94b9a2b74 100644 --- a/src/libslic3r/Fill/FillAdaptive.cpp +++ b/src/libslic3r/Fill/FillAdaptive.cpp @@ -776,27 +776,30 @@ static Polylines connect_lines_using_hooks(Polylines &&lines, const ExPolygon &b // Keeping the vector of closest points outside the loop, so the vector does not need to be reallocated. std::vector> closest; + // Pairs of lines touching at one end point. The pair is sorted to make the end point connection test symmetric. + std::vector> lines_touching_at_endpoints; { - // Insert infill lines into rtree, merge close collinear segments split by the infill boundary. + // Insert infill lines into rtree, merge close collinear segments split by the infill boundary, + // collect lines_touching_at_endpoints. double r2_close = Slic3r::sqr(1200.); for (Polyline &poly : lines) { assert(poly.points.size() == 2); if (&poly != lines.data()) { // Join collinear segments separated by a tiny gap. These gaps were likely created by clipping the infill lines with a concave dent in an infill boundary. - auto collinear_segment = [&rtree, &closest, &lines, r2_close](const Point &pt, const Point &pt_other) -> std::pair { + auto collinear_segment = [&rtree, &closest, &lines, &lines_touching_at_endpoints, r2_close](const Point& pt, const Point& pt_other, const Polyline* polyline) -> std::pair { closest.clear(); rtree.query(bgi::nearest(mk_rtree_point(pt), 1), std::back_inserter(closest)); - Polyline &other = lines[closest.front().second]; - double dist2_front = (other.points.front() - pt).cast().squaredNorm(); - double dist2_back = (other.points.back() - pt).cast().squaredNorm(); - double dist2_min = std::min(dist2_front, dist2_back); + const Polyline *other = &lines[closest.front().second]; + double dist2_front = (other->points.front() - pt).cast().squaredNorm(); + double dist2_back = (other->points.back() - pt).cast().squaredNorm(); + double dist2_min = std::min(dist2_front, dist2_back); if (dist2_min < r2_close) { // Don't connect the segments in an opposite direction. - double dist2_min_other = std::min((other.points.front() - pt_other).cast().squaredNorm(), (other.points.back() - pt_other).cast().squaredNorm()); + double dist2_min_other = std::min((other->points.front() - pt_other).cast().squaredNorm(), (other->points.back() - pt_other).cast().squaredNorm()); if (dist2_min_other > dist2_min) { // End points of the two lines are very close, they should have been merged together if they are collinear. Vec2d v1 = (pt_other - pt).cast(); - Vec2d v2 = (other.points.back() - other.points.front()).cast(); + Vec2d v2 = (other->points.back() - other->points.front()).cast(); Vec2d v1n = v1.normalized(); Vec2d v2n = v2.normalized(); // The vectors must not be collinear. @@ -804,19 +807,23 @@ static Polylines connect_lines_using_hooks(Polylines &&lines, const ExPolygon &b if (std::abs(d) > 0.99f) { // Lines are collinear, merge them. rtree.remove(closest.front()); - return std::make_pair(&other, dist2_min == dist2_front); + return std::make_pair(const_cast(other), dist2_min == dist2_front); + } else { + if (polyline > other) + std::swap(polyline, other); + lines_touching_at_endpoints.emplace_back(polyline, other); } } } return std::make_pair(static_cast(nullptr), false); }; - auto collinear_front = collinear_segment(poly.points.front(), poly.points.back()); + auto collinear_front = collinear_segment(poly.points.front(), poly.points.back(), &poly); if (collinear_front.first) { Polyline &other = *collinear_front.first; poly.points.front() = collinear_front.second ? other.points.back() : other.points.front(); other.points.clear(); } - auto collinear_back = collinear_segment(poly.points.back(), poly.points.front()); + auto collinear_back = collinear_segment(poly.points.back(), poly.points.front(), &poly); if (collinear_back.first) { Polyline &other = *collinear_front.first; poly.points.back() = collinear_front.second ? other.points.back() : other.points.front(); @@ -827,6 +834,8 @@ static Polylines connect_lines_using_hooks(Polylines &&lines, const ExPolygon &b } } + sort_remove_duplicates(lines_touching_at_endpoints); + const float scaled_offset = float(scale_(spacing) * 0.7); // 30% overlap std::vector intersections; @@ -868,8 +877,22 @@ static Polylines connect_lines_using_hooks(Polylines &&lines, const ExPolygon &b out = closest.front().second; return out; }; - tjoint_front = has_tjoint(front_point); - tjoint_back = has_tjoint(back_point); + // Refuse to create a T-joint if the infill lines touch at their ends. + auto filter_end_point_connections = [&lines_touching_at_endpoints, &lines, &line](std::optional in) { + std::optional out; + if (in) { + const Polyline *lo = &line; + const Polyline *hi = &lines[in.value()]; + if (lo > hi) + std::swap(lo, hi); + if (! std::binary_search(lines_touching_at_endpoints.begin(), lines_touching_at_endpoints.end(), std::make_pair(lo, hi))) + // Not an end-point connection, it is a valid T-joint. + out = in; + } + return out; + }; + tjoint_front = filter_end_point_connections(has_tjoint(front_point)); + tjoint_back = filter_end_point_connections(has_tjoint(back_point)); } int num_tjoints = int(tjoint_front.has_value()) + int(tjoint_back.has_value()); @@ -951,35 +974,72 @@ static Polylines connect_lines_using_hooks(Polylines &&lines, const ExPolygon &b } } - auto update_merged_polyline = [&lines, &merged_with](Intersection &intersection) { + auto update_merged_polyline_idx = [&merged_with](size_t pl_idx) { // Update the polyline index to index which is merged - size_t intersect_pl_idx = intersection.intersect_pl - lines.data(); - for (size_t last = intersect_pl_idx;;) { + for (size_t last = pl_idx;;) { size_t lower = merged_with[last]; if (lower == last) { - merged_with[intersect_pl_idx] = lower; - intersect_pl_idx = lower; - break; + merged_with[pl_idx] = lower; + return lower; } last = lower; } - + assert(false); + return size_t(0); + }; + auto update_merged_polyline = [&lines, update_merged_polyline_idx](Intersection& intersection) { + // Update the polyline index to index which is merged + size_t intersect_pl_idx = update_merged_polyline_idx(intersection.intersect_pl - lines.data()); intersection.intersect_pl = &lines[intersect_pl_idx]; // After polylines are merged, it is necessary to update "forward" based on if intersect_point is the first or the last point of intersect_pl. if (intersection.fresh()) { assert(intersection.intersect_pl->points.front() == intersection.intersect_point || - intersection.intersect_pl->points.back() == intersection.intersect_point); + intersection.intersect_pl->points.back() == intersection.intersect_point); intersection.front = intersection.intersect_pl->points.front() == intersection.intersect_point; } }; + // Merge polylines touching at their ends. This should be a very rare case, but it happens surprisingly often. + for (auto it = lines_touching_at_endpoints.rbegin(); it != lines_touching_at_endpoints.rend(); ++ it) { + Polyline *pl1 = const_cast(it->first); + Polyline *pl2 = const_cast(it->second); + assert(pl1 < pl2); + // pl1 was visited for the 1st time. + // pl2 may have alread been merged with another polyline, even with this one. + pl2 = &lines[update_merged_polyline_idx(pl2 - lines.data())]; + assert(pl1 <= pl2); + // Avoid closing a loop. + if (pl1 != pl2) { + // Merge the polylines. + assert(pl1 < pl2); + assert(pl1->points.size() >= 2); + assert(pl2->points.size() >= 2); + double d11 = (pl1->points.front() - pl2->points.front()).cast().squaredNorm(); + double d12 = (pl1->points.front() - pl2->points.back()) .cast().squaredNorm(); + double d21 = (pl1->points.back() - pl2->points.front()).cast().squaredNorm(); + double d22 = (pl1->points.back() - pl2->points.back()) .cast().squaredNorm(); + double d1min = std::min(d11, d12); + double d2min = std::min(d21, d22); + if (d1min < d2min) { + pl1->reverse(); + if (d12 == d1min) + pl2->reverse(); + } else if (d22 == d2min) + pl2->reverse(); + pl1->points.back() = (pl1->points.back() + pl2->points.front()) / 2; + pl1->append(pl2->points.begin() + 1, pl2->points.end()); + pl2->points.clear(); + merged_with[pl2 - lines.data()] = pl1 - lines.data(); + } + } + // Keep intersect_line outside the loop, so it does not get reallocated. std::vector> intersect_line; for (size_t min_idx = 0; min_idx < intersections.size();) { - const Vec2d line_dir = intersections[min_idx].closest_line->vector().cast(); intersect_line.clear(); // All the nearest points (T-joints) ending at the same line are projected onto this line. Because of it, it can easily find the nearest point. { + const Vec2d line_dir = intersections[min_idx].closest_line->vector().cast(); size_t max_idx = min_idx; for (; max_idx < intersections.size() && intersections[min_idx].closest_line == intersections[max_idx].closest_line && @@ -987,7 +1047,11 @@ static Polylines connect_lines_using_hooks(Polylines &&lines, const ExPolygon &b ++ max_idx) intersect_line.emplace_back(&intersections[max_idx], line_dir.dot(intersections[max_idx].intersect_point.cast())); min_idx = max_idx; + assert(intersect_line.size() > 0); + // Sort the intersections along line_dir. + std::sort(intersect_line.begin(), intersect_line.end(), [](const auto &i1, const auto &i2) { return i1.second < i2.second; }); } + if (intersect_line.size() == 1) { // Simple case: The current intersection is the only one touching its adjacent line. Intersection &first_i = *intersect_line.front().first; @@ -1007,10 +1071,6 @@ static Polylines connect_lines_using_hooks(Polylines &&lines, const ExPolygon &b continue; } - assert(intersect_line.size() > 1); - // Sort the intersections along line_dir. - std::sort(intersect_line.begin(), intersect_line.end(), [](const auto &i1, const auto &i2) { return i1.second < i2.second; }); - for (size_t first_idx = 0; first_idx < intersect_line.size(); ++ first_idx) { Intersection &first_i = *intersect_line[first_idx].first; update_merged_polyline(first_i);