mirror of
https://github.com/SoftFever/OrcaSlicer.git
synced 2025-10-20 15:21:21 -06:00
Merged with master
This commit is contained in:
commit
9e7634b6e8
101 changed files with 8756 additions and 4544 deletions
|
@ -1,3 +1,4 @@
|
|||
cmake_minimum_required(VERSION 3.0)
|
||||
|
||||
|
||||
add_definitions(-D_BSD_SOURCE -D_DEFAULT_SOURCE) # To enable various useful macros and functions on Unices
|
||||
|
@ -13,67 +14,74 @@ endif()
|
|||
|
||||
|
||||
set(AVRDUDE_SOURCES
|
||||
${LIBDIR}/avrdude/arduino.c
|
||||
${LIBDIR}/avrdude/avr.c
|
||||
# ${LIBDIR}/avrdude/avrftdi.c
|
||||
# ${LIBDIR}/avrdude/avrftdi_tpi.c
|
||||
${LIBDIR}/avrdude/avrpart.c
|
||||
${LIBDIR}/avrdude/avr910.c
|
||||
${LIBDIR}/avrdude/bitbang.c
|
||||
${LIBDIR}/avrdude/buspirate.c
|
||||
${LIBDIR}/avrdude/butterfly.c
|
||||
${LIBDIR}/avrdude/config.c
|
||||
${LIBDIR}/avrdude/config_gram.c
|
||||
# ${LIBDIR}/avrdude/confwin.c
|
||||
${LIBDIR}/avrdude/crc16.c
|
||||
# ${LIBDIR}/avrdude/dfu.c
|
||||
${LIBDIR}/avrdude/fileio.c
|
||||
# ${LIBDIR}/avrdude/flip1.c
|
||||
# ${LIBDIR}/avrdude/flip2.c
|
||||
# ${LIBDIR}/avrdude/ft245r.c
|
||||
# ${LIBDIR}/avrdude/jtagmkI.c
|
||||
# ${LIBDIR}/avrdude/jtagmkII.c
|
||||
# ${LIBDIR}/avrdude/jtag3.c
|
||||
${LIBDIR}/avrdude/lexer.c
|
||||
${LIBDIR}/avrdude/linuxgpio.c
|
||||
${LIBDIR}/avrdude/lists.c
|
||||
# ${LIBDIR}/avrdude/par.c
|
||||
${LIBDIR}/avrdude/pgm.c
|
||||
${LIBDIR}/avrdude/pgm_type.c
|
||||
${LIBDIR}/avrdude/pickit2.c
|
||||
${LIBDIR}/avrdude/pindefs.c
|
||||
# ${LIBDIR}/avrdude/ppi.c
|
||||
# ${LIBDIR}/avrdude/ppiwin.c
|
||||
${LIBDIR}/avrdude/safemode.c
|
||||
${LIBDIR}/avrdude/ser_avrdoper.c
|
||||
${LIBDIR}/avrdude/serbb_posix.c
|
||||
${LIBDIR}/avrdude/serbb_win32.c
|
||||
${LIBDIR}/avrdude/ser_posix.c
|
||||
${LIBDIR}/avrdude/ser_win32.c
|
||||
${LIBDIR}/avrdude/stk500.c
|
||||
${LIBDIR}/avrdude/stk500generic.c
|
||||
${LIBDIR}/avrdude/stk500v2.c
|
||||
${LIBDIR}/avrdude/term.c
|
||||
${LIBDIR}/avrdude/update.c
|
||||
# ${LIBDIR}/avrdude/usbasp.c
|
||||
# ${LIBDIR}/avrdude/usb_hidapi.c
|
||||
# ${LIBDIR}/avrdude/usb_libusb.c
|
||||
# ${LIBDIR}/avrdude/usbtiny.c
|
||||
${LIBDIR}/avrdude/wiring.c
|
||||
arduino.c
|
||||
avr.c
|
||||
# avrftdi.c
|
||||
# avrftdi_tpi.c
|
||||
avrpart.c
|
||||
avr910.c
|
||||
bitbang.c
|
||||
buspirate.c
|
||||
butterfly.c
|
||||
config.c
|
||||
config_gram.c
|
||||
# confwin.c
|
||||
crc16.c
|
||||
# dfu.c
|
||||
fileio.c
|
||||
# flip1.c
|
||||
# flip2.c
|
||||
# ft245r.c
|
||||
# jtagmkI.c
|
||||
# jtagmkII.c
|
||||
# jtag3.c
|
||||
lexer.c
|
||||
linuxgpio.c
|
||||
lists.c
|
||||
# par.c
|
||||
pgm.c
|
||||
pgm_type.c
|
||||
pickit2.c
|
||||
pindefs.c
|
||||
# ppi.c
|
||||
# ppiwin.c
|
||||
safemode.c
|
||||
ser_avrdoper.c
|
||||
serbb_posix.c
|
||||
serbb_win32.c
|
||||
ser_posix.c
|
||||
ser_win32.c
|
||||
stk500.c
|
||||
stk500generic.c
|
||||
stk500v2.c
|
||||
term.c
|
||||
update.c
|
||||
# usbasp.c
|
||||
# usb_hidapi.c
|
||||
# usb_libusb.c
|
||||
# usbtiny.c
|
||||
wiring.c
|
||||
|
||||
${LIBDIR}/avrdude/main.c
|
||||
${LIBDIR}/avrdude/avrdude-slic3r.hpp
|
||||
${LIBDIR}/avrdude/avrdude-slic3r.cpp
|
||||
main.c
|
||||
avrdude-slic3r.hpp
|
||||
avrdude-slic3r.cpp
|
||||
)
|
||||
if (WIN32)
|
||||
set(AVRDUDE_SOURCES ${AVRDUDE_SOURCES}
|
||||
${LIBDIR}/avrdude/windows/unistd.cpp
|
||||
${LIBDIR}/avrdude/windows/getopt.c
|
||||
windows/unistd.cpp
|
||||
windows/getopt.c
|
||||
)
|
||||
endif()
|
||||
add_library(avrdude STATIC ${AVRDUDE_SOURCES})
|
||||
|
||||
set(STANDALONE_SOURCES
|
||||
main-standalone.c
|
||||
)
|
||||
add_executable(avrdude-slic3r ${STANDALONE_SOURCES})
|
||||
target_link_libraries(avrdude-slic3r avrdude)
|
||||
set_target_properties(avrdude-slic3r PROPERTIES EXCLUDE_FROM_ALL TRUE)
|
||||
|
||||
if (WIN32)
|
||||
target_compile_definitions(avrdude PRIVATE WIN32NATIVE=1)
|
||||
target_include_directories(avrdude SYSTEM PRIVATE ${LIBDIR}/avrdude/windows) # So that sources find the getopt.h windows drop-in
|
||||
target_include_directories(avrdude SYSTEM PRIVATE windows) # So that sources find the getopt.h windows drop-in
|
||||
endif()
|
||||
|
|
54
xs/src/avrdude/Makefile.standalone
Normal file
54
xs/src/avrdude/Makefile.standalone
Normal file
|
@ -0,0 +1,54 @@
|
|||
|
||||
TARGET = avrdude-slic3r
|
||||
|
||||
SOURCES = \
|
||||
arduino.c \
|
||||
avr.c \
|
||||
avrpart.c \
|
||||
avr910.c \
|
||||
bitbang.c \
|
||||
buspirate.c \
|
||||
butterfly.c \
|
||||
config.c \
|
||||
config_gram.c \
|
||||
crc16.c \
|
||||
fileio.c \
|
||||
lexer.c \
|
||||
linuxgpio.c \
|
||||
lists.c \
|
||||
pgm.c \
|
||||
pgm_type.c \
|
||||
pickit2.c \
|
||||
pindefs.c \
|
||||
safemode.c \
|
||||
ser_avrdoper.c \
|
||||
serbb_posix.c \
|
||||
serbb_win32.c \
|
||||
ser_posix.c \
|
||||
ser_win32.c \
|
||||
stk500.c \
|
||||
stk500generic.c \
|
||||
stk500v2.c \
|
||||
term.c \
|
||||
update.c \
|
||||
wiring.c \
|
||||
main.c \
|
||||
main-standalone.c
|
||||
|
||||
OBJECTS = $(SOURCES:.c=.o)
|
||||
CFLAGS = -std=c99 -Wall -D_BSD_SOURCE -D_DEFAULT_SOURCE -O3 -DNDEBUG -fPIC
|
||||
LDFLAGS = -lm
|
||||
|
||||
CC = gcc
|
||||
RM = rm
|
||||
|
||||
all: $(TARGET)
|
||||
|
||||
$(TARGET): $(OBJECTS)
|
||||
$(CC) -o ./$@ $(OBJECTS) $(LDFLAGS)
|
||||
|
||||
$(OBJECTS): %.o: %.c
|
||||
$(CC) $(CFLAGS) -o $@ -c $<
|
||||
|
||||
clean:
|
||||
$(RM) -f $(OBJECTS) $(TARGET)
|
|
@ -35,6 +35,8 @@ struct AvrDude::priv
|
|||
{
|
||||
std::string sys_config;
|
||||
std::deque<std::vector<std::string>> args;
|
||||
bool cancelled = false;
|
||||
int exit_code = 0;
|
||||
size_t current_args_set = 0;
|
||||
RunFn run_fn;
|
||||
MessageFn message_fn;
|
||||
|
@ -141,14 +143,19 @@ AvrDude::Ptr AvrDude::run()
|
|||
|
||||
if (self->p) {
|
||||
auto avrdude_thread = std::thread([self]() {
|
||||
bool cancel = false;
|
||||
int res = -1;
|
||||
|
||||
if (self->p->run_fn) {
|
||||
self->p->run_fn();
|
||||
}
|
||||
|
||||
auto res = self->p->run();
|
||||
if (! self->p->cancelled) {
|
||||
self->p->exit_code = self->p->run();
|
||||
}
|
||||
|
||||
if (self->p->complete_fn) {
|
||||
self->p->complete_fn(res, self->p->current_args_set);
|
||||
self->p->complete_fn();
|
||||
}
|
||||
});
|
||||
|
||||
|
@ -160,7 +167,10 @@ AvrDude::Ptr AvrDude::run()
|
|||
|
||||
void AvrDude::cancel()
|
||||
{
|
||||
::avrdude_cancel();
|
||||
if (p) {
|
||||
p->cancelled = true;
|
||||
::avrdude_cancel();
|
||||
}
|
||||
}
|
||||
|
||||
void AvrDude::join()
|
||||
|
@ -170,5 +180,20 @@ void AvrDude::join()
|
|||
}
|
||||
}
|
||||
|
||||
bool AvrDude::cancelled()
|
||||
{
|
||||
return p ? p->cancelled : false;
|
||||
}
|
||||
|
||||
int AvrDude::exit_code()
|
||||
{
|
||||
return p ? p->exit_code : 0;
|
||||
}
|
||||
|
||||
size_t AvrDude::last_args_set()
|
||||
{
|
||||
return p ? p->current_args_set : 0;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
|
|
@ -15,7 +15,7 @@ public:
|
|||
typedef std::function<void()> RunFn;
|
||||
typedef std::function<void(const char * /* msg */, unsigned /* size */)> MessageFn;
|
||||
typedef std::function<void(const char * /* task */, unsigned /* progress */)> ProgressFn;
|
||||
typedef std::function<void(int /* exit status */, size_t /* args_id */)> CompleteFn;
|
||||
typedef std::function<void()> CompleteFn;
|
||||
|
||||
// Main c-tor, sys_config is the location of avrdude's main configuration file
|
||||
AvrDude(std::string sys_config);
|
||||
|
@ -31,7 +31,8 @@ public:
|
|||
AvrDude& push_args(std::vector<std::string> args);
|
||||
|
||||
// Set a callback to be called just after run() before avrdude is ran
|
||||
// This can be used to perform any needed setup tasks from the background thread.
|
||||
// This can be used to perform any needed setup tasks from the background thread,
|
||||
// and, optionally, to cancel by writing true to the `cancel` argument.
|
||||
// This has no effect when using run_sync().
|
||||
AvrDude& on_run(RunFn fn);
|
||||
|
||||
|
@ -53,6 +54,10 @@ public:
|
|||
|
||||
void cancel();
|
||||
void join();
|
||||
|
||||
bool cancelled(); // Whether avrdude run was cancelled
|
||||
int exit_code(); // The exit code of the last invocation
|
||||
size_t last_args_set(); // Index of the last argument set that was processsed
|
||||
private:
|
||||
struct priv;
|
||||
std::unique_ptr<priv> p;
|
||||
|
|
|
@ -98,11 +98,11 @@ static int fileio_num(struct fioparms * fio,
|
|||
char * filename, FILE * f, AVRMEM * mem, int size,
|
||||
FILEFMT fmt);
|
||||
|
||||
static int fmt_autodetect(char * fname, size_t offset);
|
||||
static int fmt_autodetect(char * fname, unsigned section);
|
||||
|
||||
|
||||
|
||||
static FILE *fopen_and_seek(const char *filename, const char *mode, size_t offset)
|
||||
static FILE *fopen_and_seek(const char *filename, const char *mode, unsigned section)
|
||||
{
|
||||
FILE *file;
|
||||
// On Windows we need to convert the filename to UTF-16
|
||||
|
@ -118,16 +118,38 @@ static FILE *fopen_and_seek(const char *filename, const char *mode, size_t offse
|
|||
file = fopen(filename, mode);
|
||||
#endif
|
||||
|
||||
if (file != NULL) {
|
||||
// Some systems allow seeking past the end of file, so we need check for that first and disallow
|
||||
if (fseek(file, 0, SEEK_END) != 0
|
||||
|| offset >= ftell(file)
|
||||
|| fseek(file, offset, SEEK_SET) != 0
|
||||
) {
|
||||
fclose(file);
|
||||
file = NULL;
|
||||
errno = EINVAL;
|
||||
if (file == NULL) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// Seek to the specified 'section'
|
||||
static const char *hex_terminator = ":00000001FF\r";
|
||||
unsigned terms_seen = 0;
|
||||
char buffer[MAX_LINE_LEN + 1];
|
||||
|
||||
while (terms_seen < section && fgets(buffer, MAX_LINE_LEN, file) != NULL) {
|
||||
size_t len = strlen(buffer);
|
||||
|
||||
if (buffer[len - 1] == '\n') {
|
||||
len--;
|
||||
buffer[len] = 0;
|
||||
}
|
||||
if (buffer[len - 1] != '\r') {
|
||||
buffer[len] = '\r';
|
||||
len++;
|
||||
buffer[len] = 0;
|
||||
}
|
||||
|
||||
if (strcmp(buffer, hex_terminator) == 0) {
|
||||
// Found a section terminator
|
||||
terms_seen++;
|
||||
}
|
||||
}
|
||||
|
||||
if (feof(file)) {
|
||||
// Section not found
|
||||
fclose(file);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return file;
|
||||
|
@ -1392,7 +1414,7 @@ int fileio_setparms(int op, struct fioparms * fp,
|
|||
|
||||
|
||||
|
||||
static int fmt_autodetect(char * fname, size_t offset)
|
||||
static int fmt_autodetect(char * fname, unsigned section)
|
||||
{
|
||||
FILE * f;
|
||||
unsigned char buf[MAX_LINE_LEN];
|
||||
|
@ -1402,9 +1424,9 @@ static int fmt_autodetect(char * fname, size_t offset)
|
|||
int first = 1;
|
||||
|
||||
#if defined(WIN32NATIVE)
|
||||
f = fopen_and_seek(fname, "r", offset);
|
||||
f = fopen_and_seek(fname, "r", section);
|
||||
#else
|
||||
f = fopen_and_seek(fname, "rb", offset);
|
||||
f = fopen_and_seek(fname, "rb", section);
|
||||
#endif
|
||||
|
||||
if (f == NULL) {
|
||||
|
@ -1480,7 +1502,7 @@ static int fmt_autodetect(char * fname, size_t offset)
|
|||
|
||||
|
||||
int fileio(int op, char * filename, FILEFMT format,
|
||||
struct avrpart * p, char * memtype, int size, size_t offset)
|
||||
struct avrpart * p, char * memtype, int size, unsigned section)
|
||||
{
|
||||
int rc;
|
||||
FILE * f;
|
||||
|
@ -1539,7 +1561,7 @@ int fileio(int op, char * filename, FILEFMT format,
|
|||
return -1;
|
||||
}
|
||||
|
||||
format_detect = fmt_autodetect(fname, offset);
|
||||
format_detect = fmt_autodetect(fname, section);
|
||||
if (format_detect < 0) {
|
||||
avrdude_message(MSG_INFO, "%s: can't determine file format for %s, specify explicitly\n",
|
||||
progname, fname);
|
||||
|
@ -1570,7 +1592,7 @@ int fileio(int op, char * filename, FILEFMT format,
|
|||
|
||||
if (format != FMT_IMM) {
|
||||
if (!using_stdio) {
|
||||
f = fopen_and_seek(fname, fio.mode, offset);
|
||||
f = fopen_and_seek(fname, fio.mode, section);
|
||||
if (f == NULL) {
|
||||
avrdude_message(MSG_INFO, "%s: can't open %s file %s: %s\n",
|
||||
progname, fio.iodesc, fname, strerror(errno));
|
||||
|
|
|
@ -821,7 +821,7 @@ extern "C" {
|
|||
char * fmtstr(FILEFMT format);
|
||||
|
||||
int fileio(int op, char * filename, FILEFMT format,
|
||||
struct avrpart * p, char * memtype, int size, size_t offset);
|
||||
struct avrpart * p, char * memtype, int size, unsigned section);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
|
@ -870,7 +870,7 @@ enum updateflags {
|
|||
typedef struct update_t {
|
||||
char * memtype;
|
||||
int op;
|
||||
size_t offset;
|
||||
unsigned section;
|
||||
char * filename;
|
||||
int format;
|
||||
} UPDATE;
|
||||
|
@ -882,7 +882,7 @@ extern "C" {
|
|||
extern UPDATE * parse_op(char * s);
|
||||
extern UPDATE * dup_update(UPDATE * upd);
|
||||
extern UPDATE * new_update(int op, char * memtype, int filefmt,
|
||||
char * filename, size_t offset);
|
||||
char * filename, unsigned section);
|
||||
extern void free_update(UPDATE * upd);
|
||||
extern int do_op(PROGRAMMER * pgm, struct avrpart * p, UPDATE * upd,
|
||||
enum updateflags flags);
|
||||
|
|
9
xs/src/avrdude/main-standalone.c
Normal file
9
xs/src/avrdude/main-standalone.c
Normal file
|
@ -0,0 +1,9 @@
|
|||
#include "avrdude.h"
|
||||
|
||||
|
||||
static const char* SYS_CONFIG = "/etc/avrdude-slic3r.conf";
|
||||
|
||||
int main(int argc, char *argv[])
|
||||
{
|
||||
return avrdude_main(argc, argv, SYS_CONFIG);
|
||||
}
|
|
@ -194,7 +194,7 @@ static void usage(void)
|
|||
" -F Override invalid signature check.\n"
|
||||
" -e Perform a chip erase.\n"
|
||||
" -O Perform RC oscillator calibration (see AVR053). \n"
|
||||
" -U <memtype>:r|w|v:<offset>:<filename>[:format]\n"
|
||||
" -U <memtype>:r|w|v:<section>:<filename>[:format]\n"
|
||||
" Memory operation specification.\n"
|
||||
" Multiple -U options are allowed, each request\n"
|
||||
" is performed in the order specified.\n"
|
||||
|
|
|
@ -311,8 +311,10 @@ static int ser_open(char * port, union pinfo pinfo, union filedescriptor *fdp)
|
|||
static void ser_close(union filedescriptor *fd)
|
||||
{
|
||||
if (serial_over_ethernet) {
|
||||
#ifdef HAVE_LIBWS2_32
|
||||
closesocket(fd->ifd);
|
||||
WSACleanup();
|
||||
#endif
|
||||
} else {
|
||||
HANDLE hComPort=(HANDLE)fd->pfd;
|
||||
if (hComPort != INVALID_HANDLE_VALUE)
|
||||
|
|
|
@ -101,22 +101,22 @@ UPDATE * parse_op(char * s)
|
|||
|
||||
p++;
|
||||
|
||||
// Extension: Parse file contents offset
|
||||
size_t offset = 0;
|
||||
// Extension: Parse file section number
|
||||
unsigned section = 0;
|
||||
|
||||
for (; *p != ':'; p++) {
|
||||
if (*p >= '0' && *p <= '9') {
|
||||
offset *= 10;
|
||||
offset += *p - 0x30;
|
||||
section *= 10;
|
||||
section += *p - 0x30;
|
||||
} else {
|
||||
avrdude_message(MSG_INFO, "%s: invalid update specification: offset is not a number\n", progname);
|
||||
avrdude_message(MSG_INFO, "%s: invalid update specification: <section> is not a number\n", progname);
|
||||
free(upd->memtype);
|
||||
free(upd);
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
|
||||
upd->offset = offset;
|
||||
upd->section = section;
|
||||
p++;
|
||||
|
||||
/*
|
||||
|
@ -194,7 +194,7 @@ UPDATE * dup_update(UPDATE * upd)
|
|||
return u;
|
||||
}
|
||||
|
||||
UPDATE * new_update(int op, char * memtype, int filefmt, char * filename, size_t offset)
|
||||
UPDATE * new_update(int op, char * memtype, int filefmt, char * filename, unsigned section)
|
||||
{
|
||||
UPDATE * u;
|
||||
|
||||
|
@ -208,7 +208,7 @@ UPDATE * new_update(int op, char * memtype, int filefmt, char * filename, size_t
|
|||
u->filename = strdup(filename);
|
||||
u->op = op;
|
||||
u->format = filefmt;
|
||||
u->offset = offset;
|
||||
u->section = section;
|
||||
|
||||
return u;
|
||||
}
|
||||
|
@ -286,7 +286,7 @@ int do_op(PROGRAMMER * pgm, struct avrpart * p, UPDATE * upd, enum updateflags f
|
|||
progname,
|
||||
strcmp(upd->filename, "-")==0 ? "<stdin>" : upd->filename);
|
||||
}
|
||||
rc = fileio(FIO_READ, upd->filename, upd->format, p, upd->memtype, -1, upd->offset);
|
||||
rc = fileio(FIO_READ, upd->filename, upd->format, p, upd->memtype, -1, upd->section);
|
||||
if (rc < 0) {
|
||||
avrdude_message(MSG_INFO, "%s: read from file '%s' failed\n",
|
||||
progname, upd->filename);
|
||||
|
@ -351,7 +351,7 @@ int do_op(PROGRAMMER * pgm, struct avrpart * p, UPDATE * upd, enum updateflags f
|
|||
progname, mem->desc, upd->filename);
|
||||
}
|
||||
|
||||
rc = fileio(FIO_READ, upd->filename, upd->format, p, upd->memtype, -1, upd->offset);
|
||||
rc = fileio(FIO_READ, upd->filename, upd->format, p, upd->memtype, -1, upd->section);
|
||||
if (rc < 0) {
|
||||
avrdude_message(MSG_INFO, "%s: read from file '%s' failed\n",
|
||||
progname, upd->filename);
|
||||
|
|
|
@ -2,8 +2,6 @@ cmake_minimum_required(VERSION 2.8)
|
|||
|
||||
project(Libnest2D)
|
||||
|
||||
enable_testing()
|
||||
|
||||
if(CMAKE_COMPILER_IS_GNUCC OR CMAKE_COMPILER_IS_GNUCXX)
|
||||
# Update if necessary
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -Wno-long-long ")
|
||||
|
@ -32,6 +30,7 @@ set(LIBNEST2D_SRCFILES
|
|||
${CMAKE_CURRENT_SOURCE_DIR}/libnest2d/geometry_traits.hpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/libnest2d/common.hpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/libnest2d/optimizer.hpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/libnest2d/metaloop.hpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/libnest2d/placers/placer_boilerplate.hpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/libnest2d/placers/bottomleftplacer.hpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/libnest2d/placers/nfpplacer.hpp
|
||||
|
@ -60,8 +59,7 @@ if(LIBNEST2D_GEOMETRIES_BACKEND STREQUAL "clipper")
|
|||
include_directories(BEFORE ${CLIPPER_INCLUDE_DIRS})
|
||||
include_directories(${Boost_INCLUDE_DIRS})
|
||||
|
||||
list(APPEND LIBNEST2D_SRCFILES ${CMAKE_CURRENT_SOURCE_DIR}/libnest2d/clipper_backend/clipper_backend.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/libnest2d/clipper_backend/clipper_backend.hpp
|
||||
list(APPEND LIBNEST2D_SRCFILES ${CMAKE_CURRENT_SOURCE_DIR}/libnest2d/clipper_backend/clipper_backend.hpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/libnest2d/boost_alg.hpp)
|
||||
list(APPEND LIBNEST2D_LIBRARIES ${CLIPPER_LIBRARIES})
|
||||
list(APPEND LIBNEST2D_HEADERS ${CLIPPER_INCLUDE_DIRS}
|
||||
|
@ -81,22 +79,12 @@ if(LIBNEST2D_OPTIMIZER_BACKEND STREQUAL "nlopt")
|
|||
${CMAKE_CURRENT_SOURCE_DIR}/libnest2d/optimizers/subplex.hpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/libnest2d/optimizers/genetic.hpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/libnest2d/optimizers/nlopt_boilerplate.hpp)
|
||||
list(APPEND LIBNEST2D_LIBRARIES ${NLopt_LIBS}
|
||||
# Threads::Threads
|
||||
)
|
||||
list(APPEND LIBNEST2D_LIBRARIES ${NLopt_LIBS})
|
||||
list(APPEND LIBNEST2D_HEADERS ${NLopt_INCLUDE_DIR})
|
||||
endif()
|
||||
|
||||
# Currently we are outsourcing the non-convex NFP implementation from
|
||||
# libnfporb and it needs libgmp to work
|
||||
#find_package(GMP)
|
||||
#if(GMP_FOUND)
|
||||
# list(APPEND LIBNEST2D_LIBRARIES ${GMP_LIBRARIES})
|
||||
# list(APPEND LIBNEST2D_HEADERS ${GMP_INCLUDE_DIR})
|
||||
# add_definitions(-DLIBNFP_USE_RATIONAL)
|
||||
#endif()
|
||||
|
||||
if(LIBNEST2D_UNITTESTS)
|
||||
enable_testing()
|
||||
add_subdirectory(tests)
|
||||
endif()
|
||||
|
||||
|
|
|
@ -27,5 +27,6 @@ set(NLOPT_LINK_PYTHON OFF CACHE BOOL "" FORCE)
|
|||
add_subdirectory(${nlopt_SOURCE_DIR} ${nlopt_BINARY_DIR})
|
||||
|
||||
set(NLopt_LIBS nlopt)
|
||||
set(NLopt_INCLUDE_DIR ${nlopt_BINARY_DIR})
|
||||
set(NLopt_INCLUDE_DIR ${nlopt_BINARY_DIR}
|
||||
${nlopt_BINARY_DIR}/src/api)
|
||||
set(SHARED_LIBS_STATE ${SHARED_STATE})
|
|
@ -1,35 +0,0 @@
|
|||
# Try to find the GMP libraries:
|
||||
# GMP_FOUND - System has GMP lib
|
||||
# GMP_INCLUDE_DIR - The GMP include directory
|
||||
# GMP_LIBRARIES - Libraries needed to use GMP
|
||||
|
||||
if (GMP_INCLUDE_DIR AND GMP_LIBRARIES)
|
||||
# Force search at every time, in case configuration changes
|
||||
unset(GMP_INCLUDE_DIR CACHE)
|
||||
unset(GMP_LIBRARIES CACHE)
|
||||
endif (GMP_INCLUDE_DIR AND GMP_LIBRARIES)
|
||||
|
||||
find_path(GMP_INCLUDE_DIR NAMES gmp.h)
|
||||
|
||||
if(WIN32)
|
||||
find_library(GMP_LIBRARIES NAMES libgmp.a gmp gmp.lib mpir mpir.lib)
|
||||
else(WIN32)
|
||||
if(STBIN)
|
||||
message(STATUS "STBIN: ${STBIN}")
|
||||
find_library(GMP_LIBRARIES NAMES libgmp.a gmp)
|
||||
else(STBIN)
|
||||
find_library(GMP_LIBRARIES NAMES libgmp.so gmp)
|
||||
endif(STBIN)
|
||||
endif(WIN32)
|
||||
|
||||
if(GMP_INCLUDE_DIR AND GMP_LIBRARIES)
|
||||
set(GMP_FOUND TRUE)
|
||||
endif(GMP_INCLUDE_DIR AND GMP_LIBRARIES)
|
||||
|
||||
if(GMP_FOUND)
|
||||
message(STATUS "Configured GMP: ${GMP_LIBRARIES}")
|
||||
else(GMP_FOUND)
|
||||
message(STATUS "Could NOT find GMP")
|
||||
endif(GMP_FOUND)
|
||||
|
||||
mark_as_advanced(GMP_INCLUDE_DIR GMP_LIBRARIES)
|
|
@ -535,19 +535,34 @@ void arrangeRectangles() {
|
|||
proba[0].rotate(Pi/3);
|
||||
proba[1].rotate(Pi-Pi/3);
|
||||
|
||||
// std::vector<Item> input(25, Rectangle(70*SCALE, 10*SCALE));
|
||||
std::vector<Item> input;
|
||||
input.insert(input.end(), prusaParts().begin(), prusaParts().end());
|
||||
// input.insert(input.end(), prusaExParts().begin(), prusaExParts().end());
|
||||
input.insert(input.end(), stegoParts().begin(), stegoParts().end());
|
||||
// input.insert(input.end(), stegoParts().begin(), stegoParts().end());
|
||||
// input.insert(input.end(), rects.begin(), rects.end());
|
||||
input.insert(input.end(), proba.begin(), proba.end());
|
||||
// input.insert(input.end(), proba.begin(), proba.end());
|
||||
// input.insert(input.end(), crasher.begin(), crasher.end());
|
||||
|
||||
Box bin(250*SCALE, 210*SCALE);
|
||||
// PolygonImpl bin = {
|
||||
// {
|
||||
// {25*SCALE, 0},
|
||||
// {0, 25*SCALE},
|
||||
// {0, 225*SCALE},
|
||||
// {25*SCALE, 250*SCALE},
|
||||
// {225*SCALE, 250*SCALE},
|
||||
// {250*SCALE, 225*SCALE},
|
||||
// {250*SCALE, 25*SCALE},
|
||||
// {225*SCALE, 0},
|
||||
// {25*SCALE, 0}
|
||||
// },
|
||||
// {}
|
||||
// };
|
||||
|
||||
Coord min_obj_distance = 6*SCALE;
|
||||
auto min_obj_distance = static_cast<Coord>(0*SCALE);
|
||||
|
||||
using Placer = NfpPlacer;
|
||||
using Placer = strategies::_NofitPolyPlacer<PolygonImpl, Box>;
|
||||
using Packer = Arranger<Placer, FirstFitSelection>;
|
||||
|
||||
Packer arrange(bin, min_obj_distance);
|
||||
|
@ -556,28 +571,107 @@ void arrangeRectangles() {
|
|||
pconf.alignment = Placer::Config::Alignment::CENTER;
|
||||
pconf.starting_point = Placer::Config::Alignment::CENTER;
|
||||
pconf.rotations = {0.0/*, Pi/2.0, Pi, 3*Pi/2*/};
|
||||
pconf.object_function = [&bin](Placer::Pile pile, double area,
|
||||
double norm, double penality) {
|
||||
pconf.accuracy = 0.5f;
|
||||
|
||||
auto bb = ShapeLike::boundingBox(pile);
|
||||
// auto bincenter = ShapeLike::boundingBox(bin).center();
|
||||
// pconf.object_function = [&bin, bincenter](
|
||||
// Placer::Pile pile, const Item& item,
|
||||
// double /*area*/, double norm, double penality) {
|
||||
|
||||
auto& sh = pile.back();
|
||||
auto rv = Nfp::referenceVertex(sh);
|
||||
auto c = bin.center();
|
||||
auto d = PointLike::distance(rv, c);
|
||||
double score = double(d)/norm;
|
||||
// using pl = PointLike;
|
||||
|
||||
// If it does not fit into the print bed we will beat it
|
||||
// with a large penality
|
||||
if(!NfpPlacer::wouldFit(bb, bin)) score = 2*penality - score;
|
||||
// static const double BIG_ITEM_TRESHOLD = 0.2;
|
||||
// static const double GRAVITY_RATIO = 0.5;
|
||||
// static const double DENSITY_RATIO = 1.0 - GRAVITY_RATIO;
|
||||
|
||||
return score;
|
||||
};
|
||||
// // We will treat big items (compared to the print bed) differently
|
||||
// NfpPlacer::Pile bigs;
|
||||
// bigs.reserve(pile.size());
|
||||
// for(auto& p : pile) {
|
||||
// auto pbb = ShapeLike::boundingBox(p);
|
||||
// auto na = std::sqrt(pbb.width()*pbb.height())/norm;
|
||||
// if(na > BIG_ITEM_TRESHOLD) bigs.emplace_back(p);
|
||||
// }
|
||||
|
||||
// // Candidate item bounding box
|
||||
// auto ibb = item.boundingBox();
|
||||
|
||||
// // Calculate the full bounding box of the pile with the candidate item
|
||||
// pile.emplace_back(item.transformedShape());
|
||||
// auto fullbb = ShapeLike::boundingBox(pile);
|
||||
// pile.pop_back();
|
||||
|
||||
// // The bounding box of the big items (they will accumulate in the center
|
||||
// // of the pile
|
||||
// auto bigbb = bigs.empty()? fullbb : ShapeLike::boundingBox(bigs);
|
||||
|
||||
// // The size indicator of the candidate item. This is not the area,
|
||||
// // but almost...
|
||||
// auto itemnormarea = std::sqrt(ibb.width()*ibb.height())/norm;
|
||||
|
||||
// // Will hold the resulting score
|
||||
// double score = 0;
|
||||
|
||||
// if(itemnormarea > BIG_ITEM_TRESHOLD) {
|
||||
// // This branch is for the bigger items..
|
||||
// // Here we will use the closest point of the item bounding box to
|
||||
// // the already arranged pile. So not the bb center nor the a choosen
|
||||
// // corner but whichever is the closest to the center. This will
|
||||
// // prevent unwanted strange arrangements.
|
||||
|
||||
// auto minc = ibb.minCorner(); // bottom left corner
|
||||
// auto maxc = ibb.maxCorner(); // top right corner
|
||||
|
||||
// // top left and bottom right corners
|
||||
// auto top_left = PointImpl{getX(minc), getY(maxc)};
|
||||
// auto bottom_right = PointImpl{getX(maxc), getY(minc)};
|
||||
|
||||
// auto cc = fullbb.center(); // The gravity center
|
||||
|
||||
// // Now the distnce of the gravity center will be calculated to the
|
||||
// // five anchor points and the smallest will be chosen.
|
||||
// std::array<double, 5> dists;
|
||||
// dists[0] = pl::distance(minc, cc);
|
||||
// dists[1] = pl::distance(maxc, cc);
|
||||
// dists[2] = pl::distance(ibb.center(), cc);
|
||||
// dists[3] = pl::distance(top_left, cc);
|
||||
// dists[4] = pl::distance(bottom_right, cc);
|
||||
|
||||
// auto dist = *(std::min_element(dists.begin(), dists.end())) / norm;
|
||||
|
||||
// // Density is the pack density: how big is the arranged pile
|
||||
// auto density = std::sqrt(fullbb.width()*fullbb.height()) / norm;
|
||||
|
||||
// // The score is a weighted sum of the distance from pile center
|
||||
// // and the pile size
|
||||
// score = GRAVITY_RATIO * dist + DENSITY_RATIO * density;
|
||||
|
||||
// } else if(itemnormarea < BIG_ITEM_TRESHOLD && bigs.empty()) {
|
||||
// // If there are no big items, only small, we should consider the
|
||||
// // density here as well to not get silly results
|
||||
// auto bindist = pl::distance(ibb.center(), bincenter) / norm;
|
||||
// auto density = std::sqrt(fullbb.width()*fullbb.height()) / norm;
|
||||
// score = GRAVITY_RATIO * bindist + DENSITY_RATIO * density;
|
||||
// } else {
|
||||
// // Here there are the small items that should be placed around the
|
||||
// // already processed bigger items.
|
||||
// // No need to play around with the anchor points, the center will be
|
||||
// // just fine for small items
|
||||
// score = pl::distance(ibb.center(), bigbb.center()) / norm;
|
||||
// }
|
||||
|
||||
// // If it does not fit into the print bed we will beat it
|
||||
// // with a large penality. If we would not do this, there would be only
|
||||
// // one big pile that doesn't care whether it fits onto the print bed.
|
||||
// if(!NfpPlacer::wouldFit(fullbb, bin)) score = 2*penality - score;
|
||||
|
||||
// return score;
|
||||
// };
|
||||
|
||||
Packer::SelectionConfig sconf;
|
||||
// sconf.allow_parallel = false;
|
||||
// sconf.force_parallel = false;
|
||||
// sconf.try_triplets = false;
|
||||
// sconf.try_triplets = true;
|
||||
// sconf.try_reverse_order = true;
|
||||
// sconf.waste_increment = 0.005;
|
||||
|
||||
|
@ -613,7 +707,7 @@ void arrangeRectangles() {
|
|||
std::vector<double> eff;
|
||||
eff.reserve(result.size());
|
||||
|
||||
auto bin_area = double(bin.height()*bin.width());
|
||||
auto bin_area = ShapeLike::area<PolygonImpl>(bin);
|
||||
for(auto& r : result) {
|
||||
double a = 0;
|
||||
std::for_each(r.begin(), r.end(), [&a] (Item& e ){ a += e.area(); });
|
||||
|
@ -630,7 +724,7 @@ void arrangeRectangles() {
|
|||
<< " %" << std::endl;
|
||||
|
||||
std::cout << "Bin usage: (";
|
||||
unsigned total = 0;
|
||||
size_t total = 0;
|
||||
for(auto& r : result) { std::cout << r.size() << " "; total += r.size(); }
|
||||
std::cout << ") Total: " << total << std::endl;
|
||||
|
||||
|
@ -643,10 +737,12 @@ void arrangeRectangles() {
|
|||
<< input.size() - total << " elements!"
|
||||
<< std::endl;
|
||||
|
||||
svg::SVGWriter::Config conf;
|
||||
using SVGWriter = svg::SVGWriter<PolygonImpl>;
|
||||
|
||||
SVGWriter::Config conf;
|
||||
conf.mm_in_coord_units = SCALE;
|
||||
svg::SVGWriter svgw(conf);
|
||||
svgw.setSize(bin);
|
||||
SVGWriter svgw(conf);
|
||||
svgw.setSize(Box(250*SCALE, 210*SCALE));
|
||||
svgw.writePackGroup(result);
|
||||
// std::for_each(input.begin(), input.end(), [&svgw](Item& item){ svgw.writeItem(item);});
|
||||
svgw.save("out");
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
#include <libnest2d/clipper_backend/clipper_backend.hpp>
|
||||
|
||||
// We include the stock optimizers for local and global optimization
|
||||
#include <libnest2d/optimizers/simplex.hpp> // Local subplex for NfpPlacer
|
||||
#include <libnest2d/optimizers/subplex.hpp> // Local subplex for NfpPlacer
|
||||
#include <libnest2d/optimizers/genetic.hpp> // Genetic for min. bounding box
|
||||
|
||||
#include <libnest2d/libnest2d.hpp>
|
||||
|
|
|
@ -8,8 +8,16 @@
|
|||
#ifdef __clang__
|
||||
#undef _MSC_EXTENSIONS
|
||||
#endif
|
||||
#include <boost/geometry.hpp>
|
||||
|
||||
#ifdef _MSC_VER
|
||||
#pragma warning(push)
|
||||
#pragma warning(disable: 4244)
|
||||
#pragma warning(disable: 4267)
|
||||
#endif
|
||||
#include <boost/geometry.hpp>
|
||||
#ifdef _MSC_VER
|
||||
#pragma warning(pop)
|
||||
#endif
|
||||
// this should be removed to not confuse the compiler
|
||||
// #include <libnest2d.h>
|
||||
|
||||
|
@ -350,7 +358,7 @@ inline double ShapeLike::area(const PolygonImpl& shape)
|
|||
#endif
|
||||
|
||||
template<>
|
||||
inline bool ShapeLike::isInside(const PointImpl& point,
|
||||
inline bool ShapeLike::isInside<PolygonImpl>(const PointImpl& point,
|
||||
const PolygonImpl& shape)
|
||||
{
|
||||
return boost::geometry::within(point, shape);
|
||||
|
@ -461,15 +469,6 @@ inline bp2d::Shapes Nfp::merge(const bp2d::Shapes& shapes,
|
|||
}
|
||||
#endif
|
||||
|
||||
//#ifndef DISABLE_BOOST_MINKOWSKI_ADD
|
||||
//template<>
|
||||
//inline PolygonImpl& Nfp::minkowskiAdd(PolygonImpl& sh,
|
||||
// const PolygonImpl& /*other*/)
|
||||
//{
|
||||
// return sh;
|
||||
//}
|
||||
//#endif
|
||||
|
||||
#ifndef DISABLE_BOOST_SERIALIZE
|
||||
template<> inline std::string ShapeLike::serialize<libnest2d::Formats::SVG>(
|
||||
const PolygonImpl& sh, double scale)
|
||||
|
|
|
@ -1,58 +0,0 @@
|
|||
//#include "clipper_backend.hpp"
|
||||
//#include <atomic>
|
||||
|
||||
//namespace libnest2d {
|
||||
|
||||
//namespace {
|
||||
|
||||
//class SpinLock {
|
||||
// std::atomic_flag& lck_;
|
||||
//public:
|
||||
|
||||
// inline SpinLock(std::atomic_flag& flg): lck_(flg) {}
|
||||
|
||||
// inline void lock() {
|
||||
// while(lck_.test_and_set(std::memory_order_acquire)) {}
|
||||
// }
|
||||
|
||||
// inline void unlock() { lck_.clear(std::memory_order_release); }
|
||||
//};
|
||||
|
||||
//class HoleCache {
|
||||
// friend struct libnest2d::ShapeLike;
|
||||
|
||||
// std::unordered_map< const PolygonImpl*, ClipperLib::Paths> map;
|
||||
|
||||
// ClipperLib::Paths& _getHoles(const PolygonImpl* p) {
|
||||
// static std::atomic_flag flg = ATOMIC_FLAG_INIT;
|
||||
// SpinLock lock(flg);
|
||||
|
||||
// lock.lock();
|
||||
// ClipperLib::Paths& paths = map[p];
|
||||
// lock.unlock();
|
||||
|
||||
// if(paths.size() != p->Childs.size()) {
|
||||
// paths.reserve(p->Childs.size());
|
||||
|
||||
// for(auto np : p->Childs) {
|
||||
// paths.emplace_back(np->Contour);
|
||||
// }
|
||||
// }
|
||||
|
||||
// return paths;
|
||||
// }
|
||||
|
||||
// ClipperLib::Paths& getHoles(PolygonImpl& p) {
|
||||
// return _getHoles(&p);
|
||||
// }
|
||||
|
||||
// const ClipperLib::Paths& getHoles(const PolygonImpl& p) {
|
||||
// return _getHoles(&p);
|
||||
// }
|
||||
//};
|
||||
//}
|
||||
|
||||
//HoleCache holeCache;
|
||||
|
||||
//}
|
||||
|
|
@ -21,7 +21,7 @@ struct PolygonImpl {
|
|||
PathImpl Contour;
|
||||
HoleStore Holes;
|
||||
|
||||
inline PolygonImpl() {}
|
||||
inline PolygonImpl() = default;
|
||||
|
||||
inline explicit PolygonImpl(const PathImpl& cont): Contour(cont) {}
|
||||
inline explicit PolygonImpl(const HoleStore& holes):
|
||||
|
@ -66,6 +66,19 @@ inline PointImpl operator-(const PointImpl& p1, const PointImpl& p2) {
|
|||
ret -= p2;
|
||||
return ret;
|
||||
}
|
||||
|
||||
inline PointImpl& operator *=(PointImpl& p, const PointImpl& pa ) {
|
||||
p.X *= pa.X;
|
||||
p.Y *= pa.Y;
|
||||
return p;
|
||||
}
|
||||
|
||||
inline PointImpl operator*(const PointImpl& p1, const PointImpl& p2) {
|
||||
PointImpl ret = p1;
|
||||
ret *= p2;
|
||||
return ret;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
namespace libnest2d {
|
||||
|
@ -135,7 +148,7 @@ inline void ShapeLike::reserve(PolygonImpl& sh, size_t vertex_capacity)
|
|||
|
||||
namespace _smartarea {
|
||||
template<Orientation o>
|
||||
inline double area(const PolygonImpl& sh) {
|
||||
inline double area(const PolygonImpl& /*sh*/) {
|
||||
return std::nan("");
|
||||
}
|
||||
|
||||
|
@ -220,22 +233,6 @@ inline void ShapeLike::offset(PolygonImpl& sh, TCoord<PointImpl> distance) {
|
|||
}
|
||||
}
|
||||
|
||||
//template<> // TODO make it support holes if this method will ever be needed.
|
||||
//inline PolygonImpl Nfp::minkowskiDiff(const PolygonImpl& sh,
|
||||
// const PolygonImpl& other)
|
||||
//{
|
||||
// #define DISABLE_BOOST_MINKOWSKI_ADD
|
||||
|
||||
// ClipperLib::Paths solution;
|
||||
|
||||
// ClipperLib::MinkowskiDiff(sh.Contour, other.Contour, solution);
|
||||
|
||||
// PolygonImpl ret;
|
||||
// ret.Contour = solution.front();
|
||||
|
||||
// return sh;
|
||||
//}
|
||||
|
||||
// Tell libnest2d how to make string out of a ClipperPolygon object
|
||||
template<> inline std::string ShapeLike::toString(const PolygonImpl& sh) {
|
||||
std::stringstream ss;
|
||||
|
@ -406,35 +403,12 @@ inline void ShapeLike::rotate(PolygonImpl& sh, const Radians& rads)
|
|||
}
|
||||
|
||||
#define DISABLE_BOOST_NFP_MERGE
|
||||
template<> inline Nfp::Shapes<PolygonImpl>
|
||||
Nfp::merge(const Nfp::Shapes<PolygonImpl>& shapes, const PolygonImpl& sh)
|
||||
{
|
||||
inline Nfp::Shapes<PolygonImpl> _merge(ClipperLib::Clipper& clipper) {
|
||||
Nfp::Shapes<PolygonImpl> retv;
|
||||
|
||||
ClipperLib::Clipper clipper(ClipperLib::ioReverseSolution);
|
||||
|
||||
bool closed = true;
|
||||
bool valid = false;
|
||||
|
||||
valid = clipper.AddPath(sh.Contour, ClipperLib::ptSubject, closed);
|
||||
|
||||
for(auto& hole : sh.Holes) {
|
||||
valid &= clipper.AddPath(hole, ClipperLib::ptSubject, closed);
|
||||
}
|
||||
|
||||
for(auto& path : shapes) {
|
||||
valid &= clipper.AddPath(path.Contour, ClipperLib::ptSubject, closed);
|
||||
|
||||
for(auto& hole : path.Holes) {
|
||||
valid &= clipper.AddPath(hole, ClipperLib::ptSubject, closed);
|
||||
}
|
||||
}
|
||||
|
||||
if(!valid) throw GeometryException(GeomErr::MERGE);
|
||||
|
||||
ClipperLib::PolyTree result;
|
||||
clipper.Execute(ClipperLib::ctUnion, result, ClipperLib::pftNonZero);
|
||||
retv.reserve(result.Total());
|
||||
clipper.Execute(ClipperLib::ctUnion, result, ClipperLib::pftNegative);
|
||||
retv.reserve(static_cast<size_t>(result.Total()));
|
||||
|
||||
std::function<void(ClipperLib::PolyNode*, PolygonImpl&)> processHole;
|
||||
|
||||
|
@ -445,7 +419,8 @@ Nfp::merge(const Nfp::Shapes<PolygonImpl>& shapes, const PolygonImpl& sh)
|
|||
retv.push_back(poly);
|
||||
};
|
||||
|
||||
processHole = [&processPoly](ClipperLib::PolyNode *pptr, PolygonImpl& poly) {
|
||||
processHole = [&processPoly](ClipperLib::PolyNode *pptr, PolygonImpl& poly)
|
||||
{
|
||||
poly.Holes.push_back(pptr->Contour);
|
||||
poly.Holes.back().push_back(poly.Holes.back().front());
|
||||
for(auto c : pptr->Childs) processPoly(c);
|
||||
|
@ -463,6 +438,27 @@ Nfp::merge(const Nfp::Shapes<PolygonImpl>& shapes, const PolygonImpl& sh)
|
|||
return retv;
|
||||
}
|
||||
|
||||
template<> inline Nfp::Shapes<PolygonImpl>
|
||||
Nfp::merge(const Nfp::Shapes<PolygonImpl>& shapes)
|
||||
{
|
||||
ClipperLib::Clipper clipper(ClipperLib::ioReverseSolution);
|
||||
|
||||
bool closed = true;
|
||||
bool valid = true;
|
||||
|
||||
for(auto& path : shapes) {
|
||||
valid &= clipper.AddPath(path.Contour, ClipperLib::ptSubject, closed);
|
||||
|
||||
for(auto& hole : path.Holes) {
|
||||
valid &= clipper.AddPath(hole, ClipperLib::ptSubject, closed);
|
||||
}
|
||||
}
|
||||
|
||||
if(!valid) throw GeometryException(GeomErr::MERGE);
|
||||
|
||||
return _merge(clipper);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
//#define DISABLE_BOOST_SERIALIZE
|
||||
|
|
|
@ -13,6 +13,7 @@
|
|||
#if defined(_MSC_VER) && _MSC_VER <= 1800 || __cplusplus < 201103L
|
||||
#define BP2D_NOEXCEPT
|
||||
#define BP2D_CONSTEXPR
|
||||
#define BP2D_COMPILER_MSVC12
|
||||
#elif __cplusplus >= 201103L
|
||||
#define BP2D_NOEXCEPT noexcept
|
||||
#define BP2D_CONSTEXPR constexpr
|
||||
|
@ -84,44 +85,6 @@ struct invoke_result {
|
|||
template<class F, class...Args>
|
||||
using invoke_result_t = typename invoke_result<F, Args...>::type;
|
||||
|
||||
/* ************************************************************************** */
|
||||
/* C++14 std::index_sequence implementation: */
|
||||
/* ************************************************************************** */
|
||||
|
||||
/**
|
||||
* \brief C++11 conformant implementation of the index_sequence type from C++14
|
||||
*/
|
||||
template<size_t...Ints> struct index_sequence {
|
||||
using value_type = size_t;
|
||||
BP2D_CONSTEXPR value_type size() const { return sizeof...(Ints); }
|
||||
};
|
||||
|
||||
// A Help structure to generate the integer list
|
||||
template<size_t...Nseq> struct genSeq;
|
||||
|
||||
// Recursive template to generate the list
|
||||
template<size_t I, size_t...Nseq> struct genSeq<I, Nseq...> {
|
||||
// Type will contain a genSeq with Nseq appended by one element
|
||||
using Type = typename genSeq< I - 1, I - 1, Nseq...>::Type;
|
||||
};
|
||||
|
||||
// Terminating recursion
|
||||
template <size_t ... Nseq> struct genSeq<0, Nseq...> {
|
||||
// If I is zero, Type will contain index_sequence with the fuly generated
|
||||
// integer list.
|
||||
using Type = index_sequence<Nseq...>;
|
||||
};
|
||||
|
||||
/// Helper alias to make an index sequence from 0 to N
|
||||
template<size_t N> using make_index_sequence = typename genSeq<N>::Type;
|
||||
|
||||
/// Helper alias to make an index sequence for a parameter pack
|
||||
template<class...Args>
|
||||
using index_sequence_for = make_index_sequence<sizeof...(Args)>;
|
||||
|
||||
|
||||
/* ************************************************************************** */
|
||||
|
||||
/**
|
||||
* A useful little tool for triggering static_assert error messages e.g. when
|
||||
* a mandatory template specialization (implementation) is missing.
|
||||
|
@ -229,7 +192,7 @@ public:
|
|||
|
||||
GeomErr errcode() const { return errcode_; }
|
||||
|
||||
virtual const char * what() const BP2D_NOEXCEPT override {
|
||||
const char * what() const BP2D_NOEXCEPT override {
|
||||
return errorstr(errcode_).c_str();
|
||||
}
|
||||
};
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
|
||||
#include <string>
|
||||
#include <type_traits>
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <vector>
|
||||
#include <numeric>
|
||||
|
@ -68,7 +69,7 @@ class _Box: PointPair<RawPoint> {
|
|||
using PointPair<RawPoint>::p2;
|
||||
public:
|
||||
|
||||
inline _Box() {}
|
||||
inline _Box() = default;
|
||||
inline _Box(const RawPoint& p, const RawPoint& pp):
|
||||
PointPair<RawPoint>({p, pp}) {}
|
||||
|
||||
|
@ -85,6 +86,31 @@ public:
|
|||
inline TCoord<RawPoint> height() const BP2D_NOEXCEPT;
|
||||
|
||||
inline RawPoint center() const BP2D_NOEXCEPT;
|
||||
|
||||
inline double area() const BP2D_NOEXCEPT {
|
||||
return double(width()*height());
|
||||
}
|
||||
};
|
||||
|
||||
template<class RawPoint>
|
||||
class _Circle {
|
||||
RawPoint center_;
|
||||
double radius_ = 0;
|
||||
public:
|
||||
|
||||
_Circle() = default;
|
||||
|
||||
_Circle(const RawPoint& center, double r): center_(center), radius_(r) {}
|
||||
|
||||
inline const RawPoint& center() const BP2D_NOEXCEPT { return center_; }
|
||||
inline const void center(const RawPoint& c) { center_ = c; }
|
||||
|
||||
inline double radius() const BP2D_NOEXCEPT { return radius_; }
|
||||
inline void radius(double r) { radius_ = r; }
|
||||
|
||||
inline double area() const BP2D_NOEXCEPT {
|
||||
return 2.0*Pi*radius_;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
|
@ -97,7 +123,7 @@ class _Segment: PointPair<RawPoint> {
|
|||
mutable Radians angletox_ = std::nan("");
|
||||
public:
|
||||
|
||||
inline _Segment() {}
|
||||
inline _Segment() = default;
|
||||
|
||||
inline _Segment(const RawPoint& p, const RawPoint& pp):
|
||||
PointPair<RawPoint>({p, pp}) {}
|
||||
|
@ -188,7 +214,7 @@ struct PointLike {
|
|||
|
||||
if( (y < y1 && y < y2) || (y > y1 && y > y2) )
|
||||
return {0, false};
|
||||
else if ((y == y1 && y == y2) && (x > x1 && x > x2))
|
||||
if ((y == y1 && y == y2) && (x > x1 && x > x2))
|
||||
ret = std::min( x-x1, x -x2);
|
||||
else if( (y == y1 && y == y2) && (x < x1 && x < x2))
|
||||
ret = -std::min(x1 - x, x2 - x);
|
||||
|
@ -214,7 +240,7 @@ struct PointLike {
|
|||
|
||||
if( (x < x1 && x < x2) || (x > x1 && x > x2) )
|
||||
return {0, false};
|
||||
else if ((x == x1 && x == x2) && (y > y1 && y > y2))
|
||||
if ((x == x1 && x == x2) && (y > y1 && y > y2))
|
||||
ret = std::min( y-y1, y -y2);
|
||||
else if( (x == x1 && x == x2) && (y < y1 && y < y2))
|
||||
ret = -std::min(y1 - y, y2 - y);
|
||||
|
@ -329,7 +355,7 @@ enum class Formats {
|
|||
};
|
||||
|
||||
// This struct serves as a namespace. The only difference is that it can be
|
||||
// used in friend declarations.
|
||||
// used in friend declarations and can be aliased at class scope.
|
||||
struct ShapeLike {
|
||||
|
||||
template<class RawShape>
|
||||
|
@ -361,6 +387,51 @@ struct ShapeLike {
|
|||
return create<RawShape>(contour, {});
|
||||
}
|
||||
|
||||
template<class RawShape>
|
||||
static THolesContainer<RawShape>& holes(RawShape& /*sh*/)
|
||||
{
|
||||
static THolesContainer<RawShape> empty;
|
||||
return empty;
|
||||
}
|
||||
|
||||
template<class RawShape>
|
||||
static const THolesContainer<RawShape>& holes(const RawShape& /*sh*/)
|
||||
{
|
||||
static THolesContainer<RawShape> empty;
|
||||
return empty;
|
||||
}
|
||||
|
||||
template<class RawShape>
|
||||
static TContour<RawShape>& getHole(RawShape& sh, unsigned long idx)
|
||||
{
|
||||
return holes(sh)[idx];
|
||||
}
|
||||
|
||||
template<class RawShape>
|
||||
static const TContour<RawShape>& getHole(const RawShape& sh,
|
||||
unsigned long idx)
|
||||
{
|
||||
return holes(sh)[idx];
|
||||
}
|
||||
|
||||
template<class RawShape>
|
||||
static size_t holeCount(const RawShape& sh)
|
||||
{
|
||||
return holes(sh).size();
|
||||
}
|
||||
|
||||
template<class RawShape>
|
||||
static TContour<RawShape>& getContour(RawShape& sh)
|
||||
{
|
||||
return sh;
|
||||
}
|
||||
|
||||
template<class RawShape>
|
||||
static const TContour<RawShape>& getContour(const RawShape& sh)
|
||||
{
|
||||
return sh;
|
||||
}
|
||||
|
||||
// Optional, does nothing by default
|
||||
template<class RawShape>
|
||||
static void reserve(RawShape& /*sh*/, size_t /*vertex_capacity*/) {}
|
||||
|
@ -402,7 +473,7 @@ struct ShapeLike {
|
|||
}
|
||||
|
||||
template<Formats, class RawShape>
|
||||
static std::string serialize(const RawShape& /*sh*/, double scale=1)
|
||||
static std::string serialize(const RawShape& /*sh*/, double /*scale*/=1)
|
||||
{
|
||||
static_assert(always_false<RawShape>::value,
|
||||
"ShapeLike::serialize() unimplemented!");
|
||||
|
@ -498,51 +569,6 @@ struct ShapeLike {
|
|||
return RawShape();
|
||||
}
|
||||
|
||||
template<class RawShape>
|
||||
static THolesContainer<RawShape>& holes(RawShape& /*sh*/)
|
||||
{
|
||||
static THolesContainer<RawShape> empty;
|
||||
return empty;
|
||||
}
|
||||
|
||||
template<class RawShape>
|
||||
static const THolesContainer<RawShape>& holes(const RawShape& /*sh*/)
|
||||
{
|
||||
static THolesContainer<RawShape> empty;
|
||||
return empty;
|
||||
}
|
||||
|
||||
template<class RawShape>
|
||||
static TContour<RawShape>& getHole(RawShape& sh, unsigned long idx)
|
||||
{
|
||||
return holes(sh)[idx];
|
||||
}
|
||||
|
||||
template<class RawShape>
|
||||
static const TContour<RawShape>& getHole(const RawShape& sh,
|
||||
unsigned long idx)
|
||||
{
|
||||
return holes(sh)[idx];
|
||||
}
|
||||
|
||||
template<class RawShape>
|
||||
static size_t holeCount(const RawShape& sh)
|
||||
{
|
||||
return holes(sh).size();
|
||||
}
|
||||
|
||||
template<class RawShape>
|
||||
static TContour<RawShape>& getContour(RawShape& sh)
|
||||
{
|
||||
return sh;
|
||||
}
|
||||
|
||||
template<class RawShape>
|
||||
static const TContour<RawShape>& getContour(const RawShape& sh)
|
||||
{
|
||||
return sh;
|
||||
}
|
||||
|
||||
template<class RawShape>
|
||||
static void rotate(RawShape& /*sh*/, const Radians& /*rads*/)
|
||||
{
|
||||
|
@ -614,6 +640,22 @@ struct ShapeLike {
|
|||
return box;
|
||||
}
|
||||
|
||||
template<class RawShape>
|
||||
static inline _Box<TPoint<RawShape>> boundingBox(
|
||||
const _Circle<TPoint<RawShape>>& circ)
|
||||
{
|
||||
using Coord = TCoord<TPoint<RawShape>>;
|
||||
TPoint<RawShape> pmin = {
|
||||
static_cast<Coord>(getX(circ.center()) - circ.radius()),
|
||||
static_cast<Coord>(getY(circ.center()) - circ.radius()) };
|
||||
|
||||
TPoint<RawShape> pmax = {
|
||||
static_cast<Coord>(getX(circ.center()) + circ.radius()),
|
||||
static_cast<Coord>(getY(circ.center()) + circ.radius()) };
|
||||
|
||||
return {pmin, pmax};
|
||||
}
|
||||
|
||||
template<class RawShape>
|
||||
static inline double area(const _Box<TPoint<RawShape>>& box)
|
||||
{
|
||||
|
@ -621,14 +663,74 @@ struct ShapeLike {
|
|||
}
|
||||
|
||||
template<class RawShape>
|
||||
static double area(const Shapes<RawShape>& shapes)
|
||||
static inline double area(const _Circle<TPoint<RawShape>>& circ)
|
||||
{
|
||||
double ret = 0;
|
||||
std::accumulate(shapes.first(), shapes.end(),
|
||||
[](const RawShape& a, const RawShape& b) {
|
||||
return area(a) + area(b);
|
||||
return circ.area();
|
||||
}
|
||||
|
||||
template<class RawShape>
|
||||
static inline double area(const Shapes<RawShape>& shapes)
|
||||
{
|
||||
return std::accumulate(shapes.begin(), shapes.end(), 0.0,
|
||||
[](double a, const RawShape& b) {
|
||||
return a += area(b);
|
||||
});
|
||||
return ret;
|
||||
}
|
||||
|
||||
template<class RawShape>
|
||||
static bool isInside(const TPoint<RawShape>& point,
|
||||
const _Circle<TPoint<RawShape>>& circ)
|
||||
{
|
||||
return PointLike::distance(point, circ.center()) < circ.radius();
|
||||
}
|
||||
|
||||
template<class RawShape>
|
||||
static bool isInside(const TPoint<RawShape>& point,
|
||||
const _Box<TPoint<RawShape>>& box)
|
||||
{
|
||||
auto px = getX(point);
|
||||
auto py = getY(point);
|
||||
auto minx = getX(box.minCorner());
|
||||
auto miny = getY(box.minCorner());
|
||||
auto maxx = getX(box.maxCorner());
|
||||
auto maxy = getY(box.maxCorner());
|
||||
|
||||
return px > minx && px < maxx && py > miny && py < maxy;
|
||||
}
|
||||
|
||||
template<class RawShape>
|
||||
static bool isInside(const RawShape& sh,
|
||||
const _Circle<TPoint<RawShape>>& circ)
|
||||
{
|
||||
return std::all_of(cbegin(sh), cend(sh),
|
||||
[&circ](const TPoint<RawShape>& p){
|
||||
return isInside<RawShape>(p, circ);
|
||||
});
|
||||
}
|
||||
|
||||
template<class RawShape>
|
||||
static bool isInside(const _Box<TPoint<RawShape>>& box,
|
||||
const _Circle<TPoint<RawShape>>& circ)
|
||||
{
|
||||
return isInside<RawShape>(box.minCorner(), circ) &&
|
||||
isInside<RawShape>(box.maxCorner(), circ);
|
||||
}
|
||||
|
||||
template<class RawShape>
|
||||
static bool isInside(const _Box<TPoint<RawShape>>& ibb,
|
||||
const _Box<TPoint<RawShape>>& box)
|
||||
{
|
||||
auto iminX = getX(ibb.minCorner());
|
||||
auto imaxX = getX(ibb.maxCorner());
|
||||
auto iminY = getY(ibb.minCorner());
|
||||
auto imaxY = getY(ibb.maxCorner());
|
||||
|
||||
auto minX = getX(box.minCorner());
|
||||
auto maxX = getX(box.maxCorner());
|
||||
auto minY = getY(box.minCorner());
|
||||
auto maxY = getY(box.maxCorner());
|
||||
|
||||
return iminX > minX && imaxX < maxX && iminY > minY && imaxY < maxY;
|
||||
}
|
||||
|
||||
template<class RawShape> // Potential O(1) implementation may exist
|
||||
|
|
|
@ -3,7 +3,9 @@
|
|||
|
||||
#include "geometry_traits.hpp"
|
||||
#include <algorithm>
|
||||
#include <functional>
|
||||
#include <vector>
|
||||
#include <iterator>
|
||||
|
||||
namespace libnest2d {
|
||||
|
||||
|
@ -23,64 +25,22 @@ struct Nfp {
|
|||
template<class RawShape>
|
||||
using Shapes = typename ShapeLike::Shapes<RawShape>;
|
||||
|
||||
/// Minkowski addition (not used yet)
|
||||
/**
|
||||
* Merge a bunch of polygons with the specified additional polygon.
|
||||
*
|
||||
* \tparam RawShape the Polygon data type.
|
||||
* \param shc The pile of polygons that will be unified with sh.
|
||||
* \param sh A single polygon to unify with shc.
|
||||
*
|
||||
* \return A set of polygons that is the union of the input polygons. Note that
|
||||
* mostly it will be a set containing only one big polygon but if the input
|
||||
* polygons are disjuct than the resulting set will contain more polygons.
|
||||
*/
|
||||
template<class RawShape>
|
||||
static RawShape minkowskiDiff(const RawShape& sh, const RawShape& cother)
|
||||
static Shapes<RawShape> merge(const Shapes<RawShape>& /*shc*/)
|
||||
{
|
||||
using Vertex = TPoint<RawShape>;
|
||||
//using Coord = TCoord<Vertex>;
|
||||
using Edge = _Segment<Vertex>;
|
||||
using sl = ShapeLike;
|
||||
using std::signbit;
|
||||
|
||||
// Copy the orbiter (controur only), we will have to work on it
|
||||
RawShape orbiter = sl::create(sl::getContour(cother));
|
||||
|
||||
// Make the orbiter reverse oriented
|
||||
for(auto &v : sl::getContour(orbiter)) v = -v;
|
||||
|
||||
// An egde with additional data for marking it
|
||||
struct MarkedEdge { Edge e; Radians turn_angle; bool is_turning_point; };
|
||||
|
||||
// Container for marked edges
|
||||
using EdgeList = std::vector<MarkedEdge>;
|
||||
|
||||
EdgeList A, B;
|
||||
|
||||
auto fillEdgeList = [](EdgeList& L, const RawShape& poly) {
|
||||
L.reserve(sl::contourVertexCount(poly));
|
||||
|
||||
auto it = sl::cbegin(poly);
|
||||
auto nextit = std::next(it);
|
||||
|
||||
L.emplace_back({Edge(*it, *nextit), 0, false});
|
||||
it++; nextit++;
|
||||
|
||||
while(nextit != sl::cend(poly)) {
|
||||
Edge e(*it, *nextit);
|
||||
auto& L_prev = L.back();
|
||||
auto phi = L_prev.e.angleToXaxis();
|
||||
auto phi_prev = e.angleToXaxis();
|
||||
auto turn_angle = phi-phi_prev;
|
||||
if(turn_angle > Pi) turn_angle -= 2*Pi;
|
||||
L.emplace_back({
|
||||
e,
|
||||
turn_angle,
|
||||
signbit(turn_angle) != signbit(L_prev.turn_angle)
|
||||
});
|
||||
it++; nextit++;
|
||||
}
|
||||
|
||||
L.front().turn_angle = L.front().e.angleToXaxis() -
|
||||
L.back().e.angleToXaxis();
|
||||
|
||||
if(L.front().turn_angle > Pi) L.front().turn_angle -= 2*Pi;
|
||||
};
|
||||
|
||||
fillEdgeList(A, sh);
|
||||
fillEdgeList(B, orbiter);
|
||||
|
||||
return sh;
|
||||
static_assert(always_false<RawShape>::value,
|
||||
"Nfp::merge(shapes, shape) unimplemented!");
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -95,10 +55,12 @@ static RawShape minkowskiDiff(const RawShape& sh, const RawShape& cother)
|
|||
* polygons are disjuct than the resulting set will contain more polygons.
|
||||
*/
|
||||
template<class RawShape>
|
||||
static Shapes<RawShape> merge(const Shapes<RawShape>& shc, const RawShape& sh)
|
||||
static Shapes<RawShape> merge(const Shapes<RawShape>& shc,
|
||||
const RawShape& sh)
|
||||
{
|
||||
static_assert(always_false<RawShape>::value,
|
||||
"Nfp::merge(shapes, shape) unimplemented!");
|
||||
auto m = merge(shc);
|
||||
m.push_back(sh);
|
||||
return merge(m);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -139,16 +101,20 @@ template<class RawShape>
|
|||
static TPoint<RawShape> rightmostUpVertex(const RawShape& sh)
|
||||
{
|
||||
|
||||
// find min x and min y vertex
|
||||
// find max x and max y vertex
|
||||
auto it = std::max_element(ShapeLike::cbegin(sh), ShapeLike::cend(sh),
|
||||
_vsort<RawShape>);
|
||||
|
||||
return *it;
|
||||
}
|
||||
|
||||
template<class RawShape>
|
||||
using NfpResult = std::pair<RawShape, TPoint<RawShape>>;
|
||||
|
||||
/// Helper function to get the NFP
|
||||
template<NfpLevel nfptype, class RawShape>
|
||||
static RawShape noFitPolygon(const RawShape& sh, const RawShape& other)
|
||||
static NfpResult<RawShape> noFitPolygon(const RawShape& sh,
|
||||
const RawShape& other)
|
||||
{
|
||||
NfpImpl<RawShape, nfptype> nfp;
|
||||
return nfp(sh, other);
|
||||
|
@ -167,44 +133,46 @@ static RawShape noFitPolygon(const RawShape& sh, const RawShape& other)
|
|||
* \tparam RawShape the Polygon data type.
|
||||
* \param sh The stationary polygon
|
||||
* \param cother The orbiting polygon
|
||||
* \return Returns the NFP of the two input polygons which have to be strictly
|
||||
* convex. The resulting NFP is proven to be convex as well in this case.
|
||||
* \return Returns a pair of the NFP and its reference vertex of the two input
|
||||
* polygons which have to be strictly convex. The resulting NFP is proven to be
|
||||
* convex as well in this case.
|
||||
*
|
||||
*/
|
||||
template<class RawShape>
|
||||
static RawShape nfpConvexOnly(const RawShape& sh, const RawShape& cother)
|
||||
static NfpResult<RawShape> nfpConvexOnly(const RawShape& sh,
|
||||
const RawShape& other)
|
||||
{
|
||||
using Vertex = TPoint<RawShape>; using Edge = _Segment<Vertex>;
|
||||
|
||||
RawShape other = cother;
|
||||
|
||||
// Make the other polygon counter-clockwise
|
||||
std::reverse(ShapeLike::begin(other), ShapeLike::end(other));
|
||||
using sl = ShapeLike;
|
||||
|
||||
RawShape rsh; // Final nfp placeholder
|
||||
Vertex top_nfp;
|
||||
std::vector<Edge> edgelist;
|
||||
|
||||
auto cap = ShapeLike::contourVertexCount(sh) +
|
||||
ShapeLike::contourVertexCount(other);
|
||||
auto cap = sl::contourVertexCount(sh) + sl::contourVertexCount(other);
|
||||
|
||||
// Reserve the needed memory
|
||||
edgelist.reserve(cap);
|
||||
ShapeLike::reserve(rsh, static_cast<unsigned long>(cap));
|
||||
sl::reserve(rsh, static_cast<unsigned long>(cap));
|
||||
|
||||
{ // place all edges from sh into edgelist
|
||||
auto first = ShapeLike::cbegin(sh);
|
||||
auto next = first + 1;
|
||||
auto endit = ShapeLike::cend(sh);
|
||||
auto first = sl::cbegin(sh);
|
||||
auto next = std::next(first);
|
||||
|
||||
while(next != endit) edgelist.emplace_back(*(first++), *(next++));
|
||||
while(next != sl::cend(sh)) {
|
||||
edgelist.emplace_back(*(first), *(next));
|
||||
++first; ++next;
|
||||
}
|
||||
}
|
||||
|
||||
{ // place all edges from other into edgelist
|
||||
auto first = ShapeLike::cbegin(other);
|
||||
auto next = first + 1;
|
||||
auto endit = ShapeLike::cend(other);
|
||||
auto first = sl::cbegin(other);
|
||||
auto next = std::next(first);
|
||||
|
||||
while(next != endit) edgelist.emplace_back(*(first++), *(next++));
|
||||
while(next != sl::cend(other)) {
|
||||
edgelist.emplace_back(*(next), *(first));
|
||||
++first; ++next;
|
||||
}
|
||||
}
|
||||
|
||||
// Sort the edges by angle to X axis.
|
||||
|
@ -215,10 +183,16 @@ static RawShape nfpConvexOnly(const RawShape& sh, const RawShape& cother)
|
|||
});
|
||||
|
||||
// Add the two vertices from the first edge into the final polygon.
|
||||
ShapeLike::addVertex(rsh, edgelist.front().first());
|
||||
ShapeLike::addVertex(rsh, edgelist.front().second());
|
||||
sl::addVertex(rsh, edgelist.front().first());
|
||||
sl::addVertex(rsh, edgelist.front().second());
|
||||
|
||||
auto tmp = std::next(ShapeLike::begin(rsh));
|
||||
// Sorting function for the nfp reference vertex search
|
||||
auto& cmp = _vsort<RawShape>;
|
||||
|
||||
// the reference (rightmost top) vertex so far
|
||||
top_nfp = *std::max_element(sl::cbegin(rsh), sl::cend(rsh), cmp );
|
||||
|
||||
auto tmp = std::next(sl::begin(rsh));
|
||||
|
||||
// Construct final nfp by placing each edge to the end of the previous
|
||||
for(auto eit = std::next(edgelist.begin());
|
||||
|
@ -226,56 +200,325 @@ static RawShape nfpConvexOnly(const RawShape& sh, const RawShape& cother)
|
|||
++eit)
|
||||
{
|
||||
auto d = *tmp - eit->first();
|
||||
auto p = eit->second() + d;
|
||||
Vertex p = eit->second() + d;
|
||||
|
||||
ShapeLike::addVertex(rsh, p);
|
||||
sl::addVertex(rsh, p);
|
||||
|
||||
// Set the new reference vertex
|
||||
if(cmp(top_nfp, p)) top_nfp = p;
|
||||
|
||||
tmp = std::next(tmp);
|
||||
}
|
||||
|
||||
// Now we have an nfp somewhere in the dark. We need to get it
|
||||
// to the right position around the stationary shape.
|
||||
// This is done by choosing the leftmost lowest vertex of the
|
||||
// orbiting polygon to be touched with the rightmost upper
|
||||
// vertex of the stationary polygon. In this configuration, the
|
||||
// reference vertex of the orbiting polygon (which can be dragged around
|
||||
// the nfp) will be its rightmost upper vertex that coincides with the
|
||||
// rightmost upper vertex of the nfp. No proof provided other than Jonas
|
||||
// Lindmark's reasoning about the reference vertex of nfp in his thesis
|
||||
// ("No fit polygon problem" - section 2.1.9)
|
||||
return {rsh, top_nfp};
|
||||
}
|
||||
|
||||
// TODO: dont do this here. Cache the rmu and lmd in Item and get translate
|
||||
// the nfp after this call
|
||||
template<class RawShape>
|
||||
static NfpResult<RawShape> nfpSimpleSimple(const RawShape& cstationary,
|
||||
const RawShape& cother)
|
||||
{
|
||||
|
||||
auto csh = sh; // Copy sh, we will sort the verices in the copy
|
||||
auto& cmp = _vsort<RawShape>;
|
||||
std::sort(ShapeLike::begin(csh), ShapeLike::end(csh), cmp);
|
||||
std::sort(ShapeLike::begin(other), ShapeLike::end(other), cmp);
|
||||
// Algorithms are from the original algorithm proposed in paper:
|
||||
// https://eprints.soton.ac.uk/36850/1/CORMSIS-05-05.pdf
|
||||
|
||||
// leftmost lower vertex of the stationary polygon
|
||||
auto& touch_sh = *(std::prev(ShapeLike::end(csh)));
|
||||
// rightmost upper vertex of the orbiting polygon
|
||||
auto& touch_other = *(ShapeLike::begin(other));
|
||||
// /////////////////////////////////////////////////////////////////////////
|
||||
// Algorithm 1: Obtaining the minkowski sum
|
||||
// /////////////////////////////////////////////////////////////////////////
|
||||
|
||||
// Calculate the difference and move the orbiter to the touch position.
|
||||
auto dtouch = touch_sh - touch_other;
|
||||
auto top_other = *(std::prev(ShapeLike::end(other))) + dtouch;
|
||||
// I guess this is not a full minkowski sum of the two input polygons by
|
||||
// definition. This yields a subset that is compatible with the next 2
|
||||
// algorithms.
|
||||
|
||||
// Get the righmost upper vertex of the nfp and move it to the RMU of
|
||||
// the orbiter because they should coincide.
|
||||
auto&& top_nfp = rightmostUpVertex(rsh);
|
||||
auto dnfp = top_other - top_nfp;
|
||||
std::for_each(ShapeLike::begin(rsh), ShapeLike::end(rsh),
|
||||
[&dnfp](Vertex& v) { v+= dnfp; } );
|
||||
using Result = NfpResult<RawShape>;
|
||||
using Vertex = TPoint<RawShape>;
|
||||
using Coord = TCoord<Vertex>;
|
||||
using Edge = _Segment<Vertex>;
|
||||
using sl = ShapeLike;
|
||||
using std::signbit;
|
||||
using std::sort;
|
||||
using std::vector;
|
||||
using std::ref;
|
||||
using std::reference_wrapper;
|
||||
|
||||
return rsh;
|
||||
// TODO The original algorithms expects the stationary polygon in
|
||||
// counter clockwise and the orbiter in clockwise order.
|
||||
// So for preventing any further complication, I will make the input
|
||||
// the way it should be, than make my way around the orientations.
|
||||
|
||||
// Reverse the stationary contour to counter clockwise
|
||||
auto stcont = sl::getContour(cstationary);
|
||||
std::reverse(stcont.begin(), stcont.end());
|
||||
RawShape stationary;
|
||||
sl::getContour(stationary) = stcont;
|
||||
|
||||
// Reverse the orbiter contour to counter clockwise
|
||||
auto orbcont = sl::getContour(cother);
|
||||
|
||||
std::reverse(orbcont.begin(), orbcont.end());
|
||||
|
||||
// Copy the orbiter (contour only), we will have to work on it
|
||||
RawShape orbiter;
|
||||
sl::getContour(orbiter) = orbcont;
|
||||
|
||||
// Step 1: Make the orbiter reverse oriented
|
||||
for(auto &v : sl::getContour(orbiter)) v = -v;
|
||||
|
||||
// An egde with additional data for marking it
|
||||
struct MarkedEdge {
|
||||
Edge e; Radians turn_angle = 0; bool is_turning_point = false;
|
||||
MarkedEdge() = default;
|
||||
MarkedEdge(const Edge& ed, Radians ta, bool tp):
|
||||
e(ed), turn_angle(ta), is_turning_point(tp) {}
|
||||
};
|
||||
|
||||
// Container for marked edges
|
||||
using EdgeList = vector<MarkedEdge>;
|
||||
|
||||
EdgeList A, B;
|
||||
|
||||
// This is how an edge list is created from the polygons
|
||||
auto fillEdgeList = [](EdgeList& L, const RawShape& poly, int dir) {
|
||||
L.reserve(sl::contourVertexCount(poly));
|
||||
|
||||
auto it = sl::cbegin(poly);
|
||||
auto nextit = std::next(it);
|
||||
|
||||
double turn_angle = 0;
|
||||
bool is_turn_point = false;
|
||||
|
||||
while(nextit != sl::cend(poly)) {
|
||||
L.emplace_back(Edge(*it, *nextit), turn_angle, is_turn_point);
|
||||
it++; nextit++;
|
||||
}
|
||||
|
||||
auto getTurnAngle = [](const Edge& e1, const Edge& e2) {
|
||||
auto phi = e1.angleToXaxis();
|
||||
auto phi_prev = e2.angleToXaxis();
|
||||
auto TwoPi = 2.0*Pi;
|
||||
if(phi > Pi) phi -= TwoPi;
|
||||
if(phi_prev > Pi) phi_prev -= TwoPi;
|
||||
auto turn_angle = phi-phi_prev;
|
||||
if(turn_angle > Pi) turn_angle -= TwoPi;
|
||||
return phi-phi_prev;
|
||||
};
|
||||
|
||||
if(dir > 0) {
|
||||
auto eit = L.begin();
|
||||
auto enext = std::next(eit);
|
||||
|
||||
eit->turn_angle = getTurnAngle(L.front().e, L.back().e);
|
||||
|
||||
while(enext != L.end()) {
|
||||
enext->turn_angle = getTurnAngle( enext->e, eit->e);
|
||||
enext->is_turning_point =
|
||||
signbit(enext->turn_angle) != signbit(eit->turn_angle);
|
||||
++eit; ++enext;
|
||||
}
|
||||
|
||||
L.front().is_turning_point = signbit(L.front().turn_angle) !=
|
||||
signbit(L.back().turn_angle);
|
||||
} else {
|
||||
std::cout << L.size() << std::endl;
|
||||
|
||||
auto eit = L.rbegin();
|
||||
auto enext = std::next(eit);
|
||||
|
||||
eit->turn_angle = getTurnAngle(L.back().e, L.front().e);
|
||||
|
||||
while(enext != L.rend()) {
|
||||
enext->turn_angle = getTurnAngle(enext->e, eit->e);
|
||||
enext->is_turning_point =
|
||||
signbit(enext->turn_angle) != signbit(eit->turn_angle);
|
||||
std::cout << enext->is_turning_point << " " << enext->turn_angle << std::endl;
|
||||
|
||||
++eit; ++enext;
|
||||
}
|
||||
|
||||
L.back().is_turning_point = signbit(L.back().turn_angle) !=
|
||||
signbit(L.front().turn_angle);
|
||||
}
|
||||
};
|
||||
|
||||
// Step 2: Fill the edgelists
|
||||
fillEdgeList(A, stationary, 1);
|
||||
fillEdgeList(B, orbiter, -1);
|
||||
|
||||
// A reference to a marked edge that also knows its container
|
||||
struct MarkedEdgeRef {
|
||||
reference_wrapper<MarkedEdge> eref;
|
||||
reference_wrapper<vector<MarkedEdgeRef>> container;
|
||||
Coord dir = 1; // Direction modifier
|
||||
|
||||
inline Radians angleX() const { return eref.get().e.angleToXaxis(); }
|
||||
inline const Edge& edge() const { return eref.get().e; }
|
||||
inline Edge& edge() { return eref.get().e; }
|
||||
inline bool isTurningPoint() const {
|
||||
return eref.get().is_turning_point;
|
||||
}
|
||||
inline bool isFrom(const vector<MarkedEdgeRef>& cont ) {
|
||||
return &(container.get()) == &cont;
|
||||
}
|
||||
inline bool eq(const MarkedEdgeRef& mr) {
|
||||
return &(eref.get()) == &(mr.eref.get());
|
||||
}
|
||||
|
||||
MarkedEdgeRef(reference_wrapper<MarkedEdge> er,
|
||||
reference_wrapper<vector<MarkedEdgeRef>> ec):
|
||||
eref(er), container(ec), dir(1) {}
|
||||
|
||||
MarkedEdgeRef(reference_wrapper<MarkedEdge> er,
|
||||
reference_wrapper<vector<MarkedEdgeRef>> ec,
|
||||
Coord d):
|
||||
eref(er), container(ec), dir(d) {}
|
||||
};
|
||||
|
||||
using EdgeRefList = vector<MarkedEdgeRef>;
|
||||
|
||||
// Comparing two marked edges
|
||||
auto sortfn = [](const MarkedEdgeRef& e1, const MarkedEdgeRef& e2) {
|
||||
return e1.angleX() < e2.angleX();
|
||||
};
|
||||
|
||||
EdgeRefList Aref, Bref; // We create containers for the references
|
||||
Aref.reserve(A.size()); Bref.reserve(B.size());
|
||||
|
||||
// Fill reference container for the stationary polygon
|
||||
std::for_each(A.begin(), A.end(), [&Aref](MarkedEdge& me) {
|
||||
Aref.emplace_back( ref(me), ref(Aref) );
|
||||
});
|
||||
|
||||
// Fill reference container for the orbiting polygon
|
||||
std::for_each(B.begin(), B.end(), [&Bref](MarkedEdge& me) {
|
||||
Bref.emplace_back( ref(me), ref(Bref) );
|
||||
});
|
||||
|
||||
struct EdgeGroup { typename EdgeRefList::const_iterator first, last; };
|
||||
|
||||
auto mink = [sortfn] // the Mink(Q, R, direction) sub-procedure
|
||||
(const EdgeGroup& Q, const EdgeGroup& R, bool positive)
|
||||
{
|
||||
|
||||
// Step 1 "merge sort_list(Q) and sort_list(R) to form merge_list(Q,R)"
|
||||
// Sort the containers of edge references and merge them.
|
||||
// Q could be sorted only once and be reused here but we would still
|
||||
// need to merge it with sorted(R).
|
||||
|
||||
EdgeRefList merged;
|
||||
EdgeRefList S, seq;
|
||||
merged.reserve((Q.last - Q.first) + (R.last - R.first));
|
||||
|
||||
merged.insert(merged.end(), Q.first, Q.last);
|
||||
merged.insert(merged.end(), R.first, R.last);
|
||||
sort(merged.begin(), merged.end(), sortfn);
|
||||
|
||||
// Step 2 "set i = 1, k = 1, direction = 1, s1 = q1"
|
||||
// we dont use i, instead, q is an iterator into Q. k would be an index
|
||||
// into the merged sequence but we use "it" as an iterator for that
|
||||
|
||||
// here we obtain references for the containers for later comparisons
|
||||
const auto& Rcont = R.first->container.get();
|
||||
const auto& Qcont = Q.first->container.get();
|
||||
|
||||
// Set the intial direction
|
||||
Coord dir = positive? 1 : -1;
|
||||
|
||||
// roughly i = 1 (so q = Q.first) and s1 = q1 so S[0] = q;
|
||||
auto q = Q.first;
|
||||
S.push_back(*q++);
|
||||
|
||||
// Roughly step 3
|
||||
while(q != Q.last) {
|
||||
auto it = merged.begin();
|
||||
while(it != merged.end() && !(it->eq(*(Q.first))) ) {
|
||||
if(it->isFrom(Rcont)) {
|
||||
auto s = *it;
|
||||
s.dir = dir;
|
||||
S.push_back(s);
|
||||
}
|
||||
if(it->eq(*q)) {
|
||||
S.push_back(*q);
|
||||
if(it->isTurningPoint()) dir = -dir;
|
||||
if(q != Q.first) it += dir;
|
||||
}
|
||||
else it += dir;
|
||||
}
|
||||
++q; // "Set i = i + 1"
|
||||
}
|
||||
|
||||
// Step 4:
|
||||
|
||||
// "Let starting edge r1 be in position si in sequence"
|
||||
// whaaat? I guess this means the following:
|
||||
S[0] = *R.first;
|
||||
auto it = S.begin();
|
||||
|
||||
// "Set j = 1, next = 2, direction = 1, seq1 = si"
|
||||
// we dont use j, seq is expanded dynamically.
|
||||
dir = 1; auto next = std::next(R.first);
|
||||
|
||||
// Step 5:
|
||||
// "If all si edges have been allocated to seqj" should mean that
|
||||
// we loop until seq has equal size with S
|
||||
while(seq.size() < S.size()) {
|
||||
++it; if(it == S.end()) it = S.begin();
|
||||
|
||||
if(it->isFrom(Qcont)) {
|
||||
seq.push_back(*it); // "If si is from Q, j = j + 1, seqj = si"
|
||||
|
||||
// "If si is a turning point in Q,
|
||||
// direction = - direction, next = next + direction"
|
||||
if(it->isTurningPoint()) { dir = -dir; next += dir; }
|
||||
}
|
||||
|
||||
if(it->eq(*next) && dir == next->dir) { // "If si = direction.rnext"
|
||||
// "j = j + 1, seqj = si, next = next + direction"
|
||||
seq.push_back(*it); next += dir;
|
||||
}
|
||||
}
|
||||
|
||||
return seq;
|
||||
};
|
||||
|
||||
EdgeGroup R{ Bref.begin(), Bref.begin() }, Q{ Aref.begin(), Aref.end() };
|
||||
auto it = Bref.begin();
|
||||
bool orientation = true;
|
||||
EdgeRefList seqlist;
|
||||
seqlist.reserve(3*(Aref.size() + Bref.size()));
|
||||
|
||||
while(it != Bref.end()) // This is step 3 and step 4 in one loop
|
||||
if(it->isTurningPoint()) {
|
||||
R = {R.last, it++};
|
||||
auto seq = mink(Q, R, orientation);
|
||||
|
||||
// TODO step 6 (should be 5 shouldn't it?): linking edges from A
|
||||
// I don't get this step
|
||||
|
||||
seqlist.insert(seqlist.end(), seq.begin(), seq.end());
|
||||
orientation = !orientation;
|
||||
} else ++it;
|
||||
|
||||
if(seqlist.empty()) seqlist = mink(Q, {Bref.begin(), Bref.end()}, true);
|
||||
|
||||
// /////////////////////////////////////////////////////////////////////////
|
||||
// Algorithm 2: breaking Minkowski sums into track line trips
|
||||
// /////////////////////////////////////////////////////////////////////////
|
||||
|
||||
|
||||
// /////////////////////////////////////////////////////////////////////////
|
||||
// Algorithm 3: finding the boundary of the NFP from track line trips
|
||||
// /////////////////////////////////////////////////////////////////////////
|
||||
|
||||
|
||||
|
||||
return Result(stationary, Vertex());
|
||||
}
|
||||
|
||||
// Specializable NFP implementation class. Specialize it if you have a faster
|
||||
// or better NFP implementation
|
||||
template<class RawShape, NfpLevel nfptype>
|
||||
struct NfpImpl {
|
||||
RawShape operator()(const RawShape& sh, const RawShape& other) {
|
||||
NfpResult<RawShape> operator()(const RawShape& sh, const RawShape& other)
|
||||
{
|
||||
static_assert(nfptype == NfpLevel::CONVEX_ONLY,
|
||||
"Nfp::noFitPolygon() unimplemented!");
|
||||
|
||||
|
|
|
@ -9,6 +9,7 @@
|
|||
#include <functional>
|
||||
|
||||
#include "geometry_traits.hpp"
|
||||
#include "optimizer.hpp"
|
||||
|
||||
namespace libnest2d {
|
||||
|
||||
|
@ -27,6 +28,7 @@ class _Item {
|
|||
using Coord = TCoord<TPoint<RawShape>>;
|
||||
using Vertex = TPoint<RawShape>;
|
||||
using Box = _Box<Vertex>;
|
||||
using sl = ShapeLike;
|
||||
|
||||
// The original shape that gets encapsulated.
|
||||
RawShape sh_;
|
||||
|
@ -51,11 +53,18 @@ class _Item {
|
|||
|
||||
enum class Convexity: char {
|
||||
UNCHECKED,
|
||||
TRUE,
|
||||
FALSE
|
||||
C_TRUE,
|
||||
C_FALSE
|
||||
};
|
||||
|
||||
mutable Convexity convexity_ = Convexity::UNCHECKED;
|
||||
mutable TVertexConstIterator<RawShape> rmt_; // rightmost top vertex
|
||||
mutable TVertexConstIterator<RawShape> lmb_; // leftmost bottom vertex
|
||||
mutable bool rmt_valid_ = false, lmb_valid_ = false;
|
||||
mutable struct BBCache {
|
||||
Box bb; bool valid; Vertex tr;
|
||||
BBCache(): valid(false), tr(0, 0) {}
|
||||
} bb_cache_;
|
||||
|
||||
public:
|
||||
|
||||
|
@ -104,15 +113,15 @@ public:
|
|||
* @param il The initializer list of vertices.
|
||||
*/
|
||||
inline _Item(const std::initializer_list< Vertex >& il):
|
||||
sh_(ShapeLike::create<RawShape>(il)) {}
|
||||
sh_(sl::create<RawShape>(il)) {}
|
||||
|
||||
inline _Item(const TContour<RawShape>& contour,
|
||||
const THolesContainer<RawShape>& holes = {}):
|
||||
sh_(ShapeLike::create<RawShape>(contour, holes)) {}
|
||||
sh_(sl::create<RawShape>(contour, holes)) {}
|
||||
|
||||
inline _Item(TContour<RawShape>&& contour,
|
||||
THolesContainer<RawShape>&& holes):
|
||||
sh_(ShapeLike::create<RawShape>(std::move(contour),
|
||||
sh_(sl::create<RawShape>(std::move(contour),
|
||||
std::move(holes))) {}
|
||||
|
||||
/**
|
||||
|
@ -122,31 +131,31 @@ public:
|
|||
*/
|
||||
inline std::string toString() const
|
||||
{
|
||||
return ShapeLike::toString(sh_);
|
||||
return sl::toString(sh_);
|
||||
}
|
||||
|
||||
/// Iterator tho the first contour vertex in the polygon.
|
||||
inline Iterator begin() const
|
||||
{
|
||||
return ShapeLike::cbegin(sh_);
|
||||
return sl::cbegin(sh_);
|
||||
}
|
||||
|
||||
/// Alias to begin()
|
||||
inline Iterator cbegin() const
|
||||
{
|
||||
return ShapeLike::cbegin(sh_);
|
||||
return sl::cbegin(sh_);
|
||||
}
|
||||
|
||||
/// Iterator to the last contour vertex.
|
||||
inline Iterator end() const
|
||||
{
|
||||
return ShapeLike::cend(sh_);
|
||||
return sl::cend(sh_);
|
||||
}
|
||||
|
||||
/// Alias to end()
|
||||
inline Iterator cend() const
|
||||
{
|
||||
return ShapeLike::cend(sh_);
|
||||
return sl::cend(sh_);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -161,7 +170,7 @@ public:
|
|||
*/
|
||||
inline Vertex vertex(unsigned long idx) const
|
||||
{
|
||||
return ShapeLike::vertex(sh_, idx);
|
||||
return sl::vertex(sh_, idx);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -176,7 +185,7 @@ public:
|
|||
inline void setVertex(unsigned long idx, const Vertex& v )
|
||||
{
|
||||
invalidateCache();
|
||||
ShapeLike::vertex(sh_, idx) = v;
|
||||
sl::vertex(sh_, idx) = v;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -191,7 +200,7 @@ public:
|
|||
double ret ;
|
||||
if(area_cache_valid_) ret = area_cache_;
|
||||
else {
|
||||
ret = ShapeLike::area(offsettedShape());
|
||||
ret = sl::area(offsettedShape());
|
||||
area_cache_ = ret;
|
||||
area_cache_valid_ = true;
|
||||
}
|
||||
|
@ -203,17 +212,17 @@ public:
|
|||
|
||||
switch(convexity_) {
|
||||
case Convexity::UNCHECKED:
|
||||
ret = ShapeLike::isConvex<RawShape>(ShapeLike::getContour(transformedShape()));
|
||||
convexity_ = ret? Convexity::TRUE : Convexity::FALSE;
|
||||
ret = sl::isConvex<RawShape>(sl::getContour(transformedShape()));
|
||||
convexity_ = ret? Convexity::C_TRUE : Convexity::C_FALSE;
|
||||
break;
|
||||
case Convexity::TRUE: ret = true; break;
|
||||
case Convexity::FALSE:;
|
||||
case Convexity::C_TRUE: ret = true; break;
|
||||
case Convexity::C_FALSE:;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
inline bool isHoleConvex(unsigned holeidx) const {
|
||||
inline bool isHoleConvex(unsigned /*holeidx*/) const {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
@ -223,11 +232,11 @@ public:
|
|||
|
||||
/// The number of the outer ring vertices.
|
||||
inline size_t vertexCount() const {
|
||||
return ShapeLike::contourVertexCount(sh_);
|
||||
return sl::contourVertexCount(sh_);
|
||||
}
|
||||
|
||||
inline size_t holeCount() const {
|
||||
return ShapeLike::holeCount(sh_);
|
||||
return sl::holeCount(sh_);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -235,36 +244,39 @@ public:
|
|||
* @param p
|
||||
* @return
|
||||
*/
|
||||
inline bool isPointInside(const Vertex& p)
|
||||
inline bool isPointInside(const Vertex& p) const
|
||||
{
|
||||
return ShapeLike::isInside(p, sh_);
|
||||
return sl::isInside(p, transformedShape());
|
||||
}
|
||||
|
||||
inline bool isInside(const _Item& sh) const
|
||||
{
|
||||
return ShapeLike::isInside(transformedShape(), sh.transformedShape());
|
||||
return sl::isInside(transformedShape(), sh.transformedShape());
|
||||
}
|
||||
|
||||
inline bool isInside(const _Box<TPoint<RawShape>>& box);
|
||||
inline bool isInside(const RawShape& sh) const
|
||||
{
|
||||
return sl::isInside(transformedShape(), sh);
|
||||
}
|
||||
|
||||
inline bool isInside(const _Box<TPoint<RawShape>>& box) const;
|
||||
inline bool isInside(const _Circle<TPoint<RawShape>>& box) const;
|
||||
|
||||
inline void translate(const Vertex& d) BP2D_NOEXCEPT
|
||||
{
|
||||
translation_ += d; has_translation_ = true;
|
||||
tr_cache_valid_ = false;
|
||||
translation(translation() + d);
|
||||
}
|
||||
|
||||
inline void rotate(const Radians& rads) BP2D_NOEXCEPT
|
||||
{
|
||||
rotation_ += rads;
|
||||
has_rotation_ = true;
|
||||
tr_cache_valid_ = false;
|
||||
rotation(rotation() + rads);
|
||||
}
|
||||
|
||||
inline void addOffset(Coord distance) BP2D_NOEXCEPT
|
||||
{
|
||||
offset_distance_ = distance;
|
||||
has_offset_ = true;
|
||||
offset_cache_valid_ = false;
|
||||
invalidateCache();
|
||||
}
|
||||
|
||||
inline void removeOffset() BP2D_NOEXCEPT {
|
||||
|
@ -286,6 +298,8 @@ public:
|
|||
{
|
||||
if(rotation_ != rot) {
|
||||
rotation_ = rot; has_rotation_ = true; tr_cache_valid_ = false;
|
||||
rmt_valid_ = false; lmb_valid_ = false;
|
||||
bb_cache_.valid = false;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -293,6 +307,7 @@ public:
|
|||
{
|
||||
if(translation_ != tr) {
|
||||
translation_ = tr; has_translation_ = true; tr_cache_valid_ = false;
|
||||
bb_cache_.valid = false;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -301,9 +316,10 @@ public:
|
|||
if(tr_cache_valid_) return tr_cache_;
|
||||
|
||||
RawShape cpy = offsettedShape();
|
||||
if(has_rotation_) ShapeLike::rotate(cpy, rotation_);
|
||||
if(has_translation_) ShapeLike::translate(cpy, translation_);
|
||||
if(has_rotation_) sl::rotate(cpy, rotation_);
|
||||
if(has_translation_) sl::translate(cpy, translation_);
|
||||
tr_cache_ = cpy; tr_cache_valid_ = true;
|
||||
rmt_valid_ = false; lmb_valid_ = false;
|
||||
|
||||
return tr_cache_;
|
||||
}
|
||||
|
@ -321,23 +337,53 @@ public:
|
|||
inline void resetTransformation() BP2D_NOEXCEPT
|
||||
{
|
||||
has_translation_ = false; has_rotation_ = false; has_offset_ = false;
|
||||
invalidateCache();
|
||||
}
|
||||
|
||||
inline Box boundingBox() const {
|
||||
return ShapeLike::boundingBox(transformedShape());
|
||||
if(!bb_cache_.valid) {
|
||||
bb_cache_.bb = sl::boundingBox(transformedShape());
|
||||
bb_cache_.tr = {0, 0};
|
||||
bb_cache_.valid = true;
|
||||
}
|
||||
|
||||
auto &bb = bb_cache_.bb; auto &tr = bb_cache_.tr;
|
||||
return {bb.minCorner() + tr, bb.maxCorner() + tr};
|
||||
}
|
||||
|
||||
inline Vertex referenceVertex() const {
|
||||
return rightmostTopVertex();
|
||||
}
|
||||
|
||||
inline Vertex rightmostTopVertex() const {
|
||||
if(!rmt_valid_ || !tr_cache_valid_) { // find max x and max y vertex
|
||||
auto& tsh = transformedShape();
|
||||
rmt_ = std::max_element(sl::cbegin(tsh), sl::cend(tsh), vsort);
|
||||
rmt_valid_ = true;
|
||||
}
|
||||
return *rmt_;
|
||||
}
|
||||
|
||||
inline Vertex leftmostBottomVertex() const {
|
||||
if(!lmb_valid_ || !tr_cache_valid_) { // find min x and min y vertex
|
||||
auto& tsh = transformedShape();
|
||||
lmb_ = std::min_element(sl::cbegin(tsh), sl::cend(tsh), vsort);
|
||||
lmb_valid_ = true;
|
||||
}
|
||||
return *lmb_;
|
||||
}
|
||||
|
||||
//Static methods:
|
||||
|
||||
inline static bool intersects(const _Item& sh1, const _Item& sh2)
|
||||
{
|
||||
return ShapeLike::intersects(sh1.transformedShape(),
|
||||
return sl::intersects(sh1.transformedShape(),
|
||||
sh2.transformedShape());
|
||||
}
|
||||
|
||||
inline static bool touches(const _Item& sh1, const _Item& sh2)
|
||||
{
|
||||
return ShapeLike::touches(sh1.transformedShape(),
|
||||
return sl::touches(sh1.transformedShape(),
|
||||
sh2.transformedShape());
|
||||
}
|
||||
|
||||
|
@ -346,12 +392,11 @@ private:
|
|||
inline const RawShape& offsettedShape() const {
|
||||
if(has_offset_ ) {
|
||||
if(offset_cache_valid_) return offset_cache_;
|
||||
else {
|
||||
offset_cache_ = sh_;
|
||||
ShapeLike::offset(offset_cache_, offset_distance_);
|
||||
offset_cache_valid_ = true;
|
||||
return offset_cache_;
|
||||
}
|
||||
|
||||
offset_cache_ = sh_;
|
||||
sl::offset(offset_cache_, offset_distance_);
|
||||
offset_cache_valid_ = true;
|
||||
return offset_cache_;
|
||||
}
|
||||
return sh_;
|
||||
}
|
||||
|
@ -359,10 +404,23 @@ private:
|
|||
inline void invalidateCache() const BP2D_NOEXCEPT
|
||||
{
|
||||
tr_cache_valid_ = false;
|
||||
lmb_valid_ = false; rmt_valid_ = false;
|
||||
area_cache_valid_ = false;
|
||||
offset_cache_valid_ = false;
|
||||
bb_cache_.valid = false;
|
||||
convexity_ = Convexity::UNCHECKED;
|
||||
}
|
||||
|
||||
static inline bool vsort(const Vertex& v1, const Vertex& v2)
|
||||
{
|
||||
Coord &&x1 = getX(v1), &&x2 = getX(v2);
|
||||
Coord &&y1 = getY(v1), &&y2 = getY(v2);
|
||||
auto diff = y1 - y2;
|
||||
if(std::abs(diff) <= std::numeric_limits<Coord>::epsilon())
|
||||
return x1 < x2;
|
||||
|
||||
return diff < 0;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
|
@ -370,7 +428,6 @@ private:
|
|||
*/
|
||||
template<class RawShape>
|
||||
class _Rectangle: public _Item<RawShape> {
|
||||
RawShape sh_;
|
||||
using _Item<RawShape>::vertex;
|
||||
using TO = Orientation;
|
||||
public:
|
||||
|
@ -415,9 +472,13 @@ public:
|
|||
};
|
||||
|
||||
template<class RawShape>
|
||||
inline bool _Item<RawShape>::isInside(const _Box<TPoint<RawShape>>& box) {
|
||||
_Rectangle<RawShape> rect(box.width(), box.height());
|
||||
return _Item<RawShape>::isInside(rect);
|
||||
inline bool _Item<RawShape>::isInside(const _Box<TPoint<RawShape>>& box) const {
|
||||
return ShapeLike::isInside<RawShape>(boundingBox(), box);
|
||||
}
|
||||
|
||||
template<class RawShape> inline bool
|
||||
_Item<RawShape>::isInside(const _Circle<TPoint<RawShape>>& circ) const {
|
||||
return ShapeLike::isInside<RawShape>(transformedShape(), circ);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -874,9 +935,8 @@ private:
|
|||
|
||||
Radians findBestRotation(Item& item) {
|
||||
opt::StopCriteria stopcr;
|
||||
stopcr.stoplimit = 0.01;
|
||||
stopcr.absolute_score_difference = 0.01;
|
||||
stopcr.max_iterations = 10000;
|
||||
stopcr.type = opt::StopLimitType::RELATIVE;
|
||||
opt::TOptimizer<opt::Method::G_GENETIC> solver(stopcr);
|
||||
|
||||
auto orig_rot = item.rotation();
|
||||
|
@ -910,7 +970,6 @@ private:
|
|||
if(min_obj_distance_ > 0) std::for_each(from, to, [](Item& item) {
|
||||
item.removeOffset();
|
||||
});
|
||||
|
||||
}
|
||||
};
|
||||
|
||||
|
|
227
xs/src/libnest2d/libnest2d/metaloop.hpp
Normal file
227
xs/src/libnest2d/libnest2d/metaloop.hpp
Normal file
|
@ -0,0 +1,227 @@
|
|||
#ifndef METALOOP_HPP
|
||||
#define METALOOP_HPP
|
||||
|
||||
#include "common.hpp"
|
||||
#include <tuple>
|
||||
#include <functional>
|
||||
|
||||
namespace libnest2d {
|
||||
|
||||
/* ************************************************************************** */
|
||||
/* C++14 std::index_sequence implementation: */
|
||||
/* ************************************************************************** */
|
||||
|
||||
/**
|
||||
* \brief C++11 conformant implementation of the index_sequence type from C++14
|
||||
*/
|
||||
template<size_t...Ints> struct index_sequence {
|
||||
using value_type = size_t;
|
||||
BP2D_CONSTEXPR value_type size() const { return sizeof...(Ints); }
|
||||
};
|
||||
|
||||
// A Help structure to generate the integer list
|
||||
template<size_t...Nseq> struct genSeq;
|
||||
|
||||
// Recursive template to generate the list
|
||||
template<size_t I, size_t...Nseq> struct genSeq<I, Nseq...> {
|
||||
// Type will contain a genSeq with Nseq appended by one element
|
||||
using Type = typename genSeq< I - 1, I - 1, Nseq...>::Type;
|
||||
};
|
||||
|
||||
// Terminating recursion
|
||||
template <size_t ... Nseq> struct genSeq<0, Nseq...> {
|
||||
// If I is zero, Type will contain index_sequence with the fuly generated
|
||||
// integer list.
|
||||
using Type = index_sequence<Nseq...>;
|
||||
};
|
||||
|
||||
/// Helper alias to make an index sequence from 0 to N
|
||||
template<size_t N> using make_index_sequence = typename genSeq<N>::Type;
|
||||
|
||||
/// Helper alias to make an index sequence for a parameter pack
|
||||
template<class...Args>
|
||||
using index_sequence_for = make_index_sequence<sizeof...(Args)>;
|
||||
|
||||
|
||||
/* ************************************************************************** */
|
||||
|
||||
namespace opt {
|
||||
|
||||
using std::forward;
|
||||
using std::tuple;
|
||||
using std::get;
|
||||
using std::tuple_element;
|
||||
|
||||
/**
|
||||
* @brief Helper class to be able to loop over a parameter pack's elements.
|
||||
*/
|
||||
class metaloop {
|
||||
|
||||
// The implementation is based on partial struct template specializations.
|
||||
// Basically we need a template type that is callable and takes an integer
|
||||
// non-type template parameter which can be used to implement recursive calls.
|
||||
//
|
||||
// C++11 will not allow the usage of a plain template function that is why we
|
||||
// use struct with overloaded call operator. At the same time C++11 prohibits
|
||||
// partial template specialization with a non type parameter such as int. We
|
||||
// need to wrap that in a type (see metaloop::Int).
|
||||
|
||||
/*
|
||||
* A helper alias to create integer values wrapped as a type. It is nessecary
|
||||
* because a non type template parameter (such as int) would be prohibited in
|
||||
* a partial specialization. Also for the same reason we have to use a class
|
||||
* _Metaloop instead of a simple function as a functor. A function cannot be
|
||||
* partially specialized in a way that is neccesary for this trick.
|
||||
*/
|
||||
template<int N> using Int = std::integral_constant<int, N>;
|
||||
|
||||
/*
|
||||
* Helper class to implement in-place functors.
|
||||
*
|
||||
* We want to be able to use inline functors like a lambda to keep the code
|
||||
* as clear as possible.
|
||||
*/
|
||||
template<int N, class Fn> class MapFn {
|
||||
Fn&& fn_;
|
||||
public:
|
||||
|
||||
// It takes the real functor that can be specified in-place but only
|
||||
// with C++14 because the second parameter's type will depend on the
|
||||
// type of the parameter pack element that is processed. In C++14 we can
|
||||
// specify this second parameter type as auto in the lamda parameter list.
|
||||
inline MapFn(Fn&& fn): fn_(forward<Fn>(fn)) {}
|
||||
|
||||
template<class T> void operator ()(T&& pack_element) {
|
||||
// We provide the index as the first parameter and the pack (or tuple)
|
||||
// element as the second parameter to the functor.
|
||||
fn_(N, forward<T>(pack_element));
|
||||
}
|
||||
};
|
||||
|
||||
/*
|
||||
* Implementation of the template loop trick.
|
||||
* We create a mechanism for looping over a parameter pack in compile time.
|
||||
* \tparam Idx is the loop index which will be decremented at each recursion.
|
||||
* \tparam Args The parameter pack that will be processed.
|
||||
*
|
||||
*/
|
||||
template <typename Idx, class...Args>
|
||||
class _MetaLoop {};
|
||||
|
||||
// Implementation for the first element of Args...
|
||||
template <class...Args>
|
||||
class _MetaLoop<Int<0>, Args...> {
|
||||
public:
|
||||
|
||||
const static BP2D_CONSTEXPR int N = 0;
|
||||
const static BP2D_CONSTEXPR int ARGNUM = sizeof...(Args)-1;
|
||||
|
||||
template<class Tup, class Fn>
|
||||
void run( Tup&& valtup, Fn&& fn) {
|
||||
MapFn<ARGNUM-N, Fn> {forward<Fn>(fn)} (get<ARGNUM-N>(valtup));
|
||||
}
|
||||
};
|
||||
|
||||
// Implementation for the N-th element of Args...
|
||||
template <int N, class...Args>
|
||||
class _MetaLoop<Int<N>, Args...> {
|
||||
public:
|
||||
|
||||
const static BP2D_CONSTEXPR int ARGNUM = sizeof...(Args)-1;
|
||||
|
||||
template<class Tup, class Fn>
|
||||
void run(Tup&& valtup, Fn&& fn) {
|
||||
MapFn<ARGNUM-N, Fn> {forward<Fn>(fn)} (std::get<ARGNUM-N>(valtup));
|
||||
|
||||
// Recursive call to process the next element of Args
|
||||
_MetaLoop<Int<N-1>, Args...> ().run(forward<Tup>(valtup),
|
||||
forward<Fn>(fn));
|
||||
}
|
||||
};
|
||||
|
||||
/*
|
||||
* Instantiation: We must instantiate the template with the last index because
|
||||
* the generalized version calls the decremented instantiations recursively.
|
||||
* Once the instantiation with the first index is called, the terminating
|
||||
* version of run is called which does not call itself anymore.
|
||||
*
|
||||
* If you are utterly annoyed, at least you have learned a super crazy
|
||||
* functional metaprogramming pattern.
|
||||
*/
|
||||
template<class...Args>
|
||||
using MetaLoop = _MetaLoop<Int<sizeof...(Args)-1>, Args...>;
|
||||
|
||||
public:
|
||||
|
||||
/**
|
||||
* \brief The final usable function template.
|
||||
*
|
||||
* This is similar to what varags was on C but in compile time C++11.
|
||||
* You can call:
|
||||
* apply(<the mapping function>, <arbitrary number of arguments of any type>);
|
||||
* For example:
|
||||
*
|
||||
* struct mapfunc {
|
||||
* template<class T> void operator()(int N, T&& element) {
|
||||
* std::cout << "The value of the parameter "<< N <<": "
|
||||
* << element << std::endl;
|
||||
* }
|
||||
* };
|
||||
*
|
||||
* apply(mapfunc(), 'a', 10, 151.545);
|
||||
*
|
||||
* C++14:
|
||||
* apply([](int N, auto&& element){
|
||||
* std::cout << "The value of the parameter "<< N <<": "
|
||||
* << element << std::endl;
|
||||
* }, 'a', 10, 151.545);
|
||||
*
|
||||
* This yields the output:
|
||||
* The value of the parameter 0: a
|
||||
* The value of the parameter 1: 10
|
||||
* The value of the parameter 2: 151.545
|
||||
*
|
||||
* As an addition, the function can be called with a tuple as the second
|
||||
* parameter holding the arguments instead of a parameter pack.
|
||||
*
|
||||
*/
|
||||
template<class...Args, class Fn>
|
||||
inline static void apply(Fn&& fn, Args&&...args) {
|
||||
MetaLoop<Args...>().run(tuple<Args&&...>(forward<Args>(args)...),
|
||||
forward<Fn>(fn));
|
||||
}
|
||||
|
||||
/// The version of apply with a tuple rvalue reference.
|
||||
template<class...Args, class Fn>
|
||||
inline static void apply(Fn&& fn, tuple<Args...>&& tup) {
|
||||
MetaLoop<Args...>().run(std::move(tup), forward<Fn>(fn));
|
||||
}
|
||||
|
||||
/// The version of apply with a tuple lvalue reference.
|
||||
template<class...Args, class Fn>
|
||||
inline static void apply(Fn&& fn, tuple<Args...>& tup) {
|
||||
MetaLoop<Args...>().run(tup, forward<Fn>(fn));
|
||||
}
|
||||
|
||||
/// The version of apply with a tuple const reference.
|
||||
template<class...Args, class Fn>
|
||||
inline static void apply(Fn&& fn, const tuple<Args...>& tup) {
|
||||
MetaLoop<Args...>().run(tup, forward<Fn>(fn));
|
||||
}
|
||||
|
||||
/**
|
||||
* Call a function with its arguments encapsualted in a tuple.
|
||||
*/
|
||||
template<class Fn, class Tup, std::size_t...Is>
|
||||
inline static auto
|
||||
callFunWithTuple(Fn&& fn, Tup&& tup, index_sequence<Is...>) ->
|
||||
decltype(fn(std::get<Is>(tup)...))
|
||||
{
|
||||
return fn(std::get<Is>(tup)...);
|
||||
}
|
||||
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
#endif // METALOOP_HPP
|
|
@ -10,8 +10,7 @@ namespace libnest2d { namespace opt {
|
|||
|
||||
using std::forward;
|
||||
using std::tuple;
|
||||
using std::get;
|
||||
using std::tuple_element;
|
||||
using std::make_tuple;
|
||||
|
||||
/// A Type trait for upper and lower limit of a numeric type.
|
||||
template<class T, class B = void >
|
||||
|
@ -51,176 +50,7 @@ inline Bound<T> bound(const T& min, const T& max) { return Bound<T>(min, max); }
|
|||
template<class...Args> using Input = tuple<Args...>;
|
||||
|
||||
template<class...Args>
|
||||
inline tuple<Args...> initvals(Args...args) { return std::make_tuple(args...); }
|
||||
|
||||
/**
|
||||
* @brief Helper class to be able to loop over a parameter pack's elements.
|
||||
*/
|
||||
class metaloop {
|
||||
// The implementation is based on partial struct template specializations.
|
||||
// Basically we need a template type that is callable and takes an integer
|
||||
// non-type template parameter which can be used to implement recursive calls.
|
||||
//
|
||||
// C++11 will not allow the usage of a plain template function that is why we
|
||||
// use struct with overloaded call operator. At the same time C++11 prohibits
|
||||
// partial template specialization with a non type parameter such as int. We
|
||||
// need to wrap that in a type (see metaloop::Int).
|
||||
|
||||
/*
|
||||
* A helper alias to create integer values wrapped as a type. It is nessecary
|
||||
* because a non type template parameter (such as int) would be prohibited in
|
||||
* a partial specialization. Also for the same reason we have to use a class
|
||||
* _Metaloop instead of a simple function as a functor. A function cannot be
|
||||
* partially specialized in a way that is neccesary for this trick.
|
||||
*/
|
||||
template<int N> using Int = std::integral_constant<int, N>;
|
||||
|
||||
/*
|
||||
* Helper class to implement in-place functors.
|
||||
*
|
||||
* We want to be able to use inline functors like a lambda to keep the code
|
||||
* as clear as possible.
|
||||
*/
|
||||
template<int N, class Fn> class MapFn {
|
||||
Fn&& fn_;
|
||||
public:
|
||||
|
||||
// It takes the real functor that can be specified in-place but only
|
||||
// with C++14 because the second parameter's type will depend on the
|
||||
// type of the parameter pack element that is processed. In C++14 we can
|
||||
// specify this second parameter type as auto in the lamda parameter list.
|
||||
inline MapFn(Fn&& fn): fn_(forward<Fn>(fn)) {}
|
||||
|
||||
template<class T> void operator ()(T&& pack_element) {
|
||||
// We provide the index as the first parameter and the pack (or tuple)
|
||||
// element as the second parameter to the functor.
|
||||
fn_(N, forward<T>(pack_element));
|
||||
}
|
||||
};
|
||||
|
||||
/*
|
||||
* Implementation of the template loop trick.
|
||||
* We create a mechanism for looping over a parameter pack in compile time.
|
||||
* \tparam Idx is the loop index which will be decremented at each recursion.
|
||||
* \tparam Args The parameter pack that will be processed.
|
||||
*
|
||||
*/
|
||||
template <typename Idx, class...Args>
|
||||
class _MetaLoop {};
|
||||
|
||||
// Implementation for the first element of Args...
|
||||
template <class...Args>
|
||||
class _MetaLoop<Int<0>, Args...> {
|
||||
public:
|
||||
|
||||
const static BP2D_CONSTEXPR int N = 0;
|
||||
const static BP2D_CONSTEXPR int ARGNUM = sizeof...(Args)-1;
|
||||
|
||||
template<class Tup, class Fn>
|
||||
void run( Tup&& valtup, Fn&& fn) {
|
||||
MapFn<ARGNUM-N, Fn> {forward<Fn>(fn)} (get<ARGNUM-N>(valtup));
|
||||
}
|
||||
};
|
||||
|
||||
// Implementation for the N-th element of Args...
|
||||
template <int N, class...Args>
|
||||
class _MetaLoop<Int<N>, Args...> {
|
||||
public:
|
||||
|
||||
const static BP2D_CONSTEXPR int ARGNUM = sizeof...(Args)-1;
|
||||
|
||||
template<class Tup, class Fn>
|
||||
void run(Tup&& valtup, Fn&& fn) {
|
||||
MapFn<ARGNUM-N, Fn> {forward<Fn>(fn)} (std::get<ARGNUM-N>(valtup));
|
||||
|
||||
// Recursive call to process the next element of Args
|
||||
_MetaLoop<Int<N-1>, Args...> ().run(forward<Tup>(valtup),
|
||||
forward<Fn>(fn));
|
||||
}
|
||||
};
|
||||
|
||||
/*
|
||||
* Instantiation: We must instantiate the template with the last index because
|
||||
* the generalized version calls the decremented instantiations recursively.
|
||||
* Once the instantiation with the first index is called, the terminating
|
||||
* version of run is called which does not call itself anymore.
|
||||
*
|
||||
* If you are utterly annoyed, at least you have learned a super crazy
|
||||
* functional metaprogramming pattern.
|
||||
*/
|
||||
template<class...Args>
|
||||
using MetaLoop = _MetaLoop<Int<sizeof...(Args)-1>, Args...>;
|
||||
|
||||
public:
|
||||
|
||||
/**
|
||||
* \brief The final usable function template.
|
||||
*
|
||||
* This is similar to what varags was on C but in compile time C++11.
|
||||
* You can call:
|
||||
* apply(<the mapping function>, <arbitrary number of arguments of any type>);
|
||||
* For example:
|
||||
*
|
||||
* struct mapfunc {
|
||||
* template<class T> void operator()(int N, T&& element) {
|
||||
* std::cout << "The value of the parameter "<< N <<": "
|
||||
* << element << std::endl;
|
||||
* }
|
||||
* };
|
||||
*
|
||||
* apply(mapfunc(), 'a', 10, 151.545);
|
||||
*
|
||||
* C++14:
|
||||
* apply([](int N, auto&& element){
|
||||
* std::cout << "The value of the parameter "<< N <<": "
|
||||
* << element << std::endl;
|
||||
* }, 'a', 10, 151.545);
|
||||
*
|
||||
* This yields the output:
|
||||
* The value of the parameter 0: a
|
||||
* The value of the parameter 1: 10
|
||||
* The value of the parameter 2: 151.545
|
||||
*
|
||||
* As an addition, the function can be called with a tuple as the second
|
||||
* parameter holding the arguments instead of a parameter pack.
|
||||
*
|
||||
*/
|
||||
template<class...Args, class Fn>
|
||||
inline static void apply(Fn&& fn, Args&&...args) {
|
||||
MetaLoop<Args...>().run(tuple<Args&&...>(forward<Args>(args)...),
|
||||
forward<Fn>(fn));
|
||||
}
|
||||
|
||||
/// The version of apply with a tuple rvalue reference.
|
||||
template<class...Args, class Fn>
|
||||
inline static void apply(Fn&& fn, tuple<Args...>&& tup) {
|
||||
MetaLoop<Args...>().run(std::move(tup), forward<Fn>(fn));
|
||||
}
|
||||
|
||||
/// The version of apply with a tuple lvalue reference.
|
||||
template<class...Args, class Fn>
|
||||
inline static void apply(Fn&& fn, tuple<Args...>& tup) {
|
||||
MetaLoop<Args...>().run(tup, forward<Fn>(fn));
|
||||
}
|
||||
|
||||
/// The version of apply with a tuple const reference.
|
||||
template<class...Args, class Fn>
|
||||
inline static void apply(Fn&& fn, const tuple<Args...>& tup) {
|
||||
MetaLoop<Args...>().run(tup, forward<Fn>(fn));
|
||||
}
|
||||
|
||||
/**
|
||||
* Call a function with its arguments encapsualted in a tuple.
|
||||
*/
|
||||
template<class Fn, class Tup, std::size_t...Is>
|
||||
inline static auto
|
||||
callFunWithTuple(Fn&& fn, Tup&& tup, index_sequence<Is...>) ->
|
||||
decltype(fn(std::get<Is>(tup)...))
|
||||
{
|
||||
return fn(std::get<Is>(tup)...);
|
||||
}
|
||||
|
||||
};
|
||||
inline tuple<Args...> initvals(Args...args) { return make_tuple(args...); }
|
||||
|
||||
/**
|
||||
* @brief Specific optimization methods for which a default optimizer
|
||||
|
@ -257,29 +87,20 @@ enum ResultCodes {
|
|||
template<class...Args>
|
||||
struct Result {
|
||||
ResultCodes resultcode;
|
||||
std::tuple<Args...> optimum;
|
||||
tuple<Args...> optimum;
|
||||
double score;
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief The stop limit can be specified as the absolute error or as the
|
||||
* relative error, just like in nlopt.
|
||||
*/
|
||||
enum class StopLimitType {
|
||||
ABSOLUTE,
|
||||
RELATIVE
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief A type for specifying the stop criteria.
|
||||
*/
|
||||
struct StopCriteria {
|
||||
|
||||
/// Relative or absolute termination error
|
||||
StopLimitType type = StopLimitType::RELATIVE;
|
||||
/// If the absolute value difference between two scores.
|
||||
double absolute_score_difference = std::nan("");
|
||||
|
||||
/// The error value that is interpredted depending on the type property.
|
||||
double stoplimit = 0.0001;
|
||||
/// If the relative value difference between two scores.
|
||||
double relative_score_difference = std::nan("");
|
||||
|
||||
unsigned max_iterations = 0;
|
||||
};
|
||||
|
@ -310,11 +131,11 @@ public:
|
|||
* \return Returns a Result<Args...> structure.
|
||||
* An example call would be:
|
||||
* auto result = opt.optimize_min(
|
||||
* [](std::tuple<double> x) // object function
|
||||
* [](tuple<double> x) // object function
|
||||
* {
|
||||
* return std::pow(std::get<0>(x), 2);
|
||||
* },
|
||||
* std::make_tuple(-0.5), // initial value
|
||||
* make_tuple(-0.5), // initial value
|
||||
* {-1.0, 1.0} // search space bounds
|
||||
* );
|
||||
*/
|
||||
|
@ -390,10 +211,14 @@ public:
|
|||
static_assert(always_false<T>::value, "Optimizer unimplemented!");
|
||||
}
|
||||
|
||||
DummyOptimizer(const StopCriteria&) {
|
||||
static_assert(always_false<T>::value, "Optimizer unimplemented!");
|
||||
}
|
||||
|
||||
template<class Func, class...Args>
|
||||
Result<Args...> optimize(Func&& func,
|
||||
std::tuple<Args...> initvals,
|
||||
Bound<Args>... args)
|
||||
Result<Args...> optimize(Func&& /*func*/,
|
||||
tuple<Args...> /*initvals*/,
|
||||
Bound<Args>... /*args*/)
|
||||
{
|
||||
return Result<Args...>();
|
||||
}
|
||||
|
|
|
@ -1,15 +1,25 @@
|
|||
#ifndef NLOPT_BOILERPLATE_HPP
|
||||
#define NLOPT_BOILERPLATE_HPP
|
||||
|
||||
#ifdef _MSC_VER
|
||||
#pragma warning(push)
|
||||
#pragma warning(disable: 4244)
|
||||
#pragma warning(disable: 4267)
|
||||
#endif
|
||||
#include <nlopt.hpp>
|
||||
#ifdef _MSC_VER
|
||||
#pragma warning(pop)
|
||||
#endif
|
||||
|
||||
#include <libnest2d/optimizer.hpp>
|
||||
#include <cassert>
|
||||
#include "libnest2d/metaloop.hpp"
|
||||
|
||||
#include <utility>
|
||||
|
||||
namespace libnest2d { namespace opt {
|
||||
|
||||
nlopt::algorithm method2nloptAlg(Method m) {
|
||||
inline nlopt::algorithm method2nloptAlg(Method m) {
|
||||
|
||||
switch(m) {
|
||||
case Method::L_SIMPLEX: return nlopt::LN_NELDERMEAD;
|
||||
|
@ -87,7 +97,7 @@ protected:
|
|||
|
||||
template<class Fn, class...Args>
|
||||
static double optfunc(const std::vector<double>& params,
|
||||
std::vector<double>& grad,
|
||||
std::vector<double>& /*grad*/,
|
||||
void *data)
|
||||
{
|
||||
auto fnptr = static_cast<remove_ref_t<Fn>*>(data);
|
||||
|
@ -132,12 +142,10 @@ protected:
|
|||
default: ;
|
||||
}
|
||||
|
||||
switch(this->stopcr_.type) {
|
||||
case StopLimitType::ABSOLUTE:
|
||||
opt_.set_ftol_abs(stopcr_.stoplimit); break;
|
||||
case StopLimitType::RELATIVE:
|
||||
opt_.set_ftol_rel(stopcr_.stoplimit); break;
|
||||
}
|
||||
auto abs_diff = stopcr_.absolute_score_difference;
|
||||
auto rel_diff = stopcr_.relative_score_difference;
|
||||
if(!std::isnan(abs_diff)) opt_.set_ftol_abs(abs_diff);
|
||||
if(!std::isnan(rel_diff)) opt_.set_ftol_rel(rel_diff);
|
||||
|
||||
if(this->stopcr_.max_iterations > 0)
|
||||
opt_.set_maxeval(this->stopcr_.max_iterations );
|
||||
|
|
|
@ -6,6 +6,10 @@
|
|||
#endif
|
||||
#include "placer_boilerplate.hpp"
|
||||
#include "../geometry_traits_nfp.hpp"
|
||||
#include "libnest2d/optimizer.hpp"
|
||||
#include <cassert>
|
||||
|
||||
#include "tools/svgtools.hpp"
|
||||
|
||||
namespace libnest2d { namespace strategies {
|
||||
|
||||
|
@ -20,15 +24,60 @@ struct NfpPConfig {
|
|||
TOP_RIGHT,
|
||||
};
|
||||
|
||||
/// Which angles to try out for better results
|
||||
/// Which angles to try out for better results.
|
||||
std::vector<Radians> rotations;
|
||||
|
||||
/// Where to align the resulting packed pile
|
||||
/// Where to align the resulting packed pile.
|
||||
Alignment alignment;
|
||||
|
||||
/// Where to start putting objects in the bin.
|
||||
Alignment starting_point;
|
||||
|
||||
std::function<double(const Nfp::Shapes<RawShape>&, double, double, double)>
|
||||
/**
|
||||
* @brief A function object representing the fitting function in the
|
||||
* placement optimization process. (Optional)
|
||||
*
|
||||
* This is the most versatile tool to configure the placer. The fitting
|
||||
* function is evaluated many times when a new item is being placed into the
|
||||
* bin. The output should be a rated score of the new item's position.
|
||||
*
|
||||
* This is not a mandatory option as there is a default fitting function
|
||||
* that will optimize for the best pack efficiency. With a custom fitting
|
||||
* function you can e.g. influence the shape of the arranged pile.
|
||||
*
|
||||
* \param shapes The first parameter is a container with all the placed
|
||||
* polygons excluding the current candidate. You can calculate a bounding
|
||||
* box or convex hull on this pile of polygons without the candidate item
|
||||
* or push back the candidate item into the container and then calculate
|
||||
* some features.
|
||||
*
|
||||
* \param item The second parameter is the candidate item.
|
||||
*
|
||||
* \param occupied_area The third parameter is the sum of areas of the
|
||||
* items in the first parameter so you don't have to iterate through them
|
||||
* if you only need their area.
|
||||
*
|
||||
* \param norm A norming factor for physical dimensions. E.g. if your score
|
||||
* is the distance between the item and the bin center, you should divide
|
||||
* that distance with the norming factor. If the score is an area than
|
||||
* divide it with the square of the norming factor. Imagine it as a unit of
|
||||
* distance.
|
||||
*
|
||||
* \param penality The fifth parameter is the amount of minimum penality if
|
||||
* the arranged pile would't fit into the bin. You can use the wouldFit()
|
||||
* function to check this. Note that the pile can be outside the bin's
|
||||
* boundaries while the placement algorithm is running. Your job is only to
|
||||
* check if the pile could be translated into a position in the bin where
|
||||
* all the items would be inside. For a box shaped bin you can use the
|
||||
* pile's bounding box to check whether it's width and height is small
|
||||
* enough. If the pile would not fit, you have to make sure that the
|
||||
* resulting score will be higher then the penality value. A good solution
|
||||
* would be to set score = 2*penality-score in case the pile wouldn't fit
|
||||
* into the bin.
|
||||
*
|
||||
*/
|
||||
std::function<double(Nfp::Shapes<RawShape>&, const _Item<RawShape>&,
|
||||
double, double, double)>
|
||||
object_function;
|
||||
|
||||
/**
|
||||
|
@ -38,11 +87,30 @@ struct NfpPConfig {
|
|||
*/
|
||||
float accuracy = 1.0;
|
||||
|
||||
/**
|
||||
* @brief If you want to see items inside other item's holes, you have to
|
||||
* turn this switch on.
|
||||
*
|
||||
* This will only work if a suitable nfp implementation is provided.
|
||||
* The library has no such implementation right now.
|
||||
*/
|
||||
bool explore_holes = false;
|
||||
|
||||
NfpPConfig(): rotations({0.0, Pi/2.0, Pi, 3*Pi/2}),
|
||||
alignment(Alignment::CENTER), starting_point(Alignment::CENTER) {}
|
||||
};
|
||||
|
||||
// A class for getting a point on the circumference of the polygon (in log time)
|
||||
/**
|
||||
* A class for getting a point on the circumference of the polygon (in log time)
|
||||
*
|
||||
* This is a transformation of the provided polygon to be able to pinpoint
|
||||
* locations on the circumference. The optimizer will pass a floating point
|
||||
* value e.g. within <0,1> and we have to transform this value quickly into a
|
||||
* coordinate on the circumference. By definition 0 should yield the first
|
||||
* vertex and 1.0 would be the last (which should coincide with first).
|
||||
*
|
||||
* We also have to make this work for the holes of the captured polygon.
|
||||
*/
|
||||
template<class RawShape> class EdgeCache {
|
||||
using Vertex = TPoint<RawShape>;
|
||||
using Coord = TCoord<Vertex>;
|
||||
|
@ -57,6 +125,8 @@ template<class RawShape> class EdgeCache {
|
|||
|
||||
std::vector<ContourCache> holes_;
|
||||
|
||||
double accuracy_ = 1.0;
|
||||
|
||||
void createCache(const RawShape& sh) {
|
||||
{ // For the contour
|
||||
auto first = ShapeLike::cbegin(sh);
|
||||
|
@ -90,21 +160,44 @@ template<class RawShape> class EdgeCache {
|
|||
}
|
||||
}
|
||||
|
||||
size_t stride(const size_t N) const {
|
||||
using std::ceil;
|
||||
using std::round;
|
||||
using std::pow;
|
||||
|
||||
return static_cast<Coord>(
|
||||
std::round(N/std::pow(N, std::pow(accuracy_, 1.0/3.0)))
|
||||
);
|
||||
}
|
||||
|
||||
void fetchCorners() const {
|
||||
if(!contour_.corners.empty()) return;
|
||||
|
||||
// TODO Accuracy
|
||||
contour_.corners = contour_.distances;
|
||||
for(auto& d : contour_.corners) d /= contour_.full_distance;
|
||||
const auto N = contour_.distances.size();
|
||||
const auto S = stride(N);
|
||||
|
||||
contour_.corners.reserve(N / S + 1);
|
||||
auto N_1 = N-1;
|
||||
contour_.corners.emplace_back(0.0);
|
||||
for(size_t i = 0; i < N_1; i += S) {
|
||||
contour_.corners.emplace_back(
|
||||
contour_.distances.at(i) / contour_.full_distance);
|
||||
}
|
||||
}
|
||||
|
||||
void fetchHoleCorners(unsigned hidx) const {
|
||||
auto& hc = holes_[hidx];
|
||||
if(!hc.corners.empty()) return;
|
||||
|
||||
// TODO Accuracy
|
||||
hc.corners = hc.distances;
|
||||
for(auto& d : hc.corners) d /= hc.full_distance;
|
||||
const auto N = hc.distances.size();
|
||||
const auto S = stride(N);
|
||||
auto N_1 = N-1;
|
||||
hc.corners.reserve(N / S + 1);
|
||||
hc.corners.emplace_back(0.0);
|
||||
for(size_t i = 0; i < N_1; i += S) {
|
||||
hc.corners.emplace_back(
|
||||
hc.distances.at(i) / hc.full_distance);
|
||||
}
|
||||
}
|
||||
|
||||
inline Vertex coords(const ContourCache& cache, double distance) const {
|
||||
|
@ -150,6 +243,9 @@ public:
|
|||
createCache(sh);
|
||||
}
|
||||
|
||||
/// Resolution of returned corners. The stride is derived from this value.
|
||||
void accuracy(double a /* within <0.0, 1.0>*/) { accuracy_ = a; }
|
||||
|
||||
/**
|
||||
* @brief Get a point on the circumference of a polygon.
|
||||
* @param distance A relative distance from the starting point to the end.
|
||||
|
@ -176,24 +272,64 @@ public:
|
|||
return holes_[hidx].full_distance;
|
||||
}
|
||||
|
||||
/// Get the normalized distance values for each vertex
|
||||
inline const std::vector<double>& corners() const BP2D_NOEXCEPT {
|
||||
fetchCorners();
|
||||
return contour_.corners;
|
||||
}
|
||||
|
||||
/// corners for a specific hole
|
||||
inline const std::vector<double>&
|
||||
corners(unsigned holeidx) const BP2D_NOEXCEPT {
|
||||
fetchHoleCorners(holeidx);
|
||||
return holes_[holeidx].corners;
|
||||
}
|
||||
|
||||
inline unsigned holeCount() const BP2D_NOEXCEPT { return holes_.size(); }
|
||||
/// The number of holes in the abstracted polygon
|
||||
inline size_t holeCount() const BP2D_NOEXCEPT { return holes_.size(); }
|
||||
|
||||
};
|
||||
|
||||
template<NfpLevel lvl>
|
||||
struct Lvl { static const NfpLevel value = lvl; };
|
||||
|
||||
template<class RawShape>
|
||||
inline void correctNfpPosition(Nfp::NfpResult<RawShape>& nfp,
|
||||
const _Item<RawShape>& stationary,
|
||||
const _Item<RawShape>& orbiter)
|
||||
{
|
||||
// The provided nfp is somewhere in the dark. We need to get it
|
||||
// to the right position around the stationary shape.
|
||||
// This is done by choosing the leftmost lowest vertex of the
|
||||
// orbiting polygon to be touched with the rightmost upper
|
||||
// vertex of the stationary polygon. In this configuration, the
|
||||
// reference vertex of the orbiting polygon (which can be dragged around
|
||||
// the nfp) will be its rightmost upper vertex that coincides with the
|
||||
// rightmost upper vertex of the nfp. No proof provided other than Jonas
|
||||
// Lindmark's reasoning about the reference vertex of nfp in his thesis
|
||||
// ("No fit polygon problem" - section 2.1.9)
|
||||
|
||||
auto touch_sh = stationary.rightmostTopVertex();
|
||||
auto touch_other = orbiter.leftmostBottomVertex();
|
||||
auto dtouch = touch_sh - touch_other;
|
||||
auto top_other = orbiter.rightmostTopVertex() + dtouch;
|
||||
auto dnfp = top_other - nfp.second; // nfp.second is the nfp reference point
|
||||
ShapeLike::translate(nfp.first, dnfp);
|
||||
}
|
||||
|
||||
template<class RawShape>
|
||||
inline void correctNfpPosition(Nfp::NfpResult<RawShape>& nfp,
|
||||
const RawShape& stationary,
|
||||
const _Item<RawShape>& orbiter)
|
||||
{
|
||||
auto touch_sh = Nfp::rightmostUpVertex(stationary);
|
||||
auto touch_other = orbiter.leftmostBottomVertex();
|
||||
auto dtouch = touch_sh - touch_other;
|
||||
auto top_other = orbiter.rightmostTopVertex() + dtouch;
|
||||
auto dnfp = top_other - nfp.second;
|
||||
ShapeLike::translate(nfp.first, dnfp);
|
||||
}
|
||||
|
||||
template<class RawShape, class Container>
|
||||
Nfp::Shapes<RawShape> nfp( const Container& polygons,
|
||||
const _Item<RawShape>& trsh,
|
||||
|
@ -203,18 +339,35 @@ Nfp::Shapes<RawShape> nfp( const Container& polygons,
|
|||
|
||||
Nfp::Shapes<RawShape> nfps;
|
||||
|
||||
//int pi = 0;
|
||||
for(Item& sh : polygons) {
|
||||
auto subnfp = Nfp::noFitPolygon<NfpLevel::CONVEX_ONLY>(
|
||||
sh.transformedShape(), trsh.transformedShape());
|
||||
auto subnfp_r = Nfp::noFitPolygon<NfpLevel::CONVEX_ONLY>(
|
||||
sh.transformedShape(), trsh.transformedShape());
|
||||
#ifndef NDEBUG
|
||||
auto vv = ShapeLike::isValid(sh.transformedShape());
|
||||
assert(vv.first);
|
||||
|
||||
auto vnfp = ShapeLike::isValid(subnfp);
|
||||
auto vnfp = ShapeLike::isValid(subnfp_r.first);
|
||||
assert(vnfp.first);
|
||||
#endif
|
||||
|
||||
nfps = Nfp::merge(nfps, subnfp);
|
||||
correctNfpPosition(subnfp_r, sh, trsh);
|
||||
|
||||
nfps = Nfp::merge(nfps, subnfp_r.first);
|
||||
|
||||
// double SCALE = 1000000;
|
||||
// using SVGWriter = svg::SVGWriter<RawShape>;
|
||||
// SVGWriter::Config conf;
|
||||
// conf.mm_in_coord_units = SCALE;
|
||||
// SVGWriter svgw(conf);
|
||||
// Box bin(250*SCALE, 210*SCALE);
|
||||
// svgw.setSize(bin);
|
||||
// for(int i = 0; i <= pi; i++) svgw.writeItem(polygons[i]);
|
||||
// svgw.writeItem(trsh);
|
||||
//// svgw.writeItem(Item(subnfp_r.first));
|
||||
// for(auto& n : nfps) svgw.writeItem(Item(n));
|
||||
// svgw.save("nfpout");
|
||||
// pi++;
|
||||
}
|
||||
|
||||
return nfps;
|
||||
|
@ -227,50 +380,73 @@ Nfp::Shapes<RawShape> nfp( const Container& polygons,
|
|||
{
|
||||
using Item = _Item<RawShape>;
|
||||
|
||||
Nfp::Shapes<RawShape> nfps, stationary;
|
||||
Nfp::Shapes<RawShape> nfps;
|
||||
|
||||
auto& orb = trsh.transformedShape();
|
||||
bool orbconvex = trsh.isContourConvex();
|
||||
|
||||
for(Item& sh : polygons) {
|
||||
stationary = Nfp::merge(stationary, sh.transformedShape());
|
||||
}
|
||||
Nfp::NfpResult<RawShape> subnfp;
|
||||
auto& stat = sh.transformedShape();
|
||||
|
||||
std::cout << "pile size: " << stationary.size() << std::endl;
|
||||
for(RawShape& sh : stationary) {
|
||||
if(sh.isContourConvex() && orbconvex)
|
||||
subnfp = Nfp::noFitPolygon<NfpLevel::CONVEX_ONLY>(stat, orb);
|
||||
else if(orbconvex)
|
||||
subnfp = Nfp::noFitPolygon<NfpLevel::ONE_CONVEX>(stat, orb);
|
||||
else
|
||||
subnfp = Nfp::noFitPolygon<Level::value>(stat, orb);
|
||||
|
||||
RawShape subnfp;
|
||||
// if(sh.isContourConvex() && trsh.isContourConvex()) {
|
||||
// subnfp = Nfp::noFitPolygon<NfpLevel::CONVEX_ONLY>(
|
||||
// sh.transformedShape(), trsh.transformedShape());
|
||||
// } else {
|
||||
subnfp = Nfp::noFitPolygon<Level::value>( sh/*.transformedShape()*/,
|
||||
trsh.transformedShape());
|
||||
// }
|
||||
correctNfpPosition(subnfp, sh, trsh);
|
||||
|
||||
// #ifndef NDEBUG
|
||||
// auto vv = ShapeLike::isValid(sh.transformedShape());
|
||||
// assert(vv.first);
|
||||
|
||||
// auto vnfp = ShapeLike::isValid(subnfp);
|
||||
// assert(vnfp.first);
|
||||
// #endif
|
||||
|
||||
// auto vnfp = ShapeLike::isValid(subnfp);
|
||||
// if(!vnfp.first) {
|
||||
// std::cout << vnfp.second << std::endl;
|
||||
// std::cout << ShapeLike::toString(subnfp) << std::endl;
|
||||
// }
|
||||
|
||||
nfps = Nfp::merge(nfps, subnfp);
|
||||
nfps = Nfp::merge(nfps, subnfp.first);
|
||||
}
|
||||
|
||||
return nfps;
|
||||
|
||||
|
||||
// using Item = _Item<RawShape>;
|
||||
// using sl = ShapeLike;
|
||||
|
||||
// Nfp::Shapes<RawShape> nfps, stationary;
|
||||
|
||||
// for(Item& sh : polygons) {
|
||||
// stationary = Nfp::merge(stationary, sh.transformedShape());
|
||||
// }
|
||||
|
||||
// for(RawShape& sh : stationary) {
|
||||
|
||||
//// auto vv = sl::isValid(sh);
|
||||
//// std::cout << vv.second << std::endl;
|
||||
|
||||
|
||||
// Nfp::NfpResult<RawShape> subnfp;
|
||||
// bool shconvex = sl::isConvex<RawShape>(sl::getContour(sh));
|
||||
// if(shconvex && trsh.isContourConvex()) {
|
||||
// subnfp = Nfp::noFitPolygon<NfpLevel::CONVEX_ONLY>(
|
||||
// sh, trsh.transformedShape());
|
||||
// } else if(trsh.isContourConvex()) {
|
||||
// subnfp = Nfp::noFitPolygon<NfpLevel::ONE_CONVEX>(
|
||||
// sh, trsh.transformedShape());
|
||||
// }
|
||||
// else {
|
||||
// subnfp = Nfp::noFitPolygon<Level::value>( sh,
|
||||
// trsh.transformedShape());
|
||||
// }
|
||||
|
||||
// correctNfpPosition(subnfp, sh, trsh);
|
||||
|
||||
// nfps = Nfp::merge(nfps, subnfp.first);
|
||||
// }
|
||||
|
||||
// return nfps;
|
||||
}
|
||||
|
||||
template<class RawShape>
|
||||
class _NofitPolyPlacer: public PlacerBoilerplate<_NofitPolyPlacer<RawShape>,
|
||||
RawShape, _Box<TPoint<RawShape>>, NfpPConfig<RawShape>> {
|
||||
template<class RawShape, class TBin = _Box<TPoint<RawShape>>>
|
||||
class _NofitPolyPlacer: public PlacerBoilerplate<_NofitPolyPlacer<RawShape, TBin>,
|
||||
RawShape, TBin, NfpPConfig<RawShape>> {
|
||||
|
||||
using Base = PlacerBoilerplate<_NofitPolyPlacer<RawShape>,
|
||||
RawShape, _Box<TPoint<RawShape>>, NfpPConfig<RawShape>>;
|
||||
using Base = PlacerBoilerplate<_NofitPolyPlacer<RawShape, TBin>,
|
||||
RawShape, TBin, NfpPConfig<RawShape>>;
|
||||
|
||||
DECLARE_PLACER(Base)
|
||||
|
||||
|
@ -280,28 +456,45 @@ class _NofitPolyPlacer: public PlacerBoilerplate<_NofitPolyPlacer<RawShape>,
|
|||
const double penality_;
|
||||
|
||||
using MaxNfpLevel = Nfp::MaxNfpLevel<RawShape>;
|
||||
using sl = ShapeLike;
|
||||
|
||||
public:
|
||||
|
||||
using Pile = const Nfp::Shapes<RawShape>&;
|
||||
using Pile = Nfp::Shapes<RawShape>;
|
||||
|
||||
inline explicit _NofitPolyPlacer(const BinType& bin):
|
||||
Base(bin),
|
||||
norm_(std::sqrt(ShapeLike::area<RawShape>(bin))),
|
||||
norm_(std::sqrt(sl::area<RawShape>(bin))),
|
||||
penality_(1e6*norm_) {}
|
||||
|
||||
_NofitPolyPlacer(const _NofitPolyPlacer&) = default;
|
||||
_NofitPolyPlacer& operator=(const _NofitPolyPlacer&) = default;
|
||||
|
||||
#ifndef BP2D_COMPILER_MSVC12 // MSVC2013 does not support default move ctors
|
||||
_NofitPolyPlacer(_NofitPolyPlacer&&) BP2D_NOEXCEPT = default;
|
||||
_NofitPolyPlacer& operator=(_NofitPolyPlacer&&) BP2D_NOEXCEPT = default;
|
||||
#endif
|
||||
|
||||
bool static inline wouldFit(const Box& bb, const RawShape& bin) {
|
||||
auto bbin = sl::boundingBox<RawShape>(bin);
|
||||
auto d = bbin.center() - bb.center();
|
||||
_Rectangle<RawShape> rect(bb.width(), bb.height());
|
||||
rect.translate(bb.minCorner() + d);
|
||||
return sl::isInside<RawShape>(rect.transformedShape(), bin);
|
||||
}
|
||||
|
||||
bool static inline wouldFit(const RawShape& chull, const RawShape& bin) {
|
||||
auto bbch = ShapeLike::boundingBox<RawShape>(chull);
|
||||
auto bbin = ShapeLike::boundingBox<RawShape>(bin);
|
||||
auto d = bbin.minCorner() - bbch.minCorner();
|
||||
auto bbch = sl::boundingBox<RawShape>(chull);
|
||||
auto bbin = sl::boundingBox<RawShape>(bin);
|
||||
auto d = bbch.center() - bbin.center();
|
||||
auto chullcpy = chull;
|
||||
ShapeLike::translate(chullcpy, d);
|
||||
return ShapeLike::isInside<RawShape>(chullcpy, bbin);
|
||||
sl::translate(chullcpy, d);
|
||||
return sl::isInside<RawShape>(chullcpy, bin);
|
||||
}
|
||||
|
||||
bool static inline wouldFit(const RawShape& chull, const Box& bin)
|
||||
{
|
||||
auto bbch = ShapeLike::boundingBox<RawShape>(chull);
|
||||
auto bbch = sl::boundingBox<RawShape>(chull);
|
||||
return wouldFit(bbch, bin);
|
||||
}
|
||||
|
||||
|
@ -310,6 +503,17 @@ public:
|
|||
return bb.width() <= bin.width() && bb.height() <= bin.height();
|
||||
}
|
||||
|
||||
bool static inline wouldFit(const Box& bb, const _Circle<Vertex>& bin)
|
||||
{
|
||||
return sl::isInside<RawShape>(bb, bin);
|
||||
}
|
||||
|
||||
bool static inline wouldFit(const RawShape& chull,
|
||||
const _Circle<Vertex>& bin)
|
||||
{
|
||||
return sl::isInside<RawShape>(chull, bin);
|
||||
}
|
||||
|
||||
PackResult trypack(Item& item) {
|
||||
|
||||
PackResult ret;
|
||||
|
@ -348,7 +552,10 @@ public:
|
|||
std::vector<EdgeCache<RawShape>> ecache;
|
||||
ecache.reserve(nfps.size());
|
||||
|
||||
for(auto& nfp : nfps ) ecache.emplace_back(nfp);
|
||||
for(auto& nfp : nfps ) {
|
||||
ecache.emplace_back(nfp);
|
||||
ecache.back().accuracy(config_.accuracy);
|
||||
}
|
||||
|
||||
struct Optimum {
|
||||
double relpos;
|
||||
|
@ -363,7 +570,7 @@ public:
|
|||
auto getNfpPoint = [&ecache](const Optimum& opt)
|
||||
{
|
||||
return opt.hidx < 0? ecache[opt.nfpidx].coords(opt.relpos) :
|
||||
ecache[opt.nfpidx].coords(opt.nfpidx, opt.relpos);
|
||||
ecache[opt.nfpidx].coords(opt.hidx, opt.relpos);
|
||||
};
|
||||
|
||||
Nfp::Shapes<RawShape> pile;
|
||||
|
@ -374,17 +581,25 @@ public:
|
|||
pile_area += mitem.area();
|
||||
}
|
||||
|
||||
auto merged_pile = Nfp::merge(pile);
|
||||
|
||||
// This is the kernel part of the object function that is
|
||||
// customizable by the library client
|
||||
auto _objfunc = config_.object_function?
|
||||
config_.object_function :
|
||||
[this](const Nfp::Shapes<RawShape>& pile, double occupied_area,
|
||||
double /*norm*/, double penality)
|
||||
[this, &merged_pile](
|
||||
Nfp::Shapes<RawShape>& /*pile*/,
|
||||
const Item& item,
|
||||
double occupied_area,
|
||||
double norm,
|
||||
double /*penality*/)
|
||||
{
|
||||
auto ch = ShapeLike::convexHull(pile);
|
||||
merged_pile.emplace_back(item.transformedShape());
|
||||
auto ch = sl::convexHull(merged_pile);
|
||||
merged_pile.pop_back();
|
||||
|
||||
// The pack ratio -- how much is the convex hull occupied
|
||||
double pack_rate = occupied_area/ShapeLike::area(ch);
|
||||
double pack_rate = occupied_area/sl::area(ch);
|
||||
|
||||
// ratio of waste
|
||||
double waste = 1.0 - pack_rate;
|
||||
|
@ -394,7 +609,7 @@ public:
|
|||
// (larger) values.
|
||||
auto score = std::sqrt(waste);
|
||||
|
||||
if(!wouldFit(ch, bin_)) score = 2*penality - score;
|
||||
if(!wouldFit(ch, bin_)) score += norm;
|
||||
|
||||
return score;
|
||||
};
|
||||
|
@ -406,23 +621,31 @@ public:
|
|||
d += startpos;
|
||||
item.translation(d);
|
||||
|
||||
pile.emplace_back(item.transformedShape());
|
||||
|
||||
double occupied_area = pile_area + item.area();
|
||||
|
||||
double score = _objfunc(pile, occupied_area,
|
||||
double score = _objfunc(pile, item, occupied_area,
|
||||
norm_, penality_);
|
||||
|
||||
pile.pop_back();
|
||||
|
||||
return score;
|
||||
};
|
||||
|
||||
auto boundaryCheck = [&](const Optimum& o) {
|
||||
auto v = getNfpPoint(o);
|
||||
auto d = v - iv;
|
||||
d += startpos;
|
||||
item.translation(d);
|
||||
|
||||
merged_pile.emplace_back(item.transformedShape());
|
||||
auto chull = sl::convexHull(merged_pile);
|
||||
merged_pile.pop_back();
|
||||
|
||||
return wouldFit(chull, bin_);
|
||||
};
|
||||
|
||||
opt::StopCriteria stopcr;
|
||||
stopcr.max_iterations = 1000;
|
||||
stopcr.stoplimit = 0.001;
|
||||
stopcr.type = opt::StopLimitType::RELATIVE;
|
||||
opt::TOptimizer<opt::Method::L_SIMPLEX> solver(stopcr);
|
||||
stopcr.max_iterations = 100;
|
||||
stopcr.relative_score_difference = 1e-6;
|
||||
opt::TOptimizer<opt::Method::L_SUBPLEX> solver(stopcr);
|
||||
|
||||
Optimum optimum(0, 0);
|
||||
double best_score = penality_;
|
||||
|
@ -441,7 +664,7 @@ public:
|
|||
std::for_each(cache.corners().begin(),
|
||||
cache.corners().end(),
|
||||
[ch, &contour_ofn, &solver, &best_score,
|
||||
&optimum] (double pos)
|
||||
&optimum, &boundaryCheck] (double pos)
|
||||
{
|
||||
try {
|
||||
auto result = solver.optimize_min(contour_ofn,
|
||||
|
@ -450,10 +673,11 @@ public:
|
|||
);
|
||||
|
||||
if(result.score < best_score) {
|
||||
best_score = result.score;
|
||||
optimum.relpos = std::get<0>(result.optimum);
|
||||
optimum.nfpidx = ch;
|
||||
optimum.hidx = -1;
|
||||
Optimum o(std::get<0>(result.optimum), ch, -1);
|
||||
if(boundaryCheck(o)) {
|
||||
best_score = result.score;
|
||||
optimum = o;
|
||||
}
|
||||
}
|
||||
} catch(std::exception& e) {
|
||||
derr() << "ERROR: " << e.what() << "\n";
|
||||
|
@ -472,7 +696,7 @@ public:
|
|||
std::for_each(cache.corners(hidx).begin(),
|
||||
cache.corners(hidx).end(),
|
||||
[&hole_ofn, &solver, &best_score,
|
||||
&optimum, ch, hidx]
|
||||
&optimum, ch, hidx, &boundaryCheck]
|
||||
(double pos)
|
||||
{
|
||||
try {
|
||||
|
@ -482,10 +706,12 @@ public:
|
|||
);
|
||||
|
||||
if(result.score < best_score) {
|
||||
best_score = result.score;
|
||||
Optimum o(std::get<0>(result.optimum),
|
||||
ch, hidx);
|
||||
optimum = o;
|
||||
if(boundaryCheck(o)) {
|
||||
best_score = result.score;
|
||||
optimum = o;
|
||||
}
|
||||
}
|
||||
} catch(std::exception& e) {
|
||||
derr() << "ERROR: " << e.what() << "\n";
|
||||
|
@ -524,34 +750,35 @@ public:
|
|||
m.reserve(items_.size());
|
||||
|
||||
for(Item& item : items_) m.emplace_back(item.transformedShape());
|
||||
auto&& bb = ShapeLike::boundingBox<RawShape>(m);
|
||||
auto&& bb = sl::boundingBox<RawShape>(m);
|
||||
|
||||
Vertex ci, cb;
|
||||
auto bbin = sl::boundingBox<RawShape>(bin_);
|
||||
|
||||
switch(config_.alignment) {
|
||||
case Config::Alignment::CENTER: {
|
||||
ci = bb.center();
|
||||
cb = bin_.center();
|
||||
cb = bbin.center();
|
||||
break;
|
||||
}
|
||||
case Config::Alignment::BOTTOM_LEFT: {
|
||||
ci = bb.minCorner();
|
||||
cb = bin_.minCorner();
|
||||
cb = bbin.minCorner();
|
||||
break;
|
||||
}
|
||||
case Config::Alignment::BOTTOM_RIGHT: {
|
||||
ci = {getX(bb.maxCorner()), getY(bb.minCorner())};
|
||||
cb = {getX(bin_.maxCorner()), getY(bin_.minCorner())};
|
||||
cb = {getX(bbin.maxCorner()), getY(bbin.minCorner())};
|
||||
break;
|
||||
}
|
||||
case Config::Alignment::TOP_LEFT: {
|
||||
ci = {getX(bb.minCorner()), getY(bb.maxCorner())};
|
||||
cb = {getX(bin_.minCorner()), getY(bin_.maxCorner())};
|
||||
cb = {getX(bbin.minCorner()), getY(bbin.maxCorner())};
|
||||
break;
|
||||
}
|
||||
case Config::Alignment::TOP_RIGHT: {
|
||||
ci = bb.maxCorner();
|
||||
cb = bin_.maxCorner();
|
||||
cb = bbin.maxCorner();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
@ -567,31 +794,32 @@ private:
|
|||
void setInitialPosition(Item& item) {
|
||||
Box&& bb = item.boundingBox();
|
||||
Vertex ci, cb;
|
||||
auto bbin = sl::boundingBox<RawShape>(bin_);
|
||||
|
||||
switch(config_.starting_point) {
|
||||
case Config::Alignment::CENTER: {
|
||||
ci = bb.center();
|
||||
cb = bin_.center();
|
||||
cb = bbin.center();
|
||||
break;
|
||||
}
|
||||
case Config::Alignment::BOTTOM_LEFT: {
|
||||
ci = bb.minCorner();
|
||||
cb = bin_.minCorner();
|
||||
cb = bbin.minCorner();
|
||||
break;
|
||||
}
|
||||
case Config::Alignment::BOTTOM_RIGHT: {
|
||||
ci = {getX(bb.maxCorner()), getY(bb.minCorner())};
|
||||
cb = {getX(bin_.maxCorner()), getY(bin_.minCorner())};
|
||||
cb = {getX(bbin.maxCorner()), getY(bbin.minCorner())};
|
||||
break;
|
||||
}
|
||||
case Config::Alignment::TOP_LEFT: {
|
||||
ci = {getX(bb.minCorner()), getY(bb.maxCorner())};
|
||||
cb = {getX(bin_.minCorner()), getY(bin_.maxCorner())};
|
||||
cb = {getX(bbin.minCorner()), getY(bbin.maxCorner())};
|
||||
break;
|
||||
}
|
||||
case Config::Alignment::TOP_RIGHT: {
|
||||
ci = bb.maxCorner();
|
||||
cb = bin_.maxCorner();
|
||||
cb = bbin.maxCorner();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
@ -602,7 +830,7 @@ private:
|
|||
|
||||
void placeOutsideOfBin(Item& item) {
|
||||
auto&& bb = item.boundingBox();
|
||||
Box binbb = ShapeLike::boundingBox<RawShape>(bin_);
|
||||
Box binbb = sl::boundingBox<RawShape>(bin_);
|
||||
|
||||
Vertex v = { getX(bb.maxCorner()), getY(bb.minCorner()) };
|
||||
|
||||
|
|
|
@ -256,14 +256,14 @@ public:
|
|||
|
||||
if(not_packed.size() < 2)
|
||||
return false; // No group of two items
|
||||
else {
|
||||
double largest_area = not_packed.front().get().area();
|
||||
auto itmp = not_packed.begin(); itmp++;
|
||||
double second_largest = itmp->get().area();
|
||||
if( free_area - second_largest - largest_area > waste)
|
||||
return false; // If even the largest two items do not fill
|
||||
// the bin to the desired waste than we can end here.
|
||||
}
|
||||
|
||||
double largest_area = not_packed.front().get().area();
|
||||
auto itmp = not_packed.begin(); itmp++;
|
||||
double second_largest = itmp->get().area();
|
||||
if( free_area - second_largest - largest_area > waste)
|
||||
return false; // If even the largest two items do not fill
|
||||
// the bin to the desired waste than we can end here.
|
||||
|
||||
|
||||
bool ret = false;
|
||||
auto it = not_packed.begin();
|
||||
|
@ -481,7 +481,7 @@ public:
|
|||
{
|
||||
std::array<bool, 3> packed = {false};
|
||||
|
||||
for(auto id : idx) packed[id] =
|
||||
for(auto id : idx) packed.at(id) =
|
||||
placer.pack(candidates[id]);
|
||||
|
||||
bool check =
|
||||
|
@ -535,10 +535,9 @@ public:
|
|||
// then it should be removed from the not_packed list
|
||||
{ auto it = store_.begin();
|
||||
while (it != store_.end()) {
|
||||
Placer p(bin);
|
||||
Placer p(bin); p.configure(pconfig);
|
||||
if(!p.pack(*it)) {
|
||||
auto itmp = it++;
|
||||
store_.erase(itmp);
|
||||
it = store_.erase(it);
|
||||
} else it++;
|
||||
}
|
||||
}
|
||||
|
@ -605,8 +604,7 @@ public:
|
|||
if(placer.pack(*it)) {
|
||||
filled_area += it->get().area();
|
||||
free_area = bin_area - filled_area;
|
||||
auto itmp = it++;
|
||||
not_packed.erase(itmp);
|
||||
it = not_packed.erase(it);
|
||||
makeProgress(placer, idx, 1);
|
||||
} else it++;
|
||||
}
|
||||
|
|
|
@ -52,17 +52,16 @@ public:
|
|||
auto total = last-first;
|
||||
auto makeProgress = [this, &total](Placer& placer, size_t idx) {
|
||||
packed_bins_[idx] = placer.getItems();
|
||||
this->progress_(--total);
|
||||
this->progress_(static_cast<unsigned>(--total));
|
||||
};
|
||||
|
||||
// Safety test: try to pack each item into an empty bin. If it fails
|
||||
// then it should be removed from the list
|
||||
{ auto it = store_.begin();
|
||||
while (it != store_.end()) {
|
||||
Placer p(bin);
|
||||
Placer p(bin); p.configure(pconfig);
|
||||
if(!p.pack(*it)) {
|
||||
auto itmp = it++;
|
||||
store_.erase(itmp);
|
||||
it = store_.erase(it);
|
||||
} else it++;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -682,7 +682,9 @@ void testNfp(const std::vector<ItemPair>& testdata) {
|
|||
auto&& nfp = Nfp::noFitPolygon<lvl>(stationary.rawShape(),
|
||||
orbiter.transformedShape());
|
||||
|
||||
auto v = ShapeLike::isValid(nfp);
|
||||
strategies::correctNfpPosition(nfp, stationary, orbiter);
|
||||
|
||||
auto v = ShapeLike::isValid(nfp.first);
|
||||
|
||||
if(!v.first) {
|
||||
std::cout << v.second << std::endl;
|
||||
|
@ -690,7 +692,7 @@ void testNfp(const std::vector<ItemPair>& testdata) {
|
|||
|
||||
ASSERT_TRUE(v.first);
|
||||
|
||||
Item infp(nfp);
|
||||
Item infp(nfp.first);
|
||||
|
||||
int i = 0;
|
||||
auto rorbiter = orbiter.transformedShape();
|
||||
|
@ -742,6 +744,15 @@ TEST(GeometryAlgorithms, nfpConvexConvex) {
|
|||
// testNfp<NfpLevel::BOTH_CONCAVE, 1000>(nfp_concave_testdata);
|
||||
//}
|
||||
|
||||
TEST(GeometryAlgorithms, nfpConcaveConcave) {
|
||||
using namespace libnest2d;
|
||||
|
||||
// Rectangle r1(10, 10);
|
||||
// Rectangle r2(20, 20);
|
||||
// auto result = Nfp::nfpSimpleSimple(r1.transformedShape(),
|
||||
// r2.transformedShape());
|
||||
}
|
||||
|
||||
TEST(GeometryAlgorithms, pointOnPolygonContour) {
|
||||
using namespace libnest2d;
|
||||
|
||||
|
|
|
@ -49,18 +49,18 @@ libnfporb::point_t scale(const libnfporb::point_t& p, long double factor) {
|
|||
long double px = p.x_.val();
|
||||
long double py = p.y_.val();
|
||||
#endif
|
||||
return libnfporb::point_t(px*factor, py*factor);
|
||||
return {px*factor, py*factor};
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
PolygonImpl _nfp(const PolygonImpl &sh, const PolygonImpl &cother)
|
||||
NfpR _nfp(const PolygonImpl &sh, const PolygonImpl &cother)
|
||||
{
|
||||
using Vertex = PointImpl;
|
||||
|
||||
PolygonImpl ret;
|
||||
NfpR ret;
|
||||
|
||||
// try {
|
||||
try {
|
||||
libnfporb::polygon_t pstat, porb;
|
||||
|
||||
boost::geometry::convert(sh, pstat);
|
||||
|
@ -85,7 +85,7 @@ PolygonImpl _nfp(const PolygonImpl &sh, const PolygonImpl &cother)
|
|||
// this can throw
|
||||
auto nfp = libnfporb::generateNFP(pstat, porb, true);
|
||||
|
||||
auto &ct = ShapeLike::getContour(ret);
|
||||
auto &ct = ShapeLike::getContour(ret.first);
|
||||
ct.reserve(nfp.front().size()+1);
|
||||
for(auto v : nfp.front()) {
|
||||
v = scale(v, refactor);
|
||||
|
@ -94,10 +94,10 @@ PolygonImpl _nfp(const PolygonImpl &sh, const PolygonImpl &cother)
|
|||
ct.push_back(ct.front());
|
||||
std::reverse(ct.begin(), ct.end());
|
||||
|
||||
auto &rholes = ShapeLike::holes(ret);
|
||||
auto &rholes = ShapeLike::holes(ret.first);
|
||||
for(size_t hidx = 1; hidx < nfp.size(); ++hidx) {
|
||||
if(nfp[hidx].size() >= 3) {
|
||||
rholes.push_back({});
|
||||
rholes.emplace_back();
|
||||
auto& h = rholes.back();
|
||||
h.reserve(nfp[hidx].size()+1);
|
||||
|
||||
|
@ -110,73 +110,48 @@ PolygonImpl _nfp(const PolygonImpl &sh, const PolygonImpl &cother)
|
|||
}
|
||||
}
|
||||
|
||||
auto& cmp = vsort;
|
||||
std::sort(pstat.outer().begin(), pstat.outer().end(), cmp);
|
||||
std::sort(porb.outer().begin(), porb.outer().end(), cmp);
|
||||
ret.second = Nfp::referenceVertex(ret.first);
|
||||
|
||||
// leftmost lower vertex of the stationary polygon
|
||||
auto& touch_sh = scale(pstat.outer().back(), refactor);
|
||||
// rightmost upper vertex of the orbiting polygon
|
||||
auto& touch_other = scale(porb.outer().front(), refactor);
|
||||
|
||||
// Calculate the difference and move the orbiter to the touch position.
|
||||
auto dtouch = touch_sh - touch_other;
|
||||
auto _top_other = scale(porb.outer().back(), refactor) + dtouch;
|
||||
|
||||
Vertex top_other(getX(_top_other), getY(_top_other));
|
||||
|
||||
// Get the righmost upper vertex of the nfp and move it to the RMU of
|
||||
// the orbiter because they should coincide.
|
||||
auto&& top_nfp = Nfp::rightmostUpVertex(ret);
|
||||
auto dnfp = top_other - top_nfp;
|
||||
|
||||
std::for_each(ShapeLike::begin(ret), ShapeLike::end(ret),
|
||||
[&dnfp](Vertex& v) { v+= dnfp; } );
|
||||
|
||||
for(auto& h : ShapeLike::holes(ret))
|
||||
std::for_each( h.begin(), h.end(),
|
||||
[&dnfp](Vertex& v) { v += dnfp; } );
|
||||
|
||||
// } catch(std::exception& e) {
|
||||
// std::cout << "Error: " << e.what() << "\nTrying with convex hull..." << std::endl;
|
||||
} catch(std::exception& e) {
|
||||
std::cout << "Error: " << e.what() << "\nTrying with convex hull..." << std::endl;
|
||||
// auto ch_stat = ShapeLike::convexHull(sh);
|
||||
// auto ch_orb = ShapeLike::convexHull(cother);
|
||||
// ret = Nfp::nfpConvexOnly(ch_stat, ch_orb);
|
||||
// }
|
||||
ret = Nfp::nfpConvexOnly(sh, cother);
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
PolygonImpl Nfp::NfpImpl<PolygonImpl, NfpLevel::CONVEX_ONLY>::operator()(
|
||||
NfpR Nfp::NfpImpl<PolygonImpl, NfpLevel::CONVEX_ONLY>::operator()(
|
||||
const PolygonImpl &sh, const ClipperLib::PolygonImpl &cother)
|
||||
{
|
||||
return _nfp(sh, cother);//nfpConvexOnly(sh, cother);
|
||||
}
|
||||
|
||||
PolygonImpl Nfp::NfpImpl<PolygonImpl, NfpLevel::ONE_CONVEX>::operator()(
|
||||
NfpR Nfp::NfpImpl<PolygonImpl, NfpLevel::ONE_CONVEX>::operator()(
|
||||
const PolygonImpl &sh, const ClipperLib::PolygonImpl &cother)
|
||||
{
|
||||
return _nfp(sh, cother);
|
||||
}
|
||||
|
||||
PolygonImpl Nfp::NfpImpl<PolygonImpl, NfpLevel::BOTH_CONCAVE>::operator()(
|
||||
NfpR Nfp::NfpImpl<PolygonImpl, NfpLevel::BOTH_CONCAVE>::operator()(
|
||||
const PolygonImpl &sh, const ClipperLib::PolygonImpl &cother)
|
||||
{
|
||||
return _nfp(sh, cother);
|
||||
}
|
||||
|
||||
PolygonImpl
|
||||
Nfp::NfpImpl<PolygonImpl, NfpLevel::ONE_CONVEX_WITH_HOLES>::operator()(
|
||||
const PolygonImpl &sh, const ClipperLib::PolygonImpl &cother)
|
||||
{
|
||||
return _nfp(sh, cother);
|
||||
}
|
||||
//PolygonImpl
|
||||
//Nfp::NfpImpl<PolygonImpl, NfpLevel::ONE_CONVEX_WITH_HOLES>::operator()(
|
||||
// const PolygonImpl &sh, const ClipperLib::PolygonImpl &cother)
|
||||
//{
|
||||
// return _nfp(sh, cother);
|
||||
//}
|
||||
|
||||
PolygonImpl
|
||||
Nfp::NfpImpl<PolygonImpl, NfpLevel::BOTH_CONCAVE_WITH_HOLES>::operator()(
|
||||
const PolygonImpl &sh, const ClipperLib::PolygonImpl &cother)
|
||||
{
|
||||
return _nfp(sh, cother);
|
||||
}
|
||||
//PolygonImpl
|
||||
//Nfp::NfpImpl<PolygonImpl, NfpLevel::BOTH_CONCAVE_WITH_HOLES>::operator()(
|
||||
// const PolygonImpl &sh, const ClipperLib::PolygonImpl &cother)
|
||||
//{
|
||||
// return _nfp(sh, cother);
|
||||
//}
|
||||
|
||||
}
|
||||
|
|
|
@ -5,37 +5,39 @@
|
|||
|
||||
namespace libnest2d {
|
||||
|
||||
PolygonImpl _nfp(const PolygonImpl& sh, const PolygonImpl& cother);
|
||||
using NfpR = Nfp::NfpResult<PolygonImpl>;
|
||||
|
||||
NfpR _nfp(const PolygonImpl& sh, const PolygonImpl& cother);
|
||||
|
||||
template<>
|
||||
struct Nfp::NfpImpl<PolygonImpl, NfpLevel::CONVEX_ONLY> {
|
||||
PolygonImpl operator()(const PolygonImpl& sh, const PolygonImpl& cother);
|
||||
NfpR operator()(const PolygonImpl& sh, const PolygonImpl& cother);
|
||||
};
|
||||
|
||||
template<>
|
||||
struct Nfp::NfpImpl<PolygonImpl, NfpLevel::ONE_CONVEX> {
|
||||
PolygonImpl operator()(const PolygonImpl& sh, const PolygonImpl& cother);
|
||||
NfpR operator()(const PolygonImpl& sh, const PolygonImpl& cother);
|
||||
};
|
||||
|
||||
template<>
|
||||
struct Nfp::NfpImpl<PolygonImpl, NfpLevel::BOTH_CONCAVE> {
|
||||
PolygonImpl operator()(const PolygonImpl& sh, const PolygonImpl& cother);
|
||||
NfpR operator()(const PolygonImpl& sh, const PolygonImpl& cother);
|
||||
};
|
||||
|
||||
template<>
|
||||
struct Nfp::NfpImpl<PolygonImpl, NfpLevel::ONE_CONVEX_WITH_HOLES> {
|
||||
PolygonImpl operator()(const PolygonImpl& sh, const PolygonImpl& cother);
|
||||
};
|
||||
//template<>
|
||||
//struct Nfp::NfpImpl<PolygonImpl, NfpLevel::ONE_CONVEX_WITH_HOLES> {
|
||||
// NfpResult operator()(const PolygonImpl& sh, const PolygonImpl& cother);
|
||||
//};
|
||||
|
||||
template<>
|
||||
struct Nfp::NfpImpl<PolygonImpl, NfpLevel::BOTH_CONCAVE_WITH_HOLES> {
|
||||
PolygonImpl operator()(const PolygonImpl& sh, const PolygonImpl& cother);
|
||||
};
|
||||
//template<>
|
||||
//struct Nfp::NfpImpl<PolygonImpl, NfpLevel::BOTH_CONCAVE_WITH_HOLES> {
|
||||
// NfpResult operator()(const PolygonImpl& sh, const PolygonImpl& cother);
|
||||
//};
|
||||
|
||||
template<> struct Nfp::MaxNfpLevel<PolygonImpl> {
|
||||
static const BP2D_CONSTEXPR NfpLevel value =
|
||||
// NfpLevel::CONVEX_ONLY;
|
||||
NfpLevel::BOTH_CONCAVE_WITH_HOLES;
|
||||
NfpLevel::BOTH_CONCAVE;
|
||||
};
|
||||
|
||||
}
|
||||
|
|
|
@ -5,11 +5,17 @@
|
|||
#include <fstream>
|
||||
#include <string>
|
||||
|
||||
#include <libnest2d.h>
|
||||
#include <libnest2d/libnest2d.hpp>
|
||||
|
||||
namespace libnest2d { namespace svg {
|
||||
|
||||
template<class RawShape>
|
||||
class SVGWriter {
|
||||
using Item = _Item<RawShape>;
|
||||
using Coord = TCoord<TPoint<RawShape>>;
|
||||
using Box = _Box<TPoint<RawShape>>;
|
||||
using PackGroup = _PackGroup<RawShape>;
|
||||
|
||||
public:
|
||||
|
||||
enum OrigoLocation {
|
||||
|
|
|
@ -1989,7 +1989,7 @@ namespace Slic3r {
|
|||
|
||||
// stores object's name
|
||||
if (!obj->name.empty())
|
||||
stream << " <" << METADATA_TAG << " " << TYPE_ATTR << "=\"" << OBJECT_TYPE << "\" " << KEY_ATTR << "=\"name\" " << VALUE_ATTR << "=\"" << obj->name << "\"/>\n";
|
||||
stream << " <" << METADATA_TAG << " " << TYPE_ATTR << "=\"" << OBJECT_TYPE << "\" " << KEY_ATTR << "=\"name\" " << VALUE_ATTR << "=\"" << xml_escape(obj->name) << "\"/>\n";
|
||||
|
||||
// stores object's config data
|
||||
for (const std::string& key : obj->config.keys())
|
||||
|
@ -2012,7 +2012,7 @@ namespace Slic3r {
|
|||
|
||||
// stores volume's name
|
||||
if (!volume->name.empty())
|
||||
stream << " <" << METADATA_TAG << " " << TYPE_ATTR << "=\"" << VOLUME_TYPE << "\" " << KEY_ATTR << "=\"" << NAME_KEY << "\" " << VALUE_ATTR << "=\"" << volume->name << "\"/>\n";
|
||||
stream << " <" << METADATA_TAG << " " << TYPE_ATTR << "=\"" << VOLUME_TYPE << "\" " << KEY_ATTR << "=\"" << NAME_KEY << "\" " << VALUE_ATTR << "=\"" << xml_escape(volume->name) << "\"/>\n";
|
||||
|
||||
// stores volume's modifier field
|
||||
if (volume->modifier)
|
||||
|
|
|
@ -8,6 +8,7 @@
|
|||
#include "../libslic3r.h"
|
||||
#include "../Model.hpp"
|
||||
#include "../GCode.hpp"
|
||||
#include "../Utils.hpp"
|
||||
#include "../slic3r/GUI/PresetBundle.hpp"
|
||||
#include "AMF.hpp"
|
||||
|
||||
|
@ -686,33 +687,6 @@ bool load_amf(const char *path, PresetBundle* bundle, Model *model)
|
|||
return false;
|
||||
}
|
||||
|
||||
std::string xml_escape(std::string text)
|
||||
{
|
||||
std::string::size_type pos = 0;
|
||||
for (;;)
|
||||
{
|
||||
pos = text.find_first_of("\"\'&<>", pos);
|
||||
if (pos == std::string::npos)
|
||||
break;
|
||||
|
||||
std::string replacement;
|
||||
switch (text[pos])
|
||||
{
|
||||
case '\"': replacement = """; break;
|
||||
case '\'': replacement = "'"; break;
|
||||
case '&': replacement = "&"; break;
|
||||
case '<': replacement = "<"; break;
|
||||
case '>': replacement = ">"; break;
|
||||
default: break;
|
||||
}
|
||||
|
||||
text.replace(pos, 1, replacement);
|
||||
pos += replacement.size();
|
||||
}
|
||||
|
||||
return text;
|
||||
}
|
||||
|
||||
bool store_amf(const char *path, Model *model, Print* print, bool export_print_config)
|
||||
{
|
||||
if ((path == nullptr) || (model == nullptr) || (print == nullptr))
|
||||
|
@ -761,7 +735,7 @@ bool store_amf(const char *path, Model *model, Print* print, bool export_print_c
|
|||
for (const std::string &key : object->config.keys())
|
||||
stream << " <metadata type=\"slic3r." << key << "\">" << object->config.serialize(key) << "</metadata>\n";
|
||||
if (!object->name.empty())
|
||||
stream << " <metadata type=\"name\">" << object->name << "</metadata>\n";
|
||||
stream << " <metadata type=\"name\">" << xml_escape(object->name) << "</metadata>\n";
|
||||
std::vector<double> layer_height_profile = object->layer_height_profile_valid ? object->layer_height_profile : std::vector<double>();
|
||||
if (layer_height_profile.size() >= 4 && (layer_height_profile.size() % 2) == 0) {
|
||||
// Store the layer height profile as a single semicolon separated list.
|
||||
|
@ -805,7 +779,7 @@ bool store_amf(const char *path, Model *model, Print* print, bool export_print_c
|
|||
for (const std::string &key : volume->config.keys())
|
||||
stream << " <metadata type=\"slic3r." << key << "\">" << volume->config.serialize(key) << "</metadata>\n";
|
||||
if (!volume->name.empty())
|
||||
stream << " <metadata type=\"name\">" << volume->name << "</metadata>\n";
|
||||
stream << " <metadata type=\"name\">" << xml_escape(volume->name) << "</metadata>\n";
|
||||
if (volume->modifier)
|
||||
stream << " <metadata type=\"slic3r.modifier\">1</metadata>\n";
|
||||
for (int i = 0; i < volume->mesh.stl.stats.number_of_facets; ++i) {
|
||||
|
|
|
@ -167,6 +167,18 @@ std::string WipeTowerIntegration::append_tcr(GCode &gcodegen, const WipeTower::T
|
|||
{
|
||||
std::string gcode;
|
||||
|
||||
// Toolchangeresult.gcode assumes the wipe tower corner is at the origin
|
||||
// We want to rotate and shift all extrusions (gcode postprocessing) and starting and ending position
|
||||
float alpha = m_wipe_tower_rotation/180.f * M_PI;
|
||||
WipeTower::xy start_pos = tcr.start_pos;
|
||||
WipeTower::xy end_pos = tcr.end_pos;
|
||||
start_pos.rotate(alpha);
|
||||
start_pos.translate(m_wipe_tower_pos);
|
||||
end_pos.rotate(alpha);
|
||||
end_pos.translate(m_wipe_tower_pos);
|
||||
std::string tcr_rotated_gcode = rotate_wipe_tower_moves(tcr.gcode, tcr.start_pos, m_wipe_tower_pos, alpha);
|
||||
|
||||
|
||||
// Disable linear advance for the wipe tower operations.
|
||||
gcode += "M900 K0\n";
|
||||
// Move over the wipe tower.
|
||||
|
@ -174,14 +186,14 @@ std::string WipeTowerIntegration::append_tcr(GCode &gcodegen, const WipeTower::T
|
|||
gcode += gcodegen.retract(true);
|
||||
gcodegen.m_avoid_crossing_perimeters.use_external_mp_once = true;
|
||||
gcode += gcodegen.travel_to(
|
||||
wipe_tower_point_to_object_point(gcodegen, tcr.start_pos),
|
||||
wipe_tower_point_to_object_point(gcodegen, start_pos),
|
||||
erMixed,
|
||||
"Travel to a Wipe Tower");
|
||||
gcode += gcodegen.unretract();
|
||||
|
||||
// Let the tool change be executed by the wipe tower class.
|
||||
// Inform the G-code writer about the changes done behind its back.
|
||||
gcode += tcr.gcode;
|
||||
gcode += tcr_rotated_gcode;
|
||||
// Let the m_writer know the current extruder_id, but ignore the generated G-code.
|
||||
if (new_extruder_id >= 0 && gcodegen.writer().need_toolchange(new_extruder_id))
|
||||
gcodegen.writer().toolchange(new_extruder_id);
|
||||
|
@ -195,18 +207,18 @@ std::string WipeTowerIntegration::append_tcr(GCode &gcodegen, const WipeTower::T
|
|||
check_add_eol(gcode);
|
||||
}
|
||||
// A phony move to the end position at the wipe tower.
|
||||
gcodegen.writer().travel_to_xy(Pointf(tcr.end_pos.x, tcr.end_pos.y));
|
||||
gcodegen.set_last_pos(wipe_tower_point_to_object_point(gcodegen, tcr.end_pos));
|
||||
gcodegen.writer().travel_to_xy(Pointf(end_pos.x, end_pos.y));
|
||||
gcodegen.set_last_pos(wipe_tower_point_to_object_point(gcodegen, end_pos));
|
||||
|
||||
// Prepare a future wipe.
|
||||
gcodegen.m_wipe.path.points.clear();
|
||||
if (new_extruder_id >= 0) {
|
||||
// Start the wipe at the current position.
|
||||
gcodegen.m_wipe.path.points.emplace_back(wipe_tower_point_to_object_point(gcodegen, tcr.end_pos));
|
||||
gcodegen.m_wipe.path.points.emplace_back(wipe_tower_point_to_object_point(gcodegen, end_pos));
|
||||
// Wipe end point: Wipe direction away from the closer tower edge to the further tower edge.
|
||||
gcodegen.m_wipe.path.points.emplace_back(wipe_tower_point_to_object_point(gcodegen,
|
||||
WipeTower::xy((std::abs(m_left - tcr.end_pos.x) < std::abs(m_right - tcr.end_pos.x)) ? m_right : m_left,
|
||||
tcr.end_pos.y)));
|
||||
WipeTower::xy((std::abs(m_left - end_pos.x) < std::abs(m_right - end_pos.x)) ? m_right : m_left,
|
||||
end_pos.y)));
|
||||
}
|
||||
|
||||
// Let the planner know we are traveling between objects.
|
||||
|
@ -214,6 +226,57 @@ std::string WipeTowerIntegration::append_tcr(GCode &gcodegen, const WipeTower::T
|
|||
return gcode;
|
||||
}
|
||||
|
||||
// This function postprocesses gcode_original, rotates and moves all G1 extrusions and returns resulting gcode
|
||||
// Starting position has to be supplied explicitely (otherwise it would fail in case first G1 command only contained one coordinate)
|
||||
std::string WipeTowerIntegration::rotate_wipe_tower_moves(const std::string& gcode_original, const WipeTower::xy& start_pos, const WipeTower::xy& translation, float angle) const
|
||||
{
|
||||
std::istringstream gcode_str(gcode_original);
|
||||
std::string gcode_out;
|
||||
std::string line;
|
||||
WipeTower::xy pos = start_pos;
|
||||
WipeTower::xy transformed_pos;
|
||||
WipeTower::xy old_pos(-1000.1f, -1000.1f);
|
||||
|
||||
while (gcode_str) {
|
||||
std::getline(gcode_str, line); // we read the gcode line by line
|
||||
if (line.find("G1 ") == 0) {
|
||||
std::ostringstream line_out;
|
||||
std::istringstream line_str(line);
|
||||
line_str >> std::noskipws; // don't skip whitespace
|
||||
char ch = 0;
|
||||
while (line_str >> ch) {
|
||||
if (ch == 'X')
|
||||
line_str >> pos.x;
|
||||
else
|
||||
if (ch == 'Y')
|
||||
line_str >> pos.y;
|
||||
else
|
||||
line_out << ch;
|
||||
}
|
||||
|
||||
transformed_pos = pos;
|
||||
transformed_pos.rotate(angle);
|
||||
transformed_pos.translate(translation);
|
||||
|
||||
if (transformed_pos != old_pos) {
|
||||
line = line_out.str();
|
||||
char buf[2048] = "G1";
|
||||
if (transformed_pos.x != old_pos.x)
|
||||
sprintf(buf + strlen(buf), " X%.3f", transformed_pos.x);
|
||||
if (transformed_pos.y != old_pos.y)
|
||||
sprintf(buf + strlen(buf), " Y%.3f", transformed_pos.y);
|
||||
|
||||
line.replace(line.find("G1 "), 3, buf);
|
||||
old_pos = transformed_pos;
|
||||
}
|
||||
}
|
||||
gcode_out += line + "\n";
|
||||
}
|
||||
return gcode_out;
|
||||
}
|
||||
|
||||
|
||||
|
||||
std::string WipeTowerIntegration::prime(GCode &gcodegen)
|
||||
{
|
||||
assert(m_layer_idx == 0);
|
||||
|
@ -309,10 +372,12 @@ std::vector<std::pair<coordf_t, std::vector<GCode::LayerToPrint>>> GCode::collec
|
|||
size_t object_idx;
|
||||
size_t layer_idx;
|
||||
};
|
||||
std::vector<std::vector<LayerToPrint>> per_object(print.objects.size(), std::vector<LayerToPrint>());
|
||||
|
||||
PrintObjectPtrs printable_objects = print.get_printable_objects();
|
||||
std::vector<std::vector<LayerToPrint>> per_object(printable_objects.size(), std::vector<LayerToPrint>());
|
||||
std::vector<OrderingItem> ordering;
|
||||
for (size_t i = 0; i < print.objects.size(); ++ i) {
|
||||
per_object[i] = collect_layers_to_print(*print.objects[i]);
|
||||
for (size_t i = 0; i < printable_objects.size(); ++i) {
|
||||
per_object[i] = collect_layers_to_print(*printable_objects[i]);
|
||||
OrderingItem ordering_item;
|
||||
ordering_item.object_idx = i;
|
||||
ordering.reserve(ordering.size() + per_object[i].size());
|
||||
|
@ -337,8 +402,8 @@ std::vector<std::pair<coordf_t, std::vector<GCode::LayerToPrint>>> GCode::collec
|
|||
std::pair<coordf_t, std::vector<LayerToPrint>> merged;
|
||||
// Assign an average print_z to the set of layers with nearly equal print_z.
|
||||
merged.first = 0.5 * (ordering[i].print_z + ordering[j-1].print_z);
|
||||
merged.second.assign(print.objects.size(), LayerToPrint());
|
||||
for (; i < j; ++ i) {
|
||||
merged.second.assign(printable_objects.size(), LayerToPrint());
|
||||
for (; i < j; ++i) {
|
||||
const OrderingItem &oi = ordering[i];
|
||||
assert(merged.second[oi.object_idx].layer() == nullptr);
|
||||
merged.second[oi.object_idx] = std::move(per_object[oi.object_idx][oi.layer_idx]);
|
||||
|
@ -375,10 +440,12 @@ void GCode::do_export(Print *print, const char *path, GCodePreviewData *preview_
|
|||
}
|
||||
fclose(file);
|
||||
|
||||
m_normal_time_estimator.post_process_remaining_times(path_tmp, 60.0f);
|
||||
|
||||
if (m_silent_time_estimator_enabled)
|
||||
m_silent_time_estimator.post_process_remaining_times(path_tmp, 60.0f);
|
||||
if (print->config.remaining_times.value)
|
||||
{
|
||||
m_normal_time_estimator.post_process_remaining_times(path_tmp, 60.0f);
|
||||
if (m_silent_time_estimator_enabled)
|
||||
m_silent_time_estimator.post_process_remaining_times(path_tmp, 60.0f);
|
||||
}
|
||||
|
||||
if (! this->m_placeholder_parser_failed_templates.empty()) {
|
||||
// G-code export proceeded, but some of the PlaceholderParser substitutions failed.
|
||||
|
@ -457,8 +524,21 @@ void GCode::_do_export(Print &print, FILE *file, GCodePreviewData *preview_data)
|
|||
m_silent_time_estimator.set_axis_max_jerk(GCodeTimeEstimator::Y, print.config.machine_max_jerk_y.values[1]);
|
||||
m_silent_time_estimator.set_axis_max_jerk(GCodeTimeEstimator::Z, print.config.machine_max_jerk_z.values[1]);
|
||||
m_silent_time_estimator.set_axis_max_jerk(GCodeTimeEstimator::E, print.config.machine_max_jerk_e.values[1]);
|
||||
if (print.config.single_extruder_multi_material) {
|
||||
// As of now the fields are shown at the UI dialog in the same combo box as the ramming values, so they
|
||||
// are considered to be active for the single extruder multi-material printers only.
|
||||
m_silent_time_estimator.set_filament_load_times(print.config.filament_load_time.values);
|
||||
m_silent_time_estimator.set_filament_unload_times(print.config.filament_unload_time.values);
|
||||
}
|
||||
}
|
||||
}
|
||||
// Filament load / unload times are not specific to a firmware flavor. Let anybody use it if they find it useful.
|
||||
if (print.config.single_extruder_multi_material) {
|
||||
// As of now the fields are shown at the UI dialog in the same combo box as the ramming values, so they
|
||||
// are considered to be active for the single extruder multi-material printers only.
|
||||
m_normal_time_estimator.set_filament_load_times(print.config.filament_load_time.values);
|
||||
m_normal_time_estimator.set_filament_unload_times(print.config.filament_unload_time.values);
|
||||
}
|
||||
|
||||
// resets analyzer
|
||||
m_analyzer.reset();
|
||||
|
@ -472,9 +552,10 @@ void GCode::_do_export(Print &print, FILE *file, GCodePreviewData *preview_data)
|
|||
// How many times will be change_layer() called?
|
||||
// change_layer() in turn increments the progress bar status.
|
||||
m_layer_count = 0;
|
||||
PrintObjectPtrs printable_objects = print.get_printable_objects();
|
||||
if (print.config.complete_objects.value) {
|
||||
// Add each of the object's layers separately.
|
||||
for (auto object : print.objects) {
|
||||
for (auto object : printable_objects) {
|
||||
std::vector<coordf_t> zs;
|
||||
zs.reserve(object->layers.size() + object->support_layers.size());
|
||||
for (auto layer : object->layers)
|
||||
|
@ -487,7 +568,7 @@ void GCode::_do_export(Print &print, FILE *file, GCodePreviewData *preview_data)
|
|||
} else {
|
||||
// Print all objects with the same print_z together.
|
||||
std::vector<coordf_t> zs;
|
||||
for (auto object : print.objects) {
|
||||
for (auto object : printable_objects) {
|
||||
zs.reserve(zs.size() + object->layers.size() + object->support_layers.size());
|
||||
for (auto layer : object->layers)
|
||||
zs.push_back(layer->print_z);
|
||||
|
@ -506,8 +587,8 @@ void GCode::_do_export(Print &print, FILE *file, GCodePreviewData *preview_data)
|
|||
{
|
||||
// get the minimum cross-section used in the print
|
||||
std::vector<double> mm3_per_mm;
|
||||
for (auto object : print.objects) {
|
||||
for (size_t region_id = 0; region_id < print.regions.size(); ++ region_id) {
|
||||
for (auto object : printable_objects) {
|
||||
for (size_t region_id = 0; region_id < print.regions.size(); ++region_id) {
|
||||
auto region = print.regions[region_id];
|
||||
for (auto layer : object->layers) {
|
||||
auto layerm = layer->regions[region_id];
|
||||
|
@ -567,7 +648,7 @@ void GCode::_do_export(Print &print, FILE *file, GCodePreviewData *preview_data)
|
|||
_write(file, "\n");
|
||||
}
|
||||
// Write some terse information on the slicing parameters.
|
||||
const PrintObject *first_object = print.objects.front();
|
||||
const PrintObject *first_object = printable_objects.front();
|
||||
const double layer_height = first_object->config.layer_height.value;
|
||||
const double first_layer_height = first_object->config.first_layer_height.get_abs_value(layer_height);
|
||||
for (size_t region_id = 0; region_id < print.regions.size(); ++ region_id) {
|
||||
|
@ -596,20 +677,24 @@ void GCode::_do_export(Print &print, FILE *file, GCodePreviewData *preview_data)
|
|||
size_t initial_print_object_id = 0;
|
||||
bool has_wipe_tower = false;
|
||||
if (print.config.complete_objects.value) {
|
||||
// Find the 1st printing object, find its tool ordering and the initial extruder ID.
|
||||
for (; initial_print_object_id < print.objects.size(); ++initial_print_object_id) {
|
||||
tool_ordering = ToolOrdering(*print.objects[initial_print_object_id], initial_extruder_id);
|
||||
if ((initial_extruder_id = tool_ordering.first_extruder()) != (unsigned int)-1)
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
// Find the 1st printing object, find its tool ordering and the initial extruder ID.
|
||||
for (; initial_print_object_id < printable_objects.size(); ++initial_print_object_id) {
|
||||
tool_ordering = ToolOrdering(*printable_objects[initial_print_object_id], initial_extruder_id);
|
||||
if ((initial_extruder_id = tool_ordering.first_extruder()) != (unsigned int)-1)
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
// Find tool ordering for all the objects at once, and the initial extruder ID.
|
||||
// If the tool ordering has been pre-calculated by Print class for wipe tower already, reuse it.
|
||||
tool_ordering = print.m_tool_ordering.empty() ?
|
||||
ToolOrdering(print, initial_extruder_id) :
|
||||
print.m_tool_ordering;
|
||||
initial_extruder_id = tool_ordering.first_extruder();
|
||||
has_wipe_tower = print.has_wipe_tower() && tool_ordering.has_wipe_tower();
|
||||
initial_extruder_id = (has_wipe_tower && ! print.config.single_extruder_multi_material_priming) ?
|
||||
// The priming towers will be skipped.
|
||||
tool_ordering.all_extruders().back() :
|
||||
// Don't skip the priming towers.
|
||||
tool_ordering.first_extruder();
|
||||
}
|
||||
if (initial_extruder_id == (unsigned int)-1) {
|
||||
// Nothing to print!
|
||||
|
@ -637,6 +722,7 @@ void GCode::_do_export(Print &print, FILE *file, GCodePreviewData *preview_data)
|
|||
m_placeholder_parser.set("current_object_idx", 0);
|
||||
// For the start / end G-code to do the priming and final filament pull in case there is no wipe tower provided.
|
||||
m_placeholder_parser.set("has_wipe_tower", has_wipe_tower);
|
||||
m_placeholder_parser.set("has_single_extruder_multi_material_priming", has_wipe_tower && print.config.single_extruder_multi_material_priming);
|
||||
std::string start_gcode = this->placeholder_parser_process("start_gcode", print.config.start_gcode.value, initial_extruder_id);
|
||||
|
||||
// Set bed temperature if the start G-code does not contain any bed temp control G-codes.
|
||||
|
@ -676,7 +762,7 @@ void GCode::_do_export(Print &print, FILE *file, GCodePreviewData *preview_data)
|
|||
// Collect outer contours of all objects over all layers.
|
||||
// Discard objects only containing thin walls (offset would fail on an empty polygon).
|
||||
Polygons islands;
|
||||
for (const PrintObject *object : print.objects)
|
||||
for (const PrintObject *object : printable_objects)
|
||||
for (const Layer *layer : object->layers)
|
||||
for (const ExPolygon &expoly : layer->slices.expolygons)
|
||||
for (const Point © : object->_shifted_copies) {
|
||||
|
@ -717,15 +803,18 @@ void GCode::_do_export(Print &print, FILE *file, GCodePreviewData *preview_data)
|
|||
}
|
||||
}
|
||||
|
||||
// Set initial extruder only after custom start G-code.
|
||||
_write(file, this->set_extruder(initial_extruder_id));
|
||||
if (! (has_wipe_tower && print.config.single_extruder_multi_material_priming)) {
|
||||
// Set initial extruder only after custom start G-code.
|
||||
// Ugly hack: Do not set the initial extruder if the extruder is primed using the MMU priming towers at the edge of the print bed.
|
||||
_write(file, this->set_extruder(initial_extruder_id));
|
||||
}
|
||||
|
||||
// Do all objects for each layer.
|
||||
if (print.config.complete_objects.value) {
|
||||
// Print objects from the smallest to the tallest to avoid collisions
|
||||
// when moving onto next object starting point.
|
||||
std::vector<PrintObject*> objects(print.objects);
|
||||
std::sort(objects.begin(), objects.end(), [](const PrintObject* po1, const PrintObject* po2) { return po1->size(2) < po2->size(2); });
|
||||
std::vector<PrintObject*> objects(printable_objects);
|
||||
std::sort(objects.begin(), objects.end(), [](const PrintObject* po1, const PrintObject* po2) { return po1->size(2) < po2->size(2); });
|
||||
size_t finished_objects = 0;
|
||||
for (size_t object_id = initial_print_object_id; object_id < objects.size(); ++ object_id) {
|
||||
const PrintObject &object = *objects[object_id];
|
||||
|
@ -788,7 +877,7 @@ void GCode::_do_export(Print &print, FILE *file, GCodePreviewData *preview_data)
|
|||
PrintObjectPtrs printable_objects = print.get_printable_objects();
|
||||
for (PrintObject *object : printable_objects)
|
||||
object_reference_points.push_back(object->_shifted_copies.front());
|
||||
Slic3r::Geometry::chained_path(object_reference_points, object_indices);
|
||||
Slic3r::Geometry::chained_path(object_reference_points, object_indices);
|
||||
// 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);
|
||||
|
@ -796,27 +885,29 @@ void GCode::_do_export(Print &print, FILE *file, GCodePreviewData *preview_data)
|
|||
if (has_wipe_tower && ! layers_to_print.empty()) {
|
||||
m_wipe_tower.reset(new WipeTowerIntegration(print.config, *print.m_wipe_tower_priming.get(), print.m_wipe_tower_tool_changes, *print.m_wipe_tower_final_purge.get()));
|
||||
_write(file, m_writer.travel_to_z(first_layer_height + m_config.z_offset.value, "Move to the first layer height"));
|
||||
_write(file, m_wipe_tower->prime(*this));
|
||||
// Verify, whether the print overaps the priming extrusions.
|
||||
BoundingBoxf bbox_print(get_print_extrusions_extents(print));
|
||||
coordf_t twolayers_printz = ((layers_to_print.size() == 1) ? layers_to_print.front() : layers_to_print[1]).first + EPSILON;
|
||||
for (const PrintObject *print_object : printable_objects)
|
||||
bbox_print.merge(get_print_object_extrusions_extents(*print_object, twolayers_printz));
|
||||
bbox_print.merge(get_wipe_tower_extrusions_extents(print, twolayers_printz));
|
||||
BoundingBoxf bbox_prime(get_wipe_tower_priming_extrusions_extents(print));
|
||||
bbox_prime.offset(0.5f);
|
||||
// Beep for 500ms, tone 800Hz. Yet better, play some Morse.
|
||||
_write(file, this->retract());
|
||||
_write(file, "M300 S800 P500\n");
|
||||
if (bbox_prime.overlap(bbox_print)) {
|
||||
// Wait for the user to remove the priming extrusions, otherwise they would
|
||||
// get covered by the print.
|
||||
_write(file, "M1 Remove priming towers and click button.\n");
|
||||
}
|
||||
else {
|
||||
// Just wait for a bit to let the user check, that the priming succeeded.
|
||||
//TODO Add a message explaining what the printer is waiting for. This needs a firmware fix.
|
||||
_write(file, "M1 S10\n");
|
||||
if (print.config.single_extruder_multi_material_priming) {
|
||||
_write(file, m_wipe_tower->prime(*this));
|
||||
// Verify, whether the print overaps the priming extrusions.
|
||||
BoundingBoxf bbox_print(get_print_extrusions_extents(print));
|
||||
coordf_t twolayers_printz = ((layers_to_print.size() == 1) ? layers_to_print.front() : layers_to_print[1]).first + EPSILON;
|
||||
for (const PrintObject *print_object : printable_objects)
|
||||
bbox_print.merge(get_print_object_extrusions_extents(*print_object, twolayers_printz));
|
||||
bbox_print.merge(get_wipe_tower_extrusions_extents(print, twolayers_printz));
|
||||
BoundingBoxf bbox_prime(get_wipe_tower_priming_extrusions_extents(print));
|
||||
bbox_prime.offset(0.5f);
|
||||
// Beep for 500ms, tone 800Hz. Yet better, play some Morse.
|
||||
_write(file, this->retract());
|
||||
_write(file, "M300 S800 P500\n");
|
||||
if (bbox_prime.overlap(bbox_print)) {
|
||||
// Wait for the user to remove the priming extrusions, otherwise they would
|
||||
// get covered by the print.
|
||||
_write(file, "M1 Remove priming towers and click button.\n");
|
||||
}
|
||||
else {
|
||||
// Just wait for a bit to let the user check, that the priming succeeded.
|
||||
//TODO Add a message explaining what the printer is waiting for. This needs a firmware fix.
|
||||
_write(file, "M1 S10\n");
|
||||
}
|
||||
}
|
||||
}
|
||||
// Extrude the layers.
|
||||
|
@ -996,9 +1087,10 @@ void GCode::print_machine_envelope(FILE *file, Print &print)
|
|||
int(print.config.machine_max_feedrate_y.values.front() + 0.5),
|
||||
int(print.config.machine_max_feedrate_z.values.front() + 0.5),
|
||||
int(print.config.machine_max_feedrate_e.values.front() + 0.5));
|
||||
fprintf(file, "M204 S%d T%d ; sets acceleration (S) and retract acceleration (T), mm/sec^2\n",
|
||||
fprintf(file, "M204 P%d R%d T%d ; sets acceleration (P, T) and retract acceleration (R), mm/sec^2\n",
|
||||
int(print.config.machine_max_acceleration_extruding.values.front() + 0.5),
|
||||
int(print.config.machine_max_acceleration_retracting.values.front() + 0.5));
|
||||
int(print.config.machine_max_acceleration_retracting.values.front() + 0.5),
|
||||
int(print.config.machine_max_acceleration_extruding.values.front() + 0.5));
|
||||
fprintf(file, "M205 X%.2lf Y%.2lf Z%.2lf E%.2lf ; sets the jerk limits, mm/sec\n",
|
||||
print.config.machine_max_jerk_x.values.front(),
|
||||
print.config.machine_max_jerk_y.values.front(),
|
||||
|
|
|
@ -83,8 +83,10 @@ public:
|
|||
const WipeTower::ToolChangeResult &priming,
|
||||
const std::vector<std::vector<WipeTower::ToolChangeResult>> &tool_changes,
|
||||
const WipeTower::ToolChangeResult &final_purge) :
|
||||
m_left(float(print_config.wipe_tower_x.value)),
|
||||
m_right(float(print_config.wipe_tower_x.value + print_config.wipe_tower_width.value)),
|
||||
m_left(/*float(print_config.wipe_tower_x.value)*/ 0.f),
|
||||
m_right(float(/*print_config.wipe_tower_x.value +*/ print_config.wipe_tower_width.value)),
|
||||
m_wipe_tower_pos(float(print_config.wipe_tower_x.value), float(print_config.wipe_tower_y.value)),
|
||||
m_wipe_tower_rotation(float(print_config.wipe_tower_rotation_angle)),
|
||||
m_priming(priming),
|
||||
m_tool_changes(tool_changes),
|
||||
m_final_purge(final_purge),
|
||||
|
@ -101,9 +103,14 @@ private:
|
|||
WipeTowerIntegration& operator=(const WipeTowerIntegration&);
|
||||
std::string append_tcr(GCode &gcodegen, const WipeTower::ToolChangeResult &tcr, int new_extruder_id) const;
|
||||
|
||||
// Postprocesses gcode: rotates and moves all G1 extrusions and returns result
|
||||
std::string rotate_wipe_tower_moves(const std::string& gcode_original, const WipeTower::xy& start_pos, const WipeTower::xy& translation, float angle) const;
|
||||
|
||||
// Left / right edges of the wipe tower, for the planning of wipe moves.
|
||||
const float m_left;
|
||||
const float m_right;
|
||||
const WipeTower::xy m_wipe_tower_pos;
|
||||
const float m_wipe_tower_rotation;
|
||||
// Reference to cached values at the Printer class.
|
||||
const WipeTower::ToolChangeResult &m_priming;
|
||||
const std::vector<std::vector<WipeTower::ToolChangeResult>> &m_tool_changes;
|
||||
|
@ -112,6 +119,7 @@ private:
|
|||
int m_layer_idx;
|
||||
int m_tool_change_idx;
|
||||
bool m_brim_done;
|
||||
bool i_have_brim = false;
|
||||
};
|
||||
|
||||
class GCode {
|
||||
|
|
|
@ -134,6 +134,11 @@ BoundingBoxf get_print_object_extrusions_extents(const PrintObject &print_object
|
|||
// The projection does not contain the priming regions.
|
||||
BoundingBoxf get_wipe_tower_extrusions_extents(const Print &print, const coordf_t max_print_z)
|
||||
{
|
||||
// Wipe tower extrusions are saved as if the tower was at the origin with no rotation
|
||||
// We need to get position and angle of the wipe tower to transform them to actual position.
|
||||
Pointf wipe_tower_pos(print.config.wipe_tower_x.value, print.config.wipe_tower_y.value);
|
||||
float wipe_tower_angle = print.config.wipe_tower_rotation_angle.value;
|
||||
|
||||
BoundingBoxf bbox;
|
||||
for (const std::vector<WipeTower::ToolChangeResult> &tool_changes : print.m_wipe_tower_tool_changes) {
|
||||
if (! tool_changes.empty() && tool_changes.front().print_z > max_print_z)
|
||||
|
@ -144,6 +149,11 @@ BoundingBoxf get_wipe_tower_extrusions_extents(const Print &print, const coordf_
|
|||
if (e.width > 0) {
|
||||
Pointf p1((&e - 1)->pos.x, (&e - 1)->pos.y);
|
||||
Pointf p2(e.pos.x, e.pos.y);
|
||||
p1.rotate(wipe_tower_angle);
|
||||
p1 += wipe_tower_pos;
|
||||
p2.rotate(wipe_tower_angle);
|
||||
p2 += wipe_tower_pos;
|
||||
|
||||
bbox.merge(p1);
|
||||
coordf_t radius = 0.5 * e.width;
|
||||
bbox.min(0) = std::min(bbox.min(0), std::min(p1(0), p2(0)) - radius);
|
||||
|
|
|
@ -451,10 +451,9 @@ float WipingExtrusions::mark_wiping_extrusions(const Print& print, unsigned int
|
|||
return volume_to_wipe; // Soluble filament cannot be wiped in a random infill, neither the filament after it
|
||||
|
||||
// we will sort objects so that dedicated for wiping are at the beginning:
|
||||
PrintObjectPtrs object_list = print.objects;
|
||||
PrintObjectPtrs object_list = print.get_printable_objects();
|
||||
std::sort(object_list.begin(), object_list.end(), [](const PrintObject* a, const PrintObject* b) { return a->config.wipe_into_objects; });
|
||||
|
||||
|
||||
// We will now iterate through
|
||||
// - first the dedicated objects to mark perimeters or infills (depending on infill_first)
|
||||
// - second through the dedicated ones again to mark infills or perimeters (depending on infill_first)
|
||||
|
@ -548,7 +547,8 @@ void WipingExtrusions::ensure_perimeters_infills_order(const Print& print)
|
|||
unsigned int first_nonsoluble_extruder = first_nonsoluble_extruder_on_layer(print.config);
|
||||
unsigned int last_nonsoluble_extruder = last_nonsoluble_extruder_on_layer(print.config);
|
||||
|
||||
for (const PrintObject* object : print.objects) {
|
||||
PrintObjectPtrs printable_objects = print.get_printable_objects();
|
||||
for (const PrintObject* object : printable_objects) {
|
||||
// Finds this layer:
|
||||
auto this_layer_it = std::find_if(object->layers.begin(), object->layers.end(), [<](const Layer* lay) { return std::abs(lt.print_z - lay->print_z)<EPSILON; });
|
||||
if (this_layer_it == object->layers.end())
|
||||
|
|
|
@ -66,8 +66,10 @@ public:
|
|||
wipe_tower_partitions(0),
|
||||
wipe_tower_layer_height(0.) {}
|
||||
|
||||
bool operator< (const LayerTools &rhs) const { return print_z - EPSILON < rhs.print_z; }
|
||||
bool operator==(const LayerTools &rhs) const { return std::abs(print_z - rhs.print_z) < EPSILON; }
|
||||
// Changing these operators to epsilon version can make a problem in cases where support and object layers get close to each other.
|
||||
// In case someone tries to do it, make sure you know what you're doing and test it properly (slice multiple objects at once with supports).
|
||||
bool operator< (const LayerTools &rhs) const { return print_z < rhs.print_z; }
|
||||
bool operator==(const LayerTools &rhs) const { return print_z == rhs.print_z; }
|
||||
|
||||
bool is_extruder_order(unsigned int a, unsigned int b) const;
|
||||
|
||||
|
|
|
@ -25,18 +25,29 @@ public:
|
|||
bool operator==(const xy &rhs) const { return x == rhs.x && y == rhs.y; }
|
||||
bool operator!=(const xy &rhs) const { return x != rhs.x || y != rhs.y; }
|
||||
|
||||
// Rotate the point around given point about given angle (in degrees)
|
||||
// shifts the result so that point of rotation is in the middle of the tower
|
||||
xy rotate(const xy& origin, float width, float depth, float angle) const {
|
||||
// Rotate the point around center of the wipe tower about given angle (in degrees)
|
||||
xy rotate(float width, float depth, float angle) const {
|
||||
xy out(0,0);
|
||||
float temp_x = x - width / 2.f;
|
||||
float temp_y = y - depth / 2.f;
|
||||
angle *= float(M_PI/180.);
|
||||
out.x += (temp_x - origin.x) * cos(angle) - (temp_y - origin.y) * sin(angle);
|
||||
out.y += (temp_x - origin.x) * sin(angle) + (temp_y - origin.y) * cos(angle);
|
||||
return out + origin;
|
||||
out.x += temp_x * cos(angle) - temp_y * sin(angle) + width / 2.f;
|
||||
out.y += temp_x * sin(angle) + temp_y * cos(angle) + depth / 2.f;
|
||||
return out;
|
||||
}
|
||||
|
||||
|
||||
// Rotate the point around origin about given angle in degrees
|
||||
void rotate(float angle) {
|
||||
float temp_x = x * cos(angle) - y * sin(angle);
|
||||
y = x * sin(angle) + y * cos(angle);
|
||||
x = temp_x;
|
||||
}
|
||||
|
||||
void translate(const xy& vect) {
|
||||
x += vect.x;
|
||||
y += vect.y;
|
||||
}
|
||||
|
||||
float x;
|
||||
float y;
|
||||
};
|
||||
|
@ -104,6 +115,9 @@ public:
|
|||
// This is useful not only for the print time estimation, but also for the control of layer cooling.
|
||||
float elapsed_time;
|
||||
|
||||
// Is this a priming extrusion? (If so, the wipe tower rotation & translation will not be applied later)
|
||||
bool priming;
|
||||
|
||||
// Sum the total length of the extrusion.
|
||||
float total_extrusion_length_in_plane() {
|
||||
float e_length = 0.f;
|
||||
|
|
|
@ -5,7 +5,7 @@ TODO LIST
|
|||
|
||||
1. cooling moves - DONE
|
||||
2. account for perimeter and finish_layer extrusions and subtract it from last wipe - DONE
|
||||
3. priming extrusions (last wipe must clear the color)
|
||||
3. priming extrusions (last wipe must clear the color) - DONE
|
||||
4. Peter's wipe tower - layer's are not exactly square
|
||||
5. Peter's wipe tower - variable width for higher levels
|
||||
6. Peter's wipe tower - make sure it is not too sparse (apply max_bridge_distance and make last wipe longer)
|
||||
|
@ -17,7 +17,6 @@ TODO LIST
|
|||
|
||||
#include <assert.h>
|
||||
#include <math.h>
|
||||
#include <fstream>
|
||||
#include <iostream>
|
||||
#include <vector>
|
||||
#include <numeric>
|
||||
|
@ -68,8 +67,11 @@ public:
|
|||
return *this;
|
||||
}
|
||||
|
||||
Writer& set_initial_position(const WipeTower::xy &pos) {
|
||||
m_start_pos = WipeTower::xy(pos,0.f,m_y_shift).rotate(m_wipe_tower_pos, m_wipe_tower_width, m_wipe_tower_depth, m_angle_deg);
|
||||
Writer& set_initial_position(const WipeTower::xy &pos, float width = 0.f, float depth = 0.f, float internal_angle = 0.f) {
|
||||
m_wipe_tower_width = width;
|
||||
m_wipe_tower_depth = depth;
|
||||
m_internal_angle = internal_angle;
|
||||
m_start_pos = WipeTower::xy(pos,0.f,m_y_shift).rotate(m_wipe_tower_width, m_wipe_tower_depth, m_internal_angle);
|
||||
m_current_pos = pos;
|
||||
return *this;
|
||||
}
|
||||
|
@ -81,9 +83,6 @@ public:
|
|||
|
||||
Writer& set_extrusion_flow(float flow)
|
||||
{ m_extrusion_flow = flow; return *this; }
|
||||
|
||||
Writer& set_rotation(WipeTower::xy& pos, float width, float depth, float angle)
|
||||
{ m_wipe_tower_pos = pos; m_wipe_tower_width = width; m_wipe_tower_depth=depth; m_angle_deg = angle; return (*this); }
|
||||
|
||||
Writer& set_y_shift(float shift) {
|
||||
m_current_pos.y -= shift-m_y_shift;
|
||||
|
@ -110,7 +109,7 @@ public:
|
|||
float y() const { return m_current_pos.y; }
|
||||
const WipeTower::xy& pos() const { return m_current_pos; }
|
||||
const WipeTower::xy start_pos_rotated() const { return m_start_pos; }
|
||||
const WipeTower::xy pos_rotated() const { return WipeTower::xy(m_current_pos,0.f,m_y_shift).rotate(m_wipe_tower_pos, m_wipe_tower_width, m_wipe_tower_depth, m_angle_deg); }
|
||||
const WipeTower::xy pos_rotated() const { return WipeTower::xy(m_current_pos, 0.f, m_y_shift).rotate(m_wipe_tower_width, m_wipe_tower_depth, m_internal_angle); }
|
||||
float elapsed_time() const { return m_elapsed_time; }
|
||||
|
||||
// Extrude with an explicitely provided amount of extrusion.
|
||||
|
@ -125,9 +124,9 @@ public:
|
|||
double len = sqrt(dx*dx+dy*dy);
|
||||
|
||||
|
||||
// For rotated wipe tower, transform position to printer coordinates
|
||||
WipeTower::xy rotated_current_pos(WipeTower::xy(m_current_pos,0.f,m_y_shift).rotate(m_wipe_tower_pos, m_wipe_tower_width, m_wipe_tower_depth, m_angle_deg)); // this is where we are
|
||||
WipeTower::xy rot(WipeTower::xy(x,y+m_y_shift).rotate(m_wipe_tower_pos, m_wipe_tower_width, m_wipe_tower_depth, m_angle_deg)); // this is where we want to go
|
||||
// Now do the "internal rotation" with respect to the wipe tower center
|
||||
WipeTower::xy rotated_current_pos(WipeTower::xy(m_current_pos,0.f,m_y_shift).rotate(m_wipe_tower_width, m_wipe_tower_depth, m_internal_angle)); // this is where we are
|
||||
WipeTower::xy rot(WipeTower::xy(x,y+m_y_shift).rotate(m_wipe_tower_width, m_wipe_tower_depth, m_internal_angle)); // this is where we want to go
|
||||
|
||||
if (! m_preview_suppressed && e > 0.f && len > 0.) {
|
||||
// Width of a squished extrusion, corrected for the roundings of the squished extrusions.
|
||||
|
@ -147,6 +146,7 @@ public:
|
|||
if (std::abs(rot.y - rotated_current_pos.y) > EPSILON)
|
||||
m_gcode += set_format_Y(rot.y);
|
||||
|
||||
|
||||
if (e != 0.f)
|
||||
m_gcode += set_format_E(e);
|
||||
|
||||
|
@ -397,9 +397,8 @@ private:
|
|||
std::string m_gcode;
|
||||
std::vector<WipeTower::Extrusion> m_extrusions;
|
||||
float m_elapsed_time;
|
||||
float m_angle_deg = 0.f;
|
||||
float m_internal_angle = 0.f;
|
||||
float m_y_shift = 0.f;
|
||||
WipeTower::xy m_wipe_tower_pos;
|
||||
float m_wipe_tower_width = 0.f;
|
||||
float m_wipe_tower_depth = 0.f;
|
||||
float m_last_fan_speed = 0.f;
|
||||
|
@ -490,7 +489,7 @@ WipeTower::ToolChangeResult WipeTowerPrusaMM::prime(
|
|||
// box_coordinates cleaning_box(xy(0.5f, - 1.5f), m_wipe_tower_width, wipe_area);
|
||||
|
||||
const float prime_section_width = std::min(240.f / tools.size(), 60.f);
|
||||
box_coordinates cleaning_box(xy(5.f, 0.f), prime_section_width, 100.f);
|
||||
box_coordinates cleaning_box(xy(5.f, 0.01f + m_perimeter_width/2.f), prime_section_width, 100.f);
|
||||
|
||||
PrusaMultiMaterial::Writer writer(m_layer_height, m_perimeter_width);
|
||||
writer.set_extrusion_flow(m_extrusion_flow)
|
||||
|
@ -539,6 +538,7 @@ WipeTower::ToolChangeResult WipeTowerPrusaMM::prime(
|
|||
m_print_brim = true;
|
||||
|
||||
ToolChangeResult result;
|
||||
result.priming = true;
|
||||
result.print_z = this->m_z_pos;
|
||||
result.layer_height = this->m_layer_height;
|
||||
result.gcode = writer.gcode();
|
||||
|
@ -575,7 +575,7 @@ WipeTower::ToolChangeResult WipeTowerPrusaMM::tool_change(unsigned int tool, boo
|
|||
}
|
||||
|
||||
box_coordinates cleaning_box(
|
||||
m_wipe_tower_pos + xy(m_perimeter_width / 2.f, m_perimeter_width / 2.f),
|
||||
xy(m_perimeter_width / 2.f, m_perimeter_width / 2.f),
|
||||
m_wipe_tower_width - m_perimeter_width,
|
||||
(tool != (unsigned int)(-1) ? /*m_layer_info->depth*/wipe_area+m_depth_traversed-0.5*m_perimeter_width
|
||||
: m_wipe_tower_depth-m_perimeter_width));
|
||||
|
@ -584,7 +584,6 @@ WipeTower::ToolChangeResult WipeTowerPrusaMM::tool_change(unsigned int tool, boo
|
|||
writer.set_extrusion_flow(m_extrusion_flow)
|
||||
.set_z(m_z_pos)
|
||||
.set_initial_tool(m_current_tool)
|
||||
.set_rotation(m_wipe_tower_pos, m_wipe_tower_width, m_wipe_tower_depth, m_wipe_tower_rotation_angle)
|
||||
.set_y_shift(m_y_shift + (tool!=(unsigned int)(-1) && (m_current_shape == SHAPE_REVERSED && !m_peters_wipe_tower) ? m_layer_info->depth - m_layer_info->toolchanges_depth(): 0.f))
|
||||
.append(";--------------------\n"
|
||||
"; CP TOOLCHANGE START\n")
|
||||
|
@ -594,7 +593,7 @@ WipeTower::ToolChangeResult WipeTowerPrusaMM::tool_change(unsigned int tool, boo
|
|||
.speed_override(100);
|
||||
|
||||
xy initial_position = cleaning_box.ld + WipeTower::xy(0.f,m_depth_traversed);
|
||||
writer.set_initial_position(initial_position);
|
||||
writer.set_initial_position(initial_position, m_wipe_tower_width, m_wipe_tower_depth, m_internal_rotation);
|
||||
|
||||
// Increase the extruder driver current to allow fast ramming.
|
||||
writer.set_extruder_trimpot(750);
|
||||
|
@ -616,11 +615,11 @@ WipeTower::ToolChangeResult WipeTowerPrusaMM::tool_change(unsigned int tool, boo
|
|||
if (last_change_in_layer) {// draw perimeter line
|
||||
writer.set_y_shift(m_y_shift);
|
||||
if (m_peters_wipe_tower)
|
||||
writer.rectangle(m_wipe_tower_pos,m_layer_info->depth + 3*m_perimeter_width,m_wipe_tower_depth);
|
||||
writer.rectangle(WipeTower::xy(0.f, 0.f),m_layer_info->depth + 3*m_perimeter_width,m_wipe_tower_depth);
|
||||
else {
|
||||
writer.rectangle(m_wipe_tower_pos,m_wipe_tower_width, m_layer_info->depth + m_perimeter_width);
|
||||
writer.rectangle(WipeTower::xy(0.f, 0.f),m_wipe_tower_width, m_layer_info->depth + m_perimeter_width);
|
||||
if (layer_finished()) { // no finish_layer will be called, we must wipe the nozzle
|
||||
writer.travel(m_wipe_tower_pos.x + (writer.x()> (m_wipe_tower_pos.x + m_wipe_tower_width) / 2.f ? 0.f : m_wipe_tower_width), writer.y());
|
||||
writer.travel(writer.x()> m_wipe_tower_width / 2.f ? 0.f : m_wipe_tower_width, writer.y());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -634,6 +633,7 @@ WipeTower::ToolChangeResult WipeTowerPrusaMM::tool_change(unsigned int tool, boo
|
|||
"\n\n");
|
||||
|
||||
ToolChangeResult result;
|
||||
result.priming = false;
|
||||
result.print_z = this->m_z_pos;
|
||||
result.layer_height = this->m_layer_height;
|
||||
result.gcode = writer.gcode();
|
||||
|
@ -647,7 +647,7 @@ WipeTower::ToolChangeResult WipeTowerPrusaMM::tool_change(unsigned int tool, boo
|
|||
WipeTower::ToolChangeResult WipeTowerPrusaMM::toolchange_Brim(bool sideOnly, float y_offset)
|
||||
{
|
||||
const box_coordinates wipeTower_box(
|
||||
m_wipe_tower_pos,
|
||||
WipeTower::xy(0.f, 0.f),
|
||||
m_wipe_tower_width,
|
||||
m_wipe_tower_depth);
|
||||
|
||||
|
@ -655,12 +655,11 @@ WipeTower::ToolChangeResult WipeTowerPrusaMM::toolchange_Brim(bool sideOnly, flo
|
|||
writer.set_extrusion_flow(m_extrusion_flow * 1.1f)
|
||||
.set_z(m_z_pos) // Let the writer know the current Z position as a base for Z-hop.
|
||||
.set_initial_tool(m_current_tool)
|
||||
.set_rotation(m_wipe_tower_pos, m_wipe_tower_width, m_wipe_tower_depth, m_wipe_tower_rotation_angle)
|
||||
.append(";-------------------------------------\n"
|
||||
"; CP WIPE TOWER FIRST LAYER BRIM START\n");
|
||||
|
||||
xy initial_position = wipeTower_box.lu - xy(m_perimeter_width * 6.f, 0);
|
||||
writer.set_initial_position(initial_position);
|
||||
writer.set_initial_position(initial_position, m_wipe_tower_width, m_wipe_tower_depth, m_internal_rotation);
|
||||
|
||||
writer.extrude_explicit(wipeTower_box.ld - xy(m_perimeter_width * 6.f, 0), // Prime the extruder left of the wipe tower.
|
||||
1.5f * m_extrusion_flow * (wipeTower_box.lu.y - wipeTower_box.ld.y), 2400);
|
||||
|
@ -685,6 +684,7 @@ WipeTower::ToolChangeResult WipeTowerPrusaMM::toolchange_Brim(bool sideOnly, flo
|
|||
m_print_brim = false; // Mark the brim as extruded
|
||||
|
||||
ToolChangeResult result;
|
||||
result.priming = false;
|
||||
result.print_z = this->m_z_pos;
|
||||
result.layer_height = this->m_layer_height;
|
||||
result.gcode = writer.gcode();
|
||||
|
@ -724,7 +724,7 @@ void WipeTowerPrusaMM::toolchange_Unload(
|
|||
if (m_layer_info > m_plan.begin() && m_layer_info < m_plan.end() && (m_layer_info-1!=m_plan.begin() || !m_adhesion )) {
|
||||
|
||||
// this is y of the center of previous sparse infill border
|
||||
float sparse_beginning_y = m_wipe_tower_pos.y;
|
||||
float sparse_beginning_y = 0.f;
|
||||
if (m_current_shape == SHAPE_REVERSED)
|
||||
sparse_beginning_y += ((m_layer_info-1)->depth - (m_layer_info-1)->toolchanges_depth())
|
||||
- ((m_layer_info)->depth-(m_layer_info)->toolchanges_depth()) ;
|
||||
|
@ -742,7 +742,7 @@ void WipeTowerPrusaMM::toolchange_Unload(
|
|||
for (const auto& tch : m_layer_info->tool_changes) { // let's find this toolchange
|
||||
if (tch.old_tool == m_current_tool) {
|
||||
sum_of_depths += tch.ramming_depth;
|
||||
float ramming_end_y = m_wipe_tower_pos.y + sum_of_depths;
|
||||
float ramming_end_y = sum_of_depths;
|
||||
ramming_end_y -= (y_step/m_extra_spacing-m_perimeter_width) / 2.f; // center of final ramming line
|
||||
|
||||
// debugging:
|
||||
|
@ -950,7 +950,7 @@ void WipeTowerPrusaMM::toolchange_Wipe(
|
|||
if (m_layer_info != m_plan.end() && m_current_tool != m_layer_info->tool_changes.back().new_tool) {
|
||||
m_left_to_right = !m_left_to_right;
|
||||
writer.travel(writer.x(), writer.y() - dy)
|
||||
.travel(m_wipe_tower_pos.x + (m_left_to_right ? m_wipe_tower_width : 0.f), writer.y());
|
||||
.travel(m_left_to_right ? m_wipe_tower_width : 0.f, writer.y());
|
||||
}
|
||||
|
||||
writer.set_extrusion_flow(m_extrusion_flow); // Reset the extrusion flow.
|
||||
|
@ -969,7 +969,6 @@ WipeTower::ToolChangeResult WipeTowerPrusaMM::finish_layer()
|
|||
writer.set_extrusion_flow(m_extrusion_flow)
|
||||
.set_z(m_z_pos)
|
||||
.set_initial_tool(m_current_tool)
|
||||
.set_rotation(m_wipe_tower_pos, m_wipe_tower_width, m_wipe_tower_depth, m_wipe_tower_rotation_angle)
|
||||
.set_y_shift(m_y_shift - (m_current_shape == SHAPE_REVERSED && !m_peters_wipe_tower ? m_layer_info->toolchanges_depth() : 0.f))
|
||||
.append(";--------------------\n"
|
||||
"; CP EMPTY GRID START\n")
|
||||
|
@ -978,14 +977,12 @@ WipeTower::ToolChangeResult WipeTowerPrusaMM::finish_layer()
|
|||
// Slow down on the 1st layer.
|
||||
float speed_factor = m_is_first_layer ? 0.5f : 1.f;
|
||||
float current_depth = m_layer_info->depth - m_layer_info->toolchanges_depth();
|
||||
box_coordinates fill_box(m_wipe_tower_pos + xy(m_perimeter_width, m_depth_traversed + m_perimeter_width),
|
||||
box_coordinates fill_box(xy(m_perimeter_width, m_depth_traversed + m_perimeter_width),
|
||||
m_wipe_tower_width - 2 * m_perimeter_width, current_depth-m_perimeter_width);
|
||||
|
||||
if (m_left_to_right) // so there is never a diagonal travel
|
||||
writer.set_initial_position(fill_box.ru);
|
||||
else
|
||||
writer.set_initial_position(fill_box.lu);
|
||||
|
||||
writer.set_initial_position((m_left_to_right ? fill_box.ru : fill_box.lu), // so there is never a diagonal travel
|
||||
m_wipe_tower_width, m_wipe_tower_depth, m_internal_rotation);
|
||||
|
||||
box_coordinates box = fill_box;
|
||||
for (int i=0;i<2;++i) {
|
||||
|
@ -1044,6 +1041,7 @@ WipeTower::ToolChangeResult WipeTowerPrusaMM::finish_layer()
|
|||
m_depth_traversed = m_wipe_tower_depth-m_perimeter_width;
|
||||
|
||||
ToolChangeResult result;
|
||||
result.priming = false;
|
||||
result.print_z = this->m_z_pos;
|
||||
result.layer_height = this->m_layer_height;
|
||||
result.gcode = writer.gcode();
|
||||
|
@ -1165,9 +1163,9 @@ void WipeTowerPrusaMM::generate(std::vector<std::vector<WipeTower::ToolChangeRes
|
|||
{
|
||||
set_layer(layer.z,layer.height,0,layer.z == m_plan.front().z,layer.z == m_plan.back().z);
|
||||
if (m_peters_wipe_tower)
|
||||
m_wipe_tower_rotation_angle += 90.f;
|
||||
m_internal_rotation += 90.f;
|
||||
else
|
||||
m_wipe_tower_rotation_angle += 180.f;
|
||||
m_internal_rotation += 180.f;
|
||||
|
||||
if (!m_peters_wipe_tower && m_layer_info->depth < m_wipe_tower_depth - m_perimeter_width)
|
||||
m_y_shift = (m_wipe_tower_depth-m_layer_info->depth-m_perimeter_width)/2.f;
|
||||
|
@ -1188,7 +1186,7 @@ void WipeTowerPrusaMM::generate(std::vector<std::vector<WipeTower::ToolChangeRes
|
|||
last_toolchange.gcode += buf;
|
||||
}
|
||||
last_toolchange.gcode += finish_layer_toolchange.gcode;
|
||||
last_toolchange.extrusions.insert(last_toolchange.extrusions.end(),finish_layer_toolchange.extrusions.begin(),finish_layer_toolchange.extrusions.end());
|
||||
last_toolchange.extrusions.insert(last_toolchange.extrusions.end(), finish_layer_toolchange.extrusions.begin(), finish_layer_toolchange.extrusions.end());
|
||||
last_toolchange.end_pos = finish_layer_toolchange.end_pos;
|
||||
}
|
||||
else
|
||||
|
|
|
@ -102,6 +102,8 @@ public:
|
|||
// Iterates through prepared m_plan, generates ToolChangeResults and appends them to "result"
|
||||
void generate(std::vector<std::vector<WipeTower::ToolChangeResult>> &result);
|
||||
|
||||
float get_depth() const { return m_wipe_tower_depth; }
|
||||
|
||||
|
||||
|
||||
// Switch to a next layer.
|
||||
|
@ -189,6 +191,7 @@ private:
|
|||
float m_wipe_tower_width; // Width of the wipe tower.
|
||||
float m_wipe_tower_depth = 0.f; // Depth of the wipe tower
|
||||
float m_wipe_tower_rotation_angle = 0.f; // Wipe tower rotation angle in degrees (with respect to x axis)
|
||||
float m_internal_rotation = 0.f;
|
||||
float m_y_shift = 0.f; // y shift passed to writer
|
||||
float m_z_pos = 0.f; // Current Z position.
|
||||
float m_layer_height = 0.f; // Current layer height.
|
||||
|
|
|
@ -114,6 +114,28 @@ void GCodeReader::parse_file(const std::string &file, callback_t callback)
|
|||
this->parse_line(line, callback);
|
||||
}
|
||||
|
||||
bool GCodeReader::GCodeLine::has(char axis) const
|
||||
{
|
||||
const char *c = m_raw.c_str();
|
||||
// Skip the whitespaces.
|
||||
c = skip_whitespaces(c);
|
||||
// Skip the command.
|
||||
c = skip_word(c);
|
||||
// Up to the end of line or comment.
|
||||
while (! is_end_of_gcode_line(*c)) {
|
||||
// Skip whitespaces.
|
||||
c = skip_whitespaces(c);
|
||||
if (is_end_of_gcode_line(*c))
|
||||
break;
|
||||
// Check the name of the axis.
|
||||
if (*c == axis)
|
||||
return true;
|
||||
// Skip the rest of the word.
|
||||
c = skip_word(c);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool GCodeReader::GCodeLine::has_value(char axis, float &value) const
|
||||
{
|
||||
const char *c = m_raw.c_str();
|
||||
|
|
|
@ -27,6 +27,7 @@ public:
|
|||
|
||||
bool has(Axis axis) const { return (m_mask & (1 << int(axis))) != 0; }
|
||||
float value(Axis axis) const { return m_axis[axis]; }
|
||||
bool has(char axis) const;
|
||||
bool has_value(char axis, float &value) const;
|
||||
float new_Z(const GCodeReader &reader) const { return this->has(Z) ? this->z() : reader.z(); }
|
||||
float new_E(const GCodeReader &reader) const { return this->has(E) ? this->e() : reader.e(); }
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
#include <iostream>
|
||||
#include <istream>
|
||||
#include <string>
|
||||
#include <thread>
|
||||
#include <boost/algorithm/string/predicate.hpp>
|
||||
#include <boost/algorithm/string/trim.hpp>
|
||||
#include <boost/date_time/posix_time/posix_time.hpp>
|
||||
|
@ -568,16 +569,12 @@ GCodeSender::set_DTR(bool on)
|
|||
void
|
||||
GCodeSender::reset()
|
||||
{
|
||||
this->set_DTR(false);
|
||||
boost::this_thread::sleep(boost::posix_time::milliseconds(200));
|
||||
this->set_DTR(true);
|
||||
boost::this_thread::sleep(boost::posix_time::milliseconds(200));
|
||||
this->set_DTR(false);
|
||||
boost::this_thread::sleep(boost::posix_time::milliseconds(1000));
|
||||
{
|
||||
boost::lock_guard<boost::mutex> l(this->queue_mutex);
|
||||
this->can_send = true;
|
||||
}
|
||||
set_DTR(false);
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(200));
|
||||
set_DTR(true);
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(200));
|
||||
set_DTR(false);
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(500));
|
||||
}
|
||||
|
||||
} // namespace Slic3r
|
||||
|
|
|
@ -469,6 +469,40 @@ namespace Slic3r {
|
|||
return _state.minimum_travel_feedrate;
|
||||
}
|
||||
|
||||
void GCodeTimeEstimator::set_filament_load_times(const std::vector<double> &filament_load_times)
|
||||
{
|
||||
_state.filament_load_times.clear();
|
||||
for (double t : filament_load_times)
|
||||
_state.filament_load_times.push_back(t);
|
||||
}
|
||||
|
||||
void GCodeTimeEstimator::set_filament_unload_times(const std::vector<double> &filament_unload_times)
|
||||
{
|
||||
_state.filament_unload_times.clear();
|
||||
for (double t : filament_unload_times)
|
||||
_state.filament_unload_times.push_back(t);
|
||||
}
|
||||
|
||||
float GCodeTimeEstimator::get_filament_load_time(unsigned int id_extruder)
|
||||
{
|
||||
return
|
||||
(_state.filament_load_times.empty() || id_extruder == _state.extruder_id_unloaded) ?
|
||||
0 :
|
||||
(_state.filament_load_times.size() <= id_extruder) ?
|
||||
_state.filament_load_times.front() :
|
||||
_state.filament_load_times[id_extruder];
|
||||
}
|
||||
|
||||
float GCodeTimeEstimator::get_filament_unload_time(unsigned int id_extruder)
|
||||
{
|
||||
return
|
||||
(_state.filament_unload_times.empty() || id_extruder == _state.extruder_id_unloaded) ?
|
||||
0 :
|
||||
(_state.filament_unload_times.size() <= id_extruder) ?
|
||||
_state.filament_unload_times.front() :
|
||||
_state.filament_unload_times[id_extruder];
|
||||
}
|
||||
|
||||
void GCodeTimeEstimator::set_extrude_factor_override_percentage(float percentage)
|
||||
{
|
||||
_state.extrude_factor_override_percentage = percentage;
|
||||
|
@ -535,6 +569,23 @@ namespace Slic3r {
|
|||
_state.g1_line_id = 0;
|
||||
}
|
||||
|
||||
void GCodeTimeEstimator::set_extruder_id(unsigned int id)
|
||||
{
|
||||
_state.extruder_id = id;
|
||||
}
|
||||
|
||||
unsigned int GCodeTimeEstimator::get_extruder_id() const
|
||||
{
|
||||
return _state.extruder_id;
|
||||
}
|
||||
|
||||
void GCodeTimeEstimator::reset_extruder_id()
|
||||
{
|
||||
// Set the initial extruder ID to unknown. For the multi-material setup it means
|
||||
// that all the filaments are parked in the MMU and no filament is loaded yet.
|
||||
_state.extruder_id = _state.extruder_id_unloaded;
|
||||
}
|
||||
|
||||
void GCodeTimeEstimator::add_additional_time(float timeSec)
|
||||
{
|
||||
PROFILE_FUNC();
|
||||
|
@ -575,6 +626,9 @@ namespace Slic3r {
|
|||
set_axis_max_acceleration(axis, DEFAULT_AXIS_MAX_ACCELERATION[a]);
|
||||
set_axis_max_jerk(axis, DEFAULT_AXIS_MAX_JERK[a]);
|
||||
}
|
||||
|
||||
_state.filament_load_times.clear();
|
||||
_state.filament_unload_times.clear();
|
||||
}
|
||||
|
||||
void GCodeTimeEstimator::reset()
|
||||
|
@ -613,6 +667,7 @@ namespace Slic3r {
|
|||
|
||||
set_additional_time(0.0f);
|
||||
|
||||
reset_extruder_id();
|
||||
reset_g1_line_id();
|
||||
_g1_line_ids.clear();
|
||||
|
||||
|
@ -666,6 +721,8 @@ namespace Slic3r {
|
|||
}
|
||||
|
||||
_last_st_synchronized_block_id = _blocks.size() - 1;
|
||||
// The additional time has been consumed (added to the total time), reset it to zero.
|
||||
set_additional_time(0.);
|
||||
}
|
||||
|
||||
void GCodeTimeEstimator::_process_gcode_line(GCodeReader&, const GCodeReader::GCodeLine& line)
|
||||
|
@ -778,8 +835,18 @@ namespace Slic3r {
|
|||
_processM566(line);
|
||||
break;
|
||||
}
|
||||
case 702: // MK3 MMU2: Process the final filament unload.
|
||||
{
|
||||
_processM702(line);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
case 'T': // Select Tools
|
||||
{
|
||||
_processT(line);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
@ -1164,11 +1231,25 @@ namespace Slic3r {
|
|||
{
|
||||
PROFILE_FUNC();
|
||||
float value;
|
||||
if (line.has_value('S', value))
|
||||
if (line.has_value('S', value)) {
|
||||
// Legacy acceleration format. This format is used by the legacy Marlin, MK2 or MK3 firmware,
|
||||
// and it is also generated by Slic3r to control acceleration per extrusion type
|
||||
// (there is a separate acceleration settings in Slicer for perimeter, first layer etc).
|
||||
set_acceleration(value);
|
||||
|
||||
if (line.has_value('T', value))
|
||||
set_retract_acceleration(value);
|
||||
if (line.has_value('T', value))
|
||||
set_retract_acceleration(value);
|
||||
} else {
|
||||
// New acceleration format, compatible with the upstream Marlin.
|
||||
if (line.has_value('P', value))
|
||||
set_acceleration(value);
|
||||
if (line.has_value('R', value))
|
||||
set_retract_acceleration(value);
|
||||
if (line.has_value('T', value)) {
|
||||
// Interpret the T value as the travel acceleration in the new Marlin format.
|
||||
//FIXME Prusa3D firmware currently does not support travel acceleration value independent from the extruding acceleration value.
|
||||
// set_travel_acceleration(value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void GCodeTimeEstimator::_processM205(const GCodeReader::GCodeLine& line)
|
||||
|
@ -1223,6 +1304,37 @@ namespace Slic3r {
|
|||
set_axis_max_jerk(E, line.e() * MMMIN_TO_MMSEC);
|
||||
}
|
||||
|
||||
void GCodeTimeEstimator::_processM702(const GCodeReader::GCodeLine& line)
|
||||
{
|
||||
PROFILE_FUNC();
|
||||
if (line.has('C')) {
|
||||
// MK3 MMU2 specific M code:
|
||||
// M702 C is expected to be sent by the custom end G-code when finalizing a print.
|
||||
// The MK3 unit shall unload and park the active filament into the MMU2 unit.
|
||||
add_additional_time(get_filament_unload_time(get_extruder_id()));
|
||||
reset_extruder_id();
|
||||
_simulate_st_synchronize();
|
||||
}
|
||||
}
|
||||
|
||||
void GCodeTimeEstimator::_processT(const GCodeReader::GCodeLine& line)
|
||||
{
|
||||
std::string cmd = line.cmd();
|
||||
if (cmd.length() > 1)
|
||||
{
|
||||
unsigned int id = (unsigned int)::strtol(cmd.substr(1).c_str(), nullptr, 10);
|
||||
if (get_extruder_id() != id)
|
||||
{
|
||||
// Specific to the MK3 MMU2: The initial extruder ID is set to -1 indicating
|
||||
// that the filament is parked in the MMU2 unit and there is nothing to be unloaded yet.
|
||||
add_additional_time(get_filament_unload_time(get_extruder_id()));
|
||||
set_extruder_id(id);
|
||||
add_additional_time(get_filament_load_time(get_extruder_id()));
|
||||
_simulate_st_synchronize();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void GCodeTimeEstimator::_simulate_st_synchronize()
|
||||
{
|
||||
PROFILE_FUNC();
|
||||
|
|
|
@ -79,7 +79,15 @@ namespace Slic3r {
|
|||
float minimum_feedrate; // mm/s
|
||||
float minimum_travel_feedrate; // mm/s
|
||||
float extrude_factor_override_percentage;
|
||||
// Additional load / unload times for a filament exchange sequence.
|
||||
std::vector<float> filament_load_times;
|
||||
std::vector<float> filament_unload_times;
|
||||
unsigned int g1_line_id;
|
||||
// extruder_id is currently used to correctly calculate filament load / unload times
|
||||
// into the total print time. This is currently only really used by the MK3 MMU2:
|
||||
// Extruder id (-1) means no filament is loaded yet, all the filaments are parked in the MK3 MMU2 unit.
|
||||
static const unsigned int extruder_id_unloaded = (unsigned int)-1;
|
||||
unsigned int extruder_id;
|
||||
};
|
||||
|
||||
public:
|
||||
|
@ -281,6 +289,11 @@ namespace Slic3r {
|
|||
void set_minimum_travel_feedrate(float feedrate_mm_sec);
|
||||
float get_minimum_travel_feedrate() const;
|
||||
|
||||
void set_filament_load_times(const std::vector<double> &filament_load_times);
|
||||
void set_filament_unload_times(const std::vector<double> &filament_unload_times);
|
||||
float get_filament_load_time(unsigned int id_extruder);
|
||||
float get_filament_unload_time(unsigned int id_extruder);
|
||||
|
||||
void set_extrude_factor_override_percentage(float percentage);
|
||||
float get_extrude_factor_override_percentage() const;
|
||||
|
||||
|
@ -300,6 +313,10 @@ namespace Slic3r {
|
|||
void increment_g1_line_id();
|
||||
void reset_g1_line_id();
|
||||
|
||||
void set_extruder_id(unsigned int id);
|
||||
unsigned int get_extruder_id() const;
|
||||
void reset_extruder_id();
|
||||
|
||||
void add_additional_time(float timeSec);
|
||||
void set_additional_time(float timeSec);
|
||||
float get_additional_time() const;
|
||||
|
@ -383,6 +400,12 @@ namespace Slic3r {
|
|||
// Set allowable instantaneous speed change
|
||||
void _processM566(const GCodeReader::GCodeLine& line);
|
||||
|
||||
// Unload the current filament into the MK3 MMU2 unit at the end of print.
|
||||
void _processM702(const GCodeReader::GCodeLine& line);
|
||||
|
||||
// Processes T line (Select Tool)
|
||||
void _processT(const GCodeReader::GCodeLine& line);
|
||||
|
||||
// Simulates firmware st_synchronize() call
|
||||
void _simulate_st_synchronize();
|
||||
|
||||
|
|
|
@ -7,11 +7,6 @@
|
|||
#include "Format/STL.hpp"
|
||||
#include "Format/3mf.hpp"
|
||||
|
||||
#include <numeric>
|
||||
#include <libnest2d.h>
|
||||
#include <ClipperUtils.hpp>
|
||||
#include "slic3r/GUI/GUI.hpp"
|
||||
|
||||
#include <float.h>
|
||||
|
||||
#include <boost/algorithm/string/predicate.hpp>
|
||||
|
@ -302,369 +297,36 @@ static bool _arrange(const Pointfs &sizes, coordf_t dist, const BoundingBoxf* bb
|
|||
return result;
|
||||
}
|
||||
|
||||
namespace arr {
|
||||
|
||||
using namespace libnest2d;
|
||||
|
||||
std::string toString(const Model& model, bool holes = true) {
|
||||
std::stringstream ss;
|
||||
|
||||
ss << "{\n";
|
||||
|
||||
for(auto objptr : model.objects) {
|
||||
if(!objptr) continue;
|
||||
|
||||
auto rmesh = objptr->raw_mesh();
|
||||
|
||||
for(auto objinst : objptr->instances) {
|
||||
if(!objinst) continue;
|
||||
|
||||
Slic3r::TriangleMesh tmpmesh = rmesh;
|
||||
tmpmesh.scale(objinst->scaling_factor);
|
||||
objinst->transform_mesh(&tmpmesh);
|
||||
ExPolygons expolys = tmpmesh.horizontal_projection();
|
||||
for(auto& expoly_complex : expolys) {
|
||||
|
||||
auto tmp = expoly_complex.simplify(1.0/SCALING_FACTOR);
|
||||
if(tmp.empty()) continue;
|
||||
auto expoly = tmp.front();
|
||||
expoly.contour.make_clockwise();
|
||||
for(auto& h : expoly.holes) h.make_counter_clockwise();
|
||||
|
||||
ss << "\t{\n";
|
||||
ss << "\t\t{\n";
|
||||
|
||||
for(auto v : expoly.contour.points) ss << "\t\t\t{"
|
||||
<< v(0) << ", "
|
||||
<< v(1) << "},\n";
|
||||
{
|
||||
auto v = expoly.contour.points.front();
|
||||
ss << "\t\t\t{" << v(0) << ", " << v(1) << "},\n";
|
||||
}
|
||||
ss << "\t\t},\n";
|
||||
|
||||
// Holes:
|
||||
ss << "\t\t{\n";
|
||||
if(holes) for(auto h : expoly.holes) {
|
||||
ss << "\t\t\t{\n";
|
||||
for(auto v : h.points) ss << "\t\t\t\t{"
|
||||
<< v(0) << ", "
|
||||
<< v(1) << "},\n";
|
||||
{
|
||||
auto v = h.points.front();
|
||||
ss << "\t\t\t\t{" << v(0) << ", " << v(1) << "},\n";
|
||||
}
|
||||
ss << "\t\t\t},\n";
|
||||
}
|
||||
ss << "\t\t},\n";
|
||||
|
||||
ss << "\t},\n";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ss << "}\n";
|
||||
|
||||
return ss.str();
|
||||
}
|
||||
|
||||
void toSVG(SVG& svg, const Model& model) {
|
||||
for(auto objptr : model.objects) {
|
||||
if(!objptr) continue;
|
||||
|
||||
auto rmesh = objptr->raw_mesh();
|
||||
|
||||
for(auto objinst : objptr->instances) {
|
||||
if(!objinst) continue;
|
||||
|
||||
Slic3r::TriangleMesh tmpmesh = rmesh;
|
||||
tmpmesh.scale(objinst->scaling_factor);
|
||||
objinst->transform_mesh(&tmpmesh);
|
||||
ExPolygons expolys = tmpmesh.horizontal_projection();
|
||||
svg.draw(expolys);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// A container which stores a pointer to the 3D object and its projected
|
||||
// 2D shape from top view.
|
||||
using ShapeData2D =
|
||||
std::vector<std::pair<Slic3r::ModelInstance*, Item>>;
|
||||
|
||||
ShapeData2D projectModelFromTop(const Slic3r::Model &model) {
|
||||
ShapeData2D ret;
|
||||
|
||||
auto s = std::accumulate(model.objects.begin(), model.objects.end(), size_t(0),
|
||||
[](size_t s, ModelObject* o){
|
||||
return s + o->instances.size();
|
||||
});
|
||||
|
||||
ret.reserve(s);
|
||||
|
||||
for(auto objptr : model.objects) {
|
||||
if(objptr) {
|
||||
|
||||
auto rmesh = objptr->raw_mesh();
|
||||
|
||||
for(auto objinst : objptr->instances) {
|
||||
if(objinst) {
|
||||
Slic3r::TriangleMesh tmpmesh = rmesh;
|
||||
ClipperLib::PolygonImpl pn;
|
||||
|
||||
tmpmesh.scale(objinst->scaling_factor);
|
||||
|
||||
// TODO export the exact 2D projection
|
||||
auto p = tmpmesh.convex_hull();
|
||||
|
||||
p.make_clockwise();
|
||||
p.append(p.first_point());
|
||||
pn.Contour = Slic3rMultiPoint_to_ClipperPath( p );
|
||||
|
||||
// Efficient conversion to item.
|
||||
Item item(std::move(pn));
|
||||
|
||||
// Invalid geometries would throw exceptions when arranging
|
||||
if(item.vertexCount() > 3) {
|
||||
item.rotation(objinst->rotation);
|
||||
item.translation( {
|
||||
ClipperLib::cInt(objinst->offset(0)/SCALING_FACTOR),
|
||||
ClipperLib::cInt(objinst->offset(1)/SCALING_FACTOR)
|
||||
});
|
||||
ret.emplace_back(objinst, item);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
/**
|
||||
* \brief Arranges the model objects on the screen.
|
||||
*
|
||||
* The arrangement considers multiple bins (aka. print beds) for placing all
|
||||
* the items provided in the model argument. If the items don't fit on one
|
||||
* print bed, the remaining will be placed onto newly created print beds.
|
||||
* The first_bin_only parameter, if set to true, disables this behaviour and
|
||||
* makes sure that only one print bed is filled and the remaining items will be
|
||||
* untouched. When set to false, the items which could not fit onto the
|
||||
* print bed will be placed next to the print bed so the user should see a
|
||||
* pile of items on the print bed and some other piles outside the print
|
||||
* area that can be dragged later onto the print bed as a group.
|
||||
*
|
||||
* \param model The model object with the 3D content.
|
||||
* \param dist The minimum distance which is allowed for any pair of items
|
||||
* on the print bed in any direction.
|
||||
* \param bb The bounding box of the print bed. It corresponds to the 'bin'
|
||||
* for bin packing.
|
||||
* \param first_bin_only This parameter controls whether to place the
|
||||
* remaining items which do not fit onto the print area next to the print
|
||||
* bed or leave them untouched (let the user arrange them by hand or remove
|
||||
* them).
|
||||
*/
|
||||
bool arrange(Model &model, coordf_t dist, const Slic3r::BoundingBoxf* bb,
|
||||
bool first_bin_only,
|
||||
std::function<void(unsigned)> progressind)
|
||||
{
|
||||
using ArrangeResult = _IndexedPackGroup<PolygonImpl>;
|
||||
|
||||
bool ret = true;
|
||||
|
||||
// Create the arranger config
|
||||
auto min_obj_distance = static_cast<Coord>(dist/SCALING_FACTOR);
|
||||
|
||||
// Get the 2D projected shapes with their 3D model instance pointers
|
||||
auto shapemap = arr::projectModelFromTop(model);
|
||||
|
||||
bool hasbin = bb != nullptr && bb->defined;
|
||||
double area_max = 0;
|
||||
|
||||
// Copy the references for the shapes only as the arranger expects a
|
||||
// sequence of objects convertible to Item or ClipperPolygon
|
||||
std::vector<std::reference_wrapper<Item>> shapes;
|
||||
shapes.reserve(shapemap.size());
|
||||
std::for_each(shapemap.begin(), shapemap.end(),
|
||||
[&shapes, min_obj_distance, &area_max, hasbin]
|
||||
(ShapeData2D::value_type& it)
|
||||
{
|
||||
shapes.push_back(std::ref(it.second));
|
||||
});
|
||||
|
||||
Box bin;
|
||||
|
||||
if(hasbin) {
|
||||
// Scale up the bounding box to clipper scale.
|
||||
BoundingBoxf bbb = *bb;
|
||||
bbb.scale(1.0/SCALING_FACTOR);
|
||||
|
||||
bin = Box({
|
||||
static_cast<libnest2d::Coord>(bbb.min(0)),
|
||||
static_cast<libnest2d::Coord>(bbb.min(1))
|
||||
},
|
||||
{
|
||||
static_cast<libnest2d::Coord>(bbb.max(0)),
|
||||
static_cast<libnest2d::Coord>(bbb.max(1))
|
||||
});
|
||||
}
|
||||
|
||||
// Will use the DJD selection heuristic with the BottomLeft placement
|
||||
// strategy
|
||||
using Arranger = Arranger<NfpPlacer, FirstFitSelection>;
|
||||
using PConf = Arranger::PlacementConfig;
|
||||
using SConf = Arranger::SelectionConfig;
|
||||
|
||||
PConf pcfg; // Placement configuration
|
||||
SConf scfg; // Selection configuration
|
||||
|
||||
// Align the arranged pile into the center of the bin
|
||||
pcfg.alignment = PConf::Alignment::CENTER;
|
||||
|
||||
// Start placing the items from the center of the print bed
|
||||
pcfg.starting_point = PConf::Alignment::CENTER;
|
||||
|
||||
// TODO cannot use rotations until multiple objects of same geometry can
|
||||
// handle different rotations
|
||||
// arranger.useMinimumBoundigBoxRotation();
|
||||
pcfg.rotations = { 0.0 };
|
||||
|
||||
// Magic: we will specify what is the goal of arrangement... In this case
|
||||
// we override the default object function to make the larger items go into
|
||||
// the center of the pile and smaller items orbit it so the resulting pile
|
||||
// has a circle-like shape. This is good for the print bed's heat profile.
|
||||
// We alse sacrafice a bit of pack efficiency for this to work. As a side
|
||||
// effect, the arrange procedure is a lot faster (we do not need to
|
||||
// calculate the convex hulls)
|
||||
pcfg.object_function = [bin, hasbin](
|
||||
NfpPlacer::Pile pile, // The currently arranged pile
|
||||
double /*area*/, // Sum area of items (not needed)
|
||||
double norm, // A norming factor for physical dimensions
|
||||
double penality) // Min penality in case of bad arrangement
|
||||
{
|
||||
auto bb = ShapeLike::boundingBox(pile);
|
||||
|
||||
// We get the current item that's being evaluated.
|
||||
auto& sh = pile.back();
|
||||
|
||||
// We retrieve the reference point of this item
|
||||
auto rv = Nfp::referenceVertex(sh);
|
||||
|
||||
// We get the distance of the reference point from the center of the
|
||||
// heat bed
|
||||
auto c = bin.center();
|
||||
auto d = PointLike::distance(rv, c);
|
||||
|
||||
// The score will be the normalized distance which will be minimized,
|
||||
// effectively creating a circle shaped pile of items
|
||||
double score = double(d)/norm;
|
||||
|
||||
// If it does not fit into the print bed we will beat it
|
||||
// with a large penality. If we would not do this, there would be only
|
||||
// one big pile that doesn't care whether it fits onto the print bed.
|
||||
if(hasbin && !NfpPlacer::wouldFit(bb, bin)) score = 2*penality - score;
|
||||
|
||||
return score;
|
||||
};
|
||||
|
||||
// Create the arranger object
|
||||
Arranger arranger(bin, min_obj_distance, pcfg, scfg);
|
||||
|
||||
// Set the progress indicator for the arranger.
|
||||
arranger.progressIndicator(progressind);
|
||||
|
||||
// Arrange and return the items with their respective indices within the
|
||||
// input sequence.
|
||||
auto result = arranger.arrangeIndexed(shapes.begin(), shapes.end());
|
||||
|
||||
auto applyResult = [&shapemap](ArrangeResult::value_type& group,
|
||||
Coord batch_offset)
|
||||
{
|
||||
for(auto& r : group) {
|
||||
auto idx = r.first; // get the original item index
|
||||
Item& item = r.second; // get the item itself
|
||||
|
||||
// Get the model instance from the shapemap using the index
|
||||
ModelInstance *inst_ptr = shapemap[idx].first;
|
||||
|
||||
// Get the tranformation data from the item object and scale it
|
||||
// appropriately
|
||||
auto off = item.translation();
|
||||
Radians rot = item.rotation();
|
||||
Pointf foff(off.X*SCALING_FACTOR + batch_offset,
|
||||
off.Y*SCALING_FACTOR);
|
||||
|
||||
// write the tranformation data into the model instance
|
||||
inst_ptr->rotation = rot;
|
||||
inst_ptr->offset = foff;
|
||||
}
|
||||
};
|
||||
|
||||
if(first_bin_only) {
|
||||
applyResult(result.front(), 0);
|
||||
} else {
|
||||
|
||||
const auto STRIDE_PADDING = 1.2;
|
||||
|
||||
Coord stride = static_cast<Coord>(STRIDE_PADDING*
|
||||
bin.width()*SCALING_FACTOR);
|
||||
Coord batch_offset = 0;
|
||||
|
||||
for(auto& group : result) {
|
||||
applyResult(group, batch_offset);
|
||||
|
||||
// Only the first pack group can be placed onto the print bed. The
|
||||
// other objects which could not fit will be placed next to the
|
||||
// print bed
|
||||
batch_offset += stride;
|
||||
}
|
||||
}
|
||||
|
||||
for(auto objptr : model.objects) objptr->invalidate_bounding_box();
|
||||
|
||||
return ret && result.size() == 1;
|
||||
}
|
||||
}
|
||||
|
||||
/* arrange objects preserving their instance count
|
||||
but altering their instance positions */
|
||||
bool Model::arrange_objects(coordf_t dist, const BoundingBoxf* bb,
|
||||
std::function<void(unsigned)> progressind)
|
||||
bool Model::arrange_objects(coordf_t dist, const BoundingBoxf* bb)
|
||||
{
|
||||
bool ret = false;
|
||||
if(bb != nullptr && bb->defined) {
|
||||
// Despite the new arrange is able to run without a specified bin,
|
||||
// the perl testsuit still fails for this case. For now the safest
|
||||
// thing to do is to use the new arrange only when a proper bin is
|
||||
// specified.
|
||||
ret = arr::arrange(*this, dist, bb, false, progressind);
|
||||
} else {
|
||||
// get the (transformed) size of each instance so that we take
|
||||
// into account their different transformations when packing
|
||||
Pointfs instance_sizes;
|
||||
Pointfs instance_centers;
|
||||
for (const ModelObject *o : this->objects)
|
||||
for (size_t i = 0; i < o->instances.size(); ++ i) {
|
||||
// an accurate snug bounding box around the transformed mesh.
|
||||
BoundingBoxf3 bbox(o->instance_bounding_box(i, true));
|
||||
instance_sizes.push_back(bbox.size().xy());
|
||||
instance_centers.push_back(bbox.center().xy());
|
||||
}
|
||||
|
||||
Pointfs positions;
|
||||
if (! _arrange(instance_sizes, dist, bb, positions))
|
||||
return false;
|
||||
|
||||
size_t idx = 0;
|
||||
for (ModelObject *o : this->objects) {
|
||||
for (ModelInstance *i : o->instances) {
|
||||
i->offset = positions[idx] - instance_centers[idx];
|
||||
++ idx;
|
||||
}
|
||||
o->invalidate_bounding_box();
|
||||
// get the (transformed) size of each instance so that we take
|
||||
// into account their different transformations when packing
|
||||
Pointfs instance_sizes;
|
||||
Pointfs instance_centers;
|
||||
for (const ModelObject *o : this->objects)
|
||||
for (size_t i = 0; i < o->instances.size(); ++ i) {
|
||||
// an accurate snug bounding box around the transformed mesh.
|
||||
BoundingBoxf3 bbox(o->instance_bounding_box(i, true));
|
||||
instance_sizes.push_back(bbox.size().xy());
|
||||
instance_centers.push_back(bbox.center().xy());
|
||||
}
|
||||
|
||||
Pointfs positions;
|
||||
if (! _arrange(instance_sizes, dist, bb, positions))
|
||||
return false;
|
||||
|
||||
size_t idx = 0;
|
||||
for (ModelObject *o : this->objects) {
|
||||
for (ModelInstance *i : o->instances) {
|
||||
i->offset = positions[idx] - instance_centers[idx];
|
||||
++ idx;
|
||||
}
|
||||
o->invalidate_bounding_box();
|
||||
}
|
||||
|
||||
return ret;
|
||||
return true;
|
||||
}
|
||||
|
||||
// Duplicate the entire model preserving instance relative positions.
|
||||
|
@ -1106,9 +768,23 @@ void ModelObject::scale(const Pointf3 &versor)
|
|||
|
||||
void ModelObject::rotate(float angle, const Axis &axis)
|
||||
{
|
||||
float min_z = FLT_MAX;
|
||||
for (ModelVolume *v : this->volumes)
|
||||
{
|
||||
v->mesh.rotate(angle, axis);
|
||||
this->origin_translation = Pointf3(0,0,0);
|
||||
min_z = std::min(min_z, v->mesh.stl.stats.min.z);
|
||||
}
|
||||
|
||||
if (min_z != 0.0f)
|
||||
{
|
||||
// translate the object so that its minimum z lays on the bed
|
||||
for (ModelVolume *v : this->volumes)
|
||||
{
|
||||
v->mesh.translate(0.0f, 0.0f, -min_z);
|
||||
}
|
||||
}
|
||||
|
||||
this->origin_translation = Pointf3(0, 0, 0);
|
||||
this->invalidate_bounding_box();
|
||||
}
|
||||
|
||||
|
|
|
@ -291,8 +291,7 @@ public:
|
|||
void center_instances_around_point(const Pointf &point);
|
||||
void translate(coordf_t x, coordf_t y, coordf_t z) { for (ModelObject *o : this->objects) o->translate(x, y, z); }
|
||||
TriangleMesh mesh() const;
|
||||
bool arrange_objects(coordf_t dist, const BoundingBoxf* bb = NULL,
|
||||
std::function<void(unsigned)> progressind = [](unsigned){});
|
||||
bool arrange_objects(coordf_t dist, const BoundingBoxf* bb = NULL);
|
||||
// Croaks if the duplicated objects do not fit the print bed.
|
||||
void duplicate(size_t copies_num, coordf_t dist, const BoundingBoxf* bb = NULL);
|
||||
void duplicate_objects(size_t copies_num, coordf_t dist, const BoundingBoxf* bb = NULL);
|
||||
|
|
597
xs/src/libslic3r/ModelArrange.hpp
Normal file
597
xs/src/libslic3r/ModelArrange.hpp
Normal file
|
@ -0,0 +1,597 @@
|
|||
#ifndef MODELARRANGE_HPP
|
||||
#define MODELARRANGE_HPP
|
||||
|
||||
#include "Model.hpp"
|
||||
#include "SVG.hpp"
|
||||
#include <libnest2d.h>
|
||||
|
||||
#include <numeric>
|
||||
#include <ClipperUtils.hpp>
|
||||
|
||||
#include <boost/geometry/index/rtree.hpp>
|
||||
|
||||
namespace Slic3r {
|
||||
namespace arr {
|
||||
|
||||
using namespace libnest2d;
|
||||
|
||||
std::string toString(const Model& model, bool holes = true) {
|
||||
std::stringstream ss;
|
||||
|
||||
ss << "{\n";
|
||||
|
||||
for(auto objptr : model.objects) {
|
||||
if(!objptr) continue;
|
||||
|
||||
auto rmesh = objptr->raw_mesh();
|
||||
|
||||
for(auto objinst : objptr->instances) {
|
||||
if(!objinst) continue;
|
||||
|
||||
Slic3r::TriangleMesh tmpmesh = rmesh;
|
||||
tmpmesh.scale(objinst->scaling_factor);
|
||||
objinst->transform_mesh(&tmpmesh);
|
||||
ExPolygons expolys = tmpmesh.horizontal_projection();
|
||||
for(auto& expoly_complex : expolys) {
|
||||
|
||||
auto tmp = expoly_complex.simplify(1.0/SCALING_FACTOR);
|
||||
if(tmp.empty()) continue;
|
||||
auto expoly = tmp.front();
|
||||
expoly.contour.make_clockwise();
|
||||
for(auto& h : expoly.holes) h.make_counter_clockwise();
|
||||
|
||||
ss << "\t{\n";
|
||||
ss << "\t\t{\n";
|
||||
|
||||
for(auto v : expoly.contour.points) ss << "\t\t\t{"
|
||||
<< v(0) << ", "
|
||||
<< v(1) << "},\n";
|
||||
{
|
||||
auto v = expoly.contour.points.front();
|
||||
ss << "\t\t\t{" << v(0) << ", " << v(1) << "},\n";
|
||||
}
|
||||
ss << "\t\t},\n";
|
||||
|
||||
// Holes:
|
||||
ss << "\t\t{\n";
|
||||
if(holes) for(auto h : expoly.holes) {
|
||||
ss << "\t\t\t{\n";
|
||||
for(auto v : h.points) ss << "\t\t\t\t{"
|
||||
<< v(0) << ", "
|
||||
<< v(1) << "},\n";
|
||||
{
|
||||
auto v = h.points.front();
|
||||
ss << "\t\t\t\t{" << v(0) << ", " << v(1) << "},\n";
|
||||
}
|
||||
ss << "\t\t\t},\n";
|
||||
}
|
||||
ss << "\t\t},\n";
|
||||
|
||||
ss << "\t},\n";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ss << "}\n";
|
||||
|
||||
return ss.str();
|
||||
}
|
||||
|
||||
void toSVG(SVG& svg, const Model& model) {
|
||||
for(auto objptr : model.objects) {
|
||||
if(!objptr) continue;
|
||||
|
||||
auto rmesh = objptr->raw_mesh();
|
||||
|
||||
for(auto objinst : objptr->instances) {
|
||||
if(!objinst) continue;
|
||||
|
||||
Slic3r::TriangleMesh tmpmesh = rmesh;
|
||||
tmpmesh.scale(objinst->scaling_factor);
|
||||
objinst->transform_mesh(&tmpmesh);
|
||||
ExPolygons expolys = tmpmesh.horizontal_projection();
|
||||
svg.draw(expolys);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
namespace bgi = boost::geometry::index;
|
||||
|
||||
using SpatElement = std::pair<Box, unsigned>;
|
||||
using SpatIndex = bgi::rtree< SpatElement, bgi::rstar<16, 4> >;
|
||||
|
||||
std::tuple<double /*score*/, Box /*farthest point from bin center*/>
|
||||
objfunc(const PointImpl& bincenter,
|
||||
double /*bin_area*/,
|
||||
ShapeLike::Shapes<PolygonImpl>& pile, // The currently arranged pile
|
||||
double /*pile_area*/,
|
||||
const Item &item,
|
||||
double norm, // A norming factor for physical dimensions
|
||||
std::vector<double>& areacache, // pile item areas will be cached
|
||||
// a spatial index to quickly get neighbors of the candidate item
|
||||
SpatIndex& spatindex
|
||||
)
|
||||
{
|
||||
using pl = PointLike;
|
||||
using sl = ShapeLike;
|
||||
|
||||
static const double BIG_ITEM_TRESHOLD = 0.2;
|
||||
static const double ROUNDNESS_RATIO = 0.5;
|
||||
static const double DENSITY_RATIO = 1.0 - ROUNDNESS_RATIO;
|
||||
|
||||
// We will treat big items (compared to the print bed) differently
|
||||
auto normarea = [norm](double area) { return std::sqrt(area)/norm; };
|
||||
|
||||
// If a new bin has been created:
|
||||
if(pile.size() < areacache.size()) {
|
||||
areacache.clear();
|
||||
spatindex.clear();
|
||||
}
|
||||
|
||||
// We must fill the caches:
|
||||
int idx = 0;
|
||||
for(auto& p : pile) {
|
||||
if(idx == areacache.size()) {
|
||||
areacache.emplace_back(sl::area(p));
|
||||
if(normarea(areacache[idx]) > BIG_ITEM_TRESHOLD)
|
||||
spatindex.insert({sl::boundingBox(p), idx});
|
||||
}
|
||||
|
||||
idx++;
|
||||
}
|
||||
|
||||
// Candidate item bounding box
|
||||
auto ibb = item.boundingBox();
|
||||
|
||||
// Calculate the full bounding box of the pile with the candidate item
|
||||
pile.emplace_back(item.transformedShape());
|
||||
auto fullbb = ShapeLike::boundingBox(pile);
|
||||
pile.pop_back();
|
||||
|
||||
// The bounding box of the big items (they will accumulate in the center
|
||||
// of the pile
|
||||
Box bigbb;
|
||||
if(spatindex.empty()) bigbb = fullbb;
|
||||
else {
|
||||
auto boostbb = spatindex.bounds();
|
||||
boost::geometry::convert(boostbb, bigbb);
|
||||
}
|
||||
|
||||
// The size indicator of the candidate item. This is not the area,
|
||||
// but almost...
|
||||
double item_normarea = normarea(item.area());
|
||||
|
||||
// Will hold the resulting score
|
||||
double score = 0;
|
||||
|
||||
if(item_normarea > BIG_ITEM_TRESHOLD) {
|
||||
// This branch is for the bigger items..
|
||||
// Here we will use the closest point of the item bounding box to
|
||||
// the already arranged pile. So not the bb center nor the a choosen
|
||||
// corner but whichever is the closest to the center. This will
|
||||
// prevent some unwanted strange arrangements.
|
||||
|
||||
auto minc = ibb.minCorner(); // bottom left corner
|
||||
auto maxc = ibb.maxCorner(); // top right corner
|
||||
|
||||
// top left and bottom right corners
|
||||
auto top_left = PointImpl{getX(minc), getY(maxc)};
|
||||
auto bottom_right = PointImpl{getX(maxc), getY(minc)};
|
||||
|
||||
// Now the distance of the gravity center will be calculated to the
|
||||
// five anchor points and the smallest will be chosen.
|
||||
std::array<double, 5> dists;
|
||||
auto cc = fullbb.center(); // The gravity center
|
||||
dists[0] = pl::distance(minc, cc);
|
||||
dists[1] = pl::distance(maxc, cc);
|
||||
dists[2] = pl::distance(ibb.center(), cc);
|
||||
dists[3] = pl::distance(top_left, cc);
|
||||
dists[4] = pl::distance(bottom_right, cc);
|
||||
|
||||
// The smalles distance from the arranged pile center:
|
||||
auto dist = *(std::min_element(dists.begin(), dists.end())) / norm;
|
||||
|
||||
// Density is the pack density: how big is the arranged pile
|
||||
auto density = std::sqrt(fullbb.width()*fullbb.height()) / norm;
|
||||
|
||||
// Prepare a variable for the alignment score.
|
||||
// This will indicate: how well is the candidate item aligned with
|
||||
// its neighbors. We will check the aligment with all neighbors and
|
||||
// return the score for the best alignment. So it is enough for the
|
||||
// candidate to be aligned with only one item.
|
||||
auto alignment_score = std::numeric_limits<double>::max();
|
||||
|
||||
auto& trsh = item.transformedShape();
|
||||
|
||||
auto querybb = item.boundingBox();
|
||||
|
||||
// Query the spatial index for the neigbours
|
||||
std::vector<SpatElement> result;
|
||||
spatindex.query(bgi::intersects(querybb), std::back_inserter(result));
|
||||
|
||||
for(auto& e : result) { // now get the score for the best alignment
|
||||
auto idx = e.second;
|
||||
auto& p = pile[idx];
|
||||
auto parea = areacache[idx];
|
||||
auto bb = sl::boundingBox(sl::Shapes<PolygonImpl>{p, trsh});
|
||||
auto bbarea = bb.area();
|
||||
auto ascore = 1.0 - (item.area() + parea)/bbarea;
|
||||
|
||||
if(ascore < alignment_score) alignment_score = ascore;
|
||||
}
|
||||
|
||||
// The final mix of the score is the balance between the distance
|
||||
// from the full pile center, the pack density and the
|
||||
// alignment with the neigbours
|
||||
auto C = 0.33;
|
||||
score = C * dist + C * density + C * alignment_score;
|
||||
|
||||
} else if( item_normarea < BIG_ITEM_TRESHOLD && spatindex.empty()) {
|
||||
// If there are no big items, only small, we should consider the
|
||||
// density here as well to not get silly results
|
||||
auto bindist = pl::distance(ibb.center(), bincenter) / norm;
|
||||
auto density = std::sqrt(fullbb.width()*fullbb.height()) / norm;
|
||||
score = ROUNDNESS_RATIO * bindist + DENSITY_RATIO * density;
|
||||
} else {
|
||||
// Here there are the small items that should be placed around the
|
||||
// already processed bigger items.
|
||||
// No need to play around with the anchor points, the center will be
|
||||
// just fine for small items
|
||||
score = pl::distance(ibb.center(), bigbb.center()) / norm;
|
||||
}
|
||||
|
||||
return std::make_tuple(score, fullbb);
|
||||
}
|
||||
|
||||
template<class PConf>
|
||||
void fillConfig(PConf& pcfg) {
|
||||
|
||||
// Align the arranged pile into the center of the bin
|
||||
pcfg.alignment = PConf::Alignment::CENTER;
|
||||
|
||||
// Start placing the items from the center of the print bed
|
||||
pcfg.starting_point = PConf::Alignment::CENTER;
|
||||
|
||||
// TODO cannot use rotations until multiple objects of same geometry can
|
||||
// handle different rotations
|
||||
// arranger.useMinimumBoundigBoxRotation();
|
||||
pcfg.rotations = { 0.0 };
|
||||
|
||||
// The accuracy of optimization.
|
||||
// Goes from 0.0 to 1.0 and scales performance as well
|
||||
pcfg.accuracy = 0.6f;
|
||||
}
|
||||
|
||||
template<class TBin>
|
||||
class AutoArranger {};
|
||||
|
||||
template<class TBin>
|
||||
class _ArrBase {
|
||||
protected:
|
||||
using Placer = strategies::_NofitPolyPlacer<PolygonImpl, TBin>;
|
||||
using Selector = FirstFitSelection;
|
||||
using Packer = Arranger<Placer, Selector>;
|
||||
using PConfig = typename Packer::PlacementConfig;
|
||||
using Distance = TCoord<PointImpl>;
|
||||
using Pile = ShapeLike::Shapes<PolygonImpl>;
|
||||
|
||||
Packer pck_;
|
||||
PConfig pconf_; // Placement configuration
|
||||
double bin_area_;
|
||||
std::vector<double> areacache_;
|
||||
SpatIndex rtree_;
|
||||
public:
|
||||
|
||||
_ArrBase(const TBin& bin, Distance dist,
|
||||
std::function<void(unsigned)> progressind):
|
||||
pck_(bin, dist), bin_area_(ShapeLike::area<PolygonImpl>(bin))
|
||||
{
|
||||
fillConfig(pconf_);
|
||||
pck_.progressIndicator(progressind);
|
||||
}
|
||||
|
||||
template<class...Args> inline IndexedPackGroup operator()(Args&&...args) {
|
||||
areacache_.clear();
|
||||
return pck_.arrangeIndexed(std::forward<Args>(args)...);
|
||||
}
|
||||
};
|
||||
|
||||
template<>
|
||||
class AutoArranger<Box>: public _ArrBase<Box> {
|
||||
public:
|
||||
|
||||
AutoArranger(const Box& bin, Distance dist,
|
||||
std::function<void(unsigned)> progressind):
|
||||
_ArrBase<Box>(bin, dist, progressind)
|
||||
{
|
||||
pconf_.object_function = [this, bin] (
|
||||
Pile& pile,
|
||||
const Item &item,
|
||||
double pile_area,
|
||||
double norm,
|
||||
double /*penality*/) {
|
||||
|
||||
auto result = objfunc(bin.center(), bin_area_, pile,
|
||||
pile_area, item, norm, areacache_, rtree_);
|
||||
double score = std::get<0>(result);
|
||||
auto& fullbb = std::get<1>(result);
|
||||
|
||||
auto wdiff = fullbb.width() - bin.width();
|
||||
auto hdiff = fullbb.height() - bin.height();
|
||||
if(wdiff > 0) score += std::pow(wdiff, 2) / norm;
|
||||
if(hdiff > 0) score += std::pow(hdiff, 2) / norm;
|
||||
|
||||
return score;
|
||||
};
|
||||
|
||||
pck_.configure(pconf_);
|
||||
}
|
||||
};
|
||||
|
||||
template<>
|
||||
class AutoArranger<PolygonImpl>: public _ArrBase<PolygonImpl> {
|
||||
public:
|
||||
AutoArranger(const PolygonImpl& bin, Distance dist,
|
||||
std::function<void(unsigned)> progressind):
|
||||
_ArrBase<PolygonImpl>(bin, dist, progressind)
|
||||
{
|
||||
pconf_.object_function = [this, &bin] (
|
||||
Pile& pile,
|
||||
const Item &item,
|
||||
double pile_area,
|
||||
double norm,
|
||||
double /*penality*/) {
|
||||
|
||||
auto binbb = ShapeLike::boundingBox(bin);
|
||||
auto result = objfunc(binbb.center(), bin_area_, pile,
|
||||
pile_area, item, norm, areacache_, rtree_);
|
||||
double score = std::get<0>(result);
|
||||
|
||||
pile.emplace_back(item.transformedShape());
|
||||
auto chull = ShapeLike::convexHull(pile);
|
||||
pile.pop_back();
|
||||
|
||||
// If it does not fit into the print bed we will beat it with a
|
||||
// large penality. If we would not do this, there would be only one
|
||||
// big pile that doesn't care whether it fits onto the print bed.
|
||||
if(!Placer::wouldFit(chull, bin)) score += norm;
|
||||
|
||||
return score;
|
||||
};
|
||||
|
||||
pck_.configure(pconf_);
|
||||
}
|
||||
};
|
||||
|
||||
template<> // Specialization with no bin
|
||||
class AutoArranger<bool>: public _ArrBase<Box> {
|
||||
public:
|
||||
|
||||
AutoArranger(Distance dist, std::function<void(unsigned)> progressind):
|
||||
_ArrBase<Box>(Box(0, 0), dist, progressind)
|
||||
{
|
||||
this->pconf_.object_function = [this] (
|
||||
Pile& pile,
|
||||
const Item &item,
|
||||
double pile_area,
|
||||
double norm,
|
||||
double /*penality*/) {
|
||||
|
||||
auto result = objfunc({0, 0}, 0, pile, pile_area,
|
||||
item, norm, areacache_, rtree_);
|
||||
return std::get<0>(result);
|
||||
};
|
||||
|
||||
this->pck_.configure(pconf_);
|
||||
}
|
||||
};
|
||||
|
||||
// A container which stores a pointer to the 3D object and its projected
|
||||
// 2D shape from top view.
|
||||
using ShapeData2D =
|
||||
std::vector<std::pair<Slic3r::ModelInstance*, Item>>;
|
||||
|
||||
ShapeData2D projectModelFromTop(const Slic3r::Model &model) {
|
||||
ShapeData2D ret;
|
||||
|
||||
auto s = std::accumulate(model.objects.begin(), model.objects.end(), 0,
|
||||
[](size_t s, ModelObject* o){
|
||||
return s + o->instances.size();
|
||||
});
|
||||
|
||||
ret.reserve(s);
|
||||
|
||||
for(auto objptr : model.objects) {
|
||||
if(objptr) {
|
||||
|
||||
auto rmesh = objptr->raw_mesh();
|
||||
|
||||
for(auto objinst : objptr->instances) {
|
||||
if(objinst) {
|
||||
Slic3r::TriangleMesh tmpmesh = rmesh;
|
||||
ClipperLib::PolygonImpl pn;
|
||||
|
||||
tmpmesh.scale(objinst->scaling_factor);
|
||||
|
||||
// TODO export the exact 2D projection
|
||||
auto p = tmpmesh.convex_hull();
|
||||
|
||||
p.make_clockwise();
|
||||
p.append(p.first_point());
|
||||
pn.Contour = Slic3rMultiPoint_to_ClipperPath( p );
|
||||
|
||||
// Efficient conversion to item.
|
||||
Item item(std::move(pn));
|
||||
|
||||
// Invalid geometries would throw exceptions when arranging
|
||||
if(item.vertexCount() > 3) {
|
||||
item.rotation(objinst->rotation);
|
||||
item.translation( {
|
||||
ClipperLib::cInt(objinst->offset(0)/SCALING_FACTOR),
|
||||
ClipperLib::cInt(objinst->offset(1)/SCALING_FACTOR)
|
||||
});
|
||||
ret.emplace_back(objinst, item);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
enum BedShapeHint {
|
||||
BOX,
|
||||
CIRCLE,
|
||||
IRREGULAR,
|
||||
WHO_KNOWS
|
||||
};
|
||||
|
||||
BedShapeHint bedShape(const Slic3r::Polyline& /*bed*/) {
|
||||
// Determine the bed shape by hand
|
||||
return BOX;
|
||||
}
|
||||
|
||||
void applyResult(
|
||||
IndexedPackGroup::value_type& group,
|
||||
Coord batch_offset,
|
||||
ShapeData2D& shapemap)
|
||||
{
|
||||
for(auto& r : group) {
|
||||
auto idx = r.first; // get the original item index
|
||||
Item& item = r.second; // get the item itself
|
||||
|
||||
// Get the model instance from the shapemap using the index
|
||||
ModelInstance *inst_ptr = shapemap[idx].first;
|
||||
|
||||
// Get the tranformation data from the item object and scale it
|
||||
// appropriately
|
||||
auto off = item.translation();
|
||||
Radians rot = item.rotation();
|
||||
Pointf foff(off.X*SCALING_FACTOR + batch_offset,
|
||||
off.Y*SCALING_FACTOR);
|
||||
|
||||
// write the tranformation data into the model instance
|
||||
inst_ptr->rotation = rot;
|
||||
inst_ptr->offset = foff;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* \brief Arranges the model objects on the screen.
|
||||
*
|
||||
* The arrangement considers multiple bins (aka. print beds) for placing all
|
||||
* the items provided in the model argument. If the items don't fit on one
|
||||
* print bed, the remaining will be placed onto newly created print beds.
|
||||
* The first_bin_only parameter, if set to true, disables this behaviour and
|
||||
* makes sure that only one print bed is filled and the remaining items will be
|
||||
* untouched. When set to false, the items which could not fit onto the
|
||||
* print bed will be placed next to the print bed so the user should see a
|
||||
* pile of items on the print bed and some other piles outside the print
|
||||
* area that can be dragged later onto the print bed as a group.
|
||||
*
|
||||
* \param model The model object with the 3D content.
|
||||
* \param dist The minimum distance which is allowed for any pair of items
|
||||
* on the print bed in any direction.
|
||||
* \param bb The bounding box of the print bed. It corresponds to the 'bin'
|
||||
* for bin packing.
|
||||
* \param first_bin_only This parameter controls whether to place the
|
||||
* remaining items which do not fit onto the print area next to the print
|
||||
* bed or leave them untouched (let the user arrange them by hand or remove
|
||||
* them).
|
||||
*/
|
||||
bool arrange(Model &model, coordf_t min_obj_distance,
|
||||
const Slic3r::Polyline& bed,
|
||||
BedShapeHint bedhint,
|
||||
bool first_bin_only,
|
||||
std::function<void(unsigned)> progressind)
|
||||
{
|
||||
using ArrangeResult = _IndexedPackGroup<PolygonImpl>;
|
||||
|
||||
bool ret = true;
|
||||
|
||||
// Get the 2D projected shapes with their 3D model instance pointers
|
||||
auto shapemap = arr::projectModelFromTop(model);
|
||||
|
||||
// Copy the references for the shapes only as the arranger expects a
|
||||
// sequence of objects convertible to Item or ClipperPolygon
|
||||
std::vector<std::reference_wrapper<Item>> shapes;
|
||||
shapes.reserve(shapemap.size());
|
||||
std::for_each(shapemap.begin(), shapemap.end(),
|
||||
[&shapes] (ShapeData2D::value_type& it)
|
||||
{
|
||||
shapes.push_back(std::ref(it.second));
|
||||
});
|
||||
|
||||
IndexedPackGroup result;
|
||||
BoundingBox bbb(bed.points);
|
||||
|
||||
auto binbb = Box({
|
||||
static_cast<libnest2d::Coord>(bbb.min(0)),
|
||||
static_cast<libnest2d::Coord>(bbb.min(1))
|
||||
},
|
||||
{
|
||||
static_cast<libnest2d::Coord>(bbb.max(0)),
|
||||
static_cast<libnest2d::Coord>(bbb.max(1))
|
||||
});
|
||||
|
||||
switch(bedhint) {
|
||||
case BOX: {
|
||||
|
||||
// Create the arranger for the box shaped bed
|
||||
AutoArranger<Box> arrange(binbb, min_obj_distance, progressind);
|
||||
|
||||
// Arrange and return the items with their respective indices within the
|
||||
// input sequence.
|
||||
result = arrange(shapes.begin(), shapes.end());
|
||||
break;
|
||||
}
|
||||
case CIRCLE:
|
||||
break;
|
||||
case IRREGULAR:
|
||||
case WHO_KNOWS: {
|
||||
using P = libnest2d::PolygonImpl;
|
||||
|
||||
auto ctour = Slic3rMultiPoint_to_ClipperPath(bed);
|
||||
P irrbed = ShapeLike::create<PolygonImpl>(std::move(ctour));
|
||||
|
||||
// std::cout << ShapeLike::toString(irrbed) << std::endl;
|
||||
|
||||
AutoArranger<P> arrange(irrbed, min_obj_distance, progressind);
|
||||
|
||||
// Arrange and return the items with their respective indices within the
|
||||
// input sequence.
|
||||
result = arrange(shapes.begin(), shapes.end());
|
||||
break;
|
||||
}
|
||||
};
|
||||
|
||||
if(first_bin_only) {
|
||||
applyResult(result.front(), 0, shapemap);
|
||||
} else {
|
||||
|
||||
const auto STRIDE_PADDING = 1.2;
|
||||
|
||||
Coord stride = static_cast<Coord>(STRIDE_PADDING*
|
||||
binbb.width()*SCALING_FACTOR);
|
||||
Coord batch_offset = 0;
|
||||
|
||||
for(auto& group : result) {
|
||||
applyResult(group, batch_offset, shapemap);
|
||||
|
||||
// Only the first pack group can be placed onto the print bed. The
|
||||
// other objects which could not fit will be placed next to the
|
||||
// print bed
|
||||
batch_offset += stride;
|
||||
}
|
||||
}
|
||||
|
||||
for(auto objptr : model.objects) objptr->invalidate_bounding_box();
|
||||
|
||||
return ret && result.size() == 1;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
#endif // MODELARRANGE_HPP
|
|
@ -128,7 +128,6 @@ bool Print::invalidate_state_by_config_options(const std::vector<t_config_option
|
|||
"gcode_comments",
|
||||
"gcode_flavor",
|
||||
"infill_acceleration",
|
||||
"infill_first",
|
||||
"layer_gcode",
|
||||
"min_fan_speed",
|
||||
"max_fan_speed",
|
||||
|
@ -155,6 +154,7 @@ bool Print::invalidate_state_by_config_options(const std::vector<t_config_option
|
|||
"retract_restart_extra",
|
||||
"retract_restart_extra_toolchange",
|
||||
"retract_speed",
|
||||
"single_extruder_multi_material_priming",
|
||||
"slowdown_below_layer_time",
|
||||
"standby_temperature_delta",
|
||||
"start_gcode",
|
||||
|
@ -166,17 +166,16 @@ bool Print::invalidate_state_by_config_options(const std::vector<t_config_option
|
|||
"use_relative_e_distances",
|
||||
"use_volumetric_e",
|
||||
"variable_layer_height",
|
||||
"wipe"
|
||||
"wipe",
|
||||
"wipe_tower_x",
|
||||
"wipe_tower_y",
|
||||
"wipe_tower_rotation_angle"
|
||||
};
|
||||
|
||||
std::vector<PrintStep> steps;
|
||||
std::vector<PrintObjectStep> osteps;
|
||||
bool invalidated = false;
|
||||
|
||||
// Always invalidate the wipe tower. This is probably necessary because of the wipe_into_infill / wipe_into_objects
|
||||
// features - nearly anything can influence what should (and could) be wiped into.
|
||||
steps.emplace_back(psWipeTower);
|
||||
|
||||
for (const t_config_option_key &opt_key : opt_keys) {
|
||||
if (steps_ignore.find(opt_key) != steps_ignore.end()) {
|
||||
// These options only affect G-code export or they are just notes without influence on the generated G-code,
|
||||
|
@ -204,18 +203,17 @@ bool Print::invalidate_state_by_config_options(const std::vector<t_config_option
|
|||
|| opt_key == "filament_unloading_speed"
|
||||
|| opt_key == "filament_toolchange_delay"
|
||||
|| opt_key == "filament_cooling_moves"
|
||||
|| opt_key == "filament_minimal_purge_on_wipe_tower"
|
||||
|| opt_key == "filament_cooling_initial_speed"
|
||||
|| opt_key == "filament_cooling_final_speed"
|
||||
|| opt_key == "filament_ramming_parameters"
|
||||
|| opt_key == "gcode_flavor"
|
||||
|| opt_key == "infill_first"
|
||||
|| opt_key == "single_extruder_multi_material"
|
||||
|| opt_key == "spiral_vase"
|
||||
|| opt_key == "temperature"
|
||||
|| opt_key == "wipe_tower"
|
||||
|| opt_key == "wipe_tower_x"
|
||||
|| opt_key == "wipe_tower_y"
|
||||
|| opt_key == "wipe_tower_width"
|
||||
|| opt_key == "wipe_tower_rotation_angle"
|
||||
|| opt_key == "wipe_tower_bridging"
|
||||
|| opt_key == "wiping_volumes_matrix"
|
||||
|| opt_key == "parking_pos_retraction"
|
||||
|
@ -1051,6 +1049,8 @@ void Print::_make_wipe_tower()
|
|||
if (! this->has_wipe_tower())
|
||||
return;
|
||||
|
||||
m_wipe_tower_depth = 0.f;
|
||||
|
||||
// Get wiping matrix to get number of extruders and convert vector<double> to vector<float>:
|
||||
std::vector<float> wiping_matrix(cast<float>(this->config.wiping_volumes_matrix.values));
|
||||
// Extract purging volumes for each extruder pair:
|
||||
|
@ -1144,12 +1144,19 @@ void Print::_make_wipe_tower()
|
|||
wipe_tower.plan_toolchange(layer_tools.print_z, layer_tools.wipe_tower_layer_height, current_extruder_id, current_extruder_id,false);
|
||||
for (const auto extruder_id : layer_tools.extruders) {
|
||||
if ((first_layer && extruder_id == m_tool_ordering.all_extruders().back()) || extruder_id != current_extruder_id) {
|
||||
float volume_to_wipe = wipe_volumes[current_extruder_id][extruder_id]; // total volume to wipe after this toolchange
|
||||
float volume_to_wipe = wipe_volumes[current_extruder_id][extruder_id]; // total volume to wipe after this toolchange
|
||||
// Not all of that can be used for infill purging:
|
||||
volume_to_wipe -= config.filament_minimal_purge_on_wipe_tower.get_at(extruder_id);
|
||||
|
||||
// try to assign some infills/objects for the wiping:
|
||||
volume_to_wipe = layer_tools.wiping_extrusions().mark_wiping_extrusions(*this, current_extruder_id, extruder_id, wipe_volumes[current_extruder_id][extruder_id]);
|
||||
volume_to_wipe = layer_tools.wiping_extrusions().mark_wiping_extrusions(*this, current_extruder_id, extruder_id, volume_to_wipe);
|
||||
|
||||
wipe_tower.plan_toolchange(layer_tools.print_z, layer_tools.wipe_tower_layer_height, current_extruder_id, extruder_id, first_layer && extruder_id == m_tool_ordering.all_extruders().back(), volume_to_wipe);
|
||||
// add back the minimal amount toforce on the wipe tower:
|
||||
volume_to_wipe += config.filament_minimal_purge_on_wipe_tower.get_at(extruder_id);
|
||||
|
||||
// request a toolchange at the wipe tower with at least volume_to_wipe purging amount
|
||||
wipe_tower.plan_toolchange(layer_tools.print_z, layer_tools.wipe_tower_layer_height, current_extruder_id, extruder_id,
|
||||
first_layer && extruder_id == m_tool_ordering.all_extruders().back(), volume_to_wipe);
|
||||
current_extruder_id = extruder_id;
|
||||
}
|
||||
}
|
||||
|
@ -1162,7 +1169,8 @@ void Print::_make_wipe_tower()
|
|||
// Generate the wipe tower layers.
|
||||
m_wipe_tower_tool_changes.reserve(m_tool_ordering.layer_tools().size());
|
||||
wipe_tower.generate(m_wipe_tower_tool_changes);
|
||||
|
||||
m_wipe_tower_depth = wipe_tower.get_depth();
|
||||
|
||||
// Unload the current filament over the purge tower.
|
||||
coordf_t layer_height = this->objects.front()->config.layer_height.value;
|
||||
if (m_tool_ordering.back().wipe_tower_partitions > 0) {
|
||||
|
@ -1183,10 +1191,6 @@ void Print::_make_wipe_tower()
|
|||
wipe_tower.tool_change((unsigned int)-1, false));
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
std::string Print::output_filename()
|
||||
{
|
||||
this->placeholder_parser.update_timestamp();
|
||||
|
@ -1225,7 +1229,6 @@ void Print::set_status(int percent, const std::string &message)
|
|||
printf("Print::status %d => %s\n", percent, message.c_str());
|
||||
}
|
||||
|
||||
|
||||
// Returns extruder this eec should be printed with, according to PrintRegion config
|
||||
int Print::get_extruder(const ExtrusionEntityCollection& fill, const PrintRegion ®ion)
|
||||
{
|
||||
|
@ -1233,5 +1236,4 @@ int Print::get_extruder(const ExtrusionEntityCollection& fill, const PrintRegion
|
|||
std::max<int>(region.config.perimeter_extruder.value - 1, 0);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
|
|
@ -273,6 +273,7 @@ public:
|
|||
|
||||
void add_model_object(ModelObject* model_object, int idx = -1);
|
||||
bool apply_config(DynamicPrintConfig config);
|
||||
float get_wipe_tower_depth() const { return m_wipe_tower_depth; }
|
||||
bool has_infinite_skirt() const;
|
||||
bool has_skirt() const;
|
||||
// Returns an empty string if valid, otherwise returns an error message.
|
||||
|
@ -326,6 +327,9 @@ private:
|
|||
bool invalidate_state_by_config_options(const std::vector<t_config_option_key> &opt_keys);
|
||||
PrintRegionConfig _region_config_from_model_volume(const ModelVolume &volume);
|
||||
|
||||
// Depth of the wipe tower to pass to GLCanvas3D for exact bounding box:
|
||||
float m_wipe_tower_depth = 0.f;
|
||||
|
||||
// Has the calculation been canceled?
|
||||
tbb::atomic<bool> m_canceled;
|
||||
};
|
||||
|
|
|
@ -504,19 +504,38 @@ PrintConfigDef::PrintConfigDef()
|
|||
def = this->add("filament_cooling_initial_speed", coFloats);
|
||||
def->label = L("Speed of the first cooling move");
|
||||
def->tooltip = L("Cooling moves are gradually accelerating beginning at this speed. ");
|
||||
def->cli = "filament-cooling-initial-speed=i@";
|
||||
def->cli = "filament-cooling-initial-speed=f@";
|
||||
def->sidetext = L("mm/s");
|
||||
def->min = 0;
|
||||
def->default_value = new ConfigOptionFloats { 2.2f };
|
||||
|
||||
def = this->add("filament_minimal_purge_on_wipe_tower", coFloats);
|
||||
def->label = L("Minimal purge on wipe tower");
|
||||
def->tooltip = L("After a tool change, the exact position of the newly loaded filament inside "
|
||||
"the nozzle may not be known, and the filament pressure is likely not yet stable. "
|
||||
"Before purging the print head into an infill or a sacrificial object, Slic3r will always prime "
|
||||
"this amount of material into the wipe tower to produce successive infill or sacrificial object extrusions reliably.");
|
||||
def->cli = "filament-minimal-purge-on-wipe-tower=f@";
|
||||
def->sidetext = L("mm³");
|
||||
def->min = 0;
|
||||
def->default_value = new ConfigOptionFloats { 15.f };
|
||||
|
||||
def = this->add("filament_cooling_final_speed", coFloats);
|
||||
def->label = L("Speed of the last cooling move");
|
||||
def->tooltip = L("Cooling moves are gradually accelerating towards this speed. ");
|
||||
def->cli = "filament-cooling-final-speed=i@";
|
||||
def->cli = "filament-cooling-final-speed=f@";
|
||||
def->sidetext = L("mm/s");
|
||||
def->min = 0;
|
||||
def->default_value = new ConfigOptionFloats { 3.4f };
|
||||
|
||||
def = this->add("filament_load_time", coFloats);
|
||||
def->label = L("Filament load time");
|
||||
def->tooltip = L("Time for the printer firmware (or the Multi Material Unit 2.0) to load a new filament during a tool change (when executing the T code). This time is added to the total print time by the G-code time estimator.");
|
||||
def->cli = "filament-load-time=i@";
|
||||
def->sidetext = L("s");
|
||||
def->min = 0;
|
||||
def->default_value = new ConfigOptionFloats { 0.0f };
|
||||
|
||||
def = this->add("filament_ramming_parameters", coStrings);
|
||||
def->label = L("Ramming parameters");
|
||||
def->tooltip = L("This string is edited by RammingDialog and contains ramming specific parameters ");
|
||||
|
@ -524,6 +543,14 @@ PrintConfigDef::PrintConfigDef()
|
|||
def->default_value = new ConfigOptionStrings { "120 100 6.6 6.8 7.2 7.6 7.9 8.2 8.7 9.4 9.9 10.0|"
|
||||
" 0.05 6.6 0.45 6.8 0.95 7.8 1.45 8.3 1.95 9.7 2.45 10 2.95 7.6 3.45 7.6 3.95 7.6 4.45 7.6 4.95 7.6" };
|
||||
|
||||
def = this->add("filament_unload_time", coFloats);
|
||||
def->label = L("Filament unload time");
|
||||
def->tooltip = L("Time for the printer firmware (or the Multi Material Unit 2.0) to unload a filament during a tool change (when executing the T code). This time is added to the total print time by the G-code time estimator.");
|
||||
def->cli = "filament-unload-time=i@";
|
||||
def->sidetext = L("s");
|
||||
def->min = 0;
|
||||
def->default_value = new ConfigOptionFloats { 0.0f };
|
||||
|
||||
def = this->add("filament_diameter", coFloats);
|
||||
def->label = L("Diameter");
|
||||
def->tooltip = L("Enter your filament diameter here. Good precision is required, so use a caliper "
|
||||
|
@ -545,10 +572,7 @@ PrintConfigDef::PrintConfigDef()
|
|||
|
||||
def = this->add("filament_type", coStrings);
|
||||
def->label = L("Filament type");
|
||||
def->tooltip = L("If you want to process the output G-code through custom scripts, just list their "
|
||||
"absolute paths here. Separate multiple scripts with a semicolon. Scripts will be passed "
|
||||
"the absolute path to the G-code file as the first argument, and they can access "
|
||||
"the Slic3r config settings by reading environment variables.");
|
||||
def->tooltip = L("The filament material type for use in custom G-codes.");
|
||||
def->cli = "filament_type=s@";
|
||||
def->gui_type = "f_enum_open";
|
||||
def->gui_flags = "show_value";
|
||||
|
@ -892,8 +916,16 @@ PrintConfigDef::PrintConfigDef()
|
|||
def->min = 0;
|
||||
def->default_value = new ConfigOptionFloat(0.3);
|
||||
|
||||
def = this->add("remaining_times", coBool);
|
||||
def->label = L("Supports remaining times");
|
||||
def->tooltip = L("Emit M73 P[percent printed] R[remaining time in minutes] at 1 minute"
|
||||
" intervals into the G-code to let the firmware show accurate remaining time."
|
||||
" As of now only the Prusa i3 MK3 firmware recognizes M73."
|
||||
" Also the i3 MK3 firmware supports M73 Qxx Sxx for the silent mode.");
|
||||
def->default_value = new ConfigOptionBool(false);
|
||||
|
||||
def = this->add("silent_mode", coBool);
|
||||
def->label = L("Support silent mode");
|
||||
def->label = L("Supports silent mode");
|
||||
def->tooltip = L("Set silent mode for the G-code flavor");
|
||||
def->default_value = new ConfigOptionBool(true);
|
||||
|
||||
|
@ -1623,6 +1655,12 @@ PrintConfigDef::PrintConfigDef()
|
|||
def->cli = "single-extruder-multi-material!";
|
||||
def->default_value = new ConfigOptionBool(false);
|
||||
|
||||
def = this->add("single_extruder_multi_material_priming", coBool);
|
||||
def->label = L("Prime all printing extruders");
|
||||
def->tooltip = L("If enabled, all printing extruders will be primed at the front edge of the print bed at the start of the print.");
|
||||
def->cli = "single-extruder-multi-material-priming!";
|
||||
def->default_value = new ConfigOptionBool(true);
|
||||
|
||||
def = this->add("support_material", coBool);
|
||||
def->label = L("Generate support material");
|
||||
def->category = L("Support material");
|
||||
|
@ -1993,8 +2031,8 @@ PrintConfigDef::PrintConfigDef()
|
|||
|
||||
def = this->add("wipe_into_infill", coBool);
|
||||
def->category = L("Extruders");
|
||||
def->label = L("Purging into infill");
|
||||
def->tooltip = L("Wiping after toolchange will be preferentially done inside infills. "
|
||||
def->label = L("Purge into this object's infill");
|
||||
def->tooltip = L("Purging after toolchange will done inside this object's infills. "
|
||||
"This lowers the amount of waste but may result in longer print time "
|
||||
" due to additional travel moves.");
|
||||
def->cli = "wipe-into-infill!";
|
||||
|
@ -2002,8 +2040,8 @@ PrintConfigDef::PrintConfigDef()
|
|||
|
||||
def = this->add("wipe_into_objects", coBool);
|
||||
def->category = L("Extruders");
|
||||
def->label = L("Purging into objects");
|
||||
def->tooltip = L("Objects will be used to wipe the nozzle after a toolchange to save material "
|
||||
def->label = L("Purge into this object");
|
||||
def->tooltip = L("Object will be used to purge the nozzle after a toolchange to save material "
|
||||
"that would otherwise end up in the wipe tower and decrease print time. "
|
||||
"Colours of the objects will be mixed as a result.");
|
||||
def->cli = "wipe-into-objects!";
|
||||
|
|
|
@ -528,10 +528,13 @@ public:
|
|||
ConfigOptionFloats filament_cost;
|
||||
ConfigOptionFloats filament_max_volumetric_speed;
|
||||
ConfigOptionFloats filament_loading_speed;
|
||||
ConfigOptionFloats filament_load_time;
|
||||
ConfigOptionFloats filament_unloading_speed;
|
||||
ConfigOptionFloats filament_toolchange_delay;
|
||||
ConfigOptionFloats filament_unload_time;
|
||||
ConfigOptionInts filament_cooling_moves;
|
||||
ConfigOptionFloats filament_cooling_initial_speed;
|
||||
ConfigOptionFloats filament_minimal_purge_on_wipe_tower;
|
||||
ConfigOptionFloats filament_cooling_final_speed;
|
||||
ConfigOptionStrings filament_ramming_parameters;
|
||||
ConfigOptionBool gcode_comments;
|
||||
|
@ -553,6 +556,7 @@ public:
|
|||
ConfigOptionString start_gcode;
|
||||
ConfigOptionStrings start_filament_gcode;
|
||||
ConfigOptionBool single_extruder_multi_material;
|
||||
ConfigOptionBool single_extruder_multi_material_priming;
|
||||
ConfigOptionString toolchange_gcode;
|
||||
ConfigOptionFloat travel_speed;
|
||||
ConfigOptionBool use_firmware_retraction;
|
||||
|
@ -562,6 +566,7 @@ public:
|
|||
ConfigOptionFloat cooling_tube_retraction;
|
||||
ConfigOptionFloat cooling_tube_length;
|
||||
ConfigOptionFloat parking_pos_retraction;
|
||||
ConfigOptionBool remaining_times;
|
||||
ConfigOptionBool silent_mode;
|
||||
ConfigOptionFloat extra_loading_move;
|
||||
|
||||
|
@ -589,10 +594,13 @@ protected:
|
|||
OPT_PTR(filament_cost);
|
||||
OPT_PTR(filament_max_volumetric_speed);
|
||||
OPT_PTR(filament_loading_speed);
|
||||
OPT_PTR(filament_load_time);
|
||||
OPT_PTR(filament_unloading_speed);
|
||||
OPT_PTR(filament_unload_time);
|
||||
OPT_PTR(filament_toolchange_delay);
|
||||
OPT_PTR(filament_cooling_moves);
|
||||
OPT_PTR(filament_cooling_initial_speed);
|
||||
OPT_PTR(filament_minimal_purge_on_wipe_tower);
|
||||
OPT_PTR(filament_cooling_final_speed);
|
||||
OPT_PTR(filament_ramming_parameters);
|
||||
OPT_PTR(gcode_comments);
|
||||
|
@ -612,6 +620,7 @@ protected:
|
|||
OPT_PTR(retract_restart_extra_toolchange);
|
||||
OPT_PTR(retract_speed);
|
||||
OPT_PTR(single_extruder_multi_material);
|
||||
OPT_PTR(single_extruder_multi_material_priming);
|
||||
OPT_PTR(start_gcode);
|
||||
OPT_PTR(start_filament_gcode);
|
||||
OPT_PTR(toolchange_gcode);
|
||||
|
@ -623,6 +632,7 @@ protected:
|
|||
OPT_PTR(cooling_tube_retraction);
|
||||
OPT_PTR(cooling_tube_length);
|
||||
OPT_PTR(parking_pos_retraction);
|
||||
OPT_PTR(remaining_times);
|
||||
OPT_PTR(silent_mode);
|
||||
OPT_PTR(extra_loading_move);
|
||||
}
|
||||
|
|
|
@ -75,6 +75,7 @@ bool PrintObject::delete_last_copy()
|
|||
|
||||
bool PrintObject::set_copies(const Points &points)
|
||||
{
|
||||
bool copies_num_changed = this->_copies.size() != points.size();
|
||||
this->_copies = points;
|
||||
|
||||
// order copies with a nearest neighbor search and translate them by _copies_shift
|
||||
|
@ -90,7 +91,8 @@ bool PrintObject::set_copies(const Points &points)
|
|||
|
||||
bool invalidated = this->_print->invalidate_step(psSkirt);
|
||||
invalidated |= this->_print->invalidate_step(psBrim);
|
||||
invalidated |= this->_print->invalidate_step(psWipeTower);
|
||||
if (copies_num_changed)
|
||||
invalidated |= this->_print->invalidate_step(psWipeTower);
|
||||
return invalidated;
|
||||
}
|
||||
|
||||
|
|
|
@ -84,6 +84,8 @@ inline T next_highest_power_of_2(T v)
|
|||
return ++ v;
|
||||
}
|
||||
|
||||
extern std::string xml_escape(std::string text);
|
||||
|
||||
class PerlCallback {
|
||||
public:
|
||||
PerlCallback(void *sv) : m_callback(nullptr) { this->register_callback(sv); }
|
||||
|
|
|
@ -14,7 +14,7 @@
|
|||
#include <boost/thread.hpp>
|
||||
|
||||
#define SLIC3R_FORK_NAME "Slic3r Prusa Edition"
|
||||
#define SLIC3R_VERSION "1.41.0-alpha2"
|
||||
#define SLIC3R_VERSION "1.41.0-beta"
|
||||
#define SLIC3R_BUILD "UNKNOWN"
|
||||
|
||||
typedef int32_t coord_t;
|
||||
|
|
|
@ -387,4 +387,31 @@ unsigned get_current_pid()
|
|||
#endif
|
||||
}
|
||||
|
||||
std::string xml_escape(std::string text)
|
||||
{
|
||||
std::string::size_type pos = 0;
|
||||
for (;;)
|
||||
{
|
||||
pos = text.find_first_of("\"\'&<>", pos);
|
||||
if (pos == std::string::npos)
|
||||
break;
|
||||
|
||||
std::string replacement;
|
||||
switch (text[pos])
|
||||
{
|
||||
case '\"': replacement = """; break;
|
||||
case '\'': replacement = "'"; break;
|
||||
case '&': replacement = "&"; break;
|
||||
case '<': replacement = "<"; break;
|
||||
case '>': replacement = ">"; break;
|
||||
default: break;
|
||||
}
|
||||
|
||||
text.replace(pos, 1, replacement);
|
||||
pos += replacement.size();
|
||||
}
|
||||
|
||||
return text;
|
||||
}
|
||||
|
||||
}; // namespace Slic3r
|
||||
|
|
|
@ -33,7 +33,9 @@
|
|||
#define UTILS_H
|
||||
|
||||
// Otherwise #defines like M_PI are undeclared under Visual Studio
|
||||
#define _USE_MATH_DEFINES
|
||||
#ifndef _USE_MATH_DEFINES
|
||||
#define _USE_MATH_DEFINES
|
||||
#endif /* _USE_MATH_DEFINES */
|
||||
|
||||
#include <exception>
|
||||
#include <math.h>
|
||||
|
|
|
@ -8,6 +8,7 @@
|
|||
#include <unordered_map>
|
||||
|
||||
#include <slic3r/GUI/GUI.hpp>
|
||||
#include <ModelArrange.hpp>
|
||||
#include <slic3r/GUI/PresetBundle.hpp>
|
||||
|
||||
#include <Geometry.hpp>
|
||||
|
@ -293,6 +294,8 @@ void AppController::arrange_model()
|
|||
supports_asynch()? std::launch::async : std::launch::deferred,
|
||||
[this]()
|
||||
{
|
||||
using Coord = libnest2d::TCoord<libnest2d::PointImpl>;
|
||||
|
||||
unsigned count = 0;
|
||||
for(auto obj : model_->objects) count += obj->instances.size();
|
||||
|
||||
|
@ -310,13 +313,25 @@ void AppController::arrange_model()
|
|||
|
||||
auto dist = print_ctl()->config().min_object_distance();
|
||||
|
||||
BoundingBoxf bb(print_ctl()->config().bed_shape.values);
|
||||
// Create the arranger config
|
||||
auto min_obj_distance = static_cast<Coord>(dist/SCALING_FACTOR);
|
||||
|
||||
auto& bedpoints = print_ctl()->config().bed_shape.values;
|
||||
Polyline bed; bed.points.reserve(bedpoints.size());
|
||||
for(auto& v : bedpoints)
|
||||
bed.append(Point::new_scale(v(0), v(1)));
|
||||
|
||||
if(pind) pind->update(0, _(L("Arranging objects...")));
|
||||
|
||||
try {
|
||||
model_->arrange_objects(dist, &bb, [pind, count](unsigned rem){
|
||||
if(pind) pind->update(count - rem, _(L("Arranging objects...")));
|
||||
arr::arrange(*model_,
|
||||
min_obj_distance,
|
||||
bed,
|
||||
arr::BOX,
|
||||
false, // create many piles not just one pile
|
||||
[pind, count](unsigned rem) {
|
||||
if(pind)
|
||||
pind->update(count - rem, _(L("Arranging objects...")));
|
||||
});
|
||||
} catch(std::exception& e) {
|
||||
std::cerr << e.what() << std::endl;
|
||||
|
|
|
@ -205,11 +205,12 @@ GLVolume::GLVolume(float r, float g, float b, float a)
|
|||
, selected(false)
|
||||
, is_active(true)
|
||||
, zoom_to_volumes(true)
|
||||
, outside_printer_detection_enabled(true)
|
||||
, shader_outside_printer_detection_enabled(false)
|
||||
, is_outside(false)
|
||||
, hover(false)
|
||||
, is_modifier(false)
|
||||
, is_wipe_tower(false)
|
||||
, is_extrusion_path(false)
|
||||
, tverts_range(0, size_t(-1))
|
||||
, qverts_range(0, size_t(-1))
|
||||
{
|
||||
|
@ -245,7 +246,7 @@ void GLVolume::set_render_color()
|
|||
set_render_color(is_outside ? SELECTED_OUTSIDE_COLOR : SELECTED_COLOR, 4);
|
||||
else if (hover)
|
||||
set_render_color(HOVER_COLOR, 4);
|
||||
else if (is_outside)
|
||||
else if (is_outside && shader_outside_printer_detection_enabled)
|
||||
set_render_color(OUTSIDE_COLOR, 4);
|
||||
else
|
||||
set_render_color(color, 4);
|
||||
|
@ -435,7 +436,7 @@ void GLVolume::render_VBOs(int color_id, int detection_id, int worldmatrix_id) c
|
|||
::glColor4f(render_color[0], render_color[1], render_color[2], render_color[3]);
|
||||
|
||||
if (detection_id != -1)
|
||||
::glUniform1i(detection_id, outside_printer_detection_enabled ? 1 : 0);
|
||||
::glUniform1i(detection_id, shader_outside_printer_detection_enabled ? 1 : 0);
|
||||
|
||||
if (worldmatrix_id != -1)
|
||||
::glUniformMatrix4fv(worldmatrix_id, 1, GL_FALSE, (const GLfloat*)world_matrix().data());
|
||||
|
@ -454,7 +455,7 @@ void GLVolume::render_VBOs(int color_id, int detection_id, int worldmatrix_id) c
|
|||
::glColor4f(render_color[0], render_color[1], render_color[2], render_color[3]);
|
||||
|
||||
if (detection_id != -1)
|
||||
::glUniform1i(detection_id, outside_printer_detection_enabled ? 1 : 0);
|
||||
::glUniform1i(detection_id, shader_outside_printer_detection_enabled ? 1 : 0);
|
||||
|
||||
if (worldmatrix_id != -1)
|
||||
::glUniformMatrix4fv(worldmatrix_id, 1, GL_FALSE, (const GLfloat*)world_matrix().data());
|
||||
|
@ -627,7 +628,7 @@ std::vector<int> GLVolumeCollection::load_object(
|
|||
v.extruder_id = extruder_id;
|
||||
}
|
||||
v.is_modifier = model_volume->modifier;
|
||||
v.outside_printer_detection_enabled = !model_volume->modifier;
|
||||
v.shader_outside_printer_detection_enabled = !model_volume->modifier;
|
||||
v.set_origin(Pointf3(instance->offset(0), instance->offset(1), 0.0));
|
||||
v.set_angle_z(instance->rotation);
|
||||
v.set_scale_factor(instance->scaling_factor);
|
||||
|
@ -637,20 +638,62 @@ std::vector<int> GLVolumeCollection::load_object(
|
|||
return volumes_idx;
|
||||
}
|
||||
|
||||
int GLVolumeCollection::load_wipe_tower_preview(
|
||||
int obj_idx, float pos_x, float pos_y, float width, float depth, float height, float rotation_angle, bool use_VBOs)
|
||||
{
|
||||
float color[4] = { 0.5f, 0.5f, 0.0f, 0.5f };
|
||||
this->volumes.emplace_back(new GLVolume(color));
|
||||
GLVolume &v = *this->volumes.back();
|
||||
|
||||
int GLVolumeCollection::load_wipe_tower_preview(
|
||||
int obj_idx, float pos_x, float pos_y, float width, float depth, float height, float rotation_angle, bool use_VBOs, bool size_unknown, float brim_width)
|
||||
{
|
||||
if (depth < 0.01f)
|
||||
return int(this->volumes.size() - 1);
|
||||
if (height == 0.0f)
|
||||
height = 0.1f;
|
||||
|
||||
auto mesh = make_cube(width, depth, height);
|
||||
mesh.translate(-width / 2.f, -depth / 2.f, 0.f);
|
||||
Point origin_of_rotation(0.f, 0.f);
|
||||
mesh.rotate(rotation_angle,&origin_of_rotation);
|
||||
TriangleMesh mesh;
|
||||
float color[4] = { 0.5f, 0.5f, 0.0f, 1.f };
|
||||
|
||||
// In case we don't know precise dimensions of the wipe tower yet, we'll draw the box with different color with one side jagged:
|
||||
if (size_unknown) {
|
||||
color[0] = 0.9f;
|
||||
color[1] = 0.6f;
|
||||
|
||||
depth = std::max(depth, 10.f); // Too narrow tower would interfere with the teeth. The estimate is not precise anyway.
|
||||
float min_width = 30.f;
|
||||
// We'll now create the box with jagged edge. y-coordinates of the pre-generated model are shifted so that the front
|
||||
// edge has y=0 and centerline of the back edge has y=depth:
|
||||
Pointf3s points;
|
||||
std::vector<Point3> facets;
|
||||
float out_points_idx[][3] = {{0, -depth, 0}, {0, 0, 0}, {38.453, 0, 0}, {61.547, 0, 0}, {100, 0, 0}, {100, -depth, 0}, {55.7735, -10, 0}, {44.2265, 10, 0},
|
||||
{38.453, 0, 1}, {0, 0, 1}, {0, -depth, 1}, {100, -depth, 1}, {100, 0, 1}, {61.547, 0, 1}, {55.7735, -10, 1}, {44.2265, 10, 1}};
|
||||
int out_facets_idx[][3] = {{0, 1, 2}, {3, 4, 5}, {6, 5, 0}, {3, 5, 6}, {6, 2, 7}, {6, 0, 2}, {8, 9, 10}, {11, 12, 13}, {10, 11, 14}, {14, 11, 13}, {15, 8, 14},
|
||||
{8, 10, 14}, {3, 12, 4}, {3, 13, 12}, {6, 13, 3}, {6, 14, 13}, {7, 14, 6}, {7, 15, 14}, {2, 15, 7}, {2, 8, 15}, {1, 8, 2}, {1, 9, 8},
|
||||
{0, 9, 1}, {0, 10, 9}, {5, 10, 0}, {5, 11, 10}, {4, 11, 5}, {4, 12, 11}};
|
||||
for (int i=0;i<16;++i)
|
||||
points.push_back(Pointf3(out_points_idx[i][0] / (100.f/min_width), out_points_idx[i][1] + depth, out_points_idx[i][2]));
|
||||
for (int i=0;i<28;++i)
|
||||
facets.push_back(Point3(out_facets_idx[i][0], out_facets_idx[i][1], out_facets_idx[i][2]));
|
||||
TriangleMesh tooth_mesh(points, facets);
|
||||
|
||||
// We have the mesh ready. It has one tooth and width of min_width. We will now append several of these together until we are close to
|
||||
// the required width of the block. Than we can scale it precisely.
|
||||
size_t n = std::max(1, int(width/min_width)); // How many shall be merged?
|
||||
for (size_t i=0;i<n;++i) {
|
||||
mesh.merge(tooth_mesh);
|
||||
tooth_mesh.translate(min_width, 0.f, 0.f);
|
||||
}
|
||||
|
||||
mesh.scale(Pointf3(width/(n*min_width), 1.f, height)); // Scaling to proper width
|
||||
}
|
||||
else
|
||||
mesh = make_cube(width, depth, height);
|
||||
|
||||
// We'll make another mesh to show the brim (fixed layer height):
|
||||
TriangleMesh brim_mesh = make_cube(width+2.f*brim_width, depth+2.f*brim_width, 0.2f);
|
||||
brim_mesh.translate(-brim_width, -brim_width, 0.f);
|
||||
mesh.merge(brim_mesh);
|
||||
|
||||
mesh.rotate(rotation_angle, &origin_of_rotation); // rotates the box according to the config rotation setting
|
||||
|
||||
this->volumes.emplace_back(new GLVolume(color));
|
||||
GLVolume &v = *this->volumes.back();
|
||||
|
||||
if (use_VBOs)
|
||||
v.indexed_vertex_array.load_mesh_full_shading(mesh);
|
||||
|
@ -666,6 +709,7 @@ int GLVolumeCollection::load_wipe_tower_preview(
|
|||
v.select_group_id = obj_idx * 1000000;
|
||||
v.drag_group_id = obj_idx * 1000;
|
||||
v.is_wipe_tower = true;
|
||||
v.shader_outside_printer_detection_enabled = ! size_unknown;
|
||||
return int(this->volumes.size() - 1);
|
||||
}
|
||||
|
||||
|
@ -1645,7 +1689,7 @@ void _3DScene::update_volumes_selection(wxGLCanvas* canvas, const std::vector<in
|
|||
s_canvas_mgr.update_volumes_selection(canvas, selections);
|
||||
}
|
||||
|
||||
bool _3DScene::check_volumes_outside_state(wxGLCanvas* canvas, const DynamicPrintConfig* config)
|
||||
int _3DScene::check_volumes_outside_state(wxGLCanvas* canvas, const DynamicPrintConfig* config)
|
||||
{
|
||||
return s_canvas_mgr.check_volumes_outside_state(canvas, config);
|
||||
}
|
||||
|
@ -1780,6 +1824,11 @@ void _3DScene::enable_force_zoom_to_bed(wxGLCanvas* canvas, bool enable)
|
|||
s_canvas_mgr.enable_force_zoom_to_bed(canvas, enable);
|
||||
}
|
||||
|
||||
void _3DScene::enable_dynamic_background(wxGLCanvas* canvas, bool enable)
|
||||
{
|
||||
s_canvas_mgr.enable_dynamic_background(canvas, enable);
|
||||
}
|
||||
|
||||
void _3DScene::allow_multisample(wxGLCanvas* canvas, bool allow)
|
||||
{
|
||||
s_canvas_mgr.allow_multisample(canvas, allow);
|
||||
|
@ -1962,26 +2011,16 @@ void _3DScene::reload_scene(wxGLCanvas* canvas, bool force)
|
|||
s_canvas_mgr.reload_scene(canvas, force);
|
||||
}
|
||||
|
||||
void _3DScene::load_print_toolpaths(wxGLCanvas* canvas)
|
||||
{
|
||||
s_canvas_mgr.load_print_toolpaths(canvas);
|
||||
}
|
||||
|
||||
void _3DScene::load_print_object_toolpaths(wxGLCanvas* canvas, const PrintObject* print_object, const std::vector<std::string>& str_tool_colors)
|
||||
{
|
||||
s_canvas_mgr.load_print_object_toolpaths(canvas, print_object, str_tool_colors);
|
||||
}
|
||||
|
||||
void _3DScene::load_wipe_tower_toolpaths(wxGLCanvas* canvas, const std::vector<std::string>& str_tool_colors)
|
||||
{
|
||||
s_canvas_mgr.load_wipe_tower_toolpaths(canvas, str_tool_colors);
|
||||
}
|
||||
|
||||
void _3DScene::load_gcode_preview(wxGLCanvas* canvas, const GCodePreviewData* preview_data, const std::vector<std::string>& str_tool_colors)
|
||||
{
|
||||
s_canvas_mgr.load_gcode_preview(canvas, preview_data, str_tool_colors);
|
||||
}
|
||||
|
||||
void _3DScene::load_preview(wxGLCanvas* canvas, const std::vector<std::string>& str_tool_colors)
|
||||
{
|
||||
s_canvas_mgr.load_preview(canvas, str_tool_colors);
|
||||
}
|
||||
|
||||
void _3DScene::reset_legend_texture()
|
||||
{
|
||||
s_canvas_mgr.reset_legend_texture();
|
||||
|
|
|
@ -289,8 +289,8 @@ public:
|
|||
bool is_active;
|
||||
// Whether or not to use this volume when applying zoom_to_volumes()
|
||||
bool zoom_to_volumes;
|
||||
// Wheter or not this volume is enabled for outside print volume detection.
|
||||
bool outside_printer_detection_enabled;
|
||||
// Wheter or not this volume is enabled for outside print volume detection in shader.
|
||||
bool shader_outside_printer_detection_enabled;
|
||||
// Wheter or not this volume is outside print volume.
|
||||
bool is_outside;
|
||||
// Boolean: Is mouse over this object?
|
||||
|
@ -299,6 +299,8 @@ public:
|
|||
bool is_modifier;
|
||||
// Wheter or not this volume has been generated from the wipe tower
|
||||
bool is_wipe_tower;
|
||||
// Wheter or not this volume has been generated from an extrusion path
|
||||
bool is_extrusion_path;
|
||||
|
||||
// Interleaved triangles & normals with indexed triangles & quads.
|
||||
GLIndexedVertexArray indexed_vertex_array;
|
||||
|
@ -399,7 +401,7 @@ public:
|
|||
bool use_VBOs);
|
||||
|
||||
int load_wipe_tower_preview(
|
||||
int obj_idx, float pos_x, float pos_y, float width, float depth, float height, float rotation_angle, bool use_VBOs);
|
||||
int obj_idx, float pos_x, float pos_y, float width, float depth, float height, float rotation_angle, bool use_VBOs, bool size_unknown, float brim_width);
|
||||
|
||||
// Render the volumes by OpenGL.
|
||||
void render_VBOs() const;
|
||||
|
@ -460,7 +462,7 @@ public:
|
|||
static void deselect_volumes(wxGLCanvas* canvas);
|
||||
static void select_volume(wxGLCanvas* canvas, unsigned int id);
|
||||
static void update_volumes_selection(wxGLCanvas* canvas, const std::vector<int>& selections);
|
||||
static bool check_volumes_outside_state(wxGLCanvas* canvas, const DynamicPrintConfig* config);
|
||||
static int check_volumes_outside_state(wxGLCanvas* canvas, const DynamicPrintConfig* config);
|
||||
static bool move_volume_up(wxGLCanvas* canvas, unsigned int id);
|
||||
static bool move_volume_down(wxGLCanvas* canvas, unsigned int id);
|
||||
|
||||
|
@ -497,6 +499,7 @@ public:
|
|||
static void enable_gizmos(wxGLCanvas* canvas, bool enable);
|
||||
static void enable_shader(wxGLCanvas* canvas, bool enable);
|
||||
static void enable_force_zoom_to_bed(wxGLCanvas* canvas, bool enable);
|
||||
static void enable_dynamic_background(wxGLCanvas* canvas, bool enable);
|
||||
static void allow_multisample(wxGLCanvas* canvas, bool allow);
|
||||
|
||||
static void zoom_to_bed(wxGLCanvas* canvas);
|
||||
|
@ -536,10 +539,8 @@ public:
|
|||
|
||||
static void reload_scene(wxGLCanvas* canvas, bool force);
|
||||
|
||||
static void load_print_toolpaths(wxGLCanvas* canvas);
|
||||
static void load_print_object_toolpaths(wxGLCanvas* canvas, const PrintObject* print_object, const std::vector<std::string>& str_tool_colors);
|
||||
static void load_wipe_tower_toolpaths(wxGLCanvas* canvas, const std::vector<std::string>& str_tool_colors);
|
||||
static void load_gcode_preview(wxGLCanvas* canvas, const GCodePreviewData* preview_data, const std::vector<std::string>& str_tool_colors);
|
||||
static void load_preview(wxGLCanvas* canvas, const std::vector<std::string>& str_tool_colors);
|
||||
|
||||
static void reset_legend_texture();
|
||||
|
||||
|
|
|
@ -9,6 +9,8 @@
|
|||
#include "Model.hpp"
|
||||
#include "boost/nowide/iostream.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
namespace Slic3r {
|
||||
namespace GUI {
|
||||
|
||||
|
@ -146,21 +148,18 @@ void BedShapePanel::set_shape(ConfigOptionPoints* points)
|
|||
if (lines[0].parallel_to(lines[2]) && lines[1].parallel_to(lines[3])) {
|
||||
// okay, it's a rectangle
|
||||
// find origin
|
||||
// the || 0 hack prevents "-0" which might confuse the user
|
||||
int x_min, x_max, y_min, y_max;
|
||||
x_max = x_min = points->values[0](0);
|
||||
coordf_t x_min, x_max, y_min, y_max;
|
||||
x_max = x_min = points->values[0](0);
|
||||
y_max = y_min = points->values[0](1);
|
||||
for (auto pt : points->values){
|
||||
if (x_min > pt(0)) x_min = pt(0);
|
||||
if (x_max < pt(0)) x_max = pt(0);
|
||||
if (y_min > pt(1)) y_min = pt(1);
|
||||
if (y_max < pt(1)) y_max = pt(1);
|
||||
}
|
||||
if (x_min < 0) x_min = 0;
|
||||
if (x_max < 0) x_max = 0;
|
||||
if (y_min < 0) y_min = 0;
|
||||
if (y_max < 0) y_max = 0;
|
||||
auto origin = new ConfigOptionPoints{ Pointf(-x_min, -y_min) };
|
||||
for (auto pt : points->values)
|
||||
{
|
||||
x_min = std::min(x_min, pt(0));
|
||||
x_max = std::max(x_max, pt(0));
|
||||
y_min = std::min(y_min, pt(1));
|
||||
y_max = std::max(y_max, pt(1));
|
||||
}
|
||||
|
||||
auto origin = new ConfigOptionPoints{ Pointf(-x_min, -y_min) };
|
||||
|
||||
m_shape_options_book->SetSelection(SHAPE_RECTANGULAR);
|
||||
auto optgroup = m_optgroups[SHAPE_RECTANGULAR];
|
||||
|
|
|
@ -95,9 +95,10 @@ namespace Slic3r { namespace GUI {
|
|||
wxString tooltip_text("");
|
||||
wxString tooltip = _(m_opt.tooltip);
|
||||
if (tooltip.length() > 0)
|
||||
tooltip_text = tooltip + "(" + _(L("default")) + ": " +
|
||||
(boost::iends_with(m_opt_id, "_gcode") ? "\n" : "") +
|
||||
default_string + ")";
|
||||
tooltip_text = tooltip + "\n" + _(L("default value")) + "\t: " +
|
||||
(boost::iends_with(m_opt_id, "_gcode") ? "\n" : "") + default_string +
|
||||
(boost::iends_with(m_opt_id, "_gcode") ? "" : "\n") +
|
||||
_(L("parameter name")) + "\t: " + m_opt_id;
|
||||
|
||||
return tooltip_text;
|
||||
}
|
||||
|
|
|
@ -1,11 +1,24 @@
|
|||
#include "FirmwareDialog.hpp"
|
||||
|
||||
#include <numeric>
|
||||
#include <algorithm>
|
||||
#include <thread>
|
||||
#include <condition_variable>
|
||||
#include <stdexcept>
|
||||
#include <boost/format.hpp>
|
||||
#include <boost/asio.hpp>
|
||||
#include <boost/filesystem/path.hpp>
|
||||
#include <boost/filesystem/fstream.hpp>
|
||||
#include <boost/log/trivial.hpp>
|
||||
#include <boost/optional.hpp>
|
||||
|
||||
#include "libslic3r/Utils.hpp"
|
||||
#include "avrdude/avrdude-slic3r.hpp"
|
||||
#include "GUI.hpp"
|
||||
#include "MsgDialog.hpp"
|
||||
#include "../Utils/HexFile.hpp"
|
||||
#include "../Utils/Serial.hpp"
|
||||
|
||||
// wx includes need to come after asio because of the WinSock.h problem
|
||||
#include "FirmwareDialog.hpp"
|
||||
|
||||
#include <wx/app.h>
|
||||
#include <wx/event.h>
|
||||
|
@ -21,17 +34,30 @@
|
|||
#include <wx/gauge.h>
|
||||
#include <wx/collpane.h>
|
||||
#include <wx/msgdlg.h>
|
||||
#include <wx/filefn.h>
|
||||
|
||||
#include "libslic3r/Utils.hpp"
|
||||
#include "avrdude/avrdude-slic3r.hpp"
|
||||
#include "GUI.hpp"
|
||||
#include "../Utils/Serial.hpp"
|
||||
|
||||
namespace fs = boost::filesystem;
|
||||
namespace asio = boost::asio;
|
||||
using boost::system::error_code;
|
||||
using boost::optional;
|
||||
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
using Utils::HexFile;
|
||||
using Utils::SerialPortInfo;
|
||||
using Utils::Serial;
|
||||
|
||||
|
||||
// USB IDs used to perform device lookup
|
||||
enum {
|
||||
USB_VID_PRUSA = 0x2c99,
|
||||
USB_PID_MK2 = 1,
|
||||
USB_PID_MK3 = 2,
|
||||
USB_PID_MMU_BOOT = 3,
|
||||
USB_PID_MMU_APP = 4,
|
||||
};
|
||||
|
||||
// This enum discriminates the kind of information in EVT_AVRDUDE,
|
||||
// it's stored in the ExtraLong field of wxCommandEvent.
|
||||
|
@ -39,12 +65,16 @@ enum AvrdudeEvent
|
|||
{
|
||||
AE_MESSAGE,
|
||||
AE_PROGRESS,
|
||||
AE_STATUS,
|
||||
AE_EXIT,
|
||||
};
|
||||
|
||||
wxDECLARE_EVENT(EVT_AVRDUDE, wxCommandEvent);
|
||||
wxDEFINE_EVENT(EVT_AVRDUDE, wxCommandEvent);
|
||||
|
||||
wxDECLARE_EVENT(EVT_ASYNC_DIALOG, wxCommandEvent);
|
||||
wxDEFINE_EVENT(EVT_ASYNC_DIALOG, wxCommandEvent);
|
||||
|
||||
|
||||
// Private
|
||||
|
||||
|
@ -55,13 +85,14 @@ struct FirmwareDialog::priv
|
|||
AC_NONE,
|
||||
AC_SUCCESS,
|
||||
AC_FAILURE,
|
||||
AC_CANCEL,
|
||||
AC_USER_CANCELLED,
|
||||
};
|
||||
|
||||
FirmwareDialog *q; // PIMPL back pointer ("Q-Pointer")
|
||||
|
||||
// GUI elements
|
||||
wxComboBox *port_picker;
|
||||
std::vector<Utils::SerialPortInfo> ports;
|
||||
wxStaticText *port_autodetect;
|
||||
wxFilePickerCtrl *hex_picker;
|
||||
wxStaticText *txt_status;
|
||||
wxGauge *progressbar;
|
||||
|
@ -72,33 +103,67 @@ struct FirmwareDialog::priv
|
|||
wxButton *btn_flash;
|
||||
wxString btn_flash_label_ready;
|
||||
wxString btn_flash_label_flashing;
|
||||
wxString label_status_flashing;
|
||||
|
||||
wxTimer timer_pulse;
|
||||
|
||||
// Async modal dialog during flashing
|
||||
std::mutex mutex;
|
||||
int modal_response;
|
||||
std::condition_variable response_cv;
|
||||
|
||||
// Data
|
||||
std::vector<SerialPortInfo> ports;
|
||||
optional<SerialPortInfo> port;
|
||||
HexFile hex_file;
|
||||
|
||||
// This is a shared pointer holding the background AvrDude task
|
||||
// also serves as a status indication (it is set _iff_ the background task is running, otherwise it is reset).
|
||||
AvrDude::Ptr avrdude;
|
||||
std::string avrdude_config;
|
||||
unsigned progress_tasks_done;
|
||||
bool cancelled;
|
||||
unsigned progress_tasks_bar;
|
||||
bool user_cancelled;
|
||||
const bool extra_verbose; // For debugging
|
||||
|
||||
priv(FirmwareDialog *q) :
|
||||
q(q),
|
||||
btn_flash_label_ready(_(L("Flash!"))),
|
||||
btn_flash_label_flashing(_(L("Cancel"))),
|
||||
label_status_flashing(_(L("Flashing in progress. Please do not disconnect the printer!"))),
|
||||
timer_pulse(q),
|
||||
avrdude_config((fs::path(::Slic3r::resources_dir()) / "avrdude" / "avrdude.conf").string()),
|
||||
progress_tasks_done(0),
|
||||
cancelled(false)
|
||||
progress_tasks_bar(0),
|
||||
user_cancelled(false),
|
||||
extra_verbose(false)
|
||||
{}
|
||||
|
||||
void find_serial_ports();
|
||||
void flashing_start(bool flashing_l10n);
|
||||
void fit_no_shrink();
|
||||
void set_txt_status(const wxString &label);
|
||||
void flashing_start(unsigned tasks);
|
||||
void flashing_done(AvrDudeComplete complete);
|
||||
size_t hex_lang_offset(const wxString &path);
|
||||
void enable_port_picker(bool enable);
|
||||
void load_hex_file(const wxString &path);
|
||||
void queue_status(wxString message);
|
||||
void queue_error(const wxString &message);
|
||||
|
||||
bool ask_model_id_mismatch(const std::string &printer_model);
|
||||
bool check_model_id();
|
||||
void wait_for_mmu_bootloader(unsigned retries);
|
||||
void mmu_reboot(const SerialPortInfo &port);
|
||||
void lookup_port_mmu();
|
||||
void prepare_common();
|
||||
void prepare_mk2();
|
||||
void prepare_mk3();
|
||||
void prepare_mm_control();
|
||||
void perform_upload();
|
||||
void cancel();
|
||||
|
||||
void user_cancel();
|
||||
void on_avrdude(const wxCommandEvent &evt);
|
||||
void on_async_dialog(const wxCommandEvent &evt);
|
||||
void ensure_joined();
|
||||
};
|
||||
|
||||
void FirmwareDialog::priv::find_serial_ports()
|
||||
|
@ -108,7 +173,7 @@ void FirmwareDialog::priv::find_serial_ports()
|
|||
this->ports = new_ports;
|
||||
port_picker->Clear();
|
||||
for (const auto &port : this->ports)
|
||||
port_picker->Append(port.friendly_name);
|
||||
port_picker->Append(wxString::FromUTF8(port.friendly_name.data()));
|
||||
if (ports.size() > 0) {
|
||||
int idx = port_picker->GetValue().IsEmpty() ? 0 : -1;
|
||||
for (int i = 0; i < (int)this->ports.size(); ++ i)
|
||||
|
@ -122,20 +187,43 @@ void FirmwareDialog::priv::find_serial_ports()
|
|||
}
|
||||
}
|
||||
|
||||
void FirmwareDialog::priv::flashing_start(bool flashing_l10n)
|
||||
void FirmwareDialog::priv::fit_no_shrink()
|
||||
{
|
||||
// Ensure content fits into window and window is not shrinked
|
||||
const auto old_size = q->GetSize();
|
||||
q->Layout();
|
||||
q->Fit();
|
||||
const auto new_size = q->GetSize();
|
||||
const auto new_width = std::max(old_size.GetWidth(), new_size.GetWidth());
|
||||
const auto new_height = std::max(old_size.GetHeight(), new_size.GetHeight());
|
||||
q->SetSize(new_width, new_height);
|
||||
}
|
||||
|
||||
void FirmwareDialog::priv::set_txt_status(const wxString &label)
|
||||
{
|
||||
const auto width = txt_status->GetSize().GetWidth();
|
||||
txt_status->SetLabel(label);
|
||||
txt_status->Wrap(width);
|
||||
|
||||
fit_no_shrink();
|
||||
}
|
||||
|
||||
void FirmwareDialog::priv::flashing_start(unsigned tasks)
|
||||
{
|
||||
modal_response = wxID_NONE;
|
||||
txt_stdout->Clear();
|
||||
txt_status->SetLabel(_(L("Flashing in progress. Please do not disconnect the printer!")));
|
||||
set_txt_status(label_status_flashing);
|
||||
txt_status->SetForegroundColour(GUI::get_label_clr_modified());
|
||||
port_picker->Disable();
|
||||
btn_rescan->Disable();
|
||||
hex_picker->Disable();
|
||||
btn_close->Disable();
|
||||
btn_flash->SetLabel(btn_flash_label_flashing);
|
||||
progressbar->SetRange(flashing_l10n ? 500 : 200); // See progress callback below
|
||||
progressbar->SetRange(200 * tasks); // See progress callback below
|
||||
progressbar->SetValue(0);
|
||||
progress_tasks_done = 0;
|
||||
cancelled = false;
|
||||
progress_tasks_bar = 0;
|
||||
user_cancelled = false;
|
||||
timer_pulse.Start(50);
|
||||
}
|
||||
|
||||
|
@ -152,69 +240,191 @@ void FirmwareDialog::priv::flashing_done(AvrDudeComplete complete)
|
|||
progressbar->SetValue(progressbar->GetRange());
|
||||
|
||||
switch (complete) {
|
||||
case AC_SUCCESS: txt_status->SetLabel(_(L("Flashing succeeded!"))); break;
|
||||
case AC_FAILURE: txt_status->SetLabel(_(L("Flashing failed. Please see the avrdude log below."))); break;
|
||||
case AC_CANCEL: txt_status->SetLabel(_(L("Flashing cancelled."))); break;
|
||||
case AC_SUCCESS: set_txt_status(_(L("Flashing succeeded!"))); break;
|
||||
case AC_FAILURE: set_txt_status(_(L("Flashing failed. Please see the avrdude log below."))); break;
|
||||
case AC_USER_CANCELLED: set_txt_status(_(L("Flashing cancelled."))); break;
|
||||
default: break;
|
||||
}
|
||||
}
|
||||
|
||||
size_t FirmwareDialog::priv::hex_lang_offset(const wxString &path)
|
||||
void FirmwareDialog::priv::enable_port_picker(bool enable)
|
||||
{
|
||||
fs::ifstream file(fs::path(path.wx_str()));
|
||||
if (! file.good()) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const char *hex_terminator = ":00000001FF\r";
|
||||
size_t res = 0;
|
||||
std::string line;
|
||||
while (getline(file, line, '\n').good()) {
|
||||
// Account for LF vs CRLF
|
||||
if (!line.empty() && line.back() != '\r') {
|
||||
line.push_back('\r');
|
||||
}
|
||||
|
||||
if (line == hex_terminator) {
|
||||
if (res == 0) {
|
||||
// This is the first terminator seen, save the position
|
||||
res = file.tellg();
|
||||
} else {
|
||||
// We've found another terminator, return the offset just after the first one
|
||||
// which is the start of the second 'section'.
|
||||
return res;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
port_picker->Show(enable);
|
||||
btn_rescan->Show(enable);
|
||||
port_autodetect->Show(! enable);
|
||||
q->Layout();
|
||||
fit_no_shrink();
|
||||
}
|
||||
|
||||
void FirmwareDialog::priv::perform_upload()
|
||||
void FirmwareDialog::priv::load_hex_file(const wxString &path)
|
||||
{
|
||||
auto filename = hex_picker->GetPath();
|
||||
std::string port = port_picker->GetValue().ToStdString();
|
||||
int selection = port_picker->GetSelection();
|
||||
if (selection != -1) {
|
||||
// Verify whether the combo box list selection equals to the combo box edit value.
|
||||
if (this->ports[selection].friendly_name == port)
|
||||
port = this->ports[selection].port;
|
||||
hex_file = HexFile(path.wx_str());
|
||||
enable_port_picker(hex_file.device != HexFile::DEV_MM_CONTROL);
|
||||
}
|
||||
|
||||
void FirmwareDialog::priv::queue_status(wxString message)
|
||||
{
|
||||
auto evt = new wxCommandEvent(EVT_AVRDUDE, this->q->GetId());
|
||||
evt->SetExtraLong(AE_STATUS);
|
||||
evt->SetString(std::move(message));
|
||||
wxQueueEvent(this->q, evt);
|
||||
}
|
||||
|
||||
void FirmwareDialog::priv::queue_error(const wxString &message)
|
||||
{
|
||||
auto evt = new wxCommandEvent(EVT_AVRDUDE, this->q->GetId());
|
||||
evt->SetExtraLong(AE_STATUS);
|
||||
evt->SetString(wxString::Format(_(L("Flashing failed: %s")), message));
|
||||
|
||||
wxQueueEvent(this->q, evt); avrdude->cancel();
|
||||
}
|
||||
|
||||
bool FirmwareDialog::priv::ask_model_id_mismatch(const std::string &printer_model)
|
||||
{
|
||||
// model_id in the hex file doesn't match what the printer repoted.
|
||||
// Ask the user if it should be flashed anyway.
|
||||
|
||||
std::unique_lock<std::mutex> lock(mutex);
|
||||
|
||||
auto evt = new wxCommandEvent(EVT_ASYNC_DIALOG, this->q->GetId());
|
||||
evt->SetString(wxString::Format(_(L(
|
||||
"This firmware hex file does not match the printer model.\n"
|
||||
"The hex file is intended for: %s\n"
|
||||
"Printer reported: %s\n\n"
|
||||
"Do you want to continue and flash this hex file anyway?\n"
|
||||
"Please only continue if you are sure this is the right thing to do.")),
|
||||
hex_file.model_id, printer_model
|
||||
));
|
||||
wxQueueEvent(this->q, evt);
|
||||
|
||||
response_cv.wait(lock, [this]() { return this->modal_response != wxID_NONE; });
|
||||
|
||||
if (modal_response == wxID_YES) {
|
||||
return true;
|
||||
} else {
|
||||
user_cancel();
|
||||
return false;
|
||||
}
|
||||
if (filename.IsEmpty() || port.empty()) { return; }
|
||||
}
|
||||
|
||||
const bool extra_verbose = false; // For debugging
|
||||
const auto lang_offset = hex_lang_offset(filename);
|
||||
const auto filename_utf8 = filename.utf8_str();
|
||||
bool FirmwareDialog::priv::check_model_id()
|
||||
{
|
||||
// XXX: The implementation in Serial doesn't currently work reliably enough to be used.
|
||||
// Therefore, regretably, so far the check cannot be used and we just return true here.
|
||||
// TODO: Rewrite Serial using more platform-native code.
|
||||
return true;
|
||||
|
||||
// if (hex_file.model_id.empty()) {
|
||||
// // No data to check against, assume it's ok
|
||||
// return true;
|
||||
// }
|
||||
|
||||
flashing_start(lang_offset > 0);
|
||||
// asio::io_service io;
|
||||
// Serial serial(io, port->port, 115200);
|
||||
// serial.printer_setup();
|
||||
|
||||
// It is ok here to use the q-pointer to the FirmwareDialog
|
||||
// because the dialog ensures it doesn't exit before the background thread is done.
|
||||
auto q = this->q;
|
||||
// enum {
|
||||
// TIMEOUT = 2000,
|
||||
// RETREIES = 5,
|
||||
// };
|
||||
|
||||
// Init the avrdude object
|
||||
AvrDude avrdude(avrdude_config);
|
||||
// if (! serial.printer_ready_wait(RETREIES, TIMEOUT)) {
|
||||
// queue_error(wxString::Format(_(L("Could not connect to the printer at %s")), port->port));
|
||||
// return false;
|
||||
// }
|
||||
|
||||
// Build argument list(s)
|
||||
// std::string line;
|
||||
// error_code ec;
|
||||
// serial.printer_write_line("PRUSA Rev");
|
||||
// while (serial.read_line(TIMEOUT, line, ec)) {
|
||||
// if (ec) {
|
||||
// queue_error(wxString::Format(_(L("Could not connect to the printer at %s")), port->port));
|
||||
// return false;
|
||||
// }
|
||||
|
||||
// if (line == "ok") { continue; }
|
||||
|
||||
// if (line == hex_file.model_id) {
|
||||
// return true;
|
||||
// } else {
|
||||
// return ask_model_id_mismatch(line);
|
||||
// }
|
||||
|
||||
// line.clear();
|
||||
// }
|
||||
|
||||
// return false;
|
||||
}
|
||||
|
||||
void FirmwareDialog::priv::wait_for_mmu_bootloader(unsigned retries)
|
||||
{
|
||||
enum {
|
||||
SLEEP_MS = 500,
|
||||
};
|
||||
|
||||
for (unsigned i = 0; i < retries && !user_cancelled; i++) {
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(SLEEP_MS));
|
||||
|
||||
auto ports = Utils::scan_serial_ports_extended();
|
||||
ports.erase(std::remove_if(ports.begin(), ports.end(), [=](const SerialPortInfo &port ) {
|
||||
return port.id_vendor != USB_VID_PRUSA && port.id_product != USB_PID_MMU_BOOT;
|
||||
}), ports.end());
|
||||
|
||||
if (ports.size() == 1) {
|
||||
port = ports[0];
|
||||
return;
|
||||
} else if (ports.size() > 1) {
|
||||
BOOST_LOG_TRIVIAL(error) << "Several VID/PID 0x2c99/3 devices found";
|
||||
queue_error(_(L("Multiple Original Prusa i3 MMU 2.0 devices found. Please only connect one at a time for flashing.")));
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void FirmwareDialog::priv::mmu_reboot(const SerialPortInfo &port)
|
||||
{
|
||||
asio::io_service io;
|
||||
Serial serial(io, port.port, 1200);
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(50));
|
||||
}
|
||||
|
||||
void FirmwareDialog::priv::lookup_port_mmu()
|
||||
{
|
||||
BOOST_LOG_TRIVIAL(info) << "Flashing MMU 2.0, looking for VID/PID 0x2c99/3 or 0x2c99/4 ...";
|
||||
|
||||
auto ports = Utils::scan_serial_ports_extended();
|
||||
ports.erase(std::remove_if(ports.begin(), ports.end(), [=](const SerialPortInfo &port ) {
|
||||
return port.id_vendor != USB_VID_PRUSA &&
|
||||
port.id_product != USB_PID_MMU_BOOT &&
|
||||
port.id_product != USB_PID_MMU_APP;
|
||||
}), ports.end());
|
||||
|
||||
if (ports.size() == 0) {
|
||||
BOOST_LOG_TRIVIAL(info) << "MMU 2.0 device not found, asking the user to press Reset and waiting for the device to show up ...";
|
||||
|
||||
queue_status(_(L(
|
||||
"The Multi Material Control device was not found.\n"
|
||||
"If the device is connected, please press the Reset button next to the USB connector ..."
|
||||
)));
|
||||
|
||||
wait_for_mmu_bootloader(30);
|
||||
} else if (ports.size() > 1) {
|
||||
BOOST_LOG_TRIVIAL(error) << "Several VID/PID 0x2c99/3 devices found";
|
||||
queue_error(_(L("Multiple Original Prusa i3 MMU 2.0 devices found. Please only connect one at a time for flashing.")));
|
||||
} else {
|
||||
if (ports[0].id_product == USB_PID_MMU_APP) {
|
||||
// The device needs to be rebooted into the bootloader mode
|
||||
BOOST_LOG_TRIVIAL(info) << boost::format("Found VID/PID 0x2c99/4 at `%1%`, rebooting the device ...") % ports[0].port;
|
||||
mmu_reboot(ports[0]);
|
||||
wait_for_mmu_bootloader(10);
|
||||
} else {
|
||||
port = ports[0];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void FirmwareDialog::priv::prepare_common()
|
||||
{
|
||||
std::vector<std::string> args {{
|
||||
extra_verbose ? "-vvvvv" : "-v",
|
||||
"-p", "atmega2560",
|
||||
|
@ -222,11 +432,10 @@ void FirmwareDialog::priv::perform_upload()
|
|||
// The Prusa's avrdude is patched to never send semicolons inside the data packets, as the USB to serial chip
|
||||
// is flashed with a buggy firmware.
|
||||
"-c", "wiring",
|
||||
"-P", port,
|
||||
"-b", "115200", // TODO: Allow other rates? Ditto below.
|
||||
"-P", port->port,
|
||||
"-b", "115200", // TODO: Allow other rates? Ditto elsewhere.
|
||||
"-D",
|
||||
// XXX: Safe mode?
|
||||
"-U", (boost::format("flash:w:0:%1%:i") % filename_utf8.data()).str(),
|
||||
"-U", (boost::format("flash:w:0:%1%:i") % hex_file.path.string()).str(),
|
||||
}};
|
||||
|
||||
BOOST_LOG_TRIVIAL(info) << "Invoking avrdude, arguments: "
|
||||
|
@ -234,33 +443,134 @@ void FirmwareDialog::priv::perform_upload()
|
|||
return a + ' ' + b;
|
||||
});
|
||||
|
||||
avrdude.push_args(std::move(args));
|
||||
|
||||
if (lang_offset > 0) {
|
||||
// The hex file also contains another section with l10n data to be flashed into the external flash on MK3 (Einsy)
|
||||
// This is done via another avrdude invocation, here we build arg list for that:
|
||||
std::vector<std::string> args_l10n {{
|
||||
extra_verbose ? "-vvvvv" : "-v",
|
||||
"-p", "atmega2560",
|
||||
// Using the "Arduino" mode to program Einsy's external flash with languages, using the STK500 protocol (not the STK500v2).
|
||||
// The Prusa's avrdude is patched again to never send semicolons inside the data packets.
|
||||
"-c", "arduino",
|
||||
"-P", port,
|
||||
"-b", "115200",
|
||||
"-D",
|
||||
"-u", // disable safe mode
|
||||
"-U", (boost::format("flash:w:%1%:%2%:i") % lang_offset % filename_utf8.data()).str(),
|
||||
}};
|
||||
avrdude->push_args(std::move(args));
|
||||
}
|
||||
|
||||
BOOST_LOG_TRIVIAL(info) << "Invoking avrdude for external flash flashing, arguments: "
|
||||
<< std::accumulate(std::next(args_l10n.begin()), args_l10n.end(), args_l10n[0], [](std::string a, const std::string &b) {
|
||||
return a + ' ' + b;
|
||||
});
|
||||
void FirmwareDialog::priv::prepare_mk2()
|
||||
{
|
||||
if (! port) { return; }
|
||||
|
||||
avrdude.push_args(std::move(args_l10n));
|
||||
if (! check_model_id()) {
|
||||
avrdude->cancel();
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
prepare_common();
|
||||
}
|
||||
|
||||
void FirmwareDialog::priv::prepare_mk3()
|
||||
{
|
||||
if (! port) { return; }
|
||||
|
||||
if (! check_model_id()) {
|
||||
avrdude->cancel();
|
||||
return;
|
||||
}
|
||||
|
||||
prepare_common();
|
||||
|
||||
// The hex file also contains another section with l10n data to be flashed into the external flash on MK3 (Einsy)
|
||||
// This is done via another avrdude invocation, here we build arg list for that:
|
||||
std::vector<std::string> args {{
|
||||
extra_verbose ? "-vvvvv" : "-v",
|
||||
"-p", "atmega2560",
|
||||
// Using the "Arduino" mode to program Einsy's external flash with languages, using the STK500 protocol (not the STK500v2).
|
||||
// The Prusa's avrdude is patched again to never send semicolons inside the data packets.
|
||||
"-c", "arduino",
|
||||
"-P", port->port,
|
||||
"-b", "115200",
|
||||
"-D",
|
||||
"-u", // disable safe mode
|
||||
"-U", (boost::format("flash:w:1:%1%:i") % hex_file.path.string()).str(),
|
||||
}};
|
||||
|
||||
BOOST_LOG_TRIVIAL(info) << "Invoking avrdude for external flash flashing, arguments: "
|
||||
<< std::accumulate(std::next(args.begin()), args.end(), args[0], [](std::string a, const std::string &b) {
|
||||
return a + ' ' + b;
|
||||
});
|
||||
|
||||
avrdude->push_args(std::move(args));
|
||||
}
|
||||
|
||||
void FirmwareDialog::priv::prepare_mm_control()
|
||||
{
|
||||
port = boost::none;
|
||||
lookup_port_mmu();
|
||||
if (! port) {
|
||||
queue_error(_(L("The device could not have been found")));
|
||||
return;
|
||||
}
|
||||
|
||||
BOOST_LOG_TRIVIAL(info) << boost::format("Found VID/PID 0x2c99/3 at `%1%`, flashing ...") % port->port;
|
||||
queue_status(label_status_flashing);
|
||||
|
||||
std::vector<std::string> args {{
|
||||
extra_verbose ? "-vvvvv" : "-v",
|
||||
"-p", "atmega32u4",
|
||||
"-c", "avr109",
|
||||
"-P", port->port,
|
||||
"-b", "57600",
|
||||
"-D",
|
||||
"-U", (boost::format("flash:w:0:%1%:i") % hex_file.path.string()).str(),
|
||||
}};
|
||||
|
||||
BOOST_LOG_TRIVIAL(info) << "Invoking avrdude, arguments: "
|
||||
<< std::accumulate(std::next(args.begin()), args.end(), args[0], [](std::string a, const std::string &b) {
|
||||
return a + ' ' + b;
|
||||
});
|
||||
|
||||
avrdude->push_args(std::move(args));
|
||||
}
|
||||
|
||||
|
||||
void FirmwareDialog::priv::perform_upload()
|
||||
{
|
||||
auto filename = hex_picker->GetPath();
|
||||
if (filename.IsEmpty()) { return; }
|
||||
|
||||
load_hex_file(filename); // Might already be loaded, but we want to make sure it's fresh
|
||||
|
||||
int selection = port_picker->GetSelection();
|
||||
if (selection != wxNOT_FOUND) {
|
||||
port = this->ports[selection];
|
||||
|
||||
// Verify whether the combo box list selection equals to the combo box edit value.
|
||||
if (wxString::FromUTF8(port->friendly_name.data()) != port_picker->GetValue()) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
const bool extra_verbose = false; // For debugging
|
||||
|
||||
flashing_start(hex_file.device == HexFile::DEV_MK3 ? 2 : 1);
|
||||
|
||||
// Init the avrdude object
|
||||
AvrDude avrdude(avrdude_config);
|
||||
|
||||
// It is ok here to use the q-pointer to the FirmwareDialog
|
||||
// because the dialog ensures it doesn't exit before the background thread is done.
|
||||
auto q = this->q;
|
||||
|
||||
this->avrdude = avrdude
|
||||
.on_run([this]() {
|
||||
try {
|
||||
switch (this->hex_file.device) {
|
||||
case HexFile::DEV_MK3:
|
||||
this->prepare_mk3();
|
||||
break;
|
||||
|
||||
case HexFile::DEV_MM_CONTROL:
|
||||
this->prepare_mm_control();
|
||||
break;
|
||||
|
||||
default:
|
||||
this->prepare_mk2();
|
||||
break;
|
||||
}
|
||||
} catch (const std::exception &ex) {
|
||||
queue_error(wxString::Format(_(L("Error accessing port at %s: %s")), port->port, ex.what()));
|
||||
}
|
||||
})
|
||||
.on_message(std::move([q, extra_verbose](const char *msg, unsigned /* size */) {
|
||||
if (extra_verbose) {
|
||||
BOOST_LOG_TRIVIAL(debug) << "avrdude: " << msg;
|
||||
|
@ -278,20 +588,19 @@ void FirmwareDialog::priv::perform_upload()
|
|||
evt->SetInt(progress);
|
||||
wxQueueEvent(q, evt);
|
||||
}))
|
||||
.on_complete(std::move([q](int status, size_t /* args_id */) {
|
||||
auto evt = new wxCommandEvent(EVT_AVRDUDE, q->GetId());
|
||||
.on_complete(std::move([this]() {
|
||||
auto evt = new wxCommandEvent(EVT_AVRDUDE, this->q->GetId());
|
||||
evt->SetExtraLong(AE_EXIT);
|
||||
evt->SetInt(status);
|
||||
wxQueueEvent(q, evt);
|
||||
evt->SetInt(this->avrdude->exit_code());
|
||||
wxQueueEvent(this->q, evt);
|
||||
}))
|
||||
.run();
|
||||
}
|
||||
|
||||
void FirmwareDialog::priv::cancel()
|
||||
void FirmwareDialog::priv::user_cancel()
|
||||
{
|
||||
if (avrdude) {
|
||||
cancelled = true;
|
||||
txt_status->SetLabel(_(L("Cancelling...")));
|
||||
user_cancelled = true;
|
||||
avrdude->cancel();
|
||||
}
|
||||
}
|
||||
|
@ -313,12 +622,15 @@ void FirmwareDialog::priv::on_avrdude(const wxCommandEvent &evt)
|
|||
// and then display overall progress during the latter tasks.
|
||||
|
||||
if (progress_tasks_done > 0) {
|
||||
progressbar->SetValue(progress_tasks_done - 100 + evt.GetInt());
|
||||
progressbar->SetValue(progress_tasks_bar + evt.GetInt());
|
||||
}
|
||||
|
||||
if (evt.GetInt() == 100) {
|
||||
timer_pulse.Stop();
|
||||
progress_tasks_done += 100;
|
||||
if (progress_tasks_done % 3 != 0) {
|
||||
progress_tasks_bar += 100;
|
||||
}
|
||||
progress_tasks_done++;
|
||||
}
|
||||
|
||||
break;
|
||||
|
@ -326,13 +638,17 @@ void FirmwareDialog::priv::on_avrdude(const wxCommandEvent &evt)
|
|||
case AE_EXIT:
|
||||
BOOST_LOG_TRIVIAL(info) << "avrdude exit code: " << evt.GetInt();
|
||||
|
||||
complete_kind = cancelled ? AC_CANCEL : (evt.GetInt() == 0 ? AC_SUCCESS : AC_FAILURE);
|
||||
// Figure out the exit state
|
||||
if (user_cancelled) { complete_kind = AC_USER_CANCELLED; }
|
||||
else if (avrdude->cancelled()) { complete_kind = AC_NONE; } // Ie. cancelled programatically
|
||||
else { complete_kind = evt.GetInt() == 0 ? AC_SUCCESS : AC_FAILURE; }
|
||||
|
||||
flashing_done(complete_kind);
|
||||
ensure_joined();
|
||||
break;
|
||||
|
||||
// Make sure the background thread is collected and the AvrDude object reset
|
||||
if (avrdude) { avrdude->join(); }
|
||||
avrdude.reset();
|
||||
|
||||
case AE_STATUS:
|
||||
set_txt_status(evt.GetString());
|
||||
break;
|
||||
|
||||
default:
|
||||
|
@ -340,6 +656,23 @@ void FirmwareDialog::priv::on_avrdude(const wxCommandEvent &evt)
|
|||
}
|
||||
}
|
||||
|
||||
void FirmwareDialog::priv::on_async_dialog(const wxCommandEvent &evt)
|
||||
{
|
||||
wxMessageDialog dlg(this->q, evt.GetString(), wxMessageBoxCaptionStr, wxYES_NO | wxNO_DEFAULT | wxICON_QUESTION);
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(mutex);
|
||||
modal_response = dlg.ShowModal();
|
||||
}
|
||||
response_cv.notify_all();
|
||||
}
|
||||
|
||||
void FirmwareDialog::priv::ensure_joined()
|
||||
{
|
||||
// Make sure the background thread is collected and the AvrDude object reset
|
||||
if (avrdude) { avrdude->join(); }
|
||||
avrdude.reset();
|
||||
}
|
||||
|
||||
|
||||
// Public
|
||||
|
||||
|
@ -360,44 +693,50 @@ FirmwareDialog::FirmwareDialog(wxWindow *parent) :
|
|||
wxFont mono_font(wxFontInfo().Family(wxFONTFAMILY_TELETYPE));
|
||||
mono_font.MakeSmaller();
|
||||
|
||||
// Create GUI components and layout
|
||||
|
||||
auto *panel = new wxPanel(this);
|
||||
wxBoxSizer *vsizer = new wxBoxSizer(wxVERTICAL);
|
||||
panel->SetSizer(vsizer);
|
||||
|
||||
auto *label_hex_picker = new wxStaticText(panel, wxID_ANY, _(L("Firmware image:")));
|
||||
p->hex_picker = new wxFilePickerCtrl(panel, wxID_ANY);
|
||||
|
||||
auto *label_port_picker = new wxStaticText(panel, wxID_ANY, _(L("Serial port:")));
|
||||
p->port_picker = new wxComboBox(panel, wxID_ANY);
|
||||
p->port_autodetect = new wxStaticText(panel, wxID_ANY, _(L("Autodetected")));
|
||||
p->btn_rescan = new wxButton(panel, wxID_ANY, _(L("Rescan")));
|
||||
auto *port_sizer = new wxBoxSizer(wxHORIZONTAL);
|
||||
port_sizer->Add(p->port_picker, 1, wxEXPAND | wxRIGHT, SPACING);
|
||||
port_sizer->Add(p->btn_rescan, 0);
|
||||
port_sizer->Add(p->port_autodetect, 1, wxEXPAND);
|
||||
p->enable_port_picker(true);
|
||||
|
||||
auto *label_hex_picker = new wxStaticText(panel, wxID_ANY, _(L("Firmware image:")));
|
||||
p->hex_picker = new wxFilePickerCtrl(panel, wxID_ANY);
|
||||
auto *label_progress = new wxStaticText(panel, wxID_ANY, _(L("Progress:")));
|
||||
p->progressbar = new wxGauge(panel, wxID_ANY, 1, wxDefaultPosition, wxDefaultSize, wxGA_HORIZONTAL | wxGA_SMOOTH);
|
||||
|
||||
auto *label_status = new wxStaticText(panel, wxID_ANY, _(L("Status:")));
|
||||
p->txt_status = new wxStaticText(panel, wxID_ANY, _(L("Ready")));
|
||||
p->txt_status->SetFont(status_font);
|
||||
|
||||
auto *label_progress = new wxStaticText(panel, wxID_ANY, _(L("Progress:")));
|
||||
p->progressbar = new wxGauge(panel, wxID_ANY, 1, wxDefaultPosition, wxDefaultSize, wxGA_HORIZONTAL | wxGA_SMOOTH);
|
||||
|
||||
auto *grid = new wxFlexGridSizer(2, SPACING, SPACING);
|
||||
grid->AddGrowableCol(1);
|
||||
grid->Add(label_port_picker, 0, wxALIGN_CENTER_VERTICAL);
|
||||
grid->Add(port_sizer, 0, wxEXPAND);
|
||||
|
||||
grid->Add(label_hex_picker, 0, wxALIGN_CENTER_VERTICAL);
|
||||
grid->Add(p->hex_picker, 0, wxEXPAND);
|
||||
|
||||
grid->Add(label_status, 0, wxALIGN_CENTER_VERTICAL);
|
||||
grid->Add(p->txt_status, 0, wxEXPAND);
|
||||
grid->Add(label_port_picker, 0, wxALIGN_CENTER_VERTICAL);
|
||||
grid->Add(port_sizer, 0, wxEXPAND);
|
||||
|
||||
grid->Add(label_progress, 0, wxALIGN_CENTER_VERTICAL);
|
||||
grid->Add(p->progressbar, 1, wxEXPAND | wxALIGN_CENTER_VERTICAL);
|
||||
|
||||
grid->Add(label_status, 0, wxALIGN_CENTER_VERTICAL);
|
||||
grid->Add(p->txt_status, 0, wxEXPAND);
|
||||
|
||||
vsizer->Add(grid, 0, wxEXPAND | wxTOP | wxBOTTOM, SPACING);
|
||||
|
||||
p->spoiler = new wxCollapsiblePane(panel, wxID_ANY, _(L("Advanced: avrdude output log")));
|
||||
p->spoiler = new wxCollapsiblePane(panel, wxID_ANY, _(L("Advanced: avrdude output log")), wxDefaultPosition, wxDefaultSize, wxCP_DEFAULT_STYLE | wxCP_NO_TLW_RESIZE);
|
||||
auto *spoiler_pane = p->spoiler->GetPane();
|
||||
auto *spoiler_sizer = new wxBoxSizer(wxVERTICAL);
|
||||
p->txt_stdout = new wxTextCtrl(spoiler_pane, wxID_ANY, wxEmptyString, wxDefaultPosition, wxDefaultSize, wxTE_MULTILINE | wxTE_READONLY);
|
||||
|
@ -410,6 +749,7 @@ FirmwareDialog::FirmwareDialog(wxWindow *parent) :
|
|||
|
||||
p->btn_close = new wxButton(panel, wxID_CLOSE);
|
||||
p->btn_flash = new wxButton(panel, wxID_ANY, p->btn_flash_label_ready);
|
||||
p->btn_flash->Disable();
|
||||
auto *bsizer = new wxBoxSizer(wxHORIZONTAL);
|
||||
bsizer->Add(p->btn_close);
|
||||
bsizer->AddStretchSpacer();
|
||||
|
@ -424,16 +764,26 @@ FirmwareDialog::FirmwareDialog(wxWindow *parent) :
|
|||
SetSize(std::max(size.GetWidth(), static_cast<int>(MIN_WIDTH)), std::max(size.GetHeight(), static_cast<int>(MIN_HEIGHT)));
|
||||
Layout();
|
||||
|
||||
// Bind events
|
||||
|
||||
p->hex_picker->Bind(wxEVT_FILEPICKER_CHANGED, [this](wxFileDirPickerEvent& evt) {
|
||||
if (wxFileExists(evt.GetPath())) {
|
||||
this->p->load_hex_file(evt.GetPath());
|
||||
this->p->btn_flash->Enable();
|
||||
}
|
||||
});
|
||||
|
||||
p->spoiler->Bind(wxEVT_COLLAPSIBLEPANE_CHANGED, [this](wxCollapsiblePaneEvent &evt) {
|
||||
// Dialog size gets screwed up by wxCollapsiblePane, we need to fix it here
|
||||
if (evt.GetCollapsed()) {
|
||||
this->SetMinSize(wxSize(MIN_WIDTH, MIN_HEIGHT));
|
||||
const auto new_height = this->GetSize().GetHeight() - this->p->txt_stdout->GetSize().GetHeight();
|
||||
this->SetSize(this->GetSize().GetWidth(), new_height);
|
||||
} else {
|
||||
this->SetMinSize(wxSize(MIN_WIDTH, MIN_HEIGHT_EXPANDED));
|
||||
}
|
||||
|
||||
this->Fit();
|
||||
this->Layout();
|
||||
this->p->fit_no_shrink();
|
||||
});
|
||||
|
||||
p->btn_close->Bind(wxEVT_BUTTON, [this](wxCommandEvent &) { this->Close(); });
|
||||
|
@ -447,7 +797,8 @@ FirmwareDialog::FirmwareDialog(wxWindow *parent) :
|
|||
_(L("Confirmation")),
|
||||
wxYES_NO | wxNO_DEFAULT | wxICON_QUESTION);
|
||||
if (dlg.ShowModal() == wxID_YES) {
|
||||
this->p->cancel();
|
||||
this->p->set_txt_status(_(L("Cancelling...")));
|
||||
this->p->user_cancel();
|
||||
}
|
||||
} else {
|
||||
// Start a flashing task
|
||||
|
@ -458,6 +809,7 @@ FirmwareDialog::FirmwareDialog(wxWindow *parent) :
|
|||
Bind(wxEVT_TIMER, [this](wxTimerEvent &evt) { this->p->progressbar->Pulse(); });
|
||||
|
||||
Bind(EVT_AVRDUDE, [this](wxCommandEvent &evt) { this->p->on_avrdude(evt); });
|
||||
Bind(EVT_ASYNC_DIALOG, [this](wxCommandEvent &evt) { this->p->on_async_dialog(evt); });
|
||||
|
||||
Bind(wxEVT_CLOSE_WINDOW, [this](wxCloseEvent &evt) {
|
||||
if (this->p->avrdude) {
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -162,7 +162,8 @@ public:
|
|||
bool is_custom() const;
|
||||
|
||||
const Pointfs& get_shape() const;
|
||||
void set_shape(const Pointfs& shape);
|
||||
// Return true if the bed shape changed, so the calee will update the UI.
|
||||
bool set_shape(const Pointfs& shape);
|
||||
|
||||
const BoundingBoxf3& get_bounding_box() const;
|
||||
bool contains(const Point& point) const;
|
||||
|
@ -449,6 +450,7 @@ private:
|
|||
bool m_picking_enabled;
|
||||
bool m_moving_enabled;
|
||||
bool m_shader_enabled;
|
||||
bool m_dynamic_background_enabled;
|
||||
bool m_multisample_allowed;
|
||||
|
||||
std::string m_color_by;
|
||||
|
@ -495,7 +497,7 @@ public:
|
|||
void deselect_volumes();
|
||||
void select_volume(unsigned int id);
|
||||
void update_volumes_selection(const std::vector<int>& selections);
|
||||
bool check_volumes_outside_state(const DynamicPrintConfig* config) const;
|
||||
int check_volumes_outside_state(const DynamicPrintConfig* config) const;
|
||||
bool move_volume_up(unsigned int id);
|
||||
bool move_volume_down(unsigned int id);
|
||||
|
||||
|
@ -539,6 +541,7 @@ public:
|
|||
void enable_gizmos(bool enable);
|
||||
void enable_shader(bool enable);
|
||||
void enable_force_zoom_to_bed(bool enable);
|
||||
void enable_dynamic_background(bool enable);
|
||||
void allow_multisample(bool allow);
|
||||
|
||||
void zoom_to_bed();
|
||||
|
@ -559,16 +562,8 @@ public:
|
|||
|
||||
void reload_scene(bool force);
|
||||
|
||||
// Create 3D thick extrusion lines for a skirt and brim.
|
||||
// Adds a new Slic3r::GUI::3DScene::Volume to volumes.
|
||||
void load_print_toolpaths();
|
||||
// Create 3D thick extrusion lines for object forming extrusions.
|
||||
// Adds a new Slic3r::GUI::3DScene::Volume to $self->volumes,
|
||||
// one for perimeters, one for infill and one for supports.
|
||||
void load_print_object_toolpaths(const PrintObject& print_object, const std::vector<std::string>& str_tool_colors);
|
||||
// Create 3D thick extrusion lines for wipe tower extrusions
|
||||
void load_wipe_tower_toolpaths(const std::vector<std::string>& str_tool_colors);
|
||||
void load_gcode_preview(const GCodePreviewData& preview_data, const std::vector<std::string>& str_tool_colors);
|
||||
void load_preview(const std::vector<std::string>& str_tool_colors);
|
||||
|
||||
void register_on_viewport_changed_callback(void* callback);
|
||||
void register_on_double_click_callback(void* callback);
|
||||
|
@ -650,7 +645,17 @@ private:
|
|||
void _stop_timer();
|
||||
|
||||
int _get_first_selected_object_id() const;
|
||||
int _get_first_selected_volume_id() const;
|
||||
int _get_first_selected_volume_id(int object_id) const;
|
||||
|
||||
// Create 3D thick extrusion lines for a skirt and brim.
|
||||
// Adds a new Slic3r::GUI::3DScene::Volume to volumes.
|
||||
void _load_print_toolpaths();
|
||||
// Create 3D thick extrusion lines for object forming extrusions.
|
||||
// Adds a new Slic3r::GUI::3DScene::Volume to $self->volumes,
|
||||
// one for perimeters, one for infill and one for supports.
|
||||
void _load_print_object_toolpaths(const PrintObject& print_object, const std::vector<std::string>& str_tool_colors);
|
||||
// Create 3D thick extrusion lines for wipe tower extrusions
|
||||
void _load_wipe_tower_toolpaths(const std::vector<std::string>& str_tool_colors);
|
||||
|
||||
// generates gcode extrusion paths geometry
|
||||
void _load_gcode_extrusion_paths(const GCodePreviewData& preview_data, const std::vector<float>& tool_colors);
|
||||
|
@ -667,6 +672,8 @@ private:
|
|||
void _load_shells();
|
||||
// sets gcode geometry visibility according to user selection
|
||||
void _update_gcode_volumes_visibility(const GCodePreviewData& preview_data);
|
||||
void _update_toolpath_volumes_outside_state();
|
||||
void _show_warning_texture_if_needed();
|
||||
|
||||
void _on_move(const std::vector<int>& volume_idxs);
|
||||
void _on_select(int volume_idx);
|
||||
|
@ -678,6 +685,8 @@ private:
|
|||
void _generate_warning_texture(const std::string& msg);
|
||||
void _reset_warning_texture();
|
||||
|
||||
bool _is_any_volume_outside() const;
|
||||
|
||||
static std::vector<float> _parse_colors(const std::vector<std::string>& colors);
|
||||
};
|
||||
|
||||
|
|
|
@ -237,7 +237,7 @@ void GLCanvas3DManager::update_volumes_selection(wxGLCanvas* canvas, const std::
|
|||
it->second->update_volumes_selection(selections);
|
||||
}
|
||||
|
||||
bool GLCanvas3DManager::check_volumes_outside_state(wxGLCanvas* canvas, const DynamicPrintConfig* config) const
|
||||
int GLCanvas3DManager::check_volumes_outside_state(wxGLCanvas* canvas, const DynamicPrintConfig* config) const
|
||||
{
|
||||
CanvasesMap::const_iterator it = _get_canvas(canvas);
|
||||
return (it != m_canvases.end()) ? it->second->check_volumes_outside_state(config) : false;
|
||||
|
@ -418,6 +418,13 @@ void GLCanvas3DManager::enable_force_zoom_to_bed(wxGLCanvas* canvas, bool enable
|
|||
it->second->enable_force_zoom_to_bed(enable);
|
||||
}
|
||||
|
||||
void GLCanvas3DManager::enable_dynamic_background(wxGLCanvas* canvas, bool enable)
|
||||
{
|
||||
CanvasesMap::iterator it = _get_canvas(canvas);
|
||||
if (it != m_canvases.end())
|
||||
it->second->enable_dynamic_background(enable);
|
||||
}
|
||||
|
||||
void GLCanvas3DManager::allow_multisample(wxGLCanvas* canvas, bool allow)
|
||||
{
|
||||
CanvasesMap::iterator it = _get_canvas(canvas);
|
||||
|
@ -516,30 +523,6 @@ void GLCanvas3DManager::reload_scene(wxGLCanvas* canvas, bool force)
|
|||
it->second->reload_scene(force);
|
||||
}
|
||||
|
||||
void GLCanvas3DManager::load_print_toolpaths(wxGLCanvas* canvas)
|
||||
{
|
||||
CanvasesMap::iterator it = _get_canvas(canvas);
|
||||
if (it != m_canvases.end())
|
||||
it->second->load_print_toolpaths();
|
||||
}
|
||||
|
||||
void GLCanvas3DManager::load_print_object_toolpaths(wxGLCanvas* canvas, const PrintObject* print_object, const std::vector<std::string>& tool_colors)
|
||||
{
|
||||
if (print_object == nullptr)
|
||||
return;
|
||||
|
||||
CanvasesMap::iterator it = _get_canvas(canvas);
|
||||
if (it != m_canvases.end())
|
||||
it->second->load_print_object_toolpaths(*print_object, tool_colors);
|
||||
}
|
||||
|
||||
void GLCanvas3DManager::load_wipe_tower_toolpaths(wxGLCanvas* canvas, const std::vector<std::string>& str_tool_colors)
|
||||
{
|
||||
CanvasesMap::iterator it = _get_canvas(canvas);
|
||||
if (it != m_canvases.end())
|
||||
it->second->load_wipe_tower_toolpaths(str_tool_colors);
|
||||
}
|
||||
|
||||
void GLCanvas3DManager::load_gcode_preview(wxGLCanvas* canvas, const GCodePreviewData* preview_data, const std::vector<std::string>& str_tool_colors)
|
||||
{
|
||||
if (preview_data == nullptr)
|
||||
|
@ -550,6 +533,13 @@ void GLCanvas3DManager::load_gcode_preview(wxGLCanvas* canvas, const GCodePrevie
|
|||
it->second->load_gcode_preview(*preview_data, str_tool_colors);
|
||||
}
|
||||
|
||||
void GLCanvas3DManager::load_preview(wxGLCanvas* canvas, const std::vector<std::string>& str_tool_colors)
|
||||
{
|
||||
CanvasesMap::iterator it = _get_canvas(canvas);
|
||||
if (it != m_canvases.end())
|
||||
it->second->load_preview(str_tool_colors);
|
||||
}
|
||||
|
||||
void GLCanvas3DManager::reset_legend_texture()
|
||||
{
|
||||
for (CanvasesMap::value_type& canvas : m_canvases)
|
||||
|
|
|
@ -75,7 +75,7 @@ public:
|
|||
void deselect_volumes(wxGLCanvas* canvas);
|
||||
void select_volume(wxGLCanvas* canvas, unsigned int id);
|
||||
void update_volumes_selection(wxGLCanvas* canvas, const std::vector<int>& selections);
|
||||
bool check_volumes_outside_state(wxGLCanvas* canvas, const DynamicPrintConfig* config) const;
|
||||
int check_volumes_outside_state(wxGLCanvas* canvas, const DynamicPrintConfig* config) const;
|
||||
bool move_volume_up(wxGLCanvas* canvas, unsigned int id);
|
||||
bool move_volume_down(wxGLCanvas* canvas, unsigned int id);
|
||||
|
||||
|
@ -112,6 +112,7 @@ public:
|
|||
void enable_gizmos(wxGLCanvas* canvas, bool enable);
|
||||
void enable_shader(wxGLCanvas* canvas, bool enable);
|
||||
void enable_force_zoom_to_bed(wxGLCanvas* canvas, bool enable);
|
||||
void enable_dynamic_background(wxGLCanvas* canvas, bool enable);
|
||||
void allow_multisample(wxGLCanvas* canvas, bool allow);
|
||||
|
||||
void zoom_to_bed(wxGLCanvas* canvas);
|
||||
|
@ -132,10 +133,8 @@ public:
|
|||
|
||||
void reload_scene(wxGLCanvas* canvas, bool force);
|
||||
|
||||
void load_print_toolpaths(wxGLCanvas* canvas);
|
||||
void load_print_object_toolpaths(wxGLCanvas* canvas, const PrintObject* print_object, const std::vector<std::string>& tool_colors);
|
||||
void load_wipe_tower_toolpaths(wxGLCanvas* canvas, const std::vector<std::string>& str_tool_colors);
|
||||
void load_gcode_preview(wxGLCanvas* canvas, const GCodePreviewData* preview_data, const std::vector<std::string>& str_tool_colors);
|
||||
void load_preview(wxGLCanvas* canvas, const std::vector<std::string>& str_tool_colors);
|
||||
|
||||
void reset_legend_texture();
|
||||
|
||||
|
|
|
@ -79,7 +79,8 @@ bool GLTexture::load_from_file(const std::string& filename, bool generate_mipmap
|
|||
if (generate_mipmaps)
|
||||
{
|
||||
// we manually generate mipmaps because glGenerateMipmap() function is not reliable on all graphics cards
|
||||
_generate_mipmaps(image);
|
||||
unsigned int levels_count = _generate_mipmaps(image);
|
||||
::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, 1 + levels_count);
|
||||
::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
|
||||
}
|
||||
else
|
||||
|
@ -149,14 +150,14 @@ void GLTexture::render_texture(unsigned int tex_id, float left, float right, flo
|
|||
::glDisable(GL_BLEND);
|
||||
}
|
||||
|
||||
void GLTexture::_generate_mipmaps(wxImage& image)
|
||||
unsigned int GLTexture::_generate_mipmaps(wxImage& image)
|
||||
{
|
||||
int w = image.GetWidth();
|
||||
int h = image.GetHeight();
|
||||
GLint level = 0;
|
||||
std::vector<unsigned char> data(w * h * 4, 0);
|
||||
|
||||
while ((w > 1) && (h > 1))
|
||||
while ((w > 1) || (h > 1))
|
||||
{
|
||||
++level;
|
||||
|
||||
|
@ -183,6 +184,8 @@ void GLTexture::_generate_mipmaps(wxImage& image)
|
|||
|
||||
::glTexImage2D(GL_TEXTURE_2D, level, GL_RGBA, (GLsizei)w, (GLsizei)h, 0, GL_RGBA, GL_UNSIGNED_BYTE, (const void*)data.data());
|
||||
}
|
||||
|
||||
return (unsigned int)level;
|
||||
}
|
||||
|
||||
} // namespace GUI
|
||||
|
|
|
@ -32,7 +32,7 @@ namespace GUI {
|
|||
static void render_texture(unsigned int tex_id, float left, float right, float bottom, float top);
|
||||
|
||||
protected:
|
||||
void _generate_mipmaps(wxImage& image);
|
||||
unsigned int _generate_mipmaps(wxImage& image);
|
||||
};
|
||||
|
||||
} // namespace GUI
|
||||
|
|
|
@ -250,6 +250,7 @@ bool select_language(wxArrayString & names,
|
|||
g_wxLocale->AddCatalogLookupPathPrefix(wxPathOnly(localization_dir()));
|
||||
g_wxLocale->AddCatalog(g_wxApp->GetAppName());
|
||||
wxSetlocale(LC_NUMERIC, "C");
|
||||
Preset::update_suffix_modified();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
|
@ -275,6 +276,7 @@ bool load_language()
|
|||
g_wxLocale->AddCatalogLookupPathPrefix(wxPathOnly(localization_dir()));
|
||||
g_wxLocale->AddCatalog(g_wxApp->GetAppName());
|
||||
wxSetlocale(LC_NUMERIC, "C");
|
||||
Preset::update_suffix_modified();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
@ -901,6 +903,7 @@ void add_frequently_changed_parameters(wxWindow* parent, wxBoxSizer* sizer, wxFl
|
|||
std::vector<float> extruders = dlg.get_extruders();
|
||||
(config.option<ConfigOptionFloats>("wiping_volumes_matrix"))->values = std::vector<double>(matrix.begin(),matrix.end());
|
||||
(config.option<ConfigOptionFloats>("wiping_volumes_extruders"))->values = std::vector<double>(extruders.begin(),extruders.end());
|
||||
g_on_request_update_callback.call();
|
||||
}
|
||||
}));
|
||||
return sizer;
|
||||
|
@ -917,7 +920,6 @@ ConfigOptionsGroup* get_optgroup()
|
|||
return m_optgroup.get();
|
||||
}
|
||||
|
||||
|
||||
wxButton* get_wiping_dialog_button()
|
||||
{
|
||||
return g_wiping_dialog_button;
|
||||
|
|
|
@ -4,6 +4,7 @@
|
|||
#include <string>
|
||||
#include <vector>
|
||||
#include "Config.hpp"
|
||||
#include "../../libslic3r/Utils.hpp"
|
||||
|
||||
#include <wx/intl.h>
|
||||
#include <wx/string.h>
|
||||
|
@ -171,6 +172,9 @@ wxString from_u8(const std::string &str);
|
|||
|
||||
void add_frequently_changed_parameters(wxWindow* parent, wxBoxSizer* sizer, wxFlexGridSizer* preset_sizer);
|
||||
|
||||
// Callback to trigger a configuration update timer on the Plater.
|
||||
static PerlCallback g_on_request_update_callback;
|
||||
|
||||
ConfigOptionsGroup* get_optgroup();
|
||||
wxButton* get_wiping_dialog_button();
|
||||
|
||||
|
|
|
@ -146,6 +146,11 @@ const std::string& Preset::suffix_modified()
|
|||
{
|
||||
return g_suffix_modified;
|
||||
}
|
||||
|
||||
void Preset::update_suffix_modified()
|
||||
{
|
||||
g_suffix_modified = (" (" + _(L("modified")) + ")").ToUTF8().data();
|
||||
}
|
||||
// Remove an optional "(modified)" suffix from a name.
|
||||
// This converts a UI name to a unique preset identifier.
|
||||
std::string Preset::remove_suffix_modified(const std::string &name)
|
||||
|
@ -298,8 +303,8 @@ const std::vector<std::string>& Preset::print_options()
|
|||
"perimeter_extrusion_width", "external_perimeter_extrusion_width", "infill_extrusion_width", "solid_infill_extrusion_width",
|
||||
"top_infill_extrusion_width", "support_material_extrusion_width", "infill_overlap", "bridge_flow_ratio", "clip_multipart_objects",
|
||||
"elefant_foot_compensation", "xy_size_compensation", "threads", "resolution", "wipe_tower", "wipe_tower_x", "wipe_tower_y",
|
||||
"wipe_tower_width", "wipe_tower_rotation_angle", "wipe_tower_bridging", "compatible_printers",
|
||||
"compatible_printers_condition","inherits"
|
||||
"wipe_tower_width", "wipe_tower_rotation_angle", "wipe_tower_bridging", "single_extruder_multi_material_priming",
|
||||
"compatible_printers", "compatible_printers_condition","inherits"
|
||||
};
|
||||
return s_opts;
|
||||
}
|
||||
|
@ -308,11 +313,13 @@ const std::vector<std::string>& Preset::filament_options()
|
|||
{
|
||||
static std::vector<std::string> s_opts {
|
||||
"filament_colour", "filament_diameter", "filament_type", "filament_soluble", "filament_notes", "filament_max_volumetric_speed",
|
||||
"extrusion_multiplier", "filament_density", "filament_cost", "filament_loading_speed", "filament_unloading_speed", "filament_toolchange_delay",
|
||||
"filament_cooling_moves", "filament_cooling_initial_speed", "filament_cooling_final_speed", "filament_ramming_parameters", "temperature",
|
||||
"first_layer_temperature", "bed_temperature", "first_layer_bed_temperature", "fan_always_on", "cooling", "min_fan_speed", "max_fan_speed",
|
||||
"bridge_fan_speed", "disable_fan_first_layers", "fan_below_layer_time", "slowdown_below_layer_time", "min_print_speed",
|
||||
"start_filament_gcode", "end_filament_gcode","compatible_printers", "compatible_printers_condition", "inherits"
|
||||
"extrusion_multiplier", "filament_density", "filament_cost",
|
||||
"filament_loading_speed", "filament_load_time", "filament_unloading_speed", "filament_unload_time", "filament_toolchange_delay",
|
||||
"filament_cooling_moves", "filament_cooling_initial_speed", "filament_cooling_final_speed", "filament_ramming_parameters",
|
||||
"filament_minimal_purge_on_wipe_tower", "temperature", "first_layer_temperature", "bed_temperature", "first_layer_bed_temperature",
|
||||
"fan_always_on", "cooling", "min_fan_speed", "max_fan_speed", "bridge_fan_speed", "disable_fan_first_layers", "fan_below_layer_time",
|
||||
"slowdown_below_layer_time", "min_print_speed", "start_filament_gcode", "end_filament_gcode","compatible_printers", "compatible_printers_condition",
|
||||
"inherits"
|
||||
};
|
||||
return s_opts;
|
||||
}
|
||||
|
@ -327,7 +334,7 @@ const std::vector<std::string>& Preset::printer_options()
|
|||
"single_extruder_multi_material", "start_gcode", "end_gcode", "before_layer_gcode", "layer_gcode", "toolchange_gcode",
|
||||
"between_objects_gcode", "printer_vendor", "printer_model", "printer_variant", "printer_notes", "cooling_tube_retraction",
|
||||
"cooling_tube_length", "parking_pos_retraction", "extra_loading_move", "max_print_height", "default_print_profile", "inherits",
|
||||
"silent_mode", "machine_max_acceleration_extruding", "machine_max_acceleration_retracting",
|
||||
"remaining_times", "silent_mode", "machine_max_acceleration_extruding", "machine_max_acceleration_retracting",
|
||||
"machine_max_acceleration_x", "machine_max_acceleration_y", "machine_max_acceleration_z", "machine_max_acceleration_e",
|
||||
"machine_max_feedrate_x", "machine_max_feedrate_y", "machine_max_feedrate_z", "machine_max_feedrate_e",
|
||||
"machine_min_extruding_rate", "machine_min_travel_rate",
|
||||
|
|
|
@ -167,6 +167,7 @@ public:
|
|||
static const std::vector<std::string>& printer_options();
|
||||
// Nozzle options of the printer options.
|
||||
static const std::vector<std::string>& nozzle_options();
|
||||
static void update_suffix_modified();
|
||||
|
||||
protected:
|
||||
friend class PresetCollection;
|
||||
|
@ -260,7 +261,7 @@ public:
|
|||
// used to update preset_choice from Tab
|
||||
const std::deque<Preset>& get_presets() { return m_presets; }
|
||||
int get_idx_selected() { return m_idx_selected; }
|
||||
const std::string& get_suffix_modified();
|
||||
static const std::string& get_suffix_modified();
|
||||
|
||||
// Return a preset possibly with modifications.
|
||||
Preset& default_preset() { return m_presets.front(); }
|
||||
|
|
|
@ -552,6 +552,8 @@ void PresetBundle::load_config_file_config(const std::string &name_or_path, bool
|
|||
std::string &inherits = Preset::inherits(config);
|
||||
compatible_printers_condition_values.resize(num_extruders + 2, std::string());
|
||||
inherits_values.resize(num_extruders + 2, std::string());
|
||||
// The "default_filament_profile" will be later extracted into the printer profile.
|
||||
config.option<ConfigOptionStrings>("default_filament_profile", true)->values.resize(num_extruders, std::string());
|
||||
|
||||
// 1) Create a name from the file name.
|
||||
// Keep the suffix (.ini, .gcode, .amf, .3mf etc) to differentiate it from the normal profiles.
|
||||
|
@ -576,7 +578,6 @@ void PresetBundle::load_config_file_config(const std::string &name_or_path, bool
|
|||
// 3) Now load the filaments. If there are multiple filament presets, split them and load them.
|
||||
auto old_filament_profile_names = config.option<ConfigOptionStrings>("filament_settings_id", true);
|
||||
old_filament_profile_names->values.resize(num_extruders, std::string());
|
||||
config.option<ConfigOptionStrings>("default_filament_profile", true)->values.resize(num_extruders, std::string());
|
||||
|
||||
if (num_extruders <= 1) {
|
||||
// Split the "compatible_printers_condition" and "inherits" from the cummulative vectors to separate filament presets.
|
||||
|
|
|
@ -919,6 +919,7 @@ void TabPrint::build()
|
|||
optgroup->append_single_option_line("wipe_tower_width");
|
||||
optgroup->append_single_option_line("wipe_tower_rotation_angle");
|
||||
optgroup->append_single_option_line("wipe_tower_bridging");
|
||||
optgroup->append_single_option_line("single_extruder_multi_material_priming");
|
||||
|
||||
optgroup = page->new_optgroup(_(L("Advanced")));
|
||||
optgroup->append_single_option_line("interface_shells");
|
||||
|
@ -1291,10 +1292,13 @@ void TabFilament::build()
|
|||
optgroup = page->new_optgroup(_(L("Toolchange parameters with single extruder MM printers")));
|
||||
optgroup->append_single_option_line("filament_loading_speed");
|
||||
optgroup->append_single_option_line("filament_unloading_speed");
|
||||
optgroup->append_single_option_line("filament_load_time");
|
||||
optgroup->append_single_option_line("filament_unload_time");
|
||||
optgroup->append_single_option_line("filament_toolchange_delay");
|
||||
optgroup->append_single_option_line("filament_cooling_moves");
|
||||
optgroup->append_single_option_line("filament_cooling_initial_speed");
|
||||
optgroup->append_single_option_line("filament_cooling_final_speed");
|
||||
optgroup->append_single_option_line("filament_minimal_purge_on_wipe_tower");
|
||||
|
||||
line = { _(L("Ramming")), "" };
|
||||
line.widget = [this](wxWindow* parent){
|
||||
|
@ -1606,6 +1610,7 @@ void TabPrinter::build()
|
|||
optgroup = page->new_optgroup(_(L("Firmware")));
|
||||
optgroup->append_single_option_line("gcode_flavor");
|
||||
optgroup->append_single_option_line("silent_mode");
|
||||
optgroup->append_single_option_line("remaining_times");
|
||||
|
||||
optgroup->m_on_change = [this, optgroup](t_config_option_key opt_key, boost::any value){
|
||||
wxTheApp->CallAfter([this, opt_key, value](){
|
||||
|
@ -2728,7 +2733,7 @@ void SavePresetWindow::accept()
|
|||
const char* unusable_symbols = "<>:/\\|?*\"";
|
||||
bool is_unusable_symbol = false;
|
||||
bool is_unusable_postfix = false;
|
||||
const std::string unusable_postfix = "(modified)";
|
||||
const std::string unusable_postfix = PresetCollection::get_suffix_modified();//"(modified)";
|
||||
for (size_t i = 0; i < std::strlen(unusable_symbols); i++){
|
||||
if (m_chosen_name.find_first_of(unusable_symbols[i]) != std::string::npos){
|
||||
is_unusable_symbol = true;
|
||||
|
@ -2743,8 +2748,9 @@ void SavePresetWindow::accept()
|
|||
_(L("the following characters are not allowed:")) + " <>:/\\|?*\"");
|
||||
}
|
||||
else if (is_unusable_postfix){
|
||||
show_error(this, _(L("The supplied name is not valid;")) + "\n" +
|
||||
_(L("the following postfix are not allowed:")) + "\n\t" + unusable_postfix);
|
||||
show_error(this,_(L("The supplied name is not valid;")) + "\n" +
|
||||
_(L("the following postfix are not allowed:")) + "\n\t" + //unusable_postfix);
|
||||
wxString::FromUTF8(unusable_postfix.c_str()));
|
||||
}
|
||||
else if (m_chosen_name.compare("- default -") == 0) {
|
||||
show_error(this, _(L("The supplied name is not available.")));
|
||||
|
|
106
xs/src/slic3r/Utils/HexFile.cpp
Normal file
106
xs/src/slic3r/Utils/HexFile.cpp
Normal file
|
@ -0,0 +1,106 @@
|
|||
#include "HexFile.hpp"
|
||||
|
||||
#include <sstream>
|
||||
#include <boost/filesystem/fstream.hpp>
|
||||
#include <boost/property_tree/ptree.hpp>
|
||||
#include <boost/property_tree/ini_parser.hpp>
|
||||
|
||||
namespace fs = boost::filesystem;
|
||||
namespace pt = boost::property_tree;
|
||||
|
||||
|
||||
namespace Slic3r {
|
||||
namespace Utils {
|
||||
|
||||
|
||||
static HexFile::DeviceKind parse_device_kind(const std::string &str)
|
||||
{
|
||||
if (str == "mk2") { return HexFile::DEV_MK2; }
|
||||
else if (str == "mk3") { return HexFile::DEV_MK3; }
|
||||
else if (str == "mm-control") { return HexFile::DEV_MM_CONTROL; }
|
||||
else { return HexFile::DEV_GENERIC; }
|
||||
}
|
||||
|
||||
static size_t hex_num_sections(fs::ifstream &file)
|
||||
{
|
||||
file.seekg(0);
|
||||
if (! file.good()) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const char *hex_terminator = ":00000001FF\r";
|
||||
size_t res = 0;
|
||||
std::string line;
|
||||
while (getline(file, line, '\n').good()) {
|
||||
// Account for LF vs CRLF
|
||||
if (!line.empty() && line.back() != '\r') {
|
||||
line.push_back('\r');
|
||||
}
|
||||
|
||||
if (line == hex_terminator) {
|
||||
res++;
|
||||
}
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
HexFile::HexFile(fs::path path) :
|
||||
path(std::move(path))
|
||||
{
|
||||
fs::ifstream file(this->path);
|
||||
if (! file.good()) {
|
||||
return;
|
||||
}
|
||||
|
||||
std::string line;
|
||||
std::stringstream header_ini;
|
||||
while (std::getline(file, line, '\n').good()) {
|
||||
if (line.empty()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Account for LF vs CRLF
|
||||
if (!line.empty() && line.back() == '\r') {
|
||||
line.pop_back();
|
||||
}
|
||||
|
||||
if (line.front() == ';') {
|
||||
line.front() = ' ';
|
||||
header_ini << line << std::endl;
|
||||
} else if (line.front() == ':') {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
pt::ptree ptree;
|
||||
try {
|
||||
pt::read_ini(header_ini, ptree);
|
||||
} catch (std::exception &e) {
|
||||
return;
|
||||
}
|
||||
|
||||
bool has_device_meta = false;
|
||||
const auto device = ptree.find("device");
|
||||
if (device != ptree.not_found()) {
|
||||
this->device = parse_device_kind(device->second.data());
|
||||
has_device_meta = true;
|
||||
}
|
||||
|
||||
const auto model_id = ptree.find("model_id");
|
||||
if (model_id != ptree.not_found()) {
|
||||
this->model_id = model_id->second.data();
|
||||
}
|
||||
|
||||
if (! has_device_meta) {
|
||||
// No device metadata, look at the number of 'sections'
|
||||
if (hex_num_sections(file) == 2) {
|
||||
// Looks like a pre-metadata l10n firmware for the MK3, assume that's the case
|
||||
this->device = DEV_MK3;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
33
xs/src/slic3r/Utils/HexFile.hpp
Normal file
33
xs/src/slic3r/Utils/HexFile.hpp
Normal file
|
@ -0,0 +1,33 @@
|
|||
#ifndef slic3r_Hex_hpp_
|
||||
#define slic3r_Hex_hpp_
|
||||
|
||||
#include <string>
|
||||
#include <boost/filesystem/path.hpp>
|
||||
|
||||
|
||||
namespace Slic3r {
|
||||
namespace Utils {
|
||||
|
||||
|
||||
struct HexFile
|
||||
{
|
||||
enum DeviceKind {
|
||||
DEV_GENERIC,
|
||||
DEV_MK2,
|
||||
DEV_MK3,
|
||||
DEV_MM_CONTROL,
|
||||
};
|
||||
|
||||
boost::filesystem::path path;
|
||||
DeviceKind device = DEV_GENERIC;
|
||||
std::string model_id;
|
||||
|
||||
HexFile() {}
|
||||
HexFile(boost::filesystem::path path);
|
||||
};
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
|
@ -322,8 +322,9 @@ Updates PresetUpdater::priv::get_config_updates() const
|
|||
|
||||
const auto ver_current = idx.find(vp.config_version);
|
||||
if (ver_current == idx.end()) {
|
||||
BOOST_LOG_TRIVIAL(error) << boost::format("Preset bundle (`%1%`) version not found in index: %2%") % idx.vendor() % vp.config_version.to_string();
|
||||
continue;
|
||||
auto message = (boost::format("Preset bundle `%1%` version not found in index: %2%") % idx.vendor() % vp.config_version.to_string()).str();
|
||||
BOOST_LOG_TRIVIAL(error) << message;
|
||||
throw std::runtime_error(message);
|
||||
}
|
||||
|
||||
// Getting a recommended version from the latest index, wich may have been downloaded
|
||||
|
|
|
@ -3,17 +3,22 @@
|
|||
#include <algorithm>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <chrono>
|
||||
#include <thread>
|
||||
#include <fstream>
|
||||
#include <stdexcept>
|
||||
|
||||
#include <boost/algorithm/string/predicate.hpp>
|
||||
#include <boost/filesystem.hpp>
|
||||
#include <boost/format.hpp>
|
||||
#include <boost/optional.hpp>
|
||||
|
||||
#if _WIN32
|
||||
#include <Windows.h>
|
||||
#include <Setupapi.h>
|
||||
#include <initguid.h>
|
||||
#include <devguid.h>
|
||||
#include <regex>
|
||||
// Undefine min/max macros incompatible with the standard library
|
||||
// For example, std::numeric_limits<std::streamsize>::max()
|
||||
// produces some weird errors
|
||||
|
@ -34,6 +39,23 @@
|
|||
#include <sys/syslimits.h>
|
||||
#endif
|
||||
|
||||
#ifndef _WIN32
|
||||
#include <sys/ioctl.h>
|
||||
#include <sys/time.h>
|
||||
#include <sys/unistd.h>
|
||||
#include <sys/select.h>
|
||||
#endif
|
||||
|
||||
#if defined(__APPLE__) || defined(__OpenBSD__)
|
||||
#include <termios.h>
|
||||
#elif defined __linux__
|
||||
#include <fcntl.h>
|
||||
#include <asm-generic/ioctls.h>
|
||||
#endif
|
||||
|
||||
using boost::optional;
|
||||
|
||||
|
||||
namespace Slic3r {
|
||||
namespace Utils {
|
||||
|
||||
|
@ -42,15 +64,43 @@ static bool looks_like_printer(const std::string &friendly_name)
|
|||
return friendly_name.find("Original Prusa") != std::string::npos;
|
||||
}
|
||||
|
||||
#ifdef __linux__
|
||||
static std::string get_tty_friendly_name(const std::string &path, const std::string &name)
|
||||
#if _WIN32
|
||||
void parse_hardware_id(const std::string &hardware_id, SerialPortInfo &spi)
|
||||
{
|
||||
const auto sysfs_product = (boost::format("/sys/class/tty/%1%/device/../product") % name).str();
|
||||
std::ifstream file(sysfs_product);
|
||||
std::string product;
|
||||
unsigned vid, pid;
|
||||
std::regex pattern("USB\\\\.*VID_([[:xdigit:]]+)&PID_([[:xdigit:]]+).*");
|
||||
std::smatch matches;
|
||||
if (std::regex_match(hardware_id, matches, pattern)) {
|
||||
try {
|
||||
vid = std::stoul(matches[1].str(), 0, 16);
|
||||
pid = std::stoul(matches[2].str(), 0, 16);
|
||||
spi.id_vendor = vid;
|
||||
spi.id_product = pid;
|
||||
}
|
||||
catch (...) {}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
std::getline(file, product);
|
||||
return file.good() ? (boost::format("%1% (%2%)") % product % path).str() : path;
|
||||
#ifdef __linux__
|
||||
optional<std::string> sysfs_tty_prop(const std::string &tty_dev, const std::string &name)
|
||||
{
|
||||
const auto prop_path = (boost::format("/sys/class/tty/%1%/device/../%2%") % tty_dev % name).str();
|
||||
std::ifstream file(prop_path);
|
||||
std::string res;
|
||||
|
||||
std::getline(file, res);
|
||||
if (file.good()) { return res; }
|
||||
else { return boost::none; }
|
||||
}
|
||||
|
||||
optional<unsigned long> sysfs_tty_prop_hex(const std::string &tty_dev, const std::string &name)
|
||||
{
|
||||
auto prop = sysfs_tty_prop(tty_dev, name);
|
||||
if (!prop) { return boost::none; }
|
||||
|
||||
try { return std::stoul(*prop, 0, 16); }
|
||||
catch (...) { return boost::none; }
|
||||
}
|
||||
#endif
|
||||
|
||||
|
@ -80,6 +130,7 @@ std::vector<SerialPortInfo> scan_serial_ports_extended()
|
|||
if (port_info.port.empty())
|
||||
continue;
|
||||
}
|
||||
|
||||
// Find the size required to hold the device info.
|
||||
DWORD regDataType;
|
||||
DWORD reqSize = 0;
|
||||
|
@ -88,7 +139,8 @@ std::vector<SerialPortInfo> scan_serial_ports_extended()
|
|||
// Now store it in a buffer.
|
||||
if (! SetupDiGetDeviceRegistryProperty(hDeviceInfo, &devInfoData, SPDRP_HARDWAREID, ®DataType, (BYTE*)hardware_id.data(), reqSize, nullptr))
|
||||
continue;
|
||||
port_info.hardware_id = boost::nowide::narrow(hardware_id.data());
|
||||
parse_hardware_id(boost::nowide::narrow(hardware_id.data()), port_info);
|
||||
|
||||
// Find the size required to hold the friendly name.
|
||||
reqSize = 0;
|
||||
SetupDiGetDeviceRegistryProperty(hDeviceInfo, &devInfoData, SPDRP_FRIENDLYNAME, nullptr, nullptr, 0, &reqSize);
|
||||
|
@ -120,6 +172,8 @@ std::vector<SerialPortInfo> scan_serial_ports_extended()
|
|||
if (result) {
|
||||
SerialPortInfo port_info;
|
||||
port_info.port = path;
|
||||
|
||||
// Attempt to read out the device friendly name
|
||||
if ((cf_property = IORegistryEntrySearchCFProperty(port, kIOServicePlane,
|
||||
CFSTR("USB Interface Name"), kCFAllocatorDefault,
|
||||
kIORegistryIterateRecursively | kIORegistryIterateParents)) ||
|
||||
|
@ -141,6 +195,23 @@ std::vector<SerialPortInfo> scan_serial_ports_extended()
|
|||
}
|
||||
if (port_info.friendly_name.empty())
|
||||
port_info.friendly_name = port_info.port;
|
||||
|
||||
// Attempt to read out the VID & PID
|
||||
int vid, pid;
|
||||
auto cf_vendor = IORegistryEntrySearchCFProperty(port, kIOServicePlane, CFSTR("idVendor"),
|
||||
kCFAllocatorDefault, kIORegistryIterateRecursively | kIORegistryIterateParents);
|
||||
auto cf_product = IORegistryEntrySearchCFProperty(port, kIOServicePlane, CFSTR("idProduct"),
|
||||
kCFAllocatorDefault, kIORegistryIterateRecursively | kIORegistryIterateParents);
|
||||
if (cf_vendor && cf_product) {
|
||||
if (CFNumberGetValue((CFNumberRef)cf_vendor, kCFNumberIntType, &vid) &&
|
||||
CFNumberGetValue((CFNumberRef)cf_product, kCFNumberIntType, &pid)) {
|
||||
port_info.id_vendor = vid;
|
||||
port_info.id_product = pid;
|
||||
}
|
||||
}
|
||||
if (cf_vendor) { CFRelease(cf_vendor); }
|
||||
if (cf_product) { CFRelease(cf_product); }
|
||||
|
||||
output.emplace_back(std::move(port_info));
|
||||
}
|
||||
}
|
||||
|
@ -158,9 +229,15 @@ std::vector<SerialPortInfo> scan_serial_ports_extended()
|
|||
const auto path = dir_entry.path().string();
|
||||
SerialPortInfo spi;
|
||||
spi.port = path;
|
||||
spi.hardware_id = path;
|
||||
#ifdef __linux__
|
||||
spi.friendly_name = get_tty_friendly_name(path, name);
|
||||
auto friendly_name = sysfs_tty_prop(name, "product");
|
||||
spi.friendly_name = friendly_name ? (boost::format("%1% (%2%)") % *friendly_name % path).str() : path;
|
||||
auto vid = sysfs_tty_prop_hex(name, "idVendor");
|
||||
auto pid = sysfs_tty_prop_hex(name, "idProduct");
|
||||
if (vid && pid) {
|
||||
spi.id_vendor = *vid;
|
||||
spi.id_product = *pid;
|
||||
}
|
||||
#else
|
||||
spi.friendly_name = path;
|
||||
#endif
|
||||
|
@ -189,5 +266,225 @@ std::vector<std::string> scan_serial_ports()
|
|||
return output;
|
||||
}
|
||||
|
||||
|
||||
|
||||
// Class Serial
|
||||
|
||||
namespace asio = boost::asio;
|
||||
using boost::system::error_code;
|
||||
|
||||
Serial::Serial(asio::io_service& io_service) :
|
||||
asio::serial_port(io_service)
|
||||
{}
|
||||
|
||||
Serial::Serial(asio::io_service& io_service, const std::string &name, unsigned baud_rate) :
|
||||
asio::serial_port(io_service, name)
|
||||
{
|
||||
set_baud_rate(baud_rate);
|
||||
}
|
||||
|
||||
Serial::~Serial() {}
|
||||
|
||||
void Serial::set_baud_rate(unsigned baud_rate)
|
||||
{
|
||||
try {
|
||||
// This does not support speeds > 115200
|
||||
set_option(boost::asio::serial_port_base::baud_rate(baud_rate));
|
||||
} catch (boost::system::system_error &) {
|
||||
auto handle = native_handle();
|
||||
|
||||
auto handle_errno = [](int retval) {
|
||||
if (retval != 0) {
|
||||
throw std::runtime_error(
|
||||
(boost::format("Could not set baud rate: %1%") % strerror(errno)).str()
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
#if __APPLE__
|
||||
termios ios;
|
||||
handle_errno(::tcgetattr(handle, &ios));
|
||||
handle_errno(::cfsetspeed(&ios, baud_rate));
|
||||
speed_t newSpeed = baud_rate;
|
||||
handle_errno(::ioctl(handle, IOSSIOSPEED, &newSpeed));
|
||||
handle_errno(::tcsetattr(handle, TCSANOW, &ios));
|
||||
#elif __linux
|
||||
|
||||
/* The following definitions are kindly borrowed from:
|
||||
/usr/include/asm-generic/termbits.h
|
||||
Unfortunately we cannot just include that one because
|
||||
it would redefine the "struct termios" already defined
|
||||
the <termios.h> already included by Boost.ASIO. */
|
||||
#define K_NCCS 19
|
||||
struct termios2 {
|
||||
tcflag_t c_iflag;
|
||||
tcflag_t c_oflag;
|
||||
tcflag_t c_cflag;
|
||||
tcflag_t c_lflag;
|
||||
cc_t c_line;
|
||||
cc_t c_cc[K_NCCS];
|
||||
speed_t c_ispeed;
|
||||
speed_t c_ospeed;
|
||||
};
|
||||
#define BOTHER CBAUDEX
|
||||
|
||||
termios2 ios;
|
||||
handle_errno(::ioctl(handle, TCGETS2, &ios));
|
||||
ios.c_ispeed = ios.c_ospeed = baud_rate;
|
||||
ios.c_cflag &= ~CBAUD;
|
||||
ios.c_cflag |= BOTHER | CLOCAL | CREAD;
|
||||
ios.c_cc[VMIN] = 1; // Minimum of characters to read, prevents eof errors when 0 bytes are read
|
||||
ios.c_cc[VTIME] = 1;
|
||||
handle_errno(::ioctl(handle, TCSETS2, &ios));
|
||||
|
||||
#elif __OpenBSD__
|
||||
struct termios ios;
|
||||
handle_errno(::tcgetattr(handle, &ios));
|
||||
handle_errno(::cfsetspeed(&ios, baud_rate));
|
||||
handle_errno(::tcsetattr(handle, TCSAFLUSH, &ios));
|
||||
#else
|
||||
throw std::runtime_error("Custom baud rates are not currently supported on this OS");
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
void Serial::set_DTR(bool on)
|
||||
{
|
||||
auto handle = native_handle();
|
||||
#if defined(_WIN32) && !defined(__SYMBIAN32__)
|
||||
if (! EscapeCommFunction(handle, on ? SETDTR : CLRDTR)) {
|
||||
throw std::runtime_error("Could not set serial port DTR");
|
||||
}
|
||||
#else
|
||||
int status;
|
||||
if (::ioctl(handle, TIOCMGET, &status) == 0) {
|
||||
on ? status |= TIOCM_DTR : status &= ~TIOCM_DTR;
|
||||
if (::ioctl(handle, TIOCMSET, &status) == 0) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
throw std::runtime_error(
|
||||
(boost::format("Could not set serial port DTR: %1%") % strerror(errno)).str()
|
||||
);
|
||||
#endif
|
||||
}
|
||||
|
||||
void Serial::reset_line_num()
|
||||
{
|
||||
// See https://github.com/MarlinFirmware/Marlin/wiki/M110
|
||||
write_string("M110 N0\n");
|
||||
m_line_num = 0;
|
||||
}
|
||||
|
||||
bool Serial::read_line(unsigned timeout, std::string &line, error_code &ec)
|
||||
{
|
||||
auto &io_service = get_io_service();
|
||||
asio::deadline_timer timer(io_service);
|
||||
char c = 0;
|
||||
bool fail = false;
|
||||
|
||||
while (true) {
|
||||
io_service.reset();
|
||||
|
||||
asio::async_read(*this, boost::asio::buffer(&c, 1), [&](const error_code &read_ec, size_t size) {
|
||||
if (ec || size == 0) {
|
||||
fail = true;
|
||||
ec = read_ec; // FIXME: only if operation not aborted
|
||||
}
|
||||
timer.cancel(); // FIXME: ditto
|
||||
});
|
||||
|
||||
if (timeout > 0) {
|
||||
timer.expires_from_now(boost::posix_time::milliseconds(timeout));
|
||||
timer.async_wait([&](const error_code &ec) {
|
||||
// Ignore timer aborts
|
||||
if (!ec) {
|
||||
fail = true;
|
||||
this->cancel();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
io_service.run();
|
||||
|
||||
if (fail) {
|
||||
return false;
|
||||
} else if (c != '\n') {
|
||||
line += c;
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Serial::printer_setup()
|
||||
{
|
||||
printer_reset();
|
||||
write_string("\r\r\r\r\r\r\r\r\r\r"); // Gets rid of line noise, if any
|
||||
}
|
||||
|
||||
size_t Serial::write_string(const std::string &str)
|
||||
{
|
||||
// TODO: might be wise to timeout here as well
|
||||
return asio::write(*this, asio::buffer(str));
|
||||
}
|
||||
|
||||
bool Serial::printer_ready_wait(unsigned retries, unsigned timeout)
|
||||
{
|
||||
std::string line;
|
||||
error_code ec;
|
||||
|
||||
for (; retries > 0; retries--) {
|
||||
reset_line_num();
|
||||
|
||||
while (read_line(timeout, line, ec)) {
|
||||
if (line == "ok") {
|
||||
return true;
|
||||
}
|
||||
line.clear();
|
||||
}
|
||||
|
||||
line.clear();
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
size_t Serial::printer_write_line(const std::string &line, unsigned line_num)
|
||||
{
|
||||
const auto formatted_line = Utils::Serial::printer_format_line(line, line_num);
|
||||
return write_string(formatted_line);
|
||||
}
|
||||
|
||||
size_t Serial::printer_write_line(const std::string &line)
|
||||
{
|
||||
m_line_num++;
|
||||
return printer_write_line(line, m_line_num);
|
||||
}
|
||||
|
||||
void Serial::printer_reset()
|
||||
{
|
||||
this->set_DTR(false);
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(200));
|
||||
this->set_DTR(true);
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(200));
|
||||
this->set_DTR(false);
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(1000));
|
||||
}
|
||||
|
||||
std::string Serial::printer_format_line(const std::string &line, unsigned line_num)
|
||||
{
|
||||
const auto line_num_str = std::to_string(line_num);
|
||||
|
||||
unsigned checksum = 'N';
|
||||
for (auto c : line_num_str) { checksum ^= c; }
|
||||
checksum ^= ' ';
|
||||
for (auto c : line) { checksum ^= c; }
|
||||
|
||||
return (boost::format("N%1% %2%*%3%\n") % line_num_str % line % checksum).str();
|
||||
}
|
||||
|
||||
|
||||
} // namespace Utils
|
||||
} // namespace Slic3r
|
||||
|
|
|
@ -1,31 +1,81 @@
|
|||
#ifndef slic3r_GUI_Utils_Serial_hpp_
|
||||
#define slic3r_GUI_Utils_Serial_hpp_
|
||||
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <boost/system/error_code.hpp>
|
||||
#include <boost/asio.hpp>
|
||||
|
||||
|
||||
namespace Slic3r {
|
||||
namespace Utils {
|
||||
|
||||
struct SerialPortInfo {
|
||||
std::string port;
|
||||
std::string hardware_id;
|
||||
unsigned id_vendor = -1;
|
||||
unsigned id_product = -1;
|
||||
std::string friendly_name;
|
||||
bool is_printer = false;
|
||||
bool is_printer = false;
|
||||
|
||||
bool id_match(unsigned id_vendor, unsigned id_product) const { return id_vendor == this->id_vendor && id_product == this->id_product; }
|
||||
};
|
||||
|
||||
inline bool operator==(const SerialPortInfo &sp1, const SerialPortInfo &sp2)
|
||||
inline bool operator==(const SerialPortInfo &sp1, const SerialPortInfo &sp2)
|
||||
{
|
||||
return sp1.port == sp2.port &&
|
||||
sp1.hardware_id == sp2.hardware_id &&
|
||||
sp1.is_printer == sp2.is_printer;
|
||||
return
|
||||
sp1.port == sp2.port &&
|
||||
sp1.id_vendor == sp2.id_vendor &&
|
||||
sp1.id_product == sp2.id_product &&
|
||||
sp1.is_printer == sp2.is_printer;
|
||||
}
|
||||
|
||||
extern std::vector<std::string> scan_serial_ports();
|
||||
extern std::vector<SerialPortInfo> scan_serial_ports_extended();
|
||||
|
||||
|
||||
class Serial : public boost::asio::serial_port
|
||||
{
|
||||
public:
|
||||
Serial(boost::asio::io_service &io_service);
|
||||
Serial(boost::asio::io_service &io_service, const std::string &name, unsigned baud_rate);
|
||||
Serial(const Serial &) = delete;
|
||||
Serial &operator=(const Serial &) = delete;
|
||||
~Serial();
|
||||
|
||||
void set_baud_rate(unsigned baud_rate);
|
||||
void set_DTR(bool on);
|
||||
|
||||
// Resets the line number both internally as well as with the firmware using M110
|
||||
void reset_line_num();
|
||||
|
||||
// Reads a line or times out, the timeout is in milliseconds
|
||||
bool read_line(unsigned timeout, std::string &line, boost::system::error_code &ec);
|
||||
|
||||
// Perform an initial setup for communicating with a printer
|
||||
void printer_setup();
|
||||
|
||||
// Write data from a string
|
||||
size_t write_string(const std::string &str);
|
||||
|
||||
// Attempts to reset the line numer and waits until the printer says "ok"
|
||||
bool printer_ready_wait(unsigned retries, unsigned timeout);
|
||||
|
||||
// Write Marlin-formatted line, with a line number and a checksum
|
||||
size_t printer_write_line(const std::string &line, unsigned line_num);
|
||||
|
||||
// Same as above, but with internally-managed line number
|
||||
size_t printer_write_line(const std::string &line);
|
||||
|
||||
// Toggles DTR to reset the printer
|
||||
void printer_reset();
|
||||
|
||||
// Formats a line Marlin-style, ie. with a sequential number and a checksum
|
||||
static std::string printer_format_line(const std::string &line, unsigned line_num);
|
||||
private:
|
||||
unsigned m_line_num = 0;
|
||||
};
|
||||
|
||||
|
||||
} // Utils
|
||||
} // Slic3r
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue