From 2f43c1f3fa7473f5fe6c5a6b8baa7766fbf0756c Mon Sep 17 00:00:00 2001 From: YuSanka Date: Wed, 22 Jul 2020 15:52:01 +0200 Subject: [PATCH 01/24] Fixed update of the TreeCtrls and "revert to system values" buttons on preset tabs, if application was run in New or Dlg mode --- src/slic3r/GUI/MainFrame.cpp | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/slic3r/GUI/MainFrame.cpp b/src/slic3r/GUI/MainFrame.cpp index 10d9d800f..64a1319d4 100644 --- a/src/slic3r/GUI/MainFrame.cpp +++ b/src/slic3r/GUI/MainFrame.cpp @@ -1527,6 +1527,7 @@ void MainFrame::load_config(const DynamicPrintConfig& config) void MainFrame::select_tab(size_t tab/* = size_t(-1)*/) { + bool tabpanel_was_hidden = false; #if ENABLE_LAYOUT_NO_RESTART if (m_layout == ESettingsLayout::Dlg) { #else @@ -1557,6 +1558,7 @@ void MainFrame::select_tab(size_t tab/* = size_t(-1)*/) if (m_settings_dialog.IsShown()) m_settings_dialog.SetFocus(); else { + tabpanel_was_hidden = true; m_tabpanel->Show(); m_settings_dialog.Show(); } @@ -1576,6 +1578,7 @@ void MainFrame::select_tab(size_t tab/* = size_t(-1)*/) #if ENABLE_LAYOUT_NO_RESTART else if (m_layout == ESettingsLayout::New) { m_main_sizer->Show(m_plater, tab == 0); + tabpanel_was_hidden = !m_main_sizer->IsShown(m_tabpanel); m_main_sizer->Show(m_tabpanel, tab != 0); #else else if (m_layout == slNew) { @@ -1589,6 +1592,14 @@ void MainFrame::select_tab(size_t tab/* = size_t(-1)*/) Layout(); } + // When we run application in ESettingsLayout::New or ESettingsLayout::Dlg mode, tabpanel is hidden from the very beginning + // and as a result Tab::update_changed_tree_ui() function couldn't update m_is_nonsys_values values, + // which are used for update TreeCtrl and "revert_buttons". + // So, force the call of this function for Tabs, if tab panel was hidden + if (tabpanel_was_hidden) + for (auto tab : wxGetApp().tabs_list) + tab->update_changed_tree_ui(); + // when tab == -1, it means we should show the last selected tab #if ENABLE_LAYOUT_NO_RESTART m_tabpanel->SetSelection(tab == (size_t)(-1) ? m_last_selected_tab : (m_layout == ESettingsLayout::Dlg && tab != 0) ? tab - 1 : tab); From fd50c3d262942338eac2abaab0c3e107d512a7ae Mon Sep 17 00:00:00 2001 From: YuSanka Date: Fri, 24 Jul 2020 11:21:49 +0200 Subject: [PATCH 02/24] Fixed a bug in selection from the 3D scene. Steps to the reproduce a crash: 1. In SLA mode add some object with several instances 2. Slice 3. Back to 3Dview scene, select all using Ctrl+A 4. Press "Delete" --- src/slic3r/GUI/GUI_ObjectList.cpp | 5 +++-- src/slic3r/GUI/Selection.cpp | 9 +++++---- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/src/slic3r/GUI/GUI_ObjectList.cpp b/src/slic3r/GUI/GUI_ObjectList.cpp index a47c8f671..f3ff264ce 100644 --- a/src/slic3r/GUI/GUI_ObjectList.cpp +++ b/src/slic3r/GUI/GUI_ObjectList.cpp @@ -2364,8 +2364,9 @@ void ObjectList::del_layers_from_object(const int obj_idx) bool ObjectList::del_subobject_from_object(const int obj_idx, const int idx, const int type) { - if (obj_idx == 1000) - // Cannot delete a wipe tower. + assert(idx >= 0); + if (obj_idx == 1000 || idx<0) + // Cannot delete a wipe tower or volume with negative id return false; ModelObject* object = (*m_objects)[obj_idx]; diff --git a/src/slic3r/GUI/Selection.cpp b/src/slic3r/GUI/Selection.cpp index 69550748d..d76c2c712 100644 --- a/src/slic3r/GUI/Selection.cpp +++ b/src/slic3r/GUI/Selection.cpp @@ -1605,10 +1605,11 @@ void Selection::update_type() if ((*m_volumes)[i]->volume_idx() < 0) ++sla_volumes_count; } - unsigned int volumes_count = model_volumes_count + sla_volumes_count; + // Note: sla_volumes_count is a count of the selected sla_volumes per object instead of per instance, like a model_volumes_count is + unsigned int instances_count = (unsigned int)model_object->instances.size(); unsigned int selected_instances_count = (unsigned int)m_cache.content.begin()->second.size(); - if (volumes_count * instances_count == (unsigned int)m_list.size()) + if (model_volumes_count * instances_count + sla_volumes_count == (unsigned int)m_list.size()) { m_type = SingleFullObject; // ensures the correct mode is selected @@ -1616,7 +1617,7 @@ void Selection::update_type() } else if (selected_instances_count == 1) { - if (volumes_count == (unsigned int)m_list.size()) + if (model_volumes_count + sla_volumes_count == (unsigned int)m_list.size()) { m_type = SingleFullInstance; // ensures the correct mode is selected @@ -1639,7 +1640,7 @@ void Selection::update_type() requires_disable = true; } } - else if ((selected_instances_count > 1) && (selected_instances_count * volumes_count == (unsigned int)m_list.size())) + else if ((selected_instances_count > 1) && (selected_instances_count * model_volumes_count + sla_volumes_count == (unsigned int)m_list.size())) { m_type = MultipleFullInstance; // ensures the correct mode is selected From 0280a2a15bcb10d3ba53692ab60a33713390aba8 Mon Sep 17 00:00:00 2001 From: YuSanka Date: Fri, 24 Jul 2020 13:02:46 +0200 Subject: [PATCH 03/24] Hot fix for the last commit --- src/slic3r/GUI/Selection.cpp | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/slic3r/GUI/Selection.cpp b/src/slic3r/GUI/Selection.cpp index d76c2c712..e9250fe9e 100644 --- a/src/slic3r/GUI/Selection.cpp +++ b/src/slic3r/GUI/Selection.cpp @@ -1595,17 +1595,17 @@ void Selection::update_type() } else { + unsigned int sla_volumes_count = 0; + // Note: sla_volumes_count is a count of the selected sla_volumes per object instead of per instance, like a model_volumes_count is + for (unsigned int i : m_list) { + if ((*m_volumes)[i]->volume_idx() < 0) + ++sla_volumes_count; + } + if (m_cache.content.size() == 1) // single object { const ModelObject* model_object = m_model->objects[m_cache.content.begin()->first]; unsigned int model_volumes_count = (unsigned int)model_object->volumes.size(); - unsigned int sla_volumes_count = 0; - for (unsigned int i : m_list) - { - if ((*m_volumes)[i]->volume_idx() < 0) - ++sla_volumes_count; - } - // Note: sla_volumes_count is a count of the selected sla_volumes per object instead of per instance, like a model_volumes_count is unsigned int instances_count = (unsigned int)model_object->instances.size(); unsigned int selected_instances_count = (unsigned int)m_cache.content.begin()->second.size(); @@ -1657,7 +1657,7 @@ void Selection::update_type() unsigned int instances_count = (unsigned int)model_object->instances.size(); sels_cntr += volumes_count * instances_count; } - if (sels_cntr == (unsigned int)m_list.size()) + if (sels_cntr + sla_volumes_count == (unsigned int)m_list.size()) { m_type = MultipleFullObject; // ensures the correct mode is selected From 953d1417a0a751ffcb9fc5c93d9e42a203a0cda4 Mon Sep 17 00:00:00 2001 From: Lukas Matena Date: Thu, 11 Jun 2020 13:09:34 +0200 Subject: [PATCH 04/24] TriangleSelector: draft of interface --- src/libslic3r/Model.hpp | 1 + src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.cpp | 66 ++++++++++++++++++ src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.hpp | 71 ++++++++++++++++++++ 3 files changed, 138 insertions(+) diff --git a/src/libslic3r/Model.hpp b/src/libslic3r/Model.hpp index e5930fb8a..be298ae4b 100644 --- a/src/libslic3r/Model.hpp +++ b/src/libslic3r/Model.hpp @@ -394,6 +394,7 @@ enum class ModelVolumeType : int { }; enum class FacetSupportType : int8_t { + // Maximum is 3. The value is serialized in TriangleSelector into 2 bits! NONE = 0, ENFORCER = 1, BLOCKER = 2 diff --git a/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.cpp b/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.cpp index cd4285724..00e236b64 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.cpp @@ -805,6 +805,10 @@ void GLGizmoFdmSupports::on_set_state() activate_internal_undo_redo_stack(true); }); } + + TriangleSelector ts{TriangleMesh()}; + ts.test(); + } if (m_state == Off && m_old_state != Off) { // the gizmo was just turned Off // we are actually shutting down @@ -854,5 +858,67 @@ void GLGizmoFdmSupports::on_save(cereal::BinaryOutputArchive&) const +void TriangleSelector::test() +{ + DivisionNode node; + while (true) { + std::cout << "Zadej pocet stran a spec stranu: "; + int num; + int spec; + std::cin >> num >> spec; + node.set_division(num, spec); + std::cout << node.number_of_split_sides() << " " + << (node.number_of_split_sides()==1 ? node.side_to_split() : node.side_to_keep()) + << std::endl << std::endl; + } +} + + +void TriangleSelector::DivisionNode::set_division(int sides_to_split, int special_side_idx) +{ + assert(sides_to_split >=0 && sides_to_split <= 3); + assert(special_side_idx >=-1 && special_side_idx < 3); + + // If splitting one or two sides, second argument must be provided. + assert(sides_to_split != 1 || special_side_idx != -1); + assert(sides_to_split != 2 || special_side_idx != -1); + + division_type = sides_to_split | (special_side_idx != -1 ? (special_side_idx << 2) : 0 ); +} + + + +void TriangleSelector::DivisionNode::set_type(FacetSupportType type) +{ + // If this is not a leaf-node, this makes no sense and + // the bits are used for storing index of an edge. + assert(number_of_split_sides() == 0); + division_type = type | (type << 2); +} + + + +int TriangleSelector::DivisionNode::side_to_keep() const +{ + assert(number_of_split_sides() == 2); + return (division_type & 0b1100) >> 2; +} + + + +int TriangleSelector::DivisionNode::side_to_split() const +{ + assert(number_of_split_sides() == 1); + return (division_type & 0b1100) >> 2; +} + + + +FacetSupportType TriangleSelector::DivisionNode::get_type() const +{ + assert(number_of_split_sides() == 0); // this must be leaf + return (division_type & 0b1100) >> 2; +} + } // namespace GUI } // namespace Slic3r diff --git a/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.hpp b/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.hpp index c4f5b153e..f815a8063 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.hpp @@ -19,6 +19,77 @@ namespace GUI { enum class SLAGizmoEventType : unsigned char; class ClippingPlane; + +// Following class holds information about selected triangles. It also has power +// to recursively subdivide the triangles and make the selection finer. +class TriangleSelector { +public: + void test(); + explicit TriangleSelector(const TriangleMesh& mesh) {} + + // Select all triangles inside the circle, subdivide where needed. + void select_patch(const Vec3f& hit, // point where to start + int facet_idx, // facet that point belongs to + const Vec3f& dir, // direction of the ray + float radius_sqr, // squared radius of the cursor + bool enforcer); // enforcer or blocker? + + void unselect_all(); + + // Remove all unnecessary data (such as vertices that are not needed + // because the selection has been made larger. + void garbage_collect(); + +private: + // A struct to hold information about how a triangle was divided. + struct DivisionNode { + // Index of triangle this describes. + int triangle_idx; + + // Bitmask encoding which sides are split. + unsigned char division_type; + // bits 0 and 1 : 00 - no division + // 01 - one-edge split + // 10 - two-edge split + // 11 - three-edge split + // bits 2 and 3 : decimal 0, 1 or 2 identifying the special edge (one that + // splits in one-edge split or one that stays in two-edge split). + + // Pointers to children nodes (not all are always used). + std::array children; + + // Set the division type. + void set_division(int sides_to_split, int special_side_idx = -1); + + // Helpers that decode the division_type bitmask. + int number_of_split_sides() const { return division_type & 0b11; } + int side_to_keep() const; + int side_to_split() const; + }; + + // Triangle and pointer to how it's divided (nullptr = not divided). + // The ptr is nullptr for all new triangles, it is only valid for + // the original (undivided) triangles. + struct Triangle { + stl_triangle_vertex_indices verts_idxs; + DivisionNode* div_info; + }; + + // Lists of vertices and triangles, both original and new + std::vector m_vertices; + std::vector m_triangles; + + // Number of original vertices and triangles. + int m_orig_size_vertices; + int m_orig_size_indices; + + // Limits for stopping the recursion. + float m_max_edge_length; + int m_max_recursion_depth; +}; + + + class GLGizmoFdmSupports : public GLGizmoBase { private: From d2b2446b077fb90b7211cc4a2a3e20e1f02e7fdc Mon Sep 17 00:00:00 2001 From: Lukas Matena Date: Tue, 16 Jun 2020 16:10:26 +0200 Subject: [PATCH 05/24] TriangleSelector: first partially working implementation --- src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.cpp | 500 ++++++++++++++----- src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.hpp | 65 ++- 2 files changed, 422 insertions(+), 143 deletions(-) diff --git a/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.cpp b/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.cpp index 00e236b64..1b045f38e 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.cpp @@ -16,7 +16,6 @@ namespace Slic3r { namespace GUI { -static constexpr size_t MaxVertexBuffers = 50; GLGizmoFdmSupports::GLGizmoFdmSupports(GLCanvas3D& parent, const std::string& icon_filename, unsigned int sprite_id) : GLGizmoBase(parent, icon_filename, sprite_id) @@ -90,12 +89,13 @@ void GLGizmoFdmSupports::set_fdm_support_data(ModelObject* model_object, const S void GLGizmoFdmSupports::on_render() const { - const Selection& selection = m_parent.get_selection(); + //const Selection& selection = m_parent.get_selection(); glsafe(::glEnable(GL_BLEND)); glsafe(::glEnable(GL_DEPTH_TEST)); - render_triangles(selection); + //render_triangles(selection); + m_triangle_selector->render(); m_c->object_clipper()->render_cut(); render_cursor_circle(); @@ -146,13 +146,13 @@ void GLGizmoFdmSupports::render_triangles(const Selection& selection) const glsafe(::glMultMatrixd(trafo_matrix.data())); // Now render both enforcers and blockers. - for (int i=0; i<2; ++i) { - glsafe(::glColor4f(i ? 1.f : 0.2f, 0.2f, i ? 0.2f : 1.0f, 0.5f)); - for (const GLIndexedVertexArray& iva : m_ivas[mesh_id][i]) { - if (iva.has_VBOs()) - iva.render(); - } - } + //for (int i=0; i<2; ++i) { + // glsafe(::glColor4f(i ? 1.f : 0.2f, 0.2f, i ? 0.2f : 1.0f, 0.5f)); + // for (const GLIndexedVertexArray& iva : m_ivas[mesh_id][i]) { + if (m_iva.has_VBOs()) + m_iva.render(); + // } + //} glsafe(::glPopMatrix()); if (is_left_handed) glsafe(::glFrontFace(GL_CCW)); @@ -209,7 +209,8 @@ void GLGizmoFdmSupports::render_cursor_circle() const void GLGizmoFdmSupports::update_model_object() const { - ModelObject* mo = m_c->selection_info()->model_object(); + return; + /*ModelObject* mo = m_c->selection_info()->model_object(); int idx = -1; for (ModelVolume* mv : mo->volumes) { ++idx; @@ -217,7 +218,7 @@ void GLGizmoFdmSupports::update_model_object() const continue; for (int i=0; im_supported_facets.set_facet(i, m_selected_facets[idx][i]); - } + }*/ } @@ -226,13 +227,15 @@ void GLGizmoFdmSupports::update_from_model_object() wxBusyCursor wait; const ModelObject* mo = m_c->selection_info()->model_object(); - size_t num_of_volumes = 0; + /*size_t num_of_volumes = 0; for (const ModelVolume* mv : mo->volumes) if (mv->is_model_part()) ++num_of_volumes; - m_selected_facets.resize(num_of_volumes); + m_selected_facets.resize(num_of_volumes);*/ - m_ivas.clear(); + m_triangle_selector = std::make_unique(mo->volumes.front()->mesh()); + + /*m_ivas.clear(); m_ivas.resize(num_of_volumes); for (size_t i=0; ivolumes) { if (! mv->is_model_part()) continue; ++mesh_id; if (mesh_id == closest_hit_mesh_id) { - mesh = &mv->mesh(); + //mesh = &mv->mesh(); break; } } - bool update_both = false; + // FIXME: just for now, only process first mesh + if (mesh_id != 0) + return false; const Transform3d& trafo_matrix = trafo_matrices[mesh_id]; @@ -426,80 +435,10 @@ bool GLGizmoFdmSupports::gizmo_event(SLAGizmoEventType action, const Vec2d& mous const float avg_scaling = (sf(0) + sf(1) + sf(2))/3.; const float limit = pow(m_cursor_radius/avg_scaling , 2.f); - const std::pair& hit_and_facet = { closest_hit, closest_facet }; - // Calculate direction from camera to the hit (in mesh coords): - Vec3f dir = ((trafo_matrix.inverse() * camera.get_position()).cast() - hit_and_facet.first).normalized(); + Vec3f dir = ((trafo_matrix.inverse() * camera.get_position()).cast() - closest_hit).normalized(); - // A lambda to calculate distance from the centerline: - auto squared_distance_from_line = [&hit_and_facet, &dir](const Vec3f& point) -> float { - Vec3f diff = hit_and_facet.first - point; - return (diff - diff.dot(dir) * dir).squaredNorm(); - }; - - // A lambda to determine whether this facet is potentionally visible (still can be obscured) - auto faces_camera = [&dir, &mesh](const size_t& facet) -> bool { - return (mesh->stl.facet_start[facet].normal.dot(dir) > 0.); - }; - // Now start with the facet the pointer points to and check all adjacent facets. - std::vector facets_to_select{hit_and_facet.second}; - std::vector visited(m_selected_facets[mesh_id].size(), false); // keep track of facets we already processed - size_t facet_idx = 0; // index into facets_to_select - while (facet_idx < facets_to_select.size()) { - size_t facet = facets_to_select[facet_idx]; - if (! visited[facet]) { - // check all three vertices and in case they're close enough, - // add neighboring facets to be proccessed later - for (size_t i=0; i<3; ++i) { - float dist = squared_distance_from_line( - mesh->its.vertices[mesh->its.indices[facet](i)]); - if (dist < limit) { - for (int n=0; n<3; ++n) { - if (faces_camera(mesh->stl.neighbors_start[facet].neighbor[n])) - facets_to_select.push_back(mesh->stl.neighbors_start[facet].neighbor[n]); - } - } - } - visited[facet] = true; - } - ++facet_idx; - } - - std::vector new_facets; - new_facets.reserve(facets_to_select.size()); - - // Now just select all facets that passed and remember which - // ones have really changed state. - for (size_t next_facet : facets_to_select) { - FacetSupportType& facet = m_selected_facets[mesh_id][next_facet]; - - if (facet != new_state) { - if (facet != FacetSupportType::NONE) { - // this triangle is currently in the other VBA. - // Both VBAs need to be refreshed. - update_both = true; - } - facet = new_state; - new_facets.push_back(next_facet); - } - } - - if (! new_facets.empty()) { - if (new_state != FacetSupportType::NONE) { - // append triangles into the respective VBA - update_vertex_buffers(mesh, mesh_id, new_state, &new_facets); - if (update_both) { - auto other = new_state == FacetSupportType::ENFORCER - ? FacetSupportType::BLOCKER - : FacetSupportType::ENFORCER; - update_vertex_buffers(mesh, mesh_id, other); // regenerate the other VBA - } - } - else { - update_vertex_buffers(mesh, mesh_id, FacetSupportType::ENFORCER); - update_vertex_buffers(mesh, mesh_id, FacetSupportType::BLOCKER); - } - } + m_triangle_selector->select_patch(closest_hit, closest_facet, dir, limit, new_state); return true; } @@ -529,7 +468,7 @@ void GLGizmoFdmSupports::update_vertex_buffers(const TriangleMesh* mesh, FacetSupportType type, const std::vector* new_facets) { - std::vector& ivas = m_ivas[mesh_id][type == FacetSupportType::ENFORCER ? 0 : 1]; + //std::vector& ivas = m_ivas[mesh_id][type == FacetSupportType::ENFORCER ? 0 : 1]; // lambda to push facet into vertex buffer auto push_facet = [this, &mesh, &mesh_id](size_t idx, GLIndexedVertexArray& iva) { @@ -543,24 +482,26 @@ void GLGizmoFdmSupports::update_vertex_buffers(const TriangleMesh* mesh, }; - if (ivas.size() == MaxVertexBuffers || ! new_facets) { + //if (ivas.size() == MaxVertexBuffers || ! new_facets) { // If there are too many or they should be regenerated, make one large // GLVertexBufferArray. - ivas.clear(); // destructors release geometry - ivas.push_back(GLIndexedVertexArray()); + //ivas.clear(); // destructors release geometry + //ivas.push_back(GLIndexedVertexArray()); + + m_iva.release_geometry(); + m_iva.clear(); bool pushed = false; for (size_t facet_idx=0; facet_idxselection_info()->model_object(); @@ -615,6 +558,7 @@ void GLGizmoFdmSupports::select_facets_by_angle(float threshold_deg, bool overwr update_model_object(); m_parent.set_as_dirty(); m_setting_angle = false; +*/ } @@ -670,7 +614,7 @@ void GLGizmoFdmSupports::on_render_input_window(float x, float y, float bottom_l ImGui::SameLine(); if (m_imgui->button(m_desc.at("remove_all"))) { - ModelObject* mo = m_c->selection_info()->model_object(); + /*ModelObject* mo = m_c->selection_info()->model_object(); int idx = -1; for (ModelVolume* mv : mo->volumes) { ++idx; @@ -681,7 +625,7 @@ void GLGizmoFdmSupports::on_render_input_window(float x, float y, float bottom_l update_vertex_buffers(&mv->mesh(), idx, FacetSupportType::BLOCKER); m_parent.set_as_dirty(); } - } + }*/ } const float max_tooltip_width = ImGui::GetFontSize() * 20.0f; @@ -805,10 +749,6 @@ void GLGizmoFdmSupports::on_set_state() activate_internal_undo_redo_stack(true); }); } - - TriangleSelector ts{TriangleMesh()}; - ts.test(); - } if (m_state == Off && m_old_state != Off) { // the gizmo was just turned Off // we are actually shutting down @@ -818,7 +758,7 @@ void GLGizmoFdmSupports::on_set_state() } activate_internal_undo_redo_stack(false); m_old_mo_id = -1; - m_ivas.clear(); + m_iva.release_geometry(); m_selected_facets.clear(); } m_old_state = m_state; @@ -858,22 +798,6 @@ void GLGizmoFdmSupports::on_save(cereal::BinaryOutputArchive&) const -void TriangleSelector::test() -{ - DivisionNode node; - while (true) { - std::cout << "Zadej pocet stran a spec stranu: "; - int num; - int spec; - std::cin >> num >> spec; - node.set_division(num, spec); - std::cout << node.number_of_split_sides() << " " - << (node.number_of_split_sides()==1 ? node.side_to_split() : node.side_to_keep()) - << std::endl << std::endl; - } -} - - void TriangleSelector::DivisionNode::set_division(int sides_to_split, int special_side_idx) { assert(sides_to_split >=0 && sides_to_split <= 3); @@ -883,17 +807,17 @@ void TriangleSelector::DivisionNode::set_division(int sides_to_split, int specia assert(sides_to_split != 1 || special_side_idx != -1); assert(sides_to_split != 2 || special_side_idx != -1); - division_type = sides_to_split | (special_side_idx != -1 ? (special_side_idx << 2) : 0 ); + division_type = sides_to_split | ((special_side_idx != -1 ? special_side_idx : 0 ) <<2); } -void TriangleSelector::DivisionNode::set_type(FacetSupportType type) +void TriangleSelector::DivisionNode::set_state(FacetSupportType type) { // If this is not a leaf-node, this makes no sense and // the bits are used for storing index of an edge. assert(number_of_split_sides() == 0); - division_type = type | (type << 2); + division_type = (int8_t(type) << 2); } @@ -914,10 +838,326 @@ int TriangleSelector::DivisionNode::side_to_split() const -FacetSupportType TriangleSelector::DivisionNode::get_type() const +FacetSupportType TriangleSelector::DivisionNode::get_state() const { assert(number_of_split_sides() == 0); // this must be leaf - return (division_type & 0b1100) >> 2; + return FacetSupportType((division_type & 0b1100) >> 2); +} + + + +void TriangleSelector::select_patch(const Vec3f& hit, int facet_start, const Vec3f& dir, + float radius_sqr, FacetSupportType new_state) +{ + assert(facet_start < m_orig_size_indices); + + // Save current cursor center, squared radius and camera direction, + // so we don't have to pass it around. + m_cursor = {hit, dir, radius_sqr}; + + // Now start with the facet the pointer points to and check all adjacent facets. + std::vector facets_to_check{facet_start}; + std::vector visited(m_orig_size_indices, false); // keep track of facets we already processed + int facet_idx = 0; // index into facets_to_check + while (facet_idx < int(facets_to_check.size())) { + int facet = facets_to_check[facet_idx]; + if (! visited[facet]) { + int num_of_inside_vertices = vertices_inside(facet); + // select the facet... + select_triangle(facet, new_state, num_of_inside_vertices, facet == facet_start); + + // ...and add neighboring facets to be proccessed later + for (int n=0; n<3; ++n) { + if (faces_camera(m_mesh->stl.neighbors_start[facet].neighbor[n])) + facets_to_check.push_back(m_mesh->stl.neighbors_start[facet].neighbor[n]); + } + } + visited[facet] = true; + ++facet_idx; + } +} + + + +// Selects either the whole triangle (discarding any children it has), or divides +// the triangle recursively, selecting just subtriangles truly inside the circle. +// This is done by an actual recursive call. +void TriangleSelector::select_triangle(int facet_idx, FacetSupportType type, + int num_of_inside_vertices, bool cursor_inside) +{ + assert(facet_idx < m_triangles.size()); +//cursor_inside=false; + if (num_of_inside_vertices == -1) + num_of_inside_vertices = vertices_inside(facet_idx); + + if (num_of_inside_vertices == 0 && ! cursor_inside) + return; // FIXME: just an edge can be inside + + if (num_of_inside_vertices == 3) { + // dump any subdivision and select whole triangle + undivide_triangle(facet_idx); + m_triangles[facet_idx].div_info->set_state(type); + } else { + // the triangle is partially inside, let's recursively divide it + // (if not already) and try selecting its children. + split_triangle(facet_idx); + assert(facet_idx < m_triangles.size()); + int num_of_children = m_triangles[facet_idx].div_info->number_of_split_sides() + 1; + if (num_of_children != 1) { + for (int i=0; ichildren[i], type, -1, cursor_inside); + } + } + } + //if (m_triangles[facet_idx].div_info->number_of_split_sides() != 0) + // remove_needless(m_triangles[facet_idx].div_info->children[0]); +} + + +bool TriangleSelector::split_triangle(int facet_idx) +{ + if (m_triangles[facet_idx].div_info->number_of_split_sides() != 0) { + // The triangle was divided already. + return 0; + } + + FacetSupportType old_type = m_triangles[facet_idx].div_info->get_state(); + + const double limit_squared = 4; + + stl_triangle_vertex_indices& facet = m_triangles[facet_idx].verts_idxs; + const stl_vertex* pts[3] = { &m_vertices[facet[0]], &m_vertices[facet[1]], &m_vertices[facet[2]]}; + double sides[3] = { (*pts[2]-*pts[1]).squaredNorm(), (*pts[0]-*pts[2]).squaredNorm(), (*pts[1]-*pts[0]).squaredNorm() }; + + std::vector sides_to_split; + int side_to_keep = -1; + for (int pt_idx = 0; pt_idx<3; ++pt_idx) { + if (sides[pt_idx] > limit_squared) + sides_to_split.push_back(pt_idx); + else + side_to_keep = pt_idx; + } + if (sides_to_split.empty()) { + m_triangles[facet_idx].div_info->set_division(0); + return 0; + } + + // indices of triangle vertices + std::vector verts_idxs; + int idx = sides_to_split.size() == 2 ? side_to_keep : sides_to_split[0]; + for (int j=0; j<3; ++j) { + verts_idxs.push_back(facet[idx++]); + if (idx == 3) + idx = 0; + } + + + if (sides_to_split.size() == 1) { + m_vertices.emplace_back((m_vertices[verts_idxs[1]] + m_vertices[verts_idxs[2]])/2.); + verts_idxs.insert(verts_idxs.begin()+2, m_vertices.size() - 1); + + m_triangles.emplace_back(verts_idxs[0], verts_idxs[1], verts_idxs[2]); + m_triangles.emplace_back(verts_idxs[2], verts_idxs[3], verts_idxs[0]); + } + + if (sides_to_split.size() == 2) { + m_vertices.emplace_back((m_vertices[verts_idxs[0]] + m_vertices[verts_idxs[1]])/2.); + verts_idxs.insert(verts_idxs.begin()+1, m_vertices.size() - 1); + + m_vertices.emplace_back((m_vertices[verts_idxs[0]] + m_vertices[verts_idxs[3]])/2.); + verts_idxs.insert(verts_idxs.begin()+4, m_vertices.size() - 1); + + m_triangles.emplace_back(verts_idxs[0], verts_idxs[1], verts_idxs[4]); + m_triangles.emplace_back(verts_idxs[1], verts_idxs[2], verts_idxs[4]); + m_triangles.emplace_back(verts_idxs[2], verts_idxs[3], verts_idxs[4]); + } + + if (sides_to_split.size() == 3) { + m_vertices.emplace_back((m_vertices[verts_idxs[0]] + m_vertices[verts_idxs[1]])/2.); + verts_idxs.insert(verts_idxs.begin()+1, m_vertices.size() - 1); + m_vertices.emplace_back((m_vertices[verts_idxs[2]] + m_vertices[verts_idxs[3]])/2.); + verts_idxs.insert(verts_idxs.begin()+3, m_vertices.size() - 1); + m_vertices.emplace_back((m_vertices[verts_idxs[4]] + m_vertices[verts_idxs[0]])/2.); + verts_idxs.insert(verts_idxs.begin()+5, m_vertices.size() - 1); + + m_triangles.emplace_back(verts_idxs[0], verts_idxs[1], verts_idxs[5]); + m_triangles.emplace_back(verts_idxs[1], verts_idxs[2], verts_idxs[3]); + m_triangles.emplace_back(verts_idxs[3], verts_idxs[4], verts_idxs[5]); + m_triangles.emplace_back(verts_idxs[1], verts_idxs[3], verts_idxs[5]); + } + + // Save how the triangle was split. Second argument makes sense only for one + // or two split sides, otherwise the value is ignored. + m_triangles[facet_idx].div_info->set_division(sides_to_split.size(), + sides_to_split.size() == 2 ? side_to_keep : sides_to_split[0]); + + // And save the children. All children should start in the same state as the triangle we just split. + assert(! sides_to_split.empty() && int(sides_to_split.size()) <= 3); + for (int i=0; i<=int(sides_to_split.size()); ++i) { + m_triangles[facet_idx].div_info->children[i] = m_triangles.size()-1-i; + m_triangles[m_triangles.size()-1-i].div_info->parent = facet_idx; + m_triangles[m_triangles[facet_idx].div_info->children[i]].div_info->set_state(old_type); + } + + +#ifndef NDEBUG + int split_sides = m_triangles[facet_idx].div_info->number_of_split_sides(); + if (split_sides != 0) { + // check that children are range + for (int i=0; i<=split_sides; ++i) + assert(m_triangles[facet_idx].div_info->children[i] >= 0 && m_triangles[facet_idx].div_info->children[i] < int(m_triangles.size())); + + } +#endif + + return 1; +} + + +// Calculate distance of a point from a line. +bool TriangleSelector::is_point_inside_cursor(const Vec3f& point) const +{ + Vec3f diff = m_cursor.center - point; + return (diff - diff.dot(m_cursor.dir) * m_cursor.dir).squaredNorm() < m_cursor.radius_sqr; +} + + + +// Determine whether this facet is potentially visible (still can be obscured). +bool TriangleSelector::faces_camera(int facet) const +{ + assert(facet < m_orig_size_indices); + // The normal is cached in mesh->stl, use it. + return (m_mesh->stl.facet_start[facet].normal.dot(m_cursor.dir) > 0.); +} + + + +// How many vertices of a triangle are inside the circle? +int TriangleSelector::vertices_inside(int facet_idx) const +{ + int inside = 0; + for (size_t i=0; i<3; ++i) { + if (is_point_inside_cursor(m_vertices[m_triangles[facet_idx].verts_idxs[i]])) + ++inside; + } + return inside; +} + + +// Is mouse pointer inside a triangle? +/*bool TriangleSelector::is_pointer_inside_triangle(int facet_idx) const +{ + +}*/ + + + +// Recursively remove all subtriangles. +void TriangleSelector::undivide_triangle(int facet_idx) +{ + assert(facet_idx < m_triangles.size()); + auto& dn_ptr = m_triangles[facet_idx].div_info; + assert(dn_ptr); + + if (dn_ptr->number_of_split_sides() != 0) { + for (int i=0; i<=dn_ptr->number_of_split_sides(); ++i) { + undivide_triangle(dn_ptr->children[i]); + m_triangles[dn_ptr->children[i]].div_info->valid = false; + } + } + + dn_ptr->set_division(0); // not split +} + + +void TriangleSelector::remove_needless(int child_facet) +{ + assert(m_triangles[child_facet].div_info->number_of_split_sides() == 0); + int parent = m_triangles[child_facet].div_info->parent; + if (parent == -1) + return; // root + // Check type of all valid children. + FacetSupportType type = m_triangles[m_triangles[parent].div_info->children[0]].div_info->get_state(); + for (int i=0; i<=m_triangles[parent].div_info->number_of_split_sides(); ++i) + if (m_triangles[m_triangles[parent].div_info->children[0]].div_info->get_state() != type) + return; // not all children are the same + + // All children are the same, let's kill them. + undivide_triangle(parent); + m_triangles[parent].div_info->set_state(type); + + // And not try the same for grandparent. + remove_needless(parent); +} + + +TriangleSelector::TriangleSelector(const TriangleMesh& mesh) +{ + for (const stl_vertex& vert : mesh.its.vertices) + m_vertices.push_back(vert); + for (const stl_triangle_vertex_indices& ind : mesh.its.indices) + m_triangles.emplace_back(Triangle(ind[0], ind[1], ind[2])); + m_orig_size_vertices = m_vertices.size(); + m_orig_size_indices = m_triangles.size(); + m_mesh = &mesh; +} + + +void TriangleSelector::render() const +{ + ::glColor3f(0.f, 0.f, 1.f); + ::glPolygonMode( GL_FRONT_AND_BACK, GL_LINE ); + + Vec3d offset = wxGetApp().model().objects.front()->instances.front()->get_transformation().get_offset(); + ::glTranslatef(offset.x(), offset.y(), offset.z()); + ::glScalef(1.01f, 1.01f, 1.01f); + + ::glBegin( GL_TRIANGLES); + + for (int tr_id=0; tr_idvalid) { + for (int i=0; i<3; ++i) + ::glVertex3f(m_vertices[tr.verts_idxs[i]][0], m_vertices[tr.verts_idxs[i]][1], m_vertices[tr.verts_idxs[i]][2]); + } + } + ::glEnd(); + ::glPolygonMode( GL_FRONT_AND_BACK, GL_FILL ); + + ::glBegin( GL_TRIANGLES); + for (int tr_id=0; tr_idvalid) + continue; + + if (tr.div_info->number_of_split_sides() == 0) { + if (tr.div_info->get_state() == FacetSupportType::ENFORCER) + ::glColor4f(0.f, 0.f, 1.f, 0.2f); + else if (tr.div_info->get_state() == FacetSupportType::BLOCKER) + ::glColor4f(1.f, 0.f, 0.f, 0.2f); + else + continue; + } + else + continue; + + + if (tr.div_info->valid) { + for (int i=0; i<3; ++i) + ::glVertex3f(m_vertices[tr.verts_idxs[i]][0], m_vertices[tr.verts_idxs[i]][1], m_vertices[tr.verts_idxs[i]][2]); + } + } + ::glEnd(); +} + + +TriangleSelector::DivisionNode::DivisionNode() +{ + set_division(0); + set_state(FacetSupportType::NONE); } } // namespace GUI diff --git a/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.hpp b/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.hpp index f815a8063..07c2e86d5 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.hpp @@ -24,15 +24,17 @@ class ClippingPlane; // to recursively subdivide the triangles and make the selection finer. class TriangleSelector { public: - void test(); - explicit TriangleSelector(const TriangleMesh& mesh) {} + void render() const; + // Create new object on a TriangleMesh. The referenced mesh must + // stay valid, a ptr to it is saved and used. + explicit TriangleSelector(const TriangleMesh& mesh); // Select all triangles inside the circle, subdivide where needed. void select_patch(const Vec3f& hit, // point where to start - int facet_idx, // facet that point belongs to + int facet_start, // facet that point belongs to const Vec3f& dir, // direction of the ray float radius_sqr, // squared radius of the cursor - bool enforcer); // enforcer or blocker? + FacetSupportType new_state); // enforcer or blocker? void unselect_all(); @@ -43,11 +45,15 @@ public: private: // A struct to hold information about how a triangle was divided. struct DivisionNode { + DivisionNode(); // Index of triangle this describes. - int triangle_idx; + bool valid{true}; + + // Index of parent triangle (-1: original) + int parent{-1}; // Bitmask encoding which sides are split. - unsigned char division_type; + int8_t division_type; // bits 0 and 1 : 00 - no division // 01 - one-edge split // 10 - two-edge split @@ -55,29 +61,37 @@ private: // bits 2 and 3 : decimal 0, 1 or 2 identifying the special edge (one that // splits in one-edge split or one that stays in two-edge split). - // Pointers to children nodes (not all are always used). - std::array children; + // Children triangles (0 = no child) + std::array children; // Set the division type. void set_division(int sides_to_split, int special_side_idx = -1); + void set_state(FacetSupportType state); // Helpers that decode the division_type bitmask. int number_of_split_sides() const { return division_type & 0b11; } int side_to_keep() const; int side_to_split() const; + + FacetSupportType get_state() const; }; // Triangle and pointer to how it's divided (nullptr = not divided). // The ptr is nullptr for all new triangles, it is only valid for // the original (undivided) triangles. struct Triangle { + Triangle(int a, int b, int c) + : verts_idxs{stl_triangle_vertex_indices(a, b, c)}, + div_info{std::make_unique()} + {} stl_triangle_vertex_indices verts_idxs; - DivisionNode* div_info; + std::unique_ptr div_info; }; // Lists of vertices and triangles, both original and new std::vector m_vertices; std::vector m_triangles; + const TriangleMesh* m_mesh; // Number of original vertices and triangles. int m_orig_size_vertices; @@ -86,6 +100,32 @@ private: // Limits for stopping the recursion. float m_max_edge_length; int m_max_recursion_depth; + + // Caches for cursor position, radius and direction. + struct Cursor { + Vec3f center; + Vec3f dir; + float radius_sqr; + }; + + Cursor m_cursor; + + // Private functions: + void select_triangle(int facet_idx, FacetSupportType type, + int num_of_inside_vertices = -1, + bool cursor_inside = false); + + bool is_point_inside_cursor(const Vec3f& point) const; + + int vertices_inside(int facet_idx) const; + + bool faces_camera(int facet) const; + + void undivide_triangle(int facet_idx); + + bool split_triangle(int facet_idx); + + void remove_needless(int child_facet); }; @@ -107,10 +147,7 @@ private: // individual facets (one of the enum values above). std::vector> m_selected_facets; - // Vertex buffer arrays for each model-part volume. There is a vector of - // arrays so that adding triangles can be done without regenerating all - // other triangles. Enforcers and blockers are of course separate. - std::vector, 2>> m_ivas; + GLIndexedVertexArray m_iva; void update_vertex_buffers(const TriangleMesh* mesh, int mesh_id, @@ -148,6 +185,8 @@ private: bool m_setting_angle = false; bool m_internal_stack_active = false; bool m_schedule_update = false; + + std::unique_ptr m_triangle_selector; // 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. From c3db84e3821d7c94fb4f615cbc7bedf10695a59b Mon Sep 17 00:00:00 2001 From: Lukas Matena Date: Fri, 19 Jun 2020 09:20:58 +0200 Subject: [PATCH 06/24] TriangleSelector: Improvements --- src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.cpp | 254 ++++++++++--------- src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.hpp | 70 ++--- 2 files changed, 176 insertions(+), 148 deletions(-) diff --git a/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.cpp b/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.cpp index 1b045f38e..1c77f437c 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.cpp @@ -436,9 +436,11 @@ bool GLGizmoFdmSupports::gizmo_event(SLAGizmoEventType action, const Vec2d& mous const float limit = pow(m_cursor_radius/avg_scaling , 2.f); // Calculate direction from camera to the hit (in mesh coords): - Vec3f dir = ((trafo_matrix.inverse() * camera.get_position()).cast() - closest_hit).normalized(); + Vec3f camera_pos = (trafo_matrix.inverse() * camera.get_position()).cast(); + Vec3f dir = (closest_hit - camera_pos).normalized(); - m_triangle_selector->select_patch(closest_hit, closest_facet, dir, limit, new_state); + m_triangle_selector->select_patch(closest_hit, closest_facet, camera_pos, + dir, limit, new_state); return true; } @@ -567,6 +569,12 @@ void GLGizmoFdmSupports::on_render_input_window(float x, float y, float bottom_l if (! m_c->selection_info()->model_object()) return; + m_imgui->begin(std::string("TriangleSelector DEBUG"), ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoCollapse); + static float edge_limit = 1.f; + m_imgui->slider_float("Edge limit (mm): ", &edge_limit, 0.1f, 8.f); + m_triangle_selector->set_edge_limit(edge_limit); + m_imgui->end(); + const float approx_height = m_imgui->scaled(18.0f); y = std::min(y, bottom_limit - approx_height); m_imgui->set_next_window_pos(x, y, ImGuiCond_Always); @@ -798,7 +806,7 @@ void GLGizmoFdmSupports::on_save(cereal::BinaryOutputArchive&) const -void TriangleSelector::DivisionNode::set_division(int sides_to_split, int special_side_idx) +void TriangleSelector::Triangle::set_division(int sides_to_split, int special_side_idx) { assert(sides_to_split >=0 && sides_to_split <= 3); assert(special_side_idx >=-1 && special_side_idx < 3); @@ -812,48 +820,49 @@ void TriangleSelector::DivisionNode::set_division(int sides_to_split, int specia -void TriangleSelector::DivisionNode::set_state(FacetSupportType type) +void TriangleSelector::Triangle::set_state(FacetSupportType type) { // If this is not a leaf-node, this makes no sense and // the bits are used for storing index of an edge. - assert(number_of_split_sides() == 0); + assert(! is_split()); division_type = (int8_t(type) << 2); } -int TriangleSelector::DivisionNode::side_to_keep() const +int TriangleSelector::Triangle::side_to_keep() const { assert(number_of_split_sides() == 2); - return (division_type & 0b1100) >> 2; + return division_type >> 2; } -int TriangleSelector::DivisionNode::side_to_split() const +int TriangleSelector::Triangle::side_to_split() const { assert(number_of_split_sides() == 1); - return (division_type & 0b1100) >> 2; + return division_type >> 2; } -FacetSupportType TriangleSelector::DivisionNode::get_state() const +FacetSupportType TriangleSelector::Triangle::get_state() const { - assert(number_of_split_sides() == 0); // this must be leaf - return FacetSupportType((division_type & 0b1100) >> 2); + assert(! is_split()); // this must be leaf + return FacetSupportType(division_type >> 2); } -void TriangleSelector::select_patch(const Vec3f& hit, int facet_start, const Vec3f& dir, +void TriangleSelector::select_patch(const Vec3f& hit, int facet_start, + const Vec3f& source, const Vec3f& dir, float radius_sqr, FacetSupportType new_state) { assert(facet_start < m_orig_size_indices); // Save current cursor center, squared radius and camera direction, // so we don't have to pass it around. - m_cursor = {hit, dir, radius_sqr}; + m_cursor = {hit, source, dir, radius_sqr}; // Now start with the facet the pointer points to and check all adjacent facets. std::vector facets_to_check{facet_start}; @@ -862,14 +871,12 @@ void TriangleSelector::select_patch(const Vec3f& hit, int facet_start, const Vec while (facet_idx < int(facets_to_check.size())) { int facet = facets_to_check[facet_idx]; if (! visited[facet]) { - int num_of_inside_vertices = vertices_inside(facet); - // select the facet... - select_triangle(facet, new_state, num_of_inside_vertices, facet == facet_start); - - // ...and add neighboring facets to be proccessed later - for (int n=0; n<3; ++n) { - if (faces_camera(m_mesh->stl.neighbors_start[facet].neighbor[n])) - facets_to_check.push_back(m_mesh->stl.neighbors_start[facet].neighbor[n]); + if (select_triangle(facet, new_state, facet == facet_start)) { + // add neighboring facets to list to be proccessed later + for (int n=0; n<3; ++n) { + if (faces_camera(m_mesh->stl.neighbors_start[facet].neighbor[n])) + facets_to_check.push_back(m_mesh->stl.neighbors_start[facet].neighbor[n]); + } } } visited[facet] = true; @@ -879,53 +886,71 @@ void TriangleSelector::select_patch(const Vec3f& hit, int facet_start, const Vec -// Selects either the whole triangle (discarding any children it has), or divides +// 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. -void TriangleSelector::select_triangle(int facet_idx, FacetSupportType type, - int num_of_inside_vertices, bool cursor_inside) +// This is done by an actual recursive call. Returns false if the triangle is +// outside the cursor. +bool TriangleSelector::select_triangle(int facet_idx, FacetSupportType type, bool cursor_inside) { - assert(facet_idx < m_triangles.size()); -//cursor_inside=false; - if (num_of_inside_vertices == -1) - num_of_inside_vertices = vertices_inside(facet_idx); + bool out = false; + assert(facet_idx < int(m_triangles.size())); + + Triangle& tr = m_triangles[facet_idx]; + if (! tr.valid) + return false; + + cursor_inside = is_pointer_in_triangle(facet_idx); + + int num_of_inside_vertices = vertices_inside(facet_idx); if (num_of_inside_vertices == 0 && ! cursor_inside) - return; // FIXME: just an edge can be inside + return out; // FIXME: just an edge can be inside if (num_of_inside_vertices == 3) { // dump any subdivision and select whole triangle undivide_triangle(facet_idx); - m_triangles[facet_idx].div_info->set_state(type); + tr.set_state(type); } else { // the triangle is partially inside, let's recursively divide it // (if not already) and try selecting its children. + + + if (! tr.is_split() && tr.get_state() == type) { + // This is leaf triangle that is already of correct type as a whole. + // No need to split, all children would end up selected anyway. + return true; + } + split_triangle(facet_idx); - assert(facet_idx < m_triangles.size()); - int num_of_children = m_triangles[facet_idx].div_info->number_of_split_sides() + 1; + assert(facet_idx < int(m_triangles.size())); + int num_of_children = tr.number_of_split_sides() + 1; if (num_of_children != 1) { - for (int i=0; ichildren[i], type, -1, cursor_inside); - } + for (int i=0; inumber_of_split_sides() != 0) - // remove_needless(m_triangles[facet_idx].div_info->children[0]); + + // In case that all siblings are leafs and have the same state now, + // they may be removed and substituted by the parent triangle. + //remove_if_needless(facet_idx); + return true; } bool TriangleSelector::split_triangle(int facet_idx) { - if (m_triangles[facet_idx].div_info->number_of_split_sides() != 0) { + if (m_triangles[facet_idx].is_split()) { // The triangle was divided already. - return 0; + return false; } - FacetSupportType old_type = m_triangles[facet_idx].div_info->get_state(); + Triangle& tr = m_triangles[facet_idx]; - const double limit_squared = 4; + FacetSupportType old_type = tr.get_state(); - stl_triangle_vertex_indices& facet = m_triangles[facet_idx].verts_idxs; + const double limit_squared = m_edge_limit_sqr; + + stl_triangle_vertex_indices& facet = tr.verts_idxs; const stl_vertex* pts[3] = { &m_vertices[facet[0]], &m_vertices[facet[1]], &m_vertices[facet[2]]}; double sides[3] = { (*pts[2]-*pts[1]).squaredNorm(), (*pts[0]-*pts[2]).squaredNorm(), (*pts[1]-*pts[0]).squaredNorm() }; @@ -938,8 +963,8 @@ bool TriangleSelector::split_triangle(int facet_idx) side_to_keep = pt_idx; } if (sides_to_split.empty()) { - m_triangles[facet_idx].div_info->set_division(0); - return 0; + tr.set_division(0); + return false; } // indices of triangle vertices @@ -988,29 +1013,18 @@ bool TriangleSelector::split_triangle(int facet_idx) // Save how the triangle was split. Second argument makes sense only for one // or two split sides, otherwise the value is ignored. - m_triangles[facet_idx].div_info->set_division(sides_to_split.size(), + tr.set_division(sides_to_split.size(), sides_to_split.size() == 2 ? side_to_keep : sides_to_split[0]); // And save the children. All children should start in the same state as the triangle we just split. assert(! sides_to_split.empty() && int(sides_to_split.size()) <= 3); for (int i=0; i<=int(sides_to_split.size()); ++i) { - m_triangles[facet_idx].div_info->children[i] = m_triangles.size()-1-i; - m_triangles[m_triangles.size()-1-i].div_info->parent = facet_idx; - m_triangles[m_triangles[facet_idx].div_info->children[i]].div_info->set_state(old_type); + tr.children[i] = m_triangles.size()-1-i; + m_triangles[tr.children[i]].parent = facet_idx; + m_triangles[tr.children[i]].set_state(old_type); } - -#ifndef NDEBUG - int split_sides = m_triangles[facet_idx].div_info->number_of_split_sides(); - if (split_sides != 0) { - // check that children are range - for (int i=0; i<=split_sides; ++i) - assert(m_triangles[facet_idx].div_info->children[i] >= 0 && m_triangles[facet_idx].div_info->children[i] < int(m_triangles.size())); - - } -#endif - - return 1; + return true; } @@ -1022,6 +1036,29 @@ bool TriangleSelector::is_point_inside_cursor(const Vec3f& point) const } +// Is pointer in a triangle? +bool TriangleSelector::is_pointer_in_triangle(int facet_idx) const +{ + auto signed_volume_sign = [](const Vec3f& a, const Vec3f& b, + const Vec3f& c, const Vec3f& d) -> bool { + return ((b-a).cross(c-a)).dot(d-a) > 0.; + }; + + const Vec3f& p1 = m_vertices[m_triangles[facet_idx].verts_idxs[0]]; + const Vec3f& p2 = m_vertices[m_triangles[facet_idx].verts_idxs[1]]; + const Vec3f& p3 = m_vertices[m_triangles[facet_idx].verts_idxs[2]]; + const Vec3f& q1 = m_cursor.center + m_cursor.dir; + const Vec3f q2 = m_cursor.center - m_cursor.dir; + + if (signed_volume_sign(q1,p1,p2,p3) != signed_volume_sign(q2,p1,p2,p3)) { + bool pos = signed_volume_sign(q1,q2,p1,p2); + if (signed_volume_sign(q1,q2,p2,p3) == pos && signed_volume_sign(q1,q2,p3,p1) == pos) + return true; + } + return false; +} + + // Determine whether this facet is potentially visible (still can be obscured). bool TriangleSelector::faces_camera(int facet) const @@ -1056,39 +1093,41 @@ int TriangleSelector::vertices_inside(int facet_idx) const // Recursively remove all subtriangles. void TriangleSelector::undivide_triangle(int facet_idx) { - assert(facet_idx < m_triangles.size()); - auto& dn_ptr = m_triangles[facet_idx].div_info; - assert(dn_ptr); + assert(facet_idx < int(m_triangles.size())); + Triangle& tr = m_triangles[facet_idx]; - if (dn_ptr->number_of_split_sides() != 0) { - for (int i=0; i<=dn_ptr->number_of_split_sides(); ++i) { - undivide_triangle(dn_ptr->children[i]); - m_triangles[dn_ptr->children[i]].div_info->valid = false; + if (tr.is_split()) { + for (int i=0; i<=tr.number_of_split_sides(); ++i) { + undivide_triangle(tr.children[i]); + m_triangles[tr.children[i]].valid = false; } } - dn_ptr->set_division(0); // not split + tr.set_division(0); // not split } -void TriangleSelector::remove_needless(int child_facet) +void TriangleSelector::remove_if_needless(int child_facet) { - assert(m_triangles[child_facet].div_info->number_of_split_sides() == 0); - int parent = m_triangles[child_facet].div_info->parent; + if (m_triangles[child_facet].is_split() || ! m_triangles[child_facet].valid) + return; + int parent = m_triangles[child_facet].parent; if (parent == -1) return; // root - // Check type of all valid children. - FacetSupportType type = m_triangles[m_triangles[parent].div_info->children[0]].div_info->get_state(); - for (int i=0; i<=m_triangles[parent].div_info->number_of_split_sides(); ++i) - if (m_triangles[m_triangles[parent].div_info->children[0]].div_info->get_state() != type) + FacetSupportType child_type = m_triangles[child_facet].get_state(); + + // Check type of all valid children, if they're same, they are needless. + for (int i=0; i<=m_triangles[parent].number_of_split_sides(); ++i) + if (m_triangles[m_triangles[parent].children[i]].is_split() + || m_triangles[m_triangles[parent].children[i]].get_state() != child_type) return; // not all children are the same - // All children are the same, let's kill them. + // All children are the same, kill them. undivide_triangle(parent); - m_triangles[parent].div_info->set_state(type); + m_triangles[parent].set_state(child_type); - // And not try the same for grandparent. - remove_needless(parent); + // And now try the same for parent (which has just become leaf). + remove_if_needless(parent); } @@ -1114,51 +1153,40 @@ void TriangleSelector::render() const ::glScalef(1.01f, 1.01f, 1.01f); ::glBegin( GL_TRIANGLES); - - for (int tr_id=0; tr_idvalid) { - for (int i=0; i<3; ++i) - ::glVertex3f(m_vertices[tr.verts_idxs[i]][0], m_vertices[tr.verts_idxs[i]][1], m_vertices[tr.verts_idxs[i]][2]); - } + if (! tr.valid) + continue; + + if (tr_id == m_orig_size_indices-1) + ::glColor3f(1.f, 0.f, 0.f); + + for (int i=0; i<3; ++i) + ::glVertex3f(m_vertices[tr.verts_idxs[i]][0], + m_vertices[tr.verts_idxs[i]][1], + m_vertices[tr.verts_idxs[i]][2]); } ::glEnd(); + ::glPolygonMode( GL_FRONT_AND_BACK, GL_FILL ); ::glBegin( GL_TRIANGLES); - for (int tr_id=0; tr_idvalid) + for (const Triangle& tr : m_triangles) { + if (! tr.valid || tr.is_split() || tr.get_state() == FacetSupportType::NONE) continue; - if (tr.div_info->number_of_split_sides() == 0) { - if (tr.div_info->get_state() == FacetSupportType::ENFORCER) - ::glColor4f(0.f, 0.f, 1.f, 0.2f); - else if (tr.div_info->get_state() == FacetSupportType::BLOCKER) - ::glColor4f(1.f, 0.f, 0.f, 0.2f); - else - continue; - } + if (tr.get_state() == FacetSupportType::ENFORCER) + ::glColor4f(0.f, 0.f, 1.f, 0.2f); else - continue; + ::glColor4f(1.f, 0.f, 0.f, 0.2f); - - if (tr.div_info->valid) { - for (int i=0; i<3; ++i) - ::glVertex3f(m_vertices[tr.verts_idxs[i]][0], m_vertices[tr.verts_idxs[i]][1], m_vertices[tr.verts_idxs[i]][2]); - } + for (int i=0; i<3; ++i) + ::glVertex3f(m_vertices[tr.verts_idxs[i]][0], + m_vertices[tr.verts_idxs[i]][1], + m_vertices[tr.verts_idxs[i]][2]); } ::glEnd(); } - -TriangleSelector::DivisionNode::DivisionNode() -{ - set_division(0); - set_state(FacetSupportType::NONE); -} - } // namespace GUI } // namespace Slic3r diff --git a/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.hpp b/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.hpp index 07c2e86d5..3e1b076c5 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.hpp @@ -24,16 +24,18 @@ class ClippingPlane; // to recursively subdivide the triangles and make the selection finer. class TriangleSelector { public: + void set_edge_limit(float edge_limit) { m_edge_limit_sqr = std::pow(edge_limit, 2.f); } void render() const; // Create new object on a TriangleMesh. The referenced mesh must // stay valid, a ptr to it is saved and used. explicit TriangleSelector(const TriangleMesh& mesh); // Select all triangles inside the circle, subdivide where needed. - void select_patch(const Vec3f& hit, // point where to start - int facet_start, // facet that point belongs to - const Vec3f& dir, // direction of the ray - float radius_sqr, // squared radius of the cursor + void select_patch(const Vec3f& hit, // point where to start + int facet_start, // facet that point belongs to + const Vec3f& source, // camera position (mesh coords) + const Vec3f& dir, // direction of the ray (mesh coords) + float radius_sqr, // squared radius of the cursor FacetSupportType new_state); // enforcer or blocker? void unselect_all(); @@ -43,49 +45,44 @@ public: void garbage_collect(); private: - // A struct to hold information about how a triangle was divided. - struct DivisionNode { - DivisionNode(); - // Index of triangle this describes. + // Triangle and info about how it's split. + struct Triangle { + public: + Triangle(int a, int b, int c) + : verts_idxs{stl_triangle_vertex_indices(a, b, c)}, + division_type{0} + {} + stl_triangle_vertex_indices verts_idxs; + + // Is this triangle valid or marked to remove? bool valid{true}; // Index of parent triangle (-1: original) int parent{-1}; - // Bitmask encoding which sides are split. - int8_t division_type; - // bits 0 and 1 : 00 - no division - // 01 - one-edge split - // 10 - two-edge split - // 11 - three-edge split - // bits 2 and 3 : decimal 0, 1 or 2 identifying the special edge (one that - // splits in one-edge split or one that stays in two-edge split). - // Children triangles (0 = no child) std::array children; // Set the division type. void set_division(int sides_to_split, int special_side_idx = -1); - void set_state(FacetSupportType state); - // Helpers that decode the division_type bitmask. + // Get/set current state. + void set_state(FacetSupportType state); + FacetSupportType get_state() const; + + // Get info on how it's split. + bool is_split() const { return number_of_split_sides() != 0; } int number_of_split_sides() const { return division_type & 0b11; } int side_to_keep() const; int side_to_split() const; - FacetSupportType get_state() const; - }; - - // Triangle and pointer to how it's divided (nullptr = not divided). - // The ptr is nullptr for all new triangles, it is only valid for - // the original (undivided) triangles. - struct Triangle { - Triangle(int a, int b, int c) - : verts_idxs{stl_triangle_vertex_indices(a, b, c)}, - div_info{std::make_unique()} - {} - stl_triangle_vertex_indices verts_idxs; - std::unique_ptr div_info; + private: + // Bitmask encoding which sides are split. + int8_t division_type; + // bits 0, 1 : decimal 0, 1, 2 or 3 (how many sides are split) + // bits 2, 3 (non-leaf): decimal 0, 1 or 2 identifying the special edge + // (one that splits in one-edge split or one that stays in two-edge split). + // bits 2, 3 (leaf): FacetSupportType value }; // Lists of vertices and triangles, both original and new @@ -93,6 +90,8 @@ private: std::vector m_triangles; const TriangleMesh* m_mesh; + float m_edge_limit_sqr = 1.f; + // Number of original vertices and triangles. int m_orig_size_vertices; int m_orig_size_indices; @@ -104,6 +103,7 @@ private: // Caches for cursor position, radius and direction. struct Cursor { Vec3f center; + Vec3f source; Vec3f dir; float radius_sqr; }; @@ -111,8 +111,7 @@ private: Cursor m_cursor; // Private functions: - void select_triangle(int facet_idx, FacetSupportType type, - int num_of_inside_vertices = -1, + bool select_triangle(int facet_idx, FacetSupportType type, bool cursor_inside = false); bool is_point_inside_cursor(const Vec3f& point) const; @@ -125,7 +124,8 @@ private: bool split_triangle(int facet_idx); - void remove_needless(int child_facet); + void remove_if_needless(int child_facet); + bool is_pointer_in_triangle(int facet_idx) const; }; From bed28bb2fff974cd2097b47d404b3cbeb69d570b Mon Sep 17 00:00:00 2001 From: Lukas Matena Date: Mon, 22 Jun 2020 11:45:51 +0200 Subject: [PATCH 07/24] TriangleSelector: even more progress --- src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.cpp | 36 ++++++++++++++------ src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.hpp | 7 +--- 2 files changed, 27 insertions(+), 16 deletions(-) diff --git a/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.cpp b/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.cpp index 1c77f437c..95f46b3fe 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.cpp @@ -859,6 +859,7 @@ void TriangleSelector::select_patch(const Vec3f& hit, int facet_start, float radius_sqr, FacetSupportType new_state) { assert(facet_start < m_orig_size_indices); + assert(is_approx(dir.norm(), 1.f)); // Save current cursor center, squared radius and camera direction, // so we don't have to pass it around. @@ -892,7 +893,6 @@ void TriangleSelector::select_patch(const Vec3f& hit, int facet_start, // outside the cursor. bool TriangleSelector::select_triangle(int facet_idx, FacetSupportType type, bool cursor_inside) { - bool out = false; assert(facet_idx < int(m_triangles.size())); Triangle& tr = m_triangles[facet_idx]; @@ -903,10 +903,12 @@ bool TriangleSelector::select_triangle(int facet_idx, FacetSupportType type, boo int num_of_inside_vertices = vertices_inside(facet_idx); - if (num_of_inside_vertices == 0 && ! cursor_inside) - return out; // FIXME: just an edge can be inside + if (num_of_inside_vertices == 0 + && ! cursor_inside + && ! is_edge_inside_cursor(facet_idx)) + return false; - if (num_of_inside_vertices == 3) { + if (vertices_inside(facet_idx) == 3) { // dump any subdivision and select whole triangle undivide_triangle(facet_idx); tr.set_state(type); @@ -914,7 +916,6 @@ bool TriangleSelector::select_triangle(int facet_idx, FacetSupportType type, boo // the triangle is partially inside, let's recursively divide it // (if not already) and try selecting its children. - if (! tr.is_split() && tr.get_state() == type) { // This is leaf triangle that is already of correct type as a whole. // No need to split, all children would end up selected anyway. @@ -1065,11 +1066,10 @@ bool TriangleSelector::faces_camera(int facet) const { assert(facet < m_orig_size_indices); // The normal is cached in mesh->stl, use it. - return (m_mesh->stl.facet_start[facet].normal.dot(m_cursor.dir) > 0.); + return (m_mesh->stl.facet_start[facet].normal.dot(m_cursor.dir) < 0.); } - // How many vertices of a triangle are inside the circle? int TriangleSelector::vertices_inside(int facet_idx) const { @@ -1082,11 +1082,27 @@ int TriangleSelector::vertices_inside(int facet_idx) const } -// Is mouse pointer inside a triangle? -/*bool TriangleSelector::is_pointer_inside_triangle(int facet_idx) const +// Is edge inside cursor? +bool TriangleSelector::is_edge_inside_cursor(int facet_idx) const { + Vec3f pts[3]; + for (int i=0; i<3; ++i) + pts[i] = m_vertices[m_triangles[facet_idx].verts_idxs[i]]; -}*/ + const Vec3f& p = m_cursor.center; + + for (int side = 0; side < 3; ++side) { + const Vec3f& a = pts[side]; + const Vec3f& b = pts[side<2 ? side+1 : 0]; + Vec3f s = (b-a).normalized(); + float t = (p-a).dot(s); + Vec3f vector = a+t*s - p; + float dist_sqr = vector.squaredNorm(); + if (dist_sqr < m_cursor.radius_sqr && t>=0.f && t<=(b-a).norm()) + return true; + } + return false; +} diff --git a/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.hpp b/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.hpp index 3e1b076c5..a50328d3b 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.hpp @@ -113,19 +113,14 @@ private: // Private functions: bool select_triangle(int facet_idx, FacetSupportType type, bool cursor_inside = false); - bool is_point_inside_cursor(const Vec3f& point) const; - int vertices_inside(int facet_idx) const; - bool faces_camera(int facet) const; - void undivide_triangle(int facet_idx); - bool split_triangle(int facet_idx); - void remove_if_needless(int child_facet); bool is_pointer_in_triangle(int facet_idx) const; + bool is_edge_inside_cursor(int facet_idx) const; }; From fb73bb1c6637fc6deb350e987b3308bb32a70e36 Mon Sep 17 00:00:00 2001 From: Lukas Matena Date: Tue, 23 Jun 2020 16:07:33 +0200 Subject: [PATCH 08/24] TriangleSelector: remerging triangles, bugfixes --- src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.cpp | 202 ++++++++++++------- src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.hpp | 18 +- 2 files changed, 140 insertions(+), 80 deletions(-) diff --git a/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.cpp b/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.cpp index 95f46b3fe..8972e9246 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.cpp @@ -95,7 +95,10 @@ void GLGizmoFdmSupports::on_render() const glsafe(::glEnable(GL_DEPTH_TEST)); //render_triangles(selection); - m_triangle_selector->render(); + + if (m_triangle_selector && ! m_setting_angle) + m_triangle_selector->render(m_imgui); + m_c->object_clipper()->render_cut(); render_cursor_circle(); @@ -569,12 +572,6 @@ void GLGizmoFdmSupports::on_render_input_window(float x, float y, float bottom_l if (! m_c->selection_info()->model_object()) return; - m_imgui->begin(std::string("TriangleSelector DEBUG"), ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoCollapse); - static float edge_limit = 1.f; - m_imgui->slider_float("Edge limit (mm): ", &edge_limit, 0.1f, 8.f); - m_triangle_selector->set_edge_limit(edge_limit); - m_imgui->end(); - const float approx_height = m_imgui->scaled(18.0f); y = std::min(y, bottom_limit - approx_height); m_imgui->set_next_window_pos(x, y, ImGuiCond_Always); @@ -740,9 +737,7 @@ CommonGizmosDataID GLGizmoFdmSupports::on_get_requirements() const int(CommonGizmosDataID::SelectionInfo) | int(CommonGizmosDataID::InstancesHider) | int(CommonGizmosDataID::Raycaster) - | int(CommonGizmosDataID::HollowedMesh) - | int(CommonGizmosDataID::ObjectClipper) - | int(CommonGizmosDataID::SupportsClipper)); + | int(CommonGizmosDataID::ObjectClipper)); } @@ -872,7 +867,7 @@ void TriangleSelector::select_patch(const Vec3f& hit, int facet_start, while (facet_idx < int(facets_to_check.size())) { int facet = facets_to_check[facet_idx]; if (! visited[facet]) { - if (select_triangle(facet, new_state, facet == facet_start)) { + if (select_triangle(facet, new_state)) { // add neighboring facets to list to be proccessed later for (int n=0; n<3; ++n) { if (faces_camera(m_mesh->stl.neighbors_start[facet].neighbor[n])) @@ -891,49 +886,54 @@ void TriangleSelector::select_patch(const Vec3f& hit, int facet_start, // the triangle recursively, selecting just subtriangles truly inside the circle. // This is done by an actual recursive call. Returns false if the triangle is // outside the cursor. -bool TriangleSelector::select_triangle(int facet_idx, FacetSupportType type, bool cursor_inside) +bool TriangleSelector::select_triangle(int facet_idx, FacetSupportType type, bool recursive_call) { assert(facet_idx < int(m_triangles.size())); - Triangle& tr = m_triangles[facet_idx]; - if (! tr.valid) + Triangle* tr = &m_triangles[facet_idx]; + if (! tr->valid) return false; - cursor_inside = is_pointer_in_triangle(facet_idx); - int num_of_inside_vertices = vertices_inside(facet_idx); if (num_of_inside_vertices == 0 - && ! cursor_inside + && ! is_pointer_in_triangle(facet_idx) && ! is_edge_inside_cursor(facet_idx)) return false; - if (vertices_inside(facet_idx) == 3) { + if (num_of_inside_vertices == 3) { // dump any subdivision and select whole triangle undivide_triangle(facet_idx); - tr.set_state(type); + tr->set_state(type); } else { // the triangle is partially inside, let's recursively divide it // (if not already) and try selecting its children. - if (! tr.is_split() && tr.get_state() == type) { + if (! tr->is_split() && tr->get_state() == type) { // This is leaf triangle that is already of correct type as a whole. // No need to split, all children would end up selected anyway. return true; } split_triangle(facet_idx); - assert(facet_idx < int(m_triangles.size())); - int num_of_children = tr.number_of_split_sides() + 1; + tr = &m_triangles[facet_idx]; // might have been invalidated + + + int num_of_children = tr->number_of_split_sides() + 1; if (num_of_children != 1) { - for (int i=0; ichildren.size())); + assert(tr->children[i] < int(m_triangles.size())); + + select_triangle(tr->children[i], type, true); + tr = &m_triangles[facet_idx]; // might have been invalidated + } } } - - // In case that all siblings are leafs and have the same state now, + // In case that all children are leafs and have the same state now, // they may be removed and substituted by the parent triangle. - //remove_if_needless(facet_idx); + if (! recursive_call) + remove_useless_children(facet_idx); return true; } @@ -945,13 +945,13 @@ bool TriangleSelector::split_triangle(int facet_idx) return false; } - Triangle& tr = m_triangles[facet_idx]; + Triangle* tr = &m_triangles[facet_idx]; - FacetSupportType old_type = tr.get_state(); + FacetSupportType old_type = tr->get_state(); const double limit_squared = m_edge_limit_sqr; - stl_triangle_vertex_indices& facet = tr.verts_idxs; + stl_triangle_vertex_indices& facet = tr->verts_idxs; const stl_vertex* pts[3] = { &m_vertices[facet[0]], &m_vertices[facet[1]], &m_vertices[facet[2]]}; double sides[3] = { (*pts[2]-*pts[1]).squaredNorm(), (*pts[0]-*pts[2]).squaredNorm(), (*pts[1]-*pts[0]).squaredNorm() }; @@ -964,7 +964,7 @@ bool TriangleSelector::split_triangle(int facet_idx) side_to_keep = pt_idx; } if (sides_to_split.empty()) { - tr.set_division(0); + tr->set_division(0); return false; } @@ -1012,17 +1012,19 @@ bool TriangleSelector::split_triangle(int facet_idx) m_triangles.emplace_back(verts_idxs[1], verts_idxs[3], verts_idxs[5]); } + tr = &m_triangles[facet_idx]; // may have been invalidated + // Save how the triangle was split. Second argument makes sense only for one // or two split sides, otherwise the value is ignored. - tr.set_division(sides_to_split.size(), + tr->set_division(sides_to_split.size(), sides_to_split.size() == 2 ? side_to_keep : sides_to_split[0]); // And save the children. All children should start in the same state as the triangle we just split. assert(! sides_to_split.empty() && int(sides_to_split.size()) <= 3); for (int i=0; i<=int(sides_to_split.size()); ++i) { - tr.children[i] = m_triangles.size()-1-i; - m_triangles[tr.children[i]].parent = facet_idx; - m_triangles[tr.children[i]].set_state(old_type); + tr->children[i] = m_triangles.size()-1-i; + m_triangles[tr->children[i]].parent = facet_idx; + m_triangles[tr->children[i]].set_state(old_type); } return true; @@ -1097,7 +1099,10 @@ bool TriangleSelector::is_edge_inside_cursor(int facet_idx) const Vec3f s = (b-a).normalized(); float t = (p-a).dot(s); Vec3f vector = a+t*s - p; - float dist_sqr = vector.squaredNorm(); + + // vector is 3D vector from center to the intersection. What we want to + // measure is length of its projection onto plane perpendicular to dir. + float dist_sqr = vector.squaredNorm() - std::pow(vector.dot(m_cursor.dir), 2.f); if (dist_sqr < m_cursor.radius_sqr && t>=0.f && t<=(b-a).norm()) return true; } @@ -1117,33 +1122,47 @@ void TriangleSelector::undivide_triangle(int facet_idx) undivide_triangle(tr.children[i]); m_triangles[tr.children[i]].valid = false; } + tr.set_division(0); // not split } - - tr.set_division(0); // not split } -void TriangleSelector::remove_if_needless(int child_facet) +void TriangleSelector::remove_useless_children(int facet_idx) { - if (m_triangles[child_facet].is_split() || ! m_triangles[child_facet].valid) + // Check that all children are leafs of the same type. If not, try to + // make them (recursive call). Remove them if sucessful. + + assert(facet_idx < int(m_triangles.size()) && m_triangles[facet_idx].valid); + Triangle& tr = m_triangles[facet_idx]; + + if (! tr.is_split()) { + // This is a leaf, there nothing to do. This can happen during the + // first (non-recursive call). Shouldn't otherwise. return; - int parent = m_triangles[child_facet].parent; - if (parent == -1) - return; // root - FacetSupportType child_type = m_triangles[child_facet].get_state(); + } - // Check type of all valid children, if they're same, they are needless. - for (int i=0; i<=m_triangles[parent].number_of_split_sides(); ++i) - if (m_triangles[m_triangles[parent].children[i]].is_split() - || m_triangles[m_triangles[parent].children[i]].get_state() != child_type) - return; // not all children are the same + // Call this for all non-leaf children. + for (int child_idx=0; child_idx<=tr.number_of_split_sides(); ++child_idx) { + assert(child_idx < int(m_triangles.size()) && m_triangles[child_idx].valid); + if (m_triangles[tr.children[child_idx]].is_split()) + remove_useless_children(tr.children[child_idx]); + } - // All children are the same, kill them. - undivide_triangle(parent); - m_triangles[parent].set_state(child_type); - // And now try the same for parent (which has just become leaf). - remove_if_needless(parent); + // Return if a child is not leaf or two children differ in type. + FacetSupportType first_child_type; + for (int child_idx=0; child_idx<=tr.number_of_split_sides(); ++child_idx) { + if (m_triangles[tr.children[child_idx]].is_split()) + return; + if (child_idx == 0) + first_child_type = m_triangles[tr.children[0]].get_state(); + else if (m_triangles[tr.children[child_idx]].get_state() != first_child_type) + return; + } + + // If we got here, the children can be removed. + undivide_triangle(facet_idx); + tr.set_state(first_child_type); } @@ -1158,33 +1177,12 @@ TriangleSelector::TriangleSelector(const TriangleMesh& mesh) m_mesh = &mesh; } - -void TriangleSelector::render() const +void TriangleSelector::render(ImGuiWrapper* imgui) { - ::glColor3f(0.f, 0.f, 1.f); - ::glPolygonMode( GL_FRONT_AND_BACK, GL_LINE ); - Vec3d offset = wxGetApp().model().objects.front()->instances.front()->get_transformation().get_offset(); ::glTranslatef(offset.x(), offset.y(), offset.z()); - ::glScalef(1.01f, 1.01f, 1.01f); + ::glScalef(1.005f, 1.005f, 1.005f); - ::glBegin( GL_TRIANGLES); - for (int tr_id=0; tr_idbegin(std::string("TriangleSelector dialog (DEV ONLY)"), + ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoCollapse); + static float edge_limit = 1.f; + imgui->text("Edge limit (mm): "); + imgui->slider_float("", &edge_limit, 0.1f, 8.f); + set_edge_limit(edge_limit); + imgui->checkbox("Show triangles: ", m_show_triangles); + + int valid_triangles = std::count_if(m_triangles.begin(), m_triangles.end(), + [](const Triangle& tr) { return tr.valid; }); + imgui->text("Valid triangles: " + std::to_string(valid_triangles) + + "/" + std::to_string(m_triangles.size())); + imgui->text("Number of vertices: " + std::to_string(m_vertices.size())); + + imgui->end(); + + if (m_show_triangles) { + ::glColor3f(0.f, 0.f, 1.f); + ::glPolygonMode( GL_FRONT_AND_BACK, GL_LINE ); + + ::glBegin( GL_TRIANGLES); + for (int tr_id=0; tr_id +#define PRUSASLICER_TRIANGLE_SELECTOR_DEBUG + + namespace Slic3r { enum class FacetSupportType : int8_t; @@ -25,7 +28,7 @@ class ClippingPlane; class TriangleSelector { public: void set_edge_limit(float edge_limit) { m_edge_limit_sqr = std::pow(edge_limit, 2.f); } - void render() const; + // Create new object on a TriangleMesh. The referenced mesh must // stay valid, a ptr to it is saved and used. explicit TriangleSelector(const TriangleMesh& mesh); @@ -40,10 +43,19 @@ public: void unselect_all(); + // Render current selection. Transformation matrices are supposed + // to be already set. + void render(ImGuiWrapper* imgui = nullptr); + // Remove all unnecessary data (such as vertices that are not needed // because the selection has been made larger. void garbage_collect(); +#ifdef PRUSASLICER_TRIANGLE_SELECTOR_DEBUG + void render_debug(ImGuiWrapper* imgui); + bool m_show_triangles{true}; +#endif + private: // Triangle and info about how it's split. struct Triangle { @@ -112,13 +124,13 @@ private: // Private functions: bool select_triangle(int facet_idx, FacetSupportType type, - bool cursor_inside = false); + bool recursive_call = false); bool is_point_inside_cursor(const Vec3f& point) const; int vertices_inside(int facet_idx) const; bool faces_camera(int facet) const; void undivide_triangle(int facet_idx); bool split_triangle(int facet_idx); - void remove_if_needless(int child_facet); + void remove_useless_children(int facet_idx); // No hidden meaning. Triangles are meant. bool is_pointer_in_triangle(int facet_idx) const; bool is_edge_inside_cursor(int facet_idx) const; }; From b9321856f387d6ef07ee9d769fe9674d83b8e31b Mon Sep 17 00:00:00 2001 From: Lukas Matena Date: Wed, 24 Jun 2020 12:24:32 +0200 Subject: [PATCH 09/24] TriangleSelector: Reusing of previously calculated triangle divisions, partial garbage collection implementation --- src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.cpp | 144 ++++++++++++------- src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.hpp | 38 ++--- 2 files changed, 117 insertions(+), 65 deletions(-) diff --git a/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.cpp b/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.cpp index 8972e9246..2296dd570 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.cpp @@ -800,51 +800,29 @@ void GLGizmoFdmSupports::on_save(cereal::BinaryOutputArchive&) const } - +// sides_to_split==-1 : just restore previous split void TriangleSelector::Triangle::set_division(int sides_to_split, int special_side_idx) { - assert(sides_to_split >=0 && sides_to_split <= 3); + assert(sides_to_split >=-1 && sides_to_split <= 3); assert(special_side_idx >=-1 && special_side_idx < 3); // If splitting one or two sides, second argument must be provided. assert(sides_to_split != 1 || special_side_idx != -1); assert(sides_to_split != 2 || special_side_idx != -1); - division_type = sides_to_split | ((special_side_idx != -1 ? special_side_idx : 0 ) <<2); -} - - - -void TriangleSelector::Triangle::set_state(FacetSupportType type) -{ - // If this is not a leaf-node, this makes no sense and - // the bits are used for storing index of an edge. - assert(! is_split()); - division_type = (int8_t(type) << 2); -} - - - -int TriangleSelector::Triangle::side_to_keep() const -{ - assert(number_of_split_sides() == 2); - return division_type >> 2; -} - - - -int TriangleSelector::Triangle::side_to_split() const -{ - assert(number_of_split_sides() == 1); - return division_type >> 2; -} - - - -FacetSupportType TriangleSelector::Triangle::get_state() const -{ - assert(! is_split()); // this must be leaf - return FacetSupportType(division_type >> 2); + if (sides_to_split != -1) { + this->number_of_splits = sides_to_split; + if (sides_to_split != 0) { + assert(old_number_of_splits == 0); + this->special_side_idx = special_side_idx; + this->old_number_of_splits = sides_to_split; + } + } + else { + assert(old_number_of_splits != 0); + this->number_of_splits = old_number_of_splits; + // indices of children should still be there. + } } @@ -938,17 +916,29 @@ bool TriangleSelector::select_triangle(int facet_idx, FacetSupportType type, boo } -bool TriangleSelector::split_triangle(int facet_idx) +void TriangleSelector::split_triangle(int facet_idx) { if (m_triangles[facet_idx].is_split()) { - // The triangle was divided already. - return false; + // The triangle is divided already. + return; } Triangle* tr = &m_triangles[facet_idx]; FacetSupportType old_type = tr->get_state(); + if (tr->was_split_before() != 0) { + // This triangle is not split at the moment, but was at one point + // in history. We can just restore it and resurrect its children. + tr->set_division(-1); + for (int i=0; i<=tr->number_of_split_sides(); ++i) { + m_triangles[tr->children[i]].set_state(old_type); + m_triangles[tr->children[i]].valid = true; + } + return; + } + + // If we got here, we are about to actually split the triangle. const double limit_squared = m_edge_limit_sqr; stl_triangle_vertex_indices& facet = tr->verts_idxs; @@ -964,8 +954,9 @@ bool TriangleSelector::split_triangle(int facet_idx) side_to_keep = pt_idx; } if (sides_to_split.empty()) { + // This shall be unselected. tr->set_division(0); - return false; + return; } // indices of triangle vertices @@ -1023,11 +1014,8 @@ bool TriangleSelector::split_triangle(int facet_idx) assert(! sides_to_split.empty() && int(sides_to_split.size()) <= 3); for (int i=0; i<=int(sides_to_split.size()); ++i) { tr->children[i] = m_triangles.size()-1-i; - m_triangles[tr->children[i]].parent = facet_idx; m_triangles[tr->children[i]].set_state(old_type); } - - return true; } @@ -1166,6 +1154,45 @@ void TriangleSelector::remove_useless_children(int facet_idx) } + +void TriangleSelector::garbage_collect() +{ + // First make a map from old to new triangle indices. + int new_idx = m_orig_size_indices; + std::vector new_triangle_indices(m_triangles.size(), -1); + std::vector invalid_vertices(m_vertices.size(), false); + for (int i = m_orig_size_indices; itext("Edge limit (mm): "); imgui->slider_float("", &edge_limit, 0.1f, 8.f); set_edge_limit(edge_limit); - imgui->checkbox("Show triangles: ", m_show_triangles); + imgui->checkbox("Show split triangles: ", m_show_triangles); + imgui->checkbox("Show invalid triangles: ", m_show_invalid); int valid_triangles = std::count_if(m_triangles.begin(), m_triangles.end(), [](const Triangle& tr) { return tr.valid; }); imgui->text("Valid triangles: " + std::to_string(valid_triangles) + "/" + std::to_string(m_triangles.size())); imgui->text("Number of vertices: " + std::to_string(m_vertices.size())); + if (imgui->button("Force garbage collection")) + garbage_collect(); imgui->end(); if (m_show_triangles) { - ::glColor3f(0.f, 0.f, 1.f); ::glPolygonMode( GL_FRONT_AND_BACK, GL_LINE ); ::glBegin( GL_TRIANGLES); for (int tr_id=0; tr_id children; @@ -79,22 +80,25 @@ private: void set_division(int sides_to_split, int special_side_idx = -1); // Get/set current state. - void set_state(FacetSupportType state); - FacetSupportType get_state() const; + void set_state(FacetSupportType type) { assert(! is_split()); state = type; } + FacetSupportType get_state() const { assert(! is_split()); return state; } // Get info on how it's split. bool is_split() const { return number_of_split_sides() != 0; } - int number_of_split_sides() const { return division_type & 0b11; } - int side_to_keep() const; - int side_to_split() const; + int number_of_split_sides() const { return number_of_splits; } + int side_to_keep() const { assert(number_of_split_sides() == 2); return special_side_idx; } + int side_to_split() const { assert(number_of_split_sides() == 1); return special_side_idx; } + bool was_split_before() const { return old_number_of_splits != 0; } + void forget_history() { old_number_of_splits = 0; } private: - // Bitmask encoding which sides are split. - int8_t division_type; - // bits 0, 1 : decimal 0, 1, 2 or 3 (how many sides are split) - // bits 2, 3 (non-leaf): decimal 0, 1 or 2 identifying the special edge - // (one that splits in one-edge split or one that stays in two-edge split). - // bits 2, 3 (leaf): FacetSupportType value + int number_of_splits; + int special_side_idx; + FacetSupportType state; + + // How many children were spawned during last split? + // Is not reset on remerging the triangle. + int old_number_of_splits; }; // Lists of vertices and triangles, both original and new @@ -129,7 +133,7 @@ private: int vertices_inside(int facet_idx) const; bool faces_camera(int facet) const; void undivide_triangle(int facet_idx); - bool split_triangle(int facet_idx); + void split_triangle(int facet_idx); void remove_useless_children(int facet_idx); // No hidden meaning. Triangles are meant. bool is_pointer_in_triangle(int facet_idx) const; bool is_edge_inside_cursor(int facet_idx) const; From da6acd73e21407a8956d64b332b8fba47a94465f Mon Sep 17 00:00:00 2001 From: Lukas Matena Date: Wed, 24 Jun 2020 14:47:53 +0200 Subject: [PATCH 10/24] TriangleSelector: Vertices are reference-counted and garbage collected Garbage collection is triggered automatically when more than half of all triangles are invalid --- src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.cpp | 133 +++++++++++++------ src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.hpp | 44 +++--- 2 files changed, 120 insertions(+), 57 deletions(-) diff --git a/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.cpp b/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.cpp index 2296dd570..10d045920 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.cpp @@ -908,10 +908,20 @@ bool TriangleSelector::select_triangle(int facet_idx, FacetSupportType type, boo } } } - // In case that all children are leafs and have the same state now, - // they may be removed and substituted by the parent triangle. - if (! recursive_call) + + if (! recursive_call) { + // In case that all children are leafs and have the same state now, + // they may be removed and substituted by the parent triangle. remove_useless_children(facet_idx); + + // Make sure that we did not lose track of invalid triangles. + assert(m_invalid_triangles == std::count_if(m_triangles.begin(), m_triangles.end(), + [](const Triangle& tr) { return ! tr.valid; })); + + // Do garbage collection maybe? + if (2*m_invalid_triangles > int(m_triangles.size())) + garbage_collect(); + } return true; } @@ -934,6 +944,7 @@ void TriangleSelector::split_triangle(int facet_idx) for (int i=0; i<=tr->number_of_split_sides(); ++i) { m_triangles[tr->children[i]].set_state(old_type); m_triangles[tr->children[i]].valid = true; + --m_invalid_triangles; } return; } @@ -941,9 +952,11 @@ void TriangleSelector::split_triangle(int facet_idx) // If we got here, we are about to actually split the triangle. const double limit_squared = m_edge_limit_sqr; - stl_triangle_vertex_indices& facet = tr->verts_idxs; - const stl_vertex* pts[3] = { &m_vertices[facet[0]], &m_vertices[facet[1]], &m_vertices[facet[2]]}; - double sides[3] = { (*pts[2]-*pts[1]).squaredNorm(), (*pts[0]-*pts[2]).squaredNorm(), (*pts[1]-*pts[0]).squaredNorm() }; + std::array& facet = tr->verts_idxs; + const stl_vertex* pts[3] = { &m_vertices[facet[0]].v, &m_vertices[facet[1]].v, &m_vertices[facet[2]].v}; + double sides[3] = { (*pts[2]-*pts[1]).squaredNorm(), + (*pts[0]-*pts[2]).squaredNorm(), + (*pts[1]-*pts[0]).squaredNorm() }; std::vector sides_to_split; int side_to_keep = -1; @@ -970,37 +983,37 @@ void TriangleSelector::split_triangle(int facet_idx) if (sides_to_split.size() == 1) { - m_vertices.emplace_back((m_vertices[verts_idxs[1]] + m_vertices[verts_idxs[2]])/2.); + m_vertices.emplace_back((m_vertices[verts_idxs[1]].v + m_vertices[verts_idxs[2]].v)/2.); verts_idxs.insert(verts_idxs.begin()+2, m_vertices.size() - 1); - m_triangles.emplace_back(verts_idxs[0], verts_idxs[1], verts_idxs[2]); - m_triangles.emplace_back(verts_idxs[2], verts_idxs[3], verts_idxs[0]); + push_triangle(verts_idxs[0], verts_idxs[1], verts_idxs[2]); + push_triangle(verts_idxs[2], verts_idxs[3], verts_idxs[0]); } if (sides_to_split.size() == 2) { - m_vertices.emplace_back((m_vertices[verts_idxs[0]] + m_vertices[verts_idxs[1]])/2.); + m_vertices.emplace_back((m_vertices[verts_idxs[0]].v + m_vertices[verts_idxs[1]].v)/2.); verts_idxs.insert(verts_idxs.begin()+1, m_vertices.size() - 1); - m_vertices.emplace_back((m_vertices[verts_idxs[0]] + m_vertices[verts_idxs[3]])/2.); + m_vertices.emplace_back((m_vertices[verts_idxs[0]].v + m_vertices[verts_idxs[3]].v)/2.); verts_idxs.insert(verts_idxs.begin()+4, m_vertices.size() - 1); - m_triangles.emplace_back(verts_idxs[0], verts_idxs[1], verts_idxs[4]); - m_triangles.emplace_back(verts_idxs[1], verts_idxs[2], verts_idxs[4]); - m_triangles.emplace_back(verts_idxs[2], verts_idxs[3], verts_idxs[4]); + push_triangle(verts_idxs[0], verts_idxs[1], verts_idxs[4]); + push_triangle(verts_idxs[1], verts_idxs[2], verts_idxs[4]); + push_triangle(verts_idxs[2], verts_idxs[3], verts_idxs[4]); } if (sides_to_split.size() == 3) { - m_vertices.emplace_back((m_vertices[verts_idxs[0]] + m_vertices[verts_idxs[1]])/2.); + m_vertices.emplace_back((m_vertices[verts_idxs[0]].v + m_vertices[verts_idxs[1]].v)/2.); verts_idxs.insert(verts_idxs.begin()+1, m_vertices.size() - 1); - m_vertices.emplace_back((m_vertices[verts_idxs[2]] + m_vertices[verts_idxs[3]])/2.); + m_vertices.emplace_back((m_vertices[verts_idxs[2]].v + m_vertices[verts_idxs[3]].v)/2.); verts_idxs.insert(verts_idxs.begin()+3, m_vertices.size() - 1); - m_vertices.emplace_back((m_vertices[verts_idxs[4]] + m_vertices[verts_idxs[0]])/2.); + m_vertices.emplace_back((m_vertices[verts_idxs[4]].v + m_vertices[verts_idxs[0]].v)/2.); verts_idxs.insert(verts_idxs.begin()+5, m_vertices.size() - 1); - m_triangles.emplace_back(verts_idxs[0], verts_idxs[1], verts_idxs[5]); - m_triangles.emplace_back(verts_idxs[1], verts_idxs[2], verts_idxs[3]); - m_triangles.emplace_back(verts_idxs[3], verts_idxs[4], verts_idxs[5]); - m_triangles.emplace_back(verts_idxs[1], verts_idxs[3], verts_idxs[5]); + push_triangle(verts_idxs[0], verts_idxs[1], verts_idxs[5]); + push_triangle(verts_idxs[1], verts_idxs[2], verts_idxs[3]); + push_triangle(verts_idxs[3], verts_idxs[4], verts_idxs[5]); + push_triangle(verts_idxs[1], verts_idxs[3], verts_idxs[5]); } tr = &m_triangles[facet_idx]; // may have been invalidated @@ -1035,9 +1048,9 @@ bool TriangleSelector::is_pointer_in_triangle(int facet_idx) const return ((b-a).cross(c-a)).dot(d-a) > 0.; }; - const Vec3f& p1 = m_vertices[m_triangles[facet_idx].verts_idxs[0]]; - const Vec3f& p2 = m_vertices[m_triangles[facet_idx].verts_idxs[1]]; - const Vec3f& p3 = m_vertices[m_triangles[facet_idx].verts_idxs[2]]; + const Vec3f& p1 = m_vertices[m_triangles[facet_idx].verts_idxs[0]].v; + const Vec3f& p2 = m_vertices[m_triangles[facet_idx].verts_idxs[1]].v; + const Vec3f& p3 = m_vertices[m_triangles[facet_idx].verts_idxs[2]].v; const Vec3f& q1 = m_cursor.center + m_cursor.dir; const Vec3f q2 = m_cursor.center - m_cursor.dir; @@ -1065,7 +1078,7 @@ int TriangleSelector::vertices_inside(int facet_idx) const { int inside = 0; for (size_t i=0; i<3; ++i) { - if (is_point_inside_cursor(m_vertices[m_triangles[facet_idx].verts_idxs[i]])) + if (is_point_inside_cursor(m_vertices[m_triangles[facet_idx].verts_idxs[i]].v)) ++inside; } return inside; @@ -1077,7 +1090,7 @@ bool TriangleSelector::is_edge_inside_cursor(int facet_idx) const { Vec3f pts[3]; for (int i=0; i<3; ++i) - pts[i] = m_vertices[m_triangles[facet_idx].verts_idxs[i]]; + pts[i] = m_vertices[m_triangles[facet_idx].verts_idxs[i]].v; const Vec3f& p = m_cursor.center; @@ -1109,6 +1122,7 @@ void TriangleSelector::undivide_triangle(int facet_idx) for (int i=0; i<=tr.number_of_split_sides(); ++i) { undivide_triangle(tr.children[i]); m_triangles[tr.children[i]].valid = false; + ++m_invalid_triangles; } tr.set_division(0); // not split } @@ -1138,7 +1152,7 @@ void TriangleSelector::remove_useless_children(int facet_idx) // Return if a child is not leaf or two children differ in type. - FacetSupportType first_child_type; + FacetSupportType first_child_type = FacetSupportType::NONE; for (int child_idx=0; child_idx<=tr.number_of_split_sides(); ++child_idx) { if (m_triangles[tr.children[child_idx]].is_split()) return; @@ -1160,13 +1174,26 @@ void TriangleSelector::garbage_collect() // First make a map from old to new triangle indices. int new_idx = m_orig_size_indices; std::vector new_triangle_indices(m_triangles.size(), -1); - std::vector invalid_vertices(m_vertices.size(), false); for (int i = m_orig_size_indices; i new_vertices_indices(m_vertices.size(), -1); + for (int i=m_orig_size_vertices; i= 0); + if (m_vertices[i].ref_cnt != 0) { + new_vertices_indices[i] = new_idx; + ++new_idx; } } @@ -1174,6 +1201,9 @@ void TriangleSelector::garbage_collect() m_triangles.erase(std::remove_if(m_triangles.begin()+m_orig_size_indices, m_triangles.end(), [](const Triangle& tr) { return ! tr.valid; }), m_triangles.end()); + m_vertices.erase(std::remove_if(m_vertices.begin()+m_orig_size_vertices, m_vertices.end(), + [](const Vertex& vert) { return vert.ref_cnt == 0; }), + m_vertices.end()); // Now go through all remaining triangles and update changed indices. for (Triangle& tr : m_triangles) { @@ -1187,20 +1217,32 @@ void TriangleSelector::garbage_collect() } } + // Update indices into m_vertices. The original vertices are never + // touched and need not be reindexed. + for (int& idx : tr.verts_idxs) { + if (idx >= m_orig_size_vertices) { + assert(new_vertices_indices[idx] != -1); + idx = new_vertices_indices[idx]; + } + } + // If this triangle was split before, forget it. // Children referenced in the cache are dead by now. tr.forget_history(); } + + m_invalid_triangles = 0; } TriangleSelector::TriangleSelector(const TriangleMesh& mesh) { for (const stl_vertex& vert : mesh.its.vertices) - m_vertices.push_back(vert); + m_vertices.emplace_back(vert); for (const stl_triangle_vertex_indices& ind : mesh.its.indices) - m_triangles.emplace_back(Triangle(ind[0], ind[1], ind[2])); + push_triangle(ind[0], ind[1], ind[2]); m_orig_size_vertices = m_vertices.size(); m_orig_size_indices = m_triangles.size(); + m_invalid_triangles = 0; m_mesh = &mesh; } @@ -1222,9 +1264,9 @@ void TriangleSelector::render(ImGuiWrapper* imgui) ::glColor4f(1.f, 0.f, 0.f, 0.2f); for (int i=0; i<3; ++i) - ::glVertex3f(m_vertices[tr.verts_idxs[i]][0], - m_vertices[tr.verts_idxs[i]][1], - m_vertices[tr.verts_idxs[i]][2]); + ::glVertex3f(m_vertices[tr.verts_idxs[i]].v[0], + m_vertices[tr.verts_idxs[i]].v[1], + m_vertices[tr.verts_idxs[i]].v[2]); } ::glEnd(); @@ -1250,6 +1292,18 @@ void TriangleSelector::set_edge_limit(float edge_limit) } } + + +void TriangleSelector::push_triangle(int a, int b, int c) +{ + for (int i : {a, b, c}) { + assert(i >= 0 && i < int(m_vertices.size())); + ++m_vertices[i].ref_cnt; + } + m_triangles.emplace_back(a, b, c); +} + + #ifdef PRUSASLICER_TRIANGLE_SELECTOR_DEBUG void TriangleSelector::render_debug(ImGuiWrapper* imgui) { @@ -1262,11 +1316,10 @@ void TriangleSelector::render_debug(ImGuiWrapper* imgui) imgui->checkbox("Show split triangles: ", m_show_triangles); imgui->checkbox("Show invalid triangles: ", m_show_invalid); - int valid_triangles = std::count_if(m_triangles.begin(), m_triangles.end(), - [](const Triangle& tr) { return tr.valid; }); + int valid_triangles = m_triangles.size() - m_invalid_triangles; imgui->text("Valid triangles: " + std::to_string(valid_triangles) + "/" + std::to_string(m_triangles.size())); - imgui->text("Number of vertices: " + std::to_string(m_vertices.size())); + imgui->text("Vertices: " + std::to_string(m_vertices.size())); if (imgui->button("Force garbage collection")) garbage_collect(); @@ -1290,9 +1343,9 @@ void TriangleSelector::render_debug(ImGuiWrapper* imgui) ::glColor3f(0.f, 0.f, 1.f); for (int i=0; i<3; ++i) - ::glVertex3f(m_vertices[tr.verts_idxs[i]][0], - m_vertices[tr.verts_idxs[i]][1], - m_vertices[tr.verts_idxs[i]][2]); + ::glVertex3f(m_vertices[tr.verts_idxs[i]].v[0], + m_vertices[tr.verts_idxs[i]].v[1], + m_vertices[tr.verts_idxs[i]].v[2]); } ::glEnd(); ::glPolygonMode( GL_FRONT_AND_BACK, GL_FILL ); diff --git a/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.hpp b/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.hpp index fa03f7987..fb14f3c5e 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.hpp @@ -33,7 +33,7 @@ public: // stay valid, a ptr to it is saved and used. explicit TriangleSelector(const TriangleMesh& mesh); - // Select all triangles inside the circle, subdivide where needed. + // Select all triangles fully inside the circle, subdivide where needed. void select_patch(const Vec3f& hit, // point where to start int facet_start, // facet that point belongs to const Vec3f& source, // camera position (mesh coords) @@ -41,14 +41,11 @@ public: float radius_sqr, // squared radius of the cursor FacetSupportType new_state); // enforcer or blocker? - void unselect_all(); - // Render current selection. Transformation matrices are supposed // to be already set. void render(ImGuiWrapper* imgui = nullptr); - // Remove all unnecessary data (such as vertices that are not needed - // because the selection has been made larger. + // Remove all unnecessary data. void garbage_collect(); #ifdef PRUSASLICER_TRIANGLE_SELECTOR_DEBUG @@ -59,21 +56,24 @@ public: private: // Triangle and info about how it's split. - struct Triangle { + class Triangle { public: + // Use TriangleSelector::push_triangle to create a new triangle. + // It increments/decrements reference counter on vertices. Triangle(int a, int b, int c) - : verts_idxs{stl_triangle_vertex_indices(a, b, c)}, + : verts_idxs{a, b, c}, state{FacetSupportType(0)}, number_of_splits{0}, special_side_idx{0}, old_number_of_splits{0} {} - stl_triangle_vertex_indices verts_idxs; + // Indices into m_vertices. + std::array verts_idxs; - // Is this triangle valid or marked to remove? + // Is this triangle valid or marked to be removed? bool valid{true}; - // Children triangles (0 = no child) + // Children triangles. std::array children; // Set the division type. @@ -101,22 +101,31 @@ private: int old_number_of_splits; }; + struct Vertex { + explicit Vertex(const stl_vertex& vert) + : v{vert}, + ref_cnt{0} + {} + stl_vertex v; + int ref_cnt; + }; + // Lists of vertices and triangles, both original and new - std::vector m_vertices; + std::vector m_vertices; std::vector m_triangles; const TriangleMesh* m_mesh; + // Number of invalid triangles (to trigger garbage collection). + int m_invalid_triangles; + + // Limiting length of triangle side (squared). float m_edge_limit_sqr = 1.f; // Number of original vertices and triangles. int m_orig_size_vertices; int m_orig_size_indices; - // Limits for stopping the recursion. - float m_max_edge_length; - int m_max_recursion_depth; - - // Caches for cursor position, radius and direction. + // Cache for cursor position, radius and direction. struct Cursor { Vec3f center; Vec3f source; @@ -130,13 +139,14 @@ private: bool select_triangle(int facet_idx, FacetSupportType type, bool recursive_call = false); bool is_point_inside_cursor(const Vec3f& point) const; - int vertices_inside(int facet_idx) const; + int vertices_inside(int facet_idx) const; bool faces_camera(int facet) const; void undivide_triangle(int facet_idx); void split_triangle(int facet_idx); void remove_useless_children(int facet_idx); // No hidden meaning. Triangles are meant. bool is_pointer_in_triangle(int facet_idx) const; bool is_edge_inside_cursor(int facet_idx) const; + void push_triangle(int a, int b, int c); }; From 814f8be92f410e2cd5a7928a64a5a032072da342 Mon Sep 17 00:00:00 2001 From: Lukas Matena Date: Wed, 1 Jul 2020 09:09:25 +0200 Subject: [PATCH 11/24] TriangleSelector: getting ready for frontend/backend separation --- src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.cpp | 279 +++++++++++++------ src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.hpp | 11 +- 2 files changed, 207 insertions(+), 83 deletions(-) diff --git a/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.cpp b/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.cpp index 10d045920..f2a6bb8ad 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.cpp @@ -972,63 +972,12 @@ void TriangleSelector::split_triangle(int facet_idx) return; } - // indices of triangle vertices - std::vector verts_idxs; - int idx = sides_to_split.size() == 2 ? side_to_keep : sides_to_split[0]; - for (int j=0; j<3; ++j) { - verts_idxs.push_back(facet[idx++]); - if (idx == 3) - idx = 0; - } - - - if (sides_to_split.size() == 1) { - m_vertices.emplace_back((m_vertices[verts_idxs[1]].v + m_vertices[verts_idxs[2]].v)/2.); - verts_idxs.insert(verts_idxs.begin()+2, m_vertices.size() - 1); - - push_triangle(verts_idxs[0], verts_idxs[1], verts_idxs[2]); - push_triangle(verts_idxs[2], verts_idxs[3], verts_idxs[0]); - } - - if (sides_to_split.size() == 2) { - m_vertices.emplace_back((m_vertices[verts_idxs[0]].v + m_vertices[verts_idxs[1]].v)/2.); - verts_idxs.insert(verts_idxs.begin()+1, m_vertices.size() - 1); - - m_vertices.emplace_back((m_vertices[verts_idxs[0]].v + m_vertices[verts_idxs[3]].v)/2.); - verts_idxs.insert(verts_idxs.begin()+4, m_vertices.size() - 1); - - push_triangle(verts_idxs[0], verts_idxs[1], verts_idxs[4]); - push_triangle(verts_idxs[1], verts_idxs[2], verts_idxs[4]); - push_triangle(verts_idxs[2], verts_idxs[3], verts_idxs[4]); - } - - if (sides_to_split.size() == 3) { - m_vertices.emplace_back((m_vertices[verts_idxs[0]].v + m_vertices[verts_idxs[1]].v)/2.); - verts_idxs.insert(verts_idxs.begin()+1, m_vertices.size() - 1); - m_vertices.emplace_back((m_vertices[verts_idxs[2]].v + m_vertices[verts_idxs[3]].v)/2.); - verts_idxs.insert(verts_idxs.begin()+3, m_vertices.size() - 1); - m_vertices.emplace_back((m_vertices[verts_idxs[4]].v + m_vertices[verts_idxs[0]].v)/2.); - verts_idxs.insert(verts_idxs.begin()+5, m_vertices.size() - 1); - - push_triangle(verts_idxs[0], verts_idxs[1], verts_idxs[5]); - push_triangle(verts_idxs[1], verts_idxs[2], verts_idxs[3]); - push_triangle(verts_idxs[3], verts_idxs[4], verts_idxs[5]); - push_triangle(verts_idxs[1], verts_idxs[3], verts_idxs[5]); - } - - tr = &m_triangles[facet_idx]; // may have been invalidated - - // Save how the triangle was split. Second argument makes sense only for one + // Save how the triangle will be split. Second argument makes sense only for one // or two split sides, otherwise the value is ignored. tr->set_division(sides_to_split.size(), sides_to_split.size() == 2 ? side_to_keep : sides_to_split[0]); - // And save the children. All children should start in the same state as the triangle we just split. - assert(! sides_to_split.empty() && int(sides_to_split.size()) <= 3); - for (int i=0; i<=int(sides_to_split.size()); ++i) { - tr->children[i] = m_triangles.size()-1-i; - m_triangles[tr->children[i]].set_state(old_type); - } + perform_split(facet_idx, old_type); } @@ -1253,22 +1202,45 @@ void TriangleSelector::render(ImGuiWrapper* imgui) ::glScalef(1.005f, 1.005f, 1.005f); - ::glBegin( GL_TRIANGLES); + int enf_cnt = 0; + int blc_cnt = 0; + for (const Triangle& tr : m_triangles) { if (! tr.valid || tr.is_split() || tr.get_state() == FacetSupportType::NONE) continue; - if (tr.get_state() == FacetSupportType::ENFORCER) - ::glColor4f(0.f, 0.f, 1.f, 0.2f); - else - ::glColor4f(1.f, 0.f, 0.f, 0.2f); + GLIndexedVertexArray& va = tr.get_state() == FacetSupportType::ENFORCER + ? m_iva_enforcers + : m_iva_blockers; + int& cnt = tr.get_state() == FacetSupportType::ENFORCER + ? enf_cnt + : blc_cnt; for (int i=0; i<3; ++i) - ::glVertex3f(m_vertices[tr.verts_idxs[i]].v[0], - m_vertices[tr.verts_idxs[i]].v[1], - m_vertices[tr.verts_idxs[i]].v[2]); + va.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]), + 0., 0., 1.); + va.push_triangle(cnt, + cnt+1, + cnt+2); + cnt += 3; } - ::glEnd(); + + m_iva_enforcers.finalize_geometry(true); + m_iva_blockers.finalize_geometry(true); + + if (m_iva_enforcers.has_VBOs()) { + ::glColor4f(0.f, 0.f, 1.f, 0.2f); + m_iva_enforcers.render(); + } + m_iva_enforcers.release_geometry(); + + if (m_iva_blockers.has_VBOs()) { + ::glColor4f(1.f, 0.f, 0.f, 0.2f); + m_iva_blockers.render(); + } + m_iva_blockers.release_geometry(); #ifdef PRUSASLICER_TRIANGLE_SELECTOR_DEBUG if (imgui) @@ -1304,6 +1276,110 @@ void TriangleSelector::push_triangle(int a, int b, int c) } +void TriangleSelector::perform_split(int facet_idx, FacetSupportType old_state) +{ + Triangle* tr = &m_triangles[facet_idx]; + + assert(tr->is_split()); + + // Read info about how to split this triangle. + int sides_to_split = tr->number_of_split_sides(); + + // indices of triangle vertices + std::vector verts_idxs; + int idx = tr->special_side(); + for (int j=0; j<3; ++j) { + verts_idxs.push_back(tr->verts_idxs[idx++]); + if (idx == 3) + idx = 0; + } + + if (sides_to_split == 1) { + m_vertices.emplace_back((m_vertices[verts_idxs[1]].v + m_vertices[verts_idxs[2]].v)/2.); + verts_idxs.insert(verts_idxs.begin()+2, m_vertices.size() - 1); + + push_triangle(verts_idxs[0], verts_idxs[1], verts_idxs[2]); + push_triangle(verts_idxs[2], verts_idxs[3], verts_idxs[0]); + } + + if (sides_to_split == 2) { + m_vertices.emplace_back((m_vertices[verts_idxs[0]].v + m_vertices[verts_idxs[1]].v)/2.); + verts_idxs.insert(verts_idxs.begin()+1, m_vertices.size() - 1); + + m_vertices.emplace_back((m_vertices[verts_idxs[0]].v + m_vertices[verts_idxs[3]].v)/2.); + verts_idxs.insert(verts_idxs.begin()+4, m_vertices.size() - 1); + + push_triangle(verts_idxs[0], verts_idxs[1], verts_idxs[4]); + push_triangle(verts_idxs[1], verts_idxs[2], verts_idxs[4]); + push_triangle(verts_idxs[2], verts_idxs[3], verts_idxs[4]); + } + + if (sides_to_split == 3) { + m_vertices.emplace_back((m_vertices[verts_idxs[0]].v + m_vertices[verts_idxs[1]].v)/2.); + verts_idxs.insert(verts_idxs.begin()+1, m_vertices.size() - 1); + m_vertices.emplace_back((m_vertices[verts_idxs[2]].v + m_vertices[verts_idxs[3]].v)/2.); + verts_idxs.insert(verts_idxs.begin()+3, m_vertices.size() - 1); + m_vertices.emplace_back((m_vertices[verts_idxs[4]].v + m_vertices[verts_idxs[0]].v)/2.); + verts_idxs.insert(verts_idxs.begin()+5, m_vertices.size() - 1); + + push_triangle(verts_idxs[0], verts_idxs[1], verts_idxs[5]); + push_triangle(verts_idxs[1], verts_idxs[2], verts_idxs[3]); + push_triangle(verts_idxs[3], verts_idxs[4], verts_idxs[5]); + push_triangle(verts_idxs[1], verts_idxs[3], verts_idxs[5]); + } + + tr = &m_triangles[facet_idx]; // may have been invalidated + + // And save the children. All children should start in the same state as the triangle we just split. + assert(sides_to_split <= 3); + for (int i=0; i<=sides_to_split; ++i) { + tr->children[i] = m_triangles.size()-1-i; + m_triangles[tr->children[i]].set_state(old_state); + } +} + + +std::map TriangleSelector::serialize() const +{ + std::map out; + for (int i=0; i serialize_recursive; + serialize_recursive = [this, &stored_triangles, &data, &serialize_recursive](int facet_idx) { + const Triangle& tr = m_triangles[facet_idx]; + int split_sides = tr.number_of_split_sides(); + assert( split_sides > 0 && split_sides <= 3); + data |= (split_sides << (stored_triangles * 4)); + + if (tr.is_split()) { + assert(split_sides > 0); + assert(tr.special_side() >= 0 && tr.special_side() <= 3); + data |= (tr.special_side() << (stored_triangles * 4 + 2)); + ++stored_triangles; + for (int child_idx=0; child_idx<=split_sides; ++child_idx) + serialize_recursive(tr.children[child_idx]); + } else { + assert(int8_t(tr.get_state()) <= 3); + data |= (int8_t(tr.get_state()) << (stored_triangles * 4 + 2)); + ++stored_triangles; + } + }; + + serialize_recursive(i); + out[i] = data; + } + + return out; +} + + #ifdef PRUSASLICER_TRIANGLE_SELECTOR_DEBUG void TriangleSelector::render_debug(ImGuiWrapper* imgui) { @@ -1323,33 +1399,74 @@ void TriangleSelector::render_debug(ImGuiWrapper* imgui) if (imgui->button("Force garbage collection")) garbage_collect(); + if (imgui->button("Serialize")) { + auto map = serialize(); + for (auto& [idx, data] : map) + std::cout << idx << "\t" << data << std::endl; + } + imgui->end(); - if (m_show_triangles) { - ::glPolygonMode( GL_FRONT_AND_BACK, GL_LINE ); + if (! m_show_triangles) + return; - ::glBegin( GL_TRIANGLES); - for (int tr_id=0; tr_id cnts; - for (int i=0; i<3; ++i) - ::glVertex3f(m_vertices[tr.verts_idxs[i]].v[0], - m_vertices[tr.verts_idxs[i]].v[1], - m_vertices[tr.verts_idxs[i]].v[2]); + ::glScalef(1.01f, 1.01f, 1.01f); + + for (int tr_id=0; tr_idpush_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]), + 0., 0., 1.); + va->push_triangle(*cnt, + *cnt+1, + *cnt+2); + *cnt += 3; } + + ::glPolygonMode( GL_FRONT_AND_BACK, GL_LINE ); + for (vtype i : {ORIGINAL, SPLIT, INVALID}) { + GLIndexedVertexArray& va = m_varrays[i]; + va.finalize_geometry(true); + if (va.has_VBOs()) { + switch (i) { + case ORIGINAL : ::glColor3f(0.f, 0.f, 1.f); break; + case SPLIT : ::glColor3f(1.f, 0.f, 0.f); break; + case INVALID : ::glColor3f(1.f, 1.f, 0.f); break; + } + va.render(); + } + } + ::glPolygonMode( GL_FRONT_AND_BACK, GL_FILL ); } #endif diff --git a/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.hpp b/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.hpp index fb14f3c5e..f3a66eca9 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.hpp @@ -48,6 +48,9 @@ public: // Remove all unnecessary data. void garbage_collect(); + // Store the division trees in compact form. + std::map serialize() const; + #ifdef PRUSASLICER_TRIANGLE_SELECTOR_DEBUG void render_debug(ImGuiWrapper* imgui); bool m_show_triangles{true}; @@ -86,8 +89,7 @@ private: // Get info on how it's split. bool is_split() const { return number_of_split_sides() != 0; } int number_of_split_sides() const { return number_of_splits; } - int side_to_keep() const { assert(number_of_split_sides() == 2); return special_side_idx; } - int side_to_split() const { assert(number_of_split_sides() == 1); return special_side_idx; } + int special_side() const { assert(is_split()); return special_side_idx; } bool was_split_before() const { return old_number_of_splits != 0; } void forget_history() { old_number_of_splits = 0; } @@ -115,6 +117,10 @@ private: std::vector m_triangles; const TriangleMesh* m_mesh; + GLIndexedVertexArray m_iva_enforcers; + GLIndexedVertexArray m_iva_blockers; + std::array m_varrays; + // Number of invalid triangles (to trigger garbage collection). int m_invalid_triangles; @@ -147,6 +153,7 @@ private: bool is_pointer_in_triangle(int facet_idx) const; bool is_edge_inside_cursor(int facet_idx) const; void push_triangle(int a, int b, int c); + void perform_split(int facet_idx, FacetSupportType old_state); }; From b250c08ec9c76fe3e1895e3ed40536c4c884ecb4 Mon Sep 17 00:00:00 2001 From: Lukas Matena Date: Thu, 2 Jul 2020 12:30:12 +0200 Subject: [PATCH 12/24] TriangleSelector: Serialization and deserialization --- src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.cpp | 153 ++++++++++++++++--- src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.hpp | 11 +- 2 files changed, 143 insertions(+), 21 deletions(-) diff --git a/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.cpp b/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.cpp index f2a6bb8ad..01427ec09 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.cpp @@ -1184,15 +1184,26 @@ void TriangleSelector::garbage_collect() } TriangleSelector::TriangleSelector(const TriangleMesh& mesh) + : m_mesh{&mesh} { - for (const stl_vertex& vert : mesh.its.vertices) + reset(); + +} + + +void TriangleSelector::reset() +{ + if (! m_orig_size_indices != 0) // unless this is run from constructor + garbage_collect(); + m_vertices.clear(); + m_triangles.clear(); + for (const stl_vertex& vert : m_mesh->its.vertices) m_vertices.emplace_back(vert); - for (const stl_triangle_vertex_indices& ind : mesh.its.indices) + for (const stl_triangle_vertex_indices& ind : m_mesh->its.indices) push_triangle(ind[0], ind[1], ind[2]); m_orig_size_vertices = m_vertices.size(); m_orig_size_indices = m_triangles.size(); m_invalid_triangles = 0; - m_mesh = &mesh; } void TriangleSelector::render(ImGuiWrapper* imgui) @@ -1339,35 +1350,56 @@ void TriangleSelector::perform_split(int facet_idx, FacetSupportType old_state) } -std::map TriangleSelector::serialize() const +std::map> TriangleSelector::serialize() const { - std::map out; + // Each original triangle of the mesh is assigned a number encoding its state + // or how it is split. Each triangle is encoded by 4 bits (xxyy): + // leaf triangle: xx = FacetSupportType, yy = 0 + // non-leaf: xx = special side, yy = number of split sides + // These are bitwise appended and formed into one 64-bit integer. + + // The function returns a map from original triangle indices to + // stream of bits encoding state and offsprings. + + std::map> out; for (int i=0; i data; // complete encoding of this mesh triangle + int stored_triangles = 0; // how many have been already encoded std::function serialize_recursive; - serialize_recursive = [this, &stored_triangles, &data, &serialize_recursive](int facet_idx) { + serialize_recursive = [this, &serialize_recursive, &stored_triangles, &data](int facet_idx) { const Triangle& tr = m_triangles[facet_idx]; + + // Always save number of split sides. It is zero for unsplit triangles. int split_sides = tr.number_of_split_sides(); - assert( split_sides > 0 && split_sides <= 3); - data |= (split_sides << (stored_triangles * 4)); + assert(split_sides >= 0 && split_sides <= 3); + + //data |= (split_sides << (stored_triangles * 4)); + data.push_back(split_sides & 0b01); + data.push_back(split_sides & 0b10); if (tr.is_split()) { + // If this triangle is split, save which side is split (in case + // of one split) or kept (in case of two splits). The value will + // be ignored for 3-side split. assert(split_sides > 0); assert(tr.special_side() >= 0 && tr.special_side() <= 3); - data |= (tr.special_side() << (stored_triangles * 4 + 2)); + data.push_back(tr.special_side() & 0b01); + data.push_back(tr.special_side() & 0b10); ++stored_triangles; + // Now save all children. for (int child_idx=0; child_idx<=split_sides; ++child_idx) serialize_recursive(tr.children[child_idx]); } else { - assert(int8_t(tr.get_state()) <= 3); - data |= (int8_t(tr.get_state()) << (stored_triangles * 4 + 2)); + // In case this is leaf, we better save information about its state. + assert(int(tr.get_state()) <= 3); + data.push_back(int(tr.get_state()) & 0b01); + data.push_back(int(tr.get_state()) & 0b10); ++stored_triangles; } }; @@ -1379,6 +1411,90 @@ std::map TriangleSelector::serialize() const return out; } +void TriangleSelector::deserialize(const std::map> data) +{ + reset(); // dump any current state + for (const auto& [triangle_id, code] : data) { + assert(triangle_id < int(m_triangles.size())); + int processed_triangles = 0; + struct ProcessingInfo { + int facet_id = 0; + int processed_children = 0; + int total_children = 0; + }; + + // Vector to store all parents that have offsprings. + std::vector parents; + + while (true) { + // Read next triangle info. + int next_code = 0; + for (int i=3; i>=0; --i) { + next_code = next_code << 1; + next_code |= int(code[4 * processed_triangles + i]); + } + ++processed_triangles; + + int num_of_split_sides = (next_code & 0b11); + int num_of_children = num_of_split_sides != 0 ? num_of_split_sides + 1 : 0; + bool is_split = num_of_children != 0; + FacetSupportType state = FacetSupportType(next_code >> 2); + int special_side = (next_code >> 2); + + // Take care of the first iteration separately, so handling of the others is simpler. + if (parents.empty()) { + if (! is_split) { + // root is not split. just set the state and that's it. + m_triangles[triangle_id].set_state(state); + break; + } else { + // root is split, add it into list of parents and split it. + // then go to the next. + parents.push_back({triangle_id, 0, num_of_children}); + m_triangles[triangle_id].set_division(num_of_children-1, special_side); + perform_split(triangle_id, FacetSupportType::NONE); + continue; + } + } + + // This is not the first iteration. This triangle is a child of last seen parent. + assert(! parents.empty()); + assert(parents.back().processed_children < parents.back().total_children); + + if (is_split) { + // split the triangle and save it as parent of the next ones. + const ProcessingInfo& last = parents.back(); + int this_idx = m_triangles[last.facet_id].children[last.processed_children]; + m_triangles[this_idx].set_division(num_of_children-1, special_side); + perform_split(this_idx, FacetSupportType::NONE); + parents.push_back({this_idx, 0, num_of_children}); + } else { + // this triangle belongs to last split one + m_triangles[m_triangles[parents.back().facet_id].children[parents.back().processed_children]].set_state(state); + ++parents.back().processed_children; + } + + + // If all children of the past parent triangle are claimed, move to grandparent. + while (parents.back().processed_children == parents.back().total_children) { + parents.pop_back(); + + if (parents.empty()) + break; + + // And increment the grandparent children counter, because + // we have just finished that branch and got back here. + ++parents.back().processed_children; + } + + // In case we popped back the root, we should be done. + if (parents.empty()) + break; + } + + } +} + #ifdef PRUSASLICER_TRIANGLE_SELECTOR_DEBUG void TriangleSelector::render_debug(ImGuiWrapper* imgui) @@ -1399,10 +1515,9 @@ void TriangleSelector::render_debug(ImGuiWrapper* imgui) if (imgui->button("Force garbage collection")) garbage_collect(); - if (imgui->button("Serialize")) { + if (imgui->button("Serialize - deserialize")) { auto map = serialize(); - for (auto& [idx, data] : map) - std::cout << idx << "\t" << data << std::endl; + deserialize(map); } imgui->end(); diff --git a/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.hpp b/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.hpp index f3a66eca9..099c9a30c 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.hpp @@ -45,11 +45,18 @@ public: // to be already set. void render(ImGuiWrapper* imgui = nullptr); + // Clear everything and make the tree empty. + void reset(); + // Remove all unnecessary data. void garbage_collect(); - // Store the division trees in compact form. - std::map serialize() const; + // Store the division trees in compact form (a long stream of + // bits for each triangle of the original mesh). + std::map> serialize() const; + + // Load serialized data. Assumes that correct mesh is loaded. + void deserialize(const std::map> data); #ifdef PRUSASLICER_TRIANGLE_SELECTOR_DEBUG void render_debug(ImGuiWrapper* imgui); From 6baff45759bc9dd69e2a836bef59f62101d58f77 Mon Sep 17 00:00:00 2001 From: Lukas Matena Date: Thu, 9 Jul 2020 16:25:34 +0200 Subject: [PATCH 13/24] TriangleSelector: Separated frontend/backend, support of multiple volumes, etc. --- src/libslic3r/CMakeLists.txt | 2 + src/libslic3r/Model.cpp | 19 +- src/libslic3r/Model.hpp | 6 +- src/libslic3r/TriangleSelector.cpp | 654 ++++++++++++++++ src/libslic3r/TriangleSelector.hpp | 149 ++++ src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.cpp | 736 +------------------ src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.hpp | 145 +--- 7 files changed, 856 insertions(+), 855 deletions(-) create mode 100644 src/libslic3r/TriangleSelector.cpp create mode 100644 src/libslic3r/TriangleSelector.hpp diff --git a/src/libslic3r/CMakeLists.txt b/src/libslic3r/CMakeLists.txt index 881466b39..9f566b405 100644 --- a/src/libslic3r/CMakeLists.txt +++ b/src/libslic3r/CMakeLists.txt @@ -187,6 +187,8 @@ add_library(libslic3r STATIC Utils.hpp Time.cpp Time.hpp + TriangleSelector.cpp + TriangleSelector.hpp MTUtils.hpp VoronoiOffset.cpp VoronoiOffset.hpp diff --git a/src/libslic3r/Model.cpp b/src/libslic3r/Model.cpp index 0719cac8c..b6bae489b 100644 --- a/src/libslic3r/Model.cpp +++ b/src/libslic3r/Model.cpp @@ -2,6 +2,7 @@ #include "ModelArrange.hpp" #include "Geometry.hpp" #include "MTUtils.hpp" +#include "TriangleSelector.hpp" #include "Format/AMF.hpp" #include "Format/OBJ.hpp" @@ -1833,25 +1834,21 @@ arrangement::ArrangePolygon ModelInstance::get_arrange_polygon() const std::vector FacetsAnnotation::get_facets(FacetSupportType type) const { std::vector out; - for (auto& [facet_idx, this_type] : m_data) + /*for (auto& [facet_idx, this_type] : m_data) if (this_type == type) out.push_back(facet_idx); - return out; + */return out; } -void FacetsAnnotation::set_facet(int idx, FacetSupportType type) +void FacetsAnnotation::set(const TriangleSelector& selector) { - bool changed = true; - - if (type == FacetSupportType::NONE) - changed = m_data.erase(idx) != 0; - else - m_data[idx] = type; - - if (changed) + std::map> sel_map = selector.serialize(); + if (sel_map != m_data) { + m_data = sel_map; update_timestamp(); + } } diff --git a/src/libslic3r/Model.hpp b/src/libslic3r/Model.hpp index be298ae4b..de20e0fdc 100644 --- a/src/libslic3r/Model.hpp +++ b/src/libslic3r/Model.hpp @@ -39,6 +39,7 @@ class ModelVolume; class ModelWipeTower; class Print; class SLAPrint; +class TriangleSelector; namespace UndoRedo { class StackImpl; @@ -404,8 +405,9 @@ class FacetsAnnotation { public: using ClockType = std::chrono::steady_clock; + const std::map>& get_data() const { return m_data; } + void set(const TriangleSelector& selector); std::vector get_facets(FacetSupportType type) const; - void set_facet(int idx, FacetSupportType type); void clear(); ClockType::time_point get_timestamp() const { return timestamp; } @@ -419,7 +421,7 @@ public: } private: - std::map m_data; + std::map> m_data; ClockType::time_point timestamp; void update_timestamp() { diff --git a/src/libslic3r/TriangleSelector.cpp b/src/libslic3r/TriangleSelector.cpp new file mode 100644 index 000000000..340f5a29a --- /dev/null +++ b/src/libslic3r/TriangleSelector.cpp @@ -0,0 +1,654 @@ +#include "TriangleSelector.hpp" +#include "Model.hpp" + + +namespace Slic3r { + + + +// sides_to_split==-1 : just restore previous split +void TriangleSelector::Triangle::set_division(int sides_to_split, int special_side_idx) +{ + assert(sides_to_split >=-1 && sides_to_split <= 3); + assert(special_side_idx >=-1 && special_side_idx < 3); + + // If splitting one or two sides, second argument must be provided. + assert(sides_to_split != 1 || special_side_idx != -1); + assert(sides_to_split != 2 || special_side_idx != -1); + + if (sides_to_split != -1) { + this->number_of_splits = sides_to_split; + if (sides_to_split != 0) { + assert(old_number_of_splits == 0); + this->special_side_idx = special_side_idx; + this->old_number_of_splits = sides_to_split; + } + } + else { + assert(old_number_of_splits != 0); + this->number_of_splits = old_number_of_splits; + // indices of children should still be there. + } +} + + + +void TriangleSelector::select_patch(const Vec3f& hit, int facet_start, + const Vec3f& source, const Vec3f& dir, + float radius_sqr, FacetSupportType new_state) +{ + assert(facet_start < m_orig_size_indices); + assert(is_approx(dir.norm(), 1.f)); + + // Save current cursor center, squared radius and camera direction, + // so we don't have to pass it around. + m_cursor = {hit, source, dir, radius_sqr}; + + // Now start with the facet the pointer points to and check all adjacent facets. + std::vector facets_to_check{facet_start}; + std::vector visited(m_orig_size_indices, false); // keep track of facets we already processed + int facet_idx = 0; // index into facets_to_check + while (facet_idx < int(facets_to_check.size())) { + int facet = facets_to_check[facet_idx]; + if (! visited[facet]) { + if (select_triangle(facet, new_state)) { + // add neighboring facets to list to be proccessed later + for (int n=0; n<3; ++n) { + if (faces_camera(m_mesh->stl.neighbors_start[facet].neighbor[n])) + facets_to_check.push_back(m_mesh->stl.neighbors_start[facet].neighbor[n]); + } + } + } + visited[facet] = true; + ++facet_idx; + } +} + + + +// 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 +// outside the cursor. +bool TriangleSelector::select_triangle(int facet_idx, FacetSupportType type, bool recursive_call) +{ + assert(facet_idx < int(m_triangles.size())); + + Triangle* tr = &m_triangles[facet_idx]; + if (! tr->valid) + return false; + + int num_of_inside_vertices = vertices_inside(facet_idx); + + if (num_of_inside_vertices == 0 + && ! is_pointer_in_triangle(facet_idx) + && ! is_edge_inside_cursor(facet_idx)) + return false; + + if (num_of_inside_vertices == 3) { + // dump any subdivision and select whole triangle + undivide_triangle(facet_idx); + tr->set_state(type); + } else { + // the triangle is partially inside, let's recursively divide it + // (if not already) and try selecting its children. + + if (! tr->is_split() && tr->get_state() == type) { + // This is leaf triangle that is already of correct type as a whole. + // No need to split, all children would end up selected anyway. + return true; + } + + split_triangle(facet_idx); + tr = &m_triangles[facet_idx]; // might have been invalidated + + + int num_of_children = tr->number_of_split_sides() + 1; + if (num_of_children != 1) { + for (int i=0; ichildren.size())); + assert(tr->children[i] < int(m_triangles.size())); + + select_triangle(tr->children[i], type, true); + tr = &m_triangles[facet_idx]; // might have been invalidated + } + } + } + + if (! recursive_call) { + // In case that all children are leafs and have the same state now, + // they may be removed and substituted by the parent triangle. + remove_useless_children(facet_idx); + + // Make sure that we did not lose track of invalid triangles. + assert(m_invalid_triangles == std::count_if(m_triangles.begin(), m_triangles.end(), + [](const Triangle& tr) { return ! tr.valid; })); + + // Do garbage collection maybe? + if (2*m_invalid_triangles > int(m_triangles.size())) + garbage_collect(); + } + return true; +} + + +void TriangleSelector::split_triangle(int facet_idx) +{ + if (m_triangles[facet_idx].is_split()) { + // The triangle is divided already. + return; + } + + Triangle* tr = &m_triangles[facet_idx]; + + FacetSupportType old_type = tr->get_state(); + + if (tr->was_split_before() != 0) { + // This triangle is not split at the moment, but was at one point + // in history. We can just restore it and resurrect its children. + tr->set_division(-1); + for (int i=0; i<=tr->number_of_split_sides(); ++i) { + m_triangles[tr->children[i]].set_state(old_type); + m_triangles[tr->children[i]].valid = true; + --m_invalid_triangles; + } + return; + } + + // If we got here, we are about to actually split the triangle. + const double limit_squared = m_edge_limit_sqr; + + std::array& facet = tr->verts_idxs; + const stl_vertex* pts[3] = { &m_vertices[facet[0]].v, &m_vertices[facet[1]].v, &m_vertices[facet[2]].v}; + double sides[3] = { (*pts[2]-*pts[1]).squaredNorm(), + (*pts[0]-*pts[2]).squaredNorm(), + (*pts[1]-*pts[0]).squaredNorm() }; + + std::vector sides_to_split; + int side_to_keep = -1; + for (int pt_idx = 0; pt_idx<3; ++pt_idx) { + if (sides[pt_idx] > limit_squared) + sides_to_split.push_back(pt_idx); + else + side_to_keep = pt_idx; + } + if (sides_to_split.empty()) { + // This shall be unselected. + tr->set_division(0); + return; + } + + // Save how the triangle will be split. Second argument makes sense only for one + // or two split sides, otherwise the value is ignored. + tr->set_division(sides_to_split.size(), + sides_to_split.size() == 2 ? side_to_keep : sides_to_split[0]); + + perform_split(facet_idx, old_type); +} + + +// Calculate distance of a point from a line. +bool TriangleSelector::is_point_inside_cursor(const Vec3f& point) const +{ + Vec3f diff = m_cursor.center - point; + return (diff - diff.dot(m_cursor.dir) * m_cursor.dir).squaredNorm() < m_cursor.radius_sqr; +} + + +// Is pointer in a triangle? +bool TriangleSelector::is_pointer_in_triangle(int facet_idx) const +{ + auto signed_volume_sign = [](const Vec3f& a, const Vec3f& b, + const Vec3f& c, const Vec3f& d) -> bool { + return ((b-a).cross(c-a)).dot(d-a) > 0.; + }; + + const Vec3f& p1 = m_vertices[m_triangles[facet_idx].verts_idxs[0]].v; + const Vec3f& p2 = m_vertices[m_triangles[facet_idx].verts_idxs[1]].v; + const Vec3f& p3 = m_vertices[m_triangles[facet_idx].verts_idxs[2]].v; + const Vec3f& q1 = m_cursor.center + m_cursor.dir; + const Vec3f q2 = m_cursor.center - m_cursor.dir; + + if (signed_volume_sign(q1,p1,p2,p3) != signed_volume_sign(q2,p1,p2,p3)) { + bool pos = signed_volume_sign(q1,q2,p1,p2); + if (signed_volume_sign(q1,q2,p2,p3) == pos && signed_volume_sign(q1,q2,p3,p1) == pos) + return true; + } + return false; +} + + + +// Determine whether this facet is potentially visible (still can be obscured). +bool TriangleSelector::faces_camera(int facet) const +{ + assert(facet < m_orig_size_indices); + // The normal is cached in mesh->stl, use it. + return (m_mesh->stl.facet_start[facet].normal.dot(m_cursor.dir) < 0.); +} + + +// How many vertices of a triangle are inside the circle? +int TriangleSelector::vertices_inside(int facet_idx) const +{ + int inside = 0; + for (size_t i=0; i<3; ++i) { + if (is_point_inside_cursor(m_vertices[m_triangles[facet_idx].verts_idxs[i]].v)) + ++inside; + } + return inside; +} + + +// Is edge inside cursor? +bool TriangleSelector::is_edge_inside_cursor(int facet_idx) const +{ + Vec3f pts[3]; + for (int i=0; i<3; ++i) + pts[i] = m_vertices[m_triangles[facet_idx].verts_idxs[i]].v; + + const Vec3f& p = m_cursor.center; + + for (int side = 0; side < 3; ++side) { + const Vec3f& a = pts[side]; + const Vec3f& b = pts[side<2 ? side+1 : 0]; + Vec3f s = (b-a).normalized(); + float t = (p-a).dot(s); + Vec3f vector = a+t*s - p; + + // vector is 3D vector from center to the intersection. What we want to + // measure is length of its projection onto plane perpendicular to dir. + float dist_sqr = vector.squaredNorm() - std::pow(vector.dot(m_cursor.dir), 2.f); + if (dist_sqr < m_cursor.radius_sqr && t>=0.f && t<=(b-a).norm()) + return true; + } + return false; +} + + + +// Recursively remove all subtriangles. +void TriangleSelector::undivide_triangle(int facet_idx) +{ + assert(facet_idx < int(m_triangles.size())); + Triangle& tr = m_triangles[facet_idx]; + + if (tr.is_split()) { + for (int i=0; i<=tr.number_of_split_sides(); ++i) { + undivide_triangle(tr.children[i]); + m_triangles[tr.children[i]].valid = false; + ++m_invalid_triangles; + } + tr.set_division(0); // not split + } +} + + +void TriangleSelector::remove_useless_children(int facet_idx) +{ + // Check that all children are leafs of the same type. If not, try to + // make them (recursive call). Remove them if sucessful. + + assert(facet_idx < int(m_triangles.size()) && m_triangles[facet_idx].valid); + Triangle& tr = m_triangles[facet_idx]; + + if (! tr.is_split()) { + // This is a leaf, there nothing to do. This can happen during the + // first (non-recursive call). Shouldn't otherwise. + return; + } + + // Call this for all non-leaf children. + for (int child_idx=0; child_idx<=tr.number_of_split_sides(); ++child_idx) { + assert(child_idx < int(m_triangles.size()) && m_triangles[child_idx].valid); + if (m_triangles[tr.children[child_idx]].is_split()) + remove_useless_children(tr.children[child_idx]); + } + + + // Return if a child is not leaf or two children differ in type. + FacetSupportType first_child_type = FacetSupportType::NONE; + for (int child_idx=0; child_idx<=tr.number_of_split_sides(); ++child_idx) { + if (m_triangles[tr.children[child_idx]].is_split()) + return; + if (child_idx == 0) + first_child_type = m_triangles[tr.children[0]].get_state(); + else if (m_triangles[tr.children[child_idx]].get_state() != first_child_type) + return; + } + + // If we got here, the children can be removed. + undivide_triangle(facet_idx); + tr.set_state(first_child_type); +} + + + +void TriangleSelector::garbage_collect() +{ + // First make a map from old to new triangle indices. + int new_idx = m_orig_size_indices; + std::vector new_triangle_indices(m_triangles.size(), -1); + for (int i = m_orig_size_indices; i new_vertices_indices(m_vertices.size(), -1); + for (int i=m_orig_size_vertices; i= 0); + if (m_vertices[i].ref_cnt != 0) { + new_vertices_indices[i] = new_idx; + ++new_idx; + } + } + + // We can remove all invalid triangles and vertices that are no longer referenced. + m_triangles.erase(std::remove_if(m_triangles.begin()+m_orig_size_indices, m_triangles.end(), + [](const Triangle& tr) { return ! tr.valid; }), + m_triangles.end()); + m_vertices.erase(std::remove_if(m_vertices.begin()+m_orig_size_vertices, m_vertices.end(), + [](const Vertex& vert) { return vert.ref_cnt == 0; }), + m_vertices.end()); + + // Now go through all remaining triangles and update changed indices. + for (Triangle& tr : m_triangles) { + assert(tr.valid); + + if (tr.is_split()) { + // There are children. Update their indices. + for (int j=0; j<=tr.number_of_split_sides(); ++j) { + assert(new_triangle_indices[tr.children[j]] != -1); + tr.children[j] = new_triangle_indices[tr.children[j]]; + } + } + + // Update indices into m_vertices. The original vertices are never + // touched and need not be reindexed. + for (int& idx : tr.verts_idxs) { + if (idx >= m_orig_size_vertices) { + assert(new_vertices_indices[idx] != -1); + idx = new_vertices_indices[idx]; + } + } + + // If this triangle was split before, forget it. + // Children referenced in the cache are dead by now. + tr.forget_history(); + } + + m_invalid_triangles = 0; +} + +TriangleSelector::TriangleSelector(const TriangleMesh& mesh) + : m_mesh{&mesh} +{ + reset(); +} + + +void TriangleSelector::reset() +{ + if (! m_orig_size_indices != 0) // unless this is run from constructor + garbage_collect(); + m_vertices.clear(); + m_triangles.clear(); + for (const stl_vertex& vert : m_mesh->its.vertices) + m_vertices.emplace_back(vert); + for (const stl_triangle_vertex_indices& ind : m_mesh->its.indices) + push_triangle(ind[0], ind[1], ind[2]); + m_orig_size_vertices = m_vertices.size(); + m_orig_size_indices = m_triangles.size(); + m_invalid_triangles = 0; +} + + + + + +void TriangleSelector::set_edge_limit(float edge_limit) +{ + float new_limit_sqr = std::pow(edge_limit, 2.f); + + if (new_limit_sqr != m_edge_limit_sqr) { + m_edge_limit_sqr = new_limit_sqr; + + // The way how triangles split may be different now, forget + // all cached splits. + garbage_collect(); + } +} + + + +void TriangleSelector::push_triangle(int a, int b, int c) +{ + for (int i : {a, b, c}) { + assert(i >= 0 && i < int(m_vertices.size())); + ++m_vertices[i].ref_cnt; + } + m_triangles.emplace_back(a, b, c); +} + + +void TriangleSelector::perform_split(int facet_idx, FacetSupportType old_state) +{ + Triangle* tr = &m_triangles[facet_idx]; + + assert(tr->is_split()); + + // Read info about how to split this triangle. + int sides_to_split = tr->number_of_split_sides(); + + // indices of triangle vertices + std::vector verts_idxs; + int idx = tr->special_side(); + for (int j=0; j<3; ++j) { + verts_idxs.push_back(tr->verts_idxs[idx++]); + if (idx == 3) + idx = 0; + } + + if (sides_to_split == 1) { + m_vertices.emplace_back((m_vertices[verts_idxs[1]].v + m_vertices[verts_idxs[2]].v)/2.); + verts_idxs.insert(verts_idxs.begin()+2, m_vertices.size() - 1); + + push_triangle(verts_idxs[0], verts_idxs[1], verts_idxs[2]); + push_triangle(verts_idxs[2], verts_idxs[3], verts_idxs[0]); + } + + if (sides_to_split == 2) { + m_vertices.emplace_back((m_vertices[verts_idxs[0]].v + m_vertices[verts_idxs[1]].v)/2.); + verts_idxs.insert(verts_idxs.begin()+1, m_vertices.size() - 1); + + m_vertices.emplace_back((m_vertices[verts_idxs[0]].v + m_vertices[verts_idxs[3]].v)/2.); + verts_idxs.insert(verts_idxs.begin()+4, m_vertices.size() - 1); + + push_triangle(verts_idxs[0], verts_idxs[1], verts_idxs[4]); + push_triangle(verts_idxs[1], verts_idxs[2], verts_idxs[4]); + push_triangle(verts_idxs[2], verts_idxs[3], verts_idxs[4]); + } + + if (sides_to_split == 3) { + m_vertices.emplace_back((m_vertices[verts_idxs[0]].v + m_vertices[verts_idxs[1]].v)/2.); + verts_idxs.insert(verts_idxs.begin()+1, m_vertices.size() - 1); + m_vertices.emplace_back((m_vertices[verts_idxs[2]].v + m_vertices[verts_idxs[3]].v)/2.); + verts_idxs.insert(verts_idxs.begin()+3, m_vertices.size() - 1); + m_vertices.emplace_back((m_vertices[verts_idxs[4]].v + m_vertices[verts_idxs[0]].v)/2.); + verts_idxs.insert(verts_idxs.begin()+5, m_vertices.size() - 1); + + push_triangle(verts_idxs[0], verts_idxs[1], verts_idxs[5]); + push_triangle(verts_idxs[1], verts_idxs[2], verts_idxs[3]); + push_triangle(verts_idxs[3], verts_idxs[4], verts_idxs[5]); + push_triangle(verts_idxs[1], verts_idxs[3], verts_idxs[5]); + } + + tr = &m_triangles[facet_idx]; // may have been invalidated + + // And save the children. All children should start in the same state as the triangle we just split. + assert(sides_to_split <= 3); + for (int i=0; i<=sides_to_split; ++i) { + tr->children[i] = m_triangles.size()-1-i; + m_triangles[tr->children[i]].set_state(old_state); + } +} + + +std::map> TriangleSelector::serialize() const +{ + // Each original triangle of the mesh is assigned a number encoding its state + // or how it is split. Each triangle is encoded by 4 bits (xxyy): + // leaf triangle: xx = FacetSupportType, yy = 0 + // non-leaf: xx = special side, yy = number of split sides + // These are bitwise appended and formed into one 64-bit integer. + + // The function returns a map from original triangle indices to + // stream of bits encoding state and offsprings. + + std::map> out; + for (int i=0; i data; // complete encoding of this mesh triangle + int stored_triangles = 0; // how many have been already encoded + + std::function serialize_recursive; + serialize_recursive = [this, &serialize_recursive, &stored_triangles, &data](int facet_idx) { + const Triangle& tr = m_triangles[facet_idx]; + + // Always save number of split sides. It is zero for unsplit triangles. + int split_sides = tr.number_of_split_sides(); + assert(split_sides >= 0 && split_sides <= 3); + + //data |= (split_sides << (stored_triangles * 4)); + data.push_back(split_sides & 0b01); + data.push_back(split_sides & 0b10); + + if (tr.is_split()) { + // If this triangle is split, save which side is split (in case + // of one split) or kept (in case of two splits). The value will + // be ignored for 3-side split. + assert(split_sides > 0); + assert(tr.special_side() >= 0 && tr.special_side() <= 3); + data.push_back(tr.special_side() & 0b01); + data.push_back(tr.special_side() & 0b10); + ++stored_triangles; + // Now save all children. + for (int child_idx=0; child_idx<=split_sides; ++child_idx) + serialize_recursive(tr.children[child_idx]); + } else { + // In case this is leaf, we better save information about its state. + assert(int(tr.get_state()) <= 3); + data.push_back(int(tr.get_state()) & 0b01); + data.push_back(int(tr.get_state()) & 0b10); + ++stored_triangles; + } + }; + + serialize_recursive(i); + out[i] = data; + } + + return out; +} + +void TriangleSelector::deserialize(const std::map> data) +{ + reset(); // dump any current state + for (const auto& [triangle_id, code] : data) { + assert(triangle_id < int(m_triangles.size())); + int processed_triangles = 0; + struct ProcessingInfo { + int facet_id = 0; + int processed_children = 0; + int total_children = 0; + }; + + // Vector to store all parents that have offsprings. + std::vector parents; + + while (true) { + // Read next triangle info. + int next_code = 0; + for (int i=3; i>=0; --i) { + next_code = next_code << 1; + next_code |= int(code[4 * processed_triangles + i]); + } + ++processed_triangles; + + int num_of_split_sides = (next_code & 0b11); + int num_of_children = num_of_split_sides != 0 ? num_of_split_sides + 1 : 0; + bool is_split = num_of_children != 0; + FacetSupportType state = FacetSupportType(next_code >> 2); + int special_side = (next_code >> 2); + + // Take care of the first iteration separately, so handling of the others is simpler. + if (parents.empty()) { + if (! is_split) { + // root is not split. just set the state and that's it. + m_triangles[triangle_id].set_state(state); + break; + } else { + // root is split, add it into list of parents and split it. + // then go to the next. + parents.push_back({triangle_id, 0, num_of_children}); + m_triangles[triangle_id].set_division(num_of_children-1, special_side); + perform_split(triangle_id, FacetSupportType::NONE); + continue; + } + } + + // This is not the first iteration. This triangle is a child of last seen parent. + assert(! parents.empty()); + assert(parents.back().processed_children < parents.back().total_children); + + if (is_split) { + // split the triangle and save it as parent of the next ones. + const ProcessingInfo& last = parents.back(); + int this_idx = m_triangles[last.facet_id].children[last.processed_children]; + m_triangles[this_idx].set_division(num_of_children-1, special_side); + perform_split(this_idx, FacetSupportType::NONE); + parents.push_back({this_idx, 0, num_of_children}); + } else { + // this triangle belongs to last split one + m_triangles[m_triangles[parents.back().facet_id].children[parents.back().processed_children]].set_state(state); + ++parents.back().processed_children; + } + + + // If all children of the past parent triangle are claimed, move to grandparent. + while (parents.back().processed_children == parents.back().total_children) { + parents.pop_back(); + + if (parents.empty()) + break; + + // And increment the grandparent children counter, because + // we have just finished that branch and got back here. + ++parents.back().processed_children; + } + + // In case we popped back the root, we should be done. + if (parents.empty()) + break; + } + + } +} + + + + +} // namespace Slic3r diff --git a/src/libslic3r/TriangleSelector.hpp b/src/libslic3r/TriangleSelector.hpp new file mode 100644 index 000000000..943029548 --- /dev/null +++ b/src/libslic3r/TriangleSelector.hpp @@ -0,0 +1,149 @@ +#ifndef libslic3r_TriangleSelector_hpp_ +#define libslic3r_TriangleSelector_hpp_ + +#define PRUSASLICER_TRIANGLE_SELECTOR_DEBUG + + +#include "Point.hpp" +#include "TriangleMesh.hpp" + +namespace Slic3r { + +enum class FacetSupportType : int8_t; + + + +// Following class holds information about selected triangles. It also has power +// to recursively subdivide the triangles and make the selection finer. +class TriangleSelector { +public: + void set_edge_limit(float edge_limit); + + // Create new object on a TriangleMesh. The referenced mesh must + // stay valid, a ptr to it is saved and used. + explicit TriangleSelector(const TriangleMesh& mesh); + + // Select all triangles fully inside the circle, subdivide where needed. + void select_patch(const Vec3f& hit, // point where to start + int facet_start, // facet that point belongs to + const Vec3f& source, // camera position (mesh coords) + const Vec3f& dir, // direction of the ray (mesh coords) + float radius_sqr, // squared radius of the cursor + FacetSupportType new_state); // enforcer or blocker? + + + // Clear everything and make the tree empty. + void reset(); + + // Remove all unnecessary data. + void garbage_collect(); + + // Store the division trees in compact form (a long stream of + // bits for each triangle of the original mesh). + std::map> serialize() const; + + // Load serialized data. Assumes that correct mesh is loaded. + void deserialize(const std::map> data); + + +protected: + // Triangle and info about how it's split. + class Triangle { + public: + // Use TriangleSelector::push_triangle to create a new triangle. + // It increments/decrements reference counter on vertices. + Triangle(int a, int b, int c) + : verts_idxs{a, b, c}, + state{FacetSupportType(0)}, + number_of_splits{0}, + special_side_idx{0}, + old_number_of_splits{0} + {} + // Indices into m_vertices. + std::array verts_idxs; + + // Is this triangle valid or marked to be removed? + bool valid{true}; + + // Children triangles. + std::array children; + + // Set the division type. + void set_division(int sides_to_split, int special_side_idx = -1); + + // Get/set current state. + void set_state(FacetSupportType type) { assert(! is_split()); state = type; } + FacetSupportType get_state() const { assert(! is_split()); return state; } + + // Get info on how it's split. + bool is_split() const { return number_of_split_sides() != 0; } + int number_of_split_sides() const { return number_of_splits; } + int special_side() const { assert(is_split()); return special_side_idx; } + bool was_split_before() const { return old_number_of_splits != 0; } + void forget_history() { old_number_of_splits = 0; } + + private: + int number_of_splits; + int special_side_idx; + FacetSupportType state; + + // How many children were spawned during last split? + // Is not reset on remerging the triangle. + int old_number_of_splits; + }; + + struct Vertex { + explicit Vertex(const stl_vertex& vert) + : v{vert}, + ref_cnt{0} + {} + stl_vertex v; + int ref_cnt; + }; + + // Lists of vertices and triangles, both original and new + std::vector m_vertices; + std::vector m_triangles; + const TriangleMesh* m_mesh; + + // Number of invalid triangles (to trigger garbage collection). + int m_invalid_triangles; + + // Limiting length of triangle side (squared). + float m_edge_limit_sqr = 1.f; + + // Number of original vertices and triangles. + int m_orig_size_vertices; + int m_orig_size_indices; + + // Cache for cursor position, radius and direction. + struct Cursor { + Vec3f center; + Vec3f source; + Vec3f dir; + float radius_sqr; + }; + + Cursor m_cursor; + + // Private functions: + bool select_triangle(int facet_idx, FacetSupportType type, + bool recursive_call = false); + bool is_point_inside_cursor(const Vec3f& point) const; + int vertices_inside(int facet_idx) const; + bool faces_camera(int facet) const; + void undivide_triangle(int facet_idx); + void split_triangle(int facet_idx); + void remove_useless_children(int facet_idx); // No hidden meaning. Triangles are meant. + bool is_pointer_in_triangle(int facet_idx) const; + bool is_edge_inside_cursor(int facet_idx) const; + void push_triangle(int a, int b, int c); + void perform_split(int facet_idx, FacetSupportType old_state); +}; + + + + +} // namespace Slic3r + +#endif // libslic3r_TriangleSelector_hpp_ diff --git a/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.cpp b/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.cpp index 01427ec09..ca7695907 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.cpp @@ -89,15 +89,12 @@ void GLGizmoFdmSupports::set_fdm_support_data(ModelObject* model_object, const S void GLGizmoFdmSupports::on_render() const { - //const Selection& selection = m_parent.get_selection(); + const Selection& selection = m_parent.get_selection(); glsafe(::glEnable(GL_BLEND)); glsafe(::glEnable(GL_DEPTH_TEST)); - //render_triangles(selection); - - if (m_triangle_selector && ! m_setting_angle) - m_triangle_selector->render(m_imgui); + render_triangles(selection); m_c->object_clipper()->render_cut(); render_cursor_circle(); @@ -148,14 +145,9 @@ void GLGizmoFdmSupports::render_triangles(const Selection& selection) const glsafe(::glPushMatrix()); glsafe(::glMultMatrixd(trafo_matrix.data())); - // Now render both enforcers and blockers. - //for (int i=0; i<2; ++i) { - // glsafe(::glColor4f(i ? 1.f : 0.2f, 0.2f, i ? 0.2f : 1.0f, 0.5f)); - // for (const GLIndexedVertexArray& iva : m_ivas[mesh_id][i]) { - if (m_iva.has_VBOs()) - m_iva.render(); - // } - //} + if (! m_setting_angle) + m_triangle_selectors[mesh_id]->render(m_imgui); + glsafe(::glPopMatrix()); if (is_left_handed) glsafe(::glFrontFace(GL_CCW)); @@ -212,16 +204,14 @@ void GLGizmoFdmSupports::render_cursor_circle() const void GLGizmoFdmSupports::update_model_object() const { - return; - /*ModelObject* mo = m_c->selection_info()->model_object(); + ModelObject* mo = m_c->selection_info()->model_object(); int idx = -1; for (ModelVolume* mv : mo->volumes) { ++idx; if (! mv->is_model_part()) continue; - for (int i=0; im_supported_facets.set_facet(i, m_selected_facets[idx][i]); - }*/ + mv->m_supported_facets.set(*m_triangle_selectors[idx].get()); + } } @@ -230,21 +220,7 @@ void GLGizmoFdmSupports::update_from_model_object() wxBusyCursor wait; const ModelObject* mo = m_c->selection_info()->model_object(); - /*size_t num_of_volumes = 0; - for (const ModelVolume* mv : mo->volumes) - if (mv->is_model_part()) - ++num_of_volumes; - m_selected_facets.resize(num_of_volumes);*/ - - m_triangle_selector = std::make_unique(mo->volumes.front()->mesh()); - - /*m_ivas.clear(); - m_ivas.resize(num_of_volumes); - for (size_t i=0; ivolumes) { @@ -256,17 +232,9 @@ void GLGizmoFdmSupports::update_from_model_object() // This mesh does not account for the possible Z up SLA offset. const TriangleMesh* mesh = &mv->mesh(); - m_selected_facets[volume_id].assign(mesh->its.indices.size(), FacetSupportType::NONE); - - // Load current state from ModelVolume. - for (FacetSupportType type : {FacetSupportType::ENFORCER, FacetSupportType::BLOCKER}) { - const std::vector& list = mv->m_supported_facets.get_facets(type); - for (int i : list) - m_selected_facets[volume_id][i] = type; - } - update_vertex_buffers(mesh, volume_id, FacetSupportType::ENFORCER); - update_vertex_buffers(mesh, volume_id, FacetSupportType::BLOCKER); - }*/ + m_triangle_selectors.emplace_back(std::make_unique(*mesh)); + m_triangle_selectors.back()->deserialize(mv->m_supported_facets.get_data()); + } } @@ -321,7 +289,7 @@ bool GLGizmoFdmSupports::gizmo_event(SLAGizmoEventType action, const Vec2d& mous || action == SLAGizmoEventType::RightDown || (action == SLAGizmoEventType::Dragging && m_button_down != Button::None)) { - if (! m_triangle_selector) + if (m_triangle_selectors.empty()) return false; FacetSupportType new_state = FacetSupportType::NONE; @@ -426,23 +394,20 @@ bool GLGizmoFdmSupports::gizmo_event(SLAGizmoEventType action, const Vec2d& mous } } - // FIXME: just for now, only process first mesh - if (mesh_id != 0) - return false; - const Transform3d& trafo_matrix = trafo_matrices[mesh_id]; // Calculate how far can a point be from the line (in mesh coords). // FIXME: The scaling of the mesh can be non-uniform. const Vec3d sf = Geometry::Transformation(trafo_matrix).get_scaling_factor(); const float avg_scaling = (sf(0) + sf(1) + sf(2))/3.; - const float limit = pow(m_cursor_radius/avg_scaling , 2.f); + const float limit = std::pow(m_cursor_radius/avg_scaling , 2.f); // Calculate direction from camera to the hit (in mesh coords): Vec3f camera_pos = (trafo_matrix.inverse() * camera.get_position()).cast(); Vec3f dir = (closest_hit - camera_pos).normalized(); - m_triangle_selector->select_patch(closest_hit, closest_facet, camera_pos, + assert(mesh_id < int(m_triangle_selectors.size())); + m_triangle_selectors[mesh_id]->select_patch(closest_hit, closest_facet, camera_pos, dir, limit, new_state); return true; @@ -468,7 +433,7 @@ bool GLGizmoFdmSupports::gizmo_event(SLAGizmoEventType action, const Vec2d& mous } -void GLGizmoFdmSupports::update_vertex_buffers(const TriangleMesh* mesh, +/*void GLGizmoFdmSupports::update_vertex_buffers(const TriangleMesh* mesh, int mesh_id, FacetSupportType type, const std::vector* new_facets) @@ -506,7 +471,7 @@ void GLGizmoFdmSupports::update_vertex_buffers(const TriangleMesh* mesh, if (pushed) m_iva.finalize_geometry(true); - /*} else { + } else { // we are only appending - let's make new vertex array and let the old ones live ivas.push_back(GLIndexedVertexArray()); for (size_t facet_idx : *new_facets) @@ -516,9 +481,9 @@ void GLGizmoFdmSupports::update_vertex_buffers(const TriangleMesh* mesh, ivas.back().finalize_geometry(true); else ivas.pop_back(); - }*/ + } -} +}*/ void GLGizmoFdmSupports::select_facets_by_angle(float threshold_deg, bool overwrite, bool block) @@ -761,8 +726,8 @@ void GLGizmoFdmSupports::on_set_state() } activate_internal_undo_redo_stack(false); m_old_mo_id = -1; - m_iva.release_geometry(); - m_selected_facets.clear(); + //m_iva.release_geometry(); + m_triangle_selectors.clear(); } m_old_state = m_state; } @@ -800,422 +765,14 @@ void GLGizmoFdmSupports::on_save(cereal::BinaryOutputArchive&) const } -// sides_to_split==-1 : just restore previous split -void TriangleSelector::Triangle::set_division(int sides_to_split, int special_side_idx) +void TriangleSelectorGUI::render(ImGuiWrapper* imgui) { - assert(sides_to_split >=-1 && sides_to_split <= 3); - assert(special_side_idx >=-1 && special_side_idx < 3); - - // If splitting one or two sides, second argument must be provided. - assert(sides_to_split != 1 || special_side_idx != -1); - assert(sides_to_split != 2 || special_side_idx != -1); - - if (sides_to_split != -1) { - this->number_of_splits = sides_to_split; - if (sides_to_split != 0) { - assert(old_number_of_splits == 0); - this->special_side_idx = special_side_idx; - this->old_number_of_splits = sides_to_split; - } - } - else { - assert(old_number_of_splits != 0); - this->number_of_splits = old_number_of_splits; - // indices of children should still be there. - } -} - - - -void TriangleSelector::select_patch(const Vec3f& hit, int facet_start, - const Vec3f& source, const Vec3f& dir, - float radius_sqr, FacetSupportType new_state) -{ - assert(facet_start < m_orig_size_indices); - assert(is_approx(dir.norm(), 1.f)); - - // Save current cursor center, squared radius and camera direction, - // so we don't have to pass it around. - m_cursor = {hit, source, dir, radius_sqr}; - - // Now start with the facet the pointer points to and check all adjacent facets. - std::vector facets_to_check{facet_start}; - std::vector visited(m_orig_size_indices, false); // keep track of facets we already processed - int facet_idx = 0; // index into facets_to_check - while (facet_idx < int(facets_to_check.size())) { - int facet = facets_to_check[facet_idx]; - if (! visited[facet]) { - if (select_triangle(facet, new_state)) { - // add neighboring facets to list to be proccessed later - for (int n=0; n<3; ++n) { - if (faces_camera(m_mesh->stl.neighbors_start[facet].neighbor[n])) - facets_to_check.push_back(m_mesh->stl.neighbors_start[facet].neighbor[n]); - } - } - } - visited[facet] = true; - ++facet_idx; - } -} - - - -// 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 -// outside the cursor. -bool TriangleSelector::select_triangle(int facet_idx, FacetSupportType type, bool recursive_call) -{ - assert(facet_idx < int(m_triangles.size())); - - Triangle* tr = &m_triangles[facet_idx]; - if (! tr->valid) - return false; - - int num_of_inside_vertices = vertices_inside(facet_idx); - - if (num_of_inside_vertices == 0 - && ! is_pointer_in_triangle(facet_idx) - && ! is_edge_inside_cursor(facet_idx)) - return false; - - if (num_of_inside_vertices == 3) { - // dump any subdivision and select whole triangle - undivide_triangle(facet_idx); - tr->set_state(type); - } else { - // the triangle is partially inside, let's recursively divide it - // (if not already) and try selecting its children. - - if (! tr->is_split() && tr->get_state() == type) { - // This is leaf triangle that is already of correct type as a whole. - // No need to split, all children would end up selected anyway. - return true; - } - - split_triangle(facet_idx); - tr = &m_triangles[facet_idx]; // might have been invalidated - - - int num_of_children = tr->number_of_split_sides() + 1; - if (num_of_children != 1) { - for (int i=0; ichildren.size())); - assert(tr->children[i] < int(m_triangles.size())); - - select_triangle(tr->children[i], type, true); - tr = &m_triangles[facet_idx]; // might have been invalidated - } - } - } - - if (! recursive_call) { - // In case that all children are leafs and have the same state now, - // they may be removed and substituted by the parent triangle. - remove_useless_children(facet_idx); - - // Make sure that we did not lose track of invalid triangles. - assert(m_invalid_triangles == std::count_if(m_triangles.begin(), m_triangles.end(), - [](const Triangle& tr) { return ! tr.valid; })); - - // Do garbage collection maybe? - if (2*m_invalid_triangles > int(m_triangles.size())) - garbage_collect(); - } - return true; -} - - -void TriangleSelector::split_triangle(int facet_idx) -{ - if (m_triangles[facet_idx].is_split()) { - // The triangle is divided already. - return; - } - - Triangle* tr = &m_triangles[facet_idx]; - - FacetSupportType old_type = tr->get_state(); - - if (tr->was_split_before() != 0) { - // This triangle is not split at the moment, but was at one point - // in history. We can just restore it and resurrect its children. - tr->set_division(-1); - for (int i=0; i<=tr->number_of_split_sides(); ++i) { - m_triangles[tr->children[i]].set_state(old_type); - m_triangles[tr->children[i]].valid = true; - --m_invalid_triangles; - } - return; - } - - // If we got here, we are about to actually split the triangle. - const double limit_squared = m_edge_limit_sqr; - - std::array& facet = tr->verts_idxs; - const stl_vertex* pts[3] = { &m_vertices[facet[0]].v, &m_vertices[facet[1]].v, &m_vertices[facet[2]].v}; - double sides[3] = { (*pts[2]-*pts[1]).squaredNorm(), - (*pts[0]-*pts[2]).squaredNorm(), - (*pts[1]-*pts[0]).squaredNorm() }; - - std::vector sides_to_split; - int side_to_keep = -1; - for (int pt_idx = 0; pt_idx<3; ++pt_idx) { - if (sides[pt_idx] > limit_squared) - sides_to_split.push_back(pt_idx); - else - side_to_keep = pt_idx; - } - if (sides_to_split.empty()) { - // This shall be unselected. - tr->set_division(0); - return; - } - - // Save how the triangle will be split. Second argument makes sense only for one - // or two split sides, otherwise the value is ignored. - tr->set_division(sides_to_split.size(), - sides_to_split.size() == 2 ? side_to_keep : sides_to_split[0]); - - perform_split(facet_idx, old_type); -} - - -// Calculate distance of a point from a line. -bool TriangleSelector::is_point_inside_cursor(const Vec3f& point) const -{ - Vec3f diff = m_cursor.center - point; - return (diff - diff.dot(m_cursor.dir) * m_cursor.dir).squaredNorm() < m_cursor.radius_sqr; -} - - -// Is pointer in a triangle? -bool TriangleSelector::is_pointer_in_triangle(int facet_idx) const -{ - auto signed_volume_sign = [](const Vec3f& a, const Vec3f& b, - const Vec3f& c, const Vec3f& d) -> bool { - return ((b-a).cross(c-a)).dot(d-a) > 0.; - }; - - const Vec3f& p1 = m_vertices[m_triangles[facet_idx].verts_idxs[0]].v; - const Vec3f& p2 = m_vertices[m_triangles[facet_idx].verts_idxs[1]].v; - const Vec3f& p3 = m_vertices[m_triangles[facet_idx].verts_idxs[2]].v; - const Vec3f& q1 = m_cursor.center + m_cursor.dir; - const Vec3f q2 = m_cursor.center - m_cursor.dir; - - if (signed_volume_sign(q1,p1,p2,p3) != signed_volume_sign(q2,p1,p2,p3)) { - bool pos = signed_volume_sign(q1,q2,p1,p2); - if (signed_volume_sign(q1,q2,p2,p3) == pos && signed_volume_sign(q1,q2,p3,p1) == pos) - return true; - } - return false; -} - - - -// Determine whether this facet is potentially visible (still can be obscured). -bool TriangleSelector::faces_camera(int facet) const -{ - assert(facet < m_orig_size_indices); - // The normal is cached in mesh->stl, use it. - return (m_mesh->stl.facet_start[facet].normal.dot(m_cursor.dir) < 0.); -} - - -// How many vertices of a triangle are inside the circle? -int TriangleSelector::vertices_inside(int facet_idx) const -{ - int inside = 0; - for (size_t i=0; i<3; ++i) { - if (is_point_inside_cursor(m_vertices[m_triangles[facet_idx].verts_idxs[i]].v)) - ++inside; - } - return inside; -} - - -// Is edge inside cursor? -bool TriangleSelector::is_edge_inside_cursor(int facet_idx) const -{ - Vec3f pts[3]; - for (int i=0; i<3; ++i) - pts[i] = m_vertices[m_triangles[facet_idx].verts_idxs[i]].v; - - const Vec3f& p = m_cursor.center; - - for (int side = 0; side < 3; ++side) { - const Vec3f& a = pts[side]; - const Vec3f& b = pts[side<2 ? side+1 : 0]; - Vec3f s = (b-a).normalized(); - float t = (p-a).dot(s); - Vec3f vector = a+t*s - p; - - // vector is 3D vector from center to the intersection. What we want to - // measure is length of its projection onto plane perpendicular to dir. - float dist_sqr = vector.squaredNorm() - std::pow(vector.dot(m_cursor.dir), 2.f); - if (dist_sqr < m_cursor.radius_sqr && t>=0.f && t<=(b-a).norm()) - return true; - } - return false; -} - - - -// Recursively remove all subtriangles. -void TriangleSelector::undivide_triangle(int facet_idx) -{ - assert(facet_idx < int(m_triangles.size())); - Triangle& tr = m_triangles[facet_idx]; - - if (tr.is_split()) { - for (int i=0; i<=tr.number_of_split_sides(); ++i) { - undivide_triangle(tr.children[i]); - m_triangles[tr.children[i]].valid = false; - ++m_invalid_triangles; - } - tr.set_division(0); // not split - } -} - - -void TriangleSelector::remove_useless_children(int facet_idx) -{ - // Check that all children are leafs of the same type. If not, try to - // make them (recursive call). Remove them if sucessful. - - assert(facet_idx < int(m_triangles.size()) && m_triangles[facet_idx].valid); - Triangle& tr = m_triangles[facet_idx]; - - if (! tr.is_split()) { - // This is a leaf, there nothing to do. This can happen during the - // first (non-recursive call). Shouldn't otherwise. - return; - } - - // Call this for all non-leaf children. - for (int child_idx=0; child_idx<=tr.number_of_split_sides(); ++child_idx) { - assert(child_idx < int(m_triangles.size()) && m_triangles[child_idx].valid); - if (m_triangles[tr.children[child_idx]].is_split()) - remove_useless_children(tr.children[child_idx]); - } - - - // Return if a child is not leaf or two children differ in type. - FacetSupportType first_child_type = FacetSupportType::NONE; - for (int child_idx=0; child_idx<=tr.number_of_split_sides(); ++child_idx) { - if (m_triangles[tr.children[child_idx]].is_split()) - return; - if (child_idx == 0) - first_child_type = m_triangles[tr.children[0]].get_state(); - else if (m_triangles[tr.children[child_idx]].get_state() != first_child_type) - return; - } - - // If we got here, the children can be removed. - undivide_triangle(facet_idx); - tr.set_state(first_child_type); -} - - - -void TriangleSelector::garbage_collect() -{ - // First make a map from old to new triangle indices. - int new_idx = m_orig_size_indices; - std::vector new_triangle_indices(m_triangles.size(), -1); - for (int i = m_orig_size_indices; i new_vertices_indices(m_vertices.size(), -1); - for (int i=m_orig_size_vertices; i= 0); - if (m_vertices[i].ref_cnt != 0) { - new_vertices_indices[i] = new_idx; - ++new_idx; - } - } - - // We can remove all invalid triangles and vertices that are no longer referenced. - m_triangles.erase(std::remove_if(m_triangles.begin()+m_orig_size_indices, m_triangles.end(), - [](const Triangle& tr) { return ! tr.valid; }), - m_triangles.end()); - m_vertices.erase(std::remove_if(m_vertices.begin()+m_orig_size_vertices, m_vertices.end(), - [](const Vertex& vert) { return vert.ref_cnt == 0; }), - m_vertices.end()); - - // Now go through all remaining triangles and update changed indices. - for (Triangle& tr : m_triangles) { - assert(tr.valid); - - if (tr.is_split()) { - // There are children. Update their indices. - for (int j=0; j<=tr.number_of_split_sides(); ++j) { - assert(new_triangle_indices[tr.children[j]] != -1); - tr.children[j] = new_triangle_indices[tr.children[j]]; - } - } - - // Update indices into m_vertices. The original vertices are never - // touched and need not be reindexed. - for (int& idx : tr.verts_idxs) { - if (idx >= m_orig_size_vertices) { - assert(new_vertices_indices[idx] != -1); - idx = new_vertices_indices[idx]; - } - } - - // If this triangle was split before, forget it. - // Children referenced in the cache are dead by now. - tr.forget_history(); - } - - m_invalid_triangles = 0; -} - -TriangleSelector::TriangleSelector(const TriangleMesh& mesh) - : m_mesh{&mesh} -{ - reset(); - -} - - -void TriangleSelector::reset() -{ - if (! m_orig_size_indices != 0) // unless this is run from constructor - garbage_collect(); - m_vertices.clear(); - m_triangles.clear(); - for (const stl_vertex& vert : m_mesh->its.vertices) - m_vertices.emplace_back(vert); - for (const stl_triangle_vertex_indices& ind : m_mesh->its.indices) - push_triangle(ind[0], ind[1], ind[2]); - m_orig_size_vertices = m_vertices.size(); - m_orig_size_indices = m_triangles.size(); - m_invalid_triangles = 0; -} - -void TriangleSelector::render(ImGuiWrapper* imgui) -{ - Vec3d offset = wxGetApp().model().objects.front()->instances.front()->get_transformation().get_offset(); - ::glTranslatef(offset.x(), offset.y(), offset.z()); - ::glScalef(1.005f, 1.005f, 1.005f); - - int enf_cnt = 0; int blc_cnt = 0; + m_iva_enforcers.release_geometry(); + m_iva_blockers.release_geometry(); + for (const Triangle& tr : m_triangles) { if (! tr.valid || tr.is_split() || tr.get_state() == FacetSupportType::NONE) continue; @@ -1245,13 +802,13 @@ void TriangleSelector::render(ImGuiWrapper* imgui) ::glColor4f(0.f, 0.f, 1.f, 0.2f); m_iva_enforcers.render(); } - m_iva_enforcers.release_geometry(); + if (m_iva_blockers.has_VBOs()) { ::glColor4f(1.f, 0.f, 0.f, 0.2f); m_iva_blockers.render(); } - m_iva_blockers.release_geometry(); + #ifdef PRUSASLICER_TRIANGLE_SELECTOR_DEBUG if (imgui) @@ -1262,242 +819,9 @@ void TriangleSelector::render(ImGuiWrapper* imgui) } -void TriangleSelector::set_edge_limit(float edge_limit) -{ - float new_limit_sqr = std::pow(edge_limit, 2.f); - - if (new_limit_sqr != m_edge_limit_sqr) { - m_edge_limit_sqr = new_limit_sqr; - - // The way how triangles split may be different now, forget - // all cached splits. - garbage_collect(); - } -} - - - -void TriangleSelector::push_triangle(int a, int b, int c) -{ - for (int i : {a, b, c}) { - assert(i >= 0 && i < int(m_vertices.size())); - ++m_vertices[i].ref_cnt; - } - m_triangles.emplace_back(a, b, c); -} - - -void TriangleSelector::perform_split(int facet_idx, FacetSupportType old_state) -{ - Triangle* tr = &m_triangles[facet_idx]; - - assert(tr->is_split()); - - // Read info about how to split this triangle. - int sides_to_split = tr->number_of_split_sides(); - - // indices of triangle vertices - std::vector verts_idxs; - int idx = tr->special_side(); - for (int j=0; j<3; ++j) { - verts_idxs.push_back(tr->verts_idxs[idx++]); - if (idx == 3) - idx = 0; - } - - if (sides_to_split == 1) { - m_vertices.emplace_back((m_vertices[verts_idxs[1]].v + m_vertices[verts_idxs[2]].v)/2.); - verts_idxs.insert(verts_idxs.begin()+2, m_vertices.size() - 1); - - push_triangle(verts_idxs[0], verts_idxs[1], verts_idxs[2]); - push_triangle(verts_idxs[2], verts_idxs[3], verts_idxs[0]); - } - - if (sides_to_split == 2) { - m_vertices.emplace_back((m_vertices[verts_idxs[0]].v + m_vertices[verts_idxs[1]].v)/2.); - verts_idxs.insert(verts_idxs.begin()+1, m_vertices.size() - 1); - - m_vertices.emplace_back((m_vertices[verts_idxs[0]].v + m_vertices[verts_idxs[3]].v)/2.); - verts_idxs.insert(verts_idxs.begin()+4, m_vertices.size() - 1); - - push_triangle(verts_idxs[0], verts_idxs[1], verts_idxs[4]); - push_triangle(verts_idxs[1], verts_idxs[2], verts_idxs[4]); - push_triangle(verts_idxs[2], verts_idxs[3], verts_idxs[4]); - } - - if (sides_to_split == 3) { - m_vertices.emplace_back((m_vertices[verts_idxs[0]].v + m_vertices[verts_idxs[1]].v)/2.); - verts_idxs.insert(verts_idxs.begin()+1, m_vertices.size() - 1); - m_vertices.emplace_back((m_vertices[verts_idxs[2]].v + m_vertices[verts_idxs[3]].v)/2.); - verts_idxs.insert(verts_idxs.begin()+3, m_vertices.size() - 1); - m_vertices.emplace_back((m_vertices[verts_idxs[4]].v + m_vertices[verts_idxs[0]].v)/2.); - verts_idxs.insert(verts_idxs.begin()+5, m_vertices.size() - 1); - - push_triangle(verts_idxs[0], verts_idxs[1], verts_idxs[5]); - push_triangle(verts_idxs[1], verts_idxs[2], verts_idxs[3]); - push_triangle(verts_idxs[3], verts_idxs[4], verts_idxs[5]); - push_triangle(verts_idxs[1], verts_idxs[3], verts_idxs[5]); - } - - tr = &m_triangles[facet_idx]; // may have been invalidated - - // And save the children. All children should start in the same state as the triangle we just split. - assert(sides_to_split <= 3); - for (int i=0; i<=sides_to_split; ++i) { - tr->children[i] = m_triangles.size()-1-i; - m_triangles[tr->children[i]].set_state(old_state); - } -} - - -std::map> TriangleSelector::serialize() const -{ - // Each original triangle of the mesh is assigned a number encoding its state - // or how it is split. Each triangle is encoded by 4 bits (xxyy): - // leaf triangle: xx = FacetSupportType, yy = 0 - // non-leaf: xx = special side, yy = number of split sides - // These are bitwise appended and formed into one 64-bit integer. - - // The function returns a map from original triangle indices to - // stream of bits encoding state and offsprings. - - std::map> out; - for (int i=0; i data; // complete encoding of this mesh triangle - int stored_triangles = 0; // how many have been already encoded - - std::function serialize_recursive; - serialize_recursive = [this, &serialize_recursive, &stored_triangles, &data](int facet_idx) { - const Triangle& tr = m_triangles[facet_idx]; - - // Always save number of split sides. It is zero for unsplit triangles. - int split_sides = tr.number_of_split_sides(); - assert(split_sides >= 0 && split_sides <= 3); - - //data |= (split_sides << (stored_triangles * 4)); - data.push_back(split_sides & 0b01); - data.push_back(split_sides & 0b10); - - if (tr.is_split()) { - // If this triangle is split, save which side is split (in case - // of one split) or kept (in case of two splits). The value will - // be ignored for 3-side split. - assert(split_sides > 0); - assert(tr.special_side() >= 0 && tr.special_side() <= 3); - data.push_back(tr.special_side() & 0b01); - data.push_back(tr.special_side() & 0b10); - ++stored_triangles; - // Now save all children. - for (int child_idx=0; child_idx<=split_sides; ++child_idx) - serialize_recursive(tr.children[child_idx]); - } else { - // In case this is leaf, we better save information about its state. - assert(int(tr.get_state()) <= 3); - data.push_back(int(tr.get_state()) & 0b01); - data.push_back(int(tr.get_state()) & 0b10); - ++stored_triangles; - } - }; - - serialize_recursive(i); - out[i] = data; - } - - return out; -} - -void TriangleSelector::deserialize(const std::map> data) -{ - reset(); // dump any current state - for (const auto& [triangle_id, code] : data) { - assert(triangle_id < int(m_triangles.size())); - int processed_triangles = 0; - struct ProcessingInfo { - int facet_id = 0; - int processed_children = 0; - int total_children = 0; - }; - - // Vector to store all parents that have offsprings. - std::vector parents; - - while (true) { - // Read next triangle info. - int next_code = 0; - for (int i=3; i>=0; --i) { - next_code = next_code << 1; - next_code |= int(code[4 * processed_triangles + i]); - } - ++processed_triangles; - - int num_of_split_sides = (next_code & 0b11); - int num_of_children = num_of_split_sides != 0 ? num_of_split_sides + 1 : 0; - bool is_split = num_of_children != 0; - FacetSupportType state = FacetSupportType(next_code >> 2); - int special_side = (next_code >> 2); - - // Take care of the first iteration separately, so handling of the others is simpler. - if (parents.empty()) { - if (! is_split) { - // root is not split. just set the state and that's it. - m_triangles[triangle_id].set_state(state); - break; - } else { - // root is split, add it into list of parents and split it. - // then go to the next. - parents.push_back({triangle_id, 0, num_of_children}); - m_triangles[triangle_id].set_division(num_of_children-1, special_side); - perform_split(triangle_id, FacetSupportType::NONE); - continue; - } - } - - // This is not the first iteration. This triangle is a child of last seen parent. - assert(! parents.empty()); - assert(parents.back().processed_children < parents.back().total_children); - - if (is_split) { - // split the triangle and save it as parent of the next ones. - const ProcessingInfo& last = parents.back(); - int this_idx = m_triangles[last.facet_id].children[last.processed_children]; - m_triangles[this_idx].set_division(num_of_children-1, special_side); - perform_split(this_idx, FacetSupportType::NONE); - parents.push_back({this_idx, 0, num_of_children}); - } else { - // this triangle belongs to last split one - m_triangles[m_triangles[parents.back().facet_id].children[parents.back().processed_children]].set_state(state); - ++parents.back().processed_children; - } - - - // If all children of the past parent triangle are claimed, move to grandparent. - while (parents.back().processed_children == parents.back().total_children) { - parents.pop_back(); - - if (parents.empty()) - break; - - // And increment the grandparent children counter, because - // we have just finished that branch and got back here. - ++parents.back().processed_children; - } - - // In case we popped back the root, we should be done. - if (parents.empty()) - break; - } - - } -} - #ifdef PRUSASLICER_TRIANGLE_SELECTOR_DEBUG -void TriangleSelector::render_debug(ImGuiWrapper* imgui) +void TriangleSelectorGUI::render_debug(ImGuiWrapper* imgui) { imgui->begin(std::string("TriangleSelector dialog (DEV ONLY)"), ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoCollapse); @@ -1585,5 +909,7 @@ void TriangleSelector::render_debug(ImGuiWrapper* imgui) } #endif + + } // namespace GUI } // namespace Slic3r diff --git a/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.hpp b/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.hpp index 099c9a30c..7020bb7d4 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.hpp @@ -6,11 +6,11 @@ #include "slic3r/GUI/3DScene.hpp" #include "libslic3r/ObjectID.hpp" +#include "libslic3r/TriangleSelector.hpp" #include -#define PRUSASLICER_TRIANGLE_SELECTOR_DEBUG namespace Slic3r { @@ -23,144 +23,26 @@ enum class SLAGizmoEventType : unsigned char; class ClippingPlane; -// Following class holds information about selected triangles. It also has power -// to recursively subdivide the triangles and make the selection finer. -class TriangleSelector { + +class TriangleSelectorGUI : public TriangleSelector { public: - void set_edge_limit(float edge_limit); - - // Create new object on a TriangleMesh. The referenced mesh must - // stay valid, a ptr to it is saved and used. - explicit TriangleSelector(const TriangleMesh& mesh); - - // Select all triangles fully inside the circle, subdivide where needed. - void select_patch(const Vec3f& hit, // point where to start - int facet_start, // facet that point belongs to - const Vec3f& source, // camera position (mesh coords) - const Vec3f& dir, // direction of the ray (mesh coords) - float radius_sqr, // squared radius of the cursor - FacetSupportType new_state); // enforcer or blocker? + explicit TriangleSelectorGUI(const TriangleMesh& mesh) + : TriangleSelector(mesh) {} // Render current selection. Transformation matrices are supposed // to be already set. void render(ImGuiWrapper* imgui = nullptr); - // Clear everything and make the tree empty. - void reset(); - - // Remove all unnecessary data. - void garbage_collect(); - - // Store the division trees in compact form (a long stream of - // bits for each triangle of the original mesh). - std::map> serialize() const; - - // Load serialized data. Assumes that correct mesh is loaded. - void deserialize(const std::map> data); - #ifdef PRUSASLICER_TRIANGLE_SELECTOR_DEBUG void render_debug(ImGuiWrapper* imgui); - bool m_show_triangles{true}; + bool m_show_triangles{false}; bool m_show_invalid{false}; #endif private: - // Triangle and info about how it's split. - class Triangle { - public: - // Use TriangleSelector::push_triangle to create a new triangle. - // It increments/decrements reference counter on vertices. - Triangle(int a, int b, int c) - : verts_idxs{a, b, c}, - state{FacetSupportType(0)}, - number_of_splits{0}, - special_side_idx{0}, - old_number_of_splits{0} - {} - // Indices into m_vertices. - std::array verts_idxs; - - // Is this triangle valid or marked to be removed? - bool valid{true}; - - // Children triangles. - std::array children; - - // Set the division type. - void set_division(int sides_to_split, int special_side_idx = -1); - - // Get/set current state. - void set_state(FacetSupportType type) { assert(! is_split()); state = type; } - FacetSupportType get_state() const { assert(! is_split()); return state; } - - // Get info on how it's split. - bool is_split() const { return number_of_split_sides() != 0; } - int number_of_split_sides() const { return number_of_splits; } - int special_side() const { assert(is_split()); return special_side_idx; } - bool was_split_before() const { return old_number_of_splits != 0; } - void forget_history() { old_number_of_splits = 0; } - - private: - int number_of_splits; - int special_side_idx; - FacetSupportType state; - - // How many children were spawned during last split? - // Is not reset on remerging the triangle. - int old_number_of_splits; - }; - - struct Vertex { - explicit Vertex(const stl_vertex& vert) - : v{vert}, - ref_cnt{0} - {} - stl_vertex v; - int ref_cnt; - }; - - // Lists of vertices and triangles, both original and new - std::vector m_vertices; - std::vector m_triangles; - const TriangleMesh* m_mesh; - GLIndexedVertexArray m_iva_enforcers; GLIndexedVertexArray m_iva_blockers; std::array m_varrays; - - // Number of invalid triangles (to trigger garbage collection). - int m_invalid_triangles; - - // Limiting length of triangle side (squared). - float m_edge_limit_sqr = 1.f; - - // Number of original vertices and triangles. - int m_orig_size_vertices; - int m_orig_size_indices; - - // Cache for cursor position, radius and direction. - struct Cursor { - Vec3f center; - Vec3f source; - Vec3f dir; - float radius_sqr; - }; - - Cursor m_cursor; - - // Private functions: - bool select_triangle(int facet_idx, FacetSupportType type, - bool recursive_call = false); - bool is_point_inside_cursor(const Vec3f& point) const; - int vertices_inside(int facet_idx) const; - bool faces_camera(int facet) const; - void undivide_triangle(int facet_idx); - void split_triangle(int facet_idx); - void remove_useless_children(int facet_idx); // No hidden meaning. Triangles are meant. - bool is_pointer_in_triangle(int facet_idx) const; - bool is_edge_inside_cursor(int facet_idx) const; - void push_triangle(int a, int b, int c); - void perform_split(int facet_idx, FacetSupportType old_state); }; @@ -178,17 +60,8 @@ private: static constexpr float CursorRadiusMax = 8.f; static constexpr float CursorRadiusStep = 0.2f; - // For each model-part volume, store a list of statuses of - // individual facets (one of the enum values above). - std::vector> m_selected_facets; - - GLIndexedVertexArray m_iva; - - void update_vertex_buffers(const TriangleMesh* mesh, - int mesh_id, - FacetSupportType type, // enforcers / blockers - const std::vector* new_facets = nullptr); // nullptr -> regenerate all - + // For each model-part volume, store status and division of the triangles. + std::vector> m_triangle_selectors; public: GLGizmoFdmSupports(GLCanvas3D& parent, const std::string& icon_filename, unsigned int sprite_id); @@ -220,8 +93,6 @@ private: bool m_setting_angle = false; bool m_internal_stack_active = false; bool m_schedule_update = false; - - std::unique_ptr m_triangle_selector; // 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. From 0756a7e4b3b8776c5885c6589a376e0fd191335b Mon Sep 17 00:00:00 2001 From: Lukas Matena Date: Tue, 14 Jul 2020 14:02:39 +0200 Subject: [PATCH 14/24] TriangleSelector: 'Select by angle' and 'reset selection' functions fixed --- src/libslic3r/TriangleSelector.cpp | 9 ++ src/libslic3r/TriangleSelector.hpp | 3 + src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.cpp | 94 ++++---------------- src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.hpp | 3 +- 4 files changed, 31 insertions(+), 78 deletions(-) diff --git a/src/libslic3r/TriangleSelector.cpp b/src/libslic3r/TriangleSelector.cpp index 340f5a29a..9210bde08 100644 --- a/src/libslic3r/TriangleSelector.cpp +++ b/src/libslic3r/TriangleSelector.cpp @@ -132,6 +132,15 @@ bool TriangleSelector::select_triangle(int facet_idx, FacetSupportType type, boo } + +void TriangleSelector::set_facet(int facet_idx, FacetSupportType state) +{ + assert(facet_idx < m_orig_size_indices); + undivide_triangle(facet_idx); + assert(! m_triangles[facet_idx].is_split()); + m_triangles[facet_idx].set_state(state); +} + void TriangleSelector::split_triangle(int facet_idx) { if (m_triangles[facet_idx].is_split()) { diff --git a/src/libslic3r/TriangleSelector.hpp b/src/libslic3r/TriangleSelector.hpp index 943029548..f3e23bea2 100644 --- a/src/libslic3r/TriangleSelector.hpp +++ b/src/libslic3r/TriangleSelector.hpp @@ -32,6 +32,9 @@ public: FacetSupportType new_state); // enforcer or blocker? + // Set facet of the mesh to a given state. Only works for original triangles. + void set_facet(int facet_idx, FacetSupportType state); + // Clear everything and make the tree empty. void reset(); diff --git a/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.cpp b/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.cpp index ca7695907..417d101ea 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.cpp @@ -48,7 +48,7 @@ bool GLGizmoFdmSupports::on_init() m_desc["block"] = _L("Block supports"); m_desc["remove_caption"] = _L("Shift + Left mouse button") + ": "; m_desc["remove"] = _L("Remove selection"); - m_desc["remove_all"] = _L("Remove all"); + m_desc["remove_all"] = _L("Remove all selection"); return true; } @@ -207,9 +207,9 @@ void GLGizmoFdmSupports::update_model_object() const ModelObject* mo = m_c->selection_info()->model_object(); int idx = -1; for (ModelVolume* mv : mo->volumes) { - ++idx; if (! mv->is_model_part()) continue; + ++idx; mv->m_supported_facets.set(*m_triangle_selectors[idx].get()); } } @@ -433,63 +433,9 @@ bool GLGizmoFdmSupports::gizmo_event(SLAGizmoEventType action, const Vec2d& mous } -/*void GLGizmoFdmSupports::update_vertex_buffers(const TriangleMesh* mesh, - int mesh_id, - FacetSupportType type, - const std::vector* new_facets) + +void GLGizmoFdmSupports::select_facets_by_angle(float threshold_deg, bool block) { - //std::vector& ivas = m_ivas[mesh_id][type == FacetSupportType::ENFORCER ? 0 : 1]; - - // lambda to push facet into vertex buffer - auto push_facet = [this, &mesh, &mesh_id](size_t idx, GLIndexedVertexArray& iva) { - for (int i=0; i<3; ++i) - iva.push_geometry( - mesh->its.vertices[mesh->its.indices[idx](i)].cast(), - m_c->raycaster()->raycasters()[mesh_id]->get_triangle_normal(idx).cast() - ); - size_t num = iva.triangle_indices_size; - iva.push_triangle(num, num+1, num+2); - }; - - - //if (ivas.size() == MaxVertexBuffers || ! new_facets) { - // If there are too many or they should be regenerated, make one large - // GLVertexBufferArray. - //ivas.clear(); // destructors release geometry - //ivas.push_back(GLIndexedVertexArray()); - - m_iva.release_geometry(); - m_iva.clear(); - - bool pushed = false; - for (size_t facet_idx=0; facet_idxempty()) - ivas.back().finalize_geometry(true); - else - ivas.pop_back(); - } - -}*/ - - -void GLGizmoFdmSupports::select_facets_by_angle(float threshold_deg, bool overwrite, bool block) -{ - return; -/* float threshold = (M_PI/180.)*threshold_deg; const Selection& selection = m_parent.get_selection(); const ModelObject* mo = m_c->selection_info()->model_object(); @@ -512,13 +458,12 @@ void GLGizmoFdmSupports::select_facets_by_angle(float threshold_deg, bool overwr int idx = -1; for (const stl_facet& facet : mv->mesh().stl.facet_start) { ++idx; - if (facet.normal.dot(down) > dot_limit && (overwrite || m_selected_facets[mesh_id][idx] == FacetSupportType::NONE)) - m_selected_facets[mesh_id][idx] = block - ? FacetSupportType::BLOCKER - : FacetSupportType::ENFORCER; + if (facet.normal.dot(down) > dot_limit) + m_triangle_selectors[mesh_id]->set_facet(idx, + block + ? FacetSupportType::BLOCKER + : FacetSupportType::ENFORCER); } - update_vertex_buffers(&mv->mesh(), mesh_id, FacetSupportType::ENFORCER); - update_vertex_buffers(&mv->mesh(), mesh_id, FacetSupportType::BLOCKER); } activate_internal_undo_redo_stack(true); @@ -528,7 +473,6 @@ void GLGizmoFdmSupports::select_facets_by_angle(float threshold_deg, bool overwr update_model_object(); m_parent.set_as_dirty(); m_setting_angle = false; -*/ } @@ -584,18 +528,17 @@ void GLGizmoFdmSupports::on_render_input_window(float x, float y, float bottom_l ImGui::SameLine(); if (m_imgui->button(m_desc.at("remove_all"))) { - /*ModelObject* mo = m_c->selection_info()->model_object(); + Plater::TakeSnapshot(wxGetApp().plater(), wxString(_L("Reset selection"))); + ModelObject* mo = m_c->selection_info()->model_object(); int idx = -1; for (ModelVolume* mv : mo->volumes) { - ++idx; if (mv->is_model_part()) { - m_selected_facets[idx].assign(m_selected_facets[idx].size(), FacetSupportType::NONE); - mv->m_supported_facets.clear(); - update_vertex_buffers(&mv->mesh(), idx, FacetSupportType::ENFORCER); - update_vertex_buffers(&mv->mesh(), idx, FacetSupportType::BLOCKER); - m_parent.set_as_dirty(); + ++idx; + m_triangle_selectors[idx]->reset(); } - }*/ + } + update_model_object(); + m_parent.set_as_dirty(); } const float max_tooltip_width = ImGui::GetFontSize() * 20.0f; @@ -651,12 +594,11 @@ void GLGizmoFdmSupports::on_render_input_window(float x, float y, float bottom_l ImGui::SameLine(); if (m_imgui->slider_float("", &m_angle_threshold_deg, 0.f, 90.f, "%.f")) m_parent.set_slope_range({90.f - m_angle_threshold_deg, 90.f - m_angle_threshold_deg}); - m_imgui->checkbox(wxString("Overwrite already selected facets"), m_overwrite_selected); if (m_imgui->button("Enforce")) - select_facets_by_angle(m_angle_threshold_deg, m_overwrite_selected, false); + select_facets_by_angle(m_angle_threshold_deg, false); ImGui::SameLine(); if (m_imgui->button("Block")) - select_facets_by_angle(m_angle_threshold_deg, m_overwrite_selected, true); + select_facets_by_angle(m_angle_threshold_deg, true); ImGui::SameLine(); if (m_imgui->button("Cancel")) m_setting_angle = false; diff --git a/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.hpp b/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.hpp index 7020bb7d4..2d1442164 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.hpp @@ -82,8 +82,7 @@ private: void update_from_model_object(); void activate_internal_undo_redo_stack(bool activate); - void select_facets_by_angle(float threshold, bool overwrite, bool block); - bool m_overwrite_selected = false; + void select_facets_by_angle(float threshold, bool block); float m_angle_threshold_deg = 45.f; bool is_mesh_point_clipped(const Vec3d& point) const; From 3b91d11ddfcc362552a62e8fe03c11b1115db3fd Mon Sep 17 00:00:00 2001 From: Lukas Matena Date: Wed, 15 Jul 2020 10:28:20 +0200 Subject: [PATCH 15/24] TriangleSelector: backend is aware of divided triangles --- src/libslic3r/Model.cpp | 13 ++++++------- src/libslic3r/Model.hpp | 2 +- src/libslic3r/PrintObject.cpp | 10 +++++----- src/libslic3r/TriangleSelector.cpp | 19 +++++++++++++++++++ src/libslic3r/TriangleSelector.hpp | 2 ++ 5 files changed, 33 insertions(+), 13 deletions(-) diff --git a/src/libslic3r/Model.cpp b/src/libslic3r/Model.cpp index b6bae489b..d0410c2d4 100644 --- a/src/libslic3r/Model.cpp +++ b/src/libslic3r/Model.cpp @@ -1831,13 +1831,12 @@ arrangement::ArrangePolygon ModelInstance::get_arrange_polygon() const } -std::vector FacetsAnnotation::get_facets(FacetSupportType type) const +indexed_triangle_set FacetsAnnotation::get_facets(const ModelVolume& mv, FacetSupportType type) const { - std::vector out; - /*for (auto& [facet_idx, this_type] : m_data) - if (this_type == type) - out.push_back(facet_idx); - */return out; + TriangleSelector selector(mv.mesh()); + selector.deserialize(m_data); + indexed_triangle_set out = selector.get_facets(type); + return out; } @@ -1932,7 +1931,7 @@ bool model_custom_supports_data_changed(const ModelObject& mo, const ModelObject return true; } return false; -}; +} extern bool model_has_multi_part_objects(const Model &model) { diff --git a/src/libslic3r/Model.hpp b/src/libslic3r/Model.hpp index de20e0fdc..3127af5ba 100644 --- a/src/libslic3r/Model.hpp +++ b/src/libslic3r/Model.hpp @@ -407,7 +407,7 @@ public: const std::map>& get_data() const { return m_data; } void set(const TriangleSelector& selector); - std::vector get_facets(FacetSupportType type) const; + indexed_triangle_set get_facets(const ModelVolume& mv, FacetSupportType type) const; void clear(); ClockType::time_point get_timestamp() const { return timestamp; } diff --git a/src/libslic3r/PrintObject.cpp b/src/libslic3r/PrintObject.cpp index d2bdb6d53..1a2edcf6e 100644 --- a/src/libslic3r/PrintObject.cpp +++ b/src/libslic3r/PrintObject.cpp @@ -2673,8 +2673,8 @@ void PrintObject::project_and_append_custom_supports( FacetSupportType type, std::vector& expolys) const { for (const ModelVolume* mv : this->model_object()->volumes) { - const std::vector custom_facets = mv->m_supported_facets.get_facets(type); - if (custom_facets.empty()) + const indexed_triangle_set custom_facets = mv->m_supported_facets.get_facets(*mv, type); + if (custom_facets.indices.empty()) continue; const TriangleMesh& mesh = mv->mesh(); @@ -2705,11 +2705,11 @@ void PrintObject::project_and_append_custom_supports( }; // Vector to collect resulting projections from each triangle. - std::vector projections_of_triangles(custom_facets.size()); + std::vector projections_of_triangles(custom_facets.indices.size()); // Iterate over all triangles. tbb::parallel_for( - tbb::blocked_range(0, custom_facets.size()), + tbb::blocked_range(0, custom_facets.indices.size()), [&](const tbb::blocked_range& range) { for (size_t idx = range.begin(); idx < range.end(); ++ idx) { @@ -2717,7 +2717,7 @@ void PrintObject::project_and_append_custom_supports( // Transform the triangle into worlds coords. for (int i=0; i<3; ++i) - facet[i] = tr * mesh.its.vertices[mesh.its.indices[custom_facets[idx]](i)]; + facet[i] = tr * custom_facets.vertices[custom_facets.indices[idx](i)]; // Ignore triangles with upward-pointing normal. if ((facet[1]-facet[0]).cross(facet[2]-facet[0]).z() > 0.) diff --git a/src/libslic3r/TriangleSelector.cpp b/src/libslic3r/TriangleSelector.cpp index 9210bde08..6e3f9f518 100644 --- a/src/libslic3r/TriangleSelector.cpp +++ b/src/libslic3r/TriangleSelector.cpp @@ -512,6 +512,25 @@ void TriangleSelector::perform_split(int facet_idx, FacetSupportType old_state) } + +indexed_triangle_set TriangleSelector::get_facets(FacetSupportType state) const +{ + indexed_triangle_set out; + for (const Triangle& tr : m_triangles) { + if (tr.valid && ! tr.is_split() && tr.get_state() == state) { + stl_triangle_vertex_indices indices; + for (int i=0; i<3; ++i) { + out.vertices.emplace_back(m_vertices[tr.verts_idxs[i]].v); + indices[i] = out.vertices.size() - 1; + } + out.indices.emplace_back(indices); + } + } + return out; +} + + + std::map> TriangleSelector::serialize() const { // Each original triangle of the mesh is assigned a number encoding its state diff --git a/src/libslic3r/TriangleSelector.hpp b/src/libslic3r/TriangleSelector.hpp index f3e23bea2..dc2ad9127 100644 --- a/src/libslic3r/TriangleSelector.hpp +++ b/src/libslic3r/TriangleSelector.hpp @@ -31,6 +31,8 @@ public: float radius_sqr, // squared radius of the cursor FacetSupportType new_state); // enforcer or blocker? + // Get facets currently in the given state. + indexed_triangle_set get_facets(FacetSupportType state) const; // Set facet of the mesh to a given state. Only works for original triangles. void set_facet(int facet_idx, FacetSupportType state); From afb5d929c47b54567c8ef5b2d1d8376621b60048 Mon Sep 17 00:00:00 2001 From: Lukas Matena Date: Wed, 15 Jul 2020 12:28:52 +0200 Subject: [PATCH 16/24] TriangleSelector: Schedule restarting background process after edit --- src/libslic3r/Model.cpp | 4 +++- src/libslic3r/Model.hpp | 2 +- src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.cpp | 6 +++++- 3 files changed, 9 insertions(+), 3 deletions(-) diff --git a/src/libslic3r/Model.cpp b/src/libslic3r/Model.cpp index d0410c2d4..4ff0a5c1b 100644 --- a/src/libslic3r/Model.cpp +++ b/src/libslic3r/Model.cpp @@ -1841,13 +1841,15 @@ indexed_triangle_set FacetsAnnotation::get_facets(const ModelVolume& mv, FacetSu -void FacetsAnnotation::set(const TriangleSelector& selector) +bool FacetsAnnotation::set(const TriangleSelector& selector) { std::map> sel_map = selector.serialize(); if (sel_map != m_data) { m_data = sel_map; update_timestamp(); + return true; } + return false; } diff --git a/src/libslic3r/Model.hpp b/src/libslic3r/Model.hpp index 3127af5ba..16f3f00ad 100644 --- a/src/libslic3r/Model.hpp +++ b/src/libslic3r/Model.hpp @@ -406,7 +406,7 @@ public: using ClockType = std::chrono::steady_clock; const std::map>& get_data() const { return m_data; } - void set(const TriangleSelector& selector); + bool set(const TriangleSelector& selector); indexed_triangle_set get_facets(const ModelVolume& mv, FacetSupportType type) const; void clear(); diff --git a/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.cpp b/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.cpp index 417d101ea..13c9cfef8 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.cpp @@ -204,14 +204,18 @@ void GLGizmoFdmSupports::render_cursor_circle() const void GLGizmoFdmSupports::update_model_object() const { + bool updated = false; ModelObject* mo = m_c->selection_info()->model_object(); int idx = -1; for (ModelVolume* mv : mo->volumes) { if (! mv->is_model_part()) continue; ++idx; - mv->m_supported_facets.set(*m_triangle_selectors[idx].get()); + updated |= mv->m_supported_facets.set(*m_triangle_selectors[idx].get()); } + + if (updated) + m_parent.post_event(SimpleEvent(EVT_GLCANVAS_SCHEDULE_BACKGROUND_PROCESS)); } From 5a1d9aee15f507be774f96c2a98ca0adda8cc1e4 Mon Sep 17 00:00:00 2001 From: Lukas Matena Date: Tue, 21 Jul 2020 09:01:17 +0200 Subject: [PATCH 17/24] TriangleSelector: Fix of a macOS crash Calling reset() from constructor relied on uninitialized variable --- src/libslic3r/TriangleSelector.hpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/libslic3r/TriangleSelector.hpp b/src/libslic3r/TriangleSelector.hpp index dc2ad9127..877bc122c 100644 --- a/src/libslic3r/TriangleSelector.hpp +++ b/src/libslic3r/TriangleSelector.hpp @@ -118,8 +118,8 @@ protected: float m_edge_limit_sqr = 1.f; // Number of original vertices and triangles. - int m_orig_size_vertices; - int m_orig_size_indices; + int m_orig_size_vertices = 0; + int m_orig_size_indices = 0; // Cache for cursor position, radius and direction. struct Cursor { From 74a1aeff8e8421fd9523bd9ff788e33e5692c6e1 Mon Sep 17 00:00:00 2001 From: Lukas Matena Date: Thu, 23 Jul 2020 08:17:04 +0200 Subject: [PATCH 18/24] TriangleSelector: bugfix - backend did not correctly account for mirrorring --- src/libslic3r/PrintObject.cpp | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/libslic3r/PrintObject.cpp b/src/libslic3r/PrintObject.cpp index 1a2edcf6e..273fc9c10 100644 --- a/src/libslic3r/PrintObject.cpp +++ b/src/libslic3r/PrintObject.cpp @@ -2677,10 +2677,10 @@ void PrintObject::project_and_append_custom_supports( if (custom_facets.indices.empty()) continue; - const TriangleMesh& mesh = mv->mesh(); const Transform3f& tr1 = mv->get_matrix().cast(); const Transform3f& tr2 = this->trafo().cast(); const Transform3f tr = tr2 * tr1; + const float tr_det_sign = (tr.matrix().determinant() > 0. ? 1.f : -1.f); // The projection will be at most a pentagon. Let's minimize heap @@ -2719,8 +2719,9 @@ void PrintObject::project_and_append_custom_supports( for (int i=0; i<3; ++i) facet[i] = tr * custom_facets.vertices[custom_facets.indices[idx](i)]; - // Ignore triangles with upward-pointing normal. - if ((facet[1]-facet[0]).cross(facet[2]-facet[0]).z() > 0.) + // Ignore triangles with upward-pointing normal. Don't forget about mirroring. + float z_comp = (facet[1]-facet[0]).cross(facet[2]-facet[0]).z(); + if (tr_det_sign * z_comp > 0.) continue; // Sort the three vertices according to z-coordinate. From 7ddb64783b6094e3b1c5a8006e977c5308a6b841 Mon Sep 17 00:00:00 2001 From: Lukas Matena Date: Fri, 24 Jul 2020 17:45:49 +0200 Subject: [PATCH 19/24] TriangleSelector: edge limit is derived from cursor size --- src/libslic3r/TriangleSelector.cpp | 10 ++++++++-- src/libslic3r/TriangleSelector.hpp | 5 +++-- src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.cpp | 2 +- src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.hpp | 2 +- 4 files changed, 13 insertions(+), 6 deletions(-) diff --git a/src/libslic3r/TriangleSelector.cpp b/src/libslic3r/TriangleSelector.cpp index 6e3f9f518..50d775ed8 100644 --- a/src/libslic3r/TriangleSelector.cpp +++ b/src/libslic3r/TriangleSelector.cpp @@ -35,14 +35,20 @@ void TriangleSelector::Triangle::set_division(int sides_to_split, int special_si void TriangleSelector::select_patch(const Vec3f& hit, int facet_start, const Vec3f& source, const Vec3f& dir, - float radius_sqr, FacetSupportType new_state) + float radius, FacetSupportType new_state) { assert(facet_start < m_orig_size_indices); assert(is_approx(dir.norm(), 1.f)); // Save current cursor center, squared radius and camera direction, // so we don't have to pass it around. - m_cursor = {hit, source, dir, radius_sqr}; + m_cursor = {hit, source, dir, radius*radius}; + + // In case user changed cursor size since last time, update triangle edge limit. + if (m_old_cursor_radius != radius) { + set_edge_limit(radius / 5.f); + m_old_cursor_radius = radius; + } // Now start with the facet the pointer points to and check all adjacent facets. std::vector facets_to_check{facet_start}; diff --git a/src/libslic3r/TriangleSelector.hpp b/src/libslic3r/TriangleSelector.hpp index 877bc122c..fb90cff76 100644 --- a/src/libslic3r/TriangleSelector.hpp +++ b/src/libslic3r/TriangleSelector.hpp @@ -1,7 +1,7 @@ #ifndef libslic3r_TriangleSelector_hpp_ #define libslic3r_TriangleSelector_hpp_ -#define PRUSASLICER_TRIANGLE_SELECTOR_DEBUG +// #define PRUSASLICER_TRIANGLE_SELECTOR_DEBUG #include "Point.hpp" @@ -28,7 +28,7 @@ public: int facet_start, // facet that point belongs to const Vec3f& source, // camera position (mesh coords) const Vec3f& dir, // direction of the ray (mesh coords) - float radius_sqr, // squared radius of the cursor + float radius, // radius of the cursor FacetSupportType new_state); // enforcer or blocker? // Get facets currently in the given state. @@ -130,6 +130,7 @@ protected: }; Cursor m_cursor; + float m_old_cursor_radius; // Private functions: bool select_triangle(int facet_idx, FacetSupportType type, diff --git a/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.cpp b/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.cpp index 13c9cfef8..3769e9660 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.cpp @@ -404,7 +404,7 @@ bool GLGizmoFdmSupports::gizmo_event(SLAGizmoEventType action, const Vec2d& mous // FIXME: The scaling of the mesh can be non-uniform. const Vec3d sf = Geometry::Transformation(trafo_matrix).get_scaling_factor(); const float avg_scaling = (sf(0) + sf(1) + sf(2))/3.; - const float limit = std::pow(m_cursor_radius/avg_scaling , 2.f); + const float limit = m_cursor_radius/avg_scaling; // Calculate direction from camera to the hit (in mesh coords): Vec3f camera_pos = (trafo_matrix.inverse() * camera.get_position()).cast(); diff --git a/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.hpp b/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.hpp index 2d1442164..ce24ea8d2 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.hpp @@ -56,7 +56,7 @@ private: GLUquadricObj* m_quadric; float m_cursor_radius = 2.f; - static constexpr float CursorRadiusMin = 0.f; + static constexpr float CursorRadiusMin = 0.4f; // cannot be zero static constexpr float CursorRadiusMax = 8.f; static constexpr float CursorRadiusStep = 0.2f; From 248fba82a4892e85ffcfecd24b82bd0f9ddd8135 Mon Sep 17 00:00:00 2001 From: Lukas Matena Date: Fri, 24 Jul 2020 16:53:05 +0200 Subject: [PATCH 20/24] TriangleSelector: 3MF loading and saving --- src/libslic3r/Format/3mf.cpp | 17 +++++++++ src/libslic3r/Model.cpp | 58 ++++++++++++++++++++++++++++++ src/libslic3r/Model.hpp | 2 ++ src/libslic3r/TriangleSelector.cpp | 1 + 4 files changed, 78 insertions(+) diff --git a/src/libslic3r/Format/3mf.cpp b/src/libslic3r/Format/3mf.cpp index edf55ba37..3612e6898 100644 --- a/src/libslic3r/Format/3mf.cpp +++ b/src/libslic3r/Format/3mf.cpp @@ -86,6 +86,7 @@ const char* OBJECTID_ATTR = "objectid"; const char* TRANSFORM_ATTR = "transform"; const char* PRINTABLE_ATTR = "printable"; const char* INSTANCESCOUNT_ATTR = "instances_count"; +const char* CUSTOM_SUPPORTS_ATTR = "slic3rpe:custom_supports"; const char* KEY_ATTR = "key"; const char* VALUE_ATTR = "value"; @@ -283,6 +284,7 @@ namespace Slic3r { { std::vector vertices; std::vector triangles; + std::vector custom_supports; bool empty() { @@ -293,6 +295,7 @@ namespace Slic3r { { vertices.clear(); triangles.clear(); + custom_supports.clear(); } }; @@ -1539,6 +1542,8 @@ namespace Slic3r { m_curr_object.geometry.triangles.push_back((unsigned int)get_attribute_value_int(attributes, num_attributes, V1_ATTR)); m_curr_object.geometry.triangles.push_back((unsigned int)get_attribute_value_int(attributes, num_attributes, V2_ATTR)); m_curr_object.geometry.triangles.push_back((unsigned int)get_attribute_value_int(attributes, num_attributes, V3_ATTR)); + + m_curr_object.geometry.custom_supports.push_back(get_attribute_value_string(attributes, num_attributes, CUSTOM_SUPPORTS_ATTR)); return true; } @@ -1872,6 +1877,13 @@ namespace Slic3r { volume->source.transform = Slic3r::Geometry::Transformation(volume_matrix_to_object); volume->calculate_convex_hull(); + // recreate custom supports from previously loaded attribute + assert(geometry.custom_supports.size() == triangles_count); + for (unsigned i=0; im_supported_facets.set_triangle_from_string(i, geometry.custom_supports[i]); + } + // apply the remaining volume's metadata for (const Metadata& metadata : volume_data.metadata) { @@ -2383,6 +2395,11 @@ namespace Slic3r { { stream << "v" << j + 1 << "=\"" << its.indices[i][j] + volume_it->second.first_vertex_id << "\" "; } + + std::string custom_supports_data_string = volume->m_supported_facets.get_triangle_as_string(i); + if (! custom_supports_data_string.empty()) + stream << CUSTOM_SUPPORTS_ATTR << "=\"" << custom_supports_data_string << "\" "; + stream << "/>\n"; } } diff --git a/src/libslic3r/Model.cpp b/src/libslic3r/Model.cpp index 4ff0a5c1b..3beb74f23 100644 --- a/src/libslic3r/Model.cpp +++ b/src/libslic3r/Model.cpp @@ -1862,6 +1862,64 @@ void FacetsAnnotation::clear() +// Following function takes data from a triangle and encodes it as string +// of hexadecimal numbers (one digit per triangle). Used for 3MF export, +// changing it may break backwards compatibility !!!!! +std::string FacetsAnnotation::get_triangle_as_string(int triangle_idx) const +{ + std::string out; + + auto triangle_it = m_data.find(triangle_idx); + if (triangle_it != m_data.end()) { + const std::vector& code = triangle_it->second; + int offset = 0; + while (offset < int(code.size())) { + int next_code = 0; + for (int i=3; i>=0; --i) { + next_code = next_code << 1; + next_code |= int(code[offset + i]); + } + offset += 4; + + assert(next_code >=0 && next_code <= 15); + char digit = next_code < 10 ? next_code + '0' : (next_code-10)+'A'; + out.insert(out.begin(), digit); + } + } + return out; +} + + + +// Recover triangle splitting & state from string of hexadecimal values previously +// generated by get_triangle_as_string. Used to load from 3MF. +void FacetsAnnotation::set_triangle_from_string(int triangle_id, const std::string& str) +{ + assert(! str.empty()); + m_data[triangle_id] = std::vector(); // zero current state or create new + std::vector& code = m_data[triangle_id]; + + for (auto it = str.crbegin(); it != str.crend(); ++it) { + const char ch = *it; + int dec = 0; + if (ch >= '0' && ch<='9') + dec = int(ch - '0'); + else if (ch >='A' && ch <= 'F') + dec = 10 + int(ch - 'A'); + else + assert(false); + + // Convert to binary and append into code. + for (int i=0; i<4; ++i) { + code.insert(code.end(), bool(dec & (1 << i))); + } + } + + +} + + + // Test whether the two models contain the same number of ModelObjects with the same set of IDs // ordered in the same order. In that case it is not necessary to kill the background processing. bool model_object_list_equal(const Model &model_old, const Model &model_new) diff --git a/src/libslic3r/Model.hpp b/src/libslic3r/Model.hpp index 16f3f00ad..92dc84d17 100644 --- a/src/libslic3r/Model.hpp +++ b/src/libslic3r/Model.hpp @@ -409,6 +409,8 @@ public: bool set(const TriangleSelector& selector); indexed_triangle_set get_facets(const ModelVolume& mv, FacetSupportType type) const; void clear(); + std::string get_triangle_as_string(int i) const; + void set_triangle_from_string(int triangle_id, const std::string& str); ClockType::time_point get_timestamp() const { return timestamp; } bool is_same_as(const FacetsAnnotation& other) const { diff --git a/src/libslic3r/TriangleSelector.cpp b/src/libslic3r/TriangleSelector.cpp index 50d775ed8..763bf5861 100644 --- a/src/libslic3r/TriangleSelector.cpp +++ b/src/libslic3r/TriangleSelector.cpp @@ -603,6 +603,7 @@ void TriangleSelector::deserialize(const std::map> data) reset(); // dump any current state for (const auto& [triangle_id, code] : data) { assert(triangle_id < int(m_triangles.size())); + assert(! code.empty()); int processed_triangles = 0; struct ProcessingInfo { int facet_id = 0; From 67d2f438458c2185d01d8db02fe74f5f56e209db Mon Sep 17 00:00:00 2001 From: David Kocik Date: Sun, 14 Jun 2020 23:14:44 +0200 Subject: [PATCH 21/24] Showing Eject button only after exporting is finished. Fix of #4212 --- src/slic3r/GUI/Plater.cpp | 5 ++++- src/slic3r/GUI/RemovableDriveManager.cpp | 2 ++ src/slic3r/GUI/RemovableDriveManager.hpp | 6 ++++-- 3 files changed, 10 insertions(+), 3 deletions(-) diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index ec13610b8..e4e43bb7c 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -3450,7 +3450,7 @@ void Plater::priv::on_slicing_completed(wxCommandEvent &) break; default: break; } -} +} void Plater::priv::on_process_completed(wxCommandEvent &evt) { @@ -3510,7 +3510,10 @@ void Plater::priv::on_process_completed(wxCommandEvent &evt) show_action_buttons(true); } else if (this->writing_to_removable_device || wxGetApp().get_mode() == comSimple) + { + wxGetApp().removable_drive_manager()->set_exporting_finished(true); show_action_buttons(false); + } this->writing_to_removable_device = false; } diff --git a/src/slic3r/GUI/RemovableDriveManager.cpp b/src/slic3r/GUI/RemovableDriveManager.cpp index d67ac4a22..d865fe347 100644 --- a/src/slic3r/GUI/RemovableDriveManager.cpp +++ b/src/slic3r/GUI/RemovableDriveManager.cpp @@ -393,6 +393,7 @@ bool RemovableDriveManager::set_and_verify_last_save_path(const std::string &pat #endif // REMOVABLE_DRIVE_MANAGER_OS_CALLBACKS m_last_save_path = this->get_removable_drive_from_path(path); + m_exporting_finished = false; return ! m_last_save_path.empty(); } @@ -407,6 +408,7 @@ RemovableDriveManager::RemovableDrivesStatus RemovableDriveManager::status() } if (! out.has_eject) m_last_save_path.clear(); + out.has_eject = out.has_eject && m_exporting_finished; return out; } diff --git a/src/slic3r/GUI/RemovableDriveManager.hpp b/src/slic3r/GUI/RemovableDriveManager.hpp index e1a8d6faf..26ee12e40 100644 --- a/src/slic3r/GUI/RemovableDriveManager.hpp +++ b/src/slic3r/GUI/RemovableDriveManager.hpp @@ -83,7 +83,7 @@ public: // Public to be accessible from RemovableDriveManagerMM::on_device_unmount OSX notification handler. // It would be better to make this method private and friend to RemovableDriveManagerMM, but RemovableDriveManagerMM is an ObjectiveC class. void update(); - + void set_exporting_finished(bool b) { m_exporting_finished = b; } #ifdef _WIN32 // Called by Win32 Volume arrived / detached callback. void volumes_changed(); @@ -121,7 +121,9 @@ private: std::vector::const_iterator find_last_save_path_drive_data() const; // Set with set_and_verify_last_save_path() to a removable drive path to be ejected. std::string m_last_save_path; - + // Verifies that exporting was finished so drive can be ejected. + // Set false by set_and_verify_last_save_path() that is called just before exporting. + bool m_exporting_finished; #if __APPLE__ void register_window_osx(); void unregister_window_osx(); From 1dc3561e2cc52fbe32aa13b2e74c09f6de621a1b Mon Sep 17 00:00:00 2001 From: David Kocik Date: Sun, 14 Jun 2020 23:53:17 +0200 Subject: [PATCH 22/24] added 's' in https when pointing users to our github --- src/slic3r/GUI/MainFrame.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/slic3r/GUI/MainFrame.cpp b/src/slic3r/GUI/MainFrame.cpp index 64a1319d4..687634d11 100644 --- a/src/slic3r/GUI/MainFrame.cpp +++ b/src/slic3r/GUI/MainFrame.cpp @@ -106,7 +106,7 @@ DPIFrame(NULL, wxID_ANY, "", wxDefaultPosition, wxDefaultSize, wxDEFAULT_FRAME_S m_statusbar->embed(this); m_statusbar->set_status_text(_(L("Version")) + " " + SLIC3R_VERSION + - _(L(" - Remember to check for updates at http://github.com/prusa3d/PrusaSlicer/releases"))); + _(L(" - Remember to check for updates at https://github.com/prusa3d/PrusaSlicer/releases"))); /* Load default preset bitmaps before a tabpanel initialization, * but after filling of an em_unit value @@ -1141,7 +1141,7 @@ void MainFrame::init_menubar() append_menu_item(helpMenu, wxID_ANY, _(L("Prusa 3D &Drivers")), _(L("Open the Prusa3D drivers download page in your browser")), [this](wxCommandEvent&) { wxGetApp().open_web_page_localized("https://www.prusa3d.com/downloads"); }); append_menu_item(helpMenu, wxID_ANY, _(L("Software &Releases")), _(L("Open the software releases page in your browser")), - [this](wxCommandEvent&) { wxLaunchDefaultBrowser("http://github.com/prusa3d/PrusaSlicer/releases"); }); + [this](wxCommandEvent&) { wxLaunchDefaultBrowser("https://github.com/prusa3d/PrusaSlicer/releases"); }); //# my $versioncheck = $self->_append_menu_item($helpMenu, "Check for &Updates...", "Check for new Slic3r versions", sub{ //# wxTheApp->check_version(1); //# }); @@ -1158,7 +1158,7 @@ void MainFrame::init_menubar() append_menu_item(helpMenu, wxID_ANY, _(L("Show &Configuration Folder")), _(L("Show user configuration folder (datadir)")), [this](wxCommandEvent&) { Slic3r::GUI::desktop_open_datadir_folder(); }); append_menu_item(helpMenu, wxID_ANY, _(L("Report an I&ssue")), wxString::Format(_(L("Report an issue on %s")), SLIC3R_APP_NAME), - [this](wxCommandEvent&) { wxLaunchDefaultBrowser("http://github.com/prusa3d/slic3r/issues/new"); }); + [this](wxCommandEvent&) { wxLaunchDefaultBrowser("https://github.com/prusa3d/slic3r/issues/new"); }); append_menu_item(helpMenu, wxID_ANY, wxString::Format(_(L("&About %s")), SLIC3R_APP_NAME), _(L("Show about dialog")), [this](wxCommandEvent&) { Slic3r::GUI::about(); }); helpMenu->AppendSeparator(); From 864ecf750cd1dab68d50ac8ee5d4bdac77d4f4c8 Mon Sep 17 00:00:00 2001 From: David Kocik Date: Mon, 15 Jun 2020 00:34:12 +0200 Subject: [PATCH 23/24] Deleted default value in Plater::export_gcode(bool prefer_removable). Only place where it is not sure what value should be is in btn_reslice - i chose true --- src/slic3r/GUI/MainFrame.cpp | 2 +- src/slic3r/GUI/Plater.cpp | 2 +- src/slic3r/GUI/Plater.hpp | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/slic3r/GUI/MainFrame.cpp b/src/slic3r/GUI/MainFrame.cpp index 687634d11..521dcab80 100644 --- a/src/slic3r/GUI/MainFrame.cpp +++ b/src/slic3r/GUI/MainFrame.cpp @@ -909,7 +909,7 @@ void MainFrame::init_menubar() wxMenu* export_menu = new wxMenu(); wxMenuItem* item_export_gcode = append_menu_item(export_menu, wxID_ANY, _(L("Export &G-code")) + dots +"\tCtrl+G", _(L("Export current plate as G-code")), - [this](wxCommandEvent&) { if (m_plater) m_plater->export_gcode(); }, "export_gcode", nullptr, + [this](wxCommandEvent&) { if (m_plater) m_plater->export_gcode(false); }, "export_gcode", nullptr, [this](){return can_export_gcode(); }, this); m_changeable_menu_items.push_back(item_export_gcode); wxMenuItem* item_send_gcode = append_menu_item(export_menu, wxID_ANY, _(L("S&end G-code")) + dots +"\tCtrl+Shift+G", _(L("Send to print current plate as G-code")), diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index e4e43bb7c..761f574e1 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -929,7 +929,7 @@ Sidebar::Sidebar(Plater *parent) { const bool export_gcode_after_slicing = wxGetKeyState(WXK_SHIFT); if (export_gcode_after_slicing) - p->plater->export_gcode(); + p->plater->export_gcode(true); else p->plater->reslice(); p->plater->select_view_3D("Preview"); diff --git a/src/slic3r/GUI/Plater.hpp b/src/slic3r/GUI/Plater.hpp index 5d60e006b..a08b19fa3 100644 --- a/src/slic3r/GUI/Plater.hpp +++ b/src/slic3r/GUI/Plater.hpp @@ -220,7 +220,7 @@ public: void cut(size_t obj_idx, size_t instance_idx, coordf_t z, bool keep_upper = true, bool keep_lower = true, bool rotate_lower = false); - void export_gcode(bool prefer_removable = true); + void export_gcode(bool prefer_removable); void export_stl(bool extended = false, bool selection_only = false); void export_amf(); void export_3mf(const boost::filesystem::path& output_path = boost::filesystem::path()); From 18594261d293651bf5f8234cfefe692e7709a103 Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Mon, 27 Jul 2020 12:18:21 +0200 Subject: [PATCH 24/24] Added handling of mouse wheel events to ImGuiWrapper --- src/slic3r/GUI/GLCanvas3D.cpp | 5 +++++ src/slic3r/GUI/ImGuiWrapper.cpp | 3 +++ 2 files changed, 8 insertions(+) diff --git a/src/slic3r/GUI/GLCanvas3D.cpp b/src/slic3r/GUI/GLCanvas3D.cpp index b4e672c4f..2d4527784 100644 --- a/src/slic3r/GUI/GLCanvas3D.cpp +++ b/src/slic3r/GUI/GLCanvas3D.cpp @@ -3300,6 +3300,11 @@ void GLCanvas3D::on_mouse_wheel(wxMouseEvent& evt) if (evt.MiddleIsDown()) return; + if (wxGetApp().imgui()->update_mouse_data(evt)) { + m_dirty = true; + return; + } + #if ENABLE_RETINA_GL const float scale = m_retina_helper->get_scale_factor(); evt.SetX(evt.GetX() * scale); diff --git a/src/slic3r/GUI/ImGuiWrapper.cpp b/src/slic3r/GUI/ImGuiWrapper.cpp index 51a9a6d4e..88dd02ccb 100644 --- a/src/slic3r/GUI/ImGuiWrapper.cpp +++ b/src/slic3r/GUI/ImGuiWrapper.cpp @@ -176,6 +176,9 @@ bool ImGuiWrapper::update_mouse_data(wxMouseEvent& evt) io.MouseDown[0] = evt.LeftIsDown(); io.MouseDown[1] = evt.RightIsDown(); io.MouseDown[2] = evt.MiddleIsDown(); + float wheel_delta = static_cast(evt.GetWheelDelta()); + if (wheel_delta != 0.0f) + io.MouseWheel = static_cast(evt.GetWheelRotation()) / wheel_delta; unsigned buttons = (evt.LeftIsDown() ? 1 : 0) | (evt.RightIsDown() ? 2 : 0) | (evt.MiddleIsDown() ? 4 : 0); m_mouse_buttons = buttons;