Clipper 2 multiline Infill (#11435)

* Grid non-crossing for multiline

cleaning

Replaced negative offset logic with surface contraction to reduce overlap with perimeters.

center the infill

filltriangles

update triangles

preallocate memory

Update FillRectilinear.cpp

Update FillRectilinear.cpp

overlapp adjustment

Fix Crash

Update FillRectilinear.cpp

density tunning

density tunning

fine tunning

reserve polilines

Grid non-crossing for multiline

Co-Authored-By: Ian Bassi <12130714+ianalexis@users.noreply.github.com>
Co-Authored-By: discip <53649486+discip@users.noreply.github.com>

* Improve multiline fill offset and polyline closure

Changed offset type from jtRound to jtMiter in multiline_fill for better geometry. Updated polyline conversion to require at least 3 points and ensured polylines are closed if not already. Updated FillGyroid, FillTpmsD, and FillTpmsFK to pass the 'close' argument to multiline_fill.

cleaning

FillAdaptive Noncross

Only use clipper if worth it

safeguard

fix overlap

Update FillRectilinear.cpp

FilllRectilineal multiline clipper

Update FillRectilinear.cpp

FilllRectilineal multiline clipper

Update FillRectilinear.cpp

Update FillRectilinear.cpp

fix 3d honeycomb

Simplify polylines

Update FillBase.cpp

Update FillBase.cpp

cleaning

Improved Multiline Function

This ensures `multiline_fill()` will correctly generate multiline infill with
closed loop polylines if the input infill line is a closedloop polyline. This
ensures that the multiline infill doesn't have little gaps or overlaps at the
"closed point" of the original infill line.

This changes how the tangent is calculated for the first and last points in a
polyline if the first and last points are the same, making it a closed loop.
Instead of just using the first or last line segment, it uses the line segment
between the points before the last point and after the first point, the same
way that all the other poly-line mid points are handled.

It also uses eigen vector operations to calculate the points instead of
explicitly calculating the x and y values. This is probably faster, and if not
then it is at least more concise.

Hibrid Multiline Function

Update FillRectilinear.cpp

Update FillRectilinear.cpp

Update FillRectilinear.cpp

Update FillRectilinear.cpp

clipperutils multiline hibrido

Update FillBase.cpp

Update FillBase.cpp

Update FillBase.cpp

multiline hibrido

arc tolerance

multiline con union

Update FillBase.cpp

Update FillBase.cpp

Update FillBase.cpp

Co-Authored-By: Ian Bassi <12130714+ianalexis@users.noreply.github.com>
Co-Authored-By: Donovan Baarda <dbaarda@gmail.com>

* Switch multiline offset logic to Clipper2

Replaces Clipper-based multiline offset logic in FillBase.cpp with Clipper2, using InflatePaths and Union for offsetting and merging. Adds new conversion utilities in Clipper2Utils for handling Paths64 to Polygons/Polylines and updates headers accordingly.

* Refactor multiline_fill to always use Clipper2 logic

Removed the 'use_clipper' parameter from multiline_fill and updated all callers to use the new signature. The function now consistently applies Clipper2-based offset logic for multiline infill, simplifying the code and ensuring uniform behavior across fill patterns.

* Change offset join type to Round in multiline_fill

Replaces the Miter join type with Round in the InflatePaths call within multiline_fill. For smotther print travels.

* Increase max infill multiline to 10

Raised the maximum allowed value for the 'Fill Multiline' infill parameter from 5 to 10 to support more lines in infill patterns.

* Refactor multiline_fill to optimize offset logic

Replaces manual conversion of polylines to Clipper2 paths with Slic3rPolylines_to_Paths64 and filters short paths using std::remove_if. Uses ClipperOffset for path inflation and streamlines merging and conversion to polylines, improving performance and code clarity.

* half iteration because is bucle

* Funciona 1

Refactored the multiline_fill function to streamline the insertion of center lines by directly checking for odd line counts and removing redundant logic. This improves code clarity and reduces unnecessary checks.

* Refactor multiline_fill for improved offset logic

Reworked the multiline_fill function to simplify and clarify the logic for generating multiple offset lines. The new implementation computes offsets more explicitly for odd and even cases, creates a fresh ClipperOffset for each band, and improves conversion between Clipper2 paths and polylines. This enhances maintainability and correctness of the multiline fill generation.

* Quartercubic multiline

* fillplanePath

fix bounding box

Co-Authored-By: Ian Bassi <12130714+ianalexis@users.noreply.github.com>

* fillconcentric multiline

Co-Authored-By: Ian Bassi <12130714+ianalexis@users.noreply.github.com>

* Update FillBase.hpp

* cleaning

* Refactor multiline_fill to clean polylines and reuse offsetter

Invalid polylines with less than two points are now removed before processing. The ClipperOffset object is created once and reused for each offset, improving efficiency and code clarity.

trigger build

* Optimize Filltrapezoidal

Refactored the trapezoidal fill pattern generation to precompute base row templates and reuse them with vertical translation, reducing redundant computations and improving code clarity. This change enhances performance and maintainability by avoiding repeated construction of row patterns within loops.

* Replace push_back with emplace_back for Polyline points

Updated Polyline point insertion from push_back to emplace_back for efficiency and clarity. Also refactored row copying logic to avoid in-place modification, improving code readability and safety.

* Update FillRectilinear.cpp

* Reserve space for poliline points

* Union not needed

* Update FillRectilinear.cpp

* unused functions

* compactado

Update FillRectilinear.cpp

* Adjust minimum rows for better performance

* Update FillRectilinear.cpp

---------

Co-authored-by: Ian Bassi <12130714+ianalexis@users.noreply.github.com>
Co-authored-by: discip <53649486+discip@users.noreply.github.com>
Co-authored-by: Donovan Baarda <dbaarda@gmail.com>
Co-authored-by: Ian Bassi <ian.bassi@outlook.com>
This commit is contained in:
Rodrigo Faselli
2025-12-23 17:53:09 -03:00
committed by GitHub
parent a240478c30
commit 506fde8f86
12 changed files with 428 additions and 96 deletions

View File

@@ -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;

View File

@@ -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);

