mirror of
https://git.mirrors.martin98.com/https://github.com/prusa3d/PrusaSlicer.git
synced 2025-08-01 04:12:04 +08:00
SPE-2003: Add an option to limit selection by the bucket fill using the angle between triangles.
Also, it adds to the bucket fill the automatic selection of tiny triangles even if they exceed the angle limit.
This commit is contained in:
parent
e672072071
commit
d25ceeeb7a
@ -316,7 +316,7 @@ void TriangleSelector::seed_fill_select_triangles(const Vec3f &hit, int facet_st
|
||||
std::queue<int> 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<float>();
|
||||
|
||||
@ -552,9 +552,29 @@ void TriangleSelector::append_touching_edges(int itriangle, int vertexi, int ver
|
||||
process_subtriangle(touching.second, Partition::Second);
|
||||
}
|
||||
|
||||
// Returns all triangles that are touching the given facet.
|
||||
std::vector<int> 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<int> 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,
|
||||
BucketFillPropagate propagate,
|
||||
ForceReselection force_reselection) {
|
||||
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.
|
||||
@ -570,26 +590,15 @@ void TriangleSelector::bucket_fill_select_triangles(const Vec3f &hit, int facet_
|
||||
return;
|
||||
}
|
||||
|
||||
auto get_all_touching_triangles = [this](int facet_idx, const Vec3i &neighbors, const Vec3i &neighbors_propagated) -> std::vector<int> {
|
||||
assert(facet_idx != -1 && facet_idx < int(m_triangles.size()));
|
||||
assert(this->verify_triangle_neighbors(m_triangles[facet_idx], neighbors));
|
||||
std::vector<int> 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<bool> visited(m_triangles.size(), false);
|
||||
std::queue<int> facet_queue;
|
||||
|
||||
// Facets that need to be checked for gap filling.
|
||||
std::vector<int> gap_fill_candidate_facets;
|
||||
|
||||
facet_queue.push(start_facet_idx);
|
||||
while (!facet_queue.empty()) {
|
||||
int current_facet = facet_queue.front();
|
||||
@ -599,18 +608,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<int> touching_triangles = get_all_touching_triangles(current_facet, neighbors[current_facet], neighbors_propagated[current_facet]);
|
||||
for(const int tr_idx : touching_triangles) {
|
||||
std::vector<int> 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;
|
||||
|
||||
// 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<int> &gap_fill_candidate_facets, const float bucket_fill_gap_area,
|
||||
const TriangleStateType start_facet_state, const std::vector<Vec3i> &neighbors,
|
||||
const std::vector<Vec3i> &neighbors_propagated) {
|
||||
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;
|
||||
}
|
||||
|
||||
// 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<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;
|
||||
|
||||
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<int> 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
|
||||
|
@ -348,6 +348,8 @@ public:
|
||||
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
|
||||
|
||||
@ -523,6 +525,9 @@ private:
|
||||
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;
|
||||
|
||||
// Returns all triangles that are touching the given facet.
|
||||
std::vector<int> 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; }
|
||||
|
||||
@ -549,6 +554,12 @@ private:
|
||||
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.
|
||||
|
||||
void bucket_fill_fill_gaps(const std::vector<int> &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<Vec3i> &neighbors,
|
||||
const std::vector<Vec3i> &neighbors_propagate);
|
||||
|
||||
int m_free_triangles_head { -1 };
|
||||
int m_free_vertices_head { -1 };
|
||||
};
|
||||
|
@ -123,6 +123,10 @@ bool GLGizmoMmuSegmentation::on_init()
|
||||
|
||||
m_desc["smart_fill_angle"] = _u8L("Smart fill angle");
|
||||
m_desc["smart_fill_gap_area"] = _u8L("Smart fill gap");
|
||||
|
||||
m_desc["bucket_fill_angle"] = _u8L("Bucket fill angle");
|
||||
m_desc["bucket_fill_gap_area"] = _u8L("Bucket fill gap");
|
||||
|
||||
m_desc["split_triangles"] = _u8L("Split triangles");
|
||||
|
||||
m_desc["height_range_z_range"] = _u8L("Height range");
|
||||
@ -475,9 +479,9 @@ 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"] + ":");
|
||||
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);
|
||||
@ -490,7 +494,7 @@ void GLGizmoMmuSegmentation::on_render_input_window(float x, float y, float bott
|
||||
}
|
||||
|
||||
ImGui::AlignTextToFramePadding();
|
||||
ImGuiPureWrap::text(m_desc["smart_fill_gap_area"] + ":");
|
||||
ImGuiPureWrap::text((m_tool_type == ToolType::SMART_FILL ? m_desc["smart_fill_gap_area"] : m_desc["bucket_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)) {
|
||||
|
@ -540,7 +540,7 @@ 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) {
|
||||
} else if (m_tool_type == ToolType::SMART_FILL || m_tool_type == ToolType::BUCKET_FILL) {
|
||||
m_smart_fill_angle = action == SLAGizmoEventType::MouseWheelDown ? std::max(m_smart_fill_angle - SmartFillAngleStep, SmartFillAngleMin)
|
||||
: std::min(m_smart_fill_angle + SmartFillAngleStep, SmartFillAngleMax);
|
||||
m_parent.set_as_dirty();
|
||||
@ -548,10 +548,19 @@ bool GLGizmoPainterBase::gizmo_event(SLAGizmoEventType action, const Vec2d& mous
|
||||
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_smart_fill_gap_area, m_paint_on_overhangs_only ? m_highlight_by_angle_threshold_deg : 0.f, TriangleSelector::ForceReselection::YES);
|
||||
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, m_smart_fill_gap_area,
|
||||
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_smart_fill_angle, m_smart_fill_gap_area,
|
||||
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;
|
||||
}
|
||||
@ -641,9 +650,9 @@ bool GLGizmoPainterBase::gizmo_event(SLAGizmoEventType action, const Vec2d& mous
|
||||
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);
|
||||
} 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, m_smart_fill_angle, m_smart_fill_gap_area, 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, TriangleSelector::BucketFillPropagate::YES, TriangleSelector::ForceReselection::YES);
|
||||
m_triangle_selectors[mesh_idx]->bucket_fill_select_triangles(mesh_hit, facet_idx, clp, m_smart_fill_angle, m_smart_fill_gap_area, TriangleSelector::BucketFillPropagate::YES, TriangleSelector::ForceReselection::YES);
|
||||
}
|
||||
|
||||
m_seed_fill_last_mesh_id = -1;
|
||||
@ -736,9 +745,9 @@ bool GLGizmoPainterBase::gizmo_event(SLAGizmoEventType action, const Vec2d& mous
|
||||
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);
|
||||
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, m_smart_fill_angle, m_smart_fill_gap_area, 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, TriangleSelector::BucketFillPropagate::YES);
|
||||
m_triangle_selectors[m_rr.mesh_id]->bucket_fill_select_triangles(m_rr.hit, int(m_rr.facet), clp, m_smart_fill_angle, m_smart_fill_gap_area, 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;
|
||||
|
Loading…
x
Reference in New Issue
Block a user