From f3d736ab63f9ec127ca030364d48c648dfb7bd1e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20=C5=A0ach?= Date: Mon, 13 Jan 2025 12:27:16 +0100 Subject: [PATCH 1/5] SPE-2639 Make seam placing tolerate invalid data. Even if the seam placer generates invalid scarf seam that has start_point == end_point, deal with it gracefully. --- src/libslic3r/GCode.cpp | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/src/libslic3r/GCode.cpp b/src/libslic3r/GCode.cpp index 21cd0bb251..fcb8d1ea77 100644 --- a/src/libslic3r/GCode.cpp +++ b/src/libslic3r/GCode.cpp @@ -2302,13 +2302,20 @@ std::pair split_with_seam( if (loop.paths.empty() || loop.paths.front().empty()) { return {SmoothPath{}, 0}; } - if (const auto seam_point{boost::get(&seam)}; seam_point != nullptr) { + const auto seam_point{boost::get(&seam)}; + const auto scarf{boost::get(&seam)}; + + if (seam_point != nullptr) { return { smooth_path_cache.resolve_or_fit_split_with_seam( loop, flipped, scaled_resolution, *seam_point, seam_point_merge_distance_threshold ), 0}; - } else if (const auto scarf{boost::get(&seam)}; scarf != nullptr) { + } else if (scarf != nullptr && scarf->start_point == scarf->end_point) { + return {smooth_path_cache.resolve_or_fit_split_with_seam( + loop, flipped, scaled_resolution, scarf->start_point, seam_point_merge_distance_threshold + ), 0}; + } else if (scarf != nullptr) { ExtrusionPaths paths{loop.paths}; const auto apply_smoothing{[&](tcb::span paths){ return smooth_path_cache.resolve_or_fit(paths, false, scaled(0.0015)); From 02779e211f7a7b299a117b20df4fce3f892f61ae Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20=C5=A0ach?= Date: Mon, 13 Jan 2025 17:34:14 +0100 Subject: [PATCH 2/5] SPE-2639: Ensure no scarf seam is generated when a point fail to project to the perimeter --- src/libslic3r/GCode/SeamPlacer.cpp | 21 ++++++++++++++++----- 1 file changed, 16 insertions(+), 5 deletions(-) diff --git a/src/libslic3r/GCode/SeamPlacer.cpp b/src/libslic3r/GCode/SeamPlacer.cpp index 257716769b..da4d7ea292 100644 --- a/src/libslic3r/GCode/SeamPlacer.cpp +++ b/src/libslic3r/GCode/SeamPlacer.cpp @@ -312,11 +312,15 @@ boost::variant finalize_seam_position( } if (loop.role() != ExtrusionRole::Perimeter) { // Outter perimeter - scarf.start_point = scaled(project_to_extrusion_loop( + const Vec2d start_point_candidate{project_to_extrusion_loop( to_seam_choice(*outter_scarf_start_point, perimeter), perimeter, distancer - ).second); + ).second}; + if ((start_point_candidate - outter_scarf_start_point->point).norm() > 5.0) { + return scaled(loop_point); + } + scarf.start_point = scaled(start_point_candidate); scarf.end_point = scaled(loop_point); scarf.end_point_previous_index = loop_line_index; return scarf; @@ -359,18 +363,25 @@ boost::variant finalize_seam_position( if (!inner_scarf_start_point) { return scaled(inner_scarf_end_point.point); } - - scarf.start_point = scaled(project_to_extrusion_loop( + const Vec2d start_point_candidate{project_to_extrusion_loop( to_seam_choice(*inner_scarf_start_point, perimeter), perimeter, distancer - ).second); + ).second}; + if ((start_point_candidate - inner_scarf_start_point->point).norm() > 5.0) { + return scaled(loop_point); + } + scarf.start_point = scaled(start_point_candidate); const auto [end_point_previous_index, end_point]{project_to_extrusion_loop( to_seam_choice(inner_scarf_end_point, perimeter), perimeter, distancer )}; + if ((end_point - inner_scarf_end_point.point).norm() > 5.0) { + return scaled(loop_point); + } + scarf.end_point = scaled(end_point); scarf.end_point_previous_index = end_point_previous_index; return scarf; From ceb9950e35712d67f0462b3c970ac8167f5c8201 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20=C5=A0ach?= Date: Mon, 13 Jan 2025 17:57:58 +0100 Subject: [PATCH 3/5] SPE-2635: Fix picking the rear seam line --- src/libslic3r/GCode/SeamRear.cpp | 60 +++++++++++++++++++++++++------- 1 file changed, 47 insertions(+), 13 deletions(-) diff --git a/src/libslic3r/GCode/SeamRear.cpp b/src/libslic3r/GCode/SeamRear.cpp index 727315fa27..e7792c5fd3 100644 --- a/src/libslic3r/GCode/SeamRear.cpp +++ b/src/libslic3r/GCode/SeamRear.cpp @@ -24,6 +24,40 @@ BoundingBoxf get_bounding_box(const Shells::Shell<> &shell) { return result; } +SeamChoice get_max_y_choice(const std::vector &possible_lines) { + if (possible_lines.empty()) { + throw std::runtime_error{"No possible lines!"}; + } + + Vec2d point{possible_lines.front().a}; + std::size_t point_index{0}; + + for (const PerimeterLine &line : possible_lines) { + if (line.a.y() > point.y()) { + point = line.a; + point_index = line.previous_index; + } + if (line.b.y() > point.y()) { + point = line.b; + point_index = line.next_index; + } + } + + return SeamChoice{point_index, point_index, point}; +} + +SeamChoice get_nearest( + const AABBTreeLines::LinesDistancer& distancer, + const Vec2d point +) { + const auto [_, line_index, resulting_point] = distancer.distance_from_lines_extra(point); + return SeamChoice{ + distancer.get_lines()[line_index].previous_index, + distancer.get_lines()[line_index].next_index, + resulting_point + }; +} + struct RearestPointCalculator { double rear_tolerance; double rear_y_offset; @@ -62,26 +96,26 @@ struct RearestPointCalculator { auto [_d, line_index_at_bb, point_bb] = possible_distancer.distance_from_lines_extra(location_at_bb); const double y_distance{point.y() - point_bb.y()}; - Vec2d result{point}; + SeamChoice result{possible_lines[line_index].previous_index, possible_lines[line_index].next_index, point}; + if (y_distance < 0) { - result = point_bb; + result = get_nearest( + possible_distancer, + point_bb + ); } else if (y_distance <= rear_tolerance) { const double factor{y_distance / rear_tolerance}; - result = factor * point + (1 - factor) * point_bb; + result = get_nearest( + possible_distancer, + factor * point + (1 - factor) * point_bb + ); } - if (bounding_box.max.y() - result.y() > rear_tolerance) { - for (const PerimeterLine &line : possible_lines) { - if (line.a.y() > result.y()) { - result = line.a; - } - if (line.b.y() > result.y()) { - result = line.b; - } - } + if (bounding_box.max.y() - result.position.y() > rear_tolerance) { + return get_max_y_choice(possible_lines); } - return SeamChoice{possible_lines[line_index].previous_index, possible_lines[line_index].next_index, result}; + return result; } }; } // namespace Impl From 6176dcc5404dd101447e7d1bf23b601c607c0d9f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20=C5=A0ach?= Date: Tue, 14 Jan 2025 10:32:48 +0100 Subject: [PATCH 4/5] SPE-2639: Fix invalid scarf seam direction leading to whole perimeter scarfs --- src/libslic3r/GCode/SeamPlacer.cpp | 21 +++++++++++++++++++-- 1 file changed, 19 insertions(+), 2 deletions(-) diff --git a/src/libslic3r/GCode/SeamPlacer.cpp b/src/libslic3r/GCode/SeamPlacer.cpp index da4d7ea292..24d0b86ff5 100644 --- a/src/libslic3r/GCode/SeamPlacer.cpp +++ b/src/libslic3r/GCode/SeamPlacer.cpp @@ -235,6 +235,24 @@ SeamChoice to_seam_choice( return result; } +Geometry::Direction1D get_direction( + const bool flipped, + const ExPolygon &perimeter_polygon, + const ExtrusionLoop &loop +) { + using Dir = Geometry::Direction1D; + + Dir result{flipped ? Dir::forward : Dir::backward}; + + // In rare cases it may happen that the original geometry perimeter + // polygon has different direction to the actual extrusion loop. + // In that case the logic is exactly opposite. + if (perimeter_polygon.contour.is_clockwise() != loop.is_clockwise()) { + result = result == Dir::forward ? Dir::backward : Dir::forward; + } + return result; +} + boost::variant finalize_seam_position( const ExtrusionLoop &loop, const PrintRegion *region, @@ -255,8 +273,7 @@ boost::variant finalize_seam_position( auto [loop_line_index, loop_point]{ project_to_extrusion_loop(seam_choice, perimeter, distancer)}; - const Geometry::Direction1D offset_direction{ - flipped ? Geometry::Direction1D::forward : Geometry::Direction1D::backward}; + const Geometry::Direction1D offset_direction{get_direction(flipped, perimeter_polygon, loop)}; // ExtrusionRole::Perimeter is inner perimeter. if (do_staggering) { From 6494a3badd3c79d907feb1025620f8ddaba450e8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20=C5=A0ach?= Date: Tue, 14 Jan 2025 13:23:35 +0100 Subject: [PATCH 5/5] Fix #13870: Prefer a corner if there is one clearly rearest corner to be picked when placing rear seam --- src/libslic3r/GCode/SeamRear.cpp | 60 ++++++++++++++++++++++++++++---- 1 file changed, 54 insertions(+), 6 deletions(-) diff --git a/src/libslic3r/GCode/SeamRear.cpp b/src/libslic3r/GCode/SeamRear.cpp index e7792c5fd3..7059801a7b 100644 --- a/src/libslic3r/GCode/SeamRear.cpp +++ b/src/libslic3r/GCode/SeamRear.cpp @@ -24,13 +24,48 @@ BoundingBoxf get_bounding_box(const Shells::Shell<> &shell) { return result; } +std::optional get_clear_max_y_corner( + const std::vector &possible_lines, + const Perimeters::Perimeter &perimeter, + const SeamChoice &max_y_choice, + const double rear_tolerance +) { + if (perimeter.angle_types[max_y_choice.previous_index] != Perimeters::AngleType::concave) { + return std::nullopt; + } + + const double epsilon{1e-2}; + + // Check if there are two max y corners (e.g. on a cube). + for (const PerimeterLine &line : possible_lines) { + if ( + line.previous_index != max_y_choice.previous_index + && perimeter.angle_types[line.previous_index] == Perimeters::AngleType::concave + && max_y_choice.position.y() < line.a.y() + epsilon + && (max_y_choice.position - line.a).norm() > epsilon + ) { + return std::nullopt; + } + if ( + line.next_index != max_y_choice.next_index + && perimeter.angle_types[line.next_index] == Perimeters::AngleType::concave + && max_y_choice.position.y() < line.b.y() + epsilon + && (max_y_choice.position - line.b).norm() > epsilon + ) { + return std::nullopt; + } + } + + return max_y_choice; +} + SeamChoice get_max_y_choice(const std::vector &possible_lines) { if (possible_lines.empty()) { throw std::runtime_error{"No possible lines!"}; } Vec2d point{possible_lines.front().a}; - std::size_t point_index{0}; + std::size_t point_index{possible_lines.front().previous_index}; for (const PerimeterLine &line : possible_lines) { if (line.a.y() > point.y()) { @@ -69,24 +104,37 @@ struct RearestPointCalculator { const PointClassification point_classification ) { std::vector possible_lines; - for (std::size_t i{0}; i < perimeter.positions.size() - 1; ++i) { + for (std::size_t i{0}; i < perimeter.positions.size(); ++i) { + const std::size_t next_index{i == perimeter.positions.size() - 1 ? 0 : i + 1}; if (perimeter.point_types[i] != point_type) { continue; } if (perimeter.point_classifications[i] != point_classification) { continue; } - if (perimeter.point_types[i + 1] != point_type) { + if (perimeter.point_types[next_index] != point_type) { continue; } - if (perimeter.point_classifications[i + 1] != point_classification) { + if (perimeter.point_classifications[next_index] != point_classification) { continue; } - possible_lines.push_back(PerimeterLine{perimeter.positions[i], perimeter.positions[i+1], i, i + 1}); + possible_lines.push_back(PerimeterLine{perimeter.positions[i], perimeter.positions[next_index], i, next_index}); } if (possible_lines.empty()) { return std::nullopt; } + + const SeamChoice max_y_choice{get_max_y_choice(possible_lines)}; + + if (const auto clear_max_y_corner{get_clear_max_y_corner( + possible_lines, + perimeter, + max_y_choice, + rear_tolerance + )}) { + return *clear_max_y_corner; + } + const BoundingBoxf bounding_box{perimeter.positions}; const AABBTreeLines::LinesDistancer possible_distancer{possible_lines}; const double center_x{(bounding_box.max.x() + bounding_box.min.x()) / 2.0}; @@ -112,7 +160,7 @@ struct RearestPointCalculator { } if (bounding_box.max.y() - result.position.y() > rear_tolerance) { - return get_max_y_choice(possible_lines); + return max_y_choice; } return result;