diff --git a/src/libslic3r/Clipper2Utils.cpp b/src/libslic3r/Clipper2Utils.cpp index 9f1f678a9c..12fd867500 100644 --- a/src/libslic3r/Clipper2Utils.cpp +++ b/src/libslic3r/Clipper2Utils.cpp @@ -35,6 +35,11 @@ Clipper2Lib::Paths64 Slic3rPoints_to_Paths64(const Container& in) return out; } +Clipper2Lib::Paths64 Slic3rPolylines_to_Paths64(const Polylines& in) +{ + return Slic3rPoints_to_Paths64(in); +} + Points Path64ToPoints(const Clipper2Lib::Path64& path64) { Points points; diff --git a/src/libslic3r/Clipper2Utils.hpp b/src/libslic3r/Clipper2Utils.hpp index c5485ad1bd..54b48d6bd7 100644 --- a/src/libslic3r/Clipper2Utils.hpp +++ b/src/libslic3r/Clipper2Utils.hpp @@ -4,9 +4,12 @@ #include "ExPolygon.hpp" #include "Polygon.hpp" #include "Polyline.hpp" +#include "clipper2/clipper.h" namespace Slic3r { +Clipper2Lib::Paths64 Slic3rPolylines_to_Paths64(const Slic3r::Polylines& in); +Slic3r::Polylines Paths64_to_polylines(const Clipper2Lib::Paths64& in); Slic3r::Polylines intersection_pl_2(const Slic3r::Polylines& subject, const Slic3r::Polygons& clip); Slic3r::Polylines diff_pl_2(const Slic3r::Polylines& subject, const Slic3r::Polygons& clip); ExPolygons union_ex_2(const Polygons &expolygons); diff --git a/src/libslic3r/Fill/Fill3DHoneycomb.cpp b/src/libslic3r/Fill/Fill3DHoneycomb.cpp index 34a1ff1b50..de4aaeacd3 100644 --- a/src/libslic3r/Fill/Fill3DHoneycomb.cpp +++ b/src/libslic3r/Fill/Fill3DHoneycomb.cpp @@ -200,6 +200,10 @@ void Fill3DHoneycomb::_fill_surface_single( if (std::abs(infill_angle) >= EPSILON) expolygon.rotate(-infill_angle); BoundingBox bb = expolygon.contour.bounding_box(); + // Expand the bounding box to avoid artifacts at the edges + coord_t expand = 5 * (scale_(this->spacing)); + bb.offset(expand); + // Note: with equally-scaled X/Y/Z, the pattern will create a vertically-stretched // truncated octahedron; so Z is pre-adjusted first by scaling by sqrt(2) coordf_t zScale = sqrt(2); diff --git a/src/libslic3r/Fill/FillAdaptive.cpp b/src/libslic3r/Fill/FillAdaptive.cpp index ed3bfcf0a0..69748ae9c6 100644 --- a/src/libslic3r/Fill/FillAdaptive.cpp +++ b/src/libslic3r/Fill/FillAdaptive.cpp @@ -1371,42 +1371,47 @@ void Filler::_fill_surface_single( all_polylines.reserve(lines.size()); std::transform(lines.begin(), lines.end(), std::back_inserter(all_polylines), [](const Line& l) { return Polyline{ l.a, l.b }; }); - // Apply multiline offset if needed - multiline_fill(all_polylines, params, spacing); + // Apply multiline offset if needed + multiline_fill(all_polylines, params, spacing); // Crop all polylines all_polylines = intersection_pl(std::move(all_polylines), expolygon); #endif } - // After intersection_pl some polylines with only one line are split into more lines - for (Polyline &polyline : all_polylines) { - //FIXME assert that all the points are collinear and in between the start and end point. - if (polyline.points.size() > 2) - polyline.points.erase(polyline.points.begin() + 1, polyline.points.end() - 1); - } -// assert(has_no_collinear_lines(all_polylines)); + if (params.multiline == 1) { + // After intersection_pl some polylines with only one line are split into more lines + for (Polyline& polyline : all_polylines) { + // FIXME assert that all the points are collinear and in between the start and end point. + if (polyline.points.size() > 2) + polyline.points.erase(polyline.points.begin() + 1, polyline.points.end() - 1); + } + // assert(has_no_collinear_lines(all_polylines)); #ifdef ADAPTIVE_CUBIC_INFILL_DEBUG_OUTPUT - { - static int iRun = 0; - export_infill_lines_to_svg(expolygon, all_polylines, debug_out_path("FillAdaptive-initial-%d.svg", iRun++)); - } + { + static int iRun = 0; + export_infill_lines_to_svg(expolygon, all_polylines, debug_out_path("FillAdaptive-initial-%d.svg", iRun++)); + } #endif /* ADAPTIVE_CUBIC_INFILL_DEBUG_OUTPUT */ - const auto hook_length = coordf_t(std::min(std::numeric_limits::max(), scale_(params.anchor_length))); - const auto hook_length_max = coordf_t(std::min(std::numeric_limits::max(), scale_(params.anchor_length_max))); + const auto hook_length = coordf_t(std::min(std::numeric_limits::max(), scale_(params.anchor_length))); + const auto hook_length_max = coordf_t(std::min(std::numeric_limits::max(), scale_(params.anchor_length_max))); Polylines all_polylines_with_hooks = all_polylines.size() > 1 ? connect_lines_using_hooks(std::move(all_polylines), expolygon, this->spacing, hook_length, hook_length_max) : std::move(all_polylines); #ifdef ADAPTIVE_CUBIC_INFILL_DEBUG_OUTPUT - { - static int iRun = 0; - export_infill_lines_to_svg(expolygon, all_polylines_with_hooks, debug_out_path("FillAdaptive-hooks-%d.svg", iRun++)); - } + { + static int iRun = 0; + export_infill_lines_to_svg(expolygon, all_polylines_with_hooks, debug_out_path("FillAdaptive-hooks-%d.svg", iRun++)); + } #endif /* ADAPTIVE_CUBIC_INFILL_DEBUG_OUTPUT */ - chain_or_connect_infill(std::move(all_polylines_with_hooks), expolygon, polylines_out, this->spacing, params); + chain_or_connect_infill(std::move(all_polylines_with_hooks), expolygon, polylines_out, this->spacing, params); + } else { + // if multiline is > 1 infill is ready to connect + chain_or_connect_infill(std::move(all_polylines), 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 3a9412119f..c585329079 100644 --- a/src/libslic3r/Fill/FillBase.cpp +++ b/src/libslic3r/Fill/FillBase.cpp @@ -3,6 +3,7 @@ #include #include "../ClipperUtils.hpp" +#include "../Clipper2Utils.hpp" #include "../EdgeGrid.hpp" #include "../Geometry.hpp" #include "../Geometry/Circle.hpp" @@ -2699,60 +2700,77 @@ void Fill::connect_base_support(Polylines &&infill_ordered, const Polygons &boun connect_base_support(std::move(infill_ordered), polygons_src, bbox, polylines_out, spacing, params); } -//Fill Multiline +// Fill Multiline -Clipper2 version void multiline_fill(Polylines& polylines, const FillParams& params, float spacing) { - if (params.multiline > 1) { - const int n_lines = params.multiline; - const int n_polylines = static_cast(polylines.size()); - Polylines all_polylines; - all_polylines.reserve(n_lines * n_polylines); + if (params.multiline <= 1) + return; - const float center = (n_lines - 1) / 2.0f; + const int n_lines = params.multiline; + const int n_polylines = static_cast(polylines.size()); + Polylines all_polylines; + all_polylines.reserve(n_lines * n_polylines); - for (int line = 0; line < n_lines; ++line) { - float offset = scale_((static_cast(line) - center) * spacing); + // Remove invalid polylines + polylines.erase(std::remove_if(polylines.begin(), polylines.end(), + [](const Polyline& p) { return p.size() < 2; }), + polylines.end()); - for (const Polyline& pl : polylines) { - const size_t n = pl.points.size(); - if (n < 2) { - all_polylines.emplace_back(pl); - continue; - } + if (polylines.empty()) + return; + // Convert source polylines to Clipper2 paths + Clipper2Lib::Paths64 subject_paths = Slic3rPolylines_to_Paths64(polylines); - Points new_points; - new_points.reserve(n); - for (size_t i = 0; i < n; ++i) { - Vec2f tangent; - // For the first and last point, if the polyline is a - // closed loop, get the tangent from the points on either - // side of the join, otherwise just use the first or last - // line. - if (i == 0) { - if (pl.points[0] == pl.points[n-1]) { - tangent = (pl.points[1] - pl.points[n-2]).template cast().normalized(); - } else { - tangent = (pl.points[1] - pl.points[0]).template cast().normalized(); - } - } else if (i == n - 1) { - if (pl.points[0] == pl.points[n-1]) { - tangent = (pl.points[1] - pl.points[n-2]).template cast().normalized(); - } else { - tangent = (pl.points[n-1] - pl.points[n-2]).template cast().normalized(); - } - } else - tangent = (pl.points[i+1] - pl.points[i-1]).template cast().normalized(); - Vec2f normal(-tangent.y(), tangent.x()); + const double miter_limit = 2.0; + const int rings = n_lines / 2; - Point p = pl.points[i] + (normal * offset).template cast(); - new_points.push_back(p); - } + // Compute offsets (in units of spacing) + std::vector offsets; + offsets.reserve(n_lines); - all_polylines.emplace_back(std::move(new_points)); - } - } - polylines = std::move(all_polylines); + if (n_lines % 2 != 0) { + // Odd: center line at offset = 0 + offsets.push_back(0.0); + + for (int i = 1; i <= rings; ++i) + offsets.push_back(i * spacing); + } else { + // Even: no center, start at 0.5 * spacing + double start = 0.5 * spacing; + for (int i = 0; i < rings; ++i) + offsets.push_back(start + i * spacing); } + + // Process each offset + Clipper2Lib::ClipperOffset offsetter(miter_limit); + offsetter.AddPaths(subject_paths, Clipper2Lib::JoinType::Round, Clipper2Lib::EndType::Round); + + for (double t : offsets) { + if (t == 0.0) { + // Center line (only applies when n_lines is odd) + all_polylines.insert(all_polylines.end(), polylines.begin(), polylines.end()); + continue; + } + + // ClipperOffset with current offset distance (union is not needed here) + Clipper2Lib::Paths64 offset_paths; + offsetter.Execute(scale_(t), offset_paths); + if (offset_paths.empty()) + continue; + + // Convert back to polylines + Polylines new_polylines = Paths64_to_polylines(offset_paths); + + for (Polyline& pl : new_polylines) { + if (pl.points.size() < 3) + continue; + if (pl.points.front() != pl.points.back()) + pl.points.push_back(pl.points.front()); + all_polylines.emplace_back(std::move(pl)); + } + } + + polylines = std::move(all_polylines); } } // namespace Slic3r diff --git a/src/libslic3r/Fill/FillConcentric.cpp b/src/libslic3r/Fill/FillConcentric.cpp index 93a54a0739..4f2a0b8970 100644 --- a/src/libslic3r/Fill/FillConcentric.cpp +++ b/src/libslic3r/Fill/FillConcentric.cpp @@ -19,7 +19,7 @@ void FillConcentric::_fill_surface_single( // no rotation is supported for this infill pattern BoundingBox bounding_box = expolygon.contour.bounding_box(); - coord_t min_spacing = scale_(this->spacing); + coord_t min_spacing = scale_(this->spacing) * params.multiline; coord_t distance = coord_t(min_spacing / params.density); if (params.density > 0.9999f && !params.dont_adjust) { @@ -27,8 +27,12 @@ void FillConcentric::_fill_surface_single( this->spacing = unscale(distance); } - Polygons loops = to_polygons(expolygon); - ExPolygons last { std::move(expolygon) }; + // Contract surface polygon by half line width to avoid excesive overlap with perimeter + ExPolygons contracted = offset_ex(expolygon, -float(scale_(0.5 * (params.multiline - 1) * this->spacing ))); + + Polygons loops = to_polygons(contracted); + + ExPolygons last { std::move(contracted) }; while (! last.empty()) { last = offset2_ex(last, -(distance + min_spacing/2), +min_spacing/2); append(loops, to_polygons(last)); @@ -46,6 +50,9 @@ void FillConcentric::_fill_surface_single( last_pos = polylines_out.back().last_point(); } + // Apply multiline offset if needed + multiline_fill(polylines_out, params, spacing); + // clip the paths to prevent the extruder from getting exactly on the first point of the loop // Keep valid paths only. size_t j = iPathFirst; diff --git a/src/libslic3r/Fill/FillHoneycomb.cpp b/src/libslic3r/Fill/FillHoneycomb.cpp index e750425a82..a595cdb664 100644 --- a/src/libslic3r/Fill/FillHoneycomb.cpp +++ b/src/libslic3r/Fill/FillHoneycomb.cpp @@ -74,7 +74,7 @@ void FillHoneycomb::_fill_surface_single( } } // Apply multiline offset if needed - multiline_fill(all_polylines, params, 1.1 * spacing); + multiline_fill(all_polylines, params, spacing); all_polylines = intersection_pl(std::move(all_polylines), expolygon); chain_or_connect_infill(std::move(all_polylines), expolygon, polylines_out, this->spacing, params); diff --git a/src/libslic3r/Fill/FillPlanePath.cpp b/src/libslic3r/Fill/FillPlanePath.cpp index 6044ba43a2..b4681d05f9 100644 --- a/src/libslic3r/Fill/FillPlanePath.cpp +++ b/src/libslic3r/Fill/FillPlanePath.cpp @@ -81,6 +81,9 @@ void FillPlanePath::_fill_surface_single( BoundingBox snug_bounding_box = get_extents(expolygon).inflated(SCALED_EPSILON); + // Expand the bounding box to avoid artifacts at the edges + snug_bounding_box.offset(scale_(this->spacing)*params.multiline); + // Rotated bounding box of the area to fill in with the pattern. BoundingBox bounding_box = align ? // Sparse infill needs to be aligned across layers. Align infill across layers using the object's bounding box. @@ -97,7 +100,7 @@ void FillPlanePath::_fill_surface_single( Polyline polyline; { - auto distance_between_lines = scaled(this->spacing) / params.density; + auto distance_between_lines = scaled(this->spacing) * params.multiline / params.density; auto min_x = coord_t(ceil(coordf_t(bounding_box.min.x()) / distance_between_lines)); auto min_y = coord_t(ceil(coordf_t(bounding_box.min.y()) / distance_between_lines)); auto max_x = coord_t(ceil(coordf_t(bounding_box.max.x()) / distance_between_lines)); @@ -117,8 +120,13 @@ void FillPlanePath::_fill_surface_single( } } + Polylines polylines = {polyline}; + + // Apply multiline offset if needed + multiline_fill(polylines, params, spacing); + if (polyline.size() >= 2) { - Polylines polylines = intersection_pl(polyline, expolygon); + polylines = intersection_pl(std::move(polylines), expolygon); if (!polylines.empty()) { Polylines chained; if (params.dont_connect() || params.density > 0.5) { diff --git a/src/libslic3r/Fill/FillRectilinear.cpp b/src/libslic3r/Fill/FillRectilinear.cpp index cfb9c32300..775b3e8b42 100644 --- a/src/libslic3r/Fill/FillRectilinear.cpp +++ b/src/libslic3r/Fill/FillRectilinear.cpp @@ -3000,7 +3000,7 @@ bool FillRectilinear::fill_surface_by_multilines(const Surface *surface, FillPar params.density /= double(sweep_params.size()); assert(params.density > 0.0001f && params.density <= 1.f); - ExPolygonWithOffset poly_with_offset_base(surface->expolygon, 0, float(scale_(this->overlap - 0.5 * this->spacing))); + ExPolygonWithOffset poly_with_offset_base(surface->expolygon, 0, float(scale_(this->overlap + 0.5 * params.multiline * this->spacing)));//increase offset to crop infill lines when using multiline infill if (poly_with_offset_base.n_contours == 0) // Not a single infill line fits. return true; @@ -3012,19 +3012,24 @@ bool FillRectilinear::fill_surface_by_multilines(const Surface *surface, FillPar for (const SweepParams &sweep : sweep_params) { // Rotate polygons so that we can work with vertical lines here float angle = rotate_vector.first + sweep.angle_base; - //Fill Multiline - for (int i = 0; i < params.multiline; ++i) { - coord_t group_offset = i * line_spacing; - coord_t internal_offset = (i - (params.multiline - 1) / 2.0f) * line_width; - coord_t total_offset = group_offset + internal_offset; - coord_t pattern_shift = scale_(sweep.pattern_shift + unscale_(total_offset)); - make_fill_lines(ExPolygonWithOffset(poly_with_offset_base, -angle), rotate_vector.second.rotated(-angle), angle, - line_width + coord_t(SCALED_EPSILON), line_spacing, pattern_shift, fill_lines); - } + make_fill_lines(ExPolygonWithOffset(poly_with_offset_base, -angle), rotate_vector.second.rotated(-angle), angle, + line_width + coord_t(SCALED_EPSILON), line_spacing, coord_t(scale_(sweep.pattern_shift)), fill_lines); } + + // Apply multiline offset if needed + multiline_fill(fill_lines, params, spacing); + + // Contract surface polygon by half line width to avoid excesive overlap with perimeter + ExPolygons contracted = offset_ex(surface->expolygon, -float(scale_(0.5 * this->spacing))); + + // if contraction results in empty polygon, use original surface + const ExPolygon &intersection_surface = contracted.empty() ? surface->expolygon : contracted.front(); -if ((params.pattern == ipLateralLattice || params.pattern == ipLateralHoneycomb ) && params.multiline >1 ) + // Intersect polylines with perimeter + fill_lines = intersection_pl(std::move(fill_lines), intersection_surface); + + if ((params.pattern == ipLateralLattice || params.pattern == ipLateralHoneycomb ) && params.multiline >1 ) remove_overlapped(fill_lines, line_width); if (!fill_lines.empty()) { @@ -3033,7 +3038,260 @@ if ((params.pattern == ipLateralLattice || params.pattern == ipLateralHoneycomb fill_lines = chain_polylines(std::move(fill_lines)); append(polylines_out, std::move(fill_lines)); } else - connect_infill(std::move(fill_lines), poly_with_offset_base.polygons_outer, get_extents(surface->expolygon.contour), polylines_out, this->spacing, params); + connect_infill(std::move(fill_lines), intersection_surface, polylines_out, this->spacing, params); + } + + return true; +} + +bool FillRectilinear::fill_surface_trapezoidal( + const Surface* surface, + FillParams params, + const std::initializer_list& sweep_params, + Polylines& polylines_out, + int Pattern_type) // 0=grid, 1=triangular +{ + assert(params.multiline > 1); + + Polylines polylines; + + // Common parameters + const coord_t d1 = coord_t(scale_(this->spacing)) * params.multiline; // Infill total wall thickness + + // Pattern-specific parameters + coord_t period; + double base_angle; + std::pair rotate_vector = this->_infill_direction(surface); + + if (Pattern_type == 0) { + // Grid pattern parameters + period = coord_t((2.0 * d1 / params.density) * std::sqrt(2.0)); + base_angle = rotate_vector.first + M_PI_4; // 45 + } else { + // Triangular pattern parameters + period = coord_t(( 2.0 * d1 / params.density) * std::sqrt(3.0)); + base_angle = rotate_vector.first + M_PI_2; //90 + } + + // Obtain the expolygon and rotate to align with pattern base angle + ExPolygon expolygon = surface->expolygon; + if (std::abs(base_angle) >= EPSILON) { + expolygon.rotate(-base_angle, rotate_vector.second); + } + + // Use extended object bounding box for consistent pattern across layers + BoundingBox bb = this->extended_object_bounding_box(); + + switch (Pattern_type) { + case 0: // Grid / Trapezoidal + { + // Generate a non-crossing trapezoidal pattern to avoid overextrusion at intersections when `multiline > 1`. + // P1--P2 + // / \ + // P0/ \P3__P4 + // + // P1x-P2x=P3x-P4x=d1 + // P0y-P1y=P2y-P3y=d2 + + const coord_t d2 = coord_t(0.5 * period - d1); + + // Align bounding box to the grid + bb.merge(align_to_grid(bb.min, Point(period, period))); + const coord_t xmin = bb.min.x(); + const coord_t xmax = bb.max.x(); + const coord_t ymin = bb.min.y(); + const coord_t ymax = bb.max.y(); + + // Create the two base row patterns once + Polyline base_row_normal; + base_row_normal.points.reserve(((xmax - xmin) / period + 1) * 5); // 5 points per trapezoid + Polyline base_row_flipped; + base_row_flipped.points.reserve(((xmax - xmin) / period + 1) * 5); // 5 points per trapezoid + + // Build complete rows from xmin to xmax + for (coord_t x = xmin; x < xmax; x += period) { + // Normal row + base_row_normal.points.emplace_back(Point(x, d1 / 2)); // P0 + base_row_normal.points.emplace_back(Point(x + d1, d1 / 2)); // P1 + base_row_normal.points.emplace_back(Point(x + d1 + d2, d1 / 2 + d2)); // P2 + base_row_normal.points.emplace_back(Point(x + 2 * d1 + d2, d1 / 2 + d2)); // P3 + base_row_normal.points.emplace_back(Point(x + 2 * d1 + 2 * d2, d1 / 2)); // P4 + } + + // Flipped row (mirrored vertically) + base_row_flipped.points = base_row_normal.points; + for (auto& p : base_row_flipped.points) { + p.y() = period / 2 - p.y(); + } + + // Pre-allocate polylines + const size_t estimated_rows = ((ymax - ymin) / (period / 2) + 1); + polylines.reserve(estimated_rows); + + bool flip_vertical = false; + + // Now just copy and translate vertically + for (coord_t y = ymin; y < ymax; y += period / 2) { + Polyline pl_row = flip_vertical ? base_row_flipped : base_row_normal; + + // Translate all points vertically + for (Point& p : pl_row.points) { + p.y() += y; + } + + polylines.emplace_back(std::move(pl_row)); + flip_vertical = !flip_vertical; + } + + // transpose points for odd layers + if (layer_id % 2 == 1) { + for (Polyline& pl : polylines) { + for (Point& p : pl.points) { + std::swap(p.x(), p.y()); + p.x() += d1 / 2; + p.y() -= d1 / 2; + } + } + } + break; + } + + case 1: // Triangular + { + // Generate a non-crossing trapezoidal pattern with a base line below. + // P1-P2 + // / \ + // P0/ \P3_P4 + // ---------------- + // P1x-P2x=P3x-P4x=d2 + // P0y-P1y=P2y-P3y=h-2d1 + // + + // Triangular pattern density adjustment: + const coord_t d2_tri = coord_t(2.0 / std::sqrt(3.0) * d1); + const coord_t h = coord_t(0.5 * std::sqrt(3.0) * period); // height of triangle + + // Align bounding box to the grid + bb.merge(align_to_grid(bb.center(), Point(period,h))); + const int layer_mod = layer_id % 3; + const double angle = layer_mod * 2.0 * M_PI / 3.0; + + const Point rotation_center = bb.center(); + const coord_t half_w = bb.size().x() / 2; + const coord_t half_h = bb.size().y() / 2; + + // Compute how many full periods fit in each direction + const coord_t num_periods_x = coord_t(std::ceil(half_w / double(period))); + coord_t num_periods_y =coord_t(std::ceil(half_h / double(h))); + // Ensure an even number of rows so the pattern stays centered + if ((num_periods_y % 2) != 0) + ++num_periods_y; + + // Compute aligned limits (symmetric around the origin) + const coord_t x_min_aligned = -num_periods_x * period; + const coord_t x_max_aligned = num_periods_x * period; + const coord_t y_min_aligned = -num_periods_y * h; + const coord_t y_max_aligned = num_periods_y * h; + + // Pre-allocate estimated number of polylines + const size_t estimated_rows = (y_max_aligned - y_min_aligned) / h + 2; + const size_t estimated_polylines = (estimated_rows + 1) * 2; // base line + trapezoid line per row + polylines.reserve(estimated_polylines); + + // Create the two base row templates once + Polyline base_line_template; + base_line_template.points.reserve(2); // 2 points for base line + Polyline trapezoid_row_normal; + trapezoid_row_normal.points.reserve(((x_max_aligned - x_min_aligned) / period + 1) * 5); // 5 points per trapezoid + Polyline trapezoid_row_shifted; + trapezoid_row_shifted.points.reserve(((x_max_aligned - x_min_aligned) / period + 1) * 5); // 5 points per trapezoid + // Build base line template (from x_min_aligned to x_max_aligned) + base_line_template.points.emplace_back(Point(x_min_aligned, 0)); + base_line_template.points.emplace_back(Point(x_max_aligned, 0)); + + // Build complete trapezoid rows once + // Normal row (no shift) + for (coord_t x = x_min_aligned; x < x_max_aligned; x += period) { + trapezoid_row_normal.points.emplace_back(Point(x + d2_tri / 2, d1)); // P0 + trapezoid_row_normal.points.emplace_back(Point(x + period / 2 - d2_tri / 2, h - d1)); // P1 + trapezoid_row_normal.points.emplace_back(Point(x + period / 2 + d2_tri / 2, h - d1)); // P2 + trapezoid_row_normal.points.emplace_back(Point(x + period - d2_tri / 2, d1)); // P3 + trapezoid_row_normal.points.emplace_back(Point(x + period, d1)); // P4 + } + + // Shifted row (mirrored vertically) + trapezoid_row_shifted.points = trapezoid_row_normal.points; + for (auto& p : trapezoid_row_shifted.points) + p.y() = h - p.y(); + + bool shift_row = false; + + // Generate pattern by copying and translating templates vertically + for (coord_t y = y_min_aligned; y < y_max_aligned; y += h) { + // Base line - copy and translate + Polyline base_line = base_line_template; + for (Point& p : base_line.points) { + p.y() += y; + } + polylines.emplace_back(std::move(base_line)); + + // Trapezoid line - copy and translate the appropriate template + Polyline trapezoid_line = shift_row ? trapezoid_row_shifted : trapezoid_row_normal; + for (Point& p : trapezoid_line.points) { + p.y() += y; + } + + if (!trapezoid_line.points.empty()) { + polylines.emplace_back(std::move(trapezoid_line)); + } + + shift_row = !shift_row; + } + + // Rotate around origin (0,0) + if (layer_mod) + for (auto& pl : polylines) + pl.rotate(angle, Point(0,0)); + + break; + } + + default: + // Handle unknown pattern type + break; + } + + // Apply multiline fill + multiline_fill(polylines, params, spacing); + + // Contract surface polygon by half line width to avoid excesive overlap with perimeter + ExPolygons contracted = offset_ex(expolygon, -float(scale_(0.5 * this->spacing))); + + // if contraction results in empty polygon, use original surface + const ExPolygon &intersection_surface = contracted.empty() ? expolygon : contracted.front(); + + // Intersect polylines with offset expolygon + polylines = intersection_pl(std::move(polylines), intersection_surface); + + // Remove very short segments that may cause connection issues + const double minlength = scale_(0.8 * this->spacing); + if (minlength > 0 && !polylines.empty()) { + polylines.erase(std::remove_if(polylines.begin(), polylines.end(), + [minlength](const Polyline& pl) { return pl.length() < minlength; }), + polylines.end()); + } + + // Connect infill lines using offset expolygon + int infill_start_idx = polylines_out.size(); + if (!polylines.empty()) { + Slic3r::Fill::chain_or_connect_infill(std::move(polylines), intersection_surface, polylines_out, this->spacing, params); + + // Rotate back the infill lines to original orientation + if (std::abs(base_angle) >= EPSILON) { + for (auto it = polylines_out.begin() + infill_start_idx; it != polylines_out.end(); ++it) { + it->rotate(base_angle, rotate_vector.second); + } + } } return true; @@ -3077,15 +3335,27 @@ Polylines FillMonotonicLine::fill_surface(const Surface* surface, const FillPara Polylines FillGrid::fill_surface(const Surface *surface, const FillParams ¶ms) { Polylines polylines_out; - if (! this->fill_surface_by_multilines( - surface, params, - { { 0.f, 0.f }, { float(M_PI / 2.), 0.f } }, - polylines_out)) - BOOST_LOG_TRIVIAL(error) << "FillGrid::fill_surface() failed to fill a region."; - if (this->layer_id % 2 == 1) - for (int i = 0; i < polylines_out.size(); i++) - std::reverse(polylines_out[i].begin(), polylines_out[i].end()); + if (params.multiline > 1) { + // Experimental trapezoidal grid + if (!this->fill_surface_trapezoidal( + surface, params, + { { 0.f, 0.f }, { float(M_PI / 2.), 0.f } }, + polylines_out,0)) + BOOST_LOG_TRIVIAL(error) << "FillGrid::fill_surface_trapezoidal() failed."; + + } else { + if (!this->fill_surface_by_multilines( + surface, params, + { { 0.f, 0.f }, { float(M_PI / 2.), 0.f } }, + polylines_out)) + BOOST_LOG_TRIVIAL(error) << "FillGrid::fill_surface() failed to fill a region."; + + + if (this->layer_id % 2 == 1) + for (int i = 0; i < polylines_out.size(); i++) + std::reverse(polylines_out[i].begin(), polylines_out[i].end()); + } return polylines_out; } @@ -3108,12 +3378,23 @@ Polylines FillLateralLattice::fill_surface(const Surface *surface, const FillPar Polylines FillTriangles::fill_surface(const Surface *surface, const FillParams ¶ms){ Polylines polylines_out; + if (params.multiline > 1) { + // Experimental trapezoidal grid + if (!this->fill_surface_trapezoidal( + surface, params, + { { 0.f, 0.f }, { float(M_PI / 2.), 0.f } }, + polylines_out,1)) + BOOST_LOG_TRIVIAL(error) << "FillGrid::fill_surface_trapezoidal() failed."; + + } else { if (! this->fill_surface_by_multilines( surface, params, { { 0.f, 0.f }, { float(M_PI / 3.), 0.f }, { float(2. * M_PI / 3.), 0. } }, polylines_out)) BOOST_LOG_TRIVIAL(error) << "FillTriangles::fill_surface() failed to fill a region."; + } return polylines_out; + } Polylines FillStars::fill_surface(const Surface *surface, const FillParams ¶ms) @@ -3144,8 +3425,8 @@ Polylines FillQuarterCubic::fill_surface(const Surface* surface, const FillParam using namespace boost::math::float_constants; Polylines polylines_out; - coord_t line_width = coord_t(scale_(this->spacing)); - coord_t period = coord_t(scale_(this->spacing) / params.density) * 4; + coord_t line_width = coord_t(scale_(this->spacing)) * params.multiline; + coord_t period = coord_t(scale_(this->spacing) *params.multiline / params.density) * 4; // First half tetrahedral fill double pattern_z_shift = 0.0; diff --git a/src/libslic3r/Fill/FillRectilinear.hpp b/src/libslic3r/Fill/FillRectilinear.hpp index 3e4f3cf92f..126cbe3a15 100644 --- a/src/libslic3r/Fill/FillRectilinear.hpp +++ b/src/libslic3r/Fill/FillRectilinear.hpp @@ -29,6 +29,7 @@ protected: float pattern_shift; }; bool fill_surface_by_multilines(const Surface *surface, FillParams params, const std::initializer_list &sweep_params, Polylines &polylines_out); + bool fill_surface_trapezoidal(const Surface *surface, FillParams params, const std::initializer_list &sweep_params, Polylines &polylines_out,int Pattern_type); // The extended bounding box of the whole object that covers any rotation of every layer. BoundingBox extended_object_bounding_box() const; diff --git a/src/libslic3r/PrintConfig.cpp b/src/libslic3r/PrintConfig.cpp index 2fb29fc2cd..37fc895411 100644 --- a/src/libslic3r/PrintConfig.cpp +++ b/src/libslic3r/PrintConfig.cpp @@ -2733,7 +2733,7 @@ void PrintConfigDef::init_fff_params() def->label = L("Fill Multiline"); def->tooltip = L("Using multiple lines for the infill pattern, if supported by infill pattern."); def->min = 1; - def->max = 5; // Maximum number of lines for infill pattern + def->max = 10; // Maximum number of lines for infill pattern def->set_default_value(new ConfigOptionInt(1)); def = this->add("sparse_infill_pattern", coEnum); diff --git a/src/slic3r/GUI/ConfigManipulation.cpp b/src/slic3r/GUI/ConfigManipulation.cpp index 8254afda2d..38bc2cb7c9 100644 --- a/src/slic3r/GUI/ConfigManipulation.cpp +++ b/src/slic3r/GUI/ConfigManipulation.cpp @@ -604,8 +604,8 @@ void ConfigManipulation::toggle_print_fff_options(DynamicPrintConfig *config, co // Infill patterns that support multiline infill. InfillPattern pattern = config->opt_enum("sparse_infill_pattern"); - bool have_multiline_infill_pattern = pattern == ipGyroid || pattern == ipGrid || pattern == ipRectilinear || pattern == ipTpmsD || pattern == ipTpmsFK || pattern == ipCrossHatch || pattern == ipHoneycomb || pattern == ipLateralLattice || pattern == ipLateralHoneycomb || - pattern == ipCubic || pattern == ipStars || pattern == ipAlignedRectilinear || pattern == ipLightning || pattern == ip3DHoneycomb || pattern == ipAdaptiveCubic || pattern == ipSupportCubic; + bool have_multiline_infill_pattern = pattern == ipGyroid || pattern == ipGrid || pattern == ipRectilinear || pattern == ipTpmsD || pattern == ipTpmsFK || pattern == ipCrossHatch || pattern == ipHoneycomb || pattern == ipLateralLattice || pattern == ipLateralHoneycomb || pattern == ipConcentric || + pattern == ipCubic || pattern == ipStars || pattern == ipAlignedRectilinear || pattern == ipLightning || pattern == ip3DHoneycomb || pattern == ipAdaptiveCubic || pattern == ipSupportCubic|| pattern == ipTriangles || pattern == ipQuarterCubic|| pattern == ipArchimedeanChords || pattern == ipHilbertCurve || pattern == ipOctagramSpiral; // If there is infill, enable/disable fill_multiline according to whether the pattern supports multiline infill. if (have_infill) {