diff --git a/bundled_deps/miniz/miniz.c b/bundled_deps/miniz/miniz.c index 09794bea02..4905189aa4 100644 --- a/bundled_deps/miniz/miniz.c +++ b/bundled_deps/miniz/miniz.c @@ -7945,7 +7945,7 @@ mz_uint mz_zip_reader_get_filename_from_extra(mz_zip_archive* pZip, mz_uint file while (p_nf + 4 < e) { mz_uint16 len = ((mz_uint16)p_nf[2]) | ((mz_uint16)p_nf[3] << 8); if (p_nf[0] == '\x75' && p_nf[1] == '\x70' && len >= 5 && p_nf + 4 + len < e && p_nf[4] == '\x01') { - mz_uint length = MZ_MIN(len - 5, extra_buf_size - 1); + mz_uint length = MZ_MIN((mz_uint)len - 5, extra_buf_size - 1); memcpy(buffer, p_nf + 9, length); return length; } diff --git a/src/libslic3r/TriangleSelector.cpp b/src/libslic3r/TriangleSelector.cpp index 302ce06fe5..197e84dc81 100644 --- a/src/libslic3r/TriangleSelector.cpp +++ b/src/libslic3r/TriangleSelector.cpp @@ -241,27 +241,21 @@ int TriangleSelector::select_unsplit_triangle(const Vec3f &hit, int facet_idx) c void TriangleSelector::select_patch(int facet_start, std::unique_ptr &&cursor, TriangleStateType new_state, const Transform3d& trafo_no_translate, bool triangle_splitting, float highlight_by_angle_deg) { - assert(facet_start < m_orig_size_indices); + assert(this->is_original_triangle(facet_start)); // Save current cursor center, squared radius and camera direction, so we don't // have to pass it around. m_cursor = std::move(cursor); - // In case user changed cursor size since last time, update triangle edge limit. - // It is necessary to compare the internal radius in m_cursor! radius is in - // world coords and does not change after scaling. - if (m_old_cursor_radius_sqr != m_cursor->radius_sqr) { - set_edge_limit(std::sqrt(m_cursor->radius_sqr) / 5.f); - m_old_cursor_radius_sqr = m_cursor->radius_sqr; - } + // In case user changed cursor parameters size since last time, update triangle edge limit. + set_edge_limit(m_cursor->get_edge_limit()); const float highlight_angle_limit = cos(Geometry::deg2rad(highlight_by_angle_deg)); Vec3f vec_down = (trafo_no_translate.inverse() * -Vec3d::UnitZ()).normalized().cast(); // Now start with the facet the pointer points to and check all adjacent facets. - std::vector facets_to_check; + std::vector facets_to_check = m_cursor->get_facets_to_select(facet_start, m_vertices, m_triangles, m_orig_size_vertices, m_orig_size_indices); facets_to_check.reserve(16); - facets_to_check.emplace_back(facet_start); // Keep track of facets of the original mesh we already processed. std::vector visited(m_orig_size_indices, false); // Breadth-first search around the hit point. facets_to_check may grow significantly large. @@ -292,14 +286,28 @@ bool TriangleSelector::is_facet_clipped(int facet_idx, const ClippingPlane &clp) return false; } +bool TriangleSelector::is_any_neighbor_selected_by_seed_fill(const Triangle &triangle) { + size_t triangle_idx = &triangle - m_triangles.data(); + assert(triangle_idx < m_triangles.size()); + + for (int neighbor_idx: m_neighbors[triangle_idx]) { + assert(neighbor_idx >= -1); + + if (neighbor_idx >= 0 && m_triangles[neighbor_idx].is_selected_by_seed_fill()) + return true; + } + + return false; +} + void TriangleSelector::seed_fill_select_triangles(const Vec3f &hit, int facet_start, const Transform3d& trafo_no_translate, - const ClippingPlane &clp, float seed_fill_angle, float highlight_by_angle_deg, - bool force_reselection) + const ClippingPlane &clp, float seed_fill_angle, float seed_fill_gap_area, + float highlight_by_angle_deg, ForceReselection force_reselection) { - assert(facet_start < m_orig_size_indices); + assert(this->is_original_triangle(facet_start)); // Recompute seed fill only if the cursor is pointing on facet unselected by seed fill or a clipping plane is active. - if (int start_facet_idx = select_unsplit_triangle(hit, facet_start); start_facet_idx >= 0 && m_triangles[start_facet_idx].is_selected_by_seed_fill() && !force_reselection && !clp.is_active()) + if (int start_facet_idx = select_unsplit_triangle(hit, facet_start); start_facet_idx >= 0 && m_triangles[start_facet_idx].is_selected_by_seed_fill() && force_reselection == ForceReselection::NO && !clp.is_active()) return; this->seed_fill_unselect_all_triangles(); @@ -308,10 +316,13 @@ void TriangleSelector::seed_fill_select_triangles(const Vec3f &hit, int facet_st std::queue facet_queue; facet_queue.push(facet_start); - const double facet_angle_limit = cos(Geometry::deg2rad(seed_fill_angle)) - EPSILON; + const float facet_angle_limit = cos(Geometry::deg2rad(seed_fill_angle)) - EPSILON; const float highlight_angle_limit = cos(Geometry::deg2rad(highlight_by_angle_deg)); Vec3f vec_down = (trafo_no_translate.inverse() * -Vec3d::UnitZ()).normalized().cast(); + // Facets that need to be checked for gap filling. + std::vector gap_fill_candidate_facets; + // Depth-first traversal of neighbors of the face hit by the ray thrown from the mouse cursor. while (!facet_queue.empty()) { int current_facet = facet_queue.front(); @@ -323,14 +334,15 @@ void TriangleSelector::seed_fill_select_triangles(const Vec3f &hit, int facet_st for (int split_triangle_idx = 0; split_triangle_idx <= m_triangles[current_facet].number_of_split_sides(); ++split_triangle_idx) { assert(split_triangle_idx < int(m_triangles[current_facet].children.size())); assert(m_triangles[current_facet].children[split_triangle_idx] < int(m_triangles.size())); - if (int child = m_triangles[current_facet].children[split_triangle_idx]; !visited[child]) + if (int child = m_triangles[current_facet].children[split_triangle_idx]; !visited[child]) { // Child triangle shares normal with its parent. Select it. facet_queue.push(child); + } } } else m_triangles[current_facet].select_by_seed_fill(); - if (current_facet < m_orig_size_indices) + if (this->is_original_triangle(current_facet)) { // Propagate over the original triangles. for (int neighbor_idx : m_neighbors[current_facet]) { assert(neighbor_idx >= -1); @@ -338,13 +350,102 @@ void TriangleSelector::seed_fill_select_triangles(const Vec3f &hit, int facet_st // Check if neighbour_facet_idx is satisfies angle in seed_fill_angle and append it to facet_queue if it do. const Vec3f &n1 = m_face_normals[m_triangles[neighbor_idx].source_triangle]; const Vec3f &n2 = m_face_normals[m_triangles[current_facet].source_triangle]; - if (std::clamp(n1.dot(n2), 0.f, 1.f) >= facet_angle_limit) + if (std::clamp(n1.dot(n2), 0.f, 1.f) >= facet_angle_limit) { facet_queue.push(neighbor_idx); + } else if (seed_fill_gap_area > 0. && get_triangle_area(m_triangles[neighbor_idx]) <= seed_fill_gap_area) { + gap_fill_candidate_facets.emplace_back(neighbor_idx); + } } } + } } + visited[current_facet] = true; } + + seed_fill_fill_gaps(gap_fill_candidate_facets, seed_fill_gap_area); +} + +void TriangleSelector::seed_fill_fill_gaps(const std::vector &gap_fill_candidate_facets, const float seed_fill_gap_area) { + std::vector visited(m_triangles.size(), false); + + for (const int starting_facet_idx: gap_fill_candidate_facets) { + const Triangle &starting_facet = m_triangles[starting_facet_idx]; + + // If starting_facet_idx was visited from any facet, then we can skip it. + if (visited[starting_facet_idx]) + continue; + + // In the way how gap_fill_candidate_facets is filled, neither of the following two conditions should ever be met. + // But both of those conditions are here to allow more general usage of this method. + if (starting_facet.is_selected_by_seed_fill() || starting_facet.is_split()) { + // Already selected by seed fill or split facet, so no additional actions are required. + visited[starting_facet_idx] = true; + continue; + } else if (!is_any_neighbor_selected_by_seed_fill(starting_facet)) { + // No neighbor triangles are selected by seed fill, so we will skip them for now. + continue; + } + + // Now we have a triangle that has at least one neighbor selected by seed fill. + // So we start depth-first (it doesn't need to be depth-first) traversal of neighbors to check + // if the total area of unselected triangles by seed fill meets the threshold. + double total_gap_area = 0.; + std::queue facet_queue; + std::vector gap_facets; + + facet_queue.push(starting_facet_idx); + while (!facet_queue.empty()) { + const int current_facet_idx = facet_queue.front(); + const Triangle ¤t_facet = m_triangles[current_facet_idx]; + facet_queue.pop(); + + if (visited[current_facet_idx]) + continue; + + if (this->is_original_triangle(current_facet_idx)) + total_gap_area += get_triangle_area(current_facet); + + // We exceed maximum gap area. + if (total_gap_area > seed_fill_gap_area) { + // It is necessary to set every facet inside gap_facets unvisited. + // Otherwise, we incorrectly select facets that are in a gap that is bigger + // than seed_fill_gap_area. + for (const int gap_facet_idx : gap_facets) + visited[gap_facet_idx] = false; + + gap_facets.clear(); + break; + } + + if (current_facet.is_split()) { + for (int split_triangle_idx = 0; split_triangle_idx <= current_facet.number_of_split_sides(); ++split_triangle_idx) { + assert(split_triangle_idx < int(current_facet.children.size())); + assert(current_facet.children[split_triangle_idx] < int(m_triangles.size())); + if (int child = current_facet.children[split_triangle_idx]; !visited[child]) + facet_queue.push(child); + } + } else if (total_gap_area < seed_fill_gap_area) { + gap_facets.emplace_back(current_facet_idx); + } + + if (this->is_original_triangle(current_facet_idx)) { + // Propagate over the original triangles. + for (int neighbor_idx: m_neighbors[current_facet_idx]) { + assert(neighbor_idx >= -1); + if (neighbor_idx >= 0 && !visited[neighbor_idx] && !m_triangles[neighbor_idx].is_selected_by_seed_fill()) + facet_queue.push(neighbor_idx); + } + } + + visited[current_facet_idx] = true; + } + + for (int to_seed_idx : gap_facets) + m_triangles[to_seed_idx].select_by_seed_fill(); + + gap_facets.clear(); + } } void TriangleSelector::precompute_all_neighbors_recursive(const int facet_idx, const Vec3i &neighbors, const Vec3i &neighbors_propagated, std::vector &neighbors_out, std::vector &neighbors_propagated_out) const @@ -451,43 +552,59 @@ void TriangleSelector::append_touching_edges(int itriangle, int vertexi, int ver process_subtriangle(touching.second, Partition::Second); } -void TriangleSelector::bucket_fill_select_triangles(const Vec3f& hit, int facet_start, const ClippingPlane &clp, bool propagate, bool force_reselection) -{ +// Returns all triangles that are touching the given facet. +std::vector TriangleSelector::get_all_touching_triangles(int facet_idx, const Vec3i &neighbors, const Vec3i &neighbors_propagated) const { + assert(facet_idx != -1 && facet_idx < int(m_triangles.size())); + assert(this->verify_triangle_neighbors(m_triangles[facet_idx], neighbors)); + + const Vec3i vertices = { m_triangles[facet_idx].verts_idxs[0], m_triangles[facet_idx].verts_idxs[1], m_triangles[facet_idx].verts_idxs[2] }; + + std::vector touching_triangles; + append_touching_subtriangles(neighbors(0), vertices(1), vertices(0), touching_triangles); + append_touching_subtriangles(neighbors(1), vertices(2), vertices(1), touching_triangles); + append_touching_subtriangles(neighbors(2), vertices(0), vertices(2), touching_triangles); + + for (int neighbor_idx: neighbors_propagated) { + if (neighbor_idx != -1 && !m_triangles[neighbor_idx].is_split()) + touching_triangles.emplace_back(neighbor_idx); + } + + return touching_triangles; +} + +void TriangleSelector::bucket_fill_select_triangles(const Vec3f &hit, int facet_start, const ClippingPlane &clp, + float bucket_fill_angle, float bucket_fill_gap_area, + BucketFillPropagate propagate, ForceReselection force_reselection) { int start_facet_idx = select_unsplit_triangle(hit, facet_start); assert(start_facet_idx != -1); // Recompute bucket fill only if the cursor is pointing on facet unselected by bucket fill or a clipping plane is active. - if (start_facet_idx == -1 || (m_triangles[start_facet_idx].is_selected_by_seed_fill() && !force_reselection && !clp.is_active())) + if (start_facet_idx == -1 || (m_triangles[start_facet_idx].is_selected_by_seed_fill() && force_reselection == ForceReselection::NO && !clp.is_active())) return; assert(!m_triangles[start_facet_idx].is_split()); TriangleStateType start_facet_state = m_triangles[start_facet_idx].get_state(); - this->seed_fill_unselect_all_triangles(); - if (!propagate) { + if (propagate == BucketFillPropagate::NO) { + if (m_triangle_selected_by_seed_fill != -1) + this->seed_fill_unselect_triangle(m_triangle_selected_by_seed_fill); + m_triangles[start_facet_idx].select_by_seed_fill(); + m_triangle_selected_by_seed_fill = start_facet_idx; return; + } else { + m_triangle_selected_by_seed_fill = -1; + this->seed_fill_unselect_all_triangles(); } - auto get_all_touching_triangles = [this](int facet_idx, const Vec3i &neighbors, const Vec3i &neighbors_propagated) -> std::vector { - assert(facet_idx != -1 && facet_idx < int(m_triangles.size())); - assert(this->verify_triangle_neighbors(m_triangles[facet_idx], neighbors)); - std::vector touching_triangles; - Vec3i vertices = {m_triangles[facet_idx].verts_idxs[0], m_triangles[facet_idx].verts_idxs[1], m_triangles[facet_idx].verts_idxs[2]}; - append_touching_subtriangles(neighbors(0), vertices(1), vertices(0), touching_triangles); - append_touching_subtriangles(neighbors(1), vertices(2), vertices(1), touching_triangles); - append_touching_subtriangles(neighbors(2), vertices(0), vertices(2), touching_triangles); - - for (int neighbor_idx : neighbors_propagated) - if (neighbor_idx != -1 && !m_triangles[neighbor_idx].is_split()) - touching_triangles.emplace_back(neighbor_idx); - - return touching_triangles; - }; + const float facet_angle_limit = std::cos(Geometry::deg2rad(bucket_fill_angle)) - EPSILON; auto [neighbors, neighbors_propagated] = this->precompute_all_neighbors(); std::vector visited(m_triangles.size(), false); std::queue facet_queue; + // Facets that need to be checked for gap filling. + std::vector gap_fill_candidate_facets; + facet_queue.push(start_facet_idx); while (!facet_queue.empty()) { int current_facet = facet_queue.front(); @@ -497,18 +614,98 @@ void TriangleSelector::bucket_fill_select_triangles(const Vec3f& hit, int facet_ if (!visited[current_facet]) { m_triangles[current_facet].select_by_seed_fill(); - std::vector touching_triangles = get_all_touching_triangles(current_facet, neighbors[current_facet], neighbors_propagated[current_facet]); - for(const int tr_idx : touching_triangles) { + std::vector touching_triangles = this->get_all_touching_triangles(current_facet, neighbors[current_facet], neighbors_propagated[current_facet]); + for (const int tr_idx: touching_triangles) { if (tr_idx < 0 || visited[tr_idx] || m_triangles[tr_idx].get_state() != start_facet_state || is_facet_clipped(tr_idx, clp)) continue; - assert(!m_triangles[tr_idx].is_split()); - facet_queue.push(tr_idx); + // Check if neighbour_facet_idx is satisfies angle in seed_fill_angle and append it to facet_queue if it do. + const Vec3f &n1 = m_face_normals[m_triangles[tr_idx].source_triangle]; + const Vec3f &n2 = m_face_normals[m_triangles[current_facet].source_triangle]; + if (std::clamp(n1.dot(n2), 0.f, 1.f) >= facet_angle_limit) { + assert(!m_triangles[tr_idx].is_split()); + facet_queue.push(tr_idx); + } else if (bucket_fill_gap_area > 0. && get_triangle_area(m_triangles[tr_idx]) <= bucket_fill_gap_area) { + gap_fill_candidate_facets.emplace_back(tr_idx); + } } } visited[current_facet] = true; } + + bucket_fill_fill_gaps(gap_fill_candidate_facets, bucket_fill_gap_area, start_facet_state, neighbors, neighbors_propagated); +} + +void TriangleSelector::bucket_fill_fill_gaps(const std::vector &gap_fill_candidate_facets, const float bucket_fill_gap_area, + const TriangleStateType start_facet_state, const std::vector &neighbors, + const std::vector &neighbors_propagated) { + std::vector visited(m_triangles.size(), false); + + for (const int starting_facet_idx: gap_fill_candidate_facets) { + const Triangle &starting_facet = m_triangles[starting_facet_idx]; + + // If starting_facet_idx was visited from any facet, then we can skip it. + if (visited[starting_facet_idx]) + continue; + + // In the way how gap_fill_candidate_facets is filled, neither of the following two conditions should ever be met. + // But both of those conditions are here to allow more general usage of this method. + if (starting_facet.is_selected_by_seed_fill() || starting_facet.is_split()) { + // Already selected by seed fill or split facet, so no additional actions are required. + visited[starting_facet_idx] = true; + continue; + } + + // In the way how bucket_fill_select_triangles() is implemented, all gap_fill_candidate_facets + // have at least one neighbor selected by seed fill. + // So we start depth-first (it doesn't need to be depth-first) traversal of neighbors to check + // if the total area of unselected triangles by seed fill meets the threshold. + double total_gap_area = 0.; + std::queue facet_queue; + std::vector gap_facets; + + facet_queue.push(starting_facet_idx); + while (!facet_queue.empty()) { + const int current_facet_idx = facet_queue.front(); + const Triangle ¤t_facet = m_triangles[current_facet_idx]; + facet_queue.pop(); + + if (visited[current_facet_idx]) + continue; + + total_gap_area += get_triangle_area(current_facet); + + // We exceed maximum gap area. + if (total_gap_area > bucket_fill_gap_area) { + // It is necessary to set every facet inside gap_facets unvisited. + // Otherwise, we incorrectly select facets that are in a gap that is bigger + // than bucket_fill_gap_area. + for (const int gap_facet_idx : gap_facets) + visited[gap_facet_idx] = false; + + gap_facets.clear(); + break; + } + + gap_facets.emplace_back(current_facet_idx); + + std::vector touching_triangles = this->get_all_touching_triangles(current_facet_idx, neighbors[current_facet_idx], neighbors_propagated[current_facet_idx]); + for (const int tr_idx: touching_triangles) { + if (tr_idx < 0 || visited[tr_idx] || m_triangles[tr_idx].get_state() != start_facet_state || m_triangles[tr_idx].is_selected_by_seed_fill()) + continue; + + facet_queue.push(tr_idx); + } + + visited[current_facet_idx] = true; + } + + for (int to_seed_idx : gap_facets) + m_triangles[to_seed_idx].select_by_seed_fill(); + + gap_facets.clear(); + } } // Selects either the whole triangle (discarding any children it had), or divides @@ -878,7 +1075,7 @@ bool TriangleSelector::select_triangle_recursive(int facet_idx, const Vec3i &nei if (num_of_inside_vertices == 0 && ! m_cursor->is_pointer_in_triangle(*tr, m_vertices) - && ! m_cursor->is_edge_inside_cursor(*tr, m_vertices)) + && ! m_cursor->is_any_edge_inside_cursor(*tr, m_vertices)) return false; if (num_of_inside_vertices == 3) { @@ -918,7 +1115,7 @@ bool TriangleSelector::select_triangle_recursive(int facet_idx, const Vec3i &nei } void TriangleSelector::set_facet(int facet_idx, TriangleStateType state) { - assert(facet_idx < m_orig_size_indices); + assert(this->is_original_triangle(facet_idx)); undivide_triangle(facet_idx); assert(! m_triangles[facet_idx].is_split()); m_triangles[facet_idx].set_state(state); @@ -949,8 +1146,8 @@ void TriangleSelector::split_triangle(int facet_idx, const Vec3i &neighbors) // In case the object is non-uniformly scaled, transform the // points to world coords. - if (! m_cursor->uniform_scaling) { - for (size_t i=0; iuniform_scaling) { + for (size_t i = 0; i < pts.size(); ++i) { pts_transformed[i] = m_cursor->trafo * (*pts[i]); pts[i] = &pts_transformed[i]; } @@ -1011,16 +1208,21 @@ int TriangleSelector::Cursor::vertices_inside(const Triangle &tr, const std::vec return inside; } -// Is any edge inside Sphere cursor? -bool TriangleSelector::Sphere::is_edge_inside_cursor(const Triangle &tr, const std::vector &vertices) const -{ +inline std::array TriangleSelector::Cursor::transform_triangle(const Triangle &tr, const std::vector &vertices) const { std::array pts; - for (int i = 0; i < 3; ++i) { + for (size_t i = 0; i < 3; ++i) { pts[i] = vertices[tr.verts_idxs[i]].v; if (!this->uniform_scaling) pts[i] = this->trafo * pts[i]; } + return pts; +} + +// Is any edge inside Sphere cursor? +bool TriangleSelector::Sphere::is_any_edge_inside_cursor(const Triangle &tr, const std::vector &vertices) const +{ + const std::array pts = this->transform_triangle(tr, vertices); for (int side = 0; side < 3; ++side) { const Vec3f &edge_a = pts[side]; const Vec3f &edge_b = pts[side < 2 ? side + 1 : 0]; @@ -1031,16 +1233,10 @@ bool TriangleSelector::Sphere::is_edge_inside_cursor(const Triangle &tr, const s } // Is edge inside cursor? -bool TriangleSelector::Circle::is_edge_inside_cursor(const Triangle &tr, const std::vector &vertices) const +bool TriangleSelector::Circle::is_any_edge_inside_cursor(const Triangle &tr, const std::vector &vertices) const { - std::array pts; - for (int i = 0; i < 3; ++i) { - pts[i] = vertices[tr.verts_idxs[i]].v; - if (!this->uniform_scaling) - pts[i] = this->trafo * pts[i]; - } - - const Vec3f &p = this->center; + const std::array pts = this->transform_triangle(tr, vertices); + const Vec3f &p = this->center; for (int side = 0; side < 3; ++side) { const Vec3f &a = pts[side]; const Vec3f &b = pts[side < 2 ? side + 1 : 0]; @@ -1096,42 +1292,43 @@ void TriangleSelector::undivide_triangle(int facet_idx) } } -void TriangleSelector::remove_useless_children(int facet_idx) -{ +// Returns true when some triangle during recursive descending was removed (undivided). +bool TriangleSelector::remove_useless_children(int facet_idx) { // Check that all children are leafs of the same type. If not, try to - // make them (recursive call). Remove them if sucessful. + // make them (recursive call). Remove them if successful. assert(facet_idx < int(m_triangles.size()) && m_triangles[facet_idx].valid()); - Triangle& tr = m_triangles[facet_idx]; + Triangle &tr = m_triangles[facet_idx]; - if (! tr.is_split()) { + if (!tr.is_split()) { // This is a leaf, there nothing to do. This can happen during the // first (non-recursive call). Shouldn't otherwise. - return; + return false; } // Call this for all non-leaf children. - for (int child_idx=0; child_idx<=tr.number_of_split_sides(); ++child_idx) { + bool children_removed = false; + for (int child_idx = 0; child_idx <= tr.number_of_split_sides(); ++child_idx) { assert(child_idx < int(m_triangles.size()) && m_triangles[child_idx].valid()); if (m_triangles[tr.children[child_idx]].is_split()) - remove_useless_children(tr.children[child_idx]); + children_removed |= remove_useless_children(tr.children[child_idx]); } - // Return if a child is not leaf or two children differ in type. TriangleStateType first_child_type = TriangleStateType::NONE; - for (int child_idx=0; child_idx<=tr.number_of_split_sides(); ++child_idx) { + for (int child_idx = 0; child_idx <= tr.number_of_split_sides(); ++child_idx) { if (m_triangles[tr.children[child_idx]].is_split()) - return; + return children_removed; if (child_idx == 0) first_child_type = m_triangles[tr.children[0]].get_state(); else if (m_triangles[tr.children[child_idx]].get_state() != first_child_type) - return; + return children_removed; } // If we got here, the children can be removed. undivide_triangle(facet_idx); tr.set_state(first_child_type); + return true; } void TriangleSelector::garbage_collect() @@ -1216,7 +1413,7 @@ void TriangleSelector::reset() void TriangleSelector::set_edge_limit(float edge_limit) { - m_edge_limit_sqr = std::pow(edge_limit, 2.f); + m_edge_limit_sqr = Slic3r::sqr(edge_limit); } int TriangleSelector::push_triangle(int a, int b, int c, int source_triangle, const TriangleStateType state) { @@ -1860,7 +2057,22 @@ void TriangleSelector::seed_fill_unselect_all_triangles() triangle.unselect_by_seed_fill(); } +void TriangleSelector::seed_fill_unselect_triangle(const int facet_idx) { + assert(facet_idx > 0 && facet_idx < m_triangles.size()); + Triangle &triangle = m_triangles[facet_idx]; + + assert(!triangle.is_split()); + if (!triangle.is_split()) + triangle.unselect_by_seed_fill(); +} + void TriangleSelector::seed_fill_apply_on_triangles(TriangleStateType new_state) { + if (m_triangle_selected_by_seed_fill != -1) { + this->seed_fill_apply_on_single_triangle(new_state, m_triangle_selected_by_seed_fill); + m_triangle_selected_by_seed_fill = -1; + return; + } + for (Triangle &triangle : m_triangles) if (!triangle.is_split() && triangle.is_selected_by_seed_fill()) triangle.set_state(new_state); @@ -1872,13 +2084,29 @@ void TriangleSelector::seed_fill_apply_on_triangles(TriangleStateType new_state) } } +void TriangleSelector::seed_fill_apply_on_single_triangle(TriangleStateType new_state, const int facet_idx) { + assert(facet_idx > 0 && facet_idx < m_triangles.size()); + + if (Triangle &triangle = m_triangles[facet_idx]; !triangle.is_split() && triangle.is_selected_by_seed_fill()) { + triangle.set_state(new_state); + remove_useless_children(triangle.source_triangle); + } +} + +double TriangleSelector::get_triangle_area(const Triangle &triangle) const { + const stl_vertex &v0 = m_vertices[triangle.verts_idxs[0]].v; + const stl_vertex &v1 = m_vertices[triangle.verts_idxs[1]].v; + const stl_vertex &v2 = m_vertices[triangle.verts_idxs[2]].v; + return (v1 - v0).cross(v2 - v0).norm() / 2.; +} + TriangleSelector::Cursor::Cursor(const Vec3f &source_, float radius_world, const Transform3d &trafo_, const ClippingPlane &clipping_plane_) : source{source_}, trafo{trafo_.cast()}, clipping_plane{clipping_plane_} { Vec3d sf = Geometry::Transformation(trafo_).get_scaling_factor(); - if (is_approx(sf(0), sf(1)) && is_approx(sf(1), sf(2))) { - radius = float(radius_world / sf(0)); - radius_sqr = float(Slic3r::sqr(radius_world / sf(0))); + if (is_approx(sf.x(), sf.y()) && is_approx(sf.y(), sf.z())) { + radius = float(radius_world / sf.x()); + radius_sqr = float(Slic3r::sqr(radius_world / sf.x())); uniform_scaling = true; } else { // In case that the transformation is non-uniform, all checks whether @@ -1890,6 +2118,8 @@ TriangleSelector::Cursor::Cursor(const Vec3f &source_, float radius_world, const radius_sqr = Slic3r::sqr(radius_world); trafo_normal = trafo.linear().inverse().transpose(); } + + m_edge_limit = std::sqrt(radius_sqr) / 5.f; } TriangleSelector::SinglePointCursor::SinglePointCursor(const Vec3f& center_, const Vec3f& source_, float radius_world, const Transform3d& trafo_, const ClippingPlane &clipping_plane_) @@ -2048,15 +2278,9 @@ bool line_plane_intersection(const Vec3f &line_a, const Vec3f &line_b, const Vec return false; } -bool TriangleSelector::Capsule3D::is_edge_inside_cursor(const Triangle &tr, const std::vector &vertices) const +bool TriangleSelector::Capsule3D::is_any_edge_inside_cursor(const Triangle &tr, const std::vector &vertices) const { - std::array pts; - for (int i = 0; i < 3; ++i) { - pts[i] = vertices[tr.verts_idxs[i]].v; - if (!this->uniform_scaling) - pts[i] = this->trafo * pts[i]; - } - + const std::array pts = this->transform_triangle(tr, vertices); for (int side = 0; side < 3; ++side) { const Vec3f &edge_a = pts[side]; const Vec3f &edge_b = pts[side < 2 ? side + 1 : 0]; @@ -2068,15 +2292,8 @@ bool TriangleSelector::Capsule3D::is_edge_inside_cursor(const Triangle &tr, cons } // Is edge inside cursor? -bool TriangleSelector::Capsule2D::is_edge_inside_cursor(const Triangle &tr, const std::vector &vertices) const +bool TriangleSelector::Capsule2D::is_any_edge_inside_cursor(const Triangle &tr, const std::vector &vertices) const { - std::array pts; - for (int i = 0; i < 3; ++i) { - pts[i] = vertices[tr.verts_idxs[i]].v; - if (!this->uniform_scaling) - pts[i] = this->trafo * pts[i]; - } - const Vec3f centers_diff = this->second_center - this->first_center; // Vector in the direction of line |AD| of the rectangle that intersects the circle with the center in first_center. const Vec3f rectangle_da_dir = centers_diff.cross(this->dir); @@ -2095,6 +2312,7 @@ bool TriangleSelector::Capsule2D::is_edge_inside_cursor(const Triangle &tr, cons return false; }; + const std::array pts = this->transform_triangle(tr, vertices); for (int side = 0; side < 3; ++side) { const Vec3f &edge_a = pts[side]; const Vec3f &edge_b = pts[side < 2 ? side + 1 : 0]; @@ -2122,4 +2340,54 @@ bool TriangleSelector::Capsule2D::is_edge_inside_cursor(const Triangle &tr, cons return false; } +TriangleSelector::HeightRange::HeightRange(const Vec3f &mesh_hit, const BoundingBoxf3 &mesh_bbox, float z_range, const Transform3d &trafo, const ClippingPlane &clipping_plane) + : Cursor(Vec3f::Zero(), 0.f, trafo, clipping_plane) { + m_z_range_top = std::max(mesh_hit.z() + z_range / 2.f, float(mesh_bbox.min.z())); + m_z_range_bottom = std::min(mesh_hit.z() - z_range / 2.f, float(mesh_bbox.max.z())); + m_edge_limit = 0.1f; +} + +bool TriangleSelector::HeightRange::is_mesh_point_inside(const Vec3f &point) const { + const float transformed_point_z = (this->uniform_scaling ? point : Vec3f(this->trafo * point)).z(); + return Slic3r::is_in_range(transformed_point_z, m_z_range_bottom, m_z_range_top); +} + +bool TriangleSelector::HeightRange::is_any_edge_inside_cursor(const Triangle &tr, const std::vector &vertices) const { + const std::array pts = this->transform_triangle(tr, vertices); + // If all vertices are below m_z_range_bottom or all vertices are above m_z_range_top, then it means that no edge + // is inside the height range. Otherwise, there is at least one edge inside the height range. + return !((pts[0].z() < m_z_range_bottom && pts[1].z() < m_z_range_bottom && pts[2].z() < m_z_range_bottom) || + (pts[0].z() > m_z_range_top && pts[1].z() > m_z_range_top && pts[2].z() > m_z_range_top)); +} + +std::vector TriangleSelector::HeightRange::get_facets_to_select(const int facet_idx, const std::vector &vertices, const std::vector &triangles, const int orig_size_vertices, const int orig_size_indices) const { + std::vector facets_to_check; + + // Assigns each vertex a value of -1, 1, or 0. The value -1 indicates a vertex is below m_z_range_bottom, + // while 1 indicates a vertex is above m_z_range_top. The value of 0 indicates that the vertex between + // m_z_range_bottom and m_z_range_top. + std::vector vertex_side(orig_size_vertices, 0); + if (trafo.matrix() == Transform3f::Identity().matrix()) { + for (int i = 0; i < orig_size_vertices; ++i) { + const float z = vertices[i].v.z(); + vertex_side[i] = z < m_z_range_bottom ? int8_t(-1) : z > m_z_range_top ? int8_t(1) : int8_t(0); + } + } else { + for (int i = 0; i < orig_size_vertices; ++i) { + const float z = (this->uniform_scaling ? vertices[i].v : Vec3f(this->trafo * vertices[i].v)).z(); + vertex_side[i] = z < m_z_range_bottom ? int8_t(-1) : z > m_z_range_top ? int8_t(1) : int8_t(0); + } + } + + // Determine if each triangle crosses m_z_range_bottom or m_z_range_top. + for (int i = 0; i < orig_size_indices; ++i) { + const std::array &face = triangles[i].verts_idxs; + const std::array sides = { vertex_side[face[0]], vertex_side[face[1]], vertex_side[face[2]] }; + if ((sides[0] * sides[1] <= 0) || (sides[1] * sides[2] <= 0) || (sides[0] * sides[2] <= 0)) + facets_to_check.emplace_back(i); + } + + return facets_to_check; +} + } // namespace Slic3r diff --git a/src/libslic3r/TriangleSelector.hpp b/src/libslic3r/TriangleSelector.hpp index 2cd5a0a980..534aa33ec7 100644 --- a/src/libslic3r/TriangleSelector.hpp +++ b/src/libslic3r/TriangleSelector.hpp @@ -67,10 +67,21 @@ protected: struct Vertex; public: - enum CursorType { + enum class CursorType { CIRCLE, SPHERE, - POINTER + POINTER, + HEIGHT_RANGE + }; + + enum class ForceReselection { + NO, + YES + }; + + enum class BucketFillPropagate { + NO, + YES }; struct ClippingPlane @@ -91,14 +102,21 @@ public: Cursor() = delete; virtual ~Cursor() = default; + float get_edge_limit() { return m_edge_limit; }; + bool is_pointer_in_triangle(const Triangle &tr, const std::vector &vertices) const; + std::array transform_triangle(const Triangle &tr, const std::vector &vertices) const; virtual bool is_mesh_point_inside(const Vec3f &point) const = 0; virtual bool is_pointer_in_triangle(const Vec3f &p1, const Vec3f &p2, const Vec3f &p3) const = 0; virtual int vertices_inside(const Triangle &tr, const std::vector &vertices) const; - virtual bool is_edge_inside_cursor(const Triangle &tr, const std::vector &vertices) const = 0; + virtual bool is_any_edge_inside_cursor(const Triangle &tr, const std::vector &vertices) const = 0; virtual bool is_facet_visible(int facet_idx, const std::vector &face_normals) const = 0; + virtual std::vector get_facets_to_select(int facet_idx, const std::vector &vertices, const std::vector &triangles, int orig_size_vertices, int orig_size_indices) const { + return { facet_idx }; + }; + static bool is_facet_visible(const Cursor &cursor, int facet_idx, const std::vector &face_normals); protected: @@ -115,6 +133,8 @@ public: ClippingPlane clipping_plane; // Clipping plane to limit painting to not clipped facets only + float m_edge_limit; + friend TriangleSelector; }; @@ -174,7 +194,7 @@ public: ~Sphere() override = default; bool is_mesh_point_inside(const Vec3f &point) const override; - bool is_edge_inside_cursor(const Triangle &tr, const std::vector &vertices) const override; + bool is_any_edge_inside_cursor(const Triangle &tr, const std::vector &vertices) const override; bool is_facet_visible(int facet_idx, const std::vector &face_normals) const override { return true; } }; @@ -187,9 +207,8 @@ public: ~Circle() override = default; bool is_mesh_point_inside(const Vec3f &point) const override; - bool is_edge_inside_cursor(const Triangle &tr, const std::vector &vertices) const override; - bool is_facet_visible(int facet_idx, const std::vector &face_normals) const override - { + bool is_any_edge_inside_cursor(const Triangle &tr, const std::vector &vertices) const override; + bool is_facet_visible(int facet_idx, const std::vector &face_normals) const override { return TriangleSelector::Cursor::is_facet_visible(*this, facet_idx, face_normals); } }; @@ -204,7 +223,7 @@ public: ~Capsule3D() override = default; bool is_mesh_point_inside(const Vec3f &point) const override; - bool is_edge_inside_cursor(const Triangle &tr, const std::vector &vertices) const override; + bool is_any_edge_inside_cursor(const Triangle &tr, const std::vector &vertices) const override; bool is_facet_visible(int facet_idx, const std::vector &face_normals) const override { return true; } }; @@ -218,13 +237,32 @@ public: ~Capsule2D() override = default; bool is_mesh_point_inside(const Vec3f &point) const override; - bool is_edge_inside_cursor(const Triangle &tr, const std::vector &vertices) const override; - bool is_facet_visible(int facet_idx, const std::vector &face_normals) const override - { + bool is_any_edge_inside_cursor(const Triangle &tr, const std::vector &vertices) const override; + bool is_facet_visible(int facet_idx, const std::vector &face_normals) const override { return TriangleSelector::Cursor::is_facet_visible(*this, facet_idx, face_normals); } }; + class HeightRange : public Cursor + { + public: + HeightRange() = delete; + + explicit HeightRange(const Vec3f &mesh_hit, const BoundingBoxf3 &mesh_bbox, float z_range, const Transform3d &trafo, const ClippingPlane &clipping_plane); + ~HeightRange() override = default; + + bool is_pointer_in_triangle(const Vec3f &p1, const Vec3f &p2, const Vec3f &p3) const override { return false; } + bool is_mesh_point_inside(const Vec3f &point) const override; + bool is_any_edge_inside_cursor(const Triangle &tr, const std::vector &vertices) const override; + bool is_facet_visible(int facet_idx, const std::vector &face_normals) const override { return true; } + + std::vector get_facets_to_select(int facet_idx, const std::vector &vertices, const std::vector &triangles, int orig_size_vertices, int orig_size_indices) const override; + + private: + float m_z_range_top; + float m_z_range_bottom; + }; + struct TriangleBitStreamMapping { // Index of the triangle to which we assign the bitstream containing splitting information. @@ -298,19 +336,22 @@ public: bool triangle_splitting, // If triangles will be split base on the cursor or not float highlight_by_angle_deg = 0.f); // The maximal angle of overhang. If it is set to a non-zero value, it is possible to paint only the triangles of overhang defined by this angle in degrees. - void seed_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 - const Transform3d &trafo_no_translate, // matrix to get from mesh to world without translation - const ClippingPlane &clp, // Clipping plane to limit painting to not clipped facets only - float seed_fill_angle, // the maximal angle between two facets to be painted by the same color - float highlight_by_angle_deg = 0.f, // The maximal angle of overhang. If it is set to a non-zero value, it is possible to paint only the triangles of overhang defined by this angle in degrees. - bool force_reselection = false); // force reselection of the triangle mesh even in cases that mouse is pointing on the selected triangle + void seed_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 + const Transform3d &trafo_no_translate, // matrix to get from mesh to world without translation + const ClippingPlane &clp, // Clipping plane to limit painting to not clipped facets only + float seed_fill_angle, // the maximal angle between two facets to be painted by the same color + float seed_fill_gap_area, // The maximal area that will be automatically selected when the surrounding triangles have already been selected. + float highlight_by_angle_deg = 0.f, // The maximal angle of overhang. If it is set to a non-zero value, it is possible to paint only the triangles of overhang defined by this angle in degrees. + ForceReselection force_reselection = ForceReselection::NO); // force reselection of the triangle mesh even in cases that mouse is pointing on the selected triangle - 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 - const ClippingPlane &clp, // Clipping plane to limit painting to not clipped facets only - 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 force_reselection = false); // force reselection of the triangle mesh even in cases that mouse is pointing on the selected triangle + 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 + const ClippingPlane &clp, // Clipping plane to limit painting to not clipped facets only + float bucket_fill_angle, // the maximal angle between two facets to be painted by the same color + float bucket_fill_gap_area, // The maximal area that will be automatically selected when the surrounding triangles have already been selected. + BucketFillPropagate 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. + ForceReselection force_reselection = ForceReselection::NO); // force reselection of the triangle mesh even in cases that mouse is pointing on the selected triangle bool has_facets(TriangleStateType state) const; static bool has_facets(const TriangleSplittingData &data, TriangleStateType test_state); @@ -360,10 +401,20 @@ public: // For all triangles, remove the flag indicating that the triangle was selected by seed fill. void seed_fill_unselect_all_triangles(); + // Remove the flag indicating that the triangle was selected by seed fill. + void seed_fill_unselect_triangle(int facet_idx); + // For all triangles selected by seed fill, set new TriangleStateType and remove flag indicating that triangle was selected by seed fill. // The operation may merge split triangles if they are being assigned the same color. void seed_fill_apply_on_triangles(TriangleStateType new_state); + // For the triangle selected by seed fill, set a new TriangleStateType and remove the flag indicating that the triangle was selected by seed fill. + // The operation may merge a split triangle if it is being assigned the same color. + void seed_fill_apply_on_single_triangle(TriangleStateType new_state, const int facet_idx); + + // Compute total area of the triangle. + double get_triangle_area(const Triangle &triangle) const; + protected: // Triangle and info about how it's split. class Triangle { @@ -450,8 +501,9 @@ protected: int m_orig_size_indices = 0; std::unique_ptr m_cursor; - // Zero indicates an uninitialized state. - float m_old_cursor_radius_sqr = 0; + + // Single triangle selected by seed fill. It is used to optimize painting using a single triangle brush. + int m_triangle_selected_by_seed_fill = -1; // Private functions: private: @@ -459,7 +511,7 @@ private: bool select_triangle_recursive(int facet_idx, const Vec3i &neighbors, TriangleStateType type, bool triangle_splitting); void undivide_triangle(int facet_idx); void split_triangle(int facet_idx, const Vec3i &neighbors); - void remove_useless_children(int facet_idx); // No hidden meaning. Triangles are meant. + bool remove_useless_children(int facet_idx); // No hidden meaning. Triangles are meant. bool is_facet_clipped(int facet_idx, const ClippingPlane &clp) const; int push_triangle(int a, int b, int c, int source_triangle, TriangleStateType state = TriangleStateType::NONE); void perform_split(int facet_idx, const Vec3i &neighbors, TriangleStateType old_state); @@ -483,6 +535,12 @@ private: void append_touching_subtriangles(int itriangle, int vertexi, int vertexj, std::vector &touching_subtriangles_out) const; void append_touching_edges(int itriangle, int vertexi, int vertexj, std::vector &touching_edges_out) const; + // Returns all triangles that are touching the given facet. + std::vector get_all_touching_triangles(int facet_idx, const Vec3i &neighbors, const Vec3i &neighbors_propagated) const; + + // Check if the triangle index is the original triangle from mesh, or it was additionally created by splitting. + bool is_original_triangle(int triangle_idx) const { return triangle_idx < m_orig_size_indices; } + #ifndef NDEBUG bool verify_triangle_neighbors(const Triangle& tr, const Vec3i& neighbors) const; bool verify_triangle_midpoints(const Triangle& tr) const; @@ -501,6 +559,17 @@ private: void get_seed_fill_contour_recursive(int facet_idx, const Vec3i &neighbors, const Vec3i &neighbors_propagated, std::vector &edges_out) const; + bool is_any_neighbor_selected_by_seed_fill(const Triangle &triangle); + + void seed_fill_fill_gaps(const std::vector &gap_fill_candidate_facets, // Facet of the original mesh (unsplit), which needs to be checked if the surrounding gap can be filled (selected). + float seed_fill_gap_area); // The maximal area that will be automatically selected when the surrounding triangles have already been selected. + + void bucket_fill_fill_gaps(const std::vector &gap_fill_candidate_facets, // Facet of the mesh (unsplit), which needs to be checked if the surrounding gap can be filled (selected). + float bucket_fill_gap_area, // The maximal area that will be automatically selected when the surrounding triangles have already been selected. + TriangleStateType start_facet_state, // The state of the starting facet that determines which neighbors to consider. + const std::vector &neighbors, + const std::vector &neighbors_propagate); + int m_free_triangles_head { -1 }; int m_free_vertices_head { -1 }; }; diff --git a/src/libslic3r/libslic3r.h b/src/libslic3r/libslic3r.h index eaeeaf966d..291e1686ec 100644 --- a/src/libslic3r/libslic3r.h +++ b/src/libslic3r/libslic3r.h @@ -487,6 +487,11 @@ Fn for_each_in_tuple(Fn fn, Tup &&tup) return fn; } +template +inline bool is_in_range(const T &value, const T &low, const T &high) { + return low <= value && value <= high; +} + } // namespace Slic3r #endif // _libslic3r_h_ diff --git a/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.cpp b/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.cpp index 1efb9035c7..dde015484d 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.cpp @@ -103,7 +103,7 @@ void GLGizmoFdmSupports::on_render_input_window(float x, float y, float bottom_l if (! m_c->selection_info()->model_object()) return; - const float approx_height = m_imgui->scaled(25.f); + const float approx_height = m_imgui->scaled(26.3f); y = std::min(y, bottom_limit - approx_height); ImGuiPureWrap::set_next_window_pos(x, y, ImGuiCond_Always); @@ -115,7 +115,7 @@ void GLGizmoFdmSupports::on_render_input_window(float x, float y, float bottom_l const float cursor_slider_left = ImGuiPureWrap::calc_text_size(m_desc.at("cursor_size")).x + m_imgui->scaled(1.f); const float smart_fill_slider_left = ImGuiPureWrap::calc_text_size(m_desc.at("smart_fill_angle")).x + m_imgui->scaled(1.f); const float autoset_slider_label_max_width = m_imgui->scaled(7.5f); - const float autoset_slider_left = ImGuiPureWrap::calc_text_size(m_desc.at("highlight_by_angle"), autoset_slider_label_max_width).x + m_imgui->scaled(1.f); + const float autoset_slider_left = ImGuiPureWrap::calc_text_size(m_desc.at("highlight_by_angle"), false, autoset_slider_label_max_width).x + m_imgui->scaled(1.f); const float cursor_type_radio_circle = ImGuiPureWrap::calc_text_size(m_desc["circle"]).x + m_imgui->scaled(2.5f); const float cursor_type_radio_sphere = ImGuiPureWrap::calc_text_size(m_desc["sphere"]).x + m_imgui->scaled(2.5f); @@ -301,11 +301,12 @@ void GLGizmoFdmSupports::on_render_input_window(float x, float y, float bottom_l ImGui::SameLine(sliders_left_width); ImGui::PushItemWidth(window_width - sliders_left_width - slider_icon_width); - if (m_imgui->slider_float("##smart_fill_angle", &m_smart_fill_angle, SmartFillAngleMin, SmartFillAngleMax, format_str.data(), 1.0f, true, _L("Alt + Mouse wheel"))) - for (auto &triangle_selector : m_triangle_selectors) { + if (m_imgui->slider_float("##smart_fill_angle", &m_smart_fill_angle, SmartFillAngleMin, SmartFillAngleMax, format_str.data(), 1.0f, true, _L("Alt + Mouse wheel"))) { + for (auto &triangle_selector: m_triangle_selectors) { triangle_selector->seed_fill_unselect_all_triangles(); triangle_selector->request_update_render_data(); } + } } ImGui::Separator(); @@ -459,7 +460,7 @@ void GLGizmoFdmSupports::update_model_object() const if (! mv->is_model_part()) continue; ++idx; - updated |= mv->supported_facets.set(*m_triangle_selectors[idx].get()); + updated |= mv->supported_facets.set(*m_triangle_selectors[idx]); } if (updated) { diff --git a/src/slic3r/GUI/Gizmos/GLGizmoMmuSegmentation.cpp b/src/slic3r/GUI/Gizmos/GLGizmoMmuSegmentation.cpp index c39eb4edc1..abc840baf4 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoMmuSegmentation.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoMmuSegmentation.cpp @@ -110,6 +110,12 @@ bool GLGizmoMmuSegmentation::on_init() m_desc["second_color"] = _u8L("Second color"); m_desc["remove_caption"] = _u8L("Shift + Left mouse button") + ": "; m_desc["remove"] = _u8L("Remove painted color"); + + m_desc["alt_caption"] = _u8L("Alt + Mouse wheel") + ": "; + m_desc["alt_brush"] = _u8L("Change brush size"); + m_desc["alt_fill"] = _u8L("Change angle"); + m_desc["alt_height_range"] = _u8L("Change height range"); + m_desc["remove_all"] = _u8L("Clear all"); m_desc["circle"] = _u8L("Circle"); m_desc["sphere"] = _u8L("Sphere"); @@ -119,10 +125,15 @@ bool GLGizmoMmuSegmentation::on_init() m_desc["tool_brush"] = _u8L("Brush"); m_desc["tool_smart_fill"] = _u8L("Smart fill"); m_desc["tool_bucket_fill"] = _u8L("Bucket fill"); + m_desc["tool_height_range"] = _u8L("Height range"); m_desc["smart_fill_angle"] = _u8L("Smart fill angle"); + m_desc["bucket_fill_angle"] = _u8L("Bucket fill angle"); + m_desc["split_triangles"] = _u8L("Split triangles"); + m_desc["height_range_z_range"] = _u8L("Height range"); + init_extruders_data(); return true; @@ -263,17 +274,19 @@ 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(22.0f); + const float approx_height = m_imgui->scaled(25.35f); y = std::min(y, bottom_limit - approx_height); ImGuiPureWrap::set_next_window_pos(x, y, ImGuiCond_Always); ImGuiPureWrap::begin(get_name(), ImGuiWindowFlags_NoMove | ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoCollapse); // First calculate width of all the texts that are could possibly be shown. We will decide set the dialog width based on that: - const float clipping_slider_left = std::max(ImGuiPureWrap::calc_text_size(m_desc.at("clipping_of_view")).x, - ImGuiPureWrap::calc_text_size(m_desc.at("reset_direction")).x) + m_imgui->scaled(1.5f); + const float clipping_slider_left = std::max(ImGuiPureWrap::calc_text_size(m_desc.at("clipping_of_view")).x, + ImGuiPureWrap::calc_text_size(m_desc.at("reset_direction")).x) + m_imgui->scaled(1.5f); const float cursor_slider_left = ImGuiPureWrap::calc_text_size(m_desc.at("cursor_size")).x + m_imgui->scaled(1.f); const float smart_fill_slider_left = ImGuiPureWrap::calc_text_size(m_desc.at("smart_fill_angle")).x + m_imgui->scaled(1.f); + const float bucket_fill_slider_left = ImGuiPureWrap::calc_text_size(m_desc.at("bucket_fill_angle")).x + m_imgui->scaled(1.f); + const float height_range_slider_left = ImGuiPureWrap::calc_text_size(m_desc.at("height_range_z_range")).x + m_imgui->scaled(1.f); const float cursor_type_radio_circle = ImGuiPureWrap::calc_text_size(m_desc["circle"]).x + m_imgui->scaled(2.5f); const float cursor_type_radio_sphere = ImGuiPureWrap::calc_text_size(m_desc["sphere"]).x + m_imgui->scaled(2.5f); @@ -286,30 +299,39 @@ void GLGizmoMmuSegmentation::on_render_input_window(float x, float y, float bott const float combo_label_width = std::max(ImGuiPureWrap::calc_text_size(m_desc.at("first_color")).x, ImGuiPureWrap::calc_text_size(m_desc.at("second_color")).x) + m_imgui->scaled(1.f); - const float tool_type_radio_brush = ImGuiPureWrap::calc_text_size(m_desc["tool_brush"]).x + m_imgui->scaled(2.5f); - const float tool_type_radio_bucket_fill = ImGuiPureWrap::calc_text_size(m_desc["tool_bucket_fill"]).x + m_imgui->scaled(2.5f); - const float tool_type_radio_smart_fill = ImGuiPureWrap::calc_text_size(m_desc["tool_smart_fill"]).x + m_imgui->scaled(2.5f); + const float tool_type_radio_brush = ImGuiPureWrap::calc_text_size(m_desc["tool_brush"]).x + m_imgui->scaled(2.5f); + const float tool_type_radio_bucket_fill = ImGuiPureWrap::calc_text_size(m_desc["tool_bucket_fill"]).x + m_imgui->scaled(2.5f); + const float tool_type_radio_smart_fill = ImGuiPureWrap::calc_text_size(m_desc["tool_smart_fill"]).x + m_imgui->scaled(2.5f); + const float tool_type_radio_height_range = ImGuiPureWrap::calc_text_size(m_desc["tool_height_range"]).x + m_imgui->scaled(2.5f); + + const float tool_type_radio_first_line = tool_type_radio_brush + tool_type_radio_bucket_fill + tool_type_radio_smart_fill; + const float tool_type_radio_second_line = tool_type_radio_height_range; + const float tool_type_radio_max_width = std::max(tool_type_radio_first_line, tool_type_radio_second_line); const float split_triangles_checkbox_width = ImGuiPureWrap::calc_text_size(m_desc["split_triangles"]).x + m_imgui->scaled(2.5f); - float caption_max = 0.f; + float caption_max = 0.f; + for (const std::string t : {"first_color", "second_color", "remove", "alt"}) { + caption_max = std::max(caption_max, ImGuiPureWrap::calc_text_size(m_desc[t + "_caption"]).x); + } + float total_text_max = 0.f; - for (const auto &t : std::array{"first_color", "second_color", "remove"}) { - caption_max = std::max(caption_max, ImGuiPureWrap::calc_text_size(m_desc[t + "_caption"]).x); + for (const std::string t : {"first_color", "second_color", "remove", "alt_brush", "alt_fill", "alt_height_range"}) { total_text_max = std::max(total_text_max, ImGuiPureWrap::calc_text_size(m_desc[t]).x); } + total_text_max += caption_max + m_imgui->scaled(1.f); caption_max += m_imgui->scaled(1.f); - const float sliders_left_width = std::max(smart_fill_slider_left, std::max(cursor_slider_left, clipping_slider_left)); + const float sliders_left_width = std::max({smart_fill_slider_left, bucket_fill_slider_left, cursor_slider_left, clipping_slider_left, height_range_slider_left}); const float slider_icon_width = ImGuiPureWrap::get_slider_icon_size().x; float window_width = minimal_slider_width + sliders_left_width + slider_icon_width; - window_width = std::max(window_width, total_text_max); - window_width = std::max(window_width, button_width); - window_width = std::max(window_width, split_triangles_checkbox_width); - 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_smart_fill); - window_width = std::max(window_width, 2.f * buttons_width + m_imgui->scaled(1.f)); + window_width = std::max(window_width, total_text_max); + window_width = std::max(window_width, button_width); + window_width = std::max(window_width, split_triangles_checkbox_width); + 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_max_width); + window_width = std::max(window_width, 2.f * buttons_width + m_imgui->scaled(1.f)); auto draw_text_with_caption = [&caption_max](const std::string &caption, const std::string& text) { ImGuiPureWrap::text_colored(ImGuiPureWrap::COL_ORANGE_LIGHT, caption); @@ -317,8 +339,14 @@ void GLGizmoMmuSegmentation::on_render_input_window(float x, float y, float bott ImGuiPureWrap::text(text); }; - for (const auto &t : std::array{"first_color", "second_color", "remove"}) + for (const std::string t : {"first_color", "second_color", "remove"}) { draw_text_with_caption(m_desc.at(t + "_caption"), m_desc.at(t)); + } + + std::string alt_hint_text = (m_tool_type == ToolType::BRUSH) ? "alt_brush" : + (m_tool_type == ToolType::HEIGHT_RANGE) ? "alt_height_range" + : "alt_fill"; + draw_text_with_caption(m_desc.at("alt_caption"), m_desc.at(alt_hint_text)); ImGui::Separator(); @@ -360,8 +388,8 @@ void GLGizmoMmuSegmentation::on_render_input_window(float x, float y, float bott ImGuiPureWrap::text(m_desc.at("tool_type")); ImGui::NewLine(); - float tool_type_offset = (window_width - tool_type_radio_brush - tool_type_radio_bucket_fill - tool_type_radio_smart_fill + m_imgui->scaled(1.5f)) / 2.f; - ImGui::SameLine(tool_type_offset); + const float tool_type_first_line_offset = (window_width - tool_type_radio_first_line + m_imgui->scaled(1.5f)) / 2.f; + ImGui::SameLine(tool_type_first_line_offset); ImGui::PushItemWidth(tool_type_radio_brush); if (ImGuiPureWrap::radio_button(m_desc["tool_brush"], m_tool_type == ToolType::BRUSH)) { m_tool_type = ToolType::BRUSH; @@ -374,7 +402,7 @@ void GLGizmoMmuSegmentation::on_render_input_window(float x, float y, float bott if (ImGui::IsItemHovered()) ImGuiPureWrap::tooltip(_u8L("Paints facets according to the chosen painting brush."), max_tooltip_width); - ImGui::SameLine(tool_type_offset + tool_type_radio_brush); + ImGui::SameLine(tool_type_first_line_offset + tool_type_radio_brush); ImGui::PushItemWidth(tool_type_radio_smart_fill); if (ImGuiPureWrap::radio_button(m_desc["tool_smart_fill"], m_tool_type == ToolType::SMART_FILL)) { m_tool_type = ToolType::SMART_FILL; @@ -387,7 +415,7 @@ void GLGizmoMmuSegmentation::on_render_input_window(float x, float y, float bott if (ImGui::IsItemHovered()) ImGuiPureWrap::tooltip(_u8L("Paints neighboring facets whose relative angle is less or equal to set angle."), max_tooltip_width); - ImGui::SameLine(tool_type_offset + tool_type_radio_brush + tool_type_radio_smart_fill); + ImGui::SameLine(tool_type_first_line_offset + tool_type_radio_brush + tool_type_radio_smart_fill); ImGui::PushItemWidth(tool_type_radio_bucket_fill); if (ImGuiPureWrap::radio_button(m_desc["tool_bucket_fill"], m_tool_type == ToolType::BUCKET_FILL)) { m_tool_type = ToolType::BUCKET_FILL; @@ -400,9 +428,25 @@ void GLGizmoMmuSegmentation::on_render_input_window(float x, float y, float bott if (ImGui::IsItemHovered()) ImGuiPureWrap::tooltip(_u8L("Paints neighboring facets that have the same color."), max_tooltip_width); + ImGui::NewLine(); + + const float tool_type_second_line_offset = (window_width - tool_type_radio_second_line + m_imgui->scaled(1.5f)) / 2.f; + ImGui::SameLine(tool_type_second_line_offset); + ImGui::PushItemWidth(tool_type_radio_height_range); + if (ImGuiPureWrap::radio_button(m_desc["tool_height_range"], m_tool_type == ToolType::HEIGHT_RANGE)) { + m_tool_type = ToolType::HEIGHT_RANGE; + for (auto &triangle_selector : m_triangle_selectors) { + triangle_selector->seed_fill_unselect_all_triangles(); + triangle_selector->request_update_render_data(); + } + } + + if (ImGui::IsItemHovered()) + ImGuiPureWrap::tooltip(_u8L("Paints facets within the chosen height range."), max_tooltip_width); + ImGui::Separator(); - if(m_tool_type == ToolType::BRUSH) { + if (m_tool_type == ToolType::BRUSH) { ImGuiPureWrap::text(m_desc.at("cursor_type")); ImGui::NewLine(); @@ -449,18 +493,35 @@ void GLGizmoMmuSegmentation::on_render_input_window(float x, float y, float bott m_imgui->disabled_end(); ImGui::Separator(); - } else if(m_tool_type == ToolType::SMART_FILL) { + } else if (m_tool_type == ToolType::SMART_FILL || m_tool_type == ToolType::BUCKET_FILL) { ImGui::AlignTextToFramePadding(); - ImGuiPureWrap::text(m_desc["smart_fill_angle"] + ":"); - 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."); + ImGuiPureWrap::text((m_tool_type == ToolType::SMART_FILL ? m_desc["smart_fill_angle"] : m_desc["bucket_fill_angle"]) + ":"); + std::string format_str_angle = 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_left_width); ImGui::PushItemWidth(window_width - sliders_left_width - slider_icon_width); - if (m_imgui->slider_float("##smart_fill_angle", &m_smart_fill_angle, SmartFillAngleMin, SmartFillAngleMax, format_str.data(), 1.0f, true, _L("Alt + Mouse wheel"))) - for (auto &triangle_selector : m_triangle_selectors) { + float &fill_angle = (m_tool_type == ToolType::SMART_FILL) ? m_smart_fill_angle : m_bucket_fill_angle; + if (m_imgui->slider_float("##fill_angle", &fill_angle, SmartFillAngleMin, SmartFillAngleMax, format_str_angle.data(), 1.0f, true, _L("Alt + Mouse wheel"))) { + for (auto &triangle_selector: m_triangle_selectors) { triangle_selector->seed_fill_unselect_all_triangles(); triangle_selector->request_update_render_data(); } + } + + ImGui::Separator(); + } else if (m_tool_type == ToolType::HEIGHT_RANGE) { + ImGui::AlignTextToFramePadding(); + ImGuiPureWrap::text(m_desc["height_range_z_range"] + ":"); + std::string format_str_angle = std::string("%.2f ") + I18N::translate_utf8("mm", "Millimeter sign to use in the respective slider in multi-material painting gizmo," + "placed after the number with space in between."); + ImGui::SameLine(sliders_left_width); + ImGui::PushItemWidth(window_width - sliders_left_width - slider_icon_width); + if (m_imgui->slider_float("##height_range_z_range", &m_height_range_z_range, HeightRangeZRangeMin, HeightRangeZRangeMax, format_str_angle.data(), 1.0f, true, _L("Alt + Mouse wheel"))) { + for (auto &triangle_selector: m_triangle_selectors) { + triangle_selector->seed_fill_unselect_all_triangles(); + triangle_selector->request_update_render_data(); + } + } ImGui::Separator(); } @@ -509,7 +570,7 @@ void GLGizmoMmuSegmentation::update_model_object() const if (! mv->is_model_part()) continue; ++idx; - updated |= mv->mm_segmentation_facets.set(*m_triangle_selectors[idx].get()); + updated |= mv->mm_segmentation_facets.set(*m_triangle_selectors[idx]); } if (updated) { @@ -699,7 +760,7 @@ void GLMmSegmentationGizmo3DScene::render(size_t triangle_indices_idx) const glsafe(::glBindBuffer(GL_ARRAY_BUFFER, this->vertices_VBO_id)); const GLint position_id = shader->get_attrib_location("v_position"); if (position_id != -1) { - glsafe(::glVertexAttribPointer(position_id, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (GLvoid*)0)); + glsafe(::glVertexAttribPointer(position_id, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (GLvoid*)nullptr)); glsafe(::glEnableVertexAttribArray(position_id)); } diff --git a/src/slic3r/GUI/Gizmos/GLGizmoMmuSegmentation.hpp b/src/slic3r/GUI/Gizmos/GLGizmoMmuSegmentation.hpp index 7bec4c78b6..a8e51962dd 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoMmuSegmentation.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoMmuSegmentation.hpp @@ -104,7 +104,7 @@ public: // 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; - const float get_cursor_radius_min() const override { return CursorRadiusMin; } + float get_cursor_radius_min() const override { return CursorRadiusMin; } protected: ColorRGBA get_cursor_sphere_left_button_color() const override; diff --git a/src/slic3r/GUI/Gizmos/GLGizmoPainterBase.cpp b/src/slic3r/GUI/Gizmos/GLGizmoPainterBase.cpp index e871e1a539..54fa9b3369 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoPainterBase.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoPainterBase.cpp @@ -18,7 +18,9 @@ #include "libslic3r/TriangleMesh.hpp" #include +#include #include +#include namespace Slic3r::GUI { @@ -139,10 +141,12 @@ void GLGizmoPainterBase::render_cursor() return; if (m_tool_type == ToolType::BRUSH) { - if (m_cursor_type == TriangleSelector::SPHERE) + if (m_cursor_type == TriangleSelector::CursorType::SPHERE) render_cursor_sphere(trafo_matrices[m_rr.mesh_id]); - else if (m_cursor_type == TriangleSelector::CIRCLE) + else if (m_cursor_type == TriangleSelector::CursorType::CIRCLE) render_cursor_circle(); + } else if (m_tool_type == ToolType::HEIGHT_RANGE) { + render_cursor_height_range(trafo_matrices[m_rr.mesh_id]); } } @@ -270,7 +274,6 @@ void GLGizmoPainterBase::render_cursor_circle() glsafe(::glEnable(GL_DEPTH_TEST)); } - void GLGizmoPainterBase::render_cursor_sphere(const Transform3d& trafo) const { if (s_sphere == nullptr) { @@ -314,6 +317,74 @@ void GLGizmoPainterBase::render_cursor_sphere(const Transform3d& trafo) const shader->stop_using(); } +void GLGizmoPainterBase::render_cursor_height_range(const Transform3d &trafo) const { + const ModelObject &model_object = *m_c->selection_info()->model_object(); + const BoundingBoxf3 mesh_bbox = model_object.volumes[m_rr.mesh_id]->mesh().bounding_box(); + + const std::array z_range = { + std::min(m_rr.hit.z() - m_height_range_z_range / 2.f, float(mesh_bbox.max.z())), + std::max(m_rr.hit.z() + m_height_range_z_range / 2.f, float(mesh_bbox.min.z())) + }; + + std::vector slice_polygons_per_z; + for (const float z: z_range) + slice_polygons_per_z.emplace_back(slice_mesh(model_object.volumes[m_rr.mesh_id]->mesh().its, z, MeshSlicingParams())); + + const size_t max_vertices_cnt = std::accumulate(slice_polygons_per_z.begin(), slice_polygons_per_z.end(), 0, + [](const size_t sum, const Polygons &polygons) { + return sum + count_points(polygons); + }); + + GLModel::Geometry z_range_geometry; + z_range_geometry.format = {GLModel::Geometry::EPrimitiveType::Lines, GLModel::Geometry::EVertexLayout::P3}; + z_range_geometry.reserve_vertices(max_vertices_cnt); + z_range_geometry.reserve_indices(max_vertices_cnt); + z_range_geometry.color = ColorRGBA::WHITE(); + + size_t vertices_cnt = 0; + for (const float z: z_range) { + const Polygons slice_polygons = slice_mesh(model_object.volumes[m_rr.mesh_id]->mesh().its, z, MeshSlicingParams()); + for (const Polygon &polygon: slice_polygons) { + for (const Point &pt: polygon.points) + z_range_geometry.add_vertex(Vec3f(unscaled(pt.x()), unscaled(pt.y()), z)); + + for (size_t pt_idx = 1; pt_idx < polygon.points.size(); ++pt_idx) + z_range_geometry.add_line(vertices_cnt + pt_idx - 1, vertices_cnt + pt_idx); + + z_range_geometry.add_line(vertices_cnt + polygon.points.size() - 1, vertices_cnt); + + vertices_cnt += polygon.points.size(); + } + } + + GLModel z_range_model; + if (!z_range_geometry.is_empty()) + z_range_model.init_from(std::move(z_range_geometry)); + + const Camera &camera = wxGetApp().plater()->get_camera(); + const Transform3d view_model_matrix = camera.get_view_matrix() * trafo; + + GLShaderProgram *shader = wxGetApp().get_shader("mm_contour"); + if (shader == nullptr) + return; + + shader->start_using(); + shader->set_uniform("offset", OpenGLManager::get_gl_info().is_mesa() ? 0.0005 : 0.00001); + shader->set_uniform("view_model_matrix", view_model_matrix); + shader->set_uniform("projection_matrix", camera.get_projection_matrix()); + ScopeGuard guard([shader]() { if (shader) shader->stop_using(); }); + + const bool is_left_handed = Geometry::Transformation(view_model_matrix).is_left_handed(); + if (is_left_handed) + glsafe(::glFrontFace(GL_CW)); + + z_range_model.render(); + + if (is_left_handed) + glsafe(::glFrontFace(GL_CCW)); + + shader->stop_using(); +} bool GLGizmoPainterBase::is_mesh_point_clipped(const Vec3d& point, const Transform3d& trafo) const { @@ -469,22 +540,38 @@ bool GLGizmoPainterBase::gizmo_event(SLAGizmoEventType action, const Vec2d& mous : std::min(m_cursor_radius + this->get_cursor_radius_step(), this->get_cursor_radius_max()); m_parent.set_as_dirty(); return true; - } else if (m_tool_type == ToolType::SMART_FILL) { - m_smart_fill_angle = action == SLAGizmoEventType::MouseWheelDown ? std::max(m_smart_fill_angle - SmartFillAngleStep, SmartFillAngleMin) - : std::min(m_smart_fill_angle + SmartFillAngleStep, SmartFillAngleMax); + } else if (m_tool_type == ToolType::SMART_FILL || m_tool_type == ToolType::BUCKET_FILL) { + float &fill_angle = (m_tool_type == ToolType::SMART_FILL) ? m_smart_fill_angle : m_bucket_fill_angle; + fill_angle = (action == SLAGizmoEventType::MouseWheelDown) ? std::max(fill_angle - SmartFillAngleStep, SmartFillAngleMin) + : std::min(fill_angle + SmartFillAngleStep, SmartFillAngleMax); + m_parent.set_as_dirty(); if (m_rr.mesh_id != -1) { - const Selection &selection = m_parent.get_selection(); - const ModelObject *mo = m_c->selection_info()->model_object(); - const ModelInstance *mi = mo->instances[selection.get_instance_idx()]; - const Transform3d trafo_matrix_not_translate = mi->get_transformation().get_matrix_no_offset() * mo->volumes[m_rr.mesh_id]->get_matrix_no_offset(); - const Transform3d trafo_matrix = mi->get_transformation().get_matrix() * mo->volumes[m_rr.mesh_id]->get_matrix(); - m_triangle_selectors[m_rr.mesh_id]->seed_fill_select_triangles(m_rr.hit, int(m_rr.facet), trafo_matrix_not_translate, this->get_clipping_plane_in_volume_coordinates(trafo_matrix), m_smart_fill_angle, - m_paint_on_overhangs_only ? m_highlight_by_angle_threshold_deg : 0.f, true); + const Selection &selection = m_parent.get_selection(); + const ModelObject *mo = m_c->selection_info()->model_object(); + const ModelInstance *mi = mo->instances[selection.get_instance_idx()]; + const Transform3d trafo_matrix = mi->get_transformation().get_matrix() * mo->volumes[m_rr.mesh_id]->get_matrix(); + const TriangleSelector::ClippingPlane &clipping_plane = this->get_clipping_plane_in_volume_coordinates(trafo_matrix); + + if (m_tool_type == ToolType::SMART_FILL) { + const Transform3d trafo_matrix_not_translate = mi->get_transformation().get_matrix_no_offset() * mo->volumes[m_rr.mesh_id]->get_matrix_no_offset(); + m_triangle_selectors[m_rr.mesh_id]->seed_fill_select_triangles(m_rr.hit, int(m_rr.facet), trafo_matrix_not_translate, clipping_plane, m_smart_fill_angle, SmartFillGapArea, + m_paint_on_overhangs_only ? m_highlight_by_angle_threshold_deg : 0.f, TriangleSelector::ForceReselection::YES); + } else { + assert(m_tool_type == ToolType::BUCKET_FILL); + m_triangle_selectors[m_rr.mesh_id]->bucket_fill_select_triangles(m_rr.hit, int(m_rr.facet), clipping_plane, m_bucket_fill_angle, BucketFillGapArea, + TriangleSelector::BucketFillPropagate::YES, TriangleSelector::ForceReselection::YES); + } + m_triangle_selectors[m_rr.mesh_id]->request_update_render_data(); m_seed_fill_last_mesh_id = m_rr.mesh_id; } return true; + } else if (m_tool_type == ToolType::HEIGHT_RANGE) { + m_height_range_z_range = action == SLAGizmoEventType::MouseWheelDown ? std::max(m_height_range_z_range - HeightRangeZRangeStep, HeightRangeZRangeMin) + : std::min(m_height_range_z_range + HeightRangeZRangeStep, HeightRangeZRangeMax); + m_parent.set_as_dirty(); + return true; } return false; @@ -556,18 +643,21 @@ bool GLGizmoPainterBase::gizmo_event(SLAGizmoEventType action, const Vec2d& mous assert(mesh_idx < int(m_triangle_selectors.size())); const TriangleSelector::ClippingPlane &clp = this->get_clipping_plane_in_volume_coordinates(trafo_matrix); if (m_tool_type == ToolType::SMART_FILL || m_tool_type == ToolType::BUCKET_FILL || (m_tool_type == ToolType::BRUSH && m_cursor_type == TriangleSelector::CursorType::POINTER)) { - for(const ProjectedMousePosition &projected_mouse_position : projected_mouse_positions) { + for (const ProjectedMousePosition &projected_mouse_position: projected_mouse_positions) { assert(projected_mouse_position.mesh_idx == mesh_idx); - const Vec3f mesh_hit = projected_mouse_position.mesh_hit; - const int facet_idx = int(projected_mouse_position.facet_idx); + const Vec3f mesh_hit = projected_mouse_position.mesh_hit; + const int facet_idx = int(projected_mouse_position.facet_idx); + m_triangle_selectors[mesh_idx]->seed_fill_apply_on_triangles(new_state); - if (m_tool_type == ToolType::SMART_FILL) - m_triangle_selectors[mesh_idx]->seed_fill_select_triangles(mesh_hit, facet_idx, trafo_matrix_not_translate, clp, m_smart_fill_angle, - m_paint_on_overhangs_only ? m_highlight_by_angle_threshold_deg : 0.f, true); - else if (m_tool_type == ToolType::BRUSH && m_cursor_type == TriangleSelector::CursorType::POINTER) - m_triangle_selectors[mesh_idx]->bucket_fill_select_triangles(mesh_hit, facet_idx, clp, false, true); - else if (m_tool_type == ToolType::BUCKET_FILL) - m_triangle_selectors[mesh_idx]->bucket_fill_select_triangles(mesh_hit, facet_idx, clp, true, true); + + if (m_tool_type == ToolType::SMART_FILL) { + m_triangle_selectors[mesh_idx]->seed_fill_select_triangles(mesh_hit, facet_idx, trafo_matrix_not_translate, clp, m_smart_fill_angle, SmartFillGapArea, + (m_paint_on_overhangs_only ? m_highlight_by_angle_threshold_deg : 0.f), TriangleSelector::ForceReselection::YES); + } else if (m_tool_type == ToolType::BRUSH && m_cursor_type == TriangleSelector::CursorType::POINTER) { + m_triangle_selectors[mesh_idx]->bucket_fill_select_triangles(mesh_hit, facet_idx, clp, m_bucket_fill_angle, BucketFillGapArea, TriangleSelector::BucketFillPropagate::NO, TriangleSelector::ForceReselection::YES); + } else if (m_tool_type == ToolType::BUCKET_FILL) { + m_triangle_selectors[mesh_idx]->bucket_fill_select_triangles(mesh_hit, facet_idx, clp, m_bucket_fill_angle, BucketFillGapArea, TriangleSelector::BucketFillPropagate::YES, TriangleSelector::ForceReselection::YES); + } m_seed_fill_last_mesh_id = -1; } @@ -588,6 +678,16 @@ bool GLGizmoPainterBase::gizmo_event(SLAGizmoEventType action, const Vec2d& mous m_triangle_selectors[mesh_idx]->select_patch(int(first_position_it->facet_idx), std::move(cursor), new_state, trafo_matrix_not_translate, m_triangle_splitting_enabled, m_paint_on_overhangs_only ? m_highlight_by_angle_threshold_deg : 0.f); } } + } else if (m_tool_type == ToolType::HEIGHT_RANGE) { + for (const ProjectedMousePosition &projected_mouse_position: projected_mouse_positions) { + const Vec3f &mesh_hit = projected_mouse_position.mesh_hit; + const int facet_idx = int(projected_mouse_position.facet_idx); + const BoundingBoxf3 mesh_bbox = mo->volumes[projected_mouse_position.mesh_idx]->mesh().bounding_box(); + + std::unique_ptr cursor = std::make_unique(mesh_hit, mesh_bbox, m_height_range_z_range, trafo_matrix, clp); + m_triangle_selectors[mesh_idx]->select_patch(facet_idx, std::move(cursor), new_state, trafo_matrix_not_translate, + m_triangle_splitting_enabled, m_paint_on_overhangs_only ? m_highlight_by_angle_threshold_deg : 0.f); + } } m_triangle_selectors[mesh_idx]->request_update_render_data(); @@ -646,12 +746,12 @@ bool GLGizmoPainterBase::gizmo_event(SLAGizmoEventType action, const Vec2d& mous assert(m_rr.mesh_id < int(m_triangle_selectors.size())); const TriangleSelector::ClippingPlane &clp = this->get_clipping_plane_in_volume_coordinates(trafo_matrix); if (m_tool_type == ToolType::SMART_FILL) - m_triangle_selectors[m_rr.mesh_id]->seed_fill_select_triangles(m_rr.hit, int(m_rr.facet), trafo_matrix_not_translate, clp, m_smart_fill_angle, + m_triangle_selectors[m_rr.mesh_id]->seed_fill_select_triangles(m_rr.hit, int(m_rr.facet), trafo_matrix_not_translate, clp, m_smart_fill_angle, SmartFillGapArea, m_paint_on_overhangs_only ? m_highlight_by_angle_threshold_deg : 0.f); 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), clp, false); + m_triangle_selectors[m_rr.mesh_id]->bucket_fill_select_triangles(m_rr.hit, int(m_rr.facet), clp, m_bucket_fill_angle, BucketFillGapArea, TriangleSelector::BucketFillPropagate::NO); 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), clp, true); + m_triangle_selectors[m_rr.mesh_id]->bucket_fill_select_triangles(m_rr.hit, int(m_rr.facet), clp, m_bucket_fill_angle, BucketFillGapArea, TriangleSelector::BucketFillPropagate::YES); 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 31f38cc743..ea600ad60e 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoPainterBase.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoPainterBase.hpp @@ -97,9 +97,9 @@ public: // after all volumes (including transparent ones) are rendered. virtual void render_painter_gizmo() = 0; - virtual const float get_cursor_radius_min() const { return CursorRadiusMin; } - virtual const float get_cursor_radius_max() const { return CursorRadiusMax; } - virtual const float get_cursor_radius_step() const { return CursorRadiusStep; } + virtual float get_cursor_radius_min() const { return CursorRadiusMin; } + virtual float get_cursor_radius_max() const { return CursorRadiusMax; } + virtual float get_cursor_radius_step() const { return CursorRadiusStep; } /// /// Implement when want to process mouse events in gizmo @@ -112,9 +112,12 @@ public: protected: virtual void render_triangles(const Selection& selection) const; + void render_cursor(); void render_cursor_circle(); - void render_cursor_sphere(const Transform3d& trafo) const; + void render_cursor_sphere(const Transform3d &trafo) const; + void render_cursor_height_range(const Transform3d &trafo) const; + virtual void update_model_object() const = 0; virtual void update_from_model_object() = 0; @@ -132,12 +135,13 @@ protected: // For each model-part volume, store status and division of the triangles. std::vector> m_triangle_selectors; - TriangleSelector::CursorType m_cursor_type = TriangleSelector::SPHERE; + TriangleSelector::CursorType m_cursor_type = TriangleSelector::CursorType::SPHERE; enum class ToolType { BRUSH, BUCKET_FILL, - SMART_FILL + SMART_FILL, + HEIGHT_RANGE }; struct ProjectedMousePosition @@ -150,6 +154,8 @@ protected: bool m_triangle_splitting_enabled = true; ToolType m_tool_type = ToolType::BRUSH; float m_smart_fill_angle = 30.f; + float m_bucket_fill_angle = 90.f; + float m_height_range_z_range = 1.00f; bool m_paint_on_overhangs_only = false; float m_highlight_by_angle_threshold_deg = 0.f; @@ -162,6 +168,13 @@ protected: static constexpr float SmartFillAngleMax = 90.f; static constexpr float SmartFillAngleStep = 1.f; + static constexpr float HeightRangeZRangeMin = 0.1f; + static constexpr float HeightRangeZRangeMax = 10.f; + static constexpr float HeightRangeZRangeStep = 0.1f; + + static constexpr float SmartFillGapArea = 0.02f; + static constexpr float BucketFillGapArea = 0.02f; + // 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; @@ -192,7 +205,6 @@ private: static std::shared_ptr s_sphere; - bool m_internal_stack_active = false; bool m_schedule_update = false; Vec2d m_last_mouse_click = Vec2d::Zero(); diff --git a/src/slic3r/GUI/Gizmos/GLGizmoSeam.cpp b/src/slic3r/GUI/Gizmos/GLGizmoSeam.cpp index 8998f33755..080c2cb2ec 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoSeam.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoSeam.cpp @@ -79,7 +79,7 @@ void GLGizmoSeam::on_render_input_window(float x, float y, float bottom_limit) if (! m_c->selection_info()->model_object()) return; - const float approx_height = m_imgui->scaled(12.5f); + const float approx_height = m_imgui->scaled(13.45f); y = std::min(y, bottom_limit - approx_height); ImGuiPureWrap::set_next_window_pos(x, y, ImGuiCond_Always); ImGuiPureWrap::begin(get_name(), ImGuiWindowFlags_NoMove | ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoCollapse); @@ -201,7 +201,7 @@ void GLGizmoSeam::update_model_object() const if (! mv->is_model_part()) continue; ++idx; - updated |= mv->seam_facets.set(*m_triangle_selectors[idx].get()); + updated |= mv->seam_facets.set(*m_triangle_selectors[idx]); } if (updated) {