diff options
author | tamasmeszaros <meszaros.q@gmail.com> | 2018-07-27 18:37:33 +0300 |
---|---|---|
committer | tamasmeszaros <meszaros.q@gmail.com> | 2018-07-27 18:46:19 +0300 |
commit | 4e901a9db778660d3471a49cd95d66f85b2dbc88 (patch) | |
tree | 3c5c646864100eb3710cb1eb713acd647ad37736 /xs | |
parent | f364bd1884c318c68be9d0ee4529848919ced917 (diff) | |
parent | 78a8acfcf187d05cfea8a0521cf238937221a86a (diff) |
Merge remote-tracking branch 'origin/master' into feature_arrange_with_libnest2d
Diffstat (limited to 'xs')
30 files changed, 1563 insertions, 696 deletions
diff --git a/xs/CMakeLists.txt b/xs/CMakeLists.txt index 976943d64..95ff990aa 100644 --- a/xs/CMakeLists.txt +++ b/xs/CMakeLists.txt @@ -26,7 +26,7 @@ include_directories(${LIBDIR}/libslic3r) if(WIN32) # BOOST_ALL_NO_LIB: Avoid the automatic linking of Boost libraries on Windows. Rather rely on explicit linking. - add_definitions(-D_USE_MATH_DEFINES -D_WIN32 -DBOOST_ALL_NO_LIB) + add_definitions(-D_USE_MATH_DEFINES -D_WIN32 -DBOOST_ALL_NO_LIB -DBOOST_USE_WINAPI_VERSION=0x601) # -D_ITERATOR_DEBUG_LEVEL) if(WIN10SDK_PATH) message("Building with Win10 Netfabb STL fixing service support") @@ -258,6 +258,8 @@ add_library(libslic3r_gui STATIC ${LIBDIR}/slic3r/Utils/PresetUpdater.hpp ${LIBDIR}/slic3r/Utils/Time.cpp ${LIBDIR}/slic3r/Utils/Time.hpp + ${LIBDIR}/slic3r/Utils/HexFile.cpp + ${LIBDIR}/slic3r/Utils/HexFile.hpp ${LIBDIR}/slic3r/IProgressIndicator.hpp ${LIBDIR}/slic3r/AppController.hpp ${LIBDIR}/slic3r/AppController.cpp diff --git a/xs/src/avrdude/CMakeLists.txt b/xs/src/avrdude/CMakeLists.txt index 043f8fb7b..d88563368 100644 --- a/xs/src/avrdude/CMakeLists.txt +++ b/xs/src/avrdude/CMakeLists.txt @@ -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() diff --git a/xs/src/avrdude/Makefile.standalone b/xs/src/avrdude/Makefile.standalone new file mode 100644 index 000000000..d9a773771 --- /dev/null +++ b/xs/src/avrdude/Makefile.standalone @@ -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) diff --git a/xs/src/avrdude/avrdude-slic3r.cpp b/xs/src/avrdude/avrdude-slic3r.cpp index 030353413..4a7f22d6e 100644 --- a/xs/src/avrdude/avrdude-slic3r.cpp +++ b/xs/src/avrdude/avrdude-slic3r.cpp @@ -36,6 +36,7 @@ struct AvrDude::priv std::string sys_config; std::deque<std::vector<std::string>> args; size_t current_args_set = 0; + bool cancelled = false; RunFn run_fn; MessageFn message_fn; ProgressFn progress_fn; @@ -141,11 +142,16 @@ 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(); + self->p->run_fn(*self); } - auto res = self->p->run(); + if (! self->p->cancelled) { + res = self->p->run(); + } if (self->p->complete_fn) { self->p->complete_fn(res, self->p->current_args_set); @@ -160,7 +166,10 @@ AvrDude::Ptr AvrDude::run() void AvrDude::cancel() { - ::avrdude_cancel(); + if (p) { + p->cancelled = true; + ::avrdude_cancel(); + } } void AvrDude::join() diff --git a/xs/src/avrdude/avrdude-slic3r.hpp b/xs/src/avrdude/avrdude-slic3r.hpp index 273aa2378..399df2358 100644 --- a/xs/src/avrdude/avrdude-slic3r.hpp +++ b/xs/src/avrdude/avrdude-slic3r.hpp @@ -12,7 +12,7 @@ class AvrDude { public: typedef std::shared_ptr<AvrDude> Ptr; - typedef std::function<void()> RunFn; + typedef std::function<void(AvrDude&)> 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; @@ -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); diff --git a/xs/src/avrdude/fileio.c b/xs/src/avrdude/fileio.c index aa57f5587..708159295 100644 --- a/xs/src/avrdude/fileio.c +++ b/xs/src/avrdude/fileio.c @@ -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)); diff --git a/xs/src/avrdude/libavrdude.h b/xs/src/avrdude/libavrdude.h index 536f1a2f7..aef792476 100644 --- a/xs/src/avrdude/libavrdude.h +++ b/xs/src/avrdude/libavrdude.h @@ -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); diff --git a/xs/src/avrdude/main-standalone.c b/xs/src/avrdude/main-standalone.c new file mode 100644 index 000000000..359a055ca --- /dev/null +++ b/xs/src/avrdude/main-standalone.c @@ -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); +} diff --git a/xs/src/avrdude/main.c b/xs/src/avrdude/main.c index d4c34fe44..5d73403b0 100644 --- a/xs/src/avrdude/main.c +++ b/xs/src/avrdude/main.c @@ -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" diff --git a/xs/src/avrdude/ser_win32.c b/xs/src/avrdude/ser_win32.c index 20d085d13..3a05cfa90 100644 --- a/xs/src/avrdude/ser_win32.c +++ b/xs/src/avrdude/ser_win32.c @@ -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) diff --git a/xs/src/avrdude/update.c b/xs/src/avrdude/update.c index e9dd6e325..417cbf71d 100644 --- a/xs/src/avrdude/update.c +++ b/xs/src/avrdude/update.c @@ -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); diff --git a/xs/src/libslic3r/GCodeSender.cpp b/xs/src/libslic3r/GCodeSender.cpp index c3530e00f..0988091ce 100644 --- a/xs/src/libslic3r/GCodeSender.cpp +++ b/xs/src/libslic3r/GCodeSender.cpp @@ -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 diff --git a/xs/src/libslic3r/Model.cpp b/xs/src/libslic3r/Model.cpp index 90ebface2..87519d5e7 100644 --- a/xs/src/libslic3r/Model.cpp +++ b/xs/src/libslic3r/Model.cpp @@ -611,14 +611,13 @@ bool arrange(Model &model, coordf_t dist, const Slic3r::BoundingBoxf* bb, // The score is a weighted sum of the distance from pile center // and the pile size score = GRAVITY_RATIO * dist + DENSITY_RATIO * density; - std::cout << "big " << std::endl; } else if(itemnormarea < BIG_ITEM_TRESHOLD && bigs.empty()) { // If there are no big items, only small, we should consider the // density here as well to not get silly results auto bindist = pl::distance(ibb.center(), bin.center()) / norm; auto density = std::sqrt(fullbb.width()*fullbb.height()) / norm; - score = GRAVITY_RATIO* bindist + DENSITY_RATIO * density; + score = GRAVITY_RATIO * bindist + DENSITY_RATIO * density; } else { // Here there are the small items that should be placed around the // already processed bigger items. diff --git a/xs/src/slic3r/GUI/3DScene.cpp b/xs/src/slic3r/GUI/3DScene.cpp index 62659033a..6ec339153 100644 --- a/xs/src/slic3r/GUI/3DScene.cpp +++ b/xs/src/slic3r/GUI/3DScene.cpp @@ -210,11 +210,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)) { @@ -250,7 +251,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); @@ -441,7 +442,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()); @@ -460,7 +461,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()); @@ -633,7 +634,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.x, instance->offset.y, 0.0)); v.set_angle_z(instance->rotation); v.set_scale_factor(instance->scaling_factor); @@ -1786,6 +1787,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); @@ -1968,24 +1974,14 @@ 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) +void _3DScene::load_gcode_preview(wxGLCanvas* canvas, const GCodePreviewData* preview_data, const std::vector<std::string>& str_tool_colors) { - s_canvas_mgr.load_wipe_tower_toolpaths(canvas, str_tool_colors); + s_canvas_mgr.load_gcode_preview(canvas, preview_data, str_tool_colors); } -void _3DScene::load_gcode_preview(wxGLCanvas* canvas, const GCodePreviewData* preview_data, const std::vector<std::string>& str_tool_colors) +void _3DScene::load_preview(wxGLCanvas* canvas, const std::vector<std::string>& str_tool_colors) { - s_canvas_mgr.load_gcode_preview(canvas, preview_data, str_tool_colors); + s_canvas_mgr.load_preview(canvas, str_tool_colors); } void _3DScene::reset_legend_texture() diff --git a/xs/src/slic3r/GUI/3DScene.hpp b/xs/src/slic3r/GUI/3DScene.hpp index 5409b9588..a2050e5a1 100644 --- a/xs/src/slic3r/GUI/3DScene.hpp +++ b/xs/src/slic3r/GUI/3DScene.hpp @@ -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; @@ -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(); diff --git a/xs/src/slic3r/GUI/FirmwareDialog.cpp b/xs/src/slic3r/GUI/FirmwareDialog.cpp index d74743055..30339e3cb 100644 --- a/xs/src/slic3r/GUI/FirmwareDialog.cpp +++ b/xs/src/slic3r/GUI/FirmwareDialog.cpp @@ -1,12 +1,23 @@ -#include "FirmwareDialog.hpp" - #include <numeric> #include <algorithm> +#include <thread> +#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 "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> #include <wx/sizer.h> @@ -22,16 +33,18 @@ #include <wx/collpane.h> #include <wx/msgdlg.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; namespace Slic3r { +using Utils::HexFile; +using Utils::SerialPortInfo; +using Utils::Serial; + // This enum discriminates the kind of information in EVT_AVRDUDE, // it's stored in the ExtraLong field of wxCommandEvent. @@ -40,6 +53,7 @@ enum AvrdudeEvent AE_MESSAGE, AE_PROGRESS, AE_EXIT, + AE_ERROR, }; wxDECLARE_EVENT(EVT_AVRDUDE, wxCommandEvent); @@ -52,7 +66,6 @@ struct FirmwareDialog::priv { enum AvrDudeComplete { - AC_NONE, AC_SUCCESS, AC_FAILURE, AC_CANCEL, @@ -61,7 +74,7 @@ struct FirmwareDialog::priv FirmwareDialog *q; // PIMPL back pointer ("Q-Pointer") wxComboBox *port_picker; - std::vector<Utils::SerialPortInfo> ports; + std::vector<SerialPortInfo> ports; wxFilePickerCtrl *hex_picker; wxStaticText *txt_status; wxGauge *progressbar; @@ -80,7 +93,9 @@ struct FirmwareDialog::priv AvrDude::Ptr avrdude; std::string avrdude_config; unsigned progress_tasks_done; + unsigned progress_tasks_bar; bool cancelled; + const bool extra_verbose; // For debugging priv(FirmwareDialog *q) : q(q), @@ -89,16 +104,25 @@ struct FirmwareDialog::priv timer_pulse(q), avrdude_config((fs::path(::Slic3r::resources_dir()) / "avrdude" / "avrdude.conf").string()), progress_tasks_done(0), - cancelled(false) + progress_tasks_bar(0), + cancelled(false), + extra_verbose(false) {} void find_serial_ports(); - void flashing_start(bool flashing_l10n); + void flashing_start(unsigned tasks); void flashing_done(AvrDudeComplete complete); - size_t hex_lang_offset(const wxString &path); + void check_model_id(const HexFile &metadata, const SerialPortInfo &port); + + void prepare_common(AvrDude &, const SerialPortInfo &port, const std::string &filename); + void prepare_mk2(AvrDude &, const SerialPortInfo &port, const std::string &filename); + void prepare_mk3(AvrDude &, const SerialPortInfo &port, const std::string &filename); + void prepare_mm_control(AvrDude &, const SerialPortInfo &port, const std::string &filename); void perform_upload(); + void cancel(); void on_avrdude(const wxCommandEvent &evt); + void ensure_joined(); }; void FirmwareDialog::priv::find_serial_ports() @@ -108,7 +132,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,7 +146,7 @@ void FirmwareDialog::priv::find_serial_ports() } } -void FirmwareDialog::priv::flashing_start(bool flashing_l10n) +void FirmwareDialog::priv::flashing_start(unsigned tasks) { txt_stdout->Clear(); txt_status->SetLabel(_(L("Flashing in progress. Please do not disconnect the printer!"))); @@ -132,9 +156,10 @@ void FirmwareDialog::priv::flashing_start(bool flashing_l10n) 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; + progress_tasks_bar = 0; cancelled = false; timer_pulse.Start(50); } @@ -158,63 +183,49 @@ void FirmwareDialog::priv::flashing_done(AvrDudeComplete complete) } } -size_t FirmwareDialog::priv::hex_lang_offset(const wxString &path) +void FirmwareDialog::priv::check_model_id(const HexFile &metadata, const SerialPortInfo &port) { - fs::ifstream file(fs::path(path.wx_str())); - if (! file.good()) { - return 0; + if (metadata.model_id.empty()) { + // No data to check against + return; + } + + asio::io_service io; + Serial serial(io, port.port, 115200); + serial.printer_setup(); + + enum { + TIMEOUT = 1000, + RETREIES = 3, + }; + + if (! serial.printer_ready_wait(RETREIES, TIMEOUT)) { + throw wxString::Format(_(L("Could not connect to the printer at %s")), port.port); } - 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'); + error_code ec; + serial.printer_write_line("PRUSA Rev"); + while (serial.read_line(TIMEOUT, line, ec)) { + if (ec) { throw wxString::Format(_(L("Could not connect to the printer at %s")), port.port); } + if (line == "ok") { continue; } + + if (line == metadata.model_id) { + return; + } else { + throw wxString::Format(_(L( + "The firmware hex file does not match the printer model.\n" + "The hex file is intended for:\n %s\n" + "Printer reports:\n %s" + )), metadata.model_id, line); } - 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; - } - } + line.clear(); } - - return 0; } -void FirmwareDialog::priv::perform_upload() +void FirmwareDialog::priv::prepare_common(AvrDude &avrdude, const SerialPortInfo &port, const std::string &filename) { - 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; - } - 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(); - - flashing_start(lang_offset > 0); - - // 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; - - // Init the avrdude object - AvrDude avrdude(avrdude_config); - - // Build argument list(s) std::vector<std::string> args {{ extra_verbose ? "-vvvvv" : "-v", "-p", "atmega2560", @@ -222,11 +233,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, + "-P", port.port, "-b", "115200", // TODO: Allow other rates? Ditto below. "-D", - // XXX: Safe mode? - "-U", (boost::format("flash:w:0:%1%:i") % filename_utf8.data()).str(), + "-U", (boost::format("flash:w:0:%1%:i") % filename).str(), }}; BOOST_LOG_TRIVIAL(info) << "Invoking avrdude, arguments: " @@ -235,32 +245,167 @@ void FirmwareDialog::priv::perform_upload() }); 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(), - }}; - - 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; - }); - - avrdude.push_args(std::move(args_l10n)); +} + +void FirmwareDialog::priv::prepare_mk2(AvrDude &avrdude, const SerialPortInfo &port, const std::string &filename) +{ + prepare_common(avrdude, port, filename); +} + +void FirmwareDialog::priv::prepare_mk3(AvrDude &avrdude, const SerialPortInfo &port, const std::string &filename) +{ + prepare_common(avrdude, port, filename); + + // 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.port, + "-b", "115200", + "-D", + "-u", // disable safe mode + "-U", (boost::format("flash:w:1:%1%:i") % filename).str(), + }}; + + BOOST_LOG_TRIVIAL(info) << "Invoking avrdude for external flash flashing, arguments: " + << std::accumulate(std::next(args_l10n.begin()), args_l10n.end(), args_l10n[0], [](std::string a, const std::string &b) { + return a + ' ' + b; + }); + + avrdude.push_args(std::move(args_l10n)); +} + +void FirmwareDialog::priv::prepare_mm_control(AvrDude &avrdude, const SerialPortInfo &port_in, const std::string &filename) +{ + // Check if the port has the PID/VID of 0x2c99/3 + // If not, check if it is the MMU (0x2c99/4) and reboot the by opening @ 1200 bauds + BOOST_LOG_TRIVIAL(info) << "Flashing MMU 2.0, looking for VID/PID 0x2c99/3 or 0x2c99/4 ..."; + SerialPortInfo port = port_in; + if (! port.id_match(0x2c99, 3)) { + if (! port.id_match(0x2c99, 4)) { + // This is not a Prusa MMU 2.0 device + BOOST_LOG_TRIVIAL(error) << boost::format("Not a Prusa MMU 2.0 device: `%1%`") % port.port; + throw wxString::Format(_(L("The device at `%s` is not am Original Prusa i3 MMU 2.0 device")), port.port); + } + + BOOST_LOG_TRIVIAL(info) << boost::format("Found VID/PID 0x2c99/4 at `%1%`, rebooting the device ...") % port.port; + + { + asio::io_service io; + Serial serial(io, port.port, 1200); + std::this_thread::sleep_for(std::chrono::milliseconds(50)); + } + + // Wait for the bootloader to show up + std::this_thread::sleep_for(std::chrono::milliseconds(2000)); + + // Look for the rebooted device + BOOST_LOG_TRIVIAL(info) << "... looking for VID/PID 0x2c99/3 ..."; + auto new_ports = Utils::scan_serial_ports_extended(); + unsigned hits = 0; + for (auto &&new_port : new_ports) { + if (new_port.id_match(0x2c99, 3)) { + hits++; + port = std::move(new_port); + } + } + + if (hits == 0) { + BOOST_LOG_TRIVIAL(error) << "No VID/PID 0x2c99/3 device found after rebooting the MMU 2.0"; + throw wxString::Format(_(L("Failed to reboot the device at `%s` for programming")), port.port); + } else if (hits > 1) { + // We found multiple 0x2c99/3 devices, this is bad, because there's no way to find out + // which one is the one user wants to flash. + BOOST_LOG_TRIVIAL(error) << "Several VID/PID 0x2c99/3 devices found after rebooting the MMU 2.0"; + throw wxString::Format(_(L("Multiple Original Prusa i3 MMU 2.0 devices found. Please only connect one at a time for flashing.")), port.port); + } } - + + BOOST_LOG_TRIVIAL(info) << boost::format("Found VID/PID 0x2c99/3 at `%1%`, flashing ...") % port.port; + + 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") % filename).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; } + + int selection = port_picker->GetSelection(); + if (selection == wxNOT_FOUND) { return; } + + const SerialPortInfo &port = this->ports[selection]; + // Verify whether the combo box list selection equals to the combo box edit value. + if (wxString::FromUTF8(this->ports[selection].friendly_name.data()) != port_picker->GetValue()) { + return; + } + + const bool extra_verbose = false; // For debugging + HexFile metadata(filename.wx_str()); + const std::string filename_utf8(filename.utf8_str().data()); + + flashing_start(metadata.device == HexFile::DEV_MK3 ? 2 : 1); + + // 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([=](AvrDude &avrdude) { + auto queue_error = [&](wxString message) { + avrdude.cancel(); + + auto evt = new wxCommandEvent(EVT_AVRDUDE, this->q->GetId()); + evt->SetExtraLong(AE_ERROR); + evt->SetString(std::move(message)); + wxQueueEvent(this->q, evt); + }; + + try { + switch (metadata.device) { + case HexFile::DEV_MK3: + this->check_model_id(metadata, port); + this->prepare_mk3(avrdude, port, filename_utf8); + break; + + case HexFile::DEV_MM_CONTROL: + this->check_model_id(metadata, port); + this->prepare_mm_control(avrdude, port, filename_utf8); + break; + + default: + this->prepare_mk2(avrdude, port, filename_utf8); + break; + } + } catch (const wxString &message) { + queue_error(message); + } catch (const std::exception &ex) { + queue_error(wxString::Format(_(L("Error accessing port at %s: %s")), port.port, ex.what())); + } + }) .on_message(std::move([q, extra_verbose](const char *msg, unsigned /* size */) { if (extra_verbose) { BOOST_LOG_TRIVIAL(debug) << "avrdude: " << msg; @@ -313,12 +458,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; @@ -328,11 +476,17 @@ void FirmwareDialog::priv::on_avrdude(const wxCommandEvent &evt) complete_kind = cancelled ? AC_CANCEL : (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_ERROR: + txt_stdout->AppendText(evt.GetString()); + flashing_done(AC_FAILURE); + ensure_joined(); + { + GUI::ErrorDialog dlg(this->q, evt.GetString()); + dlg.ShowModal(); + } break; default: @@ -340,6 +494,13 @@ void FirmwareDialog::priv::on_avrdude(const wxCommandEvent &evt) } } +void FirmwareDialog::priv::ensure_joined() +{ + // Make sure the background thread is collected and the AvrDude object reset + if (avrdude) { avrdude->join(); } + avrdude.reset(); +} + // Public diff --git a/xs/src/slic3r/GUI/GLCanvas3D.cpp b/xs/src/slic3r/GUI/GLCanvas3D.cpp index 722f1c112..6396233e8 100644 --- a/xs/src/slic3r/GUI/GLCanvas3D.cpp +++ b/xs/src/slic3r/GUI/GLCanvas3D.cpp @@ -51,6 +51,9 @@ static const float UNIT_MATRIX[] = { 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f }; +static const float DEFAULT_BG_COLOR[3] = { 10.0f / 255.0f, 98.0f / 255.0f, 144.0f / 255.0f }; +static const float ERROR_BG_COLOR[3] = { 144.0f / 255.0f, 49.0f / 255.0f, 10.0f / 255.0f }; + namespace Slic3r { namespace GUI { @@ -1703,6 +1706,7 @@ GLCanvas3D::GLCanvas3D(wxGLCanvas* canvas) , m_picking_enabled(false) , m_moving_enabled(false) , m_shader_enabled(false) + , m_dynamic_background_enabled(false) , m_multisample_allowed(false) , m_color_by("volume") , m_select_by("object") @@ -1828,7 +1832,6 @@ unsigned int GLCanvas3D::get_volumes_count() const void GLCanvas3D::reset_volumes() { - if (!m_volumes.empty()) { // ensures this canvas is current @@ -1839,6 +1842,9 @@ void GLCanvas3D::reset_volumes() m_volumes.clear(); m_dirty = true; } + + enable_warning_texture(false); + _reset_warning_texture(); } void GLCanvas3D::deselect_volumes() @@ -2065,6 +2071,11 @@ void GLCanvas3D::enable_force_zoom_to_bed(bool enable) m_force_zoom_to_bed_enabled = enable; } +void GLCanvas3D::enable_dynamic_background(bool enable) +{ + m_dynamic_background_enabled = enable; +} + void GLCanvas3D::allow_multisample(bool allow) { m_multisample_allowed = allow; @@ -2326,372 +2337,6 @@ void GLCanvas3D::reload_scene(bool force) } } -void GLCanvas3D::load_print_toolpaths() -{ - // ensures this canvas is current - if (!set_current()) - return; - - if (m_print == nullptr) - return; - - if (!m_print->state.is_done(psSkirt) || !m_print->state.is_done(psBrim)) - return; - - if (!m_print->has_skirt() && (m_print->config.brim_width.value == 0)) - return; - - const float color[] = { 0.5f, 1.0f, 0.5f, 1.0f }; // greenish - - // number of skirt layers - size_t total_layer_count = 0; - for (const PrintObject* print_object : m_print->objects) - { - total_layer_count = std::max(total_layer_count, print_object->total_layer_count()); - } - size_t skirt_height = m_print->has_infinite_skirt() ? total_layer_count : std::min<size_t>(m_print->config.skirt_height.value, total_layer_count); - if ((skirt_height == 0) && (m_print->config.brim_width.value > 0)) - skirt_height = 1; - - // get first skirt_height layers (maybe this should be moved to a PrintObject method?) - const PrintObject* object0 = m_print->objects.front(); - std::vector<float> print_zs; - print_zs.reserve(skirt_height * 2); - for (size_t i = 0; i < std::min(skirt_height, object0->layers.size()); ++i) - { - print_zs.push_back(float(object0->layers[i]->print_z)); - } - //FIXME why there are support layers? - for (size_t i = 0; i < std::min(skirt_height, object0->support_layers.size()); ++i) - { - print_zs.push_back(float(object0->support_layers[i]->print_z)); - } - sort_remove_duplicates(print_zs); - if (print_zs.size() > skirt_height) - print_zs.erase(print_zs.begin() + skirt_height, print_zs.end()); - - m_volumes.volumes.emplace_back(new GLVolume(color)); - GLVolume& volume = *m_volumes.volumes.back(); - for (size_t i = 0; i < skirt_height; ++i) { - volume.print_zs.push_back(print_zs[i]); - volume.offsets.push_back(volume.indexed_vertex_array.quad_indices.size()); - volume.offsets.push_back(volume.indexed_vertex_array.triangle_indices.size()); - if (i == 0) - _3DScene::extrusionentity_to_verts(m_print->brim, print_zs[i], Point(0, 0), volume); - - _3DScene::extrusionentity_to_verts(m_print->skirt, print_zs[i], Point(0, 0), volume); - } - volume.bounding_box = volume.indexed_vertex_array.bounding_box(); - volume.indexed_vertex_array.finalize_geometry(m_use_VBOs && m_initialized); -} - -void GLCanvas3D::load_print_object_toolpaths(const PrintObject& print_object, const std::vector<std::string>& str_tool_colors) -{ - std::vector<float> tool_colors = _parse_colors(str_tool_colors); - - struct Ctxt - { - const Points *shifted_copies; - std::vector<const Layer*> layers; - bool has_perimeters; - bool has_infill; - bool has_support; - const std::vector<float>* tool_colors; - - // Number of vertices (each vertex is 6x4=24 bytes long) - static const size_t alloc_size_max() { return 131072; } // 3.15MB - // static const size_t alloc_size_max () { return 65536; } // 1.57MB - // static const size_t alloc_size_max () { return 32768; } // 786kB - static const size_t alloc_size_reserve() { return alloc_size_max() * 2; } - - static const float* color_perimeters() { static float color[4] = { 1.0f, 1.0f, 0.0f, 1.f }; return color; } // yellow - static const float* color_infill() { static float color[4] = { 1.0f, 0.5f, 0.5f, 1.f }; return color; } // redish - static const float* color_support() { static float color[4] = { 0.5f, 1.0f, 0.5f, 1.f }; return color; } // greenish - - // For cloring by a tool, return a parsed color. - bool color_by_tool() const { return tool_colors != nullptr; } - size_t number_tools() const { return this->color_by_tool() ? tool_colors->size() / 4 : 0; } - const float* color_tool(size_t tool) const { return tool_colors->data() + tool * 4; } - int volume_idx(int extruder, int feature) const - { - return this->color_by_tool() ? std::min<int>(this->number_tools() - 1, std::max<int>(extruder - 1, 0)) : feature; - } - } ctxt; - - ctxt.shifted_copies = &print_object._shifted_copies; - - // order layers by print_z - ctxt.layers.reserve(print_object.layers.size() + print_object.support_layers.size()); - for (const Layer *layer : print_object.layers) - ctxt.layers.push_back(layer); - for (const Layer *layer : print_object.support_layers) - ctxt.layers.push_back(layer); - std::sort(ctxt.layers.begin(), ctxt.layers.end(), [](const Layer *l1, const Layer *l2) { return l1->print_z < l2->print_z; }); - - // Maximum size of an allocation block: 32MB / sizeof(float) - ctxt.has_perimeters = print_object.state.is_done(posPerimeters); - ctxt.has_infill = print_object.state.is_done(posInfill); - ctxt.has_support = print_object.state.is_done(posSupportMaterial); - ctxt.tool_colors = tool_colors.empty() ? nullptr : &tool_colors; - - BOOST_LOG_TRIVIAL(debug) << "Loading print object toolpaths in parallel - start"; - - //FIXME Improve the heuristics for a grain size. - size_t grain_size = std::max(ctxt.layers.size() / 16, size_t(1)); - tbb::spin_mutex new_volume_mutex; - auto new_volume = [this, &new_volume_mutex](const float *color) -> GLVolume* { - auto *volume = new GLVolume(color); - new_volume_mutex.lock(); - volume->outside_printer_detection_enabled = false; - m_volumes.volumes.emplace_back(volume); - new_volume_mutex.unlock(); - return volume; - }; - const size_t volumes_cnt_initial = m_volumes.volumes.size(); - std::vector<GLVolumeCollection> volumes_per_thread(ctxt.layers.size()); - tbb::parallel_for( - tbb::blocked_range<size_t>(0, ctxt.layers.size(), grain_size), - [&ctxt, &new_volume](const tbb::blocked_range<size_t>& range) { - std::vector<GLVolume*> vols; - if (ctxt.color_by_tool()) { - for (size_t i = 0; i < ctxt.number_tools(); ++i) - vols.emplace_back(new_volume(ctxt.color_tool(i))); - } - else - vols = { new_volume(ctxt.color_perimeters()), new_volume(ctxt.color_infill()), new_volume(ctxt.color_support()) }; - for (GLVolume *vol : vols) - vol->indexed_vertex_array.reserve(ctxt.alloc_size_reserve()); - for (size_t idx_layer = range.begin(); idx_layer < range.end(); ++idx_layer) { - const Layer *layer = ctxt.layers[idx_layer]; - for (size_t i = 0; i < vols.size(); ++i) { - GLVolume &vol = *vols[i]; - if (vol.print_zs.empty() || vol.print_zs.back() != layer->print_z) { - vol.print_zs.push_back(layer->print_z); - vol.offsets.push_back(vol.indexed_vertex_array.quad_indices.size()); - vol.offsets.push_back(vol.indexed_vertex_array.triangle_indices.size()); - } - } - for (const Point © : *ctxt.shifted_copies) { - for (const LayerRegion *layerm : layer->regions) { - if (ctxt.has_perimeters) - _3DScene::extrusionentity_to_verts(layerm->perimeters, float(layer->print_z), copy, - *vols[ctxt.volume_idx(layerm->region()->config.perimeter_extruder.value, 0)]); - if (ctxt.has_infill) { - for (const ExtrusionEntity *ee : layerm->fills.entities) { - // fill represents infill extrusions of a single island. - const auto *fill = dynamic_cast<const ExtrusionEntityCollection*>(ee); - if (!fill->entities.empty()) - _3DScene::extrusionentity_to_verts(*fill, float(layer->print_z), copy, - *vols[ctxt.volume_idx( - is_solid_infill(fill->entities.front()->role()) ? - layerm->region()->config.solid_infill_extruder : - layerm->region()->config.infill_extruder, - 1)]); - } - } - } - if (ctxt.has_support) { - const SupportLayer *support_layer = dynamic_cast<const SupportLayer*>(layer); - if (support_layer) { - for (const ExtrusionEntity *extrusion_entity : support_layer->support_fills.entities) - _3DScene::extrusionentity_to_verts(extrusion_entity, float(layer->print_z), copy, - *vols[ctxt.volume_idx( - (extrusion_entity->role() == erSupportMaterial) ? - support_layer->object()->config.support_material_extruder : - support_layer->object()->config.support_material_interface_extruder, - 2)]); - } - } - } - for (size_t i = 0; i < vols.size(); ++i) { - GLVolume &vol = *vols[i]; - if (vol.indexed_vertex_array.vertices_and_normals_interleaved.size() / 6 > ctxt.alloc_size_max()) { - // Store the vertex arrays and restart their containers, - vols[i] = new_volume(vol.color); - GLVolume &vol_new = *vols[i]; - // Assign the large pre-allocated buffers to the new GLVolume. - vol_new.indexed_vertex_array = std::move(vol.indexed_vertex_array); - // Copy the content back to the old GLVolume. - vol.indexed_vertex_array = vol_new.indexed_vertex_array; - // Finalize a bounding box of the old GLVolume. - vol.bounding_box = vol.indexed_vertex_array.bounding_box(); - // Clear the buffers, but keep them pre-allocated. - vol_new.indexed_vertex_array.clear(); - // Just make sure that clear did not clear the reserved memory. - vol_new.indexed_vertex_array.reserve(ctxt.alloc_size_reserve()); - } - } - } - for (GLVolume *vol : vols) { - vol->bounding_box = vol->indexed_vertex_array.bounding_box(); - vol->indexed_vertex_array.shrink_to_fit(); - } - }); - - BOOST_LOG_TRIVIAL(debug) << "Loading print object toolpaths in parallel - finalizing results"; - // Remove empty volumes from the newly added volumes. - m_volumes.volumes.erase( - std::remove_if(m_volumes.volumes.begin() + volumes_cnt_initial, m_volumes.volumes.end(), - [](const GLVolume *volume) { return volume->empty(); }), - m_volumes.volumes.end()); - for (size_t i = volumes_cnt_initial; i < m_volumes.volumes.size(); ++i) - m_volumes.volumes[i]->indexed_vertex_array.finalize_geometry(m_use_VBOs && m_initialized); - - BOOST_LOG_TRIVIAL(debug) << "Loading print object toolpaths in parallel - end"; -} - -void GLCanvas3D::load_wipe_tower_toolpaths(const std::vector<std::string>& str_tool_colors) -{ - if ((m_print == nullptr) || m_print->m_wipe_tower_tool_changes.empty()) - return; - - if (!m_print->state.is_done(psWipeTower)) - return; - - std::vector<float> tool_colors = _parse_colors(str_tool_colors); - - struct Ctxt - { - const Print *print; - const std::vector<float> *tool_colors; - - // Number of vertices (each vertex is 6x4=24 bytes long) - static const size_t alloc_size_max() { return 131072; } // 3.15MB - static const size_t alloc_size_reserve() { return alloc_size_max() * 2; } - - static const float* color_support() { static float color[4] = { 0.5f, 1.0f, 0.5f, 1.f }; return color; } // greenish - - // For cloring by a tool, return a parsed color. - bool color_by_tool() const { return tool_colors != nullptr; } - size_t number_tools() const { return this->color_by_tool() ? tool_colors->size() / 4 : 0; } - const float* color_tool(size_t tool) const { return tool_colors->data() + tool * 4; } - int volume_idx(int tool, int feature) const - { - return this->color_by_tool() ? std::min<int>(this->number_tools() - 1, std::max<int>(tool, 0)) : feature; - } - - const std::vector<WipeTower::ToolChangeResult>& tool_change(size_t idx) { - return priming.empty() ? - ((idx == print->m_wipe_tower_tool_changes.size()) ? final : print->m_wipe_tower_tool_changes[idx]) : - ((idx == 0) ? priming : (idx == print->m_wipe_tower_tool_changes.size() + 1) ? final : print->m_wipe_tower_tool_changes[idx - 1]); - } - std::vector<WipeTower::ToolChangeResult> priming; - std::vector<WipeTower::ToolChangeResult> final; - } ctxt; - - ctxt.print = m_print; - ctxt.tool_colors = tool_colors.empty() ? nullptr : &tool_colors; - if (m_print->m_wipe_tower_priming) - ctxt.priming.emplace_back(*m_print->m_wipe_tower_priming.get()); - if (m_print->m_wipe_tower_final_purge) - ctxt.final.emplace_back(*m_print->m_wipe_tower_final_purge.get()); - - BOOST_LOG_TRIVIAL(debug) << "Loading wipe tower toolpaths in parallel - start"; - - //FIXME Improve the heuristics for a grain size. - size_t n_items = m_print->m_wipe_tower_tool_changes.size() + (ctxt.priming.empty() ? 0 : 1); - size_t grain_size = std::max(n_items / 128, size_t(1)); - tbb::spin_mutex new_volume_mutex; - auto new_volume = [this, &new_volume_mutex](const float *color) -> GLVolume* { - auto *volume = new GLVolume(color); - new_volume_mutex.lock(); - volume->outside_printer_detection_enabled = false; - m_volumes.volumes.emplace_back(volume); - new_volume_mutex.unlock(); - return volume; - }; - const size_t volumes_cnt_initial = m_volumes.volumes.size(); - std::vector<GLVolumeCollection> volumes_per_thread(n_items); - tbb::parallel_for( - tbb::blocked_range<size_t>(0, n_items, grain_size), - [&ctxt, &new_volume](const tbb::blocked_range<size_t>& range) { - // Bounding box of this slab of a wipe tower. - std::vector<GLVolume*> vols; - if (ctxt.color_by_tool()) { - for (size_t i = 0; i < ctxt.number_tools(); ++i) - vols.emplace_back(new_volume(ctxt.color_tool(i))); - } - else - vols = { new_volume(ctxt.color_support()) }; - for (GLVolume *volume : vols) - volume->indexed_vertex_array.reserve(ctxt.alloc_size_reserve()); - for (size_t idx_layer = range.begin(); idx_layer < range.end(); ++idx_layer) { - const std::vector<WipeTower::ToolChangeResult> &layer = ctxt.tool_change(idx_layer); - for (size_t i = 0; i < vols.size(); ++i) { - GLVolume &vol = *vols[i]; - if (vol.print_zs.empty() || vol.print_zs.back() != layer.front().print_z) { - vol.print_zs.push_back(layer.front().print_z); - vol.offsets.push_back(vol.indexed_vertex_array.quad_indices.size()); - vol.offsets.push_back(vol.indexed_vertex_array.triangle_indices.size()); - } - } - for (const WipeTower::ToolChangeResult &extrusions : layer) { - for (size_t i = 1; i < extrusions.extrusions.size();) { - const WipeTower::Extrusion &e = extrusions.extrusions[i]; - if (e.width == 0.) { - ++i; - continue; - } - size_t j = i + 1; - if (ctxt.color_by_tool()) - for (; j < extrusions.extrusions.size() && extrusions.extrusions[j].tool == e.tool && extrusions.extrusions[j].width > 0.f; ++j); - else - for (; j < extrusions.extrusions.size() && extrusions.extrusions[j].width > 0.f; ++j); - size_t n_lines = j - i; - Lines lines; - std::vector<double> widths; - std::vector<double> heights; - lines.reserve(n_lines); - widths.reserve(n_lines); - heights.assign(n_lines, extrusions.layer_height); - for (; i < j; ++i) { - const WipeTower::Extrusion &e = extrusions.extrusions[i]; - assert(e.width > 0.f); - const WipeTower::Extrusion &e_prev = *(&e - 1); - lines.emplace_back(Point::new_scale(e_prev.pos.x, e_prev.pos.y), Point::new_scale(e.pos.x, e.pos.y)); - widths.emplace_back(e.width); - } - _3DScene::thick_lines_to_verts(lines, widths, heights, lines.front().a == lines.back().b, extrusions.print_z, - *vols[ctxt.volume_idx(e.tool, 0)]); - } - } - } - for (size_t i = 0; i < vols.size(); ++i) { - GLVolume &vol = *vols[i]; - if (vol.indexed_vertex_array.vertices_and_normals_interleaved.size() / 6 > ctxt.alloc_size_max()) { - // Store the vertex arrays and restart their containers, - vols[i] = new_volume(vol.color); - GLVolume &vol_new = *vols[i]; - // Assign the large pre-allocated buffers to the new GLVolume. - vol_new.indexed_vertex_array = std::move(vol.indexed_vertex_array); - // Copy the content back to the old GLVolume. - vol.indexed_vertex_array = vol_new.indexed_vertex_array; - // Finalize a bounding box of the old GLVolume. - vol.bounding_box = vol.indexed_vertex_array.bounding_box(); - // Clear the buffers, but keep them pre-allocated. - vol_new.indexed_vertex_array.clear(); - // Just make sure that clear did not clear the reserved memory. - vol_new.indexed_vertex_array.reserve(ctxt.alloc_size_reserve()); - } - } - for (GLVolume *vol : vols) { - vol->bounding_box = vol->indexed_vertex_array.bounding_box(); - vol->indexed_vertex_array.shrink_to_fit(); - } - }); - - BOOST_LOG_TRIVIAL(debug) << "Loading wipe tower toolpaths in parallel - finalizing results"; - // Remove empty volumes from the newly added volumes. - m_volumes.volumes.erase( - std::remove_if(m_volumes.volumes.begin() + volumes_cnt_initial, m_volumes.volumes.end(), - [](const GLVolume *volume) { return volume->empty(); }), - m_volumes.volumes.end()); - for (size_t i = volumes_cnt_initial; i < m_volumes.volumes.size(); ++i) - m_volumes.volumes[i]->indexed_vertex_array.finalize_geometry(m_use_VBOs && m_initialized); - - BOOST_LOG_TRIVIAL(debug) << "Loading wipe tower toolpaths in parallel - end"; -} - void GLCanvas3D::load_gcode_preview(const GCodePreviewData& preview_data, const std::vector<std::string>& str_tool_colors) { if ((m_canvas != nullptr) && (m_print != nullptr)) @@ -2723,12 +2368,37 @@ void GLCanvas3D::load_gcode_preview(const GCodePreviewData& preview_data, const _load_shells(); } + _update_toolpath_volumes_outside_state(); } _update_gcode_volumes_visibility(preview_data); + _show_warning_texture_if_needed(); } } +void GLCanvas3D::load_preview(const std::vector<std::string>& str_tool_colors) +{ + if (m_print == nullptr) + return; + + _load_print_toolpaths(); + _load_wipe_tower_toolpaths(str_tool_colors); + for (const PrintObject* object : m_print->objects) + { + if (object != nullptr) + _load_print_object_toolpaths(*object, str_tool_colors); + } + + for (GLVolume* volume : m_volumes.volumes) + { + volume->is_extrusion_path = true; + } + + _update_toolpath_volumes_outside_state(); + _show_warning_texture_if_needed(); + reset_legend_texture(); +} + void GLCanvas3D::register_on_viewport_changed_callback(void* callback) { if (callback != nullptr) @@ -3025,6 +2695,12 @@ void GLCanvas3D::on_mouse(wxMouseEvent& evt) m_mouse.set_start_position_2D_as_invalid(); #endif } + else if (evt.Leaving()) + { + // to remove hover when mouse goes out of this canvas + m_mouse.position = Pointf((coordf_t)pos.x, (coordf_t)pos.y); + render(); + } else if (evt.LeftDClick() && (m_hover_volume_id != -1)) m_on_double_click_callback.call(); else if (evt.LeftDown() || evt.RightDown()) @@ -3062,7 +2738,7 @@ void GLCanvas3D::on_mouse(wxMouseEvent& evt) { update_gizmos_data(); m_gizmos.start_dragging(); - m_mouse.drag.gizmo_volume_idx = _get_first_selected_volume_id(); + m_mouse.drag.gizmo_volume_idx = _get_first_selected_volume_id(selected_object_idx); m_dirty = true; } else @@ -3163,7 +2839,7 @@ void GLCanvas3D::on_mouse(wxMouseEvent& evt) { if (v != nullptr) { - if ((m_mouse.drag.move_with_shift && (v->select_group_id == group_id)) || (v->drag_group_id == group_id)) + if ((m_mouse.drag.move_with_shift && (v->select_group_id == group_id)) || (!m_mouse.drag.move_with_shift && (v->drag_group_id == group_id))) volumes.push_back(v); } } @@ -3234,7 +2910,11 @@ void GLCanvas3D::on_mouse(wxMouseEvent& evt) if (!volumes.empty()) { - const BoundingBoxf3& bb = volumes[0]->transformed_bounding_box(); + BoundingBoxf3 bb; + for (const GLVolume* volume : volumes) + { + bb.merge(volume->transformed_bounding_box()); + } const Pointf3& size = bb.size(); m_on_update_geometry_info_callback.call(size.x, size.y, size.z, m_gizmos.get_scale()); } @@ -3381,7 +3061,13 @@ void GLCanvas3D::on_key_down(wxKeyEvent& evt) if (key == WXK_DELETE) m_on_remove_object_callback.call(); else - evt.Skip(); + { +#ifdef __WXOSX__ + if (key == WXK_BACK) + m_on_remove_object_callback.call(); +#endif + evt.Skip(); + } } } @@ -3502,11 +3188,45 @@ BoundingBoxf3 GLCanvas3D::_max_bounding_box() const BoundingBoxf3 GLCanvas3D::_selected_volumes_bounding_box() const { BoundingBoxf3 bb; + + std::vector<const GLVolume*> selected_volumes; for (const GLVolume* volume : m_volumes.volumes) { if ((volume != nullptr) && !volume->is_wipe_tower && volume->selected) + selected_volumes.push_back(volume); + } + + bool use_drag_group_id = selected_volumes.size() > 1; + if (use_drag_group_id) + { + int drag_group_id = selected_volumes[0]->drag_group_id; + for (const GLVolume* volume : selected_volumes) + { + if (drag_group_id != volume->drag_group_id) + { + use_drag_group_id = false; + break; + } + } + } + + if (use_drag_group_id) + { + for (const GLVolume* volume : selected_volumes) + { + bb.merge(volume->bounding_box); + } + + bb = bb.transformed(selected_volumes[0]->world_matrix()); + } + else + { + for (const GLVolume* volume : selected_volumes) + { bb.merge(volume->transformed_bounding_box()); + } } + return bb; } @@ -3726,8 +3446,6 @@ void GLCanvas3D::_render_background() const { ::glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); - static const float COLOR[3] = { 10.0f / 255.0f, 98.0f / 255.0f, 144.0f / 255.0f }; - ::glPushMatrix(); ::glLoadIdentity(); ::glMatrixMode(GL_PROJECTION); @@ -3739,11 +3457,16 @@ void GLCanvas3D::_render_background() const ::glBegin(GL_QUADS); ::glColor3f(0.0f, 0.0f, 0.0f); - ::glVertex3f(-1.0f, -1.0f, 1.0f); - ::glVertex3f(1.0f, -1.0f, 1.0f); - ::glColor3f(COLOR[0], COLOR[1], COLOR[2]); - ::glVertex3f(1.0f, 1.0f, 1.0f); - ::glVertex3f(-1.0f, 1.0f, 1.0f); + ::glVertex2f(-1.0f, -1.0f); + ::glVertex2f(1.0f, -1.0f); + + if (m_dynamic_background_enabled && _is_any_volume_outside()) + ::glColor3f(ERROR_BG_COLOR[0], ERROR_BG_COLOR[1], ERROR_BG_COLOR[2]); + else + ::glColor3f(DEFAULT_BG_COLOR[0], DEFAULT_BG_COLOR[1], DEFAULT_BG_COLOR[2]); + + ::glVertex2f(1.0f, 1.0f); + ::glVertex2f(-1.0f, 1.0f); ::glEnd(); ::glEnable(GL_DEPTH_TEST); @@ -4083,33 +3806,382 @@ int GLCanvas3D::_get_first_selected_object_id() const return -1; } -int GLCanvas3D::_get_first_selected_volume_id() const +int GLCanvas3D::_get_first_selected_volume_id(int object_id) const { - if (m_print != nullptr) + int volume_id = -1; + + for (const GLVolume* vol : m_volumes.volumes) { - int objects_count = (int)m_print->objects.size(); + ++volume_id; + if ((vol != nullptr) && vol->selected && (object_id == vol->select_group_id / 1000000)) + return volume_id; + } - for (const GLVolume* vol : m_volumes.volumes) + return -1; +} + +void GLCanvas3D::_load_print_toolpaths() +{ + // ensures this canvas is current + if (!set_current()) + return; + + if (m_print == nullptr) + return; + + if (!m_print->state.is_done(psSkirt) || !m_print->state.is_done(psBrim)) + return; + + if (!m_print->has_skirt() && (m_print->config.brim_width.value == 0)) + return; + + const float color[] = { 0.5f, 1.0f, 0.5f, 1.0f }; // greenish + + // number of skirt layers + size_t total_layer_count = 0; + for (const PrintObject* print_object : m_print->objects) + { + total_layer_count = std::max(total_layer_count, print_object->total_layer_count()); + } + size_t skirt_height = m_print->has_infinite_skirt() ? total_layer_count : std::min<size_t>(m_print->config.skirt_height.value, total_layer_count); + if ((skirt_height == 0) && (m_print->config.brim_width.value > 0)) + skirt_height = 1; + + // get first skirt_height layers (maybe this should be moved to a PrintObject method?) + const PrintObject* object0 = m_print->objects.front(); + std::vector<float> print_zs; + print_zs.reserve(skirt_height * 2); + for (size_t i = 0; i < std::min(skirt_height, object0->layers.size()); ++i) + { + print_zs.push_back(float(object0->layers[i]->print_z)); + } + //FIXME why there are support layers? + for (size_t i = 0; i < std::min(skirt_height, object0->support_layers.size()); ++i) + { + print_zs.push_back(float(object0->support_layers[i]->print_z)); + } + sort_remove_duplicates(print_zs); + if (print_zs.size() > skirt_height) + print_zs.erase(print_zs.begin() + skirt_height, print_zs.end()); + + m_volumes.volumes.emplace_back(new GLVolume(color)); + GLVolume& volume = *m_volumes.volumes.back(); + for (size_t i = 0; i < skirt_height; ++i) { + volume.print_zs.push_back(print_zs[i]); + volume.offsets.push_back(volume.indexed_vertex_array.quad_indices.size()); + volume.offsets.push_back(volume.indexed_vertex_array.triangle_indices.size()); + if (i == 0) + _3DScene::extrusionentity_to_verts(m_print->brim, print_zs[i], Point(0, 0), volume); + + _3DScene::extrusionentity_to_verts(m_print->skirt, print_zs[i], Point(0, 0), volume); + } + volume.bounding_box = volume.indexed_vertex_array.bounding_box(); + volume.indexed_vertex_array.finalize_geometry(m_use_VBOs && m_initialized); +} + +void GLCanvas3D::_load_print_object_toolpaths(const PrintObject& print_object, const std::vector<std::string>& str_tool_colors) +{ + std::vector<float> tool_colors = _parse_colors(str_tool_colors); + + struct Ctxt + { + const Points *shifted_copies; + std::vector<const Layer*> layers; + bool has_perimeters; + bool has_infill; + bool has_support; + const std::vector<float>* tool_colors; + + // Number of vertices (each vertex is 6x4=24 bytes long) + static const size_t alloc_size_max() { return 131072; } // 3.15MB + // static const size_t alloc_size_max () { return 65536; } // 1.57MB + // static const size_t alloc_size_max () { return 32768; } // 786kB + static const size_t alloc_size_reserve() { return alloc_size_max() * 2; } + + static const float* color_perimeters() { static float color[4] = { 1.0f, 1.0f, 0.0f, 1.f }; return color; } // yellow + static const float* color_infill() { static float color[4] = { 1.0f, 0.5f, 0.5f, 1.f }; return color; } // redish + static const float* color_support() { static float color[4] = { 0.5f, 1.0f, 0.5f, 1.f }; return color; } // greenish + + // For cloring by a tool, return a parsed color. + bool color_by_tool() const { return tool_colors != nullptr; } + size_t number_tools() const { return this->color_by_tool() ? tool_colors->size() / 4 : 0; } + const float* color_tool(size_t tool) const { return tool_colors->data() + tool * 4; } + int volume_idx(int extruder, int feature) const { - if ((vol != nullptr) && vol->selected) - { - int object_id = vol->select_group_id / 1000000; - // Objects with object_id >= 1000 have a specific meaning, for example the wipe tower proxy. - if ((object_id < 10000) && (object_id < objects_count)) - { - int volume_id = 0; - for (int i = 0; i < object_id; ++i) - { - const PrintObject* obj = m_print->objects[i]; - const ModelObject* model = obj->model_object(); - volume_id += model->instances.size(); + return this->color_by_tool() ? std::min<int>(this->number_tools() - 1, std::max<int>(extruder - 1, 0)) : feature; + } + } ctxt; + + ctxt.shifted_copies = &print_object._shifted_copies; + + // order layers by print_z + ctxt.layers.reserve(print_object.layers.size() + print_object.support_layers.size()); + for (const Layer *layer : print_object.layers) + ctxt.layers.push_back(layer); + for (const Layer *layer : print_object.support_layers) + ctxt.layers.push_back(layer); + std::sort(ctxt.layers.begin(), ctxt.layers.end(), [](const Layer *l1, const Layer *l2) { return l1->print_z < l2->print_z; }); + + // Maximum size of an allocation block: 32MB / sizeof(float) + ctxt.has_perimeters = print_object.state.is_done(posPerimeters); + ctxt.has_infill = print_object.state.is_done(posInfill); + ctxt.has_support = print_object.state.is_done(posSupportMaterial); + ctxt.tool_colors = tool_colors.empty() ? nullptr : &tool_colors; + + BOOST_LOG_TRIVIAL(debug) << "Loading print object toolpaths in parallel - start"; + + //FIXME Improve the heuristics for a grain size. + size_t grain_size = std::max(ctxt.layers.size() / 16, size_t(1)); + tbb::spin_mutex new_volume_mutex; + auto new_volume = [this, &new_volume_mutex](const float *color) -> GLVolume* { + auto *volume = new GLVolume(color); + new_volume_mutex.lock(); + m_volumes.volumes.emplace_back(volume); + new_volume_mutex.unlock(); + return volume; + }; + const size_t volumes_cnt_initial = m_volumes.volumes.size(); + std::vector<GLVolumeCollection> volumes_per_thread(ctxt.layers.size()); + tbb::parallel_for( + tbb::blocked_range<size_t>(0, ctxt.layers.size(), grain_size), + [&ctxt, &new_volume](const tbb::blocked_range<size_t>& range) { + std::vector<GLVolume*> vols; + if (ctxt.color_by_tool()) { + for (size_t i = 0; i < ctxt.number_tools(); ++i) + vols.emplace_back(new_volume(ctxt.color_tool(i))); + } + else + vols = { new_volume(ctxt.color_perimeters()), new_volume(ctxt.color_infill()), new_volume(ctxt.color_support()) }; + for (GLVolume *vol : vols) + vol->indexed_vertex_array.reserve(ctxt.alloc_size_reserve()); + for (size_t idx_layer = range.begin(); idx_layer < range.end(); ++idx_layer) { + const Layer *layer = ctxt.layers[idx_layer]; + for (size_t i = 0; i < vols.size(); ++i) { + GLVolume &vol = *vols[i]; + if (vol.print_zs.empty() || vol.print_zs.back() != layer->print_z) { + vol.print_zs.push_back(layer->print_z); + vol.offsets.push_back(vol.indexed_vertex_array.quad_indices.size()); + vol.offsets.push_back(vol.indexed_vertex_array.triangle_indices.size()); + } + } + for (const Point © : *ctxt.shifted_copies) { + for (const LayerRegion *layerm : layer->regions) { + if (ctxt.has_perimeters) + _3DScene::extrusionentity_to_verts(layerm->perimeters, float(layer->print_z), copy, + *vols[ctxt.volume_idx(layerm->region()->config.perimeter_extruder.value, 0)]); + if (ctxt.has_infill) { + for (const ExtrusionEntity *ee : layerm->fills.entities) { + // fill represents infill extrusions of a single island. + const auto *fill = dynamic_cast<const ExtrusionEntityCollection*>(ee); + if (!fill->entities.empty()) + _3DScene::extrusionentity_to_verts(*fill, float(layer->print_z), copy, + *vols[ctxt.volume_idx( + is_solid_infill(fill->entities.front()->role()) ? + layerm->region()->config.solid_infill_extruder : + layerm->region()->config.infill_extruder, + 1)]); + } + } + } + if (ctxt.has_support) { + const SupportLayer *support_layer = dynamic_cast<const SupportLayer*>(layer); + if (support_layer) { + for (const ExtrusionEntity *extrusion_entity : support_layer->support_fills.entities) + _3DScene::extrusionentity_to_verts(extrusion_entity, float(layer->print_z), copy, + *vols[ctxt.volume_idx( + (extrusion_entity->role() == erSupportMaterial) ? + support_layer->object()->config.support_material_extruder : + support_layer->object()->config.support_material_interface_extruder, + 2)]); + } + } + } + for (size_t i = 0; i < vols.size(); ++i) { + GLVolume &vol = *vols[i]; + if (vol.indexed_vertex_array.vertices_and_normals_interleaved.size() / 6 > ctxt.alloc_size_max()) { + // Store the vertex arrays and restart their containers, + vols[i] = new_volume(vol.color); + GLVolume &vol_new = *vols[i]; + // Assign the large pre-allocated buffers to the new GLVolume. + vol_new.indexed_vertex_array = std::move(vol.indexed_vertex_array); + // Copy the content back to the old GLVolume. + vol.indexed_vertex_array = vol_new.indexed_vertex_array; + // Finalize a bounding box of the old GLVolume. + vol.bounding_box = vol.indexed_vertex_array.bounding_box(); + // Clear the buffers, but keep them pre-allocated. + vol_new.indexed_vertex_array.clear(); + // Just make sure that clear did not clear the reserved memory. + vol_new.indexed_vertex_array.reserve(ctxt.alloc_size_reserve()); + } + } + } + for (GLVolume *vol : vols) { + vol->bounding_box = vol->indexed_vertex_array.bounding_box(); + vol->indexed_vertex_array.shrink_to_fit(); + } + }); + + BOOST_LOG_TRIVIAL(debug) << "Loading print object toolpaths in parallel - finalizing results"; + // Remove empty volumes from the newly added volumes. + m_volumes.volumes.erase( + std::remove_if(m_volumes.volumes.begin() + volumes_cnt_initial, m_volumes.volumes.end(), + [](const GLVolume *volume) { return volume->empty(); }), + m_volumes.volumes.end()); + for (size_t i = volumes_cnt_initial; i < m_volumes.volumes.size(); ++i) + m_volumes.volumes[i]->indexed_vertex_array.finalize_geometry(m_use_VBOs && m_initialized); + + BOOST_LOG_TRIVIAL(debug) << "Loading print object toolpaths in parallel - end"; +} + +void GLCanvas3D::_load_wipe_tower_toolpaths(const std::vector<std::string>& str_tool_colors) +{ + if ((m_print == nullptr) || m_print->m_wipe_tower_tool_changes.empty()) + return; + + if (!m_print->state.is_done(psWipeTower)) + return; + + std::vector<float> tool_colors = _parse_colors(str_tool_colors); + + struct Ctxt + { + const Print *print; + const std::vector<float> *tool_colors; + + // Number of vertices (each vertex is 6x4=24 bytes long) + static const size_t alloc_size_max() { return 131072; } // 3.15MB + static const size_t alloc_size_reserve() { return alloc_size_max() * 2; } + + static const float* color_support() { static float color[4] = { 0.5f, 1.0f, 0.5f, 1.f }; return color; } // greenish + + // For cloring by a tool, return a parsed color. + bool color_by_tool() const { return tool_colors != nullptr; } + size_t number_tools() const { return this->color_by_tool() ? tool_colors->size() / 4 : 0; } + const float* color_tool(size_t tool) const { return tool_colors->data() + tool * 4; } + int volume_idx(int tool, int feature) const + { + return this->color_by_tool() ? std::min<int>(this->number_tools() - 1, std::max<int>(tool, 0)) : feature; + } + + const std::vector<WipeTower::ToolChangeResult>& tool_change(size_t idx) { + return priming.empty() ? + ((idx == print->m_wipe_tower_tool_changes.size()) ? final : print->m_wipe_tower_tool_changes[idx]) : + ((idx == 0) ? priming : (idx == print->m_wipe_tower_tool_changes.size() + 1) ? final : print->m_wipe_tower_tool_changes[idx - 1]); + } + std::vector<WipeTower::ToolChangeResult> priming; + std::vector<WipeTower::ToolChangeResult> final; + } ctxt; + + ctxt.print = m_print; + ctxt.tool_colors = tool_colors.empty() ? nullptr : &tool_colors; + if (m_print->m_wipe_tower_priming) + ctxt.priming.emplace_back(*m_print->m_wipe_tower_priming.get()); + if (m_print->m_wipe_tower_final_purge) + ctxt.final.emplace_back(*m_print->m_wipe_tower_final_purge.get()); + + BOOST_LOG_TRIVIAL(debug) << "Loading wipe tower toolpaths in parallel - start"; + + //FIXME Improve the heuristics for a grain size. + size_t n_items = m_print->m_wipe_tower_tool_changes.size() + (ctxt.priming.empty() ? 0 : 1); + size_t grain_size = std::max(n_items / 128, size_t(1)); + tbb::spin_mutex new_volume_mutex; + auto new_volume = [this, &new_volume_mutex](const float *color) -> GLVolume* { + auto *volume = new GLVolume(color); + new_volume_mutex.lock(); + m_volumes.volumes.emplace_back(volume); + new_volume_mutex.unlock(); + return volume; + }; + const size_t volumes_cnt_initial = m_volumes.volumes.size(); + std::vector<GLVolumeCollection> volumes_per_thread(n_items); + tbb::parallel_for( + tbb::blocked_range<size_t>(0, n_items, grain_size), + [&ctxt, &new_volume](const tbb::blocked_range<size_t>& range) { + // Bounding box of this slab of a wipe tower. + std::vector<GLVolume*> vols; + if (ctxt.color_by_tool()) { + for (size_t i = 0; i < ctxt.number_tools(); ++i) + vols.emplace_back(new_volume(ctxt.color_tool(i))); + } + else + vols = { new_volume(ctxt.color_support()) }; + for (GLVolume *volume : vols) + volume->indexed_vertex_array.reserve(ctxt.alloc_size_reserve()); + for (size_t idx_layer = range.begin(); idx_layer < range.end(); ++idx_layer) { + const std::vector<WipeTower::ToolChangeResult> &layer = ctxt.tool_change(idx_layer); + for (size_t i = 0; i < vols.size(); ++i) { + GLVolume &vol = *vols[i]; + if (vol.print_zs.empty() || vol.print_zs.back() != layer.front().print_z) { + vol.print_zs.push_back(layer.front().print_z); + vol.offsets.push_back(vol.indexed_vertex_array.quad_indices.size()); + vol.offsets.push_back(vol.indexed_vertex_array.triangle_indices.size()); + } + } + for (const WipeTower::ToolChangeResult &extrusions : layer) { + for (size_t i = 1; i < extrusions.extrusions.size();) { + const WipeTower::Extrusion &e = extrusions.extrusions[i]; + if (e.width == 0.) { + ++i; + continue; } - return volume_id; + size_t j = i + 1; + if (ctxt.color_by_tool()) + for (; j < extrusions.extrusions.size() && extrusions.extrusions[j].tool == e.tool && extrusions.extrusions[j].width > 0.f; ++j); + else + for (; j < extrusions.extrusions.size() && extrusions.extrusions[j].width > 0.f; ++j); + size_t n_lines = j - i; + Lines lines; + std::vector<double> widths; + std::vector<double> heights; + lines.reserve(n_lines); + widths.reserve(n_lines); + heights.assign(n_lines, extrusions.layer_height); + for (; i < j; ++i) { + const WipeTower::Extrusion &e = extrusions.extrusions[i]; + assert(e.width > 0.f); + const WipeTower::Extrusion &e_prev = *(&e - 1); + lines.emplace_back(Point::new_scale(e_prev.pos.x, e_prev.pos.y), Point::new_scale(e.pos.x, e.pos.y)); + widths.emplace_back(e.width); + } + _3DScene::thick_lines_to_verts(lines, widths, heights, lines.front().a == lines.back().b, extrusions.print_z, + *vols[ctxt.volume_idx(e.tool, 0)]); } } } - } - return -1; + for (size_t i = 0; i < vols.size(); ++i) { + GLVolume &vol = *vols[i]; + if (vol.indexed_vertex_array.vertices_and_normals_interleaved.size() / 6 > ctxt.alloc_size_max()) { + // Store the vertex arrays and restart their containers, + vols[i] = new_volume(vol.color); + GLVolume &vol_new = *vols[i]; + // Assign the large pre-allocated buffers to the new GLVolume. + vol_new.indexed_vertex_array = std::move(vol.indexed_vertex_array); + // Copy the content back to the old GLVolume. + vol.indexed_vertex_array = vol_new.indexed_vertex_array; + // Finalize a bounding box of the old GLVolume. + vol.bounding_box = vol.indexed_vertex_array.bounding_box(); + // Clear the buffers, but keep them pre-allocated. + vol_new.indexed_vertex_array.clear(); + // Just make sure that clear did not clear the reserved memory. + vol_new.indexed_vertex_array.reserve(ctxt.alloc_size_reserve()); + } + } + for (GLVolume *vol : vols) { + vol->bounding_box = vol->indexed_vertex_array.bounding_box(); + vol->indexed_vertex_array.shrink_to_fit(); + } + }); + + BOOST_LOG_TRIVIAL(debug) << "Loading wipe tower toolpaths in parallel - finalizing results"; + // Remove empty volumes from the newly added volumes. + m_volumes.volumes.erase( + std::remove_if(m_volumes.volumes.begin() + volumes_cnt_initial, m_volumes.volumes.end(), + [](const GLVolume *volume) { return volume->empty(); }), + m_volumes.volumes.end()); + for (size_t i = volumes_cnt_initial; i < m_volumes.volumes.size(); ++i) + m_volumes.volumes[i]->indexed_vertex_array.finalize_geometry(m_use_VBOs && m_initialized); + + BOOST_LOG_TRIVIAL(debug) << "Loading wipe tower toolpaths in parallel - end"; } static inline int hex_digit_to_int(const char c) @@ -4230,6 +4302,7 @@ void GLCanvas3D::_load_gcode_extrusion_paths(const GCodePreviewData& preview_dat if (volume != nullptr) { filter.volume = volume; + volume->is_extrusion_path = true; m_volumes.volumes.emplace_back(volume); } else @@ -4643,7 +4716,6 @@ void GLCanvas3D::_update_gcode_volumes_visibility(const GCodePreviewData& previe for (std::vector<GLVolume*>::iterator it = begin; it != end; ++it) { GLVolume* volume = *it; - volume->outside_printer_detection_enabled = false; switch (m_gcode_preview_volume_index.first_volumes[i].type) { @@ -4691,6 +4763,45 @@ void GLCanvas3D::_update_gcode_volumes_visibility(const GCodePreviewData& previe } } +void GLCanvas3D::_update_toolpath_volumes_outside_state() +{ + // tolerance to avoid false detection at bed edges + static const coordf_t tolerance_x = 0.05; + static const coordf_t tolerance_y = 0.05; + + BoundingBoxf3 print_volume; + if (m_config != nullptr) + { + const ConfigOptionPoints* opt = dynamic_cast<const ConfigOptionPoints*>(m_config->option("bed_shape")); + if (opt != nullptr) + { + BoundingBox bed_box_2D = get_extents(Polygon::new_scale(opt->values)); + print_volume = BoundingBoxf3(Pointf3(unscale(bed_box_2D.min.x) - tolerance_x, unscale(bed_box_2D.min.y) - tolerance_y, 0.0), Pointf3(unscale(bed_box_2D.max.x) + tolerance_x, unscale(bed_box_2D.max.y) + tolerance_y, m_config->opt_float("max_print_height"))); + // Allow the objects to protrude below the print bed + print_volume.min.z = -1e10; + } + } + + for (GLVolume* volume : m_volumes.volumes) + { + volume->is_outside = ((print_volume.radius() > 0.0) && volume->is_extrusion_path) ? !print_volume.contains(volume->bounding_box) : false; + } +} + +void GLCanvas3D::_show_warning_texture_if_needed() +{ + if (_is_any_volume_outside()) + { + enable_warning_texture(true); + _generate_warning_texture(L("Detected toolpath outside print volume")); + } + else + { + enable_warning_texture(false); + _reset_warning_texture(); + } +} + void GLCanvas3D::_on_move(const std::vector<int>& volume_idxs) { if (m_model == nullptr) @@ -4796,5 +4907,16 @@ void GLCanvas3D::_reset_warning_texture() m_warning_texture.reset(); } +bool GLCanvas3D::_is_any_volume_outside() const +{ + for (const GLVolume* volume : m_volumes.volumes) + { + if ((volume != nullptr) && volume->is_outside) + return true; + } + + return false; +} + } // namespace GUI } // namespace Slic3r diff --git a/xs/src/slic3r/GUI/GLCanvas3D.hpp b/xs/src/slic3r/GUI/GLCanvas3D.hpp index a9a6117ec..a87486261 100644 --- a/xs/src/slic3r/GUI/GLCanvas3D.hpp +++ b/xs/src/slic3r/GUI/GLCanvas3D.hpp @@ -449,6 +449,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; @@ -539,6 +540,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 +561,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 +644,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 +671,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 +684,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); }; diff --git a/xs/src/slic3r/GUI/GLCanvas3DManager.cpp b/xs/src/slic3r/GUI/GLCanvas3DManager.cpp index 5e9048d54..5249f6dc4 100644 --- a/xs/src/slic3r/GUI/GLCanvas3DManager.cpp +++ b/xs/src/slic3r/GUI/GLCanvas3DManager.cpp @@ -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,38 +523,21 @@ 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) +void GLCanvas3DManager::load_gcode_preview(wxGLCanvas* canvas, const GCodePreviewData* preview_data, const std::vector<std::string>& str_tool_colors) { - if (print_object == nullptr) + if (preview_data == nullptr) return; CanvasesMap::iterator it = _get_canvas(canvas); if (it != m_canvases.end()) - it->second->load_print_object_toolpaths(*print_object, tool_colors); + it->second->load_gcode_preview(*preview_data, str_tool_colors); } -void GLCanvas3DManager::load_wipe_tower_toolpaths(wxGLCanvas* canvas, const std::vector<std::string>& 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_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) - return; - - CanvasesMap::iterator it = _get_canvas(canvas); - if (it != m_canvases.end()) - it->second->load_gcode_preview(*preview_data, str_tool_colors); + it->second->load_preview(str_tool_colors); } void GLCanvas3DManager::reset_legend_texture() diff --git a/xs/src/slic3r/GUI/GLCanvas3DManager.hpp b/xs/src/slic3r/GUI/GLCanvas3DManager.hpp index 32aa712d3..55c42fda6 100644 --- a/xs/src/slic3r/GUI/GLCanvas3DManager.hpp +++ b/xs/src/slic3r/GUI/GLCanvas3DManager.hpp @@ -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(); diff --git a/xs/src/slic3r/GUI/GUI.cpp b/xs/src/slic3r/GUI/GUI.cpp index af7022f2b..9c62fe8eb 100644 --- a/xs/src/slic3r/GUI/GUI.cpp +++ b/xs/src/slic3r/GUI/GUI.cpp @@ -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; } } diff --git a/xs/src/slic3r/GUI/Preset.cpp b/xs/src/slic3r/GUI/Preset.cpp index 54a5c90fe..49e235146 100644 --- a/xs/src/slic3r/GUI/Preset.cpp +++ b/xs/src/slic3r/GUI/Preset.cpp @@ -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) diff --git a/xs/src/slic3r/GUI/Preset.hpp b/xs/src/slic3r/GUI/Preset.hpp index a2ee1d2eb..0d00cae48 100644 --- a/xs/src/slic3r/GUI/Preset.hpp +++ b/xs/src/slic3r/GUI/Preset.hpp @@ -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(); } diff --git a/xs/src/slic3r/GUI/PresetBundle.cpp b/xs/src/slic3r/GUI/PresetBundle.cpp index 58553e1bc..94baa0e0e 100644 --- a/xs/src/slic3r/GUI/PresetBundle.cpp +++ b/xs/src/slic3r/GUI/PresetBundle.cpp @@ -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. diff --git a/xs/src/slic3r/GUI/Tab.cpp b/xs/src/slic3r/GUI/Tab.cpp index 5d1c51eb3..187030f31 100644 --- a/xs/src/slic3r/GUI/Tab.cpp +++ b/xs/src/slic3r/GUI/Tab.cpp @@ -2728,7 +2728,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 +2743,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."))); diff --git a/xs/src/slic3r/Utils/HexFile.cpp b/xs/src/slic3r/Utils/HexFile.cpp new file mode 100644 index 000000000..ed26ddf37 --- /dev/null +++ b/xs/src/slic3r/Utils/HexFile.cpp @@ -0,0 +1,107 @@ +#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)), + device(DEV_GENERIC) +{ + 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; + } + } +} + + +} +} diff --git a/xs/src/slic3r/Utils/HexFile.hpp b/xs/src/slic3r/Utils/HexFile.hpp new file mode 100644 index 000000000..d8d1e09ab --- /dev/null +++ b/xs/src/slic3r/Utils/HexFile.hpp @@ -0,0 +1,32 @@ +#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; + std::string model_id; + + HexFile(boost::filesystem::path path); +}; + + +} +} + +#endif diff --git a/xs/src/slic3r/Utils/Serial.cpp b/xs/src/slic3r/Utils/Serial.cpp index 32b6bb160..c3c16b314 100644 --- a/xs/src/slic3r/Utils/Serial.cpp +++ b/xs/src/slic3r/Utils/Serial.cpp @@ -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; } +#if _WIN32 +void parse_hardware_id(const std::string &hardware_id, SerialPortInfo &spi) +{ + 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 + #ifdef __linux__ -static std::string get_tty_friendly_name(const std::string &path, const std::string &name) +optional<std::string> sysfs_tty_prop(const std::string &tty_dev, const std::string &name) { - const auto sysfs_product = (boost::format("/sys/class/tty/%1%/device/../product") % name).str(); - std::ifstream file(sysfs_product); - std::string product; + 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, product); - return file.good() ? (boost::format("%1% (%2%)") % product % path).str() : path; + 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,224 @@ 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 + printer_write_line("M110 N0", 0); + 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; + } + timer.cancel(); + }); + + 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(2000)); +} + +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 diff --git a/xs/src/slic3r/Utils/Serial.hpp b/xs/src/slic3r/Utils/Serial.hpp index 9381ab398..d15f249c0 100644 --- a/xs/src/slic3r/Utils/Serial.hpp +++ b/xs/src/slic3r/Utils/Serial.hpp @@ -1,31 +1,80 @@ #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 setup for communicating with a printer + void printer_setup(); + + // Write data from a string + size_t write_string(const std::string &str); + + 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 diff --git a/xs/xsp/GUI_3DScene.xsp b/xs/xsp/GUI_3DScene.xsp index 5c2f7df85..a91f32d29 100644 --- a/xs/xsp/GUI_3DScene.xsp +++ b/xs/xsp/GUI_3DScene.xsp @@ -431,6 +431,13 @@ enable_force_zoom_to_bed(canvas, enable) _3DScene::enable_force_zoom_to_bed((wxGLCanvas*)wxPli_sv_2_object(aTHX_ canvas, "Wx::GLCanvas"), enable); void +enable_dynamic_background(canvas, enable) + SV *canvas; + bool enable; + CODE: + _3DScene::enable_dynamic_background((wxGLCanvas*)wxPli_sv_2_object(aTHX_ canvas, "Wx::GLCanvas"), enable); + +void allow_multisample(canvas, allow) SV *canvas; bool allow; @@ -657,33 +664,19 @@ reload_scene(canvas, force) CODE: _3DScene::reload_scene((wxGLCanvas*)wxPli_sv_2_object(aTHX_ canvas, "Wx::GLCanvas"), force); -void -load_print_toolpaths(canvas) - SV *canvas; - CODE: - _3DScene::load_print_toolpaths((wxGLCanvas*)wxPli_sv_2_object(aTHX_ canvas, "Wx::GLCanvas")); - void -load_print_object_toolpaths(canvas, print_object, tool_colors) +load_gcode_preview(canvas, preview_data, str_tool_colors) SV *canvas; - PrintObject *print_object; - std::vector<std::string> tool_colors; + GCodePreviewData *preview_data; + std::vector<std::string> str_tool_colors; CODE: - _3DScene::load_print_object_toolpaths((wxGLCanvas*)wxPli_sv_2_object(aTHX_ canvas, "Wx::GLCanvas"), print_object, tool_colors); + _3DScene::load_gcode_preview((wxGLCanvas*)wxPli_sv_2_object(aTHX_ canvas, "Wx::GLCanvas"), preview_data, str_tool_colors); void -load_wipe_tower_toolpaths(canvas, tool_colors) +load_preview(canvas, str_tool_colors) SV *canvas; - std::vector<std::string> tool_colors; - CODE: - _3DScene::load_wipe_tower_toolpaths((wxGLCanvas*)wxPli_sv_2_object(aTHX_ canvas, "Wx::GLCanvas"), tool_colors); - -void -load_gcode_preview(canvas, preview_data, str_tool_colors) - SV *canvas; - GCodePreviewData *preview_data; std::vector<std::string> str_tool_colors; CODE: - _3DScene::load_gcode_preview((wxGLCanvas*)wxPli_sv_2_object(aTHX_ canvas, "Wx::GLCanvas"), preview_data, str_tool_colors); - + _3DScene::load_preview((wxGLCanvas*)wxPli_sv_2_object(aTHX_ canvas, "Wx::GLCanvas"), str_tool_colors); + %} |