Heal shape with points close to line

(after conversion to floating point it is on the other side of line)
ExPolygons indexing (fixed)
This commit is contained in:
Filip Sykala - NTB T15p 2022-10-11 13:35:52 +02:00
parent bdf8c5ce88
commit 8511b280bf
10 changed files with 571 additions and 99 deletions

View File

@ -48,6 +48,8 @@ set(SLIC3R_SOURCES
enum_bitmask.hpp
ExPolygon.cpp
ExPolygon.hpp
ExPolygonsIndex.cpp
ExPolygonsIndex.hpp
Extruder.cpp
Extruder.hpp
ExtrusionEntity.cpp

View File

@ -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<stbtt_fontinfo> load_font_info(const unsigned char *data, unsigned int index = 0);
static std::optional<Emboss::Glyph> get_glyph(const stbtt_fontinfo &font_info, int unicode_letter, float flatness);
namespace priv{
bool is_valid(const Emboss::FontFile &font, unsigned int index);
std::optional<stbtt_fontinfo> load_font_info(const unsigned char *data, unsigned int index = 0);
std::optional<Emboss::Glyph> get_glyph(const stbtt_fontinfo &font_info, int unicode_letter, float flatness);
// take glyph from cache
static const Emboss::Glyph* get_glyph(int unicode, const Emboss::FontFile &font, const FontProp &font_prop,
const Emboss::Glyph* get_glyph(int unicode, const Emboss::FontFile &font, const FontProp &font_prop,
Emboss::Glyphs &cache, std::optional<stbtt_fontinfo> &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
static Point to_point(const stbtt__point &point);
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)});
};
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<stbtt_fontinfo> Private::load_font_info(
std::optional<stbtt_fontinfo> priv::load_font_info(
const unsigned char *data, unsigned int index)
{
int font_offset = stbtt_GetFontOffsetForIndex(data, index);
@ -62,6 +78,204 @@ std::optional<stbtt_fontinfo> 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<Linef> 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<double>();
std::vector<size_t> 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<int>());
assert(line_b == lines[index].b.cast<int>());
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<Linef> 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<Point, size_t>;
std::vector<Div> 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<double>();
std::vector<size_t> 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<int>());
assert(line_b == lines[index].b.cast<int>());
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<bool(const Point &, const Point &)>;
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
@ -69,57 +283,66 @@ ExPolygons Emboss::heal_shape(const Polygons &shape) {
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)});
// 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;
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);
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);
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,13 +352,13 @@ 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<int>());
svg.draw(pts_i, "red", 8 / Emboss::SHAPE_SCALE);
@ -152,27 +375,25 @@ ExPolygons Emboss::heal_shape(const Polygons &shape) {
bb.min,
{bb.max.x(), bb.min.y()},
bb.max,
{bb.min.x(), bb.max.y()}
});
{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);
{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<Emboss::Glyph> Private::get_glyph(const stbtt_fontinfo &font_info, int unicode_letter, float flatness)
std::optional<Emboss::Glyph> 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<Emboss::Glyph> 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<float>(font.infos[font_index].ascent * RESOLUTION / font_prop.size_in_mm);
std::optional<Emboss::Glyph> 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<int>(std::round(point.x / Emboss::SHAPE_SCALE)),
static_cast<int>(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::FontFile> Emboss::create_font_file(
std::vector<FontFile::Info> 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::Glyph> 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<stbtt_fontinfo> font_info_opt = Private::load_font_info(font.data->data(), font_index);
std::optional<stbtt_fontinfo> 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);

View File

@ -163,6 +163,20 @@ public:
/// <returns>Healed shapes</returns>
static ExPolygons heal_shape(const Polygons &shape);
/// <summary>
/// NOTE: call Slic3r::union_ex before this call
///
/// Heal (read: Fix) issues in expolygons:
/// - self intersections
/// - duplicit points
/// - points close to line segments
/// </summary>
/// <param name="shape">In/Out shape to heal</param>
/// <param name="max_iteration">Heal could create another issue,
/// After healing it is checked again until shape is good or maximal count of iteration</param>
/// <returns>True when shapes is good otherwise False</returns>
static bool heal_shape(ExPolygons &shape, unsigned max_iteration = 10);
/// <summary>
/// Use data from font property to modify transformation
/// </summary>
@ -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);
/// <summary>
/// calculate scale for glyph shape convert from shape points to mm
/// Calculate scale for glyph shape convert from shape points to mm
/// </summary>
/// <param name="fp"></param>
/// <param name="ff"></param>
/// <param name="fp">Property of font</param>
/// <param name="ff">Font data</param>
/// <returns>Conversion to mm</returns>
static double get_shape_scale(const FontProp &fp, const FontFile &ff);

View File

@ -135,16 +135,40 @@ inline Lines to_lines(const ExPolygons &src)
return lines;
}
inline std::vector<Linef> 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<double>();
if (is_first) is_first = false;
else lines.emplace_back(prev_pd, pd);
prev_pd = pd;
}
std::vector<Linef> lines;
lines.reserve(n_lines);
lines.emplace_back(prev_pd, pts.front().cast<double>());
};
for (const ExPolygon& expoly: src) {
to_lines(expoly.contour.points);
for (const Polygon &hole : expoly.holes)
to_lines(hole.points);
}
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;

View File

@ -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<uint32_t> 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<uint32_t> &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<uint32_t> &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<uint32_t> &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<uint32_t> &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; }

View File

@ -0,0 +1,74 @@
#ifndef slic3r_ExPolygonsIndex_hpp_
#define slic3r_ExPolygonsIndex_hpp_
#include "ExPolygon.hpp"
namespace Slic3r {
/// <summary>
/// Index into ExPolygons
/// Identify expolygon, its contour (or hole) and point
/// </summary>
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; }
};
/// <summary>
/// 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
/// </summary>
class ExPolygonsIndices
{
std::vector<std::vector<uint32_t>> m_offsets;
// for check range of index
uint32_t m_count; // count of points
public:
ExPolygonsIndices(const ExPolygons &shapes);
/// <summary>
/// Convert to one index number
/// </summary>
/// <param name="id">Compose of adress into expolygons</param>
/// <returns>Index</returns>
uint32_t cvt(const ExPolygonsIndex &id) const;
/// <summary>
/// Separate to multi index
/// </summary>
/// <param name="index">adress into expolygons</param>
/// <returns></returns>
ExPolygonsIndex cvt(uint32_t index) const;
/// <summary>
/// Check whether id is last point in polygon
/// </summary>
/// <param name="id">Identify point in expolygon</param>
/// <returns>True when id is last point in polygon otherwise false</returns>
bool is_last_point(const ExPolygonsIndex &id) const;
/// <summary>
/// Count of points in expolygons
/// </summary>
/// <returns>Count of points in expolygons</returns>
uint32_t get_count() const;
};
} // namespace Slic3r
#endif // slic3r_ExPolygonsIndex_hpp_

View File

@ -210,6 +210,7 @@ public:
static const constexpr int Dim = 2;
using Scalar = Vec2d::Scalar;
};
using Linesf = std::vector<Linef>;
class Linef3
{

View File

@ -0,0 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 50 100" width="50px" height="100px">
<path fill="#808080" d="M 10,10 11,90 20,80 11,70 25,50 V 22 L 10,20 40,15 Z"/>
</svg>

After

Width:  |  Height:  |  Size: 177 B

View File

@ -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();

View File

@ -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<int>() == 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<int>() == p_b);
}
}