mirror of
				https://github.com/SoftFever/OrcaSlicer.git
				synced 2025-11-02 20:51:23 -07:00 
			
		
		
		
	Merge branch 'master' into materials
This commit is contained in:
		
						commit
						eb93d2a32d
					
				
					 92 changed files with 1925 additions and 1073 deletions
				
			
		| 
						 | 
				
			
			@ -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 {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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.
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,3 +1,12 @@
 | 
			
		|||
2018-01-17  Joerg Wunsch <j.gnu@uriah.heep.sax.de>
 | 
			
		||||
(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 <j.gnu@uriah.heep.sax.de>
 | 
			
		||||
 | 
			
		||||
	Submitted by Hannes Jochriem:
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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.
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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}"
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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 */
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -15,4 +15,4 @@
 | 
			
		|||
#undef clipper_hpp
 | 
			
		||||
#undef use_xyz
 | 
			
		||||
 | 
			
		||||
#endif clipper_z_hpp
 | 
			
		||||
#endif // clipper_z_hpp
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -81,17 +81,16 @@ inline void offset(PolygonImpl& sh, TCoord<PointImpl> 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<double>(distance));
 | 
			
		||||
    
 | 
			
		||||
    try {
 | 
			
		||||
        ClipperOffset offs;
 | 
			
		||||
        offs.AddPath(sh.Contour, jtMiter, etClosedPolygon);
 | 
			
		||||
        offs.AddPaths(sh.Holes, jtMiter, etClosedPolygon);
 | 
			
		||||
        offs.Execute(result, static_cast<double>(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.
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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<class S, class TB>
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -3,9 +3,6 @@
 | 
			
		|||
 | 
			
		||||
#include <cassert>
 | 
			
		||||
 | 
			
		||||
// For caching nfps
 | 
			
		||||
#include <unordered_map>
 | 
			
		||||
 | 
			
		||||
// For parallel for
 | 
			
		||||
#include <functional>
 | 
			
		||||
#include <iterator>
 | 
			
		||||
| 
						 | 
				
			
			@ -76,55 +73,6 @@ inline void enumerate(
 | 
			
		|||
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
namespace __itemhash {
 | 
			
		||||
 | 
			
		||||
using Key = size_t;
 | 
			
		||||
 | 
			
		||||
template<class S>
 | 
			
		||||
Key hash(const _Item<S>& item) {
 | 
			
		||||
    using Point = TPoint<S>;
 | 
			
		||||
    using Segment = _Segment<Point>;
 | 
			
		||||
 | 
			
		||||
    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<double>());
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    it = ctr.begin(); nx = std::next(it);
 | 
			
		||||
 | 
			
		||||
    while(nx != ctr.end()) {
 | 
			
		||||
        Segment seg(*it++, *nx++);
 | 
			
		||||
        auto l = int(M * std::sqrt(seg.template sqlength<double>()) / 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<std::string>()(ret);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
template<class S>
 | 
			
		||||
using Hash = std::unordered_map<Key, nfp::NfpResult<S>>;
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
namespace placers {
 | 
			
		||||
 | 
			
		||||
template<class RawShape>
 | 
			
		||||
| 
						 | 
				
			
			@ -529,17 +477,9 @@ class _NofitPolyPlacer: public PlacerBoilerplate<_NofitPolyPlacer<RawShape, TBin
 | 
			
		|||
 | 
			
		||||
    using MaxNfpLevel = nfp::MaxNfpLevel<RawShape>;
 | 
			
		||||
 | 
			
		||||
    using ItemKeys = std::vector<__itemhash::Key>;
 | 
			
		||||
 | 
			
		||||
    // Norming factor for the optimization function
 | 
			
		||||
    const double norm_;
 | 
			
		||||
 | 
			
		||||
    // Caching calculated nfps
 | 
			
		||||
    __itemhash::Hash<RawShape> nfpcache_;
 | 
			
		||||
 | 
			
		||||
    // Storing item hash keys
 | 
			
		||||
    ItemKeys item_keys_;
 | 
			
		||||
 | 
			
		||||
public:
 | 
			
		||||
 | 
			
		||||
    using Pile = nfp::Shapes<RawShape>;
 | 
			
		||||
| 
						 | 
				
			
			@ -636,15 +576,12 @@ public:
 | 
			
		|||
private:
 | 
			
		||||
 | 
			
		||||
    using Shapes = TMultiShape<RawShape>;
 | 
			
		||||
    using ItemRef = std::reference_wrapper<Item>;
 | 
			
		||||
    using ItemWithHash = const std::pair<ItemRef, __itemhash::Key>;
 | 
			
		||||
 | 
			
		||||
    Shapes calcnfp(const ItemWithHash itsh, Lvl<nfp::NfpLevel::CONVEX_ONLY>)
 | 
			
		||||
    Shapes calcnfp(const Item &trsh, Lvl<nfp::NfpLevel::CONVEX_ONLY>)
 | 
			
		||||
    {
 | 
			
		||||
        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<class Level>
 | 
			
		||||
    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<MaxNfpLevel::value>());
 | 
			
		||||
                nfps = calcnfp(item, Lvl<MaxNfpLevel::value>());
 | 
			
		||||
 | 
			
		||||
                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);
 | 
			
		||||
        }
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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++;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -40,7 +40,7 @@ struct NfpImpl<S, NfpLevel::CONVEX_ONLY>
 | 
			
		|||
}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
std::vector<libnest2d::Item>& prusaParts() {
 | 
			
		||||
static std::vector<libnest2d::Item>& prusaParts() {
 | 
			
		||||
    static std::vector<libnest2d::Item> ret;
 | 
			
		||||
    
 | 
			
		||||
    if(ret.empty()) {
 | 
			
		||||
| 
						 | 
				
			
			@ -51,7 +51,7 @@ std::vector<libnest2d::Item>& 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<Item> 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<ClipperLib::Polygon>;
 | 
			
		||||
    std::vector<Pile> 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<Item> 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<Item> 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<boost::multiprecision::int128_t>;
 | 
			
		|||
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
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<PolygonImpl, Unit, Ratio>(poly).area();
 | 
			
		||||
//    long double arearef = refMinAreaBox(poly);
 | 
			
		||||
//    long double area = minAreaBoundingBox<PolygonImpl, Unit, Ratio>(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<PathImpl, PointImpl, Unit>(rinput, 1000000));
 | 
			
		||||
        PolygonImpl poly(rinput);
 | 
			
		||||
        
 | 
			
		||||
        long double arearef = refMinAreaBox(poly);
 | 
			
		||||
| 
						 | 
				
			
			@ -993,8 +1035,6 @@ TEST(RotatingCalipers, AllPrusaMinBB) {
 | 
			
		|||
        long double area = cast<long double>(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);
 | 
			
		||||
    }
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -618,19 +618,21 @@ void arrange(ArrangePolygons &             arrangables,
 | 
			
		|||
    items.reserve(arrangables.size());
 | 
			
		||||
    
 | 
			
		||||
    // Create Item from Arrangeable
 | 
			
		||||
    auto process_arrangeable =
 | 
			
		||||
        [](const ArrangePolygon &arrpoly, std::vector<Item> &outp)
 | 
			
		||||
    auto process_arrangeable = [](const ArrangePolygon &arrpoly,
 | 
			
		||||
                                  std::vector<Item> &   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);
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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!
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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<const ConfigOptionVector<T>*>(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();
 | 
			
		||||
    }
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -5,6 +5,8 @@
 | 
			
		|||
#include "Polygon.hpp"
 | 
			
		||||
#include "Polyline.hpp"
 | 
			
		||||
 | 
			
		||||
#include <assert.h>
 | 
			
		||||
 | 
			
		||||
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);
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,4 +1,5 @@
 | 
			
		|||
#include "ExtrusionEntityCollection.hpp"
 | 
			
		||||
#include "ShortestPath.hpp"
 | 
			
		||||
#include <algorithm>
 | 
			
		||||
#include <cmath>
 | 
			
		||||
#include <map>
 | 
			
		||||
| 
						 | 
				
			
			@ -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<size_t>* 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<size_t>* 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<ExtrusionEntity*,size_t> 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
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -14,15 +14,14 @@ public:
 | 
			
		|||
	ExtrusionEntity* clone_move() override { return new ExtrusionEntityCollection(std::move(*this)); }
 | 
			
		||||
 | 
			
		||||
    ExtrusionEntitiesPtr entities;     // we own these entities
 | 
			
		||||
    std::vector<size_t> 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<size_t>* 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<size_t>* 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;
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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 {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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<double>().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;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,5 +1,5 @@
 | 
			
		|||
#include "../ClipperUtils.hpp"
 | 
			
		||||
#include "../PolylineCollection.hpp"
 | 
			
		||||
#include "../ShortestPath.hpp"
 | 
			
		||||
#include "../Surface.hpp"
 | 
			
		||||
#include <cmath>
 | 
			
		||||
#include <algorithm>
 | 
			
		||||
| 
						 | 
				
			
			@ -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;
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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<double>().norm();
 | 
			
		||||
                    double distance = (path.first_point() - paths.back().last_point()).cast<double>().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));
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,5 +1,4 @@
 | 
			
		|||
#include "../ClipperUtils.hpp"
 | 
			
		||||
#include "../PolylineCollection.hpp"
 | 
			
		||||
#include "../Surface.hpp"
 | 
			
		||||
 | 
			
		||||
#include "FillPlanePath.hpp"
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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    </metadata>\n";
 | 
			
		||||
            stream << "\n    </metadata>\n";
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // Export layer height ranges including the layer range specific config overrides.
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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.
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -6,6 +6,7 @@
 | 
			
		|||
#include "Geometry.hpp"
 | 
			
		||||
#include "GCode/PrintExtents.hpp"
 | 
			
		||||
#include "GCode/WipeTower.hpp"
 | 
			
		||||
#include "ShortestPath.hpp"
 | 
			
		||||
#include "Utils.hpp"
 | 
			
		||||
 | 
			
		||||
#include <algorithm>
 | 
			
		||||
| 
						 | 
				
			
			@ -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<LayerToPrint> 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<size_t> 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<std::pair<size_t, size_t>> 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<std::pair<coordf_t, std::vector<LayerToPrint>>> 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<GCode::ObjectByExtruder::Island>& object_islands_by_extruder(
 | 
			
		|||
    return islands;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
std::vector<GCode::InstanceToPrint> GCode::sort_print_object_instances(
 | 
			
		||||
	std::vector<GCode::ObjectByExtruder> 			&objects_by_extruder,
 | 
			
		||||
	const std::vector<LayerToPrint> 				&layers,
 | 
			
		||||
	// Ordering must be defined for normal (non-sequential print).
 | 
			
		||||
	const std::vector<std::pair<size_t, size_t>> 	*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<InstanceToPrint> 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<std::pair<const PrintObject*, ObjectByExtruder*>> 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<size_t, size_t> &instance_id : *ordering) {
 | 
			
		||||
		    	const PrintObject &print_object = *print.objects()[instance_id.first];
 | 
			
		||||
		    	std::pair<const PrintObject*, ObjectByExtruder*> 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<LayerToPrint> &layers,
 | 
			
		||||
    const LayerTools                &layer_tools,
 | 
			
		||||
	// Pairs of PrintObject index and its instance index.
 | 
			
		||||
	const std::vector<std::pair<size_t, size_t>> *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<size_t> 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<double>();
 | 
			
		||||
            	const Vec2d s2 = layer_surface_bboxes[j].size().cast<double>();
 | 
			
		||||
            	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<InstanceToPrint> 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<LayerTools&>(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<const PrintObject*, Point> 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<const PrintObject*, Point> 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::vector<ObjectBy
 | 
			
		|||
    std::string gcode;
 | 
			
		||||
    for (const ObjectByExtruder::Island::Region ®ion : by_region) {
 | 
			
		||||
        m_config.apply(print.regions()[®ion - &by_region.front()]->config());
 | 
			
		||||
		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<ExtrusionEntityCollection*>(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");
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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<GCode::LayerToPrint>                            collect_layers_to_print(const PrintObject &object);
 | 
			
		||||
    static std::vector<LayerToPrint>        		                   collect_layers_to_print(const PrintObject &object);
 | 
			
		||||
    static std::vector<std::pair<coordf_t, std::vector<LayerToPrint>>> 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<LayerToPrint> &layers,
 | 
			
		||||
        const LayerTools  &layer_tools,
 | 
			
		||||
        const LayerTools  				&layer_tools,
 | 
			
		||||
		// Pairs of PrintObject index and its instance index.
 | 
			
		||||
		const std::vector<std::pair<size_t, size_t>> *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<Island>         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<InstanceToPrint> sort_print_object_instances(
 | 
			
		||||
		std::vector<ObjectByExtruder> 			&objects_by_extruder,
 | 
			
		||||
		const std::vector<LayerToPrint> 				&layers,
 | 
			
		||||
		// Ordering must be defined for normal (non-sequential print).
 | 
			
		||||
		const std::vector<std::pair<size_t, size_t>> 	*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<ObjectByExtruder::Island::Region> &by_region, std::unique_ptr<EdgeGrid::Grid> &lower_layer_edge_grid);
 | 
			
		||||
    std::string     extrude_infill(const Print &print, const std::vector<ObjectByExtruder::Island::Region> &by_region);
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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; });
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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;
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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);
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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);
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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);
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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);
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -3,7 +3,6 @@
 | 
			
		|||
#include "ClipperUtils.hpp"
 | 
			
		||||
#include "ExPolygon.hpp"
 | 
			
		||||
#include "Line.hpp"
 | 
			
		||||
#include "PolylineCollection.hpp"
 | 
			
		||||
#include "clipper.hpp"
 | 
			
		||||
#include <algorithm>
 | 
			
		||||
#include <cassert>
 | 
			
		||||
| 
						 | 
				
			
			@ -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<Points::size_type> &retval, Point start_near)
 | 
			
		||||
{
 | 
			
		||||
    PointConstPtrs my_points;
 | 
			
		||||
    std::map<const Point*,Points::size_type> 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<Points::size_type> &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<class T>
 | 
			
		||||
void
 | 
			
		||||
chained_path_items(Points &points, T &items, T &retval)
 | 
			
		||||
{
 | 
			
		||||
    std::vector<Points::size_type> indices;
 | 
			
		||||
    chained_path(points, indices);
 | 
			
		||||
    for (std::vector<Points::size_type>::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<class T>
 | 
			
		||||
bool
 | 
			
		||||
contains(const std::vector<T> &vector, const Point &point)
 | 
			
		||||
bool contains(const std::vector<T> &vector, const Point &point)
 | 
			
		||||
{
 | 
			
		||||
    for (typename std::vector<T>::const_iterator it = vector.begin(); it != vector.end(); ++it) {
 | 
			
		||||
        if (it->contains(point)) return true;
 | 
			
		||||
| 
						 | 
				
			
			@ -369,16 +325,14 @@ contains(const std::vector<T> &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;
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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<Points::size_type> &retval, Point start_near);
 | 
			
		||||
void chained_path(const Points &points, std::vector<Points::size_type> &retval);
 | 
			
		||||
template<class T> void chained_path_items(Points &points, T &items, T &retval);
 | 
			
		||||
bool directions_parallel(double angle1, double angle2, double max_diff = 0);
 | 
			
		||||
template<class T> bool contains(const std::vector<T> &vector, const Point &point);
 | 
			
		||||
template<typename T> T rad2deg(T angle) { return T(180.0) * angle / T(PI); }
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										233
									
								
								src/libslic3r/KDTreeIndirect.hpp
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										233
									
								
								src/libslic3r/KDTreeIndirect.hpp
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -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 <algorithm>
 | 
			
		||||
#include <limits>
 | 
			
		||||
#include <vector>
 | 
			
		||||
 | 
			
		||||
#include "Utils.hpp" // for next_highest_power_of_2()
 | 
			
		||||
 | 
			
		||||
namespace Slic3r {
 | 
			
		||||
 | 
			
		||||
// KD tree for N-dimensional closest point search.
 | 
			
		||||
template<size_t ANumDimensions, typename ACoordType, typename ACoordinateFn>
 | 
			
		||||
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<size_t>   indices) : coordinate(coordinate) { this->build(std::move(indices)); }
 | 
			
		||||
	KDTreeIndirect(CoordinateFn coordinate, std::vector<size_t> &&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<size_t> 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<size_t> &&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<typename CoordType> 
 | 
			
		||||
	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<typename Visitor>
 | 
			
		||||
	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<size_t> &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 <left, right> at k using QuickSelect method.
 | 
			
		||||
	// https://en.wikipedia.org/wiki/Quickselect
 | 
			
		||||
	void partition_input(std::vector<size_t> &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<typename Visitor>
 | 
			
		||||
	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<size_t> m_nodes;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
// Find a closest point using Euclidian metrics.
 | 
			
		||||
// Returns npos if not found.
 | 
			
		||||
template<typename KDTreeIndirectType, typename PointType, typename FilterFn>
 | 
			
		||||
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<CoordType>::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<typename KDTreeIndirectType, typename PointType>
 | 
			
		||||
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_ */
 | 
			
		||||
| 
						 | 
				
			
			@ -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 <boost/log/trivial.hpp>
 | 
			
		||||
| 
						 | 
				
			
			@ -57,8 +57,7 @@ void Layer::make_slices()
 | 
			
		|||
        ordering_points.push_back(ex.contour.first_point());
 | 
			
		||||
    
 | 
			
		||||
    // sort slices
 | 
			
		||||
    std::vector<Points::size_type> order;
 | 
			
		||||
    Slic3r::Geometry::chained_path(ordering_points, order);
 | 
			
		||||
    std::vector<Points::size_type> order = chain_points(ordering_points);
 | 
			
		||||
    
 | 
			
		||||
    // populate slices vector
 | 
			
		||||
    for (size_t i : order)
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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)
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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
 | 
			
		|||
}
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
 
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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)
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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<double>().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) {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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(); }
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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<T>::iterator;
 | 
			
		||||
	using const_iterator = typename std::vector<T>::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<T>	m_heap;
 | 
			
		||||
| 
						 | 
				
			
			@ -42,6 +49,17 @@ MutablePriorityQueue<T, IndexSetter, LessPredicate> make_mutable_priority_queue(
 | 
			
		|||
    	std::forward<IndexSetter>(index_setter), std::forward<LessPredicate>(less_predicate));
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
template<class T, class LessPredicate, class IndexSetter>
 | 
			
		||||
inline void MutablePriorityQueue<T, LessPredicate, IndexSetter>::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<size_t>::max());
 | 
			
		||||
#endif /* NDEBUG */
 | 
			
		||||
	m_heap.clear();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
template<class T, class LessPredicate, class IndexSetter>
 | 
			
		||||
inline void MutablePriorityQueue<T, LessPredicate, IndexSetter>::push(const T &item)
 | 
			
		||||
{
 | 
			
		||||
| 
						 | 
				
			
			@ -64,6 +82,10 @@ template<class T, class LessPredicate, class IndexSetter>
 | 
			
		|||
inline void MutablePriorityQueue<T, LessPredicate, IndexSetter>::pop()
 | 
			
		||||
{
 | 
			
		||||
	assert(! m_heap.empty());
 | 
			
		||||
#ifndef NDEBUG
 | 
			
		||||
	// Mark as removed from the queue.
 | 
			
		||||
	m_index_setter(m_heap.front(), std::numeric_limits<size_t>::max());
 | 
			
		||||
#endif /* NDEBUG */
 | 
			
		||||
	if (m_heap.size() > 1) {
 | 
			
		||||
		m_heap.front() = m_heap.back();
 | 
			
		||||
		m_heap.pop_back();
 | 
			
		||||
| 
						 | 
				
			
			@ -77,6 +99,10 @@ template<class T, class LessPredicate, class IndexSetter>
 | 
			
		|||
inline void MutablePriorityQueue<T, LessPredicate, IndexSetter>::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<size_t>::max());
 | 
			
		||||
#endif /* NDEBUG */
 | 
			
		||||
	if (idx + 1 == m_heap.size()) {
 | 
			
		||||
		m_heap.pop_back();
 | 
			
		||||
		return;
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,6 +1,8 @@
 | 
			
		|||
#include "PerimeterGenerator.hpp"
 | 
			
		||||
#include "ClipperUtils.hpp"
 | 
			
		||||
#include "ExtrusionEntityCollection.hpp"
 | 
			
		||||
#include "ShortestPath.hpp"
 | 
			
		||||
 | 
			
		||||
#include <cmath>
 | 
			
		||||
#include <cassert>
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -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<ExtrusionEntity*> &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<std::pair<size_t, bool>> chain = chain_extrusion_entities(coll.entities, &zero_point);
 | 
			
		||||
    ExtrusionEntityCollection out;
 | 
			
		||||
    for (const std::pair<size_t, bool> &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<ExtrusionLoop*>(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<ExtrusionLoop*>(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
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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;
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -13,13 +13,14 @@ namespace Slic3r {
 | 
			
		|||
class Polygon;
 | 
			
		||||
typedef std::vector<Polygon> 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;
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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;
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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 {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,92 +0,0 @@
 | 
			
		|||
#include "PolylineCollection.hpp"
 | 
			
		||||
 | 
			
		||||
namespace Slic3r {
 | 
			
		||||
 | 
			
		||||
struct Chaining
 | 
			
		||||
{
 | 
			
		||||
    Point first;
 | 
			
		||||
    Point last;
 | 
			
		||||
    size_t idx;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
template<typename T>
 | 
			
		||||
inline int nearest_point_index(const std::vector<Chaining> &pairs, const Point &start_near, bool no_reverse)
 | 
			
		||||
{
 | 
			
		||||
    T dmin = std::numeric_limits<T>::max();
 | 
			
		||||
    int idx = 0;
 | 
			
		||||
    for (std::vector<Chaining>::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<Chaining> 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<double>(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
 | 
			
		||||
| 
						 | 
				
			
			@ -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
 | 
			
		||||
| 
						 | 
				
			
			@ -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<const ClipperLib_Z::Path*, size_t> &l, const std::pair<const ClipperLib_Z::Path*, size_t> &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<ExtrusionPath*>(m_brim.entities.back())->polyline.points;
 | 
			
		||||
			            Points &points = static_cast<ExtrusionPath*>(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 {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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)); }
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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,
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -12,7 +12,6 @@
 | 
			
		|||
#include <boost/log/trivial.hpp>
 | 
			
		||||
#include <float.h>
 | 
			
		||||
 | 
			
		||||
#include <tbb/task_scheduler_init.h>
 | 
			
		||||
#include <tbb/parallel_for.h>
 | 
			
		||||
#include <tbb/atomic.h>
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -75,13 +74,9 @@ PrintBase::ApplyStatus PrintObject::set_copies(const Points &points)
 | 
			
		|||
{
 | 
			
		||||
    // Order copies with a nearest-neighbor search.
 | 
			
		||||
    std::vector<Point> copies;
 | 
			
		||||
    {
 | 
			
		||||
        std::vector<Points::size_type> 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)
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										514
									
								
								src/libslic3r/ShortestPath.cpp
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										514
									
								
								src/libslic3r/ShortestPath.cpp
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -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 <cmath>
 | 
			
		||||
#include <cassert>
 | 
			
		||||
 | 
			
		||||
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<typename EndPointType, typename KDTreeType, typename CouldReverseFunc>
 | 
			
		||||
std::vector<std::pair<size_t, bool>> chain_segments_closest_point(std::vector<EndPointType> &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<std::pair<size_t, bool>> 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<typename PointType, typename SegmentEndPointFunc, bool REVERSE_COULD_FAIL, typename CouldReverseFunc>
 | 
			
		||||
std::vector<std::pair<size_t, bool>> chain_segments_greedy_constrained_reversals_(SegmentEndPointFunc end_point_func, CouldReverseFunc could_reverse_func, size_t num_segments, const PointType *start_near)
 | 
			
		||||
{
 | 
			
		||||
	std::vector<std::pair<size_t, bool>> 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<double>().squaredNorm() < (end_point_func(0, false) - *start_near).template cast<double>().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<double>::max();
 | 
			
		||||
			size_t    heap_idx = std::numeric_limits<size_t>::max();
 | 
			
		||||
		};
 | 
			
		||||
	    std::vector<EndPoint> 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<double>());
 | 
			
		||||
            end_points.emplace_back(end_point_func(i, false).template cast<double>());
 | 
			
		||||
	    }
 | 
			
		||||
 | 
			
		||||
	    // 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<size_t> 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<size_t>::max();
 | 
			
		||||
		if (start_near != nullptr) {
 | 
			
		||||
            size_t idx = find_closest_point(kdtree, start_near->template cast<double>());
 | 
			
		||||
			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*>(
 | 
			
		||||
			[](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<size_t>::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<size_t>::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<EndPoint, decltype(kdtree), CouldReverseFunc>(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<typename PointType, typename SegmentEndPointFunc, typename CouldReverseFunc>
 | 
			
		||||
std::vector<std::pair<size_t, bool>> 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_<PointType, SegmentEndPointFunc, true, CouldReverseFunc>(end_point_func, could_reverse_func, num_segments, start_near);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
template<typename PointType, typename SegmentEndPointFunc>
 | 
			
		||||
std::vector<std::pair<size_t, bool>> 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_<PointType, SegmentEndPointFunc, false, decltype(could_reverse_func)>(end_point_func, could_reverse_func, num_segments, start_near);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
std::vector<std::pair<size_t, bool>> chain_extrusion_entities(std::vector<ExtrusionEntity*> &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<std::pair<size_t, bool>> out = chain_segments_greedy_constrained_reversals<Point, decltype(segment_end_point), decltype(could_reverse)>(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<ExtrusionEntity*> &entities, const std::vector<std::pair<size_t, bool>> &chain)
 | 
			
		||||
{
 | 
			
		||||
	assert(entities.size() == chain.size());
 | 
			
		||||
	std::vector<ExtrusionEntity*> out;
 | 
			
		||||
	out.reserve(entities.size());
 | 
			
		||||
    for (const std::pair<size_t, bool> &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<ExtrusionEntity*> &entities, const Point *start_near)
 | 
			
		||||
{
 | 
			
		||||
	reorder_extrusion_entities(entities, chain_extrusion_entities(entities, start_near));
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
std::vector<std::pair<size_t, bool>> chain_extrusion_paths(std::vector<ExtrusionPath> &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<Point, decltype(segment_end_point)>(segment_end_point, extrusion_paths.size(), start_near);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void reorder_extrusion_paths(std::vector<ExtrusionPath> &extrusion_paths, const std::vector<std::pair<size_t, bool>> &chain)
 | 
			
		||||
{
 | 
			
		||||
	assert(extrusion_paths.size() == chain.size());
 | 
			
		||||
	std::vector<ExtrusionPath> out;
 | 
			
		||||
	out.reserve(extrusion_paths.size());
 | 
			
		||||
    for (const std::pair<size_t, bool> &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<ExtrusionPath> &extrusion_paths, const Point *start_near)
 | 
			
		||||
{
 | 
			
		||||
	reorder_extrusion_paths(extrusion_paths, chain_extrusion_paths(extrusion_paths, start_near));
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
std::vector<size_t> 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<std::pair<size_t, bool>> ordered = chain_segments_greedy<Point, decltype(segment_end_point)>(segment_end_point, points.size(), start_near);
 | 
			
		||||
	std::vector<size_t> 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<std::pair<size_t, bool>> ordered = chain_segments_greedy<Point, decltype(segment_end_point)>(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<class T> 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<std::pair<size_t, bool>> ordered = chain_segments_greedy<Point, decltype(segment_end_point)>(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<std::pair<size_t, size_t>> chain_print_object_instances(const Print &print)
 | 
			
		||||
{
 | 
			
		||||
    // Order objects using a nearest neighbor search.
 | 
			
		||||
    Points object_reference_points;
 | 
			
		||||
    std::vector<std::pair<size_t, size_t>> 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<std::pair<size_t, bool>> ordered = chain_segments_greedy<Point, decltype(segment_end_point)>(segment_end_point, instances.size(), nullptr);
 | 
			
		||||
    std::vector<std::pair<size_t, size_t>> out;
 | 
			
		||||
	out.reserve(instances.size());
 | 
			
		||||
	for (auto &segment_and_reversal : ordered)
 | 
			
		||||
		out.emplace_back(instances[segment_and_reversal.first]);
 | 
			
		||||
	return out;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
} // namespace Slic3r
 | 
			
		||||
							
								
								
									
										38
									
								
								src/libslic3r/ShortestPath.hpp
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										38
									
								
								src/libslic3r/ShortestPath.hpp
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,38 @@
 | 
			
		|||
#ifndef slic3r_ShortestPath_hpp_
 | 
			
		||||
#define slic3r_ShortestPath_hpp_
 | 
			
		||||
 | 
			
		||||
#include "libslic3r.h"
 | 
			
		||||
#include "ExtrusionEntity.hpp"
 | 
			
		||||
#include "Point.hpp"
 | 
			
		||||
 | 
			
		||||
#include <utility>
 | 
			
		||||
#include <vector>
 | 
			
		||||
 | 
			
		||||
namespace ClipperLib { class PolyNode; }
 | 
			
		||||
 | 
			
		||||
namespace Slic3r {
 | 
			
		||||
 | 
			
		||||
std::vector<size_t> 				 chain_points(const Points &points, Point *start_near = nullptr);
 | 
			
		||||
 | 
			
		||||
std::vector<std::pair<size_t, bool>> chain_extrusion_entities(std::vector<ExtrusionEntity*> &entities, const Point *start_near = nullptr);
 | 
			
		||||
void                                 reorder_extrusion_entities(std::vector<ExtrusionEntity*> &entities, const std::vector<std::pair<size_t, bool>> &chain);
 | 
			
		||||
void                                 chain_and_reorder_extrusion_entities(std::vector<ExtrusionEntity*> &entities, const Point *start_near = nullptr);
 | 
			
		||||
 | 
			
		||||
std::vector<std::pair<size_t, bool>> chain_extrusion_paths(std::vector<ExtrusionPath> &extrusion_paths, const Point *start_near = nullptr);
 | 
			
		||||
void                                 reorder_extrusion_paths(std::vector<ExtrusionPath> &extrusion_paths, std::vector<std::pair<size_t, bool>> &chain);
 | 
			
		||||
void                                 chain_and_reorder_extrusion_paths(std::vector<ExtrusionPath> &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<ClipperLib::PolyNode*>	 chain_clipper_polynodes(const Points &points, const std::vector<ClipperLib::PolyNode*> &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<std::pair<size_t, size_t>> chain_print_object_instances(const Print &print);
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
} // namespace Slic3r
 | 
			
		||||
 | 
			
		||||
#endif /* slic3r_ShortestPath_hpp_ */
 | 
			
		||||
| 
						 | 
				
			
			@ -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<float>      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));
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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<std::string, std::map<std::string, std::set<std::string>>> &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();
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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.
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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.
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -42,7 +42,7 @@ static wxString generate_html_row(const Config::Snapshot &snapshot, bool row_eve
 | 
			
		|||
        text += " (" + wxString::FromUTF8(snapshot.comment.data()) + ")";
 | 
			
		||||
    text += "</b></font><br>";
 | 
			
		||||
    // End of row header.
 | 
			
		||||
    text += _(L("slic3r version")) + ": " + snapshot.slic3r_version_captured.to_string() + "<br>";
 | 
			
		||||
    text += _(L("PrusaSlicer version")) + ": " + snapshot.slic3r_version_captured.to_string() + "<br>";
 | 
			
		||||
    text += _(L("print")) + ": " + snapshot.print + "<br>";
 | 
			
		||||
    text += _(L("filaments")) + ": " + snapshot.filaments.front() + "<br>";
 | 
			
		||||
    text += _(L("printer")) + ": " + snapshot.printer + "<br>";
 | 
			
		||||
| 
						 | 
				
			
			@ -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 += "<br>";
 | 
			
		||||
        for (const std::pair<std::string, std::set<std::string>> &model : vc.models_variants_installed) {
 | 
			
		||||
            text += _(L("model")) + ": " + model.first + ", " + _(L("variants")) + ": ";
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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;
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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<double>();
 | 
			
		||||
#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<double>();
 | 
			
		||||
#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<std::vector<float>> roles_values;
 | 
			
		||||
            std::vector<std::vector<float>> roles_values;
 | 
			
		||||
			roles_values.assign(size_t(erCount), std::vector<float>());
 | 
			
		||||
		    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<float> &values : roles_values) {
 | 
			
		||||
		    	sort_remove_duplicates(values);
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -71,6 +71,8 @@ public:
 | 
			
		|||
wxDECLARE_EVENT(EVT_GLCANVAS_OBJECT_SELECT, SimpleEvent);
 | 
			
		||||
 | 
			
		||||
using Vec2dEvent = Event<Vec2d>;
 | 
			
		||||
// _bool_ value is used as a indicator of selection in the 3DScene
 | 
			
		||||
using RBtnEvent = Event<std::pair<Vec2d, bool>>;
 | 
			
		||||
template <size_t N> using Vec2dsEvent = ArrayEvent<Vec2d, N>;
 | 
			
		||||
 | 
			
		||||
using Vec3dEvent = Event<Vec3d>;
 | 
			
		||||
| 
						 | 
				
			
			@ -78,7 +80,7 @@ template <size_t N> using Vec3dsEvent = ArrayEvent<Vec3d, N>;
 | 
			
		|||
 | 
			
		||||
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);
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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<LayerRangeEditor*>(e.GetWindow());
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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<size_t>& object_idxs)
 | 
			
		||||
| 
						 | 
				
			
			@ -761,9 +773,9 @@ void ObjectList::paste_objects_into_list(const std::vector<size_t>& 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<size_t> 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<double>(), -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();
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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<std::pair<wxString, bool>> &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);
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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")));
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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<double>()))
 | 
			
		||||
        if (is_mesh_point_clipped(support_point.pos.cast<double>()))
 | 
			
		||||
            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<Vec
 | 
			
		|||
    trafo.set_offset(trafo.get_offset() + Vec3d(0., 0., m_z_shift));
 | 
			
		||||
 | 
			
		||||
    // The raycaster query
 | 
			
		||||
    std::vector<Vec3f> hits;
 | 
			
		||||
    std::vector<Vec3f> 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<hits.size(); ++i)
 | 
			
		||||
            if (! is_point_clipped(hits[i].cast<double>()))
 | 
			
		||||
                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<Vec3f> points_inside;
 | 
			
		||||
            std::vector<unsigned int> 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<float>());
 | 
			
		||||
                points_inside.push_back(points[idx].cast<float>());
 | 
			
		||||
 | 
			
		||||
            // 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<double>()); }))
 | 
			
		||||
            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<double>())) {
 | 
			
		||||
                    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;
 | 
			
		||||
        }
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -125,7 +125,7 @@ private:
 | 
			
		|||
    mutable std::unique_ptr<MeshClipper> m_supports_clipper;
 | 
			
		||||
 | 
			
		||||
    std::vector<const ConfigOption*> get_config_options(const std::vector<std::string>& 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<Eigen::MatrixXf, 3>* aabb, const Vec3f& normal, double offset, std::vector<unsigned int>& out) const;
 | 
			
		||||
 | 
			
		||||
    // Methods that do the model_object and editing cache synchronization,
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -192,7 +192,7 @@ ConfigOptionsGroupShp LambdaObjectDialog::init_modificator_options_page(const wx
 | 
			
		|||
    else
 | 
			
		||||
        panel->SetSizer(optgroup->sizer);
 | 
			
		||||
 | 
			
		||||
	return optgroup;
 | 
			
		||||
    return optgroup;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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;
 | 
			
		||||
    }
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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<Vec3f>* positions, std::vector<Vec3f>* 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<int, 4>& 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; i<hits.size(); ++i)
 | 
			
		||||
            if (! clipping_plane->is_point_clipped(trafo * m_AABB_wrapper->get_hit_pos(hits[i]).cast<double>()))
 | 
			
		||||
                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<unsigned> MeshRaycaster::get_unobscured_idxs(const Geometry::Transformation& trafo, const Camera& camera, const std::vector<Vec3f>& points,
 | 
			
		||||
                                                       std::function<bool(const Vec3f&)> fn_ignore_hit) const
 | 
			
		||||
                                                       const ClippingPlane* clipping_plane) const
 | 
			
		||||
{
 | 
			
		||||
    std::vector<unsigned> out;
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -206,19 +211,24 @@ std::vector<unsigned> MeshRaycaster::get_unobscured_idxs(const Geometry::Transfo
 | 
			
		|||
    Vec3f direction_to_camera_mesh = (instance_matrix_no_translation_no_scaling.inverse().cast<float>() * direction_to_camera).normalized().eval();
 | 
			
		||||
    Vec3f scaling = trafo.get_scaling_factor().cast<float>();
 | 
			
		||||
    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<float>();
 | 
			
		||||
 | 
			
		||||
    for (size_t i=0; i<points.size(); ++i) {
 | 
			
		||||
        const Vec3f& pt = points[i];
 | 
			
		||||
        if (clipping_plane && clipping_plane->is_point_clipped(pt.cast<double>()))
 | 
			
		||||
            continue;
 | 
			
		||||
 | 
			
		||||
        bool is_obscured = false;
 | 
			
		||||
        // Cast a ray in the direction of the camera and look for intersection with the mesh:
 | 
			
		||||
        std::vector<igl::Hit> 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<unsigned> MeshRaycaster::get_unobscured_idxs(const Geometry::Transfo
 | 
			
		|||
            // Eradicate all hits that the caller wants to ignore
 | 
			
		||||
            for (unsigned j=0; j<hits.size(); ++j) {
 | 
			
		||||
                const igl::Hit& hit = hits[j];
 | 
			
		||||
                if (fn_ignore_hit(m_AABB_wrapper->get_hit_pos(hit))) {
 | 
			
		||||
                if (clipping_plane && clipping_plane->is_point_clipped(trafo.get_matrix() * m_AABB_wrapper->get_hit_pos(hit).cast<double>())) {
 | 
			
		||||
                    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())
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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<Vec3f>* positions = nullptr, std::vector<Vec3f>* normals = nullptr) const;
 | 
			
		||||
                           Vec3f& position, Vec3f& normal, const ClippingPlane* clipping_plane = nullptr) const;
 | 
			
		||||
 | 
			
		||||
    std::vector<unsigned> get_unobscured_idxs(const Geometry::Transformation& trafo, const Camera& camera,
 | 
			
		||||
                                              const std::vector<Vec3f>& points, std::function<bool(const Vec3f&)> fn_ignore_hit) const;
 | 
			
		||||
                                              const std::vector<Vec3f>& points, const ClippingPlane* clipping_plane = nullptr) const;
 | 
			
		||||
 | 
			
		||||
    Vec3f get_closest_point(const Vec3f& point, Vec3f* normal = nullptr) const;
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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) {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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<wxMenuItem*> 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<wxMenu*>(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<std::string> filament_presets = wxGetApp().preset_bundle->filament_presets;
 | 
			
		||||
    if (filament_presets.size() > 1 && 
 | 
			
		||||
        p->config->option<ConfigOptionStrings>("filament_colour")->values.size() == filament_presets.size())
 | 
			
		||||
    {
 | 
			
		||||
        const PresetCollection& filaments = wxGetApp().preset_bundle->filaments;
 | 
			
		||||
        std::vector<std::string> 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<ConfigOptionStrings>("filament_colour")->values != filament_colors) {
 | 
			
		||||
            config->option<ConfigOptionStrings>("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;
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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);
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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);
 | 
			
		||||
	}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -410,7 +410,7 @@ void Selection::set_deserialized(EMode mode, const std::vector<std::pair<size_t,
 | 
			
		|||
    if (! m_valid)
 | 
			
		||||
        return;
 | 
			
		||||
 | 
			
		||||
	m_mode = mode;
 | 
			
		||||
    m_mode = mode;
 | 
			
		||||
    for (unsigned int i : m_list)
 | 
			
		||||
        (*m_volumes)[i]->selected = false;
 | 
			
		||||
    m_list.clear();
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -3032,6 +3032,12 @@ void Tab::save_preset(std::string name /*= ""*/)
 | 
			
		|||
    if (m_type == Preset::TYPE_PRINTER)
 | 
			
		||||
        static_cast<TabPrinter*>(this)->m_initial_extruders_count = static_cast<TabPrinter*>(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.
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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")));
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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++;
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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<Point> first_point();
 | 
			
		||||
    Clone<Point> 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<size_t> orig_indices()
 | 
			
		||||
        %code{% RETVAL = THIS->orig_indices; %};
 | 
			
		||||
    Polygons polygons_covered_by_width();
 | 
			
		||||
    Polygons polygons_covered_by_spacing();
 | 
			
		||||
%{
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -3,7 +3,6 @@
 | 
			
		|||
%{
 | 
			
		||||
#include <xsinit.h>
 | 
			
		||||
#include "libslic3r/Fill/Fill.hpp"
 | 
			
		||||
#include "libslic3r/PolylineCollection.hpp"
 | 
			
		||||
#include "libslic3r/ExtrusionEntity.hpp"
 | 
			
		||||
#include "libslic3r/ExtrusionEntityCollection.hpp"
 | 
			
		||||
%}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -3,6 +3,7 @@
 | 
			
		|||
%{
 | 
			
		||||
#include <xsinit.h>
 | 
			
		||||
#include "libslic3r/Geometry.hpp"
 | 
			
		||||
#include "libslic3r/ShortestPath.hpp"
 | 
			
		||||
%}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -49,7 +50,7 @@ std::vector<Points::size_type>
 | 
			
		|||
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
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -19,8 +19,6 @@
 | 
			
		|||
        %code%{ RETVAL = &THIS->fill_surfaces; %};
 | 
			
		||||
    Polygons bridged()
 | 
			
		||||
        %code%{ RETVAL = THIS->bridged; %};
 | 
			
		||||
    Ref<PolylineCollection> unsupported_bridge_edges()
 | 
			
		||||
        %code%{ RETVAL = &THIS->unsupported_bridge_edges; %};
 | 
			
		||||
    Ref<ExtrusionEntityCollection> perimeters()
 | 
			
		||||
        %code%{ RETVAL = &THIS->perimeters; %};
 | 
			
		||||
    Ref<ExtrusionEntityCollection> fills()
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -2,7 +2,11 @@
 | 
			
		|||
 | 
			
		||||
%{
 | 
			
		||||
#include <xsinit.h>
 | 
			
		||||
#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<Point> leftmost_point();
 | 
			
		||||
%{
 | 
			
		||||
 | 
			
		||||
PolylineCollection*
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue