Infill Line Multiplier (#9432)
Some checks failed
Build all / Build All (push) Has been cancelled
Build all / Flatpak (push) Has been cancelled

* Infill Line Multiplier

* Modular Offset Function

* Lightning multiline

* Crosshatch Multiline

ipCrosshatch

* cleaning

Cleaning

clean2

* 3d Honeycomb

cut poliline ends

* Fill Tpmsd Multiline

Fill Tpmsd Multiline

* Update Multiline function

multiline funcion simplify

* Update FillTpmsD

* FillHoneycomb

* Update src/libslic3r/PrintConfig.cpp

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* Fix Honeycomb Multiline

Simplify polylines in honeycomb infill generation

* Improve multiline infill support and pattern simplification

Moved multiline infill application after pattern translation and simplification in Fill3DHoneycomb, and added multiline support to FillAdaptive. Updated honeycomb and 3D honeycomb infill to simplify polylines to 5x line width. Extended GUI and config to support multiline for Adaptive Cubic infill pattern and clarified max value comment.

minimum changes

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

* Increase multiline fill spacing in honeycomb infill

Adjusts the spacing parameter in the multiline_fill function to 1.1 times the original spacing, potentially improving infill distribution or print quality.

* Refine fill_multiline tooltip and pattern support logic

Updated the tooltip for the 'fill_multiline' parameter to improve clarity and punctuation. Refactored the logic in ConfigManipulation.cpp to clarify which infill patterns support multiline infill.

* better management of non supported infill patterns

---------

Co-authored-by: SoftFever <softfeverever@gmail.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Ian Bassi <ian.bassi@outlook.com>
Co-authored-by: Ian Bassi <12130714+ianalexis@users.noreply.github.com>
This commit is contained in:
Rodrigo 2025-06-30 12:07:33 -03:00 committed by GitHub
parent 10687085ee
commit a8141ef360
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
17 changed files with 164 additions and 37 deletions

View file

@ -41,6 +41,8 @@ struct SurfaceFillParams
// FillParams // FillParams
float density = 0.f; float density = 0.f;
// Infill line multiplier count.
int multiline = 1;
// Don't adjust spacing to fill the space evenly. // Don't adjust spacing to fill the space evenly.
// bool dont_adjust = false; // bool dont_adjust = false;
// Length of the infill anchor along the perimeter line. // Length of the infill anchor along the perimeter line.
@ -88,6 +90,7 @@ struct SurfaceFillParams
RETURN_COMPARE_NON_EQUAL(overlap); RETURN_COMPARE_NON_EQUAL(overlap);
RETURN_COMPARE_NON_EQUAL(angle); RETURN_COMPARE_NON_EQUAL(angle);
RETURN_COMPARE_NON_EQUAL(density); RETURN_COMPARE_NON_EQUAL(density);
RETURN_COMPARE_NON_EQUAL(multiline);
// RETURN_COMPARE_NON_EQUAL_TYPED(unsigned, dont_adjust); // RETURN_COMPARE_NON_EQUAL_TYPED(unsigned, dont_adjust);
RETURN_COMPARE_NON_EQUAL(anchor_length); RETURN_COMPARE_NON_EQUAL(anchor_length);
RETURN_COMPARE_NON_EQUAL(anchor_length_max); RETURN_COMPARE_NON_EQUAL(anchor_length_max);
@ -117,6 +120,7 @@ struct SurfaceFillParams
this->bridge == rhs.bridge && this->bridge == rhs.bridge &&
this->bridge_angle == rhs.bridge_angle && this->bridge_angle == rhs.bridge_angle &&
this->density == rhs.density && this->density == rhs.density &&
this->multiline == rhs.multiline &&
// this->dont_adjust == rhs.dont_adjust && // this->dont_adjust == rhs.dont_adjust &&
this->anchor_length == rhs.anchor_length && this->anchor_length == rhs.anchor_length &&
this->anchor_length_max == rhs.anchor_length_max && this->anchor_length_max == rhs.anchor_length_max &&
@ -647,6 +651,7 @@ std::vector<SurfaceFill> group_fills(const Layer &layer, LockRegionParam &lock_p
params.extruder = layerm.region().extruder(extrusion_role); params.extruder = layerm.region().extruder(extrusion_role);
params.pattern = region_config.sparse_infill_pattern.value; params.pattern = region_config.sparse_infill_pattern.value;
params.density = float(region_config.sparse_infill_density); params.density = float(region_config.sparse_infill_density);
params.multiline = int(region_config.fill_multiline);
params.lattice_angle_1 = region_config.lattice_angle_1; params.lattice_angle_1 = region_config.lattice_angle_1;
params.lattice_angle_2 = region_config.lattice_angle_2; params.lattice_angle_2 = region_config.lattice_angle_2;
params.infill_overhang_angle = region_config.infill_overhang_angle; params.infill_overhang_angle = region_config.infill_overhang_angle;
@ -1023,6 +1028,7 @@ void Layer::make_fills(FillAdaptive::Octree* adaptive_fill_octree, FillAdaptive:
// apply half spacing using this flow's own spacing and generate infill // apply half spacing using this flow's own spacing and generate infill
FillParams params; FillParams params;
params.density = float(0.01 * surface_fill.params.density); params.density = float(0.01 * surface_fill.params.density);
params.multiline = surface_fill.params.multiline;
params.dont_adjust = false; // surface_fill.params.dont_adjust; params.dont_adjust = false; // surface_fill.params.dont_adjust;
params.anchor_length = surface_fill.params.anchor_length; params.anchor_length = surface_fill.params.anchor_length;
params.anchor_length_max = surface_fill.params.anchor_length_max; params.anchor_length_max = surface_fill.params.anchor_length_max;
@ -1199,6 +1205,7 @@ Polylines Layer::generate_sparse_infill_polylines_for_anchoring(FillAdaptive::Oc
params.lattice_angle_1 = surface_fill.params.lattice_angle_1; params.lattice_angle_1 = surface_fill.params.lattice_angle_1;
params.lattice_angle_2 = surface_fill.params.lattice_angle_2; params.lattice_angle_2 = surface_fill.params.lattice_angle_2;
params.infill_overhang_angle = surface_fill.params.infill_overhang_angle; params.infill_overhang_angle = surface_fill.params.infill_overhang_angle;
params.multiline = surface_fill.params.multiline;
for (ExPolygon &expoly : surface_fill.expolygons) { for (ExPolygon &expoly : surface_fill.expolygons) {
// Spacing is modified by the filler to indicate adjustments. Reset it for each expolygon. // Spacing is modified by the filler to indicate adjustments. Reset it for each expolygon.

View file

@ -1,7 +1,7 @@
#include "../ClipperUtils.hpp" #include "../ClipperUtils.hpp"
#include "../ShortestPath.hpp" #include "../ShortestPath.hpp"
#include "../Surface.hpp" #include "../Surface.hpp"
#include "FillBase.hpp"
#include "Fill3DHoneycomb.hpp" #include "Fill3DHoneycomb.hpp"
namespace Slic3r { namespace Slic3r {
@ -212,7 +212,7 @@ void Fill3DHoneycomb::_fill_surface_single(
// = 4 * integrate(func=4*x(sqrt(2) - 1) + 1, from=0, to=0.25) // = 4 * integrate(func=4*x(sqrt(2) - 1) + 1, from=0, to=0.25)
// = (sqrt(2) + 1) / 2 [... I think] // = (sqrt(2) + 1) / 2 [... I think]
// make a first guess at the preferred grid Size // make a first guess at the preferred grid Size
coordf_t gridSize = (scale_(this->spacing) * ((zScale + 1.) / 2.) / params.density); coordf_t gridSize = (scale_(this->spacing) * ((zScale + 1.) / 2.) * params.multiline / params.density);
// This density calculation is incorrect for many values > 25%, possibly // This density calculation is incorrect for many values > 25%, possibly
// due to quantisation error, so this value is used as a first guess, then the // due to quantisation error, so this value is used as a first guess, then the
@ -228,7 +228,7 @@ void Fill3DHoneycomb::_fill_surface_single(
layersPerModule = 2; layersPerModule = 2;
// re-adjust the grid size for a partial octahedral path // re-adjust the grid size for a partial octahedral path
// (scale of 1.1 guessed based on modeling) // (scale of 1.1 guessed based on modeling)
gridSize = (scale_(this->spacing) * 1.1 / params.density); gridSize = (scale_(this->spacing) * 1.1 * params.multiline / params.density);
// re-adjust zScale to make layering consistent // re-adjust zScale to make layering consistent
zScale = (gridSize * 2) / (layersPerModule * layerHeight); zScale = (gridSize * 2) / (layersPerModule * layerHeight);
} else { } else {
@ -238,7 +238,7 @@ void Fill3DHoneycomb::_fill_surface_single(
// re-adjust zScale to make layering consistent // re-adjust zScale to make layering consistent
zScale = (gridSize * 2) / (layersPerModule * layerHeight); zScale = (gridSize * 2) / (layersPerModule * layerHeight);
// re-adjust the grid size to account for the new zScale // re-adjust the grid size to account for the new zScale
gridSize = (scale_(this->spacing) * ((zScale + 1.) / 2.) / params.density); gridSize = (scale_(this->spacing) * ((zScale + 1.) / 2.) * params.multiline / params.density);
// re-calculate layersPerModule and zScale // re-calculate layersPerModule and zScale
layersPerModule = floor((gridSize * 2) / (zScale * layerHeight) + 0.05); layersPerModule = floor((gridSize * 2) / (zScale * layerHeight) + 0.05);
if(layersPerModule < 2){ if(layersPerModule < 2){
@ -264,11 +264,24 @@ void Fill3DHoneycomb::_fill_surface_single(
// move pattern in place // move pattern in place
for (Polyline &pl : polylines){ for (Polyline &pl : polylines){
pl.translate(bb.min); pl.translate(bb.min);
pl.simplify(5 * spacing); // simplify to 5x line width
} }
// Apply multiline offset if needed
multiline_fill(polylines, params, spacing);
// clip pattern to boundaries, chain the clipped polylines // clip pattern to boundaries, chain the clipped polylines
polylines = intersection_pl(polylines, to_polygons(expolygon)); polylines = intersection_pl(polylines, to_polygons(expolygon));
if (! polylines.empty()) {
// Remove very small bits, but be careful to not remove infill lines connecting thin walls!
// The infill perimeter lines should be separated by around a single infill line width.
const double minlength = scale_(0.8 * this->spacing);
polylines.erase(
std::remove_if(polylines.begin(), polylines.end(), [minlength](const Polyline &pl) { return pl.length() < minlength; }),
polylines.end());
}
// copy from fliplines // copy from fliplines
if (!polylines.empty()) { if (!polylines.empty()) {
int infill_start_idx = polylines_out.size(); // only rotate what belongs to us. int infill_start_idx = polylines_out.size(); // only rotate what belongs to us.

View file

@ -1369,6 +1369,10 @@ void Filler::_fill_surface_single(
// Convert lines to polylines. // Convert lines to polylines.
all_polylines.reserve(lines.size()); 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 }; }); 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);
// Crop all polylines // Crop all polylines
all_polylines = intersection_pl(std::move(all_polylines), expolygon); all_polylines = intersection_pl(std::move(all_polylines), expolygon);
#endif #endif

View file

@ -1,6 +1,7 @@
#include <stdio.h> #include <stdio.h>
#include <numeric> #include <numeric>
#include <cmath>
#include "../ClipperUtils.hpp" #include "../ClipperUtils.hpp"
#include "../EdgeGrid.hpp" #include "../EdgeGrid.hpp"
#include "../Geometry.hpp" #include "../Geometry.hpp"
@ -25,7 +26,6 @@
// BBS: new infill pattern header // BBS: new infill pattern header
#include "FillConcentricInternal.hpp" #include "FillConcentricInternal.hpp"
#include "FillCrossHatch.hpp" #include "FillCrossHatch.hpp"
// #define INFILL_DEBUG_OUTPUT // #define INFILL_DEBUG_OUTPUT
namespace Slic3r { namespace Slic3r {
@ -190,22 +190,22 @@ void Fill::fill_surface_extrusion(const Surface* surface, const FillParams& para
// Orca: Dedicated function to calculate gap fill lines for the provided surface, according to the print object parameters // Orca: Dedicated function to calculate gap fill lines for the provided surface, according to the print object parameters
// and append them to the out ExtrusionEntityCollection. // and append them to the out ExtrusionEntityCollection.
void Fill::_create_gap_fill(const Surface* surface, const FillParams& params, ExtrusionEntityCollection* out){ void Fill::_create_gap_fill(const Surface* surface, const FillParams& params, ExtrusionEntityCollection* out){
//Orca: just to be safe, check against null pointer for the print object config and if NULL return. //Orca: just to be safe, check against null pointer for the print object config and if NULL return.
if (this->print_object_config == nullptr) return; if (this->print_object_config == nullptr) return;
// Orca: Enable gap fill as per the user preference. Return early if gap fill is to not be applied. // Orca: Enable gap fill as per the user preference. Return early if gap fill is to not be applied.
if ((this->print_object_config->gap_fill_target.value == gftNowhere) || if ((this->print_object_config->gap_fill_target.value == gftNowhere) ||
(surface->surface_type == stInternalSolid && this->print_object_config->gap_fill_target.value != gftEverywhere)) (surface->surface_type == stInternalSolid && this->print_object_config->gap_fill_target.value != gftEverywhere))
return; return;
Flow new_flow = params.flow; Flow new_flow = params.flow;
ExPolygons unextruded_areas; ExPolygons unextruded_areas;
unextruded_areas = diff_ex(this->no_overlap_expolygons, union_ex(out->polygons_covered_by_spacing(10))); unextruded_areas = diff_ex(this->no_overlap_expolygons, union_ex(out->polygons_covered_by_spacing(10)));
ExPolygons gapfill_areas = union_ex(unextruded_areas); ExPolygons gapfill_areas = union_ex(unextruded_areas);
if (!this->no_overlap_expolygons.empty()) if (!this->no_overlap_expolygons.empty())
gapfill_areas = intersection_ex(gapfill_areas, this->no_overlap_expolygons); gapfill_areas = intersection_ex(gapfill_areas, this->no_overlap_expolygons);
if (gapfill_areas.size() > 0 && params.density >= 1) { if (gapfill_areas.size() > 0 && params.density >= 1) {
double min = 0.2 * new_flow.scaled_spacing() * (1 - INSET_OVERLAP_TOLERANCE); double min = 0.2 * new_flow.scaled_spacing() * (1 - INSET_OVERLAP_TOLERANCE);
double max = 2. * new_flow.scaled_spacing(); double max = 2. * new_flow.scaled_spacing();
@ -222,20 +222,20 @@ void Fill::_create_gap_fill(const Surface* surface, const FillParams& params, Ex
std::vector<Points::size_type> order2 = chain_points(ordering_points); std::vector<Points::size_type> order2 = chain_points(ordering_points);
for (size_t i : order2) for (size_t i : order2)
gaps_ex_sorted.emplace_back(std::move(gaps_ex[i])); gaps_ex_sorted.emplace_back(std::move(gaps_ex[i]));
ThickPolylines polylines; ThickPolylines polylines;
for (ExPolygon& ex : gaps_ex_sorted) { for (ExPolygon& ex : gaps_ex_sorted) {
//BBS: Use DP simplify to avoid duplicated points and accelerate medial-axis calculation as well. //BBS: Use DP simplify to avoid duplicated points and accelerate medial-axis calculation as well.
ex.douglas_peucker(SCALED_RESOLUTION * 0.1); ex.douglas_peucker(SCALED_RESOLUTION * 0.1);
ex.medial_axis(min, max, &polylines); ex.medial_axis(min, max, &polylines);
} }
if (!polylines.empty() && !is_bridge(params.extrusion_role)) { if (!polylines.empty() && !is_bridge(params.extrusion_role)) {
polylines.erase(std::remove_if(polylines.begin(), polylines.end(), polylines.erase(std::remove_if(polylines.begin(), polylines.end(),
[&](const ThickPolyline& p) { [&](const ThickPolyline& p) {
return p.length() < scale_(params.config->filter_out_gap_fill.value); return p.length() < scale_(params.config->filter_out_gap_fill.value);
}), polylines.end()); }), polylines.end());
ExtrusionEntityCollection gap_fill; ExtrusionEntityCollection gap_fill;
variable_width(polylines, erGapFill, params.flow, gap_fill.entities); variable_width(polylines, erGapFill, params.flow, gap_fill.entities);
auto gap = std::move(gap_fill.entities); auto gap = std::move(gap_fill.entities);
@ -2696,4 +2696,55 @@ 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); connect_base_support(std::move(infill_ordered), polygons_src, bbox, polylines_out, spacing, params);
} }
//Fill Multiline
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);
const float center = (n_lines - 1) / 2.0f;
for (int line = 0; line < n_lines; ++line) {
float offset = (static_cast<float>(line) - center) * spacing;
for (const Polyline& pl : polylines) {
const size_t n = pl.points.size();
if (n < 2) {
all_polylines.emplace_back(pl);
continue;
}
Points new_points;
new_points.reserve(n);
for (size_t i = 0; i < n; ++i) {
Vec2f tangent;
if (i == 0)
tangent = Vec2f(pl.points[1].x() - pl.points[0].x(), pl.points[1].y() - pl.points[0].y());
else if (i == n - 1)
tangent = Vec2f(pl.points[n - 1].x() - pl.points[n - 2].x(), pl.points[n - 1].y() - pl.points[n - 2].y());
else
tangent = Vec2f(pl.points[i + 1].x() - pl.points[i - 1].x(), pl.points[i + 1].y() - pl.points[i - 1].y());
float len = std::hypot(tangent.x(), tangent.y());
if (len == 0)
len = 1.0f;
tangent /= len;
Vec2f normal(-tangent.y(), tangent.x());
Point p = pl.points[i];
p.x() += scale_(normal.x() * offset);
p.y() += scale_(normal.y() * offset);
new_points.push_back(p);
}
all_polylines.emplace_back(std::move(new_points));
}
}
polylines = std::move(all_polylines);
}
}
} // namespace Slic3r } // namespace Slic3r

View file

@ -53,6 +53,7 @@ struct FillParams
// Fill density, fraction in <0, 1> // Fill density, fraction in <0, 1>
float density { 0.f }; float density { 0.f };
int multiline{1};
// Length of an infill anchor along the perimeter. // Length of an infill anchor along the perimeter.
// 1000mm is roughly the maximum length line that fits into a 32bit coord_t. // 1000mm is roughly the maximum length line that fits into a 32bit coord_t.
@ -223,7 +224,8 @@ public:
static coord_t _adjust_solid_spacing(const coord_t width, const coord_t distance); static coord_t _adjust_solid_spacing(const coord_t width, const coord_t distance);
}; };
//Fill Multiline
void multiline_fill(Polylines& polylines, const FillParams& params, float spacing);
} // namespace Slic3r } // namespace Slic3r
#endif // slic3r_FillBase_hpp_ #endif // slic3r_FillBase_hpp_

View file

@ -2,7 +2,7 @@
#include "../ShortestPath.hpp" #include "../ShortestPath.hpp"
#include "../Surface.hpp" #include "../Surface.hpp"
#include <cmath> #include <cmath>
#include "FillBase.hpp"
#include "FillCrossHatch.hpp" #include "FillCrossHatch.hpp"
namespace Slic3r { namespace Slic3r {
@ -186,7 +186,8 @@ void FillCrossHatch ::_fill_surface_single(
BoundingBox bb = expolygon.contour.bounding_box(); BoundingBox bb = expolygon.contour.bounding_box();
// linespace modifier // linespace modifier
coord_t line_spacing = coord_t(scale_(this->spacing) / params.density); double density_adjusted = params.density / params.multiline;
coord_t line_spacing = coord_t(scale_(this->spacing) / density_adjusted);
// reduce density // reduce density
if (params.density < 0.999) line_spacing *= 1.08; if (params.density < 0.999) line_spacing *= 1.08;
@ -204,6 +205,9 @@ void FillCrossHatch ::_fill_surface_single(
// shift the pattern to the actual space // shift the pattern to the actual space
for (Polyline &pl : polylines) { pl.translate(bb.min); } for (Polyline &pl : polylines) { pl.translate(bb.min); }
// Apply multiline offset if needed
multiline_fill(polylines, params, spacing);
polylines = intersection_pl(polylines, to_polygons(expolygon)); polylines = intersection_pl(polylines, to_polygons(expolygon));
// --- remove small remains from gyroid infill // --- remove small remains from gyroid infill

View file

@ -4,7 +4,7 @@
#include <cmath> #include <cmath>
#include <algorithm> #include <algorithm>
#include <iostream> #include <iostream>
#include "FillBase.hpp"
#include "FillGyroid.hpp" #include "FillGyroid.hpp"
namespace Slic3r { namespace Slic3r {
@ -149,10 +149,10 @@ static Polylines make_gyroid_waves(double gridZ, double density_adjusted, double
constexpr double FillGyroid::PatternTolerance; constexpr double FillGyroid::PatternTolerance;
void FillGyroid::_fill_surface_single( void FillGyroid::_fill_surface_single(
const FillParams &params, const FillParams &params,
unsigned int thickness_layers, unsigned int thickness_layers,
const std::pair<float, Point> &direction, const std::pair<float, Point> &direction,
ExPolygon expolygon, ExPolygon expolygon,
Polylines &polylines_out) Polylines &polylines_out)
{ {
auto infill_angle = float(this->angle + (CorrectionAngle * 2*M_PI) / 360.); auto infill_angle = float(this->angle + (CorrectionAngle * 2*M_PI) / 360.);
@ -161,7 +161,7 @@ void FillGyroid::_fill_surface_single(
BoundingBox bb = expolygon.contour.bounding_box(); BoundingBox bb = expolygon.contour.bounding_box();
// Density adjusted to have a good %of weight. // Density adjusted to have a good %of weight.
double density_adjusted = std::max(0., params.density * DensityAdjust); double density_adjusted = std::max(0., params.density * DensityAdjust / params.multiline);
// Distance between the gyroid waves in scaled coordinates. // Distance between the gyroid waves in scaled coordinates.
coord_t distance = coord_t(scale_(this->spacing) / density_adjusted); coord_t distance = coord_t(scale_(this->spacing) / density_adjusted);
@ -184,6 +184,9 @@ void FillGyroid::_fill_surface_single(
for (Polyline &pl : polylines) for (Polyline &pl : polylines)
pl.translate(bb.min); pl.translate(bb.min);
// Apply multiline offset if needed
multiline_fill(polylines, params, spacing);
polylines = intersection_pl(polylines, expolygon); polylines = intersection_pl(polylines, expolygon);
if (! polylines.empty()) { if (! polylines.empty()) {

View file

@ -7,9 +7,9 @@
namespace Slic3r { namespace Slic3r {
void FillHoneycomb::_fill_surface_single( void FillHoneycomb::_fill_surface_single(
const FillParams &params, const FillParams &params,
unsigned int thickness_layers, unsigned int thickness_layers,
const std::pair<float, Point> &direction, const std::pair<float, Point> &direction,
ExPolygon expolygon, ExPolygon expolygon,
Polylines &polylines_out) Polylines &polylines_out)
{ {
@ -19,7 +19,7 @@ void FillHoneycomb::_fill_surface_single(
if (it_m == this->cache.end()) { if (it_m == this->cache.end()) {
it_m = this->cache.insert(it_m, std::pair<CacheID, CacheData>(cache_id, CacheData())); it_m = this->cache.insert(it_m, std::pair<CacheID, CacheData>(cache_id, CacheData()));
CacheData &m = it_m->second; CacheData &m = it_m->second;
coord_t min_spacing = coord_t(scale_(this->spacing)); coord_t min_spacing = coord_t(scale_(this->spacing)) * params.multiline;
m.distance = coord_t(min_spacing / params.density); m.distance = coord_t(min_spacing / params.density);
m.hex_side = coord_t(m.distance / (sqrt(3)/2)); m.hex_side = coord_t(m.distance / (sqrt(3)/2));
m.hex_width = m.distance * 2; // $m->{hex_width} == $m->{hex_side} * sqrt(3); m.hex_width = m.distance * 2; // $m->{hex_width} == $m->{hex_side} * sqrt(3);
@ -36,14 +36,14 @@ void FillHoneycomb::_fill_surface_single(
{ {
// adjust actual bounding box to the nearest multiple of our hex pattern // adjust actual bounding box to the nearest multiple of our hex pattern
// and align it so that it matches across layers // and align it so that it matches across layers
BoundingBox bounding_box = expolygon.contour.bounding_box(); BoundingBox bounding_box = expolygon.contour.bounding_box();
{ {
// rotate bounding box according to infill direction // rotate bounding box according to infill direction
Polygon bb_polygon = bounding_box.polygon(); Polygon bb_polygon = bounding_box.polygon();
bb_polygon.rotate(direction.first, m.hex_center); bb_polygon.rotate(direction.first, m.hex_center);
bounding_box = bb_polygon.bounding_box(); bounding_box = bb_polygon.bounding_box();
// extend bounding box so that our pattern will be aligned with other layers // extend bounding box so that our pattern will be aligned with other layers
// $bounding_box->[X1] and [Y1] represent the displacement between new bounding box offset and old one // $bounding_box->[X1] and [Y1] represent the displacement between new bounding box offset and old one
// The infill is not aligned to the object bounding box, but to a world coordinate system. Supposedly good enough. // The infill is not aligned to the object bounding box, but to a world coordinate system. Supposedly good enough.
@ -69,10 +69,13 @@ void FillHoneycomb::_fill_surface_single(
x += m.distance; x += m.distance;
} }
p.rotate(-direction.first, m.hex_center); p.rotate(-direction.first, m.hex_center);
p.simplify(5 * spacing); // simplify to 5x line width
all_polylines.push_back(p); all_polylines.push_back(p);
} }
} }
// Apply multiline offset if needed
multiline_fill(all_polylines, params, 1.1 * spacing);
all_polylines = intersection_pl(std::move(all_polylines), expolygon); all_polylines = intersection_pl(std::move(all_polylines), expolygon);
chain_or_connect_infill(std::move(all_polylines), expolygon, polylines_out, this->spacing, params); chain_or_connect_infill(std::move(all_polylines), expolygon, polylines_out, this->spacing, params);
} }

View file

@ -1,6 +1,6 @@
#include "../Print.hpp" #include "../Print.hpp"
#include "../ShortestPath.hpp" #include "../ShortestPath.hpp"
#include "FillBase.hpp"
#include "FillLightning.hpp" #include "FillLightning.hpp"
#include "Lightning/Generator.hpp" #include "Lightning/Generator.hpp"
@ -16,6 +16,10 @@ void Filler::_fill_surface_single(
const Layer &layer = generator->getTreesForLayer(this->layer_id); const Layer &layer = generator->getTreesForLayer(this->layer_id);
Polylines fill_lines = layer.convertToLines(to_polygons(expolygon), scaled<coord_t>(0.5 * this->spacing - this->overlap)); Polylines fill_lines = layer.convertToLines(to_polygons(expolygon), scaled<coord_t>(0.5 * this->spacing - this->overlap));
// Apply multiline offset if needed
multiline_fill(fill_lines, params, spacing);
chain_or_connect_infill(std::move(fill_lines), expolygon, polylines_out, this->spacing, params); chain_or_connect_infill(std::move(fill_lines), expolygon, polylines_out, this->spacing, params);
} }

View file

@ -2956,11 +2956,12 @@ void make_fill_lines(const ExPolygonWithOffset &poly_with_offset, Point refpt, d
} }
} }
bool FillRectilinear::fill_surface_by_multilines(const Surface *surface, FillParams params, const std::initializer_list<SweepParams> &sweep_params, Polylines &polylines_out) bool FillRectilinear::fill_surface_by_multilines(const Surface *surface, FillParams params, const std::initializer_list<SweepParams> &sweep_params, Polylines &polylines_out) // fill multiline
{ {
assert(sweep_params.size() >= 1); assert(sweep_params.size() >= 1);
assert(! params.full_infill()); assert(!params.full_infill());
params.density /= double(sweep_params.size()); params.density /= double(sweep_params.size());
int n_multilines = params.multiline;
assert(params.density > 0.0001f && params.density <= 1.f); 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 * this->spacing)));
@ -2970,12 +2971,21 @@ bool FillRectilinear::fill_surface_by_multilines(const Surface *surface, FillPar
Polylines fill_lines; Polylines fill_lines;
coord_t line_width = coord_t(scale_(this->spacing)); coord_t line_width = coord_t(scale_(this->spacing));
coord_t line_spacing = coord_t(scale_(this->spacing) / params.density); coord_t line_spacing = coord_t(scale_(this->spacing) * params.multiline / params.density);
std::pair<float, Point> rotate_vector = this->_infill_direction(surface); std::pair<float, Point> rotate_vector = this->_infill_direction(surface);
for (const SweepParams &sweep : sweep_params) { for (const SweepParams &sweep : sweep_params) {
// Rotate polygons so that we can work with vertical lines here // Rotate polygons so that we can work with vertical lines here
float angle = rotate_vector.first + sweep.angle_base; float angle = rotate_vector.first + sweep.angle_base;
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); //Fill Multiline
for (int i = 0; i < n_multilines; ++i) {
coord_t group_offset = i * line_spacing;
coord_t internal_offset = (i - (n_multilines - 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);
}
} }
if (!fill_lines.empty()) { if (!fill_lines.empty()) {
@ -3057,8 +3067,7 @@ Polylines Fill2DLattice::fill_surface(const Surface *surface, const FillParams &
return polylines_out; return polylines_out;
} }
Polylines FillTriangles::fill_surface(const Surface *surface, const FillParams &params) Polylines FillTriangles::fill_surface(const Surface *surface, const FillParams &params){
{
Polylines polylines_out; Polylines polylines_out;
if (! this->fill_surface_by_multilines( if (! this->fill_surface_by_multilines(
surface, params, surface, params,
@ -3073,7 +3082,7 @@ Polylines FillStars::fill_surface(const Surface *surface, const FillParams &para
Polylines polylines_out; Polylines polylines_out;
if (! this->fill_surface_by_multilines( if (! this->fill_surface_by_multilines(
surface, params, surface, params,
{ { 0.f, 0.f }, { float(M_PI / 3.), 0.f }, { float(2. * M_PI / 3.), float((3./2.) * this->spacing / params.density) } }, { { 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)) polylines_out))
BOOST_LOG_TRIVIAL(error) << "FillStars::fill_surface() failed to fill a region."; BOOST_LOG_TRIVIAL(error) << "FillStars::fill_surface() failed to fill a region.";
return polylines_out; return polylines_out;
@ -3094,7 +3103,6 @@ Polylines FillCubic::fill_surface(const Surface *surface, const FillParams &para
Polylines FillQuarterCubic::fill_surface(const Surface* surface, const FillParams& params) Polylines FillQuarterCubic::fill_surface(const Surface* surface, const FillParams& params)
{ {
using namespace boost::math::float_constants; using namespace boost::math::float_constants;
Polylines polylines_out; Polylines polylines_out;
coord_t line_width = coord_t(scale_(this->spacing)); coord_t line_width = coord_t(scale_(this->spacing));

View file

@ -110,7 +110,7 @@ void FillTpmsD::_fill_surface_single(
BoundingBox bb = expolygon.contour.bounding_box(); BoundingBox bb = expolygon.contour.bounding_box();
// Density adjusted to have a good %of weight. // Density adjusted to have a good %of weight.
double density_adjusted = std::max(0., params.density * DensityAdjust); double density_adjusted = std::max(0., params.density * DensityAdjust / params.multiline);
// Distance between the gyroid waves in scaled coordinates. // Distance between the gyroid waves in scaled coordinates.
coord_t distance = coord_t(scale_(this->spacing) / density_adjusted); coord_t distance = coord_t(scale_(this->spacing) / density_adjusted);
@ -129,6 +129,8 @@ void FillTpmsD::_fill_surface_single(
for (Polyline &pl : polylines) for (Polyline &pl : polylines)
pl.translate(bb.min); pl.translate(bb.min);
// Apply multiline offset if needed
multiline_fill(polylines, params, spacing);
polylines = intersection_pl(polylines, expolygon); polylines = intersection_pl(polylines, expolygon);

View file

@ -785,7 +785,7 @@ static std::vector<std::string> s_Preset_print_options {
"layer_height", "initial_layer_print_height", "wall_loops", "alternate_extra_wall", "slice_closing_radius", "spiral_mode", "spiral_mode_smooth", "spiral_mode_max_xy_smoothing", "spiral_starting_flow_ratio", "spiral_finishing_flow_ratio", "slicing_mode", "layer_height", "initial_layer_print_height", "wall_loops", "alternate_extra_wall", "slice_closing_radius", "spiral_mode", "spiral_mode_smooth", "spiral_mode_max_xy_smoothing", "spiral_starting_flow_ratio", "spiral_finishing_flow_ratio", "slicing_mode",
"top_shell_layers", "top_shell_thickness", "top_surface_density", "bottom_surface_density", "bottom_shell_layers", "bottom_shell_thickness", "top_shell_layers", "top_shell_thickness", "top_surface_density", "bottom_surface_density", "bottom_shell_layers", "bottom_shell_thickness",
"extra_perimeters_on_overhangs", "ensure_vertical_shell_thickness", "reduce_crossing_wall", "detect_thin_wall", "detect_overhang_wall", "overhang_reverse", "overhang_reverse_threshold","overhang_reverse_internal_only", "wall_direction", "extra_perimeters_on_overhangs", "ensure_vertical_shell_thickness", "reduce_crossing_wall", "detect_thin_wall", "detect_overhang_wall", "overhang_reverse", "overhang_reverse_threshold","overhang_reverse_internal_only", "wall_direction",
"seam_position", "staggered_inner_seams", "wall_sequence", "is_infill_first", "sparse_infill_density", "sparse_infill_pattern", "lattice_angle_1", "lattice_angle_2", "infill_overhang_angle", "top_surface_pattern", "bottom_surface_pattern", "seam_position", "staggered_inner_seams", "wall_sequence", "is_infill_first", "sparse_infill_density","fill_multiline", "sparse_infill_pattern", "lattice_angle_1", "lattice_angle_2", "infill_overhang_angle", "top_surface_pattern", "bottom_surface_pattern",
"infill_direction", "solid_infill_direction", "counterbore_hole_bridging","infill_shift_step", "sparse_infill_rotate_template", "solid_infill_rotate_template", "symmetric_infill_y_axis","skeleton_infill_density", "infill_lock_depth", "skin_infill_depth", "skin_infill_density", "infill_direction", "solid_infill_direction", "counterbore_hole_bridging","infill_shift_step", "sparse_infill_rotate_template", "solid_infill_rotate_template", "symmetric_infill_y_axis","skeleton_infill_density", "infill_lock_depth", "skin_infill_depth", "skin_infill_density",
"minimum_sparse_infill_area", "reduce_infill_retraction","internal_solid_infill_pattern","gap_fill_target", "minimum_sparse_infill_area", "reduce_infill_retraction","internal_solid_infill_pattern","gap_fill_target",
"ironing_type", "ironing_pattern", "ironing_flow", "ironing_speed", "ironing_spacing", "ironing_angle", "ironing_inset", "ironing_type", "ironing_pattern", "ironing_flow", "ironing_speed", "ironing_spacing", "ironing_angle", "ironing_inset",

View file

@ -2361,6 +2361,14 @@ void PrintConfigDef::init_fff_params()
def->max = 100; def->max = 100;
def->set_default_value(new ConfigOptionPercent(20)); def->set_default_value(new ConfigOptionPercent(20));
// Infill multiline
def = this->add("fill_multiline", coInt);
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->set_default_value(new ConfigOptionInt(1));
def = this->add("sparse_infill_pattern", coEnum); def = this->add("sparse_infill_pattern", coEnum);
def->label = L("Sparse infill pattern"); def->label = L("Sparse infill pattern");
def->category = L("Strength"); def->category = L("Strength");

View file

@ -980,6 +980,7 @@ PRINT_CONFIG_CLASS_DEFINE(
((ConfigOptionBool, infill_combination)) ((ConfigOptionBool, infill_combination))
// Orca: // Orca:
((ConfigOptionFloatOrPercent, infill_combination_max_layer_height)) ((ConfigOptionFloatOrPercent, infill_combination_max_layer_height))
((ConfigOptionInt, fill_multiline))
// Ironing options // Ironing options
((ConfigOptionEnum<IroningType>, ironing_type)) ((ConfigOptionEnum<IroningType>, ironing_type))
((ConfigOptionEnum<InfillPattern>, ironing_pattern)) ((ConfigOptionEnum<InfillPattern>, ironing_pattern))

View file

@ -1065,6 +1065,7 @@ bool PrintObject::invalidate_state_by_config_options(
#endif #endif
} else if ( } else if (
opt_key == "interface_shells" opt_key == "interface_shells"
|| opt_key == "infill_multiline"
|| opt_key == "infill_combination" || opt_key == "infill_combination"
|| opt_key == "infill_combination_max_layer_height" || opt_key == "infill_combination_max_layer_height"
|| opt_key == "bottom_shell_thickness" || opt_key == "bottom_shell_thickness"

View file

@ -294,6 +294,7 @@ void ConfigManipulation::update_print_fff_config(DynamicPrintConfig* config, con
} }
double sparse_infill_density = config->option<ConfigOptionPercent>("sparse_infill_density")->value; double sparse_infill_density = config->option<ConfigOptionPercent>("sparse_infill_density")->value;
int fill_multiline = config->option<ConfigOptionInt>("fill_multiline")->value;
auto timelapse_type = config->opt_enum<TimelapseType>("timelapse_type"); auto timelapse_type = config->opt_enum<TimelapseType>("timelapse_type");
if (!is_plate_config && if (!is_plate_config &&
@ -546,6 +547,20 @@ void ConfigManipulation::toggle_print_fff_options(DynamicPrintConfig *config, co
bool have_combined_infill = config->opt_bool("infill_combination") && have_infill; bool have_combined_infill = config->opt_bool("infill_combination") && have_infill;
toggle_line("infill_combination_max_layer_height", have_combined_infill); toggle_line("infill_combination_max_layer_height", have_combined_infill);
// 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 == ipCrossHatch || pattern == ipHoneycomb ||
pattern == ipCubic || pattern == ipStars || pattern == ipAlignedRectilinear || pattern == ipLightning || pattern == ip3DHoneycomb || pattern == ipAdaptiveCubic || pattern == ipSupportCubic;
toggle_line("fill_multiline", have_multiline_infill_pattern);
// If the infill pattern does not support multiline infill, set fill_multiline to 1.
if (have_multiline_infill_pattern==false) {
DynamicPrintConfig new_conf = *config;
new_conf.set_key_value("fill_multiline", new ConfigOptionInt(1));
apply(config, &new_conf);
}
// Hide infill anchor max if sparse_infill_pattern is not line or if sparse_infill_pattern is line but infill_anchor_max is 0.
bool infill_anchor = config->opt_enum<InfillPattern>("sparse_infill_pattern") != ipLine; bool infill_anchor = config->opt_enum<InfillPattern>("sparse_infill_pattern") != ipLine;
toggle_field("infill_anchor_max",infill_anchor); toggle_field("infill_anchor_max",infill_anchor);

View file

@ -2207,6 +2207,7 @@ void TabPrint::build()
optgroup = page->new_optgroup(L("Infill"), L"param_infill"); optgroup = page->new_optgroup(L("Infill"), L"param_infill");
optgroup->append_single_option_line("sparse_infill_density", "strength_settings_infill#sparse-infill-density"); optgroup->append_single_option_line("sparse_infill_density", "strength_settings_infill#sparse-infill-density");
optgroup->append_single_option_line("fill_multiline"); // fill multiline
optgroup->append_single_option_line("sparse_infill_pattern", "strength_settings_infill#sparse-infill-pattern"); optgroup->append_single_option_line("sparse_infill_pattern", "strength_settings_infill#sparse-infill-pattern");
optgroup->append_single_option_line("infill_direction", "strength_settings_infill#direction"); optgroup->append_single_option_line("infill_direction", "strength_settings_infill#direction");
optgroup->append_single_option_line("sparse_infill_rotate_template", "strength_settings_infill#rotation"); optgroup->append_single_option_line("sparse_infill_rotate_template", "strength_settings_infill#rotation");