diff --git a/src/libslic3r/Measure.cpp b/src/libslic3r/Measure.cpp index 5b9a75c04..76accc1f3 100644 --- a/src/libslic3r/Measure.cpp +++ b/src/libslic3r/Measure.cpp @@ -16,7 +16,7 @@ static std::pair<Vec3d, double> get_center_and_radius(const std::vector<Vec3d>& { Vec2ds out; double z = 0.; - for (const Vec3d pt : points) { + for (const Vec3d& pt : points) { Vec3d pt_transformed = trafo * pt; z = pt_transformed.z(); out.emplace_back(pt_transformed.x(), pt_transformed.y()); @@ -27,6 +27,14 @@ static std::pair<Vec3d, double> get_center_and_radius(const std::vector<Vec3d>& return std::make_pair(trafo.inverse() * Vec3d(circle.center.x(), circle.center.y(), z), circle.radius); } +static bool circle_fit_is_ok(const std::vector<Vec3d>& pts, const Vec3d& center, double radius) +{ + for (const Vec3d& pt : pts) + if (std::abs((pt - center).norm() - radius) > 0.05) + return false; + return true; +} + static std::array<Vec3d, 3> orthonormal_basis(const Vec3d& v) { std::array<Vec3d, 3> ret; @@ -244,10 +252,7 @@ void MeasuringImpl::update_planes() void MeasuringImpl::extract_features() { - auto are_angles_same = [](double a, double b) { return Slic3r::is_approx(a,b); }; - auto are_lengths_same = [](double a, double b) { return Slic3r::is_approx(a,b); }; - - std::vector<double> angles; + std::vector<double> angles; // placed in outer scope to prevent reallocations std::vector<double> lengths; @@ -265,175 +270,196 @@ void MeasuringImpl::extract_features() if (border.size() <= 1) continue; - // Given an idx into border, return the index that is idx+offset position, - // while taking into account the need for warp-around and the fact that - // the first and last point are the same. - auto offset_to_index = [border_size = int(border.size())](int idx, int offset) -> int { - assert(std::abs(offset) < border_size); - int out = idx+offset; - if (out >= border_size) - out = out - border_size; - else if (out < 0) - out = border_size + out; + bool done = false; - return out; - }; + if (const auto& [center, radius] = get_center_and_radius(border, trafo); + (border.size()>4) && circle_fit_is_ok(border, center, radius)) { + // The whole border is one circle. Just add it into the list of features + // and we are done. - // First calculate angles at all the vertices. - angles.clear(); - lengths.clear(); - int first_different_angle_idx = 0; - for (int i=0; i<int(border.size()); ++i) { - const Vec3d& v2 = border[i] - (i == 0 ? border[border.size()-1] : border[i-1]); - const Vec3d& v1 = (i == int(border.size()-1) ? border[0] : 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; + bool is_polygon = border.size()>4 && border.size()<=8; + bool lengths_match = std::all_of(border.begin()+2, border.end(), [is_polygon](const Vec3d& pt) { + return Slic3r::is_approx((pt - *((&pt)-1)).squaredNorm(), (*((&pt)-1) - *((&pt)-2)).squaredNorm(), is_polygon ? 0.01 : 0.01); + }); - angles.push_back(angle); - lengths.push_back(v2.norm()); - if (first_different_angle_idx == 0 && angles.size() > 1) { - if (! are_angles_same(angles.back(), angles[angles.size()-2])) - first_different_angle_idx = angles.size()-1; + if (lengths_match && (is_polygon || border.size() > 8)) { + if (is_polygon) { + // This is a polygon, add the separate edges with the center. + for (int j=0; j<int(border.size()); ++j) + plane.surface_features.emplace_back(SurfaceFeature(SurfaceFeatureType::Edge, + border[j==0 ? border.size()-1 : j-1], border[j], + std::make_optional(center))); + } else { + // The fit went well and it has more than 8 points - let's consider this a circle. + plane.surface_features.emplace_back(SurfaceFeature(SurfaceFeatureType::Circle, center, plane.normal, std::nullopt, radius)); + } + done = true; } } - 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. - int start_idx = -1; - bool circle = false; - bool first_iter = true; - std::vector<SurfaceFeature> circles; - std::vector<SurfaceFeature> edges; - std::vector<std::pair<int, int>> circles_idxs; - //std::vector<double> circles_lengths; - std::vector<Vec3d> single_circle; // could be in loop-scope, but reallocations - double single_circle_length = 0.; - int first_pt_idx = offset_to_index(first_different_angle_idx, 1); - int i = first_pt_idx; - while (i != first_pt_idx || first_iter) { - if (are_angles_same(angles[i], angles[offset_to_index(i,-1)]) - && i != offset_to_index(first_pt_idx, -1) // not the last point - && i != start_idx ) { - // circle - if (! circle) { - circle = true; - single_circle.clear(); - single_circle_length = 0.; - start_idx = offset_to_index(i, -2); - single_circle = { border[start_idx], border[offset_to_index(start_idx,1)] }; - single_circle_length += lengths[offset_to_index(i, -1)]; + if (! done) { + // In this case, the border is not a circle and may contain circular + // segments. Try to find them and then add all remaining edges as edges. + + auto are_angles_same = [](double a, double b) { return Slic3r::is_approx(a,b,0.01); }; + auto are_lengths_same = [](double a, double b) { return Slic3r::is_approx(a,b,0.01); }; + + + // Given an idx into border, return the index that is idx+offset position, + // while taking into account the need for wrap-around and the fact that + // the first and last point are the same. + auto offset_to_index = [border_size = int(border.size())](int idx, int offset) -> int { + assert(std::abs(offset) < border_size); + int out = idx+offset; + if (out >= border_size) + out = out - border_size; + else if (out < 0) + out = border_size + out; + + return out; + }; + + // First calculate angles at all the vertices. + angles.clear(); + lengths.clear(); + int first_different_angle_idx = 0; + for (int i=0; i<int(border.size()); ++i) { + const Vec3d& v2 = border[i] - (i == 0 ? border[border.size()-1] : border[i-1]); + const Vec3d& v1 = (i == int(border.size()-1) ? border[0] : 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.norm()); + if (first_different_angle_idx == 0 && angles.size() > 1) { + if (! are_angles_same(angles.back(), angles[angles.size()-2])) + first_different_angle_idx = angles.size()-1; } - single_circle.emplace_back(border[i]); - single_circle_length += lengths[i]; - } else { - if (circle && single_circle.size() >= 5) { // Less than 5 vertices? Not a circle. + } + 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. + int start_idx = -1; + bool circle = false; + bool first_iter = true; + std::vector<SurfaceFeature> circles; + std::vector<SurfaceFeature> edges; + std::vector<std::pair<int, int>> circles_idxs; + //std::vector<double> circles_lengths; + std::vector<Vec3d> single_circle; // could be in loop-scope, but reallocations + double single_circle_length = 0.; + int first_pt_idx = offset_to_index(first_different_angle_idx, 1); + int i = first_pt_idx; + while (i != first_pt_idx || first_iter) { + if (are_angles_same(angles[i], angles[offset_to_index(i,-1)]) + && i != offset_to_index(first_pt_idx, -1) // not the last point + && i != start_idx ) { + // circle + if (! circle) { + circle = true; + single_circle.clear(); + single_circle_length = 0.; + start_idx = offset_to_index(i, -2); + single_circle = { border[start_idx], border[offset_to_index(start_idx,1)] }; + single_circle_length += lengths[offset_to_index(i, -1)]; + } single_circle.emplace_back(border[i]); single_circle_length += lengths[i]; + } else { + if (circle && single_circle.size() >= 5) { // Less than 5 vertices? Not a circle. + single_circle.emplace_back(border[i]); + single_circle_length += lengths[i]; - bool accept_circle = true; - { - // Check that lengths of internal (!!!) edges match. - int j = offset_to_index(start_idx, 3); - while (j != i) { - if (! are_lengths_same(lengths[offset_to_index(j,-1)], lengths[j])) { - accept_circle = false; - break; - } - j = offset_to_index(j, 1); - } - } - - if (accept_circle) { - const auto& [center, radius] = get_center_and_radius(single_circle, trafo); - - // Check that the fit went well. The tolerance is high, only to - // reject complete failures. - for (const Vec3d& pt : single_circle) { - if (std::abs((pt - center).norm() - radius) > 0.5) { - accept_circle = false; - break; + bool accept_circle = true; + { + // Check that lengths of internal (!!!) edges match. + int j = offset_to_index(start_idx, 3); + while (j != i) { + if (! are_lengths_same(lengths[offset_to_index(j,-1)], lengths[j])) { + accept_circle = false; + break; + } + j = offset_to_index(j, 1); } } - // If the segment subtends less than 90 degrees, throw it away. - accept_circle &= single_circle_length / radius > 0.9*M_PI/2.; - - // If this is all-around and 5 to 8 vertices, consider it a polygon. - bool is_polygon = start_idx == i && single_circle.size() <= 9 && single_circle.size() >= 6; - if (accept_circle) { - // Add the circle and remember indices into borders. - circles_idxs.emplace_back(start_idx, i); - if (is_polygon) { - for (int j=0; j<=i; ++j) // No wrap-around handling needed here. - edges.emplace_back(SurfaceFeature(SurfaceFeatureType::Edge, - border[j==0 ? border.size()-1 : j-1], border[j], - std::make_optional(center))); - } - else + const auto& [center, radius] = get_center_and_radius(single_circle, trafo); + + // Check that the fit went well. The tolerance is high, only to + // reject complete failures. + accept_circle &= circle_fit_is_ok(single_circle, center, radius); + + // If the segment subtends less than 90 degrees, throw it away. + accept_circle &= single_circle_length / radius > 0.9*M_PI/2.; + + if (accept_circle) { + // 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; } - circle = false; - } - // Take care of the wrap around. - first_iter = false; - i = offset_to_index(i, 1); - } - - // We have the circles. Now go around again and pick edges, while jumping over circles. - if (circles_idxs.empty()) { - // Just add all edges. - for (int i=1; i<int(border.size()); ++i) - edges.emplace_back(SurfaceFeature(SurfaceFeatureType::Edge, border[i-1], border[i])); - edges.emplace_back(SurfaceFeature(SurfaceFeatureType::Edge, border[0], border[border.size()-1])); - } else { - // There is at least one circular segment. Start at its end and add edges until the start of the next one. - int i = circles_idxs.front().second; - int circle_idx = 1; - while (true) { + // Take care of the wrap around. + first_iter = false; i = offset_to_index(i, 1); - edges.emplace_back(SurfaceFeature(SurfaceFeatureType::Edge, border[offset_to_index(i,-1)], border[i])); - if (circle_idx < int(circles_idxs.size()) && i == circles_idxs[circle_idx].first) { - i = circles_idxs[circle_idx].second; - ++circle_idx; + } + + // We have the circles. Now go around again and pick edges, while jumping over circles. + if (circles_idxs.empty()) { + // Just add all edges. + for (int i=1; i<int(border.size()); ++i) + edges.emplace_back(SurfaceFeature(SurfaceFeatureType::Edge, border[i-1], border[i])); + edges.emplace_back(SurfaceFeature(SurfaceFeatureType::Edge, border[0], border[border.size()-1])); + } else if (circles_idxs.size() > 1 || circles_idxs.front().first != circles_idxs.front().second) { + // There is at least one circular segment. Start at its end and add edges until the start of the next one. + int i = circles_idxs.front().second; + int circle_idx = 1; + while (true) { + i = offset_to_index(i, 1); + edges.emplace_back(SurfaceFeature(SurfaceFeatureType::Edge, border[offset_to_index(i,-1)], border[i])); + if (circle_idx < int(circles_idxs.size()) && i == circles_idxs[circle_idx].first) { + i = circles_idxs[circle_idx].second; + ++circle_idx; + } + if (i == circles_idxs.front().first) + break; } - if (i == circles_idxs.front().first) - break; } - } - // 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(); + // 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(); - 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); + 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 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())); - plane.surface_features.insert(plane.surface_features.end(), std::make_move_iterator(edges.begin()), - std::make_move_iterator(edges.end())); + // 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())); + 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.