Merge branch 'lm_measurement_fixes'

This commit is contained in:
Lukas Matena 2022-11-04 11:52:55 +01:00
commit 34391a14a2

View File

@ -196,9 +196,11 @@ void MeasuringImpl::update_planes()
if (last_border.size() == 1)
m_planes[plane_id].borders.pop_back();
else {
assert(last_border.front() == last_border.back());
}
}
}
continue; // There was no failure.
PLANE_FAILURE:
@ -213,9 +215,6 @@ void MeasuringImpl::update_planes()
void MeasuringImpl::extract_features()
{
auto N_to_angle = [](double N) -> double { return 2.*M_PI / N; };
constexpr double polygon_upper_threshold = N_to_angle(4.5);
constexpr double polygon_lower_threshold = N_to_angle(8.5);
std::vector<double> angles;
std::vector<double> lengths;
@ -233,30 +232,37 @@ void MeasuringImpl::extract_features()
for (const std::vector<Vec3d>& border : plane.borders) {
if (border.size() <= 1)
continue;
assert(border.front() == border.back());
int start_idx = -1;
std::vector<SurfaceFeature> edges;
// First calculate angles at all the vertices.
angles.clear();
lengths.clear();
for (int i=0; i<int(border.size()); ++i) {
const Vec3d& v2 = (i == 0 ? border[0] - border[border.size()-1]
: border[i] - border[i-1]);
const Vec3d& v1 = i == (int)border.size()-1 ? border[0] - border.back()
for (int i=0; i<int(border.size()); ++i) { // front is the same as back, hence the weird indexing
const Vec3d& v2 = (i == 0 ? border[0] - border[border.size()-2]
: border[i] - border[i-1]);
const Vec3d& v1 = i == (int)border.size()-1 ? border[1] - border.back()
: border[i+1] - border[i];
double angle = atan2(-normal.dot(v1.cross(v2)), -v1.dot(v2)) + M_PI;
if (angle > M_PI)
angle = 2*M_PI - angle;
angles.push_back(angle);
lengths.push_back(v2.squaredNorm());
lengths.push_back(v2.norm());
}
assert(border.size() == angles.size());
assert(border.size() == lengths.size());
// First go around the border and pick what might be circular segments.
// Save pair of indices to where such potential segments start and end.
// Also remember the length of these segments.
bool circle = false;
std::vector<SurfaceFeature> circles;
std::vector<std::pair<size_t, size_t>> circles_idxs;
std::vector<std::pair<int, int>> circles_idxs;
std::vector<double> circles_lengths;
for (int i=1; i<(int)angles.size(); ++i) {
if (Slic3r::is_approx(lengths[i], lengths[i-1])
&& Slic3r::is_approx(angles[i], angles[i-1])
@ -268,62 +274,149 @@ void MeasuringImpl::extract_features()
}
} else {
if (circle) {
// Add the circle and remember indices into borders.
const auto& [center, radius] = get_center_and_radius(border, start_idx, i, trafo);
// Add the circle and remember indices into borders.
circles_idxs.emplace_back(start_idx, i);
circles.emplace_back(SurfaceFeature(SurfaceFeatureType::Circle, center, plane.normal, std::nullopt, radius));
circles_lengths.emplace_back(std::accumulate(lengths.begin() + start_idx + 1, lengths.begin() + i + 1, 0.));
circle = false;
}
}
}
// Some of the "circles" may actually be polygons. We want them detected as
// edges, but also to remember the center and save it into those edges.
// We will add all such edges manually and delete the detected circles,
// leaving it in circles_idxs so they are not picked again:
// At this point we might need to merge the first and last segment, if the starting
// point happened to be inside the segment. The discrimination of too small segments
// will follow, so we need a complete picture before that.
if (circles_idxs.size() > 1
&& circles_idxs.back().second == angles.size()-1
&& circles_idxs.front().first == 0) {
// Possibly the same circle. Check that the angle and length criterion holds along the combined segment.
bool same = true;
double last_len = -1.;
double last_angle = 0.;
for (int i=circles_idxs.back().first + 1; i != circles_idxs.front().second; ++i) {
if (i == angles.size())
i = 1;
if (last_len == -1.) {
last_len = lengths[i];
last_angle = angles[i];
} else {
if (! Slic3r::is_approx(lengths[i], last_len) || ! Slic3r::is_approx(angles[i], last_angle)) {
same = false;
break;
}
}
}
if (same) {
// This seems to really be the same circle. Better apply ransac again. The parts can be small and inexact.
std::vector<Vec3d> points(border.begin() + circles_idxs.back().first, border.end());
points.insert(points.end(), border.begin(), border.begin() + circles_idxs.front().second+1);
auto [c, radius] = get_center_and_radius(points, 0, points.size()-1, trafo);
// Now replace the first circle with the combined one, remove the last circle.
// First index of the first circle is saved negative - we are going to pick edges
// from the border later, we will need to know where the merged in segment was.
// The sign simplifies the algorithm that picks the remaining edges - see below.
circles.front() = SurfaceFeature(SurfaceFeatureType::Circle, c, plane.normal, std::nullopt, radius);
circles_idxs.front().first = - circles_idxs.back().first;
circles_lengths.front() += circles_lengths.back();
circles.pop_back();
circles_idxs.pop_back();
circles_lengths.pop_back();
}
}
// Now throw away all circles that subtend less than 90 deg.
assert(circles.size() == circles_lengths.size());
for (int i=0; i<int(circles.size()); ++i) {
double r = std::get<1>(circles[i].get_circle());
if (circles_lengths[i] / r < 0.9*M_PI/2.) {
circles_lengths.erase(circles_lengths.begin() + i);
circles.erase(circles.begin() + i);
circles_idxs.erase(circles_idxs.begin() + i);
--i;
}
}
circles_lengths.clear(); // no longer needed, make it obvious
// Some of the "circles" may actually be polygons (5-8 vertices). We want them
// detected as edges, but also to remember the center and save it into those edges.
// We will add all such edges manually and delete the detected circles, leaving it
// in circles_idxs so they are not picked again.
assert(circles.size() == circles_idxs.size());
for (int i=circles.size()-1; i>=0; --i) {
assert(circles_idxs[i].first + 1 < angles.size() - 1); // Check that this is internal point of the circle, not the first, not the last.
double angle = angles[circles_idxs[i].first + 1];
if (angle > polygon_lower_threshold) {
if (angle < polygon_upper_threshold) {
const Vec3d center = std::get<0>(circles[i].get_circle());
for (int j=(int)circles_idxs[i].first + 1; j<=(int)circles_idxs[i].second; ++j)
plane.surface_features.emplace_back(SurfaceFeature(SurfaceFeatureType::Edge,
border[j - 1], border[j], std::make_optional(center)));
} else {
// This will be handled just like a regular edge.
circles_idxs.erase(circles_idxs.begin() + i);
if (circles_idxs[i].first == 0 && circles_idxs[i].second == border.size()-1) {
int N = circles_idxs[i].second - circles_idxs[i].first;
if (N <= 8) {
if (N >= 5) { // polygon = 5,6,7,8 vertices
const Vec3d center = std::get<0>(circles[i].get_circle());
for (int j=(int)circles_idxs[i].first + 1; j<=(int)circles_idxs[i].second; ++j)
edges.emplace_back(SurfaceFeature(SurfaceFeatureType::Edge,
border[j - 1], border[j], std::make_optional(center)));
} else {
// This will be handled just like a regular edge (squares, triangles).
circles_idxs.erase(circles_idxs.begin() + i);
}
circles.erase(circles.begin() + i);
}
}
}
// Anything under 5 vertices shall not be considered a circle.
assert(circles_idxs.size() == circles.size());
for (int i=0; i<int(circles_idxs.size()); ++i) {
const auto& [start, end] = circles_idxs[i];
int N = start >= 0
? end - start + (start == 0 && end == border.size()-1 ? 0 : 1) // last point is the same as first
: end + (border.size() + start);
if (N < 5) {
circles.erase(circles.begin() + i);
circles_idxs.erase(circles_idxs.begin() + i);
--i;
}
}
// We have the circles. Now go around again and pick edges.
int cidx = 0; // index of next circle in the way
// We have the circles. Now go around again and pick edges, while jumping over circles.
// If the first index of the first circle is negative, it means that it was merged
// with a segment that was originally at the back and is no longer there. Ressurect
// its pair of indices so that edges are not picked again.
if (! circles_idxs.empty() && circles_idxs.front().first < 0)
circles_idxs.emplace_back(-circles_idxs.front().first, int(border.size()));
int cidx = 0; // index of next circle to jump over
for (int i=1; i<int(border.size()); ++i) {
if (cidx < (int)circles_idxs.size() && i > (int)circles_idxs[cidx].first)
i = circles_idxs[cidx++].second;
else
plane.surface_features.emplace_back(SurfaceFeature(SurfaceFeatureType::Edge, border[i - 1], border[i]));
edges.emplace_back(SurfaceFeature(SurfaceFeatureType::Edge, border[i - 1], border[i]));
}
// FIXME Throw away / do not create edges which are parts of circles or
// which lead to circle points (unless they belong to the same plane.)
// Merge adjacent edges where needed.
assert(std::all_of(edges.begin(), edges.end(),
[](const SurfaceFeature& f) { return f.get_type() == SurfaceFeatureType::Edge; }));
for (int i=edges.size()-1; i>=0; --i) {
const auto& [first_start, first_end] = edges[i==0 ? edges.size()-1 : i-1].get_edge();
const auto& [second_start, second_end] = edges[i].get_edge();
// FIXME Check and merge first and last circle if needed.
if (Slic3r::is_approx(first_end, second_start)
&& Slic3r::is_approx((first_end-first_start).normalized().dot((second_end-second_start).normalized()), 1.)) {
// The edges have the same direction and share a point. Merge them.
edges[i==0 ? edges.size()-1 : i-1] = SurfaceFeature(SurfaceFeatureType::Edge, first_start, second_end);
edges.erase(edges.begin() + i);
}
}
// Now move the circles into the feature list.
// Now move the circles and edges into the feature list for the plane.
assert(std::all_of(circles.begin(), circles.end(), [](const SurfaceFeature& f) {
return f.get_type() == SurfaceFeatureType::Circle;
}));
assert(std::all_of(edges.begin(), edges.end(), [](const SurfaceFeature& f) {
return f.get_type() == SurfaceFeatureType::Edge;
}));
plane.surface_features.insert(plane.surface_features.end(), std::make_move_iterator(circles.begin()),
std::make_move_iterator(circles.end()));
std::make_move_iterator(circles.end()));
plane.surface_features.insert(plane.surface_features.end(), std::make_move_iterator(edges.begin()),
std::make_move_iterator(edges.end()));
}
// The last surface feature is the plane itself.