diff --git a/src/libslic3r/TriangleSelector.cpp b/src/libslic3r/TriangleSelector.cpp index b24a975061..b64bef17a1 100644 --- a/src/libslic3r/TriangleSelector.cpp +++ b/src/libslic3r/TriangleSelector.cpp @@ -69,7 +69,70 @@ void TriangleSelector::Triangle::set_division(int sides_to_split, int special_si this->special_side_idx = special_side_idx; } +inline bool is_point_inside_triangle(const Vec3f &pt, const Vec3f &p1, const Vec3f &p2, const Vec3f &p3) +{ + // Real-time collision detection, Ericson, Chapter 3.4 + auto barycentric = [&pt, &p1, &p2, &p3]() -> Vec3f { + std::array v = {p2 - p1, p3 - p1, pt - p1}; + float d00 = v[0].dot(v[0]); + float d01 = v[0].dot(v[1]); + float d11 = v[1].dot(v[1]); + float d20 = v[2].dot(v[0]); + float d21 = v[2].dot(v[1]); + float denom = d00 * d11 - d01 * d01; + Vec3f barycentric_cords(1.f, (d11 * d20 - d01 * d21) / denom, (d00 * d21 - d01 * d20) / denom); + barycentric_cords.x() = barycentric_cords.x() - barycentric_cords.y() - barycentric_cords.z(); + return barycentric_cords; + }; + + Vec3f barycentric_cords = barycentric(); + return std::all_of(begin(barycentric_cords), end(barycentric_cords), [](float cord) { return 0.f <= cord && cord <= 1.0; }); +} + +int TriangleSelector::select_unsplit_triangle(const Vec3f &hit, int facet_idx, const Vec3i &neighbors) const +{ + assert(facet_idx < int(m_triangles.size())); + const Triangle *tr = &m_triangles[facet_idx]; + if (!tr->valid()) + return -1; + + if (!tr->is_split()) { + if (const std::array &t_vert = m_triangles[facet_idx].verts_idxs; is_point_inside_triangle(hit, m_vertices[t_vert[0]].v, m_vertices[t_vert[1]].v, m_vertices[t_vert[2]].v)) + return facet_idx; + + return -1; + } + + assert(this->verify_triangle_neighbors(*tr, neighbors)); + + int num_of_children = tr->number_of_split_sides() + 1; + if (num_of_children != 1) { + for (int i = 0; i < num_of_children; ++i) { + assert(i < int(tr->children.size())); + assert(tr->children[i] < int(m_triangles.size())); + // Recursion, deep first search over the children of this triangle. + // All children of this triangle were created by splitting a single source triangle of the original mesh. + + const std::array &t_vert = m_triangles[tr->children[i]].verts_idxs; + if (is_point_inside_triangle(hit, m_vertices[t_vert[0]].v, m_vertices[t_vert[1]].v, m_vertices[t_vert[2]].v)) + return this->select_unsplit_triangle(hit, tr->children[i], this->child_neighbors(*tr, neighbors, i)); + } + } + + return -1; +} + +int TriangleSelector::select_unsplit_triangle(const Vec3f &hit, int facet_idx) const +{ + assert(facet_idx < int(m_triangles.size())); + if (!m_triangles[facet_idx].valid()) + return -1; + + Vec3i neighbors = root_neighbors(*m_mesh, facet_idx); + assert(this->verify_triangle_neighbors(m_triangles[facet_idx], neighbors)); + return this->select_unsplit_triangle(hit, facet_idx, neighbors); +} void TriangleSelector::select_patch(const Vec3f& hit, int facet_start, const Vec3f& source, float radius, @@ -116,19 +179,19 @@ void TriangleSelector::select_patch(const Vec3f& hit, int facet_start, } } -void TriangleSelector::seed_fill_select_triangles(const Vec3f& hit, int facet_start, float seed_fill_angle) +void TriangleSelector::seed_fill_select_triangles(const Vec3f &hit, int facet_start, float seed_fill_angle) { assert(facet_start < m_orig_size_indices); this->seed_fill_unselect_all_triangles(); std::vector visited(m_triangles.size(), false); - std::queue facet_queue; + std::queue facet_queue; facet_queue.push(facet_start); const double facet_angle_limit = cos(Geometry::deg2rad(seed_fill_angle)) - EPSILON; // Depth-first traversal of neighbors of the face hit by the ray thrown from the mouse cursor. - while(!facet_queue.empty()) { + while (!facet_queue.empty()) { int current_facet = facet_queue.front(); facet_queue.pop(); @@ -161,6 +224,129 @@ void TriangleSelector::seed_fill_select_triangles(const Vec3f& hit, int facet_st } } +void TriangleSelector::precompute_all_level_neighbors_recursive(const int facet_idx, const Vec3i &neighbors, const Vec3i &neighbors_propagated, std::vector &neighbors_out) const +{ + assert(facet_idx < int(m_triangles.size())); + + const Triangle *tr = &m_triangles[facet_idx]; + if (!tr->valid()) + return; + + neighbors_out[facet_idx] = neighbors_propagated; + if (tr->is_split()) { + assert(this->verify_triangle_neighbors(*tr, neighbors)); + + int num_of_children = tr->number_of_split_sides() + 1; + if (num_of_children != 1) { + for (int i = 0; i < num_of_children; ++i) { + assert(i < int(tr->children.size())); + assert(tr->children[i] < int(m_triangles.size())); + // Recursion, deep first search over the children of this triangle. + // All children of this triangle were created by splitting a single source triangle of the original mesh. + this->precompute_all_level_neighbors_recursive(tr->children[i], this->child_neighbors(*tr, neighbors, i), this->child_neighbors_propagated(*tr, neighbors_propagated, i), neighbors_out); + } + } + } +} + +std::vector TriangleSelector::precompute_all_level_neighbors() const +{ + std::vector neighbors(m_triangles.size(), Vec3i(-1, -1, -1)); + for (int facet_idx = 0; facet_idx < this->m_orig_size_indices; ++facet_idx) { + neighbors[facet_idx] = root_neighbors(*m_mesh, facet_idx); + assert(this->verify_triangle_neighbors(m_triangles[facet_idx], neighbors[facet_idx])); + if (m_triangles[facet_idx].is_split()) + this->precompute_all_level_neighbors_recursive(facet_idx, neighbors[facet_idx], neighbors[facet_idx], neighbors); + } + return neighbors; +} + +bool TriangleSelector::are_triangles_touching(const int first_facet_idx, const int second_facet_idx) const +{ + std::array sides_facet = {Linef3(m_vertices[m_triangles[first_facet_idx].verts_idxs[0]].v.cast(), m_vertices[m_triangles[first_facet_idx].verts_idxs[1]].v.cast()), + Linef3(m_vertices[m_triangles[first_facet_idx].verts_idxs[1]].v.cast(), m_vertices[m_triangles[first_facet_idx].verts_idxs[2]].v.cast()), + Linef3(m_vertices[m_triangles[first_facet_idx].verts_idxs[2]].v.cast(), m_vertices[m_triangles[first_facet_idx].verts_idxs[0]].v.cast())}; + + const Vec3d p0 = m_vertices[m_triangles[second_facet_idx].verts_idxs[0]].v.cast(); + const Vec3d p1 = m_vertices[m_triangles[second_facet_idx].verts_idxs[1]].v.cast(); + const Vec3d p2 = m_vertices[m_triangles[second_facet_idx].verts_idxs[2]].v.cast(); + + for (size_t idx = 0; idx < 3; ++idx) + if (line_alg::distance_to_squared(sides_facet[idx], p0) <= EPSILON && (line_alg::distance_to_squared(sides_facet[idx], p1) <= EPSILON || line_alg::distance_to_squared(sides_facet[idx], p2) <= EPSILON)) + return true; + else if (line_alg::distance_to_squared(sides_facet[idx], p1) <= EPSILON && line_alg::distance_to_squared(sides_facet[idx], p2) <= EPSILON) + return true; + + return false; +} + +std::vector TriangleSelector::neighboring_triangles(const int first_facet_idx, const int second_facet_idx, EnforcerBlockerType second_facet_state) const +{ + assert(first_facet_idx < int(m_triangles.size())); + + const Triangle *tr = &m_triangles[first_facet_idx]; + if (!tr->valid()) + return {}; + + if (!tr->is_split() && tr->get_state() == second_facet_state && (are_triangles_touching(second_facet_idx, first_facet_idx) || are_triangles_touching(first_facet_idx, second_facet_idx))) + return {first_facet_idx}; + + std::vector neighbor_facets_out; + int num_of_children = tr->number_of_split_sides() + 1; + if (num_of_children != 1) { + for (int i = 0; i < num_of_children; ++i) { + assert(i < int(tr->children.size())); + assert(tr->children[i] < int(m_triangles.size())); + + if (std::vector neighbor_facets = neighboring_triangles(tr->children[i], second_facet_idx, second_facet_state); !neighbor_facets.empty()) + Slic3r::append(neighbor_facets_out, std::move(neighbor_facets)); + } + } + + return neighbor_facets_out; +} + +void TriangleSelector::bucket_fill_select_triangles(const Vec3f& hit, int facet_start, bool propagate) +{ + this->seed_fill_unselect_all_triangles(); + + int start_facet_idx = select_unsplit_triangle(hit, facet_start); + EnforcerBlockerType start_facet_state = m_triangles[start_facet_idx].get_state(); + if (start_facet_idx == -1) + return; + + if (!propagate) { + m_triangles[start_facet_idx].select_by_seed_fill(); + return; + } + + std::vector all_level_neighbors = this->precompute_all_level_neighbors(); + std::vector visited(m_triangles.size(), false); + std::queue facet_queue; + + facet_queue.push(start_facet_idx); + while (!facet_queue.empty()) { + int current_facet = facet_queue.front(); + facet_queue.pop(); + assert(!m_triangles[current_facet].is_split()); + + if (!visited[current_facet]) { + m_triangles[current_facet].select_by_seed_fill(); + for(int neighbor_idx : all_level_neighbors[current_facet]) { + if(!m_triangles[neighbor_idx].is_split()) { + if(m_triangles[neighbor_idx].get_state() == start_facet_state) + facet_queue.push(neighbor_idx); + } else { + for(int neighbor_facet_idx : neighboring_triangles(neighbor_idx, current_facet, start_facet_state)) + facet_queue.push(neighbor_facet_idx); + } + } + } + + visited[current_facet] = true; + } +} + // Selects either the whole triangle (discarding any children it had), or divides // the triangle recursively, selecting just subtriangles truly inside the circle. // This is done by an actual recursive call. Returns false if the triangle is @@ -412,6 +598,89 @@ Vec3i TriangleSelector::child_neighbors(const Triangle &tr, const Vec3i &neighbo return out; } +// Return neighbors of the ith child of a triangle given neighbors of the triangle. +// If such a neighbor doesn't exist, return the neighbor from the previous depth. +Vec3i TriangleSelector::child_neighbors_propagated(const Triangle &tr, const Vec3i &neighbors, int child_idx) const +{ + int i = tr.special_side(); + int j = i + 1; + if (j >= 3) j = 0; + int k = j + 1; + if (k >= 3) k = 0; + + Vec3i out; + switch (tr.number_of_split_sides()) { + case 1: + switch (child_idx) { + case 0: + out(0) = neighbors(i); + out(1) = neighbors(j); + out(2) = tr.children[1]; + break; + default: + assert(child_idx == 1); + out(0) = neighbors(j); + out(1) = neighbors(k); + out(2) = tr.children[0]; + break; + } + break; + + case 2: + switch (child_idx) { + case 0: + out(0) = neighbors(i); + out(1) = tr.children[1]; + out(2) = neighbors(k); + break; + case 1: + assert(child_idx == 1); + out(0) = neighbors(i); + out(1) = tr.children[2]; + out(2) = tr.children[0]; + break; + default: + assert(child_idx == 2); + out(0) = neighbors(j); + out(1) = neighbors(k); + out(2) = tr.children[1]; + break; + } + break; + + case 3: + assert(tr.special_side() == 0); + switch (child_idx) { + case 0: + out(0) = neighbors(0); + out(1) = tr.children[3]; + out(2) = neighbors(2); + break; + case 1: + out(0) = neighbors(0); + out(1) = neighbors(1); + out(2) = tr.children[3]; + break; + case 2: + out(0) = neighbors(1); + out(1) = neighbors(2); + out(2) = tr.children[3]; + break; + default: + assert(child_idx == 3); + out(0) = tr.children[1]; + out(1) = tr.children[2]; + out(2) = tr.children[0]; + break; + } + break; + + default: assert(false); + } + + return out; +} + bool TriangleSelector::select_triangle_recursive(int facet_idx, const Vec3i &neighbors, EnforcerBlockerType type, bool triangle_splitting) { assert(facet_idx < int(m_triangles.size())); diff --git a/src/libslic3r/TriangleSelector.hpp b/src/libslic3r/TriangleSelector.hpp index 6efae21500..643daba457 100644 --- a/src/libslic3r/TriangleSelector.hpp +++ b/src/libslic3r/TriangleSelector.hpp @@ -18,9 +18,13 @@ class TriangleSelector { public: enum CursorType { CIRCLE, - SPHERE + SPHERE, + POINTER }; + [[nodiscard]] std::vector precompute_all_level_neighbors() const; + void precompute_all_level_neighbors_recursive(const int facet_idx, const Vec3i &neighbors, const Vec3i &neighbors_propagated, std::vector &neighbors_out) const; + // Set a limit to the edge length, below which the edge will not be split by select_patch(). // Called by select_patch() internally. Made public for debugging purposes, see TriangleSelectorGUI::render_debug(). void set_edge_limit(float edge_limit); @@ -29,6 +33,14 @@ public: // stay valid, a ptr to it is saved and used. explicit TriangleSelector(const TriangleMesh& mesh); + // Returns the facet_idx of the unsplit triangle containing the "hit". Returns -1 if the triangle isn't found. + [[nodiscard]] int select_unsplit_triangle(const Vec3f &hit, int facet_idx) const; + [[nodiscard]] int select_unsplit_triangle(const Vec3f &hit, int facet_idx, const Vec3i &neighbors) const; + + [[nodiscard]] bool are_triangles_touching(int first_facet_idx, int second_facet_idx) const; + + [[nodiscard]] std::vector neighboring_triangles(int first_facet_idx, int second_facet_idx, EnforcerBlockerType second_facet_state) const; + // Select all triangles fully inside the circle, subdivide where needed. void select_patch(const Vec3f &hit, // point where to start int facet_start, // facet of the original mesh (unsplit) that the hit point belongs to @@ -43,6 +55,10 @@ public: int facet_start, // facet of the original mesh (unsplit) that the hit point belongs to float seed_fill_angle); // the maximal angle between two facets to be painted by the same color + void bucket_fill_select_triangles(const Vec3f &hit, // point where to start + int facet_start, // facet of the original mesh (unsplit) that the hit point belongs to + bool propagate); // if bucket fill is propagated to neighbor faces or if it fills the only facet of the modified mesh that the hit point belongs to. + bool has_facets(EnforcerBlockerType state) const; static bool has_facets(const std::pair>, std::vector> &data, const EnforcerBlockerType test_state); int num_facets(EnforcerBlockerType state) const; @@ -192,6 +208,7 @@ private: int push_triangle(int a, int b, int c, int source_triangle, const EnforcerBlockerType state = EnforcerBlockerType{0}); void perform_split(int facet_idx, const Vec3i &neighbors, EnforcerBlockerType old_state); Vec3i child_neighbors(const Triangle &tr, const Vec3i &neighbors, int child_idx) const; + Vec3i child_neighbors_propagated(const Triangle &tr, const Vec3i &neighbors, int child_idx) const; // Return child of itriangle at a CCW oriented side (vertexi, vertexj), either first or 2nd part. // If itriangle == -1 or if the side sharing (vertexi, vertexj) is not split, return -1. enum class Partition { diff --git a/src/slic3r/GUI/Gizmos/GLGizmoMmuSegmentation.cpp b/src/slic3r/GUI/Gizmos/GLGizmoMmuSegmentation.cpp index 344568bffc..1a8bbd539a 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoMmuSegmentation.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoMmuSegmentation.cpp @@ -107,16 +107,24 @@ bool GLGizmoMmuSegmentation::on_init() m_desc["reset_direction"] = _L("Reset direction"); m_desc["clipping_of_view"] = _L("Clipping of view") + ": "; m_desc["cursor_size"] = _L("Brush size") + ": "; - m_desc["cursor_type"] = _L("Brush shape") + ": "; + m_desc["cursor_type"] = _L("Brush shape"); m_desc["first_color_caption"] = _L("Left mouse button") + ": "; m_desc["first_color"] = _L("First color"); m_desc["second_color_caption"] = _L("Right mouse button") + ": "; m_desc["second_color"] = _L("Second color"); m_desc["remove_caption"] = _L("Shift + Left mouse button") + ": "; m_desc["remove"] = _L("Remove painted color"); - m_desc["remove_all"] = _L("Remove all painted colors"); + m_desc["remove_all"] = _L("Remove all painted areas"); m_desc["circle"] = _L("Circle"); m_desc["sphere"] = _L("Sphere"); + m_desc["pointer"] = _L("Pointer"); + + m_desc["tool_type"] = _L("Tool type"); + m_desc["tool_brush"] = _L("Brush"); + m_desc["tool_seed_fill"] = _L("Seed fill"); + m_desc["tool_bucket_fill"] = _L("Bucket fill"); + + m_desc["seed_fill"] = _L("Seed fill"); m_desc["seed_fill_angle"] = _L("Seed fill angle"); init_extruders_data(); @@ -222,7 +230,7 @@ void GLGizmoMmuSegmentation::on_render_input_window(float x, float y, float bott if (!m_c->selection_info()->model_object()) return; - const float approx_height = m_imgui->scaled(23.0f); + const float approx_height = m_imgui->scaled(25.0f); y = std::min(y, bottom_limit - approx_height); m_imgui->set_next_window_pos(x, y, ImGuiCond_Always); @@ -232,10 +240,12 @@ void GLGizmoMmuSegmentation::on_render_input_window(float x, float y, float bott const float clipping_slider_left = std::max(m_imgui->calc_text_size(m_desc.at("clipping_of_view")).x, m_imgui->calc_text_size(m_desc.at("reset_direction")).x) + m_imgui->scaled(1.5f); const float cursor_slider_left = m_imgui->calc_text_size(m_desc.at("cursor_size")).x + m_imgui->scaled(1.f); - const float autoset_slider_left = m_imgui->calc_text_size(m_desc.at("seed_fill_angle")).x + m_imgui->scaled(1.f); - const float cursor_type_radio_left = m_imgui->calc_text_size(m_desc.at("cursor_type")).x + m_imgui->scaled(1.f); - const float cursor_type_radio_width1 = m_imgui->calc_text_size(m_desc["circle"]).x + m_imgui->scaled(2.5f); - const float cursor_type_radio_width2 = m_imgui->calc_text_size(m_desc["sphere"]).x + m_imgui->scaled(2.5f); + const float seed_fill_slider_left = m_imgui->calc_text_size(m_desc.at("seed_fill_angle")).x + m_imgui->scaled(1.f); + + const float cursor_type_radio_circle = m_imgui->calc_text_size(m_desc["circle"]).x + m_imgui->scaled(2.5f); + const float cursor_type_radio_sphere = m_imgui->calc_text_size(m_desc["sphere"]).x + m_imgui->scaled(2.5f); + const float cursor_type_radio_pointer = m_imgui->calc_text_size(m_desc["pointer"]).x + m_imgui->scaled(2.5f); + const float button_width = m_imgui->calc_text_size(m_desc.at("remove_all")).x + m_imgui->scaled(1.f); const float buttons_width = m_imgui->scaled(0.5f); const float minimal_slider_width = m_imgui->scaled(4.f); @@ -243,6 +253,10 @@ void GLGizmoMmuSegmentation::on_render_input_window(float x, float y, float bott const float combo_label_width = std::max(m_imgui->calc_text_size(m_desc.at("first_color")).x, m_imgui->calc_text_size(m_desc.at("second_color")).x) + m_imgui->scaled(1.f); + const float tool_type_radio_brush = m_imgui->calc_text_size(m_desc["tool_brush"]).x + m_imgui->scaled(2.5f); + const float tool_type_radio_bucket_fill = m_imgui->calc_text_size(m_desc["tool_bucket_fill"]).x + m_imgui->scaled(2.5f); + const float tool_type_radio_seed_fill = m_imgui->calc_text_size(m_desc["tool_seed_fill"]).x + m_imgui->scaled(2.5f); + float caption_max = 0.f; float total_text_max = 0.; for (const auto &t : std::array{"first_color", "second_color", "remove"}) { @@ -252,10 +266,12 @@ void GLGizmoMmuSegmentation::on_render_input_window(float x, float y, float bott caption_max += m_imgui->scaled(1.f); total_text_max += m_imgui->scaled(1.f); - float window_width = minimal_slider_width + std::max(autoset_slider_left, std::max(cursor_slider_left, clipping_slider_left)); + float sliders_width = std::max(seed_fill_slider_left, std::max(cursor_slider_left, clipping_slider_left)); + float window_width = minimal_slider_width + sliders_width; window_width = std::max(window_width, total_text_max); window_width = std::max(window_width, button_width); - window_width = std::max(window_width, cursor_type_radio_left + cursor_type_radio_width1 + cursor_type_radio_width2); + window_width = std::max(window_width, cursor_type_radio_circle + cursor_type_radio_sphere + cursor_type_radio_pointer); + window_width = std::max(window_width, tool_type_radio_brush + tool_type_radio_bucket_fill + tool_type_radio_seed_fill); window_width = std::max(window_width, 2.f * buttons_width + m_imgui->scaled(1.f)); auto draw_text_with_caption = [this, &caption_max](const wxString &caption, const wxString &text) { @@ -292,96 +308,166 @@ void GLGizmoMmuSegmentation::on_render_input_window(float x, float y, float bott if(ImGui::ColorEdit4("Second color##color_picker", (float*)&second_color, ImGuiColorEditFlags_NoAlpha | ImGuiColorEditFlags_NoInputs | ImGuiColorEditFlags_NoLabel)) m_modified_extruders_colors[m_second_selected_extruder_idx] = {second_color.x, second_color.y, second_color.z, second_color.w}; - ImGui::Separator(); - - if (m_imgui->checkbox(_L("Seed fill"), m_seed_fill_enabled)) - if (!m_seed_fill_enabled) - for (auto &triangle_selector : m_triangle_selectors) { - triangle_selector->seed_fill_unselect_all_triangles(); - triangle_selector->request_update_render_data(); - } - - m_imgui->text(m_desc["seed_fill_angle"] + ":"); - ImGui::AlignTextToFramePadding(); - std::string format_str = std::string("%.f") + I18N::translate_utf8("°", "Degree sign to use in the respective slider in FDM supports gizmo," - "placed after the number with no whitespace in between."); - ImGui::SameLine(autoset_slider_left); - ImGui::PushItemWidth(window_width - autoset_slider_left); - m_imgui->disabled_begin(!m_seed_fill_enabled); - m_imgui->slider_float("##seed_fill_angle", &m_seed_fill_angle, 0.f, 90.f, format_str.data()); - m_imgui->disabled_end(); - - ImGui::Separator(); - - if (m_imgui->button(m_desc.at("remove_all"))) { - Plater::TakeSnapshot snapshot(wxGetApp().plater(), wxString(_L("Reset selection"))); - ModelObject *mo = m_c->selection_info()->model_object(); - int idx = -1; - for (ModelVolume *mv : mo->volumes) { - if (mv->is_model_part()) { - ++idx; - m_triangle_selectors[idx]->reset(); - m_triangle_selectors[idx]->request_update_render_data(); - } - } - - update_model_object(); - m_parent.set_as_dirty(); - } - const float max_tooltip_width = ImGui::GetFontSize() * 20.0f; + ImGui::Separator(); + ImGui::AlignTextToFramePadding(); - m_imgui->text(m_desc.at("cursor_size")); - ImGui::SameLine(cursor_slider_left); - ImGui::PushItemWidth(window_width - cursor_slider_left); - ImGui::SliderFloat(" ", &m_cursor_radius, CursorRadiusMin, CursorRadiusMax, "%.2f"); + m_imgui->text(m_desc.at("tool_type")); + + float tool_type_offset = (window_width - tool_type_radio_brush - tool_type_radio_bucket_fill - tool_type_radio_seed_fill + m_imgui->scaled(2.f)) / 2.f; + + ImGui::NewLine(); + + ImGui::AlignTextToFramePadding(); + ImGui::SameLine(tool_type_offset + m_imgui->scaled(0.f)); + ImGui::PushItemWidth(tool_type_radio_brush); + if (m_imgui->radio_button(m_desc["tool_brush"], m_tool_type == GLGizmoMmuSegmentation::ToolType::BRUSH)) + m_tool_type = GLGizmoMmuSegmentation::ToolType::BRUSH; + if (ImGui::IsItemHovered()) { ImGui::BeginTooltip(); ImGui::PushTextWrapPos(max_tooltip_width); - ImGui::TextUnformatted(_L("Alt + Mouse wheel").ToUTF8().data()); + ImGui::TextUnformatted(_L("Paints facets according to the chosen painting brush.").ToUTF8().data()); + ImGui::PopTextWrapPos(); + ImGui::EndTooltip(); + } + + ImGui::SameLine(tool_type_offset + tool_type_radio_brush + m_imgui->scaled(0.f)); + ImGui::PushItemWidth(tool_type_radio_bucket_fill); + if (m_imgui->radio_button(m_desc["tool_bucket_fill"], m_tool_type == GLGizmoMmuSegmentation::ToolType::BUCKET_FILL)) { + m_tool_type = GLGizmoMmuSegmentation::ToolType::BUCKET_FILL; + for (auto &triangle_selector : m_triangle_selectors) { + triangle_selector->seed_fill_unselect_all_triangles(); + triangle_selector->request_update_render_data(); + } + } + + if (ImGui::IsItemHovered()) { + ImGui::BeginTooltip(); + ImGui::PushTextWrapPos(max_tooltip_width); + ImGui::TextUnformatted(_L("Paints neighboring facets whose relative angle is less or equal to set angle.").ToUTF8().data()); + ImGui::PopTextWrapPos(); + ImGui::EndTooltip(); + } + + ImGui::SameLine(tool_type_offset + tool_type_radio_brush + tool_type_radio_bucket_fill + m_imgui->scaled(0.f)); + ImGui::PushItemWidth(tool_type_radio_seed_fill); + if (m_imgui->radio_button(m_desc["tool_seed_fill"], m_tool_type == GLGizmoMmuSegmentation::ToolType::SEED_FILL)) { + m_tool_type = GLGizmoMmuSegmentation::ToolType::SEED_FILL; + for (auto &triangle_selector : m_triangle_selectors) { + triangle_selector->seed_fill_unselect_all_triangles(); + triangle_selector->request_update_render_data(); + } + } + + if (ImGui::IsItemHovered()) { + ImGui::BeginTooltip(); + ImGui::PushTextWrapPos(max_tooltip_width); + ImGui::TextUnformatted(_L("Paints neighboring that have the same color.").ToUTF8().data()); ImGui::PopTextWrapPos(); ImGui::EndTooltip(); } // Manually inserted values aren't clamped by ImGui. Zero cursor size results in a crash. m_cursor_radius = std::clamp(m_cursor_radius, CursorRadiusMin, CursorRadiusMax); - ImGui::AlignTextToFramePadding(); - m_imgui->text(m_desc.at("cursor_type")); - ImGui::SameLine(cursor_type_radio_left + m_imgui->scaled(0.f)); - ImGui::PushItemWidth(cursor_type_radio_width1); - - bool sphere_sel = m_cursor_type == TriangleSelector::CursorType::SPHERE; - if (m_imgui->radio_button(m_desc["sphere"], sphere_sel)) - sphere_sel = true; - - if (ImGui::IsItemHovered()) { - ImGui::BeginTooltip(); - ImGui::PushTextWrapPos(max_tooltip_width); - ImGui::TextUnformatted(_L("Paints all facets inside, regardless of their orientation.").ToUTF8().data()); - ImGui::PopTextWrapPos(); - ImGui::EndTooltip(); - } - - ImGui::SameLine(cursor_type_radio_left + cursor_type_radio_width2 + m_imgui->scaled(0.f)); - ImGui::PushItemWidth(cursor_type_radio_width2); - - if (m_imgui->radio_button(m_desc["circle"], !sphere_sel)) - sphere_sel = false; - - if (ImGui::IsItemHovered()) { - ImGui::BeginTooltip(); - ImGui::PushTextWrapPos(max_tooltip_width); - ImGui::TextUnformatted(_L("Ignores facets facing away from the camera.").ToUTF8().data()); - ImGui::PopTextWrapPos(); - ImGui::EndTooltip(); - } - - m_cursor_type = sphere_sel ? TriangleSelector::CursorType::SPHERE : TriangleSelector::CursorType::CIRCLE; - - m_imgui->checkbox(_L("Split triangles"), m_triangle_splitting_enabled); - ImGui::Separator(); + + if(m_tool_type == ToolType::BRUSH) { + ImGui::AlignTextToFramePadding(); + m_imgui->text(m_desc.at("cursor_type")); + ImGui::NewLine(); + + float cursor_type_offset = (window_width - cursor_type_radio_sphere - cursor_type_radio_circle - cursor_type_radio_pointer + m_imgui->scaled(2.f)) / 2.f; + ImGui::AlignTextToFramePadding(); + ImGui::SameLine(cursor_type_offset + m_imgui->scaled(0.f)); + ImGui::PushItemWidth(cursor_type_radio_sphere); + if (m_imgui->radio_button(m_desc["sphere"], m_cursor_type == TriangleSelector::CursorType::SPHERE)) + m_cursor_type = TriangleSelector::CursorType::SPHERE; + + if (ImGui::IsItemHovered()) { + ImGui::BeginTooltip(); + ImGui::PushTextWrapPos(max_tooltip_width); + ImGui::TextUnformatted(_L("Paints all facets inside, regardless of their orientation.").ToUTF8().data()); + ImGui::PopTextWrapPos(); + ImGui::EndTooltip(); + } + + ImGui::SameLine(cursor_type_offset +cursor_type_radio_sphere + m_imgui->scaled(0.f)); + ImGui::PushItemWidth(cursor_type_radio_circle); + + if (m_imgui->radio_button(m_desc["circle"], m_cursor_type == TriangleSelector::CursorType::CIRCLE)) + m_cursor_type = TriangleSelector::CursorType::CIRCLE; + + if (ImGui::IsItemHovered()) { + ImGui::BeginTooltip(); + ImGui::PushTextWrapPos(max_tooltip_width); + ImGui::TextUnformatted(_L("Ignores facets facing away from the camera.").ToUTF8().data()); + ImGui::PopTextWrapPos(); + ImGui::EndTooltip(); + } + + ImGui::SameLine(cursor_type_offset + cursor_type_radio_sphere + cursor_type_radio_circle + m_imgui->scaled(0.f)); + ImGui::PushItemWidth(cursor_type_radio_pointer); + + if (m_imgui->radio_button(m_desc["pointer"], m_cursor_type == TriangleSelector::CursorType::POINTER)) + m_cursor_type = TriangleSelector::CursorType::POINTER; + + if (ImGui::IsItemHovered()) { + ImGui::BeginTooltip(); + ImGui::PushTextWrapPos(max_tooltip_width); + ImGui::TextUnformatted(_L("Paints only one facet.").ToUTF8().data()); + ImGui::PopTextWrapPos(); + ImGui::EndTooltip(); + } + + m_imgui->disabled_begin(m_cursor_type != TriangleSelector::CursorType::SPHERE && m_cursor_type != TriangleSelector::CursorType::CIRCLE); + + ImGui::AlignTextToFramePadding(); + m_imgui->text(m_desc.at("cursor_size")); + ImGui::SameLine(sliders_width); + ImGui::PushItemWidth(window_width - sliders_width); + ImGui::SliderFloat(" ", &m_cursor_radius, CursorRadiusMin, CursorRadiusMax, "%.2f"); + if (ImGui::IsItemHovered()) { + ImGui::BeginTooltip(); + ImGui::PushTextWrapPos(max_tooltip_width); + ImGui::TextUnformatted(_L("Alt + Mouse wheel").ToUTF8().data()); + ImGui::PopTextWrapPos(); + ImGui::EndTooltip(); + } + + m_imgui->checkbox(_L("Split triangles"), m_triangle_splitting_enabled); + + if (ImGui::IsItemHovered()) { + ImGui::BeginTooltip(); + ImGui::PushTextWrapPos(max_tooltip_width); + ImGui::TextUnformatted(_L("Split bigger facets into smaller ones while the object is painted.").ToUTF8().data()); + ImGui::PopTextWrapPos(); + ImGui::EndTooltip(); + } + + m_imgui->disabled_end(); + + ImGui::Separator(); + } else if(m_tool_type == ToolType::SEED_FILL) { + m_imgui->text(m_desc["seed_fill_angle"] + ":"); + ImGui::AlignTextToFramePadding(); + std::string format_str = std::string("%.f") + I18N::translate_utf8("°", "Degree sign to use in the respective slider in MMU gizmo," + "placed after the number with no whitespace in between."); + ImGui::SameLine(sliders_width); + ImGui::PushItemWidth(window_width - sliders_width); + m_imgui->slider_float("##seed_fill_angle", &m_seed_fill_angle, SeedFillAngleMin, SeedFillAngleMax, format_str.data()); + if (ImGui::IsItemHovered()) { + ImGui::BeginTooltip(); + ImGui::PushTextWrapPos(max_tooltip_width); + ImGui::TextUnformatted(_L("Alt + Mouse wheel").ToUTF8().data()); + ImGui::PopTextWrapPos(); + ImGui::EndTooltip(); + } + + ImGui::Separator(); + } + if (m_c->object_clipper()->get_position() == 0.f) { ImGui::AlignTextToFramePadding(); m_imgui->text(m_desc.at("clipping_of_view")); @@ -391,8 +477,8 @@ void GLGizmoMmuSegmentation::on_render_input_window(float x, float y, float bott } } - ImGui::SameLine(clipping_slider_left); - ImGui::PushItemWidth(window_width - clipping_slider_left); + ImGui::SameLine(sliders_width); + ImGui::PushItemWidth(window_width - sliders_width); auto clp_dist = float(m_c->object_clipper()->get_position()); if (ImGui::SliderFloat(" ", &clp_dist, 0.f, 1.f, "%.2f")) m_c->object_clipper()->set_position(clp_dist, true); @@ -403,6 +489,23 @@ void GLGizmoMmuSegmentation::on_render_input_window(float x, float y, float bott ImGui::PopTextWrapPos(); ImGui::EndTooltip(); } + + ImGui::Separator(); + if (m_imgui->button(m_desc.at("remove_all"))) { + Plater::TakeSnapshot snapshot(wxGetApp().plater(), wxString(_L("Reset selection"))); + ModelObject * mo = m_c->selection_info()->model_object(); + int idx = -1; + for (ModelVolume *mv : mo->volumes) + if (mv->is_model_part()) { + ++idx; + m_triangle_selectors[idx]->reset(); + m_triangle_selectors[idx]->request_update_render_data(); + } + + update_model_object(); + m_parent.set_as_dirty(); + } + m_imgui->end(); } diff --git a/src/slic3r/GUI/Gizmos/GLGizmoPainterBase.cpp b/src/slic3r/GUI/Gizmos/GLGizmoPainterBase.cpp index c265942e54..84591ff591 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoPainterBase.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoPainterBase.cpp @@ -194,10 +194,10 @@ void GLGizmoPainterBase::render_cursor() const if (m_rr.mesh_id == -1) return; - if (!m_seed_fill_enabled) { + if (m_tool_type == ToolType::BRUSH) { if (m_cursor_type == TriangleSelector::SPHERE) render_cursor_sphere(trafo_matrices[m_rr.mesh_id]); - else + else if (m_cursor_type == TriangleSelector::CIRCLE) render_cursor_circle(); } } @@ -307,11 +307,19 @@ bool GLGizmoPainterBase::gizmo_event(SLAGizmoEventType action, const Vec2d& mous return true; } else if (alt_down) { - m_cursor_radius = action == SLAGizmoEventType::MouseWheelDown - ? std::max(m_cursor_radius - CursorRadiusStep, CursorRadiusMin) - : std::min(m_cursor_radius + CursorRadiusStep, CursorRadiusMax); - m_parent.set_as_dirty(); - return true; + if (m_tool_type == ToolType::BRUSH) { + m_cursor_radius = action == SLAGizmoEventType::MouseWheelDown ? std::max(m_cursor_radius - CursorRadiusStep, CursorRadiusMin) + : std::min(m_cursor_radius + CursorRadiusStep, CursorRadiusMax); + m_parent.set_as_dirty(); + return true; + } else if (m_tool_type == ToolType::SEED_FILL) { + m_seed_fill_angle = action == SLAGizmoEventType::MouseWheelDown ? std::max(m_seed_fill_angle - SeedFillAngleStep, SeedFillAngleMin) + : std::min(m_seed_fill_angle + SeedFillAngleStep, SeedFillAngleMax); + m_parent.set_as_dirty(); + return true; + } + + return false; } } @@ -396,10 +404,10 @@ bool GLGizmoPainterBase::gizmo_event(SLAGizmoEventType action, const Vec2d& mous Vec3f camera_pos = (trafo_matrix.inverse() * camera.get_position()).cast(); assert(m_rr.mesh_id < int(m_triangle_selectors.size())); - if (m_seed_fill_enabled) { + if (m_tool_type == ToolType::SEED_FILL || m_tool_type == ToolType::BUCKET_FILL || (m_tool_type == ToolType::BRUSH && m_cursor_type == TriangleSelector::CursorType::POINTER)) { m_triangle_selectors[m_rr.mesh_id]->seed_fill_apply_on_triangles(new_state); m_seed_fill_last_mesh_id = -1; - } else + } else if (m_tool_type == ToolType::BRUSH) m_triangle_selectors[m_rr.mesh_id]->select_patch(m_rr.hit, int(m_rr.facet), camera_pos, m_cursor_radius, m_cursor_type, new_state, trafo_matrix, m_triangle_splitting_enabled); @@ -410,7 +418,7 @@ bool GLGizmoPainterBase::gizmo_event(SLAGizmoEventType action, const Vec2d& mous return true; } - if (action == SLAGizmoEventType::Moving && m_seed_fill_enabled) { + if (action == SLAGizmoEventType::Moving && (m_tool_type == ToolType::SEED_FILL || m_tool_type == ToolType::BUCKET_FILL || (m_tool_type == ToolType::BRUSH && m_cursor_type == TriangleSelector::CursorType::POINTER))) { if (m_triangle_selectors.empty()) return false; @@ -450,7 +458,12 @@ bool GLGizmoPainterBase::gizmo_event(SLAGizmoEventType action, const Vec2d& mous seed_fill_unselect_all(); assert(m_rr.mesh_id < int(m_triangle_selectors.size())); - m_triangle_selectors[m_rr.mesh_id]->seed_fill_select_triangles(m_rr.hit, int(m_rr.facet), m_seed_fill_angle); + if (m_tool_type == ToolType::SEED_FILL) + m_triangle_selectors[m_rr.mesh_id]->seed_fill_select_triangles(m_rr.hit, int(m_rr.facet), m_seed_fill_angle); + else if (m_tool_type == ToolType::BRUSH && m_cursor_type == TriangleSelector::CursorType::POINTER) + m_triangle_selectors[m_rr.mesh_id]->bucket_fill_select_triangles(m_rr.hit, int(m_rr.facet), false); + else if (m_tool_type == ToolType::BUCKET_FILL) + m_triangle_selectors[m_rr.mesh_id]->bucket_fill_select_triangles(m_rr.hit, int(m_rr.facet), true); m_triangle_selectors[m_rr.mesh_id]->request_update_render_data(); m_seed_fill_last_mesh_id = m_rr.mesh_id; return true; diff --git a/src/slic3r/GUI/Gizmos/GLGizmoPainterBase.hpp b/src/slic3r/GUI/Gizmos/GLGizmoPainterBase.hpp index bfda33a204..a551759b62 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoPainterBase.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoPainterBase.hpp @@ -106,13 +106,23 @@ protected: TriangleSelector::CursorType m_cursor_type = TriangleSelector::SPHERE; - bool m_triangle_splitting_enabled = true; - bool m_seed_fill_enabled = false; - float m_seed_fill_angle = 0.f; + enum class ToolType { + BRUSH, + BUCKET_FILL, + SEED_FILL + }; + + bool m_triangle_splitting_enabled = true; + ToolType m_tool_type = ToolType::BRUSH; + float m_seed_fill_angle = 0.f; + + static constexpr float SeedFillAngleMin = 0.0f; + static constexpr float SeedFillAngleMax = 90.f; + static constexpr float SeedFillAngleStep = 1.f; // It stores the value of the previous mesh_id to which the seed fill was applied. // It is used to detect when the mouse has moved from one volume to another one. - int m_seed_fill_last_mesh_id = -1; + int m_seed_fill_last_mesh_id = -1; enum class Button { None,