mirror of
https://github.com/OrcaSlicer/OrcaSlicer.git
synced 2026-04-06 00:32:05 +02:00
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:
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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
|
||||
{
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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 ¶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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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) {
|
||||
|
||||
Reference in New Issue
Block a user