-
Notifications
You must be signed in to change notification settings - Fork 1.3k
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
Conversation
- now keep the bit with 0 width to merge them instead of just delete them - merging algorithm re-coded (extensively) - change behavior of thick polyline to only 1 width value per point instead of 2 per pair of adjacent points. - do not post-process the voronoi diagram in polylines, now it's don in expolygon.medial_axis. - failsafes in perimeter_generator (too many splits, too small) - some new tests.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
just random remarks, I'll have to look at medial_axis a bit more to understant it...
Also int32_t
s look ugly and resulting code is probably slower than using size_t / int / usingned
xs/src/libslic3r/ExPolygon.cpp
Outdated
remove_point_too_near(ThickPolyline* to_reduce) { | ||
const int32_t smallest = SCALED_EPSILON * 2; | ||
uint32_t id = 1; | ||
while (id < to_reduce->points.size() - 2) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This looks like last two points won't be checked for smallest
, is that intentional?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
i won't check the first and the last. But it seems I forgot to check the last-1 point... Thank you.
xs/src/libslic3r/ExPolygon.cpp
Outdated
const int32_t smallest = SCALED_EPSILON * 2; | ||
uint32_t id = 1; | ||
while (id < to_reduce->points.size() - 2) { | ||
uint32_t newdist = min(to_reduce->points[id].distance_to(to_reduce->points[id - 1]) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
(subtle performance - to_reduce->points[id].distance_to(to_reduce->points[id - 1]
can be small only if id==1
)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
you're right
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There is another (theoretical) problem: If points are less than smallest
apart, it will remove all of them, one by one ...
xs/src/libslic3r/ExPolygon.cpp
Outdated
coordf_t new_width = to_modify->width[idx_other - 1] * (1 - percent_dist); | ||
new_width += to_modify->width[idx_other] * (percent_dist); | ||
Point new_point; | ||
new_point.x = (coord_t)((double)(to_modify->points[idx_other - 1].x) * (1 - percent_dist)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It may be better to calculate to_modify->points[idx_other - 1].x * (1 - percent_dist) + to_modify->points[idx_other].x * (percent_dist)
and then round to integer (coord_t
)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
note: I forgot to remove the unnecessary (coord_t)((double)( )).
I don't see a cast problem.
Maybe i can pre-compute some things to remove some unnecessary operations here.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
the code truncates to both parts to integer before adding the (+=). Adding doubles and then truncating will slightly improve accuracy. But that is minot detail.
Function to blend/interpolate points (in point class) may be good idea - it will improve code readability
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
coordf_t is a double, or i doesn't understand.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
coord_t
is integer type
xs/src/libslic3r/ExPolygon.cpp
Outdated
Point point_before = id_nearest == 0 ? contour.contour.points.back() : contour.contour.points[id_nearest - 1]; | ||
Point point_after = id_nearest == contour.contour.points.size()-1 ? contour.contour.points.front() : contour.contour.points[id_nearest + 1]; | ||
//compute angle | ||
angle = min(nearest.ccw_angle(point_before, point_after), nearest.ccw_angle(point_after, point_before)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
(this is equivalent to
angle = nearest.ccw_angle(point_before, point_after);
if (angle >= PI) angle = PI - angle; // smaller angle
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks. i think i was tired...
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The correct one is
angle = nearest.ccw_angle(point_before, point_after);
if (angle >= PI) angle = 2*PI - angle; // smaller angle
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, sorry ..
xs/src/libslic3r/ExPolygon.cpp
Outdated
angle = min(nearest.ccw_angle(point_before, point_after), nearest.ccw_angle(point_after, point_before)); | ||
//compute the diff from 90� | ||
angle = abs(angle - PI / 2); | ||
if (near.x != nearest.x && near.y != nearest.y && max(nearestDist, nearDist) + SCALED_EPSILON < nearest.distance_to(near)) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nearDist
is not updated in loop, it is still distance from contour.contour.points.front()
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
no need to test near.x != nearest.x && near.y != nearest.y
, nearest.distance_to(near)
will handle that
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
near.x != nearest.x && near.y != nearest.y
I used near != nearest in slic3r pe but the comparison operator isn't made here. You think nearest.distance_to(near) != 0 is better? I'm okay with that
i will look for nearDist tomorrow
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
distance_to use sqrt(distance_to_sq(point));, it may be more efficient to use the comparison.
I may just copy the operator== from slic3rPE
xs/src/libslic3r/ExPolygon.cpp
Outdated
polyline.width.erase(polyline.width.begin()); | ||
changes = true; | ||
} | ||
while (polyline.points.size() > 1 && polyline.width.back() < min_width && polyline.endpoints.second) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
(may be cleaner to reverse
polyline and reuse front code)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
you're right
xs/src/libslic3r/Polyline.cpp
Outdated
if (j == i) continue; | ||
ThickPolyline *other = &pp[j]; | ||
if (polyline->last_point().coincides_with(other->last_point())) { | ||
other->reverse(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
(it may be better to delay reverse
until other
is actually chosen for concatenation)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
i will look at it
xs/src/libslic3r/Polyline.cpp
Outdated
} | ||
if (id_candidate_last_point == id_candidate_first_point && nbCandidate_first_point == 1 && nbCandidate_last_point == 1) { | ||
// it's a trap! it's a loop! | ||
if (pp[id_candidate_first_point].points.size() > 2) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this won't close polyline
- is it different from case when 3 polylines are joined together. But I'm not sure which one is correct
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It test if a polyline loop on itself. There are a test with a square that trigger this, if i remember well.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes - but in his case first and last point of resulting polyline won't overlap. It is different when 3 polylines are joined in one step
Thank you for your review! I can replace some int32_t by coord_t when it's semantically useful. I didn't re-check all the types, there are maybe some improvement to do. Don't ever use int / unsigned if you want more than 2^16 values or less than 2^64, it's not cross-platform safe. They are the c++ correct way to define a type with a defined number of bytes. https://en.cppreference.com/w/cpp/types/integer For the performance impact: if i need the precision, i can't do something else. If there are no need for precision, maybe int is better. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
int_fast32_t
should be used then ...
int32_t
may be slower that int
on 64bit system ... (int16_t
is slower that int
on ARM)
An int32_t
is distracting - it makes me think 'why is 32bit important`
Personally I use int
/ signed
/ unsigned
as meaning 'reasonably large integer type without special requirements' .. only 8-bit CPU may be a bit problematic, but even then int
should be enough to index most object that fit in memory ...
xs/src/libslic3r/ExPolygon.cpp
Outdated
const int32_t smallest = SCALED_EPSILON * 2; | ||
uint32_t id = 1; | ||
while (id < to_reduce->points.size() - 2) { | ||
uint32_t newdist = min(to_reduce->points[id].distance_to(to_reduce->points[id - 1]) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There is another (theoretical) problem: If points are less than smallest
apart, it will remove all of them, one by one ...
xs/src/libslic3r/ExPolygon.cpp
Outdated
/// add points from pattern to to_modify at the same % of the length | ||
/// so not add if an other point is present at the correct position | ||
void | ||
add_point_same_percent(ThickPolyline* pattern, ThickPolyline* to_modify) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
(not exactly sure how merging works) - it may work better if point distance (not percentage) is matched (starting from branching point)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
need % because two polylines don't have the same length
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You can use length of shorter one to do pairwise merging and them handle rest of somewow. But that is probably not important, lines should be short anyway.
xs/src/libslic3r/ExPolygon.cpp
Outdated
coordf_t new_width = to_modify->width[idx_other - 1] * (1 - percent_dist); | ||
new_width += to_modify->width[idx_other] * (percent_dist); | ||
Point new_point; | ||
new_point.x = (coord_t)((double)(to_modify->points[idx_other - 1].x) * (1 - percent_dist)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
the code truncates to both parts to integer before adding the (+=). Adding doubles and then truncating will slightly improve accuracy. But that is minot detail.
Function to blend/interpolate points (in point class) may be good idea - it will improve code readability
xs/src/libslic3r/ExPolygon.cpp
Outdated
if (k == i | k == j) continue; | ||
ThickPolyline& main = pp[k]; | ||
if (polyline.first_point().coincides_with(main.last_point())) { | ||
main.reverse(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, I missed the reference ... on first look is looks like only main
is affected ...
xs/src/libslic3r/ExPolygon.cpp
Outdated
if (polyline.width[1] > min_width) { | ||
double percent_can_keep = (min_width - polyline.width[0]) / (polyline.width[1] - polyline.width[0]); | ||
if (polyline.points.front().distance_to(polyline.points[1]) * percent_can_keep > max_width / 2 | ||
&& polyline.points.front().distance_to(polyline.points[1])* (1 - percent_can_keep) > max_width / 2) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, but why test part that is removed ? (1 - percent_can_keep)
?
(I assume this code only cuts end , does not split)
xs/src/libslic3r/Polyline.cpp
Outdated
} | ||
if (id_candidate_last_point == id_candidate_first_point && nbCandidate_first_point == 1 && nbCandidate_last_point == 1) { | ||
// it's a trap! it's a loop! | ||
if (pp[id_candidate_first_point].points.size() > 2) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes - but in his case first and last point of resulting polyline won't overlap. It is different when 3 polylines are joined in one step
I've implemented most of your advices, changed most of int32 into size_t (you're right, that is the right thing) |
@supermerill : Can you post some screenshots of interesting Voronoi cases this PR tries to handle, please? |
@supermerill : One quick observation is that it may be useful to create voronoi over whole area including anchors and too thin areas and then prune/mark branches that lead outside of green area. This will remove most Y-branches (ot there will be middle branch - Ψ)... |
I tried that first. Problem is the anchor is too wide and this lead to absurdly complex branch. |
xs/src/libslic3r/ExPolygon.cpp
Outdated
|
||
double dot_poly_branch = 0; | ||
double dot_candidate_branch = 0; | ||
|
||
// find another polyline starting here | ||
for (size_t j = i + 1; j < pp.size(); ++j) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe it would be worth finding all polylines starting from (ends of) polyline
, assign scores to them and select good candidates ... It would partially remove k
loop and may be easier to understand the algorithm ...
@supermerill : Just a guess, but voronoi / medial axis is mathematically sound approach to this. Trying to reinvent wheel isn't usually good idea ... BTW: the branch complexity is inside current voronoi area? |
@supermerill : the tree in anchor may be better if anchor areas for voronoi are wider (1 or 2 extrusion widths in size) |
The corrections made since i post this PR deteriorate the results, and i didn't succeed to apply a good enough rubber band to it.
|
@supermerill : |
@ledvinap I haven't butted in yet mostly because I thought you were doing a fine review job ;) @supermerill I do note that a test is failing on the Perl build specifically around thin wall so far. I would prefer that tests be applied to the C++ testing apparatus instead of the Perl side, but that can be covered as part of porting the existing tests. |
Slic3r/xs/src/libslic3r/Geometry.cpp Line 882 in d5a2e1b
|
* corrections from review * more functions for a clearer code * now simplify the frontier with the anchor to avoid weird edge-cases. * new post-process: remove "curve edge" before merging * new post-process: cube corner: the small bit that go from the voronoi corner to the cube corner is now a "pulling string" that pull the voronoi corner a bit to make a nicer cube. * _variable_width : reduce the threshold for creating a new extrusion by half vs threshold to create segments (if not, it doesn't create enough)
It's explained on the initial post. I talked to you about that and you said "you want to see it by yourself before making an opinion". btw, here is the v3. I move all the code in a new medial_axis.cpp file, in a dozen of functions for clarity. |
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) { |
There was a problem hiding this comment.
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.
Tried to build after your refactor; compile failed because Slic3r::Polygon doesn't define remove_point_too_near() and you use it during PerimeterGenerator (line 133). |
I backed up to 6ba61fe so I could build. Looks like with that version, there's some undesired side effects. Left is with your build, right is current master. Config + model: thinwall-problems.zip |
Fwiw, I get the same failure as @lordofhyphens..
|
I'll look at why. Maybe a file i forgot to add to a commit. |
Still no good (applied on top of commit 21eb603)
(needs rebased, also) |
0143d53
to
6f61c00
Compare
❌ Build Slic3r 1.3.0-master-2285 failed (commit 26aeaec7b5 by @) |
0f0147f
to
bab76a9
Compare
❌ Build Slic3r 1.3.0-master-2286 failed (commit a9ae12dba4 by @) |
4c3946d
to
0cadc74
Compare
* concatenate_polylines_with_crossing: now connected the good end with the other good end * discretize_variable_width: now use TP.width->flow.spacing for gap-fill and TP.width->flow.width if possible for thin-walls. * bugfix thin wall (medial axis): use correct temp variables for main_fusion.
0cadc74
to
f06491c
Compare
❌ Build Slic3r 1.3.0-master-2289 failed (commit 47c0f543d8 by @) |
@supermerill just want to remind you that thin wall mode is negatively affecting gap fill... I am at commit 05a2f90 with #4743 and your thin walls PR applied on top. Consider a wall of variable thickness (due to tapering or curving), ranging from around 1 mm to around 1.5 mm. With thin walls enabled, and 2 perimeter loops requested, Slic3r sees that the wall is too thin for two perimeter loops, ignores that setting, and produces a perfect result, with one loop plus gap fill as needed: With thin walls DISabled, and still 2 perimeter loops requested, the result is one perimeter plus gap fill in the thinner areas, and two perimeter loops where the thickest gap fill should have been, with the inner loop overlapping itself, causing over-filling of the gap: With thin walls disabled, and now just 1 perimeter loop requested, the result is one perimeter loop plus gap fill in the thinner places, and empty in the places where the thickest gap fill should have been: In all three cases, gap fill is of course enabled. The model pictured above: Penguin Extruder MK6 - cold end fan shroud.stl.zip As an aside, some CI tests are failing, maybe you'll want to look into it -- it looks like just some failed unit tests, but I had no such failures on my build. |
I agree with each of three use-cases. What is the problem (the negative part)? |
The negative part is that a "thin wall" is not (or should not be) related to gap fill; turning thin wall mode off should not cause any change in the gap fill behavior of a wall that's already too thick for "thin wall" mode to affect to begin with, so all three cases should produce the same result, as in the first image. Especially since trying to cram a second perimeter loop into a space that's only big enough for a little over half of its volume just over-stuffs the wall, leading to poor print quality. |
It's not my fault, I kept the original design (or maybe i made a mistake?). I can change that but I won't unless a majority are for the change also you said "thin wall mode is negatively affecting gap fill", but maybe "not using thin wall mode is negatively affecting gap fill" was the right thing to understand? for the some CI tests failing, i have to devote some time to push my fixes |
Yeah perhaps my terminology is a little off. However,
Considering that both overfilling a wall and leaving it empty results in bad print quality, and gap fill being affected by the thin wall setting is clearly a bug anyway, I would advise fixing this. I doubt anyone will complain. 😃 @lordofhyphens @alranel @dwillmore @platsch ? |
I didn't run the test provided by @VanessaE myself. Why is there a difference between the 1. and 3. case? Does thin wall change the extrusion width in the 1. case or is it same in 1 and 3 and the gap fill behaves different? I generally agree with @VanessaE but would expect that in the 3. case either additional perimeters are generated in the non-filled region or, if that doesn't fit, it would be covered by infill or gap fill and currently don't understand why that isn't happening. The 2. case looks like a bug, but not in the thin wall logic? |
Hello adaptative-width.tthe first test is about how many perimeters & gap fill we need to fill a wall.
the 5 first column are what is written in the test. the last column is my calculus of the wall thickness from the width+spacing and the perimeter count + gapfill/thinwall. the fourth line, in my opinion, may need a small gapfill of 0.086. The 7 line over-extrude because it uses a "INSET_OVERLAP_TOLERANCE" (of 40%) to "avoid using medial axis". testfill.cppIt's not related to this pr, but i will ask it here for convenience. I can open a new issue if the answer isn't trivial. So how can this test pass? i tried it against my fork and it fails. |
❌ Build Slic3r 1.3.0-master-2305 failed (commit 6c0396a3c2 by @) |
❌ Build Slic3r 1.3.0-master-2306 failed (commit c5eb936f53 by @) |
0818ce6
to
1c45045
Compare
✅ Build Slic3r 1.3.0-master-2307 completed (commit 2b145d55ee by @) |
strange, github doesn't want to add more commit from my branch inside this pr... |
Force push again? |
corrected the last issue. Do i have something more to correct? |
Continued at #4790 |
Closing as it is continued elsewhere. |
also, a test will fail (the one that check if the half-circle has only angle of the same direction:
From my test, changing my port-processing algorithm to push it more to the inner side has many unwanted side-effects. It's intended that it's going a bit more to the outer rim because there are more space to fill, and i can't know for sure the circle continue and it's not a strait after that.
Tell me if i forgot some formatting.