mirror of
https://git.mirrors.martin98.com/https://github.com/slic3r/Slic3r.git
synced 2025-08-05 12:20:40 +08:00
thinwall improvements (#4472)
* First step of thin wall improvements for alexrj branch. * bugfix * fix * remove tabs, remove dead code * Convert comments to C++ instead of C-style. Converting comments to C++ style, fixed typos in comments. C-style comments are normally reserved for commenting out code blocks, not descriptions.
This commit is contained in:
parent
55a4e95b94
commit
cc42bb411c
@ -185,7 +185,7 @@ ExPolygon::simplify(double tolerance, ExPolygons* expolygons) const
|
||||
}
|
||||
|
||||
void
|
||||
ExPolygon::medial_axis(double max_width, double min_width, ThickPolylines* polylines) const
|
||||
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);
|
||||
@ -195,41 +195,145 @@ ExPolygon::medial_axis(double max_width, double min_width, ThickPolylines* polyl
|
||||
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. */
|
||||
// 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()));
|
||||
|
||||
/* Loop through all returned polylines in order to extend their endpoints to the
|
||||
expolygon boundaries */
|
||||
|
||||
// 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 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
|
||||
/* We assign new endpoints to temporary variables because in case of a single-line
|
||||
polyline, after we extend the start point it will be caught by the intersection()
|
||||
call, so we keep the inner point until we perform the second intersection() as well */
|
||||
// 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 && !this->has_boundary_point(new_front)) {
|
||||
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)this->contour.intersection(line, &new_front);
|
||||
(void)bounds.contour.intersection(line, &new_front);
|
||||
}
|
||||
if (polyline.endpoints.second && !this->has_boundary_point(new_back)) {
|
||||
if (polyline.endpoints.second && !bounds.has_boundary_point(new_back)) {
|
||||
Line line(
|
||||
*(polyline.points.end() - 2),
|
||||
polyline.points.back()
|
||||
@ -239,60 +343,83 @@ ExPolygon::medial_axis(double max_width, double min_width, ThickPolylines* polyl
|
||||
if (polyline.points.size() == 2) line.a = line.midpoint();
|
||||
line.extend_end(max_width);
|
||||
|
||||
(void)this->contour.intersection(line, &new_back);
|
||||
(void)bounds.contour.intersection(line, &new_back);
|
||||
}
|
||||
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.
|
||||
|
||||
/* 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) */
|
||||
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) {
|
||||
&& polyline.length() < max_w * 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());
|
||||
}
|
||||
@ -301,7 +428,7 @@ void
|
||||
ExPolygon::medial_axis(double max_width, double min_width, Polylines* polylines) const
|
||||
{
|
||||
ThickPolylines tp;
|
||||
this->medial_axis(max_width, min_width, &tp);
|
||||
this->medial_axis(*this, max_width, min_width, &tp);
|
||||
polylines->insert(polylines->end(), tp.begin(), tp.end());
|
||||
}
|
||||
|
||||
|
@ -39,7 +39,7 @@ class ExPolygon
|
||||
Polygons simplify_p(double tolerance) const;
|
||||
ExPolygons simplify(double tolerance) const;
|
||||
void simplify(double tolerance, ExPolygons* expolygons) const;
|
||||
void medial_axis(double max_width, double min_width, ThickPolylines* polylines) const;
|
||||
void medial_axis(const ExPolygon &bounds, double max_width, double min_width, ThickPolylines* polylines) 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;
|
||||
|
@ -99,9 +99,10 @@ PerimeterGenerator::process()
|
||||
|
||||
// look for thin walls
|
||||
if (this->config->thin_walls) {
|
||||
Polygons no_thin_zone = offset(offsets, +ext_pwidth/2);
|
||||
Polygons diffpp = diff(
|
||||
last,
|
||||
offset(offsets, +ext_pwidth/2),
|
||||
no_thin_zone,
|
||||
true // medial axis requires non-overlapping geometry
|
||||
);
|
||||
|
||||
@ -109,11 +110,22 @@ PerimeterGenerator::process()
|
||||
// (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)
|
||||
ex->medial_axis(ext_pwidth + ext_pspacing2, min_width, &thin_walls);
|
||||
|
||||
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
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
#ifdef DEBUG
|
||||
printf(" %zu thin walls detected\n", thin_walls.size());
|
||||
#endif
|
||||
@ -281,7 +293,7 @@ PerimeterGenerator::process()
|
||||
|
||||
ThickPolylines polylines;
|
||||
for (ExPolygons::const_iterator ex = gaps_ex.begin(); ex != gaps_ex.end(); ++ex)
|
||||
ex->medial_axis(max, min, &polylines);
|
||||
ex->medial_axis(*ex, max, min, &polylines);
|
||||
|
||||
if (!polylines.empty()) {
|
||||
ExtrusionEntityCollection gap_fill = this->_variable_width(polylines,
|
||||
|
Loading…
x
Reference in New Issue
Block a user