From 4b9630c23b7c46d3f7cfdb26837779ee690cc46a Mon Sep 17 00:00:00 2001 From: Lukas Matena Date: Thu, 3 Nov 2022 15:10:47 +0100 Subject: [PATCH 1/3] Measurement: Circles filtering (part 1) --- src/libslic3r/Measure.cpp | 36 +++++++++++++++++++++++++++--------- 1 file changed, 27 insertions(+), 9 deletions(-) diff --git a/src/libslic3r/Measure.cpp b/src/libslic3r/Measure.cpp index ef685b15d..b309fc56d 100644 --- a/src/libslic3r/Measure.cpp +++ b/src/libslic3r/Measure.cpp @@ -196,14 +196,17 @@ void MeasuringImpl::update_planes() if (last_border.size() == 1) m_planes[plane_id].borders.pop_back(); + else { + assert(m_planes[plane_id].borders.front() == m_planes[plane_id].borders.back()); + } } } - continue; // There was no failure. PLANE_FAILURE: m_planes[plane_id].borders.clear(); } + } @@ -233,15 +236,16 @@ void MeasuringImpl::extract_features() for (const std::vector& border : plane.borders) { if (border.size() <= 1) continue; + assert(border.front() == border.back()); int start_idx = -1; // First calculate angles at all the vertices. angles.clear(); lengths.clear(); - for (int i=0; i M_PI) @@ -268,10 +272,24 @@ 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); - circles_idxs.emplace_back(start_idx, i); - circles.emplace_back(SurfaceFeature(SurfaceFeatureType::Circle, center, plane.normal, std::nullopt, radius)); + // Anything made up of less than 4 points shall not be considered a circle. + if (i-start_idx > 3) { + // This may be just a partial circle, maybe a small one generated by two short edges. + // To qualify as a circle, it should cover at least one quadrant. Check this now. + const auto& [center, radius] = get_center_and_radius(border, start_idx, i, trafo); + const Vec3d& p1 = border[start_idx]; + const Vec3d& p2 = border[i]; + const Vec3d& p3 = border[size_t(start_idx+i)/2]; + + // Measure angle between the first and the second radiuses. If smaller than 90 deg, measure angle + // between first radius and one corresponding to the middle of the circle. + if ((p1-center).normalized().dot((p2-center).normalized()) < 0.05 // exactly 90 deg might get rejected + || (p1-center).normalized().dot((p3-center).normalized()) < 0.) { + // 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)); + } + } circle = false; } } From d07537c1f047a23082ead5bec131757b0e6598e1 Mon Sep 17 00:00:00 2001 From: Lukas Matena Date: Thu, 3 Nov 2022 17:57:09 +0100 Subject: [PATCH 2/3] Measurement: Merge adjacent edges --- src/libslic3r/Measure.cpp | 38 ++++++++++++++++++++++++++++++-------- 1 file changed, 30 insertions(+), 8 deletions(-) diff --git a/src/libslic3r/Measure.cpp b/src/libslic3r/Measure.cpp index b309fc56d..965fad9f9 100644 --- a/src/libslic3r/Measure.cpp +++ b/src/libslic3r/Measure.cpp @@ -206,7 +206,6 @@ void MeasuringImpl::update_planes() PLANE_FAILURE: m_planes[plane_id].borders.clear(); } - } @@ -261,6 +260,7 @@ void MeasuringImpl::extract_features() bool circle = false; std::vector circles; std::vector> circles_idxs; + //std::vector angle_and_vert_num_per_circle; 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]) @@ -288,12 +288,24 @@ void MeasuringImpl::extract_features() // 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)); + //angle_and_vert_num_per_circle.emplace_back(..., ...); } } circle = false; } } } +/* + // At this point we must merge the first and last circles. The dicrimination of too small + // circles will follow, so we need a complete picture before that. + assert(circles.size() == angle_and_vert_num_per_circle.size()); + for (size_t i=0; i=0; --i) { + const auto& [first_start, first_end] = plane.surface_features[i==0 ? plane.surface_features.size()-1 : i-1].get_edge(); + const auto& [second_start, second_end] = plane.surface_features[i].get_edge(); + + 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. + plane.surface_features[i==0 ? plane.surface_features.size()-1 : i-1] = SurfaceFeature(SurfaceFeatureType::Edge, first_start, second_end); + plane.surface_features.erase(plane.surface_features.begin() + 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.) - // FIXME Check and merge first and last circle if needed. - // Now move the circles into the feature list. assert(std::all_of(circles.begin(), circles.end(), [](const SurfaceFeature& f) { return f.get_type() == SurfaceFeatureType::Circle; From 0c88b5712a0ab67c784c9a8ab4814959f049326c Mon Sep 17 00:00:00 2001 From: Lukas Matena Date: Fri, 4 Nov 2022 09:05:12 +0100 Subject: [PATCH 3/3] Measurement: Circles filtering (part 2) --- src/libslic3r/Measure.cpp | 187 ++++++++++++++++++++++++-------------- 1 file changed, 120 insertions(+), 67 deletions(-) diff --git a/src/libslic3r/Measure.cpp b/src/libslic3r/Measure.cpp index 965fad9f9..f15be5bea 100644 --- a/src/libslic3r/Measure.cpp +++ b/src/libslic3r/Measure.cpp @@ -197,7 +197,7 @@ void MeasuringImpl::update_planes() if (last_border.size() == 1) m_planes[plane_id].borders.pop_back(); else { - assert(m_planes[plane_id].borders.front() == m_planes[plane_id].borders.back()); + assert(last_border.front() == last_border.back()); } } } @@ -215,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 angles; std::vector lengths; @@ -238,6 +235,8 @@ void MeasuringImpl::extract_features() assert(border.front() == border.back()); int start_idx = -1; + std::vector edges; + // First calculate angles at all the vertices. angles.clear(); lengths.clear(); @@ -251,16 +250,19 @@ void MeasuringImpl::extract_features() 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 circles; - std::vector> circles_idxs; - //std::vector angle_and_vert_num_per_circle; + std::vector> circles_idxs; + std::vector 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]) @@ -272,98 +274,149 @@ void MeasuringImpl::extract_features() } } else { if (circle) { - // Anything made up of less than 4 points shall not be considered a circle. - if (i-start_idx > 3) { - // This may be just a partial circle, maybe a small one generated by two short edges. - // To qualify as a circle, it should cover at least one quadrant. Check this now. - const auto& [center, radius] = get_center_and_radius(border, start_idx, i, trafo); - const Vec3d& p1 = border[start_idx]; - const Vec3d& p2 = border[i]; - const Vec3d& p3 = border[size_t(start_idx+i)/2]; - - // Measure angle between the first and the second radiuses. If smaller than 90 deg, measure angle - // between first radius and one corresponding to the middle of the circle. - if ((p1-center).normalized().dot((p2-center).normalized()) < 0.05 // exactly 90 deg might get rejected - || (p1-center).normalized().dot((p3-center).normalized()) < 0.) { - // 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)); - //angle_and_vert_num_per_circle.emplace_back(..., ...); - } - } + 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; } } } -/* - // At this point we must merge the first and last circles. The dicrimination of too small - // circles will follow, so we need a complete picture before that. - assert(circles.size() == angle_and_vert_num_per_circle.size()); - for (size_t i=0; i 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 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(); + } + } - // 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: + // Now throw away all circles that subtend less than 90 deg. + assert(circles.size() == circles_lengths.size()); + for (int i=0; i(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); } - circles.erase(circles.begin() + i); } } - // We have the circles. Now go around again and pick edges. - int cidx = 0; // index of next circle in the way + // Anything under 5 vertices shall not be considered a circle. + assert(circles_idxs.size() == circles.size()); + for (int i=0; i= 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, 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)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])); } // Merge adjacent edges where needed. - assert(std::all_of(plane.surface_features.begin(), plane.surface_features.end(), + assert(std::all_of(edges.begin(), edges.end(), [](const SurfaceFeature& f) { return f.get_type() == SurfaceFeatureType::Edge; })); - for (int i=plane.surface_features.size()-1; i>=0; --i) { - const auto& [first_start, first_end] = plane.surface_features[i==0 ? plane.surface_features.size()-1 : i-1].get_edge(); - const auto& [second_start, second_end] = plane.surface_features[i].get_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(); 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. - plane.surface_features[i==0 ? plane.surface_features.size()-1 : i-1] = SurfaceFeature(SurfaceFeatureType::Edge, first_start, second_end); - plane.surface_features.erase(plane.surface_features.begin() + i); + edges[i==0 ? edges.size()-1 : i-1] = SurfaceFeature(SurfaceFeatureType::Edge, first_start, second_end); + edges.erase(edges.begin() + 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.) - - // 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.