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