mirror of
https://git.mirrors.martin98.com/https://github.com/prusa3d/PrusaSlicer.git
synced 2025-08-14 23:06:13 +08:00
SPE-2003: Improve the smart fill in the multi-material painting gizmo to automatically select tiny triangles even if they exceed the angle limit.
This commit is contained in:
parent
f253822707
commit
535cbb2567
@ -241,7 +241,7 @@ int TriangleSelector::select_unsplit_triangle(const Vec3f &hit, int facet_idx) c
|
|||||||
|
|
||||||
void TriangleSelector::select_patch(int facet_start, std::unique_ptr<Cursor> &&cursor, TriangleStateType new_state, const Transform3d& trafo_no_translate, bool triangle_splitting, float highlight_by_angle_deg)
|
void TriangleSelector::select_patch(int facet_start, std::unique_ptr<Cursor> &&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
|
// Save current cursor center, squared radius and camera direction, so we don't
|
||||||
// have to pass it around.
|
// have to pass it around.
|
||||||
@ -286,11 +286,25 @@ bool TriangleSelector::is_facet_clipped(int facet_idx, const ClippingPlane &clp)
|
|||||||
return false;
|
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,
|
void TriangleSelector::seed_fill_select_triangles(const Vec3f &hit, int facet_start, const Transform3d& trafo_no_translate,
|
||||||
const ClippingPlane &clp, float seed_fill_angle,
|
const ClippingPlane &clp, float seed_fill_angle, float seed_fill_gap_area,
|
||||||
float highlight_by_angle_deg, ForceReselection force_reselection)
|
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.
|
// 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 == ForceReselection::NO && !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())
|
||||||
@ -306,6 +320,9 @@ void TriangleSelector::seed_fill_select_triangles(const Vec3f &hit, int facet_st
|
|||||||
const float highlight_angle_limit = cos(Geometry::deg2rad(highlight_by_angle_deg));
|
const float highlight_angle_limit = cos(Geometry::deg2rad(highlight_by_angle_deg));
|
||||||
Vec3f vec_down = (trafo_no_translate.inverse() * -Vec3d::UnitZ()).normalized().cast<float>();
|
Vec3f vec_down = (trafo_no_translate.inverse() * -Vec3d::UnitZ()).normalized().cast<float>();
|
||||||
|
|
||||||
|
// Facets that need to be checked for gap filling.
|
||||||
|
std::vector<int> gap_fill_candidate_facets;
|
||||||
|
|
||||||
// Depth-first traversal of neighbors of the face hit by the ray thrown from the mouse cursor.
|
// 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();
|
int current_facet = facet_queue.front();
|
||||||
@ -317,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) {
|
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(split_triangle_idx < int(m_triangles[current_facet].children.size()));
|
||||||
assert(m_triangles[current_facet].children[split_triangle_idx] < int(m_triangles.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.
|
// Child triangle shares normal with its parent. Select it.
|
||||||
facet_queue.push(child);
|
facet_queue.push(child);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
} else
|
} else
|
||||||
m_triangles[current_facet].select_by_seed_fill();
|
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.
|
// Propagate over the original triangles.
|
||||||
for (int neighbor_idx : m_neighbors[current_facet]) {
|
for (int neighbor_idx : m_neighbors[current_facet]) {
|
||||||
assert(neighbor_idx >= -1);
|
assert(neighbor_idx >= -1);
|
||||||
@ -332,12 +350,101 @@ 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.
|
// 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 &n1 = m_face_normals[m_triangles[neighbor_idx].source_triangle];
|
||||||
const Vec3f &n2 = m_face_normals[m_triangles[current_facet].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<int> &gap_fill_candidate_facets, const float seed_fill_gap_area) {
|
||||||
|
std::vector<bool> 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<int> facet_queue;
|
||||||
|
std::vector<int> 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);
|
facet_queue.push(neighbor_idx);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
visited[current_facet_idx] = true;
|
||||||
}
|
}
|
||||||
visited[current_facet] = true;
|
|
||||||
|
for (int to_seed_idx : gap_facets)
|
||||||
|
m_triangles[to_seed_idx].select_by_seed_fill();
|
||||||
|
|
||||||
|
gap_facets.clear();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -913,7 +1020,7 @@ bool TriangleSelector::select_triangle_recursive(int facet_idx, const Vec3i &nei
|
|||||||
}
|
}
|
||||||
|
|
||||||
void TriangleSelector::set_facet(int facet_idx, TriangleStateType state) {
|
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);
|
undivide_triangle(facet_idx);
|
||||||
assert(! m_triangles[facet_idx].is_split());
|
assert(! m_triangles[facet_idx].is_split());
|
||||||
m_triangles[facet_idx].set_state(state);
|
m_triangles[facet_idx].set_state(state);
|
||||||
@ -1866,6 +1973,13 @@ void TriangleSelector::seed_fill_apply_on_triangles(TriangleStateType new_state)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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_)
|
TriangleSelector::Cursor::Cursor(const Vec3f &source_, float radius_world, const Transform3d &trafo_, const ClippingPlane &clipping_plane_)
|
||||||
: source{source_}, trafo{trafo_.cast<float>()}, clipping_plane{clipping_plane_}
|
: source{source_}, trafo{trafo_.cast<float>()}, clipping_plane{clipping_plane_}
|
||||||
{
|
{
|
||||||
|
@ -320,6 +320,7 @@ public:
|
|||||||
const Transform3d &trafo_no_translate, // matrix to get from mesh to world without translation
|
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
|
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_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.
|
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
|
ForceReselection force_reselection = ForceReselection::NO); // force reselection of the triangle mesh even in cases that mouse is pointing on the selected triangle
|
||||||
|
|
||||||
@ -381,6 +382,9 @@ public:
|
|||||||
// The operation may merge split triangles if they are being assigned the same color.
|
// The operation may merge split triangles if they are being assigned the same color.
|
||||||
void seed_fill_apply_on_triangles(TriangleStateType new_state);
|
void seed_fill_apply_on_triangles(TriangleStateType new_state);
|
||||||
|
|
||||||
|
// Compute total area of the triangle.
|
||||||
|
double get_triangle_area(const Triangle &triangle) const;
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
// Triangle and info about how it's split.
|
// Triangle and info about how it's split.
|
||||||
class Triangle {
|
class Triangle {
|
||||||
@ -498,6 +502,9 @@ private:
|
|||||||
void append_touching_subtriangles(int itriangle, int vertexi, int vertexj, std::vector<int> &touching_subtriangles_out) const;
|
void append_touching_subtriangles(int itriangle, int vertexi, int vertexj, std::vector<int> &touching_subtriangles_out) const;
|
||||||
void append_touching_edges(int itriangle, int vertexi, int vertexj, std::vector<Vec2i> &touching_edges_out) const;
|
void append_touching_edges(int itriangle, int vertexi, int vertexj, std::vector<Vec2i> &touching_edges_out) 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
|
#ifndef NDEBUG
|
||||||
bool verify_triangle_neighbors(const Triangle& tr, const Vec3i& neighbors) const;
|
bool verify_triangle_neighbors(const Triangle& tr, const Vec3i& neighbors) const;
|
||||||
bool verify_triangle_midpoints(const Triangle& tr) const;
|
bool verify_triangle_midpoints(const Triangle& tr) const;
|
||||||
@ -516,6 +523,11 @@ private:
|
|||||||
|
|
||||||
void get_seed_fill_contour_recursive(int facet_idx, const Vec3i &neighbors, const Vec3i &neighbors_propagated, std::vector<Vec2i> &edges_out) const;
|
void get_seed_fill_contour_recursive(int facet_idx, const Vec3i &neighbors, const Vec3i &neighbors_propagated, std::vector<Vec2i> &edges_out) const;
|
||||||
|
|
||||||
|
bool is_any_neighbor_selected_by_seed_fill(const Triangle &triangle);
|
||||||
|
|
||||||
|
void seed_fill_fill_gaps(const std::vector<int> &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.
|
||||||
|
|
||||||
int m_free_triangles_head { -1 };
|
int m_free_triangles_head { -1 };
|
||||||
int m_free_vertices_head { -1 };
|
int m_free_vertices_head { -1 };
|
||||||
};
|
};
|
||||||
|
@ -301,12 +301,13 @@ void GLGizmoFdmSupports::on_render_input_window(float x, float y, float bottom_l
|
|||||||
|
|
||||||
ImGui::SameLine(sliders_left_width);
|
ImGui::SameLine(sliders_left_width);
|
||||||
ImGui::PushItemWidth(window_width - sliders_left_width - slider_icon_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")))
|
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) {
|
for (auto &triangle_selector: m_triangle_selectors) {
|
||||||
triangle_selector->seed_fill_unselect_all_triangles();
|
triangle_selector->seed_fill_unselect_all_triangles();
|
||||||
triangle_selector->request_update_render_data();
|
triangle_selector->request_update_render_data();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
ImGui::Separator();
|
ImGui::Separator();
|
||||||
if (m_c->object_clipper()->get_position() == 0.f) {
|
if (m_c->object_clipper()->get_position() == 0.f) {
|
||||||
|
@ -121,6 +121,7 @@ bool GLGizmoMmuSegmentation::on_init()
|
|||||||
m_desc["tool_bucket_fill"] = _u8L("Bucket fill");
|
m_desc["tool_bucket_fill"] = _u8L("Bucket fill");
|
||||||
|
|
||||||
m_desc["smart_fill_angle"] = _u8L("Smart fill angle");
|
m_desc["smart_fill_angle"] = _u8L("Smart fill angle");
|
||||||
|
m_desc["smart_fill_gap_area"] = _u8L("Smart fill gap");
|
||||||
m_desc["split_triangles"] = _u8L("Split triangles");
|
m_desc["split_triangles"] = _u8L("Split triangles");
|
||||||
|
|
||||||
init_extruders_data();
|
init_extruders_data();
|
||||||
@ -452,15 +453,27 @@ void GLGizmoMmuSegmentation::on_render_input_window(float x, float y, float bott
|
|||||||
} else if(m_tool_type == ToolType::SMART_FILL) {
|
} else if(m_tool_type == ToolType::SMART_FILL) {
|
||||||
ImGui::AlignTextToFramePadding();
|
ImGui::AlignTextToFramePadding();
|
||||||
ImGuiPureWrap::text(m_desc["smart_fill_angle"] + ":");
|
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,"
|
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.");
|
"placed after the number with no whitespace in between.");
|
||||||
ImGui::SameLine(sliders_left_width);
|
ImGui::SameLine(sliders_left_width);
|
||||||
ImGui::PushItemWidth(window_width - sliders_left_width - slider_icon_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")))
|
if (m_imgui->slider_float("##smart_fill_angle", &m_smart_fill_angle, SmartFillAngleMin, SmartFillAngleMax, format_str_angle.data(), 1.0f, true, _L("Alt + Mouse wheel"))) {
|
||||||
for (auto &triangle_selector : m_triangle_selectors) {
|
for (auto &triangle_selector: m_triangle_selectors) {
|
||||||
triangle_selector->seed_fill_unselect_all_triangles();
|
triangle_selector->seed_fill_unselect_all_triangles();
|
||||||
triangle_selector->request_update_render_data();
|
triangle_selector->request_update_render_data();
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui::AlignTextToFramePadding();
|
||||||
|
ImGuiPureWrap::text(m_desc["smart_fill_gap_area"] + ":");
|
||||||
|
ImGui::SameLine(sliders_left_width);
|
||||||
|
ImGui::PushItemWidth(window_width - sliders_left_width - slider_icon_width);
|
||||||
|
if (m_imgui->slider_float("##smart_fill_gap_area", &m_smart_fill_gap_area, SmartFillGapAreaMin, SmartFillGapAreaMax, "%.2f", 1.0f, true)) {
|
||||||
|
for (auto &triangle_selector: m_triangle_selectors) {
|
||||||
|
triangle_selector->seed_fill_unselect_all_triangles();
|
||||||
|
triangle_selector->request_update_render_data();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
ImGui::Separator();
|
ImGui::Separator();
|
||||||
}
|
}
|
||||||
|
@ -480,7 +480,7 @@ bool GLGizmoPainterBase::gizmo_event(SLAGizmoEventType action, const Vec2d& mous
|
|||||||
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_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();
|
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_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, TriangleSelector::ForceReselection::YES);
|
m_smart_fill_gap_area, m_paint_on_overhangs_only ? m_highlight_by_angle_threshold_deg : 0.f, TriangleSelector::ForceReselection::YES);
|
||||||
m_triangle_selectors[m_rr.mesh_id]->request_update_render_data();
|
m_triangle_selectors[m_rr.mesh_id]->request_update_render_data();
|
||||||
m_seed_fill_last_mesh_id = m_rr.mesh_id;
|
m_seed_fill_last_mesh_id = m_rr.mesh_id;
|
||||||
}
|
}
|
||||||
@ -562,7 +562,7 @@ bool GLGizmoPainterBase::gizmo_event(SLAGizmoEventType action, const Vec2d& mous
|
|||||||
const int facet_idx = int(projected_mouse_position.facet_idx);
|
const int facet_idx = int(projected_mouse_position.facet_idx);
|
||||||
m_triangle_selectors[mesh_idx]->seed_fill_apply_on_triangles(new_state);
|
m_triangle_selectors[mesh_idx]->seed_fill_apply_on_triangles(new_state);
|
||||||
if (m_tool_type == ToolType::SMART_FILL) {
|
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_triangle_selectors[mesh_idx]->seed_fill_select_triangles(mesh_hit, facet_idx, trafo_matrix_not_translate, clp, m_smart_fill_angle, m_smart_fill_gap_area,
|
||||||
(m_paint_on_overhangs_only ? m_highlight_by_angle_threshold_deg : 0.f), TriangleSelector::ForceReselection::YES);
|
(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) {
|
} 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, TriangleSelector::BucketFillPropagate::NO, TriangleSelector::ForceReselection::YES);
|
m_triangle_selectors[mesh_idx]->bucket_fill_select_triangles(mesh_hit, facet_idx, clp, TriangleSelector::BucketFillPropagate::NO, TriangleSelector::ForceReselection::YES);
|
||||||
@ -647,7 +647,7 @@ bool GLGizmoPainterBase::gizmo_event(SLAGizmoEventType action, const Vec2d& mous
|
|||||||
assert(m_rr.mesh_id < int(m_triangle_selectors.size()));
|
assert(m_rr.mesh_id < int(m_triangle_selectors.size()));
|
||||||
const TriangleSelector::ClippingPlane &clp = this->get_clipping_plane_in_volume_coordinates(trafo_matrix);
|
const TriangleSelector::ClippingPlane &clp = this->get_clipping_plane_in_volume_coordinates(trafo_matrix);
|
||||||
if (m_tool_type == ToolType::SMART_FILL)
|
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, m_smart_fill_gap_area,
|
||||||
m_paint_on_overhangs_only ? m_highlight_by_angle_threshold_deg : 0.f);
|
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)
|
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, TriangleSelector::BucketFillPropagate::NO);
|
m_triangle_selectors[m_rr.mesh_id]->bucket_fill_select_triangles(m_rr.hit, int(m_rr.facet), clp, TriangleSelector::BucketFillPropagate::NO);
|
||||||
|
@ -150,6 +150,7 @@ protected:
|
|||||||
bool m_triangle_splitting_enabled = true;
|
bool m_triangle_splitting_enabled = true;
|
||||||
ToolType m_tool_type = ToolType::BRUSH;
|
ToolType m_tool_type = ToolType::BRUSH;
|
||||||
float m_smart_fill_angle = 30.f;
|
float m_smart_fill_angle = 30.f;
|
||||||
|
float m_smart_fill_gap_area = 0.02f;
|
||||||
|
|
||||||
bool m_paint_on_overhangs_only = false;
|
bool m_paint_on_overhangs_only = false;
|
||||||
float m_highlight_by_angle_threshold_deg = 0.f;
|
float m_highlight_by_angle_threshold_deg = 0.f;
|
||||||
@ -162,6 +163,9 @@ protected:
|
|||||||
static constexpr float SmartFillAngleMax = 90.f;
|
static constexpr float SmartFillAngleMax = 90.f;
|
||||||
static constexpr float SmartFillAngleStep = 1.f;
|
static constexpr float SmartFillAngleStep = 1.f;
|
||||||
|
|
||||||
|
static constexpr float SmartFillGapAreaMin = 0.0f;
|
||||||
|
static constexpr float SmartFillGapAreaMax = 1.f;
|
||||||
|
|
||||||
// It stores the value of the previous mesh_id to which the seed fill was applied.
|
// 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.
|
// 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;
|
||||||
|
Loading…
x
Reference in New Issue
Block a user