Bug fixes for the neighborhood detection
This commit is contained in:
parent
08fb677583
commit
20b7aad6d1
12 changed files with 190 additions and 206 deletions
|
@ -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)
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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(); }
|
||||||
|
|
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
|
@ -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());
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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, ¬_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);
|
||||||
|
|
|
@ -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();
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue