Merge branch 'lm_seam_240_no_stagger_improved'

This commit is contained in:
Lukas Matena 2022-01-25 15:40:50 +01:00
commit cd24594555
2 changed files with 155 additions and 118 deletions

View file

@ -23,28 +23,35 @@ static constexpr float ENFORCER_CENTER_PENALTY = -10.f;
// This function was introduced in 2016 to assign penalties to overhangs.
// LukasM thinks that it discriminated a bit too much, so especially external
// seams were than placed in funny places (non-overhangs were preferred too much).
// He implemented his own version (below) which applies fixed penalty for really big overlaps.
// static float extrudate_overlap_penalty(float nozzle_r, float weight_zero, float overlap_distance)
// {
// // The extrudate is not fully supported by the lower layer. Fit a polynomial penalty curve.
// // Solved by sympy package:
// /*
// from sympy import *
// (x,a,b,c,d,r,z)=symbols('x a b c d r z')
// p = a + b*x + c*x*x + d*x*x*x
// p2 = p.subs(solve([p.subs(x, -r), p.diff(x).subs(x, -r), p.diff(x,x).subs(x, -r), p.subs(x, 0)-z], [a, b, c, d]))
// from sympy.plotting import plot
// plot(p2.subs(r,0.2).subs(z,1.), (x, -1, 3), adaptive=False, nb_of_points=400)
// */
// if (overlap_distance < - nozzle_r) {
// // The extrudate is fully supported by the lower layer. This is the ideal case, therefore zero penalty.
// return 0.f;
// } else {
// float x = overlap_distance / nozzle_r;
// float x2 = x * x;
// float x3 = x2 * x;
// return weight_zero * (1.f + 3.f * x + 3.f * x2 + x3);
// }
// }
static float extrudate_overlap_penalty(float nozzle_r, float weight_zero, float overlap_distance)
{
// The extrudate is not fully supported by the lower layer. Fit a polynomial penalty curve.
// Solved by sympy package:
/*
from sympy import *
(x,a,b,c,d,r,z)=symbols('x a b c d r z')
p = a + b*x + c*x*x + d*x*x*x
p2 = p.subs(solve([p.subs(x, -r), p.diff(x).subs(x, -r), p.diff(x,x).subs(x, -r), p.subs(x, 0)-z], [a, b, c, d]))
from sympy.plotting import plot
plot(p2.subs(r,0.2).subs(z,1.), (x, -1, 3), adaptive=False, nb_of_points=400)
*/
if (overlap_distance < - nozzle_r) {
// The extrudate is fully supported by the lower layer. This is the ideal case, therefore zero penalty.
return 0.f;
} else {
float x = overlap_distance / nozzle_r;
float x2 = x * x;
float x3 = x2 * x;
return weight_zero * (1.f + 3.f * x + 3.f * x2 + x3);
}
return overlap_distance > nozzle_r ? weight_zero : 0.f;
}
@ -313,12 +320,12 @@ void SeamPlacer::plan_perimeters(const std::vector<const ExtrusionEntity*> perim
if (perimeters[i]->role() == erExternalPerimeter && perimeters[i]->is_loop()) {
last_pos = this->calculate_seam(
layer, seam_position, *dynamic_cast<const ExtrusionLoop*>(perimeters[i]), nozzle_dmr,
po, lower_layer_edge_grid, last_pos);
po, lower_layer_edge_grid, last_pos, false);
m_plan[i].external = true;
m_plan[i].seam_position = seam_position;
m_plan[i].layer = &layer;
m_plan[i].po = po;
}
m_plan[i].seam_position = seam_position;
m_plan[i].layer = &layer;
m_plan[i].po = po;
m_plan[i].pt = last_pos;
}
}
@ -327,7 +334,7 @@ void SeamPlacer::plan_perimeters(const std::vector<const ExtrusionEntity*> perim
void SeamPlacer::place_seam(ExtrusionLoop& loop, const Point& last_pos, bool external_first, double nozzle_diameter,
const EdgeGrid::Grid* lower_layer_edge_grid)
{
const double seam_offset = nozzle_diameter;
// const double seam_offset = nozzle_diameter;
Point seam = last_pos;
if (! m_plan.empty() && m_plan_idx < m_plan.size()) {
@ -339,75 +346,100 @@ void SeamPlacer::place_seam(ExtrusionLoop& loop, const Point& last_pos, bool ext
// far from each other.
if ((seam.cast<double>() - last_pos.cast<double>()).squaredNorm() > std::pow(scale_(5.*nozzle_diameter), 2.))
seam = this->calculate_seam(*m_plan[m_plan_idx].layer, m_plan[m_plan_idx].seam_position, loop, nozzle_diameter,
m_plan[m_plan_idx].po, lower_layer_edge_grid, last_pos);
}
else if (! external_first) {
// Internal perimeter printed before the external.
// First get list of external seams.
std::vector<size_t> ext_seams;
for (size_t i = 0; i < m_plan.size(); ++i) {
if (m_plan[i].external)
ext_seams.emplace_back(i);
}
m_plan[m_plan_idx].po, lower_layer_edge_grid, last_pos, false);
if (! ext_seams.empty()) {
// First find the line segment closest to an external seam:
int path_idx = 0;
int line_idx = 0;
size_t ext_seam_idx = size_t(-1);
double min_dist_sqr = std::numeric_limits<double>::max();
std::vector<Lines> lines_vect;
for (int i = 0; i < int(loop.paths.size()); ++i) {
lines_vect.emplace_back(loop.paths[i].polyline.lines());
const Lines& lines = lines_vect.back();
for (int j = 0; j < int(lines.size()); ++j) {
for (size_t k : ext_seams) {
double d_sqr = lines[j].distance_to_squared(m_plan[k].pt);
if (d_sqr < min_dist_sqr) {
path_idx = i;
line_idx = j;
ext_seam_idx = k;
min_dist_sqr = d_sqr;
if (m_plan[m_plan_idx].seam_position == spAligned)
m_seam_history.add_seam(m_plan[m_plan_idx].po, m_plan[m_plan_idx].pt, loop.polygon().bounding_box());
}
else {
if (!external_first) {
// Internal perimeter printed before the external.
// First get list of external seams.
std::vector<size_t> ext_seams;
size_t external_cnt = 0;
for (size_t i = 0; i < m_plan.size(); ++i) {
if (m_plan[i].external) {
ext_seams.emplace_back(i);
++external_cnt;
}
}
if (!ext_seams.empty()) {
// First find the line segment closest to an external seam:
//int path_idx = 0;
//int line_idx = 0;
size_t ext_seam_idx = size_t(-1);
double min_dist_sqr = std::numeric_limits<double>::max();
std::vector<Lines> lines_vect;
for (int i = 0; i < int(loop.paths.size()); ++i) {
lines_vect.emplace_back(loop.paths[i].polyline.lines());
const Lines& lines = lines_vect.back();
for (int j = 0; j < int(lines.size()); ++j) {
for (size_t k : ext_seams) {
double d_sqr = lines[j].distance_to_squared(m_plan[k].pt);
if (d_sqr < min_dist_sqr) {
//path_idx = i;
//line_idx = j;
ext_seam_idx = k;
min_dist_sqr = d_sqr;
}
}
}
}
}
// Only accept seam that is reasonably close.
double limit_dist_sqr = std::pow(double(scale_((ext_seam_idx - m_plan_idx) * nozzle_diameter * 2.)), 2.);
if (ext_seam_idx != size_t(-1) && min_dist_sqr < limit_dist_sqr) {
// Now find a projection of the external seam
const Lines& lines = lines_vect[path_idx];
Point closest = m_plan[ext_seam_idx].pt.projection_onto(lines[line_idx]);
double dist = (closest.cast<double>() - lines[line_idx].b.cast<double>()).norm();
// Only accept seam that is reasonably close.
if (ext_seam_idx != size_t(-1)) {
// How many nozzle diameters is considered "close"?
const double nozzle_d_limit = 2. * (1. + m_plan.size() / external_cnt);
const double limit_dist_sqr = double(scale_(scale_((unscale(m_plan[ext_seam_idx].pt) - unscale(m_plan[m_plan_idx].pt)).squaredNorm() * std::pow(nozzle_d_limit * nozzle_diameter, 2.))));
// And walk along the perimeter until we make enough space for
// seams of all perimeters beforethe external one.
double offset = (ext_seam_idx - m_plan_idx) * scale_(seam_offset);
double last_offset = offset;
offset -= dist;
const Point* a = &closest;
const Point* b = &lines[line_idx].b;
while (++line_idx < int(lines.size()) && offset > 0.) {
last_offset = offset;
offset -= lines[line_idx].length();
a = &lines[line_idx].a;
b = &lines[line_idx].b;
if (min_dist_sqr < limit_dist_sqr) {
// Now find a projection of the external seam
//const Lines& lines = lines_vect[path_idx];
//Point closest = m_plan[ext_seam_idx].pt.projection_onto(lines[line_idx]);
// This code does staggering of internal perimeters, turned off for now.
//
// double dist = (closest.cast<double>() - lines[line_idx].b.cast<double>()).norm();
//
// // And walk along the perimeter until we make enough space for
// // seams of all perimeters beforethe external one.
// double offset = (ext_seam_idx - m_plan_idx) * scale_(seam_offset);
// double last_offset = offset;
// offset -= dist;
// const Point* a = &closest;
// const Point* b = &lines[line_idx].b;
// while (++line_idx < int(lines.size()) && offset > 0.) {
// last_offset = offset;
// offset -= lines[line_idx].length();
// a = &lines[line_idx].a;
// b = &lines[line_idx].b;
// }
//
// // We have walked far enough, too far maybe. Interpolate on the
// // last segment to find the end precisely.
// offset = std::min(0., offset); // In case that offset is still positive (we may have "wrapped around")
// double ratio = last_offset / (last_offset - offset);
// seam = (a->cast<double>() + ((b->cast<double>() - a->cast<double>()) * ratio)).cast<coord_t>();
seam = m_plan[ext_seam_idx].pt;
}
}
// We have walked far enough, too far maybe. Interpolate on the
// last segment to find the end precisely.
offset = std::min(0., offset); // In case that offset is still positive (we may have "wrapped around")
double ratio = last_offset / (last_offset - offset);
seam = (a->cast<double>() + ((b->cast<double>() - a->cast<double>()) * ratio)).cast<coord_t>();
}
}
else {
// We should have a candidate ready from before. If not, use last_pos.
if (m_plan_idx > 0 && m_plan[m_plan_idx - 1].precalculated)
seam = m_plan[m_plan_idx - 1].pt;
}
// seam now contains a hot candidate for internal seam. Use it unless there is a sharp corner nearby.
// We will call the normal seam planning function, pretending that we are currently at the candidate point
// and set to spNearest. If the ideal seam it finds is close to current candidate, use it.
// This is to prevent having seams very close to corners, just because of external seam position.
seam = calculate_seam(*m_plan[m_plan_idx].layer, spNearest, loop, nozzle_diameter,
m_plan[m_plan_idx].po, lower_layer_edge_grid, seam, true);
}
else {
// We should have a candidate ready from before. If not, use last_pos.
if (m_plan_idx > 0 && m_plan[m_plan_idx - 1].precalculated)
seam = m_plan[m_plan_idx - 1].pt;
}
m_plan[m_plan_idx].pt = seam;
}
@ -417,41 +449,42 @@ void SeamPlacer::place_seam(ExtrusionLoop& loop, const Point& last_pos, bool ext
loop.split_at(seam, true);
if (external_first && m_plan_idx+1<m_plan.size() && ! m_plan[m_plan_idx+1].external) {
// Next perimeter should start near this one.
const double dist_sqr = std::pow(double(scale_(seam_offset)), 2.);
double running_sqr = 0.;
double running_sqr_last = 0.;
if (!loop.paths.empty() && loop.paths.back().polyline.points.size() > 1) {
const ExtrusionPath& last = loop.paths.back();
auto it = last.polyline.points.crbegin() + 1;
for (; it != last.polyline.points.crend(); ++it) {
running_sqr += (it->cast<double>() - (it - 1)->cast<double>()).squaredNorm();
if (running_sqr > dist_sqr)
break;
running_sqr_last = running_sqr;
}
if (running_sqr <= dist_sqr)
it = last.polyline.points.crend() - 1;
// Now interpolate.
double ratio = (std::sqrt(dist_sqr) - std::sqrt(running_sqr_last)) / (std::sqrt(running_sqr) - std::sqrt(running_sqr_last));
m_plan[m_plan_idx + 1].pt = ((it - 1)->cast<double>() + (it->cast<double>() - (it - 1)->cast<double>()) * std::min(ratio, 1.)).cast<coord_t>();
// This code does staggering of internal perimeters, turned off for now.
// Next perimeter should start near this one.
// const double dist_sqr = std::pow(double(scale_(seam_offset)), 2.);
// double running_sqr = 0.;
// double running_sqr_last = 0.;
// if (!loop.paths.empty() && loop.paths.back().polyline.points.size() > 1) {
// const ExtrusionPath& last = loop.paths.back();
// auto it = last.polyline.points.crbegin() + 1;
// for (; it != last.polyline.points.crend(); ++it) {
// running_sqr += (it->cast<double>() - (it - 1)->cast<double>()).squaredNorm();
// if (running_sqr > dist_sqr)
// break;
// running_sqr_last = running_sqr;
// }
// if (running_sqr <= dist_sqr)
// it = last.polyline.points.crend() - 1;
// // Now interpolate.
// double ratio = (std::sqrt(dist_sqr) - std::sqrt(running_sqr_last)) / (std::sqrt(running_sqr) - std::sqrt(running_sqr_last));
// m_plan[m_plan_idx + 1].pt = ((it - 1)->cast<double>() + (it->cast<double>() - (it - 1)->cast<double>()) * std::min(ratio, 1.)).cast<coord_t>();
// m_plan[m_plan_idx + 1].precalculated = true;
m_plan[m_plan_idx + 1].pt = m_plan[m_plan_idx].pt;
m_plan[m_plan_idx + 1].precalculated = true;
}
// }
}
++m_plan_idx;
}
// Returns a seam for an EXTERNAL perimeter.
// Returns "best" seam for a given perimeter.
Point SeamPlacer::calculate_seam(const Layer& layer, const SeamPosition seam_position,
const ExtrusionLoop& loop, coordf_t nozzle_dmr, const PrintObject* po,
const EdgeGrid::Grid* lower_layer_edge_grid, Point last_pos)
const EdgeGrid::Grid* lower_layer_edge_grid, Point last_pos, bool prefer_nearest)
{
assert(loop.role() == erExternalPerimeter);
Polygon polygon = loop.polygon();
bool was_clockwise = polygon.make_counter_clockwise();
BoundingBox polygon_bb = polygon.bounding_box();
const coord_t nozzle_r = coord_t(scale_(0.5 * nozzle_dmr) + 0.5);
size_t po_idx = std::find(m_po_list.begin(), m_po_list.end(), po) - m_po_list.begin();
@ -462,7 +495,7 @@ Point SeamPlacer::calculate_seam(const Layer& layer, const SeamPosition seam_pos
if (po == m_last_po && layer.print_z == m_last_print_z)
layer_po = m_last_layer_po;
else {
layer_po = po->get_layer_at_printz(layer.print_z);
layer_po = po ? po->get_layer_at_printz(layer.print_z) : nullptr;
m_last_po = po;
m_last_print_z = layer.print_z;
m_last_layer_po = layer_po;
@ -475,7 +508,9 @@ Point SeamPlacer::calculate_seam(const Layer& layer, const SeamPosition seam_pos
assert(layer_idx < po->layer_count());
if (this->is_custom_seam_on_layer(layer_idx, po_idx)) {
const bool custom_seam = loop.role() == erExternalPerimeter && this->is_custom_seam_on_layer(layer_idx, po_idx);
if (custom_seam) {
// Seam enf/blockers can begin and end in between the original vertices.
// Let add extra points in between and update the leghths.
polygon.densify(MINIMAL_POLYGON_SIDE);
@ -488,7 +523,7 @@ Point SeamPlacer::calculate_seam(const Layer& layer, const SeamPosition seam_pos
if (seam_position == spAligned) {
// Seam is aligned to the seam at the preceding layer.
if (po != nullptr) {
std::optional<Point> pos = m_seam_history.get_last_seam(m_po_list[po_idx], layer_idx, polygon_bb);
std::optional<Point> pos = m_seam_history.get_last_seam(m_po_list[po_idx], layer_idx, loop.polygon().bounding_box());
if (pos.has_value()) {
last_pos = *pos;
last_pos_weight = is_custom_enforcer_on_layer(layer_idx, po_idx) ? 0.f : 1.f;
@ -519,7 +554,7 @@ Point SeamPlacer::calculate_seam(const Layer& layer, const SeamPosition seam_pos
std::vector<float> penalties = polygon_angles_at_vertices(polygon, lengths, float(nozzle_r));
// No penalty for reflex points, slight penalty for convex points, high penalty for flat surfaces.
const float penaltyConvexVertex = 1.f;
const float penaltyFlatSurface = 5.f;
const float penaltyFlatSurface = 3.f;
const float penaltyOverhangHalf = 10.f;
// Penalty for visible seams.
for (size_t i = 0; i < polygon.points.size(); ++ i) {
@ -548,8 +583,12 @@ Point SeamPlacer::calculate_seam(const Layer& layer, const SeamPosition seam_pos
float dist_max = 0.1f * lengths.back(); // 5.f * nozzle_dmr
penalty -= last_pos_weight * bspline_kernel(dist_to_last_pos_proj / dist_max);
penalties[i] = std::max(0.f, penalty);
if (prefer_nearest) {
// This hack limits the search around the nearest position projection.
penalties[i] += dist_to_last_pos_proj > 6.f * nozzle_r ? 100.f : 0.f;
}
}
// Penalty for overhangs.
if (lower_layer_edge_grid) {
// Use the edge grid distance field structure over the lower layer to calculate overhangs.
@ -568,10 +607,11 @@ Point SeamPlacer::calculate_seam(const Layer& layer, const SeamPosition seam_pos
penalties[i] += extrudate_overlap_penalty(float(nozzle_r), penaltyOverhangHalf, float(dist));
}
}
// Custom seam. Huge (negative) constant penalty is applied inside
// blockers (enforcers) to rule out points that should not win.
this->apply_custom_seam(polygon, po_idx, penalties, lengths, layer_idx, seam_position);
if (custom_seam)
this->apply_custom_seam(polygon, po_idx, penalties, lengths, layer_idx, seam_position);
// Find a point with a minimum penalty.
size_t idx_min = std::min_element(penalties.begin(), penalties.end()) - penalties.begin();
@ -592,9 +632,6 @@ Point SeamPlacer::calculate_seam(const Layer& layer, const SeamPosition seam_pos
}
}
if (seam_position == spAligned)
m_seam_history.add_seam(po, polygon.points[idx_min], polygon_bb);
// Export the contour into a SVG file.
#if 0

View file

@ -65,7 +65,7 @@ private:
// When given an external perimeter (!), returns the seam.
Point calculate_seam(const Layer& layer, const SeamPosition seam_position,
const ExtrusionLoop& loop, coordf_t nozzle_dmr, const PrintObject* po,
const EdgeGrid::Grid* lower_layer_edge_grid, Point last_pos);
const EdgeGrid::Grid* lower_layer_edge_grid, Point last_pos, bool prefer_nearest);
struct CustomTrianglesPerLayer {
Polygons polys;