Rewritten the medial axis algorithm, now more robust (don't just prune MAT from endpoints, but validate all single edges)

This commit is contained in:
Alessandro Ranellucci 2016-03-20 20:20:32 +01:00
parent e4147a6bed
commit 7041bb5bd9
7 changed files with 231 additions and 209 deletions

View File

@ -61,7 +61,7 @@ if (0) {
[160, 140], [160, 140],
); );
my $expolygon = Slic3r::ExPolygon->new($square, $hole_in_square); my $expolygon = Slic3r::ExPolygon->new($square, $hole_in_square);
my $res = $expolygon->medial_axis(scale 1, scale 0.5); my $res = $expolygon->medial_axis(scale 40, scale 0.5);
is scalar(@$res), 1, 'medial axis of a square shape is a single path'; is scalar(@$res), 1, 'medial axis of a square shape is a single path';
isa_ok $res->[0], 'Slic3r::Polyline', 'medial axis result is a polyline'; isa_ok $res->[0], 'Slic3r::Polyline', 'medial axis result is a polyline';
ok $res->[0]->first_point->coincides_with($res->[0]->last_point), 'polyline forms a closed loop'; ok $res->[0]->first_point->coincides_with($res->[0]->last_point), 'polyline forms a closed loop';
@ -76,7 +76,7 @@ if (0) {
[120, 200], [120, 200],
[100, 200], [100, 200],
)); ));
my $res = $expolygon->medial_axis(scale 1, scale 0.5); my $res = $expolygon->medial_axis(scale 20, scale 0.5);
is scalar(@$res), 1, 'medial axis of a narrow rectangle is a single line'; is scalar(@$res), 1, 'medial axis of a narrow rectangle is a single line';
ok unscale($res->[0]->length) >= (200-100 - (120-100)) - epsilon, 'medial axis has reasonable length'; ok unscale($res->[0]->length) >= (200-100 - (120-100)) - epsilon, 'medial axis has reasonable length';
@ -101,7 +101,7 @@ if (0) {
[112, 200], [112, 200],
[108, 200], [108, 200],
)); ));
my $res = $expolygon->medial_axis(scale 1, scale 0.5); my $res = $expolygon->medial_axis(scale 20, scale 0.5);
is scalar(@$res), 1, 'medial axis of a narrow trapezoid is a single line'; is scalar(@$res), 1, 'medial axis of a narrow trapezoid is a single line';
ok unscale($res->[0]->length) >= (200-100 - (120-100)) - epsilon, 'medial axis has reasonable length'; ok unscale($res->[0]->length) >= (200-100 - (120-100)) - epsilon, 'medial axis has reasonable length';
} }
@ -115,7 +115,7 @@ if (0) {
[200, 200], [200, 200],
[100, 200], [100, 200],
)); ));
my $res = $expolygon->medial_axis(scale 1, scale 0.5); my $res = $expolygon->medial_axis(scale 20, scale 0.5);
is scalar(@$res), 1, 'medial axis of a L shape is a single polyline'; is scalar(@$res), 1, 'medial axis of a L shape is a single polyline';
my $len = unscale($res->[0]->length) + 20; # 20 is the thickness of the expolygon, which is subtracted from the ends my $len = unscale($res->[0]->length) + 20; # 20 is the thickness of the expolygon, which is subtracted from the ends
ok $len > 80*2 && $len < 100*2, 'medial axis has reasonable length'; ok $len > 80*2 && $len < 100*2, 'medial axis has reasonable length';
@ -150,7 +150,6 @@ if (0) {
[91294454,31032190],[11294481,31032190],[11294481,29967810],[44969182,29967810],[89909960,29967808],[91294454,29967808] [91294454,31032190],[11294481,31032190],[11294481,29967810],[44969182,29967810],[89909960,29967808],[91294454,29967808]
)); ));
my $polylines = $expolygon->medial_axis(1871238, 500000); my $polylines = $expolygon->medial_axis(1871238, 500000);
is scalar(@$polylines), 1, 'medial axis is a single polyline'; is scalar(@$polylines), 1, 'medial axis is a single polyline';
my $polyline = $polylines->[0]; my $polyline = $polylines->[0];

View File

