Bug fixes for the neighborhood detection

This commit is contained in:
tamasmeszaros 2018-08-07 19:48:00 +02:00
parent 08fb677583
commit 20b7aad6d1
12 changed files with 190 additions and 206 deletions

View file

@ -9,18 +9,28 @@ with templated geometry types. These geometries can have custom or already
existing implementation to avoid copying or having unnecessary dependencies. 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 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 fast and robust, being built on top of boost geometry and the
[polyclipping](http://www.angusj.com/delphi/clipper.php) library. Usage of [polyclipping](http://www.angusj.com/delphi/clipper.php) library. Usage of
this default backend implies the dependency on these packages as well as the this default backend implies the dependency on these packages but its header
compilation of the backend itself (The default backend is not yet header only). only as well.
This software is currently under construction and lacks a throughout This software is currently under construction and lacks a throughout
documentation and some essential algorithms as well. At this stage it works well documentation and some essential algorithms as well. At this stage it works well
for rectangles and convex closed polygons without considering holes and for rectangles and convex closed polygons without considering holes and
concavities. 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 # References
- [SVGNest](https://github.com/Jack000/SVGnest) - [SVGNest](https://github.com/Jack000/SVGnest)

View file

@ -95,98 +95,6 @@ void arrangeRectangles() {
pconf.rotations = {0.0/*, Pi/2.0, Pi, 3*Pi/2*/}; pconf.rotations = {0.0/*, Pi/2.0, Pi, 3*Pi/2*/};
pconf.accuracy = 1.0f; pconf.accuracy = 1.0f;
// auto bincenter = ShapeLike::boundingBox<PolygonImpl>(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<double, 5> 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; Packer::SelectionConfig sconf;
// sconf.allow_parallel = false; // sconf.allow_parallel = false;
// sconf.force_parallel = false; // sconf.force_parallel = false;

View file

@ -313,9 +313,9 @@ inline RawPoint _Box<RawPoint>::center() const BP2D_NOEXCEPT {
using Coord = TCoord<RawPoint>; using Coord = TCoord<RawPoint>;
RawPoint ret = { RawPoint ret = { // No rounding here, we dont know if these are int coords
static_cast<Coord>( std::round((getX(minc) + getX(maxc))/2.0) ), static_cast<Coord>( (getX(minc) + getX(maxc))/2.0 ),
static_cast<Coord>( std::round((getY(minc) + getY(maxc))/2.0) ) static_cast<Coord>( (getY(minc) + getY(maxc))/2.0 )
}; };
return ret; return ret;

View file

@ -541,21 +541,20 @@ public:
inline void configure(const Config& config) { impl_.configure(config); } inline void configure(const Config& config) { impl_.configure(config); }
/** /**
* @brief A method that tries to pack an item and returns an object * Try to pack an item with a result object that contains the packing
* describing the pack result. * information for later accepting it.
* *
* The result can be casted to bool and used as an argument to the accept * \param item_store A container of items
* 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.
*/ */
inline PackResult trypack(Item& item) { return impl_.trypack(item); } template<class Container>
inline PackResult trypack(Container& item_store,
typename Container::iterator from,
unsigned count = 1) {
using V = typename Container::value_type;
static_assert(std::is_convertible<V, const Item&>::value,
"Invalid Item container!");
return impl_.trypack(item_store, from, count);
}
/** /**
* @brief A method to accept a previously tried item. * @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 * @return Returns true if the item was packed or false if it could not be
* packed. * packed.
*/ */
inline bool pack(Item& item) { return impl_.pack(item); } template<class Container>
inline bool pack(Container& item_store,
typename Container::iterator from,
unsigned count = 1)
{
using V = typename Container::value_type;
static_assert(std::is_convertible<V, const Item&>::value,
"Invalid Item container!");
return impl_.pack(item_store, from, count);
}
/// Unpack the last element (remove it from the list of packed items). /// Unpack the last element (remove it from the list of packed items).
inline void unpackLast() { impl_.unpackLast(); } inline void unpackLast() { impl_.unpackLast(); }

View file

@ -27,9 +27,14 @@ public:
explicit _BottomLeftPlacer(const BinType& bin): Base(bin) {} explicit _BottomLeftPlacer(const BinType& bin): Base(bin) {}
PackResult trypack(Item& item) { template<class Store>
PackResult trypack(Store& /*s*/, typename Store::iterator from,
unsigned /*count*/ = 1)
{
Item& item = *from;
auto r = _trypack(item); auto r = _trypack(item);
if(!r && Base::config_.allow_rotations) { if(!r && Base::config_.allow_rotations) {
item.rotate(Degrees(90)); item.rotate(Degrees(90));
r =_trypack(item); r =_trypack(item);
} }

View file

@ -19,6 +19,8 @@ namespace libnest2d { namespace strategies {
template<class RawShape> template<class RawShape>
struct NfpPConfig { struct NfpPConfig {
using ItemGroup = std::vector<std::reference_wrapper<_Item<RawShape>>>;
enum class Alignment { enum class Alignment {
CENTER, CENTER,
BOTTOM_LEFT, BOTTOM_LEFT,
@ -57,8 +59,8 @@ struct NfpPConfig {
* \param item The second parameter is the candidate item. * \param item The second parameter is the candidate item.
* *
* \param occupied_area The third parameter is the sum of areas of the * \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 * items in the first parameter (no candidate item there) so you don't have
* if you only need their area. * to iterate through them if you only need their accumulated area.
* *
* \param norm A norming factor for physical dimensions. E.g. if your score * \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 * 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 * divide it with the square of the norming factor. Imagine it as a unit of
* distance. * distance.
* *
* \param penality The fifth parameter is the amount of minimum penality if * \param remaining A container with the remaining items waiting to be
* the arranged pile would't fit into the bin. You can use the wouldFit() * placed. You can use some features about the remaining items to alter to
* function to check this. Note that the pile can be outside the bin's * score of the current placement. If you know that you have to leave place
* boundaries while the placement algorithm is running. Your job is only to * for other items as well, that might influence your decision about where
* check if the pile could be translated into a position in the bin where * the current candidate should be placed. E.g. imagine three big circles
* all the items would be inside. For a box shaped bin you can use the * which you want to place into a box: you might place them in a triangle
* pile's bounding box to check whether it's width and height is small * shape which has the maximum pack density. But if there is a 4th big
* enough. If the pile would not fit, you have to make sure that the * circle than you won't be able to pack it. If you knew apriori that
* resulting score will be higher then the penality value. A good solution * there four circles are to be placed, you would have placed the first 3
* would be to set score = 2*penality-score in case the pile wouldn't fit * into an L shape. This parameter can be used to make these kind of
* into the bin. * decisions (for you or a more intelligent AI).
* *
*/ */
std::function<double(Nfp::Shapes<RawShape>&, const _Item<RawShape>&, std::function<double(Nfp::Shapes<RawShape>&, const _Item<RawShape>&,
double, double, double)> double, double, const ItemGroup&)>
object_function; object_function;
/** /**
@ -450,11 +452,13 @@ _Circle<TPoint<RawShape>> minimizeCircle(const RawShape& sh) {
using Point = TPoint<RawShape>; using Point = TPoint<RawShape>;
using Coord = TCoord<Point>; using Coord = TCoord<Point>;
auto& ctr = sl::getContour(sh);
if(ctr.empty()) return {{0, 0}, 0};
auto bb = sl::boundingBox(sh); auto bb = sl::boundingBox(sh);
auto capprx = bb.center(); auto capprx = bb.center();
auto rapprx = pl::distance(bb.minCorner(), bb.maxCorner()); auto rapprx = pl::distance(bb.minCorner(), bb.maxCorner());
auto& ctr = sl::getContour(sh);
opt::StopCriteria stopcr; opt::StopCriteria stopcr;
stopcr.max_iterations = 100; stopcr.max_iterations = 100;
@ -513,7 +517,6 @@ class _NofitPolyPlacer: public PlacerBoilerplate<_NofitPolyPlacer<RawShape, TBin
using Box = _Box<TPoint<RawShape>>; using Box = _Box<TPoint<RawShape>>;
const double norm_; const double norm_;
const double penality_;
using MaxNfpLevel = Nfp::MaxNfpLevel<RawShape>; using MaxNfpLevel = Nfp::MaxNfpLevel<RawShape>;
using sl = ShapeLike; using sl = ShapeLike;
@ -524,8 +527,7 @@ public:
inline explicit _NofitPolyPlacer(const BinType& bin): inline explicit _NofitPolyPlacer(const BinType& bin):
Base(bin), Base(bin),
norm_(std::sqrt(sl::area<RawShape>(bin))), norm_(std::sqrt(sl::area<RawShape>(bin))) {}
penality_(1e6*norm_) {}
_NofitPolyPlacer(const _NofitPolyPlacer&) = default; _NofitPolyPlacer(const _NofitPolyPlacer&) = default;
_NofitPolyPlacer& operator=(const _NofitPolyPlacer&) = default; _NofitPolyPlacer& operator=(const _NofitPolyPlacer&) = default;
@ -575,7 +577,15 @@ public:
return boundingCircle(chull).radius() < bin.radius(); return boundingCircle(chull).radius() < bin.radius();
} }
PackResult trypack(Item& item) { template<class Container>
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; PackResult ret;
@ -586,7 +596,7 @@ public:
can_pack = item.isInside(bin_); can_pack = item.isInside(bin_);
} else { } else {
double global_score = penality_; double global_score = std::numeric_limits<double>::max();
auto initial_tr = item.translation(); auto initial_tr = item.translation();
auto initial_rot = item.rotation(); auto initial_rot = item.rotation();
@ -630,9 +640,8 @@ public:
auto getNfpPoint = [&ecache](const Optimum& opt) 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); ecache[opt.nfpidx].coords(opt.hidx, opt.relpos);
return ret;
}; };
Nfp::Shapes<RawShape> pile; Nfp::Shapes<RawShape> pile;
@ -654,7 +663,7 @@ public:
const Item& item, const Item& item,
double occupied_area, double occupied_area,
double norm, double norm,
double /*penality*/) const ItemGroup& /*remaining*/)
{ {
merged_pile.emplace_back(item.transformedShape()); merged_pile.emplace_back(item.transformedShape());
auto ch = sl::convexHull(merged_pile); auto ch = sl::convexHull(merged_pile);
@ -686,7 +695,7 @@ public:
double occupied_area = pile_area + item.area(); double occupied_area = pile_area + item.area();
double score = _objfunc(pile, item, occupied_area, double score = _objfunc(pile, item, occupied_area,
norm_, penality_); norm_, remaining);
return score; return score;
}; };
@ -705,12 +714,12 @@ public:
}; };
opt::StopCriteria stopcr; opt::StopCriteria stopcr;
stopcr.max_iterations = 100; stopcr.max_iterations = 200;
stopcr.relative_score_difference = 1e-12; stopcr.relative_score_difference = 1e-20;
opt::TOptimizer<opt::Method::L_SUBPLEX> solver(stopcr); opt::TOptimizer<opt::Method::L_SUBPLEX> solver(stopcr);
Optimum optimum(0, 0); Optimum optimum(0, 0);
double best_score = penality_; double best_score = std::numeric_limits<double>::max();
// Local optimization with the four polygon corners as // Local optimization with the four polygon corners as
// starting points // starting points
@ -821,7 +830,6 @@ private:
inline void finalAlign(_Circle<TPoint<RawShape>> cbin) { inline void finalAlign(_Circle<TPoint<RawShape>> cbin) {
if(items_.empty()) return; if(items_.empty()) return;
Nfp::Shapes<RawShape> m; Nfp::Shapes<RawShape> m;
m.reserve(items_.size()); m.reserve(items_.size());
for(Item& item : items_) m.emplace_back(item.transformedShape()); for(Item& item : items_) m.emplace_back(item.transformedShape());
@ -833,6 +841,7 @@ private:
} }
inline void finalAlign(Box bbin) { inline void finalAlign(Box bbin) {
if(items_.empty()) return;
Nfp::Shapes<RawShape> m; Nfp::Shapes<RawShape> m;
m.reserve(items_.size()); m.reserve(items_.size());
for(Item& item : items_) m.emplace_back(item.transformedShape()); for(Item& item : items_) m.emplace_back(item.transformedShape());

View file

@ -56,8 +56,11 @@ public:
config_ = config; config_ = config;
} }
bool pack(Item& item) { template<class Container>
auto&& r = static_cast<Subclass*>(this)->trypack(item); bool pack(Container& items,
typename Container::iterator from,
unsigned count = 1) {
auto&& r = static_cast<Subclass*>(this)->trypack(items, from, count);
if(r) { if(r) {
items_.push_back(*(r.item_ptr_)); items_.push_back(*(r.item_ptr_));
farea_valid_ = false; farea_valid_ = false;

View file

@ -230,7 +230,7 @@ public:
while(it != not_packed.end() && !ret && while(it != not_packed.end() && !ret &&
free_area - (item_area = it->get().area()) <= waste) 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; free_area -= item_area;
filled_area = bin_area - free_area; filled_area = bin_area - free_area;
ret = true; ret = true;
@ -278,7 +278,7 @@ public:
if(item_area + smallestPiece(it, not_packed)->get().area() > if(item_area + smallestPiece(it, not_packed)->get().area() >
free_area ) { it++; continue; } free_area ) { it++; continue; }
auto pr = placer.trypack(*it); auto pr = placer.trypack(not_packed, it);
// First would fit // First would fit
it2 = not_packed.begin(); it2 = not_packed.begin();
@ -294,14 +294,14 @@ public:
} }
placer.accept(pr); placer.accept(pr);
auto pr2 = placer.trypack(*it2); auto pr2 = placer.trypack(not_packed, it2);
if(!pr2) { if(!pr2) {
placer.unpackLast(); // remove first placer.unpackLast(); // remove first
if(try_reverse) { if(try_reverse) {
pr2 = placer.trypack(*it2); pr2 = placer.trypack(not_packed, it2);
if(pr2) { if(pr2) {
placer.accept(pr2); placer.accept(pr2);
auto pr12 = placer.trypack(*it); auto pr12 = placer.trypack(not_packed, it);
if(pr12) { if(pr12) {
placer.accept(pr12); placer.accept(pr12);
ret = true; ret = true;
@ -394,7 +394,7 @@ public:
it++; continue; 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... // Check for free area and try to pack the 1st item...
if(!pr) { it++; continue; } if(!pr) { it++; continue; }
@ -420,15 +420,15 @@ public:
bool can_pack2 = false; bool can_pack2 = false;
placer.accept(pr); placer.accept(pr);
auto pr2 = placer.trypack(*it2); auto pr2 = placer.trypack(not_packed, it2);
auto pr12 = pr; auto pr12 = pr;
if(!pr2) { if(!pr2) {
placer.unpackLast(); // remove first placer.unpackLast(); // remove first
if(try_reverse) { if(try_reverse) {
pr2 = placer.trypack(*it2); pr2 = placer.trypack(not_packed, it2);
if(pr2) { if(pr2) {
placer.accept(pr2); placer.accept(pr2);
pr12 = placer.trypack(*it); pr12 = placer.trypack(not_packed, it);
if(pr12) can_pack2 = true; if(pr12) can_pack2 = true;
placer.unpackLast(); placer.unpackLast();
} }
@ -463,7 +463,7 @@ public:
if(a3_sum > free_area) { it3++; continue; } if(a3_sum > free_area) { it3++; continue; }
placer.accept(pr12); placer.accept(pr2); placer.accept(pr12); placer.accept(pr2);
bool can_pack3 = placer.pack(*it3); bool can_pack3 = placer.pack(not_packed, it3);
if(!can_pack3) { if(!can_pack3) {
placer.unpackLast(); placer.unpackLast();
@ -473,16 +473,16 @@ public:
if(!can_pack3 && try_reverse) { if(!can_pack3 && try_reverse) {
std::array<size_t, 3> indices = {0, 1, 2}; std::array<size_t, 3> indices = {0, 1, 2};
std::array<ItemRef, 3> std::array<typename ItemList::iterator, 3>
candidates = {*it, *it2, *it3}; candidates = {it, it2, it3};
auto tryPack = [&placer, &candidates]( auto tryPack = [&placer, &candidates, &not_packed](
const decltype(indices)& idx) const decltype(indices)& idx)
{ {
std::array<bool, 3> packed = {false}; std::array<bool, 3> packed = {false};
for(auto id : idx) packed.at(id) = for(auto id : idx) packed.at(id) =
placer.pack(candidates[id]); placer.pack(not_packed, candidates[id]);
bool check = bool check =
std::all_of(packed.begin(), std::all_of(packed.begin(),
@ -536,7 +536,7 @@ public:
{ auto it = store_.begin(); { auto it = store_.begin();
while (it != store_.end()) { while (it != store_.end()) {
Placer p(bin); p.configure(pconfig); Placer p(bin); p.configure(pconfig);
if(!p.pack(*it)) { if(!p.pack(store_, it)) {
it = store_.erase(it); it = store_.erase(it);
} else it++; } else it++;
} }
@ -601,7 +601,7 @@ public:
while(it != not_packed.end() && while(it != not_packed.end() &&
filled_area < INITIAL_FILL_AREA) filled_area < INITIAL_FILL_AREA)
{ {
if(placer.pack(*it)) { if(placer.pack(not_packed, it)) {
filled_area += it->get().area(); filled_area += it->get().area();
free_area = bin_area - filled_area; free_area = bin_area - filled_area;
it = not_packed.erase(it); it = not_packed.erase(it);

View file

@ -65,7 +65,7 @@ public:
auto it = store_.begin(); auto it = store_.begin();
while(it != store_.end()) { while(it != store_.end()) {
if(!placer.pack(*it)) { if(!placer.pack(store_, it)) {
if(packed_bins_.back().empty()) ++it; if(packed_bins_.back().empty()) ++it;
// makeProgress(placer); // makeProgress(placer);
placer.clearItems(); placer.clearItems();

View file

@ -40,6 +40,7 @@ public:
packed_bins_.clear(); packed_bins_.clear();
std::vector<Placer> placers; std::vector<Placer> placers;
placers.reserve(last-first);
std::copy(first, last, std::back_inserter(store_)); std::copy(first, last, std::back_inserter(store_));
@ -60,18 +61,19 @@ public:
{ auto it = store_.begin(); { auto it = store_.begin();
while (it != store_.end()) { while (it != store_.end()) {
Placer p(bin); p.configure(pconfig); Placer p(bin); p.configure(pconfig);
if(!p.pack(*it)) { if(!p.pack(store_, it)) {
it = store_.erase(it); it = store_.erase(it);
} else it++; } else it++;
} }
} }
for(auto& item : store_ ) { auto it = store_.begin();
while(it != store_.end()) {
bool was_packed = false; bool was_packed = false;
while(!was_packed) { while(!was_packed) {
for(size_t j = 0; j < placers.size() && !was_packed; j++) { 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); makeProgress(placers[j], j);
} }
@ -81,6 +83,7 @@ public:
packed_bins_.emplace_back(); packed_bins_.emplace_back();
} }
} }
++it;
} }
} }

View file

@ -471,8 +471,8 @@ TEST(GeometryAlgorithms, BottomLeftStressTest) {
auto next = it; auto next = it;
int i = 0; int i = 0;
while(it != input.end() && ++next != input.end()) { while(it != input.end() && ++next != input.end()) {
placer.pack(*it); placer.pack(input, it);
placer.pack(*next); placer.pack(input, next);
auto result = placer.getItems(); auto result = placer.getItems();
bool valid = true; bool valid = true;

View file

@ -99,6 +99,7 @@ namespace bgi = boost::geometry::index;
using SpatElement = std::pair<Box, unsigned>; using SpatElement = std::pair<Box, unsigned>;
using SpatIndex = bgi::rtree< SpatElement, bgi::rstar<16, 4> >; using SpatIndex = bgi::rtree< SpatElement, bgi::rstar<16, 4> >;
using ItemGroup = std::vector<std::reference_wrapper<Item>>;
std::tuple<double /*score*/, Box /*farthest point from bin center*/> std::tuple<double /*score*/, Box /*farthest point from bin center*/>
objfunc(const PointImpl& bincenter, objfunc(const PointImpl& bincenter,
@ -109,24 +110,21 @@ objfunc(const PointImpl& bincenter,
double norm, // A norming factor for physical dimensions double norm, // A norming factor for physical dimensions
std::vector<double>& areacache, // pile item areas will be cached std::vector<double>& areacache, // pile item areas will be cached
// a spatial index to quickly get neighbors of the candidate item // a spatial index to quickly get neighbors of the candidate item
SpatIndex& spatindex SpatIndex& spatindex,
const ItemGroup& remaining
) )
{ {
using pl = PointLike; using pl = PointLike;
using sl = ShapeLike; using sl = ShapeLike;
using Coord = TCoord<PointImpl>;
static const double BIG_ITEM_TRESHOLD = 0.02; static const double BIG_ITEM_TRESHOLD = 0.02;
static const double ROUNDNESS_RATIO = 0.5; static const double ROUNDNESS_RATIO = 0.5;
static const double DENSITY_RATIO = 1.0 - ROUNDNESS_RATIO; static const double DENSITY_RATIO = 1.0 - ROUNDNESS_RATIO;
// We will treat big items (compared to the print bed) differently // We will treat big items (compared to the print bed) differently
auto isBig = [&areacache, bin_area](double a) { auto isBig = [&areacache, bin_area](double a) {
double farea = areacache.empty() ? 0 : areacache.front(); return a/bin_area > BIG_ITEM_TRESHOLD ;
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;
}; };
// If a new bin has been created: // If a new bin has been created:
@ -195,8 +193,19 @@ objfunc(const PointImpl& bincenter,
auto dist = *(std::min_element(dists.begin(), dists.end())) / norm; auto dist = *(std::min_element(dists.begin(), dists.end())) / norm;
// Density is the pack density: how big is the arranged pile // Density is the pack density: how big is the arranged pile
auto density = std::sqrt(fullbb.width()*fullbb.height()) / norm; double density = 0;
if(remaining.empty()) {
pile.emplace_back(item.transformedShape());
auto chull = sl::convexHull(pile);
pile.pop_back();
strategies::EdgeCache<PolygonImpl> ec(chull);
double circ = ec.circumference() / norm;
double bcirc = 2.0*(fullbb.width() + fullbb.height()) / norm;
score = 0.5*circ + 0.5*bcirc;
} else {
// Prepare a variable for the alignment score. // Prepare a variable for the alignment score.
// This will indicate: how well is the candidate item aligned with // This will indicate: how well is the candidate item aligned with
// its neighbors. We will check the aligment with all neighbors and // its neighbors. We will check the aligment with all neighbors and
@ -204,13 +213,32 @@ objfunc(const PointImpl& bincenter,
// candidate to be aligned with only one item. // candidate to be aligned with only one item.
auto alignment_score = std::numeric_limits<double>::max(); auto alignment_score = std::numeric_limits<double>::max();
density = (fullbb.width()*fullbb.height()) / (norm*norm);
auto& trsh = item.transformedShape(); auto& trsh = item.transformedShape();
auto querybb = item.boundingBox(); 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
});
// Query the spatial index for the neigbours // Query the spatial index for the neigbours
std::vector<SpatElement> result; std::vector<SpatElement> result;
spatindex.query(bgi::intersects(querybb), std::back_inserter(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<PolygonImpl>(querybb, ibb) ||
// boost::geometry::intersects(querybb, ibb)) << std::endl;
// }
for(auto& e : result) { // now get the score for the best alignment for(auto& e : result) { // now get the score for the best alignment
auto idx = e.second; auto idx = e.second;
@ -226,8 +254,13 @@ objfunc(const PointImpl& bincenter,
// The final mix of the score is the balance between the distance // The final mix of the score is the balance between the distance
// from the full pile center, the pack density and the // from the full pile center, the pack density and the
// alignment with the neigbours // 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; score = 0.45 * dist + 0.45 * density + 0.1 * alignment_score;
}
} else if( !isBig(item.area()) && spatindex.empty()) { } else if( !isBig(item.area()) && spatindex.empty()) {
// If there are no big items, only small, we should consider the // If there are no big items, only small, we should consider the
// density here as well to not get silly results // density here as well to not get silly results
@ -312,10 +345,12 @@ public:
const Item &item, const Item &item,
double pile_area, double pile_area,
double norm, double norm,
double /*penality*/) { const ItemGroup& rem) {
auto result = objfunc(bin.center(), bin_area_, pile, 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); double score = std::get<0>(result);
auto& fullbb = std::get<1>(result); auto& fullbb = std::get<1>(result);
@ -346,10 +381,11 @@ public:
const Item &item, const Item &item,
double pile_area, double pile_area,
double norm, double norm,
double /*penality*/) { const ItemGroup& rem) {
auto result = objfunc(bin.center(), bin_area_, pile, 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); double score = std::get<0>(result);
auto& fullbb = std::get<1>(result); auto& fullbb = std::get<1>(result);
@ -391,11 +427,12 @@ public:
const Item &item, const Item &item,
double pile_area, double pile_area,
double norm, double norm,
double /*penality*/) { const ItemGroup& rem) {
auto binbb = ShapeLike::boundingBox(bin); auto binbb = ShapeLike::boundingBox(bin);
auto result = objfunc(binbb.center(), bin_area_, pile, 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); double score = std::get<0>(result);
return score; return score;
@ -417,10 +454,11 @@ public:
const Item &item, const Item &item,
double pile_area, double pile_area,
double norm, double norm,
double /*penality*/) { const ItemGroup& rem) {
auto result = objfunc({0, 0}, 0, pile, pile_area, auto result = objfunc({0, 0}, 0, pile, pile_area,
item, norm, areacache_, rtree_); item, norm, areacache_,
rtree_, rem);
return std::get<0>(result); return std::get<0>(result);
}; };