mirror of
https://github.com/OrcaSlicer/OrcaSlicer.git
synced 2026-09-29 11:51:24 +00:00
Non-crossing infill optimization (#15931)
* Non-crossing infill optimization * test triangles * test grid * cleaning * Align and clip rectilinear infill paths Generate infill coverage in the pattern's local frame, rotate triangular patterns by layer, and clip centerlines to the surface vicinity. Start closed outlines outside the surface so clipping splits them cleanly. * Update test_fill.cpp * Update multiline-infill.md --------- Co-authored-by: Ian Bassi <ian.bassi@outlook.com>
This commit is contained in:
co-authored by
Ian Bassi
parent
9d8257d2eb
commit
11e9e07f20
@@ -40,6 +40,12 @@ The pattern is phased on fixed positions, so it lines up across layers and
|
||||
across the regions of one layer. Rounding the corners with
|
||||
`sparse_infill_smooth_factor` happens before `multiline_fill()`.
|
||||
|
||||
Each region builds only the rows over its bounding box in that frame, and
|
||||
outlines only the centerlines within `d1 / 2` of it, the ones whose outlines
|
||||
reach it. Every row is monotone along its direction, so each outline is started
|
||||
on the cap at the first end of its centerline, outside the region, and clipping
|
||||
to the region cuts it only where it crosses the boundary.
|
||||
|
||||
## Cubic
|
||||
|
||||
Single-line Cubic draws the three families at the same spacing `h` and shifts
|
||||
|
||||
@@ -3094,7 +3094,6 @@ static std::vector<Vec2d> cubic_upper_level(double tau, double h, double period,
|
||||
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, 2=stars, 3=cubic
|
||||
{
|
||||
@@ -3126,9 +3125,20 @@ bool FillRectilinear::fill_surface_trapezoidal(
|
||||
expolygon.rotate(-base_angle, rotate_vector.second);
|
||||
}
|
||||
|
||||
// Use extended object bounding box for consistent pattern across layers
|
||||
BoundingBox bb = this->extended_object_bounding_box();
|
||||
const size_t infill_layer_id = (surface->thickness_layers > 0) ? this->layer_id / surface->thickness_layers : this->layer_id;
|
||||
// The triangular family turns by 120 degrees every layer, about the origin of the frame it is built in.
|
||||
const size_t layer_mod = infill_layer_id % 3;
|
||||
const double angle = layer_mod * 2.0 * M_PI / 3.0;
|
||||
|
||||
// Only build the rows over the surface, seen in the frame they are built in.
|
||||
Polygon local = expolygon.contour;
|
||||
if (Pattern_type != 0) {
|
||||
local.translate(-rotate_vector.second.x(), -rotate_vector.second.y());
|
||||
if (layer_mod)
|
||||
local.rotate(-angle);
|
||||
}
|
||||
BoundingBox cover = get_extents(local);
|
||||
cover.offset(period);
|
||||
|
||||
switch (Pattern_type) {
|
||||
case 0: // Grid / Trapezoidal
|
||||
@@ -3148,21 +3158,31 @@ bool FillRectilinear::fill_surface_trapezoidal(
|
||||
// Align bounding box to the grid, phased through the box center so separated infills align
|
||||
// each part on itself (grid_center is the origin for a standalone object / feature off).
|
||||
// Captured before the merge, which grows bb and would otherwise shift its center.
|
||||
BoundingBox bb = this->extended_object_bounding_box();
|
||||
const Point grid_center = bb.center();
|
||||
bb.merge(align_to_grid(bb.min, Point(period, period), grid_center));
|
||||
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();
|
||||
|
||||
auto transpose = [&grid_center](const Point &p) {
|
||||
return Point(grid_center.x() + p.y() - grid_center.y(), grid_center.y() + p.x() - grid_center.x());
|
||||
};
|
||||
if (infill_layer_id % 2 == 1)
|
||||
cover = BoundingBox(transpose(cover.min), transpose(cover.max));
|
||||
const coord_t row_spacing = period / 2;
|
||||
const coord_t first_x = xmin + (coord_t(std::floor(double(cover.min.x() - xmin) / period)) - 1) * period;
|
||||
const coord_t last_x = xmin + (coord_t(std::ceil(double(cover.max.x() - xmin) / period)) + 1) * period;
|
||||
const coord_t first_row = coord_t(std::floor(double(cover.min.y() - ymin) / row_spacing)) - 1;
|
||||
const coord_t last_row = coord_t(std::ceil(double(cover.max.y() - ymin) / row_spacing)) + 1;
|
||||
|
||||
// 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
|
||||
base_row_normal.points.reserve(((last_x - first_x) / 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
|
||||
base_row_flipped.points.reserve(((last_x - first_x) / period + 1) * 5); // 5 points per trapezoid
|
||||
|
||||
// Build complete rows from xmin to xmax
|
||||
for (coord_t x = xmin; x < xmax; x += period) {
|
||||
// Build rows on the same global period grid, limited to the surface cover.
|
||||
for (coord_t x = first_x; x < last_x; x += period) {
|
||||
// Normal row
|
||||
base_row_normal.points.emplace_back(Point(x, d1 / 2)); // P0
|
||||
base_row_normal.points.emplace_back(Point(x + d1 / 2, d1 / 2)); // P1
|
||||
@@ -3178,13 +3198,14 @@ bool FillRectilinear::fill_surface_trapezoidal(
|
||||
}
|
||||
|
||||
// Pre-allocate polylines
|
||||
const size_t estimated_rows = ((ymax - ymin) / (period / 2) + 1);
|
||||
const size_t estimated_rows = size_t(last_row - first_row + 1);
|
||||
polylines.reserve(estimated_rows);
|
||||
|
||||
bool flip_vertical = false;
|
||||
bool flip_vertical = (first_row % 2) != 0;
|
||||
|
||||
// Now just copy and translate vertically
|
||||
for (coord_t y = ymin; y < ymax; y += period / 2) {
|
||||
// Copy and translate only rows intersecting the surface cover.
|
||||
for (coord_t row = first_row; row <= last_row; ++row) {
|
||||
const coord_t y = ymin + row * row_spacing;
|
||||
Polyline pl_row = flip_vertical ? base_row_flipped : base_row_normal;
|
||||
|
||||
// Translate all points vertically
|
||||
@@ -3200,16 +3221,10 @@ bool FillRectilinear::fill_surface_trapezoidal(
|
||||
// Orca: mirror across the diagonal through grid_center (not the origin), so the swapped
|
||||
// layers stay aligned with the center-phased grid. For a standalone object / feature off,
|
||||
// grid_center is the origin and this is a plain x/y swap.
|
||||
if (infill_layer_id % 2 == 1) {
|
||||
for (Polyline& pl : polylines) {
|
||||
for (Point& p : pl.points) {
|
||||
const coord_t dx = p.x() - grid_center.x();
|
||||
const coord_t dy = p.y() - grid_center.y();
|
||||
p.x() = grid_center.x() + dy;
|
||||
p.y() = grid_center.y() + dx;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (infill_layer_id % 2 == 1)
|
||||
for (Polyline& pl : polylines)
|
||||
for (Point& p : pl.points)
|
||||
p = transpose(p);
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -3230,40 +3245,24 @@ bool FillRectilinear::fill_surface_trapezoidal(
|
||||
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 size_t layer_mod = infill_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;
|
||||
// Keep the existing origin-anchored lattice, but generate only nearby tiles.
|
||||
const coord_t x_min_aligned = (coord_t(std::floor(double(cover.min.x()) / period)) - 1) * period;
|
||||
const coord_t x_max_aligned = (coord_t(std::ceil(double(cover.max.x()) / period)) + 1) * period;
|
||||
const coord_t first_row = coord_t(std::floor(double(cover.min.y()) / h)) - 1;
|
||||
const coord_t last_row = coord_t(std::ceil(double(cover.max.y()) / h)) + 1;
|
||||
|
||||
// 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
|
||||
const size_t estimated_rows = size_t(last_row - first_row + 1);
|
||||
const size_t estimated_polylines = estimated_rows * 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
|
||||
trapezoid_row_normal.points.reserve(((x_max_aligned - x_min_aligned) / period) * 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
|
||||
trapezoid_row_shifted.points.reserve(((x_max_aligned - x_min_aligned) / period) * 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));
|
||||
@@ -3283,10 +3282,10 @@ bool FillRectilinear::fill_surface_trapezoidal(
|
||||
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) {
|
||||
bool shift_row = (first_row % 2) != 0;
|
||||
for (coord_t row = first_row; row <= last_row; ++row) {
|
||||
const coord_t y = row * h;
|
||||
// Base line - copy and translate
|
||||
Polyline base_line = base_line_template;
|
||||
for (Point& p : base_line.points) {
|
||||
@@ -3306,12 +3305,6 @@ bool FillRectilinear::fill_surface_trapezoidal(
|
||||
|
||||
shift_row = !shift_row;
|
||||
}
|
||||
|
||||
// Rotate around origin (0,0)
|
||||
if (layer_mod)
|
||||
for (auto& pl : polylines)
|
||||
pl.rotate(angle, Point(0,0));
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -3325,36 +3318,25 @@ bool FillRectilinear::fill_surface_trapezoidal(
|
||||
const coord_t d1_half_base = d1_half / std::sqrt(3.0);
|
||||
const coord_t half_period = period / 2;
|
||||
const coord_t quarter_period = period / 4;
|
||||
const coord_t row_y_offset = tri_height - (2 * tri_height) / 3;
|
||||
|
||||
bb.merge(align_to_grid(bb.center(), Point(period, tri_height)));
|
||||
const size_t layer_mod = infill_layer_id % 3;
|
||||
const double angle = layer_mod * 2.0 * M_PI / 3.0;
|
||||
// Keep the lattice anchored at the origin while generating only tiles around this surface.
|
||||
const int64_t first_tile = int64_t(std::floor(double(cover.min.x()) / period)) - 1;
|
||||
const int64_t last_tile = int64_t(std::ceil(double(cover.max.x()) / period)) + 1;
|
||||
const int64_t first_row = int64_t(std::floor(double(cover.min.y() - row_y_offset) / hex_height)) - 1;
|
||||
const int64_t last_row = int64_t(std::ceil(double(cover.max.y() - row_y_offset) / hex_height)) + 1;
|
||||
|
||||
const coord_t half_w = bb.size().x() / 2;
|
||||
const coord_t half_h = bb.size().y() / 2;
|
||||
|
||||
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(hex_height)));
|
||||
if ((num_periods_y % 2) != 0)
|
||||
++num_periods_y;
|
||||
|
||||
const coord_t x_alignment_shift = half_period;
|
||||
const coord_t y_alignment_shift = (2 * tri_height) / 3;
|
||||
const coord_t x_min_aligned = -num_periods_x * period - x_alignment_shift;
|
||||
const coord_t x_max_aligned = num_periods_x * period - x_alignment_shift;
|
||||
const coord_t y_min_aligned = -num_periods_y * hex_height - y_alignment_shift;
|
||||
const coord_t y_max_aligned = num_periods_y * hex_height - y_alignment_shift;
|
||||
|
||||
const size_t estimated_rows = (y_max_aligned - y_min_aligned) / hex_height + 2;
|
||||
const size_t estimated_polylines = (estimated_rows + 1) * 2;
|
||||
const size_t estimated_rows = size_t(last_row - first_row + 1);
|
||||
const size_t estimated_polylines = estimated_rows * 2;
|
||||
polylines.reserve(estimated_polylines);
|
||||
|
||||
Polyline star_row_normal;
|
||||
star_row_normal.points.reserve(((x_max_aligned - x_min_aligned) / period + 1) * 7);
|
||||
star_row_normal.points.reserve(size_t(last_tile - first_tile) * 7);
|
||||
Polyline star_row_mirrored;
|
||||
star_row_mirrored.points.reserve(((x_max_aligned - x_min_aligned) / period + 1) * 7);
|
||||
star_row_mirrored.points.reserve(size_t(last_tile - first_tile) * 7);
|
||||
|
||||
for (coord_t x = x_min_aligned; x < x_max_aligned; x += period) {
|
||||
for (int64_t tile = first_tile; tile < last_tile; ++tile) {
|
||||
const coord_t x = coord_t(tile * period) - half_period;
|
||||
star_row_normal.points.emplace_back(Point(x, hex_height)); // P0
|
||||
star_row_normal.points.emplace_back(Point(x + quarter_period - d1, hex_height)); // P1
|
||||
star_row_normal.points.emplace_back(Point(x + quarter_period + d1_half, hex_height - chamfer_height)); // P2
|
||||
@@ -3368,9 +3350,7 @@ bool FillRectilinear::fill_surface_trapezoidal(
|
||||
for (auto& p : star_row_mirrored.points)
|
||||
p.y() = hex_height - p.y();
|
||||
|
||||
size_t pair_idx = 0;
|
||||
const coord_t global_x_shift = half_period;
|
||||
const coord_t global_y_shift = tri_height;
|
||||
auto append_row_with_shift = [&polylines](const Polyline& row_template, coord_t x_shift, coord_t y_shift) {
|
||||
Polyline row = row_template;
|
||||
for (Point& p : row.points) {
|
||||
@@ -3381,16 +3361,12 @@ bool FillRectilinear::fill_surface_trapezoidal(
|
||||
polylines.emplace_back(std::move(row));
|
||||
};
|
||||
|
||||
for (coord_t y = y_min_aligned; y < y_max_aligned; y += hex_height, ++pair_idx) {
|
||||
const coord_t x_shift = (pair_idx % 2 == 0) ? 0 : half_period;
|
||||
append_row_with_shift(star_row_normal, x_shift + global_x_shift, y + global_y_shift);
|
||||
append_row_with_shift(star_row_mirrored, x_shift + global_x_shift, y + global_y_shift);
|
||||
for (int64_t row = first_row; row <= last_row; ++row) {
|
||||
const coord_t y = coord_t(row * hex_height) + row_y_offset;
|
||||
const coord_t x_shift = (row % 2 == 0) ? 0 : half_period;
|
||||
append_row_with_shift(star_row_normal, x_shift + global_x_shift, y);
|
||||
append_row_with_shift(star_row_mirrored, x_shift + global_x_shift, y);
|
||||
}
|
||||
|
||||
if (layer_mod)
|
||||
for (auto& pl : polylines)
|
||||
pl.rotate(angle, Point(0, 0));
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -3407,19 +3383,9 @@ bool FillRectilinear::fill_surface_trapezoidal(
|
||||
for (Vec2d &p : levels.front())
|
||||
p.y() = h - p.y();
|
||||
|
||||
const size_t layer_mod = infill_layer_id % 3;
|
||||
const double angle = layer_mod * 2.0 * M_PI / 3.0;
|
||||
|
||||
// Only cover the surface, seen in the frame the pattern is built in.
|
||||
ExPolygon local = expolygon;
|
||||
local.translate(-rotate_vector.second.x(), -rotate_vector.second.y());
|
||||
if (layer_mod)
|
||||
local.rotate(-angle);
|
||||
BoundingBox cover = get_extents(local);
|
||||
cover.offset(period);
|
||||
|
||||
const int64_t n_min = int64_t(std::floor((cover.min.y() - y0) / h)) - 1;
|
||||
const int64_t n_max = int64_t(std::ceil((cover.max.y() - y0) / h)) + 1;
|
||||
polylines.reserve(size_t(n_max - n_min + 1) * levels.size());
|
||||
for (int64_t n = n_min; n <= n_max; ++n) {
|
||||
const double x_off = (n & 1) ? 0.5 * period : 0.;
|
||||
const double base = y0 + double(n) * h - tau;
|
||||
@@ -3435,11 +3401,6 @@ bool FillRectilinear::fill_surface_trapezoidal(
|
||||
polylines.emplace_back(std::move(row));
|
||||
}
|
||||
}
|
||||
|
||||
if (layer_mod)
|
||||
for (Polyline &pl : polylines)
|
||||
pl.rotate(angle, Point(0, 0));
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -3448,21 +3409,38 @@ bool FillRectilinear::fill_surface_trapezoidal(
|
||||
break;
|
||||
}
|
||||
|
||||
// Orca: cases 1 & 2 build the pattern symmetrically around the origin, so on their own they
|
||||
// phase to the global origin and every part shares one grid. Shift the pattern onto the box
|
||||
// center this->bounding_box carries, so separated infills align each part on itself. The center
|
||||
// is the origin for a standalone object (or when the feature is off), making this a no-op there.
|
||||
// Orca: cases 1 to 3 anchor the pattern at the origin, so on their own they phase to the global
|
||||
// origin and every part shares one grid. Shift the pattern onto the box center
|
||||
// this->bounding_box carries, so separated infills align each part on itself. The center is the
|
||||
// origin for a standalone object (or when the feature is off), making the shift a no-op there.
|
||||
if (Pattern_type != 0)
|
||||
for (Polyline &pl : polylines)
|
||||
for (Polyline &pl : polylines) {
|
||||
if (layer_mod)
|
||||
pl.rotate(angle, Point(0, 0));
|
||||
pl.translate(rotate_vector.second);
|
||||
}
|
||||
|
||||
// Orca: round the corners of the trapezoids. The straight base lines of the triangular family
|
||||
// have no corner to round.
|
||||
smooth_polylines_corners(polylines, params.smooth_factor, scaled<double>(params.resolution));
|
||||
|
||||
// Only the centerlines within d1 / 2 of the surface have outlines reaching it.
|
||||
polylines = intersection_pl(std::move(polylines), offset(expolygon, float(d1 / 2)));
|
||||
|
||||
// Apply multiline fill
|
||||
multiline_fill(polylines, params, spacing);
|
||||
|
||||
// Start each outline on the cap at the first end of its path, outside the surface, so clipping splits it only there.
|
||||
const Vec2d row_dir = Pattern_type != 0 ? Vec2d(std::cos(angle), std::sin(angle)) : infill_layer_id % 2 ? Vec2d::UnitY() : Vec2d::UnitX();
|
||||
for (Polyline &pl : polylines)
|
||||
if (pl.size() > 3 && pl.first_point() == pl.last_point()) {
|
||||
pl.points.pop_back();
|
||||
std::rotate(pl.points.begin(), std::min_element(pl.points.begin(), pl.points.end(), [&row_dir](const Point &a, const Point &b) {
|
||||
return row_dir.dot(a.cast<double>()) < row_dir.dot(b.cast<double>());
|
||||
}), pl.points.end());
|
||||
pl.points.emplace_back(pl.points.front());
|
||||
}
|
||||
|
||||
// Contract surface polygon by half line width to avoid excesive overlap with perimeter
|
||||
ExPolygons contracted = offset_ex(expolygon, -float(scale_(0.5 * this->spacing)));
|
||||
|
||||
@@ -3545,9 +3523,7 @@ Polylines FillGrid::fill_surface(const Surface *surface, const FillParams ¶m
|
||||
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))
|
||||
surface, params, polylines_out, 0))
|
||||
BOOST_LOG_TRIVIAL(error) << "FillGrid::fill_surface_trapezoidal() failed.";
|
||||
|
||||
} else {
|
||||
@@ -3587,9 +3563,7 @@ Polylines FillTriangles::fill_surface(const Surface *surface, const FillParams &
|
||||
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))
|
||||
surface, params, polylines_out, 1))
|
||||
BOOST_LOG_TRIVIAL(error) << "FillGrid::fill_surface_trapezoidal() failed.";
|
||||
|
||||
} else {
|
||||
@@ -3608,9 +3582,7 @@ Polylines FillStars::fill_surface(const Surface *surface, const FillParams ¶
|
||||
Polylines polylines_out;
|
||||
if (params.multiline > 1) {
|
||||
if (!this->fill_surface_trapezoidal(
|
||||
surface, params,
|
||||
{{0.f, 0.f}, {float(M_PI / 3.), 0.f}, {float(2. * M_PI / 3.), float((3. / 2.) * this->spacing * params.multiline / params.density)}},
|
||||
polylines_out, 2))
|
||||
surface, params, polylines_out, 2))
|
||||
BOOST_LOG_TRIVIAL(error) << "FillStars::fill_surface_trapezoidal() failed.";
|
||||
} else {
|
||||
if (! this->fill_surface_by_multilines(
|
||||
@@ -3626,7 +3598,7 @@ Polylines FillCubic::fill_surface(const Surface *surface, const FillParams ¶
|
||||
{
|
||||
Polylines polylines_out;
|
||||
if (params.multiline > 1) {
|
||||
if (!this->fill_surface_trapezoidal(surface, params, {}, polylines_out, 3))
|
||||
if (!this->fill_surface_trapezoidal(surface, params, polylines_out, 3))
|
||||
BOOST_LOG_TRIVIAL(error) << "FillCubic::fill_surface_trapezoidal() failed.";
|
||||
return polylines_out;
|
||||
}
|
||||
|
||||
@@ -29,7 +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);
|
||||
bool fill_surface_trapezoidal(const Surface *surface, FillParams 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;
|
||||
|
||||
@@ -1199,6 +1199,64 @@ TEST_CASE("Trapezoidal grid infill rounds its corners only with more than one li
|
||||
REQUIRE(single_smooth.length == single_sharp.length);
|
||||
}
|
||||
|
||||
TEST_CASE("Multiline infill of an object matches the infill of a larger object with the same center", "[Fill]")
|
||||
{
|
||||
const InfillPattern pattern = GENERATE(ipGrid, ipTriangles, ipStars, ipCubic);
|
||||
const int multiline = GENERATE(2, 3);
|
||||
// A square with cells as large as itself, whose corners are as far out as the object bounding box
|
||||
// reaches, and a strip with small cells, whose extents change with every layer orientation.
|
||||
const auto [half, density] = GENERATE(table<Vec2d, float>({ { Vec2d(20., 20.), 0.15f }, { Vec2d(60., 4.), 0.35f } }));
|
||||
CAPTURE(pattern, multiline, half.x(), half.y(), density);
|
||||
|
||||
// Off the origin; the same center gives both objects the same pattern.
|
||||
auto rectangle = [](const Vec2d &half) {
|
||||
const Vec2d center(100., 60.);
|
||||
return ExPolygon{ Points{ Point::new_scale(center.x() - half.x(), center.y() - half.y()),
|
||||
Point::new_scale(center.x() + half.x(), center.y() - half.y()),
|
||||
Point::new_scale(center.x() + half.x(), center.y() + half.y()),
|
||||
Point::new_scale(center.x() - half.x(), center.y() + half.y()) } };
|
||||
};
|
||||
const ExPolygon object = rectangle(half);
|
||||
const ExPolygon larger = rectangle(half + Vec2d(10., 10.));
|
||||
auto fill = [pattern, multiline, density = density](const ExPolygon ®ion, size_t layer_id) {
|
||||
std::unique_ptr<Fill> filler(Fill::new_from_type(pattern));
|
||||
filler->spacing = 0.45;
|
||||
filler->angle = float(M_PI / 7.);
|
||||
filler->fixed_angle = true;
|
||||
filler->layer_id = layer_id;
|
||||
filler->z = 0.2 * double(layer_id + 1);
|
||||
filler->set_bounding_box(get_extents(region.contour));
|
||||
|
||||
FillParams params;
|
||||
params.density = density;
|
||||
params.multiline = multiline;
|
||||
params.dont_adjust = true;
|
||||
Surface surface(stInternal, region);
|
||||
return filler->fill_surface(&surface, params);
|
||||
};
|
||||
// Away from the boundary of the object, where both are clipped and connected the same way.
|
||||
const Polygons inner = shrink(to_polygons(object), scale_(1.));
|
||||
auto farthest = [&inner](const Polylines &from, const Polylines &to) {
|
||||
const AABBTreeLines::LinesDistancer<Line> tree(to_lines(to));
|
||||
double distance = 0.;
|
||||
for (const Polyline &path : intersection_pl(from, inner))
|
||||
for (const Point &point : path.equally_spaced_points(scale_(0.2)))
|
||||
distance = std::max(distance, tree.distance_from_lines<false>(point));
|
||||
return unscale<double>(distance);
|
||||
};
|
||||
|
||||
// Both layer orientations of Grid, all three of the triangular family.
|
||||
for (size_t layer_id = 0; layer_id < 3; ++layer_id) {
|
||||
CAPTURE(layer_id);
|
||||
const Polylines walls = fill(object, layer_id);
|
||||
REQUIRE_FALSE(walls.empty());
|
||||
CHECK(get_intersections(to_lines(walls)).empty());
|
||||
const Polylines reference = fill(larger, layer_id);
|
||||
CHECK(farthest(reference, walls) < 0.01);
|
||||
CHECK(farthest(walls, reference) < 0.01);
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("Multiline cubic infill follows the cubic lines without crossing itself", "[Fill]")
|
||||
{
|
||||
const int multiline = GENERATE(2, 3);
|
||||
|
||||
Reference in New Issue
Block a user