Browse Source

Fix faulty virtual bed handling

Changes to firstfit selection to use a map of bed contexts to avoid memory overuse when a fixed item has a large bed index number.

fixes SPE-1844
tamasmeszaros 1 year ago
parent
commit
1d4594ad66

+ 19 - 7
src/libslic3r/Arrange/ArrangeImpl.hpp

@@ -2,6 +2,7 @@
 #define ARRANGEIMPL_HPP
 
 #include <random>
+#include <map>
 
 #include "Arrange.hpp"
 
@@ -43,25 +44,36 @@ void arrange(SelectionStrategy &&selstrategy,
             std::forward<PackStrategy>(packingstrategy), items, fixed,
             RectangleBed{bed.bb}, SelStrategyTag<SelectionStrategy>{});
 
-    size_t beds = get_bed_count(crange(items));
-    size_t fixed_beds = std::max(beds, get_bed_count(fixed));
-    std::vector<bool> fixed_is_empty(fixed_beds, true);
+    std::vector<int> bed_indices = get_bed_indices(items, fixed);
 
-    std::vector<BoundingBox> pilebb(beds);
+    size_t beds = bed_indices.size();
+
+    auto fixed_is_empty = [&bed_indices](int bidx) {
+        auto it = std::lower_bound(bed_indices.begin(), bed_indices.end(), bidx);
+        return it == bed_indices.end() || *it != bidx;
+    };
+
+    auto set_bed_as_empty = [&bed_indices](int bidx) {
+        auto it = std::lower_bound(bed_indices.begin(), bed_indices.end(), bidx);
+        if (it != bed_indices.end())
+            bed_indices.erase(it);
+    };
+
+    std::vector<BoundingBox> pilebb(bed_indices.size());
 
     for (auto &itm : items) {
         auto bedidx = get_bed_index(itm);
         if (bedidx >= 0) {
             pilebb[bedidx].merge(fixed_bounding_box(itm));
             if (is_wipe_tower(itm))
-                fixed_is_empty[bedidx] = false;
+                set_bed_as_empty(bedidx);
         }
     }
 
     for (auto &fxitm : fixed) {
         auto bedidx = get_bed_index(fxitm);
         if (bedidx >= 0 || is_wipe_tower(fxitm))
-            fixed_is_empty[bedidx] = false;
+            set_bed_as_empty(bedidx);
     }
 
     auto bedbb = bounding_box(bed);
