From c044b310ee5a7b9c037a6e69462901d31020a4f6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luk=C3=A1=C5=A1=20Hejl?= Date: Fri, 31 Jan 2025 02:47:46 +0100 Subject: [PATCH 1/4] SPE-2194: Snap places where there is a change in projected colors to the nearest sharp angles in multi-material segmentation. --- src/libslic3r/MultiMaterialSegmentation.cpp | 143 +++++++++++++++++++- 1 file changed, 141 insertions(+), 2 deletions(-) diff --git a/src/libslic3r/MultiMaterialSegmentation.cpp b/src/libslic3r/MultiMaterialSegmentation.cpp index cb9c2da30b..aa26cfebae 100644 --- a/src/libslic3r/MultiMaterialSegmentation.cpp +++ b/src/libslic3r/MultiMaterialSegmentation.cpp @@ -61,6 +61,8 @@ const constexpr double POLYGON_COLOR_FILTER_TOLERANCE_SCALED = scaled(0.001); const constexpr double MM_SEGMENTATION_MAX_PROJECTION_DISTANCE_SCALED = scaled(0.4); const constexpr double MM_SEGMENTATION_MAX_SNAP_DISTANCE_SCALED = scaled(0.01); +const constexpr double MM_SEGMENTATION_SNAP_ANGLE_THRESHOLD = PI / 12.; +const constexpr double MM_SEGMENTATION_SNAP_ANGLE_MAX_DISTANCE_SCALED = scaled(0.1); enum VD_ANNOTATION : Voronoi::VD::cell_type::color_type { VERTEX_ON_CONTOUR = 1, @@ -1429,6 +1431,142 @@ static void filter_projected_color_points_on_polygons(std::vector().normalized(); + const Vec2d cb_vec = (next_pt.p - curr_pt.p).cast().normalized(); + return PI - std::acos(std::clamp(ab_vec.dot(cb_vec), -1.0, 1.0)); +} + +struct SnapAngle +{ + enum class Direction { Forward, Backward }; + + size_t pt_index; + Direction direction; +}; + +static std::optional find_nearest_snap_angle(const ColorPoints &color_polygon_points, const size_t begin_idx, const double max_distance, const double angle_threshold) +{ + assert(begin_idx < color_polygon_points.size() && color_polygon_points.size() >= 3); + + const auto calc_three_points_angle = [&color_polygon_points](const size_t prev_idx, const size_t curr_idx, const size_t next_idx) -> double { + return calc_three_color_points_angle(color_polygon_points[prev_idx], color_polygon_points[curr_idx], color_polygon_points[next_idx]); + }; + + const auto next_index = [&color_polygon_points](const size_t curr_idx) -> size_t { + return curr_idx < (color_polygon_points.size() - 1) ? curr_idx + 1 : 0; + }; + + const auto prev_index = [&color_polygon_points](const size_t curr_idx) -> size_t { + return curr_idx > 0 ? curr_idx - 1 : color_polygon_points.size() - 1; + }; + + const auto distance_sqr = [&color_polygon_points](const size_t first_idx, const size_t second_idx) -> double { + return (color_polygon_points[second_idx].p - color_polygon_points[first_idx].p).cast().squaredNorm(); + }; + + // Try to find a snap angle in the forward direction. + const double max_distance_sqr = Slic3r::sqr(max_distance); + size_t prev_idx = prev_index(begin_idx); + size_t curr_idx = begin_idx; + size_t next_idx = next_index(begin_idx); + double total_distance_sqr = 0.; + do { + total_distance_sqr += distance_sqr(curr_idx, next_idx); + if (total_distance_sqr > max_distance_sqr) { + break; + } + + prev_idx = curr_idx; + curr_idx = next_idx; + next_idx = next_index(next_idx); + + if (color_polygon_points[prev_idx].color_next != color_polygon_points[curr_idx].color_next) { + break; + } + + if (const double angle = calc_three_points_angle(prev_idx, curr_idx, next_idx); angle > angle_threshold) { + return SnapAngle{curr_idx, SnapAngle::Direction::Forward}; + } + } while (curr_idx != begin_idx); + + // Try to find a snap angle in the backward direction. + prev_idx = prev_index(begin_idx); + curr_idx = begin_idx; + next_idx = next_index(begin_idx); + total_distance_sqr = 0.; + do { + total_distance_sqr += distance_sqr(prev_idx, curr_idx); + if (total_distance_sqr > max_distance_sqr) { + break; + } + + next_idx = curr_idx; + curr_idx = prev_idx; + prev_idx = prev_index(prev_idx); + + if (color_polygon_points[prev_idx].color_next != color_polygon_points[curr_idx].color_next) { + break; + } + + if (const double angle = calc_three_points_angle(prev_idx, curr_idx, next_idx); angle > angle_threshold) { + return SnapAngle{curr_idx, SnapAngle::Direction::Backward}; + } + } while (curr_idx != begin_idx); + + return std::nullopt; +} + +static void snap_projected_color_points_to_nearest_angles(std::vector &color_polygons_points) +{ + for (ColorPoints &color_polygon_points : color_polygons_points) { + assert(color_polygon_points.size() >= 3); + + const auto next_index = [&color_polygon_points](const size_t curr_idx) -> size_t { + return curr_idx < (color_polygon_points.size() - 1) ? curr_idx + 1 : 0; + }; + + const auto prev_index = [&color_polygon_points](const size_t curr_idx) -> size_t { + return curr_idx > 0 ? curr_idx - 1 : color_polygon_points.size() - 1; + }; + + for (size_t curr_idx = 0; curr_idx < color_polygon_points.size(); ++curr_idx) { + const ColorPoint &prev_pt = color_polygon_points[prev_index(curr_idx)]; + const ColorPoint &curr_pt = color_polygon_points[curr_idx]; + const ColorPoint &next_pt = color_polygon_points[next_index(curr_idx)]; + + // We care only about places in which color changes with angles below the threshold. + if (prev_pt.color_next == curr_pt.color_next || calc_three_color_points_angle(prev_pt, curr_pt, next_pt) > MM_SEGMENTATION_SNAP_ANGLE_THRESHOLD) { + continue; + } + + const std::optional snap_angle = find_nearest_snap_angle(color_polygon_points, curr_idx, MM_SEGMENTATION_SNAP_ANGLE_MAX_DISTANCE_SCALED, MM_SEGMENTATION_SNAP_ANGLE_THRESHOLD); + if (!snap_angle.has_value()) { + continue; + } + + if (snap_angle->direction == SnapAngle::Direction::Forward) { + color_polygon_points[snap_angle->pt_index].color_next = curr_pt.color_next; + + for (size_t modify_idx = curr_idx; modify_idx != snap_angle->pt_index; modify_idx = next_index(modify_idx)) { + color_polygon_points[modify_idx].color_next = prev_pt.color_next; + } + } else { + assert(snap_angle->direction == SnapAngle::Direction::Backward); + color_polygon_points[prev_index(snap_angle->pt_index)].color_next = prev_pt.color_next; + + for (size_t modify_idx = snap_angle->pt_index; modify_idx != curr_idx; modify_idx = next_index(modify_idx)) { + color_polygon_points[modify_idx].color_next = curr_pt.color_next; + } + } + } + } +} + static std::vector convert_color_polygons_projection_lines_to_color_points(const std::vector &color_polygons_projection_lines) { std::vector color_polygons_points; color_polygons_points.reserve(color_polygons_projection_lines.size()); @@ -1813,13 +1951,14 @@ std::vector> segmentation_by_painting(const PrintObject update_color_changes_using_color_projection_ranges(input_polygons_projection_lines); filter_projected_color_points_on_polygons(input_polygons_projection_lines); - const std::vector color_polygons_points = convert_color_polygons_projection_lines_to_color_points(input_polygons_projection_lines); - const std::vector colored_polygons = color_points_to_colored_lines(color_polygons_points); + std::vector color_polygons_points = convert_color_polygons_projection_lines_to_color_points(input_polygons_projection_lines); + snap_projected_color_points_to_nearest_angles(color_polygons_points); if constexpr (MM_SEGMENTATION_DEBUG_COLORIZED_POLYGONS) { export_color_polygons_points_to_svg(debug_out_path("mm-projected-color_polygon-%d.svg", layer_idx), color_polygons_points, input_expolygons[layer_idx]); } + const std::vector colored_polygons = color_points_to_colored_lines(color_polygons_points); assert(!colored_polygons.empty()); if (has_layer_only_one_color(colored_polygons)) { // When the whole layer is painted using the same color, it is not needed to construct a Voronoi diagram for the segmentation of this layer. From 36f64d9dd74dfa07bb4db23affe3e8e0f9a56fac Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luk=C3=A1=C5=A1=20Hejl?= Date: Fri, 31 Jan 2025 02:48:01 +0100 Subject: [PATCH 2/4] SPE-2194: Improve filtering of projected colors in multi-material segmentation. --- src/libslic3r/MultiMaterialSegmentation.cpp | 85 +++++++++++++++++---- 1 file changed, 72 insertions(+), 13 deletions(-) diff --git a/src/libslic3r/MultiMaterialSegmentation.cpp b/src/libslic3r/MultiMaterialSegmentation.cpp index aa26cfebae..6f6b407ad9 100644 --- a/src/libslic3r/MultiMaterialSegmentation.cpp +++ b/src/libslic3r/MultiMaterialSegmentation.cpp @@ -1351,6 +1351,68 @@ static uint8_t get_color_of_first_polygon_line(const ColorProjectionLines &color } } +// Helper function to find the dominant color in a map. +static std::optional get_dominant_color_from_map(const std::unordered_map &area_per_color) +{ + if (area_per_color.empty()) + return std::nullopt; + + return std::max_element(area_per_color.begin(), area_per_color.end(), + [](const std::pair &p1, const std::pair &p2) { + return p1.second < p2.second; + })->first; +} + +// Function to find the dominant color over the beginning of the ColorProjectionLine. +static std::optional find_dominant_color_from_begin(const ColorProjectionLine &color_line) +{ + assert(!color_line.color_changes.empty()); + const ColorChanges &color_changes = color_line.color_changes; + const double line_length = (color_line.b - color_line.a).cast().norm(); + const double max_snap_distance = std::min(line_length, 10. * MM_SEGMENTATION_MAX_SNAP_DISTANCE_SCALED); + bool max_snap_distance_exceeded = false; + std::unordered_map area_per_color; + for (auto curr_it = std::next(color_changes.cbegin()); curr_it != color_changes.cend(); ++curr_it) { + auto prev_it = std::prev(curr_it); + if (const double endpoint_dist = curr_it->t * line_length; endpoint_dist < max_snap_distance) { + area_per_color[prev_it->color_next] += (curr_it->t - prev_it->t) * line_length; + } else { + area_per_color[prev_it->color_next] += max_snap_distance - prev_it->t * line_length; + max_snap_distance_exceeded = true; + break; + } + } + + if (!max_snap_distance_exceeded) { + const ColorChange &last_color_change = color_changes.back(); + area_per_color[last_color_change.color_next] += max_snap_distance - last_color_change.t * line_length; + } + + return get_dominant_color_from_map(area_per_color); +} + +// Function to find the dominant color over the end of the ColorProjectionLine. +std::optional find_dominant_color_from_end(const ColorProjectionLine &color_line) +{ + const ColorChanges &color_changes = color_line.color_changes; + const double line_length = (color_line.b - color_line.a).cast().norm(); + const double max_snap_distance = std::min(line_length, 10. * MM_SEGMENTATION_MAX_SNAP_DISTANCE_SCALED); + double prev_t = 1.; + std::unordered_map area_per_color; + for (auto curr_it = color_changes.crbegin(); curr_it != color_changes.crend(); ++curr_it) { + if (const double endpoint_dist = (1. - curr_it->t) * line_length; endpoint_dist < max_snap_distance) { + area_per_color[curr_it->color_next] += (prev_t - curr_it->t) * line_length; + } else { + area_per_color[curr_it->color_next] += max_snap_distance - (1. - prev_t) * line_length; + break; + } + + prev_t = curr_it->t; + } + + return get_dominant_color_from_map(area_per_color); +} + static void filter_projected_color_points_on_polygons(std::vector &color_polygons_projection_lines) { for (ColorProjectionLines &color_polygon_projection_lines : color_polygons_projection_lines) { for (ColorProjectionLine &color_line : color_polygon_projection_lines) { @@ -1373,13 +1435,11 @@ static void filter_projected_color_points_on_polygons(std::vectort = 0.; - } else if (snap_candidates.size() > 1) { + } else if (const std::optional dominant_color_from_begin = find_dominant_color_from_begin(color_line); snap_candidates.size() > 1 && dominant_color_from_begin.has_value()) { ColorChange &first_candidate = *snap_candidates.front(); - ColorChange &last_candidate = *snap_candidates.back(); - - first_candidate.t = 0.; - for (auto cr_it = std::next(snap_candidates.begin()); cr_it != snap_candidates.end(); ++cr_it) { - (*cr_it)->color_next = last_candidate.color_next; + first_candidate.t = 0.; + for (ColorChange *snap_candidate : snap_candidates) { + snap_candidate->color_next = *dominant_color_from_begin; } } @@ -1395,14 +1455,13 @@ static void filter_projected_color_points_on_polygons(std::vector 1) { - snap_candidates.pop_back(); - color_line.color_changes.pop_back(); - } + if (const std::optional dominant_color_from_end = find_dominant_color_from_end(color_line); !snap_candidates.empty() && dominant_color_from_end.has_value()) { + for (ColorChange *snap_candidate : snap_candidates) { + snap_candidate->color_next = *dominant_color_from_end; + } - if (!snap_candidates.empty()) { - assert(snap_candidates.size() == 1); - snap_candidates.back()->t = 1.; + // Be aware that snap_candidates are sorted in the opposite order to color_line.color_changes. + color_line.color_changes.back().t = 1.; } // Remove color ranges that just repeating the same color. From 86fadfc08eda42e6927748b37d67a8337efcede2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luk=C3=A1=C5=A1=20Hejl?= Date: Fri, 31 Jan 2025 02:48:13 +0100 Subject: [PATCH 3/4] SPE-2194: Split Voronoi cells generated by vertexes into two polygons of equal area instead of selecting just one of the edges based on heuristics. --- src/libslic3r/Geometry/VoronoiUtils.hpp | 2 + src/libslic3r/MultiMaterialSegmentation.cpp | 303 +++++++++++++------- 2 files changed, 199 insertions(+), 106 deletions(-) diff --git a/src/libslic3r/Geometry/VoronoiUtils.hpp b/src/libslic3r/Geometry/VoronoiUtils.hpp index 6872e4ea72..304e4dcd2c 100644 --- a/src/libslic3r/Geometry/VoronoiUtils.hpp +++ b/src/libslic3r/Geometry/VoronoiUtils.hpp @@ -40,6 +40,8 @@ template struct PointCellRange PointCellRange() = delete; explicit PointCellRange(const PT &source_point) : source_point(source_point) {} + PointCellRange(const PT &source_point, const VD::edge_type *edge_begin, const VD::edge_type *edge_end) + : source_point(source_point), edge_begin(edge_begin), edge_end(edge_end) {} bool is_valid() const { return edge_begin && edge_end && edge_begin != edge_end; } }; diff --git a/src/libslic3r/MultiMaterialSegmentation.cpp b/src/libslic3r/MultiMaterialSegmentation.cpp index 6f6b407ad9..35ecf99843 100644 --- a/src/libslic3r/MultiMaterialSegmentation.cpp +++ b/src/libslic3r/MultiMaterialSegmentation.cpp @@ -415,6 +415,7 @@ static void delete_vertex_deep(const VD::vertex_type &vertex) { while (!vertices_to_delete.empty()) { const VD::vertex_type &vertex_to_delete = *vertices_to_delete.front(); + assert(vertex_to_delete.color() != VD_ANNOTATION::VERTEX_ON_CONTOUR); vertices_to_delete.pop(); vertex_to_delete.color(VD_ANNOTATION::DELETED); @@ -429,99 +430,165 @@ static void delete_vertex_deep(const VD::vertex_type &vertex) { } } -static inline Vec2d mk_point_vec2d(const VD::vertex_type *point) { - assert(point != nullptr); - return {point->x(), point->y()}; -} - -static inline Vec2d mk_vector_vec2d(const VD::edge_type *edge) { - assert(edge != nullptr); - return mk_point_vec2d(edge->vertex1()) - mk_point_vec2d(edge->vertex0()); -} - -static inline Vec2d mk_flipped_vector_vec2d(const VD::edge_type *edge) { - assert(edge != nullptr); - return mk_point_vec2d(edge->vertex0()) - mk_point_vec2d(edge->vertex1()); -} - -static double edge_length(const VD::edge_type &edge) { - assert(edge.is_finite()); - return mk_vector_vec2d(&edge).norm(); -} - -// Used in remove_multiple_edges_in_vertices() -// Returns length of edge with is connected to contour. To this length is include other edges with follows it if they are almost straight (with the -// tolerance of 15) And also if node between two subsequent edges is connected only to these two edges. -static inline double calc_total_edge_length(const VD::edge_type &starting_edge) +// Removes all edges except one in a Voronoi vertex that has more than one Voronoi edge (for example, +// in concave parts of a polygon). The one edge that is kept is selected arbitrarily. +static void remove_excess_voronoi_edges(const VD::vertex_type &vertex) { - double total_edge_length = edge_length(starting_edge); - const VD::edge_type *prev = &starting_edge; - do { - if (prev->is_finite() && non_deleted_edge_count(*prev->vertex1()) > 2) - break; - - bool found_next_edge = false; - const VD::edge_type *current = prev->next(); - do { - if (current->color() == VD_ANNOTATION::DELETED) - continue; - - Vec2d first_line_vec_n = mk_flipped_vector_vec2d(prev).normalized(); - Vec2d second_line_vec_n = mk_vector_vec2d(current).normalized(); - double angle = ::acos(std::clamp(first_line_vec_n.dot(second_line_vec_n), -1.0, 1.0)); - if (Slic3r::cross2(first_line_vec_n, second_line_vec_n) < 0.0) - angle = 2.0 * (double) PI - angle; - - if (std::abs(angle - PI) >= (PI / 12)) - continue; - - prev = current; - found_next_edge = true; - total_edge_length += edge_length(*current); - - break; - } while (current = current->prev()->twin(), current != prev->next()); - - if (!found_next_edge) - break; - - } while (prev != &starting_edge); - - return total_edge_length; -} - -// When a Voronoi vertex has more than one Voronoi edge (for example, in concave parts of a polygon), -// we leave just one Voronoi edge in the Voronoi vertex. -// This Voronoi edge is selected based on a heuristic. -static void remove_multiple_edges_in_vertex(const VD::vertex_type &vertex) { - if (non_deleted_edge_count(vertex) <= 1) + if (non_deleted_edge_count(vertex) <= 1) { return; + } - std::vector> edges_to_check; - const VD::edge_type *edge = vertex.incident_edge(); + std::vector vertex_edges; + const VD::edge_type *edge = vertex.incident_edge(); do { - if (edge->color() == VD_ANNOTATION::DELETED) + if (edge->color() == VD_ANNOTATION::DELETED) { continue; + } - edges_to_check.emplace_back(edge, calc_total_edge_length(*edge)); + vertex_edges.emplace_back(edge); } while (edge = edge->prev()->twin(), edge != vertex.incident_edge()); - std::sort(edges_to_check.begin(), edges_to_check.end(), [](const auto &l, const auto &r) -> bool { - return l.second > r.second; - }); - - while (edges_to_check.size() > 1) { - const VD::edge_type &edge_to_check = *edges_to_check.back().first; + while (vertex_edges.size() > 1) { + const VD::edge_type &edge_to_check = *vertex_edges.back(); edge_to_check.color(VD_ANNOTATION::DELETED); edge_to_check.twin()->color(VD_ANNOTATION::DELETED); - if (const VD::vertex_type &vertex_to_delete = *edge_to_check.vertex1(); can_vertex_be_deleted(vertex_to_delete)) + if (const VD::vertex_type &vertex_to_delete = *edge_to_check.vertex1(); can_vertex_be_deleted(vertex_to_delete)) { delete_vertex_deep(vertex_to_delete); + } - edges_to_check.pop_back(); + vertex_edges.pop_back(); } } +// Returns first non-deleted edge in clockwise. +static const VD::edge_type *get_cw_first_non_deleted_edge(const VD::edge_type *edge_begin) +{ + const VD::edge_type *edge = edge_begin; + do { + if (edge->color() == VD_ANNOTATION::DELETED) { + continue; + } else { + assert(edge->color() != VD_ANNOTATION::VERTEX_ON_CONTOUR); + return edge; + } + } while (edge = edge->prev()->twin(), edge != edge_begin); + + return nullptr; +} + +// Return convex Polygon for Voronoi cell (Voronoi cell is always convex). +// During processing a Voronoi cell, it marks its edges as deleted. +template +static typename std::enable_if< + std::is_same>::value || std::is_same>::value, + std::optional>::type +cell_range_to_polygon(const CellRange &cell_range) +{ + assert(cell_range.is_valid()); + assert(cell_range.edge_begin->vertex0()->color() == VD_ANNOTATION::VERTEX_ON_CONTOUR); + + if (!cell_range.is_valid() || cell_range.edge_begin->vertex0()->color() != VD_ANNOTATION::VERTEX_ON_CONTOUR) { + return std::nullopt; + } + + Polygon cell_polygon; + cell_polygon.points.emplace_back(Geometry::VoronoiUtils::to_point(cell_range.edge_begin->vertex0()).template cast()); + + // We have ensured that each cell_polygon have to start at edge_begin->vertex0() and end at edge_end->vertex1(). + const VD::edge_type *edge = cell_range.edge_begin; + do { + if (edge->color() == VD_ANNOTATION::DELETED) { + continue; + } + + const VD::vertex_type &next_vertex = *edge->vertex1(); + cell_polygon.points.emplace_back(Geometry::VoronoiUtils::to_point(next_vertex).cast()); + edge->color(VD_ANNOTATION::DELETED); + + if (next_vertex.color() == VD_ANNOTATION::VERTEX_ON_CONTOUR || next_vertex.color() == VD_ANNOTATION::DELETED) { + break; + } + + edge = edge->twin(); + } while (edge = edge->twin()->next(), edge != cell_range.edge_begin); + + if (edge->vertex1() != cell_range.edge_end->vertex1()) { + return std::nullopt; + } + + if (cell_range.edge_begin->vertex0() == cell_range.edge_end->vertex1()) { + cell_polygon.points.pop_back(); + } + + return cell_polygon; +} + +static std::optional segment_cell_range_to_polygon(const Geometry::SegmentCellRange &cell_range) +{ + return cell_range_to_polygon(cell_range); +} + +static std::optional point_cell_range_to_polygon(const Geometry::PointCellRange &cell_range) +{ + return cell_range_to_polygon(cell_range); +} + +inline double calc_triangle_area(const Point &a_pt, const Point &b_pt, const Point &c_pt) +{ + Vec2d ba = (b_pt - a_pt).cast(); + Vec2d ca = (c_pt - a_pt).cast(); + return Slic3r::cross2(ba, ca) / 2.; +} + +static std::pair split_convex_polygon_to_two_polygons_with_equal_area(const Polygon &input_polygon) +{ + assert(input_polygon.size() >= 3); + const double half_area = input_polygon.area() / 2.; + + assert(half_area > 0.); + if (half_area <= 0.) { + return {}; + } + + Polygon left_polygon; + left_polygon.points.emplace_back(input_polygon.points[0]); + + Polygon right_polygon; + right_polygon.points.emplace_back(input_polygon.points[0]); + right_polygon.points.emplace_back(input_polygon.points[1]); + + double right_polygon_area = 0.; + size_t curr_idx = 2; + for (; curr_idx < input_polygon.size(); ++curr_idx) { + const Point &start_pt = input_polygon.points[0]; + const Point &prev_pt = input_polygon.points[curr_idx - 1]; + const Point &curr_pt = input_polygon.points[curr_idx]; + const double curr_triangle_area = calc_triangle_area(start_pt, prev_pt, curr_pt); + + if ((right_polygon_area + curr_triangle_area) <= half_area) { + right_polygon_area += curr_triangle_area; + right_polygon.points.emplace_back(curr_pt); + } else { + // Split current triangle. + const double remaining_area = half_area - right_polygon_area; + const double split_ratio = remaining_area / curr_triangle_area; + const Point intersection_pt = prev_pt + (split_ratio * (curr_pt - prev_pt).cast()).cast(); + + right_polygon.points.emplace_back(intersection_pt); + left_polygon.points.emplace_back(intersection_pt); + break; + } + } + + for (; curr_idx < input_polygon.size(); ++curr_idx) { + left_polygon.points.emplace_back(input_polygon.points[curr_idx]); + } + + assert(left_polygon.size() >= 3 && right_polygon.size() >= 3); + return {left_polygon, right_polygon}; +} + // Returns list of ExPolygons for each extruder + 1 for default unpainted regions. // It iterates through all nodes on the border between two different colors, and from this point, // start selection always left most edges for every node to construct CCW polygons. @@ -575,7 +642,7 @@ static std::vector extract_colored_segments(const std::vector extract_colored_segments(const std::vector cell_range = Geometry::VoronoiUtils::compute_segment_cell_range(cell, colored_lines.begin(), colored_lines.end()); + cell_range.is_valid()) { + const VD::vertex_type &vertex = *cell_range.edge_begin->vertex0(); + assert(vertex.color() == VD_ANNOTATION::VERTEX_ON_CONTOUR); + remove_excess_voronoi_edges(vertex); + } } if constexpr (MM_SEGMENTATION_DEBUG_GRAPH) { @@ -617,38 +699,47 @@ static std::vector extract_colored_segments(const std::vectorvertex0()->color() != VD_ANNOTATION::VERTEX_ON_CONTOUR) continue; - const ColoredLine source_segment = Geometry::VoronoiUtils::get_source_segment(cell, colored_lines.begin(), colored_lines.end()); - - Polygon segmented_polygon; - segmented_polygon.points.emplace_back(source_segment.line.b); - - // We have ensured that each segmented_polygon have to start at edge_begin->vertex0() and end at edge_end->vertex1(). - const VD::edge_type *edge = cell_range.edge_begin; - do { - if (edge->color() == VD_ANNOTATION::DELETED) - continue; - - const VD::vertex_type &next_vertex = *edge->vertex1(); - segmented_polygon.points.emplace_back(Geometry::VoronoiUtils::to_point(next_vertex).cast()); - edge->color(VD_ANNOTATION::DELETED); - - if (next_vertex.color() == VD_ANNOTATION::VERTEX_ON_CONTOUR || next_vertex.color() == VD_ANNOTATION::DELETED) - break; - - edge = edge->twin(); - } while (edge = edge->twin()->next(), edge != cell_range.edge_begin); - - if (edge->vertex1() != cell_range.edge_end->vertex1()) + const ColoredLine &curr_line = Geometry::VoronoiUtils::get_source_segment(cell, colored_lines.begin(), colored_lines.end()); + const std::optional segment_cell_polygon = segment_cell_range_to_polygon(cell_range); + if (!segment_cell_polygon.has_value()) continue; + const Voronoi::VD::vertex_type &cell_begin_vertex = *cell_range.edge_begin->vertex0(); + // The cell_range.edge_begin was deleted, so if any edge is starting in the cell_range.edge_begin->vertex0(), + // then it is an edge from a cell that was generated by a point, and the cell is between two different colors. + assert(non_deleted_edge_count(cell_begin_vertex) <= 1); + if (non_deleted_edge_count(cell_begin_vertex) == 1) { + const VD::edge_type *point_cell_begin_edge = get_cw_first_non_deleted_edge(cell_range.edge_begin); + const VD::edge_type *point_cell_end_edge = cell_range.edge_begin->twin(); + + assert(point_cell_begin_edge != nullptr && point_cell_end_edge != nullptr); + if (point_cell_begin_edge == nullptr || point_cell_end_edge == nullptr) + continue; + + assert(point_cell_begin_edge->color() != VD_ANNOTATION::DELETED && point_cell_end_edge->color() != VD_ANNOTATION::DELETED); + assert(point_cell_begin_edge->vertex0() == point_cell_end_edge->vertex1()); + + const Geometry::PointCellRange point_cell_range(Geometry::VoronoiUtils::to_point(point_cell_begin_edge->vertex0()).cast(), point_cell_begin_edge, point_cell_end_edge); + const std::optional point_cell_polygon = point_cell_range_to_polygon(point_cell_range); + if (!point_cell_polygon.has_value()) + continue; + + const ColoredLine &next_line = get_next_contour_line(curr_line); + std::pair point_cell_polygons = split_convex_polygon_to_two_polygons_with_equal_area(*point_cell_polygon); + segmented_expolygons_per_extruder[curr_line.color].emplace_back(std::move(point_cell_polygons.first)); + segmented_expolygons_per_extruder[next_line.color].emplace_back(std::move(point_cell_polygons.second)); + } + cell_range.edge_begin->vertex0()->color(VD_ANNOTATION::DELETED); - segmented_expolygons_per_extruder[source_segment.color].emplace_back(std::move(segmented_polygon)); + segmented_expolygons_per_extruder[curr_line.color].emplace_back(*segment_cell_polygon); } } // Merge all polygons together for each extruder - for (auto &segmented_expolygons : segmented_expolygons_per_extruder) + for (auto &segmented_expolygons : segmented_expolygons_per_extruder) { segmented_expolygons = union_ex(segmented_expolygons); + segmented_expolygons = Slic3r::closing_ex(segmented_expolygons, static_cast(SCALED_EPSILON)); + } return segmented_expolygons_per_extruder; } From 7ff23a5844b845c255fa6bf962109190015c2f40 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luk=C3=A1=C5=A1=20Hejl?= Date: Fri, 31 Jan 2025 02:48:21 +0100 Subject: [PATCH 4/4] SPE-2669: Fix multi-material segmentation artifacts by applying multi-material segmentation to ExPolygon instead of the whole layer. Those artifacts were caused by issues in the Boost Voronoi diagram generator. Processing ExPolygon by ExPolygon significantly reduces the probability that those issues show. --- src/libslic3r/MultiMaterialSegmentation.cpp | 533 ++++++++++++-------- 1 file changed, 322 insertions(+), 211 deletions(-) diff --git a/src/libslic3r/MultiMaterialSegmentation.cpp b/src/libslic3r/MultiMaterialSegmentation.cpp index 35ecf99843..42d72a4de3 100644 --- a/src/libslic3r/MultiMaterialSegmentation.cpp +++ b/src/libslic3r/MultiMaterialSegmentation.cpp @@ -143,6 +143,34 @@ struct ColorProjectionLine using ColorProjectionLines = std::vector; +struct ColorProjectionExPolygon +{ + ColorProjectionLines contour; + std::vector holes; + + size_t polygons_count() const { return holes.size() + 1; } + + size_t lines_count() const + { + size_t lines_cnt = this->contour.size(); + for (const ColorProjectionLines &hole : this->holes) { + lines_cnt += hole.size(); + } + + return lines_cnt; + } +}; + +using ColorProjectionExPolygons = std::vector; + +size_t lines_count(const ColorProjectionExPolygons &color_projection_expolygons) +{ + return std::accumulate(color_projection_expolygons.begin(), color_projection_expolygons.end(), size_t(0), + [](const size_t acc, const ColorProjectionExPolygon &color_projection_expolygon) { + return acc + color_projection_expolygon.lines_count(); + }); +} + struct ColorProjectionLineWrapper { static const constexpr int Dim = 2; @@ -281,7 +309,7 @@ using ColorPoints = std::vector; } } -[[maybe_unused]] static void export_color_projection_lines_color_ranges_to_svg(const std::string &path, const std::vector &color_polygons_projection_lines, const ExPolygons &lslices) { +[[maybe_unused]] static void export_color_projection_lines_color_ranges_to_svg(const std::string &path, const ColorProjectionExPolygon &color_projection_expolygon, const ExPolygons &lslices) { const std::vector colors = {"blue", "cyan", "red", "orange", "pink", "yellow", "magenta", "purple", "black"}; const std::string default_color = "black"; const coordf_t stroke_width = scaled(0.05); @@ -289,8 +317,8 @@ using ColorPoints = std::vector; ::Slic3r::SVG svg(path.c_str(), bbox); - for (const ColorProjectionLines &color_polygon_projection_lines : color_polygons_projection_lines) { - for (const ColorProjectionLine &color_projection_line : color_polygon_projection_lines) { + const auto draw_color_projection_line = [&](const ColorProjectionLines &color_projection_lines) -> void { + for (const ColorProjectionLine &color_projection_line : color_projection_lines) { svg.draw(Line(color_projection_line.a, color_projection_line.b), default_color, stroke_width); for (const ColorProjectionRange &range : color_projection_line.color_projection_ranges) { @@ -301,6 +329,11 @@ using ColorPoints = std::vector; svg.draw(Line(from_pt, to_pt), (range.color < colors.size() ? colors[range.color] : default_color), stroke_width); } } + }; + + draw_color_projection_line(color_projection_expolygon.contour); + for (const ColorProjectionLines &hole_color_projection_lines : color_projection_expolygon.holes) { + draw_color_projection_line(hole_color_projection_lines); } } @@ -1115,8 +1148,9 @@ static std::vector> merge_segmented_layers(const std::ve return segmented_regions_merged; } -// Check if all ColoredLine representing a single layer uses the same color. -static bool has_layer_only_one_color(const std::vector &colored_polygons) { +// Check if all ColoredLines representing several Polygons (ExPolygon) uses the same color. +static bool has_polygons_only_one_color(const std::vector &colored_polygons) +{ assert(!colored_polygons.empty()); assert(!colored_polygons.front().empty()); int first_line_color = colored_polygons.front().front().color; @@ -1371,49 +1405,61 @@ void static filter_color_of_small_segments(ColorPoints &color_polygon_points, co return true; } -static std::vector create_color_projection_lines(const ExPolygon &ex_polygon) { - std::vector color_projection_lines(ex_polygon.num_contours()); +static ColorProjectionLines create_color_projection_lines(const Polygon &polygon) +{ + ColorProjectionLines color_projection_lines; + color_projection_lines.reserve(polygon.size()); - for (size_t contour_idx = 0; contour_idx < ex_polygon.num_contours(); ++contour_idx) { - const Lines lines = ex_polygon.contour_or_hole(contour_idx).lines(); - color_projection_lines[contour_idx].reserve(lines.size()); - - for (const Line &line : lines) { - color_projection_lines[contour_idx].emplace_back(line); - } + for (const Line &line : polygon.lines()) { + color_projection_lines.emplace_back(line); } return color_projection_lines; } -static std::vector create_color_projection_lines(const ExPolygons &ex_polygons) { - std::vector color_projection_lines; - color_projection_lines.reserve(number_polygons(ex_polygons)); +static ColorProjectionExPolygon create_color_projection_expolygon(const ExPolygon &expolygon) +{ + ColorProjectionExPolygon color_projection_expolygon; + color_projection_expolygon.contour = create_color_projection_lines(expolygon.contour); - for (const ExPolygon &ex_polygon : ex_polygons) { - Slic3r::append(color_projection_lines, create_color_projection_lines(ex_polygon)); + color_projection_expolygon.holes.reserve(expolygon.holes.size()); + for (const Polygon &hole_polygon : expolygon.holes) { + color_projection_expolygon.holes.emplace_back(create_color_projection_lines(hole_polygon)); } - return color_projection_lines; + return color_projection_expolygon; +} + +static ColorProjectionExPolygons create_color_projection_expolygons(const ExPolygons &expolygons) +{ + ColorProjectionExPolygons color_projection_expolygons; + color_projection_expolygons.reserve(expolygons.size()); + + for (const ExPolygon &expolygon : expolygons) { + color_projection_expolygons.emplace_back(create_color_projection_expolygon(expolygon)); + } + + return color_projection_expolygons; } // Create the flat vector of ColorProjectionLineWrapper where each ColorProjectionLineWrapper -// is pointing into the one ColorProjectionLine in the vector of ColorProjectionLines. -static std::vector create_color_projection_lines_mapping(std::vector &color_polygons_projection_lines) { - auto total_lines_count = [&color_polygons_projection_lines]() { - return std::accumulate(color_polygons_projection_lines.begin(), color_polygons_projection_lines.end(), size_t(0), - [](const size_t acc, const ColorProjectionLines &color_projection_lines) { - return acc + color_projection_lines.size(); - }); - }; - +// is pointing into the one ColorProjectionLine in the vector of ColorProjectionExPolygons. +static std::vector create_color_projection_lines_mapping(ColorProjectionExPolygons &color_projection_expolygons) +{ std::vector color_projection_lines_mapping; - color_projection_lines_mapping.reserve(total_lines_count()); + color_projection_lines_mapping.reserve(lines_count(color_projection_expolygons)); - for (ColorProjectionLines &color_polygon_projection_lines : color_polygons_projection_lines) { + const auto append_color_projection_lines = [&color_projection_lines_mapping](ColorProjectionLines &color_polygon_projection_lines) { for (ColorProjectionLine &color_projection_line : color_polygon_projection_lines) { color_projection_lines_mapping.emplace_back(&color_projection_line); } + }; + + for (ColorProjectionExPolygon &color_projection_expolygon : color_projection_expolygons) { + append_color_projection_lines(color_projection_expolygon.contour); + for (ColorProjectionLines &hole_color_projection_lines : color_projection_expolygon.holes) { + append_color_projection_lines(hole_color_projection_lines); + } } return color_projection_lines_mapping; @@ -1504,80 +1550,87 @@ std::optional find_dominant_color_from_end(const ColorProjectionLine &c return get_dominant_color_from_map(area_per_color); } -static void filter_projected_color_points_on_polygons(std::vector &color_polygons_projection_lines) { - for (ColorProjectionLines &color_polygon_projection_lines : color_polygons_projection_lines) { - for (ColorProjectionLine &color_line : color_polygon_projection_lines) { - if (color_line.color_changes.empty()) - continue; +static void filter_projected_color_points_on_polygon(ColorProjectionLines &color_polygon_projection_lines) +{ + for (ColorProjectionLine &color_line : color_polygon_projection_lines) { + if (color_line.color_changes.empty()) + continue; - std::sort(color_line.color_changes.begin(), color_line.color_changes.end()); + std::sort(color_line.color_changes.begin(), color_line.color_changes.end()); - // Snap projected points to the first endpoint of the line. - const double line_length = (color_line.b - color_line.a).cast().norm(); + // Snap projected points to the first endpoint of the line. + const double line_length = (color_line.b - color_line.a).cast().norm(); - std::vector snap_candidates; - for (ColorChange &color_change : color_line.color_changes) { - if (const double endpoint_dist = color_change.t * line_length; endpoint_dist < MM_SEGMENTATION_MAX_SNAP_DISTANCE_SCALED) { - snap_candidates.emplace_back(&color_change); - } else { - break; - } - } - - if (snap_candidates.size() == 1) { - snap_candidates.front()->t = 0.; - } else if (const std::optional dominant_color_from_begin = find_dominant_color_from_begin(color_line); snap_candidates.size() > 1 && dominant_color_from_begin.has_value()) { - ColorChange &first_candidate = *snap_candidates.front(); - first_candidate.t = 0.; - for (ColorChange *snap_candidate : snap_candidates) { - snap_candidate->color_next = *dominant_color_from_begin; - } - } - - snap_candidates.clear(); - - // Snap projected points to the second endpoint of the line. - for (auto cr_it = color_line.color_changes.rbegin(); cr_it != color_line.color_changes.rend(); ++cr_it) { - ColorChange &color_change = *cr_it; - if (const double endpoint_dist = (1. - color_change.t) * line_length; endpoint_dist < MM_SEGMENTATION_MAX_SNAP_DISTANCE_SCALED) { - snap_candidates.emplace_back(&color_change); - } else { - break; - } - } - - if (const std::optional dominant_color_from_end = find_dominant_color_from_end(color_line); !snap_candidates.empty() && dominant_color_from_end.has_value()) { - for (ColorChange *snap_candidate : snap_candidates) { - snap_candidate->color_next = *dominant_color_from_end; - } - - // Be aware that snap_candidates are sorted in the opposite order to color_line.color_changes. - color_line.color_changes.back().t = 1.; - } - - // Remove color ranges that just repeating the same color. - // We don't care about color_prev, because both color_prev and color_next may not be connected. - // Also, we will not use color_prev during the final stage of producing ColorPolygons. - if (color_line.color_changes.size() > 1) { - ColorChanges color_changes_filtered; - color_changes_filtered.reserve(color_line.color_changes.size()); - - color_changes_filtered.emplace_back(color_line.color_changes.front()); - for (auto cr_it = std::next(color_line.color_changes.begin()); cr_it != color_line.color_changes.end(); ++cr_it) { - ColorChange &color_change = *cr_it; - - if (color_changes_filtered.back().color_next == color_change.color_next) { - continue; - } else if (const double t_diff = (color_change.t - color_changes_filtered.back().t); t_diff * line_length < MM_SEGMENTATION_MAX_SNAP_DISTANCE_SCALED) { - color_changes_filtered.back().color_next = color_change.color_next; - } else { - color_changes_filtered.emplace_back(color_change); - } - } - - color_line.color_changes = std::move(color_changes_filtered); + std::vector snap_candidates; + for (ColorChange &color_change : color_line.color_changes) { + if (const double endpoint_dist = color_change.t * line_length; endpoint_dist < MM_SEGMENTATION_MAX_SNAP_DISTANCE_SCALED) { + snap_candidates.emplace_back(&color_change); + } else { + break; } } + + if (snap_candidates.size() == 1) { + snap_candidates.front()->t = 0.; + } else if (const std::optional dominant_color_from_begin = find_dominant_color_from_begin(color_line); snap_candidates.size() > 1 && dominant_color_from_begin.has_value()) { + ColorChange &first_candidate = *snap_candidates.front(); + first_candidate.t = 0.; + for (ColorChange *snap_candidate : snap_candidates) { + snap_candidate->color_next = *dominant_color_from_begin; + } + } + + snap_candidates.clear(); + + // Snap projected points to the second endpoint of the line. + for (auto cr_it = color_line.color_changes.rbegin(); cr_it != color_line.color_changes.rend(); ++cr_it) { + ColorChange &color_change = *cr_it; + if (const double endpoint_dist = (1. - color_change.t) * line_length; endpoint_dist < MM_SEGMENTATION_MAX_SNAP_DISTANCE_SCALED) { + snap_candidates.emplace_back(&color_change); + } else { + break; + } + } + + if (const std::optional dominant_color_from_end = find_dominant_color_from_end(color_line); !snap_candidates.empty() && dominant_color_from_end.has_value()) { + for (ColorChange *snap_candidate : snap_candidates) { + snap_candidate->color_next = *dominant_color_from_end; + } + + // Be aware that snap_candidates are sorted in the opposite order to color_line.color_changes. + color_line.color_changes.back().t = 1.; + } + + // Remove color ranges that just repeating the same color. + // We don't care about color_prev, because both color_prev and color_next may not be connected. + // Also, we will not use color_prev during the final stage of producing ColorPolygons. + if (color_line.color_changes.size() > 1) { + ColorChanges color_changes_filtered; + color_changes_filtered.reserve(color_line.color_changes.size()); + + color_changes_filtered.emplace_back(color_line.color_changes.front()); + for (auto cr_it = std::next(color_line.color_changes.begin()); cr_it != color_line.color_changes.end(); ++cr_it) { + ColorChange &color_change = *cr_it; + + if (color_changes_filtered.back().color_next == color_change.color_next) { + continue; + } else if (const double t_diff = (color_change.t - color_changes_filtered.back().t); t_diff * line_length < MM_SEGMENTATION_MAX_SNAP_DISTANCE_SCALED) { + color_changes_filtered.back().color_next = color_change.color_next; + } else { + color_changes_filtered.emplace_back(color_change); + } + } + + color_line.color_changes = std::move(color_changes_filtered); + } + } +} + +static void filter_projected_color_points_on_expolygon(ColorProjectionExPolygon &color_projection_expolygon) +{ + filter_projected_color_points_on_polygon(color_projection_expolygon.contour); + for (ColorProjectionLines &hole_color_projection_lines : color_projection_expolygon.holes) { + filter_projected_color_points_on_polygon(hole_color_projection_lines); } } @@ -1717,60 +1770,75 @@ static void snap_projected_color_points_to_nearest_angles(std::vector convert_color_polygons_projection_lines_to_color_points(const std::vector &color_polygons_projection_lines) { - std::vector color_polygons_points; - color_polygons_points.reserve(color_polygons_projection_lines.size()); +static ColorPoints convert_color_polygon_projection_lines_to_color_points(const ColorProjectionLines &color_polygon_projection_lines) +{ + if (color_polygon_projection_lines.empty()) + return {}; - for (const ColorProjectionLines &color_polygon_projection_lines : color_polygons_projection_lines) { - if (color_polygon_projection_lines.empty()) - continue; + ColorPoints color_polygon_points; + color_polygon_points.reserve(color_polygon_projection_lines.size()); - ColorPoints color_polygon_points; - color_polygon_points.reserve(color_polygon_projection_lines.size()); - - uint8_t prev_color = get_color_of_first_polygon_line(color_polygon_projection_lines); - uint8_t curr_color = prev_color; - for (const ColorProjectionLine &color_line : color_polygon_projection_lines) { - if (color_line.color_changes.empty()) { + uint8_t prev_color = get_color_of_first_polygon_line(color_polygon_projection_lines); + uint8_t curr_color = prev_color; + for (const ColorProjectionLine &color_line : color_polygon_projection_lines) { + if (color_line.color_changes.empty()) { + color_polygon_points.emplace_back(color_line.a, prev_color, curr_color); + prev_color = curr_color; + } else { + if (const ColorChange &first_color_change = color_line.color_changes.front(); first_color_change.t != 0.) { color_polygon_points.emplace_back(color_line.a, prev_color, curr_color); prev_color = curr_color; - } else { - if (const ColorChange &first_color_change = color_line.color_changes.front(); first_color_change.t != 0.) { - color_polygon_points.emplace_back(color_line.a, prev_color, curr_color); + } + + for (const ColorChange &color_change : color_line.color_changes) { + if (color_change.t != 1.) { + const Vec2d color_line_vec = (color_line.b - color_line.a).cast(); + const Point color_line_new_pt = (color_change.t * color_line_vec).cast() + color_line.a; + + color_polygon_points.emplace_back(color_line_new_pt, prev_color, color_change.color_next); + curr_color = color_change.color_next; prev_color = curr_color; } + } - for (const ColorChange &color_change : color_line.color_changes) { - if (color_change.t != 1.) { - const Vec2d color_line_vec = (color_line.b - color_line.a).cast(); - const Point color_line_new_pt = (color_change.t * color_line_vec).cast() + color_line.a; - - color_polygon_points.emplace_back(color_line_new_pt, prev_color, color_change.color_next); - curr_color = color_change.color_next; - prev_color = curr_color; - } - } - - if (const ColorChange &last_color_change = color_line.color_changes.back(); last_color_change.t == 1.) { - curr_color = last_color_change.color_next; - } + if (const ColorChange &last_color_change = color_line.color_changes.back(); last_color_change.t == 1.) { + curr_color = last_color_change.color_next; } } - - ColorPoints color_polygon_points_filtered; - color_polygon_points_filtered.reserve(color_polygon_points.size()); - - douglas_peucker(color_polygon_points.begin(), color_polygon_points.end(), std::back_inserter(color_polygon_points_filtered), INPUT_POLYGONS_FILTER_TOLERANCE_SCALED, POLYGON_COLOR_FILTER_DISTANCE_SCALED); - - if (color_polygon_points_filtered.size() < 3) - continue; - - filter_color_of_small_segments(color_polygon_points_filtered, POLYGON_COLOR_FILTER_DISTANCE_SCALED); - - color_polygons_points.emplace_back(std::move(color_polygon_points_filtered)); } - return color_polygons_points; + ColorPoints color_polygon_points_filtered; + color_polygon_points_filtered.reserve(color_polygon_points.size()); + + douglas_peucker(color_polygon_points.begin(), color_polygon_points.end(), std::back_inserter(color_polygon_points_filtered), INPUT_POLYGONS_FILTER_TOLERANCE_SCALED, POLYGON_COLOR_FILTER_DISTANCE_SCALED); + + if (color_polygon_points_filtered.size() < 3) + return {}; + + filter_color_of_small_segments(color_polygon_points_filtered, POLYGON_COLOR_FILTER_DISTANCE_SCALED); + + return color_polygon_points_filtered; +} + +static std::vector convert_color_expolygon_projection_lines_to_color_points(const ColorProjectionExPolygon &color_projection_expolygon) +{ + std::vector color_expolygon_points; + color_expolygon_points.reserve(color_projection_expolygon.polygons_count()); + + ColorPoints contour_color_points = convert_color_polygon_projection_lines_to_color_points(color_projection_expolygon.contour); + if (contour_color_points.empty()) + return color_expolygon_points; + + color_expolygon_points.emplace_back(std::move(contour_color_points)); + for (const ColorProjectionLines &hole_color_projection_lines : color_projection_expolygon.holes) { + ColorPoints hole_color_points = convert_color_polygon_projection_lines_to_color_points(hole_color_projection_lines); + if (hole_color_points.empty()) + continue; + + color_expolygon_points.emplace_back(std::move(hole_color_points)); + } + + return color_expolygon_points; } static std::optional project_color_line_on_projection_line(const ColorLine &color_line, const ColorProjectionLine &projection_line, const double max_projection_distance_scaled) { @@ -1888,11 +1956,18 @@ inline void update_color_changes_using_color_projection_ranges(ColorProjectionLi } } -static void update_color_changes_using_color_projection_ranges(std::vector &polygons_projection_lines) { - for (ColorProjectionLines &polygon_projection_lines : polygons_projection_lines) { - for (ColorProjectionLine &projection_line : polygon_projection_lines) { - update_color_changes_using_color_projection_ranges(projection_line); - } +static void update_color_changes_using_color_projection_ranges(ColorProjectionLines &polygon_projection_lines) +{ + for (ColorProjectionLine &projection_line : polygon_projection_lines) { + update_color_changes_using_color_projection_ranges(projection_line); + } +} + +static void update_color_changes_using_color_projection_ranges(ColorProjectionExPolygon &color_projection_expolygon) +{ + update_color_changes_using_color_projection_ranges(color_projection_expolygon.contour); + for (ColorProjectionLines &hole_color_projection_lines : color_projection_expolygon.holes) { + update_color_changes_using_color_projection_ranges(hole_color_projection_lines); } } @@ -1940,6 +2015,63 @@ static std::vector slice_model_volume_with_color(const ModelVolum return color_polygons_per_layer; } +// For each ColorProjectionLine, find the nearest ColorLines and project them on the ColorProjectionLine. +static void project_color_projection_lines_on_color_lines(ColorProjectionLines &color_projection_lines, const AABBTreeLines::LinesDistancer &color_lines_distancer) +{ + for (ColorProjectionLine &projection_line : color_projection_lines) { + std::vector nearest_color_line_indices; + Slic3r::append(nearest_color_line_indices, color_lines_distancer.all_lines_in_radius(projection_line.a, MM_SEGMENTATION_MAX_PROJECTION_DISTANCE_SCALED)); + Slic3r::append(nearest_color_line_indices, color_lines_distancer.all_lines_in_radius(projection_line.b, MM_SEGMENTATION_MAX_PROJECTION_DISTANCE_SCALED)); + Slic3r::sort_remove_duplicates(nearest_color_line_indices); + + for (size_t nearest_color_line_idx : nearest_color_line_indices) { + const ColorLine &color_line = color_lines_distancer.get_line(nearest_color_line_idx); + std::optional projection = project_color_line_on_projection_line(color_line, projection_line, MM_SEGMENTATION_MAX_PROJECTION_DISTANCE_SCALED); + if (projection.has_value()) { + projection_line.color_projection_ranges.emplace_back(*projection); + } + } + } +} + +// For each ColorProjectionLine, find the nearest ColorLines and project them on the ColorProjectionLine. +static void project_color_projection_expolygon_on_color_lines(ColorProjectionExPolygon &color_projection_expolygon, const AABBTreeLines::LinesDistancer &color_lines_distancer) +{ + project_color_projection_lines_on_color_lines(color_projection_expolygon.contour, color_lines_distancer); + for (ColorProjectionLines &hole_color_projection_lines : color_projection_expolygon.holes) { + project_color_projection_lines_on_color_lines(hole_color_projection_lines, color_lines_distancer); + } +} + +// For each ColorProjectionLine, find the nearest ColorLines and project them on the ColorProjectionLine. +static void project_color_projection_expolygons_on_color_lines(ColorProjectionExPolygons &color_projection_expolygons, const AABBTreeLines::LinesDistancer &color_lines_distancer) +{ + for (ColorProjectionExPolygon &color_projection_expolygon : color_projection_expolygons) { + project_color_projection_expolygon_on_color_lines(color_projection_expolygon, color_lines_distancer); + } +} + +// For each ColorLine, find the nearest ColorProjectionLines and project the ColorLine on each ColorProjectionLine. +static void project_color_lines_on_color_projection_lines(std::vector &color_polygons_lines, const AABBTreeLines::LinesDistancer &color_projection_lines_distancer) +{ + for (const ColorLines &color_polygon : color_polygons_lines) { + for (const ColorLine &color_line : color_polygon) { + std::vector nearest_projection_line_indices; + Slic3r::append(nearest_projection_line_indices, color_projection_lines_distancer.all_lines_in_radius(color_line.a, MM_SEGMENTATION_MAX_PROJECTION_DISTANCE_SCALED)); + Slic3r::append(nearest_projection_line_indices, color_projection_lines_distancer.all_lines_in_radius(color_line.b, MM_SEGMENTATION_MAX_PROJECTION_DISTANCE_SCALED)); + Slic3r::sort_remove_duplicates(nearest_projection_line_indices); + + for (size_t nearest_projection_line_idx : nearest_projection_line_indices) { + ColorProjectionLine &color_projection_line = *color_projection_lines_distancer.get_line(nearest_projection_line_idx).color_projection_line; + std::optional projection = project_color_line_on_projection_line(color_line, color_projection_line, MM_SEGMENTATION_MAX_PROJECTION_DISTANCE_SCALED); + if (projection.has_value()) { + color_projection_line.color_projection_ranges.emplace_back(*projection); + } + } + } + } +} + std::vector> segmentation_by_painting(const PrintObject &print_object, const std::function &extract_facets_info, const size_t num_facets_states, @@ -1951,13 +2083,13 @@ std::vector> segmentation_by_painting(const PrintObject const size_t num_layers = print_object.layers().size(); const SpanOfConstPtrs layers = print_object.layers(); - std::vector input_expolygons(num_layers); - std::vector> input_polygons_projection_lines_layers(num_layers); - std::vector> color_polygons_lines_layers(num_layers); + std::vector input_expolygons(num_layers); + std::vector input_expolygons_projection_lines_layers(num_layers); + std::vector> color_polygons_lines_layers(num_layers); // Merge all regions and remove small holes BOOST_LOG_TRIVIAL(debug) << "Print object segmentation - Slices preprocessing in parallel - Begin"; - tbb::parallel_for(tbb::blocked_range(0, num_layers), [&layers, &input_expolygons, &input_polygons_projection_lines_layers, &throw_on_cancel_callback](const tbb::blocked_range &range) { + tbb::parallel_for(tbb::blocked_range(0, num_layers), [&layers, &input_expolygons, &input_expolygons_projection_lines_layers, &throw_on_cancel_callback](const tbb::blocked_range &range) { for (size_t layer_idx = range.begin(); layer_idx < range.end(); ++layer_idx) { throw_on_cancel_callback(); @@ -1980,8 +2112,8 @@ std::vector> segmentation_by_painting(const PrintObject // Such close points sometimes caused that the Voronoi diagram has self-intersecting edges around these vertices. // This consequently leads to issues with the extraction of colored segments by function extract_colored_segments. // Calling expolygons_simplify fixed these issues. - input_expolygons[layer_idx] = remove_duplicates(expolygons_simplify(offset_ex(ex_polygons, -10.f * float(SCALED_EPSILON)), 5 * SCALED_EPSILON), scaled(0.01), PI / 6); - input_polygons_projection_lines_layers[layer_idx] = create_color_projection_lines(input_expolygons[layer_idx]); + input_expolygons[layer_idx] = remove_duplicates(expolygons_simplify(offset_ex(ex_polygons, -10.f * float(SCALED_EPSILON)), 5 * SCALED_EPSILON), scaled(0.01), PI / 6); + input_expolygons_projection_lines_layers[layer_idx] = create_color_projection_expolygons(input_expolygons[layer_idx]); if constexpr (MM_SEGMENTATION_DEBUG_INPUT) { export_processed_input_expolygons_to_svg(debug_out_path("mm-input-%d.svg", layer_idx), layers[layer_idx]->regions(), input_expolygons[layer_idx]); @@ -2034,47 +2166,17 @@ std::vector> segmentation_by_painting(const PrintObject // Project sliced ColorPolygons on sliced layers (input_expolygons). BOOST_LOG_TRIVIAL(debug) << "Print object segmentation - Projection of painted triangles - Begin"; - tbb::parallel_for(tbb::blocked_range(0, num_layers), [&color_polygons_lines_layers, &input_polygons_projection_lines_layers, &throw_on_cancel_callback](const tbb::blocked_range &range) { + tbb::parallel_for(tbb::blocked_range(0, num_layers), [&color_polygons_lines_layers, &input_expolygons_projection_lines_layers, &throw_on_cancel_callback](const tbb::blocked_range &range) { for (size_t layer_idx = range.begin(); layer_idx < range.end(); ++layer_idx) { throw_on_cancel_callback(); // For each ColorLine, find the nearest ColorProjectionLines and project the ColorLine on each ColorProjectionLine. - const AABBTreeLines::LinesDistancer color_projection_lines_distancer{create_color_projection_lines_mapping(input_polygons_projection_lines_layers[layer_idx])}; - for (const ColorLines &color_polygon : color_polygons_lines_layers[layer_idx]) { - for (const ColorLine &color_line : color_polygon) { - std::vector nearest_projection_line_indices; - Slic3r::append(nearest_projection_line_indices, color_projection_lines_distancer.all_lines_in_radius(color_line.a, MM_SEGMENTATION_MAX_PROJECTION_DISTANCE_SCALED)); - Slic3r::append(nearest_projection_line_indices, color_projection_lines_distancer.all_lines_in_radius(color_line.b, MM_SEGMENTATION_MAX_PROJECTION_DISTANCE_SCALED)); - Slic3r::sort_remove_duplicates(nearest_projection_line_indices); - - for (size_t nearest_projection_line_idx : nearest_projection_line_indices) { - ColorProjectionLine &color_projection_line = *color_projection_lines_distancer.get_line(nearest_projection_line_idx).color_projection_line; - std::optional projection = project_color_line_on_projection_line(color_line, color_projection_line, MM_SEGMENTATION_MAX_PROJECTION_DISTANCE_SCALED); - if (projection.has_value()) { - color_projection_line.color_projection_ranges.emplace_back(*projection); - } - } - } - } + const AABBTreeLines::LinesDistancer color_projection_lines_distancer{create_color_projection_lines_mapping(input_expolygons_projection_lines_layers[layer_idx])}; + project_color_lines_on_color_projection_lines(color_polygons_lines_layers[layer_idx], color_projection_lines_distancer); // For each ColorProjectionLine, find the nearest ColorLines and project them on the ColorProjectionLine. const AABBTreeLines::LinesDistancer color_lines_distancer{flatten_color_lines(color_polygons_lines_layers[layer_idx])}; - for (ColorProjectionLines &input_polygon_projection_lines : input_polygons_projection_lines_layers[layer_idx]) { - for (ColorProjectionLine &projection_lines : input_polygon_projection_lines) { - std::vector nearest_color_line_indices; - Slic3r::append(nearest_color_line_indices, color_lines_distancer.all_lines_in_radius(projection_lines.a, MM_SEGMENTATION_MAX_PROJECTION_DISTANCE_SCALED)); - Slic3r::append(nearest_color_line_indices, color_lines_distancer.all_lines_in_radius(projection_lines.b, MM_SEGMENTATION_MAX_PROJECTION_DISTANCE_SCALED)); - Slic3r::sort_remove_duplicates(nearest_color_line_indices); - - for (size_t nearest_color_line_idx : nearest_color_line_indices) { - const ColorLine &color_line = color_lines_distancer.get_line(nearest_color_line_idx); - std::optional projection = project_color_line_on_projection_line(color_line, projection_lines, MM_SEGMENTATION_MAX_PROJECTION_DISTANCE_SCALED); - if (projection.has_value()) { - projection_lines.color_projection_ranges.emplace_back(*projection); - } - } - } - } + project_color_projection_expolygons_on_color_lines(input_expolygons_projection_lines_layers[layer_idx], color_lines_distancer); } }); // end of parallel_for BOOST_LOG_TRIVIAL(debug) << "MM segmentation - Projection of painted triangles - End"; @@ -2085,37 +2187,46 @@ std::vector> segmentation_by_painting(const PrintObject // Be aware that after the projection of the ColorPolygons and its postprocessing isn't // ensured that consistency of the color_prev. So, only color_next can be used. BOOST_LOG_TRIVIAL(debug) << "Print object segmentation - Layers segmentation in parallel - Begin"; - tbb::parallel_for(tbb::blocked_range(0, num_layers), [&input_polygons_projection_lines_layers, &segmented_regions, &input_expolygons, &num_facets_states, &throw_on_cancel_callback](const tbb::blocked_range &range) { + tbb::parallel_for(tbb::blocked_range(0, num_layers), [&input_expolygons_projection_lines_layers, &segmented_regions, &input_expolygons, &num_facets_states, &throw_on_cancel_callback](const tbb::blocked_range &range) { for (size_t layer_idx = range.begin(); layer_idx < range.end(); ++layer_idx) { throw_on_cancel_callback(); - std::vector &input_polygons_projection_lines = input_polygons_projection_lines_layers[layer_idx]; - if (input_polygons_projection_lines.empty()) { - continue; - } + ColorProjectionExPolygons &input_expolygons_projection_lines = input_expolygons_projection_lines_layers[layer_idx]; + for (ColorProjectionExPolygon &input_expolygon_projection_lines : input_expolygons_projection_lines) { + const size_t expolygon_idx = &input_expolygon_projection_lines - input_expolygons_projection_lines.data(); - if constexpr (MM_SEGMENTATION_DEBUG_COLOR_RANGES) { - export_color_projection_lines_color_ranges_to_svg(debug_out_path("mm-color-ranges-%d.svg", layer_idx), input_polygons_projection_lines, input_expolygons[layer_idx]); - } + if constexpr (MM_SEGMENTATION_DEBUG_COLOR_RANGES) { + export_color_projection_lines_color_ranges_to_svg(debug_out_path("mm-color-ranges-%d-%d.svg", layer_idx, expolygon_idx), input_expolygon_projection_lines, input_expolygons[layer_idx]); + } - update_color_changes_using_color_projection_ranges(input_polygons_projection_lines); - filter_projected_color_points_on_polygons(input_polygons_projection_lines); + update_color_changes_using_color_projection_ranges(input_expolygon_projection_lines); + filter_projected_color_points_on_expolygon(input_expolygon_projection_lines); - std::vector color_polygons_points = convert_color_polygons_projection_lines_to_color_points(input_polygons_projection_lines); - snap_projected_color_points_to_nearest_angles(color_polygons_points); + std::vector color_polygons_points = convert_color_expolygon_projection_lines_to_color_points(input_expolygon_projection_lines); + if (color_polygons_points.empty()) + continue; - if constexpr (MM_SEGMENTATION_DEBUG_COLORIZED_POLYGONS) { - export_color_polygons_points_to_svg(debug_out_path("mm-projected-color_polygon-%d.svg", layer_idx), color_polygons_points, input_expolygons[layer_idx]); - } + snap_projected_color_points_to_nearest_angles(color_polygons_points); - const std::vector colored_polygons = color_points_to_colored_lines(color_polygons_points); - assert(!colored_polygons.empty()); - if (has_layer_only_one_color(colored_polygons)) { - // When the whole layer is painted using the same color, it is not needed to construct a Voronoi diagram for the segmentation of this layer. - assert(!colored_polygons.front().empty()); - segmented_regions[layer_idx][size_t(colored_polygons.front().front().color)] = input_expolygons[layer_idx]; - } else { - segmented_regions[layer_idx] = extract_colored_segments(colored_polygons, num_facets_states, layer_idx); + if constexpr (MM_SEGMENTATION_DEBUG_COLORIZED_POLYGONS) { + export_color_polygons_points_to_svg(debug_out_path("mm-projected-color_polygon-%d-%d.svg", layer_idx, expolygon_idx), color_polygons_points, input_expolygons[layer_idx]); + } + + const std::vector colored_polygons = color_points_to_colored_lines(color_polygons_points); + assert(!colored_polygons.empty()); + if (has_polygons_only_one_color(colored_polygons)) { + // When the whole ExPolygon is painted using the same color, it is not needed to construct a Voronoi diagram for the segmentation of this ExPolygon. + assert(!colored_polygons.front().empty()); + segmented_regions[layer_idx][size_t(colored_polygons.front().front().color)].emplace_back(input_expolygons[layer_idx][expolygon_idx]); + } else { + std::vector colored_segments_by_states = extract_colored_segments(colored_polygons, num_facets_states, layer_idx); + for (size_t state_idx = 0; state_idx < num_facets_states; ++state_idx) { + if (colored_segments_by_states[state_idx].empty()) + continue; + + Slic3r::append(segmented_regions[layer_idx][state_idx], std::move(colored_segments_by_states[state_idx])); + } + } } if constexpr (MM_SEGMENTATION_DEBUG_REGIONS) {