From 46da54ffaf6c1877de0710f5fcd4a20cda1932ac Mon Sep 17 00:00:00 2001 From: Lukas Matena Date: Fri, 3 Jun 2022 12:53:02 +0200 Subject: [PATCH 01/13] First implementation of SurfaceMesh --- src/libslic3r/CMakeLists.txt | 1 + src/libslic3r/SurfaceMesh.hpp | 151 ++++++++++++++++++++++++++++++++++ 2 files changed, 152 insertions(+) create mode 100644 src/libslic3r/SurfaceMesh.hpp diff --git a/src/libslic3r/CMakeLists.txt b/src/libslic3r/CMakeLists.txt index e637b1402f..396494a181 100644 --- a/src/libslic3r/CMakeLists.txt +++ b/src/libslic3r/CMakeLists.txt @@ -249,6 +249,7 @@ set(SLIC3R_SOURCES Surface.hpp SurfaceCollection.cpp SurfaceCollection.hpp + SurfaceMesh.hpp SVG.cpp SVG.hpp Technologies.hpp diff --git a/src/libslic3r/SurfaceMesh.hpp b/src/libslic3r/SurfaceMesh.hpp new file mode 100644 index 0000000000..47d5ed0dcc --- /dev/null +++ b/src/libslic3r/SurfaceMesh.hpp @@ -0,0 +1,151 @@ +#ifndef slic3r_SurfaceMesh_hpp_ +#define slic3r_SurfaceMesh_hpp_ + +#include + +namespace Slic3r { + +class TriangleMesh; + + + +enum Face_index : int; + +class Halfedge_index { + friend class SurfaceMesh; + +public: + Halfedge_index() : m_face(Face_index(-1)), m_side(0) {} + Face_index face() const { return m_face; } + bool is_invalid() const { return int(m_face) < 0; } + bool operator!=(const Halfedge_index& rhs) const { return ! ((*this) == rhs); } + bool operator==(const Halfedge_index& rhs) const { return m_face == rhs.m_face && m_side == rhs.m_side; } + +private: + Halfedge_index(int face_idx, unsigned char side_idx) : m_face(Face_index(face_idx)), m_side(side_idx) {} + + Face_index m_face; + unsigned char m_side; +}; + + + +class Vertex_index { + friend class SurfaceMesh; + +public: + Vertex_index() : m_face(Face_index(-1)), m_vertex_idx(0) {} + bool is_invalid() const { return int(m_face) < 0; } + bool operator==(const Vertex_index& rhs) const = delete; // Use SurfaceMesh::is_same_vertex. + +private: + Vertex_index(int face_idx, unsigned char vertex_idx) : m_face(Face_index(face_idx)), m_vertex_idx(vertex_idx) {} + + Face_index m_face; + unsigned char m_vertex_idx; +}; + + + +class SurfaceMesh { +public: + explicit SurfaceMesh(const indexed_triangle_set& its) + : m_its(its), + m_face_neighbors(its_face_neighbors_par(its)) + {} + SurfaceMesh(const SurfaceMesh&) = delete; + SurfaceMesh& operator=(const SurfaceMesh&) = delete; + + Vertex_index source(Halfedge_index h) const { assert(! h.is_invalid()); return Vertex_index(h.m_face, h.m_side); } + Vertex_index target(Halfedge_index h) const { assert(! h.is_invalid()); return Vertex_index(h.m_face, h.m_side == 2 ? 0 : h.m_side + 1); } + Face_index face(Halfedge_index h) const { assert(! h.is_invalid()); return h.m_face; } + + Halfedge_index next(Halfedge_index h) const { assert(! h.is_invalid()); h.m_side = (h.m_side + 1) % 3; return h; } + Halfedge_index prev(Halfedge_index h) const { assert(! h.is_invalid()); h.m_side = (h.m_side == 0 ? 2 : h.m_side - 1); return h; } + Halfedge_index halfedge(Vertex_index v) const { return Halfedge_index(v.m_face, (v.m_vertex_idx == 0 ? 2 : v.m_vertex_idx - 1)); } + Halfedge_index halfedge(Face_index f) const { return Halfedge_index(f, 0); } + Halfedge_index opposite(Halfedge_index h) const { + if (h.is_invalid()) + return h; + + int face_idx = m_face_neighbors[h.m_face][h.m_side]; + Halfedge_index h_candidate = halfedge(Face_index(face_idx)); + + if (h_candidate.is_invalid()) + return Halfedge_index(); // invalid + + for (int i=0; i<3; ++i) { + if (is_same_vertex(source(h_candidate), target(h))) { + // Meshes in PrusaSlicer should be fixed enough for the following not to happen. + assert(is_same_vertex(target(h_candidate), source(h))); + return h_candidate; + } + h_candidate = next(h_candidate); + } + return Halfedge_index(); // invalid + } + + Halfedge_index next_around_target(Halfedge_index h) const { return opposite(next(h)); } + Halfedge_index prev_around_target(Halfedge_index h) const { Halfedge_index op = opposite(h); return (op.is_invalid() ? Halfedge_index() : prev(op)); } + Halfedge_index next_around_source(Halfedge_index h) const { Halfedge_index op = opposite(h); return (op.is_invalid() ? Halfedge_index() : next(op)); } + Halfedge_index prev_around_source(Halfedge_index h) const { return opposite(prev(h)); } + Halfedge_index halfedge(Vertex_index source, Vertex_index target) const + { + Halfedge_index hi(source.m_face, source.m_vertex_idx); + assert(! hi.is_invalid()); + + const Vertex_index orig_target = this->target(hi); + Vertex_index current_target = orig_target; + + while (! is_same_vertex(current_target, target)) { + hi = next_around_source(hi); + if (hi.is_invalid()) + break; + current_target = this->target(hi); + if (is_same_vertex(current_target, orig_target)) + return Halfedge_index(); // invalid + } + + return hi; + } + + const stl_vertex& point(Vertex_index v) const { return m_its.vertices[m_its.indices[v.m_face][v.m_vertex_idx]]; } + + size_t degree(Vertex_index v) const + { + Halfedge_index h_first = halfedge(v); + Halfedge_index h = next_around_target(h_first); + size_t degree = 2; + while (! h.is_invalid() && h != h_first) { + h = next_around_target(h); + ++degree; + } + return h.is_invalid() ? 0 : degree - 1; + } + + size_t degree(Face_index f) const { + size_t total = 0; + for (unsigned char i=0; i<3; ++i) { + size_t d = degree(Vertex_index(f, i)); + if (d == 0) + return 0; + total += d; + } + assert(total - 6 >= 0); + return total - 6; // we counted 3 halfedges from f, and one more for each neighbor + } + + bool is_border(Halfedge_index h) const { return m_face_neighbors[h.m_face][h.m_side] == -1; } + + bool is_same_vertex(const Vertex_index& a, const Vertex_index& b) const { return m_its.indices[a.m_face][a.m_vertex_idx] == m_its.indices[b.m_face][b.m_vertex_idx]; } + + + +private: + const std::vector m_face_neighbors; + const indexed_triangle_set& m_its; +}; + +} //namespace Slic3r + +#endif // slic3r_SurfaceMesh_hpp_ From 9a163c356b713a00600ece17f76fa5805e205198 Mon Sep 17 00:00:00 2001 From: Lukas Matena Date: Fri, 3 Jun 2022 12:53:34 +0200 Subject: [PATCH 02/13] Added some unit tests (SurfaceMesh) --- tests/libslic3r/CMakeLists.txt | 1 + tests/libslic3r/test_surface_mesh.cpp | 122 ++++++++++++++++++++++++++ 2 files changed, 123 insertions(+) create mode 100644 tests/libslic3r/test_surface_mesh.cpp diff --git a/tests/libslic3r/CMakeLists.txt b/tests/libslic3r/CMakeLists.txt index cf89b2246f..3622ac2b8e 100644 --- a/tests/libslic3r/CMakeLists.txt +++ b/tests/libslic3r/CMakeLists.txt @@ -24,6 +24,7 @@ add_executable(${_TEST_NAME}_tests test_voronoi.cpp test_optimizers.cpp test_png_io.cpp + test_surface_mesh.cpp test_timeutils.cpp test_indexed_triangle_set.cpp test_astar.cpp diff --git a/tests/libslic3r/test_surface_mesh.cpp b/tests/libslic3r/test_surface_mesh.cpp new file mode 100644 index 0000000000..34ff356679 --- /dev/null +++ b/tests/libslic3r/test_surface_mesh.cpp @@ -0,0 +1,122 @@ +#include +#include + + +#include + +using namespace Slic3r; + + +// Generate a broken cube mesh. Face 8 is inverted, face 11 is missing. +indexed_triangle_set its_make_cube_broken(double xd, double yd, double zd) +{ + auto x = float(xd), y = float(yd), z = float(zd); + return { + { {0, 1, 2}, {0, 2, 3}, {4, 5, 6}, {4, 6, 7}, + {0, 4, 7}, {0, 7, 1}, {1, 7, 6}, {1, 6, 2}, + {2, 5, 6}, {2, 5, 3}, {4, 0, 3} /*missing face*/ }, + { {x, y, 0}, {x, 0, 0}, {0, 0, 0}, {0, y, 0}, + {x, y, z}, {0, y, z}, {0, 0, z}, {x, 0, z} } + }; +} + + + +TEST_CASE("SurfaceMesh on a cube", "[SurfaceMesh]") { + indexed_triangle_set cube = its_make_cube(1., 1., 1.); + SurfaceMesh sm(cube); + const Halfedge_index hi_first = sm.halfedge(Face_index(0)); + Halfedge_index hi = hi_first; + + REQUIRE(! hi_first.is_invalid()); + + SECTION("next / prev halfedge") { + hi = sm.next(hi); + REQUIRE(hi != hi_first); + hi = sm.next(hi); + hi = sm.next(hi); + REQUIRE(hi == hi_first); + hi = sm.prev(hi); + REQUIRE(hi != hi_first); + hi = sm.prev(hi); + hi = sm.prev(hi); + REQUIRE(hi == hi_first); + } + + SECTION("next_around_target") { + // Check that we get to the same halfedge after applying next_around_target + // four times. + const Vertex_index target_vert = sm.target(hi_first); + for (int i=0; i<4;++i) { + hi = sm.next_around_target(hi); + REQUIRE((hi == hi_first) == (i == 3)); + REQUIRE(sm.is_same_vertex(sm.target(hi), target_vert)); + REQUIRE(! sm.is_border(hi)); + } + } + + SECTION("iterate around target and source") { + hi = sm.next_around_target(hi); + hi = sm.prev_around_target(hi); + hi = sm.prev_around_source(hi); + hi = sm.next_around_source(hi); + REQUIRE(hi == hi_first); + } + + SECTION("opposite") { + const Vertex_index target = sm.target(hi); + const Vertex_index source = sm.source(hi); + hi = sm.opposite(hi); + REQUIRE(sm.is_same_vertex(target, sm.source(hi))); + REQUIRE(sm.is_same_vertex(source, sm.target(hi))); + hi = sm.opposite(hi); + REQUIRE(hi == hi_first); + } + + SECTION("halfedges walk") { + for (int i=0; i<4; ++i) { + hi = sm.next(hi); + hi = sm.opposite(hi); + } + REQUIRE(hi == hi_first); + } + + SECTION("point accessor") { + Halfedge_index hi = sm.halfedge(Face_index(0)); + hi = sm.opposite(hi); + hi = sm.prev(hi); + hi = sm.opposite(hi); + REQUIRE(hi.face() == Face_index(6)); + REQUIRE(sm.point(sm.target(hi)).isApprox(cube.vertices[7])); + } +} + + + + +TEST_CASE("SurfaceMesh on a broken cube", "[SurfaceMesh]") { + indexed_triangle_set cube = its_make_cube_broken(1., 1., 1.); + SurfaceMesh sm(cube); + + SECTION("Check inverted face") { + Halfedge_index hi = sm.halfedge(Face_index(8)); + for (int i=0; i<3; ++i) { + REQUIRE(! hi.is_invalid()); + REQUIRE(sm.is_border(hi)); + } + REQUIRE(hi == sm.halfedge(Face_index(8))); + hi = sm.opposite(hi); + REQUIRE(hi.is_invalid()); + } + + SECTION("missing face") { + Halfedge_index hi = sm.halfedge(Face_index(0)); + for (int i=0; i<3; ++i) + hi = sm.next_around_source(hi); + hi = sm.next(hi); + REQUIRE(sm.is_border(hi)); + REQUIRE(! hi.is_invalid()); + hi = sm.opposite(hi); + REQUIRE(hi.is_invalid()); + } +} From 1e494e30af62b36c6e6a1ad1196ee1a20ee07957 Mon Sep 17 00:00:00 2001 From: Lukas Matena Date: Fri, 17 Jun 2022 12:19:47 +0200 Subject: [PATCH 03/13] SurfaceMesh testing (to be reverted later) --- src/libslic3r/TriangleMesh.cpp | 11 +- src/slic3r/GUI/Gizmos/GLGizmoFlatten.cpp | 253 ++++++++++++----------- src/slic3r/GUI/Gizmos/GLGizmoFlatten.hpp | 2 + 3 files changed, 149 insertions(+), 117 deletions(-) diff --git a/src/libslic3r/TriangleMesh.cpp b/src/libslic3r/TriangleMesh.cpp index 0dffdaab0e..8dc87c6fb1 100644 --- a/src/libslic3r/TriangleMesh.cpp +++ b/src/libslic3r/TriangleMesh.cpp @@ -877,13 +877,20 @@ Polygon its_convex_hull_2d_above(const indexed_triangle_set &its, const Transfor indexed_triangle_set its_make_cube(double xd, double yd, double zd) { auto x = float(xd), y = float(yd), z = float(zd); - return { + /*return { { {0, 1, 2}, {0, 2, 3}, {4, 5, 6}, {4, 6, 7}, {0, 4, 7}, {0, 7, 1}, {1, 7, 6}, {1, 6, 2}, {2, 6, 5}, {2, 5, 3}, {4, 0, 3}, {4, 3, 5} }, { {x, y, 0}, {x, 0, 0}, {0, 0, 0}, {0, y, 0}, {x, y, z}, {0, y, z}, {0, 0, z}, {x, 0, z} } - }; + };*/ + return { + { {0, 1, 2}, {0, 2, 3}, {4, 5, 6}, {4, 6, 7}, + {0, 4, 7}, {0, 7, 1}, {1, 7, 6}, {1, 6, 2}, + {2, 5, 6}, {2, 5, 3}, {4, 0, 3}, /*{4, 3, 5}*/ }, + { {x, y, 0}, {x, 0, 0}, {0, 0, 0}, {0, y, 0}, + {x, y, z}, {0, y, z}, {0, 0, z}, {x, 0, z} } + }; } indexed_triangle_set its_make_prism(float width, float length, float height) diff --git a/src/slic3r/GUI/Gizmos/GLGizmoFlatten.cpp b/src/slic3r/GUI/Gizmos/GLGizmoFlatten.cpp index f854edb817..f8d6abad98 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoFlatten.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoFlatten.cpp @@ -12,20 +12,45 @@ #include "libslic3r/Geometry/ConvexHull.hpp" #include "libslic3r/Model.hpp" +#include "libslic3r/SurfaceMesh.hpp" #include #include + + namespace Slic3r { namespace GUI { static const Slic3r::ColorRGBA DEFAULT_PLANE_COLOR = { 0.9f, 0.9f, 0.9f, 0.5f }; static const Slic3r::ColorRGBA DEFAULT_HOVER_PLANE_COLOR = { 0.9f, 0.9f, 0.9f, 0.75f }; + + + +// TESTING: +static Halfedge_index hi; +static bool hi_initialized = false; +static std::unique_ptr sm_ptr; +static Vertex_index src; +static Vertex_index tgt; + + + + + + GLGizmoFlatten::GLGizmoFlatten(GLCanvas3D& parent, const std::string& icon_filename, unsigned int sprite_id) : GLGizmoBase(parent, icon_filename, sprite_id) -{} +{ + indexed_triangle_set a = its_make_cone(0.05, .2); + its_rotate_x(a, M_PI); + its_translate(a, stl_vertex(0., 0., .8)); + indexed_triangle_set b = its_make_cylinder(.02, 0.8); + its_merge(a, b); + arrow.init_from(a); +} bool GLGizmoFlatten::on_mouse(const wxMouseEvent &mouse_event) { @@ -39,9 +64,7 @@ bool GLGizmoFlatten::on_mouse(const wxMouseEvent &mouse_event) m_mouse_left_down = true; Selection &selection = m_parent.get_selection(); if (selection.is_single_full_instance()) { - // Rotate the object so the normal points downward: - selection.flattening_rotate(m_planes[m_hover_id].normal); - m_parent.do_rotate(L("Gizmo-Place on Face")); + hi = sm_ptr->halfedge(Face_index(m_hover_id)); } return true; } @@ -105,6 +128,19 @@ void GLGizmoFlatten::on_render() const Selection& selection = m_parent.get_selection(); #if ENABLE_LEGACY_OPENGL_REMOVAL + + + + if (! hi_initialized) { + const indexed_triangle_set& its = m_c->selection_info()->model_object()->volumes.front()->mesh().its; + sm_ptr.reset(new SurfaceMesh(its)); + hi = sm_ptr->halfedge(Face_index(0)); + hi_initialized = true; + } + SurfaceMesh& sm = *sm_ptr; + + + GLShaderProgram* shader = wxGetApp().get_shader("flat"); if (shader == nullptr) return; @@ -135,8 +171,12 @@ void GLGizmoFlatten::on_render() update_planes(); for (int i = 0; i < (int)m_planes.size(); ++i) { #if ENABLE_LEGACY_OPENGL_REMOVAL + int cur_face = hi.is_invalid() ? 1000000 : sm.face(hi); + for (int i=0; i < m_planes.size(); ++i) { m_planes[i].vbo.set_color(i == m_hover_id ? DEFAULT_HOVER_PLANE_COLOR : DEFAULT_PLANE_COLOR); - m_planes[i].vbo.render(); + if (i == cur_face) + m_planes[i].vbo.set_color(i == m_hover_id ? ColorRGBA(.5f, 0.f, 0.f, 1.f) : ColorRGBA(1.f, 0.f, 0.f, 1.f)); + m_planes[i].vbo.render(); #else glsafe(::glColor4fv(i == m_hover_id ? DEFAULT_HOVER_PLANE_COLOR.data() : DEFAULT_PLANE_COLOR.data())); if (m_planes[i].vbo.has_VBOs()) @@ -146,8 +186,92 @@ void GLGizmoFlatten::on_render() #if !ENABLE_GL_SHADERS_ATTRIBUTES glsafe(::glPopMatrix()); #endif // !ENABLE_GL_SHADERS_ATTRIBUTES + } } + + + + ///////////////// + //////////////// + ////////////////// + + auto draw_arrow = [&](const Vec3d& from, const Vec3d& to) -> void { + Vec3d desired_pos = from; + Vec3d desired_dir = to - from; + double desired_len = desired_dir.norm(); + desired_dir.normalize(); + + Transform3d m = selection.get_volume(*selection.get_volume_idxs().begin())->get_instance_transformation().get_matrix(); + m.translate(desired_pos); + Eigen::Quaterniond q; + Transform3d rot = Transform3d::Identity(); + rot.matrix().block(0, 0, 3, 3) = q.setFromTwoVectors(Vec3d::UnitZ(), desired_dir).toRotationMatrix(); + Transform3d sc = Transform3d::Identity(); + sc.scale(desired_len); + m = m*sc*rot; + + const Camera& camera = wxGetApp().plater()->get_camera(); + Transform3d view_model_matrix = camera.get_view_matrix() * + Geometry::assemble_transform(selection.get_volume(*selection.get_volume_idxs().begin())->get_sla_shift_z() * Vec3d::UnitZ()) * m; + + shader->set_uniform("view_model_matrix", view_model_matrix); + arrow.render(); + }; + + m_imgui->begin(std::string("DEBUG")); + bool invalid = hi.is_invalid(); + if (invalid) { + if (m_imgui->button(std::string("HALFEDGE INVALID (Click to reset)"))) + hi = sm.halfedge(Face_index(0)); + } else { + m_imgui->text(sm.is_border(hi) ? "BORDER HALFEDGE !" : "Halfedge is not border"); + m_imgui->text((std::string("Face: ") + std::to_string(int(hi.face()))).c_str()); + m_imgui->text(std::string("Target degree:" + std::to_string(sm.degree(sm.target(hi))))); + m_imgui->text(std::string("Face degree:" + std::to_string(sm.degree(sm.face(hi))))); + } + m_imgui->disabled_begin(invalid); + if (m_imgui->button(std::string("next"))) + hi = sm.next(hi); + if (m_imgui->button(std::string("prev"))) + hi = sm.prev(hi); + if (m_imgui->button(std::string("opposite"))) + hi = sm.opposite(hi); + if (m_imgui->button(std::string("next_around_target"))) + hi = sm.next_around_target(hi); + if (m_imgui->button(std::string("prev_around_target"))) + hi = sm.prev_around_target(hi); + if (m_imgui->button(std::string("next_around_source"))) + hi = sm.next_around_source(hi); + if (m_imgui->button(std::string("prev_around_source"))) + hi = sm.prev_around_source(hi); + if (m_imgui->button(std::string("remember one"))) + src = sm.target(hi); + if (m_imgui->button(std::string("switch to halfedge"))) { + tgt = sm.target(hi); + hi = sm.halfedge(src, tgt); + } + + if (invalid) + m_imgui->disabled_end(); + m_imgui->end(); + + if (! hi.is_invalid()) { + Vec3d a = sm.point(sm.source(hi)).cast(); + Vec3d b = sm.point(sm.target(hi)).cast(); + draw_arrow(a, b); + } + + + ///////////////// + //////////////// + ////////////////// + + + + + + glsafe(::glEnable(GL_CULL_FACE)); glsafe(::glDisable(GL_BLEND)); @@ -222,11 +346,11 @@ void GLGizmoFlatten::update_planes() for (const ModelVolume* vol : mo->volumes) { if (vol->type() != ModelVolumeType::MODEL_PART) continue; - TriangleMesh vol_ch = vol->get_convex_hull(); + TriangleMesh vol_ch = vol->mesh(); //vol->get_convex_hull(); vol_ch.transform(vol->get_matrix()); ch.merge(vol_ch); } - ch = ch.convex_hull_3d(); + //ch = ch.convex_hull_3d(); m_planes.clear(); #if ENABLE_WORLD_COORDINATE const Transform3d inst_matrix = mo->instances.front()->get_matrix_no_offset(); @@ -247,47 +371,18 @@ void GLGizmoFlatten::update_planes() std::vector facet_visited(num_of_facets, false); int facet_queue_cnt = 0; const stl_normal* normal_ptr = nullptr; - int facet_idx = 0; - while (1) { - // Find next unvisited triangle: - for (; facet_idx < num_of_facets; ++ facet_idx) - if (!facet_visited[facet_idx]) { - facet_queue[facet_queue_cnt ++] = facet_idx; - facet_visited[facet_idx] = true; - normal_ptr = &face_normals[facet_idx]; - m_planes.emplace_back(); - break; - } - if (facet_idx == num_of_facets) - break; // Everything was visited already - - while (facet_queue_cnt > 0) { - int facet_idx = facet_queue[-- facet_queue_cnt]; - const stl_normal& this_normal = face_normals[facet_idx]; - if (std::abs(this_normal(0) - (*normal_ptr)(0)) < 0.001 && std::abs(this_normal(1) - (*normal_ptr)(1)) < 0.001 && std::abs(this_normal(2) - (*normal_ptr)(2)) < 0.001) { - const Vec3i face = ch.its.indices[facet_idx]; - for (int j=0; j<3; ++j) - m_planes.back().vertices.emplace_back(ch.its.vertices[face[j]].cast()); - - facet_visited[facet_idx] = true; - for (int j = 0; j < 3; ++ j) - if (int neighbor_idx = face_neighbors[facet_idx][j]; neighbor_idx >= 0 && ! facet_visited[neighbor_idx]) - facet_queue[facet_queue_cnt ++] = neighbor_idx; - } - } - m_planes.back().normal = normal_ptr->cast(); + + for (size_t i=0; i()); + m_planes.back().normal = face_normals[i].cast(); Pointf3s& verts = m_planes.back().vertices; // Now we'll transform all the points into world coordinates, so that the areas, angles and distances // make real sense. verts = transform(verts, inst_matrix); - - // if this is a just a very small triangle, remove it to speed up further calculations (it would be rejected later anyway): - if (verts.size() == 3 && - ((verts[0] - verts[1]).norm() < minimal_side - || (verts[0] - verts[2]).norm() < minimal_side - || (verts[1] - verts[2]).norm() < minimal_side)) - m_planes.pop_back(); } // Let's prepare transformation of the normal vector from mesh to instance coordinates. @@ -319,84 +414,12 @@ void GLGizmoFlatten::update_planes() polygon = Slic3r::Geometry::convex_hull(polygon); polygon = transform(polygon, tr.inverse()); - // Calculate area of the polygons and discard ones that are too small - float& area = m_planes[polygon_id].area; - area = 0.f; - for (unsigned int i = 0; i < polygon.size(); i++) // Shoelace formula - area += polygon[i](0)*polygon[i + 1 < polygon.size() ? i + 1 : 0](1) - polygon[i + 1 < polygon.size() ? i + 1 : 0](0)*polygon[i](1); - area = 0.5f * std::abs(area); - - bool discard = false; - if (area < minimal_area) - discard = true; - else { - // We also check the inner angles and discard polygons with angles smaller than the following threshold - const double angle_threshold = ::cos(10.0 * (double)PI / 180.0); - - for (unsigned int i = 0; i < polygon.size(); ++i) { - const Vec3d& prec = polygon[(i == 0) ? polygon.size() - 1 : i - 1]; - const Vec3d& curr = polygon[i]; - const Vec3d& next = polygon[(i == polygon.size() - 1) ? 0 : i + 1]; - - if ((prec - curr).normalized().dot((next - curr).normalized()) > angle_threshold) { - discard = true; - break; - } - } - } - - if (discard) { - m_planes[polygon_id--] = std::move(m_planes.back()); - m_planes.pop_back(); - continue; - } - // We will shrink the polygon a little bit so it does not touch the object edges: Vec3d centroid = std::accumulate(polygon.begin(), polygon.end(), Vec3d(0.0, 0.0, 0.0)); centroid /= (double)polygon.size(); for (auto& vertex : polygon) vertex = 0.9f*vertex + 0.1f*centroid; - // Polygon is now simple and convex, we'll round the corners to make them look nicer. - // The algorithm takes a vertex, calculates middles of respective sides and moves the vertex - // towards their average (controlled by 'aggressivity'). This is repeated k times. - // In next iterations, the neighbours are not always taken at the middle (to increase the - // rounding effect at the corners, where we need it most). - const unsigned int k = 10; // number of iterations - const float aggressivity = 0.2f; // agressivity - const unsigned int N = polygon.size(); - std::vector> neighbours; - if (k != 0) { - Pointf3s points_out(2*k*N); // vector long enough to store the future vertices - for (unsigned int j=0; j vertices; // should be in fact local in update_planes() #if ENABLE_LEGACY_OPENGL_REMOVAL From f0cf420a8457e1355181ba0e22efcb8f4856dcf8 Mon Sep 17 00:00:00 2001 From: Lukas Matena Date: Fri, 3 Jun 2022 15:37:11 +0200 Subject: [PATCH 04/13] Measuring: separated another gizmo --- resources/icons/measure.svg | 13 + src/libslic3r/SurfaceMesh.hpp | 1 + src/slic3r/CMakeLists.txt | 2 + src/slic3r/GUI/Gizmos/GLGizmoMeasure.cpp | 388 ++++++++++++++++++++++ src/slic3r/GUI/Gizmos/GLGizmoMeasure.hpp | 75 +++++ src/slic3r/GUI/Gizmos/GLGizmosManager.cpp | 2 + src/slic3r/GUI/Gizmos/GLGizmosManager.hpp | 1 + 7 files changed, 482 insertions(+) create mode 100644 resources/icons/measure.svg create mode 100644 src/slic3r/GUI/Gizmos/GLGizmoMeasure.cpp create mode 100644 src/slic3r/GUI/Gizmos/GLGizmoMeasure.hpp diff --git a/resources/icons/measure.svg b/resources/icons/measure.svg new file mode 100644 index 0000000000..275c522251 --- /dev/null +++ b/resources/icons/measure.svg @@ -0,0 +1,13 @@ + + + Layer 1 + + + + + + + + + + \ No newline at end of file diff --git a/src/libslic3r/SurfaceMesh.hpp b/src/libslic3r/SurfaceMesh.hpp index 47d5ed0dcc..e8ada4cd80 100644 --- a/src/libslic3r/SurfaceMesh.hpp +++ b/src/libslic3r/SurfaceMesh.hpp @@ -138,6 +138,7 @@ public: bool is_border(Halfedge_index h) const { return m_face_neighbors[h.m_face][h.m_side] == -1; } bool is_same_vertex(const Vertex_index& a, const Vertex_index& b) const { return m_its.indices[a.m_face][a.m_vertex_idx] == m_its.indices[b.m_face][b.m_vertex_idx]; } + Vec3i get_face_neighbors(Face_index face_id) const { assert(int(face_id) < int(m_face_neighbors.size())); return m_face_neighbors[face_id]; } diff --git a/src/slic3r/CMakeLists.txt b/src/slic3r/CMakeLists.txt index 29b8b7e736..a56556baa5 100644 --- a/src/slic3r/CMakeLists.txt +++ b/src/slic3r/CMakeLists.txt @@ -61,6 +61,8 @@ set(SLIC3R_GUI_SOURCES GUI/Gizmos/GLGizmoSimplify.hpp GUI/Gizmos/GLGizmoMmuSegmentation.cpp GUI/Gizmos/GLGizmoMmuSegmentation.hpp + GUI/Gizmos/GLGizmoMeasure.cpp + GUI/Gizmos/GLGizmoMeasure.hpp GUI/GLSelectionRectangle.cpp GUI/GLSelectionRectangle.hpp GUI/GLModel.hpp diff --git a/src/slic3r/GUI/Gizmos/GLGizmoMeasure.cpp b/src/slic3r/GUI/Gizmos/GLGizmoMeasure.cpp new file mode 100644 index 0000000000..572ea75dbe --- /dev/null +++ b/src/slic3r/GUI/Gizmos/GLGizmoMeasure.cpp @@ -0,0 +1,388 @@ +// Include GLGizmoBase.hpp before I18N.hpp as it includes some libigl code, which overrides our localization "L" macro. +#include "GLGizmoMeasure.hpp" +#include "slic3r/GUI/GLCanvas3D.hpp" +#include "slic3r/GUI/GUI_App.hpp" +#include "slic3r/GUI/Plater.hpp" + +#include "slic3r/GUI/Gizmos/GLGizmosCommon.hpp" + +#include "libslic3r/Geometry/ConvexHull.hpp" +#include "libslic3r/Model.hpp" +#include "libslic3r/SurfaceMesh.hpp" + +#include + +#include + +namespace Slic3r { +namespace GUI { + +static const Slic3r::ColorRGBA DEFAULT_PLANE_COLOR = { 0.9f, 0.9f, 0.9f, 0.5f }; +static const Slic3r::ColorRGBA DEFAULT_HOVER_PLANE_COLOR = { 0.9f, 0.9f, 0.9f, 0.75f }; + +GLGizmoMeasure::GLGizmoMeasure(GLCanvas3D& parent, const std::string& icon_filename, unsigned int sprite_id) + : GLGizmoBase(parent, icon_filename, sprite_id) +{} + + + +bool GLGizmoMeasure::on_mouse(const wxMouseEvent &mouse_event) +{ + if (mouse_event.Moving()) { + // only for sure + m_mouse_left_down = false; + return false; + } + if (mouse_event.LeftDown()) { + if (m_hover_id != -1) { + m_mouse_left_down = true; + Selection &selection = m_parent.get_selection(); + if (selection.is_single_full_instance()) { + // Rotate the object so the normal points downward: + selection.flattening_rotate(m_planes[m_hover_id].normal); + m_parent.do_rotate(L("Gizmo-Place on Face")); + } + return true; + } + + // fix: prevent restart gizmo when reselect object + // take responsibility for left up + if (m_parent.get_first_hover_volume_idx() >= 0) m_mouse_left_down = true; + + } else if (mouse_event.LeftUp()) { + if (m_mouse_left_down) { + // responsible for mouse left up after selecting plane + m_mouse_left_down = false; + return true; + } + } else if (mouse_event.Leaving()) { + m_mouse_left_down = false; + } + return false; +} + + + +void GLGizmoMeasure::data_changed() +{ + const Selection & selection = m_parent.get_selection(); + const ModelObject *model_object = nullptr; + if (selection.is_single_full_instance() || + selection.is_from_single_object() ) { + model_object = selection.get_model()->objects[selection.get_object_idx()]; + } + set_flattening_data(model_object); +} + + + +bool GLGizmoMeasure::on_init() +{ + // FIXME m_shortcut_key = WXK_CONTROL_F; + return true; +} + + + +void GLGizmoMeasure::on_set_state() +{ +} + + + +CommonGizmosDataID GLGizmoMeasure::on_get_requirements() const +{ + return CommonGizmosDataID::SelectionInfo; +} + + + +std::string GLGizmoMeasure::on_get_name() const +{ + return _u8L("Measure"); +} + + + +bool GLGizmoMeasure::on_is_activable() const +{ + // This is assumed in GLCanvas3D::do_rotate, do not change this + // without updating that function too. + return m_parent.get_selection().is_single_full_instance(); +} + + + +void GLGizmoMeasure::on_render() +{ + const Selection& selection = m_parent.get_selection(); + + GLShaderProgram* shader = wxGetApp().get_shader("flat"); + if (shader == nullptr) + return; + + shader->start_using(); + + glsafe(::glClear(GL_DEPTH_BUFFER_BIT)); + + glsafe(::glEnable(GL_DEPTH_TEST)); + glsafe(::glEnable(GL_BLEND)); + + if (selection.is_single_full_instance()) { + const Transform3d& m = selection.get_volume(*selection.get_volume_idxs().begin())->get_instance_transformation().get_matrix(); + const Camera& camera = wxGetApp().plater()->get_camera(); + const Transform3d view_model_matrix = camera.get_view_matrix() * + Geometry::assemble_transform(selection.get_volume(*selection.get_volume_idxs().begin())->get_sla_shift_z() * Vec3d::UnitZ()) * m; + + shader->set_uniform("view_model_matrix", view_model_matrix); + shader->set_uniform("projection_matrix", camera.get_projection_matrix()); + if (this->is_plane_update_necessary()) + update_planes(); + for (int i = 0; i < (int)m_planes.size(); ++i) { + m_planes[i].vbo.set_color(i == m_hover_id ? DEFAULT_HOVER_PLANE_COLOR : DEFAULT_PLANE_COLOR); + m_planes[i].vbo.render(); + } + } + + glsafe(::glEnable(GL_CULL_FACE)); + glsafe(::glDisable(GL_BLEND)); + + shader->stop_using(); +} + + + + + +#if ! ENABLE_LEGACY_OPENGL_REMOVAL + #error NOT IMPLEMENTED +#endif +#if ! ENABLE_GL_SHADERS_ATTRIBUTES + #error NOT IMPLEMENTED +#endif + + + + + +void GLGizmoMeasure::on_render_for_picking() +{ + const Selection& selection = m_parent.get_selection(); + + GLShaderProgram* shader = wxGetApp().get_shader("flat"); + if (shader == nullptr) + return; + + shader->start_using(); + + glsafe(::glDisable(GL_DEPTH_TEST)); + glsafe(::glDisable(GL_BLEND)); + + if (selection.is_single_full_instance() && !wxGetKeyState(WXK_CONTROL)) { + const Transform3d& m = selection.get_volume(*selection.get_volume_idxs().begin())->get_instance_transformation().get_matrix(); + const Camera& camera = wxGetApp().plater()->get_camera(); + const Transform3d view_model_matrix = camera.get_view_matrix() * + Geometry::assemble_transform(selection.get_volume(*selection.get_volume_idxs().begin())->get_sla_shift_z() * Vec3d::UnitZ()) * m; + + shader->set_uniform("view_model_matrix", view_model_matrix); + shader->set_uniform("projection_matrix", camera.get_projection_matrix()); + if (this->is_plane_update_necessary()) + update_planes(); + for (int i = 0; i < (int)m_planes.size(); ++i) { + m_planes[i].vbo.set_color(picking_color_component(i)); + m_planes[i].vbo.render(); + } + } + + glsafe(::glEnable(GL_CULL_FACE)); + + shader->stop_using(); +} + + + +void GLGizmoMeasure::set_flattening_data(const ModelObject* model_object) +{ + if (model_object != m_old_model_object) { + m_planes.clear(); + m_planes_valid = false; + } +} + + + +void GLGizmoMeasure::update_planes() +{ + const ModelObject* mo = m_c->selection_info()->model_object(); + TriangleMesh ch; + for (const ModelVolume* vol : mo->volumes) { + if (vol->type() != ModelVolumeType::MODEL_PART) + continue; + TriangleMesh vol_ch = vol->mesh(); + vol_ch.transform(vol->get_matrix()); + ch.merge(vol_ch); + } + m_planes.clear(); + const Transform3d& inst_matrix = mo->instances.front()->get_matrix(); + + // Now we'll go through all the facets and append Points of facets sharing the same normal. + // This part is still performed in mesh coordinate system. + const int num_of_facets = ch.facets_count(); + std::vector face_to_plane(num_of_facets, 0); + const std::vector face_normals = its_face_normals(ch.its); + const std::vector face_neighbors = its_face_neighbors(ch.its); + std::vector facet_queue(num_of_facets, 0); + std::vector facet_visited(num_of_facets, false); + int facet_queue_cnt = 0; + const stl_normal* normal_ptr = nullptr; + int facet_idx = 0; + + auto is_same_normal = [](const stl_normal& a, const stl_normal& b) -> bool { + return (std::abs(a(0) - b(0)) < 0.001 && std::abs(a(1) - b(1)) < 0.001 && std::abs(a(2) - b(2)) < 0.001); + }; + + while (1) { + // Find next unvisited triangle: + for (; facet_idx < num_of_facets; ++ facet_idx) + if (!facet_visited[facet_idx]) { + facet_queue[facet_queue_cnt ++] = facet_idx; + facet_visited[facet_idx] = true; + normal_ptr = &face_normals[facet_idx]; + face_to_plane[facet_idx] = m_planes.size(); + m_planes.emplace_back(); + break; + } + if (facet_idx == num_of_facets) + break; // Everything was visited already + + while (facet_queue_cnt > 0) { + int facet_idx = facet_queue[-- facet_queue_cnt]; + const stl_normal& this_normal = face_normals[facet_idx]; + if (is_same_normal(this_normal, *normal_ptr)) { + const Vec3i& face = ch.its.indices[facet_idx]; + for (int j=0; j<3; ++j) + m_planes.back().vertices.emplace_back(ch.its.vertices[face[j]].cast()); + + facet_visited[facet_idx] = true; + face_to_plane[facet_idx] = m_planes.size() - 1; + for (int j = 0; j < 3; ++ j) + if (int neighbor_idx = face_neighbors[facet_idx][j]; neighbor_idx >= 0 && ! facet_visited[neighbor_idx]) + facet_queue[facet_queue_cnt ++] = neighbor_idx; + } + } + m_planes.back().normal = normal_ptr->cast(); + + Pointf3s& verts = m_planes.back().vertices; + // Now we'll transform all the points into world coordinates, so that the areas, angles and distances + // make real sense. + verts = transform(verts, inst_matrix); + } + + // Let's prepare transformation of the normal vector from mesh to instance coordinates. + Geometry::Transformation t(inst_matrix); + Vec3d scaling = t.get_scaling_factor(); + t.set_scaling_factor(Vec3d(1./scaling(0), 1./scaling(1), 1./scaling(2))); + + // Now we'll go through all the polygons, transform the points into xy plane to process them: + for (unsigned int polygon_id=0; polygon_id < m_planes.size(); ++polygon_id) { + Pointf3s& polygon = m_planes[polygon_id].vertices; + const Vec3d& normal = m_planes[polygon_id].normal; + + // transform the normal according to the instance matrix: + Vec3d normal_transformed = t.get_matrix() * normal; + + // We are going to rotate about z and y to flatten the plane + Eigen::Quaterniond q; + Transform3d m = Transform3d::Identity(); + m.matrix().block(0, 0, 3, 3) = q.setFromTwoVectors(normal_transformed, Vec3d::UnitZ()).toRotationMatrix(); + polygon = transform(polygon, m); + + // Now to remove the inner points. We'll misuse Geometry::convex_hull for that, but since + // it works in fixed point representation, we will rescale the polygon to avoid overflows. + // And yes, it is a nasty thing to do. Whoever has time is free to refactor. + Vec3d bb_size = BoundingBoxf3(polygon).size(); + float sf = std::min(1./bb_size(0), 1./bb_size(1)); + Transform3d tr = Geometry::assemble_transform(Vec3d::Zero(), Vec3d::Zero(), Vec3d(sf, sf, 1.f)); + polygon = transform(polygon, tr); + polygon = Slic3r::Geometry::convex_hull(polygon); + polygon = transform(polygon, tr.inverse()); + + // We will shrink the polygon a little bit so it does not touch the object edges: + Vec3d centroid = std::accumulate(polygon.begin(), polygon.end(), Vec3d(0.0, 0.0, 0.0)); + centroid /= (double)polygon.size(); + for (auto& vertex : polygon) + vertex = 0.95f*vertex + 0.05f*centroid; + + // Raise a bit above the object surface to avoid flickering: + for (auto& b : polygon) + b(2) += 0.1f; + + // Transform back to 3D (and also back to mesh coordinates) + polygon = transform(polygon, inst_matrix.inverse() * m.inverse()); + } + + // We'll sort the planes by area and only keep the 254 largest ones (because of the picking pass limitations): + std::sort(m_planes.rbegin(), m_planes.rend(), [](const PlaneData& a, const PlaneData& b) { return a.area < b.area; }); + m_planes.resize(std::min((int)m_planes.size(), 254)); + + // Planes are finished - let's save what we calculated it from: + m_volumes_matrices.clear(); + m_volumes_types.clear(); + for (const ModelVolume* vol : mo->volumes) { + m_volumes_matrices.push_back(vol->get_matrix()); + m_volumes_types.push_back(vol->type()); + } + m_first_instance_scale = mo->instances.front()->get_scaling_factor(); + m_first_instance_mirror = mo->instances.front()->get_mirror(); + m_old_model_object = mo; + + // And finally create respective VBOs. The polygon is convex with + // the vertices in order, so triangulation is trivial. + for (auto& plane : m_planes) { + GLModel::Geometry init_data; + init_data.format = { GLModel::Geometry::EPrimitiveType::TriangleFan, GLModel::Geometry::EVertexLayout::P3N3 }; + init_data.reserve_vertices(plane.vertices.size()); + init_data.reserve_indices(plane.vertices.size()); + // vertices + indices + for (size_t i = 0; i < plane.vertices.size(); ++i) { + init_data.add_vertex((Vec3f)plane.vertices[i].cast(), (Vec3f)plane.normal.cast()); + init_data.add_index((unsigned int)i); + } + plane.vbo.init_from(std::move(init_data)); + + // FIXME: vertices should really be local, they need not + // persist now when we use VBOs + plane.vertices.clear(); + plane.vertices.shrink_to_fit(); + } + + m_planes_valid = true; +} + + + +bool GLGizmoMeasure::is_plane_update_necessary() const +{ + const ModelObject* mo = m_c->selection_info()->model_object(); + if (m_state != On || ! mo || mo->instances.empty()) + return false; + + if (! m_planes_valid || mo != m_old_model_object + || mo->volumes.size() != m_volumes_matrices.size()) + return true; + + // We want to recalculate when the scale changes - some planes could (dis)appear. + if (! mo->instances.front()->get_scaling_factor().isApprox(m_first_instance_scale) + || ! mo->instances.front()->get_mirror().isApprox(m_first_instance_mirror)) + return true; + + for (unsigned int i=0; i < mo->volumes.size(); ++i) + if (! mo->volumes[i]->get_matrix().isApprox(m_volumes_matrices[i]) + || mo->volumes[i]->type() != m_volumes_types[i]) + return true; + + return false; +} + +} // namespace GUI +} // namespace Slic3r diff --git a/src/slic3r/GUI/Gizmos/GLGizmoMeasure.hpp b/src/slic3r/GUI/Gizmos/GLGizmoMeasure.hpp new file mode 100644 index 0000000000..9bf87a29f6 --- /dev/null +++ b/src/slic3r/GUI/Gizmos/GLGizmoMeasure.hpp @@ -0,0 +1,75 @@ +#ifndef slic3r_GLGizmoMeasure_hpp_ +#define slic3r_GLGizmoMeasure_hpp_ + +#include "GLGizmoBase.hpp" +#if ENABLE_LEGACY_OPENGL_REMOVAL +#include "slic3r/GUI/GLModel.hpp" +#else +#include "slic3r/GUI/3DScene.hpp" +#endif // ENABLE_LEGACY_OPENGL_REMOVAL + + +namespace Slic3r { + +enum class ModelVolumeType : int; + + +namespace GUI { + + +class GLGizmoMeasure : public GLGizmoBase +{ +// This gizmo does not use grabbers. The m_hover_id relates to polygon managed by the class itself. + +private: + + struct PlaneData { + std::vector vertices; // should be in fact local in update_planes() + std::vector borders_facets; + GLModel vbo; + Vec3d normal; + float area; + }; + + // This holds information to decide whether recalculation is necessary: + std::vector m_volumes_matrices; + std::vector m_volumes_types; + Vec3d m_first_instance_scale; + Vec3d m_first_instance_mirror; + + std::vector m_planes; + std::vector m_face_to_plane; + bool m_mouse_left_down = false; // for detection left_up of this gizmo + bool m_planes_valid = false; + const ModelObject* m_old_model_object = nullptr; + std::vector instances_matrices; + + void update_planes(); + bool is_plane_update_necessary() const; + void set_flattening_data(const ModelObject* model_object); + +public: + GLGizmoMeasure(GLCanvas3D& parent, const std::string& icon_filename, unsigned int sprite_id); + + /// + /// Apply rotation on select plane + /// + /// Keep information about mouse click + /// Return True when use the information otherwise False. + bool on_mouse(const wxMouseEvent &mouse_event) override; + + void data_changed() override; +protected: + bool on_init() override; + std::string on_get_name() const override; + bool on_is_activable() const override; + void on_render() override; + void on_render_for_picking() override; + void on_set_state() override; + CommonGizmosDataID on_get_requirements() const override; +}; + +} // namespace GUI +} // namespace Slic3r + +#endif // slic3r_GLGizmoMeasure_hpp_ diff --git a/src/slic3r/GUI/Gizmos/GLGizmosManager.cpp b/src/slic3r/GUI/Gizmos/GLGizmosManager.cpp index 39a1cba8e3..2a1ccd176b 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmosManager.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmosManager.cpp @@ -21,6 +21,7 @@ #include "slic3r/GUI/Gizmos/GLGizmoSeam.hpp" #include "slic3r/GUI/Gizmos/GLGizmoMmuSegmentation.hpp" #include "slic3r/GUI/Gizmos/GLGizmoSimplify.hpp" +#include "slic3r/GUI/Gizmos/GLGizmoMeasure.hpp" #include "libslic3r/format.hpp" #include "libslic3r/Model.hpp" @@ -106,6 +107,7 @@ bool GLGizmosManager::init() m_gizmos.emplace_back(new GLGizmoSeam(m_parent, "seam.svg", 8)); m_gizmos.emplace_back(new GLGizmoMmuSegmentation(m_parent, "mmu_segmentation.svg", 9)); m_gizmos.emplace_back(new GLGizmoSimplify(m_parent, "cut.svg", 10)); + m_gizmos.emplace_back(new GLGizmoMeasure(m_parent, "measure.svg", 11)); m_common_gizmos_data.reset(new CommonGizmosDataPool(&m_parent)); diff --git a/src/slic3r/GUI/Gizmos/GLGizmosManager.hpp b/src/slic3r/GUI/Gizmos/GLGizmosManager.hpp index 2e9e6bb65c..1a203621d2 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmosManager.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmosManager.hpp @@ -80,6 +80,7 @@ public: Seam, MmuSegmentation, Simplify, + Measure, Undefined }; From 5d8aaed18f97906481ae6bbd53d8bc175ab28e9f Mon Sep 17 00:00:00 2001 From: Lukas Matena Date: Tue, 21 Jun 2022 12:47:47 +0200 Subject: [PATCH 05/13] Measuring: Initial plane detection --- src/slic3r/GUI/Gizmos/GLGizmoMeasure.cpp | 144 +++++++++++------------ src/slic3r/GUI/Gizmos/GLGizmoMeasure.hpp | 4 +- 2 files changed, 74 insertions(+), 74 deletions(-) diff --git a/src/slic3r/GUI/Gizmos/GLGizmoMeasure.cpp b/src/slic3r/GUI/Gizmos/GLGizmoMeasure.cpp index 572ea75dbe..129407764c 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoMeasure.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoMeasure.cpp @@ -127,6 +127,7 @@ void GLGizmoMeasure::on_render() glsafe(::glEnable(GL_DEPTH_TEST)); glsafe(::glEnable(GL_BLEND)); + glsafe(::glLineWidth(5.f)); if (selection.is_single_full_instance()) { const Transform3d& m = selection.get_volume(*selection.get_volume_idxs().begin())->get_instance_transformation().get_matrix(); @@ -227,15 +228,14 @@ void GLGizmoMeasure::update_planes() // Now we'll go through all the facets and append Points of facets sharing the same normal. // This part is still performed in mesh coordinate system. - const int num_of_facets = ch.facets_count(); - std::vector face_to_plane(num_of_facets, 0); + const size_t num_of_facets = ch.facets_count(); + std::vector face_to_plane(num_of_facets, size_t(-1)); const std::vector face_normals = its_face_normals(ch.its); const std::vector face_neighbors = its_face_neighbors(ch.its); std::vector facet_queue(num_of_facets, 0); - std::vector facet_visited(num_of_facets, false); int facet_queue_cnt = 0; const stl_normal* normal_ptr = nullptr; - int facet_idx = 0; + size_t seed_facet_idx = 0; auto is_same_normal = [](const stl_normal& a, const stl_normal& b) -> bool { return (std::abs(a(0) - b(0)) < 0.001 && std::abs(a(1) - b(1)) < 0.001 && std::abs(a(2) - b(2)) < 0.001); @@ -243,16 +243,15 @@ void GLGizmoMeasure::update_planes() while (1) { // Find next unvisited triangle: - for (; facet_idx < num_of_facets; ++ facet_idx) - if (!facet_visited[facet_idx]) { - facet_queue[facet_queue_cnt ++] = facet_idx; - facet_visited[facet_idx] = true; - normal_ptr = &face_normals[facet_idx]; - face_to_plane[facet_idx] = m_planes.size(); + for (; seed_facet_idx < num_of_facets; ++ seed_facet_idx) + if (face_to_plane[seed_facet_idx] == size_t(-1)) { + facet_queue[facet_queue_cnt ++] = seed_facet_idx; + normal_ptr = &face_normals[seed_facet_idx]; + face_to_plane[seed_facet_idx] = m_planes.size(); m_planes.emplace_back(); break; } - if (facet_idx == num_of_facets) + if (seed_facet_idx == num_of_facets) break; // Everything was visited already while (facet_queue_cnt > 0) { @@ -260,70 +259,69 @@ void GLGizmoMeasure::update_planes() const stl_normal& this_normal = face_normals[facet_idx]; if (is_same_normal(this_normal, *normal_ptr)) { const Vec3i& face = ch.its.indices[facet_idx]; - for (int j=0; j<3; ++j) - m_planes.back().vertices.emplace_back(ch.its.vertices[face[j]].cast()); - facet_visited[facet_idx] = true; face_to_plane[facet_idx] = m_planes.size() - 1; + m_planes.back().facets.emplace_back(facet_idx); for (int j = 0; j < 3; ++ j) - if (int neighbor_idx = face_neighbors[facet_idx][j]; neighbor_idx >= 0 && ! facet_visited[neighbor_idx]) + if (int neighbor_idx = face_neighbors[facet_idx][j]; neighbor_idx >= 0 && face_to_plane[neighbor_idx] == size_t(-1)) facet_queue[facet_queue_cnt ++] = neighbor_idx; } } - m_planes.back().normal = normal_ptr->cast(); - Pointf3s& verts = m_planes.back().vertices; - // Now we'll transform all the points into world coordinates, so that the areas, angles and distances - // make real sense. - verts = transform(verts, inst_matrix); + m_planes.back().normal = normal_ptr->cast(); } + assert(std::none_of(face_to_plane.begin(), face_to_plane.end(), [](size_t val) { return val == size_t(-1); })); + + SurfaceMesh sm(ch.its); + for (int plane_id=0; plane_id < m_planes.size(); ++plane_id) { + //int plane_id = 5; { + const auto& facets = m_planes[plane_id].facets; + std::vector pts; + for (int face_id=0; face_id{ sm.point(sm.source(he)).cast() }); + Vertex_index target = sm.target(he); + const Halfedge_index he_start = he; + + do { + const Halfedge_index he_orig = he; + he = sm.next_around_target(he); + while ( face_to_plane[sm.face(he)] == plane_id && he != he_orig) + he = sm.next_around_target(he); + he = sm.opposite(he); + m_planes[plane_id].borders.back().emplace_back(sm.point(sm.source(he)).cast()); + } while (he != he_start); + } + } + + + // DEBUGGING: + //m_planes.erase(std::remove_if(m_planes.begin(), m_planes.end(), [](const PlaneData& p) { return p.borders.empty(); }), m_planes.end()); + + + + + // Let's prepare transformation of the normal vector from mesh to instance coordinates. Geometry::Transformation t(inst_matrix); Vec3d scaling = t.get_scaling_factor(); t.set_scaling_factor(Vec3d(1./scaling(0), 1./scaling(1), 1./scaling(2))); - // Now we'll go through all the polygons, transform the points into xy plane to process them: - for (unsigned int polygon_id=0; polygon_id < m_planes.size(); ++polygon_id) { - Pointf3s& polygon = m_planes[polygon_id].vertices; - const Vec3d& normal = m_planes[polygon_id].normal; - - // transform the normal according to the instance matrix: - Vec3d normal_transformed = t.get_matrix() * normal; - - // We are going to rotate about z and y to flatten the plane - Eigen::Quaterniond q; - Transform3d m = Transform3d::Identity(); - m.matrix().block(0, 0, 3, 3) = q.setFromTwoVectors(normal_transformed, Vec3d::UnitZ()).toRotationMatrix(); - polygon = transform(polygon, m); - - // Now to remove the inner points. We'll misuse Geometry::convex_hull for that, but since - // it works in fixed point representation, we will rescale the polygon to avoid overflows. - // And yes, it is a nasty thing to do. Whoever has time is free to refactor. - Vec3d bb_size = BoundingBoxf3(polygon).size(); - float sf = std::min(1./bb_size(0), 1./bb_size(1)); - Transform3d tr = Geometry::assemble_transform(Vec3d::Zero(), Vec3d::Zero(), Vec3d(sf, sf, 1.f)); - polygon = transform(polygon, tr); - polygon = Slic3r::Geometry::convex_hull(polygon); - polygon = transform(polygon, tr.inverse()); - - // We will shrink the polygon a little bit so it does not touch the object edges: - Vec3d centroid = std::accumulate(polygon.begin(), polygon.end(), Vec3d(0.0, 0.0, 0.0)); - centroid /= (double)polygon.size(); - for (auto& vertex : polygon) - vertex = 0.95f*vertex + 0.05f*centroid; - - // Raise a bit above the object surface to avoid flickering: - for (auto& b : polygon) - b(2) += 0.1f; - - // Transform back to 3D (and also back to mesh coordinates) - polygon = transform(polygon, inst_matrix.inverse() * m.inverse()); - } - - // We'll sort the planes by area and only keep the 254 largest ones (because of the picking pass limitations): - std::sort(m_planes.rbegin(), m_planes.rend(), [](const PlaneData& a, const PlaneData& b) { return a.area < b.area; }); - m_planes.resize(std::min((int)m_planes.size(), 254)); + // Planes are finished - let's save what we calculated it from: m_volumes_matrices.clear(); @@ -339,21 +337,23 @@ void GLGizmoMeasure::update_planes() // And finally create respective VBOs. The polygon is convex with // the vertices in order, so triangulation is trivial. for (auto& plane : m_planes) { - GLModel::Geometry init_data; - init_data.format = { GLModel::Geometry::EPrimitiveType::TriangleFan, GLModel::Geometry::EVertexLayout::P3N3 }; - init_data.reserve_vertices(plane.vertices.size()); - init_data.reserve_indices(plane.vertices.size()); - // vertices + indices - for (size_t i = 0; i < plane.vertices.size(); ++i) { - init_data.add_vertex((Vec3f)plane.vertices[i].cast(), (Vec3f)plane.normal.cast()); - init_data.add_index((unsigned int)i); + for (auto& vertices : plane.borders) { + GLModel::Geometry init_data; + init_data.format = { GLModel::Geometry::EPrimitiveType::LineStrip, GLModel::Geometry::EVertexLayout::P3N3 }; + init_data.reserve_vertices(vertices.size()); + init_data.reserve_indices(vertices.size()); + // vertices + indices + for (size_t i = 0; i < vertices.size(); ++i) { + init_data.add_vertex((Vec3f)vertices[i].cast(), (Vec3f)plane.normal.cast()); + init_data.add_index((unsigned int)i); + } + plane.vbo.init_from(std::move(init_data)); } - plane.vbo.init_from(std::move(init_data)); // FIXME: vertices should really be local, they need not // persist now when we use VBOs - plane.vertices.clear(); - plane.vertices.shrink_to_fit(); + plane.borders.clear(); + plane.borders.shrink_to_fit(); } m_planes_valid = true; diff --git a/src/slic3r/GUI/Gizmos/GLGizmoMeasure.hpp b/src/slic3r/GUI/Gizmos/GLGizmoMeasure.hpp index 9bf87a29f6..87ad73d8c6 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoMeasure.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoMeasure.hpp @@ -24,8 +24,8 @@ class GLGizmoMeasure : public GLGizmoBase private: struct PlaneData { - std::vector vertices; // should be in fact local in update_planes() - std::vector borders_facets; + std::vector> borders; // should be in fact local in update_planes() + std::vector facets; GLModel vbo; Vec3d normal; float area; From 985b16e858621579d17c1b716ae42bd55ede7456 Mon Sep 17 00:00:00 2001 From: Lukas Matena Date: Wed, 22 Jun 2022 10:34:58 +0200 Subject: [PATCH 06/13] Measuring: Simple visualization --- src/slic3r/GUI/Gizmos/GLGizmoMeasure.cpp | 62 ++++++++++++++++++------ src/slic3r/GUI/Gizmos/GLGizmoMeasure.hpp | 4 +- 2 files changed, 49 insertions(+), 17 deletions(-) diff --git a/src/slic3r/GUI/Gizmos/GLGizmoMeasure.cpp b/src/slic3r/GUI/Gizmos/GLGizmoMeasure.cpp index 129407764c..de3fc398b9 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoMeasure.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoMeasure.cpp @@ -17,15 +17,13 @@ namespace Slic3r { namespace GUI { -static const Slic3r::ColorRGBA DEFAULT_PLANE_COLOR = { 0.9f, 0.9f, 0.9f, 0.5f }; -static const Slic3r::ColorRGBA DEFAULT_HOVER_PLANE_COLOR = { 0.9f, 0.9f, 0.9f, 0.75f }; +static const Slic3r::ColorRGBA DEFAULT_PLANE_COLOR = { 0.9f, 0.9f, 0.9f, 0.9f }; +static const Slic3r::ColorRGBA DEFAULT_HOVER_PLANE_COLOR = { 0.9f, 0.2f, 0.2f, 1.f }; GLGizmoMeasure::GLGizmoMeasure(GLCanvas3D& parent, const std::string& icon_filename, unsigned int sprite_id) : GLGizmoBase(parent, icon_filename, sprite_id) {} - - bool GLGizmoMeasure::on_mouse(const wxMouseEvent &mouse_event) { if (mouse_event.Moving()) { @@ -139,9 +137,29 @@ void GLGizmoMeasure::on_render() shader->set_uniform("projection_matrix", camera.get_projection_matrix()); if (this->is_plane_update_necessary()) update_planes(); - for (int i = 0; i < (int)m_planes.size(); ++i) { - m_planes[i].vbo.set_color(i == m_hover_id ? DEFAULT_HOVER_PLANE_COLOR : DEFAULT_PLANE_COLOR); - m_planes[i].vbo.render(); + + m_imgui->begin(std::string("DEBUG")); + if (m_imgui->button("<-")) + --m_currently_shown_plane; + ImGui::SameLine(); + if (m_imgui->button("->")) + ++m_currently_shown_plane; + m_currently_shown_plane = std::clamp(m_currently_shown_plane, 0, int(m_planes.size())-1); + m_imgui->text(std::to_string(m_currently_shown_plane)); + m_imgui->end(); + + + //for (int i = 0; i < (int)m_planes.size(); ++i) { + int i = m_currently_shown_plane; + std::cout << m_hover_id << "\t" << m_currently_shown_plane << "\t" << std::endl; + if (i < m_planes.size()) { + for (int j=0; j<(int)m_planes[i].vbos.size(); ++j) { + m_planes[i].vbos[j].set_color(j == m_hover_id ? DEFAULT_HOVER_PLANE_COLOR : DEFAULT_PLANE_COLOR); + if (j == m_hover_id) + m_planes[i].vbos[j].render(); + std::cout << " * " << j; + } + std::cout <get_instance_transformation().get_matrix(); @@ -189,9 +208,13 @@ void GLGizmoMeasure::on_render_for_picking() shader->set_uniform("projection_matrix", camera.get_projection_matrix()); if (this->is_plane_update_necessary()) update_planes(); - for (int i = 0; i < (int)m_planes.size(); ++i) { - m_planes[i].vbo.set_color(picking_color_component(i)); - m_planes[i].vbo.render(); + //for (int i = 0; i < (int)m_planes.size(); ++i) { + int i = m_currently_shown_plane; + if (i < m_planes.size()) { + for (int j=0; j<(int)m_planes[i].vbos.size(); ++j) { + m_planes[i].vbos[j].set_color(picking_color_component(j)); + m_planes[i].vbos[j].render(); + } } } @@ -277,7 +300,7 @@ void GLGizmoMeasure::update_planes() for (int plane_id=0; plane_id < m_planes.size(); ++plane_id) { //int plane_id = 5; { const auto& facets = m_planes[plane_id].facets; - std::vector pts; + std::vector pts; for (int face_id=0; face_id{ sm.point(sm.source(he)).cast() }); + pts.emplace_back(sm.point(sm.source(he)).cast()); Vertex_index target = sm.target(he); const Halfedge_index he_start = he; @@ -303,14 +326,20 @@ void GLGizmoMeasure::update_planes() while ( face_to_plane[sm.face(he)] == plane_id && he != he_orig) he = sm.next_around_target(he); he = sm.opposite(he); - m_planes[plane_id].borders.back().emplace_back(sm.point(sm.source(he)).cast()); + pts.emplace_back(sm.point(sm.source(he)).cast()); } while (he != he_start); + + if (pts.size() != 1) { + m_planes[plane_id].borders.emplace_back(pts); + pts.clear(); + } + } } // DEBUGGING: - //m_planes.erase(std::remove_if(m_planes.begin(), m_planes.end(), [](const PlaneData& p) { return p.borders.empty(); }), m_planes.end()); + m_planes.erase(std::remove_if(m_planes.begin(), m_planes.end(), [](const PlaneData& p) { return p.borders.empty(); }), m_planes.end()); @@ -337,7 +366,7 @@ void GLGizmoMeasure::update_planes() // And finally create respective VBOs. The polygon is convex with // the vertices in order, so triangulation is trivial. for (auto& plane : m_planes) { - for (auto& vertices : plane.borders) { + for (const auto& vertices : plane.borders) { GLModel::Geometry init_data; init_data.format = { GLModel::Geometry::EPrimitiveType::LineStrip, GLModel::Geometry::EVertexLayout::P3N3 }; init_data.reserve_vertices(vertices.size()); @@ -347,7 +376,8 @@ void GLGizmoMeasure::update_planes() init_data.add_vertex((Vec3f)vertices[i].cast(), (Vec3f)plane.normal.cast()); init_data.add_index((unsigned int)i); } - plane.vbo.init_from(std::move(init_data)); + plane.vbos.emplace_back(); + plane.vbos.back().init_from(std::move(init_data)); } // FIXME: vertices should really be local, they need not diff --git a/src/slic3r/GUI/Gizmos/GLGizmoMeasure.hpp b/src/slic3r/GUI/Gizmos/GLGizmoMeasure.hpp index 87ad73d8c6..32b43e6f84 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoMeasure.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoMeasure.hpp @@ -23,10 +23,12 @@ class GLGizmoMeasure : public GLGizmoBase private: + int m_currently_shown_plane = 0; + struct PlaneData { std::vector> borders; // should be in fact local in update_planes() std::vector facets; - GLModel vbo; + std::vector vbos; Vec3d normal; float area; }; From 9aa706c0a743d68847b3237c4027abafe0b7adee Mon Sep 17 00:00:00 2001 From: Lukas Matena Date: Fri, 1 Jul 2022 15:51:51 +0200 Subject: [PATCH 07/13] Measuring: First steps on extracting features --- src/libslic3r/SurfaceMesh.hpp | 1 + src/slic3r/GUI/Gizmos/GLGizmoMeasure.cpp | 204 ++++++++++++++++++----- src/slic3r/GUI/Gizmos/GLGizmoMeasure.hpp | 20 ++- 3 files changed, 183 insertions(+), 42 deletions(-) diff --git a/src/libslic3r/SurfaceMesh.hpp b/src/libslic3r/SurfaceMesh.hpp index e8ada4cd80..a4b261ceb9 100644 --- a/src/libslic3r/SurfaceMesh.hpp +++ b/src/libslic3r/SurfaceMesh.hpp @@ -17,6 +17,7 @@ class Halfedge_index { public: Halfedge_index() : m_face(Face_index(-1)), m_side(0) {} Face_index face() const { return m_face; } + unsigned char side() const { return m_side; } bool is_invalid() const { return int(m_face) < 0; } bool operator!=(const Halfedge_index& rhs) const { return ! ((*this) == rhs); } bool operator==(const Halfedge_index& rhs) const { return m_face == rhs.m_face && m_side == rhs.m_side; } diff --git a/src/slic3r/GUI/Gizmos/GLGizmoMeasure.cpp b/src/slic3r/GUI/Gizmos/GLGizmoMeasure.cpp index de3fc398b9..6c95e70b7f 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoMeasure.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoMeasure.cpp @@ -22,7 +22,10 @@ static const Slic3r::ColorRGBA DEFAULT_HOVER_PLANE_COLOR = { 0.9f, 0.2f, 0.2f, 1 GLGizmoMeasure::GLGizmoMeasure(GLCanvas3D& parent, const std::string& icon_filename, unsigned int sprite_id) : GLGizmoBase(parent, icon_filename, sprite_id) -{} +{ + m_vbo_sphere.init_from(its_make_sphere(1., M_PI/32.)); + m_vbo_cylinder.init_from(its_make_cylinder(1., 1.)); +} bool GLGizmoMeasure::on_mouse(const wxMouseEvent &mouse_event) { @@ -125,7 +128,7 @@ void GLGizmoMeasure::on_render() glsafe(::glEnable(GL_DEPTH_TEST)); glsafe(::glEnable(GL_BLEND)); - glsafe(::glLineWidth(5.f)); + glsafe(::glLineWidth(2.f)); if (selection.is_single_full_instance()) { const Transform3d& m = selection.get_volume(*selection.get_volume_idxs().begin())->get_instance_transformation().get_matrix(); @@ -144,22 +147,47 @@ void GLGizmoMeasure::on_render() ImGui::SameLine(); if (m_imgui->button("->")) ++m_currently_shown_plane; - m_currently_shown_plane = std::clamp(m_currently_shown_plane, 0, int(m_planes.size())-1); - m_imgui->text(std::to_string(m_currently_shown_plane)); + m_currently_shown_plane = std::clamp(m_currently_shown_plane, 0, std::max(0, int(m_planes.size())-1)); + m_imgui->text(std::to_string(m_currently_shown_plane)); + m_imgui->checkbox(wxString("Show all"), m_show_all); m_imgui->end(); - //for (int i = 0; i < (int)m_planes.size(); ++i) { - int i = m_currently_shown_plane; - std::cout << m_hover_id << "\t" << m_currently_shown_plane << "\t" << std::endl; - if (i < m_planes.size()) { + int i = m_show_all ? 0 : m_currently_shown_plane; + for (int i = 0; i < (int)m_planes.size(); ++i) { + // Render all the borders. for (int j=0; j<(int)m_planes[i].vbos.size(); ++j) { m_planes[i].vbos[j].set_color(j == m_hover_id ? DEFAULT_HOVER_PLANE_COLOR : DEFAULT_PLANE_COLOR); - if (j == m_hover_id) m_planes[i].vbos[j].render(); - std::cout << " * " << j; } - std::cout <::FromTwoVectors(Vec3d::UnitZ(), feature.endpoint - feature.pos); + view_feature_matrix *= q; + view_feature_matrix.scale(Vec3d(0.3, 0.3, (feature.endpoint - feature.pos).norm())); + shader->set_uniform("view_model_matrix", view_feature_matrix); + m_vbo_cylinder.set_color(ColorRGBA(0.7f, 0.7f, 0.f, 1.f)); + m_vbo_cylinder.render(); + } + + view_feature_matrix = view_model_matrix * Transform3d(Eigen::Translation3d(feature.pos)); + view_feature_matrix.scale(0.5); + shader->set_uniform("view_model_matrix", view_feature_matrix); + m_vbo_sphere.set_color(feature.type == SurfaceFeature::Line + ? ColorRGBA(1.f, 0.f, 0.f, 1.f) + : ColorRGBA(0.f, 1.f, 0.f, 1.f)); + m_vbo_sphere.render(); + + + shader->set_uniform("view_model_matrix", view_model_matrix); + } + + if (! m_show_all) + break; } } @@ -196,7 +224,7 @@ void GLGizmoMeasure::on_render_for_picking() glsafe(::glDisable(GL_DEPTH_TEST)); glsafe(::glDisable(GL_BLEND)); - glsafe(::glLineWidth(5.f)); + glsafe(::glLineWidth(2.f)); if (selection.is_single_full_instance() && !wxGetKeyState(WXK_CONTROL)) { const Transform3d& m = selection.get_volume(*selection.get_volume_idxs().begin())->get_instance_transformation().get_matrix(); @@ -235,6 +263,73 @@ void GLGizmoMeasure::set_flattening_data(const ModelObject* model_object) +void GLGizmoMeasure::extract_features(GLGizmoMeasure::PlaneData& plane) +{ + plane.surface_features.clear(); + const Vec3d& normal = plane.normal; + + const double edge_threshold = 10. * (M_PI/180.); + + + + for (const std::vector& border : plane.borders) { + assert(border.size() > 1); + assert(! border.front().isApprox(border.back())); + double last_angle = 0.; + size_t first_idx = 0; + + for (size_t i=0; i edge_threshold || i == border.size() - 1) { + // Current feature ended. Save it and remember current point as beginning of the next. + bool is_line = (i == first_idx + 1); + plane.surface_features.emplace_back(SurfaceFeature{ + is_line ? SurfaceFeature::Line : SurfaceFeature::Circle, + is_line ? border[first_idx] : border[first_idx], // FIXME + border[i], + 0. // FIXME + }); + first_idx = i; + } else if (Slic3r::is_approx(angle, last_angle)) { + // possibly a segment of a circle + } else { + first_idx = i; + + } + } + last_angle = angle; + } + + // FIXME: Possibly merge the first and last feature. + + + + std::cout << "==================== " << std::endl; + } + + + for (const SurfaceFeature& f : plane.surface_features) { + std::cout << "- detected " << (f.type == SurfaceFeature::Line ? "Line" : "Circle") << std::endl; + std::cout<< f.pos << std::endl << std::endl << f.endpoint << std::endl; + std::cout << "----------------" << std::endl; + } + + + +} + + + void GLGizmoMeasure::update_planes() { const ModelObject* mo = m_c->selection_info()->model_object(); @@ -292,6 +387,7 @@ void GLGizmoMeasure::update_planes() } m_planes.back().normal = normal_ptr->cast(); + std::sort(m_planes.back().facets.begin(), m_planes.back().facets.end()); } assert(std::none_of(face_to_plane.begin(), face_to_plane.end(), [](size_t val) { return val == size_t(-1); })); @@ -300,40 +396,57 @@ void GLGizmoMeasure::update_planes() for (int plane_id=0; plane_id < m_planes.size(); ++plane_id) { //int plane_id = 5; { const auto& facets = m_planes[plane_id].facets; - std::vector pts; + m_planes[plane_id].borders.clear(); + std::vector> visited(facets.size(), {false, false, false}); + for (int face_id=0; face_id()); - Vertex_index target = sm.target(he); - const Halfedge_index he_start = he; - - do { + // he is the first halfedge on the border. Now walk around and append the points. const Halfedge_index he_orig = he; - he = sm.next_around_target(he); - while ( face_to_plane[sm.face(he)] == plane_id && he != he_orig) - he = sm.next_around_target(he); - he = sm.opposite(he); - pts.emplace_back(sm.point(sm.source(he)).cast()); - } while (he != he_start); + m_planes[plane_id].borders.emplace_back(); + m_planes[plane_id].borders.back().emplace_back(sm.point(sm.source(he)).cast()); + Vertex_index target = sm.target(he); + const Halfedge_index he_start = he; + + Face_index fi = he.face(); + auto face_it = std::lower_bound(facets.begin(), facets.end(), int(fi)); + assert(face_it != facets.end()); + assert(*face_it == int(fi)); + visited[face_it - facets.begin()][he.side()] = true; - if (pts.size() != 1) { - m_planes[plane_id].borders.emplace_back(pts); - pts.clear(); + do { + const Halfedge_index he_orig = he; + he = sm.next_around_target(he); + while ( face_to_plane[sm.face(he)] == plane_id && he != he_orig) + he = sm.next_around_target(he); + he = sm.opposite(he); + + Face_index fi = he.face(); + auto face_it = std::lower_bound(facets.begin(), facets.end(), int(fi)); + assert(face_it != facets.end()); + assert(*face_it == int(fi)); + if (visited[face_it - facets.begin()][he.side()] && he != he_start) { + m_planes[plane_id].borders.back().resize(1); + break; + } + visited[face_it - facets.begin()][he.side()] = true; + + m_planes[plane_id].borders.back().emplace_back(sm.point(sm.source(he)).cast()); + } while (he != he_start); + + if (m_planes[plane_id].borders.back().size() == 1) + m_planes[plane_id].borders.pop_back(); } - } } @@ -365,8 +478,8 @@ void GLGizmoMeasure::update_planes() // And finally create respective VBOs. The polygon is convex with // the vertices in order, so triangulation is trivial. - for (auto& plane : m_planes) { - for (const auto& vertices : plane.borders) { + for (PlaneData& plane : m_planes) { + for (std::vector& vertices : plane.borders) { GLModel::Geometry init_data; init_data.format = { GLModel::Geometry::EPrimitiveType::LineStrip, GLModel::Geometry::EVertexLayout::P3N3 }; init_data.reserve_vertices(vertices.size()); @@ -378,8 +491,17 @@ void GLGizmoMeasure::update_planes() } plane.vbos.emplace_back(); plane.vbos.back().init_from(std::move(init_data)); + vertices.pop_back(); // first and last are the same } + static int n=0; + std::cout << "==================== " << std::endl; + std::cout << "==================== " << std::endl; + std::cout << "==================== " << std::endl; + std::cout << "Plane num. " << n++ << std::endl; + extract_features(plane); + + // FIXME: vertices should really be local, they need not // persist now when we use VBOs plane.borders.clear(); diff --git a/src/slic3r/GUI/Gizmos/GLGizmoMeasure.hpp b/src/slic3r/GUI/Gizmos/GLGizmoMeasure.hpp index 32b43e6f84..3099366736 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoMeasure.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoMeasure.hpp @@ -24,15 +24,33 @@ class GLGizmoMeasure : public GLGizmoBase private: int m_currently_shown_plane = 0; + bool m_show_all = false; + + GLModel m_vbo_sphere; + GLModel m_vbo_cylinder; + + struct SurfaceFeature { + enum Type { + Circle, + Line + }; + Type type; + Vec3d pos; + Vec3d endpoint; // for type == Line + double radius; // for type == Circle; + }; struct PlaneData { - std::vector> borders; // should be in fact local in update_planes() std::vector facets; + std::vector> borders; // should be in fact local in update_planes() + std::vector surface_features; std::vector vbos; Vec3d normal; float area; }; + static void extract_features(PlaneData& plane); + // This holds information to decide whether recalculation is necessary: std::vector m_volumes_matrices; std::vector m_volumes_types; From 9644fd4c595a382f3bd0dd1b1eb49092d123b704 Mon Sep 17 00:00:00 2001 From: Lukas Matena Date: Thu, 7 Jul 2022 12:30:26 +0200 Subject: [PATCH 08/13] Measuring: Improved visualization --- src/slic3r/GUI/Gizmos/GLGizmoMeasure.cpp | 50 ++++++++++++++---------- src/slic3r/GUI/Gizmos/GLGizmoMeasure.hpp | 5 ++- 2 files changed, 34 insertions(+), 21 deletions(-) diff --git a/src/slic3r/GUI/Gizmos/GLGizmoMeasure.cpp b/src/slic3r/GUI/Gizmos/GLGizmoMeasure.cpp index 6c95e70b7f..0cb1067c39 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoMeasure.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoMeasure.cpp @@ -149,12 +149,15 @@ void GLGizmoMeasure::on_render() ++m_currently_shown_plane; m_currently_shown_plane = std::clamp(m_currently_shown_plane, 0, std::max(0, int(m_planes.size())-1)); m_imgui->text(std::to_string(m_currently_shown_plane)); - m_imgui->checkbox(wxString("Show all"), m_show_all); + m_imgui->checkbox(wxString("Show all"), m_show_all_planes); + m_imgui->checkbox(wxString("Show points"), m_show_points); + m_imgui->checkbox(wxString("Show edges"), m_show_edges); + m_imgui->checkbox(wxString("Show circles"), m_show_circles); m_imgui->end(); - int i = m_show_all ? 0 : m_currently_shown_plane; - for (int i = 0; i < (int)m_planes.size(); ++i) { + int i = m_show_all_planes ? 0 : m_currently_shown_plane; + for (; i < (int)m_planes.size(); ++i) { // Render all the borders. for (int j=0; j<(int)m_planes[i].vbos.size(); ++j) { m_planes[i].vbos[j].set_color(j == m_hover_id ? DEFAULT_HOVER_PLANE_COLOR : DEFAULT_PLANE_COLOR); @@ -165,7 +168,7 @@ void GLGizmoMeasure::on_render() // Render features: for (const SurfaceFeature& feature : m_planes[i].surface_features) { Transform3d view_feature_matrix = view_model_matrix * Transform3d(Eigen::Translation3d(feature.pos)); - if (feature.type == SurfaceFeature::Line) { + if (m_show_edges && feature.type == SurfaceFeature::Line) { auto q = Eigen::Quaternion::FromTwoVectors(Vec3d::UnitZ(), feature.endpoint - feature.pos); view_feature_matrix *= q; view_feature_matrix.scale(Vec3d(0.3, 0.3, (feature.endpoint - feature.pos).norm())); @@ -174,19 +177,21 @@ void GLGizmoMeasure::on_render() m_vbo_cylinder.render(); } - view_feature_matrix = view_model_matrix * Transform3d(Eigen::Translation3d(feature.pos)); - view_feature_matrix.scale(0.5); - shader->set_uniform("view_model_matrix", view_feature_matrix); - m_vbo_sphere.set_color(feature.type == SurfaceFeature::Line - ? ColorRGBA(1.f, 0.f, 0.f, 1.f) - : ColorRGBA(0.f, 1.f, 0.f, 1.f)); - m_vbo_sphere.render(); + if (m_show_points && feature.type == SurfaceFeature::Line || m_show_circles && feature.type == SurfaceFeature::Circle) { + view_feature_matrix = view_model_matrix * Transform3d(Eigen::Translation3d(feature.pos)); + view_feature_matrix.scale(0.5); + shader->set_uniform("view_model_matrix", view_feature_matrix); + m_vbo_sphere.set_color(feature.type == SurfaceFeature::Line + ? ColorRGBA(1.f, 0.f, 0.f, 1.f) + : ColorRGBA(0.f, 1.f, 0.f, 1.f)); + m_vbo_sphere.render(); + } shader->set_uniform("view_model_matrix", view_model_matrix); } - if (! m_show_all) + if (! m_show_all_planes) break; } } @@ -268,7 +273,7 @@ void GLGizmoMeasure::extract_features(GLGizmoMeasure::PlaneData& plane) plane.surface_features.clear(); const Vec3d& normal = plane.normal; - const double edge_threshold = 10. * (M_PI/180.); + const double edge_threshold = 25. * (M_PI/180.); @@ -276,9 +281,10 @@ void GLGizmoMeasure::extract_features(GLGizmoMeasure::PlaneData& plane) assert(border.size() > 1); assert(! border.front().isApprox(border.back())); double last_angle = 0.; - size_t first_idx = 0; + int first_idx = 0; + bool circle = false; - for (size_t i=0; i edge_threshold || i == border.size() - 1) { + bool same_as_last = Slic3r::is_approx(angle, last_angle); + + if (std::abs(angle) > edge_threshold || (! same_as_last && circle) || i == border.size() - 1) { // Current feature ended. Save it and remember current point as beginning of the next. bool is_line = (i == first_idx + 1); plane.surface_features.emplace_back(SurfaceFeature{ @@ -300,11 +308,13 @@ void GLGizmoMeasure::extract_features(GLGizmoMeasure::PlaneData& plane) 0. // FIXME }); first_idx = i; - } else if (Slic3r::is_approx(angle, last_angle)) { + circle = false; + } else if (same_as_last && ! circle) { // possibly a segment of a circle - } else { + first_idx = std::max(i-2, 0); + circle = true; + } else if (! circle) { first_idx = i; - } } last_angle = angle; diff --git a/src/slic3r/GUI/Gizmos/GLGizmoMeasure.hpp b/src/slic3r/GUI/Gizmos/GLGizmoMeasure.hpp index 3099366736..9534796a71 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoMeasure.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoMeasure.hpp @@ -24,7 +24,10 @@ class GLGizmoMeasure : public GLGizmoBase private: int m_currently_shown_plane = 0; - bool m_show_all = false; + bool m_show_all_planes = false; + bool m_show_points = true; + bool m_show_edges = true; + bool m_show_circles = true; GLModel m_vbo_sphere; GLModel m_vbo_cylinder; From 00eb8661c0bfdc057dad9f3fd9472bb6069c1ebe Mon Sep 17 00:00:00 2001 From: Lukas Matena Date: Wed, 13 Jul 2022 16:37:16 +0200 Subject: [PATCH 09/13] Measuring: Improved feature detection, added circle center calculation --- src/slic3r/GUI/Gizmos/GLGizmoMeasure.cpp | 120 ++++++++++++++--------- 1 file changed, 75 insertions(+), 45 deletions(-) diff --git a/src/slic3r/GUI/Gizmos/GLGizmoMeasure.cpp b/src/slic3r/GUI/Gizmos/GLGizmoMeasure.cpp index 0cb1067c39..e00d587814 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoMeasure.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoMeasure.cpp @@ -9,6 +9,7 @@ #include "libslic3r/Geometry/ConvexHull.hpp" #include "libslic3r/Model.hpp" #include "libslic3r/SurfaceMesh.hpp" +#include "libslic3r/Geometry/Circle.hpp" #include @@ -177,7 +178,7 @@ void GLGizmoMeasure::on_render() m_vbo_cylinder.render(); } - if (m_show_points && feature.type == SurfaceFeature::Line || m_show_circles && feature.type == SurfaceFeature::Circle) { + if ((m_show_points && feature.type == SurfaceFeature::Line) || m_show_circles && feature.type == SurfaceFeature::Circle) { view_feature_matrix = view_model_matrix * Transform3d(Eigen::Translation3d(feature.pos)); view_feature_matrix.scale(0.5); shader->set_uniform("view_model_matrix", view_feature_matrix); @@ -185,6 +186,14 @@ void GLGizmoMeasure::on_render() ? ColorRGBA(1.f, 0.f, 0.f, 1.f) : ColorRGBA(0.f, 1.f, 0.f, 1.f)); m_vbo_sphere.render(); + + /*view_feature_matrix = view_model_matrix * Transform3d(Eigen::Translation3d(feature.endpoint)); + view_feature_matrix.scale(0.5); + shader->set_uniform("view_model_matrix", view_feature_matrix); + m_vbo_sphere.set_color(feature.type == SurfaceFeature::Line + ? ColorRGBA(1.f, 0.f, 0.f, 1.f) + : ColorRGBA(1.f, 1.f, 0.f, 1.f)); + m_vbo_sphere.render();*/ } @@ -243,7 +252,7 @@ void GLGizmoMeasure::on_render_for_picking() update_planes(); //for (int i = 0; i < (int)m_planes.size(); ++i) { int i = m_currently_shown_plane; - if (i < m_planes.size()) { + if (i < int(m_planes.size())) { for (int j=0; j<(int)m_planes[i].vbos.size(); ++j) { m_planes[i].vbos[j].set_color(picking_color_component(j)); m_planes[i].vbos[j].render(); @@ -268,60 +277,84 @@ void GLGizmoMeasure::set_flattening_data(const ModelObject* model_object) +static std::pair get_center_and_radius(const std::vector& border, int start_idx, int end_idx, const Transform3d& trafo) +{ + Vec2ds pts; + double z = 0.; + for (int i=start_idx; i<=end_idx; ++i) { + Vec3d pt_transformed = trafo * border[i]; + z = pt_transformed.z(); + pts.emplace_back(pt_transformed.x(), pt_transformed.y()); + } + + auto circle = Geometry::circle_ransac(pts, 20); // FIXME: iterations? + + return std::make_pair(trafo.inverse() * Vec3d(circle.center.x(), circle.center.y(), z), circle.radius); +} + + + void GLGizmoMeasure::extract_features(GLGizmoMeasure::PlaneData& plane) { plane.surface_features.clear(); const Vec3d& normal = plane.normal; const double edge_threshold = 25. * (M_PI/180.); + std::vector angles; + + Eigen::Quaterniond q; + q.setFromTwoVectors(plane.normal, Vec3d::UnitZ()); + Transform3d trafo = Transform3d::Identity(); + trafo.rotate(q); for (const std::vector& border : plane.borders) { assert(border.size() > 1); assert(! border.front().isApprox(border.back())); - double last_angle = 0.; - int first_idx = 0; - bool circle = false; + int start_idx = -1; + + // First calculate angles at all the vertices. + angles.clear(); for (int i=0; i edge_threshold || (! same_as_last && circle) || i == border.size() - 1) { - // Current feature ended. Save it and remember current point as beginning of the next. - bool is_line = (i == first_idx + 1); - plane.surface_features.emplace_back(SurfaceFeature{ - is_line ? SurfaceFeature::Line : SurfaceFeature::Circle, - is_line ? border[first_idx] : border[first_idx], // FIXME - border[i], - 0. // FIXME - }); - first_idx = i; - circle = false; - } else if (same_as_last && ! circle) { - // possibly a segment of a circle - first_idx = std::max(i-2, 0); + + bool circle = false; + std::vector> circles; + for (int i=1; i center_and_radius = get_center_and_radius(border, start_idx, end_idx, trafo); + plane.surface_features.emplace_back(SurfaceFeature{ + SurfaceFeature::Circle, + // border[start_idx], border[end_idx], + center_and_radius.first, center_and_radius.first, center_and_radius.second + }); + } std::cout << "==================== " << std::endl; @@ -352,7 +385,7 @@ void GLGizmoMeasure::update_planes() ch.merge(vol_ch); } m_planes.clear(); - const Transform3d& inst_matrix = mo->instances.front()->get_matrix(); + // Now we'll go through all the facets and append Points of facets sharing the same normal. // This part is still performed in mesh coordinate system. @@ -403,13 +436,13 @@ void GLGizmoMeasure::update_planes() assert(std::none_of(face_to_plane.begin(), face_to_plane.end(), [](size_t val) { return val == size_t(-1); })); SurfaceMesh sm(ch.its); - for (int plane_id=0; plane_id < m_planes.size(); ++plane_id) { + for (int plane_id=0; plane_id < int(m_planes.size()); ++plane_id) { //int plane_id = 5; { const auto& facets = m_planes[plane_id].facets; m_planes[plane_id].borders.clear(); std::vector> visited(facets.size(), {false, false, false}); - for (int face_id=0; face_id()); - Vertex_index target = sm.target(he); + std::vector& last_border = m_planes[plane_id].borders.back(); + last_border.emplace_back(sm.point(sm.source(he)).cast()); + //Vertex_index target = sm.target(he); const Halfedge_index he_start = he; Face_index fi = he.face(); @@ -446,15 +480,15 @@ void GLGizmoMeasure::update_planes() assert(face_it != facets.end()); assert(*face_it == int(fi)); if (visited[face_it - facets.begin()][he.side()] && he != he_start) { - m_planes[plane_id].borders.back().resize(1); + last_border.resize(1); break; } visited[face_it - facets.begin()][he.side()] = true; - m_planes[plane_id].borders.back().emplace_back(sm.point(sm.source(he)).cast()); + last_border.emplace_back(sm.point(sm.source(he)).cast()); } while (he != he_start); - if (m_planes[plane_id].borders.back().size() == 1) + if (last_border.size() == 1) m_planes[plane_id].borders.pop_back(); } } @@ -468,11 +502,7 @@ void GLGizmoMeasure::update_planes() - // Let's prepare transformation of the normal vector from mesh to instance coordinates. - Geometry::Transformation t(inst_matrix); - Vec3d scaling = t.get_scaling_factor(); - t.set_scaling_factor(Vec3d(1./scaling(0), 1./scaling(1), 1./scaling(2))); - + // Planes are finished - let's save what we calculated it from: From fe9540130aaad4d0b5a76e6ce209fd1e49d7d4af Mon Sep 17 00:00:00 2001 From: Lukas Matena Date: Tue, 26 Jul 2022 10:12:59 +0200 Subject: [PATCH 10/13] Measuring: Separating frontend and backend --- src/libslic3r/CMakeLists.txt | 2 + src/libslic3r/Measure.cpp | 323 ++++++++++++++++ src/libslic3r/Measure.hpp | 98 +++++ src/slic3r/GUI/Gizmos/GLGizmoMeasure.cpp | 448 ++++------------------- src/slic3r/GUI/Gizmos/GLGizmoMeasure.hpp | 43 +-- 5 files changed, 499 insertions(+), 415 deletions(-) create mode 100644 src/libslic3r/Measure.cpp create mode 100644 src/libslic3r/Measure.hpp diff --git a/src/libslic3r/CMakeLists.txt b/src/libslic3r/CMakeLists.txt index 396494a181..1c060243da 100644 --- a/src/libslic3r/CMakeLists.txt +++ b/src/libslic3r/CMakeLists.txt @@ -178,6 +178,8 @@ set(SLIC3R_SOURCES MultiMaterialSegmentation.hpp MeshNormals.hpp MeshNormals.cpp + Measure.hpp + Measure.cpp CustomGCode.cpp CustomGCode.hpp Arrange.hpp diff --git a/src/libslic3r/Measure.cpp b/src/libslic3r/Measure.cpp new file mode 100644 index 0000000000..d5cb9c24b3 --- /dev/null +++ b/src/libslic3r/Measure.cpp @@ -0,0 +1,323 @@ +#include "Measure.hpp" + +#include "libslic3r/Geometry/Circle.hpp" +#include "libslic3r/SurfaceMesh.hpp" + + + +namespace Slic3r { +namespace Measure { + + + +static std::pair get_center_and_radius(const std::vector& border, int start_idx, int end_idx, const Transform3d& trafo) +{ + Vec2ds pts; + double z = 0.; + for (int i=start_idx; i<=end_idx; ++i) { + Vec3d pt_transformed = trafo * border[i]; + z = pt_transformed.z(); + pts.emplace_back(pt_transformed.x(), pt_transformed.y()); + } + + auto circle = Geometry::circle_ransac(pts, 20); // FIXME: iterations? + + return std::make_pair(trafo.inverse() * Vec3d(circle.center.x(), circle.center.y(), z), circle.radius); +} + + + + +class MeasuringImpl { +public: + explicit MeasuringImpl(const indexed_triangle_set& its); + struct PlaneData { + std::vector facets; + std::vector> borders; // FIXME: should be in fact local in update_planes() + std::vector> surface_features; + Vec3d normal; + float area; + }; + + const std::vector& get_features() const; + +private: + void update_planes(); + void extract_features(PlaneData& plane); + void save_features(); + + + std::vector m_planes; + std::vector m_features; + const indexed_triangle_set& m_its; +}; + + + + + + +MeasuringImpl::MeasuringImpl(const indexed_triangle_set& its) +: m_its{its} +{ + update_planes(); + + for (PlaneData& plane : m_planes) { + extract_features(plane); + + plane.borders.clear(); + plane.borders.shrink_to_fit(); + } + + save_features(); +} + + +void MeasuringImpl::update_planes() +{ + m_planes.clear(); + + // Now we'll go through all the facets and append Points of facets sharing the same normal. + // This part is still performed in mesh coordinate system. + const size_t num_of_facets = m_its.indices.size(); + std::vector face_to_plane(num_of_facets, size_t(-1)); + const std::vector face_normals = its_face_normals(m_its); + const std::vector face_neighbors = its_face_neighbors(m_its); + std::vector facet_queue(num_of_facets, 0); + int facet_queue_cnt = 0; + const stl_normal* normal_ptr = nullptr; + size_t seed_facet_idx = 0; + + auto is_same_normal = [](const stl_normal& a, const stl_normal& b) -> bool { + return (std::abs(a(0) - b(0)) < 0.001 && std::abs(a(1) - b(1)) < 0.001 && std::abs(a(2) - b(2)) < 0.001); + }; + + while (1) { + // Find next unvisited triangle: + for (; seed_facet_idx < num_of_facets; ++ seed_facet_idx) + if (face_to_plane[seed_facet_idx] == size_t(-1)) { + facet_queue[facet_queue_cnt ++] = seed_facet_idx; + normal_ptr = &face_normals[seed_facet_idx]; + face_to_plane[seed_facet_idx] = m_planes.size(); + m_planes.emplace_back(); + break; + } + if (seed_facet_idx == num_of_facets) + break; // Everything was visited already + + while (facet_queue_cnt > 0) { + int facet_idx = facet_queue[-- facet_queue_cnt]; + const stl_normal& this_normal = face_normals[facet_idx]; + if (is_same_normal(this_normal, *normal_ptr)) { + const Vec3i& face = m_its.indices[facet_idx]; + + face_to_plane[facet_idx] = m_planes.size() - 1; + m_planes.back().facets.emplace_back(facet_idx); + for (int j = 0; j < 3; ++ j) + if (int neighbor_idx = face_neighbors[facet_idx][j]; neighbor_idx >= 0 && face_to_plane[neighbor_idx] == size_t(-1)) + facet_queue[facet_queue_cnt ++] = neighbor_idx; + } + } + + m_planes.back().normal = normal_ptr->cast(); + std::sort(m_planes.back().facets.begin(), m_planes.back().facets.end()); + } + + assert(std::none_of(face_to_plane.begin(), face_to_plane.end(), [](size_t val) { return val == size_t(-1); })); + + SurfaceMesh sm(m_its); + for (int plane_id=0; plane_id < int(m_planes.size()); ++plane_id) { + //int plane_id = 5; { + const auto& facets = m_planes[plane_id].facets; + m_planes[plane_id].borders.clear(); + std::vector> visited(facets.size(), {false, false, false}); + + for (int face_id=0; face_id& last_border = m_planes[plane_id].borders.back(); + last_border.emplace_back(sm.point(sm.source(he)).cast()); + //Vertex_index target = sm.target(he); + const Halfedge_index he_start = he; + + Face_index fi = he.face(); + auto face_it = std::lower_bound(facets.begin(), facets.end(), int(fi)); + assert(face_it != facets.end()); + assert(*face_it == int(fi)); + visited[face_it - facets.begin()][he.side()] = true; + + do { + const Halfedge_index he_orig = he; + he = sm.next_around_target(he); + while ( face_to_plane[sm.face(he)] == plane_id && he != he_orig) + he = sm.next_around_target(he); + he = sm.opposite(he); + + Face_index fi = he.face(); + auto face_it = std::lower_bound(facets.begin(), facets.end(), int(fi)); + assert(face_it != facets.end()); + assert(*face_it == int(fi)); + if (visited[face_it - facets.begin()][he.side()] && he != he_start) { + last_border.resize(1); + break; + } + visited[face_it - facets.begin()][he.side()] = true; + + last_border.emplace_back(sm.point(sm.source(he)).cast()); + } while (he != he_start); + + if (last_border.size() == 1) + m_planes[plane_id].borders.pop_back(); + } + } + } + + m_planes.erase(std::remove_if(m_planes.begin(), m_planes.end(), + [](const PlaneData& p) { return p.borders.empty(); }), + m_planes.end()); +} + + + + + + +void MeasuringImpl::extract_features(PlaneData& plane) +{ + plane.surface_features.clear(); + const Vec3d& normal = plane.normal; + + const double edge_threshold = 25. * (M_PI/180.); + std::vector angles; + + Eigen::Quaterniond q; + q.setFromTwoVectors(plane.normal, Vec3d::UnitZ()); + Transform3d trafo = Transform3d::Identity(); + trafo.rotate(q); + + + + for (const std::vector& border : plane.borders) { + assert(border.size() > 1); + int start_idx = -1; + + + // First calculate angles at all the vertices. + angles.clear(); + for (int i=0; i> circles; + for (int i=1; i circles[cidx].first) + i = circles[cidx++].second; + else plane.surface_features.emplace_back(std::unique_ptr( + new Edge(border[i-1], border[i]))); + } + + // FIXME Throw away / do not create edges which are parts of circles. + + // FIXME Check and maybe merge first and last circle. + + for (const auto& [start_idx, end_idx] : circles) { + std::pair center_and_radius = get_center_and_radius(border, start_idx, end_idx, trafo); + plane.surface_features.emplace_back(std::unique_ptr( + new Circle(center_and_radius.first, center_and_radius.second) + )); + } + + } +} + + + +void MeasuringImpl::save_features() +{ + m_features.clear(); + for (PlaneData& plane : m_planes) + //PlaneData& plane = m_planes[0]; + { + for (std::unique_ptr& feature : plane.surface_features) { + m_features.emplace_back(feature.get()); + } + } +} + + + +const std::vector& MeasuringImpl::get_features() const +{ + return m_features; +} + + + + + + + + + + + + + +Measuring::Measuring(const indexed_triangle_set& its) +: priv{std::make_unique(its)} +{} + +Measuring::~Measuring() {} + + +const std::vector& Measuring::get_features() const +{ + return priv->get_features(); +} + + + + + + +} // namespace Measure +} // namespace Slic3r diff --git a/src/libslic3r/Measure.hpp b/src/libslic3r/Measure.hpp new file mode 100644 index 0000000000..bbc7d9e1e9 --- /dev/null +++ b/src/libslic3r/Measure.hpp @@ -0,0 +1,98 @@ +#ifndef Slic3r_Measure_hpp_ +#define Slic3r_Measure_hpp_ + +#include + +#include "Point.hpp" + + +struct indexed_triangle_set; + + + +namespace Slic3r { +namespace Measure { + + +enum class SurfaceFeatureType { + Edge = 1 << 0, + Circle = 1 << 1, + Plane = 1 << 2 + }; + +class SurfaceFeature { +public: + virtual SurfaceFeatureType get_type() const = 0; +}; + +class Edge : public SurfaceFeature { +public: + Edge(const Vec3d& start, const Vec3d& end) : m_start{start}, m_end{end} {} + SurfaceFeatureType get_type() const override { return SurfaceFeatureType::Edge; } + std::pair get_edge() const { return std::make_pair(m_start, m_end); } +private: + Vec3d m_start; + Vec3d m_end; +}; + +class Circle : public SurfaceFeature { +public: + Circle(const Vec3d& center, double radius) : m_center{center}, m_radius{radius} {} + SurfaceFeatureType get_type() const override { return SurfaceFeatureType::Circle; } + Vec3d get_center() const { return m_center; } + double get_radius() const { return m_radius; } +private: + Vec3d m_center; + double m_radius; +}; + +class Plane : public SurfaceFeature { +public: + SurfaceFeatureType get_type() const override { return SurfaceFeatureType::Plane; } + +}; + + +class MeasuringImpl; + + +class Measuring { +public: + // Construct the measurement object on a given its. The its must remain + // valid and unchanged during the whole lifetime of the object. + explicit Measuring(const indexed_triangle_set& its); + ~Measuring(); + + // Return a reference to a list of all features identified on the its. + const std::vector& get_features() const; + + // Given a face_idx where the mouse cursor points, return a feature that + // should be highlighted or nullptr. + const SurfaceFeature* get_feature(size_t face_idx, const Vec3d& point) const; + + // Returns distance between two SurfaceFeatures. + static double get_distance(const SurfaceFeature* a, const SurfaceFeature* b); + + // Returns true if an x/y/z distance between features makes sense. + // If so, result contains the distances. + static bool get_distances(const SurfaceFeature* a, const SurfaceFeature* b, std::array& result); + + // Returns true if an x/y/z distance between feature and a point makes sense. + // If so, result contains the distances. + static bool get_axis_aligned_distances(const SurfaceFeature* feature, const Vec3d* pt, std::array& result); + + // Returns true if measuring angles between features makes sense. + // If so, result contains the angle in radians. + static bool get_angle(const SurfaceFeature* a, const SurfaceFeature* b, double& result); + + +private: + + std::unique_ptr priv; +}; + + +} // namespace Measure +} // namespace Slic3r + +#endif // Slic3r_Measure_hpp_ diff --git a/src/slic3r/GUI/Gizmos/GLGizmoMeasure.cpp b/src/slic3r/GUI/Gizmos/GLGizmoMeasure.cpp index e00d587814..043adba976 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoMeasure.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoMeasure.cpp @@ -6,10 +6,8 @@ #include "slic3r/GUI/Gizmos/GLGizmosCommon.hpp" -#include "libslic3r/Geometry/ConvexHull.hpp" #include "libslic3r/Model.hpp" -#include "libslic3r/SurfaceMesh.hpp" -#include "libslic3r/Geometry/Circle.hpp" +#include "libslic3r/Measure.hpp" #include @@ -30,6 +28,10 @@ GLGizmoMeasure::GLGizmoMeasure(GLCanvas3D& parent, const std::string& icon_filen bool GLGizmoMeasure::on_mouse(const wxMouseEvent &mouse_event) { + m_mouse_pos_x = mouse_event.GetX(); + m_mouse_pos_y = mouse_event.GetY(); + + if (mouse_event.Moving()) { // only for sure m_mouse_left_down = false; @@ -38,12 +40,7 @@ bool GLGizmoMeasure::on_mouse(const wxMouseEvent &mouse_event) if (mouse_event.LeftDown()) { if (m_hover_id != -1) { m_mouse_left_down = true; - Selection &selection = m_parent.get_selection(); - if (selection.is_single_full_instance()) { - // Rotate the object so the normal points downward: - selection.flattening_rotate(m_planes[m_hover_id].normal); - m_parent.do_rotate(L("Gizmo-Place on Face")); - } + return true; } @@ -94,7 +91,7 @@ void GLGizmoMeasure::on_set_state() CommonGizmosDataID GLGizmoMeasure::on_get_requirements() const { - return CommonGizmosDataID::SelectionInfo; + return CommonGizmosDataID(int(CommonGizmosDataID::SelectionInfo) | int(CommonGizmosDataID::Raycaster)); } @@ -139,70 +136,59 @@ void GLGizmoMeasure::on_render() shader->set_uniform("view_model_matrix", view_model_matrix); shader->set_uniform("projection_matrix", camera.get_projection_matrix()); - if (this->is_plane_update_necessary()) - update_planes(); + + + update_if_needed(); + m_imgui->begin(std::string("DEBUG")); - if (m_imgui->button("<-")) - --m_currently_shown_plane; - ImGui::SameLine(); - if (m_imgui->button("->")) - ++m_currently_shown_plane; - m_currently_shown_plane = std::clamp(m_currently_shown_plane, 0, std::max(0, int(m_planes.size())-1)); - m_imgui->text(std::to_string(m_currently_shown_plane)); - m_imgui->checkbox(wxString("Show all"), m_show_all_planes); - m_imgui->checkbox(wxString("Show points"), m_show_points); - m_imgui->checkbox(wxString("Show edges"), m_show_edges); - m_imgui->checkbox(wxString("Show circles"), m_show_circles); - m_imgui->end(); + + m_imgui->checkbox(wxString("Show all features"), m_show_all); + + Vec3f pos; + Vec3f normal; + size_t facet_idx; + m_c->raycaster()->raycasters().front()->unproject_on_mesh(Vec2d(m_mouse_pos_x, m_mouse_pos_y), m, camera, pos, normal, nullptr, &facet_idx); + ImGui::Separator(); + m_imgui->text(std::string("face_idx: ") + std::to_string(facet_idx)); + m_imgui->text(std::string("pos_x: ") + std::to_string(pos.x())); + m_imgui->text(std::string("pos_y: ") + std::to_string(pos.y())); + m_imgui->text(std::string("pos_z: ") + std::to_string(pos.z())); - int i = m_show_all_planes ? 0 : m_currently_shown_plane; - for (; i < (int)m_planes.size(); ++i) { - // Render all the borders. - for (int j=0; j<(int)m_planes[i].vbos.size(); ++j) { - m_planes[i].vbos[j].set_color(j == m_hover_id ? DEFAULT_HOVER_PLANE_COLOR : DEFAULT_PLANE_COLOR); - m_planes[i].vbos[j].render(); - } + + if (m_show_all) { + const std::vector features = m_measuring->get_features(); + for (const Measure::SurfaceFeature* feature : features) { + + if (feature->get_type() == Measure::SurfaceFeatureType::Circle) { + const auto* circle = static_cast(feature); + Transform3d view_feature_matrix = view_model_matrix * Transform3d(Eigen::Translation3d(circle->get_center())); + view_feature_matrix = view_model_matrix * Transform3d(Eigen::Translation3d(circle->get_center())); + view_feature_matrix.scale(0.5); + shader->set_uniform("view_model_matrix", view_feature_matrix); + m_vbo_sphere.set_color(ColorRGBA(0.f, 1.f, 0.f, 1.f)); + m_vbo_sphere.render(); + } - // Render features: - for (const SurfaceFeature& feature : m_planes[i].surface_features) { - Transform3d view_feature_matrix = view_model_matrix * Transform3d(Eigen::Translation3d(feature.pos)); - if (m_show_edges && feature.type == SurfaceFeature::Line) { - auto q = Eigen::Quaternion::FromTwoVectors(Vec3d::UnitZ(), feature.endpoint - feature.pos); + else if (feature->get_type() == Measure::SurfaceFeatureType::Edge) { + const auto* edge = static_cast(feature); + auto& [start, end] = edge->get_edge(); + Transform3d view_feature_matrix = view_model_matrix * Transform3d(Eigen::Translation3d(start)); + auto q = Eigen::Quaternion::FromTwoVectors(Vec3d::UnitZ(), end - start); view_feature_matrix *= q; - view_feature_matrix.scale(Vec3d(0.3, 0.3, (feature.endpoint - feature.pos).norm())); + view_feature_matrix.scale(Vec3d(0.075, 0.075, (end - start).norm())); shader->set_uniform("view_model_matrix", view_feature_matrix); m_vbo_cylinder.set_color(ColorRGBA(0.7f, 0.7f, 0.f, 1.f)); m_vbo_cylinder.render(); } - if ((m_show_points && feature.type == SurfaceFeature::Line) || m_show_circles && feature.type == SurfaceFeature::Circle) { - view_feature_matrix = view_model_matrix * Transform3d(Eigen::Translation3d(feature.pos)); - view_feature_matrix.scale(0.5); - shader->set_uniform("view_model_matrix", view_feature_matrix); - m_vbo_sphere.set_color(feature.type == SurfaceFeature::Line - ? ColorRGBA(1.f, 0.f, 0.f, 1.f) - : ColorRGBA(0.f, 1.f, 0.f, 1.f)); - m_vbo_sphere.render(); - - /*view_feature_matrix = view_model_matrix * Transform3d(Eigen::Translation3d(feature.endpoint)); - view_feature_matrix.scale(0.5); - shader->set_uniform("view_model_matrix", view_feature_matrix); - m_vbo_sphere.set_color(feature.type == SurfaceFeature::Line - ? ColorRGBA(1.f, 0.f, 0.f, 1.f) - : ColorRGBA(1.f, 1.f, 0.f, 1.f)); - m_vbo_sphere.render();*/ - } - - shader->set_uniform("view_model_matrix", view_model_matrix); } - - if (! m_show_all_planes) - break; + shader->set_uniform("view_model_matrix", view_model_matrix); } + m_imgui->end(); } glsafe(::glEnable(GL_CULL_FACE)); @@ -222,290 +208,45 @@ void GLGizmoMeasure::on_render() #error NOT IMPLEMENTED #endif - - - - void GLGizmoMeasure::on_render_for_picking() { - const Selection& selection = m_parent.get_selection(); - - GLShaderProgram* shader = wxGetApp().get_shader("flat"); - if (shader == nullptr) - return; - - shader->start_using(); - - glsafe(::glDisable(GL_DEPTH_TEST)); - glsafe(::glDisable(GL_BLEND)); - glsafe(::glLineWidth(2.f)); - - if (selection.is_single_full_instance() && !wxGetKeyState(WXK_CONTROL)) { - const Transform3d& m = selection.get_volume(*selection.get_volume_idxs().begin())->get_instance_transformation().get_matrix(); - const Camera& camera = wxGetApp().plater()->get_camera(); - const Transform3d view_model_matrix = camera.get_view_matrix() * - Geometry::assemble_transform(selection.get_volume(*selection.get_volume_idxs().begin())->get_sla_shift_z() * Vec3d::UnitZ()) * m; - - shader->set_uniform("view_model_matrix", view_model_matrix); - shader->set_uniform("projection_matrix", camera.get_projection_matrix()); - if (this->is_plane_update_necessary()) - update_planes(); - //for (int i = 0; i < (int)m_planes.size(); ++i) { - int i = m_currently_shown_plane; - if (i < int(m_planes.size())) { - for (int j=0; j<(int)m_planes[i].vbos.size(); ++j) { - m_planes[i].vbos[j].set_color(picking_color_component(j)); - m_planes[i].vbos[j].render(); - } - } - } - - glsafe(::glEnable(GL_CULL_FACE)); - - shader->stop_using(); } void GLGizmoMeasure::set_flattening_data(const ModelObject* model_object) { - if (model_object != m_old_model_object) { - m_planes.clear(); - m_planes_valid = false; - } + if (model_object != m_old_model_object) + update_if_needed(); } - -static std::pair get_center_and_radius(const std::vector& border, int start_idx, int end_idx, const Transform3d& trafo) -{ - Vec2ds pts; - double z = 0.; - for (int i=start_idx; i<=end_idx; ++i) { - Vec3d pt_transformed = trafo * border[i]; - z = pt_transformed.z(); - pts.emplace_back(pt_transformed.x(), pt_transformed.y()); - } - - auto circle = Geometry::circle_ransac(pts, 20); // FIXME: iterations? - - return std::make_pair(trafo.inverse() * Vec3d(circle.center.x(), circle.center.y(), z), circle.radius); -} - - - -void GLGizmoMeasure::extract_features(GLGizmoMeasure::PlaneData& plane) -{ - plane.surface_features.clear(); - const Vec3d& normal = plane.normal; - - const double edge_threshold = 25. * (M_PI/180.); - std::vector angles; - - Eigen::Quaterniond q; - q.setFromTwoVectors(plane.normal, Vec3d::UnitZ()); - Transform3d trafo = Transform3d::Identity(); - trafo.rotate(q); - - - - for (const std::vector& border : plane.borders) { - assert(border.size() > 1); - assert(! border.front().isApprox(border.back())); - int start_idx = -1; - - - // First calculate angles at all the vertices. - angles.clear(); - for (int i=0; i> circles; - for (int i=1; i center_and_radius = get_center_and_radius(border, start_idx, end_idx, trafo); - plane.surface_features.emplace_back(SurfaceFeature{ - SurfaceFeature::Circle, - // border[start_idx], border[end_idx], - center_and_radius.first, center_and_radius.first, center_and_radius.second - }); - } - - - std::cout << "==================== " << std::endl; - } - - - for (const SurfaceFeature& f : plane.surface_features) { - std::cout << "- detected " << (f.type == SurfaceFeature::Line ? "Line" : "Circle") << std::endl; - std::cout<< f.pos << std::endl << std::endl << f.endpoint << std::endl; - std::cout << "----------------" << std::endl; - } - - - -} - - - -void GLGizmoMeasure::update_planes() +void GLGizmoMeasure::update_if_needed() { const ModelObject* mo = m_c->selection_info()->model_object(); - TriangleMesh ch; - for (const ModelVolume* vol : mo->volumes) { - if (vol->type() != ModelVolumeType::MODEL_PART) - continue; - TriangleMesh vol_ch = vol->mesh(); - vol_ch.transform(vol->get_matrix()); - ch.merge(vol_ch); - } - m_planes.clear(); - + if (m_state != On || ! mo || mo->instances.empty()) + return; - // Now we'll go through all the facets and append Points of facets sharing the same normal. - // This part is still performed in mesh coordinate system. - const size_t num_of_facets = ch.facets_count(); - std::vector face_to_plane(num_of_facets, size_t(-1)); - const std::vector face_normals = its_face_normals(ch.its); - const std::vector face_neighbors = its_face_neighbors(ch.its); - std::vector facet_queue(num_of_facets, 0); - int facet_queue_cnt = 0; - const stl_normal* normal_ptr = nullptr; - size_t seed_facet_idx = 0; + if (! m_measuring || mo != m_old_model_object + || mo->volumes.size() != m_volumes_matrices.size()) + goto UPDATE; - auto is_same_normal = [](const stl_normal& a, const stl_normal& b) -> bool { - return (std::abs(a(0) - b(0)) < 0.001 && std::abs(a(1) - b(1)) < 0.001 && std::abs(a(2) - b(2)) < 0.001); - }; + // We want to recalculate when the scale changes - some planes could (dis)appear. + if (! mo->instances.front()->get_scaling_factor().isApprox(m_first_instance_scale) + || ! mo->instances.front()->get_mirror().isApprox(m_first_instance_mirror)) + goto UPDATE; - while (1) { - // Find next unvisited triangle: - for (; seed_facet_idx < num_of_facets; ++ seed_facet_idx) - if (face_to_plane[seed_facet_idx] == size_t(-1)) { - facet_queue[facet_queue_cnt ++] = seed_facet_idx; - normal_ptr = &face_normals[seed_facet_idx]; - face_to_plane[seed_facet_idx] = m_planes.size(); - m_planes.emplace_back(); - break; - } - if (seed_facet_idx == num_of_facets) - break; // Everything was visited already + for (unsigned int i=0; i < mo->volumes.size(); ++i) + if (! mo->volumes[i]->get_matrix().isApprox(m_volumes_matrices[i]) + || mo->volumes[i]->type() != m_volumes_types[i]) + goto UPDATE; - while (facet_queue_cnt > 0) { - int facet_idx = facet_queue[-- facet_queue_cnt]; - const stl_normal& this_normal = face_normals[facet_idx]; - if (is_same_normal(this_normal, *normal_ptr)) { - const Vec3i& face = ch.its.indices[facet_idx]; + return; - face_to_plane[facet_idx] = m_planes.size() - 1; - m_planes.back().facets.emplace_back(facet_idx); - for (int j = 0; j < 3; ++ j) - if (int neighbor_idx = face_neighbors[facet_idx][j]; neighbor_idx >= 0 && face_to_plane[neighbor_idx] == size_t(-1)) - facet_queue[facet_queue_cnt ++] = neighbor_idx; - } - } +UPDATE: + m_measuring.reset(new Measure::Measuring(mo->volumes.front()->mesh().its)); - m_planes.back().normal = normal_ptr->cast(); - std::sort(m_planes.back().facets.begin(), m_planes.back().facets.end()); - } - - assert(std::none_of(face_to_plane.begin(), face_to_plane.end(), [](size_t val) { return val == size_t(-1); })); - - SurfaceMesh sm(ch.its); - for (int plane_id=0; plane_id < int(m_planes.size()); ++plane_id) { - //int plane_id = 5; { - const auto& facets = m_planes[plane_id].facets; - m_planes[plane_id].borders.clear(); - std::vector> visited(facets.size(), {false, false, false}); - - for (int face_id=0; face_id& last_border = m_planes[plane_id].borders.back(); - last_border.emplace_back(sm.point(sm.source(he)).cast()); - //Vertex_index target = sm.target(he); - const Halfedge_index he_start = he; - - Face_index fi = he.face(); - auto face_it = std::lower_bound(facets.begin(), facets.end(), int(fi)); - assert(face_it != facets.end()); - assert(*face_it == int(fi)); - visited[face_it - facets.begin()][he.side()] = true; - - do { - const Halfedge_index he_orig = he; - he = sm.next_around_target(he); - while ( face_to_plane[sm.face(he)] == plane_id && he != he_orig) - he = sm.next_around_target(he); - he = sm.opposite(he); - - Face_index fi = he.face(); - auto face_it = std::lower_bound(facets.begin(), facets.end(), int(fi)); - assert(face_it != facets.end()); - assert(*face_it == int(fi)); - if (visited[face_it - facets.begin()][he.side()] && he != he_start) { - last_border.resize(1); - break; - } - visited[face_it - facets.begin()][he.side()] = true; - - last_border.emplace_back(sm.point(sm.source(he)).cast()); - } while (he != he_start); - - if (last_border.size() == 1) - m_planes[plane_id].borders.pop_back(); - } - } - } - - - // DEBUGGING: - m_planes.erase(std::remove_if(m_planes.begin(), m_planes.end(), [](const PlaneData& p) { return p.borders.empty(); }), m_planes.end()); - - - - - - - - - // Planes are finished - let's save what we calculated it from: + // Let's save what we calculated it from: m_volumes_matrices.clear(); m_volumes_types.clear(); for (const ModelVolume* vol : mo->volumes) { @@ -515,65 +256,6 @@ void GLGizmoMeasure::update_planes() m_first_instance_scale = mo->instances.front()->get_scaling_factor(); m_first_instance_mirror = mo->instances.front()->get_mirror(); m_old_model_object = mo; - - // And finally create respective VBOs. The polygon is convex with - // the vertices in order, so triangulation is trivial. - for (PlaneData& plane : m_planes) { - for (std::vector& vertices : plane.borders) { - GLModel::Geometry init_data; - init_data.format = { GLModel::Geometry::EPrimitiveType::LineStrip, GLModel::Geometry::EVertexLayout::P3N3 }; - init_data.reserve_vertices(vertices.size()); - init_data.reserve_indices(vertices.size()); - // vertices + indices - for (size_t i = 0; i < vertices.size(); ++i) { - init_data.add_vertex((Vec3f)vertices[i].cast(), (Vec3f)plane.normal.cast()); - init_data.add_index((unsigned int)i); - } - plane.vbos.emplace_back(); - plane.vbos.back().init_from(std::move(init_data)); - vertices.pop_back(); // first and last are the same - } - - static int n=0; - std::cout << "==================== " << std::endl; - std::cout << "==================== " << std::endl; - std::cout << "==================== " << std::endl; - std::cout << "Plane num. " << n++ << std::endl; - extract_features(plane); - - - // FIXME: vertices should really be local, they need not - // persist now when we use VBOs - plane.borders.clear(); - plane.borders.shrink_to_fit(); - } - - m_planes_valid = true; -} - - - -bool GLGizmoMeasure::is_plane_update_necessary() const -{ - const ModelObject* mo = m_c->selection_info()->model_object(); - if (m_state != On || ! mo || mo->instances.empty()) - return false; - - if (! m_planes_valid || mo != m_old_model_object - || mo->volumes.size() != m_volumes_matrices.size()) - return true; - - // We want to recalculate when the scale changes - some planes could (dis)appear. - if (! mo->instances.front()->get_scaling_factor().isApprox(m_first_instance_scale) - || ! mo->instances.front()->get_mirror().isApprox(m_first_instance_mirror)) - return true; - - for (unsigned int i=0; i < mo->volumes.size(); ++i) - if (! mo->volumes[i]->get_matrix().isApprox(m_volumes_matrices[i]) - || mo->volumes[i]->type() != m_volumes_types[i]) - return true; - - return false; } } // namespace GUI diff --git a/src/slic3r/GUI/Gizmos/GLGizmoMeasure.hpp b/src/slic3r/GUI/Gizmos/GLGizmoMeasure.hpp index 9534796a71..2781d2b353 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoMeasure.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoMeasure.hpp @@ -9,10 +9,15 @@ #endif // ENABLE_LEGACY_OPENGL_REMOVAL +#include + + namespace Slic3r { enum class ModelVolumeType : int; +namespace Measure { class Measuring; } + namespace GUI { @@ -22,53 +27,27 @@ class GLGizmoMeasure : public GLGizmoBase // This gizmo does not use grabbers. The m_hover_id relates to polygon managed by the class itself. private: - - int m_currently_shown_plane = 0; - bool m_show_all_planes = false; - bool m_show_points = true; - bool m_show_edges = true; - bool m_show_circles = true; + std::unique_ptr m_measuring; GLModel m_vbo_sphere; GLModel m_vbo_cylinder; - struct SurfaceFeature { - enum Type { - Circle, - Line - }; - Type type; - Vec3d pos; - Vec3d endpoint; // for type == Line - double radius; // for type == Circle; - }; - - struct PlaneData { - std::vector facets; - std::vector> borders; // should be in fact local in update_planes() - std::vector surface_features; - std::vector vbos; - Vec3d normal; - float area; - }; - - static void extract_features(PlaneData& plane); - // This holds information to decide whether recalculation is necessary: std::vector m_volumes_matrices; std::vector m_volumes_types; Vec3d m_first_instance_scale; Vec3d m_first_instance_mirror; - std::vector m_planes; - std::vector m_face_to_plane; bool m_mouse_left_down = false; // for detection left_up of this gizmo bool m_planes_valid = false; const ModelObject* m_old_model_object = nullptr; std::vector instances_matrices; - void update_planes(); - bool is_plane_update_necessary() const; + int m_mouse_pos_x; + int m_mouse_pos_y; + bool m_show_all = true; + + void update_if_needed(); void set_flattening_data(const ModelObject* model_object); public: From c8e9622ab21e186f24e580a596d05ecc78e1905c Mon Sep 17 00:00:00 2001 From: Lukas Matena Date: Wed, 27 Jul 2022 09:58:21 +0200 Subject: [PATCH 11/13] Measuring: further separating frontend and backend --- src/libslic3r/Measure.cpp | 236 +++++++++++++++-------- src/libslic3r/Measure.hpp | 24 ++- src/slic3r/GUI/Gizmos/GLGizmoMeasure.cpp | 91 ++++++--- src/slic3r/GUI/Gizmos/GLGizmoMeasure.hpp | 4 +- 4 files changed, 233 insertions(+), 122 deletions(-) diff --git a/src/libslic3r/Measure.cpp b/src/libslic3r/Measure.cpp index d5cb9c24b3..a8ee3d3265 100644 --- a/src/libslic3r/Measure.cpp +++ b/src/libslic3r/Measure.cpp @@ -39,16 +39,19 @@ public: float area; }; - const std::vector& get_features() const; + const std::vector& get_features() const; + const SurfaceFeature* get_feature(size_t face_idx, const Vec3d& point) const; + const std::vector> get_planes_triangle_indices() const; private: void update_planes(); - void extract_features(PlaneData& plane); + void extract_features(); void save_features(); std::vector m_planes; - std::vector m_features; + std::vector m_face_to_plane; + std::vector m_features; const indexed_triangle_set& m_its; }; @@ -61,14 +64,7 @@ MeasuringImpl::MeasuringImpl(const indexed_triangle_set& its) : m_its{its} { update_planes(); - - for (PlaneData& plane : m_planes) { - extract_features(plane); - - plane.borders.clear(); - plane.borders.shrink_to_fit(); - } - + extract_features(); save_features(); } @@ -80,7 +76,7 @@ void MeasuringImpl::update_planes() // Now we'll go through all the facets and append Points of facets sharing the same normal. // This part is still performed in mesh coordinate system. const size_t num_of_facets = m_its.indices.size(); - std::vector face_to_plane(num_of_facets, size_t(-1)); + m_face_to_plane.resize(num_of_facets, size_t(-1)); const std::vector face_normals = its_face_normals(m_its); const std::vector face_neighbors = its_face_neighbors(m_its); std::vector facet_queue(num_of_facets, 0); @@ -95,10 +91,10 @@ void MeasuringImpl::update_planes() while (1) { // Find next unvisited triangle: for (; seed_facet_idx < num_of_facets; ++ seed_facet_idx) - if (face_to_plane[seed_facet_idx] == size_t(-1)) { + if (m_face_to_plane[seed_facet_idx] == size_t(-1)) { facet_queue[facet_queue_cnt ++] = seed_facet_idx; normal_ptr = &face_normals[seed_facet_idx]; - face_to_plane[seed_facet_idx] = m_planes.size(); + m_face_to_plane[seed_facet_idx] = m_planes.size(); m_planes.emplace_back(); break; } @@ -111,10 +107,10 @@ void MeasuringImpl::update_planes() if (is_same_normal(this_normal, *normal_ptr)) { const Vec3i& face = m_its.indices[facet_idx]; - face_to_plane[facet_idx] = m_planes.size() - 1; + m_face_to_plane[facet_idx] = m_planes.size() - 1; m_planes.back().facets.emplace_back(facet_idx); for (int j = 0; j < 3; ++ j) - if (int neighbor_idx = face_neighbors[facet_idx][j]; neighbor_idx >= 0 && face_to_plane[neighbor_idx] == size_t(-1)) + if (int neighbor_idx = face_neighbors[facet_idx][j]; neighbor_idx >= 0 && m_face_to_plane[neighbor_idx] == size_t(-1)) facet_queue[facet_queue_cnt ++] = neighbor_idx; } } @@ -123,7 +119,7 @@ void MeasuringImpl::update_planes() std::sort(m_planes.back().facets.begin(), m_planes.back().facets.end()); } - assert(std::none_of(face_to_plane.begin(), face_to_plane.end(), [](size_t val) { return val == size_t(-1); })); + assert(std::none_of(m_face_to_plane.begin(), m_face_to_plane.end(), [](size_t val) { return val == size_t(-1); })); SurfaceMesh sm(m_its); for (int plane_id=0; plane_id < int(m_planes.size()); ++plane_id) { @@ -133,9 +129,9 @@ void MeasuringImpl::update_planes() std::vector> visited(facets.size(), {false, false, false}); for (int face_id=0; face_id angles; - Eigen::Quaterniond q; - q.setFromTwoVectors(plane.normal, Vec3d::UnitZ()); - Transform3d trafo = Transform3d::Identity(); - trafo.rotate(q); - - - for (const std::vector& border : plane.borders) { - assert(border.size() > 1); - int start_idx = -1; + for (int i=0; i& border : plane.borders) { + assert(border.size() > 1); + int start_idx = -1; + + // First calculate angles at all the vertices. + angles.clear(); + for (int i=0; i> circles; - for (int i=1; i> circles; + for (int i=1; i circles[cidx].first) + i = circles[cidx++].second; + else plane.surface_features.emplace_back(std::unique_ptr( + new Edge(border[i-1], border[i]))); + } + + // FIXME Throw away / do not create edges which are parts of circles or + // which lead to circle points (unless they belong to the same plane.) + + // FIXME Check and merge first and last circle if needed. + + // Now create the circle-typed surface features. + for (const auto& [start_idx, end_idx] : circles) { + std::pair center_and_radius = get_center_and_radius(border, start_idx, end_idx, trafo); + plane.surface_features.emplace_back(std::unique_ptr( + new Circle(center_and_radius.first, center_and_radius.second))); + } + } - // We have the circles. Now go around again and pick edges. - int cidx = 0; // index of next circle in the way - for (int i=1; i circles[cidx].first) - i = circles[cidx++].second; - else plane.surface_features.emplace_back(std::unique_ptr( - new Edge(border[i-1], border[i]))); - } - - // FIXME Throw away / do not create edges which are parts of circles. - - // FIXME Check and maybe merge first and last circle. - - for (const auto& [start_idx, end_idx] : circles) { - std::pair center_and_radius = get_center_and_radius(border, start_idx, end_idx, trafo); - plane.surface_features.emplace_back(std::unique_ptr( - new Circle(center_and_radius.first, center_and_radius.second) - )); - } + // The last surface feature is the plane itself. + plane.surface_features.emplace_back(std::unique_ptr( + new Plane(i))); + plane.borders.clear(); + plane.borders.shrink_to_fit(); } } @@ -277,7 +284,7 @@ void MeasuringImpl::save_features() for (PlaneData& plane : m_planes) //PlaneData& plane = m_planes[0]; { - for (std::unique_ptr& feature : plane.surface_features) { + for (const std::unique_ptr& feature : plane.surface_features) { m_features.emplace_back(feature.get()); } } @@ -285,13 +292,51 @@ void MeasuringImpl::save_features() -const std::vector& MeasuringImpl::get_features() const +const SurfaceFeature* MeasuringImpl::get_feature(size_t face_idx, const Vec3d& point) const +{ + if (face_idx >= m_face_to_plane.size()) + return nullptr; + + const PlaneData& plane = m_planes[m_face_to_plane[face_idx]]; + + const SurfaceFeature* closest_feature = nullptr; + double min_dist = std::numeric_limits::max(); + + for (const std::unique_ptr& feature : plane.surface_features) { + double dist = Measuring::get_distance(feature.get(), &point); + if (dist < 0.5 && dist < min_dist) { + min_dist = std::min(dist, min_dist); + closest_feature = feature.get(); + } + } + + if (closest_feature) + return closest_feature; + + // Nothing detected, return the plane as a whole. + assert(plane.surface_features.back().get()->get_type() == SurfaceFeatureType::Plane); + return plane.surface_features.back().get(); +} + + + +const std::vector& MeasuringImpl::get_features() const { return m_features; } +const std::vector> MeasuringImpl::get_planes_triangle_indices() const +{ + std::vector> out; + for (const PlaneData& plane : m_planes) + out.emplace_back(plane.facets); + return out; +} + + + @@ -309,12 +354,39 @@ Measuring::Measuring(const indexed_triangle_set& its) Measuring::~Measuring() {} -const std::vector& Measuring::get_features() const +const std::vector& Measuring::get_features() const { return priv->get_features(); } +const SurfaceFeature* Measuring::get_feature(size_t face_idx, const Vec3d& point) const +{ + return priv->get_feature(face_idx, point); +} + + + +const std::vector> Measuring::get_planes_triangle_indices() const +{ + return priv->get_planes_triangle_indices(); +} + + + +double Measuring::get_distance(const SurfaceFeature* feature, const Vec3d* pt) +{ + if (feature->get_type() == SurfaceFeatureType::Edge) { + const Edge* edge = static_cast(feature); + const auto& [s,e] = edge->get_edge(); + Eigen::ParametrizedLine line(s, (e-s).normalized()); + return line.distance(*pt); + } + + return std::numeric_limits::max(); +} + + diff --git a/src/libslic3r/Measure.hpp b/src/libslic3r/Measure.hpp index bbc7d9e1e9..1360b47ff7 100644 --- a/src/libslic3r/Measure.hpp +++ b/src/libslic3r/Measure.hpp @@ -48,8 +48,12 @@ private: class Plane : public SurfaceFeature { public: + Plane(int idx) : m_idx(idx) {} SurfaceFeatureType get_type() const override { return SurfaceFeatureType::Plane; } + int get_plane_idx() const { return m_idx; } // index into vector provided by Measuring::get_plane_triangle_indices +private: + int m_idx; }; @@ -64,30 +68,30 @@ public: ~Measuring(); // Return a reference to a list of all features identified on the its. - const std::vector& get_features() const; + [[deprecated]]const std::vector& get_features() const; // Given a face_idx where the mouse cursor points, return a feature that // should be highlighted or nullptr. const SurfaceFeature* get_feature(size_t face_idx, const Vec3d& point) const; + // Returns a list of triangle indices for each identified plane. Each + // Plane object contains an index into this vector. + const std::vector> get_planes_triangle_indices() const; + + + // Returns distance between two SurfaceFeatures. static double get_distance(const SurfaceFeature* a, const SurfaceFeature* b); - // Returns true if an x/y/z distance between features makes sense. - // If so, result contains the distances. - static bool get_distances(const SurfaceFeature* a, const SurfaceFeature* b, std::array& result); - - // Returns true if an x/y/z distance between feature and a point makes sense. - // If so, result contains the distances. - static bool get_axis_aligned_distances(const SurfaceFeature* feature, const Vec3d* pt, std::array& result); + // Returns distance between a SurfaceFeature and a point. + static double get_distance(const SurfaceFeature* a, const Vec3d* pt); // Returns true if measuring angles between features makes sense. // If so, result contains the angle in radians. static bool get_angle(const SurfaceFeature* a, const SurfaceFeature* b, double& result); -private: - +private: std::unique_ptr priv; }; diff --git a/src/slic3r/GUI/Gizmos/GLGizmoMeasure.cpp b/src/slic3r/GUI/Gizmos/GLGizmoMeasure.cpp index 043adba976..a8b257a937 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoMeasure.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoMeasure.cpp @@ -144,6 +144,7 @@ void GLGizmoMeasure::on_render() m_imgui->begin(std::string("DEBUG")); m_imgui->checkbox(wxString("Show all features"), m_show_all); + m_imgui->checkbox(wxString("Show all planes"), m_show_planes); Vec3f pos; Vec3f normal; @@ -157,37 +158,51 @@ void GLGizmoMeasure::on_render() - if (m_show_all) { - const std::vector features = m_measuring->get_features(); - for (const Measure::SurfaceFeature* feature : features) { + std::vector features = {m_measuring->get_feature(facet_idx, pos.cast())}; + if (m_show_all) { + features = m_measuring->get_features(); + features.erase(std::remove_if(features.begin(), features.end(), + [](const Measure::SurfaceFeature* f) { + return f->get_type() == Measure::SurfaceFeatureType::Plane; + }), features.end()); + } + + + for (const Measure::SurfaceFeature* feature : features) { + if (! feature) + continue; - if (feature->get_type() == Measure::SurfaceFeatureType::Circle) { - const auto* circle = static_cast(feature); - Transform3d view_feature_matrix = view_model_matrix * Transform3d(Eigen::Translation3d(circle->get_center())); - view_feature_matrix = view_model_matrix * Transform3d(Eigen::Translation3d(circle->get_center())); - view_feature_matrix.scale(0.5); - shader->set_uniform("view_model_matrix", view_feature_matrix); - m_vbo_sphere.set_color(ColorRGBA(0.f, 1.f, 0.f, 1.f)); - m_vbo_sphere.render(); - } - - - else if (feature->get_type() == Measure::SurfaceFeatureType::Edge) { - const auto* edge = static_cast(feature); - auto& [start, end] = edge->get_edge(); - Transform3d view_feature_matrix = view_model_matrix * Transform3d(Eigen::Translation3d(start)); - auto q = Eigen::Quaternion::FromTwoVectors(Vec3d::UnitZ(), end - start); - view_feature_matrix *= q; - view_feature_matrix.scale(Vec3d(0.075, 0.075, (end - start).norm())); - shader->set_uniform("view_model_matrix", view_feature_matrix); - m_vbo_cylinder.set_color(ColorRGBA(0.7f, 0.7f, 0.f, 1.f)); - m_vbo_cylinder.render(); - } - - + if (feature->get_type() == Measure::SurfaceFeatureType::Circle) { + const auto* circle = static_cast(feature); + Transform3d view_feature_matrix = view_model_matrix * Transform3d(Eigen::Translation3d(circle->get_center())); + view_feature_matrix = view_model_matrix * Transform3d(Eigen::Translation3d(circle->get_center())); + view_feature_matrix.scale(0.5); + shader->set_uniform("view_model_matrix", view_feature_matrix); + m_vbo_sphere.set_color(ColorRGBA(0.f, 1.f, 0.f, 1.f)); + m_vbo_sphere.render(); } - shader->set_uniform("view_model_matrix", view_model_matrix); + else if (feature->get_type() == Measure::SurfaceFeatureType::Edge) { + const auto* edge = static_cast(feature); + auto& [start, end] = edge->get_edge(); + Transform3d view_feature_matrix = view_model_matrix * Transform3d(Eigen::Translation3d(start)); + auto q = Eigen::Quaternion::FromTwoVectors(Vec3d::UnitZ(), end - start); + view_feature_matrix *= q; + view_feature_matrix.scale(Vec3d(0.075, 0.075, (end - start).norm())); + shader->set_uniform("view_model_matrix", view_feature_matrix); + m_vbo_cylinder.set_color(ColorRGBA(0.8f, 0.2f, 0.2f, 1.f)); + m_vbo_cylinder.render(); + } + else if (feature->get_type() == Measure::SurfaceFeatureType::Plane) { + const auto* plane = static_cast(feature); + assert(plane->get_plane_idx() < m_plane_models.size()); + m_plane_models[plane->get_plane_idx()]->render(); + } } + shader->set_uniform("view_model_matrix", view_model_matrix); + if (m_show_planes) + for (const auto& glmodel : m_plane_models) + glmodel->render(); + m_imgui->end(); } @@ -244,7 +259,25 @@ void GLGizmoMeasure::update_if_needed() return; UPDATE: - m_measuring.reset(new Measure::Measuring(mo->volumes.front()->mesh().its)); + const indexed_triangle_set& its = mo->volumes.front()->mesh().its; + m_measuring.reset(new Measure::Measuring(its)); + m_plane_models.clear(); + const std::vector> planes_triangles = m_measuring->get_planes_triangle_indices(); + for (const std::vector& triangle_indices : planes_triangles) { + m_plane_models.emplace_back(std::unique_ptr(new GLModel())); + GUI::GLModel::Geometry init_data; + init_data.format = { GUI::GLModel::Geometry::EPrimitiveType::Triangles, GUI::GLModel::Geometry::EVertexLayout::P3 }; + init_data.color = ColorRGBA(0.9f, 0.9f, 0.9f, 0.5f); + int i = 0; + for (int idx : triangle_indices) { + init_data.add_vertex(its.vertices[its.indices[idx][0]]); + init_data.add_vertex(its.vertices[its.indices[idx][1]]); + init_data.add_vertex(its.vertices[its.indices[idx][2]]); + init_data.add_triangle(i, i+1, i+2); + i+=3; + } + m_plane_models.back()->init_from(std::move(init_data)); + } // Let's save what we calculated it from: m_volumes_matrices.clear(); diff --git a/src/slic3r/GUI/Gizmos/GLGizmoMeasure.hpp b/src/slic3r/GUI/Gizmos/GLGizmoMeasure.hpp index 2781d2b353..f74b82fa46 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoMeasure.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoMeasure.hpp @@ -45,7 +45,9 @@ private: int m_mouse_pos_x; int m_mouse_pos_y; - bool m_show_all = true; + bool m_show_all = false; + bool m_show_planes = false; + std::vector> m_plane_models; void update_if_needed(); void set_flattening_data(const ModelObject* model_object); From f68e7526b215428315d8fc269fe78cce80bb8303 Mon Sep 17 00:00:00 2001 From: Lukas Matena Date: Wed, 27 Jul 2022 11:45:42 +0200 Subject: [PATCH 12/13] Measuring: added getters for circle visualization --- src/libslic3r/Measure.cpp | 11 ++++++++++- src/libslic3r/Measure.hpp | 8 +++++++- src/slic3r/GUI/Gizmos/GLGizmoMeasure.cpp | 22 +++++++++++++++++++--- 3 files changed, 36 insertions(+), 5 deletions(-) diff --git a/src/libslic3r/Measure.cpp b/src/libslic3r/Measure.cpp index a8ee3d3265..724f4ab806 100644 --- a/src/libslic3r/Measure.cpp +++ b/src/libslic3r/Measure.cpp @@ -262,7 +262,7 @@ void MeasuringImpl::extract_features() for (const auto& [start_idx, end_idx] : circles) { std::pair center_and_radius = get_center_and_radius(border, start_idx, end_idx, trafo); plane.surface_features.emplace_back(std::unique_ptr( - new Circle(center_and_radius.first, center_and_radius.second))); + new Circle(center_and_radius.first, center_and_radius.second, plane.normal))); } } @@ -382,6 +382,15 @@ double Measuring::get_distance(const SurfaceFeature* feature, const Vec3d* pt) Eigen::ParametrizedLine line(s, (e-s).normalized()); return line.distance(*pt); } + else if (feature->get_type() == SurfaceFeatureType::Circle) { + const Circle* circle = static_cast(feature); + // Find a plane containing normal, center and the point. + const Vec3d& c = circle->get_center(); + const Vec3d& n = circle->get_normal(); + Eigen::Hyperplane circle_plane(n, c); + Vec3d proj = circle_plane.projection(*pt); + return std::sqrt( std::pow((proj - c).norm() - circle->get_radius(), 2.) + (*pt - proj).squaredNorm()); + } return std::numeric_limits::max(); } diff --git a/src/libslic3r/Measure.hpp b/src/libslic3r/Measure.hpp index 1360b47ff7..0455291bfc 100644 --- a/src/libslic3r/Measure.hpp +++ b/src/libslic3r/Measure.hpp @@ -28,22 +28,28 @@ public: class Edge : public SurfaceFeature { public: Edge(const Vec3d& start, const Vec3d& end) : m_start{start}, m_end{end} {} + Edge(const Vec3d& start, const Vec3d& end, const Vec3d& pin) : m_start{start}, m_end{end}, + m_pin{std::unique_ptr(new Vec3d(pin))} {} SurfaceFeatureType get_type() const override { return SurfaceFeatureType::Edge; } std::pair get_edge() const { return std::make_pair(m_start, m_end); } private: Vec3d m_start; Vec3d m_end; + std::unique_ptr m_pin; }; class Circle : public SurfaceFeature { public: - Circle(const Vec3d& center, double radius) : m_center{center}, m_radius{radius} {} + Circle(const Vec3d& center, double radius, const Vec3d& normal) + : m_center{center}, m_radius{radius}, m_normal{normal} {} SurfaceFeatureType get_type() const override { return SurfaceFeatureType::Circle; } Vec3d get_center() const { return m_center; } double get_radius() const { return m_radius; } + Vec3d get_normal() const { return m_normal; } private: Vec3d m_center; double m_radius; + Vec3d m_normal; }; class Plane : public SurfaceFeature { diff --git a/src/slic3r/GUI/Gizmos/GLGizmoMeasure.cpp b/src/slic3r/GUI/Gizmos/GLGizmoMeasure.cpp index a8b257a937..44dd44cc55 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoMeasure.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoMeasure.cpp @@ -174,12 +174,28 @@ void GLGizmoMeasure::on_render() if (feature->get_type() == Measure::SurfaceFeatureType::Circle) { const auto* circle = static_cast(feature); - Transform3d view_feature_matrix = view_model_matrix * Transform3d(Eigen::Translation3d(circle->get_center())); - view_feature_matrix = view_model_matrix * Transform3d(Eigen::Translation3d(circle->get_center())); + const Vec3d& c = circle->get_center(); + const Vec3d& n = circle->get_normal(); + Transform3d view_feature_matrix = view_model_matrix * Transform3d(Eigen::Translation3d(c)); view_feature_matrix.scale(0.5); shader->set_uniform("view_model_matrix", view_feature_matrix); - m_vbo_sphere.set_color(ColorRGBA(0.f, 1.f, 0.f, 1.f)); + m_vbo_sphere.set_color(ColorRGBA(0.8f, 0.2f, 0.2f, 1.f)); m_vbo_sphere.render(); + + // Now draw the circle itself - let's take a funny shortcut: + Vec3d rad = n.cross(Vec3d::UnitX()); + if (rad.squaredNorm() < 0.1) + rad = n.cross(Vec3d::UnitY()); + rad *= circle->get_radius() * rad.norm(); + const int N = 20; + for (int i=0; iset_uniform("view_model_matrix", view_feature_matrix); + m_vbo_sphere.render(); + } } else if (feature->get_type() == Measure::SurfaceFeatureType::Edge) { const auto* edge = static_cast(feature); From 31baf5859bb85df8f977ce173d5e9f30b7544c75 Mon Sep 17 00:00:00 2001 From: Lukas Matena Date: Wed, 27 Jul 2022 15:36:21 +0200 Subject: [PATCH 13/13] Measuring: Add detection of polygons and their centers --- src/libslic3r/Measure.cpp | 72 ++++++++++++++++++------ src/libslic3r/Measure.hpp | 1 + src/slic3r/GUI/Gizmos/GLGizmoMeasure.cpp | 8 +++ 3 files changed, 63 insertions(+), 18 deletions(-) diff --git a/src/libslic3r/Measure.cpp b/src/libslic3r/Measure.cpp index 724f4ab806..056178bc4e 100644 --- a/src/libslic3r/Measure.cpp +++ b/src/libslic3r/Measure.cpp @@ -192,10 +192,11 @@ void MeasuringImpl::update_planes() void MeasuringImpl::extract_features() { - - - const double edge_threshold = 25. * (M_PI/180.); + auto N_to_angle = [](double N) -> double { return 2.*M_PI / N; }; + constexpr double polygon_upper_threshold = N_to_angle(4.5); + constexpr double polygon_lower_threshold = N_to_angle(8.5); std::vector angles; + std::vector lengths; for (int i=0; i M_PI) + angle = 2*M_PI - angle; + angles.push_back(angle); + lengths.push_back(v2.squaredNorm()); } assert(border.size() == angles.size()); + assert(border.size() == lengths.size()); bool circle = false; - std::vector> circles; + std::vector> circles; + std::vector> circles_idxs; for (int i=1; i( + new Circle(center, radius, plane.normal))); circle = false; } } } + // Some of the "circles" may actually be polygons. We want them detected as + // edges, but also to remember the center and save it into those edges. + // We will add all such edges manually and delete the detected circles, + // leaving it in circles_idxs so they are not picked again: + assert(circles.size() == circles_idxs.size()); + for (int i=circles.size()-1; i>=0; --i) { + assert(circles_idxs[i].first + 1 < angles.size() - 1); // Check that this is internal point of the circle, not the first, not the last. + double angle = angles[circles_idxs[i].first + 1]; + if (angle > polygon_lower_threshold) { + if (angle < polygon_upper_threshold) { + const Vec3d center = static_cast(circles[i].get())->get_center(); + for (int j=circles_idxs[i].first + 1; j<=circles_idxs[i].second; ++j) + plane.surface_features.emplace_back(std::unique_ptr( + new Edge(border[j-1], border[j], center))); + } else { + // This will be handled just like a regular edge. + circles_idxs.erase(circles_idxs.begin() + i); + } + circles.erase(circles.begin() + i); + } + } + + + + + + // We have the circles. Now go around again and pick edges. int cidx = 0; // index of next circle in the way for (int i=1; i circles[cidx].first) - i = circles[cidx++].second; + if (cidx < circles_idxs.size() && i > circles_idxs[cidx].first) + i = circles_idxs[cidx++].second; else plane.surface_features.emplace_back(std::unique_ptr( new Edge(border[i-1], border[i]))); } @@ -258,13 +297,10 @@ void MeasuringImpl::extract_features() // FIXME Check and merge first and last circle if needed. - // Now create the circle-typed surface features. - for (const auto& [start_idx, end_idx] : circles) { - std::pair center_and_radius = get_center_and_radius(border, start_idx, end_idx, trafo); - plane.surface_features.emplace_back(std::unique_ptr( - new Circle(center_and_radius.first, center_and_radius.second, plane.normal))); - } - + // Now move the circles into the feature list. + assert(std::all_of(circles.begin(), circles.end(), [](const std::unique_ptr& f) { return f->get_type() == SurfaceFeatureType::Circle; })); + plane.surface_features.insert(plane.surface_features.end(), std::make_move_iterator(circles.begin()), + std::make_move_iterator(circles.end())); } // The last surface feature is the plane itself. diff --git a/src/libslic3r/Measure.hpp b/src/libslic3r/Measure.hpp index 0455291bfc..1db35d9fcd 100644 --- a/src/libslic3r/Measure.hpp +++ b/src/libslic3r/Measure.hpp @@ -32,6 +32,7 @@ public: m_pin{std::unique_ptr(new Vec3d(pin))} {} SurfaceFeatureType get_type() const override { return SurfaceFeatureType::Edge; } std::pair get_edge() const { return std::make_pair(m_start, m_end); } + const Vec3d* get_point_of_interest() const { return m_pin.get(); } private: Vec3d m_start; Vec3d m_end; diff --git a/src/slic3r/GUI/Gizmos/GLGizmoMeasure.cpp b/src/slic3r/GUI/Gizmos/GLGizmoMeasure.cpp index 44dd44cc55..1869983015 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoMeasure.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoMeasure.cpp @@ -207,6 +207,14 @@ void GLGizmoMeasure::on_render() shader->set_uniform("view_model_matrix", view_feature_matrix); m_vbo_cylinder.set_color(ColorRGBA(0.8f, 0.2f, 0.2f, 1.f)); m_vbo_cylinder.render(); + if (edge->get_point_of_interest()) { + Vec3d pin = *edge->get_point_of_interest(); + view_feature_matrix = view_model_matrix * Transform3d(Eigen::Translation3d(pin)); + view_feature_matrix.scale(0.5); + shader->set_uniform("view_model_matrix", view_feature_matrix); + m_vbo_sphere.set_color(ColorRGBA(0.8f, 0.2f, 0.2f, 1.f)); + m_vbo_sphere.render(); + } } else if (feature->get_type() == Measure::SurfaceFeatureType::Plane) { const auto* plane = static_cast(feature);