From d136d61edded36ad36f17ee14b4b954a55d69db5 Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Mon, 30 Jul 2018 15:16:44 +0200 Subject: [PATCH 1/6] linest2d ready for arbitrary shaped beds. --- xs/src/libnest2d/examples/main.cpp | 123 +++++++++++++---- xs/src/libnest2d/libnest2d/boost_alg.hpp | 2 +- .../libnest2d/libnest2d/geometry_traits.hpp | 77 ++++++++++- xs/src/libnest2d/libnest2d/libnest2d.hpp | 11 ++ .../libnest2d/libnest2d/placers/nfpplacer.hpp | 130 ++++++++++++------ .../libnest2d/selections/djd_heuristic.hpp | 2 +- .../libnest2d/selections/firstfit.hpp | 2 +- xs/src/libslic3r/Model.cpp | 5 +- 8 files changed, 275 insertions(+), 77 deletions(-) diff --git a/xs/src/libnest2d/examples/main.cpp b/xs/src/libnest2d/examples/main.cpp index a97618578..883d12610 100644 --- a/xs/src/libnest2d/examples/main.cpp +++ b/xs/src/libnest2d/examples/main.cpp @@ -544,57 +544,126 @@ 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}, + {0, 25*SCALE}, + {0, 225*SCALE}, + {25*SCALE, 250*SCALE}, + {225*SCALE, 250*SCALE}, + {250*SCALE, 225*SCALE}, + {250*SCALE, 25*SCALE}, + {225*SCALE, 0}, + {25*SCALE, 0} + }, + {} + }; auto min_obj_distance = static_cast(0*SCALE); - using Placer = NfpPlacer; + using Placer = strategies::_NofitPolyPlacer; using Packer = Arranger; Packer arrange(bin, min_obj_distance); Packer::PlacementConfig pconf; pconf.alignment = Placer::Config::Alignment::CENTER; - pconf.starting_point = Placer::Config::Alignment::BOTTOM_LEFT; + pconf.starting_point = Placer::Config::Alignment::CENTER; pconf.rotations = {0.0/*, Pi/2.0, Pi, 3*Pi/2*/}; + pconf.accuracy = 1.0; - double norm_2 = std::nan(""); - pconf.object_function = [&bin, &norm_2](Placer::Pile pile, const Item& item, + 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; - auto bb = ShapeLike::boundingBox(pile); + 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(); - auto minc = ibb.minCorner(); - auto maxc = ibb.maxCorner(); - if(std::isnan(norm_2)) norm_2 = pow(norm, 2); + // 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(); - // We get the distance of the reference point from the center of the - // heat bed - auto cc = bb.center(); - auto top_left = PointImpl{getX(minc), getY(maxc)}; - auto bottom_right = PointImpl{getX(maxc), getY(minc)}; + // The bounding box of the big items (they will accumulate in the center + // of the pile + auto bigbb = bigs.empty()? fullbb : ShapeLike::boundingBox(bigs); - auto a = pl::distance(ibb.maxCorner(), cc); - auto b = pl::distance(ibb.minCorner(), cc); - auto c = pl::distance(ibb.center(), cc); - auto d = pl::distance(top_left, cc); - auto e = pl::distance(bottom_right, cc); + // The size indicator of the candidate item. This is not the area, + // but almost... + auto itemnormarea = std::sqrt(ibb.width()*ibb.height())/norm; - auto area = bb.width() * bb.height() / norm_2; + // Will hold the resulting score + double score = 0; - auto min_dist = std::min({a, b, c, d, e}) / norm; + 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. - // The score will be the normalized distance which will be minimized, - // effectively creating a circle shaped pile of items - double score = 0.8*min_dist + 0.2*area; + 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 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(bb, bin)) score = 2*penality - score; + if(!NfpPlacer::wouldFit(fullbb, bin)) score = 2*penality - score; return score; }; @@ -638,7 +707,7 @@ void arrangeRectangles() { std::vector eff; eff.reserve(result.size()); - auto bin_area = double(bin.height()*bin.width()); + auto bin_area = ShapeLike::area(bin); for(auto& r : result) { double a = 0; std::for_each(r.begin(), r.end(), [&a] (Item& e ){ a += e.area(); }); @@ -673,7 +742,7 @@ void arrangeRectangles() { SVGWriter::Config conf; conf.mm_in_coord_units = SCALE; SVGWriter svgw(conf); - svgw.setSize(bin); + 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 422616d20..67e19fcbd 100644 --- a/xs/src/libnest2d/libnest2d/boost_alg.hpp +++ b/xs/src/libnest2d/libnest2d/boost_alg.hpp @@ -358,7 +358,7 @@ inline double ShapeLike::area(const PolygonImpl& shape) #endif template<> -inline bool ShapeLike::isInside(const PointImpl& point, +inline bool ShapeLike::isInside(const PointImpl& point, const PolygonImpl& shape) { return boost::geometry::within(point, shape); diff --git a/xs/src/libnest2d/libnest2d/geometry_traits.hpp b/xs/src/libnest2d/libnest2d/geometry_traits.hpp index 568c0a766..99511d775 100644 --- a/xs/src/libnest2d/libnest2d/geometry_traits.hpp +++ b/xs/src/libnest2d/libnest2d/geometry_traits.hpp @@ -3,6 +3,7 @@ #include #include +#include #include #include #include @@ -85,6 +86,31 @@ public: inline TCoord height() const BP2D_NOEXCEPT; inline RawPoint center() const BP2D_NOEXCEPT; + + inline double area() const BP2D_NOEXCEPT { + return double(width()*height()); + } +}; + +template +class _Circle { + RawPoint center_; + double radius_ = 0; +public: + + _Circle() = default; + + _Circle(const RawPoint& center, double r): center_(center), radius_(r) {} + + inline const RawPoint& center() const BP2D_NOEXCEPT { return center_; } + inline const void center(const RawPoint& c) { center_ = c; } + + inline double radius() const BP2D_NOEXCEPT { return radius_; } + inline void radius(double r) { radius_ = r; } + + inline double area() const BP2D_NOEXCEPT { + return 2.0*Pi*radius_; + } }; /** @@ -288,8 +314,8 @@ 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) ) + static_cast( (getX(minc) + getX(maxc))/2.0 ), + static_cast( (getY(minc) + getY(maxc))/2.0 ) }; return ret; @@ -614,12 +640,34 @@ struct ShapeLike { return box; } + template + static inline _Box> boundingBox( + const _Circle>& circ) + { + using Coord = TCoord>; + TPoint pmin = { + static_cast(getX(circ.center()) - circ.radius()), + static_cast(getY(circ.center()) - circ.radius()) }; + + TPoint 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) { return static_cast(box.width() * box.height()); } + template + static inline double area(const _Circle>& circ) + { + return circ.area(); + } + template static inline double area(const Shapes& shapes) { @@ -629,6 +677,31 @@ struct ShapeLike { }); } + template + static bool isInside(const TPoint& point, + const _Circle>& circ) + { + return PointLike::distance(point, circ.center()) < circ.radius(); + } + + template + static bool isInside(const RawShape& sh, + const _Circle>& circ) + { + return std::all_of(cbegin(sh), cend(sh), + [&circ](const TPoint& p){ + return isInside(p, circ); + }); + } + + template + static bool isInside(const _Box>& box, + const _Circle>& circ) + { + return isInside(box.minCorner(), circ) && + isInside(box.maxCorner(), circ); + } + template // Potential O(1) implementation may exist static inline TPoint& vertex(RawShape& sh, unsigned long idx) { diff --git a/xs/src/libnest2d/libnest2d/libnest2d.hpp b/xs/src/libnest2d/libnest2d/libnest2d.hpp index 37b5fea95..1aa672447 100644 --- a/xs/src/libnest2d/libnest2d/libnest2d.hpp +++ b/xs/src/libnest2d/libnest2d/libnest2d.hpp @@ -254,7 +254,13 @@ public: return sl::isInside(transformedShape(), sh.transformedShape()); } + inline bool isInside(const RawShape& sh) const + { + return sl::isInside(transformedShape(), sh); + } + inline bool isInside(const _Box>& box) const; + inline bool isInside(const _Circle>& box) const; inline void translate(const Vertex& d) BP2D_NOEXCEPT { @@ -471,6 +477,11 @@ inline bool _Item::isInside(const _Box>& box) const { return _Item::isInside(rect); } +template inline bool +_Item::isInside(const _Circle>& circ) const { + return ShapeLike::isInside(transformedShape(), circ); +} + /** * \brief A wrapper interface (trait) class for any placement strategy provider. * diff --git a/xs/src/libnest2d/libnest2d/placers/nfpplacer.hpp b/xs/src/libnest2d/libnest2d/placers/nfpplacer.hpp index 61d923b87..6ae71bb48 100644 --- a/xs/src/libnest2d/libnest2d/placers/nfpplacer.hpp +++ b/xs/src/libnest2d/libnest2d/placers/nfpplacer.hpp @@ -46,14 +46,12 @@ struct NfpPConfig { * 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 including the current candidate. You can calculate a bounding - * box or convex hull on this pile of polygons. + * 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. Note that - * calling transformedShape() on this second argument returns an identical - * shape as calling shapes.back(). These would not be the same objects only - * identical shapes! Using the second parameter is a lot faster due to - * caching some properties of the polygon (area, etc...) + * \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 @@ -127,6 +125,8 @@ template class EdgeCache { std::vector holes_; + double accuracy_ = 1.0; + void createCache(const RawShape& sh) { { // For the contour auto first = ShapeLike::cbegin(sh); @@ -160,11 +160,25 @@ template class EdgeCache { } } + size_t stride(const size_t N) const { + using std::ceil; + using std::round; + using std::pow; + + return static_cast( + round( N/(ceil(pow(accuracy_, 2)*(N-1)) + 1) ) + ); + } + void fetchCorners() const { if(!contour_.corners.empty()) return; - contour_.corners.reserve(contour_.distances.size() / 3 + 1); - for(size_t i = 0; i < contour_.distances.size() - 1; i += 3) { + const auto N = contour_.distances.size(); + const auto S = stride(N); + + contour_.corners.reserve(N / S + 1); + auto N_1 = N-1; + for(size_t i = 0; i < N_1; i += S) { contour_.corners.emplace_back( contour_.distances.at(i) / contour_.full_distance); } @@ -174,8 +188,11 @@ template class EdgeCache { auto& hc = holes_[hidx]; if(!hc.corners.empty()) return; - hc.corners.reserve(hc.distances.size() / 3 + 1); - for(size_t i = 0; i < hc.distances.size() - 1; i += 3) { + const auto N = hc.distances.size(); + const auto S = stride(N); + auto N_1 = N-1; + hc.corners.reserve(N / S + 1); + for(size_t i = 0; i < N_1; i += S) { hc.corners.emplace_back( hc.distances.at(i) / hc.full_distance); } @@ -224,6 +241,9 @@ public: createCache(sh); } + /// Resolution of returned corners. The stride is derived from this value. + void accuracy(double a /* within <0.0, 1.0>*/) { accuracy_ = a; } + /** * @brief Get a point on the circumference of a polygon. * @param distance A relative distance from the starting point to the end. @@ -419,12 +439,12 @@ Nfp::Shapes nfp( const Container& polygons, // return nfps; } -template -class _NofitPolyPlacer: public PlacerBoilerplate<_NofitPolyPlacer, - RawShape, _Box>, NfpPConfig> { +template>> +class _NofitPolyPlacer: public PlacerBoilerplate<_NofitPolyPlacer, + RawShape, TBin, NfpPConfig> { - using Base = PlacerBoilerplate<_NofitPolyPlacer, - RawShape, _Box>, NfpPConfig>; + using Base = PlacerBoilerplate<_NofitPolyPlacer, + RawShape, TBin, NfpPConfig>; DECLARE_PLACER(Base) @@ -434,6 +454,7 @@ class _NofitPolyPlacer: public PlacerBoilerplate<_NofitPolyPlacer, const double penality_; using MaxNfpLevel = Nfp::MaxNfpLevel; + using sl = ShapeLike; public: @@ -441,7 +462,7 @@ public: inline explicit _NofitPolyPlacer(const BinType& bin): Base(bin), - norm_(std::sqrt(ShapeLike::area(bin))), + norm_(std::sqrt(sl::area(bin))), penality_(1e6*norm_) {} _NofitPolyPlacer(const _NofitPolyPlacer&) = default; @@ -452,18 +473,26 @@ public: _NofitPolyPlacer& operator=(_NofitPolyPlacer&&) BP2D_NOEXCEPT = default; #endif + bool static inline wouldFit(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); + } + bool static inline wouldFit(const RawShape& chull, const RawShape& bin) { - auto bbch = ShapeLike::boundingBox(chull); - auto bbin = ShapeLike::boundingBox(bin); - auto d = bbin.minCorner() - bbch.minCorner(); + auto bbch = sl::boundingBox(chull); + auto bbin = sl::boundingBox(bin); + auto d = bbin.center() - bbch.center(); auto chullcpy = chull; - ShapeLike::translate(chullcpy, d); - return ShapeLike::isInside(chullcpy, bbin); + sl::translate(chullcpy, d); + return sl::isInside(chullcpy, bin); } bool static inline wouldFit(const RawShape& chull, const Box& bin) { - auto bbch = ShapeLike::boundingBox(chull); + auto bbch = sl::boundingBox(chull); return wouldFit(bbch, bin); } @@ -472,6 +501,17 @@ public: return bb.width() <= bin.width() && bb.height() <= bin.height(); } + 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); + } + PackResult trypack(Item& item) { PackResult ret; @@ -510,7 +550,10 @@ public: std::vector> ecache; ecache.reserve(nfps.size()); - for(auto& nfp : nfps ) ecache.emplace_back(nfp); + for(auto& nfp : nfps ) { + ecache.emplace_back(nfp); + ecache.back().accuracy(config_.accuracy); + } struct Optimum { double relpos; @@ -540,14 +583,16 @@ public: // customizable by the library client auto _objfunc = config_.object_function? config_.object_function : - [this](Nfp::Shapes& pile, Item, + [this](Nfp::Shapes& pile, const Item& item, double occupied_area, double /*norm*/, double penality) { - auto ch = ShapeLike::convexHull(pile); + pile.emplace_back(item.transformedShape()); + auto ch = sl::convexHull(pile); + pile.pop_back(); // The pack ratio -- how much is the convex hull occupied - double pack_rate = occupied_area/ShapeLike::area(ch); + double pack_rate = occupied_area/sl::area(ch); // ratio of waste double waste = 1.0 - pack_rate; @@ -569,22 +614,17 @@ public: d += startpos; item.translation(d); -// pile.emplace_back(item.transformedShape()); - double occupied_area = pile_area + item.area(); double score = _objfunc(pile, item, occupied_area, norm_, penality_); -// pile.pop_back(); - return score; }; opt::StopCriteria stopcr; stopcr.max_iterations = 1000; stopcr.absolute_score_difference = 1e-20*norm_; -// stopcr.relative_score_difference = 1e-20; opt::TOptimizer solver(stopcr); Optimum optimum(0, 0); @@ -702,34 +742,35 @@ public: m.reserve(items_.size()); for(Item& item : items_) m.emplace_back(item.transformedShape()); - auto&& bb = ShapeLike::boundingBox(m); + auto&& bb = sl::boundingBox(m); Vertex ci, cb; + auto bbin = sl::boundingBox(bin_); switch(config_.alignment) { case Config::Alignment::CENTER: { ci = bb.center(); - cb = bin_.center(); + cb = bbin.center(); break; } case Config::Alignment::BOTTOM_LEFT: { ci = bb.minCorner(); - cb = bin_.minCorner(); + cb = bbin.minCorner(); break; } case Config::Alignment::BOTTOM_RIGHT: { ci = {getX(bb.maxCorner()), getY(bb.minCorner())}; - cb = {getX(bin_.maxCorner()), getY(bin_.minCorner())}; + cb = {getX(bbin.maxCorner()), getY(bbin.minCorner())}; break; } case Config::Alignment::TOP_LEFT: { ci = {getX(bb.minCorner()), getY(bb.maxCorner())}; - cb = {getX(bin_.minCorner()), getY(bin_.maxCorner())}; + cb = {getX(bbin.minCorner()), getY(bbin.maxCorner())}; break; } case Config::Alignment::TOP_RIGHT: { ci = bb.maxCorner(); - cb = bin_.maxCorner(); + cb = bbin.maxCorner(); break; } } @@ -745,31 +786,32 @@ private: void setInitialPosition(Item& item) { Box&& bb = item.boundingBox(); Vertex ci, cb; + auto bbin = sl::boundingBox(bin_); switch(config_.starting_point) { case Config::Alignment::CENTER: { ci = bb.center(); - cb = bin_.center(); + cb = bbin.center(); break; } case Config::Alignment::BOTTOM_LEFT: { ci = bb.minCorner(); - cb = bin_.minCorner(); + cb = bbin.minCorner(); break; } case Config::Alignment::BOTTOM_RIGHT: { ci = {getX(bb.maxCorner()), getY(bb.minCorner())}; - cb = {getX(bin_.maxCorner()), getY(bin_.minCorner())}; + cb = {getX(bbin.maxCorner()), getY(bbin.minCorner())}; break; } case Config::Alignment::TOP_LEFT: { ci = {getX(bb.minCorner()), getY(bb.maxCorner())}; - cb = {getX(bin_.minCorner()), getY(bin_.maxCorner())}; + cb = {getX(bbin.minCorner()), getY(bbin.maxCorner())}; break; } case Config::Alignment::TOP_RIGHT: { ci = bb.maxCorner(); - cb = bin_.maxCorner(); + cb = bbin.maxCorner(); break; } } @@ -780,7 +822,7 @@ private: void placeOutsideOfBin(Item& item) { auto&& bb = item.boundingBox(); - Box binbb = ShapeLike::boundingBox(bin_); + Box binbb = sl::boundingBox(bin_); Vertex v = { getX(bb.maxCorner()), getY(bb.minCorner()) }; diff --git a/xs/src/libnest2d/libnest2d/selections/djd_heuristic.hpp b/xs/src/libnest2d/libnest2d/selections/djd_heuristic.hpp index 17ac1167d..e3ad97c10 100644 --- a/xs/src/libnest2d/libnest2d/selections/djd_heuristic.hpp +++ b/xs/src/libnest2d/libnest2d/selections/djd_heuristic.hpp @@ -535,7 +535,7 @@ public: // then it should be removed from the not_packed list { auto it = store_.begin(); while (it != store_.end()) { - Placer p(bin); + Placer p(bin); p.configure(pconfig); if(!p.pack(*it)) { it = store_.erase(it); } else it++; diff --git a/xs/src/libnest2d/libnest2d/selections/firstfit.hpp b/xs/src/libnest2d/libnest2d/selections/firstfit.hpp index f34961f80..665b9da9f 100644 --- a/xs/src/libnest2d/libnest2d/selections/firstfit.hpp +++ b/xs/src/libnest2d/libnest2d/selections/firstfit.hpp @@ -59,7 +59,7 @@ public: // then it should be removed from the list { auto it = store_.begin(); while (it != store_.end()) { - Placer p(bin); + Placer p(bin); p.configure(pconfig); if(!p.pack(*it)) { it = store_.erase(it); } else it++; diff --git a/xs/src/libslic3r/Model.cpp b/xs/src/libslic3r/Model.cpp index 87519d5e7..5f9c7a7d4 100644 --- a/xs/src/libslic3r/Model.cpp +++ b/xs/src/libslic3r/Model.cpp @@ -530,6 +530,9 @@ bool arrange(Model &model, coordf_t dist, const Slic3r::BoundingBoxf* bb, // arranger.useMinimumBoundigBoxRotation(); pcfg.rotations = { 0.0 }; + // The accuracy of optimization. Goes from 0.0 to 1.0 and scales performance + pcfg.accuracy = 0.8; + // Magic: we will specify what is the goal of arrangement... In this case // we override the default object function to make the larger items go into // the center of the pile and smaller items orbit it so the resulting pile @@ -539,7 +542,7 @@ bool arrange(Model &model, coordf_t dist, const Slic3r::BoundingBoxf* bb, // calculate the convex hulls) pcfg.object_function = [bin, hasbin]( NfpPlacer::Pile& pile, // The currently arranged pile - Item item, + const Item &item, double /*area*/, // Sum area of items (not needed) double norm, // A norming factor for physical dimensions double penality) // Min penality in case of bad arrangement From a7ba51bd111a8a38fda5a98fc82e12145c793d35 Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Thu, 2 Aug 2018 13:15:30 +0200 Subject: [PATCH 2/6] Fixing the "last item doesn't fit" problem. --- .../libnest2d/libnest2d/geometry_traits.hpp | 31 ++ xs/src/libnest2d/libnest2d/libnest2d.hpp | 3 +- .../libnest2d/libnest2d/placers/nfpplacer.hpp | 75 +-- xs/src/libslic3r/ModelArrange.hpp | 490 +++++++++++------- xs/src/slic3r/AppController.cpp | 19 +- 5 files changed, 399 insertions(+), 219 deletions(-) diff --git a/xs/src/libnest2d/libnest2d/geometry_traits.hpp b/xs/src/libnest2d/libnest2d/geometry_traits.hpp index 99511d775..00740f30c 100644 --- a/xs/src/libnest2d/libnest2d/geometry_traits.hpp +++ b/xs/src/libnest2d/libnest2d/geometry_traits.hpp @@ -684,6 +684,20 @@ struct ShapeLike { return PointLike::distance(point, circ.center()) < circ.radius(); } + template + static bool isInside(const TPoint& point, + const _Box>& box) + { + auto px = getX(point); + auto py = getY(point); + auto minx = getX(box.minCorner()); + auto miny = getY(box.minCorner()); + auto maxx = getX(box.maxCorner()); + auto maxy = getY(box.maxCorner()); + + return px > minx && px < maxx && py > miny && py < maxy; + } + template static bool isInside(const RawShape& sh, const _Circle>& circ) @@ -702,6 +716,23 @@ struct ShapeLike { isInside(box.maxCorner(), circ); } + template + static bool isInside(const _Box>& ibb, + const _Box>& box) + { + auto iminX = getX(ibb.minCorner()); + auto imaxX = getX(ibb.maxCorner()); + auto iminY = getY(ibb.minCorner()); + auto imaxY = getY(ibb.maxCorner()); + + auto minX = getX(box.minCorner()); + auto maxX = getX(box.maxCorner()); + auto minY = getY(box.minCorner()); + auto maxY = getY(box.maxCorner()); + + return iminX > minX && imaxX < maxX && iminY > minY && imaxY < maxY; + } + template // Potential O(1) implementation may exist static inline TPoint& vertex(RawShape& sh, unsigned long idx) { diff --git a/xs/src/libnest2d/libnest2d/libnest2d.hpp b/xs/src/libnest2d/libnest2d/libnest2d.hpp index fad38b9a3..7f23de358 100644 --- a/xs/src/libnest2d/libnest2d/libnest2d.hpp +++ b/xs/src/libnest2d/libnest2d/libnest2d.hpp @@ -473,8 +473,7 @@ public: template inline bool _Item::isInside(const _Box>& box) const { - _Rectangle rect(box.width(), box.height()); - return _Item::isInside(rect); + return ShapeLike::isInside(boundingBox(), box); } template inline bool diff --git a/xs/src/libnest2d/libnest2d/placers/nfpplacer.hpp b/xs/src/libnest2d/libnest2d/placers/nfpplacer.hpp index 06163b00a..0c5776400 100644 --- a/xs/src/libnest2d/libnest2d/placers/nfpplacer.hpp +++ b/xs/src/libnest2d/libnest2d/placers/nfpplacer.hpp @@ -166,7 +166,7 @@ template class EdgeCache { using std::pow; return static_cast( - round( N/(ceil(pow(accuracy_, 2)*(N-1)) + 1) ) + std::round(N/std::pow(N, std::pow(accuracy_, 1.0/3.0))) ); } @@ -178,6 +178,7 @@ template class EdgeCache { contour_.corners.reserve(N / S + 1); auto N_1 = N-1; + contour_.corners.emplace_back(0.0); for(size_t i = 0; i < N_1; i += S) { contour_.corners.emplace_back( contour_.distances.at(i) / contour_.full_distance); @@ -192,6 +193,7 @@ template class EdgeCache { const auto S = stride(N); auto N_1 = N-1; hc.corners.reserve(N / S + 1); + hc.corners.emplace_back(0.0); for(size_t i = 0; i < N_1; i += S) { hc.corners.emplace_back( hc.distances.at(i) / hc.full_distance); @@ -484,7 +486,7 @@ public: bool static inline wouldFit(const RawShape& chull, const RawShape& bin) { auto bbch = sl::boundingBox(chull); auto bbin = sl::boundingBox(bin); - auto d = bbin.center() - bbch.center(); + auto d = bbch.center() - bbin.center(); auto chullcpy = chull; sl::translate(chullcpy, d); return sl::isInside(chullcpy, bin); @@ -579,17 +581,21 @@ public: 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](Nfp::Shapes& pile, const Item& item, - double occupied_area, double /*norm*/, - double penality) + [this, &merged_pile]( + Nfp::Shapes& /*pile*/, + const Item& item, + double occupied_area, double norm, + double /*penality*/) { - pile.emplace_back(item.transformedShape()); - auto ch = sl::convexHull(pile); - pile.pop_back(); + 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 = occupied_area/sl::area(ch); @@ -602,7 +608,7 @@ public: // (larger) values. auto score = std::sqrt(waste); - if(!wouldFit(ch, bin_)) score = 2*penality - score; + if(!wouldFit(ch, bin_)) score += norm; return score; }; @@ -622,9 +628,22 @@ public: return score; }; + auto boundaryCheck = [&](const Optimum& o) { + auto v = getNfpPoint(o); + auto d = v - iv; + d += startpos; + item.translation(d); + + merged_pile.emplace_back(item.transformedShape()); + auto chull = sl::convexHull(merged_pile); + merged_pile.pop_back(); + + return wouldFit(chull, bin_); + }; + opt::StopCriteria stopcr; - stopcr.max_iterations = 1000; - stopcr.absolute_score_difference = 1e-20*norm_; + stopcr.max_iterations = 100; + stopcr.relative_score_difference = 1e-6; opt::TOptimizer solver(stopcr); Optimum optimum(0, 0); @@ -644,7 +663,7 @@ public: std::for_each(cache.corners().begin(), cache.corners().end(), [ch, &contour_ofn, &solver, &best_score, - &optimum] (double pos) + &optimum, &boundaryCheck] (double pos) { try { auto result = solver.optimize_min(contour_ofn, @@ -653,22 +672,15 @@ public: ); if(result.score < best_score) { - best_score = result.score; - optimum.relpos = std::get<0>(result.optimum); - optimum.nfpidx = ch; - optimum.hidx = -1; + 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"; } - -// auto sc = contour_ofn(pos); -// if(sc < best_score) { -// best_score = sc; -// optimum.relpos = pos; -// optimum.nfpidx = ch; -// optimum.hidx = -1; -// } }); for(unsigned hidx = 0; hidx < cache.holeCount(); ++hidx) { @@ -683,7 +695,7 @@ public: std::for_each(cache.corners(hidx).begin(), cache.corners(hidx).end(), [&hole_ofn, &solver, &best_score, - &optimum, ch, hidx] + &optimum, ch, hidx, &boundaryCheck] (double pos) { try { @@ -693,21 +705,16 @@ public: ); if(result.score < best_score) { - best_score = result.score; Optimum o(std::get<0>(result.optimum), ch, hidx); - optimum = o; + if(boundaryCheck(o)) { + best_score = result.score; + optimum = o; + } } } catch(std::exception& e) { derr() << "ERROR: " << e.what() << "\n"; } -// auto sc = hole_ofn(pos); -// if(sc < best_score) { -// best_score = sc; -// optimum.relpos = pos; -// optimum.nfpidx = ch; -// optimum.hidx = hidx; -// } }); } } diff --git a/xs/src/libslic3r/ModelArrange.hpp b/xs/src/libslic3r/ModelArrange.hpp index af4bfcf70..73dc83c57 100644 --- a/xs/src/libslic3r/ModelArrange.hpp +++ b/xs/src/libslic3r/ModelArrange.hpp @@ -93,6 +93,237 @@ void toSVG(SVG& svg, const Model& model) { } } +std::tuple +objfunc(const PointImpl& bincenter, + ShapeLike::Shapes& pile, // The currently arranged pile + const Item &item, + double norm // A norming factor for physical dimensions + ) +{ + using pl = PointLike; + + static const double BIG_ITEM_TRESHOLD = 0.2; + 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 + 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. + + // Now the distance of the gravity center will be calculated to the + // five anchor points and the smallest will be chosen. + + 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)}; + + // Now the distnce of the gravity center will be calculated to the + // five anchor points and the smallest will be chosen. + std::array dists; + auto cc = fullbb.center(); // The gravity center + 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 = ROUNDNESS_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 = ROUNDNESS_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; + } + + return std::make_tuple(score, fullbb); +} + +template +void fillConfig(PConf& pcfg) { + + // Align the arranged pile into the center of the bin + pcfg.alignment = PConf::Alignment::CENTER; + + // Start placing the items from the center of the print bed + pcfg.starting_point = PConf::Alignment::CENTER; + + // TODO cannot use rotations until multiple objects of same geometry can + // handle different rotations + // arranger.useMinimumBoundigBoxRotation(); + pcfg.rotations = { 0.0 }; + + // The accuracy of optimization. + // Goes from 0.0 to 1.0 and scales performance as well + pcfg.accuracy = 0.35f; +} + +template +class AutoArranger {}; + +template +class _ArrBase { +protected: + using Placer = strategies::_NofitPolyPlacer; + using Selector = FirstFitSelection; + using Packer = Arranger; + using PConfig = typename Packer::PlacementConfig; + using Distance = TCoord; + using Pile = ShapeLike::Shapes; + + Packer pck_; + PConfig pconf_; // Placement configuration + +public: + + _ArrBase(const TBin& bin, Distance dist, + std::function progressind): + pck_(bin, dist) + { + fillConfig(pconf_); + pck_.progressIndicator(progressind); + } + + template inline IndexedPackGroup operator()(Args&&...args) { + return pck_.arrangeIndexed(std::forward(args)...); + } +}; + +template<> +class AutoArranger: public _ArrBase { +public: + + AutoArranger(const Box& bin, Distance dist, + std::function progressind): + _ArrBase(bin, dist, progressind) + { + pconf_.object_function = [bin] ( + Pile& pile, + const Item &item, + double /*occupied_area*/, + double norm, + double penality) { + + auto result = objfunc(bin.center(), pile, item, norm); + 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; + + return score; + }; + + pck_.configure(pconf_); + } +}; + +template<> +class AutoArranger: public _ArrBase { +public: + AutoArranger(const PolygonImpl& bin, Distance dist, + std::function progressind): + _ArrBase(bin, dist, progressind) + { + pconf_.object_function = [&bin] ( + Pile& pile, + const Item &item, + double /*area*/, + double norm, + double /*penality*/) { + + auto binbb = ShapeLike::boundingBox(bin); + auto result = objfunc(binbb.center(), pile, item, norm); + 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; + }; + + pck_.configure(pconf_); + } +}; + +template<> // Specialization with no bin +class AutoArranger: public _ArrBase { +public: + + AutoArranger(Distance dist, std::function progressind): + _ArrBase(Box(0, 0), dist, progressind) + { + this->pconf_.object_function = [] ( + Pile& pile, + const Item &item, + double /*area*/, + double norm, + double /*penality*/) { + + auto result = objfunc({0, 0}, pile, item, norm); + return std::get<0>(result); + }; + + this->pck_.configure(pconf_); + } +}; + // A container which stores a pointer to the 3D object and its projected // 2D shape from top view. using ShapeData2D = @@ -147,6 +378,44 @@ ShapeData2D projectModelFromTop(const Slic3r::Model &model) { return ret; } +enum BedShapeHint { + BOX, + CIRCLE, + IRREGULAR, + WHO_KNOWS +}; + +BedShapeHint bedShape(const Slic3r::Polyline& /*bed*/) { + // Determine the bed shape by hand + return BOX; +} + +void applyResult( + IndexedPackGroup::value_type& group, + Coord batch_offset, + ShapeData2D& shapemap) +{ + for(auto& r : group) { + auto idx = r.first; // get the original item index + Item& item = r.second; // get the item itself + + // Get the model instance from the shapemap using the index + ModelInstance *inst_ptr = shapemap[idx].first; + + // Get the tranformation data from the item object and scale it + // appropriately + auto off = item.translation(); + Radians rot = item.rotation(); + Pointf foff(off.X*SCALING_FACTOR + batch_offset, + off.Y*SCALING_FACTOR); + + // write the tranformation data into the model instance + inst_ptr->rotation = rot; + inst_ptr->offset = foff; + } +} + + /** * \brief Arranges the model objects on the screen. * @@ -170,7 +439,9 @@ ShapeData2D projectModelFromTop(const Slic3r::Model &model) { * bed or leave them untouched (let the user arrange them by hand or remove * them). */ -bool arrange(Model &model, coordf_t dist, const Slic3r::BoundingBoxf* bb, +bool arrange(Model &model, coordf_t min_obj_distance, + const Slic3r::Polyline& bed, + BedShapeHint bedhint, bool first_bin_only, std::function progressind) { @@ -178,215 +449,74 @@ bool arrange(Model &model, coordf_t dist, const Slic3r::BoundingBoxf* bb, bool ret = true; - // Create the arranger config - auto min_obj_distance = static_cast(dist/SCALING_FACTOR); - // Get the 2D projected shapes with their 3D model instance pointers auto shapemap = arr::projectModelFromTop(model); - bool hasbin = bb != nullptr && bb->defined; - double area_max = 0; - // Copy the references for the shapes only as the arranger expects a // sequence of objects convertible to Item or ClipperPolygon std::vector> shapes; shapes.reserve(shapemap.size()); std::for_each(shapemap.begin(), shapemap.end(), - [&shapes, min_obj_distance, &area_max, hasbin] - (ShapeData2D::value_type& it) + [&shapes] (ShapeData2D::value_type& it) { shapes.push_back(std::ref(it.second)); }); - Box bin; + IndexedPackGroup result; + BoundingBox bbb(bed.points); - if(hasbin) { - // Scale up the bounding box to clipper scale. - BoundingBoxf bbb = *bb; - bbb.scale(1.0/SCALING_FACTOR); + auto binbb = Box({ + static_cast(bbb.min.x), + static_cast(bbb.min.y) + }, + { + static_cast(bbb.max.x), + static_cast(bbb.max.y) + }); - bin = Box({ - static_cast(bbb.min.x), - static_cast(bbb.min.y) - }, - { - static_cast(bbb.max.x), - static_cast(bbb.max.y) - }); + switch(bedhint) { + case BOX: { + + // Create the arranger for the box shaped bed + AutoArranger arrange(binbb, min_obj_distance, progressind); + + // Arrange and return the items with their respective indices within the + // input sequence. + result = arrange(shapes.begin(), shapes.end()); + break; } + case CIRCLE: + break; + case IRREGULAR: + case WHO_KNOWS: { + using P = libnest2d::PolygonImpl; - // Will use the DJD selection heuristic with the BottomLeft placement - // strategy - using Arranger = Arranger; - using PConf = Arranger::PlacementConfig; - using SConf = Arranger::SelectionConfig; + auto ctour = Slic3rMultiPoint_to_ClipperPath(bed); + P irrbed = ShapeLike::create(std::move(ctour)); - PConf pcfg; // Placement configuration - SConf scfg; // Selection configuration + std::cout << ShapeLike::toString(irrbed) << std::endl; - // Align the arranged pile into the center of the bin - pcfg.alignment = PConf::Alignment::CENTER; + AutoArranger

arrange(irrbed, min_obj_distance, progressind); - // Start placing the items from the center of the print bed - pcfg.starting_point = PConf::Alignment::CENTER; - - // TODO cannot use rotations until multiple objects of same geometry can - // handle different rotations - // arranger.useMinimumBoundigBoxRotation(); - pcfg.rotations = { 0.0 }; - - // The accuracy of optimization. Goes from 0.0 to 1.0 and scales performance - pcfg.accuracy = 0.4f; - - // Magic: we will specify what is the goal of arrangement... In this case - // we override the default object function to make the larger items go into - // the center of the pile and smaller items orbit it so the resulting pile - // has a circle-like shape. This is good for the print bed's heat profile. - // We alse sacrafice a bit of pack efficiency for this to work. As a side - // effect, the arrange procedure is a lot faster (we do not need to - // calculate the convex hulls) - pcfg.object_function = [bin, hasbin]( - NfpPlacer::Pile& pile, // The currently arranged pile - const Item &item, - double /*area*/, // Sum area of items (not needed) - double norm, // A norming factor for physical dimensions - double penality) // Min penality in case of bad arrangement - { - 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(), bin.center()) / 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 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; - - return score; - }; - - // Create the arranger object - Arranger arranger(bin, min_obj_distance, pcfg, scfg); - - // Set the progress indicator for the arranger. - arranger.progressIndicator(progressind); - - // Arrange and return the items with their respective indices within the - // input sequence. - auto result = arranger.arrangeIndexed(shapes.begin(), shapes.end()); - - auto applyResult = [&shapemap](ArrangeResult::value_type& group, - Coord batch_offset) - { - for(auto& r : group) { - auto idx = r.first; // get the original item index - Item& item = r.second; // get the item itself - - // Get the model instance from the shapemap using the index - ModelInstance *inst_ptr = shapemap[idx].first; - - // Get the tranformation data from the item object and scale it - // appropriately - auto off = item.translation(); - Radians rot = item.rotation(); - Pointf foff(off.X*SCALING_FACTOR + batch_offset, - off.Y*SCALING_FACTOR); - - // write the tranformation data into the model instance - inst_ptr->rotation = rot; - inst_ptr->offset = foff; - } + // Arrange and return the items with their respective indices within the + // input sequence. + result = arrange(shapes.begin(), shapes.end()); + break; + } }; if(first_bin_only) { - applyResult(result.front(), 0); + applyResult(result.front(), 0, shapemap); } else { const auto STRIDE_PADDING = 1.2; Coord stride = static_cast(STRIDE_PADDING* - bin.width()*SCALING_FACTOR); + binbb.width()*SCALING_FACTOR); Coord batch_offset = 0; for(auto& group : result) { - applyResult(group, batch_offset); + applyResult(group, batch_offset, shapemap); // Only the first pack group can be placed onto the print bed. The // other objects which could not fit will be placed next to the diff --git a/xs/src/slic3r/AppController.cpp b/xs/src/slic3r/AppController.cpp index 1d4b7d545..58858f5fc 100644 --- a/xs/src/slic3r/AppController.cpp +++ b/xs/src/slic3r/AppController.cpp @@ -294,6 +294,8 @@ void AppController::arrange_model() supports_asynch()? std::launch::async : std::launch::deferred, [this]() { + using Coord = libnest2d::TCoord; + unsigned count = 0; for(auto obj : model_->objects) count += obj->instances.size(); @@ -311,14 +313,25 @@ void AppController::arrange_model() auto dist = print_ctl()->config().min_object_distance(); + // Create the arranger config + auto min_obj_distance = static_cast(dist/SCALING_FACTOR); - BoundingBoxf bb(print_ctl()->config().bed_shape.values); + auto& bedpoints = print_ctl()->config().bed_shape.values; + Polyline bed; bed.points.reserve(bedpoints.size()); + for(auto& v : bedpoints) + bed.append(Point::new_scale(v.x, v.y)); if(pind) pind->update(0, _(L("Arranging objects..."))); try { - arr::arrange(*model_, dist, &bb, false, [pind, count](unsigned rem){ - if(pind) pind->update(count - rem, _(L("Arranging objects..."))); + arr::arrange(*model_, + min_obj_distance, + bed, + arr::BOX, + false, // create many piles not just one pile + [pind, count](unsigned rem) { + if(pind) + pind->update(count - rem, _(L("Arranging objects..."))); }); } catch(std::exception& e) { std::cerr << e.what() << std::endl; From c8370b5408cd367026faf976149967cd589f00c8 Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Thu, 2 Aug 2018 17:51:11 +0200 Subject: [PATCH 3/6] New approach to big items with calculating the best alignment with other big items. --- .../libnest2d/libnest2d/geometry_traits.hpp | 4 +- .../libnest2d/libnest2d/placers/nfpplacer.hpp | 3 +- xs/src/libslic3r/ModelArrange.hpp | 72 +++++++++++++------ 3 files changed, 56 insertions(+), 23 deletions(-) diff --git a/xs/src/libnest2d/libnest2d/geometry_traits.hpp b/xs/src/libnest2d/libnest2d/geometry_traits.hpp index 00740f30c..058c47cd4 100644 --- a/xs/src/libnest2d/libnest2d/geometry_traits.hpp +++ b/xs/src/libnest2d/libnest2d/geometry_traits.hpp @@ -314,8 +314,8 @@ inline RawPoint _Box::center() const BP2D_NOEXCEPT { using Coord = TCoord; RawPoint ret = { - static_cast( (getX(minc) + getX(maxc))/2.0 ), - static_cast( (getY(minc) + getY(maxc))/2.0 ) + static_cast( std::round((getX(minc) + getX(maxc))/2.0) ), + static_cast( std::round((getY(minc) + getY(maxc))/2.0) ) }; return ret; diff --git a/xs/src/libnest2d/libnest2d/placers/nfpplacer.hpp b/xs/src/libnest2d/libnest2d/placers/nfpplacer.hpp index 0c5776400..5d09a61fc 100644 --- a/xs/src/libnest2d/libnest2d/placers/nfpplacer.hpp +++ b/xs/src/libnest2d/libnest2d/placers/nfpplacer.hpp @@ -590,7 +590,8 @@ public: [this, &merged_pile]( Nfp::Shapes& /*pile*/, const Item& item, - double occupied_area, double norm, + double occupied_area, + double norm, double /*penality*/) { merged_pile.emplace_back(item.transformedShape()); diff --git a/xs/src/libslic3r/ModelArrange.hpp b/xs/src/libslic3r/ModelArrange.hpp index 73dc83c57..5f1717f71 100644 --- a/xs/src/libslic3r/ModelArrange.hpp +++ b/xs/src/libslic3r/ModelArrange.hpp @@ -95,12 +95,16 @@ void toSVG(SVG& svg, const Model& model) { std::tuple objfunc(const PointImpl& bincenter, + double bin_area, ShapeLike::Shapes& pile, // The currently arranged pile + double pile_area, const Item &item, - double norm // A norming factor for physical dimensions + double norm, // A norming factor for physical dimensions + std::vector& areacache ) { using pl = PointLike; + using sl = ShapeLike; static const double BIG_ITEM_TRESHOLD = 0.2; static const double ROUNDNESS_RATIO = 0.5; @@ -109,10 +113,14 @@ objfunc(const PointImpl& bincenter, // We will treat big items (compared to the print bed) differently NfpPlacer::Pile bigs; bigs.reserve(pile.size()); + + int idx = 0; + if(pile.size() < areacache.size()) areacache.clear(); 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); + if(idx == areacache.size()) areacache.emplace_back(sl::area(p)); + if(std::sqrt(areacache[idx])/norm > BIG_ITEM_TRESHOLD) + bigs.emplace_back(p); + idx++; } // Candidate item bounding box @@ -166,9 +174,28 @@ objfunc(const PointImpl& bincenter, // 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 = ROUNDNESS_RATIO * dist + DENSITY_RATIO * density; + auto alignment_score = std::numeric_limits::max(); + + auto& trsh = item.transformedShape(); + + idx = 0; + for(auto& p : pile) { + + auto parea = areacache[idx]; + if(std::sqrt(parea)/norm > BIG_ITEM_TRESHOLD) { + auto chull = sl::convexHull(sl::Shapes{p, trsh}); + auto carea = sl::area(chull); + + auto ascore = carea - (item.area() + parea); + ascore = std::sqrt(ascore) / norm; + + if(ascore < alignment_score) alignment_score = ascore; + } + idx++; + } + + auto C = 0.33; + score = C * dist + C * density + C * alignment_score; } else if(itemnormarea < BIG_ITEM_TRESHOLD && bigs.empty()) { // If there are no big items, only small, we should consider the @@ -203,7 +230,7 @@ void fillConfig(PConf& pcfg) { // The accuracy of optimization. // Goes from 0.0 to 1.0 and scales performance as well - pcfg.accuracy = 0.35f; + pcfg.accuracy = 0.4f; } template @@ -221,18 +248,20 @@ protected: Packer pck_; PConfig pconf_; // Placement configuration - + double bin_area_; + std::vector areacache_; public: _ArrBase(const TBin& bin, Distance dist, std::function progressind): - pck_(bin, dist) + pck_(bin, dist), bin_area_(ShapeLike::area(bin)) { fillConfig(pconf_); pck_.progressIndicator(progressind); } template inline IndexedPackGroup operator()(Args&&...args) { + areacache_.clear(); return pck_.arrangeIndexed(std::forward(args)...); } }; @@ -245,14 +274,15 @@ public: std::function progressind): _ArrBase(bin, dist, progressind) { - pconf_.object_function = [bin] ( + pconf_.object_function = [this, bin] ( Pile& pile, const Item &item, - double /*occupied_area*/, + double pile_area, double norm, - double penality) { + double /*penality*/) { - auto result = objfunc(bin.center(), pile, item, norm); + auto result = objfunc(bin.center(), bin_area_, pile, + pile_area, item, norm, areacache_); double score = std::get<0>(result); auto& fullbb = std::get<1>(result); @@ -275,15 +305,16 @@ public: std::function progressind): _ArrBase(bin, dist, progressind) { - pconf_.object_function = [&bin] ( + pconf_.object_function = [this, &bin] ( Pile& pile, const Item &item, - double /*area*/, + double pile_area, double norm, double /*penality*/) { auto binbb = ShapeLike::boundingBox(bin); - auto result = objfunc(binbb.center(), pile, item, norm); + auto result = objfunc(binbb.center(), bin_area_, pile, + pile_area, item, norm, areacache_); double score = std::get<0>(result); pile.emplace_back(item.transformedShape()); @@ -309,14 +340,15 @@ public: AutoArranger(Distance dist, std::function progressind): _ArrBase(Box(0, 0), dist, progressind) { - this->pconf_.object_function = [] ( + this->pconf_.object_function = [this] ( Pile& pile, const Item &item, - double /*area*/, + double pile_area, double norm, double /*penality*/) { - auto result = objfunc({0, 0}, pile, item, norm); + auto result = objfunc({0, 0}, 0, pile, pile_area, + item, norm, areacache_); return std::get<0>(result); }; From 9172a69e27faf75c43013464c421a8cdf1b0760c Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Thu, 2 Aug 2018 19:17:27 +0200 Subject: [PATCH 4/6] Nlopt build fix --- .../cmake_modules/DownloadNLopt.cmake | 3 ++- xs/src/libslic3r/ModelArrange.hpp | 22 +++++++++++-------- 2 files changed, 15 insertions(+), 10 deletions(-) diff --git a/xs/src/libnest2d/cmake_modules/DownloadNLopt.cmake b/xs/src/libnest2d/cmake_modules/DownloadNLopt.cmake index 814213b38..0f5392596 100644 --- a/xs/src/libnest2d/cmake_modules/DownloadNLopt.cmake +++ b/xs/src/libnest2d/cmake_modules/DownloadNLopt.cmake @@ -27,5 +27,6 @@ set(NLOPT_LINK_PYTHON OFF CACHE BOOL "" FORCE) add_subdirectory(${nlopt_SOURCE_DIR} ${nlopt_BINARY_DIR}) set(NLopt_LIBS nlopt) -set(NLopt_INCLUDE_DIR ${nlopt_BINARY_DIR}) +set(NLopt_INCLUDE_DIR ${nlopt_BINARY_DIR} + ${nlopt_BINARY_DIR}/src/api) set(SHARED_LIBS_STATE ${SHARED_STATE}) \ No newline at end of file diff --git a/xs/src/libslic3r/ModelArrange.hpp b/xs/src/libslic3r/ModelArrange.hpp index 5f1717f71..baf31af4e 100644 --- a/xs/src/libslic3r/ModelArrange.hpp +++ b/xs/src/libslic3r/ModelArrange.hpp @@ -95,9 +95,9 @@ void toSVG(SVG& svg, const Model& model) { 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 @@ -114,11 +114,14 @@ objfunc(const PointImpl& bincenter, NfpPlacer::Pile bigs; bigs.reserve(pile.size()); - int idx = 0; if(pile.size() < areacache.size()) areacache.clear(); + + auto normarea = [norm](double area) { return std::sqrt(area)/norm; }; + + int idx = 0; for(auto& p : pile) { if(idx == areacache.size()) areacache.emplace_back(sl::area(p)); - if(std::sqrt(areacache[idx])/norm > BIG_ITEM_TRESHOLD) + if( normarea(areacache[idx]) > BIG_ITEM_TRESHOLD) bigs.emplace_back(p); idx++; } @@ -137,12 +140,13 @@ objfunc(const PointImpl& bincenter, // 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) { + double item_normarea = normarea(item.area()); + + if(item_normarea > 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 @@ -182,7 +186,7 @@ objfunc(const PointImpl& bincenter, for(auto& p : pile) { auto parea = areacache[idx]; - if(std::sqrt(parea)/norm > BIG_ITEM_TRESHOLD) { + if(normarea(areacache[idx]) > BIG_ITEM_TRESHOLD) { auto chull = sl::convexHull(sl::Shapes{p, trsh}); auto carea = sl::area(chull); @@ -197,7 +201,7 @@ objfunc(const PointImpl& bincenter, auto C = 0.33; score = C * dist + C * density + C * alignment_score; - } else if(itemnormarea < BIG_ITEM_TRESHOLD && bigs.empty()) { + } else if( item_normarea < 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; @@ -230,7 +234,7 @@ void fillConfig(PConf& pcfg) { // The accuracy of optimization. // Goes from 0.0 to 1.0 and scales performance as well - pcfg.accuracy = 0.4f; + pcfg.accuracy = 1.0f; } template From 8e516bc3e431a41ce4cfc41614db9e3c110e2e5a Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Thu, 2 Aug 2018 19:25:19 +0200 Subject: [PATCH 5/6] reduce accuracy to acceptable performance --- xs/src/libslic3r/ModelArrange.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/xs/src/libslic3r/ModelArrange.hpp b/xs/src/libslic3r/ModelArrange.hpp index baf31af4e..b1e331eeb 100644 --- a/xs/src/libslic3r/ModelArrange.hpp +++ b/xs/src/libslic3r/ModelArrange.hpp @@ -234,7 +234,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.5f; } template From e7e212cb52ce916a4f18ff295760ecc0d9403f2a Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Fri, 3 Aug 2018 12:37:27 +0200 Subject: [PATCH 6/6] Added a spatial index to speed up alignment score calculation. --- xs/src/libslic3r/ModelArrange.hpp | 94 ++++++++++++++++++++----------- 1 file changed, 60 insertions(+), 34 deletions(-) diff --git a/xs/src/libslic3r/ModelArrange.hpp b/xs/src/libslic3r/ModelArrange.hpp index b1e331eeb..f2d399ac6 100644 --- a/xs/src/libslic3r/ModelArrange.hpp +++ b/xs/src/libslic3r/ModelArrange.hpp @@ -8,6 +8,8 @@ #include #include +#include + namespace Slic3r { namespace arr { @@ -93,6 +95,11 @@ void toSVG(SVG& svg, const Model& model) { } } +namespace bgi = boost::geometry::index; + +using SpatElement = std::pair; +using SpatIndex = bgi::rtree< SpatElement, bgi::rstar<16, 4> >; + std::tuple objfunc(const PointImpl& bincenter, double /*bin_area*/, @@ -100,7 +107,9 @@ objfunc(const PointImpl& bincenter, double /*pile_area*/, const Item &item, double norm, // A norming factor for physical dimensions - std::vector& areacache + std::vector& areacache, // pile item areas will be cached + // a spatial index to quickly get neighbors of the candidate item + SpatIndex& spatindex ) { using pl = PointLike; @@ -111,18 +120,23 @@ objfunc(const PointImpl& bincenter, static const double DENSITY_RATIO = 1.0 - ROUNDNESS_RATIO; // We will treat big items (compared to the print bed) differently - NfpPlacer::Pile bigs; - bigs.reserve(pile.size()); - - if(pile.size() < areacache.size()) areacache.clear(); - auto normarea = [norm](double area) { return std::sqrt(area)/norm; }; + // 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( normarea(areacache[idx]) > BIG_ITEM_TRESHOLD) - bigs.emplace_back(p); + if(idx == areacache.size()) { + areacache.emplace_back(sl::area(p)); + if(normarea(areacache[idx]) > BIG_ITEM_TRESHOLD) + spatindex.insert({sl::boundingBox(p), idx}); + } + idx++; } @@ -136,25 +150,26 @@ objfunc(const PointImpl& bincenter, // The bounding box of the big items (they will accumulate in the center // of the pile - auto bigbb = bigs.empty()? fullbb : ShapeLike::boundingBox(bigs); + Box bigbb; + if(spatindex.empty()) bigbb = fullbb; + else { + auto boostbb = spatindex.bounds(); + 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; - double item_normarea = normarea(item.area()); - if(item_normarea > 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. - - // Now the distance of the gravity center will be calculated to the - // five anchor points and the smallest will be chosen. + // prevent some unwanted strange arrangements. auto minc = ibb.minCorner(); // bottom left corner auto maxc = ibb.maxCorner(); // top right corner @@ -163,7 +178,7 @@ objfunc(const PointImpl& bincenter, auto top_left = PointImpl{getX(minc), getY(maxc)}; auto bottom_right = PointImpl{getX(maxc), getY(minc)}; - // Now the distnce of the gravity center will be calculated to the + // Now the distance of the gravity center will be calculated to the // five anchor points and the smallest will be chosen. std::array dists; auto cc = fullbb.center(); // The gravity center @@ -173,35 +188,45 @@ objfunc(const PointImpl& bincenter, dists[3] = pl::distance(top_left, cc); dists[4] = pl::distance(bottom_right, cc); + // The smalles distance from the arranged pile center: 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; + // 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(); auto& trsh = item.transformedShape(); - idx = 0; - for(auto& p : pile) { + auto querybb = item.boundingBox(); + // Query the spatial index for the neigbours + std::vector result; + spatindex.query(bgi::intersects(querybb), std::back_inserter(result)); + + for(auto& e : result) { // now get the score for the best alignment + auto idx = e.second; + auto& p = pile[idx]; auto parea = areacache[idx]; - if(normarea(areacache[idx]) > BIG_ITEM_TRESHOLD) { - auto chull = sl::convexHull(sl::Shapes{p, trsh}); - auto carea = sl::area(chull); + auto bb = sl::boundingBox(sl::Shapes{p, trsh}); + auto bbarea = bb.area(); + auto ascore = 1.0 - (item.area() + parea)/bbarea; - auto ascore = carea - (item.area() + parea); - ascore = std::sqrt(ascore) / norm; - - if(ascore < alignment_score) alignment_score = ascore; - } - idx++; + 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 auto C = 0.33; score = C * dist + C * density + C * alignment_score; - } else if( item_normarea < BIG_ITEM_TRESHOLD && bigs.empty()) { + } else if( item_normarea < BIG_ITEM_TRESHOLD && 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; @@ -234,7 +259,7 @@ void fillConfig(PConf& pcfg) { // The accuracy of optimization. // Goes from 0.0 to 1.0 and scales performance as well - pcfg.accuracy = 0.5f; + pcfg.accuracy = 0.6f; } template @@ -254,6 +279,7 @@ protected: PConfig pconf_; // Placement configuration double bin_area_; std::vector areacache_; + SpatIndex rtree_; public: _ArrBase(const TBin& bin, Distance dist, @@ -286,7 +312,7 @@ public: double /*penality*/) { auto result = objfunc(bin.center(), bin_area_, pile, - pile_area, item, norm, areacache_); + pile_area, item, norm, areacache_, rtree_); double score = std::get<0>(result); auto& fullbb = std::get<1>(result); @@ -318,7 +344,7 @@ public: auto binbb = ShapeLike::boundingBox(bin); auto result = objfunc(binbb.center(), bin_area_, pile, - pile_area, item, norm, areacache_); + pile_area, item, norm, areacache_, rtree_); double score = std::get<0>(result); pile.emplace_back(item.transformedShape()); @@ -352,7 +378,7 @@ public: double /*penality*/) { auto result = objfunc({0, 0}, 0, pile, pile_area, - item, norm, areacache_); + item, norm, areacache_, rtree_); return std::get<0>(result); }; @@ -530,7 +556,7 @@ 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; +// std::cout << ShapeLike::toString(irrbed) << std::endl; AutoArranger

arrange(irrbed, min_obj_distance, progressind);