#include "SeamPlacer.hpp" #include "libslic3r/ExtrusionEntity.hpp" #include "libslic3r/Print.hpp" #include "libslic3r/BoundingBox.hpp" #include "libslic3r/EdgeGrid.hpp" #include "libslic3r/ClipperUtils.hpp" #include "libslic3r/SVG.hpp" #include "libslic3r/Layer.hpp" namespace Slic3r { // This penalty is added to all points inside custom blockers (subtracted from pts inside enforcers). static constexpr float ENFORCER_BLOCKER_PENALTY = 100; // In case there are custom enforcers/blockers, the loop polygon shall always have // sides smaller than this (so it isn't limited to original resolution). static constexpr float MINIMAL_POLYGON_SIDE = scale_(0.2f); // When spAligned is active and there is a support enforcer, // add this penalty to its center. static constexpr float ENFORCER_CENTER_PENALTY = -10.f; static float extrudate_overlap_penalty(float nozzle_r, float weight_zero, float overlap_distance) { // The extrudate is not fully supported by the lower layer. Fit a polynomial penalty curve. // Solved by sympy package: /* from sympy import * (x,a,b,c,d,r,z)=symbols('x a b c d r z') p = a + b*x + c*x*x + d*x*x*x p2 = p.subs(solve([p.subs(x, -r), p.diff(x).subs(x, -r), p.diff(x,x).subs(x, -r), p.subs(x, 0)-z], [a, b, c, d])) from sympy.plotting import plot plot(p2.subs(r,0.2).subs(z,1.), (x, -1, 3), adaptive=False, nb_of_points=400) */ if (overlap_distance < - nozzle_r) { // The extrudate is fully supported by the lower layer. This is the ideal case, therefore zero penalty. return 0.f; } else { float x = overlap_distance / nozzle_r; float x2 = x * x; float x3 = x2 * x; return weight_zero * (1.f + 3.f * x + 3.f * x2 + x3); } } // Return a value in <0, 1> of a cubic B-spline kernel centered around zero. // The B-spline is re-scaled so it has value 1 at zero. static inline float bspline_kernel(float x) { x = std::abs(x); if (x < 1.f) { return 1.f - (3.f / 2.f) * x * x + (3.f / 4.f) * x * x * x; } else if (x < 2.f) { x -= 1.f; float x2 = x * x; float x3 = x2 * x; return (1.f / 4.f) - (3.f / 4.f) * x + (3.f / 4.f) * x2 - (1.f / 4.f) * x3; } else return 0; } static Points::const_iterator project_point_to_polygon_and_insert(Polygon &polygon, const Point &pt, double eps) { assert(polygon.points.size() >= 2); if (polygon.points.size() <= 1) if (polygon.points.size() == 1) return polygon.points.begin(); Point pt_min; double d_min = std::numeric_limits::max(); size_t i_min = size_t(-1); for (size_t i = 0; i < polygon.points.size(); ++ i) { size_t j = i + 1; if (j == polygon.points.size()) j = 0; const Point &p1 = polygon.points[i]; const Point &p2 = polygon.points[j]; const Slic3r::Point v_seg = p2 - p1; const Slic3r::Point v_pt = pt - p1; const int64_t l2_seg = int64_t(v_seg(0)) * int64_t(v_seg(0)) + int64_t(v_seg(1)) * int64_t(v_seg(1)); int64_t t_pt = int64_t(v_seg(0)) * int64_t(v_pt(0)) + int64_t(v_seg(1)) * int64_t(v_pt(1)); if (t_pt < 0) { // Closest to p1. double dabs = sqrt(int64_t(v_pt(0)) * int64_t(v_pt(0)) + int64_t(v_pt(1)) * int64_t(v_pt(1))); if (dabs < d_min) { d_min = dabs; i_min = i; pt_min = p1; } } else if (t_pt > l2_seg) { // Closest to p2. Then p2 is the starting point of another segment, which shall be discovered in the next step. continue; } else { // Closest to the segment. assert(t_pt >= 0 && t_pt <= l2_seg); int64_t d_seg = int64_t(v_seg(1)) * int64_t(v_pt(0)) - int64_t(v_seg(0)) * int64_t(v_pt(1)); double d = double(d_seg) / sqrt(double(l2_seg)); double dabs = std::abs(d); if (dabs < d_min) { d_min = dabs; i_min = i; // Evaluate the foot point. pt_min = p1; double linv = double(d_seg) / double(l2_seg); pt_min(0) = pt(0) - coord_t(floor(double(v_seg(1)) * linv + 0.5)); pt_min(1) = pt(1) + coord_t(floor(double(v_seg(0)) * linv + 0.5)); assert(Line(p1, p2).distance_to(pt_min) < scale_(1e-5)); } } } assert(i_min != size_t(-1)); if ((pt_min - polygon.points[i_min]).cast().norm() > eps) { // Insert a new point on the segment i_min, i_min+1. return polygon.points.insert(polygon.points.begin() + (i_min + 1), pt_min); } return polygon.points.begin() + i_min; } static std::vector polygon_angles_at_vertices(const Polygon &polygon, const std::vector &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(); // 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 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; } 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; } while (idx_next < idx_curr && lengths.back() - lengths[idx_curr] + lengths[idx_next] < min_arm_length) ++ idx_next; // Calculate angle between idx_prev, idx_curr, idx_next. const Point &p0 = polygon.points[idx_prev]; const Point &p1 = polygon.points[idx_curr]; const Point &p2 = polygon.points[idx_next]; const Point v1 = p1 - p0; const Point v2 = p2 - p1; 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(double(cross), double(dot))); angles[idx_curr] = angle; } return angles; } void SeamPlacer::init(const Print& print) { m_enforcers.clear(); m_blockers.clear(); m_seam_history.clear(); m_po_list.clear(); const std::vector& nozzle_dmrs = print.config().nozzle_diameter.values; float max_nozzle_dmr = *std::max_element(nozzle_dmrs.begin(), nozzle_dmrs.end()); std::vector temp_enf; std::vector temp_blk; for (const PrintObject* po : print.objects()) { temp_enf.clear(); temp_blk.clear(); po->project_and_append_custom_facets(true, EnforcerBlockerType::ENFORCER, temp_enf); po->project_and_append_custom_facets(true, EnforcerBlockerType::BLOCKER, temp_blk); // Offset the triangles out slightly. for (auto* custom_per_object : {&temp_enf, &temp_blk}) for (ExPolygons& explgs : *custom_per_object) explgs = Slic3r::offset_ex(explgs, scale_(max_nozzle_dmr)); // FIXME: Offsetting should be done somehow cheaper, but following does not work // for (auto* custom_per_object : {&temp_enf, &temp_blk}) { // for (ExPolygons& plgs : *custom_per_object) { // for (ExPolygon& plg : plgs) { // auto out = Slic3r::offset_ex(plg, scale_(max_nozzle_dmr)); // plg = out.empty() ? ExPolygon() : out.front(); // assert(out.empty() || out.size() == 1); // } // } // } // Remember this PrintObject and initialize a store of enforcers and blockers for it. m_po_list.push_back(po); size_t po_idx = m_po_list.size() - 1; m_enforcers.emplace_back(std::vector(temp_enf.size())); m_blockers.emplace_back(std::vector(temp_blk.size())); // A helper class to store data to build the AABB tree from. class CustomTriangleRef { public: CustomTriangleRef(size_t idx, Point&& centroid, BoundingBox&& bb) : m_idx{idx}, m_centroid{centroid}, m_bbox{AlignedBoxType(bb.min, bb.max)} {} size_t idx() const { return m_idx; } const Point& centroid() const { return m_centroid; } const TreeType::BoundingBox& bbox() const { return m_bbox; } private: size_t m_idx; Point m_centroid; AlignedBoxType m_bbox; }; // A lambda to extract the ExPolygons and save them into the member AABB tree. // Will be called for enforcers and blockers separately. auto add_custom = [](std::vector& src, std::vector& dest) { // Go layer by layer, and append all the ExPolygons into the AABB tree. size_t layer_idx = 0; for (ExPolygons& expolys_on_layer : src) { CustomTrianglesPerLayer& layer_data = dest[layer_idx]; std::vector triangles_data; layer_data.polys.reserve(expolys_on_layer.size()); triangles_data.reserve(expolys_on_layer.size()); for (ExPolygon& expoly : expolys_on_layer) { if (expoly.empty()) continue; layer_data.polys.emplace_back(std::move(expoly)); triangles_data.emplace_back(layer_data.polys.size() - 1, layer_data.polys.back().centroid(), layer_data.polys.back().bounding_box()); } // All polygons are saved, build the AABB tree for them. layer_data.tree.build(std::move(triangles_data)); ++layer_idx; } }; add_custom(temp_enf, m_enforcers.at(po_idx)); add_custom(temp_blk, m_blockers.at(po_idx)); } } Point SeamPlacer::get_seam(const Layer& layer, const SeamPosition seam_position, const ExtrusionLoop& loop, Point last_pos, coordf_t nozzle_dmr, const PrintObject* po, bool was_clockwise, const EdgeGrid::Grid* lower_layer_edge_grid) { Polygon polygon = loop.polygon(); BoundingBox polygon_bb = polygon.bounding_box(); const coord_t nozzle_r = coord_t(scale_(0.5 * nozzle_dmr) + 0.5); size_t po_idx = std::find(m_po_list.begin(), m_po_list.end(), po) - m_po_list.begin(); // Find current layer in respective PrintObject. Cache the result so the // lookup is only done once per layer, not for each loop. const Layer* layer_po = nullptr; if (po == m_last_po && layer.print_z == m_last_print_z) layer_po = m_last_layer_po; else { layer_po = po->get_layer_at_printz(layer.print_z); m_last_po = po; m_last_print_z = layer.print_z; m_last_layer_po = layer_po; } if (! layer_po) return last_pos; // Index of this layer in the respective PrintObject. size_t layer_idx = layer_po->id() - po->layers().front()->id(); // raft layers assert(layer_idx < po->layer_count()); if (this->is_custom_seam_on_layer(layer_idx, po_idx)) { // Seam enf/blockers can begin and end in between the original vertices. // Let add extra points in between and update the leghths. polygon.densify(MINIMAL_POLYGON_SIDE); } if (seam_position != spRandom) { // Retrieve the last start position for this object. float last_pos_weight = 1.f; if (seam_position == spAligned) { // Seam is aligned to the seam at the preceding layer. if (po != nullptr) { std::optional pos = m_seam_history.get_last_seam(m_po_list[po_idx], layer_idx, polygon_bb); if (pos.has_value()) { last_pos = *pos; last_pos_weight = is_custom_enforcer_on_layer(layer_idx, po_idx) ? 0.f : 1.f; } } } else if (seam_position == spRear) { // Object is centered around (0,0) in its current coordinate system. last_pos.x() = 0; last_pos.y() += coord_t(3. * po->bounding_box().radius()); last_pos_weight = 5.f; } if (seam_position == spNearest) { // last_pos already contains current nozzle position } // Insert a projection of last_pos into the polygon. size_t last_pos_proj_idx; { auto it = project_point_to_polygon_and_insert(polygon, last_pos, 0.1 * nozzle_r); last_pos_proj_idx = it - polygon.points.begin(); } // Parametrize the polygon by its length. std::vector lengths = polygon.parameter_by_length(); // For each polygon point, store a penalty. // First calculate the angles, store them as penalties. The angles are caluculated over a minimum arm length of nozzle_r. std::vector penalties = polygon_angles_at_vertices(polygon, lengths, float(nozzle_r)); // No penalty for reflex points, slight penalty for convex points, high penalty for flat surfaces. const float penaltyConvexVertex = 1.f; const float penaltyFlatSurface = 5.f; const float penaltyOverhangHalf = 10.f; // Penalty for visible seams. for (size_t i = 0; i < polygon.points.size(); ++ i) { float ccwAngle = penalties[i]; if (was_clockwise) ccwAngle = - ccwAngle; float penalty = 0; if (ccwAngle <- float(0.6 * PI)) // Sharp reflex vertex. We love that, it hides the seam perfectly. penalty = 0.f; else if (ccwAngle > float(0.6 * PI)) // Seams on sharp convex vertices are more visible than on reflex vertices. penalty = penaltyConvexVertex; else if (ccwAngle < 0.f) { // Interpolate penalty between maximum and zero. penalty = penaltyFlatSurface * bspline_kernel(ccwAngle * float(PI * 2. / 3.)); } else { assert(ccwAngle >= 0.f); // Interpolate penalty between maximum and the penalty for a convex vertex. penalty = penaltyConvexVertex + (penaltyFlatSurface - penaltyConvexVertex) * bspline_kernel(ccwAngle * float(PI * 2. / 3.)); } // Give a negative penalty for points close to the last point or the prefered seam location. float dist_to_last_pos_proj = (i < last_pos_proj_idx) ? std::min(lengths[last_pos_proj_idx] - lengths[i], lengths.back() - lengths[last_pos_proj_idx] + lengths[i]) : std::min(lengths[i] - lengths[last_pos_proj_idx], lengths.back() - lengths[i] + lengths[last_pos_proj_idx]); float dist_max = 0.1f * lengths.back(); // 5.f * nozzle_dmr penalty -= last_pos_weight * bspline_kernel(dist_to_last_pos_proj / dist_max); penalties[i] = std::max(0.f, penalty); } // Penalty for overhangs. if (lower_layer_edge_grid) { // Use the edge grid distance field structure over the lower layer to calculate overhangs. coord_t nozzle_r = coord_t(std::floor(scale_(0.5 * nozzle_dmr) + 0.5)); coord_t search_r = coord_t(std::floor(scale_(0.8 * nozzle_dmr) + 0.5)); for (size_t i = 0; i < polygon.points.size(); ++ i) { const Point &p = polygon.points[i]; coordf_t dist; // Signed distance is positive outside the object, negative inside the object. // The point is considered at an overhang, if it is more than nozzle radius // outside of the lower layer contour. [[maybe_unused]] bool found = lower_layer_edge_grid->signed_distance(p, search_r, dist); // If the approximate Signed Distance Field was initialized over lower_layer_edge_grid, // then the signed distnace shall always be known. assert(found); penalties[i] += extrudate_overlap_penalty(float(nozzle_r), penaltyOverhangHalf, float(dist)); } } // Custom seam. Huge (negative) constant penalty is applied inside // blockers (enforcers) to rule out points that should not win. this->apply_custom_seam(polygon, po_idx, penalties, lengths, layer_idx, seam_position); // Find a point with a minimum penalty. size_t idx_min = std::min_element(penalties.begin(), penalties.end()) - penalties.begin(); if (seam_position != spAligned || ! is_custom_enforcer_on_layer(layer_idx, po_idx)) { // Very likely the weight of idx_min is very close to the weight of last_pos_proj_idx. // In that case use last_pos_proj_idx instead. float penalty_aligned = penalties[last_pos_proj_idx]; float penalty_min = penalties[idx_min]; float penalty_diff_abs = std::abs(penalty_min - penalty_aligned); float penalty_max = std::max(penalty_min, penalty_aligned); float penalty_diff_rel = (penalty_max == 0.f) ? 0.f : penalty_diff_abs / penalty_max; // printf("Align seams, penalty aligned: %f, min: %f, diff abs: %f, diff rel: %f\n", penalty_aligned, penalty_min, penalty_diff_abs, penalty_diff_rel); if (std::abs(penalty_diff_rel) < 0.05) { // Penalty of the aligned point is very close to the minimum penalty. // Align the seams as accurately as possible. idx_min = last_pos_proj_idx; } } if (seam_position == spAligned && loop.role() == erExternalPerimeter) m_seam_history.add_seam(po, polygon.points[idx_min], polygon_bb); // Export the contour into a SVG file. #if 0 { static int iRun = 0; SVG svg(debug_out_path("GCode_extrude_loop-%d.svg", iRun ++)); if (m_layer->lower_layer != NULL) svg.draw(m_layer->lower_layer->slices); for (size_t i = 0; i < loop.paths.size(); ++ i) svg.draw(loop.paths[i].as_polyline(), "red"); Polylines polylines; for (size_t i = 0; i < loop.paths.size(); ++ i) polylines.push_back(loop.paths[i].as_polyline()); Slic3r::Polygons polygons; coordf_t nozzle_dmr = EXTRUDER_CONFIG(nozzle_diameter); coord_t delta = scale_(0.5*nozzle_dmr); Slic3r::offset(polylines, &polygons, delta); // for (size_t i = 0; i < polygons.size(); ++ i) svg.draw((Polyline)polygons[i], "blue"); svg.draw(last_pos, "green", 3); svg.draw(polygon.points[idx_min], "yellow", 3); svg.Close(); } #endif return polygon.points[idx_min]; } else { // spRandom if (po->print()->default_region_config().external_perimeters_first) { if (loop.role() == erExternalPerimeter) last_pos = this->get_random_seam(layer_idx, polygon, po_idx); else { // Internal perimeters will just use last_pos. } } else { if (loop.loop_role() == elrContourInternalPerimeter && loop.role() != erExternalPerimeter) { // This loop does not contain any other loop. Set a random position. // The other loops will get a seam close to the random point chosen // on the innermost contour. last_pos = this->get_random_seam(layer_idx, polygon, po_idx); m_last_loop_was_external = false; } if (loop.role() == erExternalPerimeter) { if (m_last_loop_was_external) { // There was no internal perimeter before this one. last_pos = this->get_random_seam(layer_idx, polygon, po_idx); } else { if (is_custom_seam_on_layer(layer_idx, po_idx)) { // There is a possibility that the loop will be influenced by custom // seam enforcer/blocker. In this case do not inherit the seam // from internal loops (which may conflict with the custom selection // and generate another random one. bool saw_custom = false; Point candidate = this->get_random_seam(layer_idx, polygon, po_idx, &saw_custom); if (saw_custom) last_pos = candidate; } } m_last_loop_was_external = true; } } return last_pos; } } Point SeamPlacer::get_random_seam(size_t layer_idx, const Polygon& polygon, size_t po_idx, bool* saw_custom) const { // Parametrize the polygon by its length. std::vector lengths = polygon.parameter_by_length(); // Which of the points are inside enforcers/blockers? std::vector enforcers_idxs; std::vector blockers_idxs; this->get_enforcers_and_blockers(layer_idx, polygon, po_idx, enforcers_idxs, blockers_idxs); bool has_enforcers = ! enforcers_idxs.empty(); bool has_blockers = ! blockers_idxs.empty(); if (saw_custom) *saw_custom = has_enforcers || has_blockers; // FIXME FIXME FIXME: This is just to test the outcome and whether it is // reasonable. The algorithm should really sum the length of all available // pieces, get a random length and find the respective point. float rand_len = 0.f; size_t pt_idx = 0; do { rand_len = lengths.back() * (rand()/float(RAND_MAX)); auto it = std::lower_bound(lengths.begin(), lengths.end(), rand_len); pt_idx = it == lengths.end() ? 0 : (it-lengths.begin()-1); // If there are blockers and the point is inside, repeat. // If there are enforcers and the point is NOT inside, repeat. } while ((has_blockers && std::binary_search(blockers_idxs.begin(), blockers_idxs.end(), pt_idx)) || (has_enforcers && ! std::binary_search(enforcers_idxs.begin(), enforcers_idxs.end(), pt_idx))); if (! has_enforcers && ! has_blockers) { // The polygon may be too coarse, calculate the point exactly. bool last_seg = pt_idx == polygon.points.size()-1; size_t next_idx = last_seg ? 0 : pt_idx+1; const Point& prev = polygon.points[pt_idx]; const Point& next = polygon.points[next_idx]; assert(next_idx == 0 || pt_idx+1 == next_idx); coordf_t diff_x = next.x() - prev.x(); coordf_t diff_y = next.y() - prev.y(); coordf_t dist = lengths[last_seg ? pt_idx+1 : next_idx] - lengths[pt_idx]; return Point(prev.x() + (rand_len - lengths[pt_idx]) * (diff_x/dist), prev.y() + (rand_len - lengths[pt_idx]) * (diff_y/dist)); } else { // The polygon should be dense enough. return polygon.points[pt_idx]; } } void SeamPlacer::get_enforcers_and_blockers(size_t layer_id, const Polygon& polygon, size_t po_idx, std::vector& enforcers_idxs, std::vector& blockers_idxs) const { enforcers_idxs.clear(); blockers_idxs.clear(); auto is_inside = [](const Point& pt, const CustomTrianglesPerLayer& custom_data) -> bool { assert(! custom_data.polys.empty()); // Now ask the AABB tree which polygon we should check and check it. size_t candidate = AABBTreeIndirect::get_candidate_idx(custom_data.tree, pt); if (candidate != size_t(-1) && custom_data.polys[candidate].contains(pt)) return true; return false; }; if (! m_enforcers[po_idx].empty()) { const CustomTrianglesPerLayer& enforcers = m_enforcers[po_idx][layer_id]; if (! enforcers.polys.empty()) { for (size_t i=0; i find_enforcer_centers(const Polygon& polygon, const std::vector& lengths, const std::vector& enforcers_idxs) { std::vector out; assert(polygon.points.size()+1 == lengths.size()); assert(std::is_sorted(enforcers_idxs.begin(), enforcers_idxs.end())); if (polygon.size() < 2 || enforcers_idxs.empty()) return out; auto get_center_idx = [&polygon, &lengths](size_t start_idx, size_t end_idx) -> size_t { assert(end_idx >= start_idx); if (start_idx == end_idx) return start_idx; float t_c = lengths[start_idx] + 0.5f * (lengths[end_idx] - lengths[start_idx]); auto it = std::lower_bound(lengths.begin() + start_idx, lengths.begin() + end_idx, t_c); int ret = it - lengths.begin(); return ret; }; int last_enforcer_start_idx = enforcers_idxs.front(); bool first_pt_in_list = enforcers_idxs.front() != 0; bool last_pt_in_list = enforcers_idxs.back() == polygon.points.size() - 1; bool wrap_around = last_pt_in_list && first_pt_in_list; for (size_t i=0; i t_e) ? t_s + half_dist : t_e - half_dist; auto it = std::lower_bound(lengths.begin(), lengths.end(), t_c); out[0] = it - lengths.begin(); if (out[0] == lengths.size() - 1) --out[0]; assert(out[0] < lengths.size() - 1); } return out; } void SeamPlacer::apply_custom_seam(const Polygon& polygon, size_t po_idx, std::vector& penalties, const std::vector& lengths, int layer_id, SeamPosition seam_position) const { if (! is_custom_seam_on_layer(layer_id, po_idx)) return; std::vector enforcers_idxs; std::vector blockers_idxs; this->get_enforcers_and_blockers(layer_id, polygon, po_idx, enforcers_idxs, blockers_idxs); for (size_t i : enforcers_idxs) { assert(i < penalties.size()); penalties[i] -= float(ENFORCER_BLOCKER_PENALTY); } for (size_t i : blockers_idxs) { assert(i < penalties.size()); penalties[i] += float(ENFORCER_BLOCKER_PENALTY); } if (seam_position == spAligned) { std::vector enf_centers = find_enforcer_centers(polygon, lengths, enforcers_idxs); for (size_t idx : enf_centers) { assert(idx < penalties.size()); penalties[idx] += ENFORCER_CENTER_PENALTY; } } //////////////////////// // std::ostringstream os; // os << std::setw(3) << std::setfill('0') << layer_id; // int a = scale_(20.); // SVG svg("custom_seam" + os.str() + ".svg", BoundingBox(Point(-a, -a), Point(a, a))); // /*if (! m_enforcers.empty()) // svg.draw(m_enforcers[layer_id], "blue"); // if (! m_blockers.empty()) // svg.draw(m_blockers[layer_id], "red");*/ // size_t min_idx = std::min_element(penalties.begin(), penalties.end()) - penalties.begin(); // //svg.draw(polygon.points[idx_min], "red", 6e5); // for (size_t i=0; i SeamHistory::get_last_seam(const PrintObject* po, size_t layer_id, const BoundingBox& island_bb) { assert(layer_id >= m_layer_id); if (layer_id > m_layer_id) { // Get seam was called for different layer than last time. m_data_last_layer = m_data_this_layer; m_data_this_layer.clear(); m_layer_id = layer_id; } std::optional out; auto seams_it = m_data_last_layer.find(po); if (seams_it == m_data_last_layer.end()) return out; const std::vector& seam_data_po = seams_it->second; // Find a bounding-box on the last layer that is close to one we see now. double min_score = std::numeric_limits::max(); for (const SeamPoint& sp : seam_data_po) { const BoundingBox& bb = sp.m_island_bb; if (! bb.overlap(island_bb)) { // This bb does not even overlap. It is likely unrelated. continue; } double score = std::pow(bb.min(0) - island_bb.min(0), 2.) + std::pow(bb.min(1) - island_bb.min(1), 2.) + std::pow(bb.max(0) - island_bb.max(0), 2.) + std::pow(bb.max(1) - island_bb.max(1), 2.); if (score < min_score) { min_score = score; out = sp.m_pos; } } return out; } void SeamHistory::add_seam(const PrintObject* po, const Point& pos, const BoundingBox& island_bb) { m_data_this_layer[po].push_back({pos, island_bb});; } void SeamHistory::clear() { m_layer_id = 0; m_data_last_layer.clear(); m_data_this_layer.clear(); } }