Added seed fill for MMU segmentation

This commit is contained in:
Lukáš Hejl 2021-04-27 06:48:09 +02:00
parent be1b4ce18c
commit 576c5b78e9
8 changed files with 228 additions and 174 deletions

View file

@ -36,7 +36,7 @@ void TriangleSelector::Triangle::set_division(int sides_to_split, int special_si
void TriangleSelector::select_patch(const Vec3f& hit, int facet_start, void TriangleSelector::select_patch(const Vec3f& hit, int facet_start,
const Vec3f& source, float radius, const Vec3f& source, float radius,
CursorType cursor_type, EnforcerBlockerType new_state, CursorType cursor_type, EnforcerBlockerType new_state,
const Transform3d& trafo) const Transform3d& trafo, bool triangle_splitting)
{ {
assert(facet_start < m_orig_size_indices); assert(facet_start < m_orig_size_indices);
@ -59,7 +59,7 @@ void TriangleSelector::select_patch(const Vec3f& hit, int facet_start,
while (facet_idx < int(facets_to_check.size())) { while (facet_idx < int(facets_to_check.size())) {
int facet = facets_to_check[facet_idx]; int facet = facets_to_check[facet_idx];
if (! visited[facet]) { if (! visited[facet]) {
if (select_triangle(facet, new_state)) { if (select_triangle(facet, new_state, false, triangle_splitting)) {
// add neighboring facets to list to be proccessed later // add neighboring facets to list to be proccessed later
for (int n=0; n<3; ++n) { for (int n=0; n<3; ++n) {
int neighbor_idx = m_mesh->stl.neighbors_start[facet].neighbor[n]; int neighbor_idx = m_mesh->stl.neighbors_start[facet].neighbor[n];
@ -73,13 +73,56 @@ void TriangleSelector::select_patch(const Vec3f& hit, int facet_start,
} }
} }
void TriangleSelector::seed_fill_select_triangles(const Vec3f& hit, int facet_start, float seed_fill_angle)
{
this->seed_fill_unselect_all_triangles();
std::vector<bool> visited(m_triangles.size(), false);
std::queue<size_t> facet_queue;
facet_queue.push(facet_start);
// Check if neighbour_facet_idx is satisfies angle in seed_fill_angle and append it to facet_queue if it do.
auto check_angle_and_append = [this, &facet_queue](const size_t facet_idx, const size_t neighbour_facet_idx, const float seed_fill_angle) -> void {
double dot_product = m_triangles[neighbour_facet_idx].normal.dot(m_triangles[facet_idx].normal);
dot_product = std::clamp(dot_product, 0., 1.);
double facet_angle_limit = cos(Geometry::deg2rad(seed_fill_angle));
if ((dot_product + EPSILON) >= facet_angle_limit)
facet_queue.push(neighbour_facet_idx);
};
while(!facet_queue.empty()) {
size_t current_facet = facet_queue.front();
facet_queue.pop();
if (!visited[current_facet]) {
if (!m_triangles[current_facet].is_split())
m_triangles[current_facet].select_by_seed_fill();
if (m_triangles[current_facet].is_split())
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 (!visited[m_triangles[current_facet].children[split_triangle_idx]])
check_angle_and_append(current_facet, m_triangles[current_facet].children[split_triangle_idx], seed_fill_angle);
}
if (int(current_facet) < m_orig_size_indices)
for (int neighbor_idx : m_mesh->stl.neighbors_start[current_facet].neighbor) {
assert(neighbor_idx >= 0);
if (neighbor_idx >= 0 && !visited[neighbor_idx])
check_angle_and_append(current_facet, neighbor_idx, seed_fill_angle);
}
}
visited[current_facet] = true;
}
}
// Selects either the whole triangle (discarding any children it had), or divides // Selects either the whole triangle (discarding any children it had), or divides
// the triangle recursively, selecting just subtriangles truly inside the circle. // the triangle recursively, selecting just subtriangles truly inside the circle.
// This is done by an actual recursive call. Returns false if the triangle is // This is done by an actual recursive call. Returns false if the triangle is
// outside the cursor. // outside the cursor.
bool TriangleSelector::select_triangle(int facet_idx, EnforcerBlockerType type, bool recursive_call) bool TriangleSelector::select_triangle(int facet_idx, EnforcerBlockerType type, bool recursive_call, bool triangle_splitting)
{ {
assert(facet_idx < int(m_triangles.size())); assert(facet_idx < int(m_triangles.size()));
@ -108,7 +151,10 @@ bool TriangleSelector::select_triangle(int facet_idx, EnforcerBlockerType type,
return true; return true;
} }
if(triangle_splitting)
split_triangle(facet_idx); split_triangle(facet_idx);
else if(!m_triangles[facet_idx].is_split())
m_triangles[facet_idx].set_state(type);
tr = &m_triangles[facet_idx]; // might have been invalidated tr = &m_triangles[facet_idx]; // might have been invalidated
@ -118,7 +164,7 @@ bool TriangleSelector::select_triangle(int facet_idx, EnforcerBlockerType type,
assert(i < int(tr->children.size())); assert(i < int(tr->children.size()));
assert(tr->children[i] < int(m_triangles.size())); assert(tr->children[i] < int(m_triangles.size()));
select_triangle(tr->children[i], type, true); select_triangle(tr->children[i], type, true, triangle_splitting);
tr = &m_triangles[facet_idx]; // might have been invalidated tr = &m_triangles[facet_idx]; // might have been invalidated
} }
} }
@ -710,6 +756,18 @@ void TriangleSelector::deserialize(const std::map<int, std::vector<bool>> data)
} }
} }
void TriangleSelector::seed_fill_unselect_all_triangles() {
for (Triangle &triangle : m_triangles)
if (!triangle.is_split())
triangle.unselect_by_seed_fill();
}
void TriangleSelector::seed_fill_apply_on_triangles(EnforcerBlockerType new_state)
{
for (Triangle &triangle : m_triangles)
if (!triangle.is_split() && triangle.is_selected_by_seed_fill())
triangle.set_state(new_state);
}
TriangleSelector::Cursor::Cursor( TriangleSelector::Cursor::Cursor(
const Vec3f& center_, const Vec3f& source_, float radius_world, const Vec3f& center_, const Vec3f& source_, float radius_world,

View file

@ -35,7 +35,12 @@ public:
float radius, // radius of the cursor float radius, // radius of the cursor
CursorType type, // current type of cursor CursorType type, // current type of cursor
EnforcerBlockerType new_state, // enforcer or blocker? EnforcerBlockerType new_state, // enforcer or blocker?
const Transform3d& trafo); // matrix to get from mesh to world const Transform3d &trafo, // matrix to get from mesh to world
bool triangle_splitting); // If triangles will be split base on the cursor or not
void seed_fill_select_triangles(const Vec3f &hit, // point where to start
int facet_start, // facet that point belongs to
float seed_fill_angle); // the maximal angle between two facets to be painted by the same color
// Get facets currently in the given state. // Get facets currently in the given state.
indexed_triangle_set get_facets(EnforcerBlockerType state) const; indexed_triangle_set get_facets(EnforcerBlockerType state) const;
@ -56,6 +61,11 @@ public:
// Load serialized data. Assumes that correct mesh is loaded. // Load serialized data. Assumes that correct mesh is loaded.
void deserialize(const std::map<int, std::vector<bool>> data); void deserialize(const std::map<int, std::vector<bool>> data);
// For all triangles, remove the flag indicating that the triangle was selected by seed fill.
void seed_fill_unselect_all_triangles();
// For all triangles selected by seed fill, set new EnforcerBlockerType and remove flag indicating that triangle was selected by seed fill.
void seed_fill_apply_on_triangles(EnforcerBlockerType new_state);
protected: protected:
// Triangle and info about how it's split. // Triangle and info about how it's split.
@ -90,6 +100,12 @@ protected:
void set_state(EnforcerBlockerType type) { assert(! is_split()); state = type; } void set_state(EnforcerBlockerType type) { assert(! is_split()); state = type; }
EnforcerBlockerType get_state() const { assert(! is_split()); return state; } EnforcerBlockerType get_state() const { assert(! is_split()); return state; }
// Set if the triangle has been selected or unselected by seed fill.
void select_by_seed_fill() { assert(! is_split()); m_selected_by_seed_fill = true; }
void unselect_by_seed_fill() { assert(! is_split()); m_selected_by_seed_fill = false; }
// Get if the triangle has been selected or not by seed fill.
bool is_selected_by_seed_fill() const { assert(! is_split()); return m_selected_by_seed_fill; }
// Get info on how it's split. // Get info on how it's split.
bool is_split() const { return number_of_split_sides() != 0; } bool is_split() const { return number_of_split_sides() != 0; }
int number_of_split_sides() const { return number_of_splits; } int number_of_split_sides() const { return number_of_splits; }
@ -101,6 +117,7 @@ protected:
int number_of_splits; int number_of_splits;
int special_side_idx; int special_side_idx;
EnforcerBlockerType state; EnforcerBlockerType state;
bool m_selected_by_seed_fill = false;
// How many children were spawned during last split? // How many children were spawned during last split?
// Is not reset on remerging the triangle. // Is not reset on remerging the triangle.
@ -153,8 +170,7 @@ protected:
float m_old_cursor_radius_sqr; float m_old_cursor_radius_sqr;
// Private functions: // Private functions:
bool select_triangle(int facet_idx, EnforcerBlockerType type, bool select_triangle(int facet_idx, EnforcerBlockerType type, bool recursive_call = false, bool triangle_splitting = true);
bool recursive_call = false);
int vertices_inside(int facet_idx) const; int vertices_inside(int facet_idx) const;
bool faces_camera(int facet) const; bool faces_camera(int facet) const;
void undivide_triangle(int facet_idx); void undivide_triangle(int facet_idx);

View file

@ -13,20 +13,15 @@
#include <GL/glew.h> #include <GL/glew.h>
namespace Slic3r { namespace Slic3r::GUI {
namespace GUI {
void GLGizmoMmuSegmentation::on_shutdown() void GLGizmoMmuSegmentation::on_shutdown()
{ {
m_angle_threshold_deg = 0.f; // m_seed_fill_angle = 0.f;
// m_seed_fill_enabled = false;
m_parent.use_slope(false); m_parent.use_slope(false);
} }
std::string GLGizmoMmuSegmentation::on_get_name() const std::string GLGizmoMmuSegmentation::on_get_name() const
{ {
// FIXME Lukas H.: Discuss and change shortcut // FIXME Lukas H.: Discuss and change shortcut
@ -44,28 +39,24 @@ bool GLGizmoMmuSegmentation::on_init()
// FIXME Lukas H.: Discuss and change shortcut // FIXME Lukas H.: Discuss and change shortcut
m_shortcut_key = WXK_CONTROL_N; m_shortcut_key = WXK_CONTROL_N;
m_desc["clipping_of_view"] = _L("Clipping of view") + ": ";
m_desc["reset_direction"] = _L("Reset direction"); m_desc["reset_direction"] = _L("Reset direction");
m_desc["clipping_of_view"] = _L("Clipping of view") + ": ";
m_desc["cursor_size"] = _L("Brush size") + ": "; m_desc["cursor_size"] = _L("Brush size") + ": ";
m_desc["cursor_type"] = _L("Brush shape") + ": "; m_desc["cursor_type"] = _L("Brush shape") + ": ";
m_desc["enforce_caption"] = _L("Left mouse button") + ": "; m_desc["first_color_caption"] = _L("Left mouse button") + ": ";
m_desc["enforce"] = _L("Enforce supports"); m_desc["first_color"] = _L("First color");
m_desc["block_caption"] = _L("Right mouse button") + ": "; m_desc["second_color_caption"] = _L("Right mouse button") + ": ";
m_desc["block"] = _L("Block supports"); m_desc["second_color"] = _L("Second color");
m_desc["remove_caption"] = _L("Shift + Left mouse button") + ": "; m_desc["remove_caption"] = _L("Shift + Left mouse button") + ": ";
m_desc["remove"] = _L("Remove selection"); m_desc["remove"] = _L("Remove painted color");
m_desc["remove_all"] = _L("Remove all selection"); m_desc["remove_all"] = _L("Remove all painted colors");
m_desc["circle"] = _L("Circle"); m_desc["circle"] = _L("Circle");
m_desc["sphere"] = _L("Sphere"); m_desc["sphere"] = _L("Sphere");
m_desc["highlight_by_angle"] = _L("Highlight by angle"); m_desc["seed_fill_angle"] = _L("Seed fill angle");
m_desc["enforce_button"] = _L("Enforce");
m_desc["cancel"] = _L("Cancel");
return true; return true;
} }
void GLGizmoMmuSegmentation::render_painter_gizmo() const void GLGizmoMmuSegmentation::render_painter_gizmo() const
{ {
const Selection& selection = m_parent.get_selection(); const Selection& selection = m_parent.get_selection();
@ -81,14 +72,12 @@ void GLGizmoMmuSegmentation::render_painter_gizmo() const
glsafe(::glDisable(GL_BLEND)); glsafe(::glDisable(GL_BLEND));
} }
void GLGizmoMmuSegmentation::on_render_input_window(float x, float y, float bottom_limit) void GLGizmoMmuSegmentation::on_render_input_window(float x, float y, float bottom_limit)
{ {
if (!m_c->selection_info()->model_object()) if (!m_c->selection_info()->model_object())
return; return;
const float approx_height = m_imgui->scaled(17.0f); const float approx_height = m_imgui->scaled(23.0f);
y = std::min(y, bottom_limit - approx_height); y = std::min(y, bottom_limit - approx_height);
m_imgui->set_next_window_pos(x, y, ImGuiCond_Always); m_imgui->set_next_window_pos(x, y, ImGuiCond_Always);
@ -96,24 +85,19 @@ void GLGizmoMmuSegmentation::on_render_input_window(float x, float y, float bott
// First calculate width of all the texts that are could possibly be shown. We will decide set the dialog width based on that: // 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(m_imgui->calc_text_size(m_desc.at("clipping_of_view")).x, const float clipping_slider_left = std::max(m_imgui->calc_text_size(m_desc.at("clipping_of_view")).x,
m_imgui->calc_text_size(m_desc.at("reset_direction")).x) m_imgui->calc_text_size(m_desc.at("reset_direction")).x) + m_imgui->scaled(1.5f);
+ m_imgui->scaled(1.5f);
const float cursor_slider_left = m_imgui->calc_text_size(m_desc.at("cursor_size")).x + m_imgui->scaled(1.f); const float cursor_slider_left = m_imgui->calc_text_size(m_desc.at("cursor_size")).x + m_imgui->scaled(1.f);
const float autoset_slider_left = m_imgui->calc_text_size(m_desc.at("highlight_by_angle")).x + m_imgui->scaled(1.f); const float autoset_slider_left = m_imgui->calc_text_size(m_desc.at("seed_fill_angle")).x + m_imgui->scaled(1.f);
const float cursor_type_radio_left = m_imgui->calc_text_size(m_desc.at("cursor_type")).x + m_imgui->scaled(1.f); const float cursor_type_radio_left = m_imgui->calc_text_size(m_desc.at("cursor_type")).x + m_imgui->scaled(1.f);
const float cursor_type_radio_width1 = m_imgui->calc_text_size(m_desc["circle"]).x const float cursor_type_radio_width1 = m_imgui->calc_text_size(m_desc["circle"]).x + m_imgui->scaled(2.5f);
+ m_imgui->scaled(2.5f); const float cursor_type_radio_width2 = m_imgui->calc_text_size(m_desc["sphere"]).x + m_imgui->scaled(2.5f);
const float cursor_type_radio_width2 = m_imgui->calc_text_size(m_desc["sphere"]).x
+ m_imgui->scaled(2.5f);
const float button_width = m_imgui->calc_text_size(m_desc.at("remove_all")).x + m_imgui->scaled(1.f); const float button_width = m_imgui->calc_text_size(m_desc.at("remove_all")).x + m_imgui->scaled(1.f);
const float button_enforce_width = m_imgui->calc_text_size(m_desc.at("enforce_button")).x; const float buttons_width = m_imgui->scaled(0.5f);
const float button_cancel_width = m_imgui->calc_text_size(m_desc.at("cancel")).x;
const float buttons_width = std::max(button_enforce_width, button_cancel_width) + m_imgui->scaled(0.5f);
const float minimal_slider_width = m_imgui->scaled(4.f); const float minimal_slider_width = m_imgui->scaled(4.f);
float caption_max = 0.f; float caption_max = 0.f;
float total_text_max = 0.; float total_text_max = 0.;
for (const std::string& t : {"enforce", "block", "remove"}) { for (const std::string &t : {"first_color", "second_color", "remove"}) {
caption_max = std::max(caption_max, m_imgui->calc_text_size(m_desc.at(t + "_caption")).x); caption_max = std::max(caption_max, m_imgui->calc_text_size(m_desc.at(t + "_caption")).x);
total_text_max = std::max(total_text_max, caption_max + m_imgui->calc_text_size(m_desc.at(t)).x); total_text_max = std::max(total_text_max, caption_max + m_imgui->calc_text_size(m_desc.at(t)).x);
} }
@ -132,40 +116,29 @@ void GLGizmoMmuSegmentation::on_render_input_window(float x, float y, float bott
m_imgui->text(text); m_imgui->text(text);
}; };
for (const std::string& t : {"enforce", "block", "remove"}) for (const std::string &t : {"first_color", "second_color", "remove"})
draw_text_with_caption(m_desc.at(t + "_caption"), m_desc.at(t)); draw_text_with_caption(m_desc.at(t + "_caption"), m_desc.at(t));
m_imgui->text(""); m_imgui->text("");
ImGui::Separator(); ImGui::Separator();
m_imgui->text(m_desc["highlight_by_angle"] + ":"); if (m_imgui->checkbox(_L("Seed fill"), m_seed_fill_enabled))
if (!m_seed_fill_enabled)
for (auto &triangle_selector : m_triangle_selectors)
triangle_selector->seed_fill_unselect_all_triangles();
m_imgui->text(m_desc["seed_fill_angle"] + ":");
ImGui::AlignTextToFramePadding(); ImGui::AlignTextToFramePadding();
std::string format_str = std::string("%.f") + I18N::translate_utf8("°", std::string format_str = std::string("%.f") + I18N::translate_utf8("°", "Degree sign to use in the respective slider in FDM supports gizmo,"
"Degree sign to use in the respective slider in FDM supports gizmo,"
"placed after the number with no whitespace in between."); "placed after the number with no whitespace in between.");
ImGui::SameLine(autoset_slider_left); ImGui::SameLine(autoset_slider_left);
ImGui::PushItemWidth(window_width - autoset_slider_left); ImGui::PushItemWidth(window_width - autoset_slider_left);
if (m_imgui->slider_float("", &m_angle_threshold_deg, 0.f, 90.f, format_str.data())) { m_imgui->disabled_begin(!m_seed_fill_enabled);
m_parent.set_slope_normal_angle(90.f - m_angle_threshold_deg); m_imgui->slider_float("", &m_seed_fill_angle, 0.f, 90.f, format_str.data());
if (! m_parent.is_using_slope()) { m_imgui->disabled_end();
m_parent.use_slope(true);
m_parent.set_as_dirty();
}
}
m_imgui->disabled_begin(m_angle_threshold_deg == 0.f);
ImGui::NewLine(); ImGui::NewLine();
ImGui::SameLine(window_width - 2.f * buttons_width - m_imgui->scaled(0.5f)); ImGui::SameLine(window_width - 2.f * buttons_width - m_imgui->scaled(0.5f));
if (m_imgui->button(m_desc["enforce_button"], buttons_width, 0.f)) {
select_facets_by_angle(m_angle_threshold_deg, false);
m_angle_threshold_deg = 0.f;
}
ImGui::SameLine(window_width - buttons_width);
if (m_imgui->button(m_desc["cancel"], buttons_width, 0.f)) {
m_angle_threshold_deg = 0.f;
m_parent.use_slope(false);
}
m_imgui->disabled_end();
ImGui::Separator(); ImGui::Separator();
@ -184,7 +157,6 @@ void GLGizmoMmuSegmentation::on_render_input_window(float x, float y, float bott
m_parent.set_as_dirty(); m_parent.set_as_dirty();
} }
const float max_tooltip_width = ImGui::GetFontSize() * 20.0f; const float max_tooltip_width = ImGui::GetFontSize() * 20.0f;
ImGui::AlignTextToFramePadding(); ImGui::AlignTextToFramePadding();
@ -200,7 +172,6 @@ void GLGizmoMmuSegmentation::on_render_input_window(float x, float y, float bott
ImGui::EndTooltip(); ImGui::EndTooltip();
} }
ImGui::AlignTextToFramePadding(); ImGui::AlignTextToFramePadding();
m_imgui->text(m_desc.at("cursor_type")); m_imgui->text(m_desc.at("cursor_type"));
ImGui::SameLine(cursor_type_radio_left + m_imgui->scaled(0.f)); ImGui::SameLine(cursor_type_radio_left + m_imgui->scaled(0.f));
@ -232,23 +203,17 @@ void GLGizmoMmuSegmentation::on_render_input_window(float x, float y, float bott
ImGui::EndTooltip(); ImGui::EndTooltip();
} }
m_cursor_type = sphere_sel m_cursor_type = sphere_sel ? TriangleSelector::CursorType::SPHERE : TriangleSelector::CursorType::CIRCLE;
? TriangleSelector::CursorType::SPHERE
: TriangleSelector::CursorType::CIRCLE;
m_imgui->checkbox(_L("Split triangles"), m_triangle_splitting_enabled);
ImGui::Separator(); ImGui::Separator();
if (m_c->object_clipper()->get_position() == 0.f) { if (m_c->object_clipper()->get_position() == 0.f) {
ImGui::AlignTextToFramePadding(); ImGui::AlignTextToFramePadding();
m_imgui->text(m_desc.at("clipping_of_view")); m_imgui->text(m_desc.at("clipping_of_view"));
} } else {
else {
if (m_imgui->button(m_desc.at("reset_direction"))) { if (m_imgui->button(m_desc.at("reset_direction"))) {
wxGetApp().CallAfter([this](){ wxGetApp().CallAfter([this]() { m_c->object_clipper()->set_position(-1., false); });
m_c->object_clipper()->set_position(-1., false);
});
} }
} }
@ -267,50 +232,6 @@ void GLGizmoMmuSegmentation::on_render_input_window(float x, float y, float bott
m_imgui->end(); m_imgui->end();
} }
void GLGizmoMmuSegmentation::select_facets_by_angle(float threshold_deg, bool block)
{
float threshold = (M_PI/180.)*threshold_deg;
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()];
int mesh_id = -1;
for (const ModelVolume* mv : mo->volumes) {
if (! mv->is_model_part())
continue;
++mesh_id;
const Transform3d trafo_matrix = mi->get_matrix(true) * mv->get_matrix(true);
Vec3f down = (trafo_matrix.inverse() * (-Vec3d::UnitZ())).cast<float>().normalized();
Vec3f limit = (trafo_matrix.inverse() * Vec3d(std::sin(threshold), 0, -std::cos(threshold))).cast<float>().normalized();
float dot_limit = limit.dot(down);
// Now calculate dot product of vert_direction and facets' normals.
int idx = -1;
for (const stl_facet& facet : mv->mesh().stl.facet_start) {
++idx;
if (facet.normal.dot(down) > dot_limit)
m_triangle_selectors[mesh_id]->set_facet(idx,
block
? EnforcerBlockerType::BLOCKER
: EnforcerBlockerType::ENFORCER);
}
}
activate_internal_undo_redo_stack(true);
Plater::TakeSnapshot(wxGetApp().plater(), block ? _L("Block supports by angle")
: _L("Add supports by angle"));
update_model_object();
m_parent.set_as_dirty();
}
void GLGizmoMmuSegmentation::update_model_object() const void GLGizmoMmuSegmentation::update_model_object() const
{ {
bool updated = false; bool updated = false;
@ -327,8 +248,6 @@ void GLGizmoMmuSegmentation::update_model_object() const
m_parent.post_event(SimpleEvent(EVT_GLCANVAS_SCHEDULE_BACKGROUND_PROCESS)); m_parent.post_event(SimpleEvent(EVT_GLCANVAS_SCHEDULE_BACKGROUND_PROCESS));
} }
void GLGizmoMmuSegmentation::update_from_model_object() void GLGizmoMmuSegmentation::update_from_model_object()
{ {
wxBusyCursor wait; wxBusyCursor wait;
@ -351,13 +270,10 @@ void GLGizmoMmuSegmentation::update_from_model_object()
} }
} }
PainterGizmoType GLGizmoMmuSegmentation::get_painter_type() const PainterGizmoType GLGizmoMmuSegmentation::get_painter_type() const
{ {
return PainterGizmoType::MMU_SEGMENTATION; return PainterGizmoType::MMU_SEGMENTATION;
} }
} // namespace GUI
} // namespace Slic3r } // namespace Slic3r

