diff --git a/src/libslic3r/Fill/Fill.cpp b/src/libslic3r/Fill/Fill.cpp index 3e24d502d..95c8bb9b9 100644 --- a/src/libslic3r/Fill/Fill.cpp +++ b/src/libslic3r/Fill/Fill.cpp @@ -37,6 +37,8 @@ struct SurfaceFillParams bool dont_connect = false; // Don't adjust spacing to fill the space evenly. bool dont_adjust = false; + // Length of the infill anchor along the perimeter line. + float anchor_length = std::numeric_limits::max(); // width, height of extrusion, nozzle diameter, is bridge // For the output, for fill generator. @@ -67,6 +69,7 @@ struct SurfaceFillParams RETURN_COMPARE_NON_EQUAL(density); RETURN_COMPARE_NON_EQUAL_TYPED(unsigned, dont_connect); RETURN_COMPARE_NON_EQUAL_TYPED(unsigned, dont_adjust); + RETURN_COMPARE_NON_EQUAL(anchor_length); RETURN_COMPARE_NON_EQUAL(flow.width); RETURN_COMPARE_NON_EQUAL(flow.height); RETURN_COMPARE_NON_EQUAL(flow.nozzle_diameter); @@ -85,6 +88,7 @@ struct SurfaceFillParams this->density == rhs.density && this->dont_connect == rhs.dont_connect && this->dont_adjust == rhs.dont_adjust && + this->anchor_length == rhs.anchor_length && this->flow == rhs.flow && this->extrusion_role == rhs.extrusion_role; } @@ -115,16 +119,17 @@ std::vector group_fills(const Layer &layer) if (surface.surface_type == stInternalVoid) has_internal_voids = true; else { + const PrintRegionConfig ®ion_config = layerm.region()->config(); FlowRole extrusion_role = surface.is_top() ? frTopSolidInfill : (surface.is_solid() ? frSolidInfill : frInfill); bool is_bridge = layer.id() > 0 && surface.is_bridge(); params.extruder = layerm.region()->extruder(extrusion_role); - params.pattern = layerm.region()->config().fill_pattern.value; - params.density = float(layerm.region()->config().fill_density); + params.pattern = region_config.fill_pattern.value; + params.density = float(region_config.fill_density); if (surface.is_solid()) { params.density = 100.f; params.pattern = (surface.is_external() && ! is_bridge) ? - (surface.is_top() ? layerm.region()->config().top_fill_pattern.value : layerm.region()->config().bottom_fill_pattern.value) : + (surface.is_top() ? region_config.top_fill_pattern.value : region_config.bottom_fill_pattern.value) : ipRectilinear; } else if (params.density <= 0) continue; @@ -136,7 +141,7 @@ std::vector group_fills(const Layer &layer) (surface.is_top() ? erTopSolidInfill : erSolidInfill) : erInternalInfill); params.bridge_angle = float(surface.bridge_angle); - params.angle = float(Geometry::deg2rad(layerm.region()->config().fill_angle.value)); + params.angle = float(Geometry::deg2rad(region_config.fill_angle.value)); // calculate the actual flow we'll be using for this infill params.flow = layerm.region()->flow( @@ -165,6 +170,10 @@ std::vector group_fills(const Layer &layer) } else params.spacing = params.flow.spacing(); + params.anchor_length = float(region_config.infill_anchor); + if (region_config.infill_anchor.percent) + params.anchor_length *= 0.01 * params.spacing; + auto it_params = set_surface_params.find(params); if (it_params == set_surface_params.end()) it_params = set_surface_params.insert(it_params, params); @@ -367,8 +376,9 @@ void Layer::make_fills(FillAdaptive::Octree* adaptive_fill_octree, FillAdaptive: // apply half spacing using this flow's own spacing and generate infill FillParams params; - params.density = float(0.01 * surface_fill.params.density); - params.dont_adjust = surface_fill.params.dont_adjust; // false + params.density = float(0.01 * surface_fill.params.density); + params.dont_adjust = surface_fill.params.dont_adjust; // false + params.anchor_length = surface_fill.params.anchor_length; for (ExPolygon &expoly : surface_fill.expolygons) { // Spacing is modified by the filler to indicate adjustments. Reset it for each expolygon. diff --git a/src/libslic3r/Fill/FillAdaptive.cpp b/src/libslic3r/Fill/FillAdaptive.cpp index c96e4e023..188deca7c 100644 --- a/src/libslic3r/Fill/FillAdaptive.cpp +++ b/src/libslic3r/Fill/FillAdaptive.cpp @@ -639,7 +639,6 @@ static inline Intersection* get_nearest_intersection(std::vectorvector().cast().normalized()) * (intersection.left ? scaled_offset : - scaled_offset)).cast()); // Extend the line by a small value to guarantee a collision with adjacent lines offset_line.extend(coord_t(scaled_offset * 1.16)); // / cos(PI/6) @@ -767,8 +766,10 @@ static Polylines connect_lines_using_hooks(Polylines &&lines, const ExPolygon &b lines_src.reserve(lines.size()); std::transform(lines.begin(), lines.end(), std::back_inserter(lines_src), [](const Line& l) { return Polyline{ l.a, l.b }; }); - const float scaled_offset = float(scale_(spacing) * 0.7); // 30% overlap - const float scaled_trim_distance = float(scale_(spacing) * 0.5 * 0.75); // 25% overlap + // 19% overlap, slightly lower than the allowed overlap in Fill::connect_infill() + const float scaled_offset = float(scale_(spacing) * 0.81); + // 25% overlap + const float scaled_trim_distance = float(scale_(spacing) * 0.5 * 0.75); // Keeping the vector of closest points outside the loop, so the vector does not need to be reallocated. std::vector> closest; @@ -1199,9 +1200,6 @@ static Polylines connect_lines_using_hooks(Polylines &&lines, const ExPolygon &b return polylines_out; } -//coord_t get_hook_length(const double spacing) { return coord_t(scale_(spacing)) * 2; } -coord_t get_hook_length(const double spacing) { return coord_t(scale_(spacing)) * 5; } - #ifndef NDEBUG bool has_no_collinear_lines(const Polylines &polylines) { @@ -1323,7 +1321,8 @@ void Filler::_fill_surface_single( } #endif /* ADAPTIVE_CUBIC_INFILL_DEBUG_OUTPUT */ - coord_t hook_length = get_hook_length(this->spacing); + const auto hook_length = coord_t(std::min(scale_(this->spacing * 5), scale_(params.anchor_length))); + Polylines all_polylines_with_hooks = all_polylines.size() > 1 ? connect_lines_using_hooks(std::move(all_polylines), expolygon, this->spacing, hook_length) : std::move(all_polylines); #ifdef ADAPTIVE_CUBIC_INFILL_DEBUG_OUTPUT @@ -1336,7 +1335,7 @@ void Filler::_fill_surface_single( if (params.dont_connect || all_polylines_with_hooks.size() <= 1) append(polylines_out, std::move(all_polylines_with_hooks)); else - connect_infill(chain_polylines(std::move(all_polylines_with_hooks)), expolygon, polylines_out, this->spacing, params, hook_length); + connect_infill(chain_polylines(std::move(all_polylines_with_hooks)), expolygon, polylines_out, this->spacing, params); #ifdef ADAPTIVE_CUBIC_INFILL_DEBUG_OUTPUT { diff --git a/src/libslic3r/Fill/FillBase.cpp b/src/libslic3r/Fill/FillBase.cpp index a3b83e38f..782545114 100644 --- a/src/libslic3r/Fill/FillBase.cpp +++ b/src/libslic3r/Fill/FillBase.cpp @@ -154,377 +154,6 @@ std::pair Fill::_infill_direction(const Surface *surface) const return std::pair(out_angle, out_shift); } -#if 0 -// From pull request "Gyroid improvements" #2730 by @supermerill - -/// cut poly between poly.point[idx_1] & poly.point[idx_1+1] -/// add p1+-width to one part and p2+-width to the other one. -/// add the "new" polyline to polylines (to part cut from poly) -/// p1 & p2 have to be between poly.point[idx_1] & poly.point[idx_1+1] -/// if idx_1 is ==0 or == size-1, then we don't need to create a new polyline. -static void cut_polyline(Polyline &poly, Polylines &polylines, size_t idx_1, Point p1, Point p2) { - //reorder points - if (p1.distance_to_square(poly.points[idx_1]) > p2.distance_to_square(poly.points[idx_1])) { - Point temp = p2; - p2 = p1; - p1 = temp; - } - if (idx_1 == poly.points.size() - 1) { - //shouldn't be possible. - poly.points.erase(poly.points.end() - 1); - } else { - // create new polyline - Polyline new_poly; - //put points in new_poly - new_poly.points.push_back(p2); - new_poly.points.insert(new_poly.points.end(), poly.points.begin() + idx_1 + 1, poly.points.end()); - //erase&put points in poly - poly.points.erase(poly.points.begin() + idx_1 + 1, poly.points.end()); - poly.points.push_back(p1); - //safe test - if (poly.length() == 0) - poly.points = new_poly.points; - else - polylines.emplace_back(new_poly); - } -} - -/// the poly is like a polygon but with first_point != last_point (already removed) -static void cut_polygon(Polyline &poly, size_t idx_1, Point p1, Point p2) { - //reorder points - if (p1.distance_to_square(poly.points[idx_1]) > p2.distance_to_square(poly.points[idx_1])) { - Point temp = p2; - p2 = p1; - p1 = temp; - } - //check if we need to rotate before cutting - if (idx_1 != poly.size() - 1) { - //put points in new_poly - poly.points.insert(poly.points.end(), poly.points.begin(), poly.points.begin() + idx_1 + 1); - poly.points.erase(poly.points.begin(), poly.points.begin() + idx_1 + 1); - } - //put points in poly - poly.points.push_back(p1); - poly.points.insert(poly.points.begin(), p2); -} - -/// check if the polyline from pts_to_check may be at 'width' distance of a point in polylines_blocker -/// it use equally_spaced_points with width/2 precision, so don't worry with pts_to_check number of points. -/// it use the given polylines_blocker points, be sure to put enough of them to be reliable. -/// complexity : N(pts_to_check.equally_spaced_points(width / 2)) x N(polylines_blocker.points) -static bool collision(const Points &pts_to_check, const Polylines &polylines_blocker, const coordf_t width) { - //check if it's not too close to a polyline - coordf_t min_dist_square = width * width * 0.9 - SCALED_EPSILON; - Polyline better_polylines(pts_to_check); - Points better_pts = better_polylines.equally_spaced_points(width / 2); - for (const Point &p : better_pts) { - for (const Polyline &poly2 : polylines_blocker) { - for (const Point &p2 : poly2.points) { - if (p.distance_to_square(p2) < min_dist_square) { - return true; - } - } - } - } - return false; -} - -/// Try to find a path inside polylines that allow to go from p1 to p2. -/// width if the width of the extrusion -/// polylines_blockers are the array of polylines to check if the path isn't blocked by something. -/// complexity: N(polylines.points) + a collision check after that if we finded a path: N(2(p2-p1)/width) x N(polylines_blocker.points) -static Points get_frontier(Polylines &polylines, const Point& p1, const Point& p2, const coord_t width, const Polylines &polylines_blockers, coord_t max_size = -1) { - for (size_t idx_poly = 0; idx_poly < polylines.size(); ++idx_poly) { - Polyline &poly = polylines[idx_poly]; - if (poly.size() <= 1) continue; - - //loop? - if (poly.first_point() == poly.last_point()) { - //polygon : try to find a line for p1 & p2. - size_t idx_11, idx_12, idx_21, idx_22; - idx_11 = poly.closest_point_index(p1); - idx_12 = idx_11; - if (Line(poly.points[idx_11], poly.points[(idx_11 + 1) % (poly.points.size() - 1)]).distance_to(p1) < SCALED_EPSILON) { - idx_12 = (idx_11 + 1) % (poly.points.size() - 1); - } else if (Line(poly.points[(idx_11 > 0) ? (idx_11 - 1) : (poly.points.size() - 2)], poly.points[idx_11]).distance_to(p1) < SCALED_EPSILON) { - idx_11 = (idx_11 > 0) ? (idx_11 - 1) : (poly.points.size() - 2); - } else { - continue; - } - idx_21 = poly.closest_point_index(p2); - idx_22 = idx_21; - if (Line(poly.points[idx_21], poly.points[(idx_21 + 1) % (poly.points.size() - 1)]).distance_to(p2) < SCALED_EPSILON) { - idx_22 = (idx_21 + 1) % (poly.points.size() - 1); - } else if (Line(poly.points[(idx_21 > 0) ? (idx_21 - 1) : (poly.points.size() - 2)], poly.points[idx_21]).distance_to(p2) < SCALED_EPSILON) { - idx_21 = (idx_21 > 0) ? (idx_21 - 1) : (poly.points.size() - 2); - } else { - continue; - } - - - //edge case: on the same line - if (idx_11 == idx_21 && idx_12 == idx_22) { - if (collision(Points() = { p1, p2 }, polylines_blockers, width)) return Points(); - //break loop - poly.points.erase(poly.points.end() - 1); - cut_polygon(poly, idx_11, p1, p2); - return Points() = { Line(p1, p2).midpoint() }; - } - - //compute distance & array for the ++ path - Points ret_1_to_2; - double dist_1_to_2 = p1.distance_to(poly.points[idx_12]); - ret_1_to_2.push_back(poly.points[idx_12]); - size_t max = idx_12 <= idx_21 ? idx_21+1 : poly.points.size(); - for (size_t i = idx_12 + 1; i < max; i++) { - dist_1_to_2 += poly.points[i - 1].distance_to(poly.points[i]); - ret_1_to_2.push_back(poly.points[i]); - } - if (idx_12 > idx_21) { - dist_1_to_2 += poly.points.back().distance_to(poly.points.front()); - ret_1_to_2.push_back(poly.points[0]); - for (size_t i = 1; i <= idx_21; i++) { - dist_1_to_2 += poly.points[i - 1].distance_to(poly.points[i]); - ret_1_to_2.push_back(poly.points[i]); - } - } - dist_1_to_2 += p2.distance_to(poly.points[idx_21]); - - //compute distance & array for the -- path - Points ret_2_to_1; - double dist_2_to_1 = p1.distance_to(poly.points[idx_11]); - ret_2_to_1.push_back(poly.points[idx_11]); - size_t min = idx_22 <= idx_11 ? idx_22 : 0; - for (size_t i = idx_11; i > min; i--) { - dist_2_to_1 += poly.points[i - 1].distance_to(poly.points[i]); - ret_2_to_1.push_back(poly.points[i - 1]); - } - if (idx_22 > idx_11) { - dist_2_to_1 += poly.points.back().distance_to(poly.points.front()); - ret_2_to_1.push_back(poly.points[poly.points.size() - 1]); - for (size_t i = poly.points.size() - 1; i > idx_22; i--) { - dist_2_to_1 += poly.points[i - 1].distance_to(poly.points[i]); - ret_2_to_1.push_back(poly.points[i - 1]); - } - } - dist_2_to_1 += p2.distance_to(poly.points[idx_22]); - - if (max_size < dist_2_to_1 && max_size < dist_1_to_2) { - return Points(); - } - - //choose between the two direction (keep the short one) - if (dist_1_to_2 < dist_2_to_1) { - if (collision(ret_1_to_2, polylines_blockers, width)) return Points(); - //break loop - poly.points.erase(poly.points.end() - 1); - //remove points - if (idx_12 <= idx_21) { - poly.points.erase(poly.points.begin() + idx_12, poly.points.begin() + idx_21 + 1); - if (idx_12 != 0) { - cut_polygon(poly, idx_11, p1, p2); - } //else : already cut at the good place - } else { - poly.points.erase(poly.points.begin() + idx_12, poly.points.end()); - poly.points.erase(poly.points.begin(), poly.points.begin() + idx_21); - cut_polygon(poly, poly.points.size() - 1, p1, p2); - } - return ret_1_to_2; - } else { - if (collision(ret_2_to_1, polylines_blockers, width)) return Points(); - //break loop - poly.points.erase(poly.points.end() - 1); - //remove points - if (idx_22 <= idx_11) { - poly.points.erase(poly.points.begin() + idx_22, poly.points.begin() + idx_11 + 1); - if (idx_22 != 0) { - cut_polygon(poly, idx_21, p1, p2); - } //else : already cut at the good place - } else { - poly.points.erase(poly.points.begin() + idx_22, poly.points.end()); - poly.points.erase(poly.points.begin(), poly.points.begin() + idx_11); - cut_polygon(poly, poly.points.size() - 1, p1, p2); - } - return ret_2_to_1; - } - } else { - //polyline : try to find a line for p1 & p2. - size_t idx_1, idx_2; - idx_1 = poly.closest_point_index(p1); - if (idx_1 < poly.points.size() - 1 && Line(poly.points[idx_1], poly.points[idx_1 + 1]).distance_to(p1) < SCALED_EPSILON) { - } else if (idx_1 > 0 && Line(poly.points[idx_1 - 1], poly.points[idx_1]).distance_to(p1) < SCALED_EPSILON) { - idx_1 = idx_1 - 1; - } else { - continue; - } - idx_2 = poly.closest_point_index(p2); - if (idx_2 < poly.points.size() - 1 && Line(poly.points[idx_2], poly.points[idx_2 + 1]).distance_to(p2) < SCALED_EPSILON) { - } else if (idx_2 > 0 && Line(poly.points[idx_2 - 1], poly.points[idx_2]).distance_to(p2) < SCALED_EPSILON) { - idx_2 = idx_2 - 1; - } else { - continue; - } - - //edge case: on the same line - if (idx_1 == idx_2) { - if (collision(Points() = { p1, p2 }, polylines_blockers, width)) return Points(); - cut_polyline(poly, polylines, idx_1, p1, p2); - return Points() = { Line(p1, p2).midpoint() }; - } - - //create ret array - size_t first_idx = idx_1; - size_t last_idx = idx_2 + 1; - if (idx_1 > idx_2) { - first_idx = idx_2; - last_idx = idx_1 + 1; - } - Points p_ret; - p_ret.insert(p_ret.end(), poly.points.begin() + first_idx + 1, poly.points.begin() + last_idx); - - coordf_t length = 0; - for (size_t i = 1; i < p_ret.size(); i++) length += p_ret[i - 1].distance_to(p_ret[i]); - - if (max_size < length) { - return Points(); - } - - if (collision(p_ret, polylines_blockers, width)) return Points(); - //cut polyline - poly.points.erase(poly.points.begin() + first_idx + 1, poly.points.begin() + last_idx); - cut_polyline(poly, polylines, first_idx, p1, p2); - //order the returned array to be p1->p2 - if (idx_1 > idx_2) { - std::reverse(p_ret.begin(), p_ret.end()); - } - return p_ret; - } - - } - - return Points(); -} - -/// Connect the infill_ordered polylines, in this order, from the back point to the next front point. -/// It uses only the boundary polygons to do so, and can't pass two times at the same place. -/// It avoid passing over the infill_ordered's polylines (preventing local over-extrusion). -/// return the connected polylines in polylines_out. Can output polygons (stored as polylines with first_point = last_point). -/// complexity: worst: N(infill_ordered.points) x N(boundary.points) -/// typical: N(infill_ordered) x ( N(boundary.points) + N(infill_ordered.points) ) -void Fill::connect_infill(Polylines &&infill_ordered, const ExPolygon &boundary, Polylines &polylines_out, const FillParams ¶ms) { - - //TODO: fallback to the quick & dirty old algorithm when n(points) is too high. - Polylines polylines_frontier = to_polylines(((Polygons)boundary)); - - Polylines polylines_blocker; - coord_t clip_size = scale_(this->spacing) * 2; - for (const Polyline &polyline : infill_ordered) { - if (polyline.length() > 2.01 * clip_size) { - polylines_blocker.push_back(polyline); - polylines_blocker.back().clip_end(clip_size); - polylines_blocker.back().clip_start(clip_size); - } - } - - //length between two lines - coordf_t ideal_length = (1 / params.density) * this->spacing; - - Polylines polylines_connected_first; - bool first = true; - for (const Polyline &polyline : infill_ordered) { - if (!first) { - // Try to connect the lines. - Points &pts_end = polylines_connected_first.back().points; - const Point &last_point = pts_end.back(); - const Point &first_point = polyline.points.front(); - if (last_point.distance_to(first_point) < scale_(this->spacing) * 10) { - Points pts_frontier = get_frontier(polylines_frontier, last_point, first_point, scale_(this->spacing), polylines_blocker, (coord_t)scale_(ideal_length) * 2); - if (!pts_frontier.empty()) { - // The lines can be connected. - pts_end.insert(pts_end.end(), pts_frontier.begin(), pts_frontier.end()); - pts_end.insert(pts_end.end(), polyline.points.begin(), polyline.points.end()); - continue; - } - } - } - // The lines cannot be connected. - polylines_connected_first.emplace_back(std::move(polyline)); - - first = false; - } - - Polylines polylines_connected; - first = true; - for (const Polyline &polyline : polylines_connected_first) { - if (!first) { - // Try to connect the lines. - Points &pts_end = polylines_connected.back().points; - const Point &last_point = pts_end.back(); - const Point &first_point = polyline.points.front(); - - Polylines before = polylines_frontier; - Points pts_frontier = get_frontier(polylines_frontier, last_point, first_point, scale_(this->spacing), polylines_blocker); - if (!pts_frontier.empty()) { - // The lines can be connected. - pts_end.insert(pts_end.end(), pts_frontier.begin(), pts_frontier.end()); - pts_end.insert(pts_end.end(), polyline.points.begin(), polyline.points.end()); - continue; - } - } - // The lines cannot be connected. - polylines_connected.emplace_back(std::move(polyline)); - - first = false; - } - - //try to link to nearest point if possible - for (size_t idx1 = 0; idx1 < polylines_connected.size(); idx1++) { - size_t min_idx = 0; - coordf_t min_length = 0; - bool switch_id1 = false; - bool switch_id2 = false; - for (size_t idx2 = idx1 + 1; idx2 < polylines_connected.size(); idx2++) { - double last_first = polylines_connected[idx1].last_point().distance_to_square(polylines_connected[idx2].first_point()); - double first_first = polylines_connected[idx1].first_point().distance_to_square(polylines_connected[idx2].first_point()); - double first_last = polylines_connected[idx1].first_point().distance_to_square(polylines_connected[idx2].last_point()); - double last_last = polylines_connected[idx1].last_point().distance_to_square(polylines_connected[idx2].last_point()); - double min = std::min(std::min(last_first, last_last), std::min(first_first, first_last)); - if (min < min_length || min_length == 0) { - min_idx = idx2; - switch_id1 = (std::min(last_first, last_last) > std::min(first_first, first_last)); - switch_id2 = (std::min(last_first, first_first) > std::min(last_last, first_last)); - min_length = min; - } - } - if (min_idx > idx1 && min_idx < polylines_connected.size()){ - Points pts_frontier = get_frontier(polylines_frontier, - switch_id1 ? polylines_connected[idx1].first_point() : polylines_connected[idx1].last_point(), - switch_id2 ? polylines_connected[min_idx].last_point() : polylines_connected[min_idx].first_point(), - scale_(this->spacing), polylines_blocker); - if (!pts_frontier.empty()) { - if (switch_id1) polylines_connected[idx1].reverse(); - if (switch_id2) polylines_connected[min_idx].reverse(); - Points &pts_end = polylines_connected[idx1].points; - pts_end.insert(pts_end.end(), pts_frontier.begin(), pts_frontier.end()); - pts_end.insert(pts_end.end(), polylines_connected[min_idx].points.begin(), polylines_connected[min_idx].points.end()); - polylines_connected.erase(polylines_connected.begin() + min_idx); - } - } - } - - //try to create some loops if possible - for (Polyline &polyline : polylines_connected) { - Points pts_frontier = get_frontier(polylines_frontier, polyline.last_point(), polyline.first_point(), scale_(this->spacing), polylines_blocker); - if (!pts_frontier.empty()) { - polyline.points.insert(polyline.points.end(), pts_frontier.begin(), pts_frontier.end()); - polyline.points.insert(polyline.points.begin(), polyline.points.back()); - } - polylines_out.emplace_back(polyline); - } -} - -#else - // A single T joint of an infill line to a closed contour or one of its holes. struct ContourIntersectionPoint { // Contour and point on a contour where an infill line is connected to. @@ -579,6 +208,8 @@ struct ContourIntersectionPoint { // Distance from param1 to param2 when going counter-clockwise. static inline float closed_contour_distance_ccw(float param1, float param2, float contour_length) { + assert(param1 >= 0.f && param1 <= contour_length); + assert(param2 >= 0.f && param2 <= contour_length); float d = param2 - param1; if (d < 0.f) d += contour_length; @@ -598,12 +229,7 @@ float path_length_along_contour_ccw(const ContourIntersectionPoint *cp1, const C assert(cp2 != nullptr); assert(cp1->contour_idx == cp2->contour_idx); assert(cp1 != cp2); - // Zero'th param is the length of the contour. - float param_lo = cp1->param; - float param_hi = cp2->param; - assert(param_lo >= 0.f && param_lo <= contour_length); - assert(param_hi >= 0.f && param_hi <= contour_length); - return cp1 < cp2 ? param_hi - param_lo : param_lo + contour_length - param_hi; + return closed_contour_distance_ccw(cp1->param, cp2->param, contour_length); } // Lengths along the contour from cp1 to cp2 going CCW and going CW. @@ -645,6 +271,7 @@ static inline float take_cw_limited(Polyline &pl, const Points &contour, const s // If appending to an infill line, then the start point of a perimeter line shall match the end point of an infill line. assert(pl.empty() || pl.points.back() == contour[idx_start]); assert(contour.size() + 1 == params.size()); + assert(length_to_take > SCALED_EPSILON); // Length of the contour. float length = params.back(); // Parameter (length from contour.front()) for the first point. @@ -700,6 +327,7 @@ static inline float take_ccw_limited(Polyline &pl, const Points &contour, const // If appending to an infill line, then the start point of a perimeter line shall match the end point of an infill line. assert(pl.empty() || pl.points.back() == contour[idx_start]); assert(contour.size() + 1 == params.size()); + assert(length_to_take > SCALED_EPSILON); // Length of the contour. float length = params.back(); // Parameter (length from contour.front()) for the first point. @@ -815,13 +443,15 @@ static void take_limited( // Length of the segment from cp to cp->prev_on_contour. float l = closed_contour_distance_cw(cp->param, cp->prev_on_contour->param, length); length_to_go = std::min(length_to_go, cp->contour_not_taken_length_prev); - if (cp->prev_on_contour->consumed) + //if (cp->prev_on_contour->consumed) // Don't overlap with an already extruded infill line. length_to_go = std::max(0.f, std::min(length_to_go, l - line_half_width)); cp->consume_prev(); if (l >= length_to_go) { - cp->prev_on_contour->trim_next(l - length_to_go); - take_cw_limited(pl1, contour, params, cp->point_idx, cp->prev_on_contour->point_idx, length_to_go); + if (length_to_go > SCALED_EPSILON) { + cp->prev_on_contour->trim_next(l - length_to_go); + take_cw_limited(pl1, contour, params, cp->point_idx, cp->prev_on_contour->point_idx, length_to_go); + } break; } else { cp->prev_on_contour->trim_next(0.f); @@ -833,13 +463,15 @@ static void take_limited( for (ContourIntersectionPoint *cp = cp_start; cp != cp_end; cp = cp->next_on_contour) { float l = closed_contour_distance_ccw(cp->param, cp->next_on_contour->param, length); length_to_go = std::min(length_to_go, cp->contour_not_taken_length_next); - if (cp->next_on_contour->consumed) + //if (cp->next_on_contour->consumed) // Don't overlap with an already extruded infill line. length_to_go = std::max(0.f, std::min(length_to_go, l - line_half_width)); cp->consume_next(); if (l >= length_to_go) { - cp->next_on_contour->trim_prev(l - length_to_go); - take_ccw_limited(pl1, contour, params, cp->point_idx, cp->next_on_contour->point_idx, length_to_go); + if (length_to_go > SCALED_EPSILON) { + cp->next_on_contour->trim_prev(l - length_to_go); + take_ccw_limited(pl1, contour, params, cp->point_idx, cp->next_on_contour->point_idx, length_to_go); + } break; } else { cp->next_on_contour->trim_prev(0.f); @@ -877,14 +509,14 @@ static inline SegmentPoint clip_start_segment_and_point(const Points &polyline, for (size_t i = 1; i < polyline.size(); ++ i) { Vec2d pt = polyline[i].cast(); Vec2d v = pt - pt_prev; - double l2 = v.squaredNorm(); - if (l2 > distance * distance) { - out.idx_segment = i; - out.t = distance / sqrt(l2); + double l = v.norm(); + if (l > distance) { + out.idx_segment = i - 1; + out.t = distance / l; out.point = pt_prev + out.t * v; break; } - distance -= sqrt(l2); + distance -= l; pt_prev = pt; } } @@ -902,16 +534,16 @@ static inline SegmentPoint clip_end_segment_and_point(const Points &polyline, do for (int i = int(polyline.size()) - 2; i >= 0; -- i) { Vec2d pt = polyline[i].cast(); Vec2d v = pt - pt_next; - double l2 = v.squaredNorm(); - if (l2 > distance * distance) { + double l = v.norm(); + if (l > distance) { out.idx_segment = i; - out.t = distance / sqrt(l2); + out.t = distance / l; out.point = pt_next + out.t * v; // Store the parameter referenced to the starting point of a segment. out.t = 1. - out.t; break; } - distance -= sqrt(l2); + distance -= l; pt_next = pt; } } @@ -937,13 +569,9 @@ static inline bool line_rounded_thick_segment_collision( { // Very short line vector. Just test whether the center point is inside the offset line. Vec2d lpt = 0.5 * (line_a + line_b); - if (segment_l > SCALED_EPSILON) { - intersects = (segment_a - lpt).squaredNorm() < offset2; - intersects |= (segment_b - lpt).squaredNorm() < offset2; - if (! intersects) { - - } + struct Linef { Vec2d a, b; }; + intersects = line_alg::distance_to_squared(Linef{ segment_a, segment_b }, lpt) < offset2; } else intersects = (0.5 * (segment_a + segment_b) - lpt).squaredNorm() < offset2; if (intersects) { @@ -986,9 +614,10 @@ static inline bool line_rounded_thick_segment_collision( // where the segment spans (0, 0) to (segment_l, 0). const Vec2d dir_x = segment_v / segment_l; const Vec2d dir_y(- dir_x.y(), dir_x.x()); + const Vec2d line_p0(line_a - segment_a); std::pair interval; if (Geometry::liang_barsky_line_clipping_interval( - Vec2d(line_a - segment_a), + Vec2d(line_p0.dot(dir_x), line_p0.dot(dir_y)), Vec2d(line_v0.dot(dir_x), line_v0.dot(dir_y)), BoundingBoxf(Vec2d(0., - offset), Vec2d(segment_l, offset)), interval)) @@ -1004,6 +633,26 @@ static inline bool line_rounded_thick_segment_collision( } } +#if 0 + { + BoundingBox bbox; + bbox.merge(line_a.cast()); + bbox.merge(line_a.cast()); + bbox.merge(segment_a.cast()); + bbox.merge(segment_b.cast()); + static int iRun = 0; + ::Slic3r::SVG svg(debug_out_path("%s-%03d.svg", "line-thick-segment-intersect", iRun ++), bbox); + svg.draw(Line(line_a.cast(), line_b.cast()), "black"); + svg.draw(Line(segment_a.cast(), segment_b.cast()), "blue", offset * 2.); + svg.draw(segment_a.cast(), "blue", offset); + svg.draw(segment_b.cast(), "blue", offset); + svg.draw(Line(segment_a.cast(), segment_b.cast()), "black"); + if (intersects) + svg.draw(Line((line_a + (line_b - line_a).normalized() * out_interval.first).cast(), + (line_a + (line_b - line_a).normalized() * out_interval.second).cast()), "red"); + } +#endif + return intersects; } @@ -1032,6 +681,132 @@ static inline bool cyclic_interval_inside_interval(float outer_low, float outer_ return interval_inside_interval(outer_low, outer_high, inner_low, inner_high, float(SCALED_EPSILON)); } +// #define INFILL_DEBUG_OUTPUT + +#ifdef INFILL_DEBUG_OUTPUT +static void export_infill_to_svg( + // Boundary contour, along which the perimeter extrusions will be drawn. + const std::vector &boundary, + // Parametrization of boundary with Euclidian length. + const std::vector> &boundary_parameters, + // Intersections (T-joints) of the infill lines with the boundary. + std::vector> &boundary_intersections, + // Infill lines, either completely inside the boundary, or touching the boundary. + const Polylines &infill, + const coord_t scaled_spacing, + const std::string &path, + const Polylines &overlap_lines = Polylines(), + const Polylines &polylines = Polylines(), + const Points &pts = Points()) +{ + Polygons polygons; + std::transform(boundary.begin(), boundary.end(), std::back_inserter(polygons), [](auto &pts) { return Polygon(pts); }); + ExPolygons expolygons = union_ex(polygons); + BoundingBox bbox = get_extents(polygons); + bbox.offset(scale_(3.)); + + ::Slic3r::SVG svg(path, bbox); + // Draw the filled infill polygons. + svg.draw(expolygons); + + // Draw the pieces of boundary allowed to be used as anchors of infill lines, not yet consumed. + const std::string color_boundary_trimmed = "blue"; + const std::string color_boundary_not_trimmed = "yellow"; + const coordf_t boundary_line_width = scaled_spacing; + svg.draw_outline(polygons, "red", boundary_line_width); + for (const std::vector &intersections : boundary_intersections) { + const size_t boundary_idx = &intersections - boundary_intersections.data(); + const Points &contour = boundary[boundary_idx]; + const std::vector &contour_param = boundary_parameters[boundary_idx]; + for (const ContourIntersectionPoint *ip : intersections) { + assert(ip->next_trimmed == ip->next_on_contour->prev_trimmed); + assert(ip->prev_trimmed == ip->prev_on_contour->next_trimmed); + { + Polyline pl { contour[ip->point_idx] }; + if (ip->next_trimmed) { + if (ip->contour_not_taken_length_next > SCALED_EPSILON) { + take_ccw_limited(pl, contour, contour_param, ip->point_idx, ip->next_on_contour->point_idx, ip->contour_not_taken_length_next); + svg.draw(pl, color_boundary_trimmed, boundary_line_width); + } + } else { + take_ccw_full(pl, contour, ip->point_idx, ip->next_on_contour->point_idx); + svg.draw(pl, color_boundary_not_trimmed, boundary_line_width); + } + } + { + Polyline pl { contour[ip->point_idx] }; + if (ip->prev_trimmed) { + if (ip->contour_not_taken_length_prev > SCALED_EPSILON) { + take_cw_limited(pl, contour, contour_param, ip->point_idx, ip->prev_on_contour->point_idx, ip->contour_not_taken_length_prev); + svg.draw(pl, color_boundary_trimmed, boundary_line_width); + } + } else { + take_cw_full(pl, contour, ip->point_idx, ip->prev_on_contour->point_idx); + svg.draw(pl, color_boundary_not_trimmed, boundary_line_width); + } + } + } + } + + // Draw the full infill polygon boundary. + svg.draw_outline(polygons, "green"); + + // Draw the infill lines, first the full length with red color, then a slightly shortened length with black color. + svg.draw(infill, "brown"); + static constexpr double trim_length = scale_(0.15); + for (Polyline polyline : infill) + if (! polyline.empty()) { + Vec2d a = polyline.points.front().cast(); + Vec2d d = polyline.points.back().cast(); + if (polyline.size() == 2) { + Vec2d v = d - a; + double l = v.norm(); + if (l > 2. * trim_length) { + a += v * trim_length / l; + d -= v * trim_length / l; + polyline.points.front() = a.cast(); + polyline.points.back() = d.cast(); + } else + polyline.points.clear(); + } else if (polyline.size() > 2) { + Vec2d b = polyline.points[1].cast(); + Vec2d c = polyline.points[polyline.points.size() - 2].cast(); + Vec2d v = b - a; + double l = v.norm(); + if (l > trim_length) { + a += v * trim_length / l; + polyline.points.front() = a.cast(); + } else + polyline.points.erase(polyline.points.begin()); + v = d - c; + l = v.norm(); + if (l > trim_length) + polyline.points.back() = (d - v * trim_length / l).cast(); + else + polyline.points.pop_back(); + } + svg.draw(polyline, "black"); + } + + svg.draw(overlap_lines, "red", scale_(0.05)); + svg.draw(polylines, "magenta", scale_(0.05)); + svg.draw(pts, "magenta"); +} +#endif // INFILL_DEBUG_OUTPUT + +#ifndef NDEBUG +bool validate_boundary_intersections(const std::vector> &boundary_intersections) +{ + for (const std::vector& contour : boundary_intersections) { + for (ContourIntersectionPoint* ip : contour) { + assert(ip->next_trimmed == ip->next_on_contour->prev_trimmed); + assert(ip->prev_trimmed == ip->prev_on_contour->next_trimmed); + } + } + return true; +} +#endif // NDEBUG + // Mark the segments of split boundary as consumed if they are very close to some of the infill line. void mark_boundary_segments_touching_infill( // Boundary contour, along which the perimeter extrusions will be drawn. @@ -1053,8 +828,17 @@ void mark_boundary_segments_touching_infill( #ifndef NDEBUG for (size_t i = 0; i < boundary.size(); ++ i) assert(boundary[i].size() + 1 == boundary_parameters[i].size()); + assert(validate_boundary_intersections(boundary_intersections)); #endif +#ifdef INFILL_DEBUG_OUTPUT + static int iRun = 0; + ++ iRun; + int iStep = 0; + export_infill_to_svg(boundary, boundary_parameters, boundary_intersections, infill, distance_colliding * 2, debug_out_path("%s-%03d.svg", "FillBase-mark_boundary_segments_touching_infill-start", iRun)); + Polylines perimeter_overlaps; +#endif // INFILL_DEBUG_OUTPUT + EdgeGrid::Grid grid; // Make sure that the the grid is big enough for queries against the thick segment. grid.set_bbox(boundary_bbox.inflated(distance_colliding + SCALED_EPSILON)); @@ -1066,13 +850,17 @@ void mark_boundary_segments_touching_infill( Visitor(const EdgeGrid::Grid &grid, const std::vector &boundary, const std::vector> &boundary_parameters, std::vector> &boundary_intersections, const double radius) : - grid(grid), boundary(boundary), boundary_parameters(boundary_parameters), boundary_intersections(boundary_intersections), radius(radius) {} + grid(grid), boundary(boundary), boundary_parameters(boundary_parameters), boundary_intersections(boundary_intersections), radius(radius), trim_l_threshold(0.5 * radius) {} // Init with a segment of an infill line. void init(const Vec2d &infill_pt1, const Vec2d &infill_pt2) { this->infill_pt1 = &infill_pt1; this->infill_pt2 = &infill_pt2; - } + this->infill_bbox.reset(); + this->infill_bbox.merge(infill_pt1); + this->infill_bbox.merge(infill_pt2); + this->infill_bbox.offset(this->radius + SCALED_EPSILON); + } bool operator()(coord_t iy, coord_t ix) { // Called with a row and colum of the grid cell, which is intersected by a line. @@ -1083,13 +871,23 @@ void mark_boundary_segments_touching_infill( const Vec2d seg_pt1 = segment.first.cast(); const Vec2d seg_pt2 = segment.second.cast(); std::pair interval; - if (line_rounded_thick_segment_collision(seg_pt1, seg_pt2, *this->infill_pt1, *this->infill_pt2, this->radius, interval)) { + BoundingBoxf bbox_seg; + bbox_seg.merge(seg_pt1); + bbox_seg.merge(seg_pt2); +#ifdef INFILL_DEBUG_OUTPUT + //if (this->infill_bbox.overlap(bbox_seg)) this->perimeter_overlaps.push_back({ segment.first, segment.second }); +#endif // INFILL_DEBUG_OUTPUT + if (this->infill_bbox.overlap(bbox_seg) && line_rounded_thick_segment_collision(seg_pt1, seg_pt2, *this->infill_pt1, *this->infill_pt2, this->radius, interval)) { // The boundary segment intersects with the infill segment thickened by radius. // Interval is specified in Euclidian length from seg_pt1 to seg_pt2. // 1) Find the Euclidian parameters of seg_pt1 and seg_pt2 on its boundary contour. const std::vector &contour_parameters = boundary_parameters[it_contour_and_segment->first]; const float contour_length = contour_parameters.back(); const float param_seg_pt1 = contour_parameters[it_contour_and_segment->second]; +#ifdef INFILL_DEBUG_OUTPUT + this->perimeter_overlaps.push_back({ Point((seg_pt1 + (seg_pt2 - seg_pt1).normalized() * interval.first).cast()), + Point((seg_pt1 + (seg_pt2 - seg_pt1).normalized() * interval.second).cast()) }); +#endif // INFILL_DEBUG_OUTPUT const float param_overlap1 = param_seg_pt1 + interval.first; const float param_overlap2 = param_seg_pt1 + interval.second; // 2) Find the ContourIntersectionPoints before param_overlap1 and after param_overlap2. @@ -1107,29 +905,38 @@ void mark_boundary_segments_touching_infill( assert(ip_low != ip_high); // Verify that the interval (param_overlap1, param_overlap2) is inside the interval (ip_low->param, ip_high->param). assert(cyclic_interval_inside_interval(ip_low->param, ip_high->param, param_overlap1, param_overlap2, contour_length)); + assert(validate_boundary_intersections(boundary_intersections)); // Mark all ContourIntersectionPoints between ip_low and ip_high as consumed. if (ip_low->next_on_contour != ip_high) - for (ContourIntersectionPoint *ip = ip_low->next_on_contour; ip->next_on_contour != ip_high; ip = ip->next_on_contour) { + for (ContourIntersectionPoint *ip = ip_low->next_on_contour; ip != ip_high; ip = ip->next_on_contour) { ip->consume_prev(); ip->consume_next(); } // Subtract the interval from the first and last segments. - ip_low->trim_next(closed_contour_distance_ccw(ip_low->param, param_overlap1, contour_length)); - ip_high->trim_prev(closed_contour_distance_ccw(param_overlap2, ip_high->param, contour_length)); + float trim_l = closed_contour_distance_ccw(ip_low->param, param_overlap1, contour_length); + //if (trim_l > trim_l_threshold) + ip_low->trim_next(trim_l); + trim_l = closed_contour_distance_ccw(param_overlap2, ip_high->param, contour_length); + //if (trim_l > trim_l_threshold) + ip_high->trim_prev(trim_l); + assert(ip_low->next_trimmed == ip_high->prev_trimmed); + assert(validate_boundary_intersections(boundary_intersections)); //FIXME mark point as consumed? //FIXME verify the sequence between prev and next? -#if 0 +#ifdef INFILL_DEBUG_OUTPUT { - static size_t iRun = 0; +#if 0 + static size_t iRun = 0; ExPolygon expoly(Polygon(*grid.contours().front())); for (size_t i = 1; i < grid.contours().size(); ++i) expoly.holes.emplace_back(Polygon(*grid.contours()[i])); SVG svg(debug_out_path("%s-%d.svg", "FillBase-mark_boundary_segments_touching_infill", iRun ++).c_str(), get_extents(expoly)); svg.draw(expoly, "green"); svg.draw(Line(segment.first, segment.second), "red"); - svg.draw(Line(this->pt1->cast(), this->pt2->cast()), "magenta"); - } + svg.draw(Line(this->infill_pt1->cast(), this->infill_pt2->cast()), "magenta"); #endif + } +#endif // INFILL_DEBUG_OUTPUT } } // Continue traversing the grid along the edge. @@ -1142,21 +949,34 @@ void mark_boundary_segments_touching_infill( std::vector> &boundary_intersections; // Maximum distance between the boundary and the infill line allowed to consider the boundary not touching the infill line. const double radius; + // Region around the contour / infill line intersection point, where the intersections are ignored. + const float trim_l_threshold; const Vec2d *infill_pt1; const Vec2d *infill_pt2; + BoundingBoxf infill_bbox; + +#ifdef INFILL_DEBUG_OUTPUT + Polylines perimeter_overlaps; +#endif // INFILL_DEBUG_OUTPUT } visitor(grid, boundary, boundary_parameters, boundary_intersections, distance_colliding); BoundingBoxf bboxf(boundary_bbox.min.cast(), boundary_bbox.max.cast()); bboxf.offset(- SCALED_EPSILON); for (const Polyline &polyline : infill) { +#ifdef INFILL_DEBUG_OUTPUT + ++ iStep; +#endif // INFILL_DEBUG_OUTPUT // Clip the infill polyline by the Eucledian distance along the polyline. SegmentPoint start_point = clip_start_segment_and_point(polyline.points, clip_distance); SegmentPoint end_point = clip_end_segment_and_point(polyline.points, clip_distance); if (start_point.valid() && end_point.valid() && (start_point.idx_segment < end_point.idx_segment || (start_point.idx_segment == end_point.idx_segment && start_point.t < end_point.t))) { // The clipped polyline is non-empty. +#ifdef INFILL_DEBUG_OUTPUT + visitor.perimeter_overlaps.clear(); +#endif // INFILL_DEBUG_OUTPUT for (size_t point_idx = start_point.idx_segment; point_idx <= end_point.idx_segment; ++ point_idx) { //FIXME extend the EdgeGrid to suport tracing a thick line. #if 0 @@ -1196,22 +1016,36 @@ void mark_boundary_segments_touching_infill( visitor.init(pt1, pt2); // Simulate tracing of a thick line. This only works reliably if distance_colliding <= grid cell size. Vec2d v = (pt2 - pt1).normalized() * distance_colliding; - Vec2d vperp(-v.y(), v.x()); + Vec2d vperp = perp(v); Vec2d a = pt1 - v - vperp; - Vec2d b = pt1 + v - vperp; + Vec2d b = pt2 + v - vperp; if (Geometry::liang_barsky_line_clipping(a, b, bboxf)) grid.visit_cells_intersecting_line(a.cast(), b.cast(), visitor); a = pt1 - v + vperp; - b = pt1 + v + vperp; + b = pt2 + v + vperp; if (Geometry::liang_barsky_line_clipping(a, b, bboxf)) grid.visit_cells_intersecting_line(a.cast(), b.cast(), visitor); #endif +#ifdef INFILL_DEBUG_OUTPUT +// export_infill_to_svg(boundary, boundary_parameters, boundary_intersections, infill, distance_colliding * 2, debug_out_path("%s-%03d-%03d-%03d.svg", "FillBase-mark_boundary_segments_touching_infill-step", iRun, iStep, int(point_idx)), { polyline }); +#endif // INFILL_DEBUG_OUTPUT } - } +#ifdef INFILL_DEBUG_OUTPUT + Polylines perimeter_overlaps; + export_infill_to_svg(boundary, boundary_parameters, boundary_intersections, infill, distance_colliding * 2, debug_out_path("%s-%03d-%03d.svg", "FillBase-mark_boundary_segments_touching_infill-step", iRun, iStep), visitor.perimeter_overlaps, { polyline }); + append(perimeter_overlaps, std::move(visitor.perimeter_overlaps)); + perimeter_overlaps.clear(); +#endif // INFILL_DEBUG_OUTPUT + } } + +#ifdef INFILL_DEBUG_OUTPUT + export_infill_to_svg(boundary, boundary_parameters, boundary_intersections, infill, distance_colliding * 2, debug_out_path("%s-%03d.svg", "FillBase-mark_boundary_segments_touching_infill-end", iRun), perimeter_overlaps); +#endif // INFILL_DEBUG_OUTPUT + assert(validate_boundary_intersections(boundary_intersections)); } -void Fill::connect_infill(Polylines &&infill_ordered, const ExPolygon &boundary_src, Polylines &polylines_out, const double spacing, const FillParams ¶ms, const int hook_length) +void Fill::connect_infill(Polylines &&infill_ordered, const ExPolygon &boundary_src, Polylines &polylines_out, const double spacing, const FillParams ¶ms) { assert(! boundary_src.contour.points.empty()); auto polygons_src = reserve_vector(boundary_src.holes.size() + 1); @@ -1219,21 +1053,22 @@ void Fill::connect_infill(Polylines &&infill_ordered, const ExPolygon &boundary_ for (const Polygon &polygon : boundary_src.holes) polygons_src.emplace_back(&polygon); - connect_infill(std::move(infill_ordered), polygons_src, get_extents(boundary_src.contour), polylines_out, spacing, params, hook_length); + connect_infill(std::move(infill_ordered), polygons_src, get_extents(boundary_src.contour), polylines_out, spacing, params); } -void Fill::connect_infill(Polylines &&infill_ordered, const Polygons &boundary_src, const BoundingBox &bbox, Polylines &polylines_out, const double spacing, const FillParams ¶ms, const int hook_length) +void Fill::connect_infill(Polylines &&infill_ordered, const Polygons &boundary_src, const BoundingBox &bbox, Polylines &polylines_out, const double spacing, const FillParams ¶ms) { auto polygons_src = reserve_vector(boundary_src.size()); for (const Polygon &polygon : boundary_src) polygons_src.emplace_back(&polygon); - connect_infill(std::move(infill_ordered), polygons_src, bbox, polylines_out, spacing, params, hook_length); + connect_infill(std::move(infill_ordered), polygons_src, bbox, polylines_out, spacing, params); } -void Fill::connect_infill(Polylines &&infill_ordered, const std::vector &boundary_src, const BoundingBox &bbox, Polylines &polylines_out, const double spacing, const FillParams ¶ms, const int hook_length) +void Fill::connect_infill(Polylines &&infill_ordered, const std::vector &boundary_src, const BoundingBox &bbox, Polylines &polylines_out, const double spacing, const FillParams ¶ms) { assert(! infill_ordered.empty()); + const auto anchor_length = float(scale_(params.anchor_length)); #if 0 append(polylines_out, infill_ordered); @@ -1295,9 +1130,9 @@ void Fill::connect_infill(Polylines &&infill_ordered, const std::vectorfirst.contour_idx == idx_contour && it->first.start_point_idx == idx_point; ++ it) { // Add these points to the destination contour. -#ifndef NDEBUG const Polyline &infill_line = infill_ordered[it->second / 2]; const Point &pt = (it->second & 1) ? infill_line.points.back() : infill_line.points.front(); +#ifndef NDEBUG { const Vec2d pt1 = contour_src[idx_point].cast(); const Vec2d pt2 = (idx_point + 1 == contour_src.size() ? contour_src.points.front() : contour_src.points[idx_point + 1]).cast(); @@ -1352,8 +1187,11 @@ void Fill::connect_infill(Polylines &&infill_ordered, const std::vector::max(); }; - const float take_max_length = hook_length > 0.f ? hook_length : std::numeric_limits::max(); + const float take_max_length = anchor_length > 0.f ? anchor_length : std::numeric_limits::max(); const float line_half_width = 0.5f * scale_(spacing); + +#if 0 for (ConnectionCost &connection_cost : connections_sorted) { ContourIntersectionPoint *cp1 = &map_infill_end_point_to_boundary[connection_cost.idx_first * 2 + 1]; ContourIntersectionPoint *cp2 = &map_infill_end_point_to_boundary[(connection_cost.idx_first + 1) * 2]; @@ -1419,17 +1259,16 @@ void Fill::connect_infill(Polylines &&infill_ordered, const std::vectorparam, cp_high->param, boundary_params[cp1->contour_idx].back())) < SCALED_EPSILON); could_connect = ! cp_low->next_trimmed && ! cp_high->prev_trimmed; - if (! could_connect && cp_low->next_on_contour != cp_high) { + if (could_connect && cp_low->next_on_contour != cp_high) { // Other end of cp1, may or may not be on the same contour as cp1. - const ContourIntersectionPoint* cp1prev = cp1 - 1; + const ContourIntersectionPoint *cp1prev = cp1 - 1; // Other end of cp2, may or may not be on the same contour as cp2. - const ContourIntersectionPoint* cp2next = cp2 + 1; - for (auto *cp = cp_low->next_on_contour; cp->next_on_contour != cp_high; cp = cp->next_on_contour) { + const ContourIntersectionPoint *cp2next = cp2 + 1; + for (auto *cp = cp_low->next_on_contour; cp != cp_high; cp = cp->next_on_contour) if (cp->consumed || cp == cp1prev || cp == cp2next || cp->prev_trimmed || cp->next_trimmed) { could_connect = false; break; } - } } } // Indices of the polylines to be connected by a perimeter segment. @@ -1438,7 +1277,7 @@ void Fill::connect_infill(Polylines &&infill_ordered, const std::vectorcontour_idx], cp1, cp2, connection_cost.reversed); @@ -1451,6 +1290,56 @@ void Fill::connect_infill(Polylines &&infill_ordered, const std::vectorcontour_idx], boundary_params[cp1->contour_idx], cp2, cp1, ! connection_cost.reversed, take_max_length, line_half_width); } } +#endif + + struct Arc { + ContourIntersectionPoint *intersection; + float arc_length; + }; + std::vector arches; + arches.reserve(map_infill_end_point_to_boundary.size()); + for (ContourIntersectionPoint &cp : map_infill_end_point_to_boundary) + if (! cp.contour_idx != boundary_idx_unconnected && cp.next_on_contour != &cp && cp.could_connect_next()) + arches.push_back({ &cp, path_length_along_contour_ccw(&cp, cp.next_on_contour, boundary_params[cp.contour_idx].back()) }); + std::sort(arches.begin(), arches.end(), [](const auto &l, const auto &r) { return l.arc_length < r.arc_length; }); + + for (Arc &arc : arches) + if (! arc.intersection->consumed && ! arc.intersection->next_on_contour->consumed) { + // Indices of the polylines to be connected by a perimeter segment. + ContourIntersectionPoint *cp1 = arc.intersection; + ContourIntersectionPoint *cp2 = arc.intersection->next_on_contour; + size_t polyline_idx1 = get_and_update_merged_with(((cp1 - map_infill_end_point_to_boundary.data()) / 2)); + size_t polyline_idx2 = get_and_update_merged_with(((cp2 - map_infill_end_point_to_boundary.data()) / 2)); + const Points &contour = boundary[cp1->contour_idx]; + const std::vector &contour_params = boundary_params[cp1->contour_idx]; + if (polyline_idx1 != polyline_idx2) { + Polyline &polyline1 = infill_ordered[polyline_idx1]; + Polyline &polyline2 = infill_ordered[polyline_idx2]; + if (anchor_length == 0.f || arc.arc_length < anchor_length * 2.5) { + // Not closing a loop, connecting the lines. + assert(contour[cp1->point_idx] == polyline1.points.front() || contour[cp1->point_idx] == polyline1.points.back()); + if (contour[cp1->point_idx] == polyline1.points.front()) + polyline1.reverse(); + assert(contour[cp2->point_idx] == polyline2.points.front() || contour[cp2->point_idx] == polyline2.points.back()); + if (contour[cp2->point_idx] == polyline2.points.back()) + polyline2.reverse(); + take(polyline1, polyline2, contour, cp1, cp2, false); + // Mark the second polygon as merged with the first one. + if (polyline_idx2 < polyline_idx1) { + polyline2 = std::move(polyline1); + polyline1.points.clear(); + merged_with[polyline_idx1] = merged_with[polyline_idx2]; + } else { + polyline2.points.clear(); + merged_with[polyline_idx2] = merged_with[polyline_idx1]; + } + } else { + // Move along the perimeter, but don't take the whole arc. + take_limited(polyline1, contour, contour_params, cp1, cp2, false, anchor_length, line_half_width); + take_limited(polyline2, contour, contour_params, cp2, cp1, true, anchor_length, line_half_width); + } + } + } // Connect the remaining open infill lines to the perimeter lines if possible. for (ContourIntersectionPoint &contour_point : map_infill_end_point_to_boundary) @@ -1471,7 +1360,7 @@ void Fill::connect_infill(Polylines &&infill_ordered, const std::vector::max() || (hook_length > 0.f && l > hook_length * 2.5)) + if (l == std::numeric_limits::max() || (anchor_length > 0.f && l > anchor_length * 2.5)) break; // Take the complete contour. bool reversed = l == lprev; @@ -1525,6 +1414,4 @@ void Fill::connect_infill(Polylines &&infill_ordered, const std::vector float density { 0.f }; + // Length of an infill anchor along the perimeter. + float anchor_length { std::numeric_limits::max() }; + // Don't connect the fill lines around the inner perimeter. bool dont_connect { false }; @@ -124,9 +127,9 @@ protected: virtual std::pair _infill_direction(const Surface *surface) const; public: - static void connect_infill(Polylines &&infill_ordered, const ExPolygon &boundary, Polylines &polylines_out, const double spacing, const FillParams ¶ms, const int hook_length = 0); - static void connect_infill(Polylines &&infill_ordered, const Polygons &boundary, const BoundingBox& bbox, Polylines &polylines_out, const double spacing, const FillParams ¶ms, const int hook_length = 0); - static void connect_infill(Polylines &&infill_ordered, const std::vector &boundary, const BoundingBox &bbox, Polylines &polylines_out, double spacing, const FillParams ¶ms, const int hook_length = 0); + static void connect_infill(Polylines &&infill_ordered, const ExPolygon &boundary, Polylines &polylines_out, const double spacing, const FillParams ¶ms); + static void connect_infill(Polylines &&infill_ordered, const Polygons &boundary, const BoundingBox& bbox, Polylines &polylines_out, const double spacing, const FillParams ¶ms); + static void connect_infill(Polylines &&infill_ordered, const std::vector &boundary, const BoundingBox &bbox, Polylines &polylines_out, double spacing, const FillParams ¶ms); static coord_t _adjust_solid_spacing(const coord_t width, const coord_t distance); diff --git a/src/libslic3r/Fill/FillRectilinear2.cpp b/src/libslic3r/Fill/FillRectilinear2.cpp index a110dd144..0da865ba8 100644 --- a/src/libslic3r/Fill/FillRectilinear2.cpp +++ b/src/libslic3r/Fill/FillRectilinear2.cpp @@ -2778,6 +2778,7 @@ bool FillRectilinear2::fill_surface_by_multilines(const Surface *surface, FillPa return true; Polylines fill_lines; + coord_t line_width = coord_t(scale_(this->spacing)); coord_t line_spacing = coord_t(scale_(this->spacing) / params.density); std::pair rotate_vector = this->_infill_direction(surface); for (const SweepParams &sweep : sweep_params) { @@ -2787,6 +2788,9 @@ bool FillRectilinear2::fill_surface_by_multilines(const Surface *surface, FillPa double angle = rotate_vector.first + sweep.angle_base; ExPolygonWithOffset poly_with_offset(poly_with_offset_base, - angle); BoundingBox bounding_box = poly_with_offset.bounding_box_src(); + // Don't produce infill lines, which fully overlap with the infill perimeter. + coord_t x_min = bounding_box.min.x() + line_width + coord_t(SCALED_EPSILON); + coord_t x_max = bounding_box.max.x() - line_width - coord_t(SCALED_EPSILON); // extend bounding box so that our pattern will be aligned with other layers // Transform the reference point to the rotated coordinate system. Point refpt = rotate_vector.second.rotated(- angle); @@ -2800,20 +2804,23 @@ bool FillRectilinear2::fill_surface_by_multilines(const Surface *surface, FillPa const size_t n_vlines = (bounding_box.max.x() - bounding_box.min.x() + line_spacing - 1) / line_spacing; const double cos_a = cos(angle); const double sin_a = sin(angle); - for (const SegmentedIntersectionLine &vline : slice_region_by_vertical_lines(poly_with_offset, n_vlines, bounding_box.min.x(), line_spacing)) { - for (auto it = vline.intersections.begin(); it != vline.intersections.end();) { - auto it_low = it ++; - assert(it_low->type == SegmentIntersection::OUTER_LOW); - if (it_low->type != SegmentIntersection::OUTER_LOW) - continue; - auto it_high = it; - assert(it_high->type == SegmentIntersection::OUTER_HIGH); - if (it_high->type == SegmentIntersection::OUTER_HIGH) { - fill_lines.emplace_back(Point(vline.pos, it_low->pos()).rotated(cos_a, sin_a), Point(vline.pos, it_high->pos()).rotated(cos_a, sin_a)); - ++ it; + for (const SegmentedIntersectionLine &vline : slice_region_by_vertical_lines(poly_with_offset, n_vlines, bounding_box.min.x(), line_spacing)) + if (vline.pos > x_min) { + if (vline.pos >= x_max) + break; + for (auto it = vline.intersections.begin(); it != vline.intersections.end();) { + auto it_low = it ++; + assert(it_low->type == SegmentIntersection::OUTER_LOW); + if (it_low->type != SegmentIntersection::OUTER_LOW) + continue; + auto it_high = it; + assert(it_high->type == SegmentIntersection::OUTER_HIGH); + if (it_high->type == SegmentIntersection::OUTER_HIGH) { + fill_lines.emplace_back(Point(vline.pos, it_low->pos()).rotated(cos_a, sin_a), Point(vline.pos, it_high->pos()).rotated(cos_a, sin_a)); + ++ it; + } } } - } } if (fill_lines.size() > 1) @@ -2821,11 +2828,8 @@ bool FillRectilinear2::fill_surface_by_multilines(const Surface *surface, FillPa if (params.dont_connect || fill_lines.size() <= 1) append(polylines_out, std::move(fill_lines)); - else { -// coord_t hook_length = 0; - coord_t hook_length = coord_t(scale_(this->spacing)) * 5; - connect_infill(std::move(fill_lines), poly_with_offset_base.polygons_outer, get_extents(surface->expolygon.contour), polylines_out, this->spacing, params, hook_length); - } + else + connect_infill(std::move(fill_lines), poly_with_offset_base.polygons_outer, get_extents(surface->expolygon.contour), polylines_out, this->spacing, params); return true; } diff --git a/src/libslic3r/Preset.cpp b/src/libslic3r/Preset.cpp index 1982a91fb..a245adb89 100644 --- a/src/libslic3r/Preset.cpp +++ b/src/libslic3r/Preset.cpp @@ -427,7 +427,7 @@ const std::vector& Preset::print_options() "infill_extruder", "solid_infill_extruder", "support_material_extruder", "support_material_interface_extruder", "ooze_prevention", "standby_temperature_delta", "interface_shells", "extrusion_width", "first_layer_extrusion_width", "perimeter_extrusion_width", "external_perimeter_extrusion_width", "infill_extrusion_width", "solid_infill_extrusion_width", - "top_infill_extrusion_width", "support_material_extrusion_width", "infill_overlap", "bridge_flow_ratio", "clip_multipart_objects", + "top_infill_extrusion_width", "support_material_extrusion_width", "infill_overlap", "infill_anchor", "bridge_flow_ratio", "clip_multipart_objects", "elefant_foot_compensation", "xy_size_compensation", "threads", "resolution", "wipe_tower", "wipe_tower_x", "wipe_tower_y", "wipe_tower_width", "wipe_tower_rotation_angle", "wipe_tower_bridging", "single_extruder_multi_material_priming", "wipe_tower_no_sparse_layers", "compatible_printers", "compatible_printers_condition", "inherits" diff --git a/src/libslic3r/PrintConfig.cpp b/src/libslic3r/PrintConfig.cpp index ee4a0945e..c6b1fc309 100644 --- a/src/libslic3r/PrintConfig.cpp +++ b/src/libslic3r/PrintConfig.cpp @@ -1050,6 +1050,16 @@ void PrintConfigDef::init_fff_params() def->mode = comAdvanced; def->set_default_value(new ConfigOptionInt(1)); + def = this->add("infill_anchor", coFloatOrPercent); + def->label = L("Length of the infill anchor"); + def->category = L("Advanced"); + def->tooltip = L("Connect an infill line to an internal perimeter with a short segment of an additional perimeter. " + "If expressed as percentage (example: 15%) it is calculated over infill extrusion width."); + def->sidetext = L("mm or %"); + def->ratio_over = "infill_extrusion_width"; + def->mode = comAdvanced; + def->set_default_value(new ConfigOptionFloatOrPercent(300, true)); + def = this->add("infill_extruder", coInt); def->label = L("Infill extruder"); def->category = L("Extruders"); diff --git a/src/libslic3r/PrintConfig.hpp b/src/libslic3r/PrintConfig.hpp index 89c9c7a97..790e15af6 100644 --- a/src/libslic3r/PrintConfig.hpp +++ b/src/libslic3r/PrintConfig.hpp @@ -530,6 +530,7 @@ public: ConfigOptionPercent fill_density; ConfigOptionEnum fill_pattern; ConfigOptionFloat gap_fill_speed; + ConfigOptionFloatOrPercent infill_anchor; ConfigOptionInt infill_extruder; ConfigOptionFloatOrPercent infill_extrusion_width; ConfigOptionInt infill_every_layers; @@ -581,6 +582,7 @@ protected: OPT_PTR(fill_density); OPT_PTR(fill_pattern); OPT_PTR(gap_fill_speed); + OPT_PTR(infill_anchor); OPT_PTR(infill_extruder); OPT_PTR(infill_extrusion_width); OPT_PTR(infill_every_layers); diff --git a/src/libslic3r/PrintObject.cpp b/src/libslic3r/PrintObject.cpp index db654bb34..3937f4fed 100644 --- a/src/libslic3r/PrintObject.cpp +++ b/src/libslic3r/PrintObject.cpp @@ -590,7 +590,7 @@ bool PrintObject::invalidate_state_by_config_options(const std::vectorappend_single_option_line("fill_pattern", category_path + "fill-pattern"); optgroup->append_single_option_line("top_fill_pattern", category_path + "top-fill-pattern"); optgroup->append_single_option_line("bottom_fill_pattern", category_path + "bottom-fill-pattern"); + optgroup->append_single_option_line("infill_anchor", category_path + "fill-pattern"); optgroup = page->new_optgroup(L("Ironing")); optgroup->append_single_option_line("ironing"); diff --git a/xs/lib/Slic3r/XS.pm b/xs/lib/Slic3r/XS.pm index 3073d068e..fa4dde43a 100644 --- a/xs/lib/Slic3r/XS.pm +++ b/xs/lib/Slic3r/XS.pm @@ -145,7 +145,6 @@ package Slic3r::Filler; sub fill_surface { my ($self, $surface, %args) = @_; $self->set_density($args{density}) if defined($args{density}); - $self->set_dont_connect($args{dont_connect}) if defined($args{dont_connect}); $self->set_dont_adjust($args{dont_adjust}) if defined($args{dont_adjust}); $self->set_complete($args{complete}) if defined($args{complete}); return $self->_fill_surface($surface); diff --git a/xs/xsp/Filler.xsp b/xs/xsp/Filler.xsp index 647d851eb..caeb2af9a 100644 --- a/xs/xsp/Filler.xsp +++ b/xs/xsp/Filler.xsp @@ -34,8 +34,6 @@ void set_density(float density) %code{% THIS->params.density = density; %}; - void set_dont_connect(bool dont_connect) - %code{% THIS->params.dont_connect = dont_connect; %}; void set_dont_adjust(bool dont_adjust) %code{% THIS->params.dont_adjust = dont_adjust; %}; void set_complete(bool complete)