diff --git a/src/libslic3r/CMakeLists.txt b/src/libslic3r/CMakeLists.txt index 8b162a1d584..0388b1ac037 100644 --- a/src/libslic3r/CMakeLists.txt +++ b/src/libslic3r/CMakeLists.txt @@ -22,6 +22,8 @@ add_library(libslic3r STATIC Config.hpp EdgeGrid.cpp EdgeGrid.hpp + ElephantFootCompensation.cpp + ElephantFootCompensation.hpp ExPolygon.cpp ExPolygon.hpp ExPolygonCollection.cpp diff --git a/src/libslic3r/ClipperUtils.cpp b/src/libslic3r/ClipperUtils.cpp index 55bb4b446ac..3db2f1f0095 100644 --- a/src/libslic3r/ClipperUtils.cpp +++ b/src/libslic3r/ClipperUtils.cpp @@ -1060,6 +1060,22 @@ ClipperLib::Path mittered_offset_path_scaled(const Points &contour, const std::v } } +#if 0 + { + ClipperLib::Path polytmp(out); + unscaleClipperPolygon(polytmp); + Slic3r::Polygon offsetted = ClipperPath_to_Slic3rPolygon(polytmp); + BoundingBox bbox = get_extents(contour); + bbox.merge(get_extents(offsetted)); + static int iRun = 0; + SVG svg(debug_out_path("mittered_offset_path_scaled-%d.svg", iRun ++).c_str(), bbox); + svg.draw_outline(Polygon(contour), "blue", scale_(0.01)); + svg.draw_outline(offsetted, "red", scale_(0.01)); + svg.draw(contour, "blue", scale_(0.03)); + svg.draw((Points)offsetted, "blue", scale_(0.03)); + } +#endif + return out; } diff --git a/src/libslic3r/EdgeGrid.cpp b/src/libslic3r/EdgeGrid.cpp index 5e19c3b0826..52ac4a0aa6d 100644 --- a/src/libslic3r/EdgeGrid.cpp +++ b/src/libslic3r/EdgeGrid.cpp @@ -282,7 +282,11 @@ void EdgeGrid::Grid::create_from_m_contours(coord_t resolution) Visitor(std::vector> &cell_data, std::vector &cells, size_t cols) : cell_data(cell_data), cells(cells), cols(cols), i(0), j(0) {} - void operator()(coord_t iy, coord_t ix) { cell_data[cells[iy*cols + ix].end++] = std::pair(i, j); } + inline bool operator()(coord_t iy, coord_t ix) { + cell_data[cells[iy*cols + ix].end++] = std::pair(i, j); + // Continue traversing the grid along the edge. + return true; + } std::vector> &cell_data; std::vector &cells; diff --git a/src/libslic3r/EdgeGrid.hpp b/src/libslic3r/EdgeGrid.hpp index 38a7d854285..92cee836290 100644 --- a/src/libslic3r/EdgeGrid.hpp +++ b/src/libslic3r/EdgeGrid.hpp @@ -25,6 +25,8 @@ class Grid void create(const ExPolygons &expolygons, coord_t resolution); void create(const ExPolygonCollection &expolygons, coord_t resolution); + const std::vector& contours() const { return m_contours; } + #if 0 // Test, whether the edges inside the grid intersect with the polygons provided. bool intersect(const MultiPoint &polyline, bool closed); @@ -77,7 +79,7 @@ class Grid std::vector> intersecting_edges() const; bool has_intersecting_edges() const; - template void visit_cells_intersecting_line(Slic3r::Point p1, Slic3r::Point p2, FUNCTION func) const + template void visit_cells_intersecting_line(Slic3r::Point p1, Slic3r::Point p2, VISITOR &visitor) const { // End points of the line segment. p1(0) -= m_bbox.min(0); @@ -94,8 +96,7 @@ class Grid assert(ixb >= 0 && size_t(ixb) < m_cols); assert(iyb >= 0 && size_t(iyb) < m_rows); // Account for the end points. - func(iy, ix); - if (ix == ixb && iy == iyb) + if (! visitor(iy, ix) || (ix == ixb && iy == iyb)) // Both ends fall into the same cell. return; // Raster the centeral part of the line. @@ -125,7 +126,8 @@ class Grid ey = int64_t(dx) * m_resolution; iy += 1; } - func(iy, ix); + if (! visitor(iy, ix)) + return; } while (ix != ixb || iy != iyb); } else { @@ -143,7 +145,8 @@ class Grid ey = int64_t(dx) * m_resolution; iy -= 1; } - func(iy, ix); + if (! visitor(iy, ix)) + return; } while (ix != ixb || iy != iyb); } } @@ -165,7 +168,8 @@ class Grid ey = int64_t(dx) * m_resolution; iy += 1; } - func(iy, ix); + if (! visitor(iy, ix)) + return; } while (ix != ixb || iy != iyb); } else { @@ -197,7 +201,8 @@ class Grid ey = int64_t(dx) * m_resolution; iy -= 1; } - func(iy, ix); + if (! visitor(iy, ix)) + return; } while (ix != ixb || iy != iyb); } } diff --git a/src/libslic3r/ElephantFootCompensation.cpp b/src/libslic3r/ElephantFootCompensation.cpp new file mode 100644 index 00000000000..b23854f113b --- /dev/null +++ b/src/libslic3r/ElephantFootCompensation.cpp @@ -0,0 +1,320 @@ +#include "clipper/clipper_z.hpp" + +#include "libslic3r.h" +#include "EdgeGrid.hpp" +#include "ExPolygon.hpp" +#include "ElephantFootCompensation.hpp" +#include "Flow.hpp" +#include "Geometry.hpp" +#include "SVG.hpp" + +#include +#include + +// #define CONTOUR_DISTANCE_DEBUG_SVG + +namespace Slic3r { + +struct ResampledPoint { + ResampledPoint(size_t idx_src, bool interpolated, double curve_parameter) : idx_src(idx_src), interpolated(interpolated), curve_parameter(curve_parameter) {} + + size_t idx_src; + // Is this point interpolated or initial? + bool interpolated; + // Euclidean distance along the curve from the 0th point. + double curve_parameter; +}; + +std::vector contour_distance(const EdgeGrid::Grid &grid, const size_t idx_contour, const Slic3r::Points &contour, const std::vector &resampled_point_parameters, double search_radius) +{ + assert(! contour.empty()); + assert(contour.size() >= 2); + + std::vector out; + + if (contour.size() > 2) + { +#ifdef CONTOUR_DISTANCE_DEBUG_SVG + static int iRun = 0; + ++ iRun; + BoundingBox bbox = get_extents(contour); + bbox.merge(grid.bbox()); + ExPolygon expoly_grid; + expoly_grid.contour = Polygon(*grid.contours().front()); + for (size_t i = 1; i < grid.contours().size(); ++ i) + expoly_grid.holes.emplace_back(Polygon(*grid.contours()[i])); +#endif + struct Visitor { + Visitor(const EdgeGrid::Grid &grid, const size_t idx_contour, const std::vector &resampled_point_parameters, double dist_same_contour_reject) : + grid(grid), idx_contour(idx_contour), resampled_point_parameters(resampled_point_parameters), dist_same_contour_reject(dist_same_contour_reject) {} + + void init(const size_t aidx_point_start, const Point &apt_start, Vec2d dir, const double radius) { + this->idx_point_start = aidx_point_start; + this->pt = apt_start.cast() + SCALED_EPSILON * dir; + dir *= radius; + this->pt_start = this->pt.cast(); + // Trim the vector by the grid's bounding box. + const BoundingBox &bbox = this->grid.bbox(); + double t = 1.; + for (size_t axis = 0; axis < 2; ++ axis) { + double dx = std::abs(dir(axis)); + if (dx >= EPSILON) { + double tedge = (dir(axis) > 0) ? (double(bbox.max(axis)) - EPSILON - this->pt(axis)) : (this->pt(axis) - double(bbox.min(axis)) - EPSILON); + if (tedge < dx) + t = tedge / dx; + } + } + this->dir = dir; + if (t < 1.) + dir *= t; + this->pt_end = (this->pt + dir).cast(); + this->t_min = 1.; + } + + bool operator()(coord_t iy, coord_t ix) { + // Called with a row and colum of the grid cell, which is intersected by a line. + auto cell_data_range = this->grid.cell_data_range(iy, ix); + bool valid = true; + for (auto it_contour_and_segment = cell_data_range.first; it_contour_and_segment != cell_data_range.second; ++ it_contour_and_segment) { + // End points of the line segment and their vector. + auto segment = this->grid.segment(*it_contour_and_segment); + if (Geometry::segments_intersect(segment.first, segment.second, this->pt_start, this->pt_end)) { + // The two segments intersect. Calculate the intersection. + Vec2d pt2 = segment.first.cast(); + Vec2d dir2 = segment.second.cast() - pt2; + Vec2d vptpt2 = pt - pt2; + double denom = dir(0) * dir2(1) - dir2(0) * dir(1); + + if (std::abs(denom) >= EPSILON) { + double t = cross2(dir2, vptpt2) / denom; + assert(t > 0. && t <= 1.); + bool this_valid = true; + if (it_contour_and_segment->first == idx_contour) { + // The intersected segment originates from the same contour as the starting point. + // Reject the intersection if it is close to the starting point. + // Find the start and end points of this segment + double param_lo = resampled_point_parameters[idx_point_start].curve_parameter; + double param_hi; + double param_end = resampled_point_parameters.back().curve_parameter; + { + const Slic3r::Points &ipts = *grid.contours()[it_contour_and_segment->first]; + size_t ipt = it_contour_and_segment->second; + ResampledPoint key(ipt, false, 0.); + auto lower = [](const ResampledPoint& l, const ResampledPoint r) { return l.idx_src < r.idx_src || (l.idx_src == r.idx_src && int(l.interpolated) > int(r.interpolated)); }; + auto it = std::lower_bound(resampled_point_parameters.begin(), resampled_point_parameters.end(), key, lower); + assert(it != resampled_point_parameters.end() && it->idx_src == ipt && ! it->interpolated); + double t2 = cross2(dir, vptpt2) / denom; + assert(t2 >= 0. && t2 <= 1.); + if (++ ipt == ipts.size()) + param_hi = t2 * dir2.norm(); + else + param_hi = it->curve_parameter + t2 * dir2.norm(); + } + if (param_lo > param_hi) + std::swap(param_lo, param_hi); + assert(param_lo >= 0. && param_lo <= param_end); + assert(param_hi >= 0. && param_hi <= param_end); + this_valid = param_hi > param_lo + dist_same_contour_reject && param_hi - param_end < param_lo - dist_same_contour_reject; + } + if (t < this->t_min) { + this->t_min = t; + valid = this_valid; + } + } + } + if (! valid) + this->t_min = 1.; + } + // Continue traversing the grid along the edge. + return true; + } + + const EdgeGrid::Grid &grid; + const size_t idx_contour; + const std::vector &resampled_point_parameters; + const double dist_same_contour_reject; + + size_t idx_point_start; + Point pt_start; + Point pt_end; + Vec2d pt; + Vec2d dir; + // Minium parameter along the vector (pt_end - pt_start). + double t_min; + } visitor(grid, idx_contour, resampled_point_parameters, search_radius); + + const Point *pt_this = &contour.back(); + size_t idx_pt_this = contour.size() - 1; + const Point *pt_prev = pt_this - 1; + // perpenduclar vector + auto perp = [](const Vec2d& v) -> Vec2d { return Vec2d(v.y(), -v.x()); }; + Vec2d vprev = (*pt_this - *pt_prev).cast().normalized(); + out.reserve(contour.size() + 1); + for (const Point &pt_next : contour) { + Vec2d vnext = (pt_next - *pt_this).cast().normalized(); + Vec2d dir = - (perp(vprev) + perp(vnext)).normalized(); + Vec2d dir_perp = perp(dir); + double cross = cross2(vprev, vnext); + double dot = vprev.dot(vnext); + double a = (cross < 0 || dot > 0.5) ? (M_PI / 3.) : (0.48 * acos(std::min(1., - dot))); + // Throw rays, collect distances. + std::vector distances; + int num_rays = 15; + +#ifdef CONTOUR_DISTANCE_DEBUG_SVG + SVG svg(debug_out_path("contour_distance_raycasted-%d-%d.svg", iRun, &pt_next - contour.data()).c_str(), bbox); + svg.draw(expoly_grid); + svg.draw_outline(Polygon(contour), "blue", scale_(0.01)); + svg.draw(*pt_this, "red", scale_(0.1)); +#endif /* CONTOUR_DISTANCE_DEBUG_SVG */ + + for (int i = - num_rays + 1; i < num_rays; ++ i) { + double angle = a * i / (int)num_rays; + double c = cos(angle); + double s = sin(angle); + Vec2d v = c * dir + s * dir_perp; + visitor.init(idx_pt_this, *pt_this, v, search_radius); + grid.visit_cells_intersecting_line(visitor.pt_start, visitor.pt_end, visitor); + distances.emplace_back(visitor.t_min); +#ifdef CONTOUR_DISTANCE_DEBUG_SVG + svg.draw(Line(visitor.pt_start, visitor.pt_end), "yellow", scale_(0.01)); + if (visitor.t_min < 1.) { + Vec2d pt = visitor.pt + visitor.dir * visitor.t_min; + svg.draw(Point(pt), "red", scale_(0.1)); + } +#endif /* CONTOUR_DISTANCE_DEBUG_SVG */ + } +#ifdef CONTOUR_DISTANCE_DEBUG_SVG + svg.Close(); +#endif /* CONTOUR_DISTANCE_DEBUG_SVG */ + std::sort(distances.begin(), distances.end()); +#if 0 + double median = distances[distances.size() / 2]; + double standard_deviation = 0; + for (double d : distances) + standard_deviation += (d - median) * (d - median); + standard_deviation = sqrt(standard_deviation / (distances.size() - 1)); + double avg = 0; + size_t cnt = 0; + for (double d : distances) + if (d > median - standard_deviation - EPSILON && d < median + standard_deviation + EPSILON) { + avg += d; + ++ cnt; + } + avg /= double(cnt); + out.emplace_back(float(avg * search_radius)); +#else + out.emplace_back(float(distances.front() * search_radius)); +#endif +#ifdef CONTOUR_DISTANCE_DEBUG_SVG + printf("contour_distance_raycasted-%d-%d.svg - distance %lf\n", iRun, &pt_next - contour.data(), unscale(out.back())); +#endif /* CONTOUR_DISTANCE_DEBUG_SVG */ + pt_this = &pt_next; + idx_pt_this = &pt_next - contour.data(); + vprev = vnext; + } + // Rotate the vector by one item. + out.emplace_back(out.front()); + out.erase(out.begin()); + } + + return out; +} + +Points resample_polygon(const Points &contour, double dist, std::vector &resampled_point_parameters) +{ + Points out; + out.reserve(contour.size()); + resampled_point_parameters.reserve(contour.size()); + if (contour.size() > 2) { + Vec2d pt_prev = contour.back().cast(); + for (const Point &pt : contour) { + size_t idx_this = &pt - contour.data(); + const Vec2d pt_this = pt.cast(); + const Vec2d v = pt_this - pt_prev; + const double l = v.norm(); + const size_t n = size_t(ceil(l / dist)); + const double l_step = l / n; + for (size_t i = 1; i < n; ++ i) { + double interpolation_parameter = double(i) / n; + Vec2d new_pt = pt_prev + v * interpolation_parameter; + out.emplace_back(new_pt.cast()); + resampled_point_parameters.emplace_back(idx_this, true, l_step); + } + out.emplace_back(pt); + resampled_point_parameters.emplace_back(idx_this, false, l_step); + pt_prev = pt_this; + } + for (size_t i = 1; i < resampled_point_parameters.size(); ++i) + resampled_point_parameters[i].curve_parameter += resampled_point_parameters[i - 1].curve_parameter; + } + return out; +} + +static inline void smooth_compensation(std::vector &compensation, float strength, size_t num_iterations) +{ + std::vector out(compensation); + for (size_t iter = 0; iter < num_iterations; ++ iter) { + for (size_t i = 0; i < compensation.size(); ++ i) { + float prev = (i == 0) ? compensation.back() : compensation[i - 1]; + float next = (i + 1 == compensation.size()) ? compensation.front() : compensation[i + 1]; + float laplacian = compensation[i] * (1.f - strength) + 0.5f * strength * (prev + next); + // Compensations are negative. Only apply the laplacian if it leads to lower compensation. + out[i] = std::max(laplacian, compensation[i]); + } + out.swap(compensation); + } +} + +ExPolygon elephant_foot_compensation(const ExPolygon &input_expoly, const Flow &external_perimeter_flow, const double compensation) +{ + // The contour shall be wide enough to apply the external perimeter plus compensation on both sides. + double min_contour_width = double(external_perimeter_flow.scaled_width() + external_perimeter_flow.scaled_spacing()); + double scaled_compensation = scale_(compensation); + double min_contour_width_compensated = min_contour_width + 2. * scaled_compensation; + // Make the search radius a bit larger for the averaging in contour_distance over a fan of rays to work. + double search_radius = min_contour_width_compensated + min_contour_width * 0.5; + + EdgeGrid::Grid grid; + ExPolygon simplified = input_expoly.simplify(SCALED_EPSILON).front(); + BoundingBox bbox = get_extents(simplified.contour); + bbox.offset(SCALED_EPSILON); + grid.set_bbox(bbox); + grid.create(simplified, coord_t(0.7 * search_radius)); + std::vector> deltas; + deltas.reserve(simplified.holes.size() + 1); + ExPolygon resampled(simplified); + for (size_t idx_contour = 0; idx_contour <= simplified.holes.size(); ++ idx_contour) { + Polygon &poly = (idx_contour == 0) ? resampled.contour : resampled.holes[idx_contour - 1]; + std::vector resampled_point_parameters; + poly.points = resample_polygon(poly.points, scale_(0.5), resampled_point_parameters); + std::vector dists = contour_distance(grid, idx_contour, poly.points, resampled_point_parameters, search_radius); + for (float &d : dists) { +// printf("Point %d, Distance: %lf\n", int(&d - dists.data()), unscale(d)); + // Convert contour width to available compensation distance. + if (d < min_contour_width) + d = 0.f; + else if (d > min_contour_width_compensated) + d = - float(scaled_compensation); + else + d = - (d - float(min_contour_width)) / 2.f; + assert(d >= - float(scaled_compensation) && d <= 0.f); + } + smooth_compensation(dists, 0.4f, 10); + deltas.emplace_back(dists); + } + + ExPolygons out = variable_offset_inner_ex(resampled, deltas, 2.); + return out.front(); +} + +ExPolygons elephant_foot_compensation(const ExPolygons &input, const Flow &external_perimeter_flow, const double compensation) +{ + ExPolygons out; + out.reserve(input.size()); + for (const ExPolygon &expoly : input) + out.emplace_back(elephant_foot_compensation(expoly, external_perimeter_flow, compensation)); + return out; +} + +} // namespace Slic3r diff --git a/src/libslic3r/ElephantFootCompensation.hpp b/src/libslic3r/ElephantFootCompensation.hpp new file mode 100644 index 00000000000..0119df1af5c --- /dev/null +++ b/src/libslic3r/ElephantFootCompensation.hpp @@ -0,0 +1,16 @@ +#ifndef slic3r_ElephantFootCompensation_hpp_ +#define slic3r_ElephantFootCompensation_hpp_ + +#include "libslic3r.h" +#include + +namespace Slic3r { + +class Flow; + +ExPolygon elephant_foot_compensation(const ExPolygon &input, const Flow &external_perimeter_flow, const double compensation); +ExPolygons elephant_foot_compensation(const ExPolygons &input, const Flow &external_perimeter_flow, const double compensation); + +} // Slic3r + +#endif /* slic3r_ElephantFootCompensation_hpp_ */ diff --git a/src/libslic3r/GCode.cpp b/src/libslic3r/GCode.cpp index 26d9c90f8a5..26d2fc649d2 100644 --- a/src/libslic3r/GCode.cpp +++ b/src/libslic3r/GCode.cpp @@ -543,7 +543,7 @@ std::vector GCode::collect_layers_to_print(const PrintObjec //FIXME should we use the printing extruders instead? double gap_over_supports = object.config().support_material_contact_distance; // FIXME should we test object.config().support_material_synchronize_layers ? Currently the support layers are synchronized with object layers iff soluble supports. - assert(gap_over_supports != 0. || object.config().support_material_synchronize_layers); + assert(! object.config().support_material || gap_over_supports != 0. || object.config().support_material_synchronize_layers); if (gap_over_supports != 0.) { gap_over_supports = std::max(0., gap_over_supports); // Not a soluble support, diff --git a/src/libslic3r/Layer.cpp b/src/libslic3r/Layer.cpp index 74deabf3e7e..53a7f2fc454 100644 --- a/src/libslic3r/Layer.cpp +++ b/src/libslic3r/Layer.cpp @@ -88,8 +88,12 @@ ExPolygons Layer::merged(float offset_scaled) const offset_scaled2 = float(- EPSILON); } Polygons polygons; - for (LayerRegion *layerm : m_regions) - append(polygons, offset(to_expolygons(layerm->slices.surfaces), offset_scaled)); + for (LayerRegion *layerm : m_regions) { + const PrintRegionConfig &config = layerm->region()->config(); + // Our users learned to bend Slic3r to produce empty volumes to act as subtracters. Only add the region if it is non-empty. + if (config.bottom_solid_layers > 0 || config.top_solid_layers > 0 || config.fill_density > 0. || config.perimeters > 0) + append(polygons, offset(to_expolygons(layerm->slices.surfaces), offset_scaled)); + } ExPolygons out = union_ex(polygons); if (offset_scaled2 != 0.f) out = offset_ex(out, offset_scaled2); diff --git a/src/libslic3r/Polygon.cpp b/src/libslic3r/Polygon.cpp index bd5ec3de50f..e1e2991444d 100644 --- a/src/libslic3r/Polygon.cpp +++ b/src/libslic3r/Polygon.cpp @@ -254,6 +254,11 @@ Point Polygon::point_projection(const Point &point) const return proj; } +BoundingBox get_extents(const Points &points) +{ + return BoundingBox(points); +} + BoundingBox get_extents(const Polygon &poly) { return poly.bounding_box(); diff --git a/src/libslic3r/Polygon.hpp b/src/libslic3r/Polygon.hpp index 4d53e97e1cb..8230b49f8a6 100644 --- a/src/libslic3r/Polygon.hpp +++ b/src/libslic3r/Polygon.hpp @@ -70,7 +70,7 @@ class Polygon : public MultiPoint inline bool operator==(const Polygon &lhs, const Polygon &rhs) { return lhs.points == rhs.points; } inline bool operator!=(const Polygon &lhs, const Polygon &rhs) { return lhs.points != rhs.points; } - +extern BoundingBox get_extents(const Points &points); extern BoundingBox get_extents(const Polygon &poly); extern BoundingBox get_extents(const Polygons &polygons); extern BoundingBox get_extents_rotated(const Polygon &poly, double angle); diff --git a/src/libslic3r/PolygonTrimmer.cpp b/src/libslic3r/PolygonTrimmer.cpp index a2779b6667c..2c4e06fc584 100644 --- a/src/libslic3r/PolygonTrimmer.cpp +++ b/src/libslic3r/PolygonTrimmer.cpp @@ -16,7 +16,7 @@ TrimmedLoop trim_loop(const Polygon &loop, const EdgeGrid::Grid &grid) struct Visitor { Visitor(const EdgeGrid::Grid &grid, const Slic3r::Point *pt_prev, const Slic3r::Point *pt_this) : grid(grid), pt_prev(pt_prev), pt_this(pt_this) {} - void operator()(coord_t iy, coord_t ix) { + bool operator()(coord_t iy, coord_t ix) { // Called with a row and colum of the grid cell, which is intersected by a line. auto cell_data_range = grid.cell_data_range(iy, ix); for (auto it_contour_and_segment = cell_data_range.first; it_contour_and_segment != cell_data_range.second; ++ it_contour_and_segment) { @@ -26,6 +26,8 @@ TrimmedLoop trim_loop(const Polygon &loop, const EdgeGrid::Grid &grid) // The two segments intersect. Add them to the output. } } + // Continue traversing the grid along the edge. + return true; } const EdgeGrid::Grid &grid; diff --git a/src/libslic3r/PrintObject.cpp b/src/libslic3r/PrintObject.cpp index d87e63c2733..b334b70fc7d 100644 --- a/src/libslic3r/PrintObject.cpp +++ b/src/libslic3r/PrintObject.cpp @@ -1,6 +1,7 @@ #include "Print.hpp" #include "BoundingBox.hpp" #include "ClipperUtils.hpp" +#include "ElephantFootCompensation.hpp" #include "Geometry.hpp" #include "I18N.hpp" #include "SupportMaterial.hpp" @@ -1769,6 +1770,7 @@ void PrintObject::_slice(const std::vector &layer_height_profile) Layer *layer = m_layers[layer_id]; // Apply size compensation and perform clipping of multi-part objects. float delta = float(scale_(m_config.xy_size_compensation.value)); + //FIXME only apply the compensation if no raft is enabled. float elephant_foot_compensation = 0.f; if (layer_id == 0) elephant_foot_compensation = float(scale_(m_config.elefant_foot_compensation.value)); @@ -1789,19 +1791,8 @@ void PrintObject::_slice(const std::vector &layer_height_profile) to_expolygons(std::move(layerm->slices.surfaces)) : offset_ex(to_expolygons(std::move(layerm->slices.surfaces)), delta); // Apply the elephant foot compensation. - if (elephant_foot_compensation > 0) { - float elephant_foot_spacing = float(layerm->flow(frExternalPerimeter).scaled_elephant_foot_spacing()); - float external_perimeter_nozzle = float(scale_(this->print()->config().nozzle_diameter.get_at(layerm->region()->config().perimeter_extruder.value - 1))); - // Apply the elephant foot compensation by steps of 1/10 nozzle diameter. - float steps = std::ceil(elephant_foot_compensation / (0.1f * external_perimeter_nozzle)); - size_t nsteps = size_t(steps); - float step = elephant_foot_compensation / steps; - for (size_t i = 0; i < nsteps; ++ i) { - Polygons tmp = offset(expolygons, - step); - append(tmp, diff(to_polygons(expolygons), offset(offset_ex(expolygons, -elephant_foot_spacing - step), elephant_foot_spacing + step))); - expolygons = union_ex(tmp); - } - } + if (elephant_foot_compensation > 0) + expolygons = union_ex(Slic3r::elephant_foot_compensation(expolygons, layerm->flow(frExternalPerimeter), unscale(elephant_foot_compensation))); layerm->slices.set(std::move(expolygons), stInternal); } } else { @@ -1825,33 +1816,17 @@ void PrintObject::_slice(const std::vector &layer_height_profile) layerm->slices.set(std::move(slices), stInternal); } } - if (delta < 0.f) { + if (delta < 0.f || elephant_foot_compensation > 0.f) { // Apply the negative XY compensation. - Polygons trimming = offset(layer->merged(float(EPSILON)), delta - float(EPSILON)); + Polygons trimming; + if (elephant_foot_compensation > 0.f) { + trimming = to_polygons(Slic3r::elephant_foot_compensation(offset_ex(layer->merged(float(EPSILON)), std::min(delta, 0.f) - float(EPSILON)), + layer->m_regions.front()->flow(frExternalPerimeter), unscale(elephant_foot_compensation))); + } else + trimming = offset(layer->merged(float(EPSILON)), delta - float(EPSILON)); for (size_t region_id = 0; region_id < layer->m_regions.size(); ++ region_id) layer->m_regions[region_id]->trim_surfaces(trimming); } - if (elephant_foot_compensation > 0.f) { - // Apply the elephant foot compensation. - std::vector elephant_foot_spacing; - elephant_foot_spacing.reserve(layer->m_regions.size()); - float external_perimeter_nozzle = 0.f; - for (size_t region_id = 0; region_id < layer->m_regions.size(); ++ region_id) { - LayerRegion *layerm = layer->m_regions[region_id]; - elephant_foot_spacing.emplace_back(float(layerm->flow(frExternalPerimeter).scaled_elephant_foot_spacing())); - external_perimeter_nozzle += float(scale_(this->print()->config().nozzle_diameter.get_at(layerm->region()->config().perimeter_extruder.value - 1))); - } - external_perimeter_nozzle /= (float)layer->m_regions.size(); - // Apply the elephant foot compensation by steps of 1/10 nozzle diameter. - float steps = std::ceil(elephant_foot_compensation / (0.1f * external_perimeter_nozzle)); - size_t nsteps = size_t(steps); - float step = elephant_foot_compensation / steps; - for (size_t i = 0; i < nsteps; ++ i) { - Polygons trimming_polygons = offset(layer->merged(float(EPSILON)), - step - float(EPSILON)); - for (size_t region_id = 0; region_id < layer->m_regions.size(); ++ region_id) - layer->m_regions[region_id]->elephant_foot_compensation_step(elephant_foot_spacing[region_id] + step, trimming_polygons); - } - } } // Merge all regions' slices to get islands, chain them by a shortest path. layer->make_slices(); diff --git a/src/libslic3r/SVG.cpp b/src/libslic3r/SVG.cpp index 03f55802efc..6e4b973eac9 100644 --- a/src/libslic3r/SVG.cpp +++ b/src/libslic3r/SVG.cpp @@ -368,6 +368,10 @@ void SVG::export_expolygons(const char *path, const std::vector 0) + for (const ExPolygon &expoly : exp_with_attr.first) + svg.draw((Points)expoly, exp_with_attr.second.color_points, exp_with_attr.second.radius_points); svg.Close(); } diff --git a/src/libslic3r/SVG.hpp b/src/libslic3r/SVG.hpp index 3a56021963d..c1b387554c7 100644 --- a/src/libslic3r/SVG.hpp +++ b/src/libslic3r/SVG.hpp @@ -105,19 +105,25 @@ class SVG const std::string &color_contour, const std::string &color_holes, const coord_t outline_width = scale_(0.05), - const float fill_opacity = 0.5f) : + const float fill_opacity = 0.5f, + const std::string &color_points = "black", + const coord_t radius_points = 0) : color_fill (color_fill), color_contour (color_contour), color_holes (color_holes), outline_width (outline_width), - fill_opacity (fill_opacity) + fill_opacity (fill_opacity), + color_points (color_points), + radius_points (radius_points) {} std::string color_fill; std::string color_contour; std::string color_holes; + std::string color_points; coord_t outline_width; float fill_opacity; + coord_t radius_points; }; static void export_expolygons(const char *path, const std::vector> &expolygons_with_attributes); diff --git a/tests/libslic3r/CMakeLists.txt b/tests/libslic3r/CMakeLists.txt index c2a4b4f781a..02764589b01 100644 --- a/tests/libslic3r/CMakeLists.txt +++ b/tests/libslic3r/CMakeLists.txt @@ -5,7 +5,7 @@ add_executable(${_TEST_NAME}_tests test_clipper_offset.cpp test_clipper_utils.cpp test_config.cpp -# test_elephant_foot_compensation.cpp + test_elephant_foot_compensation.cpp test_geometry.cpp test_polygon.cpp test_stl.cpp diff --git a/tests/libslic3r/test_elephant_foot_compensation.cpp b/tests/libslic3r/test_elephant_foot_compensation.cpp new file mode 100644 index 00000000000..a1b893975e6 --- /dev/null +++ b/tests/libslic3r/test_elephant_foot_compensation.cpp @@ -0,0 +1,304 @@ +#include + +#include +#include + +#include "libslic3r/ClipperUtils.hpp" +#include "libslic3r/ElephantFootCompensation.hpp" +#include "libslic3r/ExPolygon.hpp" +#include "libslic3r/Flow.hpp" +#include "libslic3r/SVG.hpp" + +#include "libslic3r/VoronoiOffset.hpp" + +using namespace Slic3r; + +// #define TESTS_EXPORT_SVGS + +namespace Slic3r { + ClipperLib::Path mittered_offset_path_scaled(const Points& contour, const std::vector& deltas, double miter_limit); + + static Points mittered_offset_path_scaled_points(const Points& contour, const std::vector& deltas, double miter_limit) + { + Points out; + ClipperLib::Path scaled = mittered_offset_path_scaled(contour, deltas, miter_limit); + for (ClipperLib::IntPoint& pt : scaled) { + pt.X += CLIPPER_OFFSET_SCALE_ROUNDING_DELTA; + pt.Y += CLIPPER_OFFSET_SCALE_ROUNDING_DELTA; + pt.X >>= CLIPPER_OFFSET_POWER_OF_2; + pt.Y >>= CLIPPER_OFFSET_POWER_OF_2; + out.emplace_back(coord_t(pt.X), coord_t(pt.Y)); + } + return out; + } +} + +static ExPolygon spirograph_gear_1mm() +{ + ExPolygon out; + out.contour.points = { { 8989059, 1015976 }, { 9012502, 1051010 }, { 9224741, 1786512 }, { 9232060, 1811874 }, { 9222459, 2132217 }, { 10263301, 2241715 }, { 10318693, 1936696 }, { 10320603, 1926178 }, { 10680972, 1250945 }, { 10693399, 1227661 }, { 10723616, 1198273 }, { 11599898, 346008 }, { 11616108, 351267 }, { 12086183, 503769 }, { 12293780, 1708518 }, { 12300939, 1750061 }, { 12195899, 2508234 }, { 12192277, 2534378 }, { 12053161, 2823089 }, { 12959357, 3346344 }, { 13133980, 3090414 }, { 13140002, 3081589 }, { 13160830, 3065371 }, { 13764842, 2595047 }, { 13804400, 2580484 }, { 14951581, 2158173 }, { 14964243, 2169573 }, { 15331439, 2500198 }, { 15031347, 3685330 }, { 15020999, 3726196 }, { 14616409, 4376044 }, { 14602458, 4398453 }, { 14594311, 4405358 }, { 14358060, 4605591 }, { 14973020, 5452271 }, { 15245662, 5283768 }, { 15271287, 5277427 }, { 16014420, 5093552 }, { 16056481, 5096336 }, { 17276242, 5177094 }, { 17477040, 5628611 }, { 17483964, 5644181 }, { 16727991, 6604475 }, { 16701923, 6637589 }, { 16680060, 6652386 }, { 16046043, 7081528 }, { 16035789, 7084529 }, { 15738421, 7171570 }, { 15955998, 8195191 }, { 16273777, 8152008 }, { 16299760, 8156636 }, { 17053280, 8290848 }, { 17090572, 8310500 }, { 18172024, 8880417 }, { 18172024, 9391815 }, { 18134732, 9411467 }, { 17053280, 9981369 }, { 17027297, 9985997 }, { 16273777, 10120209 }, { 16263184, 10118770 }, { 15955998, 10077026 }, { 15738421, 11100647 }, { 16046043, 11190704 }, { 16067906, 11205502 }, { 16701923, 11634644 }, { 17457896, 12594938 }, { 17483964, 12628052 }, { 17283166, 13079569 }, { 17276242, 13095139 }, { 17234181, 13097923 }, { 16014420, 13178665 }, { 15988795, 13172324 }, { 15245662, 12988449 }, { 15236574, 12982832 }, { 14973020, 12819946 }, { 14358060, 13666641 }, { 14602458, 13873764 }, { 15007048, 14523627 }, { 15020999, 14546036 }, { 15321091, 15731152 }, { 15331439, 15772018 }, { 15318777, 15783419 }, { 14951581, 16114059 }, { 14912023, 16099496 }, { 13764842, 15677170 }, { 13744014, 15660952 }, { 13140002, 15190628 }, { 12959357, 14925887 }, { 12053161, 15449127 }, { 12187640, 15728230 }, { 12192277, 15737854 }, { 12297317, 16496013 }, { 12300939, 16522156 }, { 12093342, 17726920 }, { 12086183, 17768464 }, { 12069973, 17773722 }, { 11599898, 17926208 }, { 11569681, 17896820 }, { 10693399, 17044556 }, { 10333030, 16369337 }, { 10320603, 16346054 }, { 10263301, 16030502 }, { 9222459, 16140015 }, { 9231740, 16449664 }, { 9232060, 16460342 }, { 9019821, 17195859 }, { 9012502, 17221222 }, { 8332646, 18237183 }, { 8309203, 18272216 }, { 8292260, 18270438 }, { 7800922, 18218872 }, { 7347225, 17083700 }, { 7331580, 17044556 }, { 7276730, 16280940 }, { 7274839, 16254608 }, { 7350758, 15943314 }, { 6355663, 15619904 }, { 6238078, 15906603 }, { 6234023, 15916489 }, { 5741039, 16501967 }, { 5724040, 16522156 }, { 5688377, 16544630 }, { 4654144, 17196380 }, { 4639383, 17187857 }, { 4211318, 16940704 }, { 4258533, 15719288 }, { 4260162, 15677170 }, { 4520697, 14957214 }, { 4529681, 14932388 }, { 4725821, 14678955 }, { 3948022, 13978775 }, { 3716296, 14200317 }, { 3692552, 14211841 }, { 3003980, 14546036 }, { 2962267, 14552057 }, { 1752599, 14726654 }, { 1462059, 14326969 }, { 1452041, 14313187 }, { 1991943, 13216482 }, { 2010560, 13178665 }, { 2541453, 12627039 }, { 2559760, 12608017 }, { 2569167, 12602956 }, { 2841979, 12456177 }, { 2416404, 11500290 }, { 2114701, 11608368 }, { 2088313, 11609244 }, { 1323058, 11634644 }, { 1282503, 11623175 }, { 106399, 11290588 }, { 3546, 10807167 }, { -1, 10790497 }, { 32389, 10763526 }, { 971700, 9981369 }, { 996159, 9971434 }, { 1705482, 9683334 }, { 1716131, 9682534 }, { 2024962, 9659348 }, { 2024962, 8612869 }, { 1705482, 8588898 }, { 1681022, 8578963 }, { 971700, 8290848 }, { 939310, 8263878 }, { -1, 7481735 }, { 102852, 6998299 }, { 106399, 6981629 }, { 146954, 6970160 }, { 1323058, 6637589 }, { 1349446, 6638464 }, { 2114701, 6663849 }, { 2124758, 6667452 }, { 2416404, 6771927 }, { 2841979, 5816056 }, { 2559760, 5664200 }, { 2028867, 5112573 }, { 2010560, 5093552 }, { 1470658, 3996848 }, { 1452041, 3959030 }, { 1742580, 3559360 }, { 1752599, 3545578 }, { 1794312, 3551599 }, { 3003980, 3726196 }, { 3027724, 3737720 }, { 3716296, 4071915 }, { 3724020, 4079299 }, { 3948022, 4293442 }, { 4725822, 3593262 }, { 4536219, 3348276 }, { 4529681, 3339829 }, { 4269146, 2619873 }, { 4260162, 2595047 }, { 4212946, 1373645 }, { 4211318, 1331528 }, { 4226079, 1323005 }, { 4654144, 1075852 }, { 4689807, 1098325 }, { 5724040, 1750061 }, { 6217024, 2335539 }, { 6234023, 2355728 }, { 6355663, 2652329 }, { 7350759, 2328903 }, { 7277369, 2027985 }, { 7274839, 2017609 }, { 7329689, 1253993 }, { 7331580, 1227661 }, { 7785277, 92503 }, { 7800922, 53360 }, { 7817864, 51581 }, { 8309203, 0 } }; + out.holes.emplace_back(Slic3r::Points({ {8982039, 9119734}, {8675233, 9160126}, {8654832, 9168577}, {8368934, 9287003}, {8351415, 9300446}, {8105907, 9488831}, {7917523, 9734328}, {7904081, 9751846}, {7785658, 10037750}, {7777208, 10058151}, {7736814, 10364949}, {7733932, 10386841}, {7774325, 10693653}, {7777208, 10715546}, {7895630, 11001450}, {7904081, 11021851}, {8092464, 11267363}, {8105907, 11284882}, {8123425, 11298325}, {8368934, 11486710}, {8389335, 11495160}, {8675233, 11613571}, {8697126, 11616453}, {9003932, 11656845}, {9025825, 11653963}, {9332633, 11613571}, {9353034, 11605121}, {9638932, 11486710}, {9656451, 11473267}, {9901958, 11284882}, {10090343, 11039370}, {10103786, 11021851}, {10222209, 10735947}, {10230659, 10715546}, {10271050, 10408734}, {10273932, 10386841}, {10233541, 10080043}, {10230659, 10058151}, {10112236, 9772247}, {10103786, 9751846}, {9915401, 9506349}, {9901958, 9488831}, {9884439, 9475388}, {9638932, 9287003}, {9618531, 9278552}, {9332633, 9160126}, {9310740, 9157244}, {9003932, 9116852} })); + out.holes.emplace_back(Slic3r::Points({ {5301863, 6863631}, {4995055, 6904022}, {4974654, 6912473}, {4688756, 7030899}, {4671237, 7044342}, {4425731, 7232727}, {4237345, 7478225}, {4223903, 7495743}, {4105480, 7781646}, {4097030, 7802048}, {4056638, 8108859}, {4053756, 8130753}, {4094147, 8437550}, {4097030, 8459442}, {4215452, 8745346}, {4223903, 8765747}, {4412288, 9011259}, {4425731, 9028778}, {4443249, 9042221}, {4688756, 9230606}, {4709157, 9239057}, {4995055, 9357483}, {5016948, 9360365}, {5323756, 9400757}, {5345649, 9397875}, {5652456, 9357483}, {5672856, 9349032}, {5958755, 9230606}, {5976273, 9217163}, {6221782, 9028778}, {6410165, 8783266}, {6423608, 8765747}, {6542031, 8479843}, {6550481, 8459442}, {6590874, 8152645}, {6593757, 8130753}, {6553363, 7823941}, {6550481, 7802048}, {6432058, 7516144}, {6423608, 7495743}, {6235224, 7250245}, {6221782, 7232727}, {6204263, 7219284}, {5958755, 7030899}, {5938354, 7022448}, {5652456, 6904022}, {5630563, 6901140}, {5323756, 6860749} })); + out.holes.emplace_back(Slic3r::Points({ {10306044, 5682112}, {9999236, 5722504}, {9978835, 5730953}, {9692937, 5849365}, {9675418, 5862808}, {9429912, 6051194}, {9241527, 6296691}, {9228084, 6314209}, {9109661, 6600113}, {9101211, 6620514}, {9060819, 6927326}, {9057937, 6949219}, {9098329, 7256016}, {9101211, 7277909}, {9219634, 7563812}, {9228084, 7584214}, {9416469, 7829725}, {9429912, 7847245}, {9447431, 7860687}, {9692937, 8049073}, {9713338, 8057523}, {9999236, 8175949}, {10021129, 8178831}, {10327937, 8219223}, {10349830, 8216341}, {10656638, 8175949}, {10677039, 8167498}, {10962937, 8049073}, {10980456, 8035630}, {11225963, 7847245}, {11414346, 7601733}, {11427789, 7584214}, {11546212, 7298310}, {11554662, 7277909}, {11595056, 6971111}, {11597938, 6949219}, {11557544, 6642407}, {11554662, 6620514}, {11436239, 6334610}, {11427789, 6314209}, {11239406, 6068712}, {11225963, 6051194}, {11208444, 6037751}, {10962937, 5849365}, {10942536, 5840915}, {10656638, 5722504}, {10634745, 5719621}, {10327937, 5679230} })); + return out; +} + +// Contour from GH issue #2998. +static ExPolygon box_with_hole_close_to_wall() +{ + ExPolygon out; + out.contour.points = { { 20000000, 20000000}, { 0, 20000000}, { 0, 0}, { 20000000, 0} }; + out.holes.emplace_back(Slic3r::Points( { + { 9905173, 501406}, { 9895707, 501967}, { 9715853, 512640}, { 9706437, 513762}, { 9527531, 535071}, { 9518198, 536749}, { 9340868, 568619}, { 9331651, 570846}, { 9156521, 613166}, + { 9147452, 615935}, { 8975137, 668555}, { 8966248, 671857}, { 8797352, 734593}, { 8788674, 738416}, { 8623792, 811047}, { 8615356, 815377}, { 8455065, 897648}, { 8446900, 902470}, + { 8291765, 994093}, { 8283900, 999390}, { 8134465, 1100042}, { 8126928, 1105796}, { 7983719, 1215124}, { 7976536, 1221315}, { 7840055, 1338934}, { 7833251, 1345539}, { 7703977, 1471037}, + { 7697576, 1478034}, { 7575964, 1610970}, { 7569989, 1618333}, { 7456466, 1758240}, { 7450937, 1765944}, { 7345902, 1912331}, { 7340840, 1920349}, { 7244661, 2072701}, { 7240082, 2081005}, + { 7153097, 2238787}, { 7149019, 2247348}, { 7071534, 2410005}, { 7067970, 2418793}, { 7000257, 2585755}, { 6997220, 2594738}, { 6939517, 2765418}, { 6937018, 2774565}, + { 6889527, 2948365}, { 6887574, 2957644}, { 6850462, 3133951}, { 6849062, 3143330}, { 6822461, 3321526}, { 6821618, 3330971}, { 6805620, 3510430}, { 6805339, 3519909}, + { 6800000, 3700000}, { 6800281, 3709478}, { 6805620, 3889570}, { 6806462, 3899015}, { 6822461, 4078474}, { 6823861, 4087853}, { 6850462, 4266049}, { 6852415, 4275328}, + { 6889527, 4451636}, { 6892027, 4460783}, { 6939517, 4634582}, { 6942554, 4643565}, { 7000257, 4814245}, { 7003821, 4823033}, { 7071534, 4989995}, { 7075612, 4998556}, + { 7153097, 5161214}, { 7157675, 5169518}, { 7244661, 5327300}, { 7249723, 5335318}, { 7345902, 5487670}, { 7351430, 5495374}, { 7456466, 5641761}, { 7462440, 5649124}, + { 7575964, 5789031}, { 7582365, 5796027}, { 7703977, 5928963}, { 7710780, 5935568}, { 7840055, 6061067}, { 7847238, 6067257}, { 7983719, 6184877}, { 7991256, 6190631}, + { 8134465, 6299958}, { 8142330, 6305255}, { 8291765, 6405907}, { 8299930, 6410729}, { 8455065, 6502352}, { 8463501, 6506682}, { 8623792, 6588953}, { 8632470, 6592776}, + { 8797352, 6665407}, { 8806241, 6668708}, { 8975137, 6731445}, { 8984206, 6734214}, { 9156521, 6786834}, { 9165738, 6789061}, { 9340868, 6831381}, { 9350201, 6833058}, + { 9527531, 6864929}, { 9536947, 6866050}, { 9715853, 6887360}, { 9725319, 6887921}, { 9905173, 6898595}, { 10094827, 6898595}, { 10104293, 6898033}, { 10284147, 6887360}, + { 10293563, 6886238}, { 10472469, 6864929}, { 10481802, 6863251}, { 10659132, 6831381}, { 10668349, 6829154}, { 10843479, 6786834}, { 10852548, 6784065}, { 11024863, 6731445}, + { 11033752, 6728143}, { 11202648, 6665407}, { 11211326, 6661584}, { 11376208, 6588953}, { 11384644, 6584623}, { 11544935, 6502352}, { 11553100, 6497530}, { 11708235, 6405907}, + { 11716100, 6400610}, { 11865535, 6299958}, { 11873072, 6294204}, { 12016281, 6184877}, { 12023464, 6178686}, { 12159946, 6061067}, { 12166750, 6054461}, { 12296023, 5928963}, + { 12302424, 5921966}, { 12424036, 5789031}, { 12430011, 5781667}, { 12543534, 5641761}, { 12549062, 5634056}, { 12654099, 5487670}, { 12659161, 5479651}, { 12755340, 5327300}, + { 12759918, 5318995}, { 12846903, 5161214}, { 12850981, 5152653}, { 12928466, 4989995}, { 12932030, 4981208}, { 12999743, 4814245}, { 13002780, 4805262}, { 13060483, 4634582}, + { 13062983, 4625434}, { 13110474, 4451636}, { 13112427, 4442356}, { 13149538, 4266049}, { 13150938, 4256670}, { 13177540, 4078474}, { 13178382, 4069029}, { 13194380, 3889570}, + { 13194661, 3880092}, { 13200000, 3700000}, { 13199719, 3690521}, { 13194380, 3510430}, { 13193538, 3500985}, { 13177540, 3321526}, { 13176140, 3312147}, { 13149538, 3133951}, + { 13147585, 3124672}, { 13110474, 2948365}, { 13107974, 2939217}, { 13060483, 2765418}, { 13057446, 2756435}, { 12999743, 2585755}, { 12996179, 2576968}, { 12928466, 2410005}, + { 12924388, 2401444}, { 12846903, 2238787}, { 12842325, 2230482}, { 12755340, 2072701}, { 12750278, 2064682}, { 12654099, 1912331}, { 12648571, 1904626}, { 12543534, 1758240}, + { 12537559, 1750876}, { 12424036, 1610970}, { 12417635, 1603973}, { 12296023, 1471037}, { 12289219, 1464432}, { 12159946, 1338934}, { 12152763, 1332744}, { 12016281, 1215124}, + { 12008744, 1209370}, { 11865535, 1100042}, { 11857670, 1094745}, { 11708235, 994093}, { 11700070, 989271}, { 11544935, 897648}, { 11536499, 893318}, { 11376208, 811047}, + { 11367530, 807224}, { 11202648, 734593}, { 11193759, 731291}, { 11024863, 668555}, { 11015794, 665786}, { 10843479, 613166}, { 10834262, 610939}, { 10659132, 568619}, + { 10649799, 566941}, { 10472469, 535071}, { 10463053, 533950}, { 10284147, 512640}, { 10274681, 512078}, { 10094827, 501406} + })); + return out; +} + +// Contour from GH issue #2085. +static ExPolygon thin_ring() +{ + ExPolygon out; + out.contour.points = { + { 7805980, 147}, { 8182728, 9400}, { 8188694, 9840}, { 8564533, 37560}, { 8570470, 38292}, { 8944500, 84420}, { 8950394, 85443}, { 9321700, 149880}, + { 9327537, 151191}, { 9695240, 233760}, { 9701005, 235356}, { 10064220, 335870}, { 10069900, 337747}, { 10427740, 455960}, { 10433321, 458113}, { 10784930, 593740}, + { 10790399, 596164}, { 11134930, 748880}, { 11140273, 751570}, { 11476891, 921010}, { 11482096, 923959}, { 11810000, 1109720}, { 11815054, 1112921}, { 12133450, 1314540}, + { 12138341, 1317985}, { 12446450, 1534980}, { 12451166, 1538661}, { 12748270, 1770520}, { 12752800, 1774427}, { 13038160, 2020580}, { 13042492, 2024705}, { 13315430, 2284570}, + { 13575295, 2557508}, { 13579420, 2561840}, { 13825573, 2847201}, { 13829480, 2851730}, { 14061340, 3148834}, { 14065020, 3153550}, { 14282016, 3461660}, { 14285460, 3466550}, + { 14487080, 3784946}, { 14490280, 3790000}, { 14676041, 4117905}, { 14678990, 4123110}, { 14848430, 4459727}, { 14851120, 4465071}, { 15003836, 4809601}, { 15006260, 4815070}, + { 15141887, 5166679}, { 15144040, 5172261}, { 15262254, 5530100}, { 15264130, 5535780}, { 15364645, 5898995}, { 15366240, 5904761}, { 15448809, 6272464}, { 15450120, 6278301}, + { 15514557, 6649607}, { 15515580, 6655501}, { 15561709, 7029530}, { 15562441, 7035467}, { 15590160, 7411306}, { 15590600, 7417272}, { 15599853, 7794020}, { 15600000, 7800000}, + { 15590747, 8176748}, { 15590600, 8182728}, { 15562881, 8558567}, { 15562441, 8564533}, { 15516312, 8938563}, { 15515580, 8944500}, { 15451143, 9315806}, { 15450120, 9321700}, + { 15367551, 9689403}, { 15366240, 9695240}, { 15265725, 10058455}, { 15264130, 10064220}, { 15145916, 10422060}, { 15144040, 10427740}, { 15008413, 10779349}, { 15006260, 10784930}, + { 14853544, 11129461}, { 14851120, 11134930}, { 14681680, 11471548}, { 14678990, 11476891}, { 14493229, 11804795}, { 14490280, 11810000}, { 14288660, 12128396}, { 14285460, 12133450}, + { 14068464, 12441559}, { 14065020, 12446450}, { 13833160, 12743554}, { 13829480, 12748270}, { 13583327, 13033630}, { 13579420, 13038160}, { 13319555, 13311098}, { 13315430, 13315430}, + { 13311098, 13319555}, { 13038160, 13579420}, { 13033630, 13583327}, { 12748270, 13829480}, { 12743554, 13833160}, { 12446450, 14065020}, { 12441559, 14068464}, { 12133450, 14285460}, + { 12128396, 14288660}, { 11810000, 14490280}, { 11804795, 14493229}, { 11476891, 14678990}, { 11471548, 14681680}, { 11134930, 14851120}, { 11129461, 14853544}, { 10784930, 15006260}, + { 10779349, 15008413}, { 10427740, 15144040}, { 10422060, 15145916}, { 10064220, 15264130}, { 10058455, 15265725}, { 9695240, 15366240}, { 9689403, 15367551}, { 9321700, 15450120}, + { 9315806, 15451143}, { 8944500, 15515580}, { 8938563, 15516312}, { 8564533, 15562441}, { 8558567, 15562881}, { 8182728, 15590600}, { 8176748, 15590747}, { 7800000, 15600000}, + { 7794020, 15599853}, { 7417272, 15590600}, { 7411306, 15590160}, { 7035467, 15562441}, { 7029530, 15561709}, { 6655501, 15515580}, { 6649607, 15514557}, { 6278301, 15450120}, + { 6272464, 15448809}, { 5904761, 15366240}, { 5898995, 15364645}, { 5535780, 15264130}, { 5530100, 15262254}, { 5172261, 15144040}, { 5166679, 15141887}, { 4815070, 15006260}, + { 4809601, 15003836}, { 4465071, 14851120}, { 4459727, 14848430}, { 4123110, 14678990}, { 4117905, 14676041}, { 3790000, 14490280}, { 3784946, 14487080}, { 3466550, 14285460}, + { 3461660, 14282016}, { 3153550, 14065020}, { 3148834, 14061340}, { 2851730, 13829480}, { 2847201, 13825573}, { 2561840, 13579420}, { 2557508, 13575295}, { 2284570, 13315430}, + { 2024705, 13042492}, { 2020580, 13038160}, { 1774427, 12752800}, { 1770520, 12748270}, { 1538661, 12451166}, { 1534980, 12446450}, { 1317985, 12138341}, { 1314540, 12133450}, + { 1112921, 11815054}, { 1109720, 11810000}, { 923959, 11482096}, { 921010, 11476891}, { 751570, 11140273}, { 748880, 11134930}, { 596164, 10790399}, { 593740, 10784930}, + { 458113, 10433321}, { 455960, 10427740}, { 337747, 10069900}, { 335870, 10064220}, { 235356, 9701005}, { 233760, 9695240}, { 151191, 9327537}, { 149880, 9321700}, { 85443, 8950394}, + { 84420, 8944500}, { 38292, 8570470}, { 37560, 8564533}, { 9840, 8188694}, { 9400, 8182728}, { 147, 7805980}, { 0, 7800000}, { 9253, 7423252}, { 9400, 7417272}, { 37120, 7041433}, + { 37560, 7035467}, { 83688, 6661437}, { 84420, 6655501}, { 148858, 6284194}, { 149880, 6278301}, { 232450, 5910597}, { 233760, 5904761}, { 334275, 5541545}, { 335870, 5535780}, + { 454084, 5177940}, { 455960, 5172261}, { 591587, 4820651}, { 593740, 4815070}, { 746456, 4470539}, { 748880, 4465071}, { 918320, 4128453}, { 921010, 4123110}, { 1106772, 3795205}, + { 1109720, 3790000}, { 1311340, 3471604}, { 1314540, 3466550}, { 1531536, 3158441}, { 1534980, 3153550}, { 1766840, 2856446}, { 1770520, 2851730}, { 2016673, 2566370}, { 2020580, 2561840}, + { 2280445, 2288903}, { 2284570, 2284570}, { 2288903, 2280445}, { 2561840, 2020580}, { 2566370, 2016673}, { 2851730, 1770520}, { 2856446, 1766840}, { 3153550, 1534980}, { 3158441, 1531536}, + { 3466550, 1314540}, { 3471604, 1311340}, { 3790000, 1109720}, { 3795205, 1106772}, { 4123110, 921010}, { 4128453, 918320}, { 4465071, 748880}, { 4470539, 746456}, { 4815070, 593740}, + { 4820651, 591587}, { 5172261, 455960}, { 5177940, 454084}, { 5535780, 335870}, { 5541545, 334275}, { 5904761, 233760}, { 5910597, 232450}, { 6278301, 149880}, { 6284194, 148858}, + { 6655501, 84420}, { 6661437, 83688}, { 7035467, 37560}, { 7041433, 37120}, { 7417272, 9400}, { 7423252, 9253}, { 7800000, 0} + }; + out.holes.emplace_back(Slic3r::Points( { + { 7794921, 1002175}, { 7466441, 1010240}, { 7461374, 1010614}, { 7133685, 1034780}, { 7128642, 1035402}, { 6802534, 1075630}, { 6797528, 1076499}, { 6473790, 1132670}, + { 6468832, 1133784}, { 6148230, 1205780}, { 6143333, 1207135}, { 5826660, 1294770}, { 5821835, 1296364}, { 5509840, 1399430}, { 5505100, 1401259}, { 5198540, 1519510}, + { 5193895, 1521569}, { 4893501, 1654720}, { 4888962, 1657005}, { 4595471, 1804740}, { 4591050, 1807245}, { 4305150, 1969200}, { 4300857, 1971918}, { 4023260, 2147710}, + { 4019106, 2150636}, { 3750470, 2339831}, { 3746465, 2342956}, { 3487430, 2545110}, { 3483583, 2548429}, { 3234780, 2763050}, { 3231100, 2766553}, { 2993120, 2993120}, + { 2766553, 3231100}, { 2763050, 3234780}, { 2548429, 3483583}, { 2545110, 3487430}, { 2342956, 3746465}, { 2339831, 3750470}, { 2150636, 4019106}, { 2147710, 4023260}, + { 1971918, 4300857}, { 1969200, 4305150}, { 1807245, 4591050}, { 1804740, 4595471}, { 1657005, 4888962}, { 1654720, 4893501}, { 1521569, 5193895}, { 1519510, 5198540}, + { 1401259, 5505100}, { 1399430, 5509840}, { 1296364, 5821835}, { 1294770, 5826660}, { 1207135, 6143333}, { 1205780, 6148230}, { 1133784, 6468832}, { 1132670, 6473790}, + { 1076499, 6797528}, { 1075630, 6802534}, { 1035402, 7128642}, { 1034780, 7133685}, { 1010614, 7461374}, { 1010240, 7466441}, { 1002175, 7794921}, { 1002050, 7800000}, + { 1010115, 8128480}, { 1010240, 8133559}, { 1034406, 8461248}, { 1034780, 8466315}, { 1075008, 8792423}, { 1075630, 8797466}, { 1131802, 9121204}, { 1132670, 9126210}, + { 1204667, 9446812}, { 1205780, 9451770}, { 1293415, 9768443}, { 1294770, 9773340}, { 1397836, 10085335}, { 1399430, 10090160}, { 1517682, 10396721}, { 1519510, 10401461}, + { 1652661, 10701855}, { 1654720, 10706500}, { 1802456, 10999992}, { 1804740, 11004530}, { 1966696, 11290429}, { 1969200, 11294850}, { 2144992, 11572447}, { 2147710, 11576740}, + { 2336905, 11845376}, { 2339831, 11849530}, { 2541984, 12108564}, { 2545110, 12112570}, { 2759731, 12361373}, { 2763050, 12365220}, { 2989617, 12603200}, { 2993120, 12606880}, + { 2996800, 12610383}, { 3234780, 12836950}, { 3238628, 12840269}, { 3487430, 13054890}, { 3491436, 13058016}, { 3750470, 13260170}, { 3754624, 13263096}, { 4023260, 13452290}, + { 4027553, 13455008}, { 4305150, 13630800}, { 4309571, 13633304}, { 4595471, 13795260}, { 4600009, 13797544}, { 4893501, 13945280}, { 4898146, 13947339}, { 5198540, 14080490}, + { 5203280, 14082319}, { 5509840, 14200570}, { 5514665, 14202164}, { 5826660, 14305230}, { 5831557, 14306585}, { 6148230, 14394220}, { 6153188, 14395333}, { 6473790, 14467330}, + { 6478796, 14468199}, { 6802534, 14524370}, { 6807577, 14524992}, { 7133685, 14565220}, { 7138752, 14565594}, { 7466441, 14589760}, { 7471520, 14589885}, { 7800000, 14597950}, + { 7805079, 14597825}, { 8133559, 14589760}, { 8138626, 14589386}, { 8466315, 14565220}, { 8471358, 14564598}, { 8797466, 14524370}, { 8802472, 14523501}, { 9126210, 14467330}, + { 9131168, 14466217}, { 9451770, 14394220}, { 9456667, 14392865}, { 9773340, 14305230}, { 9778165, 14303636}, { 10090160, 14200570}, { 10094900, 14198741}, { 10401461, 14080490}, + { 10406106, 14078431}, { 10706500, 13945280}, { 10711038, 13942996}, { 11004530, 13795260}, { 11008951, 13792756}, { 11294850, 13630800}, { 11299143, 13628082}, { 11576740, 13452290}, + { 11580894, 13449364}, { 11849530, 13260170}, { 11853536, 13257044}, { 12112570, 13054890}, { 12116417, 13051571}, { 12365220, 12836950}, { 12368900, 12833447}, { 12606880, 12606880}, + { 12833447, 12368900}, { 12836950, 12365220}, { 13051571, 12116417}, { 13054890, 12112570}, { 13257044, 11853536}, { 13260170, 11849530}, { 13449364, 11580894}, { 13452290, 11576740}, + { 13628082, 11299143}, { 13630800, 11294850}, { 13792756, 11008951}, { 13795260, 11004530}, { 13942996, 10711038}, { 13945280, 10706500}, { 14078431, 10406106}, { 14080490, 10401461}, + { 14198741, 10094900}, { 14200570, 10090160}, { 14303636, 9778165}, { 14305230, 9773340}, { 14392865, 9456667}, { 14394220, 9451770}, { 14466217, 9131168}, { 14467330, 9126210}, + { 14523501, 8802472}, { 14524370, 8797466}, { 14564598, 8471358}, { 14565220, 8466315}, { 14589386, 8138626}, { 14589760, 8133559}, { 14597825, 7805079}, { 14597950, 7800000}, + { 14589885, 7471520}, { 14589760, 7466441}, { 14565594, 7138752}, { 14565220, 7133685}, { 14524992, 6807577}, { 14524370, 6802534}, { 14468199, 6478796}, { 14467330, 6473790}, + { 14395333, 6153188}, { 14394220, 6148230}, { 14306585, 5831557}, { 14305230, 5826660}, { 14202164, 5514665}, { 14200570, 5509840}, { 14082319, 5203280}, { 14080490, 5198540}, + { 13947339, 4898146}, { 13945280, 4893501}, { 13797544, 4600009}, { 13795260, 4595471}, { 13633304, 4309571}, { 13630800, 4305150}, { 13455008, 4027553}, { 13452290, 4023260}, + { 13263096, 3754624}, { 13260170, 3750470}, { 13058016, 3491436}, { 13054890, 3487430}, { 12840269, 3238628}, { 12836950, 3234780}, { 12610383, 2996800}, { 12606880, 2993120}, + { 12603200, 2989617}, { 12365220, 2763050}, { 12361373, 2759731}, { 12112570, 2545110}, { 12108564, 2541984}, { 11849530, 2339831}, { 11845376, 2336905}, { 11576740, 2147710}, + { 11572447, 2144992}, { 11294850, 1969200}, { 11290429, 1966696}, { 11004530, 1804740}, { 10999992, 1802456}, { 10706500, 1654720}, { 10701855, 1652661}, { 10401461, 1519510}, + { 10396721, 1517682}, { 10090160, 1399430}, { 10085335, 1397836}, { 9773340, 1294770}, { 9768443, 1293415}, { 9451770, 1205780}, { 9446812, 1204667}, { 9126210, 1132670}, + { 9121204, 1131802}, { 8797466, 1075630}, { 8792423, 1075008}, { 8466315, 1034780}, { 8461248, 1034406}, { 8133559, 1010240}, { 8128480, 1010115}, { 7800000, 1002050} + } )); + return out; +} + +SCENARIO("Elephant foot compensation", "[ElephantFoot]") { + + GIVEN("Large box") { + ExPolygon expoly( { {50000000, 50000000 }, { 0, 50000000 }, { 0, 0 }, { 50000000, 0 } } ); + WHEN("Compensated") { + ExPolygon expoly_compensated = elephant_foot_compensation(expoly, Flow(0.419999987f, 0.2f, 0.4f, false), 0.21f); +#ifdef TESTS_EXPORT_SVGS + SVG::export_expolygons(debug_out_path("elephant_foot_compensation_large_box.svg").c_str(), + { { { expoly }, { "gray", "black", "blue", coord_t(scale_(0.02)), 0.5f, "black", coord_t(scale_(0.05)) } }, + { { expoly_compensated }, { "gray", "black", "blue", coord_t(scale_(0.02)), 0.5f, "black", coord_t(scale_(0.05)) } } }); +#endif /* TESTS_EXPORT_SVGS */ + THEN("area of the compensated polygon is smaller") { + REQUIRE(expoly_compensated.area() < expoly.area()); + } + } + } + + GIVEN("Thin ring (GH issue #2085)") { + ExPolygon expoly = thin_ring(); + WHEN("Compensated") { + ExPolygon expoly_compensated = elephant_foot_compensation(expoly, Flow(0.419999987f, 0.2f, 0.4f, false), 0.25f); +#ifdef TESTS_EXPORT_SVGS + SVG::export_expolygons(debug_out_path("elephant_foot_compensation_thin_ring.svg").c_str(), + { { { expoly }, { "gray", "black", "blue", coord_t(scale_(0.02)), 0.5f, "black", coord_t(scale_(0.05)) } }, + { { expoly_compensated }, { "gray", "black", "blue", coord_t(scale_(0.02)), 0.5f, "black", coord_t(scale_(0.05)) } } }); +#endif /* TESTS_EXPORT_SVGS */ + THEN("area of the compensated polygon is smaller") { + REQUIRE(expoly_compensated.area() < expoly.area()); + } + } + } + +#if 0 + GIVEN("Varying inner offset") { + ExPolygon input = spirograph_gear_1mm().simplify(SCALED_EPSILON).front(); + ExPolygon output; + std::vector deltas(input.contour.points.size(), scale_(1.)); + output.contour.points = Slic3r::mittered_offset_path_scaled_points(input.contour.points, deltas, 2.); +#ifdef TESTS_EXPORT_SVGS + { + SVG svg(debug_out_path("elephant_foot_compensation_0.svg").c_str(), get_extents(output)); + svg.draw(input, "blue"); + svg.draw_outline(output, "black", coord_t(scale_(0.01))); + } +#endif /* TESTS_EXPORT_SVGS */ + for (size_t i = 0; i <= deltas.size() / 2; ++ i) + deltas[i] = deltas[deltas.size() - i - 1] = scale_(1.) * double(i) / (0.5 * double(deltas.size())); + output.contour.points = Slic3r::mittered_offset_path_scaled_points(input.contour.points, deltas, 2.); +#ifdef TESTS_EXPORT_SVGS + { + SVG svg(debug_out_path("elephant_foot_compensation_varying.svg").c_str(), get_extents(output)); + svg.draw(input, "blue"); + svg.draw_outline(output, "black", coord_t(scale_(0.01))); + } +#endif /* TESTS_EXPORT_SVGS */ + } +#endif + + GIVEN("Rectangle with a narrow part sticking out") { + // Rectangle + ExPolygon expoly; + coord_t scaled_w = coord_t(scale_(10)); + expoly.contour.points = { { 0, 0 }, { 0, scaled_w, }, { scaled_w, scaled_w }, { scaled_w, 0 } }; + // Narrow part + ExPolygon expoly2; + coord_t scaled_h = coord_t(scale_(0.8)); + expoly2.contour.points = { { scaled_w - coord_t(SCALED_EPSILON), scaled_w / 2 }, { scaled_w - coord_t(SCALED_EPSILON), scaled_w / 2 + scaled_h, }, + { 2 * scaled_w, scaled_w / 2 + scaled_h }, { 2 * scaled_w, scaled_w / 2 } }; + // Rectangle with the narrow part. + expoly = union_ex({ expoly, expoly2 }).front(); + + WHEN("Partially compensated") { + ExPolygon expoly_compensated = elephant_foot_compensation(expoly, Flow(0.45f, 0.2f, 0.4f, false), 0.25f); +#ifdef TESTS_EXPORT_SVGS + SVG::export_expolygons(debug_out_path("elephant_foot_compensation_0.svg").c_str(), + { { { expoly }, { "gray", "black", "blue", coord_t(scale_(0.02)), 0.5f, "black", coord_t(scale_(0.05)) } }, + { { expoly_compensated }, { "gray", "black", "blue", coord_t(scale_(0.02)), 0.5f, "black", coord_t(scale_(0.05)) } } }); +#endif /* TESTS_EXPORT_SVGS */ + THEN("area of the compensated polygon is smaller") { + REQUIRE(expoly_compensated.area() < expoly.area()); + } + } + WHEN("Fully compensated") { + ExPolygon expoly_compensated = elephant_foot_compensation(expoly, Flow(0.35f, 0.2f, 0.4f, false), 0.17f); +#ifdef TESTS_EXPORT_SVGS + SVG::export_expolygons(debug_out_path("elephant_foot_compensation_1.svg").c_str(), + { { { expoly }, { "gray", "black", "blue", coord_t(scale_(0.02)), 0.5f, "black", coord_t(scale_(0.05)) } }, + { { expoly_compensated }, { "gray", "black", "blue", coord_t(scale_(0.02)), 0.5f, "black", coord_t(scale_(0.05)) } } }); +#endif /* TESTS_EXPORT_SVGS */ + THEN("area of the compensated polygon is smaller") { + REQUIRE(expoly_compensated.area() < expoly.area()); + } + } + } + + GIVEN("Box with hole close to wall (GH issue #2998)") { + ExPolygon expoly = box_with_hole_close_to_wall(); + WHEN("Compensated") { + ExPolygon expoly_compensated = elephant_foot_compensation(expoly, Flow(0.419999987f, 0.2f, 0.4f, false), 0.25f); +#ifdef TESTS_EXPORT_SVGS + SVG::export_expolygons(debug_out_path("elephant_foot_compensation_2.svg").c_str(), + { { { expoly }, { "gray", "black", "blue", coord_t(scale_(0.02)), 0.5f, "black", coord_t(scale_(0.05)) } }, + { { expoly_compensated }, { "gray", "black", "blue", coord_t(scale_(0.02)), 0.5f, "black", coord_t(scale_(0.05)) } } }); +#endif /* TESTS_EXPORT_SVGS */ + THEN("area of the compensated polygon is smaller") { + REQUIRE(expoly_compensated.area() < expoly.area()); + } + } + } + + GIVEN("Spirograph wheel") { + // Rectangle + ExPolygon expoly = spirograph_gear_1mm(); + + WHEN("Partially compensated") { + ExPolygon expoly_compensated = elephant_foot_compensation(expoly, Flow(0.45f, 0.2f, 0.4f, false), 0.25f); +#ifdef TESTS_EXPORT_SVGS + SVG::export_expolygons(debug_out_path("elephant_foot_compensation_2.svg").c_str(), + { { { expoly }, { "gray", "black", "blue", coord_t(scale_(0.02)), 0.5f, "black", coord_t(scale_(0.05)) } }, + { { expoly_compensated }, { "gray", "black", "blue", coord_t(scale_(0.02)), 0.5f, "black", coord_t(scale_(0.05)) } } }); +#endif /* TESTS_EXPORT_SVGS */ + THEN("area of the compensated polygon is smaller") { + REQUIRE(expoly_compensated.area() < expoly.area()); + } + } + WHEN("Fully compensated") { + ExPolygon expoly_compensated = elephant_foot_compensation(expoly, Flow(0.35f, 0.2f, 0.4f, false), 0.17f); +#ifdef TESTS_EXPORT_SVGS + SVG::export_expolygons(debug_out_path("elephant_foot_compensation_3.svg").c_str(), + { { { expoly }, { "gray", "black", "blue", coord_t(scale_(0.02)), 0.5f, "black", coord_t(scale_(0.05)) } }, + { { expoly_compensated }, { "gray", "black", "blue", coord_t(scale_(0.02)), 0.5f, "black", coord_t(scale_(0.05)) } } }); +#endif /* TESTS_EXPORT_SVGS */ + THEN("area of the compensated polygon is smaller") { + REQUIRE(expoly_compensated.area() < expoly.area()); + } + } + WHEN("Brutally compensated") { + ExPolygon expoly_compensated = elephant_foot_compensation(expoly, Flow(0.45f, 0.2f, 0.4f, false), 0.6f); +#ifdef TESTS_EXPORT_SVGS + SVG::export_expolygons(debug_out_path("elephant_foot_compensation_4.svg").c_str(), + { { { expoly }, { "gray", "black", "blue", coord_t(scale_(0.02)), 0.5f, "black", coord_t(scale_(0.05)) } }, + { { expoly_compensated }, { "gray", "black", "blue", coord_t(scale_(0.02)), 0.5f, "black", coord_t(scale_(0.05)) } } }); +#endif /* TESTS_EXPORT_SVGS */ + THEN("area of the compensated polygon is smaller") { + REQUIRE(expoly_compensated.area() < expoly.area()); + } + } + } +}