diff --git a/src/libslic3r/CMakeLists.txt b/src/libslic3r/CMakeLists.txt index 2abe94656..e59d1efd8 100644 --- a/src/libslic3r/CMakeLists.txt +++ b/src/libslic3r/CMakeLists.txt @@ -128,6 +128,8 @@ add_library(libslic3r STATIC Model.hpp ModelArrange.hpp ModelArrange.cpp + MultiMaterialSegmentation.cpp + MultiMaterialSegmentation.hpp CustomGCode.cpp CustomGCode.hpp Arrange.hpp diff --git a/src/libslic3r/MultiMaterialSegmentation.cpp b/src/libslic3r/MultiMaterialSegmentation.cpp new file mode 100644 index 000000000..d4840a363 --- /dev/null +++ b/src/libslic3r/MultiMaterialSegmentation.cpp @@ -0,0 +1,1498 @@ +#include "BoundingBox.hpp" +#include "ClipperUtils.hpp" +#include "EdgeGrid.hpp" +#include "Layer.hpp" +#include "Print.hpp" +#include "VoronoiVisualUtils.hpp" + +#include +#include +#include + +#include + +#include + +#include +#include +#include +#include + +namespace Slic3r { +struct ColoredLine { + Line line; + int color; + int poly_idx = -1; + int local_line_idx = -1; +}; +} + +#include +namespace boost { namespace polygon { +template <> +struct geometry_concept { typedef segment_concept type; }; + +template <> +struct segment_traits { + typedef coord_t coordinate_type; + typedef Slic3r::Point point_type; + + static inline point_type get(const Slic3r::ColoredLine& line, direction_1d dir) { + return dir.to_int() ? line.line.b : line.line.a; + } +}; +} } + +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.cast() + t1 * v1).cast(); + Point p2 = (projection_l.a.cast() + 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 = 1; +}; + +struct PaintedLineVisitor +{ + PaintedLineVisitor(const EdgeGrid::Grid &grid, std::vector &painted_lines) : grid(grid), painted_lines(painted_lines) + { + painted_lines_set.reserve(painted_lines.capacity()); + } + + 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); + 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 v1 = (line_to_test.b - line_to_test.a).cast().normalized(); + const Vec2d v2 = (grid_line.b - grid_line.a).cast().normalized(); + double angle = ::acos(clamp(-1.0, 1.0, v1.dot(v2))); + double angle_deg = Geometry::rad2deg(angle); + // When lines have too different length, it is necessary to normalize them + if ((angle_deg >= 0 && angle_deg <= 30) || (angle_deg >= 150)) { + Line line_to_test_projected; + project_line_on_line(grid_line, line_to_test, &line_to_test_projected); + + if (painted_lines_set.find(*it_contour_and_segment) == painted_lines_set.end()) { + if (Line(grid_line.a, line_to_test_projected.a).length() > Line(grid_line.a, line_to_test_projected.b).length()) { + line_to_test_projected.reverse(); + } + + double dist_1 = grid_line.distance_to(line_to_test.a); + double dist_2 = grid_line.distance_to(line_to_test.b); + double dist_3 = line_to_test.distance_to(grid_line.a); + double dist_4 = line_to_test.distance_to(grid_line.b); + double total_dist = std::min(std::min(dist_1, dist_2), std::min(dist_3, dist_4)); + + if (total_dist > 50 * SCALED_EPSILON) + continue; + + painted_lines.push_back({it_contour_and_segment->first, it_contour_and_segment->second, line_to_test_projected, this->color}); + painted_lines_set.insert(*it_contour_and_segment); + } + } + } + // Continue traversing the grid along the edge. + return true; + } + + const EdgeGrid::Grid &grid; + std::vector &painted_lines; + Line line_to_test; + std::unordered_set, boost::hash>> painted_lines_set; + int color = -1; +}; + +static std::vector to_colored_lines(const Polygon &polygon, int color) +{ + std::vector lines; + lines.reserve(polygon.points.size()); + if (polygon.points.size() > 2) { + for (auto it = polygon.points.begin(); it != polygon.points.end() - 1; ++it) + lines.push_back({Line(*it, *(it + 1)), color}); + lines.push_back({Line(polygon.points.back(), polygon.points.front()), color}); + } + return lines; +} + +static Polygon colored_points_to_polygon(const std::vector &lines) +{ + Points out; + for (const ColoredLine &l : lines) + out.emplace_back(l.line.a); + return Polygon(out); +} + +static Polygons colored_points_to_polygon(const std::vector> &lines) +{ + Polygons out; + for (const std::vector &l : lines) + out.emplace_back(colored_points_to_polygon(l)); + return out; +} + +inline std::vector to_lines(const std::vector> &c_lines) +{ + size_t n_lines = 0; + for (const auto &c_line : c_lines) + n_lines += c_line.size(); + std::vector lines; + lines.reserve(n_lines); + for (const auto &c_line : c_lines) + lines.insert(lines.end(), c_line.begin(), c_line.end()); + return lines; +} + +// Double vertex equal to a coord_t point after conversion to double. +template +inline bool vertex_equal_to_point(const VertexType &vertex, const Point &ipt) +{ + // Convert ipt to doubles, force the 80bit FPU temporary to 64bit and then compare. + // This should work with any settings of math compiler switches and the C++ compiler + // shall understand the memcpies as type punning and it shall optimize them out. + using ulp_cmp_type = boost::polygon::detail::ulp_comparison; + ulp_cmp_type ulp_cmp; + static constexpr int ULPS = boost::polygon::voronoi_diagram_traits::vertex_equality_predicate_type::ULPS; + return ulp_cmp(vertex.x(), double(ipt.x()), ULPS) == ulp_cmp_type::EQUAL && + ulp_cmp(vertex.y(), double(ipt.y()), ULPS) == ulp_cmp_type::EQUAL; +} + +bool vertex_equal_to_point(const Voronoi::VD::vertex_type *vertex, const Point &ipt) { + return vertex_equal_to_point(*vertex, ipt); +} + +static std::vector> get_segments(const std::vector &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>> get_all_segments(const std::vector> &color_poly) +{ + std::vector>> all_segments(color_poly.size()); + for (size_t poly_idx = 0; poly_idx < color_poly.size(); ++poly_idx) { + const std::vector &c_polygon = color_poly[poly_idx]; + all_segments[poly_idx] = get_segments(c_polygon); + } + return all_segments; +} + +static std::vector colorize_line(const Line & line_to_process, + const size_t start_idx, + const size_t end_idx, + std::vector &painted_lines) +{ + std::vector internal_painted; + for (size_t line_idx = start_idx; line_idx <= end_idx; ++line_idx) { internal_painted.emplace_back(painted_lines[line_idx]); } + const int filter_eps_value = scale_(0.1f); + std::vector filtered_lines; + filtered_lines.emplace_back(internal_painted.front()); + for (size_t line_idx = 1; line_idx < internal_painted.size(); ++line_idx) { + PaintedLine &prev = filtered_lines.back(); + PaintedLine &curr = internal_painted[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) { + } else { + if (prev.color == curr.color) { + prev.projected_line.b = curr.projected_line.b; + } else { + curr.projected_line.a = prev.projected_line.b; + filtered_lines.emplace_back(curr); + } + } + } + } + + std::vector final_lines; + double dist_to_start = (filtered_lines.front().projected_line.a - line_to_process.a).cast().norm(); + if (dist_to_start <= filter_eps_value) { + filtered_lines.front().projected_line.a = line_to_process.a; + final_lines.push_back({filtered_lines.front().projected_line, filtered_lines.front().color}); + } else { + final_lines.push_back({Line(line_to_process.a, filtered_lines.front().projected_line.a), 0}); + final_lines.push_back({filtered_lines.front().projected_line, filtered_lines.front().color}); + } + + for (size_t line_idx = 1; line_idx < filtered_lines.size(); ++line_idx) { + ColoredLine &prev = final_lines.back(); + PaintedLine &curr = filtered_lines[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}); + } + } + + double dist_to_end = (final_lines.back().line.b - line_to_process.b).cast().norm(); + if (dist_to_end <= filter_eps_value) + final_lines.back().line.b = line_to_process.b; + else + final_lines.push_back({Line(final_lines.back().line.b, line_to_process.b), 0}); + + for (size_t line_idx = 1; line_idx < final_lines.size(); ++line_idx) + assert(final_lines[line_idx - 1].line.b == final_lines[line_idx].line.a); + + 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; + } + + std::vector colored_lines_simpl; + colored_lines_simpl.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_simpl.back().color == line_0.color) + colored_lines_simpl.back().line.b = line_0.line.b; + else + colored_lines_simpl.emplace_back(line_0); + } + + final_lines = colored_lines_simpl; + + 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 std::vector colorize_polygon(const Polygon &poly, const size_t start_idx, const size_t end_idx, std::vector &painted_lines) +{ + std::vector new_lines; + Lines lines = poly.lines(); + + for (size_t idx = 0; idx < painted_lines[start_idx].line_idx; ++idx) + new_lines.emplace_back(ColoredLine{lines[idx], 0}); + + for (size_t first_idx = start_idx; first_idx <= end_idx; ++first_idx) { + size_t second_idx = first_idx; + while (second_idx <= end_idx && painted_lines[first_idx].line_idx == painted_lines[second_idx].line_idx) ++second_idx; + --second_idx; + + assert(painted_lines[first_idx].line_idx == painted_lines[second_idx].line_idx); + std::vector lines_c_line = colorize_line(lines[painted_lines[first_idx].line_idx], first_idx, second_idx, painted_lines); + new_lines.insert(new_lines.end(), lines_c_line.begin(), lines_c_line.end()); + + if (second_idx + 1 <= end_idx) + for (size_t idx = painted_lines[second_idx].line_idx + 1; idx < painted_lines[second_idx + 1].line_idx; ++idx) + new_lines.emplace_back(ColoredLine{lines[idx], 0}); + + first_idx = second_idx; + } + + for (size_t idx = painted_lines[end_idx].line_idx + 1; idx < poly.size(); ++idx) + new_lines.emplace_back(ColoredLine{lines[idx], 0}); + + 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; + }; + + for (size_t pair_idx = 1; pair_idx < segments.size(); ++pair_idx) { + int color0 = new_lines[segments[pair_idx - 1].first].color; + int color1 = new_lines[segments[pair_idx - 0].first].color; + + double seg0l = segment_length(segments[pair_idx - 1]); + double seg1l = segment_length(segments[pair_idx - 0]); + + if (color0 != color1 && seg0l >= scale_(0.1) && seg1l <= scale_(0.2)) { + for (size_t seg_start_idx = segments[pair_idx].first; seg_start_idx != segments[pair_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[pair_idx].second].color = color0; + } + } + + segments = get_segments(new_lines); + for (size_t pair_idx = 1; pair_idx < segments.size(); ++pair_idx) { + int color0 = new_lines[segments[pair_idx - 1].first].color; + int color1 = new_lines[segments[pair_idx - 0].first].color; + double seg1l = segment_length(segments[pair_idx - 0]); + + if (color0 >= 1 && color0 != color1 && seg1l <= scale_(0.2)) { + for (size_t seg_start_idx = segments[pair_idx].first; seg_start_idx != segments[pair_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[pair_idx].second].color = color0; + } + } + + for (size_t pair_idx = 2; pair_idx < segments.size(); ++pair_idx) { + int color0 = new_lines[segments[pair_idx - 2].first].color; + int color1 = new_lines[segments[pair_idx - 1].first].color; + int color2 = new_lines[segments[pair_idx - 0].first].color; + + if (color0 > 0 && color0 == color2 && color0 != color1 && segment_length(segments[pair_idx - 1]) <= scale_(0.5)) { + for (size_t seg_start_idx = segments[pair_idx].first; seg_start_idx != segments[pair_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[pair_idx].second].color = color0; + } + } + + return new_lines; +} + +static std::vector> colorize_polygons(const Polygons &polygons, std::vector &painted_lines) +{ + const size_t start_idx = 0; + const size_t end_idx = painted_lines.size() - 1; + + std::vector> new_polygons; + + for (size_t idx = 0; idx < painted_lines[start_idx].contour_idx; ++idx) + new_polygons.emplace_back(to_colored_lines(polygons[idx], 0)); + + for (size_t first_idx = start_idx; first_idx <= end_idx; ++first_idx) { + size_t second_idx = first_idx; + while (second_idx <= end_idx && painted_lines[first_idx].contour_idx == painted_lines[second_idx].contour_idx) + ++second_idx; + --second_idx; + + assert(painted_lines[first_idx].contour_idx == painted_lines[second_idx].contour_idx); + std::vector polygon_c = colorize_polygon(polygons[painted_lines[first_idx].contour_idx], first_idx, second_idx, painted_lines); + new_polygons.emplace_back(polygon_c); + + if (second_idx + 1 <= end_idx) + for (size_t idx = painted_lines[second_idx].contour_idx + 1; idx < painted_lines[second_idx + 1].contour_idx; ++idx) + new_polygons.emplace_back(to_colored_lines(polygons[idx], 0)); + + first_idx = second_idx; + } + + for (size_t idx = painted_lines[end_idx].contour_idx + 1; idx < polygons.size(); ++idx) + new_polygons.emplace_back(to_colored_lines(polygons[idx], 0)); + + return new_polygons; +} + +using boost::polygon::voronoi_diagram; + +struct MMU_Graph +{ + enum class ARC_TYPE { BORDER, NON_BORDER }; + + struct Arc + { + size_t from_idx; + size_t to_idx; + int color; + ARC_TYPE type; + bool used{false}; + + bool operator==(const Arc &rhs) const { return (from_idx == rhs.from_idx) && (to_idx == rhs.to_idx) && (color == rhs.color) && (type == rhs.type); } + bool operator!=(const Arc &rhs) const { return !operator==(rhs); } + }; + + struct Node + { + Point point; + std::list neighbours; + + void remove_edge(const size_t to_idx) + { + for (auto arc_it = this->neighbours.begin(); arc_it != this->neighbours.end(); ++arc_it) { + if (arc_it->to_idx == to_idx) { + assert(arc_it->type != ARC_TYPE::BORDER); + this->neighbours.erase(arc_it); + break; + } + } + } + }; + + std::vector nodes; + std::vector arcs; + size_t all_border_points{}; + + std::vector polygon_idx_offset; + std::vector polygon_sizes; + + void remove_edge(const size_t from_idx, const size_t to_idx) + { + nodes[from_idx].remove_edge(to_idx); + nodes[to_idx].remove_edge(from_idx); + } + + size_t get_global_index(const size_t poly_idx, const size_t point_idx) const { return polygon_idx_offset[poly_idx] + point_idx; } + + void append_edge(const size_t &from_idx, const size_t &to_idx, int color = -1, ARC_TYPE type = ARC_TYPE::NON_BORDER) + { + // Don't append duplicate edges between the same nodes. + for (const MMU_Graph::Arc &arc : this->nodes[from_idx].neighbours) + if (arc.to_idx == to_idx) + return; + for (const MMU_Graph::Arc &arc : this->nodes[to_idx].neighbours) + if (arc.to_idx == to_idx) + return; + + this->nodes[from_idx].neighbours.push_back({from_idx, to_idx, color, type}); + this->nodes[to_idx].neighbours.push_back({to_idx, from_idx, color, type}); + this->arcs.push_back({from_idx, to_idx, color, type}); + this->arcs.push_back({to_idx, from_idx, color, type}); + } + + // Ignoring arcs in the opposite direction + MMU_Graph::Arc get_arc(size_t idx) { return this->arcs[idx * 2]; } + + size_t nodes_count() const { return this->nodes.size(); } + + void remove_nodes_with_one_arc() + { + std::queue update_queue; + for (const MMU_Graph::Node &node : this->nodes) + if (node.neighbours.size() == 1) update_queue.emplace(&node - &this->nodes.front()); + + while (!update_queue.empty()) { + size_t node_from_idx = update_queue.front(); + MMU_Graph::Node &node_from = this->nodes[update_queue.front()]; + update_queue.pop(); + if (node_from.neighbours.empty()) + continue; + + assert(node_from.neighbours.size() == 1); + size_t node_to_idx = node_from.neighbours.front().to_idx; + MMU_Graph::Node &node_to = this->nodes[node_to_idx]; + this->remove_edge(node_from_idx, node_to_idx); + if (node_to.neighbours.size() == 1) + update_queue.emplace(node_to_idx); + } + } + + void add_contours(const std::vector> &color_poly) + { + this->all_border_points = nodes.size(); + this->polygon_sizes = std::vector(color_poly.size()); + for (size_t polygon_idx = 0; polygon_idx < color_poly.size(); ++polygon_idx) + this->polygon_sizes[polygon_idx] = color_poly[polygon_idx].size(); + this->polygon_idx_offset = std::vector(color_poly.size()); + this->polygon_idx_offset[0] = 0; + for (size_t polygon_idx = 1; polygon_idx < color_poly.size(); ++polygon_idx) { + this->polygon_idx_offset[polygon_idx] = this->polygon_idx_offset[polygon_idx - 1] + color_poly[polygon_idx - 1].size(); + } + + size_t poly_idx = 0; + for (const std::vector &color_lines : color_poly) { + size_t line_idx = 0; + for (const ColoredLine &color_line : color_lines) { + size_t from_idx = this->get_global_index(poly_idx, line_idx); + size_t to_idx = this->get_global_index(poly_idx, (line_idx + 1) % color_lines.size()); + this->append_edge(from_idx, to_idx, color_line.color, ARC_TYPE::BORDER); + ++line_idx; + } + ++poly_idx; + } + } + + // Nodes 0..all_border_points are only one with are on countour. Other vertexis are consider as not on coouter. So we check if base on attach index + inline bool is_vertex_on_contour(const Voronoi::VD::vertex_type *vertex) const + { + assert(vertex != nullptr); + return vertex->color() < this->all_border_points; + } + + inline bool is_edge_attach_to_contour(const voronoi_diagram::const_edge_iterator &edge_iterator) const + { + return this->is_vertex_on_contour(edge_iterator->vertex0()) || this->is_vertex_on_contour(edge_iterator->vertex1()); + } + + inline bool is_edge_connecting_two_contour_vertices(const voronoi_diagram::const_edge_iterator &edge_iterator) const + { + return this->is_vertex_on_contour(edge_iterator->vertex0()) && this->is_vertex_on_contour(edge_iterator->vertex1()); + } +}; + +namespace bg = boost::geometry; +namespace bgm = boost::geometry::model; +namespace bgi = boost::geometry::index; + +// float is needed because for coord_t bgi::intersects throws "bad numeric conversion: positive overflow" +using rtree_point_t = bgm::point; +using rtree_t = bgi::rtree, bgi::rstar<16, 4>>; + +static inline rtree_point_t mk_rtree_point(const Point &pt) { return rtree_point_t(float(pt.x()), float(pt.y())); } + +static inline Point mk_point(const Voronoi::VD::vertex_type *point) { return Point(coord_t(point->x()), coord_t(point->y())); } + +static inline Point mk_point(const Voronoi::Internal::point_type &point) { return Point(coord_t(point.x()), coord_t(point.y())); } + +static inline Point mk_point(const voronoi_diagram::vertex_type &point) { return Point(coord_t(point.x()), coord_t(point.y())); } + +static inline void mark_processed(const voronoi_diagram::const_edge_iterator &edge_iterator) +{ + edge_iterator->color(true); + edge_iterator->twin()->color(true); +} + +// Return true, if "p" is closer to line.a, then line.b +static inline bool is_point_closer_to_beginning_of_line(const Line &line, const Point &p) +{ + return (p - line.a).cast().squaredNorm() < (p - line.b).cast().squaredNorm(); +} + +static inline bool has_same_color(const ColoredLine &cl1, const ColoredLine &cl2) { return cl1.color == cl2.color; } + +// 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 inline bool line_intersection_with_epsilon(const Line &line_to_extend, const Line &other, Point *intersection) +{ + Line extended_line = line_to_extend; + extended_line.extend(15 * SCALED_EPSILON); + return extended_line.intersection(other, intersection); +} + +// For every ColoredLine in lines_colored_out, assign the index of the polygon to which belongs and also the index of this line inside of the polygon. +static inline void init_polygon_indices(const MMU_Graph &graph, + const std::vector> &color_poly, + std::vector &lines_colored_out) +{ + size_t poly_idx = 0; + for (const std::vector &color_lines : color_poly) { + size_t line_idx = 0; + for (size_t color_line_idx = 0; color_line_idx < color_lines.size(); ++color_line_idx) { + size_t from_idx = graph.get_global_index(poly_idx, line_idx); + lines_colored_out[from_idx].poly_idx = int(poly_idx); + lines_colored_out[from_idx].local_line_idx = int(line_idx); + ++line_idx; + } + ++poly_idx; + } +} + +static MMU_Graph build_graph(size_t layer_idx, const std::vector> &color_poly) +{ + Geometry::VoronoiDiagram vd; + std::vector lines_colored = to_lines(color_poly); + Polygons color_poly_tmp = colored_points_to_polygon(color_poly); + const Points points = to_points(color_poly_tmp); + const Lines lines = to_lines(color_poly_tmp); + + boost::polygon::construct_voronoi(lines_colored.begin(), lines_colored.end(), &vd); + MMU_Graph graph; + for (const Point &point : points) + graph.nodes.push_back({point}); + + graph.add_contours(color_poly); + init_polygon_indices(graph, color_poly, lines_colored); + + assert(graph.nodes.size() == lines_colored.size()); + + // All Voronoi vertices are post-processes to merge very close vertices to single. Witch Eliminates issues with intersection edges. + // Also, Voronoi vertices outside of the bounding of input polygons are throw away by marking them. + auto append_voronoi_vertices_to_graph = [&graph, &color_poly_tmp, &vd]() -> void { + auto is_equal_points = [](const Point &p1, const Point &p2) { return p1 == p2 || (p1 - p2).cast().norm() <= 3 * SCALED_EPSILON; }; + + BoundingBox bbox = get_extents(color_poly_tmp); + bbox.offset(SCALED_EPSILON); + // EdgeGrid is used for vertices near to contour and rtree for other vertices + // FIXME Lukas H.: Get rid of EdgeGrid and rtree. Use only one structure for both cases. + EdgeGrid::Grid grid; + grid.set_bbox(bbox); + grid.create(color_poly_tmp, coord_t(scale_(10.))); + rtree_t rtree; + for (const voronoi_diagram::vertex_type &vertex : vd.vertices()) { + vertex.color(-1); + Point vertex_point = mk_point(vertex); + + const Point &first_point = graph.nodes[graph.get_arc(vertex.incident_edge()->cell()->source_index()).from_idx].point; + const Point &second_point = graph.nodes[graph.get_arc(vertex.incident_edge()->twin()->cell()->source_index()).from_idx].point; + + if (vertex_equal_to_point(&vertex, first_point)) { + assert(vertex.color() != vertex.incident_edge()->cell()->source_index()); + assert(vertex.color() != vertex.incident_edge()->twin()->cell()->source_index()); + vertex.color(graph.get_arc(vertex.incident_edge()->cell()->source_index()).from_idx); + } else if (vertex_equal_to_point(&vertex, second_point)) { + assert(vertex.color() != vertex.incident_edge()->cell()->source_index()); + assert(vertex.color() != vertex.incident_edge()->twin()->cell()->source_index()); + vertex.color(graph.get_arc(vertex.incident_edge()->twin()->cell()->source_index()).from_idx); + } else if (bbox.contains(vertex_point)) { + EdgeGrid::Grid::ClosestPointResult cp = grid.closest_point_signed_distance(vertex_point, coord_t(3 * SCALED_EPSILON)); + if (cp.valid()) { + size_t global_idx = graph.get_global_index(cp.contour_idx, cp.start_point_idx); + size_t global_idx_next = graph.get_global_index(cp.contour_idx, (cp.start_point_idx + 1) % color_poly_tmp[cp.contour_idx].points.size()); + vertex.color(is_equal_points(vertex_point, graph.nodes[global_idx].point) ? global_idx : global_idx_next); + } else { + if (rtree.empty()) { + rtree.insert(std::make_pair(mk_rtree_point(vertex_point), graph.nodes_count())); + vertex.color(graph.nodes_count()); + graph.nodes.push_back({vertex_point}); + } else { + std::vector> closest; + rtree.query(bgi::nearest(mk_rtree_point(vertex_point), 1), std::back_inserter(closest)); + assert(!closest.empty()); + rtree_point_t r_point = closest.front().first; + Point closest_p(bg::get<0>(r_point), bg::get<1>(r_point)); + if (Line(vertex_point, closest_p).length() > 3 * SCALED_EPSILON) { + rtree.insert(std::make_pair(mk_rtree_point(vertex_point), graph.nodes_count())); + vertex.color(graph.nodes_count()); + graph.nodes.push_back({vertex_point}); + } else { + vertex.color(closest.front().second); + } + } + } + } + } + }; + + append_voronoi_vertices_to_graph(); + + auto get_prev_contour_line = [&lines_colored, &color_poly, &graph](const voronoi_diagram::const_edge_iterator &edge_it) -> ColoredLine { + size_t contour_line_local_idx = lines_colored[edge_it->cell()->source_index()].local_line_idx; + size_t contour_line_size = color_poly[lines_colored[edge_it->cell()->source_index()].poly_idx].size(); + size_t contour_prev_idx = graph.get_global_index(lines_colored[edge_it->cell()->source_index()].poly_idx, + (contour_line_local_idx > 0) ? contour_line_local_idx - 1 : contour_line_size - 1); + return lines_colored[contour_prev_idx]; + }; + + auto get_next_contour_line = [&lines_colored, &color_poly, &graph](const voronoi_diagram::const_edge_iterator &edge_it) -> ColoredLine { + size_t contour_line_local_idx = lines_colored[edge_it->cell()->source_index()].local_line_idx; + size_t contour_line_size = color_poly[lines_colored[edge_it->cell()->source_index()].poly_idx].size(); + size_t contour_next_idx = graph.get_global_index(lines_colored[edge_it->cell()->source_index()].poly_idx, + (contour_line_local_idx + 1) % contour_line_size); + return lines_colored[contour_next_idx]; + }; + + BoundingBox bbox = get_extents(color_poly_tmp); + bbox.offset(scale_(10.)); + const double bbox_dim_max = double(std::max(bbox.size().x(), bbox.size().y())); + + // Make a copy of the input segments with the double type. + std::vector segments; + for (const Line &line : lines) + segments.emplace_back(Voronoi::Internal::point_type(double(line.a(0)), double(line.a(1))), + Voronoi::Internal::point_type(double(line.b(0)), double(line.b(1)))); + + for (auto edge_it = vd.edges().begin(); edge_it != vd.edges().end(); ++edge_it) { + // Skip second half-edge + if (edge_it->cell()->source_index() > edge_it->twin()->cell()->source_index() || edge_it->color()) + continue; + + if (edge_it->is_infinite()) { + // Infinite edge is leading through a point on the counter, but there are no Voronoi vertices. + // So we could fix this case by computing the intersection between the contour line and infinity edge. + std::vector samples; + Voronoi::Internal::clip_infinite_edge(points, segments, *edge_it, bbox_dim_max, &samples); + if (samples.empty()) + continue; + + const Line edge_line(mk_point(samples[0]), mk_point(samples[1])); + const ColoredLine &contour_line = lines_colored[edge_it->cell()->source_index()]; + Point contour_intersection; + + if (line_intersection_with_epsilon(contour_line.line, edge_line, &contour_intersection)) { + const MMU_Graph::Arc &graph_arc = graph.get_arc(edge_it->cell()->source_index()); + const size_t from_idx = (edge_it->vertex1() != nullptr) ? edge_it->vertex1()->color() : edge_it->vertex0()->color(); + size_t to_idx = ((contour_line.line.a - contour_intersection).cast().squaredNorm() < + (contour_line.line.b - contour_intersection).cast().squaredNorm()) ? + graph_arc.from_idx : + graph_arc.to_idx; + if (from_idx != to_idx && from_idx < graph.nodes_count() && to_idx < graph.nodes_count()) { + graph.append_edge(from_idx, to_idx); + mark_processed(edge_it); + } + } + } else if (edge_it->is_finite()) { + const Point v0 = mk_point(edge_it->vertex0()); + const Point v1 = mk_point(edge_it->vertex1()); + const size_t from_idx = edge_it->vertex0()->color(); + const size_t to_idx = edge_it->vertex1()->color(); + + // Both points are on contour, so skip them. In cases of duplicate Voronoi vertices, skip edges between the same two points. + if (graph.is_edge_connecting_two_contour_vertices(edge_it) || (edge_it->vertex0()->color() == edge_it->vertex1()->color())) continue; + + const Line edge_line(v0, v1); + const Line contour_line = lines_colored[edge_it->cell()->source_index()].line; + const ColoredLine colored_line = lines_colored[edge_it->cell()->source_index()]; + const ColoredLine contour_line_prev = get_prev_contour_line(edge_it); + const ColoredLine contour_line_next = get_next_contour_line(edge_it); + + Point intersection; + if (edge_it->vertex0()->color() >= graph.nodes_count() || edge_it->vertex1()->color() >= graph.nodes_count()) { +// if(edge_it->vertex0()->color() < graph.nodes_count() && !graph.is_vertex_on_contour(edge_it->vertex0())) { +// +// } + if (edge_it->vertex1()->color() < graph.nodes_count() && !graph.is_vertex_on_contour(edge_it->vertex1())) { + Line contour_line_twin = lines_colored[edge_it->twin()->cell()->source_index()].line; + if (line_intersection_with_epsilon(contour_line_twin, edge_line, &intersection)) { + const MMU_Graph::Arc &graph_arc = graph.get_arc(edge_it->twin()->cell()->source_index()); + const size_t to_idx_l = is_point_closer_to_beginning_of_line(contour_line_twin, intersection) ? graph_arc.from_idx : + graph_arc.to_idx; + graph.append_edge(edge_it->vertex1()->color(), to_idx_l); + } else if (line_intersection_with_epsilon(contour_line, edge_line, &intersection)) { + const MMU_Graph::Arc &graph_arc = graph.get_arc(edge_it->cell()->source_index()); + const size_t to_idx_l = is_point_closer_to_beginning_of_line(contour_line, intersection) ? graph_arc.from_idx : graph_arc.to_idx; + graph.append_edge(edge_it->vertex1()->color(), to_idx_l); + } + mark_processed(edge_it); + } + } else if (graph.is_edge_attach_to_contour(edge_it)) { + mark_processed(edge_it); + // Skip edges witch connection two points on a contour + if (graph.is_edge_connecting_two_contour_vertices(edge_it)) + continue; + + if (graph.is_vertex_on_contour(edge_it->vertex0())) { + if (is_point_closer_to_beginning_of_line(contour_line, v0)) { + if (!has_same_color(contour_line_prev, colored_line) && points_inside(contour_line_prev.line, contour_line, v1)) { + graph.append_edge(from_idx, to_idx); + } + } else { + if (!has_same_color(contour_line_next, colored_line) && points_inside(contour_line, contour_line_next.line, v1)) { + graph.append_edge(from_idx, to_idx); + } + } + } else { + assert(graph.is_vertex_on_contour(edge_it->vertex1())); + if (is_point_closer_to_beginning_of_line(contour_line, v1)) { + if (!has_same_color(contour_line_prev, colored_line) && points_inside(contour_line_prev.line, contour_line, v0)) { + graph.append_edge(from_idx, to_idx); + } + } else { + if (!has_same_color(contour_line_next, colored_line) && points_inside(contour_line, contour_line_next.line, v0)) { + graph.append_edge(from_idx, to_idx); + } + } + } + } else if (line_intersection_with_epsilon(contour_line, edge_line, &intersection)) { + mark_processed(edge_it); + Point real_v0 = graph.nodes[edge_it->vertex0()->color()].point; + Point real_v1 = graph.nodes[edge_it->vertex1()->color()].point; + + if (is_point_closer_to_beginning_of_line(contour_line, intersection)) { + Line first_part(intersection, real_v0); + Line second_part(intersection, real_v1); + + if (!has_same_color(contour_line_prev, colored_line)) { + if (points_inside(contour_line_prev.line, contour_line, first_part.b)) { + graph.append_edge(edge_it->vertex0()->color(), graph.get_arc(edge_it->cell()->source_index()).from_idx); + } + if (points_inside(contour_line_prev.line, contour_line, second_part.b)) { + graph.append_edge(edge_it->vertex1()->color(), graph.get_arc(edge_it->cell()->source_index()).from_idx); + } + } + } else { + const size_t int_point_idx = graph.get_arc(edge_it->cell()->source_index()).to_idx; + const Point int_point = graph.nodes[int_point_idx].point; + + const Line first_part(int_point, real_v0); + const Line second_part(int_point, real_v1); + + if (!has_same_color(contour_line_next, colored_line)) { + if (points_inside(contour_line, contour_line_next.line, first_part.b)) { + graph.append_edge(edge_it->vertex0()->color(), int_point_idx); + } + if (points_inside(contour_line, contour_line_next.line, second_part.b)) { + graph.append_edge(edge_it->vertex1()->color(), int_point_idx); + } + } + } + } + } + } + + for (auto edge_it = vd.edges().begin(); edge_it != vd.edges().end(); ++edge_it) { + // Skip second half-edge and processed edges + if (edge_it->cell()->source_index() > edge_it->twin()->cell()->source_index() || edge_it->color()) + continue; + + if (edge_it->is_finite() && !bool(edge_it->color()) && edge_it->vertex0()->color() < graph.nodes_count() && + edge_it->vertex1()->color() < graph.nodes_count()) { + // Skip cases, when the edge is between two same vertices, which is in cases two near vertices were merged together. + if (edge_it->vertex0()->color() == edge_it->vertex1()->color()) + continue; + + size_t from_idx = edge_it->vertex0()->color(); + size_t to_idx = edge_it->vertex1()->color(); + graph.append_edge(from_idx, to_idx); + } + mark_processed(edge_it); + } + + graph.remove_nodes_with_one_arc(); + return graph; +} + +static inline Polygon to_polygon(const Lines &lines) +{ + Polygon poly_out; + poly_out.points.reserve(lines.size()); + for (const Line &line : lines) + poly_out.points.emplace_back(line.a); + return poly_out; +} + +// Returns list of polygons and assigned colors. +// 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. +// Assumes that graph is planar (without self-intersection edges) +static std::vector> extract_colored_segments(MMU_Graph &graph) +{ + // When there is no next arc, then is returned original_arc or edge with is marked as used + auto get_next = [&graph](const Line &process_line, MMU_Graph::Arc &original_arc) -> MMU_Graph::Arc & { + std::vector> sorted_arcs; + for (MMU_Graph::Arc &arc : graph.nodes[original_arc.to_idx].neighbours) { + if (graph.nodes[arc.to_idx].point == process_line.a || arc.used) + continue; + + assert(original_arc.to_idx == arc.from_idx); + Vec2d process_line_vec_n = (process_line.a - process_line.b).cast().normalized(); + Vec2d neighbour_line_vec_n = (graph.nodes[arc.to_idx].point - graph.nodes[arc.from_idx].point).cast().normalized(); + + double angle = ::acos(clamp(-1.0, 1.0, neighbour_line_vec_n.dot(process_line_vec_n))); + if (Slic3r::cross2(neighbour_line_vec_n, process_line_vec_n) < 0.0) + angle = 2.0 * (double) PI - angle; + + sorted_arcs.emplace_back(&arc, angle); + } + + std::sort(sorted_arcs.begin(), sorted_arcs.end(), + [](std::pair &l, std::pair &r) -> bool { return l.second < r.second; }); + + // Try to return left most edge witch is unused + for (auto &sorted_arc : sorted_arcs) + if (!sorted_arc.first->used) + return *sorted_arc.first; + + if (sorted_arcs.empty()) + return original_arc; + + return *(sorted_arcs.front().first); + }; + + std::vector> polygons_segments; + for (MMU_Graph::Node &node : graph.nodes) + for (MMU_Graph::Arc &arc : node.neighbours) + arc.used = false; + + for (size_t node_idx = 0; node_idx < graph.all_border_points; ++node_idx) { + MMU_Graph::Node &node = graph.nodes[node_idx]; + + for (MMU_Graph::Arc &arc : node.neighbours) { + if (arc.type == MMU_Graph::ARC_TYPE::NON_BORDER || arc.used) continue; + + Line process_line(node.point, graph.nodes[arc.to_idx].point); + arc.used = true; + + Lines face_lines; + face_lines.emplace_back(process_line); + Point start_p = process_line.a; + + Line p_vec = process_line; + MMU_Graph::Arc *p_arc = &arc; + do { + MMU_Graph::Arc &next = get_next(p_vec, *p_arc); + face_lines.emplace_back(Line(graph.nodes[next.from_idx].point, graph.nodes[next.to_idx].point)); + if (next.used) break; + + next.used = true; + p_vec = Line(graph.nodes[next.from_idx].point, graph.nodes[next.to_idx].point); + p_arc = &next; + } while (graph.nodes[p_arc->to_idx].point != start_p); + + Polygon poly = to_polygon(face_lines); + if (poly.is_counter_clockwise() && poly.is_valid()) + polygons_segments.emplace_back(poly, arc.color); + } + } + return polygons_segments; +} + +// 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 compute_edge_length(MMU_Graph &graph, size_t start_idx, MMU_Graph::Arc &start_edge) +{ + for (MMU_Graph::Node &node : graph.nodes) + for (MMU_Graph::Arc &arc : node.neighbours) + arc.used = false; + + start_edge.used = true; + MMU_Graph::Arc *arc = &start_edge; + size_t idx = start_idx; + double line_total_length = Line(graph.nodes[idx].point, graph.nodes[arc->to_idx].point).length(); + while (graph.nodes[arc->to_idx].neighbours.size() == 2) { + bool found = false; + for (MMU_Graph::Arc &arc_n : graph.nodes[arc->to_idx].neighbours) { + if (arc_n.type == MMU_Graph::ARC_TYPE::NON_BORDER && !arc_n.used && arc_n.to_idx != idx) { + Line first_line(graph.nodes[idx].point, graph.nodes[arc->to_idx].point); + Line second_line(graph.nodes[arc->to_idx].point, graph.nodes[arc_n.to_idx].point); + + Vec2d first_line_vec = (first_line.a - first_line.b).cast(); + Vec2d second_line_vec = (second_line.b - second_line.a).cast(); + Vec2d first_line_vec_n = first_line_vec.normalized(); + Vec2d second_line_vec_n = second_line_vec.normalized(); + double angle = ::acos(clamp(-1.0, 1.0, first_line_vec_n.dot(second_line_vec_n))); + 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; + + idx = arc->to_idx; + arc = &arc_n; + + line_total_length += Line(graph.nodes[idx].point, graph.nodes[arc->to_idx].point).length(); + arc_n.used = true; + found = true; + break; + } + } + if (!found) + break; + } + + return line_total_length; +} + +// Used for fixing double Voronoi edges for concave parts of the polygon. +static void remove_multiple_edges_in_vertices(MMU_Graph &graph, const std::vector> &color_poly) +{ + std::vector>> colored_segments = get_all_segments(color_poly); + for (const std::vector> &colored_segment_p : colored_segments) { + size_t poly_idx = &colored_segment_p - &colored_segments.front(); + for (const std::pair &colored_segment : colored_segment_p) { + size_t first_idx = graph.get_global_index(poly_idx, colored_segment.first); + size_t second_idx = graph.get_global_index(poly_idx, (colored_segment.second + 1) % graph.polygon_sizes[poly_idx]); + Line seg_line(graph.nodes[first_idx].point, graph.nodes[second_idx].point); + + if (graph.nodes[first_idx].neighbours.size() >= 3) { + std::vector> arc_to_check; + for (MMU_Graph::Arc &n_arc : graph.nodes[first_idx].neighbours) { + if (n_arc.type == MMU_Graph::ARC_TYPE::NON_BORDER) { + double total_len = compute_edge_length(graph, first_idx, n_arc); + arc_to_check.emplace_back(&n_arc, total_len); + } + } + std::sort(arc_to_check.begin(), arc_to_check.end(), + [](std::pair &l, std::pair &r) -> bool { return l.second > r.second; }); + + while (arc_to_check.size() > 1) { + graph.remove_edge(first_idx, arc_to_check.back().first->to_idx); + arc_to_check.pop_back(); + } + } + } + } +} + +static void cut_segmented_layers(const ConstLayerPtrsAdaptor layers, std::vector>> &segmented_regions, const float cut_width) { + tbb::parallel_for(tbb::blocked_range(0, segmented_regions.size()),[&](const tbb::blocked_range& range) { + for (size_t layer_idx = range.begin(); layer_idx < range.end(); ++layer_idx) { + std::vector> segmented_regions_cuts; + for (const std::pair &colored_expoly : segmented_regions[layer_idx]) { + ExPolygons cut_colored_expoly = diff_ex({colored_expoly.first}, offset_ex(layers[layer_idx]->lslices, cut_width)); + for (const ExPolygon &expoly : cut_colored_expoly) { + segmented_regions_cuts.emplace_back(expoly, colored_expoly.second); + } + } + segmented_regions[layer_idx] = segmented_regions_cuts; + } + }); // end of parallel_for +} + +// Returns MMU segmentation of top and bottom layers based on painting in MMU segmentation gizmo +static inline std::vector> mmu_segmentation_top_and_bottom_layers(const PrintObject &print_object) +{ + const ConstLayerPtrsAdaptor layers = print_object.layers(); + std::vector> triangles_by_color(3); + triangles_by_color.assign(3, std::vector(layers.size())); + for (const ModelVolume *mv : print_object.model_object()->volumes) { + for (const auto ¶ms : {std::make_pair(EnforcerBlockerType::NONE, 0), std::make_pair(EnforcerBlockerType::ENFORCER, 1), + std::make_pair(EnforcerBlockerType::BLOCKER, 2)}) { + const indexed_triangle_set custom_facets = mv->mmu_segmentation_facets.get_facets(*mv, params.first); + if (!mv->is_model_part() || custom_facets.indices.empty()) + continue; + + const Transform3f tr = print_object.trafo().cast() * mv->get_matrix().cast(); + for (size_t facet_idx = 0; facet_idx < custom_facets.indices.size(); ++facet_idx) { + float min_z = std::numeric_limits::max(); + float max_z = std::numeric_limits::lowest(); + + std::array facet; + Points projected_facet(3); + 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()); + } + + // 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(); }); + + for (int p_idx = 0; p_idx < 3; ++p_idx) { + projected_facet[p_idx] = Point(scale_(facet[p_idx].x()), scale_(facet[p_idx].y())); + projected_facet[p_idx] = projected_facet[p_idx] - print_object.center_offset(); + } + + ExPolygon triangle = ExPolygon(projected_facet); + + // 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 + l1->height * 0.5; }); + auto last_layer = std::upper_bound(layers.begin(), layers.end(), float(max_z - EPSILON), + [](float z, const Layer *l1) { return z < l1->slice_z + l1->height * 0.5; }); + + if (last_layer == layers.end()) + --last_layer; + + if (first_layer == layers.end() || (first_layer != layers.begin() && facet[0].z() < (*first_layer)->print_z - EPSILON)) + --first_layer; + + for (auto layer_it = first_layer; (layer_it != (last_layer + 1) && layer_it != layers.end()); ++layer_it) { + size_t layer_idx = layer_it - layers.begin(); + triangles_by_color[params.second][layer_idx].emplace_back(triangle); + } + } + } + } + + auto get_extrusion_width = [&layers = std::as_const(layers)](const size_t layer_idx) -> float { + auto extrusion_width_it = std::max_element(layers[layer_idx]->regions().begin(), layers[layer_idx]->regions().end(), + [](const LayerRegion *l1, const LayerRegion *l2) { + return l1->region()->config().perimeter_extrusion_width < + l2->region()->config().perimeter_extrusion_width; + }); + assert(extrusion_width_it != layers[layer_idx]->regions().end()); + return float((*extrusion_width_it)->region()->config().perimeter_extrusion_width); + }; + + auto get_top_solid_layers = [&layers = std::as_const(layers)](const size_t layer_idx) -> int { + auto top_solid_layer_it = std::max_element(layers[layer_idx]->regions().begin(), layers[layer_idx]->regions().end(), + [](const LayerRegion *l1, const LayerRegion *l2) { + return l1->region()->config().top_solid_layers < l2->region()->config().top_solid_layers; + }); + assert(top_solid_layer_it != layers[layer_idx]->regions().end()); + return (*top_solid_layer_it)->region()->config().top_solid_layers; + }; + + auto get_bottom_solid_layers = [&layers = std::as_const(layers)](const size_t layer_idx) -> int { + auto top_bottom_layer_it = std::max_element(layers[layer_idx]->regions().begin(), layers[layer_idx]->regions().end(), + [](const LayerRegion *l1, const LayerRegion *l2) { + return l1->region()->config().bottom_solid_layers < l2->region()->config().bottom_solid_layers; + }); + assert(top_bottom_layer_it != layers[layer_idx]->regions().end()); + return (*top_bottom_layer_it)->region()->config().bottom_solid_layers; + }; + + std::vector top_layers(layers.size()); + top_layers.back() = layers.back()->lslices; + tbb::parallel_for(tbb::blocked_range(1, layers.size()), [&](const tbb::blocked_range &range) { + for (size_t layer_idx = range.begin(); layer_idx < range.end(); ++layer_idx) { + float extrusion_width = 0.1f * float(scale_(get_extrusion_width(layer_idx))); + top_layers[layer_idx - 1] = diff_ex(layers[layer_idx - 1]->lslices, offset_ex(layers[layer_idx]->lslices, extrusion_width)); + } + }); // end of parallel_for + + std::vector bottom_layers(layers.size()); + bottom_layers.front() = layers.front()->lslices; + tbb::parallel_for(tbb::blocked_range(0, layers.size() - 1), [&](const tbb::blocked_range &range) { + for (size_t layer_idx = range.begin(); layer_idx < range.end(); ++layer_idx) { + float extrusion_width = 0.1f * float(scale_(get_extrusion_width(layer_idx))); + bottom_layers[layer_idx + 1] = diff_ex(layers[layer_idx + 1]->lslices, offset_ex(layers[layer_idx]->lslices, extrusion_width)); + } + }); // end of parallel_for + + tbb::parallel_for(tbb::blocked_range(0, print_object.layers().size()), [&](const tbb::blocked_range &range) { + for (size_t layer_idx = range.begin(); layer_idx < range.end(); ++layer_idx) { + float extrusion_width = 0.1f * float(scale_(get_extrusion_width(layer_idx))); + for (std::vector &triangles : triangles_by_color) { + if (!triangles[layer_idx].empty() && (!top_layers[layer_idx].empty() || !bottom_layers[layer_idx].empty())) { + ExPolygons connected = union_ex(offset_ex(triangles[layer_idx], float(10 * SCALED_EPSILON))); + triangles[layer_idx] = union_ex(offset_ex(offset_ex(connected, -extrusion_width / 1), extrusion_width / 1)); + } else { + triangles[layer_idx].clear(); + } + } + } + }); // end of parallel_for + + std::vector> triangles_by_color_bottom(3); + std::vector> triangles_by_color_top(3); + triangles_by_color_bottom.assign(3, std::vector(layers.size())); + triangles_by_color_top.assign(3, std::vector(layers.size())); + + for (size_t layer_idx = 0; layer_idx < print_object.layers().size(); ++layer_idx) { + BOOST_LOG_TRIVIAL(debug) << "MMU segmentation of top layer: " << layer_idx; + float extrusion_width = scale_(get_extrusion_width(layer_idx)); + int top_solid_layers = get_top_solid_layers(layer_idx); + ExPolygons top_expolygon = top_layers[layer_idx]; + if (top_expolygon.empty()) + continue; + + for (size_t color_idx = 0; color_idx < triangles_by_color.size(); ++color_idx) { + if (triangles_by_color[color_idx][layer_idx].empty()) + continue; + ExPolygons intersection_poly = intersection_ex(triangles_by_color[color_idx][layer_idx], top_expolygon); + if (!intersection_poly.empty()) { + triangles_by_color_top[color_idx][layer_idx].insert(triangles_by_color_top[color_idx][layer_idx].end(), intersection_poly.begin(), + intersection_poly.end()); + for (int last_idx = int(layer_idx) - 1; last_idx >= std::max(int(layer_idx - top_solid_layers), int(0)); --last_idx) { + float offset_value = float(layer_idx - last_idx) * (-1.0f) * extrusion_width; + if (offset_ex(top_expolygon, offset_value).empty()) + continue; + ExPolygons layer_slices_trimmed = layers[last_idx]->lslices; + + for (int last_idx_1 = last_idx; last_idx_1 < int(layer_idx); ++last_idx_1) { + layer_slices_trimmed = intersection_ex(layer_slices_trimmed, layers[last_idx_1 + 1]->lslices); + } + + ExPolygons offset_e = offset_ex(layer_slices_trimmed, offset_value); + ExPolygons intersection_poly_2 = intersection_ex(triangles_by_color_top[color_idx][layer_idx], offset_e); + triangles_by_color_top[color_idx][last_idx].insert(triangles_by_color_top[color_idx][last_idx].end(), intersection_poly_2.begin(), + intersection_poly_2.end()); + } + } + } + } + + for (size_t layer_idx = 0; layer_idx < print_object.layers().size(); ++layer_idx) { + BOOST_LOG_TRIVIAL(debug) << "MMU segmentation of bottom layer: " << layer_idx; + float extrusion_width = scale_(get_extrusion_width(layer_idx)); + int bottom_solid_layers = get_bottom_solid_layers(layer_idx); + ExPolygons bottom_expolygon = bottom_layers[layer_idx]; + if (bottom_expolygon.empty()) + continue; + + for (size_t color_idx = 0; color_idx < triangles_by_color.size(); ++color_idx) { + if (triangles_by_color[color_idx][layer_idx].empty()) + continue; + + ExPolygons intersection_poly = intersection_ex(triangles_by_color[color_idx][layer_idx], bottom_expolygon); + if (!intersection_poly.empty()) { + triangles_by_color_bottom[color_idx][layer_idx].insert(triangles_by_color_bottom[color_idx][layer_idx].end(), intersection_poly.begin(), + intersection_poly.end()); + for (size_t last_idx = layer_idx + 1; last_idx < std::min(layer_idx + bottom_solid_layers, layers.size()); ++last_idx) { + float offset_value = float(last_idx - layer_idx) * (-1.0f) * extrusion_width; + if (offset_ex(bottom_expolygon, offset_value).empty()) + continue; + ExPolygons layer_slices_trimmed = layers[last_idx]->lslices; + for (int last_idx_1 = int(last_idx); last_idx_1 > int(layer_idx); --last_idx_1) { + layer_slices_trimmed = intersection_ex(layer_slices_trimmed, offset_ex(layers[last_idx_1 - 1]->lslices, offset_value)); + } + + ExPolygons offset_e = offset_ex(layer_slices_trimmed, offset_value); + ExPolygons intersection_poly_2 = intersection_ex(triangles_by_color_bottom[color_idx][layer_idx], offset_e); + triangles_by_color_bottom[color_idx][last_idx].insert(triangles_by_color_bottom[color_idx][last_idx].end(), intersection_poly_2.begin(), + intersection_poly_2.end()); + } + } + } + } + + std::vector> triangles_by_color_merged(3); + triangles_by_color_merged.assign(3, std::vector(layers.size())); + for (size_t layer_idx = 0; layer_idx < layers.size(); ++layer_idx) { + for (size_t color_idx = 0; color_idx < triangles_by_color_merged.size(); ++color_idx) { + triangles_by_color_merged[color_idx][layer_idx].insert(triangles_by_color_merged[color_idx][layer_idx].end(), + triangles_by_color_bottom[color_idx][layer_idx].begin(), + triangles_by_color_bottom[color_idx][layer_idx].end()); + triangles_by_color_merged[color_idx][layer_idx].insert(triangles_by_color_merged[color_idx][layer_idx].end(), + triangles_by_color_top[color_idx][layer_idx].begin(), + triangles_by_color_top[color_idx][layer_idx].end()); + triangles_by_color_merged[color_idx][layer_idx] = union_ex(triangles_by_color_merged[color_idx][layer_idx]); + } + + // Cut all colors for cases when two colors are overlapping + for (size_t color_idx = 1; color_idx < triangles_by_color_merged.size(); ++color_idx) { + triangles_by_color_merged[color_idx][layer_idx] = diff_ex(triangles_by_color_merged[color_idx][layer_idx], + triangles_by_color_merged[color_idx - 1][layer_idx]); + } + } + + return triangles_by_color_merged; +} + +static std::vector>> merge_segmented_layers( + const std::vector>> &segmented_regions, const std::vector> &top_and_bottom_layers) +{ + std::vector>> segmented_regions_merged(segmented_regions.size()); + + tbb::parallel_for(tbb::blocked_range(0, segmented_regions.size()), [&](const tbb::blocked_range &range) { + for (size_t layer_idx = range.begin(); layer_idx < range.end(); ++layer_idx) { + BOOST_LOG_TRIVIAL(debug) << "MMU segmentation - merging region: " << layer_idx; + for (const std::pair &colored_expoly : segmented_regions[layer_idx]) { + ExPolygons cut_colored_expoly = {colored_expoly.first}; + for (const std::vector &top_and_bottom_layer : top_and_bottom_layers) + cut_colored_expoly = diff_ex(cut_colored_expoly, top_and_bottom_layer[layer_idx]); + for (ExPolygon &ex_poly : cut_colored_expoly) + segmented_regions_merged[layer_idx].emplace_back(ex_poly, colored_expoly.second); + + } + + for (size_t color_idx = 0; color_idx < top_and_bottom_layers.size(); ++color_idx) { + ExPolygons top_and_bottom_expoly = top_and_bottom_layers[color_idx][layer_idx]; + for (const ExPolygon &expoly : top_and_bottom_expoly) { segmented_regions_merged[layer_idx].emplace_back(expoly, color_idx); } + } + } + }); // end of parallel_for + + return segmented_regions_merged; +} + +std::vector>> multi_material_segmentation_by_painting(const PrintObject &print_object) +{ + std::vector>> segmented_regions(print_object.layers().size()); + std::vector> painted_lines(print_object.layers().size()); + std::vector edge_grids(print_object.layers().size()); + const ConstLayerPtrsAdaptor layers = print_object.layers(); + + for (size_t layer_idx = 0; layer_idx < layers.size(); ++layer_idx) { + const Layer *layer = layers[layer_idx]; + BoundingBox bbox(get_extents(layer->lslices)); + bbox.offset(SCALED_EPSILON); + edge_grids[layer_idx].set_bbox(bbox); + edge_grids[layer_idx].create(layer->lslices, coord_t(scale_(10.))); + } + + for (const ModelVolume *mv : print_object.model_object()->volumes) { + for (const auto ¶ms : {std::make_pair(EnforcerBlockerType::ENFORCER, 1), std::make_pair(EnforcerBlockerType::BLOCKER, 2)}) { + const indexed_triangle_set custom_facets = mv->mmu_segmentation_facets.get_facets(*mv, params.first); + if (!mv->is_model_part() || custom_facets.indices.empty()) + continue; + + const Transform3f tr = print_object.trafo().cast() * mv->get_matrix().cast(); + for (size_t facet_idx = 0; facet_idx < custom_facets.indices.size(); ++facet_idx) { + float min_z = std::numeric_limits::max(); + float max_z = std::numeric_limits::lowest(); + + std::array facet; + Points projected_facet(3); + 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()); + } + + // 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(); }); + + for (int p_idx = 0; p_idx < 3; ++p_idx) { + projected_facet[p_idx] = Point(scale_(facet[p_idx].x()), scale_(facet[p_idx].y())); + projected_facet[p_idx] = projected_facet[p_idx] - print_object.center_offset(); + } + + ExPolygon triangle = ExPolygon(projected_facet); + + // Find lowest slice not below the triangle. + auto first_layer = std::upper_bound(print_object.layers().begin(), print_object.layers().end(), float(min_z - EPSILON), + [](float z, const Layer *l1) { return z < l1->slice_z; }); + auto last_layer = std::upper_bound(print_object.layers().begin(), print_object.layers().end(), float(max_z + EPSILON), + [](float z, const Layer *l1) { return z < l1->slice_z; }); + --last_layer; + + for (auto layer_it = first_layer; layer_it != (last_layer + 1); ++layer_it) { + const Layer *layer = *layer_it; + size_t layer_idx = layer_it - print_object.layers().begin(); + if (facet[0].z() > layer->slice_z || layer->slice_z > facet[2].z()) + continue; + + // 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 (facet[1].z() > layer->slice_z) { + // [P0, P2] a [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 if (facet[1].z() <= layer->slice_z) { + // [P0, P2] a [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]); + } + + Point line_start(scale_(line_start_f.x()), scale_(line_start_f.y())); + Point line_end(scale_(line_end_f.x()), scale_(line_end_f.y())); + line_start -= print_object.center_offset(); + line_end -= print_object.center_offset(); + + std::vector painted_line_tmp; + PaintedLineVisitor visitor(edge_grids[layer_idx], painted_line_tmp); + visitor.reset(); + visitor.line_to_test.a = line_start; + visitor.line_to_test.b = line_end; + visitor.color = params.second; + edge_grids[layer_idx].visit_cells_intersecting_line(line_start, line_end, visitor); + + if (!painted_line_tmp.empty()) + painted_lines[layer_idx].insert(painted_lines[layer_idx].end(), painted_line_tmp.begin(), painted_line_tmp.end()); + } + } + } + } + + tbb::parallel_for(tbb::blocked_range(0, print_object.layers().size()), [&](const tbb::blocked_range &range) { + for (size_t layer_idx = range.begin(); layer_idx < range.end(); ++layer_idx) { + // for(size_t layer_idx = 0; layer_idx < print_object.layers().size(); ++layer_idx) { + BOOST_LOG_TRIVIAL(debug) << "MMU segmentation of layer: " << layer_idx; + auto comp = [&edge_grids, &layer_idx](const PaintedLine &first, const PaintedLine &second) { + Point first_start_p = *(edge_grids[layer_idx].contours()[first.contour_idx].begin() + 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 && + Line(first_start_p, first.projected_line.a).length() < Line(first_start_p, second.projected_line.a).length()))); + }; + + std::sort(painted_lines[layer_idx].begin(), painted_lines[layer_idx].end(), comp); + std::vector &painted_lines_single = painted_lines[layer_idx]; + + if (!painted_lines_single.empty()) { + Polygons original_polygons; + for (const Slic3r::EdgeGrid::Contour &contour : edge_grids[layer_idx].contours()) { + Points points; + for (const Point &point : contour) points.emplace_back(point); + original_polygons.emplace_back(points); + } + + std::vector> color_poly = colorize_polygons(original_polygons, painted_lines_single); + MMU_Graph graph = build_graph(layer_idx, color_poly); + remove_multiple_edges_in_vertices(graph, color_poly); + graph.remove_nodes_with_one_arc(); + std::vector> segmentation = extract_colored_segments(graph); + for (const std::pair ®ion : segmentation) + segmented_regions[layer_idx].emplace_back(region); + } + } + }); // end of parallel_for + + if (auto w = print_object.print()->config().mmu_segmented_region_max_width; w > 0.f) + cut_segmented_layers(layers, segmented_regions, float(-scale_(w))); + +// return segmented_regions; + std::vector> top_and_bottom_layers = mmu_segmentation_top_and_bottom_layers(print_object); + std::vector>> segmented_regions_merged = merge_segmented_layers(segmented_regions, top_and_bottom_layers); + return segmented_regions_merged; +} + +} // namespace Slic3r diff --git a/src/libslic3r/MultiMaterialSegmentation.hpp b/src/libslic3r/MultiMaterialSegmentation.hpp new file mode 100644 index 000000000..213744f15 --- /dev/null +++ b/src/libslic3r/MultiMaterialSegmentation.hpp @@ -0,0 +1,18 @@ +#ifndef slic3r_MultiMaterialSegmentation_hpp_ +#define slic3r_MultiMaterialSegmentation_hpp_ + +#include +#include + +namespace Slic3r { + + +class PrintObject; +class ExPolygon; + +// Returns MMU segmentation based on painting in MMU segmentation gizmo +std::vector>> multi_material_segmentation_by_painting(const PrintObject &print_object); + +} // namespace Slic3r + +#endif // slic3r_MultiMaterialSegmentation_hpp_ diff --git a/src/libslic3r/Print.hpp b/src/libslic3r/Print.hpp index 6903fc608..91f86d010 100644 --- a/src/libslic3r/Print.hpp +++ b/src/libslic3r/Print.hpp @@ -240,11 +240,6 @@ public: // Helpers to project custom facets on slices void project_and_append_custom_facets(bool seam, EnforcerBlockerType type, std::vector& expolys) const; - // Returns MMU segmentation based on painting in MMU segmentation gizmo - std::vector>> mmu_segmentation_by_painting(); - // Returns MMU segmentation of top and bottom layers based on painting in MMU segmentation gizmo - std::vector> mmu_segmentation_top_and_bottom_layers(); - private: // to be called from Print only. friend class Print; diff --git a/src/libslic3r/PrintObject.cpp b/src/libslic3r/PrintObject.cpp index b59db7733..f85568ba5 100644 --- a/src/libslic3r/PrintObject.cpp +++ b/src/libslic3r/PrintObject.cpp @@ -6,6 +6,7 @@ #include "Geometry.hpp" #include "I18N.hpp" #include "Layer.hpp" +#include "MultiMaterialSegmentation.hpp" #include "SupportMaterial.hpp" #include "Surface.hpp" #include "Slicing.hpp" @@ -13,25 +14,16 @@ #include "Utils.hpp" #include "Fill/FillAdaptive.hpp" #include "Format/STL.hpp" -#include "EdgeGrid.hpp" -#include "VoronoiVisualUtils.hpp" -#include "VoronoiOffset.hpp" #include #include #include -#include #include #include #include -#include -#include -#include -#include - //! macro used to mark string used at localization, //! return same string #define L(s) Slic3r::I18N::translate(s) @@ -52,31 +44,6 @@ #include #endif -namespace Slic3r { -struct ColoredLine { - Line line; - int color; - int poly_idx = -1; - int local_line_idx = -1; -}; -} - -#include -namespace boost { namespace polygon { -template <> -struct geometry_concept { typedef segment_concept type; }; - -template <> -struct segment_traits { - typedef coord_t coordinate_type; - typedef Slic3r::Point point_type; - - static inline point_type get(const Slic3r::ColoredLine& line, direction_1d dir) { - return dir.to_int() ? line.line.b : line.line.a; - } -}; -} } - namespace Slic3r { // Constructor is called from the main thread, therefore all Model / ModelObject / ModelIntance data are valid. @@ -1949,7 +1916,7 @@ void PrintObject::_slice(const std::vector &layer_height_profile) } size_t region_count_before_change = this->region_volumes.size(); - std::vector>> segmented_regions = this->mmu_segmentation_by_painting(); + std::vector>> segmented_regions = multi_material_segmentation_by_painting(*this); // Skip region with default extruder for (size_t region_idx = 1; region_idx < 3; ++region_idx) { std::vector c_layers(m_layers.size()); @@ -3122,1454 +3089,4 @@ const Layer *PrintObject::get_first_layer_bellow_printz(coordf_t print_z, coordf return (it == m_layers.begin()) ? nullptr : *(--it); } -// --------------------MMU_START---------------------- -// 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.cast() + t1 * v1).cast(); - Point p2 = (projection_l.a.cast() + 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 = 1; -}; - -struct PaintedLineVisitor -{ - PaintedLineVisitor(const EdgeGrid::Grid &grid, std::vector &painted_lines) : grid(grid), painted_lines(painted_lines) - { - painted_lines_set.reserve(painted_lines.capacity()); - } - - 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); - 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 v1 = (line_to_test.b - line_to_test.a).cast().normalized(); - const Vec2d v2 = (grid_line.b - grid_line.a).cast().normalized(); - double angle = ::acos(clamp(-1.0, 1.0, v1.dot(v2))); - double angle_deg = Geometry::rad2deg(angle); - // When lines have too different length, it is necessary to normalize them - if ((angle_deg >= 0 && angle_deg <= 30) || (angle_deg >= 150)) { - Line line_to_test_projected; - project_line_on_line(grid_line, line_to_test, &line_to_test_projected); - - if (painted_lines_set.find(*it_contour_and_segment) == painted_lines_set.end()) { - if (Line(grid_line.a, line_to_test_projected.a).length() > Line(grid_line.a, line_to_test_projected.b).length()) { - line_to_test_projected.reverse(); - } - - double dist_1 = grid_line.distance_to(line_to_test.a); - double dist_2 = grid_line.distance_to(line_to_test.b); - double dist_3 = line_to_test.distance_to(grid_line.a); - double dist_4 = line_to_test.distance_to(grid_line.b); - double total_dist = std::min(std::min(dist_1, dist_2), std::min(dist_3, dist_4)); - - if (total_dist > 50 * SCALED_EPSILON) - continue; - - painted_lines.push_back({it_contour_and_segment->first, it_contour_and_segment->second, line_to_test_projected, this->color}); - painted_lines_set.insert(*it_contour_and_segment); - } - } - } - // Continue traversing the grid along the edge. - return true; - } - - const EdgeGrid::Grid &grid; - std::vector &painted_lines; - Line line_to_test; - std::unordered_set, boost::hash>> painted_lines_set; - int color = -1; -}; - -static std::vector to_colored_lines(const Polygon &polygon, int color) -{ - std::vector lines; - lines.reserve(polygon.points.size()); - if (polygon.points.size() > 2) { - for (auto it = polygon.points.begin(); it != polygon.points.end() - 1; ++it) - lines.push_back({Line(*it, *(it + 1)), color}); - lines.push_back({Line(polygon.points.back(), polygon.points.front()), color}); - } - return lines; -} - -static Polygon colored_points_to_polygon(const std::vector &lines) -{ - Points out; - for (const ColoredLine &l : lines) - out.emplace_back(l.line.a); - return Polygon(out); -} - -static Polygons colored_points_to_polygon(const std::vector> &lines) -{ - Polygons out; - for (const std::vector &l : lines) - out.emplace_back(colored_points_to_polygon(l)); - return out; -} - -inline std::vector to_lines(const std::vector> &c_lines) -{ - size_t n_lines = 0; - for (const auto &c_line : c_lines) - n_lines += c_line.size(); - std::vector lines; - lines.reserve(n_lines); - for (const auto &c_line : c_lines) - lines.insert(lines.end(), c_line.begin(), c_line.end()); - return lines; -} - -// Double vertex equal to a coord_t point after conversion to double. -template -inline bool vertex_equal_to_point(const VertexType &vertex, const Point &ipt) -{ - // Convert ipt to doubles, force the 80bit FPU temporary to 64bit and then compare. - // This should work with any settings of math compiler switches and the C++ compiler - // shall understand the memcpies as type punning and it shall optimize them out. - using ulp_cmp_type = boost::polygon::detail::ulp_comparison; - ulp_cmp_type ulp_cmp; - static constexpr int ULPS = boost::polygon::voronoi_diagram_traits::vertex_equality_predicate_type::ULPS; - return ulp_cmp(vertex.x(), double(ipt.x()), ULPS) == ulp_cmp_type::EQUAL && - ulp_cmp(vertex.y(), double(ipt.y()), ULPS) == ulp_cmp_type::EQUAL; -} - -bool vertex_equal_to_point(const Voronoi::VD::vertex_type *vertex, const Point &ipt) { - return vertex_equal_to_point(*vertex, ipt); -} - -static std::vector> get_segments(const std::vector &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>> get_all_segments(const std::vector> &color_poly) -{ - std::vector>> all_segments(color_poly.size()); - for (size_t poly_idx = 0; poly_idx < color_poly.size(); ++poly_idx) { - const std::vector &c_polygon = color_poly[poly_idx]; - all_segments[poly_idx] = get_segments(c_polygon); - } - return all_segments; -} - -static std::vector colorize_line(const Line & line_to_process, - const size_t start_idx, - const size_t end_idx, - std::vector &painted_lines) -{ - std::vector internal_painted; - for (size_t line_idx = start_idx; line_idx <= end_idx; ++line_idx) { internal_painted.emplace_back(painted_lines[line_idx]); } - const int filter_eps_value = scale_(0.1f); - std::vector filtered_lines; - filtered_lines.emplace_back(internal_painted.front()); - for (size_t line_idx = 1; line_idx < internal_painted.size(); ++line_idx) { - PaintedLine &prev = filtered_lines.back(); - PaintedLine &curr = internal_painted[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) { - } else { - if (prev.color == curr.color) { - prev.projected_line.b = curr.projected_line.b; - } else { - curr.projected_line.a = prev.projected_line.b; - filtered_lines.emplace_back(curr); - } - } - } - } - - std::vector final_lines; - double dist_to_start = (filtered_lines.front().projected_line.a - line_to_process.a).cast().norm(); - if (dist_to_start <= filter_eps_value) { - filtered_lines.front().projected_line.a = line_to_process.a; - final_lines.push_back({filtered_lines.front().projected_line, filtered_lines.front().color}); - } else { - final_lines.push_back({Line(line_to_process.a, filtered_lines.front().projected_line.a), 0}); - final_lines.push_back({filtered_lines.front().projected_line, filtered_lines.front().color}); - } - - for (size_t line_idx = 1; line_idx < filtered_lines.size(); ++line_idx) { - ColoredLine &prev = final_lines.back(); - PaintedLine &curr = filtered_lines[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}); - } - } - - double dist_to_end = (final_lines.back().line.b - line_to_process.b).cast().norm(); - if (dist_to_end <= filter_eps_value) - final_lines.back().line.b = line_to_process.b; - else - final_lines.push_back({Line(final_lines.back().line.b, line_to_process.b), 0}); - - for (size_t line_idx = 1; line_idx < final_lines.size(); ++line_idx) - assert(final_lines[line_idx - 1].line.b == final_lines[line_idx].line.a); - - 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; - } - - std::vector colored_lines_simpl; - colored_lines_simpl.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_simpl.back().color == line_0.color) - colored_lines_simpl.back().line.b = line_0.line.b; - else - colored_lines_simpl.emplace_back(line_0); - } - - final_lines = colored_lines_simpl; - - 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 std::vector colorize_polygon(const Polygon &poly, const size_t start_idx, const size_t end_idx, std::vector &painted_lines) -{ - std::vector new_lines; - Lines lines = poly.lines(); - - for (size_t idx = 0; idx < painted_lines[start_idx].line_idx; ++idx) - new_lines.emplace_back(ColoredLine{lines[idx], 0}); - - for (size_t first_idx = start_idx; first_idx <= end_idx; ++first_idx) { - size_t second_idx = first_idx; - while (second_idx <= end_idx && painted_lines[first_idx].line_idx == painted_lines[second_idx].line_idx) ++second_idx; - --second_idx; - - assert(painted_lines[first_idx].line_idx == painted_lines[second_idx].line_idx); - std::vector lines_c_line = colorize_line(lines[painted_lines[first_idx].line_idx], first_idx, second_idx, painted_lines); - new_lines.insert(new_lines.end(), lines_c_line.begin(), lines_c_line.end()); - - if (second_idx + 1 <= end_idx) - for (size_t idx = painted_lines[second_idx].line_idx + 1; idx < painted_lines[second_idx + 1].line_idx; ++idx) - new_lines.emplace_back(ColoredLine{lines[idx], 0}); - - first_idx = second_idx; - } - - for (size_t idx = painted_lines[end_idx].line_idx + 1; idx < poly.size(); ++idx) - new_lines.emplace_back(ColoredLine{lines[idx], 0}); - - 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; - }; - - for (size_t pair_idx = 1; pair_idx < segments.size(); ++pair_idx) { - int color0 = new_lines[segments[pair_idx - 1].first].color; - int color1 = new_lines[segments[pair_idx - 0].first].color; - - double seg0l = segment_length(segments[pair_idx - 1]); - double seg1l = segment_length(segments[pair_idx - 0]); - - if (color0 != color1 && seg0l >= scale_(0.1) && seg1l <= scale_(0.2)) { - for (size_t seg_start_idx = segments[pair_idx].first; seg_start_idx != segments[pair_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[pair_idx].second].color = color0; - } - } - - segments = get_segments(new_lines); - for (size_t pair_idx = 1; pair_idx < segments.size(); ++pair_idx) { - int color0 = new_lines[segments[pair_idx - 1].first].color; - int color1 = new_lines[segments[pair_idx - 0].first].color; - double seg1l = segment_length(segments[pair_idx - 0]); - - if (color0 >= 1 && color0 != color1 && seg1l <= scale_(0.2)) { - for (size_t seg_start_idx = segments[pair_idx].first; seg_start_idx != segments[pair_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[pair_idx].second].color = color0; - } - } - - for (size_t pair_idx = 2; pair_idx < segments.size(); ++pair_idx) { - int color0 = new_lines[segments[pair_idx - 2].first].color; - int color1 = new_lines[segments[pair_idx - 1].first].color; - int color2 = new_lines[segments[pair_idx - 0].first].color; - - if (color0 > 0 && color0 == color2 && color0 != color1 && segment_length(segments[pair_idx - 1]) <= scale_(0.5)) { - for (size_t seg_start_idx = segments[pair_idx].first; seg_start_idx != segments[pair_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[pair_idx].second].color = color0; - } - } - - return new_lines; -} - -static std::vector> colorize_polygons(const Polygons &polygons, std::vector &painted_lines) -{ - const size_t start_idx = 0; - const size_t end_idx = painted_lines.size() - 1; - - std::vector> new_polygons; - - for (size_t idx = 0; idx < painted_lines[start_idx].contour_idx; ++idx) - new_polygons.emplace_back(to_colored_lines(polygons[idx], 0)); - - for (size_t first_idx = start_idx; first_idx <= end_idx; ++first_idx) { - size_t second_idx = first_idx; - while (second_idx <= end_idx && painted_lines[first_idx].contour_idx == painted_lines[second_idx].contour_idx) - ++second_idx; - --second_idx; - - assert(painted_lines[first_idx].contour_idx == painted_lines[second_idx].contour_idx); - std::vector polygon_c = colorize_polygon(polygons[painted_lines[first_idx].contour_idx], first_idx, second_idx, painted_lines); - new_polygons.emplace_back(polygon_c); - - if (second_idx + 1 <= end_idx) - for (size_t idx = painted_lines[second_idx].contour_idx + 1; idx < painted_lines[second_idx + 1].contour_idx; ++idx) - new_polygons.emplace_back(to_colored_lines(polygons[idx], 0)); - - first_idx = second_idx; - } - - for (size_t idx = painted_lines[end_idx].contour_idx + 1; idx < polygons.size(); ++idx) - new_polygons.emplace_back(to_colored_lines(polygons[idx], 0)); - - return new_polygons; -} - -using boost::polygon::voronoi_diagram; - -struct MMU_Graph -{ - enum class ARC_TYPE { BORDER, NON_BORDER }; - - struct Arc - { - size_t from_idx; - size_t to_idx; - int color; - ARC_TYPE type; - bool used{false}; - - bool operator==(const Arc &rhs) const { return (from_idx == rhs.from_idx) && (to_idx == rhs.to_idx) && (color == rhs.color) && (type == rhs.type); } - bool operator!=(const Arc &rhs) const { return !operator==(rhs); } - }; - - struct Node - { - Point point; - std::list neighbours; - - void remove_edge(const size_t to_idx) - { - for (auto arc_it = this->neighbours.begin(); arc_it != this->neighbours.end(); ++arc_it) { - if (arc_it->to_idx == to_idx) { - assert(arc_it->type != ARC_TYPE::BORDER); - this->neighbours.erase(arc_it); - break; - } - } - } - }; - - std::vector nodes; - std::vector arcs; - size_t all_border_points{}; - - std::vector polygon_idx_offset; - std::vector polygon_sizes; - - void remove_edge(const size_t from_idx, const size_t to_idx) - { - nodes[from_idx].remove_edge(to_idx); - nodes[to_idx].remove_edge(from_idx); - } - - size_t get_global_index(const size_t poly_idx, const size_t point_idx) const { return polygon_idx_offset[poly_idx] + point_idx; } - - void append_edge(const size_t &from_idx, const size_t &to_idx, int color = -1, ARC_TYPE type = ARC_TYPE::NON_BORDER) - { - // Don't append duplicate edges between the same nodes. - for (const MMU_Graph::Arc &arc : this->nodes[from_idx].neighbours) - if (arc.to_idx == to_idx) - return; - for (const MMU_Graph::Arc &arc : this->nodes[to_idx].neighbours) - if (arc.to_idx == to_idx) - return; - - this->nodes[from_idx].neighbours.push_back({from_idx, to_idx, color, type}); - this->nodes[to_idx].neighbours.push_back({to_idx, from_idx, color, type}); - this->arcs.push_back({from_idx, to_idx, color, type}); - this->arcs.push_back({to_idx, from_idx, color, type}); - } - - // Ignoring arcs in the opposite direction - MMU_Graph::Arc get_arc(size_t idx) { return this->arcs[idx * 2]; } - - size_t nodes_count() const { return this->nodes.size(); } - - void remove_nodes_with_one_arc() - { - std::queue update_queue; - for (const MMU_Graph::Node &node : this->nodes) - if (node.neighbours.size() == 1) update_queue.emplace(&node - &this->nodes.front()); - - while (!update_queue.empty()) { - size_t node_from_idx = update_queue.front(); - MMU_Graph::Node &node_from = this->nodes[update_queue.front()]; - update_queue.pop(); - if (node_from.neighbours.empty()) - continue; - - assert(node_from.neighbours.size() == 1); - size_t node_to_idx = node_from.neighbours.front().to_idx; - MMU_Graph::Node &node_to = this->nodes[node_to_idx]; - this->remove_edge(node_from_idx, node_to_idx); - if (node_to.neighbours.size() == 1) - update_queue.emplace(node_to_idx); - } - } - - void add_contours(const std::vector> &color_poly) - { - this->all_border_points = nodes.size(); - this->polygon_sizes = std::vector(color_poly.size()); - for (size_t polygon_idx = 0; polygon_idx < color_poly.size(); ++polygon_idx) - this->polygon_sizes[polygon_idx] = color_poly[polygon_idx].size(); - this->polygon_idx_offset = std::vector(color_poly.size()); - this->polygon_idx_offset[0] = 0; - for (size_t polygon_idx = 1; polygon_idx < color_poly.size(); ++polygon_idx) { - this->polygon_idx_offset[polygon_idx] = this->polygon_idx_offset[polygon_idx - 1] + color_poly[polygon_idx - 1].size(); - } - - size_t poly_idx = 0; - for (const std::vector &color_lines : color_poly) { - size_t line_idx = 0; - for (const ColoredLine &color_line : color_lines) { - size_t from_idx = this->get_global_index(poly_idx, line_idx); - size_t to_idx = this->get_global_index(poly_idx, (line_idx + 1) % color_lines.size()); - this->append_edge(from_idx, to_idx, color_line.color, ARC_TYPE::BORDER); - ++line_idx; - } - ++poly_idx; - } - } - - // Nodes 0..all_border_points are only one with are on countour. Other vertexis are consider as not on coouter. So we check if base on attach index - inline bool is_vertex_on_contour(const Voronoi::VD::vertex_type *vertex) const - { - assert(vertex != nullptr); - return vertex->color() < this->all_border_points; - } - - inline bool is_edge_attach_to_contour(const voronoi_diagram::const_edge_iterator &edge_iterator) const - { - return this->is_vertex_on_contour(edge_iterator->vertex0()) || this->is_vertex_on_contour(edge_iterator->vertex1()); - } - - inline bool is_edge_connecting_two_contour_vertices(const voronoi_diagram::const_edge_iterator &edge_iterator) const - { - return this->is_vertex_on_contour(edge_iterator->vertex0()) && this->is_vertex_on_contour(edge_iterator->vertex1()); - } -}; - -namespace bg = boost::geometry; -namespace bgm = boost::geometry::model; -namespace bgi = boost::geometry::index; - -// float is needed because for coord_t bgi::intersects throws "bad numeric conversion: positive overflow" -using rtree_point_t = bgm::point; -using rtree_t = bgi::rtree, bgi::rstar<16, 4>>; - -static inline rtree_point_t mk_rtree_point(const Point &pt) { return rtree_point_t(float(pt.x()), float(pt.y())); } - -static inline Point mk_point(const Voronoi::VD::vertex_type *point) { return Point(coord_t(point->x()), coord_t(point->y())); } - -static inline Point mk_point(const Voronoi::Internal::point_type &point) { return Point(coord_t(point.x()), coord_t(point.y())); } - -static inline Point mk_point(const voronoi_diagram::vertex_type &point) { return Point(coord_t(point.x()), coord_t(point.y())); } - -static inline void mark_processed(const voronoi_diagram::const_edge_iterator &edge_iterator) -{ - edge_iterator->color(true); - edge_iterator->twin()->color(true); -} - -// Return true, if "p" is closer to line.a, then line.b -static inline bool is_point_closer_to_beginning_of_line(const Line &line, const Point &p) -{ - return (p - line.a).cast().squaredNorm() < (p - line.b).cast().squaredNorm(); -} - -static inline bool has_same_color(const ColoredLine &cl1, const ColoredLine &cl2) { return cl1.color == cl2.color; } - -// 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 inline bool line_intersection_with_epsilon(const Line &line_to_extend, const Line &other, Point *intersection) -{ - Line extended_line = line_to_extend; - extended_line.extend(15 * SCALED_EPSILON); - return extended_line.intersection(other, intersection); -} - -// For every ColoredLine in lines_colored_out, assign the index of the polygon to which belongs and also the index of this line inside of the polygon. -static inline void init_polygon_indices(const MMU_Graph &graph, - const std::vector> &color_poly, - std::vector &lines_colored_out) -{ - size_t poly_idx = 0; - for (const std::vector &color_lines : color_poly) { - size_t line_idx = 0; - for (size_t color_line_idx = 0; color_line_idx < color_lines.size(); ++color_line_idx) { - size_t from_idx = graph.get_global_index(poly_idx, line_idx); - lines_colored_out[from_idx].poly_idx = int(poly_idx); - lines_colored_out[from_idx].local_line_idx = int(line_idx); - ++line_idx; - } - ++poly_idx; - } -} - -static MMU_Graph build_graph(size_t layer_idx, const std::vector> &color_poly) -{ - Geometry::VoronoiDiagram vd; - std::vector lines_colored = to_lines(color_poly); - Polygons color_poly_tmp = colored_points_to_polygon(color_poly); - const Points points = to_points(color_poly_tmp); - const Lines lines = to_lines(color_poly_tmp); - - boost::polygon::construct_voronoi(lines_colored.begin(), lines_colored.end(), &vd); - MMU_Graph graph; - for (const Point &point : points) - graph.nodes.push_back({point}); - - graph.add_contours(color_poly); - init_polygon_indices(graph, color_poly, lines_colored); - - assert(graph.nodes.size() == lines_colored.size()); - - // All Voronoi vertices are post-processes to merge very close vertices to single. Witch Eliminates issues with intersection edges. - // Also, Voronoi vertices outside of the bounding of input polygons are throw away by marking them. - auto append_voronoi_vertices_to_graph = [&graph, &color_poly_tmp, &vd]() -> void { - auto is_equal_points = [](const Point &p1, const Point &p2) { return p1 == p2 || (p1 - p2).cast().norm() <= 3 * SCALED_EPSILON; }; - - BoundingBox bbox = get_extents(color_poly_tmp); - bbox.offset(SCALED_EPSILON); - // EdgeGrid is used for vertices near to contour and rtree for other vertices - // FIXME Lukas H.: Get rid of EdgeGrid and rtree. Use only one structure for both cases. - EdgeGrid::Grid grid; - grid.set_bbox(bbox); - grid.create(color_poly_tmp, coord_t(scale_(10.))); - rtree_t rtree; - for (const voronoi_diagram::vertex_type &vertex : vd.vertices()) { - vertex.color(-1); - Point vertex_point = mk_point(vertex); - - const Point &first_point = graph.nodes[graph.get_arc(vertex.incident_edge()->cell()->source_index()).from_idx].point; - const Point &second_point = graph.nodes[graph.get_arc(vertex.incident_edge()->twin()->cell()->source_index()).from_idx].point; - - if (vertex_equal_to_point(&vertex, first_point)) { - assert(vertex.color() != vertex.incident_edge()->cell()->source_index()); - assert(vertex.color() != vertex.incident_edge()->twin()->cell()->source_index()); - vertex.color(graph.get_arc(vertex.incident_edge()->cell()->source_index()).from_idx); - } else if (vertex_equal_to_point(&vertex, second_point)) { - assert(vertex.color() != vertex.incident_edge()->cell()->source_index()); - assert(vertex.color() != vertex.incident_edge()->twin()->cell()->source_index()); - vertex.color(graph.get_arc(vertex.incident_edge()->twin()->cell()->source_index()).from_idx); - } else if (bbox.contains(vertex_point)) { - EdgeGrid::Grid::ClosestPointResult cp = grid.closest_point_signed_distance(vertex_point, coord_t(3 * SCALED_EPSILON)); - if (cp.valid()) { - size_t global_idx = graph.get_global_index(cp.contour_idx, cp.start_point_idx); - size_t global_idx_next = graph.get_global_index(cp.contour_idx, (cp.start_point_idx + 1) % color_poly_tmp[cp.contour_idx].points.size()); - vertex.color(is_equal_points(vertex_point, graph.nodes[global_idx].point) ? global_idx : global_idx_next); - } else { - if (rtree.empty()) { - rtree.insert(std::make_pair(mk_rtree_point(vertex_point), graph.nodes_count())); - vertex.color(graph.nodes_count()); - graph.nodes.push_back({vertex_point}); - } else { - std::vector> closest; - rtree.query(bgi::nearest(mk_rtree_point(vertex_point), 1), std::back_inserter(closest)); - assert(!closest.empty()); - rtree_point_t r_point = closest.front().first; - Point closest_p(bg::get<0>(r_point), bg::get<1>(r_point)); - if (Line(vertex_point, closest_p).length() > 3 * SCALED_EPSILON) { - rtree.insert(std::make_pair(mk_rtree_point(vertex_point), graph.nodes_count())); - vertex.color(graph.nodes_count()); - graph.nodes.push_back({vertex_point}); - } else { - vertex.color(closest.front().second); - } - } - } - } - } - }; - - append_voronoi_vertices_to_graph(); - - auto get_prev_contour_line = [&lines_colored, &color_poly, &graph](const voronoi_diagram::const_edge_iterator &edge_it) -> ColoredLine { - size_t contour_line_local_idx = lines_colored[edge_it->cell()->source_index()].local_line_idx; - size_t contour_line_size = color_poly[lines_colored[edge_it->cell()->source_index()].poly_idx].size(); - size_t contour_prev_idx = graph.get_global_index(lines_colored[edge_it->cell()->source_index()].poly_idx, - (contour_line_local_idx > 0) ? contour_line_local_idx - 1 : contour_line_size - 1); - return lines_colored[contour_prev_idx]; - }; - - auto get_next_contour_line = [&lines_colored, &color_poly, &graph](const voronoi_diagram::const_edge_iterator &edge_it) -> ColoredLine { - size_t contour_line_local_idx = lines_colored[edge_it->cell()->source_index()].local_line_idx; - size_t contour_line_size = color_poly[lines_colored[edge_it->cell()->source_index()].poly_idx].size(); - size_t contour_next_idx = graph.get_global_index(lines_colored[edge_it->cell()->source_index()].poly_idx, - (contour_line_local_idx + 1) % contour_line_size); - return lines_colored[contour_next_idx]; - }; - - BoundingBox bbox = get_extents(color_poly_tmp); - bbox.offset(scale_(10.)); - const double bbox_dim_max = double(std::max(bbox.size().x(), bbox.size().y())); - - // Make a copy of the input segments with the double type. - std::vector segments; - for (const Line &line : lines) - segments.emplace_back(Voronoi::Internal::point_type(double(line.a(0)), double(line.a(1))), - Voronoi::Internal::point_type(double(line.b(0)), double(line.b(1)))); - - for (auto edge_it = vd.edges().begin(); edge_it != vd.edges().end(); ++edge_it) { - // Skip second half-edge - if (edge_it->cell()->source_index() > edge_it->twin()->cell()->source_index() || edge_it->color()) - continue; - - if (edge_it->is_infinite()) { - // Infinite edge is leading through a point on the counter, but there are no Voronoi vertices. - // So we could fix this case by computing the intersection between the contour line and infinity edge. - std::vector samples; - Voronoi::Internal::clip_infinite_edge(points, segments, *edge_it, bbox_dim_max, &samples); - if (samples.empty()) - continue; - - const Line edge_line(mk_point(samples[0]), mk_point(samples[1])); - const ColoredLine &contour_line = lines_colored[edge_it->cell()->source_index()]; - Point contour_intersection; - - if (line_intersection_with_epsilon(contour_line.line, edge_line, &contour_intersection)) { - const MMU_Graph::Arc &graph_arc = graph.get_arc(edge_it->cell()->source_index()); - const size_t from_idx = (edge_it->vertex1() != nullptr) ? edge_it->vertex1()->color() : edge_it->vertex0()->color(); - size_t to_idx = ((contour_line.line.a - contour_intersection).cast().squaredNorm() < - (contour_line.line.b - contour_intersection).cast().squaredNorm()) ? - graph_arc.from_idx : - graph_arc.to_idx; - if (from_idx != to_idx && from_idx < graph.nodes_count() && to_idx < graph.nodes_count()) { - graph.append_edge(from_idx, to_idx); - mark_processed(edge_it); - } - } - } else if (edge_it->is_finite()) { - const Point v0 = mk_point(edge_it->vertex0()); - const Point v1 = mk_point(edge_it->vertex1()); - const size_t from_idx = edge_it->vertex0()->color(); - const size_t to_idx = edge_it->vertex1()->color(); - - // Both points are on contour, so skip them. In cases of duplicate Voronoi vertices, skip edges between the same two points. - if (graph.is_edge_connecting_two_contour_vertices(edge_it) || (edge_it->vertex0()->color() == edge_it->vertex1()->color())) continue; - - const Line edge_line(v0, v1); - const Line contour_line = lines_colored[edge_it->cell()->source_index()].line; - const ColoredLine colored_line = lines_colored[edge_it->cell()->source_index()]; - const ColoredLine contour_line_prev = get_prev_contour_line(edge_it); - const ColoredLine contour_line_next = get_next_contour_line(edge_it); - - Point intersection; - if (edge_it->vertex0()->color() >= graph.nodes_count() || edge_it->vertex1()->color() >= graph.nodes_count()) { -// if(edge_it->vertex0()->color() < graph.nodes_count() && !graph.is_vertex_on_contour(edge_it->vertex0())) { -// -// } - if (edge_it->vertex1()->color() < graph.nodes_count() && !graph.is_vertex_on_contour(edge_it->vertex1())) { - Line contour_line_twin = lines_colored[edge_it->twin()->cell()->source_index()].line; - if (line_intersection_with_epsilon(contour_line_twin, edge_line, &intersection)) { - const MMU_Graph::Arc &graph_arc = graph.get_arc(edge_it->twin()->cell()->source_index()); - const size_t to_idx_l = is_point_closer_to_beginning_of_line(contour_line_twin, intersection) ? graph_arc.from_idx : - graph_arc.to_idx; - graph.append_edge(edge_it->vertex1()->color(), to_idx_l); - } else if (line_intersection_with_epsilon(contour_line, edge_line, &intersection)) { - const MMU_Graph::Arc &graph_arc = graph.get_arc(edge_it->cell()->source_index()); - const size_t to_idx_l = is_point_closer_to_beginning_of_line(contour_line, intersection) ? graph_arc.from_idx : graph_arc.to_idx; - graph.append_edge(edge_it->vertex1()->color(), to_idx_l); - } - mark_processed(edge_it); - } - } else if (graph.is_edge_attach_to_contour(edge_it)) { - mark_processed(edge_it); - // Skip edges witch connection two points on a contour - if (graph.is_edge_connecting_two_contour_vertices(edge_it)) - continue; - - if (graph.is_vertex_on_contour(edge_it->vertex0())) { - if (is_point_closer_to_beginning_of_line(contour_line, v0)) { - if (!has_same_color(contour_line_prev, colored_line) && points_inside(contour_line_prev.line, contour_line, v1)) { - graph.append_edge(from_idx, to_idx); - } - } else { - if (!has_same_color(contour_line_next, colored_line) && points_inside(contour_line, contour_line_next.line, v1)) { - graph.append_edge(from_idx, to_idx); - } - } - } else { - assert(graph.is_vertex_on_contour(edge_it->vertex1())); - if (is_point_closer_to_beginning_of_line(contour_line, v1)) { - if (!has_same_color(contour_line_prev, colored_line) && points_inside(contour_line_prev.line, contour_line, v0)) { - graph.append_edge(from_idx, to_idx); - } - } else { - if (!has_same_color(contour_line_next, colored_line) && points_inside(contour_line, contour_line_next.line, v0)) { - graph.append_edge(from_idx, to_idx); - } - } - } - } else if (line_intersection_with_epsilon(contour_line, edge_line, &intersection)) { - mark_processed(edge_it); - Point real_v0 = graph.nodes[edge_it->vertex0()->color()].point; - Point real_v1 = graph.nodes[edge_it->vertex1()->color()].point; - - if (is_point_closer_to_beginning_of_line(contour_line, intersection)) { - Line first_part(intersection, real_v0); - Line second_part(intersection, real_v1); - - if (!has_same_color(contour_line_prev, colored_line)) { - if (points_inside(contour_line_prev.line, contour_line, first_part.b)) { - graph.append_edge(edge_it->vertex0()->color(), graph.get_arc(edge_it->cell()->source_index()).from_idx); - } - if (points_inside(contour_line_prev.line, contour_line, second_part.b)) { - graph.append_edge(edge_it->vertex1()->color(), graph.get_arc(edge_it->cell()->source_index()).from_idx); - } - } - } else { - const size_t int_point_idx = graph.get_arc(edge_it->cell()->source_index()).to_idx; - const Point int_point = graph.nodes[int_point_idx].point; - - const Line first_part(int_point, real_v0); - const Line second_part(int_point, real_v1); - - if (!has_same_color(contour_line_next, colored_line)) { - if (points_inside(contour_line, contour_line_next.line, first_part.b)) { - graph.append_edge(edge_it->vertex0()->color(), int_point_idx); - } - if (points_inside(contour_line, contour_line_next.line, second_part.b)) { - graph.append_edge(edge_it->vertex1()->color(), int_point_idx); - } - } - } - } - } - } - - for (auto edge_it = vd.edges().begin(); edge_it != vd.edges().end(); ++edge_it) { - // Skip second half-edge and processed edges - if (edge_it->cell()->source_index() > edge_it->twin()->cell()->source_index() || edge_it->color()) - continue; - - if (edge_it->is_finite() && !bool(edge_it->color()) && edge_it->vertex0()->color() < graph.nodes_count() && - edge_it->vertex1()->color() < graph.nodes_count()) { - // Skip cases, when the edge is between two same vertices, which is in cases two near vertices were merged together. - if (edge_it->vertex0()->color() == edge_it->vertex1()->color()) - continue; - - size_t from_idx = edge_it->vertex0()->color(); - size_t to_idx = edge_it->vertex1()->color(); - graph.append_edge(from_idx, to_idx); - } - mark_processed(edge_it); - } - - graph.remove_nodes_with_one_arc(); - return graph; -} - -static inline Polygon to_polygon(const Lines &lines) -{ - Polygon poly_out; - poly_out.points.reserve(lines.size()); - for (const Line &line : lines) - poly_out.points.emplace_back(line.a); - return poly_out; -} - -// Returns list of polygons and assigned colors. -// 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. -// Assumes that graph is planar (without self-intersection edges) -static std::vector> extract_colored_segments(MMU_Graph &graph) -{ - // When there is no next arc, then is returned original_arc or edge with is marked as used - auto get_next = [&graph](const Line &process_line, MMU_Graph::Arc &original_arc) -> MMU_Graph::Arc & { - std::vector> sorted_arcs; - for (MMU_Graph::Arc &arc : graph.nodes[original_arc.to_idx].neighbours) { - if (graph.nodes[arc.to_idx].point == process_line.a || arc.used) - continue; - - assert(original_arc.to_idx == arc.from_idx); - Vec2d process_line_vec_n = (process_line.a - process_line.b).cast().normalized(); - Vec2d neighbour_line_vec_n = (graph.nodes[arc.to_idx].point - graph.nodes[arc.from_idx].point).cast().normalized(); - - double angle = ::acos(clamp(-1.0, 1.0, neighbour_line_vec_n.dot(process_line_vec_n))); - if (Slic3r::cross2(neighbour_line_vec_n, process_line_vec_n) < 0.0) - angle = 2.0 * (double) PI - angle; - - sorted_arcs.emplace_back(&arc, angle); - } - - std::sort(sorted_arcs.begin(), sorted_arcs.end(), - [](std::pair &l, std::pair &r) -> bool { return l.second < r.second; }); - - // Try to return left most edge witch is unused - for (auto &sorted_arc : sorted_arcs) - if (!sorted_arc.first->used) - return *sorted_arc.first; - - if (sorted_arcs.empty()) - return original_arc; - - return *(sorted_arcs.front().first); - }; - - std::vector> polygons_segments; - for (MMU_Graph::Node &node : graph.nodes) - for (MMU_Graph::Arc &arc : node.neighbours) - arc.used = false; - - for (size_t node_idx = 0; node_idx < graph.all_border_points; ++node_idx) { - MMU_Graph::Node &node = graph.nodes[node_idx]; - - for (MMU_Graph::Arc &arc : node.neighbours) { - if (arc.type == MMU_Graph::ARC_TYPE::NON_BORDER || arc.used) continue; - - Line process_line(node.point, graph.nodes[arc.to_idx].point); - arc.used = true; - - Lines face_lines; - face_lines.emplace_back(process_line); - Point start_p = process_line.a; - - Line p_vec = process_line; - MMU_Graph::Arc *p_arc = &arc; - do { - MMU_Graph::Arc &next = get_next(p_vec, *p_arc); - face_lines.emplace_back(Line(graph.nodes[next.from_idx].point, graph.nodes[next.to_idx].point)); - if (next.used) break; - - next.used = true; - p_vec = Line(graph.nodes[next.from_idx].point, graph.nodes[next.to_idx].point); - p_arc = &next; - } while (graph.nodes[p_arc->to_idx].point != start_p); - - Polygon poly = to_polygon(face_lines); - if (poly.is_counter_clockwise() && poly.is_valid()) - polygons_segments.emplace_back(poly, arc.color); - } - } - return polygons_segments; -} - -// 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 compute_edge_length(MMU_Graph &graph, size_t start_idx, MMU_Graph::Arc &start_edge) -{ - for (MMU_Graph::Node &node : graph.nodes) - for (MMU_Graph::Arc &arc : node.neighbours) - arc.used = false; - - start_edge.used = true; - MMU_Graph::Arc *arc = &start_edge; - size_t idx = start_idx; - double line_total_length = Line(graph.nodes[idx].point, graph.nodes[arc->to_idx].point).length(); - while (graph.nodes[arc->to_idx].neighbours.size() == 2) { - bool found = false; - for (MMU_Graph::Arc &arc_n : graph.nodes[arc->to_idx].neighbours) { - if (arc_n.type == MMU_Graph::ARC_TYPE::NON_BORDER && !arc_n.used && arc_n.to_idx != idx) { - Line first_line(graph.nodes[idx].point, graph.nodes[arc->to_idx].point); - Line second_line(graph.nodes[arc->to_idx].point, graph.nodes[arc_n.to_idx].point); - - Vec2d first_line_vec = (first_line.a - first_line.b).cast(); - Vec2d second_line_vec = (second_line.b - second_line.a).cast(); - Vec2d first_line_vec_n = first_line_vec.normalized(); - Vec2d second_line_vec_n = second_line_vec.normalized(); - double angle = ::acos(clamp(-1.0, 1.0, first_line_vec_n.dot(second_line_vec_n))); - 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; - - idx = arc->to_idx; - arc = &arc_n; - - line_total_length += Line(graph.nodes[idx].point, graph.nodes[arc->to_idx].point).length(); - arc_n.used = true; - found = true; - break; - } - } - if (!found) - break; - } - - return line_total_length; -} - -// Used for fixing double Voronoi edges for concave parts of the polygon. -static void remove_multiple_edges_in_vertices(MMU_Graph &graph, const std::vector> &color_poly) -{ - std::vector>> colored_segments = get_all_segments(color_poly); - for (const std::vector> &colored_segment_p : colored_segments) { - size_t poly_idx = &colored_segment_p - &colored_segments.front(); - for (const std::pair &colored_segment : colored_segment_p) { - size_t first_idx = graph.get_global_index(poly_idx, colored_segment.first); - size_t second_idx = graph.get_global_index(poly_idx, (colored_segment.second + 1) % graph.polygon_sizes[poly_idx]); - Line seg_line(graph.nodes[first_idx].point, graph.nodes[second_idx].point); - - if (graph.nodes[first_idx].neighbours.size() >= 3) { - std::vector> arc_to_check; - for (MMU_Graph::Arc &n_arc : graph.nodes[first_idx].neighbours) { - if (n_arc.type == MMU_Graph::ARC_TYPE::NON_BORDER) { - double total_len = compute_edge_length(graph, first_idx, n_arc); - arc_to_check.emplace_back(&n_arc, total_len); - } - } - std::sort(arc_to_check.begin(), arc_to_check.end(), - [](std::pair &l, std::pair &r) -> bool { return l.second > r.second; }); - - while (arc_to_check.size() > 1) { - graph.remove_edge(first_idx, arc_to_check.back().first->to_idx); - arc_to_check.pop_back(); - } - } - } - } -} - -static void cut_segmented_layers(const LayerPtrs &layers, std::vector>> &segmented_regions, const float cut_width) { - tbb::parallel_for(tbb::blocked_range(0, segmented_regions.size()),[&](const tbb::blocked_range& range) { - for (size_t layer_idx = range.begin(); layer_idx < range.end(); ++layer_idx) { - std::vector> segmented_regions_cuts; - for (const std::pair &colored_expoly : segmented_regions[layer_idx]) { - ExPolygons cut_colored_expoly = diff_ex({colored_expoly.first}, offset_ex(layers[layer_idx]->lslices, cut_width)); - for (const ExPolygon &expoly : cut_colored_expoly) { - segmented_regions_cuts.emplace_back(expoly, colored_expoly.second); - } - } - segmented_regions[layer_idx] = segmented_regions_cuts; - } - }); // end of parallel_for -} - -std::vector> PrintObject::mmu_segmentation_top_and_bottom_layers() -{ - std::vector> triangles_by_color(3); - triangles_by_color.assign(3, std::vector(m_layers.size())); - for (const ModelVolume *mv : this->model_object()->volumes) { - for (const auto ¶ms : {std::make_pair(EnforcerBlockerType::NONE, 0), std::make_pair(EnforcerBlockerType::ENFORCER, 1), - std::make_pair(EnforcerBlockerType::BLOCKER, 2)}) { - const indexed_triangle_set custom_facets = mv->mmu_segmentation_facets.get_facets(*mv, params.first); - if (!mv->is_model_part() || custom_facets.indices.empty()) - continue; - - const Transform3f tr = this->trafo().cast() * mv->get_matrix().cast(); - for (size_t facet_idx = 0; facet_idx < custom_facets.indices.size(); ++facet_idx) { - float min_z = std::numeric_limits::max(); - float max_z = std::numeric_limits::lowest(); - - std::array facet; - Points projected_facet(3); - 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()); - } - - // 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(); }); - - for (int p_idx = 0; p_idx < 3; ++p_idx) { - projected_facet[p_idx] = Point(scale_(facet[p_idx].x()), scale_(facet[p_idx].y())); - projected_facet[p_idx] = projected_facet[p_idx] - this->center_offset(); - } - - ExPolygon triangle = ExPolygon(projected_facet); - - // Find lowest slice not below the triangle. - auto first_layer = std::upper_bound(m_layers.begin(), m_layers.end(), float(min_z - EPSILON), - [](float z, const Layer *l1) { return z < l1->slice_z + l1->height * 0.5; }); - auto last_layer = std::upper_bound(m_layers.begin(), m_layers.end(), float(max_z - EPSILON), - [](float z, const Layer *l1) { return z < l1->slice_z + l1->height * 0.5; }); - - if (last_layer == m_layers.end()) - --last_layer; - - if (first_layer == m_layers.end() || (first_layer != m_layers.begin() && facet[0].z() < (*first_layer)->print_z - EPSILON)) - --first_layer; - - for (auto layer_it = first_layer; (layer_it != (last_layer + 1) && layer_it != m_layers.end()); ++layer_it) { - size_t layer_idx = layer_it - m_layers.begin(); - triangles_by_color[params.second][layer_idx].emplace_back(triangle); - } - } - } - } - - auto get_extrusion_width = [&m_layers = std::as_const(m_layers)](const size_t layer_idx) -> float { - auto extrusion_width_it = std::max_element(m_layers[layer_idx]->m_regions.begin(), m_layers[layer_idx]->m_regions.end(), - [](const LayerRegion *l1, const LayerRegion *l2) { - return l1->region()->config().perimeter_extrusion_width < - l2->region()->config().perimeter_extrusion_width; - }); - assert(extrusion_width_it != m_layers[layer_idx]->m_regions.end()); - return float((*extrusion_width_it)->region()->config().perimeter_extrusion_width); - }; - - auto get_top_solid_layers = [&m_layers = std::as_const(m_layers)](const size_t layer_idx) -> int { - auto top_solid_layer_it = std::max_element(m_layers[layer_idx]->m_regions.begin(), m_layers[layer_idx]->m_regions.end(), - [](const LayerRegion *l1, const LayerRegion *l2) { - return l1->region()->config().top_solid_layers < l2->region()->config().top_solid_layers; - }); - assert(top_solid_layer_it != m_layers[layer_idx]->m_regions.end()); - return (*top_solid_layer_it)->region()->config().top_solid_layers; - }; - - auto get_bottom_solid_layers = [&m_layers = std::as_const(m_layers)](const size_t layer_idx) -> int { - auto top_bottom_layer_it = std::max_element(m_layers[layer_idx]->m_regions.begin(), m_layers[layer_idx]->m_regions.end(), - [](const LayerRegion *l1, const LayerRegion *l2) { - return l1->region()->config().bottom_solid_layers < l2->region()->config().bottom_solid_layers; - }); - assert(top_bottom_layer_it != m_layers[layer_idx]->m_regions.end()); - return (*top_bottom_layer_it)->region()->config().bottom_solid_layers; - }; - - std::vector top_layers(m_layers.size()); - top_layers.back() = m_layers.back()->lslices; - tbb::parallel_for(tbb::blocked_range(1, m_layers.size()), [&](const tbb::blocked_range &range) { - for (size_t layer_idx = range.begin(); layer_idx < range.end(); ++layer_idx) { - float extrusion_width = 0.1f * float(scale_(get_extrusion_width(layer_idx))); - top_layers[layer_idx - 1] = diff_ex(m_layers[layer_idx - 1]->lslices, offset_ex(m_layers[layer_idx]->lslices, extrusion_width)); - } - }); // end of parallel_for - - std::vector bottom_layers(m_layers.size()); - bottom_layers.front() = m_layers.front()->lslices; - tbb::parallel_for(tbb::blocked_range(0, m_layers.size() - 1), [&](const tbb::blocked_range &range) { - for (size_t layer_idx = range.begin(); layer_idx < range.end(); ++layer_idx) { - float extrusion_width = 0.1f * float(scale_(get_extrusion_width(layer_idx))); - bottom_layers[layer_idx + 1] = diff_ex(m_layers[layer_idx + 1]->lslices, offset_ex(m_layers[layer_idx]->lslices, extrusion_width)); - } - }); // end of parallel_for - - tbb::parallel_for(tbb::blocked_range(0, this->layers().size()), [&](const tbb::blocked_range &range) { - for (size_t layer_idx = range.begin(); layer_idx < range.end(); ++layer_idx) { - float extrusion_width = 0.1f * float(scale_(get_extrusion_width(layer_idx))); - for (std::vector &triangles : triangles_by_color) { - if (!triangles[layer_idx].empty() && (!top_layers[layer_idx].empty() || !bottom_layers[layer_idx].empty())) { - ExPolygons connected = union_ex(offset_ex(triangles[layer_idx], float(10 * SCALED_EPSILON))); - triangles[layer_idx] = union_ex(offset_ex(offset_ex(connected, -extrusion_width / 1), extrusion_width / 1)); - } else { - triangles[layer_idx].clear(); - } - } - } - }); // end of parallel_for - - std::vector> triangles_by_color_bottom(3); - std::vector> triangles_by_color_top(3); - triangles_by_color_bottom.assign(3, std::vector(m_layers.size())); - triangles_by_color_top.assign(3, std::vector(m_layers.size())); - - for (size_t layer_idx = 0; layer_idx < this->layers().size(); ++layer_idx) { - BOOST_LOG_TRIVIAL(debug) << "MMU segmentation of top layer: " << layer_idx; - float extrusion_width = scale_(get_extrusion_width(layer_idx)); - int top_solid_layers = get_top_solid_layers(layer_idx); - ExPolygons top_expolygon = top_layers[layer_idx]; - if (top_expolygon.empty()) - continue; - - for (size_t color_idx = 0; color_idx < triangles_by_color.size(); ++color_idx) { - if (triangles_by_color[color_idx][layer_idx].empty()) - continue; - ExPolygons intersection_poly = intersection_ex(triangles_by_color[color_idx][layer_idx], top_expolygon); - if (!intersection_poly.empty()) { - triangles_by_color_top[color_idx][layer_idx].insert(triangles_by_color_top[color_idx][layer_idx].end(), intersection_poly.begin(), - intersection_poly.end()); - for (int last_idx = int(layer_idx) - 1; last_idx >= std::max(int(layer_idx - top_solid_layers), int(0)); --last_idx) { - float offset_value = float(layer_idx - last_idx) * (-1.0f) * extrusion_width; - if (offset_ex(top_expolygon, offset_value).empty()) - continue; - ExPolygons layer_slices_trimmed = m_layers[last_idx]->lslices; - - for (int last_idx_1 = last_idx; last_idx_1 < int(layer_idx); ++last_idx_1) { - layer_slices_trimmed = intersection_ex(layer_slices_trimmed, m_layers[last_idx_1 + 1]->lslices); - } - - ExPolygons offset_e = offset_ex(layer_slices_trimmed, offset_value); - ExPolygons intersection_poly_2 = intersection_ex(triangles_by_color_top[color_idx][layer_idx], offset_e); - triangles_by_color_top[color_idx][last_idx].insert(triangles_by_color_top[color_idx][last_idx].end(), intersection_poly_2.begin(), - intersection_poly_2.end()); - } - } - } - } - - for (size_t layer_idx = 0; layer_idx < this->layers().size(); ++layer_idx) { - BOOST_LOG_TRIVIAL(debug) << "MMU segmentation of bottom layer: " << layer_idx; - float extrusion_width = scale_(get_extrusion_width(layer_idx)); - int bottom_solid_layers = get_bottom_solid_layers(layer_idx); - ExPolygons bottom_expolygon = bottom_layers[layer_idx]; - if (bottom_expolygon.empty()) - continue; - - for (size_t color_idx = 0; color_idx < triangles_by_color.size(); ++color_idx) { - if (triangles_by_color[color_idx][layer_idx].empty()) - continue; - - ExPolygons intersection_poly = intersection_ex(triangles_by_color[color_idx][layer_idx], bottom_expolygon); - if (!intersection_poly.empty()) { - triangles_by_color_bottom[color_idx][layer_idx].insert(triangles_by_color_bottom[color_idx][layer_idx].end(), intersection_poly.begin(), - intersection_poly.end()); - for (size_t last_idx = layer_idx + 1; last_idx < std::min(layer_idx + bottom_solid_layers, m_layers.size()); ++last_idx) { - float offset_value = float(last_idx - layer_idx) * (-1.0f) * extrusion_width; - if (offset_ex(bottom_expolygon, offset_value).empty()) - continue; - ExPolygons layer_slices_trimmed = m_layers[last_idx]->lslices; - - for (int last_idx_1 = int(last_idx); last_idx_1 > int(layer_idx); --last_idx_1) { - layer_slices_trimmed = intersection_ex(layer_slices_trimmed, offset_ex(m_layers[last_idx_1 - 1]->lslices, offset_value)); - } - - ExPolygons offset_e = offset_ex(layer_slices_trimmed, offset_value); - ExPolygons intersection_poly_2 = intersection_ex(triangles_by_color_bottom[color_idx][layer_idx], offset_e); - triangles_by_color_bottom[color_idx][last_idx].insert(triangles_by_color_bottom[color_idx][last_idx].end(), intersection_poly_2.begin(), - intersection_poly_2.end()); - } - } - } - } - - std::vector> triangles_by_color_merged(3); - triangles_by_color_merged.assign(3, std::vector(m_layers.size())); - for (size_t layer_idx = 0; layer_idx < m_layers.size(); ++layer_idx) { - for (size_t color_idx = 0; color_idx < triangles_by_color_merged.size(); ++color_idx) { - triangles_by_color_merged[color_idx][layer_idx].insert(triangles_by_color_merged[color_idx][layer_idx].end(), - triangles_by_color_bottom[color_idx][layer_idx].begin(), - triangles_by_color_bottom[color_idx][layer_idx].end()); - triangles_by_color_merged[color_idx][layer_idx].insert(triangles_by_color_merged[color_idx][layer_idx].end(), - triangles_by_color_top[color_idx][layer_idx].begin(), - triangles_by_color_top[color_idx][layer_idx].end()); - triangles_by_color_merged[color_idx][layer_idx] = union_ex(triangles_by_color_merged[color_idx][layer_idx]); - } - - // Cut all colors for cases when two colors are overlapping - for (size_t color_idx = 1; color_idx < triangles_by_color_merged.size(); ++color_idx) { - triangles_by_color_merged[color_idx][layer_idx] = diff_ex(triangles_by_color_merged[color_idx][layer_idx], - triangles_by_color_merged[color_idx - 1][layer_idx]); - } - } - - return triangles_by_color_merged; -} - -static std::vector>> merge_segmented_layers( - const std::vector>> &segmented_regions, const std::vector> &top_and_bottom_layers) -{ - std::vector>> segmented_regions_merged(segmented_regions.size()); - - tbb::parallel_for(tbb::blocked_range(0, segmented_regions.size()), [&](const tbb::blocked_range &range) { - for (size_t layer_idx = range.begin(); layer_idx < range.end(); ++layer_idx) { - BOOST_LOG_TRIVIAL(debug) << "MMU segmentation - merging region: " << layer_idx; - for (const std::pair &colored_expoly : segmented_regions[layer_idx]) { - ExPolygons cut_colored_expoly = {colored_expoly.first}; - for (const std::vector &top_and_bottom_layer : top_and_bottom_layers) - cut_colored_expoly = diff_ex(cut_colored_expoly, top_and_bottom_layer[layer_idx]); - for (ExPolygon &ex_poly : cut_colored_expoly) - segmented_regions_merged[layer_idx].emplace_back(ex_poly, colored_expoly.second); - - } - - for (size_t color_idx = 0; color_idx < top_and_bottom_layers.size(); ++color_idx) { - ExPolygons top_and_bottom_expoly = top_and_bottom_layers[color_idx][layer_idx]; - for (const ExPolygon &expoly : top_and_bottom_expoly) { segmented_regions_merged[layer_idx].emplace_back(expoly, color_idx); } - } - } - }); // end of parallel_for - - return segmented_regions_merged; -} - -std::vector>> PrintObject::mmu_segmentation_by_painting() -{ - std::vector>> segmented_regions(this->layers().size()); - std::vector> painted_lines(this->layers().size()); - std::vector edge_grids(this->layers().size()); - - for (size_t layer_idx = 0; layer_idx < m_layers.size(); ++layer_idx) { - const Layer *layer = m_layers[layer_idx]; - BoundingBox bbox(get_extents(layer->lslices)); - bbox.offset(SCALED_EPSILON); - edge_grids[layer_idx].set_bbox(bbox); - edge_grids[layer_idx].create(layer->lslices, coord_t(scale_(10.))); - } - - for (const ModelVolume *mv : this->model_object()->volumes) { - for (const auto ¶ms : {std::make_pair(EnforcerBlockerType::ENFORCER, 1), std::make_pair(EnforcerBlockerType::BLOCKER, 2)}) { - const indexed_triangle_set custom_facets = mv->mmu_segmentation_facets.get_facets(*mv, params.first); - if (!mv->is_model_part() || custom_facets.indices.empty()) - continue; - - const Transform3f tr = this->trafo().cast() * mv->get_matrix().cast(); - for (size_t facet_idx = 0; facet_idx < custom_facets.indices.size(); ++facet_idx) { - float min_z = std::numeric_limits::max(); - float max_z = std::numeric_limits::lowest(); - - std::array facet; - Points projected_facet(3); - 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()); - } - - // 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(); }); - - for (int p_idx = 0; p_idx < 3; ++p_idx) { - projected_facet[p_idx] = Point(scale_(facet[p_idx].x()), scale_(facet[p_idx].y())); - projected_facet[p_idx] = projected_facet[p_idx] - this->center_offset(); - } - - ExPolygon triangle = ExPolygon(projected_facet); - - // Find lowest slice not below the triangle. - auto first_layer = std::upper_bound(this->layers().begin(), this->layers().end(), float(min_z - EPSILON), - [](float z, const Layer *l1) { return z < l1->slice_z; }); - auto last_layer = std::upper_bound(this->layers().begin(), this->layers().end(), float(max_z + EPSILON), - [](float z, const Layer *l1) { return z < l1->slice_z; }); - --last_layer; - - for (auto layer_it = first_layer; layer_it != (last_layer + 1); ++layer_it) { - const Layer *layer = *layer_it; - size_t layer_idx = layer_it - this->layers().begin(); - if (facet[0].z() > layer->slice_z || layer->slice_z > facet[2].z()) - continue; - - // 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 (facet[1].z() > layer->slice_z) { - // [P0, P2] a [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 if (facet[1].z() <= layer->slice_z) { - // [P0, P2] a [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]); - } - - Point line_start(scale_(line_start_f.x()), scale_(line_start_f.y())); - Point line_end(scale_(line_end_f.x()), scale_(line_end_f.y())); - line_start -= this->center_offset(); - line_end -= this->center_offset(); - - std::vector painted_line_tmp; - PaintedLineVisitor visitor(edge_grids[layer_idx], painted_line_tmp); - visitor.reset(); - visitor.line_to_test.a = line_start; - visitor.line_to_test.b = line_end; - visitor.color = params.second; - edge_grids[layer_idx].visit_cells_intersecting_line(line_start, line_end, visitor); - - if (!painted_line_tmp.empty()) - painted_lines[layer_idx].insert(painted_lines[layer_idx].end(), painted_line_tmp.begin(), painted_line_tmp.end()); - } - } - } - } - - tbb::parallel_for(tbb::blocked_range(0, this->layers().size()), [&](const tbb::blocked_range &range) { - for (size_t layer_idx = range.begin(); layer_idx < range.end(); ++layer_idx) { - // for(size_t layer_idx = 0; layer_idx < this->layers().size(); ++layer_idx) { - BOOST_LOG_TRIVIAL(debug) << "MMU segmentation of layer: " << layer_idx; - auto comp = [&edge_grids, &layer_idx](const PaintedLine &first, const PaintedLine &second) { - Point first_start_p = *(edge_grids[layer_idx].contours()[first.contour_idx].begin() + 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 && - Line(first_start_p, first.projected_line.a).length() < Line(first_start_p, second.projected_line.a).length()))); - }; - - std::sort(painted_lines[layer_idx].begin(), painted_lines[layer_idx].end(), comp); - std::vector &painted_lines_single = painted_lines[layer_idx]; - - if (!painted_lines_single.empty()) { - Polygons original_polygons; - for (const Slic3r::EdgeGrid::Contour &contour : edge_grids[layer_idx].contours()) { - Points points; - for (const Point &point : contour) points.emplace_back(point); - original_polygons.emplace_back(points); - } - - std::vector> color_poly = colorize_polygons(original_polygons, painted_lines_single); - MMU_Graph graph = build_graph(layer_idx, color_poly); - remove_multiple_edges_in_vertices(graph, color_poly); - graph.remove_nodes_with_one_arc(); - std::vector> segmentation = extract_colored_segments(graph); - for (const std::pair ®ion : segmentation) - segmented_regions[layer_idx].emplace_back(region); - } - } - }); // end of parallel_for - - if(m_print->config().mmu_segmented_region_max_width > 0.f) - cut_segmented_layers(m_layers, segmented_regions, float(-scale_(m_print->config().mmu_segmented_region_max_width))); - -// return segmented_regions; - std::vector> top_and_bottom_layers = mmu_segmentation_top_and_bottom_layers(); - std::vector>> segmented_regions_merged = merge_segmented_layers(segmented_regions, top_and_bottom_layers); - return segmented_regions_merged; -} -// --------------------MMU_END---------------------- - } // namespace Slic3r