1 #ifndef NOFITPOLY_HPP
2 #define NOFITPOLY_HPP
3 
4 #include <cassert>
5 
6 // For parallel for
7 #include <functional>
8 #include <iterator>
9 #include <future>
10 #include <atomic>
11 
12 #ifndef NDEBUG
13 #include <iostream>
14 #endif
15 #include <libnest2d/geometry_traits_nfp.hpp>
16 #include <libnest2d/optimizer.hpp>
17 
18 #include "placer_boilerplate.hpp"
19 
20 // temporary
21 //#include "../tools/svgtools.hpp"
22 
23 #include <libnest2d/parallel.hpp>
24 
25 namespace libnest2d {
26 namespace placers {
27 
28 template<class RawShape>
29 struct NfpPConfig {
30 
31     using ItemGroup = _ItemGroup<RawShape>;
32 
33     enum class Alignment {
34         CENTER,
35         BOTTOM_LEFT,
36         BOTTOM_RIGHT,
37         TOP_LEFT,
38         TOP_RIGHT,
39         DONT_ALIGN      //!> Warning: parts may end up outside the bin with the
40                         //! default object function.
41     };
42 
43     /// Which angles to try out for better results.
44     std::vector<Radians> rotations;
45 
46     /// Where to align the resulting packed pile.
47     Alignment alignment;
48 
49     /// Where to start putting objects in the bin.
50     Alignment starting_point;
51 
52     /**
53      * @brief A function object representing the fitting function in the
54      * placement optimization process. (Optional)
55      *
56      * This is the most versatile tool to configure the placer. The fitting
57      * function is evaluated many times when a new item is being placed into the
58      * bin. The output should be a rated score of the new item's position.
59      *
60      * This is not a mandatory option as there is a default fitting function
61      * that will optimize for the best pack efficiency. With a custom fitting
62      * function you can e.g. influence the shape of the arranged pile.
63      *
64      * \param item The only parameter is the candidate item which has info
65      * about its current position. Your job is to rate this position compared to
66      * the already packed items.
67      *
68      */
69     std::function<double(const _Item<RawShape>&)> object_function;
70 
71     /**
72      * @brief The quality of search for an optimal placement.
73      * This is a compromise slider between quality and speed. Zero is the
74      * fast and poor solution while 1.0 is the slowest but most accurate.
75      */
76     float accuracy = 0.65f;
77 
78     /**
79      * @brief If you want to see items inside other item's holes, you have to
80      * turn this switch on.
81      *
82      * This will only work if a suitable nfp implementation is provided.
83      * The library has no such implementation right now.
84      */
85     bool explore_holes = false;
86 
87     /**
88      * @brief If true, use all CPUs available. Run on a single core otherwise.
89      */
90     bool parallel = true;
91 
92     /**
93      * @brief before_packing Callback that is called just before a search for
94      * a new item's position is started. You can use this to create various
95      * cache structures and update them between subsequent packings.
96      *
97      * \param merged pile A polygon that is the union of all items in the bin.
98      *
99      * \param pile The items parameter is a container with all the placed
100      * polygons excluding the current candidate. You can for instance check the
101      * alignment with the candidate item or do anything else.
102      *
103      * \param remaining A container with the remaining items waiting to be
104      * placed. You can use some features about the remaining items to alter the
105      * score of the current placement. If you know that you have to leave place
106      * for other items as well, that might influence your decision about where
107      * the current candidate should be placed. E.g. imagine three big circles
108      * which you want to place into a box: you might place them in a triangle
109      * shape which has the maximum pack density. But if there is a 4th big
110      * circle than you won't be able to pack it. If you knew apriori that
111      * there four circles are to be placed, you would have placed the first 3
112      * into an L shape. This parameter can be used to make these kind of
113      * decisions (for you or a more intelligent AI).
114      */
115     std::function<void(const nfp::Shapes<RawShape>&, // merged pile
116                        const ItemGroup&,             // packed items
117                        const ItemGroup&              // remaining items
118                        )> before_packing;
119 
NfpPConfiglibnest2d::placers::NfpPConfig120     NfpPConfig(): rotations({0.0, Pi/2.0, Pi, 3*Pi/2}),
121         alignment(Alignment::CENTER), starting_point(Alignment::CENTER) {}
122 };
123 
124 /**
125  * A class for getting a point on the circumference of the polygon (in log time)
126  *
127  * This is a transformation of the provided polygon to be able to pinpoint
128  * locations on the circumference. The optimizer will pass a floating point
129  * value e.g. within <0,1> and we have to transform this value quickly into a
130  * coordinate on the circumference. By definition 0 should yield the first
131  * vertex and 1.0 would be the last (which should coincide with first).
132  *
133  * We also have to make this work for the holes of the captured polygon.
134  */
135 template<class RawShape> class EdgeCache {
136     using Vertex = TPoint<RawShape>;
137     using Coord = TCoord<Vertex>;
138     using Edge = _Segment<Vertex>;
139 
140     struct ContourCache {
141         mutable std::vector<double> corners;
142         std::vector<Edge> emap;
143         std::vector<double> distances;
144         double full_distance = 0;
145     } contour_;
146 
147     std::vector<ContourCache> holes_;
148 
149     double accuracy_ = 1.0;
150 
length(const Edge & e)151     static double length(const Edge &e)
152     {
153         return std::sqrt(e.template sqlength<double>());
154     }
155 
createCache(const RawShape & sh)156     void createCache(const RawShape& sh) {
157         {   // For the contour
158             auto first = shapelike::cbegin(sh);
159             auto next = std::next(first);
160             auto endit = shapelike::cend(sh);
161 
162             contour_.distances.reserve(shapelike::contourVertexCount(sh));
163 
164             while(next != endit) {
165                 contour_.emap.emplace_back(*(first++), *(next++));
166                 contour_.full_distance += length(contour_.emap.back());
167                 contour_.distances.emplace_back(contour_.full_distance);
168             }
169         }
170 
171         for(auto& h : shapelike::holes(sh)) { // For the holes
172             auto first = h.begin();
173             auto next = std::next(first);
174             auto endit = h.end();
175 
176             ContourCache hc;
177             hc.distances.reserve(endit - first);
178 
179             while(next != endit) {
180                 hc.emap.emplace_back(*(first++), *(next++));
181                 hc.full_distance += length(hc.emap.back());
182                 hc.distances.emplace_back(hc.full_distance);
183             }
184 
185             holes_.emplace_back(std::move(hc));
186         }
187     }
188 
stride(const size_t N) const189     size_t stride(const size_t N) const {
190         using std::round;
191         using std::pow;
192 
193         return static_cast<size_t>(
194                     round(N/pow(N, pow(accuracy_, 1.0/3.0)))
195                 );
196     }
197 
fetchCorners() const198     void fetchCorners() const {
199         if(!contour_.corners.empty()) return;
200 
201         const auto N = contour_.distances.size();
202         const auto S = stride(N);
203 
204         contour_.corners.reserve(N / S + 1);
205         contour_.corners.emplace_back(0.0);
206         auto N_1 = N-1;
207         contour_.corners.emplace_back(0.0);
208         for(size_t i = 0; i < N_1; i += S) {
209             contour_.corners.emplace_back(
210                     contour_.distances.at(i) / contour_.full_distance);
211         }
212     }
213 
fetchHoleCorners(unsigned hidx) const214     void fetchHoleCorners(unsigned hidx) const {
215         auto& hc = holes_[hidx];
216         if(!hc.corners.empty()) return;
217 
218         const auto N = hc.distances.size();
219         auto N_1 = N-1;
220         const auto S = stride(N);
221         hc.corners.reserve(N / S + 1);
222         hc.corners.emplace_back(0.0);
223         for(size_t i = 0; i < N_1; i += S) {
224             hc.corners.emplace_back(
225                     hc.distances.at(i) / hc.full_distance);
226         }
227     }
228 
coords(const ContourCache & cache,double distance) const229     inline Vertex coords(const ContourCache& cache, double distance) const {
230         assert(distance >= .0 && distance <= 1.0);
231 
232         // distance is from 0.0 to 1.0, we scale it up to the full length of
233         // the circumference
234         double d = distance*cache.full_distance;
235 
236         auto& distances = cache.distances;
237 
238         // Magic: we find the right edge in log time
239         auto it = std::lower_bound(distances.begin(), distances.end(), d);
240         auto idx = it - distances.begin();      // get the index of the edge
241         auto edge = cache.emap[idx];         // extrac the edge
242 
243         // Get the remaining distance on the target edge
244         auto ed = d - (idx > 0 ? *std::prev(it) : 0 );
245         auto angle = edge.angleToXaxis();
246         Vertex ret = edge.first();
247 
248         // Get the point on the edge which lies in ed distance from the start
249         ret += { static_cast<Coord>(std::round(ed*std::cos(angle))),
250                  static_cast<Coord>(std::round(ed*std::sin(angle))) };
251 
252         return ret;
253     }
254 
255 public:
256 
257     using iterator = std::vector<double>::iterator;
258     using const_iterator = std::vector<double>::const_iterator;
259 
260     inline EdgeCache() = default;
261 
EdgeCache(const _Item<RawShape> & item)262     inline EdgeCache(const _Item<RawShape>& item)
263     {
264         createCache(item.transformedShape());
265     }
266 
EdgeCache(const RawShape & sh)267     inline EdgeCache(const RawShape& sh)
268     {
269         createCache(sh);
270     }
271 
272     /// Resolution of returned corners. The stride is derived from this value.
accuracy(double a)273     void accuracy(double a /* within <0.0, 1.0>*/) { accuracy_ = a; }
274 
275     /**
276      * @brief Get a point on the circumference of a polygon.
277      * @param distance A relative distance from the starting point to the end.
278      * Can be from 0.0 to 1.0 where 0.0 is the starting point and 1.0 is the
279      * closing point (which should be eqvivalent with the starting point with
280      * closed polygons).
281      * @return Returns the coordinates of the point lying on the polygon
282      * circumference.
283      */
coords(double distance) const284     inline Vertex coords(double distance) const {
285         return coords(contour_, distance);
286     }
287 
coords(unsigned hidx,double distance) const288     inline Vertex coords(unsigned hidx, double distance) const {
289         assert(hidx < holes_.size());
290         return coords(holes_[hidx], distance);
291     }
292 
circumference() const293     inline double circumference() const BP2D_NOEXCEPT {
294         return contour_.full_distance;
295     }
296 
circumference(unsigned hidx) const297     inline double circumference(unsigned hidx) const BP2D_NOEXCEPT {
298         return holes_[hidx].full_distance;
299     }
300 
301     /// Get the normalized distance values for each vertex
corners() const302     inline const std::vector<double>& corners() const BP2D_NOEXCEPT {
303         fetchCorners();
304         return contour_.corners;
305     }
306 
307     /// corners for a specific hole
308     inline const std::vector<double>&
corners(unsigned holeidx) const309     corners(unsigned holeidx) const BP2D_NOEXCEPT {
310         fetchHoleCorners(holeidx);
311         return holes_[holeidx].corners;
312     }
313 
314     /// The number of holes in the abstracted polygon
holeCount() const315     inline size_t holeCount() const BP2D_NOEXCEPT { return holes_.size(); }
316 
317 };
318 
319 template<nfp::NfpLevel lvl>
320 struct Lvl { static const nfp::NfpLevel value = lvl; };
321 
322 template<class RawShape>
correctNfpPosition(nfp::NfpResult<RawShape> & nfp,const _Item<RawShape> & stationary,const _Item<RawShape> & orbiter)323 inline void correctNfpPosition(nfp::NfpResult<RawShape>& nfp,
324                                const _Item<RawShape>& stationary,
325                                const _Item<RawShape>& orbiter)
326 {
327     // The provided nfp is somewhere in the dark. We need to get it
328     // to the right position around the stationary shape.
329     // This is done by choosing the leftmost lowest vertex of the
330     // orbiting polygon to be touched with the rightmost upper
331     // vertex of the stationary polygon. In this configuration, the
332     // reference vertex of the orbiting polygon (which can be dragged around
333     // the nfp) will be its rightmost upper vertex that coincides with the
334     // rightmost upper vertex of the nfp. No proof provided other than Jonas
335     // Lindmark's reasoning about the reference vertex of nfp in his thesis
336     // ("No fit polygon problem" - section 2.1.9)
337 
338     auto touch_sh = stationary.rightmostTopVertex();
339     auto touch_other = orbiter.leftmostBottomVertex();
340     auto dtouch = touch_sh - touch_other;
341     auto top_other = orbiter.rightmostTopVertex() + dtouch;
342     auto dnfp = top_other - nfp.second; // nfp.second is the nfp reference point
343     shapelike::translate(nfp.first, dnfp);
344 }
345 
346 template<class RawShape>
correctNfpPosition(nfp::NfpResult<RawShape> & nfp,const RawShape & stationary,const _Item<RawShape> & orbiter)347 inline void correctNfpPosition(nfp::NfpResult<RawShape>& nfp,
348                                const RawShape& stationary,
349                                const _Item<RawShape>& orbiter)
350 {
351     auto touch_sh = nfp::rightmostUpVertex(stationary);
352     auto touch_other = orbiter.leftmostBottomVertex();
353     auto dtouch = touch_sh - touch_other;
354     auto top_other = orbiter.rightmostTopVertex() + dtouch;
355     auto dnfp = top_other - nfp.second;
356     shapelike::translate(nfp.first, dnfp);
357 }
358 
359 template<class RawShape, class Circle = _Circle<TPoint<RawShape>> >
minimizeCircle(const RawShape & sh)360 Circle minimizeCircle(const RawShape& sh) {
361     using Point = TPoint<RawShape>;
362     using Coord = TCoord<Point>;
363 
364     auto& ctr = sl::contour(sh);
365     if(ctr.empty()) return {{0, 0}, 0};
366 
367     auto bb = sl::boundingBox(sh);
368     auto capprx = bb.center();
369     auto rapprx = pl::distance(bb.minCorner(), bb.maxCorner());
370 
371 
372     opt::StopCriteria stopcr;
373     stopcr.max_iterations = 30;
374     stopcr.relative_score_difference = 1e-3;
375     opt::TOptimizer<opt::Method::L_SUBPLEX> solver(stopcr);
376 
377     std::vector<double> dists(ctr.size(), 0);
378 
379     auto result = solver.optimize_min(
380         [capprx, rapprx, &ctr, &dists](double xf, double yf) {
381             auto xt = Coord( std::round(getX(capprx) + rapprx*xf) );
382             auto yt = Coord( std::round(getY(capprx) + rapprx*yf) );
383 
384             Point centr(xt, yt);
385 
386             unsigned i = 0;
387             for(auto v : ctr) {
388                 dists[i++] = pl::distance(v, centr);
389             }
390 
391             auto mit = std::max_element(dists.begin(), dists.end());
392 
393             assert(mit != dists.end());
394 
395             return *mit;
396         },
397         opt::initvals(0.0, 0.0),
398         opt::bound(-1.0, 1.0), opt::bound(-1.0, 1.0)
399     );
400 
401     double oxf = std::get<0>(result.optimum);
402     double oyf = std::get<1>(result.optimum);
403     auto xt = Coord( std::round(getX(capprx) + rapprx*oxf) );
404     auto yt = Coord( std::round(getY(capprx) + rapprx*oyf) );
405 
406     Point cc(xt, yt);
407     auto r = result.score;
408 
409     return {cc, r};
410 }
411 
412 template<class RawShape>
boundingCircle(const RawShape & sh)413 _Circle<TPoint<RawShape>> boundingCircle(const RawShape& sh) {
414     return minimizeCircle(sh);
415 }
416 
417 template<class RawShape, class TBin = _Box<TPoint<RawShape>>>
418 class _NofitPolyPlacer: public PlacerBoilerplate<_NofitPolyPlacer<RawShape, TBin>,
419         RawShape, TBin, NfpPConfig<RawShape>> {
420 
421     using Base = PlacerBoilerplate<_NofitPolyPlacer<RawShape, TBin>,
422     RawShape, TBin, NfpPConfig<RawShape>>;
423 
424     DECLARE_PLACER(Base)
425 
426     using Box = _Box<TPoint<RawShape>>;
427 
428     using MaxNfpLevel = nfp::MaxNfpLevel<RawShape>;
429 
430     // Norming factor for the optimization function
431     const double norm_;
432 
433 public:
434 
435     using Pile = nfp::Shapes<RawShape>;
436 
_NofitPolyPlacer(const BinType & bin)437     inline explicit _NofitPolyPlacer(const BinType& bin):
438         Base(bin),
439         norm_(std::sqrt(sl::area(bin)))
440     {
441         // In order to not have items out of bin, it will be shrinked by an
442         // very little empiric offset value.
443         // sl::offset(bin_, 1e-5 * norm_);
444     }
445 
446     _NofitPolyPlacer(const _NofitPolyPlacer&) = default;
447     _NofitPolyPlacer& operator=(const _NofitPolyPlacer&) = default;
448 
449 #ifndef BP2D_COMPILER_MSVC12 // MSVC2013 does not support default move ctors
450     _NofitPolyPlacer(_NofitPolyPlacer&&) = default;
451     _NofitPolyPlacer& operator=(_NofitPolyPlacer&&) = default;
452 #endif
453 
overfit(const Box & bb,const RawShape & bin)454     static inline double overfit(const Box& bb, const RawShape& bin) {
455         auto bbin = sl::boundingBox(bin);
456         auto d = bbin.center() - bb.center();
457         _Rectangle<RawShape> rect(bb.width(), bb.height());
458         rect.translate(bb.minCorner() + d);
459         return sl::isInside(rect.transformedShape(), bin) ? -1.0 : 1;
460     }
461 
overfit(const RawShape & chull,const RawShape & bin)462     static inline double overfit(const RawShape& chull, const RawShape& bin) {
463         auto bbch = sl::boundingBox(chull);
464         auto bbin = sl::boundingBox(bin);
465         auto d =  bbch.center() - bbin.center();
466         auto chullcpy = chull;
467         sl::translate(chullcpy, d);
468         return sl::isInside(chullcpy, bin) ? -1.0 : 1.0;
469     }
470 
overfit(const RawShape & chull,const Box & bin)471     static inline double overfit(const RawShape& chull, const Box& bin)
472     {
473         auto bbch = sl::boundingBox(chull);
474         return overfit(bbch, bin);
475     }
476 
overfit(const Box & bb,const Box & bin)477     static inline double overfit(const Box& bb, const Box& bin)
478     {
479         auto Bw = bin.width();
480         auto Bh = bin.height();
481         auto mBw = -Bw;
482         auto mBh = -Bh;
483         auto wdiff = double(bb.width()) + mBw;
484         auto hdiff = double(bb.height()) + mBh;
485         double diff = 0;
486         if(wdiff > 0) diff += wdiff;
487         if(hdiff > 0) diff += hdiff;
488         return diff;
489     }
490 
overfit(const Box & bb,const _Circle<Vertex> & bin)491     static inline double overfit(const Box& bb, const _Circle<Vertex>& bin)
492     {
493         double boxr = 0.5*pl::distance(bb.minCorner(), bb.maxCorner());
494         double diff = boxr - bin.radius();
495         return diff;
496     }
497 
overfit(const RawShape & chull,const _Circle<Vertex> & bin)498     static inline double overfit(const RawShape& chull,
499                                 const _Circle<Vertex>& bin)
500     {
501         double r = boundingCircle(chull).radius();
502         double diff = r - bin.radius();
503         return diff;
504     }
505 
506     template<class Range = ConstItemRange<typename Base::DefaultIter>>
trypack(Item & item,const Range & remaining=Range ())507     PackResult trypack(Item& item,
508                         const Range& remaining = Range()) {
509         auto result = _trypack(item, remaining);
510 
511         // Experimental
512         // if(!result) repack(item, result);
513 
514         return result;
515     }
516 
~_NofitPolyPlacer()517     ~_NofitPolyPlacer() {
518         clearItems();
519     }
520 
clearItems()521     inline void clearItems() {
522         finalAlign(bin_);
523         Base::clearItems();
524     }
525 
526 private:
527 
528     using Shapes = TMultiShape<RawShape>;
529 
calcnfp(const Item & trsh,Lvl<nfp::NfpLevel::CONVEX_ONLY>)530     Shapes calcnfp(const Item &trsh, Lvl<nfp::NfpLevel::CONVEX_ONLY>)
531     {
532         using namespace nfp;
533 
534         Shapes nfps(items_.size());
535 
536         // /////////////////////////////////////////////////////////////////////
537         // TODO: this is a workaround and should be solved in Item with mutexes
538         // guarding the mutable members when writing them.
539         // /////////////////////////////////////////////////////////////////////
540         trsh.transformedShape();
541         trsh.referenceVertex();
542         trsh.rightmostTopVertex();
543         trsh.leftmostBottomVertex();
544 
545         for(Item& itm : items_) {
546             itm.transformedShape();
547             itm.referenceVertex();
548             itm.rightmostTopVertex();
549             itm.leftmostBottomVertex();
550         }
551         // /////////////////////////////////////////////////////////////////////
552 
553         __parallel::enumerate(items_.begin(), items_.end(),
554                               [&nfps, &trsh](const Item& sh, size_t n)
555         {
556             auto& fixedp = sh.transformedShape();
557             auto& orbp = trsh.transformedShape();
558             auto subnfp_r = noFitPolygon<NfpLevel::CONVEX_ONLY>(fixedp, orbp);
559             correctNfpPosition(subnfp_r, sh, trsh);
560             nfps[n] = subnfp_r.first;
561         });
562 
563         return nfp::merge(nfps);
564     }
565 
566 
567     template<class Level>
calcnfp(const Item & trsh,Level)568     Shapes calcnfp(const Item &trsh, Level)
569     { // Function for arbitrary level of nfp implementation
570         using namespace nfp;
571 
572         Shapes nfps;
573 
574         auto& orb = trsh.transformedShape();
575         bool orbconvex = trsh.isContourConvex();
576 
577         for(Item& sh : items_) {
578             nfp::NfpResult<RawShape> subnfp;
579             auto& stat = sh.transformedShape();
580 
581             if(sh.isContourConvex() && orbconvex)
582                 subnfp = nfp::noFitPolygon<NfpLevel::CONVEX_ONLY>(stat, orb);
583             else if(orbconvex)
584                 subnfp = nfp::noFitPolygon<NfpLevel::ONE_CONVEX>(stat, orb);
585             else
586                 subnfp = nfp::noFitPolygon<Level::value>(stat, orb);
587 
588             correctNfpPosition(subnfp, sh, trsh);
589 
590             nfps = nfp::merge(nfps, subnfp.first);
591         }
592 
593         return nfps;
594     }
595 
596     // Very much experimental
repack(Item & item,PackResult & result)597     void repack(Item& item, PackResult& result) {
598 
599         if((sl::area(bin_) - this->filledArea()) >= item.area()) {
600             auto prev_func = config_.object_function;
601 
602             unsigned iter = 0;
603             ItemGroup backup_rf = items_;
604             std::vector<Item> backup_cpy;
605             for(Item& itm : items_) backup_cpy.emplace_back(itm);
606 
607             auto ofn = [this, &item, &result, &iter, &backup_cpy, &backup_rf]
608                     (double ratio)
609             {
610                 auto& bin = bin_;
611                 iter++;
612                 config_.object_function = [bin, ratio](
613                         nfp::Shapes<RawShape>& pile,
614                         const Item& item,
615                         const ItemGroup& /*remaining*/)
616                 {
617                     pile.emplace_back(item.transformedShape());
618                     auto ch = sl::convexHull(pile);
619                     auto pbb = sl::boundingBox(pile);
620                     pile.pop_back();
621 
622                     double parea = 0.5*(sl::area(ch) + sl::area(pbb));
623 
624                     double pile_area = std::accumulate(
625                                 pile.begin(), pile.end(), item.area(),
626                                 [](double sum, const RawShape& sh){
627                         return sum + sl::area(sh);
628                     });
629 
630                     // The pack ratio -- how much is the convex hull occupied
631                     double pack_rate = (pile_area)/parea;
632 
633                     // ratio of waste
634                     double waste = 1.0 - pack_rate;
635 
636                     // Score is the square root of waste. This will extend the
637                     // range of good (lower) values and shrink the range of bad
638                     // (larger) values.
639                     auto wscore = std::sqrt(waste);
640 
641 
642                     auto ibb = item.boundingBox();
643                     auto bbb = sl::boundingBox(bin);
644                     auto c = ibb.center();
645                     double norm = 0.5*pl::distance(bbb.minCorner(),
646                                                    bbb.maxCorner());
647 
648                     double dscore = pl::distance(c, pbb.center()) / norm;
649 
650                     return ratio*wscore + (1.0 - ratio) * dscore;
651                 };
652 
653                 auto bb = sl::boundingBox(bin);
654                 double norm = bb.width() + bb.height();
655 
656                 auto items = items_;
657                 clearItems();
658                 auto it = items.begin();
659                 while(auto pr = _trypack(*it++)) {
660                     this->accept(pr); if(it == items.end()) break;
661                 }
662 
663                 auto count_diff = items.size() - items_.size();
664                 double score = count_diff;
665 
666                 if(count_diff == 0) {
667                     result = _trypack(item);
668 
669                     if(result) {
670                         std::cout << "Success" << std::endl;
671                         score = 0.0;
672                     } else {
673                         score += result.overfit() / norm;
674                     }
675                 } else {
676                     result = PackResult();
677                     items_ = backup_rf;
678                     for(unsigned i = 0; i < items_.size(); i++) {
679                         items_[i].get() = backup_cpy[i];
680                     }
681                 }
682 
683                 std::cout << iter << " repack result: " << score << " "
684                           << ratio << " " << count_diff << std::endl;
685 
686                 return score;
687             };
688 
689                 opt::StopCriteria stopcr;
690                 stopcr.max_iterations = 30;
691                 stopcr.stop_score = 1e-20;
692                 opt::TOptimizer<opt::Method::L_SUBPLEX> solver(stopcr);
693                 solver.optimize_min(ofn, opt::initvals(0.5),
694                                     opt::bound(0.0, 1.0));
695 
696             // optimize
697             config_.object_function = prev_func;
698         }
699     }
700 
701     struct Optimum {
702         double relpos;
703         unsigned nfpidx;
704         int hidx;
Optimumlibnest2d::placers::_NofitPolyPlacer::Optimum705         Optimum(double pos, unsigned nidx):
706             relpos(pos), nfpidx(nidx), hidx(-1) {}
Optimumlibnest2d::placers::_NofitPolyPlacer::Optimum707         Optimum(double pos, unsigned nidx, int holeidx):
708             relpos(pos), nfpidx(nidx), hidx(holeidx) {}
709     };
710 
711     class Optimizer: public opt::TOptimizer<opt::Method::L_SUBPLEX> {
712     public:
Optimizer(float accuracy=1.f)713         Optimizer(float accuracy = 1.f) {
714             opt::StopCriteria stopcr;
715             stopcr.max_iterations = unsigned(std::floor(1000 * accuracy));
716             stopcr.relative_score_difference = 1e-20;
717             this->stopcr_ = stopcr;
718         }
719     };
720 
721     using Edges = EdgeCache<RawShape>;
722 
723     template<class Range = ConstItemRange<typename Base::DefaultIter>>
_trypack(Item & item,const Range & remaining=Range ())724     PackResult _trypack(
725             Item& item,
726             const Range& remaining = Range()) {
727 
728         PackResult ret;
729 
730         bool can_pack = false;
731         double best_overfit = std::numeric_limits<double>::max();
732 
733         ItemGroup remlist;
734         if(remaining.valid) {
735             remlist.insert(remlist.end(), remaining.from, remaining.to);
736         }
737 
738         if(std::all_of(items_.begin(), items_.end(),
739                 [](const Item& item) { return item.isDisallowedArea(); })) {
740             setInitialPosition(item);
741             best_overfit = overfit(item.transformedShape(), bin_);
742             can_pack = best_overfit <= 0;
743         } else {
744 
745             double global_score = std::numeric_limits<double>::max();
746 
747             auto initial_tr = item.translation();
748             auto initial_rot = item.rotation();
749             Vertex final_tr = {0, 0};
750             Radians final_rot = initial_rot;
751             Shapes nfps;
752 
753             for(auto rot : config_.rotations) {
754 
755                 item.translation(initial_tr);
756                 item.rotation(initial_rot + rot);
757                 item.boundingBox(); // fill the bb cache
758 
759                 // place the new item outside of the print bed to make sure
760                 // it is disjunct from the current merged pile
761                 placeOutsideOfBin(item);
762 
763                 nfps = calcnfp(item, Lvl<MaxNfpLevel::value>());
764 
765                 auto iv = item.referenceVertex();
766 
767                 auto startpos = item.translation();
768 
769                 std::vector<Edges> ecache;
770                 ecache.reserve(nfps.size());
771 
772                 for(auto& nfp : nfps ) {
773                     ecache.emplace_back(nfp);
774                     ecache.back().accuracy(config_.accuracy);
775                 }
776 
777                 Shapes pile;
778                 pile.reserve(items_.size()+1);
779                 // double pile_area = 0;
780                 for(Item& mitem : items_) {
781                     pile.emplace_back(mitem.transformedShape());
782                     // pile_area += mitem.area();
783                 }
784 
785                 auto merged_pile = nfp::merge(pile);
786                 auto& bin = bin_;
787                 double norm = norm_;
788                 auto pbb = sl::boundingBox(merged_pile);
789                 auto binbb = sl::boundingBox(bin);
790 
791                 // This is the kernel part of the object function that is
792                 // customizable by the library client
793                 std::function<double(const Item&)> _objfunc;
794                 if(config_.object_function) _objfunc = config_.object_function;
795                 else {
796 
797                     // Inside check has to be strict if no alignment was enabled
798                     std::function<double(const Box&)> ins_check;
799                     if(config_.alignment == Config::Alignment::DONT_ALIGN)
800                         ins_check = [&binbb, norm](const Box& fullbb) {
801                             double ret = 0;
802                             if(!sl::isInside(fullbb, binbb))
803                                 ret += norm;
804                             return ret;
805                         };
806                     else
807                         ins_check = [&bin](const Box& fullbb) {
808                             double miss = overfit(fullbb, bin);
809                             miss = miss > 0? miss : 0;
810                             return std::pow(miss, 2);
811                         };
812 
813                     _objfunc = [norm, binbb, pbb, ins_check](const Item& item)
814                     {
815                         auto ibb = item.boundingBox();
816                         auto fullbb = sl::boundingBox(pbb, ibb);
817 
818                         double score = pl::distance(ibb.center(),
819                                                     binbb.center());
820                         score /= norm;
821 
822                         score += ins_check(fullbb);
823 
824                         return score;
825                     };
826                 }
827 
828                 // Our object function for placement
829                 auto rawobjfunc = [_objfunc, iv, startpos]
830                         (Vertex v, Item& itm)
831                 {
832                     auto d = v - iv;
833                     d += startpos;
834                     itm.translation(d);
835                     return _objfunc(itm);
836                 };
837 
838                 auto getNfpPoint = [&ecache](const Optimum& opt)
839                 {
840                     return opt.hidx < 0? ecache[opt.nfpidx].coords(opt.relpos) :
841                             ecache[opt.nfpidx].coords(opt.hidx, opt.relpos);
842                 };
843 
844                 auto alignment = config_.alignment;
845 
846                 auto boundaryCheck = [alignment, &merged_pile, &getNfpPoint,
847                         &item, &bin, &iv, &startpos] (const Optimum& o)
848                 {
849                     auto v = getNfpPoint(o);
850                     auto d = v - iv;
851                     d += startpos;
852                     item.translation(d);
853 
854                     merged_pile.emplace_back(item.transformedShape());
855                     auto chull = sl::convexHull(merged_pile);
856                     merged_pile.pop_back();
857 
858                     double miss = 0;
859                     if(alignment == Config::Alignment::DONT_ALIGN)
860                        miss = sl::isInside(chull, bin) ? -1.0 : 1.0;
861                     else miss = overfit(chull, bin);
862 
863                     return miss;
864                 };
865 
866                 Optimum optimum(0, 0);
867                 double best_score = std::numeric_limits<double>::max();
868                 std::launch policy = std::launch::deferred;
869                 if(config_.parallel) policy |= std::launch::async;
870 
871                 if(config_.before_packing)
872                     config_.before_packing(merged_pile, items_, remlist);
873 
874                 using OptResult = opt::Result<double>;
875                 using OptResults = std::vector<OptResult>;
876 
877                 // Local optimization with the four polygon corners as
878                 // starting points
879                 for(unsigned ch = 0; ch < ecache.size(); ch++) {
880                     auto& cache = ecache[ch];
881 
882                     OptResults results(cache.corners().size());
883 
884                     auto& rofn = rawobjfunc;
885                     auto& nfpoint = getNfpPoint;
886                     float accuracy = config_.accuracy;
887 
888                     __parallel::enumerate(
889                                 cache.corners().begin(),
890                                 cache.corners().end(),
891                                 [&results, &item, &rofn, &nfpoint, ch, accuracy]
892                                 (double pos, size_t n)
893                     {
894                         Optimizer solver(accuracy);
895 
896                         Item itemcpy = item;
897                         auto contour_ofn = [&rofn, &nfpoint, ch, &itemcpy]
898                                 (double relpos)
899                         {
900                             Optimum op(relpos, ch);
901                             return rofn(nfpoint(op), itemcpy);
902                         };
903 
904                         try {
905                             results[n] = solver.optimize_min(contour_ofn,
906                                             opt::initvals<double>(pos),
907                                             opt::bound<double>(0, 1.0)
908                                             );
909                         } catch(std::exception& e) {
910                             derr() << "ERROR: " << e.what() << "\n";
911                         }
912                     }, policy);
913 
914                     auto resultcomp =
915                             []( const OptResult& r1, const OptResult& r2 ) {
916                         return r1.score < r2.score;
917                     };
918 
919                     auto mr = *std::min_element(results.begin(), results.end(),
920                                                 resultcomp);
921 
922                     if(mr.score < best_score) {
923                         Optimum o(std::get<0>(mr.optimum), ch, -1);
924                         double miss = boundaryCheck(o);
925                         if(miss <= 0) {
926                             best_score = mr.score;
927                             optimum = o;
928                         } else {
929                             best_overfit = std::min(miss, best_overfit);
930                         }
931                     }
932 
933                     for(unsigned hidx = 0; hidx < cache.holeCount(); ++hidx) {
934                         results.clear();
935                         results.resize(cache.corners(hidx).size());
936 
937                         // TODO : use parallel for
938                         __parallel::enumerate(cache.corners(hidx).begin(),
939                                       cache.corners(hidx).end(),
940                                       [&results, &item, &nfpoint,
941                                        &rofn, ch, hidx, accuracy]
942                                       (double pos, size_t n)
943                         {
944                             Optimizer solver(accuracy);
945 
946                             Item itmcpy = item;
947                             auto hole_ofn =
948                                     [&rofn, &nfpoint, ch, hidx, &itmcpy]
949                                     (double pos)
950                             {
951                                 Optimum opt(pos, ch, hidx);
952                                 return rofn(nfpoint(opt), itmcpy);
953                             };
954 
955                             try {
956                                 results[n] = solver.optimize_min(hole_ofn,
957                                                 opt::initvals<double>(pos),
958                                                 opt::bound<double>(0, 1.0)
959                                                 );
960 
961                             } catch(std::exception& e) {
962                                 derr() << "ERROR: " << e.what() << "\n";
963                             }
964                         }, policy);
965 
966                         auto hmr = *std::min_element(results.begin(),
967                                                     results.end(),
968                                                     resultcomp);
969 
970                         if(hmr.score < best_score) {
971                             Optimum o(std::get<0>(hmr.optimum),
972                                       ch, hidx);
973                             double miss = boundaryCheck(o);
974                             if(miss <= 0.0) {
975                                 best_score = hmr.score;
976                                 optimum = o;
977                             } else {
978                                 best_overfit = std::min(miss, best_overfit);
979                             }
980                         }
981                     }
982                 }
983 
984                 if( best_score < global_score ) {
985                     auto d = getNfpPoint(optimum) - iv;
986                     d += startpos;
987                     final_tr = d;
988                     final_rot = initial_rot + rot;
989                     can_pack = true;
990                     global_score = best_score;
991                 }
992             }
993 
994             item.translation(final_tr);
995             item.rotation(final_rot);
996         }
997 
998         if(can_pack) {
999             ret = PackResult(item);
1000         } else {
1001             ret = PackResult(best_overfit);
1002         }
1003 
1004         return ret;
1005     }
1006 
finalAlign(const RawShape & pbin)1007     inline void finalAlign(const RawShape& pbin) {
1008         auto bbin = sl::boundingBox(pbin);
1009         finalAlign(bbin);
1010     }
1011 
finalAlign(_Circle<TPoint<RawShape>> cbin)1012     inline void finalAlign(_Circle<TPoint<RawShape>> cbin) {
1013         if(items_.empty() ||
1014                 config_.alignment == Config::Alignment::DONT_ALIGN) return;
1015 
1016         nfp::Shapes<RawShape> m;
1017         m.reserve(items_.size());
1018         for(Item& item : items_) m.emplace_back(item.transformedShape());
1019 
1020         auto c = boundingCircle(sl::convexHull(m));
1021 
1022         auto d = cbin.center() - c.center();
1023         for(Item& item : items_) item.translate(d);
1024     }
1025 
finalAlign(Box bbin)1026     inline void finalAlign(Box bbin) {
1027         if(items_.empty() ||
1028                 config_.alignment == Config::Alignment::DONT_ALIGN) return;
1029 
1030         nfp::Shapes<RawShape> m;
1031         m.reserve(items_.size());
1032         for(Item& item : items_) m.emplace_back(item.transformedShape());
1033         auto&& bb = sl::boundingBox(m);
1034 
1035         Vertex ci, cb;
1036 
1037         switch(config_.alignment) {
1038         case Config::Alignment::CENTER: {
1039             ci = bb.center();
1040             cb = bbin.center();
1041             break;
1042         }
1043         case Config::Alignment::BOTTOM_LEFT: {
1044             ci = bb.minCorner();
1045             cb = bbin.minCorner();
1046             break;
1047         }
1048         case Config::Alignment::BOTTOM_RIGHT: {
1049             ci = {getX(bb.maxCorner()), getY(bb.minCorner())};
1050             cb = {getX(bbin.maxCorner()), getY(bbin.minCorner())};
1051             break;
1052         }
1053         case Config::Alignment::TOP_LEFT: {
1054             ci = {getX(bb.minCorner()), getY(bb.maxCorner())};
1055             cb = {getX(bbin.minCorner()), getY(bbin.maxCorner())};
1056             break;
1057         }
1058         case Config::Alignment::TOP_RIGHT: {
1059             ci = bb.maxCorner();
1060             cb = bbin.maxCorner();
1061             break;
1062         }
1063         default: ; // DONT_ALIGN
1064         }
1065 
1066         auto d = cb - ci;
1067         for(Item& item : items_) item.translate(d);
1068     }
1069 
setInitialPosition(Item & item)1070     void setInitialPosition(Item& item) {
1071         auto sh = item.rawShape();
1072         sl::translate(sh, item.translation());
1073         sl::rotate(sh, item.rotation());
1074 
1075         Box bb = sl::boundingBox(sh);
1076 
1077         Vertex ci, cb;
1078         auto bbin = sl::boundingBox(bin_);
1079 
1080         switch(config_.starting_point) {
1081         case Config::Alignment::CENTER: {
1082             ci = bb.center();
1083             cb = bbin.center();
1084             break;
1085         }
1086         case Config::Alignment::BOTTOM_LEFT: {
1087             ci = bb.minCorner();
1088             cb = bbin.minCorner();
1089             break;
1090         }
1091         case Config::Alignment::BOTTOM_RIGHT: {
1092             ci = {getX(bb.maxCorner()), getY(bb.minCorner())};
1093             cb = {getX(bbin.maxCorner()), getY(bbin.minCorner())};
1094             break;
1095         }
1096         case Config::Alignment::TOP_LEFT: {
1097             ci = {getX(bb.minCorner()), getY(bb.maxCorner())};
1098             cb = {getX(bbin.minCorner()), getY(bbin.maxCorner())};
1099             break;
1100         }
1101         case Config::Alignment::TOP_RIGHT: {
1102             ci = bb.maxCorner();
1103             cb = bbin.maxCorner();
1104             break;
1105         }
1106         default:;
1107         }
1108 
1109         auto d = cb - ci;
1110         item.translate(d);
1111     }
1112 
placeOutsideOfBin(Item & item)1113     void placeOutsideOfBin(Item& item) {
1114         auto&& bb = item.boundingBox();
1115         Box binbb = sl::boundingBox(bin_);
1116 
1117         Vertex v = { getX(bb.maxCorner()), getY(bb.minCorner()) };
1118 
1119         Coord dx = getX(binbb.maxCorner()) - getX(v);
1120         Coord dy = getY(binbb.maxCorner()) - getY(v);
1121 
1122         item.translate({dx, dy});
1123     }
1124 
1125 };
1126 
1127 
1128 }
1129 }
1130 
1131 #endif // NOFITPOLY_H
1132