Allow specifying rotation patterns for Sparse and Solid infill (#9924)

* SPE-2405: Add Zig Zag infill that is rectilinear infill but with a consistent pattern between layers.

This Zig Zag infill is inspired by the Zig Zag infill in Cura.

Change-Id: I798affa99f4b5c3bd67f47643e67530fb7c3e0cb
(cherry picked from commit 2808d04d5deef6f99f9618648e46f11de03efc98)

* Add Cross zag and locked-zag for shoes

Ported from BambuStudio

* wip

* sparse infill roratation template

* solid_infill_rotate_template

* remove rotate_solid_infill_direction

* hide sparse infill rotation template for non applicable infill pattern

* hide solid_infill_rotate_template for non supported solid infill patterns

* update icon

* support empty string for ConfigOptionFloats deserialize

* fix build errors

---------

Co-authored-by: Lukáš Hejl <hejl.lukas@gmail.com>
This commit is contained in:
SoftFever
2025-06-22 23:10:35 +08:00
committed by GitHub
parent fa70582ed1
commit 88fb8187d9
39 changed files with 1551 additions and 714 deletions

View File

@@ -987,7 +987,6 @@ static std::vector<SegmentedIntersectionLine> slice_region_by_vertical_lines(con
throw;
}
#endif //INFILL_DEBUG_OUTPUT
return segs;
}
@@ -1352,8 +1351,11 @@ static SegmentIntersection& end_of_vertical_run(SegmentedIntersectionLine &il, S
return const_cast<SegmentIntersection&>(end_of_vertical_run(std::as_const(il), std::as_const(start)));
}
static void traverse_graph_generate_polylines(
const ExPolygonWithOffset& poly_with_offset, const FillParams& params, const coord_t link_max_length, std::vector<SegmentedIntersectionLine>& segs, Polylines& polylines_out)
static void traverse_graph_generate_polylines(const ExPolygonWithOffset &poly_with_offset,
const FillParams &params,
std::vector<SegmentedIntersectionLine> &segs,
const bool consistent_pattern,
Polylines &polylines_out)
{
// For each outer only chords, measure their maximum distance to the bow of the outer contour.
// Mark an outer only chord as consumed, if the distance is low.
@@ -1387,34 +1389,28 @@ static void traverse_graph_generate_polylines(
pointLast = polylines_out.back().points.back();
for (;;) {
if (i_intersection == -1) {
// The path has been interrupted. Find a next starting point, closest to the previous extruder position.
coordf_t dist2min = std::numeric_limits<coordf_t>().max();
for (int i_vline2 = 0; i_vline2 < int(segs.size()); ++ i_vline2) {
// The path has been interrupted. Find a next starting point.
for (int i_vline2 = 0; i_vline2 < int(segs.size()); ++i_vline2) {
const SegmentedIntersectionLine &vline = segs[i_vline2];
if (! vline.intersections.empty()) {
if (!vline.intersections.empty()) {
assert(vline.intersections.size() > 1);
// Even number of intersections with the loops.
assert((vline.intersections.size() & 1) == 0);
assert(vline.intersections.front().type == SegmentIntersection::OUTER_LOW);
for (int i = 0; i < int(vline.intersections.size()); ++ i) {
const SegmentIntersection& intrsctn = vline.intersections[i];
// For infill that needs to be consistent between layers (like Zig Zag),
// we are switching between forward and backward passes based on the line index.
const bool forward_pass = !consistent_pattern || (i_vline2 % 2 == 0);
for (int i = 0; i < int(vline.intersections.size()); ++i) {
const int intrsctn_idx = forward_pass ? i : int(vline.intersections.size()) - i - 1;
const SegmentIntersection &intrsctn = vline.intersections[intrsctn_idx];
if (intrsctn.is_outer()) {
assert(intrsctn.is_low() || i > 0);
bool consumed = intrsctn.is_low() ?
intrsctn.consumed_vertical_up :
vline.intersections[i - 1].consumed_vertical_up;
if (! consumed) {
coordf_t dist2 = sqr(coordf_t(pointLast(0) - vline.pos)) + sqr(coordf_t(pointLast(1) - intrsctn.pos()));
if (dist2 < dist2min) {
dist2min = dist2;
i_vline = i_vline2;
i_intersection = i;
//FIXME We are taking the first left point always. Verify, that the caller chains the paths
// by a shortest distance, while reversing the paths if needed.
//if (polylines_out.empty())
// Initial state, take the first line, which is the first from the left.
goto found;
}
assert(intrsctn.is_low() || intrsctn_idx > 0);
const bool consumed = intrsctn.is_low() ? intrsctn.consumed_vertical_up : vline.intersections[intrsctn_idx - 1].consumed_vertical_up;
if (!consumed) {
i_vline = i_vline2;
i_intersection = intrsctn_idx;
goto found;
}
}
}
@@ -1487,9 +1483,13 @@ static void traverse_graph_generate_polylines(
// 1) Find possible connection points on the previous / next vertical line.
int i_prev = it->left_horizontal();
int i_next = it->right_horizontal();
bool intersection_prev_valid = intersection_on_prev_vertical_line_valid(segs, i_vline, i_intersection);
// To ensure pattern consistency between layers for Zig Zag infill, we always
// try to connect to the next vertical line and never to the previous vertical line.
bool intersection_prev_valid = intersection_on_prev_vertical_line_valid(segs, i_vline, i_intersection) && !consistent_pattern;
bool intersection_next_valid = intersection_on_next_vertical_line_valid(segs, i_vline, i_intersection);
bool intersection_horizontal_valid = intersection_prev_valid || intersection_next_valid;
// Mark both the left and right connecting segment as consumed, because one cannot go to this intersection point as it has been consumed.
if (i_prev != -1)
segs[i_vline - 1].intersections[i_prev].consumed_perimeter_right = true;
@@ -2737,6 +2737,17 @@ static void polylines_from_paths(const std::vector<MonotonicRegionLink> &path, c
}
}
// The extended bounding box of the whole object that covers any rotation of every layer.
BoundingBox FillRectilinear::extended_object_bounding_box() const {
BoundingBox out = this->bounding_box;
out.merge(Point(out.min.y(), out.min.x()));
out.merge(Point(out.max.y(), out.max.x()));
// The bounding box is scaled by sqrt(2.) to ensure that the bounding box
// covers any possible rotations.
return out.scaled(sqrt(2.));
}
bool FillRectilinear::fill_surface_by_lines(const Surface *surface, const FillParams &params, float angleBase, float pattern_shift, Polylines &polylines_out)
{
// At the end, only the new polylines will be rotated back.
@@ -2749,6 +2760,8 @@ bool FillRectilinear::fill_surface_by_lines(const Surface *surface, const FillPa
// Rotate polygons so that we can work with vertical lines here
std::pair<float, Point> rotate_vector = this->_infill_direction(surface);
if (params.locked_zag)
rotate_vector.first += float(M_PI/2.);
rotate_vector.first += angleBase;
assert(params.density > 0.0001f && params.density <= 1.f);
@@ -2766,11 +2779,14 @@ bool FillRectilinear::fill_surface_by_lines(const Surface *surface, const FillPa
return true;
}
BoundingBox bounding_box = poly_with_offset.bounding_box_src();
// For infill that needs to be consistent between layers (like Zig Zag),
// we use bounding box of whole object to match vertical lines between layers.
BoundingBox bounding_box_src = poly_with_offset.bounding_box_src();
BoundingBox bounding_box = this->has_consistent_pattern() ? this->extended_object_bounding_box() : bounding_box_src;
// define flow spacing according to requested density
if (params.full_infill() && !params.dont_adjust) {
line_spacing = this->_adjust_solid_spacing(bounding_box.size()(0), line_spacing);
line_spacing = this->_adjust_solid_spacing(bounding_box_src.size().x(), line_spacing);
this->spacing = unscale<double>(line_spacing);
} else {
// extend bounding box so that our pattern will be aligned with other layers
@@ -2792,6 +2808,13 @@ bool FillRectilinear::fill_surface_by_lines(const Surface *surface, const FillPa
if (params.full_infill())
x0 += (line_spacing + coord_t(SCALED_EPSILON)) / 2;
int gap_line = params.horiz_move / line_spacing;
if (gap_line % 2 == 0) {
x0 += params.horiz_move - gap_line * line_spacing;
} else {
x0 += params.horiz_move - (gap_line - 1) * line_spacing;
n_vlines += 1;
}
#ifdef SLIC3R_DEBUG
static int iRun = 0;
BoundingBox bbox_svg = poly_with_offset.bounding_box_outer();
@@ -2803,7 +2826,6 @@ bool FillRectilinear::fill_surface_by_lines(const Surface *surface, const FillPa
}
iRun ++;
#endif /* SLIC3R_DEBUG */
std::vector<SegmentedIntersectionLine> segs = slice_region_by_vertical_lines(poly_with_offset, n_vlines, x0, line_spacing);
// Connect by horizontal / vertical links, classify the links based on link_max_length as too long.
connect_segment_intersections_by_contours(poly_with_offset, segs, params, link_max_length);
@@ -2848,8 +2870,9 @@ bool FillRectilinear::fill_surface_by_lines(const Surface *surface, const FillPa
std::vector<MonotonicRegionLink> path = chain_monotonic_regions(regions, poly_with_offset, segs, rng);
polylines_from_paths(path, poly_with_offset, segs, polylines_out);
}
} else
traverse_graph_generate_polylines(poly_with_offset, params, this->link_max_length, segs, polylines_out);
} else {
traverse_graph_generate_polylines(poly_with_offset, params, segs, this->has_consistent_pattern(), polylines_out);
}
#ifdef SLIC3R_DEBUG
{
@@ -2876,6 +2899,11 @@ bool FillRectilinear::fill_surface_by_lines(const Surface *surface, const FillPa
//FIXME rather simplify the paths to avoid very short edges?
//assert(! it->has_duplicate_points());
it->remove_duplicate_points();
//get origin direction infill
if (params.symmetric_infill_y_axis) {
it->symmetric_y(params.symmetric_y_axis);
}
}
#ifdef SLIC3R_DEBUG
@@ -2884,6 +2912,8 @@ bool FillRectilinear::fill_surface_by_lines(const Surface *surface, const FillPa
assert(! polyline.has_duplicate_points());
#endif /* SLIC3R_DEBUG */
return true;
}
@@ -2963,7 +2993,8 @@ bool FillRectilinear::fill_surface_by_multilines(const Surface *surface, FillPar
Polylines FillRectilinear::fill_surface(const Surface *surface, const FillParams &params)
{
Polylines polylines_out;
if (params.full_infill()) {
// Orca Todo: fow now don't use fill_surface_by_multilines for zipzag infill
if (params.full_infill() || params.pattern == ipCrossZag || params.pattern == ipZigZag || params.pattern == ipLockedZag) {
if (!fill_surface_by_lines(surface, params, 0.f, 0.f, polylines_out))
BOOST_LOG_TRIVIAL(error) << "FillRectilinear::fill_surface() fill_surface_by_lines() failed to fill a region.";
} else {
@@ -3409,5 +3440,119 @@ void FillMonotonicLineWGapFill::fill_surface_by_lines(const Surface* surface, co
}
}*/
void FillLockedZag::fill_surface_locked_zag (const Surface * surface,
const FillParams & params,
std::vector<std::pair<Polylines, Flow>> &multi_width_polyline)
{
// merge different part exps
// diff skin flow
Polylines skin_lines;
Polylines skeloton_lines;
double offset_threshold = params.skin_infill_depth;
double overlap_threshold = params.infill_lock_depth;
Surface cross_surface = *surface;
Surface zig_surface = *surface;
// inner exps
// inner union exps
ExPolygons zig_expas = offset_ex({surface->expolygon}, -offset_threshold);
ExPolygons cross_expas = diff_ex(surface->expolygon, zig_expas);
bool zig_get = false;
FillParams zig_params = params;
zig_params.horiz_move = 0;
// generate skeleton for diff density
auto generate_for_different_flow = [&multi_width_polyline](const std::map<Flow, ExPolygons> &flow_params, const Polylines &polylines) {
auto it = flow_params.begin();
while (it != flow_params.end()) {
ExPolygons region_exp = union_safety_offset_ex(it->second);
Polylines polys = intersection_pl(polylines, region_exp);
multi_width_polyline.emplace_back(polys, it->first);
it++;
}
};
auto it = this->lock_param.skeleton_density_params.begin();
while (it != this->lock_param.skeleton_density_params.end()) {
ExPolygons region_exp = union_safety_offset_ex(it->second);
ExPolygons exps = intersection_ex(region_exp, zig_expas);
zig_params.density = it->first;
exps = intersection_ex(offset_ex(exps, overlap_threshold), surface->expolygon);
for (ExPolygon &exp : exps) {
zig_surface.expolygon = exp;
Polylines zig_polylines_out = this->fill_surface(&zig_surface, zig_params);
skeloton_lines.insert(skeloton_lines.end(), zig_polylines_out.begin(), zig_polylines_out.end());
}
it++;
}
// set skeleton flow
generate_for_different_flow(this->lock_param.skeleton_flow_params, skeloton_lines);
// skin exps
bool cross_get = false;
FillParams cross_params = params;
cross_params.locked_zag = false;
auto skin_density = this->lock_param.skin_density_params.begin();
while (skin_density != this->lock_param.skin_density_params.end()) {
ExPolygons region_exp = union_safety_offset_ex(skin_density->second);
ExPolygons exps = intersection_ex(region_exp, cross_expas);
cross_params.density = skin_density->first;
for (ExPolygon &exp : exps) {
cross_surface.expolygon = exp;
Polylines cross_polylines_out = this->fill_surface(&cross_surface, cross_params);
skin_lines.insert(skin_lines.end(), cross_polylines_out.begin(), cross_polylines_out.end());
}
skin_density++;
}
generate_for_different_flow(this->lock_param.skin_flow_params, skin_lines);
}
void FillLockedZag::fill_surface_extrusion(const Surface *surface, const FillParams &params, ExtrusionEntitiesPtr &out)
{
Polylines polylines;
ThickPolylines thick_polylines;
std::vector<std::pair<Polylines, Flow>> multi_width_polyline;
try {
this->fill_surface_locked_zag(surface, params, multi_width_polyline);
}
catch (InfillFailedException&) {}
if (!thick_polylines.empty() || !multi_width_polyline.empty()) {
// Save into layer.
ExtrusionEntityCollection* eec = nullptr;
out.push_back(eec = new ExtrusionEntityCollection());
// Only concentric fills are not sorted.
eec->no_sort = this->no_sort();
size_t idx = eec->entities.size();
{
for (std::pair<Polylines, Flow> &poly_with_flow: multi_width_polyline) {
// calculate actual flow from spacing (which might have been adjusted by the infill
// pattern generator)
double flow_mm3_per_mm = poly_with_flow.second.mm3_per_mm();
double flow_width = poly_with_flow.second.width();
if (params.using_internal_flow) {
// if we used the internal flow we're not doing a solid infill
// so we can safely ignore the slight variation that might have
// been applied to f->spacing
} else {
Flow new_flow = poly_with_flow.second.with_spacing(this->spacing);
flow_mm3_per_mm = new_flow.mm3_per_mm();
flow_width = new_flow.width();
}
extrusion_entities_append_paths(
eec->entities, std::move(poly_with_flow.first),
params.extrusion_role,
flow_mm3_per_mm, float(flow_width), poly_with_flow.second.height());
}
}
if (!params.can_reverse) {
for (size_t i = idx; i < eec->entities.size(); i++)
eec->entities[i]->set_reverse();
}
}
}
} // namespace Slic3r