diff --git a/xs/src/libnest2d/examples/main.cpp b/xs/src/libnest2d/examples/main.cpp index 4623a6add..e5a47161e 100644 --- a/xs/src/libnest2d/examples/main.cpp +++ b/xs/src/libnest2d/examples/main.cpp @@ -519,20 +519,28 @@ void arrangeRectangles() { std::vector proba = { { - { {0, 0}, {20, 20}, {40, 0}, {0, 0} } + Rectangle(100, 2) }, { - { {0, 100}, {50, 60}, {100, 100}, {50, 0}, {0, 100} } - + Rectangle(100, 2) + }, + { + Rectangle(100, 2) + }, + { + Rectangle(10, 10) }, }; + proba[0].rotate(Pi/3); + proba[1].rotate(Pi-Pi/3); + std::vector input; input.insert(input.end(), prusaParts().begin(), prusaParts().end()); // input.insert(input.end(), prusaExParts().begin(), prusaExParts().end()); -// input.insert(input.end(), stegoParts().begin(), stegoParts().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(), proba.begin(), proba.end()); // input.insert(input.end(), crasher.begin(), crasher.end()); Box bin(250*SCALE, 210*SCALE); @@ -569,9 +577,9 @@ void arrangeRectangles() { Packer::SelectionConfig sconf; // sconf.allow_parallel = false; // sconf.force_parallel = false; -// sconf.try_triplets = true; +// sconf.try_triplets = false; // sconf.try_reverse_order = true; -// sconf.waste_increment = 0.1; +// sconf.waste_increment = 0.005; arrange.configure(pconf, sconf); diff --git a/xs/src/libnest2d/libnest2d/geometry_traits_nfp.hpp b/xs/src/libnest2d/libnest2d/geometry_traits_nfp.hpp index 56e8527b4..581b6bed0 100644 --- a/xs/src/libnest2d/libnest2d/geometry_traits_nfp.hpp +++ b/xs/src/libnest2d/libnest2d/geometry_traits_nfp.hpp @@ -25,9 +25,60 @@ using Shapes = typename ShapeLike::Shapes; /// Minkowski addition (not used yet) template -static RawShape minkowskiDiff(const RawShape& sh, const RawShape& /*other*/) +static RawShape minkowskiDiff(const RawShape& sh, const RawShape& cother) { + using Vertex = TPoint; + //using Coord = TCoord; + using Edge = _Segment; + using sl = ShapeLike; + using std::signbit; + // Copy the orbiter (controur only), we will have to work on it + RawShape orbiter = sl::create(sl::getContour(cother)); + + // Make the orbiter reverse oriented + for(auto &v : sl::getContour(orbiter)) v = -v; + + // An egde with additional data for marking it + struct MarkedEdge { Edge e; Radians turn_angle; bool is_turning_point; }; + + // Container for marked edges + using EdgeList = std::vector; + + EdgeList A, B; + + auto fillEdgeList = [](EdgeList& L, const RawShape& poly) { + L.reserve(sl::contourVertexCount(poly)); + + auto it = sl::cbegin(poly); + auto nextit = std::next(it); + + L.emplace_back({Edge(*it, *nextit), 0, false}); + it++; nextit++; + + while(nextit != sl::cend(poly)) { + Edge e(*it, *nextit); + auto& L_prev = L.back(); + auto phi = L_prev.e.angleToXaxis(); + auto phi_prev = e.angleToXaxis(); + auto turn_angle = phi-phi_prev; + if(turn_angle > Pi) turn_angle -= 2*Pi; + L.emplace_back({ + e, + turn_angle, + signbit(turn_angle) != signbit(L_prev.turn_angle) + }); + it++; nextit++; + } + + L.front().turn_angle = L.front().e.angleToXaxis() - + L.back().e.angleToXaxis(); + + if(L.front().turn_angle > Pi) L.front().turn_angle -= 2*Pi; + }; + + fillEdgeList(A, sh); + fillEdgeList(B, orbiter); return sh; } @@ -193,6 +244,9 @@ static RawShape nfpConvexOnly(const RawShape& sh, const RawShape& cother) // Lindmark's reasoning about the reference vertex of nfp in his thesis // ("No fit polygon problem" - section 2.1.9) + // TODO: dont do this here. Cache the rmu and lmd in Item and get translate + // the nfp after this call + auto csh = sh; // Copy sh, we will sort the verices in the copy auto& cmp = _vsort; std::sort(ShapeLike::begin(csh), ShapeLike::end(csh), cmp); diff --git a/xs/src/libnest2d/libnest2d/placers/nfpplacer.hpp b/xs/src/libnest2d/libnest2d/placers/nfpplacer.hpp index d6bd154db..89848eb53 100644 --- a/xs/src/libnest2d/libnest2d/placers/nfpplacer.hpp +++ b/xs/src/libnest2d/libnest2d/placers/nfpplacer.hpp @@ -48,32 +48,89 @@ template class EdgeCache { using Coord = TCoord; using Edge = _Segment; - mutable std::vector corners_; + struct ContourCache { + mutable std::vector corners; + std::vector emap; + std::vector distances; + double full_distance = 0; + } contour_; - std::vector emap_; - std::vector distances_; - double full_distance_ = 0; + std::vector holes_; void createCache(const RawShape& sh) { - auto first = ShapeLike::cbegin(sh); - auto next = first + 1; - auto endit = ShapeLike::cend(sh); + { // For the contour + auto first = ShapeLike::cbegin(sh); + auto next = std::next(first); + auto endit = ShapeLike::cend(sh); - distances_.reserve(ShapeLike::contourVertexCount(sh)); + contour_.distances.reserve(ShapeLike::contourVertexCount(sh)); - while(next != endit) { - emap_.emplace_back(*(first++), *(next++)); - full_distance_ += emap_.back().length(); - distances_.push_back(full_distance_); + while(next != endit) { + contour_.emap.emplace_back(*(first++), *(next++)); + contour_.full_distance += contour_.emap.back().length(); + contour_.distances.push_back(contour_.full_distance); + } + } + + for(auto& h : ShapeLike::holes(sh)) { // For the holes + auto first = h.begin(); + auto next = std::next(first); + auto endit = h.end(); + + ContourCache hc; + hc.distances.reserve(endit - first); + + while(next != endit) { + hc.emap.emplace_back(*(first++), *(next++)); + hc.full_distance += hc.emap.back().length(); + hc.distances.push_back(hc.full_distance); + } + + holes_.push_back(hc); } } void fetchCorners() const { - if(!corners_.empty()) return; + if(!contour_.corners.empty()) return; // TODO Accuracy - corners_ = distances_; - for(auto& d : corners_) d /= full_distance_; + contour_.corners = contour_.distances; + for(auto& d : contour_.corners) d /= contour_.full_distance; + } + + void fetchHoleCorners(unsigned hidx) const { + auto& hc = holes_[hidx]; + if(!hc.corners.empty()) return; + + // TODO Accuracy + hc.corners = hc.distances; + for(auto& d : hc.corners) d /= hc.full_distance; + } + + inline Vertex coords(const ContourCache& cache, double distance) const { + assert(distance >= .0 && distance <= 1.0); + + // distance is from 0.0 to 1.0, we scale it up to the full length of + // the circumference + double d = distance*cache.full_distance; + + auto& distances = cache.distances; + + // Magic: we find the right edge in log time + auto it = std::lower_bound(distances.begin(), distances.end(), d); + auto idx = it - distances.begin(); // get the index of the edge + auto edge = cache.emap[idx]; // extrac the edge + + // Get the remaining distance on the target edge + auto ed = d - (idx > 0 ? *std::prev(it) : 0 ); + auto angle = edge.angleToXaxis(); + Vertex ret = edge.first(); + + // Get the point on the edge which lies in ed distance from the start + ret += { static_cast(std::round(ed*std::cos(angle))), + static_cast(std::round(ed*std::sin(angle))) }; + + return ret; } public: @@ -102,37 +159,36 @@ public: * @return Returns the coordinates of the point lying on the polygon * circumference. */ - inline Vertex coords(double distance) { - assert(distance >= .0 && distance <= 1.0); - - // distance is from 0.0 to 1.0, we scale it up to the full length of - // the circumference - double d = distance*full_distance_; - - // Magic: we find the right edge in log time - auto it = std::lower_bound(distances_.begin(), distances_.end(), d); - auto idx = it - distances_.begin(); // get the index of the edge - auto edge = emap_[idx]; // extrac the edge - - // Get the remaining distance on the target edge - auto ed = d - (idx > 0 ? *std::prev(it) : 0 ); - auto angle = edge.angleToXaxis(); - Vertex ret = edge.first(); - - // Get the point on the edge which lies in ed distance from the start - ret += { static_cast(std::round(ed*std::cos(angle))), - static_cast(std::round(ed*std::sin(angle))) }; - - return ret; + inline Vertex coords(double distance) const { + return coords(contour_, distance); } - inline double circumference() const BP2D_NOEXCEPT { return full_distance_; } + inline Vertex coords(unsigned hidx, double distance) const { + assert(hidx < holes_.size()); + return coords(holes_[hidx], distance); + } + + inline double circumference() const BP2D_NOEXCEPT { + return contour_.full_distance; + } + + inline double circumference(unsigned hidx) const BP2D_NOEXCEPT { + return holes_[hidx].full_distance; + } inline const std::vector& corners() const BP2D_NOEXCEPT { fetchCorners(); - return corners_; + return contour_.corners; } + inline const std::vector& + corners(unsigned holeidx) const BP2D_NOEXCEPT { + fetchHoleCorners(holeidx); + return holes_[holeidx].corners; + } + + inline unsigned holeCount() const BP2D_NOEXCEPT { return holes_.size(); } + }; template @@ -294,12 +350,20 @@ public: for(auto& nfp : nfps ) ecache.emplace_back(nfp); - auto getNfpPoint = [&ecache](double relpos) { - auto relpfloor = std::floor(relpos); - auto nfp_idx = static_cast(relpfloor); - if(nfp_idx >= ecache.size()) nfp_idx--; - auto p = relpos - relpfloor; - return ecache[nfp_idx].coords(p); + 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) {} + }; + + auto getNfpPoint = [&ecache](const Optimum& opt) + { + return opt.hidx < 0? ecache[opt.nfpidx].coords(opt.relpos) : + ecache[opt.nfpidx].coords(opt.nfpidx, opt.relpos); }; Nfp::Shapes pile; @@ -310,6 +374,8 @@ public: pile_area += mitem.area(); } + // 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](const Nfp::Shapes& pile, double occupied_area, @@ -334,9 +400,8 @@ public: }; // Our object function for placement - auto objfunc = [&] (double relpos) + auto rawobjfunc = [&] (Vertex v) { - Vertex v = getNfpPoint(relpos); auto d = v - iv; d += startpos; item.translation(d); @@ -359,46 +424,74 @@ public: stopcr.type = opt::StopLimitType::RELATIVE; opt::TOptimizer solver(stopcr); - double optimum = 0; + Optimum optimum(0, 0); double best_score = penality_; - // double max_bound = 1.0*nfps.size(); - // Genetic should look like this: - /*auto result = solver.optimize_min(objfunc, - opt::initvals(0.0), - opt::bound(0.0, max_bound) - ); - - if(result.score < penality_) { - best_score = result.score; - optimum = std::get<0>(result.optimum); - }*/ - // 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] + (double relpos) + { + return rawobjfunc(getNfpPoint(Optimum(relpos, ch))); + }; + std::for_each(cache.corners().begin(), cache.corners().end(), - [ch, &solver, &objfunc, - &best_score, &optimum] - (double pos) + [ch, &contour_ofn, &solver, &best_score, + &optimum] (double pos) { try { - auto result = solver.optimize_min(objfunc, - opt::initvals(ch+pos), - opt::bound(ch, 1.0 + ch) + auto result = solver.optimize_min(contour_ofn, + opt::initvals(pos), + opt::bound(0, 1.0) ); if(result.score < best_score) { best_score = result.score; - optimum = std::get<0>(result.optimum); + optimum.relpos = std::get<0>(result.optimum); + optimum.nfpidx = ch; + optimum.hidx = -1; } } catch(std::exception& e) { derr() << "ERROR: " << e.what() << "\n"; } }); + + 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)); + }; + + std::for_each(cache.corners(hidx).begin(), + cache.corners(hidx).end(), + [&hole_ofn, &solver, &best_score, + &optimum, ch, hidx] + (double pos) + { + try { + auto result = solver.optimize_min(hole_ofn, + opt::initvals(pos), + opt::bound(0, 1.0) + ); + + if(result.score < best_score) { + best_score = result.score; + Optimum o(std::get<0>(result.optimum), + ch, hidx); + optimum = o; + } + } catch(std::exception& e) { + derr() << "ERROR: " << e.what() << "\n"; + } + }); + } } if( best_score < global_score ) { diff --git a/xs/src/libnest2d/libnest2d/selections/firstfit.hpp b/xs/src/libnest2d/libnest2d/selections/firstfit.hpp index 2253a0dfe..b6e80520c 100644 --- a/xs/src/libnest2d/libnest2d/selections/firstfit.hpp +++ b/xs/src/libnest2d/libnest2d/selections/firstfit.hpp @@ -56,7 +56,7 @@ public: }; // Safety test: try to pack each item into an empty bin. If it fails - // then it should be removed from the not_packed list + // then it should be removed from the list { auto it = store_.begin(); while (it != store_.end()) { Placer p(bin); @@ -72,7 +72,7 @@ public: 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(item))) makeProgress(placers[j], j); } diff --git a/xs/src/libslic3r/Model.cpp b/xs/src/libslic3r/Model.cpp index 2925251eb..b2e439e5d 100644 --- a/xs/src/libslic3r/Model.cpp +++ b/xs/src/libslic3r/Model.cpp @@ -530,12 +530,12 @@ bool arrange(Model &model, coordf_t dist, const Slic3r::BoundingBoxf* bb, // arranger.useMinimumBoundigBoxRotation(); pcfg.rotations = { 0.0 }; - // Magic: we will specify what is the goal of arrangement... - // In this case we override the default object 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 + // 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