diff --git a/src/libslic3r/MultiMaterialSegmentation.cpp b/src/libslic3r/MultiMaterialSegmentation.cpp index 5297fdcc92..b765c9c76d 100644 --- a/src/libslic3r/MultiMaterialSegmentation.cpp +++ b/src/libslic3r/MultiMaterialSegmentation.cpp @@ -3,13 +3,12 @@ ///|/ PrusaSlicer is released under the terms of the AGPLv3 or higher ///|/ #include -#include #include #include #include #include #include -#include +#include #include #include #include @@ -22,14 +21,15 @@ #include "BoundingBox.hpp" #include "ClipperUtils.hpp" -#include "EdgeGrid.hpp" #include "Layer.hpp" #include "Print.hpp" #include "Geometry/VoronoiUtils.hpp" #include "MutablePolygon.hpp" #include "admesh/stl.h" +#include "libslic3r/AABBTreeLines.hpp" #include "libslic3r/ExPolygon.hpp" #include "libslic3r/Flow.hpp" +#include "libslic3r/format.hpp" #include "libslic3r/Geometry/VoronoiOffset.hpp" #include "libslic3r/LayerRegion.hpp" #include "libslic3r/Line.hpp" @@ -38,520 +38,83 @@ #include "libslic3r/Polygon.hpp" #include "libslic3r/PrintConfig.hpp" #include "libslic3r/Surface.hpp" +#include "libslic3r/SVG.hpp" #include "libslic3r/TriangleMeshSlicer.hpp" #include "libslic3r/TriangleSelector.hpp" #include "libslic3r/Utils.hpp" #include "libslic3r/libslic3r.h" #include "MultiMaterialSegmentation.hpp" -//#define MM_SEGMENTATION_DEBUG_GRAPH -//#define MM_SEGMENTATION_DEBUG_REGIONS -//#define MM_SEGMENTATION_DEBUG_INPUT -//#define MM_SEGMENTATION_DEBUG_PAINTED_LINES -//#define MM_SEGMENTATION_DEBUG_COLORIZED_POLYGONS - -#if defined(MM_SEGMENTATION_DEBUG_GRAPH) || defined(MM_SEGMENTATION_DEBUG_REGIONS) || \ - defined(MM_SEGMENTATION_DEBUG_INPUT) || defined(MM_SEGMENTATION_DEBUG_PAINTED_LINES) || \ - defined(MM_SEGMENTATION_DEBUG_COLORIZED_POLYGONS) -#define MM_SEGMENTATION_DEBUG -#endif - -//#define MM_SEGMENTATION_DEBUG_TOP_BOTTOM +constexpr bool MM_SEGMENTATION_DEBUG_GRAPH = false; +constexpr bool MM_SEGMENTATION_DEBUG_REGIONS = false; +constexpr bool MM_SEGMENTATION_DEBUG_INPUT = false; +constexpr bool MM_SEGMENTATION_DEBUG_COLORIZED_POLYGONS = false; +constexpr bool MM_SEGMENTATION_DEBUG_TOP_BOTTOM = false; namespace Slic3r { -// Assumes that is at most same projected_l length or below than projection_l -static bool project_line_on_line(const Line &projection_l, const Line &projected_l, Line *new_projected) -{ - const Vec2d v1 = (projection_l.b - projection_l.a).cast(); - const Vec2d va = (projected_l.a - projection_l.a).cast(); - const Vec2d vb = (projected_l.b - projection_l.a).cast(); - const double l2 = v1.squaredNorm(); // avoid a sqrt - if (l2 == 0.0) - return false; - double t1 = va.dot(v1) / l2; - double t2 = vb.dot(v1) / l2; - t1 = std::clamp(t1, 0., 1.); - t2 = std::clamp(t2, 0., 1.); - assert(t1 >= 0.); - assert(t2 >= 0.); - assert(t1 <= 1.); - assert(t2 <= 1.); - - Point p1 = projection_l.a + (t1 * v1).cast(); - Point p2 = projection_l.a + (t2 * v1).cast(); - *new_projected = Line(p1, p2); - return true; -} - -struct PaintedLine -{ - size_t contour_idx; - size_t line_idx; - Line projected_line; - int color; -}; - -struct PaintedLineVisitor -{ - PaintedLineVisitor(const EdgeGrid::Grid &grid, std::vector &painted_lines, std::mutex &painted_lines_mutex, size_t reserve) : grid(grid), painted_lines(painted_lines), painted_lines_mutex(painted_lines_mutex) - { - painted_lines_set.reserve(reserve); - } - - void reset() { painted_lines_set.clear(); } - - bool operator()(coord_t iy, coord_t ix) - { - // Called with a row and column of the grid cell, which is intersected by a line. - auto cell_data_range = grid.cell_data_range(iy, ix); - const Vec2d v1 = line_to_test.vector().cast(); - const double v1_sqr_norm = v1.squaredNorm(); - const double heuristic_thr_part = line_to_test.length() + append_threshold; - for (auto it_contour_and_segment = cell_data_range.first; it_contour_and_segment != cell_data_range.second; ++it_contour_and_segment) { - Line grid_line = grid.line(*it_contour_and_segment); - const Vec2d v2 = grid_line.vector().cast(); - double heuristic_thr_sqr = Slic3r::sqr(heuristic_thr_part + grid_line.length()); - - // An inexpensive heuristic to test whether line_to_test and grid_line can be somewhere close enough to each other. - // This helps filter out cases when the following expensive calculations are useless. - if ((grid_line.a - line_to_test.a).cast().squaredNorm() > heuristic_thr_sqr || - (grid_line.b - line_to_test.a).cast().squaredNorm() > heuristic_thr_sqr || - (grid_line.a - line_to_test.b).cast().squaredNorm() > heuristic_thr_sqr || - (grid_line.b - line_to_test.b).cast().squaredNorm() > heuristic_thr_sqr) - continue; - - // When lines have too different length, it is necessary to normalize them - if (Slic3r::sqr(v1.dot(v2)) > cos_threshold2 * v1_sqr_norm * v2.squaredNorm()) { - // The two vectors are nearly collinear (their mutual angle is lower than 30 degrees) - if (painted_lines_set.find(*it_contour_and_segment) == painted_lines_set.end()) { - if (grid_line.distance_to_squared(line_to_test.a) < append_threshold2 || - grid_line.distance_to_squared(line_to_test.b) < append_threshold2 || - line_to_test.distance_to_squared(grid_line.a) < append_threshold2 || - line_to_test.distance_to_squared(grid_line.b) < append_threshold2) { - Line line_to_test_projected; - project_line_on_line(grid_line, line_to_test, &line_to_test_projected); - - if ((line_to_test_projected.a - grid_line.a).cast().squaredNorm() > (line_to_test_projected.b - grid_line.a).cast().squaredNorm()) - line_to_test_projected.reverse(); - - painted_lines_set.insert(*it_contour_and_segment); - { - boost::lock_guard lock(painted_lines_mutex); - painted_lines.push_back({it_contour_and_segment->first, it_contour_and_segment->second, line_to_test_projected, this->color}); - } - } - } - } - } - // Continue traversing the grid along the edge. - return true; - } - - const EdgeGrid::Grid &grid; - std::vector &painted_lines; - std::mutex &painted_lines_mutex; - Line line_to_test; - std::unordered_set, boost::hash>> painted_lines_set; - int color = -1; - - static inline const double cos_threshold2 = Slic3r::sqr(cos(M_PI * 30. / 180.)); - static inline const double append_threshold = 50 * SCALED_EPSILON; - static inline const double append_threshold2 = Slic3r::sqr(append_threshold); -}; - -BoundingBox get_extents(const std::vector &colored_polygons) { - BoundingBox bbox; - for (const ColoredLines &colored_lines : colored_polygons) { - for (const ColoredLine &colored_line : colored_lines) { - bbox.merge(colored_line.line.a); - bbox.merge(colored_line.line.b); - } - } - return bbox; -} - -// Flatten the vector of vectors into a vector. -static inline ColoredLines to_lines(const std::vector &c_lines) -{ - size_t n_lines = 0; - for (const auto &c_line : c_lines) - n_lines += c_line.size(); - ColoredLines lines; - lines.reserve(n_lines); - for (const auto &c_line : c_lines) - lines.insert(lines.end(), c_line.begin(), c_line.end()); - return lines; -} - -static std::vector> get_segments(const ColoredLines &polygon) -{ - std::vector> segments; - - size_t segment_end = 0; - while (segment_end + 1 < polygon.size() && polygon[segment_end].color == polygon[segment_end + 1].color) - segment_end++; - - if (segment_end == polygon.size() - 1) - return {std::make_pair(0, polygon.size() - 1)}; - - size_t first_different_color = (segment_end + 1) % polygon.size(); - for (size_t line_offset_idx = 0; line_offset_idx < polygon.size(); ++line_offset_idx) { - size_t start_s = (first_different_color + line_offset_idx) % polygon.size(); - size_t end_s = start_s; - - while (line_offset_idx + 1 < polygon.size() && polygon[start_s].color == polygon[(first_different_color + line_offset_idx + 1) % polygon.size()].color) { - end_s = (first_different_color + line_offset_idx + 1) % polygon.size(); - line_offset_idx++; - } - segments.emplace_back(start_s, end_s); - } - return segments; -} - -static std::vector filter_painted_lines(const Line &line_to_process, const size_t start_idx, const size_t end_idx, const std::vector &painted_lines) -{ - const int filter_eps_value = scale_(0.1f); - std::vector filtered_lines; - filtered_lines.emplace_back(painted_lines[start_idx]); - for (size_t line_idx = start_idx + 1; line_idx <= end_idx; ++line_idx) { - // line_to_process is already all colored. Skip another possible duplicate coloring. - if(filtered_lines.back().projected_line.b == line_to_process.b) - break; - - PaintedLine &prev = filtered_lines.back(); - const PaintedLine &curr = painted_lines[line_idx]; - - double prev_length = prev.projected_line.length(); - double curr_dist_start = (curr.projected_line.a - prev.projected_line.a).cast().norm(); - double dist_between_lines = curr_dist_start - prev_length; - - if (dist_between_lines >= 0) { - if (prev.color == curr.color) { - if (dist_between_lines <= filter_eps_value) { - prev.projected_line.b = curr.projected_line.b; - } else { - filtered_lines.emplace_back(curr); - } - } else { - filtered_lines.emplace_back(curr); - } - } else { - double curr_dist_end = (curr.projected_line.b - prev.projected_line.a).cast().norm(); - if (curr_dist_end > prev_length) { - if (prev.color == curr.color) - prev.projected_line.b = curr.projected_line.b; - else - filtered_lines.push_back({curr.contour_idx, curr.line_idx, Line{prev.projected_line.b, curr.projected_line.b}, curr.color}); - } - } - } - - if (double dist_to_start = (filtered_lines.front().projected_line.a - line_to_process.a).cast().norm(); dist_to_start <= filter_eps_value) - filtered_lines.front().projected_line.a = line_to_process.a; - - if (double dist_to_end = (filtered_lines.back().projected_line.b - line_to_process.b).cast().norm(); dist_to_end <= filter_eps_value) - filtered_lines.back().projected_line.b = line_to_process.b; - - return filtered_lines; -} - -static std::vector> post_process_painted_lines(const std::vector &contours, std::vector &&painted_lines) -{ - if (painted_lines.empty()) - return {}; - - auto comp = [&contours](const PaintedLine &first, const PaintedLine &second) { - Point first_start_p = contours[first.contour_idx].segment_start(first.line_idx); - return first.contour_idx < second.contour_idx || - (first.contour_idx == second.contour_idx && - (first.line_idx < second.line_idx || - (first.line_idx == second.line_idx && - ((first.projected_line.a - first_start_p).cast().squaredNorm() < (second.projected_line.a - first_start_p).cast().squaredNorm() || - ((first.projected_line.a - first_start_p).cast().squaredNorm() == (second.projected_line.a - first_start_p).cast().squaredNorm() && - (first.projected_line.b - first.projected_line.a).cast().squaredNorm() < (second.projected_line.b - second.projected_line.a).cast().squaredNorm()))))); - }; - std::sort(painted_lines.begin(), painted_lines.end(), comp); - - std::vector> filtered_painted_lines(contours.size()); - size_t prev_painted_line_idx = 0; - for (size_t curr_painted_line_idx = 0; curr_painted_line_idx < painted_lines.size(); ++curr_painted_line_idx) { - size_t next_painted_line_idx = curr_painted_line_idx + 1; - if (next_painted_line_idx >= painted_lines.size() || painted_lines[curr_painted_line_idx].contour_idx != painted_lines[next_painted_line_idx].contour_idx || painted_lines[curr_painted_line_idx].line_idx != painted_lines[next_painted_line_idx].line_idx) { - const PaintedLine &start_line = painted_lines[prev_painted_line_idx]; - const Line &line_to_process = contours[start_line.contour_idx].get_segment(start_line.line_idx); - Slic3r::append(filtered_painted_lines[painted_lines[curr_painted_line_idx].contour_idx], filter_painted_lines(line_to_process, prev_painted_line_idx, curr_painted_line_idx, painted_lines)); - prev_painted_line_idx = next_painted_line_idx; - } - } - - return filtered_painted_lines; -} - -#ifndef NDEBUG -static bool are_lines_connected(const ColoredLines &colored_lines) -{ - for (size_t line_idx = 1; line_idx < colored_lines.size(); ++line_idx) - if (colored_lines[line_idx - 1].line.b != colored_lines[line_idx].line.a) - return false; - return true; -} -#endif - -static ColoredLines colorize_line(const Line &line_to_process, - const size_t start_idx, - const size_t end_idx, - const std::vector &painted_contour) -{ - assert(start_idx < painted_contour.size() && end_idx < painted_contour.size() && start_idx <= end_idx); - assert(std::all_of(painted_contour.begin() + start_idx, painted_contour.begin() + end_idx + 1, [&painted_contour, &start_idx](const auto &p_line) { return painted_contour[start_idx].line_idx == p_line.line_idx; })); - - const int filter_eps_value = scale_(0.1f); - ColoredLines final_lines; - const PaintedLine &first_line = painted_contour[start_idx]; - if (double dist_to_start = (first_line.projected_line.a - line_to_process.a).cast().norm(); dist_to_start > filter_eps_value) - final_lines.push_back({Line(line_to_process.a, first_line.projected_line.a), 0}); - final_lines.push_back({first_line.projected_line, first_line.color}); - - for (size_t line_idx = start_idx + 1; line_idx <= end_idx; ++line_idx) { - ColoredLine &prev = final_lines.back(); - const PaintedLine &curr = painted_contour[line_idx]; - - double line_dist = (curr.projected_line.a - prev.line.b).cast().norm(); - if (line_dist <= filter_eps_value) { - if (prev.color == curr.color) { - prev.line.b = curr.projected_line.b; - } else { - prev.line.b = curr.projected_line.a; - final_lines.push_back({curr.projected_line, curr.color}); - } - } else { - final_lines.push_back({Line(prev.line.b, curr.projected_line.a), 0}); - final_lines.push_back({curr.projected_line, curr.color}); - } - } - - // If there is non-painted space, then inserts line painted by a default color. - if (double dist_to_end = (final_lines.back().line.b - line_to_process.b).cast().norm(); dist_to_end > filter_eps_value) - final_lines.push_back({Line(final_lines.back().line.b, line_to_process.b), 0}); - - // Make sure all the lines are connected. - assert(are_lines_connected(final_lines)); - - for (size_t line_idx = 2; line_idx < final_lines.size(); ++line_idx) { - const ColoredLine &line_0 = final_lines[line_idx - 2]; - ColoredLine &line_1 = final_lines[line_idx - 1]; - const ColoredLine &line_2 = final_lines[line_idx - 0]; - - if (line_0.color == line_2.color && line_0.color != line_1.color) - if (line_1.line.length() <= scale_(0.2)) line_1.color = line_0.color; - } - - ColoredLines colored_lines_simple; - colored_lines_simple.emplace_back(final_lines.front()); - for (size_t line_idx = 1; line_idx < final_lines.size(); ++line_idx) { - const ColoredLine &line_0 = final_lines[line_idx]; - - if (colored_lines_simple.back().color == line_0.color) - colored_lines_simple.back().line.b = line_0.line.b; - else - colored_lines_simple.emplace_back(line_0); - } - - final_lines = colored_lines_simple; - - if (final_lines.size() > 1) - if (final_lines.front().color != final_lines[1].color && final_lines.front().line.length() <= scale_(0.2)) { - final_lines[1].line.a = final_lines.front().line.a; - final_lines.erase(final_lines.begin()); - } - - if (final_lines.size() > 1) - if (final_lines.back().color != final_lines[final_lines.size() - 2].color && final_lines.back().line.length() <= scale_(0.2)) { - final_lines[final_lines.size() - 2].line.b = final_lines.back().line.b; - final_lines.pop_back(); - } - - return final_lines; -} - -static ColoredLines filter_colorized_polygon(ColoredLines &&new_lines) { - for (size_t line_idx = 2; line_idx < new_lines.size(); ++line_idx) { - const ColoredLine &line_0 = new_lines[line_idx - 2]; - ColoredLine &line_1 = new_lines[line_idx - 1]; - const ColoredLine &line_2 = new_lines[line_idx - 0]; - - if (line_0.color == line_2.color && line_0.color != line_1.color && line_0.color >= 1) { - if (line_1.line.length() <= scale_(0.5)) line_1.color = line_0.color; - } - } - - for (size_t line_idx = 3; line_idx < new_lines.size(); ++line_idx) { - const ColoredLine &line_0 = new_lines[line_idx - 3]; - ColoredLine &line_1 = new_lines[line_idx - 2]; - ColoredLine &line_2 = new_lines[line_idx - 1]; - const ColoredLine &line_3 = new_lines[line_idx - 0]; - - if (line_0.color == line_3.color && (line_0.color != line_1.color || line_0.color != line_2.color) && line_0.color >= 1 && line_3.color >= 1) { - if ((line_1.line.length() + line_2.line.length()) <= scale_(0.5)) { - line_1.color = line_0.color; - line_2.color = line_0.color; - } - } - } - - std::vector> segments = get_segments(new_lines); - auto segment_length = [&new_lines](const std::pair &segment) { - double total_length = 0; - for (size_t seg_start_idx = segment.first; seg_start_idx != segment.second; seg_start_idx = (seg_start_idx + 1 < new_lines.size()) ? seg_start_idx + 1 : 0) - total_length += new_lines[seg_start_idx].line.length(); - total_length += new_lines[segment.second].line.length(); - return total_length; - }; - - if (segments.size() >= 2) - for (size_t curr_idx = 0; curr_idx < segments.size(); ++curr_idx) { - size_t next_idx = next_idx_modulo(curr_idx, segments.size()); - assert(curr_idx != next_idx); - - int color0 = new_lines[segments[curr_idx].first].color; - int color1 = new_lines[segments[next_idx].first].color; - - double seg0l = segment_length(segments[curr_idx]); - double seg1l = segment_length(segments[next_idx]); - - if (color0 != color1 && seg0l >= scale_(0.1) && seg1l <= scale_(0.2)) { - for (size_t seg_start_idx = segments[next_idx].first; seg_start_idx != segments[next_idx].second; seg_start_idx = (seg_start_idx + 1 < new_lines.size()) ? seg_start_idx + 1 : 0) - new_lines[seg_start_idx].color = color0; - new_lines[segments[next_idx].second].color = color0; - } - } - - segments = get_segments(new_lines); - if (segments.size() >= 2) - for (size_t curr_idx = 0; curr_idx < segments.size(); ++curr_idx) { - size_t next_idx = next_idx_modulo(curr_idx, segments.size()); - assert(curr_idx != next_idx); - - int color0 = new_lines[segments[curr_idx].first].color; - int color1 = new_lines[segments[next_idx].first].color; - double seg1l = segment_length(segments[next_idx]); - - if (color0 >= 1 && color0 != color1 && seg1l <= scale_(0.2)) { - for (size_t seg_start_idx = segments[next_idx].first; seg_start_idx != segments[next_idx].second; seg_start_idx = (seg_start_idx + 1 < new_lines.size()) ? seg_start_idx + 1 : 0) - new_lines[seg_start_idx].color = color0; - new_lines[segments[next_idx].second].color = color0; - } - } - - segments = get_segments(new_lines); - if (segments.size() >= 3) - for (size_t curr_idx = 0; curr_idx < segments.size(); ++curr_idx) { - size_t next_idx = next_idx_modulo(curr_idx, segments.size()); - size_t next_next_idx = next_idx_modulo(next_idx, segments.size()); - - int color0 = new_lines[segments[curr_idx].first].color; - int color1 = new_lines[segments[next_idx].first].color; - int color2 = new_lines[segments[next_next_idx].first].color; - - if (color0 > 0 && color0 == color2 && color0 != color1 && segment_length(segments[next_idx]) <= scale_(0.5)) { - for (size_t seg_start_idx = segments[next_next_idx].first; seg_start_idx != segments[next_next_idx].second; seg_start_idx = (seg_start_idx + 1 < new_lines.size()) ? seg_start_idx + 1 : 0) - new_lines[seg_start_idx].color = color0; - new_lines[segments[next_next_idx].second].color = color0; - } - } - - return std::move(new_lines); -} - -static ColoredLines colorize_contour(const EdgeGrid::Contour &contour, const std::vector &painted_contour) { - assert(painted_contour.empty() || std::all_of(painted_contour.begin(), painted_contour.end(), [&painted_contour](const auto &p_line) { return painted_contour.front().contour_idx == p_line.contour_idx; })); - - ColoredLines colorized_contour; - if (painted_contour.empty()) { - // Appends contour with default color for lines before the first PaintedLine. - colorized_contour.reserve(contour.num_segments()); - for (const Line &line : contour.get_segments()) - colorized_contour.emplace_back(ColoredLine{line, 0}); - return colorized_contour; - } - - colorized_contour.reserve(contour.num_segments() + painted_contour.size()); - for (size_t idx = 0; idx < painted_contour.front().line_idx; ++idx) - colorized_contour.emplace_back(ColoredLine{contour.get_segment(idx), 0}); - - size_t prev_painted_line_idx = 0; - for (size_t curr_painted_line_idx = 0; curr_painted_line_idx < painted_contour.size(); ++curr_painted_line_idx) { - size_t next_painted_line_idx = curr_painted_line_idx + 1; - if (next_painted_line_idx >= painted_contour.size() || painted_contour[curr_painted_line_idx].line_idx != painted_contour[next_painted_line_idx].line_idx) { - const std::vector &painted_contour_copy = painted_contour; - Slic3r::append(colorized_contour, colorize_line(contour.get_segment(painted_contour[prev_painted_line_idx].line_idx), prev_painted_line_idx, curr_painted_line_idx, painted_contour_copy)); - - // Appends contour with default color for lines between the current and the next PaintedLine. - if (next_painted_line_idx < painted_contour.size()) - for (size_t idx = painted_contour[curr_painted_line_idx].line_idx + 1; idx < painted_contour[next_painted_line_idx].line_idx; ++idx) - colorized_contour.emplace_back(ColoredLine{contour.get_segment(idx), 0}); - - prev_painted_line_idx = next_painted_line_idx; - } - } - - // Appends contour with default color for lines after the last PaintedLine. - for (size_t idx = painted_contour.back().line_idx + 1; idx < contour.num_segments(); ++idx) - colorized_contour.emplace_back(ColoredLine{contour.get_segment(idx), 0}); - - assert(!colorized_contour.empty()); - return filter_colorized_polygon(std::move(colorized_contour)); -} - -static std::vector colorize_contours(const std::vector &contours, const std::vector> &painted_contours) -{ - assert(contours.size() == painted_contours.size()); - std::vector colorized_contours(contours.size()); - for (const std::vector &painted_contour : painted_contours) { - size_t contour_idx = &painted_contour - &painted_contours.front(); - colorized_contours[contour_idx] = colorize_contour(contours[contour_idx], painted_contours[contour_idx]); - } - - size_t poly_idx = 0; - for (ColoredLines &color_lines : colorized_contours) { - size_t line_idx = 0; - for (size_t color_line_idx = 0; color_line_idx < color_lines.size(); ++color_line_idx) { - color_lines[color_line_idx].poly_idx = int(poly_idx); - color_lines[color_line_idx].local_line_idx = int(line_idx); - ++line_idx; - } - ++poly_idx; - } - - return colorized_contours; -} - -// Determines if the line points from the point between two contour lines is pointing inside polygon or outside. -static inline bool points_inside(const Line &contour_first, const Line &contour_second, const Point &new_point) -{ - // TODO: Used in points_inside for decision if line leading thought the common point of two lines is pointing inside polygon or outside - auto three_points_inward_normal = [](const Point &left, const Point &middle, const Point &right) -> Vec2d { - assert(left != middle); - assert(middle != right); - return (perp(Point(middle - left)).cast().normalized() + perp(Point(right - middle)).cast().normalized()).normalized(); - }; - - assert(contour_first.b == contour_second.a); - Vec2d inward_normal = three_points_inward_normal(contour_first.a, contour_first.b, contour_second.b); - Vec2d edge_norm = (new_point - contour_first.b).cast().normalized(); - double side = inward_normal.dot(edge_norm); - // assert(side != 0.); - return side > 0.; -} +const constexpr double POLYGON_FILTER_MIN_AREA_SCALED = scaled(0.1f); +const constexpr double POLYGON_FILTER_MIN_OFFSET_SCALED = scaled(0.01f); +const constexpr double POLYGON_COLOR_FILTER_DISTANCE_SCALED = scaled(0.2); +const constexpr double POLYGON_COLOR_FILTER_TOLERANCE_SCALED = scaled(0.02); +const constexpr double INPUT_POLYGONS_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); enum VD_ANNOTATION : Voronoi::VD::cell_type::color_type { VERTEX_ON_CONTOUR = 1, DELETED = 2 }; -#ifdef MM_SEGMENTATION_DEBUG_GRAPH -static void export_graph_to_svg(const std::string &path, const Voronoi::VD& vd, const std::vector& colored_polygons) { +struct ColorChange { + explicit ColorChange(double t, uint8_t color_next) : t(t), color_next(color_next) {} + + // Relative position on the line from range <0, 1> + double t = 0.; + // Color after (including) t value on the line. + uint8_t color_next = 0; + + friend bool operator<(const ColorChange &lhs, const ColorChange &rhs) { return lhs.t < rhs.t; } +}; + +using ColorChanges = std::vector; + +struct ColorProjectionLine +{ + explicit ColorProjectionLine(const Line &line) : a(line.a), b(line.b){}; + + Point a; + Point b; + ColorChanges color_changes; +}; + +using ColorProjectionLines = std::vector; + +struct ColorProjectionLineWrapper +{ + static const constexpr int Dim = 2; + using Scalar = Point::Scalar; + + explicit ColorProjectionLineWrapper(ColorProjectionLine *const color_projection_line) : a(color_projection_line->a), b(color_projection_line->b), color_projection_line(color_projection_line){}; + + const Point a; + const Point b; + ColorProjectionLine *const color_projection_line; +}; + +struct ColorPoint +{ + Point p; + uint8_t color_prev; + uint8_t color_next; + + explicit ColorPoint(const Point &p, uint8_t color_prev, uint8_t color_next) : p(p), color_prev(color_prev), color_next(color_next) {} +}; + +using ColorPoints = std::vector; + + +[[maybe_unused]] static void export_graph_to_svg(const std::string &path, const Voronoi::VD& vd, const std::vector& colored_polygons) { const coordf_t stroke_width = scaled(0.05f); const BoundingBox bbox = get_extents(colored_polygons); @@ -581,7 +144,144 @@ static void export_graph_to_svg(const std::string &path, const Voronoi::VD& vd, svg.draw(Line(from, to), "red", stroke_width); } } -#endif // MM_SEGMENTATION_DEBUG_GRAPH + +[[maybe_unused]] static void export_regions_to_svg(const std::string &path, const std::vector ®ions, const ExPolygons &lslices) { + const std::vector colors = {"blue", "cyan", "red", "orange", "magenta", "pink", "purple", "yellow"}; + const coordf_t stroke_width = scaled(0.05); + const BoundingBox bbox = get_extents(lslices); + + ::Slic3r::SVG svg(path.c_str(), bbox); + svg.draw_outline(lslices, "green", "lime", stroke_width); + + for (const ExPolygons &by_extruder : regions) { + if (const size_t extrude_idx = &by_extruder - ®ions.front(); extrude_idx < colors.size()) { + svg.draw(by_extruder, colors[extrude_idx]); + } else { + svg.draw(by_extruder, "black"); + } + } +} + +[[maybe_unused]] void export_processed_input_expolygons_to_svg(const std::string &path, const LayerRegionPtrs ®ions, const ExPolygons &processed_input_expolygons) { + const coordf_t stroke_width = scaled(0.05); + BoundingBox bbox = get_extents(regions); + bbox.merge(get_extents(processed_input_expolygons)); + + ::Slic3r::SVG svg(path.c_str(), bbox); + + for (LayerRegion *region : regions) { + for (const Surface &surface : region->slices()) { + svg.draw_outline(surface, "blue", "cyan", stroke_width); + } + } + + svg.draw_outline(processed_input_expolygons, "red", "pink", stroke_width); +} + +[[maybe_unused]] static void export_color_polygons_points_to_svg(const std::string &path, const std::vector &color_polygons_points, const ExPolygons &lslices) { + const std::vector colors = {"aqua", "black", "blue", "fuchsia", "gray", "green", "lime", "maroon", "navy", "olive", "purple", "red", "silver", "teal", "yellow"}; + const coordf_t stroke_width = scaled(0.02); + const BoundingBox bbox = get_extents(lslices); + + ::Slic3r::SVG svg(path.c_str(), bbox); + + for (const ColorPoints &color_polygon_points : color_polygons_points) { + for (size_t pt_idx = 1; pt_idx < color_polygon_points.size(); ++pt_idx) { + const ColorPoint &prev_color_pt = color_polygon_points[pt_idx - 1]; + const ColorPoint &curr_color_pt = color_polygon_points[pt_idx]; + svg.draw(Line(prev_color_pt.p, curr_color_pt.p), colors[prev_color_pt.color_next]); + } + + svg.draw(Line(color_polygon_points.back().p, color_polygon_points.front().p), colors[color_polygon_points.back().color_next], stroke_width); + } +} + +template +inline OutputIterator douglas_peucker(ColorPoints::const_iterator begin, ColorPoints::const_iterator end, OutputIterator out, const double tolerance, const double max_different_color_length) { + const int64_t tolerance_sq = static_cast(sqr(tolerance)); + const double max_different_color_length_sq = sqr(max_different_color_length); + + auto point_getter = [](const ColorPoint &color_point) -> Point { + return color_point.p; + }; + + auto take_floater_predicate = [&tolerance_sq, &max_different_color_length_sq](ColorPoints::const_iterator anchor_it, ColorPoints::const_iterator floater_it, const int64_t max_dist_sq) -> bool { + // We allow removing points between the anchor and the floater only when the color after the anchor is the same as the color before the floater. + if (max_dist_sq > tolerance_sq || anchor_it->color_next != floater_it->color_prev) + return false; + + const uint8_t anchor_color = anchor_it->color_next; + double different_color_length_sq = 0.; + std::optional color_point_prev; + for (auto cp_it = std::next(anchor_it); cp_it != floater_it; ++cp_it) { + if (cp_it->color_next == anchor_color) { + if (!color_point_prev.has_value()) + continue; + + different_color_length_sq += (cp_it->p - color_point_prev->p).cast().squaredNorm(); + color_point_prev.reset(); + } else if (color_point_prev.has_value()) { + different_color_length_sq += (cp_it->p - color_point_prev->p).cast().squaredNorm(); + color_point_prev = *cp_it; + } else { + assert(!color_point_prev.has_value()); + different_color_length_sq = 0.; + color_point_prev = *cp_it; + } + + if (different_color_length_sq > max_different_color_length_sq) + return false; + } + + return true; + }; + + return douglas_peucker(begin, end, out, take_floater_predicate, point_getter); +} + +BoundingBox get_extents(const std::vector &colored_polygons) { + BoundingBox bbox; + for (const ColoredLines &colored_lines : colored_polygons) { + for (const ColoredLine &colored_line : colored_lines) { + bbox.merge(colored_line.line.a); + bbox.merge(colored_line.line.b); + } + } + return bbox; +} + +// Flatten the vector of vectors into a vector. +static inline ColoredLines to_lines(const std::vector &c_lines) { + size_t n_lines = 0; + for (const auto &c_line : c_lines) { + n_lines += c_line.size(); + } + + ColoredLines lines; + lines.reserve(n_lines); + for (const auto &c_line : c_lines) { + lines.insert(lines.end(), c_line.begin(), c_line.end()); + } + + return lines; +} + +// Determines if the line points from the point between two contour lines is pointing inside polygon or outside. +static inline bool points_inside(const Line &contour_first, const Line &contour_second, const Point &new_point) { + // Used in points_inside for decision if line leading thought the common point of two lines is pointing inside polygon or outside + auto three_points_inward_normal = [](const Point &left, const Point &middle, const Point &right) -> Vec2d { + assert(left != middle); + assert(middle != right); + return (perp(Point(middle - left)).cast().normalized() + perp(Point(right - middle)).cast().normalized()).normalized(); + }; + + assert(contour_first.b == contour_second.a); + Vec2d inward_normal = three_points_inward_normal(contour_first.a, contour_first.b, contour_second.b); + Vec2d edge_norm = (new_point - contour_first.b).cast().normalized(); + double side = inward_normal.dot(edge_norm); + // assert(side != 0.); + return side > 0.; +} static size_t non_deleted_edge_count(const VD::vertex_type &vertex) { size_t non_deleted_edge_cnt = 0; @@ -795,12 +495,9 @@ static std::vector extract_colored_segments(const std::vector segmented_expolygons_per_extruder(num_extruders + 1); @@ -854,7 +551,7 @@ static void cut_segmented_layers(const std::vector &input_exp const float interlocking_depth, const std::function &throw_on_cancel_callback) { - BOOST_LOG_TRIVIAL(debug) << "MM segmentation - cutting segmented layers in parallel - begin"; + BOOST_LOG_TRIVIAL(debug) << "MM segmentation - Cutting segmented layers in parallel - Begin"; const float interlocking_cut_width = interlocking_depth > 0.f ? std::max(cut_width - interlocking_depth, 0.f) : 0.f; tbb::parallel_for(tbb::blocked_range(0, segmented_regions.size()),[&segmented_regions, &input_expolygons, &cut_width, &interlocking_cut_width, &throw_on_cancel_callback](const tbb::blocked_range& range) { for (size_t layer_idx = range.begin(); layer_idx < range.end(); ++layer_idx) { @@ -870,14 +567,16 @@ static void cut_segmented_layers(const std::vector &input_exp } } }); // end of parallel_for - BOOST_LOG_TRIVIAL(debug) << "MM segmentation - cutting segmented layers in parallel - end"; + BOOST_LOG_TRIVIAL(debug) << "MM segmentation - Cutting segmented layers in parallel - End"; } -static bool is_volume_sinking(const indexed_triangle_set &its, const Transform3d &trafo) -{ +static bool is_volume_sinking(const indexed_triangle_set &its, const Transform3d &trafo) { const Transform3f trafo_f = trafo.cast(); - for (const stl_vertex &vertex : its.vertices) - if ((trafo_f * vertex).z() < SINKING_Z_THRESHOLD) return true; + for (const stl_vertex &vertex : its.vertices) { + if ((trafo_f * vertex).z() < SINKING_Z_THRESHOLD) + return true; + } + return false; } @@ -899,6 +598,7 @@ static inline std::vector> mm_segmentation_top_and_botto const std::vector &input_expolygons, const std::function &throw_on_cancel_callback) { + BOOST_LOG_TRIVIAL(debug) << "MM segmentation - Segmentation of top and bottom layers in parallel - Begin"; const size_t num_extruders = print_object.print()->config().nozzle_diameter.size() + 1; const size_t num_layers = input_expolygons.size(); const SpanOfConstPtrs layers = print_object.layers(); @@ -920,22 +620,17 @@ static inline std::vector> mm_segmentation_top_and_botto std::vector zs = zs_from_layers(layers); Transform3d object_trafo = print_object.trafo_centered(); -#ifdef MM_SEGMENTATION_DEBUG_TOP_BOTTOM - static int iRun = 0; -#endif // MM_SEGMENTATION_DEBUG_TOP_BOTTOM - if (max_top_layers > 0 || max_bottom_layers > 0) { for (const ModelVolume *mv : print_object.model_object()->volumes) if (mv->is_model_part()) { const Transform3d volume_trafo = object_trafo * mv->get_matrix(); for (size_t extruder_idx = 0; extruder_idx < num_extruders; ++ extruder_idx) { const indexed_triangle_set painted = mv->mm_segmentation_facets.get_facets_strict(*mv, TriangleStateType(extruder_idx)); -#ifdef MM_SEGMENTATION_DEBUG_TOP_BOTTOM - { - static int iRun = 0; - its_write_obj(painted, debug_out_path("mm-painted-patch-%d-%d.obj", iRun ++, extruder_idx).c_str()); + + if constexpr (MM_SEGMENTATION_DEBUG_TOP_BOTTOM) { + its_write_obj(painted, debug_out_path("mm-painted-patch-%d.obj", extruder_idx).c_str()); } -#endif // MM_SEGMENTATION_DEBUG_TOP_BOTTOM + if (! painted.indices.empty()) { std::vector top, bottom; if (!zs.empty() && is_volume_sinking(painted, volume_trafo)) { @@ -993,8 +688,8 @@ static inline std::vector> mm_segmentation_top_and_botto }; // Filter out polygons less than 0.1mm^2, because they are unprintable and causing dimples on outer primers (#7104) - filter_out_small_polygons(top_raw, Slic3r::sqr(scale_(0.1f))); - filter_out_small_polygons(bottom_raw, Slic3r::sqr(scale_(0.1f))); + filter_out_small_polygons(top_raw, Slic3r::sqr(POLYGON_FILTER_MIN_AREA_SCALED)); + filter_out_small_polygons(bottom_raw, Slic3r::sqr(POLYGON_FILTER_MIN_AREA_SCALED)); // Remove top and bottom surfaces that are covered by the previous or next sliced layer. for (size_t extruder_idx = 0; extruder_idx < num_extruders; ++extruder_idx) { @@ -1012,29 +707,29 @@ static inline std::vector> mm_segmentation_top_and_botto } } -#ifdef MM_SEGMENTATION_DEBUG_TOP_BOTTOM - { - const char* colors[] = { "aqua", "black", "blue", "fuchsia", "gray", "green", "lime", "maroon", "navy", "olive", "purple", "red", "silver", "teal", "yellow" }; - static int iRun = 0; + if constexpr (MM_SEGMENTATION_DEBUG_TOP_BOTTOM) { + const std::vector colors = {"aqua", "black", "blue", "fuchsia", "gray", "green", "lime", "maroon", "navy", "olive", "purple", "red", "silver", "teal", "yellow"}; for (size_t layer_id = 0; layer_id < zs.size(); ++layer_id) { std::vector> svg; - for (size_t extruder_idx = 0; extruder_idx < num_extruders; ++ extruder_idx) { - if (! top_raw[extruder_idx].empty() && ! top_raw[extruder_idx][layer_id].empty()) - if (ExPolygons expoly = union_ex(top_raw[extruder_idx][layer_id]); ! expoly.empty()) { - const char *color = colors[extruder_idx]; - svg.emplace_back(expoly, SVG::ExPolygonAttributes{ format("top%d", extruder_idx), color, color, color }); + for (size_t extruder_idx = 0; extruder_idx < num_extruders; ++extruder_idx) { + if (!top_raw[extruder_idx].empty() && !top_raw[extruder_idx][layer_id].empty()) { + if (ExPolygons expoly = union_ex(top_raw[extruder_idx][layer_id]); !expoly.empty()) { + const std::string &color = colors[extruder_idx]; + svg.emplace_back(expoly, SVG::ExPolygonAttributes{format("top%d", extruder_idx), color, color, color}); } - if (! bottom_raw[extruder_idx].empty() && ! bottom_raw[extruder_idx][layer_id].empty()) - if (ExPolygons expoly = union_ex(bottom_raw[extruder_idx][layer_id]); ! expoly.empty()) { - const char *color = colors[extruder_idx + 8]; - svg.emplace_back(expoly, SVG::ExPolygonAttributes{ format("bottom%d", extruder_idx), color, color, color }); + } + + if (!bottom_raw[extruder_idx].empty() && !bottom_raw[extruder_idx][layer_id].empty()) { + if (ExPolygons expoly = union_ex(bottom_raw[extruder_idx][layer_id]); !expoly.empty()) { + const std::string &color = colors[extruder_idx + 8]; + svg.emplace_back(expoly, SVG::ExPolygonAttributes{format("bottom%d", extruder_idx), color, color, color}); } + } } - SVG::export_expolygons(debug_out_path("mm-segmentation-top-bottom-%d-%d-%lf.svg", iRun, layer_id, zs[layer_id]), svg); + + SVG::export_expolygons(debug_out_path("mm-segmentation-top-bottom-%d-%lf.svg", layer_id, zs[layer_id]), svg); } - ++ iRun; } -#endif // MM_SEGMENTATION_DEBUG_TOP_BOTTOM std::vector> triangles_by_color_bottom(num_extruders); std::vector> triangles_by_color_top(num_extruders); @@ -1167,6 +862,7 @@ static inline std::vector> mm_segmentation_top_and_botto triangles_by_color_merged[color_idx - 1][layer_idx]); } }); + BOOST_LOG_TRIVIAL(debug) << "MM segmentation - Segmentation of top and bottom layers in parallel - End"; return triangles_by_color_merged; } @@ -1182,7 +878,7 @@ static std::vector> merge_segmented_layers( segmented_regions_merged.assign(num_layers, std::vector(num_extruders)); assert(num_extruders + 1 == top_and_bottom_layers.size()); - BOOST_LOG_TRIVIAL(debug) << "MM segmentation - merging segmented layers in parallel - begin"; + BOOST_LOG_TRIVIAL(debug) << "MM segmentation - Merging segmented layers in parallel - Begin"; tbb::parallel_for(tbb::blocked_range(0, num_layers), [&segmented_regions, &top_and_bottom_layers, &segmented_regions_merged, &num_extruders, &throw_on_cancel_callback](const tbb::blocked_range &range) { for (size_t layer_idx = range.begin(); layer_idx < range.end(); ++layer_idx) { assert(segmented_regions[layer_idx].size() == num_extruders + 1); @@ -1209,127 +905,470 @@ static std::vector> merge_segmented_layers( } } }); // end of parallel_for - BOOST_LOG_TRIVIAL(debug) << "MM segmentation - merging segmented layers in parallel - end"; + BOOST_LOG_TRIVIAL(debug) << "MM segmentation - Merging segmented layers in parallel - End"; return segmented_regions_merged; } -#ifdef MM_SEGMENTATION_DEBUG_REGIONS -static void export_regions_to_svg(const std::string &path, const std::vector ®ions, const ExPolygons &lslices) -{ - const std::vector colors = {"blue", "cyan", "red", "orange", "magenta", "pink", "purple", "yellow"}; - coordf_t stroke_width = scale_(0.05); - BoundingBox bbox = get_extents(lslices); - bbox.offset(scale_(1.)); - ::Slic3r::SVG svg(path.c_str(), bbox); - - svg.draw_outline(lslices, "green", "lime", stroke_width); - for (const ExPolygons &by_extruder : regions) { - size_t extrude_idx = &by_extruder - ®ions.front(); - if (extrude_idx < int(colors.size())) - svg.draw(by_extruder, colors[extrude_idx]); - else - svg.draw(by_extruder, "black"); - } -} -#endif // MM_SEGMENTATION_DEBUG_REGIONS - -#ifdef MM_SEGMENTATION_DEBUG_INPUT -void export_processed_input_expolygons_to_svg(const std::string &path, const LayerRegionPtrs ®ions, const ExPolygons &processed_input_expolygons) -{ - coordf_t stroke_width = scale_(0.05); - BoundingBox bbox = get_extents(regions); - bbox.merge(get_extents(processed_input_expolygons)); - bbox.offset(scale_(1.)); - ::Slic3r::SVG svg(path.c_str(), bbox); - - for (LayerRegion *region : regions) - for (const Surface &surface : region->slices()) - svg.draw_outline(surface, "blue", "cyan", stroke_width); - - svg.draw_outline(processed_input_expolygons, "red", "pink", stroke_width); -} -#endif // MM_SEGMENTATION_DEBUG_INPUT - -#ifdef MM_SEGMENTATION_DEBUG_PAINTED_LINES -static void export_painted_lines_to_svg(const std::string &path, const std::vector> &all_painted_lines, const ExPolygons &lslices) -{ - const std::vector colors = {"blue", "cyan", "red", "orange", "magenta", "pink", "purple", "yellow"}; - coordf_t stroke_width = scale_(0.05); - BoundingBox bbox = get_extents(lslices); - bbox.offset(scale_(1.)); - ::Slic3r::SVG svg(path.c_str(), bbox); - - for (const Line &line : to_lines(lslices)) - svg.draw(line, "green", stroke_width); - - for (const std::vector &painted_lines : all_painted_lines) - for (const PaintedLine &painted_line : painted_lines) - svg.draw(painted_line.projected_line, painted_line.color < int(colors.size()) ? colors[painted_line.color] : "black", stroke_width); -} -#endif // MM_SEGMENTATION_DEBUG_PAINTED_LINES - -#ifdef MM_SEGMENTATION_DEBUG_COLORIZED_POLYGONS -static void export_colorized_polygons_to_svg(const std::string &path, const std::vector &colorized_polygons, const ExPolygons &lslices) -{ - const std::vector colors = {"blue", "cyan", "red", "orange", "magenta", "pink", "purple", "green", "yellow"}; - coordf_t stroke_width = scale_(0.05); - BoundingBox bbox = get_extents(lslices); - bbox.offset(scale_(1.)); - ::Slic3r::SVG svg(path.c_str(), bbox); - - for (const ColoredLines &colorized_polygon : colorized_polygons) - for (const ColoredLine &colorized_line : colorized_polygon) - svg.draw(colorized_line.line, colorized_line.color < int(colors.size())? colors[colorized_line.color] : "black", stroke_width); -} -#endif // MM_SEGMENTATION_DEBUG_COLORIZED_POLYGONS - // Check if all ColoredLine representing a single layer uses the same color. -static bool has_layer_only_one_color(const std::vector &colored_polygons) -{ +static bool has_layer_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; - for (const ColoredLines &colored_polygon : colored_polygons) - for (const ColoredLine &colored_line : colored_polygon) + for (const ColoredLines &colored_polygon : colored_polygons) { + for (const ColoredLine &colored_line : colored_polygon) { if (first_line_color != colored_line.color) return false; + } + } return true; } +BoundingBox get_extents(const ColorPolygon &c_poly) { + return c_poly.bounding_box(); +} + +BoundingBox get_extents(const ColorPolygons &c_polygons) { + BoundingBox bb; + if (!c_polygons.empty()) { + bb = get_extents(c_polygons.front()); + for (size_t i = 1; i < c_polygons.size(); ++i) { + bb.merge(get_extents(c_polygons[i])); + } + } + + return bb; +} + +// Filter out small ColorPolygons based on minimum area and by applying polygon offset. +bool filter_out_small_color_polygons(ColorPolygons &color_polygons, const double filter_min_area, const float filter_offset) { + assert(filter_offset >= 0.); + + bool modified = false; + size_t first_free_idx = 0; + + for (ColorPolygon &color_polygon : color_polygons) { + if (std::abs(color_polygon.area()) >= filter_min_area && (filter_offset <= 0. || !offset(Polygon(color_polygon.points), filter_offset).empty())) { + if (const size_t color_polygon_idx = &color_polygon - color_polygons.data(); first_free_idx < color_polygon_idx) { + std::swap(color_polygon.points, color_polygons[first_free_idx].points); + std::swap(color_polygon.colors, color_polygons[first_free_idx].colors); + } + + ++first_free_idx; + } else { + modified = true; + } + } + + if (first_free_idx < color_polygons.size()) { + color_polygons.erase(color_polygons.begin() + int(first_free_idx), color_polygons.end()); + } + + return modified; +} + +ColorPoints color_polygon_to_color_points(const ColorPolygon &color_polygon) { + assert(!color_polygon.empty()); + assert(color_polygon.points.size() == color_polygon.colors.size()); + + ColorPoints color_points_out; + color_points_out.reserve(color_polygon.size()); + + for (const Point &pt : color_polygon.points) { + const size_t pt_idx = &pt - color_polygon.points.data(); + const uint8_t color_prev = (pt_idx == 0) ? color_polygon.colors.back() : color_polygon.colors[pt_idx - 1]; + const uint8_t color_next = color_polygon.colors[pt_idx]; + + color_points_out.emplace_back(pt, color_prev, color_next); + } + + return color_points_out; +} + +std::vector color_polygons_to_color_points(const ColorPolygons &color_polygons) { + std::vector color_polygons_points_out; + color_polygons_points_out.reserve(color_polygons.size()); + + for (const ColorPolygon &color_polygon : color_polygons) + color_polygons_points_out.emplace_back(color_polygon_to_color_points(color_polygon)); + + return color_polygons_points_out; +} + +std::vector color_points_to_colored_lines(const std::vector &color_polygons_points) { + std::vector colored_lines_vec_out(color_polygons_points.size()); + + for (const ColorPoints &color_polygon_points : color_polygons_points) { + const size_t color_polygon_idx = &color_polygon_points - color_polygons_points.data(); + ColoredLines &colored_lines = colored_lines_vec_out[color_polygon_idx]; + colored_lines.reserve(color_polygon_points.size()); + + for (size_t cpt_idx = 0; cpt_idx < color_polygon_points.size() - 1; ++cpt_idx) { + const ColorPoint &curr_color_point = color_polygon_points[cpt_idx]; + const ColorPoint &next_color_point = color_polygon_points[cpt_idx + 1]; + colored_lines.push_back({Line(curr_color_point.p, next_color_point.p), curr_color_point.color_next, int(color_polygon_idx), int(cpt_idx)}); + } + + colored_lines.push_back({Line(color_polygon_points.back().p, color_polygon_points.front().p), color_polygon_points.back().color_next, int(color_polygon_idx), int(color_polygon_points.size() - 1)}); + } + + return colored_lines_vec_out; +} + +static std::vector get_print_object_layers_zs(const SpanOfConstPtrs &layers) { + std::vector layers_zs; + layers_zs.reserve(layers.size()); + + for (const Layer *layer : layers) { + layers_zs.emplace_back(static_cast(layer->slice_z)); + } + + return layers_zs; +} + +void static filter_color_of_small_segments(ColorPoints &color_polygon_points, const double max_different_color_length) { + struct ColorSegment + { + explicit ColorSegment(size_t color_pt_begin_idx, size_t color_pt_end_idx, uint8_t color, double length) + : color_pt_begin_idx(color_pt_begin_idx), color_pt_end_idx(color_pt_end_idx), color(color), length(length) {} + + size_t color_pt_begin_idx = 0; + size_t color_pt_end_idx = 0; + uint8_t color = 0; + double length = 0.; + }; + + auto pt_length = [](const ColorPoint &color_pt_a, const ColorPoint &color_pt_b) -> double { + return (color_pt_b.p.cast() - color_pt_a.p.cast()).norm(); + }; + + std::vector color_segments; + color_segments.emplace_back(0, 0, color_polygon_points.front().color_next, 0.); + + for (size_t color_pt_idx = 1; color_pt_idx < color_polygon_points.size(); ++color_pt_idx) { + const ColorPoint &prev_color_pt = color_polygon_points[color_pt_idx - 1]; + const ColorPoint &curr_color_pt = color_polygon_points[color_pt_idx]; + + ColorSegment &last_color_segment = color_segments.back(); + + if (last_color_segment.color == curr_color_pt.color_next) { + last_color_segment.color_pt_end_idx = color_pt_idx; + last_color_segment.length += pt_length(prev_color_pt, curr_color_pt); + } else { + last_color_segment.color_pt_end_idx = color_pt_idx; + last_color_segment.length += pt_length(prev_color_pt, curr_color_pt); + color_segments.emplace_back(color_pt_idx, color_pt_idx, curr_color_pt.color_next, 0.); + } + } + + ColorSegment &last_color_segment = color_segments.back(); + last_color_segment.color_pt_end_idx = 0; + last_color_segment.length += pt_length(color_polygon_points.back(), color_polygon_points.front()); + + if (color_segments.size() > 2 && color_segments.front().color == last_color_segment.color) { + color_segments.front().color_pt_begin_idx = last_color_segment.color_pt_begin_idx; + color_segments.front().length += last_color_segment.length; + color_segments.pop_back(); + } + + auto next_segment_idx = [&color_segments](const size_t curr_segment_idx) -> size_t { + return curr_segment_idx < (color_segments.size() - 1) ? curr_segment_idx + 1 : 0; + }; + + for (size_t from_segment_idx = 0; from_segment_idx < color_segments.size();) { + size_t to_segment_idx = next_segment_idx(from_segment_idx); + double total_diff_color_length = 0.; + + bool update_color = false; + while (from_segment_idx != to_segment_idx) { + if (total_diff_color_length > max_different_color_length) { + break; + } else if (color_segments[from_segment_idx].color == color_segments[to_segment_idx].color) { + update_color = true; + break; + } + + total_diff_color_length += color_segments[to_segment_idx].length; + to_segment_idx = next_segment_idx(to_segment_idx); + } + + if (!update_color) { + ++from_segment_idx; + continue; + } + + const uint8_t new_color = color_segments[from_segment_idx].color; + for (size_t curr_segment_idx = next_segment_idx(from_segment_idx); curr_segment_idx != to_segment_idx; curr_segment_idx = next_segment_idx(curr_segment_idx)) { + for (size_t pt_idx = color_segments[curr_segment_idx].color_pt_begin_idx; pt_idx != color_segments[curr_segment_idx].color_pt_end_idx; pt_idx = (pt_idx < color_polygon_points.size() - 1) ? pt_idx + 1 : 0) { + color_polygon_points[pt_idx].color_prev = new_color; + color_polygon_points[pt_idx].color_next = new_color; + } + + color_polygon_points[color_segments[curr_segment_idx].color_pt_end_idx].color_prev = new_color; + color_polygon_points[color_segments[curr_segment_idx].color_pt_end_idx].color_next = new_color; + + color_segments[curr_segment_idx].color = new_color; + } + + if (from_segment_idx < to_segment_idx) { + from_segment_idx = to_segment_idx; + } else { + // We already processed all segments. + break; + } + } +} + +[[maybe_unused]] static bool is_valid_color_polygon_points(const ColorPoints &color_polygon_points) { + for (size_t pt_idx = 1; pt_idx < color_polygon_points.size(); ++pt_idx) { + const ColorPoint &prev_color_pt = color_polygon_points[pt_idx - 1]; + const ColorPoint &curr_color_pt = color_polygon_points[pt_idx]; + + if (prev_color_pt.color_next != curr_color_pt.color_prev) + return false; + } + + if (color_polygon_points.back().color_next != color_polygon_points.front().color_prev) + return false; + + return true; +} + +static std::vector create_color_projection_lines(const ExPolygon &ex_polygon) { + std::vector color_projection_lines(ex_polygon.num_contours()); + + 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); + } + } + + 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)); + + for (const ExPolygon &ex_polygon : ex_polygons) { + Slic3r::append(color_projection_lines, create_color_projection_lines(ex_polygon)); + } + + return color_projection_lines; +} + +// 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(); + }); + }; + + std::vector color_projection_lines_mapping; + color_projection_lines_mapping.reserve(total_lines_count()); + + for (ColorProjectionLines &color_polygon_projection_lines : color_polygons_projection_lines) { + for (ColorProjectionLine &color_projection_line : color_polygon_projection_lines) { + color_projection_lines_mapping.emplace_back(&color_projection_line); + } + } + + return color_projection_lines_mapping; +} + +// Return the color of the first part of the first line of the polygon (after projection). +static uint8_t get_color_of_first_polygon_line(const ColorProjectionLines &color_polygon_projection_lines) { + assert(!color_polygon_projection_lines.empty()); + + if (color_polygon_projection_lines.empty()) { + return 0; + } else if (const ColorProjectionLine &first_line = color_polygon_projection_lines.front(); !first_line.color_changes.empty() && first_line.color_changes.front().t == 0.f) { + return first_line.color_changes.front().color_next; + } + + auto last_projection_line_it = std::find_if(color_polygon_projection_lines.rbegin(), color_polygon_projection_lines.rend(), [](const ColorProjectionLine &line) { + return !line.color_changes.empty(); + }); + + assert(last_projection_line_it != color_polygon_projection_lines.rend()); + if (last_projection_line_it == color_polygon_projection_lines.rend()) { + // There is no projected color on this whole polygon. + return 0; + } else { + return last_projection_line_it->color_changes.back().color_next; + } +} + +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; + + 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(); + + 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 (snap_candidates.size() > 1) { + 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; + } + } + + 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; + } + } + + while (snap_candidates.size() > 1) { + snap_candidates.pop_back(); + color_line.color_changes.pop_back(); + } + + if (!snap_candidates.empty()) { + assert(snap_candidates.size() == 1); + snap_candidates.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 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()); + + 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()); + + 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; + } + + 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; + } + } + } + + 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; +} + std::vector> multi_material_segmentation_by_painting(const PrintObject &print_object, const std::function &throw_on_cancel_callback) { - const size_t num_extruders = print_object.print()->config().nozzle_diameter.size(); - const size_t num_layers = print_object.layers().size(); - std::vector> segmented_regions(num_layers); - segmented_regions.assign(num_layers, std::vector(num_extruders + 1)); - std::vector> painted_lines(num_layers); - std::array painted_lines_mutex; - std::vector edge_grids(num_layers); - const SpanOfConstPtrs layers = print_object.layers(); - std::vector input_expolygons(num_layers); + const size_t num_extruders = print_object.print()->config().nozzle_diameter.size(); + const size_t num_layers = print_object.layers().size(); + const SpanOfConstPtrs layers = print_object.layers(); - throw_on_cancel_callback(); - -#ifdef MM_SEGMENTATION_DEBUG - static int iRun = 0; -#endif // MM_SEGMENTATION_DEBUG + std::vector input_expolygons(num_layers); + std::vector> input_polygons_projection_lines_layers(num_layers); // Merge all regions and remove small holes - BOOST_LOG_TRIVIAL(debug) << "MM segmentation - slices preparation in parallel - begin"; - tbb::parallel_for(tbb::blocked_range(0, num_layers), [&layers, &input_expolygons, &throw_on_cancel_callback](const tbb::blocked_range &range) { + BOOST_LOG_TRIVIAL(debug) << "MM 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) { for (size_t layer_idx = range.begin(); layer_idx < range.end(); ++layer_idx) { throw_on_cancel_callback(); + ExPolygons ex_polygons; - for (LayerRegion *region : layers[layer_idx]->regions()) - for (const Surface &surface : region->slices()) + for (LayerRegion *region : layers[layer_idx]->regions()) { + for (const Surface &surface : region->slices()) { Slic3r::append(ex_polygons, offset_ex(surface.expolygon, float(10 * SCALED_EPSILON))); + } + } + // All expolygons are expanded by SCALED_EPSILON, merged, and then shrunk again by SCALED_EPSILON // to ensure that very close polygons will be merged. ex_polygons = union_ex(ex_polygons); // Remove all expolygons and holes with an area less than 0.1mm^2 - remove_small_and_small_holes(ex_polygons, Slic3r::sqr(scale_(0.1f))); + remove_small_and_small_holes(ex_polygons, Slic3r::sqr(POLYGON_FILTER_MIN_AREA_SCALED)); // Occasionally, some input polygons contained self-intersections that caused problems with Voronoi diagrams // and consequently with the extraction of colored segments by function extract_colored_segments. // Calling simplify_polygons removes these self-intersections. @@ -1337,158 +1376,117 @@ std::vector> multi_material_segmentation_by_painting(con // 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_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]); -#ifdef MM_SEGMENTATION_DEBUG_INPUT - export_processed_input_expolygons_to_svg(debug_out_path("mm-input-%d-%d.svg", layer_idx, iRun), layers[layer_idx]->regions(), input_expolygons[layer_idx]); -#endif // MM_SEGMENTATION_DEBUG_INPUT + 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]); + } } }); // end of parallel_for - BOOST_LOG_TRIVIAL(debug) << "MM segmentation - slices preparation in parallel - end"; - std::vector layer_bboxes(num_layers); - for (size_t layer_idx = 0; layer_idx < num_layers; ++layer_idx) { - throw_on_cancel_callback(); - layer_bboxes[layer_idx] = get_extents(layers[layer_idx]->regions()); - layer_bboxes[layer_idx].merge(get_extents(input_expolygons[layer_idx])); - } + std::vector> color_projection_lines_distancer_layers(num_layers); - for (size_t layer_idx = 0; layer_idx < num_layers; ++layer_idx) { - throw_on_cancel_callback(); - BoundingBox bbox = layer_bboxes[layer_idx]; - // Projected triangles could, in rare cases (as in GH issue #7299), belongs to polygons printed in the previous or the next layer. - // Let's merge the bounding box of the current layer with bounding boxes of the previous and the next layer to ensure that - // every projected triangle will be inside the resulting bounding box. - if (layer_idx > 1) bbox.merge(layer_bboxes[layer_idx - 1]); - if (layer_idx < num_layers - 1) bbox.merge(layer_bboxes[layer_idx + 1]); - // Projected triangles may slightly exceed the input polygons. - bbox.offset(20 * SCALED_EPSILON); - edge_grids[layer_idx].set_bbox(bbox); - edge_grids[layer_idx].create(input_expolygons[layer_idx], coord_t(scale_(10.))); - } + tbb::parallel_for(tbb::blocked_range(0, num_layers), [&input_polygons_projection_lines_layers, &color_projection_lines_distancer_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(); - BOOST_LOG_TRIVIAL(debug) << "MM segmentation - projection of painted triangles - begin"; + color_projection_lines_distancer_layers[layer_idx] = AABBTreeLines::LinesDistancer{create_color_projection_lines_mapping(input_polygons_projection_lines_layers[layer_idx])}; + } + }); // end of parallel_for + BOOST_LOG_TRIVIAL(debug) << "MM segmentation - Slices preprocessing in parallel - End"; + + BOOST_LOG_TRIVIAL(debug) << "MM segmentation - Projection of painted triangles - Begin"; + const std::vector layer_zs = get_print_object_layers_zs(layers); for (const ModelVolume *mv : print_object.model_object()->volumes) { - tbb::parallel_for(tbb::blocked_range(1, num_extruders + 1), [&mv, &print_object, &layers, &edge_grids, &painted_lines, &painted_lines_mutex, &input_expolygons, &throw_on_cancel_callback](const tbb::blocked_range &range) { - for (size_t extruder_idx = range.begin(); extruder_idx < range.end(); ++extruder_idx) { + const indexed_triangle_set_with_color mesh_with_color = mv->mm_segmentation_facets.get_all_facets_strict_with_colors(*mv); + const Transform3d trafo = print_object.trafo_centered() * mv->get_matrix(); + const MeshSlicingParams slicing_params{trafo}; + + std::vector color_polygons_per_layer = slice_mesh(mesh_with_color, layer_zs, slicing_params); + + // Project sliced ColorPolygons on sliced layers (input_expolygons). + tbb::parallel_for(tbb::blocked_range(0, num_layers), [&color_projection_lines_distancer_layers, &color_polygons_per_layer, &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(); - const indexed_triangle_set custom_facets = mv->mm_segmentation_facets.get_facets(*mv, TriangleStateType(extruder_idx)); - if (!mv->is_model_part() || custom_facets.indices.empty()) + + const AABBTreeLines::LinesDistancer &color_projection_lines_distancer = color_projection_lines_distancer_layers[layer_idx]; + ColorPolygons &color_polygons = color_polygons_per_layer[layer_idx]; + + filter_out_small_color_polygons(color_polygons, POLYGON_FILTER_MIN_AREA_SCALED, POLYGON_FILTER_MIN_OFFSET_SCALED); + + if (color_polygons.empty()) continue; - const Transform3f tr = print_object.trafo().cast() * mv->get_matrix().cast(); - tbb::parallel_for(tbb::blocked_range(0, custom_facets.indices.size()), [&tr, &custom_facets, &print_object, &layers, &edge_grids, &input_expolygons, &painted_lines, &painted_lines_mutex, &extruder_idx](const tbb::blocked_range &range) { - for (size_t facet_idx = range.begin(); facet_idx < range.end(); ++facet_idx) { - float min_z = std::numeric_limits::max(); - float max_z = std::numeric_limits::lowest(); + const std::vector color_polygons_points = color_polygons_to_color_points(color_polygons); + for (const ColorPoints &color_polygon_points : color_polygons_points) { + ColorPoints color_polygon_points_filtered; + color_polygon_points_filtered.reserve(color_polygon_points.size()); - std::array facet; - for (int p_idx = 0; p_idx < 3; ++p_idx) { - facet[p_idx] = tr * custom_facets.vertices[custom_facets.indices[facet_idx](p_idx)]; - max_z = std::max(max_z, facet[p_idx].z()); - min_z = std::min(min_z, facet[p_idx].z()); - } + douglas_peucker(color_polygon_points.begin(), color_polygon_points.end(), std::back_inserter(color_polygon_points_filtered), POLYGON_COLOR_FILTER_TOLERANCE_SCALED, POLYGON_COLOR_FILTER_DISTANCE_SCALED); - // Sort the vertices by z-axis for simplification of projected_facet on slices - std::sort(facet.begin(), facet.end(), [](const Vec3f &p1, const Vec3f &p2) { return p1.z() < p2.z(); }); + if (color_polygon_points_filtered.size() < 3) + continue; - // Find lowest slice not below the triangle. - auto first_layer = std::upper_bound(layers.begin(), layers.end(), float(min_z - EPSILON), - [](float z, const Layer *l1) { return z < l1->slice_z; }); - auto last_layer = std::upper_bound(layers.begin(), layers.end(), float(max_z + EPSILON), - [](float z, const Layer *l1) { return z < l1->slice_z; }); - --last_layer; + filter_color_of_small_segments(color_polygon_points_filtered, POLYGON_COLOR_FILTER_DISTANCE_SCALED); + assert(is_valid_color_polygon_points(color_polygon_points_filtered)); - for (auto layer_it = first_layer; layer_it != (last_layer + 1); ++layer_it) { - const Layer *layer = *layer_it; - size_t layer_idx = layer_it - layers.begin(); - if (input_expolygons[layer_idx].empty() || facet[0].z() > layer->slice_z || layer->slice_z > facet[2].z()) - continue; + for (const ColorPoint &color_pt : color_polygon_points_filtered) { + auto [proj_distance, nearest_line_index, nearest_point] = color_projection_lines_distancer.distance_from_lines_extra(color_pt.p); - // https://kandepet.com/3d-printing-slicing-3d-objects/ - float t = (float(layer->slice_z) - facet[0].z()) / (facet[2].z() - facet[0].z()); - Vec3f line_start_f = facet[0] + t * (facet[2] - facet[0]); - Vec3f line_end_f; + if (proj_distance > MM_SEGMENTATION_MAX_PROJECTION_DISTANCE_SCALED) + continue; - if (facet[1].z() > layer->slice_z) { - // [P0, P2] and [P0, P1] - float t1 = (float(layer->slice_z) - facet[0].z()) / (facet[1].z() - facet[0].z()); - line_end_f = facet[0] + t1 * (facet[1] - facet[0]); - } else { - // [P0, P2] and [P1, P2] - float t2 = (float(layer->slice_z) - facet[1].z()) / (facet[2].z() - facet[1].z()); - line_end_f = facet[1] + t2 * (facet[2] - facet[1]); - } + ColorProjectionLine &color_projection_line = *color_projection_lines_distancer.get_line(nearest_line_index).color_projection_line; + const double line_t_value = std::clamp((nearest_point - color_projection_line.a.cast()).norm() / (color_projection_line.b - color_projection_line.a).cast().norm(), 0., 1.); - Line line_to_test(Point(scale_(line_start_f.x()), scale_(line_start_f.y())), - Point(scale_(line_end_f.x()), scale_(line_end_f.y()))); - line_to_test.translate(-print_object.center_offset()); - - // BoundingBoxes for EdgeGrids are computed from printable regions. It is possible that the painted line (line_to_test) could - // be outside EdgeGrid's BoundingBox, for example, when the negative volume is used on the painted area (GH #7618). - // To ensure that the painted line is always inside EdgeGrid's BoundingBox, it is clipped by EdgeGrid's BoundingBox in cases - // when any of the endpoints of the line are outside the EdgeGrid's BoundingBox. - if (const BoundingBox &edge_grid_bbox = edge_grids[layer_idx].bbox(); !edge_grid_bbox.contains(line_to_test.a) || !edge_grid_bbox.contains(line_to_test.b)) { - // If the painted line (line_to_test) is entirely outside EdgeGrid's BoundingBox, skip this painted line. - if (!edge_grid_bbox.overlap(BoundingBox(Points{line_to_test.a, line_to_test.b})) || - !line_to_test.clip_with_bbox(edge_grid_bbox)) - continue; - } - - size_t mutex_idx = layer_idx & 0x3F; - assert(mutex_idx < painted_lines_mutex.size()); - - PaintedLineVisitor visitor(edge_grids[layer_idx], painted_lines[layer_idx], painted_lines_mutex[mutex_idx], 16); - visitor.line_to_test = line_to_test; - visitor.color = int(extruder_idx); - edge_grids[layer_idx].visit_cells_intersecting_line(line_to_test.a, line_to_test.b, visitor); - } + color_projection_line.color_changes.emplace_back(line_t_value, color_pt.color_next); } - }); // end of parallel_for + } } }); // end of parallel_for } - BOOST_LOG_TRIVIAL(debug) << "MM segmentation - projection of painted triangles - end"; - BOOST_LOG_TRIVIAL(debug) << "MM segmentation - painted layers count: " - << std::count_if(painted_lines.begin(), painted_lines.end(), [](const std::vector &pl) { return !pl.empty(); }); + BOOST_LOG_TRIVIAL(debug) << "MM segmentation - Projection of painted triangles - End"; - BOOST_LOG_TRIVIAL(debug) << "MM segmentation - layers segmentation in parallel - begin"; - tbb::parallel_for(tbb::blocked_range(0, num_layers), [&edge_grids, &input_expolygons, &painted_lines, &segmented_regions, &num_extruders, &throw_on_cancel_callback](const tbb::blocked_range &range) { + std::vector> segmented_regions(num_layers); + segmented_regions.assign(num_layers, std::vector(num_extruders + 1)); + + // 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) << "MM 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_extruders, &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(); - if (!painted_lines[layer_idx].empty()) { -#ifdef MM_SEGMENTATION_DEBUG_PAINTED_LINES - export_painted_lines_to_svg(debug_out_path("mm-painted-lines-%d-%d.svg", layer_idx, iRun), {painted_lines[layer_idx]}, input_expolygons[layer_idx]); -#endif // MM_SEGMENTATION_DEBUG_PAINTED_LINES - std::vector> post_processed_painted_lines = post_process_painted_lines(edge_grids[layer_idx].contours(), std::move(painted_lines[layer_idx])); + std::vector &input_polygons_projection_lines = input_polygons_projection_lines_layers[layer_idx]; + filter_projected_color_points_on_polygons(input_polygons_projection_lines); -#ifdef MM_SEGMENTATION_DEBUG_PAINTED_LINES - export_painted_lines_to_svg(debug_out_path("mm-painted-lines-post-processed-%d-%d.svg", layer_idx, iRun), post_processed_painted_lines, input_expolygons[layer_idx]); -#endif // MM_SEGMENTATION_DEBUG_PAINTED_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_poly = colorize_contours(edge_grids[layer_idx].contours(), post_processed_painted_lines); + 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]); + } -#ifdef MM_SEGMENTATION_DEBUG_COLORIZED_POLYGONS - export_colorized_polygons_to_svg(debug_out_path("mm-colorized_polygons-%d-%d.svg", layer_idx, iRun), color_poly, input_expolygons[layer_idx]); -#endif // MM_SEGMENTATION_DEBUG_COLORIZED_POLYGONS + 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_extruders, layer_idx); + } - assert(!color_poly.empty()); - assert(!color_poly.front().empty()); - if (has_layer_only_one_color(color_poly)) { - // If the whole layer is painted using the same color, it is not needed to construct a Voronoi diagram for the segmentation of this layer. - segmented_regions[layer_idx][size_t(color_poly.front().front().color)] = input_expolygons[layer_idx]; - } else { - segmented_regions[layer_idx] = extract_colored_segments(color_poly, num_extruders, layer_idx); - } - -#ifdef MM_SEGMENTATION_DEBUG_REGIONS - export_regions_to_svg(debug_out_path("mm-regions-sides-%d-%d.svg", layer_idx, iRun), segmented_regions[layer_idx], input_expolygons[layer_idx]); -#endif // MM_SEGMENTATION_DEBUG_REGIONS + if constexpr (MM_SEGMENTATION_DEBUG_REGIONS) { + export_regions_to_svg(debug_out_path("mm-regions-non-merged-%d.svg", layer_idx), segmented_regions[layer_idx], input_expolygons[layer_idx]); } } }); // end of parallel_for - BOOST_LOG_TRIVIAL(debug) << "MM segmentation - layers segmentation in parallel - end"; + BOOST_LOG_TRIVIAL(debug) << "MM segmentation - Layers segmentation in parallel - End"; + throw_on_cancel_callback(); + + // The first index is extruder number (includes default extruder), and the second one is layer number + std::vector> top_and_bottom_layers = mm_segmentation_top_and_bottom_layers(print_object, input_expolygons, throw_on_cancel_callback); throw_on_cancel_callback(); if (auto max_width = print_object.config().mmu_segmented_region_max_width, interlocking_depth = print_object.config().mmu_segmented_region_interlocking_depth; max_width > 0.f) { @@ -1496,23 +1494,16 @@ std::vector> multi_material_segmentation_by_painting(con throw_on_cancel_callback(); } - // The first index is extruder number (includes default extruder), and the second one is layer number - std::vector> top_and_bottom_layers = mm_segmentation_top_and_bottom_layers(print_object, input_expolygons, throw_on_cancel_callback); - throw_on_cancel_callback(); - std::vector> segmented_regions_merged = merge_segmented_layers(segmented_regions, std::move(top_and_bottom_layers), num_extruders, throw_on_cancel_callback); throw_on_cancel_callback(); -#ifdef MM_SEGMENTATION_DEBUG_REGIONS - for (size_t layer_idx = 0; layer_idx < print_object.layers().size(); ++layer_idx) - export_regions_to_svg(debug_out_path("mm-regions-merged-%d-%d.svg", layer_idx, iRun), segmented_regions_merged[layer_idx], input_expolygons[layer_idx]); -#endif // MM_SEGMENTATION_DEBUG_REGIONS - -#ifdef MM_SEGMENTATION_DEBUG - ++iRun; -#endif // MM_SEGMENTATION_DEBUG + if constexpr (MM_SEGMENTATION_DEBUG_REGIONS) { + for (size_t layer_idx = 0; layer_idx < print_object.layers().size(); ++layer_idx) { + export_regions_to_svg(debug_out_path("mm-regions-merged-%d.svg", layer_idx), segmented_regions_merged[layer_idx], input_expolygons[layer_idx]); + } + } return segmented_regions_merged; } -} // namespace Slic3r +} // namespace Slic3r \ No newline at end of file diff --git a/src/libslic3r/MultiMaterialSegmentation.hpp b/src/libslic3r/MultiMaterialSegmentation.hpp index 6a9c37cd47..8de0963af3 100644 --- a/src/libslic3r/MultiMaterialSegmentation.hpp +++ b/src/libslic3r/MultiMaterialSegmentation.hpp @@ -35,6 +35,8 @@ using ColoredLines = std::vector; // Returns MMU segmentation based on painting in MMU segmentation gizmo std::vector> multi_material_segmentation_by_painting(const PrintObject &print_object, const std::function &throw_on_cancel_callback); +BoundingBox get_extents(const std::vector &colored_polygons); + } // namespace Slic3r namespace boost::polygon {