Merge branch 'lh_mm_gizmo_bucket_fill'

This commit is contained in:
Lukáš Hejl 2021-07-16 09:27:18 +02:00
commit ab7c758881
5 changed files with 519 additions and 107 deletions

View file

@ -69,7 +69,70 @@ void TriangleSelector::Triangle::set_division(int sides_to_split, int special_si
this->special_side_idx = special_side_idx;
}
inline bool is_point_inside_triangle(const Vec3f &pt, const Vec3f &p1, const Vec3f &p2, const Vec3f &p3)
{
// Real-time collision detection, Ericson, Chapter 3.4
auto barycentric = [&pt, &p1, &p2, &p3]() -> Vec3f {
std::array<Vec3f, 3> v = {p2 - p1, p3 - p1, pt - p1};
float d00 = v[0].dot(v[0]);
float d01 = v[0].dot(v[1]);
float d11 = v[1].dot(v[1]);
float d20 = v[2].dot(v[0]);
float d21 = v[2].dot(v[1]);
float denom = d00 * d11 - d01 * d01;
Vec3f barycentric_cords(1.f, (d11 * d20 - d01 * d21) / denom, (d00 * d21 - d01 * d20) / denom);
barycentric_cords.x() = barycentric_cords.x() - barycentric_cords.y() - barycentric_cords.z();
return barycentric_cords;
};
Vec3f barycentric_cords = barycentric();
return std::all_of(begin(barycentric_cords), end(barycentric_cords), [](float cord) { return 0.f <= cord && cord <= 1.0; });
}
int TriangleSelector::select_unsplit_triangle(const Vec3f &hit, int facet_idx, const Vec3i &neighbors) const
{
assert(facet_idx < int(m_triangles.size()));
const Triangle *tr = &m_triangles[facet_idx];
if (!tr->valid())
return -1;
if (!tr->is_split()) {
if (const std::array<int, 3> &t_vert = m_triangles[facet_idx].verts_idxs; is_point_inside_triangle(hit, m_vertices[t_vert[0]].v, m_vertices[t_vert[1]].v, m_vertices[t_vert[2]].v))
return facet_idx;
return -1;
}
assert(this->verify_triangle_neighbors(*tr, neighbors));
int num_of_children = tr->number_of_split_sides() + 1;
if (num_of_children != 1) {
for (int i = 0; i < num_of_children; ++i) {
assert(i < int(tr->children.size()));
assert(tr->children[i] < int(m_triangles.size()));
// Recursion, deep first search over the children of this triangle.
// All children of this triangle were created by splitting a single source triangle of the original mesh.
const std::array<int, 3> &t_vert = m_triangles[tr->children[i]].verts_idxs;
if (is_point_inside_triangle(hit, m_vertices[t_vert[0]].v, m_vertices[t_vert[1]].v, m_vertices[t_vert[2]].v))
return this->select_unsplit_triangle(hit, tr->children[i], this->child_neighbors(*tr, neighbors, i));
}
}
return -1;
}
int TriangleSelector::select_unsplit_triangle(const Vec3f &hit, int facet_idx) const
{
assert(facet_idx < int(m_triangles.size()));
if (!m_triangles[facet_idx].valid())
return -1;
Vec3i neighbors = root_neighbors(*m_mesh, facet_idx);
assert(this->verify_triangle_neighbors(m_triangles[facet_idx], neighbors));
return this->select_unsplit_triangle(hit, facet_idx, neighbors);
}
void TriangleSelector::select_patch(const Vec3f& hit, int facet_start,
const Vec3f& source, float radius,
@ -116,19 +179,19 @@ void TriangleSelector::select_patch(const Vec3f& hit, int facet_start,
}
}
void TriangleSelector::seed_fill_select_triangles(const Vec3f& hit, int facet_start, float seed_fill_angle)
void TriangleSelector::seed_fill_select_triangles(const Vec3f &hit, int facet_start, float seed_fill_angle)
{
assert(facet_start < m_orig_size_indices);
this->seed_fill_unselect_all_triangles();
std::vector<bool> visited(m_triangles.size(), false);
std::queue<int> facet_queue;
std::queue<int> facet_queue;
facet_queue.push(facet_start);
const double facet_angle_limit = cos(Geometry::deg2rad(seed_fill_angle)) - EPSILON;
// Depth-first traversal of neighbors of the face hit by the ray thrown from the mouse cursor.
while(!facet_queue.empty()) {
while (!facet_queue.empty()) {
int current_facet = facet_queue.front();
facet_queue.pop();
@ -161,6 +224,129 @@ void TriangleSelector::seed_fill_select_triangles(const Vec3f& hit, int facet_st
}
}
void TriangleSelector::precompute_all_level_neighbors_recursive(const int facet_idx, const Vec3i &neighbors, const Vec3i &neighbors_propagated, std::vector<Vec3i> &neighbors_out) const
{
assert(facet_idx < int(m_triangles.size()));
const Triangle *tr = &m_triangles[facet_idx];
if (!tr->valid())
return;
neighbors_out[facet_idx] = neighbors_propagated;
if (tr->is_split()) {
assert(this->verify_triangle_neighbors(*tr, neighbors));
int num_of_children = tr->number_of_split_sides() + 1;
if (num_of_children != 1) {
for (int i = 0; i < num_of_children; ++i) {
assert(i < int(tr->children.size()));
assert(tr->children[i] < int(m_triangles.size()));
// Recursion, deep first search over the children of this triangle.
// All children of this triangle were created by splitting a single source triangle of the original mesh.
this->precompute_all_level_neighbors_recursive(tr->children[i], this->child_neighbors(*tr, neighbors, i), this->child_neighbors_propagated(*tr, neighbors_propagated, i), neighbors_out);
}
}
}
}
std::vector<Vec3i> TriangleSelector::precompute_all_level_neighbors() const
{
std::vector<Vec3i> neighbors(m_triangles.size(), Vec3i(-1, -1, -1));
for (int facet_idx = 0; facet_idx < this->m_orig_size_indices; ++facet_idx) {
neighbors[facet_idx] = root_neighbors(*m_mesh, facet_idx);
assert(this->verify_triangle_neighbors(m_triangles[facet_idx], neighbors[facet_idx]));
if (m_triangles[facet_idx].is_split())
this->precompute_all_level_neighbors_recursive(facet_idx, neighbors[facet_idx], neighbors[facet_idx], neighbors);
}
return neighbors;
}
bool TriangleSelector::are_triangles_touching(const int first_facet_idx, const int second_facet_idx) const
{
std::array<Linef3, 3> sides_facet = {Linef3(m_vertices[m_triangles[first_facet_idx].verts_idxs[0]].v.cast<double>(), m_vertices[m_triangles[first_facet_idx].verts_idxs[1]].v.cast<double>()),
Linef3(m_vertices[m_triangles[first_facet_idx].verts_idxs[1]].v.cast<double>(), m_vertices[m_triangles[first_facet_idx].verts_idxs[2]].v.cast<double>()),
Linef3(m_vertices[m_triangles[first_facet_idx].verts_idxs[2]].v.cast<double>(), m_vertices[m_triangles[first_facet_idx].verts_idxs[0]].v.cast<double>())};
const Vec3d p0 = m_vertices[m_triangles[second_facet_idx].verts_idxs[0]].v.cast<double>();
const Vec3d p1 = m_vertices[m_triangles[second_facet_idx].verts_idxs[1]].v.cast<double>();
const Vec3d p2 = m_vertices[m_triangles[second_facet_idx].verts_idxs[2]].v.cast<double>();
for (size_t idx = 0; idx < 3; ++idx)
if (line_alg::distance_to_squared(sides_facet[idx], p0) <= EPSILON && (line_alg::distance_to_squared(sides_facet[idx], p1) <= EPSILON || line_alg::distance_to_squared(sides_facet[idx], p2) <= EPSILON))
return true;
else if (line_alg::distance_to_squared(sides_facet[idx], p1) <= EPSILON && line_alg::distance_to_squared(sides_facet[idx], p2) <= EPSILON)
return true;
return false;
}
std::vector<int> TriangleSelector::neighboring_triangles(const int first_facet_idx, const int second_facet_idx, EnforcerBlockerType second_facet_state) const
{
assert(first_facet_idx < int(m_triangles.size()));
const Triangle *tr = &m_triangles[first_facet_idx];
if (!tr->valid())
return {};
if (!tr->is_split() && tr->get_state() == second_facet_state && (are_triangles_touching(second_facet_idx, first_facet_idx) || are_triangles_touching(first_facet_idx, second_facet_idx)))
return {first_facet_idx};
std::vector<int> neighbor_facets_out;
int num_of_children = tr->number_of_split_sides() + 1;
if (num_of_children != 1) {
for (int i = 0; i < num_of_children; ++i) {
assert(i < int(tr->children.size()));
assert(tr->children[i] < int(m_triangles.size()));
if (std::vector<int> neighbor_facets = neighboring_triangles(tr->children[i], second_facet_idx, second_facet_state); !neighbor_facets.empty())
Slic3r::append(neighbor_facets_out, std::move(neighbor_facets));
}
}
return neighbor_facets_out;
}
void TriangleSelector::bucket_fill_select_triangles(const Vec3f& hit, int facet_start, bool propagate)
{
this->seed_fill_unselect_all_triangles();
int start_facet_idx = select_unsplit_triangle(hit, facet_start);
EnforcerBlockerType start_facet_state = m_triangles[start_facet_idx].get_state();
if (start_facet_idx == -1)
return;
if (!propagate) {
m_triangles[start_facet_idx].select_by_seed_fill();
return;
}
std::vector<Vec3i> all_level_neighbors = this->precompute_all_level_neighbors();
std::vector<bool> visited(m_triangles.size(), false);
std::queue<int> facet_queue;
facet_queue.push(start_facet_idx);
while (!facet_queue.empty()) {
int current_facet = facet_queue.front();
facet_queue.pop();
assert(!m_triangles[current_facet].is_split());
if (!visited[current_facet]) {
m_triangles[current_facet].select_by_seed_fill();
for(int neighbor_idx : all_level_neighbors[current_facet]) {
if(!m_triangles[neighbor_idx].is_split()) {
if(m_triangles[neighbor_idx].get_state() == start_facet_state)
facet_queue.push(neighbor_idx);
} else {
for(int neighbor_facet_idx : neighboring_triangles(neighbor_idx, current_facet, start_facet_state))
facet_queue.push(neighbor_facet_idx);
}
}
}
visited[current_facet] = true;
}
}
// Selects either the whole triangle (discarding any children it had), or divides
// the triangle recursively, selecting just subtriangles truly inside the circle.
// This is done by an actual recursive call. Returns false if the triangle is
@ -412,6 +598,89 @@ Vec3i TriangleSelector::child_neighbors(const Triangle &tr, const Vec3i &neighbo
return out;
}
// Return neighbors of the ith child of a triangle given neighbors of the triangle.
// If such a neighbor doesn't exist, return the neighbor from the previous depth.
Vec3i TriangleSelector::child_neighbors_propagated(const Triangle &tr, const Vec3i &neighbors, int child_idx) const
{
int i = tr.special_side();
int j = i + 1;
if (j >= 3) j = 0;
int k = j + 1;
if (k >= 3) k = 0;
Vec3i out;
switch (tr.number_of_split_sides()) {
case 1:
switch (child_idx) {
case 0:
out(0) = neighbors(i);
out(1) = neighbors(j);
out(2) = tr.children[1];
break;
default:
assert(child_idx == 1);
out(0) = neighbors(j);
out(1) = neighbors(k);
out(2) = tr.children[0];
break;
}
break;
case 2:
switch (child_idx) {
case 0:
out(0) = neighbors(i);
out(1) = tr.children[1];
out(2) = neighbors(k);
break;
case 1:
assert(child_idx == 1);
out(0) = neighbors(i);
out(1) = tr.children[2];
out(2) = tr.children[0];
break;
default:
assert(child_idx == 2);
out(0) = neighbors(j);
out(1) = neighbors(k);
out(2) = tr.children[1];
break;
}
break;
case 3:
assert(tr.special_side() == 0);
switch (child_idx) {
case 0:
out(0) = neighbors(0);
out(1) = tr.children[3];
out(2) = neighbors(2);
break;
case 1:
out(0) = neighbors(0);
out(1) = neighbors(1);
out(2) = tr.children[3];
break;
case 2:
out(0) = neighbors(1);
out(1) = neighbors(2);
out(2) = tr.children[3];
break;
default:
assert(child_idx == 3);
out(0) = tr.children[1];
out(1) = tr.children[2];
out(2) = tr.children[0];
break;
}
break;
default: assert(false);
}
return out;
}
bool TriangleSelector::select_triangle_recursive(int facet_idx, const Vec3i &neighbors, EnforcerBlockerType type, bool triangle_splitting)
{
assert(facet_idx < int(m_triangles.size()));

View file

@ -18,9 +18,13 @@ class TriangleSelector {
public:
enum CursorType {
CIRCLE,
SPHERE
SPHERE,
POINTER
};
[[nodiscard]] std::vector<Vec3i> precompute_all_level_neighbors() const;
void precompute_all_level_neighbors_recursive(const int facet_idx, const Vec3i &neighbors, const Vec3i &neighbors_propagated, std::vector<Vec3i> &neighbors_out) const;
// Set a limit to the edge length, below which the edge will not be split by select_patch().
// Called by select_patch() internally. Made public for debugging purposes, see TriangleSelectorGUI::render_debug().
void set_edge_limit(float edge_limit);
@ -29,6 +33,14 @@ public:
// stay valid, a ptr to it is saved and used.
explicit TriangleSelector(const TriangleMesh& mesh);
// Returns the facet_idx of the unsplit triangle containing the "hit". Returns -1 if the triangle isn't found.
[[nodiscard]] int select_unsplit_triangle(const Vec3f &hit, int facet_idx) const;
[[nodiscard]] int select_unsplit_triangle(const Vec3f &hit, int facet_idx, const Vec3i &neighbors) const;
[[nodiscard]] bool are_triangles_touching(int first_facet_idx, int second_facet_idx) const;
[[nodiscard]] std::vector<int> neighboring_triangles(int first_facet_idx, int second_facet_idx, EnforcerBlockerType second_facet_state) const;
// Select all triangles fully inside the circle, subdivide where needed.
void select_patch(const Vec3f &hit, // point where to start
int facet_start, // facet of the original mesh (unsplit) that the hit point belongs to
@ -43,6 +55,10 @@ public:
int facet_start, // facet of the original mesh (unsplit) that the hit point belongs to
float seed_fill_angle); // the maximal angle between two facets to be painted by the same color
void bucket_fill_select_triangles(const Vec3f &hit, // point where to start
int facet_start, // facet of the original mesh (unsplit) that the hit point belongs to
bool propagate); // if bucket fill is propagated to neighbor faces or if it fills the only facet of the modified mesh that the hit point belongs to.
bool has_facets(EnforcerBlockerType state) const;
static bool has_facets(const std::pair<std::vector<std::pair<int, int>>, std::vector<bool>> &data, const EnforcerBlockerType test_state);
int num_facets(EnforcerBlockerType state) const;
@ -192,6 +208,7 @@ private:
int push_triangle(int a, int b, int c, int source_triangle, const EnforcerBlockerType state = EnforcerBlockerType{0});
void perform_split(int facet_idx, const Vec3i &neighbors, EnforcerBlockerType old_state);
Vec3i child_neighbors(const Triangle &tr, const Vec3i &neighbors, int child_idx) const;
Vec3i child_neighbors_propagated(const Triangle &tr, const Vec3i &neighbors, int child_idx) const;
// Return child of itriangle at a CCW oriented side (vertexi, vertexj), either first or 2nd part.
// If itriangle == -1 or if the side sharing (vertexi, vertexj) is not split, return -1.
enum class Partition {

View file

@ -107,16 +107,24 @@ bool GLGizmoMmuSegmentation::on_init()
m_desc["reset_direction"] = _L("Reset direction");
m_desc["clipping_of_view"] = _L("Clipping of view") + ": ";
m_desc["cursor_size"] = _L("Brush size") + ": ";
m_desc["cursor_type"] = _L("Brush shape") + ": ";
m_desc["cursor_type"] = _L("Brush shape");
m_desc["first_color_caption"] = _L("Left mouse button") + ": ";
m_desc["first_color"] = _L("First color");
m_desc["second_color_caption"] = _L("Right mouse button") + ": ";
m_desc["second_color"] = _L("Second color");
m_desc["remove_caption"] = _L("Shift + Left mouse button") + ": ";
m_desc["remove"] = _L("Remove painted color");
m_desc["remove_all"] = _L("Remove all painted colors");
m_desc["remove_all"] = _L("Remove all painted areas");
m_desc["circle"] = _L("Circle");
m_desc["sphere"] = _L("Sphere");
m_desc["pointer"] = _L("Pointer");
m_desc["tool_type"] = _L("Tool type");
m_desc["tool_brush"] = _L("Brush");
m_desc["tool_seed_fill"] = _L("Seed fill");
m_desc["tool_bucket_fill"] = _L("Bucket fill");
m_desc["seed_fill"] = _L("Seed fill");
m_desc["seed_fill_angle"] = _L("Seed fill angle");
init_extruders_data();
@ -222,7 +230,7 @@ void GLGizmoMmuSegmentation::on_render_input_window(float x, float y, float bott
if (!m_c->selection_info()->model_object())
return;
const float approx_height = m_imgui->scaled(23.0f);
const float approx_height = m_imgui->scaled(25.0f);
y = std::min(y, bottom_limit - approx_height);
m_imgui->set_next_window_pos(x, y, ImGuiCond_Always);
@ -232,10 +240,12 @@ void GLGizmoMmuSegmentation::on_render_input_window(float x, float y, float bott
const float clipping_slider_left = std::max(m_imgui->calc_text_size(m_desc.at("clipping_of_view")).x,
m_imgui->calc_text_size(m_desc.at("reset_direction")).x) + m_imgui->scaled(1.5f);
const float cursor_slider_left = m_imgui->calc_text_size(m_desc.at("cursor_size")).x + m_imgui->scaled(1.f);
const float autoset_slider_left = m_imgui->calc_text_size(m_desc.at("seed_fill_angle")).x + m_imgui->scaled(1.f);
const float cursor_type_radio_left = m_imgui->calc_text_size(m_desc.at("cursor_type")).x + m_imgui->scaled(1.f);
const float cursor_type_radio_width1 = m_imgui->calc_text_size(m_desc["circle"]).x + m_imgui->scaled(2.5f);
const float cursor_type_radio_width2 = m_imgui->calc_text_size(m_desc["sphere"]).x + m_imgui->scaled(2.5f);
const float seed_fill_slider_left = m_imgui->calc_text_size(m_desc.at("seed_fill_angle")).x + m_imgui->scaled(1.f);
const float cursor_type_radio_circle = m_imgui->calc_text_size(m_desc["circle"]).x + m_imgui->scaled(2.5f);
const float cursor_type_radio_sphere = m_imgui->calc_text_size(m_desc["sphere"]).x + m_imgui->scaled(2.5f);
const float cursor_type_radio_pointer = m_imgui->calc_text_size(m_desc["pointer"]).x + m_imgui->scaled(2.5f);
const float button_width = m_imgui->calc_text_size(m_desc.at("remove_all")).x + m_imgui->scaled(1.f);
const float buttons_width = m_imgui->scaled(0.5f);
const float minimal_slider_width = m_imgui->scaled(4.f);
@ -243,6 +253,10 @@ void GLGizmoMmuSegmentation::on_render_input_window(float x, float y, float bott
const float combo_label_width = std::max(m_imgui->calc_text_size(m_desc.at("first_color")).x,
m_imgui->calc_text_size(m_desc.at("second_color")).x) + m_imgui->scaled(1.f);
const float tool_type_radio_brush = m_imgui->calc_text_size(m_desc["tool_brush"]).x + m_imgui->scaled(2.5f);
const float tool_type_radio_bucket_fill = m_imgui->calc_text_size(m_desc["tool_bucket_fill"]).x + m_imgui->scaled(2.5f);
const float tool_type_radio_seed_fill = m_imgui->calc_text_size(m_desc["tool_seed_fill"]).x + m_imgui->scaled(2.5f);
float caption_max = 0.f;
float total_text_max = 0.;
for (const auto &t : std::array<std::string, 3>{"first_color", "second_color", "remove"}) {
@ -252,10 +266,12 @@ void GLGizmoMmuSegmentation::on_render_input_window(float x, float y, float bott
caption_max += m_imgui->scaled(1.f);
total_text_max += m_imgui->scaled(1.f);
float window_width = minimal_slider_width + std::max(autoset_slider_left, std::max(cursor_slider_left, clipping_slider_left));
float sliders_width = std::max(seed_fill_slider_left, std::max(cursor_slider_left, clipping_slider_left));
float window_width = minimal_slider_width + sliders_width;
window_width = std::max(window_width, total_text_max);
window_width = std::max(window_width, button_width);
window_width = std::max(window_width, cursor_type_radio_left + cursor_type_radio_width1 + cursor_type_radio_width2);
window_width = std::max(window_width, cursor_type_radio_circle + cursor_type_radio_sphere + cursor_type_radio_pointer);
window_width = std::max(window_width, tool_type_radio_brush + tool_type_radio_bucket_fill + tool_type_radio_seed_fill);
window_width = std::max(window_width, 2.f * buttons_width + m_imgui->scaled(1.f));
auto draw_text_with_caption = [this, &caption_max](const wxString &caption, const wxString &text) {
@ -292,96 +308,166 @@ void GLGizmoMmuSegmentation::on_render_input_window(float x, float y, float bott
if(ImGui::ColorEdit4("Second color##color_picker", (float*)&second_color, ImGuiColorEditFlags_NoAlpha | ImGuiColorEditFlags_NoInputs | ImGuiColorEditFlags_NoLabel))
m_modified_extruders_colors[m_second_selected_extruder_idx] = {second_color.x, second_color.y, second_color.z, second_color.w};
ImGui::Separator();
if (m_imgui->checkbox(_L("Seed fill"), m_seed_fill_enabled))
if (!m_seed_fill_enabled)
for (auto &triangle_selector : m_triangle_selectors) {
triangle_selector->seed_fill_unselect_all_triangles();
triangle_selector->request_update_render_data();
}
m_imgui->text(m_desc["seed_fill_angle"] + ":");
ImGui::AlignTextToFramePadding();
std::string format_str = std::string("%.f") + I18N::translate_utf8("°", "Degree sign to use in the respective slider in FDM supports gizmo,"
"placed after the number with no whitespace in between.");
ImGui::SameLine(autoset_slider_left);
ImGui::PushItemWidth(window_width - autoset_slider_left);
m_imgui->disabled_begin(!m_seed_fill_enabled);
m_imgui->slider_float("##seed_fill_angle", &m_seed_fill_angle, 0.f, 90.f, format_str.data());
m_imgui->disabled_end();
ImGui::Separator();
if (m_imgui->button(m_desc.at("remove_all"))) {
Plater::TakeSnapshot snapshot(wxGetApp().plater(), wxString(_L("Reset selection")));
ModelObject *mo = m_c->selection_info()->model_object();
int idx = -1;
for (ModelVolume *mv : mo->volumes) {
if (mv->is_model_part()) {
++idx;
m_triangle_selectors[idx]->reset();
m_triangle_selectors[idx]->request_update_render_data();
}
}
update_model_object();
m_parent.set_as_dirty();
}
const float max_tooltip_width = ImGui::GetFontSize() * 20.0f;
ImGui::Separator();
ImGui::AlignTextToFramePadding();
m_imgui->text(m_desc.at("cursor_size"));
ImGui::SameLine(cursor_slider_left);
ImGui::PushItemWidth(window_width - cursor_slider_left);
ImGui::SliderFloat(" ", &m_cursor_radius, CursorRadiusMin, CursorRadiusMax, "%.2f");
m_imgui->text(m_desc.at("tool_type"));
float tool_type_offset = (window_width - tool_type_radio_brush - tool_type_radio_bucket_fill - tool_type_radio_seed_fill + m_imgui->scaled(2.f)) / 2.f;
ImGui::NewLine();
ImGui::AlignTextToFramePadding();
ImGui::SameLine(tool_type_offset + m_imgui->scaled(0.f));
ImGui::PushItemWidth(tool_type_radio_brush);
if (m_imgui->radio_button(m_desc["tool_brush"], m_tool_type == GLGizmoMmuSegmentation::ToolType::BRUSH))
m_tool_type = GLGizmoMmuSegmentation::ToolType::BRUSH;
if (ImGui::IsItemHovered()) {
ImGui::BeginTooltip();
ImGui::PushTextWrapPos(max_tooltip_width);
ImGui::TextUnformatted(_L("Alt + Mouse wheel").ToUTF8().data());
ImGui::TextUnformatted(_L("Paints facets according to the chosen painting brush.").ToUTF8().data());
ImGui::PopTextWrapPos();
ImGui::EndTooltip();
}
ImGui::SameLine(tool_type_offset + tool_type_radio_brush + m_imgui->scaled(0.f));
ImGui::PushItemWidth(tool_type_radio_bucket_fill);
if (m_imgui->radio_button(m_desc["tool_bucket_fill"], m_tool_type == GLGizmoMmuSegmentation::ToolType::BUCKET_FILL)) {
m_tool_type = GLGizmoMmuSegmentation::ToolType::BUCKET_FILL;
for (auto &triangle_selector : m_triangle_selectors) {
triangle_selector->seed_fill_unselect_all_triangles();
triangle_selector->request_update_render_data();
}
}
if (ImGui::IsItemHovered()) {
ImGui::BeginTooltip();
ImGui::PushTextWrapPos(max_tooltip_width);
ImGui::TextUnformatted(_L("Paints neighboring facets whose relative angle is less or equal to set angle.").ToUTF8().data());
ImGui::PopTextWrapPos();
ImGui::EndTooltip();
}
ImGui::SameLine(tool_type_offset + tool_type_radio_brush + tool_type_radio_bucket_fill + m_imgui->scaled(0.f));
ImGui::PushItemWidth(tool_type_radio_seed_fill);
if (m_imgui->radio_button(m_desc["tool_seed_fill"], m_tool_type == GLGizmoMmuSegmentation::ToolType::SEED_FILL)) {
m_tool_type = GLGizmoMmuSegmentation::ToolType::SEED_FILL;
for (auto &triangle_selector : m_triangle_selectors) {
triangle_selector->seed_fill_unselect_all_triangles();
triangle_selector->request_update_render_data();
}
}
if (ImGui::IsItemHovered()) {
ImGui::BeginTooltip();
ImGui::PushTextWrapPos(max_tooltip_width);
ImGui::TextUnformatted(_L("Paints neighboring that have the same color.").ToUTF8().data());
ImGui::PopTextWrapPos();
ImGui::EndTooltip();
}
// Manually inserted values aren't clamped by ImGui. Zero cursor size results in a crash.
m_cursor_radius = std::clamp(m_cursor_radius, CursorRadiusMin, CursorRadiusMax);
ImGui::AlignTextToFramePadding();
m_imgui->text(m_desc.at("cursor_type"));
ImGui::SameLine(cursor_type_radio_left + m_imgui->scaled(0.f));
ImGui::PushItemWidth(cursor_type_radio_width1);
bool sphere_sel = m_cursor_type == TriangleSelector::CursorType::SPHERE;
if (m_imgui->radio_button(m_desc["sphere"], sphere_sel))
sphere_sel = true;
if (ImGui::IsItemHovered()) {
ImGui::BeginTooltip();
ImGui::PushTextWrapPos(max_tooltip_width);
ImGui::TextUnformatted(_L("Paints all facets inside, regardless of their orientation.").ToUTF8().data());
ImGui::PopTextWrapPos();
ImGui::EndTooltip();
}
ImGui::SameLine(cursor_type_radio_left + cursor_type_radio_width2 + m_imgui->scaled(0.f));
ImGui::PushItemWidth(cursor_type_radio_width2);
if (m_imgui->radio_button(m_desc["circle"], !sphere_sel))
sphere_sel = false;
if (ImGui::IsItemHovered()) {
ImGui::BeginTooltip();
ImGui::PushTextWrapPos(max_tooltip_width);
ImGui::TextUnformatted(_L("Ignores facets facing away from the camera.").ToUTF8().data());
ImGui::PopTextWrapPos();
ImGui::EndTooltip();
}
m_cursor_type = sphere_sel ? TriangleSelector::CursorType::SPHERE : TriangleSelector::CursorType::CIRCLE;
m_imgui->checkbox(_L("Split triangles"), m_triangle_splitting_enabled);
ImGui::Separator();
if(m_tool_type == ToolType::BRUSH) {
ImGui::AlignTextToFramePadding();
m_imgui->text(m_desc.at("cursor_type"));
ImGui::NewLine();
float cursor_type_offset = (window_width - cursor_type_radio_sphere - cursor_type_radio_circle - cursor_type_radio_pointer + m_imgui->scaled(2.f)) / 2.f;
ImGui::AlignTextToFramePadding();
ImGui::SameLine(cursor_type_offset + m_imgui->scaled(0.f));
ImGui::PushItemWidth(cursor_type_radio_sphere);
if (m_imgui->radio_button(m_desc["sphere"], m_cursor_type == TriangleSelector::CursorType::SPHERE))
m_cursor_type = TriangleSelector::CursorType::SPHERE;
if (ImGui::IsItemHovered()) {
ImGui::BeginTooltip();
ImGui::PushTextWrapPos(max_tooltip_width);
ImGui::TextUnformatted(_L("Paints all facets inside, regardless of their orientation.").ToUTF8().data());
ImGui::PopTextWrapPos();
ImGui::EndTooltip();
}
ImGui::SameLine(cursor_type_offset +cursor_type_radio_sphere + m_imgui->scaled(0.f));
ImGui::PushItemWidth(cursor_type_radio_circle);
if (m_imgui->radio_button(m_desc["circle"], m_cursor_type == TriangleSelector::CursorType::CIRCLE))
m_cursor_type = TriangleSelector::CursorType::CIRCLE;
if (ImGui::IsItemHovered()) {
ImGui::BeginTooltip();
ImGui::PushTextWrapPos(max_tooltip_width);
ImGui::TextUnformatted(_L("Ignores facets facing away from the camera.").ToUTF8().data());
ImGui::PopTextWrapPos();
ImGui::EndTooltip();
}
ImGui::SameLine(cursor_type_offset + cursor_type_radio_sphere + cursor_type_radio_circle + m_imgui->scaled(0.f));
ImGui::PushItemWidth(cursor_type_radio_pointer);
if (m_imgui->radio_button(m_desc["pointer"], m_cursor_type == TriangleSelector::CursorType::POINTER))
m_cursor_type = TriangleSelector::CursorType::POINTER;
if (ImGui::IsItemHovered()) {
ImGui::BeginTooltip();
ImGui::PushTextWrapPos(max_tooltip_width);
ImGui::TextUnformatted(_L("Paints only one facet.").ToUTF8().data());
ImGui::PopTextWrapPos();
ImGui::EndTooltip();
}
m_imgui->disabled_begin(m_cursor_type != TriangleSelector::CursorType::SPHERE && m_cursor_type != TriangleSelector::CursorType::CIRCLE);
ImGui::AlignTextToFramePadding();
m_imgui->text(m_desc.at("cursor_size"));
ImGui::SameLine(sliders_width);
ImGui::PushItemWidth(window_width - sliders_width);
ImGui::SliderFloat(" ", &m_cursor_radius, CursorRadiusMin, CursorRadiusMax, "%.2f");
if (ImGui::IsItemHovered()) {
ImGui::BeginTooltip();
ImGui::PushTextWrapPos(max_tooltip_width);
ImGui::TextUnformatted(_L("Alt + Mouse wheel").ToUTF8().data());
ImGui::PopTextWrapPos();
ImGui::EndTooltip();
}
m_imgui->checkbox(_L("Split triangles"), m_triangle_splitting_enabled);
if (ImGui::IsItemHovered()) {
ImGui::BeginTooltip();
ImGui::PushTextWrapPos(max_tooltip_width);
ImGui::TextUnformatted(_L("Split bigger facets into smaller ones while the object is painted.").ToUTF8().data());
ImGui::PopTextWrapPos();
ImGui::EndTooltip();
}
m_imgui->disabled_end();
ImGui::Separator();
} else if(m_tool_type == ToolType::SEED_FILL) {
m_imgui->text(m_desc["seed_fill_angle"] + ":");
ImGui::AlignTextToFramePadding();
std::string format_str = std::string("%.f") + I18N::translate_utf8("°", "Degree sign to use in the respective slider in MMU gizmo,"
"placed after the number with no whitespace in between.");
ImGui::SameLine(sliders_width);
ImGui::PushItemWidth(window_width - sliders_width);
m_imgui->slider_float("##seed_fill_angle", &m_seed_fill_angle, SeedFillAngleMin, SeedFillAngleMax, format_str.data());
if (ImGui::IsItemHovered()) {
ImGui::BeginTooltip();
ImGui::PushTextWrapPos(max_tooltip_width);
ImGui::TextUnformatted(_L("Alt + Mouse wheel").ToUTF8().data());
ImGui::PopTextWrapPos();
ImGui::EndTooltip();
}
ImGui::Separator();
}
if (m_c->object_clipper()->get_position() == 0.f) {
ImGui::AlignTextToFramePadding();
m_imgui->text(m_desc.at("clipping_of_view"));
@ -391,8 +477,8 @@ void GLGizmoMmuSegmentation::on_render_input_window(float x, float y, float bott
}
}
ImGui::SameLine(clipping_slider_left);
ImGui::PushItemWidth(window_width - clipping_slider_left);
ImGui::SameLine(sliders_width);
ImGui::PushItemWidth(window_width - sliders_width);
auto clp_dist = float(m_c->object_clipper()->get_position());
if (ImGui::SliderFloat(" ", &clp_dist, 0.f, 1.f, "%.2f"))
m_c->object_clipper()->set_position(clp_dist, true);
@ -403,6 +489,23 @@ void GLGizmoMmuSegmentation::on_render_input_window(float x, float y, float bott
ImGui::PopTextWrapPos();
ImGui::EndTooltip();
}
ImGui::Separator();
if (m_imgui->button(m_desc.at("remove_all"))) {
Plater::TakeSnapshot snapshot(wxGetApp().plater(), wxString(_L("Reset selection")));
ModelObject * mo = m_c->selection_info()->model_object();
int idx = -1;
for (ModelVolume *mv : mo->volumes)
if (mv->is_model_part()) {
++idx;
m_triangle_selectors[idx]->reset();
m_triangle_selectors[idx]->request_update_render_data();
}
update_model_object();
m_parent.set_as_dirty();
}
m_imgui->end();
}

View file

@ -194,10 +194,10 @@ void GLGizmoPainterBase::render_cursor() const
if (m_rr.mesh_id == -1)
return;
if (!m_seed_fill_enabled) {
if (m_tool_type == ToolType::BRUSH) {
if (m_cursor_type == TriangleSelector::SPHERE)
render_cursor_sphere(trafo_matrices[m_rr.mesh_id]);
else
else if (m_cursor_type == TriangleSelector::CIRCLE)
render_cursor_circle();
}
}
@ -307,11 +307,19 @@ bool GLGizmoPainterBase::gizmo_event(SLAGizmoEventType action, const Vec2d& mous
return true;
}
else if (alt_down) {
m_cursor_radius = action == SLAGizmoEventType::MouseWheelDown
? std::max(m_cursor_radius - CursorRadiusStep, CursorRadiusMin)
: std::min(m_cursor_radius + CursorRadiusStep, CursorRadiusMax);
m_parent.set_as_dirty();
return true;
if (m_tool_type == ToolType::BRUSH) {
m_cursor_radius = action == SLAGizmoEventType::MouseWheelDown ? std::max(m_cursor_radius - CursorRadiusStep, CursorRadiusMin)
: std::min(m_cursor_radius + CursorRadiusStep, CursorRadiusMax);
m_parent.set_as_dirty();
return true;
} else if (m_tool_type == ToolType::SEED_FILL) {
m_seed_fill_angle = action == SLAGizmoEventType::MouseWheelDown ? std::max(m_seed_fill_angle - SeedFillAngleStep, SeedFillAngleMin)
: std::min(m_seed_fill_angle + SeedFillAngleStep, SeedFillAngleMax);
m_parent.set_as_dirty();
return true;
}
return false;
}
}
@ -396,10 +404,10 @@ bool GLGizmoPainterBase::gizmo_event(SLAGizmoEventType action, const Vec2d& mous
Vec3f camera_pos = (trafo_matrix.inverse() * camera.get_position()).cast<float>();
assert(m_rr.mesh_id < int(m_triangle_selectors.size()));
if (m_seed_fill_enabled) {
if (m_tool_type == ToolType::SEED_FILL || m_tool_type == ToolType::BUCKET_FILL || (m_tool_type == ToolType::BRUSH && m_cursor_type == TriangleSelector::CursorType::POINTER)) {
m_triangle_selectors[m_rr.mesh_id]->seed_fill_apply_on_triangles(new_state);
m_seed_fill_last_mesh_id = -1;
} else
} else if (m_tool_type == ToolType::BRUSH)
m_triangle_selectors[m_rr.mesh_id]->select_patch(m_rr.hit, int(m_rr.facet), camera_pos, m_cursor_radius, m_cursor_type,
new_state, trafo_matrix, m_triangle_splitting_enabled);
@ -410,7 +418,7 @@ bool GLGizmoPainterBase::gizmo_event(SLAGizmoEventType action, const Vec2d& mous
return true;
}
if (action == SLAGizmoEventType::Moving && m_seed_fill_enabled) {
if (action == SLAGizmoEventType::Moving && (m_tool_type == ToolType::SEED_FILL || m_tool_type == ToolType::BUCKET_FILL || (m_tool_type == ToolType::BRUSH && m_cursor_type == TriangleSelector::CursorType::POINTER))) {
if (m_triangle_selectors.empty())
return false;
@ -450,7 +458,12 @@ bool GLGizmoPainterBase::gizmo_event(SLAGizmoEventType action, const Vec2d& mous
seed_fill_unselect_all();
assert(m_rr.mesh_id < int(m_triangle_selectors.size()));
m_triangle_selectors[m_rr.mesh_id]->seed_fill_select_triangles(m_rr.hit, int(m_rr.facet), m_seed_fill_angle);
if (m_tool_type == ToolType::SEED_FILL)
m_triangle_selectors[m_rr.mesh_id]->seed_fill_select_triangles(m_rr.hit, int(m_rr.facet), m_seed_fill_angle);
else if (m_tool_type == ToolType::BRUSH && m_cursor_type == TriangleSelector::CursorType::POINTER)
m_triangle_selectors[m_rr.mesh_id]->bucket_fill_select_triangles(m_rr.hit, int(m_rr.facet), false);
else if (m_tool_type == ToolType::BUCKET_FILL)
m_triangle_selectors[m_rr.mesh_id]->bucket_fill_select_triangles(m_rr.hit, int(m_rr.facet), true);
m_triangle_selectors[m_rr.mesh_id]->request_update_render_data();
m_seed_fill_last_mesh_id = m_rr.mesh_id;
return true;

View file

@ -106,13 +106,23 @@ protected:
TriangleSelector::CursorType m_cursor_type = TriangleSelector::SPHERE;
bool m_triangle_splitting_enabled = true;
bool m_seed_fill_enabled = false;
float m_seed_fill_angle = 0.f;
enum class ToolType {
BRUSH,
BUCKET_FILL,
SEED_FILL
};
bool m_triangle_splitting_enabled = true;
ToolType m_tool_type = ToolType::BRUSH;
float m_seed_fill_angle = 0.f;
static constexpr float SeedFillAngleMin = 0.0f;
static constexpr float SeedFillAngleMax = 90.f;
static constexpr float SeedFillAngleStep = 1.f;
// It stores the value of the previous mesh_id to which the seed fill was applied.
// It is used to detect when the mouse has moved from one volume to another one.
int m_seed_fill_last_mesh_id = -1;
int m_seed_fill_last_mesh_id = -1;
enum class Button {
None,