diff --git a/src/libslic3r/Emboss.cpp b/src/libslic3r/Emboss.cpp
index 7afccda67e..4a9f98f01e 100644
--- a/src/libslic3r/Emboss.cpp
+++ b/src/libslic3r/Emboss.cpp
@@ -92,17 +92,17 @@ struct SpikeDesc
///
/// Size of spike width after cut of the tip, has to be grater than 2.5
/// When spike has same or more pixels with width less than 1 pixel
- SpikeDesc(double bevel_size, double pixel_spike_length = 6)
- {
+ SpikeDesc(double bevel_size, double pixel_spike_length = 6):
// create min angle given by spike_length
// Use it as minimal height of 1 pixel base spike
- double angle = 2. * atan2(pixel_spike_length, .5); // [rad]
- cos_angle = std::fabs(cos(angle));
+ cos_angle(std::fabs(std::cos(
+ /*angle*/ 2. * std::atan2(pixel_spike_length, .5)
+ ))),
// When remove spike this angle is set.
// Value must be grater than min_angle
- half_bevel = bevel_size / 2;
- }
+ half_bevel(bevel_size / 2)
+ {}
};
// return TRUE when remove point. It could create polygon with 2 points.
@@ -120,6 +120,8 @@ void remove_spikes(ExPolygons &expolygons, const SpikeDesc &spike_desc);
};
+// spike ... very sharp corner - when not removed cause iteration of heal process
+// index ... index of duplicit point in polygon
bool priv::remove_when_spike(Polygon &polygon, size_t index, const SpikeDesc &spike_desc) {
std::optional add;
@@ -205,7 +207,8 @@ bool priv::remove_when_spike(Polygon &polygon, size_t index, const SpikeDesc &sp
}
void priv::remove_spikes_in_duplicates(ExPolygons &expolygons, const Points &duplicates) {
-
+ if (duplicates.empty())
+ return;
auto check = [](Polygon &polygon, const Point &d) -> bool {
double spike_bevel = 1 / SHAPE_SCALE;
double spike_length = 5.;
@@ -486,16 +489,20 @@ bool priv::remove_self_intersections(ExPolygons &shape, unsigned max_iteration)
return false;
}
-ExPolygons Emboss::heal_shape(const Polygons &shape)
+ExPolygons Emboss::heal_polygons(const Polygons &shape, bool is_non_zero)
{
+ const double clean_distance = 1.415; // little grater than sqrt(2)
+ ClipperLib::PolyFillType fill_type = is_non_zero ?
+ ClipperLib::pftNonZero : ClipperLib::pftEvenOdd;
+
// 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);
- const double clean_distance = 1.415; // little grater than sqrt(2)
+ ClipperLib::Paths paths = ClipperLib::SimplifyPolygons(ClipperUtils::PolygonsProvider(shape), fill_type);
ClipperLib::CleanPolygons(paths, clean_distance);
Polygons polygons = to_polygons(paths);
- polygons.erase(std::remove_if(polygons.begin(), polygons.end(), [](const Polygon &p) { return p.size() < 3; }), polygons.end());
+ polygons.erase(std::remove_if(polygons.begin(), polygons.end(),
+ [](const Polygon &p) { return p.size() < 3; }), polygons.end());
// Do not remove all duplicates but do it better way
// Overlap all duplicit points by rectangle 3x3
@@ -508,12 +515,11 @@ ExPolygons Emboss::heal_shape(const Polygons &shape)
polygons.push_back(rect_3x3);
}
}
+ ExPolygons res = Slic3r::union_ex(polygons, fill_type);
- // 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);
+
+ const unsigned int max_iteration = 10;
+ bool is_healed = heal_expolygons(res, max_iteration);
return res;
}
@@ -527,52 +533,105 @@ void priv::visualize_heal(const std::string &svg_filepath, const ExPolygons &exp
svg.draw(expolygons);
Points duplicits = collect_duplicates(pts);
- svg.draw(duplicits, "black", 7 / SHAPE_SCALE);
+ int black_size = std::max(bb.size().x(), bb.size().y()) / 20;
+ svg.draw(duplicits, "black", black_size);
Pointfs intersections_f = intersection_points(expolygons);
Points intersections;
intersections.reserve(intersections_f.size());
std::transform(intersections_f.begin(), intersections_f.end(), std::back_inserter(intersections),
[](const Vec2d &p) { return p.cast(); });
- svg.draw(intersections, "red", 8 / SHAPE_SCALE);
+ svg.draw(intersections, "red", black_size * 1.2);
}
-bool Emboss::heal_shape(ExPolygons &shape, unsigned max_iteration)
+bool Emboss::heal_expolygons(ExPolygons &shape, unsigned max_iteration)
{
return priv::heal_dupl_inter(shape, max_iteration);
}
#ifndef HEAL_WITH_CLOSING
+namespace {
+
+Points get_unique_intersections(const ExPolygons &shape)
+{
+ Pointfs intersections_f = intersection_points(shape);
+ Points intersections; // result
+ if (intersections_f.empty())
+ return intersections;
+
+ // convert intersections into Points
+ intersections.reserve(intersections_f.size());
+ std::transform(intersections_f.begin(), intersections_f.end(), std::back_inserter(intersections),
+ [](const Vec2d &p) { return Point(std::floor(p.x()), std::floor(p.y())); });
+
+ // intersections should be unique poits
+ std::sort(intersections.begin(), intersections.end());
+ auto it = std::unique(intersections.begin(), intersections.end());
+ intersections.erase(it, intersections.end());
+ return intersections;
+}
+
+Polygons get_holes_with_points(const Polygons &holes, const Points &points)
+{
+ Polygons result;
+ for (const Slic3r::Polygon &hole : holes)
+ for (const Point &p : points)
+ for (const Point &h : hole)
+ if (p == h) {
+ result.push_back(hole);
+ break;
+ }
+ return result;
+}
+
+///
+/// Fill holes which create duplicits or intersections
+/// When healing hole creates trouble in shape again try to heal by an union instead of diff_ex
+///
+/// Holes which was substracted from shape previous
+/// Current duplicates in shape
+/// Current intersections in shape
+/// Partialy healed shape[could be modified]
+/// True when modify shape otherwise False
+bool fill_trouble_holes(const Polygons &holes, const Points &duplicates, const Points &intersections, ExPolygons &shape)
+{
+ if (holes.empty())
+ return false;
+ if (duplicates.empty() && intersections.empty())
+ return false;
+
+ Polygons fill = get_holes_with_points(holes, duplicates);
+ append(fill, get_holes_with_points(holes, intersections));
+ if (fill.empty())
+ return false;
+
+ shape = union_ex(shape, fill);
+ return true;
+}
+
+} // namespace
+
bool priv::heal_dupl_inter(ExPolygons &shape, unsigned max_iteration)
{
if (shape.empty()) return true;
// create loop permanent memory
Polygons holes;
- Points intersections;
while (--max_iteration) {
remove_same_neighbor(shape);
- Pointfs intersections_f = intersection_points(shape);
-
- // convert intersections into Points
- assert(intersections.empty());
- intersections.reserve(intersections_f.size());
- std::transform(intersections_f.begin(), intersections_f.end(), std::back_inserter(intersections),
- [](const Vec2d &p) { return Point(std::floor(p.x()), std::floor(p.y())); });
-
- // intersections should be unique poits
- std::sort(intersections.begin(), intersections.end());
- auto it = std::unique(intersections.begin(), intersections.end());
- intersections.erase(it, intersections.end());
-
Points duplicates = collect_duplicates(to_points(shape));
- // duplicates are already uniqua and sorted
-
+ Points intersections = get_unique_intersections(shape);
+
// Check whether shape is already healed
if (intersections.empty() && duplicates.empty())
return true;
- assert(holes.empty());
+ if (fill_trouble_holes(holes, duplicates, intersections, shape)) {
+ holes.clear();
+ continue;
+ }
+
+ holes.clear();
holes.reserve(intersections.size() + duplicates.size());
remove_spikes_in_duplicates(shape, duplicates);
@@ -591,11 +650,8 @@ bool priv::heal_dupl_inter(ExPolygons &shape, unsigned max_iteration)
holes.push_back(hole);
}
- shape = Slic3r::diff_ex(shape, holes, ApplySafetyOffset::Yes);
-
- // prepare for next loop
- holes.clear();
- intersections.clear();
+ shape = Slic3r::diff_ex(shape, holes, ApplySafetyOffset::No);
+ // ApplySafetyOffset::Yes is incompatible with function fill_trouble_holes
}
//priv::visualize_heal("C:/data/temp/heal.svg", shape);
@@ -736,8 +792,12 @@ std::optional priv::get_glyph(const stbtt_fontinfo &font_info, int unicod
std::reverse(pts.begin(), pts.end());
glyph_polygons.emplace_back(pts);
}
- if (!glyph_polygons.empty())
- glyph.shape = Emboss::heal_shape(glyph_polygons);
+ if (!glyph_polygons.empty()) {
+ // 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
+ glyph.shape = Emboss::heal_polygons(glyph_polygons);
+ }
return glyph;
}
@@ -1238,7 +1298,22 @@ ExPolygons Slic3r::union_ex(const ExPolygonsWithIds &shapes)
expolygons_append(result, shape.expoly);
}
result = union_ex(result);
- heal_shape(result);
+ bool is_healed = heal_expolygons(result);
+ return result;
+}
+
+ExPolygons Slic3r::union_with_delta(const ExPolygonsWithIds &shapes, float delta)
+{
+ // unify to one expolygons
+ ExPolygons expolygons;
+ for (const ExPolygonsWithId &shape : shapes) {
+ if (shape.expoly.empty())
+ continue;
+ expolygons_append(expolygons, offset_ex(shape.expoly, delta));
+ }
+ ExPolygons result = union_ex(expolygons);
+ result = offset_ex(result, -delta);
+ bool is_healed = heal_expolygons(result);
return result;
}
diff --git a/src/libslic3r/Emboss.hpp b/src/libslic3r/Emboss.hpp
index 870fa3183b..ebf3c508ec 100644
--- a/src/libslic3r/Emboss.hpp
+++ b/src/libslic3r/Emboss.hpp
@@ -166,9 +166,9 @@ namespace Emboss
/// Fix duplicit points and self intersections in polygons.
/// Also try to reduce amount of points and remove useless polygon parts
///
- /// Define wanted precision of shape after heal
+ /// Fill type ClipperLib::pftNonZero for overlapping otherwise
/// Healed shapes
- ExPolygons heal_shape(const Polygons &shape);
+ ExPolygons heal_polygons(const Polygons &shape, bool is_non_zero = true);
///
/// NOTE: call Slic3r::union_ex before this call
@@ -182,7 +182,7 @@ namespace Emboss
/// 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
- bool heal_shape(ExPolygons &shape, unsigned max_iteration = 10);
+ bool heal_expolygons(ExPolygons &shape, unsigned max_iteration = 10);
///
/// Divide line segments in place near to point
@@ -467,6 +467,8 @@ void translate(ExPolygonsWithIds &e, const Point &p);
BoundingBox get_extents(const ExPolygonsWithIds &e);
void center(ExPolygonsWithIds &e);
ExPolygons union_ex(const ExPolygonsWithIds &shapes);
-
+// delta .. safe offset before union (use as boolean close)
+// NOTE: remove unprintable spaces between neighbor curves (made by linearization of curve)
+ExPolygons union_with_delta(const ExPolygonsWithIds &shapes, float delta);
} // namespace Slic3r
#endif // slic3r_Emboss_hpp_
diff --git a/src/libslic3r/NSVGUtils.cpp b/src/libslic3r/NSVGUtils.cpp
index 8a569ba4aa..688b42a1b4 100644
--- a/src/libslic3r/NSVGUtils.cpp
+++ b/src/libslic3r/NSVGUtils.cpp
@@ -53,11 +53,6 @@ ExPolygonsWithIds create_shape_with_ids(const NSVGimage &image, const NSVGLinePa
}
}
- // heal shapes
- if (param.max_heal_iteration > 0)
- for (auto &[id, expoly] : result)
- Slic3r::Emboss::heal_shape(expoly, param.max_heal_iteration);
-
// SVG is used as centered
// Do not disturb user by settings of pivot position
center(result);
@@ -366,11 +361,12 @@ ExPolygons fill_to_expolygons(const LinesPath &lines_path, const NSVGshape &shap
if (fill.empty())
return {};
- ClipperLib::PolyFillType fill_type = ClipperLib::pftNonZero;
// if (shape->fillRule == NSVGfillRule::NSVG_FILLRULE_NONZERO)
+ bool is_non_zero = true;
if (shape.fillRule == NSVGfillRule::NSVG_FILLRULE_EVENODD)
- fill_type = ClipperLib::pftEvenOdd;
- return union_ex(fill, fill_type);
+ is_non_zero = false;
+
+ return Emboss::heal_polygons(fill, is_non_zero);
}
struct DashesParam{
@@ -518,7 +514,7 @@ ExPolygons stroke_to_expolygons(const LinesPath &lines_path, const NSVGshape &sh
polygons_append(result, offset(lines_path.polylines, stroke_width / 2, join_type, mitter, end_type));
}
- return union_ex(result, ClipperLib::pftNonZero);
+ return Emboss::heal_polygons(result);
}
} // namespace
\ No newline at end of file
diff --git a/src/libslic3r/NSVGUtils.hpp b/src/libslic3r/NSVGUtils.hpp
index 12cd2c3f59..3d1b69450d 100644
--- a/src/libslic3r/NSVGUtils.hpp
+++ b/src/libslic3r/NSVGUtils.hpp
@@ -32,9 +32,6 @@ struct NSVGLineParams
// NOTE: Every point coor from image(float) is multiplied by scale and rounded to integer --> Slic3r::Point
double scale = 1. / SCALING_FACTOR;
- // count of iteration to heal shape
- unsigned max_heal_iteration = 10;
-
// Flag wether y is negative, when true than y coor is multiplied by -1
bool is_y_negative = true;
diff --git a/src/slic3r/GUI/Jobs/EmbossJob.cpp b/src/slic3r/GUI/Jobs/EmbossJob.cpp
index 36544c26f6..1d28e518ce 100644
--- a/src/slic3r/GUI/Jobs/EmbossJob.cpp
+++ b/src/slic3r/GUI/Jobs/EmbossJob.cpp
@@ -792,7 +792,8 @@ bool check(const UpdateSurfaceVolumeData &input, bool is_main_thread)
template
ExPolygons create_shape(DataBase &input, Fnc was_canceled) {
const EmbossShape &es = input.create_shape();
- return union_ex(es.shapes_with_ids);
+ float delta = 50.f;
+ return union_with_delta(es.shapes_with_ids, delta);
}
//#define STORE_SAMPLING
diff --git a/tests/data/contour_neighbor.svg b/tests/data/contour_neighbor.svg
new file mode 100644
index 0000000000..3f520ea1d2
--- /dev/null
+++ b/tests/data/contour_neighbor.svg
@@ -0,0 +1,4 @@
+
diff --git a/tests/libslic3r/test_emboss.cpp b/tests/libslic3r/test_emboss.cpp
index adff1b7a37..d487d1e9d9 100644
--- a/tests/libslic3r/test_emboss.cpp
+++ b/tests/libslic3r/test_emboss.cpp
@@ -203,7 +203,7 @@ ExPolygons heal_and_check(const Polygons &polygons)
Points polygons_points = to_points(polygons);
Points duplicits_prev = collect_duplicates(polygons_points);
- ExPolygons shape = Emboss::heal_shape(polygons);
+ ExPolygons shape = Emboss::heal_polygons(polygons);
// Is default shape for unhealabled shape?
bool is_default_shape =
@@ -286,6 +286,30 @@ TEST_CASE("Heal of 'm' in Allura_Script.ttf", "[Emboss]")
auto a = heal_and_check(polygons);
}
+#include "libslic3r/NSVGUtils.hpp"
+TEST_CASE("Heal of svg contour overlap", "[Emboss]") {
+
+ std::string svg_file = "contour_neighbor.svg";
+ auto image = nsvgParseFromFile(TEST_DATA_DIR PATH_SEPARATOR + svg_file, "mm");
+ NSVGLineParams param(1e10);
+ ExPolygonsWithIds shapes = create_shape_with_ids(*image, param);
+ Polygons polygons;
+ for (ExPolygonsWithId &shape : shapes)
+ polygons.push_back(shape.expoly.front().contour);
+ auto a = heal_and_check(polygons);
+}
+
+// Input contour is extracted from case above "contour_neighbor.svg" with trouble shooted scale
+TEST_CASE("Heal of overlaping contour", "[Emboss]"){
+ // Extracted from svg:
+ Points contour{{2228926, 1543620}, {745002, 2065101}, {745002, 2065094}, {744990, 2065094}, {684487, 1466338},
+ {510999, 908378}, {236555, 403250}, {-126813, -37014}, {-567074, -400382}, {-1072201, -674822},
+ {-567074, -400378}, {-126813, -37010}, {236555, 403250}, {510999, 908382}, {684487, 1466346},
+ {744990, 2065105}, {-2219648, 2073234}, {-2228926, -908814}, {-1646879, -2073235}};
+ ExPolygons shapes = {ExPolygon{contour}};
+ CHECK(Emboss::heal_expolygons(shapes));
+}
+
TEST_CASE("Heal of points close to line", "[Emboss]")
{
std::string file_name = "points_close_to_line.svg";