diff --git a/src/libslic3r/MultiMaterialSegmentation.cpp b/src/libslic3r/MultiMaterialSegmentation.cpp index 2fbdf85bc..d9c79a44e 100644 --- a/src/libslic3r/MultiMaterialSegmentation.cpp +++ b/src/libslic3r/MultiMaterialSegmentation.cpp @@ -4,20 +4,15 @@ #include "Layer.hpp" #include "Print.hpp" #include "VoronoiVisualUtils.hpp" +#include "MutablePolygon.hpp" #include #include #include #include - #include -#include -#include -#include -#include - namespace Slic3r { struct ColoredLine { Line line; @@ -89,28 +84,37 @@ struct PaintedLineVisitor bool operator()(coord_t iy, coord_t ix) { // Called with a row and column of the grid cell, which is intersected by a line. - auto cell_data_range = grid.cell_data_range(iy, ix); - const Vec2d v1 = line_to_test.vector().cast(); + auto cell_data_range = grid.cell_data_range(iy, ix); + const Vec2d v1 = line_to_test.vector().cast(); + const double v1_sqr_norm = v1.squaredNorm(); + const double heuristic_thr_part = line_to_test.length() + append_threshold; for (auto it_contour_and_segment = cell_data_range.first; it_contour_and_segment != cell_data_range.second; ++it_contour_and_segment) { - Line grid_line = grid.line(*it_contour_and_segment); - const Vec2d v2 = grid_line.vector().cast(); + Line grid_line = grid.line(*it_contour_and_segment); + const Vec2d v2 = grid_line.vector().cast(); + double heuristic_thr_sqr = Slic3r::sqr(heuristic_thr_part + grid_line.length()); + + // An inexpensive heuristic to test whether line_to_test and grid_line can be somewhere close enough to each other. + // This helps filter out cases when the following expensive calculations are useless. + if ((grid_line.a - line_to_test.a).cast().squaredNorm() > heuristic_thr_sqr || + (grid_line.b - line_to_test.a).cast().squaredNorm() > heuristic_thr_sqr || + (grid_line.a - line_to_test.b).cast().squaredNorm() > heuristic_thr_sqr || + (grid_line.b - line_to_test.b).cast().squaredNorm() > heuristic_thr_sqr) + continue; + // When lines have too different length, it is necessary to normalize them - if (Slic3r::sqr(v1.dot(v2)) > cos_threshold2 * v1.squaredNorm() * v2.squaredNorm()) { + if (Slic3r::sqr(v1.dot(v2)) > cos_threshold2 * v1_sqr_norm * v2.squaredNorm()) { // The two vectors are nearly collinear (their mutual angle is lower than 30 degrees) if (painted_lines_set.find(*it_contour_and_segment) == painted_lines_set.end()) { - 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) { + if (grid_line.distance_to_squared(line_to_test.a) < append_threshold2 || + grid_line.distance_to_squared(line_to_test.b) < append_threshold2 || + line_to_test.distance_to_squared(grid_line.a) < append_threshold2 || + line_to_test.distance_to_squared(grid_line.b) < append_threshold2) { Line line_to_test_projected; project_line_on_line(grid_line, line_to_test, &line_to_test_projected); - if (Line(grid_line.a, line_to_test_projected.a).length() > Line(grid_line.a, line_to_test_projected.b).length()) { + if ((line_to_test_projected.a - grid_line.a).cast().squaredNorm() > (line_to_test_projected.b - grid_line.a).cast().squaredNorm()) line_to_test_projected.reverse(); - } + painted_lines.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); } @@ -125,9 +129,11 @@ struct PaintedLineVisitor std::vector &painted_lines; Line line_to_test; std::unordered_set, boost::hash>> painted_lines_set; - int color = -1; + int color = -1; - static inline const double cos_threshold2 = Slic3r::sqr(cos(M_PI * 30. / 180.)); + static inline const double cos_threshold2 = Slic3r::sqr(cos(M_PI * 30. / 180.)); + static inline const double append_threshold = 50 * SCALED_EPSILON; + static inline const double append_threshold2 = Slic3r::sqr(append_threshold); }; static std::vector to_colored_lines(const Polygon &polygon, int color) @@ -154,6 +160,7 @@ static Polygon colored_points_to_polygon(const std::vector &lines) static Polygons colored_points_to_polygon(const std::vector> &lines) { Polygons out; + out.reserve(lines.size()); for (const std::vector &l : lines) out.emplace_back(colored_points_to_polygon(l)); return out; @@ -484,6 +491,12 @@ static std::vector> colorize_polygons(const Polygons &p using boost::polygon::voronoi_diagram; +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())); } + struct MMU_Graph { enum class ARC_TYPE { BORDER, NON_BORDER }; @@ -616,24 +629,63 @@ struct MMU_Graph { return this->is_vertex_on_contour(edge_iterator->vertex0()) && this->is_vertex_on_contour(edge_iterator->vertex1()); } + + // 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. + void append_voronoi_vertices(const Geometry::VoronoiDiagram &vd, const Polygons &color_poly_tmp, BoundingBox bbox) { + bbox.offset(SCALED_EPSILON); + + struct CPoint + { + CPoint() = delete; + CPoint(const Point &point, size_t contour_idx, size_t point_idx) : m_point(point), m_point_idx(point_idx), m_contour_idx(contour_idx) {} + CPoint(const Point &point, size_t point_idx) : m_point(point), m_point_idx(point_idx), m_contour_idx(0) {} + const Point m_point; + size_t m_point_idx; + size_t m_contour_idx; + + [[nodiscard]] const Point &point() const { return m_point; } + bool operator==(const CPoint &rhs) const { return this->m_point == rhs.m_point && this->m_contour_idx == rhs.m_contour_idx && this->m_point_idx == rhs.m_point_idx; } + }; + struct CPointAccessor { const Point* operator()(const CPoint &pt) const { return &pt.point(); }}; + typedef ClosestPointInRadiusLookup CPointLookupType; + + CPointLookupType closest_voronoi_point(3 * coord_t(SCALED_EPSILON)); + CPointLookupType closest_contour_point(3 * coord_t(SCALED_EPSILON)); + for (const Polygon &polygon : color_poly_tmp) + for (const Point &pt : polygon.points) + closest_contour_point.insert(CPoint(pt, &polygon - &color_poly_tmp.front(), &pt - &polygon.points.front())); + + for (const voronoi_diagram::vertex_type &vertex : vd.vertices()) { + vertex.color(-1); + Point vertex_point = mk_point(vertex); + + const Point &first_point = this->nodes[this->get_arc(vertex.incident_edge()->cell()->source_index()).from_idx].point; + const Point &second_point = this->nodes[this->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(this->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(this->get_arc(vertex.incident_edge()->twin()->cell()->source_index()).from_idx); + } else if (bbox.contains(vertex_point)) { + if (auto [contour_pt, c_dist_sqr] = closest_contour_point.find(vertex_point); contour_pt != nullptr && c_dist_sqr < 3 * SCALED_EPSILON) { + vertex.color(this->get_global_index(contour_pt->m_contour_idx, contour_pt->m_point_idx)); + } else if (auto [voronoi_pt, v_dist_sqr] = closest_voronoi_point.find(vertex_point); voronoi_pt == nullptr || v_dist_sqr >= 3 * SCALED_EPSILON) { + closest_voronoi_point.insert(CPoint(vertex_point, this->nodes_count())); + vertex.color(this->nodes_count()); + this->nodes.push_back({vertex_point}); + } else { + vertex.color(voronoi_pt->m_point_idx); + } + } + } + } }; -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); @@ -695,7 +747,7 @@ static MMU_Graph build_graph(size_t layer_idx, const std::vector lines_colored = to_lines(color_poly); - Polygons color_poly_tmp = colored_points_to_polygon(color_poly); + const 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); @@ -719,6 +771,7 @@ static MMU_Graph build_graph(size_t layer_idx, const std::vector 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(); + BoundingBox bbox = get_extents(color_poly_tmp); + graph.append_voronoi_vertices(vd, color_poly_tmp, bbox); 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; @@ -803,7 +798,6 @@ static MMU_Graph build_graph(size_t layer_idx, const std::vector>> multi_material_segmentati // All expolygons are expanded by SCALED_EPSILON, merged, and then shrunk again by SCALED_EPSILON // to ensure that very close polygons will be merged. ex_polygons = union_ex(ex_polygons); - // Remove all expolygons and holes with an area less than 0.01mm^2 + // Remove all expolygons and holes with an area less than 0.1mm^2 remove_small_and_small_holes(ex_polygons, Slic3r::sqr(scale_(0.1f))); // Occasionally, some input polygons contained self-intersections that caused problems with Voronoi diagrams // and consequently with the extraction of colored segments by function extract_colored_segments. @@ -1437,19 +1431,19 @@ std::vector>> multi_material_segmentati // Such close points sometimes caused that the Voronoi diagram has self-intersecting edges around these vertices. // This consequently leads to issues with the extraction of colored segments by function extract_colored_segments. // Calling expolygons_simplify fixed these issues. - input_expolygons[layer_idx] = simplify_polygons_ex(to_polygons(expolygons_simplify(offset_ex(ex_polygons, float(-10 * SCALED_EPSILON)), 5 * SCALED_EPSILON))); - input_polygons[layer_idx] = to_polygons(input_expolygons[layer_idx]); + input_expolygons[layer_idx] = smooth_outward(expolygons_simplify(offset_ex(ex_polygons, -10.f * float(SCALED_EPSILON)), 5 * SCALED_EPSILON), 10 * coord_t(SCALED_EPSILON)); + input_polygons[layer_idx] = to_polygons(input_expolygons[layer_idx]); } }); // end of parallel_for BOOST_LOG_TRIVIAL(debug) << "MMU segmentation - slices preparation in parallel - end"; for (size_t layer_idx = 0; layer_idx < layers.size(); ++layer_idx) { throw_on_cancel_callback(); - BoundingBox bbox(get_extents(input_expolygons[layer_idx])); + BoundingBox bbox(get_extents(input_polygons[layer_idx])); // Projected triangles may slightly exceed the input polygons. bbox.offset(20 * SCALED_EPSILON); edge_grids[layer_idx].set_bbox(bbox); - edge_grids[layer_idx].create(input_expolygons[layer_idx], coord_t(scale_(10.))); + edge_grids[layer_idx].create(input_polygons[layer_idx], coord_t(scale_(10.))); } BOOST_LOG_TRIVIAL(debug) << "MMU segmentation - projection of painted triangles - begin"; @@ -1498,7 +1492,7 @@ std::vector>> multi_material_segmentati // [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) { + } else { // [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]); diff --git a/src/libslic3r/MutablePolygon.hpp b/src/libslic3r/MutablePolygon.hpp index 14d7787cf..1b2b4e445 100644 --- a/src/libslic3r/MutablePolygon.hpp +++ b/src/libslic3r/MutablePolygon.hpp @@ -3,6 +3,7 @@ #include "Point.hpp" #include "Polygon.hpp" +#include "ExPolygon.hpp" namespace Slic3r { @@ -330,6 +331,24 @@ inline Polygons smooth_outward(Polygons polygons, coord_t clip_dist_scaled) return polygons; } +inline ExPolygons smooth_outward(ExPolygons expolygons, coord_t clip_dist_scaled) +{ + MutablePolygon mp; + for (ExPolygon &expolygon : expolygons) { + mp.assign(expolygon.contour, expolygon.contour.size() * 2); + smooth_outward(mp, clip_dist_scaled); + mp.polygon(expolygon.contour); + for (Polygon &hole : expolygon.holes) { + mp.assign(hole, hole.size() * 2); + smooth_outward(mp, clip_dist_scaled); + mp.polygon(hole); + } + expolygon.holes.erase(std::remove_if(expolygon.holes.begin(), expolygon.holes.end(), [](const auto &p) { return p.empty(); }), expolygon.holes.end()); + } + expolygons.erase(std::remove_if(expolygons.begin(), expolygons.end(), [](const auto &p) { return p.empty(); }), expolygons.end()); + return expolygons; +} + } #endif // slic3r_MutablePolygon_hpp_ diff --git a/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.cpp b/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.cpp index 3274f000c..5fa4ab51d 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.cpp @@ -21,6 +21,7 @@ void GLGizmoFdmSupports::on_shutdown() { m_angle_threshold_deg = 0.f; m_parent.use_slope(false); + m_parent.toggle_model_objects_visibility(true); } diff --git a/src/slic3r/GUI/Gizmos/GLGizmoMmuSegmentation.cpp b/src/slic3r/GUI/Gizmos/GLGizmoMmuSegmentation.cpp index c6dced670..2abc35344 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoMmuSegmentation.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoMmuSegmentation.cpp @@ -8,6 +8,7 @@ #include "slic3r/GUI/BitmapCache.hpp" #include "slic3r/GUI/format.hpp" #include "slic3r/GUI/GUI_ObjectList.hpp" +#include "slic3r/GUI/NotificationManager.hpp" #include "libslic3r/PresetBundle.hpp" #include "libslic3r/Model.hpp" @@ -16,9 +17,26 @@ namespace Slic3r::GUI { +static inline void show_notification_extruders_limit_exceeded() +{ + wxGetApp() + .plater() + ->get_notification_manager() + ->push_notification(NotificationType::MmSegmentationExceededExtrudersLimit, NotificationManager::NotificationLevel::RegularNotification, + GUI::format(_L("Your printer has more extruders than the multi-material painting gizmo supports. For this reason, only the " + "first %1% extruders will be able to be used for painting."), GLGizmoMmuSegmentation::EXTRUDERS_LIMIT)); +} + +void GLGizmoMmuSegmentation::on_opening() +{ + if (wxGetApp().extruders_edited_cnt() > int(GLGizmoMmuSegmentation::EXTRUDERS_LIMIT)) + show_notification_extruders_limit_exceeded(); +} + void GLGizmoMmuSegmentation::on_shutdown() { m_parent.use_slope(false); + m_parent.toggle_model_objects_visibility(true); } std::string GLGizmoMmuSegmentation::on_get_name() const @@ -131,6 +149,9 @@ void GLGizmoMmuSegmentation::set_painter_gizmo_data(const Selection &selection) ModelObject *model_object = m_c->selection_info()->model_object(); int prev_extruders_count = int(m_original_extruders_colors.size()); if (prev_extruders_count != wxGetApp().extruders_edited_cnt() || get_extruders_colors() != m_original_extruders_colors) { + if (wxGetApp().extruders_edited_cnt() > int(GLGizmoMmuSegmentation::EXTRUDERS_LIMIT)) + show_notification_extruders_limit_exceeded(); + this->init_extruders_data(); // Reinitialize triangle selectors because of change of extruder count need also change the size of GLIndexedVertexArray if (prev_extruders_count != wxGetApp().extruders_edited_cnt()) @@ -157,7 +178,7 @@ static void render_extruders_combo(const std::string &labe ImGui::BeginGroup(); ImVec2 combo_pos = ImGui::GetCursorScreenPos(); if (ImGui::BeginCombo(label.c_str(), "")) { - for (size_t extruder_idx = 0; extruder_idx < extruders.size(); ++extruder_idx) { + for (size_t extruder_idx = 0; extruder_idx < std::min(extruders.size(), GLGizmoMmuSegmentation::EXTRUDERS_LIMIT); ++extruder_idx) { ImGui::PushID(int(extruder_idx)); ImVec2 start_position = ImGui::GetCursorScreenPos(); diff --git a/src/slic3r/GUI/Gizmos/GLGizmoMmuSegmentation.hpp b/src/slic3r/GUI/Gizmos/GLGizmoMmuSegmentation.hpp index f5c97801b..5ece0ec2a 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoMmuSegmentation.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoMmuSegmentation.hpp @@ -36,6 +36,12 @@ public: void set_painter_gizmo_data(const Selection& selection) override; + // TriangleSelector::serialization/deserialization has a limit to store 19 different states. + // EXTRUDER_LIMIT + 1 states are used to storing the painting because also uncolored triangles are stored. + // When increasing EXTRUDER_LIMIT, it needs to ensure that TriangleSelector::serialization/deserialization + // will be also extended to support additional states, requiring at least one state to remain free out of 19 states. + static const constexpr size_t EXTRUDERS_LIMIT = 16; + protected: std::array get_cursor_sphere_left_button_color() const override; std::array get_cursor_sphere_right_button_color() const override; @@ -63,7 +69,7 @@ private: void update_model_object() const override; void update_from_model_object() override; - void on_opening() override {} + void on_opening() override; void on_shutdown() override; PainterGizmoType get_painter_type() const override; diff --git a/src/slic3r/GUI/Gizmos/GLGizmoSeam.cpp b/src/slic3r/GUI/Gizmos/GLGizmoSeam.cpp index d3c0c7d04..6b28e8ca7 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoSeam.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoSeam.cpp @@ -16,6 +16,13 @@ namespace Slic3r::GUI { +void GLGizmoSeam::on_shutdown() +{ + m_parent.toggle_model_objects_visibility(true); +} + + + bool GLGizmoSeam::on_init() { m_shortcut_key = WXK_CONTROL_P; diff --git a/src/slic3r/GUI/Gizmos/GLGizmoSeam.hpp b/src/slic3r/GUI/Gizmos/GLGizmoSeam.hpp index d97bad10f..196fe5023 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoSeam.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoSeam.hpp @@ -27,7 +27,7 @@ private: void update_from_model_object() override; void on_opening() override {} - void on_shutdown() override {} + void on_shutdown() override; // This map holds all translated description texts, so they can be easily referenced during layout calculations // etc. When language changes, GUI is recreated and this class constructed again, so the change takes effect. diff --git a/src/slic3r/GUI/NotificationManager.hpp b/src/slic3r/GUI/NotificationManager.hpp index 1bcb93de0..17db606c0 100644 --- a/src/slic3r/GUI/NotificationManager.hpp +++ b/src/slic3r/GUI/NotificationManager.hpp @@ -87,7 +87,9 @@ enum class NotificationType DesktopIntegrationSuccess, DesktopIntegrationFail, UndoDesktopIntegrationSuccess, - UndoDesktopIntegrationFail + UndoDesktopIntegrationFail, + // Notification that a printer has more extruders than are supported by MM Gizmo/segmentation. + MmSegmentationExceededExtrudersLimit };