From 8511b280bf26c256ae58af4cb847c0afcacd75b9 Mon Sep 17 00:00:00 2001 From: Filip Sykala - NTB T15p Date: Tue, 11 Oct 2022 13:35:52 +0200 Subject: [PATCH] Heal shape with points close to line (after conversion to floating point it is on the other side of line) ExPolygons indexing (fixed) --- src/libslic3r/CMakeLists.txt | 2 + src/libslic3r/Emboss.cpp | 397 ++++++++++++++++++++++------ src/libslic3r/Emboss.hpp | 20 +- src/libslic3r/ExPolygon.hpp | 40 ++- src/libslic3r/ExPolygonsIndex.cpp | 82 ++++++ src/libslic3r/ExPolygonsIndex.hpp | 74 ++++++ src/libslic3r/Line.hpp | 1 + tests/data/points_close_to_line.svg | 3 + tests/libslic3r/test_emboss.cpp | 20 +- tests/libslic3r/test_polygon.cpp | 31 +++ 10 files changed, 571 insertions(+), 99 deletions(-) create mode 100644 src/libslic3r/ExPolygonsIndex.cpp create mode 100644 src/libslic3r/ExPolygonsIndex.hpp create mode 100644 tests/data/points_close_to_line.svg diff --git a/src/libslic3r/CMakeLists.txt b/src/libslic3r/CMakeLists.txt index b136953b9..af10b476a 100644 --- a/src/libslic3r/CMakeLists.txt +++ b/src/libslic3r/CMakeLists.txt @@ -48,6 +48,8 @@ set(SLIC3R_SOURCES enum_bitmask.hpp ExPolygon.cpp ExPolygon.hpp + ExPolygonsIndex.cpp + ExPolygonsIndex.hpp Extruder.cpp Extruder.hpp ExtrusionEntity.cpp diff --git a/src/libslic3r/Emboss.cpp b/src/libslic3r/Emboss.cpp index ef357c4ff..c6dd8a3a5 100644 --- a/src/libslic3r/Emboss.cpp +++ b/src/libslic3r/Emboss.cpp @@ -14,37 +14,53 @@ #include "ClipperUtils.hpp" // for boldness - polygon extend(offset) +// to heal shape +#include "ExPolygonsIndex.hpp" +#include "libslic3r/AABBTreeLines.hpp" // search structure for found close points +#include "libslic3r/Line.hpp" + using namespace Slic3r; double Emboss::SHAPE_SCALE = 0.001;//SCALING_FACTOR; // do not expose out of this file stbtt_ data types -class Private -{ -public: - Private() = delete; - static bool is_valid(const Emboss::FontFile &font, unsigned int index); - static std::optional load_font_info(const unsigned char *data, unsigned int index = 0); - static std::optional get_glyph(const stbtt_fontinfo &font_info, int unicode_letter, float flatness); +namespace priv{ - // take glyph from cache - static const Emboss::Glyph* get_glyph(int unicode, const Emboss::FontFile &font, const FontProp &font_prop, +bool is_valid(const Emboss::FontFile &font, unsigned int index); +std::optional load_font_info(const unsigned char *data, unsigned int index = 0); +std::optional get_glyph(const stbtt_fontinfo &font_info, int unicode_letter, float flatness); + +// take glyph from cache +const Emboss::Glyph* get_glyph(int unicode, const Emboss::FontFile &font, const FontProp &font_prop, Emboss::Glyphs &cache, std::optional &font_info_opt); - static EmbossStyle create_style(std::wstring name, std::wstring path); +EmbossStyle create_style(std::wstring name, std::wstring path); + +// scale and convert float to int coordinate +Point to_point(const stbtt__point &point); + +// helpr for heal shape +bool remove_same_neighbor(Slic3r::Points &points); +bool remove_same_neighbor(Slic3r::Polygons &polygons); +bool remove_same_neighbor(ExPolygons &expolygons); +bool divide_segments_for_close_point(ExPolygons &expolygons, double distance = .6); + +// NOTE: expolygons can't contain same_neighbor +Points collect_close_points(const ExPolygons &expolygons, double distance = .6); + +const Points pts_2x2({Point(0, 0), Point(1, 0), Point(1, 1), Point(0, 1)}); +const Points pts_3x3({Point(-1, -1), Point(1, -1), Point(1, 1), Point(-1, 1)}); - // scale and convert float to int coordinate - static Point to_point(const stbtt__point &point); }; -bool Private::is_valid(const Emboss::FontFile &font, unsigned int index) { +bool priv::is_valid(const Emboss::FontFile &font, unsigned int index) { if (font.data == nullptr) return false; if (font.data->empty()) return false; if (index >= font.infos.size()) return false; return true; } -std::optional Private::load_font_info( +std::optional priv::load_font_info( const unsigned char *data, unsigned int index) { int font_offset = stbtt_GetFontOffsetForIndex(data, index); @@ -62,64 +78,271 @@ std::optional Private::load_font_info( return font_info; } +bool priv::remove_same_neighbor(Slic3r::Points &points) +{ + if (points.empty()) return false; + auto last = std::unique(points.begin(), points.end()); + if (last == points.end()) return false; + points.erase(last, points.end()); + // clear points without area + if (points.size() <= 2) points.clear(); + return true; +} + +bool priv::remove_same_neighbor(Slic3r::Polygons &polygons) { + if (polygons.empty()) return false; + bool exist = false; + for (Slic3r::Polygon& polygon : polygons) + exist |= remove_same_neighbor(polygon.points); + // remove empty polygons + polygons.erase( + std::remove_if(polygons.begin(), polygons.end(), + [](const Slic3r::Polygon &p) { return p.empty(); }), + polygons.end()); + return exist; +} + +bool priv::remove_same_neighbor(ExPolygons &expolygons) { + if(expolygons.empty()) return false; + bool exist = false; + for (ExPolygon &expoly : expolygons) { + exist |= remove_same_neighbor(expoly.contour.points); + Polygons &holes = expoly.holes; + for (Slic3r::Polygon &hole : holes) + exist |= remove_same_neighbor(hole.points); + // remove empy holes + holes.erase( + std::remove_if(holes.begin(), holes.end(), + [](const Slic3r::Polygon &p) { return p.empty(); }), + holes.end()); + } + // remove empty contours + expolygons.erase( + std::remove_if(expolygons.begin(), expolygons.end(), + [](const ExPolygon &p) { return p.contour.empty(); }), + expolygons.end()); + return exist; +} + +Points priv::collect_close_points(const ExPolygons &expolygons, double distance) { + if (expolygons.empty()) return {}; + if (distance < 0.) return {}; + + // IMPROVE: use int(insted of double) lines and tree + const ExPolygonsIndices ids(expolygons); + const std::vector lines = Slic3r::to_linesf(expolygons, ids.get_count()); + AABBTreeIndirect::Tree<2, double> tree = AABBTreeLines::build_aabb_tree_over_indexed_lines(lines); + // Result close points + Points res; + size_t point_index = 0; + auto collect_close = [&res, &point_index, &lines, &tree, &distance, &ids, &expolygons](const Points &pts) { + for (const Point &p : pts) { + Vec2d p_d = p.cast(); + std::vector close_lines = AABBTreeLines::all_lines_in_radius(lines, tree, p_d, distance); + for (size_t index : close_lines) { + // skip point neighbour lines indices + if (index == point_index) continue; + if (&p != &pts.front()) { + if (index == point_index - 1) continue; + } else if (index == (pts.size()-1)) continue; + + // do not doubled side point of segment + const ExPolygonsIndex id = ids.cvt(index); + const ExPolygon &expoly = expolygons[id.expolygons_index]; + const Slic3r::Polygon &poly = id.is_contour() ? expoly.contour : expoly.holes[id.hole_index()]; + const Points &poly_pts = poly.points; + const Point &line_a = poly_pts[id.point_index]; + const Point &line_b = (!ids.is_last_point(id)) ? poly_pts[id.point_index + 1] : poly_pts.front(); + assert(line_a == lines[index].a.cast()); + assert(line_b == lines[index].b.cast()); + if (p == line_a || p == line_b) continue; + res.push_back(p); + } + ++point_index; + } + }; + for (const ExPolygon &expoly : expolygons) { + collect_close(expoly.contour.points); + for (const Slic3r::Polygon &hole : expoly.holes) + collect_close(hole.points); + } + if (res.empty()) return {}; + std::sort(res.begin(), res.end()); + // only unique points + res.erase(std::unique(res.begin(), res.end()), res.end()); + return res; +} + +bool priv::divide_segments_for_close_point(ExPolygons &expolygons, double distance) +{ + if (expolygons.empty()) return false; + if (distance < 0.) return false; + + // ExPolygons can't contain same neigbours + remove_same_neighbor(expolygons); + + // IMPROVE: use int(insted of double) lines and tree + const ExPolygonsIndices ids(expolygons); + const std::vector lines = Slic3r::to_linesf(expolygons, ids.get_count()); + AABBTreeIndirect::Tree<2, double> tree = AABBTreeLines::build_aabb_tree_over_indexed_lines(lines); + using Div = std::pair; + std::vector
divs; + size_t point_index = 0; + auto check_points = [&divs, &point_index, &lines, &tree, &distance, &ids, &expolygons](const Points &pts) { + for (const Point &p : pts) { + Vec2d p_d = p.cast(); + std::vector close_lines = AABBTreeLines::all_lines_in_radius(lines, tree, p_d, distance); + for (size_t index : close_lines) { + // skip point neighbour lines indices + if (index == point_index) continue; + if (&p != &pts.front()) { + if (index == point_index - 1) continue; + } else if (index == (pts.size()-1)) continue; + + // do not doubled side point of segment + const ExPolygonsIndex id = ids.cvt(index); + const ExPolygon &expoly = expolygons[id.expolygons_index]; + const Slic3r::Polygon &poly = id.is_contour() ? expoly.contour : expoly.holes[id.hole_index()]; + const Points &poly_pts = poly.points; + const Point &line_a = poly_pts[id.point_index]; + const Point &line_b = (!ids.is_last_point(id)) ? poly_pts[id.point_index + 1] : poly_pts.front(); + assert(line_a == lines[index].a.cast()); + assert(line_b == lines[index].b.cast()); + if (p == line_a || p == line_b) continue; + + divs.emplace_back(p, index); + } + ++point_index; + } + }; + for (const ExPolygon &expoly : expolygons) { + check_points(expoly.contour.points); + for (const Slic3r::Polygon &hole : expoly.holes) + check_points(hole.points); + } + + // check if exist division + if (divs.empty()) return false; + + // sort from biggest index to zero + // to be able add points and not interupt indices + std::sort(divs.begin(), divs.end(), + [](const Div &d1, const Div &d2) { return d1.second > d2.second; }); + + auto it = divs.begin(); + // divide close line + while (it != divs.end()) { + // colect division of a line segmen + size_t index = it->second; + auto it2 = it+1; + while (it2 != divs.end() && it2->second == index) ++it2; + + ExPolygonsIndex id = ids.cvt(index); + ExPolygon &expoly = expolygons[id.expolygons_index]; + Slic3r::Polygon &poly = id.is_contour() ? expoly.contour : expoly.holes[id.hole_index()]; + Points &pts = poly.points; + size_t count = it2 - it; + + // add points into polygon to divide in place of near point + if (count == 1) { + pts.insert(pts.begin() + id.point_index + 1, it->first); + ++it; + } else { + // collect points to add into polygon + Points points; + points.reserve(count); + for (; it < it2; ++it) + points.push_back(it->first); + + // need sort by line direction + const Linef &line = lines[index]; + Vec2d dir = line.b - line.a; + // select mayorit direction + int axis = (abs(dir.x()) > abs(dir.y())) ? 0 : 1; + using Fnc = std::function; + Fnc fnc = (dir[axis] < 0) ? Fnc([axis](const Point &p1, const Point &p2) { return p1[axis] > p2[axis]; }) : + Fnc([axis](const Point &p1, const Point &p2) { return p1[axis] < p2[axis]; }) ; + std::sort(points.begin(), points.end(), fnc); + + // use only unique points + points.erase(std::unique(points.begin(), points.end()), points.end()); + + // divide line by adding points into polygon + pts.insert(pts.begin() + id.point_index + 1, + points.begin(), points.end()); + } + assert(it == it2); + } + return true; +} + ExPolygons Emboss::heal_shape(const Polygons &shape) { // When edit this code check that font 'ALIENATE.TTF' and glyph 'i' still work // fix of self intersections // http://www.angusj.com/delphi/clipper/documentation/Docs/Units/ClipperLib/Functions/SimplifyPolygon.htm ClipperLib::Paths paths = ClipperLib::SimplifyPolygons(ClipperUtils::PolygonsProvider(shape), ClipperLib::pftNonZero); ClipperLib::CleanPolygons(paths); - Polygons polygons = to_polygons(paths); - static const Points pts_2x2({Point(0, 0), Point(1, 0), Point(1, 1), Point(0, 1)}); - static const Points pts_3x3({Point(-1, -1), Point(1, -1), Point(1, 1), Point(-1, 1)}); - + Polygons polygons = to_polygons(paths); + // do not remove all duplicits but do it better way Points duplicits = collect_duplications(to_points(polygons)); if (!duplicits.empty()) { polygons.reserve(polygons.size() + duplicits.size()); for (const Point &p : duplicits) { - Slic3r::Polygon rect_3x3(pts_3x3); + Slic3r::Polygon rect_3x3(priv::pts_3x3); rect_3x3.translate(p); polygons.push_back(rect_3x3); } } - // TTF use non zero winding number + // TrueTypeFonts use non zero winding number // https://docs.microsoft.com/en-us/typography/opentype/spec/ttch01 // https://developer.apple.com/fonts/TrueType-Reference-Manual/RM01/Chap1.html ExPolygons res = Slic3r::union_ex(polygons, ClipperLib::pftNonZero); + heal_shape(res); + return res; +} + +bool Emboss::heal_shape(ExPolygons &shape, unsigned max_iteration) +{ + if (shape.empty()) return true; Slic3r::Polygons holes; - int max_iteration = 10; - while (--max_iteration){ - Pointfs intersections = intersection_points(res); - Points duplicits = collect_duplications(to_points(res)); - if (intersections.empty() && duplicits.empty()) break; - + while (--max_iteration) { + priv::remove_same_neighbor(shape); + + Pointfs intersections = intersection_points(shape); + Points duplicits = collect_duplications(to_points(shape)); + Points close = priv::collect_close_points(shape); + if (intersections.empty() && duplicits.empty() && close.empty()) break; + holes.clear(); - holes.reserve(intersections.size() + duplicits.size()); + holes.reserve(intersections.size() + duplicits.size() + close.size()); // Fix self intersection in result by subtracting hole 2x2 - if (!intersections.empty()) { - for (const Vec2d &p : intersections) { - Slic3r::Polygon hole(pts_2x2); - Point tr(std::floor(p.x()), std::floor(p.y())); - hole.translate(tr); - holes.push_back(hole); - } + for (const Vec2d &p : intersections) { + Slic3r::Polygon hole(priv::pts_2x2); + Point tr(std::floor(p.x()), std::floor(p.y())); + hole.translate(tr); + holes.push_back(hole); } // fix duplicit points by hole 3x3 around duplicit point - if (!duplicits.empty()) { - holes.reserve(duplicits.size()); - for (const Point &p : duplicits) { - Slic3r::Polygon hole(pts_3x3); - hole.translate(p); - holes.push_back(hole); - } + for (const Point &p : duplicits) { + Slic3r::Polygon hole(priv::pts_3x3); + hole.translate(p); + holes.push_back(hole); + } + + // fix close points in simmilar way as duplicits + for (const Point &p : close) { + Slic3r::Polygon hole(priv::pts_3x3); + hole.translate(p); + holes.push_back(hole); } holes = Slic3r::union_(holes); - res = Slic3r::diff_ex(res, holes, ApplySafetyOffset::Yes); + shape = Slic3r::diff_ex(shape, holes, ApplySafetyOffset::Yes); } /* VISUALIZATION of BAD symbols for debug { @@ -129,50 +352,48 @@ ExPolygons Emboss::heal_shape(const Polygons &shape) { SVG svg("C:/data/temp/fix_self_intersection.svg", bb); svg.draw(shape); // svg.draw(polygons, "orange"); - svg.draw(res, "green"); + svg.draw(shape, "green"); svg.draw(duplicits, "lightgray", 13 / Emboss::SHAPE_SCALE); - Points duplicits3 = collect_duplications(to_points(res)); + Points duplicits3 = collect_duplications(to_points(shape)); svg.draw(duplicits3, "black", 7 / Emboss::SHAPE_SCALE); - - Pointfs pts2 = intersection_points(res); + + Pointfs pts2 = intersection_points(shape); Points pts_i; pts_i.reserve(pts2.size()); for (auto p : pts2) pts_i.push_back(p.cast()); svg.draw(pts_i, "red", 8 / Emboss::SHAPE_SCALE); } //*/ - if (max_iteration == 0) { + if (max_iteration == 0) { assert(false); - BoundingBox bb = get_extents(shape); - Point size = bb.size(); + BoundingBox bb = get_extents(shape); + Point size = bb.size(); if (size.x() < 10) bb.max.x() += 10; if (size.y() < 10) bb.max.y() += 10; - Polygon rect({ // CCW - bb.min, - {bb.max.x(), bb.min.y()}, - bb.max, - {bb.min.x(), bb.max.y()} - }); + Polygon rect({// CCW + bb.min, + {bb.max.x(), bb.min.y()}, + bb.max, + {bb.min.x(), bb.max.y()}}); - Point offset = bb.size()*0.1; - Polygon hole({ // CW - bb.min + offset, - {bb.min.x()+offset.x(), bb.max.y()-offset.y()}, - bb.max - offset, - {bb.max.x()-offset.x(), bb.min.y()+offset.y()} - }); - ExPolygon res(rect, hole); + Point offset = bb.size() * 0.1; + Polygon hole({// CW + bb.min + offset, + {bb.min.x() + offset.x(), bb.max.y() - offset.y()}, + bb.max - offset, + {bb.max.x() - offset.x(), bb.min.y() + offset.y()}}); // BAD symbol - return {res}; + shape = {ExPolygon(rect, hole)}; + return false; } - assert(intersection_points(res).empty()); - assert(collect_duplications(to_points(res)).empty()); - return res; + assert(intersection_points(shape).empty()); + assert(collect_duplications(to_points(shape)).empty()); + return true; } -std::optional Private::get_glyph(const stbtt_fontinfo &font_info, int unicode_letter, float flatness) +std::optional priv::get_glyph(const stbtt_fontinfo &font_info, int unicode_letter, float flatness) { int glyph_index = stbtt_FindGlyphIndex(&font_info, unicode_letter); if (glyph_index == 0) { @@ -234,7 +455,7 @@ std::optional Private::get_glyph(const stbtt_fontinfo &font_info, return glyph; } -const Emboss::Glyph* Private::get_glyph( +const Emboss::Glyph* priv::get_glyph( int unicode, const Emboss::FontFile & font, const FontProp & font_prop, @@ -251,14 +472,14 @@ const Emboss::Glyph* Private::get_glyph( if (!font_info_opt.has_value()) { - font_info_opt = Private::load_font_info(font.data->data(), font_index); + font_info_opt = priv::load_font_info(font.data->data(), font_index); // can load font info? if (!font_info_opt.has_value()) return nullptr; } float flatness = static_cast(font.infos[font_index].ascent * RESOLUTION / font_prop.size_in_mm); std::optional glyph_opt = - Private::get_glyph(*font_info_opt, unicode, flatness); + priv::get_glyph(*font_info_opt, unicode, flatness); // IMPROVE: multiple loadig glyph without data // has definition inside of font? @@ -297,13 +518,13 @@ const Emboss::Glyph* Private::get_glyph( return &it.first->second; } -EmbossStyle Private::create_style(std::wstring name, std::wstring path) { +EmbossStyle priv::create_style(std::wstring name, std::wstring path) { return { boost::nowide::narrow(name.c_str()), boost::nowide::narrow(path.c_str()), EmbossStyle::Type::file_path, FontProp() }; } -Point Private::to_point(const stbtt__point &point) { +Point priv::to_point(const stbtt__point &point) { return Point(static_cast(std::round(point.x / Emboss::SHAPE_SCALE)), static_cast(std::round(point.y / Emboss::SHAPE_SCALE))); } @@ -435,7 +656,7 @@ EmbossStyles Emboss::get_font_list_by_register() { if (pos >= font_name_w.size()) continue; // remove TrueType text from name font_name_w = std::wstring(font_name_w, 0, pos); - font_list.emplace_back(Private::create_style(font_name_w, path_w)); + font_list.emplace_back(priv::create_style(font_name_w, path_w)); } while (result != ERROR_NO_MORE_ITEMS); delete[] font_name; delete[] fileTTF_name; @@ -470,7 +691,7 @@ EmbossStyles Emboss::get_font_list_by_enumeration() { EmbossStyles font_list; for (const std::wstring &font_name : font_names) { - font_list.emplace_back(Private::create_style(font_name, L"")); + font_list.emplace_back(priv::create_style(font_name, L"")); } return font_list; } @@ -492,7 +713,7 @@ EmbossStyles Emboss::get_font_list_by_folder() { if (fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) continue; std::wstring file_name(fd.cFileName); // TODO: find font name instead of filename - result.emplace_back(Private::create_style(file_name, search_dir + file_name)); + result.emplace_back(priv::create_style(file_name, search_dir + file_name)); } while (::FindNextFile(hFind, &fd)); ::FindClose(hFind); } @@ -526,7 +747,7 @@ std::unique_ptr Emboss::create_font_file( std::vector infos; infos.reserve(c_size); for (unsigned int i = 0; i < c_size; ++i) { - auto font_info = Private::load_font_info(data->data(), i); + auto font_info = priv::load_font_info(data->data(), i); if (!font_info.has_value()) return nullptr; const stbtt_fontinfo *info = &(*font_info); @@ -645,10 +866,10 @@ std::optional Emboss::letter2glyph(const FontFile &font, int letter, float flatness) { - if (!Private::is_valid(font, font_index)) return {}; - auto font_info_opt = Private::load_font_info(font.data->data(), font_index); + if (!priv::is_valid(font, font_index)) return {}; + auto font_info_opt = priv::load_font_info(font.data->data(), font_index); if (!font_info_opt.has_value()) return {}; - return Private::get_glyph(*font_info_opt, letter, flatness); + return priv::get_glyph(*font_info_opt, letter, flatness); } ExPolygons Emboss::text2shapes(FontFileWithCache &font_with_cache, @@ -663,7 +884,7 @@ ExPolygons Emboss::text2shapes(FontFileWithCache &font_with_cache, const FontFile& font = *font_with_cache.font_file; unsigned int font_index = font_prop.collection_number.has_value()? *font_prop.collection_number : 0; - if (!Private::is_valid(font, font_index)) return {}; + if (!priv::is_valid(font, font_index)) return {}; const FontFile::Info& info = font.infos[font_index]; Emboss::Glyphs& cache = *font_with_cache.cache; std::wstring ws = boost::nowide::widen(text); @@ -681,7 +902,7 @@ ExPolygons Emboss::text2shapes(FontFileWithCache &font_with_cache, if (wc == '\t') { // '\t' = 4*space => same as imgui const int count_spaces = 4; - const Glyph* space = Private::get_glyph(int(' '), font, font_prop, cache, font_info_opt); + const Glyph* space = priv::get_glyph(int(' '), font, font_prop, cache, font_info_opt); if (space == nullptr) continue; cursor.x() += count_spaces * space->advance_width; continue; @@ -693,7 +914,7 @@ ExPolygons Emboss::text2shapes(FontFileWithCache &font_with_cache, auto it = cache.find(unicode); if (it == cache.end() && was_canceled != nullptr && was_canceled()) return {}; const Glyph *glyph_ptr = (it != cache.end())? &it->second : - Private::get_glyph(unicode, font, font_prop, cache, font_info_opt); + priv::get_glyph(unicode, font, font_prop, cache, font_info_opt); if (glyph_ptr == nullptr) continue; // move glyph to cursor position @@ -704,7 +925,9 @@ ExPolygons Emboss::text2shapes(FontFileWithCache &font_with_cache, cursor.x() += glyph_ptr->advance_width; expolygons_append(result, std::move(expolygons)); } - return Slic3r::union_ex(result); + result = Slic3r::union_ex(result); + heal_shape(result); + return result; } void Emboss::apply_transformation(const FontProp &font_prop, @@ -723,7 +946,7 @@ void Emboss::apply_transformation(const FontProp &font_prop, bool Emboss::is_italic(const FontFile &font, unsigned int font_index) { if (font_index >= font.infos.size()) return false; - std::optional font_info_opt = Private::load_font_info(font.data->data(), font_index); + std::optional font_info_opt = priv::load_font_info(font.data->data(), font_index); if (!font_info_opt.has_value()) return false; stbtt_fontinfo *info = &(*font_info_opt); @@ -763,14 +986,14 @@ std::string Emboss::create_range_text(const std::string &text, unsigned int font_index, bool *exist_unknown) { - if (!Private::is_valid(font, font_index)) return {}; + if (!priv::is_valid(font, font_index)) return {}; std::wstring ws = boost::nowide::widen(text); // need remove symbols not contained in font std::sort(ws.begin(), ws.end()); - auto font_info_opt = Private::load_font_info(font.data->data(), 0); + auto font_info_opt = priv::load_font_info(font.data->data(), 0); if (!font_info_opt.has_value()) return {}; const stbtt_fontinfo *font_info = &(*font_info_opt); diff --git a/src/libslic3r/Emboss.hpp b/src/libslic3r/Emboss.hpp index 9747186a3..d649f67eb 100644 --- a/src/libslic3r/Emboss.hpp +++ b/src/libslic3r/Emboss.hpp @@ -163,6 +163,20 @@ public: /// Healed shapes static ExPolygons heal_shape(const Polygons &shape); + /// + /// NOTE: call Slic3r::union_ex before this call + /// + /// Heal (read: Fix) issues in expolygons: + /// - self intersections + /// - duplicit points + /// - points close to line segments + /// + /// In/Out shape to heal + /// Heal could create another issue, + /// After healing it is checked again until shape is good or maximal count of iteration + /// True when shapes is good otherwise False + static bool heal_shape(ExPolygons &shape, unsigned max_iteration = 10); + /// /// Use data from font property to modify transformation /// @@ -191,10 +205,10 @@ public: static std::string create_range_text(const std::string &text, const FontFile &font, unsigned int font_index, bool* exist_unknown = nullptr); /// - /// calculate scale for glyph shape convert from shape points to mm + /// Calculate scale for glyph shape convert from shape points to mm /// - /// - /// + /// Property of font + /// Font data /// Conversion to mm static double get_shape_scale(const FontProp &fp, const FontFile &ff); diff --git a/src/libslic3r/ExPolygon.hpp b/src/libslic3r/ExPolygon.hpp index 9b44902f0..179d0aafa 100644 --- a/src/libslic3r/ExPolygon.hpp +++ b/src/libslic3r/ExPolygon.hpp @@ -135,16 +135,40 @@ inline Lines to_lines(const ExPolygons &src) return lines; } -inline std::vector to_unscaled_linesf(const ExPolygons &src) +// Line is from point index(see to_points) to next point. +// Next point of last point in polygon is first polygon point. +inline Linesf to_linesf(const ExPolygons &src, uint32_t count_lines = 0) { - size_t n_lines = 0; - for (ExPolygons::const_iterator it_expoly = src.begin(); it_expoly != src.end(); ++ it_expoly) { - n_lines += it_expoly->contour.points.size(); - for (size_t i = 0; i < it_expoly->holes.size(); ++ i) - n_lines += it_expoly->holes[i].points.size(); + assert(count_lines == 0 || count_lines == count_points(src)); + if (count_lines == 0) count_lines = count_points(src); + Linesf lines; + lines.reserve(count_lines); + Vec2d prev_pd; + auto to_lines = [&lines, &prev_pd](const Points &pts) { + assert(pts.size() >= 3); + if (pts.size() < 2) return; + bool is_first = true; + for (const Point &p : pts) { + Vec2d pd = p.cast(); + if (is_first) is_first = false; + else lines.emplace_back(prev_pd, pd); + prev_pd = pd; + } + lines.emplace_back(prev_pd, pts.front().cast()); + }; + for (const ExPolygon& expoly: src) { + to_lines(expoly.contour.points); + for (const Polygon &hole : expoly.holes) + to_lines(hole.points); } - std::vector lines; - lines.reserve(n_lines); + assert(lines.size() == count_lines); + return lines; +} + +inline Linesf to_unscaled_linesf(const ExPolygons &src) +{ + Linesf lines; + lines.reserve(count_points(src)); for (ExPolygons::const_iterator it_expoly = src.begin(); it_expoly != src.end(); ++ it_expoly) { for (size_t i = 0; i <= it_expoly->holes.size(); ++ i) { const Points &points = ((i == 0) ? it_expoly->contour : it_expoly->holes[i - 1]).points; diff --git a/src/libslic3r/ExPolygonsIndex.cpp b/src/libslic3r/ExPolygonsIndex.cpp new file mode 100644 index 000000000..976531799 --- /dev/null +++ b/src/libslic3r/ExPolygonsIndex.cpp @@ -0,0 +1,82 @@ +#include "ExPolygonsIndex.hpp" +using namespace Slic3r; + +// IMPROVE: use one dimensional vector for polygons offset with searching by std::lower_bound +ExPolygonsIndices::ExPolygonsIndices(const ExPolygons &shapes) +{ + // prepare offsets + m_offsets.reserve(shapes.size()); + uint32_t offset = 0; + for (const ExPolygon &shape : shapes) { + assert(!shape.contour.points.empty()); + std::vector shape_offsets; + shape_offsets.reserve(shape.holes.size() + 1); + shape_offsets.push_back(offset); + offset += shape.contour.points.size(); + for (const Polygon &hole: shape.holes) { + shape_offsets.push_back(offset); + offset += hole.points.size(); + } + m_offsets.push_back(std::move(shape_offsets)); + } + m_count = offset; +} + +uint32_t ExPolygonsIndices::cvt(const ExPolygonsIndex &id) const +{ + assert(id.expolygons_index < m_offsets.size()); + const std::vector &shape_offset = m_offsets[id.expolygons_index]; + assert(id.polygon_index < shape_offset.size()); + uint32_t res = shape_offset[id.polygon_index] + id.point_index; + assert(res < m_count); + return res; +} + +ExPolygonsIndex ExPolygonsIndices::cvt(uint32_t index) const +{ + assert(index < m_count); + ExPolygonsIndex result{0, 0, 0}; + // find expolygon index + auto fn = [](const std::vector &offsets, uint32_t index) { return offsets[0] < index; }; + auto it = std::lower_bound(m_offsets.begin() + 1, m_offsets.end(), index, fn); + result.expolygons_index = it - m_offsets.begin(); + if (it == m_offsets.end() || it->at(0) != index) --result.expolygons_index; + + // find polygon index + const std::vector &shape_offset = m_offsets[result.expolygons_index]; + auto it2 = std::lower_bound(shape_offset.begin() + 1, shape_offset.end(), index); + result.polygon_index = it2 - shape_offset.begin(); + if (it2 == shape_offset.end() || *it2 != index) --result.polygon_index; + + // calculate point index + uint32_t polygon_offset = shape_offset[result.polygon_index]; + assert(index >= polygon_offset); + result.point_index = index - polygon_offset; + return result; +} + +bool ExPolygonsIndices::is_last_point(const ExPolygonsIndex &id) const { + assert(id.expolygons_index < m_offsets.size()); + const std::vector &shape_offset = m_offsets[id.expolygons_index]; + assert(id.polygon_index < shape_offset.size()); + uint32_t index = shape_offset[id.polygon_index] + id.point_index; + assert(index < m_count); + // next index + uint32_t next_point_index = index + 1; + uint32_t next_poly_index = id.polygon_index + 1; + uint32_t next_expoly_index = id.expolygons_index + 1; + // is last expoly? + if (next_expoly_index == m_offsets.size()) { + // is last expoly last poly? + if (next_poly_index == shape_offset.size()) + return next_point_index == m_count; + } else { + // (not last expoly) is expoly last poly? + if (next_poly_index == shape_offset.size()) + return next_point_index == m_offsets[next_expoly_index][0]; + } + // Not last polygon in expolygon + return next_point_index == shape_offset[next_poly_index]; +} + +uint32_t ExPolygonsIndices::get_count() const { return m_count; } diff --git a/src/libslic3r/ExPolygonsIndex.hpp b/src/libslic3r/ExPolygonsIndex.hpp new file mode 100644 index 000000000..b46fd5089 --- /dev/null +++ b/src/libslic3r/ExPolygonsIndex.hpp @@ -0,0 +1,74 @@ +#ifndef slic3r_ExPolygonsIndex_hpp_ +#define slic3r_ExPolygonsIndex_hpp_ + +#include "ExPolygon.hpp" +namespace Slic3r { + +/// +/// Index into ExPolygons +/// Identify expolygon, its contour (or hole) and point +/// +struct ExPolygonsIndex +{ + // index of ExPolygons + uint32_t expolygons_index; + + // index of Polygon + // 0 .. contour + // N .. hole[N-1] + uint32_t polygon_index; + + // index of point in polygon + uint32_t point_index; + + bool is_contour() const { return polygon_index == 0; } + bool is_hole() const { return polygon_index != 0; } + uint32_t hole_index() const { return polygon_index - 1; } +}; + +/// +/// Keep conversion from ExPolygonsIndex to Index and vice versa +/// ExPolygonsIndex .. contour(or hole) point from ExPolygons +/// Index .. continous number +/// +/// index is used to address lines and points as result from function +/// Slic3r::to_lines, Slic3r::to_points +/// +class ExPolygonsIndices +{ + std::vector> m_offsets; + // for check range of index + uint32_t m_count; // count of points +public: + ExPolygonsIndices(const ExPolygons &shapes); + + /// + /// Convert to one index number + /// + /// Compose of adress into expolygons + /// Index + uint32_t cvt(const ExPolygonsIndex &id) const; + + /// + /// Separate to multi index + /// + /// adress into expolygons + /// + ExPolygonsIndex cvt(uint32_t index) const; + + /// + /// Check whether id is last point in polygon + /// + /// Identify point in expolygon + /// True when id is last point in polygon otherwise false + bool is_last_point(const ExPolygonsIndex &id) const; + + /// + /// Count of points in expolygons + /// + /// Count of points in expolygons + uint32_t get_count() const; +}; + +} // namespace Slic3r +#endif // slic3r_ExPolygonsIndex_hpp_ diff --git a/src/libslic3r/Line.hpp b/src/libslic3r/Line.hpp index f9a8977d5..9a939ac38 100644 --- a/src/libslic3r/Line.hpp +++ b/src/libslic3r/Line.hpp @@ -210,6 +210,7 @@ public: static const constexpr int Dim = 2; using Scalar = Vec2d::Scalar; }; +using Linesf = std::vector; class Linef3 { diff --git a/tests/data/points_close_to_line.svg b/tests/data/points_close_to_line.svg new file mode 100644 index 000000000..61ee61e61 --- /dev/null +++ b/tests/data/points_close_to_line.svg @@ -0,0 +1,3 @@ + + + diff --git a/tests/libslic3r/test_emboss.cpp b/tests/libslic3r/test_emboss.cpp index cf01befb6..f2c431535 100644 --- a/tests/libslic3r/test_emboss.cpp +++ b/tests/libslic3r/test_emboss.cpp @@ -197,7 +197,7 @@ TEST_CASE("Visualize glyph from font", "[Emboss]") #include "test_utils.hpp" #include "nanosvg/nanosvg.h" // load SVG file #include "libslic3r/NSVGUtils.hpp" -void heal_and_check(const Polygons &polygons) +ExPolygons heal_and_check(const Polygons &polygons) { Pointfs intersections_prev = intersection_points(polygons); Points polygons_points = to_points(polygons); @@ -233,6 +233,7 @@ void heal_and_check(const Polygons &polygons) CHECK(intersections.empty()); CHECK(duplicits.empty()); + return shape; } void scale(Polygons &polygons, double multiplicator) { @@ -270,6 +271,23 @@ TEST_CASE("Heal of damaged polygons", "[Emboss]") #endif // VISUALIZE } +TEST_CASE("Heal of points close to line", "[Emboss]") +{ + // Shape loaded from svg is letter 'i' from font 'ALIENATE.TTF' + std::string file_name = "points_close_to_line.svg"; + std::string file_path = TEST_DATA_DIR PATH_SEPARATOR + file_name; + NSVGimage *image = nsvgParseFromFile(file_path.c_str(), "px", 96.0f); + Polygons polygons = NSVGUtils::to_polygons(image); + nsvgDelete(image); + REQUIRE(polygons.size() == 1); + Polygon polygon = polygons.front(); + polygon.points.pop_back();// NSVG put first point as last one when polygon is closed + ExPolygons expoly({ExPolygon(polygon)}); + Emboss::heal_shape(expoly); + //{ SVG svg("C:/data/temp/healed.svg"); svg.draw(expoly);} + CHECK(expoly.size() == 3); +} + TEST_CASE("Convert text with glyph cache to model", "[Emboss]") { std::string font_path = get_font_filepath(); diff --git a/tests/libslic3r/test_polygon.cpp b/tests/libslic3r/test_polygon.cpp index 7774b44a6..c1e1c3b73 100644 --- a/tests/libslic3r/test_polygon.cpp +++ b/tests/libslic3r/test_polygon.cpp @@ -210,3 +210,34 @@ SCENARIO("Simplify polygon", "[Polygon]") } } } + +#include "libslic3r/ExPolygon.hpp" +#include "libslic3r/ExPolygonsIndex.hpp" +TEST_CASE("Indexing expolygons", "[ExPolygon]") +{ + ExPolygons expolys{ + ExPolygon{Polygon{{0, 0}, {10, 0}, {0, 5}}, Polygon{{4, 3}, {6, 3}, {5, 2}}}, + ExPolygon{Polygon{{100, 0}, {110, 0}, {100, 5}}, Polygon{{104, 3}, {106, 3}, {105, 2}}} + }; + Points points = to_points(expolys); + Lines lines = to_lines(expolys); + Linesf linesf = to_linesf(expolys); + ExPolygonsIndices ids(expolys); + REQUIRE(points.size() == lines.size()); + REQUIRE(points.size() == linesf.size()); + REQUIRE(points.size() == ids.get_count()); + for (size_t i = 0; i < ids.get_count(); i++) { + ExPolygonsIndex id = ids.cvt(i); + const ExPolygon &expoly = expolys[id.expolygons_index]; + const Polygon &poly = id.is_contour() ? expoly.contour : expoly.holes[id.hole_index()]; + const Points &pts = poly.points; + const Point &p = pts[id.point_index]; + CHECK(points[i] == p); + CHECK(lines[i].a == p); + CHECK(linesf[i].a.cast() == p); + CHECK(ids.cvt(id) == i); + const Point &p_b = ids.is_last_point(id) ? pts.front() : pts[id.point_index + 1]; + CHECK(lines[i].b == p_b); + CHECK(linesf[i].b.cast() == p_b); + } +}