@ -7,6 +7,7 @@
#include "polypartition.h" #include "polypartition.h"
#include "poly2tri/poly2tri.h" #include "poly2tri/poly2tri.h"
#include <algorithm> #include <algorithm>
#include <cassert>
#include <list> #include <list>
namespace Slic3r { namespace Slic3r {
@ -194,6 +195,7 @@ ExPolygon::medial_axis(double max_width, double min_width, ThickPolylines* polyl
svg.Close(); svg.Close();
*/ */
bool removed = false;
for (size_t i = 0; i < pp.size(); ++i) { for (size_t i = 0; i < pp.size(); ++i) {
ThickPolyline& polyline = pp[i]; ThickPolyline& polyline = pp[i];
@ -203,7 +205,7 @@ ExPolygon::medial_axis(double max_width, double min_width, ThickPolylines* polyl
call, so we keep the inner point until we perform the second intersection() as well */ call, so we keep the inner point until we perform the second intersection() as well */
Point new_front = polyline.points.front(); Point new_front = polyline.points.front();
Point new_back = polyline.points.back(); Point new_back = polyline.points.back();
if (polyline.endpoints.front() && !this->has_boundary_point(new_front)) { if (polyline.endpoints.first && !this->has_boundary_point(new_front)) {
Line line(polyline.points.front(), polyline.points[1]); Line line(polyline.points.front(), polyline.points[1]);
// prevent the line from touching on the other side, otherwise intersection() might return that solution // prevent the line from touching on the other side, otherwise intersection() might return that solution
@ -212,7 +214,7 @@ ExPolygon::medial_axis(double max_width, double min_width, ThickPolylines* polyl
line.extend_start(max_width); line.extend_start(max_width);
(void)this->contour.intersection(line, &new_front); (void)this->contour.intersection(line, &new_front);
} }
if (polyline.endpoints.back() && !this->has_boundary_point(new_back)) { if (polyline.endpoints.second && !this->has_boundary_point(new_back)) {
Line line( Line line(
*(polyline.points.end() - 2), *(polyline.points.end() - 2),
polyline.points.back() polyline.points.back()
@ -230,14 +232,54 @@ ExPolygon::medial_axis(double max_width, double min_width, ThickPolylines* polyl
/* remove too short polylines /* remove too short polylines
(we can't do this check before endpoints extension and clipping because we don't (we can't do this check before endpoints extension and clipping because we don't
know how long will the endpoints be extended since it depends on polygon thickness know how long will the endpoints be extended since it depends on polygon thickness
which is variable - extension will be <= max_width/2 on each side) */ which is variable - extension will be <= max_width/2 on each side)
if (polyline.length() < max_width) { Maybe instead of using max_width we should use max_element(polyline.width) */
if ((polyline.endpoints.first || polyline.endpoints.second)
&& polyline.length() < max_width*2) {
pp.erase(pp.begin() + i); pp.erase(pp.begin() + i);
--i; --i;
removed = true;
continue; continue;
} }
} }
/* If we removed any short polylines we now try to connect consecutive polylines
in order to allow loop detection. Note that this algorithm is greedier than
MedialAxis::process_edge_neighbors() as it will connect random pairs of
polylines even when more than two start from the same point. This has no
drawbacks since we optimize later using nearest-neighbor which would do the
same, but should we use a more sophisticated optimization algorithm we should
not connect polylines when more than two meet. */
if (removed) {
for (size_t i = 0; i < pp.size(); ++i) {
ThickPolyline& polyline = pp[i];
if (polyline.endpoints.first && polyline.endpoints.second) continue; // optimization
// find another polyline starting here
for (size_t j = i+1; j < pp.size(); ++j) {
ThickPolyline& other = pp[j];
if (polyline.last_point().coincides_with(other.last_point())) {
other.reverse();
} else if (polyline.first_point().coincides_with(other.last_point())) {
polyline.reverse();
other.reverse();
} else if (polyline.first_point().coincides_with(other.first_point())) {
polyline.reverse();
} else if (!polyline.last_point().coincides_with(other.first_point())) {
continue;
}
polyline.points.insert(polyline.points.end(), other.points.begin() + 1, other.points.end());
polyline.width.insert(polyline.width.end(), other.width.begin(), other.width.end());
polyline.endpoints.second = other.endpoints.second;
assert(polyline.width.size() == polyline.points.size()*2 - 2);
pp.erase(pp.begin() + j);
j = i; // restart search from i+1
}
}
}
polylines->insert(polylines->end(), pp.begin(), pp.end()); polylines->insert(polylines->end(), pp.begin(), pp.end());
} }

View File

@ -293,12 +293,6 @@ arrange(size_t total_parts, Pointf part, coordf_t dist, const BoundingBoxf* bb)
void void
MedialAxis::build(ThickPolylines* polylines) MedialAxis::build(ThickPolylines* polylines)
{ {
/*
// build bounding box (we use it for clipping infinite segments)
// --> we have no infinite segments
this->bb = BoundingBox(this->lines);
*/
construct_voronoi(this->lines.begin(), this->lines.end(), &this->vd); construct_voronoi(this->lines.begin(), this->lines.end(), &this->vd);
/* /*
@ -321,105 +315,59 @@ MedialAxis::build(ThickPolylines* polylines)
// 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->edges.clear(); this->valid_edges.clear();
for (VD::const_edge_iterator edge = this->vd.edges().begin(); edge != this->vd.edges().end(); ++edge) { {
// if we only process segments representing closed loops, none if the std::set<const VD::edge_type*> seen_edges;
// infinite edges (if any) would be part of our MAT anyway for (VD::const_edge_iterator edge = this->vd.edges().begin(); edge != this->vd.edges().end(); ++edge) {
if (edge->is_secondary() || edge->is_infinite()) continue; // if we only process segments representing closed loops, none if the
this->edges.insert(&*edge); // infinite edges (if any) would be part of our MAT anyway
} if (edge->is_secondary() || edge->is_infinite()) continue;
// count valid segments for each vertex // don't re-validate twins
std::map< vert_t*,std::set<edge_t*> > vertex_edges; // collects edges connected for each vertex if (seen_edges.find(&*edge) != seen_edges.end()) continue;
std::set<vert_t*> startpoints; // collects all vertices having a single starting edge seen_edges.insert(&*edge);
for (VD::const_vertex_iterator it = this->vd.vertices().begin(); it != this->vd.vertices().end(); ++it) { seen_edges.insert(edge->twin());
vert_t* vertex = &*it;
// loop through all edges originating from this vertex if (!this->validate_edge(&*edge)) continue;
// starting from a random one this->valid_edges.insert(&*edge);
edge_t* edge = vertex->incident_edge(); this->valid_edges.insert(edge->twin());
do {
// if this edge was not pruned by our filter above,
// add it to vertex_edges
if (this->edges.count(edge) > 0)
vertex_edges[vertex].insert(edge);
// continue looping next edge originating from this vertex
edge = edge->rot_next();
} while (edge != vertex->incident_edge());
// if there's only one edge starting at this vertex then it's an endpoint
if (vertex_edges[vertex].size() == 1) {
startpoints.insert(vertex);
} }
} }
this->edges = this->valid_edges;
// prune startpoints recursively if extreme segments are not valid
while (!startpoints.empty()) {
// get a random entry node
vert_t* v = *startpoints.begin();
// get edge starting from v
assert(vertex_edges[v].size() == 1);
edge_t* edge = *vertex_edges[v].begin();
if (!this->validate_edge(*edge)) {
// if edge is not valid, erase it and its twin from edge list
(void)this->edges.erase(edge);
(void)this->edges.erase(edge->twin());
// decrement edge counters for the affected nodes
vert_t* v1 = edge->vertex1();
(void)vertex_edges[v].erase(edge);
(void)vertex_edges[v1].erase(edge->twin());
// also, check whether the end vertex is a new leaf
if (vertex_edges[v1].size() == 1) {
startpoints.insert(v1);
} else if (vertex_edges[v1].empty()) {
startpoints.erase(v1);
}
}
// remove node from the set to prevent it from being visited again
startpoints.erase(v);
}
// iterate through the valid edges to build polylines // iterate through the valid edges to build polylines
while (!this->edges.empty()) { while (!this->edges.empty()) {
edge_t &edge = **this->edges.begin(); const edge_t* edge = *this->edges.begin();
// start a polyline // start a polyline
ThickPolyline polyline; ThickPolyline polyline;
polyline.points.push_back(Point( edge.vertex0()->x(), edge.vertex0()->y() )); polyline.points.push_back(Point( edge->vertex0()->x(), edge->vertex0()->y() ));
polyline.points.push_back(Point( edge.vertex1()->x(), edge.vertex1()->y() )); polyline.points.push_back(Point( edge->vertex1()->x(), edge->vertex1()->y() ));
polyline.width.push_back(this->thickness[&edge].first); polyline.width.push_back(this->thickness[edge].first);
polyline.width.push_back(this->thickness[&edge].second); polyline.width.push_back(this->thickness[edge].second);
// remove this edge and its twin from the available edges // remove this edge and its twin from the available edges
(void)this->edges.erase(&edge); (void)this->edges.erase(edge);
(void)this->edges.erase(edge.twin()); (void)this->edges.erase(edge->twin());
// get next points // get next points
this->process_edge_neighbors(edge, &polyline.points, &polyline.width, &polyline.endpoints); this->process_edge_neighbors(edge, &polyline);
// get previous points // get previous points
{ {
Points pp; ThickPolyline rpolyline;
std::vector<coordf_t> width; this->process_edge_neighbors(edge->twin(), &rpolyline);
std::vector<bool> endpoints; polyline.points.insert(polyline.points.begin(), rpolyline.points.rbegin(), rpolyline.points.rend());
this->process_edge_neighbors(*edge.twin(), &pp, &width, &endpoints); polyline.width.insert(polyline.width.begin(), rpolyline.width.rbegin(), rpolyline.width.rend());
polyline.points.insert(polyline.points.begin(), pp.rbegin(), pp.rend()); polyline.endpoints.first = rpolyline.endpoints.second;
polyline.width.insert(polyline.width.begin(), width.rbegin(), width.rend());
polyline.endpoints.insert(polyline.endpoints.begin(), endpoints.rbegin(), endpoints.rend());
} }
assert(polyline.width.size() == polyline.points.size()*2 - 2); assert(polyline.width.size() == polyline.points.size()*2 - 2);
assert(polyline.endpoints.size() == polyline.points.size());
// prevent loop endpoints from being extended
if (polyline.first_point().coincides_with(polyline.last_point())) { if (polyline.first_point().coincides_with(polyline.last_point())) {
polyline.endpoints.front() = false; polyline.endpoints.first = false;
polyline.endpoints.back() = false; polyline.endpoints.second = false;
} }
// append polyline to result // append polyline to result
@ -436,106 +384,139 @@ MedialAxis::build(Polylines* polylines)
} }
void void
MedialAxis::process_edge_neighbors(const VD::edge_type& edge, Points* points, MedialAxis::process_edge_neighbors(const VD::edge_type* edge, ThickPolyline* polyline)
std::vector<coordf_t>* width, std::vector<bool>* endpoints)
{ {
// Since rot_next() works on the edge starting point but we want while (true) {
// to find neighbors on the ending point, we just swap edge with // Since rot_next() works on the edge starting point but we want
// its twin. // to find neighbors on the ending point, we just swap edge with
const VD::edge_type& twin = *edge.twin(); // its 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; std::vector<const VD::edge_type*> neighbors;
for (const VD::edge_type* neighbor = twin.rot_next(); neighbor != &twin; neighbor = neighbor->rot_next()) { for (const VD::edge_type* neighbor = twin->rot_next(); neighbor != twin;
if (this->edges.count(neighbor) > 0) neighbors.push_back(neighbor); neighbor = neighbor->rot_next()) {
} if (this->valid_edges.count(neighbor) > 0) neighbors.push_back(neighbor);
}
// 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 (neighbors.size() == 1) {
endpoints->push_back(false); const VD::edge_type* neighbor = neighbors.front();
const VD::edge_type& neighbor = *neighbors.front();
points->push_back(Point( neighbor.vertex1()->x(), neighbor.vertex1()->y() )); // break if this is a closed loop
width->push_back(this->thickness[&neighbor].first); if (this->edges.count(neighbor) == 0) return;
width->push_back(this->thickness[&neighbor].second);
(void)this->edges.erase(&neighbor); Point new_point(neighbor->vertex1()->x(), neighbor->vertex1()->y());
(void)this->edges.erase(neighbor.twin()); polyline->points.push_back(new_point);
this->process_edge_neighbors(neighbor, points, width, endpoints); polyline->width.push_back(this->thickness[neighbor].first);
} else if (neighbors.size() == 0) { polyline->width.push_back(this->thickness[neighbor].second);
endpoints->push_back(true); (void)this->edges.erase(neighbor);
} else { (void)this->edges.erase(neighbor->twin());
// T-shaped or star-shaped joint edge = neighbor;
endpoints->push_back(false); } else if (neighbors.size() == 0) {
polyline->endpoints.second = true;
return;
} else {
// T-shaped or star-shaped joint
return;
}
} }
} }
bool bool
MedialAxis::validate_edge(const VD::edge_type& edge) MedialAxis::validate_edge(const VD::edge_type* edge)
{ {
/* If the cells sharing this edge have a common vertex, we're not (probably) interested // construct the line representing this edge of the Voronoi diagram
in this edge. Why? Because it means that the edge lies on the bisector of const Line line(
two contiguous input lines and it was included in the Voronoi graph because Point( edge->vertex0()->x(), edge->vertex0()->y() ),
it's the locus of centers of circles tangent to both vertices. Due to the Point( edge->vertex1()->x(), edge->vertex1()->y() )
"thin" nature of our input, these edges will be very short and not part of );
our wanted output. */
// 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.coincides_with(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 &cell1 = *edge.cell(); const VD::cell_type* cell1 = edge->cell();
const VD::cell_type &cell2 = *edge.twin()->cell(); const VD::cell_type* cell2 = edge->twin()->cell();
if (!cell1.contains_segment() || !cell2.contains_segment()) return false;
const Line &segment1 = this->retrieve_segment(cell1); const Line &segment1 = this->retrieve_segment(cell1);
const Line &segment2 = this->retrieve_segment(cell2); const Line &segment2 = this->retrieve_segment(cell2);
// calculate the relative angle between the two boundary segments /* Calculate thickness of the section at both the endpoints of this edge.
double angle = fabs(segment2.orientation() - segment1.orientation()); Our Voronoi edge is part of a CCW sequence going around its Voronoi cell
(segment1). This edge's twin goes around segment2. Thus, segment2 is
oriented in the same direction as our main edge, and segment1 is oriented
in the same direction as our twin edge.
We used to only consider the (half-)distances to segment2, and that works
whenever segment1 and segment2 are almost specular and facing. However,
at curves they are staggered and they only face for a very little length
(such visibility actually coincides with our very short edge). This is why
we calculate w0 and w1 this way.
When cell1 or cell2 don't refer to the segment but only to an endpoint, we
calculate the distance to that endpoint instead. */
// fabs(angle) ranges from 0 (collinear, same direction) to PI (collinear, opposite direction) coordf_t w0 = cell2->contains_segment()
// we're interested only in segments close to the second case (facing segments) ? line.a.perp_distance_to(segment2)*2
// so we allow some tolerance. : line.a.distance_to(this->retrieve_endpoint(cell2))*2;
// this filter ensures that we're dealing with a narrow/oriented area (longer than thick)
if (fabs(angle - PI) > PI/5) {
return false;
}
// each edge vertex is equidistant to both cell segments coordf_t w1 = cell1->contains_segment()
// but such distance might differ between the two vertices; ? line.b.perp_distance_to(segment1)*2
// in this case it means the shape is getting narrow (like a corner) : line.b.distance_to(this->retrieve_endpoint(cell1))*2;
// and we might need to skip the edge since it's not really part of
// our skeleton
/* Calculate perpendicular distance. We consider segment2 instead of segment1
because our Voronoi edge is part of a CCW sequence going around its Voronoi cell
(segment).
This means that such segment is on the left on our edge, and goes backwards.
So we use the cell of the twin edge, which is located on the right of our edge
and goes in the same direction as it. This way we can map dist0 and dist1
correctly. */
const Line line(
Point( edge.vertex0()->x(), edge.vertex0()->y() ),
Point( edge.vertex1()->x(), edge.vertex1()->y() )
);
coordf_t dist0 = segment2.a.perp_distance_to(line)*2;
coordf_t dist1 = segment2.b.perp_distance_to(line)*2;
// if this edge is the centerline for a very thin area, we might want to skip it // if this edge is the centerline for a very thin area, we might want to skip it
// in case the area is too thin // in case the area is too thin
if (dist0 < this->min_width && dist1 < this->min_width) { if (w0 < SCALED_EPSILON || w1 < SCALED_EPSILON) {
//printf(" => too thin, skipping\n"); if (cell1->contains_segment() && cell2->contains_segment()) {
return false; // calculate the relative angle between the two boundary segments
double angle = fabs(segment2.orientation() - segment1.orientation());
// 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)
// so we allow some tolerance.
// 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
// and the endpoint of another segment), since their orientation would not be meaningful
if (fabs(angle - PI) > PI/5) return false;
} else {
return false;
}
} }
if (this->expolygon != NULL && !this->expolygon->contains(line)) if (w0 < this->min_width && w1 < this->min_width)
return false; return false;
this->thickness[&edge] = std::make_pair(dist0, dist1); if (w0 > this->max_width && w1 > this->max_width)
return false;
this->thickness[edge] = std::make_pair(w0, w1);
this->thickness[edge->twin()] = std::make_pair(w1, w0);
return true; return true;
} }
const Line& const Line&
MedialAxis::retrieve_segment(const VD::cell_type& cell) const MedialAxis::retrieve_segment(const VD::cell_type* cell) const
{ {
VD::cell_type::source_index_type index = cell.source_index() - this->points.size(); return this->lines[cell->source_index()];
return this->lines[index]; }
const Point&
MedialAxis::retrieve_endpoint(const VD::cell_type* cell) const
{
const Line& line = this->retrieve_segment(cell);
if (cell->source_category() == SOURCE_CATEGORY_SEGMENT_START_POINT) {
return line.a;
} else {
return line.b;
}
} }
} } } }

View File

@ -42,7 +42,6 @@ Pointfs arrange(size_t total_parts, Pointf part, coordf_t dist, const BoundingBo
class MedialAxis { class MedialAxis {
public: public:
Points points;
Lines lines; Lines lines;
const ExPolygon* expolygon; const ExPolygon* expolygon;
double max_width; double max_width;
@ -55,12 +54,12 @@ class MedialAxis {
private: private:
typedef voronoi_diagram<double> VD; typedef voronoi_diagram<double> VD;
VD vd; VD vd;
std::set<const VD::edge_type*> edges; std::set<const VD::edge_type*> edges, valid_edges;
std::map<const VD::edge_type*, std::pair<coordf_t,coordf_t> > thickness; std::map<const VD::edge_type*, std::pair<coordf_t,coordf_t> > thickness;
void process_edge_neighbors(const voronoi_diagram<double>::edge_type& edge, void process_edge_neighbors(const VD::edge_type* edge, ThickPolyline* polyline);
Points* points, std::vector<coordf_t>* width, std::vector<bool>* endpoints); bool validate_edge(const VD::edge_type* edge);
bool validate_edge(const voronoi_diagram<double>::edge_type& edge); const Line& retrieve_segment(const VD::cell_type* cell) const;
const Line& retrieve_segment(const voronoi_diagram<double>::cell_type& cell) const; const Point& retrieve_endpoint(const VD::cell_type* cell) const;
}; };
} } } }