View file

@ -31,9 +31,6 @@ private:
void on_shutdown() override; void on_shutdown() override;
PainterGizmoType get_painter_type() const override; PainterGizmoType get_painter_type() const override;
void select_facets_by_angle(float threshold, bool block);
float m_angle_threshold_deg = 0.f;
// This map holds all translated description texts, so they can be easily referenced during layout calculations // This map holds all translated description texts, so they can be easily referenced during layout calculations
// etc. When language changes, GUI is recreated and this class constructed again, so the change takes effect. // etc. When language changes, GUI is recreated and this class constructed again, so the change takes effect.
std::map<std::string, wxString> m_desc; std::map<std::string, wxString> m_desc;

View file

@ -143,12 +143,13 @@ void GLGizmoPainterBase::render_cursor() const
if (m_rr.mesh_id == -1) if (m_rr.mesh_id == -1)
return; return;
if (!m_seed_fill_enabled) {
if (m_cursor_type == TriangleSelector::SPHERE) if (m_cursor_type == TriangleSelector::SPHERE)
render_cursor_sphere(trafo_matrices[m_rr.mesh_id]); render_cursor_sphere(trafo_matrices[m_rr.mesh_id]);
else else
render_cursor_circle(); render_cursor_circle();
} }
}
@ -351,14 +352,50 @@ bool GLGizmoPainterBase::gizmo_event(SLAGizmoEventType action, const Vec2d& mous
Vec3f camera_pos = (trafo_matrix.inverse() * camera.get_position()).cast<float>(); Vec3f camera_pos = (trafo_matrix.inverse() * camera.get_position()).cast<float>();
assert(m_rr.mesh_id < int(m_triangle_selectors.size())); assert(m_rr.mesh_id < int(m_triangle_selectors.size()));
m_triangle_selectors[m_rr.mesh_id]->select_patch(m_rr.hit, m_rr.facet, camera_pos, if (m_seed_fill_enabled)
m_cursor_radius, m_cursor_type, new_state, trafo_matrix); m_triangle_selectors[m_rr.mesh_id]->seed_fill_apply_on_triangles(new_state);
else
m_triangle_selectors[m_rr.mesh_id]->select_patch(m_rr.hit, m_rr.facet, camera_pos, m_cursor_radius, m_cursor_type,
new_state, trafo_matrix, m_triangle_splitting_enabled);
m_last_mouse_click = mouse_position; m_last_mouse_click = mouse_position;
} }
return true; return true;
} }
if (action == SLAGizmoEventType::Moving && m_seed_fill_enabled) {
if (m_triangle_selectors.empty())
return false;
const Camera & camera = wxGetApp().plater()->get_camera();
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 & instance_trafo = mi->get_transformation().get_matrix();
// Precalculate transformations of individual meshes.
std::vector<Transform3d> trafo_matrices;
for (const ModelVolume *mv : mo->volumes)
if (mv->is_model_part())
trafo_matrices.emplace_back(instance_trafo * mv->get_matrix());
// Now "click" into all the prepared points and spill paint around them.
update_raycast_cache(mouse_position, camera, trafo_matrices);
if (m_rr.mesh_id == -1) {
// Clean selected by seed fill for all triangles
for (auto &triangle_selector : m_triangle_selectors)
triangle_selector->seed_fill_unselect_all_triangles();
// In case we have no valid hit, we can return.
return false;
}
assert(m_rr.mesh_id < int(m_triangle_selectors.size()));
m_triangle_selectors[m_rr.mesh_id]->seed_fill_select_triangles(m_rr.hit, m_rr.facet, m_seed_fill_angle);
return true;
}
if ((action == SLAGizmoEventType::LeftUp || action == SLAGizmoEventType::RightUp) if ((action == SLAGizmoEventType::LeftUp || action == SLAGizmoEventType::RightUp)
&& m_button_down != Button::None) { && m_button_down != Button::None) {
// Take snapshot and update ModelVolume data. // Take snapshot and update ModelVolume data.
@ -521,12 +558,14 @@ void TriangleSelectorGUI::render(ImGuiWrapper* imgui)
{ {
int enf_cnt = 0; int enf_cnt = 0;
int blc_cnt = 0; int blc_cnt = 0;
int seed_fill_cnt = 0;
m_iva_enforcers.release_geometry(); m_iva_enforcers.release_geometry();
m_iva_blockers.release_geometry(); m_iva_blockers.release_geometry();
m_iva_seed_fill.release_geometry();
for (const Triangle& tr : m_triangles) { for (const Triangle& tr : m_triangles) {
if (! tr.valid || tr.is_split() || tr.get_state() == EnforcerBlockerType::NONE) if (!tr.valid || tr.is_split() || tr.get_state() == EnforcerBlockerType::NONE || tr.is_selected_by_seed_fill())
continue; continue;
GLIndexedVertexArray& va = tr.get_state() == EnforcerBlockerType::ENFORCER GLIndexedVertexArray& va = tr.get_state() == EnforcerBlockerType::ENFORCER
@ -543,17 +582,32 @@ void TriangleSelectorGUI::render(ImGuiWrapper* imgui)
double(tr.normal[0]), double(tr.normal[0]),
double(tr.normal[1]), double(tr.normal[1]),
double(tr.normal[2])); double(tr.normal[2]));
va.push_triangle(cnt, va.push_triangle(cnt, cnt + 1, cnt + 2);
cnt+1,
cnt+2);
cnt += 3; cnt += 3;
} }
for (const Triangle &tr : m_triangles) {
if (!tr.valid || tr.is_split() || !tr.is_selected_by_seed_fill())
continue;
for (int i = 0; i < 3; ++i)
m_iva_seed_fill.push_geometry(double(m_vertices[tr.verts_idxs[i]].v[0]),
double(m_vertices[tr.verts_idxs[i]].v[1]),
double(m_vertices[tr.verts_idxs[i]].v[2]),
double(tr.normal[0]),
double(tr.normal[1]),
double(tr.normal[2]));
m_iva_seed_fill.push_triangle(seed_fill_cnt, seed_fill_cnt + 1, seed_fill_cnt + 2);
seed_fill_cnt += 3;
}
m_iva_enforcers.finalize_geometry(true); m_iva_enforcers.finalize_geometry(true);
m_iva_blockers.finalize_geometry(true); m_iva_blockers.finalize_geometry(true);
m_iva_seed_fill.finalize_geometry(true);
bool render_enf = m_iva_enforcers.has_VBOs(); bool render_enf = m_iva_enforcers.has_VBOs();
bool render_blc = m_iva_blockers.has_VBOs(); bool render_blc = m_iva_blockers.has_VBOs();
bool render_seed_fill = m_iva_seed_fill.has_VBOs();
auto* shader = wxGetApp().get_shader("gouraud"); auto* shader = wxGetApp().get_shader("gouraud");
if (! shader) if (! shader)
@ -575,6 +629,12 @@ void TriangleSelectorGUI::render(ImGuiWrapper* imgui)
m_iva_blockers.render(); m_iva_blockers.render();
} }
if (render_seed_fill) {
std::array<float, 4> color = { 0.f, 1.00f, 0.44f, 1.f };
shader->set_uniform("uniform_color", color);
m_iva_seed_fill.render();
}
#ifdef PRUSASLICER_TRIANGLE_SELECTOR_DEBUG #ifdef PRUSASLICER_TRIANGLE_SELECTOR_DEBUG
if (imgui) if (imgui)

