Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Thin wall improvements (v2) #4534

Closed
wants to merge 27 commits into from
Closed
Show file tree
Hide file tree
Changes from 15 commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
6ba61fe
Thin wall improvements
Aug 10, 2018
2841809
thin wall : medial axis:
Sep 25, 2018
e4484c4
forgot medial_axis.hpp/cpp files
Oct 1, 2018
418c770
Merge remote-tracking branch 'remotes/origin/master' into alexrj_thin…
Dec 3, 2018
2235c87
correct error with Polygon / ExPolygon
Dec 3, 2018
97c226a
Correct thin_wall bugs discovered with disc.stl from lordofhyphens
Dec 3, 2018
1c1451c
some modifs for a small bug (a hole that appeared between thin zone a…
Dec 4, 2018
155a68b
change the all 'medial axis segments of a semicircumference have the …
Dec 4, 2018
b6edcc4
rename variable "near" as appveyor seems to not like it.
Dec 4, 2018
d0f5bd7
one more test, reworked the thin semi-circle test.
Dec 5, 2018
32e9d9d
bugfix polylines & try to avoid many periemter splits.
Dec 5, 2018
90df3a5
debug test, relax min area for medial_axis
Dec 6, 2018
ca9f3f7
edge-case bugfix (a perimeter inside a thin_wall area)
Dec 6, 2018
6643d78
stop_at_min_width : do not extends the thin wall if it's over the min…
Dec 7, 2018
137c99c
bugfix, remove \t, #4640
Dec 10, 2018
4534c5e
Review corrections
Dec 11, 2018
767fc86
Medial axis: avoid duplication + bugfix
Dec 17, 2018
44e7ec8
bugfix ensure_not_overextrude
Dec 18, 2018
fff41c2
thin_walls_min_width & min size of thin wall is the nozzle diameter
Jan 4, 2019
69aea31
taper ends of thin walls lines
Jan 4, 2019
8555930
Merge remote-tracking branch 'remotes/origin/master' into alexrj_thin…
Jan 4, 2019
e34e75a
typo
Jan 4, 2019
fc46316
Thin_wall / medial axis:
Feb 7, 2019
f6ae12f
bugfix thin wall / gapfill width
Feb 8, 2019
f06491c
medial axis: debug
Feb 18, 2019
1687c5c
little bugfix for thinwall : concatenate_polylines_with_crossing
Apr 6, 2019
1c45045
Add sanity check for gapfill's min width.
Apr 7, 2019
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions src/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -199,6 +199,7 @@ add_library(libslic3r STATIC
${LIBDIR}/libslic3r/LayerHeightSpline.cpp
${LIBDIR}/libslic3r/Line.cpp
${LIBDIR}/libslic3r/Log.cpp
${LIBDIR}/libslic3r/MedialAxis.cpp
${LIBDIR}/libslic3r/Model.cpp
${LIBDIR}/libslic3r/MotionPlanner.cpp
${LIBDIR}/libslic3r/MultiPoint.cpp
Expand Down
40 changes: 37 additions & 3 deletions t/thin.t
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use Test::More tests => 23;
use Test::More tests => 29;
use strict;
use warnings;

Expand Down Expand Up @@ -102,10 +102,44 @@ if (0) {
is scalar(@$res), 1, 'medial axis of a semicircumference is a single line';

# check whether turns are all CCW or all CW
my @lines = @{$res->[0]->lines};
my @all_lines = @{$res->[0]->lines};
# remove lines that are near the end.
my @lines = grep($_->a->y >= 1578184 || $_->b->y >= 1578184, @all_lines);
my @angles = map { $lines[$_-1]->ccw($lines[$_]->b) } 1..$#lines;
ok !!(none { $_ < 0 } @angles) || (none { $_ > 0 } @angles),
'all medial axis segments of a semicircumference have the same orientation';
'all medial axis segments of a semicircumference have the same orientation (but the 2 end points)';
}

{
my $expolygon = Slic3r::ExPolygon->new(Slic3r::Polygon->new_scale(
[4.3, 4], [4.3, 0], [4,0], [4,4], [0,4], [0,4.5], [4,4.5], [4,10], [4.3,10], [4.3, 4.5],
[6, 4.5], [6,10], [6.2,10], [6.2,4.5], [10,4.5], [10,4], [6.2,4], [6.2,0], [6, 0], [6, 4],
));
$expolygon->contour->make_counter_clockwise();
my $res = $expolygon->medial_axis(scale 0.55, scale 0.25);
is scalar(@$res), 2, 'medial axis of a (bit too narrow) french cross is two lines';
ok unscale($res->[0]->length) >= (9.9) - epsilon, 'medial axis has reasonable length';
ok unscale($res->[1]->length) >= (9.9) - epsilon, 'medial axis has reasonable length';

my @lines1 = @{$res->[0]->lines};
my @angles1 = map { $lines1[$_-1]->ccw($lines1[$_]->b) } 1..$#lines1;
my @lines2 = @{$res->[1]->lines};
my @angles2 = map { $lines2[$_-1]->ccw($lines2[$_]->b) } 1..$#lines2;
my @angles = (@angles1, @angles2);
ok !!(none { $_ != 0 } @angles),
'medial axis of a (bit too narrow) french cross is two lines has only strait lines';
}

{
my $expolygon = Slic3r::ExPolygon->new(Slic3r::Polygon->new_scale(
[0.86526705,1.4509841], [0.57696039,1.8637021], [0.4502297,2.5569978], [0.45626199,3.2965596], [1.1218851,3.3049455], [0.96681072,2.8243202], [0.86328971,2.2056997], [0.85367905,1.7790778],
));
$expolygon->contour->make_counter_clockwise();
my $res = $expolygon->medial_axis(scale 1, scale 0.25);
is scalar(@$res), 1, 'medial axis of a (bit too narrow) french cross is two lines';
ok unscale($res->[0]->length) >= (1.4) - epsilon, 'medial axis has reasonable length';
# TODO: check if min width is < 0.3 and max width is > 0.6 (min($res->[0]->width.front, $res->[0]->width.back) # problem: can't have access to width

}

{
Expand Down
2 changes: 2 additions & 0 deletions xs/MANIFEST
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,8 @@ src/libslic3r/libslic3r.h
src/libslic3r/Line.cpp
src/libslic3r/Line.hpp
src/libslic3r/Log.hpp
src/libslic3r/MedialAxis.cpp
src/libslic3r/MedialAxis.hpp
src/libslic3r/Model.cpp
src/libslic3r/Model.hpp
src/libslic3r/MotionPlanner.cpp
Expand Down
258 changes: 25 additions & 233 deletions xs/src/libslic3r/ExPolygon.cpp
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
#include "BoundingBox.hpp"
#include "ExPolygon.hpp"
#include "MedialAxis.hpp"
#include "Geometry.hpp"
#include "Polygon.hpp"
#include "Line.hpp"
Expand Down Expand Up @@ -184,251 +185,42 @@ ExPolygon::simplify(double tolerance, ExPolygons* expolygons) const
expolygons->insert(expolygons->end(), ep.begin(), ep.end());
}

/// remove point that are at SCALED_EPSILON * 2 distance.
//simplier than simplify
void
ExPolygon::medial_axis(const ExPolygon &bounds, double max_width, double min_width, ThickPolylines* polylines) const
{
// init helper object
Slic3r::Geometry::MedialAxis ma(max_width, min_width, this);
ma.lines = this->lines();

// compute the Voronoi diagram and extract medial axis polylines
ThickPolylines pp;
ma.build(&pp);

/* // Commented out debug code
SVG svg("medial_axis.svg");
svg.draw(*this);
svg.draw(pp);
svg.Close();
*/

// Find the maximum width returned; we're going to use this for validating and
// filtering the output segments.
double max_w = 0;
for (ThickPolylines::const_iterator it = pp.begin(); it != pp.end(); ++it)
max_w = fmaxf(max_w, *std::max_element(it->width.begin(), it->width.end()));


// Aligned fusion: Fusion the bits at the end of lines by "increasing thickness"
// For that, we have to find other lines,
// and with a next point no more distant than the max width.
// Then, we can merge the bit from the first point to the second by following the mean.

bool changes = true;
while (changes) {
changes = false;
for (size_t i = 0; i < pp.size(); ++i) {
ThickPolyline& polyline = pp[i];

ThickPolyline* best_candidate = nullptr;
float best_dot = -1;
int best_idx = 0;

// 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())) {
polyline.reverse();
other.reverse();
}
else if (polyline.first_point().coincides_with(other.last_point())) {
other.reverse();
}
else if (polyline.first_point().coincides_with(other.first_point())) {
}
else if (polyline.last_point().coincides_with(other.first_point())) {
polyline.reverse();
} else {
continue;
}

//only consider the other if the next point is near us
if (polyline.points.size() < 2 && other.points.size() < 2) continue;
if (!polyline.endpoints.second || !other.endpoints.second) continue;
if (polyline.points.back().distance_to(other.points.back()) > max_width) continue;
if (polyline.points.size() != other.points.size()) continue;


Pointf v_poly(polyline.lines().front().vector().x, polyline.lines().front().vector().y);
v_poly.scale(1 / std::sqrt(v_poly.x*v_poly.x + v_poly.y*v_poly.y));
Pointf v_other(other.lines().front().vector().x, other.lines().front().vector().y);
v_other.scale(1 / std::sqrt(v_other.x*v_other.x + v_other.y*v_other.y));
float other_dot = v_poly.x*v_other.x + v_poly.y*v_other.y;
if (other_dot > best_dot) {
best_candidate = &other;
best_idx = j;
best_dot = other_dot;
}
}
if (best_candidate != nullptr) {

//assert polyline.size == best_candidate->size (see selection loop, an 'if' takes care of that)

//iterate the points
// as voronoi should create symetric thing, we can iterate synchonously
unsigned int idx_point = 1;
while (idx_point < polyline.points.size() && polyline.points[idx_point].distance_to(best_candidate->points[idx_point]) < max_width) {
//fusion
polyline.points[idx_point].x += best_candidate->points[idx_point].x;
polyline.points[idx_point].x /= 2;
polyline.points[idx_point].y += best_candidate->points[idx_point].y;
polyline.points[idx_point].y /= 2;
polyline.width[idx_point] += best_candidate->width[idx_point];
++idx_point;
}
//select if an end occur
polyline.endpoints.second &= best_candidate->endpoints.second;

//remove points that are the same or too close each other, ie simplify
for (unsigned int idx_point = 1; idx_point < polyline.points.size(); ++idx_point) {
//distance of 1 is on the sclaed coordinates, so it correspond to SCALE_FACTOR, so it's very small
if (polyline.points[idx_point - 1].distance_to(polyline.points[idx_point]) < 1) {
if (idx_point < polyline.points.size() -1) {
polyline.points.erase(polyline.points.begin() + idx_point);
} else {
polyline.points.erase(polyline.points.begin() + idx_point -1);
}
--idx_point;
}
}
//remove points that are outside of the geometry
for (unsigned int idx_point = 0; idx_point < polyline.points.size(); ++idx_point) {
//distance of 1 is on the sclaed coordinates, so it correspond to SCALE_FACTOR, so it's very small
if (!bounds.contains_b(polyline.points[idx_point])) {
polyline.points.erase(polyline.points.begin() + idx_point);
--idx_point;
}
}
if (polyline.points.size() < 2) {
//remove self
pp.erase(pp.begin() + i);
--i;
--best_idx;
}

pp.erase(pp.begin() + best_idx);
changes = true;
}
ExPolygon::remove_point_too_near(const coord_t tolerance) {
size_t id = 1;
while (id < this->contour.points.size() - 1) {
double newdist = min(this->contour.points[id].distance_to(this->contour.points[id - 1])
, this->contour.points[id].distance_to(this->contour.points[id + 1]));
if (newdist < (double)tolerance) {
this->contour.points.erase(this->contour.points.begin() + id);
newdist = this->contour.points[id].distance_to(this->contour.points[id - 1]);
}
}

// Loop through all returned polylines in order to extend their endpoints to the
// expolygon boundaries
bool removed = false;
for (size_t i = 0; i < pp.size(); ++i) {
ThickPolyline& polyline = pp[i];

// extend initial and final segments of each polyline if they're actual endpoints
// Assign new endpoints to temporary variables because in case of a single-line
// polyline. After the start point is extended it will be caught by the intersection()
// call, so keep the inner point until the second intersection() is performed.
Point new_front = polyline.points.front();
Point new_back = polyline.points.back();
if (polyline.endpoints.first && !bounds.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
if (polyline.points.size() == 2) line.b = line.midpoint();

line.extend_start(max_width);
(void)bounds.contour.intersection(line, &new_front);
}
if (polyline.endpoints.second && !bounds.has_boundary_point(new_back)) {
Line line(
*(polyline.points.end() - 2),
polyline.points.back()
);

// prevent the line from touching on the other side, otherwise intersection() might return that solution
if (polyline.points.size() == 2) line.a = line.midpoint();
line.extend_end(max_width);

(void)bounds.contour.intersection(line, &new_back);
//go to next one
//if you removed a point, it check if the next one isn't too near from the previous one.
// if not, it byepass it.
if (newdist > (double)tolerance) {
++id;
}
polyline.points.front() = new_front;
polyline.points.back() = new_back;
}

// 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.
// Optimisation of the old algorithm : Select the most "straight line" choice
// when we merge with an other line at a point with more than two meet.

for (size_t i = 0; i < pp.size(); ++i) {
ThickPolyline& polyline = pp[i];
if (polyline.endpoints.first && polyline.endpoints.second) continue; // optimization

ThickPolyline* best_candidate = nullptr;
float best_dot = -1;
int best_idx = 0;

// 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;
}

Pointf v_poly(polyline.lines().back().vector().x, polyline.lines().back().vector().y);
v_poly.scale(1 / std::sqrt(v_poly.x*v_poly.x + v_poly.y*v_poly.y));
Pointf v_other(other.lines().front().vector().x, other.lines().front().vector().y);
v_other.scale(1 / std::sqrt(v_other.x*v_other.x + v_other.y*v_other.y));
float other_dot = v_poly.x*v_other.x + v_poly.y*v_other.y;
if (other_dot > best_dot) {
best_candidate = &other;
best_idx = j;
best_dot = other_dot;
}
}
if (best_candidate != nullptr) {

polyline.points.insert(polyline.points.end(), best_candidate->points.begin() + 1, best_candidate->points.end());
polyline.width.insert(polyline.width.end(), best_candidate->width.begin(), best_candidate->width.end());
polyline.endpoints.second = best_candidate->endpoints.second;
assert(polyline.width.size() == polyline.points.size()*2 - 2);

pp.erase(pp.begin() + best_idx);
}
}

for (size_t i = 0; i < pp.size(); ++i) {
ThickPolyline& polyline = pp[i];

// 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.endpoints.first || polyline.endpoints.second)
&& polyline.length() < max_w * 2) {
pp.erase(pp.begin() + i);
--i;
removed = true;
continue;
}

if (this->contour.points.front().distance_to(this->contour.points.back()) < tolerance) {
this->contour.points.erase(this->contour.points.end() -1);
}
}


polylines->insert(polylines->end(), pp.begin(), pp.end());
void
ExPolygon::medial_axis(const ExPolygon &bounds, double max_width, double min_width, ThickPolylines* polylines, double height, bool stop_at_min_width) const {
Slic3r::MedialAxis ma(*this, bounds, max_width, min_width, height);
ma.stop_at_min_width = stop_at_min_width;
ma.build(polylines);
}

void
ExPolygon::medial_axis(double max_width, double min_width, Polylines* polylines) const
{
ThickPolylines tp;
this->medial_axis(*this, max_width, min_width, &tp);
this->medial_axis(*this, max_width, min_width, &tp, max_width/2.0);
polylines->insert(polylines->end(), tp.begin(), tp.end());
}

Expand Down
3 changes: 2 additions & 1 deletion xs/src/libslic3r/ExPolygon.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,8 @@ class ExPolygon
Polygons simplify_p(double tolerance) const;
ExPolygons simplify(double tolerance) const;
void simplify(double tolerance, ExPolygons* expolygons) const;
void medial_axis(const ExPolygon &bounds, double max_width, double min_width, ThickPolylines* polylines) const;
void remove_point_too_near(const coord_t tolerance);
void medial_axis(const ExPolygon &bounds, double max_width, double min_width, ThickPolylines* polylines, double height, bool stop_at_min_width = true) const;
void medial_axis(double max_width, double min_width, Polylines* polylines) const;
void get_trapezoids(Polygons* polygons) const;
void get_trapezoids(Polygons* polygons, double angle) const;
Expand Down
Loading