@@ -74,7 +86,7 @@ void arrange(SelectionStrategy &&selstrategy,
     Pivots pivot = bed.alignment();
 
     for (size_t bedidx = 0; bedidx < beds; ++bedidx) {
-        if (! fixed_is_empty[bedidx])
+        if (! fixed_is_empty(bedidx))
             continue;
 
         BoundingBox bb;

+ 39 - 3
src/libslic3r/Arrange/Core/ArrangeBase.hpp

@@ -233,8 +233,44 @@ void arrange(SelectionStrategy &&selstrategy,
                   "Arrange unimplemented for this selection strategy");
 }
 
+template<class It>
+std::vector<int> get_bed_indices(const Range<It> &items)
+{
+    auto bed_indices = reserve_vector<int>(items.size());
+
+    for (auto &itm : items)
+        bed_indices.emplace_back(get_bed_index(itm));
+
+    std::sort(bed_indices.begin(), bed_indices.end());
+    auto endit = std::unique(bed_indices.begin(), bed_indices.end());
+
+    bed_indices.erase(endit, bed_indices.end());
+
+    return bed_indices;
+}
+
+template<class It, class CIt>
+std::vector<int> get_bed_indices(const Range<It> &items, const Range<CIt> &fixed)
+{
+    std::vector<int> ret;
+
+    auto iitems = get_bed_indices(items);
+    auto ifixed = get_bed_indices(fixed);
+    ret.reserve(std::max(iitems.size(), ifixed.size()));
+    std::set_union(iitems.begin(), iitems.end(),
+                   ifixed.begin(), ifixed.end(),
+                   std::back_inserter(ret));
+
+    return ret;
+}
+
 template<class It>
 size_t get_bed_count(const Range<It> &items)
+{
+    return get_bed_indices(items).size();
+}
+
+template<class It> int get_max_bed_index(const Range<It> &items)
 {
     auto it = std::max_element(items.begin(),
                                items.end(),
@@ -242,11 +278,11 @@ size_t get_bed_count(const Range<It> &items)
                                    return get_bed_index(i1) < get_bed_index(i2);
                                });
 
-    size_t beds = 0;
+    int ret = Unarranged;
     if (it != items.end())
-        beds = get_bed_index(*it) + 1;
+        ret = get_bed_index(*it);
 
-    return beds;
+    return ret;
 }
 
 struct DefaultStopCondition {

+ 25 - 21
src/libslic3r/Arrange/Core/ArrangeFirstFit.hpp

@@ -2,6 +2,7 @@
 #define ARRANGEFIRSTFIT_HPP
 
 #include <iterator>
+#include <map>
 
 #include <libslic3r/Arrange/Core/ArrangeBase.hpp>
 
@@ -88,21 +89,28 @@ void arrange(
         sorted_items.emplace_back(itm);
     }
 
-    int max_bed_idx = get_bed_count(fixed);
-
     using Context = PackStrategyContext<PackStrategy, ArrItem>;
 
-    auto bed_contexts = reserve_vector<Context>(max_bed_idx + 1);
+    std::map<int, Context> bed_contexts;
+    auto get_or_init_context = [&ps, &bed, &bed_contexts](int bedidx) -> Context& {
+        auto ctx_it = bed_contexts.find(bedidx);
+        if (ctx_it == bed_contexts.end()) {
+            auto res = bed_contexts.emplace(
+                bedidx, create_context<ArrItem>(ps, bed, bedidx));
 
-    for (auto &itm : fixed) {
-        if (get_bed_index(itm) >= 0) {
-            auto bedidx = static_cast<size_t>(get_bed_index(itm));
+            assert(res.second);
 
-            while (bed_contexts.size() <= bedidx)
-                bed_contexts.emplace_back(
-                    create_context<ArrItem>(ps, bed, bedidx));
+            ctx_it = res.first;
+        }
+
+        return ctx_it->second;
+    };
 
-            add_fixed_item(bed_contexts[bedidx], itm);
+    for (auto &itm : fixed) {
+        auto bedidx = get_bed_index(itm);
+        if (bedidx >= 0) {
+            Context &ctx = get_or_init_context(bedidx);
+            add_fixed_item(ctx, itm);
         }
     }
 
@@ -124,18 +132,20 @@ void arrange(
 
     while (it != sorted_items.end() && !is_cancelled()) {
         bool was_packed = false;
-        size_t j = 0;
+        int bedidx = 0;
         while (!was_packed && !is_cancelled()) {
-            for (; j < bed_contexts.size() && !was_packed && !is_cancelled(); j++) {
-                set_bed_index(*it, int(j));
+            for (; !was_packed && !is_cancelled(); bedidx++) {
+                set_bed_index(*it, bedidx);
 
                 auto remaining = Range{std::next(static_cast<SConstIt>(it)),
                                        sorted_items.cend()};
 
-                was_packed = pack(ps, bed, *it, bed_contexts[j], remaining);
+                Context &ctx = get_or_init_context(bedidx);
+
+                was_packed = pack(ps, bed, *it, ctx, remaining);
 
                 if(was_packed) {
-                    add_packed_item(bed_contexts[j], *it);
+                    add_packed_item(ctx, *it);
 
                     auto packed_range = Range{sorted_items.cbegin(),
                                               static_cast<SConstIt>(it)};
@@ -145,12 +155,6 @@ void arrange(
                     set_bed_index(*it, Unarranged);
                 }
             }
-
-            if (!was_packed) {
-                bed_contexts.emplace_back(
-                    create_context<ArrItem>(ps, bed, bed_contexts.size()));
-                j = bed_contexts.size() - 1;
-            }
         }
         ++it;
     }

+ 12 - 6
src/libslic3r/Arrange/SceneBuilder.cpp

@@ -304,22 +304,28 @@ Transform3d YStriderVBedHandler::get_physical_bed_trafo(int bed_index) const
     return tr;
 }
 
-const int GridStriderVBedHandler::ColsOutside =
-        static_cast<int>(std::sqrt(std::numeric_limits<int>::max()));
+const int GridStriderVBedHandler::Cols =
+    2 * static_cast<int>(std::sqrt(std::numeric_limits<int>::max()) / 2);
+
+const int GridStriderVBedHandler::HalfCols = Cols / 2;
+const int GridStriderVBedHandler::Offset = HalfCols + Cols * HalfCols;
 
 Vec2i GridStriderVBedHandler::raw2grid(int bed_idx) const
 {
-    Vec2i ret{bed_idx % ColsOutside, bed_idx / ColsOutside};
+    bed_idx += Offset;
+
+    Vec2i ret{bed_idx % Cols - HalfCols, bed_idx / Cols - HalfCols};
 
     return ret;
 }
 
 int GridStriderVBedHandler::grid2raw(const Vec2i &crd) const
 {
-    assert(std::abs(crd.x()) < ColsOutside - 1 &&
-           std::abs(crd.y()) < ColsOutside - 1);
+    // Overlapping virtual beds will happen if the crd values exceed limits
+    assert((crd.x() < HalfCols - 1 && crd.x() >= -HalfCols) &&
+           (crd.y() < HalfCols - 1 && crd.y() >= -HalfCols));
 
-    return crd.y() * ColsOutside + crd.x();
+    return (crd.x() + HalfCols) + Cols * (crd.y() + HalfCols) - Offset;
 }
 
 int GridStriderVBedHandler::get_bed_index(const VBedPlaceable &obj) const

+ 3 - 1
src/libslic3r/Arrange/SceneBuilder.hpp

@@ -338,7 +338,9 @@ class GridStriderVBedHandler: public VirtualBedHandler
     // not representable with scaled coordinates. Combining XStrider with
     // YStrider takes care of the X and Y axis to be mapped into the physical
     // bed's coordinate region (which is representable in scaled coords)
-    static const int ColsOutside;
+    static const int Cols;
+    static const int HalfCols;
+    static const int Offset;
 
     XStriderVBedHandler m_xstrider;
     YStriderVBedHandler m_ystrider;

+ 24 - 7
src/libslic3r/Arrange/Tasks/ArrangeTaskImpl.hpp

@@ -75,6 +75,21 @@ void prepare_fixed_unselected(ItemCont &items, int shift)
                 items.end());
 }
 
+inline int find_first_empty_bed(const std::vector<int>& bed_indices,
+                                int starting_from = 0) {
+    int ret = starting_from;
+
+    for (int idx : bed_indices) {
+        if (idx == ret) {
+            ret++;
+        } else if (idx > ret) {
+            break;
+        }
+    }
+
+    return ret;
+}
+
 template<class ArrItem>
 std::unique_ptr<ArrangeTaskResult>
 ArrangeTask<ArrItem>::process_native(Ctl &ctl)
@@ -109,15 +124,17 @@ ArrangeTask<ArrItem>::process_native(Ctl &ctl)
 
     arranger->arrange(printable.selected, fixed_items, bed, subctl);
 
-    // Unprintable items should go to the first bed not containing any printable
-    // items
-    auto beds = std::max(get_bed_count(crange(printable.selected)),
-                         get_bed_count(crange(printable.unselected)));
+    std::vector<int> printable_bed_indices =
+        get_bed_indices(crange(printable.selected), crange(printable.unselected));
 
     // If there are no printables, leave the physical bed empty
-    beds = std::max(beds, size_t{1});
+    constexpr int SearchFrom = 1;
+
+    // Unprintable items should go to the first logical (!) bed not containing
+    // any printable items
+    int first_empty_bed = find_first_empty_bed(printable_bed_indices, SearchFrom);
 
-    prepare_fixed_unselected(unprintable.unselected, beds);
+    prepare_fixed_unselected(unprintable.unselected, first_empty_bed);
 
     arranger->arrange(unprintable.selected, unprintable.unselected, bed, ctl);
 
@@ -125,7 +142,7 @@ ArrangeTask<ArrItem>::process_native(Ctl &ctl)
 
     for (auto &itm : unprintable.selected) {
         if (is_arranged(itm)) {
-            int bedidx = get_bed_index(itm) + beds;
+            int bedidx = get_bed_index(itm) + first_empty_bed;
             arr2::set_bed_index(itm, bedidx);
         }