implemented seam alignment using exponential smoothing

This commit is contained in:
PavelMikus 2022-02-24 17:56:27 +01:00
parent 596bd68f18
commit 38a9d870c0
2 changed files with 297 additions and 150 deletions

View file

@ -168,47 +168,46 @@ std::vector<HitInfo> raycast_visibility(const AABBTreeIndirect::Tree<3, float> &
}
std::vector<float> calculate_polygon_angles_at_vertices(const Polygon &polygon, const std::vector<float> &lengths,
float min_arm_length)
{
assert(polygon.points.size() + 1 == lengths.size());
if (min_arm_length > 0.25f * lengths.back())
min_arm_length = 0.25f * lengths.back();
float min_arm_length) {
if (polygon.size() == 1) {
return {0.0f};
}
// Find the initial prev / next point span.
size_t idx_prev = polygon.points.size();
size_t idx_curr = 0;
size_t idx_next = 1;
while (idx_prev > idx_curr && lengths.back() - lengths[idx_prev] < min_arm_length)
--idx_prev;
while (idx_next < idx_prev && lengths[idx_next] < min_arm_length)
++idx_next;
std::vector<float> result(polygon.size());
std::vector<float> angles(polygon.points.size(), 0.f);
for (; idx_curr < polygon.points.size(); ++idx_curr) {
// Move idx_prev up until the distance between idx_prev and idx_curr is lower than min_arm_length.
if (idx_prev >= idx_curr) {
while (idx_prev < polygon.points.size()
&& lengths.back() - lengths[idx_prev] + lengths[idx_curr] > min_arm_length)
++idx_prev;
if (idx_prev == polygon.points.size())
idx_prev = 0;
auto make_idx_circular = [&](int index) {
while (index < 0) {
index += polygon.size();
}
while (idx_prev < idx_curr && lengths[idx_curr] - lengths[idx_prev] > min_arm_length)
++idx_prev;
// Move idx_prev one step back.
if (idx_prev == 0)
idx_prev = polygon.points.size() - 1;
else
--idx_prev;
// Move idx_next up until the distance between idx_curr and idx_next is greater than min_arm_length.
if (idx_curr <= idx_next) {
while (idx_next < polygon.points.size() && lengths[idx_next] - lengths[idx_curr] < min_arm_length)
++idx_next;
if (idx_next == polygon.points.size())
idx_next = 0;
return index % polygon.size();
};
int idx_prev = 0;
int idx_curr = 0;
int idx_next = 0;
float distance_to_prev = 0;
float distance_to_next = 0;
//push idx_prev far enough back as initialization
while (distance_to_prev < min_arm_length) {
idx_prev = make_idx_circular(idx_prev - 1);
distance_to_prev += lengths[idx_prev];
}
for (size_t _i = 0; _i < polygon.size(); ++_i) {
// pull idx_prev to current as much as possible, while respecting the min_arm_length
while (distance_to_prev - lengths[idx_prev] > min_arm_length) {
distance_to_prev -= lengths[idx_prev];
idx_prev = make_idx_circular(idx_prev + 1);
}
while (idx_next < idx_curr && lengths.back() - lengths[idx_curr] + lengths[idx_next] < min_arm_length)
++idx_next;
//push idx_next forward as far as needed
while (distance_to_next < min_arm_length) {
distance_to_next += lengths[idx_next];
idx_next = make_idx_circular(idx_next + 1);
}
// Calculate angle between idx_prev, idx_curr, idx_next.
const Point &p0 = polygon.points[idx_prev];
const Point &p1 = polygon.points[idx_curr];
@ -218,10 +217,16 @@ std::vector<float> calculate_polygon_angles_at_vertices(const Polygon &polygon,
int64_t dot = int64_t(v1(0)) * int64_t(v2(0)) + int64_t(v1(1)) * int64_t(v2(1));
int64_t cross = int64_t(v1(0)) * int64_t(v2(1)) - int64_t(v1(1)) * int64_t(v2(0));
float angle = float(atan2(float(cross), float(dot)));
angles[idx_curr] = angle;
result[idx_curr] = angle;
// increase idx_curr by one
float curr_distance = lengths[idx_curr];
idx_curr++;
distance_to_prev += curr_distance;
distance_to_next -= curr_distance;
}
return angles;
return result;
}
struct GlobalModelInfo {
@ -348,12 +353,21 @@ Polygons extract_perimeter_polygons(const Layer *layer) {
void process_perimeter_polygon(const Polygon &orig_polygon, float z_coord, std::vector<SeamCandidate> &result_vec,
const GlobalModelInfo &global_model_info) {
if (orig_polygon.size() == 0) {
return;
}
Polygon polygon = orig_polygon;
bool was_clockwise = polygon.make_counter_clockwise();
std::vector<float> lengths = polygon.parameter_by_length();
std::vector<float> angles = calculate_polygon_angles_at_vertices(polygon, lengths,
SeamPlacer::polygon_angles_arm_distance);
std::vector<float> lengths { };
for (size_t point_idx = 0; point_idx < polygon.size() - 1; ++point_idx) {
lengths.push_back(std::max((unscale(polygon[point_idx]) - unscale(polygon[point_idx + 1])).norm(), 0.01));
}
lengths.push_back(std::max((unscale(polygon[0]) - unscale(polygon[polygon.size() - 1])).norm(), 0.01));
std::vector<float> local_angles = calculate_polygon_angles_at_vertices(polygon, lengths,
SeamPlacer::polygon_local_angles_arm_distance);
std::shared_ptr<Perimeter> perimeter = std::make_shared<Perimeter>();
perimeter->start_index = result_vec.size();
@ -372,9 +386,9 @@ void process_perimeter_polygon(const Polygon &orig_polygon, float z_coord, std::
type = EnforcedBlockedSeamPoint::Blocked;
}
float ccw_angle = was_clockwise ? -angles[index] : angles[index];
float local_ccw_angle = was_clockwise ? -local_angles[index] : local_angles[index];
result_vec.emplace_back(unscaled_position, perimeter, ccw_angle, type);
result_vec.emplace_back(unscaled_position, perimeter, local_ccw_angle, type);
}
}
@ -398,6 +412,7 @@ std::pair<size_t, size_t> find_previous_and_next_perimeter_point(const std::vect
}
//NOTE: only rough esitmation of overhang distance
// value represents distance from edge, positive is overhang, negative is inside shape
float calculate_overhang(const SeamCandidate &point, const SeamCandidate &under_a, const SeamCandidate &under_b,
const SeamCandidate &under_c) {
auto p = Vec2d { point.position.x(), point.position.y() };
@ -412,24 +427,24 @@ float calculate_overhang(const SeamCandidate &point, const SeamCandidate &under_
auto dist_ab = oriented_line_dist(a, b, p);
auto dist_bc = oriented_line_dist(b, c, p);
if (under_b.ccw_angle > 0 && dist_ab > 0 && dist_bc > 0) { //convex shape, p is inside
return 0;
if (under_b.local_ccw_angle > 0 && dist_ab > 0 && dist_bc > 0) { //convex shape, p is inside
return -((p - b).norm() + dist_ab + dist_bc) / 3.0;
}
if (under_b.ccw_angle < 0 && (dist_ab < 0 || dist_bc < 0)) { //concave shape, p is inside
return 0;
if (under_b.local_ccw_angle < 0 && (dist_ab < 0 || dist_bc < 0)) { //concave shape, p is inside
return -((p - b).norm() + dist_ab + dist_bc) / 3.0;
}
return (p - b).norm();
return ((p - b).norm() + dist_ab + dist_bc) / 3.0;
}
template<typename CompareFunc>
template<typename Comparator>
void pick_seam_point(std::vector<SeamCandidate> &perimeter_points, size_t start_index,
const CompareFunc &is_first_better) {
const Comparator &comparator) {
size_t end_index = perimeter_points[start_index].perimeter->end_index;
size_t seam_index = start_index;
for (size_t index = start_index + 1; index <= end_index; ++index) {
if (is_first_better(perimeter_points[index], perimeter_points[seam_index])) {
if (comparator.is_first_better(perimeter_points[index], perimeter_points[seam_index])) {
seam_index = index;
}
}
@ -481,8 +496,21 @@ void gather_global_model_info(GlobalModelInfo &result, const PrintObject *po) {
}
struct DefaultSeamComparator {
//is A better?
bool operator()(const SeamCandidate &a, const SeamCandidate &b) const {
static constexpr float angle_clusters[] { -1.0, 0.4 * PI, 0.6
* PI, 0.7 * PI, 0.8 * PI, 0.9 * PI };
const float get_angle_category(float ccw_angle) const {
float concave_bonus = ccw_angle < 0 ? 0.1 : 0;
float abs_angle = abs(ccw_angle) + concave_bonus;
auto category = std::find_if_not(std::begin(angle_clusters), std::end(angle_clusters),
[&](float category_limit) {
return abs_angle > category_limit;
});
category--;
return *category;
}
bool is_first_better(const SeamCandidate &a, const SeamCandidate &b) const {
// Blockers/Enforcers discrimination, top priority
if (a.type > b.type) {
return true;
@ -492,35 +520,52 @@ struct DefaultSeamComparator {
}
//avoid overhangs
if (a.overhang > 0.5f && b.overhang < a.overhang) {
if (a.overhang > 0.3f && b.overhang < a.overhang) {
return false;
}
auto angle_score = [](float ccw_angle) {
if (ccw_angle > 0) {
float normalized = (ccw_angle / float(PI)) * 0.9f;
return normalized;
} else {
float normalized = (-ccw_angle / float(PI)) * 1.1f;
return normalized;
{ //local angles
float a_local_category = get_angle_category(a.local_ccw_angle);
float b_local_category = get_angle_category(b.local_ccw_angle);
if (a_local_category > b_local_category) {
return true;
}
};
float angle_weight = 2.0f;
if (a_local_category < b_local_category) {
return false;
}
}
auto vis_score = [](float visibility) {
return (1.0f - visibility / SeamPlacer::expected_hits_per_area);
};
float vis_weight = 1.2f;
return a.visibility < b.visibility;
}
float score_a = angle_score(a.ccw_angle) * angle_weight +
vis_score(a.visibility) * vis_weight;
float score_b = angle_score(b.ccw_angle) * angle_weight +
vis_score(b.visibility) * vis_weight;
if (score_a > score_b)
bool is_first_not_much_worse(const SeamCandidate &a, const SeamCandidate &b) const {
// Blockers/Enforcers discrimination, top priority
if (a.type > b.type) {
return true;
else
}
if (b.type > a.type) {
return false;
}
//avoid overhangs
if (a.overhang > 0.3f && b.overhang < a.overhang) {
return false;
}
{ //local angles
float a_local_category = get_angle_category(a.local_ccw_angle) + 0.1 * PI; //give a slight bonus
float b_local_category = get_angle_category(b.local_ccw_angle);
if (a_local_category > b_local_category) {
return true;
}
if (a_local_category < b_local_category) {
return false;
}
}
return a.visibility < b.visibility * 1.3;
}
}
;
@ -546,8 +591,8 @@ tbb::parallel_for(tbb::blocked_range<size_t>(0, po->layers().size()),
global_model_info);
}
auto functor = SeamCandidateCoordinateFunctor { &layer_candidates };
m_perimeter_points_trees_per_object[po][layer_idx] = (std::make_unique<SeamCandidatesTree>(
functor, layer_candidates.size()));
m_perimeter_points_trees_per_object[po][layer_idx] = std::make_unique<SeamCandidatesTree>(
functor, layer_candidates.size());
}
}
);
@ -561,7 +606,8 @@ tbb::parallel_for(tbb::blocked_range<size_t>(0, m_perimeter_points_per_object[po
[&](tbb::blocked_range<size_t> r) {
for (size_t layer_idx = r.begin(); layer_idx < r.end(); ++layer_idx) {
for (auto &perimeter_point : m_perimeter_points_per_object[po][layer_idx]) {
perimeter_point.visibility = global_model_info.calculate_point_visibility(perimeter_point.position);
perimeter_point.visibility = global_model_info.calculate_point_visibility(
perimeter_point.position);
}
}
});
@ -574,31 +620,155 @@ tbb::parallel_for(tbb::blocked_range<size_t>(0, m_perimeter_points_per_object[po
[&](tbb::blocked_range<size_t> r) {
for (size_t layer_idx = r.begin(); layer_idx < r.end(); ++layer_idx) {
for (SeamCandidate &perimeter_point : m_perimeter_points_per_object[po][layer_idx]) {
if (layer_idx > 0) {
const auto calculate_layer_overhang = [&](size_t other_layer_idx) {
size_t closest_supporter = find_closest_point(
*m_perimeter_points_trees_per_object[po][layer_idx - 1],
*m_perimeter_points_trees_per_object[po][other_layer_idx],
perimeter_point.position);
const SeamCandidate &supporter_point =
m_perimeter_points_per_object[po][layer_idx - 1][closest_supporter];
m_perimeter_points_per_object[po][other_layer_idx][closest_supporter];
auto prev_next = find_previous_and_next_perimeter_point(m_perimeter_points_per_object[po][layer_idx-1], closest_supporter);
auto prev_next = find_previous_and_next_perimeter_point(m_perimeter_points_per_object[po][other_layer_idx], closest_supporter);
const SeamCandidate &prev_point =
m_perimeter_points_per_object[po][layer_idx - 1][prev_next.first];
m_perimeter_points_per_object[po][other_layer_idx][prev_next.first];
const SeamCandidate &next_point =
m_perimeter_points_per_object[po][layer_idx - 1][prev_next.second];
m_perimeter_points_per_object[po][other_layer_idx][prev_next.second];
perimeter_point.overhang = calculate_overhang(perimeter_point, prev_point,
return calculate_overhang(perimeter_point, prev_point,
supporter_point, next_point);
};
if (layer_idx > 0) { //calculate overhang
perimeter_point.overhang = calculate_layer_overhang(layer_idx-1);
}
if (layer_idx < m_perimeter_points_per_object[po].size() - 1) { //calculate higher_layer_overhang
perimeter_point.higher_layer_overhang = calculate_layer_overhang(layer_idx+1);
}
}
}
});
}
void SeamPlacer::distribute_seam_positions_for_alignment(const PrintObject *po) {
// sadly cannot be const because map access operator[] is not const, since it can create new object
template<typename Comparator>
bool SeamPlacer::find_next_seam_in_string(const PrintObject *po, const Vec3f &last_point_pos,
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>> &potential_string_seams) {
using namespace SeamPlacerImpl;
Vec3f projected_position { last_point_pos.x(), last_point_pos.y(), float(
po->get_layer(layer_idx)->slice_z) };
//find closest point in next layer
size_t closest_point_index = find_closest_point(
*m_perimeter_points_trees_per_object[po][layer_idx], projected_position);
SeamCandidate &closest_point = m_perimeter_points_per_object[po][layer_idx][closest_point_index];
if (closest_point.perimeter->aligned) { //already aligned, skip
return false;
}
//from the closest point, deduce index of seam in the next layer
SeamCandidate &next_layer_seam =
m_perimeter_points_per_object[po][layer_idx][closest_point.perimeter->seam_index];
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
seam_strings.emplace_back(layer_idx, closest_point.perimeter->seam_index);
return true;
} else if ((closest_point.position - projected_position).norm()
< SeamPlacer::seam_align_tolerable_dist
&& comparator.is_first_not_much_worse(closest_point, next_layer_seam)) {
//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);
return true;
} else {
return false;
}
}
//https://towardsdatascience.com/least-square-polynomial-fitting-using-c-eigen-package-c0673728bd01
template<typename Comparator>
void SeamPlacer::align_seam_points(const PrintObject *po, const Comparator &comparator) {
using namespace SeamPlacerImpl;
for (size_t layer_idx = 0; layer_idx < m_perimeter_points_per_object[po].size(); ++layer_idx) {
std::vector<SeamCandidate> &layer_perimeter_points =
m_perimeter_points_per_object[po][layer_idx];
size_t current_point_index = 0;
while (current_point_index < layer_perimeter_points.size()) {
if (layer_perimeter_points[current_point_index].perimeter->aligned) {
//skip
} else {
int skips = SeamPlacer::seam_align_tolerable_skips;
int next_layer = layer_idx - 1;
Vec3f last_point_pos = layer_perimeter_points[current_point_index].position;
std::vector<std::pair<size_t, size_t>> seam_string;
std::vector<std::pair<size_t, size_t>> potential_string_seams;
//find close by points and outliers; there is a budget of skips allowed
// search from bottom up in z dir. Heuristics which avoids smooth top surfaces (like head) where the seam is not well defined
while (skips >= 0 && next_layer >= 0) {
if (find_next_seam_in_string(po, last_point_pos, next_layer, comparator, seam_string,
potential_string_seams)) {
last_point_pos =
m_perimeter_points_per_object[po][seam_string.back().first][seam_string.back().second].position;
} else {
skips--;
}
next_layer--;
}
if (seam_string.size() > 4) { //string long enough to be worth aligning
//do additional check in back direction
next_layer = layer_idx;
skips = SeamPlacer::seam_align_tolerable_skips;
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, DefaultSeamComparator { },
seam_string,
potential_string_seams)) {
last_point_pos =
m_perimeter_points_per_object[po][seam_string.back().first][seam_string.back().second].position;
} else {
skips--;
}
next_layer++;
}
// all string seams and potential string seams gathered, now do the alignment
seam_string.insert(seam_string.end(), potential_string_seams.begin(), potential_string_seams.end());
std::sort(seam_string.begin(), seam_string.end(),
[](const std::pair<size_t, size_t> &left, const std::pair<size_t, size_t> &right) {
return left.first < right.first;
});
//https://en.wikipedia.org/wiki/Exponential_smoothing
//inititalization
float smoothing_factor = 0.5;
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 (const auto &pair : seam_string) {
Vec3f current_pos = m_perimeter_points_per_object[po][pair.first][pair.second].position;
float current_height = current_pos.z();
Vec2f current_pos_xy = current_pos.head<2>();
current_pos_xy = smoothing_factor * prev_pos_xy + (1.0 - smoothing_factor) * current_pos_xy;
Perimeter *perimeter =
m_perimeter_points_per_object[po][pair.first][pair.second].perimeter.get();
perimeter->final_seam_position =
Vec3f { current_pos_xy.x(), current_pos_xy.y(), current_height };
perimeter->aligned = true;
prev_pos_xy = current_pos_xy;
}
} // else string is not long enough, so dont do anything
}
current_point_index = layer_perimeter_points[current_point_index].perimeter->end_index + 1;
}
}
}
void SeamPlacer::init(const Print &print) {
@ -630,31 +800,29 @@ void SeamPlacer::init(const Print &print) {
<< "SeamPlacer: calculate_overhangs : end";
BOOST_LOG_TRIVIAL(debug)
<< "SeamPlacer: distribute_seam_positions_for_alignment, pick_seams : start";
for (size_t iteration = 0; iteration < seam_align_iterations; ++iteration) {
if (iteration > 0) { //skip this in first iteration, no seam has been picked yet; nothing to distribute
distribute_seam_positions_for_alignment(po);
}
//pick seam point
tbb::parallel_for(tbb::blocked_range<size_t>(0, m_perimeter_points_per_object[po].size()),
[&](tbb::blocked_range<size_t> r) {
for (size_t layer_idx = r.begin(); layer_idx < r.end(); ++layer_idx) {
std::vector<SeamCandidate> &layer_perimeter_points =
m_perimeter_points_per_object[po][layer_idx];
size_t current = 0;
while (current < layer_perimeter_points.size()) {
//NOTE: pick seam point function also resets the m_nearby_seam_points count on all passed points
pick_seam_point(layer_perimeter_points, current, DefaultSeamComparator { });
current = layer_perimeter_points[current].perimeter->end_index + 1;
}
<< "SeamPlacer: pick_seam_point : start";
//pick seam point
tbb::parallel_for(tbb::blocked_range<size_t>(0, m_perimeter_points_per_object[po].size()),
[&](tbb::blocked_range<size_t> r) {
for (size_t layer_idx = r.begin(); layer_idx < r.end(); ++layer_idx) {
std::vector<SeamCandidate> &layer_perimeter_points =
m_perimeter_points_per_object[po][layer_idx];
size_t current = 0;
while (current < layer_perimeter_points.size()) {
pick_seam_point(layer_perimeter_points, current, DefaultSeamComparator { });
current = layer_perimeter_points[current].perimeter->end_index + 1;
}
});
}
}
});
BOOST_LOG_TRIVIAL(debug)
<< "SeamPlacer: distribute_seam_positions_for_alignment, pick_seams : end";
<< "SeamPlacer: pick_seam_point : end";
BOOST_LOG_TRIVIAL(debug)
<< "SeamPlacer: align_seam_points : start";
align_seam_points(po, DefaultSeamComparator { });
BOOST_LOG_TRIVIAL(debug)
<< "SeamPlacer: align_seam_points : end";
}
}
@ -683,36 +851,4 @@ void SeamPlacer::place_seam(const Layer *layer, ExtrusionLoop &loop, bool extern
loop.split_at(seam_point, true);
}
// Disabled debug code, can be used to export debug data into obj files (e.g. point cloud of visibility hits)
#if 0
#include <boost/nowide/cstdio.hpp>
Slic3r::CNumericLocalesSetter locales_setter;
FILE *fp = boost::nowide::fopen("perimeters.obj", "w");
if (fp == nullptr) {
BOOST_LOG_TRIVIAL(error)
<< "Couldn't open " << "perimeters.obj" << " for writing";
}
for (size_t i = 0; i < perimeter_points.size(); ++i)
fprintf(fp, "v %f %f %f %f\n", perimeter_points[i].position[0], perimeter_points[i].position[1],
perimeter_points[i].position[2], perimeter_points[i].visibility);
fclose(fp);
#endif
#if 0
its_write_obj(triangles, "triangles.obj");
Slic3r::CNumericLocalesSetter locales_setter;
FILE *fp = boost::nowide::fopen("hits.obj", "w");
if (fp == nullptr) {
BOOST_LOG_TRIVIAL(error)
<< "Couldn't open " << "hits.obj" << " for writing";
}
for (size_t i = 0; i < hit_points.size(); ++i)
fprintf(fp, "v %f %f %f \n", hit_points[i].position[0], hit_points[i].position[1],
hit_points[i].position[2]);
fclose(fp);
#endif
} // namespace Slic3r

View file

@ -38,18 +38,23 @@ struct Perimeter {
size_t start_index;
size_t end_index;
size_t seam_index;
bool aligned = false;
Vec3f final_seam_position;
};
struct SeamCandidate {
SeamCandidate(const Vec3f &pos, std::shared_ptr<Perimeter> perimeter, float ccw_angle,
SeamCandidate(const Vec3f &pos, std::shared_ptr<Perimeter> perimeter, float local_ccw_angle,
EnforcedBlockedSeamPoint type) :
position(pos), perimeter(perimeter), visibility(0.0f), overhang(0.0f), ccw_angle(ccw_angle), type(type) {
position(pos), perimeter(perimeter), visibility(0.0f), overhang(0.0f), higher_layer_overhang(0.0f), local_ccw_angle(
local_ccw_angle), type(type) {
}
const Vec3f position;
const std::shared_ptr<Perimeter> perimeter;
float visibility;
float overhang;
float ccw_angle;
float higher_layer_overhang; // represents how much is the position covered by the upper layer, useful for local visibility
float local_ccw_angle;
EnforcedBlockedSeamPoint type;
};
@ -88,12 +93,12 @@ public:
static constexpr float cosine_hemisphere_sampling_power = 6.0f;
static constexpr float polygon_angles_arm_distance = 0.6f;
static constexpr float polygon_local_angles_arm_distance = 0.6f;
static constexpr float enforcer_blocker_sqr_distance_tolerance = 0.4f;
static constexpr size_t seam_align_iterations = 1;
static constexpr size_t seam_align_layer_dist = 30;
static constexpr float seam_align_tolerable_dist = 0.3f;
static constexpr float enforcer_blocker_sqr_distance_tolerance = 0.2f;
static constexpr float seam_align_tolerable_dist = 1.5f;
static constexpr size_t seam_align_tolerable_skips = 4;
//perimeter points per object per layer idx, and their corresponding KD trees
std::unordered_map<const PrintObject*, std::vector<std::vector<SeamPlacerImpl::SeamCandidate>>> m_perimeter_points_per_object;
std::unordered_map<const PrintObject*, std::vector<std::unique_ptr<SeamCandidatesTree>>> m_perimeter_points_trees_per_object;
@ -107,7 +112,13 @@ private:
void calculate_candidates_visibility(const PrintObject *po,
const SeamPlacerImpl::GlobalModelInfo &global_model_info);
void calculate_overhangs(const PrintObject *po);
void distribute_seam_positions_for_alignment(const PrintObject *po);
template<typename Comparator>
void align_seam_points(const PrintObject *po, const Comparator &comparator);
template<typename Comparator>
bool find_next_seam_in_string(const PrintObject *po, const Vec3f &last_point_pos,
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>> &outliers);
};
} // namespace Slic3r