View file

@ -48,6 +48,7 @@ public:
private: private:
GLIndexedVertexArray m_iva_enforcers; GLIndexedVertexArray m_iva_enforcers;
GLIndexedVertexArray m_iva_blockers; GLIndexedVertexArray m_iva_blockers;
GLIndexedVertexArray m_iva_seed_fill;
std::array<GLIndexedVertexArray, 3> m_varrays; std::array<GLIndexedVertexArray, 3> m_varrays;
}; };
@ -95,6 +96,9 @@ protected:
TriangleSelector::CursorType m_cursor_type = TriangleSelector::SPHERE; TriangleSelector::CursorType m_cursor_type = TriangleSelector::SPHERE;
bool m_triangle_splitting_enabled = true;
bool m_seed_fill_enabled = false;
float m_seed_fill_angle = 0.f;
private: private:
bool is_mesh_point_clipped(const Vec3d& point, const Transform3d& trafo) const; bool is_mesh_point_clipped(const Vec3d& point, const Transform3d& trafo) const;

View file

@ -32,7 +32,8 @@ enum class SLAGizmoEventType : unsigned char {
ManualEditing, ManualEditing,
MouseWheelUp, MouseWheelUp,
MouseWheelDown, MouseWheelDown,
ResetClippingPlane ResetClippingPlane,
Moving
}; };

View file

@ -522,9 +522,11 @@ bool GLGizmosManager::on_mouse(wxMouseEvent& evt)
bool control_down = evt.CmdDown(); bool control_down = evt.CmdDown();
// mouse anywhere // mouse anywhere
if (evt.Moving()) if (evt.Moving()) {
m_tooltip = update_hover_state(mouse_pos); m_tooltip = update_hover_state(mouse_pos);
else if (evt.LeftUp()) { if (m_current == MmuSegmentation)
gizmo_event(SLAGizmoEventType::Moving, mouse_pos, evt.ShiftDown(), evt.AltDown());
} else if (evt.LeftUp()) {
if (m_mouse_capture.left) { if (m_mouse_capture.left) {
processed = true; processed = true;
m_mouse_capture.left = false; m_mouse_capture.left = false;