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 1 commit
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
24 changes: 23 additions & 1 deletion 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 => 28;
use strict;
use warnings;

Expand Down Expand Up @@ -108,6 +108,28 @@ if (0) {
'all medial axis segments of a semicircumference have the same orientation';
}

{
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],
));
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 $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],
));
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

}

{
my $expolygon = Slic3r::ExPolygon->new(Slic3r::Polygon->new_scale(
[100, 100],
Expand Down
526 changes: 467 additions & 59 deletions xs/src/libslic3r/ExPolygon.cpp

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion xs/src/libslic3r/ExPolygon.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ 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 medial_axis(const ExPolygon &bounds, double max_width, double min_width, ThickPolylines* polylines, double height) 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
65 changes: 35 additions & 30 deletions xs/src/libslic3r/Geometry.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -724,7 +724,7 @@ MedialAxis::build(ThickPolylines* polylines)
polyline.endpoints.first = rpolyline.endpoints.second;
}

assert(polyline.width.size() == polyline.points.size()*2 - 2);
assert(polyline.width.size() == polyline.points.size());

// prevent loop endpoints from being extended
if (polyline.first_point().coincides_with(polyline.last_point())) {
Expand Down Expand Up @@ -770,8 +770,8 @@ MedialAxis::process_edge_neighbors(const VD::edge_type* edge, ThickPolyline* pol

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;
Expand Down Expand Up @@ -851,34 +851,39 @@ MedialAxis::validate_edge(const VD::edge_type* edge)
? line.b.distance_to(segment_l)*2
: line.b.distance_to(this->retrieve_endpoint(cell_l))*2;

if (cell_l->contains_segment() && cell_r->contains_segment()) {
// calculate the relative angle between the two boundary segments
double angle = fabs(segment_r.orientation() - segment_l.orientation());
if (angle > PI) angle = 2*PI - angle;
assert(angle >= 0 && angle <= PI);

// 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 (PI - angle > PI/8) {
// angle is not narrow enough

// only apply this filter to segments that are not too short otherwise their
// angle could possibly be not meaningful
if (w0 < SCALED_EPSILON || w1 < SCALED_EPSILON || line.length() >= this->min_width)
return false;
}
} else {
if (w0 < SCALED_EPSILON || w1 < SCALED_EPSILON)
return false;
}

if (w0 < this->min_width && w1 < this->min_width)
return false;

//don't remove the line that goes to the intersection of the contour
// we use them to create nicer thin wall lines
//if (cell_l->contains_segment() && cell_r->contains_segment()) {
// // calculate the relative angle between the two boundary segments
// double angle = fabs(segment_r.orientation() - segment_l.orientation());
// if (angle > PI) angle = 2*PI - angle;
// assert(angle >= 0 && angle <= PI);
//
// // 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 (PI - angle > PI/8) {
// // angle is not narrow enough
//
// // only apply this filter to segments that are not too short otherwise their
// // angle could possibly be not meaningful
// if (w0 < SCALED_EPSILON || w1 < SCALED_EPSILON || line.length() >= this->min_width)
// return false;
// }
//} else {
// if (w0 < SCALED_EPSILON || w1 < SCALED_EPSILON)
// return false;
//}

// don't do that before we try to fusion them
//if (w0 < this->min_width && w1 < this->min_width)
// return false;
//

//shouldn't occur if perimeter_generator is well made
if (w0 > this->max_width && w1 > this->max_width)
return false;

Expand Down
24 changes: 24 additions & 0 deletions xs/src/libslic3r/MultiPoint.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -158,6 +158,30 @@ MultiPoint::intersection(const Line& line, Point* intersection) const
return false;
}

bool
MultiPoint::first_intersection(const Line& line, Point* intersection) const
ledvinap marked this conversation as resolved.
Show resolved Hide resolved
{
bool found = false;
double dmin = 0.;
for (const Line &l : this->lines()) {
Point ip;
if (l.intersection(line, &ip)) {
if (! found) {
found = true;
dmin = ip.distance_to(line.a);
*intersection = ip;
} else {
double d = ip.distance_to(line.a);
if (d < dmin) {
dmin = d;
*intersection = ip;
}
}
}
}
return found;
}

std::string
MultiPoint::dump_perl() const
{
Expand Down
1 change: 1 addition & 0 deletions xs/src/libslic3r/MultiPoint.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ class MultiPoint
void append(const Points &points);
void append(const Points::const_iterator &begin, const Points::const_iterator &end);
bool intersection(const Line& line, Point* intersection) const;
bool first_intersection(const Line& line, Point* intersection) const;
std::string dump_perl() const;

static Points _douglas_peucker(const Points &points, const double tolerance);
Expand Down
87 changes: 56 additions & 31 deletions xs/src/libslic3r/PerimeterGenerator.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -85,44 +85,65 @@ PerimeterGenerator::process()
for (int i = 0; i <= loop_number+1; ++i) { // outer loop is 0
Polygons offsets;
if (i == 0) {
// the minimum thickness of a single loop is:
// ext_width/2 + ext_spacing/2 + spacing/2 + width/2
// compute next onion, without taking care of thin_walls : destroy too thin areas.
if (!this->config->thin_walls)
offsets = offset(last, -(float)(ext_pwidth / 2));
ledvinap marked this conversation as resolved.
Show resolved Hide resolved


// look for thin walls
if (this->config->thin_walls) {
// the minimum thickness of a single loop is:
// ext_width/2 + ext_spacing/2 + spacing/2 + width/2
offsets = offset2(
last,
-(ext_pwidth/2 + ext_min_spacing/2 - 1),
+(ext_min_spacing/2 - 1)
);
} else {
offsets = offset(last, -ext_pwidth/2);
}

// look for thin walls
if (this->config->thin_walls) {
Polygons no_thin_zone = offset(offsets, +ext_pwidth/2);
Polygons diffpp = diff(
last,
no_thin_zone,
true // medial axis requires non-overlapping geometry
);
+(ext_min_spacing/2 - 1));

// detect edge case where a curve can be split in multiple small chunks.
Polygons no_thin_onion = offset(last, -(float)(ext_pwidth / 2));
if (no_thin_onion.size()>0 && offsets.size() > 3 * no_thin_onion.size()) {
//use a sightly smaller spacing to try to drastically improve the split
Polygons next_onion_secondTry = offset2(
last,
-(float)(ext_pwidth / 2 + ext_min_spacing / 2.5 - 1),
+(float)(ext_min_spacing / 2.5 - 1));
if (abs(((int32_t)offsets.size()) - ((int32_t)no_thin_onion.size())) >
2*abs(((int32_t)next_onion_secondTry.size()) - ((int32_t)no_thin_onion.size()))) {
offsets = next_onion_secondTry;
}
}

// the following offset2 ensures almost nothing in @thin_walls is narrower than $min_width
// (actually, something larger than that still may exist due to mitering or other causes)
coord_t min_width = scale_(this->ext_perimeter_flow.nozzle_diameter / 3);
ExPolygons expp = offset2_ex(diffpp, -min_width/2, +min_width/2);

// compute a bit of overlap to anchor thin walls inside the print.
ExPolygons anchor = intersection_ex(to_polygons(offset_ex(expp, (float)(ext_pwidth / 2))), no_thin_zone, true);

// the maximum thickness of our thin wall area is equal to the minimum thickness of a single loop
for (ExPolygons::const_iterator ex = expp.begin(); ex != expp.end(); ++ex) {
ExPolygons bounds = _clipper_ex(ClipperLib::ctUnion, (Polygons)*ex, to_polygons(anchor), true);
//search our bound
Polygons no_thin_zone = offset(offsets, +ext_pwidth/2);
// medial axis requires non-overlapping geometry
Polygons thin_zones = diff(last, no_thin_zone, true);
//don't use offset2_ex, because we don't want to merge the zones that have been separated.
Polygons expp = offset(thin_zones, (float)(-min_width / 2));
//we push the bits removed and put them into what we will use as our anchor
if (expp.size() > 0) {
no_thin_zone = diff(last, offset(expp, (float)(min_width / 2)), true);
}
// compute a bit of overlap to anchor thin walls inside the print.
for (Polygon &ex : expp) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should this be an ExPolygon? I'm getting a build error on my system when trying to do a clean build of your branch.

//growing back the polygon
//a vary little bit of overlap can be created here with other thin polygon, but it's more useful than worisome.
ExPolygons ex_bigger = offset_ex(ex, (float)(min_width / 2));
if (ex_bigger.size() != 1) continue; // impossible error, growing a single polygon can't create multiple or 0.
ExPolygons anchor = intersection_ex(offset(ex, (float)(ext_pwidth / 2),
CLIPPER_OFFSET_SCALE, jtSquare, 3), no_thin_zone, true);
ExPolygons bounds = _clipper_ex(ClipperLib::ctUnion, to_polygons(ex_bigger), to_polygons(anchor), true);
for (ExPolygon &bound : bounds) {
if (!intersection_ex(*ex, bound).empty()) {
// the maximum thickness of our thin wall area is equal to the minimum thickness of a single loop
ex->medial_axis(bound, ext_pwidth + ext_pspacing2, min_width, &thin_walls);
continue;
if (!intersection_ex(ex_bigger[0], bound).empty()) {
//be sure it's not too small to extrude reliably
if (ex_bigger[0].area() > min_width*(ext_pwidth + ext_pspacing2)) {
// the maximum thickness of our thin wall area is equal to the minimum thickness of a single loop
ex_bigger[0].medial_axis(bound, ext_pwidth + ext_pspacing2, min_width,
&thin_walls, this->layer_height);
}
break;
}
}
}
Expand Down Expand Up @@ -292,9 +313,13 @@ PerimeterGenerator::process()
);

ThickPolylines polylines;
for (ExPolygons::const_iterator ex = gaps_ex.begin(); ex != gaps_ex.end(); ++ex)
ex->medial_axis(*ex, max, min, &polylines);

for (const ExPolygon &ex : gaps_ex) {
//remove too small gaps that are too hard to fill.
//ie one that are smaller than an extrusion with width of min and a length of max.
if (ex.area() > min*max) {
ex.medial_axis(ex, max, min, &polylines, this->layer_height);
}
}
if (!polylines.empty()) {
ExtrusionEntityCollection gap_fill = this->_variable_width(polylines,
erGapFill, this->solid_infill_flow);
Expand Down
Loading