From a1d7040902211517cccc500af0e9ed03c5905d38 Mon Sep 17 00:00:00 2001 From: Filip Sykala Date: Tue, 26 Apr 2022 16:54:24 +0200 Subject: [PATCH] WIP: cut surface of model update emboss icons to not be soo huge - pixel preccisse --- resources/icons/add_text_modifier.svg | 4 +- resources/icons/add_text_negative.svg | 4 +- resources/icons/add_text_part.svg | 4 +- src/libslic3r/CutSurface.cpp | 470 +++++++++++++++++++----- src/libslic3r/CutSurface.hpp | 38 +- src/libslic3r/Emboss.cpp | 23 +- src/libslic3r/Emboss.hpp | 27 +- src/slic3r/GUI/Gizmos/GLGizmoEmboss.cpp | 219 ++++++++++- src/slic3r/GUI/Gizmos/GLGizmoEmboss.hpp | 3 + src/slic3r/GUI/Jobs/EmbossJob.cpp | 1 + tests/libslic3r/test_emboss.cpp | 19 +- 11 files changed, 668 insertions(+), 144 deletions(-) diff --git a/resources/icons/add_text_modifier.svg b/resources/icons/add_text_modifier.svg index 49f4d4252..0a8376741 100644 --- a/resources/icons/add_text_modifier.svg +++ b/resources/icons/add_text_modifier.svg @@ -1,4 +1,4 @@ - - + + diff --git a/resources/icons/add_text_negative.svg b/resources/icons/add_text_negative.svg index e447d5328..b125f0839 100644 --- a/resources/icons/add_text_negative.svg +++ b/resources/icons/add_text_negative.svg @@ -1,4 +1,4 @@ - - + + diff --git a/resources/icons/add_text_part.svg b/resources/icons/add_text_part.svg index 01f0ff1ea..3d36ed0fc 100644 --- a/resources/icons/add_text_part.svg +++ b/resources/icons/add_text_part.svg @@ -1,4 +1,4 @@ - - + + diff --git a/src/libslic3r/CutSurface.cpp b/src/libslic3r/CutSurface.cpp index b76431e97..0d4cd2aa1 100644 --- a/src/libslic3r/CutSurface.cpp +++ b/src/libslic3r/CutSurface.cpp @@ -9,29 +9,18 @@ void Slic3r::append(SurfaceCut &sc, SurfaceCut &&sc_add) return; } - if (!sc_add.cut.empty()) { + if (!sc_add.contours.empty()) { SurfaceCut::Index offset = static_cast( sc.vertices.size()); - size_t require = sc.cut.size() + sc_add.cut.size(); - if (sc.cut.capacity() < require) sc.cut.reserve(require); - for (std::vector &cut : sc_add.cut) + size_t require = sc.contours.size() + sc_add.contours.size(); + if (sc.contours.capacity() < require) sc.contours.reserve(require); + for (std::vector &cut : sc_add.contours) for (SurfaceCut::Index &i : cut) i += offset; - append(sc.cut, std::move(sc_add.cut)); + append(sc.contours, std::move(sc_add.contours)); } its_merge(sc, std::move(sc_add)); } -#if !ENABLE_NEW_CGAL - -SurfaceCuts Slic3r::cut_surface(const indexed_triangle_set &model, - const ExPolygons &shapes, - const Emboss::IProject &projection) -{ - return {}; -} - -#else - #include #include #include @@ -172,7 +161,10 @@ struct Visitor { // Properties of the object mesh. VertexShapeMap vert_shape_map; - + + // check for anomalities + bool* is_valid; + // keep source of intersection for each intersection // used to copy data into vert_shape_map std::vector intersections; @@ -214,6 +206,7 @@ struct Visitor { bool is_source_coplanar); /// + /// Called when a new vertex is added in tm (either an edge split or a vertex inserted in the interior of a face). /// Fill vertex_shape_map by intersections /// /// Order number of intersection point @@ -264,14 +257,29 @@ void set_face_type(FaceTypeMap &face_type_map, const Project &project, const CutMesh &shape_mesh); + +/// +/// Check orientation(normal direction) of face on mesh +/// +/// Face index to inspect +/// Mesh contained fi +/// Define direction of projection +/// TRUE for cutted face otherwise FALSE +bool is_toward_projection(FI fi, + const CutMesh &mesh, + const Project &projection); + /// /// Change FaceType from not_constrained to inside /// For neighbor(or neighbor of neighbor of ...) of inside triangles. /// Process only not_constrained triangles /// /// Corefined mesh +/// Projection from 2d to 3d /// In/Out map with faces type -void flood_fill_inner(const CutMesh &mesh, FaceTypeMap &face_type_map); +void flood_fill_inner(const CutMesh &mesh, + const Project &projection, + FaceTypeMap &face_type_map); using ReductionMap = CutMesh::Property_map; /// @@ -290,6 +298,38 @@ void create_reduce_map(ReductionMap &reduction_map, const FaceTypeMap &face_type_map, const VertexShapeMap &vert_shape_map); +// connected faces(triangles) and outlines(halfEdges) for one surface cut +using CutAOI = std::pair, std::vector>; +using CutAOIs = std::vector; + +/// +/// Create areas from mesh surface +/// +/// Model +/// Cutted shapes +/// Define Triangles of interest. +/// Edge between inside / outside. +/// NOTE: Not const because it need to flag proccessed faces +/// Areas of interest from mesh +CutAOIs create_cut_area_of_interests(const CutMesh &mesh, + const ExPolygons &shapes, + FaceTypeMap &face_type_map); + +/// +/// Filter out cuts which are behind another. +/// Prevent overlapping embossed shape in space. +/// +/// AOIs +/// triangle model +/// 2d cutted shapes +/// Projection from 2d to 3d +/// Identify source of intersection +void filter_cuts(CutAOIs &cuts, + const CutMesh &mesh, + const ExPolygons &shapes, + const Project &projection, + const VertexShapeMap &vert_shape_map); + using ConvertMap = CutMesh::Property_map; /// /// Create surface cuts from mesh model @@ -303,11 +343,11 @@ using ConvertMap = CutMesh::Property_map; /// Used only inside function. /// Store conversion from mesh to result. /// Created surface cuts -SurfaceCuts create_surface_cut(const CutMesh &mesh, - const ExPolygons &shapes, - const ReductionMap &reduction_map, - FaceTypeMap &face_type_map, - ConvertMap &convert_map); +SurfaceCuts create_surface_cuts(const CutAOIs &cutAOIs, + const CutMesh &mesh, + const ReductionMap &reduction_map, + ConvertMap &convert_map); + /// /// Collect connected inside faces @@ -349,7 +389,7 @@ SurfaceCut create_index_triangle_set(const std::vector &faces, /// Reduction of vertices /// Map to convert CGAL vertex to its::vertex /// Cuts - outlines of surface -SurfaceCut::CutType create_cut(const std::vector &outlines, +SurfaceCut::CutContour create_cut(const std::vector &outlines, const CutMesh &mesh, const ReductionMap &reduction_map, const ConvertMap &v2v); @@ -371,10 +411,12 @@ SurfaceCuts Slic3r::cut_surface(const indexed_triangle_set &model, const Emboss::IProject &projection) { priv::CutMesh cgal_model = priv::to_cgal(model); + CGAL::IO::write_OFF("C:/data/temp/model.off", cgal_model); // only debug std::string edge_shape_map_name = "e:IntersectingElement"; std::string face_shape_map_name = "f:IntersectingElement"; - priv::CutMesh cgal_shape = priv::to_cgal(shapes, projection, edge_shape_map_name, face_shape_map_name); + priv::CutMesh cgal_shape = priv::to_cgal(shapes, projection, edge_shape_map_name, face_shape_map_name); + CGAL::IO::write_OFF("C:/data/temp/shape.off", cgal_shape); // only debug auto edge_shape_map = cgal_shape.property_map(edge_shape_map_name).first; auto face_shape_map = cgal_shape.property_map(face_shape_map_name).first; @@ -382,9 +424,11 @@ SurfaceCuts Slic3r::cut_surface(const indexed_triangle_set &model, std::string vert_shape_map_name = "v:IntersectingElement"; // pointer to edge or face shape_map priv::VertexShapeMap vert_shape_map = cgal_model.add_property_map(vert_shape_map_name).first; - - // create anotation visitor - priv::Visitor visitor{cgal_model, cgal_shape, edge_shape_map, face_shape_map, vert_shape_map}; + + // detect anomalities in visitor. + bool is_valid = true; + // create anotation visitor - Must be copyable + priv::Visitor visitor{cgal_model, cgal_shape, edge_shape_map, face_shape_map, vert_shape_map, &is_valid}; // bool map for affected edge priv::EcmType ecm = get(priv::DynamicEdgeProperty(), cgal_model); @@ -395,6 +439,8 @@ SurfaceCuts Slic3r::cut_surface(const indexed_triangle_set &model, const auto& q = CGAL::parameters::do_not_modify(true); CGAL::Polygon_mesh_processing::corefine(cgal_model, cgal_shape, p, q); + if (!is_valid) return {}; + std::string face_type_map_name = "f:side"; priv::FaceTypeMap face_type_map = cgal_model.add_property_map(face_type_map_name).first; @@ -403,7 +449,7 @@ SurfaceCuts Slic3r::cut_surface(const indexed_triangle_set &model, priv::store(cgal_model, face_type_map, "C:/data/temp/constrained.off"); // only debug // Seed fill the other faces inside the region. - priv::flood_fill_inner(cgal_model, face_type_map); + priv::flood_fill_inner(cgal_model, projection, face_type_map); priv::store(cgal_model, face_type_map, "C:/data/temp/filled.off"); // only debug std::string vertex_reduction_map_name = "v:reduction"; @@ -411,11 +457,16 @@ SurfaceCuts Slic3r::cut_surface(const indexed_triangle_set &model, priv::create_reduce_map(vertex_reduction_map, cgal_model, face_type_map, vert_shape_map); priv::store(cgal_model, vertex_reduction_map, "C:/data/temp/reduction.off"); // only debug + priv::CutAOIs cutAOIs = create_cut_area_of_interests(cgal_model, shapes, face_type_map); + + // Filter out NO top one cuts + priv::filter_cuts(cutAOIs, cgal_model, shapes, projection, vert_shape_map); + // conversion map between vertex index in cgal_model and indices in result // used instead of std::map std::string vertec_convert_map_name = "v:convert"; priv::ConvertMap vertex_convert_map = cgal_model.add_property_map(vertec_convert_map_name).first; - SurfaceCuts result = priv::create_surface_cut(cgal_model, shapes, vertex_reduction_map, face_type_map, vertex_convert_map); + SurfaceCuts result = priv::create_surface_cuts(cutAOIs, cgal_model, vertex_reduction_map, vertex_convert_map); priv::store(result, "C:/data/temp/cut"); // only debug @@ -423,6 +474,87 @@ SurfaceCuts Slic3r::cut_surface(const indexed_triangle_set &model, return result; } +indexed_triangle_set Slic3r::cuts2model(const SurfaceCuts &cuts, + const Emboss::IProject &projection) +{ + indexed_triangle_set result; + size_t count_vertices = 0; + size_t count_indices = 0; + for (const SurfaceCut &cut : cuts) { + assert(!cut.empty()); + count_indices += cut.indices.size()*2; + // indices from from zig zag + for (const auto &c : cut.contours) { + assert(!c.empty()); + count_indices += c.size() * 2; + } + count_vertices += cut.vertices.size()*2; + } + result.vertices.reserve(count_vertices); + result.indices.reserve(count_indices); + + size_t indices_offset = 0; + for (const SurfaceCut &cut : cuts) { + // front + for (const auto &v : cut.vertices) + result.vertices.push_back(v); + for (const auto &i : cut.indices) + result.indices.emplace_back(i.x() + indices_offset, + i.y() + indices_offset, + i.z() + indices_offset); + + // back + for (const auto &v : cut.vertices) { + Vec3f v2 = projection.project(v); + result.vertices.push_back(v2); + } + size_t back_offset = indices_offset + cut.vertices.size(); + for (const auto &i : cut.indices) { + assert(i.x() + back_offset < result.vertices.size()); + assert(i.y() + back_offset < result.vertices.size()); + assert(i.z() + back_offset < result.vertices.size()); + // Y and Z is swapped CCW triangles for back side + result.indices.emplace_back(i.x() + back_offset, + i.z() + back_offset, + i.y() + back_offset); + } + + // zig zag indices + for (const auto &contour : cut.contours) { + size_t prev_ci = contour.back(); + size_t prev_front_index = indices_offset + prev_ci; + size_t prev_back_index = back_offset + prev_ci; + for (size_t ci : contour) { + size_t front_index = indices_offset + ci; + size_t back_index = back_offset + ci; + assert(front_index < result.vertices.size()); + assert(prev_front_index < result.vertices.size()); + assert(back_index < result.vertices.size()); + assert(prev_back_index < result.vertices.size()); + + result.indices.emplace_back( + front_index, + prev_front_index, + back_index + ); + result.indices.emplace_back( + prev_front_index, + prev_back_index, + back_index + ); + prev_front_index = front_index; + prev_back_index = back_index; + } + } + + indices_offset = result.vertices.size(); + } + + assert(count_vertices == result.vertices.size()); + assert(count_indices == result.indices.size()); + return result; +} + priv::CutMesh priv::to_cgal(const indexed_triangle_set &its) { CutMesh result; @@ -530,11 +662,11 @@ void priv::set_face_type(FaceTypeMap &face_type_map, const Project &project, const CutMesh &shape_mesh) { - for (auto& fi : mesh.faces()) { + for (const FI& fi : mesh.faces()) { FaceType face_type = FaceType::not_constrained; - auto hi_end = mesh.halfedge(fi); - auto hi = hi_end; + HI hi_end = mesh.halfedge(fi); + HI hi = hi_end; do { EI edge_index = mesh.edge(hi); // is edge new created - constrained? @@ -587,7 +719,7 @@ void priv::set_face_type(FaceTypeMap &face_type_map, shape_mesh.point(VI(j)), shape_mesh.point(VI(i + 1)), shape_mesh.point(VI(j + 1)), p); - is_inside = abcp == CGAL::POSITIVE; + is_inside = abcp == CGAL::NEGATIVE; } else if (i_from < i_to || (i_from == i_to && type_from < type_to)) { // TODO: check that it is continous indices of contour bool is_last = shape_from.is_first() && shape_to.is_last() && @@ -601,16 +733,7 @@ void priv::set_face_type(FaceTypeMap &face_type_map, } if (is_inside) { - // Is this face oriented towards p or away from p? - const auto &a = mesh.point(mesh.source(hi)); - const auto &b = mesh.point(mesh.target(hi)); - const auto &c = mesh.point(mesh.target(mesh.next(hi))); - - Vec3f a_(a.x(), a.y(), a.z()); - Vec3f p_ = project.project(a_); - CGAL::Epick::Point_3 p{p_.x(), p_.y(), p_.z()}; - auto abcp = CGAL::orientation(a, b, c, p); - if (abcp == CGAL::POSITIVE) + if (is_toward_projection(fi, mesh, project)) face_type = FaceType::inside; else is_inside = false; @@ -625,7 +748,27 @@ void priv::set_face_type(FaceTypeMap &face_type_map, } } -void priv::flood_fill_inner(const CutMesh &mesh, FaceTypeMap &face_type_map) +bool priv::is_toward_projection(FI fi, + const CutMesh &mesh, + const Project &projection) +{ + HI hi = mesh.halfedge(fi); + const auto &a = mesh.point(mesh.source(hi)); + const auto &b = mesh.point(mesh.target(hi)); + const auto &c = mesh.point(mesh.target(mesh.next(hi))); + + Vec3f a_(a.x(), a.y(), a.z()); + Vec3f p_ = projection.project(a_); + + CGAL::Epick::Point_3 p{p_.x(), p_.y(), p_.z()}; + + return CGAL::orientation(a, b, c, p) == CGAL::NEGATIVE; +} + + +void priv::flood_fill_inner(const CutMesh &mesh, + const Project &projection, + FaceTypeMap &face_type_map) { for (FI fi : mesh.faces()) { if (face_type_map[fi] != FaceType::not_constrained) continue; @@ -663,9 +806,15 @@ void priv::flood_fill_inner(const CutMesh &mesh, FaceTypeMap &face_type_map) HI hi_end = hi; do { FI fi_opposite = mesh.face(mesh.opposite(hi)); - FaceType side = face_type_map[fi_opposite]; - if (side == FaceType::not_constrained) - queue.emplace(fi_opposite); + FaceType& side = face_type_map[fi_opposite]; + if (side == FaceType::not_constrained) { + if (is_toward_projection(fi_opposite, mesh, projection)) { + queue.emplace(fi_opposite); + } else { + // Is in opposit direction + side = FaceType::outside; + } + } hi = mesh.next(hi); } while (hi != hi_end); } @@ -712,6 +861,13 @@ void priv::Visitor::intersection_point_detected(std::size_t i_id, intersection_ptr = &edge_shape_map[shape.edge(h_f)]; if (sdim == 0) vert_shape_map[object.target(h_e)] = intersection_ptr; } + + if (intersection_ptr->point_index == std::numeric_limits::max()) { + // there is unexpected intersection + // Top (or Bottom) shape contour edge (or vertex) intersection + // Suggest to change projection min/max limits + *is_valid = false; + } intersections[i_id] = intersection_ptr; } @@ -721,7 +877,8 @@ void priv::Visitor::new_vertex_added(std::size_t i_id, VI v, const CutMesh &tm) assert(i_id < intersections.size()); const IntersectingElement *intersection_ptr = intersections[i_id]; assert(intersection_ptr != nullptr); - assert(intersection_ptr->point_index != std::numeric_limits::max()); + // intersection was not filled in function intersection_point_detected + //assert(intersection_ptr->point_index != std::numeric_limits::max()); vert_shape_map[v] = intersection_ptr; } @@ -895,14 +1052,14 @@ SurfaceCut priv::create_index_triangle_set(const std::vector &faces, } -SurfaceCut::CutType priv::create_cut(const std::vector &outlines, +SurfaceCut::CutContour priv::create_cut(const std::vector &outlines, const CutMesh &mesh, const ReductionMap &reduction_map, const ConvertMap &v2v) { using Index = SurfaceCut::Index; - SurfaceCut::CutType cut; - SurfaceCut::CutType unclosed_cut; + SurfaceCut::CutContour cut; + SurfaceCut::CutContour unclosed_cut; for (HI hi : outlines) { VI vi_s = mesh.source(hi); VI vi_t = mesh.target(hi); @@ -976,49 +1133,194 @@ SurfaceCut::CutType priv::create_cut(const std::vector &outlines, return cut; } -SurfaceCuts priv::create_surface_cut(const CutMesh &mesh, - const ExPolygons &shapes, - const ReductionMap &reduction_map, - FaceTypeMap &face_type_map, - ConvertMap &convert_map) +priv::CutAOIs priv::create_cut_area_of_interests(const CutMesh &mesh, + const ExPolygons &shapes, + FaceTypeMap &face_type_map) { - // faces from one surface cut - std::vector faces; - // IMPROVE: Size can't be greater but it is too big. - faces.reserve(mesh.faces().size()); - std::vector outlines; - // IMPROVE: Create better guess of size - size_t max_outline_count = mesh.faces().size()/2; - outlines.reserve(max_outline_count); + // IMPROVE: Create better heuristic for count. + size_t faces_per_cut = mesh.faces().size() / shapes.size(); + size_t outlines_per_cut = faces_per_cut / 2; + size_t cuts_per_model = shapes.size() * 2; + CutAOIs result; + result.reserve(cuts_per_model); + + // It is faster to use one queue for all cuts + std::queue process; + for (FI fi : mesh.faces()) { + if (face_type_map[fi] != FaceType::inside) continue; + + CutAOI cut; + std::vector &faces = cut.first; + std::vector &outlines = cut.second; + + // faces for one surface cut + faces.reserve(faces_per_cut); + // outline for one surface cut + outlines.reserve(outlines_per_cut); + + assert(process.empty()); + // Process queue of faces to separate to surface_cut + process.push(fi); + collect_surface_data(process, faces, outlines, face_type_map, mesh); + + assert(!faces.empty()); + assert(!outlines.empty()); + result.emplace_back(std::move(cut)); + } + return result; +} + +void priv::filter_cuts(CutAOIs &cuts, + const CutMesh &mesh, + const ExPolygons &shapes, + const Project &projection, + const VertexShapeMap &vert_shape_map) +{ + auto get_point = [&shapes](const IntersectingElement &intersection) -> Point { + assert(intersection.vertex_base != std::numeric_limits::max()); + assert(intersection.point_index != std::numeric_limits::max()); + size_t offset = 0; + for (const ExPolygon &s : shapes) { + if (offset == intersection.vertex_base) { + assert(s.contour.size() > intersection.point_index); + return s.contour[intersection.point_index]; + } + // *2 .. see description of IntersectingElement::vertex_base + offset += 2*s.contour.size(); + assert(offset <= intersection.vertex_base); + + for (const Polygon &h : s.holes) { + if (offset == intersection.vertex_base) { + assert(h.points.size() > intersection.point_index); + return h.points[intersection.point_index]; + } + // *2 .. see description of IntersectingElement::vertex_base + offset += 2*h.points.size(); + assert(offset <= intersection.vertex_base); + } + } + + // index is out of shape + assert(false); + return Point{}; + }; + + struct CutIndex + { + // index in vector into cuts + size_t cut_index = std::numeric_limits::max(); + // vertex index inside of mesh + VI vi; + }; + size_t count = count_points(shapes); + // each source point from shapes could has only one nearest projection + std::vector indices(count); + + // flags which cut is not first + std::vector del_cuts(cuts.size(), false); + + // check whether vertex is behind another cut + auto is_behind = [&vert_shape_map, &indices, &del_cuts, &get_point, + &projection, &mesh] + (VI vi, size_t cut_index) -> bool { + const IntersectingElement *i = vert_shape_map[vi]; + + // Is vertex made by corefine? + if (i == nullptr) return false; + + assert(i->vertex_base != std::numeric_limits::max()); + assert(i->vertex_base%2 == 0); + assert(i->point_index != std::numeric_limits::max()); + assert(i->attr != (unsigned char)IntersectingElement::Type::undefined); + + // Use only straigh edge + if (i->get_type() != IntersectingElement::Type::edge_1) + return false; + + + size_t index = i->vertex_base/2 + i->point_index; + CutIndex &ci = indices[index]; + + // is first cut for vertex OR + // is remembred cut is deleted? + if (ci.cut_index == std::numeric_limits::max() || + del_cuts[ci.cut_index] ) { + ci.cut_index = cut_index; + ci.vi = vi; + return false; + } + + if (ci.cut_index == cut_index) { + assert(ci.vi == vi); + return false; + } + + // compare distances of vertices + Point p = get_point(*i); + Vec3f source_point = projection.project(p).first; + const auto &prev = mesh.point(ci.vi); + Vec3f prev_point(prev.x(), prev.y(), prev.z()); + float prev_sq_norm = (source_point - prev_point).squaredNorm(); + + const auto &act = mesh.point(vi); + Vec3f act_point(act.x(), act.y(), act.z()); + float act_sq_norm = (source_point - act_point).squaredNorm(); + + if (act_sq_norm > prev_sq_norm) { + del_cuts[cut_index] = true; + return true; + } + + // previous cut is behind actual one + del_cuts[ci.cut_index] = true; + ci.cut_index = cut_index; + ci.vi = vi; + return false; + }; + + // filter top one cuts + for (const CutAOI &cut : cuts) { + size_t cut_index = &cut - &cuts.front(); + const std::vector &outlines = cut.second; + for (HI hi : outlines) { + if (is_behind(mesh.source(hi), cut_index) || + is_behind(mesh.target(hi), cut_index)) + break; + } + } + + // remove flagged cuts + for (size_t i = del_cuts.size(); i > 0; --i) { + size_t index = i - 1; + if (del_cuts[index]) + cuts.erase(cuts.begin() + index); + } +} + + +SurfaceCuts priv::create_surface_cuts(const CutAOIs &cuts, + const CutMesh &mesh, + const ReductionMap &reduction_map, + ConvertMap &convert_map) +{ // initialize convert_map to MAX values for (VI vi : mesh.vertices()) convert_map[vi] = std::numeric_limits::max(); - std::queue process; - - SurfaceCuts result; - for (FI fi: mesh.faces()) { - if (face_type_map[fi] != FaceType::inside) continue; - - faces.clear(); - outlines.clear(); - - assert(process.empty()); - // Process queue of faces to separate to surface_cut - process.push(fi); - collect_surface_data(process, faces, outlines, face_type_map, mesh); + SurfaceCuts result; + for (const CutAOI &cut : cuts) { + const std::vector& faces = cut.first; + const std::vector &outlines = cut.second; + // convert_map could be used separately for each surface cut. + // But it is moore faster to use one memory allocation for them all. SurfaceCut sc = create_index_triangle_set(faces, outlines.size(), mesh, reduction_map, convert_map); // connect outlines - sc.cut = create_cut(outlines, mesh, reduction_map, convert_map); - - // TODO: create vertex2contour map - + sc.contours = create_cut(outlines, mesh, reduction_map, convert_map); result.emplace_back(std::move(sc)); } - return result; } @@ -1065,5 +1367,3 @@ void priv::store(const SurfaceCuts &cut, const std::string &file_prefix) { its_write_obj(c, file.c_str()); } } - -#endif // ENABLE_NEW_CGAL diff --git a/src/libslic3r/CutSurface.hpp b/src/libslic3r/CutSurface.hpp index dc8400d65..64d46fdd9 100644 --- a/src/libslic3r/CutSurface.hpp +++ b/src/libslic3r/CutSurface.hpp @@ -11,29 +11,6 @@ namespace Slic3r{ -/// -/// Address of contour point in ExPolygon -/// -struct ExPolygonPoint -{ - // Index of Polygon in ExPolygon - // 0 .. ExPolygon::contour - // N .. ExPolygon::hole[N-1] - size_t poly_id; - - // Index of point in Polygon - size_t index; -}; - -/// -/// Address of contour point in ExPolygons -/// -struct ExPolygonsPoint : public ExPolygonPoint -{ - // Index of ExPolygon in ExPolygons - size_t expoly_id; -}; - /// /// Represents cutted surface from object /// Extend index triangle set by outlines @@ -43,11 +20,11 @@ struct SurfaceCut : public indexed_triangle_set // connected cutted surface indexed_triangle_set mesh; - // verticex index(index to mesh vertices) + // vertex indices(index to mesh vertices) using Index = unsigned int; - using CutType = std::vector>; + using CutContour = std::vector>; // list of circulated open surface - CutType cut; + CutContour contours; // Conversion map from vertex index to contour point // Could be used for filtration of surface cuts @@ -78,5 +55,14 @@ SurfaceCuts cut_surface(const indexed_triangle_set &model, const ExPolygons &shapes, const Emboss::IProject &projection); +/// +/// Create model from surface cuts by projection +/// +/// Surfaces from model +/// Way of emboss +/// Mesh +indexed_triangle_set cuts2model(const SurfaceCuts &cuts, + const Emboss::IProject &projection); + } // namespace Slic3r #endif // slic3r_CutSurface_hpp_ diff --git a/src/libslic3r/Emboss.cpp b/src/libslic3r/Emboss.cpp index e67bf4d8e..07be4e026 100644 --- a/src/libslic3r/Emboss.cpp +++ b/src/libslic3r/Emboss.cpp @@ -47,8 +47,7 @@ public: bool Private::is_valid(const Emboss::FontFile &font, unsigned int index) { if (font.data == nullptr) return false; if (font.data->empty()) return false; - if (font.count == 0) return false; - if (index >= font.count) return false; + if (index >= font.infos.size()) return false; return true; } @@ -512,9 +511,7 @@ std::unique_ptr Emboss::create_font_file( infos.emplace_back(FontFile::Info{ascent, descent, linegap, units_per_em}); } - - return std::make_unique( - std::move(data), collection_size, std::move(infos)); + return std::make_unique(std::move(data), std::move(infos)); } std::unique_ptr Emboss::create_font_file(const char *file_path) @@ -693,7 +690,7 @@ void Emboss::apply_transformation(const FontProp &font_prop, bool Emboss::is_italic(const FontFile &font, unsigned int font_index) { - if (font_index >= font.count) return false; + if (font_index >= font.infos.size()) return false; std::optional font_info_opt = Private::load_font_info(font.data->data(), font_index); if (!font_info_opt.has_value()) return false; @@ -901,3 +898,17 @@ Transform3d Emboss::create_transformation_onto_surface(const Vec3f &position, transform.rotate(up_rot); return transform; } + + +// OrthoProject + +std::pair Emboss::OrthoProject::project(const Point &p) const { + Vec3d front(p.x(), p.y(), 0.); + Vec3f front_tr = (m_matrix * front).cast(); + return std::make_pair(front_tr, project(front_tr)); +} + +Vec3f Emboss::OrthoProject::project(const Vec3f &point) const +{ + return point + m_direction; +} diff --git a/src/libslic3r/Emboss.hpp b/src/libslic3r/Emboss.hpp index edc093b97..143890108 100644 --- a/src/libslic3r/Emboss.hpp +++ b/src/libslic3r/Emboss.hpp @@ -68,9 +68,6 @@ public: // data are stored inside unique_ptr std::unique_ptr> data; - // count of fonts when data are collection of fonts - unsigned int count; - struct Info { // vertical position is "scale*(ascent - descent + lineGap)" @@ -83,26 +80,22 @@ public: std::vector infos; FontFile(std::unique_ptr> data, - unsigned int count, std::vector &&infos) - : data(std::move(data)) - , count(count) - , infos(std::move(infos)) + : data(std::move(data)), infos(std::move(infos)) { assert(this->data != nullptr); assert(!this->data->empty()); - assert(count == this->infos.size()); } + bool operator==(const FontFile &other) const { - if (count != other.count || data->size() != other.data->size()) + if (data->size() != other.data->size()) return false; //if(*data != *other.data) return false; - for (unsigned int i = 0; i < count; i++) + for (size_t i = 0; i < infos.size(); i++) if (infos[i].ascent != other.infos[i].ascent || infos[i].descent == other.infos[i].descent || infos[i].linegap == other.infos[i].linegap) return false; - return true; } }; @@ -263,7 +256,19 @@ public: Vec3f project(const Vec3f &point) const override{ return core->project(point); } + }; + class OrthoProject: public Emboss::IProject { + Transform3d m_matrix; + // size and direction of emboss for ortho projection + Vec3f m_direction; + public: + OrthoProject(Transform3d matrix, Vec3f direction) + : m_matrix(matrix), m_direction(direction) + {} + // Inherited via IProject + std::pair project(const Point &p) const override; + Vec3f project(const Vec3f &point) const override; }; }; diff --git a/src/slic3r/GUI/Gizmos/GLGizmoEmboss.cpp b/src/slic3r/GUI/Gizmos/GLGizmoEmboss.cpp index 8c7483b12..e682a50de 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoEmboss.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoEmboss.cpp @@ -271,8 +271,10 @@ bool GLGizmoEmboss::on_mouse_for_translate(const wxMouseEvent &mouse_event) return false; } + // Dragging starts out of window + if (!m_dragging_mouse_offset.has_value()) return false; + const Camera &camera = wxGetApp().plater()->get_camera(); - assert(m_dragging_mouse_offset.has_value()); Vec2d offseted_mouse = mouse_pos + *m_dragging_mouse_offset; auto hit = m_raycast_manager.unproject(offseted_mouse, camera, &condition); if (!hit.has_value()) { @@ -910,6 +912,202 @@ void GLGizmoEmboss::select_stored_font_item() m_stored_font_item = it->second; } +/// +/// choose valid source object for cut surface +/// +/// Source model volume +/// triangle set OR nullptr +const indexed_triangle_set *get_source_object(const ModelVolume* mv) { + if (mv == nullptr) return nullptr; + if (!mv->text_configuration.has_value()) return nullptr; + const auto &volumes = mv->get_object()->volumes; + // no other volume in object + if (volumes.size() <= 1) return nullptr; + + // Improve create object from part or use gl_volume + // Get first model part in object + for (const ModelVolume *v : volumes) { + if (v->id() == mv->id()) continue; + if (!v->is_model_part()) continue; + const TriangleMesh &tm = v->mesh(); + if (tm.empty()) continue; + return &tm.its; + } + + // No valid source volume in objct volumes + return nullptr; +} + +/// +/// Choose valid source Volume to project on(cut surface from). +/// +/// Volume with text +/// ModelVolume to project on +const ModelVolume *get_source_volume(const ModelVolume *text_volume) +{ + if (text_volume == nullptr) return nullptr; + if (!text_volume->text_configuration.has_value()) return nullptr; + const auto &volumes = text_volume->get_object()->volumes; + // no other volume in object + if (volumes.size() <= 1) return nullptr; + + // Improve create object from part or use gl_volume + // Get first model part in object + for (const ModelVolume *v : volumes) { + if (v->id() == text_volume->id()) continue; + if (!v->is_model_part()) continue; + const TriangleMesh &tm = v->mesh(); + if (tm.empty()) continue; + if (tm.its.empty()) continue; + return v; + } + + // No valid source volume in objct volumes + return nullptr; +} + +// search area range for cut surface +struct SurfaceConfig +{ + // zero is after move on surface + depth move + float min = -10.f; // [in mm] + float max = 100.f;// [in mm] +}; +static SurfaceConfig surface_cfg; + +double get_shape_scale(const FontProp &fp, const Emboss::FontFile &ff) +{ + const auto &cn = fp.collection_number; + unsigned int font_index = (cn.has_value()) ? *cn : 0; + int unit_per_em = ff.infos[font_index].unit_per_em; + double scale = fp.size_in_mm / unit_per_em; + // Shape is scaled for store point coordinate as integer + return scale * Emboss::SHAPE_SCALE; +} + +/// +/// Create cut_projection for cut surface +/// +/// Volume transformation in object +/// Configuration of embossig +/// Font file for size --> unit per em +/// Bounding box of shape to center result volume +/// Orthogonal cut_projection +std::unique_ptr create_projection_for_cut( + Transform3d tr, + const TextConfiguration &tc, + const Emboss::FontFile &ff, + const BoundingBox shape_bb) +{ + double z_dir = -(surface_cfg.max - surface_cfg.min); + Vec3f dir = (tr * Vec3d(0., 0., z_dir)).cast(); + + tr.scale(get_shape_scale(tc.font_item.prop, ff)); + + // Text aligmnemnt to center 2D + Vec2d move = -(shape_bb.max + shape_bb.min).cast() / 2.; + tr.translate(Vec3d(move.x(), move.y(), -surface_cfg.min)); + + return std::make_unique(tr, dir); +} + +#include "libslic3r/CutSurface.hpp" +/// +/// Create tranformation for emboss +/// +/// True .. raise, False .. engrave +/// Text configuration +/// Text voliume transformation inside object +/// Cutted surfaces from model +/// Projection +static std::unique_ptr create_emboss_projection( + bool is_outside, + const TextConfiguration &tc, + Transform3d tr, + SurfaceCuts &cuts) +{ + // Offset of clossed side to model + const float surface_offset = 1e-3; // [in mm] + + const FontProp &fp = tc.font_item.prop; + float front_move, back_move; + if (is_outside) { + front_move = fp.emboss; + back_move = -surface_offset; + } else { + front_move = surface_offset; + back_move = -fp.emboss; + } + Matrix3d rot = tr.rotation(); + + float z_dir = back_move - front_move; + Vec3f dir = (rot * Vec3d(0., 0., z_dir)).cast(); + + // move to front distance + Vec3f move = (rot * Vec3d(0., 0., front_move)).cast(); + for (SurfaceCut &cut : cuts) + its_translate(cut, move); + + // Transformation is not used + return std::make_unique(Transform3d::Identity(), dir); +} + +void GLGizmoEmboss::use_surface() { + const ModelVolume *source = get_source_volume(m_volume); + if (source == nullptr) return; + + auto ffc = m_font_manager.get_font().font_file_with_cache; + if (!ffc.has_value()) return; + + const TextConfiguration &tc = *m_volume->text_configuration; + const char *text = tc.text.c_str(); + const FontProp& fp = tc.font_item.prop; + ExPolygons shapes = Emboss::text2shapes(ffc, text, fp); + + if (shapes.empty()) return; + if (shapes.front().contour.empty()) return; + + BoundingBox bb = get_extents(shapes); + + Transform3d input_tr = m_volume->get_matrix(); + if (tc.fix_3mf_tr.has_value()) + input_tr = input_tr * tc.fix_3mf_tr->inverse(); + Transform3d cut_projection_tr = source->get_matrix().inverse() * input_tr; + + const Emboss::FontFile &ff = *ffc.font_file; + auto cut_projection = create_projection_for_cut(cut_projection_tr, tc, ff, bb); + if (cut_projection == nullptr) return; + + SurfaceCuts cuts = cut_surface(source->mesh().its, shapes, *cut_projection); + if (cuts.empty()) return; + + bool is_outside = m_volume->is_model_part(); + assert(is_outside || m_volume->is_negative_volume() || m_volume->is_modifier()); + + //Transform3d wanted_tr = ; + + // NOTE! - It needs to translate cuts + Transform3d tr = source->get_matrix().inverse() * input_tr; + auto projection = create_emboss_projection(is_outside, tc, tr, cuts); + if (projection == nullptr) return; + + indexed_triangle_set new_its = cuts2model(cuts, *projection); + its_write_obj(new_its, "C:/data/temp/projected.obj"); // only debug + + TriangleMesh tm(std::move(new_its)); + // center triangle mesh + Vec3d shift = tm.bounding_box().center(); + tm.translate(-shift.cast()); + + Transform3d trafo = Transform3d::Identity(); + trafo.translate(shift); + trafo = source->get_matrix() * trafo; + m_volume->set_transformation(trafo); + m_volume->set_mesh(std::move(tm)); + m_volume->set_new_unique_id(); + wxGetApp().plater()->canvas3D()->reload_scene(true); +} + void GLGizmoEmboss::draw_window() { #ifdef ALLOW_DEBUG_MODE @@ -955,6 +1153,17 @@ void GLGizmoEmboss::draw_window() if (ImGui::Button(_u8L("Close").c_str())) close(); + ImGui::SameLine(); + if (ImGui::Button(_u8L("UseSurface").c_str())) + use_surface(); + + ImGui::SameLine(); + ImGui::SetNextItemWidth(150); + ImGui::InputFloat("##min_cut", &surface_cfg.min); + ImGui::SameLine(); + ImGui::SetNextItemWidth(150); + ImGui::InputFloat("##max_cut", &surface_cfg.max); + // Option to create text volume when reselecting volumes m_imgui->disabled_begin(!exist_font_file); if (m_volume == nullptr) { @@ -1901,10 +2110,10 @@ void GLGizmoEmboss::draw_advanced() ", lineGap=" + std::to_string(font_info.linegap) + ", unitPerEm=" + std::to_string(font_info.unit_per_em) + ", cache(" + std::to_string(cache_size) + " glyphs)"; - if (font_file->count > 1) { + if (font_file->infos.size() > 1) { unsigned int collection = font_prop.collection_number.has_value() ? *font_prop.collection_number : 0; - ff_property += ", collect=" + std::to_string(collection+1) + "/" + std::to_string(font_file->count); + ff_property += ", collect=" + std::to_string(collection+1) + "/" + std::to_string(font_file->infos.size()); } m_imgui->text_colored(ImGuiWrapper::COL_GREY_DARK, ff_property); #endif // SHOW_FONT_FILE_PROPERTY @@ -2002,14 +2211,14 @@ void GLGizmoEmboss::draw_advanced() } // when more collection add selector - if (font_file->count > 1) { + if (font_file->infos.size() > 1) { ImGui::Text("%s", tr.collection.c_str()); ImGui::SameLine(m_gui_cfg->advanced_input_offset); ImGui::SetNextItemWidth(m_gui_cfg->advanced_input_width); unsigned int selected = font_prop.collection_number.has_value() ? *font_prop.collection_number : 0; if (ImGui::BeginCombo("## Font collection", std::to_string(selected).c_str())) { - for (unsigned int i = 0; i < font_file->count; ++i) { + for (unsigned int i = 0; i < font_file->infos.size(); ++i) { ImGui::PushID(1 << (10 + i)); bool is_selected = (i == selected); if (ImGui::Selectable(std::to_string(i).c_str(), is_selected)) { diff --git a/src/slic3r/GUI/Gizmos/GLGizmoEmboss.hpp b/src/slic3r/GUI/Gizmos/GLGizmoEmboss.hpp index c1de15f95..f3b9e7ca6 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoEmboss.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoEmboss.hpp @@ -112,6 +112,9 @@ private: void do_translate(const Vec3d& relative_move); void do_rotate(float relative_z_angle); + // TODO: only for developing - remove it + void use_surface(); + /// /// Reversible input float with option to restor default value /// TODO: make more general, static and move to ImGuiWrapper diff --git a/src/slic3r/GUI/Jobs/EmbossJob.cpp b/src/slic3r/GUI/Jobs/EmbossJob.cpp index 542103d39..a31932d5e 100644 --- a/src/slic3r/GUI/Jobs/EmbossJob.cpp +++ b/src/slic3r/GUI/Jobs/EmbossJob.cpp @@ -314,6 +314,7 @@ TriangleMesh priv::create_mesh(const char *text, const auto &cn = font_prop.collection_number; unsigned int font_index = (cn.has_value()) ? *cn : 0; + assert(font_index < font.font_file->infos.size()); int unit_per_em = font.font_file->infos[font_index].unit_per_em; float scale = font_prop.size_in_mm / unit_per_em; float depth = font_prop.emboss / scale; diff --git a/tests/libslic3r/test_emboss.cpp b/tests/libslic3r/test_emboss.cpp index 38f91c23e..b0c980784 100644 --- a/tests/libslic3r/test_emboss.cpp +++ b/tests/libslic3r/test_emboss.cpp @@ -296,7 +296,6 @@ TEST_CASE("Italic check", "[Emboss]") } #endif // not __APPLE__ -#if ENABLE_NEW_CGAL #include "libslic3r/CutSurface.hpp" TEST_CASE("Cut surface", "[]") { @@ -304,6 +303,7 @@ TEST_CASE("Cut surface", "[]") char letter = '%'; float flatness = 2.; unsigned int font_index = 0; // collection + float z_depth = 50.f; // projection size auto font = Emboss::create_font_file(font_path.c_str()); REQUIRE(font != nullptr); @@ -315,8 +315,10 @@ TEST_CASE("Cut surface", "[]") ExPolygons shape = glyph->shape; REQUIRE(!shape.empty()); - float z_depth = 50.f; - Emboss::ProjectZ projection(z_depth); + Transform3d tr = Transform3d::Identity(); + tr.translate(Vec3d(0., 0., z_depth)); + tr.scale(Emboss::SHAPE_SCALE); + Emboss::OrthoProject cut_projection(tr, Vec3f(0.f, 0.f, -50)); auto object = its_make_cube(782 - 49 + 50, 724 + 10 + 50, 5); its_translate(object, Vec3f(49 - 25, -10 - 25, 2.5)); @@ -324,8 +326,16 @@ TEST_CASE("Cut surface", "[]") its_translate(cube2, Vec3f(100, -40, 40)); its_merge(object, std::move(cube2)); - auto surfaces = cut_surface(object, shape, projection); + auto surfaces = cut_surface(object, shape, cut_projection); CHECK(!surfaces.empty()); + + Emboss::OrthoProject projection(Transform3d::Identity(), Vec3f(0.f, 0.f, -10.f)); + for (auto &surface : surfaces) + its_translate(surface, Vec3f(0.f, 0.f, 10)); + + indexed_triangle_set its = cuts2model(surfaces, projection); + CHECK(!its.empty()); + its_write_obj(its, "C:/data/temp/projected.obj"); } @@ -1080,4 +1090,3 @@ TEST_CASE("Emboss extrude cut", "[Emboss-Cut]") // REQUIRE(!MeshBoolean::cgal::does_self_intersect(cube)); } -#endif // ENABLE_NEW_CGAL