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:
parent
bdf8c5ce88
commit
8511b280bf
@ -48,6 +48,8 @@ set(SLIC3R_SOURCES
|
||||
enum_bitmask.hpp
|
||||
ExPolygon.cpp
|
||||
ExPolygon.hpp
|
||||
ExPolygonsIndex.cpp
|
||||
ExPolygonsIndex.hpp
|
||||
Extruder.cpp
|
||||
Extruder.hpp
|
||||
ExtrusionEntity.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<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{
|
||||
|
||||
// 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<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
|
||||
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
|
||||
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<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,64 +78,271 @@ 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
|
||||
// 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<int>());
|
||||
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<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);
|
||||
|
||||
|
@ -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);
|
||||
|
||||
|
@ -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;
|
||||
}
|
||||
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);
|
||||
}
|
||||
std::vector<Linef> 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;
|
||||
|
82
src/libslic3r/ExPolygonsIndex.cpp
Normal file
82
src/libslic3r/ExPolygonsIndex.cpp
Normal 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; }
|
74
src/libslic3r/ExPolygonsIndex.hpp
Normal file
74
src/libslic3r/ExPolygonsIndex.hpp
Normal 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_
|
@ -210,6 +210,7 @@ public:
|
||||
static const constexpr int Dim = 2;
|
||||
using Scalar = Vec2d::Scalar;
|
||||
};
|
||||
using Linesf = std::vector<Linef>;
|
||||
|
||||
class Linef3
|
||||
{
|
||||
|
3
tests/data/points_close_to_line.svg
Normal file
3
tests/data/points_close_to_line.svg
Normal 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 |
@ -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();
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user