Thin wall improvement

* no more strange turn at end => fusion of voronoi end lines when they are near enough
 * anchoring inside the perimeters => extends the lin ends inside the perimeter to join it. A gui parameter can be created to adjust it if needed.
 * conservation of too small segments of thin walls when they are crossing ech other => modification of the fusion algorithm to select the most strait path
 * no more cutting of small walls path by the gcode writer(no_sort for each thin wall extrusion) to have nice long lines of extrusions

!! reuse some code from ironing commits (and improve them) !! (for the 4th thing), can't be cherry-picked easily
This commit is contained in:
supermerill 2018-07-04 19:34:49 +02:00
parent 5ceec2822e
commit 0f41fb46ba
7 changed files with 244 additions and 89 deletions

View File

@ -207,7 +207,7 @@ void 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);
@ -229,92 +229,243 @@ ExPolygon::medial_axis(double max_width, double min_width, ThickPolylines* polyl
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 thikness"
* 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) {
//TODO: witch if polyline.size > best_candidate->size
//doesn't matter rright now because a if in the selection process prevent this.
//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;
}
if (idx_point < best_candidate->points.size()) {
if (idx_point + 1 < best_candidate->points.size()) {
//create a new polyline
pp.emplace_back();
pp.back().endpoints.first = true;
pp.back().endpoints.second = best_candidate->endpoints.second;
for (int idx_point_new_line = idx_point; idx_point_new_line < best_candidate->points.size(); ++idx_point_new_line) {
pp.back().points.push_back(best_candidate->points[idx_point_new_line]);
pp.back().width.push_back(best_candidate->width[idx_point_new_line]);
}
} else {
//Add last point
polyline.points.push_back(best_candidate->points[idx_point]);
polyline.width.push_back(best_candidate->width[idx_point]);
//select if an end opccur
polyline.endpoints.second &= best_candidate->endpoints.second;
}
} else {
//select if an end opccur
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 */
Point new_front = polyline.points.front();
Point new_back = polyline.points.back();
if (polyline.endpoints.first && !this->has_boundary_point(new_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)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()
);
);
// 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)this->contour.intersection(line, &new_back);
(void)bounds.contour.intersection(line, &new_back);
}
polyline.points.front() = new_front;
polyline.points.back() = new_back;
/* 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;
}
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. */
if (removed) {
for (size_t i = 0; i < pp.size(); ++i) {
ThickPolyline& polyline = pp[i];
if (polyline.endpoints.first && polyline.endpoints.second) continue; // optimization
not connect polylines when more than two meet.
Optimisation of the old algorithm : now we select the most "strait 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
// 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
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;
}
}
polylines->insert(polylines->end(), pp.begin(), pp.end());
}
@ -323,7 +474,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());
}

View File

@ -53,7 +53,7 @@ public:
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;

View File