View File

@@ -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);

View File

@@ -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<float>(std::numeric_limits<coord_t>::max(), scale_(params.anchor_length)));
const auto hook_length_max = coordf_t(std::min<float>(std::numeric_limits<coord_t>::max(), scale_(params.anchor_length_max)));
const auto hook_length = coordf_t(std::min<float>(std::numeric_limits<coord_t>::max(), scale_(params.anchor_length)));
const auto hook_length_max = coordf_t(std::min<float>(std::numeric_limits<coord_t>::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
{

View File

@@ -3,6 +3,7 @@
#include <cmath>
#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<int>(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<int>(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<float>(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<float>().normalized();
} else {
tangent = (pl.points[1] - pl.points[0]).template cast<float>().normalized();
}
} else if (i == n - 1) {
if (pl.points[0] == pl.points[n-1]) {
tangent = (pl.points[1] - pl.points[n-2]).template cast<float>().normalized();
} else {
tangent = (pl.points[n-1] - pl.points[n-2]).template cast<float>().normalized();
}
} else
tangent = (pl.points[i+1] - pl.points[i-1]).template cast<float>().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<coord_t>();
new_points.push_back(p);
}
// Compute offsets (in units of spacing)
std::vector<double> 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

View File

@@ -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<double>(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;

View File

@@ -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);

View File

@@ -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<double>(this->spacing) / params.density;
auto distance_between_lines = scaled<double>(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) {

View File

@@ -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<SweepParams>& 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<double, Point> 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 &params)
{
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 &params){
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 &params)
@@ -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;

View File

@@ -29,6 +29,7 @@ protected:
float pattern_shift;
};
bool fill_surface_by_multilines(const Surface *surface, FillParams params, const std::initializer_list<SweepParams> &sweep_params, Polylines &polylines_out);
bool fill_surface_trapezoidal(const Surface *surface, FillParams params, const std::initializer_list<SweepParams> &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;

View File

@@ -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);

View File

@@ -604,8 +604,8 @@ void ConfigManipulation::toggle_print_fff_options(DynamicPrintConfig *config, co
// Infill patterns that support multiline infill.
InfillPattern pattern = config->opt_enum<InfillPattern>("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) {