split occlusion and enforcers/blockers into separate functions

added weights to polynomial fitting
This commit is contained in:
PavelMikus 2022-03-04 09:15:47 +01:00
parent f837759928
commit 962282c9ef
2 changed files with 119 additions and 56 deletions

View File

@ -34,26 +34,33 @@ namespace SeamPlacerImpl {
//https://towardsdatascience.com/least-square-polynomial-fitting-using-c-eigen-package-c0673728bd01 //https://towardsdatascience.com/least-square-polynomial-fitting-using-c-eigen-package-c0673728bd01
// interpolates points in z (treats z coordinates as time) and returns coefficients for axis x and y // interpolates points in z (treats z coordinates as time) and returns coefficients for axis x and y
std::vector<Vec2f> polyfit(const std::vector<Vec3f> &points, size_t order) { std::vector<Vec2f> polyfit(const std::vector<Vec3f> &points, const std::vector<float> &weights, size_t order) {
// check to make sure inputs are correct
assert(points.size() >= order + 1);
assert(points.size() == weights.size());
std::vector<float> squared_weights(weights.size());
for (size_t index = 0; index < weights.size(); ++index) {
squared_weights[index] = sqrt(weights[index]);
}
Eigen::VectorXf V0(points.size()); Eigen::VectorXf V0(points.size());
Eigen::VectorXf V1(points.size()); Eigen::VectorXf V1(points.size());
Eigen::VectorXf V2(points.size()); Eigen::VectorXf V2(points.size());
for (size_t index = 0; index < points.size(); index++) { for (size_t index = 0; index < points.size(); index++) {
V0(index) = points[index].x(); V0(index) = points[index].x() * squared_weights[index];
V1(index) = points[index].y(); V1(index) = points[index].y() * squared_weights[index];
V2(index) = points[index].z(); V2(index) = points[index].z();
} }
// Create Matrix Placeholder of size n x k, n= number of datapoints, k = order of polynomial, for exame k = 3 for cubic polynomial // Create Matrix Placeholder of size n x k, n= number of datapoints, k = order of polynomial, for exame k = 3 for cubic polynomial
Eigen::MatrixXf T(points.size(), order + 1); Eigen::MatrixXf T(points.size(), order + 1);
// check to make sure inputs are correct
assert(points.size() >= order + 1);
// Populate the matrix // Populate the matrix
for (size_t i = 0; i < points.size(); ++i) for (size_t i = 0; i < points.size(); ++i)
{ {
for (size_t j = 0; j < order + 1; ++j) for (size_t j = 0; j < order + 1; ++j)
{ {
T(i, j) = pow(V2(i), j); T(i, j) = pow(V2(i), j) * squared_weights[i];
} }
} }
@ -588,19 +595,19 @@ void pick_seam_point(std::vector<SeamCandidate> &perimeter_points, size_t start_
// Computes all global model info - transforms object, performs raycasting, // Computes all global model info - transforms object, performs raycasting,
// stores enforces and blockers // stores enforces and blockers
void gather_global_model_info(GlobalModelInfo &result, const PrintObject *po) { void compute_global_occlusion(GlobalModelInfo &result, const PrintObject *po) {
BOOST_LOG_TRIVIAL(debug) BOOST_LOG_TRIVIAL(debug)
<< "SeamPlacer: build AABB tree for raycasting and gather occlusion info: start"; << "SeamPlacer: build AABB tree for raycasting and gather occlusion info: start";
// Build AABB tree for raycasting // Build AABB tree for raycasting
auto obj_transform = po->trafo(); auto obj_transform = po->trafo();
auto triangle_set = po->model_object()->raw_indexed_triangle_set(); auto triangle_set = po->model_object()->raw_indexed_triangle_set();
//add model parts //add model parts
for (const ModelVolume* model_volume : po->model_object()->volumes){ for (const ModelVolume *model_volume : po->model_object()->volumes) {
if (model_volume->type() == ModelVolumeType::MODEL_PART) { if (model_volume->type() == ModelVolumeType::MODEL_PART) {
auto model_transformation = model_volume->get_matrix(); auto model_transformation = model_volume->get_matrix();
indexed_triangle_set model_its = model_volume->mesh().its; indexed_triangle_set model_its = model_volume->mesh().its;
its_transform(model_its, model_transformation); its_transform(model_its, model_transformation);
its_merge(triangle_set , model_its); its_merge(triangle_set, model_its);
} }
} }
@ -617,9 +624,18 @@ void gather_global_model_info(GlobalModelInfo &result, const PrintObject *po) {
BOOST_LOG_TRIVIAL(debug) BOOST_LOG_TRIVIAL(debug)
<< "SeamPlacer: build AABB tree for raycasting and gather occlusion info: end"; << "SeamPlacer: build AABB tree for raycasting and gather occlusion info: end";
#ifdef DEBUG_FILES
auto filename = "visiblity_of_" + std::to_string(po->id().id) + ".obj";
result.debug_export(triangle_set, filename.c_str());
#endif
}
void gather_enforcers_blockers(GlobalModelInfo &result, const PrintObject *po) {
BOOST_LOG_TRIVIAL(debug) BOOST_LOG_TRIVIAL(debug)
<< "SeamPlacer: build AABB trees for raycasting enforcers/blockers: start"; << "SeamPlacer: build AABB trees for raycasting enforcers/blockers: start";
auto obj_transform = po->trafo();
for (const ModelVolume *mv : po->model_object()->volumes) { for (const ModelVolume *mv : po->model_object()->volumes) {
if (mv->is_seam_painted()) { if (mv->is_seam_painted()) {
auto model_transformation = mv->get_matrix(); auto model_transformation = mv->get_matrix();
@ -643,22 +659,22 @@ void gather_global_model_info(GlobalModelInfo &result, const PrintObject *po) {
BOOST_LOG_TRIVIAL(debug) BOOST_LOG_TRIVIAL(debug)
<< "SeamPlacer: build AABB trees for raycasting enforcers/blockers: end"; << "SeamPlacer: build AABB trees for raycasting enforcers/blockers: end";
#ifdef DEBUG_FILES
auto filename = "visiblity_of_" + std::to_string(po->id().id) + ".obj";
result.debug_export(triangle_set, filename.c_str());
#endif
} }
//Comparator of seam points. It has two necessary methods: is_first_better and is_first_not_much_worse //Comparator of seam points. It has two necessary methods: is_first_better and is_first_not_much_worse
struct DefaultSeamComparator { struct SeamComparator {
SeamPosition setup;
SeamComparator(SeamPosition setup) :
setup(setup) {
}
float compute_angle_penalty(float ccw_angle) const { float compute_angle_penalty(float ccw_angle) const {
if (ccw_angle >= 0) { if (ccw_angle >= 0) {
return PI * PI - ccw_angle * ccw_angle; return PI * PI - ccw_angle * ccw_angle;
} else { } else {
return (PI * PI - ccw_angle * ccw_angle) * 0.8f; return (PI * PI - ccw_angle * ccw_angle) * 0.7f;
} }
} }
// Standard comparator, must respect the requirements of comparators (e.g. give same result on same inputs) for sorting usage // Standard comparator, must respect the requirements of comparators (e.g. give same result on same inputs) for sorting usage
@ -677,6 +693,10 @@ struct DefaultSeamComparator {
return false; return false;
} }
if (setup == SeamPosition::spRear) {
return a.position.y() > b.position.y();
}
return (a.visibility + SeamPlacer::expected_hits_per_area) * compute_angle_penalty(a.local_ccw_angle) < return (a.visibility + SeamPlacer::expected_hits_per_area) * compute_angle_penalty(a.local_ccw_angle) <
(b.visibility + SeamPlacer::expected_hits_per_area) * compute_angle_penalty(b.local_ccw_angle); (b.visibility + SeamPlacer::expected_hits_per_area) * compute_angle_penalty(b.local_ccw_angle);
} }
@ -697,9 +717,23 @@ struct DefaultSeamComparator {
return false; return false;
} }
if (setup == SeamPosition::spRear) {
return a.position.y() > b.position.y();
}
return (a.visibility + SeamPlacer::expected_hits_per_area) * compute_angle_penalty(a.local_ccw_angle) * 0.75f <= return (a.visibility + SeamPlacer::expected_hits_per_area) * compute_angle_penalty(a.local_ccw_angle) * 0.75f <=
(b.visibility + SeamPlacer::expected_hits_per_area) * compute_angle_penalty(b.local_ccw_angle); (b.visibility + SeamPlacer::expected_hits_per_area) * compute_angle_penalty(b.local_ccw_angle);
} }
float get_weight(const SeamCandidate &a) const {
if (setup == SeamPosition::spRear) {
return a.position.y() + int(a.type) * 10000.0f;
}
//return negative, beacuse we want to minimize this value (sort of antiweight). normalization in alignment fixes that with respect to other points
return int(a.type) * 10000.0f
- (a.visibility + SeamPlacer::expected_hits_per_area) * compute_angle_penalty(a.local_ccw_angle);
}
} }
; ;
@ -789,13 +823,16 @@ tbb::parallel_for(tbb::blocked_range<size_t>(0, m_perimeter_points_per_object[po
// Otherwise does nothing, returns false // Otherwise does nothing, returns false
// sadly cannot be const because map access operator[] is not const, since it can create new object // sadly cannot be const because map access operator[] is not const, since it can create new object
template<typename Comparator> template<typename Comparator>
bool SeamPlacer::find_next_seam_in_string(const PrintObject *po, Vec3f &last_point_pos, bool SeamPlacer::find_next_seam_in_layer(const PrintObject *po,
std::pair<size_t, size_t> &last_point_indexes,
size_t layer_idx, const Comparator &comparator, size_t layer_idx, const Comparator &comparator,
std::vector<std::pair<size_t, size_t>> &seam_string, std::vector<std::pair<size_t, size_t>> &seam_string,
std::vector<std::pair<size_t, size_t>> &potential_string_seams) { std::vector<std::pair<size_t, size_t>> &potential_string_seams) {
using namespace SeamPlacerImpl; using namespace SeamPlacerImpl;
Vec3f projected_position { last_point_pos.x(), last_point_pos.y(), float( const SeamCandidate& last_point = m_perimeter_points_per_object[po][last_point_indexes.first][last_point_indexes.second];
Vec3f projected_position { last_point.position.x(), last_point.position.y(), float(
po->get_layer(layer_idx)->slice_z) }; po->get_layer(layer_idx)->slice_z) };
//find closest point in next layer //find closest point in next layer
size_t closest_point_index = find_closest_point( size_t closest_point_index = find_closest_point(
@ -811,17 +848,21 @@ bool SeamPlacer::find_next_seam_in_string(const PrintObject *po, Vec3f &last_poi
SeamCandidate &next_layer_seam = SeamCandidate &next_layer_seam =
m_perimeter_points_per_object[po][layer_idx][closest_point.perimeter->seam_index]; m_perimeter_points_per_object[po][layer_idx][closest_point.perimeter->seam_index];
auto are_similar = [&](const SeamCandidate& a, const SeamCandidate& b) {
return comparator.is_first_not_much_worse(a,b) && comparator.is_first_not_much_worse(b,a);
};
if ((next_layer_seam.position - projected_position).norm() if ((next_layer_seam.position - projected_position).norm()
< SeamPlacer::seam_align_tolerable_dist) { //seam point is within limits, put in the close_by_points list < SeamPlacer::seam_align_tolerable_dist && are_similar(last_point, next_layer_seam)) { //seam point is within limits, put in the close_by_points list
seam_string.emplace_back(layer_idx, closest_point.perimeter->seam_index); seam_string.emplace_back(layer_idx, closest_point.perimeter->seam_index);
last_point_pos = next_layer_seam.position; last_point_indexes = std::pair<size_t,size_t>{layer_idx, closest_point.perimeter->seam_index};
return true; return true;
} else if ((closest_point.position - projected_position).norm() } else if ((closest_point.position - projected_position).norm()
< SeamPlacer::seam_align_tolerable_dist < SeamPlacer::seam_align_tolerable_dist
&& comparator.is_first_not_much_worse(closest_point, next_layer_seam)) { && comparator.is_first_not_much_worse(closest_point, next_layer_seam) && are_similar(last_point, closest_point)) {
//seam point is far, but if the close point is not much worse, do not count it as a skip and add it to potential_string_seams //seam point is far, but if the close point is not much worse, do not count it as a skip and add it to potential_string_seams
potential_string_seams.emplace_back(layer_idx, closest_point_index); potential_string_seams.emplace_back(layer_idx, closest_point_index);
last_point_pos = closest_point.position; last_point_indexes = std::pair<size_t,size_t>{layer_idx, closest_point_index};
return true; return true;
} else { } else {
return false; return false;
@ -891,14 +932,14 @@ void SeamPlacer::align_seam_points(const PrintObject *po, const Comparator &comp
//initialize searching for seam string - cluster of nearby seams on previous and next layers //initialize searching for seam string - cluster of nearby seams on previous and next layers
int skips = SeamPlacer::seam_align_tolerable_skips / 2; int skips = SeamPlacer::seam_align_tolerable_skips / 2;
int next_layer = layer_idx + 1; int next_layer = layer_idx + 1;
Vec3f last_point_pos = layer_perimeter_points[seam_index].position; std::pair<size_t,size_t> last_point_indexes = std::pair<size_t,size_t>(layer_idx, seam_index);
std::vector<std::pair<size_t, size_t>> seam_string; std::vector<std::pair<size_t, size_t>> seam_string;
std::vector<std::pair<size_t, size_t>> potential_string_seams; std::vector<std::pair<size_t, size_t>> potential_string_seams;
//find seams or potential seams in forward direction; there is a budget of skips allowed //find seams or potential seams in forward direction; there is a budget of skips allowed
while (skips >= 0 && next_layer < int(m_perimeter_points_per_object[po].size())) { while (skips >= 0 && next_layer < int(m_perimeter_points_per_object[po].size())) {
if (find_next_seam_in_string(po, last_point_pos, next_layer, comparator, seam_string, if (find_next_seam_in_layer(po, last_point_indexes, next_layer, comparator, seam_string,
potential_string_seams)) { potential_string_seams)) {
//String added, last_point_pos updated, nothing to be done //String added, last_point_pos updated, nothing to be done
} else { } else {
@ -911,9 +952,9 @@ void SeamPlacer::align_seam_points(const PrintObject *po, const Comparator &comp
//do additional check in back direction //do additional check in back direction
next_layer = layer_idx - 1; next_layer = layer_idx - 1;
skips = SeamPlacer::seam_align_tolerable_skips / 2; skips = SeamPlacer::seam_align_tolerable_skips / 2;
last_point_pos = layer_perimeter_points[seam_index].position; last_point_indexes = std::pair<size_t,size_t>(layer_idx, seam_index);
while (skips >= 0 && next_layer >= 0) { while (skips >= 0 && next_layer >= 0) {
if (find_next_seam_in_string(po, last_point_pos, next_layer, comparator, if (find_next_seam_in_layer(po, last_point_indexes, next_layer, comparator,
seam_string, seam_string,
potential_string_seams)) { potential_string_seams)) {
//String added, last_point_pos updated, nothing to be done //String added, last_point_pos updated, nothing to be done
@ -938,15 +979,26 @@ void SeamPlacer::align_seam_points(const PrintObject *po, const Comparator &comp
return left.first < right.first; return left.first < right.first;
}); });
// gather all positions of seams // gather all positions of seams and their weights
std::vector<Vec3f> points(seam_string.size()); std::vector<Vec3f> points(seam_string.size());
std::vector<float> weights(seam_string.size());
float min_weight = comparator.get_weight(
m_perimeter_points_per_object[po][seam_string[0].first][seam_string[0].second]);
for (size_t index = 0; index < seam_string.size(); ++index) { for (size_t index = 0; index < seam_string.size(); ++index) {
points[index] = points[index] =
m_perimeter_points_per_object[po][seam_string[index].first][seam_string[index].second].position; m_perimeter_points_per_object[po][seam_string[index].first][seam_string[index].second].position;
weights[index] = comparator.get_weight(
m_perimeter_points_per_object[po][seam_string[index].first][seam_string[index].second]);
min_weight = std::min(min_weight, weights[index]);
}
for (float &w : weights) {
w = w - min_weight + 1.0; //makes all weights positive, nonzero
} }
// find coefficients of polynomial fit. Z coord is treated as parameter along which to fit both X and Y coords. // find coefficients of polynomial fit. Z coord is treated as parameter along which to fit both X and Y coords.
std::vector<Vec2f> coefficients = polyfit(points, 4); std::vector<Vec2f> coefficients = polyfit(points, weights, 4);
// Do alignment - compute fitted point for each point in the string from its Z coord, and store the position into // Do alignment - compute fitted point for each point in the string from its Z coord, and store the position into
// Perimeter structure of the point; also set flag aligned to true // Perimeter structure of the point; also set flag aligned to true
@ -960,24 +1012,29 @@ void SeamPlacer::align_seam_points(const PrintObject *po, const Comparator &comp
perimeter->aligned = true; perimeter->aligned = true;
} }
// //https://en.wikipedia.org/wiki/Exponential_smoothing // for (Vec3f& p : points){
// //inititalization // p = get_fitted_point(coefficients, p.z());
// float smoothing_factor = 0.8; // }
// std::pair<size_t, size_t> init = seam_string[0];
// Vec2f prev_pos_xy = m_perimeter_points_per_object[po][init.first][init.second].position.head<2>(); // for (size_t iteration = 0; iteration < 20; ++iteration) {
// for (const auto &pair : seam_string) { // std::vector<Vec3f> new_points(seam_string.size());
// Vec3f current_pos = m_perimeter_points_per_object[po][pair.first][pair.second].position; // for (int point_index = 0; point_index < points.size(); ++point_index) {
// float current_height = current_pos.z(); // size_t prev_idx = point_index > 0 ? point_index - 1 : point_index;
// Vec2f current_pos_xy = current_pos.head<2>(); // size_t next_idx = point_index < points.size() - 1 ? point_index + 1 : point_index;
// current_pos_xy = smoothing_factor * prev_pos_xy + (1.0 - smoothing_factor) * current_pos_xy;
// //
// Perimeter *perimeter = // new_points[point_index] = (points[prev_idx] * weights[prev_idx]
// m_perimeter_points_per_object[po][pair.first][pair.second].perimeter.get(); // + points[next_idx] * weights[next_idx]) /
// perimeter->final_seam_position = // (weights[prev_idx] + weights[next_idx]);
// Vec3f { current_pos_xy.x(), current_pos_xy.y(), current_height }; // }
// perimeter->aligned = true; // points = new_points;
// prev_pos_xy = current_pos_xy; // }
// } //
// for (size_t index = 0; index < seam_string.size(); ++index) {
// Perimeter *perimeter =
// m_perimeter_points_per_object[po][seam_string[index].first][seam_string[index].second].perimeter.get();
// perimeter->final_seam_position = points[index];
// perimeter->aligned = true;
// }
#ifdef DEBUG_FILES #ifdef DEBUG_FILES
auto randf = []() { auto randf = []() {
@ -1020,9 +1077,14 @@ void SeamPlacer::init(const Print &print) {
for (const PrintObject *po : print.objects()) { for (const PrintObject *po : print.objects()) {
SeamPosition configured_seam_preference = po->config().seam_position.value; SeamPosition configured_seam_preference = po->config().seam_position.value;
SeamComparator comparator { configured_seam_preference };
GlobalModelInfo global_model_info { }; GlobalModelInfo global_model_info { };
gather_global_model_info(global_model_info, po); gather_enforcers_blockers(global_model_info, po);
if (configured_seam_preference == spNearest || configured_seam_preference == spRandom) {
compute_global_occlusion(global_model_info, po);
}
BOOST_LOG_TRIVIAL(debug) BOOST_LOG_TRIVIAL(debug)
<< "SeamPlacer: gather_seam_candidates: start"; << "SeamPlacer: gather_seam_candidates: start";
@ -1030,7 +1092,7 @@ void SeamPlacer::init(const Print &print) {
BOOST_LOG_TRIVIAL(debug) BOOST_LOG_TRIVIAL(debug)
<< "SeamPlacer: gather_seam_candidates: end"; << "SeamPlacer: gather_seam_candidates: end";
if (configured_seam_preference != spAligned) { if (configured_seam_preference == spNearest || configured_seam_preference == spRandom) {
BOOST_LOG_TRIVIAL(debug) BOOST_LOG_TRIVIAL(debug)
<< "SeamPlacer: calculate_candidates_visibility : start"; << "SeamPlacer: calculate_candidates_visibility : start";
calculate_candidates_visibility(po, global_model_info); calculate_candidates_visibility(po, global_model_info);
@ -1054,7 +1116,7 @@ void SeamPlacer::init(const Print &print) {
m_perimeter_points_per_object[po][layer_idx]; m_perimeter_points_per_object[po][layer_idx];
size_t current = 0; size_t current = 0;
while (current < layer_perimeter_points.size()) { while (current < layer_perimeter_points.size()) {
pick_seam_point(layer_perimeter_points, current, DefaultSeamComparator { }); pick_seam_point(layer_perimeter_points, current, comparator);
current = layer_perimeter_points[current].perimeter->end_index + 1; current = layer_perimeter_points[current].perimeter->end_index + 1;
} }
} }
@ -1065,7 +1127,7 @@ void SeamPlacer::init(const Print &print) {
if (configured_seam_preference != spRandom) { if (configured_seam_preference != spRandom) {
BOOST_LOG_TRIVIAL(debug) BOOST_LOG_TRIVIAL(debug)
<< "SeamPlacer: align_seam_points : start"; << "SeamPlacer: align_seam_points : start";
align_seam_points(po, DefaultSeamComparator { }); align_seam_points(po, comparator);
BOOST_LOG_TRIVIAL(debug) BOOST_LOG_TRIVIAL(debug)
<< "SeamPlacer: align_seam_points : end"; << "SeamPlacer: align_seam_points : end";
} }

View File

@ -97,26 +97,26 @@ public:
using SeamCandidatesTree = using SeamCandidatesTree =
KDTreeIndirect<3, float, SeamPlacerImpl::SeamCandidateCoordinateFunctor>; KDTreeIndirect<3, float, SeamPlacerImpl::SeamCandidateCoordinateFunctor>;
// Rough estimates of hits of the mesh during raycasting per surface circle defined by considered_area_radius // Rough estimates of hits of the mesh during raycasting per surface circle defined by considered_area_radius
static constexpr float expected_hits_per_area = 400.0f; static constexpr float expected_hits_per_area = 1000.0f;
// area considered when computing number of rays and then gathering visiblity info from the hits // area considered when computing number of rays and then gathering visiblity info from the hits
static constexpr float considered_area_radius = 4.0f; static constexpr float considered_area_radius = 3.0f;
// quadric error limit of quadric decimation function used on the mesh before raycasting // quadric error limit of quadric decimation function used on the mesh before raycasting
static constexpr float raycasting_decimation_target_error = 0.1f; static constexpr float raycasting_decimation_target_error = 0.1f;
// cosine sampling power represents how prefered are forward directions when raycasting from given spot // cosine sampling power represents how prefered are forward directions when raycasting from given spot
// in this case, forward direction means towards the center of the mesh // in this case, forward direction means towards the center of the mesh
static constexpr float cosine_hemisphere_sampling_power = 4.0f; static constexpr float cosine_hemisphere_sampling_power = 8.0f;
// arm length used during angles computation // arm length used during angles computation
static constexpr float polygon_local_angles_arm_distance = 1.0f; static constexpr float polygon_local_angles_arm_distance = 1.0f;
// If enforcer or blocker is closer to the seam candidate than this limit, the seam candidate is set to Blocer or Enforcer // If enforcer or blocker is closer to the seam candidate than this limit, the seam candidate is set to Blocker or Enforcer
static constexpr float enforcer_blocker_distance_tolerance = 0.1f; static constexpr float enforcer_blocker_distance_tolerance = 0.1f;
// When searching for seam clusters for alignment: // When searching for seam clusters for alignment:
// seam_align_tolerable_dist - if seam is closer to the previous seam position projected to the current layer than this value, // seam_align_tolerable_dist - if seam is closer to the previous seam position projected to the current layer than this value,
//it belongs automaticaly to the cluster //it belongs automaticaly to the cluster
static constexpr float seam_align_tolerable_dist = 1.0f; static constexpr float seam_align_tolerable_dist = 0.5f;
// if the seam of the current layer is too far away, and the closest seam candidate is not very good, layer is skipped. // if the seam of the current layer is too far away, and the closest seam candidate is not very good, layer is skipped.
// this param limits the number of allowed skips // this param limits the number of allowed skips
static constexpr size_t seam_align_tolerable_skips = 4; static constexpr size_t seam_align_tolerable_skips = 4;
@ -141,7 +141,8 @@ private:
template<typename Comparator> template<typename Comparator>
void align_seam_points(const PrintObject *po, const Comparator &comparator); void align_seam_points(const PrintObject *po, const Comparator &comparator);
template<typename Comparator> template<typename Comparator>
bool find_next_seam_in_string(const PrintObject *po, Vec3f &last_point_pos, bool find_next_seam_in_layer(const PrintObject *po,
std::pair<size_t, size_t> &last_point,
size_t layer_idx, const Comparator &comparator, size_t layer_idx, const Comparator &comparator,
std::vector<std::pair<size_t, size_t>> &seam_strings, std::vector<std::pair<size_t, size_t>> &seam_strings,
std::vector<std::pair<size_t, size_t>> &outliers); std::vector<std::pair<size_t, size_t>> &outliers);