From 8da8ecb4157aefa24e3ff4d13367bf94d0f1952e Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Fri, 3 Aug 2018 14:49:26 +0200 Subject: [PATCH 01/10] Bed shape detection in progress --- xs/src/libslic3r/ModelArrange.hpp | 164 ++++++++++++++++++++++++++++-- xs/src/slic3r/AppController.cpp | 7 +- 2 files changed, 161 insertions(+), 10 deletions(-) diff --git a/xs/src/libslic3r/ModelArrange.hpp b/xs/src/libslic3r/ModelArrange.hpp index f2d399ac6..5b7a2a325 100644 --- a/xs/src/libslic3r/ModelArrange.hpp +++ b/xs/src/libslic3r/ModelArrange.hpp @@ -292,6 +292,7 @@ public: template inline IndexedPackGroup operator()(Args&&...args) { areacache_.clear(); + rtree_.clear(); return pck_.arrangeIndexed(std::forward(args)...); } }; @@ -440,16 +441,157 @@ ShapeData2D projectModelFromTop(const Slic3r::Model &model) { return ret; } -enum BedShapeHint { +class Circle { + Point center_; + double radius_; +public: + + inline Circle(): center_(0, 0), radius_(std::nan("")) {} + inline Circle(const Point& c, double r): center_(c), radius_(r) {} + + inline double radius() const { return radius_; } + inline const Point& center() const { return center_; } + inline operator bool() { return std::isnan(radius_); } +}; + + +Circle circle(std::array P) { + + using Coord = Point::coord_type; + using std::pow; + using std::abs; + using std::round; + using std::nan; + + auto getX = [](const Point& p) { return p.x; }; + auto getY = [](const Point& p) { return p.y; }; + + auto distance = [](const Point& p1, const Point& p2) { + return abs(p1.distance_to(p2)); + }; + + static const auto E = 10.0/SCALING_FACTOR; + + auto x1 = getX(P[0]), y1 = getY(P[0]); + auto x2 = getX(P[1]), y2 = getY(P[1]); + auto x3 = getX(P[2]), y3 = getY(P[2]); + + + auto A_div = (x2 - x1); + auto B_div = (x3 - x2); + if(A_div == 0 || B_div == 0) return Circle(); + + auto A = (y2 - y1)/A_div; + auto B = (y2 - y3)/B_div; + auto C = (-pow(x1, 2) - pow(y1, 2) + pow(x2, 2) + pow(y2, 2))/(2*(x2 - x1)); + auto D = (pow(x2, 2) + pow(y1, 2) - pow(x3, 2) - pow(y3, 2))/(2*(x3 - x2)); + + auto cy = (C + D)/(A + B); + auto cx = B*cy - D; + + Point cc = {Coord(round(cx)), Coord(round(cy))}; + auto d = distance(cc, P[0]); + auto d2 = distance(cc, P[1]); + auto d3 = distance(cc, P[2]); + + auto e1 = abs(d - d2); + auto e2 = abs(d - d3); + + if(e1 > E || e2 > E) return Circle(); + + return { cc, d }; +} + +Circle isCircle(const Polyline& p) { + + using std::abs; + + auto& pp = p.points; + static const double E = 10/SCALING_FACTOR; + double radius = 0; + bool ret = true; + Circle c; + for(auto i = 0; i < pp.size() - 3 && ret; i += 3) { + c = circle({pp[i], pp[i+1], pp[i+2]}); + if(c || abs(radius - c.radius()) >= E) ret = false; + else radius = c.radius(); + } + +// auto rem = pp.size() % 3; + +// if(ret && rem > 0) { +// std::array remarr; + +// auto i = 0; +// for(i = 0; i < rem; i++) remarr[i] = *(pp.rbegin() - i); +// while(i < 3) remarr[i] = pp[i++]; +// c = circle(remarr); +// if(c || abs(radius - c.radius()) >= E) ret = false; +// } + + if(!ret) c = Circle(); + + return c; +} + +enum class BedShapeType { BOX, CIRCLE, IRREGULAR, WHO_KNOWS }; -BedShapeHint bedShape(const Slic3r::Polyline& /*bed*/) { +struct BedShapeHint { + BedShapeType type; + /*union*/ struct { // I know but who cares... + Circle circ; + BoundingBox box; + Polyline polygon; + } shape; +}; + +BedShapeHint bedShape(const Polyline& bed) { + static const double E = 10/SCALING_FACTOR; + + BedShapeHint ret; + + auto width = [](const BoundingBox& box) { + return box.max.x - box.min.x; + }; + + auto height = [](const BoundingBox& box) { + return box.max.y - box.min.y; + }; + + auto area = [&width, &height](const BoundingBox& box) { + return width(box) * height(box); + }; + + auto poly_area = [](Polyline p) { + Polygon pp; pp.points.reserve(p.points.size() + 1); + pp.points = std::move(p.points); + pp.points.emplace_back(pp.points.front()); + return std::abs(pp.area()); + }; + + auto bb = bed.bounding_box(); + if(std::abs(area(bb) - poly_area(bed)) < E) { + ret.type = BedShapeType::BOX; + ret.shape.box = bb; + std::cout << "BOX" << std::endl; + } + else if(auto c = isCircle(bed)) { + ret.type = BedShapeType::CIRCLE; + ret.shape.circ = c; + std::cout << "Circle" << std::endl; + } else { + std::cout << "Polygon" << std::endl; + ret.type = BedShapeType::IRREGULAR; + ret.shape.polygon = bed; + } + // Determine the bed shape by hand - return BOX; + return ret; } void applyResult( @@ -525,7 +667,10 @@ bool arrange(Model &model, coordf_t min_obj_distance, }); IndexedPackGroup result; - BoundingBox bbb(bed.points); + + if(bedhint.type == BedShapeType::WHO_KNOWS) bedhint = bedShape(bed); + + BoundingBox bbb(bed); auto binbb = Box({ static_cast(bbb.min.x), @@ -536,8 +681,8 @@ bool arrange(Model &model, coordf_t min_obj_distance, static_cast(bbb.max.y) }); - switch(bedhint) { - case BOX: { + switch(bedhint.type) { + case BedShapeType::BOX: { // Create the arranger for the box shaped bed AutoArranger arrange(binbb, min_obj_distance, progressind); @@ -547,10 +692,11 @@ bool arrange(Model &model, coordf_t min_obj_distance, result = arrange(shapes.begin(), shapes.end()); break; } - case CIRCLE: + case BedShapeType::CIRCLE: break; - case IRREGULAR: - case WHO_KNOWS: { + case BedShapeType::IRREGULAR: + case BedShapeType::WHO_KNOWS: { + using P = libnest2d::PolygonImpl; auto ctour = Slic3rMultiPoint_to_ClipperPath(bed); diff --git a/xs/src/slic3r/AppController.cpp b/xs/src/slic3r/AppController.cpp index 58858f5fc..9394df363 100644 --- a/xs/src/slic3r/AppController.cpp +++ b/xs/src/slic3r/AppController.cpp @@ -288,6 +288,7 @@ const PrintConfig &PrintController::config() const return print_->config; } + void AppController::arrange_model() { auto ftr = std::async( @@ -324,10 +325,14 @@ void AppController::arrange_model() if(pind) pind->update(0, _(L("Arranging objects..."))); try { + arr::BedShapeHint hint; + // TODO: from Sasha from GUI + hint.type = arr::BedShapeType::WHO_KNOWS; + arr::arrange(*model_, min_obj_distance, bed, - arr::BOX, + hint, false, // create many piles not just one pile [pind, count](unsigned rem) { if(pind) From db8762a93cefaa7790198c6ef8d2781abcb4f5e8 Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Mon, 6 Aug 2018 11:30:10 +0200 Subject: [PATCH 02/10] bed shape detection works and circle shaped bed now supported with limited arrange quality. --- .../libnest2d/libnest2d/placers/nfpplacer.hpp | 7 +- xs/src/libslic3r/ModelArrange.hpp | 178 ++++++++---------- 2 files changed, 86 insertions(+), 99 deletions(-) diff --git a/xs/src/libnest2d/libnest2d/placers/nfpplacer.hpp b/xs/src/libnest2d/libnest2d/placers/nfpplacer.hpp index 5d09a61fc..a3429bf48 100644 --- a/xs/src/libnest2d/libnest2d/placers/nfpplacer.hpp +++ b/xs/src/libnest2d/libnest2d/placers/nfpplacer.hpp @@ -505,13 +505,18 @@ public: bool static inline wouldFit(const Box& bb, const _Circle& bin) { + return sl::isInside(bb, bin); } bool static inline wouldFit(const RawShape& chull, const _Circle& bin) { - return sl::isInside(chull, bin); + auto bb = sl::boundingBox(chull); + auto d = bin.center() - bb.center(); + auto chullcpy = chull; + sl::translate(chullcpy, d); + return sl::isInside(chullcpy, bin); } PackResult trypack(Item& item) { diff --git a/xs/src/libslic3r/ModelArrange.hpp b/xs/src/libslic3r/ModelArrange.hpp index 5b7a2a325..53b99b781 100644 --- a/xs/src/libslic3r/ModelArrange.hpp +++ b/xs/src/libslic3r/ModelArrange.hpp @@ -329,6 +329,45 @@ public: } }; +using lnCircle = libnest2d::_Circle; + +template<> +class AutoArranger: public _ArrBase { +public: + + AutoArranger(const lnCircle& bin, Distance dist, + std::function progressind): + _ArrBase(bin, dist, progressind) { + + pconf_.object_function = [this, &bin] ( + Pile& pile, + const Item &item, + double pile_area, + double norm, + double /*penality*/) { + + auto result = objfunc(bin.center(), bin_area_, pile, + pile_area, item, norm, areacache_, rtree_); + double score = std::get<0>(result); + + // Circle fitting detection is very rough at the moment but + // we still need something that tells how badly the arrangement + // misses the print bed. + auto& fullbb = std::get<1>(result); + auto bbr = 0.5*PointLike::distance(fullbb.minCorner(), + fullbb.maxCorner()); + auto diff = bbr - bin.radius(); + + if(diff > 0) score += std::pow(diff, 2) / norm; + + + return score; + }; + + pck_.configure(pconf_); + } +}; + template<> class AutoArranger: public _ArrBase { public: @@ -348,15 +387,6 @@ public: pile_area, item, norm, areacache_, rtree_); double score = std::get<0>(result); - pile.emplace_back(item.transformedShape()); - auto chull = ShapeLike::convexHull(pile); - pile.pop_back(); - - // If it does not fit into the print bed we will beat it with a - // large penality. If we would not do this, there would be only one - // big pile that doesn't care whether it fits onto the print bed. - if(!Placer::wouldFit(chull, bin)) score += norm; - return score; }; @@ -451,89 +481,9 @@ public: inline double radius() const { return radius_; } inline const Point& center() const { return center_; } - inline operator bool() { return std::isnan(radius_); } + inline operator bool() { return !std::isnan(radius_); } }; - -Circle circle(std::array P) { - - using Coord = Point::coord_type; - using std::pow; - using std::abs; - using std::round; - using std::nan; - - auto getX = [](const Point& p) { return p.x; }; - auto getY = [](const Point& p) { return p.y; }; - - auto distance = [](const Point& p1, const Point& p2) { - return abs(p1.distance_to(p2)); - }; - - static const auto E = 10.0/SCALING_FACTOR; - - auto x1 = getX(P[0]), y1 = getY(P[0]); - auto x2 = getX(P[1]), y2 = getY(P[1]); - auto x3 = getX(P[2]), y3 = getY(P[2]); - - - auto A_div = (x2 - x1); - auto B_div = (x3 - x2); - if(A_div == 0 || B_div == 0) return Circle(); - - auto A = (y2 - y1)/A_div; - auto B = (y2 - y3)/B_div; - auto C = (-pow(x1, 2) - pow(y1, 2) + pow(x2, 2) + pow(y2, 2))/(2*(x2 - x1)); - auto D = (pow(x2, 2) + pow(y1, 2) - pow(x3, 2) - pow(y3, 2))/(2*(x3 - x2)); - - auto cy = (C + D)/(A + B); - auto cx = B*cy - D; - - Point cc = {Coord(round(cx)), Coord(round(cy))}; - auto d = distance(cc, P[0]); - auto d2 = distance(cc, P[1]); - auto d3 = distance(cc, P[2]); - - auto e1 = abs(d - d2); - auto e2 = abs(d - d3); - - if(e1 > E || e2 > E) return Circle(); - - return { cc, d }; -} - -Circle isCircle(const Polyline& p) { - - using std::abs; - - auto& pp = p.points; - static const double E = 10/SCALING_FACTOR; - double radius = 0; - bool ret = true; - Circle c; - for(auto i = 0; i < pp.size() - 3 && ret; i += 3) { - c = circle({pp[i], pp[i+1], pp[i+2]}); - if(c || abs(radius - c.radius()) >= E) ret = false; - else radius = c.radius(); - } - -// auto rem = pp.size() % 3; - -// if(ret && rem > 0) { -// std::array remarr; - -// auto i = 0; -// for(i = 0; i < rem; i++) remarr[i] = *(pp.rbegin() - i); -// while(i < 3) remarr[i] = pp[i++]; -// c = circle(remarr); -// if(c || abs(radius - c.radius()) >= E) ret = false; -// } - - if(!ret) c = Circle(); - - return c; -} - enum class BedShapeType { BOX, CIRCLE, @@ -564,7 +514,9 @@ BedShapeHint bedShape(const Polyline& bed) { }; auto area = [&width, &height](const BoundingBox& box) { - return width(box) * height(box); + double w = width(box); + double h = height(box); + return w*h; }; auto poly_area = [](Polyline p) { @@ -574,18 +526,43 @@ BedShapeHint bedShape(const Polyline& bed) { return std::abs(pp.area()); }; + auto bb = bed.bounding_box(); - if(std::abs(area(bb) - poly_area(bed)) < E) { + + auto isCircle = [bb](const Polyline& polygon) { + auto center = bb.center(); + std::vector vertex_distances; + double avg_dist = 0; + for (auto pt: polygon.points) + { + double distance = center.distance_to(pt); + vertex_distances.push_back(distance); + avg_dist += distance; + } + + avg_dist /= vertex_distances.size(); + + Circle ret(center, avg_dist); + for (auto el: vertex_distances) + { + if (abs(el - avg_dist) > 10 * SCALED_EPSILON) + ret = Circle(); + break; + } + + return ret; + }; + + auto parea = poly_area(bed); + + if( (1.0 - parea/area(bb)) < 1e-3 ) { ret.type = BedShapeType::BOX; ret.shape.box = bb; - std::cout << "BOX" << std::endl; } else if(auto c = isCircle(bed)) { ret.type = BedShapeType::CIRCLE; ret.shape.circ = c; - std::cout << "Circle" << std::endl; } else { - std::cout << "Polygon" << std::endl; ret.type = BedShapeType::IRREGULAR; ret.shape.polygon = bed; } @@ -692,8 +669,15 @@ bool arrange(Model &model, coordf_t min_obj_distance, result = arrange(shapes.begin(), shapes.end()); break; } - case BedShapeType::CIRCLE: + case BedShapeType::CIRCLE: { + + auto c = bedhint.shape.circ; + auto cc = lnCircle({c.center().x, c.center().y} , c.radius()); + + AutoArranger arrange(cc, min_obj_distance, progressind); + result = arrange(shapes.begin(), shapes.end()); break; + } case BedShapeType::IRREGULAR: case BedShapeType::WHO_KNOWS: { @@ -702,8 +686,6 @@ bool arrange(Model &model, coordf_t min_obj_distance, auto ctour = Slic3rMultiPoint_to_ClipperPath(bed); P irrbed = ShapeLike::create(std::move(ctour)); -// std::cout << ShapeLike::toString(irrbed) << std::endl; - AutoArranger

arrange(irrbed, min_obj_distance, progressind); // Arrange and return the items with their respective indices within the From e1edb05bbbd95bf0f15d3540545d9bf1cf63356e Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Mon, 6 Aug 2018 20:13:04 +0200 Subject: [PATCH 03/10] Better support for circular bed. --- xs/src/libnest2d/examples/main.cpp | 501 +----------------- .../libnest2d/libnest2d/geometry_traits.hpp | 2 +- .../libnest2d/libnest2d/placers/nfpplacer.hpp | 110 +++- xs/src/libnest2d/tests/test.cpp | 37 ++ xs/src/libslic3r/ModelArrange.hpp | 52 +- 5 files changed, 172 insertions(+), 530 deletions(-) diff --git a/xs/src/libnest2d/examples/main.cpp b/xs/src/libnest2d/examples/main.cpp index d6b2ccc34..02be465a8 100644 --- a/xs/src/libnest2d/examples/main.cpp +++ b/xs/src/libnest2d/examples/main.cpp @@ -50,492 +50,12 @@ void arrangeRectangles() { using namespace libnest2d; const int SCALE = 1000000; -// const int SCALE = 1; + std::vector rects = { - {80*SCALE, 80*SCALE}, - {60*SCALE, 90*SCALE}, - {70*SCALE, 30*SCALE}, - {80*SCALE, 60*SCALE}, - {60*SCALE, 60*SCALE}, - {60*SCALE, 40*SCALE}, - {40*SCALE, 40*SCALE}, - {10*SCALE, 10*SCALE}, - {10*SCALE, 10*SCALE}, - {10*SCALE, 10*SCALE}, - {10*SCALE, 10*SCALE}, - {10*SCALE, 10*SCALE}, - {5*SCALE, 5*SCALE}, - {5*SCALE, 5*SCALE}, - {5*SCALE, 5*SCALE}, - {5*SCALE, 5*SCALE}, - {5*SCALE, 5*SCALE}, - {5*SCALE, 5*SCALE}, - {5*SCALE, 5*SCALE}, - {20*SCALE, 20*SCALE} - }; - -// std::vector rects = { -// {20*SCALE, 10*SCALE}, -// {20*SCALE, 10*SCALE}, -// {20*SCALE, 20*SCALE}, -// }; - -// std::vector input { -// {{0, 0}, {0, 20*SCALE}, {10*SCALE, 0}, {0, 0}} -// }; - - std::vector crasher = - { - { - {-5000000, 8954050}, - {5000000, 8954050}, - {5000000, -45949}, - {4972609, -568549}, - {3500000, -8954050}, - {-3500000, -8954050}, - {-4972609, -568549}, - {-5000000, -45949}, - {-5000000, 8954050}, - }, - { - {-5000000, 8954050}, - {5000000, 8954050}, - {5000000, -45949}, - {4972609, -568549}, - {3500000, -8954050}, - {-3500000, -8954050}, - {-4972609, -568549}, - {-5000000, -45949}, - {-5000000, 8954050}, - }, - { - {-5000000, 8954050}, - {5000000, 8954050}, - {5000000, -45949}, - {4972609, -568549}, - {3500000, -8954050}, - {-3500000, -8954050}, - {-4972609, -568549}, - {-5000000, -45949}, - {-5000000, 8954050}, - }, - { - {-5000000, 8954050}, - {5000000, 8954050}, - {5000000, -45949}, - {4972609, -568549}, - {3500000, -8954050}, - {-3500000, -8954050}, - {-4972609, -568549}, - {-5000000, -45949}, - {-5000000, 8954050}, - }, - { - {-5000000, 8954050}, - {5000000, 8954050}, - {5000000, -45949}, - {4972609, -568549}, - {3500000, -8954050}, - {-3500000, -8954050}, - {-4972609, -568549}, - {-5000000, -45949}, - {-5000000, 8954050}, - }, - { - {-5000000, 8954050}, - {5000000, 8954050}, - {5000000, -45949}, - {4972609, -568549}, - {3500000, -8954050}, - {-3500000, -8954050}, - {-4972609, -568549}, - {-5000000, -45949}, - {-5000000, 8954050}, - }, - { - {-9945219, -3065619}, - {-9781479, -2031780}, - {-9510560, -1020730}, - {-9135450, -43529}, - {-2099999, 14110899}, - {2099999, 14110899}, - {9135450, -43529}, - {9510560, -1020730}, - {9781479, -2031780}, - {9945219, -3065619}, - {10000000, -4110899}, - {9945219, -5156179}, - {9781479, -6190020}, - {9510560, -7201069}, - {9135450, -8178270}, - {8660249, -9110899}, - {8090169, -9988750}, - {7431449, -10802200}, - {6691309, -11542300}, - {5877850, -12201100}, - {5000000, -12771100}, - {4067369, -13246399}, - {3090169, -13621500}, - {2079119, -13892399}, - {1045279, -14056099}, - {0, -14110899}, - {-1045279, -14056099}, - {-2079119, -13892399}, - {-3090169, -13621500}, - {-4067369, -13246399}, - {-5000000, -12771100}, - {-5877850, -12201100}, - {-6691309, -11542300}, - {-7431449, -10802200}, - {-8090169, -9988750}, - {-8660249, -9110899}, - {-9135450, -8178270}, - {-9510560, -7201069}, - {-9781479, -6190020}, - {-9945219, -5156179}, - {-10000000, -4110899}, - {-9945219, -3065619}, - }, - { - {-9945219, -3065619}, - {-9781479, -2031780}, - {-9510560, -1020730}, - {-9135450, -43529}, - {-2099999, 14110899}, - {2099999, 14110899}, - {9135450, -43529}, - {9510560, -1020730}, - {9781479, -2031780}, - {9945219, -3065619}, - {10000000, -4110899}, - {9945219, -5156179}, - {9781479, -6190020}, - {9510560, -7201069}, - {9135450, -8178270}, - {8660249, -9110899}, - {8090169, -9988750}, - {7431449, -10802200}, - {6691309, -11542300}, - {5877850, -12201100}, - {5000000, -12771100}, - {4067369, -13246399}, - {3090169, -13621500}, - {2079119, -13892399}, - {1045279, -14056099}, - {0, -14110899}, - {-1045279, -14056099}, - {-2079119, -13892399}, - {-3090169, -13621500}, - {-4067369, -13246399}, - {-5000000, -12771100}, - {-5877850, -12201100}, - {-6691309, -11542300}, - {-7431449, -10802200}, - {-8090169, -9988750}, - {-8660249, -9110899}, - {-9135450, -8178270}, - {-9510560, -7201069}, - {-9781479, -6190020}, - {-9945219, -5156179}, - {-10000000, -4110899}, - {-9945219, -3065619}, - }, - { - {-9945219, -3065619}, - {-9781479, -2031780}, - {-9510560, -1020730}, - {-9135450, -43529}, - {-2099999, 14110899}, - {2099999, 14110899}, - {9135450, -43529}, - {9510560, -1020730}, - {9781479, -2031780}, - {9945219, -3065619}, - {10000000, -4110899}, - {9945219, -5156179}, - {9781479, -6190020}, - {9510560, -7201069}, - {9135450, -8178270}, - {8660249, -9110899}, - {8090169, -9988750}, - {7431449, -10802200}, - {6691309, -11542300}, - {5877850, -12201100}, - {5000000, -12771100}, - {4067369, -13246399}, - {3090169, -13621500}, - {2079119, -13892399}, - {1045279, -14056099}, - {0, -14110899}, - {-1045279, -14056099}, - {-2079119, -13892399}, - {-3090169, -13621500}, - {-4067369, -13246399}, - {-5000000, -12771100}, - {-5877850, -12201100}, - {-6691309, -11542300}, - {-7431449, -10802200}, - {-8090169, -9988750}, - {-8660249, -9110899}, - {-9135450, -8178270}, - {-9510560, -7201069}, - {-9781479, -6190020}, - {-9945219, -5156179}, - {-10000000, -4110899}, - {-9945219, -3065619}, - }, - { - {-9945219, -3065619}, - {-9781479, -2031780}, - {-9510560, -1020730}, - {-9135450, -43529}, - {-2099999, 14110899}, - {2099999, 14110899}, - {9135450, -43529}, - {9510560, -1020730}, - {9781479, -2031780}, - {9945219, -3065619}, - {10000000, -4110899}, - {9945219, -5156179}, - {9781479, -6190020}, - {9510560, -7201069}, - {9135450, -8178270}, - {8660249, -9110899}, - {8090169, -9988750}, - {7431449, -10802200}, - {6691309, -11542300}, - {5877850, -12201100}, - {5000000, -12771100}, - {4067369, -13246399}, - {3090169, -13621500}, - {2079119, -13892399}, - {1045279, -14056099}, - {0, -14110899}, - {-1045279, -14056099}, - {-2079119, -13892399}, - {-3090169, -13621500}, - {-4067369, -13246399}, - {-5000000, -12771100}, - {-5877850, -12201100}, - {-6691309, -11542300}, - {-7431449, -10802200}, - {-8090169, -9988750}, - {-8660249, -9110899}, - {-9135450, -8178270}, - {-9510560, -7201069}, - {-9781479, -6190020}, - {-9945219, -5156179}, - {-10000000, -4110899}, - {-9945219, -3065619}, - }, - { - {-9945219, -3065619}, - {-9781479, -2031780}, - {-9510560, -1020730}, - {-9135450, -43529}, - {-2099999, 14110899}, - {2099999, 14110899}, - {9135450, -43529}, - {9510560, -1020730}, - {9781479, -2031780}, - {9945219, -3065619}, - {10000000, -4110899}, - {9945219, -5156179}, - {9781479, -6190020}, - {9510560, -7201069}, - {9135450, -8178270}, - {8660249, -9110899}, - {8090169, -9988750}, - {7431449, -10802200}, - {6691309, -11542300}, - {5877850, -12201100}, - {5000000, -12771100}, - {4067369, -13246399}, - {3090169, -13621500}, - {2079119, -13892399}, - {1045279, -14056099}, - {0, -14110899}, - {-1045279, -14056099}, - {-2079119, -13892399}, - {-3090169, -13621500}, - {-4067369, -13246399}, - {-5000000, -12771100}, - {-5877850, -12201100}, - {-6691309, -11542300}, - {-7431449, -10802200}, - {-8090169, -9988750}, - {-8660249, -9110899}, - {-9135450, -8178270}, - {-9510560, -7201069}, - {-9781479, -6190020}, - {-9945219, -5156179}, - {-10000000, -4110899}, - {-9945219, -3065619}, - }, - { - {-9945219, -3065619}, - {-9781479, -2031780}, - {-9510560, -1020730}, - {-9135450, -43529}, - {-2099999, 14110899}, - {2099999, 14110899}, - {9135450, -43529}, - {9510560, -1020730}, - {9781479, -2031780}, - {9945219, -3065619}, - {10000000, -4110899}, - {9945219, -5156179}, - {9781479, -6190020}, - {9510560, -7201069}, - {9135450, -8178270}, - {8660249, -9110899}, - {8090169, -9988750}, - {7431449, -10802200}, - {6691309, -11542300}, - {5877850, -12201100}, - {5000000, -12771100}, - {4067369, -13246399}, - {3090169, -13621500}, - {2079119, -13892399}, - {1045279, -14056099}, - {0, -14110899}, - {-1045279, -14056099}, - {-2079119, -13892399}, - {-3090169, -13621500}, - {-4067369, -13246399}, - {-5000000, -12771100}, - {-5877850, -12201100}, - {-6691309, -11542300}, - {-7431449, -10802200}, - {-8090169, -9988750}, - {-8660249, -9110899}, - {-9135450, -8178270}, - {-9510560, -7201069}, - {-9781479, -6190020}, - {-9945219, -5156179}, - {-10000000, -4110899}, - {-9945219, -3065619}, - }, - { - {-9945219, -3065619}, - {-9781479, -2031780}, - {-9510560, -1020730}, - {-9135450, -43529}, - {-2099999, 14110899}, - {2099999, 14110899}, - {9135450, -43529}, - {9510560, -1020730}, - {9781479, -2031780}, - {9945219, -3065619}, - {10000000, -4110899}, - {9945219, -5156179}, - {9781479, -6190020}, - {9510560, -7201069}, - {9135450, -8178270}, - {8660249, -9110899}, - {8090169, -9988750}, - {7431449, -10802200}, - {6691309, -11542300}, - {5877850, -12201100}, - {5000000, -12771100}, - {4067369, -13246399}, - {3090169, -13621500}, - {2079119, -13892399}, - {1045279, -14056099}, - {0, -14110899}, - {-1045279, -14056099}, - {-2079119, -13892399}, - {-3090169, -13621500}, - {-4067369, -13246399}, - {-5000000, -12771100}, - {-5877850, -12201100}, - {-6691309, -11542300}, - {-7431449, -10802200}, - {-8090169, -9988750}, - {-8660249, -9110899}, - {-9135450, -8178270}, - {-9510560, -7201069}, - {-9781479, -6190020}, - {-9945219, -5156179}, - {-10000000, -4110899}, - {-9945219, -3065619}, - }, - { - {-9945219, -3065619}, - {-9781479, -2031780}, - {-9510560, -1020730}, - {-9135450, -43529}, - {-2099999, 14110899}, - {2099999, 14110899}, - {9135450, -43529}, - {9510560, -1020730}, - {9781479, -2031780}, - {9945219, -3065619}, - {10000000, -4110899}, - {9945219, -5156179}, - {9781479, -6190020}, - {9510560, -7201069}, - {9135450, -8178270}, - {8660249, -9110899}, - {8090169, -9988750}, - {7431449, -10802200}, - {6691309, -11542300}, - {5877850, -12201100}, - {5000000, -12771100}, - {4067369, -13246399}, - {3090169, -13621500}, - {2079119, -13892399}, - {1045279, -14056099}, - {0, -14110899}, - {-1045279, -14056099}, - {-2079119, -13892399}, - {-3090169, -13621500}, - {-4067369, -13246399}, - {-5000000, -12771100}, - {-5877850, -12201100}, - {-6691309, -11542300}, - {-7431449, -10802200}, - {-8090169, -9988750}, - {-8660249, -9110899}, - {-9135450, -8178270}, - {-9510560, -7201069}, - {-9781479, -6190020}, - {-9945219, -5156179}, - {-10000000, -4110899}, - {-9945219, -3065619}, - }, - { - {-18000000, -1000000}, - {-15000000, 22000000}, - {-11000000, 26000000}, - {11000000, 26000000}, - {15000000, 22000000}, - {18000000, -1000000}, - {18000000, -26000000}, - {-18000000, -26000000}, - {-18000000, -1000000}, - }, + {60*SCALE, 200*SCALE}, + {60*SCALE, 200*SCALE} }; - std::vector proba = { - { - Rectangle(100, 2) - }, - { - Rectangle(100, 2) - }, - { - Rectangle(100, 2) - }, - { - Rectangle(10, 10) - }, - }; - - proba[0].rotate(Pi/3); - proba[1].rotate(Pi-Pi/3); - -// std::vector input(25, Rectangle(70*SCALE, 10*SCALE)); std::vector input; input.insert(input.end(), prusaParts().begin(), prusaParts().end()); // input.insert(input.end(), prusaExParts().begin(), prusaExParts().end()); @@ -544,7 +64,7 @@ void arrangeRectangles() { // input.insert(input.end(), proba.begin(), proba.end()); // input.insert(input.end(), crasher.begin(), crasher.end()); - Box bin(250*SCALE, 210*SCALE); +// Box bin(250*SCALE, 210*SCALE); // PolygonImpl bin = { // { // {25*SCALE, 0}, @@ -560,9 +80,11 @@ void arrangeRectangles() { // {} // }; + _Circle bin({0, 0}, 125*SCALE); + auto min_obj_distance = static_cast(0*SCALE); - using Placer = strategies::_NofitPolyPlacer; + using Placer = strategies::_NofitPolyPlacer; using Packer = Arranger; Packer arrange(bin, min_obj_distance); @@ -571,9 +93,9 @@ void arrangeRectangles() { pconf.alignment = Placer::Config::Alignment::CENTER; pconf.starting_point = Placer::Config::Alignment::CENTER; pconf.rotations = {0.0/*, Pi/2.0, Pi, 3*Pi/2*/}; - pconf.accuracy = 0.5f; + pconf.accuracy = 1.0f; -// auto bincenter = ShapeLike::boundingBox(bin).center(); +// auto bincenter = ShapeLike::boundingBox(bin).center(); // pconf.object_function = [&bin, bincenter]( // Placer::Pile pile, const Item& item, // double /*area*/, double norm, double penality) { @@ -660,10 +182,7 @@ void arrangeRectangles() { // score = pl::distance(ibb.center(), bigbb.center()) / norm; // } -// // If it does not fit into the print bed we will beat it -// // with a large penality. If we would not do this, there would be only -// // one big pile that doesn't care whether it fits onto the print bed. -// if(!NfpPlacer::wouldFit(fullbb, bin)) score = 2*penality - score; +// if(!Placer::wouldFit(fullbb, bin)) score += norm; // return score; // }; diff --git a/xs/src/libnest2d/libnest2d/geometry_traits.hpp b/xs/src/libnest2d/libnest2d/geometry_traits.hpp index 058c47cd4..830643130 100644 --- a/xs/src/libnest2d/libnest2d/geometry_traits.hpp +++ b/xs/src/libnest2d/libnest2d/geometry_traits.hpp @@ -109,7 +109,7 @@ public: inline void radius(double r) { radius_ = r; } inline double area() const BP2D_NOEXCEPT { - return 2.0*Pi*radius_; + return 2.0*Pi*radius_*radius_; } }; diff --git a/xs/src/libnest2d/libnest2d/placers/nfpplacer.hpp b/xs/src/libnest2d/libnest2d/placers/nfpplacer.hpp index a3429bf48..c506a5d5a 100644 --- a/xs/src/libnest2d/libnest2d/placers/nfpplacer.hpp +++ b/xs/src/libnest2d/libnest2d/placers/nfpplacer.hpp @@ -1,16 +1,19 @@ #ifndef NOFITPOLY_HPP #define NOFITPOLY_HPP +#include +#include + #ifndef NDEBUG #include #endif #include "placer_boilerplate.hpp" #include "../geometry_traits_nfp.hpp" #include "libnest2d/optimizer.hpp" -#include #include "tools/svgtools.hpp" + namespace libnest2d { namespace strategies { template @@ -161,12 +164,11 @@ template class EdgeCache { } size_t stride(const size_t N) const { - using std::ceil; using std::round; using std::pow; return static_cast( - std::round(N/std::pow(N, std::pow(accuracy_, 1.0/3.0))) + round(N/pow(N, pow(accuracy_, 1.0/3.0))) ); } @@ -177,6 +179,7 @@ template class EdgeCache { const auto S = stride(N); contour_.corners.reserve(N / S + 1); + contour_.corners.emplace_back(0.0); auto N_1 = N-1; contour_.corners.emplace_back(0.0); for(size_t i = 0; i < N_1; i += S) { @@ -190,8 +193,8 @@ template class EdgeCache { if(!hc.corners.empty()) return; const auto N = hc.distances.size(); - const auto S = stride(N); auto N_1 = N-1; + const auto S = stride(N); hc.corners.reserve(N / S + 1); hc.corners.emplace_back(0.0); for(size_t i = 0; i < N_1; i += S) { @@ -339,7 +342,7 @@ Nfp::Shapes nfp( const Container& polygons, Nfp::Shapes nfps; - //int pi = 0; +// int pi = 0; for(Item& sh : polygons) { auto subnfp_r = Nfp::noFitPolygon( sh.transformedShape(), trsh.transformedShape()); @@ -441,6 +444,63 @@ Nfp::Shapes nfp( const Container& polygons, // return nfps; } +template +_Circle> minimizeCircle(const RawShape& sh) { + using sl = ShapeLike; using pl = PointLike; + using Point = TPoint; + using Coord = TCoord; + + auto bb = sl::boundingBox(sh); + auto capprx = bb.center(); + auto rapprx = pl::distance(bb.minCorner(), bb.maxCorner()); + + auto& ctr = sl::getContour(sh); + + opt::StopCriteria stopcr; + stopcr.max_iterations = 100; + stopcr.relative_score_difference = 1e-3; + opt::TOptimizer solver(stopcr); + + std::vector dists(ctr.size(), 0); + + auto result = solver.optimize_min( + [capprx, rapprx, &ctr, &dists](double xf, double yf) { + auto xt = Coord( std::round(getX(capprx) + rapprx*xf) ); + auto yt = Coord( std::round(getY(capprx) + rapprx*yf) ); + + Point centr(xt, yt); + + unsigned i = 0; + for(auto v : ctr) { + dists[i++] = pl::distance(v, centr); + } + + auto mit = std::max_element(dists.begin(), dists.end()); + + assert(mit != dists.end()); + + return *mit; + }, + opt::initvals(0.0, 0.0), + opt::bound(-1.0, 1.0), opt::bound(-1.0, 1.0) + ); + + double oxf = std::get<0>(result.optimum); + double oyf = std::get<1>(result.optimum); + auto xt = Coord( std::round(getX(capprx) + rapprx*oxf) ); + auto yt = Coord( std::round(getY(capprx) + rapprx*oyf) ); + + Point cc(xt, yt); + auto r = result.score; + + return {cc, r}; +} + +template +_Circle> boundingCircle(const RawShape& sh) { + return minimizeCircle(sh); +} + template>> class _NofitPolyPlacer: public PlacerBoilerplate<_NofitPolyPlacer, RawShape, TBin, NfpPConfig> { @@ -512,11 +572,7 @@ public: bool static inline wouldFit(const RawShape& chull, const _Circle& bin) { - auto bb = sl::boundingBox(chull); - auto d = bin.center() - bb.center(); - auto chullcpy = chull; - sl::translate(chullcpy, d); - return sl::isInside(chullcpy, bin); + return boundingCircle(chull).radius() < bin.radius(); } PackResult trypack(Item& item) { @@ -574,8 +630,9 @@ public: auto getNfpPoint = [&ecache](const Optimum& opt) { - return opt.hidx < 0? ecache[opt.nfpidx].coords(opt.relpos) : + auto ret = opt.hidx < 0? ecache[opt.nfpidx].coords(opt.relpos) : ecache[opt.nfpidx].coords(opt.hidx, opt.relpos); + return ret; }; Nfp::Shapes pile; @@ -595,7 +652,7 @@ public: [this, &merged_pile]( Nfp::Shapes& /*pile*/, const Item& item, - double occupied_area, + double occupied_area, double norm, double /*penality*/) { @@ -751,14 +808,37 @@ public: } inline void clearItems() { + finalAlign(bin_); + Base::clearItems(); + } + +private: + + inline void finalAlign(const RawShape& pbin) { + auto bbin = sl::boundingBox(pbin); + finalAlign(bbin); + } + + inline void finalAlign(_Circle> cbin) { + if(items_.empty()) return; + Nfp::Shapes m; m.reserve(items_.size()); + for(Item& item : items_) m.emplace_back(item.transformedShape()); + auto c = boundingCircle(sl::convexHull(m)); + + auto d = cbin.center() - c.center(); + for(Item& item : items_) item.translate(d); + } + + inline void finalAlign(Box bbin) { + Nfp::Shapes m; + m.reserve(items_.size()); for(Item& item : items_) m.emplace_back(item.transformedShape()); auto&& bb = sl::boundingBox(m); Vertex ci, cb; - auto bbin = sl::boundingBox(bin_); switch(config_.alignment) { case Config::Alignment::CENTER: { @@ -790,12 +870,8 @@ public: auto d = cb - ci; for(Item& item : items_) item.translate(d); - - Base::clearItems(); } -private: - void setInitialPosition(Item& item) { Box&& bb = item.boundingBox(); Vertex ci, cb; diff --git a/xs/src/libnest2d/tests/test.cpp b/xs/src/libnest2d/tests/test.cpp index 39315ff1a..1e030c056 100644 --- a/xs/src/libnest2d/tests/test.cpp +++ b/xs/src/libnest2d/tests/test.cpp @@ -99,6 +99,43 @@ TEST(BasicFunctionality, creationAndDestruction) } +TEST(GeometryAlgorithms, boundingCircle) { + using namespace libnest2d; + using strategies::boundingCircle; + + PolygonImpl p = {{{0, 10}, {10, 0}, {0, -10}, {0, 10}}, {}}; + _Circle c = boundingCircle(p); + + ASSERT_EQ(c.center().X, 0); + ASSERT_EQ(c.center().Y, 0); + ASSERT_DOUBLE_EQ(c.radius(), 10); + + ShapeLike::translate(p, PointImpl{10, 10}); + c = boundingCircle(p); + + ASSERT_EQ(c.center().X, 10); + ASSERT_EQ(c.center().Y, 10); + ASSERT_DOUBLE_EQ(c.radius(), 10); + + auto parts = prusaParts(); + + int i = 0; + for(auto& part : parts) { + c = boundingCircle(part.transformedShape()); + if(std::isnan(c.radius())) std::cout << "fail: radius is nan" << std::endl; + + else for(auto v : ShapeLike::getContour(part.transformedShape()) ) { + auto d = PointLike::distance(v, c.center()); + if(d > c.radius() ) { + auto e = std::abs( 1.0 - d/c.radius()); + ASSERT_LE(e, 1e-3); + } + } + i++; + } + +} + TEST(GeometryAlgorithms, Distance) { using namespace libnest2d; diff --git a/xs/src/libslic3r/ModelArrange.hpp b/xs/src/libslic3r/ModelArrange.hpp index 53b99b781..57a169205 100644 --- a/xs/src/libslic3r/ModelArrange.hpp +++ b/xs/src/libslic3r/ModelArrange.hpp @@ -102,9 +102,9 @@ using SpatIndex = bgi::rtree< SpatElement, bgi::rstar<16, 4> >; std::tuple objfunc(const PointImpl& bincenter, - double /*bin_area*/, + double bin_area, ShapeLike::Shapes& pile, // The currently arranged pile - double /*pile_area*/, + double pile_area, const Item &item, double norm, // A norming factor for physical dimensions std::vector& areacache, // pile item areas will be cached @@ -115,12 +115,16 @@ objfunc(const PointImpl& bincenter, using pl = PointLike; using sl = ShapeLike; - static const double BIG_ITEM_TRESHOLD = 0.2; + static const double BIG_ITEM_TRESHOLD = 0.04; static const double ROUNDNESS_RATIO = 0.5; static const double DENSITY_RATIO = 1.0 - ROUNDNESS_RATIO; // We will treat big items (compared to the print bed) differently - auto normarea = [norm](double area) { return std::sqrt(area)/norm; }; + + auto isBig = [&areacache, bin_area](double a) { + bool t = areacache.empty() ? true : a > 0.5*areacache.front(); + return a/bin_area > BIG_ITEM_TRESHOLD || t; + }; // If a new bin has been created: if(pile.size() < areacache.size()) { @@ -133,7 +137,7 @@ objfunc(const PointImpl& bincenter, for(auto& p : pile) { if(idx == areacache.size()) { areacache.emplace_back(sl::area(p)); - if(normarea(areacache[idx]) > BIG_ITEM_TRESHOLD) + if(isBig(areacache[idx])) spatindex.insert({sl::boundingBox(p), idx}); } @@ -157,14 +161,10 @@ objfunc(const PointImpl& bincenter, boost::geometry::convert(boostbb, bigbb); } - // The size indicator of the candidate item. This is not the area, - // but almost... - double item_normarea = normarea(item.area()); - // Will hold the resulting score double score = 0; - if(item_normarea > BIG_ITEM_TRESHOLD) { + if(isBig(item.area())) { // 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 @@ -223,10 +223,9 @@ objfunc(const PointImpl& bincenter, // The final mix of the score is the balance between the distance // from the full pile center, the pack density and the // alignment with the neigbours - auto C = 0.33; - score = C * dist + C * density + C * alignment_score; + score = 0.4 * dist + 0.4 * density + 0.2 * alignment_score; - } else if( item_normarea < BIG_ITEM_TRESHOLD && spatindex.empty()) { + } else if( !isBig(item.area()) && spatindex.empty()) { // If there are no big items, only small, we should consider the // density here as well to not get silly results auto bindist = pl::distance(ibb.center(), bincenter) / norm; @@ -349,17 +348,26 @@ public: auto result = objfunc(bin.center(), bin_area_, pile, pile_area, item, norm, areacache_, rtree_); double score = std::get<0>(result); - - // Circle fitting detection is very rough at the moment but - // we still need something that tells how badly the arrangement - // misses the print bed. auto& fullbb = std::get<1>(result); - auto bbr = 0.5*PointLike::distance(fullbb.minCorner(), - fullbb.maxCorner()); - auto diff = bbr - bin.radius(); - if(diff > 0) score += std::pow(diff, 2) / norm; + auto d = PointLike::distance(fullbb.minCorner(), + fullbb.maxCorner()); + auto diff = d - 2*bin.radius(); + if(diff > 0) { + if( item.area() > 0.01*bin_area_ && item.vertexCount() < 20) { + pile.emplace_back(item.transformedShape()); + auto chull = ShapeLike::convexHull(pile); + pile.pop_back(); + + auto C = strategies::boundingCircle(chull); + auto rdiff = C.radius() - bin.radius(); + + if(rdiff > 0) { + score += std::pow(rdiff, 3) / norm; + } + } + } return score; }; @@ -695,6 +703,8 @@ bool arrange(Model &model, coordf_t min_obj_distance, } }; + if(result.empty()) return false; + if(first_bin_only) { applyResult(result.front(), 0, shapemap); } else { From 224c0e74eab48b9bc117d1af60ddcd7f1362e837 Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Tue, 7 Aug 2018 10:57:22 +0200 Subject: [PATCH 04/10] Precision raised and big item classification improved --- xs/src/libnest2d/libnest2d/placers/nfpplacer.hpp | 2 +- xs/src/libslic3r/ModelArrange.hpp | 13 ++++++++----- 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/xs/src/libnest2d/libnest2d/placers/nfpplacer.hpp b/xs/src/libnest2d/libnest2d/placers/nfpplacer.hpp index c506a5d5a..110c05f0c 100644 --- a/xs/src/libnest2d/libnest2d/placers/nfpplacer.hpp +++ b/xs/src/libnest2d/libnest2d/placers/nfpplacer.hpp @@ -706,7 +706,7 @@ public: opt::StopCriteria stopcr; stopcr.max_iterations = 100; - stopcr.relative_score_difference = 1e-6; + stopcr.relative_score_difference = 1e-12; opt::TOptimizer solver(stopcr); Optimum optimum(0, 0); diff --git a/xs/src/libslic3r/ModelArrange.hpp b/xs/src/libslic3r/ModelArrange.hpp index 57a169205..118bff17f 100644 --- a/xs/src/libslic3r/ModelArrange.hpp +++ b/xs/src/libslic3r/ModelArrange.hpp @@ -115,15 +115,18 @@ objfunc(const PointImpl& bincenter, using pl = PointLike; using sl = ShapeLike; - static const double BIG_ITEM_TRESHOLD = 0.04; + static const double BIG_ITEM_TRESHOLD = 0.02; static const double ROUNDNESS_RATIO = 0.5; static const double DENSITY_RATIO = 1.0 - ROUNDNESS_RATIO; // We will treat big items (compared to the print bed) differently auto isBig = [&areacache, bin_area](double a) { - bool t = areacache.empty() ? true : a > 0.5*areacache.front(); - return a/bin_area > BIG_ITEM_TRESHOLD || t; + double farea = areacache.empty() ? 0 : areacache.front(); + bool fbig = farea / bin_area > BIG_ITEM_TRESHOLD; + bool abig = a/bin_area > BIG_ITEM_TRESHOLD; + bool rbig = fbig && a > 0.5*farea; + return abig || rbig; }; // If a new bin has been created: @@ -258,7 +261,7 @@ void fillConfig(PConf& pcfg) { // The accuracy of optimization. // Goes from 0.0 to 1.0 and scales performance as well - pcfg.accuracy = 0.6f; + pcfg.accuracy = 1.0f; } template @@ -355,7 +358,7 @@ public: auto diff = d - 2*bin.radius(); if(diff > 0) { - if( item.area() > 0.01*bin_area_ && item.vertexCount() < 20) { + if( item.area() > 0.01*bin_area_ && item.vertexCount() < 30) { pile.emplace_back(item.transformedShape()); auto chull = ShapeLike::convexHull(pile); pile.pop_back(); From 08fb677583518382f3ff4a42fadc431d0a9870f0 Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Tue, 7 Aug 2018 14:23:57 +0200 Subject: [PATCH 05/10] Fine tuning of precision. --- xs/src/libslic3r/ModelArrange.hpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/xs/src/libslic3r/ModelArrange.hpp b/xs/src/libslic3r/ModelArrange.hpp index 118bff17f..f4ce0daca 100644 --- a/xs/src/libslic3r/ModelArrange.hpp +++ b/xs/src/libslic3r/ModelArrange.hpp @@ -226,7 +226,7 @@ objfunc(const PointImpl& bincenter, // The final mix of the score is the balance between the distance // from the full pile center, the pack density and the // alignment with the neigbours - score = 0.4 * dist + 0.4 * density + 0.2 * alignment_score; + score = 0.45 * dist + 0.45 * density + 0.1 * alignment_score; } else if( !isBig(item.area()) && spatindex.empty()) { // If there are no big items, only small, we should consider the @@ -261,7 +261,7 @@ void fillConfig(PConf& pcfg) { // The accuracy of optimization. // Goes from 0.0 to 1.0 and scales performance as well - pcfg.accuracy = 1.0f; + pcfg.accuracy = 0.6f; } template From 20b7aad6d1cbb4b282586a3844fb66843c81f10d Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Tue, 7 Aug 2018 19:48:00 +0200 Subject: [PATCH 06/10] Bug fixes for the neighborhood detection --- xs/src/libnest2d/README.md | 18 ++- xs/src/libnest2d/examples/main.cpp | 92 -------------- .../libnest2d/libnest2d/geometry_traits.hpp | 6 +- xs/src/libnest2d/libnest2d/libnest2d.hpp | 36 +++--- .../libnest2d/placers/bottomleftplacer.hpp | 7 +- .../libnest2d/libnest2d/placers/nfpplacer.hpp | 65 +++++----- .../libnest2d/placers/placer_boilerplate.hpp | 7 +- .../libnest2d/selections/djd_heuristic.hpp | 32 ++--- .../libnest2d/libnest2d/selections/filler.hpp | 2 +- .../libnest2d/selections/firstfit.hpp | 9 +- xs/src/libnest2d/tests/test.cpp | 4 +- xs/src/libslic3r/ModelArrange.hpp | 118 ++++++++++++------ 12 files changed, 190 insertions(+), 206 deletions(-) diff --git a/xs/src/libnest2d/README.md b/xs/src/libnest2d/README.md index 3508801a8..61a7ac7d0 100644 --- a/xs/src/libnest2d/README.md +++ b/xs/src/libnest2d/README.md @@ -9,18 +9,28 @@ with templated geometry types. These geometries can have custom or already existing implementation to avoid copying or having unnecessary dependencies. A default backend is provided if the user of the library just wants to use it -out of the box without additional integration. The default backend is reasonably +out of the box without additional integration. This backend is reasonably fast and robust, being built on top of boost geometry and the [polyclipping](http://www.angusj.com/delphi/clipper.php) library. Usage of -this default backend implies the dependency on these packages as well as the -compilation of the backend itself (The default backend is not yet header only). +this default backend implies the dependency on these packages but its header +only as well. This software is currently under construction and lacks a throughout documentation and some essential algorithms as well. At this stage it works well for rectangles and convex closed polygons without considering holes and concavities. -Holes and non-convex polygons will be usable in the near future as well. +Holes and non-convex polygons will be usable in the near future as well. The +no fit polygon based placer module combined with the first fit selection +strategy is now used in the [Slic3r](https://github.com/prusa3d/Slic3r) +application's arrangement feature. It uses local optimization techniques to find +the best placement of each new item based on some features of the arrangement. + +In the near future I would like to use machine learning to evaluate the +placements and (or) the order if items in which they are placed and see what +results can be obtained. This is a different approach than that of SVGnest which +uses genetic algorithms to find better and better selection orders. Maybe the +two approaches can be combined as well. # References - [SVGNest](https://github.com/Jack000/SVGnest) diff --git a/xs/src/libnest2d/examples/main.cpp b/xs/src/libnest2d/examples/main.cpp index 02be465a8..11f8f50cf 100644 --- a/xs/src/libnest2d/examples/main.cpp +++ b/xs/src/libnest2d/examples/main.cpp @@ -95,98 +95,6 @@ void arrangeRectangles() { pconf.rotations = {0.0/*, Pi/2.0, Pi, 3*Pi/2*/}; pconf.accuracy = 1.0f; -// auto bincenter = ShapeLike::boundingBox(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 dists; -// dists[0] = pl::distance(minc, cc); -// dists[1] = pl::distance(maxc, cc); -// dists[2] = pl::distance(ibb.center(), cc); -// dists[3] = pl::distance(top_left, cc); -// dists[4] = pl::distance(bottom_right, cc); - -// auto dist = *(std::min_element(dists.begin(), dists.end())) / norm; - -// // Density is the pack density: how big is the arranged pile -// auto density = std::sqrt(fullbb.width()*fullbb.height()) / norm; - -// // The score is a weighted sum of the distance from pile center -// // and the pile size -// score = GRAVITY_RATIO * dist + DENSITY_RATIO * density; - -// } else if(itemnormarea < BIG_ITEM_TRESHOLD && bigs.empty()) { -// // If there are no big items, only small, we should consider the -// // density here as well to not get silly results -// auto bindist = pl::distance(ibb.center(), bincenter) / norm; -// auto density = std::sqrt(fullbb.width()*fullbb.height()) / norm; -// score = GRAVITY_RATIO * bindist + DENSITY_RATIO * density; -// } else { -// // Here there are the small items that should be placed around the -// // already processed bigger items. -// // No need to play around with the anchor points, the center will be -// // just fine for small items -// score = pl::distance(ibb.center(), bigbb.center()) / norm; -// } - -// if(!Placer::wouldFit(fullbb, bin)) score += norm; - -// return score; -// }; - Packer::SelectionConfig sconf; // sconf.allow_parallel = false; // sconf.force_parallel = false; diff --git a/xs/src/libnest2d/libnest2d/geometry_traits.hpp b/xs/src/libnest2d/libnest2d/geometry_traits.hpp index 830643130..1c0d44c9f 100644 --- a/xs/src/libnest2d/libnest2d/geometry_traits.hpp +++ b/xs/src/libnest2d/libnest2d/geometry_traits.hpp @@ -313,9 +313,9 @@ inline RawPoint _Box::center() const BP2D_NOEXCEPT { using Coord = TCoord; - RawPoint ret = { - static_cast( std::round((getX(minc) + getX(maxc))/2.0) ), - static_cast( std::round((getY(minc) + getY(maxc))/2.0) ) + RawPoint ret = { // No rounding here, we dont know if these are int coords + static_cast( (getX(minc) + getX(maxc))/2.0 ), + static_cast( (getY(minc) + getY(maxc))/2.0 ) }; return ret; diff --git a/xs/src/libnest2d/libnest2d/libnest2d.hpp b/xs/src/libnest2d/libnest2d/libnest2d.hpp index 7f23de358..eadd1e110 100644 --- a/xs/src/libnest2d/libnest2d/libnest2d.hpp +++ b/xs/src/libnest2d/libnest2d/libnest2d.hpp @@ -541,21 +541,20 @@ public: inline void configure(const Config& config) { impl_.configure(config); } /** - * @brief A method that tries to pack an item and returns an object - * describing the pack result. + * Try to pack an item with a result object that contains the packing + * information for later accepting it. * - * The result can be casted to bool and used as an argument to the accept - * method to accept a succesfully packed item. This way the next packing - * will consider the accepted item as well. The PackResult should carry the - * transformation info so that if the tried item is later modified or tried - * multiple times, the result object should set it to the originally - * determied position. An implementation can be found in the - * strategies::PlacerBoilerplate::PackResult class. - * - * @param item Ithe item to be packed. - * @return The PackResult object that can be implicitly casted to bool. + * \param item_store A container of items */ - inline PackResult trypack(Item& item) { return impl_.trypack(item); } + template + inline PackResult trypack(Container& item_store, + typename Container::iterator from, + unsigned count = 1) { + using V = typename Container::value_type; + static_assert(std::is_convertible::value, + "Invalid Item container!"); + return impl_.trypack(item_store, from, count); + } /** * @brief A method to accept a previously tried item. @@ -578,7 +577,16 @@ public: * @return Returns true if the item was packed or false if it could not be * packed. */ - inline bool pack(Item& item) { return impl_.pack(item); } + template + inline bool pack(Container& item_store, + typename Container::iterator from, + unsigned count = 1) + { + using V = typename Container::value_type; + static_assert(std::is_convertible::value, + "Invalid Item container!"); + return impl_.pack(item_store, from, count); + } /// Unpack the last element (remove it from the list of packed items). inline void unpackLast() { impl_.unpackLast(); } diff --git a/xs/src/libnest2d/libnest2d/placers/bottomleftplacer.hpp b/xs/src/libnest2d/libnest2d/placers/bottomleftplacer.hpp index 775e44e09..71573e34d 100644 --- a/xs/src/libnest2d/libnest2d/placers/bottomleftplacer.hpp +++ b/xs/src/libnest2d/libnest2d/placers/bottomleftplacer.hpp @@ -27,9 +27,14 @@ public: explicit _BottomLeftPlacer(const BinType& bin): Base(bin) {} - PackResult trypack(Item& item) { + template + PackResult trypack(Store& /*s*/, typename Store::iterator from, + unsigned /*count*/ = 1) + { + Item& item = *from; auto r = _trypack(item); if(!r && Base::config_.allow_rotations) { + item.rotate(Degrees(90)); r =_trypack(item); } diff --git a/xs/src/libnest2d/libnest2d/placers/nfpplacer.hpp b/xs/src/libnest2d/libnest2d/placers/nfpplacer.hpp index 110c05f0c..d74fe2b1d 100644 --- a/xs/src/libnest2d/libnest2d/placers/nfpplacer.hpp +++ b/xs/src/libnest2d/libnest2d/placers/nfpplacer.hpp @@ -19,6 +19,8 @@ namespace libnest2d { namespace strategies { template struct NfpPConfig { + using ItemGroup = std::vector>>; + enum class Alignment { CENTER, BOTTOM_LEFT, @@ -57,8 +59,8 @@ struct NfpPConfig { * \param item The second parameter is the candidate item. * * \param occupied_area The third parameter is the sum of areas of the - * items in the first parameter so you don't have to iterate through them - * if you only need their area. + * items in the first parameter (no candidate item there) so you don't have + * to iterate through them if you only need their accumulated area. * * \param norm A norming factor for physical dimensions. E.g. if your score * is the distance between the item and the bin center, you should divide @@ -66,21 +68,21 @@ struct NfpPConfig { * divide it with the square of the norming factor. Imagine it as a unit of * distance. * - * \param penality The fifth parameter is the amount of minimum penality if - * the arranged pile would't fit into the bin. You can use the wouldFit() - * function to check this. Note that the pile can be outside the bin's - * boundaries while the placement algorithm is running. Your job is only to - * check if the pile could be translated into a position in the bin where - * all the items would be inside. For a box shaped bin you can use the - * pile's bounding box to check whether it's width and height is small - * enough. If the pile would not fit, you have to make sure that the - * resulting score will be higher then the penality value. A good solution - * would be to set score = 2*penality-score in case the pile wouldn't fit - * into the bin. + * \param remaining A container with the remaining items waiting to be + * placed. You can use some features about the remaining items to alter to + * score of the current placement. If you know that you have to leave place + * for other items as well, that might influence your decision about where + * the current candidate should be placed. E.g. imagine three big circles + * which you want to place into a box: you might place them in a triangle + * shape which has the maximum pack density. But if there is a 4th big + * circle than you won't be able to pack it. If you knew apriori that + * there four circles are to be placed, you would have placed the first 3 + * into an L shape. This parameter can be used to make these kind of + * decisions (for you or a more intelligent AI). * */ std::function&, const _Item&, - double, double, double)> + double, double, const ItemGroup&)> object_function; /** @@ -450,11 +452,13 @@ _Circle> minimizeCircle(const RawShape& sh) { using Point = TPoint; using Coord = TCoord; + auto& ctr = sl::getContour(sh); + if(ctr.empty()) return {{0, 0}, 0}; + auto bb = sl::boundingBox(sh); auto capprx = bb.center(); auto rapprx = pl::distance(bb.minCorner(), bb.maxCorner()); - auto& ctr = sl::getContour(sh); opt::StopCriteria stopcr; stopcr.max_iterations = 100; @@ -513,7 +517,6 @@ class _NofitPolyPlacer: public PlacerBoilerplate<_NofitPolyPlacer>; const double norm_; - const double penality_; using MaxNfpLevel = Nfp::MaxNfpLevel; using sl = ShapeLike; @@ -524,8 +527,7 @@ public: inline explicit _NofitPolyPlacer(const BinType& bin): Base(bin), - norm_(std::sqrt(sl::area(bin))), - penality_(1e6*norm_) {} + norm_(std::sqrt(sl::area(bin))) {} _NofitPolyPlacer(const _NofitPolyPlacer&) = default; _NofitPolyPlacer& operator=(const _NofitPolyPlacer&) = default; @@ -575,7 +577,15 @@ public: return boundingCircle(chull).radius() < bin.radius(); } - PackResult trypack(Item& item) { + template + PackResult trypack(Container& items, + typename Container::iterator from, + unsigned /*count*/ = 1) + { + return trypack(*from, {std::next(from), items.end()}); + } + + PackResult trypack(Item& item, ItemGroup remaining) { PackResult ret; @@ -586,7 +596,7 @@ public: can_pack = item.isInside(bin_); } else { - double global_score = penality_; + double global_score = std::numeric_limits::max(); auto initial_tr = item.translation(); auto initial_rot = item.rotation(); @@ -630,9 +640,8 @@ public: auto getNfpPoint = [&ecache](const Optimum& opt) { - auto ret = opt.hidx < 0? ecache[opt.nfpidx].coords(opt.relpos) : + return opt.hidx < 0? ecache[opt.nfpidx].coords(opt.relpos) : ecache[opt.nfpidx].coords(opt.hidx, opt.relpos); - return ret; }; Nfp::Shapes pile; @@ -654,7 +663,7 @@ public: const Item& item, double occupied_area, double norm, - double /*penality*/) + const ItemGroup& /*remaining*/) { merged_pile.emplace_back(item.transformedShape()); auto ch = sl::convexHull(merged_pile); @@ -686,7 +695,7 @@ public: double occupied_area = pile_area + item.area(); double score = _objfunc(pile, item, occupied_area, - norm_, penality_); + norm_, remaining); return score; }; @@ -705,12 +714,12 @@ public: }; opt::StopCriteria stopcr; - stopcr.max_iterations = 100; - stopcr.relative_score_difference = 1e-12; + stopcr.max_iterations = 200; + stopcr.relative_score_difference = 1e-20; opt::TOptimizer solver(stopcr); Optimum optimum(0, 0); - double best_score = penality_; + double best_score = std::numeric_limits::max(); // Local optimization with the four polygon corners as // starting points @@ -821,7 +830,6 @@ private: inline void finalAlign(_Circle> cbin) { if(items_.empty()) return; - Nfp::Shapes m; m.reserve(items_.size()); for(Item& item : items_) m.emplace_back(item.transformedShape()); @@ -833,6 +841,7 @@ private: } inline void finalAlign(Box bbin) { + if(items_.empty()) return; Nfp::Shapes m; m.reserve(items_.size()); for(Item& item : items_) m.emplace_back(item.transformedShape()); diff --git a/xs/src/libnest2d/libnest2d/placers/placer_boilerplate.hpp b/xs/src/libnest2d/libnest2d/placers/placer_boilerplate.hpp index 9d2cb626b..f31a9343c 100644 --- a/xs/src/libnest2d/libnest2d/placers/placer_boilerplate.hpp +++ b/xs/src/libnest2d/libnest2d/placers/placer_boilerplate.hpp @@ -56,8 +56,11 @@ public: config_ = config; } - bool pack(Item& item) { - auto&& r = static_cast(this)->trypack(item); + template + bool pack(Container& items, + typename Container::iterator from, + unsigned count = 1) { + auto&& r = static_cast(this)->trypack(items, from, count); if(r) { items_.push_back(*(r.item_ptr_)); farea_valid_ = false; diff --git a/xs/src/libnest2d/libnest2d/selections/djd_heuristic.hpp b/xs/src/libnest2d/libnest2d/selections/djd_heuristic.hpp index e3ad97c10..34d6d05c5 100644 --- a/xs/src/libnest2d/libnest2d/selections/djd_heuristic.hpp +++ b/xs/src/libnest2d/libnest2d/selections/djd_heuristic.hpp @@ -230,7 +230,7 @@ public: while(it != not_packed.end() && !ret && free_area - (item_area = it->get().area()) <= waste) { - if(item_area <= free_area && placer.pack(*it) ) { + if(item_area <= free_area && placer.pack(not_packed, it) ) { free_area -= item_area; filled_area = bin_area - free_area; ret = true; @@ -278,7 +278,7 @@ public: if(item_area + smallestPiece(it, not_packed)->get().area() > free_area ) { it++; continue; } - auto pr = placer.trypack(*it); + auto pr = placer.trypack(not_packed, it); // First would fit it2 = not_packed.begin(); @@ -294,14 +294,14 @@ public: } placer.accept(pr); - auto pr2 = placer.trypack(*it2); + auto pr2 = placer.trypack(not_packed, it2); if(!pr2) { placer.unpackLast(); // remove first if(try_reverse) { - pr2 = placer.trypack(*it2); + pr2 = placer.trypack(not_packed, it2); if(pr2) { placer.accept(pr2); - auto pr12 = placer.trypack(*it); + auto pr12 = placer.trypack(not_packed, it); if(pr12) { placer.accept(pr12); ret = true; @@ -394,7 +394,7 @@ public: it++; continue; } - auto pr = placer.trypack(*it); + auto pr = placer.trypack(not_packed, it); // Check for free area and try to pack the 1st item... if(!pr) { it++; continue; } @@ -420,15 +420,15 @@ public: bool can_pack2 = false; placer.accept(pr); - auto pr2 = placer.trypack(*it2); + auto pr2 = placer.trypack(not_packed, it2); auto pr12 = pr; if(!pr2) { placer.unpackLast(); // remove first if(try_reverse) { - pr2 = placer.trypack(*it2); + pr2 = placer.trypack(not_packed, it2); if(pr2) { placer.accept(pr2); - pr12 = placer.trypack(*it); + pr12 = placer.trypack(not_packed, it); if(pr12) can_pack2 = true; placer.unpackLast(); } @@ -463,7 +463,7 @@ public: if(a3_sum > free_area) { it3++; continue; } placer.accept(pr12); placer.accept(pr2); - bool can_pack3 = placer.pack(*it3); + bool can_pack3 = placer.pack(not_packed, it3); if(!can_pack3) { placer.unpackLast(); @@ -473,16 +473,16 @@ public: if(!can_pack3 && try_reverse) { std::array indices = {0, 1, 2}; - std::array - candidates = {*it, *it2, *it3}; + std::array + candidates = {it, it2, it3}; - auto tryPack = [&placer, &candidates]( + auto tryPack = [&placer, &candidates, ¬_packed]( const decltype(indices)& idx) { std::array packed = {false}; for(auto id : idx) packed.at(id) = - placer.pack(candidates[id]); + placer.pack(not_packed, candidates[id]); bool check = std::all_of(packed.begin(), @@ -536,7 +536,7 @@ public: { auto it = store_.begin(); while (it != store_.end()) { Placer p(bin); p.configure(pconfig); - if(!p.pack(*it)) { + if(!p.pack(store_, it)) { it = store_.erase(it); } else it++; } @@ -601,7 +601,7 @@ public: while(it != not_packed.end() && filled_area < INITIAL_FILL_AREA) { - if(placer.pack(*it)) { + if(placer.pack(not_packed, it)) { filled_area += it->get().area(); free_area = bin_area - filled_area; it = not_packed.erase(it); diff --git a/xs/src/libnest2d/libnest2d/selections/filler.hpp b/xs/src/libnest2d/libnest2d/selections/filler.hpp index d0018dc73..ca1281fe6 100644 --- a/xs/src/libnest2d/libnest2d/selections/filler.hpp +++ b/xs/src/libnest2d/libnest2d/selections/filler.hpp @@ -65,7 +65,7 @@ public: auto it = store_.begin(); while(it != store_.end()) { - if(!placer.pack(*it)) { + if(!placer.pack(store_, it)) { if(packed_bins_.back().empty()) ++it; // makeProgress(placer); placer.clearItems(); diff --git a/xs/src/libnest2d/libnest2d/selections/firstfit.hpp b/xs/src/libnest2d/libnest2d/selections/firstfit.hpp index 665b9da9f..93ca02b1e 100644 --- a/xs/src/libnest2d/libnest2d/selections/firstfit.hpp +++ b/xs/src/libnest2d/libnest2d/selections/firstfit.hpp @@ -40,6 +40,7 @@ public: packed_bins_.clear(); std::vector placers; + placers.reserve(last-first); std::copy(first, last, std::back_inserter(store_)); @@ -60,18 +61,19 @@ public: { auto it = store_.begin(); while (it != store_.end()) { Placer p(bin); p.configure(pconfig); - if(!p.pack(*it)) { + if(!p.pack(store_, it)) { it = store_.erase(it); } else it++; } } - for(auto& item : store_ ) { + auto it = store_.begin(); + while(it != store_.end()) { bool was_packed = false; while(!was_packed) { for(size_t j = 0; j < placers.size() && !was_packed; j++) { - if((was_packed = placers[j].pack(item))) + if((was_packed = placers[j].pack(store_, it))) makeProgress(placers[j], j); } @@ -81,6 +83,7 @@ public: packed_bins_.emplace_back(); } } + ++it; } } diff --git a/xs/src/libnest2d/tests/test.cpp b/xs/src/libnest2d/tests/test.cpp index 1e030c056..79832b683 100644 --- a/xs/src/libnest2d/tests/test.cpp +++ b/xs/src/libnest2d/tests/test.cpp @@ -471,8 +471,8 @@ TEST(GeometryAlgorithms, BottomLeftStressTest) { auto next = it; int i = 0; while(it != input.end() && ++next != input.end()) { - placer.pack(*it); - placer.pack(*next); + placer.pack(input, it); + placer.pack(input, next); auto result = placer.getItems(); bool valid = true; diff --git a/xs/src/libslic3r/ModelArrange.hpp b/xs/src/libslic3r/ModelArrange.hpp index f4ce0daca..952a9e3a6 100644 --- a/xs/src/libslic3r/ModelArrange.hpp +++ b/xs/src/libslic3r/ModelArrange.hpp @@ -99,6 +99,7 @@ namespace bgi = boost::geometry::index; using SpatElement = std::pair; using SpatIndex = bgi::rtree< SpatElement, bgi::rstar<16, 4> >; +using ItemGroup = std::vector>; std::tuple objfunc(const PointImpl& bincenter, @@ -109,24 +110,21 @@ objfunc(const PointImpl& bincenter, double norm, // A norming factor for physical dimensions std::vector& areacache, // pile item areas will be cached // a spatial index to quickly get neighbors of the candidate item - SpatIndex& spatindex + SpatIndex& spatindex, + const ItemGroup& remaining ) { using pl = PointLike; using sl = ShapeLike; + using Coord = TCoord; static const double BIG_ITEM_TRESHOLD = 0.02; static const double ROUNDNESS_RATIO = 0.5; static const double DENSITY_RATIO = 1.0 - ROUNDNESS_RATIO; // We will treat big items (compared to the print bed) differently - auto isBig = [&areacache, bin_area](double a) { - double farea = areacache.empty() ? 0 : areacache.front(); - bool fbig = farea / bin_area > BIG_ITEM_TRESHOLD; - bool abig = a/bin_area > BIG_ITEM_TRESHOLD; - bool rbig = fbig && a > 0.5*farea; - return abig || rbig; + return a/bin_area > BIG_ITEM_TRESHOLD ; }; // If a new bin has been created: @@ -195,39 +193,74 @@ objfunc(const PointImpl& bincenter, auto dist = *(std::min_element(dists.begin(), dists.end())) / norm; // Density is the pack density: how big is the arranged pile - auto density = std::sqrt(fullbb.width()*fullbb.height()) / norm; + double density = 0; - // Prepare a variable for the alignment score. - // This will indicate: how well is the candidate item aligned with - // its neighbors. We will check the aligment with all neighbors and - // return the score for the best alignment. So it is enough for the - // candidate to be aligned with only one item. - auto alignment_score = std::numeric_limits::max(); + if(remaining.empty()) { + pile.emplace_back(item.transformedShape()); + auto chull = sl::convexHull(pile); + pile.pop_back(); + strategies::EdgeCache ec(chull); - auto& trsh = item.transformedShape(); + double circ = ec.circumference() / norm; + double bcirc = 2.0*(fullbb.width() + fullbb.height()) / norm; + score = 0.5*circ + 0.5*bcirc; - auto querybb = item.boundingBox(); + } else { + // Prepare a variable for the alignment score. + // This will indicate: how well is the candidate item aligned with + // its neighbors. We will check the aligment with all neighbors and + // return the score for the best alignment. So it is enough for the + // candidate to be aligned with only one item. + auto alignment_score = std::numeric_limits::max(); - // Query the spatial index for the neigbours - std::vector result; - spatindex.query(bgi::intersects(querybb), std::back_inserter(result)); + density = (fullbb.width()*fullbb.height()) / (norm*norm); + auto& trsh = item.transformedShape(); + auto querybb = item.boundingBox(); + auto wp = querybb.width()*0.2; + auto hp = querybb.height()*0.2; + auto pad = PointImpl( Coord(wp), Coord(hp)); + querybb = Box({ querybb.minCorner() - pad, + querybb.maxCorner() + pad + }); - for(auto& e : result) { // now get the score for the best alignment - auto idx = e.second; - auto& p = pile[idx]; - auto parea = areacache[idx]; - auto bb = sl::boundingBox(sl::Shapes{p, trsh}); - auto bbarea = bb.area(); - auto ascore = 1.0 - (item.area() + parea)/bbarea; + // Query the spatial index for the neigbours + std::vector 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(querybb, ibb) || +// boost::geometry::intersects(querybb, ibb)) << std::endl; +// } + + for(auto& e : result) { // now get the score for the best alignment + auto idx = e.second; + auto& p = pile[idx]; + auto parea = areacache[idx]; + auto bb = sl::boundingBox(sl::Shapes{p, trsh}); + auto bbarea = bb.area(); + auto ascore = 1.0 - (item.area() + parea)/bbarea; + + if(ascore < alignment_score) alignment_score = ascore; + } + + // The final mix of the score is the balance between the distance + // from the full pile center, the pack density and the + // alignment with the neigbours + if(result.empty()) + score = 0.5 * dist + 0.5 * density; + else + score = 0.45 * dist + 0.45 * density + 0.1 * alignment_score; - if(ascore < alignment_score) alignment_score = ascore; } - // The final mix of the score is the balance between the distance - // from the full pile center, the pack density and the - // alignment with the neigbours - score = 0.45 * dist + 0.45 * density + 0.1 * alignment_score; - } else if( !isBig(item.area()) && spatindex.empty()) { // If there are no big items, only small, we should consider the // density here as well to not get silly results @@ -312,10 +345,12 @@ public: const Item &item, double pile_area, double norm, - double /*penality*/) { + const ItemGroup& rem) { auto result = objfunc(bin.center(), bin_area_, pile, - pile_area, item, norm, areacache_, rtree_); + pile_area, item, norm, areacache_, + rtree_, + rem); double score = std::get<0>(result); auto& fullbb = std::get<1>(result); @@ -346,10 +381,11 @@ public: const Item &item, double pile_area, double norm, - double /*penality*/) { + const ItemGroup& rem) { auto result = objfunc(bin.center(), bin_area_, pile, - pile_area, item, norm, areacache_, rtree_); + pile_area, item, norm, areacache_, + rtree_, rem); double score = std::get<0>(result); auto& fullbb = std::get<1>(result); @@ -391,11 +427,12 @@ public: const Item &item, double pile_area, double norm, - double /*penality*/) { + const ItemGroup& rem) { auto binbb = ShapeLike::boundingBox(bin); auto result = objfunc(binbb.center(), bin_area_, pile, - pile_area, item, norm, areacache_, rtree_); + pile_area, item, norm, areacache_, + rtree_, rem); double score = std::get<0>(result); return score; @@ -417,10 +454,11 @@ public: const Item &item, double pile_area, double norm, - double /*penality*/) { + const ItemGroup& rem) { auto result = objfunc({0, 0}, 0, pile, pile_area, - item, norm, areacache_, rtree_); + item, norm, areacache_, + rtree_, rem); return std::get<0>(result); }; From ad92aa74865fcaf522f936b5962988753b7bdac3 Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Wed, 8 Aug 2018 12:51:17 +0200 Subject: [PATCH 07/10] Solution for stupid arrangement of rotated items and some fine tuning. --- xs/src/libslic3r/ModelArrange.hpp | 44 ++++++++----------------------- 1 file changed, 11 insertions(+), 33 deletions(-) diff --git a/xs/src/libslic3r/ModelArrange.hpp b/xs/src/libslic3r/ModelArrange.hpp index 952a9e3a6..79371cdb2 100644 --- a/xs/src/libslic3r/ModelArrange.hpp +++ b/xs/src/libslic3r/ModelArrange.hpp @@ -167,10 +167,6 @@ objfunc(const PointImpl& bincenter, if(isBig(item.area())) { // 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 some unwanted strange arrangements. auto minc = ibb.minCorner(); // bottom left corner auto maxc = ibb.maxCorner(); // top right corner @@ -211,17 +207,11 @@ objfunc(const PointImpl& bincenter, // its neighbors. We will check the aligment with all neighbors and // return the score for the best alignment. So it is enough for the // candidate to be aligned with only one item. - auto alignment_score = std::numeric_limits::max(); + auto alignment_score = 1.0; density = (fullbb.width()*fullbb.height()) / (norm*norm); auto& trsh = item.transformedShape(); auto querybb = item.boundingBox(); - auto wp = querybb.width()*0.2; - auto hp = querybb.height()*0.2; - auto pad = PointImpl( Coord(wp), Coord(hp)); - querybb = Box({ querybb.minCorner() - pad, - querybb.maxCorner() + pad - }); // Query the spatial index for the neigbours std::vector result; @@ -229,26 +219,17 @@ objfunc(const PointImpl& bincenter, 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(querybb, ibb) || -// boost::geometry::intersects(querybb, ibb)) << std::endl; -// } - for(auto& e : result) { // now get the score for the best alignment auto idx = e.second; auto& p = pile[idx]; auto parea = areacache[idx]; - auto bb = sl::boundingBox(sl::Shapes{p, trsh}); - auto bbarea = bb.area(); - auto ascore = 1.0 - (item.area() + parea)/bbarea; + if(std::abs(1.0 - parea/item.area()) < 1e-6) { + auto bb = sl::boundingBox(sl::Shapes{p, trsh}); + auto bbarea = bb.area(); + auto ascore = 1.0 - (item.area() + parea)/bbarea; - if(ascore < alignment_score) alignment_score = ascore; + if(ascore < alignment_score) alignment_score = ascore; + } } // The final mix of the score is the balance between the distance @@ -258,15 +239,12 @@ objfunc(const PointImpl& bincenter, score = 0.5 * dist + 0.5 * density; else score = 0.45 * dist + 0.45 * density + 0.1 * alignment_score; - } - } else if( !isBig(item.area()) && spatindex.empty()) { - // If there are no big items, only small, we should consider the - // density here as well to not get silly results auto bindist = pl::distance(ibb.center(), bincenter) / norm; - auto density = std::sqrt(fullbb.width()*fullbb.height()) / norm; - score = ROUNDNESS_RATIO * bindist + DENSITY_RATIO * density; + + // Bindist is surprisingly enough... + score = bindist; } else { // Here there are the small items that should be placed around the // already processed bigger items. @@ -294,7 +272,7 @@ void fillConfig(PConf& pcfg) { // The accuracy of optimization. // Goes from 0.0 to 1.0 and scales performance as well - pcfg.accuracy = 0.6f; + pcfg.accuracy = 0.65f; } template From e678368b233670ab6a707f87b42306964f12d463 Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Thu, 9 Aug 2018 09:59:05 +0200 Subject: [PATCH 08/10] fix compilation on linux and mac --- xs/src/libnest2d/CMakeLists.txt | 1 + xs/src/libnest2d/examples/main.cpp | 20 +- xs/src/libnest2d/libnest2d.h | 4 +- xs/src/libnest2d/libnest2d/boost_alg.hpp | 98 ++++----- .../clipper_backend/clipper_backend.hpp | 105 +++++---- .../libnest2d/libnest2d/geometry_traits.hpp | 208 ++++++++++-------- .../libnest2d/geometry_traits_nfp.hpp | 113 +++++----- xs/src/libnest2d/libnest2d/libnest2d.hpp | 143 ++++++------ .../libnest2d/placers/bottomleftplacer.hpp | 63 +++--- .../libnest2d/libnest2d/placers/nfpplacer.hpp | 125 +++++------ .../libnest2d/placers/placer_boilerplate.hpp | 25 +-- xs/src/libnest2d/libnest2d/rotfinder.hpp | 41 ++++ .../libnest2d/selections/djd_heuristic.hpp | 48 ++-- .../libnest2d/libnest2d/selections/filler.hpp | 10 +- .../libnest2d/selections/firstfit.hpp | 7 +- xs/src/libnest2d/tests/test.cpp | 58 ++--- xs/src/libnest2d/tools/svgtools.hpp | 6 +- xs/src/libslic3r/ModelArrange.hpp | 58 ++--- 18 files changed, 590 insertions(+), 543 deletions(-) create mode 100644 xs/src/libnest2d/libnest2d/rotfinder.hpp diff --git a/xs/src/libnest2d/CMakeLists.txt b/xs/src/libnest2d/CMakeLists.txt index 835e8311d..0a181f4ab 100644 --- a/xs/src/libnest2d/CMakeLists.txt +++ b/xs/src/libnest2d/CMakeLists.txt @@ -31,6 +31,7 @@ set(LIBNEST2D_SRCFILES ${CMAKE_CURRENT_SOURCE_DIR}/libnest2d/common.hpp ${CMAKE_CURRENT_SOURCE_DIR}/libnest2d/optimizer.hpp ${CMAKE_CURRENT_SOURCE_DIR}/libnest2d/metaloop.hpp + ${CMAKE_CURRENT_SOURCE_DIR}/libnest2d/rotfinder.hpp ${CMAKE_CURRENT_SOURCE_DIR}/libnest2d/placers/placer_boilerplate.hpp ${CMAKE_CURRENT_SOURCE_DIR}/libnest2d/placers/bottomleftplacer.hpp ${CMAKE_CURRENT_SOURCE_DIR}/libnest2d/placers/nfpplacer.hpp diff --git a/xs/src/libnest2d/examples/main.cpp b/xs/src/libnest2d/examples/main.cpp index 11f8f50cf..37096019d 100644 --- a/xs/src/libnest2d/examples/main.cpp +++ b/xs/src/libnest2d/examples/main.cpp @@ -9,6 +9,8 @@ #include "tests/printer_parts.h" #include "tools/benchmark.h" #include "tools/svgtools.hpp" +#include "libnest2d/rotfinder.hpp" + //#include "tools/libnfpglue.hpp" using namespace libnest2d; @@ -64,7 +66,7 @@ void arrangeRectangles() { // input.insert(input.end(), proba.begin(), proba.end()); // input.insert(input.end(), crasher.begin(), crasher.end()); -// Box bin(250*SCALE, 210*SCALE); + Box bin(250*SCALE, 210*SCALE); // PolygonImpl bin = { // { // {25*SCALE, 0}, @@ -80,12 +82,12 @@ void arrangeRectangles() { // {} // }; - _Circle bin({0, 0}, 125*SCALE); +// _Circle bin({0, 0}, 125*SCALE); auto min_obj_distance = static_cast(0*SCALE); using Placer = strategies::_NofitPolyPlacer; - using Packer = Arranger; + using Packer = Nester; Packer arrange(bin, min_obj_distance); @@ -112,7 +114,9 @@ void arrangeRectangles() { // svgw.writePackGroup(arrange.lastResult()); // svgw.save("debout"); std::cout << "Remaining items: " << r << std::endl; - })/*.useMinimumBoundigBoxRotation()*/; + }); + +// findMinimumBoundingBoxRotations(input.begin(), input.end()); Benchmark bench; @@ -120,7 +124,7 @@ void arrangeRectangles() { Packer::ResultType result; try { - result = arrange.arrange(input.begin(), input.end()); + result = arrange.execute(input.begin(), input.end()); } catch(GeometryException& ge) { std::cerr << "Geometry error: " << ge.what() << std::endl; return ; @@ -134,7 +138,7 @@ void arrangeRectangles() { std::vector eff; eff.reserve(result.size()); - auto bin_area = ShapeLike::area(bin); + auto bin_area = sl::area(bin); for(auto& r : result) { double a = 0; std::for_each(r.begin(), r.end(), [&a] (Item& e ){ a += e.area(); }); @@ -156,7 +160,7 @@ void arrangeRectangles() { std::cout << ") Total: " << total << std::endl; for(auto& it : input) { - auto ret = ShapeLike::isValid(it.transformedShape()); + auto ret = sl::isValid(it.transformedShape()); std::cout << ret.second << std::endl; } @@ -177,7 +181,7 @@ void arrangeRectangles() { int main(void /*int argc, char **argv*/) { arrangeRectangles(); -// findDegenerateCase(); +//// findDegenerateCase(); return EXIT_SUCCESS; } diff --git a/xs/src/libnest2d/libnest2d.h b/xs/src/libnest2d/libnest2d.h index c9e21ecfb..05677afd7 100644 --- a/xs/src/libnest2d/libnest2d.h +++ b/xs/src/libnest2d/libnest2d.h @@ -22,6 +22,7 @@ using Point = PointImpl; using Coord = TCoord; using Box = _Box; using Segment = _Segment; +using Circle = _Circle; using Item = _Item; using Rectangle = _Rectangle; @@ -36,9 +37,6 @@ using DJDHeuristic = strategies::_DJDHeuristic; using NfpPlacer = strategies::_NofitPolyPlacer; using BottomLeftPlacer = strategies::_BottomLeftPlacer; -//template -//using NofitPolyPlacer = strategies::_NofitPolyPlacer; - } #endif // LIBNEST2D_H diff --git a/xs/src/libnest2d/libnest2d/boost_alg.hpp b/xs/src/libnest2d/libnest2d/boost_alg.hpp index 67e19fcbd..7da1036f0 100644 --- a/xs/src/libnest2d/libnest2d/boost_alg.hpp +++ b/xs/src/libnest2d/libnest2d/boost_alg.hpp @@ -36,7 +36,7 @@ using libnest2d::setX; using libnest2d::setY; using Box = libnest2d::_Box; using Segment = libnest2d::_Segment; -using Shapes = libnest2d::Nfp::Shapes; +using Shapes = libnest2d::nfp::Shapes; } @@ -241,11 +241,11 @@ template<> struct tag { template<> struct exterior_ring { static inline bp2d::PathImpl& get(bp2d::PolygonImpl& p) { - return libnest2d::ShapeLike::getContour(p); + return libnest2d::shapelike::getContour(p); } static inline bp2d::PathImpl const& get(bp2d::PolygonImpl const& p) { - return libnest2d::ShapeLike::getContour(p); + return libnest2d::shapelike::getContour(p); } }; @@ -271,13 +271,13 @@ struct interior_rings { static inline libnest2d::THolesContainer& get( bp2d::PolygonImpl& p) { - return libnest2d::ShapeLike::holes(p); + return libnest2d::shapelike::holes(p); } static inline const libnest2d::THolesContainer& get( bp2d::PolygonImpl const& p) { - return libnest2d::ShapeLike::holes(p); + return libnest2d::shapelike::holes(p); } }; @@ -311,83 +311,78 @@ struct range_value { namespace libnest2d { // Now the algorithms that boost can provide... +namespace pointlike { template<> -inline double PointLike::distance(const PointImpl& p1, - const PointImpl& p2 ) +inline double distance(const PointImpl& p1, const PointImpl& p2 ) { return boost::geometry::distance(p1, p2); } template<> -inline double PointLike::distance(const PointImpl& p, - const bp2d::Segment& seg ) +inline double distance(const PointImpl& p, const bp2d::Segment& seg ) { return boost::geometry::distance(p, seg); } +} +namespace shapelike { // Tell libnest2d how to make string out of a ClipperPolygon object template<> -inline bool ShapeLike::intersects(const PathImpl& sh1, - const PathImpl& sh2) +inline bool intersects(const PathImpl& sh1, const PathImpl& sh2) { return boost::geometry::intersects(sh1, sh2); } // Tell libnest2d how to make string out of a ClipperPolygon object template<> -inline bool ShapeLike::intersects(const PolygonImpl& sh1, - const PolygonImpl& sh2) +inline bool intersects(const PolygonImpl& sh1, const PolygonImpl& sh2) { return boost::geometry::intersects(sh1, sh2); } // Tell libnest2d how to make string out of a ClipperPolygon object template<> -inline bool ShapeLike::intersects(const bp2d::Segment& s1, - const bp2d::Segment& s2) +inline bool intersects(const bp2d::Segment& s1, const bp2d::Segment& s2) { return boost::geometry::intersects(s1, s2); } #ifndef DISABLE_BOOST_AREA template<> -inline double ShapeLike::area(const PolygonImpl& shape) +inline double area(const PolygonImpl& shape, const PolygonTag&) { return boost::geometry::area(shape); } #endif template<> -inline bool ShapeLike::isInside(const PointImpl& point, - const PolygonImpl& shape) +inline bool isInside(const PointImpl& point, + const PolygonImpl& shape) { return boost::geometry::within(point, shape); } template<> -inline bool ShapeLike::isInside(const PolygonImpl& sh1, - const PolygonImpl& sh2) +inline bool isInside(const PolygonImpl& sh1, const PolygonImpl& sh2) { return boost::geometry::within(sh1, sh2); } template<> -inline bool ShapeLike::touches( const PolygonImpl& sh1, - const PolygonImpl& sh2) +inline bool touches(const PolygonImpl& sh1, const PolygonImpl& sh2) { return boost::geometry::touches(sh1, sh2); } template<> -inline bool ShapeLike::touches( const PointImpl& point, - const PolygonImpl& shape) +inline bool touches( const PointImpl& point, const PolygonImpl& shape) { return boost::geometry::touches(point, shape); } #ifndef DISABLE_BOOST_BOUNDING_BOX template<> -inline bp2d::Box ShapeLike::boundingBox(const PolygonImpl& sh) +inline bp2d::Box boundingBox(const PolygonImpl& sh, const PolygonTag&) { bp2d::Box b; boost::geometry::envelope(sh, b); @@ -395,7 +390,7 @@ inline bp2d::Box ShapeLike::boundingBox(const PolygonImpl& sh) } template<> -inline bp2d::Box ShapeLike::boundingBox(const bp2d::Shapes& shapes) +inline bp2d::Box boundingBox(const bp2d::Shapes& shapes) { bp2d::Box b; boost::geometry::envelope(shapes, b); @@ -405,7 +400,7 @@ inline bp2d::Box ShapeLike::boundingBox(const bp2d::Shapes& shapes) #ifndef DISABLE_BOOST_CONVEX_HULL template<> -inline PolygonImpl ShapeLike::convexHull(const PolygonImpl& sh) +inline PolygonImpl convexHull(const PolygonImpl& sh) { PolygonImpl ret; boost::geometry::convex_hull(sh, ret); @@ -413,7 +408,7 @@ inline PolygonImpl ShapeLike::convexHull(const PolygonImpl& sh) } template<> -inline PolygonImpl ShapeLike::convexHull(const bp2d::Shapes& shapes) +inline PolygonImpl convexHull(const bp2d::Shapes& shapes) { PolygonImpl ret; boost::geometry::convex_hull(shapes, ret); @@ -423,7 +418,7 @@ inline PolygonImpl ShapeLike::convexHull(const bp2d::Shapes& shapes) #ifndef DISABLE_BOOST_ROTATE template<> -inline void ShapeLike::rotate(PolygonImpl& sh, const Radians& rads) +inline void rotate(PolygonImpl& sh, const Radians& rads) { namespace trans = boost::geometry::strategy::transform; @@ -437,7 +432,7 @@ inline void ShapeLike::rotate(PolygonImpl& sh, const Radians& rads) #ifndef DISABLE_BOOST_TRANSLATE template<> -inline void ShapeLike::translate(PolygonImpl& sh, const PointImpl& offs) +inline void translate(PolygonImpl& sh, const PointImpl& offs) { namespace trans = boost::geometry::strategy::transform; @@ -451,26 +446,15 @@ inline void ShapeLike::translate(PolygonImpl& sh, const PointImpl& offs) #ifndef DISABLE_BOOST_OFFSET template<> -inline void ShapeLike::offset(PolygonImpl& sh, bp2d::Coord distance) +inline void offset(PolygonImpl& sh, bp2d::Coord distance) { PolygonImpl cpy = sh; boost::geometry::buffer(cpy, sh, distance); } #endif -#ifndef DISABLE_BOOST_NFP_MERGE -template<> -inline bp2d::Shapes Nfp::merge(const bp2d::Shapes& shapes, - const PolygonImpl& sh) -{ - bp2d::Shapes retv; - boost::geometry::union_(shapes, sh, retv); - return retv; -} -#endif - #ifndef DISABLE_BOOST_SERIALIZE -template<> inline std::string ShapeLike::serialize( +template<> inline std::string serialize( const PolygonImpl& sh, double scale) { std::stringstream ss; @@ -482,14 +466,14 @@ template<> inline std::string ShapeLike::serialize( Polygonf::ring_type ring; Polygonf::inner_container_type holes; - ring.reserve(ShapeLike::contourVertexCount(sh)); + ring.reserve(shapelike::contourVertexCount(sh)); - for(auto it = ShapeLike::cbegin(sh); it != ShapeLike::cend(sh); it++) { + for(auto it = shapelike::cbegin(sh); it != shapelike::cend(sh); it++) { auto& v = *it; ring.emplace_back(getX(v)*scale, getY(v)*scale); }; - auto H = ShapeLike::holes(sh); + auto H = shapelike::holes(sh); for(PathImpl& h : H ) { Polygonf::ring_type hf; for(auto it = h.begin(); it != h.end(); it++) { @@ -512,21 +496,37 @@ template<> inline std::string ShapeLike::serialize( #ifndef DISABLE_BOOST_UNSERIALIZE template<> -inline void ShapeLike::unserialize( +inline void unserialize( PolygonImpl& sh, const std::string& str) { } #endif -template<> inline std::pair -ShapeLike::isValid(const PolygonImpl& sh) +template<> inline std::pair isValid(const PolygonImpl& sh) { std::string message; bool ret = boost::geometry::is_valid(sh, message); return {ret, message}; } +} + +namespace nfp { + +#ifndef DISABLE_BOOST_NFP_MERGE +template<> +inline bp2d::Shapes Nfp::merge(const bp2d::Shapes& shapes, + const PolygonImpl& sh) +{ + bp2d::Shapes retv; + boost::geometry::union_(shapes, sh, retv); + return retv; +} +#endif + +} + } diff --git a/xs/src/libnest2d/libnest2d/clipper_backend/clipper_backend.hpp b/xs/src/libnest2d/libnest2d/clipper_backend/clipper_backend.hpp index 15ceb1576..4238212ad 100644 --- a/xs/src/libnest2d/libnest2d/clipper_backend/clipper_backend.hpp +++ b/xs/src/libnest2d/libnest2d/clipper_backend/clipper_backend.hpp @@ -21,6 +21,9 @@ struct PolygonImpl { PathImpl Contour; HoleStore Holes; + using Tag = libnest2d::PolygonTag; + using PointType = PointImpl; + inline PolygonImpl() = default; inline explicit PolygonImpl(const PathImpl& cont): Contour(cont) {} @@ -113,35 +116,32 @@ template<> struct CountourType { using Type = PathImpl; }; +namespace pointlike { + // Tell binpack2d how to extract the X coord from a ClipperPoint object -template<> inline TCoord PointLike::x(const PointImpl& p) +template<> inline TCoord x(const PointImpl& p) { return p.X; } // Tell binpack2d how to extract the Y coord from a ClipperPoint object -template<> inline TCoord PointLike::y(const PointImpl& p) +template<> inline TCoord y(const PointImpl& p) { return p.Y; } // Tell binpack2d how to extract the X coord from a ClipperPoint object -template<> inline TCoord& PointLike::x(PointImpl& p) +template<> inline TCoord& x(PointImpl& p) { return p.X; } // Tell binpack2d how to extract the Y coord from a ClipperPoint object -template<> -inline TCoord& PointLike::y(PointImpl& p) +template<> inline TCoord& y(PointImpl& p) { return p.Y; } -template<> -inline void ShapeLike::reserve(PolygonImpl& sh, size_t vertex_capacity) -{ - return sh.Contour.reserve(vertex_capacity); } #define DISABLE_BOOST_AREA @@ -175,16 +175,28 @@ inline double area(const PolygonImpl& sh) { return ClipperLib::Area(sh.Contour) + a; } + +} + +template<> struct HolesContainer { + using Type = ClipperLib::Paths; +}; + +namespace shapelike { + +template<> inline void reserve(PolygonImpl& sh, size_t vertex_capacity) +{ + return sh.Contour.reserve(vertex_capacity); } // Tell binpack2d how to make string out of a ClipperPolygon object -template<> -inline double ShapeLike::area(const PolygonImpl& sh) { +template<> inline double area(const PolygonImpl& sh, const PolygonTag&) +{ return _smartarea::area::Value>(sh); } -template<> -inline void ShapeLike::offset(PolygonImpl& sh, TCoord distance) { +template<> inline void offset(PolygonImpl& sh, TCoord distance) +{ #define DISABLE_BOOST_OFFSET using ClipperLib::ClipperOffset; @@ -234,7 +246,8 @@ inline void ShapeLike::offset(PolygonImpl& sh, TCoord distance) { } // Tell libnest2d how to make string out of a ClipperPolygon object -template<> inline std::string ShapeLike::toString(const PolygonImpl& sh) { +template<> inline std::string toString(const PolygonImpl& sh) +{ std::stringstream ss; ss << "Contour {\n"; @@ -256,38 +269,31 @@ template<> inline std::string ShapeLike::toString(const PolygonImpl& sh) { return ss.str(); } -template<> -inline TVertexIterator ShapeLike::begin(PolygonImpl& sh) +template<> inline TVertexIterator begin(PolygonImpl& sh) { return sh.Contour.begin(); } -template<> -inline TVertexIterator ShapeLike::end(PolygonImpl& sh) +template<> inline TVertexIterator end(PolygonImpl& sh) { return sh.Contour.end(); } template<> -inline TVertexConstIterator ShapeLike::cbegin( - const PolygonImpl& sh) +inline TVertexConstIterator cbegin(const PolygonImpl& sh) { return sh.Contour.cbegin(); } -template<> -inline TVertexConstIterator ShapeLike::cend( +template<> inline TVertexConstIterator cend( const PolygonImpl& sh) { return sh.Contour.cend(); } -template<> struct HolesContainer { - using Type = ClipperLib::Paths; -}; - -template<> inline PolygonImpl ShapeLike::create(const PathImpl& path, - const HoleStore& holes) { +template<> +inline PolygonImpl create(const PathImpl& path, const HoleStore& holes) +{ PolygonImpl p; p.Contour = path; @@ -308,8 +314,7 @@ template<> inline PolygonImpl ShapeLike::create(const PathImpl& path, return p; } -template<> inline PolygonImpl ShapeLike::create( PathImpl&& path, - HoleStore&& holes) { +template<> inline PolygonImpl create( PathImpl&& path, HoleStore&& holes) { PolygonImpl p; p.Contour.swap(path); @@ -331,49 +336,49 @@ template<> inline PolygonImpl ShapeLike::create( PathImpl&& path, return p; } -template<> inline const THolesContainer& -ShapeLike::holes(const PolygonImpl& sh) +template<> +inline const THolesContainer& holes(const PolygonImpl& sh) { return sh.Holes; } -template<> inline THolesContainer& -ShapeLike::holes(PolygonImpl& sh) +template<> inline THolesContainer& holes(PolygonImpl& sh) { return sh.Holes; } -template<> inline TContour& -ShapeLike::getHole(PolygonImpl& sh, unsigned long idx) +template<> +inline TContour& getHole(PolygonImpl& sh, unsigned long idx) { return sh.Holes[idx]; } -template<> inline const TContour& -ShapeLike::getHole(const PolygonImpl& sh, unsigned long idx) +template<> +inline const TContour& getHole(const PolygonImpl& sh, + unsigned long idx) { return sh.Holes[idx]; } -template<> inline size_t ShapeLike::holeCount(const PolygonImpl& sh) +template<> inline size_t holeCount(const PolygonImpl& sh) { return sh.Holes.size(); } -template<> inline PathImpl& ShapeLike::getContour(PolygonImpl& sh) +template<> inline PathImpl& getContour(PolygonImpl& sh) { return sh.Contour; } template<> -inline const PathImpl& ShapeLike::getContour(const PolygonImpl& sh) +inline const PathImpl& getContour(const PolygonImpl& sh) { return sh.Contour; } #define DISABLE_BOOST_TRANSLATE template<> -inline void ShapeLike::translate(PolygonImpl& sh, const PointImpl& offs) +inline void translate(PolygonImpl& sh, const PointImpl& offs) { for(auto& p : sh.Contour) { p += offs; } for(auto& hole : sh.Holes) for(auto& p : hole) { p += offs; } @@ -381,7 +386,7 @@ inline void ShapeLike::translate(PolygonImpl& sh, const PointImpl& offs) #define DISABLE_BOOST_ROTATE template<> -inline void ShapeLike::rotate(PolygonImpl& sh, const Radians& rads) +inline void rotate(PolygonImpl& sh, const Radians& rads) { using Coord = TCoord; @@ -402,9 +407,11 @@ inline void ShapeLike::rotate(PolygonImpl& sh, const Radians& rads) } } +} // namespace shapelike + #define DISABLE_BOOST_NFP_MERGE -inline Nfp::Shapes _merge(ClipperLib::Clipper& clipper) { - Nfp::Shapes retv; +inline nfp::Shapes _merge(ClipperLib::Clipper& clipper) { + nfp::Shapes retv; ClipperLib::PolyTree result; clipper.Execute(ClipperLib::ctUnion, result, ClipperLib::pftNegative); @@ -438,8 +445,10 @@ inline Nfp::Shapes _merge(ClipperLib::Clipper& clipper) { return retv; } -template<> inline Nfp::Shapes -Nfp::merge(const Nfp::Shapes& shapes) +namespace nfp { + +template<> inline nfp::Shapes +merge(const nfp::Shapes& shapes) { ClipperLib::Clipper clipper(ClipperLib::ioReverseSolution); @@ -461,6 +470,8 @@ Nfp::merge(const Nfp::Shapes& shapes) } +} + //#define DISABLE_BOOST_SERIALIZE //#define DISABLE_BOOST_UNSERIALIZE diff --git a/xs/src/libnest2d/libnest2d/geometry_traits.hpp b/xs/src/libnest2d/libnest2d/geometry_traits.hpp index 1c0d44c9f..0826cbd4b 100644 --- a/xs/src/libnest2d/libnest2d/geometry_traits.hpp +++ b/xs/src/libnest2d/libnest2d/geometry_traits.hpp @@ -60,6 +60,10 @@ struct PointPair { RawPoint p2; }; +struct PolygonTag {}; +struct BoxTag {}; +struct CircleTag {}; + /** * \brief An abstraction of a box; */ @@ -69,6 +73,9 @@ class _Box: PointPair { using PointPair::p2; public: + using Tag = BoxTag; + using PointType = RawPoint; + inline _Box() = default; inline _Box(const RawPoint& p, const RawPoint& pp): PointPair({p, pp}) {} @@ -98,6 +105,9 @@ class _Circle { double radius_ = 0; public: + using Tag = CircleTag; + using PointType = RawPoint; + _Circle() = default; _Circle(const RawPoint& center, double r): center_(center), radius_(r) {} @@ -123,6 +133,8 @@ class _Segment: PointPair { mutable Radians angletox_ = std::nan(""); public: + using PointType = RawPoint; + inline _Segment() = default; inline _Segment(const RawPoint& p, const RawPoint& pp): @@ -156,36 +168,36 @@ public: inline double length(); }; -// This struct serves as a namespace. The only difference is that is can be +// This struct serves almost as a namespace. The only difference is that is can // used in friend declarations. -struct PointLike { +namespace pointlike { template - static TCoord x(const RawPoint& p) + inline TCoord x(const RawPoint& p) { return p.x(); } template - static TCoord y(const RawPoint& p) + inline TCoord y(const RawPoint& p) { return p.y(); } template - static TCoord& x(RawPoint& p) + inline TCoord& x(RawPoint& p) { return p.x(); } template - static TCoord& y(RawPoint& p) + inline TCoord& y(RawPoint& p) { return p.y(); } template - static double distance(const RawPoint& /*p1*/, const RawPoint& /*p2*/) + inline double distance(const RawPoint& /*p1*/, const RawPoint& /*p2*/) { static_assert(always_false::value, "PointLike::distance(point, point) unimplemented!"); @@ -193,7 +205,7 @@ struct PointLike { } template - static double distance(const RawPoint& /*p1*/, + inline double distance(const RawPoint& /*p1*/, const _Segment& /*s*/) { static_assert(always_false::value, @@ -202,13 +214,13 @@ struct PointLike { } template - static std::pair, bool> horizontalDistance( + inline std::pair, bool> horizontalDistance( const RawPoint& p, const _Segment& s) { using Unit = TCoord; - auto x = PointLike::x(p), y = PointLike::y(p); - auto x1 = PointLike::x(s.first()), y1 = PointLike::y(s.first()); - auto x2 = PointLike::x(s.second()), y2 = PointLike::y(s.second()); + auto x = pointlike::x(p), y = pointlike::y(p); + auto x1 = pointlike::x(s.first()), y1 = pointlike::y(s.first()); + auto x2 = pointlike::x(s.second()), y2 = pointlike::y(s.second()); TCoord ret; @@ -228,13 +240,13 @@ struct PointLike { } template - static std::pair, bool> verticalDistance( + inline std::pair, bool> verticalDistance( const RawPoint& p, const _Segment& s) { using Unit = TCoord; - auto x = PointLike::x(p), y = PointLike::y(p); - auto x1 = PointLike::x(s.first()), y1 = PointLike::y(s.first()); - auto x2 = PointLike::x(s.second()), y2 = PointLike::y(s.second()); + auto x = pointlike::x(p), y = pointlike::y(p); + auto x1 = pointlike::x(s.first()), y1 = pointlike::y(s.first()); + auto x2 = pointlike::x(s.second()), y2 = pointlike::y(s.second()); TCoord ret; @@ -252,36 +264,36 @@ struct PointLike { return {ret, true}; } -}; +} template TCoord _Box::width() const BP2D_NOEXCEPT { - return PointLike::x(maxCorner()) - PointLike::x(minCorner()); + return pointlike::x(maxCorner()) - pointlike::x(minCorner()); } template TCoord _Box::height() const BP2D_NOEXCEPT { - return PointLike::y(maxCorner()) - PointLike::y(minCorner()); + return pointlike::y(maxCorner()) - pointlike::y(minCorner()); } template -TCoord getX(const RawPoint& p) { return PointLike::x(p); } +TCoord getX(const RawPoint& p) { return pointlike::x(p); } template -TCoord getY(const RawPoint& p) { return PointLike::y(p); } +TCoord getY(const RawPoint& p) { return pointlike::y(p); } template void setX(RawPoint& p, const TCoord& val) { - PointLike::x(p) = val; + pointlike::x(p) = val; } template void setY(RawPoint& p, const TCoord& val) { - PointLike::y(p) = val; + pointlike::y(p) = val; } template @@ -303,7 +315,7 @@ inline Radians _Segment::angleToXaxis() const template inline double _Segment::length() { - return PointLike::distance(first(), second()); + return pointlike::distance(first(), second()); } template @@ -356,124 +368,124 @@ enum class Formats { // This struct serves as a namespace. The only difference is that it can be // used in friend declarations and can be aliased at class scope. -struct ShapeLike { +namespace shapelike { template using Shapes = std::vector; template - static RawShape create(const TContour& contour, + inline RawShape create(const TContour& contour, const THolesContainer& holes) { return RawShape(contour, holes); } template - static RawShape create(TContour&& contour, + inline RawShape create(TContour&& contour, THolesContainer&& holes) { return RawShape(contour, holes); } template - static RawShape create(const TContour& contour) + inline RawShape create(const TContour& contour) { return create(contour, {}); } template - static RawShape create(TContour&& contour) + inline RawShape create(TContour&& contour) { return create(contour, {}); } template - static THolesContainer& holes(RawShape& /*sh*/) + inline THolesContainer& holes(RawShape& /*sh*/) { static THolesContainer empty; return empty; } template - static const THolesContainer& holes(const RawShape& /*sh*/) + inline const THolesContainer& holes(const RawShape& /*sh*/) { static THolesContainer empty; return empty; } template - static TContour& getHole(RawShape& sh, unsigned long idx) + inline TContour& getHole(RawShape& sh, unsigned long idx) { return holes(sh)[idx]; } template - static const TContour& getHole(const RawShape& sh, + inline const TContour& getHole(const RawShape& sh, unsigned long idx) { return holes(sh)[idx]; } template - static size_t holeCount(const RawShape& sh) + inline size_t holeCount(const RawShape& sh) { return holes(sh).size(); } template - static TContour& getContour(RawShape& sh) + inline TContour& getContour(RawShape& sh) { return sh; } template - static const TContour& getContour(const RawShape& sh) + inline const TContour& getContour(const RawShape& sh) { return sh; } // Optional, does nothing by default template - static void reserve(RawShape& /*sh*/, size_t /*vertex_capacity*/) {} + inline void reserve(RawShape& /*sh*/, size_t /*vertex_capacity*/) {} template - static void addVertex(RawShape& sh, Args...args) + inline void addVertex(RawShape& sh, Args...args) { return getContour(sh).emplace_back(std::forward(args)...); } template - static TVertexIterator begin(RawShape& sh) + inline TVertexIterator begin(RawShape& sh) { return sh.begin(); } template - static TVertexIterator end(RawShape& sh) + inline TVertexIterator end(RawShape& sh) { return sh.end(); } template - static TVertexConstIterator cbegin(const RawShape& sh) + inline TVertexConstIterator cbegin(const RawShape& sh) { return sh.cbegin(); } template - static TVertexConstIterator cend(const RawShape& sh) + inline TVertexConstIterator cend(const RawShape& sh) { return sh.cend(); } template - static std::string toString(const RawShape& /*sh*/) + inline std::string toString(const RawShape& /*sh*/) { return ""; } template - static std::string serialize(const RawShape& /*sh*/, double /*scale*/=1) + inline std::string serialize(const RawShape& /*sh*/, double /*scale*/=1) { static_assert(always_false::value, "ShapeLike::serialize() unimplemented!"); @@ -481,14 +493,14 @@ struct ShapeLike { } template - static void unserialize(RawShape& /*sh*/, const std::string& /*str*/) + inline void unserialize(RawShape& /*sh*/, const std::string& /*str*/) { static_assert(always_false::value, "ShapeLike::unserialize() unimplemented!"); } template - static double area(const RawShape& /*sh*/) + inline double area(const RawShape& /*sh*/, const PolygonTag&) { static_assert(always_false::value, "ShapeLike::area() unimplemented!"); @@ -496,7 +508,7 @@ struct ShapeLike { } template - static bool intersects(const RawShape& /*sh*/, const RawShape& /*sh*/) + inline bool intersects(const RawShape& /*sh*/, const RawShape& /*sh*/) { static_assert(always_false::value, "ShapeLike::intersects() unimplemented!"); @@ -504,7 +516,7 @@ struct ShapeLike { } template - static bool isInside(const TPoint& /*point*/, + inline bool isInside(const TPoint& /*point*/, const RawShape& /*shape*/) { static_assert(always_false::value, @@ -513,7 +525,7 @@ struct ShapeLike { } template - static bool isInside(const RawShape& /*shape*/, + inline bool isInside(const RawShape& /*shape*/, const RawShape& /*shape*/) { static_assert(always_false::value, @@ -522,7 +534,7 @@ struct ShapeLike { } template - static bool touches( const RawShape& /*shape*/, + inline bool touches( const RawShape& /*shape*/, const RawShape& /*shape*/) { static_assert(always_false::value, @@ -531,7 +543,7 @@ struct ShapeLike { } template - static bool touches( const TPoint& /*point*/, + inline bool touches( const TPoint& /*point*/, const RawShape& /*shape*/) { static_assert(always_false::value, @@ -540,21 +552,22 @@ struct ShapeLike { } template - static _Box> boundingBox(const RawShape& /*sh*/) + inline _Box> boundingBox(const RawShape& /*sh*/, + const PolygonTag&) { static_assert(always_false::value, "ShapeLike::boundingBox(shape) unimplemented!"); } template - static _Box> boundingBox(const Shapes& /*sh*/) + inline _Box> boundingBox(const Shapes& /*sh*/) { static_assert(always_false::value, "ShapeLike::boundingBox(shapes) unimplemented!"); } template - static RawShape convexHull(const RawShape& /*sh*/) + inline RawShape convexHull(const RawShape& /*sh*/) { static_assert(always_false::value, "ShapeLike::convexHull(shape) unimplemented!"); @@ -562,7 +575,7 @@ struct ShapeLike { } template - static RawShape convexHull(const Shapes& /*sh*/) + inline RawShape convexHull(const Shapes& /*sh*/) { static_assert(always_false::value, "ShapeLike::convexHull(shapes) unimplemented!"); @@ -570,34 +583,34 @@ struct ShapeLike { } template - static void rotate(RawShape& /*sh*/, const Radians& /*rads*/) + inline void rotate(RawShape& /*sh*/, const Radians& /*rads*/) { static_assert(always_false::value, "ShapeLike::rotate() unimplemented!"); } template - static void translate(RawShape& /*sh*/, const RawPoint& /*offs*/) + inline void translate(RawShape& /*sh*/, const RawPoint& /*offs*/) { static_assert(always_false::value, "ShapeLike::translate() unimplemented!"); } template - static void offset(RawShape& /*sh*/, TCoord> /*distance*/) + inline void offset(RawShape& /*sh*/, TCoord> /*distance*/) { static_assert(always_false::value, "ShapeLike::offset() unimplemented!"); } template - static std::pair isValid(const RawShape& /*sh*/) + inline std::pair isValid(const RawShape& /*sh*/) { return {false, "ShapeLike::isValid() unimplemented!"}; } template - static inline bool isConvex(const TContour& sh) + inline bool isConvex(const TContour& sh) { using Vertex = TPoint; auto first = sh.begin(); @@ -633,43 +646,55 @@ struct ShapeLike { // No need to implement these // ************************************************************************* - template - static inline _Box> boundingBox( - const _Box>& box) + template + inline Box boundingBox(const Box& box, const BoxTag& ) { return box; } - template - static inline _Box> boundingBox( - const _Circle>& circ) + template + inline _Box boundingBox( + const Circle& circ, const CircleTag&) { - using Coord = TCoord>; - TPoint pmin = { + using Point = typename Circle::PointType; + using Coord = TCoord; + Point pmin = { static_cast(getX(circ.center()) - circ.radius()), static_cast(getY(circ.center()) - circ.radius()) }; - TPoint pmax = { + Point pmax = { static_cast(getX(circ.center()) + circ.radius()), static_cast(getY(circ.center()) + circ.radius()) }; return {pmin, pmax}; } - template - static inline double area(const _Box>& box) + template // Dispatch function + inline _Box boundingBox(const S& sh) { - return static_cast(box.width() * box.height()); + return boundingBox(sh, typename S::Tag()); } - template - static inline double area(const _Circle>& circ) + template + inline double area(const Box& box, const BoxTag& ) + { + return box.area(); + } + + template + inline double area(const Circle& circ, const CircleTag& ) { return circ.area(); } + template // Dispatching function + inline double area(const RawShape& sh) + { + return area(sh, typename RawShape::Tag()); + } + template - static inline double area(const Shapes& shapes) + inline double area(const Shapes& shapes) { return std::accumulate(shapes.begin(), shapes.end(), 0.0, [](double a, const RawShape& b) { @@ -678,14 +703,14 @@ struct ShapeLike { } template - static bool isInside(const TPoint& point, + inline bool isInside(const TPoint& point, const _Circle>& circ) { - return PointLike::distance(point, circ.center()) < circ.radius(); + return pointlike::distance(point, circ.center()) < circ.radius(); } template - static bool isInside(const TPoint& point, + inline bool isInside(const TPoint& point, const _Box>& box) { auto px = getX(point); @@ -699,7 +724,7 @@ struct ShapeLike { } template - static bool isInside(const RawShape& sh, + inline bool isInside(const RawShape& sh, const _Circle>& circ) { return std::all_of(cbegin(sh), cend(sh), @@ -709,7 +734,7 @@ struct ShapeLike { } template - static bool isInside(const _Box>& box, + inline bool isInside(const _Box>& box, const _Circle>& circ) { return isInside(box.minCorner(), circ) && @@ -717,7 +742,7 @@ struct ShapeLike { } template - static bool isInside(const _Box>& ibb, + inline bool isInside(const _Box>& ibb, const _Box>& box) { auto iminX = getX(ibb.minCorner()); @@ -734,31 +759,31 @@ struct ShapeLike { } template // Potential O(1) implementation may exist - static inline TPoint& vertex(RawShape& sh, unsigned long idx) + inline TPoint& vertex(RawShape& sh, unsigned long idx) { return *(begin(sh) + idx); } template // Potential O(1) implementation may exist - static inline const TPoint& vertex(const RawShape& sh, + inline const TPoint& vertex(const RawShape& sh, unsigned long idx) { return *(cbegin(sh) + idx); } template - static inline size_t contourVertexCount(const RawShape& sh) + inline size_t contourVertexCount(const RawShape& sh) { return cend(sh) - cbegin(sh); } template - static inline void foreachContourVertex(RawShape& sh, Fn fn) { + inline void foreachContourVertex(RawShape& sh, Fn fn) { for(auto it = begin(sh); it != end(sh); ++it) fn(*it); } template - static inline void foreachHoleVertex(RawShape& sh, Fn fn) { + inline void foreachHoleVertex(RawShape& sh, Fn fn) { for(int i = 0; i < holeCount(sh); ++i) { auto& h = getHole(sh, i); for(auto it = begin(h); it != end(h); ++it) fn(*it); @@ -766,12 +791,12 @@ struct ShapeLike { } template - static inline void foreachContourVertex(const RawShape& sh, Fn fn) { + inline void foreachContourVertex(const RawShape& sh, Fn fn) { for(auto it = cbegin(sh); it != cend(sh); ++it) fn(*it); } template - static inline void foreachHoleVertex(const RawShape& sh, Fn fn) { + inline void foreachHoleVertex(const RawShape& sh, Fn fn) { for(int i = 0; i < holeCount(sh); ++i) { auto& h = getHole(sh, i); for(auto it = cbegin(h); it != cend(h); ++it) fn(*it); @@ -779,18 +804,17 @@ struct ShapeLike { } template - static inline void foreachVertex(RawShape& sh, Fn fn) { + inline void foreachVertex(RawShape& sh, Fn fn) { foreachContourVertex(sh, fn); foreachHoleVertex(sh, fn); } template - static inline void foreachVertex(const RawShape& sh, Fn fn) { + inline void foreachVertex(const RawShape& sh, Fn fn) { foreachContourVertex(sh, fn); foreachHoleVertex(sh, fn); } - -}; +} } diff --git a/xs/src/libnest2d/libnest2d/geometry_traits_nfp.hpp b/xs/src/libnest2d/libnest2d/geometry_traits_nfp.hpp index 90cf21be5..6cac374ae 100644 --- a/xs/src/libnest2d/libnest2d/geometry_traits_nfp.hpp +++ b/xs/src/libnest2d/libnest2d/geometry_traits_nfp.hpp @@ -9,6 +9,27 @@ namespace libnest2d { +namespace __nfp { +// Do not specialize this... +template +inline bool _vsort(const TPoint& v1, const TPoint& v2) +{ + using Coord = TCoord>; + Coord &&x1 = getX(v1), &&x2 = getX(v2), &&y1 = getY(v1), &&y2 = getY(v2); + auto diff = y1 - y2; + if(std::abs(diff) <= std::numeric_limits::epsilon()) + return x1 < x2; + + return diff < 0; +} +} + +/// A collection of static methods for handling the no fit polygon creation. +namespace nfp { + +namespace sl = shapelike; +namespace pl = pointlike; + /// The complexity level of a polygon that an NFP implementation can handle. enum class NfpLevel: unsigned { CONVEX_ONLY, @@ -18,12 +39,17 @@ enum class NfpLevel: unsigned { BOTH_CONCAVE_WITH_HOLES }; -/// A collection of static methods for handling the no fit polygon creation. -struct Nfp { +template +using NfpResult = std::pair>; + +template struct MaxNfpLevel { + static const BP2D_CONSTEXPR NfpLevel value = NfpLevel::CONVEX_ONLY; +}; + // Shorthand for a pile of polygons template -using Shapes = typename ShapeLike::Shapes; +using Shapes = typename shapelike::Shapes; /** * Merge a bunch of polygons with the specified additional polygon. @@ -37,7 +63,7 @@ using Shapes = typename ShapeLike::Shapes; * polygons are disjuct than the resulting set will contain more polygons. */ template -static Shapes merge(const Shapes& /*shc*/) +inline Shapes merge(const Shapes& /*shc*/) { static_assert(always_false::value, "Nfp::merge(shapes, shape) unimplemented!"); @@ -55,7 +81,7 @@ static Shapes merge(const Shapes& /*shc*/) * polygons are disjuct than the resulting set will contain more polygons. */ template -static Shapes merge(const Shapes& shc, +inline Shapes merge(const Shapes& shc, const RawShape& sh) { auto m = merge(shc); @@ -63,31 +89,18 @@ static Shapes merge(const Shapes& shc, return merge(m); } -/** - * A method to get a vertex from a polygon that always maintains a relative - * position to the coordinate system: It is always the rightmost top vertex. - * - * This way it does not matter in what order the vertices are stored, the - * reference will be always the same for the same polygon. - */ -template -inline static TPoint referenceVertex(const RawShape& sh) -{ - return rightmostUpVertex(sh); -} - /** * Get the vertex of the polygon that is at the lowest values (bottom) in the Y * axis and if there are more than one vertices on the same Y coordinate than * the result will be the leftmost (with the highest X coordinate). */ template -static TPoint leftmostDownVertex(const RawShape& sh) +inline TPoint leftmostDownVertex(const RawShape& sh) { // find min x and min y vertex - auto it = std::min_element(ShapeLike::cbegin(sh), ShapeLike::cend(sh), - _vsort); + auto it = std::min_element(shapelike::cbegin(sh), shapelike::cend(sh), + __nfp::_vsort); return *it; } @@ -98,26 +111,27 @@ static TPoint leftmostDownVertex(const RawShape& sh) * the result will be the rightmost (with the lowest X coordinate). */ template -static TPoint rightmostUpVertex(const RawShape& sh) +TPoint rightmostUpVertex(const RawShape& sh) { // find max x and max y vertex - auto it = std::max_element(ShapeLike::cbegin(sh), ShapeLike::cend(sh), - _vsort); + auto it = std::max_element(shapelike::cbegin(sh), shapelike::cend(sh), + __nfp::_vsort); return *it; } +/** + * A method to get a vertex from a polygon that always maintains a relative + * position to the coordinate system: It is always the rightmost top vertex. + * + * This way it does not matter in what order the vertices are stored, the + * reference will be always the same for the same polygon. + */ template -using NfpResult = std::pair>; - -/// Helper function to get the NFP -template -static NfpResult noFitPolygon(const RawShape& sh, - const RawShape& other) +inline TPoint referenceVertex(const RawShape& sh) { - NfpImpl nfp; - return nfp(sh, other); + return rightmostUpVertex(sh); } /** @@ -139,11 +153,11 @@ static NfpResult noFitPolygon(const RawShape& sh, * */ template -static NfpResult nfpConvexOnly(const RawShape& sh, +inline NfpResult nfpConvexOnly(const RawShape& sh, const RawShape& other) { using Vertex = TPoint; using Edge = _Segment; - using sl = ShapeLike; + namespace sl = shapelike; RawShape rsh; // Final nfp placeholder Vertex top_nfp; @@ -187,7 +201,7 @@ static NfpResult nfpConvexOnly(const RawShape& sh, sl::addVertex(rsh, edgelist.front().second()); // Sorting function for the nfp reference vertex search - auto& cmp = _vsort; + auto& cmp = __nfp::_vsort; // the reference (rightmost top) vertex so far top_nfp = *std::max_element(sl::cbegin(rsh), sl::cend(rsh), cmp ); @@ -214,7 +228,7 @@ static NfpResult nfpConvexOnly(const RawShape& sh, } template -static NfpResult nfpSimpleSimple(const RawShape& cstationary, +NfpResult nfpSimpleSimple(const RawShape& cstationary, const RawShape& cother) { @@ -233,7 +247,7 @@ static NfpResult nfpSimpleSimple(const RawShape& cstationary, using Vertex = TPoint; using Coord = TCoord; using Edge = _Segment; - using sl = ShapeLike; + namespace sl = shapelike; using std::signbit; using std::sort; using std::vector; @@ -528,27 +542,16 @@ struct NfpImpl { } }; -template struct MaxNfpLevel { - static const BP2D_CONSTEXPR NfpLevel value = NfpLevel::CONVEX_ONLY; -}; - -private: - -// Do not specialize this... -template -static inline bool _vsort(const TPoint& v1, - const TPoint& v2) +/// Helper function to get the NFP +template +inline NfpResult noFitPolygon(const RawShape& sh, + const RawShape& other) { - using Coord = TCoord>; - Coord &&x1 = getX(v1), &&x2 = getX(v2), &&y1 = getY(v1), &&y2 = getY(v2); - auto diff = y1 - y2; - if(std::abs(diff) <= std::numeric_limits::epsilon()) - return x1 < x2; - - return diff < 0; + NfpImpl nfps; + return nfps(sh, other); } -}; +} } diff --git a/xs/src/libnest2d/libnest2d/libnest2d.hpp b/xs/src/libnest2d/libnest2d/libnest2d.hpp index eadd1e110..d2850d4ed 100644 --- a/xs/src/libnest2d/libnest2d/libnest2d.hpp +++ b/xs/src/libnest2d/libnest2d/libnest2d.hpp @@ -9,10 +9,12 @@ #include #include "geometry_traits.hpp" -#include "optimizer.hpp" namespace libnest2d { +namespace sl = shapelike; +namespace pl = pointlike; + /** * \brief An item to be placed on a bin. * @@ -28,7 +30,6 @@ class _Item { using Coord = TCoord>; using Vertex = TPoint; using Box = _Box; - using sl = ShapeLike; // The original shape that gets encapsulated. RawShape sh_; @@ -438,7 +439,7 @@ public: inline _Rectangle(Unit width, Unit height, // disable this ctor if o != CLOCKWISE enable_if_t< o == TO::CLOCKWISE, int> = 0 ): - _Item( ShapeLike::create( { + _Item( sl::create( { {0, 0}, {0, height}, {width, height}, @@ -452,7 +453,7 @@ public: inline _Rectangle(Unit width, Unit height, // disable this ctor if o != COUNTER_CLOCKWISE enable_if_t< o == TO::COUNTER_CLOCKWISE, int> = 0 ): - _Item( ShapeLike::create( { + _Item( sl::create( { {0, 0}, {width, 0}, {width, height}, @@ -473,12 +474,32 @@ public: template inline bool _Item::isInside(const _Box>& box) const { - return ShapeLike::isInside(boundingBox(), box); + return sl::isInside(boundingBox(), box); } template inline bool _Item::isInside(const _Circle>& circ) const { - return ShapeLike::isInside(transformedShape(), circ); + return sl::isInside(transformedShape(), circ); +} + + +template using _ItemRef = std::reference_wrapper; +template using _ItemGroup = std::vector<_ItemRef>; + +template +struct ConstItemRange { + Iterator from; + Iterator to; + bool valid = false; + + ConstItemRange() = default; + ConstItemRange(Iterator f, Iterator t): from(f), to(t), valid(true) {} +}; + +template +inline ConstItemRange +rem(typename Container::const_iterator it, const Container& cont) { + return {std::next(it), cont.end()}; } /** @@ -515,8 +536,9 @@ public: */ using PackResult = typename PlacementStrategy::PackResult; - using ItemRef = std::reference_wrapper; - using ItemGroup = std::vector; + using ItemRef = _ItemRef; + using ItemGroup = _ItemGroup; + using DefaultIterator = typename ItemGroup::const_iterator; /** * @brief Constructor taking the bin and an optional configuration. @@ -544,20 +566,24 @@ public: * Try to pack an item with a result object that contains the packing * information for later accepting it. * - * \param item_store A container of items + * \param item_store A container of items that are intended to be packed + * later. Can be used by the placer to switch tactics. When it's knows that + * many items will come a greedy startegy may not be the best. + * \param from The iterator to the item from which the packing should start, + * including the pointed item + * \param count How many items should be packed. If the value is 1, than + * just the item pointed to by "from" argument should be packed. */ - template - inline PackResult trypack(Container& item_store, - typename Container::iterator from, - unsigned count = 1) { - using V = typename Container::value_type; - static_assert(std::is_convertible::value, - "Invalid Item container!"); - return impl_.trypack(item_store, from, count); + template + inline PackResult trypack( + Item& item, + const ConstItemRange& remaining = ConstItemRange()) + { + return impl_.trypack(item, remaining); } /** - * @brief A method to accept a previously tried item. + * @brief A method to accept a previously tried item (or items). * * If the pack result is a failure the method should ignore it. * @param r The result of a previous trypack call. @@ -565,10 +591,10 @@ public: inline void accept(PackResult& r) { impl_.accept(r); } /** - * @brief pack Try to pack an item and immediately accept it on success. + * @brief pack Try to pack and immediately accept it on success. * * A default implementation would be to call - * { auto&& r = trypack(item); accept(r); return r; } but we should let the + * { auto&& r = trypack(...); accept(r); return r; } but we should let the * implementor of the placement strategy to harvest any optimizations from * the absence of an intermadiate step. The above version can still be used * in the implementation. @@ -577,15 +603,12 @@ public: * @return Returns true if the item was packed or false if it could not be * packed. */ - template - inline bool pack(Container& item_store, - typename Container::iterator from, - unsigned count = 1) + template> + inline bool pack( + Item& item, + const Range& remaining = Range()) { - using V = typename Container::value_type; - static_assert(std::is_convertible::value, - "Invalid Item container!"); - return impl_.pack(item_store, from, count); + return impl_.pack(item, remaining); } /// Unpack the last element (remove it from the list of packed items). @@ -736,10 +759,9 @@ using _IndexedPackGroup = std::vector< * inside the provided bin. */ template -class Arranger { +class Nester { using TSel = SelectionStrategyLike; TSel selector_; - bool use_min_bb_rotation_ = false; public: using Item = typename PlacementStrategy::Item; using ItemRef = std::reference_wrapper; @@ -777,7 +799,7 @@ public: template - Arranger( TBinType&& bin, + Nester( TBinType&& bin, Unit min_obj_distance = 0, PConf&& pconfig = PConf(), SConf&& sconfig = SConf()): @@ -810,9 +832,9 @@ public: * the selection algorithm. */ template - inline PackGroup arrange(TIterator from, TIterator to) + inline PackGroup execute(TIterator from, TIterator to) { - return _arrange(from, to); + return _execute(from, to); } /** @@ -823,20 +845,20 @@ public: * input sequence size. */ template - inline IndexedPackGroup arrangeIndexed(TIterator from, TIterator to) + inline IndexedPackGroup executeIndexed(TIterator from, TIterator to) { - return _arrangeIndexed(from, to); + return _executeIndexed(from, to); } /// Shorthand to normal arrange method. template inline PackGroup operator() (TIterator from, TIterator to) { - return _arrange(from, to); + return _execute(from, to); } /// Set a progress indicatior function object for the selector. - inline Arranger& progressIndicator(ProgressFunction func) + inline Nester& progressIndicator(ProgressFunction func) { selector_.progressIndicator(func); return *this; } @@ -850,10 +872,6 @@ public: return ret; } - inline Arranger& useMinimumBoundigBoxRotation(bool s = true) { - use_min_bb_rotation_ = s; return *this; - } - private: template::value, IT> > - inline PackGroup _arrange(TIterator from, TIterator to, bool = false) + inline PackGroup _execute(TIterator from, TIterator to, bool = false) { - __arrange(from, to); + __execute(from, to); return lastResult(); } @@ -875,11 +893,11 @@ private: class IT = remove_cvref_t, class T = enable_if_t::value, IT> > - inline PackGroup _arrange(TIterator from, TIterator to, int = false) + inline PackGroup _execute(TIterator from, TIterator to, int = false) { item_cache_ = {from, to}; - __arrange(item_cache_.begin(), item_cache_.end()); + __execute(item_cache_.begin(), item_cache_.end()); return lastResult(); } @@ -892,11 +910,11 @@ private: // have to exist for the lifetime of this call. class T = enable_if_t< std::is_convertible::value, IT> > - inline IndexedPackGroup _arrangeIndexed(TIterator from, + inline IndexedPackGroup _executeIndexed(TIterator from, TIterator to, bool = false) { - __arrange(from, to); + __execute(from, to); return createIndexedPackGroup(from, to, selector_); } @@ -904,12 +922,12 @@ private: class IT = remove_cvref_t, class T = enable_if_t::value, IT> > - inline IndexedPackGroup _arrangeIndexed(TIterator from, + inline IndexedPackGroup _executeIndexed(TIterator from, TIterator to, int = false) { item_cache_ = {from, to}; - __arrange(item_cache_.begin(), item_cache_.end()); + __execute(item_cache_.begin(), item_cache_.end()); return createIndexedPackGroup(from, to, selector_); } @@ -941,37 +959,12 @@ private: return pg; } - Radians findBestRotation(Item& item) { - opt::StopCriteria stopcr; - stopcr.absolute_score_difference = 0.01; - stopcr.max_iterations = 10000; - opt::TOptimizer solver(stopcr); - - auto orig_rot = item.rotation(); - - auto result = solver.optimize_min([&item, &orig_rot](Radians rot){ - item.rotation(orig_rot + rot); - auto bb = item.boundingBox(); - return std::sqrt(bb.height()*bb.width()); - }, opt::initvals(Radians(0)), opt::bound(-Pi/2, Pi/2)); - - item.rotation(orig_rot); - - return std::get<0>(result.optimum); - } - - template inline void __arrange(TIter from, TIter to) + template inline void __execute(TIter from, TIter to) { if(min_obj_distance_ > 0) std::for_each(from, to, [this](Item& item) { item.addOffset(static_cast(std::ceil(min_obj_distance_/2.0))); }); - if(use_min_bb_rotation_) - std::for_each(from, to, [this](Item& item){ - Radians rot = findBestRotation(item); - item.rotate(rot); - }); - selector_.template packItems( from, to, bin_, pconfig_); diff --git a/xs/src/libnest2d/libnest2d/placers/bottomleftplacer.hpp b/xs/src/libnest2d/libnest2d/placers/bottomleftplacer.hpp index 71573e34d..af1678372 100644 --- a/xs/src/libnest2d/libnest2d/placers/bottomleftplacer.hpp +++ b/xs/src/libnest2d/libnest2d/placers/bottomleftplacer.hpp @@ -27,11 +27,10 @@ public: explicit _BottomLeftPlacer(const BinType& bin): Base(bin) {} - template - PackResult trypack(Store& /*s*/, typename Store::iterator from, - unsigned /*count*/ = 1) + template> + PackResult trypack(Item& item, + const Range& = Range()) { - Item& item = *from; auto r = _trypack(item); if(!r && Base::config_.allow_rotations) { @@ -117,10 +116,10 @@ protected: const RawShape& scanpoly) { auto tsh = other.transformedShape(); - return ( ShapeLike::intersects(tsh, scanpoly) || - ShapeLike::isInside(tsh, scanpoly) ) && - ( !ShapeLike::intersects(tsh, item.rawShape()) && - !ShapeLike::isInside(tsh, item.rawShape()) ); + return ( sl::intersects(tsh, scanpoly) || + sl::isInside(tsh, scanpoly) ) && + ( !sl::intersects(tsh, item.rawShape()) && + !sl::isInside(tsh, item.rawShape()) ); } template @@ -131,25 +130,25 @@ protected: { auto tsh = other.transformedShape(); - bool inters_scanpoly = ShapeLike::intersects(tsh, scanpoly) && - !ShapeLike::touches(tsh, scanpoly); - bool inters_item = ShapeLike::intersects(tsh, item.rawShape()) && - !ShapeLike::touches(tsh, item.rawShape()); + bool inters_scanpoly = sl::intersects(tsh, scanpoly) && + !sl::touches(tsh, scanpoly); + bool inters_item = sl::intersects(tsh, item.rawShape()) && + !sl::touches(tsh, item.rawShape()); return ( inters_scanpoly || - ShapeLike::isInside(tsh, scanpoly)) && + sl::isInside(tsh, scanpoly)) && ( !inters_item && - !ShapeLike::isInside(tsh, item.rawShape()) + !sl::isInside(tsh, item.rawShape()) ); } - Container itemsInTheWayOf(const Item& item, const Dir dir) { + ItemGroup itemsInTheWayOf(const Item& item, const Dir dir) { // Get the left or down polygon, that has the same area as the shadow // of input item reflected to the left or downwards auto&& scanpoly = dir == Dir::LEFT? leftPoly(item) : downPoly(item); - Container ret; // packed items 'in the way' of item + ItemGroup ret; // packed items 'in the way' of item ret.reserve(items_.size()); // Predicate to find items that are 'in the way' for left (down) move @@ -178,18 +177,18 @@ protected: if(dir == Dir::LEFT) { getCoord = [](const Vertex& v) { return getX(v); }; - availableDistance = PointLike::horizontalDistance; + availableDistance = pointlike::horizontalDistance; availableDistanceSV = [](const Segment& s, const Vertex& v) { - auto ret = PointLike::horizontalDistance(v, s); + auto ret = pointlike::horizontalDistance(v, s); if(ret.second) ret.first = -ret.first; return ret; }; } else { getCoord = [](const Vertex& v) { return getY(v); }; - availableDistance = PointLike::verticalDistance; + availableDistance = pointlike::verticalDistance; availableDistanceSV = [](const Segment& s, const Vertex& v) { - auto ret = PointLike::verticalDistance(v, s); + auto ret = pointlike::verticalDistance(v, s); if(ret.second) ret.first = -ret.first; return ret; }; @@ -219,9 +218,9 @@ protected: assert(pleft.vertexCount() > 0); auto trpleft = pleft.transformedShape(); - auto first = ShapeLike::begin(trpleft); + auto first = sl::begin(trpleft); auto next = first + 1; - auto endit = ShapeLike::end(trpleft); + auto endit = sl::end(trpleft); while(next != endit) { Segment seg(*(first++), *(next++)); @@ -345,16 +344,16 @@ protected: // reserve for all vertices plus 2 for the left horizontal wall, 2 for // the additional vertices for maintaning min object distance - ShapeLike::reserve(rsh, finish-start+4); + sl::reserve(rsh, finish-start+4); /*auto addOthers = [&rsh, finish, start, &item](){ for(size_t i = start+1; i < finish; i++) - ShapeLike::addVertex(rsh, item.vertex(i)); + sl::addVertex(rsh, item.vertex(i)); };*/ auto reverseAddOthers = [&rsh, finish, start, &item](){ for(auto i = finish-1; i > start; i--) - ShapeLike::addVertex(rsh, item.vertex( + sl::addVertex(rsh, item.vertex( static_cast(i))); }; @@ -366,25 +365,25 @@ protected: // Clockwise polygon construction - ShapeLike::addVertex(rsh, topleft_vertex); + sl::addVertex(rsh, topleft_vertex); if(dir == Dir::LEFT) reverseAddOthers(); else { - ShapeLike::addVertex(rsh, getX(topleft_vertex), 0); - ShapeLike::addVertex(rsh, getX(bottomleft_vertex), 0); + sl::addVertex(rsh, getX(topleft_vertex), 0); + sl::addVertex(rsh, getX(bottomleft_vertex), 0); } - ShapeLike::addVertex(rsh, bottomleft_vertex); + sl::addVertex(rsh, bottomleft_vertex); if(dir == Dir::LEFT) { - ShapeLike::addVertex(rsh, 0, getY(bottomleft_vertex)); - ShapeLike::addVertex(rsh, 0, getY(topleft_vertex)); + sl::addVertex(rsh, 0, getY(bottomleft_vertex)); + sl::addVertex(rsh, 0, getY(topleft_vertex)); } else reverseAddOthers(); // Close the polygon - ShapeLike::addVertex(rsh, topleft_vertex); + sl::addVertex(rsh, topleft_vertex); return rsh; } diff --git a/xs/src/libnest2d/libnest2d/placers/nfpplacer.hpp b/xs/src/libnest2d/libnest2d/placers/nfpplacer.hpp index d74fe2b1d..638d606e0 100644 --- a/xs/src/libnest2d/libnest2d/placers/nfpplacer.hpp +++ b/xs/src/libnest2d/libnest2d/placers/nfpplacer.hpp @@ -19,7 +19,7 @@ namespace libnest2d { namespace strategies { template struct NfpPConfig { - using ItemGroup = std::vector>>; + using ItemGroup = _ItemGroup<_Item>; enum class Alignment { CENTER, @@ -58,16 +58,6 @@ struct NfpPConfig { * * \param item The second parameter is the candidate item. * - * \param occupied_area The third parameter is the sum of areas of the - * items in the first parameter (no candidate item there) so you don't have - * to iterate through them if you only need their accumulated area. - * - * \param norm A norming factor for physical dimensions. E.g. if your score - * is the distance between the item and the bin center, you should divide - * that distance with the norming factor. If the score is an area than - * divide it with the square of the norming factor. Imagine it as a unit of - * distance. - * * \param remaining A container with the remaining items waiting to be * placed. You can use some features about the remaining items to alter to * score of the current placement. If you know that you have to leave place @@ -81,8 +71,8 @@ struct NfpPConfig { * decisions (for you or a more intelligent AI). * */ - std::function&, const _Item&, - double, double, const ItemGroup&)> + std::function&, const _Item&, + const ItemGroup&)> object_function; /** @@ -134,11 +124,11 @@ template class EdgeCache { void createCache(const RawShape& sh) { { // For the contour - auto first = ShapeLike::cbegin(sh); + auto first = shapelike::cbegin(sh); auto next = std::next(first); - auto endit = ShapeLike::cend(sh); + auto endit = shapelike::cend(sh); - contour_.distances.reserve(ShapeLike::contourVertexCount(sh)); + contour_.distances.reserve(shapelike::contourVertexCount(sh)); while(next != endit) { contour_.emap.emplace_back(*(first++), *(next++)); @@ -147,7 +137,7 @@ template class EdgeCache { } } - for(auto& h : ShapeLike::holes(sh)) { // For the holes + for(auto& h : shapelike::holes(sh)) { // For the holes auto first = h.begin(); auto next = std::next(first); auto endit = h.end(); @@ -295,11 +285,11 @@ public: }; -template -struct Lvl { static const NfpLevel value = lvl; }; +template +struct Lvl { static const nfp::NfpLevel value = lvl; }; template -inline void correctNfpPosition(Nfp::NfpResult& nfp, +inline void correctNfpPosition(nfp::NfpResult& nfp, const _Item& stationary, const _Item& orbiter) { @@ -319,46 +309,47 @@ inline void correctNfpPosition(Nfp::NfpResult& nfp, auto dtouch = touch_sh - touch_other; auto top_other = orbiter.rightmostTopVertex() + dtouch; auto dnfp = top_other - nfp.second; // nfp.second is the nfp reference point - ShapeLike::translate(nfp.first, dnfp); + shapelike::translate(nfp.first, dnfp); } template -inline void correctNfpPosition(Nfp::NfpResult& nfp, +inline void correctNfpPosition(nfp::NfpResult& nfp, const RawShape& stationary, const _Item& orbiter) { - auto touch_sh = Nfp::rightmostUpVertex(stationary); + auto touch_sh = nfp::rightmostUpVertex(stationary); auto touch_other = orbiter.leftmostBottomVertex(); auto dtouch = touch_sh - touch_other; auto top_other = orbiter.rightmostTopVertex() + dtouch; auto dnfp = top_other - nfp.second; - ShapeLike::translate(nfp.first, dnfp); + shapelike::translate(nfp.first, dnfp); } template -Nfp::Shapes nfp( const Container& polygons, +nfp::Shapes calcnfp( const Container& polygons, const _Item& trsh, - Lvl) + Lvl) { using Item = _Item; + using namespace nfp; - Nfp::Shapes nfps; + nfp::Shapes nfps; // int pi = 0; for(Item& sh : polygons) { - auto subnfp_r = Nfp::noFitPolygon( + auto subnfp_r = noFitPolygon( sh.transformedShape(), trsh.transformedShape()); #ifndef NDEBUG - auto vv = ShapeLike::isValid(sh.transformedShape()); + auto vv = sl::isValid(sh.transformedShape()); assert(vv.first); - auto vnfp = ShapeLike::isValid(subnfp_r.first); + auto vnfp = sl::isValid(subnfp_r.first); assert(vnfp.first); #endif correctNfpPosition(subnfp_r, sh, trsh); - nfps = Nfp::merge(nfps, subnfp_r.first); + nfps = nfp::merge(nfps, subnfp_r.first); // double SCALE = 1000000; // using SVGWriter = svg::SVGWriter; @@ -379,31 +370,32 @@ Nfp::Shapes nfp( const Container& polygons, } template -Nfp::Shapes nfp( const Container& polygons, +nfp::Shapes calcnfp( const Container& polygons, const _Item& trsh, Level) { + using namespace nfp; using Item = _Item; - Nfp::Shapes nfps; + Shapes nfps; auto& orb = trsh.transformedShape(); bool orbconvex = trsh.isContourConvex(); for(Item& sh : polygons) { - Nfp::NfpResult subnfp; + nfp::NfpResult subnfp; auto& stat = sh.transformedShape(); if(sh.isContourConvex() && orbconvex) - subnfp = Nfp::noFitPolygon(stat, orb); + subnfp = nfp::noFitPolygon(stat, orb); else if(orbconvex) - subnfp = Nfp::noFitPolygon(stat, orb); + subnfp = nfp::noFitPolygon(stat, orb); else - subnfp = Nfp::noFitPolygon(stat, orb); + subnfp = nfp::noFitPolygon(stat, orb); correctNfpPosition(subnfp, sh, trsh); - nfps = Nfp::merge(nfps, subnfp.first); + nfps = nfp::merge(nfps, subnfp.first); } return nfps; @@ -448,7 +440,6 @@ Nfp::Shapes nfp( const Container& polygons, template _Circle> minimizeCircle(const RawShape& sh) { - using sl = ShapeLike; using pl = PointLike; using Point = TPoint; using Coord = TCoord; @@ -518,16 +509,14 @@ class _NofitPolyPlacer: public PlacerBoilerplate<_NofitPolyPlacer; - using sl = ShapeLike; - + using MaxNfpLevel = nfp::MaxNfpLevel; public: - using Pile = Nfp::Shapes; + using Pile = nfp::Shapes; inline explicit _NofitPolyPlacer(const BinType& bin): Base(bin), - norm_(std::sqrt(sl::area(bin))) {} + norm_(std::sqrt(sl::area(bin))) {} _NofitPolyPlacer(const _NofitPolyPlacer&) = default; _NofitPolyPlacer& operator=(const _NofitPolyPlacer&) = default; @@ -577,20 +566,17 @@ public: return boundingCircle(chull).radius() < bin.radius(); } - template - 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) { + template> + PackResult trypack( + Item& item, + const Range& remaining = Range()) { PackResult ret; bool can_pack = false; + auto remlist = ItemGroup(remaining.from, remaining.to); + if(items_.empty()) { setInitialPosition(item); can_pack = item.isInside(bin_); @@ -602,7 +588,7 @@ public: auto initial_rot = item.rotation(); Vertex final_tr = {0, 0}; Radians final_rot = initial_rot; - Nfp::Shapes nfps; + nfp::Shapes nfps; for(auto rot : config_.rotations) { @@ -615,8 +601,8 @@ public: auto trsh = item.transformedShape(); - nfps = nfp(items_, item, Lvl()); - auto iv = Nfp::referenceVertex(trsh); + nfps = calcnfp(items_, item, Lvl()); + auto iv = nfp::referenceVertex(trsh); auto startpos = item.translation(); @@ -644,7 +630,7 @@ public: ecache[opt.nfpidx].coords(opt.hidx, opt.relpos); }; - Nfp::Shapes pile; + nfp::Shapes pile; pile.reserve(items_.size()+1); double pile_area = 0; for(Item& mitem : items_) { @@ -652,17 +638,15 @@ public: pile_area += mitem.area(); } - auto merged_pile = Nfp::merge(pile); + auto merged_pile = nfp::merge(pile); // This is the kernel part of the object function that is // customizable by the library client auto _objfunc = config_.object_function? config_.object_function : - [this, &merged_pile]( - Nfp::Shapes& /*pile*/, + [this, &merged_pile, &pile_area]( + nfp::Shapes& /*pile*/, const Item& item, - double occupied_area, - double norm, const ItemGroup& /*remaining*/) { merged_pile.emplace_back(item.transformedShape()); @@ -670,7 +654,7 @@ public: merged_pile.pop_back(); // The pack ratio -- how much is the convex hull occupied - double pack_rate = occupied_area/sl::area(ch); + double pack_rate = (pile_area + item.area())/sl::area(ch); // ratio of waste double waste = 1.0 - pack_rate; @@ -680,7 +664,7 @@ public: // (larger) values. auto score = std::sqrt(waste); - if(!wouldFit(ch, bin_)) score += norm; + if(!wouldFit(ch, bin_)) score += norm_; return score; }; @@ -692,10 +676,7 @@ public: d += startpos; item.translation(d); - double occupied_area = pile_area + item.area(); - - double score = _objfunc(pile, item, occupied_area, - norm_, remaining); + double score = _objfunc(pile, item, remlist); return score; }; @@ -830,7 +811,7 @@ private: inline void finalAlign(_Circle> cbin) { if(items_.empty()) return; - Nfp::Shapes m; + nfp::Shapes m; m.reserve(items_.size()); for(Item& item : items_) m.emplace_back(item.transformedShape()); @@ -842,7 +823,7 @@ private: inline void finalAlign(Box bbin) { if(items_.empty()) return; - Nfp::Shapes m; + nfp::Shapes m; m.reserve(items_.size()); for(Item& item : items_) m.emplace_back(item.transformedShape()); auto&& bb = sl::boundingBox(m); @@ -884,7 +865,7 @@ private: void setInitialPosition(Item& item) { Box&& bb = item.boundingBox(); Vertex ci, cb; - auto bbin = sl::boundingBox(bin_); + auto bbin = sl::boundingBox(bin_); switch(config_.starting_point) { case Config::Alignment::CENTER: { @@ -920,7 +901,7 @@ private: void placeOutsideOfBin(Item& item) { auto&& bb = item.boundingBox(); - Box binbb = sl::boundingBox(bin_); + Box binbb = sl::boundingBox(bin_); Vertex v = { getX(bb.maxCorner()), getY(bb.minCorner()) }; diff --git a/xs/src/libnest2d/libnest2d/placers/placer_boilerplate.hpp b/xs/src/libnest2d/libnest2d/placers/placer_boilerplate.hpp index f31a9343c..1a0730d88 100644 --- a/xs/src/libnest2d/libnest2d/placers/placer_boilerplate.hpp +++ b/xs/src/libnest2d/libnest2d/placers/placer_boilerplate.hpp @@ -7,10 +7,7 @@ namespace libnest2d { namespace strategies { struct EmptyConfig {}; -template>> - > +template class PlacerBoilerplate { mutable bool farea_valid_ = false; mutable double farea_ = 0.0; @@ -22,7 +19,8 @@ public: using Coord = TCoord; using Unit = Coord; using Config = Cfg; - using Container = Store; + using ItemGroup = _ItemGroup; + using DefaultIter = typename ItemGroup::const_iterator; class PackResult { Item *item_ptr_; @@ -39,8 +37,6 @@ public: operator bool() { return item_ptr_ != nullptr; } }; - using ItemGroup = const Container&; - inline PlacerBoilerplate(const BinType& bin, unsigned cap = 50): bin_(bin) { items_.reserve(cap); @@ -56,11 +52,10 @@ public: config_ = config; } - template - bool pack(Container& items, - typename Container::iterator from, - unsigned count = 1) { - auto&& r = static_cast(this)->trypack(items, from, count); + template> + bool pack(Item& item, + const Range& rem = Range()) { + auto&& r = static_cast(this)->trypack(item, rem); if(r) { items_.push_back(*(r.item_ptr_)); farea_valid_ = false; @@ -82,7 +77,7 @@ public: farea_valid_ = false; } - inline ItemGroup getItems() const { return items_; } + inline const ItemGroup& getItems() const { return items_; } inline void clearItems() { items_.clear(); @@ -113,7 +108,7 @@ public: protected: BinType bin_; - Container items_; + ItemGroup items_; Cfg config_; }; @@ -124,6 +119,7 @@ using Base::items_; \ using Base::config_; \ public: \ using typename Base::Item; \ +using typename Base::ItemGroup; \ using typename Base::BinType; \ using typename Base::Config; \ using typename Base::Vertex; \ @@ -131,7 +127,6 @@ using typename Base::Segment; \ using typename Base::PackResult; \ using typename Base::Coord; \ using typename Base::Unit; \ -using typename Base::Container; \ private: } diff --git a/xs/src/libnest2d/libnest2d/rotfinder.hpp b/xs/src/libnest2d/libnest2d/rotfinder.hpp new file mode 100644 index 000000000..525fd8759 --- /dev/null +++ b/xs/src/libnest2d/libnest2d/rotfinder.hpp @@ -0,0 +1,41 @@ +#ifndef ROTFINDER_HPP +#define ROTFINDER_HPP + +#include +#include +#include + +namespace libnest2d { + +template +Radians findBestRotation(_Item& item) { + opt::StopCriteria stopcr; + stopcr.absolute_score_difference = 0.01; + stopcr.max_iterations = 10000; + opt::TOptimizer solver(stopcr); + + auto orig_rot = item.rotation(); + + auto result = solver.optimize_min([&item, &orig_rot](Radians rot){ + item.rotation(orig_rot + rot); + auto bb = item.boundingBox(); + return std::sqrt(bb.height()*bb.width()); + }, opt::initvals(Radians(0)), opt::bound(-Pi/2, Pi/2)); + + item.rotation(orig_rot); + + return std::get<0>(result.optimum); +} + +template +void findMinimumBoundingBoxRotations(Iterator from, Iterator to) { + using V = typename std::iterator_traits::value_type; + std::for_each(from, to, [](V& item){ + Radians rot = findBestRotation(item); + item.rotate(rot); + }); +} + +} + +#endif // ROTFINDER_HPP diff --git a/xs/src/libnest2d/libnest2d/selections/djd_heuristic.hpp b/xs/src/libnest2d/libnest2d/selections/djd_heuristic.hpp index 34d6d05c5..8c02dc373 100644 --- a/xs/src/libnest2d/libnest2d/selections/djd_heuristic.hpp +++ b/xs/src/libnest2d/libnest2d/selections/djd_heuristic.hpp @@ -118,7 +118,7 @@ public: using Placer = PlacementStrategyLike; using ItemList = std::list; - const double bin_area = ShapeLike::area(bin); + const double bin_area = sl::area(bin); const double w = bin_area * config_.waste_increment; const double INITIAL_FILL_PROPORTION = config_.initial_fill_proportion; @@ -227,10 +227,14 @@ public: bool ret = false; auto it = not_packed.begin(); + auto pack = [&placer, ¬_packed](ItemListIt it) { + return placer.pack(*it, rem(it, not_packed)); + }; + while(it != not_packed.end() && !ret && free_area - (item_area = it->get().area()) <= waste) { - if(item_area <= free_area && placer.pack(not_packed, it) ) { + if(item_area <= free_area && pack(it) ) { free_area -= item_area; filled_area = bin_area - free_area; ret = true; @@ -270,6 +274,11 @@ public: auto it2 = it; std::vector wrong_pairs; + using std::placeholders::_1; + + auto trypack = [&placer, ¬_packed](ItemListIt it) { + return placer.trypack(*it, rem(it, not_packed)); + }; while(it != endit && !ret && free_area - (item_area = it->get().area()) - @@ -278,7 +287,7 @@ public: if(item_area + smallestPiece(it, not_packed)->get().area() > free_area ) { it++; continue; } - auto pr = placer.trypack(not_packed, it); + auto pr = trypack(it); // First would fit it2 = not_packed.begin(); @@ -294,14 +303,14 @@ public: } placer.accept(pr); - auto pr2 = placer.trypack(not_packed, it2); + auto pr2 = trypack(it2); if(!pr2) { placer.unpackLast(); // remove first if(try_reverse) { - pr2 = placer.trypack(not_packed, it2); + pr2 = trypack(it2); if(pr2) { placer.accept(pr2); - auto pr12 = placer.trypack(not_packed, it); + auto pr12 = trypack(it); if(pr12) { placer.accept(pr12); ret = true; @@ -365,6 +374,14 @@ public: return it->get().area(); }; + auto trypack = [&placer, ¬_packed](ItemListIt it) { + return placer.trypack(*it, rem(it, not_packed)); + }; + + auto pack = [&placer, ¬_packed](ItemListIt it) { + return placer.pack(*it, rem(it, not_packed)); + }; + while (it != endit && !ret) { // drill down 1st level // We need to determine in each iteration the largest, second @@ -394,7 +411,7 @@ public: it++; continue; } - auto pr = placer.trypack(not_packed, it); + auto pr = trypack(it); // Check for free area and try to pack the 1st item... if(!pr) { it++; continue; } @@ -420,15 +437,15 @@ public: bool can_pack2 = false; placer.accept(pr); - auto pr2 = placer.trypack(not_packed, it2); + auto pr2 = trypack(it2); auto pr12 = pr; if(!pr2) { placer.unpackLast(); // remove first if(try_reverse) { - pr2 = placer.trypack(not_packed, it2); + pr2 = trypack(it2); if(pr2) { placer.accept(pr2); - pr12 = placer.trypack(not_packed, it); + pr12 = trypack(it); if(pr12) can_pack2 = true; placer.unpackLast(); } @@ -463,7 +480,7 @@ public: if(a3_sum > free_area) { it3++; continue; } placer.accept(pr12); placer.accept(pr2); - bool can_pack3 = placer.pack(not_packed, it3); + bool can_pack3 = pack(it3); if(!can_pack3) { placer.unpackLast(); @@ -476,13 +493,14 @@ public: std::array candidates = {it, it2, it3}; - auto tryPack = [&placer, &candidates, ¬_packed]( + auto tryPack = [&placer, &candidates, ¬_packed, + &pack]( const decltype(indices)& idx) { std::array packed = {false}; for(auto id : idx) packed.at(id) = - placer.pack(not_packed, candidates[id]); + pack(candidates[id]); bool check = std::all_of(packed.begin(), @@ -536,7 +554,7 @@ public: { auto it = store_.begin(); while (it != store_.end()) { Placer p(bin); p.configure(pconfig); - if(!p.pack(store_, it)) { + if(!p.pack(*it, rem(it, store_))) { it = store_.erase(it); } else it++; } @@ -601,7 +619,7 @@ public: while(it != not_packed.end() && filled_area < INITIAL_FILL_AREA) { - if(placer.pack(not_packed, it)) { + if(placer.pack(*it, rem(it, not_packed))) { filled_area += it->get().area(); free_area = bin_area - filled_area; it = not_packed.erase(it); diff --git a/xs/src/libnest2d/libnest2d/selections/filler.hpp b/xs/src/libnest2d/libnest2d/selections/filler.hpp index ca1281fe6..b20455b0e 100644 --- a/xs/src/libnest2d/libnest2d/selections/filler.hpp +++ b/xs/src/libnest2d/libnest2d/selections/filler.hpp @@ -56,18 +56,13 @@ public: std::sort(store_.begin(), store_.end(), sortfunc); -// Container a = {store_[0], store_[1], store_[4], store_[5] }; -//// a.insert(a.end(), store_.end()-10, store_.end()); -// store_ = a; - PlacementStrategyLike placer(bin); placer.configure(pconfig); auto it = store_.begin(); while(it != store_.end()) { - if(!placer.pack(store_, it)) { + if(!placer.pack(*it, {std::next(it), store_.end()})) { if(packed_bins_.back().empty()) ++it; -// makeProgress(placer); placer.clearItems(); packed_bins_.emplace_back(); } else { @@ -76,9 +71,6 @@ public: } } -// if(was_packed) { -// packed_bins_.push_back(placer.getItems()); -// } } }; diff --git a/xs/src/libnest2d/libnest2d/selections/firstfit.hpp b/xs/src/libnest2d/libnest2d/selections/firstfit.hpp index 93ca02b1e..1312f9874 100644 --- a/xs/src/libnest2d/libnest2d/selections/firstfit.hpp +++ b/xs/src/libnest2d/libnest2d/selections/firstfit.hpp @@ -61,7 +61,7 @@ public: { auto it = store_.begin(); while (it != store_.end()) { Placer p(bin); p.configure(pconfig); - if(!p.pack(store_, it)) { + if(!p.pack(*it)) { it = store_.erase(it); } else it++; } @@ -73,8 +73,9 @@ public: while(!was_packed) { for(size_t j = 0; j < placers.size() && !was_packed; j++) { - if((was_packed = placers[j].pack(store_, it))) - makeProgress(placers[j], j); + if((was_packed = + placers[j].pack(*it, rem(it, store_) ))) + makeProgress(placers[j], j); } if(!was_packed) { diff --git a/xs/src/libnest2d/tests/test.cpp b/xs/src/libnest2d/tests/test.cpp index 79832b683..197ff6598 100644 --- a/xs/src/libnest2d/tests/test.cpp +++ b/xs/src/libnest2d/tests/test.cpp @@ -110,7 +110,7 @@ TEST(GeometryAlgorithms, boundingCircle) { ASSERT_EQ(c.center().Y, 0); ASSERT_DOUBLE_EQ(c.radius(), 10); - ShapeLike::translate(p, PointImpl{10, 10}); + shapelike::translate(p, PointImpl{10, 10}); c = boundingCircle(p); ASSERT_EQ(c.center().X, 10); @@ -124,8 +124,8 @@ TEST(GeometryAlgorithms, boundingCircle) { c = boundingCircle(part.transformedShape()); if(std::isnan(c.radius())) std::cout << "fail: radius is nan" << std::endl; - else for(auto v : ShapeLike::getContour(part.transformedShape()) ) { - auto d = PointLike::distance(v, c.center()); + else for(auto v : shapelike::getContour(part.transformedShape()) ) { + auto d = pointlike::distance(v, c.center()); if(d > c.radius() ) { auto e = std::abs( 1.0 - d/c.radius()); ASSERT_LE(e, 1e-3); @@ -144,14 +144,14 @@ TEST(GeometryAlgorithms, Distance) { Point p2 = {10, 0}; Point p3 = {10, 10}; - ASSERT_DOUBLE_EQ(PointLike::distance(p1, p2), 10); - ASSERT_DOUBLE_EQ(PointLike::distance(p1, p3), sqrt(200)); + ASSERT_DOUBLE_EQ(pointlike::distance(p1, p2), 10); + ASSERT_DOUBLE_EQ(pointlike::distance(p1, p3), sqrt(200)); Segment seg(p1, p3); - ASSERT_DOUBLE_EQ(PointLike::distance(p2, seg), 7.0710678118654755); + ASSERT_DOUBLE_EQ(pointlike::distance(p2, seg), 7.0710678118654755); - auto result = PointLike::horizontalDistance(p2, seg); + auto result = pointlike::horizontalDistance(p2, seg); auto check = [](Coord val, Coord expected) { if(std::is_floating_point::value) @@ -164,11 +164,11 @@ TEST(GeometryAlgorithms, Distance) { ASSERT_TRUE(result.second); check(result.first, 10); - result = PointLike::verticalDistance(p2, seg); + result = pointlike::verticalDistance(p2, seg); ASSERT_TRUE(result.second); check(result.first, -10); - result = PointLike::verticalDistance(Point{10, 20}, seg); + result = pointlike::verticalDistance(Point{10, 20}, seg); ASSERT_TRUE(result.second); check(result.first, 10); @@ -176,12 +176,12 @@ TEST(GeometryAlgorithms, Distance) { Point p4 = {80, 0}; Segment seg2 = { {0, 0}, {0, 40} }; - result = PointLike::horizontalDistance(p4, seg2); + result = pointlike::horizontalDistance(p4, seg2); ASSERT_TRUE(result.second); check(result.first, 80); - result = PointLike::verticalDistance(p4, seg2); + result = pointlike::verticalDistance(p4, seg2); // Point should not be related to the segment ASSERT_FALSE(result.second); @@ -209,7 +209,7 @@ TEST(GeometryAlgorithms, Area) { {61, 97} }; - ASSERT_TRUE(ShapeLike::area(item.transformedShape()) > 0 ); + ASSERT_TRUE(shapelike::area(item.transformedShape()) > 0 ); } TEST(GeometryAlgorithms, IsPointInsidePolygon) { @@ -287,7 +287,7 @@ TEST(GeometryAlgorithms, LeftAndDownPolygon) Item leftp(placer.leftPoly(item)); - ASSERT_TRUE(ShapeLike::isValid(leftp.rawShape()).first); + ASSERT_TRUE(shapelike::isValid(leftp.rawShape()).first); ASSERT_EQ(leftp.vertexCount(), leftControl.vertexCount()); for(unsigned long i = 0; i < leftControl.vertexCount(); i++) { @@ -297,7 +297,7 @@ TEST(GeometryAlgorithms, LeftAndDownPolygon) Item downp(placer.downPoly(item)); - ASSERT_TRUE(ShapeLike::isValid(downp.rawShape()).first); + ASSERT_TRUE(shapelike::isValid(downp.rawShape()).first); ASSERT_EQ(downp.vertexCount(), downControl.vertexCount()); for(unsigned long i = 0; i < downControl.vertexCount(); i++) { @@ -334,7 +334,7 @@ TEST(GeometryAlgorithms, ArrangeRectanglesTight) {20, 20} }; - Arranger arrange(Box(210, 250)); + Nester arrange(Box(210, 250)); auto groups = arrange(rects.begin(), rects.end()); @@ -387,7 +387,7 @@ TEST(GeometryAlgorithms, ArrangeRectanglesLoose) Coord min_obj_distance = 5; - Arranger arrange(Box(210, 250), + Nester arrange(Box(210, 250), min_obj_distance); auto groups = arrange(rects.begin(), rects.end()); @@ -438,7 +438,7 @@ R"raw( setX(v, getX(v)/SCALE); rbin.setVertex(i, v); } - out << ShapeLike::serialize(rbin.rawShape()) << std::endl; + out << shapelike::serialize(rbin.rawShape()) << std::endl; for(Item& sh : r) { Item tsh(sh.transformedShape()); for(unsigned i = 0; i < tsh.vertexCount(); i++) { @@ -447,7 +447,7 @@ R"raw( setX(v, getX(v)/SCALE); tsh.setVertex(i, v); } - out << ShapeLike::serialize(tsh.rawShape()) << std::endl; + out << shapelike::serialize(tsh.rawShape()) << std::endl; } out << "\n" << std::endl; } @@ -471,8 +471,8 @@ TEST(GeometryAlgorithms, BottomLeftStressTest) { auto next = it; int i = 0; while(it != input.end() && ++next != input.end()) { - placer.pack(input, it); - placer.pack(input, next); + placer.pack(*it); + placer.pack(*next); auto result = placer.getItems(); bool valid = true; @@ -701,7 +701,7 @@ std::vector nfp_concave_testdata = { } }; -template +template void testNfp(const std::vector& testdata) { using namespace libnest2d; @@ -716,12 +716,12 @@ void testNfp(const std::vector& testdata) { orbiter.translate({210*SCALE, 0}); - auto&& nfp = Nfp::noFitPolygon(stationary.rawShape(), + auto&& nfp = nfp::noFitPolygon(stationary.rawShape(), orbiter.transformedShape()); strategies::correctNfpPosition(nfp, stationary, orbiter); - auto v = ShapeLike::isValid(nfp.first); + auto v = shapelike::isValid(nfp.first); if(!v.first) { std::cout << v.second << std::endl; @@ -733,7 +733,7 @@ void testNfp(const std::vector& testdata) { int i = 0; auto rorbiter = orbiter.transformedShape(); - auto vo = Nfp::referenceVertex(rorbiter); + auto vo = nfp::referenceVertex(rorbiter); ASSERT_TRUE(stationary.isInside(infp)); @@ -774,7 +774,7 @@ void testNfp(const std::vector& testdata) { } TEST(GeometryAlgorithms, nfpConvexConvex) { - testNfp(nfp_testdata); + testNfp(nfp_testdata); } //TEST(GeometryAlgorithms, nfpConcaveConcave) { @@ -807,7 +807,7 @@ TEST(GeometryAlgorithms, pointOnPolygonContour) { for(int i = 0; i <= 100; i++) { auto v = ecache.coords(i*(0.01)); - ASSERT_TRUE(ShapeLike::touches(v, input.transformedShape())); + ASSERT_TRUE(shapelike::touches(v, input.transformedShape())); } } @@ -821,17 +821,17 @@ TEST(GeometryAlgorithms, mergePileWithPolygon) { rect2.translate({10, 0}); rect3.translate({25, 0}); - ShapeLike::Shapes pile; + shapelike::Shapes pile; pile.push_back(rect1.transformedShape()); pile.push_back(rect2.transformedShape()); - auto result = Nfp::merge(pile, rect3.transformedShape()); + auto result = nfp::merge(pile, rect3.transformedShape()); ASSERT_EQ(result.size(), 1); Rectangle ref(45, 15); - ASSERT_EQ(ShapeLike::area(result.front()), ref.area()); + ASSERT_EQ(shapelike::area(result.front()), ref.area()); } int main(int argc, char **argv) { diff --git a/xs/src/libnest2d/tools/svgtools.hpp b/xs/src/libnest2d/tools/svgtools.hpp index 3a83caa70..776dd5a1a 100644 --- a/xs/src/libnest2d/tools/svgtools.hpp +++ b/xs/src/libnest2d/tools/svgtools.hpp @@ -56,14 +56,14 @@ public: auto d = static_cast( std::round(conf_.height*conf_.mm_in_coord_units) ); - auto& contour = ShapeLike::getContour(tsh); + auto& contour = shapelike::getContour(tsh); for(auto& v : contour) setY(v, -getY(v) + d); - auto& holes = ShapeLike::holes(tsh); + auto& holes = shapelike::holes(tsh); for(auto& h : holes) for(auto& v : h) setY(v, -getY(v) + d); } - currentLayer() += ShapeLike::serialize(tsh, + currentLayer() += shapelike::serialize(tsh, 1.0/conf_.mm_in_coord_units) + "\n"; } diff --git a/xs/src/libslic3r/ModelArrange.hpp b/xs/src/libslic3r/ModelArrange.hpp index 79371cdb2..cc4bfff0f 100644 --- a/xs/src/libslic3r/ModelArrange.hpp +++ b/xs/src/libslic3r/ModelArrange.hpp @@ -104,8 +104,7 @@ using ItemGroup = std::vector>; std::tuple objfunc(const PointImpl& bincenter, double bin_area, - ShapeLike::Shapes& pile, // The currently arranged pile - double pile_area, + sl::Shapes& pile, // The currently arranged pile const Item &item, double norm, // A norming factor for physical dimensions std::vector& areacache, // pile item areas will be cached @@ -114,8 +113,6 @@ objfunc(const PointImpl& bincenter, const ItemGroup& remaining ) { - using pl = PointLike; - using sl = ShapeLike; using Coord = TCoord; static const double BIG_ITEM_TRESHOLD = 0.02; @@ -150,7 +147,7 @@ objfunc(const PointImpl& bincenter, // Calculate the full bounding box of the pile with the candidate item pile.emplace_back(item.transformedShape()); - auto fullbb = ShapeLike::boundingBox(pile); + auto fullbb = sl::boundingBox(pile); pile.pop_back(); // The bounding box of the big items (they will accumulate in the center @@ -283,21 +280,23 @@ class _ArrBase { protected: using Placer = strategies::_NofitPolyPlacer; using Selector = FirstFitSelection; - using Packer = Arranger; + using Packer = Nester; using PConfig = typename Packer::PlacementConfig; using Distance = TCoord; - using Pile = ShapeLike::Shapes; + using Pile = sl::Shapes; Packer pck_; PConfig pconf_; // Placement configuration double bin_area_; std::vector areacache_; SpatIndex rtree_; + double norm_; public: _ArrBase(const TBin& bin, Distance dist, std::function progressind): - pck_(bin, dist), bin_area_(ShapeLike::area(bin)) + pck_(bin, dist), bin_area_(sl::area(bin)), + norm_(std::sqrt(sl::area(bin))) { fillConfig(pconf_); pck_.progressIndicator(progressind); @@ -306,7 +305,7 @@ public: template inline IndexedPackGroup operator()(Args&&...args) { areacache_.clear(); rtree_.clear(); - return pck_.arrangeIndexed(std::forward(args)...); + return pck_.executeIndexed(std::forward(args)...); } }; @@ -321,21 +320,17 @@ public: pconf_.object_function = [this, bin] ( Pile& pile, const Item &item, - double pile_area, - double norm, const ItemGroup& rem) { auto result = objfunc(bin.center(), bin_area_, pile, - pile_area, item, norm, areacache_, - rtree_, - rem); + item, norm_, areacache_, rtree_, rem); double score = std::get<0>(result); auto& fullbb = std::get<1>(result); auto wdiff = fullbb.width() - bin.width(); auto hdiff = fullbb.height() - bin.height(); - if(wdiff > 0) score += std::pow(wdiff, 2) / norm; - if(hdiff > 0) score += std::pow(hdiff, 2) / norm; + if(wdiff > 0) score += std::pow(wdiff, 2) / norm_; + if(hdiff > 0) score += std::pow(hdiff, 2) / norm_; return score; }; @@ -357,31 +352,28 @@ public: pconf_.object_function = [this, &bin] ( Pile& pile, const Item &item, - double pile_area, - double norm, const ItemGroup& rem) { - auto result = objfunc(bin.center(), bin_area_, pile, - pile_area, item, norm, areacache_, - rtree_, rem); + auto result = objfunc(bin.center(), bin_area_, pile, item, norm_, + areacache_, rtree_, rem); double score = std::get<0>(result); auto& fullbb = std::get<1>(result); - auto d = PointLike::distance(fullbb.minCorner(), + auto d = pl::distance(fullbb.minCorner(), fullbb.maxCorner()); auto diff = d - 2*bin.radius(); if(diff > 0) { if( item.area() > 0.01*bin_area_ && item.vertexCount() < 30) { pile.emplace_back(item.transformedShape()); - auto chull = ShapeLike::convexHull(pile); + auto chull = sl::convexHull(pile); pile.pop_back(); auto C = strategies::boundingCircle(chull); auto rdiff = C.radius() - bin.radius(); if(rdiff > 0) { - score += std::pow(rdiff, 3) / norm; + score += std::pow(rdiff, 3) / norm_; } } } @@ -403,14 +395,11 @@ public: pconf_.object_function = [this, &bin] ( Pile& pile, const Item &item, - double pile_area, - double norm, const ItemGroup& rem) { - auto binbb = ShapeLike::boundingBox(bin); - auto result = objfunc(binbb.center(), bin_area_, pile, - pile_area, item, norm, areacache_, - rtree_, rem); + auto binbb = sl::boundingBox(bin); + auto result = objfunc(binbb.center(), bin_area_, pile, item, norm_, + areacache_, rtree_, rem); double score = std::get<0>(result); return score; @@ -430,13 +419,10 @@ public: this->pconf_.object_function = [this] ( Pile& pile, const Item &item, - double pile_area, - double norm, const ItemGroup& rem) { - auto result = objfunc({0, 0}, 0, pile, pile_area, - item, norm, areacache_, - rtree_, rem); + auto result = objfunc({0, 0}, 0, pile, item, norm_, + areacache_, rtree_, rem); return std::get<0>(result); }; @@ -711,7 +697,7 @@ bool arrange(Model &model, coordf_t min_obj_distance, using P = libnest2d::PolygonImpl; auto ctour = Slic3rMultiPoint_to_ClipperPath(bed); - P irrbed = ShapeLike::create(std::move(ctour)); + P irrbed = sl::create(std::move(ctour)); AutoArranger

arrange(irrbed, min_obj_distance, progressind); From 8617b0a409ba0cbb682c5cd0078fa23be66ab440 Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Mon, 20 Aug 2018 16:34:35 +0200 Subject: [PATCH 09/10] parallel nesting can be enabled but fails with the current objectfunction. --- xs/src/libnest2d/CMakeLists.txt | 2 + xs/src/libnest2d/examples/main.cpp | 59 +- xs/src/libnest2d/libnest2d/boost_alg.hpp | 51 +- .../clipper_backend/clipper_backend.hpp | 71 +- .../libnest2d/libnest2d/geometry_traits.hpp | 94 +- .../libnest2d/geometry_traits_nfp.hpp | 20 +- xs/src/libnest2d/libnest2d/libnest2d.hpp | 53 +- xs/src/libnest2d/libnest2d/metaloop.hpp | 8 +- xs/src/libnest2d/libnest2d/optimizer.hpp | 3 + .../optimizers/nlopt_boilerplate.hpp | 6 +- .../libnest2d/placers/bottomleftplacer.hpp | 28 +- .../libnest2d/libnest2d/placers/nfpplacer.hpp | 730 +++++++----- .../libnest2d/placers/placer_boilerplate.hpp | 15 +- .../libnest2d/selections/djd_heuristic.hpp | 9 +- .../libnest2d/selections/firstfit.hpp | 9 +- xs/src/libnest2d/tests/test.cpp | 9 +- xs/src/libnest2d/tools/libnfpglue.cpp | 16 +- xs/src/libnest2d/tools/libnfpglue.hpp | 10 +- xs/src/libnest2d/tools/nfp_svgnest.hpp | 1004 +++++++++++++++++ xs/src/libnest2d/tools/nfp_svgnest_glue.hpp | 77 ++ xs/src/libslic3r/ModelArrange.hpp | 49 +- 21 files changed, 1833 insertions(+), 490 deletions(-) create mode 100644 xs/src/libnest2d/tools/nfp_svgnest.hpp create mode 100644 xs/src/libnest2d/tools/nfp_svgnest_glue.hpp diff --git a/xs/src/libnest2d/CMakeLists.txt b/xs/src/libnest2d/CMakeLists.txt index 0a181f4ab..cd3e35b97 100644 --- a/xs/src/libnest2d/CMakeLists.txt +++ b/xs/src/libnest2d/CMakeLists.txt @@ -93,6 +93,8 @@ if(LIBNEST2D_BUILD_EXAMPLES) add_executable(example examples/main.cpp # tools/libnfpglue.hpp # tools/libnfpglue.cpp + tools/nfp_svgnest.hpp + tools/nfp_svgnest_glue.hpp tools/svgtools.hpp tests/printer_parts.cpp tests/printer_parts.h diff --git a/xs/src/libnest2d/examples/main.cpp b/xs/src/libnest2d/examples/main.cpp index 37096019d..57be7a208 100644 --- a/xs/src/libnest2d/examples/main.cpp +++ b/xs/src/libnest2d/examples/main.cpp @@ -1,7 +1,6 @@ #include #include #include - //#define DEBUG_EXPORT_NFP #include @@ -12,6 +11,8 @@ #include "libnest2d/rotfinder.hpp" //#include "tools/libnfpglue.hpp" +//#include "tools/nfp_svgnest_glue.hpp" + using namespace libnest2d; using ItemGroup = std::vector>; @@ -53,10 +54,50 @@ void arrangeRectangles() { const int SCALE = 1000000; - std::vector rects = { - {60*SCALE, 200*SCALE}, - {60*SCALE, 200*SCALE} - }; + std::vector rects(100, { + {-9945219, -3065619}, + {-9781479, -2031780}, + {-9510560, -1020730}, + {-9135450, -43529}, + {-2099999, 14110899}, + {2099999, 14110899}, + {9135450, -43529}, + {9510560, -1020730}, + {9781479, -2031780}, + {9945219, -3065619}, + {10000000, -4110899}, + {9945219, -5156179}, + {9781479, -6190019}, + {9510560, -7201069}, + {9135450, -8178270}, + {8660249, -9110899}, + {8090169, -9988750}, + {7431449, -10802209}, + {6691309, -11542349}, + {5877850, -12201069}, + {5000000, -12771149}, + {4067369, -13246350}, + {3090169, -13621459}, + {2079119, -13892379}, + {1045279, -14056119}, + {0, -14110899}, + {-1045279, -14056119}, + {-2079119, -13892379}, + {-3090169, -13621459}, + {-4067369, -13246350}, + {-5000000, -12771149}, + {-5877850, -12201069}, + {-6691309, -11542349}, + {-7431449, -10802209}, + {-8090169, -9988750}, + {-8660249, -9110899}, + {-9135450, -8178270}, + {-9510560, -7201069}, + {-9781479, -6190019}, + {-9945219, -5156179}, + {-10000000, -4110899}, + {-9945219, -3065619}, + }); std::vector input; input.insert(input.end(), prusaParts().begin(), prusaParts().end()); @@ -84,7 +125,7 @@ void arrangeRectangles() { // _Circle bin({0, 0}, 125*SCALE); - auto min_obj_distance = static_cast(0*SCALE); + auto min_obj_distance = static_cast(1.5*SCALE); using Placer = strategies::_NofitPolyPlacer; using Packer = Nester; @@ -95,14 +136,15 @@ void arrangeRectangles() { pconf.alignment = Placer::Config::Alignment::CENTER; pconf.starting_point = Placer::Config::Alignment::CENTER; pconf.rotations = {0.0/*, Pi/2.0, Pi, 3*Pi/2*/}; - pconf.accuracy = 1.0f; + pconf.accuracy = 0.5f; + pconf.parallel = true; Packer::SelectionConfig sconf; // sconf.allow_parallel = false; // sconf.force_parallel = false; // sconf.try_triplets = true; // sconf.try_reverse_order = true; -// sconf.waste_increment = 0.005; +// sconf.waste_increment = 0.01; arrange.configure(pconf, sconf); @@ -175,7 +217,6 @@ void arrangeRectangles() { SVGWriter svgw(conf); svgw.setSize(Box(250*SCALE, 210*SCALE)); svgw.writePackGroup(result); -// std::for_each(input.begin(), input.end(), [&svgw](Item& item){ svgw.writeItem(item);}); svgw.save("out"); } diff --git a/xs/src/libnest2d/libnest2d/boost_alg.hpp b/xs/src/libnest2d/libnest2d/boost_alg.hpp index 7da1036f0..bb0403b06 100644 --- a/xs/src/libnest2d/libnest2d/boost_alg.hpp +++ b/xs/src/libnest2d/libnest2d/boost_alg.hpp @@ -356,8 +356,7 @@ inline double area(const PolygonImpl& shape, const PolygonTag&) #endif template<> -inline bool isInside(const PointImpl& point, - const PolygonImpl& shape) +inline bool isInside(const PointImpl& point, const PolygonImpl& shape) { return boost::geometry::within(point, shape); } @@ -390,7 +389,8 @@ inline bp2d::Box boundingBox(const PolygonImpl& sh, const PolygonTag&) } template<> -inline bp2d::Box boundingBox(const bp2d::Shapes& shapes) +inline bp2d::Box boundingBox(const bp2d::Shapes& shapes, + const MultiPolygonTag&) { bp2d::Box b; boost::geometry::envelope(shapes, b); @@ -400,7 +400,7 @@ inline bp2d::Box boundingBox(const bp2d::Shapes& shapes) #ifndef DISABLE_BOOST_CONVEX_HULL template<> -inline PolygonImpl convexHull(const PolygonImpl& sh) +inline PolygonImpl convexHull(const PolygonImpl& sh, const PolygonTag&) { PolygonImpl ret; boost::geometry::convex_hull(sh, ret); @@ -408,7 +408,8 @@ inline PolygonImpl convexHull(const PolygonImpl& sh) } template<> -inline PolygonImpl convexHull(const bp2d::Shapes& shapes) +inline PolygonImpl convexHull(const TMultiShape& shapes, + const MultiPolygonTag&) { PolygonImpl ret; boost::geometry::convex_hull(shapes, ret); @@ -416,34 +417,6 @@ inline PolygonImpl convexHull(const bp2d::Shapes& shapes) } #endif -#ifndef DISABLE_BOOST_ROTATE -template<> -inline void rotate(PolygonImpl& sh, const Radians& rads) -{ - namespace trans = boost::geometry::strategy::transform; - - PolygonImpl cpy = sh; - trans::rotate_transformer - rotate(rads); - - boost::geometry::transform(cpy, sh, rotate); -} -#endif - -#ifndef DISABLE_BOOST_TRANSLATE -template<> -inline void translate(PolygonImpl& sh, const PointImpl& offs) -{ - namespace trans = boost::geometry::strategy::transform; - - PolygonImpl cpy = sh; - trans::translate_transformer translate( - bp2d::getX(offs), bp2d::getY(offs)); - - boost::geometry::transform(cpy, sh, translate); -} -#endif - #ifndef DISABLE_BOOST_OFFSET template<> inline void offset(PolygonImpl& sh, bp2d::Coord distance) @@ -515,14 +488,24 @@ template<> inline std::pair isValid(const PolygonImpl& sh) namespace nfp { #ifndef DISABLE_BOOST_NFP_MERGE + +// Warning: I could not get boost union_ to work. Geometries will overlap. template<> -inline bp2d::Shapes Nfp::merge(const bp2d::Shapes& shapes, +inline bp2d::Shapes nfp::merge(const bp2d::Shapes& shapes, const PolygonImpl& sh) { bp2d::Shapes retv; boost::geometry::union_(shapes, sh, retv); return retv; } + +template<> +inline bp2d::Shapes nfp::merge(const bp2d::Shapes& shapes) +{ + bp2d::Shapes retv; + boost::geometry::union_(shapes, shapes.back(), retv); + return retv; +} #endif } diff --git a/xs/src/libnest2d/libnest2d/clipper_backend/clipper_backend.hpp b/xs/src/libnest2d/libnest2d/clipper_backend/clipper_backend.hpp index 4238212ad..745fd2108 100644 --- a/xs/src/libnest2d/libnest2d/clipper_backend/clipper_backend.hpp +++ b/xs/src/libnest2d/libnest2d/clipper_backend/clipper_backend.hpp @@ -21,9 +21,6 @@ struct PolygonImpl { PathImpl Contour; HoleStore Holes; - using Tag = libnest2d::PolygonTag; - using PointType = PointImpl; - inline PolygonImpl() = default; inline explicit PolygonImpl(const PathImpl& cont): Contour(cont) {} @@ -102,41 +99,49 @@ template<> struct PointType { using Type = PointImpl; }; -// Type of vertex iterator used by Clipper -template<> struct VertexIteratorType { - using Type = ClipperLib::Path::iterator; -}; - -// Type of vertex iterator used by Clipper -template<> struct VertexConstIteratorType { - using Type = ClipperLib::Path::const_iterator; +template<> struct PointType { + using Type = PointImpl; }; template<> struct CountourType { using Type = PathImpl; }; +template<> struct ShapeTag { using Type = PolygonTag; }; + +template<> struct ShapeTag> { + using Type = MultiPolygonTag; +}; + +template<> struct PointType> { + using Type = PointImpl; +}; + +template<> struct HolesContainer { + using Type = ClipperLib::Paths; +}; + namespace pointlike { -// Tell binpack2d how to extract the X coord from a ClipperPoint object +// Tell libnest2d how to extract the X coord from a ClipperPoint object template<> inline TCoord x(const PointImpl& p) { return p.X; } -// Tell binpack2d how to extract the Y coord from a ClipperPoint object +// Tell libnest2d how to extract the Y coord from a ClipperPoint object template<> inline TCoord y(const PointImpl& p) { return p.Y; } -// Tell binpack2d how to extract the X coord from a ClipperPoint object +// Tell libnest2d how to extract the X coord from a ClipperPoint object template<> inline TCoord& x(PointImpl& p) { return p.X; } -// Tell binpack2d how to extract the Y coord from a ClipperPoint object +// Tell libnest2d how to extract the Y coord from a ClipperPoint object template<> inline TCoord& y(PointImpl& p) { return p.Y; @@ -178,10 +183,6 @@ inline double area(const PolygonImpl& sh) { } -template<> struct HolesContainer { - using Type = ClipperLib::Paths; -}; - namespace shapelike { template<> inline void reserve(PolygonImpl& sh, size_t vertex_capacity) @@ -189,7 +190,7 @@ template<> inline void reserve(PolygonImpl& sh, size_t vertex_capacity) return sh.Contour.reserve(vertex_capacity); } -// Tell binpack2d how to make string out of a ClipperPolygon object +// Tell libnest2d how to make string out of a ClipperPolygon object template<> inline double area(const PolygonImpl& sh, const PolygonTag&) { return _smartarea::area::Value>(sh); @@ -269,28 +270,6 @@ template<> inline std::string toString(const PolygonImpl& sh) return ss.str(); } -template<> inline TVertexIterator begin(PolygonImpl& sh) -{ - return sh.Contour.begin(); -} - -template<> inline TVertexIterator end(PolygonImpl& sh) -{ - return sh.Contour.end(); -} - -template<> -inline TVertexConstIterator cbegin(const PolygonImpl& sh) -{ - return sh.Contour.cbegin(); -} - -template<> inline TVertexConstIterator cend( - const PolygonImpl& sh) -{ - return sh.Contour.cend(); -} - template<> inline PolygonImpl create(const PathImpl& path, const HoleStore& holes) { @@ -410,8 +389,8 @@ inline void rotate(PolygonImpl& sh, const Radians& rads) } // namespace shapelike #define DISABLE_BOOST_NFP_MERGE -inline nfp::Shapes _merge(ClipperLib::Clipper& clipper) { - nfp::Shapes retv; +inline std::vector _merge(ClipperLib::Clipper& clipper) { + shapelike::Shapes retv; ClipperLib::PolyTree result; clipper.Execute(ClipperLib::ctUnion, result, ClipperLib::pftNegative); @@ -447,8 +426,8 @@ inline nfp::Shapes _merge(ClipperLib::Clipper& clipper) { namespace nfp { -template<> inline nfp::Shapes -merge(const nfp::Shapes& shapes) +template<> inline std::vector +merge(const std::vector& shapes) { ClipperLib::Clipper clipper(ClipperLib::ioReverseSolution); diff --git a/xs/src/libnest2d/libnest2d/geometry_traits.hpp b/xs/src/libnest2d/libnest2d/geometry_traits.hpp index 0826cbd4b..d16257731 100644 --- a/xs/src/libnest2d/libnest2d/geometry_traits.hpp +++ b/xs/src/libnest2d/libnest2d/geometry_traits.hpp @@ -22,34 +22,12 @@ template using TCoord = typename CoordType>::Type; /// Getting the type of point structure used by a shape. -template struct PointType { /*using Type = void;*/ }; +template struct PointType { using Type = typename Sh::PointType; }; /// TPoint as shorthand for `typename PointType::Type`. template using TPoint = typename PointType>::Type; -/// Getting the VertexIterator type of a shape class. -template struct VertexIteratorType { /*using Type = void;*/ }; - -/// Getting the const vertex iterator for a shape class. -template struct VertexConstIteratorType {/* using Type = void;*/ }; - -/** - * TVertexIterator as shorthand for - * `typename VertexIteratorType::Type` - */ -template -using TVertexIterator = -typename VertexIteratorType>::Type; - -/** - * \brief TVertexConstIterator as shorthand for - * `typename VertexConstIteratorType::Type` - */ -template -using TVertexConstIterator = -typename VertexConstIteratorType>::Type; - /** * \brief A point pair base class for other point pairs (segment, box, ...). * \tparam RawPoint The actual point type to use. @@ -61,9 +39,16 @@ struct PointPair { }; struct PolygonTag {}; +struct MultiPolygonTag {}; struct BoxTag {}; struct CircleTag {}; +template struct ShapeTag { using Type = typename Shape::Tag; }; +template using Tag = typename ShapeTag::Type; + +template struct MultiShape { using Type = std::vector; }; +template using TMultiShape = typename MultiShape::Type; + /** * \brief An abstraction of a box; */ @@ -371,7 +356,7 @@ enum class Formats { namespace shapelike { template - using Shapes = std::vector; + using Shapes = TMultiShape; template inline RawShape create(const TContour& contour, @@ -455,27 +440,28 @@ namespace shapelike { } template - inline TVertexIterator begin(RawShape& sh) + inline typename TContour::iterator begin(RawShape& sh) { - return sh.begin(); + return getContour(sh).begin(); } template - inline TVertexIterator end(RawShape& sh) + inline typename TContour::iterator end(RawShape& sh) { - return sh.end(); + return getContour(sh).end(); } template - inline TVertexConstIterator cbegin(const RawShape& sh) + inline typename TContour::const_iterator + cbegin(const RawShape& sh) { - return sh.cbegin(); + return getContour(sh).cbegin(); } template - inline TVertexConstIterator cend(const RawShape& sh) + inline typename TContour::const_iterator cend(const RawShape& sh) { - return sh.cend(); + return getContour(sh).cend(); } template @@ -559,27 +545,29 @@ namespace shapelike { "ShapeLike::boundingBox(shape) unimplemented!"); } - template - inline _Box> boundingBox(const Shapes& /*sh*/) + template + inline _Box> + boundingBox(const RawShapes& /*sh*/, const MultiPolygonTag&) { - static_assert(always_false::value, + static_assert(always_false::value, "ShapeLike::boundingBox(shapes) unimplemented!"); } template - inline RawShape convexHull(const RawShape& /*sh*/) + inline RawShape convexHull(const RawShape& /*sh*/, const PolygonTag&) { static_assert(always_false::value, "ShapeLike::convexHull(shape) unimplemented!"); return RawShape(); } - template - inline RawShape convexHull(const Shapes& /*sh*/) + template + inline typename RawShapes::value_type + convexHull(const RawShapes& /*sh*/, const MultiPolygonTag&) { - static_assert(always_false::value, + static_assert(always_false::value, "ShapeLike::convexHull(shapes) unimplemented!"); - return RawShape(); + return typename RawShapes::value_type(); } template @@ -599,8 +587,7 @@ namespace shapelike { template inline void offset(RawShape& /*sh*/, TCoord> /*distance*/) { - static_assert(always_false::value, - "ShapeLike::offset() unimplemented!"); + dout() << "The current geometry backend does not support offsetting!\n"; } template @@ -670,9 +657,9 @@ namespace shapelike { } template // Dispatch function - inline _Box boundingBox(const S& sh) + inline _Box> boundingBox(const S& sh) { - return boundingBox(sh, typename S::Tag()); + return boundingBox(sh, Tag() ); } template @@ -690,7 +677,7 @@ namespace shapelike { template // Dispatching function inline double area(const RawShape& sh) { - return area(sh, typename RawShape::Tag()); + return area(sh, Tag()); } template @@ -702,6 +689,13 @@ namespace shapelike { }); } + template + inline auto convexHull(const RawShape& sh) + -> decltype(convexHull(sh, Tag())) // TODO: C++14 could deduce + { + return convexHull(sh, Tag()); + } + template inline bool isInside(const TPoint& point, const _Circle>& circ) @@ -816,6 +810,16 @@ namespace shapelike { } } +#define DECLARE_MAIN_TYPES(T) \ + using Polygon = T; \ + using Point = TPoint; \ + using Coord = TCoord; \ + using Contour = TContour; \ + using Box = _Box; \ + using Circle = _Circle; \ + using Segment = _Segment; \ + using Polygons = TMultiShape + } #endif // GEOMETRY_TRAITS_HPP diff --git a/xs/src/libnest2d/libnest2d/geometry_traits_nfp.hpp b/xs/src/libnest2d/libnest2d/geometry_traits_nfp.hpp index 6cac374ae..b9dfd2185 100644 --- a/xs/src/libnest2d/libnest2d/geometry_traits_nfp.hpp +++ b/xs/src/libnest2d/libnest2d/geometry_traits_nfp.hpp @@ -27,8 +27,8 @@ inline bool _vsort(const TPoint& v1, const TPoint& v2) /// A collection of static methods for handling the no fit polygon creation. namespace nfp { -namespace sl = shapelike; -namespace pl = pointlike; +//namespace sl = shapelike; +//namespace pl = pointlike; /// The complexity level of a polygon that an NFP implementation can handle. enum class NfpLevel: unsigned { @@ -49,7 +49,7 @@ template struct MaxNfpLevel { // Shorthand for a pile of polygons template -using Shapes = typename shapelike::Shapes; +using Shapes = TMultiShape; /** * Merge a bunch of polygons with the specified additional polygon. @@ -62,10 +62,10 @@ using Shapes = typename shapelike::Shapes; * mostly it will be a set containing only one big polygon but if the input * polygons are disjuct than the resulting set will contain more polygons. */ -template -inline Shapes merge(const Shapes& /*shc*/) +template +inline RawShapes merge(const RawShapes& /*shc*/) { - static_assert(always_false::value, + static_assert(always_false::value, "Nfp::merge(shapes, shape) unimplemented!"); } @@ -81,12 +81,12 @@ inline Shapes merge(const Shapes& /*shc*/) * polygons are disjuct than the resulting set will contain more polygons. */ template -inline Shapes merge(const Shapes& shc, - const RawShape& sh) +inline TMultiShape merge(const TMultiShape& shc, + const RawShape& sh) { - auto m = merge(shc); + auto m = nfp::merge(shc); m.push_back(sh); - return merge(m); + return nfp::merge(m); } /** diff --git a/xs/src/libnest2d/libnest2d/libnest2d.hpp b/xs/src/libnest2d/libnest2d/libnest2d.hpp index d2850d4ed..42255cbb4 100644 --- a/xs/src/libnest2d/libnest2d/libnest2d.hpp +++ b/xs/src/libnest2d/libnest2d/libnest2d.hpp @@ -31,6 +31,8 @@ class _Item { using Vertex = TPoint; using Box = _Box; + using VertexConstIterator = typename TContour::const_iterator; + // The original shape that gets encapsulated. RawShape sh_; @@ -39,7 +41,7 @@ class _Item { Radians rotation_; Coord offset_distance_; - // Info about whether the tranformations will have to take place + // Info about whether the transformations will have to take place // This is needed because if floating point is used, it is hard to say // that a zero angle is not a rotation because of testing for equality. bool has_rotation_ = false, has_translation_ = false, has_offset_ = false; @@ -59,8 +61,8 @@ class _Item { }; mutable Convexity convexity_ = Convexity::UNCHECKED; - mutable TVertexConstIterator rmt_; // rightmost top vertex - mutable TVertexConstIterator lmb_; // leftmost bottom vertex + mutable VertexConstIterator rmt_; // rightmost top vertex + mutable VertexConstIterator lmb_; // leftmost bottom vertex mutable bool rmt_valid_ = false, lmb_valid_ = false; mutable struct BBCache { Box bb; bool valid; Vertex tr; @@ -81,7 +83,7 @@ public: * supports. Giving out a non const iterator would make it impossible to * perform correct cache invalidation. */ - using Iterator = TVertexConstIterator; + using Iterator = VertexConstIterator; /** * @brief Get the orientation of the polygon. @@ -110,7 +112,7 @@ public: explicit inline _Item(RawShape&& sh): sh_(std::move(sh)) {} /** - * @brief Create an item from an initilizer list. + * @brief Create an item from an initializer list. * @param il The initializer list of vertices. */ inline _Item(const std::initializer_list< Vertex >& il): @@ -160,7 +162,7 @@ public: } /** - * @brief Get a copy of an outer vertex whithin the carried shape. + * @brief Get a copy of an outer vertex within the carried shape. * * Note that the vertex considered here is taken from the original shape * that this item is constructed from. This means that no transformation is @@ -245,7 +247,7 @@ public: * @param p * @return */ - inline bool isPointInside(const Vertex& p) const + inline bool isInside(const Vertex& p) const { return sl::isInside(p, transformedShape()); } @@ -505,7 +507,7 @@ rem(typename Container::const_iterator it, const Container& cont) { /** * \brief A wrapper interface (trait) class for any placement strategy provider. * - * If a client want's to use its own placement algorithm, all it has to do is to + * If a client wants to use its own placement algorithm, all it has to do is to * specialize this class template and define all the ten methods it has. It can * use the strategies::PlacerBoilerplace class for creating a new placement * strategy where only the constructor and the trypack method has to be provided @@ -558,7 +560,7 @@ public: * Note that it depends on the particular placer implementation how it * reacts to config changes in the middle of a calculation. * - * @param config The configuration object defined by the placement startegy. + * @param config The configuration object defined by the placement strategy. */ inline void configure(const Config& config) { impl_.configure(config); } @@ -568,7 +570,7 @@ public: * * \param item_store A container of items that are intended to be packed * later. Can be used by the placer to switch tactics. When it's knows that - * many items will come a greedy startegy may not be the best. + * many items will come a greedy strategy may not be the best. * \param from The iterator to the item from which the packing should start, * including the pointed item * \param count How many items should be packed. If the value is 1, than @@ -596,7 +598,7 @@ public: * A default implementation would be to call * { auto&& r = trypack(...); accept(r); return r; } but we should let the * implementor of the placement strategy to harvest any optimizations from - * the absence of an intermadiate step. The above version can still be used + * the absence of an intermediate step. The above version can still be used * in the implementation. * * @param item The item to pack. @@ -628,13 +630,6 @@ public: inline double filledArea() const { return impl_.filledArea(); } -#ifndef NDEBUG - inline auto getDebugItems() -> decltype(impl_.debug_items_)& - { - return impl_.debug_items_; - } -#endif - }; // The progress function will be called with the number of placed items @@ -659,15 +654,15 @@ public: * Note that it depends on the particular placer implementation how it * reacts to config changes in the middle of a calculation. * - * @param config The configuration object defined by the selection startegy. + * @param config The configuration object defined by the selection strategy. */ inline void configure(const Config& config) { impl_.configure(config); } /** - * @brief A function callback which should be called whenewer an item or - * a group of items where succesfully packed. + * @brief A function callback which should be called whenever an item or + * a group of items where successfully packed. * @param fn A function callback object taking one unsigned integer as the * number of the remaining items to pack. */ @@ -680,7 +675,7 @@ public: * placer compatible with the PlacementStrategyLike interface. * * \param first, last The first and last iterator if the input sequence. It - * can be only an iterator of a type converitible to Item. + * can be only an iterator of a type convertible to Item. * \param bin. The shape of the bin. It has to be supported by the placement * strategy. * \param An optional config object for the placer. @@ -712,7 +707,7 @@ public: /** * @brief Get the items for a particular bin. * @param binIndex The index of the requested bin. - * @return Returns a list of allitems packed into the requested bin. + * @return Returns a list of all items packed into the requested bin. */ inline ItemGroup itemsForBin(size_t binIndex) { return impl_.itemsForBin(binIndex); @@ -754,7 +749,7 @@ using _IndexedPackGroup = std::vector< >; /** - * The Arranger is the frontend class for the binpack2d library. It takes the + * The Arranger is the front-end class for the libnest2d library. It takes the * input items and outputs the items with the proper transformations to be * inside the provided bin. */ @@ -857,7 +852,7 @@ public: return _execute(from, to); } - /// Set a progress indicatior function object for the selector. + /// Set a progress indicator function object for the selector. inline Nester& progressIndicator(ProgressFunction func) { selector_.progressIndicator(func); return *this; @@ -877,8 +872,8 @@ private: template, - // This funtion will be used only if the iterators are pointing to - // a type compatible with the binpack2d::_Item template. + // This function will be used only if the iterators are pointing to + // a type compatible with the libnets2d::_Item template. // This way we can use references to input elements as they will // have to exist for the lifetime of this call. class T = enable_if_t< std::is_convertible::value, IT> @@ -904,8 +899,8 @@ private: template, - // This funtion will be used only if the iterators are pointing to - // a type compatible with the binpack2d::_Item template. + // This function will be used only if the iterators are pointing to + // a type compatible with the libnest2d::_Item template. // This way we can use references to input elements as they will // have to exist for the lifetime of this call. class T = enable_if_t< std::is_convertible::value, IT> diff --git a/xs/src/libnest2d/libnest2d/metaloop.hpp b/xs/src/libnest2d/libnest2d/metaloop.hpp index 18755525c..d88988ba1 100644 --- a/xs/src/libnest2d/libnest2d/metaloop.hpp +++ b/xs/src/libnest2d/libnest2d/metaloop.hpp @@ -67,11 +67,11 @@ class metaloop { // need to wrap that in a type (see metaloop::Int). /* - * A helper alias to create integer values wrapped as a type. It is nessecary + * A helper alias to create integer values wrapped as a type. It is necessary * because a non type template parameter (such as int) would be prohibited in * a partial specialization. Also for the same reason we have to use a class * _Metaloop instead of a simple function as a functor. A function cannot be - * partially specialized in a way that is neccesary for this trick. + * partially specialized in a way that is necessary for this trick. */ template using Int = std::integral_constant; @@ -88,7 +88,7 @@ public: // It takes the real functor that can be specified in-place but only // with C++14 because the second parameter's type will depend on the // type of the parameter pack element that is processed. In C++14 we can - // specify this second parameter type as auto in the lamda parameter list. + // specify this second parameter type as auto in the lambda parameter list. inline MapFn(Fn&& fn): fn_(forward(fn)) {} template void operator ()(T&& pack_element) { @@ -146,7 +146,7 @@ public: * version of run is called which does not call itself anymore. * * If you are utterly annoyed, at least you have learned a super crazy - * functional metaprogramming pattern. + * functional meta-programming pattern. */ template using MetaLoop = _MetaLoop, Args...>; diff --git a/xs/src/libnest2d/libnest2d/optimizer.hpp b/xs/src/libnest2d/libnest2d/optimizer.hpp index c8ed2e378..90d2f2ff9 100644 --- a/xs/src/libnest2d/libnest2d/optimizer.hpp +++ b/xs/src/libnest2d/libnest2d/optimizer.hpp @@ -102,6 +102,9 @@ struct StopCriteria { /// If the relative value difference between two scores. double relative_score_difference = std::nan(""); + /// Stop if this value or better is found. + double stop_score = std::nan(""); + unsigned max_iterations = 0; }; diff --git a/xs/src/libnest2d/libnest2d/optimizers/nlopt_boilerplate.hpp b/xs/src/libnest2d/libnest2d/optimizers/nlopt_boilerplate.hpp index 737ca6e3c..1a0f06e02 100644 --- a/xs/src/libnest2d/libnest2d/optimizers/nlopt_boilerplate.hpp +++ b/xs/src/libnest2d/libnest2d/optimizers/nlopt_boilerplate.hpp @@ -142,10 +142,12 @@ protected: default: ; } - auto abs_diff = stopcr_.absolute_score_difference; - auto rel_diff = stopcr_.relative_score_difference; + double abs_diff = stopcr_.absolute_score_difference; + double rel_diff = stopcr_.relative_score_difference; + double stopval = stopcr_.stop_score; if(!std::isnan(abs_diff)) opt_.set_ftol_abs(abs_diff); if(!std::isnan(rel_diff)) opt_.set_ftol_rel(rel_diff); + if(!std::isnan(stopval)) opt_.set_stopval(stopval); if(this->stopcr_.max_iterations > 0) opt_.set_maxeval(this->stopcr_.max_iterations ); diff --git a/xs/src/libnest2d/libnest2d/placers/bottomleftplacer.hpp b/xs/src/libnest2d/libnest2d/placers/bottomleftplacer.hpp index af1678372..0ba9eb3c0 100644 --- a/xs/src/libnest2d/libnest2d/placers/bottomleftplacer.hpp +++ b/xs/src/libnest2d/libnest2d/placers/bottomleftplacer.hpp @@ -7,9 +7,24 @@ namespace libnest2d { namespace strategies { +template struct Epsilon {}; + +template +struct Epsilon::value, T> > { + static const T Value = 1; +}; + +template +struct Epsilon::value, T> > { + static const T Value = 1e-3; +}; + template struct BLConfig { - TCoord> min_obj_distance = 0; + DECLARE_MAIN_TYPES(RawShape); + + Coord min_obj_distance = 0; + Coord epsilon = Epsilon::Value; bool allow_rotations = false; }; @@ -69,20 +84,21 @@ protected: setInitialPosition(item); Unit d = availableSpaceDown(item); - bool can_move = d > 1 /*std::numeric_limits::epsilon()*/; + auto eps = config_.epsilon; + bool can_move = d > eps; bool can_be_packed = can_move; bool left = true; while(can_move) { if(left) { // write previous down move and go down - item.translate({0, -d+1}); + item.translate({0, -d+eps}); d = availableSpaceLeft(item); - can_move = d > 1/*std::numeric_limits::epsilon()*/; + can_move = d > eps; left = false; } else { // write previous left move and go down - item.translate({-d+1, 0}); + item.translate({-d+eps, 0}); d = availableSpaceDown(item); - can_move = d > 1/*std::numeric_limits::epsilon()*/; + can_move = d > eps; left = true; } } diff --git a/xs/src/libnest2d/libnest2d/placers/nfpplacer.hpp b/xs/src/libnest2d/libnest2d/placers/nfpplacer.hpp index 638d606e0..b9e0ba8f1 100644 --- a/xs/src/libnest2d/libnest2d/placers/nfpplacer.hpp +++ b/xs/src/libnest2d/libnest2d/placers/nfpplacer.hpp @@ -2,7 +2,15 @@ #define NOFITPOLY_HPP #include -#include + +// For caching nfps +#include + +// For parallel for +#include +#include +#include +#include #ifndef NDEBUG #include @@ -13,8 +21,84 @@ #include "tools/svgtools.hpp" +namespace libnest2d { -namespace libnest2d { namespace strategies { +namespace __parallel { + +using std::function; +using std::iterator_traits; +template +using TIteratorValue = typename iterator_traits::value_type; + +template +inline void enumerate( + Iterator from, Iterator to, + function, unsigned)> fn, + std::launch policy = std::launch::deferred | std::launch::async) +{ + auto N = to-from; + std::vector> rets(N); + + auto it = from; + for(unsigned b = 0; b < N; b++) { + rets[b] = std::async(policy, fn, *it++, b); + } + + for(unsigned fi = 0; fi < rets.size(); ++fi) rets[fi].wait(); +} + +} + +namespace __itemhash { + +using Key = size_t; + +template +Key hash(const _Item& item) { + using Point = TPoint; + using Segment = _Segment; + + static const int N = 26; + static const int M = N*N - 1; + + std::string ret; + auto& rhs = item.rawShape(); + auto& ctr = sl::getContour(rhs); + auto it = ctr.begin(); + auto nx = std::next(it); + + double circ = 0; + while(nx != ctr.end()) { + Segment seg(*it++, *nx++); + Radians a = seg.angleToXaxis(); + double deg = Degrees(a); + int ms = 'A', ls = 'A'; + while(deg > N) { ms++; deg -= N; } + ls += int(deg); + ret.push_back(char(ms)); ret.push_back(char(ls)); + circ += seg.length(); + } + + it = ctr.begin(); nx = std::next(it); + + while(nx != ctr.end()) { + Segment seg(*it++, *nx++); + auto l = int(M * seg.length() / circ); + int ms = 'A', ls = 'A'; + while(l > N) { ms++; l -= N; } + ls += l; + ret.push_back(char(ms)); ret.push_back(char(ls)); + } + + return std::hash()(ret); +} + +template +using Hash = std::unordered_map>; + +} + +namespace strategies { template struct NfpPConfig { @@ -71,7 +155,7 @@ struct NfpPConfig { * decisions (for you or a more intelligent AI). * */ - std::function&, const _Item&, + std::function&, const _Item&, const ItemGroup&)> object_function; @@ -80,7 +164,7 @@ struct NfpPConfig { * This is a compromise slider between quality and speed. Zero is the * fast and poor solution while 1.0 is the slowest but most accurate. */ - float accuracy = 1.0; + float accuracy = 0.65f; /** * @brief If you want to see items inside other item's holes, you have to @@ -91,6 +175,11 @@ struct NfpPConfig { */ bool explore_holes = false; + /** + * @brief If true, use all CPUs available. Run on a single core otherwise. + */ + bool parallel = true; + NfpPConfig(): rotations({0.0, Pi/2.0, Pi, 3*Pi/2}), alignment(Alignment::CENTER), starting_point(Alignment::CENTER) {} }; @@ -325,121 +414,8 @@ inline void correctNfpPosition(nfp::NfpResult& nfp, shapelike::translate(nfp.first, dnfp); } -template -nfp::Shapes calcnfp( const Container& polygons, - const _Item& trsh, - Lvl) -{ - using Item = _Item; - using namespace nfp; - - nfp::Shapes nfps; - -// int pi = 0; - for(Item& sh : polygons) { - auto subnfp_r = noFitPolygon( - sh.transformedShape(), trsh.transformedShape()); - #ifndef NDEBUG - auto vv = sl::isValid(sh.transformedShape()); - assert(vv.first); - - auto vnfp = sl::isValid(subnfp_r.first); - assert(vnfp.first); - #endif - - correctNfpPosition(subnfp_r, sh, trsh); - - nfps = nfp::merge(nfps, subnfp_r.first); - -// double SCALE = 1000000; -// using SVGWriter = svg::SVGWriter; -// SVGWriter::Config conf; -// conf.mm_in_coord_units = SCALE; -// SVGWriter svgw(conf); -// Box bin(250*SCALE, 210*SCALE); -// svgw.setSize(bin); -// for(int i = 0; i <= pi; i++) svgw.writeItem(polygons[i]); -// svgw.writeItem(trsh); -//// svgw.writeItem(Item(subnfp_r.first)); -// for(auto& n : nfps) svgw.writeItem(Item(n)); -// svgw.save("nfpout"); -// pi++; - } - - return nfps; -} - -template -nfp::Shapes calcnfp( const Container& polygons, - const _Item& trsh, - Level) -{ - using namespace nfp; - using Item = _Item; - - Shapes nfps; - - auto& orb = trsh.transformedShape(); - bool orbconvex = trsh.isContourConvex(); - - for(Item& sh : polygons) { - nfp::NfpResult subnfp; - auto& stat = sh.transformedShape(); - - if(sh.isContourConvex() && orbconvex) - subnfp = nfp::noFitPolygon(stat, orb); - else if(orbconvex) - subnfp = nfp::noFitPolygon(stat, orb); - else - subnfp = nfp::noFitPolygon(stat, orb); - - correctNfpPosition(subnfp, sh, trsh); - - nfps = nfp::merge(nfps, subnfp.first); - } - - return nfps; - - -// using Item = _Item; -// using sl = ShapeLike; - -// Nfp::Shapes nfps, stationary; - -// for(Item& sh : polygons) { -// stationary = Nfp::merge(stationary, sh.transformedShape()); -// } - -// for(RawShape& sh : stationary) { - -//// auto vv = sl::isValid(sh); -//// std::cout << vv.second << std::endl; - - -// Nfp::NfpResult subnfp; -// bool shconvex = sl::isConvex(sl::getContour(sh)); -// if(shconvex && trsh.isContourConvex()) { -// subnfp = Nfp::noFitPolygon( -// sh, trsh.transformedShape()); -// } else if(trsh.isContourConvex()) { -// subnfp = Nfp::noFitPolygon( -// sh, trsh.transformedShape()); -// } -// else { -// subnfp = Nfp::noFitPolygon( sh, -// trsh.transformedShape()); -// } - -// correctNfpPosition(subnfp, sh, trsh); - -// nfps = Nfp::merge(nfps, subnfp.first); -// } - -// return nfps; -} - -template -_Circle> minimizeCircle(const RawShape& sh) { +template> > +Circle minimizeCircle(const RawShape& sh) { using Point = TPoint; using Coord = TCoord; @@ -507,9 +483,19 @@ class _NofitPolyPlacer: public PlacerBoilerplate<_NofitPolyPlacer>; + using MaxNfpLevel = nfp::MaxNfpLevel; + + using ItemKeys = std::vector<__itemhash::Key>; + + // Norming factor for the optimization function const double norm_; - using MaxNfpLevel = nfp::MaxNfpLevel; + // Caching calculated nfps + __itemhash::Hash nfpcache_; + + // Storing item hash keys + ItemKeys item_keys_; + public: using Pile = nfp::Shapes; @@ -526,60 +512,290 @@ public: _NofitPolyPlacer& operator=(_NofitPolyPlacer&&) BP2D_NOEXCEPT = default; #endif - bool static inline wouldFit(const Box& bb, const RawShape& bin) { - auto bbin = sl::boundingBox(bin); + static inline double overfit(const Box& bb, const RawShape& bin) { + auto bbin = sl::boundingBox(bin); auto d = bbin.center() - bb.center(); _Rectangle rect(bb.width(), bb.height()); rect.translate(bb.minCorner() + d); - return sl::isInside(rect.transformedShape(), bin); + return sl::isInside(rect.transformedShape(), bin) ? -1.0 : 1; } - bool static inline wouldFit(const RawShape& chull, const RawShape& bin) { - auto bbch = sl::boundingBox(chull); - auto bbin = sl::boundingBox(bin); + static inline double overfit(const RawShape& chull, const RawShape& bin) { + auto bbch = sl::boundingBox(chull); + auto bbin = sl::boundingBox(bin); auto d = bbch.center() - bbin.center(); auto chullcpy = chull; sl::translate(chullcpy, d); - return sl::isInside(chullcpy, bin); + return sl::isInside(chullcpy, bin) ? -1.0 : 1.0; } - bool static inline wouldFit(const RawShape& chull, const Box& bin) + static inline double overfit(const RawShape& chull, const Box& bin) { - auto bbch = sl::boundingBox(chull); - return wouldFit(bbch, bin); + auto bbch = sl::boundingBox(chull); + return overfit(bbch, bin); } - bool static inline wouldFit(const Box& bb, const Box& bin) + static inline double overfit(const Box& bb, const Box& bin) { - return bb.width() <= bin.width() && bb.height() <= bin.height(); + auto wdiff = double(bb.width() - bin.width()); + auto hdiff = double(bb.height() - bin.height()); + double diff = 0; + if(wdiff > 0) diff += wdiff; + if(hdiff > 0) diff += hdiff; + return diff; } - bool static inline wouldFit(const Box& bb, const _Circle& bin) + static inline double overfit(const Box& bb, const _Circle& bin) { - - return sl::isInside(bb, bin); + double boxr = 0.5*pl::distance(bb.minCorner(), bb.maxCorner()); + double diff = boxr - bin.radius(); + return diff; } - bool static inline wouldFit(const RawShape& chull, + static inline double overfit(const RawShape& chull, const _Circle& bin) { - return boundingCircle(chull).radius() < bin.radius(); + double r = boundingCircle(chull).radius(); + double diff = r - bin.radius(); + return diff; } template> - PackResult trypack( + PackResult trypack(Item& item, + const Range& remaining = Range()) { + auto result = _trypack(item, remaining); + + // Experimental + // if(!result) repack(item, result); + + return result; + } + + ~_NofitPolyPlacer() { + clearItems(); + } + + inline void clearItems() { + finalAlign(bin_); + Base::clearItems(); + } + +private: + + using Shapes = TMultiShape; + using ItemRef = std::reference_wrapper; + using ItemWithHash = const std::pair; + + Shapes calcnfp(const ItemWithHash itsh, Lvl) + { + using namespace nfp; + + Shapes nfps; + const Item& trsh = itsh.first; + // nfps.reserve(polygons.size()); + +// unsigned idx = 0; + for(Item& sh : items_) { + +// auto ik = item_keys_[idx++] + itsh.second; +// auto fnd = nfpcache_.find(ik); + +// nfp::NfpResult subnfp_r; +// if(fnd == nfpcache_.end()) { + + auto subnfp_r = noFitPolygon( + sh.transformedShape(), trsh.transformedShape()); +// nfpcache_[ik] = subnfp_r; +// } else { +// subnfp_r = fnd->second; +// } + + correctNfpPosition(subnfp_r, sh, trsh); + + // nfps.emplace_back(subnfp_r.first); + nfps = nfp::merge(nfps, subnfp_r.first); + } + + // nfps = nfp::merge(nfps); + + return nfps; + } + + template + Shapes calcnfp( const ItemWithHash itsh, Level) + { // Function for arbitrary level of nfp implementation + using namespace nfp; + + Shapes nfps; + const Item& trsh = itsh.first; + + auto& orb = trsh.transformedShape(); + bool orbconvex = trsh.isContourConvex(); + + for(Item& sh : items_) { + nfp::NfpResult subnfp; + auto& stat = sh.transformedShape(); + + if(sh.isContourConvex() && orbconvex) + subnfp = nfp::noFitPolygon(stat, orb); + else if(orbconvex) + subnfp = nfp::noFitPolygon(stat, orb); + else + subnfp = nfp::noFitPolygon(stat, orb); + + correctNfpPosition(subnfp, sh, trsh); + + nfps = nfp::merge(nfps, subnfp.first); + } + + return nfps; + } + + // Very much experimental + void repack(Item& item, PackResult& result) { + + if((sl::area(bin_) - this->filledArea()) >= item.area()) { + auto prev_func = config_.object_function; + + unsigned iter = 0; + ItemGroup backup_rf = items_; + std::vector backup_cpy; + for(Item& itm : items_) backup_cpy.emplace_back(itm); + + auto ofn = [this, &item, &result, &iter, &backup_cpy, &backup_rf] + (double ratio) + { + auto& bin = bin_; + iter++; + config_.object_function = [bin, ratio]( + nfp::Shapes& pile, + const Item& item, + const ItemGroup& /*remaining*/) + { + pile.emplace_back(item.transformedShape()); + auto ch = sl::convexHull(pile); + auto pbb = sl::boundingBox(pile); + pile.pop_back(); + + double parea = 0.5*(sl::area(ch) + sl::area(pbb)); + + double pile_area = std::accumulate( + pile.begin(), pile.end(), item.area(), + [](double sum, const RawShape& sh){ + return sum + sl::area(sh); + }); + + // The pack ratio -- how much is the convex hull occupied + double pack_rate = (pile_area)/parea; + + // ratio of waste + double waste = 1.0 - pack_rate; + + // Score is the square root of waste. This will extend the + // range of good (lower) values and shrink the range of bad + // (larger) values. + auto wscore = std::sqrt(waste); + + + auto ibb = item.boundingBox(); + auto bbb = sl::boundingBox(bin); + auto c = ibb.center(); + double norm = 0.5*pl::distance(bbb.minCorner(), + bbb.maxCorner()); + + double dscore = pl::distance(c, pbb.center()) / norm; + + return ratio*wscore + (1.0 - ratio) * dscore; + }; + + auto bb = sl::boundingBox(bin); + double norm = bb.width() + bb.height(); + + auto items = items_; + clearItems(); + auto it = items.begin(); + while(auto pr = _trypack(*it++)) { + this->accept(pr); if(it == items.end()) break; + } + + auto count_diff = items.size() - items_.size(); + double score = count_diff; + + if(count_diff == 0) { + result = _trypack(item); + + if(result) { + std::cout << "Success" << std::endl; + score = 0.0; + } else { + score += result.overfit() / norm; + } + } else { + result = PackResult(); + items_ = backup_rf; + for(unsigned i = 0; i < items_.size(); i++) { + items_[i].get() = backup_cpy[i]; + } + } + + std::cout << iter << " repack result: " << score << " " + << ratio << " " << count_diff << std::endl; + + return score; + }; + + opt::StopCriteria stopcr; + stopcr.max_iterations = 30; + stopcr.stop_score = 1e-20; + opt::TOptimizer solver(stopcr); + solver.optimize_min(ofn, opt::initvals(0.5), + opt::bound(0.0, 1.0)); + + // optimize + config_.object_function = prev_func; + } + + } + + struct Optimum { + double relpos; + unsigned nfpidx; + int hidx; + Optimum(double pos, unsigned nidx): + relpos(pos), nfpidx(nidx), hidx(-1) {} + Optimum(double pos, unsigned nidx, int holeidx): + relpos(pos), nfpidx(nidx), hidx(holeidx) {} + }; + + class Optimizer: public opt::TOptimizer { + public: + Optimizer() { + opt::StopCriteria stopcr; + stopcr.max_iterations = 200; + stopcr.relative_score_difference = 1e-20; + this->stopcr_ = stopcr; + } + }; + + using Edges = EdgeCache; + + template> + PackResult _trypack( Item& item, const Range& remaining = Range()) { PackResult ret; bool can_pack = false; + double best_overfit = std::numeric_limits::max(); auto remlist = ItemGroup(remaining.from, remaining.to); + size_t itemhash = __itemhash::hash(item); if(items_.empty()) { setInitialPosition(item); - can_pack = item.isInside(bin_); + best_overfit = overfit(item.transformedShape(), bin_); + can_pack = best_overfit <= 0; } else { double global_score = std::numeric_limits::max(); @@ -588,7 +804,7 @@ public: auto initial_rot = item.rotation(); Vertex final_tr = {0, 0}; Radians final_rot = initial_rot; - nfp::Shapes nfps; + Shapes nfps; for(auto rot : config_.rotations) { @@ -596,17 +812,16 @@ public: item.rotation(initial_rot + rot); // place the new item outside of the print bed to make sure - // it is disjuct from the current merged pile + // it is disjunct from the current merged pile placeOutsideOfBin(item); - auto trsh = item.transformedShape(); + nfps = calcnfp({item, itemhash}, Lvl()); - nfps = calcnfp(items_, item, Lvl()); - auto iv = nfp::referenceVertex(trsh); + auto iv = item.referenceVertex(); auto startpos = item.translation(); - std::vector> ecache; + std::vector ecache; ecache.reserve(nfps.size()); for(auto& nfp : nfps ) { @@ -614,14 +829,54 @@ public: ecache.back().accuracy(config_.accuracy); } - struct Optimum { - double relpos; - unsigned nfpidx; - int hidx; - Optimum(double pos, unsigned nidx): - relpos(pos), nfpidx(nidx), hidx(-1) {} - Optimum(double pos, unsigned nidx, int holeidx): - relpos(pos), nfpidx(nidx), hidx(holeidx) {} + Shapes pile; + pile.reserve(items_.size()+1); + // double pile_area = 0; + for(Item& mitem : items_) { + pile.emplace_back(mitem.transformedShape()); + // pile_area += mitem.area(); + } + + auto merged_pile = nfp::merge(pile); + auto& bin = bin_; + double norm = norm_; + + // This is the kernel part of the object function that is + // customizable by the library client + auto _objfunc = config_.object_function? + config_.object_function : + [norm, /*pile_area,*/ bin, merged_pile]( + const Pile& /*pile*/, + const Item& item, + const ItemGroup& /*remaining*/) + { + auto ibb = item.boundingBox(); + auto binbb = sl::boundingBox(bin); + auto mp = merged_pile; + mp.emplace_back(item.transformedShape()); + auto fullbb = sl::boundingBox(mp); + + double score = pl::distance(ibb.center(), binbb.center()); + score /= norm; + + double miss = overfit(fullbb, bin); + miss = miss > 0? miss : 0; + score += std::pow(miss, 2); + + return score; + }; + + // Our object function for placement + auto rawobjfunc = + [item, _objfunc, iv, + startpos, remlist, pile] (Vertex v) + { + auto d = v - iv; + d += startpos; + Item itm = item; + itm.translation(d); + + return _objfunc(pile, itm, remlist); }; auto getNfpPoint = [&ecache](const Optimum& opt) @@ -630,58 +885,10 @@ public: ecache[opt.nfpidx].coords(opt.hidx, opt.relpos); }; - nfp::Shapes pile; - pile.reserve(items_.size()+1); - double pile_area = 0; - for(Item& mitem : items_) { - pile.emplace_back(mitem.transformedShape()); - pile_area += mitem.area(); - } - - auto merged_pile = nfp::merge(pile); - - // This is the kernel part of the object function that is - // customizable by the library client - auto _objfunc = config_.object_function? - config_.object_function : - [this, &merged_pile, &pile_area]( - nfp::Shapes& /*pile*/, - const Item& item, - const ItemGroup& /*remaining*/) + auto boundaryCheck = + [&merged_pile, &getNfpPoint, &item, &bin, &iv, &startpos] + (const Optimum& o) { - merged_pile.emplace_back(item.transformedShape()); - auto ch = sl::convexHull(merged_pile); - merged_pile.pop_back(); - - // The pack ratio -- how much is the convex hull occupied - double pack_rate = (pile_area + item.area())/sl::area(ch); - - // ratio of waste - double waste = 1.0 - pack_rate; - - // Score is the square root of waste. This will extend the - // range of good (lower) values and shring the range of bad - // (larger) values. - auto score = std::sqrt(waste); - - if(!wouldFit(ch, bin_)) score += norm_; - - return score; - }; - - // Our object function for placement - auto rawobjfunc = [&] (Vertex v) - { - auto d = v - iv; - d += startpos; - item.translation(d); - - double score = _objfunc(pile, item, remlist); - - return score; - }; - - auto boundaryCheck = [&](const Optimum& o) { auto v = getNfpPoint(o); auto d = v - iv; d += startpos; @@ -691,84 +898,111 @@ public: auto chull = sl::convexHull(merged_pile); merged_pile.pop_back(); - return wouldFit(chull, bin_); + return overfit(chull, bin); }; - opt::StopCriteria stopcr; - stopcr.max_iterations = 200; - stopcr.relative_score_difference = 1e-20; - opt::TOptimizer solver(stopcr); - Optimum optimum(0, 0); double best_score = std::numeric_limits::max(); + std::launch policy = std::launch::deferred; + if(config_.parallel) policy |= std::launch::async; + + using OptResult = opt::Result; + using OptResults = std::vector; // Local optimization with the four polygon corners as // starting points for(unsigned ch = 0; ch < ecache.size(); ch++) { auto& cache = ecache[ch]; - auto contour_ofn = [&rawobjfunc, &getNfpPoint, ch] + auto contour_ofn = [rawobjfunc, getNfpPoint, ch] (double relpos) { return rawobjfunc(getNfpPoint(Optimum(relpos, ch))); }; - std::for_each(cache.corners().begin(), - cache.corners().end(), - [ch, &contour_ofn, &solver, &best_score, - &optimum, &boundaryCheck] (double pos) + OptResults results(cache.corners().size()); + + __parallel::enumerate( + cache.corners().begin(), + cache.corners().end(), + [&contour_ofn, &results] + (double pos, unsigned n) { + Optimizer solver; try { - auto result = solver.optimize_min(contour_ofn, + results[n] = solver.optimize_min(contour_ofn, opt::initvals(pos), opt::bound(0, 1.0) ); - - if(result.score < best_score) { - Optimum o(std::get<0>(result.optimum), ch, -1); - if(boundaryCheck(o)) { - best_score = result.score; - optimum = o; - } - } } catch(std::exception& e) { derr() << "ERROR: " << e.what() << "\n"; } - }); + }, policy); + + auto resultcomp = + []( const OptResult& r1, const OptResult& r2 ) { + return r1.score < r2.score; + }; + + auto mr = *std::min_element(results.begin(), results.end(), + resultcomp); + + if(mr.score < best_score) { + Optimum o(std::get<0>(mr.optimum), ch, -1); + double miss = boundaryCheck(o); + if(miss <= 0) { + best_score = mr.score; + optimum = o; + } else { + best_overfit = std::min(miss, best_overfit); + } + } for(unsigned hidx = 0; hidx < cache.holeCount(); ++hidx) { auto hole_ofn = - [&rawobjfunc, &getNfpPoint, ch, hidx] + [rawobjfunc, getNfpPoint, ch, hidx] (double pos) { Optimum opt(pos, ch, hidx); return rawobjfunc(getNfpPoint(opt)); }; - std::for_each(cache.corners(hidx).begin(), + results.clear(); + results.resize(cache.corners(hidx).size()); + + // TODO : use parallel for + __parallel::enumerate(cache.corners(hidx).begin(), cache.corners(hidx).end(), - [&hole_ofn, &solver, &best_score, - &optimum, ch, hidx, &boundaryCheck] - (double pos) + [&hole_ofn, &results] + (double pos, unsigned n) { + Optimizer solver; try { - auto result = solver.optimize_min(hole_ofn, + results[n] = solver.optimize_min(hole_ofn, opt::initvals(pos), opt::bound(0, 1.0) ); - if(result.score < best_score) { - Optimum o(std::get<0>(result.optimum), - ch, hidx); - if(boundaryCheck(o)) { - best_score = result.score; - optimum = o; - } - } } catch(std::exception& e) { derr() << "ERROR: " << e.what() << "\n"; } - }); + }, policy); + + auto hmr = *std::min_element(results.begin(), + results.end(), + resultcomp); + + if(hmr.score < best_score) { + Optimum o(std::get<0>(hmr.optimum), + ch, hidx); + double miss = boundaryCheck(o); + if(miss <= 0.0) { + best_score = hmr.score; + optimum = o; + } else { + best_overfit = std::min(miss, best_overfit); + } + } } } @@ -788,22 +1022,14 @@ public: if(can_pack) { ret = PackResult(item); + item_keys_.emplace_back(itemhash); + } else { + ret = PackResult(best_overfit); } return ret; } - ~_NofitPolyPlacer() { - clearItems(); - } - - inline void clearItems() { - finalAlign(bin_); - Base::clearItems(); - } - -private: - inline void finalAlign(const RawShape& pbin) { auto bbin = sl::boundingBox(pbin); finalAlign(bbin); @@ -826,7 +1052,7 @@ private: nfp::Shapes m; m.reserve(items_.size()); for(Item& item : items_) m.emplace_back(item.transformedShape()); - auto&& bb = sl::boundingBox(m); + auto&& bb = sl::boundingBox(m); Vertex ci, cb; diff --git a/xs/src/libnest2d/libnest2d/placers/placer_boilerplate.hpp b/xs/src/libnest2d/libnest2d/placers/placer_boilerplate.hpp index 1a0730d88..44e2bc1b0 100644 --- a/xs/src/libnest2d/libnest2d/placers/placer_boilerplate.hpp +++ b/xs/src/libnest2d/libnest2d/placers/placer_boilerplate.hpp @@ -26,15 +26,21 @@ public: Item *item_ptr_; Vertex move_; Radians rot_; + double overfit_; friend class PlacerBoilerplate; friend Subclass; + PackResult(Item& item): item_ptr_(&item), move_(item.translation()), rot_(item.rotation()) {} - PackResult(): item_ptr_(nullptr) {} + + PackResult(double overfit = 1.0): + item_ptr_(nullptr), overfit_(overfit) {} + public: operator bool() { return item_ptr_ != nullptr; } + double overfit() const { return overfit_; } }; inline PlacerBoilerplate(const BinType& bin, unsigned cap = 50): bin_(bin) @@ -82,9 +88,6 @@ public: inline void clearItems() { items_.clear(); farea_valid_ = false; -#ifndef NDEBUG - debug_items_.clear(); -#endif } inline double filledArea() const { @@ -101,10 +104,6 @@ public: return farea_; } -#ifndef NDEBUG - std::vector debug_items_; -#endif - protected: BinType bin_; diff --git a/xs/src/libnest2d/libnest2d/selections/djd_heuristic.hpp b/xs/src/libnest2d/libnest2d/selections/djd_heuristic.hpp index 8c02dc373..846b00bad 100644 --- a/xs/src/libnest2d/libnest2d/selections/djd_heuristic.hpp +++ b/xs/src/libnest2d/libnest2d/selections/djd_heuristic.hpp @@ -493,8 +493,7 @@ public: std::array candidates = {it, it2, it3}; - auto tryPack = [&placer, &candidates, ¬_packed, - &pack]( + auto tryPack = [&placer, &candidates, &pack]( const decltype(indices)& idx) { std::array packed = {false}; @@ -569,11 +568,7 @@ public: { packed_bins_[idx] = placer.getItems(); -#ifndef NDEBUG - packed_bins_[idx].insert(packed_bins_[idx].end(), - placer.getDebugItems().begin(), - placer.getDebugItems().end()); -#endif + // TODO here should be a spinlock slock.lock(); acounter -= packednum; diff --git a/xs/src/libnest2d/libnest2d/selections/firstfit.hpp b/xs/src/libnest2d/libnest2d/selections/firstfit.hpp index 1312f9874..eb820a518 100644 --- a/xs/src/libnest2d/libnest2d/selections/firstfit.hpp +++ b/xs/src/libnest2d/libnest2d/selections/firstfit.hpp @@ -68,13 +68,13 @@ public: } auto it = store_.begin(); + while(it != store_.end()) { bool was_packed = false; + size_t j = 0; while(!was_packed) { - - for(size_t j = 0; j < placers.size() && !was_packed; j++) { - if((was_packed = - placers[j].pack(*it, rem(it, store_) ))) + for(; j < placers.size() && !was_packed; j++) { + if((was_packed = placers[j].pack(*it, rem(it, store_) ))) makeProgress(placers[j], j); } @@ -82,6 +82,7 @@ public: placers.emplace_back(bin); placers.back().configure(pconfig); packed_bins_.emplace_back(); + j = placers.size() - 1; } } ++it; diff --git a/xs/src/libnest2d/tests/test.cpp b/xs/src/libnest2d/tests/test.cpp index 197ff6598..b85bbc111 100644 --- a/xs/src/libnest2d/tests/test.cpp +++ b/xs/src/libnest2d/tests/test.cpp @@ -5,6 +5,7 @@ #include "printer_parts.h" #include //#include "../tools/libnfpglue.hpp" +//#include "../tools/nfp_svgnest_glue.hpp" std::vector& prusaParts() { static std::vector ret; @@ -219,21 +220,21 @@ TEST(GeometryAlgorithms, IsPointInsidePolygon) { Point p = {1, 1}; - ASSERT_TRUE(rect.isPointInside(p)); + ASSERT_TRUE(rect.isInside(p)); p = {11, 11}; - ASSERT_FALSE(rect.isPointInside(p)); + ASSERT_FALSE(rect.isInside(p)); p = {11, 12}; - ASSERT_FALSE(rect.isPointInside(p)); + ASSERT_FALSE(rect.isInside(p)); p = {3, 3}; - ASSERT_TRUE(rect.isPointInside(p)); + ASSERT_TRUE(rect.isInside(p)); } diff --git a/xs/src/libnest2d/tools/libnfpglue.cpp b/xs/src/libnest2d/tools/libnfpglue.cpp index 18656fd40..31733acf9 100644 --- a/xs/src/libnest2d/tools/libnfpglue.cpp +++ b/xs/src/libnest2d/tools/libnfpglue.cpp @@ -56,7 +56,7 @@ libnfporb::point_t scale(const libnfporb::point_t& p, long double factor) { NfpR _nfp(const PolygonImpl &sh, const PolygonImpl &cother) { - using Vertex = PointImpl; + namespace sl = shapelike; NfpR ret; @@ -85,7 +85,7 @@ NfpR _nfp(const PolygonImpl &sh, const PolygonImpl &cother) // this can throw auto nfp = libnfporb::generateNFP(pstat, porb, true); - auto &ct = ShapeLike::getContour(ret.first); + auto &ct = sl::getContour(ret.first); ct.reserve(nfp.front().size()+1); for(auto v : nfp.front()) { v = scale(v, refactor); @@ -94,7 +94,7 @@ NfpR _nfp(const PolygonImpl &sh, const PolygonImpl &cother) ct.push_back(ct.front()); std::reverse(ct.begin(), ct.end()); - auto &rholes = ShapeLike::holes(ret.first); + auto &rholes = sl::holes(ret.first); for(size_t hidx = 1; hidx < nfp.size(); ++hidx) { if(nfp[hidx].size() >= 3) { rholes.emplace_back(); @@ -110,31 +110,31 @@ NfpR _nfp(const PolygonImpl &sh, const PolygonImpl &cother) } } - ret.second = Nfp::referenceVertex(ret.first); + ret.second = nfp::referenceVertex(ret.first); } catch(std::exception& e) { std::cout << "Error: " << e.what() << "\nTrying with convex hull..." << std::endl; // auto ch_stat = ShapeLike::convexHull(sh); // auto ch_orb = ShapeLike::convexHull(cother); - ret = Nfp::nfpConvexOnly(sh, cother); + ret = nfp::nfpConvexOnly(sh, cother); } return ret; } -NfpR Nfp::NfpImpl::operator()( +NfpR nfp::NfpImpl::operator()( const PolygonImpl &sh, const ClipperLib::PolygonImpl &cother) { return _nfp(sh, cother);//nfpConvexOnly(sh, cother); } -NfpR Nfp::NfpImpl::operator()( +NfpR nfp::NfpImpl::operator()( const PolygonImpl &sh, const ClipperLib::PolygonImpl &cother) { return _nfp(sh, cother); } -NfpR Nfp::NfpImpl::operator()( +NfpR nfp::NfpImpl::operator()( const PolygonImpl &sh, const ClipperLib::PolygonImpl &cother) { return _nfp(sh, cother); diff --git a/xs/src/libnest2d/tools/libnfpglue.hpp b/xs/src/libnest2d/tools/libnfpglue.hpp index 75f639445..1ff033cb9 100644 --- a/xs/src/libnest2d/tools/libnfpglue.hpp +++ b/xs/src/libnest2d/tools/libnfpglue.hpp @@ -5,22 +5,22 @@ namespace libnest2d { -using NfpR = Nfp::NfpResult; +using NfpR = nfp::NfpResult; NfpR _nfp(const PolygonImpl& sh, const PolygonImpl& cother); template<> -struct Nfp::NfpImpl { +struct nfp::NfpImpl { NfpR operator()(const PolygonImpl& sh, const PolygonImpl& cother); }; template<> -struct Nfp::NfpImpl { +struct nfp::NfpImpl { NfpR operator()(const PolygonImpl& sh, const PolygonImpl& cother); }; template<> -struct Nfp::NfpImpl { +struct nfp::NfpImpl { NfpR operator()(const PolygonImpl& sh, const PolygonImpl& cother); }; @@ -34,7 +34,7 @@ struct Nfp::NfpImpl { // NfpResult operator()(const PolygonImpl& sh, const PolygonImpl& cother); //}; -template<> struct Nfp::MaxNfpLevel { +template<> struct nfp::MaxNfpLevel { static const BP2D_CONSTEXPR NfpLevel value = // NfpLevel::CONVEX_ONLY; NfpLevel::BOTH_CONCAVE; diff --git a/xs/src/libnest2d/tools/nfp_svgnest.hpp b/xs/src/libnest2d/tools/nfp_svgnest.hpp new file mode 100644 index 000000000..8ab571c00 --- /dev/null +++ b/xs/src/libnest2d/tools/nfp_svgnest.hpp @@ -0,0 +1,1004 @@ +#ifndef NFP_SVGNEST_HPP +#define NFP_SVGNEST_HPP + +#include +#include + +#include + +namespace libnest2d { + +namespace __svgnest { + +using std::sqrt; +using std::min; +using std::max; +using std::abs; +using std::isnan; + +//template struct _Scale { +// static const BP2D_CONSTEXPR long long Value = 1000000; +//}; + +template struct _alg { + using Contour = TContour; + using Point = TPoint; + using iCoord = TCoord; + using Coord = double; + using Shapes = nfp::Shapes; + + static const Coord TOL; + +#define dNAN std::nan("") + + struct Vector { + Coord x, y; + bool marked = false; + Vector() = default; + Vector(Coord X, Coord Y): x(X), y(Y) {} + Vector(const Point& p): x(getX(p)), y(getY(p)) {} + operator Point() const { return {iCoord(x), iCoord(y)}; } + Vector& operator=(const Point& p) { + x = getX(p), y = getY(p); return *this; + } + Vector(std::initializer_list il): + x(*il.begin()), y(*std::next(il.begin())) {} + }; + + static inline Coord x(const Point& p) { return Coord(getX(p)); } + static inline Coord y(const Point& p) { return Coord(getY(p)); } + + static inline Coord x(const Vector& p) { return p.x; } + static inline Coord y(const Vector& p) { return p.y; } + + class Cntr { + std::vector v_; + public: + Cntr(const Contour& c) { + v_.reserve(c.size()); + std::transform(c.begin(), c.end(), std::back_inserter(v_), + [](const Point& p) { + return Vector(double(x(p))/1e6, double(y(p))/1e6); + }); + } + Cntr() = default; + + Coord offsetx = 0; + Coord offsety = 0; + size_t size() const { return v_.size(); } + bool empty() const { return v_.empty(); } + typename Contour::const_iterator begin() const { return v_.cbegin(); } + typename Contour::const_iterator end() const { return v_.cend(); } + Vector& operator[](size_t idx) { return v_[idx]; } + const Vector& operator[](size_t idx) const { return v_[idx]; } + template + void emplace_back(Args&&...args) { + v_.emplace_back(std::forward(args)...); + } + template + void push(Args&&...args) { + v_.emplace_back(std::forward(args)...); + } + void clear() { v_.clear(); } + + operator Contour() const { + Contour cnt; + cnt.reserve(v_.size()); + std::transform(v_.begin(), v_.end(), std::back_inserter(cnt), + [](const Vector& vertex) { + return Point(iCoord(vertex.x*1e6), iCoord(vertex.y*1e6)); + }); + return cnt; + } + }; + + inline static bool _almostEqual(Coord a, Coord b, + Coord tolerance = TOL) + { + return std::abs(a - b) < tolerance; + } + + // returns true if p lies on the line segment defined by AB, + // but not at any endpoints may need work! + static bool _onSegment(const Vector& A, const Vector& B, const Vector& p) { + + // vertical line + if(_almostEqual(A.x, B.x) && _almostEqual(p.x, A.x)) { + if(!_almostEqual(p.y, B.y) && !_almostEqual(p.y, A.y) && + p.y < max(B.y, A.y) && p.y > min(B.y, A.y)){ + return true; + } + else{ + return false; + } + } + + // horizontal line + if(_almostEqual(A.y, B.y) && _almostEqual(p.y, A.y)){ + if(!_almostEqual(p.x, B.x) && !_almostEqual(p.x, A.x) && + p.x < max(B.x, A.x) && p.x > min(B.x, A.x)){ + return true; + } + else{ + return false; + } + } + + //range check + if((p.x < A.x && p.x < B.x) || (p.x > A.x && p.x > B.x) || + (p.y < A.y && p.y < B.y) || (p.y > A.y && p.y > B.y)) + return false; + + // exclude end points + if((_almostEqual(p.x, A.x) && _almostEqual(p.y, A.y)) || + (_almostEqual(p.x, B.x) && _almostEqual(p.y, B.y))) + return false; + + + double cross = (p.y - A.y) * (B.x - A.x) - (p.x - A.x) * (B.y - A.y); + + if(abs(cross) > TOL) return false; + + double dot = (p.x - A.x) * (B.x - A.x) + (p.y - A.y)*(B.y - A.y); + + if(dot < 0 || _almostEqual(dot, 0)) return false; + + double len2 = (B.x - A.x)*(B.x - A.x) + (B.y - A.y)*(B.y - A.y); + + if(dot > len2 || _almostEqual(dot, len2)) return false; + + return true; + } + + // return true if point is in the polygon, false if outside, and null if exactly on a point or edge + static int pointInPolygon(const Vector& point, const Cntr& polygon) { + if(polygon.size() < 3){ + return 0; + } + + bool inside = false; + Coord offsetx = polygon.offsetx; + Coord offsety = polygon.offsety; + + for (size_t i = 0, j = polygon.size() - 1; i < polygon.size(); j=i++) { + auto xi = polygon[i].x + offsetx; + auto yi = polygon[i].y + offsety; + auto xj = polygon[j].x + offsetx; + auto yj = polygon[j].y + offsety; + + if(_almostEqual(xi, point.x) && _almostEqual(yi, point.y)){ + return 0; // no result + } + + if(_onSegment({xi, yi}, {xj, yj}, point)){ + return 0; // exactly on the segment + } + + if(_almostEqual(xi, xj) && _almostEqual(yi, yj)){ // ignore very small lines + continue; + } + + bool intersect = ((yi > point.y) != (yj > point.y)) && + (point.x < (xj - xi) * (point.y - yi) / (yj - yi) + xi); + if (intersect) inside = !inside; + } + + return inside? 1 : -1; + } + + static bool intersect(const Cntr& A, const Cntr& B){ + Contour a = A, b = B; + return shapelike::intersects(shapelike::create(a), shapelike::create(b)); + } + + static Vector _normalizeVector(const Vector& v) { + if(_almostEqual(v.x*v.x + v.y*v.y, Coord(1))){ + return Point(v); // given vector was already a unit vector + } + auto len = sqrt(v.x*v.x + v.y*v.y); + auto inverse = 1/len; + + return { Coord(v.x*inverse), Coord(v.y*inverse) }; + } + + static double pointDistance( const Vector& p, + const Vector& s1, + const Vector& s2, + Vector normal, + bool infinite = false) + { + normal = _normalizeVector(normal); + + Vector dir = { + normal.y, + -normal.x + }; + + auto pdot = p.x*dir.x + p.y*dir.y; + auto s1dot = s1.x*dir.x + s1.y*dir.y; + auto s2dot = s2.x*dir.x + s2.y*dir.y; + + auto pdotnorm = p.x*normal.x + p.y*normal.y; + auto s1dotnorm = s1.x*normal.x + s1.y*normal.y; + auto s2dotnorm = s2.x*normal.x + s2.y*normal.y; + + if(!infinite){ + if (((pdots1dot || _almostEqual(pdot, s1dot)) && + (pdot>s2dot || _almostEqual(pdot, s2dot)))) + { + // dot doesn't collide with segment, + // or lies directly on the vertex + return dNAN; + } + if ((_almostEqual(pdot, s1dot) && _almostEqual(pdot, s2dot)) && + (pdotnorm>s1dotnorm && pdotnorm>s2dotnorm)) + { + return double(min(pdotnorm - s1dotnorm, pdotnorm - s2dotnorm)); + } + if ((_almostEqual(pdot, s1dot) && _almostEqual(pdot, s2dot)) && + (pdotnorm EFmax){ + return dNAN; + } + + double overlap = 0; + + if((ABmax > EFmax && ABmin < EFmin) || (EFmax > ABmax && EFmin < ABmin)) + { + overlap = 1; + } + else{ + auto minMax = min(ABmax, EFmax); + auto maxMin = max(ABmin, EFmin); + + auto maxMax = max(ABmax, EFmax); + auto minMin = min(ABmin, EFmin); + + overlap = (minMax-maxMin)/(maxMax-minMin); + } + + auto crossABE = (E.y - A.y) * (B.x - A.x) - (E.x - A.x) * (B.y - A.y); + auto crossABF = (F.y - A.y) * (B.x - A.x) - (F.x - A.x) * (B.y - A.y); + + // lines are colinear + if(_almostEqual(crossABE,0) && _almostEqual(crossABF,0)){ + + Vector ABnorm = {B.y-A.y, A.x-B.x}; + Vector EFnorm = {F.y-E.y, E.x-F.x}; + + auto ABnormlength = sqrt(ABnorm.x*ABnorm.x + ABnorm.y*ABnorm.y); + ABnorm.x /= ABnormlength; + ABnorm.y /= ABnormlength; + + auto EFnormlength = sqrt(EFnorm.x*EFnorm.x + EFnorm.y*EFnorm.y); + EFnorm.x /= EFnormlength; + EFnorm.y /= EFnormlength; + + // segment normals must point in opposite directions + if(abs(ABnorm.y * EFnorm.x - ABnorm.x * EFnorm.y) < TOL && + ABnorm.y * EFnorm.y + ABnorm.x * EFnorm.x < 0){ + // normal of AB segment must point in same direction as + // given direction vector + auto normdot = ABnorm.y * direction.y + ABnorm.x * direction.x; + // the segments merely slide along eachother + if(_almostEqual(normdot,0, TOL)){ + return dNAN; + } + if(normdot < 0){ + return 0.0; + } + } + return dNAN; + } + + std::vector distances; distances.reserve(10); + + // coincident points + if(_almostEqual(dotA, dotE)){ + distances.emplace_back(crossA-crossE); + } + else if(_almostEqual(dotA, dotF)){ + distances.emplace_back(crossA-crossF); + } + else if(dotA > EFmin && dotA < EFmax){ + auto d = pointDistance(A,E,F,reverse); + if(!isnan(d) && _almostEqual(d, 0)) + { // A currently touches EF, but AB is moving away from EF + auto dB = pointDistance(B,E,F,reverse,true); + if(dB < 0 || _almostEqual(dB*overlap,0)){ + d = dNAN; + } + } + if(isnan(d)){ + distances.emplace_back(d); + } + } + + if(_almostEqual(dotB, dotE)){ + distances.emplace_back(crossB-crossE); + } + else if(_almostEqual(dotB, dotF)){ + distances.emplace_back(crossB-crossF); + } + else if(dotB > EFmin && dotB < EFmax){ + auto d = pointDistance(B,E,F,reverse); + + if(!isnan(d) && _almostEqual(d, 0)) + { // crossA>crossB A currently touches EF, but AB is moving away from EF + double dA = pointDistance(A,E,F,reverse,true); + if(dA < 0 || _almostEqual(dA*overlap,0)){ + d = dNAN; + } + } + if(!isnan(d)){ + distances.emplace_back(d); + } + } + + if(dotE > ABmin && dotE < ABmax){ + auto d = pointDistance(E,A,B,direction); + if(!isnan(d) && _almostEqual(d, 0)) + { // crossF ABmin && dotF < ABmax){ + auto d = pointDistance(F,A,B,direction); + if(!isnan(d) && _almostEqual(d, 0)) + { // && crossE 0 || _almostEqual(d, 0)){ + distance = d; + } + } + } + } + return distance; + } + + static double polygonProjectionDistance(const Cntr& A, + const Cntr& B, + Vector direction) + { + auto Boffsetx = B.offsetx; + auto Boffsety = B.offsety; + auto Aoffsetx = A.offsetx; + auto Aoffsety = A.offsety; + + // close the loop for polygons + /*if(A[0] != A[A.length-1]){ + A.push(A[0]); + } + + if(B[0] != B[B.length-1]){ + B.push(B[0]); + }*/ + + auto& edgeA = A; + auto& edgeB = B; + + double distance = dNAN, d; +// Vector p, s1, s2; + + for(size_t i = 0; i < edgeB.size(); i++) { + // the shortest/most negative projection of B onto A + double minprojection = dNAN; + Vector minp; + for(size_t j = 0; j < edgeA.size() - 1; j++){ + Vector p = {x(edgeB[i]) + Boffsetx, y(edgeB[i]) + Boffsety }; + Vector s1 = {x(edgeA[j]) + Aoffsetx, y(edgeA[j]) + Aoffsety }; + Vector s2 = {x(edgeA[j+1]) + Aoffsetx, y(edgeA[j+1]) + Aoffsety }; + + if(abs((s2.y-s1.y) * direction.x - + (s2.x-s1.x) * direction.y) < TOL) continue; + + // project point, ignore edge boundaries + d = pointDistance(p, s1, s2, direction); + + if(!isnan(d) && (isnan(minprojection) || d < minprojection)) { + minprojection = d; + minp = p; + } + } + + if(!isnan(minprojection) && (isnan(distance) || + minprojection > distance)){ + distance = minprojection; + } + } + + return distance; + } + + static std::pair searchStartPoint( + const Cntr& AA, const Cntr& BB, bool inside, const std::vector& NFP = {}) + { + // clone arrays + auto A = AA; + auto B = BB; + +// // close the loop for polygons +// if(A[0] != A[A.size()-1]){ +// A.push(A[0]); +// } + +// if(B[0] != B[B.size()-1]){ +// B.push(B[0]); +// } + + // returns true if point already exists in the given nfp + auto inNfp = [](const Vector& p, const std::vector& nfp){ + if(nfp.empty()){ + return false; + } + + for(size_t i=0; i < nfp.size(); i++){ + for(size_t j = 0; j< nfp[i].size(); j++){ + if(_almostEqual(p.x, nfp[i][j].x) && + _almostEqual(p.y, nfp[i][j].y)){ + return true; + } + } + } + + return false; + }; + + for(size_t i = 0; i < A.size() - 1; i++){ + if(!A[i].marked) { + A[i].marked = true; + for(size_t j = 0; j < B.size(); j++){ + B.offsetx = A[i].x - B[j].x; + B.offsety = A[i].y - B[j].y; + + int Binside = 0; + for(size_t k = 0; k < B.size(); k++){ + int inpoly = pointInPolygon({B[k].x + B.offsetx, B[k].y + B.offsety}, A); + if(inpoly != 0){ + Binside = inpoly; + break; + } + } + + if(Binside == 0){ // A and B are the same + return {false, {}}; + } + + auto startPoint = std::make_pair(true, Vector(B.offsetx, B.offsety)); + if(((Binside && inside) || (!Binside && !inside)) && + !intersect(A,B) && !inNfp(startPoint.second, NFP)){ + return startPoint; + } + + // slide B along vector + auto vx = A[i+1].x - A[i].x; + auto vy = A[i+1].y - A[i].y; + + double d1 = polygonProjectionDistance(A,B,{vx, vy}); + double d2 = polygonProjectionDistance(B,A,{-vx, -vy}); + + double d = dNAN; + + // todo: clean this up + if(isnan(d1) && isnan(d2)){ + // nothin + } + else if(isnan(d1)){ + d = d2; + } + else if(isnan(d2)){ + d = d1; + } + else{ + d = min(d1,d2); + } + + // only slide until no longer negative + // todo: clean this up + if(!isnan(d) && !_almostEqual(d,0) && d > 0){ + + } + else{ + continue; + } + + auto vd2 = vx*vx + vy*vy; + + if(d*d < vd2 && !_almostEqual(d*d, vd2)){ + auto vd = sqrt(vx*vx + vy*vy); + vx *= d/vd; + vy *= d/vd; + } + + B.offsetx += vx; + B.offsety += vy; + + for(size_t k = 0; k < B.size(); k++){ + int inpoly = pointInPolygon({B[k].x + B.offsetx, B[k].y + B.offsety}, A); + if(inpoly != 0){ + Binside = inpoly; + break; + } + } + startPoint = std::make_pair(true, Vector{B.offsetx, B.offsety}); + if(((Binside && inside) || (!Binside && !inside)) && + !intersect(A,B) && !inNfp(startPoint.second, NFP)){ + return startPoint; + } + } + } + } + + return {false, Vector(0, 0)}; + } + + static std::vector noFitPolygon(Cntr A, + Cntr B, + bool inside, + bool searchEdges) + { + if(A.size() < 3 || B.size() < 3) { + throw GeometryException(GeomErr::NFP); + return {}; + } + + A.offsetx = 0; + A.offsety = 0; + + unsigned i = 0, j = 0; + + auto minA = y(A[0]); + long minAindex = 0; + + auto maxB = y(B[0]); + long maxBindex = 0; + + for(i = 1; i < A.size(); i++){ + A[i].marked = false; + if(y(A[i]) < minA){ + minA = y(A[i]); + minAindex = i; + } + } + + for(i = 1; i < B.size(); i++){ + B[i].marked = false; + if(y(B[i]) > maxB){ + maxB = y(B[i]); + maxBindex = i; + } + } + + std::pair startpoint; + + if(!inside){ + // shift B such that the bottom-most point of B is at the top-most + // point of A. This guarantees an initial placement with no + // intersections + startpoint = { true, + { x(A[minAindex]) - x(B[maxBindex]), + y(A[minAindex]) - y(B[maxBindex]) } + }; + } + else { + // no reliable heuristic for inside + startpoint = searchStartPoint(A, B, true); + } + + std::vector NFPlist; + + struct Touch { + int type; + long A, B; + Touch(int t, long a, long b): type(t), A(a), B(b) {} + }; + + while(startpoint.first) { + + B.offsetx = startpoint.second.x; + B.offsety = startpoint.second.y; + + // maintain a list of touching points/edges + std::vector touching; + + Cntr NFP; + NFP.emplace_back(x(B[0]) + B.offsetx, y(B[0]) + B.offsety); + + auto referencex = x(B[0]) + B.offsetx; + auto referencey = y(B[0]) + B.offsety; + auto startx = referencex; + auto starty = referencey; + unsigned counter = 0; + + // sanity check, prevent infinite loop + while(counter < 10*(A.size() + B.size())){ + touching.clear(); + + // find touching vertices/edges + for(i = 0; i < A.size(); i++){ + auto nexti = (i == A.size() - 1) ? 0 : i + 1; + for(j = 0; j < B.size(); j++){ + + auto nextj = (j == B.size() - 1) ? 0 : j + 1; + + if( _almostEqual(A[i].x, B[j].x+B.offsetx) && + _almostEqual(A[i].y, B[j].y+B.offsety)) + { + touching.emplace_back(0, i, j); + } + else if( _onSegment( + A[i], A[nexti], + { B[j].x+B.offsetx, B[j].y + B.offsety}) ) + { + touching.emplace_back(1, nexti, j); + } + else if( _onSegment( + {B[j].x+B.offsetx, B[j].y + B.offsety}, + {B[nextj].x+B.offsetx, B[nextj].y + B.offsety}, + A[i]) ) + { + touching.emplace_back(2, i, nextj); + } + } + } + + struct V { + Coord x, y; + Vector *start, *end; + operator bool() { + return start != nullptr && end != nullptr; + } + operator Vector() const { return {x, y}; } + }; + + // generate translation vectors from touching vertices/edges + std::vector vectors; + for(i=0; i < touching.size(); i++){ + auto& vertexA = A[touching[i].A]; + vertexA.marked = true; + + // adjacent A vertices + auto prevAindex = touching[i].A - 1; + auto nextAindex = touching[i].A + 1; + + prevAindex = (prevAindex < 0) ? A.size() - 1 : prevAindex; // loop + nextAindex = (nextAindex >= A.size()) ? 0 : nextAindex; // loop + + auto& prevA = A[prevAindex]; + auto& nextA = A[nextAindex]; + + // adjacent B vertices + auto& vertexB = B[touching[i].B]; + + auto prevBindex = touching[i].B-1; + auto nextBindex = touching[i].B+1; + + prevBindex = (prevBindex < 0) ? B.size() - 1 : prevBindex; // loop + nextBindex = (nextBindex >= B.size()) ? 0 : nextBindex; // loop + + auto& prevB = B[prevBindex]; + auto& nextB = B[nextBindex]; + + if(touching[i].type == 0){ + + V vA1 = { + prevA.x - vertexA.x, + prevA.y - vertexA.y, + &vertexA, + &prevA + }; + + V vA2 = { + nextA.x - vertexA.x, + nextA.y - vertexA.y, + &vertexA, + &nextA + }; + + // B vectors need to be inverted + V vB1 = { + vertexB.x - prevB.x, + vertexB.y - prevB.y, + &prevB, + &vertexB + }; + + V vB2 = { + vertexB.x - nextB.x, + vertexB.y - nextB.y, + &nextB, + &vertexB + }; + + vectors.emplace_back(vA1); + vectors.emplace_back(vA2); + vectors.emplace_back(vB1); + vectors.emplace_back(vB2); + } + else if(touching[i].type == 1){ + vectors.emplace_back(V{ + vertexA.x-(vertexB.x+B.offsetx), + vertexA.y-(vertexB.y+B.offsety), + &prevA, + &vertexA + }); + + vectors.emplace_back(V{ + prevA.x-(vertexB.x+B.offsetx), + prevA.y-(vertexB.y+B.offsety), + &vertexA, + &prevA + }); + } + else if(touching[i].type == 2){ + vectors.emplace_back(V{ + vertexA.x-(vertexB.x+B.offsetx), + vertexA.y-(vertexB.y+B.offsety), + &prevB, + &vertexB + }); + + vectors.emplace_back(V{ + vertexA.x-(prevB.x+B.offsetx), + vertexA.y-(prevB.y+B.offsety), + &vertexB, + &prevB + }); + } + } + + // TODO: there should be a faster way to reject vectors that + // will cause immediate intersection. For now just check them all + + V translate = {0, 0, nullptr, nullptr}; + V prevvector = {0, 0, nullptr, nullptr}; + double maxd = 0; + + for(i = 0; i < vectors.size(); i++) { + if(vectors[i].x == 0 && vectors[i].y == 0){ + continue; + } + + // if this vector points us back to where we came from, ignore it. + // ie cross product = 0, dot product < 0 + if(prevvector && vectors[i].y * prevvector.y + vectors[i].x * prevvector.x < 0){ + + // compare magnitude with unit vectors + double vectorlength = sqrt(vectors[i].x*vectors[i].x+vectors[i].y*vectors[i].y); + Vector unitv = {Coord(vectors[i].x/vectorlength), + Coord(vectors[i].y/vectorlength)}; + + double prevlength = sqrt(prevvector.x*prevvector.x+prevvector.y*prevvector.y); + Vector prevunit = { prevvector.x/prevlength, prevvector.y/prevlength}; + + // we need to scale down to unit vectors to normalize vector length. Could also just do a tan here + if(abs(unitv.y * prevunit.x - unitv.x * prevunit.y) < 0.0001){ + continue; + } + } + + double d = polygonSlideDistance(A, B, vectors[i], true); + double vecd2 = vectors[i].x*vectors[i].x + vectors[i].y*vectors[i].y; + + if(isnan(d) || d*d > vecd2){ + double vecd = sqrt(vectors[i].x*vectors[i].x + vectors[i].y*vectors[i].y); + d = vecd; + } + + if(!isnan(d) && d > maxd){ + maxd = d; + translate = vectors[i]; + } + } + + if(!translate || _almostEqual(maxd, 0)) + { + // didn't close the loop, something went wrong here + NFP.clear(); + break; + } + + translate.start->marked = true; + translate.end->marked = true; + + prevvector = translate; + + // trim + double vlength2 = translate.x*translate.x + translate.y*translate.y; + if(maxd*maxd < vlength2 && !_almostEqual(maxd*maxd, vlength2)){ + double scale = sqrt((maxd*maxd)/vlength2); + translate.x *= scale; + translate.y *= scale; + } + + referencex += translate.x; + referencey += translate.y; + + if(_almostEqual(referencex, startx) && + _almostEqual(referencey, starty)) { + // we've made a full loop + break; + } + + // if A and B start on a touching horizontal line, + // the end point may not be the start point + bool looped = false; + if(NFP.size() > 0) { + for(i = 0; i < NFP.size() - 1; i++) { + if(_almostEqual(referencex, NFP[i].x) && + _almostEqual(referencey, NFP[i].y)){ + looped = true; + } + } + } + + if(looped){ + // we've made a full loop + break; + } + + NFP.emplace_back(referencex, referencey); + + B.offsetx += translate.x; + B.offsety += translate.y; + + counter++; + } + + if(NFP.size() > 0){ + NFPlist.emplace_back(NFP); + } + + if(!searchEdges){ + // only get outer NFP or first inner NFP + break; + } + + startpoint = + searchStartPoint(A, B, inside, NFPlist); + + } + + return NFPlist; + } +}; + +template const double _alg::TOL = std::pow(10, -9); + +//template +//nfp::NfpResult nfpSimpleSimple(const S& stat, const S& orb) { +//// using Cntr = TContour; +// using Point = TPoint; +//// using Coord = TCoord; +//// using Shapes = nfp::Shapes; + +// namespace sl = shapelike; + +// noFitPolygon(sl::getContour(stat), sl::getContour(orb), true, true); +// return {S(), Point()}; +//} + +} +} + +#endif // NFP_SVGNEST_HPP diff --git a/xs/src/libnest2d/tools/nfp_svgnest_glue.hpp b/xs/src/libnest2d/tools/nfp_svgnest_glue.hpp new file mode 100644 index 000000000..7ceb2d24d --- /dev/null +++ b/xs/src/libnest2d/tools/nfp_svgnest_glue.hpp @@ -0,0 +1,77 @@ +#ifndef NFP_SVGNEST_GLUE_HPP +#define NFP_SVGNEST_GLUE_HPP + +#include "nfp_svgnest.hpp" + +#include + +namespace libnest2d { + +namespace __svgnest { + +//template<> struct _Tol { +// static const BP2D_CONSTEXPR TCoord Value = 1000000; +//}; + +} + +namespace nfp { + +using NfpR = NfpResult; + +template<> struct NfpImpl { + NfpR operator()(const PolygonImpl& sh, const PolygonImpl& cother) { +// return nfpConvexOnly(sh, cother); + namespace sl = shapelike; + using alg = __svgnest::_alg; + + std::cout << "Itt vagyok" << std::endl; + auto nfp_p = alg::noFitPolygon(sl::getContour(sh), + sl::getContour(cother), false, false); + + PolygonImpl nfp_cntr; + nfp_cntr.Contour = nfp_p.front(); + std::cout << "Contour size: " << nfp_cntr.Contour.size() << std::endl; + return {nfp_cntr, referenceVertex(nfp_cntr)}; + } +}; + +template<> struct NfpImpl { + NfpR operator()(const PolygonImpl& sh, const PolygonImpl& cother) { +// return nfpConvexOnly(sh, cother); + namespace sl = shapelike; + using alg = __svgnest::_alg; + + std::cout << "Itt vagyok" << std::endl; + auto nfp_p = alg::noFitPolygon(sl::getContour(sh), + sl::getContour(cother), false, false); + + PolygonImpl nfp_cntr; + nfp_cntr.Contour = nfp_p.front(); + return {nfp_cntr, referenceVertex(nfp_cntr)}; + } +}; + +template<> +struct NfpImpl { + NfpR operator()(const PolygonImpl& sh, const PolygonImpl& cother) { + namespace sl = shapelike; + using alg = __svgnest::_alg; + + auto nfp_p = alg::noFitPolygon(sl::getContour(sh), + sl::getContour(cother), true, false); + + PolygonImpl nfp_cntr; + nfp_cntr.Contour = nfp_p.front(); + return {nfp_cntr, referenceVertex(nfp_cntr)}; + } +}; + +template<> struct MaxNfpLevel { +// static const BP2D_CONSTEXPR NfpLevel value = NfpLevel::BOTH_CONCAVE; + static const BP2D_CONSTEXPR NfpLevel value = NfpLevel::CONVEX_ONLY; +}; + +}} + +#endif // NFP_SVGNEST_GLUE_HPP diff --git a/xs/src/libslic3r/ModelArrange.hpp b/xs/src/libslic3r/ModelArrange.hpp index cc4bfff0f..dcb0da9e5 100644 --- a/xs/src/libslic3r/ModelArrange.hpp +++ b/xs/src/libslic3r/ModelArrange.hpp @@ -270,6 +270,8 @@ void fillConfig(PConf& pcfg) { // The accuracy of optimization. // Goes from 0.0 to 1.0 and scales performance as well pcfg.accuracy = 0.65f; + + pcfg.parallel = false; } template @@ -291,6 +293,7 @@ protected: std::vector areacache_; SpatIndex rtree_; double norm_; + Pile pile_cache_; public: _ArrBase(const TBin& bin, Distance dist, @@ -317,23 +320,26 @@ public: std::function progressind): _ArrBase(bin, dist, progressind) { - pconf_.object_function = [this, bin] ( - Pile& pile, - const Item &item, - const ItemGroup& rem) { +// pconf_.object_function = [this, bin] ( +// const Pile& pile_c, +// const Item &item, +// const ItemGroup& rem) { - auto result = objfunc(bin.center(), bin_area_, pile, - item, norm_, areacache_, rtree_, rem); - double score = std::get<0>(result); - auto& fullbb = std::get<1>(result); +// auto& pile = pile_cache_; +// if(pile.size() != pile_c.size()) pile = pile_c; - auto wdiff = fullbb.width() - bin.width(); - auto hdiff = fullbb.height() - bin.height(); - if(wdiff > 0) score += std::pow(wdiff, 2) / norm_; - if(hdiff > 0) score += std::pow(hdiff, 2) / norm_; +// auto result = objfunc(bin.center(), bin_area_, pile, +// item, norm_, areacache_, rtree_, rem); +// double score = std::get<0>(result); +// auto& fullbb = std::get<1>(result); - return score; - }; +// auto wdiff = fullbb.width() - bin.width(); +// auto hdiff = fullbb.height() - bin.height(); +// if(wdiff > 0) score += std::pow(wdiff, 2) / norm_; +// if(hdiff > 0) score += std::pow(hdiff, 2) / norm_; + +// return score; +// }; pck_.configure(pconf_); } @@ -350,10 +356,13 @@ public: _ArrBase(bin, dist, progressind) { pconf_.object_function = [this, &bin] ( - Pile& pile, + const Pile& pile_c, const Item &item, const ItemGroup& rem) { + auto& pile = pile_cache_; + if(pile.size() != pile_c.size()) pile = pile_c; + auto result = objfunc(bin.center(), bin_area_, pile, item, norm_, areacache_, rtree_, rem); double score = std::get<0>(result); @@ -393,10 +402,13 @@ public: _ArrBase(bin, dist, progressind) { pconf_.object_function = [this, &bin] ( - Pile& pile, + const Pile& pile_c, const Item &item, const ItemGroup& rem) { + auto& pile = pile_cache_; + if(pile.size() != pile_c.size()) pile = pile_c; + auto binbb = sl::boundingBox(bin); auto result = objfunc(binbb.center(), bin_area_, pile, item, norm_, areacache_, rtree_, rem); @@ -417,10 +429,13 @@ public: _ArrBase(Box(0, 0), dist, progressind) { this->pconf_.object_function = [this] ( - Pile& pile, + const Pile& pile_c, const Item &item, const ItemGroup& rem) { + auto& pile = pile_cache_; + if(pile.size() != pile_c.size()) pile = pile_c; + auto result = objfunc({0, 0}, 0, pile, item, norm_, areacache_, rtree_, rem); return std::get<0>(result); From e522ad1a00cfb85b6cd21c664b213d5d35dca2bd Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Wed, 22 Aug 2018 13:52:41 +0200 Subject: [PATCH 10/10] Parallel placer now works with the custom Slic3r object function. Works an order of magnitude faster. --- xs/CMakeLists.txt | 1 + xs/src/libnest2d/CMakeLists.txt | 25 +- xs/src/libnest2d/cmake_modules/FindTBB.cmake | 322 ++++++++++++++++++ xs/src/libnest2d/examples/main.cpp | 28 +- xs/src/libnest2d/libnest2d.h | 10 +- .../libnest2d/geometry_traits_nfp.hpp | 4 +- xs/src/libnest2d/libnest2d/libnest2d.hpp | 20 +- .../libnest2d/placers/bottomleftplacer.hpp | 2 +- .../libnest2d/libnest2d/placers/nfpplacer.hpp | 227 +++++++----- .../libnest2d/placers/placer_boilerplate.hpp | 2 +- .../libnest2d/selections/djd_heuristic.hpp | 2 +- .../libnest2d/libnest2d/selections/filler.hpp | 2 +- .../libnest2d/selections/firstfit.hpp | 2 +- .../selections/selection_boilerplate.hpp | 3 +- xs/src/libnest2d/tests/test.cpp | 34 +- xs/src/libnest2d/tools/nfp_svgnest.hpp | 114 ++++--- xs/src/libnest2d/tools/nfp_svgnest_glue.hpp | 4 +- xs/src/libslic3r/ModelArrange.hpp | 233 +++++++------ 18 files changed, 739 insertions(+), 296 deletions(-) create mode 100644 xs/src/libnest2d/cmake_modules/FindTBB.cmake diff --git a/xs/CMakeLists.txt b/xs/CMakeLists.txt index d41b4c13a..70e933058 100644 --- a/xs/CMakeLists.txt +++ b/xs/CMakeLists.txt @@ -729,6 +729,7 @@ add_custom_target(pot set(LIBNEST2D_UNITTESTS ON CACHE BOOL "Force generating unittests for libnest2d") add_subdirectory(${LIBDIR}/libnest2d) +target_compile_definitions(libslic3r PUBLIC -DUSE_TBB) target_include_directories(libslic3r PUBLIC BEFORE ${LIBNEST2D_INCLUDES}) target_include_directories(libslic3r_gui PUBLIC BEFORE ${LIBNEST2D_INCLUDES}) diff --git a/xs/src/libnest2d/CMakeLists.txt b/xs/src/libnest2d/CMakeLists.txt index cd3e35b97..f81355012 100644 --- a/xs/src/libnest2d/CMakeLists.txt +++ b/xs/src/libnest2d/CMakeLists.txt @@ -90,6 +90,7 @@ if(LIBNEST2D_UNITTESTS) endif() if(LIBNEST2D_BUILD_EXAMPLES) + add_executable(example examples/main.cpp # tools/libnfpglue.hpp # tools/libnfpglue.cpp @@ -98,8 +99,30 @@ if(LIBNEST2D_BUILD_EXAMPLES) tools/svgtools.hpp tests/printer_parts.cpp tests/printer_parts.h - ${LIBNEST2D_SRCFILES}) + ${LIBNEST2D_SRCFILES} + ) + set(TBB_STATIC ON) + find_package(TBB QUIET) + if(TBB_FOUND) + message(STATUS "Parallelization with Intel TBB") + target_include_directories(example PUBLIC ${TBB_INCLUDE_DIRS}) + target_compile_definitions(example PUBLIC ${TBB_DEFINITIONS} -DUSE_TBB) + if(MSVC) + # Suppress implicit linking of the TBB libraries by the Visual Studio compiler. + target_compile_definitions(example PUBLIC -D__TBB_NO_IMPLICIT_LINKAGE) + endif() + # The Intel TBB library will use the std::exception_ptr feature of C++11. + target_compile_definitions(example PUBLIC -DTBB_USE_CAPTURED_EXCEPTION=1) + target_link_libraries(example ${TBB_LIBRARIES}) + else() + find_package(OpenMP QUIET) + if(OpenMP_CXX_FOUND) + message(STATUS "Parallelization with OpenMP") + target_include_directories(example PUBLIC OpenMP::OpenMP_CXX) + target_link_libraries(example OpenMP::OpenMP_CXX) + endif() + endif() target_link_libraries(example ${LIBNEST2D_LIBRARIES}) target_include_directories(example PUBLIC ${LIBNEST2D_HEADERS}) diff --git a/xs/src/libnest2d/cmake_modules/FindTBB.cmake b/xs/src/libnest2d/cmake_modules/FindTBB.cmake new file mode 100644 index 000000000..8b498d3ab --- /dev/null +++ b/xs/src/libnest2d/cmake_modules/FindTBB.cmake @@ -0,0 +1,322 @@ +# The MIT License (MIT) +# +# Copyright (c) 2015 Justus Calvin +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in all +# copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. + +# +# FindTBB +# ------- +# +# Find TBB include directories and libraries. +# +# Usage: +# +# find_package(TBB [major[.minor]] [EXACT] +# [QUIET] [REQUIRED] +# [[COMPONENTS] [components...]] +# [OPTIONAL_COMPONENTS components...]) +# +# where the allowed components are tbbmalloc and tbb_preview. Users may modify +# the behavior of this module with the following variables: +# +# * TBB_ROOT_DIR - The base directory the of TBB installation. +# * TBB_INCLUDE_DIR - The directory that contains the TBB headers files. +# * TBB_LIBRARY - The directory that contains the TBB library files. +# * TBB__LIBRARY - The path of the TBB the corresponding TBB library. +# These libraries, if specified, override the +# corresponding library search results, where +# may be tbb, tbb_debug, tbbmalloc, tbbmalloc_debug, +# tbb_preview, or tbb_preview_debug. +# * TBB_USE_DEBUG_BUILD - The debug version of tbb libraries, if present, will +# be used instead of the release version. +# * TBB_STATIC - Static linking of libraries with a _static suffix. +# For example, on Windows a tbb_static.lib will be searched for +# instead of tbb.lib. +# +# Users may modify the behavior of this module with the following environment +# variables: +# +# * TBB_INSTALL_DIR +# * TBBROOT +# * LIBRARY_PATH +# +# This module will set the following variables: +# +# * TBB_FOUND - Set to false, or undefined, if we haven’t found, or +# don’t want to use TBB. +# * TBB__FOUND - If False, optional part of TBB sytem is +# not available. +# * TBB_VERSION - The full version string +# * TBB_VERSION_MAJOR - The major version +# * TBB_VERSION_MINOR - The minor version +# * TBB_INTERFACE_VERSION - The interface version number defined in +# tbb/tbb_stddef.h. +# * TBB__LIBRARY_RELEASE - The path of the TBB release version of +# , where may be tbb, tbb_debug, +# tbbmalloc, tbbmalloc_debug, tbb_preview, or +# tbb_preview_debug. +# * TBB__LIBRARY_DEGUG - The path of the TBB release version of +# , where may be tbb, tbb_debug, +# tbbmalloc, tbbmalloc_debug, tbb_preview, or +# tbb_preview_debug. +# +# The following varibles should be used to build and link with TBB: +# +# * TBB_INCLUDE_DIRS - The include directory for TBB. +# * TBB_LIBRARIES - The libraries to link against to use TBB. +# * TBB_LIBRARIES_RELEASE - The release libraries to link against to use TBB. +# * TBB_LIBRARIES_DEBUG - The debug libraries to link against to use TBB. +# * TBB_DEFINITIONS - Definitions to use when compiling code that uses +# TBB. +# * TBB_DEFINITIONS_RELEASE - Definitions to use when compiling release code that +# uses TBB. +# * TBB_DEFINITIONS_DEBUG - Definitions to use when compiling debug code that +# uses TBB. +# +# This module will also create the "tbb" target that may be used when building +# executables and libraries. + +include(FindPackageHandleStandardArgs) + +if(NOT TBB_FOUND) + + ################################## + # Check the build type + ################################## + + if(NOT DEFINED TBB_USE_DEBUG_BUILD) + if(CMAKE_BUILD_TYPE MATCHES "(Debug|DEBUG|debug)") + set(TBB_BUILD_TYPE DEBUG) + else() + set(TBB_BUILD_TYPE RELEASE) + endif() + elseif(TBB_USE_DEBUG_BUILD) + set(TBB_BUILD_TYPE DEBUG) + else() + set(TBB_BUILD_TYPE RELEASE) + endif() + + ################################## + # Set the TBB search directories + ################################## + + # Define search paths based on user input and environment variables + set(TBB_SEARCH_DIR ${TBB_ROOT_DIR} $ENV{TBB_INSTALL_DIR} $ENV{TBBROOT}) + + # Define the search directories based on the current platform + if(CMAKE_SYSTEM_NAME STREQUAL "Windows") + set(TBB_DEFAULT_SEARCH_DIR "C:/Program Files/Intel/TBB" + "C:/Program Files (x86)/Intel/TBB") + + # Set the target architecture + if(CMAKE_SIZEOF_VOID_P EQUAL 8) + set(TBB_ARCHITECTURE "intel64") + else() + set(TBB_ARCHITECTURE "ia32") + endif() + + # Set the TBB search library path search suffix based on the version of VC + if(WINDOWS_STORE) + set(TBB_LIB_PATH_SUFFIX "lib/${TBB_ARCHITECTURE}/vc11_ui") + elseif(MSVC14) + set(TBB_LIB_PATH_SUFFIX "lib/${TBB_ARCHITECTURE}/vc14") + elseif(MSVC12) + set(TBB_LIB_PATH_SUFFIX "lib/${TBB_ARCHITECTURE}/vc12") + elseif(MSVC11) + set(TBB_LIB_PATH_SUFFIX "lib/${TBB_ARCHITECTURE}/vc11") + elseif(MSVC10) + set(TBB_LIB_PATH_SUFFIX "lib/${TBB_ARCHITECTURE}/vc10") + endif() + + # Add the library path search suffix for the VC independent version of TBB + list(APPEND TBB_LIB_PATH_SUFFIX "lib/${TBB_ARCHITECTURE}/vc_mt") + + elseif(CMAKE_SYSTEM_NAME STREQUAL "Darwin") + # OS X + set(TBB_DEFAULT_SEARCH_DIR "/opt/intel/tbb") + + # TODO: Check to see which C++ library is being used by the compiler. + if(NOT ${CMAKE_SYSTEM_VERSION} VERSION_LESS 13.0) + # The default C++ library on OS X 10.9 and later is libc++ + set(TBB_LIB_PATH_SUFFIX "lib/libc++" "lib") + else() + set(TBB_LIB_PATH_SUFFIX "lib") + endif() + elseif(CMAKE_SYSTEM_NAME STREQUAL "Linux") + # Linux + set(TBB_DEFAULT_SEARCH_DIR "/opt/intel/tbb") + + # TODO: Check compiler version to see the suffix should be /gcc4.1 or + # /gcc4.1. For now, assume that the compiler is more recent than + # gcc 4.4.x or later. + if(CMAKE_SYSTEM_PROCESSOR STREQUAL "x86_64") + set(TBB_LIB_PATH_SUFFIX "lib/intel64/gcc4.4") + elseif(CMAKE_SYSTEM_PROCESSOR MATCHES "^i.86$") + set(TBB_LIB_PATH_SUFFIX "lib/ia32/gcc4.4") + endif() + endif() + + ################################## + # Find the TBB include dir + ################################## + + find_path(TBB_INCLUDE_DIRS tbb/tbb.h + HINTS ${TBB_INCLUDE_DIR} ${TBB_SEARCH_DIR} + PATHS ${TBB_DEFAULT_SEARCH_DIR} + PATH_SUFFIXES include) + + ################################## + # Set version strings + ################################## + + if(TBB_INCLUDE_DIRS) + file(READ "${TBB_INCLUDE_DIRS}/tbb/tbb_stddef.h" _tbb_version_file) + string(REGEX REPLACE ".*#define TBB_VERSION_MAJOR ([0-9]+).*" "\\1" + TBB_VERSION_MAJOR "${_tbb_version_file}") + string(REGEX REPLACE ".*#define TBB_VERSION_MINOR ([0-9]+).*" "\\1" + TBB_VERSION_MINOR "${_tbb_version_file}") + string(REGEX REPLACE ".*#define TBB_INTERFACE_VERSION ([0-9]+).*" "\\1" + TBB_INTERFACE_VERSION "${_tbb_version_file}") + set(TBB_VERSION "${TBB_VERSION_MAJOR}.${TBB_VERSION_MINOR}") + endif() + + ################################## + # Find TBB components + ################################## + + if(TBB_VERSION VERSION_LESS 4.3) + set(TBB_SEARCH_COMPOMPONENTS tbb_preview tbbmalloc tbb) + else() + set(TBB_SEARCH_COMPOMPONENTS tbb_preview tbbmalloc_proxy tbbmalloc tbb) + endif() + + if(TBB_STATIC) + set(TBB_STATIC_SUFFIX "_static") + endif() + + # Find each component + foreach(_comp ${TBB_SEARCH_COMPOMPONENTS}) + if(";${TBB_FIND_COMPONENTS};tbb;" MATCHES ";${_comp};") + + # Search for the libraries + find_library(TBB_${_comp}_LIBRARY_RELEASE ${_comp}${TBB_STATIC_SUFFIX} + HINTS ${TBB_LIBRARY} ${TBB_SEARCH_DIR} + PATHS ${TBB_DEFAULT_SEARCH_DIR} ENV LIBRARY_PATH + PATH_SUFFIXES ${TBB_LIB_PATH_SUFFIX}) + + find_library(TBB_${_comp}_LIBRARY_DEBUG ${_comp}${TBB_STATIC_SUFFIX}_debug + HINTS ${TBB_LIBRARY} ${TBB_SEARCH_DIR} + PATHS ${TBB_DEFAULT_SEARCH_DIR} ENV LIBRARY_PATH + PATH_SUFFIXES ${TBB_LIB_PATH_SUFFIX}) + + if(TBB_${_comp}_LIBRARY_DEBUG) + list(APPEND TBB_LIBRARIES_DEBUG "${TBB_${_comp}_LIBRARY_DEBUG}") + endif() + if(TBB_${_comp}_LIBRARY_RELEASE) + list(APPEND TBB_LIBRARIES_RELEASE "${TBB_${_comp}_LIBRARY_RELEASE}") + endif() + if(TBB_${_comp}_LIBRARY_${TBB_BUILD_TYPE} AND NOT TBB_${_comp}_LIBRARY) + set(TBB_${_comp}_LIBRARY "${TBB_${_comp}_LIBRARY_${TBB_BUILD_TYPE}}") + endif() + + if(TBB_${_comp}_LIBRARY AND EXISTS "${TBB_${_comp}_LIBRARY}") + set(TBB_${_comp}_FOUND TRUE) + else() + set(TBB_${_comp}_FOUND FALSE) + endif() + + # Mark internal variables as advanced + mark_as_advanced(TBB_${_comp}_LIBRARY_RELEASE) + mark_as_advanced(TBB_${_comp}_LIBRARY_DEBUG) + mark_as_advanced(TBB_${_comp}_LIBRARY) + + endif() + endforeach() + + unset(TBB_STATIC_SUFFIX) + + ################################## + # Set compile flags and libraries + ################################## + + set(TBB_DEFINITIONS_RELEASE "") + set(TBB_DEFINITIONS_DEBUG "-DTBB_USE_DEBUG=1") + + if(TBB_LIBRARIES_${TBB_BUILD_TYPE}) + set(TBB_DEFINITIONS "${TBB_DEFINITIONS_${TBB_BUILD_TYPE}}") + set(TBB_LIBRARIES "${TBB_LIBRARIES_${TBB_BUILD_TYPE}}") + elseif(TBB_LIBRARIES_RELEASE) + set(TBB_DEFINITIONS "${TBB_DEFINITIONS_RELEASE}") + set(TBB_LIBRARIES "${TBB_LIBRARIES_RELEASE}") + elseif(TBB_LIBRARIES_DEBUG) + set(TBB_DEFINITIONS "${TBB_DEFINITIONS_DEBUG}") + set(TBB_LIBRARIES "${TBB_LIBRARIES_DEBUG}") + endif() + + find_package_handle_standard_args(TBB + REQUIRED_VARS TBB_INCLUDE_DIRS TBB_LIBRARIES + HANDLE_COMPONENTS + VERSION_VAR TBB_VERSION) + + ################################## + # Create targets + ################################## + + if(NOT CMAKE_VERSION VERSION_LESS 3.0 AND TBB_FOUND) + add_library(tbb SHARED IMPORTED) + set_target_properties(tbb PROPERTIES + INTERFACE_INCLUDE_DIRECTORIES ${TBB_INCLUDE_DIRS} + IMPORTED_LOCATION ${TBB_LIBRARIES}) + if(TBB_LIBRARIES_RELEASE AND TBB_LIBRARIES_DEBUG) + set_target_properties(tbb PROPERTIES + INTERFACE_COMPILE_DEFINITIONS "$<$,$>:TBB_USE_DEBUG=1>" + IMPORTED_LOCATION_DEBUG ${TBB_LIBRARIES_DEBUG} + IMPORTED_LOCATION_RELWITHDEBINFO ${TBB_LIBRARIES_DEBUG} + IMPORTED_LOCATION_RELEASE ${TBB_LIBRARIES_RELEASE} + IMPORTED_LOCATION_MINSIZEREL ${TBB_LIBRARIES_RELEASE} + ) + elseif(TBB_LIBRARIES_RELEASE) + set_target_properties(tbb PROPERTIES IMPORTED_LOCATION ${TBB_LIBRARIES_RELEASE}) + else() + set_target_properties(tbb PROPERTIES + INTERFACE_COMPILE_DEFINITIONS "${TBB_DEFINITIONS_DEBUG}" + IMPORTED_LOCATION ${TBB_LIBRARIES_DEBUG} + ) + endif() + endif() + + mark_as_advanced(TBB_INCLUDE_DIRS TBB_LIBRARIES) + + unset(TBB_ARCHITECTURE) + unset(TBB_BUILD_TYPE) + unset(TBB_LIB_PATH_SUFFIX) + unset(TBB_DEFAULT_SEARCH_DIR) + + if(TBB_DEBUG) + message(STATUS " TBB_INCLUDE_DIRS = ${TBB_INCLUDE_DIRS}") + message(STATUS " TBB_DEFINITIONS = ${TBB_DEFINITIONS}") + message(STATUS " TBB_LIBRARIES = ${TBB_LIBRARIES}") + message(STATUS " TBB_DEFINITIONS_DEBUG = ${TBB_DEFINITIONS_DEBUG}") + message(STATUS " TBB_LIBRARIES_DEBUG = ${TBB_LIBRARIES_DEBUG}") + message(STATUS " TBB_DEFINITIONS_RELEASE = ${TBB_DEFINITIONS_RELEASE}") + message(STATUS " TBB_LIBRARIES_RELEASE = ${TBB_LIBRARIES_RELEASE}") + endif() + +endif() diff --git a/xs/src/libnest2d/examples/main.cpp b/xs/src/libnest2d/examples/main.cpp index 57be7a208..ebc3fb15c 100644 --- a/xs/src/libnest2d/examples/main.cpp +++ b/xs/src/libnest2d/examples/main.cpp @@ -54,7 +54,7 @@ void arrangeRectangles() { const int SCALE = 1000000; - std::vector rects(100, { + std::vector rects(202, { {-9945219, -3065619}, {-9781479, -2031780}, {-9510560, -1020730}, @@ -104,8 +104,6 @@ void arrangeRectangles() { // input.insert(input.end(), prusaExParts().begin(), prusaExParts().end()); // input.insert(input.end(), stegoParts().begin(), stegoParts().end()); // input.insert(input.end(), rects.begin(), rects.end()); -// input.insert(input.end(), proba.begin(), proba.end()); -// input.insert(input.end(), crasher.begin(), crasher.end()); Box bin(250*SCALE, 210*SCALE); // PolygonImpl bin = { @@ -123,11 +121,11 @@ void arrangeRectangles() { // {} // }; -// _Circle bin({0, 0}, 125*SCALE); +// Circle bin({0, 0}, 125*SCALE); - auto min_obj_distance = static_cast(1.5*SCALE); + auto min_obj_distance = static_cast(6*SCALE); - using Placer = strategies::_NofitPolyPlacer; + using Placer = placers::_NofitPolyPlacer; using Packer = Nester; Packer arrange(bin, min_obj_distance); @@ -136,7 +134,7 @@ void arrangeRectangles() { pconf.alignment = Placer::Config::Alignment::CENTER; pconf.starting_point = Placer::Config::Alignment::CENTER; pconf.rotations = {0.0/*, Pi/2.0, Pi, 3*Pi/2*/}; - pconf.accuracy = 0.5f; + pconf.accuracy = 0.65f; pconf.parallel = true; Packer::SelectionConfig sconf; @@ -149,12 +147,6 @@ void arrangeRectangles() { arrange.configure(pconf, sconf); arrange.progressIndicator([&](unsigned r){ -// svg::SVGWriter::Config conf; -// conf.mm_in_coord_units = SCALE; -// svg::SVGWriter svgw(conf); -// svgw.setSize(bin); -// svgw.writePackGroup(arrange.lastResult()); -// svgw.save("debout"); std::cout << "Remaining items: " << r << std::endl; }); @@ -201,10 +193,10 @@ void arrangeRectangles() { for(auto& r : result) { std::cout << r.size() << " "; total += r.size(); } std::cout << ") Total: " << total << std::endl; - for(auto& it : input) { - auto ret = sl::isValid(it.transformedShape()); - std::cout << ret.second << std::endl; - } +// for(auto& it : input) { +// auto ret = sl::isValid(it.transformedShape()); +// std::cout << ret.second << std::endl; +// } if(total != input.size()) std::cout << "ERROR " << "could not pack " << input.size() - total << " elements!" @@ -222,7 +214,5 @@ void arrangeRectangles() { int main(void /*int argc, char **argv*/) { arrangeRectangles(); -//// findDegenerateCase(); - return EXIT_SUCCESS; } diff --git a/xs/src/libnest2d/libnest2d.h b/xs/src/libnest2d/libnest2d.h index 05677afd7..bfd88f4f5 100644 --- a/xs/src/libnest2d/libnest2d.h +++ b/xs/src/libnest2d/libnest2d.h @@ -30,12 +30,12 @@ using Rectangle = _Rectangle; using PackGroup = _PackGroup; using IndexedPackGroup = _IndexedPackGroup; -using FillerSelection = strategies::_FillerSelection; -using FirstFitSelection = strategies::_FirstFitSelection; -using DJDHeuristic = strategies::_DJDHeuristic; +using FillerSelection = selections::_FillerSelection; +using FirstFitSelection = selections::_FirstFitSelection; +using DJDHeuristic = selections::_DJDHeuristic; -using NfpPlacer = strategies::_NofitPolyPlacer; -using BottomLeftPlacer = strategies::_BottomLeftPlacer; +using NfpPlacer = placers::_NofitPolyPlacer; +using BottomLeftPlacer = placers::_BottomLeftPlacer; } diff --git a/xs/src/libnest2d/libnest2d/geometry_traits_nfp.hpp b/xs/src/libnest2d/libnest2d/geometry_traits_nfp.hpp index b9dfd2185..2982454cd 100644 --- a/xs/src/libnest2d/libnest2d/geometry_traits_nfp.hpp +++ b/xs/src/libnest2d/libnest2d/geometry_traits_nfp.hpp @@ -102,7 +102,7 @@ inline TPoint leftmostDownVertex(const RawShape& sh) auto it = std::min_element(shapelike::cbegin(sh), shapelike::cend(sh), __nfp::_vsort); - return *it; + return it == shapelike::cend(sh) ? TPoint() : *it;; } /** @@ -118,7 +118,7 @@ TPoint rightmostUpVertex(const RawShape& sh) auto it = std::max_element(shapelike::cbegin(sh), shapelike::cend(sh), __nfp::_vsort); - return *it; + return it == shapelike::cend(sh) ? TPoint() : *it; } /** diff --git a/xs/src/libnest2d/libnest2d/libnest2d.hpp b/xs/src/libnest2d/libnest2d/libnest2d.hpp index 42255cbb4..4d1e62f99 100644 --- a/xs/src/libnest2d/libnest2d/libnest2d.hpp +++ b/xs/src/libnest2d/libnest2d/libnest2d.hpp @@ -65,8 +65,8 @@ class _Item { mutable VertexConstIterator lmb_; // leftmost bottom vertex mutable bool rmt_valid_ = false, lmb_valid_ = false; mutable struct BBCache { - Box bb; bool valid; Vertex tr; - BBCache(): valid(false), tr(0, 0) {} + Box bb; bool valid; + BBCache(): valid(false) {} } bb_cache_; public: @@ -310,7 +310,7 @@ public: { if(translation_ != tr) { translation_ = tr; has_translation_ = true; tr_cache_valid_ = false; - bb_cache_.valid = false; + //bb_cache_.valid = false; } } @@ -345,13 +345,19 @@ public: inline Box boundingBox() const { if(!bb_cache_.valid) { - bb_cache_.bb = sl::boundingBox(transformedShape()); - bb_cache_.tr = {0, 0}; + if(!has_rotation_) + bb_cache_.bb = sl::boundingBox(offsettedShape()); + else { + // TODO make sure this works + auto rotsh = offsettedShape(); + sl::rotate(rotsh, rotation_); + bb_cache_.bb = sl::boundingBox(rotsh); + } bb_cache_.valid = true; } - auto &bb = bb_cache_.bb; auto &tr = bb_cache_.tr; - return {bb.minCorner() + tr, bb.maxCorner() + tr}; + auto &bb = bb_cache_.bb; auto &tr = translation_; + return {bb.minCorner() + tr, bb.maxCorner() + tr }; } inline Vertex referenceVertex() const { diff --git a/xs/src/libnest2d/libnest2d/placers/bottomleftplacer.hpp b/xs/src/libnest2d/libnest2d/placers/bottomleftplacer.hpp index 0ba9eb3c0..18c27c40c 100644 --- a/xs/src/libnest2d/libnest2d/placers/bottomleftplacer.hpp +++ b/xs/src/libnest2d/libnest2d/placers/bottomleftplacer.hpp @@ -5,7 +5,7 @@ #include "placer_boilerplate.hpp" -namespace libnest2d { namespace strategies { +namespace libnest2d { namespace placers { template struct Epsilon {}; diff --git a/xs/src/libnest2d/libnest2d/placers/nfpplacer.hpp b/xs/src/libnest2d/libnest2d/placers/nfpplacer.hpp index b9e0ba8f1..c86fb507e 100644 --- a/xs/src/libnest2d/libnest2d/placers/nfpplacer.hpp +++ b/xs/src/libnest2d/libnest2d/placers/nfpplacer.hpp @@ -21,6 +21,12 @@ #include "tools/svgtools.hpp" +#ifdef USE_TBB +#include +#elif defined(_OPENMP) +#include +#endif + namespace libnest2d { namespace __parallel { @@ -33,20 +39,52 @@ using TIteratorValue = typename iterator_traits::value_type; template inline void enumerate( Iterator from, Iterator to, - function, unsigned)> fn, + function, size_t)> fn, std::launch policy = std::launch::deferred | std::launch::async) { - auto N = to-from; + using TN = size_t; + auto iN = to-from; + TN N = iN < 0? 0 : TN(iN); + +#ifdef USE_TBB + if((policy & std::launch::async) == std::launch::async) { + tbb::parallel_for(0, N, [from, fn] (TN n) { fn(*(from + n), n); } ); + } else { + for(TN n = 0; n < N; n++) fn(*(from + n), n); + } +#elif defined(_OPENMP) + if((policy & std::launch::async) == std::launch::async) { + #pragma omp parallel for + for(TN n = 0; n < N; n++) fn(*(from + n), n); + } + else { + for(TN n = 0; n < N; n++) fn(*(from + n), n); + } +#else std::vector> rets(N); auto it = from; - for(unsigned b = 0; b < N; b++) { - rets[b] = std::async(policy, fn, *it++, b); + for(TN b = 0; b < N; b++) { + rets[b] = std::async(policy, fn, *it++, unsigned(b)); } - for(unsigned fi = 0; fi < rets.size(); ++fi) rets[fi].wait(); + for(TN fi = 0; fi < N; ++fi) rets[fi].wait(); +#endif } +class SpinLock { + static std::atomic_flag locked; +public: + void lock() { + while (locked.test_and_set(std::memory_order_acquire)) { ; } + } + void unlock() { + locked.clear(std::memory_order_release); + } +}; + +std::atomic_flag SpinLock::locked = ATOMIC_FLAG_INIT ; + } namespace __itemhash { @@ -98,7 +136,7 @@ using Hash = std::unordered_map>; } -namespace strategies { +namespace placers { template struct NfpPConfig { @@ -134,30 +172,12 @@ struct NfpPConfig { * that will optimize for the best pack efficiency. With a custom fitting * function you can e.g. influence the shape of the arranged pile. * - * \param shapes The first parameter is a container with all the placed - * polygons excluding the current candidate. You can calculate a bounding - * box or convex hull on this pile of polygons without the candidate item - * or push back the candidate item into the container and then calculate - * some features. - * - * \param item The second parameter is the candidate item. - * - * \param remaining A container with the remaining items waiting to be - * placed. You can use some features about the remaining items to alter to - * score of the current placement. If you know that you have to leave place - * for other items as well, that might influence your decision about where - * the current candidate should be placed. E.g. imagine three big circles - * which you want to place into a box: you might place them in a triangle - * shape which has the maximum pack density. But if there is a 4th big - * circle than you won't be able to pack it. If you knew apriori that - * there four circles are to be placed, you would have placed the first 3 - * into an L shape. This parameter can be used to make these kind of - * decisions (for you or a more intelligent AI). + * \param item The only parameter is the candidate item which has info + * about its current position. Your job is to rate this position compared to + * the already packed items. * */ - std::function&, const _Item&, - const ItemGroup&)> - object_function; + std::function&)> object_function; /** * @brief The quality of search for an optimal placement. @@ -180,6 +200,34 @@ struct NfpPConfig { */ bool parallel = true; + /** + * @brief before_packing Callback that is called just before a search for + * a new item's position is started. You can use this to create various + * cache structures and update them between subsequent packings. + * + * \param merged pile A polygon that is the union of all items in the bin. + * + * \param pile The items parameter is a container with all the placed + * polygons excluding the current candidate. You can for instance check the + * alignment with the candidate item or do anything else. + * + * \param remaining A container with the remaining items waiting to be + * placed. You can use some features about the remaining items to alter to + * score of the current placement. If you know that you have to leave place + * for other items as well, that might influence your decision about where + * the current candidate should be placed. E.g. imagine three big circles + * which you want to place into a box: you might place them in a triangle + * shape which has the maximum pack density. But if there is a 4th big + * circle than you won't be able to pack it. If you knew apriori that + * there four circles are to be placed, you would have placed the first 3 + * into an L shape. This parameter can be used to make these kind of + * decisions (for you or a more intelligent AI). + */ + std::function&, // merged pile + const ItemGroup&, // packed items + const ItemGroup& // remaining items + )> before_packing; + NfpPConfig(): rotations({0.0, Pi/2.0, Pi, 3*Pi/2}), alignment(Alignment::CENTER), starting_point(Alignment::CENTER) {} }; @@ -428,7 +476,7 @@ Circle minimizeCircle(const RawShape& sh) { opt::StopCriteria stopcr; - stopcr.max_iterations = 100; + stopcr.max_iterations = 30; stopcr.relative_score_difference = 1e-3; opt::TOptimizer solver(stopcr); @@ -590,35 +638,25 @@ private: { using namespace nfp; - Shapes nfps; + Shapes nfps(items_.size()); const Item& trsh = itsh.first; - // nfps.reserve(polygons.size()); - -// unsigned idx = 0; - for(Item& sh : items_) { - -// auto ik = item_keys_[idx++] + itsh.second; -// auto fnd = nfpcache_.find(ik); - -// nfp::NfpResult subnfp_r; -// if(fnd == nfpcache_.end()) { - - auto subnfp_r = noFitPolygon( - sh.transformedShape(), trsh.transformedShape()); -// nfpcache_[ik] = subnfp_r; -// } else { -// subnfp_r = fnd->second; -// } + __parallel::enumerate(items_.begin(), items_.end(), + [&nfps, &trsh](const Item& sh, size_t n) + { + auto& fixedp = sh.transformedShape(); + auto& orbp = trsh.transformedShape(); + auto subnfp_r = noFitPolygon(fixedp, orbp); correctNfpPosition(subnfp_r, sh, trsh); + nfps[n] = subnfp_r.first; + }); - // nfps.emplace_back(subnfp_r.first); - nfps = nfp::merge(nfps, subnfp_r.first); - } +// for(auto& n : nfps) { +// auto valid = sl::isValid(n); +// if(!valid.first) std::cout << "Warning: " << valid.second << std::endl; +// } - // nfps = nfp::merge(nfps); - - return nfps; + return nfp::merge(nfps); } template @@ -777,6 +815,21 @@ private: } }; + static Box boundingBox(const Box& pilebb, const Box& ibb ) { + auto& pminc = pilebb.minCorner(); + auto& pmaxc = pilebb.maxCorner(); + auto& iminc = ibb.minCorner(); + auto& imaxc = ibb.maxCorner(); + Vertex minc, maxc; + + setX(minc, std::min(getX(pminc), getX(iminc))); + setY(minc, std::min(getY(pminc), getY(iminc))); + + setX(maxc, std::max(getX(pmaxc), getX(imaxc))); + setY(maxc, std::max(getY(pmaxc), getY(imaxc))); + return Box(minc, maxc); + } + using Edges = EdgeCache; template> @@ -810,6 +863,7 @@ private: item.translation(initial_tr); item.rotation(initial_rot + rot); + item.boundingBox(); // fill the bb cache // place the new item outside of the print bed to make sure // it is disjunct from the current merged pile @@ -840,21 +894,17 @@ private: auto merged_pile = nfp::merge(pile); auto& bin = bin_; double norm = norm_; + auto pbb = sl::boundingBox(merged_pile); + auto binbb = sl::boundingBox(bin); // This is the kernel part of the object function that is // customizable by the library client auto _objfunc = config_.object_function? config_.object_function : - [norm, /*pile_area,*/ bin, merged_pile]( - const Pile& /*pile*/, - const Item& item, - const ItemGroup& /*remaining*/) + [norm, bin, binbb, pbb](const Item& item) { auto ibb = item.boundingBox(); - auto binbb = sl::boundingBox(bin); - auto mp = merged_pile; - mp.emplace_back(item.transformedShape()); - auto fullbb = sl::boundingBox(mp); + auto fullbb = boundingBox(pbb, ibb); double score = pl::distance(ibb.center(), binbb.center()); score /= norm; @@ -868,15 +918,12 @@ private: // Our object function for placement auto rawobjfunc = - [item, _objfunc, iv, - startpos, remlist, pile] (Vertex v) + [_objfunc, iv, startpos] (Vertex v, Item& itm) { auto d = v - iv; d += startpos; - Item itm = item; itm.translation(d); - - return _objfunc(pile, itm, remlist); + return _objfunc(itm); }; auto getNfpPoint = [&ecache](const Optimum& opt) @@ -906,6 +953,9 @@ private: std::launch policy = std::launch::deferred; if(config_.parallel) policy |= std::launch::async; + if(config_.before_packing) + config_.before_packing(merged_pile, items_, remlist); + using OptResult = opt::Result; using OptResults = std::vector; @@ -914,21 +964,27 @@ private: for(unsigned ch = 0; ch < ecache.size(); ch++) { auto& cache = ecache[ch]; - auto contour_ofn = [rawobjfunc, getNfpPoint, ch] - (double relpos) - { - return rawobjfunc(getNfpPoint(Optimum(relpos, ch))); - }; - OptResults results(cache.corners().size()); + auto& rofn = rawobjfunc; + auto& nfpoint = getNfpPoint; + __parallel::enumerate( cache.corners().begin(), cache.corners().end(), - [&contour_ofn, &results] - (double pos, unsigned n) + [&results, &item, &rofn, &nfpoint, ch] + (double pos, size_t n) { Optimizer solver; + + Item itemcpy = item; + auto contour_ofn = [&rofn, &nfpoint, ch, &itemcpy] + (double relpos) + { + Optimum op(relpos, ch); + return rofn(nfpoint(op), itemcpy); + }; + try { results[n] = solver.optimize_min(contour_ofn, opt::initvals(pos), @@ -959,24 +1015,27 @@ private: } for(unsigned hidx = 0; hidx < cache.holeCount(); ++hidx) { - auto hole_ofn = - [rawobjfunc, getNfpPoint, ch, hidx] - (double pos) - { - Optimum opt(pos, ch, hidx); - return rawobjfunc(getNfpPoint(opt)); - }; - results.clear(); results.resize(cache.corners(hidx).size()); // TODO : use parallel for __parallel::enumerate(cache.corners(hidx).begin(), cache.corners(hidx).end(), - [&hole_ofn, &results] - (double pos, unsigned n) + [&results, &item, &nfpoint, + &rofn, ch, hidx] + (double pos, size_t n) { Optimizer solver; + + Item itmcpy = item; + auto hole_ofn = + [&rofn, &nfpoint, ch, hidx, &itmcpy] + (double pos) + { + Optimum opt(pos, ch, hidx); + return rofn(nfpoint(opt), itmcpy); + }; + try { results[n] = solver.optimize_min(hole_ofn, opt::initvals(pos), diff --git a/xs/src/libnest2d/libnest2d/placers/placer_boilerplate.hpp b/xs/src/libnest2d/libnest2d/placers/placer_boilerplate.hpp index 44e2bc1b0..0df1b8c91 100644 --- a/xs/src/libnest2d/libnest2d/placers/placer_boilerplate.hpp +++ b/xs/src/libnest2d/libnest2d/placers/placer_boilerplate.hpp @@ -3,7 +3,7 @@ #include "../libnest2d.hpp" -namespace libnest2d { namespace strategies { +namespace libnest2d { namespace placers { struct EmptyConfig {}; diff --git a/xs/src/libnest2d/libnest2d/selections/djd_heuristic.hpp b/xs/src/libnest2d/libnest2d/selections/djd_heuristic.hpp index 846b00bad..ee93d0592 100644 --- a/xs/src/libnest2d/libnest2d/selections/djd_heuristic.hpp +++ b/xs/src/libnest2d/libnest2d/selections/djd_heuristic.hpp @@ -8,7 +8,7 @@ #include "selection_boilerplate.hpp" -namespace libnest2d { namespace strategies { +namespace libnest2d { namespace selections { /** * Selection heuristic based on [López-Camacho]\ diff --git a/xs/src/libnest2d/libnest2d/selections/filler.hpp b/xs/src/libnest2d/libnest2d/selections/filler.hpp index b20455b0e..0da7220a1 100644 --- a/xs/src/libnest2d/libnest2d/selections/filler.hpp +++ b/xs/src/libnest2d/libnest2d/selections/filler.hpp @@ -3,7 +3,7 @@ #include "selection_boilerplate.hpp" -namespace libnest2d { namespace strategies { +namespace libnest2d { namespace selections { template class _FillerSelection: public SelectionBoilerplate { diff --git a/xs/src/libnest2d/libnest2d/selections/firstfit.hpp b/xs/src/libnest2d/libnest2d/selections/firstfit.hpp index eb820a518..bca7497db 100644 --- a/xs/src/libnest2d/libnest2d/selections/firstfit.hpp +++ b/xs/src/libnest2d/libnest2d/selections/firstfit.hpp @@ -4,7 +4,7 @@ #include "../libnest2d.hpp" #include "selection_boilerplate.hpp" -namespace libnest2d { namespace strategies { +namespace libnest2d { namespace selections { template class _FirstFitSelection: public SelectionBoilerplate { diff --git a/xs/src/libnest2d/libnest2d/selections/selection_boilerplate.hpp b/xs/src/libnest2d/libnest2d/selections/selection_boilerplate.hpp index 59ef5cb23..05bbae658 100644 --- a/xs/src/libnest2d/libnest2d/selections/selection_boilerplate.hpp +++ b/xs/src/libnest2d/libnest2d/selections/selection_boilerplate.hpp @@ -3,8 +3,7 @@ #include "../libnest2d.hpp" -namespace libnest2d { -namespace strategies { +namespace libnest2d { namespace selections { template class SelectionBoilerplate { diff --git a/xs/src/libnest2d/tests/test.cpp b/xs/src/libnest2d/tests/test.cpp index b85bbc111..323fb8d31 100644 --- a/xs/src/libnest2d/tests/test.cpp +++ b/xs/src/libnest2d/tests/test.cpp @@ -102,17 +102,17 @@ TEST(BasicFunctionality, creationAndDestruction) TEST(GeometryAlgorithms, boundingCircle) { using namespace libnest2d; - using strategies::boundingCircle; + using placers::boundingCircle; PolygonImpl p = {{{0, 10}, {10, 0}, {0, -10}, {0, 10}}, {}}; - _Circle c = boundingCircle(p); + Circle c = boundingCircle(p); ASSERT_EQ(c.center().X, 0); ASSERT_EQ(c.center().Y, 0); ASSERT_DOUBLE_EQ(c.radius(), 10); shapelike::translate(p, PointImpl{10, 10}); - c = boundingCircle(p); + c = boundingCircle(p); ASSERT_EQ(c.center().X, 10); ASSERT_EQ(c.center().Y, 10); @@ -712,7 +712,7 @@ void testNfp(const std::vector& testdata) { auto& exportfun = exportSVG; - auto onetest = [&](Item& orbiter, Item& stationary){ + auto onetest = [&](Item& orbiter, Item& stationary, unsigned testidx){ testcase++; orbiter.translate({210*SCALE, 0}); @@ -720,15 +720,19 @@ void testNfp(const std::vector& testdata) { auto&& nfp = nfp::noFitPolygon(stationary.rawShape(), orbiter.transformedShape()); - strategies::correctNfpPosition(nfp, stationary, orbiter); + placers::correctNfpPosition(nfp, stationary, orbiter); - auto v = shapelike::isValid(nfp.first); + auto valid = shapelike::isValid(nfp.first); - if(!v.first) { - std::cout << v.second << std::endl; - } + /*Item infp(nfp.first); + if(!valid.first) { + std::cout << "test instance: " << testidx << " " + << valid.second << std::endl; + std::vector> inp = {std::ref(infp)}; + exportfun(inp, bin, testidx); + }*/ - ASSERT_TRUE(v.first); + ASSERT_TRUE(valid.first); Item infp(nfp.first); @@ -748,7 +752,7 @@ void testNfp(const std::vector& testdata) { bool touching = Item::touches(tmp, stationary); - if(!touching) { + if(!touching || !valid.first) { std::vector> inp = { std::ref(stationary), std::ref(tmp), std::ref(infp) }; @@ -760,16 +764,18 @@ void testNfp(const std::vector& testdata) { } }; + unsigned tidx = 0; for(auto& td : testdata) { auto orbiter = td.orbiter; auto stationary = td.stationary; - onetest(orbiter, stationary); + onetest(orbiter, stationary, tidx++); } + tidx = 0; for(auto& td : testdata) { auto orbiter = td.stationary; auto stationary = td.orbiter; - onetest(orbiter, stationary); + onetest(orbiter, stationary, tidx++); } } } @@ -796,7 +802,7 @@ TEST(GeometryAlgorithms, pointOnPolygonContour) { Rectangle input(10, 10); - strategies::EdgeCache ecache(input); + placers::EdgeCache ecache(input); auto first = *input.begin(); ASSERT_TRUE(getX(first) == getX(ecache.coords(0))); diff --git a/xs/src/libnest2d/tools/nfp_svgnest.hpp b/xs/src/libnest2d/tools/nfp_svgnest.hpp index 8ab571c00..ac5700c10 100644 --- a/xs/src/libnest2d/tools/nfp_svgnest.hpp +++ b/xs/src/libnest2d/tools/nfp_svgnest.hpp @@ -32,15 +32,18 @@ template struct _alg { #define dNAN std::nan("") struct Vector { - Coord x, y; + Coord x = 0.0, y = 0.0; bool marked = false; Vector() = default; Vector(Coord X, Coord Y): x(X), y(Y) {} - Vector(const Point& p): x(getX(p)), y(getY(p)) {} + Vector(const Point& p): x(Coord(getX(p))), y(Coord(getY(p))) {} operator Point() const { return {iCoord(x), iCoord(y)}; } Vector& operator=(const Point& p) { x = getX(p), y = getY(p); return *this; } + bool operator!=(const Vector& v) const { + return v.x != x || v.y != y; + } Vector(std::initializer_list il): x(*il.begin()), y(*std::next(il.begin())) {} }; @@ -58,8 +61,10 @@ template struct _alg { v_.reserve(c.size()); std::transform(c.begin(), c.end(), std::back_inserter(v_), [](const Point& p) { - return Vector(double(x(p))/1e6, double(y(p))/1e6); + return Vector(double(x(p)) / 1e6, double(y(p)) / 1e6); }); + std::reverse(v_.begin(), v_.end()); + v_.pop_back(); } Cntr() = default; @@ -67,8 +72,10 @@ template struct _alg { Coord offsety = 0; size_t size() const { return v_.size(); } bool empty() const { return v_.empty(); } - typename Contour::const_iterator begin() const { return v_.cbegin(); } - typename Contour::const_iterator end() const { return v_.cend(); } + typename std::vector::const_iterator cbegin() const { return v_.cbegin(); } + typename std::vector::const_iterator cend() const { return v_.cend(); } + typename std::vector::iterator begin() { return v_.begin(); } + typename std::vector::iterator end() { return v_.end(); } Vector& operator[](size_t idx) { return v_[idx]; } const Vector& operator[](size_t idx) const { return v_[idx]; } template @@ -83,12 +90,16 @@ template struct _alg { operator Contour() const { Contour cnt; - cnt.reserve(v_.size()); + cnt.reserve(v_.size() + 1); std::transform(v_.begin(), v_.end(), std::back_inserter(cnt), [](const Vector& vertex) { - return Point(iCoord(vertex.x*1e6), iCoord(vertex.y*1e6)); + return Point(iCoord(vertex.x) * 1000000, iCoord(vertex.y) * 1000000); }); - return cnt; + if(!cnt.empty()) cnt.emplace_back(cnt.front()); + S sh = shapelike::create(cnt); + +// std::reverse(cnt.begin(), cnt.end()); + return shapelike::getContour(sh); } }; @@ -235,11 +246,11 @@ template struct _alg { if ((_almostEqual(pdot, s1dot) && _almostEqual(pdot, s2dot)) && (pdotnorm>s1dotnorm && pdotnorm>s2dotnorm)) { - return double(min(pdotnorm - s1dotnorm, pdotnorm - s2dotnorm)); + return min(pdotnorm - s1dotnorm, pdotnorm - s2dotnorm); } if ((_almostEqual(pdot, s1dot) && _almostEqual(pdot, s2dot)) && (pdotnorm struct _alg { auto EFmin = min(dotE, dotF); // segments that will merely touch at one point - if(_almostEqual(ABmax, EFmin,TOL) || _almostEqual(ABmin, EFmax,TOL)){ + if(_almostEqual(ABmax, EFmin, TOL) || _almostEqual(ABmin, EFmax,TOL)) { return dNAN; } // segments miss eachother completely @@ -362,7 +373,7 @@ template struct _alg { d = dNAN; } } - if(isnan(d)){ + if(!isnan(d)){ distances.emplace_back(d); } } @@ -392,7 +403,7 @@ template struct _alg { auto d = pointDistance(E,A,B,direction); if(!isnan(d) && _almostEqual(d, 0)) { // crossF struct _alg { if(!isnan(d) && _almostEqual(d, 0)) { // && crossE struct _alg { return *std::min_element(distances.begin(), distances.end()); } - static double polygonSlideDistance( const Cntr& A, - const Cntr& B, + static double polygonSlideDistance( const Cntr& AA, + const Cntr& BB, Vector direction, bool ignoreNegative) { // Vector A1, A2, B1, B2; + Cntr A = AA; + Cntr B = BB; + Coord Aoffsetx = A.offsetx; Coord Boffsetx = B.offsetx; Coord Aoffsety = A.offsety; Coord Boffsety = B.offsety; + // close the loop for polygons + if(A[0] != A[A.size()-1]){ + A.emplace_back(AA[0]); + } + + if(B[0] != B[B.size()-1]){ + B.emplace_back(BB[0]); + } + auto& edgeA = A; auto& edgeB = B; @@ -457,7 +480,7 @@ template struct _alg { Vector A1 = {x(edgeA[j]) + Aoffsetx, y(edgeA[j]) + Aoffsety }; Vector A2 = {x(edgeA[j+1]) + Aoffsetx, y(edgeA[j+1]) + Aoffsety}; Vector B1 = {x(edgeB[i]) + Boffsetx, y(edgeB[i]) + Boffsety }; - Vector B2 = {x(edgeB[i+1]) + Boffsety, y(edgeB[i+1]) + Boffsety}; + Vector B2 = {x(edgeB[i+1]) + Boffsetx, y(edgeB[i+1]) + Boffsety}; if((_almostEqual(A1.x, A2.x) && _almostEqual(A1.y, A2.y)) || (_almostEqual(B1.x, B2.x) && _almostEqual(B1.y, B2.y))){ @@ -476,23 +499,26 @@ template struct _alg { return distance; } - static double polygonProjectionDistance(const Cntr& A, - const Cntr& B, + static double polygonProjectionDistance(const Cntr& AA, + const Cntr& BB, Vector direction) { + Cntr A = AA; + Cntr B = BB; + auto Boffsetx = B.offsetx; auto Boffsety = B.offsety; auto Aoffsetx = A.offsetx; auto Aoffsety = A.offsety; // close the loop for polygons - /*if(A[0] != A[A.length-1]){ + if(A[0] != A[A.size()-1]){ A.push(A[0]); } - if(B[0] != B[B.length-1]){ + if(B[0] != B[B.size()-1]){ B.push(B[0]); - }*/ + } auto& edgeA = A; auto& edgeB = B; @@ -665,7 +691,7 @@ template struct _alg { A.offsetx = 0; A.offsety = 0; - unsigned i = 0, j = 0; + long i = 0, j = 0; auto minA = y(A[0]); long minAindex = 0; @@ -709,7 +735,8 @@ template struct _alg { struct Touch { int type; - long A, B; + long A; + long B; Touch(int t, long a, long b): type(t), A(a), B(b) {} }; @@ -721,6 +748,15 @@ template struct _alg { // maintain a list of touching points/edges std::vector touching; + struct V { + Coord x, y; + Vector *start, *end; + operator bool() { + return start != nullptr && end != nullptr; + } + operator Vector() const { return {x, y}; } + } prevvector = {0, 0, nullptr, nullptr}; + Cntr NFP; NFP.emplace_back(x(B[0]) + B.offsetx, y(B[0]) + B.offsety); @@ -736,10 +772,10 @@ template struct _alg { // find touching vertices/edges for(i = 0; i < A.size(); i++){ - auto nexti = (i == A.size() - 1) ? 0 : i + 1; + long nexti = (i == A.size() - 1) ? 0 : i + 1; for(j = 0; j < B.size(); j++){ - auto nextj = (j == B.size() - 1) ? 0 : j + 1; + long nextj = (j == B.size() - 1) ? 0 : j + 1; if( _almostEqual(A[i].x, B[j].x+B.offsetx) && _almostEqual(A[i].y, B[j].y+B.offsety)) @@ -762,15 +798,6 @@ template struct _alg { } } - struct V { - Coord x, y; - Vector *start, *end; - operator bool() { - return start != nullptr && end != nullptr; - } - operator Vector() const { return {x, y}; } - }; - // generate translation vectors from touching vertices/edges std::vector vectors; for(i=0; i < touching.size(); i++){ @@ -871,7 +898,6 @@ template struct _alg { // will cause immediate intersection. For now just check them all V translate = {0, 0, nullptr, nullptr}; - V prevvector = {0, 0, nullptr, nullptr}; double maxd = 0; for(i = 0; i < vectors.size(); i++) { @@ -897,7 +923,8 @@ template struct _alg { } } - double d = polygonSlideDistance(A, B, vectors[i], true); + V vi = vectors[i]; + double d = polygonSlideDistance(A, B, vi, true); double vecd2 = vectors[i].x*vectors[i].x + vectors[i].y*vectors[i].y; if(isnan(d) || d*d > vecd2){ @@ -985,19 +1012,6 @@ template struct _alg { template const double _alg::TOL = std::pow(10, -9); -//template -//nfp::NfpResult nfpSimpleSimple(const S& stat, const S& orb) { -//// using Cntr = TContour; -// using Point = TPoint; -//// using Coord = TCoord; -//// using Shapes = nfp::Shapes; - -// namespace sl = shapelike; - -// noFitPolygon(sl::getContour(stat), sl::getContour(orb), true, true); -// return {S(), Point()}; -//} - } } diff --git a/xs/src/libnest2d/tools/nfp_svgnest_glue.hpp b/xs/src/libnest2d/tools/nfp_svgnest_glue.hpp index 7ceb2d24d..ea1fb4d07 100644 --- a/xs/src/libnest2d/tools/nfp_svgnest_glue.hpp +++ b/xs/src/libnest2d/tools/nfp_svgnest_glue.hpp @@ -25,13 +25,11 @@ template<> struct NfpImpl { namespace sl = shapelike; using alg = __svgnest::_alg; - std::cout << "Itt vagyok" << std::endl; auto nfp_p = alg::noFitPolygon(sl::getContour(sh), sl::getContour(cother), false, false); PolygonImpl nfp_cntr; - nfp_cntr.Contour = nfp_p.front(); - std::cout << "Contour size: " << nfp_cntr.Contour.size() << std::endl; + if(!nfp_p.empty()) nfp_cntr.Contour = nfp_p.front(); return {nfp_cntr, referenceVertex(nfp_cntr)}; } }; diff --git a/xs/src/libslic3r/ModelArrange.hpp b/xs/src/libslic3r/ModelArrange.hpp index dcb0da9e5..618230cb7 100644 --- a/xs/src/libslic3r/ModelArrange.hpp +++ b/xs/src/libslic3r/ModelArrange.hpp @@ -100,55 +100,54 @@ namespace bgi = boost::geometry::index; using SpatElement = std::pair; using SpatIndex = bgi::rtree< SpatElement, bgi::rstar<16, 4> >; using ItemGroup = std::vector>; +template +using TPacker = typename placers::_NofitPolyPlacer; + +const double BIG_ITEM_TRESHOLD = 0.02; + +Box boundingBox(const Box& pilebb, const Box& ibb ) { + auto& pminc = pilebb.minCorner(); + auto& pmaxc = pilebb.maxCorner(); + auto& iminc = ibb.minCorner(); + auto& imaxc = ibb.maxCorner(); + PointImpl minc, maxc; + + setX(minc, std::min(getX(pminc), getX(iminc))); + setY(minc, std::min(getY(pminc), getY(iminc))); + + setX(maxc, std::max(getX(pmaxc), getX(imaxc))); + setY(maxc, std::max(getY(pmaxc), getY(imaxc))); + return Box(minc, maxc); +} std::tuple objfunc(const PointImpl& bincenter, - double bin_area, - sl::Shapes& pile, // The currently arranged pile + const shapelike::Shapes& merged_pile, + const Box& pilebb, + const ItemGroup& items, const Item &item, + double bin_area, double norm, // A norming factor for physical dimensions - std::vector& areacache, // pile item areas will be cached // a spatial index to quickly get neighbors of the candidate item - SpatIndex& spatindex, + const SpatIndex& spatindex, const ItemGroup& remaining ) { using Coord = TCoord; - static const double BIG_ITEM_TRESHOLD = 0.02; static const double ROUNDNESS_RATIO = 0.5; static const double DENSITY_RATIO = 1.0 - ROUNDNESS_RATIO; // We will treat big items (compared to the print bed) differently - auto isBig = [&areacache, bin_area](double a) { + auto isBig = [bin_area](double a) { return a/bin_area > BIG_ITEM_TRESHOLD ; }; - // If a new bin has been created: - if(pile.size() < areacache.size()) { - areacache.clear(); - spatindex.clear(); - } - - // We must fill the caches: - int idx = 0; - for(auto& p : pile) { - if(idx == areacache.size()) { - areacache.emplace_back(sl::area(p)); - if(isBig(areacache[idx])) - spatindex.insert({sl::boundingBox(p), idx}); - } - - idx++; - } - // Candidate item bounding box - auto ibb = item.boundingBox(); + auto ibb = sl::boundingBox(item.transformedShape()); // Calculate the full bounding box of the pile with the candidate item - pile.emplace_back(item.transformedShape()); - auto fullbb = sl::boundingBox(pile); - pile.pop_back(); + auto fullbb = boundingBox(pilebb, ibb); // The bounding box of the big items (they will accumulate in the center // of the pile @@ -189,10 +188,12 @@ objfunc(const PointImpl& bincenter, double density = 0; if(remaining.empty()) { - pile.emplace_back(item.transformedShape()); - auto chull = sl::convexHull(pile); - pile.pop_back(); - strategies::EdgeCache ec(chull); + + auto mp = merged_pile; + mp.emplace_back(item.transformedShape()); + auto chull = sl::convexHull(mp); + + placers::EdgeCache ec(chull); double circ = ec.circumference() / norm; double bcirc = 2.0*(fullbb.width() + fullbb.height()) / norm; @@ -201,16 +202,15 @@ objfunc(const PointImpl& bincenter, } else { // Prepare a variable for the alignment score. // This will indicate: how well is the candidate item aligned with - // its neighbors. We will check the aligment with all neighbors and + // its neighbors. We will check the alignment with all neighbors and // return the score for the best alignment. So it is enough for the // candidate to be aligned with only one item. auto alignment_score = 1.0; density = (fullbb.width()*fullbb.height()) / (norm*norm); - auto& trsh = item.transformedShape(); auto querybb = item.boundingBox(); - // Query the spatial index for the neigbours + // Query the spatial index for the neighbors std::vector result; result.reserve(spatindex.size()); spatindex.query(bgi::intersects(querybb), @@ -218,10 +218,10 @@ objfunc(const PointImpl& bincenter, for(auto& e : result) { // now get the score for the best alignment auto idx = e.second; - auto& p = pile[idx]; - auto parea = areacache[idx]; + Item& p = items[idx]; + auto parea = p.area(); if(std::abs(1.0 - parea/item.area()) < 1e-6) { - auto bb = sl::boundingBox(sl::Shapes{p, trsh}); + auto bb = boundingBox(p.boundingBox(), ibb); auto bbarea = bb.area(); auto ascore = 1.0 - (item.area() + parea)/bbarea; @@ -231,7 +231,7 @@ objfunc(const PointImpl& bincenter, // The final mix of the score is the balance between the distance // from the full pile center, the pack density and the - // alignment with the neigbours + // alignment with the neighbors if(result.empty()) score = 0.5 * dist + 0.5 * density; else @@ -239,7 +239,6 @@ objfunc(const PointImpl& bincenter, } } else if( !isBig(item.area()) && spatindex.empty()) { auto bindist = pl::distance(ibb.center(), bincenter) / norm; - // Bindist is surprisingly enough... score = bindist; } else { @@ -271,7 +270,7 @@ void fillConfig(PConf& pcfg) { // Goes from 0.0 to 1.0 and scales performance as well pcfg.accuracy = 0.65f; - pcfg.parallel = false; + pcfg.parallel = true; } template @@ -280,7 +279,8 @@ class AutoArranger {}; template class _ArrBase { protected: - using Placer = strategies::_NofitPolyPlacer; + + using Placer = TPacker; using Selector = FirstFitSelection; using Packer = Nester; using PConfig = typename Packer::PlacementConfig; @@ -290,10 +290,12 @@ protected: Packer pck_; PConfig pconf_; // Placement configuration double bin_area_; - std::vector areacache_; SpatIndex rtree_; double norm_; - Pile pile_cache_; + Pile merged_pile_; + Box pilebb_; + ItemGroup remaining_; + ItemGroup items_; public: _ArrBase(const TBin& bin, Distance dist, @@ -302,11 +304,35 @@ public: norm_(std::sqrt(sl::area(bin))) { fillConfig(pconf_); + + pconf_.before_packing = + [this](const Pile& merged_pile, // merged pile + const ItemGroup& items, // packed items + const ItemGroup& remaining) // future items to be packed + { + items_ = items; + merged_pile_ = merged_pile; + remaining_ = remaining; + + pilebb_ = sl::boundingBox(merged_pile); + + rtree_.clear(); + + // We will treat big items (compared to the print bed) differently + auto isBig = [this](double a) { + return a/bin_area_ > BIG_ITEM_TRESHOLD ; + }; + + for(unsigned idx = 0; idx < items.size(); ++idx) { + Item& itm = items[idx]; + if(isBig(itm.area())) rtree_.insert({itm.boundingBox(), idx}); + } + }; + pck_.progressIndicator(progressind); } template inline IndexedPackGroup operator()(Args&&...args) { - areacache_.clear(); rtree_.clear(); return pck_.executeIndexed(std::forward(args)...); } @@ -320,26 +346,28 @@ public: std::function progressind): _ArrBase(bin, dist, progressind) { -// pconf_.object_function = [this, bin] ( -// const Pile& pile_c, -// const Item &item, -// const ItemGroup& rem) { -// auto& pile = pile_cache_; -// if(pile.size() != pile_c.size()) pile = pile_c; + pconf_.object_function = [this, bin] (const Item &item) { -// auto result = objfunc(bin.center(), bin_area_, pile, -// item, norm_, areacache_, rtree_, rem); -// double score = std::get<0>(result); -// auto& fullbb = std::get<1>(result); + auto result = objfunc(bin.center(), + merged_pile_, + pilebb_, + items_, + item, + bin_area_, + norm_, + rtree_, + remaining_); -// auto wdiff = fullbb.width() - bin.width(); -// auto hdiff = fullbb.height() - bin.height(); -// if(wdiff > 0) score += std::pow(wdiff, 2) / norm_; -// if(hdiff > 0) score += std::pow(hdiff, 2) / norm_; + double score = std::get<0>(result); + auto& fullbb = std::get<1>(result); -// return score; -// }; + double miss = Placer::overfit(fullbb, bin); + miss = miss > 0? miss : 0; + score += miss*miss; + + return score; + }; pck_.configure(pconf_); } @@ -355,36 +383,31 @@ public: std::function progressind): _ArrBase(bin, dist, progressind) { - pconf_.object_function = [this, &bin] ( - const Pile& pile_c, - const Item &item, - const ItemGroup& rem) { + pconf_.object_function = [this, &bin] (const Item &item) { - auto& pile = pile_cache_; - if(pile.size() != pile_c.size()) pile = pile_c; + auto result = objfunc(bin.center(), + merged_pile_, + pilebb_, + items_, + item, + bin_area_, + norm_, + rtree_, + remaining_); - auto result = objfunc(bin.center(), bin_area_, pile, item, norm_, - areacache_, rtree_, rem); double score = std::get<0>(result); - auto& fullbb = std::get<1>(result); - auto d = pl::distance(fullbb.minCorner(), - fullbb.maxCorner()); - auto diff = d - 2*bin.radius(); + auto isBig = [this](const Item& itm) { + return itm.area()/bin_area_ > BIG_ITEM_TRESHOLD ; + }; - if(diff > 0) { - if( item.area() > 0.01*bin_area_ && item.vertexCount() < 30) { - pile.emplace_back(item.transformedShape()); - auto chull = sl::convexHull(pile); - pile.pop_back(); - - auto C = strategies::boundingCircle(chull); - auto rdiff = C.radius() - bin.radius(); - - if(rdiff > 0) { - score += std::pow(rdiff, 3) / norm_; - } - } + if(isBig(item)) { + auto mp = merged_pile_; + mp.push_back(item.transformedShape()); + auto chull = sl::convexHull(mp); + double miss = Placer::overfit(chull, bin); + if(miss < 0) miss = 0; + score += miss*miss; } return score; @@ -401,17 +424,18 @@ public: std::function progressind): _ArrBase(bin, dist, progressind) { - pconf_.object_function = [this, &bin] ( - const Pile& pile_c, - const Item &item, - const ItemGroup& rem) { - - auto& pile = pile_cache_; - if(pile.size() != pile_c.size()) pile = pile_c; + pconf_.object_function = [this, &bin] (const Item &item) { auto binbb = sl::boundingBox(bin); - auto result = objfunc(binbb.center(), bin_area_, pile, item, norm_, - areacache_, rtree_, rem); + auto result = objfunc(binbb.center(), + merged_pile_, + pilebb_, + items_, + item, + bin_area_, + norm_, + rtree_, + remaining_); double score = std::get<0>(result); return score; @@ -428,16 +452,17 @@ public: AutoArranger(Distance dist, std::function progressind): _ArrBase(Box(0, 0), dist, progressind) { - this->pconf_.object_function = [this] ( - const Pile& pile_c, - const Item &item, - const ItemGroup& rem) { + this->pconf_.object_function = [this] (const Item &item) { - auto& pile = pile_cache_; - if(pile.size() != pile_c.size()) pile = pile_c; - - auto result = objfunc({0, 0}, 0, pile, item, norm_, - areacache_, rtree_, rem); + auto result = objfunc({0, 0}, + merged_pile_, + pilebb_, + items_, + item, + 0, + norm_, + rtree_, + remaining_); return std::get<0>(result); };