@ -118,7 +118,7 @@ void ExtrusionEntityCollection::chained_path_from(Point start_near, ExtrusionEnt
for (ExtrusionEntitiesPtr::const_iterator it = this->entities.begin(); it != this->entities.end(); ++it) {
if (role != erMixed) {
// The caller wants only paths with a specific extrusion role.
auto role2 = (*it)->role();
ExtrusionRole role2 = (*it)->role();
if (role != role2) {
// This extrusion entity does not match the role asked.
assert(role2 != erMixed);

View File

@ -1981,7 +1981,16 @@ std::string GCode::extrude_entity(const ExtrusionEntity &entity, std::string des
return this->extrude_multi_path(*multipath, description, speed);
else if (const ExtrusionLoop* loop = dynamic_cast<const ExtrusionLoop*>(&entity))
return this->extrude_loop(*loop, description, speed, lower_layer_edge_grid);
else {
else if (const ExtrusionEntityCollection* coll = dynamic_cast<const ExtrusionEntityCollection*>(&entity)){
std::string gcode;
ExtrusionEntityCollection chained;
if (coll->no_sort) chained = *coll;
else chained = coll->chained_path_from(m_last_pos, false);
for (ExtrusionEntity *next_entity : chained.entities) {
gcode += extrude_entity(*next_entity, description, speed, lower_layer_edge_grid);
}
return gcode;
} else {
CONFESS("Invalid argument supplied to extrude()");
return "";
}
@ -2020,30 +2029,11 @@ std::string GCode::extrude_infill(const Print &print, const std::vector<ObjectBy
for (const ObjectByExtruder::Island::Region &region : by_region) {
m_config.apply(print.regions[&region - &by_region.front()]->config);
ExtrusionEntityCollection chained = region.infills.chained_path_from(m_last_pos, false);
gcode += extrude_infill(print, chained);
gcode += extrude_entity(chained, "infill");
}
return gcode;
}
//recursive algorithm to explore the collection tree
std::string GCode::extrude_infill(const Print &print, const ExtrusionEntityCollection &collection)
{
std::string gcode;
ExtrusionEntityCollection chained;
if (collection.no_sort) chained = collection;
else chained = collection.chained_path_from(m_last_pos, false);
for (ExtrusionEntity *fill : chained.entities) {
auto *eec = dynamic_cast<ExtrusionEntityCollection*>(fill);
if (eec) {
gcode += extrude_infill(print, *eec);
} else {
gcode += this->extrude_entity(*fill, "infill");
}
}
return gcode;
}
std::string GCode::extrude_support(const ExtrusionEntityCollection &support_fills)
{
std::string gcode;

View File

@ -222,7 +222,6 @@ protected:
};
std::string extrude_perimeters(const Print &print, const std::vector<ObjectByExtruder::Island::Region> &by_region, std::unique_ptr<EdgeGrid::Grid> &lower_layer_edge_grid);
std::string extrude_infill(const Print &print, const std::vector<ObjectByExtruder::Island::Region> &by_region);
std::string extrude_infill(const Print &print, const ExtrusionEntityCollection &collection); // recursive extrude_infill
std::string extrude_support(const ExtrusionEntityCollection &support_fills);
std::string travel_to(const Point &point, ExtrusionRole role, std::string comment);

View File

@ -839,7 +839,7 @@ void
MedialAxis::build(ThickPolylines* polylines)
{
construct_voronoi(this->lines.begin(), this->lines.end(), &this->vd);
/*
// DEBUG: dump all Voronoi edges
{

View File

@ -84,16 +84,27 @@ void PerimeterGenerator::process()
// 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);
Polygons no_thin_zone = offset(offsets, (float)(ext_perimeter_width / 2));
ExPolygons expp = offset2_ex(
// medial axis requires non-overlapping geometry
diff_ex(to_polygons(last),
offset(offsets, ext_perimeter_width / 2),
no_thin_zone,
true),
- min_width / 2, min_width / 2);
// the maximum thickness of our thin wall area is equal to the minimum thickness of a single loop
for (ExPolygon &ex : expp)
ex.medial_axis(ext_perimeter_width + ext_perimeter_spacing2, min_width, &thin_walls);
(float)(-min_width / 2), (float)(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_perimeter_width / 2))), no_thin_zone, true);
for (ExPolygon &ex : expp) {
ExPolygons bounds = _clipper_ex(ClipperLib::ctUnion, to_polygons(ex), 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_perimeter_width + ext_perimeter_spacing2, min_width, &thin_walls);
continue;
}
}
}
}
} else {
//FIXME Is this offset correct if the line width of the inner perimeters differs
// from the line width of the infill?
@ -235,7 +246,7 @@ void PerimeterGenerator::process()
true);
ThickPolylines polylines;
for (const ExPolygon &ex : gaps_ex)
ex.medial_axis(max, min, &polylines);
ex.medial_axis(ex, max, min, &polylines);
if (!polylines.empty()) {
ExtrusionEntityCollection gap_fill = this->_variable_width(polylines,
erGapFill, this->solid_infill_flow);
@ -474,13 +485,17 @@ ExtrusionEntityCollection PerimeterGenerator::_variable_width(const ThickPolylin
paths.emplace_back(std::move(path));
// Append paths to collection.
if (!paths.empty()) {
if (paths.front().first_point().coincides_with(paths.back().last_point()))
if (paths.front().first_point().coincides_with(paths.back().last_point())) {
coll.append(ExtrusionLoop(paths));
else
coll.append(paths);
} else {
//not a loop : avoid to "sort" it.
ExtrusionEntityCollection unsortable_coll(paths);
unsortable_coll.no_sort = true;
coll.append(unsortable_coll);
}
}
}
return coll;
}