diff --git a/src/admesh/stl.h b/src/admesh/stl.h index 117090dd8..fa0edec2b 100644 --- a/src/admesh/stl.h +++ b/src/admesh/stl.h @@ -89,33 +89,34 @@ struct stl_neighbors { }; struct stl_stats { - stl_stats() { this->reset(); } - void reset() { memset(this, 0, sizeof(stl_stats)); this->volume = -1.0; } - char header[81]; - stl_type type; - uint32_t number_of_facets; - stl_vertex max; - stl_vertex min; - stl_vertex size; - float bounding_diameter; - float shortest_edge; - float volume; - int connected_edges; - int connected_facets_1_edge; - int connected_facets_2_edge; - int connected_facets_3_edge; - int facets_w_1_bad_edge; - int facets_w_2_bad_edge; - int facets_w_3_bad_edge; - int original_num_facets; - int edges_fixed; - int degenerate_facets; - int facets_removed; - int facets_added; - int facets_reversed; - int backwards_edges; - int normals_fixed; - int number_of_parts; + stl_stats() { memset(&header, 0, 81); } + char header[81]; + stl_type type = (stl_type)0; + uint32_t number_of_facets = 0; + stl_vertex max = stl_vertex::Zero(); + stl_vertex min = stl_vertex::Zero(); + stl_vertex size = stl_vertex::Zero(); + float bounding_diameter = 0.f; + float shortest_edge = 0.f; + float volume = -1.f; + int connected_edges = 0; + int connected_facets_1_edge = 0; + int connected_facets_2_edge = 0; + int connected_facets_3_edge = 0; + int facets_w_1_bad_edge = 0; + int facets_w_2_bad_edge = 0; + int facets_w_3_bad_edge = 0; + int original_num_facets = 0; + int edges_fixed = 0; + int degenerate_facets = 0; + int facets_removed = 0; + int facets_added = 0; + int facets_reversed = 0; + int backwards_edges = 0; + int normals_fixed = 0; + int number_of_parts = 0; + + void clear() { *this = stl_stats(); } }; struct stl_file { @@ -124,7 +125,7 @@ struct stl_file { void clear() { this->facet_start.clear(); this->neighbors_start.clear(); - this->stats.reset(); + this->stats.clear(); } size_t memsize() const { diff --git a/src/admesh/stlinit.cpp b/src/admesh/stlinit.cpp index 693aad086..390fe56a4 100644 --- a/src/admesh/stlinit.cpp +++ b/src/admesh/stlinit.cpp @@ -36,6 +36,10 @@ #error "SEEK_SET not defined" #endif +#ifndef BOOST_LITTLE_ENDIAN +extern void stl_internal_reverse_quads(char *buf, size_t cnt); +#endif /* BOOST_LITTLE_ENDIAN */ + static FILE* stl_open_count_facets(stl_file *stl, const char *file) { // Open the file in binary mode first. @@ -238,10 +242,6 @@ bool stl_open(stl_file *stl, const char *file) return result; } -#ifndef BOOST_LITTLE_ENDIAN -extern void stl_internal_reverse_quads(char *buf, size_t cnt); -#endif /* BOOST_LITTLE_ENDIAN */ - void stl_allocate(stl_file *stl) { // Allocate memory for the entire .STL file. diff --git a/src/avrdude/ChangeLog b/src/avrdude/ChangeLog index 975f52317..879fbf957 100644 --- a/src/avrdude/ChangeLog +++ b/src/avrdude/ChangeLog @@ -1,3 +1,12 @@ +2018-01-17 Joerg Wunsch +(cherry-picked) + Submitted by Reinhard Max + patch #8311: Add IPv6 support to the -Pnet:host:port option + * ser_posix.c (net_open): Rewrite to use getaddrinfo() + rather than gethostbyname() + * avrdude.1: Document IPv6 feature + * doc/avrdude.texi: (Dito) + 2016-05-10 Joerg Wunsch Submitted by Hannes Jochriem: diff --git a/src/avrdude/avrdude.1 b/src/avrdude/avrdude.1 index 65fc7b1d6..47ca4edde 100644 --- a/src/avrdude/avrdude.1 +++ b/src/avrdude/avrdude.1 @@ -505,12 +505,19 @@ network connection to (TCP) on .Ar host is established. +Square brackets may be placed around +.Ar host +to improve readability, for numeric IPv6 addresses (e.g. +.Li net:[2001:db8::42]:1337 ) . The remote endpoint is assumed to be a terminal or console server that connects the network stream to a local serial port where the actual programmer has been attached to. The port is assumed to be properly configured, for example using a transparent 8-bit data connection without parity at 115200 Baud for a STK500. +.Pp +Note: The ability to handle IPv6 hostnames and addresses is limited to +Posix systems (by now). .It Fl q Disable (or quell) output of the progress bar while reading or writing to the device. Specify it a second time for even quieter operation. diff --git a/src/avrdude/configure.ac b/src/avrdude/configure.ac index a23a959f2..d14fc545f 100644 --- a/src/avrdude/configure.ac +++ b/src/avrdude/configure.ac @@ -214,7 +214,7 @@ AC_HEADER_TIME AC_CHECK_LIB([ws2_32], [puts]) # Checks for library functions. -AC_CHECK_FUNCS([memset select strcasecmp strdup strerror strncasecmp strtol strtoul gettimeofday usleep]) +AC_CHECK_FUNCS([memset select strcasecmp strdup strerror strncasecmp strtol strtoul gettimeofday usleep getaddrinfo]) AC_MSG_CHECKING([for a Win32 HID libray]) SAVED_LIBS="${LIBS}" diff --git a/src/avrdude/doc/avrdude.texi b/src/avrdude/doc/avrdude.texi index 6941389df..7062a9920 100644 --- a/src/avrdude/doc/avrdude.texi +++ b/src/avrdude/doc/avrdude.texi @@ -557,6 +557,9 @@ higher level protocol (as opposed to bit-bang style programmers), In this case, instead of trying to open a local device, a TCP network connection to (TCP) @var{port} on @var{host} is established. +Square brackets may be placed around @var{host} to improve +readability for numeric IPv6 addresses (e.g. +@code{net:[2001:db8::42]:1337}). The remote endpoint is assumed to be a terminal or console server that connects the network stream to a local serial port where the actual programmer has been attached to. @@ -564,6 +567,8 @@ The port is assumed to be properly configured, for example using a transparent 8-bit data connection without parity at 115200 Baud for a STK500. +Note: The ability to handle IPv6 hostnames and addresses is limited to +Posix systems (by now). @item -q Disable (or quell) output of the progress bar while reading or writing diff --git a/src/avrdude/ser_posix.c b/src/avrdude/ser_posix.c index 9992f78e3..dfa02f9fe 100644 --- a/src/avrdude/ser_posix.c +++ b/src/avrdude/ser_posix.c @@ -150,6 +150,7 @@ static int ser_setspeed(union filedescriptor *fd, long baud) return 0; } +#include "ac_cfg.h" // Timeout read & write variants // Additionally to the regular -1 on I/O error, they return -2 on timeout @@ -221,23 +222,35 @@ ssize_t write_timeout(int fd, const void *buf, size_t count, long timeout) static int net_open(const char *port, union filedescriptor *fdp) { - char *hstr, *pstr, *end; - unsigned int pnum; - int fd; - struct sockaddr_in sockaddr; - struct hostent *hp; +#ifdef HAVE_GETADDRINFO + char *hp, *hstr, *pstr; + int s, fd, ret = -1; + struct addrinfo hints; + struct addrinfo *result, *rp; - if ((hstr = strdup(port)) == NULL) { + if ((hstr = hp = strdup(port)) == NULL) { avrdude_message(MSG_INFO, "%s: net_open(): Out of memory!\n", progname); return -1; } - if (((pstr = strchr(hstr, ':')) == NULL) || (pstr == hstr)) { + /* + * As numeric IPv6 addresses use colons as separators, we need to + * look for the last colon here, which separates the port number or + * service name from the host or IP address. + */ + if (((pstr = strrchr(hstr, ':')) == NULL) || (pstr == hstr)) { avrdude_message(MSG_INFO, "%s: net_open(): Mangled host:port string \"%s\"\n", progname, hstr); - free(hstr); - return -1; + goto error; + } + + /* + * Remove brackets from the host part, if present. + */ + if (*hstr == '[' && *(pstr-1) == ']') { + hstr++; + *(pstr-1) = '\0'; } /* @@ -245,43 +258,49 @@ net_open(const char *port, union filedescriptor *fdp) */ *pstr++ = '\0'; - pnum = strtoul(pstr, &end, 10); + memset(&hints, 0, sizeof(hints)); + hints.ai_family = AF_UNSPEC; + hints.ai_socktype = SOCK_STREAM; + s = getaddrinfo(hstr, pstr, &hints, &result); - if ((*pstr == '\0') || (*end != '\0') || (pnum == 0) || (pnum > 65535)) { - avrdude_message(MSG_INFO, "%s: net_open(): Bad port number \"%s\"\n", - progname, pstr); - free(hstr); - return -1; + if (s != 0) { + avrdude_message(MSG_INFO, + "%s: net_open(): Cannot resolve " + "host=\"%s\", port=\"%s\": %s\n", + progname, hstr, pstr, gai_strerror(s)); + goto error; } - - if ((hp = gethostbyname(hstr)) == NULL) { - avrdude_message(MSG_INFO, "%s: net_open(): unknown host \"%s\"\n", - progname, hstr); - free(hstr); - return -1; + for (rp = result; rp != NULL; rp = rp->ai_next) { + fd = socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol); + if (fd == -1) { + /* This one failed, loop over */ + continue; + } + if (connect(fd, rp->ai_addr, rp->ai_addrlen) != -1) { + /* Success, we are connected */ + break; + } + close(fd); } - - free(hstr); - - if ((fd = socket(PF_INET, SOCK_STREAM, 0)) < 0) { - avrdude_message(MSG_INFO, "%s: net_open(): Cannot open socket: %s\n", - progname, strerror(errno)); - return -1; + if (rp == NULL) { + avrdude_message(MSG_INFO, "%s: net_open(): Cannot connect: %s\n", + progname, strerror(errno)); } - - memset(&sockaddr, 0, sizeof(struct sockaddr_in)); - sockaddr.sin_family = AF_INET; - sockaddr.sin_port = htons(pnum); - memcpy(&(sockaddr.sin_addr.s_addr), hp->h_addr, sizeof(struct in_addr)); - - if (connect(fd, (struct sockaddr *)&sockaddr, sizeof(sockaddr))) { - avrdude_message(MSG_INFO, "%s: net_open(): Connect failed: %s\n", - progname, strerror(errno)); - return -1; + else { + fdp->ifd = fd; + ret = 0; } + freeaddrinfo(result); - fdp->ifd = fd; - return 0; +error: + free(hp); + return ret; +#else + avrdude_message(MSG_INFO, + "%s: Networking is not supported on your platform.\n" + "If you need it, please open a bug report.\n", progname); + return -1; +#endif /* HAVE_GETADDRINFO */ } diff --git a/src/clipper/clipper_z.hpp b/src/clipper/clipper_z.hpp index 0f31ac11c..e5e7d48ce 100644 --- a/src/clipper/clipper_z.hpp +++ b/src/clipper/clipper_z.hpp @@ -15,4 +15,4 @@ #undef clipper_hpp #undef use_xyz -#endif clipper_z_hpp +#endif // clipper_z_hpp diff --git a/src/libnest2d/include/libnest2d/backends/clipper/geometries.hpp b/src/libnest2d/include/libnest2d/backends/clipper/geometries.hpp index 56330e15e..57da6ec12 100644 --- a/src/libnest2d/include/libnest2d/backends/clipper/geometries.hpp +++ b/src/libnest2d/include/libnest2d/backends/clipper/geometries.hpp @@ -81,17 +81,16 @@ inline void offset(PolygonImpl& sh, TCoord distance, const PolygonTag using ClipperLib::etClosedPolygon; using ClipperLib::Paths; - // If the input is not at least a triangle, we can not do this algorithm - if(sh.Contour.size() <= 3 || - std::any_of(sh.Holes.begin(), sh.Holes.end(), - [](const PathImpl& p) { return p.size() <= 3; }) - ) throw GeometryException(GeomErr::OFFSET); - - ClipperOffset offs; Paths result; - offs.AddPath(sh.Contour, jtMiter, etClosedPolygon); - offs.AddPaths(sh.Holes, jtMiter, etClosedPolygon); - offs.Execute(result, static_cast(distance)); + + try { + ClipperOffset offs; + offs.AddPath(sh.Contour, jtMiter, etClosedPolygon); + offs.AddPaths(sh.Holes, jtMiter, etClosedPolygon); + offs.Execute(result, static_cast(distance)); + } catch (ClipperLib::clipperException &) { + throw GeometryException(GeomErr::OFFSET); + } // Offsetting reverts the orientation and also removes the last vertex // so boost will not have a closed polygon. diff --git a/src/libnest2d/include/libnest2d/geometry_traits.hpp b/src/libnest2d/include/libnest2d/geometry_traits.hpp index 827e2d8ba..72e239a70 100644 --- a/src/libnest2d/include/libnest2d/geometry_traits.hpp +++ b/src/libnest2d/include/libnest2d/geometry_traits.hpp @@ -1144,7 +1144,7 @@ inline bool isInside(const TBGuest& ibb, const TBHost& box, auto minY = getY(box.minCorner()); auto maxY = getY(box.maxCorner()); - return iminX > minX && imaxX < maxX && iminY > minY && imaxY < maxY; + return iminX >= minX && imaxX <= maxX && iminY >= minY && imaxY <= maxY; } template diff --git a/src/libnest2d/include/libnest2d/placers/nfpplacer.hpp b/src/libnest2d/include/libnest2d/placers/nfpplacer.hpp index 1a341d691..686857a87 100644 --- a/src/libnest2d/include/libnest2d/placers/nfpplacer.hpp +++ b/src/libnest2d/include/libnest2d/placers/nfpplacer.hpp @@ -3,9 +3,6 @@ #include -// For caching nfps -#include - // For parallel for #include #include @@ -76,55 +73,6 @@ inline void enumerate( } -namespace __itemhash { - -using Key = size_t; - -template -Key hash(const _Item& item) { - using Point = TPoint; - using Segment = _Segment; - - static const int N = 26; - static const int M = N*N - 1; - - std::string ret; - auto& rhs = item.rawShape(); - auto& ctr = sl::contour(rhs); - auto it = ctr.begin(); - auto nx = std::next(it); - - double circ = 0; - while(nx != ctr.end()) { - Segment seg(*it++, *nx++); - Radians a = seg.angleToXaxis(); - double deg = Degrees(a); - int ms = 'A', ls = 'A'; - while(deg > N) { ms++; deg -= N; } - ls += int(deg); - ret.push_back(char(ms)); ret.push_back(char(ls)); - circ += std::sqrt(seg.template sqlength()); - } - - it = ctr.begin(); nx = std::next(it); - - while(nx != ctr.end()) { - Segment seg(*it++, *nx++); - auto l = int(M * std::sqrt(seg.template sqlength()) / circ); - int ms = 'A', ls = 'A'; - while(l > N) { ms++; l -= N; } - ls += l; - ret.push_back(char(ms)); ret.push_back(char(ls)); - } - - return std::hash()(ret); -} - -template -using Hash = std::unordered_map>; - -} - namespace placers { template @@ -529,17 +477,9 @@ class _NofitPolyPlacer: public PlacerBoilerplate<_NofitPolyPlacer; - using ItemKeys = std::vector<__itemhash::Key>; - // Norming factor for the optimization function const double norm_; - // Caching calculated nfps - __itemhash::Hash nfpcache_; - - // Storing item hash keys - ItemKeys item_keys_; - public: using Pile = nfp::Shapes; @@ -636,15 +576,12 @@ public: private: using Shapes = TMultiShape; - using ItemRef = std::reference_wrapper; - using ItemWithHash = const std::pair; - Shapes calcnfp(const ItemWithHash itsh, Lvl) + Shapes calcnfp(const Item &trsh, Lvl) { using namespace nfp; Shapes nfps(items_.size()); - const Item& trsh = itsh.first; // ///////////////////////////////////////////////////////////////////// // TODO: this is a workaround and should be solved in Item with mutexes @@ -678,12 +615,11 @@ private: template - Shapes calcnfp( const ItemWithHash itsh, Level) + Shapes calcnfp(const Item &trsh, Level) { // Function for arbitrary level of nfp implementation using namespace nfp; Shapes nfps; - const Item& trsh = itsh.first; auto& orb = trsh.transformedShape(); bool orbconvex = trsh.isContourConvex(); @@ -849,8 +785,6 @@ private: remlist.insert(remlist.end(), remaining.from, remaining.to); } - size_t itemhash = __itemhash::hash(item); - if(items_.empty()) { setInitialPosition(item); best_overfit = overfit(item.transformedShape(), bin_); @@ -875,7 +809,7 @@ private: // it is disjunct from the current merged pile placeOutsideOfBin(item); - nfps = calcnfp({item, itemhash}, Lvl()); + nfps = calcnfp(item, Lvl()); auto iv = item.referenceVertex(); @@ -1112,7 +1046,6 @@ private: if(can_pack) { ret = PackResult(item); - item_keys_.emplace_back(itemhash); } else { ret = PackResult(best_overfit); } diff --git a/src/libnest2d/include/libnest2d/selections/selection_boilerplate.hpp b/src/libnest2d/include/libnest2d/selections/selection_boilerplate.hpp index 2df9a26c3..36fec7164 100644 --- a/src/libnest2d/include/libnest2d/selections/selection_boilerplate.hpp +++ b/src/libnest2d/include/libnest2d/selections/selection_boilerplate.hpp @@ -43,7 +43,7 @@ protected: Placer p{bin}; p.configure(pcfg); - if (!p.pack(cpy)) it = c.erase(it); + if (itm.area() <= 0 || !p.pack(cpy)) it = c.erase(it); else it++; } } diff --git a/src/libnest2d/tests/test.cpp b/src/libnest2d/tests/test.cpp index 4a6691415..5ba28228a 100644 --- a/src/libnest2d/tests/test.cpp +++ b/src/libnest2d/tests/test.cpp @@ -40,7 +40,7 @@ struct NfpImpl } } -std::vector& prusaParts() { +static std::vector& prusaParts() { static std::vector ret; if(ret.empty()) { @@ -51,7 +51,7 @@ std::vector& prusaParts() { return ret; } -TEST(BasicFunctionality, Angles) +TEST(GeometryAlgorithms, Angles) { using namespace libnest2d; @@ -109,7 +109,7 @@ TEST(BasicFunctionality, Angles) } // Simple test, does not use gmock -TEST(BasicFunctionality, creationAndDestruction) +TEST(Nesting, ItemCreationAndDestruction) { using namespace libnest2d; @@ -572,26 +572,74 @@ TEST(GeometryAlgorithms, convexHull) { } -TEST(GeometryAlgorithms, NestTest) { +TEST(Nesting, NestPrusaPartsShouldFitIntoTwoBins) { + + // Get the input items and define the bin. std::vector input = prusaParts(); - - libnest2d::nest(input, Box(250000000, 210000000), [](unsigned cnt) { - std::cout << "parts left: " << cnt << std::endl; + auto bin = Box(250000000, 210000000); + + // Do the nesting. Check in each step if the remaining items are less than + // in the previous step. (Some algorithms can place more items in one step) + size_t pcount = input.size(); + libnest2d::nest(input, bin, [&pcount](unsigned cnt) { + ASSERT_TRUE(cnt < pcount); + pcount = cnt; }); - + + // Get the number of logical bins: search for the max binId... auto max_binid_it = std::max_element(input.begin(), input.end(), [](const Item &i1, const Item &i2) { return i1.binId() < i2.binId(); }); - - size_t bins = max_binid_it == input.end() ? 0 : max_binid_it->binId() + 1; - ASSERT_EQ(bins, 2u); - + auto bins = size_t(max_binid_it == input.end() ? 0 : + max_binid_it->binId() + 1); + + // For prusa parts, 2 bins should be enough... + ASSERT_LE(bins, 2u); + + // All parts should be processed by the algorithm ASSERT_TRUE( std::all_of(input.begin(), input.end(), [](const Item &itm) { return itm.binId() != BIN_ID_UNSET; })); + + // Gather the items into piles of arranged polygons... + using Pile = TMultiShape; + std::vector piles(bins); + + for (auto &itm : input) + piles[size_t(itm.binId())].emplace_back(itm.transformedShape()); + + // Now check all the piles, the bounding box of each pile should be inside + // the defined bin. + for (auto &pile : piles) { + auto bb = sl::boundingBox(pile); + ASSERT_TRUE(sl::isInside(bb, bin)); + } +} + +TEST(Nesting, NestEmptyItemShouldBeUntouched) { + auto bin = Box(250000000, 210000000); // dummy bin + + std::vector items; + items.emplace_back(Item{}); // Emplace empty item + items.emplace_back(Item{0, 200, 0}); // Emplace zero area item + + libnest2d::nest(items, bin); + + for (auto &itm : items) ASSERT_EQ(itm.binId(), BIN_ID_UNSET); +} + +TEST(Nesting, NestLargeItemShouldBeUntouched) { + auto bin = Box(250000000, 210000000); // dummy bin + + std::vector items; + items.emplace_back(Rectangle{250000001, 210000001}); // Emplace large item + + libnest2d::nest(items, bin); + + ASSERT_EQ(items.front().binId(), BIN_ID_UNSET); } namespace { @@ -966,26 +1014,20 @@ using Ratio = boost::rational; } -TEST(RotatingCalipers, MinAreaBBCClk) { - auto u = [](ClipperLib::cInt n) { return n*1000000; }; - PolygonImpl poly({ {u(0), u(0)}, {u(4), u(1)}, {u(2), u(4)}}); +//TEST(GeometryAlgorithms, MinAreaBBCClk) { +// auto u = [](ClipperLib::cInt n) { return n*1000000; }; +// PolygonImpl poly({ {u(0), u(0)}, {u(4), u(1)}, {u(2), u(4)}}); - long double arearef = refMinAreaBox(poly); - long double area = minAreaBoundingBox(poly).area(); +// long double arearef = refMinAreaBox(poly); +// long double area = minAreaBoundingBox(poly).area(); - ASSERT_LE(std::abs(area - arearef), 500e6 ); -} +// ASSERT_LE(std::abs(area - arearef), 500e6 ); +//} -TEST(RotatingCalipers, AllPrusaMinBB) { - // /size_t idx = 0; +TEST(GeometryAlgorithms, MinAreaBBWithRotatingCalipers) { long double err_epsilon = 500e6l; for(ClipperLib::Path rinput : PRINTER_PART_POLYGONS) { - // ClipperLib::Path rinput = PRINTER_PART_POLYGONS[idx]; - // rinput.pop_back(); - // std::reverse(rinput.begin(), rinput.end()); - - // PolygonImpl poly(removeCollinearPoints(rinput, 1000000)); PolygonImpl poly(rinput); long double arearef = refMinAreaBox(poly); @@ -993,8 +1035,6 @@ TEST(RotatingCalipers, AllPrusaMinBB) { long double area = cast(bb.area()); bool succ = std::abs(arearef - area) < err_epsilon; - // std::cout << idx++ << " " << (succ? "ok" : "failed") << " ref: " -// << arearef << " actual: " << area << std::endl; ASSERT_TRUE(succ); } @@ -1011,8 +1051,6 @@ TEST(RotatingCalipers, AllPrusaMinBB) { bool succ = std::abs(arearef - area) < err_epsilon; - // std::cout << idx++ << " " << (succ? "ok" : "failed") << " ref: " -// << arearef << " actual: " << area << std::endl; ASSERT_TRUE(succ); } diff --git a/src/libslic3r/Arrange.cpp b/src/libslic3r/Arrange.cpp index 52168c929..20dfd8926 100644 --- a/src/libslic3r/Arrange.cpp +++ b/src/libslic3r/Arrange.cpp @@ -618,19 +618,21 @@ void arrange(ArrangePolygons & arrangables, items.reserve(arrangables.size()); // Create Item from Arrangeable - auto process_arrangeable = - [](const ArrangePolygon &arrpoly, std::vector &outp) + auto process_arrangeable = [](const ArrangePolygon &arrpoly, + std::vector & outp) { - Polygon p = arrpoly.poly.contour; - const Vec2crd & offs = arrpoly.translation; - double rotation = arrpoly.rotation; + Polygon p = arrpoly.poly.contour; + const Vec2crd &offs = arrpoly.translation; + double rotation = arrpoly.rotation; if (p.is_counter_clockwise()) p.reverse(); clppr::Polygon clpath(Slic3rMultiPoint_to_ClipperPath(p)); - - auto firstp = clpath.Contour.front(); - clpath.Contour.emplace_back(firstp); + + if (!clpath.Contour.empty()) { + auto firstp = clpath.Contour.front(); + clpath.Contour.emplace_back(firstp); + } outp.emplace_back(std::move(clpath)); outp.back().rotation(rotation); diff --git a/src/libslic3r/CMakeLists.txt b/src/libslic3r/CMakeLists.txt index 97e0fc09b..5232be2b6 100644 --- a/src/libslic3r/CMakeLists.txt +++ b/src/libslic3r/CMakeLists.txt @@ -100,7 +100,7 @@ add_library(libslic3r STATIC Geometry.cpp Geometry.hpp Int128.hpp -# KdTree.hpp + KDTreeIndirect.hpp Layer.cpp Layer.hpp LayerRegion.cpp @@ -131,8 +131,6 @@ add_library(libslic3r STATIC PolygonTrimmer.hpp Polyline.cpp Polyline.hpp - PolylineCollection.cpp - PolylineCollection.hpp Print.cpp Print.hpp PrintBase.cpp @@ -142,6 +140,8 @@ add_library(libslic3r STATIC PrintObject.cpp PrintRegion.cpp Semver.cpp + ShortestPath.cpp + ShortestPath.hpp SLAPrint.cpp SLAPrint.hpp SLA/SLAAutoSupports.hpp diff --git a/src/libslic3r/ClipperUtils.cpp b/src/libslic3r/ClipperUtils.cpp index 4c6e542f4..c3183bd8e 100644 --- a/src/libslic3r/ClipperUtils.cpp +++ b/src/libslic3r/ClipperUtils.cpp @@ -1,5 +1,6 @@ #include "ClipperUtils.hpp" #include "Geometry.hpp" +#include "ShortestPath.hpp" // #define CLIPPER_UTILS_DEBUG @@ -671,21 +672,19 @@ void traverse_pt(ClipperLib::PolyNodes &nodes, Polygons* retval) // collect ordering points Points ordering_points; ordering_points.reserve(nodes.size()); - for (ClipperLib::PolyNodes::const_iterator it = nodes.begin(); it != nodes.end(); ++it) { - Point p((*it)->Contour.front().X, (*it)->Contour.front().Y); - ordering_points.emplace_back(p); - } + for (ClipperLib::PolyNode *pn : nodes) + ordering_points.emplace_back(Point(pn->Contour.front().X, pn->Contour.front().Y)); // perform the ordering - ClipperLib::PolyNodes ordered_nodes; - Slic3r::Geometry::chained_path_items(ordering_points, nodes, ordered_nodes); - + ClipperLib::PolyNodes ordered_nodes = chain_clipper_polynodes(ordering_points, nodes); + // push results recursively - for (ClipperLib::PolyNodes::iterator it = ordered_nodes.begin(); it != ordered_nodes.end(); ++it) { + for (ClipperLib::PolyNode *pn : ordered_nodes) { // traverse the next depth - traverse_pt((*it)->Childs, retval); - retval->emplace_back(ClipperPath_to_Slic3rPolygon((*it)->Contour)); - if ((*it)->IsHole()) retval->back().reverse(); // ccw + traverse_pt(pn->Childs, retval); + retval->emplace_back(ClipperPath_to_Slic3rPolygon(pn->Contour)); + if (pn->IsHole()) + retval->back().reverse(); // ccw } } diff --git a/src/libslic3r/Config.cpp b/src/libslic3r/Config.cpp index 577698071..9aab3a0eb 100644 --- a/src/libslic3r/Config.cpp +++ b/src/libslic3r/Config.cpp @@ -249,7 +249,7 @@ ConfigOption* ConfigOptionDef::create_default_option() const // Special case: For a DynamicConfig, convert a templated enum to a generic enum. new ConfigOptionEnumGeneric(this->enum_keys_map, this->default_value->getInt()) : this->default_value->clone(); - return this->create_empty_option(); + return this->create_empty_option(); } // Assignment of the serialization IDs is not thread safe. The Defs shall be initialized from the main thread! diff --git a/src/libslic3r/Config.hpp b/src/libslic3r/Config.hpp index c349ad3e1..334593ab5 100644 --- a/src/libslic3r/Config.hpp +++ b/src/libslic3r/Config.hpp @@ -353,7 +353,7 @@ public: bool apply_override(const ConfigOption *rhs) override { if (this->nullable()) throw std::runtime_error("Cannot override a nullable ConfigOption."); - if (rhs->type() != this->type()) + if (rhs->type() != this->type()) throw std::runtime_error("ConfigOptionVector.apply_override() applied to different types."); auto rhs_vec = static_cast*>(rhs); if (! rhs->nullable()) { @@ -461,7 +461,7 @@ public: for (const double &v : this->values) { if (&v != &this->values.front()) ss << ","; - serialize_single_value(ss, v); + serialize_single_value(ss, v); } return ss.str(); } @@ -607,7 +607,7 @@ public: for (const int &v : this->values) { if (&v != &this->values.front()) ss << ","; - serialize_single_value(ss, v); + serialize_single_value(ss, v); } return ss.str(); } diff --git a/src/libslic3r/ExtrusionEntity.hpp b/src/libslic3r/ExtrusionEntity.hpp index ce52ae152..f5ac72def 100644 --- a/src/libslic3r/ExtrusionEntity.hpp +++ b/src/libslic3r/ExtrusionEntity.hpp @@ -5,6 +5,8 @@ #include "Polygon.hpp" #include "Polyline.hpp" +#include + namespace Slic3r { class ExPolygonCollection; @@ -79,8 +81,8 @@ public: virtual ExtrusionEntity* clone_move() = 0; virtual ~ExtrusionEntity() {} virtual void reverse() = 0; - virtual Point first_point() const = 0; - virtual Point last_point() const = 0; + virtual const Point& first_point() const = 0; + virtual const Point& last_point() const = 0; // Produce a list of 2D polygons covered by the extruded paths, offsetted by the extrusion width. // Increase the offset by scaled_epsilon to achieve an overlap, so a union will produce no gaps. virtual void polygons_covered_by_width(Polygons &out, const float scaled_epsilon) const = 0; @@ -121,24 +123,26 @@ public: unsigned int extruder_id; // Id of the color, used for visualization purposes in the color printing case. unsigned int cp_color_id; + // Fan speed for the extrusion, used for visualization purposes. + float fan_speed; - ExtrusionPath(ExtrusionRole role) : mm3_per_mm(-1), width(-1), height(-1), feedrate(0.0f), extruder_id(0), cp_color_id(0), m_role(role) {} - ExtrusionPath(ExtrusionRole role, double mm3_per_mm, float width, float height) : mm3_per_mm(mm3_per_mm), width(width), height(height), feedrate(0.0f), extruder_id(0), cp_color_id(0), m_role(role) {} - ExtrusionPath(const ExtrusionPath &rhs) : polyline(rhs.polyline), mm3_per_mm(rhs.mm3_per_mm), width(rhs.width), height(rhs.height), feedrate(rhs.feedrate), extruder_id(rhs.extruder_id), cp_color_id(rhs.cp_color_id), m_role(rhs.m_role) {} - ExtrusionPath(const Polyline &polyline, const ExtrusionPath &rhs) : polyline(polyline), mm3_per_mm(rhs.mm3_per_mm), width(rhs.width), height(rhs.height), feedrate(rhs.feedrate), extruder_id(rhs.extruder_id), cp_color_id(rhs.cp_color_id), m_role(rhs.m_role) {} - ExtrusionPath(ExtrusionPath &&rhs) : polyline(std::move(rhs.polyline)), mm3_per_mm(rhs.mm3_per_mm), width(rhs.width), height(rhs.height), feedrate(rhs.feedrate), extruder_id(rhs.extruder_id), cp_color_id(rhs.cp_color_id), m_role(rhs.m_role) {} - ExtrusionPath(Polyline &&polyline, const ExtrusionPath &rhs) : polyline(std::move(polyline)), mm3_per_mm(rhs.mm3_per_mm), width(rhs.width), height(rhs.height), feedrate(rhs.feedrate), extruder_id(rhs.extruder_id), cp_color_id(rhs.cp_color_id), m_role(rhs.m_role) {} + ExtrusionPath(ExtrusionRole role) : mm3_per_mm(-1), width(-1), height(-1), feedrate(0.0f), extruder_id(0), cp_color_id(0), fan_speed(0.0f), m_role(role) {}; + ExtrusionPath(ExtrusionRole role, double mm3_per_mm, float width, float height) : mm3_per_mm(mm3_per_mm), width(width), height(height), feedrate(0.0f), extruder_id(0), cp_color_id(0), fan_speed(0.0f), m_role(role) {}; + ExtrusionPath(const ExtrusionPath& rhs) : polyline(rhs.polyline), mm3_per_mm(rhs.mm3_per_mm), width(rhs.width), height(rhs.height), feedrate(rhs.feedrate), extruder_id(rhs.extruder_id), cp_color_id(rhs.cp_color_id), fan_speed(rhs.fan_speed), m_role(rhs.m_role) {} + ExtrusionPath(ExtrusionPath&& rhs) : polyline(std::move(rhs.polyline)), mm3_per_mm(rhs.mm3_per_mm), width(rhs.width), height(rhs.height), feedrate(rhs.feedrate), extruder_id(rhs.extruder_id), cp_color_id(rhs.cp_color_id), fan_speed(rhs.fan_speed), m_role(rhs.m_role) {} + ExtrusionPath(const Polyline &polyline, const ExtrusionPath &rhs) : polyline(polyline), mm3_per_mm(rhs.mm3_per_mm), width(rhs.width), height(rhs.height), feedrate(rhs.feedrate), extruder_id(rhs.extruder_id), cp_color_id(rhs.cp_color_id), fan_speed(rhs.fan_speed), m_role(rhs.m_role) {} + ExtrusionPath(Polyline &&polyline, const ExtrusionPath &rhs) : polyline(std::move(polyline)), mm3_per_mm(rhs.mm3_per_mm), width(rhs.width), height(rhs.height), feedrate(rhs.feedrate), extruder_id(rhs.extruder_id), cp_color_id(rhs.cp_color_id), fan_speed(rhs.fan_speed), m_role(rhs.m_role) {} // ExtrusionPath(ExtrusionRole role, const Flow &flow) : m_role(role), mm3_per_mm(flow.mm3_per_mm()), width(flow.width), height(flow.height), feedrate(0.0f), extruder_id(0) {}; - ExtrusionPath& operator=(const ExtrusionPath &rhs) { m_role = rhs.m_role; this->mm3_per_mm = rhs.mm3_per_mm; this->width = rhs.width; this->height = rhs.height; this->feedrate = rhs.feedrate; this->extruder_id = rhs.extruder_id; this->cp_color_id = rhs.cp_color_id; this->polyline = rhs.polyline; return *this; } - ExtrusionPath& operator=(ExtrusionPath &&rhs) { m_role = rhs.m_role; this->mm3_per_mm = rhs.mm3_per_mm; this->width = rhs.width; this->height = rhs.height; this->feedrate = rhs.feedrate; this->extruder_id = rhs.extruder_id; this->cp_color_id = rhs.cp_color_id; this->polyline = std::move(rhs.polyline); return *this; } + ExtrusionPath& operator=(const ExtrusionPath& rhs) { m_role = rhs.m_role; this->mm3_per_mm = rhs.mm3_per_mm; this->width = rhs.width; this->height = rhs.height; this->feedrate = rhs.feedrate, this->extruder_id = rhs.extruder_id, this->cp_color_id = rhs.cp_color_id, this->fan_speed = rhs.fan_speed, this->polyline = rhs.polyline; return *this; } + ExtrusionPath& operator=(ExtrusionPath&& rhs) { m_role = rhs.m_role; this->mm3_per_mm = rhs.mm3_per_mm; this->width = rhs.width; this->height = rhs.height; this->feedrate = rhs.feedrate, this->extruder_id = rhs.extruder_id, this->cp_color_id = rhs.cp_color_id, this->fan_speed = rhs.fan_speed, this->polyline = std::move(rhs.polyline); return *this; } ExtrusionEntity* clone() const override { return new ExtrusionPath(*this); } // Create a new object, initialize it with this object using the move semantics. ExtrusionEntity* clone_move() override { return new ExtrusionPath(std::move(*this)); } void reverse() override { this->polyline.reverse(); } - Point first_point() const override { return this->polyline.points.front(); } - Point last_point() const override { return this->polyline.points.back(); } + const Point& first_point() const override { return this->polyline.points.front(); } + const Point& last_point() const override { return this->polyline.points.back(); } size_t size() const { return this->polyline.size(); } bool empty() const { return this->polyline.empty(); } bool is_closed() const { return ! this->empty() && this->polyline.points.front() == this->polyline.points.back(); } @@ -198,8 +202,8 @@ public: // Create a new object, initialize it with this object using the move semantics. ExtrusionEntity* clone_move() override { return new ExtrusionMultiPath(std::move(*this)); } void reverse() override; - Point first_point() const override { return this->paths.front().polyline.points.front(); } - Point last_point() const override { return this->paths.back().polyline.points.back(); } + const Point& first_point() const override { return this->paths.front().polyline.points.front(); } + const Point& last_point() const override { return this->paths.back().polyline.points.back(); } double length() const override; ExtrusionRole role() const override { return this->paths.empty() ? erNone : this->paths.front().role(); } // Produce a list of 2D polygons covered by the extruded paths, offsetted by the extrusion width. @@ -241,8 +245,8 @@ public: bool make_clockwise(); bool make_counter_clockwise(); void reverse() override; - Point first_point() const override { return this->paths.front().polyline.points.front(); } - Point last_point() const override { assert(first_point() == this->paths.back().polyline.points.back()); return first_point(); } + const Point& first_point() const override { return this->paths.front().polyline.points.front(); } + const Point& last_point() const override { assert(this->first_point() == this->paths.back().polyline.points.back()); return this->first_point(); } Polygon polygon() const; double length() const override; bool split_at_vertex(const Point &point); diff --git a/src/libslic3r/ExtrusionEntityCollection.cpp b/src/libslic3r/ExtrusionEntityCollection.cpp index 70c2348af..8c7e00e60 100644 --- a/src/libslic3r/ExtrusionEntityCollection.cpp +++ b/src/libslic3r/ExtrusionEntityCollection.cpp @@ -1,4 +1,5 @@ #include "ExtrusionEntityCollection.hpp" +#include "ShortestPath.hpp" #include #include #include @@ -16,7 +17,6 @@ ExtrusionEntityCollection& ExtrusionEntityCollection::operator=(const ExtrusionE this->entities = other.entities; for (size_t i = 0; i < this->entities.size(); ++i) this->entities[i] = this->entities[i]->clone(); - this->orig_indices = other.orig_indices; this->no_sort = other.no_sort; return *this; } @@ -24,7 +24,6 @@ ExtrusionEntityCollection& ExtrusionEntityCollection::operator=(const ExtrusionE void ExtrusionEntityCollection::swap(ExtrusionEntityCollection &c) { std::swap(this->entities, c.entities); - std::swap(this->orig_indices, c.orig_indices); std::swap(this->no_sort, c.no_sort); } @@ -75,79 +74,31 @@ void ExtrusionEntityCollection::remove(size_t i) this->entities.erase(this->entities.begin() + i); } -ExtrusionEntityCollection ExtrusionEntityCollection::chained_path(bool no_reverse, ExtrusionRole role) const +ExtrusionEntityCollection ExtrusionEntityCollection::chained_path_from(const Point &start_near, ExtrusionRole role) const { - ExtrusionEntityCollection coll; - this->chained_path(&coll, no_reverse, role); - return coll; -} - -void ExtrusionEntityCollection::chained_path(ExtrusionEntityCollection* retval, bool no_reverse, ExtrusionRole role, std::vector* orig_indices) const -{ - if (this->entities.empty()) return; - this->chained_path_from(this->entities.front()->first_point(), retval, no_reverse, role, orig_indices); -} - -ExtrusionEntityCollection ExtrusionEntityCollection::chained_path_from(Point start_near, bool no_reverse, ExtrusionRole role) const -{ - ExtrusionEntityCollection coll; - this->chained_path_from(start_near, &coll, no_reverse, role); - return coll; -} - -void ExtrusionEntityCollection::chained_path_from(Point start_near, ExtrusionEntityCollection* retval, bool no_reverse, ExtrusionRole role, std::vector* orig_indices) const -{ - if (this->no_sort) { - *retval = *this; - return; - } - - retval->entities.reserve(this->entities.size()); - retval->orig_indices.reserve(this->entities.size()); - - // if we're asked to return the original indices, build a map - std::map indices_map; - - ExtrusionEntitiesPtr my_paths; - for (ExtrusionEntity * const &entity_src : this->entities) { - if (role != erMixed) { - // The caller wants only paths with a specific extrusion role. - auto role2 = entity_src->role(); - if (role != role2) { - // This extrusion entity does not match the role asked. - assert(role2 != erMixed); - continue; - } - } - - ExtrusionEntity *entity = entity_src->clone(); - my_paths.push_back(entity); - if (orig_indices != nullptr) - indices_map[entity] = &entity_src - &this->entities.front(); - } - - Points endpoints; - for (const ExtrusionEntity *entity : my_paths) { - endpoints.push_back(entity->first_point()); - endpoints.push_back((no_reverse || ! entity->can_reverse()) ? - entity->first_point() : entity->last_point()); - } - - while (! my_paths.empty()) { - // find nearest point - int start_index = start_near.nearest_point_index(endpoints); - int path_index = start_index/2; - ExtrusionEntity* entity = my_paths.at(path_index); - // never reverse loops, since it's pointless for chained path and callers might depend on orientation - if (start_index % 2 && !no_reverse && entity->can_reverse()) - entity->reverse(); - retval->entities.push_back(my_paths.at(path_index)); - if (orig_indices != nullptr) - orig_indices->push_back(indices_map[entity]); - my_paths.erase(my_paths.begin() + path_index); - endpoints.erase(endpoints.begin() + 2*path_index, endpoints.begin() + 2*path_index + 2); - start_near = retval->entities.back()->last_point(); - } + ExtrusionEntityCollection out; + if (this->no_sort) { + out = *this; + } else { + if (role == erMixed) + out = *this; + else { + for (const ExtrusionEntity *ee : this->entities) { + if (role != erMixed) { + // The caller wants only paths with a specific extrusion role. + auto role2 = ee->role(); + if (role != role2) { + // This extrusion entity does not match the role asked. + assert(role2 != erMixed); + continue; + } + } + out.entities.emplace_back(ee->clone()); + } + } + chain_and_reorder_extrusion_entities(out.entities, &start_near); + } + return out; } void ExtrusionEntityCollection::polygons_covered_by_width(Polygons &out, const float scaled_epsilon) const diff --git a/src/libslic3r/ExtrusionEntityCollection.hpp b/src/libslic3r/ExtrusionEntityCollection.hpp index 221afc453..4e1491724 100644 --- a/src/libslic3r/ExtrusionEntityCollection.hpp +++ b/src/libslic3r/ExtrusionEntityCollection.hpp @@ -14,15 +14,14 @@ public: ExtrusionEntity* clone_move() override { return new ExtrusionEntityCollection(std::move(*this)); } ExtrusionEntitiesPtr entities; // we own these entities - std::vector orig_indices; // handy for XS bool no_sort; ExtrusionEntityCollection(): no_sort(false) {}; - ExtrusionEntityCollection(const ExtrusionEntityCollection &other) : orig_indices(other.orig_indices), no_sort(other.no_sort) { this->append(other.entities); } - ExtrusionEntityCollection(ExtrusionEntityCollection &&other) : entities(std::move(other.entities)), orig_indices(std::move(other.orig_indices)), no_sort(other.no_sort) {} + ExtrusionEntityCollection(const ExtrusionEntityCollection &other) : no_sort(other.no_sort) { this->append(other.entities); } + ExtrusionEntityCollection(ExtrusionEntityCollection &&other) : entities(std::move(other.entities)), no_sort(other.no_sort) {} explicit ExtrusionEntityCollection(const ExtrusionPaths &paths); ExtrusionEntityCollection& operator=(const ExtrusionEntityCollection &other); ExtrusionEntityCollection& operator=(ExtrusionEntityCollection &&other) - { this->entities = std::move(other.entities); this->orig_indices = std::move(other.orig_indices); this->no_sort = other.no_sort; return *this; } + { this->entities = std::move(other.entities); this->no_sort = other.no_sort; return *this; } ~ExtrusionEntityCollection() { clear(); } explicit operator ExtrusionPaths() const; @@ -66,13 +65,10 @@ public: } void replace(size_t i, const ExtrusionEntity &entity); void remove(size_t i); - ExtrusionEntityCollection chained_path(bool no_reverse = false, ExtrusionRole role = erMixed) const; - void chained_path(ExtrusionEntityCollection* retval, bool no_reverse = false, ExtrusionRole role = erMixed, std::vector* orig_indices = nullptr) const; - ExtrusionEntityCollection chained_path_from(Point start_near, bool no_reverse = false, ExtrusionRole role = erMixed) const; - void chained_path_from(Point start_near, ExtrusionEntityCollection* retval, bool no_reverse = false, ExtrusionRole role = erMixed, std::vector* orig_indices = nullptr) const; + ExtrusionEntityCollection chained_path_from(const Point &start_near, ExtrusionRole role = erMixed) const; void reverse(); - Point first_point() const { return this->entities.front()->first_point(); } - Point last_point() const { return this->entities.back()->last_point(); } + const Point& first_point() const { return this->entities.front()->first_point(); } + const Point& last_point() const { return this->entities.back()->last_point(); } // Produce a list of 2D polygons covered by the extruded paths, offsetted by the extrusion width. // Increase the offset by scaled_epsilon to achieve an overlap, so a union will produce no gaps. void polygons_covered_by_width(Polygons &out, const float scaled_epsilon) const override; diff --git a/src/libslic3r/Fill/Fill.cpp b/src/libslic3r/Fill/Fill.cpp index ad308adab..ab20bbddb 100644 --- a/src/libslic3r/Fill/Fill.cpp +++ b/src/libslic3r/Fill/Fill.cpp @@ -15,40 +15,39 @@ namespace Slic3r { struct SurfaceFillParams { - SurfaceFillParams() : flow(0.f, 0.f, 0.f, false) { memset(this, 0, sizeof(*this)); } // Zero based extruder ID. - unsigned int extruder; + unsigned int extruder = 0; // Infill pattern, adjusted for the density etc. - InfillPattern pattern; + InfillPattern pattern = InfillPattern(0); // FillBase // in unscaled coordinates - coordf_t spacing; + coordf_t spacing = 0.; // infill / perimeter overlap, in unscaled coordinates - coordf_t overlap; + coordf_t overlap = 0.; // Angle as provided by the region config, in radians. - float angle; + float angle = 0.f; // Non-negative for a bridge. - float bridge_angle; + float bridge_angle = 0.f; // FillParams - float density; + float density = 0.f; // Don't connect the fill lines around the inner perimeter. - bool dont_connect; + bool dont_connect = false; // Don't adjust spacing to fill the space evenly. - bool dont_adjust; + bool dont_adjust = false; // width, height of extrusion, nozzle diameter, is bridge // For the output, for fill generator. - Flow flow; + Flow flow = Flow(0.f, 0.f, 0.f, false); // For the output - ExtrusionRole extrusion_role; + ExtrusionRole extrusion_role = ExtrusionRole(0); // Various print settings? // Index of this entry in a linear vector. - size_t idx; + size_t idx = 0; bool operator<(const SurfaceFillParams &rhs) const { diff --git a/src/libslic3r/Fill/Fill3DHoneycomb.cpp b/src/libslic3r/Fill/Fill3DHoneycomb.cpp index 6a37e4369..820f0008b 100644 --- a/src/libslic3r/Fill/Fill3DHoneycomb.cpp +++ b/src/libslic3r/Fill/Fill3DHoneycomb.cpp @@ -1,5 +1,5 @@ #include "../ClipperUtils.hpp" -#include "../PolylineCollection.hpp" +#include "../ShortestPath.hpp" #include "../Surface.hpp" #include "Fill3DHoneycomb.hpp" @@ -175,27 +175,24 @@ void Fill3DHoneycomb::_fill_surface_single( std::swap(expolygon_off, expolygons_off.front()); } } - Polylines chained = PolylineCollection::chained_path_from( - std::move(polylines), - PolylineCollection::leftmost_point(polylines), false); // reverse allowed bool first = true; - for (Polylines::iterator it_polyline = chained.begin(); it_polyline != chained.end(); ++ it_polyline) { + for (Polyline &polyline : chain_polylines(std::move(polylines))) { if (! first) { // Try to connect the lines. Points &pts_end = polylines_out.back().points; - const Point &first_point = it_polyline->points.front(); + const Point &first_point = polyline.points.front(); const Point &last_point = pts_end.back(); // TODO: we should also check that both points are on a fill_boundary to avoid // connecting paths on the boundaries of internal regions if ((last_point - first_point).cast().norm() <= 1.5 * distance && expolygon_off.contains(Line(last_point, first_point))) { // Append the polyline. - pts_end.insert(pts_end.end(), it_polyline->points.begin(), it_polyline->points.end()); + pts_end.insert(pts_end.end(), polyline.points.begin(), polyline.points.end()); continue; } } // The lines cannot be connected. - polylines_out.emplace_back(std::move(*it_polyline)); + polylines_out.emplace_back(std::move(polyline)); first = false; } } diff --git a/src/libslic3r/Fill/FillGyroid.cpp b/src/libslic3r/Fill/FillGyroid.cpp index 04319bb26..7cd955892 100644 --- a/src/libslic3r/Fill/FillGyroid.cpp +++ b/src/libslic3r/Fill/FillGyroid.cpp @@ -1,5 +1,5 @@ #include "../ClipperUtils.hpp" -#include "../PolylineCollection.hpp" +#include "../ShortestPath.hpp" #include "../Surface.hpp" #include #include @@ -166,11 +166,8 @@ void FillGyroid::_fill_surface_single( std::swap(expolygon_off, expolygons_off.front()); } } - Polylines chained = PolylineCollection::chained_path_from( - std::move(polylines), - PolylineCollection::leftmost_point(polylines), false); // reverse allowed bool first = true; - for (Polyline &polyline : chained) { + for (Polyline &polyline : chain_polylines(std::move(polylines))) { if (! first) { // Try to connect the lines. Points &pts_end = polylines_out.back().points; diff --git a/src/libslic3r/Fill/FillHoneycomb.cpp b/src/libslic3r/Fill/FillHoneycomb.cpp index cbfe926f2..948af182b 100644 --- a/src/libslic3r/Fill/FillHoneycomb.cpp +++ b/src/libslic3r/Fill/FillHoneycomb.cpp @@ -1,5 +1,5 @@ #include "../ClipperUtils.hpp" -#include "../PolylineCollection.hpp" +#include "../ShortestPath.hpp" #include "../Surface.hpp" #include "FillHoneycomb.hpp" @@ -93,22 +93,20 @@ void FillHoneycomb::_fill_surface_single( // connect paths if (! paths.empty()) { // prevent calling leftmost_point() on empty collections - Polylines chained = PolylineCollection::chained_path_from( - std::move(paths), - PolylineCollection::leftmost_point(paths), false); + Polylines chained = chain_polylines(std::move(paths)); assert(paths.empty()); paths.clear(); - for (Polylines::iterator it_path = chained.begin(); it_path != chained.end(); ++ it_path) { + for (Polyline &path : chained) { if (! paths.empty()) { // distance between first point of this path and last point of last path - double distance = (it_path->first_point() - paths.back().last_point()).cast().norm(); + double distance = (path.first_point() - paths.back().last_point()).cast().norm(); if (distance <= m.hex_width) { - paths.back().points.insert(paths.back().points.end(), it_path->points.begin(), it_path->points.end()); + paths.back().points.insert(paths.back().points.end(), path.points.begin(), path.points.end()); continue; } } // Don't connect the paths. - paths.push_back(*it_path); + paths.push_back(std::move(path)); } } diff --git a/src/libslic3r/Fill/FillPlanePath.cpp b/src/libslic3r/Fill/FillPlanePath.cpp index 3b9266a0f..7a322ce99 100644 --- a/src/libslic3r/Fill/FillPlanePath.cpp +++ b/src/libslic3r/Fill/FillPlanePath.cpp @@ -1,5 +1,4 @@ #include "../ClipperUtils.hpp" -#include "../PolylineCollection.hpp" #include "../Surface.hpp" #include "FillPlanePath.hpp" diff --git a/src/libslic3r/Fill/FillRectilinear.cpp b/src/libslic3r/Fill/FillRectilinear.cpp index 205eb1b66..629e5b6f4 100644 --- a/src/libslic3r/Fill/FillRectilinear.cpp +++ b/src/libslic3r/Fill/FillRectilinear.cpp @@ -1,6 +1,6 @@ #include "../ClipperUtils.hpp" #include "../ExPolygon.hpp" -#include "../PolylineCollection.hpp" +#include "../ShortestPath.hpp" #include "../Surface.hpp" #include "FillRectilinear.hpp" @@ -92,15 +92,12 @@ void FillRectilinear::_fill_surface_single( std::swap(expolygon_off, expolygons_off.front()); } } - Polylines chained = PolylineCollection::chained_path_from( - std::move(polylines), - PolylineCollection::leftmost_point(polylines), false); // reverse allowed bool first = true; - for (Polylines::iterator it_polyline = chained.begin(); it_polyline != chained.end(); ++ it_polyline) { + for (Polyline &polyline : chain_polylines(std::move(polylines))) { if (! first) { // Try to connect the lines. Points &pts_end = polylines_out.back().points; - const Point &first_point = it_polyline->points.front(); + const Point &first_point = polyline.points.front(); const Point &last_point = pts_end.back(); // Distance in X, Y. const Vector distance = last_point - first_point; @@ -109,12 +106,12 @@ void FillRectilinear::_fill_surface_single( if (this->_can_connect(std::abs(distance(0)), std::abs(distance(1))) && expolygon_off.contains(Line(last_point, first_point))) { // Append the polyline. - pts_end.insert(pts_end.end(), it_polyline->points.begin(), it_polyline->points.end()); + pts_end.insert(pts_end.end(), polyline.points.begin(), polyline.points.end()); continue; } } // The lines cannot be connected. - polylines_out.emplace_back(std::move(*it_polyline)); + polylines_out.emplace_back(std::move(polyline)); first = false; } } diff --git a/src/libslic3r/Format/AMF.cpp b/src/libslic3r/Format/AMF.cpp index 92c958d8a..8989487cc 100644 --- a/src/libslic3r/Format/AMF.cpp +++ b/src/libslic3r/Format/AMF.cpp @@ -979,7 +979,7 @@ bool store_amf(const char *path, Model *model, const DynamicPrintConfig *config) stream << layer_height_profile.front(); for (size_t i = 1; i < layer_height_profile.size(); ++i) stream << ";" << layer_height_profile[i]; - stream << "\n \n"; + stream << "\n \n"; } // Export layer height ranges including the layer range specific config overrides. diff --git a/src/libslic3r/Format/PRUS.cpp b/src/libslic3r/Format/PRUS.cpp index 03ea71a83..d6f87197d 100644 --- a/src/libslic3r/Format/PRUS.cpp +++ b/src/libslic3r/Format/PRUS.cpp @@ -246,7 +246,7 @@ static void extract_model_from_archive( sscanf(normal_buf[2], "%f", &facet.normal(2)) != 1) { // Normal was mangled. Maybe denormals or "not a number" were stored? // Just reset the normal and silently ignore it. - memset(&facet.normal, 0, sizeof(facet.normal)); + facet.normal = stl_normal::Zero(); } facets.emplace_back(facet); } @@ -278,7 +278,7 @@ static void extract_model_from_archive( instance->set_rotation(instance_rotation); instance->set_scaling_factor(instance_scaling_factor); instance->set_offset(instance_offset); - if (group_id != (size_t)-1) + if (group_id != (unsigned int)(-1)) group_to_model_object[group_id] = model_object; } else { // This is not the 1st mesh of a group. Add it to the ModelObject. diff --git a/src/libslic3r/GCode.cpp b/src/libslic3r/GCode.cpp index 0ccc3ddf5..efcf15ad5 100644 --- a/src/libslic3r/GCode.cpp +++ b/src/libslic3r/GCode.cpp @@ -6,6 +6,7 @@ #include "Geometry.hpp" #include "GCode/PrintExtents.hpp" #include "GCode/WipeTower.hpp" +#include "ShortestPath.hpp" #include "Utils.hpp" #include @@ -659,7 +660,7 @@ void GCode::do_export(Print *print, const char *path, GCodePreviewData *preview_ if (print->is_step_done(psGCodeExport) && boost::filesystem::exists(boost::filesystem::path(path))) return; - print->set_started(psGCodeExport); + print->set_started(psGCodeExport); BOOST_LOG_TRIVIAL(info) << "Exporting G-code..." << log_memory_info(); @@ -1160,7 +1161,7 @@ void GCode::_do_export(Print &print, FILE *file) for (const LayerToPrint <p : layers_to_print) { std::vector lrs; lrs.emplace_back(std::move(ltp)); - this->process_layer(file, print, lrs, tool_ordering.tools_for_layer(ltp.print_z()), © - object.copies().data()); + this->process_layer(file, print, lrs, tool_ordering.tools_for_layer(ltp.print_z()), nullptr, © - object.copies().data()); print.throw_if_canceled(); } #ifdef HAS_PRESSURE_EQUALIZER @@ -1174,12 +1175,8 @@ void GCode::_do_export(Print &print, FILE *file) } } } else { - // Order objects using a nearest neighbor search. - std::vector object_indices; - Points object_reference_points; - for (PrintObject *object : print.objects()) - object_reference_points.push_back(object->copies().front()); - Slic3r::Geometry::chained_path(object_reference_points, object_indices); + // Order object instances using a nearest neighbor search. + std::vector> print_object_instances_ordering = chain_print_object_instances(print); // Sort layers by Z. // All extrusion moves with the same top layer height are extruded uninterrupted. std::vector>> layers_to_print = collect_layers_to_print(print); @@ -1218,7 +1215,7 @@ void GCode::_do_export(Print &print, FILE *file) const LayerTools &layer_tools = tool_ordering.tools_for_layer(layer.first); if (m_wipe_tower && layer_tools.has_wipe_tower) m_wipe_tower->next_layer(); - this->process_layer(file, print, layer.second, layer_tools, size_t(-1)); + this->process_layer(file, print, layer.second, layer_tools, &print_object_instances_ordering, size_t(-1)); print.throw_if_canceled(); } #ifdef HAS_PRESSURE_EQUALIZER @@ -1415,7 +1412,7 @@ static bool custom_gcode_sets_temperature(const std::string &gcode, const int mc // Skip the rest of the line. for (; *ptr != 0 && *ptr != '\r' && *ptr != '\n'; ++ ptr); // Skip the end of line indicators. - for (; *ptr == '\r' || *ptr == '\n'; ++ ptr); + for (; *ptr == '\r' || *ptr == '\n'; ++ ptr); } return temp_set_by_gcode; } @@ -1529,8 +1526,54 @@ inline std::vector& object_islands_by_extruder( return islands; } +std::vector GCode::sort_print_object_instances( + std::vector &objects_by_extruder, + const std::vector &layers, + // Ordering must be defined for normal (non-sequential print). + const std::vector> *ordering, + // For sequential print, the instance of the object to be printing has to be defined. + const size_t single_object_instance_idx) +{ + std::vector out; + + if (ordering == nullptr) { + // Sequential print, single object is being printed. + for (ObjectByExtruder &object_by_extruder : objects_by_extruder) { + const size_t layer_id = &object_by_extruder - objects_by_extruder.data(); + const PrintObject *print_object = layers[layer_id].object(); + if (print_object) + out.emplace_back(object_by_extruder, layer_id, *print_object, single_object_instance_idx); + } + } else { + // Create mapping from PrintObject* to ObjectByExtruder*. + std::vector> sorted; + sorted.reserve(objects_by_extruder.size()); + for (ObjectByExtruder &object_by_extruder : objects_by_extruder) { + const size_t layer_id = &object_by_extruder - objects_by_extruder.data(); + const PrintObject *print_object = layers[layer_id].object(); + if (print_object) + sorted.emplace_back(print_object, &object_by_extruder); + } + std::sort(sorted.begin(), sorted.end()); + + if (! sorted.empty()) { + const Print &print = *sorted.front().first->print(); + out.reserve(sorted.size()); + for (const std::pair &instance_id : *ordering) { + const PrintObject &print_object = *print.objects()[instance_id.first]; + std::pair key(&print_object, nullptr); + auto it = std::lower_bound(sorted.begin(), sorted.end(), key); + if (it != sorted.end() && it->first == &print_object) + // ObjectByExtruder for this PrintObject was found. + out.emplace_back(*it->second, it->second - objects_by_extruder.data(), print_object, instance_id.second); + } + } + } + return out; +} + // In sequential mode, process_layer is called once per each object and its copy, -// therefore layers will contain a single entry and single_object_idx will point to the copy of the object. +// therefore layers will contain a single entry and single_object_instance_idx will point to the copy of the object. // In non-sequential mode, process_layer is called per each print_z height with all object and support layers accumulated. // For multi-material prints, this routine minimizes extruder switches by gathering extruder specific extrusion paths // and performing the extruder specific extrusions together. @@ -1541,14 +1584,16 @@ void GCode::process_layer( // Set of object & print layers of the same PrintObject and with the same print_z. const std::vector &layers, const LayerTools &layer_tools, + // Pairs of PrintObject index and its instance index. + const std::vector> *ordering, // If set to size_t(-1), then print all copies of all objects. // Otherwise print a single copy of a single object. - const size_t single_object_idx) + const size_t single_object_instance_idx) { assert(! layers.empty()); // assert(! layer_tools.extruders.empty()); // Either printing all copies of all objects, or just a single copy of a single object. - assert(single_object_idx == size_t(-1) || layers.size() == 1); + assert(single_object_instance_idx == size_t(-1) || layers.size() == 1); if (layer_tools.extruders.empty()) // Nothing to extrude. @@ -1762,6 +1807,17 @@ void GCode::process_layer( layer_surface_bboxes.reserve(n_slices); for (const ExPolygon &expoly : layer.slices.expolygons) layer_surface_bboxes.push_back(get_extents(expoly.contour)); + // Traverse the slices in an increasing order of bounding box size, so that the islands inside another islands are tested first, + // so we can just test a point inside ExPolygon::contour and we may skip testing the holes. + std::vector slices_test_order; + slices_test_order.reserve(n_slices); + for (size_t i = 0; i < n_slices; ++ i) + slices_test_order.emplace_back(i); + std::sort(slices_test_order.begin(), slices_test_order.end(), [&layer_surface_bboxes](int i, int j) { + const Vec2d s1 = layer_surface_bboxes[i].size().cast(); + const Vec2d s2 = layer_surface_bboxes[j].size().cast(); + return s1.x() * s1.y() < s2.x() * s2.y(); + }); auto point_inside_surface = [&layer, &layer_surface_bboxes](const size_t i, const Point &point) { const BoundingBox &bbox = layer_surface_bboxes[i]; return point(0) >= bbox.min(0) && point(0) < bbox.max(0) && @@ -1809,16 +1865,19 @@ void GCode::process_layer( extruder, &layer_to_print - layers.data(), layers.size(), n_slices+1); - for (size_t i = 0; i <= n_slices; ++i) + for (size_t i = 0; i <= n_slices; ++ i) { + bool last = i == n_slices; + size_t island_idx = last ? n_slices : slices_test_order[i]; if (// fill->first_point does not fit inside any slice - i == n_slices || + last || // fill->first_point fits inside ith slice - point_inside_surface(i, fill->first_point())) { - if (islands[i].by_region.empty()) - islands[i].by_region.assign(print.regions().size(), ObjectByExtruder::Island::Region()); - islands[i].by_region[region_id].append(entity_type, fill, entity_overrides, layer_to_print.object()->copies().size()); + point_inside_surface(island_idx, fill->first_point())) { + if (islands[island_idx].by_region.empty()) + islands[island_idx].by_region.assign(print.regions().size(), ObjectByExtruder::Island::Region()); + islands[island_idx].by_region[region_id].append(entity_type, fill, entity_overrides, layer_to_print.object()->copies().size()); break; } + } } } } @@ -1883,62 +1942,49 @@ void GCode::process_layer( if (objects_by_extruder_it == by_extruder.end()) continue; + std::vector instances_to_print = sort_print_object_instances(objects_by_extruder_it->second, layers, ordering, single_object_instance_idx); + // We are almost ready to print. However, we must go through all the objects twice to print the the overridden extrusions first (infill/perimeter wiping feature): bool is_anything_overridden = const_cast(layer_tools).wiping_extrusions().is_anything_overridden(); for (int print_wipe_extrusions = is_anything_overridden; print_wipe_extrusions>=0; --print_wipe_extrusions) { if (is_anything_overridden && print_wipe_extrusions == 0) gcode+="; PURGING FINISHED\n"; - for (ObjectByExtruder &object_by_extruder : objects_by_extruder_it->second) { - const size_t layer_id = &object_by_extruder - objects_by_extruder_it->second.data(); - const PrintObject *print_object = layers[layer_id].object(); - if (print_object == nullptr) - // This layer is empty for this particular object, it has neither object extrusions nor support extrusions at this print_z. - continue; - - m_config.apply(print_object->config(), true); - m_layer = layers[layer_id].layer(); + for (InstanceToPrint &instance_to_print : instances_to_print) { + m_config.apply(instance_to_print.print_object.config(), true); + m_layer = layers[instance_to_print.layer_id].layer(); if (m_config.avoid_crossing_perimeters) m_avoid_crossing_perimeters.init_layer_mp(union_ex(m_layer->slices, true)); - Points copies; - if (single_object_idx == size_t(-1)) - copies = print_object->copies(); - else - copies.push_back(print_object->copies()[single_object_idx]); - // Sort the copies by the closest point starting with the current print position. - unsigned int copy_id = 0; - for (const Point © : copies) { - if (this->config().gcode_label_objects) - gcode += std::string("; printing object ") + print_object->model_object()->name + " id:" + std::to_string(layer_id) + " copy " + std::to_string(copy_id) + "\n"; - // When starting a new object, use the external motion planner for the first travel move. - std::pair this_object_copy(print_object, copy); - if (m_last_obj_copy != this_object_copy) - m_avoid_crossing_perimeters.use_external_mp_once = true; - m_last_obj_copy = this_object_copy; - this->set_origin(unscale(copy)); - if (object_by_extruder.support != nullptr && !print_wipe_extrusions) { - m_layer = layers[layer_id].support_layer; - gcode += this->extrude_support( - // support_extrusion_role is erSupportMaterial, erSupportMaterialInterface or erMixed for all extrusion paths. - object_by_extruder.support->chained_path_from(m_last_pos, false, object_by_extruder.support_extrusion_role)); - m_layer = layers[layer_id].layer(); - } - for (ObjectByExtruder::Island &island : object_by_extruder.islands) { - const auto& by_region_specific = is_anything_overridden ? island.by_region_per_copy(copy_id, extruder_id, print_wipe_extrusions) : island.by_region; - - if (print.config().infill_first) { - gcode += this->extrude_infill(print, by_region_specific); - gcode += this->extrude_perimeters(print, by_region_specific, lower_layer_edge_grids[layer_id]); - } else { - gcode += this->extrude_perimeters(print, by_region_specific, lower_layer_edge_grids[layer_id]); - gcode += this->extrude_infill(print,by_region_specific); - } - } - if (this->config().gcode_label_objects) - gcode += std::string("; stop printing object ") + print_object->model_object()->name + " id:" + std::to_string(layer_id) + " copy " + std::to_string(copy_id) + "\n"; - ++ copy_id; + if (this->config().gcode_label_objects) + gcode += std::string("; printing object ") + instance_to_print.print_object.model_object()->name + " id:" + std::to_string(instance_to_print.layer_id) + " copy " + std::to_string(instance_to_print.instance_id) + "\n"; + // When starting a new object, use the external motion planner for the first travel move. + const Point &offset = instance_to_print.print_object.copies()[instance_to_print.instance_id]; + std::pair this_object_copy(&instance_to_print.print_object, offset); + if (m_last_obj_copy != this_object_copy) + m_avoid_crossing_perimeters.use_external_mp_once = true; + m_last_obj_copy = this_object_copy; + this->set_origin(unscale(offset)); + if (instance_to_print.object_by_extruder.support != nullptr && !print_wipe_extrusions) { + m_layer = layers[instance_to_print.layer_id].support_layer; + gcode += this->extrude_support( + // support_extrusion_role is erSupportMaterial, erSupportMaterialInterface or erMixed for all extrusion paths. + instance_to_print.object_by_extruder.support->chained_path_from(m_last_pos, instance_to_print.object_by_extruder.support_extrusion_role)); + m_layer = layers[instance_to_print.layer_id].layer(); } + for (ObjectByExtruder::Island &island : instance_to_print.object_by_extruder.islands) { + const auto& by_region_specific = is_anything_overridden ? island.by_region_per_copy(instance_to_print.instance_id, extruder_id, print_wipe_extrusions) : island.by_region; + + if (print.config().infill_first) { + gcode += this->extrude_infill(print, by_region_specific); + gcode += this->extrude_perimeters(print, by_region_specific, lower_layer_edge_grids[instance_to_print.layer_id]); + } else { + gcode += this->extrude_perimeters(print, by_region_specific, lower_layer_edge_grids[instance_to_print.layer_id]); + gcode += this->extrude_infill(print,by_region_specific); + } + } + if (this->config().gcode_label_objects) + gcode += std::string("; stop printing object ") + instance_to_print.print_object.model_object()->name + " id:" + std::to_string(instance_to_print.layer_id) + " copy " + std::to_string(instance_to_print.instance_id) + "\n"; } } } @@ -2542,12 +2588,10 @@ std::string GCode::extrude_infill(const Print &print, const std::vectorconfig()); - ExtrusionEntityCollection chained = region.infills.chained_path_from(m_last_pos, false); - for (ExtrusionEntity *fill : chained.entities) { + for (ExtrusionEntity *fill : region.infills.chained_path_from(m_last_pos).entities) { auto *eec = dynamic_cast(fill); if (eec) { - ExtrusionEntityCollection chained2 = eec->chained_path_from(m_last_pos, false); - for (ExtrusionEntity *ee : chained2.entities) + for (ExtrusionEntity *ee : eec->chained_path_from(m_last_pos).entities) gcode += this->extrude_entity(*ee, "infill"); } else gcode += this->extrude_entity(*fill, "infill"); diff --git a/src/libslic3r/GCode.hpp b/src/libslic3r/GCode.hpp index 72813810b..45ff7eda6 100644 --- a/src/libslic3r/GCode.hpp +++ b/src/libslic3r/GCode.hpp @@ -202,7 +202,7 @@ protected: const PrintObject* object() const { return (this->layer() != nullptr) ? this->layer()->object() : nullptr; } coordf_t print_z() const { return (object_layer != nullptr && support_layer != nullptr) ? 0.5 * (object_layer->print_z + support_layer->print_z) : this->layer()->print_z; } }; - static std::vector collect_layers_to_print(const PrintObject &object); + static std::vector collect_layers_to_print(const PrintObject &object); static std::vector>> collect_layers_to_print(const Print &print); void process_layer( // Write into the output file. @@ -210,7 +210,9 @@ protected: const Print &print, // Set of object & print layers of the same PrintObject and with the same print_z. const std::vector &layers, - const LayerTools &layer_tools, + const LayerTools &layer_tools, + // Pairs of PrintObject index and its instance index. + const std::vector> *ordering, // If set to size_t(-1), then print all copies of all objects. // Otherwise print a single copy of a single object. const size_t single_object_idx = size_t(-1)); @@ -258,6 +260,25 @@ protected: std::vector islands; }; + struct InstanceToPrint + { + InstanceToPrint(ObjectByExtruder &object_by_extruder, size_t layer_id, const PrintObject &print_object, size_t instance_id) : + object_by_extruder(object_by_extruder), layer_id(layer_id), print_object(print_object), instance_id(instance_id) {} + + ObjectByExtruder &object_by_extruder; + const size_t layer_id; + const PrintObject &print_object; + // Instance idx of the copy of a print object. + const size_t instance_id; + }; + + std::vector sort_print_object_instances( + std::vector &objects_by_extruder, + const std::vector &layers, + // Ordering must be defined for normal (non-sequential print). + const std::vector> *ordering, + // For sequential print, the instance of the object to be printing has to be defined. + const size_t single_object_instance_idx); std::string extrude_perimeters(const Print &print, const std::vector &by_region, std::unique_ptr &lower_layer_edge_grid); std::string extrude_infill(const Print &print, const std::vector &by_region); diff --git a/src/libslic3r/GCode/Analyzer.cpp b/src/libslic3r/GCode/Analyzer.cpp index 20f0483b0..fa4414da9 100644 --- a/src/libslic3r/GCode/Analyzer.cpp +++ b/src/libslic3r/GCode/Analyzer.cpp @@ -20,6 +20,7 @@ static const unsigned int DEFAULT_EXTRUDER_ID = 0; static const unsigned int DEFAULT_COLOR_PRINT_ID = 0; static const Slic3r::Vec3d DEFAULT_START_POSITION = Slic3r::Vec3d(0.0f, 0.0f, 0.0f); static const float DEFAULT_START_EXTRUSION = 0.0f; +static const float DEFAULT_FAN_SPEED = 0.0f; namespace Slic3r { @@ -36,21 +37,23 @@ const float GCodeAnalyzer::Default_Height = 0.0f; GCodeAnalyzer::Metadata::Metadata() : extrusion_role(erNone) , extruder_id(DEFAULT_EXTRUDER_ID) - , cp_color_id(DEFAULT_COLOR_PRINT_ID) , mm3_per_mm(GCodeAnalyzer::Default_mm3_per_mm) , width(GCodeAnalyzer::Default_Width) , height(GCodeAnalyzer::Default_Height) , feedrate(DEFAULT_FEEDRATE) + , fan_speed(DEFAULT_FAN_SPEED) + , cp_color_id(DEFAULT_COLOR_PRINT_ID) { } -GCodeAnalyzer::Metadata::Metadata(ExtrusionRole extrusion_role, unsigned int extruder_id, double mm3_per_mm, float width, float height, float feedrate, unsigned int cp_color_id/* = 0*/) +GCodeAnalyzer::Metadata::Metadata(ExtrusionRole extrusion_role, unsigned int extruder_id, double mm3_per_mm, float width, float height, float feedrate, float fan_speed, unsigned int cp_color_id/* = 0*/) : extrusion_role(extrusion_role) , extruder_id(extruder_id) , mm3_per_mm(mm3_per_mm) , width(width) , height(height) , feedrate(feedrate) + , fan_speed(fan_speed) , cp_color_id(cp_color_id) { } @@ -75,15 +78,18 @@ bool GCodeAnalyzer::Metadata::operator != (const GCodeAnalyzer::Metadata& other) if (feedrate != other.feedrate) return true; + if (fan_speed != other.fan_speed) + return true; + if (cp_color_id != other.cp_color_id) return true; return false; } -GCodeAnalyzer::GCodeMove::GCodeMove(GCodeMove::EType type, ExtrusionRole extrusion_role, unsigned int extruder_id, double mm3_per_mm, float width, float height, float feedrate, const Vec3d& start_position, const Vec3d& end_position, float delta_extruder, unsigned int cp_color_id/* = 0*/) +GCodeAnalyzer::GCodeMove::GCodeMove(GCodeMove::EType type, ExtrusionRole extrusion_role, unsigned int extruder_id, double mm3_per_mm, float width, float height, float feedrate, const Vec3d& start_position, const Vec3d& end_position, float delta_extruder, float fan_speed, unsigned int cp_color_id/* = 0*/) : type(type) - , data(extrusion_role, extruder_id, mm3_per_mm, width, height, feedrate, cp_color_id) + , data(extrusion_role, extruder_id, mm3_per_mm, width, height, feedrate, fan_speed, cp_color_id) , start_position(start_position) , end_position(end_position) , delta_extruder(delta_extruder) @@ -133,6 +139,7 @@ void GCodeAnalyzer::reset() _set_feedrate(DEFAULT_FEEDRATE); _set_start_position(DEFAULT_START_POSITION); _set_start_extrusion(DEFAULT_START_EXTRUSION); + _set_fan_speed(DEFAULT_FAN_SPEED); _reset_axes_position(); _reset_cached_position(); @@ -259,6 +266,16 @@ void GCodeAnalyzer::_process_gcode_line(GCodeReader&, const GCodeReader::GCodeLi _processM83(line); break; } + case 106: // Set fan speed + { + _processM106(line); + break; + } + case 107: // Disable fan + { + _processM107(line); + break; + } case 108: case 135: { @@ -448,6 +465,24 @@ void GCodeAnalyzer::_processM83(const GCodeReader::GCodeLine& line) _set_e_local_positioning_type(Relative); } +void GCodeAnalyzer::_processM106(const GCodeReader::GCodeLine& line) +{ + if (!line.has('P')) + { + // The absence of P means the print cooling fan, so ignore anything else. + float new_fan_speed; + if (line.has_value('S', new_fan_speed)) + _set_fan_speed((100.0f / 256.0f) * new_fan_speed); + else + _set_fan_speed(100.0f); + } +} + +void GCodeAnalyzer::_processM107(const GCodeReader::GCodeLine& line) +{ + _set_fan_speed(0.0f); +} + void GCodeAnalyzer::_processM108orM135(const GCodeReader::GCodeLine& line) { // These M-codes are used by MakerWare and Sailfish to change active tool. @@ -726,6 +761,16 @@ float GCodeAnalyzer::_get_feedrate() const return m_state.data.feedrate; } +void GCodeAnalyzer::_set_fan_speed(float fan_speed_percentage) +{ + m_state.data.fan_speed = fan_speed_percentage; +} + +float GCodeAnalyzer::_get_fan_speed() const +{ + return m_state.data.fan_speed; +} + void GCodeAnalyzer::_set_axis_position(EAxis axis, float position) { m_state.position[axis] = position; @@ -798,7 +843,7 @@ void GCodeAnalyzer::_store_move(GCodeAnalyzer::GCodeMove::EType type) Vec3d start_position = _get_start_position() + extruder_offset; Vec3d end_position = _get_end_position() + extruder_offset; - it->second.emplace_back(type, _get_extrusion_role(), extruder_id, _get_mm3_per_mm(), _get_width(), _get_height(), _get_feedrate(), start_position, end_position, _get_delta_extrusion(), _get_cp_color_id()); + it->second.emplace_back(type, _get_extrusion_role(), extruder_id, _get_mm3_per_mm(), _get_width(), _get_height(), _get_feedrate(), start_position, end_position, _get_delta_extrusion(), _get_fan_speed(), _get_cp_color_id()); } bool GCodeAnalyzer::_is_valid_extrusion_role(int value) const @@ -834,6 +879,7 @@ void GCodeAnalyzer::_calc_gcode_preview_extrusion_layers(GCodePreviewData& previ path.polyline = polyline; path.feedrate = data.feedrate; path.extruder_id = data.extruder_id; + path.fan_speed = data.fan_speed; path.cp_color_id = data.cp_color_id; get_layer_at_z(preview_data.extrusion.layers, z).paths.push_back(path); @@ -854,6 +900,7 @@ void GCodeAnalyzer::_calc_gcode_preview_extrusion_layers(GCodePreviewData& previ GCodePreviewData::Range width_range; GCodePreviewData::Range feedrate_range; GCodePreviewData::Range volumetric_rate_range; + GCodePreviewData::Range fan_speed_range; // to avoid to call the callback too often unsigned int cancel_callback_threshold = (unsigned int)std::max((int)extrude_moves->second.size() / 25, 1); @@ -888,6 +935,7 @@ void GCodeAnalyzer::_calc_gcode_preview_extrusion_layers(GCodePreviewData& previ width_range.update_from(move.data.width); feedrate_range.update_from(move.data.feedrate); volumetric_rate_range.update_from(volumetric_rate); + fan_speed_range.update_from(move.data.fan_speed); } else // append end vertex of the move to current polyline @@ -906,6 +954,7 @@ void GCodeAnalyzer::_calc_gcode_preview_extrusion_layers(GCodePreviewData& previ preview_data.ranges.width.update_from(width_range); preview_data.ranges.feedrate.update_from(feedrate_range); preview_data.ranges.volumetric_rate.update_from(volumetric_rate_range); + preview_data.ranges.fan_speed.update_from(fan_speed_range); // we need to sort the layers by their z as they can be shuffled in case of sequential prints std::sort(preview_data.extrusion.layers.begin(), preview_data.extrusion.layers.end(), [](const GCodePreviewData::Extrusion::Layer& l1, const GCodePreviewData::Extrusion::Layer& l2)->bool { return l1.z < l2.z; }); diff --git a/src/libslic3r/GCode/Analyzer.hpp b/src/libslic3r/GCode/Analyzer.hpp index 0372d9da7..529610b0b 100644 --- a/src/libslic3r/GCode/Analyzer.hpp +++ b/src/libslic3r/GCode/Analyzer.hpp @@ -54,10 +54,11 @@ public: float width; // mm float height; // mm float feedrate; // mm/s + float fan_speed; // percentage unsigned int cp_color_id; Metadata(); - Metadata(ExtrusionRole extrusion_role, unsigned int extruder_id, double mm3_per_mm, float width, float height, float feedrate, unsigned int cp_color_id = 0); + Metadata(ExtrusionRole extrusion_role, unsigned int extruder_id, double mm3_per_mm, float width, float height, float feedrate, float fan_speed, unsigned int cp_color_id = 0); bool operator != (const Metadata& other) const; }; @@ -81,7 +82,7 @@ public: Vec3d end_position; float delta_extruder; - GCodeMove(EType type, ExtrusionRole extrusion_role, unsigned int extruder_id, double mm3_per_mm, float width, float height, float feedrate, const Vec3d& start_position, const Vec3d& end_position, float delta_extruder, unsigned int cp_color_id = 0); + GCodeMove(EType type, ExtrusionRole extrusion_role, unsigned int extruder_id, double mm3_per_mm, float width, float height, float feedrate, const Vec3d& start_position, const Vec3d& end_position, float delta_extruder, float fan_speed, unsigned int cp_color_id = 0); GCodeMove(EType type, const Metadata& data, const Vec3d& start_position, const Vec3d& end_position, float delta_extruder); }; @@ -171,6 +172,12 @@ private: // Set extruder to relative mode void _processM83(const GCodeReader::GCodeLine& line); + // Set fan speed + void _processM106(const GCodeReader::GCodeLine& line); + + // Disable fan + void _processM107(const GCodeReader::GCodeLine& line); + // Set tool (MakerWare and Sailfish flavor) void _processM108orM135(const GCodeReader::GCodeLine& line); @@ -233,6 +240,9 @@ private: void _set_feedrate(float feedrate_mm_sec); float _get_feedrate() const; + void _set_fan_speed(float fan_speed_percentage); + float _get_fan_speed() const; + void _set_axis_position(EAxis axis, float position); float _get_axis_position(EAxis axis) const; diff --git a/src/libslic3r/GCode/PreviewData.cpp b/src/libslic3r/GCode/PreviewData.cpp index 74de2f532..c6cfcc8af 100644 --- a/src/libslic3r/GCode/PreviewData.cpp +++ b/src/libslic3r/GCode/PreviewData.cpp @@ -241,6 +241,7 @@ void GCodePreviewData::set_default() ::memcpy((void*)ranges.height.colors, (const void*)Range::Default_Colors, Range::Colors_Count * sizeof(Color)); ::memcpy((void*)ranges.width.colors, (const void*)Range::Default_Colors, Range::Colors_Count * sizeof(Color)); ::memcpy((void*)ranges.feedrate.colors, (const void*)Range::Default_Colors, Range::Colors_Count * sizeof(Color)); + ::memcpy((void*)ranges.fan_speed.colors, (const void*)Range::Default_Colors, Range::Colors_Count * sizeof(Color)); ::memcpy((void*)ranges.volumetric_rate.colors, (const void*)Range::Default_Colors, Range::Colors_Count * sizeof(Color)); extrusion.set_default(); @@ -287,6 +288,11 @@ GCodePreviewData::Color GCodePreviewData::get_feedrate_color(float feedrate) con return ranges.feedrate.get_color_at(feedrate); } +GCodePreviewData::Color GCodePreviewData::get_fan_speed_color(float fan_speed) const +{ + return ranges.fan_speed.get_color_at(fan_speed); +} + GCodePreviewData::Color GCodePreviewData::get_volumetric_rate_color(float rate) const { return ranges.volumetric_rate.get_color_at(rate); @@ -358,8 +364,10 @@ std::string GCodePreviewData::get_legend_title() const return L("Width (mm)"); case Extrusion::Feedrate: return L("Speed (mm/s)"); + case Extrusion::FanSpeed: + return L("Fan Speed (%)"); case Extrusion::VolumetricRate: - return L("Volumetric flow rate (mm3/s)"); + return L("Volumetric flow rate (mm³/s)"); case Extrusion::Tool: return L("Tool"); case Extrusion::ColorPrint: @@ -421,6 +429,11 @@ GCodePreviewData::LegendItemsList GCodePreviewData::get_legend_items(const std:: Helper::FillListFromRange(items, ranges.feedrate, 1, 1.0f); break; } + case Extrusion::FanSpeed: + { + Helper::FillListFromRange(items, ranges.fan_speed, 0, 1.0f); + break; + } case Extrusion::VolumetricRate: { Helper::FillListFromRange(items, ranges.volumetric_rate, 3, 1.0f); diff --git a/src/libslic3r/GCode/PreviewData.hpp b/src/libslic3r/GCode/PreviewData.hpp index 6490399b4..7f5b691e1 100644 --- a/src/libslic3r/GCode/PreviewData.hpp +++ b/src/libslic3r/GCode/PreviewData.hpp @@ -52,6 +52,8 @@ public: Range width; // Color mapping by feedrate. Range feedrate; + // Color mapping by fan speed. + Range fan_speed; // Color mapping by volumetric extrusion rate. Range volumetric_rate; }; @@ -74,6 +76,7 @@ public: Height, Width, Feedrate, + FanSpeed, VolumetricRate, Tool, ColorPrint, @@ -205,6 +208,7 @@ public: Color get_height_color(float height) const; Color get_width_color(float width) const; Color get_feedrate_color(float feedrate) const; + Color get_fan_speed_color(float fan_speed) const; Color get_volumetric_rate_color(float rate) const; void set_extrusion_role_color(const std::string& role_name, float red, float green, float blue, float alpha); diff --git a/src/libslic3r/GCode/ToolOrdering.cpp b/src/libslic3r/GCode/ToolOrdering.cpp index 0a9ec320e..b7d1d57df 100644 --- a/src/libslic3r/GCode/ToolOrdering.cpp +++ b/src/libslic3r/GCode/ToolOrdering.cpp @@ -308,7 +308,7 @@ void ToolOrdering::fill_wipe_tower_partitions(const PrintConfig &config, coordf_ LayerTools lt_new(0.5f * (lt.print_z + lt_object.print_z)); // Find the 1st layer above lt_new. for (j = i + 1; j < m_layer_tools.size() && m_layer_tools[j].print_z < lt_new.print_z - EPSILON; ++ j); - if (std::abs(m_layer_tools[j].print_z - lt_new.print_z) < EPSILON) { + if (std::abs(m_layer_tools[j].print_z - lt_new.print_z) < EPSILON) { m_layer_tools[j].has_wipe_tower = true; } else { LayerTools <_extra = *m_layer_tools.insert(m_layer_tools.begin() + j, lt_new); diff --git a/src/libslic3r/GCode/WipeTower.cpp b/src/libslic3r/GCode/WipeTower.cpp index b35761b5f..ea8465f22 100644 --- a/src/libslic3r/GCode/WipeTower.cpp +++ b/src/libslic3r/GCode/WipeTower.cpp @@ -698,7 +698,7 @@ WipeTower::ToolChangeResult WipeTower::tool_change(size_t tool, bool last_in_lay writer.append(std::string("; material : " + (m_current_tool < m_filpar.size() ? m_filpar[m_current_tool].material : "(NONE)") + " -> " + m_filpar[tool].material + "\n").c_str()) .append(";--------------------\n"); - writer.speed_override_backup(); + writer.speed_override_backup(); writer.speed_override(100); Vec2f initial_position = cleaning_box.ld + Vec2f(0.f, m_depth_traversed); @@ -748,7 +748,7 @@ WipeTower::ToolChangeResult WipeTower::tool_change(size_t tool, bool last_in_lay if (m_current_tool < m_used_filament_length.size()) m_used_filament_length[m_current_tool] += writer.get_and_reset_used_filament_length(); - ToolChangeResult result; + ToolChangeResult result; result.priming = false; result.initial_tool = int(old_tool); result.new_tool = int(m_current_tool); @@ -806,7 +806,7 @@ WipeTower::ToolChangeResult WipeTower::toolchange_Brim(bool sideOnly, float y_of if (m_current_tool < m_used_filament_length.size()) m_used_filament_length[m_current_tool] += writer.get_and_reset_used_filament_length(); - ToolChangeResult result; + ToolChangeResult result; result.priming = false; result.initial_tool = int(old_tool); result.new_tool = int(m_current_tool); @@ -1163,7 +1163,7 @@ WipeTower::ToolChangeResult WipeTower::finish_layer() writer.extrude(box.rd.x() - m_perimeter_width / 2.f, writer.y() + 0.5f * step); writer.extrude(box.ld.x() + m_perimeter_width / 2.f, writer.y()); } - writer.travel(box.rd.x()-m_perimeter_width/2.f,writer.y()); // wipe the nozzle + writer.travel(box.rd.x()-m_perimeter_width/2.f,writer.y()); // wipe the nozzle } else { // Extrude a sparse infill to support the material to be printed above. const float dy = (fill_box.lu.y() - fill_box.ld.y() - m_perimeter_width); @@ -1196,7 +1196,7 @@ WipeTower::ToolChangeResult WipeTower::finish_layer() if (m_current_tool < m_used_filament_length.size()) m_used_filament_length[m_current_tool] += writer.get_and_reset_used_filament_length(); - ToolChangeResult result; + ToolChangeResult result; result.priming = false; result.initial_tool = int(old_tool); result.new_tool = int(m_current_tool); diff --git a/src/libslic3r/Geometry.cpp b/src/libslic3r/Geometry.cpp index cc8a86a96..e80b365bb 100644 --- a/src/libslic3r/Geometry.cpp +++ b/src/libslic3r/Geometry.cpp @@ -3,7 +3,6 @@ #include "ClipperUtils.hpp" #include "ExPolygon.hpp" #include "Line.hpp" -#include "PolylineCollection.hpp" #include "clipper.hpp" #include #include @@ -309,49 +308,7 @@ convex_hull(const Polygons &polygons) return convex_hull(std::move(pp)); } -/* accepts an arrayref of points and returns a list of indices - according to a nearest-neighbor walk */ -void -chained_path(const Points &points, std::vector &retval, Point start_near) -{ - PointConstPtrs my_points; - std::map indices; - my_points.reserve(points.size()); - for (Points::const_iterator it = points.begin(); it != points.end(); ++it) { - my_points.push_back(&*it); - indices[&*it] = it - points.begin(); - } - - retval.reserve(points.size()); - while (!my_points.empty()) { - Points::size_type idx = start_near.nearest_point_index(my_points); - start_near = *my_points[idx]; - retval.push_back(indices[ my_points[idx] ]); - my_points.erase(my_points.begin() + idx); - } -} - -void -chained_path(const Points &points, std::vector &retval) -{ - if (points.empty()) return; // can't call front() on empty vector - chained_path(points, retval, points.front()); -} - -/* retval and items must be different containers */ -template -void -chained_path_items(Points &points, T &items, T &retval) -{ - std::vector indices; - chained_path(points, indices); - for (std::vector::const_iterator it = indices.begin(); it != indices.end(); ++it) - retval.push_back(items[*it]); -} -template void chained_path_items(Points &points, ClipperLib::PolyNodes &items, ClipperLib::PolyNodes &retval); - -bool -directions_parallel(double angle1, double angle2, double max_diff) +bool directions_parallel(double angle1, double angle2, double max_diff) { double diff = fabs(angle1 - angle2); max_diff += EPSILON; @@ -359,8 +316,7 @@ directions_parallel(double angle1, double angle2, double max_diff) } template -bool -contains(const std::vector &vector, const Point &point) +bool contains(const std::vector &vector, const Point &point) { for (typename std::vector::const_iterator it = vector.begin(); it != vector.end(); ++it) { if (it->contains(point)) return true; @@ -369,16 +325,14 @@ contains(const std::vector &vector, const Point &point) } template bool contains(const ExPolygons &vector, const Point &point); -double -rad2deg_dir(double angle) +double rad2deg_dir(double angle) { angle = (angle < PI) ? (-angle + PI/2.0) : (angle + PI/2.0); if (angle < 0) angle += PI; return rad2deg(angle); } -void -simplify_polygons(const Polygons &polygons, double tolerance, Polygons* retval) +void simplify_polygons(const Polygons &polygons, double tolerance, Polygons* retval) { Polygons pp; for (Polygons::const_iterator it = polygons.begin(); it != polygons.end(); ++it) { @@ -391,8 +345,7 @@ simplify_polygons(const Polygons &polygons, double tolerance, Polygons* retval) *retval = Slic3r::simplify_polygons(pp); } -double -linint(double value, double oldmin, double oldmax, double newmin, double newmax) +double linint(double value, double oldmin, double oldmax, double newmin, double newmax) { return (value - oldmin) * (newmax - newmin) / (oldmax - oldmin) + newmin; } diff --git a/src/libslic3r/Geometry.hpp b/src/libslic3r/Geometry.hpp index eec267322..a574209d8 100644 --- a/src/libslic3r/Geometry.hpp +++ b/src/libslic3r/Geometry.hpp @@ -138,9 +138,6 @@ Pointf3s convex_hull(Pointf3s points); Polygon convex_hull(Points points); Polygon convex_hull(const Polygons &polygons); -void chained_path(const Points &points, std::vector &retval, Point start_near); -void chained_path(const Points &points, std::vector &retval); -template void chained_path_items(Points &points, T &items, T &retval); bool directions_parallel(double angle1, double angle2, double max_diff = 0); template bool contains(const std::vector &vector, const Point &point); template T rad2deg(T angle) { return T(180.0) * angle / T(PI); } diff --git a/src/libslic3r/KDTreeIndirect.hpp b/src/libslic3r/KDTreeIndirect.hpp new file mode 100644 index 000000000..3cccfdafa --- /dev/null +++ b/src/libslic3r/KDTreeIndirect.hpp @@ -0,0 +1,233 @@ +// KD tree built upon external data set, referencing the external data by integer indices. + +#ifndef slic3r_KDTreeIndirect_hpp_ +#define slic3r_KDTreeIndirect_hpp_ + +#include +#include +#include + +#include "Utils.hpp" // for next_highest_power_of_2() + +namespace Slic3r { + +// KD tree for N-dimensional closest point search. +template +class KDTreeIndirect +{ +public: + static constexpr size_t NumDimensions = ANumDimensions; + using CoordinateFn = ACoordinateFn; + using CoordType = ACoordType; + // Following could be static constexpr size_t, but that would not link in C++11 + enum : size_t { + npos = size_t(-1) + }; + + KDTreeIndirect(CoordinateFn coordinate) : coordinate(coordinate) {} + KDTreeIndirect(CoordinateFn coordinate, std::vector indices) : coordinate(coordinate) { this->build(std::move(indices)); } + KDTreeIndirect(CoordinateFn coordinate, std::vector &&indices) : coordinate(coordinate) { this->build(std::move(indices)); } + KDTreeIndirect(CoordinateFn coordinate, size_t num_indices) : coordinate(coordinate) { this->build(num_indices); } + KDTreeIndirect(KDTreeIndirect &&rhs) : m_nodes(std::move(rhs.m_nodes)), coordinate(std::move(rhs.coordinate)) {} + KDTreeIndirect& operator=(KDTreeIndirect &&rhs) { m_nodes = std::move(rhs.m_nodes); coordinate = std::move(rhs.coordinate); return *this; } + void clear() { m_nodes.clear(); } + + void build(size_t num_indices) + { + std::vector indices; + indices.reserve(num_indices); + for (size_t i = 0; i < num_indices; ++ i) + indices.emplace_back(i); + this->build(std::move(indices)); + } + + void build(std::vector &&indices) + { + if (indices.empty()) + clear(); + else { + // Allocate a next highest power of 2 nodes, because the incomplete binary tree will not have the leaves filled strictly from the left. + m_nodes.assign(next_highest_power_of_2(indices.size() + 1), npos); + build_recursive(indices, 0, 0, 0, (int)(indices.size() - 1)); + } + indices.clear(); + } + + enum class VisitorReturnMask : unsigned int + { + CONTINUE_LEFT = 1, + CONTINUE_RIGHT = 2, + STOP = 4, + }; + template + unsigned int descent_mask(const CoordType &point_coord, const CoordType &search_radius, size_t idx, size_t dimension) const + { + CoordType dist = point_coord - this->coordinate(idx, dimension); + return (dist * dist < search_radius + CoordType(EPSILON)) ? + // The plane intersects a hypersphere centered at point_coord of search_radius. + ((unsigned int)(VisitorReturnMask::CONTINUE_LEFT) | (unsigned int)(VisitorReturnMask::CONTINUE_RIGHT)) : + // The plane does not intersect the hypersphere. + (dist > CoordType(0)) ? (unsigned int)(VisitorReturnMask::CONTINUE_RIGHT) : (unsigned int)(VisitorReturnMask::CONTINUE_LEFT); + } + + // Visitor is supposed to return a bit mask of VisitorReturnMask. + template + void visit(Visitor &visitor) const + { + visit_recursive(0, 0, visitor); + } + + CoordinateFn coordinate; + +private: + // Build a balanced tree by splitting the input sequence by an axis aligned plane at a dimension. + void build_recursive(std::vector &input, size_t node, int dimension, int left, int right) + { + if (left > right) + return; + + assert(node < m_nodes.size()); + + if (left == right) { + // Insert a node into the balanced tree. + m_nodes[node] = input[left]; + return; + } + + // Partition the input sequence to two equal halves. + int center = (left + right) >> 1; + partition_input(input, dimension, left, right, center); + // Insert a node into the tree. + m_nodes[node] = input[center]; + // Partition the left and right subtrees. + size_t next_dimension = (++ dimension == NumDimensions) ? 0 : dimension; + build_recursive(input, (node << 1) + 1, next_dimension, left, center - 1); + build_recursive(input, (node << 1) + 2, next_dimension, center + 1, right); + } + + // Partition the input m_nodes at k using QuickSelect method. + // https://en.wikipedia.org/wiki/Quickselect + void partition_input(std::vector &input, int dimension, int left, int right, int k) const + { + while (left < right) { + // Guess the k'th element. + // Pick the pivot as a median of first, center and last value. + // Sort first, center and last values. + int center = (left + right) >> 1; + auto left_value = this->coordinate(input[left], dimension); + auto center_value = this->coordinate(input[center], dimension); + auto right_value = this->coordinate(input[right], dimension); + if (center_value < left_value) { + std::swap(input[left], input[center]); + std::swap(left_value, center_value); + } + if (right_value < left_value) { + std::swap(input[left], input[right]); + std::swap(left_value, right_value); + } + if (right_value < center_value) { + std::swap(input[center], input[right]); + // No need to do that, result is not used. + // std::swap(center_value, right_value); + } + // Only two or three values are left and those are sorted already. + if (left + 3 > right) + break; + // left and right items are already at their correct positions. + // input[left].point[dimension] <= input[center].point[dimension] <= input[right].point[dimension] + // Move the pivot to the (right - 1) position. + std::swap(input[center], input[right - 1]); + // Pivot value. + double pivot = this->coordinate(input[right - 1], dimension); + // Partition the set based on the pivot. + int i = left; + int j = right - 1; + for (;;) { + // Skip left points that are already at correct positions. + // Search will certainly stop at position (right - 1), which stores the pivot. + while (this->coordinate(input[++ i], dimension) < pivot) ; + // Skip right points that are already at correct positions. + while (this->coordinate(input[-- j], dimension) > pivot && i < j) ; + if (i >= j) + break; + std::swap(input[i], input[j]); + } + // Restore pivot to the center of the sequence. + std::swap(input[i], input[right]); + // Which side the kth element is in? + if (k < i) + right = i - 1; + else if (k == i) + // Sequence is partitioned, kth element is at its place. + break; + else + left = i + 1; + } + } + + template + void visit_recursive(size_t node, size_t dimension, Visitor &visitor) const + { + assert(! m_nodes.empty()); + if (node >= m_nodes.size() || m_nodes[node] == npos) + return; + + // Left / right child node index. + size_t left = (node << 1) + 1; + size_t right = left + 1; + unsigned int mask = visitor(m_nodes[node], dimension); + if ((mask & (unsigned int)VisitorReturnMask::STOP) == 0) { + size_t next_dimension = (++ dimension == NumDimensions) ? 0 : dimension; + if (mask & (unsigned int)VisitorReturnMask::CONTINUE_LEFT) + visit_recursive(left, next_dimension, visitor); + if (mask & (unsigned int)VisitorReturnMask::CONTINUE_RIGHT) + visit_recursive(right, next_dimension, visitor); + } + } + + std::vector m_nodes; +}; + +// Find a closest point using Euclidian metrics. +// Returns npos if not found. +template +size_t find_closest_point(const KDTreeIndirectType &kdtree, const PointType &point, FilterFn filter) +{ + struct Visitor { + using CoordType = typename KDTreeIndirectType::CoordType; + const KDTreeIndirectType &kdtree; + const PointType &point; + const FilterFn filter; + size_t min_idx = KDTreeIndirectType::npos; + CoordType min_dist = std::numeric_limits::max(); + + Visitor(const KDTreeIndirectType &kdtree, const PointType &point, FilterFn filter) : kdtree(kdtree), point(point), filter(filter) {} + unsigned int operator()(size_t idx, size_t dimension) { + if (this->filter(idx)) { + auto dist = CoordType(0); + for (size_t i = 0; i < KDTreeIndirectType::NumDimensions; ++ i) { + CoordType d = point[i] - kdtree.coordinate(idx, i); + dist += d * d; + } + if (dist < min_dist) { + min_dist = dist; + min_idx = idx; + } + } + return kdtree.descent_mask(point[dimension], min_dist, idx, dimension); + } + } visitor(kdtree, point, filter); + + kdtree.visit(visitor); + return visitor.min_idx; +} + +template +size_t find_closest_point(const KDTreeIndirectType& kdtree, const PointType& point) +{ + return find_closest_point(kdtree, point, [](size_t) { return true; }); +} + +} // namespace Slic3r + +#endif /* slic3r_KDTreeIndirect_hpp_ */ diff --git a/src/libslic3r/Layer.cpp b/src/libslic3r/Layer.cpp index 4ac64b777..94f114a26 100644 --- a/src/libslic3r/Layer.cpp +++ b/src/libslic3r/Layer.cpp @@ -1,8 +1,8 @@ #include "Layer.hpp" #include "ClipperUtils.hpp" -#include "Geometry.hpp" #include "Print.hpp" #include "Fill/Fill.hpp" +#include "ShortestPath.hpp" #include "SVG.hpp" #include @@ -57,8 +57,7 @@ void Layer::make_slices() ordering_points.push_back(ex.contour.first_point()); // sort slices - std::vector order; - Slic3r::Geometry::chained_path(ordering_points, order); + std::vector order = chain_points(ordering_points); // populate slices vector for (size_t i : order) diff --git a/src/libslic3r/Layer.hpp b/src/libslic3r/Layer.hpp index 555017207..539ae3925 100644 --- a/src/libslic3r/Layer.hpp +++ b/src/libslic3r/Layer.hpp @@ -6,8 +6,6 @@ #include "SurfaceCollection.hpp" #include "ExtrusionEntityCollection.hpp" #include "ExPolygonCollection.hpp" -#include "PolylineCollection.hpp" - namespace Slic3r { @@ -48,7 +46,7 @@ public: Polygons bridged; // collection of polylines representing the unsupported bridge edges - PolylineCollection unsupported_bridge_edges; + Polylines unsupported_bridge_edges; // ordered collection of extrusion paths/loops to build all perimeters // (this collection contains only ExtrusionEntityCollection objects) diff --git a/src/libslic3r/LayerRegion.cpp b/src/libslic3r/LayerRegion.cpp index 6fc0b4e37..d13549bf4 100644 --- a/src/libslic3r/LayerRegion.cpp +++ b/src/libslic3r/LayerRegion.cpp @@ -144,7 +144,7 @@ void LayerRegion::process_external_surfaces(const Layer *lower_layer, const Poly } if (! lower_layer_covered->empty()) voids = diff(voids, *lower_layer_covered); - fill_boundaries = diff(fill_boundaries, voids); + fill_boundaries = diff(fill_boundaries, voids); } } @@ -272,7 +272,7 @@ void LayerRegion::process_external_surfaces(const Layer *lower_layer, const Poly bridges[idx_last].bridge_angle = bd.angle; if (this->layer()->object()->config().support_material) { polygons_append(this->bridged, bd.coverage()); - this->unsupported_bridge_edges.append(bd.unsupported_edges()); + append(this->unsupported_bridge_edges, bd.unsupported_edges()); } } else if (custom_angle > 0) { // Bridge was not detected (likely it is only supported at one side). Still it is a surface filled in @@ -473,4 +473,4 @@ void LayerRegion::export_region_fill_surfaces_to_svg_debug(const char *name) con } } - \ No newline at end of file + diff --git a/src/libslic3r/Model.cpp b/src/libslic3r/Model.cpp index 7526dd16a..1e06f0703 100644 --- a/src/libslic3r/Model.cpp +++ b/src/libslic3r/Model.cpp @@ -1462,7 +1462,7 @@ stl_stats ModelObject::get_object_stl_stats() const return this->volumes[0]->mesh().stl.stats; stl_stats full_stats; - memset(&full_stats, 0, sizeof(stl_stats)); + full_stats.volume = 0.f; // fill full_stats from all objet's meshes for (ModelVolume* volume : this->volumes) diff --git a/src/libslic3r/MultiPoint.cpp b/src/libslic3r/MultiPoint.cpp index ee3b99747..39b07e7d8 100644 --- a/src/libslic3r/MultiPoint.cpp +++ b/src/libslic3r/MultiPoint.cpp @@ -3,11 +3,6 @@ namespace Slic3r { -MultiPoint::operator Points() const -{ - return this->points; -} - void MultiPoint::scale(double factor) { for (Point &pt : points) @@ -57,18 +52,7 @@ void MultiPoint::rotate(double angle, const Point ¢er) } } -void MultiPoint::reverse() -{ - std::reverse(this->points.begin(), this->points.end()); -} - -Point MultiPoint::first_point() const -{ - return this->points.front(); -} - -double -MultiPoint::length() const +double MultiPoint::length() const { Lines lines = this->lines(); double len = 0; @@ -78,8 +62,7 @@ MultiPoint::length() const return len; } -int -MultiPoint::find_point(const Point &point) const +int MultiPoint::find_point(const Point &point) const { for (const Point &pt : this->points) if (pt == point) @@ -87,21 +70,18 @@ MultiPoint::find_point(const Point &point) const return -1; // not found } -bool -MultiPoint::has_boundary_point(const Point &point) const +bool MultiPoint::has_boundary_point(const Point &point) const { double dist = (point.projection_onto(*this) - point).cast().norm(); return dist < SCALED_EPSILON; } -BoundingBox -MultiPoint::bounding_box() const +BoundingBox MultiPoint::bounding_box() const { return BoundingBox(this->points); } -bool -MultiPoint::has_duplicate_points() const +bool MultiPoint::has_duplicate_points() const { for (size_t i = 1; i < points.size(); ++i) if (points[i-1] == points[i]) @@ -109,8 +89,7 @@ MultiPoint::has_duplicate_points() const return false; } -bool -MultiPoint::remove_duplicate_points() +bool MultiPoint::remove_duplicate_points() { size_t j = 0; for (size_t i = 1; i < points.size(); ++i) { @@ -129,8 +108,7 @@ MultiPoint::remove_duplicate_points() return false; } -bool -MultiPoint::intersection(const Line& line, Point* intersection) const +bool MultiPoint::intersection(const Line& line, Point* intersection) const { Lines lines = this->lines(); for (Lines::const_iterator it = lines.begin(); it != lines.end(); ++it) { diff --git a/src/libslic3r/MultiPoint.hpp b/src/libslic3r/MultiPoint.hpp index 38020d6e8..9ff91b502 100644 --- a/src/libslic3r/MultiPoint.hpp +++ b/src/libslic3r/MultiPoint.hpp @@ -17,7 +17,8 @@ class MultiPoint public: Points points; - operator Points() const; + operator Points() const { return this->points; } + MultiPoint() {} MultiPoint(const MultiPoint &other) : points(other.points) {} MultiPoint(MultiPoint &&other) : points(std::move(other.points)) {} @@ -32,9 +33,10 @@ public: void rotate(double angle) { this->rotate(cos(angle), sin(angle)); } void rotate(double cos_angle, double sin_angle); void rotate(double angle, const Point ¢er); - void reverse(); - Point first_point() const; - virtual Point last_point() const = 0; + void reverse() { std::reverse(this->points.begin(), this->points.end()); } + + const Point& first_point() const { return this->points.front(); } + virtual const Point& last_point() const = 0; virtual Lines lines() const = 0; size_t size() const { return points.size(); } bool empty() const { return points.empty(); } diff --git a/src/libslic3r/MutablePriorityQueue.hpp b/src/libslic3r/MutablePriorityQueue.hpp index 82e992fd6..da469b7ba 100644 --- a/src/libslic3r/MutablePriorityQueue.hpp +++ b/src/libslic3r/MutablePriorityQueue.hpp @@ -13,21 +13,28 @@ public: {} ~MutablePriorityQueue() { clear(); } - inline void clear() { m_heap.clear(); } - inline void reserve(size_t cnt) { m_heap.reserve(cnt); } - inline void push(const T &item); - inline void push(T &&item); - inline void pop(); - inline T& top() { return m_heap.front(); } - inline void remove(size_t idx); - inline void update(size_t idx) { T item = m_heap[idx]; remove(idx); push(item); } + void clear(); + void reserve(size_t cnt) { m_heap.reserve(cnt); } + void push(const T &item); + void push(T &&item); + void pop(); + T& top() { return m_heap.front(); } + void remove(size_t idx); + void update(size_t idx) { T item = m_heap[idx]; remove(idx); push(item); } - inline size_t size() const { return m_heap.size(); } - inline bool empty() const { return m_heap.empty(); } + size_t size() const { return m_heap.size(); } + bool empty() const { return m_heap.empty(); } + + using iterator = typename std::vector::iterator; + using const_iterator = typename std::vector::const_iterator; + iterator begin() { return m_heap.begin(); } + iterator end() { return m_heap.end(); } + const_iterator cbegin() const { return m_heap.cbegin(); } + const_iterator cend() const { return m_heap.cend(); } protected: - inline void update_heap_up(size_t top, size_t bottom); - inline void update_heap_down(size_t top, size_t bottom); + void update_heap_up(size_t top, size_t bottom); + void update_heap_down(size_t top, size_t bottom); private: std::vector m_heap; @@ -42,6 +49,17 @@ MutablePriorityQueue make_mutable_priority_queue( std::forward(index_setter), std::forward(less_predicate)); } +template +inline void MutablePriorityQueue::clear() +{ +#ifndef NDEBUG + for (size_t idx = 0; idx < m_heap.size(); ++ idx) + // Mark as removed from the queue. + m_index_setter(m_heap[idx], std::numeric_limits::max()); +#endif /* NDEBUG */ + m_heap.clear(); +} + template inline void MutablePriorityQueue::push(const T &item) { @@ -64,6 +82,10 @@ template inline void MutablePriorityQueue::pop() { assert(! m_heap.empty()); +#ifndef NDEBUG + // Mark as removed from the queue. + m_index_setter(m_heap.front(), std::numeric_limits::max()); +#endif /* NDEBUG */ if (m_heap.size() > 1) { m_heap.front() = m_heap.back(); m_heap.pop_back(); @@ -77,6 +99,10 @@ template inline void MutablePriorityQueue::remove(size_t idx) { assert(idx < m_heap.size()); +#ifndef NDEBUG + // Mark as removed from the queue. + m_index_setter(m_heap[idx], std::numeric_limits::max()); +#endif /* NDEBUG */ if (idx + 1 == m_heap.size()) { m_heap.pop_back(); return; diff --git a/src/libslic3r/PerimeterGenerator.cpp b/src/libslic3r/PerimeterGenerator.cpp index 0c16f4a1d..450fff351 100644 --- a/src/libslic3r/PerimeterGenerator.cpp +++ b/src/libslic3r/PerimeterGenerator.cpp @@ -1,6 +1,8 @@ #include "PerimeterGenerator.hpp" #include "ClipperUtils.hpp" #include "ExtrusionEntityCollection.hpp" +#include "ShortestPath.hpp" + #include #include @@ -86,24 +88,24 @@ static ExtrusionPaths thick_polyline_to_extrusion_paths(const ThickPolyline &thi return paths; } -static ExtrusionEntityCollection variable_width(const ThickPolylines& polylines, ExtrusionRole role, Flow flow) +static void variable_width(const ThickPolylines& polylines, ExtrusionRole role, Flow flow, std::vector &out) { // This value determines granularity of adaptive width, as G-code does not allow // variable extrusion within a single move; this value shall only affect the amount // of segments, and any pruning shall be performed before we apply this tolerance. - ExtrusionEntityCollection coll; const float tolerance = float(scale_(0.05)); for (const ThickPolyline &p : polylines) { ExtrusionPaths paths = thick_polyline_to_extrusion_paths(p, role, flow, tolerance); // Append paths to collection. if (! paths.empty()) { if (paths.front().first_point() == paths.back().last_point()) - coll.append(ExtrusionLoop(std::move(paths))); - else - coll.append(std::move(paths)); + out.emplace_back(new ExtrusionLoop(std::move(paths))); + else { + for (ExtrusionPath &path : paths) + out.emplace_back(new ExtrusionPath(std::move(path))); + } } } - return coll; } // Hierarchy of perimeters. @@ -173,10 +175,9 @@ static ExtrusionEntityCollection traverse_loops(const PerimeterGenerator &perime perimeter_generator.overhang_flow.width, perimeter_generator.overhang_flow.height); - // reapply the nearest point search for starting point - // We allow polyline reversal because Clipper may have randomly - // reversed polylines during clipping. - paths = (ExtrusionPaths)ExtrusionEntityCollection(paths).chained_path(); + // Reapply the nearest point search for starting point. + // We allow polyline reversal because Clipper may have randomly reversed polylines during clipping. + chain_and_reorder_extrusion_paths(paths, &paths.front().first_point()); } else { ExtrusionPath path(role); path.polyline = loop.polygon.split_at_first_point(); @@ -186,43 +187,47 @@ static ExtrusionEntityCollection traverse_loops(const PerimeterGenerator &perime paths.push_back(path); } - coll.append(ExtrusionLoop(paths, loop_role)); + coll.append(ExtrusionLoop(std::move(paths), loop_role)); } // Append thin walls to the nearest-neighbor search (only for first iteration) if (! thin_walls.empty()) { - ExtrusionEntityCollection tw = variable_width(thin_walls, erExternalPerimeter, perimeter_generator.ext_perimeter_flow); - coll.append(tw.entities); + variable_width(thin_walls, erExternalPerimeter, perimeter_generator.ext_perimeter_flow, coll.entities); thin_walls.clear(); } - // Sort entities into a new collection using a nearest-neighbor search, - // preserving the original indices which are useful for detecting thin walls. - ExtrusionEntityCollection sorted_coll; - coll.chained_path(&sorted_coll, false, erMixed, &sorted_coll.orig_indices); - - // traverse children and build the final collection - ExtrusionEntityCollection entities; - for (const size_t &idx : sorted_coll.orig_indices) { - if (idx >= loops.size()) { - // This is a thin wall. Let's get it from the sorted collection as it might have been reversed. - entities.append(std::move(*sorted_coll.entities[&idx - &sorted_coll.orig_indices.front()])); + // Traverse children and build the final collection. + Point zero_point(0, 0); + std::vector> chain = chain_extrusion_entities(coll.entities, &zero_point); + ExtrusionEntityCollection out; + for (const std::pair &idx : chain) { + assert(coll.entities[idx.first] != nullptr); + if (idx.first >= loops.size()) { + // This is a thin wall. + out.entities.reserve(out.entities.size() + 1); + out.entities.emplace_back(coll.entities[idx.first]); + coll.entities[idx.first] = nullptr; + if (idx.second) + out.entities.back()->reverse(); } else { - const PerimeterGeneratorLoop &loop = loops[idx]; - ExtrusionLoop eloop = *dynamic_cast(coll.entities[idx]); + const PerimeterGeneratorLoop &loop = loops[idx.first]; + assert(thin_walls.empty()); ExtrusionEntityCollection children = traverse_loops(perimeter_generator, loop.children, thin_walls); + out.entities.reserve(out.entities.size() + children.entities.size() + 1); + ExtrusionLoop *eloop = static_cast(coll.entities[idx.first]); + coll.entities[idx.first] = nullptr; if (loop.is_contour) { - eloop.make_counter_clockwise(); - entities.append(std::move(children.entities)); - entities.append(std::move(eloop)); + eloop->make_counter_clockwise(); + out.append(std::move(children.entities)); + out.entities.emplace_back(eloop); } else { - eloop.make_clockwise(); - entities.append(std::move(eloop)); - entities.append(std::move(children.entities)); + eloop->make_clockwise(); + out.entities.emplace_back(eloop); + out.append(std::move(children.entities)); } } } - return entities; + return out; } void PerimeterGenerator::process() @@ -445,8 +450,8 @@ void PerimeterGenerator::process() for (const ExPolygon &ex : gaps_ex) ex.medial_axis(max, min, &polylines); if (! polylines.empty()) { - ExtrusionEntityCollection gap_fill = variable_width(polylines, erGapFill, this->solid_infill_flow); - this->gap_fill->append(gap_fill.entities); + ExtrusionEntityCollection gap_fill; + variable_width(polylines, erGapFill, this->solid_infill_flow, gap_fill.entities); /* Make sure we don't infill narrow parts that are already gap-filled (we only consider this surface's gaps to reduce the diff() complexity). Growing actual extrusions ensures that gaps not filled by medial axis @@ -456,7 +461,8 @@ void PerimeterGenerator::process() //FIXME Vojtech: This grows by a rounded extrusion width, not by line spacing, // therefore it may cover the area, but no the volume. last = diff_ex(to_polygons(last), gap_fill.polygons_covered_by_width(10.f)); - } + this->gap_fill->append(std::move(gap_fill.entities)); + } } // create one more offset to be used as boundary for fill diff --git a/src/libslic3r/Polygon.cpp b/src/libslic3r/Polygon.cpp index cf0783bae..5ef5d0ceb 100644 --- a/src/libslic3r/Polygon.cpp +++ b/src/libslic3r/Polygon.cpp @@ -5,43 +5,12 @@ namespace Slic3r { -Polygon::operator Polygons() const -{ - Polygons pp; - pp.push_back(*this); - return pp; -} - -Polygon::operator Polyline() const -{ - return this->split_at_first_point(); -} - -Point& -Polygon::operator[](Points::size_type idx) -{ - return this->points[idx]; -} - -const Point& -Polygon::operator[](Points::size_type idx) const -{ - return this->points[idx]; -} - -Point -Polygon::last_point() const -{ - return this->points.front(); // last point == first point for polygons -} - Lines Polygon::lines() const { return to_lines(*this); } -Polyline -Polygon::split_at_vertex(const Point &point) const +Polyline Polygon::split_at_vertex(const Point &point) const { // find index of point for (const Point &pt : this->points) @@ -52,8 +21,7 @@ Polygon::split_at_vertex(const Point &point) const } // Split a closed polygon into an open polyline, with the split point duplicated at both ends. -Polyline -Polygon::split_at_index(int index) const +Polyline Polygon::split_at_index(int index) const { Polyline polyline; polyline.points.reserve(this->points.size() + 1); @@ -64,19 +32,6 @@ Polygon::split_at_index(int index) const return polyline; } -// Split a closed polygon into an open polyline, with the split point duplicated at both ends. -Polyline -Polygon::split_at_first_point() const -{ - return this->split_at_index(0); -} - -Points -Polygon::equally_spaced_points(double distance) const -{ - return this->split_at_first_point().equally_spaced_points(distance); -} - /* int64_t Polygon::area2x() const { @@ -107,20 +62,17 @@ double Polygon::area() const return 0.5 * a; } -bool -Polygon::is_counter_clockwise() const +bool Polygon::is_counter_clockwise() const { return ClipperLib::Orientation(Slic3rMultiPoint_to_ClipperPath(*this)); } -bool -Polygon::is_clockwise() const +bool Polygon::is_clockwise() const { return !this->is_counter_clockwise(); } -bool -Polygon::make_counter_clockwise() +bool Polygon::make_counter_clockwise() { if (!this->is_counter_clockwise()) { this->reverse(); @@ -129,8 +81,7 @@ Polygon::make_counter_clockwise() return false; } -bool -Polygon::make_clockwise() +bool Polygon::make_clockwise() { if (this->is_counter_clockwise()) { this->reverse(); @@ -139,16 +90,9 @@ Polygon::make_clockwise() return false; } -bool -Polygon::is_valid() const -{ - return this->points.size() >= 3; -} - // Does an unoriented polygon contain a point? // Tested by counting intersections along a horizontal line. -bool -Polygon::contains(const Point &point) const +bool Polygon::contains(const Point &point) const { // http://www.ecse.rpi.edu/Homepages/wrf/Research/Short_Notes/pnpoly.html bool result = false; @@ -174,8 +118,7 @@ Polygon::contains(const Point &point) const } // this only works on CCW polygons as CW will be ripped out by Clipper's simplify_polygons() -Polygons -Polygon::simplify(double tolerance) const +Polygons Polygon::simplify(double tolerance) const { // repeat first point at the end in order to apply Douglas-Peucker // on the whole polygon @@ -189,8 +132,7 @@ Polygon::simplify(double tolerance) const return simplify_polygons(pp); } -void -Polygon::simplify(double tolerance, Polygons &polygons) const +void Polygon::simplify(double tolerance, Polygons &polygons) const { Polygons pp = this->simplify(tolerance); polygons.reserve(polygons.size() + pp.size()); @@ -198,8 +140,7 @@ Polygon::simplify(double tolerance, Polygons &polygons) const } // Only call this on convex polygons or it will return invalid results -void -Polygon::triangulate_convex(Polygons* polygons) const +void Polygon::triangulate_convex(Polygons* polygons) const { for (Points::const_iterator it = this->points.begin() + 2; it != this->points.end(); ++it) { Polygon p; @@ -214,8 +155,7 @@ Polygon::triangulate_convex(Polygons* polygons) const } // center of mass -Point -Polygon::centroid() const +Point Polygon::centroid() const { double area_temp = this->area(); double x_temp = 0; @@ -232,8 +172,7 @@ Polygon::centroid() const // find all concave vertices (i.e. having an internal angle greater than the supplied angle) // (external = right side, thus we consider ccw orientation) -Points -Polygon::concave_points(double angle) const +Points Polygon::concave_points(double angle) const { Points points; angle = 2*PI - angle; @@ -256,8 +195,7 @@ Polygon::concave_points(double angle) const // find all convex vertices (i.e. having an internal angle smaller than the supplied angle) // (external = right side, thus we consider ccw orientation) -Points -Polygon::convex_points(double angle) const +Points Polygon::convex_points(double angle) const { Points points; angle = 2*PI - angle; diff --git a/src/libslic3r/Polygon.hpp b/src/libslic3r/Polygon.hpp index 63162d953..42f938c72 100644 --- a/src/libslic3r/Polygon.hpp +++ b/src/libslic3r/Polygon.hpp @@ -13,13 +13,14 @@ namespace Slic3r { class Polygon; typedef std::vector Polygons; -class Polygon : public MultiPoint { +class Polygon : public MultiPoint +{ public: - operator Polygons() const; - operator Polyline() const; - Point& operator[](Points::size_type idx); - const Point& operator[](Points::size_type idx) const; - + operator Polygons() const { Polygons pp; pp.push_back(*this); return pp; } + operator Polyline() const { return this->split_at_first_point(); } + Point& operator[](Points::size_type idx) { return this->points[idx]; } + const Point& operator[](Points::size_type idx) const { return this->points[idx]; } + Polygon() {} explicit Polygon(const Points &points): MultiPoint(points) {} Polygon(const Polygon &other) : MultiPoint(other.points) {} @@ -34,20 +35,24 @@ public: Polygon& operator=(const Polygon &other) { points = other.points; return *this; } Polygon& operator=(Polygon &&other) { points = std::move(other.points); return *this; } - Point last_point() const; + // last point == first point for polygons + const Point& last_point() const override { return this->points.front(); } + virtual Lines lines() const; Polyline split_at_vertex(const Point &point) const; // Split a closed polygon into an open polyline, with the split point duplicated at both ends. Polyline split_at_index(int index) const; // Split a closed polygon into an open polyline, with the split point duplicated at both ends. - Polyline split_at_first_point() const; - Points equally_spaced_points(double distance) const; + Polyline split_at_first_point() const { return this->split_at_index(0); } + Points equally_spaced_points(double distance) const { return this->split_at_first_point().equally_spaced_points(distance); } + double area() const; bool is_counter_clockwise() const; bool is_clockwise() const; bool make_counter_clockwise(); bool make_clockwise(); - bool is_valid() const; + bool is_valid() const { return this->points.size() >= 3; } + // Does an unoriented polygon contain a point? // Tested by counting intersections along a horizontal line. bool contains(const Point &point) const; diff --git a/src/libslic3r/Polyline.cpp b/src/libslic3r/Polyline.cpp index af155468a..26aad83d2 100644 --- a/src/libslic3r/Polyline.cpp +++ b/src/libslic3r/Polyline.cpp @@ -23,24 +23,17 @@ Polyline::operator Line() const return Line(this->points.front(), this->points.back()); } -Point -Polyline::last_point() const +const Point& Polyline::leftmost_point() const { - return this->points.back(); -} - -Point -Polyline::leftmost_point() const -{ - Point p = this->points.front(); - for (Points::const_iterator it = this->points.begin() + 1; it != this->points.end(); ++it) { - if ((*it)(0) < p(0)) p = *it; + const Point *p = &this->points.front(); + for (Points::const_iterator it = this->points.begin() + 1; it != this->points.end(); ++ it) { + if (it->x() < p->x()) + p = &(*it); } - return p; + return *p; } -Lines -Polyline::lines() const +Lines Polyline::lines() const { Lines lines; if (this->points.size() >= 2) { @@ -211,6 +204,20 @@ BoundingBox get_extents(const Polylines &polylines) return bb; } +const Point& leftmost_point(const Polylines &polylines) +{ + if (polylines.empty()) + throw std::invalid_argument("leftmost_point() called on empty PolylineCollection"); + Polylines::const_iterator it = polylines.begin(); + const Point *p = &it->leftmost_point(); + for (++ it; it != polylines.end(); ++it) { + const Point *p2 = &it->leftmost_point(); + if (p2->x() < p->x()) + p = p2; + } + return *p; +} + bool remove_degenerate(Polylines &polylines) { bool modified = false; diff --git a/src/libslic3r/Polyline.hpp b/src/libslic3r/Polyline.hpp index e17fafd62..7e3a1e506 100644 --- a/src/libslic3r/Polyline.hpp +++ b/src/libslic3r/Polyline.hpp @@ -62,8 +62,9 @@ public: operator Polylines() const; operator Line() const; - Point last_point() const; - Point leftmost_point() const; + const Point& last_point() const override { return this->points.back(); } + + const Point& leftmost_point() const; virtual Lines lines() const; void clip_end(double distance); void clip_start(double distance); @@ -76,6 +77,15 @@ public: bool is_straight() const; }; +// Don't use this class in production code, it is used exclusively by the Perl binding for unit tests! +#ifdef PERL_UCHAR_MIN +class PolylineCollection +{ +public: + Polylines polylines; +}; +#endif /* PERL_UCHAR_MIN */ + extern BoundingBox get_extents(const Polyline &polyline); extern BoundingBox get_extents(const Polylines &polylines); @@ -128,6 +138,8 @@ inline void polylines_append(Polylines &dst, Polylines &&src) } } +const Point& leftmost_point(const Polylines &polylines); + bool remove_degenerate(Polylines &polylines); class ThickPolyline : public Polyline { diff --git a/src/libslic3r/PolylineCollection.cpp b/src/libslic3r/PolylineCollection.cpp deleted file mode 100644 index 0c66c371a..000000000 --- a/src/libslic3r/PolylineCollection.cpp +++ /dev/null @@ -1,92 +0,0 @@ -#include "PolylineCollection.hpp" - -namespace Slic3r { - -struct Chaining -{ - Point first; - Point last; - size_t idx; -}; - -template -inline int nearest_point_index(const std::vector &pairs, const Point &start_near, bool no_reverse) -{ - T dmin = std::numeric_limits::max(); - int idx = 0; - for (std::vector::const_iterator it = pairs.begin(); it != pairs.end(); ++it) { - T d = sqr(T(start_near(0) - it->first(0))); - if (d <= dmin) { - d += sqr(T(start_near(1) - it->first(1))); - if (d < dmin) { - idx = (it - pairs.begin()) * 2; - dmin = d; - if (dmin < EPSILON) - break; - } - } - if (! no_reverse) { - d = sqr(T(start_near(0) - it->last(0))); - if (d <= dmin) { - d += sqr(T(start_near(1) - it->last(1))); - if (d < dmin) { - idx = (it - pairs.begin()) * 2 + 1; - dmin = d; - if (dmin < EPSILON) - break; - } - } - } - } - return idx; -} - -Polylines PolylineCollection::_chained_path_from( - const Polylines &src, - Point start_near, - bool no_reverse, - bool move_from_src) -{ - std::vector endpoints; - endpoints.reserve(src.size()); - for (size_t i = 0; i < src.size(); ++ i) { - Chaining c; - c.first = src[i].first_point(); - if (! no_reverse) - c.last = src[i].last_point(); - c.idx = i; - endpoints.push_back(c); - } - Polylines retval; - while (! endpoints.empty()) { - // find nearest point - int endpoint_index = nearest_point_index(endpoints, start_near, no_reverse); - assert(endpoint_index >= 0 && size_t(endpoint_index) < endpoints.size() * 2); - if (move_from_src) { - retval.push_back(std::move(src[endpoints[endpoint_index/2].idx])); - } else { - retval.push_back(src[endpoints[endpoint_index/2].idx]); - } - if (endpoint_index & 1) - retval.back().reverse(); - endpoints.erase(endpoints.begin() + endpoint_index/2); - start_near = retval.back().last_point(); - } - return retval; -} - -Point PolylineCollection::leftmost_point(const Polylines &polylines) -{ - if (polylines.empty()) - throw std::invalid_argument("leftmost_point() called on empty PolylineCollection"); - Polylines::const_iterator it = polylines.begin(); - Point p = it->leftmost_point(); - for (++ it; it != polylines.end(); ++it) { - Point p2 = it->leftmost_point(); - if (p2(0) < p(0)) - p = p2; - } - return p; -} - -} // namespace Slic3r diff --git a/src/libslic3r/PolylineCollection.hpp b/src/libslic3r/PolylineCollection.hpp deleted file mode 100644 index 87fc1985b..000000000 --- a/src/libslic3r/PolylineCollection.hpp +++ /dev/null @@ -1,47 +0,0 @@ -#ifndef slic3r_PolylineCollection_hpp_ -#define slic3r_PolylineCollection_hpp_ - -#include "libslic3r.h" -#include "Polyline.hpp" - -namespace Slic3r { - -class PolylineCollection -{ - static Polylines _chained_path_from( - const Polylines &src, - Point start_near, - bool no_reverse, - bool move_from_src); - -public: - Polylines polylines; - void chained_path(PolylineCollection* retval, bool no_reverse = false) const - { retval->polylines = chained_path(this->polylines, no_reverse); } - void chained_path_from(Point start_near, PolylineCollection* retval, bool no_reverse = false) const - { retval->polylines = chained_path_from(this->polylines, start_near, no_reverse); } - Point leftmost_point() const - { return leftmost_point(polylines); } - void append(const Polylines &polylines) - { this->polylines.insert(this->polylines.end(), polylines.begin(), polylines.end()); } - - static Point leftmost_point(const Polylines &polylines); - static Polylines chained_path(Polylines &&src, bool no_reverse = false) { - return (src.empty() || src.front().points.empty()) ? - Polylines() : - _chained_path_from(src, src.front().first_point(), no_reverse, true); - } - static Polylines chained_path_from(Polylines &&src, Point start_near, bool no_reverse = false) - { return _chained_path_from(src, start_near, no_reverse, true); } - static Polylines chained_path(const Polylines &src, bool no_reverse = false) { - return (src.empty() || src.front().points.empty()) ? - Polylines() : - _chained_path_from(src, src.front().first_point(), no_reverse, false); - } - static Polylines chained_path_from(const Polylines &src, Point start_near, bool no_reverse = false) - { return _chained_path_from(src, start_near, no_reverse, false); } -}; - -} - -#endif diff --git a/src/libslic3r/Print.cpp b/src/libslic3r/Print.cpp index cbbbf1f1a..f5601276f 100644 --- a/src/libslic3r/Print.cpp +++ b/src/libslic3r/Print.cpp @@ -7,6 +7,7 @@ #include "Flow.hpp" #include "Geometry.hpp" #include "I18N.hpp" +#include "ShortestPath.hpp" #include "SupportMaterial.hpp" #include "GCode.hpp" #include "GCode/WipeTower.hpp" @@ -252,7 +253,7 @@ bool Print::is_step_done(PrintObjectStep step) const { if (m_objects.empty()) return false; - tbb::mutex::scoped_lock lock(this->state_mutex()); + tbb::mutex::scoped_lock lock(this->state_mutex()); for (const PrintObject *object : m_objects) if (! object->is_step_done_unguarded(step)) return false; @@ -1236,7 +1237,8 @@ std::string Print::validate() const // The comparison of the profiles is not just about element-wise equality, some layers may not be // explicitely included. Always remember z and height of last reference layer that in the vector - // and compare to that. + // and compare to that. In case some layers are in the vectors multiple times, only the last entry is + // taken into account and compared. size_t i = 0; // index into tested profile size_t j = 0; // index into reference profile coordf_t ref_z = -1.; @@ -1244,8 +1246,12 @@ std::string Print::validate() const coordf_t ref_height = -1.; while (i < layer_height_profile.size()) { coordf_t this_z = layer_height_profile[i]; + // find the last entry with this z + while (i+2 < layer_height_profile.size() && layer_height_profile[i+2] == this_z) + i += 2; + coordf_t this_height = layer_height_profile[i+1]; - if (next_ref_z < this_z + EPSILON) { + if (ref_height < -1. || next_ref_z < this_z + EPSILON) { ref_z = next_ref_z; do { // one layer can be in the vector several times ref_height = layer_height_profile_tallest[j+1]; @@ -1819,8 +1825,8 @@ void Print::_make_brim() [](const std::pair &l, const std::pair &r) { return l.second < r.second; }); - Vec3f last_pt(0.f, 0.f, 0.f); + Point last_pt(0, 0); for (size_t i = 0; i < loops_trimmed_order.size();) { // Find all pieces that the initial loop was split into. size_t j = i + 1; @@ -1836,16 +1842,23 @@ void Print::_make_brim() points.emplace_back(coord_t(pt.X), coord_t(pt.Y)); i = j; } else { - //FIXME this is not optimal as the G-code generator will follow the sequence of paths verbatim without respect to minimum travel distance. + //FIXME The path chaining here may not be optimal. + ExtrusionEntityCollection this_loop_trimmed; + this_loop_trimmed.entities.reserve(j - i); for (; i < j; ++ i) { - m_brim.entities.emplace_back(new ExtrusionPath(erSkirt, float(flow.mm3_per_mm()), float(flow.width), float(this->skirt_first_layer_height()))); + this_loop_trimmed.entities.emplace_back(new ExtrusionPath(erSkirt, float(flow.mm3_per_mm()), float(flow.width), float(this->skirt_first_layer_height()))); const ClipperLib_Z::Path &path = *loops_trimmed_order[i].first; - Points &points = static_cast(m_brim.entities.back())->polyline.points; + Points &points = static_cast(this_loop_trimmed.entities.back())->polyline.points; points.reserve(path.size()); for (const ClipperLib_Z::IntPoint &pt : path) points.emplace_back(coord_t(pt.X), coord_t(pt.Y)); } + chain_and_reorder_extrusion_entities(this_loop_trimmed.entities, &last_pt); + m_brim.entities.reserve(m_brim.entities.size() + this_loop_trimmed.entities.size()); + append(m_brim.entities, std::move(this_loop_trimmed.entities)); + this_loop_trimmed.entities.clear(); } + last_pt = m_brim.last_point(); } } } else { diff --git a/src/libslic3r/Print.hpp b/src/libslic3r/Print.hpp index 89a5f3e74..5cb13039c 100644 --- a/src/libslic3r/Print.hpp +++ b/src/libslic3r/Print.hpp @@ -96,6 +96,7 @@ public: const SupportLayerPtrs& support_layers() const { return m_support_layers; } const Transform3d& trafo() const { return m_trafo; } const Points& copies() const { return m_copies; } + const Point copy_center(size_t idx) const { return m_copies[idx] + m_copies_shift + Point(this->size.x() / 2, this->size.y() / 2); } // since the object is aligned to origin, bounding box coincides with size BoundingBox bounding_box() const { return BoundingBox(Point(0,0), to_2d(this->size)); } diff --git a/src/libslic3r/PrintBase.hpp b/src/libslic3r/PrintBase.hpp index aebc87904..05d884cc8 100644 --- a/src/libslic3r/PrintBase.hpp +++ b/src/libslic3r/PrintBase.hpp @@ -268,8 +268,7 @@ public: std::string text; // Bitmap of flags. enum FlagBits { - DEFAULT, - NO_RELOAD_SCENE = 0, + DEFAULT = 0, RELOAD_SCENE = 1 << 1, RELOAD_SLA_SUPPORT_POINTS = 1 << 2, RELOAD_SLA_PREVIEW = 1 << 3, diff --git a/src/libslic3r/PrintObject.cpp b/src/libslic3r/PrintObject.cpp index 2834d9105..c4f77b6d7 100644 --- a/src/libslic3r/PrintObject.cpp +++ b/src/libslic3r/PrintObject.cpp @@ -12,7 +12,6 @@ #include #include -#include #include #include @@ -75,13 +74,9 @@ PrintBase::ApplyStatus PrintObject::set_copies(const Points &points) { // Order copies with a nearest-neighbor search. std::vector copies; - { - std::vector ordered_copies; - Slic3r::Geometry::chained_path(points, ordered_copies); - copies.reserve(ordered_copies.size()); - for (size_t point_idx : ordered_copies) - copies.emplace_back(points[point_idx] + m_copies_shift); - } + copies.reserve(points.size()); + for (const Point &pt : points) + copies.emplace_back(pt + m_copies_shift); // Invalidate and set copies. PrintBase::ApplyStatus status = PrintBase::APPLY_STATUS_UNCHANGED; if (copies != m_copies) { @@ -1480,7 +1475,7 @@ SlicingParameters PrintObject::slicing_parameters(const DynamicPrintConfig& full if (object_max_z <= 0.f) object_max_z = (float)model_object.raw_bounding_box().size().z(); - return SlicingParameters::create_from_config(print_config, object_config, object_max_z, object_extruders); + return SlicingParameters::create_from_config(print_config, object_config, object_max_z, object_extruders); } // returns 0-based indices of extruders used to print the object (without brim, support and other helper extrusions) diff --git a/src/libslic3r/ShortestPath.cpp b/src/libslic3r/ShortestPath.cpp new file mode 100644 index 000000000..a0fb05005 --- /dev/null +++ b/src/libslic3r/ShortestPath.cpp @@ -0,0 +1,514 @@ +#if 0 + #pragma optimize("", off) + #undef NDEBUG + #undef assert +#endif + +#include "clipper.hpp" +#include "ShortestPath.hpp" +#include "KDTreeIndirect.hpp" +#include "MutablePriorityQueue.hpp" +#include "Print.hpp" + +#include +#include + +namespace Slic3r { + +// Naive implementation of the Traveling Salesman Problem, it works by always taking the next closest neighbor. +// This implementation will always produce valid result even if some segments cannot reverse. +template +std::vector> chain_segments_closest_point(std::vector &end_points, KDTreeType &kdtree, CouldReverseFunc &could_reverse_func, EndPointType &first_point) +{ + assert((end_points.size() & 1) == 0); + size_t num_segments = end_points.size() / 2; + assert(num_segments >= 2); + for (EndPointType &ep : end_points) + ep.chain_id = 0; + std::vector> out; + out.reserve(num_segments); + size_t first_point_idx = &first_point - end_points.data(); + out.emplace_back(first_point_idx / 2, (first_point_idx & 1) != 0); + first_point.chain_id = 1; + size_t this_idx = first_point_idx ^ 1; + for (int iter = (int)num_segments - 2; iter >= 0; -- iter) { + EndPointType &this_point = end_points[this_idx]; + this_point.chain_id = 1; + // Find the closest point to this end_point, which lies on a different extrusion path (filtered by the lambda). + // Ignore the starting point as the starting point is considered to be occupied, no end point coud connect to it. + size_t next_idx = find_closest_point(kdtree, this_point.pos, + [this_idx, &end_points, &could_reverse_func](size_t idx) { + return (idx ^ this_idx) > 1 && end_points[idx].chain_id == 0 && ((idx ^ 1) == 0 || could_reverse_func(idx >> 1)); + }); + assert(next_idx < end_points.size()); + EndPointType &end_point = end_points[next_idx]; + end_point.chain_id = 1; + this_idx = next_idx ^ 1; + } +#ifndef NDEBUG + assert(end_points[this_idx].chain_id == 0); + for (EndPointType &ep : end_points) + assert(&ep == &end_points[this_idx] || ep.chain_id == 1); +#endif /* NDEBUG */ + return out; +} + +// Chain perimeters (always closed) and thin fills (closed or open) using a greedy algorithm. +// Solving a Traveling Salesman Problem (TSP) with the modification, that the sites are not always points, but points and segments. +// Solving using a greedy algorithm, where a shortest edge is added to the solution if it does not produce a bifurcation or a cycle. +// Return index and "reversed" flag. +// https://en.wikipedia.org/wiki/Multi-fragment_algorithm +// The algorithm builds a tour for the traveling salesman one edge at a time and thus maintains multiple tour fragments, each of which +// is a simple path in the complete graph of cities. At each stage, the algorithm selects the edge of minimal cost that either creates +// a new fragment, extends one of the existing paths or creates a cycle of length equal to the number of cities. +template +std::vector> chain_segments_greedy_constrained_reversals_(SegmentEndPointFunc end_point_func, CouldReverseFunc could_reverse_func, size_t num_segments, const PointType *start_near) +{ + std::vector> out; + + if (num_segments == 0) { + // Nothing to do. + } + else if (num_segments == 1) + { + // Just sort the end points so that the first point visited is closest to start_near. + out.emplace_back(0, start_near != nullptr && + (end_point_func(0, true) - *start_near).template cast().squaredNorm() < (end_point_func(0, false) - *start_near).template cast().squaredNorm()); + } + else + { + // End points of segments for the KD tree closest point search. + // A single end point is inserted into the search structure for loops, two end points are entered for open paths. + struct EndPoint { + EndPoint(const Vec2d &pos) : pos(pos) {} + Vec2d pos; + // Identifier of the chain, to which this end point belongs. Zero means unassigned. + size_t chain_id = 0; + // Link to the closest currently valid end point. + EndPoint *edge_out = nullptr; + // Distance to the next end point following the link. + // Zero value -> start of the final path. + double distance_out = std::numeric_limits::max(); + size_t heap_idx = std::numeric_limits::max(); + }; + std::vector end_points; + end_points.reserve(num_segments * 2); + for (size_t i = 0; i < num_segments; ++ i) { + end_points.emplace_back(end_point_func(i, true ).template cast()); + end_points.emplace_back(end_point_func(i, false).template cast()); + } + + // Construct the closest point KD tree over end points of segments. + auto coordinate_fn = [&end_points](size_t idx, size_t dimension) -> double { return end_points[idx].pos[dimension]; }; + KDTreeIndirect<2, double, decltype(coordinate_fn)> kdtree(coordinate_fn, end_points.size()); + + // Helper to detect loops in already connected paths. + // Unique chain IDs are assigned to paths. If paths are connected, end points will not have their chain IDs updated, but the chain IDs + // will remember an "equivalent" chain ID, which is the lowest ID of all the IDs in the path, and the lowest ID is equivalent to itself. + class EquivalentChains { + public: + // Zero'th chain ID is invalid. + EquivalentChains(size_t reserve) { m_equivalent_with.reserve(reserve); m_equivalent_with.emplace_back(0); } + // Generate next equivalence class. + size_t next() { + m_equivalent_with.emplace_back(++ m_last_chain_id); + return m_last_chain_id; + } + // Get equivalence class for chain ID. + size_t operator()(size_t chain_id) { + if (chain_id != 0) { + for (size_t last = chain_id;;) { + size_t lower = m_equivalent_with[last]; + if (lower == last) { + m_equivalent_with[chain_id] = lower; + chain_id = lower; + break; + } + last = lower; + } + } + return chain_id; + } + size_t merge(size_t chain_id1, size_t chain_id2) { + size_t chain_id = std::min((*this)(chain_id1), (*this)(chain_id2)); + m_equivalent_with[chain_id1] = chain_id; + m_equivalent_with[chain_id2] = chain_id; + return chain_id; + } + +#ifndef NDEBUG + bool validate() + { + assert(m_last_chain_id >= 0); + assert(m_last_chain_id + 1 == m_equivalent_with.size()); + for (size_t i = 0; i < m_equivalent_with.size(); ++ i) { + for (size_t last = i;;) { + size_t lower = m_equivalent_with[last]; + assert(lower <= last); + if (lower == last) + break; + last = lower; + } + } + return true; + } +#endif /* NDEBUG */ + + private: + // Unique chain ID assigned to chains of end points of segments. + size_t m_last_chain_id = 0; + std::vector m_equivalent_with; + } equivalent_chain(num_segments); + + // Find the first end point closest to start_near. + EndPoint *first_point = nullptr; + size_t first_point_idx = std::numeric_limits::max(); + if (start_near != nullptr) { + size_t idx = find_closest_point(kdtree, start_near->template cast()); + assert(idx < end_points.size()); + first_point = &end_points[idx]; + first_point->distance_out = 0.; + first_point->chain_id = equivalent_chain.next(); + first_point_idx = idx; + } + EndPoint *initial_point = first_point; + EndPoint *last_point = nullptr; + + // Assign the closest point and distance to the end points. + for (EndPoint &end_point : end_points) { + assert(end_point.edge_out == nullptr); + if (&end_point != first_point) { + size_t this_idx = &end_point - &end_points.front(); + // Find the closest point to this end_point, which lies on a different extrusion path (filtered by the lambda). + // Ignore the starting point as the starting point is considered to be occupied, no end point coud connect to it. + size_t next_idx = find_closest_point(kdtree, end_point.pos, + [this_idx, first_point_idx](size_t idx){ return idx != first_point_idx && (idx ^ this_idx) > 1; }); + assert(next_idx < end_points.size()); + EndPoint &end_point2 = end_points[next_idx]; + end_point.edge_out = &end_point2; + end_point.distance_out = (end_point2.pos - end_point.pos).squaredNorm(); + } + } + + // Initialize a heap of end points sorted by the lowest distance to the next valid point of a path. + auto queue = make_mutable_priority_queue( + [](EndPoint *ep, size_t idx){ ep->heap_idx = idx; }, + [](EndPoint *l, EndPoint *r){ return l->distance_out < r->distance_out; }); + queue.reserve(end_points.size() * 2 - 1); + for (EndPoint &ep : end_points) + if (first_point != &ep) + queue.push(&ep); + +#ifndef NDEBUG + auto validate_graph_and_queue = [&equivalent_chain, &end_points, &queue, first_point]() -> bool { + assert(equivalent_chain.validate()); + for (EndPoint &ep : end_points) { + if (ep.heap_idx < queue.size()) { + // End point is on the heap. + assert(*(queue.cbegin() + ep.heap_idx) == &ep); + assert(ep.chain_id == 0); + } else { + // End point is NOT on the heap, therefore it is part of the output path. + assert(ep.heap_idx == std::numeric_limits::max()); + assert(ep.chain_id != 0); + if (&ep == first_point) { + assert(ep.edge_out == nullptr); + } else { + assert(ep.edge_out != nullptr); + // Detect loops. + for (EndPoint *pt = &ep; pt != nullptr;) { + // Out of queue. It is a final point. + assert(pt->heap_idx == std::numeric_limits::max()); + EndPoint *pt_other = &end_points[(pt - &end_points.front()) ^ 1]; + if (pt_other->heap_idx < queue.size()) + // The other side of this segment is undecided yet. + break; + pt = pt_other->edge_out; + } + } + } + } + for (EndPoint *ep : queue) + // Points in the queue are not connected yet. + assert(ep->chain_id == 0); + return true; + }; +#endif /* NDEBUG */ + + // Chain the end points: find (num_segments - 1) shortest links not forming bifurcations or loops. + assert(num_segments >= 2); + for (int iter = int(num_segments) - 2;; -- iter) { + assert(validate_graph_and_queue()); + // Take the first end point, for which the link points to the currently closest valid neighbor. + EndPoint &end_point1 = *queue.top(); + assert(end_point1.edge_out != nullptr); + // No point on the queue may be connected yet. + assert(end_point1.chain_id == 0); + // Take the closest end point to the first end point, + EndPoint &end_point2 = *end_point1.edge_out; + bool valid = true; + size_t end_point1_other_chain_id = 0; + size_t end_point2_other_chain_id = 0; + if (end_point2.chain_id > 0) { + // The other side is part of the output path. Don't connect to end_point2, update end_point1 and try another one. + valid = false; + } else { + // End points of the opposite ends of the segments. + end_point1_other_chain_id = equivalent_chain(end_points[(&end_point1 - &end_points.front()) ^ 1].chain_id); + end_point2_other_chain_id = equivalent_chain(end_points[(&end_point2 - &end_points.front()) ^ 1].chain_id); + if (end_point1_other_chain_id == end_point2_other_chain_id && end_point1_other_chain_id != 0) + // This edge forms a loop. Update end_point1 and try another one. + valid = false; + } + if (valid) { + // Remove the first and second point from the queue. + queue.pop(); + queue.remove(end_point2.heap_idx); + assert(end_point1.edge_out = &end_point2); + end_point2.edge_out = &end_point1; + end_point2.distance_out = end_point1.distance_out; + // Assign chain IDs to the newly connected end points, set equivalent_chain if two chains were merged. + size_t chain_id = + (end_point1_other_chain_id == 0) ? + ((end_point2_other_chain_id == 0) ? equivalent_chain.next() : end_point2_other_chain_id) : + ((end_point2_other_chain_id == 0) ? end_point1_other_chain_id : + (end_point1_other_chain_id == end_point2_other_chain_id) ? + end_point1_other_chain_id : + equivalent_chain.merge(end_point1_other_chain_id, end_point2_other_chain_id)); + end_point1.chain_id = chain_id; + end_point2.chain_id = chain_id; + assert(validate_graph_and_queue()); + if (iter == 0) { + // Last iteration. There shall be exactly one or two end points waiting to be connected. + assert(queue.size() == ((first_point == nullptr) ? 2 : 1)); + if (first_point == nullptr) { + first_point = queue.top(); + queue.pop(); + first_point->edge_out = nullptr; + } + last_point = queue.top(); + last_point->edge_out = nullptr; + queue.pop(); + assert(queue.empty()); + break; + } + } else { + // This edge forms a loop. Update end_point1 and try another one. + ++ iter; + end_point1.edge_out = nullptr; + // Update edge_out and distance. + size_t this_idx = &end_point1 - &end_points.front(); + // Find the closest point to this end_point, which lies on a different extrusion path (filtered by the filter lambda). + size_t next_idx = find_closest_point(kdtree, end_point1.pos, [&end_points, &equivalent_chain, this_idx](size_t idx) { + assert(end_points[this_idx].edge_out == nullptr); + assert(end_points[this_idx].chain_id == 0); + if ((idx ^ this_idx) <= 1 || end_points[idx].chain_id != 0) + // Points of the same segment shall not be connected, + // cannot connect to an already connected point (ideally those would be removed from the KD tree, but the update is difficult). + return false; + size_t chain1 = equivalent_chain(end_points[this_idx ^ 1].chain_id); + size_t chain2 = equivalent_chain(end_points[idx ^ 1].chain_id); + return chain1 != chain2 || chain1 == 0; + }); + assert(next_idx < end_points.size()); + end_point1.edge_out = &end_points[next_idx]; + end_point1.distance_out = (end_points[next_idx].pos - end_point1.pos).squaredNorm(); + // Update position of this end point in the queue based on the distance calculated at the line above. + queue.update(end_point1.heap_idx); + //FIXME Remove the other end point from the KD tree. + // As the KD tree update is expensive, do it only after some larger number of points is removed from the queue. + assert(validate_graph_and_queue()); + } + } + assert(queue.empty()); + + // Now interconnect pairs of segments into a chain. + assert(first_point != nullptr); + out.reserve(num_segments); + bool failed = false; + do { + assert(out.size() < num_segments); + size_t first_point_id = first_point - &end_points.front(); + size_t segment_id = first_point_id >> 1; + bool reverse = (first_point_id & 1) != 0; + EndPoint *second_point = &end_points[first_point_id ^ 1]; + if (REVERSE_COULD_FAIL) { + if (reverse && ! could_reverse_func(segment_id)) { + failed = true; + break; + } + } else { + assert(! reverse || could_reverse_func(segment_id)); + } + out.emplace_back(segment_id, reverse); + first_point = second_point->edge_out; + } while (first_point != nullptr); + if (REVERSE_COULD_FAIL) { + if (failed) { + if (start_near == nullptr) { + // We may try the reverse order. + out.clear(); + first_point = last_point; + failed = false; + do { + assert(out.size() < num_segments); + size_t first_point_id = first_point - &end_points.front(); + size_t segment_id = first_point_id >> 1; + bool reverse = (first_point_id & 1) != 0; + EndPoint *second_point = &end_points[first_point_id ^ 1]; + if (reverse && ! could_reverse_func(segment_id)) { + failed = true; + break; + } + out.emplace_back(segment_id, reverse); + first_point = second_point->edge_out; + } while (first_point != nullptr); + } + } + if (failed) + // As a last resort, try a dumb algorithm, which is not sensitive to edge reversal constraints. + out = chain_segments_closest_point(end_points, kdtree, could_reverse_func, (initial_point != nullptr) ? *initial_point : end_points.front()); + } else { + assert(! failed); + } + } + + assert(out.size() == num_segments); + return out; +} + +template +std::vector> chain_segments_greedy_constrained_reversals(SegmentEndPointFunc end_point_func, CouldReverseFunc could_reverse_func, size_t num_segments, const PointType *start_near) +{ + return chain_segments_greedy_constrained_reversals_(end_point_func, could_reverse_func, num_segments, start_near); +} + +template +std::vector> chain_segments_greedy(SegmentEndPointFunc end_point_func, size_t num_segments, const PointType *start_near) +{ + auto could_reverse_func = [](size_t /* idx */) -> bool { return true; }; + return chain_segments_greedy_constrained_reversals_(end_point_func, could_reverse_func, num_segments, start_near); +} + +std::vector> chain_extrusion_entities(std::vector &entities, const Point *start_near) +{ + auto segment_end_point = [&entities](size_t idx, bool first_point) -> const Point& { return first_point ? entities[idx]->first_point() : entities[idx]->last_point(); }; + auto could_reverse = [&entities](size_t idx) { const ExtrusionEntity *ee = entities[idx]; return ee->is_loop() || ee->can_reverse(); }; + std::vector> out = chain_segments_greedy_constrained_reversals(segment_end_point, could_reverse, entities.size(), start_near); + for (size_t i = 0; i < entities.size(); ++ i) { + ExtrusionEntity *ee = entities[i]; + if (ee->is_loop()) + // Ignore reversals for loops, as the start point equals the end point. + out[i].second = false; + // Is can_reverse() respected by the reversals? + assert(entities[i]->can_reverse() || ! out[i].second); + } + return out; +} + +void reorder_extrusion_entities(std::vector &entities, const std::vector> &chain) +{ + assert(entities.size() == chain.size()); + std::vector out; + out.reserve(entities.size()); + for (const std::pair &idx : chain) { + assert(entities[idx.first] != nullptr); + out.emplace_back(entities[idx.first]); + if (idx.second) + out.back()->reverse(); + } + entities.swap(out); +} + +void chain_and_reorder_extrusion_entities(std::vector &entities, const Point *start_near) +{ + reorder_extrusion_entities(entities, chain_extrusion_entities(entities, start_near)); +} + +std::vector> chain_extrusion_paths(std::vector &extrusion_paths, const Point *start_near) +{ + auto segment_end_point = [&extrusion_paths](size_t idx, bool first_point) -> const Point& { return first_point ? extrusion_paths[idx].first_point() : extrusion_paths[idx].last_point(); }; + return chain_segments_greedy(segment_end_point, extrusion_paths.size(), start_near); +} + +void reorder_extrusion_paths(std::vector &extrusion_paths, const std::vector> &chain) +{ + assert(extrusion_paths.size() == chain.size()); + std::vector out; + out.reserve(extrusion_paths.size()); + for (const std::pair &idx : chain) { + out.emplace_back(std::move(extrusion_paths[idx.first])); + if (idx.second) + out.back().reverse(); + } + extrusion_paths.swap(out); +} + +void chain_and_reorder_extrusion_paths(std::vector &extrusion_paths, const Point *start_near) +{ + reorder_extrusion_paths(extrusion_paths, chain_extrusion_paths(extrusion_paths, start_near)); +} + +std::vector chain_points(const Points &points, Point *start_near) +{ + auto segment_end_point = [&points](size_t idx, bool /* first_point */) -> const Point& { return points[idx]; }; + std::vector> ordered = chain_segments_greedy(segment_end_point, points.size(), start_near); + std::vector out; + out.reserve(ordered.size()); + for (auto &segment_and_reversal : ordered) + out.emplace_back(segment_and_reversal.first); + return out; +} + +Polylines chain_polylines(Polylines &&polylines, const Point *start_near) +{ + auto segment_end_point = [&polylines](size_t idx, bool first_point) -> const Point& { return first_point ? polylines[idx].first_point() : polylines[idx].last_point(); }; + std::vector> ordered = chain_segments_greedy(segment_end_point, polylines.size(), start_near); + Polylines out; + out.reserve(polylines.size()); + for (auto &segment_and_reversal : ordered) { + out.emplace_back(std::move(polylines[segment_and_reversal.first])); + if (segment_and_reversal.second) + out.back().reverse(); + } + return out; +} + +template static inline T chain_path_items(const Points &points, const T &items) +{ + auto segment_end_point = [&points](size_t idx, bool /* first_point */) -> const Point& { return points[idx]; }; + std::vector> ordered = chain_segments_greedy(segment_end_point, points.size(), nullptr); + T out; + out.reserve(items.size()); + for (auto &segment_and_reversal : ordered) + out.emplace_back(items[segment_and_reversal.first]); + return out; +} + +ClipperLib::PolyNodes chain_clipper_polynodes(const Points &points, const ClipperLib::PolyNodes &items) +{ + return chain_path_items(points, items); +} + +std::vector> chain_print_object_instances(const Print &print) +{ + // Order objects using a nearest neighbor search. + Points object_reference_points; + std::vector> instances; + for (size_t i = 0; i < print.objects().size(); ++ i) { + const PrintObject &object = *print.objects()[i]; + for (size_t j = 0; j < object.copies().size(); ++ j) { + object_reference_points.emplace_back(object.copy_center(j)); + instances.emplace_back(i, j); + } + } + auto segment_end_point = [&object_reference_points](size_t idx, bool /* first_point */) -> const Point& { return object_reference_points[idx]; }; + std::vector> ordered = chain_segments_greedy(segment_end_point, instances.size(), nullptr); + std::vector> out; + out.reserve(instances.size()); + for (auto &segment_and_reversal : ordered) + out.emplace_back(instances[segment_and_reversal.first]); + return out; +} + +} // namespace Slic3r diff --git a/src/libslic3r/ShortestPath.hpp b/src/libslic3r/ShortestPath.hpp new file mode 100644 index 000000000..cd342015d --- /dev/null +++ b/src/libslic3r/ShortestPath.hpp @@ -0,0 +1,38 @@ +#ifndef slic3r_ShortestPath_hpp_ +#define slic3r_ShortestPath_hpp_ + +#include "libslic3r.h" +#include "ExtrusionEntity.hpp" +#include "Point.hpp" + +#include +#include + +namespace ClipperLib { class PolyNode; } + +namespace Slic3r { + +std::vector chain_points(const Points &points, Point *start_near = nullptr); + +std::vector> chain_extrusion_entities(std::vector &entities, const Point *start_near = nullptr); +void reorder_extrusion_entities(std::vector &entities, const std::vector> &chain); +void chain_and_reorder_extrusion_entities(std::vector &entities, const Point *start_near = nullptr); + +std::vector> chain_extrusion_paths(std::vector &extrusion_paths, const Point *start_near = nullptr); +void reorder_extrusion_paths(std::vector &extrusion_paths, std::vector> &chain); +void chain_and_reorder_extrusion_paths(std::vector &extrusion_paths, const Point *start_near = nullptr); + +Polylines chain_polylines(Polylines &&src, const Point *start_near = nullptr); +inline Polylines chain_polylines(const Polylines& src, const Point* start_near = nullptr) { Polylines tmp(src); return chain_polylines(std::move(tmp), start_near); } + +std::vector chain_clipper_polynodes(const Points &points, const std::vector &items); + +// Chain instances of print objects by an approximate shortest path. +// Returns pairs of PrintObject idx and instance of that PrintObject. +class Print; +std::vector> chain_print_object_instances(const Print &print); + + +} // namespace Slic3r + +#endif /* slic3r_ShortestPath_hpp_ */ diff --git a/src/libslic3r/SupportMaterial.cpp b/src/libslic3r/SupportMaterial.cpp index 505fdcaf5..a6648f108 100644 --- a/src/libslic3r/SupportMaterial.cpp +++ b/src/libslic3r/SupportMaterial.cpp @@ -783,7 +783,7 @@ namespace SupportMaterialInternal { for (const ExtrusionPath &ep : loop.paths) if (ep.role() == erOverhangPerimeter && ! ep.polyline.empty()) return ep.size() >= (ep.is_closed() ? 3 : 2); - return false; + return false; } static bool has_bridging_perimeters(const ExtrusionEntityCollection &perimeters) { @@ -923,7 +923,7 @@ namespace SupportMaterialInternal { //FIXME add supports at regular intervals to support long bridges! bridges = diff(bridges, // Offset unsupported edges into polygons. - offset(layerm->unsupported_bridge_edges.polylines, scale_(SUPPORT_MATERIAL_MARGIN), SUPPORT_SURFACES_OFFSET_PARAMETERS)); + offset(layerm->unsupported_bridge_edges, scale_(SUPPORT_MATERIAL_MARGIN), SUPPORT_SURFACES_OFFSET_PARAMETERS)); // Remove bridged areas from the supported areas. contact_polygons = diff(contact_polygons, bridges, true); } @@ -2125,7 +2125,7 @@ void PrintObjectSupportMaterial::trim_support_layers_by_object( } // $layer->slices contains the full shape of layer, thus including // perimeter's width. $support contains the full shape of support - // material, thus including the width of its foremost extrusion. + // material, thus including the width of its foremost extrusion. // We leave a gap equal to a full extrusion width. support_layer.polygons = diff(support_layer.polygons, polygons_trimming); } @@ -2934,20 +2934,13 @@ void PrintObjectSupportMaterial::generate_toolpaths( // Prepare fillers. SupportMaterialPattern support_pattern = m_object_config->support_material_pattern; bool with_sheath = m_object_config->support_material_with_sheath; - InfillPattern infill_pattern; + InfillPattern infill_pattern = (support_pattern == smpHoneycomb ? ipHoneycomb : ipRectilinear); std::vector angles; angles.push_back(base_angle); - switch (support_pattern) { - case smpRectilinearGrid: + + if (support_pattern == smpRectilinearGrid) angles.push_back(interface_angle); - // fall through - case smpRectilinear: - infill_pattern = ipRectilinear; - break; - case smpHoneycomb: - infill_pattern = ipHoneycomb; - break; - } + BoundingBox bbox_object(Point(-scale_(1.), -scale_(1.0)), Point(scale_(1.), scale_(1.))); // const coordf_t link_max_length_factor = 3.; @@ -3217,7 +3210,7 @@ void PrintObjectSupportMaterial::generate_toolpaths( density = 0.5f; flow = m_first_layer_flow; // use the proper spacing for first layer as we don't need to align - // its pattern to the other layers + // its pattern to the other layers //FIXME When paralellizing, each thread shall have its own copy of the fillers. filler->spacing = flow.spacing(); filler->link_max_length = coord_t(scale_(filler->spacing * link_max_length_factor / density)); diff --git a/src/slic3r/Config/Snapshot.cpp b/src/slic3r/Config/Snapshot.cpp index 521a7c531..7e17542b9 100644 --- a/src/slic3r/Config/Snapshot.cpp +++ b/src/slic3r/Config/Snapshot.cpp @@ -342,7 +342,7 @@ static void copy_config_dir_single_level(const boost::filesystem::path &path_src ! boost::filesystem::create_directory(path_dst)) throw std::runtime_error(std::string("Slic3r was unable to create a directory at ") + path_dst.string()); - for (auto &dir_entry : boost::filesystem::directory_iterator(path_src)) + for (auto &dir_entry : boost::filesystem::directory_iterator(path_src)) if (Slic3r::is_ini_file(dir_entry)) boost::filesystem::copy_file(dir_entry.path(), path_dst / dir_entry.path().filename(), boost::filesystem::copy_option::overwrite_if_exists); } @@ -351,7 +351,7 @@ static void delete_existing_ini_files(const boost::filesystem::path &path) { if (! boost::filesystem::is_directory(path)) return; - for (auto &dir_entry : boost::filesystem::directory_iterator(path)) + for (auto &dir_entry : boost::filesystem::directory_iterator(path)) if (boost::filesystem::is_regular_file(dir_entry.status()) && boost::algorithm::iends_with(dir_entry.path().filename().string(), ".ini")) boost::filesystem::remove(dir_entry.path()); } @@ -378,7 +378,7 @@ const Snapshot& SnapshotDB::take_snapshot(const AppConfig &app_config, Snapshot: sprintf(name, "filament_%u", i); if (! app_config.has("presets", name)) break; - snapshot.filaments.emplace_back(app_config.get("presets", name)); + snapshot.filaments.emplace_back(app_config.get("presets", name)); } // Vendor specific config bundles and installed printers. for (const std::pair>> &vendor : app_config.vendors()) { @@ -417,7 +417,7 @@ const Snapshot& SnapshotDB::take_snapshot(const AppConfig &app_config, Snapshot: // Backup the presets. for (const char *subdir : { "print", "filament", "printer", "vendor" }) copy_config_dir_single_level(data_dir / subdir, snapshot_dir / subdir); - snapshot.save_ini((snapshot_dir / "snapshot.ini").string()); + snapshot.save_ini((snapshot_dir / "snapshot.ini").string()); assert(m_snapshots.empty() || m_snapshots.back().time_captured <= snapshot.time_captured); m_snapshots.emplace_back(std::move(snapshot)); return m_snapshots.back(); diff --git a/src/slic3r/Config/Version.cpp b/src/slic3r/Config/Version.cpp index 175abff69..3f8f960f1 100644 --- a/src/slic3r/Config/Version.cpp +++ b/src/slic3r/Config/Version.cpp @@ -227,9 +227,9 @@ size_t Index::load(const boost::filesystem::path &path) // End of semver or keyword. break; } - if (*key_end != 0 && *key_end != ' ' && *key_end != '\t' && *key_end != '=') + if (*key_end != 0 && *key_end != ' ' && *key_end != '\t' && *key_end != '=') throw file_parser_error("Invalid keyword or semantic version", path, idx_line); - char *value = left_trim(key_end); + char *value = left_trim(key_end); bool key_value_pair = *value == '='; if (key_value_pair) value = left_trim(value + 1); @@ -245,11 +245,11 @@ size_t Index::load(const boost::filesystem::path &path) if (strcmp(key, "min_slic3r_version") == 0 || strcmp(key, "max_slic3r_version") == 0) { if (! svalue.empty()) semver = Semver::parse(svalue); - if (! semver) + if (! semver) throw file_parser_error(std::string(key) + " must referece a valid semantic version", path, idx_line); - if (strcmp(key, "min_slic3r_version") == 0) + if (strcmp(key, "min_slic3r_version") == 0) ver.min_slic3r_version = *semver; - else + else ver.max_slic3r_version = *semver; } else { // Ignore unknown keys, as there may come new keys in the future. diff --git a/src/slic3r/GUI/BedShapeDialog.cpp b/src/slic3r/GUI/BedShapeDialog.cpp index 90fc0ff80..5624ada9d 100644 --- a/src/slic3r/GUI/BedShapeDialog.cpp +++ b/src/slic3r/GUI/BedShapeDialog.cpp @@ -455,7 +455,7 @@ void BedShapePanel::update_shape() else if (page_idx == SHAPE_CUSTOM) m_shape = m_loaded_shape; - update_preview(); + update_preview(); } // Loads an stl file, projects it to the XY plane and calculates a polygon. diff --git a/src/slic3r/GUI/ConfigSnapshotDialog.cpp b/src/slic3r/GUI/ConfigSnapshotDialog.cpp index 836a0a4d3..c89e4895e 100644 --- a/src/slic3r/GUI/ConfigSnapshotDialog.cpp +++ b/src/slic3r/GUI/ConfigSnapshotDialog.cpp @@ -42,7 +42,7 @@ static wxString generate_html_row(const Config::Snapshot &snapshot, bool row_eve text += " (" + wxString::FromUTF8(snapshot.comment.data()) + ")"; text += "
"; // End of row header. - text += _(L("slic3r version")) + ": " + snapshot.slic3r_version_captured.to_string() + "
"; + text += _(L("PrusaSlicer version")) + ": " + snapshot.slic3r_version_captured.to_string() + "
"; text += _(L("print")) + ": " + snapshot.print + "
"; text += _(L("filaments")) + ": " + snapshot.filaments.front() + "
"; text += _(L("printer")) + ": " + snapshot.printer + "
"; @@ -50,9 +50,9 @@ static wxString generate_html_row(const Config::Snapshot &snapshot, bool row_eve bool compatible = true; for (const Config::Snapshot::VendorConfig &vc : snapshot.vendor_configs) { text += _(L("vendor")) + ": " + vc.name +", " + _(L("version")) + ": " + vc.version.config_version.to_string() + - ", " + _(L("min slic3r version")) + ": " + vc.version.min_slic3r_version.to_string(); + ", " + _(L("min PrusaSlicer version")) + ": " + vc.version.min_slic3r_version.to_string(); if (vc.version.max_slic3r_version != Semver::inf()) - text += ", " + _(L("max slic3r version")) + ": " + vc.version.max_slic3r_version.to_string(); + text += ", " + _(L("max PrusaSlicer version")) + ": " + vc.version.max_slic3r_version.to_string(); text += "
"; for (const std::pair> &model : vc.models_variants_installed) { text += _(L("model")) + ": " + model.first + ", " + _(L("variants")) + ": "; diff --git a/src/slic3r/GUI/ConfigWizard.cpp b/src/slic3r/GUI/ConfigWizard.cpp index cbbaa32b6..4038426ba 100644 --- a/src/slic3r/GUI/ConfigWizard.cpp +++ b/src/slic3r/GUI/ConfigWizard.cpp @@ -1792,13 +1792,16 @@ bool ConfigWizard::run(RunReason reason, StartPage start_page) const wxString& ConfigWizard::name(const bool from_menu/* = false*/) { - // A different naming convention is used for the Wizard on Windows vs. OSX & GTK. + // A different naming convention is used for the Wizard on Windows & GTK vs. OSX. + // Note: Don't call _() macro here. + // This function just return the current name according to the OS. + // Translation is implemented inside GUI_App::add_config_menu() #if __APPLE__ - static const wxString config_wizard_name = _(L("Configuration Assistant")); - static const wxString config_wizard_name_menu = _(L("Configuration &Assistant")); + static const wxString config_wizard_name = L("Configuration Assistant"); + static const wxString config_wizard_name_menu = L("Configuration &Assistant"); #else - static const wxString config_wizard_name = _(L("Configuration Wizard")); - static const wxString config_wizard_name_menu = _(L("Configuration &Wizard")); + static const wxString config_wizard_name = L("Configuration Wizard"); + static const wxString config_wizard_name_menu = L("Configuration &Wizard"); #endif return from_menu ? config_wizard_name_menu : config_wizard_name; } diff --git a/src/slic3r/GUI/GLCanvas3D.cpp b/src/slic3r/GUI/GLCanvas3D.cpp index edb9c7830..9f5dd16e7 100644 --- a/src/slic3r/GUI/GLCanvas3D.cpp +++ b/src/slic3r/GUI/GLCanvas3D.cpp @@ -928,8 +928,8 @@ bool GLCanvas3D::LegendTexture::generate(const GCodePreviewData& preview_data, c if (items_count > 1) m_original_height += (items_count - 1) * scaled_square_contour; - m_width = (int)next_highest_power_of_2((uint32_t)m_original_width); - m_height = (int)next_highest_power_of_2((uint32_t)m_original_height); + m_width = (int)next_highest_power_of_2((uint32_t)m_original_width); + m_height = (int)next_highest_power_of_2((uint32_t)m_original_height); // generates bitmap wxBitmap bitmap(m_width, m_height); @@ -1094,7 +1094,7 @@ void GLCanvas3D::LegendTexture::render(const GLCanvas3D& canvas) const wxDEFINE_EVENT(EVT_GLCANVAS_INIT, SimpleEvent); wxDEFINE_EVENT(EVT_GLCANVAS_SCHEDULE_BACKGROUND_PROCESS, SimpleEvent); wxDEFINE_EVENT(EVT_GLCANVAS_OBJECT_SELECT, SimpleEvent); -wxDEFINE_EVENT(EVT_GLCANVAS_RIGHT_CLICK, Vec2dEvent); +wxDEFINE_EVENT(EVT_GLCANVAS_RIGHT_CLICK, RBtnEvent); wxDEFINE_EVENT(EVT_GLCANVAS_REMOVE_OBJECT, SimpleEvent); wxDEFINE_EVENT(EVT_GLCANVAS_ARRANGE, SimpleEvent); wxDEFINE_EVENT(EVT_GLCANVAS_SELECT_ALL, SimpleEvent); @@ -1882,7 +1882,7 @@ void GLCanvas3D::reload_scene(bool refresh_immediately, bool force_full_scene_re if (m_reload_delayed) return; - bool update_object_list = false; + bool update_object_list = false; if (m_volumes.volumes != glvolumes_new) update_object_list = true; @@ -3012,15 +3012,16 @@ void GLCanvas3D::on_mouse(wxMouseEvent& evt) wxGetApp().obj_manipul()->set_dirty(); // forces a frame render to update the view before the context menu is shown render(); - - Vec2d logical_pos = pos.cast(); -#if ENABLE_RETINA_GL - const float factor = m_retina_helper->get_scale_factor(); - logical_pos = logical_pos.cwiseQuotient(Vec2d(factor, factor)); -#endif // ENABLE_RETINA_GL - post_event(Vec2dEvent(EVT_GLCANVAS_RIGHT_CLICK, logical_pos)); } } + Vec2d logical_pos = pos.cast(); +#if ENABLE_RETINA_GL + const float factor = m_retina_helper->get_scale_factor(); + logical_pos = logical_pos.cwiseQuotient(Vec2d(factor, factor)); +#endif // ENABLE_RETINA_GL + if (!m_mouse.dragging) + // do not post the event if the user is panning the scene + post_event(RBtnEvent(EVT_GLCANVAS_RIGHT_CLICK, { logical_pos, m_hover_volume_idxs.empty() })); } mouse_up_cleanup(); @@ -3372,7 +3373,7 @@ void GLCanvas3D::do_mirror(const std::string& snapshot_type) void GLCanvas3D::set_camera_zoom(double zoom) { const Size& cnv_size = get_canvas_size(); - m_camera.set_zoom(zoom, _max_bounding_box(false, false), cnv_size.get_width(), cnv_size.get_height()); + m_camera.set_zoom(zoom, _max_bounding_box(false, true), cnv_size.get_width(), cnv_size.get_height()); m_dirty = true; } @@ -3388,10 +3389,9 @@ void GLCanvas3D::handle_sidebar_focus_event(const std::string& opt_key, bool foc m_sidebar_field = focus_on ? opt_key : ""; if (!m_sidebar_field.empty()) - { m_gizmos.reset_all_states(); - m_dirty = true; - } + + m_dirty = true; } void GLCanvas3D::handle_layers_data_focus_event(const t_layer_height_range range, const EditorType type) @@ -5000,6 +5000,8 @@ void GLCanvas3D::_load_gcode_extrusion_paths(const GCodePreviewData& preview_dat return path.width; case GCodePreviewData::Extrusion::Feedrate: return path.feedrate; + case GCodePreviewData::Extrusion::FanSpeed: + return path.fan_speed; case GCodePreviewData::Extrusion::VolumetricRate: return path.feedrate * (float)path.mm3_per_mm; case GCodePreviewData::Extrusion::Tool: @@ -5025,6 +5027,8 @@ void GLCanvas3D::_load_gcode_extrusion_paths(const GCodePreviewData& preview_dat return data.get_width_color(value); case GCodePreviewData::Extrusion::Feedrate: return data.get_feedrate_color(value); + case GCodePreviewData::Extrusion::FanSpeed: + return data.get_fan_speed_color(value); case GCodePreviewData::Extrusion::VolumetricRate: return data.get_volumetric_rate_color(value); case GCodePreviewData::Extrusion::Tool: @@ -5069,14 +5073,14 @@ void GLCanvas3D::_load_gcode_extrusion_paths(const GCodePreviewData& preview_dat for (const GCodePreviewData::Extrusion::Layer &layer : preview_data.extrusion.layers) for (const ExtrusionPath &path : layer.paths) ++ num_paths_per_role[size_t(path.role())]; - std::vector> roles_values; + std::vector> roles_values; roles_values.assign(size_t(erCount), std::vector()); for (size_t i = 0; i < roles_values.size(); ++ i) roles_values[i].reserve(num_paths_per_role[i]); - for (const GCodePreviewData::Extrusion::Layer& layer : preview_data.extrusion.layers) + for (const GCodePreviewData::Extrusion::Layer& layer : preview_data.extrusion.layers) for (const ExtrusionPath& path : layer.paths) roles_values[size_t(path.role())].emplace_back(Helper::path_filter(preview_data.extrusion.view_type, path)); - roles_filters.reserve(size_t(erCount)); + roles_filters.reserve(size_t(erCount)); size_t num_buffers = 0; for (std::vector &values : roles_values) { sort_remove_duplicates(values); diff --git a/src/slic3r/GUI/GLCanvas3D.hpp b/src/slic3r/GUI/GLCanvas3D.hpp index b15402a52..fb767360c 100644 --- a/src/slic3r/GUI/GLCanvas3D.hpp +++ b/src/slic3r/GUI/GLCanvas3D.hpp @@ -71,6 +71,8 @@ public: wxDECLARE_EVENT(EVT_GLCANVAS_OBJECT_SELECT, SimpleEvent); using Vec2dEvent = Event; +// _bool_ value is used as a indicator of selection in the 3DScene +using RBtnEvent = Event>; template using Vec2dsEvent = ArrayEvent; using Vec3dEvent = Event; @@ -78,7 +80,7 @@ template using Vec3dsEvent = ArrayEvent; wxDECLARE_EVENT(EVT_GLCANVAS_INIT, SimpleEvent); wxDECLARE_EVENT(EVT_GLCANVAS_SCHEDULE_BACKGROUND_PROCESS, SimpleEvent); -wxDECLARE_EVENT(EVT_GLCANVAS_RIGHT_CLICK, Vec2dEvent); +wxDECLARE_EVENT(EVT_GLCANVAS_RIGHT_CLICK, RBtnEvent); wxDECLARE_EVENT(EVT_GLCANVAS_REMOVE_OBJECT, SimpleEvent); wxDECLARE_EVENT(EVT_GLCANVAS_ARRANGE, SimpleEvent); wxDECLARE_EVENT(EVT_GLCANVAS_SELECT_ALL, SimpleEvent); diff --git a/src/slic3r/GUI/GUI_App.cpp b/src/slic3r/GUI/GUI_App.cpp index b5d9c3d61..b5e70c0a1 100644 --- a/src/slic3r/GUI/GUI_App.cpp +++ b/src/slic3r/GUI/GUI_App.cpp @@ -725,7 +725,7 @@ bool GUI_App::load_language(wxString language, bool initial) #endif if (initial) message + "\n\nApplication will close."; - wxMessageBox(message, "PrusaSlicer - Switching language failed", wxOK | wxICON_ERROR); + wxMessageBox(message, "PrusaSlicer - Switching language failed", wxOK | wxICON_ERROR); if (initial) std::exit(EXIT_FAILURE); else diff --git a/src/slic3r/GUI/GUI_ObjectLayers.cpp b/src/slic3r/GUI/GUI_ObjectLayers.cpp index 9053cdd42..d209214ae 100644 --- a/src/slic3r/GUI/GUI_ObjectLayers.cpp +++ b/src/slic3r/GUI/GUI_ObjectLayers.cpp @@ -284,6 +284,9 @@ LayerRangeEditor::LayerRangeEditor( ObjectLayers* parent, wxSize(8 * em_unit(parent->m_parent), wxDefaultCoord), wxTE_PROCESS_ENTER) { this->SetFont(wxGetApp().normal_font()); + + // Reset m_enter_pressed flag to _false_, when value is editing + this->Bind(wxEVT_TEXT, [this](wxEvent&) { m_enter_pressed = false; }, this->GetId()); this->Bind(wxEVT_TEXT_ENTER, [this, edit_fn](wxEvent&) { @@ -307,7 +310,7 @@ LayerRangeEditor::LayerRangeEditor( ObjectLayers* parent, if (!m_enter_pressed) { #ifndef __WXGTK__ /* Update data for next editor selection. - * But under GTK it lucks like there is no information about selected control at e.GetWindow(), + * But under GTK it looks like there is no information about selected control at e.GetWindow(), * so we'll take it from wxEVT_LEFT_DOWN event * */ LayerRangeEditor* new_editor = dynamic_cast(e.GetWindow()); diff --git a/src/slic3r/GUI/GUI_ObjectList.cpp b/src/slic3r/GUI/GUI_ObjectList.cpp index 49f0f6fa2..0835a0d40 100644 --- a/src/slic3r/GUI/GUI_ObjectList.cpp +++ b/src/slic3r/GUI/GUI_ObjectList.cpp @@ -131,7 +131,7 @@ ObjectList::ObjectList(wxWindow* parent) : { wxDataViewItemArray sels; GetSelections(sels); - if (sels.front() == m_last_selected_item) + if (! sels.empty() && sels.front() == m_last_selected_item) m_last_selected_item = sels.back(); else m_last_selected_item = event.GetItem(); @@ -255,21 +255,32 @@ void ObjectList::create_objects_ctrl() EnableDropTarget(wxDF_UNICODETEXT); #endif // wxUSE_DRAG_AND_DROP && wxUSE_UNICODE + const int em = wxGetApp().em_unit(); + // column ItemName(Icon+Text) of the view control: // And Icon can be consisting of several bitmaps AppendColumn(new wxDataViewColumn(_(L("Name")), new BitmapTextRenderer(), - colName, 20*wxGetApp().em_unit()/*200*/, wxALIGN_LEFT, wxDATAVIEW_COL_RESIZABLE)); + colName, 20*em, wxALIGN_LEFT, wxDATAVIEW_COL_RESIZABLE)); // column PrintableProperty (Icon) of the view control: - AppendBitmapColumn(" ", colPrint, wxDATAVIEW_CELL_INERT, int(2 * wxGetApp().em_unit()), + AppendBitmapColumn(" ", colPrint, wxDATAVIEW_CELL_INERT, 3*em, wxALIGN_CENTER_HORIZONTAL, wxDATAVIEW_COL_RESIZABLE); // column Extruder of the view control: AppendColumn(create_objects_list_extruder_column(4)); // column ItemEditing of the view control: - AppendBitmapColumn(_(L("Editing")), colEditing, wxDATAVIEW_CELL_INERT, int(2.5 * wxGetApp().em_unit())/*25*/, + AppendBitmapColumn(_(L("Editing")), colEditing, wxDATAVIEW_CELL_INERT, 3*em, wxALIGN_CENTER_HORIZONTAL, wxDATAVIEW_COL_RESIZABLE); + + // For some reason under OSX on 4K(5K) monitors in wxDataViewColumn constructor doesn't set width of column. + // Therefore, force set column width. + if (wxOSX) + { + GetColumn(colName)->SetWidth(20*em); + GetColumn(colPrint)->SetWidth(3*em); + GetColumn(colExtruder)->SetWidth(8*em); + } } void ObjectList::create_popup_menus() @@ -279,6 +290,7 @@ void ObjectList::create_popup_menus() create_part_popupmenu(&m_menu_part); create_sla_object_popupmenu(&m_menu_sla_object); create_instance_popupmenu(&m_menu_instance); + create_default_popupmenu(&m_menu_default); } void ObjectList::get_selected_item_indexes(int& obj_idx, int& vol_idx, const wxDataViewItem& input_item/* = wxDataViewItem(nullptr)*/) @@ -741,9 +753,9 @@ void ObjectList::paste_volumes_into_list(int obj_idx, const ModelVolumePtrs& vol } select_items(items); -#ifndef __WXOSX__ //#ifdef __WXMSW__ // #ys_FIXME +//#ifndef __WXOSX__ //#ifdef __WXMSW__ // #ys_FIXME selection_changed(); -#endif //no __WXOSX__ //__WXMSW__ +//#endif //no __WXOSX__ //__WXMSW__ } void ObjectList::paste_objects_into_list(const std::vector& object_idxs) @@ -761,9 +773,9 @@ void ObjectList::paste_objects_into_list(const std::vector& object_idxs) wxGetApp().plater()->changed_objects(object_idxs); select_items(items); -#ifndef __WXOSX__ //#ifdef __WXMSW__ // #ys_FIXME +//#ifndef __WXOSX__ //#ifdef __WXMSW__ // #ys_FIXME selection_changed(); -#endif //no __WXOSX__ //__WXMSW__ +//#endif //no __WXOSX__ //__WXMSW__ } #ifdef __WXOSX__ @@ -783,18 +795,41 @@ void ObjectList::OnChar(wxKeyEvent& event) void ObjectList::OnContextMenu(wxDataViewEvent&) { - list_manipulation(); + list_manipulation(true); } -void ObjectList::list_manipulation() +void ObjectList::list_manipulation(bool evt_context_menu/* = false*/) { wxDataViewItem item; wxDataViewColumn* col = nullptr; const wxPoint pt = get_mouse_position_in_control(); HitTest(pt, item, col); - if (!item || col == nullptr) { - return; + /* Note: Under OSX right click doesn't send "selection changed" event. + * It means that Selection() will be return still previously selected item. + * Thus under OSX we should force UnselectAll(), when item and col are nullptr, + * and select new item otherwise. + */ + + if (!item) { + if (col == nullptr) { + if (wxOSX) + UnselectAll(); + else if (!evt_context_menu) + // Case, when last item was deleted and under GTK was called wxEVT_DATAVIEW_SELECTION_CHANGED, + // which invoked next list_manipulation(false) + return; + } + + if (evt_context_menu) { + show_context_menu(evt_context_menu); + return; + } + } + + if (wxOSX && item && col) { + UnselectAll(); + Select(item); } const wxString title = col->GetTitle(); @@ -802,15 +837,21 @@ void ObjectList::list_manipulation() if (title == " ") toggle_printable_state(item); else if (title == _("Editing")) - show_context_menu(); + show_context_menu(evt_context_menu); else if (title == _("Name")) { - int obj_idx, vol_idx; - get_selected_item_indexes(obj_idx, vol_idx, item); + if (wxOSX) + show_context_menu(evt_context_menu); // return context menu under OSX (related to #2909) - if (is_windows10() && get_mesh_errors_count(obj_idx, vol_idx) > 0 && - pt.x > 2*wxGetApp().em_unit() && pt.x < 4*wxGetApp().em_unit() ) - fix_through_netfabb(); + if (is_windows10()) + { + int obj_idx, vol_idx; + get_selected_item_indexes(obj_idx, vol_idx, item); + + if (get_mesh_errors_count(obj_idx, vol_idx) > 0 && + pt.x > 2*wxGetApp().em_unit() && pt.x < 4*wxGetApp().em_unit() ) + fix_through_netfabb(); + } } #ifndef __WXMSW__ @@ -818,7 +859,7 @@ void ObjectList::list_manipulation() #endif //__WXMSW__ } -void ObjectList::show_context_menu() +void ObjectList::show_context_menu(const bool evt_context_menu) { if (multiple_selection()) { @@ -831,22 +872,26 @@ void ObjectList::show_context_menu() } const auto item = GetSelection(); + wxMenu* menu {nullptr}; if (item) { const ItemType type = m_objects_model->GetItemType(item); if (!(type & (itObject | itVolume | itLayer | itInstance))) return; - wxMenu* menu = type & itInstance ? &m_menu_instance : + menu = type & itInstance ? &m_menu_instance : type & itLayer ? &m_menu_layer : m_objects_model->GetParent(item) != wxDataViewItem(nullptr) ? &m_menu_part : printer_technology() == ptFFF ? &m_menu_object : &m_menu_sla_object; if (!(type & itInstance)) append_menu_item_settings(menu); - - wxGetApp().plater()->PopupMenu(menu); } + else if (evt_context_menu) + menu = &m_menu_default; + + if (menu) + wxGetApp().plater()->PopupMenu(menu); } void ObjectList::copy() @@ -1286,13 +1331,16 @@ void ObjectList::show_settings(const wxDataViewItem settings_item) wxMenu* ObjectList::append_submenu_add_generic(wxMenu* menu, const ModelVolumeType type) { auto sub_menu = new wxMenu; - if (wxGetApp().get_mode() == comExpert) { + if (wxGetApp().get_mode() == comExpert && type != ModelVolumeType::INVALID) { append_menu_item(sub_menu, wxID_ANY, _(L("Load")) + " " + dots, "", [this, type](wxCommandEvent&) { load_subobject(type); }, "", menu); sub_menu->AppendSeparator(); } - for (auto& item : { L("Box"), L("Cylinder"), L("Sphere"), L("Slab") }) { + for (auto& item : { L("Box"), L("Cylinder"), L("Sphere"), L("Slab") }) + { + if (type == ModelVolumeType::INVALID && strncmp(item, "Slab", 4) == 0) + continue; append_menu_item(sub_menu, wxID_ANY, _(item), "", [this, type, item](wxCommandEvent&) { load_generic_subobject(item, type); }, "", menu); } @@ -1579,6 +1627,12 @@ void ObjectList::create_instance_popupmenu(wxMenu*menu) }, m_menu_item_split_instances->GetId()); } +void ObjectList::create_default_popupmenu(wxMenu*menu) +{ + wxMenu* sub_menu = append_submenu_add_generic(menu, ModelVolumeType::INVALID); + append_submenu(menu, sub_menu, wxID_ANY, _(L("Add Shape")), "", "add_part"); +} + wxMenu* ObjectList::create_settings_popupmenu(wxMenu *parent_menu) { wxMenu *menu = new wxMenu; @@ -1668,9 +1722,9 @@ void ObjectList::load_subobject(ModelVolumeType type) if (sel_item) select_item(sel_item); -#ifndef __WXOSX__ //#ifdef __WXMSW__ // #ys_FIXME +//#ifndef __WXOSX__ //#ifdef __WXMSW__ // #ys_FIXME selection_changed(); -#endif //no __WXOSX__ //__WXMSW__ +//#endif //no __WXOSX__ //__WXMSW__ } void ObjectList::load_part( ModelObject* model_object, @@ -1717,8 +1771,38 @@ void ObjectList::load_part( ModelObject* model_object, } +static TriangleMesh create_mesh(const std::string& type_name, const BoundingBoxf3& bb) +{ + TriangleMesh mesh; + + const double side = wxGetApp().plater()->canvas3D()->get_size_proportional_to_max_bed_size(0.1); + + if (type_name == "Box") + // Sitting on the print bed, left front front corner at (0, 0). + mesh = make_cube(side, side, side); + else if (type_name == "Cylinder") + // Centered around 0, sitting on the print bed. + // The cylinder has the same volume as the box above. + mesh = make_cylinder(0.564 * side, side); + else if (type_name == "Sphere") + // Centered around 0, half the sphere below the print bed, half above. + // The sphere has the same volume as the box above. + mesh = make_sphere(0.62 * side, PI / 18); + else if (type_name == "Slab") + // Sitting on the print bed, left front front corner at (0, 0). + mesh = make_cube(bb.size().x() * 1.5, bb.size().y() * 1.5, bb.size().z() * 0.5); + mesh.repair(); + + return mesh; +} + void ObjectList::load_generic_subobject(const std::string& type_name, const ModelVolumeType type) { + if (type == ModelVolumeType::INVALID) { + load_shape_object(type_name); + return; + } + const int obj_idx = get_selected_obj_idx(); if (obj_idx < 0) return; @@ -1741,26 +1825,7 @@ void ObjectList::load_generic_subobject(const std::string& type_name, const Mode // Bounding box of the selected instance in world coordinate system including the translation, without modifiers. BoundingBoxf3 instance_bb = model_object.instance_bounding_box(instance_idx); - const wxString name = _(L("Generic")) + "-" + _(type_name); - TriangleMesh mesh; - - double side = wxGetApp().plater()->canvas3D()->get_size_proportional_to_max_bed_size(0.1); - - if (type_name == "Box") - // Sitting on the print bed, left front front corner at (0, 0). - mesh = make_cube(side, side, side); - else if (type_name == "Cylinder") - // Centered around 0, sitting on the print bed. - // The cylinder has the same volume as the box above. - mesh = make_cylinder(0.564 * side, side); - else if (type_name == "Sphere") - // Centered around 0, half the sphere below the print bed, half above. - // The sphere has the same volume as the box above. - mesh = make_sphere(0.62 * side, PI / 18); - else if (type_name == "Slab") - // Sitting on the print bed, left front front corner at (0, 0). - mesh = make_cube(instance_bb.size().x()*1.5, instance_bb.size().y()*1.5, instance_bb.size().z()*0.5); - mesh.repair(); + TriangleMesh mesh = create_mesh(type_name, instance_bb); // Mesh will be centered when loading. ModelVolume *new_volume = model_object.add_volume(std::move(mesh)); @@ -1782,6 +1847,7 @@ void ObjectList::load_generic_subobject(const std::string& type_name, const Mode new_volume->set_offset(v->get_instance_transformation().get_matrix(true).inverse() * offset); } + const wxString name = _(L("Generic")) + "-" + _(type_name); new_volume->name = into_u8(name); // set a default extruder value, since user can't add it manually new_volume->config.set_key_value("extruder", new ConfigOptionInt(0)); @@ -1794,9 +1860,63 @@ void ObjectList::load_generic_subobject(const std::string& type_name, const Mode const auto object_item = m_objects_model->GetTopParent(GetSelection()); select_item(m_objects_model->AddVolumeChild(object_item, name, type, new_volume->get_mesh_errors_count()>0)); -#ifndef __WXOSX__ //#ifdef __WXMSW__ // #ys_FIXME +//#ifndef __WXOSX__ //#ifdef __WXMSW__ // #ys_FIXME selection_changed(); -#endif //no __WXOSX__ //__WXMSW__ +//#endif //no __WXOSX__ //__WXMSW__ +} + +void ObjectList::load_shape_object(const std::string& type_name) +{ + const Selection& selection = wxGetApp().plater()->canvas3D()->get_selection(); + assert(selection.get_object_idx() == -1); // Add nothing is something is selected on 3DScene + if (selection.get_object_idx() != -1) + return; + + const int obj_idx = m_objects->size(); + if (obj_idx < 0) + return; + + take_snapshot(_(L("Add Shape"))); + + // Create mesh + BoundingBoxf3 bb; + TriangleMesh mesh = create_mesh(type_name, bb); + + // Add mesh to model as a new object + Model& model = wxGetApp().plater()->model(); + const wxString name = _(L("Shape")) + "-" + _(type_name); + +#ifdef _DEBUG + check_model_ids_validity(model); +#endif /* _DEBUG */ + + std::vector object_idxs; + ModelObject* new_object = model.add_object(); + new_object->name = into_u8(name); + new_object->add_instance(); // each object should have at list one instance + + ModelVolume* new_volume = new_object->add_volume(mesh); + new_volume->name = into_u8(name); + // set a default extruder value, since user can't add it manually + new_volume->config.set_key_value("extruder", new ConfigOptionInt(0)); + new_object->invalidate_bounding_box(); + + new_object->center_around_origin(); + new_object->ensure_on_bed(); + + const BoundingBoxf bed_shape = wxGetApp().plater()->bed_shape_bb(); + new_object->instances[0]->set_offset(Slic3r::to_3d(bed_shape.center().cast(), -new_object->origin_translation(2))); + + object_idxs.push_back(model.objects.size() - 1); +#ifdef _DEBUG + check_model_ids_validity(model); +#endif /* _DEBUG */ + + paste_objects_into_list(object_idxs); + +#ifdef _DEBUG + check_model_ids_validity(model); +#endif /* _DEBUG */ } void ObjectList::del_object(const int obj_idx) @@ -3569,10 +3689,10 @@ void ObjectList::msw_rescale() // update min size !!! A width of control shouldn't be a wxDefaultCoord SetMinSize(wxSize(1, 15 * em)); - GetColumn(colName)->SetWidth(19 * em); - GetColumn(colPrint)->SetWidth( 2 * em); + GetColumn(colName )->SetWidth(20 * em); + GetColumn(colPrint )->SetWidth( 3 * em); GetColumn(colExtruder)->SetWidth( 8 * em); - GetColumn(colEditing)->SetWidth( 2 * em); + GetColumn(colEditing )->SetWidth( 3 * em); // rescale all icons, used by ObjectList msw_rescale_icons(); @@ -3585,7 +3705,8 @@ void ObjectList::msw_rescale() &m_menu_part, &m_menu_sla_object, &m_menu_instance, - &m_menu_layer }) + &m_menu_layer, + &m_menu_default}) msw_rescale_menu(menu); Layout(); diff --git a/src/slic3r/GUI/GUI_ObjectList.hpp b/src/slic3r/GUI/GUI_ObjectList.hpp index 13d1106fc..4dd618a90 100644 --- a/src/slic3r/GUI/GUI_ObjectList.hpp +++ b/src/slic3r/GUI/GUI_ObjectList.hpp @@ -132,6 +132,7 @@ private: MenuWithSeparators m_menu_sla_object; MenuWithSeparators m_menu_instance; MenuWithSeparators m_menu_layer; + MenuWithSeparators m_menu_default; wxMenuItem* m_menu_item_settings { nullptr }; wxMenuItem* m_menu_item_split_instances { nullptr }; @@ -208,7 +209,7 @@ public: void set_tooltip_for_item(const wxPoint& pt); void selection_changed(); - void show_context_menu(); + void show_context_menu(const bool evt_context_menu); #ifndef __WXOSX__ void key_event(wxKeyEvent& event); #endif /* __WXOSX__ */ @@ -240,6 +241,7 @@ public: void create_sla_object_popupmenu(wxMenu*menu); void create_part_popupmenu(wxMenu*menu); void create_instance_popupmenu(wxMenu*menu); + void create_default_popupmenu(wxMenu *menu); wxMenu* create_settings_popupmenu(wxMenu *parent_menu); void create_freq_settings_popupmenu(wxMenu *parent_menu, const bool is_object_settings = true); @@ -248,6 +250,7 @@ public: void load_subobject(ModelVolumeType type); void load_part(ModelObject* model_object, std::vector> &volumes_info, ModelVolumeType type); void load_generic_subobject(const std::string& type_name, const ModelVolumeType type); + void load_shape_object(const std::string &type_name); void del_object(const int obj_idx); void del_subobject_item(wxDataViewItem& item); void del_settings_from_config(const wxDataViewItem& parent_item); @@ -362,7 +365,7 @@ private: // void OnChar(wxKeyEvent& event); #endif /* __WXOSX__ */ void OnContextMenu(wxDataViewEvent &event); - void list_manipulation(); + void list_manipulation(bool evt_context_menu = false); void OnBeginDrag(wxDataViewEvent &event); void OnDropPossible(wxDataViewEvent &event); diff --git a/src/slic3r/GUI/GUI_Preview.cpp b/src/slic3r/GUI/GUI_Preview.cpp index 7c761ed5f..b6350bcab 100644 --- a/src/slic3r/GUI/GUI_Preview.cpp +++ b/src/slic3r/GUI/GUI_Preview.cpp @@ -221,6 +221,7 @@ bool Preview::init(wxWindow* parent, Bed3D& bed, Camera& camera, GLToolbar& view m_choice_view_type->Append(_(L("Height"))); m_choice_view_type->Append(_(L("Width"))); m_choice_view_type->Append(_(L("Speed"))); + m_choice_view_type->Append(_(L("Fan speed"))); m_choice_view_type->Append(_(L("Volumetric flow rate"))); m_choice_view_type->Append(_(L("Tool"))); m_choice_view_type->Append(_(L("Color Print"))); diff --git a/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp b/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp index 992f4912a..000ddf8c9 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp @@ -252,7 +252,7 @@ void GLGizmoSlaSupports::render_points(const Selection& selection, bool picking) const sla::SupportPoint& support_point = m_editing_mode ? m_editing_cache[i].support_point : m_normal_cache[i]; const bool& point_selected = m_editing_mode ? m_editing_cache[i].selected : false; - if (is_point_clipped(support_point.pos.cast())) + if (is_mesh_point_clipped(support_point.pos.cast())) continue; // First decide about the color of the point. @@ -335,14 +335,14 @@ void GLGizmoSlaSupports::render_points(const Selection& selection, bool picking) -bool GLGizmoSlaSupports::is_point_clipped(const Vec3d& point) const +bool GLGizmoSlaSupports::is_mesh_point_clipped(const Vec3d& point) const { if (m_clipping_plane_distance == 0.f) return false; Vec3d transformed_point = m_model_object->instances.front()->get_transformation().get_matrix() * point; transformed_point(2) += m_z_shift; - return m_clipping_plane->distance(transformed_point) < 0.; + return m_clipping_plane->is_point_clipped(transformed_point); } @@ -391,27 +391,15 @@ bool GLGizmoSlaSupports::unproject_on_mesh(const Vec2d& mouse_pos, std::pair hits; - std::vector normals; - m_mesh_raycaster->unproject_on_mesh(mouse_pos, trafo.get_matrix(), camera, &hits, &normals); - - // We must also take care of the clipping plane (if active) - unsigned i = 0; - if (m_clipping_plane_distance != 0.f) { - for (i=0; i())) - break; + Vec3f hit; + Vec3f normal; + if (m_mesh_raycaster->unproject_on_mesh(mouse_pos, trafo.get_matrix(), camera, hit, normal, m_clipping_plane.get())) { + // Return both the point and the facet normal. + pos_and_normal = std::make_pair(hit, normal); + return true; } - - if (i==hits.size() || (hits.size()-i) % 2 != 0) { - // All hits are either clipped, or there is an odd number of unclipped - // hits - meaning the nearest must be from inside the mesh. + else return false; - } - - // Calculate and return both the point and the facet normal. - pos_and_normal = std::make_pair(hits[i], normals[i]); - return true; } // Following function is called from GLCanvas3D to inform the gizmo about a mouse/keyboard event. @@ -481,19 +469,15 @@ bool GLGizmoSlaSupports::gizmo_event(SLAGizmoEventType action, const Vec2d& mous std::vector points_inside; std::vector points_idxs = m_selection_rectangle.stop_dragging(m_parent, points); for (size_t idx : points_idxs) - points_inside.push_back((trafo.get_matrix() * points[idx]).cast()); + points_inside.push_back(points[idx].cast()); // Only select/deselect points that are actually visible - for (size_t idx : m_mesh_raycaster->get_unobscured_idxs(trafo, m_parent.get_camera(), points_inside, - [this](const Vec3f& pt) { return is_point_clipped(pt.cast()); })) + for (size_t idx : m_mesh_raycaster->get_unobscured_idxs(trafo, m_parent.get_camera(), points_inside, m_clipping_plane.get())) { - const sla::SupportPoint &support_point = m_editing_cache[points_idxs[idx]].support_point; - if (! is_point_clipped(support_point.pos.cast())) { - if (rectangle_status == GLSelectionRectangle::Deselect) - unselect_point(points_idxs[idx]); - else - select_point(points_idxs[idx]); - } + if (rectangle_status == GLSelectionRectangle::Deselect) + unselect_point(points_idxs[idx]); + else + select_point(points_idxs[idx]); } return true; } diff --git a/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.hpp b/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.hpp index cf5245f19..15b2df80e 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.hpp @@ -125,7 +125,7 @@ private: mutable std::unique_ptr m_supports_clipper; std::vector get_config_options(const std::vector& keys) const; - bool is_point_clipped(const Vec3d& point) const; + bool is_mesh_point_clipped(const Vec3d& point) const; //void find_intersecting_facets(const igl::AABB* aabb, const Vec3f& normal, double offset, std::vector& out) const; // Methods that do the model_object and editing cache synchronization, diff --git a/src/slic3r/GUI/LambdaObjectDialog.cpp b/src/slic3r/GUI/LambdaObjectDialog.cpp index 4d1cb0658..63c8d329c 100644 --- a/src/slic3r/GUI/LambdaObjectDialog.cpp +++ b/src/slic3r/GUI/LambdaObjectDialog.cpp @@ -192,7 +192,7 @@ ConfigOptionsGroupShp LambdaObjectDialog::init_modificator_options_page(const wx else panel->SetSizer(optgroup->sizer); - return optgroup; + return optgroup; } diff --git a/src/slic3r/GUI/MainFrame.cpp b/src/slic3r/GUI/MainFrame.cpp index dfe3a9cf9..7c36f3665 100644 --- a/src/slic3r/GUI/MainFrame.cpp +++ b/src/slic3r/GUI/MainFrame.cpp @@ -917,7 +917,7 @@ void MainFrame::load_config_file() wxString file; if (dlg.ShowModal() == wxID_OK) file = dlg.GetPath(); - if (! file.IsEmpty() && this->load_config_file(file.ToUTF8().data())) { + if (! file.IsEmpty() && this->load_config_file(file.ToUTF8().data())) { wxGetApp().app_config->update_config_dir(get_dir_name(file)); m_last_config = file; } diff --git a/src/slic3r/GUI/MeshUtils.cpp b/src/slic3r/GUI/MeshUtils.cpp index e559020e9..62a6813a6 100644 --- a/src/slic3r/GUI/MeshUtils.cpp +++ b/src/slic3r/GUI/MeshUtils.cpp @@ -152,8 +152,8 @@ Vec3f MeshRaycaster::AABBWrapper::get_hit_normal(const igl::Hit& hit) const } -bool MeshRaycaster::unproject_on_mesh(const Vec2d& mouse_pos, const Transform3d& trafo, - const Camera& camera, std::vector* positions, std::vector* normals) const +bool MeshRaycaster::unproject_on_mesh(const Vec2d& mouse_pos, const Transform3d& trafo, const Camera& camera, + Vec3f& position, Vec3f& normal, const ClippingPlane* clipping_plane) const { const std::array& viewport = camera.get_viewport(); const Transform3d& model_mat = camera.get_view_matrix(); @@ -179,25 +179,30 @@ bool MeshRaycaster::unproject_on_mesh(const Vec2d& mouse_pos, const Transform3d& std::sort(hits.begin(), hits.end(), [](const igl::Hit& a, const igl::Hit& b) { return a.t < b.t; }); - // Now stuff the points in the provided vector and calculate normals if asked about them: - if (positions != nullptr) { - positions->clear(); - if (normals != nullptr) - normals->clear(); - for (const igl::Hit& hit : hits) { - positions->push_back(m_AABB_wrapper->get_hit_pos(hit)); + unsigned i = 0; - if (normals != nullptr) - normals->push_back(m_AABB_wrapper->get_hit_normal(hit)); + // Remove points that are obscured or cut by the clipping plane + if (clipping_plane) { + for (i=0; iis_point_clipped(trafo * m_AABB_wrapper->get_hit_pos(hits[i]).cast())) + break; + + if (i==hits.size() || (hits.size()-i) % 2 != 0) { + // All hits are either clipped, or there is an odd number of unclipped + // hits - meaning the nearest must be from inside the mesh. + return false; } } + // Now stuff the points in the provided vector and calculate normals if asked about them: + position = m_AABB_wrapper->get_hit_pos(hits[i]); + normal = m_AABB_wrapper->get_hit_normal(hits[i]); return true; } std::vector MeshRaycaster::get_unobscured_idxs(const Geometry::Transformation& trafo, const Camera& camera, const std::vector& points, - std::function fn_ignore_hit) const + const ClippingPlane* clipping_plane) const { std::vector out; @@ -206,19 +211,24 @@ std::vector MeshRaycaster::get_unobscured_idxs(const Geometry::Transfo Vec3f direction_to_camera_mesh = (instance_matrix_no_translation_no_scaling.inverse().cast() * direction_to_camera).normalized().eval(); Vec3f scaling = trafo.get_scaling_factor().cast(); direction_to_camera_mesh = Vec3f(direction_to_camera_mesh(0)*scaling(0), direction_to_camera_mesh(1)*scaling(1), direction_to_camera_mesh(2)*scaling(2)); + const Transform3f inverse_trafo = trafo.get_matrix().inverse().cast(); for (size_t i=0; iis_point_clipped(pt.cast())) + continue; + bool is_obscured = false; // Cast a ray in the direction of the camera and look for intersection with the mesh: std::vector hits; - // Offset the start of the ray to the front of the ball + EPSILON to account for numerical inaccuracies. + // Offset the start of the ray by EPSILON to account for numerical inaccuracies. if (m_AABB_wrapper->m_AABB.intersect_ray( AABBWrapper::MapMatrixXfUnaligned(m_mesh->its.vertices.front().data(), m_mesh->its.vertices.size(), 3), AABBWrapper::MapMatrixXiUnaligned(m_mesh->its.indices.front().data(), m_mesh->its.indices.size(), 3), - pt + direction_to_camera_mesh * EPSILON, direction_to_camera_mesh, hits)) { + inverse_trafo * pt + direction_to_camera_mesh * EPSILON, direction_to_camera_mesh, hits)) { std::sort(hits.begin(), hits.end(), [](const igl::Hit& h1, const igl::Hit& h2) { return h1.t < h2.t; }); + // If the closest hit facet normal points in the same direction as the ray, // we are looking through the mesh and should therefore discard the point: if (m_AABB_wrapper->get_hit_normal(hits.front()).dot(direction_to_camera_mesh) > 0.f) @@ -227,11 +237,12 @@ std::vector MeshRaycaster::get_unobscured_idxs(const Geometry::Transfo // Eradicate all hits that the caller wants to ignore for (unsigned j=0; jget_hit_pos(hit))) { + if (clipping_plane && clipping_plane->is_point_clipped(trafo.get_matrix() * m_AABB_wrapper->get_hit_pos(hit).cast())) { hits.erase(hits.begin()+j); --j; } } + // FIXME: the intersection could in theory be behind the camera, but as of now we only have camera direction. // Also, the threshold is in mesh coordinates, not in actual dimensions. if (! hits.empty()) diff --git a/src/slic3r/GUI/MeshUtils.hpp b/src/slic3r/GUI/MeshUtils.hpp index a2be2677b..e4c4c20d2 100644 --- a/src/slic3r/GUI/MeshUtils.hpp +++ b/src/slic3r/GUI/MeshUtils.hpp @@ -50,6 +50,7 @@ public: return (-get_normal().dot(pt) + m_data[3]); } + bool is_point_clipped(const Vec3d& point) const { return distance(point) < 0.; } void set_normal(const Vec3d& normal) { for (size_t i=0; i<3; ++i) m_data[i] = normal(i); } void set_offset(double offset) { m_data[3] = offset; } Vec3d get_normal() const { return Vec3d(m_data[0], m_data[1], m_data[2]); } @@ -98,10 +99,10 @@ public: void set_camera(const Camera& camera); bool unproject_on_mesh(const Vec2d& mouse_pos, const Transform3d& trafo, const Camera& camera, - std::vector* positions = nullptr, std::vector* normals = nullptr) const; + Vec3f& position, Vec3f& normal, const ClippingPlane* clipping_plane = nullptr) const; std::vector get_unobscured_idxs(const Geometry::Transformation& trafo, const Camera& camera, - const std::vector& points, std::function fn_ignore_hit) const; + const std::vector& points, const ClippingPlane* clipping_plane = nullptr) const; Vec3f get_closest_point(const Vec3f& point, Vec3f* normal = nullptr) const; diff --git a/src/slic3r/GUI/OptionsGroup.cpp b/src/slic3r/GUI/OptionsGroup.cpp index c032aac72..698c1e034 100644 --- a/src/slic3r/GUI/OptionsGroup.cpp +++ b/src/slic3r/GUI/OptionsGroup.cpp @@ -133,7 +133,7 @@ void OptionsGroup::append_line(const Line& line, wxStaticText** full_Label/* = n m_options_mode.push_back(option_set[0].opt.mode); // if we have a single option with no label, no sidetext just add it directly to sizer - if (option_set.size() == 1 && label_width == 0 && option_set.front().opt.full_width && + if (option_set.size() == 1 && label_width == 0 && option_set.front().opt.full_width && option_set.front().opt.label.empty() && option_set.front().opt.sidetext.size() == 0 && option_set.front().side_widget == nullptr && line.get_extra_widgets().size() == 0) { diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index 2e3c314fd..fc5ca1921 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -1347,6 +1347,8 @@ struct Plater::priv MenuWithSeparators part_menu; // SLA-Object popup menu MenuWithSeparators sla_object_menu; + // Default popup menu (when nothing is selected on 3DScene) + MenuWithSeparators default_menu; // Removed/Prepended Items according to the view mode std::vector items_increase; @@ -1886,7 +1888,7 @@ struct Plater::priv void on_action_layersediting(SimpleEvent&); void on_object_select(SimpleEvent&); - void on_right_click(Vec2dEvent&); + void on_right_click(RBtnEvent&); void on_wipetower_moved(Vec3dEvent&); void on_wipetower_rotated(Vec3dEvent&); void on_update_geometry(Vec3dsEvent<2>&); @@ -2525,6 +2527,10 @@ wxString Plater::priv::get_export_file(GUI::FileType file_type) if (output_file.empty()) // Find the file name of the first printable object. output_file = this->model.propose_export_file_name_and_path(); + + if (output_file.empty() && !model.objects.empty()) + // Find the file name of the first object. + output_file = this->model.objects[0]->get_export_filename(); } wxString dlg_title; @@ -3323,7 +3329,7 @@ void Plater::priv::on_slicing_update(SlicingStatusEvent &evt) this->statusbar()->set_progress(evt.status.percent); this->statusbar()->set_status_text(_(evt.status.text) + wxString::FromUTF8("…")); } - if (evt.status.flags & (PrintBase::SlicingStatus::RELOAD_SCENE || PrintBase::SlicingStatus::RELOAD_SLA_SUPPORT_POINTS)) { + if (evt.status.flags & (PrintBase::SlicingStatus::RELOAD_SCENE | PrintBase::SlicingStatus::RELOAD_SLA_SUPPORT_POINTS)) { switch (this->printer_technology) { case ptFFF: this->update_fff_scene(); @@ -3449,57 +3455,66 @@ void Plater::priv::on_object_select(SimpleEvent& evt) selection_changed(); } -void Plater::priv::on_right_click(Vec2dEvent& evt) +void Plater::priv::on_right_click(RBtnEvent& evt) { int obj_idx = get_selected_object_idx(); + + wxMenu* menu = nullptr; + if (obj_idx == -1) - return; - - wxMenu* menu = printer_technology == ptSLA ? &sla_object_menu : - get_selection().is_single_full_instance() ? // show "Object menu" for each FullInstance instead of FullObject - &object_menu : &part_menu; - - sidebar->obj_list()->append_menu_item_settings(menu); - - if (printer_technology != ptSLA) - sidebar->obj_list()->append_menu_item_change_extruder(menu); - - if (menu != &part_menu) + menu = &default_menu; + else { - /* Remove/Prepend "increase/decrease instances" menu items according to the view mode. - * Suppress to show those items for a Simple mode - */ - const MenuIdentifier id = printer_technology == ptSLA ? miObjectSLA : miObjectFFF; - if (wxGetApp().get_mode() == comSimple) { - if (menu->FindItem(_(L("Add instance"))) != wxNOT_FOUND) - { - /* Detach an items from the menu, but don't delete them - * so that they can be added back later - * (after switching to the Advanced/Expert mode) - */ - menu->Remove(items_increase[id]); - menu->Remove(items_decrease[id]); - menu->Remove(items_set_number_of_copies[id]); + // If in 3DScene is(are) selected volume(s), but right button was clicked on empty space + if (evt.data.second) + return; + + menu = printer_technology == ptSLA ? &sla_object_menu : + get_selection().is_single_full_instance() ? // show "Object menu" for each FullInstance instead of FullObject + &object_menu : &part_menu; + + sidebar->obj_list()->append_menu_item_settings(menu); + + if (printer_technology != ptSLA) + sidebar->obj_list()->append_menu_item_change_extruder(menu); + + if (menu != &part_menu) + { + /* Remove/Prepend "increase/decrease instances" menu items according to the view mode. + * Suppress to show those items for a Simple mode + */ + const MenuIdentifier id = printer_technology == ptSLA ? miObjectSLA : miObjectFFF; + if (wxGetApp().get_mode() == comSimple) { + if (menu->FindItem(_(L("Add instance"))) != wxNOT_FOUND) + { + /* Detach an items from the menu, but don't delete them + * so that they can be added back later + * (after switching to the Advanced/Expert mode) + */ + menu->Remove(items_increase[id]); + menu->Remove(items_decrease[id]); + menu->Remove(items_set_number_of_copies[id]); + } } - } - else { - if (menu->FindItem(_(L("Add instance"))) == wxNOT_FOUND) - { - // Prepend items to the menu, if those aren't not there - menu->Prepend(items_set_number_of_copies[id]); - menu->Prepend(items_decrease[id]); - menu->Prepend(items_increase[id]); + else { + if (menu->FindItem(_(L("Add instance"))) == wxNOT_FOUND) + { + // Prepend items to the menu, if those aren't not there + menu->Prepend(items_set_number_of_copies[id]); + menu->Prepend(items_decrease[id]); + menu->Prepend(items_increase[id]); + } } } } - if (q != nullptr) { + if (q != nullptr && menu) { #ifdef __linux__ // For some reason on Linux the menu isn't displayed if position is specified // (even though the position is sane). q->PopupMenu(menu); #else - q->PopupMenu(menu, (int)evt.data.x(), (int)evt.data.y()); + q->PopupMenu(menu, (int)evt.data.first.x(), (int)evt.data.first.y()); #endif } } @@ -3551,12 +3566,14 @@ bool Plater::priv::init_object_menu() init_common_menu(&part_menu, true); complit_init_part_menu(); + sidebar->obj_list()->create_default_popupmenu(&default_menu); + return true; } void Plater::priv::msw_rescale_object_menu() { - for (MenuWithSeparators* menu : { &object_menu, &sla_object_menu, &part_menu }) + for (MenuWithSeparators* menu : { &object_menu, &sla_object_menu, &part_menu, &default_menu }) msw_rescale_menu(dynamic_cast(menu)); } @@ -4278,11 +4295,10 @@ void Plater::increase_instances(size_t num) sidebar().obj_list()->increase_object_instances(obj_idx, was_one_instance ? num + 1 : num); - if (p->get_config("autocenter") == "1") { + if (p->get_config("autocenter") == "1") p->arrange(); - } else { - p->update(); - } + + p->update(); p->get_selection().add_instance(obj_idx, (int)model_object->instances.size() - 1); @@ -4326,14 +4342,14 @@ void Plater::set_number_of_copies(/*size_t num*/) ModelObject* model_object = p->model.objects[obj_idx]; - const auto num = wxGetNumberFromUser( " ", _("Enter the number of copies:"), + const int num = wxGetNumberFromUser( " ", _("Enter the number of copies:"), _("Copies of the selected object"), model_object->instances.size(), 0, 1000, this ); if (num < 0) return; Plater::TakeSnapshot snapshot(this, wxString::Format(_(L("Set numbers of copies to %d")), num)); - int diff = (int)num - (int)model_object->instances.size(); + int diff = num - (int)model_object->instances.size(); if (diff > 0) increase_instances(diff); else if (diff < 0) @@ -4833,6 +4849,34 @@ void Plater::on_config_change(const DynamicPrintConfig &config) this->p->schedule_background_process(); } +void Plater::force_filament_colors_update() +{ + bool update_scheduled = false; + DynamicPrintConfig* config = p->config; + const std::vector filament_presets = wxGetApp().preset_bundle->filament_presets; + if (filament_presets.size() > 1 && + p->config->option("filament_colour")->values.size() == filament_presets.size()) + { + const PresetCollection& filaments = wxGetApp().preset_bundle->filaments; + std::vector filament_colors; + filament_colors.reserve(filament_presets.size()); + + for (const std::string& filament_preset : filament_presets) + filament_colors.push_back(filaments.find_preset(filament_preset, true)->config.opt_string("filament_colour", (unsigned)0)); + + if (config->option("filament_colour")->values != filament_colors) { + config->option("filament_colour")->values = filament_colors; + update_scheduled = true; + } + } + + if (update_scheduled) + update(); + + if (p->main_frame->is_loaded()) + this->p->schedule_background_process(); +} + void Plater::on_activate() { #ifdef __linux__ @@ -4884,6 +4928,11 @@ GLCanvas3D* Plater::canvas3D() return p->view3D->get_canvas3d(); } +BoundingBoxf Plater::bed_shape_bb() const +{ + return p->bed_shape_bb(); +} + PrinterTechnology Plater::printer_technology() const { return p->printer_technology; diff --git a/src/slic3r/GUI/Plater.hpp b/src/slic3r/GUI/Plater.hpp index 28599e433..744f2eae3 100644 --- a/src/slic3r/GUI/Plater.hpp +++ b/src/slic3r/GUI/Plater.hpp @@ -215,6 +215,7 @@ public: void on_extruders_change(size_t extruders_count); void on_config_change(const DynamicPrintConfig &config); + void force_filament_colors_update(); // On activating the parent window. void on_activate(); const DynamicPrintConfig* get_plater_config() const; @@ -229,6 +230,7 @@ public: int get_selected_object_idx(); bool is_single_full_object_selection() const; GLCanvas3D* canvas3D(); + BoundingBoxf bed_shape_bb() const; PrinterTechnology printer_technology() const; void set_printer_technology(PrinterTechnology printer_technology); diff --git a/src/slic3r/GUI/PresetBundle.cpp b/src/slic3r/GUI/PresetBundle.cpp index 54dd92a71..92db623f0 100644 --- a/src/slic3r/GUI/PresetBundle.cpp +++ b/src/slic3r/GUI/PresetBundle.cpp @@ -282,7 +282,7 @@ std::string PresetBundle::load_system_presets() errors_cummulative += "\n"; } } - if (first) { + if (first) { // No config bundle loaded, reset. this->reset(false); } diff --git a/src/slic3r/GUI/Selection.cpp b/src/slic3r/GUI/Selection.cpp index 40fbbbac6..13d4a7360 100644 --- a/src/slic3r/GUI/Selection.cpp +++ b/src/slic3r/GUI/Selection.cpp @@ -410,7 +410,7 @@ void Selection::set_deserialized(EMode mode, const std::vectorselected = false; m_list.clear(); diff --git a/src/slic3r/GUI/Tab.cpp b/src/slic3r/GUI/Tab.cpp index 921a18147..d727c980e 100644 --- a/src/slic3r/GUI/Tab.cpp +++ b/src/slic3r/GUI/Tab.cpp @@ -3032,6 +3032,12 @@ void Tab::save_preset(std::string name /*= ""*/) if (m_type == Preset::TYPE_PRINTER) static_cast(this)->m_initial_extruders_count = static_cast(this)->m_extruders_count; update_changed_ui(); + + /* If filament preset is saved for multi-material printer preset, + * there are cases when filament comboboxs are updated for old (non-modified) colors, + * but in full_config a filament_colors option aren't.*/ + if (m_type == Preset::TYPE_FILAMENT && wxGetApp().extruders_edited_cnt() > 1) + wxGetApp().plater()->force_filament_colors_update(); } // Called for a currently selected preset. diff --git a/src/slic3r/GUI/WipeTowerDialog.cpp b/src/slic3r/GUI/WipeTowerDialog.cpp index 460683f77..539422545 100644 --- a/src/slic3r/GUI/WipeTowerDialog.cpp +++ b/src/slic3r/GUI/WipeTowerDialog.cpp @@ -371,7 +371,7 @@ void WipingPanel::toggle_advanced(bool user_action) { else m_advanced = !advanced_matches_simple(); // if called from constructor, show what is appropriate - (m_advanced ? m_page_advanced : m_page_simple)->Show(); + (m_advanced ? m_page_advanced : m_page_simple)->Show(); (!m_advanced ? m_page_advanced : m_page_simple)->Hide(); m_widget_button->SetLabel(m_advanced ? _(L("Show simplified settings")) : _(L("Show advanced settings"))); diff --git a/src/slic3r/GUI/wxExtensions.cpp b/src/slic3r/GUI/wxExtensions.cpp index bf5500cf4..59406b6e9 100644 --- a/src/slic3r/GUI/wxExtensions.cpp +++ b/src/slic3r/GUI/wxExtensions.cpp @@ -669,7 +669,7 @@ wxDataViewItem ObjectDataViewModel::Add(const wxString &name, if (has_errors) root->m_bmp = *m_warning_bmp; - m_objects.push_back(root); + m_objects.push_back(root); // notify control wxDataViewItem child((void*)root); wxDataViewItem parent((void*)NULL); @@ -720,7 +720,7 @@ wxDataViewItem ObjectDataViewModel::AddVolumeChild( const wxDataViewItem &parent root->SetBitmap(*m_warning_bmp); // notify control - const wxDataViewItem child((void*)node); + const wxDataViewItem child((void*)node); ItemAdded(parent_item, child); root->m_volumes_cnt++; diff --git a/xs/xsp/ExtrusionEntityCollection.xsp b/xs/xsp/ExtrusionEntityCollection.xsp index a868ea6bb..a01788c56 100644 --- a/xs/xsp/ExtrusionEntityCollection.xsp +++ b/xs/xsp/ExtrusionEntityCollection.xsp @@ -14,13 +14,17 @@ void clear(); ExtrusionEntityCollection* chained_path(bool no_reverse, ExtrusionRole role = erMixed) %code{% + if (no_reverse) + croak("no_reverse must be false"); RETVAL = new ExtrusionEntityCollection(); - THIS->chained_path(RETVAL, no_reverse, role); + *RETVAL = THIS->chained_path_from(THIS->entities.front()->first_point()); %}; ExtrusionEntityCollection* chained_path_from(Point* start_near, bool no_reverse, ExtrusionRole role = erMixed) %code{% + if (no_reverse) + croak("no_reverse must be false"); RETVAL = new ExtrusionEntityCollection(); - THIS->chained_path_from(*start_near, RETVAL, no_reverse, role); + *RETVAL = THIS->chained_path_from(*start_near, role); %}; Clone first_point(); Clone last_point(); @@ -31,13 +35,11 @@ ExtrusionEntityCollection* flatten() %code{% RETVAL = new ExtrusionEntityCollection(); - THIS->flatten(RETVAL); + *RETVAL = THIS->flatten(); %}; double min_mm3_per_mm(); bool empty() %code{% RETVAL = THIS->entities.empty(); %}; - std::vector orig_indices() - %code{% RETVAL = THIS->orig_indices; %}; Polygons polygons_covered_by_width(); Polygons polygons_covered_by_spacing(); %{ diff --git a/xs/xsp/Filler.xsp b/xs/xsp/Filler.xsp index 34a6d33be..647d851eb 100644 --- a/xs/xsp/Filler.xsp +++ b/xs/xsp/Filler.xsp @@ -3,7 +3,6 @@ %{ #include #include "libslic3r/Fill/Fill.hpp" -#include "libslic3r/PolylineCollection.hpp" #include "libslic3r/ExtrusionEntity.hpp" #include "libslic3r/ExtrusionEntityCollection.hpp" %} diff --git a/xs/xsp/Geometry.xsp b/xs/xsp/Geometry.xsp index b7e92ba69..5d6454e8a 100644 --- a/xs/xsp/Geometry.xsp +++ b/xs/xsp/Geometry.xsp @@ -3,6 +3,7 @@ %{ #include #include "libslic3r/Geometry.hpp" +#include "libslic3r/ShortestPath.hpp" %} @@ -49,7 +50,7 @@ std::vector chained_path(points) Points points CODE: - Slic3r::Geometry::chained_path(points, RETVAL); + RETVAL = chain_points(points); OUTPUT: RETVAL @@ -58,7 +59,7 @@ chained_path_from(points, start_from) Points points Point* start_from CODE: - Slic3r::Geometry::chained_path(points, RETVAL, *start_from); + RETVAL = chain_points(points, start_from); OUTPUT: RETVAL diff --git a/xs/xsp/Layer.xsp b/xs/xsp/Layer.xsp index efd6c9ae6..fc94d5115 100644 --- a/xs/xsp/Layer.xsp +++ b/xs/xsp/Layer.xsp @@ -19,8 +19,6 @@ %code%{ RETVAL = &THIS->fill_surfaces; %}; Polygons bridged() %code%{ RETVAL = THIS->bridged; %}; - Ref unsupported_bridge_edges() - %code%{ RETVAL = &THIS->unsupported_bridge_edges; %}; Ref perimeters() %code%{ RETVAL = &THIS->perimeters; %}; Ref fills() diff --git a/xs/xsp/PolylineCollection.xsp b/xs/xsp/PolylineCollection.xsp index 7237be5bb..d8bb41b49 100644 --- a/xs/xsp/PolylineCollection.xsp +++ b/xs/xsp/PolylineCollection.xsp @@ -2,7 +2,11 @@ %{ #include -#include "libslic3r/PolylineCollection.hpp" + +#include "libslic3r.h" +#include "Polyline.hpp" +#include "ShortestPath.hpp" + %} %name{Slic3r::Polyline::Collection} class PolylineCollection { @@ -14,16 +18,15 @@ PolylineCollection* chained_path(bool no_reverse) %code{% RETVAL = new PolylineCollection(); - THIS->chained_path(RETVAL, no_reverse); + RETVAL->polylines = chain_polylines(THIS->polylines, &THIS->polylines.front().first_point()); %}; PolylineCollection* chained_path_from(Point* start_near, bool no_reverse) %code{% RETVAL = new PolylineCollection(); - THIS->chained_path_from(*start_near, RETVAL, no_reverse); + RETVAL->polylines = chain_polylines(THIS->polylines, start_near); %}; int count() %code{% RETVAL = THIS->polylines.size(); %}; - Clone leftmost_point(); %{ PolylineCollection*