mirror of
https://git.mirrors.martin98.com/https://github.com/slic3r/Slic3r.git
synced 2025-08-03 18:30:37 +08:00
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:
parent
5ceec2822e
commit
0f41fb46ba
@ -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());
|
||||
}
|
||||
|
||||
|
@ -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;
|
||||
|
@ -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);
|
||||
|
@ -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 ®ion : by_region) {
|
||||
m_config.apply(print.regions[®ion - &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;
|
||||
|
@ -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);
|
||||
|
@ -839,7 +839,7 @@ void
|
||||
MedialAxis::build(ThickPolylines* polylines)
|
||||
{
|
||||
construct_voronoi(this->lines.begin(), this->lines.end(), &this->vd);
|
||||
|
||||
|
||||
/*
|
||||
// DEBUG: dump all Voronoi edges
|
||||
{
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user