Fix of Layer::build_up_down_graph() for non-manifold inputs
by shrinking the input expolygons before intersecting them. GH #10150, #10158, SPE-1621, SPE-1612
This commit is contained in:
parent
d3d48e9895
commit
bceed00ae8
1 changed files with 60 additions and 19 deletions
|
@ -60,7 +60,10 @@ void Layer::make_slices()
|
||||||
}
|
}
|
||||||
|
|
||||||
// used by Layer::build_up_down_graph()
|
// used by Layer::build_up_down_graph()
|
||||||
[[nodiscard]] static ClipperLib_Z::Paths expolygons_to_zpaths(const ExPolygons &expolygons, coord_t isrc)
|
// Shrink source polygons one by one, so that they will be separated if they were touching
|
||||||
|
// at vertices (non-manifold situation).
|
||||||
|
// Then convert them to Z-paths with Z coordinate indicating index of the source expolygon.
|
||||||
|
[[nodiscard]] static ClipperLib_Z::Paths expolygons_to_zpaths_shrunk(const ExPolygons &expolygons, coord_t isrc)
|
||||||
{
|
{
|
||||||
size_t num_paths = 0;
|
size_t num_paths = 0;
|
||||||
for (const ExPolygon &expolygon : expolygons)
|
for (const ExPolygon &expolygon : expolygons)
|
||||||
|
@ -69,15 +72,50 @@ void Layer::make_slices()
|
||||||
ClipperLib_Z::Paths out;
|
ClipperLib_Z::Paths out;
|
||||||
out.reserve(num_paths);
|
out.reserve(num_paths);
|
||||||
|
|
||||||
for (const ExPolygon &expolygon : expolygons) {
|
ClipperLib::Paths contours;
|
||||||
for (size_t icontour = 0; icontour < expolygon.num_contours(); ++ icontour) {
|
ClipperLib::Paths holes;
|
||||||
const Polygon &contour = expolygon.contour_or_hole(icontour);
|
ClipperLib::Clipper clipper;
|
||||||
|
ClipperLib::ClipperOffset co;
|
||||||
|
ClipperLib::Paths out2;
|
||||||
|
|
||||||
|
static constexpr const float delta = ClipperSafetyOffset; // *10.f;
|
||||||
|
co.MiterLimit = scaled<double>(3.);
|
||||||
|
// Use the default zero edge merging distance. For this kind of safety offset the accuracy of normal direction is not important.
|
||||||
|
// co.ShortestEdgeLength = delta * ClipperOffsetShortestEdgeFactor;
|
||||||
|
|
||||||
|
for (const ExPolygon &expoly : expolygons) {
|
||||||
|
contours.clear();
|
||||||
|
co.Clear();
|
||||||
|
co.AddPath(expoly.contour.points, ClipperLib::jtMiter, ClipperLib::etClosedPolygon);
|
||||||
|
co.Execute(contours, - delta);
|
||||||
|
if (! contours.empty()) {
|
||||||
|
holes.clear();
|
||||||
|
for (const Polygon &hole : expoly.holes) {
|
||||||
|
co.Clear();
|
||||||
|
co.AddPath(hole.points, ClipperLib::jtMiter, ClipperLib::etClosedPolygon);
|
||||||
|
// Execute reorients the contours so that the outer most contour has a positive area. Thus the output
|
||||||
|
// contours will be CCW oriented even though the input paths are CW oriented.
|
||||||
|
// Offset is applied after contour reorientation, thus the signum of the offset value is reversed.
|
||||||
|
out2.clear();
|
||||||
|
co.Execute(out2, delta);
|
||||||
|
append(holes, std::move(out2));
|
||||||
|
}
|
||||||
|
// Subtract holes from the contours.
|
||||||
|
if (! holes.empty()) {
|
||||||
|
clipper.Clear();
|
||||||
|
clipper.AddPaths(contours, ClipperLib::ptSubject, true);
|
||||||
|
clipper.AddPaths(holes, ClipperLib::ptClip, true);
|
||||||
|
contours.clear();
|
||||||
|
clipper.Execute(ClipperLib::ctDifference, contours, ClipperLib::pftNonZero, ClipperLib::pftNonZero);
|
||||||
|
}
|
||||||
|
for (const auto &contour : contours) {
|
||||||
out.emplace_back();
|
out.emplace_back();
|
||||||
ClipperLib_Z::Path &path = out.back();
|
ClipperLib_Z::Path &path = out.back();
|
||||||
path.reserve(contour.size());
|
path.reserve(contour.size());
|
||||||
for (const Point &p : contour.points)
|
for (const Point &p : contour)
|
||||||
path.push_back({ p.x(), p.y(), isrc });
|
path.push_back({ p.x(), p.y(), isrc });
|
||||||
}
|
}
|
||||||
|
}
|
||||||
++ isrc;
|
++ isrc;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -183,6 +221,10 @@ static void connect_layer_slices(
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
//FIXME remove the following block one day, it should not be needed.
|
||||||
|
// The following shall not happen now as the source expolygons are being shrunk a bit before intersecting,
|
||||||
|
// thus each point of each intersection polygon should fit completely inside one of the original (unshrunk) expolygons.
|
||||||
|
assert(found);
|
||||||
if (!found) {
|
if (!found) {
|
||||||
// The check above might sometimes fail when the polygons overlap only on points, which causes the clipper to detect no intersection.
|
// The check above might sometimes fail when the polygons overlap only on points, which causes the clipper to detect no intersection.
|
||||||
// The problem happens rarely, mostly on simple polygons (in terms of number of points), but regardless of size!
|
// The problem happens rarely, mostly on simple polygons (in terms of number of points), but regardless of size!
|
||||||
|
@ -191,10 +233,7 @@ static void connect_layer_slices(
|
||||||
// layer B = Polygon{(-24877897,-11100524),(-22504249,-8726874),(-22504249,11477151),(-23244827,12218916),(-23752371,12727276),(-25002495,12727276),(-27502745,10227026),(-27502745,-12727274),(-26504645,-12727274)}
|
// layer B = Polygon{(-24877897,-11100524),(-22504249,-8726874),(-22504249,11477151),(-23244827,12218916),(-23752371,12727276),(-25002495,12727276),(-27502745,10227026),(-27502745,-12727274),(-26504645,-12727274)}
|
||||||
// note that first point is not identical, and the check above picks (-24877897,-11100524) as the first contour point (polynode.Contour.front()).
|
// note that first point is not identical, and the check above picks (-24877897,-11100524) as the first contour point (polynode.Contour.front()).
|
||||||
// that point is sadly slightly outisde of the layer A, so no link is detected, eventhough they are overlaping "completely"
|
// that point is sadly slightly outisde of the layer A, so no link is detected, eventhough they are overlaping "completely"
|
||||||
Polygon contour_poly;
|
Polygon contour_poly(ClipperZUtils::from_zpath(polynode.Contour));
|
||||||
for (const auto& p : polynode.Contour) {
|
|
||||||
contour_poly.points.emplace_back(p.x(), p.y());
|
|
||||||
}
|
|
||||||
BoundingBox contour_aabb{contour_poly.points};
|
BoundingBox contour_aabb{contour_poly.points};
|
||||||
for (int l = int(m_above.lslices_ex.size()) - 1; l >= 0; --l) {
|
for (int l = int(m_above.lslices_ex.size()) - 1; l >= 0; --l) {
|
||||||
LayerSlice &lslice = m_above.lslices_ex[l];
|
LayerSlice &lslice = m_above.lslices_ex[l];
|
||||||
|
@ -222,11 +261,11 @@ static void connect_layer_slices(
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
//FIXME remove the following block one day, it should not be needed.
|
||||||
|
// The following shall not happen now as the source expolygons are being shrunk a bit before intersecting,
|
||||||
|
// thus each point of each intersection polygon should fit completely inside one of the original (unshrunk) expolygons.
|
||||||
if (!found) { // Explanation for aditional check is above.
|
if (!found) { // Explanation for aditional check is above.
|
||||||
Polygon contour_poly;
|
Polygon contour_poly(ClipperZUtils::from_zpath(polynode.Contour));
|
||||||
for (const auto &p : polynode.Contour) {
|
|
||||||
contour_poly.points.emplace_back(p.x(), p.y());
|
|
||||||
}
|
|
||||||
BoundingBox contour_aabb{contour_poly.points};
|
BoundingBox contour_aabb{contour_poly.points};
|
||||||
for (int l = int(m_below.lslices_ex.size()) - 1; l >= 0; --l) {
|
for (int l = int(m_below.lslices_ex.size()) - 1; l >= 0; --l) {
|
||||||
LayerSlice &lslice = m_below.lslices_ex[l];
|
LayerSlice &lslice = m_below.lslices_ex[l];
|
||||||
|
@ -249,6 +288,8 @@ static void connect_layer_slices(
|
||||||
found = true;
|
found = true;
|
||||||
}
|
}
|
||||||
if (found) {
|
if (found) {
|
||||||
|
assert(i >= 0 && i < m_below.lslices_ex.size());
|
||||||
|
assert(j >= 0 && j < m_above.lslices_ex.size());
|
||||||
// Subtract area of holes from the area of outer contour.
|
// Subtract area of holes from the area of outer contour.
|
||||||
double area = ClipperLib_Z::Area(polynode.Contour);
|
double area = ClipperLib_Z::Area(polynode.Contour);
|
||||||
for (int icontour = 0; icontour < polynode.ChildCount(); ++ icontour)
|
for (int icontour = 0; icontour < polynode.ChildCount(); ++ icontour)
|
||||||
|
@ -340,9 +381,9 @@ static void connect_layer_slices(
|
||||||
void Layer::build_up_down_graph(Layer& below, Layer& above)
|
void Layer::build_up_down_graph(Layer& below, Layer& above)
|
||||||
{
|
{
|
||||||
coord_t paths_below_offset = 0;
|
coord_t paths_below_offset = 0;
|
||||||
ClipperLib_Z::Paths paths_below = expolygons_to_zpaths(below.lslices, paths_below_offset);
|
ClipperLib_Z::Paths paths_below = expolygons_to_zpaths_shrunk(below.lslices, paths_below_offset);
|
||||||
coord_t paths_above_offset = paths_below_offset + coord_t(below.lslices.size());
|
coord_t paths_above_offset = paths_below_offset + coord_t(below.lslices.size());
|
||||||
ClipperLib_Z::Paths paths_above = expolygons_to_zpaths(above.lslices, paths_above_offset);
|
ClipperLib_Z::Paths paths_above = expolygons_to_zpaths_shrunk(above.lslices, paths_above_offset);
|
||||||
#ifndef NDEBUG
|
#ifndef NDEBUG
|
||||||
coord_t paths_end = paths_above_offset + coord_t(above.lslices.size());
|
coord_t paths_end = paths_above_offset + coord_t(above.lslices.size());
|
||||||
#endif // NDEBUG
|
#endif // NDEBUG
|
||||||
|
|
Loading…
Add table
Reference in a new issue