From 1745e5cff9f97c3b7fc35a5bf443bb09466738b2 Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Wed, 18 Jul 2018 16:37:44 +0200 Subject: [PATCH 01/29] Small objects can now fit inside free space surrounded by objects. --- xs/src/libnest2d/examples/main.cpp | 22 +- .../libnest2d/geometry_traits_nfp.hpp | 56 ++++- .../libnest2d/libnest2d/placers/nfpplacer.hpp | 227 ++++++++++++------ .../libnest2d/selections/firstfit.hpp | 4 +- xs/src/libslic3r/Model.cpp | 12 +- 5 files changed, 238 insertions(+), 83 deletions(-) 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 From 629108265b5c9c1e6f4496a73fe6b1c0d0f83d6d Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Thu, 26 Jul 2018 12:57:47 +0200 Subject: [PATCH 02/29] Fix for SPE-421 and emergency fix for SPE-422 (needs further investigation) --- xs/src/libnest2d/libnest2d/selections/firstfit.hpp | 3 +-- xs/src/libslic3r/Model.cpp | 4 ++-- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/xs/src/libnest2d/libnest2d/selections/firstfit.hpp b/xs/src/libnest2d/libnest2d/selections/firstfit.hpp index b6e80520c..5185014a8 100644 --- a/xs/src/libnest2d/libnest2d/selections/firstfit.hpp +++ b/xs/src/libnest2d/libnest2d/selections/firstfit.hpp @@ -61,8 +61,7 @@ public: while (it != store_.end()) { Placer p(bin); if(!p.pack(*it)) { - auto itmp = it++; - store_.erase(itmp); + it = store_.erase(it); } else it++; } } diff --git a/xs/src/libslic3r/Model.cpp b/xs/src/libslic3r/Model.cpp index b2e439e5d..8054d6a69 100644 --- a/xs/src/libslic3r/Model.cpp +++ b/xs/src/libslic3r/Model.cpp @@ -549,7 +549,7 @@ bool arrange(Model &model, coordf_t dist, const Slic3r::BoundingBoxf* bb, auto& sh = pile.back(); // We retrieve the reference point of this item - auto rv = Nfp::referenceVertex(sh); + auto rv = ShapeLike::boundingBox(sh).center(); // We get the distance of the reference point from the center of the // heat bed @@ -558,7 +558,7 @@ bool arrange(Model &model, coordf_t dist, const Slic3r::BoundingBoxf* bb, // The score will be the normalized distance which will be minimized, // effectively creating a circle shaped pile of items - double score = double(d)/norm; + double score = d/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 From 84f97e1f64adc8b0f6f3c31fe1e22ba2e97e4572 Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Fri, 27 Jul 2018 12:28:14 +0200 Subject: [PATCH 03/29] Improved libnest2d caching --- xs/src/libnest2d/CMakeLists.txt | 20 +- xs/src/libnest2d/cmake_modules/FindGMP.cmake | 35 -- xs/src/libnest2d/examples/main.cpp | 59 ++- xs/src/libnest2d/libnest2d.h | 2 +- xs/src/libnest2d/libnest2d/boost_alg.hpp | 19 +- .../clipper_backend/clipper_backend.cpp | 58 --- .../clipper_backend/clipper_backend.hpp | 86 ++-- xs/src/libnest2d/libnest2d/common.hpp | 41 +- .../libnest2d/libnest2d/geometry_traits.hpp | 112 ++--- .../libnest2d/geometry_traits_nfp.hpp | 475 +++++++++++++----- xs/src/libnest2d/libnest2d/libnest2d.hpp | 131 +++-- xs/src/libnest2d/libnest2d/metaloop.hpp | 227 +++++++++ xs/src/libnest2d/libnest2d/optimizer.hpp | 207 +------- .../optimizers/nlopt_boilerplate.hpp | 24 +- .../libnest2d/libnest2d/placers/nfpplacer.hpp | 260 ++++++++-- .../libnest2d/selections/djd_heuristic.hpp | 24 +- .../libnest2d/selections/firstfit.hpp | 2 +- xs/src/libnest2d/tests/test.cpp | 15 +- xs/src/libnest2d/tools/libnfpglue.cpp | 79 +-- xs/src/libnest2d/tools/libnfpglue.hpp | 28 +- xs/src/libnest2d/tools/svgtools.hpp | 8 +- xs/src/libslic3r/Model.cpp | 34 +- 22 files changed, 1178 insertions(+), 768 deletions(-) delete mode 100644 xs/src/libnest2d/cmake_modules/FindGMP.cmake delete mode 100644 xs/src/libnest2d/libnest2d/clipper_backend/clipper_backend.cpp create mode 100644 xs/src/libnest2d/libnest2d/metaloop.hpp diff --git a/xs/src/libnest2d/CMakeLists.txt b/xs/src/libnest2d/CMakeLists.txt index bfdb551fc..835e8311d 100644 --- a/xs/src/libnest2d/CMakeLists.txt +++ b/xs/src/libnest2d/CMakeLists.txt @@ -2,8 +2,6 @@ cmake_minimum_required(VERSION 2.8) project(Libnest2D) -enable_testing() - if(CMAKE_COMPILER_IS_GNUCC OR CMAKE_COMPILER_IS_GNUCXX) # Update if necessary set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -Wno-long-long ") @@ -32,6 +30,7 @@ set(LIBNEST2D_SRCFILES ${CMAKE_CURRENT_SOURCE_DIR}/libnest2d/geometry_traits.hpp ${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/placers/placer_boilerplate.hpp ${CMAKE_CURRENT_SOURCE_DIR}/libnest2d/placers/bottomleftplacer.hpp ${CMAKE_CURRENT_SOURCE_DIR}/libnest2d/placers/nfpplacer.hpp @@ -60,8 +59,7 @@ if(LIBNEST2D_GEOMETRIES_BACKEND STREQUAL "clipper") include_directories(BEFORE ${CLIPPER_INCLUDE_DIRS}) include_directories(${Boost_INCLUDE_DIRS}) - list(APPEND LIBNEST2D_SRCFILES ${CMAKE_CURRENT_SOURCE_DIR}/libnest2d/clipper_backend/clipper_backend.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/libnest2d/clipper_backend/clipper_backend.hpp + list(APPEND LIBNEST2D_SRCFILES ${CMAKE_CURRENT_SOURCE_DIR}/libnest2d/clipper_backend/clipper_backend.hpp ${CMAKE_CURRENT_SOURCE_DIR}/libnest2d/boost_alg.hpp) list(APPEND LIBNEST2D_LIBRARIES ${CLIPPER_LIBRARIES}) list(APPEND LIBNEST2D_HEADERS ${CLIPPER_INCLUDE_DIRS} @@ -81,22 +79,12 @@ if(LIBNEST2D_OPTIMIZER_BACKEND STREQUAL "nlopt") ${CMAKE_CURRENT_SOURCE_DIR}/libnest2d/optimizers/subplex.hpp ${CMAKE_CURRENT_SOURCE_DIR}/libnest2d/optimizers/genetic.hpp ${CMAKE_CURRENT_SOURCE_DIR}/libnest2d/optimizers/nlopt_boilerplate.hpp) - list(APPEND LIBNEST2D_LIBRARIES ${NLopt_LIBS} -# Threads::Threads - ) + list(APPEND LIBNEST2D_LIBRARIES ${NLopt_LIBS}) list(APPEND LIBNEST2D_HEADERS ${NLopt_INCLUDE_DIR}) endif() -# Currently we are outsourcing the non-convex NFP implementation from -# libnfporb and it needs libgmp to work -#find_package(GMP) -#if(GMP_FOUND) -# list(APPEND LIBNEST2D_LIBRARIES ${GMP_LIBRARIES}) -# list(APPEND LIBNEST2D_HEADERS ${GMP_INCLUDE_DIR}) -# add_definitions(-DLIBNFP_USE_RATIONAL) -#endif() - if(LIBNEST2D_UNITTESTS) + enable_testing() add_subdirectory(tests) endif() diff --git a/xs/src/libnest2d/cmake_modules/FindGMP.cmake b/xs/src/libnest2d/cmake_modules/FindGMP.cmake deleted file mode 100644 index db173bc90..000000000 --- a/xs/src/libnest2d/cmake_modules/FindGMP.cmake +++ /dev/null @@ -1,35 +0,0 @@ -# Try to find the GMP libraries: -# GMP_FOUND - System has GMP lib -# GMP_INCLUDE_DIR - The GMP include directory -# GMP_LIBRARIES - Libraries needed to use GMP - -if (GMP_INCLUDE_DIR AND GMP_LIBRARIES) - # Force search at every time, in case configuration changes - unset(GMP_INCLUDE_DIR CACHE) - unset(GMP_LIBRARIES CACHE) -endif (GMP_INCLUDE_DIR AND GMP_LIBRARIES) - -find_path(GMP_INCLUDE_DIR NAMES gmp.h) - -if(WIN32) - find_library(GMP_LIBRARIES NAMES libgmp.a gmp gmp.lib mpir mpir.lib) -else(WIN32) - if(STBIN) - message(STATUS "STBIN: ${STBIN}") - find_library(GMP_LIBRARIES NAMES libgmp.a gmp) - else(STBIN) - find_library(GMP_LIBRARIES NAMES libgmp.so gmp) - endif(STBIN) -endif(WIN32) - -if(GMP_INCLUDE_DIR AND GMP_LIBRARIES) - set(GMP_FOUND TRUE) -endif(GMP_INCLUDE_DIR AND GMP_LIBRARIES) - -if(GMP_FOUND) - message(STATUS "Configured GMP: ${GMP_LIBRARIES}") -else(GMP_FOUND) - message(STATUS "Could NOT find GMP") -endif(GMP_FOUND) - -mark_as_advanced(GMP_INCLUDE_DIR GMP_LIBRARIES) \ No newline at end of file diff --git a/xs/src/libnest2d/examples/main.cpp b/xs/src/libnest2d/examples/main.cpp index e5a47161e..a97618578 100644 --- a/xs/src/libnest2d/examples/main.cpp +++ b/xs/src/libnest2d/examples/main.cpp @@ -535,17 +535,18 @@ void arrangeRectangles() { 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()); - 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); - Coord min_obj_distance = 6*SCALE; + auto min_obj_distance = static_cast(0*SCALE); using Placer = NfpPlacer; using Packer = Arranger; @@ -554,21 +555,45 @@ void arrangeRectangles() { Packer::PlacementConfig pconf; pconf.alignment = Placer::Config::Alignment::CENTER; - pconf.starting_point = Placer::Config::Alignment::CENTER; + pconf.starting_point = Placer::Config::Alignment::BOTTOM_LEFT; pconf.rotations = {0.0/*, Pi/2.0, Pi, 3*Pi/2*/}; - pconf.object_function = [&bin](Placer::Pile pile, double area, - double norm, double penality) { + + double norm_2 = std::nan(""); + pconf.object_function = [&bin, &norm_2](Placer::Pile pile, const Item& item, + double /*area*/, double norm, double penality) { + + using pl = PointLike; auto bb = ShapeLike::boundingBox(pile); + auto ibb = item.boundingBox(); + auto minc = ibb.minCorner(); + auto maxc = ibb.maxCorner(); - auto& sh = pile.back(); - auto rv = Nfp::referenceVertex(sh); - auto c = bin.center(); - auto d = PointLike::distance(rv, c); - double score = double(d)/norm; + if(std::isnan(norm_2)) norm_2 = pow(norm, 2); + + // 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)}; + + 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); + + auto area = bb.width() * bb.height() / norm_2; + + auto min_dist = std::min({a, b, c, d, e}) / norm; + + // 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; // If it does not fit into the print bed we will beat it - // with a large penality + // 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; return score; @@ -577,7 +602,7 @@ void arrangeRectangles() { Packer::SelectionConfig sconf; // sconf.allow_parallel = false; // sconf.force_parallel = false; -// sconf.try_triplets = false; +// sconf.try_triplets = true; // sconf.try_reverse_order = true; // sconf.waste_increment = 0.005; @@ -630,7 +655,7 @@ void arrangeRectangles() { << " %" << std::endl; std::cout << "Bin usage: ("; - unsigned total = 0; + size_t total = 0; for(auto& r : result) { std::cout << r.size() << " "; total += r.size(); } std::cout << ") Total: " << total << std::endl; @@ -643,9 +668,11 @@ void arrangeRectangles() { << input.size() - total << " elements!" << std::endl; - svg::SVGWriter::Config conf; + using SVGWriter = svg::SVGWriter; + + SVGWriter::Config conf; conf.mm_in_coord_units = SCALE; - svg::SVGWriter svgw(conf); + SVGWriter svgw(conf); svgw.setSize(bin); svgw.writePackGroup(result); // std::for_each(input.begin(), input.end(), [&svgw](Item& item){ svgw.writeItem(item);}); diff --git a/xs/src/libnest2d/libnest2d.h b/xs/src/libnest2d/libnest2d.h index 1e0a98f6a..e0ad05c41 100644 --- a/xs/src/libnest2d/libnest2d.h +++ b/xs/src/libnest2d/libnest2d.h @@ -6,7 +6,7 @@ #include // We include the stock optimizers for local and global optimization -#include // Local subplex for NfpPlacer +#include // Local simplex for NfpPlacer #include // Genetic for min. bounding box #include diff --git a/xs/src/libnest2d/libnest2d/boost_alg.hpp b/xs/src/libnest2d/libnest2d/boost_alg.hpp index a50b397d3..422616d20 100644 --- a/xs/src/libnest2d/libnest2d/boost_alg.hpp +++ b/xs/src/libnest2d/libnest2d/boost_alg.hpp @@ -8,8 +8,16 @@ #ifdef __clang__ #undef _MSC_EXTENSIONS #endif -#include +#ifdef _MSC_VER +#pragma warning(push) +#pragma warning(disable: 4244) +#pragma warning(disable: 4267) +#endif +#include +#ifdef _MSC_VER +#pragma warning(pop) +#endif // this should be removed to not confuse the compiler // #include @@ -461,15 +469,6 @@ inline bp2d::Shapes Nfp::merge(const bp2d::Shapes& shapes, } #endif -//#ifndef DISABLE_BOOST_MINKOWSKI_ADD -//template<> -//inline PolygonImpl& Nfp::minkowskiAdd(PolygonImpl& sh, -// const PolygonImpl& /*other*/) -//{ -// return sh; -//} -//#endif - #ifndef DISABLE_BOOST_SERIALIZE template<> inline std::string ShapeLike::serialize( const PolygonImpl& sh, double scale) diff --git a/xs/src/libnest2d/libnest2d/clipper_backend/clipper_backend.cpp b/xs/src/libnest2d/libnest2d/clipper_backend/clipper_backend.cpp deleted file mode 100644 index 830d235a3..000000000 --- a/xs/src/libnest2d/libnest2d/clipper_backend/clipper_backend.cpp +++ /dev/null @@ -1,58 +0,0 @@ -//#include "clipper_backend.hpp" -//#include - -//namespace libnest2d { - -//namespace { - -//class SpinLock { -// std::atomic_flag& lck_; -//public: - -// inline SpinLock(std::atomic_flag& flg): lck_(flg) {} - -// inline void lock() { -// while(lck_.test_and_set(std::memory_order_acquire)) {} -// } - -// inline void unlock() { lck_.clear(std::memory_order_release); } -//}; - -//class HoleCache { -// friend struct libnest2d::ShapeLike; - -// std::unordered_map< const PolygonImpl*, ClipperLib::Paths> map; - -// ClipperLib::Paths& _getHoles(const PolygonImpl* p) { -// static std::atomic_flag flg = ATOMIC_FLAG_INIT; -// SpinLock lock(flg); - -// lock.lock(); -// ClipperLib::Paths& paths = map[p]; -// lock.unlock(); - -// if(paths.size() != p->Childs.size()) { -// paths.reserve(p->Childs.size()); - -// for(auto np : p->Childs) { -// paths.emplace_back(np->Contour); -// } -// } - -// return paths; -// } - -// ClipperLib::Paths& getHoles(PolygonImpl& p) { -// return _getHoles(&p); -// } - -// const ClipperLib::Paths& getHoles(const PolygonImpl& p) { -// return _getHoles(&p); -// } -//}; -//} - -//HoleCache holeCache; - -//} - diff --git a/xs/src/libnest2d/libnest2d/clipper_backend/clipper_backend.hpp b/xs/src/libnest2d/libnest2d/clipper_backend/clipper_backend.hpp index 8cc27573a..15ceb1576 100644 --- a/xs/src/libnest2d/libnest2d/clipper_backend/clipper_backend.hpp +++ b/xs/src/libnest2d/libnest2d/clipper_backend/clipper_backend.hpp @@ -21,7 +21,7 @@ struct PolygonImpl { PathImpl Contour; HoleStore Holes; - inline PolygonImpl() {} + inline PolygonImpl() = default; inline explicit PolygonImpl(const PathImpl& cont): Contour(cont) {} inline explicit PolygonImpl(const HoleStore& holes): @@ -66,6 +66,19 @@ inline PointImpl operator-(const PointImpl& p1, const PointImpl& p2) { ret -= p2; return ret; } + +inline PointImpl& operator *=(PointImpl& p, const PointImpl& pa ) { + p.X *= pa.X; + p.Y *= pa.Y; + return p; +} + +inline PointImpl operator*(const PointImpl& p1, const PointImpl& p2) { + PointImpl ret = p1; + ret *= p2; + return ret; +} + } namespace libnest2d { @@ -135,7 +148,7 @@ inline void ShapeLike::reserve(PolygonImpl& sh, size_t vertex_capacity) namespace _smartarea { template -inline double area(const PolygonImpl& sh) { +inline double area(const PolygonImpl& /*sh*/) { return std::nan(""); } @@ -220,22 +233,6 @@ inline void ShapeLike::offset(PolygonImpl& sh, TCoord distance) { } } -//template<> // TODO make it support holes if this method will ever be needed. -//inline PolygonImpl Nfp::minkowskiDiff(const PolygonImpl& sh, -// const PolygonImpl& other) -//{ -// #define DISABLE_BOOST_MINKOWSKI_ADD - -// ClipperLib::Paths solution; - -// ClipperLib::MinkowskiDiff(sh.Contour, other.Contour, solution); - -// PolygonImpl ret; -// ret.Contour = solution.front(); - -// return sh; -//} - // Tell libnest2d how to make string out of a ClipperPolygon object template<> inline std::string ShapeLike::toString(const PolygonImpl& sh) { std::stringstream ss; @@ -406,35 +403,12 @@ inline void ShapeLike::rotate(PolygonImpl& sh, const Radians& rads) } #define DISABLE_BOOST_NFP_MERGE -template<> inline Nfp::Shapes -Nfp::merge(const Nfp::Shapes& shapes, const PolygonImpl& sh) -{ +inline Nfp::Shapes _merge(ClipperLib::Clipper& clipper) { Nfp::Shapes retv; - ClipperLib::Clipper clipper(ClipperLib::ioReverseSolution); - - bool closed = true; - bool valid = false; - - valid = clipper.AddPath(sh.Contour, ClipperLib::ptSubject, closed); - - for(auto& hole : sh.Holes) { - valid &= clipper.AddPath(hole, ClipperLib::ptSubject, closed); - } - - for(auto& path : shapes) { - valid &= clipper.AddPath(path.Contour, ClipperLib::ptSubject, closed); - - for(auto& hole : path.Holes) { - valid &= clipper.AddPath(hole, ClipperLib::ptSubject, closed); - } - } - - if(!valid) throw GeometryException(GeomErr::MERGE); - ClipperLib::PolyTree result; - clipper.Execute(ClipperLib::ctUnion, result, ClipperLib::pftNonZero); - retv.reserve(result.Total()); + clipper.Execute(ClipperLib::ctUnion, result, ClipperLib::pftNegative); + retv.reserve(static_cast(result.Total())); std::function processHole; @@ -445,7 +419,8 @@ Nfp::merge(const Nfp::Shapes& shapes, const PolygonImpl& sh) retv.push_back(poly); }; - processHole = [&processPoly](ClipperLib::PolyNode *pptr, PolygonImpl& poly) { + processHole = [&processPoly](ClipperLib::PolyNode *pptr, PolygonImpl& poly) + { poly.Holes.push_back(pptr->Contour); poly.Holes.back().push_back(poly.Holes.back().front()); for(auto c : pptr->Childs) processPoly(c); @@ -463,6 +438,27 @@ Nfp::merge(const Nfp::Shapes& shapes, const PolygonImpl& sh) return retv; } +template<> inline Nfp::Shapes +Nfp::merge(const Nfp::Shapes& shapes) +{ + ClipperLib::Clipper clipper(ClipperLib::ioReverseSolution); + + bool closed = true; + bool valid = true; + + for(auto& path : shapes) { + valid &= clipper.AddPath(path.Contour, ClipperLib::ptSubject, closed); + + for(auto& hole : path.Holes) { + valid &= clipper.AddPath(hole, ClipperLib::ptSubject, closed); + } + } + + if(!valid) throw GeometryException(GeomErr::MERGE); + + return _merge(clipper); +} + } //#define DISABLE_BOOST_SERIALIZE diff --git a/xs/src/libnest2d/libnest2d/common.hpp b/xs/src/libnest2d/libnest2d/common.hpp index 18f313712..6867f76f3 100644 --- a/xs/src/libnest2d/libnest2d/common.hpp +++ b/xs/src/libnest2d/libnest2d/common.hpp @@ -13,6 +13,7 @@ #if defined(_MSC_VER) && _MSC_VER <= 1800 || __cplusplus < 201103L #define BP2D_NOEXCEPT #define BP2D_CONSTEXPR + #define BP2D_COMPILER_MSVC12 #elif __cplusplus >= 201103L #define BP2D_NOEXCEPT noexcept #define BP2D_CONSTEXPR constexpr @@ -84,44 +85,6 @@ struct invoke_result { template using invoke_result_t = typename invoke_result::type; -/* ************************************************************************** */ -/* C++14 std::index_sequence implementation: */ -/* ************************************************************************** */ - -/** - * \brief C++11 conformant implementation of the index_sequence type from C++14 - */ -template struct index_sequence { - using value_type = size_t; - BP2D_CONSTEXPR value_type size() const { return sizeof...(Ints); } -}; - -// A Help structure to generate the integer list -template struct genSeq; - -// Recursive template to generate the list -template struct genSeq { - // Type will contain a genSeq with Nseq appended by one element - using Type = typename genSeq< I - 1, I - 1, Nseq...>::Type; -}; - -// Terminating recursion -template struct genSeq<0, Nseq...> { - // If I is zero, Type will contain index_sequence with the fuly generated - // integer list. - using Type = index_sequence; -}; - -/// Helper alias to make an index sequence from 0 to N -template using make_index_sequence = typename genSeq::Type; - -/// Helper alias to make an index sequence for a parameter pack -template -using index_sequence_for = make_index_sequence; - - -/* ************************************************************************** */ - /** * A useful little tool for triggering static_assert error messages e.g. when * a mandatory template specialization (implementation) is missing. @@ -229,7 +192,7 @@ public: GeomErr errcode() const { return errcode_; } - virtual const char * what() const BP2D_NOEXCEPT override { + const char * what() const BP2D_NOEXCEPT override { return errorstr(errcode_).c_str(); } }; diff --git a/xs/src/libnest2d/libnest2d/geometry_traits.hpp b/xs/src/libnest2d/libnest2d/geometry_traits.hpp index dbd609201..568c0a766 100644 --- a/xs/src/libnest2d/libnest2d/geometry_traits.hpp +++ b/xs/src/libnest2d/libnest2d/geometry_traits.hpp @@ -68,7 +68,7 @@ class _Box: PointPair { using PointPair::p2; public: - inline _Box() {} + inline _Box() = default; inline _Box(const RawPoint& p, const RawPoint& pp): PointPair({p, pp}) {} @@ -97,7 +97,7 @@ class _Segment: PointPair { mutable Radians angletox_ = std::nan(""); public: - inline _Segment() {} + inline _Segment() = default; inline _Segment(const RawPoint& p, const RawPoint& pp): PointPair({p, pp}) {} @@ -188,7 +188,7 @@ struct PointLike { if( (y < y1 && y < y2) || (y > y1 && y > y2) ) return {0, false}; - else if ((y == y1 && y == y2) && (x > x1 && x > x2)) + if ((y == y1 && y == y2) && (x > x1 && x > x2)) ret = std::min( x-x1, x -x2); else if( (y == y1 && y == y2) && (x < x1 && x < x2)) ret = -std::min(x1 - x, x2 - x); @@ -214,7 +214,7 @@ struct PointLike { if( (x < x1 && x < x2) || (x > x1 && x > x2) ) return {0, false}; - else if ((x == x1 && x == x2) && (y > y1 && y > y2)) + if ((x == x1 && x == x2) && (y > y1 && y > y2)) ret = std::min( y-y1, y -y2); else if( (x == x1 && x == x2) && (y < y1 && y < y2)) ret = -std::min(y1 - y, y2 - y); @@ -329,7 +329,7 @@ enum class Formats { }; // This struct serves as a namespace. The only difference is that it can be -// used in friend declarations. +// used in friend declarations and can be aliased at class scope. struct ShapeLike { template @@ -361,6 +361,51 @@ struct ShapeLike { return create(contour, {}); } + template + static THolesContainer& holes(RawShape& /*sh*/) + { + static THolesContainer empty; + return empty; + } + + template + static const THolesContainer& holes(const RawShape& /*sh*/) + { + static THolesContainer empty; + return empty; + } + + template + static TContour& getHole(RawShape& sh, unsigned long idx) + { + return holes(sh)[idx]; + } + + template + static const TContour& getHole(const RawShape& sh, + unsigned long idx) + { + return holes(sh)[idx]; + } + + template + static size_t holeCount(const RawShape& sh) + { + return holes(sh).size(); + } + + template + static TContour& getContour(RawShape& sh) + { + return sh; + } + + template + static const TContour& getContour(const RawShape& sh) + { + return sh; + } + // Optional, does nothing by default template static void reserve(RawShape& /*sh*/, size_t /*vertex_capacity*/) {} @@ -402,7 +447,7 @@ struct ShapeLike { } template - static std::string serialize(const RawShape& /*sh*/, double scale=1) + static std::string serialize(const RawShape& /*sh*/, double /*scale*/=1) { static_assert(always_false::value, "ShapeLike::serialize() unimplemented!"); @@ -498,51 +543,6 @@ struct ShapeLike { return RawShape(); } - template - static THolesContainer& holes(RawShape& /*sh*/) - { - static THolesContainer empty; - return empty; - } - - template - static const THolesContainer& holes(const RawShape& /*sh*/) - { - static THolesContainer empty; - return empty; - } - - template - static TContour& getHole(RawShape& sh, unsigned long idx) - { - return holes(sh)[idx]; - } - - template - static const TContour& getHole(const RawShape& sh, - unsigned long idx) - { - return holes(sh)[idx]; - } - - template - static size_t holeCount(const RawShape& sh) - { - return holes(sh).size(); - } - - template - static TContour& getContour(RawShape& sh) - { - return sh; - } - - template - static const TContour& getContour(const RawShape& sh) - { - return sh; - } - template static void rotate(RawShape& /*sh*/, const Radians& /*rads*/) { @@ -621,14 +621,12 @@ struct ShapeLike { } template - static double area(const Shapes& shapes) + static inline double area(const Shapes& shapes) { - double ret = 0; - std::accumulate(shapes.first(), shapes.end(), - [](const RawShape& a, const RawShape& b) { - return area(a) + area(b); + return std::accumulate(shapes.begin(), shapes.end(), 0.0, + [](double a, const RawShape& b) { + return a += area(b); }); - return ret; } template // Potential O(1) implementation may exist diff --git a/xs/src/libnest2d/libnest2d/geometry_traits_nfp.hpp b/xs/src/libnest2d/libnest2d/geometry_traits_nfp.hpp index 581b6bed0..90cf21be5 100644 --- a/xs/src/libnest2d/libnest2d/geometry_traits_nfp.hpp +++ b/xs/src/libnest2d/libnest2d/geometry_traits_nfp.hpp @@ -3,7 +3,9 @@ #include "geometry_traits.hpp" #include +#include #include +#include namespace libnest2d { @@ -23,64 +25,22 @@ struct Nfp { template using Shapes = typename ShapeLike::Shapes; -/// Minkowski addition (not used yet) +/** + * Merge a bunch of polygons with the specified additional polygon. + * + * \tparam RawShape the Polygon data type. + * \param shc The pile of polygons that will be unified with sh. + * \param sh A single polygon to unify with shc. + * + * \return A set of polygons that is the union of the input polygons. Note that + * 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 -static RawShape minkowskiDiff(const RawShape& sh, const RawShape& cother) +static Shapes merge(const Shapes& /*shc*/) { - 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; + static_assert(always_false::value, + "Nfp::merge(shapes, shape) unimplemented!"); } /** @@ -95,10 +55,12 @@ static RawShape minkowskiDiff(const RawShape& sh, const RawShape& cother) * polygons are disjuct than the resulting set will contain more polygons. */ template -static Shapes merge(const Shapes& shc, const RawShape& sh) +static Shapes merge(const Shapes& shc, + const RawShape& sh) { - static_assert(always_false::value, - "Nfp::merge(shapes, shape) unimplemented!"); + auto m = merge(shc); + m.push_back(sh); + return merge(m); } /** @@ -139,16 +101,20 @@ template static TPoint rightmostUpVertex(const RawShape& sh) { - // find min x and min y vertex + // find max x and max y vertex auto it = std::max_element(ShapeLike::cbegin(sh), ShapeLike::cend(sh), _vsort); return *it; } +template +using NfpResult = std::pair>; + /// Helper function to get the NFP template -static RawShape noFitPolygon(const RawShape& sh, const RawShape& other) +static NfpResult noFitPolygon(const RawShape& sh, + const RawShape& other) { NfpImpl nfp; return nfp(sh, other); @@ -167,44 +133,46 @@ static RawShape noFitPolygon(const RawShape& sh, const RawShape& other) * \tparam RawShape the Polygon data type. * \param sh The stationary polygon * \param cother The orbiting polygon - * \return Returns the NFP of the two input polygons which have to be strictly - * convex. The resulting NFP is proven to be convex as well in this case. + * \return Returns a pair of the NFP and its reference vertex of the two input + * polygons which have to be strictly convex. The resulting NFP is proven to be + * convex as well in this case. * */ template -static RawShape nfpConvexOnly(const RawShape& sh, const RawShape& cother) +static NfpResult nfpConvexOnly(const RawShape& sh, + const RawShape& other) { using Vertex = TPoint; using Edge = _Segment; - - RawShape other = cother; - - // Make the other polygon counter-clockwise - std::reverse(ShapeLike::begin(other), ShapeLike::end(other)); + using sl = ShapeLike; RawShape rsh; // Final nfp placeholder + Vertex top_nfp; std::vector edgelist; - auto cap = ShapeLike::contourVertexCount(sh) + - ShapeLike::contourVertexCount(other); + auto cap = sl::contourVertexCount(sh) + sl::contourVertexCount(other); // Reserve the needed memory edgelist.reserve(cap); - ShapeLike::reserve(rsh, static_cast(cap)); + sl::reserve(rsh, static_cast(cap)); { // place all edges from sh into edgelist - auto first = ShapeLike::cbegin(sh); - auto next = first + 1; - auto endit = ShapeLike::cend(sh); + auto first = sl::cbegin(sh); + auto next = std::next(first); - while(next != endit) edgelist.emplace_back(*(first++), *(next++)); + while(next != sl::cend(sh)) { + edgelist.emplace_back(*(first), *(next)); + ++first; ++next; + } } { // place all edges from other into edgelist - auto first = ShapeLike::cbegin(other); - auto next = first + 1; - auto endit = ShapeLike::cend(other); + auto first = sl::cbegin(other); + auto next = std::next(first); - while(next != endit) edgelist.emplace_back(*(first++), *(next++)); + while(next != sl::cend(other)) { + edgelist.emplace_back(*(next), *(first)); + ++first; ++next; + } } // Sort the edges by angle to X axis. @@ -215,10 +183,16 @@ static RawShape nfpConvexOnly(const RawShape& sh, const RawShape& cother) }); // Add the two vertices from the first edge into the final polygon. - ShapeLike::addVertex(rsh, edgelist.front().first()); - ShapeLike::addVertex(rsh, edgelist.front().second()); + sl::addVertex(rsh, edgelist.front().first()); + sl::addVertex(rsh, edgelist.front().second()); - auto tmp = std::next(ShapeLike::begin(rsh)); + // Sorting function for the nfp reference vertex search + auto& cmp = _vsort; + + // the reference (rightmost top) vertex so far + top_nfp = *std::max_element(sl::cbegin(rsh), sl::cend(rsh), cmp ); + + auto tmp = std::next(sl::begin(rsh)); // Construct final nfp by placing each edge to the end of the previous for(auto eit = std::next(edgelist.begin()); @@ -226,56 +200,325 @@ static RawShape nfpConvexOnly(const RawShape& sh, const RawShape& cother) ++eit) { auto d = *tmp - eit->first(); - auto p = eit->second() + d; + Vertex p = eit->second() + d; - ShapeLike::addVertex(rsh, p); + sl::addVertex(rsh, p); + + // Set the new reference vertex + if(cmp(top_nfp, p)) top_nfp = p; tmp = std::next(tmp); } - // Now we have an nfp somewhere in the dark. We need to get it - // to the right position around the stationary shape. - // This is done by choosing the leftmost lowest vertex of the - // orbiting polygon to be touched with the rightmost upper - // vertex of the stationary polygon. In this configuration, the - // reference vertex of the orbiting polygon (which can be dragged around - // the nfp) will be its rightmost upper vertex that coincides with the - // rightmost upper vertex of the nfp. No proof provided other than Jonas - // Lindmark's reasoning about the reference vertex of nfp in his thesis - // ("No fit polygon problem" - section 2.1.9) + return {rsh, top_nfp}; +} - // TODO: dont do this here. Cache the rmu and lmd in Item and get translate - // the nfp after this call +template +static NfpResult nfpSimpleSimple(const RawShape& cstationary, + const RawShape& cother) +{ - 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); - std::sort(ShapeLike::begin(other), ShapeLike::end(other), cmp); + // Algorithms are from the original algorithm proposed in paper: + // https://eprints.soton.ac.uk/36850/1/CORMSIS-05-05.pdf - // leftmost lower vertex of the stationary polygon - auto& touch_sh = *(std::prev(ShapeLike::end(csh))); - // rightmost upper vertex of the orbiting polygon - auto& touch_other = *(ShapeLike::begin(other)); + // ///////////////////////////////////////////////////////////////////////// + // Algorithm 1: Obtaining the minkowski sum + // ///////////////////////////////////////////////////////////////////////// - // Calculate the difference and move the orbiter to the touch position. - auto dtouch = touch_sh - touch_other; - auto top_other = *(std::prev(ShapeLike::end(other))) + dtouch; + // I guess this is not a full minkowski sum of the two input polygons by + // definition. This yields a subset that is compatible with the next 2 + // algorithms. - // Get the righmost upper vertex of the nfp and move it to the RMU of - // the orbiter because they should coincide. - auto&& top_nfp = rightmostUpVertex(rsh); - auto dnfp = top_other - top_nfp; - std::for_each(ShapeLike::begin(rsh), ShapeLike::end(rsh), - [&dnfp](Vertex& v) { v+= dnfp; } ); + using Result = NfpResult; + using Vertex = TPoint; + using Coord = TCoord; + using Edge = _Segment; + using sl = ShapeLike; + using std::signbit; + using std::sort; + using std::vector; + using std::ref; + using std::reference_wrapper; - return rsh; + // TODO The original algorithms expects the stationary polygon in + // counter clockwise and the orbiter in clockwise order. + // So for preventing any further complication, I will make the input + // the way it should be, than make my way around the orientations. + + // Reverse the stationary contour to counter clockwise + auto stcont = sl::getContour(cstationary); + std::reverse(stcont.begin(), stcont.end()); + RawShape stationary; + sl::getContour(stationary) = stcont; + + // Reverse the orbiter contour to counter clockwise + auto orbcont = sl::getContour(cother); + + std::reverse(orbcont.begin(), orbcont.end()); + + // Copy the orbiter (contour only), we will have to work on it + RawShape orbiter; + sl::getContour(orbiter) = orbcont; + + // Step 1: 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 = 0; bool is_turning_point = false; + MarkedEdge() = default; + MarkedEdge(const Edge& ed, Radians ta, bool tp): + e(ed), turn_angle(ta), is_turning_point(tp) {} + }; + + // Container for marked edges + using EdgeList = vector; + + EdgeList A, B; + + // This is how an edge list is created from the polygons + auto fillEdgeList = [](EdgeList& L, const RawShape& poly, int dir) { + L.reserve(sl::contourVertexCount(poly)); + + auto it = sl::cbegin(poly); + auto nextit = std::next(it); + + double turn_angle = 0; + bool is_turn_point = false; + + while(nextit != sl::cend(poly)) { + L.emplace_back(Edge(*it, *nextit), turn_angle, is_turn_point); + it++; nextit++; + } + + auto getTurnAngle = [](const Edge& e1, const Edge& e2) { + auto phi = e1.angleToXaxis(); + auto phi_prev = e2.angleToXaxis(); + auto TwoPi = 2.0*Pi; + if(phi > Pi) phi -= TwoPi; + if(phi_prev > Pi) phi_prev -= TwoPi; + auto turn_angle = phi-phi_prev; + if(turn_angle > Pi) turn_angle -= TwoPi; + return phi-phi_prev; + }; + + if(dir > 0) { + auto eit = L.begin(); + auto enext = std::next(eit); + + eit->turn_angle = getTurnAngle(L.front().e, L.back().e); + + while(enext != L.end()) { + enext->turn_angle = getTurnAngle( enext->e, eit->e); + enext->is_turning_point = + signbit(enext->turn_angle) != signbit(eit->turn_angle); + ++eit; ++enext; + } + + L.front().is_turning_point = signbit(L.front().turn_angle) != + signbit(L.back().turn_angle); + } else { + std::cout << L.size() << std::endl; + + auto eit = L.rbegin(); + auto enext = std::next(eit); + + eit->turn_angle = getTurnAngle(L.back().e, L.front().e); + + while(enext != L.rend()) { + enext->turn_angle = getTurnAngle(enext->e, eit->e); + enext->is_turning_point = + signbit(enext->turn_angle) != signbit(eit->turn_angle); + std::cout << enext->is_turning_point << " " << enext->turn_angle << std::endl; + + ++eit; ++enext; + } + + L.back().is_turning_point = signbit(L.back().turn_angle) != + signbit(L.front().turn_angle); + } + }; + + // Step 2: Fill the edgelists + fillEdgeList(A, stationary, 1); + fillEdgeList(B, orbiter, -1); + + // A reference to a marked edge that also knows its container + struct MarkedEdgeRef { + reference_wrapper eref; + reference_wrapper> container; + Coord dir = 1; // Direction modifier + + inline Radians angleX() const { return eref.get().e.angleToXaxis(); } + inline const Edge& edge() const { return eref.get().e; } + inline Edge& edge() { return eref.get().e; } + inline bool isTurningPoint() const { + return eref.get().is_turning_point; + } + inline bool isFrom(const vector& cont ) { + return &(container.get()) == &cont; + } + inline bool eq(const MarkedEdgeRef& mr) { + return &(eref.get()) == &(mr.eref.get()); + } + + MarkedEdgeRef(reference_wrapper er, + reference_wrapper> ec): + eref(er), container(ec), dir(1) {} + + MarkedEdgeRef(reference_wrapper er, + reference_wrapper> ec, + Coord d): + eref(er), container(ec), dir(d) {} + }; + + using EdgeRefList = vector; + + // Comparing two marked edges + auto sortfn = [](const MarkedEdgeRef& e1, const MarkedEdgeRef& e2) { + return e1.angleX() < e2.angleX(); + }; + + EdgeRefList Aref, Bref; // We create containers for the references + Aref.reserve(A.size()); Bref.reserve(B.size()); + + // Fill reference container for the stationary polygon + std::for_each(A.begin(), A.end(), [&Aref](MarkedEdge& me) { + Aref.emplace_back( ref(me), ref(Aref) ); + }); + + // Fill reference container for the orbiting polygon + std::for_each(B.begin(), B.end(), [&Bref](MarkedEdge& me) { + Bref.emplace_back( ref(me), ref(Bref) ); + }); + + struct EdgeGroup { typename EdgeRefList::const_iterator first, last; }; + + auto mink = [sortfn] // the Mink(Q, R, direction) sub-procedure + (const EdgeGroup& Q, const EdgeGroup& R, bool positive) + { + + // Step 1 "merge sort_list(Q) and sort_list(R) to form merge_list(Q,R)" + // Sort the containers of edge references and merge them. + // Q could be sorted only once and be reused here but we would still + // need to merge it with sorted(R). + + EdgeRefList merged; + EdgeRefList S, seq; + merged.reserve((Q.last - Q.first) + (R.last - R.first)); + + merged.insert(merged.end(), Q.first, Q.last); + merged.insert(merged.end(), R.first, R.last); + sort(merged.begin(), merged.end(), sortfn); + + // Step 2 "set i = 1, k = 1, direction = 1, s1 = q1" + // we dont use i, instead, q is an iterator into Q. k would be an index + // into the merged sequence but we use "it" as an iterator for that + + // here we obtain references for the containers for later comparisons + const auto& Rcont = R.first->container.get(); + const auto& Qcont = Q.first->container.get(); + + // Set the intial direction + Coord dir = positive? 1 : -1; + + // roughly i = 1 (so q = Q.first) and s1 = q1 so S[0] = q; + auto q = Q.first; + S.push_back(*q++); + + // Roughly step 3 + while(q != Q.last) { + auto it = merged.begin(); + while(it != merged.end() && !(it->eq(*(Q.first))) ) { + if(it->isFrom(Rcont)) { + auto s = *it; + s.dir = dir; + S.push_back(s); + } + if(it->eq(*q)) { + S.push_back(*q); + if(it->isTurningPoint()) dir = -dir; + if(q != Q.first) it += dir; + } + else it += dir; + } + ++q; // "Set i = i + 1" + } + + // Step 4: + + // "Let starting edge r1 be in position si in sequence" + // whaaat? I guess this means the following: + S[0] = *R.first; + auto it = S.begin(); + + // "Set j = 1, next = 2, direction = 1, seq1 = si" + // we dont use j, seq is expanded dynamically. + dir = 1; auto next = std::next(R.first); + + // Step 5: + // "If all si edges have been allocated to seqj" should mean that + // we loop until seq has equal size with S + while(seq.size() < S.size()) { + ++it; if(it == S.end()) it = S.begin(); + + if(it->isFrom(Qcont)) { + seq.push_back(*it); // "If si is from Q, j = j + 1, seqj = si" + + // "If si is a turning point in Q, + // direction = - direction, next = next + direction" + if(it->isTurningPoint()) { dir = -dir; next += dir; } + } + + if(it->eq(*next) && dir == next->dir) { // "If si = direction.rnext" + // "j = j + 1, seqj = si, next = next + direction" + seq.push_back(*it); next += dir; + } + } + + return seq; + }; + + EdgeGroup R{ Bref.begin(), Bref.begin() }, Q{ Aref.begin(), Aref.end() }; + auto it = Bref.begin(); + bool orientation = true; + EdgeRefList seqlist; + seqlist.reserve(3*(Aref.size() + Bref.size())); + + while(it != Bref.end()) // This is step 3 and step 4 in one loop + if(it->isTurningPoint()) { + R = {R.last, it++}; + auto seq = mink(Q, R, orientation); + + // TODO step 6 (should be 5 shouldn't it?): linking edges from A + // I don't get this step + + seqlist.insert(seqlist.end(), seq.begin(), seq.end()); + orientation = !orientation; + } else ++it; + + if(seqlist.empty()) seqlist = mink(Q, {Bref.begin(), Bref.end()}, true); + + // ///////////////////////////////////////////////////////////////////////// + // Algorithm 2: breaking Minkowski sums into track line trips + // ///////////////////////////////////////////////////////////////////////// + + + // ///////////////////////////////////////////////////////////////////////// + // Algorithm 3: finding the boundary of the NFP from track line trips + // ///////////////////////////////////////////////////////////////////////// + + + + return Result(stationary, Vertex()); } // Specializable NFP implementation class. Specialize it if you have a faster // or better NFP implementation template struct NfpImpl { - RawShape operator()(const RawShape& sh, const RawShape& other) { + NfpResult operator()(const RawShape& sh, const RawShape& other) + { static_assert(nfptype == NfpLevel::CONVEX_ONLY, "Nfp::noFitPolygon() unimplemented!"); diff --git a/xs/src/libnest2d/libnest2d/libnest2d.hpp b/xs/src/libnest2d/libnest2d/libnest2d.hpp index 96316c344..37b5fea95 100644 --- a/xs/src/libnest2d/libnest2d/libnest2d.hpp +++ b/xs/src/libnest2d/libnest2d/libnest2d.hpp @@ -9,6 +9,7 @@ #include #include "geometry_traits.hpp" +#include "optimizer.hpp" namespace libnest2d { @@ -27,6 +28,7 @@ class _Item { using Coord = TCoord>; using Vertex = TPoint; using Box = _Box; + using sl = ShapeLike; // The original shape that gets encapsulated. RawShape sh_; @@ -56,6 +58,13 @@ class _Item { }; mutable Convexity convexity_ = Convexity::UNCHECKED; + mutable TVertexConstIterator rmt_; // rightmost top vertex + mutable TVertexConstIterator 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) {} + } bb_cache_; public: @@ -104,15 +113,15 @@ public: * @param il The initializer list of vertices. */ inline _Item(const std::initializer_list< Vertex >& il): - sh_(ShapeLike::create(il)) {} + sh_(sl::create(il)) {} inline _Item(const TContour& contour, const THolesContainer& holes = {}): - sh_(ShapeLike::create(contour, holes)) {} + sh_(sl::create(contour, holes)) {} inline _Item(TContour&& contour, THolesContainer&& holes): - sh_(ShapeLike::create(std::move(contour), + sh_(sl::create(std::move(contour), std::move(holes))) {} /** @@ -122,31 +131,31 @@ public: */ inline std::string toString() const { - return ShapeLike::toString(sh_); + return sl::toString(sh_); } /// Iterator tho the first contour vertex in the polygon. inline Iterator begin() const { - return ShapeLike::cbegin(sh_); + return sl::cbegin(sh_); } /// Alias to begin() inline Iterator cbegin() const { - return ShapeLike::cbegin(sh_); + return sl::cbegin(sh_); } /// Iterator to the last contour vertex. inline Iterator end() const { - return ShapeLike::cend(sh_); + return sl::cend(sh_); } /// Alias to end() inline Iterator cend() const { - return ShapeLike::cend(sh_); + return sl::cend(sh_); } /** @@ -161,7 +170,7 @@ public: */ inline Vertex vertex(unsigned long idx) const { - return ShapeLike::vertex(sh_, idx); + return sl::vertex(sh_, idx); } /** @@ -176,7 +185,7 @@ public: inline void setVertex(unsigned long idx, const Vertex& v ) { invalidateCache(); - ShapeLike::vertex(sh_, idx) = v; + sl::vertex(sh_, idx) = v; } /** @@ -191,7 +200,7 @@ public: double ret ; if(area_cache_valid_) ret = area_cache_; else { - ret = ShapeLike::area(offsettedShape()); + ret = sl::area(offsettedShape()); area_cache_ = ret; area_cache_valid_ = true; } @@ -203,7 +212,7 @@ public: switch(convexity_) { case Convexity::UNCHECKED: - ret = ShapeLike::isConvex(ShapeLike::getContour(transformedShape())); + ret = sl::isConvex(sl::getContour(transformedShape())); convexity_ = ret? Convexity::TRUE : Convexity::FALSE; break; case Convexity::TRUE: ret = true; break; @@ -213,7 +222,7 @@ public: return ret; } - inline bool isHoleConvex(unsigned holeidx) const { + inline bool isHoleConvex(unsigned /*holeidx*/) const { return false; } @@ -223,11 +232,11 @@ public: /// The number of the outer ring vertices. inline size_t vertexCount() const { - return ShapeLike::contourVertexCount(sh_); + return sl::contourVertexCount(sh_); } inline size_t holeCount() const { - return ShapeLike::holeCount(sh_); + return sl::holeCount(sh_); } /** @@ -235,36 +244,33 @@ public: * @param p * @return */ - inline bool isPointInside(const Vertex& p) + inline bool isPointInside(const Vertex& p) const { - return ShapeLike::isInside(p, sh_); + return sl::isInside(p, transformedShape()); } inline bool isInside(const _Item& sh) const { - return ShapeLike::isInside(transformedShape(), sh.transformedShape()); + return sl::isInside(transformedShape(), sh.transformedShape()); } - inline bool isInside(const _Box>& box); + inline bool isInside(const _Box>& box) const; inline void translate(const Vertex& d) BP2D_NOEXCEPT { - translation_ += d; has_translation_ = true; - tr_cache_valid_ = false; + translation(translation() + d); } inline void rotate(const Radians& rads) BP2D_NOEXCEPT { - rotation_ += rads; - has_rotation_ = true; - tr_cache_valid_ = false; + rotation(rotation() + rads); } inline void addOffset(Coord distance) BP2D_NOEXCEPT { offset_distance_ = distance; has_offset_ = true; - offset_cache_valid_ = false; + invalidateCache(); } inline void removeOffset() BP2D_NOEXCEPT { @@ -286,6 +292,8 @@ public: { if(rotation_ != rot) { rotation_ = rot; has_rotation_ = true; tr_cache_valid_ = false; + rmt_valid_ = false; lmb_valid_ = false; + bb_cache_.valid = false; } } @@ -293,6 +301,7 @@ public: { if(translation_ != tr) { translation_ = tr; has_translation_ = true; tr_cache_valid_ = false; + bb_cache_.valid = false; } } @@ -301,9 +310,10 @@ public: if(tr_cache_valid_) return tr_cache_; RawShape cpy = offsettedShape(); - if(has_rotation_) ShapeLike::rotate(cpy, rotation_); - if(has_translation_) ShapeLike::translate(cpy, translation_); + if(has_rotation_) sl::rotate(cpy, rotation_); + if(has_translation_) sl::translate(cpy, translation_); tr_cache_ = cpy; tr_cache_valid_ = true; + rmt_valid_ = false; lmb_valid_ = false; return tr_cache_; } @@ -321,23 +331,53 @@ public: inline void resetTransformation() BP2D_NOEXCEPT { has_translation_ = false; has_rotation_ = false; has_offset_ = false; + invalidateCache(); } inline Box boundingBox() const { - return ShapeLike::boundingBox(transformedShape()); + if(!bb_cache_.valid) { + bb_cache_.bb = sl::boundingBox(transformedShape()); + bb_cache_.tr = {0, 0}; + bb_cache_.valid = true; + } + + auto &bb = bb_cache_.bb; auto &tr = bb_cache_.tr; + return {bb.minCorner() + tr, bb.maxCorner() + tr}; + } + + inline Vertex referenceVertex() const { + return rightmostTopVertex(); + } + + inline Vertex rightmostTopVertex() const { + if(!rmt_valid_ || !tr_cache_valid_) { // find max x and max y vertex + auto& tsh = transformedShape(); + rmt_ = std::max_element(sl::cbegin(tsh), sl::cend(tsh), vsort); + rmt_valid_ = true; + } + return *rmt_; + } + + inline Vertex leftmostBottomVertex() const { + if(!lmb_valid_ || !tr_cache_valid_) { // find min x and min y vertex + auto& tsh = transformedShape(); + lmb_ = std::min_element(sl::cbegin(tsh), sl::cend(tsh), vsort); + lmb_valid_ = true; + } + return *lmb_; } //Static methods: inline static bool intersects(const _Item& sh1, const _Item& sh2) { - return ShapeLike::intersects(sh1.transformedShape(), + return sl::intersects(sh1.transformedShape(), sh2.transformedShape()); } inline static bool touches(const _Item& sh1, const _Item& sh2) { - return ShapeLike::touches(sh1.transformedShape(), + return sl::touches(sh1.transformedShape(), sh2.transformedShape()); } @@ -346,12 +386,11 @@ private: inline const RawShape& offsettedShape() const { if(has_offset_ ) { if(offset_cache_valid_) return offset_cache_; - else { - offset_cache_ = sh_; - ShapeLike::offset(offset_cache_, offset_distance_); - offset_cache_valid_ = true; - return offset_cache_; - } + + offset_cache_ = sh_; + sl::offset(offset_cache_, offset_distance_); + offset_cache_valid_ = true; + return offset_cache_; } return sh_; } @@ -359,10 +398,23 @@ private: inline void invalidateCache() const BP2D_NOEXCEPT { tr_cache_valid_ = false; + lmb_valid_ = false; rmt_valid_ = false; area_cache_valid_ = false; offset_cache_valid_ = false; + bb_cache_.valid = false; convexity_ = Convexity::UNCHECKED; } + + static inline bool vsort(const Vertex& v1, const Vertex& v2) + { + Coord &&x1 = getX(v1), &&x2 = getX(v2); + Coord &&y1 = getY(v1), &&y2 = getY(v2); + auto diff = y1 - y2; + if(std::abs(diff) <= std::numeric_limits::epsilon()) + return x1 < x2; + + return diff < 0; + } }; /** @@ -370,7 +422,6 @@ private: */ template class _Rectangle: public _Item { - RawShape sh_; using _Item::vertex; using TO = Orientation; public: @@ -415,7 +466,7 @@ public: }; template -inline bool _Item::isInside(const _Box>& box) { +inline bool _Item::isInside(const _Box>& box) const { _Rectangle rect(box.width(), box.height()); return _Item::isInside(rect); } @@ -874,9 +925,8 @@ private: Radians findBestRotation(Item& item) { opt::StopCriteria stopcr; - stopcr.stoplimit = 0.01; + stopcr.absolute_score_difference = 0.01; stopcr.max_iterations = 10000; - stopcr.type = opt::StopLimitType::RELATIVE; opt::TOptimizer solver(stopcr); auto orig_rot = item.rotation(); @@ -910,7 +960,6 @@ private: if(min_obj_distance_ > 0) std::for_each(from, to, [](Item& item) { item.removeOffset(); }); - } }; diff --git a/xs/src/libnest2d/libnest2d/metaloop.hpp b/xs/src/libnest2d/libnest2d/metaloop.hpp new file mode 100644 index 000000000..18755525c --- /dev/null +++ b/xs/src/libnest2d/libnest2d/metaloop.hpp @@ -0,0 +1,227 @@ +#ifndef METALOOP_HPP +#define METALOOP_HPP + +#include "common.hpp" +#include +#include + +namespace libnest2d { + +/* ************************************************************************** */ +/* C++14 std::index_sequence implementation: */ +/* ************************************************************************** */ + +/** + * \brief C++11 conformant implementation of the index_sequence type from C++14 + */ +template struct index_sequence { + using value_type = size_t; + BP2D_CONSTEXPR value_type size() const { return sizeof...(Ints); } +}; + +// A Help structure to generate the integer list +template struct genSeq; + +// Recursive template to generate the list +template struct genSeq { + // Type will contain a genSeq with Nseq appended by one element + using Type = typename genSeq< I - 1, I - 1, Nseq...>::Type; +}; + +// Terminating recursion +template struct genSeq<0, Nseq...> { + // If I is zero, Type will contain index_sequence with the fuly generated + // integer list. + using Type = index_sequence; +}; + +/// Helper alias to make an index sequence from 0 to N +template using make_index_sequence = typename genSeq::Type; + +/// Helper alias to make an index sequence for a parameter pack +template +using index_sequence_for = make_index_sequence; + + +/* ************************************************************************** */ + +namespace opt { + +using std::forward; +using std::tuple; +using std::get; +using std::tuple_element; + +/** + * @brief Helper class to be able to loop over a parameter pack's elements. + */ +class metaloop { + +// The implementation is based on partial struct template specializations. +// Basically we need a template type that is callable and takes an integer +// non-type template parameter which can be used to implement recursive calls. +// +// C++11 will not allow the usage of a plain template function that is why we +// use struct with overloaded call operator. At the same time C++11 prohibits +// partial template specialization with a non type parameter such as int. We +// need to wrap that in a type (see metaloop::Int). + +/* + * A helper alias to create integer values wrapped as a type. It is nessecary + * 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. + */ +template using Int = std::integral_constant; + +/* + * Helper class to implement in-place functors. + * + * We want to be able to use inline functors like a lambda to keep the code + * as clear as possible. + */ +template class MapFn { + Fn&& fn_; +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. + inline MapFn(Fn&& fn): fn_(forward(fn)) {} + + template void operator ()(T&& pack_element) { + // We provide the index as the first parameter and the pack (or tuple) + // element as the second parameter to the functor. + fn_(N, forward(pack_element)); + } +}; + +/* + * Implementation of the template loop trick. + * We create a mechanism for looping over a parameter pack in compile time. + * \tparam Idx is the loop index which will be decremented at each recursion. + * \tparam Args The parameter pack that will be processed. + * + */ +template +class _MetaLoop {}; + +// Implementation for the first element of Args... +template +class _MetaLoop, Args...> { +public: + + const static BP2D_CONSTEXPR int N = 0; + const static BP2D_CONSTEXPR int ARGNUM = sizeof...(Args)-1; + + template + void run( Tup&& valtup, Fn&& fn) { + MapFn {forward(fn)} (get(valtup)); + } +}; + +// Implementation for the N-th element of Args... +template +class _MetaLoop, Args...> { +public: + + const static BP2D_CONSTEXPR int ARGNUM = sizeof...(Args)-1; + + template + void run(Tup&& valtup, Fn&& fn) { + MapFn {forward(fn)} (std::get(valtup)); + + // Recursive call to process the next element of Args + _MetaLoop, Args...> ().run(forward(valtup), + forward(fn)); + } +}; + +/* + * Instantiation: We must instantiate the template with the last index because + * the generalized version calls the decremented instantiations recursively. + * Once the instantiation with the first index is called, the terminating + * 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. + */ +template +using MetaLoop = _MetaLoop, Args...>; + +public: + +/** + * \brief The final usable function template. + * + * This is similar to what varags was on C but in compile time C++11. + * You can call: + * apply(, ); + * For example: + * + * struct mapfunc { + * template void operator()(int N, T&& element) { + * std::cout << "The value of the parameter "<< N <<": " + * << element << std::endl; + * } + * }; + * + * apply(mapfunc(), 'a', 10, 151.545); + * + * C++14: + * apply([](int N, auto&& element){ + * std::cout << "The value of the parameter "<< N <<": " + * << element << std::endl; + * }, 'a', 10, 151.545); + * + * This yields the output: + * The value of the parameter 0: a + * The value of the parameter 1: 10 + * The value of the parameter 2: 151.545 + * + * As an addition, the function can be called with a tuple as the second + * parameter holding the arguments instead of a parameter pack. + * + */ +template +inline static void apply(Fn&& fn, Args&&...args) { + MetaLoop().run(tuple(forward(args)...), + forward(fn)); +} + +/// The version of apply with a tuple rvalue reference. +template +inline static void apply(Fn&& fn, tuple&& tup) { + MetaLoop().run(std::move(tup), forward(fn)); +} + +/// The version of apply with a tuple lvalue reference. +template +inline static void apply(Fn&& fn, tuple& tup) { + MetaLoop().run(tup, forward(fn)); +} + +/// The version of apply with a tuple const reference. +template +inline static void apply(Fn&& fn, const tuple& tup) { + MetaLoop().run(tup, forward(fn)); +} + +/** + * Call a function with its arguments encapsualted in a tuple. + */ +template +inline static auto +callFunWithTuple(Fn&& fn, Tup&& tup, index_sequence) -> + decltype(fn(std::get(tup)...)) +{ + return fn(std::get(tup)...); +} + +}; +} +} + +#endif // METALOOP_HPP diff --git a/xs/src/libnest2d/libnest2d/optimizer.hpp b/xs/src/libnest2d/libnest2d/optimizer.hpp index 52e67f7d5..c8ed2e378 100644 --- a/xs/src/libnest2d/libnest2d/optimizer.hpp +++ b/xs/src/libnest2d/libnest2d/optimizer.hpp @@ -10,8 +10,7 @@ namespace libnest2d { namespace opt { using std::forward; using std::tuple; -using std::get; -using std::tuple_element; +using std::make_tuple; /// A Type trait for upper and lower limit of a numeric type. template @@ -51,176 +50,7 @@ inline Bound bound(const T& min, const T& max) { return Bound(min, max); } template using Input = tuple; template -inline tuple initvals(Args...args) { return std::make_tuple(args...); } - -/** - * @brief Helper class to be able to loop over a parameter pack's elements. - */ -class metaloop { -// The implementation is based on partial struct template specializations. -// Basically we need a template type that is callable and takes an integer -// non-type template parameter which can be used to implement recursive calls. -// -// C++11 will not allow the usage of a plain template function that is why we -// use struct with overloaded call operator. At the same time C++11 prohibits -// partial template specialization with a non type parameter such as int. We -// need to wrap that in a type (see metaloop::Int). - -/* - * A helper alias to create integer values wrapped as a type. It is nessecary - * 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. - */ -template using Int = std::integral_constant; - -/* - * Helper class to implement in-place functors. - * - * We want to be able to use inline functors like a lambda to keep the code - * as clear as possible. - */ -template class MapFn { - Fn&& fn_; -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. - inline MapFn(Fn&& fn): fn_(forward(fn)) {} - - template void operator ()(T&& pack_element) { - // We provide the index as the first parameter and the pack (or tuple) - // element as the second parameter to the functor. - fn_(N, forward(pack_element)); - } -}; - -/* - * Implementation of the template loop trick. - * We create a mechanism for looping over a parameter pack in compile time. - * \tparam Idx is the loop index which will be decremented at each recursion. - * \tparam Args The parameter pack that will be processed. - * - */ -template -class _MetaLoop {}; - -// Implementation for the first element of Args... -template -class _MetaLoop, Args...> { -public: - - const static BP2D_CONSTEXPR int N = 0; - const static BP2D_CONSTEXPR int ARGNUM = sizeof...(Args)-1; - - template - void run( Tup&& valtup, Fn&& fn) { - MapFn {forward(fn)} (get(valtup)); - } -}; - -// Implementation for the N-th element of Args... -template -class _MetaLoop, Args...> { -public: - - const static BP2D_CONSTEXPR int ARGNUM = sizeof...(Args)-1; - - template - void run(Tup&& valtup, Fn&& fn) { - MapFn {forward(fn)} (std::get(valtup)); - - // Recursive call to process the next element of Args - _MetaLoop, Args...> ().run(forward(valtup), - forward(fn)); - } -}; - -/* - * Instantiation: We must instantiate the template with the last index because - * the generalized version calls the decremented instantiations recursively. - * Once the instantiation with the first index is called, the terminating - * 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. - */ -template -using MetaLoop = _MetaLoop, Args...>; - -public: - -/** - * \brief The final usable function template. - * - * This is similar to what varags was on C but in compile time C++11. - * You can call: - * apply(, ); - * For example: - * - * struct mapfunc { - * template void operator()(int N, T&& element) { - * std::cout << "The value of the parameter "<< N <<": " - * << element << std::endl; - * } - * }; - * - * apply(mapfunc(), 'a', 10, 151.545); - * - * C++14: - * apply([](int N, auto&& element){ - * std::cout << "The value of the parameter "<< N <<": " - * << element << std::endl; - * }, 'a', 10, 151.545); - * - * This yields the output: - * The value of the parameter 0: a - * The value of the parameter 1: 10 - * The value of the parameter 2: 151.545 - * - * As an addition, the function can be called with a tuple as the second - * parameter holding the arguments instead of a parameter pack. - * - */ -template -inline static void apply(Fn&& fn, Args&&...args) { - MetaLoop().run(tuple(forward(args)...), - forward(fn)); -} - -/// The version of apply with a tuple rvalue reference. -template -inline static void apply(Fn&& fn, tuple&& tup) { - MetaLoop().run(std::move(tup), forward(fn)); -} - -/// The version of apply with a tuple lvalue reference. -template -inline static void apply(Fn&& fn, tuple& tup) { - MetaLoop().run(tup, forward(fn)); -} - -/// The version of apply with a tuple const reference. -template -inline static void apply(Fn&& fn, const tuple& tup) { - MetaLoop().run(tup, forward(fn)); -} - -/** - * Call a function with its arguments encapsualted in a tuple. - */ -template -inline static auto -callFunWithTuple(Fn&& fn, Tup&& tup, index_sequence) -> - decltype(fn(std::get(tup)...)) -{ - return fn(std::get(tup)...); -} - -}; +inline tuple initvals(Args...args) { return make_tuple(args...); } /** * @brief Specific optimization methods for which a default optimizer @@ -257,29 +87,20 @@ enum ResultCodes { template struct Result { ResultCodes resultcode; - std::tuple optimum; + tuple optimum; double score; }; -/** - * @brief The stop limit can be specified as the absolute error or as the - * relative error, just like in nlopt. - */ -enum class StopLimitType { - ABSOLUTE, - RELATIVE -}; - /** * @brief A type for specifying the stop criteria. */ struct StopCriteria { - /// Relative or absolute termination error - StopLimitType type = StopLimitType::RELATIVE; + /// If the absolute value difference between two scores. + double absolute_score_difference = std::nan(""); - /// The error value that is interpredted depending on the type property. - double stoplimit = 0.0001; + /// If the relative value difference between two scores. + double relative_score_difference = std::nan(""); unsigned max_iterations = 0; }; @@ -310,11 +131,11 @@ public: * \return Returns a Result structure. * An example call would be: * auto result = opt.optimize_min( - * [](std::tuple x) // object function + * [](tuple x) // object function * { * return std::pow(std::get<0>(x), 2); * }, - * std::make_tuple(-0.5), // initial value + * make_tuple(-0.5), // initial value * {-1.0, 1.0} // search space bounds * ); */ @@ -390,10 +211,14 @@ public: static_assert(always_false::value, "Optimizer unimplemented!"); } + DummyOptimizer(const StopCriteria&) { + static_assert(always_false::value, "Optimizer unimplemented!"); + } + template - Result optimize(Func&& func, - std::tuple initvals, - Bound... args) + Result optimize(Func&& /*func*/, + tuple /*initvals*/, + Bound... /*args*/) { return Result(); } diff --git a/xs/src/libnest2d/libnest2d/optimizers/nlopt_boilerplate.hpp b/xs/src/libnest2d/libnest2d/optimizers/nlopt_boilerplate.hpp index 798bf9622..737ca6e3c 100644 --- a/xs/src/libnest2d/libnest2d/optimizers/nlopt_boilerplate.hpp +++ b/xs/src/libnest2d/libnest2d/optimizers/nlopt_boilerplate.hpp @@ -1,15 +1,25 @@ #ifndef NLOPT_BOILERPLATE_HPP #define NLOPT_BOILERPLATE_HPP +#ifdef _MSC_VER +#pragma warning(push) +#pragma warning(disable: 4244) +#pragma warning(disable: 4267) +#endif #include +#ifdef _MSC_VER +#pragma warning(pop) +#endif + #include #include +#include "libnest2d/metaloop.hpp" #include namespace libnest2d { namespace opt { -nlopt::algorithm method2nloptAlg(Method m) { +inline nlopt::algorithm method2nloptAlg(Method m) { switch(m) { case Method::L_SIMPLEX: return nlopt::LN_NELDERMEAD; @@ -87,7 +97,7 @@ protected: template static double optfunc(const std::vector& params, - std::vector& grad, + std::vector& /*grad*/, void *data) { auto fnptr = static_cast*>(data); @@ -132,12 +142,10 @@ protected: default: ; } - switch(this->stopcr_.type) { - case StopLimitType::ABSOLUTE: - opt_.set_ftol_abs(stopcr_.stoplimit); break; - case StopLimitType::RELATIVE: - opt_.set_ftol_rel(stopcr_.stoplimit); break; - } + auto abs_diff = stopcr_.absolute_score_difference; + auto rel_diff = stopcr_.relative_score_difference; + if(!std::isnan(abs_diff)) opt_.set_ftol_abs(abs_diff); + if(!std::isnan(rel_diff)) opt_.set_ftol_rel(rel_diff); if(this->stopcr_.max_iterations > 0) opt_.set_maxeval(this->stopcr_.max_iterations ); diff --git a/xs/src/libnest2d/libnest2d/placers/nfpplacer.hpp b/xs/src/libnest2d/libnest2d/placers/nfpplacer.hpp index 89848eb53..9e8b3be91 100644 --- a/xs/src/libnest2d/libnest2d/placers/nfpplacer.hpp +++ b/xs/src/libnest2d/libnest2d/placers/nfpplacer.hpp @@ -6,6 +6,10 @@ #endif #include "placer_boilerplate.hpp" #include "../geometry_traits_nfp.hpp" +#include "libnest2d/optimizer.hpp" +#include + +#include "tools/svgtools.hpp" namespace libnest2d { namespace strategies { @@ -20,15 +24,62 @@ struct NfpPConfig { TOP_RIGHT, }; - /// Which angles to try out for better results + /// Which angles to try out for better results. std::vector rotations; - /// Where to align the resulting packed pile + /// Where to align the resulting packed pile. Alignment alignment; + /// Where to start putting objects in the bin. Alignment starting_point; - std::function&, double, double, double)> + /** + * @brief A function object representing the fitting function in the + * placement optimization process. (Optional) + * + * This is the most versatile tool to configure the placer. The fitting + * function is evaluated many times when a new item is being placed into the + * bin. The output should be a rated score of the new item's position. + * + * This is not a mandatory option as there is a default fitting function + * 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 including the current candidate. You can calculate a bounding + * box or convex hull on this pile of polygons. + * + * \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 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. + * + * \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 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. + * + */ + std::function&, const _Item&, + double, double, double)> object_function; /** @@ -38,11 +89,30 @@ struct NfpPConfig { */ float accuracy = 1.0; + /** + * @brief If you want to see items inside other item's holes, you have to + * turn this switch on. + * + * This will only work if a suitable nfp implementation is provided. + * The library has no such implementation right now. + */ + bool explore_holes = false; + NfpPConfig(): rotations({0.0, Pi/2.0, Pi, 3*Pi/2}), alignment(Alignment::CENTER), starting_point(Alignment::CENTER) {} }; -// A class for getting a point on the circumference of the polygon (in log time) +/** + * A class for getting a point on the circumference of the polygon (in log time) + * + * This is a transformation of the provided polygon to be able to pinpoint + * locations on the circumference. The optimizer will pass a floating point + * value e.g. within <0,1> and we have to transform this value quickly into a + * coordinate on the circumference. By definition 0 should yield the first + * vertex and 1.0 would be the last (which should coincide with first). + * + * We also have to make this work for the holes of the captured polygon. + */ template class EdgeCache { using Vertex = TPoint; using Coord = TCoord; @@ -176,24 +246,64 @@ public: return holes_[hidx].full_distance; } + /// Get the normalized distance values for each vertex inline const std::vector& corners() const BP2D_NOEXCEPT { fetchCorners(); return contour_.corners; } + /// corners for a specific hole 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(); } + /// The number of holes in the abstracted polygon + inline size_t holeCount() const BP2D_NOEXCEPT { return holes_.size(); } }; template struct Lvl { static const NfpLevel value = lvl; }; +template +inline void correctNfpPosition(Nfp::NfpResult& nfp, + const _Item& stationary, + const _Item& orbiter) +{ + // The provided nfp is somewhere in the dark. We need to get it + // to the right position around the stationary shape. + // This is done by choosing the leftmost lowest vertex of the + // orbiting polygon to be touched with the rightmost upper + // vertex of the stationary polygon. In this configuration, the + // reference vertex of the orbiting polygon (which can be dragged around + // the nfp) will be its rightmost upper vertex that coincides with the + // rightmost upper vertex of the nfp. No proof provided other than Jonas + // Lindmark's reasoning about the reference vertex of nfp in his thesis + // ("No fit polygon problem" - section 2.1.9) + + auto touch_sh = stationary.rightmostTopVertex(); + auto touch_other = orbiter.leftmostBottomVertex(); + 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); +} + +template +inline void correctNfpPosition(Nfp::NfpResult& nfp, + const RawShape& stationary, + const _Item& orbiter) +{ + 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); +} + template Nfp::Shapes nfp( const Container& polygons, const _Item& trsh, @@ -203,18 +313,35 @@ Nfp::Shapes nfp( const Container& polygons, Nfp::Shapes nfps; + //int pi = 0; for(Item& sh : polygons) { - auto subnfp = Nfp::noFitPolygon( - sh.transformedShape(), trsh.transformedShape()); + auto subnfp_r = Nfp::noFitPolygon( + sh.transformedShape(), trsh.transformedShape()); #ifndef NDEBUG auto vv = ShapeLike::isValid(sh.transformedShape()); assert(vv.first); - auto vnfp = ShapeLike::isValid(subnfp); + auto vnfp = ShapeLike::isValid(subnfp_r.first); assert(vnfp.first); #endif - nfps = Nfp::merge(nfps, subnfp); + 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; @@ -227,42 +354,65 @@ Nfp::Shapes nfp( const Container& polygons, { using Item = _Item; - Nfp::Shapes nfps, stationary; + Nfp::Shapes nfps; + + auto& orb = trsh.transformedShape(); + bool orbconvex = trsh.isContourConvex(); for(Item& sh : polygons) { - stationary = Nfp::merge(stationary, sh.transformedShape()); - } + Nfp::NfpResult subnfp; + auto& stat = sh.transformedShape(); - std::cout << "pile size: " << stationary.size() << std::endl; - for(RawShape& sh : stationary) { + if(sh.isContourConvex() && orbconvex) + subnfp = Nfp::noFitPolygon(stat, orb); + else if(orbconvex) + subnfp = Nfp::noFitPolygon(stat, orb); + else + subnfp = Nfp::noFitPolygon(stat, orb); - RawShape subnfp; -// if(sh.isContourConvex() && trsh.isContourConvex()) { -// subnfp = Nfp::noFitPolygon( -// sh.transformedShape(), trsh.transformedShape()); -// } else { - subnfp = Nfp::noFitPolygon( sh/*.transformedShape()*/, - trsh.transformedShape()); -// } + correctNfpPosition(subnfp, sh, trsh); -// #ifndef NDEBUG -// auto vv = ShapeLike::isValid(sh.transformedShape()); -// assert(vv.first); - -// auto vnfp = ShapeLike::isValid(subnfp); -// assert(vnfp.first); -// #endif - -// auto vnfp = ShapeLike::isValid(subnfp); -// if(!vnfp.first) { -// std::cout << vnfp.second << std::endl; -// std::cout << ShapeLike::toString(subnfp) << std::endl; -// } - - nfps = Nfp::merge(nfps, subnfp); + 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 @@ -290,6 +440,14 @@ public: norm_(std::sqrt(ShapeLike::area(bin))), penality_(1e6*norm_) {} + _NofitPolyPlacer(const _NofitPolyPlacer&) = default; + _NofitPolyPlacer& operator=(const _NofitPolyPlacer&) = default; + +#ifndef BP2D_COMPILER_MSVC12 // MSVC2013 does not support default move ctors + _NofitPolyPlacer(_NofitPolyPlacer&&) BP2D_NOEXCEPT = default; + _NofitPolyPlacer& operator=(_NofitPolyPlacer&&) BP2D_NOEXCEPT = default; +#endif + bool static inline wouldFit(const RawShape& chull, const RawShape& bin) { auto bbch = ShapeLike::boundingBox(chull); auto bbin = ShapeLike::boundingBox(bin); @@ -363,7 +521,7 @@ public: auto getNfpPoint = [&ecache](const Optimum& opt) { return opt.hidx < 0? ecache[opt.nfpidx].coords(opt.relpos) : - ecache[opt.nfpidx].coords(opt.nfpidx, opt.relpos); + ecache[opt.nfpidx].coords(opt.hidx, opt.relpos); }; Nfp::Shapes pile; @@ -378,8 +536,9 @@ public: // customizable by the library client auto _objfunc = config_.object_function? config_.object_function : - [this](const Nfp::Shapes& pile, double occupied_area, - double /*norm*/, double penality) + [this](const Nfp::Shapes& pile, Item, + double occupied_area, double /*norm*/, + double penality) { auto ch = ShapeLike::convexHull(pile); @@ -410,7 +569,7 @@ public: double occupied_area = pile_area + item.area(); - double score = _objfunc(pile, occupied_area, + double score = _objfunc(pile, item, occupied_area, norm_, penality_); pile.pop_back(); @@ -420,8 +579,8 @@ public: opt::StopCriteria stopcr; stopcr.max_iterations = 1000; - stopcr.stoplimit = 0.001; - stopcr.type = opt::StopLimitType::RELATIVE; + stopcr.absolute_score_difference = 1e-20*norm_; +// stopcr.relative_score_difference = 1e-20; opt::TOptimizer solver(stopcr); Optimum optimum(0, 0); @@ -458,6 +617,14 @@ public: } 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) { @@ -490,6 +657,13 @@ public: } 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/libnest2d/libnest2d/selections/djd_heuristic.hpp b/xs/src/libnest2d/libnest2d/selections/djd_heuristic.hpp index 1d233cf35..17ac1167d 100644 --- a/xs/src/libnest2d/libnest2d/selections/djd_heuristic.hpp +++ b/xs/src/libnest2d/libnest2d/selections/djd_heuristic.hpp @@ -256,14 +256,14 @@ public: if(not_packed.size() < 2) return false; // No group of two items - else { - double largest_area = not_packed.front().get().area(); - auto itmp = not_packed.begin(); itmp++; - double second_largest = itmp->get().area(); - if( free_area - second_largest - largest_area > waste) - return false; // If even the largest two items do not fill - // the bin to the desired waste than we can end here. - } + + double largest_area = not_packed.front().get().area(); + auto itmp = not_packed.begin(); itmp++; + double second_largest = itmp->get().area(); + if( free_area - second_largest - largest_area > waste) + return false; // If even the largest two items do not fill + // the bin to the desired waste than we can end here. + bool ret = false; auto it = not_packed.begin(); @@ -481,7 +481,7 @@ public: { std::array packed = {false}; - for(auto id : idx) packed[id] = + for(auto id : idx) packed.at(id) = placer.pack(candidates[id]); bool check = @@ -537,8 +537,7 @@ public: while (it != store_.end()) { Placer p(bin); if(!p.pack(*it)) { - auto itmp = it++; - store_.erase(itmp); + it = store_.erase(it); } else it++; } } @@ -605,8 +604,7 @@ public: if(placer.pack(*it)) { filled_area += it->get().area(); free_area = bin_area - filled_area; - auto itmp = it++; - not_packed.erase(itmp); + it = not_packed.erase(it); makeProgress(placer, idx, 1); } else it++; } diff --git a/xs/src/libnest2d/libnest2d/selections/firstfit.hpp b/xs/src/libnest2d/libnest2d/selections/firstfit.hpp index 5185014a8..f34961f80 100644 --- a/xs/src/libnest2d/libnest2d/selections/firstfit.hpp +++ b/xs/src/libnest2d/libnest2d/selections/firstfit.hpp @@ -52,7 +52,7 @@ public: auto total = last-first; auto makeProgress = [this, &total](Placer& placer, size_t idx) { packed_bins_[idx] = placer.getItems(); - this->progress_(--total); + this->progress_(static_cast(--total)); }; // Safety test: try to pack each item into an empty bin. If it fails diff --git a/xs/src/libnest2d/tests/test.cpp b/xs/src/libnest2d/tests/test.cpp index b37274f84..39315ff1a 100644 --- a/xs/src/libnest2d/tests/test.cpp +++ b/xs/src/libnest2d/tests/test.cpp @@ -682,7 +682,9 @@ void testNfp(const std::vector& testdata) { auto&& nfp = Nfp::noFitPolygon(stationary.rawShape(), orbiter.transformedShape()); - auto v = ShapeLike::isValid(nfp); + strategies::correctNfpPosition(nfp, stationary, orbiter); + + auto v = ShapeLike::isValid(nfp.first); if(!v.first) { std::cout << v.second << std::endl; @@ -690,7 +692,7 @@ void testNfp(const std::vector& testdata) { ASSERT_TRUE(v.first); - Item infp(nfp); + Item infp(nfp.first); int i = 0; auto rorbiter = orbiter.transformedShape(); @@ -742,6 +744,15 @@ TEST(GeometryAlgorithms, nfpConvexConvex) { // testNfp(nfp_concave_testdata); //} +TEST(GeometryAlgorithms, nfpConcaveConcave) { + using namespace libnest2d; + +// Rectangle r1(10, 10); +// Rectangle r2(20, 20); +// auto result = Nfp::nfpSimpleSimple(r1.transformedShape(), +// r2.transformedShape()); +} + TEST(GeometryAlgorithms, pointOnPolygonContour) { using namespace libnest2d; diff --git a/xs/src/libnest2d/tools/libnfpglue.cpp b/xs/src/libnest2d/tools/libnfpglue.cpp index 4cbdb5442..18656fd40 100644 --- a/xs/src/libnest2d/tools/libnfpglue.cpp +++ b/xs/src/libnest2d/tools/libnfpglue.cpp @@ -49,18 +49,18 @@ libnfporb::point_t scale(const libnfporb::point_t& p, long double factor) { long double px = p.x_.val(); long double py = p.y_.val(); #endif - return libnfporb::point_t(px*factor, py*factor); + return {px*factor, py*factor}; } } -PolygonImpl _nfp(const PolygonImpl &sh, const PolygonImpl &cother) +NfpR _nfp(const PolygonImpl &sh, const PolygonImpl &cother) { using Vertex = PointImpl; - PolygonImpl ret; + NfpR ret; -// try { + try { libnfporb::polygon_t pstat, porb; boost::geometry::convert(sh, pstat); @@ -85,7 +85,7 @@ PolygonImpl _nfp(const PolygonImpl &sh, const PolygonImpl &cother) // this can throw auto nfp = libnfporb::generateNFP(pstat, porb, true); - auto &ct = ShapeLike::getContour(ret); + auto &ct = ShapeLike::getContour(ret.first); ct.reserve(nfp.front().size()+1); for(auto v : nfp.front()) { v = scale(v, refactor); @@ -94,10 +94,10 @@ PolygonImpl _nfp(const PolygonImpl &sh, const PolygonImpl &cother) ct.push_back(ct.front()); std::reverse(ct.begin(), ct.end()); - auto &rholes = ShapeLike::holes(ret); + auto &rholes = ShapeLike::holes(ret.first); for(size_t hidx = 1; hidx < nfp.size(); ++hidx) { if(nfp[hidx].size() >= 3) { - rholes.push_back({}); + rholes.emplace_back(); auto& h = rholes.back(); h.reserve(nfp[hidx].size()+1); @@ -110,73 +110,48 @@ PolygonImpl _nfp(const PolygonImpl &sh, const PolygonImpl &cother) } } - auto& cmp = vsort; - std::sort(pstat.outer().begin(), pstat.outer().end(), cmp); - std::sort(porb.outer().begin(), porb.outer().end(), cmp); + ret.second = Nfp::referenceVertex(ret.first); - // leftmost lower vertex of the stationary polygon - auto& touch_sh = scale(pstat.outer().back(), refactor); - // rightmost upper vertex of the orbiting polygon - auto& touch_other = scale(porb.outer().front(), refactor); - - // Calculate the difference and move the orbiter to the touch position. - auto dtouch = touch_sh - touch_other; - auto _top_other = scale(porb.outer().back(), refactor) + dtouch; - - Vertex top_other(getX(_top_other), getY(_top_other)); - - // Get the righmost upper vertex of the nfp and move it to the RMU of - // the orbiter because they should coincide. - auto&& top_nfp = Nfp::rightmostUpVertex(ret); - auto dnfp = top_other - top_nfp; - - std::for_each(ShapeLike::begin(ret), ShapeLike::end(ret), - [&dnfp](Vertex& v) { v+= dnfp; } ); - - for(auto& h : ShapeLike::holes(ret)) - std::for_each( h.begin(), h.end(), - [&dnfp](Vertex& v) { v += dnfp; } ); - -// } catch(std::exception& e) { -// std::cout << "Error: " << e.what() << "\nTrying with convex hull..." << std::endl; + } 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(ch_stat, ch_orb); -// } + ret = Nfp::nfpConvexOnly(sh, cother); + } return ret; } -PolygonImpl Nfp::NfpImpl::operator()( +NfpR Nfp::NfpImpl::operator()( const PolygonImpl &sh, const ClipperLib::PolygonImpl &cother) { return _nfp(sh, cother);//nfpConvexOnly(sh, cother); } -PolygonImpl Nfp::NfpImpl::operator()( +NfpR Nfp::NfpImpl::operator()( const PolygonImpl &sh, const ClipperLib::PolygonImpl &cother) { return _nfp(sh, cother); } -PolygonImpl Nfp::NfpImpl::operator()( +NfpR Nfp::NfpImpl::operator()( const PolygonImpl &sh, const ClipperLib::PolygonImpl &cother) { return _nfp(sh, cother); } -PolygonImpl -Nfp::NfpImpl::operator()( - const PolygonImpl &sh, const ClipperLib::PolygonImpl &cother) -{ - return _nfp(sh, cother); -} +//PolygonImpl +//Nfp::NfpImpl::operator()( +// const PolygonImpl &sh, const ClipperLib::PolygonImpl &cother) +//{ +// return _nfp(sh, cother); +//} -PolygonImpl -Nfp::NfpImpl::operator()( - const PolygonImpl &sh, const ClipperLib::PolygonImpl &cother) -{ - return _nfp(sh, cother); -} +//PolygonImpl +//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 87b0e0833..75f639445 100644 --- a/xs/src/libnest2d/tools/libnfpglue.hpp +++ b/xs/src/libnest2d/tools/libnfpglue.hpp @@ -5,37 +5,39 @@ namespace libnest2d { -PolygonImpl _nfp(const PolygonImpl& sh, const PolygonImpl& cother); +using NfpR = Nfp::NfpResult; + +NfpR _nfp(const PolygonImpl& sh, const PolygonImpl& cother); template<> struct Nfp::NfpImpl { - PolygonImpl operator()(const PolygonImpl& sh, const PolygonImpl& cother); + NfpR operator()(const PolygonImpl& sh, const PolygonImpl& cother); }; template<> struct Nfp::NfpImpl { - PolygonImpl operator()(const PolygonImpl& sh, const PolygonImpl& cother); + NfpR operator()(const PolygonImpl& sh, const PolygonImpl& cother); }; template<> struct Nfp::NfpImpl { - PolygonImpl operator()(const PolygonImpl& sh, const PolygonImpl& cother); + NfpR operator()(const PolygonImpl& sh, const PolygonImpl& cother); }; -template<> -struct Nfp::NfpImpl { - PolygonImpl operator()(const PolygonImpl& sh, const PolygonImpl& cother); -}; +//template<> +//struct Nfp::NfpImpl { +// NfpResult operator()(const PolygonImpl& sh, const PolygonImpl& cother); +//}; -template<> -struct Nfp::NfpImpl { - PolygonImpl operator()(const PolygonImpl& sh, const PolygonImpl& cother); -}; +//template<> +//struct Nfp::NfpImpl { +// NfpResult operator()(const PolygonImpl& sh, const PolygonImpl& cother); +//}; template<> struct Nfp::MaxNfpLevel { static const BP2D_CONSTEXPR NfpLevel value = // NfpLevel::CONVEX_ONLY; - NfpLevel::BOTH_CONCAVE_WITH_HOLES; + NfpLevel::BOTH_CONCAVE; }; } diff --git a/xs/src/libnest2d/tools/svgtools.hpp b/xs/src/libnest2d/tools/svgtools.hpp index 273ecabac..3a83caa70 100644 --- a/xs/src/libnest2d/tools/svgtools.hpp +++ b/xs/src/libnest2d/tools/svgtools.hpp @@ -5,11 +5,17 @@ #include #include -#include +#include namespace libnest2d { namespace svg { +template class SVGWriter { + using Item = _Item; + using Coord = TCoord>; + using Box = _Box>; + using PackGroup = _PackGroup; + public: enum OrigoLocation { diff --git a/xs/src/libslic3r/Model.cpp b/xs/src/libslic3r/Model.cpp index be0765ea7..4b61e1c9a 100644 --- a/xs/src/libslic3r/Model.cpp +++ b/xs/src/libslic3r/Model.cpp @@ -529,6 +529,7 @@ bool arrange(Model &model, coordf_t dist, const Slic3r::BoundingBoxf* bb, // handle different rotations // arranger.useMinimumBoundigBoxRotation(); pcfg.rotations = { 0.0 }; + double norm_2 = std::nan(""); // 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 @@ -537,33 +538,46 @@ bool arrange(Model &model, coordf_t dist, const Slic3r::BoundingBoxf* bb, // 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]( + pcfg.object_function = [bin, hasbin, &norm_2]( NfpPlacer::Pile pile, // The currently arranged pile + 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; + auto bb = ShapeLike::boundingBox(pile); + auto ibb = item.boundingBox(); + auto minc = ibb.minCorner(); + auto maxc = ibb.maxCorner(); - // We get the current item that's being evaluated. - auto& sh = pile.back(); - - // We retrieve the reference point of this item - auto rv = ShapeLike::boundingBox(sh).center(); + if(std::isnan(norm_2)) norm_2 = pow(norm, 2); // We get the distance of the reference point from the center of the // heat bed - auto c = bin.center(); - auto d = PointLike::distance(rv, c); + auto cc = bb.center(); + auto top_left = PointImpl{getX(minc), getY(maxc)}; + auto bottom_right = PointImpl{getX(maxc), getY(minc)}; + + 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); + + auto area = bb.width() * bb.height() / norm_2; + + auto min_dist = std::min({a, b, c, d, e}) / norm; // The score will be the normalized distance which will be minimized, // effectively creating a circle shaped pile of items - double score = d/norm; + double score = 0.8*min_dist + 0.2*area; // 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(hasbin && !NfpPlacer::wouldFit(bb, bin)) score = 2*penality - score; + if(!NfpPlacer::wouldFit(bb, bin)) score = 2*penality - score; return score; }; From f364bd1884c318c68be9d0ee4529848919ced917 Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Fri, 27 Jul 2018 17:31:30 +0200 Subject: [PATCH 04/29] New object function considering item size categories (big and small) --- .../libnest2d/libnest2d/placers/nfpplacer.hpp | 26 ++--- xs/src/libslic3r/Model.cpp | 99 ++++++++++++++----- 2 files changed, 91 insertions(+), 34 deletions(-) diff --git a/xs/src/libnest2d/libnest2d/placers/nfpplacer.hpp b/xs/src/libnest2d/libnest2d/placers/nfpplacer.hpp index 9e8b3be91..61d923b87 100644 --- a/xs/src/libnest2d/libnest2d/placers/nfpplacer.hpp +++ b/xs/src/libnest2d/libnest2d/placers/nfpplacer.hpp @@ -78,7 +78,7 @@ struct NfpPConfig { * into the bin. * */ - std::function&, const _Item&, + std::function&, const _Item&, double, double, double)> object_function; @@ -163,18 +163,22 @@ template class EdgeCache { void fetchCorners() const { if(!contour_.corners.empty()) return; - // TODO Accuracy - contour_.corners = contour_.distances; - for(auto& d : contour_.corners) d /= contour_.full_distance; + contour_.corners.reserve(contour_.distances.size() / 3 + 1); + for(size_t i = 0; i < contour_.distances.size() - 1; i += 3) { + contour_.corners.emplace_back( + contour_.distances.at(i) / 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; + hc.corners.reserve(hc.distances.size() / 3 + 1); + for(size_t i = 0; i < hc.distances.size() - 1; i += 3) { + hc.corners.emplace_back( + hc.distances.at(i) / hc.full_distance); + } } inline Vertex coords(const ContourCache& cache, double distance) const { @@ -433,7 +437,7 @@ class _NofitPolyPlacer: public PlacerBoilerplate<_NofitPolyPlacer, public: - using Pile = const Nfp::Shapes&; + using Pile = Nfp::Shapes; inline explicit _NofitPolyPlacer(const BinType& bin): Base(bin), @@ -536,7 +540,7 @@ public: // customizable by the library client auto _objfunc = config_.object_function? config_.object_function : - [this](const Nfp::Shapes& pile, Item, + [this](Nfp::Shapes& pile, Item, double occupied_area, double /*norm*/, double penality) { @@ -565,14 +569,14 @@ public: d += startpos; item.translation(d); - pile.emplace_back(item.transformedShape()); +// pile.emplace_back(item.transformedShape()); double occupied_area = pile_area + item.area(); double score = _objfunc(pile, item, occupied_area, norm_, penality_); - pile.pop_back(); +// pile.pop_back(); return score; }; diff --git a/xs/src/libslic3r/Model.cpp b/xs/src/libslic3r/Model.cpp index 4b61e1c9a..90ebface2 100644 --- a/xs/src/libslic3r/Model.cpp +++ b/xs/src/libslic3r/Model.cpp @@ -529,7 +529,6 @@ bool arrange(Model &model, coordf_t dist, const Slic3r::BoundingBoxf* bb, // handle different rotations // arranger.useMinimumBoundigBoxRotation(); pcfg.rotations = { 0.0 }; - double norm_2 = std::nan(""); // 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 @@ -538,8 +537,8 @@ bool arrange(Model &model, coordf_t dist, const Slic3r::BoundingBoxf* bb, // 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, &norm_2]( - NfpPlacer::Pile pile, // The currently arranged pile + pcfg.object_function = [bin, hasbin]( + NfpPlacer::Pile& pile, // The currently arranged pile Item item, double /*area*/, // Sum area of items (not needed) double norm, // A norming factor for physical dimensions @@ -547,37 +546,91 @@ bool arrange(Model &model, coordf_t dist, const Slic3r::BoundingBoxf* bb, { 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; + std::cout << "big " << std::endl; + + } 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(bb, bin)) score = 2*penality - score; + if(!NfpPlacer::wouldFit(fullbb, bin)) score = 2*penality - score; return score; }; From db8ba5fb76c285bfde0e16620cf8d1b3cb0ca25b Mon Sep 17 00:00:00 2001 From: bubnikv Date: Fri, 27 Jul 2018 22:19:46 +0200 Subject: [PATCH 05/29] New parameter "single_extruder_multi_material_priming" to be able to suppress the MM priming towers. The PrusaResearch.ini was modified for the MMU2 printers to correctly prime the initial extruder when single_extruder_multi_material_priming is disabled. --- resources/profiles/PrusaResearch.ini | 8 ++-- xs/src/libslic3r/GCode.cpp | 61 ++++++++++++++++------------ xs/src/libslic3r/Print.cpp | 7 +--- xs/src/libslic3r/PrintConfig.cpp | 6 +++ xs/src/libslic3r/PrintConfig.hpp | 2 + xs/src/slic3r/GUI/Preset.cpp | 4 +- xs/src/slic3r/GUI/Tab.cpp | 1 + 7 files changed, 51 insertions(+), 38 deletions(-) diff --git a/resources/profiles/PrusaResearch.ini b/resources/profiles/PrusaResearch.ini index 6817c8712..8e0ab024b 100644 --- a/resources/profiles/PrusaResearch.ini +++ b/resources/profiles/PrusaResearch.ini @@ -5,7 +5,7 @@ name = Prusa Research # Configuration version of this file. Config file will only be installed, if the config_version differs. # This means, the server may force the Slic3r configuration to be downgraded. -config_version = 0.2.0-alpha3 +config_version = 0.2.0-alpha4 # Where to get the updates from? config_update_url = https://raw.githubusercontent.com/prusa3d/Slic3r-settings/master/live/PrusaResearch/ @@ -1069,7 +1069,7 @@ end_gcode = {if not has_wipe_tower}\n; Pull the filament into the cooling tubes. extruder_colour = #FFAA55;#5182DB;#4ECDD3;#FB7259 nozzle_diameter = 0.4,0.4,0.4,0.4 printer_notes = Don't remove the following keywords! These keywords are used in the "compatible printer" condition of the print and filament profiles to link the particular print and filament profiles to this printer profile.\nPRINTER_VENDOR_PRUSA3D\nPRINTER_MODEL_MK2\nPRINTER_HAS_BOWDEN -start_gcode = M115 U3.1.0 ; tell printer latest fw version\n; Start G-Code sequence START\nT[initial_tool]\nM104 S[first_layer_temperature] ; set extruder temp\nM140 S[first_layer_bed_temperature] ; set bed temp\nM190 S[first_layer_bed_temperature] ; wait for bed temp\nM109 S[first_layer_temperature] ; wait for extruder temp\nG21 ; set units to millimeters\nG90 ; use absolute coordinates\nM83 ; use relative distances for extrusion\nG28 W\nG80\nG92 E0.0\nM203 E100 ; set max feedrate\nM92 E140 ; E-steps per filament milimeter\n{if not has_wipe_tower}\nG1 Z0.250 F7200.000\nG1 X50.0 E80.0 F1000.0\nG1 X160.0 E20.0 F1000.0\nG1 Z0.200 F7200.000\nG1 X220.0 E13 F1000.0\nG1 X240.0 E0 F1000.0\nG1 E-4 F1000.0\n{endif}\nG92 E0.0 +start_gcode = M115 U3.1.0 ; tell printer latest fw version\n; Start G-Code sequence START\nT[initial_tool]\nM104 S[first_layer_temperature] ; set extruder temp\nM140 S[first_layer_bed_temperature] ; set bed temp\nM190 S[first_layer_bed_temperature] ; wait for bed temp\nM109 S[first_layer_temperature] ; wait for extruder temp\nG21 ; set units to millimeters\nG90 ; use absolute coordinates\nM83 ; use relative distances for extrusion\nG28 W\nG80\nG92 E0.0\nM203 E100 ; set max feedrate\nM92 E140 ; E-steps per filament milimeter\n{if not has_single_extruder_multi_material_priming}\nG1 Z0.250 F7200.000\nG1 X50.0 E80.0 F1000.0\nG1 X160.0 E20.0 F1000.0\nG1 Z0.200 F7200.000\nG1 X220.0 E13 F1000.0\nG1 X240.0 E0 F1000.0\nG1 E-4 F1000.0\n{endif}\nG92 E0.0 variable_layer_height = 0 default_print_profile = 0.15mm OPTIMAL @@ -1213,8 +1213,8 @@ inherits = *mm2* # to be defined explicitely. nozzle_diameter = 0.4,0.4,0.4,0.4,0.4 extruder_colour = #FF8000;#0080FF;#00FFFF;#FF4F4F;#9FFF9F -start_gcode = M107\n\nM83 ; extruder relative mode\nM104 S[first_layer_temperature] ; set extruder temp\nM140 S[first_layer_bed_temperature] ; set bed temp\nM190 S[first_layer_bed_temperature] ; wait for bed temp\nM109 S[first_layer_temperature] ; wait for extruder temp\nG28 W ; home all without mesh bed level\nG80 ; mesh bed leveling\n\nG21 ; set units to millimeters\nG90 ; use absolute coordinates\nM83 ; use relative distances for extrusion\nG92 E0.0\n -end_gcode = G1 E-15.0000 F3000\n\nM702 C\n\nG4 ; wait\nM104 S0 ; turn off temperature\nM140 S0 ; turn off heatbed\nM107 ; turn off fan\nG1 X0 Y200; home X axis\nM84 ; disable motors +start_gcode = M107\n\nM83 ; extruder relative mode\nM104 S[first_layer_temperature] ; set extruder temp\nM140 S[first_layer_bed_temperature] ; set bed temp\nM190 S[first_layer_bed_temperature] ; wait for bed temp\nM109 S[first_layer_temperature] ; wait for extruder temp\nG28 W ; home all without mesh bed level\nG80 ; mesh bed leveling\nG21 ; set units to millimeters\n\n{if not has_single_extruder_multi_material_priming}\n; go outside print area\nG1 Y-3.0 F1000.0 \nG1 Z0.4 F1000\n; select extruder\nT[initial_tool]\n; initial load\nG1 X50 E15 F1073\nG1 X100 E10 F2000\nG1 Z0.3 F1000\nG92 E0.0\nG1 X240.0 E15.0 F2400.0 \nG1 Y-2.0 F1000.0\nG1 X100.0 E10 F1400.0 \nG1 Z0.20 F1000\nG1 X0.0 E4 F1000.0\nG92 E0.0\n{endif}\n\n;M221 S{if layer_height<0.075}100{else}95{endif}\nG90 ; use absolute coordinates\nM83 ; use relative distances for extrusion\nG92 E0.0\n +end_gcode = {if has_wipe_tower}\nG1 E-15.0000 F3000\n{else}\nG1 X0 Y210 F7200\nG1 E2 F5000\nG1 E2 F5500\nG1 E2 F6000\nG1 E-15.0000 F5800\nG1 E-20.0000 F5500\nG1 E10.0000 F3000\nG1 E-10.0000 F3100\nG1 E10.0000 F3150\nG1 E-10.0000 F3250\nG1 E10.0000 F3300\n{endif}\n\n; Unload filament\nM702 C\n\nG4 ; wait\nM104 S0 ; turn off temperature\nM140 S0 ; turn off heatbed\nM107 ; turn off fan\n; Lift print head a bit\n{if layer_z < max_print_height}G1 Z{z_offset+min(layer_z+30, max_print_height)}{endif} ; Move print head up\nG1 X0 Y200; home X axis\nM84 ; disable motors\n # The obsolete presets will be removed when upgrading from the legacy configuration structure (up to Slic3r 1.39.2) to 1.40.0 and newer. [obsolete_presets] diff --git a/xs/src/libslic3r/GCode.cpp b/xs/src/libslic3r/GCode.cpp index 94634f4e4..308b1ea04 100644 --- a/xs/src/libslic3r/GCode.cpp +++ b/xs/src/libslic3r/GCode.cpp @@ -608,15 +608,18 @@ void GCode::_do_export(Print &print, FILE *file, GCodePreviewData *preview_data) if ((initial_extruder_id = tool_ordering.first_extruder()) != (unsigned int)-1) break; } - } - else { + } else { // Find tool ordering for all the objects at once, and the initial extruder ID. // If the tool ordering has been pre-calculated by Print class for wipe tower already, reuse it. tool_ordering = print.m_tool_ordering.empty() ? ToolOrdering(print, initial_extruder_id) : print.m_tool_ordering; - initial_extruder_id = tool_ordering.first_extruder(); has_wipe_tower = print.has_wipe_tower() && tool_ordering.has_wipe_tower(); + initial_extruder_id = (has_wipe_tower && ! print.config.single_extruder_multi_material_priming) ? + // The priming towers will be skipped. + tool_ordering.all_extruders().back() : + // Don't skip the priming towers. + tool_ordering.first_extruder(); } if (initial_extruder_id == (unsigned int)-1) { // Nothing to print! @@ -644,6 +647,7 @@ void GCode::_do_export(Print &print, FILE *file, GCodePreviewData *preview_data) m_placeholder_parser.set("current_object_idx", 0); // For the start / end G-code to do the priming and final filament pull in case there is no wipe tower provided. m_placeholder_parser.set("has_wipe_tower", has_wipe_tower); + m_placeholder_parser.set("has_single_extruder_multi_material_priming", has_wipe_tower && print.config.single_extruder_multi_material_priming); std::string start_gcode = this->placeholder_parser_process("start_gcode", print.config.start_gcode.value, initial_extruder_id); // Set bed temperature if the start G-code does not contain any bed temp control G-codes. @@ -724,8 +728,11 @@ void GCode::_do_export(Print &print, FILE *file, GCodePreviewData *preview_data) } } - // Set initial extruder only after custom start G-code. - _write(file, this->set_extruder(initial_extruder_id)); + if (! (has_wipe_tower && print.config.single_extruder_multi_material_priming)) { + // Set initial extruder only after custom start G-code. + // Ugly hack: Do not set the initial extruder if the extruder is primed using the MMU priming towers at the edge of the print bed. + _write(file, this->set_extruder(initial_extruder_id)); + } // Do all objects for each layer. if (print.config.complete_objects.value) { @@ -803,27 +810,29 @@ void GCode::_do_export(Print &print, FILE *file, GCodePreviewData *preview_data) if (has_wipe_tower && ! layers_to_print.empty()) { m_wipe_tower.reset(new WipeTowerIntegration(print.config, *print.m_wipe_tower_priming.get(), print.m_wipe_tower_tool_changes, *print.m_wipe_tower_final_purge.get())); _write(file, m_writer.travel_to_z(first_layer_height + m_config.z_offset.value, "Move to the first layer height")); - _write(file, m_wipe_tower->prime(*this)); - // Verify, whether the print overaps the priming extrusions. - BoundingBoxf bbox_print(get_print_extrusions_extents(print)); - coordf_t twolayers_printz = ((layers_to_print.size() == 1) ? layers_to_print.front() : layers_to_print[1]).first + EPSILON; - for (const PrintObject *print_object : printable_objects) - bbox_print.merge(get_print_object_extrusions_extents(*print_object, twolayers_printz)); - bbox_print.merge(get_wipe_tower_extrusions_extents(print, twolayers_printz)); - BoundingBoxf bbox_prime(get_wipe_tower_priming_extrusions_extents(print)); - bbox_prime.offset(0.5f); - // Beep for 500ms, tone 800Hz. Yet better, play some Morse. - _write(file, this->retract()); - _write(file, "M300 S800 P500\n"); - if (bbox_prime.overlap(bbox_print)) { - // Wait for the user to remove the priming extrusions, otherwise they would - // get covered by the print. - _write(file, "M1 Remove priming towers and click button.\n"); - } - else { - // Just wait for a bit to let the user check, that the priming succeeded. - //TODO Add a message explaining what the printer is waiting for. This needs a firmware fix. - _write(file, "M1 S10\n"); + if (print.config.single_extruder_multi_material_priming) { + _write(file, m_wipe_tower->prime(*this)); + // Verify, whether the print overaps the priming extrusions. + BoundingBoxf bbox_print(get_print_extrusions_extents(print)); + coordf_t twolayers_printz = ((layers_to_print.size() == 1) ? layers_to_print.front() : layers_to_print[1]).first + EPSILON; + for (const PrintObject *print_object : printable_objects) + bbox_print.merge(get_print_object_extrusions_extents(*print_object, twolayers_printz)); + bbox_print.merge(get_wipe_tower_extrusions_extents(print, twolayers_printz)); + BoundingBoxf bbox_prime(get_wipe_tower_priming_extrusions_extents(print)); + bbox_prime.offset(0.5f); + // Beep for 500ms, tone 800Hz. Yet better, play some Morse. + _write(file, this->retract()); + _write(file, "M300 S800 P500\n"); + if (bbox_prime.overlap(bbox_print)) { + // Wait for the user to remove the priming extrusions, otherwise they would + // get covered by the print. + _write(file, "M1 Remove priming towers and click button.\n"); + } + else { + // Just wait for a bit to let the user check, that the priming succeeded. + //TODO Add a message explaining what the printer is waiting for. This needs a firmware fix. + _write(file, "M1 S10\n"); + } } } // Extrude the layers. diff --git a/xs/src/libslic3r/Print.cpp b/xs/src/libslic3r/Print.cpp index e08ae1fc4..8c91eb192 100644 --- a/xs/src/libslic3r/Print.cpp +++ b/xs/src/libslic3r/Print.cpp @@ -155,6 +155,7 @@ bool Print::invalidate_state_by_config_options(const std::vectorplaceholder_parser.update_timestamp(); @@ -1225,7 +1222,6 @@ void Print::set_status(int percent, const std::string &message) printf("Print::status %d => %s\n", percent, message.c_str()); } - // Returns extruder this eec should be printed with, according to PrintRegion config int Print::get_extruder(const ExtrusionEntityCollection& fill, const PrintRegion ®ion) { @@ -1233,5 +1229,4 @@ int Print::get_extruder(const ExtrusionEntityCollection& fill, const PrintRegion std::max(region.config.perimeter_extruder.value - 1, 0); } - } diff --git a/xs/src/libslic3r/PrintConfig.cpp b/xs/src/libslic3r/PrintConfig.cpp index 7064e19fe..d80347a4d 100644 --- a/xs/src/libslic3r/PrintConfig.cpp +++ b/xs/src/libslic3r/PrintConfig.cpp @@ -1623,6 +1623,12 @@ PrintConfigDef::PrintConfigDef() def->cli = "single-extruder-multi-material!"; def->default_value = new ConfigOptionBool(false); + def = this->add("single_extruder_multi_material_priming", coBool); + def->label = L("Prime all printing extruders"); + def->tooltip = L("If enabled, all printing extruders will be primed at the front edge of the print bed at the start of the print."); + def->cli = "single-extruder-multi-material-priming!"; + def->default_value = new ConfigOptionBool(true); + def = this->add("support_material", coBool); def->label = L("Generate support material"); def->category = L("Support material"); diff --git a/xs/src/libslic3r/PrintConfig.hpp b/xs/src/libslic3r/PrintConfig.hpp index c530868a1..3848ba55b 100644 --- a/xs/src/libslic3r/PrintConfig.hpp +++ b/xs/src/libslic3r/PrintConfig.hpp @@ -553,6 +553,7 @@ public: ConfigOptionString start_gcode; ConfigOptionStrings start_filament_gcode; ConfigOptionBool single_extruder_multi_material; + ConfigOptionBool single_extruder_multi_material_priming; ConfigOptionString toolchange_gcode; ConfigOptionFloat travel_speed; ConfigOptionBool use_firmware_retraction; @@ -612,6 +613,7 @@ protected: OPT_PTR(retract_restart_extra_toolchange); OPT_PTR(retract_speed); OPT_PTR(single_extruder_multi_material); + OPT_PTR(single_extruder_multi_material_priming); OPT_PTR(start_gcode); OPT_PTR(start_filament_gcode); OPT_PTR(toolchange_gcode); diff --git a/xs/src/slic3r/GUI/Preset.cpp b/xs/src/slic3r/GUI/Preset.cpp index 49e235146..de9b59a95 100644 --- a/xs/src/slic3r/GUI/Preset.cpp +++ b/xs/src/slic3r/GUI/Preset.cpp @@ -303,8 +303,8 @@ const std::vector& Preset::print_options() "perimeter_extrusion_width", "external_perimeter_extrusion_width", "infill_extrusion_width", "solid_infill_extrusion_width", "top_infill_extrusion_width", "support_material_extrusion_width", "infill_overlap", "bridge_flow_ratio", "clip_multipart_objects", "elefant_foot_compensation", "xy_size_compensation", "threads", "resolution", "wipe_tower", "wipe_tower_x", "wipe_tower_y", - "wipe_tower_width", "wipe_tower_rotation_angle", "wipe_tower_bridging", "compatible_printers", - "compatible_printers_condition","inherits" + "wipe_tower_width", "wipe_tower_rotation_angle", "wipe_tower_bridging", "single_extruder_multi_material_priming", + "compatible_printers", "compatible_printers_condition","inherits" }; return s_opts; } diff --git a/xs/src/slic3r/GUI/Tab.cpp b/xs/src/slic3r/GUI/Tab.cpp index 187030f31..70f3bf8be 100644 --- a/xs/src/slic3r/GUI/Tab.cpp +++ b/xs/src/slic3r/GUI/Tab.cpp @@ -919,6 +919,7 @@ void TabPrint::build() optgroup->append_single_option_line("wipe_tower_width"); optgroup->append_single_option_line("wipe_tower_rotation_angle"); optgroup->append_single_option_line("wipe_tower_bridging"); + optgroup->append_single_option_line("single_extruder_multi_material_priming"); optgroup = page->new_optgroup(_(L("Advanced"))); optgroup->append_single_option_line("interface_shells"); From b6c5fa7f687ce4ff41ff20529ccbd99b6c8c72fb Mon Sep 17 00:00:00 2001 From: bubnikv Date: Fri, 27 Jul 2018 23:28:59 +0200 Subject: [PATCH 06/29] Added the alpha4 config to the index. --- resources/profiles/PrusaResearch.idx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/resources/profiles/PrusaResearch.idx b/resources/profiles/PrusaResearch.idx index 9d214ce40..db5a17b8d 100644 --- a/resources/profiles/PrusaResearch.idx +++ b/resources/profiles/PrusaResearch.idx @@ -1,5 +1,6 @@ min_slic3r_version = 1.41.0-alpha -0.2.0-alpha3 Adjusted machine limits for time estimates, added filament density and cost, +0.2.0-alpha4 Extended the custom start/end G-codes of the MMU2.0 printers for no priming towers. +0.2.0-alpha3 Adjusted machine limits for time estimates, added filament density and cost 0.2.0-alpha2 Renamed the key MK3SMMU to MK3MMU2, added a generic PLA MMU2 material 0.2.0-alpha1 added initial profiles for the i3 MK3 Multi Material Upgrade 2.0 0.2.0-alpha moved machine limits from the start G-code to the new print profile parameters From 11b0325c66b7851be91a5564d33b1c3928051d70 Mon Sep 17 00:00:00 2001 From: Enrico Turri Date: Mon, 30 Jul 2018 10:03:17 +0200 Subject: [PATCH 07/29] Fixed calculation of bed origin in bed shape dialog --- xs/src/slic3r/GUI/BedShapeDialog.cpp | 27 +++++++++++++-------------- 1 file changed, 13 insertions(+), 14 deletions(-) diff --git a/xs/src/slic3r/GUI/BedShapeDialog.cpp b/xs/src/slic3r/GUI/BedShapeDialog.cpp index 3dd60ef88..d52535589 100644 --- a/xs/src/slic3r/GUI/BedShapeDialog.cpp +++ b/xs/src/slic3r/GUI/BedShapeDialog.cpp @@ -9,6 +9,8 @@ #include "Model.hpp" #include "boost/nowide/iostream.hpp" +#include + namespace Slic3r { namespace GUI { @@ -146,21 +148,18 @@ void BedShapePanel::set_shape(ConfigOptionPoints* points) if (lines[0].parallel_to(lines[2]) && lines[1].parallel_to(lines[3])) { // okay, it's a rectangle // find origin - // the || 0 hack prevents "-0" which might confuse the user - int x_min, x_max, y_min, y_max; - x_max = x_min = points->values[0].x; + coordf_t x_min, x_max, y_min, y_max; + x_max = x_min = points->values[0].x; y_max = y_min = points->values[0].y; - for (auto pt : points->values){ - if (x_min > pt.x) x_min = pt.x; - if (x_max < pt.x) x_max = pt.x; - if (y_min > pt.y) y_min = pt.y; - if (y_max < pt.y) y_max = pt.y; - } - if (x_min < 0) x_min = 0; - if (x_max < 0) x_max = 0; - if (y_min < 0) y_min = 0; - if (y_max < 0) y_max = 0; - auto origin = new ConfigOptionPoints{ Pointf(-x_min, -y_min) }; + for (auto pt : points->values) + { + x_min = std::min(x_min, pt.x); + x_max = std::max(x_max, pt.x); + y_min = std::min(y_min, pt.y); + y_max = std::max(y_max, pt.y); + } + + auto origin = new ConfigOptionPoints{ Pointf(-x_min, -y_min) }; m_shape_options_book->SetSelection(SHAPE_RECTANGULAR); auto optgroup = m_optgroups[SHAPE_RECTANGULAR]; From 3f6d3b903d836e78047af5b3824d7a2ffb526896 Mon Sep 17 00:00:00 2001 From: Enrico Turri Date: Mon, 30 Jul 2018 10:35:08 +0200 Subject: [PATCH 08/29] Fixed rotation of 3D view camera after change of bed data --- xs/src/slic3r/GUI/GLCanvas3D.cpp | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/xs/src/slic3r/GUI/GLCanvas3D.cpp b/xs/src/slic3r/GUI/GLCanvas3D.cpp index 46bcf4f44..872ae2dc6 100644 --- a/xs/src/slic3r/GUI/GLCanvas3D.cpp +++ b/xs/src/slic3r/GUI/GLCanvas3D.cpp @@ -1947,6 +1947,12 @@ void GLCanvas3D::set_bed_shape(const Pointfs& shape) m_axes.origin = Pointf3(0.0, 0.0, (coordf_t)GROUND_Z); set_axes_length(0.3f * (float)m_bed.get_bounding_box().max_size()); + // forces the selection of the proper camera target + if (m_volumes.volumes.empty()) + zoom_to_bed(); + else + zoom_to_volumes(); + m_dirty = true; } From df201d65f406a23cbd85da159abbd93147640391 Mon Sep 17 00:00:00 2001 From: Enrico Turri Date: Mon, 30 Jul 2018 11:38:36 +0200 Subject: [PATCH 09/29] Minimum z of object to lay on the bed after rotations. Fixes #1093 --- xs/src/libslic3r/Model.cpp | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/xs/src/libslic3r/Model.cpp b/xs/src/libslic3r/Model.cpp index d6f1f05c9..2f73cad20 100644 --- a/xs/src/libslic3r/Model.cpp +++ b/xs/src/libslic3r/Model.cpp @@ -1109,9 +1109,23 @@ void ModelObject::scale(const Pointf3 &versor) void ModelObject::rotate(float angle, const Axis &axis) { + float min_z = FLT_MAX; for (ModelVolume *v : this->volumes) + { v->mesh.rotate(angle, axis); - this->origin_translation = Pointf3(0,0,0); + min_z = std::min(min_z, v->mesh.stl.stats.min.z); + } + + if (min_z != 0.0f) + { + // translate the object so that its minimum z lays on the bed + for (ModelVolume *v : this->volumes) + { + v->mesh.translate(0.0f, 0.0f, -min_z); + } + } + + this->origin_translation = Pointf3(0, 0, 0); this->invalidate_bounding_box(); } From 09de343e6592e8f0dc1210f5cff545e37d34c23b Mon Sep 17 00:00:00 2001 From: Enrico Turri Date: Mon, 30 Jul 2018 13:54:54 +0200 Subject: [PATCH 10/29] Fixed slice info after re-export of gcode. Fixes #1081 --- lib/Slic3r/GUI/Plater.pm | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/lib/Slic3r/GUI/Plater.pm b/lib/Slic3r/GUI/Plater.pm index a5d5eaf53..5790965ff 100644 --- a/lib/Slic3r/GUI/Plater.pm +++ b/lib/Slic3r/GUI/Plater.pm @@ -1600,7 +1600,7 @@ sub print_info_box_show { my ($self, $show) = @_; my $scrolled_window_panel = $self->{scrolled_window_panel}; my $scrolled_window_sizer = $self->{scrolled_window_sizer}; - return if $scrolled_window_sizer->IsShown(2) == $show; + return if (!$show && ($scrolled_window_sizer->IsShown(2) == $show)); if ($show) { my $print_info_sizer = $self->{print_info_sizer}; @@ -1836,6 +1836,8 @@ sub update { $self->resume_background_process; } + $self->print_info_box_show(0); + # $self->{canvas}->reload_scene if $self->{canvas}; my $selections = $self->collect_selections; Slic3r::GUI::_3DScene::set_objects_selections($self->{canvas3D}, \@$selections); From 2f7876b8522c83c0d04595d48f5f5a6efb22d729 Mon Sep 17 00:00:00 2001 From: Enrico Turri Date: Mon, 30 Jul 2018 13:57:05 +0200 Subject: [PATCH 11/29] Fixed camera jump after object rotate --- xs/src/slic3r/GUI/GLCanvas3D.cpp | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/xs/src/slic3r/GUI/GLCanvas3D.cpp b/xs/src/slic3r/GUI/GLCanvas3D.cpp index 872ae2dc6..f8dd94fd4 100644 --- a/xs/src/slic3r/GUI/GLCanvas3D.cpp +++ b/xs/src/slic3r/GUI/GLCanvas3D.cpp @@ -1941,17 +1941,22 @@ void GLCanvas3D::set_model(Model* model) void GLCanvas3D::set_bed_shape(const Pointfs& shape) { - m_bed.set_shape(shape); + bool new_shape = (shape != m_bed.get_shape()); + if (new_shape) + m_bed.set_shape(shape); // Set the origin and size for painting of the coordinate system axes. m_axes.origin = Pointf3(0.0, 0.0, (coordf_t)GROUND_Z); set_axes_length(0.3f * (float)m_bed.get_bounding_box().max_size()); - // forces the selection of the proper camera target - if (m_volumes.volumes.empty()) - zoom_to_bed(); - else - zoom_to_volumes(); + if (new_shape) + { + // forces the selection of the proper camera target + if (m_volumes.volumes.empty()) + zoom_to_bed(); + else + zoom_to_volumes(); + } m_dirty = true; } From d136d61edded36ad36f17ee14b4b954a55d69db5 Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Mon, 30 Jul 2018 15:16:44 +0200 Subject: [PATCH 12/29] 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 14c9ff174d25c88fff9593b83e60cdd856ddbcd4 Mon Sep 17 00:00:00 2001 From: YuSanka Date: Tue, 31 Jul 2018 10:42:37 +0200 Subject: [PATCH 13/29] Add variable name to tooltips --- xs/src/slic3r/GUI/Field.cpp | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/xs/src/slic3r/GUI/Field.cpp b/xs/src/slic3r/GUI/Field.cpp index 36a1c396f..0885e041b 100644 --- a/xs/src/slic3r/GUI/Field.cpp +++ b/xs/src/slic3r/GUI/Field.cpp @@ -95,9 +95,10 @@ namespace Slic3r { namespace GUI { wxString tooltip_text(""); wxString tooltip = _(m_opt.tooltip); if (tooltip.length() > 0) - tooltip_text = tooltip + "(" + _(L("default")) + ": " + - (boost::iends_with(m_opt_id, "_gcode") ? "\n" : "") + - default_string + ")"; + tooltip_text = tooltip + "\n " + _(L("default value")) + "\t: " + + (boost::iends_with(m_opt_id, "_gcode") ? "\n" : "") + default_string + + (boost::iends_with(m_opt_id, "_gcode") ? "" : "\n") + "\n " + + _(L("variable name")) + "\t: " + m_opt_id; return tooltip_text; } From b6d70f5fe8653297c6fa6f23019502101c6d655b Mon Sep 17 00:00:00 2001 From: Vojtech Kral Date: Fri, 27 Jul 2018 11:55:11 +0200 Subject: [PATCH 14/29] FirmwareDialog: UI improvements, bugfixes --- xs/src/avrdude/avrdude-slic3r.cpp | 24 +- xs/src/avrdude/avrdude-slic3r.hpp | 8 +- xs/src/slic3r/GUI/FirmwareDialog.cpp | 507 ++++++++++++++++++--------- xs/src/slic3r/Utils/HexFile.cpp | 3 +- xs/src/slic3r/Utils/HexFile.hpp | 3 +- xs/src/slic3r/Utils/Serial.cpp | 9 +- xs/src/slic3r/Utils/Serial.hpp | 5 +- 7 files changed, 385 insertions(+), 174 deletions(-) diff --git a/xs/src/avrdude/avrdude-slic3r.cpp b/xs/src/avrdude/avrdude-slic3r.cpp index 4a7f22d6e..0577fe6d0 100644 --- a/xs/src/avrdude/avrdude-slic3r.cpp +++ b/xs/src/avrdude/avrdude-slic3r.cpp @@ -35,8 +35,9 @@ struct AvrDude::priv { std::string sys_config; std::deque> args; - size_t current_args_set = 0; bool cancelled = false; + int exit_code = 0; + size_t current_args_set = 0; RunFn run_fn; MessageFn message_fn; ProgressFn progress_fn; @@ -146,15 +147,15 @@ AvrDude::Ptr AvrDude::run() int res = -1; if (self->p->run_fn) { - self->p->run_fn(*self); + self->p->run_fn(); } if (! self->p->cancelled) { - res = self->p->run(); + self->p->exit_code = self->p->run(); } if (self->p->complete_fn) { - self->p->complete_fn(res, self->p->current_args_set); + self->p->complete_fn(); } }); @@ -179,5 +180,20 @@ void AvrDude::join() } } +bool AvrDude::cancelled() +{ + return p ? p->cancelled : false; +} + +int AvrDude::exit_code() +{ + return p ? p->exit_code : 0; +} + +size_t AvrDude::last_args_set() +{ + return p ? p->current_args_set : 0; +} + } diff --git a/xs/src/avrdude/avrdude-slic3r.hpp b/xs/src/avrdude/avrdude-slic3r.hpp index 399df2358..86e097034 100644 --- a/xs/src/avrdude/avrdude-slic3r.hpp +++ b/xs/src/avrdude/avrdude-slic3r.hpp @@ -12,10 +12,10 @@ class AvrDude { public: typedef std::shared_ptr Ptr; - typedef std::function RunFn; + typedef std::function RunFn; typedef std::function MessageFn; typedef std::function ProgressFn; - typedef std::function CompleteFn; + typedef std::function CompleteFn; // Main c-tor, sys_config is the location of avrdude's main configuration file AvrDude(std::string sys_config); @@ -54,6 +54,10 @@ public: void cancel(); void join(); + + bool cancelled(); // Whether avrdude run was cancelled + int exit_code(); // The exit code of the last invocation + size_t last_args_set(); // Index of the last argument set that was processsed private: struct priv; std::unique_ptr p; diff --git a/xs/src/slic3r/GUI/FirmwareDialog.cpp b/xs/src/slic3r/GUI/FirmwareDialog.cpp index 30339e3cb..c33a50e7d 100644 --- a/xs/src/slic3r/GUI/FirmwareDialog.cpp +++ b/xs/src/slic3r/GUI/FirmwareDialog.cpp @@ -1,12 +1,14 @@ #include #include #include +#include #include #include #include #include #include #include +#include #include "libslic3r/Utils.hpp" #include "avrdude/avrdude-slic3r.hpp" @@ -32,11 +34,13 @@ #include #include #include +#include namespace fs = boost::filesystem; namespace asio = boost::asio; using boost::system::error_code; +using boost::optional; namespace Slic3r { @@ -46,19 +50,31 @@ using Utils::SerialPortInfo; using Utils::Serial; +// USB IDs used to perform device lookup +enum { + USB_VID_PRUSA = 0x2c99, + USB_PID_MK2 = 1, + USB_PID_MK3 = 2, + USB_PID_MMU_BOOT = 3, + USB_PID_MMU_APP = 4, +}; + // This enum discriminates the kind of information in EVT_AVRDUDE, // it's stored in the ExtraLong field of wxCommandEvent. enum AvrdudeEvent { AE_MESSAGE, AE_PROGRESS, + AE_STATUS, AE_EXIT, - AE_ERROR, }; wxDECLARE_EVENT(EVT_AVRDUDE, wxCommandEvent); wxDEFINE_EVENT(EVT_AVRDUDE, wxCommandEvent); +wxDECLARE_EVENT(EVT_ASYNC_DIALOG, wxCommandEvent); +wxDEFINE_EVENT(EVT_ASYNC_DIALOG, wxCommandEvent); + // Private @@ -66,15 +82,17 @@ struct FirmwareDialog::priv { enum AvrDudeComplete { + AC_NONE, AC_SUCCESS, AC_FAILURE, - AC_CANCEL, + AC_USER_CANCELLED, }; FirmwareDialog *q; // PIMPL back pointer ("Q-Pointer") + // GUI elements wxComboBox *port_picker; - std::vector ports; + wxStaticText *port_autodetect; wxFilePickerCtrl *hex_picker; wxStaticText *txt_status; wxGauge *progressbar; @@ -85,43 +103,66 @@ struct FirmwareDialog::priv wxButton *btn_flash; wxString btn_flash_label_ready; wxString btn_flash_label_flashing; + wxString label_status_flashing; wxTimer timer_pulse; + // Async modal dialog during flashing + std::mutex mutex; + int modal_response; + std::condition_variable response_cv; + + // Data + std::vector ports; + optional port; + HexFile hex_file; + // This is a shared pointer holding the background AvrDude task // also serves as a status indication (it is set _iff_ the background task is running, otherwise it is reset). AvrDude::Ptr avrdude; std::string avrdude_config; unsigned progress_tasks_done; unsigned progress_tasks_bar; - bool cancelled; + bool user_cancelled; const bool extra_verbose; // For debugging priv(FirmwareDialog *q) : q(q), btn_flash_label_ready(_(L("Flash!"))), btn_flash_label_flashing(_(L("Cancel"))), + label_status_flashing(_(L("Flashing in progress. Please do not disconnect the printer!"))), timer_pulse(q), avrdude_config((fs::path(::Slic3r::resources_dir()) / "avrdude" / "avrdude.conf").string()), progress_tasks_done(0), progress_tasks_bar(0), - cancelled(false), + user_cancelled(false), extra_verbose(false) {} void find_serial_ports(); + void fit_no_shrink(); + void set_txt_status(const wxString &label); void flashing_start(unsigned tasks); void flashing_done(AvrDudeComplete complete); - void check_model_id(const HexFile &metadata, const SerialPortInfo &port); + void enable_port_picker(bool enable); + void load_hex_file(const wxString &path); + void queue_status(wxString message); + void queue_error(const wxString &message); - void prepare_common(AvrDude &, const SerialPortInfo &port, const std::string &filename); - void prepare_mk2(AvrDude &, const SerialPortInfo &port, const std::string &filename); - void prepare_mk3(AvrDude &, const SerialPortInfo &port, const std::string &filename); - void prepare_mm_control(AvrDude &, const SerialPortInfo &port, const std::string &filename); + bool ask_model_id_mismatch(const std::string &printer_model); + bool check_model_id(); + void wait_for_mmu_bootloader(unsigned retries); + void mmu_reboot(const SerialPortInfo &port); + void lookup_port_mmu(); + void prepare_common(); + void prepare_mk2(); + void prepare_mk3(); + void prepare_mm_control(); void perform_upload(); - void cancel(); + void user_cancel(); void on_avrdude(const wxCommandEvent &evt); + void on_async_dialog(const wxCommandEvent &evt); void ensure_joined(); }; @@ -146,10 +187,30 @@ void FirmwareDialog::priv::find_serial_ports() } } +void FirmwareDialog::priv::fit_no_shrink() +{ + // Ensure content fits into window and window is not shrinked + auto old_size = q->GetSize(); + q->Layout(); + q->Fit(); + auto new_size = q->GetSize(); + q->SetSize(std::max(old_size.GetWidth(), new_size.GetWidth()), std::max(old_size.GetHeight(), new_size.GetHeight())); +} + +void FirmwareDialog::priv::set_txt_status(const wxString &label) +{ + const auto width = txt_status->GetSize().GetWidth(); + txt_status->SetLabel(label); + txt_status->Wrap(width); + + fit_no_shrink(); +} + void FirmwareDialog::priv::flashing_start(unsigned tasks) { + modal_response = wxID_NONE; txt_stdout->Clear(); - txt_status->SetLabel(_(L("Flashing in progress. Please do not disconnect the printer!"))); + set_txt_status(label_status_flashing); txt_status->SetForegroundColour(GUI::get_label_clr_modified()); port_picker->Disable(); btn_rescan->Disable(); @@ -160,7 +221,7 @@ void FirmwareDialog::priv::flashing_start(unsigned tasks) progressbar->SetValue(0); progress_tasks_done = 0; progress_tasks_bar = 0; - cancelled = false; + user_cancelled = false; timer_pulse.Start(50); } @@ -177,54 +238,190 @@ void FirmwareDialog::priv::flashing_done(AvrDudeComplete complete) progressbar->SetValue(progressbar->GetRange()); switch (complete) { - case AC_SUCCESS: txt_status->SetLabel(_(L("Flashing succeeded!"))); break; - case AC_FAILURE: txt_status->SetLabel(_(L("Flashing failed. Please see the avrdude log below."))); break; - case AC_CANCEL: txt_status->SetLabel(_(L("Flashing cancelled."))); break; + case AC_SUCCESS: set_txt_status(_(L("Flashing succeeded!"))); break; + case AC_FAILURE: set_txt_status(_(L("Flashing failed. Please see the avrdude log below."))); break; + case AC_USER_CANCELLED: set_txt_status(_(L("Flashing cancelled."))); break; + default: break; } } -void FirmwareDialog::priv::check_model_id(const HexFile &metadata, const SerialPortInfo &port) +void FirmwareDialog::priv::enable_port_picker(bool enable) { - if (metadata.model_id.empty()) { - // No data to check against - return; + port_picker->Show(enable); + btn_rescan->Show(enable); + port_autodetect->Show(! enable); + q->Layout(); + fit_no_shrink(); +} + +void FirmwareDialog::priv::load_hex_file(const wxString &path) +{ + hex_file = HexFile(path.wx_str()); + enable_port_picker(hex_file.device != HexFile::DEV_MM_CONTROL); +} + +void FirmwareDialog::priv::queue_status(wxString message) +{ + auto evt = new wxCommandEvent(EVT_AVRDUDE, this->q->GetId()); + evt->SetExtraLong(AE_STATUS); + evt->SetString(std::move(message)); + wxQueueEvent(this->q, evt); +} + +void FirmwareDialog::priv::queue_error(const wxString &message) +{ + auto evt = new wxCommandEvent(EVT_AVRDUDE, this->q->GetId()); + evt->SetExtraLong(AE_STATUS); + evt->SetString(wxString::Format(_(L("Flashing failed: %s")), message)); + + wxQueueEvent(this->q, evt); avrdude->cancel(); +} + +bool FirmwareDialog::priv::ask_model_id_mismatch(const std::string &printer_model) +{ + // model_id in the hex file doesn't match what the printer repoted. + // Ask the user if it should be flashed anyway. + + std::unique_lock lock(mutex); + + auto evt = new wxCommandEvent(EVT_ASYNC_DIALOG, this->q->GetId()); + evt->SetString(wxString::Format(_(L( + "This firmware hex file does not match the printer model.\n" + "The hex file is intended for: %s\n" + "Printer reported: %s\n\n" + "Do you want to continue and flash this hex file anyway?\n" + "Please only continue if you are sure this is the right thing to do.")), + hex_file.model_id, printer_model + )); + wxQueueEvent(this->q, evt); + + response_cv.wait(lock, [this]() { return this->modal_response != wxID_NONE; }); + + if (modal_response == wxID_YES) { + return true; + } else { + user_cancel(); + return false; } +} - asio::io_service io; - Serial serial(io, port.port, 115200); - serial.printer_setup(); +bool FirmwareDialog::priv::check_model_id() +{ + // XXX: The implementation in Serial doesn't currently work reliably enough to be used. + // Therefore, regretably, so far the check cannot be used and we just return true here. + // TODO: Rewrite Serial using more platform-native code. + return true; + + // if (hex_file.model_id.empty()) { + // // No data to check against, assume it's ok + // return true; + // } + // asio::io_service io; + // Serial serial(io, port->port, 115200); + // serial.printer_setup(); + + // enum { + // TIMEOUT = 2000, + // RETREIES = 5, + // }; + + // if (! serial.printer_ready_wait(RETREIES, TIMEOUT)) { + // queue_error(wxString::Format(_(L("Could not connect to the printer at %s")), port->port)); + // return false; + // } + + // std::string line; + // error_code ec; + // serial.printer_write_line("PRUSA Rev"); + // while (serial.read_line(TIMEOUT, line, ec)) { + // if (ec) { + // queue_error(wxString::Format(_(L("Could not connect to the printer at %s")), port->port)); + // return false; + // } + + // if (line == "ok") { continue; } + + // if (line == hex_file.model_id) { + // return true; + // } else { + // return ask_model_id_mismatch(line); + // } + + // line.clear(); + // } + + // return false; +} + +void FirmwareDialog::priv::wait_for_mmu_bootloader(unsigned retries) +{ enum { - TIMEOUT = 1000, - RETREIES = 3, + SLEEP_MS = 500, }; - if (! serial.printer_ready_wait(RETREIES, TIMEOUT)) { - throw wxString::Format(_(L("Could not connect to the printer at %s")), port.port); - } + for (unsigned i = 0; i < retries && !user_cancelled; i++) { + std::this_thread::sleep_for(std::chrono::milliseconds(SLEEP_MS)); - std::string line; - error_code ec; - serial.printer_write_line("PRUSA Rev"); - while (serial.read_line(TIMEOUT, line, ec)) { - if (ec) { throw wxString::Format(_(L("Could not connect to the printer at %s")), port.port); } - if (line == "ok") { continue; } + auto ports = Utils::scan_serial_ports_extended(); + ports.erase(std::remove_if(ports.begin(), ports.end(), [=](const SerialPortInfo &port ) { + return port.id_vendor != USB_VID_PRUSA && port.id_product != USB_PID_MMU_BOOT; + }), ports.end()); - if (line == metadata.model_id) { + if (ports.size() == 1) { + port = ports[0]; + return; + } else if (ports.size() > 1) { + BOOST_LOG_TRIVIAL(error) << "Several VID/PID 0x2c99/3 devices found"; + queue_error(_(L("Multiple Original Prusa i3 MMU 2.0 devices found. Please only connect one at a time for flashing."))); return; - } else { - throw wxString::Format(_(L( - "The firmware hex file does not match the printer model.\n" - "The hex file is intended for:\n %s\n" - "Printer reports:\n %s" - )), metadata.model_id, line); } - - line.clear(); } } -void FirmwareDialog::priv::prepare_common(AvrDude &avrdude, const SerialPortInfo &port, const std::string &filename) +void FirmwareDialog::priv::mmu_reboot(const SerialPortInfo &port) +{ + asio::io_service io; + Serial serial(io, port.port, 1200); + std::this_thread::sleep_for(std::chrono::milliseconds(50)); +} + +void FirmwareDialog::priv::lookup_port_mmu() +{ + BOOST_LOG_TRIVIAL(info) << "Flashing MMU 2.0, looking for VID/PID 0x2c99/3 or 0x2c99/4 ..."; + + auto ports = Utils::scan_serial_ports_extended(); + ports.erase(std::remove_if(ports.begin(), ports.end(), [=](const SerialPortInfo &port ) { + return port.id_vendor != USB_VID_PRUSA && + port.id_product != USB_PID_MMU_BOOT && + port.id_product != USB_PID_MMU_APP; + }), ports.end()); + + if (ports.size() == 0) { + BOOST_LOG_TRIVIAL(info) << "MMU 2.0 device not found, asking the user to press Reset and waiting for the device to show up ..."; + + queue_status(_(L( + "The Multi Material Control device was not found.\n" + "If the device is connected, please press the Reset button next to the USB connector ..." + ))); + + wait_for_mmu_bootloader(30); + } else if (ports.size() > 1) { + BOOST_LOG_TRIVIAL(error) << "Several VID/PID 0x2c99/3 devices found"; + queue_error(_(L("Multiple Original Prusa i3 MMU 2.0 devices found. Please only connect one at a time for flashing."))); + } else { + if (ports[0].id_product == USB_PID_MMU_APP) { + // The device needs to be rebooted into the bootloader mode + BOOST_LOG_TRIVIAL(info) << boost::format("Found VID/PID 0x2c99/4 at `%1%`, rebooting the device ...") % ports[0].port; + mmu_reboot(ports[0]); + wait_for_mmu_bootloader(10); + } else { + port = ports[0]; + } + } +} + +void FirmwareDialog::priv::prepare_common() { std::vector args {{ extra_verbose ? "-vvvvv" : "-v", @@ -233,10 +430,10 @@ void FirmwareDialog::priv::prepare_common(AvrDude &avrdude, const SerialPortInfo // The Prusa's avrdude is patched to never send semicolons inside the data packets, as the USB to serial chip // is flashed with a buggy firmware. "-c", "wiring", - "-P", port.port, - "-b", "115200", // TODO: Allow other rates? Ditto below. + "-P", port->port, + "-b", "115200", // TODO: Allow other rates? Ditto elsewhere. "-D", - "-U", (boost::format("flash:w:0:%1%:i") % filename).str(), + "-U", (boost::format("flash:w:0:%1%:i") % hex_file.path.string()).str(), }}; BOOST_LOG_TRIVIAL(info) << "Invoking avrdude, arguments: " @@ -244,97 +441,75 @@ void FirmwareDialog::priv::prepare_common(AvrDude &avrdude, const SerialPortInfo return a + ' ' + b; }); - avrdude.push_args(std::move(args)); + avrdude->push_args(std::move(args)); } -void FirmwareDialog::priv::prepare_mk2(AvrDude &avrdude, const SerialPortInfo &port, const std::string &filename) +void FirmwareDialog::priv::prepare_mk2() { - prepare_common(avrdude, port, filename); + if (! port) { return; } + + if (! check_model_id()) { + avrdude->cancel(); + return; + } + + prepare_common(); } -void FirmwareDialog::priv::prepare_mk3(AvrDude &avrdude, const SerialPortInfo &port, const std::string &filename) +void FirmwareDialog::priv::prepare_mk3() { - prepare_common(avrdude, port, filename); + if (! port) { return; } + + if (! check_model_id()) { + avrdude->cancel(); + return; + } + + prepare_common(); // The hex file also contains another section with l10n data to be flashed into the external flash on MK3 (Einsy) // This is done via another avrdude invocation, here we build arg list for that: - std::vector args_l10n {{ + std::vector args {{ extra_verbose ? "-vvvvv" : "-v", "-p", "atmega2560", // Using the "Arduino" mode to program Einsy's external flash with languages, using the STK500 protocol (not the STK500v2). // The Prusa's avrdude is patched again to never send semicolons inside the data packets. "-c", "arduino", - "-P", port.port, + "-P", port->port, "-b", "115200", "-D", "-u", // disable safe mode - "-U", (boost::format("flash:w:1:%1%:i") % filename).str(), + "-U", (boost::format("flash:w:1:%1%:i") % hex_file.path.string()).str(), }}; BOOST_LOG_TRIVIAL(info) << "Invoking avrdude for external flash flashing, arguments: " - << std::accumulate(std::next(args_l10n.begin()), args_l10n.end(), args_l10n[0], [](std::string a, const std::string &b) { + << std::accumulate(std::next(args.begin()), args.end(), args[0], [](std::string a, const std::string &b) { return a + ' ' + b; }); - avrdude.push_args(std::move(args_l10n)); + avrdude->push_args(std::move(args)); } -void FirmwareDialog::priv::prepare_mm_control(AvrDude &avrdude, const SerialPortInfo &port_in, const std::string &filename) +void FirmwareDialog::priv::prepare_mm_control() { - // Check if the port has the PID/VID of 0x2c99/3 - // If not, check if it is the MMU (0x2c99/4) and reboot the by opening @ 1200 bauds - BOOST_LOG_TRIVIAL(info) << "Flashing MMU 2.0, looking for VID/PID 0x2c99/3 or 0x2c99/4 ..."; - SerialPortInfo port = port_in; - if (! port.id_match(0x2c99, 3)) { - if (! port.id_match(0x2c99, 4)) { - // This is not a Prusa MMU 2.0 device - BOOST_LOG_TRIVIAL(error) << boost::format("Not a Prusa MMU 2.0 device: `%1%`") % port.port; - throw wxString::Format(_(L("The device at `%s` is not am Original Prusa i3 MMU 2.0 device")), port.port); - } - - BOOST_LOG_TRIVIAL(info) << boost::format("Found VID/PID 0x2c99/4 at `%1%`, rebooting the device ...") % port.port; - - { - asio::io_service io; - Serial serial(io, port.port, 1200); - std::this_thread::sleep_for(std::chrono::milliseconds(50)); - } - - // Wait for the bootloader to show up - std::this_thread::sleep_for(std::chrono::milliseconds(2000)); - - // Look for the rebooted device - BOOST_LOG_TRIVIAL(info) << "... looking for VID/PID 0x2c99/3 ..."; - auto new_ports = Utils::scan_serial_ports_extended(); - unsigned hits = 0; - for (auto &&new_port : new_ports) { - if (new_port.id_match(0x2c99, 3)) { - hits++; - port = std::move(new_port); - } - } - - if (hits == 0) { - BOOST_LOG_TRIVIAL(error) << "No VID/PID 0x2c99/3 device found after rebooting the MMU 2.0"; - throw wxString::Format(_(L("Failed to reboot the device at `%s` for programming")), port.port); - } else if (hits > 1) { - // We found multiple 0x2c99/3 devices, this is bad, because there's no way to find out - // which one is the one user wants to flash. - BOOST_LOG_TRIVIAL(error) << "Several VID/PID 0x2c99/3 devices found after rebooting the MMU 2.0"; - throw wxString::Format(_(L("Multiple Original Prusa i3 MMU 2.0 devices found. Please only connect one at a time for flashing.")), port.port); - } + port = boost::none; + lookup_port_mmu(); + if (! port) { + queue_error(_(L("The device could not have been found"))); + return; } - BOOST_LOG_TRIVIAL(info) << boost::format("Found VID/PID 0x2c99/3 at `%1%`, flashing ...") % port.port; + BOOST_LOG_TRIVIAL(info) << boost::format("Found VID/PID 0x2c99/3 at `%1%`, flashing ...") % port->port; + queue_status(label_status_flashing); std::vector args {{ extra_verbose ? "-vvvvv" : "-v", "-p", "atmega32u4", "-c", "avr109", - "-P", port.port, + "-P", port->port, "-b", "57600", "-D", - "-U", (boost::format("flash:w:0:%1%:i") % filename).str(), + "-U", (boost::format("flash:w:0:%1%:i") % hex_file.path.string()).str(), }}; BOOST_LOG_TRIVIAL(info) << "Invoking avrdude, arguments: " @@ -342,7 +517,7 @@ void FirmwareDialog::priv::prepare_mm_control(AvrDude &avrdude, const SerialPort return a + ' ' + b; }); - avrdude.push_args(std::move(args)); + avrdude->push_args(std::move(args)); } @@ -351,20 +526,21 @@ void FirmwareDialog::priv::perform_upload() auto filename = hex_picker->GetPath(); if (filename.IsEmpty()) { return; } - int selection = port_picker->GetSelection(); - if (selection == wxNOT_FOUND) { return; } + load_hex_file(filename); // Might already be loaded, but we want to make sure it's fresh - const SerialPortInfo &port = this->ports[selection]; - // Verify whether the combo box list selection equals to the combo box edit value. - if (wxString::FromUTF8(this->ports[selection].friendly_name.data()) != port_picker->GetValue()) { - return; + int selection = port_picker->GetSelection(); + if (selection != wxNOT_FOUND) { + port = this->ports[selection]; + + // Verify whether the combo box list selection equals to the combo box edit value. + if (wxString::FromUTF8(port->friendly_name.data()) != port_picker->GetValue()) { + return; + } } const bool extra_verbose = false; // For debugging - HexFile metadata(filename.wx_str()); - const std::string filename_utf8(filename.utf8_str().data()); - flashing_start(metadata.device == HexFile::DEV_MK3 ? 2 : 1); + flashing_start(hex_file.device == HexFile::DEV_MK3 ? 2 : 1); // Init the avrdude object AvrDude avrdude(avrdude_config); @@ -374,36 +550,23 @@ void FirmwareDialog::priv::perform_upload() auto q = this->q; this->avrdude = avrdude - .on_run([=](AvrDude &avrdude) { - auto queue_error = [&](wxString message) { - avrdude.cancel(); - - auto evt = new wxCommandEvent(EVT_AVRDUDE, this->q->GetId()); - evt->SetExtraLong(AE_ERROR); - evt->SetString(std::move(message)); - wxQueueEvent(this->q, evt); - }; - + .on_run([this]() { try { - switch (metadata.device) { + switch (this->hex_file.device) { case HexFile::DEV_MK3: - this->check_model_id(metadata, port); - this->prepare_mk3(avrdude, port, filename_utf8); + this->prepare_mk3(); break; case HexFile::DEV_MM_CONTROL: - this->check_model_id(metadata, port); - this->prepare_mm_control(avrdude, port, filename_utf8); + this->prepare_mm_control(); break; default: - this->prepare_mk2(avrdude, port, filename_utf8); + this->prepare_mk2(); break; } - } catch (const wxString &message) { - queue_error(message); } catch (const std::exception &ex) { - queue_error(wxString::Format(_(L("Error accessing port at %s: %s")), port.port, ex.what())); + queue_error(wxString::Format(_(L("Error accessing port at %s: %s")), port->port, ex.what())); } }) .on_message(std::move([q, extra_verbose](const char *msg, unsigned /* size */) { @@ -423,20 +586,19 @@ void FirmwareDialog::priv::perform_upload() evt->SetInt(progress); wxQueueEvent(q, evt); })) - .on_complete(std::move([q](int status, size_t /* args_id */) { - auto evt = new wxCommandEvent(EVT_AVRDUDE, q->GetId()); + .on_complete(std::move([this]() { + auto evt = new wxCommandEvent(EVT_AVRDUDE, this->q->GetId()); evt->SetExtraLong(AE_EXIT); - evt->SetInt(status); - wxQueueEvent(q, evt); + evt->SetInt(this->avrdude->exit_code()); + wxQueueEvent(this->q, evt); })) .run(); } -void FirmwareDialog::priv::cancel() +void FirmwareDialog::priv::user_cancel() { if (avrdude) { - cancelled = true; - txt_status->SetLabel(_(L("Cancelling..."))); + user_cancelled = true; avrdude->cancel(); } } @@ -474,19 +636,17 @@ void FirmwareDialog::priv::on_avrdude(const wxCommandEvent &evt) case AE_EXIT: BOOST_LOG_TRIVIAL(info) << "avrdude exit code: " << evt.GetInt(); - complete_kind = cancelled ? AC_CANCEL : (evt.GetInt() == 0 ? AC_SUCCESS : AC_FAILURE); + // Figure out the exit state + if (user_cancelled) { complete_kind = AC_USER_CANCELLED; } + else if (avrdude->cancelled()) { complete_kind = AC_NONE; } // Ie. cancelled programatically + else { complete_kind = evt.GetInt() == 0 ? AC_SUCCESS : AC_FAILURE; } + flashing_done(complete_kind); ensure_joined(); break; - case AE_ERROR: - txt_stdout->AppendText(evt.GetString()); - flashing_done(AC_FAILURE); - ensure_joined(); - { - GUI::ErrorDialog dlg(this->q, evt.GetString()); - dlg.ShowModal(); - } + case AE_STATUS: + set_txt_status(evt.GetString()); break; default: @@ -494,6 +654,16 @@ void FirmwareDialog::priv::on_avrdude(const wxCommandEvent &evt) } } +void FirmwareDialog::priv::on_async_dialog(const wxCommandEvent &evt) +{ + wxMessageDialog dlg(this->q, evt.GetString(), wxMessageBoxCaptionStr, wxYES_NO | wxNO_DEFAULT | wxICON_QUESTION); + { + std::lock_guard lock(mutex); + modal_response = dlg.ShowModal(); + } + response_cv.notify_all(); +} + void FirmwareDialog::priv::ensure_joined() { // Make sure the background thread is collected and the AvrDude object reset @@ -521,41 +691,47 @@ FirmwareDialog::FirmwareDialog(wxWindow *parent) : wxFont mono_font(wxFontInfo().Family(wxFONTFAMILY_TELETYPE)); mono_font.MakeSmaller(); + // Create GUI components and layout + auto *panel = new wxPanel(this); wxBoxSizer *vsizer = new wxBoxSizer(wxVERTICAL); panel->SetSizer(vsizer); + auto *label_hex_picker = new wxStaticText(panel, wxID_ANY, _(L("Firmware image:"))); + p->hex_picker = new wxFilePickerCtrl(panel, wxID_ANY); + auto *label_port_picker = new wxStaticText(panel, wxID_ANY, _(L("Serial port:"))); p->port_picker = new wxComboBox(panel, wxID_ANY); + p->port_autodetect = new wxStaticText(panel, wxID_ANY, _(L("Autodetected"))); p->btn_rescan = new wxButton(panel, wxID_ANY, _(L("Rescan"))); auto *port_sizer = new wxBoxSizer(wxHORIZONTAL); port_sizer->Add(p->port_picker, 1, wxEXPAND | wxRIGHT, SPACING); port_sizer->Add(p->btn_rescan, 0); + port_sizer->Add(p->port_autodetect, 1, wxEXPAND); + p->enable_port_picker(true); - auto *label_hex_picker = new wxStaticText(panel, wxID_ANY, _(L("Firmware image:"))); - p->hex_picker = new wxFilePickerCtrl(panel, wxID_ANY); + auto *label_progress = new wxStaticText(panel, wxID_ANY, _(L("Progress:"))); + p->progressbar = new wxGauge(panel, wxID_ANY, 1, wxDefaultPosition, wxDefaultSize, wxGA_HORIZONTAL | wxGA_SMOOTH); auto *label_status = new wxStaticText(panel, wxID_ANY, _(L("Status:"))); p->txt_status = new wxStaticText(panel, wxID_ANY, _(L("Ready"))); p->txt_status->SetFont(status_font); - auto *label_progress = new wxStaticText(panel, wxID_ANY, _(L("Progress:"))); - p->progressbar = new wxGauge(panel, wxID_ANY, 1, wxDefaultPosition, wxDefaultSize, wxGA_HORIZONTAL | wxGA_SMOOTH); - auto *grid = new wxFlexGridSizer(2, SPACING, SPACING); grid->AddGrowableCol(1); - grid->Add(label_port_picker, 0, wxALIGN_CENTER_VERTICAL); - grid->Add(port_sizer, 0, wxEXPAND); grid->Add(label_hex_picker, 0, wxALIGN_CENTER_VERTICAL); grid->Add(p->hex_picker, 0, wxEXPAND); - grid->Add(label_status, 0, wxALIGN_CENTER_VERTICAL); - grid->Add(p->txt_status, 0, wxEXPAND); + grid->Add(label_port_picker, 0, wxALIGN_CENTER_VERTICAL); + grid->Add(port_sizer, 0, wxEXPAND); grid->Add(label_progress, 0, wxALIGN_CENTER_VERTICAL); grid->Add(p->progressbar, 1, wxEXPAND | wxALIGN_CENTER_VERTICAL); + grid->Add(label_status, 0, wxALIGN_CENTER_VERTICAL); + grid->Add(p->txt_status, 0, wxEXPAND); + vsizer->Add(grid, 0, wxEXPAND | wxTOP | wxBOTTOM, SPACING); p->spoiler = new wxCollapsiblePane(panel, wxID_ANY, _(L("Advanced: avrdude output log"))); @@ -571,6 +747,7 @@ FirmwareDialog::FirmwareDialog(wxWindow *parent) : p->btn_close = new wxButton(panel, wxID_CLOSE); p->btn_flash = new wxButton(panel, wxID_ANY, p->btn_flash_label_ready); + p->btn_flash->Disable(); auto *bsizer = new wxBoxSizer(wxHORIZONTAL); bsizer->Add(p->btn_close); bsizer->AddStretchSpacer(); @@ -585,6 +762,15 @@ FirmwareDialog::FirmwareDialog(wxWindow *parent) : SetSize(std::max(size.GetWidth(), static_cast(MIN_WIDTH)), std::max(size.GetHeight(), static_cast(MIN_HEIGHT))); Layout(); + // Bind events + + p->hex_picker->Bind(wxEVT_FILEPICKER_CHANGED, [this](wxFileDirPickerEvent& evt) { + if (wxFileExists(evt.GetPath())) { + this->p->load_hex_file(evt.GetPath()); + this->p->btn_flash->Enable(); + } + }); + p->spoiler->Bind(wxEVT_COLLAPSIBLEPANE_CHANGED, [this](wxCollapsiblePaneEvent &evt) { // Dialog size gets screwed up by wxCollapsiblePane, we need to fix it here if (evt.GetCollapsed()) { @@ -593,7 +779,6 @@ FirmwareDialog::FirmwareDialog(wxWindow *parent) : this->SetMinSize(wxSize(MIN_WIDTH, MIN_HEIGHT_EXPANDED)); } - this->Fit(); this->Layout(); }); @@ -608,7 +793,10 @@ FirmwareDialog::FirmwareDialog(wxWindow *parent) : _(L("Confirmation")), wxYES_NO | wxNO_DEFAULT | wxICON_QUESTION); if (dlg.ShowModal() == wxID_YES) { - this->p->cancel(); + if (this->p->avrdude) { + this->p->set_txt_status(_(L("Cancelling..."))); + this->p->user_cancel(); + } } } else { // Start a flashing task @@ -619,6 +807,7 @@ FirmwareDialog::FirmwareDialog(wxWindow *parent) : Bind(wxEVT_TIMER, [this](wxTimerEvent &evt) { this->p->progressbar->Pulse(); }); Bind(EVT_AVRDUDE, [this](wxCommandEvent &evt) { this->p->on_avrdude(evt); }); + Bind(EVT_ASYNC_DIALOG, [this](wxCommandEvent &evt) { this->p->on_async_dialog(evt); }); Bind(wxEVT_CLOSE_WINDOW, [this](wxCloseEvent &evt) { if (this->p->avrdude) { diff --git a/xs/src/slic3r/Utils/HexFile.cpp b/xs/src/slic3r/Utils/HexFile.cpp index ed26ddf37..282c647bd 100644 --- a/xs/src/slic3r/Utils/HexFile.cpp +++ b/xs/src/slic3r/Utils/HexFile.cpp @@ -46,8 +46,7 @@ static size_t hex_num_sections(fs::ifstream &file) } HexFile::HexFile(fs::path path) : - path(std::move(path)), - device(DEV_GENERIC) + path(std::move(path)) { fs::ifstream file(this->path); if (! file.good()) { diff --git a/xs/src/slic3r/Utils/HexFile.hpp b/xs/src/slic3r/Utils/HexFile.hpp index d8d1e09ab..1201d23a4 100644 --- a/xs/src/slic3r/Utils/HexFile.hpp +++ b/xs/src/slic3r/Utils/HexFile.hpp @@ -19,9 +19,10 @@ struct HexFile }; boost::filesystem::path path; - DeviceKind device; + DeviceKind device = DEV_GENERIC; std::string model_id; + HexFile() {} HexFile(boost::filesystem::path path); }; diff --git a/xs/src/slic3r/Utils/Serial.cpp b/xs/src/slic3r/Utils/Serial.cpp index c3c16b314..183119b44 100644 --- a/xs/src/slic3r/Utils/Serial.cpp +++ b/xs/src/slic3r/Utils/Serial.cpp @@ -373,7 +373,7 @@ void Serial::set_DTR(bool on) void Serial::reset_line_num() { // See https://github.com/MarlinFirmware/Marlin/wiki/M110 - printer_write_line("M110 N0", 0); + write_string("M110 N0\n"); m_line_num = 0; } @@ -390,9 +390,9 @@ bool Serial::read_line(unsigned timeout, std::string &line, error_code &ec) asio::async_read(*this, boost::asio::buffer(&c, 1), [&](const error_code &read_ec, size_t size) { if (ec || size == 0) { fail = true; - ec = read_ec; + ec = read_ec; // FIXME: only if operation not aborted } - timer.cancel(); + timer.cancel(); // FIXME: ditto }); if (timeout > 0) { @@ -444,6 +444,7 @@ bool Serial::printer_ready_wait(unsigned retries, unsigned timeout) } line.clear(); } + line.clear(); } @@ -469,7 +470,7 @@ void Serial::printer_reset() this->set_DTR(true); std::this_thread::sleep_for(std::chrono::milliseconds(200)); this->set_DTR(false); - std::this_thread::sleep_for(std::chrono::milliseconds(2000)); + std::this_thread::sleep_for(std::chrono::milliseconds(1000)); } std::string Serial::printer_format_line(const std::string &line, unsigned line_num) diff --git a/xs/src/slic3r/Utils/Serial.hpp b/xs/src/slic3r/Utils/Serial.hpp index d15f249c0..e4a28de09 100644 --- a/xs/src/slic3r/Utils/Serial.hpp +++ b/xs/src/slic3r/Utils/Serial.hpp @@ -51,12 +51,13 @@ public: // Reads a line or times out, the timeout is in milliseconds bool read_line(unsigned timeout, std::string &line, boost::system::error_code &ec); - // Perform setup for communicating with a printer + // Perform an initial setup for communicating with a printer void printer_setup(); // Write data from a string size_t write_string(const std::string &str); - + + // Attempts to reset the line numer and waits until the printer says "ok" bool printer_ready_wait(unsigned retries, unsigned timeout); // Write Marlin-formatted line, with a line number and a checksum From 675e4cfd2439aa31c8daeffd7e49e1ba02f45cac Mon Sep 17 00:00:00 2001 From: Vojtech Kral Date: Mon, 30 Jul 2018 14:51:49 +0200 Subject: [PATCH 15/29] FirmwareDialog: Fix dialog resizing --- xs/src/slic3r/GUI/FirmwareDialog.cpp | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/xs/src/slic3r/GUI/FirmwareDialog.cpp b/xs/src/slic3r/GUI/FirmwareDialog.cpp index c33a50e7d..77e70c49b 100644 --- a/xs/src/slic3r/GUI/FirmwareDialog.cpp +++ b/xs/src/slic3r/GUI/FirmwareDialog.cpp @@ -190,11 +190,13 @@ void FirmwareDialog::priv::find_serial_ports() void FirmwareDialog::priv::fit_no_shrink() { // Ensure content fits into window and window is not shrinked - auto old_size = q->GetSize(); + const auto old_size = q->GetSize(); q->Layout(); q->Fit(); - auto new_size = q->GetSize(); - q->SetSize(std::max(old_size.GetWidth(), new_size.GetWidth()), std::max(old_size.GetHeight(), new_size.GetHeight())); + const auto new_size = q->GetSize(); + const auto new_width = std::max(old_size.GetWidth(), new_size.GetWidth()); + const auto new_height = std::max(old_size.GetHeight(), new_size.GetHeight()); + q->SetSize(new_width, new_height); } void FirmwareDialog::priv::set_txt_status(const wxString &label) @@ -734,7 +736,7 @@ FirmwareDialog::FirmwareDialog(wxWindow *parent) : vsizer->Add(grid, 0, wxEXPAND | wxTOP | wxBOTTOM, SPACING); - p->spoiler = new wxCollapsiblePane(panel, wxID_ANY, _(L("Advanced: avrdude output log"))); + p->spoiler = new wxCollapsiblePane(panel, wxID_ANY, _(L("Advanced: avrdude output log")), wxDefaultPosition, wxDefaultSize, wxCP_DEFAULT_STYLE | wxCP_NO_TLW_RESIZE); auto *spoiler_pane = p->spoiler->GetPane(); auto *spoiler_sizer = new wxBoxSizer(wxVERTICAL); p->txt_stdout = new wxTextCtrl(spoiler_pane, wxID_ANY, wxEmptyString, wxDefaultPosition, wxDefaultSize, wxTE_MULTILINE | wxTE_READONLY); @@ -772,14 +774,16 @@ FirmwareDialog::FirmwareDialog(wxWindow *parent) : }); p->spoiler->Bind(wxEVT_COLLAPSIBLEPANE_CHANGED, [this](wxCollapsiblePaneEvent &evt) { - // Dialog size gets screwed up by wxCollapsiblePane, we need to fix it here if (evt.GetCollapsed()) { this->SetMinSize(wxSize(MIN_WIDTH, MIN_HEIGHT)); + const auto new_height = this->GetSize().GetHeight() - this->p->txt_stdout->GetSize().GetHeight(); + this->SetSize(this->GetSize().GetWidth(), new_height); } else { this->SetMinSize(wxSize(MIN_WIDTH, MIN_HEIGHT_EXPANDED)); } this->Layout(); + this->p->fit_no_shrink(); }); p->btn_close->Bind(wxEVT_BUTTON, [this](wxCommandEvent &) { this->Close(); }); @@ -793,10 +797,8 @@ FirmwareDialog::FirmwareDialog(wxWindow *parent) : _(L("Confirmation")), wxYES_NO | wxNO_DEFAULT | wxICON_QUESTION); if (dlg.ShowModal() == wxID_YES) { - if (this->p->avrdude) { - this->p->set_txt_status(_(L("Cancelling..."))); - this->p->user_cancel(); - } + this->p->set_txt_status(_(L("Cancelling..."))); + this->p->user_cancel(); } } else { // Start a flashing task From 212ebc5615142ade23df909de05396d345942c90 Mon Sep 17 00:00:00 2001 From: bubnikv Date: Wed, 1 Aug 2018 19:07:49 +0200 Subject: [PATCH 16/29] Updated the start G-codes for the MK3 MMU2 profiles: Implemented wiping line for MMU when the priming blocks are disabled, added initialization of the MMU2 unit with the filament types. --- resources/profiles/PrusaResearch.ini | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/resources/profiles/PrusaResearch.ini b/resources/profiles/PrusaResearch.ini index 8e0ab024b..446ee5148 100644 --- a/resources/profiles/PrusaResearch.ini +++ b/resources/profiles/PrusaResearch.ini @@ -1203,7 +1203,7 @@ default_filament_profile = Prusa PLA MMU2 [printer:Original Prusa i3 MK3 MMU2 Single] inherits = *mm2* -start_gcode = M107\nM83 ; extruder relative mode\nM104 S[first_layer_temperature] ; set extruder temp\nM140 S[first_layer_bed_temperature] ; set bed temp\nM190 S[first_layer_bed_temperature] ; wait for bed temp\nM109 S[first_layer_temperature] ; wait for extruder temp\nG28 W ; home all without mesh bed level\nG80 ; mesh bed leveling\n\nG21 ; set units to millimeters\n\n ; go outside print area\nG1 Y-3.0 F1000.0 \nG1 Z0.4 F1000\n; select extruder\nT?\n; initial load\nG1 X50 E15 F1073\nG1 X100 E10 F2000\nG1 Z0.3 F1000\n\nG92 E0.0\nG1 X240.0 E15.0 F2400.0 \nG1 Y-2.0 F1000.0\nG1 X100.0 E10 F1400.0 \nG1 Z0.20 F1000\nG1 X0.0 E4 F1000.0\n\nG92 E0.0\nM221 S{if layer_height<0.075}100{else}95{endif}\nG90 ; use absolute coordinates\nM83 ; use relative distances for extrusion\nG92 E0.0\n +start_gcode = M107\nM83 ; extruder relative mode\nM104 S[first_layer_temperature] ; set extruder temp\nM140 S[first_layer_bed_temperature] ; set bed temp\nM190 S[first_layer_bed_temperature] ; wait for bed temp\nM109 S[first_layer_temperature] ; wait for extruder temp\nG28 W ; home all without mesh bed level\nG80 ; mesh bed leveling\n\nG21 ; set units to millimeters\n\n;go outside print area\nG1 Y-3.0 F1000.0\nG1 Z0.4 F1000.0\n; select extruder\nT?\n; initial load\nG1 X55.0 E32.0 F1073.0\nG1 X5.0 E32.0 F1800.0\nG1 X55.0 E8.0 F2000.0\nG1 Z0.3 F1000.0\nG92 E0.0\nG1 X240.0 E25.0 F2200.0\nG1 Y-2.0 F1000.0\nG1 X55.0 E25 F1400.0\nG1 Z0.20 F1000.0\nG1 X5.0 E4.0 F1000.0\n\nM221 S{if layer_height<0.075}100{else}95{endif}\nG90 ; use absolute coordinates\nM83 ; use relative distances for extrusion\nG92 E0.0\n end_gcode = G1 X0 Y210 F7200\nG1 E2 F5000\nG1 E2 F5500\nG1 E2 F6000\nG1 E-15.0000 F5800\nG1 E-20.0000 F5500\nG1 E10.0000 F3000\nG1 E-10.0000 F3100\nG1 E10.0000 F3150\nG1 E-10.0000 F3250\nG1 E10.0000 F3300\n\nM702 C\n\nG4 ; wait\nM104 S0 ; turn off temperature\nM140 S0 ; turn off heatbed\nM107 ; turn off fan\nG1 X0 Y200; home X axis\nM84 ; disable motors [printer:Original Prusa i3 MK3 MMU2] @@ -1213,7 +1213,7 @@ inherits = *mm2* # to be defined explicitely. nozzle_diameter = 0.4,0.4,0.4,0.4,0.4 extruder_colour = #FF8000;#0080FF;#00FFFF;#FF4F4F;#9FFF9F -start_gcode = M107\n\nM83 ; extruder relative mode\nM104 S[first_layer_temperature] ; set extruder temp\nM140 S[first_layer_bed_temperature] ; set bed temp\nM190 S[first_layer_bed_temperature] ; wait for bed temp\nM109 S[first_layer_temperature] ; wait for extruder temp\nG28 W ; home all without mesh bed level\nG80 ; mesh bed leveling\nG21 ; set units to millimeters\n\n{if not has_single_extruder_multi_material_priming}\n; go outside print area\nG1 Y-3.0 F1000.0 \nG1 Z0.4 F1000\n; select extruder\nT[initial_tool]\n; initial load\nG1 X50 E15 F1073\nG1 X100 E10 F2000\nG1 Z0.3 F1000\nG92 E0.0\nG1 X240.0 E15.0 F2400.0 \nG1 Y-2.0 F1000.0\nG1 X100.0 E10 F1400.0 \nG1 Z0.20 F1000\nG1 X0.0 E4 F1000.0\nG92 E0.0\n{endif}\n\n;M221 S{if layer_height<0.075}100{else}95{endif}\nG90 ; use absolute coordinates\nM83 ; use relative distances for extrusion\nG92 E0.0\n +start_gcode = M107\n\nM83 ; extruder relative mode\nM104 S[first_layer_temperature] ; set extruder temp\nM140 S[first_layer_bed_temperature] ; set bed temp\nM190 S[first_layer_bed_temperature] ; wait for bed temp\nM109 S[first_layer_temperature] ; wait for extruder temp\nG28 W ; home all without mesh bed level\nG80 ; mesh bed leveling\nG21 ; set units to millimeters\n\n; Send the filament type to the MMU2.0 unit.\n; E stands for extruder number, F stands for filament type (0: default; 1:flex; 2: PVA)\nM403 E0 F{"" + ((filament_type[0]=="FLEX") ? 1 : ((filament_type[0]=="PVA") ? 2 : 0))}\nM403 E1 F{"" + ((filament_type[1]=="FLEX") ? 1 : ((filament_type[1]=="PVA") ? 2 : 0))}\nM403 E2 F{"" + ((filament_type[2]=="FLEX") ? 1 : ((filament_type[2]=="PVA") ? 2 : 0))}\nM403 E3 F{"" + ((filament_type[3]=="FLEX") ? 1 : ((filament_type[3]=="PVA") ? 2 : 0))}\nM403 E4 F{"" + ((filament_type[4]=="FLEX") ? 1 : ((filament_type[4]=="PVA") ? 2 : 0))}\n\n{if not has_single_extruder_multi_material_priming}\n;go outside print area\nG1 Y-3.0 F1000.0\nG1 Z0.4 F1000.0\n; select extruder\nT[initial_tool]\n; initial load\nG1 X55.0 E32.0 F1073.0\nG1 X5.0 E32.0 F1800.0\nG1 X55.0 E8.0 F2000.0\nG1 Z0.3 F1000.0\nG92 E0.0\nG1 X240.0 E25.0 F2200.0\nG1 Y-2.0 F1000.0\nG1 X55.0 E25 F1400.0\nG1 Z0.20 F1000.0\nG1 X5.0 E4.0 F1000.0\nG92 E0.0\n{endif}\n\n;M221 S{if layer_height<0.075}100{else}95{endif}\nG90 ; use absolute coordinates\nM83 ; use relative distances for extrusion\nG92 E0.0\n end_gcode = {if has_wipe_tower}\nG1 E-15.0000 F3000\n{else}\nG1 X0 Y210 F7200\nG1 E2 F5000\nG1 E2 F5500\nG1 E2 F6000\nG1 E-15.0000 F5800\nG1 E-20.0000 F5500\nG1 E10.0000 F3000\nG1 E-10.0000 F3100\nG1 E10.0000 F3150\nG1 E-10.0000 F3250\nG1 E10.0000 F3300\n{endif}\n\n; Unload filament\nM702 C\n\nG4 ; wait\nM104 S0 ; turn off temperature\nM140 S0 ; turn off heatbed\nM107 ; turn off fan\n; Lift print head a bit\n{if layer_z < max_print_height}G1 Z{z_offset+min(layer_z+30, max_print_height)}{endif} ; Move print head up\nG1 X0 Y200; home X axis\nM84 ; disable motors\n # The obsolete presets will be removed when upgrading from the legacy configuration structure (up to Slic3r 1.39.2) to 1.40.0 and newer. From 8fc11c2f14a04c5911cd6efc3a888cfc45467976 Mon Sep 17 00:00:00 2001 From: bubnikv Date: Thu, 2 Aug 2018 09:11:11 +0200 Subject: [PATCH 17/29] Configuration improvements: Bumped up firmware versions for MK2.5/MK3 to 3.3.1, disabled priming areas for MK3MMU2 --- resources/profiles/PrusaResearch.idx | 1 + resources/profiles/PrusaResearch.ini | 49 +++++++++++++--------------- 2 files changed, 23 insertions(+), 27 deletions(-) diff --git a/resources/profiles/PrusaResearch.idx b/resources/profiles/PrusaResearch.idx index db5a17b8d..9d1806c03 100644 --- a/resources/profiles/PrusaResearch.idx +++ b/resources/profiles/PrusaResearch.idx @@ -1,4 +1,5 @@ min_slic3r_version = 1.41.0-alpha +0.2.0-alpha5 Bumped up firmware versions for MK2.5/MK3 to 3.3.1, disabled priming areas for MK3MMU2 0.2.0-alpha4 Extended the custom start/end G-codes of the MMU2.0 printers for no priming towers. 0.2.0-alpha3 Adjusted machine limits for time estimates, added filament density and cost 0.2.0-alpha2 Renamed the key MK3SMMU to MK3MMU2, added a generic PLA MMU2 material diff --git a/resources/profiles/PrusaResearch.ini b/resources/profiles/PrusaResearch.ini index 446ee5148..63f4bfe3f 100644 --- a/resources/profiles/PrusaResearch.ini +++ b/resources/profiles/PrusaResearch.ini @@ -5,7 +5,7 @@ name = Prusa Research # Configuration version of this file. Config file will only be installed, if the config_version differs. # This means, the server may force the Slic3r configuration to be downgraded. -config_version = 0.2.0-alpha4 +config_version = 0.2.0-alpha5 # Where to get the updates from? config_update_url = https://raw.githubusercontent.com/prusa3d/Slic3r-settings/master/live/PrusaResearch/ @@ -95,6 +95,7 @@ print_settings_id = raft_layers = 0 resolution = 0 seam_position = nearest +single_extruder_multi_material_priming = 1 skirts = 1 skirt_distance = 2 skirt_height = 3 @@ -136,6 +137,10 @@ wipe_tower_x = 200 wipe_tower_y = 155 xy_size_compensation = 0 +[print:*MK3*] +fill_pattern = grid +single_extruder_multi_material_priming = 0 + # Print parameters common to a 0.25mm diameter nozzle. [print:*0.25nozzle*] external_perimeter_extrusion_width = 0.25 @@ -211,9 +216,8 @@ compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and infill_extrusion_width = 0.5 [print:0.05mm ULTRADETAIL MK3] -inherits = *0.05mm* +inherits = *0.05mm*, *MK3* compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK3.*/ and nozzle_diameter[0]==0.4 and ! single_extruder_multi_material -fill_pattern = grid top_infill_extrusion_width = 0.4 [print:0.05mm ULTRADETAIL 0.25 nozzle] @@ -228,9 +232,8 @@ solid_infill_speed = 20 support_material_speed = 20 [print:0.05mm ULTRADETAIL 0.25 nozzle MK3] -inherits = *0.05mm*; *0.25nozzle* +inherits = *0.05mm*; *0.25nozzle*, *MK3* compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK3.*/ and nozzle_diameter[0]==0.25 and num_extruders==1 -fill_pattern = grid # XXXXXXXXXXXXXXXXXXXX # XXX--- 0.10mm ---XXX @@ -255,11 +258,10 @@ perimeter_speed = 50 solid_infill_speed = 50 [print:0.10mm DETAIL MK3] -inherits = *0.10mm* +inherits = *0.10mm*, *MK3* bridge_speed = 30 compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK3.*/ and nozzle_diameter[0]==0.4 and ! single_extruder_multi_material external_perimeter_speed = 35 -fill_pattern = grid infill_acceleration = 1250 infill_speed = 200 max_print_speed = 200 @@ -282,11 +284,10 @@ solid_infill_speed = 40 top_solid_infill_speed = 30 [print:0.10mm DETAIL 0.25 nozzle MK3] -inherits = *0.10mm*; *0.25nozzle* +inherits = *0.10mm*; *0.25nozzle*, *MK3* bridge_speed = 30 compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK3.*/ and nozzle_diameter[0]==0.25 external_perimeter_speed = 35 -fill_pattern = grid infill_acceleration = 1250 infill_speed = 200 max_print_speed = 200 @@ -295,11 +296,10 @@ solid_infill_speed = 200 top_solid_infill_speed = 50 [print:0.10mm DETAIL 0.6 nozzle MK3] -inherits = *0.10mm*; *0.6nozzle* +inherits = *0.10mm*; *0.6nozzle*, *MK3* bridge_speed = 30 compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK3.*/ and nozzle_diameter[0]==0.6 external_perimeter_speed = 35 -fill_pattern = grid infill_acceleration = 1250 infill_speed = 200 max_print_speed = 200 @@ -361,11 +361,10 @@ inherits = *0.15mm*; *0.6nozzle* compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK2.*/ and nozzle_diameter[0]==0.6 [print:0.15mm OPTIMAL MK3] -inherits = *0.15mm* +inherits = *0.15mm*, *MK3* bridge_speed = 30 compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK3.*/ and nozzle_diameter[0]==0.4 external_perimeter_speed = 35 -fill_pattern = grid infill_acceleration = 1250 infill_speed = 200 max_print_speed = 200 @@ -393,11 +392,10 @@ support_material_with_sheath = 0 support_material_xy_spacing = 80% [print:0.15mm OPTIMAL 0.25 nozzle MK3] -inherits = *0.15mm*; *0.25nozzle* +inherits = *0.15mm*; *0.25nozzle*, *MK3* bridge_speed = 30 compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK3.*/ and nozzle_diameter[0]==0.25 external_perimeter_speed = 35 -fill_pattern = grid infill_acceleration = 1250 infill_speed = 200 max_print_speed = 200 @@ -419,11 +417,10 @@ top_infill_extrusion_width = 0.4 top_solid_layers = 5 [print:0.15mm OPTIMAL 0.6 nozzle MK3] -inherits = *0.15mm*; *0.6nozzle* +inherits = *0.15mm*; *0.6nozzle*, *MK3* bridge_speed = 30 compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK3.*/ and nozzle_diameter[0]==0.6 external_perimeter_speed = 35 -fill_pattern = grid infill_acceleration = 1250 infill_speed = 200 max_print_speed = 200 @@ -448,11 +445,10 @@ support_material_speed = 60 top_solid_infill_speed = 70 [print:0.20mm FAST MK3] -inherits = *0.20mm* +inherits = *0.20mm*, *MK3* bridge_speed = 30 compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK3.*/ and nozzle_diameter[0]==0.4 external_perimeter_speed = 35 -fill_pattern = grid infill_acceleration = 1250 infill_speed = 200 max_print_speed = 200 @@ -486,11 +482,10 @@ support_material_with_sheath = 0 support_material_xy_spacing = 80% [print:0.20mm FAST 0.6 nozzle MK3] -inherits = *0.20mm*; *0.6nozzle* +inherits = *0.20mm*; *0.6nozzle*, *MK3* bridge_speed = 30 compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK3.*/ and nozzle_diameter[0]==0.6 external_perimeter_speed = 35 -fill_pattern = grid infill_acceleration = 1250 infill_speed = 200 max_print_speed = 200 @@ -1129,17 +1124,17 @@ default_print_profile = 0.20mm NORMAL 0.6 nozzle [printer:Original Prusa i3 MK2.5] inherits = Original Prusa i3 MK2 printer_model = MK2.5 -start_gcode = M115 U3.2.1 ; tell printer latest fw version\nM83 ; extruder relative mode\nM104 S[first_layer_temperature] ; set extruder temp\nM140 S[first_layer_bed_temperature] ; set bed temp\nM190 S[first_layer_bed_temperature] ; wait for bed temp\nM109 S[first_layer_temperature] ; wait for extruder temp\nG28 W ; home all without mesh bed level\nG80 ; mesh bed leveling\nG1 Y-3.0 F1000.0 ; go outside print area\nG92 E0.0\nG1 X60.0 E9.0 F1000.0 ; intro line\nG1 X100.0 E12.5 F1000.0 ; intro line\nG92 E0.0 +start_gcode = M115 U3.3.1 ; tell printer latest fw version\nM83 ; extruder relative mode\nM104 S[first_layer_temperature] ; set extruder temp\nM140 S[first_layer_bed_temperature] ; set bed temp\nM190 S[first_layer_bed_temperature] ; wait for bed temp\nM109 S[first_layer_temperature] ; wait for extruder temp\nG28 W ; home all without mesh bed level\nG80 ; mesh bed leveling\nG1 Y-3.0 F1000.0 ; go outside print area\nG92 E0.0\nG1 X60.0 E9.0 F1000.0 ; intro line\nG1 X100.0 E12.5 F1000.0 ; intro line\nG92 E0.0 [printer:Original Prusa i3 MK2.5 0.25 nozzle] inherits = Original Prusa i3 MK2 0.25 nozzle printer_model = MK2.5 -start_gcode = M115 U3.2.1 ; tell printer latest fw version\nM83 ; extruder relative mode\nM104 S[first_layer_temperature] ; set extruder temp\nM140 S[first_layer_bed_temperature] ; set bed temp\nM190 S[first_layer_bed_temperature] ; wait for bed temp\nM109 S[first_layer_temperature] ; wait for extruder temp\nG28 W ; home all without mesh bed level\nG80 ; mesh bed leveling\nG1 Y-3.0 F1000.0 ; go outside print area\nG92 E0.0\nG1 X60.0 E9.0 F1000.0 ; intro line\nG1 X100.0 E12.5 F1000.0 ; intro line\nG92 E0.0 +start_gcode = M115 U3.3.1 ; tell printer latest fw version\nM83 ; extruder relative mode\nM104 S[first_layer_temperature] ; set extruder temp\nM140 S[first_layer_bed_temperature] ; set bed temp\nM190 S[first_layer_bed_temperature] ; wait for bed temp\nM109 S[first_layer_temperature] ; wait for extruder temp\nG28 W ; home all without mesh bed level\nG80 ; mesh bed leveling\nG1 Y-3.0 F1000.0 ; go outside print area\nG92 E0.0\nG1 X60.0 E9.0 F1000.0 ; intro line\nG1 X100.0 E12.5 F1000.0 ; intro line\nG92 E0.0 [printer:Original Prusa i3 MK2.5 0.6 nozzle] inherits = Original Prusa i3 MK2 0.6 nozzle printer_model = MK2.5 -start_gcode = M115 U3.2.1 ; tell printer latest fw version\nM83 ; extruder relative mode\nM104 S[first_layer_temperature] ; set extruder temp\nM140 S[first_layer_bed_temperature] ; set bed temp\nM190 S[first_layer_bed_temperature] ; wait for bed temp\nM109 S[first_layer_temperature] ; wait for extruder temp\nG28 W ; home all without mesh bed level\nG80 ; mesh bed leveling\nG1 Y-3.0 F1000.0 ; go outside print area\nG92 E0.0\nG1 X60.0 E9.0 F1000.0 ; intro line\nG1 X100.0 E12.5 F1000.0 ; intro line\nG92 E0.0 +start_gcode = M115 U3.3.1 ; tell printer latest fw version\nM83 ; extruder relative mode\nM104 S[first_layer_temperature] ; set extruder temp\nM140 S[first_layer_bed_temperature] ; set bed temp\nM190 S[first_layer_bed_temperature] ; wait for bed temp\nM109 S[first_layer_temperature] ; wait for extruder temp\nG28 W ; home all without mesh bed level\nG80 ; mesh bed leveling\nG1 Y-3.0 F1000.0 ; go outside print area\nG92 E0.0\nG1 X60.0 E9.0 F1000.0 ; intro line\nG1 X100.0 E12.5 F1000.0 ; intro line\nG92 E0.0 # XXXXXXXXXXXXXXXXX # XXX--- MK3 ---XXX @@ -1168,7 +1163,7 @@ silent_mode = 1 printer_notes = Don't remove the following keywords! These keywords are used in the "compatible printer" condition of the print and filament profiles to link the particular print and filament profiles to this printer profile.\nPRINTER_VENDOR_PRUSA3D\nPRINTER_MODEL_MK3\n retract_lift_below = 209 max_print_height = 210 -start_gcode = M115 U3.3.0 ; tell printer latest fw version\nM83 ; extruder relative mode\nM104 S[first_layer_temperature] ; set extruder temp\nM140 S[first_layer_bed_temperature] ; set bed temp\nM190 S[first_layer_bed_temperature] ; wait for bed temp\nM109 S[first_layer_temperature] ; wait for extruder temp\nG28 W ; home all without mesh bed level\nG80 ; mesh bed leveling\nG1 Y-3.0 F1000.0 ; go outside print area\nG92 E0.0\nG1 X60.0 E9.0 F1000.0 ; intro line\nG1 X100.0 E12.5 F1000.0 ; intro line\nG92 E0.0\nM221 S{if layer_height==0.05}100{else}95{endif} +start_gcode = M115 U3.3.1 ; tell printer latest fw version\nM83 ; extruder relative mode\nM104 S[first_layer_temperature] ; set extruder temp\nM140 S[first_layer_bed_temperature] ; set bed temp\nM190 S[first_layer_bed_temperature] ; wait for bed temp\nM109 S[first_layer_temperature] ; wait for extruder temp\nG28 W ; home all without mesh bed level\nG80 ; mesh bed leveling\nG1 Y-3.0 F1000.0 ; go outside print area\nG92 E0.0\nG1 X60.0 E9.0 F1000.0 ; intro line\nG1 X100.0 E12.5 F1000.0 ; intro line\nG92 E0.0\nM221 S{if layer_height==0.05}100{else}95{endif} printer_model = MK3 default_print_profile = 0.15mm OPTIMAL MK3 @@ -1203,7 +1198,7 @@ default_filament_profile = Prusa PLA MMU2 [printer:Original Prusa i3 MK3 MMU2 Single] inherits = *mm2* -start_gcode = M107\nM83 ; extruder relative mode\nM104 S[first_layer_temperature] ; set extruder temp\nM140 S[first_layer_bed_temperature] ; set bed temp\nM190 S[first_layer_bed_temperature] ; wait for bed temp\nM109 S[first_layer_temperature] ; wait for extruder temp\nG28 W ; home all without mesh bed level\nG80 ; mesh bed leveling\n\nG21 ; set units to millimeters\n\n;go outside print area\nG1 Y-3.0 F1000.0\nG1 Z0.4 F1000.0\n; select extruder\nT?\n; initial load\nG1 X55.0 E32.0 F1073.0\nG1 X5.0 E32.0 F1800.0\nG1 X55.0 E8.0 F2000.0\nG1 Z0.3 F1000.0\nG92 E0.0\nG1 X240.0 E25.0 F2200.0\nG1 Y-2.0 F1000.0\nG1 X55.0 E25 F1400.0\nG1 Z0.20 F1000.0\nG1 X5.0 E4.0 F1000.0\n\nM221 S{if layer_height<0.075}100{else}95{endif}\nG90 ; use absolute coordinates\nM83 ; use relative distances for extrusion\nG92 E0.0\n +start_gcode = M107\nM115 U3.3.1 ; tell printer latest fw version\nM83 ; extruder relative mode\nM104 S[first_layer_temperature] ; set extruder temp\nM140 S[first_layer_bed_temperature] ; set bed temp\nM190 S[first_layer_bed_temperature] ; wait for bed temp\nM109 S[first_layer_temperature] ; wait for extruder temp\nG28 W ; home all without mesh bed level\nG80 ; mesh bed leveling\n\nG21 ; set units to millimeters\n\n;go outside print area\nG1 Y-3.0 F1000.0\nG1 Z0.4 F1000.0\n; select extruder\nT?\n; initial load\nG1 X55.0 E32.0 F1073.0\nG1 X5.0 E32.0 F1800.0\nG1 X55.0 E8.0 F2000.0\nG1 Z0.3 F1000.0\nG92 E0.0\nG1 X240.0 E25.0 F2200.0\nG1 Y-2.0 F1000.0\nG1 X55.0 E25 F1400.0\nG1 Z0.20 F1000.0\nG1 X5.0 E4.0 F1000.0\n\nM221 S{if layer_height<0.075}100{else}95{endif}\nG90 ; use absolute coordinates\nM83 ; use relative distances for extrusion\nG92 E0.0\n end_gcode = G1 X0 Y210 F7200\nG1 E2 F5000\nG1 E2 F5500\nG1 E2 F6000\nG1 E-15.0000 F5800\nG1 E-20.0000 F5500\nG1 E10.0000 F3000\nG1 E-10.0000 F3100\nG1 E10.0000 F3150\nG1 E-10.0000 F3250\nG1 E10.0000 F3300\n\nM702 C\n\nG4 ; wait\nM104 S0 ; turn off temperature\nM140 S0 ; turn off heatbed\nM107 ; turn off fan\nG1 X0 Y200; home X axis\nM84 ; disable motors [printer:Original Prusa i3 MK3 MMU2] @@ -1213,7 +1208,7 @@ inherits = *mm2* # to be defined explicitely. nozzle_diameter = 0.4,0.4,0.4,0.4,0.4 extruder_colour = #FF8000;#0080FF;#00FFFF;#FF4F4F;#9FFF9F -start_gcode = M107\n\nM83 ; extruder relative mode\nM104 S[first_layer_temperature] ; set extruder temp\nM140 S[first_layer_bed_temperature] ; set bed temp\nM190 S[first_layer_bed_temperature] ; wait for bed temp\nM109 S[first_layer_temperature] ; wait for extruder temp\nG28 W ; home all without mesh bed level\nG80 ; mesh bed leveling\nG21 ; set units to millimeters\n\n; Send the filament type to the MMU2.0 unit.\n; E stands for extruder number, F stands for filament type (0: default; 1:flex; 2: PVA)\nM403 E0 F{"" + ((filament_type[0]=="FLEX") ? 1 : ((filament_type[0]=="PVA") ? 2 : 0))}\nM403 E1 F{"" + ((filament_type[1]=="FLEX") ? 1 : ((filament_type[1]=="PVA") ? 2 : 0))}\nM403 E2 F{"" + ((filament_type[2]=="FLEX") ? 1 : ((filament_type[2]=="PVA") ? 2 : 0))}\nM403 E3 F{"" + ((filament_type[3]=="FLEX") ? 1 : ((filament_type[3]=="PVA") ? 2 : 0))}\nM403 E4 F{"" + ((filament_type[4]=="FLEX") ? 1 : ((filament_type[4]=="PVA") ? 2 : 0))}\n\n{if not has_single_extruder_multi_material_priming}\n;go outside print area\nG1 Y-3.0 F1000.0\nG1 Z0.4 F1000.0\n; select extruder\nT[initial_tool]\n; initial load\nG1 X55.0 E32.0 F1073.0\nG1 X5.0 E32.0 F1800.0\nG1 X55.0 E8.0 F2000.0\nG1 Z0.3 F1000.0\nG92 E0.0\nG1 X240.0 E25.0 F2200.0\nG1 Y-2.0 F1000.0\nG1 X55.0 E25 F1400.0\nG1 Z0.20 F1000.0\nG1 X5.0 E4.0 F1000.0\nG92 E0.0\n{endif}\n\n;M221 S{if layer_height<0.075}100{else}95{endif}\nG90 ; use absolute coordinates\nM83 ; use relative distances for extrusion\nG92 E0.0\n +start_gcode = M107\nM115 U3.3.1 ; tell printer latest fw version\nM83 ; extruder relative mode\nM104 S[first_layer_temperature] ; set extruder temp\nM140 S[first_layer_bed_temperature] ; set bed temp\nM190 S[first_layer_bed_temperature] ; wait for bed temp\nM109 S[first_layer_temperature] ; wait for extruder temp\nG28 W ; home all without mesh bed level\nG80 ; mesh bed leveling\nG21 ; set units to millimeters\n\n; Send the filament type to the MMU2.0 unit.\n; E stands for extruder number, F stands for filament type (0: default; 1:flex; 2: PVA)\nM403 E0 F{"" + ((filament_type[0]=="FLEX") ? 1 : ((filament_type[0]=="PVA") ? 2 : 0))}\nM403 E1 F{"" + ((filament_type[1]=="FLEX") ? 1 : ((filament_type[1]=="PVA") ? 2 : 0))}\nM403 E2 F{"" + ((filament_type[2]=="FLEX") ? 1 : ((filament_type[2]=="PVA") ? 2 : 0))}\nM403 E3 F{"" + ((filament_type[3]=="FLEX") ? 1 : ((filament_type[3]=="PVA") ? 2 : 0))}\nM403 E4 F{"" + ((filament_type[4]=="FLEX") ? 1 : ((filament_type[4]=="PVA") ? 2 : 0))}\n\n{if not has_single_extruder_multi_material_priming}\n;go outside print area\nG1 Y-3.0 F1000.0\nG1 Z0.4 F1000.0\n; select extruder\nT[initial_tool]\n; initial load\nG1 X55.0 E32.0 F1073.0\nG1 X5.0 E32.0 F1800.0\nG1 X55.0 E8.0 F2000.0\nG1 Z0.3 F1000.0\nG92 E0.0\nG1 X240.0 E25.0 F2200.0\nG1 Y-2.0 F1000.0\nG1 X55.0 E25 F1400.0\nG1 Z0.20 F1000.0\nG1 X5.0 E4.0 F1000.0\nG92 E0.0\n{endif}\n\n;M221 S{if layer_height<0.075}100{else}95{endif}\nG90 ; use absolute coordinates\nM83 ; use relative distances for extrusion\nG92 E0.0\n end_gcode = {if has_wipe_tower}\nG1 E-15.0000 F3000\n{else}\nG1 X0 Y210 F7200\nG1 E2 F5000\nG1 E2 F5500\nG1 E2 F6000\nG1 E-15.0000 F5800\nG1 E-20.0000 F5500\nG1 E10.0000 F3000\nG1 E-10.0000 F3100\nG1 E10.0000 F3150\nG1 E-10.0000 F3250\nG1 E10.0000 F3300\n{endif}\n\n; Unload filament\nM702 C\n\nG4 ; wait\nM104 S0 ; turn off temperature\nM140 S0 ; turn off heatbed\nM107 ; turn off fan\n; Lift print head a bit\n{if layer_z < max_print_height}G1 Z{z_offset+min(layer_z+30, max_print_height)}{endif} ; Move print head up\nG1 X0 Y200; home X axis\nM84 ; disable motors\n # The obsolete presets will be removed when upgrading from the legacy configuration structure (up to Slic3r 1.39.2) to 1.40.0 and newer. From d5f042b4b82d46592739e19bc20408622258458e Mon Sep 17 00:00:00 2001 From: Lukas Matena Date: Fri, 27 Jul 2018 15:56:27 +0200 Subject: [PATCH 18/29] Wipe tower postprocessing, wipe tower block on 3D plate improved. - it renders red with one egde as indeterminate, the front edge is where the wipe tower will start - changing width changes depth of the block (as requested) - the block shows the brim of the wipe tower - after slicing, the block is rendered in usual dark green and takes the exact shape of the tower (also with brim) - moving or rotationg the block after slicing does not invalidate the wipe tower (and hence the exact block dimensions are preserved) - changing anything that invalidates the wipe tower reverts the block back to the "indeterminate" shape - the block is not shown after slicing, if the wipe tower is not actually generated (printing single color object with the wipe tower enabled) This required changes in the wipe tower generator, which now generates the tower at origin with no rotation. Resulting gcode is postprocessed and transformed during gcode export. This means the wipe tower needs not be invalidated when it is moved or rotated. --- lib/Slic3r/GUI/Plater.pm | 8 +++ xs/src/libslic3r/GCode.cpp | 77 +++++++++++++++++++-- xs/src/libslic3r/GCode.hpp | 12 +++- xs/src/libslic3r/GCode/PrintExtents.cpp | 10 +++ xs/src/libslic3r/GCode/WipeTower.hpp | 29 ++++++-- xs/src/libslic3r/GCode/WipeTowerPrusaMM.cpp | 66 +++++++++--------- xs/src/libslic3r/GCode/WipeTowerPrusaMM.hpp | 3 + xs/src/libslic3r/Print.cpp | 20 ++++-- xs/src/libslic3r/Print.hpp | 4 ++ xs/src/libslic3r/PrintObject.cpp | 4 +- xs/src/slic3r/GUI/3DScene.cpp | 62 ++++++++++++++--- xs/src/slic3r/GUI/3DScene.hpp | 2 +- xs/src/slic3r/GUI/GLCanvas3D.cpp | 42 ++++++++--- xs/xsp/GUI_3DScene.xsp | 2 +- 14 files changed, 264 insertions(+), 77 deletions(-) diff --git a/lib/Slic3r/GUI/Plater.pm b/lib/Slic3r/GUI/Plater.pm index c0718c77b..719f98a48 100644 --- a/lib/Slic3r/GUI/Plater.pm +++ b/lib/Slic3r/GUI/Plater.pm @@ -1281,6 +1281,11 @@ sub async_apply_config { $self->{gcode_preview_data}->reset; $self->{toolpaths2D}->reload_print if $self->{toolpaths2D}; $self->{preview3D}->reload_print if $self->{preview3D}; + + # We also need to reload 3D scene because of the wipe tower preview box + if ($self->{config}->wipe_tower) { + Slic3r::GUI::_3DScene::reload_scene($self->{canvas3D}, 1) if $self->{canvas3D} + } } } @@ -1493,6 +1498,9 @@ sub on_process_completed { return if $error; $self->{toolpaths2D}->reload_print if $self->{toolpaths2D}; $self->{preview3D}->reload_print if $self->{preview3D}; + + # in case this was MM print, wipe tower bounding box on 3D tab might need redrawing with exact depth: + Slic3r::GUI::_3DScene::reload_scene($self->{canvas3D}, 1); # if we have an export filename, start a new thread for exporting G-code if ($self->{export_gcode_output_file}) { diff --git a/xs/src/libslic3r/GCode.cpp b/xs/src/libslic3r/GCode.cpp index 94634f4e4..290872151 100644 --- a/xs/src/libslic3r/GCode.cpp +++ b/xs/src/libslic3r/GCode.cpp @@ -167,6 +167,18 @@ std::string WipeTowerIntegration::append_tcr(GCode &gcodegen, const WipeTower::T { std::string gcode; + // Toolchangeresult.gcode assumes the wipe tower corner is at the origin + // We want to rotate and shift all extrusions (gcode postprocessing) and starting and ending position + float alpha = m_wipe_tower_rotation/180.f * M_PI; + WipeTower::xy start_pos = tcr.start_pos; + WipeTower::xy end_pos = tcr.end_pos; + start_pos.rotate(alpha); + start_pos.translate(m_wipe_tower_pos); + end_pos.rotate(alpha); + end_pos.translate(m_wipe_tower_pos); + std::string tcr_rotated_gcode = rotate_wipe_tower_moves(tcr.gcode, tcr.start_pos, m_wipe_tower_pos, alpha); + + // Disable linear advance for the wipe tower operations. gcode += "M900 K0\n"; // Move over the wipe tower. @@ -174,14 +186,14 @@ std::string WipeTowerIntegration::append_tcr(GCode &gcodegen, const WipeTower::T gcode += gcodegen.retract(true); gcodegen.m_avoid_crossing_perimeters.use_external_mp_once = true; gcode += gcodegen.travel_to( - wipe_tower_point_to_object_point(gcodegen, tcr.start_pos), + wipe_tower_point_to_object_point(gcodegen, start_pos), erMixed, "Travel to a Wipe Tower"); gcode += gcodegen.unretract(); // Let the tool change be executed by the wipe tower class. // Inform the G-code writer about the changes done behind its back. - gcode += tcr.gcode; + gcode += tcr_rotated_gcode; // Let the m_writer know the current extruder_id, but ignore the generated G-code. if (new_extruder_id >= 0 && gcodegen.writer().need_toolchange(new_extruder_id)) gcodegen.writer().toolchange(new_extruder_id); @@ -195,18 +207,18 @@ std::string WipeTowerIntegration::append_tcr(GCode &gcodegen, const WipeTower::T check_add_eol(gcode); } // A phony move to the end position at the wipe tower. - gcodegen.writer().travel_to_xy(Pointf(tcr.end_pos.x, tcr.end_pos.y)); - gcodegen.set_last_pos(wipe_tower_point_to_object_point(gcodegen, tcr.end_pos)); + gcodegen.writer().travel_to_xy(Pointf(end_pos.x, end_pos.y)); + gcodegen.set_last_pos(wipe_tower_point_to_object_point(gcodegen, end_pos)); // Prepare a future wipe. gcodegen.m_wipe.path.points.clear(); if (new_extruder_id >= 0) { // Start the wipe at the current position. - gcodegen.m_wipe.path.points.emplace_back(wipe_tower_point_to_object_point(gcodegen, tcr.end_pos)); + gcodegen.m_wipe.path.points.emplace_back(wipe_tower_point_to_object_point(gcodegen, end_pos)); // Wipe end point: Wipe direction away from the closer tower edge to the further tower edge. gcodegen.m_wipe.path.points.emplace_back(wipe_tower_point_to_object_point(gcodegen, - WipeTower::xy((std::abs(m_left - tcr.end_pos.x) < std::abs(m_right - tcr.end_pos.x)) ? m_right : m_left, - tcr.end_pos.y))); + WipeTower::xy((std::abs(m_left - end_pos.x) < std::abs(m_right - end_pos.x)) ? m_right : m_left, + end_pos.y))); } // Let the planner know we are traveling between objects. @@ -214,6 +226,57 @@ std::string WipeTowerIntegration::append_tcr(GCode &gcodegen, const WipeTower::T return gcode; } +// This function postprocesses gcode_original, rotates and moves all G1 extrusions and returns resulting gcode +// Starting position has to be supplied explicitely (otherwise it would fail in case first G1 command only contained one coordinate) +std::string WipeTowerIntegration::rotate_wipe_tower_moves(const std::string& gcode_original, const WipeTower::xy& start_pos, const WipeTower::xy& translation, float angle) const +{ + std::istringstream gcode_str(gcode_original); + std::string gcode_out; + std::string line; + WipeTower::xy pos = start_pos; + WipeTower::xy transformed_pos; + WipeTower::xy old_pos(-1000.1f, -1000.1f); + + while (gcode_str) { + std::getline(gcode_str, line); // we read the gcode line by line + if (line.find("G1 ") == 0) { + std::ostringstream line_out; + std::istringstream line_str(line); + line_str >> std::noskipws; // don't skip whitespace + char ch = 0; + while (line_str >> ch) { + if (ch == 'X') + line_str >> pos.x; + else + if (ch == 'Y') + line_str >> pos.y; + else + line_out << ch; + } + + transformed_pos = pos; + transformed_pos.rotate(angle); + transformed_pos.translate(translation); + + if (transformed_pos != old_pos) { + line = line_out.str(); + char buf[2048] = "G1"; + if (transformed_pos.x != old_pos.x) + sprintf(buf + strlen(buf), " X%.3f", transformed_pos.x); + if (transformed_pos.y != old_pos.y) + sprintf(buf + strlen(buf), " Y%.3f", transformed_pos.y); + + line.replace(line.find("G1 "), 3, buf); + old_pos = transformed_pos; + } + } + gcode_out += line + "\n"; + } + return gcode_out; +} + + + std::string WipeTowerIntegration::prime(GCode &gcodegen) { assert(m_layer_idx == 0); diff --git a/xs/src/libslic3r/GCode.hpp b/xs/src/libslic3r/GCode.hpp index 8b40385e6..4953c39fe 100644 --- a/xs/src/libslic3r/GCode.hpp +++ b/xs/src/libslic3r/GCode.hpp @@ -83,8 +83,10 @@ public: const WipeTower::ToolChangeResult &priming, const std::vector> &tool_changes, const WipeTower::ToolChangeResult &final_purge) : - m_left(float(print_config.wipe_tower_x.value)), - m_right(float(print_config.wipe_tower_x.value + print_config.wipe_tower_width.value)), + m_left(/*float(print_config.wipe_tower_x.value)*/ 0.f), + m_right(float(/*print_config.wipe_tower_x.value +*/ print_config.wipe_tower_width.value)), + m_wipe_tower_pos(float(print_config.wipe_tower_x.value), float(print_config.wipe_tower_y.value)), + m_wipe_tower_rotation(float(print_config.wipe_tower_rotation_angle)), m_priming(priming), m_tool_changes(tool_changes), m_final_purge(final_purge), @@ -101,9 +103,14 @@ private: WipeTowerIntegration& operator=(const WipeTowerIntegration&); std::string append_tcr(GCode &gcodegen, const WipeTower::ToolChangeResult &tcr, int new_extruder_id) const; + // Postprocesses gcode: rotates and moves all G1 extrusions and returns result + std::string rotate_wipe_tower_moves(const std::string& gcode_original, const WipeTower::xy& start_pos, const WipeTower::xy& translation, float angle) const; + // Left / right edges of the wipe tower, for the planning of wipe moves. const float m_left; const float m_right; + const WipeTower::xy m_wipe_tower_pos; + const float m_wipe_tower_rotation; // Reference to cached values at the Printer class. const WipeTower::ToolChangeResult &m_priming; const std::vector> &m_tool_changes; @@ -112,6 +119,7 @@ private: int m_layer_idx; int m_tool_change_idx; bool m_brim_done; + bool i_have_brim = false; }; class GCode { diff --git a/xs/src/libslic3r/GCode/PrintExtents.cpp b/xs/src/libslic3r/GCode/PrintExtents.cpp index 3c3f0f8d5..37b79f343 100644 --- a/xs/src/libslic3r/GCode/PrintExtents.cpp +++ b/xs/src/libslic3r/GCode/PrintExtents.cpp @@ -134,6 +134,11 @@ BoundingBoxf get_print_object_extrusions_extents(const PrintObject &print_object // The projection does not contain the priming regions. BoundingBoxf get_wipe_tower_extrusions_extents(const Print &print, const coordf_t max_print_z) { + // Wipe tower extrusions are saved as if the tower was at the origin with no rotation + // We need to get position and angle of the wipe tower to transform them to actual position. + Pointf wipe_tower_pos(print.config.wipe_tower_x.value, print.config.wipe_tower_y.value); + float wipe_tower_angle = print.config.wipe_tower_rotation_angle.value; + BoundingBoxf bbox; for (const std::vector &tool_changes : print.m_wipe_tower_tool_changes) { if (! tool_changes.empty() && tool_changes.front().print_z > max_print_z) @@ -144,6 +149,11 @@ BoundingBoxf get_wipe_tower_extrusions_extents(const Print &print, const coordf_ if (e.width > 0) { Pointf p1((&e - 1)->pos.x, (&e - 1)->pos.y); Pointf p2(e.pos.x, e.pos.y); + p1.rotate(wipe_tower_angle); + p1.translate(wipe_tower_pos); + p2.rotate(wipe_tower_angle); + p2.translate(wipe_tower_pos); + bbox.merge(p1); coordf_t radius = 0.5 * e.width; bbox.min.x = std::min(bbox.min.x, std::min(p1.x, p2.x) - radius); diff --git a/xs/src/libslic3r/GCode/WipeTower.hpp b/xs/src/libslic3r/GCode/WipeTower.hpp index 36cebeb84..9bf350328 100644 --- a/xs/src/libslic3r/GCode/WipeTower.hpp +++ b/xs/src/libslic3r/GCode/WipeTower.hpp @@ -25,18 +25,30 @@ public: bool operator==(const xy &rhs) const { return x == rhs.x && y == rhs.y; } bool operator!=(const xy &rhs) const { return x != rhs.x || y != rhs.y; } - // Rotate the point around given point about given angle (in degrees) - // shifts the result so that point of rotation is in the middle of the tower - xy rotate(const xy& origin, float width, float depth, float angle) const { + // Rotate the point around center of the wipe tower about given angle (in degrees) + xy rotate(float width, float depth, float angle) const { xy out(0,0); float temp_x = x - width / 2.f; float temp_y = y - depth / 2.f; angle *= M_PI/180.; - out.x += (temp_x - origin.x) * cos(angle) - (temp_y - origin.y) * sin(angle); - out.y += (temp_x - origin.x) * sin(angle) + (temp_y - origin.y) * cos(angle); - return out + origin; + out.x += temp_x * cos(angle) - temp_y * sin(angle) + width / 2.f; + out.y += temp_x * sin(angle) + temp_y * cos(angle) + depth / 2.f; + + return out; } - + + // Rotate the point around origin about given angle in degrees + void rotate(float angle) { + float temp_x = x * cos(angle) - y * sin(angle); + y = x * sin(angle) + y * cos(angle); + x = temp_x; + } + + void translate(const xy& vect) { + x += vect.x; + y += vect.y; + } + float x; float y; }; @@ -104,6 +116,9 @@ public: // This is useful not only for the print time estimation, but also for the control of layer cooling. float elapsed_time; + // Is this a priming extrusion? (If so, the wipe tower rotation & translation will not be applied later) + bool priming; + // Sum the total length of the extrusion. float total_extrusion_length_in_plane() { float e_length = 0.f; diff --git a/xs/src/libslic3r/GCode/WipeTowerPrusaMM.cpp b/xs/src/libslic3r/GCode/WipeTowerPrusaMM.cpp index f466fc4f6..3d0dba07a 100644 --- a/xs/src/libslic3r/GCode/WipeTowerPrusaMM.cpp +++ b/xs/src/libslic3r/GCode/WipeTowerPrusaMM.cpp @@ -5,7 +5,7 @@ TODO LIST 1. cooling moves - DONE 2. account for perimeter and finish_layer extrusions and subtract it from last wipe - DONE -3. priming extrusions (last wipe must clear the color) +3. priming extrusions (last wipe must clear the color) - DONE 4. Peter's wipe tower - layer's are not exactly square 5. Peter's wipe tower - variable width for higher levels 6. Peter's wipe tower - make sure it is not too sparse (apply max_bridge_distance and make last wipe longer) @@ -17,7 +17,6 @@ TODO LIST #include #include -#include #include #include #include @@ -68,8 +67,11 @@ public: return *this; } - Writer& set_initial_position(const WipeTower::xy &pos) { - m_start_pos = WipeTower::xy(pos,0.f,m_y_shift).rotate(m_wipe_tower_pos, m_wipe_tower_width, m_wipe_tower_depth, m_angle_deg); + Writer& set_initial_position(const WipeTower::xy &pos, float width = 0.f, float depth = 0.f, float internal_angle = 0.f) { + m_wipe_tower_width = width; + m_wipe_tower_depth = depth; + m_internal_angle = internal_angle; + m_start_pos = WipeTower::xy(pos,0.f,m_y_shift).rotate(m_wipe_tower_width, m_wipe_tower_depth, m_internal_angle); m_current_pos = pos; return *this; } @@ -81,9 +83,6 @@ public: Writer& set_extrusion_flow(float flow) { m_extrusion_flow = flow; return *this; } - - Writer& set_rotation(WipeTower::xy& pos, float width, float depth, float angle) - { m_wipe_tower_pos = pos; m_wipe_tower_width = width; m_wipe_tower_depth=depth; m_angle_deg = angle; return (*this); } Writer& set_y_shift(float shift) { m_current_pos.y -= shift-m_y_shift; @@ -110,7 +109,7 @@ public: float y() const { return m_current_pos.y; } const WipeTower::xy& pos() const { return m_current_pos; } const WipeTower::xy start_pos_rotated() const { return m_start_pos; } - const WipeTower::xy pos_rotated() const { return WipeTower::xy(m_current_pos,0.f,m_y_shift).rotate(m_wipe_tower_pos, m_wipe_tower_width, m_wipe_tower_depth, m_angle_deg); } + const WipeTower::xy pos_rotated() const { return WipeTower::xy(m_current_pos, 0.f, m_y_shift).rotate(m_wipe_tower_width, m_wipe_tower_depth, m_internal_angle); } float elapsed_time() const { return m_elapsed_time; } // Extrude with an explicitely provided amount of extrusion. @@ -125,9 +124,9 @@ public: double len = sqrt(dx*dx+dy*dy); - // For rotated wipe tower, transform position to printer coordinates - WipeTower::xy rotated_current_pos(WipeTower::xy(m_current_pos,0.f,m_y_shift).rotate(m_wipe_tower_pos, m_wipe_tower_width, m_wipe_tower_depth, m_angle_deg)); // this is where we are - WipeTower::xy rot(WipeTower::xy(x,y+m_y_shift).rotate(m_wipe_tower_pos, m_wipe_tower_width, m_wipe_tower_depth, m_angle_deg)); // this is where we want to go + // Now do the "internal rotation" with respect to the wipe tower center + WipeTower::xy rotated_current_pos(WipeTower::xy(m_current_pos,0.f,m_y_shift).rotate(m_wipe_tower_width, m_wipe_tower_depth, m_internal_angle)); // this is where we are + WipeTower::xy rot(WipeTower::xy(x,y+m_y_shift).rotate(m_wipe_tower_width, m_wipe_tower_depth, m_internal_angle)); // this is where we want to go if (! m_preview_suppressed && e > 0.f && len > 0.) { // Width of a squished extrusion, corrected for the roundings of the squished extrusions. @@ -147,6 +146,7 @@ public: if (std::abs(rot.y - rotated_current_pos.y) > EPSILON) m_gcode += set_format_Y(rot.y); + if (e != 0.f) m_gcode += set_format_E(e); @@ -397,9 +397,8 @@ private: std::string m_gcode; std::vector m_extrusions; float m_elapsed_time; - float m_angle_deg = 0.f; + float m_internal_angle = 0.f; float m_y_shift = 0.f; - WipeTower::xy m_wipe_tower_pos; float m_wipe_tower_width = 0.f; float m_wipe_tower_depth = 0.f; float m_last_fan_speed = 0.f; @@ -539,6 +538,7 @@ WipeTower::ToolChangeResult WipeTowerPrusaMM::prime( m_print_brim = true; ToolChangeResult result; + result.priming = true; result.print_z = this->m_z_pos; result.layer_height = this->m_layer_height; result.gcode = writer.gcode(); @@ -575,7 +575,7 @@ WipeTower::ToolChangeResult WipeTowerPrusaMM::tool_change(unsigned int tool, boo } box_coordinates cleaning_box( - m_wipe_tower_pos + xy(m_perimeter_width / 2.f, m_perimeter_width / 2.f), + xy(m_perimeter_width / 2.f, m_perimeter_width / 2.f), m_wipe_tower_width - m_perimeter_width, (tool != (unsigned int)(-1) ? /*m_layer_info->depth*/wipe_area+m_depth_traversed-0.5*m_perimeter_width : m_wipe_tower_depth-m_perimeter_width)); @@ -584,7 +584,6 @@ WipeTower::ToolChangeResult WipeTowerPrusaMM::tool_change(unsigned int tool, boo writer.set_extrusion_flow(m_extrusion_flow) .set_z(m_z_pos) .set_initial_tool(m_current_tool) - .set_rotation(m_wipe_tower_pos, m_wipe_tower_width, m_wipe_tower_depth, m_wipe_tower_rotation_angle) .set_y_shift(m_y_shift + (tool!=(unsigned int)(-1) && (m_current_shape == SHAPE_REVERSED && !m_peters_wipe_tower) ? m_layer_info->depth - m_layer_info->toolchanges_depth(): 0.f)) .append(";--------------------\n" "; CP TOOLCHANGE START\n") @@ -594,7 +593,7 @@ WipeTower::ToolChangeResult WipeTowerPrusaMM::tool_change(unsigned int tool, boo .speed_override(100); xy initial_position = cleaning_box.ld + WipeTower::xy(0.f,m_depth_traversed); - writer.set_initial_position(initial_position); + writer.set_initial_position(initial_position, m_wipe_tower_width, m_wipe_tower_depth, m_internal_rotation); // Increase the extruder driver current to allow fast ramming. writer.set_extruder_trimpot(750); @@ -616,11 +615,11 @@ WipeTower::ToolChangeResult WipeTowerPrusaMM::tool_change(unsigned int tool, boo if (last_change_in_layer) {// draw perimeter line writer.set_y_shift(m_y_shift); if (m_peters_wipe_tower) - writer.rectangle(m_wipe_tower_pos,m_layer_info->depth + 3*m_perimeter_width,m_wipe_tower_depth); + writer.rectangle(WipeTower::xy(0.f, 0.f),m_layer_info->depth + 3*m_perimeter_width,m_wipe_tower_depth); else { - writer.rectangle(m_wipe_tower_pos,m_wipe_tower_width, m_layer_info->depth + m_perimeter_width); + writer.rectangle(WipeTower::xy(0.f, 0.f),m_wipe_tower_width, m_layer_info->depth + m_perimeter_width); if (layer_finished()) { // no finish_layer will be called, we must wipe the nozzle - writer.travel(m_wipe_tower_pos.x + (writer.x()> (m_wipe_tower_pos.x + m_wipe_tower_width) / 2.f ? 0.f : m_wipe_tower_width), writer.y()); + writer.travel(writer.x()> m_wipe_tower_width / 2.f ? 0.f : m_wipe_tower_width, writer.y()); } } } @@ -634,6 +633,7 @@ WipeTower::ToolChangeResult WipeTowerPrusaMM::tool_change(unsigned int tool, boo "\n\n"); ToolChangeResult result; + result.priming = false; result.print_z = this->m_z_pos; result.layer_height = this->m_layer_height; result.gcode = writer.gcode(); @@ -647,7 +647,7 @@ WipeTower::ToolChangeResult WipeTowerPrusaMM::tool_change(unsigned int tool, boo WipeTower::ToolChangeResult WipeTowerPrusaMM::toolchange_Brim(bool sideOnly, float y_offset) { const box_coordinates wipeTower_box( - m_wipe_tower_pos, + WipeTower::xy(0.f, 0.f), m_wipe_tower_width, m_wipe_tower_depth); @@ -655,12 +655,11 @@ WipeTower::ToolChangeResult WipeTowerPrusaMM::toolchange_Brim(bool sideOnly, flo writer.set_extrusion_flow(m_extrusion_flow * 1.1f) .set_z(m_z_pos) // Let the writer know the current Z position as a base for Z-hop. .set_initial_tool(m_current_tool) - .set_rotation(m_wipe_tower_pos, m_wipe_tower_width, m_wipe_tower_depth, m_wipe_tower_rotation_angle) .append(";-------------------------------------\n" "; CP WIPE TOWER FIRST LAYER BRIM START\n"); xy initial_position = wipeTower_box.lu - xy(m_perimeter_width * 6.f, 0); - writer.set_initial_position(initial_position); + writer.set_initial_position(initial_position, m_wipe_tower_width, m_wipe_tower_depth, m_internal_rotation); writer.extrude_explicit(wipeTower_box.ld - xy(m_perimeter_width * 6.f, 0), // Prime the extruder left of the wipe tower. 1.5f * m_extrusion_flow * (wipeTower_box.lu.y - wipeTower_box.ld.y), 2400); @@ -685,6 +684,7 @@ WipeTower::ToolChangeResult WipeTowerPrusaMM::toolchange_Brim(bool sideOnly, flo m_print_brim = false; // Mark the brim as extruded ToolChangeResult result; + result.priming = false; result.print_z = this->m_z_pos; result.layer_height = this->m_layer_height; result.gcode = writer.gcode(); @@ -724,7 +724,7 @@ void WipeTowerPrusaMM::toolchange_Unload( if (m_layer_info > m_plan.begin() && m_layer_info < m_plan.end() && (m_layer_info-1!=m_plan.begin() || !m_adhesion )) { // this is y of the center of previous sparse infill border - float sparse_beginning_y = m_wipe_tower_pos.y; + float sparse_beginning_y = 0.f; if (m_current_shape == SHAPE_REVERSED) sparse_beginning_y += ((m_layer_info-1)->depth - (m_layer_info-1)->toolchanges_depth()) - ((m_layer_info)->depth-(m_layer_info)->toolchanges_depth()) ; @@ -742,7 +742,7 @@ void WipeTowerPrusaMM::toolchange_Unload( for (const auto& tch : m_layer_info->tool_changes) { // let's find this toolchange if (tch.old_tool == m_current_tool) { sum_of_depths += tch.ramming_depth; - float ramming_end_y = m_wipe_tower_pos.y + sum_of_depths; + float ramming_end_y = sum_of_depths; ramming_end_y -= (y_step/m_extra_spacing-m_perimeter_width) / 2.f; // center of final ramming line // debugging: @@ -950,7 +950,7 @@ void WipeTowerPrusaMM::toolchange_Wipe( if (m_layer_info != m_plan.end() && m_current_tool != m_layer_info->tool_changes.back().new_tool) { m_left_to_right = !m_left_to_right; writer.travel(writer.x(), writer.y() - dy) - .travel(m_wipe_tower_pos.x + (m_left_to_right ? m_wipe_tower_width : 0.f), writer.y()); + .travel(m_left_to_right ? m_wipe_tower_width : 0.f, writer.y()); } writer.set_extrusion_flow(m_extrusion_flow); // Reset the extrusion flow. @@ -969,7 +969,6 @@ WipeTower::ToolChangeResult WipeTowerPrusaMM::finish_layer() writer.set_extrusion_flow(m_extrusion_flow) .set_z(m_z_pos) .set_initial_tool(m_current_tool) - .set_rotation(m_wipe_tower_pos, m_wipe_tower_width, m_wipe_tower_depth, m_wipe_tower_rotation_angle) .set_y_shift(m_y_shift - (m_current_shape == SHAPE_REVERSED && !m_peters_wipe_tower ? m_layer_info->toolchanges_depth() : 0.f)) .append(";--------------------\n" "; CP EMPTY GRID START\n") @@ -978,14 +977,12 @@ WipeTower::ToolChangeResult WipeTowerPrusaMM::finish_layer() // Slow down on the 1st layer. float speed_factor = m_is_first_layer ? 0.5f : 1.f; float current_depth = m_layer_info->depth - m_layer_info->toolchanges_depth(); - box_coordinates fill_box(m_wipe_tower_pos + xy(m_perimeter_width, m_depth_traversed + m_perimeter_width), + box_coordinates fill_box(xy(m_perimeter_width, m_depth_traversed + m_perimeter_width), m_wipe_tower_width - 2 * m_perimeter_width, current_depth-m_perimeter_width); - if (m_left_to_right) // so there is never a diagonal travel - writer.set_initial_position(fill_box.ru); - else - writer.set_initial_position(fill_box.lu); + writer.set_initial_position((m_left_to_right ? fill_box.ru : fill_box.lu), // so there is never a diagonal travel + m_wipe_tower_width, m_wipe_tower_depth, m_internal_rotation); box_coordinates box = fill_box; for (int i=0;i<2;++i) { @@ -1044,6 +1041,7 @@ WipeTower::ToolChangeResult WipeTowerPrusaMM::finish_layer() m_depth_traversed = m_wipe_tower_depth-m_perimeter_width; ToolChangeResult result; + result.priming = false; result.print_z = this->m_z_pos; result.layer_height = this->m_layer_height; result.gcode = writer.gcode(); @@ -1165,9 +1163,9 @@ void WipeTowerPrusaMM::generate(std::vectordepth < m_wipe_tower_depth - m_perimeter_width) m_y_shift = (m_wipe_tower_depth-m_layer_info->depth-m_perimeter_width)/2.f; @@ -1188,7 +1186,7 @@ void WipeTowerPrusaMM::generate(std::vector> &result); + float get_depth() const { return m_wipe_tower_depth; } + // Switch to a next layer. @@ -189,6 +191,7 @@ private: float m_wipe_tower_width; // Width of the wipe tower. float m_wipe_tower_depth = 0.f; // Depth of the wipe tower float m_wipe_tower_rotation_angle = 0.f; // Wipe tower rotation angle in degrees (with respect to x axis) + float m_internal_rotation = 0.f; float m_y_shift = 0.f; // y shift passed to writer float m_z_pos = 0.f; // Current Z position. float m_layer_height = 0.f; // Current layer height. diff --git a/xs/src/libslic3r/Print.cpp b/xs/src/libslic3r/Print.cpp index e08ae1fc4..3f0646bc6 100644 --- a/xs/src/libslic3r/Print.cpp +++ b/xs/src/libslic3r/Print.cpp @@ -166,7 +166,10 @@ bool Print::invalidate_state_by_config_options(const std::vector steps; @@ -175,7 +178,12 @@ bool Print::invalidate_state_by_config_options(const std::vectorhas_wipe_tower()) return; + m_wipe_tower_depth = 0.f; + // Get wiping matrix to get number of extruders and convert vector to vector: std::vector wiping_matrix((this->config.wiping_volumes_matrix.values).begin(),(this->config.wiping_volumes_matrix.values).end()); // Extract purging volumes for each extruder pair: @@ -1162,7 +1169,8 @@ void Print::_make_wipe_tower() // Generate the wipe tower layers. m_wipe_tower_tool_changes.reserve(m_tool_ordering.layer_tools().size()); wipe_tower.generate(m_wipe_tower_tool_changes); - + m_wipe_tower_depth = wipe_tower.get_depth(); + // Unload the current filament over the purge tower. coordf_t layer_height = this->objects.front()->config.layer_height.value; if (m_tool_ordering.back().wipe_tower_partitions > 0) { diff --git a/xs/src/libslic3r/Print.hpp b/xs/src/libslic3r/Print.hpp index bcd61ea02..e3430ad0e 100644 --- a/xs/src/libslic3r/Print.hpp +++ b/xs/src/libslic3r/Print.hpp @@ -273,6 +273,7 @@ public: void add_model_object(ModelObject* model_object, int idx = -1); bool apply_config(DynamicPrintConfig config); + float get_wipe_tower_depth() const { return m_wipe_tower_depth; } bool has_infinite_skirt() const; bool has_skirt() const; // Returns an empty string if valid, otherwise returns an error message. @@ -326,6 +327,9 @@ private: bool invalidate_state_by_config_options(const std::vector &opt_keys); PrintRegionConfig _region_config_from_model_volume(const ModelVolume &volume); + // Depth of the wipe tower to pass to GLCanvas3D for exact bounding box: + float m_wipe_tower_depth = 0.f; + // Has the calculation been canceled? tbb::atomic m_canceled; }; diff --git a/xs/src/libslic3r/PrintObject.cpp b/xs/src/libslic3r/PrintObject.cpp index 47495dad8..7150ead59 100644 --- a/xs/src/libslic3r/PrintObject.cpp +++ b/xs/src/libslic3r/PrintObject.cpp @@ -75,6 +75,7 @@ bool PrintObject::delete_last_copy() bool PrintObject::set_copies(const Points &points) { + bool copies_num_changed = this->_copies.size() != points.size(); this->_copies = points; // order copies with a nearest neighbor search and translate them by _copies_shift @@ -93,7 +94,8 @@ bool PrintObject::set_copies(const Points &points) bool invalidated = this->_print->invalidate_step(psSkirt); invalidated |= this->_print->invalidate_step(psBrim); - invalidated |= this->_print->invalidate_step(psWipeTower); + if (copies_num_changed) + invalidated |= this->_print->invalidate_step(psWipeTower); return invalidated; } diff --git a/xs/src/slic3r/GUI/3DScene.cpp b/xs/src/slic3r/GUI/3DScene.cpp index 62659033a..c4cfa537e 100644 --- a/xs/src/slic3r/GUI/3DScene.cpp +++ b/xs/src/slic3r/GUI/3DScene.cpp @@ -643,20 +643,62 @@ std::vector GLVolumeCollection::load_object( return volumes_idx; } -int GLVolumeCollection::load_wipe_tower_preview( - int obj_idx, float pos_x, float pos_y, float width, float depth, float height, float rotation_angle, bool use_VBOs) -{ - float color[4] = { 0.5f, 0.5f, 0.0f, 0.5f }; - this->volumes.emplace_back(new GLVolume(color)); - GLVolume &v = *this->volumes.back(); +int GLVolumeCollection::load_wipe_tower_preview( + int obj_idx, float pos_x, float pos_y, float width, float depth, float height, float rotation_angle, bool use_VBOs, bool size_unknown, float brim_width) +{ + if (depth < 0.01f) + return int(this->volumes.size() - 1); if (height == 0.0f) height = 0.1f; - - auto mesh = make_cube(width, depth, height); - mesh.translate(-width / 2.f, -depth / 2.f, 0.f); Point origin_of_rotation(0.f, 0.f); - mesh.rotate(rotation_angle,&origin_of_rotation); + TriangleMesh mesh; + float color[4] = { 0.5f, 0.5f, 0.0f, 1.f }; + + // In case we don't know precise dimensions of the wipe tower yet, we'll draw the box with different color with one side jagged: + if (size_unknown) { + color[0] = 1.f; + color[1] = 0.f; + + depth = std::max(depth, 10.f); // Too narrow tower would interfere with the teeth. The estimate is not precise anyway. + float min_width = 30.f; + // We'll now create the box with jagged edge. y-coordinates of the pre-generated model are shifted so that the front + // edge has y=0 and centerline of the back edge has y=depth: + Pointf3s points; + std::vector facets; + float out_points_idx[][3] = {{0, -depth, 0}, {0, 0, 0}, {38.453, 0, 0}, {61.547, 0, 0}, {100, 0, 0}, {100, -depth, 0}, {55.7735, -10, 0}, {44.2265, 10, 0}, + {38.453, 0, 1}, {0, 0, 1}, {0, -depth, 1}, {100, -depth, 1}, {100, 0, 1}, {61.547, 0, 1}, {55.7735, -10, 1}, {44.2265, 10, 1}}; + int out_facets_idx[][3] = {{0, 1, 2}, {3, 4, 5}, {6, 5, 0}, {3, 5, 6}, {6, 2, 7}, {6, 0, 2}, {8, 9, 10}, {11, 12, 13}, {10, 11, 14}, {14, 11, 13}, {15, 8, 14}, + {8, 10, 14}, {3, 12, 4}, {3, 13, 12}, {6, 13, 3}, {6, 14, 13}, {7, 14, 6}, {7, 15, 14}, {2, 15, 7}, {2, 8, 15}, {1, 8, 2}, {1, 9, 8}, + {0, 9, 1}, {0, 10, 9}, {5, 10, 0}, {5, 11, 10}, {4, 11, 5}, {4, 12, 11}}; + for (int i=0;i<16;++i) + points.push_back(Pointf3(out_points_idx[i][0] / (100.f/min_width), out_points_idx[i][1] + depth, out_points_idx[i][2])); + for (int i=0;i<28;++i) + facets.push_back(Point3(out_facets_idx[i][0], out_facets_idx[i][1], out_facets_idx[i][2])); + TriangleMesh tooth_mesh(points, facets); + + // We have the mesh ready. It has one tooth and width of min_width. We will now append several of these together until we are close to + // the required width of the block. Than we can scale it precisely. + size_t n = std::max(1, int(width/min_width)); // How many shall be merged? + for (size_t i=0;ivolumes.emplace_back(new GLVolume(color)); + GLVolume &v = *this->volumes.back(); if (use_VBOs) v.indexed_vertex_array.load_mesh_full_shading(mesh); diff --git a/xs/src/slic3r/GUI/3DScene.hpp b/xs/src/slic3r/GUI/3DScene.hpp index 5409b9588..ca51704d8 100644 --- a/xs/src/slic3r/GUI/3DScene.hpp +++ b/xs/src/slic3r/GUI/3DScene.hpp @@ -399,7 +399,7 @@ public: bool use_VBOs); int load_wipe_tower_preview( - int obj_idx, float pos_x, float pos_y, float width, float depth, float height, float rotation_angle, bool use_VBOs); + int obj_idx, float pos_x, float pos_y, float width, float depth, float height, float rotation_angle, bool use_VBOs, bool size_unknown, float brim_width); // Render the volumes by OpenGL. void render_VBOs() const; diff --git a/xs/src/slic3r/GUI/GLCanvas3D.cpp b/xs/src/slic3r/GUI/GLCanvas3D.cpp index 722f1c112..957a3e4f2 100644 --- a/xs/src/slic3r/GUI/GLCanvas3D.cpp +++ b/xs/src/slic3r/GUI/GLCanvas3D.cpp @@ -2292,7 +2292,12 @@ void GLCanvas3D::reload_scene(bool force) float w = dynamic_cast(m_config->option("wipe_tower_width"))->value; float a = dynamic_cast(m_config->option("wipe_tower_rotation_angle"))->value; - m_volumes.load_wipe_tower_preview(1000, x, y, w, 15.0f * (float)(extruders_count - 1), (float)height, a, m_use_VBOs && m_initialized); + float depth = m_print->get_wipe_tower_depth(); + if (!m_print->state.is_done(psWipeTower)) + depth = (900.f/w) * (float)(extruders_count - 1) ; + + m_volumes.load_wipe_tower_preview(1000, x, y, w, depth, (float)height, a, m_use_VBOs && m_initialized, !m_print->state.is_done(psWipeTower), + m_print->config.nozzle_diameter.values[0] * 1.25f * 4.5f); } } @@ -2554,6 +2559,8 @@ void GLCanvas3D::load_wipe_tower_toolpaths(const std::vector& str_t { const Print *print; const std::vector *tool_colors; + WipeTower::xy wipe_tower_pos; + float wipe_tower_angle; // Number of vertices (each vertex is 6x4=24 bytes long) static const size_t alloc_size_max() { return 131072; } // 3.15MB @@ -2586,6 +2593,9 @@ void GLCanvas3D::load_wipe_tower_toolpaths(const std::vector& str_t if (m_print->m_wipe_tower_final_purge) ctxt.final.emplace_back(*m_print->m_wipe_tower_final_purge.get()); + ctxt.wipe_tower_angle = ctxt.print->config.wipe_tower_rotation_angle.value/180.f * M_PI; + ctxt.wipe_tower_pos = WipeTower::xy(ctxt.print->config.wipe_tower_x.value, ctxt.print->config.wipe_tower_y.value); + BOOST_LOG_TRIVIAL(debug) << "Loading wipe tower toolpaths in parallel - start"; //FIXME Improve the heuristics for a grain size. @@ -2644,12 +2654,25 @@ void GLCanvas3D::load_wipe_tower_toolpaths(const std::vector& str_t lines.reserve(n_lines); widths.reserve(n_lines); heights.assign(n_lines, extrusions.layer_height); + WipeTower::Extrusion e_prev = extrusions.extrusions[i-1]; + + if (!extrusions.priming) { // wipe tower extrusions describe the wipe tower at the origin with no rotation + e_prev.pos.rotate(ctxt.wipe_tower_angle); + e_prev.pos.translate(ctxt.wipe_tower_pos); + } + for (; i < j; ++i) { - const WipeTower::Extrusion &e = extrusions.extrusions[i]; + WipeTower::Extrusion e = extrusions.extrusions[i]; assert(e.width > 0.f); - const WipeTower::Extrusion &e_prev = *(&e - 1); + if (!extrusions.priming) { + e.pos.rotate(ctxt.wipe_tower_angle); + e.pos.translate(ctxt.wipe_tower_pos); + } + lines.emplace_back(Point::new_scale(e_prev.pos.x, e_prev.pos.y), Point::new_scale(e.pos.x, e.pos.y)); widths.emplace_back(e.width); + + e_prev = e; } _3DScene::thick_lines_to_verts(lines, widths, heights, lines.front().a == lines.back().b, extrusions.print_z, *vols[ctxt.volume_idx(e.tool, 0)]); @@ -3652,7 +3675,7 @@ void GLCanvas3D::_camera_tranform() const ::glMatrixMode(GL_MODELVIEW); ::glLoadIdentity(); - ::glRotatef(-m_camera.get_theta(), 1.0f, 0.0f, 0.0f); // pitch + ::glRotatef(-m_camera.get_theta(), 1.0f, 0.0f, 0.0f); // pitch ::glRotatef(m_camera.phi, 0.0f, 0.0f, 1.0f); // yaw Pointf3 neg_target = m_camera.target.negative(); @@ -4627,8 +4650,11 @@ void GLCanvas3D::_load_shells() const PrintConfig& config = m_print->config; unsigned int extruders_count = config.nozzle_diameter.size(); if ((extruders_count > 1) && config.single_extruder_multi_material && config.wipe_tower && !config.complete_objects) { - const float width_per_extruder = 15.0f; // a simple workaround after wipe_tower_per_color_wipe got obsolete - m_volumes.load_wipe_tower_preview(1000, config.wipe_tower_x, config.wipe_tower_y, config.wipe_tower_width, width_per_extruder * (extruders_count - 1), max_z, config.wipe_tower_rotation_angle, m_use_VBOs && m_initialized); + float depth = m_print->get_wipe_tower_depth(); + if (!m_print->state.is_done(psWipeTower)) + depth = (900.f/config.wipe_tower_width) * (float)(extruders_count - 1) ; + m_volumes.load_wipe_tower_preview(1000, config.wipe_tower_x, config.wipe_tower_y, config.wipe_tower_width, depth, max_z, config.wipe_tower_rotation_angle, + m_use_VBOs && m_initialized, !m_print->state.is_done(psWipeTower), m_print->config.nozzle_diameter.values[0] * 1.25f * 4.5f); } } @@ -4696,7 +4722,7 @@ void GLCanvas3D::_on_move(const std::vector& volume_idxs) if (m_model == nullptr) return; - std::set done; // prevent moving instances twice + std::set done; // prevent moving instances twice bool object_moved = false; Pointf3 wipe_tower_origin(0.0, 0.0, 0.0); for (int volume_idx : volume_idxs) @@ -4705,7 +4731,7 @@ void GLCanvas3D::_on_move(const std::vector& volume_idxs) int obj_idx = volume->object_idx(); int instance_idx = volume->instance_idx(); - // prevent moving instances twice + // prevent moving instances twice char done_id[64]; ::sprintf(done_id, "%d_%d", obj_idx, instance_idx); if (done.find(done_id) != done.end()) diff --git a/xs/xsp/GUI_3DScene.xsp b/xs/xsp/GUI_3DScene.xsp index 5c2f7df85..7c71904f3 100644 --- a/xs/xsp/GUI_3DScene.xsp +++ b/xs/xsp/GUI_3DScene.xsp @@ -89,7 +89,7 @@ std::vector load_object(ModelObject *object, int obj_idx, std::vector instance_idxs, std::string color_by, std::string select_by, std::string drag_by, bool use_VBOs); - int load_wipe_tower_preview(int obj_idx, float pos_x, float pos_y, float width, float depth, float height, float rotation_angle, bool use_VBOs); + int load_wipe_tower_preview(int obj_idx, float pos_x, float pos_y, float width, float depth, float height, float rotation_angle, bool use_VBOs, bool size_unknown, float brim_width); void erase() %code{% THIS->clear(); %}; From 76838703502be55a6cb67d72bf4990619a013ad6 Mon Sep 17 00:00:00 2001 From: Lukas Matena Date: Wed, 1 Aug 2018 15:34:33 +0200 Subject: [PATCH 19/29] New perl callback to force reloading of 3d scene after Purging volumes are changed After the changes in previous commit, the 3D scene must be reloaded after the wipe tower is invalidated. This can mostly be done on the C++ side, but reloading after Purging volumes are changed required this C++ -> Perl call --- lib/Slic3r/GUI/Plater.pm | 7 +++++++ xs/src/slic3r/GUI/GUI.cpp | 5 +++++ xs/src/slic3r/GUI/GUI.hpp | 4 ++++ xs/xsp/GUI.xsp | 9 +++++++++ 4 files changed, 25 insertions(+) diff --git a/lib/Slic3r/GUI/Plater.pm b/lib/Slic3r/GUI/Plater.pm index 719f98a48..3bfd57bb3 100644 --- a/lib/Slic3r/GUI/Plater.pm +++ b/lib/Slic3r/GUI/Plater.pm @@ -144,6 +144,11 @@ sub new { my ($angle_z) = @_; $self->rotate(rad2deg($angle_z), Z, 'absolute'); }; + + # callback to call schedule_background_process + my $on_request_update = sub { + $self->schedule_background_process; + }; # callback to update object's geometry info while using gizmos my $on_update_geometry_info = sub { @@ -202,6 +207,8 @@ sub new { Slic3r::GUI::_3DScene::register_on_viewport_changed_callback($self->{canvas3D}, sub { Slic3r::GUI::_3DScene::set_viewport_from_scene($self->{preview3D}->canvas, $self->{canvas3D}); }); } + + Slic3r::_GUI::register_on_request_update_callback($on_request_update); # Initialize 2D preview canvas $self->{canvas} = Slic3r::GUI::Plater::2D->new($self->{preview_notebook}, wxDefaultSize, $self->{objects}, $self->{model}, $self->{config}); diff --git a/xs/src/slic3r/GUI/GUI.cpp b/xs/src/slic3r/GUI/GUI.cpp index af7022f2b..8e351b05f 100644 --- a/xs/src/slic3r/GUI/GUI.cpp +++ b/xs/src/slic3r/GUI/GUI.cpp @@ -901,6 +901,7 @@ void add_frequently_changed_parameters(wxWindow* parent, wxBoxSizer* sizer, wxFl std::vector extruders = dlg.get_extruders(); (config.option("wiping_volumes_matrix"))->values = std::vector(matrix.begin(),matrix.end()); (config.option("wiping_volumes_extruders"))->values = std::vector(extruders.begin(),extruders.end()); + g_on_request_update_callback.call(); } })); return sizer; @@ -917,6 +918,10 @@ ConfigOptionsGroup* get_optgroup() return m_optgroup.get(); } +void register_on_request_update_callback(void* callback) { + if (callback != nullptr) + g_on_request_update_callback.register_callback(callback); +} wxButton* get_wiping_dialog_button() { diff --git a/xs/src/slic3r/GUI/GUI.hpp b/xs/src/slic3r/GUI/GUI.hpp index efb11b7df..c41ba2521 100644 --- a/xs/src/slic3r/GUI/GUI.hpp +++ b/xs/src/slic3r/GUI/GUI.hpp @@ -4,6 +4,7 @@ #include #include #include "Config.hpp" +#include "../../libslic3r/Utils.hpp" #include #include @@ -171,6 +172,9 @@ wxString from_u8(const std::string &str); void add_frequently_changed_parameters(wxWindow* parent, wxBoxSizer* sizer, wxFlexGridSizer* preset_sizer); +static PerlCallback g_on_request_update_callback; +void register_on_request_update_callback(void* callback); + ConfigOptionsGroup* get_optgroup(); wxButton* get_wiping_dialog_button(); diff --git a/xs/xsp/GUI.xsp b/xs/xsp/GUI.xsp index 6b05e9a67..7872abc80 100644 --- a/xs/xsp/GUI.xsp +++ b/xs/xsp/GUI.xsp @@ -104,3 +104,12 @@ void fix_model_by_win10_sdk_gui(ModelObject *model_object_src, Print *print, Mod void set_3DScene(SV *scene) %code%{ Slic3r::GUI::set_3DScene((_3DScene *)wxPli_sv_2_object(aTHX_ scene, "Slic3r::Model::3DScene") ); %}; + +%package{Slic3r::_GUI}; +%{ +void +register_on_request_update_callback(callback) + SV *callback; + CODE: + Slic3r::GUI::register_on_request_update_callback((void*)callback); +%} From cd919d986a308fe3b5520cb6a1a475110078ab5a Mon Sep 17 00:00:00 2001 From: bubnikv Date: Thu, 2 Aug 2018 13:09:53 +0200 Subject: [PATCH 20/29] Fixed the *MK3* references in Prusa3D profiles --- resources/profiles/PrusaResearch.idx | 2 ++ resources/profiles/PrusaResearch.ini | 22 +++++++++++----------- 2 files changed, 13 insertions(+), 11 deletions(-) diff --git a/resources/profiles/PrusaResearch.idx b/resources/profiles/PrusaResearch.idx index 9d1806c03..b122a13c5 100644 --- a/resources/profiles/PrusaResearch.idx +++ b/resources/profiles/PrusaResearch.idx @@ -1,4 +1,6 @@ min_slic3r_version = 1.41.0-alpha +0.2.0-alpha7 Fixed the *MK3* references +0.2.0-alpha6 0.2.0-alpha5 Bumped up firmware versions for MK2.5/MK3 to 3.3.1, disabled priming areas for MK3MMU2 0.2.0-alpha4 Extended the custom start/end G-codes of the MMU2.0 printers for no priming towers. 0.2.0-alpha3 Adjusted machine limits for time estimates, added filament density and cost diff --git a/resources/profiles/PrusaResearch.ini b/resources/profiles/PrusaResearch.ini index 63f4bfe3f..27ba179ad 100644 --- a/resources/profiles/PrusaResearch.ini +++ b/resources/profiles/PrusaResearch.ini @@ -5,7 +5,7 @@ name = Prusa Research # Configuration version of this file. Config file will only be installed, if the config_version differs. # This means, the server may force the Slic3r configuration to be downgraded. -config_version = 0.2.0-alpha5 +config_version = 0.2.0-alpha7 # Where to get the updates from? config_update_url = https://raw.githubusercontent.com/prusa3d/Slic3r-settings/master/live/PrusaResearch/ @@ -216,7 +216,7 @@ compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and infill_extrusion_width = 0.5 [print:0.05mm ULTRADETAIL MK3] -inherits = *0.05mm*, *MK3* +inherits = *0.05mm*; *MK3* compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK3.*/ and nozzle_diameter[0]==0.4 and ! single_extruder_multi_material top_infill_extrusion_width = 0.4 @@ -232,7 +232,7 @@ solid_infill_speed = 20 support_material_speed = 20 [print:0.05mm ULTRADETAIL 0.25 nozzle MK3] -inherits = *0.05mm*; *0.25nozzle*, *MK3* +inherits = *0.05mm*; *0.25nozzle*; *MK3* compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK3.*/ and nozzle_diameter[0]==0.25 and num_extruders==1 # XXXXXXXXXXXXXXXXXXXX @@ -258,7 +258,7 @@ perimeter_speed = 50 solid_infill_speed = 50 [print:0.10mm DETAIL MK3] -inherits = *0.10mm*, *MK3* +inherits = *0.10mm*; *MK3* bridge_speed = 30 compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK3.*/ and nozzle_diameter[0]==0.4 and ! single_extruder_multi_material external_perimeter_speed = 35 @@ -284,7 +284,7 @@ solid_infill_speed = 40 top_solid_infill_speed = 30 [print:0.10mm DETAIL 0.25 nozzle MK3] -inherits = *0.10mm*; *0.25nozzle*, *MK3* +inherits = *0.10mm*; *0.25nozzle*; *MK3* bridge_speed = 30 compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK3.*/ and nozzle_diameter[0]==0.25 external_perimeter_speed = 35 @@ -296,7 +296,7 @@ solid_infill_speed = 200 top_solid_infill_speed = 50 [print:0.10mm DETAIL 0.6 nozzle MK3] -inherits = *0.10mm*; *0.6nozzle*, *MK3* +inherits = *0.10mm*; *0.6nozzle*; *MK3* bridge_speed = 30 compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK3.*/ and nozzle_diameter[0]==0.6 external_perimeter_speed = 35 @@ -361,7 +361,7 @@ inherits = *0.15mm*; *0.6nozzle* compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK2.*/ and nozzle_diameter[0]==0.6 [print:0.15mm OPTIMAL MK3] -inherits = *0.15mm*, *MK3* +inherits = *0.15mm*; *MK3* bridge_speed = 30 compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK3.*/ and nozzle_diameter[0]==0.4 external_perimeter_speed = 35 @@ -392,7 +392,7 @@ support_material_with_sheath = 0 support_material_xy_spacing = 80% [print:0.15mm OPTIMAL 0.25 nozzle MK3] -inherits = *0.15mm*; *0.25nozzle*, *MK3* +inherits = *0.15mm*; *0.25nozzle*; *MK3* bridge_speed = 30 compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK3.*/ and nozzle_diameter[0]==0.25 external_perimeter_speed = 35 @@ -417,7 +417,7 @@ top_infill_extrusion_width = 0.4 top_solid_layers = 5 [print:0.15mm OPTIMAL 0.6 nozzle MK3] -inherits = *0.15mm*; *0.6nozzle*, *MK3* +inherits = *0.15mm*; *0.6nozzle*; *MK3* bridge_speed = 30 compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK3.*/ and nozzle_diameter[0]==0.6 external_perimeter_speed = 35 @@ -445,7 +445,7 @@ support_material_speed = 60 top_solid_infill_speed = 70 [print:0.20mm FAST MK3] -inherits = *0.20mm*, *MK3* +inherits = *0.20mm*; *MK3* bridge_speed = 30 compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK3.*/ and nozzle_diameter[0]==0.4 external_perimeter_speed = 35 @@ -482,7 +482,7 @@ support_material_with_sheath = 0 support_material_xy_spacing = 80% [print:0.20mm FAST 0.6 nozzle MK3] -inherits = *0.20mm*; *0.6nozzle*, *MK3* +inherits = *0.20mm*; *0.6nozzle*; *MK3* bridge_speed = 30 compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK3.*/ and nozzle_diameter[0]==0.6 external_perimeter_speed = 35 From a7ba51bd111a8a38fda5a98fc82e12145c793d35 Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Thu, 2 Aug 2018 13:15:30 +0200 Subject: [PATCH 21/29] 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 751fe864e2aa0024f763ef2a51030b618c202748 Mon Sep 17 00:00:00 2001 From: Lukas Matena Date: Thu, 2 Aug 2018 14:04:50 +0200 Subject: [PATCH 22/29] Bugfix: priming lines for MM print were shown in preview even when disabled --- xs/src/slic3r/GUI/GLCanvas3D.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/xs/src/slic3r/GUI/GLCanvas3D.cpp b/xs/src/slic3r/GUI/GLCanvas3D.cpp index f8dd94fd4..1c73a69b8 100644 --- a/xs/src/slic3r/GUI/GLCanvas3D.cpp +++ b/xs/src/slic3r/GUI/GLCanvas3D.cpp @@ -3383,7 +3383,7 @@ void GLCanvas3D::_camera_tranform() const ::glMatrixMode(GL_MODELVIEW); ::glLoadIdentity(); - ::glRotatef(-m_camera.get_theta(), 1.0f, 0.0f, 0.0f); // pitch + ::glRotatef(-m_camera.get_theta(), 1.0f, 0.0f, 0.0f); // pitch ::glRotatef(m_camera.phi, 0.0f, 0.0f, 1.0f); // yaw Pointf3 neg_target = m_camera.target.negative(); @@ -4090,7 +4090,7 @@ void GLCanvas3D::_load_wipe_tower_toolpaths(const std::vector& str_ ctxt.print = m_print; ctxt.tool_colors = tool_colors.empty() ? nullptr : &tool_colors; - if (m_print->m_wipe_tower_priming) + if (m_print->m_wipe_tower_priming && m_print->config.single_extruder_multi_material_priming) ctxt.priming.emplace_back(*m_print->m_wipe_tower_priming.get()); if (m_print->m_wipe_tower_final_purge) ctxt.final.emplace_back(*m_print->m_wipe_tower_final_purge.get()); @@ -4823,7 +4823,7 @@ void GLCanvas3D::_on_move(const std::vector& volume_idxs) if (m_model == nullptr) return; - std::set done; // prevent moving instances twice + std::set done; // prevent moving instances twice bool object_moved = false; Pointf3 wipe_tower_origin(0.0, 0.0, 0.0); for (int volume_idx : volume_idxs) @@ -4832,7 +4832,7 @@ void GLCanvas3D::_on_move(const std::vector& volume_idxs) int obj_idx = volume->object_idx(); int instance_idx = volume->instance_idx(); - // prevent moving instances twice + // prevent moving instances twice char done_id[64]; ::sprintf(done_id, "%d_%d", obj_idx, instance_idx); if (done.find(done_id) != done.end()) From cc2486104211cee5dfefe7d691281e5732a6054d Mon Sep 17 00:00:00 2001 From: Lukas Matena Date: Thu, 2 Aug 2018 15:14:12 +0200 Subject: [PATCH 23/29] Added a threshold for purging on the wipe tower (before it goes into infill/sacrificial object) --- xs/src/libslic3r/Print.cpp | 14 +++++++++++--- xs/src/libslic3r/PrintConfig.cpp | 16 ++++++++++++++-- xs/src/libslic3r/PrintConfig.hpp | 2 ++ xs/src/slic3r/GUI/Preset.cpp | 9 +++++---- xs/src/slic3r/GUI/Tab.cpp | 1 + 5 files changed, 33 insertions(+), 9 deletions(-) diff --git a/xs/src/libslic3r/Print.cpp b/xs/src/libslic3r/Print.cpp index 8c91eb192..1709703a5 100644 --- a/xs/src/libslic3r/Print.cpp +++ b/xs/src/libslic3r/Print.cpp @@ -205,6 +205,7 @@ bool Print::invalidate_state_by_config_options(const std::vectoradd("filament_cooling_initial_speed", coFloats); def->label = L("Speed of the first cooling move"); def->tooltip = L("Cooling moves are gradually accelerating beginning at this speed. "); - def->cli = "filament-cooling-initial-speed=i@"; + def->cli = "filament-cooling-initial-speed=f@"; def->sidetext = L("mm/s"); def->min = 0; def->default_value = new ConfigOptionFloats { 2.2f }; + def = this->add("filament_minimal_purge_on_wipe_tower", coFloats); + def->label = L("Minimal purge on wipe tower"); + def->tooltip = L("After a toolchange, certain amount of filament is used for purging. This " + "can end up on the wipe tower, infill or sacrificial object. If there was " + "enough infill etc. available, this could result in bad quality at the beginning " + "of purging. This is a minimum that must be wiped on the wipe tower before " + "Slic3r considers moving elsewhere. "); + def->cli = "filament-minimal-purge-on-wipe-tower=f@"; + def->sidetext = L("mm³"); + def->min = 0; + def->default_value = new ConfigOptionFloats { 5.f }; + def = this->add("filament_cooling_final_speed", coFloats); def->label = L("Speed of the last cooling move"); def->tooltip = L("Cooling moves are gradually accelerating towards this speed. "); - def->cli = "filament-cooling-final-speed=i@"; + def->cli = "filament-cooling-final-speed=f@"; def->sidetext = L("mm/s"); def->min = 0; def->default_value = new ConfigOptionFloats { 3.4f }; diff --git a/xs/src/libslic3r/PrintConfig.hpp b/xs/src/libslic3r/PrintConfig.hpp index 3848ba55b..89b39e2f4 100644 --- a/xs/src/libslic3r/PrintConfig.hpp +++ b/xs/src/libslic3r/PrintConfig.hpp @@ -532,6 +532,7 @@ public: ConfigOptionFloats filament_toolchange_delay; ConfigOptionInts filament_cooling_moves; ConfigOptionFloats filament_cooling_initial_speed; + ConfigOptionFloats filament_minimal_purge_on_wipe_tower; ConfigOptionFloats filament_cooling_final_speed; ConfigOptionStrings filament_ramming_parameters; ConfigOptionBool gcode_comments; @@ -594,6 +595,7 @@ protected: OPT_PTR(filament_toolchange_delay); OPT_PTR(filament_cooling_moves); OPT_PTR(filament_cooling_initial_speed); + OPT_PTR(filament_minimal_purge_on_wipe_tower); OPT_PTR(filament_cooling_final_speed); OPT_PTR(filament_ramming_parameters); OPT_PTR(gcode_comments); diff --git a/xs/src/slic3r/GUI/Preset.cpp b/xs/src/slic3r/GUI/Preset.cpp index de9b59a95..ebc49e870 100644 --- a/xs/src/slic3r/GUI/Preset.cpp +++ b/xs/src/slic3r/GUI/Preset.cpp @@ -314,10 +314,11 @@ const std::vector& Preset::filament_options() static std::vector s_opts { "filament_colour", "filament_diameter", "filament_type", "filament_soluble", "filament_notes", "filament_max_volumetric_speed", "extrusion_multiplier", "filament_density", "filament_cost", "filament_loading_speed", "filament_unloading_speed", "filament_toolchange_delay", - "filament_cooling_moves", "filament_cooling_initial_speed", "filament_cooling_final_speed", "filament_ramming_parameters", "temperature", - "first_layer_temperature", "bed_temperature", "first_layer_bed_temperature", "fan_always_on", "cooling", "min_fan_speed", "max_fan_speed", - "bridge_fan_speed", "disable_fan_first_layers", "fan_below_layer_time", "slowdown_below_layer_time", "min_print_speed", - "start_filament_gcode", "end_filament_gcode","compatible_printers", "compatible_printers_condition", "inherits" + "filament_cooling_moves", "filament_cooling_initial_speed", "filament_cooling_final_speed", "filament_ramming_parameters", + "filament_minimal_purge_on_wipe_tower", "temperature", "first_layer_temperature", "bed_temperature", "first_layer_bed_temperature", + "fan_always_on", "cooling", "min_fan_speed", "max_fan_speed", "bridge_fan_speed", "disable_fan_first_layers", "fan_below_layer_time", + "slowdown_below_layer_time", "min_print_speed", "start_filament_gcode", "end_filament_gcode","compatible_printers", "compatible_printers_condition", + "inherits" }; return s_opts; } diff --git a/xs/src/slic3r/GUI/Tab.cpp b/xs/src/slic3r/GUI/Tab.cpp index 70f3bf8be..5c036c1da 100644 --- a/xs/src/slic3r/GUI/Tab.cpp +++ b/xs/src/slic3r/GUI/Tab.cpp @@ -1296,6 +1296,7 @@ void TabFilament::build() optgroup->append_single_option_line("filament_cooling_moves"); optgroup->append_single_option_line("filament_cooling_initial_speed"); optgroup->append_single_option_line("filament_cooling_final_speed"); + optgroup->append_single_option_line("filament_minimal_purge_on_wipe_tower"); line = { _(L("Ramming")), "" }; line.widget = [this](wxWindow* parent){ From c8370b5408cd367026faf976149967cd589f00c8 Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Thu, 2 Aug 2018 17:51:11 +0200 Subject: [PATCH 24/29] 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 25/29] 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 26/29] 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 27/29] 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); From 2fe26bfac7c5cb1d0cf0e9aa0cf27de6be852dc8 Mon Sep 17 00:00:00 2001 From: Lukas Matena Date: Fri, 3 Aug 2018 15:36:47 +0200 Subject: [PATCH 28/29] Changed color of preliminary wipe tower block --- xs/src/slic3r/GUI/3DScene.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/xs/src/slic3r/GUI/3DScene.cpp b/xs/src/slic3r/GUI/3DScene.cpp index ed66dad62..33b3768ed 100644 --- a/xs/src/slic3r/GUI/3DScene.cpp +++ b/xs/src/slic3r/GUI/3DScene.cpp @@ -658,8 +658,8 @@ int GLVolumeCollection::load_wipe_tower_preview( // In case we don't know precise dimensions of the wipe tower yet, we'll draw the box with different color with one side jagged: if (size_unknown) { - color[0] = 1.f; - color[1] = 0.f; + color[0] = 0.9f; + color[1] = 0.6f; depth = std::max(depth, 10.f); // Too narrow tower would interfere with the teeth. The estimate is not precise anyway. float min_width = 30.f; From 0454adc19415499635b5d268c5d928f5bbe3b71e Mon Sep 17 00:00:00 2001 From: bubnikv Date: Fri, 3 Aug 2018 16:26:28 +0200 Subject: [PATCH 29/29] Added support for the upstream Marlin interpretation of the M204 code. Fix of https://github.com/prusa3d/Slic3r/issues/1089 M204 S.. T..: T is interpreted by the firmware and Slic3r time estimator the old way (as acceleration when retracting) only if an S code is found at the same line. This allows PrusaResearch to interpret the legacy G-codes generated by our older Slic3r with older Slic3r profiles. M204 P.. R.. T..: T is ignored, P is interpreted as acceleration when extruding, R is interpreted as acceleration when retracting. This will be the format the Slic3r 1.41.0 will produce from the Machine Limits page. In the future both MK3 firmware and Slic3r will likely be extended to support the separate travel acceleration. This change is in sync with the Prusa3D firmware: https://github.com/prusa3d/Prusa-Firmware/commit/dd4c4b39b4359d61a3329c54bea58df119a731c6 Slic3r will now export M204 P[machine_max_acceleration_extruding] R[machine_max_acceleration_retracting] T[machine_max_acceleration_extruding] before the custom start G-code, which will be correctly interpreted by both the new Prusa3D firmware and the Slic3r's time estimator. To support our legacy MK2 firmware before we merge the commit above, we may just insert the following line into the custom start G-code section to override the block inserted by Slic3r automatically before the custom start G-code: M204 S[machine_max_acceleration_extruding] T[machine_max_acceleration_retracting] --- xs/src/libslic3r/GCode.cpp | 5 +++-- xs/src/libslic3r/GCodeTimeEstimator.cpp | 22 ++++++++++++++++++---- 2 files changed, 21 insertions(+), 6 deletions(-) diff --git a/xs/src/libslic3r/GCode.cpp b/xs/src/libslic3r/GCode.cpp index 308b1ea04..261c8e59d 100644 --- a/xs/src/libslic3r/GCode.cpp +++ b/xs/src/libslic3r/GCode.cpp @@ -1012,9 +1012,10 @@ void GCode::print_machine_envelope(FILE *file, Print &print) int(print.config.machine_max_feedrate_y.values.front() + 0.5), int(print.config.machine_max_feedrate_z.values.front() + 0.5), int(print.config.machine_max_feedrate_e.values.front() + 0.5)); - fprintf(file, "M204 S%d T%d ; sets acceleration (S) and retract acceleration (T), mm/sec^2\n", + fprintf(file, "M204 P%d R%d T%d ; sets acceleration (P, T) and retract acceleration (R), mm/sec^2\n", int(print.config.machine_max_acceleration_extruding.values.front() + 0.5), - int(print.config.machine_max_acceleration_retracting.values.front() + 0.5)); + int(print.config.machine_max_acceleration_retracting.values.front() + 0.5), + int(print.config.machine_max_acceleration_extruding.values.front() + 0.5)); fprintf(file, "M205 X%.2lf Y%.2lf Z%.2lf E%.2lf ; sets the jerk limits, mm/sec\n", print.config.machine_max_jerk_x.values.front(), print.config.machine_max_jerk_y.values.front(), diff --git a/xs/src/libslic3r/GCodeTimeEstimator.cpp b/xs/src/libslic3r/GCodeTimeEstimator.cpp index 5543b5cc9..749aac88b 100644 --- a/xs/src/libslic3r/GCodeTimeEstimator.cpp +++ b/xs/src/libslic3r/GCodeTimeEstimator.cpp @@ -1164,11 +1164,25 @@ namespace Slic3r { { PROFILE_FUNC(); float value; - if (line.has_value('S', value)) + if (line.has_value('S', value)) { + // Legacy acceleration format. This format is used by the legacy Marlin, MK2 or MK3 firmware, + // and it is also generated by Slic3r to control acceleration per extrusion type + // (there is a separate acceleration settings in Slicer for perimeter, first layer etc). set_acceleration(value); - - if (line.has_value('T', value)) - set_retract_acceleration(value); + if (line.has_value('T', value)) + set_retract_acceleration(value); + } else { + // New acceleration format, compatible with the upstream Marlin. + if (line.has_value('P', value)) + set_acceleration(value); + if (line.has_value('R', value)) + set_retract_acceleration(value); + if (line.has_value('T', value)) { + // Interpret the T value as the travel acceleration in the new Marlin format. + //FIXME Prusa3D firmware currently does not support travel acceleration value independent from the extruding acceleration value. + // set_travel_acceleration(value); + } + } } void GCodeTimeEstimator::_processM205(const GCodeReader::GCodeLine& line)