From 7041bb5bd950eaf7b4b4e7476e1b12afdef138c2 Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Sun, 20 Mar 2016 20:20:32 +0100 Subject: [PATCH] Rewritten the medial axis algorithm, now more robust (don't just prune MAT from endpoints, but validate all single edges) --- t/thin.t | 9 +- xs/src/libslic3r/ExPolygon.cpp | 50 +++- xs/src/libslic3r/Geometry.cpp | 291 +++++++++++------------- xs/src/libslic3r/Geometry.hpp | 11 +- xs/src/libslic3r/PerimeterGenerator.cpp | 66 +++--- xs/src/libslic3r/Polyline.cpp | 9 + xs/src/libslic3r/Polyline.hpp | 4 +- 7 files changed, 231 insertions(+), 209 deletions(-) diff --git a/t/thin.t b/t/thin.t index 0b3843a10..276a29e2b 100644 --- a/t/thin.t +++ b/t/thin.t @@ -61,7 +61,7 @@ if (0) { [160, 140], ); 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'; 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'; @@ -76,7 +76,7 @@ if (0) { [120, 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'; ok unscale($res->[0]->length) >= (200-100 - (120-100)) - epsilon, 'medial axis has reasonable length'; @@ -101,7 +101,7 @@ if (0) { [112, 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'; ok unscale($res->[0]->length) >= (200-100 - (120-100)) - epsilon, 'medial axis has reasonable length'; } @@ -115,7 +115,7 @@ if (0) { [200, 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'; 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'; @@ -150,7 +150,6 @@ if (0) { [91294454,31032190],[11294481,31032190],[11294481,29967810],[44969182,29967810],[89909960,29967808],[91294454,29967808] )); my $polylines = $expolygon->medial_axis(1871238, 500000); - is scalar(@$polylines), 1, 'medial axis is a single polyline'; my $polyline = $polylines->[0]; diff --git a/xs/src/libslic3r/ExPolygon.cpp b/xs/src/libslic3r/ExPolygon.cpp index 04ba1023a..800443d96 100644 --- a/xs/src/libslic3r/ExPolygon.cpp +++ b/xs/src/libslic3r/ExPolygon.cpp @@ -7,6 +7,7 @@ #include "polypartition.h" #include "poly2tri/poly2tri.h" #include +#include #include namespace Slic3r { @@ -194,6 +195,7 @@ ExPolygon::medial_axis(double max_width, double min_width, ThickPolylines* polyl svg.Close(); */ + bool removed = false; for (size_t i = 0; i < pp.size(); ++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 */ Point new_front = polyline.points.front(); 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]); // 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); (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( *(polyline.points.end() - 2), polyline.points.back() @@ -230,14 +232,54 @@ ExPolygon::medial_axis(double max_width, double min_width, ThickPolylines* polyl /* remove too short polylines (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 - which is variable - extension will be <= max_width/2 on each side) */ - if (polyline.length() < max_width) { + which is variable - extension will be <= max_width/2 on each side) + 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); --i; + removed = true; 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()); } diff --git a/xs/src/libslic3r/Geometry.cpp b/xs/src/libslic3r/Geometry.cpp index 890272eb5..d4528e864 100644 --- a/xs/src/libslic3r/Geometry.cpp +++ b/xs/src/libslic3r/Geometry.cpp @@ -293,12 +293,6 @@ arrange(size_t total_parts, Pointf part, coordf_t dist, const BoundingBoxf* bb) void 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); /* @@ -321,105 +315,59 @@ MedialAxis::build(ThickPolylines* polylines) // 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 - this->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 - // infinite edges (if any) would be part of our MAT anyway - if (edge->is_secondary() || edge->is_infinite()) continue; - this->edges.insert(&*edge); - } - - // count valid segments for each vertex - std::map< vert_t*,std::set > vertex_edges; // collects edges connected for each vertex - std::set startpoints; // collects all vertices having a single starting edge - for (VD::const_vertex_iterator it = this->vd.vertices().begin(); it != this->vd.vertices().end(); ++it) { - vert_t* vertex = &*it; + this->valid_edges.clear(); + { + std::set seen_edges; + 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 + // infinite edges (if any) would be part of our MAT anyway + if (edge->is_secondary() || edge->is_infinite()) continue; - // loop through all edges originating from this vertex - // starting from a random one - edge_t* edge = vertex->incident_edge(); - 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); + // don't re-validate twins + if (seen_edges.find(&*edge) != seen_edges.end()) continue; + seen_edges.insert(&*edge); + seen_edges.insert(edge->twin()); - // 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); + if (!this->validate_edge(&*edge)) continue; + this->valid_edges.insert(&*edge); + this->valid_edges.insert(edge->twin()); } } - - // 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); - } + this->edges = this->valid_edges; // iterate through the valid edges to build polylines while (!this->edges.empty()) { - edge_t &edge = **this->edges.begin(); + const edge_t* edge = *this->edges.begin(); // start a polyline ThickPolyline polyline; - polyline.points.push_back(Point( edge.vertex0()->x(), edge.vertex0()->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].second); + polyline.points.push_back(Point( edge->vertex0()->x(), edge->vertex0()->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].second); // remove this edge and its twin from the available edges - (void)this->edges.erase(&edge); - (void)this->edges.erase(edge.twin()); + (void)this->edges.erase(edge); + (void)this->edges.erase(edge->twin()); // get next points - this->process_edge_neighbors(edge, &polyline.points, &polyline.width, &polyline.endpoints); + this->process_edge_neighbors(edge, &polyline); // get previous points { - Points pp; - std::vector width; - std::vector endpoints; - this->process_edge_neighbors(*edge.twin(), &pp, &width, &endpoints); - polyline.points.insert(polyline.points.begin(), pp.rbegin(), pp.rend()); - polyline.width.insert(polyline.width.begin(), width.rbegin(), width.rend()); - polyline.endpoints.insert(polyline.endpoints.begin(), endpoints.rbegin(), endpoints.rend()); + ThickPolyline rpolyline; + this->process_edge_neighbors(edge->twin(), &rpolyline); + polyline.points.insert(polyline.points.begin(), rpolyline.points.rbegin(), rpolyline.points.rend()); + 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); - assert(polyline.endpoints.size() == polyline.points.size()); + // prevent loop endpoints from being extended if (polyline.first_point().coincides_with(polyline.last_point())) { - polyline.endpoints.front() = false; - polyline.endpoints.back() = false; + polyline.endpoints.first = false; + polyline.endpoints.second = false; } // append polyline to result @@ -436,106 +384,139 @@ MedialAxis::build(Polylines* polylines) } void -MedialAxis::process_edge_neighbors(const VD::edge_type& edge, Points* points, - std::vector* width, std::vector* endpoints) +MedialAxis::process_edge_neighbors(const VD::edge_type* edge, ThickPolyline* polyline) { - // Since rot_next() works on the edge starting point but we want - // to find neighbors on the ending point, we just swap edge with - // its twin. - const VD::edge_type& twin = *edge.twin(); + while (true) { + // Since rot_next() works on the edge starting point but we want + // to find neighbors on the ending point, we just swap edge with + // its twin. + const VD::edge_type* twin = edge->twin(); - // count neighbors for this edge - std::vector neighbors; - for (const VD::edge_type* neighbor = twin.rot_next(); neighbor != &twin; neighbor = neighbor->rot_next()) { - if (this->edges.count(neighbor) > 0) neighbors.push_back(neighbor); - } + // count neighbors for this edge + std::vector neighbors; + 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 we have a single neighbor then we can continue recursively - if (neighbors.size() == 1) { - endpoints->push_back(false); - const VD::edge_type& neighbor = *neighbors.front(); - points->push_back(Point( neighbor.vertex1()->x(), neighbor.vertex1()->y() )); - width->push_back(this->thickness[&neighbor].first); - width->push_back(this->thickness[&neighbor].second); - (void)this->edges.erase(&neighbor); - (void)this->edges.erase(neighbor.twin()); - this->process_edge_neighbors(neighbor, points, width, endpoints); - } else if (neighbors.size() == 0) { - endpoints->push_back(true); - } else { - // T-shaped or star-shaped joint - endpoints->push_back(false); + // if we have a single neighbor then we can continue recursively + if (neighbors.size() == 1) { + const VD::edge_type* neighbor = neighbors.front(); + + // break if this is a closed loop + if (this->edges.count(neighbor) == 0) return; + + Point new_point(neighbor->vertex1()->x(), neighbor->vertex1()->y()); + polyline->points.push_back(new_point); + polyline->width.push_back(this->thickness[neighbor].first); + polyline->width.push_back(this->thickness[neighbor].second); + (void)this->edges.erase(neighbor); + (void)this->edges.erase(neighbor->twin()); + edge = neighbor; + } else if (neighbors.size() == 0) { + polyline->endpoints.second = true; + return; + } else { + // T-shaped or star-shaped joint + return; + } } } 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 - in this edge. Why? Because it means that the edge lies on the bisector of - two contiguous input lines and it was included in the Voronoi graph because - it's the locus of centers of circles tangent to both vertices. Due to the - "thin" nature of our input, these edges will be very short and not part of - our wanted output. */ + // construct the line representing this edge of the Voronoi diagram + const Line line( + Point( edge->vertex0()->x(), edge->vertex0()->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.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 - const VD::cell_type &cell1 = *edge.cell(); - const VD::cell_type &cell2 = *edge.twin()->cell(); - if (!cell1.contains_segment() || !cell2.contains_segment()) return false; + const VD::cell_type* cell1 = edge->cell(); + const VD::cell_type* cell2 = edge->twin()->cell(); const Line &segment1 = this->retrieve_segment(cell1); const Line &segment2 = this->retrieve_segment(cell2); - // calculate the relative angle between the two boundary segments - double angle = fabs(segment2.orientation() - segment1.orientation()); + /* Calculate thickness of the section at both the endpoints of this edge. + 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) - // 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) - if (fabs(angle - PI) > PI/5) { - return false; - } + coordf_t w0 = cell2->contains_segment() + ? line.a.perp_distance_to(segment2)*2 + : line.a.distance_to(this->retrieve_endpoint(cell2))*2; - // each edge vertex is equidistant to both cell segments - // but such distance might differ between the two vertices; - // in this case it means the shape is getting narrow (like a corner) - // 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; + coordf_t w1 = cell1->contains_segment() + ? line.b.perp_distance_to(segment1)*2 + : line.b.distance_to(this->retrieve_endpoint(cell1))*2; // if this edge is the centerline for a very thin area, we might want to skip it // in case the area is too thin - if (dist0 < this->min_width && dist1 < this->min_width) { - //printf(" => too thin, skipping\n"); - return false; + if (w0 < SCALED_EPSILON || w1 < SCALED_EPSILON) { + if (cell1->contains_segment() && cell2->contains_segment()) { + // 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; - 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; } 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[index]; + 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() == SOURCE_CATEGORY_SEGMENT_START_POINT) { + return line.a; + } else { + return line.b; + } } } } diff --git a/xs/src/libslic3r/Geometry.hpp b/xs/src/libslic3r/Geometry.hpp index 6d9a63b73..af4667d42 100644 --- a/xs/src/libslic3r/Geometry.hpp +++ b/xs/src/libslic3r/Geometry.hpp @@ -42,7 +42,6 @@ Pointfs arrange(size_t total_parts, Pointf part, coordf_t dist, const BoundingBo class MedialAxis { public: - Points points; Lines lines; const ExPolygon* expolygon; double max_width; @@ -55,12 +54,12 @@ class MedialAxis { private: typedef voronoi_diagram VD; VD vd; - std::set edges; + std::set edges, valid_edges; std::map > thickness; - void process_edge_neighbors(const voronoi_diagram::edge_type& edge, - Points* points, std::vector* width, std::vector* endpoints); - bool validate_edge(const voronoi_diagram::edge_type& edge); - const Line& retrieve_segment(const voronoi_diagram::cell_type& cell) const; + void process_edge_neighbors(const VD::edge_type* edge, ThickPolyline* polyline); + 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; }; } } diff --git a/xs/src/libslic3r/PerimeterGenerator.cpp b/xs/src/libslic3r/PerimeterGenerator.cpp index ae3a2088f..c0022ec73 100644 --- a/xs/src/libslic3r/PerimeterGenerator.cpp +++ b/xs/src/libslic3r/PerimeterGenerator.cpp @@ -25,7 +25,6 @@ PerimeterGenerator::process() // solid infill 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. // 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 // (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 - ExPolygons diff_expp = diff_ex( + Polygons diff_pp = diff( offset(last, -0.5*distance), offset(offsets, +0.5*distance + 10) // safety offset ); - for (ExPolygons::const_iterator ex = diff_expp.begin(); ex != diff_expp.end(); ++ex) { - if (fabs(ex->area()) >= gap_area_threshold) { - Polygons pp = *ex; - gaps.insert(gaps.end(), pp.begin(), pp.end()); - } - } + gaps.insert(gaps.end(), diff_pp.begin(), diff_pp.end()); } } @@ -396,18 +390,10 @@ PerimeterGenerator::_traverse_loops(const PerimeterGeneratorLoops &loops, } // append thin walls to the nearest-neighbor search (only for first iteration) - { + if (!thin_walls.empty()) { ExtrusionEntityCollection tw = this->_variable_width (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); thin_walls.clear(); } @@ -457,12 +443,16 @@ PerimeterGenerator::_variable_width(const ThickPolylines &polylines, ExtrusionRo ExtrusionPaths paths; ExtrusionPath path(role); ThickLines lines = p->thicklines(); + for (size_t i = 0; i < lines.size(); ++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) { const unsigned short segments = ceil(thickness_delta / tolerance); - const coordf_t line_len = line.length(); const coordf_t seg_len = line_len / segments; Points pp; std::vector width; @@ -509,30 +499,30 @@ PerimeterGenerator::_variable_width(const ThickPolylines &polylines, ExtrusionRo path.mm3_per_mm = flow.mm3_per_mm(); path.width = flow.width; 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 { - // we need to initialize a new line - paths.push_back(path); - path = ExtrusionPath(role); - --i; + thickness_delta = fabs(scale_(flow.width) - w); + if (thickness_delta <= tolerance) { + // the width difference between this line and the current flow width is + // 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); - // loop through generated paths - for (ExtrusionPaths::const_iterator p = paths.begin(); p != paths.end(); ++p) { - if (p->polyline.is_valid()) { - if (p->first_point().coincides_with(p->last_point())) { - // since medial_axis() now returns only Polyline objects, detect loops here - coll.append(ExtrusionLoop(*p)); - } else { - coll.append(*p); - } + // append paths to collection + if (!paths.empty()) { + if (paths.front().first_point().coincides_with(paths.back().last_point())) { + coll.append(ExtrusionLoop(paths)); + } else { + coll.append(paths); } } } diff --git a/xs/src/libslic3r/Polyline.cpp b/xs/src/libslic3r/Polyline.cpp index 5de9b6268..baa2bbf5d 100644 --- a/xs/src/libslic3r/Polyline.cpp +++ b/xs/src/libslic3r/Polyline.cpp @@ -4,6 +4,7 @@ #include "Line.hpp" #include "Polygon.hpp" #include +#include namespace Slic3r { @@ -235,4 +236,12 @@ ThickPolyline::thicklines() const return lines; } +void +ThickPolyline::reverse() +{ + Polyline::reverse(); + std::reverse(this->width.begin(), this->width.end()); + std::swap(this->endpoints.first, this->endpoints.second); +} + } diff --git a/xs/src/libslic3r/Polyline.hpp b/xs/src/libslic3r/Polyline.hpp index 1c50c50d7..4d6673f2c 100644 --- a/xs/src/libslic3r/Polyline.hpp +++ b/xs/src/libslic3r/Polyline.hpp @@ -36,8 +36,10 @@ class Polyline : public MultiPoint { class ThickPolyline : public Polyline { public: std::vector width; - std::vector endpoints; + std::pair endpoints; + ThickPolyline() : endpoints(std::make_pair(false, false)) {}; ThickLines thicklines() const; + void reverse(); }; }