Bug fixes for the neighborhood detection
This commit is contained in:
parent
08fb677583
commit
20b7aad6d1
@ -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)
|
||||
|
@ -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<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;
|
||||
// sconf.allow_parallel = false;
|
||||
// sconf.force_parallel = false;
|
||||
|
@ -313,9 +313,9 @@ inline RawPoint _Box<RawPoint>::center() const BP2D_NOEXCEPT {
|
||||
|
||||
using Coord = TCoord<RawPoint>;
|
||||
|
||||
RawPoint ret = {
|
||||
static_cast<Coord>( std::round((getX(minc) + getX(maxc))/2.0) ),
|
||||
static_cast<Coord>( std::round((getY(minc) + getY(maxc))/2.0) )
|
||||
RawPoint ret = { // No rounding here, we dont know if these are int coords
|
||||
static_cast<Coord>( (getX(minc) + getX(maxc))/2.0 ),
|
||||
static_cast<Coord>( (getY(minc) + getY(maxc))/2.0 )
|
||||
};
|
||||
|
||||
return ret;
|
||||
|
@ -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<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.
|
||||
@ -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<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).
|
||||
inline void unpackLast() { impl_.unpackLast(); }
|
||||
|
@ -27,9 +27,14 @@ public:
|
||||
|
||||
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);
|
||||
if(!r && Base::config_.allow_rotations) {
|
||||
|
||||
item.rotate(Degrees(90));
|
||||
r =_trypack(item);
|
||||
}
|
||||
|
@ -19,6 +19,8 @@ namespace libnest2d { namespace strategies {
|
||||
template<class RawShape>
|
||||
struct NfpPConfig {
|
||||
|
||||
using ItemGroup = std::vector<std::reference_wrapper<_Item<RawShape>>>;
|
||||
|
||||
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<double(Nfp::Shapes<RawShape>&, const _Item<RawShape>&,
|
||||
double, double, double)>
|
||||
double, double, const ItemGroup&)>
|
||||
object_function;
|
||||
|
||||
/**
|
||||
@ -450,11 +452,13 @@ _Circle<TPoint<RawShape>> minimizeCircle(const RawShape& sh) {
|
||||
using Point = TPoint<RawShape>;
|
||||
using Coord = TCoord<Point>;
|
||||
|
||||
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<RawShape, TBin
|
||||
using Box = _Box<TPoint<RawShape>>;
|
||||
|
||||
const double norm_;
|
||||
const double penality_;
|
||||
|
||||
using MaxNfpLevel = Nfp::MaxNfpLevel<RawShape>;
|
||||
using sl = ShapeLike;
|
||||
@ -524,8 +527,7 @@ public:
|
||||
|
||||
inline explicit _NofitPolyPlacer(const BinType& bin):
|
||||
Base(bin),
|
||||
norm_(std::sqrt(sl::area<RawShape>(bin))),
|
||||
penality_(1e6*norm_) {}
|
||||
norm_(std::sqrt(sl::area<RawShape>(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<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;
|
||||
|
||||
@ -586,7 +596,7 @@ public:
|
||||
can_pack = item.isInside(bin_);
|
||||
} else {
|
||||
|
||||
double global_score = penality_;
|
||||
double global_score = std::numeric_limits<double>::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<RawShape> 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<opt::Method::L_SUBPLEX> solver(stopcr);
|
||||
|
||||
Optimum optimum(0, 0);
|
||||
double best_score = penality_;
|
||||
double best_score = std::numeric_limits<double>::max();
|
||||
|
||||
// Local optimization with the four polygon corners as
|
||||
// starting points
|
||||
@ -821,7 +830,6 @@ private:
|
||||
|
||||
inline void finalAlign(_Circle<TPoint<RawShape>> cbin) {
|
||||
if(items_.empty()) return;
|
||||
|
||||
Nfp::Shapes<RawShape> 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<RawShape> m;
|
||||
m.reserve(items_.size());
|
||||
for(Item& item : items_) m.emplace_back(item.transformedShape());
|
||||
|
@ -56,8 +56,11 @@ public:
|
||||
config_ = config;
|
||||
}
|
||||
|
||||
bool pack(Item& item) {
|
||||
auto&& r = static_cast<Subclass*>(this)->trypack(item);
|
||||
template<class Container>
|
||||
bool pack(Container& items,
|
||||
typename Container::iterator from,
|
||||
unsigned count = 1) {
|
||||
auto&& r = static_cast<Subclass*>(this)->trypack(items, from, count);
|
||||
if(r) {
|
||||
items_.push_back(*(r.item_ptr_));
|
||||
farea_valid_ = false;
|
||||
|
@ -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<size_t, 3> indices = {0, 1, 2};
|
||||
std::array<ItemRef, 3>
|
||||
candidates = {*it, *it2, *it3};
|
||||
std::array<typename ItemList::iterator, 3>
|
||||
candidates = {it, it2, it3};
|
||||
|
||||
auto tryPack = [&placer, &candidates](
|
||||
auto tryPack = [&placer, &candidates, ¬_packed](
|
||||
const decltype(indices)& idx)
|
||||
{
|
||||
std::array<bool, 3> 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);
|
||||
|
@ -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();
|
||||
|
@ -40,6 +40,7 @@ public:
|
||||
packed_bins_.clear();
|
||||
|
||||
std::vector<Placer> 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;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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;
|
||||
|
@ -99,6 +99,7 @@ namespace bgi = boost::geometry::index;
|
||||
|
||||
using SpatElement = std::pair<Box, unsigned>;
|
||||
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*/>
|
||||
objfunc(const PointImpl& bincenter,
|
||||
@ -109,24 +110,21 @@ objfunc(const PointImpl& bincenter,
|
||||
double norm, // A norming factor for physical dimensions
|
||||
std::vector<double>& 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<PointImpl>;
|
||||
|
||||
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<double>::max();
|
||||
if(remaining.empty()) {
|
||||
pile.emplace_back(item.transformedShape());
|
||||
auto chull = sl::convexHull(pile);
|
||||
pile.pop_back();
|
||||
strategies::EdgeCache<PolygonImpl> 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<double>::max();
|
||||
|
||||
// Query the spatial index for the neigbours
|
||||
std::vector<SpatElement> 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<PolygonImpl>{p, trsh});
|
||||
auto bbarea = bb.area();
|
||||
auto ascore = 1.0 - (item.area() + parea)/bbarea;
|
||||
// Query the spatial index for the neigbours
|
||||
std::vector<SpatElement> 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
|
||||
auto idx = e.second;
|
||||
auto& p = pile[idx];
|
||||
auto parea = areacache[idx];
|
||||
auto bb = sl::boundingBox(sl::Shapes<PolygonImpl>{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);
|
||||
};
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user