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 47a9d602f2..c731e5f0c2 100644 --- a/src/libslic3r/GCode/SeamGeometry.cpp +++ b/src/libslic3r/GCode/SeamGeometry.cpp @@ -138,12 +138,14 @@ Extrusion::Extrusion( Polygon &&polygon, BoundingBox bounding_box, const double width, - const ExPolygon &island_boundary + const ExPolygon &island_boundary, + Overhangs &&overhangs ) - : polygon(polygon) + : polygon(std::move(polygon)) , bounding_box(std::move(bounding_box)) , width(width) - , island_boundary(island_boundary) { + , island_boundary(island_boundary) + , overhangs(std::move(overhangs)) { this->island_boundary_bounding_boxes.push_back(island_boundary.contour.bounding_box()); std::transform( @@ -153,6 +155,66 @@ Extrusion::Extrusion( ); } +Overhangs get_overhangs(const ExtrusionPaths& paths) { + if (paths.empty()) { + return {}; + } + + Overhangs result; + std::optional start_point; + if (paths.front().role().is_bridge()) { + start_point = paths.front().first_point(); + } + Point previous_last_point{paths.front().last_point()}; + for (const ExtrusionPath& path : tcb::span{paths}.subspan(1)) { + if(path.role().is_bridge() && !start_point) { + start_point = path.first_point(); + } + if(!path.role().is_bridge() && start_point) { + result.push_back(Overhang{*start_point, previous_last_point}); + start_point = std::nullopt; + } + previous_last_point = path.last_point(); + } + if (start_point) { + result.push_back(Overhang{*start_point, previous_last_point}); + } + return result; +} + +Overhangs get_overhangs(const ExtrusionEntity *entity) { + Overhangs result; + if (auto path{dynamic_cast(entity)}) { + if(path->role().is_bridge()) { + result.emplace_back(Overhang{ + path->first_point(), + path->last_point() + }); + } + } else if (auto path{dynamic_cast(entity)}) { + const Overhangs overhangs{get_overhangs(path->paths)}; + result.insert(result.end(), overhangs.begin(), overhangs.end()); + } else if (auto path{dynamic_cast(entity)}) { + const bool bridge_loop{std::all_of( + path->paths.begin(), + path->paths.end(), + [](const ExtrusionPath& path){ + return path.role().is_bridge(); + } + )}; + + if (bridge_loop) { + result.emplace_back(LoopOverhang{}); + } else { + const Overhangs overhangs{get_overhangs(path->paths)}; + result.insert(result.end(), overhangs.begin(), overhangs.end()); + } + } else { + throw std::runtime_error{"Unknown extrusion entity!"}; + } + return result; +} + Geometry::Extrusions get_external_perimeters(const Slic3r::Layer &layer, const LayerSlice &slice) { std::vector result; for (const LayerIsland &island : slice.islands) { @@ -163,10 +225,11 @@ Geometry::Extrusions get_external_perimeters(const Slic3r::Layer &layer, const L )}; for (const ExtrusionEntity *entity : *collection) { if (entity->role().is_external_perimeter()) { + Overhangs overhangs{get_overhangs(entity)}; Polygon polygon{entity->as_polyline().points}; const BoundingBox bounding_box{polygon.bounding_box()}; const double width{layer_region.flow(FlowRole::frExternalPerimeter).width()}; - result.emplace_back(std::move(polygon), bounding_box, width, island.boundary); + result.emplace_back(std::move(polygon), bounding_box, width, island.boundary, std::move(overhangs)); } } } @@ -215,19 +278,33 @@ BoundedPolygons project_to_geometry(const Geometry::Extrusions &external_perimet )}; if (distance > max_bb_distance) { - Polygons expanded_extrusion{expand(external_perimeter.polygon, Slic3r::scaled(external_perimeter.width / 2.0))}; + const Polygons expanded_extrusion{expand(external_perimeter.polygon, Slic3r::scaled(external_perimeter.width / 2.0))}; if (!expanded_extrusion.empty()) { return BoundedPolygon{ - expanded_extrusion.front(), expanded_extrusion.front().bounding_box(), external_perimeter.polygon.is_clockwise(), 0.0 + expanded_extrusion.front(), + external_perimeter.overhangs, + expanded_extrusion.front().bounding_box(), + external_perimeter.polygon.is_clockwise(), }; } + return BoundedPolygon{ + external_perimeter.polygon, + external_perimeter.overhangs, + external_perimeter.polygon.bounding_box(), + external_perimeter.polygon.is_clockwise() + }; } const bool is_hole{choosen_index != 0}; const Polygon &adjacent_boundary{ !is_hole ? external_perimeter.island_boundary.contour : external_perimeter.island_boundary.holes[choosen_index - 1]}; - return BoundedPolygon{adjacent_boundary, external_perimeter.island_boundary_bounding_boxes[choosen_index], is_hole, 0.0}; + return BoundedPolygon{ + adjacent_boundary, + external_perimeter.overhangs, + external_perimeter.island_boundary_bounding_boxes[choosen_index], + is_hole, + }; } ); return result; @@ -243,27 +320,6 @@ std::vector project_to_geometry(const std::vector convert_to_geometry(const std::vector &extrusions) { - std::vector result; - result.reserve(extrusions.size()); - - for (const Geometry::Extrusions &layer : extrusions) { - result.emplace_back(); - - using std::transform, std::back_inserter; - transform( - layer.begin(), layer.end(), back_inserter(result.back()), - [&](const Geometry::Extrusion &extrusion) { - return BoundedPolygon{ - extrusion.polygon, extrusion.bounding_box, extrusion.polygon.is_clockwise(), extrusion.width / 2.0 - }; - } - ); - } - - return result; -} - std::vector oversample_edge(const Vec2d &from, const Vec2d &to, const double max_distance) { const double total_distance{(from - to).norm()}; const auto points_count{static_cast(std::ceil(total_distance / max_distance)) + 1}; @@ -337,34 +393,6 @@ Points scaled(const std::vector &points) { return result; } -std::vector get_embedding_distances( - const std::vector &points, const AABBTreeLines::LinesDistancer &perimeter_distancer -) { - std::vector result; - result.reserve(points.size()); - using std::transform, std::back_inserter; - transform(points.begin(), points.end(), back_inserter(result), [&](const Vec2d &point) { - const double distance{perimeter_distancer.distance_from_lines(point)}; - return distance < 0 ? -distance : 0.0; - }); - return result; -} - -std::vector get_overhangs( - const std::vector &points, - const AABBTreeLines::LinesDistancer &previous_layer_perimeter_distancer, - const double layer_height -) { - std::vector result; - result.reserve(points.size()); - using std::transform, std::back_inserter; - transform(points.begin(), points.end(), back_inserter(result), [&](const Vec2d &point) { - const double distance{previous_layer_perimeter_distancer.distance_from_lines(point)}; - return distance > 0 ? M_PI / 2 - std::atan(layer_height / distance) : 0.0; - }); - return result; -} - // Measured from outside, convex is positive std::vector get_vertex_angles(const std::vector &points, const double min_arm_length) { std::vector result; @@ -441,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 9b5c1c47ce..61593dddb4 100644 --- a/src/libslic3r/GCode/SeamGeometry.hpp +++ b/src/libslic3r/GCode/SeamGeometry.hpp @@ -11,6 +11,8 @@ #include #include +#include + #include "libslic3r/ExtrusionEntity.hpp" #include "libslic3r/ExPolygon.hpp" #include "libslic3r/AABBTreeLines.hpp" @@ -30,13 +32,23 @@ template class LinesDistancer; namespace Slic3r::Seams::Geometry { +struct Overhang { + Point start; + Point end; +}; + +struct LoopOverhang {}; + +using Overhangs = std::vector>; + struct Extrusion { Extrusion( Polygon &&polygon, BoundingBox bounding_box, const double width, - const ExPolygon &island_boundary + const ExPolygon &island_boundary, + Overhangs &&overhangs ); Extrusion(const Extrusion &) = delete; @@ -51,6 +63,7 @@ struct Extrusion // At index 0 there is the bounding box of contour. Rest are the bounding boxes of holes in order. BoundingBoxes island_boundary_bounding_boxes; + Overhangs overhangs; }; using Extrusions = std::vector; @@ -59,16 +72,15 @@ std::vector get_extrusions(tcb::span obj struct BoundedPolygon { Polygon polygon; + Overhangs overhangs; BoundingBox bounding_box; bool is_hole{false}; - double offset_inside{}; }; using BoundedPolygons = std::vector; BoundedPolygons project_to_geometry(const Geometry::Extrusions &external_perimeters, const double max_bb_distance); std::vector project_to_geometry(const std::vector &extrusions, const double max_bb_distance); -std::vector convert_to_geometry(const std::vector &extrusions); Vec2d get_polygon_normal( const std::vector &points, const std::size_t index, const double min_arm_length @@ -164,10 +176,6 @@ std::vector unscaled(const Lines &lines); Points scaled(const std::vector &points); -std::vector get_embedding_distances( - const std::vector &points, const AABBTreeLines::LinesDistancer &perimeter_distancer -); - /** * @brief Calculate overhang angle for each of the points over the previous layer perimeters. * @@ -202,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 7971cdf6d8..c90ebab0c5 100644 --- a/src/libslic3r/GCode/SeamPerimeters.cpp +++ b/src/libslic3r/GCode/SeamPerimeters.cpp @@ -5,6 +5,8 @@ #include #include +#include + #include "libslic3r/ClipperUtils.hpp" #include "libslic3r/Layer.hpp" #include "libslic3r/GCode/SeamGeometry.hpp" @@ -15,114 +17,239 @@ #include "tcbspan/span.hpp" namespace Slic3r::Seams::Perimeters::Impl { - -std::vector oversample_painted( - const std::vector &points, +PerimeterPoints oversample_painted( + PerimeterPoints &points, const std::function &is_painted, const double slice_z, const double max_distance ) { - std::vector result; + PerimeterPoints result; for (std::size_t index{0}; index < points.size(); ++index) { - const Vec2d &point{points[index]}; - - result.push_back(point); + result.push_back(points[index]); + const Vec2d &point{points[index].position}; const std::size_t next_index{index == points.size() - 1 ? 0 : index + 1}; - const Vec2d &next_point{points[next_index]}; + const Vec2d &next_point{points[next_index].position}; const float next_point_distance{static_cast((point - next_point).norm())}; const Vec2d middle_point{(point + next_point) / 2.0}; Vec3f point3d{to_3d(middle_point, slice_z).cast()}; if (is_painted(point3d, next_point_distance / 2.0)) { - for (const Vec2d &edge_point : - Geometry::oversample_edge(point, next_point, max_distance)) { - result.push_back(edge_point); + for (const Vec2d &edge_point : Geometry::oversample_edge(point, next_point, max_distance)) { + PerimeterPoint perimeter_point; + perimeter_point.position = edge_point; + if (points[next_index].classification != PointClassification::common) { + perimeter_point.classification = points[next_index].classification; + } + if (points[index].classification != PointClassification::common) { + perimeter_point.classification = points[index].classification; + } + result.push_back(std::move(perimeter_point)); } } } return result; } -std::pair, std::vector> remove_redundant_points( - const std::vector &points, - const std::vector &point_types, +PerimeterPoints remove_redundant_points( + const PerimeterPoints &points, const double tolerance ) { - std::vector points_result; - std::vector point_types_result; + PerimeterPoints result; auto range_start{points.begin()}; for (auto iterator{points.begin()}; iterator != points.end(); ++iterator) { const std::int64_t index{std::distance(points.begin(), iterator)}; - if (next(iterator) == points.end() || point_types[index] != point_types[index + 1]) { - std::vector simplification_result; - douglas_peucker(range_start, next(iterator), std::back_inserter(simplification_result), tolerance); - - points_result.insert( - points_result.end(), simplification_result.begin(), simplification_result.end() - ); - const std::vector - point_types_to_add(simplification_result.size(), point_types[index]); - point_types_result.insert( - point_types_result.end(), point_types_to_add.begin(), point_types_to_add.end() + if ( + next(iterator) == points.end() + || points[index].type != points[index + 1].type + || points[index].classification != points[index + 1].classification + ) { + douglas_peucker( + range_start, next(iterator), std::back_inserter(result), tolerance, + [](const PerimeterPoint &point) { + return point.position; + } ); range_start = next(iterator); } } - return {points_result, point_types_result}; + return result; } -std::vector get_point_types( - const std::vector &positions, +PerimeterPoints get_point_types( + const PerimeterPoints &perimeter_points, const ModelInfo::Painting &painting, const double slice_z, const double painting_radius ) { - std::vector result; - result.reserve(positions.size()); + PerimeterPoints result; + result.reserve(perimeter_points.size()); using std::transform, std::back_inserter; transform( - positions.begin(), positions.end(), back_inserter(result), - [&](const Vec2d &point) { - const Vec3f point3d{to_3d(point.cast(), static_cast(slice_z))}; + perimeter_points.begin(), perimeter_points.end(), back_inserter(result), + [&](PerimeterPoint point) { + const Vec3f point3d{to_3d(point.position.cast(), static_cast(slice_z))}; if (painting.is_blocked(point3d, painting_radius)) { - return PointType::blocker; + point.type = PointType::blocker; + } else if (painting.is_enforced(point3d, painting_radius)) { + point.type = PointType::enforcer; + } else { + point.type = PointType::common; } - if (painting.is_enforced(point3d, painting_radius)) { - return PointType::enforcer; - } - return PointType::common; + return point; } ); return result; } -std::vector classify_points( - const std::vector &embeddings, - const std::optional> &overhangs, - const double overhang_threshold, - const double embedding_threshold +void project_overhang( + PerimeterPoints &points, + const AABBTreeLines::LinesDistancer &points_distancer, + const Geometry::Overhang &overhang, + std::map& output ) { - std::vector result; - result.reserve(embeddings.size()); - using std::transform, std::back_inserter; - transform( - embeddings.begin(), embeddings.end(), back_inserter(result), - [&, i = 0](const double embedding) mutable { - const unsigned index = i++; - if (overhangs && overhangs->operator[](index) > overhang_threshold) { - return PointClassification::overhang; + + const auto [start_distance, start_line_index, start_point]{ + points_distancer.distance_from_lines_extra( + unscaled(overhang.start) + ) + }; + + PerimeterPoint common_start_point{}; + common_start_point.position = start_point; + common_start_point.classification = PointClassification::common; + output[start_line_index].push_back(common_start_point); + + PerimeterPoint perimeter_start_point{}; + perimeter_start_point.position = start_point; + perimeter_start_point.classification = PointClassification::overhang; + output[start_line_index].push_back(perimeter_start_point); + + + const auto [end_distance, end_line_index, end_point]{ + points_distancer.distance_from_lines_extra( + unscaled(overhang.end) + ) + }; + PerimeterPoint perimeter_end_point{}; + perimeter_end_point.position = end_point; + perimeter_end_point.classification = PointClassification::overhang; + output[end_line_index].push_back(perimeter_end_point); + + PerimeterPoint common_end_point{}; + common_end_point.position = end_point; + common_end_point.classification = PointClassification::common; + output[end_line_index].push_back(common_end_point); +} + +double get_overhang_angle( + const Vec2d& point, + const AABBTreeLines::LinesDistancer &previous_layer_perimeter_distancer, + const double layer_height +) { + const double distance{previous_layer_perimeter_distancer.distance_from_lines(point)}; + return distance > 0 ? M_PI / 2 - std::atan(layer_height / distance) : 0.0; +} + +Linesf to_lines(const PerimeterPoints &points) { + Linesf lines; + for (std::size_t i{0}; i < points.size(); ++i) { + const std::size_t current_index{i}; + const std::size_t next_index{i == points.size() - 1 ? 0 : i + 1}; + const Vec2d a{points[current_index].position}; + const Vec2d b{points[next_index].position}; + lines.push_back(Linef{a, b}); + } + return lines; +} + +PerimeterPoints classify_overhangs( + PerimeterPoints &&points, + const Geometry::Overhangs &overhangs, + const LayerInfo &layer_info, + const double overhang_threshold +) { + using boost::apply_visitor; + using boost::hana::overload_linearly; + + PerimeterPoints classified_points{std::move(points)}; + + if (!layer_info.previous_distancer) { + return classified_points; + } + + const AABBTreeLines::LinesDistancer points_distancer{to_lines(classified_points)}; + + std::map points_to_add_to_lines; + for (const auto &overhang : overhangs) { + apply_visitor(overload_linearly( + [&](const Geometry::Overhang& overhang) { + project_overhang( + classified_points, + points_distancer, + overhang, + points_to_add_to_lines + ); + }, + [&](const Geometry::LoopOverhang&) { + for (PerimeterPoint &point : classified_points) { + point.classification = PointClassification::overhang; + } } - if (embedding > embedding_threshold) { - return PointClassification::embedded; - } - return PointClassification::common; + ), overhang); + } + + PerimeterPoints result; + + for (std::size_t i{0}; i < classified_points.size(); ++i) { + PerimeterPoint &point{classified_points[i]}; + if (point.classification != PointClassification::overhang) { + const double overhang_aangle{ + get_overhang_angle(point.position, *layer_info.previous_distancer, layer_info.height)}; + point.classification = overhang_aangle > overhang_threshold ? + PointClassification::overhang : + point.classification; } - ); + result.push_back(point); + if (points_to_add_to_lines.count(i) > 0) { + for (const PerimeterPoint &point : points_to_add_to_lines[i]) { + result.push_back(point); + } + } + } + + return result; +} + +PerimeterPoints classify_points( + PerimeterPoints &&points, + const Geometry::Overhangs &overhangs, + const double embedding_threshold, + const LayerInfo& layer_info, + const double overhang_threshold +) { + PerimeterPoints result{classify_overhangs(std::move(points), overhangs, layer_info, overhang_threshold)}; + + for (PerimeterPoint& point : result) { + if (point.classification != PointClassification::common) { + continue; + } + // This is an optimization avoiding distance_from_lines which is expensive. + const double embedding_distance{layer_info.distancer.distance_from_lines(point.position)}; + if (embedding_distance < embedding_threshold) { + continue; + } + if (layer_info.distancer.outside(point.position) == 1) { + continue; + } + + point.classification = PointClassification::embedded; + } + return result; } @@ -187,6 +314,21 @@ std::vector merge_angle_types( return result; } +PerimeterPoints get_perimeter_points(const std::vector &points){ + PerimeterPoints perimeter_points; + std::transform( + points.begin(), + points.end(), + std::back_inserter(perimeter_points), + [](const Vec2d &point){ + PerimeterPoint perimeter_point; + perimeter_point.position = point; + return perimeter_point; + } + ); + return perimeter_points; +} + } // namespace Slic3r::Seams::Perimeters::Impl namespace Slic3r::Seams::Perimeters { @@ -322,10 +464,10 @@ Perimeter Perimeter::create_degenerate( Perimeter Perimeter::create( const Polygon &polygon, + const Geometry::Overhangs &overhangs, const ModelInfo::Painting &painting, const LayerInfo &layer_info, - const PerimeterParams ¶ms, - const double offset_inside + const PerimeterParams ¶ms ) { if (polygon.size() < 3) { return Perimeter::create_degenerate( @@ -344,44 +486,48 @@ Perimeter Perimeter::create( points = Geometry::unscaled(polygon.points); } - auto is_painted{[&](const Vec3f &point, const double radius) { + PerimeterPoints perimeter_points{Impl::get_perimeter_points(points)}; + + perimeter_points = Impl::classify_points( + std::move(perimeter_points), + overhangs, + params.embedding_threshold, + layer_info, + params.overhang_threshold + ); + + const auto is_painted{[&](const Vec3f &point, const double radius) { return painting.is_enforced(point, radius) || painting.is_blocked(point, radius); }}; - std::vector perimeter_points{ - Impl::oversample_painted(points, is_painted, layer_info.slice_z, params.oversampling_max_distance)}; + perimeter_points = Impl::oversample_painted( + perimeter_points, + is_painted, + layer_info.slice_z, + params.oversampling_max_distance + ); - std::vector point_types{ - Impl::get_point_types(perimeter_points, painting, layer_info.slice_z, offset_inside > 0 ? offset_inside * 2 : params.painting_radius)}; + perimeter_points = Impl::get_point_types(perimeter_points, painting, layer_info.slice_z, params.painting_radius); - // Geometry converted from extrusions has non zero offset_inside. - // Do not remomve redundant points for extrusions, becouse the redundant - // points can be on overhangs. - if (offset_inside < std::numeric_limits::epsilon()) { - // The following is optimization with significant impact. If in doubt, run - // the "Seam benchmarks" test case in fff_print_tests. - std::tie(perimeter_points, point_types) = - Impl::remove_redundant_points(perimeter_points, point_types, params.simplification_epsilon); + perimeter_points = Impl::remove_redundant_points(perimeter_points, params.simplification_epsilon); + + std::vector positions{}; + std::vector point_types{}; + std::vector point_classifications{}; + + for (const PerimeterPoint &point : perimeter_points) { + positions.push_back(point.position); + point_types.push_back(point.type); + point_classifications.push_back(point.classification); } - const std::vector embeddings{ - Geometry::get_embedding_distances(perimeter_points, layer_info.distancer)}; - std::optional> overhangs; - if (layer_info.previous_distancer) { - overhangs = Geometry::get_overhangs( - perimeter_points, *layer_info.previous_distancer, layer_info.height - ); - } - std::vector point_classifications{ - Impl::classify_points(embeddings, overhangs, params.overhang_threshold, params.embedding_threshold)}; - - std::vector smooth_angles{Geometry::get_vertex_angles(perimeter_points, params.smooth_angle_arm_length)}; - std::vector angles{Geometry::get_vertex_angles(perimeter_points, params.sharp_angle_arm_length)}; + std::vector smooth_angles{Geometry::get_vertex_angles(positions, params.smooth_angle_arm_length)}; + std::vector angles{Geometry::get_vertex_angles(positions, params.sharp_angle_arm_length)}; std::vector angle_types{ Impl::get_angle_types(angles, params.convex_threshold, params.concave_threshold)}; std::vector smooth_angle_types{ Impl::get_angle_types(smooth_angles, params.convex_threshold, params.concave_threshold)}; - angle_types = Impl::merge_angle_types(angle_types, smooth_angle_types, perimeter_points, params.smooth_angle_arm_length); + angle_types = Impl::merge_angle_types(angle_types, smooth_angle_types, positions, params.smooth_angle_arm_length); const bool is_hole{polygon.is_clockwise()}; @@ -389,7 +535,7 @@ Perimeter Perimeter::create( layer_info.slice_z, layer_info.index, is_hole, - std::move(perimeter_points), + std::move(positions), std::move(angles), std::move(point_types), std::move(point_classifications), @@ -417,11 +563,113 @@ LayerPerimeters create_perimeters( const Geometry::BoundedPolygon &bounded_polygon{layer[polygon_index]}; const LayerInfo &layer_info{layer_infos[layer_index]}; result[layer_index][polygon_index] = BoundedPerimeter{ - Perimeter::create(bounded_polygon.polygon, painting, layer_info, params, bounded_polygon.offset_inside), + Perimeter::create( + bounded_polygon.polygon, + bounded_polygon.overhangs, + painting, + layer_info, + params + ), bounded_polygon.bounding_box}; } ); 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 54660f6ddb..1dc3083ba2 100644 --- a/src/libslic3r/GCode/SeamPerimeters.hpp +++ b/src/libslic3r/GCode/SeamPerimeters.hpp @@ -27,12 +27,23 @@ class Painting; } namespace Slic3r::Seams::Perimeters { -enum class AngleType; -enum class PointType; -enum class PointClassification; +enum class AngleType { convex, concave, smooth }; +enum class PointType { enforcer, blocker, common }; +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( @@ -55,6 +66,18 @@ using LayerInfos = std::vector; LayerInfos get_layer_infos( tcb::span object_layers, const double elephant_foot_compensation ); + +struct PerimeterPoint { + Vec2d position{Vec2d::Zero()}; + double angle{}; + PointType type{PointType::common}; + PointClassification classification{PointClassification::common}; + AngleType angle_type{AngleType::smooth}; +}; + +using PerimeterPoints = std::vector; + + } // namespace Slic3r::Seams::Perimeters namespace Slic3r::Seams::Perimeters::Impl { @@ -69,8 +92,8 @@ namespace Slic3r::Seams::Perimeters::Impl { * * @return All the points (original and added) in order along the edges. */ -std::vector oversample_painted( - const std::vector &points, +PerimeterPoints oversample_painted( + PerimeterPoints &points, const std::function &is_painted, const double slice_z, const double max_distance @@ -83,9 +106,8 @@ std::vector oversample_painted( * * @param tolerance Douglas-Peucker epsilon. */ -std::pair, std::vector> remove_redundant_points( - const std::vector &points, - const std::vector &point_types, +PerimeterPoints remove_redundant_points( + const PerimeterPoints &points, const double tolerance ); @@ -93,12 +115,6 @@ std::pair, std::vector> remove_redundant_points( namespace Slic3r::Seams::Perimeters { -enum class AngleType { convex, concave, smooth }; - -enum class PointType { enforcer, blocker, common }; - -enum class PointClassification { overhang, embedded, common }; - struct PerimeterParams { double elephant_foot_compensation{}; @@ -149,10 +165,10 @@ struct Perimeter static Perimeter create( const Polygon &polygon, + const Geometry::Overhangs &overhangs, const ModelInfo::Painting &painting, const LayerInfo &layer_info, - const PerimeterParams ¶ms, - const double offset_inside + const PerimeterParams ¶ms ); static Perimeter create_degenerate( @@ -204,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 24d0b86ff5..36d7b119b4 100644 --- a/src/libslic3r/GCode/SeamPlacer.cpp +++ b/src/libslic3r/GCode/SeamPlacer.cpp @@ -40,8 +40,6 @@ ObjectLayerPerimeters get_perimeters( print_object->layers(), params.perimeter.elephant_foot_compensation )}; const std::vector projected{ - print_object->config().seam_position == spRandom ? - Geometry::convert_to_geometry(extrusions) : Geometry::project_to_geometry(extrusions, params.max_distance) }; Perimeters::LayerPerimeters perimeters{Perimeters::create_perimeters(projected, layer_infos, painting, params.perimeter)}; @@ -137,7 +135,7 @@ Params Placer::get_params(const DynamicPrintConfig &config) { params.max_distance = 5.0; params.perimeter.oversampling_max_distance = 0.2; params.perimeter.embedding_threshold = 0.5; - params.perimeter.painting_radius = 0.1; + params.perimeter.painting_radius = 0.05; params.perimeter.simplification_epsilon = 0.001; params.perimeter.smooth_angle_arm_length = 0.5; params.perimeter.sharp_angle_arm_length = 0.25; @@ -253,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, @@ -261,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()}; @@ -275,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() - @@ -282,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); } } @@ -317,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); @@ -330,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); @@ -342,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 }; @@ -352,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 { @@ -366,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.cpp b/src/libslic3r/GCode/SeamRandom.cpp index 5681e57157..16c14851b7 100644 --- a/src/libslic3r/GCode/SeamRandom.cpp +++ b/src/libslic3r/GCode/SeamRandom.cpp @@ -26,7 +26,9 @@ std::vector get_segments( Vec2d previous_position{positions.front()}; double distance{0.0}; std::vector result; - for (std::size_t index{0}; index < positions.size(); ++index) { + for (std::size_t i{0}; i <= positions.size(); ++i) { + const std::size_t index{i == positions.size() ? 0 : i}; + const double previous_distance{distance}; distance += (positions[index] - previous_position).norm(); previous_position = positions[index]; @@ -38,7 +40,7 @@ std::vector get_segments( } } else { if (current_begin) { - result.push_back(PerimeterSegment{*current_begin, distance, *current_begin_index}); + result.push_back(PerimeterSegment{*current_begin, previous_distance, *current_begin_index}); } current_begin = std::nullopt; current_begin_index = std::nullopt; @@ -86,19 +88,21 @@ SeamChoice pick_random_point( double distance{0.0}; std::size_t previous_index{segment.begin_index}; - for (std::size_t index{segment.begin_index + 1}; index < perimeter.positions.size(); ++index) { + for (std::size_t i{segment.begin_index + 1}; i <= perimeter.positions.size(); ++i) { + const std::size_t index{i == perimeter.positions.size() ? 0 : i}; const Vec2d edge{positions[index] - positions[previous_index]}; if (distance + edge.norm() >= random_distance) { + std::size_t current_index{index}; if (random_distance - distance < std::numeric_limits::epsilon()) { - index = previous_index; + current_index = previous_index; } else if (distance + edge.norm() - random_distance < std::numeric_limits::epsilon()) { previous_index = index; } const double remaining_distance{random_distance - distance}; const Vec2d position{positions[previous_index] + remaining_distance * edge.normalized()}; - return {previous_index, index, position}; + return {previous_index, current_index, position}; } distance += edge.norm(); 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 688cbc7d4a..d6c3791aae 100644 --- a/tests/fff_print/test_seam_geometry.cpp +++ b/tests/fff_print/test_seam_geometry.cpp @@ -126,50 +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])); } - -TEST_CASE("Calculate overhangs", "[Seams][SeamGeometry]") { - const ExPolygon square{ - scaled(Vec2d{0.0, 0.0}), - scaled(Vec2d{1.0, 0.0}), - scaled(Vec2d{1.0, 1.0}), - scaled(Vec2d{0.0, 1.0}) - }; - const std::vector points{Seams::Geometry::unscaled(square.contour.points)}; - ExPolygon previous_layer{square}; - previous_layer.translate(scaled(Vec2d{-0.5, 0})); - AABBTreeLines::LinesDistancer previous_layer_distancer{ - to_unscaled_linesf({previous_layer})}; - const std::vector overhangs{ - Seams::Geometry::get_overhangs(points, previous_layer_distancer, 0.5)}; - REQUIRE(overhangs.size() == points.size()); - CHECK_THAT(overhangs, Catch::Matchers::Approx(std::vector{ - 0.0, M_PI / 4.0, M_PI / 4.0, 0.0 - })); -} - -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 76bef83205..d144af752b 100644 --- a/tests/fff_print/test_seam_perimeters.cpp +++ b/tests/fff_print/test_seam_perimeters.cpp @@ -16,23 +16,26 @@ using namespace Catch; constexpr bool debug_files{false}; -const ExPolygon square{ - 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("Oversample painted", "[Seams][SeamPerimeters]") { + Perimeters::PerimeterPoints square(4); + square[0].position = Vec2d{0.0, 0.0}; + square[1].position = Vec2d{1.0, 0.0}; + square[2].position = Vec2d{1.0, 1.0}; + square[3].position = Vec2d{0.0, 1.0}; + auto is_painted{[](const Vec3f &position, float radius) { return (position - Vec3f{0.5, 0.0, 1.0}).norm() < radius; }}; - std::vector points{Perimeters::Impl::oversample_painted( - Seams::Geometry::unscaled(square.contour.points), is_painted, 1.0, 0.2 + Perimeters::PerimeterPoints points{Perimeters::Impl::oversample_painted( + square, is_painted, 1.0, 0.2 )}; REQUIRE(points.size() == 8); - CHECK((points[1] - Vec2d{0.2, 0.0}).norm() == Approx(0.0)); + CHECK((points[1].position - Vec2d{0.2, 0.0}).norm() == Approx(0.0)); points = Perimeters::Impl::oversample_painted( - Seams::Geometry::unscaled(square.contour.points), is_painted, 1.0, 0.199 + square, is_painted, 1.0, 0.199 ); CHECK(points.size() == 9); } @@ -41,24 +44,35 @@ TEST_CASE("Remove redundant points", "[Seams][SeamPerimeters]") { using Perimeters::PointType; using Perimeters::PointClassification; - std::vector points{{0.0, 0.0}, {1.0, 0.0}, {2.0, 0.0}, {3.0, 0.0}, - {3.0, 1.0}, {3.0, 2.0}, {0.0, 2.0}}; - std::vector point_types{PointType::common, - PointType::enforcer, // Should keep this. - PointType::enforcer, // Should keep this. - PointType::blocker, - PointType::blocker, // Should remove this. - PointType::blocker, PointType::common}; + Perimeters::PerimeterPoints points(9); + points[0].position = {0.0, 0.0}; + points[0].type = PointType::common; + points[1].position = {1.0, 0.0}; + points[1].type = PointType::enforcer; // Should keep + points[2].position = {2.0, 0.0}; + points[2].type = PointType::enforcer; // Should keep + points[3].position = {3.0, 0.0}; + points[3].type = PointType::blocker; + points[4].position = {3.0, 1.0}; + points[4].type = PointType::blocker; // Should remove + points[5].position = {3.0, 1.1}; + points[5].type = PointType::blocker; + points[6].position = {3.0, 1.2}; + points[6].type = PointType::blocker; + points[6].classification = PointClassification::overhang; // Should keep + points[7].position = {3.0, 2.0}; + points[7].type = PointType::blocker; + points[8].position = {0.0, 2.0}; + points[8].type = PointType::common; - const auto [resulting_points, resulting_point_types]{ - Perimeters::Impl::remove_redundant_points(points, point_types, 0.1)}; + Perimeters::PerimeterPoints result{ + Perimeters::Impl::remove_redundant_points(points, 0.1)}; - REQUIRE(resulting_points.size() == 6); - REQUIRE(resulting_point_types.size() == 6); - CHECK((resulting_points[3] - Vec2d{3.0, 0.0}).norm() == Approx(0.0)); - CHECK((resulting_points[4] - Vec2d{3.0, 2.0}).norm() == Approx(0.0)); - CHECK(resulting_point_types[3] == PointType::blocker); - CHECK(resulting_point_types[4] == PointType::blocker); + REQUIRE(result.size() == 8); + CHECK((result[3].position - Vec2d{3.0, 0.0}).norm() == Approx(0.0)); + CHECK((result[4].position - Vec2d{3.0, 1.1}).norm() == Approx(0.0)); + CHECK(result[3].type == PointType::blocker); + CHECK(result[4].type == PointType::blocker); } TEST_CASE("Perimeter constructs KD trees", "[Seams][SeamPerimeters]") { @@ -182,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); +} diff --git a/tests/fff_print/test_seam_random.cpp b/tests/fff_print/test_seam_random.cpp index 65f7c91d7b..59341ec137 100644 --- a/tests/fff_print/test_seam_random.cpp +++ b/tests/fff_print/test_seam_random.cpp @@ -32,7 +32,7 @@ Perimeters::Perimeter get_perimeter() { } } // namespace RandomTest -double get_chi2_uniform(const std::vector &data, double min, double max, const std::size_t bin_count) { +double get_chi2_uniform(const std::vector &data, const double min, const double max, const std::size_t bin_count) { std::vector bins(bin_count); const double bin_size{(max - min) / bin_count}; const double expected_frequncy{static_cast(data.size()) / bin_count}; @@ -62,7 +62,7 @@ TEST_CASE("Random is uniform", "[Seams][SeamRandom]") { return choice->position.x(); }); const std::size_t degrees_of_freedom{10}; - const double critical{18.307}; // dof 10, significance 0.05 + const double critical{29.588}; // dof 10, significance 0.001 CHECK(get_chi2_uniform(x_positions, 0.0, 1.0, degrees_of_freedom + 1) < critical); } diff --git a/tests/fff_print/test_seam_shells.cpp b/tests/fff_print/test_seam_shells.cpp index 56d57a9d3a..d18a59e94f 100644 --- a/tests/fff_print/test_seam_shells.cpp +++ b/tests/fff_print/test_seam_shells.cpp @@ -23,7 +23,12 @@ struct ProjectionFixture double extrusion_width{0.2}; ProjectionFixture() { - extrusions.emplace_back(Polygon{extrusion_path}, extrusion_path.bounding_box(), extrusion_width, island_boundary); + extrusions.emplace_back( + Polygon{extrusion_path}, + extrusion_path.bounding_box(), + extrusion_width, island_boundary, + Seams::Geometry::Overhangs{} + ); } };