Improvements in performance of Medial Axis algorithm.

Fixes Slicing slows or hangs on "Generating Permiters 20%" cpu load is at 100% #8164
Fixes Slicing hangs on generating perimeters with thing:3565827 (30g) #3259
This commit is contained in:
Vojtech Bubnik 2022-11-16 12:03:31 +01:00
parent 0a84421ea4
commit fe51f77839
8 changed files with 157 additions and 174 deletions

View File

@ -206,12 +206,10 @@ void ExPolygon::simplify(double tolerance, ExPolygons* expolygons) const
append(*expolygons, this->simplify(tolerance)); append(*expolygons, this->simplify(tolerance));
} }
void void ExPolygon::medial_axis(double min_width, double max_width, ThickPolylines* polylines) const
ExPolygon::medial_axis(double max_width, double min_width, ThickPolylines* polylines) const
{ {
// init helper object // init helper object
Slic3r::Geometry::MedialAxis ma(max_width, min_width, this); Slic3r::Geometry::MedialAxis ma(min_width, max_width, *this);
ma.lines = this->lines();
// compute the Voronoi diagram and extract medial axis polylines // compute the Voronoi diagram and extract medial axis polylines
ThickPolylines pp; ThickPolylines pp;
@ -318,11 +316,10 @@ ExPolygon::medial_axis(double max_width, double min_width, ThickPolylines* polyl
polylines->insert(polylines->end(), pp.begin(), pp.end()); polylines->insert(polylines->end(), pp.begin(), pp.end());
} }
void void ExPolygon::medial_axis(double min_width, double max_width, Polylines* polylines) const
ExPolygon::medial_axis(double max_width, double min_width, Polylines* polylines) const
{ {
ThickPolylines tp; ThickPolylines tp;
this->medial_axis(max_width, min_width, &tp); this->medial_axis(min_width, max_width, &tp);
polylines->insert(polylines->end(), tp.begin(), tp.end()); polylines->insert(polylines->end(), tp.begin(), tp.end());
} }

View File

@ -70,10 +70,10 @@ public:
Polygons simplify_p(double tolerance) const; Polygons simplify_p(double tolerance) const;
ExPolygons simplify(double tolerance) const; ExPolygons simplify(double tolerance) const;
void simplify(double tolerance, ExPolygons* expolygons) const; void simplify(double tolerance, ExPolygons* expolygons) const;
void medial_axis(double max_width, double min_width, ThickPolylines* polylines) const; void medial_axis(double min_width, double max_width, ThickPolylines* polylines) const;
void medial_axis(double max_width, double min_width, Polylines* polylines) const; void medial_axis(double min_width, double max_width, Polylines* polylines) const;
Polylines medial_axis(double max_width, double min_width) const Polylines medial_axis(double min_width, double max_width) const
{ Polylines out; this->medial_axis(max_width, min_width, &out); return out; } { Polylines out; this->medial_axis(min_width, max_width, &out); return out; }
Lines lines() const; Lines lines() const;
// Number of contours (outer contour with holes). // Number of contours (outer contour with holes).

View File

@ -1,6 +1,7 @@
#include "MedialAxis.hpp" #include "MedialAxis.hpp"
#include "clipper.hpp" #include "clipper.hpp"
#include "VoronoiOffset.hpp"
#ifdef SLIC3R_DEBUG #ifdef SLIC3R_DEBUG
namespace boost { namespace polygon { namespace boost { namespace polygon {
@ -392,8 +393,7 @@ inline const typename VD::point_type retrieve_cell_point(const typename VD::cell
} }
template<typename VD, typename SEGMENTS> template<typename VD, typename SEGMENTS>
inline std::pair<typename VD::coord_type, typename VD::coord_type> inline std::pair<typename VD::coord_type, typename VD::coord_type> measure_edge_thickness(const VD &vd, const typename VD::edge_type& edge, const SEGMENTS &segments)
measure_edge_thickness(const VD &vd, const typename VD::edge_type& edge, const SEGMENTS &segments)
{ {
typedef typename VD::coord_type T; typedef typename VD::coord_type T;
const typename VD::point_type pa(edge.vertex0()->x(), edge.vertex0()->y()); const typename VD::point_type pa(edge.vertex0()->x(), edge.vertex0()->y());
@ -442,15 +442,21 @@ private:
const Lines &lines; const Lines &lines;
}; };
void MedialAxis::MedialAxis(double min_width, double max_width, const ExPolygon &expolygon) :
MedialAxis::build(ThickPolylines* polylines) m_expolygon(expolygon), m_lines(expolygon.lines()), m_min_width(min_width), m_max_width(max_width)
{}
void MedialAxis::build(ThickPolylines* polylines)
{ {
construct_voronoi(this->lines.begin(), this->lines.end(), &this->vd); construct_voronoi(m_lines.begin(), m_lines.end(), &m_vd);
Slic3r::Voronoi::annotate_inside_outside(m_vd, m_lines);
// static constexpr double threshold_alpha = M_PI / 12.; // 30 degrees
// std::vector<Vec2d> skeleton_edges = Slic3r::Voronoi::skeleton_edges_rough(vd, lines, threshold_alpha);
/* /*
// DEBUG: dump all Voronoi edges // DEBUG: dump all Voronoi edges
{ {
for (VD::const_edge_iterator edge = this->vd.edges().begin(); edge != this->vd.edges().end(); ++edge) { for (VD::const_edge_iterator edge = m_vd.edges().begin(); edge != m_vd.edges().end(); ++edge) {
if (edge->is_infinite()) continue; if (edge->is_infinite()) continue;
ThickPolyline polyline; ThickPolyline polyline;
@ -463,73 +469,59 @@ MedialAxis::build(ThickPolylines* polylines)
*/ */
//typedef const VD::vertex_type vert_t; //typedef const VD::vertex_type vert_t;
typedef const VD::edge_type edge_t; using edge_t = const VD::edge_type;
// collect valid edges (i.e. prune those not belonging to MAT) // collect valid edges (i.e. prune those not belonging to MAT)
// note: this keeps twins, so it inserts twice the number of the valid edges // note: this keeps twins, so it inserts twice the number of the valid edges
this->valid_edges.clear(); m_edge_data.assign(m_vd.edges().size() / 2, EdgeData{});
{ for (VD::const_edge_iterator edge = m_vd.edges().begin(); edge != m_vd.edges().end(); edge += 2)
std::set<const VD::edge_type*> seen_edges; if (edge->is_primary() && edge->is_finite() &&
for (VD::const_edge_iterator edge = this->vd.edges().begin(); edge != this->vd.edges().end(); ++edge) { (Voronoi::vertex_category(edge->vertex0()) == Voronoi::VertexCategory::Inside ||
// if we only process segments representing closed loops, none if the Voronoi::vertex_category(edge->vertex1()) == Voronoi::VertexCategory::Inside) &&
// infinite edges (if any) would be part of our MAT anyway this->validate_edge(&*edge)) {
if (edge->is_secondary() || edge->is_infinite()) continue; // Valid skeleton edge.
this->edge_data(*edge).first.active = true;
// don't re-validate twins
if (seen_edges.find(&*edge) != seen_edges.end()) continue; // TODO: is this needed?
seen_edges.insert(&*edge);
seen_edges.insert(edge->twin());
if (!this->validate_edge(&*edge)) continue;
this->valid_edges.insert(&*edge);
this->valid_edges.insert(edge->twin());
} }
}
this->edges = this->valid_edges;
// iterate through the valid edges to build polylines // iterate through the valid edges to build polylines
while (!this->edges.empty()) { ThickPolyline reverse_polyline;
const edge_t* edge = *this->edges.begin(); for (VD::const_edge_iterator seed_edge = m_vd.edges().begin(); seed_edge != m_vd.edges().end(); seed_edge += 2)
if (EdgeData &seed_edge_data = this->edge_data(*seed_edge).first; seed_edge_data.active) {
// Mark this edge as visited.
seed_edge_data.active = false;
// Start a polyline.
ThickPolyline polyline;
polyline.points.emplace_back(seed_edge->vertex0()->x(), seed_edge->vertex0()->y());
polyline.points.emplace_back(seed_edge->vertex1()->x(), seed_edge->vertex1()->y());
polyline.width.emplace_back(seed_edge_data.width_start);
polyline.width.emplace_back(seed_edge_data.width_end);
// Grow the polyline in a forward direction.
this->process_edge_neighbors(&*seed_edge, &polyline);
// start a polyline // Grow the polyline in a backward direction.
ThickPolyline polyline; reverse_polyline.clear();
polyline.points.push_back(Point( edge->vertex0()->x(), edge->vertex0()->y() )); this->process_edge_neighbors(seed_edge->twin(), &reverse_polyline);
polyline.points.push_back(Point( edge->vertex1()->x(), edge->vertex1()->y() )); polyline.points.insert(polyline.points.begin(), reverse_polyline.points.rbegin(), reverse_polyline.points.rend());
polyline.width.push_back(this->thickness[edge].first); polyline.width.insert(polyline.width.begin(), reverse_polyline.width.rbegin(), reverse_polyline.width.rend());
polyline.width.push_back(this->thickness[edge].second); polyline.endpoints.first = reverse_polyline.endpoints.second;
// remove this edge and its twin from the available edges assert(polyline.width.size() == polyline.points.size() * 2 - 2);
(void)this->edges.erase(edge);
(void)this->edges.erase(edge->twin());
// get next points // Prevent loop endpoints from being extended.
this->process_edge_neighbors(edge, &polyline); if (polyline.first_point() == polyline.last_point()) {
polyline.endpoints.first = false;
// get previous points polyline.endpoints.second = false;
{ }
ThickPolyline rpolyline;
this->process_edge_neighbors(edge->twin(), &rpolyline); // Append polyline to result.
polyline.points.insert(polyline.points.begin(), rpolyline.points.rbegin(), rpolyline.points.rend()); polylines->emplace_back(std::move(polyline));
polyline.width.insert(polyline.width.begin(), rpolyline.width.rbegin(), rpolyline.width.rend());
polyline.endpoints.first = rpolyline.endpoints.second;
} }
assert(polyline.width.size() == polyline.points.size()*2 - 2);
// prevent loop endpoints from being extended
if (polyline.first_point() == polyline.last_point()) {
polyline.endpoints.first = false;
polyline.endpoints.second = false;
}
// append polyline to result
polylines->push_back(polyline);
}
#ifdef SLIC3R_DEBUG #ifdef SLIC3R_DEBUG
{ {
static int iRun = 0; static int iRun = 0;
dump_voronoi_to_svg(this->lines, this->vd, polylines, debug_out_path("MedialAxis-%d.svg", iRun ++).c_str()); dump_voronoi_to_svg(m_lines, m_vd, polylines, debug_out_path("MedialAxis-%d.svg", iRun ++).c_str());
printf("Thick lines: "); printf("Thick lines: ");
for (ThickPolylines::const_iterator it = polylines->begin(); it != polylines->end(); ++ it) { for (ThickPolylines::const_iterator it = polylines->begin(); it != polylines->end(); ++ it) {
ThickLines lines = it->thicklines(); ThickLines lines = it->thicklines();
@ -542,56 +534,66 @@ MedialAxis::build(ThickPolylines* polylines)
#endif /* SLIC3R_DEBUG */ #endif /* SLIC3R_DEBUG */
} }
void void MedialAxis::build(Polylines* polylines)
MedialAxis::build(Polylines* polylines)
{ {
ThickPolylines tp; ThickPolylines tp;
this->build(&tp); this->build(&tp);
polylines->insert(polylines->end(), tp.begin(), tp.end()); polylines->insert(polylines->end(), tp.begin(), tp.end());
} }
void void MedialAxis::process_edge_neighbors(const VD::edge_type *edge, ThickPolyline* polyline)
MedialAxis::process_edge_neighbors(const VD::edge_type* edge, ThickPolyline* polyline)
{ {
while (true) { for (;;) {
// Since rot_next() works on the edge starting point but we want // Since rot_next() works on the edge starting point but we want
// to find neighbors on the ending point, we just swap edge with // to find neighbors on the ending point, we just swap edge with
// its twin. // its twin.
const VD::edge_type* twin = edge->twin(); const VD::edge_type *twin = edge->twin();
// count neighbors for this edge // count neighbors for this edge
std::vector<const VD::edge_type*> neighbors; size_t num_neighbors = 0;
for (const VD::edge_type* neighbor = twin->rot_next(); neighbor != twin; const VD::edge_type *first_neighbor = nullptr;
neighbor = neighbor->rot_next()) { for (const VD::edge_type *neighbor = twin->rot_next(); neighbor != twin; neighbor = neighbor->rot_next())
if (this->valid_edges.count(neighbor) > 0) neighbors.push_back(neighbor); if (this->edge_data(*neighbor).first.active) {
} if (num_neighbors == 0)
first_neighbor = neighbor;
++ num_neighbors;
}
// if we have a single neighbor then we can continue recursively // if we have a single neighbor then we can continue recursively
if (neighbors.size() == 1) { if (num_neighbors == 1) {
const VD::edge_type* neighbor = neighbors.front(); if (std::pair<EdgeData&, bool> neighbor_data = this->edge_data(*first_neighbor);
neighbor_data.first.active) {
// break if this is a closed loop neighbor_data.first.active = false;
if (this->edges.count(neighbor) == 0) return; polyline->points.emplace_back(first_neighbor->vertex1()->x(), first_neighbor->vertex1()->y());
if (neighbor_data.second) {
Point new_point(neighbor->vertex1()->x(), neighbor->vertex1()->y()); polyline->width.push_back(neighbor_data.first.width_end);
polyline->points.push_back(new_point); polyline->width.push_back(neighbor_data.first.width_start);
polyline->width.push_back(this->thickness[neighbor].first); } else {
polyline->width.push_back(this->thickness[neighbor].second); polyline->width.push_back(neighbor_data.first.width_start);
(void)this->edges.erase(neighbor); polyline->width.push_back(neighbor_data.first.width_end);
(void)this->edges.erase(neighbor->twin()); }
edge = neighbor; edge = first_neighbor;
} else if (neighbors.size() == 0) { // Continue chaining.
continue;
}
} else if (num_neighbors == 0) {
polyline->endpoints.second = true; polyline->endpoints.second = true;
return;
} else { } else {
// T-shaped or star-shaped joint // T-shaped or star-shaped joint
return;
} }
// Stop chaining.
break;
} }
} }
bool MedialAxis::validate_edge(const VD::edge_type* edge) bool MedialAxis::validate_edge(const VD::edge_type* edge)
{ {
auto retrieve_segment = [this](const VD::cell_type* cell) -> const Line& { return m_lines[cell->source_index()]; };
auto retrieve_endpoint = [retrieve_segment](const VD::cell_type* cell) -> const Point& {
const Line &line = retrieve_segment(cell);
return cell->source_category() == boost::polygon::SOURCE_CATEGORY_SEGMENT_START_POINT ? line.a : line.b;
};
// prevent overflows and detect almost-infinite edges // prevent overflows and detect almost-infinite edges
#ifndef CLIPPERLIB_INT32 #ifndef CLIPPERLIB_INT32
if (std::abs(edge->vertex0()->x()) > double(CLIPPER_MAX_COORD_UNSCALED) || if (std::abs(edge->vertex0()->x()) > double(CLIPPER_MAX_COORD_UNSCALED) ||
@ -602,32 +604,18 @@ bool MedialAxis::validate_edge(const VD::edge_type* edge)
#endif // CLIPPERLIB_INT32 #endif // CLIPPERLIB_INT32
// construct the line representing this edge of the Voronoi diagram // construct the line representing this edge of the Voronoi diagram
const Line line( const Line line({ edge->vertex0()->x(), edge->vertex0()->y() },
Point( edge->vertex0()->x(), edge->vertex0()->y() ), { edge->vertex1()->x(), edge->vertex1()->y() });
Point( edge->vertex1()->x(), edge->vertex1()->y() )
);
// discard edge if it lies outside the supplied shape
// this could maybe be optimized (checking inclusion of the endpoints
// might give false positives as they might belong to the contour itself)
if (this->expolygon != NULL) {
if (line.a == line.b) {
// in this case, contains(line) returns a false positive
if (!this->expolygon->contains(line.a)) return false;
} else {
if (!this->expolygon->contains(line)) return false;
}
}
// retrieve the original line segments which generated the edge we're checking // retrieve the original line segments which generated the edge we're checking
const VD::cell_type* cell_l = edge->cell(); const VD::cell_type* cell_l = edge->cell();
const VD::cell_type* cell_r = edge->twin()->cell(); const VD::cell_type* cell_r = edge->twin()->cell();
const Line &segment_l = this->retrieve_segment(cell_l); const Line &segment_l = retrieve_segment(cell_l);
const Line &segment_r = this->retrieve_segment(cell_r); const Line &segment_r = retrieve_segment(cell_r);
/* /*
SVG svg("edge.svg"); SVG svg("edge.svg");
svg.draw(*this->expolygon); svg.draw(m_expolygon);
svg.draw(line); svg.draw(line);
svg.draw(segment_l, "red"); svg.draw(segment_l, "red");
svg.draw(segment_r, "blue"); svg.draw(segment_r, "blue");
@ -651,30 +639,30 @@ bool MedialAxis::validate_edge(const VD::edge_type* edge)
coordf_t w0 = cell_r->contains_segment() coordf_t w0 = cell_r->contains_segment()
? segment_r.distance_to(line.a)*2 ? segment_r.distance_to(line.a)*2
: (this->retrieve_endpoint(cell_r) - line.a).cast<double>().norm()*2; : (retrieve_endpoint(cell_r) - line.a).cast<double>().norm()*2;
coordf_t w1 = cell_l->contains_segment() coordf_t w1 = cell_l->contains_segment()
? segment_l.distance_to(line.b)*2 ? segment_l.distance_to(line.b)*2
: (this->retrieve_endpoint(cell_l) - line.b).cast<double>().norm()*2; : (retrieve_endpoint(cell_l) - line.b).cast<double>().norm()*2;
if (cell_l->contains_segment() && cell_r->contains_segment()) { if (cell_l->contains_segment() && cell_r->contains_segment()) {
// calculate the relative angle between the two boundary segments // calculate the relative angle between the two boundary segments
double angle = fabs(segment_r.orientation() - segment_l.orientation()); double angle = fabs(segment_r.orientation() - segment_l.orientation());
if (angle > PI) angle = 2*PI - angle; if (angle > PI)
angle = 2. * PI - angle;
assert(angle >= 0 && angle <= PI); assert(angle >= 0 && angle <= PI);
// fabs(angle) ranges from 0 (collinear, same direction) to PI (collinear, opposite direction) // fabs(angle) ranges from 0 (collinear, same direction) to PI (collinear, opposite direction)
// we're interested only in segments close to the second case (facing segments) // we're interested only in segments close to the second case (facing segments)
// so we allow some tolerance. // so we allow some tolerance.
// this filter ensures that we're dealing with a narrow/oriented area (longer than thick) // this filter ensures that we're dealing with a narrow/oriented area (longer than thick)
// we don't run it on edges not generated by two segments (thus generated by one segment // we don't run it on edges not generated by two segments (thus generated by one segment
// and the endpoint of another segment), since their orientation would not be meaningful // and the endpoint of another segment), since their orientation would not be meaningful
if (PI - angle > PI/8) { if (PI - angle > PI / 8.) {
// angle is not narrow enough // angle is not narrow enough
// only apply this filter to segments that are not too short otherwise their // only apply this filter to segments that are not too short otherwise their
// angle could possibly be not meaningful // angle could possibly be not meaningful
if (w0 < SCALED_EPSILON || w1 < SCALED_EPSILON || line.length() >= this->min_width) if (w0 < SCALED_EPSILON || w1 < SCALED_EPSILON || line.length() >= m_min_width)
return false; return false;
} }
} else { } else {
@ -682,31 +670,17 @@ bool MedialAxis::validate_edge(const VD::edge_type* edge)
return false; return false;
} }
if (w0 < this->min_width && w1 < this->min_width) if ((w0 >= m_min_width || w1 >= m_min_width) &&
return false; (w0 <= m_max_width || w1 <= m_max_width)) {
std::pair<EdgeData&, bool> ed = this->edge_data(*edge);
if (w0 > this->max_width && w1 > this->max_width) if (ed.second)
return false; std::swap(w0, w1);
ed.first.width_start = w0;
this->thickness[edge] = std::make_pair(w0, w1); ed.first.width_end = w1;
this->thickness[edge->twin()] = std::make_pair(w1, w0); return true;
return true;
}
const Line& MedialAxis::retrieve_segment(const VD::cell_type* cell) const
{
return this->lines[cell->source_index()];
}
const Point& MedialAxis::retrieve_endpoint(const VD::cell_type* cell) const
{
const Line& line = this->retrieve_segment(cell);
if (cell->source_category() == boost::polygon::SOURCE_CATEGORY_SEGMENT_START_POINT) {
return line.a;
} else {
return line.b;
} }
return false;
} }
} } // namespace Slicer::Geometry } } // namespace Slicer::Geometry

View File

@ -4,30 +4,43 @@
#include "Voronoi.hpp" #include "Voronoi.hpp"
#include "../ExPolygon.hpp" #include "../ExPolygon.hpp"
namespace Slic3r { namespace Geometry { namespace Slic3r::Geometry {
class MedialAxis { class MedialAxis {
public: public:
Lines lines; MedialAxis(double min_width, double max_width, const ExPolygon &expolygon);
const ExPolygon* expolygon;
double max_width;
double min_width;
MedialAxis(double _max_width, double _min_width, const ExPolygon* _expolygon = NULL)
: expolygon(_expolygon), max_width(_max_width), min_width(_min_width) {};
void build(ThickPolylines* polylines); void build(ThickPolylines* polylines);
void build(Polylines* polylines); void build(Polylines* polylines);
private: private:
// Input
const ExPolygon &m_expolygon;
Lines m_lines;
// for filtering of the skeleton edges
double m_min_width;
double m_max_width;
// Voronoi Diagram.
using VD = VoronoiDiagram; using VD = VoronoiDiagram;
VD vd; VD m_vd;
std::set<const VD::edge_type*> edges, valid_edges;
std::map<const VD::edge_type*, std::pair<coordf_t,coordf_t> > thickness; // Annotations of the VD skeleton edges.
struct EdgeData {
bool active { false };
double width_start { 0 };
double width_end { 0 };
};
// Returns a reference to EdgeData and a "reversed" boolean.
std::pair<EdgeData&, bool> edge_data(const VD::edge_type &edge) {
size_t edge_id = &edge - &m_vd.edges().front();
return { m_edge_data[edge_id / 2], (edge_id & 1) != 0 };
}
std::vector<EdgeData> m_edge_data;
void process_edge_neighbors(const VD::edge_type* edge, ThickPolyline* polyline); void process_edge_neighbors(const VD::edge_type* edge, ThickPolyline* polyline);
bool validate_edge(const VD::edge_type* edge); bool validate_edge(const VD::edge_type* edge);
const Line& retrieve_segment(const VD::cell_type* cell) const;
const Point& retrieve_endpoint(const VD::cell_type* cell) const;
}; };
} } // namespace Slicer::Geometry } // namespace Slicer::Geometry
#endif // slic3r_Geometry_MedialAxis_hpp_ #endif // slic3r_Geometry_MedialAxis_hpp_

View File

@ -1,4 +1,4 @@
// Polygon offsetting using Voronoi diagram prodiced by boost::polygon. // Polygon offsetting using Voronoi diagram produced by boost::polygon.
#ifndef slic3r_VoronoiOffset_hpp_ #ifndef slic3r_VoronoiOffset_hpp_
#define slic3r_VoronoiOffset_hpp_ #define slic3r_VoronoiOffset_hpp_

View File

@ -920,7 +920,7 @@ void PerimeterGenerator::process_classic(
float(min_width / 2.)); float(min_width / 2.));
// the maximum thickness of our thin wall area is equal to the minimum thickness of a single loop // the maximum thickness of our thin wall area is equal to the minimum thickness of a single loop
for (ExPolygon &ex : expp) for (ExPolygon &ex : expp)
ex.medial_axis(ext_perimeter_width + ext_perimeter_spacing2, min_width, &thin_walls); ex.medial_axis(min_width, ext_perimeter_width + ext_perimeter_spacing2, &thin_walls);
} }
if (params.spiral_vase && offsets.size() > 1) { if (params.spiral_vase && offsets.size() > 1) {
// Remove all but the largest area polygon. // Remove all but the largest area polygon.
@ -1066,7 +1066,7 @@ void PerimeterGenerator::process_classic(
offset2_ex(gaps, - float(max / 2.), float(max / 2. + ClipperSafetyOffset))); offset2_ex(gaps, - float(max / 2.), float(max / 2. + ClipperSafetyOffset)));
ThickPolylines polylines; ThickPolylines polylines;
for (const ExPolygon &ex : gaps_ex) for (const ExPolygon &ex : gaps_ex)
ex.medial_axis(max, min, &polylines); ex.medial_axis(min, max, &polylines);
if (! polylines.empty()) { if (! polylines.empty()) {
ExtrusionEntityCollection gap_fill; ExtrusionEntityCollection gap_fill;
variable_width(polylines, erGapFill, params.solid_infill_flow, gap_fill.entities); variable_width(polylines, erGapFill, params.solid_infill_flow, gap_fill.entities);

View File

@ -16,7 +16,7 @@ SCENARIO("Medial Axis", "[ThinWalls]") {
auto hole_in_square = Polygon::new_scale({ {140, 140}, {140, 160}, {160, 160}, {160, 140} }); auto hole_in_square = Polygon::new_scale({ {140, 140}, {140, 160}, {160, 160}, {160, 140} });
ExPolygon expolygon{ square, hole_in_square }; ExPolygon expolygon{ square, hole_in_square };
WHEN("Medial axis is extracted") { WHEN("Medial axis is extracted") {
Polylines res = expolygon.medial_axis(scaled<double>(40.), scaled<double>(0.5)); Polylines res = expolygon.medial_axis(scaled<double>(0.5), scaled<double>(40.));
THEN("medial axis of a square shape is a single path") { THEN("medial axis of a square shape is a single path") {
REQUIRE(res.size() == 1); REQUIRE(res.size() == 1);
} }
@ -32,7 +32,7 @@ SCENARIO("Medial Axis", "[ThinWalls]") {
GIVEN("narrow rectangle") { GIVEN("narrow rectangle") {
ExPolygon expolygon{ Polygon::new_scale({ {100, 100}, {120, 100}, {120, 200}, {100, 200} }) }; ExPolygon expolygon{ Polygon::new_scale({ {100, 100}, {120, 100}, {120, 200}, {100, 200} }) };
WHEN("Medial axis is extracted") { WHEN("Medial axis is extracted") {
Polylines res = expolygon.medial_axis(scaled<double>(20.), scaled<double>(0.5)); Polylines res = expolygon.medial_axis(scaled<double>(0.5), scaled<double>(20.));
THEN("medial axis of a narrow rectangle is a single line") { THEN("medial axis of a narrow rectangle is a single line") {
REQUIRE(res.size() == 1); REQUIRE(res.size() == 1);
} }
@ -50,7 +50,7 @@ SCENARIO("Medial Axis", "[ThinWalls]") {
{100, 200} {100, 200}
})}; })};
WHEN("Medial axis is extracted") { WHEN("Medial axis is extracted") {
Polylines res = expolygon.medial_axis(scaled<double>(1.), scaled<double>(0.5)); Polylines res = expolygon.medial_axis(scaled<double>(0.5), scaled<double>(1.));
THEN("medial axis of a narrow rectangle with an extra vertex is still a single line") { THEN("medial axis of a narrow rectangle with an extra vertex is still a single line") {
REQUIRE(res.size() == 1); REQUIRE(res.size() == 1);
} }
@ -81,7 +81,7 @@ SCENARIO("Medial Axis", "[ThinWalls]") {
{1687689,4235755},{1218962,3499999},{827499,2748020},{482284,1920196},{219954,1088186},{31126,236479},{0,0},{1005754,0} {1687689,4235755},{1218962,3499999},{827499,2748020},{482284,1920196},{219954,1088186},{31126,236479},{0,0},{1005754,0}
}}; }};
WHEN("Medial axis is extracted") { WHEN("Medial axis is extracted") {
Polylines res = expolygon.medial_axis(scaled<double>(1.324888), scaled<double>(0.25)); Polylines res = expolygon.medial_axis(scaled<double>(0.25), scaled<double>(1.324888));
THEN("medial axis of a semicircumference is a single line") { THEN("medial axis of a semicircumference is a single line") {
REQUIRE(res.size() == 1); REQUIRE(res.size() == 1);
} }
@ -103,7 +103,7 @@ SCENARIO("Medial Axis", "[ThinWalls]") {
GIVEN("narrow trapezoid") { GIVEN("narrow trapezoid") {
ExPolygon expolygon{ Polygon::new_scale({ {100, 100}, {120, 100}, {112, 200}, {108, 200} }) }; ExPolygon expolygon{ Polygon::new_scale({ {100, 100}, {120, 100}, {112, 200}, {108, 200} }) };
WHEN("Medial axis is extracted") { WHEN("Medial axis is extracted") {
Polylines res = expolygon.medial_axis(scaled<double>(20.), scaled<double>(0.5)); Polylines res = expolygon.medial_axis(scaled<double>(0.5), scaled<double>(20.));
THEN("medial axis of a narrow trapezoid is a single line") { THEN("medial axis of a narrow trapezoid is a single line") {
REQUIRE(res.size() == 1); REQUIRE(res.size() == 1);
} }
@ -115,7 +115,7 @@ SCENARIO("Medial Axis", "[ThinWalls]") {
GIVEN("L shape") { GIVEN("L shape") {
ExPolygon expolygon{ Polygon::new_scale({ {100, 100}, {120, 100}, {120, 180}, {200, 180}, {200, 200}, {100, 200}, }) }; ExPolygon expolygon{ Polygon::new_scale({ {100, 100}, {120, 100}, {120, 180}, {200, 180}, {200, 200}, {100, 200}, }) };
WHEN("Medial axis is extracted") { WHEN("Medial axis is extracted") {
Polylines res = expolygon.medial_axis(scaled<double>(20.), scaled<double>(0.5)); Polylines res = expolygon.medial_axis(scaled<double>(0.5), scaled<double>(20.));
THEN("medial axis of an L shape is a single line") { THEN("medial axis of an L shape is a single line") {
REQUIRE(res.size() == 1); REQUIRE(res.size() == 1);
} }
@ -134,7 +134,7 @@ SCENARIO("Medial Axis", "[ThinWalls]") {
{-220815482,-37738966},{-221117540,-37738966},{-221117540,-51762024},{-203064906,-51762024}, {-220815482,-37738966},{-221117540,-37738966},{-221117540,-51762024},{-203064906,-51762024},
}}; }};
WHEN("Medial axis is extracted") { WHEN("Medial axis is extracted") {
Polylines res = expolygon.medial_axis(819998., 102499.75); Polylines res = expolygon.medial_axis(102499.75, 819998.);
THEN("medial axis is a single line") { THEN("medial axis is a single line") {
REQUIRE(res.size() == 1); REQUIRE(res.size() == 1);
} }
@ -147,7 +147,7 @@ SCENARIO("Medial Axis", "[ThinWalls]") {
GIVEN("narrow triangle") { GIVEN("narrow triangle") {
ExPolygon expolygon{ Polygon::new_scale({ {50, 100}, {1000, 102}, {50, 104} }) }; ExPolygon expolygon{ Polygon::new_scale({ {50, 100}, {1000, 102}, {50, 104} }) };
WHEN("Medial axis is extracted") { WHEN("Medial axis is extracted") {
Polylines res = expolygon.medial_axis(scaled<double>(4.), scaled<double>(0.5)); Polylines res = expolygon.medial_axis(scaled<double>(0.5), scaled<double>(4.));
THEN("medial axis of a narrow triangle is a single line") { THEN("medial axis of a narrow triangle is a single line") {
REQUIRE(res.size() == 1); REQUIRE(res.size() == 1);
} }
@ -159,7 +159,7 @@ SCENARIO("Medial Axis", "[ThinWalls]") {
GIVEN("GH #2474") { GIVEN("GH #2474") {
ExPolygon expolygon{{ {91294454,31032190},{11294481,31032190},{11294481,29967810},{44969182,29967810},{89909960,29967808},{91294454,29967808} }}; ExPolygon expolygon{{ {91294454,31032190},{11294481,31032190},{11294481,29967810},{44969182,29967810},{89909960,29967808},{91294454,29967808} }};
WHEN("Medial axis is extracted") { WHEN("Medial axis is extracted") {
Polylines res = expolygon.medial_axis(1871238, 500000); Polylines res = expolygon.medial_axis(500000, 1871238);
THEN("medial axis is a single line") { THEN("medial axis is a single line") {
REQUIRE(res.size() == 1); REQUIRE(res.size() == 1);
} }

View File

@ -1918,7 +1918,6 @@ TEST_CASE("Voronoi skeleton", "[VoronoiSkeleton]")
Lines lines = to_lines(poly); Lines lines = to_lines(poly);
construct_voronoi(lines.begin(), lines.end(), &vd); construct_voronoi(lines.begin(), lines.end(), &vd);
Slic3r::Voronoi::annotate_inside_outside(vd, lines); Slic3r::Voronoi::annotate_inside_outside(vd, lines);
Slic3r::Voronoi::annotate_inside_outside(vd, lines);
static constexpr double threshold_alpha = M_PI / 12.; // 30 degrees static constexpr double threshold_alpha = M_PI / 12.; // 30 degrees
std::vector<Vec2d> skeleton_edges = Slic3r::Voronoi::skeleton_edges_rough(vd, lines, threshold_alpha); std::vector<Vec2d> skeleton_edges = Slic3r::Voronoi::skeleton_edges_rough(vd, lines, threshold_alpha);