From 20b7aad6d1cbb4b282586a3844fb66843c81f10d Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Tue, 7 Aug 2018 19:48:00 +0200 Subject: [PATCH] Bug fixes for the neighborhood detection --- xs/src/libnest2d/README.md | 18 ++- xs/src/libnest2d/examples/main.cpp | 92 -------------- .../libnest2d/libnest2d/geometry_traits.hpp | 6 +- xs/src/libnest2d/libnest2d/libnest2d.hpp | 36 +++--- .../libnest2d/placers/bottomleftplacer.hpp | 7 +- .../libnest2d/libnest2d/placers/nfpplacer.hpp | 65 +++++----- .../libnest2d/placers/placer_boilerplate.hpp | 7 +- .../libnest2d/selections/djd_heuristic.hpp | 32 ++--- .../libnest2d/libnest2d/selections/filler.hpp | 2 +- .../libnest2d/selections/firstfit.hpp | 9 +- xs/src/libnest2d/tests/test.cpp | 4 +- xs/src/libslic3r/ModelArrange.hpp | 118 ++++++++++++------ 12 files changed, 190 insertions(+), 206 deletions(-) diff --git a/xs/src/libnest2d/README.md b/xs/src/libnest2d/README.md index 3508801a8..61a7ac7d0 100644 --- a/xs/src/libnest2d/README.md +++ b/xs/src/libnest2d/README.md @@ -9,18 +9,28 @@ with templated geometry types. These geometries can have custom or already existing implementation to avoid copying or having unnecessary dependencies. A default backend is provided if the user of the library just wants to use it -out of the box without additional integration. The default backend is reasonably +out of the box without additional integration. This backend is reasonably fast and robust, being built on top of boost geometry and the [polyclipping](http://www.angusj.com/delphi/clipper.php) library. Usage of -this default backend implies the dependency on these packages as well as the -compilation of the backend itself (The default backend is not yet header only). +this default backend implies the dependency on these packages but its header +only as well. This software is currently under construction and lacks a throughout documentation and some essential algorithms as well. At this stage it works well for rectangles and convex closed polygons without considering holes and concavities. -Holes and non-convex polygons will be usable in the near future as well. +Holes and non-convex polygons will be usable in the near future as well. The +no fit polygon based placer module combined with the first fit selection +strategy is now used in the [Slic3r](https://github.com/prusa3d/Slic3r) +application's arrangement feature. It uses local optimization techniques to find +the best placement of each new item based on some features of the arrangement. + +In the near future I would like to use machine learning to evaluate the +placements and (or) the order if items in which they are placed and see what +results can be obtained. This is a different approach than that of SVGnest which +uses genetic algorithms to find better and better selection orders. Maybe the +two approaches can be combined as well. # References - [SVGNest](https://github.com/Jack000/SVGnest) diff --git a/xs/src/libnest2d/examples/main.cpp b/xs/src/libnest2d/examples/main.cpp index 02be465a8..11f8f50cf 100644 --- a/xs/src/libnest2d/examples/main.cpp +++ b/xs/src/libnest2d/examples/main.cpp @@ -95,98 +95,6 @@ void arrangeRectangles() { pconf.rotations = {0.0/*, Pi/2.0, Pi, 3*Pi/2*/}; pconf.accuracy = 1.0f; -// auto bincenter = ShapeLike::boundingBox(bin).center(); -// pconf.object_function = [&bin, bincenter]( -// Placer::Pile pile, const Item& item, -// double /*area*/, double norm, double penality) { - -// using pl = PointLike; - -// static const double BIG_ITEM_TRESHOLD = 0.2; -// static const double GRAVITY_RATIO = 0.5; -// static const double DENSITY_RATIO = 1.0 - GRAVITY_RATIO; - -// // We will treat big items (compared to the print bed) differently -// NfpPlacer::Pile bigs; -// bigs.reserve(pile.size()); -// for(auto& p : pile) { -// auto pbb = ShapeLike::boundingBox(p); -// auto na = std::sqrt(pbb.width()*pbb.height())/norm; -// if(na > BIG_ITEM_TRESHOLD) bigs.emplace_back(p); -// } - -// // Candidate item bounding box -// auto ibb = item.boundingBox(); - -// // Calculate the full bounding box of the pile with the candidate item -// pile.emplace_back(item.transformedShape()); -// auto fullbb = ShapeLike::boundingBox(pile); -// pile.pop_back(); - -// // The bounding box of the big items (they will accumulate in the center -// // of the pile -// auto bigbb = bigs.empty()? fullbb : ShapeLike::boundingBox(bigs); - -// // The size indicator of the candidate item. This is not the area, -// // but almost... -// auto itemnormarea = std::sqrt(ibb.width()*ibb.height())/norm; - -// // Will hold the resulting score -// double score = 0; - -// if(itemnormarea > BIG_ITEM_TRESHOLD) { -// // This branch is for the bigger items.. -// // Here we will use the closest point of the item bounding box to -// // the already arranged pile. So not the bb center nor the a choosen -// // corner but whichever is the closest to the center. This will -// // prevent unwanted strange arrangements. - -// auto minc = ibb.minCorner(); // bottom left corner -// auto maxc = ibb.maxCorner(); // top right corner - -// // top left and bottom right corners -// auto top_left = PointImpl{getX(minc), getY(maxc)}; -// auto bottom_right = PointImpl{getX(maxc), getY(minc)}; - -// auto cc = fullbb.center(); // The gravity center - -// // Now the distnce of the gravity center will be calculated to the -// // five anchor points and the smallest will be chosen. -// std::array dists; -// dists[0] = pl::distance(minc, cc); -// dists[1] = pl::distance(maxc, cc); -// dists[2] = pl::distance(ibb.center(), cc); -// dists[3] = pl::distance(top_left, cc); -// dists[4] = pl::distance(bottom_right, cc); - -// auto dist = *(std::min_element(dists.begin(), dists.end())) / norm; - -// // Density is the pack density: how big is the arranged pile -// auto density = std::sqrt(fullbb.width()*fullbb.height()) / norm; - -// // The score is a weighted sum of the distance from pile center -// // and the pile size -// score = GRAVITY_RATIO * dist + DENSITY_RATIO * density; - -// } else if(itemnormarea < BIG_ITEM_TRESHOLD && bigs.empty()) { -// // If there are no big items, only small, we should consider the -// // density here as well to not get silly results -// auto bindist = pl::distance(ibb.center(), bincenter) / norm; -// auto density = std::sqrt(fullbb.width()*fullbb.height()) / norm; -// score = GRAVITY_RATIO * bindist + DENSITY_RATIO * density; -// } else { -// // Here there are the small items that should be placed around the -// // already processed bigger items. -// // No need to play around with the anchor points, the center will be -// // just fine for small items -// score = pl::distance(ibb.center(), bigbb.center()) / norm; -// } - -// if(!Placer::wouldFit(fullbb, bin)) score += norm; - -// return score; -// }; - Packer::SelectionConfig sconf; // sconf.allow_parallel = false; // sconf.force_parallel = false; diff --git a/xs/src/libnest2d/libnest2d/geometry_traits.hpp b/xs/src/libnest2d/libnest2d/geometry_traits.hpp index 830643130..1c0d44c9f 100644 --- a/xs/src/libnest2d/libnest2d/geometry_traits.hpp +++ b/xs/src/libnest2d/libnest2d/geometry_traits.hpp @@ -313,9 +313,9 @@ inline RawPoint _Box::center() const BP2D_NOEXCEPT { using Coord = TCoord; - RawPoint ret = { - static_cast( std::round((getX(minc) + getX(maxc))/2.0) ), - static_cast( std::round((getY(minc) + getY(maxc))/2.0) ) + RawPoint ret = { // No rounding here, we dont know if these are int coords + static_cast( (getX(minc) + getX(maxc))/2.0 ), + static_cast( (getY(minc) + getY(maxc))/2.0 ) }; return ret; diff --git a/xs/src/libnest2d/libnest2d/libnest2d.hpp b/xs/src/libnest2d/libnest2d/libnest2d.hpp index 7f23de358..eadd1e110 100644 --- a/xs/src/libnest2d/libnest2d/libnest2d.hpp +++ b/xs/src/libnest2d/libnest2d/libnest2d.hpp @@ -541,21 +541,20 @@ public: inline void configure(const Config& config) { impl_.configure(config); } /** - * @brief A method that tries to pack an item and returns an object - * describing the pack result. + * Try to pack an item with a result object that contains the packing + * information for later accepting it. * - * The result can be casted to bool and used as an argument to the accept - * method to accept a succesfully packed item. This way the next packing - * will consider the accepted item as well. The PackResult should carry the - * transformation info so that if the tried item is later modified or tried - * multiple times, the result object should set it to the originally - * determied position. An implementation can be found in the - * strategies::PlacerBoilerplate::PackResult class. - * - * @param item Ithe item to be packed. - * @return The PackResult object that can be implicitly casted to bool. + * \param item_store A container of items */ - inline PackResult trypack(Item& item) { return impl_.trypack(item); } + template + inline PackResult trypack(Container& item_store, + typename Container::iterator from, + unsigned count = 1) { + using V = typename Container::value_type; + static_assert(std::is_convertible::value, + "Invalid Item container!"); + return impl_.trypack(item_store, from, count); + } /** * @brief A method to accept a previously tried item. @@ -578,7 +577,16 @@ public: * @return Returns true if the item was packed or false if it could not be * packed. */ - inline bool pack(Item& item) { return impl_.pack(item); } + template + inline bool pack(Container& item_store, + typename Container::iterator from, + unsigned count = 1) + { + using V = typename Container::value_type; + static_assert(std::is_convertible::value, + "Invalid Item container!"); + return impl_.pack(item_store, from, count); + } /// Unpack the last element (remove it from the list of packed items). inline void unpackLast() { impl_.unpackLast(); } diff --git a/xs/src/libnest2d/libnest2d/placers/bottomleftplacer.hpp b/xs/src/libnest2d/libnest2d/placers/bottomleftplacer.hpp index 775e44e09..71573e34d 100644 --- a/xs/src/libnest2d/libnest2d/placers/bottomleftplacer.hpp +++ b/xs/src/libnest2d/libnest2d/placers/bottomleftplacer.hpp @@ -27,9 +27,14 @@ public: explicit _BottomLeftPlacer(const BinType& bin): Base(bin) {} - PackResult trypack(Item& item) { + template + PackResult trypack(Store& /*s*/, typename Store::iterator from, + unsigned /*count*/ = 1) + { + Item& item = *from; auto r = _trypack(item); if(!r && Base::config_.allow_rotations) { + item.rotate(Degrees(90)); r =_trypack(item); } diff --git a/xs/src/libnest2d/libnest2d/placers/nfpplacer.hpp b/xs/src/libnest2d/libnest2d/placers/nfpplacer.hpp index 110c05f0c..d74fe2b1d 100644 --- a/xs/src/libnest2d/libnest2d/placers/nfpplacer.hpp +++ b/xs/src/libnest2d/libnest2d/placers/nfpplacer.hpp @@ -19,6 +19,8 @@ namespace libnest2d { namespace strategies { template struct NfpPConfig { + using ItemGroup = std::vector>>; + enum class Alignment { CENTER, BOTTOM_LEFT, @@ -57,8 +59,8 @@ struct NfpPConfig { * \param item The second parameter is the candidate item. * * \param occupied_area The third parameter is the sum of areas of the - * items in the first parameter so you don't have to iterate through them - * if you only need their area. + * items in the first parameter (no candidate item there) so you don't have + * to iterate through them if you only need their accumulated area. * * \param norm A norming factor for physical dimensions. E.g. if your score * is the distance between the item and the bin center, you should divide @@ -66,21 +68,21 @@ struct NfpPConfig { * divide it with the square of the norming factor. Imagine it as a unit of * distance. * - * \param penality The fifth parameter is the amount of minimum penality if - * the arranged pile would't fit into the bin. You can use the wouldFit() - * function to check this. Note that the pile can be outside the bin's - * boundaries while the placement algorithm is running. Your job is only to - * check if the pile could be translated into a position in the bin where - * all the items would be inside. For a box shaped bin you can use the - * pile's bounding box to check whether it's width and height is small - * enough. If the pile would not fit, you have to make sure that the - * resulting score will be higher then the penality value. A good solution - * would be to set score = 2*penality-score in case the pile wouldn't fit - * into the bin. + * \param remaining A container with the remaining items waiting to be + * placed. You can use some features about the remaining items to alter to + * score of the current placement. If you know that you have to leave place + * for other items as well, that might influence your decision about where + * the current candidate should be placed. E.g. imagine three big circles + * which you want to place into a box: you might place them in a triangle + * shape which has the maximum pack density. But if there is a 4th big + * circle than you won't be able to pack it. If you knew apriori that + * there four circles are to be placed, you would have placed the first 3 + * into an L shape. This parameter can be used to make these kind of + * decisions (for you or a more intelligent AI). * */ std::function&, const _Item&, - double, double, double)> + double, double, const ItemGroup&)> object_function; /** @@ -450,11 +452,13 @@ _Circle> minimizeCircle(const RawShape& sh) { using Point = TPoint; using Coord = TCoord; + auto& ctr = sl::getContour(sh); + if(ctr.empty()) return {{0, 0}, 0}; + auto bb = sl::boundingBox(sh); auto capprx = bb.center(); auto rapprx = pl::distance(bb.minCorner(), bb.maxCorner()); - auto& ctr = sl::getContour(sh); opt::StopCriteria stopcr; stopcr.max_iterations = 100; @@ -513,7 +517,6 @@ class _NofitPolyPlacer: public PlacerBoilerplate<_NofitPolyPlacer>; const double norm_; - const double penality_; using MaxNfpLevel = Nfp::MaxNfpLevel; using sl = ShapeLike; @@ -524,8 +527,7 @@ public: inline explicit _NofitPolyPlacer(const BinType& bin): Base(bin), - norm_(std::sqrt(sl::area(bin))), - penality_(1e6*norm_) {} + norm_(std::sqrt(sl::area(bin))) {} _NofitPolyPlacer(const _NofitPolyPlacer&) = default; _NofitPolyPlacer& operator=(const _NofitPolyPlacer&) = default; @@ -575,7 +577,15 @@ public: return boundingCircle(chull).radius() < bin.radius(); } - PackResult trypack(Item& item) { + template + PackResult trypack(Container& items, + typename Container::iterator from, + unsigned /*count*/ = 1) + { + return trypack(*from, {std::next(from), items.end()}); + } + + PackResult trypack(Item& item, ItemGroup remaining) { PackResult ret; @@ -586,7 +596,7 @@ public: can_pack = item.isInside(bin_); } else { - double global_score = penality_; + double global_score = std::numeric_limits::max(); auto initial_tr = item.translation(); auto initial_rot = item.rotation(); @@ -630,9 +640,8 @@ public: auto getNfpPoint = [&ecache](const Optimum& opt) { - auto ret = opt.hidx < 0? ecache[opt.nfpidx].coords(opt.relpos) : + return opt.hidx < 0? ecache[opt.nfpidx].coords(opt.relpos) : ecache[opt.nfpidx].coords(opt.hidx, opt.relpos); - return ret; }; Nfp::Shapes pile; @@ -654,7 +663,7 @@ public: const Item& item, double occupied_area, double norm, - double /*penality*/) + const ItemGroup& /*remaining*/) { merged_pile.emplace_back(item.transformedShape()); auto ch = sl::convexHull(merged_pile); @@ -686,7 +695,7 @@ public: double occupied_area = pile_area + item.area(); double score = _objfunc(pile, item, occupied_area, - norm_, penality_); + norm_, remaining); return score; }; @@ -705,12 +714,12 @@ public: }; opt::StopCriteria stopcr; - stopcr.max_iterations = 100; - stopcr.relative_score_difference = 1e-12; + stopcr.max_iterations = 200; + stopcr.relative_score_difference = 1e-20; opt::TOptimizer solver(stopcr); Optimum optimum(0, 0); - double best_score = penality_; + double best_score = std::numeric_limits::max(); // Local optimization with the four polygon corners as // starting points @@ -821,7 +830,6 @@ private: inline void finalAlign(_Circle> cbin) { if(items_.empty()) return; - Nfp::Shapes m; m.reserve(items_.size()); for(Item& item : items_) m.emplace_back(item.transformedShape()); @@ -833,6 +841,7 @@ private: } inline void finalAlign(Box bbin) { + if(items_.empty()) return; Nfp::Shapes m; m.reserve(items_.size()); for(Item& item : items_) m.emplace_back(item.transformedShape()); diff --git a/xs/src/libnest2d/libnest2d/placers/placer_boilerplate.hpp b/xs/src/libnest2d/libnest2d/placers/placer_boilerplate.hpp index 9d2cb626b..f31a9343c 100644 --- a/xs/src/libnest2d/libnest2d/placers/placer_boilerplate.hpp +++ b/xs/src/libnest2d/libnest2d/placers/placer_boilerplate.hpp @@ -56,8 +56,11 @@ public: config_ = config; } - bool pack(Item& item) { - auto&& r = static_cast(this)->trypack(item); + template + bool pack(Container& items, + typename Container::iterator from, + unsigned count = 1) { + auto&& r = static_cast(this)->trypack(items, from, count); if(r) { items_.push_back(*(r.item_ptr_)); farea_valid_ = false; diff --git a/xs/src/libnest2d/libnest2d/selections/djd_heuristic.hpp b/xs/src/libnest2d/libnest2d/selections/djd_heuristic.hpp index e3ad97c10..34d6d05c5 100644 --- a/xs/src/libnest2d/libnest2d/selections/djd_heuristic.hpp +++ b/xs/src/libnest2d/libnest2d/selections/djd_heuristic.hpp @@ -230,7 +230,7 @@ public: while(it != not_packed.end() && !ret && free_area - (item_area = it->get().area()) <= waste) { - if(item_area <= free_area && placer.pack(*it) ) { + if(item_area <= free_area && placer.pack(not_packed, it) ) { free_area -= item_area; filled_area = bin_area - free_area; ret = true; @@ -278,7 +278,7 @@ public: if(item_area + smallestPiece(it, not_packed)->get().area() > free_area ) { it++; continue; } - auto pr = placer.trypack(*it); + auto pr = placer.trypack(not_packed, it); // First would fit it2 = not_packed.begin(); @@ -294,14 +294,14 @@ public: } placer.accept(pr); - auto pr2 = placer.trypack(*it2); + auto pr2 = placer.trypack(not_packed, it2); if(!pr2) { placer.unpackLast(); // remove first if(try_reverse) { - pr2 = placer.trypack(*it2); + pr2 = placer.trypack(not_packed, it2); if(pr2) { placer.accept(pr2); - auto pr12 = placer.trypack(*it); + auto pr12 = placer.trypack(not_packed, it); if(pr12) { placer.accept(pr12); ret = true; @@ -394,7 +394,7 @@ public: it++; continue; } - auto pr = placer.trypack(*it); + auto pr = placer.trypack(not_packed, it); // Check for free area and try to pack the 1st item... if(!pr) { it++; continue; } @@ -420,15 +420,15 @@ public: bool can_pack2 = false; placer.accept(pr); - auto pr2 = placer.trypack(*it2); + auto pr2 = placer.trypack(not_packed, it2); auto pr12 = pr; if(!pr2) { placer.unpackLast(); // remove first if(try_reverse) { - pr2 = placer.trypack(*it2); + pr2 = placer.trypack(not_packed, it2); if(pr2) { placer.accept(pr2); - pr12 = placer.trypack(*it); + pr12 = placer.trypack(not_packed, it); if(pr12) can_pack2 = true; placer.unpackLast(); } @@ -463,7 +463,7 @@ public: if(a3_sum > free_area) { it3++; continue; } placer.accept(pr12); placer.accept(pr2); - bool can_pack3 = placer.pack(*it3); + bool can_pack3 = placer.pack(not_packed, it3); if(!can_pack3) { placer.unpackLast(); @@ -473,16 +473,16 @@ public: if(!can_pack3 && try_reverse) { std::array indices = {0, 1, 2}; - std::array - candidates = {*it, *it2, *it3}; + std::array + candidates = {it, it2, it3}; - auto tryPack = [&placer, &candidates]( + auto tryPack = [&placer, &candidates, ¬_packed]( const decltype(indices)& idx) { std::array packed = {false}; for(auto id : idx) packed.at(id) = - placer.pack(candidates[id]); + placer.pack(not_packed, candidates[id]); bool check = std::all_of(packed.begin(), @@ -536,7 +536,7 @@ public: { auto it = store_.begin(); while (it != store_.end()) { Placer p(bin); p.configure(pconfig); - if(!p.pack(*it)) { + if(!p.pack(store_, it)) { it = store_.erase(it); } else it++; } @@ -601,7 +601,7 @@ public: while(it != not_packed.end() && filled_area < INITIAL_FILL_AREA) { - if(placer.pack(*it)) { + if(placer.pack(not_packed, it)) { filled_area += it->get().area(); free_area = bin_area - filled_area; it = not_packed.erase(it); diff --git a/xs/src/libnest2d/libnest2d/selections/filler.hpp b/xs/src/libnest2d/libnest2d/selections/filler.hpp index d0018dc73..ca1281fe6 100644 --- a/xs/src/libnest2d/libnest2d/selections/filler.hpp +++ b/xs/src/libnest2d/libnest2d/selections/filler.hpp @@ -65,7 +65,7 @@ public: auto it = store_.begin(); while(it != store_.end()) { - if(!placer.pack(*it)) { + if(!placer.pack(store_, it)) { if(packed_bins_.back().empty()) ++it; // makeProgress(placer); placer.clearItems(); diff --git a/xs/src/libnest2d/libnest2d/selections/firstfit.hpp b/xs/src/libnest2d/libnest2d/selections/firstfit.hpp index 665b9da9f..93ca02b1e 100644 --- a/xs/src/libnest2d/libnest2d/selections/firstfit.hpp +++ b/xs/src/libnest2d/libnest2d/selections/firstfit.hpp @@ -40,6 +40,7 @@ public: packed_bins_.clear(); std::vector placers; + placers.reserve(last-first); std::copy(first, last, std::back_inserter(store_)); @@ -60,18 +61,19 @@ public: { auto it = store_.begin(); while (it != store_.end()) { Placer p(bin); p.configure(pconfig); - if(!p.pack(*it)) { + if(!p.pack(store_, it)) { it = store_.erase(it); } else it++; } } - for(auto& item : store_ ) { + auto it = store_.begin(); + while(it != store_.end()) { bool was_packed = false; while(!was_packed) { for(size_t j = 0; j < placers.size() && !was_packed; j++) { - if((was_packed = placers[j].pack(item))) + if((was_packed = placers[j].pack(store_, it))) makeProgress(placers[j], j); } @@ -81,6 +83,7 @@ public: packed_bins_.emplace_back(); } } + ++it; } } diff --git a/xs/src/libnest2d/tests/test.cpp b/xs/src/libnest2d/tests/test.cpp index 1e030c056..79832b683 100644 --- a/xs/src/libnest2d/tests/test.cpp +++ b/xs/src/libnest2d/tests/test.cpp @@ -471,8 +471,8 @@ TEST(GeometryAlgorithms, BottomLeftStressTest) { auto next = it; int i = 0; while(it != input.end() && ++next != input.end()) { - placer.pack(*it); - placer.pack(*next); + placer.pack(input, it); + placer.pack(input, next); auto result = placer.getItems(); bool valid = true; diff --git a/xs/src/libslic3r/ModelArrange.hpp b/xs/src/libslic3r/ModelArrange.hpp index f4ce0daca..952a9e3a6 100644 --- a/xs/src/libslic3r/ModelArrange.hpp +++ b/xs/src/libslic3r/ModelArrange.hpp @@ -99,6 +99,7 @@ namespace bgi = boost::geometry::index; using SpatElement = std::pair; using SpatIndex = bgi::rtree< SpatElement, bgi::rstar<16, 4> >; +using ItemGroup = std::vector>; std::tuple objfunc(const PointImpl& bincenter, @@ -109,24 +110,21 @@ objfunc(const PointImpl& bincenter, double norm, // A norming factor for physical dimensions std::vector& areacache, // pile item areas will be cached // a spatial index to quickly get neighbors of the candidate item - SpatIndex& spatindex + SpatIndex& spatindex, + const ItemGroup& remaining ) { using pl = PointLike; using sl = ShapeLike; + using Coord = TCoord; static const double BIG_ITEM_TRESHOLD = 0.02; static const double ROUNDNESS_RATIO = 0.5; static const double DENSITY_RATIO = 1.0 - ROUNDNESS_RATIO; // We will treat big items (compared to the print bed) differently - auto isBig = [&areacache, bin_area](double a) { - double farea = areacache.empty() ? 0 : areacache.front(); - bool fbig = farea / bin_area > BIG_ITEM_TRESHOLD; - bool abig = a/bin_area > BIG_ITEM_TRESHOLD; - bool rbig = fbig && a > 0.5*farea; - return abig || rbig; + return a/bin_area > BIG_ITEM_TRESHOLD ; }; // If a new bin has been created: @@ -195,39 +193,74 @@ objfunc(const PointImpl& bincenter, auto dist = *(std::min_element(dists.begin(), dists.end())) / norm; // Density is the pack density: how big is the arranged pile - auto density = std::sqrt(fullbb.width()*fullbb.height()) / norm; + double density = 0; - // Prepare a variable for the alignment score. - // This will indicate: how well is the candidate item aligned with - // its neighbors. We will check the aligment with all neighbors and - // return the score for the best alignment. So it is enough for the - // candidate to be aligned with only one item. - auto alignment_score = std::numeric_limits::max(); + if(remaining.empty()) { + pile.emplace_back(item.transformedShape()); + auto chull = sl::convexHull(pile); + pile.pop_back(); + strategies::EdgeCache ec(chull); - auto& trsh = item.transformedShape(); + double circ = ec.circumference() / norm; + double bcirc = 2.0*(fullbb.width() + fullbb.height()) / norm; + score = 0.5*circ + 0.5*bcirc; - auto querybb = item.boundingBox(); + } else { + // Prepare a variable for the alignment score. + // This will indicate: how well is the candidate item aligned with + // its neighbors. We will check the aligment with all neighbors and + // return the score for the best alignment. So it is enough for the + // candidate to be aligned with only one item. + auto alignment_score = std::numeric_limits::max(); - // Query the spatial index for the neigbours - std::vector result; - spatindex.query(bgi::intersects(querybb), std::back_inserter(result)); + density = (fullbb.width()*fullbb.height()) / (norm*norm); + auto& trsh = item.transformedShape(); + auto querybb = item.boundingBox(); + auto wp = querybb.width()*0.2; + auto hp = querybb.height()*0.2; + auto pad = PointImpl( Coord(wp), Coord(hp)); + querybb = Box({ querybb.minCorner() - pad, + querybb.maxCorner() + pad + }); - for(auto& e : result) { // now get the score for the best alignment - auto idx = e.second; - auto& p = pile[idx]; - auto parea = areacache[idx]; - auto bb = sl::boundingBox(sl::Shapes{p, trsh}); - auto bbarea = bb.area(); - auto ascore = 1.0 - (item.area() + parea)/bbarea; + // Query the spatial index for the neigbours + std::vector result; + result.reserve(spatindex.size()); + spatindex.query(bgi::intersects(querybb), + std::back_inserter(result)); + +// if(result.empty()) { +// std::cout << "Error while arranging!" << std::endl; +// std::cout << spatindex.size() << " " << pile.size() << std::endl; + +// auto ib = spatindex.bounds(); +// Box ibb; +// boost::geometry::convert(ib, ibb); +// std::cout << "Inside: " << (sl::isInside(querybb, ibb) || +// boost::geometry::intersects(querybb, ibb)) << std::endl; +// } + + for(auto& e : result) { // now get the score for the best alignment + auto idx = e.second; + auto& p = pile[idx]; + auto parea = areacache[idx]; + auto bb = sl::boundingBox(sl::Shapes{p, trsh}); + auto bbarea = bb.area(); + auto ascore = 1.0 - (item.area() + parea)/bbarea; + + if(ascore < alignment_score) alignment_score = ascore; + } + + // The final mix of the score is the balance between the distance + // from the full pile center, the pack density and the + // alignment with the neigbours + if(result.empty()) + score = 0.5 * dist + 0.5 * density; + else + score = 0.45 * dist + 0.45 * density + 0.1 * alignment_score; - if(ascore < alignment_score) alignment_score = ascore; } - // The final mix of the score is the balance between the distance - // from the full pile center, the pack density and the - // alignment with the neigbours - score = 0.45 * dist + 0.45 * density + 0.1 * alignment_score; - } else if( !isBig(item.area()) && spatindex.empty()) { // If there are no big items, only small, we should consider the // density here as well to not get silly results @@ -312,10 +345,12 @@ public: const Item &item, double pile_area, double norm, - double /*penality*/) { + const ItemGroup& rem) { auto result = objfunc(bin.center(), bin_area_, pile, - pile_area, item, norm, areacache_, rtree_); + pile_area, item, norm, areacache_, + rtree_, + rem); double score = std::get<0>(result); auto& fullbb = std::get<1>(result); @@ -346,10 +381,11 @@ public: const Item &item, double pile_area, double norm, - double /*penality*/) { + const ItemGroup& rem) { auto result = objfunc(bin.center(), bin_area_, pile, - pile_area, item, norm, areacache_, rtree_); + pile_area, item, norm, areacache_, + rtree_, rem); double score = std::get<0>(result); auto& fullbb = std::get<1>(result); @@ -391,11 +427,12 @@ public: const Item &item, double pile_area, double norm, - double /*penality*/) { + const ItemGroup& rem) { auto binbb = ShapeLike::boundingBox(bin); auto result = objfunc(binbb.center(), bin_area_, pile, - pile_area, item, norm, areacache_, rtree_); + pile_area, item, norm, areacache_, + rtree_, rem); double score = std::get<0>(result); return score; @@ -417,10 +454,11 @@ public: const Item &item, double pile_area, double norm, - double /*penality*/) { + const ItemGroup& rem) { auto result = objfunc({0, 0}, 0, pile, pile_area, - item, norm, areacache_, rtree_); + item, norm, areacache_, + rtree_, rem); return std::get<0>(result); };