diff --git a/.clang-format b/.clang-format index 0e6ea30f72..1ae60dcf90 100644 --- a/.clang-format +++ b/.clang-format @@ -95,6 +95,7 @@ PenaltyBreakString: 600 PenaltyBreakTemplateDeclaration: 10 PenaltyExcessCharacter: 50 PenaltyReturnTypeOnItsOwnLine: 300 +PenaltyIndentedWhitespace: 10 PointerAlignment: Right ReflowComments: true SortIncludes: false diff --git a/src/libslic3r/GCode/SeamAligned.cpp b/src/libslic3r/GCode/SeamAligned.cpp index 4572e5cc71..724ead312f 100644 --- a/src/libslic3r/GCode/SeamAligned.cpp +++ b/src/libslic3r/GCode/SeamAligned.cpp @@ -41,42 +41,17 @@ const Perimeters::Perimeter::OptionalPointTree &pick_tree( throw std::runtime_error("Point tree for classification does not exist."); } -unsigned point_value(PointType point_type, PointClassification point_classification) { - // Better be explicit than smart. - switch (point_type) { - case PointType::enforcer: - switch (point_classification) { - case PointClassification::embedded: return 9; - case PointClassification::common: return 8; - case PointClassification::overhang: return 7; - } - case PointType::common: - switch (point_classification) { - case PointClassification::embedded: return 6; - case PointClassification::common: return 5; - case PointClassification::overhang: return 4; - } - case PointType::blocker: - switch (point_classification) { - case PointClassification::embedded: return 3; - case PointClassification::common: return 2; - case PointClassification::overhang: return 1; - } - } - return 0; -} - SeamChoice pick_seam_option(const Perimeters::Perimeter &perimeter, const SeamOptions &options) { const std::vector &types{perimeter.point_types}; const std::vector &classifications{perimeter.point_classifications}; const std::vector &positions{perimeter.positions}; unsigned closeset_point_value = - point_value(types.at(options.closest), classifications[options.closest]); + get_point_value(types.at(options.closest), classifications[options.closest]); if (options.snapped) { unsigned snapped_point_value = - point_value(types.at(*options.snapped), classifications[*options.snapped]); + get_point_value(types.at(*options.snapped), classifications[*options.snapped]); if (snapped_point_value >= closeset_point_value) { const Vec2d position{positions.at(*options.snapped)}; return {*options.snapped, *options.snapped, position}; @@ -84,7 +59,7 @@ SeamChoice pick_seam_option(const Perimeters::Perimeter &perimeter, const SeamOp } unsigned adjacent_point_value = - point_value(types.at(options.adjacent), classifications[options.adjacent]); + get_point_value(types.at(options.adjacent), classifications[options.adjacent]); if (adjacent_point_value < closeset_point_value) { const Vec2d position = positions[options.closest]; return {options.closest, options.closest, position}; diff --git a/src/libslic3r/GCode/SeamChoice.hpp b/src/libslic3r/GCode/SeamChoice.hpp index d31833ed50..d369f3d685 100644 --- a/src/libslic3r/GCode/SeamChoice.hpp +++ b/src/libslic3r/GCode/SeamChoice.hpp @@ -14,17 +14,7 @@ #include "libslic3r/Point.hpp" namespace Slic3r::Seams { - -/** - * When previous_index == next_index, the point is at the point. - * Otherwise the point is at the edge. - */ -struct SeamChoice -{ - std::size_t previous_index{}; - std::size_t next_index{}; - Vec2d position{Vec2d::Zero()}; -}; +using SeamChoice = Perimeters::PointOnPerimeter; struct SeamPerimeterChoice { diff --git a/src/libslic3r/GCode/SeamGeometry.cpp b/src/libslic3r/GCode/SeamGeometry.cpp index 04a2668709..c731e5f0c2 100644 --- a/src/libslic3r/GCode/SeamGeometry.cpp +++ b/src/libslic3r/GCode/SeamGeometry.cpp @@ -469,56 +469,4 @@ Polygon to_polygon(const ExtrusionLoop &loop) { return Polygon{loop_points}; } -std::optional offset_along_lines( - const Vec2d &point, - const std::size_t loop_line_index, - const Linesf &loop_lines, - const double offset, - const Direction1D direction -) { - const Linef initial_line{loop_lines[loop_line_index]}; - double distance{ - direction == Direction1D::forward ? (initial_line.b - point).norm() : - (point - initial_line.a).norm()}; - if (distance >= offset) { - const Vec2d edge_direction{(initial_line.b - initial_line.a).normalized()}; - const Vec2d offset_point{direction == Direction1D::forward ? Vec2d{point + offset * edge_direction} : Vec2d{point - offset * edge_direction}}; - return {{offset_point, loop_line_index}}; - } - - std::optional offset_point; - - bool skip_first{direction == Direction1D::forward}; - const auto visitor{[&](std::size_t index) { - if (skip_first) { - skip_first = false; - return false; - } - const Vec2d previous_point{ - direction == Direction1D::forward ? loop_lines[index].a : loop_lines[index].b}; - const Vec2d next_point{ - direction == Direction1D::forward ? loop_lines[index].b : loop_lines[index].a}; - const Vec2d edge{next_point - previous_point}; - - if (distance + edge.norm() > offset) { - const double remaining_distance{offset - distance}; - offset_point = - PointOnLine{previous_point + remaining_distance * edge.normalized(), index}; - return true; - } - - distance += edge.norm(); - - return false; - }}; - - if (direction == Direction1D::forward) { - Geometry::visit_forward(loop_line_index, loop_lines.size(), visitor); - } else { - Geometry::visit_backward(loop_line_index, loop_lines.size(), visitor); - } - - return offset_point; -} - } // namespace Slic3r::Seams::Geometry diff --git a/src/libslic3r/GCode/SeamGeometry.hpp b/src/libslic3r/GCode/SeamGeometry.hpp index 181046a3eb..61593dddb4 100644 --- a/src/libslic3r/GCode/SeamGeometry.hpp +++ b/src/libslic3r/GCode/SeamGeometry.hpp @@ -210,14 +210,6 @@ struct PointOnLine{ std::size_t line_index; }; -std::optional offset_along_lines( - const Vec2d &point, - const std::size_t loop_line_index, - const Linesf &loop_lines, - const double offset, - const Direction1D direction -); - } // namespace Slic3r::Seams::Geometry #endif // libslic3r_SeamGeometry_hpp_ diff --git a/src/libslic3r/GCode/SeamPerimeters.cpp b/src/libslic3r/GCode/SeamPerimeters.cpp index b4fbd8409c..c90ebab0c5 100644 --- a/src/libslic3r/GCode/SeamPerimeters.cpp +++ b/src/libslic3r/GCode/SeamPerimeters.cpp @@ -576,4 +576,100 @@ LayerPerimeters create_perimeters( return result; } +std::optional offset_along_perimeter( + const PointOnPerimeter &point, + const Perimeter& perimeter, + const double offset, + const Seams::Geometry::Direction1D direction, + const std::function &early_stop_condition +) { + using Dir = Seams::Geometry::Direction1D; + + const Linef initial_line{ + perimeter.positions[point.previous_index], perimeter.positions[point.next_index]}; + double distance{ + direction == Dir::forward ? + (initial_line.b - point.position).norm() : + (point.position - initial_line.a).norm()}; + + if (distance >= offset) { + const Vec2d edge_direction{(initial_line.b - initial_line.a).normalized()}; + const Vec2d offset_point{direction == Dir::forward ? Vec2d{point.position + offset * edge_direction} : Vec2d{point.position - offset * edge_direction}}; + return {{point.previous_index, point.next_index, offset_point}}; + } + + std::optional offset_point; + + bool skip_first{direction == Dir::forward}; + const auto visitor{[&](std::size_t index) { + if (skip_first) { + skip_first = false; + return false; + } + + const std::size_t previous_index{ + direction == Dir::forward ? + (index == 0 ? perimeter.positions.size() - 1 : index - 1) : + (index == perimeter.positions.size() - 1 ? 0 : index + 1)}; + + const Vec2d previous_point{perimeter.positions[previous_index]}; + const Vec2d next_point{perimeter.positions[index]}; + const Vec2d edge{next_point - previous_point}; + + if (early_stop_condition(perimeter, index)) { + offset_point = PointOnPerimeter{previous_index, previous_index, perimeter.positions[previous_index]}; + return true; + } + + if (distance + edge.norm() > offset) { + const double remaining_distance{offset - distance}; + const Vec2d result{previous_point + remaining_distance * edge.normalized()}; + + if (direction == Dir::forward) { + offset_point = PointOnPerimeter{previous_index, index, result}; + } else { + offset_point = PointOnPerimeter{index, previous_index, result}; + } + return true; + } + + distance += edge.norm(); + + return false; + }}; + + if (direction == Dir::forward) { + Geometry::visit_forward(point.next_index, perimeter.positions.size(), visitor); + } else { + Geometry::visit_backward(point.previous_index, perimeter.positions.size(), visitor); + } + + return offset_point; +} + +unsigned get_point_value(const PointType point_type, const PointClassification point_classification) { + // Better be explicit than smart. + switch (point_type) { + case PointType::enforcer: + switch (point_classification) { + case PointClassification::embedded: return 9; + case PointClassification::common: return 8; + case PointClassification::overhang: return 7; + } + case PointType::common: + switch (point_classification) { + case PointClassification::embedded: return 6; + case PointClassification::common: return 5; + case PointClassification::overhang: return 4; + } + case PointType::blocker: + switch (point_classification) { + case PointClassification::embedded: return 3; + case PointClassification::common: return 2; + case PointClassification::overhang: return 1; + } + } + return 0; +} + } // namespace Slic3r::Seams::Perimeter diff --git a/src/libslic3r/GCode/SeamPerimeters.hpp b/src/libslic3r/GCode/SeamPerimeters.hpp index f5751c3261..1dc3083ba2 100644 --- a/src/libslic3r/GCode/SeamPerimeters.hpp +++ b/src/libslic3r/GCode/SeamPerimeters.hpp @@ -33,6 +33,17 @@ enum class PointClassification { overhang, embedded, common }; struct Perimeter; struct PerimeterParams; +/** + * When previous_index == next_index, the point is at the point. + * Otherwise the point is at the edge. + */ +struct PointOnPerimeter +{ + std::size_t previous_index{}; + std::size_t next_index{}; + Vec2d position{Vec2d::Zero()}; +}; + struct LayerInfo { static LayerInfo create( @@ -209,6 +220,19 @@ inline std::vector extract_points( return result; } + +using Seams::Geometry::PointOnLine; + +std::optional offset_along_perimeter( + const PointOnPerimeter &point, + const Perimeter& perimeter, + const double offset, + const Seams::Geometry::Direction1D direction, + const std::function &early_stop_condition +); + +unsigned get_point_value(const PointType point_type, const PointClassification point_classification); + } // namespace Slic3r::Seams::Perimeters #endif // libslic3r_SeamPerimeters_hpp_ diff --git a/src/libslic3r/GCode/SeamPlacer.cpp b/src/libslic3r/GCode/SeamPlacer.cpp index 4a5fccd92a..36d7b119b4 100644 --- a/src/libslic3r/GCode/SeamPlacer.cpp +++ b/src/libslic3r/GCode/SeamPlacer.cpp @@ -251,6 +251,20 @@ Geometry::Direction1D get_direction( return result; } +unsigned get_seam_choice_value(const SeamChoice &seam_choice, const Perimeters::Perimeter& perimeter) { + const unsigned previous_value{Perimeters::get_point_value( + perimeter.point_types[seam_choice.previous_index], + perimeter.point_classifications[seam_choice.previous_index] + )}; + + const unsigned next_value{Perimeters::get_point_value( + perimeter.point_types[seam_choice.next_index], + perimeter.point_classifications[seam_choice.next_index] + )}; + + return std::max(previous_value, next_value); +} + boost::variant finalize_seam_position( const ExtrusionLoop &loop, const PrintRegion *region, @@ -259,6 +273,9 @@ boost::variant finalize_seam_position( const bool staggered_inner_seams, const bool flipped ) { + using Perimeters::offset_along_perimeter; + using Perimeters::PointOnPerimeter; + const Polygon loop_polygon{Geometry::to_polygon(loop)}; const bool do_staggering{staggered_inner_seams && loop.role() == ExtrusionRole::Perimeter}; const double loop_width{loop.paths.empty() ? 0.0 : loop.paths.front().width()}; @@ -273,6 +290,25 @@ boost::variant finalize_seam_position( const Geometry::Direction1D offset_direction{get_direction(flipped, perimeter_polygon, loop)}; + const auto offset_stop_condition{ + [choice_value = get_seam_choice_value(seam_choice, perimeter)]( + const Perimeters::Perimeter &perimeter, const std::size_t index + ) { + const unsigned current_point_value{Perimeters::get_point_value( + perimeter.point_types[index], perimeter.point_classifications[index] + )}; + if ( + current_point_value < choice_value + && ( + perimeter.point_types[index] == Perimeters::PointType::blocker + || perimeter.point_classifications[index] == Perimeters::PointClassification::overhang + ) + ) { + return true; + } + return false; + }}; + // ExtrusionRole::Perimeter is inner perimeter. if (do_staggering) { const double depth = (loop_point - seam_choice.position).norm() - @@ -280,13 +316,16 @@ boost::variant finalize_seam_position( const double staggering_offset{depth}; - std::optional staggered_point{Geometry::offset_along_lines( - loop_point, seam_choice.previous_index, perimeter_lines, staggering_offset, - offset_direction + std::optional staggered_point{offset_along_perimeter( + {seam_choice.previous_index, seam_choice.next_index, loop_point}, + perimeter, + staggering_offset, + offset_direction, + offset_stop_condition )}; if (staggered_point) { - seam_choice = to_seam_choice(*staggered_point, perimeter); + seam_choice = *staggered_point; std::tie(loop_line_index, loop_point) = project_to_extrusion_loop(seam_choice, perimeter, distancer); } } @@ -315,12 +354,12 @@ boost::variant finalize_seam_position( scarf.start_height = std::min(region->config().scarf_seam_start_height.get_abs_value(1.0), 1.0); const double offset{scarf.entire_loop ? 0.0 : region->config().scarf_seam_length.value}; - const std::optional outter_scarf_start_point{Geometry::offset_along_lines( - seam_choice.position, - seam_choice.previous_index, - perimeter_lines, + const std::optional outter_scarf_start_point{offset_along_perimeter( + seam_choice, + perimeter, offset, - offset_direction + offset_direction, + offset_stop_condition )}; if (!outter_scarf_start_point) { return scaled(loop_point); @@ -328,11 +367,11 @@ boost::variant finalize_seam_position( if (loop.role() != ExtrusionRole::Perimeter) { // Outter perimeter const Vec2d start_point_candidate{project_to_extrusion_loop( - to_seam_choice(*outter_scarf_start_point, perimeter), + *outter_scarf_start_point, perimeter, distancer ).second}; - if ((start_point_candidate - outter_scarf_start_point->point).norm() > 5.0) { + if ((start_point_candidate - outter_scarf_start_point->position).norm() > 5.0) { return scaled(loop_point); } scarf.start_point = scaled(start_point_candidate); @@ -340,7 +379,7 @@ boost::variant finalize_seam_position( scarf.end_point_previous_index = loop_line_index; return scarf; } else { - Geometry::PointOnLine inner_scarf_end_point{ + PointOnPerimeter inner_scarf_end_point{ *outter_scarf_start_point }; @@ -350,12 +389,12 @@ boost::variant finalize_seam_position( Geometry::Direction1D::backward : Geometry::Direction1D::forward }; - if (auto result{Geometry::offset_along_lines( - seam_choice.position, - seam_choice.previous_index, - perimeter_lines, + if (auto result{offset_along_perimeter( + seam_choice, + perimeter, offset, - external_first_offset_direction + external_first_offset_direction, + offset_stop_condition )}) { inner_scarf_end_point = *result; } else { @@ -364,36 +403,36 @@ boost::variant finalize_seam_position( } if (!region->config().scarf_seam_on_inner_perimeters) { - return scaled(inner_scarf_end_point.point); + return scaled(inner_scarf_end_point.position); } - const std::optional inner_scarf_start_point{Geometry::offset_along_lines( - inner_scarf_end_point.point, - inner_scarf_end_point.line_index, - perimeter_lines, + const std::optional inner_scarf_start_point{offset_along_perimeter( + inner_scarf_end_point, + perimeter, offset, - offset_direction + offset_direction, + offset_stop_condition )}; if (!inner_scarf_start_point) { - return scaled(inner_scarf_end_point.point); + return scaled(inner_scarf_end_point.position); } const Vec2d start_point_candidate{project_to_extrusion_loop( - to_seam_choice(*inner_scarf_start_point, perimeter), + *inner_scarf_start_point, perimeter, distancer ).second}; - if ((start_point_candidate - inner_scarf_start_point->point).norm() > 5.0) { + if ((start_point_candidate - inner_scarf_start_point->position).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), + inner_scarf_end_point, perimeter, distancer )}; - if ((end_point - inner_scarf_end_point.point).norm() > 5.0) { + if ((end_point - inner_scarf_end_point.position).norm() > 5.0) { return scaled(loop_point); } diff --git a/src/libslic3r/GCode/SeamRandom.hpp b/src/libslic3r/GCode/SeamRandom.hpp index a21268d58d..da265a2c57 100644 --- a/src/libslic3r/GCode/SeamRandom.hpp +++ b/src/libslic3r/GCode/SeamRandom.hpp @@ -3,12 +3,12 @@ #include #include +#include "libslic3r/GCode/SeamGeometry.hpp" #include "libslic3r/GCode/SeamChoice.hpp" #include "libslic3r/GCode/SeamPerimeters.hpp" namespace Slic3r { namespace Seams { -struct SeamChoice; struct SeamPerimeterChoice; } // namespace Seams } // namespace Slic3r diff --git a/tests/fff_print/test_seam_geometry.cpp b/tests/fff_print/test_seam_geometry.cpp index 394bf0a44b..d6c3791aae 100644 --- a/tests/fff_print/test_seam_geometry.cpp +++ b/tests/fff_print/test_seam_geometry.cpp @@ -126,30 +126,3 @@ TEST_CASE("Vertex angle is rotation agnostic", "[Seams][SeamGeometry]") { std::vector rotated_angles = Seams::Geometry::get_vertex_angles(points, 0.1); CHECK(rotated_angles[1] == Approx(angles[1])); } - -const Linesf lines{to_unscaled_linesf({ExPolygon{ - scaled(Vec2d{0.0, 0.0}), - scaled(Vec2d{1.0, 0.0}), - scaled(Vec2d{1.0, 1.0}), - scaled(Vec2d{0.0, 1.0}) -}})}; - -TEST_CASE("Offset along loop lines forward", "[Seams][SeamGeometry]") { - const std::optional result{Seams::Geometry::offset_along_lines( - {0.5, 0.0}, 0, lines, 3.9, Seams::Geometry::Direction1D::forward - )}; - REQUIRE(result); - const auto &[point, line_index] = *result; - CHECK((scaled(point) - Point::new_scale(0.4, 0.0)).norm() < scaled(EPSILON)); - CHECK(line_index == 0); -} - -TEST_CASE("Offset along loop lines backward", "[Seams][SeamGeometry]") { - const std::optional result{Seams::Geometry::offset_along_lines( - {1.0, 0.5}, 1, lines, 1.8, Seams::Geometry::Direction1D::backward - )}; - REQUIRE(result); - const auto &[point, line_index] = *result; - CHECK((scaled(point) - Point::new_scale(0.0, 0.3)).norm() < scaled(EPSILON)); - CHECK(line_index == 3); -} diff --git a/tests/fff_print/test_seam_perimeters.cpp b/tests/fff_print/test_seam_perimeters.cpp index 593399678c..d144af752b 100644 --- a/tests/fff_print/test_seam_perimeters.cpp +++ b/tests/fff_print/test_seam_perimeters.cpp @@ -196,3 +196,57 @@ TEST_CASE_METHOD(Test::SeamsFixture, "Create perimeters", "[Seams][SeamPerimeter serialize_shells(csv, shells); } } + +using Dir = Seams::Geometry::Direction1D; + +Perimeters::Perimeter get_perimeter(){ + Perimeters::Perimeter perimeter; + perimeter.positions = { + Vec2d{0.0, 0.0}, + Vec2d{1.0, 0.0}, + Vec2d{1.0, 1.0}, + Vec2d{0.0, 1.0} + }; + return perimeter; +} + +TEST_CASE("Offset along perimeter forward", "[Seams][SeamPerimeters]") { + const std::optional result{Perimeters::offset_along_perimeter( + {0, 1, {0.5, 0.0}}, get_perimeter(), 3.9, Dir::forward, + [](const Perimeters::Perimeter &, const std::size_t) { return false; } + )}; + REQUIRE(result); + const auto &[previous_index, next_index, point] = *result; + CHECK((scaled(point) - Point::new_scale(0.4, 0.0)).norm() < scaled(EPSILON)); + CHECK(previous_index == 0); + CHECK(next_index == 1); +} + +TEST_CASE("Offset along perimeter backward", "[Seams][SeamPerimeters]") { + const std::optional result{Perimeters::offset_along_perimeter( + {1, 2, {1.0, 0.5}}, get_perimeter(), 1.8, Dir::backward, + [](const Perimeters::Perimeter &, const std::size_t) { return false; } + )}; + REQUIRE(result); + const auto &[previous_index, next_index, point] = *result; + CHECK((scaled(point) - Point::new_scale(0.0, 0.3)).norm() < scaled(EPSILON)); + CHECK(previous_index == 3); + CHECK(next_index == 0); +} + +TEST_CASE("Offset along perimeter forward respects stop condition", "[Seams][SeamPerimeters]") { + Perimeters::Perimeter perimeter{get_perimeter()}; + perimeter.point_types = std::vector(perimeter.positions.size(), Perimeters::PointType::common); + perimeter.point_types[2] = Perimeters::PointType::blocker; + const std::optional result{Perimeters::offset_along_perimeter( + {0, 1, {0.5, 0.0}}, perimeter, 3.9, Dir::forward, + [](const Perimeters::Perimeter &perimeter, const std::size_t index) { + return perimeter.point_types[index] == Perimeters::PointType::blocker; + } + )}; + REQUIRE(result); + const auto &[previous_index, next_index, point] = *result; + CHECK((scaled(point) - Point::new_scale(1.0, 0.0)).norm() < scaled(EPSILON)); + CHECK(previous_index == 1); + CHECK(next_index == 1); +}