View File

@ -25,7 +25,6 @@ PerimeterGenerator::process()
// solid infill // solid infill
coord_t ispacing = this->solid_infill_flow.scaled_spacing(); coord_t ispacing = this->solid_infill_flow.scaled_spacing();
coord_t gap_area_threshold = pwidth * pwidth;
// Calculate the minimum required spacing between two adjacent traces. // Calculate the minimum required spacing between two adjacent traces.
// This should be equal to the nominal flow spacing but we experiment // This should be equal to the nominal flow spacing but we experiment
@ -137,16 +136,11 @@ PerimeterGenerator::process()
// not using safety offset here would "detect" very narrow gaps // not using safety offset here would "detect" very narrow gaps
// (but still long enough to escape the area threshold) that gap fill // (but still long enough to escape the area threshold) that gap fill
// won't be able to fill but we'd still remove from infill area // won't be able to fill but we'd still remove from infill area
ExPolygons diff_expp = diff_ex( Polygons diff_pp = diff(
offset(last, -0.5*distance), offset(last, -0.5*distance),
offset(offsets, +0.5*distance + 10) // safety offset offset(offsets, +0.5*distance + 10) // safety offset
); );
for (ExPolygons::const_iterator ex = diff_expp.begin(); ex != diff_expp.end(); ++ex) { gaps.insert(gaps.end(), diff_pp.begin(), diff_pp.end());
if (fabs(ex->area()) >= gap_area_threshold) {
Polygons pp = *ex;
gaps.insert(gaps.end(), pp.begin(), pp.end());
}
}
} }
} }
@ -396,18 +390,10 @@ PerimeterGenerator::_traverse_loops(const PerimeterGeneratorLoops &loops,
} }
// append thin walls to the nearest-neighbor search (only for first iteration) // append thin walls to the nearest-neighbor search (only for first iteration)
{ if (!thin_walls.empty()) {
ExtrusionEntityCollection tw = this->_variable_width ExtrusionEntityCollection tw = this->_variable_width
(thin_walls, erExternalPerimeter, this->ext_perimeter_flow); (thin_walls, erExternalPerimeter, this->ext_perimeter_flow);
const double threshold = this->ext_perimeter_flow.scaled_width() * 2;
for (size_t i = 0; i < tw.entities.size(); ++i) {
if (tw.entities[i]->length() < threshold) {
tw.remove(i);
--i;
}
}
coll.append(tw.entities); coll.append(tw.entities);
thin_walls.clear(); thin_walls.clear();
} }
@ -457,12 +443,16 @@ PerimeterGenerator::_variable_width(const ThickPolylines &polylines, ExtrusionRo
ExtrusionPaths paths; ExtrusionPaths paths;
ExtrusionPath path(role); ExtrusionPath path(role);
ThickLines lines = p->thicklines(); ThickLines lines = p->thicklines();
for (size_t i = 0; i < lines.size(); ++i) { for (size_t i = 0; i < lines.size(); ++i) {
const ThickLine& line = lines[i]; const ThickLine& line = lines[i];
const double thickness_delta = fabs(line.a_width - line.b_width);
const coordf_t line_len = line.length();
if (line_len < SCALED_EPSILON) continue;
double thickness_delta = fabs(line.a_width - line.b_width);
if (thickness_delta > tolerance) { if (thickness_delta > tolerance) {
const unsigned short segments = ceil(thickness_delta / tolerance); const unsigned short segments = ceil(thickness_delta / tolerance);
const coordf_t line_len = line.length();
const coordf_t seg_len = line_len / segments; const coordf_t seg_len = line_len / segments;
Points pp; Points pp;
std::vector<coordf_t> width; std::vector<coordf_t> width;
@ -509,30 +499,30 @@ PerimeterGenerator::_variable_width(const ThickPolylines &polylines, ExtrusionRo
path.mm3_per_mm = flow.mm3_per_mm(); path.mm3_per_mm = flow.mm3_per_mm();
path.width = flow.width; path.width = flow.width;
path.height = flow.height; path.height = flow.height;
} else if (fabs(flow.width - w) <= tolerance) {
// the width difference between this line and the current flow width is
// within the accepted tolerance
path.polyline.append(line.b);
} else { } else {
// we need to initialize a new line thickness_delta = fabs(scale_(flow.width) - w);
paths.push_back(path); if (thickness_delta <= tolerance) {
path = ExtrusionPath(role); // the width difference between this line and the current flow width is
--i; // within the accepted tolerance
path.polyline.append(line.b);
} else {
// we need to initialize a new line
paths.push_back(path);
path = ExtrusionPath(role);
--i;
}
} }
} }
if (!path.polyline.points.empty()) if (path.polyline.is_valid())
paths.push_back(path); paths.push_back(path);
// loop through generated paths // append paths to collection
for (ExtrusionPaths::const_iterator p = paths.begin(); p != paths.end(); ++p) { if (!paths.empty()) {
if (p->polyline.is_valid()) { if (paths.front().first_point().coincides_with(paths.back().last_point())) {
if (p->first_point().coincides_with(p->last_point())) { coll.append(ExtrusionLoop(paths));
// since medial_axis() now returns only Polyline objects, detect loops here } else {
coll.append(ExtrusionLoop(*p)); coll.append(paths);
} else {
coll.append(*p);
}
} }
} }
} }

View File

@ -4,6 +4,7 @@
#include "Line.hpp" #include "Line.hpp"
#include "Polygon.hpp" #include "Polygon.hpp"
#include <iostream> #include <iostream>
#include <utility>
namespace Slic3r { namespace Slic3r {
@ -235,4 +236,12 @@ ThickPolyline::thicklines() const
return lines; return lines;
} }
void
ThickPolyline::reverse()
{
Polyline::reverse();
std::reverse(this->width.begin(), this->width.end());
std::swap(this->endpoints.first, this->endpoints.second);
}
} }

View File

@ -36,8 +36,10 @@ class Polyline : public MultiPoint {
class ThickPolyline : public Polyline { class ThickPolyline : public Polyline {
public: public:
std::vector<coordf_t> width; std::vector<coordf_t> width;
std::vector<bool> endpoints; std::pair<bool,bool> endpoints;
ThickPolyline() : endpoints(std::make_pair(false, false)) {};
ThickLines thicklines() const; ThickLines thicklines() const;
void reverse();
}; };
} }