diff --git a/src/libslic3r/GCode.cpp b/src/libslic3r/GCode.cpp index e1d4294887..689897bc76 100644 --- a/src/libslic3r/GCode.cpp +++ b/src/libslic3r/GCode.cpp @@ -2586,7 +2586,7 @@ std::string GCode::extrude_loop(ExtrusionLoop loop, std::string description, dou loop.split_at(last_pos, false); } else - m_seam_placer.place_seam(m_layer, loop, m_config.external_perimeters_first); + m_seam_placer.place_seam(m_layer, loop, m_config.external_perimeters_first, this->last_pos()); // clip the path to avoid the extruder to get exactly on the first point of the loop; // if polyline was shorter than the clipping distance we'd get a null polyline, so diff --git a/src/libslic3r/GCode/SeamPlacerNG.cpp b/src/libslic3r/GCode/SeamPlacerNG.cpp index 5037916b2d..8ab76c9765 100644 --- a/src/libslic3r/GCode/SeamPlacerNG.cpp +++ b/src/libslic3r/GCode/SeamPlacerNG.cpp @@ -8,6 +8,8 @@ #include #include +#include "Subdivide.hpp" + //For polynomial fitting #include #include @@ -24,7 +26,6 @@ #define DEBUG_FILES #ifdef DEBUG_FILES -#include "Subdivide.hpp" #include #include #endif @@ -200,8 +201,7 @@ std::vector raycast_visibility(const AABBTreeIndirect::Tree< for (size_t face_index = r.begin(); face_index < r.end(); ++face_index) { FaceVisibilityInfo &dest = result[face_index]; dest.visibility = 1.0f; - constexpr float decrease = 1.0f / (SeamPlacer::sqr_rays_per_triangle * SeamPlacer::sqr_rays_per_triangle); - + constexpr float decrease = 1.0f / (SeamPlacer::sqr_rays_per_triangle * SeamPlacer::sqr_rays_per_triangle); Vec3i face = triangles.indices[face_index]; Vec3f A = triangles.vertices[face.x()]; @@ -218,7 +218,7 @@ std::vector raycast_visibility(const AABBTreeIndirect::Tree< igl::Hit hitpoint; // FIXME: This AABBTTreeIndirect query will not compile for float ray origin and // direction. - Vec3d ray_origin_d = (center+0.1*normal).cast(); + Vec3d ray_origin_d = (center + normal).cast(); // start one mm above surface. Vec3d final_ray_dir_d = final_ray_dir.cast(); bool hit = AABBTreeIndirect::intersect_ray_first_hit(triangles.vertices, triangles.indices, raycasting_tree, ray_origin_d, final_ray_dir_d, hitpoint); @@ -312,7 +312,7 @@ struct GlobalModelInfo { if (enforcers.empty()) { return false; } - float radius_sqr = radius*radius; + float radius_sqr = radius * radius; return AABBTreeIndirect::is_any_triangle_in_radius(enforcers.vertices, enforcers.indices, enforcers_tree, position, radius_sqr); } @@ -321,7 +321,7 @@ struct GlobalModelInfo { if (blockers.empty()) { return false; } - float radius_sqr = radius*radius; + float radius_sqr = radius * radius; return AABBTreeIndirect::is_any_triangle_in_radius(blockers.vertices, blockers.indices, blockers_tree, position, radius_sqr); } @@ -535,27 +535,6 @@ float calculate_overhang(const SeamCandidate &point, const SeamCandidate &under_ return ((p - b).norm() + dist_ab + dist_bc) / 3.0; } -// Pick best seam point based on the given comparator -template -void pick_seam_point(std::vector &perimeter_points, size_t start_index, - const Comparator &comparator) { - size_t end_index = perimeter_points[start_index].perimeter->end_index; - - std::vector indices(end_index + 1 - start_index); - for (size_t index = start_index; index <= end_index; ++index) { - indices[index - start_index] = index; - } - - size_t seam_index = indices[0]; - for (size_t index : indices) { - if (comparator.is_first_better(perimeter_points[index], perimeter_points[seam_index])) { - seam_index = index; - } - } - - perimeter_points[start_index].perimeter->seam_index = seam_index; -} - // Computes all global model info - transforms object, performs raycasting, // stores enforces and blockers void compute_global_occlusion(GlobalModelInfo &result, const PrintObject *po) { @@ -563,8 +542,8 @@ void compute_global_occlusion(GlobalModelInfo &result, const PrintObject *po) { << "SeamPlacer: build AABB tree for raycasting and gather occlusion info: start"; // Build AABB tree for raycasting auto obj_transform = po->trafo(); - auto triangle_set = po->model_object()->raw_indexed_triangle_set(); - //add model parts + indexed_triangle_set triangle_set; + //add all parts for (const ModelVolume *model_volume : po->model_object()->volumes) { if (model_volume->type() == ModelVolumeType::MODEL_PART) { auto model_transformation = model_volume->get_matrix(); @@ -573,6 +552,7 @@ void compute_global_occlusion(GlobalModelInfo &result, const PrintObject *po) { its_merge(triangle_set, model_its); } } + float target_error = SeamPlacer::raycasting_decimation_target_error; its_quadric_edge_collapse(triangle_set, 0, &target_error, nullptr, nullptr); triangle_set = subdivide(triangle_set, SeamPlacer::raycasting_subdivision_target_length); @@ -634,12 +614,19 @@ struct SeamComparator { } float compute_angle_penalty(float ccw_angle) const { - return gauss(ccw_angle, 0.2f, 1.0f, 4.0f); + // This function is used: + // ((ℯ^(((1)/(x^(2)*3+1)))-1)/(ℯ-1))*1+((1)/(2+ℯ^(-x))) + // looks terribly, but it is gaussian combined with sigmoid, + // so that concave points have much smaller penalty over convex ones + + return gauss(ccw_angle, 0.0f, 1.0f, 3.0f) + + 1.0f / (2 + std::exp(-ccw_angle)); // sigmoid, which heavily favourizes concave angles } // Standard comparator, must respect the requirements of comparators (e.g. give same result on same inputs) for sorting usage // should return if a is better seamCandidate than b - bool is_first_better(const SeamCandidate &a, const SeamCandidate &b) const { + bool is_first_better(const SeamCandidate &a, const SeamCandidate &b, const Vec2f &preffered_location = Vec2f { 0.0f, + 0.0f }) const { // Blockers/Enforcers discrimination, top priority if (a.type > b.type) { return true; @@ -649,7 +636,7 @@ struct SeamComparator { } //avoid overhangs - if (a.overhang > 0.3f && b.overhang < a.overhang) { + if (a.overhang > 0.1f && b.overhang < a.overhang) { return false; } @@ -657,8 +644,18 @@ struct SeamComparator { return a.position.y() > b.position.y(); } - return (a.visibility + 1.0f) * compute_angle_penalty(a.local_ccw_angle) < - (b.visibility + 1.0f) * compute_angle_penalty(b.local_ccw_angle); + float distance_penalty_a = 1.0f; + float distance_penalty_b = 1.0f; + if (setup == spNearest) { + distance_penalty_a = 1.1f - gauss((a.position.head<2>() - preffered_location).norm(), 0.0f, 1.0f, 0.001f); + distance_penalty_b = 1.1f - gauss((a.position.head<2>() - preffered_location).norm(), 0.0f, 1.0f, 0.001f); + } + + //ranges: [0 - 1] (0 - 1.3] [0.1 - 1.1) ; total range: (0 - 2.1] + float penalty_a = (a.visibility + 0.5f) * compute_angle_penalty(a.local_ccw_angle) * distance_penalty_a; + float penalty_b = (b.visibility + 0.5f) * compute_angle_penalty(b.local_ccw_angle) * distance_penalty_b; + + return penalty_a < penalty_b; } // Comparator used during alignment. If there is close potential aligned point, it is comapred to the current @@ -673,25 +670,32 @@ struct SeamComparator { } //avoid overhangs - if (a.overhang > 0.3f && b.overhang < a.overhang) { + if (a.overhang > 0.1f && b.overhang < a.overhang) { return false; } + if (setup == SeamPosition::spRandom) { + return true; + } + if (setup == SeamPosition::spRear) { return a.position.y() > b.position.y(); } - return (a.visibility + 1.0f) * compute_angle_penalty(a.local_ccw_angle) * 0.5f <= - (b.visibility + 1.0f) * compute_angle_penalty(b.local_ccw_angle); + //ranges: [0 - 1] (0 - 1.3] ; total range: (0 - 1.95]; + float penalty_a = (a.visibility + 0.5f) * compute_angle_penalty(a.local_ccw_angle); + float penalty_b = (b.visibility + 0.5f) * compute_angle_penalty(b.local_ccw_angle); + + return penalty_a <= penalty_b || std::abs(penalty_a - penalty_b) < SeamPlacer::seam_align_score_tolerance; } - //returns negative value of penalties, should be nromalized against others in the same perimeter for use - float get_weight(const SeamCandidate &a) const { + //always nonzero, positive + float get_penalty(const SeamCandidate &a) const { if (setup == SeamPosition::spRear) { return a.position.y(); } - return -(a.visibility + 1.0f) * compute_angle_penalty(a.local_ccw_angle); + return (a.visibility + 0.5f) * compute_angle_penalty(a.local_ccw_angle); } } ; @@ -715,8 +719,8 @@ void debug_export_points(const std::vector())), visibility_fill); - Vec3i weight_color = value_rgbi(min_weight, max_weight, comparator.get_weight(point)); + Vec3i weight_color = value_rgbi(min_weight, max_weight, comparator.get_penalty(point)); std::string weight_fill = "rgb(" + std::to_string(weight_color.x()) + "," + std::to_string(weight_color.y()) + "," + std::to_string(weight_color.z()) + ")"; @@ -738,6 +742,106 @@ void debug_export_points(const std::vector &perimeter_points, size_t start_index, + const SeamComparator &comparator) { + size_t end_index = perimeter_points[start_index].perimeter->end_index; + + size_t seam_index = start_index; + for (size_t index = start_index; index <= end_index; ++index) { + if (comparator.is_first_better(perimeter_points[index], perimeter_points[seam_index])) { + seam_index = index; + } + } + perimeter_points[start_index].perimeter->seam_index = seam_index; +} + +size_t pick_nearest_seam_point_index(const std::vector &perimeter_points, size_t start_index, + const Vec2f &preffered_location) { + size_t end_index = perimeter_points[start_index].perimeter->end_index; + SeamComparator comparator { spNearest }; + + size_t seam_index = start_index; + for (size_t index = start_index; index <= end_index; ++index) { + if (comparator.is_first_better(perimeter_points[index], perimeter_points[seam_index], preffered_location)) { + seam_index = index; + } + } + return seam_index; +} + +// picks random seam point uniformly, respecting enforcers blockers and overhang avoidance. +void pick_random_seam_point(std::vector &perimeter_points, size_t start_index) { + SeamComparator comparator { spRandom }; + + // algorithm keeps a list of viable points and their lengths. If it finds a point + // that is much better than the viable_example_index (e.g. better type, no overhang; see is_first_not_much_worse) + // then it throws away stored lists and starts from start + // in the end, he list should contain points with same type (Enforced > Neutral > Blocked) and also only those which are not + // big overhang. + size_t viable_example_index = start_index; + size_t end_index = perimeter_points[start_index].perimeter->end_index; + std::vector viable_indices; + std::vector viable_edges_lengths; + std::vector viable_edges; + + for (size_t index = start_index; index <= end_index; ++index) { + if (comparator.is_first_not_much_worse(perimeter_points[index], perimeter_points[viable_example_index]) && + comparator.is_first_not_much_worse(perimeter_points[viable_example_index], perimeter_points[index])) { + // index ok, push info into respective vectors + Vec3f edge_to_next; + if (index == end_index) { + edge_to_next = (perimeter_points[start_index].position - perimeter_points[index].position); + } else + { + edge_to_next = (perimeter_points[index + 1].position - perimeter_points[index].position); + } + float dist_to_next = edge_to_next.norm(); + viable_indices.push_back(index); + viable_edges_lengths.push_back(dist_to_next); + viable_edges.push_back(edge_to_next); + } else if (comparator.is_first_not_much_worse(perimeter_points[viable_example_index], + perimeter_points[index])) { + // index is worse then viable_example_index, skip this point + } else { + // index is better than viable example index, update example, clear gathered info, start again + // clear up all gathered info, start from scratch, update example index + viable_example_index = index; + viable_indices.clear(); + viable_edges_lengths.clear(); + viable_edges.clear(); + + Vec3f edge_to_next; + if (index == end_index) { + edge_to_next = (perimeter_points[start_index].position - perimeter_points[index].position); + } else { + edge_to_next = (perimeter_points[index + 1].position - perimeter_points[index].position); + } + float dist_to_next = edge_to_next.norm(); + viable_indices.push_back(index); + viable_edges_lengths.push_back(dist_to_next); + viable_edges.push_back(edge_to_next); + } + } + + // now pick random point from the stored options + float len_sum = std::accumulate(viable_edges_lengths.begin(), viable_edges_lengths.end(), 0.0f); + float picked_len = len_sum * (rand() / (float(RAND_MAX) + 1)); + + size_t point_idx = 0; + while (picked_len - viable_edges_lengths[point_idx] > 0) { + picked_len = picked_len - viable_edges_lengths[point_idx]; + point_idx++; + } + + Perimeter *perimeter = perimeter_points[start_index].perimeter.get(); + perimeter->seam_index = viable_indices[point_idx]; + perimeter->final_seam_position = perimeter_points[perimeter->seam_index].position + + viable_edges[point_idx].normalized() * picked_len; + perimeter->finalized = true; + +} + } // namespace SeamPlacerImpl // Parallel process and extract each perimeter polygon of the given print object. @@ -753,7 +857,8 @@ void SeamPlacer::gather_seam_candidates(const PrintObject *po, tbb::parallel_for(tbb::blocked_range(0, po->layers().size()), [&](tbb::blocked_range r) { for (size_t layer_idx = r.begin(); layer_idx < r.end(); ++layer_idx) { - std::vector &layer_candidates = m_perimeter_points_per_object[po][layer_idx]; + std::vector &layer_candidates = + m_perimeter_points_per_object[po][layer_idx]; const Layer *layer = po->get_layer(layer_idx); auto unscaled_z = layer->slice_z; Polygons polygons = extract_perimeter_polygons(layer); @@ -762,8 +867,9 @@ void SeamPlacer::gather_seam_candidates(const PrintObject *po, global_model_info); } auto functor = SeamCandidateCoordinateFunctor { &layer_candidates }; - m_perimeter_points_trees_per_object[po][layer_idx] = std::make_unique( - functor, layer_candidates.size()); + m_perimeter_points_trees_per_object[po][layer_idx] = std::make_unique( + functor, layer_candidates.size()); } } ); @@ -823,10 +929,9 @@ void SeamPlacer::calculate_overhangs(const PrintObject *po) { // If the closest point is good enough to replace current chosen seam, it is stored in potential_string_seams, returns true and updates last_point_pos // Otherwise does nothing, returns false // sadly cannot be const because map access operator[] is not const, since it can create new object -template bool SeamPlacer::find_next_seam_in_layer(const PrintObject *po, std::pair &last_point_indexes, - size_t layer_idx, const Comparator &comparator, + size_t layer_idx, const SeamPlacerImpl::SeamComparator &comparator, std::vector> &seam_string, std::vector> &potential_string_seams) { using namespace SeamPlacerImpl; @@ -842,7 +947,7 @@ bool SeamPlacer::find_next_seam_in_layer(const PrintObject *po, SeamCandidate &closest_point = m_perimeter_points_per_object[po][layer_idx][closest_point_index]; - if (closest_point.perimeter->aligned) { //already aligned, skip + if (closest_point.perimeter->finalized) { //already finalized, skip return false; } @@ -878,8 +983,7 @@ bool SeamPlacer::find_next_seam_in_layer(const PrintObject *po, // Does not change the positions of the SeamCandidates themselves, instead stores // the new aligned position into the shared Perimeter structure of each perimeter // Note that this position does not necesarilly lay on the perimeter. -template -void SeamPlacer::align_seam_points(const PrintObject *po, const Comparator &comparator) { +void SeamPlacer::align_seam_points(const PrintObject *po, const SeamPlacerImpl::SeamComparator &comparator) { using namespace SeamPlacerImpl; // Prepares Debug files for writing. @@ -901,7 +1005,7 @@ void SeamPlacer::align_seam_points(const PrintObject *po, const Comparator &comp } #endif - //gahter vector of all seams on the print_object - pair of layer_index and seam__index within that layer + //gather vector of all seams on the print_object - pair of layer_index and seam__index within that layer std::vector> seams; for (size_t layer_idx = 0; layer_idx < m_perimeter_points_per_object[po].size(); ++layer_idx) { std::vector &layer_perimeter_points = @@ -921,13 +1025,13 @@ void SeamPlacer::align_seam_points(const PrintObject *po, const Comparator &comp } ); - //align the sema points - start with the best, and check if they are aligned, if yes, skip, else start alignment + //align the seam points - start with the best, and check if they are aligned, if yes, skip, else start alignment for (const std::pair &seam : seams) { size_t layer_idx = seam.first; size_t seam_index = seam.second; std::vector &layer_perimeter_points = m_perimeter_points_per_object[po][layer_idx]; - if (layer_perimeter_points[seam_index].perimeter->aligned) { + if (layer_perimeter_points[seam_index].perimeter->finalized) { // This perimeter is already aligned, skip seam continue; } else { @@ -937,7 +1041,7 @@ void SeamPlacer::align_seam_points(const PrintObject *po, const Comparator &comp int next_layer = layer_idx + 1; std::pair last_point_indexes = std::pair(layer_idx, seam_index); - std::vector> seam_string; + std::vector> seam_string { std::pair(layer_idx, seam_index) }; std::vector> potential_string_seams; //find seams or potential seams in forward direction; there is a budget of skips allowed @@ -969,12 +1073,12 @@ void SeamPlacer::align_seam_points(const PrintObject *po, const Comparator &comp } if (seam_string.size() + potential_string_seams.size() < seam_align_minimum_string_seams) { - //string long enough to be worth aligning, skip + //string NOT long enough to be worth aligning, skip continue; } // String is long engouh, all string seams and potential string seams gathered, now do the alignment - // first merge potential_string_seams and seam_string; from now on, they all will be aligned + // first merge potential_string_seams and seam_string seam_string.insert(seam_string.end(), potential_string_seams.begin(), potential_string_seams.end()); //sort by layer index std::sort(seam_string.begin(), seam_string.end(), @@ -982,62 +1086,101 @@ void SeamPlacer::align_seam_points(const PrintObject *po, const Comparator &comp return left.first < right.first; }); - // gather all positions of seams and their weights + // gather all positions of seams and their weights (weights are derived as negative penalty, they are made positive in next step) std::vector points(seam_string.size()); std::vector weights(seam_string.size()); - float min_weight = comparator.get_weight( + + //init min_weight by the first point + float min_weight = -comparator.get_penalty( m_perimeter_points_per_object[po][seam_string[0].first][seam_string[0].second]); + // In the sorted seam_string array, point which started the alignment - the best candidate + size_t best_candidate_point_index = 0; + + //gather points positions and weights, update min_weight in each step, and find the best candidate for (size_t index = 0; index < seam_string.size(); ++index) { points[index] = m_perimeter_points_per_object[po][seam_string[index].first][seam_string[index].second].position; - weights[index] = comparator.get_weight( + weights[index] = -comparator.get_penalty( m_perimeter_points_per_object[po][seam_string[index].first][seam_string[index].second]); min_weight = std::min(min_weight, weights[index]); + // find the best candidate by comparing the layer indexes + if (seam_string[index].first == layer_idx) { + best_candidate_point_index = index; + } } + //makes all weights positive for (float &w : weights) { - w = w - min_weight + 1.0; //makes all weights positive, nonzero + w = w - min_weight + 0.01; } - // find coefficients of polynomial fit. Z coord is treated as parameter along which to fit both X and Y coords. - std::vector 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 - // Perimeter structure of the point; also set flag aligned to true - for (const auto &pair : seam_string) { - float current_height = m_perimeter_points_per_object[po][pair.first][pair.second].position.z(); - Vec3f seam_pos = get_fitted_point(coefficients, current_height); - - Perimeter *perimeter = - m_perimeter_points_per_object[po][pair.first][pair.second].perimeter.get(); - perimeter->final_seam_position = seam_pos; - perimeter->aligned = true; - } - - for (Vec3f &p : points) { - p = get_fitted_point(coefficients, p.z()); - } - -// for (size_t iteration = 0; iteration < 20; ++iteration) { + //NOTE: the following commented block does polynomial line fitting of the seam string. + // pre-smoothen by Laplace +// for (size_t iteration = 0; iteration < SeamPlacer::seam_align_laplace_smoothing_iterations; ++iteration) { // std::vector new_points(seam_string.size()); // for (int point_index = 0; point_index < points.size(); ++point_index) { // size_t prev_idx = point_index > 0 ? point_index - 1 : point_index; // size_t next_idx = point_index < points.size() - 1 ? point_index + 1 : point_index; // // new_points[point_index] = (points[prev_idx] * weights[prev_idx] -// +points[point_index] * weights[point_index]+ points[next_idx] * weights[next_idx]) / -// (weights[prev_idx] + weights[point_index]+ weights[next_idx]); +// + points[point_index] * weights[point_index] + points[next_idx] * weights[next_idx]) / +// (weights[prev_idx] + weights[point_index] + weights[next_idx]); // } // points = new_points; // } // -// for (size_t index = 0; index < seam_string.size(); ++index) { +// // find coefficients of polynomial fit. Z coord is treated as parameter along which to fit both X and Y coords. +// std::vector 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 +// // Perimeter structure of the point; also set flag aligned to true +// for (const auto &pair : seam_string) { +// float current_height = m_perimeter_points_per_object[po][pair.first][pair.second].position.z(); +// Vec3f seam_pos = get_fitted_point(coefficients, current_height); +// // 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; +// m_perimeter_points_per_object[po][pair.first][pair.second].perimeter.get(); +// perimeter->final_seam_position = seam_pos; +// perimeter->finalized = true; // } +// +// for (Vec3f &p : points) { +// p = get_fitted_point(coefficients, p.z()); +// } + + // LaPlace smoothing iterations over the gathered points. New positions from each iteration are stored in the new_points vector + // and assigned to points at the end of iteration + for (size_t iteration = 0; iteration < SeamPlacer::seam_align_laplace_smoothing_iterations; ++iteration) { + std::vector new_points(seam_string.size()); + // start from the best candidate, and smoothen down + for (int point_index = best_candidate_point_index; point_index >= 0; --point_index) { + int prev_idx = point_index > 0 ? point_index - 1 : point_index; + size_t next_idx = point_index < int(points.size()) - 1 ? point_index + 1 : point_index; + + new_points[point_index] = (points[prev_idx] * weights[prev_idx] + + points[point_index] * weights[point_index] + points[next_idx] * weights[next_idx]) / + (weights[prev_idx] + weights[point_index] + weights[next_idx]); + } + // smoothen up the rest of the points + for (size_t point_index = best_candidate_point_index + 1; point_index < points.size(); ++point_index) { + size_t prev_idx = point_index > 0 ? point_index - 1 : point_index; + size_t next_idx = point_index < points.size() - 1 ? point_index + 1 : point_index; + + new_points[point_index] = (points[prev_idx] * weights[prev_idx] + + points[point_index] * weights[point_index] + points[next_idx] * weights[next_idx]) / + (weights[prev_idx] + weights[point_index] + weights[next_idx]); + } + points = new_points; + } + + // Assign smoothened posiiton to each participating perimeter and set finalized flag + 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->finalized = true; + } #ifdef DEBUG_FILES auto randf = []() { @@ -1085,7 +1228,7 @@ void SeamPlacer::init(const Print &print) { GlobalModelInfo global_model_info { }; gather_enforcers_blockers(global_model_info, po); - if (configured_seam_preference == spNearest || configured_seam_preference == spRandom) { + if (configured_seam_preference == spAligned || configured_seam_preference == spNearest) { compute_global_occlusion(global_model_info, po); } @@ -1095,7 +1238,7 @@ void SeamPlacer::init(const Print &print) { BOOST_LOG_TRIVIAL(debug) << "SeamPlacer: gather_seam_candidates: end"; - if (configured_seam_preference == spNearest || configured_seam_preference == spRandom) { + if (configured_seam_preference == spAligned || configured_seam_preference == spNearest) { BOOST_LOG_TRIVIAL(debug) << "SeamPlacer: calculate_candidates_visibility : start"; calculate_candidates_visibility(po, global_model_info); @@ -1119,7 +1262,11 @@ void SeamPlacer::init(const Print &print) { 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, comparator); + if (configured_seam_preference == spRandom) { + pick_random_seam_point(layer_perimeter_points, current); + } else { + pick_seam_point(layer_perimeter_points, current, comparator); + } current = layer_perimeter_points[current].perimeter->end_index + 1; } } @@ -1127,7 +1274,7 @@ void SeamPlacer::init(const Print &print) { BOOST_LOG_TRIVIAL(debug) << "SeamPlacer: pick_seam_point : end"; - if (configured_seam_preference != spRandom) { + if (configured_seam_preference == spAligned) { BOOST_LOG_TRIVIAL(debug) << "SeamPlacer: align_seam_points : start"; align_seam_points(po, comparator); @@ -1135,20 +1282,22 @@ void SeamPlacer::init(const Print &print) { << "SeamPlacer: align_seam_points : end"; } +#ifdef DEBUG_FILES debug_export_points(m_perimeter_points_per_object[po], po->bounding_box(), std::to_string(po->id().id), comparator); +#endif } } -void SeamPlacer::place_seam(const Layer *layer, ExtrusionLoop &loop, bool external_first) { +void SeamPlacer::place_seam(const Layer *layer, ExtrusionLoop &loop, bool external_first, const Point &last_pos) const { using namespace SeamPlacerImpl; const PrintObject *po = layer->object(); //NOTE this is necessary, since layer->id() is quite unreliable size_t layer_index = std::max(0, int(layer->id()) - int(po->slicing_parameters().raft_layers())); double unscaled_z = layer->slice_z; - const auto &perimeter_points_tree = *m_perimeter_points_trees_per_object[po][layer_index]; - const auto &perimeter_points = m_perimeter_points_per_object[po][layer_index]; + const auto &perimeter_points_tree = *m_perimeter_points_trees_per_object.find(po)->second[layer_index]; + const auto &perimeter_points = m_perimeter_points_per_object.find(po)->second[layer_index]; const Point &fp = loop.first_point(); @@ -1156,11 +1305,19 @@ void SeamPlacer::place_seam(const Layer *layer, ExtrusionLoop &loop, bool extern size_t closest_perimeter_point_index = find_closest_point(perimeter_points_tree, Vec3f { unscaled_p.x(), unscaled_p.y(), float(unscaled_z) }); const Perimeter *perimeter = perimeter_points[closest_perimeter_point_index].perimeter.get(); - Vec3f seam_position = perimeter_points[perimeter->seam_index].position; - if (perimeter->aligned) { - seam_position = perimeter->final_seam_position; + + size_t seam_index; + if (po->config().seam_position == spNearest) { + seam_index = pick_nearest_seam_point_index(perimeter_points, perimeter->start_index, + unscale(last_pos).cast()); + } else { + seam_index = perimeter->seam_index; } + Vec3f seam_position = perimeter_points[seam_index].position; + if (perimeter->finalized) { + seam_position = perimeter->final_seam_position; + } Point seam_point = scaled(Vec2d { seam_position.x(), seam_position.y() }); if (!loop.split_at_vertex(seam_point)) diff --git a/src/libslic3r/GCode/SeamPlacerNG.hpp b/src/libslic3r/GCode/SeamPlacerNG.hpp index a37543bbe7..422164c7e8 100644 --- a/src/libslic3r/GCode/SeamPlacerNG.hpp +++ b/src/libslic3r/GCode/SeamPlacerNG.hpp @@ -27,6 +27,7 @@ class Grid; namespace SeamPlacerImpl { struct GlobalModelInfo; +struct SeamComparator; enum class EnforcedBlockedSeamPoint { Blocked = 0, @@ -40,9 +41,10 @@ struct Perimeter { size_t end_index; //inclusive! size_t seam_index; - // During alignment, a final position may be stored here. In that case, aligned is set to true. + // During alignment, a final position may be stored here. In that case, finalized is set to true. // Note that final seam position is not limited to points of the perimeter loop. In theory it can be any position - bool aligned = false; + // Random position also uses this flexibility to set final seam point position + bool finalized = false; Vec3f final_seam_position; }; @@ -84,20 +86,22 @@ class SeamPlacer { public: using SeamCandidatesTree = KDTreeIndirect<3, float, SeamPlacerImpl::SeamCandidateCoordinateFunctor>; - static constexpr float raycasting_decimation_target_error = 2.0f; - static constexpr float raycasting_subdivision_target_length = 3.0f; + static constexpr float raycasting_decimation_target_error = 1.0f; + static constexpr float raycasting_subdivision_target_length = 1.0f; //square of number of rays per triangle - static constexpr size_t sqr_rays_per_triangle = 8; + static constexpr size_t sqr_rays_per_triangle = 7; // arm length used during angles computation - static constexpr float polygon_local_angles_arm_distance = 0.4f; + static constexpr float polygon_local_angles_arm_distance = 0.5f; // 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.3f; // For long polygon sides, if they are close to the custom seam drawings, they are oversampled with this step size - static constexpr float enforcer_blocker_oversampling_distance = 0.3f; + static constexpr float enforcer_blocker_oversampling_distance = 0.1f; // When searching for seam clusters for alignment: + // following value describes, how much worse score can point have and still be picked into seam cluster instead of original seam point on the same layer + static constexpr float seam_align_score_tolerance = 0.6f; // 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 static constexpr float seam_align_tolerable_dist = 0.5f; @@ -106,6 +110,8 @@ public: static constexpr size_t seam_align_tolerable_skips = 4; // minimum number of seams needed in cluster to make alignemnt happen static constexpr size_t seam_align_minimum_string_seams = 6; + // iterations of laplace smoothing + static constexpr size_t seam_align_laplace_smoothing_iterations = 20; //The following data structures hold all perimeter points for all PrintObject. The structure is as follows: // Map of PrintObjects (PO) -> vector of layers of PO -> vector of perimeter points of the given layer @@ -115,19 +121,17 @@ public: void init(const Print &print); - void place_seam(const Layer *layer, ExtrusionLoop &loop, bool external_first); + void place_seam(const Layer *layer, ExtrusionLoop &loop, bool external_first, const Point& last_pos) const; private: void gather_seam_candidates(const PrintObject *po, const SeamPlacerImpl::GlobalModelInfo &global_model_info); void calculate_candidates_visibility(const PrintObject *po, const SeamPlacerImpl::GlobalModelInfo &global_model_info); void calculate_overhangs(const PrintObject *po); - template - void align_seam_points(const PrintObject *po, const Comparator &comparator); - template + void align_seam_points(const PrintObject *po, const SeamPlacerImpl::SeamComparator &comparator); bool find_next_seam_in_layer(const PrintObject *po, std::pair &last_point, - size_t layer_idx, const Comparator &comparator, + size_t layer_idx,const SeamPlacerImpl::SeamComparator &comparator, std::vector> &seam_strings, std::vector> &outliers); };