diff options
Diffstat (limited to 'xs')
62 files changed, 9042 insertions, 1425 deletions
diff --git a/xs/CMakeLists.txt b/xs/CMakeLists.txt index 9c61fa729..f4d0b17ea 100644 --- a/xs/CMakeLists.txt +++ b/xs/CMakeLists.txt @@ -8,6 +8,7 @@ list(APPEND CMAKE_MODULE_PATH ${PROJECT_SOURCE_DIR}/cmake/modules/) if (CMAKE_SYSTEM_NAME STREQUAL "Linux") # Workaround for an old CMake, which does not understand CMAKE_CXX_STANDARD. set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11 -Wall" ) + find_package(PkgConfig REQUIRED) endif() if (CMAKE_COMPILER_IS_GNUCC OR CMAKE_COMPILER_IS_GNUXX) @@ -27,6 +28,13 @@ 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) # -D_ITERATOR_DEBUG_LEVEL) + if(WIN10SDK_PATH) + message("Building with Win10 Netfabb STL fixing service support") + add_definitions(-DHAS_WIN10SDK) + include_directories("${WIN10SDK_PATH}/Include") + else() + message("Building without Win10 Netfabb STL fixing service support") + endif() endif() add_definitions(-DwxUSE_UNICODE -D_UNICODE -DUNICODE -DWXINTL_NO_GETTEXT_MACRO) @@ -181,7 +189,15 @@ add_library(libslic3r_gui STATIC ${LIBDIR}/slic3r/GUI/3DScene.cpp ${LIBDIR}/slic3r/GUI/3DScene.hpp ${LIBDIR}/slic3r/GUI/GLShader.cpp - ${LIBDIR}/slic3r/GUI/GLShader.hpp + ${LIBDIR}/slic3r/GUI/GLShader.hpp + ${LIBDIR}/slic3r/GUI/GLCanvas3D.hpp + ${LIBDIR}/slic3r/GUI/GLCanvas3D.cpp + ${LIBDIR}/slic3r/GUI/GLCanvas3DManager.hpp + ${LIBDIR}/slic3r/GUI/GLCanvas3DManager.cpp + ${LIBDIR}/slic3r/GUI/GLGizmo.hpp + ${LIBDIR}/slic3r/GUI/GLGizmo.cpp + ${LIBDIR}/slic3r/GUI/GLTexture.hpp + ${LIBDIR}/slic3r/GUI/GLTexture.cpp ${LIBDIR}/slic3r/GUI/Preferences.cpp ${LIBDIR}/slic3r/GUI/Preferences.hpp ${LIBDIR}/slic3r/GUI/Preset.cpp @@ -232,6 +248,8 @@ add_library(libslic3r_gui STATIC ${LIBDIR}/slic3r/GUI/FirmwareDialog.hpp ${LIBDIR}/slic3r/Utils/Http.cpp ${LIBDIR}/slic3r/Utils/Http.hpp + ${LIBDIR}/slic3r/Utils/FixModelByWin10.cpp + ${LIBDIR}/slic3r/Utils/FixModelByWin10.hpp ${LIBDIR}/slic3r/Utils/OctoPrint.cpp ${LIBDIR}/slic3r/Utils/OctoPrint.hpp ${LIBDIR}/slic3r/Utils/Bonjour.cpp @@ -336,8 +354,6 @@ add_library(semver STATIC ) -add_subdirectory(src/avrdude) - # Generate the Slic3r Perl module (XS) typemap file. set(MyTypemap ${CMAKE_CURRENT_BINARY_DIR}/typemap) add_custom_command( @@ -500,12 +516,12 @@ if (WIN32 AND ";${PerlEmbed_CCFLAGS};" MATCHES ";[-/]Od;") message("Old CMAKE_CXX_FLAGS_RELEASE: ${CMAKE_CXX_FLAGS_RELEASE}") message("Old CMAKE_CXX_FLAGS_RELWITHDEBINFO: ${CMAKE_CXX_FLAGS_RELEASE}") message("Old CMAKE_CXX_FLAGS: ${CMAKE_CXX_FLAGS_RELEASE}") - set(CMAKE_CXX_FLAGS_RELEASE "/MD /Od /Zi /EHsc /DNDEBUG") - set(CMAKE_C_FLAGS_RELEASE "/MD /Od /Zi /DNDEBUG") - set(CMAKE_CXX_FLAGS_RELWITHDEBINFO "/MD /Od /Zi /EHsc /DNDEBUG") - set(CMAKE_C_FLAGS_RELWITHDEBINFO "/MD /Od /Zi /DNDEBUG") - set(CMAKE_CXX_FLAGS "/MD /Od /Zi /EHsc /DNDEBUG") - set(CMAKE_C_FLAGS "/MD /Od /Zi /DNDEBUG") + set(CMAKE_CXX_FLAGS_RELEASE "/MD /Od /Zi /EHsc /DNDEBUG /DWIN32") + set(CMAKE_C_FLAGS_RELEASE "/MD /Od /Zi /DNDEBUG /DWIN32") + set(CMAKE_CXX_FLAGS_RELWITHDEBINFO "/MD /Od /Zi /EHsc /DNDEBUG /DWIN32") + set(CMAKE_C_FLAGS_RELWITHDEBINFO "/MD /Od /Zi /DNDEBUG /DWIN32") + set(CMAKE_CXX_FLAGS "/MD /Od /Zi /EHsc /DNDEBUG /DWIN32") + set(CMAKE_C_FLAGS "/MD /Od /Zi /DNDEBUG /DWIN32") endif() # The following line will add -fPIC on Linux to make the XS.so rellocable. add_definitions(${PerlEmbed_CCCDLFLAGS}) @@ -513,6 +529,8 @@ if (WIN32) target_link_libraries(XS ${PERL_LIBRARY}) endif() +add_subdirectory(src/avrdude) + ## REQUIRED packages # Find and configure boost @@ -557,13 +575,22 @@ if (SLIC3R_PRUSACONTROL) set(wxWidgets_UseAlienWx 1) if (wxWidgets_UseAlienWx) set(AlienWx_DEBUG 1) - find_package(AlienWx REQUIRED COMPONENTS base core adv html) + find_package(AlienWx REQUIRED COMPONENTS base core adv html gl) include_directories(${AlienWx_INCLUDE_DIRS}) #add_compile_options(${AlienWx_CXX_FLAGS}) add_definitions(${AlienWx_DEFINITIONS}) set(wxWidgets_LIBRARIES ${AlienWx_LIBRARIES}) + # On Linux / gtk, we need to have a direct access to gtk+ for some workarounds. + if (AlienWx_GUI_TOOLKIT STREQUAL "gtk2") + pkg_check_modules(GTK2 gtk+-2.0) + include_directories(${GTK2_INCLUDE_DIRS}) + endif() + if (AlienWx_GUI_TOOLKIT STREQUAL "gtk3") + pkg_check_modules(GTK3 gtk+-3.0) + include_directories(${GTK3_INCLUDE_DIRS}) + endif() else () - find_package(wxWidgets REQUIRED COMPONENTS base core adv html) + find_package(wxWidgets REQUIRED COMPONENTS base core adv html gl) include(${wxWidgets_USE_FILE}) endif () add_definitions(-DSLIC3R_GUI -DSLIC3R_PRUS) diff --git a/xs/lib/Slic3r/XS.pm b/xs/lib/Slic3r/XS.pm index 06eb041df..a4847fb45 100644 --- a/xs/lib/Slic3r/XS.pm +++ b/xs/lib/Slic3r/XS.pm @@ -12,6 +12,8 @@ our $VERSION = '0.01'; BEGIN { if ($^O eq 'MSWin32') { eval "use Wx"; + eval "use Wx::GLCanvas"; + eval "use Wx::GLContext"; eval "use Wx::Html"; eval "use Wx::Print"; # because of some Wx bug, thread creation fails if we don't have this (looks like Wx::Printout is hard-coded in some thread cleanup code) } @@ -280,6 +282,7 @@ for my $class (qw( Slic3r::Geometry::BoundingBox Slic3r::Geometry::BoundingBoxf Slic3r::Geometry::BoundingBoxf3 + Slic3r::GUI::_3DScene::GLShader Slic3r::GUI::_3DScene::GLVolume Slic3r::GUI::Preset Slic3r::GUI::PresetCollection diff --git a/xs/src/avrdude/arduino.c b/xs/src/avrdude/arduino.c index 566f56abd..5a9cb465e 100644 --- a/xs/src/avrdude/arduino.c +++ b/xs/src/avrdude/arduino.c @@ -80,6 +80,49 @@ static int arduino_read_sig_bytes(PROGRAMMER * pgm, AVRPART * p, AVRMEM * m) return 3; } +static int prusa_init_external_flash(PROGRAMMER * pgm) +{ + // Note: send/receive as in _the firmare_ send & receives + const char entry_magic_send [] = "start\n"; + const char entry_magic_receive[] = "w25x20cl_enter\n"; + const char entry_magic_cfm [] = "w25x20cl_cfm\n"; + const size_t buffer_len = 32; // Should be large enough for the above messages + + int res; + size_t recv_size; + char *buffer = alloca(buffer_len); + + // 1. receive the "start" command + recv_size = sizeof(entry_magic_send) - 1; + res = serial_recv(&pgm->fd, buffer, recv_size); + if (res < 0) { + avrdude_message(MSG_INFO, "%s: prusa_init_external_flash(): MK3 printer did not boot up on time or serial communication failed\n", progname); + return -1; + } else if (strncmp(buffer, entry_magic_send, recv_size) != 0) { + avrdude_message(MSG_INFO, "%s: prusa_init_external_flash(): MK3 printer emitted incorrect start code: `%*s`\n", progname, recv_size, buffer); + return -1; + } + + // 2. Send the external flash programmer enter command + if (serial_send(&pgm->fd, entry_magic_receive, sizeof(entry_magic_receive) - 1) < 0) { + avrdude_message(MSG_INFO, "%s: prusa_init_external_flash(): Failed to send command to the printer\n",progname); + return -1; + } + + // 3. Receive the entry confirmation command + recv_size = sizeof(entry_magic_cfm) - 1; + res = serial_recv(&pgm->fd, buffer, recv_size); + if (res < 0) { + avrdude_message(MSG_INFO, "%s: prusa_init_external_flash(): MK3 printer did not boot up on time or serial communication failed\n", progname); + return -1; + } else if (strncmp(buffer, entry_magic_cfm, recv_size) != 0) { + avrdude_message(MSG_INFO, "%s: prusa_init_external_flash(): MK3 printer emitted incorrect cfm code: `%*s`\n", progname, recv_size, buffer); + return -1; + } + + return 0; +} + static int arduino_open(PROGRAMMER * pgm, char * port) { union pinfo pinfo; @@ -97,11 +140,24 @@ static int arduino_open(PROGRAMMER * pgm, char * port) serial_set_dtr_rts(&pgm->fd, 1); usleep(50*1000); + // Sometimes there may be line noise generating input on the printer's USB-to-serial IC + // Here we try to clean its input buffer with a sequence of newlines (a minimum of 9 is needed): + const char cleanup_newlines[] = "\n\n\n\n\n\n\n\n\n\n"; + if (serial_send(&pgm->fd, cleanup_newlines, sizeof(cleanup_newlines) - 1) < 0) { + return -1; + } + /* * drain any extraneous input */ stk500_drain(pgm, 0); + // Initialization sequence for programming the external FLASH on the Prusa MK3 + if (prusa_init_external_flash(pgm) < 0) { + avrdude_message(MSG_INFO, "%s: arduino_open(): Failed to initialize MK3 external flash programming mode\n", progname); + return -1; + } + if (stk500_getsync(pgm) < 0) return -1; diff --git a/xs/src/avrdude/avrdude-slic3r.cpp b/xs/src/avrdude/avrdude-slic3r.cpp index a859200fb..030353413 100644 --- a/xs/src/avrdude/avrdude-slic3r.cpp +++ b/xs/src/avrdude/avrdude-slic3r.cpp @@ -1,5 +1,6 @@ #include "avrdude-slic3r.hpp" +#include <deque> #include <thread> extern "C" { @@ -33,17 +34,22 @@ static void avrdude_progress_handler_closure(const char *task, unsigned progress struct AvrDude::priv { std::string sys_config; - std::vector<std::string> args; + std::deque<std::vector<std::string>> args; + size_t current_args_set = 0; + RunFn run_fn; MessageFn message_fn; ProgressFn progress_fn; CompleteFn complete_fn; std::thread avrdude_thread; + priv(std::string &&sys_config) : sys_config(sys_config) {} + + int run_one(const std::vector<std::string> &args); int run(); }; -int AvrDude::priv::run() { +int AvrDude::priv::run_one(const std::vector<std::string> &args) { std::vector<char*> c_args {{ const_cast<char*>(PACKAGE_NAME) }}; for (const auto &arg : args) { c_args.push_back(const_cast<char*>(arg.data())); @@ -68,10 +74,22 @@ int AvrDude::priv::run() { return res; } +int AvrDude::priv::run() { + for (; args.size() > 0; current_args_set++) { + int res = run_one(args.front()); + args.pop_front(); + if (res != 0) { + return res; + } + } + + return 0; +} + // Public -AvrDude::AvrDude() : p(new priv()) {} +AvrDude::AvrDude(std::string sys_config) : p(new priv(std::move(sys_config))) {} AvrDude::AvrDude(AvrDude &&other) : p(std::move(other.p)) {} @@ -82,15 +100,15 @@ AvrDude::~AvrDude() } } -AvrDude& AvrDude::sys_config(std::string sys_config) +AvrDude& AvrDude::push_args(std::vector<std::string> args) { - if (p) { p->sys_config = std::move(sys_config); } + if (p) { p->args.push_back(std::move(args)); } return *this; } -AvrDude& AvrDude::args(std::vector<std::string> args) +AvrDude& AvrDude::on_run(RunFn fn) { - if (p) { p->args = std::move(args); } + if (p) { p->run_fn = std::move(fn); } return *this; } @@ -123,11 +141,17 @@ AvrDude::Ptr AvrDude::run() if (self->p) { auto avrdude_thread = std::thread([self]() { - auto res = self->p->run(); - if (self->p->complete_fn) { - self->p->complete_fn(res); - } - }); + if (self->p->run_fn) { + self->p->run_fn(); + } + + auto res = self->p->run(); + + if (self->p->complete_fn) { + self->p->complete_fn(res, self->p->current_args_set); + } + }); + self->p->avrdude_thread = std::move(avrdude_thread); } diff --git a/xs/src/avrdude/avrdude-slic3r.hpp b/xs/src/avrdude/avrdude-slic3r.hpp index 8d881b094..273aa2378 100644 --- a/xs/src/avrdude/avrdude-slic3r.hpp +++ b/xs/src/avrdude/avrdude-slic3r.hpp @@ -12,22 +12,28 @@ class AvrDude { public: typedef std::shared_ptr<AvrDude> Ptr; + typedef std::function<void()> RunFn; typedef std::function<void(const char * /* msg */, unsigned /* size */)> MessageFn; typedef std::function<void(const char * /* task */, unsigned /* progress */)> ProgressFn; - typedef std::function<void(int /* exit status */)> CompleteFn; + typedef std::function<void(int /* exit status */, size_t /* args_id */)> CompleteFn; - AvrDude(); + // Main c-tor, sys_config is the location of avrdude's main configuration file + AvrDude(std::string sys_config); AvrDude(AvrDude &&); AvrDude(const AvrDude &) = delete; AvrDude &operator=(AvrDude &&) = delete; AvrDude &operator=(const AvrDude &) = delete; ~AvrDude(); - // Set location of avrdude's main configuration file - AvrDude& sys_config(std::string sys_config); + // Push a set of avrdude cli arguments + // Each set makes one avrdude invocation - use this method multiple times to push + // more than one avrdude invocations. + AvrDude& push_args(std::vector<std::string> args); - // Set avrdude cli arguments - AvrDude& 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 has no effect when using run_sync(). + AvrDude& on_run(RunFn fn); // Set message output callback AvrDude& on_message(MessageFn fn); @@ -36,7 +42,10 @@ public: // Progress is reported per each task (reading / writing) in percents. AvrDude& on_progress(ProgressFn fn); - // Called when avrdude's main function finishes + // Called when the last avrdude invocation finishes with the exit status of zero, + // or earlier, if one of the invocations return a non-zero status. + // The second argument contains the sequential id of the last avrdude invocation argument set. + // This has no effect when using run_sync(). AvrDude& on_complete(CompleteFn fn); int run_sync(); diff --git a/xs/src/avrdude/avrpart.c b/xs/src/avrdude/avrpart.c index 621a85b98..b04851ac1 100644 --- a/xs/src/avrdude/avrpart.c +++ b/xs/src/avrdude/avrpart.c @@ -378,7 +378,7 @@ void avr_mem_display(const char * prefix, FILE * f, AVRMEM * m, int type, char * optr; if (m == NULL) { - fprintf(f, + avrdude_message(MSG_INFO, "%s Block Poll Page Polled\n" "%sMemory Type Mode Delay Size Indx Paged Size Size #Pages MinW MaxW ReadBack\n" "%s----------- ---- ----- ----- ---- ------ ------ ---- ------ ----- ----- ---------\n", @@ -386,13 +386,13 @@ void avr_mem_display(const char * prefix, FILE * f, AVRMEM * m, int type, } else { if (verbose > 2) { - fprintf(f, + avrdude_message(MSG_INFO, "%s Block Poll Page Polled\n" "%sMemory Type Mode Delay Size Indx Paged Size Size #Pages MinW MaxW ReadBack\n" "%s----------- ---- ----- ----- ---- ------ ------ ---- ------ ----- ----- ---------\n", prefix, prefix, prefix); } - fprintf(f, + avrdude_message(MSG_INFO, "%s%-11s %4d %5d %5d %4d %-6s %6d %4d %6d %5d %5d 0x%02x 0x%02x\n", prefix, m->desc, m->mode, m->delay, m->blocksize, m->pollindex, m->paged ? "yes" : "no", @@ -415,7 +415,7 @@ void avr_mem_display(const char * prefix, FILE * f, AVRMEM * m, int type, optr = avr_op_str(i); else optr = " "; - fprintf(f, + avrdude_message(MSG_INFO, "%s %-11s %8d %8s %5d %5d\n", prefix, optr, j, bittype(m->op[i]->bit[j].type), @@ -620,7 +620,7 @@ void avr_display(FILE * f, AVRPART * p, const char * prefix, int verbose) LNODEID ln; AVRMEM * m; - fprintf(f, + avrdude_message(MSG_INFO, "%sAVR Part : %s\n" "%sChip Erase delay : %d us\n" "%sPAGEL : P%02X\n" diff --git a/xs/src/avrdude/fileio.c b/xs/src/avrdude/fileio.c index f2d617823..aa57f5587 100644 --- a/xs/src/avrdude/fileio.c +++ b/xs/src/avrdude/fileio.c @@ -45,6 +45,8 @@ #define MAX_LINE_LEN 256 /* max line length for ASCII format input files */ +#define MAX_MODE_LEN 32 // For fopen_and_seek() + struct ihexrec { unsigned char reclen; @@ -96,8 +98,40 @@ static int fileio_num(struct fioparms * fio, char * filename, FILE * f, AVRMEM * mem, int size, FILEFMT fmt); -static int fmt_autodetect(char * fname); +static int fmt_autodetect(char * fname, size_t offset); + + + +static FILE *fopen_and_seek(const char *filename, const char *mode, size_t offset) +{ + FILE *file; + // On Windows we need to convert the filename to UTF-16 +#if defined(WIN32NATIVE) + static wchar_t fname_buffer[PATH_MAX]; + static wchar_t mode_buffer[MAX_MODE_LEN]; + + if (MultiByteToWideChar(CP_UTF8, 0, filename, -1, fname_buffer, PATH_MAX) == 0) { return NULL; } + if (MultiByteToWideChar(CP_ACP, 0, mode, -1, mode_buffer, MAX_MODE_LEN) == 0) { return NULL; } + + file = _wfopen(fname_buffer, mode_buffer); +#else + 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; + } + } + return file; +} char * fmtstr(FILEFMT format) @@ -1358,7 +1392,7 @@ int fileio_setparms(int op, struct fioparms * fp, -static int fmt_autodetect(char * fname) +static int fmt_autodetect(char * fname, size_t offset) { FILE * f; unsigned char buf[MAX_LINE_LEN]; @@ -1368,10 +1402,11 @@ static int fmt_autodetect(char * fname) int first = 1; #if defined(WIN32NATIVE) - f = fopen(fname, "r"); + f = fopen_and_seek(fname, "r", offset); #else - f = fopen(fname, "rb"); + f = fopen_and_seek(fname, "rb", offset); #endif + if (f == NULL) { avrdude_message(MSG_INFO, "%s: error opening %s: %s\n", progname, fname, strerror(errno)); @@ -1445,7 +1480,7 @@ static int fmt_autodetect(char * fname) int fileio(int op, char * filename, FILEFMT format, - struct avrpart * p, char * memtype, int size) + struct avrpart * p, char * memtype, int size, size_t offset) { int rc; FILE * f; @@ -1477,15 +1512,17 @@ int fileio(int op, char * filename, FILEFMT format, using_stdio = 0; if (strcmp(filename, "-")==0) { - if (fio.op == FIO_READ) { - fname = "<stdin>"; - f = stdin; - } - else { - fname = "<stdout>"; - f = stdout; - } - using_stdio = 1; + return -1; + // Note: we don't want to read stdin or write to stdout as part of Slic3r + // if (fio.op == FIO_READ) { + // fname = "<stdin>"; + // f = stdin; + // } + // else { + // fname = "<stdout>"; + // f = stdout; + // } + // using_stdio = 1; } else { fname = filename; @@ -1502,7 +1539,7 @@ int fileio(int op, char * filename, FILEFMT format, return -1; } - format_detect = fmt_autodetect(fname); + format_detect = fmt_autodetect(fname, offset); if (format_detect < 0) { avrdude_message(MSG_INFO, "%s: can't determine file format for %s, specify explicitly\n", progname, fname); @@ -1533,7 +1570,7 @@ int fileio(int op, char * filename, FILEFMT format, if (format != FMT_IMM) { if (!using_stdio) { - f = fopen(fname, fio.mode); + f = fopen_and_seek(fname, fio.mode, offset); 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 e8197f9c2..536f1a2f7 100644 --- a/xs/src/avrdude/libavrdude.h +++ b/xs/src/avrdude/libavrdude.h @@ -737,7 +737,7 @@ extern bool cancel_flag; #define RETURN_IF_CANCEL() \ do { \ if (cancel_flag) { \ - avrdude_message(MSG_INFO, "%s(): Cancelled, exiting...\n", __func__); \ + avrdude_message(MSG_INFO, "avrdude: %s(): Cancelled, exiting...\n", __func__); \ return -99; \ } \ } while (0) @@ -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); + struct avrpart * p, char * memtype, int size, size_t offset); #ifdef __cplusplus } @@ -870,6 +870,7 @@ enum updateflags { typedef struct update_t { char * memtype; int op; + size_t offset; char * filename; int format; } UPDATE; @@ -881,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); + char * filename, size_t offset); 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.c b/xs/src/avrdude/main.c index 0550ceff1..d4c34fe44 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:<filename>[:format]\n" + " -U <memtype>:r|w|v:<offset>:<filename>[:format]\n" " Memory operation specification.\n" " Multiple -U options are allowed, each request\n" " is performed in the order specified.\n" @@ -374,7 +374,7 @@ static void list_parts(FILE * f, const char *prefix, LISTID avrparts) static int cleanup_main(int status) { - if (pgm_setup && pgm->teardown) { + if (pgm_setup && pgm != NULL && pgm->teardown) { pgm->teardown(pgm); } diff --git a/xs/src/avrdude/ser_posix.c b/xs/src/avrdude/ser_posix.c index 91b18e945..cb0fc0385 100644 --- a/xs/src/avrdude/ser_posix.c +++ b/xs/src/avrdude/ser_posix.c @@ -376,6 +376,10 @@ static int ser_recv(union filedescriptor *fd, unsigned char * buf, size_t buflen FD_SET(fd->ifd, &rfds); nfds = select(fd->ifd + 1, &rfds, NULL, NULL, &to2); + // FIXME: The timeout has different behaviour on Linux vs other Unices + // On Linux, the timeout is modified by subtracting the time spent, + // on OS X (for example), it is not modified. + // POSIX recommends re-initializing it before selecting. if (nfds == 0) { avrdude_message(MSG_NOTICE2, "%s: ser_recv(): programmer is not responding\n", progname); diff --git a/xs/src/avrdude/stk500.c b/xs/src/avrdude/stk500.c index 5d2d3c1df..63deb228f 100644 --- a/xs/src/avrdude/stk500.c +++ b/xs/src/avrdude/stk500.c @@ -716,11 +716,14 @@ static int stk500_loadaddr(PROGRAMMER * pgm, AVRMEM * mem, unsigned int addr) } buf[0] = Cmnd_STK_LOAD_ADDRESS; - buf[1] = addr & 0xff; - buf[2] = (addr >> 8) & 0xff; - buf[3] = Sync_CRC_EOP; - - stk500_send(pgm, buf, 4); + // Workaround for the infamous ';' bug in the Prusa3D usb to serial converter. + // Send the binary data by nibbles to avoid transmitting the ';' character. + buf[1] = addr & 0x0f; + buf[2] = addr & 0xf0; + buf[3] = (addr >> 8) & 0x0f; + buf[4] = (addr >> 8) & 0xf0; + buf[5] = Sync_CRC_EOP; + stk500_send(pgm, buf, 6); if (stk500_recv(pgm, buf, 1) < 0) return -1; @@ -765,7 +768,9 @@ static int stk500_paged_write(PROGRAMMER * pgm, AVRPART * p, AVRMEM * m, int block_size; int tries; unsigned int n; - unsigned int i; + unsigned int i, j; + unsigned int prusa3d_semicolon_workaround_round = 0; + bool has_semicolon = false; if (strcmp(m->desc, "flash") == 0) { memtype = 'F'; @@ -806,44 +811,64 @@ static int stk500_paged_write(PROGRAMMER * pgm, AVRPART * p, AVRMEM * m, tries++; stk500_loadaddr(pgm, m, addr/a_div); - /* build command block and avoid multiple send commands as it leads to a crash - of the silabs usb serial driver on mac os x */ - i = 0; - buf[i++] = Cmnd_STK_PROG_PAGE; - buf[i++] = (block_size >> 8) & 0xff; - buf[i++] = block_size & 0xff; - buf[i++] = memtype; - memcpy(&buf[i], &m->buf[addr], block_size); - i += block_size; - buf[i++] = Sync_CRC_EOP; - stk500_send( pgm, buf, i); + for (i = 0; i < n_bytes; ++ i) + if (m->buf[addr + i] == ';') { + has_semicolon = true; + break; + } - if (stk500_recv(pgm, buf, 1) < 0) - return -1; - if (buf[0] == Resp_STK_NOSYNC) { - if (tries > 33) { - avrdude_message(MSG_INFO, "\n%s: stk500_paged_write(): can't get into sync\n", - progname); - return -3; + for (prusa3d_semicolon_workaround_round = 0; prusa3d_semicolon_workaround_round < (has_semicolon ? 2 : 1); ++ prusa3d_semicolon_workaround_round) { + /* build command block and avoid multiple send commands as it leads to a crash + of the silabs usb serial driver on mac os x */ + i = 0; + buf[i++] = Cmnd_STK_PROG_PAGE; + // Workaround for the infamous ';' bug in the Prusa3D usb to serial converter. + // Send the binary data by nibbles to avoid transmitting the ';' character. + buf[i++] = (block_size >> 8) & 0xf0; + buf[i++] = (block_size >> 8) & 0x0f; + buf[i++] = block_size & 0xf0; + buf[i++] = block_size & 0x0f; + buf[i++] = memtype; + if (has_semicolon) { + for (j = 0; j < block_size; ++i, ++ j) { + buf[i] = m->buf[addr + j]; + if (buf[i] == ';') + buf[i] |= (prusa3d_semicolon_workaround_round ? 0xf0 : 0x0f); + } + } else { + memcpy(&buf[i], &m->buf[addr], block_size); + i += block_size; + } + buf[i++] = Sync_CRC_EOP; + stk500_send( pgm, buf, i); + + if (stk500_recv(pgm, buf, 1) < 0) + return -1; + if (buf[0] == Resp_STK_NOSYNC) { + if (tries > 33) { + avrdude_message(MSG_INFO, "\n%s: stk500_paged_write(): can't get into sync\n", + progname); + return -3; + } + if (stk500_getsync(pgm) < 0) + return -1; + goto retry; + } + else if (buf[0] != Resp_STK_INSYNC) { + avrdude_message(MSG_INFO, "\n%s: stk500_paged_write(): (a) protocol error, " + "expect=0x%02x, resp=0x%02x\n", + progname, Resp_STK_INSYNC, buf[0]); + return -4; + } + + if (stk500_recv(pgm, buf, 1) < 0) + return -1; + if (buf[0] != Resp_STK_OK) { + avrdude_message(MSG_INFO, "\n%s: stk500_paged_write(): (a) protocol error, " + "expect=0x%02x, resp=0x%02x\n", + progname, Resp_STK_INSYNC, buf[0]); + return -5; } - if (stk500_getsync(pgm) < 0) - return -1; - goto retry; - } - else if (buf[0] != Resp_STK_INSYNC) { - avrdude_message(MSG_INFO, "\n%s: stk500_paged_write(): (a) protocol error, " - "expect=0x%02x, resp=0x%02x\n", - progname, Resp_STK_INSYNC, buf[0]); - return -4; - } - - if (stk500_recv(pgm, buf, 1) < 0) - return -1; - if (buf[0] != Resp_STK_OK) { - avrdude_message(MSG_INFO, "\n%s: stk500_paged_write(): (a) protocol error, " - "expect=0x%02x, resp=0x%02x\n", - progname, Resp_STK_INSYNC, buf[0]); - return -5; } } @@ -893,11 +918,15 @@ static int stk500_paged_load(PROGRAMMER * pgm, AVRPART * p, AVRMEM * m, tries++; stk500_loadaddr(pgm, m, addr/a_div); buf[0] = Cmnd_STK_READ_PAGE; - buf[1] = (block_size >> 8) & 0xff; - buf[2] = block_size & 0xff; - buf[3] = memtype; - buf[4] = Sync_CRC_EOP; - stk500_send(pgm, buf, 5); + // Workaround for the infamous ';' bug in the Prusa3D usb to serial converter. + // Send the binary data by nibbles to avoid transmitting the ';' character. + buf[1] = (block_size >> 8) & 0xf0; + buf[2] = (block_size >> 8) & 0x0f; + buf[3] = block_size & 0xf0; + buf[4] = block_size & 0x0f; + buf[5] = memtype; + buf[6] = Sync_CRC_EOP; + stk500_send(pgm, buf, 7); if (stk500_recv(pgm, buf, 1) < 0) return -1; diff --git a/xs/src/avrdude/stk500v2.c b/xs/src/avrdude/stk500v2.c index d3acb639c..4d62640c0 100644 --- a/xs/src/avrdude/stk500v2.c +++ b/xs/src/avrdude/stk500v2.c @@ -79,7 +79,7 @@ #define SERIAL_TIMEOUT 2 // Retry count -#define RETRIES 5 +#define RETRIES 0 #if 0 #define DEBUG(...) avrdude_message(MSG_INFO, __VA_ARGS__) @@ -745,7 +745,7 @@ static int stk500v2_recv(PROGRAMMER * pgm, unsigned char *msg, size_t maxsize) { -static int stk500v2_getsync_internal(PROGRAMMER * pgm, int retries) { +int stk500v2_getsync(PROGRAMMER * pgm) { int tries = 0; unsigned char buf[1], resp[32]; int status; @@ -804,7 +804,7 @@ retry: progname, pgmname[PDATA(pgm)->pgmtype]); return 0; } else { - if (tries > retries) { + if (tries > RETRIES) { avrdude_message(MSG_INFO, "%s: stk500v2_getsync(): can't communicate with device: resp=0x%02x\n", progname, resp[0]); return -6; @@ -814,7 +814,7 @@ retry: // or if we got a timeout } else if (status == -1) { - if (tries > retries) { + if (tries > RETRIES) { avrdude_message(MSG_INFO, "%s: stk500v2_getsync(): timeout communicating with programmer\n", progname); return -1; @@ -823,7 +823,7 @@ retry: // or any other error } else { - if (tries > retries) { + if (tries > RETRIES) { avrdude_message(MSG_INFO, "%s: stk500v2_getsync(): error communicating with programmer: (%d)\n", progname,status); } else @@ -833,11 +833,6 @@ retry: return 0; } -int stk500v2_getsync(PROGRAMMER * pgm) { - // This is to avoid applying RETRIES exponentially - return stk500v2_getsync_internal(pgm, RETRIES); -} - static int stk500v2_command(PROGRAMMER * pgm, unsigned char * buf, size_t len, size_t maxlen) { int i; @@ -947,7 +942,7 @@ retry: } // otherwise try to sync up again - status = stk500v2_getsync_internal(pgm, 1); + status = stk500v2_getsync(pgm); if (status != 0) { if (tries > RETRIES) { avrdude_message(MSG_INFO, "%s: stk500v2_command(): failed miserably to execute command 0x%02x\n", diff --git a/xs/src/avrdude/update.c b/xs/src/avrdude/update.c index a73461dfa..e9dd6e325 100644 --- a/xs/src/avrdude/update.c +++ b/xs/src/avrdude/update.c @@ -101,6 +101,24 @@ UPDATE * parse_op(char * s) p++; + // Extension: Parse file contents offset + size_t offset = 0; + + for (; *p != ':'; p++) { + if (*p >= '0' && *p <= '9') { + offset *= 10; + offset += *p - 0x30; + } else { + avrdude_message(MSG_INFO, "%s: invalid update specification: offset is not a number\n", progname); + free(upd->memtype); + free(upd); + return NULL; + } + } + + upd->offset = offset; + p++; + /* * Now, parse the filename component. Instead of looking for the * leftmost possible colon delimiter, we look for the rightmost one. @@ -176,7 +194,7 @@ UPDATE * dup_update(UPDATE * upd) return u; } -UPDATE * new_update(int op, char * memtype, int filefmt, char * filename) +UPDATE * new_update(int op, char * memtype, int filefmt, char * filename, size_t offset) { UPDATE * u; @@ -190,6 +208,7 @@ UPDATE * new_update(int op, char * memtype, int filefmt, char * filename) u->filename = strdup(filename); u->op = op; u->format = filefmt; + u->offset = offset; return u; } @@ -250,7 +269,7 @@ int do_op(PROGRAMMER * pgm, struct avrpart * p, UPDATE * upd, enum updateflags f progname, strcmp(upd->filename, "-")==0 ? "<stdout>" : upd->filename); } - rc = fileio(FIO_WRITE, upd->filename, upd->format, p, upd->memtype, size); + rc = fileio(FIO_WRITE, upd->filename, upd->format, p, upd->memtype, size, 0); if (rc < 0) { avrdude_message(MSG_INFO, "%s: write to file '%s' failed\n", progname, upd->filename); @@ -267,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); + rc = fileio(FIO_READ, upd->filename, upd->format, p, upd->memtype, -1, upd->offset); if (rc < 0) { avrdude_message(MSG_INFO, "%s: read from file '%s' failed\n", progname, upd->filename); @@ -296,11 +315,11 @@ int do_op(PROGRAMMER * pgm, struct avrpart * p, UPDATE * upd, enum updateflags f report_progress(1,1,NULL); } else { - /* - * test mode, don't actually write to the chip, output the buffer - * to stdout in intel hex instead - */ - rc = fileio(FIO_WRITE, "-", FMT_IHEX, p, upd->memtype, size); + // /* + // * test mode, don't actually write to the chip, output the buffer + // * to stdout in intel hex instead + // */ + // rc = fileio(FIO_WRITE, "-", FMT_IHEX, p, upd->memtype, size, 0); } if (rc < 0) { @@ -332,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); + rc = fileio(FIO_READ, upd->filename, upd->format, p, upd->memtype, -1, upd->offset); if (rc < 0) { avrdude_message(MSG_INFO, "%s: read from file '%s' failed\n", progname, upd->filename); diff --git a/xs/src/libslic3r/BoundingBox.cpp b/xs/src/libslic3r/BoundingBox.cpp index 91ba88d84..ceb968a50 100644 --- a/xs/src/libslic3r/BoundingBox.cpp +++ b/xs/src/libslic3r/BoundingBox.cpp @@ -222,6 +222,14 @@ BoundingBox3Base<PointClass>::center() const } template Pointf3 BoundingBox3Base<Pointf3>::center() const; +template <class PointClass> coordf_t +BoundingBox3Base<PointClass>::max_size() const +{ + PointClass s = size(); + return std::max(s.x, std::max(s.y, s.z)); +} +template coordf_t BoundingBox3Base<Pointf3>::max_size() const; + // Align a coordinate to a grid. The coordinate may be negative, // the aligned value will never be bigger than the original one. static inline coord_t _align_to_grid(const coord_t coord, const coord_t spacing) { diff --git a/xs/src/libslic3r/BoundingBox.hpp b/xs/src/libslic3r/BoundingBox.hpp index 92a2bd451..5de94aa9c 100644 --- a/xs/src/libslic3r/BoundingBox.hpp +++ b/xs/src/libslic3r/BoundingBox.hpp @@ -94,6 +94,7 @@ public: void translate(const Pointf3 &pos) { this->translate(pos.x, pos.y, pos.z); } void offset(coordf_t delta); PointClass center() const; + coordf_t max_size() const; bool contains(const PointClass &point) const { return BoundingBoxBase<PointClass>::contains(point) && point.z >= this->min.z && point.z <= this->max.z; diff --git a/xs/src/libslic3r/Format/3mf.cpp b/xs/src/libslic3r/Format/3mf.cpp index 0467962c3..2c32db1a6 100644 --- a/xs/src/libslic3r/Format/3mf.cpp +++ b/xs/src/libslic3r/Format/3mf.cpp @@ -1271,6 +1271,7 @@ namespace Slic3r { if ((std::abs(sx - sy) > 0.00001) || (std::abs(sx - sz) > 0.00001)) return; +#if 0 // use quaternions // rotations (extracted using quaternion) double inv_sx = 1.0 / sx; double inv_sy = 1.0 / sy; @@ -1331,6 +1332,25 @@ namespace Slic3r { if (angle_z < 0.0) angle_z += 2.0 * PI; } +#else // use eigen library + double inv_sx = 1.0 / sx; + double inv_sy = 1.0 / sy; + double inv_sz = 1.0 / sz; + + Eigen::Matrix3d m3x3; + m3x3 << (double)matrix(0, 0) * inv_sx, (double)matrix(0, 1) * inv_sy, (double)matrix(0, 2) * inv_sz, + (double)matrix(1, 0) * inv_sx, (double)matrix(1, 1) * inv_sy, (double)matrix(1, 2) * inv_sz, + (double)matrix(2, 0) * inv_sx, (double)matrix(2, 1) * inv_sy, (double)matrix(2, 2) * inv_sz; + + Eigen::AngleAxisd rotation; + rotation.fromRotationMatrix(m3x3); + + // invalid rotation axis, we currently handle only rotations around Z axis + if ((rotation.angle() != 0.0) && (rotation.axis() != Eigen::Vector3d::UnitZ()) && (rotation.axis() != -Eigen::Vector3d::UnitZ())) + return; + + double angle_z = (rotation.axis() == Eigen::Vector3d::UnitZ()) ? rotation.angle() : -rotation.angle(); +#endif instance.offset.x = offset_x; instance.offset.y = offset_y; diff --git a/xs/src/libslic3r/Format/AMF.cpp b/xs/src/libslic3r/Format/AMF.cpp index 83b50ec9e..263363756 100644 --- a/xs/src/libslic3r/Format/AMF.cpp +++ b/xs/src/libslic3r/Format/AMF.cpp @@ -13,6 +13,9 @@ #include <boost/filesystem/operations.hpp> #include <boost/algorithm/string.hpp> +//############################################################################################################################################ +#include <boost/nowide/fstream.hpp> +//############################################################################################################################################ #include <miniz/miniz_zip.h> #if 0 @@ -666,10 +669,21 @@ bool load_amf_archive(const char *path, PresetBundle* bundle, Model *model) // If bundle is not a null pointer, updates it if the amf file/archive contains config data bool load_amf(const char *path, PresetBundle* bundle, Model *model) { - if (boost::iends_with(path, ".zip.amf")) - return load_amf_archive(path, bundle, model); - else if (boost::iends_with(path, ".amf") || boost::iends_with(path, ".amf.xml")) + if (boost::iends_with(path, ".amf.xml")) + // backward compatibility with older slic3r output return load_amf_file(path, bundle, model); + else if (boost::iends_with(path, ".amf")) + { + boost::nowide::ifstream file(path, boost::nowide::ifstream::binary); + if (!file.good()) + return false; + + std::string zip_mask(2, '\0'); + file.read(const_cast<char*>(zip_mask.data()), 2); + file.close(); + + return (zip_mask == "PK") ? load_amf_archive(path, bundle, model) : load_amf_file(path, bundle, model); + } else return false; } diff --git a/xs/src/libslic3r/GCode/WipeTowerPrusaMM.cpp b/xs/src/libslic3r/GCode/WipeTowerPrusaMM.cpp index 6486f2917..5aa6470a2 100644 --- a/xs/src/libslic3r/GCode/WipeTowerPrusaMM.cpp +++ b/xs/src/libslic3r/GCode/WipeTowerPrusaMM.cpp @@ -142,24 +142,21 @@ public: } m_gcode += "G1"; - if (rot.x != rotated_current_pos.x) { - m_gcode += set_format_X(rot.x); // Transform current position back to wipe tower coordinates (was updated by set_format_X) - m_current_pos.x = x; - } - if (rot.y != rotated_current_pos.y) { + if (std::abs(rot.x - rotated_current_pos.x) > EPSILON) + m_gcode += set_format_X(rot.x); + + if (std::abs(rot.y - rotated_current_pos.y) > EPSILON) m_gcode += set_format_Y(rot.y); - m_current_pos.y = y; - } if (e != 0.f) m_gcode += set_format_E(e); if (f != 0.f && f != m_current_feedrate) m_gcode += set_format_F(f); - - - - + + m_current_pos.x = x; + m_current_pos.y = y; + // Update the elapsed time with a rough estimate. m_elapsed_time += ((len == 0) ? std::abs(e) : len) / m_current_feedrate * 60.f; m_gcode += "\n"; diff --git a/xs/src/libslic3r/Model.cpp b/xs/src/libslic3r/Model.cpp index 8ce23b1e5..147353abd 100644 --- a/xs/src/libslic3r/Model.cpp +++ b/xs/src/libslic3r/Model.cpp @@ -603,7 +603,10 @@ void ModelObject::clear_instances() // Returns the bounding box of the transformed instances. // This bounding box is approximate and not snug. -const BoundingBoxf3& ModelObject::bounding_box() +//======================================================================================================== +const BoundingBoxf3& ModelObject::bounding_box() const +//const BoundingBoxf3& ModelObject::bounding_box() +//======================================================================================================== { if (! m_bounding_box_valid) { BoundingBoxf3 raw_bbox; diff --git a/xs/src/libslic3r/Model.hpp b/xs/src/libslic3r/Model.hpp index 8b63c3641..b148ec29d 100644 --- a/xs/src/libslic3r/Model.hpp +++ b/xs/src/libslic3r/Model.hpp @@ -103,7 +103,10 @@ public: // Returns the bounding box of the transformed instances. // This bounding box is approximate and not snug. // This bounding box is being cached. - const BoundingBoxf3& bounding_box(); +//======================================================================================================== + const BoundingBoxf3& bounding_box() const; +// const BoundingBoxf3& bounding_box(); +//======================================================================================================== void invalidate_bounding_box() { m_bounding_box_valid = false; } // Returns a snug bounding box of the transformed instances. // This bounding box is not being cached. @@ -145,8 +148,10 @@ private: // Parent object, owning this ModelObject. Model *m_model; // Bounding box, cached. - BoundingBoxf3 m_bounding_box; - bool m_bounding_box_valid; +//======================================================================================================== + mutable BoundingBoxf3 m_bounding_box; + mutable bool m_bounding_box_valid; +//======================================================================================================== }; // An object STL, or a modifier volume, over which a different set of parameters shall be applied. diff --git a/xs/src/libslic3r/Point.hpp b/xs/src/libslic3r/Point.hpp index 6c9096a3d..a52cdceb6 100644 --- a/xs/src/libslic3r/Point.hpp +++ b/xs/src/libslic3r/Point.hpp @@ -238,6 +238,11 @@ inline coordf_t dot(const Pointf &v1, const Pointf &v2) { return v1.x * v2.x + v inline coordf_t dot(const Pointf &v) { return v.x * v.x + v.y * v.y; } inline double length(const Vectorf &v) { return sqrt(dot(v)); } inline double l2(const Vectorf &v) { return dot(v); } +inline Vectorf normalize(const Vectorf& v) +{ + coordf_t len = ::sqrt(sqr(v.x) + sqr(v.y)); + return (len != 0.0) ? 1.0 / len * v : Vectorf(0.0, 0.0); +} class Pointf3 : public Pointf { diff --git a/xs/src/libslic3r/Polygon.cpp b/xs/src/libslic3r/Polygon.cpp index 27f9a2ca1..b5fd7e64f 100644 --- a/xs/src/libslic3r/Polygon.cpp +++ b/xs/src/libslic3r/Polygon.cpp @@ -103,7 +103,7 @@ double Polygon::area() const double a = 0.; for (size_t i = 0, j = n - 1; i < n; ++i) { - a += double(points[j].x + points[i].x) * double(points[i].y - points[j].y); + a += ((double)points[j].x + (double)points[i].x) * ((double)points[i].y - (double)points[j].y); j = i; } return 0.5 * a; diff --git a/xs/src/libslic3r/Print.hpp b/xs/src/libslic3r/Print.hpp index c56e64c6c..86c15b679 100644 --- a/xs/src/libslic3r/Print.hpp +++ b/xs/src/libslic3r/Print.hpp @@ -184,6 +184,8 @@ public: void reset_layer_height_profile(); + void adjust_layer_height_profile(coordf_t z, coordf_t layer_thickness_delta, coordf_t band_width, int action); + // Collect the slicing parameters, to be used by variable layer thickness algorithm, // by the interactive layer height editor and by the printing process itself. // The slicing parameters are dependent on various configuration values diff --git a/xs/src/libslic3r/PrintObject.cpp b/xs/src/libslic3r/PrintObject.cpp index b0341db16..ba0876a85 100644 --- a/xs/src/libslic3r/PrintObject.cpp +++ b/xs/src/libslic3r/PrintObject.cpp @@ -4,6 +4,7 @@ #include "Geometry.hpp" #include "SupportMaterial.hpp" #include "Surface.hpp" +#include "Slicing.hpp" #include <utility> #include <boost/log/trivial.hpp> @@ -1961,4 +1962,12 @@ void PrintObject::reset_layer_height_profile() this->model_object()->layer_height_profile_valid = false; } +void PrintObject::adjust_layer_height_profile(coordf_t z, coordf_t layer_thickness_delta, coordf_t band_width, int action) +{ + update_layer_height_profile(_model_object->layer_height_profile); + Slic3r::adjust_layer_height_profile(slicing_parameters(), _model_object->layer_height_profile, z, layer_thickness_delta, band_width, LayerHeightEditActionType(action)); + _model_object->layer_height_profile_valid = true; + layer_height_profile_valid = false; +} + } // namespace Slic3r diff --git a/xs/src/libslic3r/Utils.hpp b/xs/src/libslic3r/Utils.hpp index e5157741e..921841a27 100644 --- a/xs/src/libslic3r/Utils.hpp +++ b/xs/src/libslic3r/Utils.hpp @@ -91,10 +91,13 @@ public: ~PerlCallback() { this->deregister_callback(); } void register_callback(void *sv); void deregister_callback(); - void call(); - void call(int i); - void call(int i, int j); -// void call(const std::vector<int> &ints); + void call() const; + void call(int i) const; + void call(int i, int j) const; + void call(const std::vector<int>& ints) const; + void call(double d) const; + void call(double x, double y) const; + void call(bool b) const; private: void *m_callback; }; diff --git a/xs/src/libslic3r/libslic3r.h b/xs/src/libslic3r/libslic3r.h index 4aef4d5c1..b0c144efe 100644 --- a/xs/src/libslic3r/libslic3r.h +++ b/xs/src/libslic3r/libslic3r.h @@ -14,7 +14,7 @@ #include <boost/thread.hpp> #define SLIC3R_FORK_NAME "Slic3r Prusa Edition" -#define SLIC3R_VERSION "1.40.0" +#define SLIC3R_VERSION "1.40.1" #define SLIC3R_BUILD "UNKNOWN" typedef int32_t coord_t; diff --git a/xs/src/libslic3r/utils.cpp b/xs/src/libslic3r/utils.cpp index 582488c5a..745d07fcd 100644 --- a/xs/src/libslic3r/utils.cpp +++ b/xs/src/libslic3r/utils.cpp @@ -184,7 +184,7 @@ void PerlCallback::deregister_callback() } } -void PerlCallback::call() +void PerlCallback::call() const { if (! m_callback) return; @@ -198,7 +198,7 @@ void PerlCallback::call() LEAVE; } -void PerlCallback::call(int i) +void PerlCallback::call(int i) const { if (! m_callback) return; @@ -213,7 +213,7 @@ void PerlCallback::call(int i) LEAVE; } -void PerlCallback::call(int i, int j) +void PerlCallback::call(int i, int j) const { if (! m_callback) return; @@ -229,8 +229,7 @@ void PerlCallback::call(int i, int j) LEAVE; } -/* -void PerlCallback::call(const std::vector<int> &ints) +void PerlCallback::call(const std::vector<int>& ints) const { if (! m_callback) return; @@ -238,16 +237,51 @@ void PerlCallback::call(const std::vector<int> &ints) ENTER; SAVETMPS; PUSHMARK(SP); - AV* av = newAV(); for (int i : ints) - av_push(av, newSViv(i)); - XPUSHs(av); + { + XPUSHs(sv_2mortal(newSViv(i))); + } + PUTBACK; + perl_call_sv(SvRV((SV*)m_callback), G_DISCARD); + FREETMPS; + LEAVE; +} + +void PerlCallback::call(double d) const +{ + if (!m_callback) + return; + dSP; + ENTER; + SAVETMPS; + PUSHMARK(SP); + XPUSHs(sv_2mortal(newSVnv(d))); + PUTBACK; + perl_call_sv(SvRV((SV*)m_callback), G_DISCARD); + FREETMPS; + LEAVE; +} + +void PerlCallback::call(double x, double y) const +{ + if (!m_callback) + return; + dSP; + ENTER; + SAVETMPS; + PUSHMARK(SP); + XPUSHs(sv_2mortal(newSVnv(x))); + XPUSHs(sv_2mortal(newSVnv(y))); PUTBACK; perl_call_sv(SvRV((SV*)m_callback), G_DISCARD); FREETMPS; LEAVE; } -*/ + +void PerlCallback::call(bool b) const +{ + call(b ? 1 : 0); +} #ifdef WIN32 #ifndef NOMINMAX diff --git a/xs/src/slic3r/GUI/3DScene.cpp b/xs/src/slic3r/GUI/3DScene.cpp index cb2e08e1f..09e10ac28 100644 --- a/xs/src/slic3r/GUI/3DScene.cpp +++ b/xs/src/slic3r/GUI/3DScene.cpp @@ -433,6 +433,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; } } @@ -663,7 +664,7 @@ bool GLVolumeCollection::check_outside_state(const DynamicPrintConfig* config) bool contained = true; for (GLVolume* volume : this->volumes) { - if (volume != nullptr) + if ((volume != nullptr) && !volume->is_modifier) { bool state = print_volume.contains(volume->transformed_bounding_box()); contained &= state; @@ -1348,8 +1349,8 @@ static void point_to_indexed_vertex_array(const Point3& point, volume.push_triangle(idxs[0], idxs[3], idxs[4]); } -static void thick_lines_to_verts( - const Lines &lines, +void _3DScene::thick_lines_to_verts( + const Lines &lines, const std::vector<double> &widths, const std::vector<double> &heights, bool closed, @@ -1359,7 +1360,7 @@ static void thick_lines_to_verts( thick_lines_to_indexed_vertex_array(lines, widths, heights, closed, top_z, volume.indexed_vertex_array); } -static void thick_lines_to_verts(const Lines3& lines, +void _3DScene::thick_lines_to_verts(const Lines3& lines, const std::vector<double>& widths, const std::vector<double>& heights, bool closed, @@ -1377,7 +1378,7 @@ static void thick_point_to_verts(const Point3& point, } // Fill in the qverts and tverts with quads and triangles for the extrusion_path. -static inline void extrusionentity_to_verts(const ExtrusionPath &extrusion_path, float print_z, GLVolume &volume) +void _3DScene::extrusionentity_to_verts(const ExtrusionPath &extrusion_path, float print_z, GLVolume &volume) { Lines lines = extrusion_path.polyline.lines(); std::vector<double> widths(lines.size(), extrusion_path.width); @@ -1386,7 +1387,7 @@ static inline void extrusionentity_to_verts(const ExtrusionPath &extrusion_path, } // Fill in the qverts and tverts with quads and triangles for the extrusion_path. -static inline void extrusionentity_to_verts(const ExtrusionPath &extrusion_path, float print_z, const Point ©, GLVolume &volume) +void _3DScene::extrusionentity_to_verts(const ExtrusionPath &extrusion_path, float print_z, const Point ©, GLVolume &volume) { Polyline polyline = extrusion_path.polyline; polyline.remove_duplicate_points(); @@ -1398,7 +1399,7 @@ static inline void extrusionentity_to_verts(const ExtrusionPath &extrusion_path, } // Fill in the qverts and tverts with quads and triangles for the extrusion_loop. -static inline void extrusionentity_to_verts(const ExtrusionLoop &extrusion_loop, float print_z, const Point ©, GLVolume &volume) +void _3DScene::extrusionentity_to_verts(const ExtrusionLoop &extrusion_loop, float print_z, const Point ©, GLVolume &volume) { Lines lines; std::vector<double> widths; @@ -1416,7 +1417,7 @@ static inline void extrusionentity_to_verts(const ExtrusionLoop &extrusion_loop, } // Fill in the qverts and tverts with quads and triangles for the extrusion_multi_path. -static inline void extrusionentity_to_verts(const ExtrusionMultiPath &extrusion_multi_path, float print_z, const Point ©, GLVolume &volume) +void _3DScene::extrusionentity_to_verts(const ExtrusionMultiPath &extrusion_multi_path, float print_z, const Point ©, GLVolume &volume) { Lines lines; std::vector<double> widths; @@ -1433,15 +1434,13 @@ static inline void extrusionentity_to_verts(const ExtrusionMultiPath &extrusion_ thick_lines_to_verts(lines, widths, heights, false, print_z, volume); } -static void extrusionentity_to_verts(const ExtrusionEntity *extrusion_entity, float print_z, const Point ©, GLVolume &volume); - -static inline void extrusionentity_to_verts(const ExtrusionEntityCollection &extrusion_entity_collection, float print_z, const Point ©, GLVolume &volume) +void _3DScene::extrusionentity_to_verts(const ExtrusionEntityCollection &extrusion_entity_collection, float print_z, const Point ©, GLVolume &volume) { for (const ExtrusionEntity *extrusion_entity : extrusion_entity_collection.entities) extrusionentity_to_verts(extrusion_entity, print_z, copy, volume); } -static void extrusionentity_to_verts(const ExtrusionEntity *extrusion_entity, float print_z, const Point ©, GLVolume &volume) +void _3DScene::extrusionentity_to_verts(const ExtrusionEntity *extrusion_entity, float print_z, const Point ©, GLVolume &volume) { if (extrusion_entity != nullptr) { auto *extrusion_path = dynamic_cast<const ExtrusionPath*>(extrusion_entity); @@ -1468,7 +1467,7 @@ static void extrusionentity_to_verts(const ExtrusionEntity *extrusion_entity, fl } } -static void polyline3_to_verts(const Polyline3& polyline, double width, double height, GLVolume& volume) +void _3DScene::polyline3_to_verts(const Polyline3& polyline, double width, double height, GLVolume& volume) { Lines3 lines = polyline.lines(); std::vector<double> widths(lines.size(), width); @@ -1476,14 +1475,14 @@ static void polyline3_to_verts(const Polyline3& polyline, double width, double h thick_lines_to_verts(lines, widths, heights, false, volume); } -static void point3_to_verts(const Point3& point, double width, double height, GLVolume& volume) +void _3DScene::point3_to_verts(const Point3& point, double width, double height, GLVolume& volume) { thick_point_to_verts(point, width, height, volume); } -_3DScene::GCodePreviewVolumeIndex _3DScene::s_gcode_preview_volume_index; _3DScene::LegendTexture _3DScene::s_legend_texture; _3DScene::WarningTexture _3DScene::s_warning_texture; +GUI::GLCanvas3DManager _3DScene::s_canvas_mgr; unsigned int _3DScene::TextureBase::finalize() { @@ -1720,1049 +1719,441 @@ bool _3DScene::LegendTexture::generate(const GCodePreviewData& preview_data, con return true; } -void _3DScene::_glew_init() -{ - glewInit(); +void _3DScene::init_gl() +{ + s_canvas_mgr.init_gl(); } -static inline int hex_digit_to_int(const char c) +std::string _3DScene::get_gl_info(bool format_as_html, bool extensions) { - return - (c >= '0' && c <= '9') ? int(c - '0') : - (c >= 'A' && c <= 'F') ? int(c - 'A') + 10 : - (c >= 'a' && c <= 'f') ? int(c - 'a') + 10 : -1; + return s_canvas_mgr.get_gl_info(format_as_html, extensions); } -static inline std::vector<float> parse_colors(const std::vector<std::string> &scolors) +bool _3DScene::use_VBOs() { - std::vector<float> output(scolors.size() * 4, 1.f); - for (size_t i = 0; i < scolors.size(); ++ i) { - const std::string &scolor = scolors[i]; - const char *c = scolor.data() + 1; - if (scolor.size() == 7 && scolor.front() == '#') { - for (size_t j = 0; j < 3; ++j) { - int digit1 = hex_digit_to_int(*c ++); - int digit2 = hex_digit_to_int(*c ++); - if (digit1 == -1 || digit2 == -1) - break; - output[i * 4 + j] = float(digit1 * 16 + digit2) / 255.f; - } - } - } - return output; + return s_canvas_mgr.use_VBOs(); } -void _3DScene::load_gcode_preview(const Print* print, const GCodePreviewData* preview_data, GLVolumeCollection* volumes, const std::vector<std::string>& str_tool_colors, bool use_VBOs) +bool _3DScene::add_canvas(wxGLCanvas* canvas) { - if ((preview_data == nullptr) || (volumes == nullptr)) - return; - - if (volumes->empty()) - { - std::vector<float> tool_colors = parse_colors(str_tool_colors); - - s_gcode_preview_volume_index.reset(); - - _load_gcode_extrusion_paths(*preview_data, *volumes, tool_colors, use_VBOs); - _load_gcode_travel_paths(*preview_data, *volumes, tool_colors, use_VBOs); - _load_gcode_retractions(*preview_data, *volumes, use_VBOs); - _load_gcode_unretractions(*preview_data, *volumes, use_VBOs); - - if (volumes->empty()) - reset_legend_texture(); - else - { - _generate_legend_texture(*preview_data, tool_colors); - - // removes empty volumes - volumes->volumes.erase(std::remove_if(volumes->volumes.begin(), volumes->volumes.end(), - [](const GLVolume *volume) { return volume->print_zs.empty(); }), - volumes->volumes.end()); - - _load_shells(*print, *volumes, use_VBOs); - } - } - - _update_gcode_volumes_visibility(*preview_data, *volumes); + return s_canvas_mgr.add(canvas); } -unsigned int _3DScene::get_legend_texture_width() +bool _3DScene::remove_canvas(wxGLCanvas* canvas) { - return s_legend_texture.get_texture_width(); + return s_canvas_mgr.remove(canvas); } -unsigned int _3DScene::get_legend_texture_height() +void _3DScene::remove_all_canvases() { - return s_legend_texture.get_texture_height(); + s_canvas_mgr.remove_all(); } -void _3DScene::reset_legend_texture() +bool _3DScene::init(wxGLCanvas* canvas) { - s_legend_texture.reset_texture(); + return s_canvas_mgr.init(canvas); } -unsigned int _3DScene::finalize_legend_texture() +void _3DScene::set_as_dirty(wxGLCanvas* canvas) { - return s_legend_texture.finalize(); + s_canvas_mgr.set_as_dirty(canvas); } -unsigned int _3DScene::get_warning_texture_width() +unsigned int _3DScene::get_volumes_count(wxGLCanvas* canvas) { - return s_warning_texture.get_texture_width(); + return s_canvas_mgr.get_volumes_count(canvas); } -unsigned int _3DScene::get_warning_texture_height() +void _3DScene::reset_volumes(wxGLCanvas* canvas) { - return s_warning_texture.get_texture_height(); + s_canvas_mgr.reset_volumes(canvas); } -void _3DScene::generate_warning_texture(const std::string& msg) +void _3DScene::deselect_volumes(wxGLCanvas* canvas) { - s_warning_texture.generate(msg); + s_canvas_mgr.deselect_volumes(canvas); } -void _3DScene::reset_warning_texture() +void _3DScene::select_volume(wxGLCanvas* canvas, unsigned int id) { - s_warning_texture.reset_texture(); + s_canvas_mgr.select_volume(canvas, id); } -unsigned int _3DScene::finalize_warning_texture() +void _3DScene::update_volumes_selection(wxGLCanvas* canvas, const std::vector<int>& selections) { - return s_warning_texture.finalize(); + s_canvas_mgr.update_volumes_selection(canvas, selections); } -// Create 3D thick extrusion lines for a skirt and brim. -// Adds a new Slic3r::GUI::3DScene::Volume to volumes. -void _3DScene::_load_print_toolpaths( - const Print *print, - GLVolumeCollection *volumes, - const std::vector<std::string> &tool_colors, - bool use_VBOs) +bool _3DScene::check_volumes_outside_state(wxGLCanvas* canvas, const DynamicPrintConfig* config) { - if (!print->has_skirt() && print->config.brim_width.value == 0) - return; - - const float color[] = { 0.5f, 1.0f, 0.5f, 1.f }; // greenish - - // number of skirt layers - size_t total_layer_count = 0; - for (const PrintObject *print_object : print->objects) - total_layer_count = std::max(total_layer_count, print_object->total_layer_count()); - size_t skirt_height = print->has_infinite_skirt() ? - total_layer_count : - std::min<size_t>(print->config.skirt_height.value, total_layer_count); - if (skirt_height == 0 && 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 = 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()); - - volumes->volumes.emplace_back(new GLVolume(color)); - GLVolume &volume = *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) - extrusionentity_to_verts(print->brim, print_zs[i], Point(0, 0), volume); - extrusionentity_to_verts(print->skirt, print_zs[i], Point(0, 0), volume); - } - volume.bounding_box = volume.indexed_vertex_array.bounding_box(); - volume.indexed_vertex_array.finalize_geometry(use_VBOs); + return s_canvas_mgr.check_volumes_outside_state(canvas, config); } -// 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 _3DScene::_load_print_object_toolpaths( - const PrintObject *print_object, - GLVolumeCollection *volumes, - const std::vector<std::string> &tool_colors_str, - bool use_VBOs) +bool _3DScene::move_volume_up(wxGLCanvas* canvas, unsigned int id) { - std::vector<float> tool_colors = parse_colors(tool_colors_str); - - 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 = [volumes, &new_volume_mutex](const float *color) -> GLVolume* { - auto *volume = new GLVolume(color); - new_volume_mutex.lock(); - volume->outside_printer_detection_enabled = false; - volumes->volumes.emplace_back(volume); - new_volume_mutex.unlock(); - return volume; - }; - const size_t volumes_cnt_initial = 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) - 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()) - 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) - 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. - volumes->volumes.erase( - std::remove_if(volumes->volumes.begin() + volumes_cnt_initial, volumes->volumes.end(), - [](const GLVolume *volume) { return volume->empty(); }), - volumes->volumes.end()); - for (size_t i = volumes_cnt_initial; i < volumes->volumes.size(); ++ i) - volumes->volumes[i]->indexed_vertex_array.finalize_geometry(use_VBOs); - - BOOST_LOG_TRIVIAL(debug) << "Loading print object toolpaths in parallel - end"; -} - -void _3DScene::_load_wipe_tower_toolpaths( - const Print *print, - GLVolumeCollection *volumes, - const std::vector<std::string> &tool_colors_str, - bool use_VBOs) -{ - if (print->m_wipe_tower_tool_changes.empty()) - return; - - std::vector<float> tool_colors = parse_colors(tool_colors_str); - - 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 = print; - ctxt.tool_colors = tool_colors.empty() ? nullptr : &tool_colors; - if (print->m_wipe_tower_priming) - ctxt.priming.emplace_back(*print->m_wipe_tower_priming.get()); - if (print->m_wipe_tower_final_purge) - ctxt.final.emplace_back(*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 = 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 = [volumes, &new_volume_mutex](const float *color) -> GLVolume* { - auto *volume = new GLVolume(color); - new_volume_mutex.lock(); - volume->outside_printer_detection_enabled = false; - volumes->volumes.emplace_back(volume); - new_volume_mutex.unlock(); - return volume; - }; - const size_t volumes_cnt_initial = 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); - } - 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. - volumes->volumes.erase( - std::remove_if(volumes->volumes.begin() + volumes_cnt_initial, volumes->volumes.end(), - [](const GLVolume *volume) { return volume->empty(); }), - volumes->volumes.end()); - for (size_t i = volumes_cnt_initial; i < volumes->volumes.size(); ++ i) - volumes->volumes[i]->indexed_vertex_array.finalize_geometry(use_VBOs); - - BOOST_LOG_TRIVIAL(debug) << "Loading wipe tower toolpaths in parallel - end"; + return s_canvas_mgr.move_volume_up(canvas, id); } -void _3DScene::_load_gcode_extrusion_paths(const GCodePreviewData& preview_data, GLVolumeCollection& volumes, const std::vector<float>& tool_colors, bool use_VBOs) +bool _3DScene::move_volume_down(wxGLCanvas* canvas, unsigned int id) { - // helper functions to select data in dependence of the extrusion view type - struct Helper - { - static float path_filter(GCodePreviewData::Extrusion::EViewType type, const ExtrusionPath& path) - { - switch (type) - { - case GCodePreviewData::Extrusion::FeatureType: - return (float)path.role(); - case GCodePreviewData::Extrusion::Height: - return path.height; - case GCodePreviewData::Extrusion::Width: - return path.width; - case GCodePreviewData::Extrusion::Feedrate: - return path.feedrate; - case GCodePreviewData::Extrusion::VolumetricRate: - return path.feedrate * (float)path.mm3_per_mm; - case GCodePreviewData::Extrusion::Tool: - return (float)path.extruder_id; - } - - return 0.0f; - } - - static GCodePreviewData::Color path_color(const GCodePreviewData& data, const std::vector<float>& tool_colors, float value) - { - switch (data.extrusion.view_type) - { - case GCodePreviewData::Extrusion::FeatureType: - return data.get_extrusion_role_color((ExtrusionRole)(int)value); - case GCodePreviewData::Extrusion::Height: - return data.get_height_color(value); - case GCodePreviewData::Extrusion::Width: - return data.get_width_color(value); - case GCodePreviewData::Extrusion::Feedrate: - return data.get_feedrate_color(value); - case GCodePreviewData::Extrusion::VolumetricRate: - return data.get_volumetric_rate_color(value); - case GCodePreviewData::Extrusion::Tool: - { - GCodePreviewData::Color color; - ::memcpy((void*)color.rgba, (const void*)(tool_colors.data() + (unsigned int)value * 4), 4 * sizeof(float)); - return color; - } - } - - return GCodePreviewData::Color::Dummy; - } - }; - - // Helper structure for filters - struct Filter - { - float value; - ExtrusionRole role; - GLVolume* volume; - - Filter(float value, ExtrusionRole role) - : value(value) - , role(role) - , volume(nullptr) - { - } - - bool operator == (const Filter& other) const - { - if (value != other.value) - return false; - - if (role != other.role) - return false; - - return true; - } - }; + return s_canvas_mgr.move_volume_down(canvas, id); +} - typedef std::vector<Filter> FiltersList; - size_t initial_volumes_count = volumes.volumes.size(); +void _3DScene::set_objects_selections(wxGLCanvas* canvas, const std::vector<int>& selections) +{ + s_canvas_mgr.set_objects_selections(canvas, selections); +} - // detects filters - FiltersList filters; - for (const GCodePreviewData::Extrusion::Layer& layer : preview_data.extrusion.layers) - { - for (const ExtrusionPath& path : layer.paths) - { - ExtrusionRole role = path.role(); - float path_filter = Helper::path_filter(preview_data.extrusion.view_type, path); - if (std::find(filters.begin(), filters.end(), Filter(path_filter, role)) == filters.end()) - filters.emplace_back(path_filter, role); - } - } +void _3DScene::set_config(wxGLCanvas* canvas, DynamicPrintConfig* config) +{ + s_canvas_mgr.set_config(canvas, config); +} - // nothing to render, return - if (filters.empty()) - return; +void _3DScene::set_print(wxGLCanvas* canvas, Print* print) +{ + s_canvas_mgr.set_print(canvas, print); +} - // creates a new volume for each filter - for (Filter& filter : filters) - { - s_gcode_preview_volume_index.first_volumes.emplace_back(GCodePreviewVolumeIndex::Extrusion, (unsigned int)filter.role, (unsigned int)volumes.volumes.size()); - GLVolume* volume = new GLVolume(Helper::path_color(preview_data, tool_colors, filter.value).rgba); - if (volume != nullptr) - { - filter.volume = volume; - volumes.volumes.emplace_back(volume); - } - else - { - // an error occourred - restore to previous state and return - s_gcode_preview_volume_index.first_volumes.pop_back(); - if (initial_volumes_count != volumes.volumes.size()) - { - std::vector<GLVolume*>::iterator begin = volumes.volumes.begin() + initial_volumes_count; - std::vector<GLVolume*>::iterator end = volumes.volumes.end(); - for (std::vector<GLVolume*>::iterator it = begin; it < end; ++it) - { - GLVolume* volume = *it; - delete volume; - } - volumes.volumes.erase(begin, end); - return; - } - } - } +void _3DScene::set_model(wxGLCanvas* canvas, Model* model) +{ + s_canvas_mgr.set_model(canvas, model); +} - // populates volumes - for (const GCodePreviewData::Extrusion::Layer& layer : preview_data.extrusion.layers) - { - for (const ExtrusionPath& path : layer.paths) - { - float path_filter = Helper::path_filter(preview_data.extrusion.view_type, path); - FiltersList::iterator filter = std::find(filters.begin(), filters.end(), Filter(path_filter, path.role())); - if (filter != filters.end()) - { - filter->volume->print_zs.push_back(layer.z); - filter->volume->offsets.push_back(filter->volume->indexed_vertex_array.quad_indices.size()); - filter->volume->offsets.push_back(filter->volume->indexed_vertex_array.triangle_indices.size()); +void _3DScene::set_bed_shape(wxGLCanvas* canvas, const Pointfs& shape) +{ + return s_canvas_mgr.set_bed_shape(canvas, shape); +} - extrusionentity_to_verts(path, layer.z, *filter->volume); - } - } - } +void _3DScene::set_auto_bed_shape(wxGLCanvas* canvas) +{ + return s_canvas_mgr.set_auto_bed_shape(canvas); +} - // finalize volumes and sends geometry to gpu - if (volumes.volumes.size() > initial_volumes_count) - { - for (size_t i = initial_volumes_count; i < volumes.volumes.size(); ++i) - { - GLVolume* volume = volumes.volumes[i]; - volume->bounding_box = volume->indexed_vertex_array.bounding_box(); - volume->indexed_vertex_array.finalize_geometry(use_VBOs); - } - } +BoundingBoxf3 _3DScene::get_volumes_bounding_box(wxGLCanvas* canvas) +{ + return s_canvas_mgr.get_volumes_bounding_box(canvas); } -void _3DScene::_load_gcode_travel_paths(const GCodePreviewData& preview_data, GLVolumeCollection& volumes, const std::vector<float>& tool_colors, bool use_VBOs) +void _3DScene::set_axes_length(wxGLCanvas* canvas, float length) { - size_t initial_volumes_count = volumes.volumes.size(); - s_gcode_preview_volume_index.first_volumes.emplace_back(GCodePreviewVolumeIndex::Travel, 0, (unsigned int)initial_volumes_count); + s_canvas_mgr.set_axes_length(canvas, length); +} - bool res = true; - switch (preview_data.extrusion.view_type) - { - case GCodePreviewData::Extrusion::Feedrate: - { - res = _travel_paths_by_feedrate(preview_data, volumes); - break; - } - case GCodePreviewData::Extrusion::Tool: - { - res = _travel_paths_by_tool(preview_data, volumes, tool_colors); - break; - } - default: - { - res = _travel_paths_by_type(preview_data, volumes); - break; - } - } +void _3DScene::set_cutting_plane(wxGLCanvas* canvas, float z, const ExPolygons& polygons) +{ + return s_canvas_mgr.set_cutting_plane(canvas, z, polygons); +} - if (!res) - { - // an error occourred - restore to previous state and return - if (initial_volumes_count != volumes.volumes.size()) - { - std::vector<GLVolume*>::iterator begin = volumes.volumes.begin() + initial_volumes_count; - std::vector<GLVolume*>::iterator end = volumes.volumes.end(); - for (std::vector<GLVolume*>::iterator it = begin; it < end; ++it) - { - GLVolume* volume = *it; - delete volume; - } - volumes.volumes.erase(begin, end); - } +void _3DScene::set_color_by(wxGLCanvas* canvas, const std::string& value) +{ + return s_canvas_mgr.set_color_by(canvas, value); +} - return; - } +void _3DScene::set_select_by(wxGLCanvas* canvas, const std::string& value) +{ + return s_canvas_mgr.set_select_by(canvas, value); +} - // finalize volumes and sends geometry to gpu - if (volumes.volumes.size() > initial_volumes_count) - { - for (size_t i = initial_volumes_count; i < volumes.volumes.size(); ++i) - { - GLVolume* volume = volumes.volumes[i]; - volume->bounding_box = volume->indexed_vertex_array.bounding_box(); - volume->indexed_vertex_array.finalize_geometry(use_VBOs); - } - } +void _3DScene::set_drag_by(wxGLCanvas* canvas, const std::string& value) +{ + return s_canvas_mgr.set_drag_by(canvas, value); } -bool _3DScene::_travel_paths_by_type(const GCodePreviewData& preview_data, GLVolumeCollection& volumes) +bool _3DScene::is_layers_editing_enabled(wxGLCanvas* canvas) { - // Helper structure for types - struct Type - { - GCodePreviewData::Travel::EType value; - GLVolume* volume; + return s_canvas_mgr.is_layers_editing_enabled(canvas); +} - explicit Type(GCodePreviewData::Travel::EType value) - : value(value) - , volume(nullptr) - { - } +bool _3DScene::is_layers_editing_allowed(wxGLCanvas* canvas) +{ + return s_canvas_mgr.is_layers_editing_allowed(canvas); +} - bool operator == (const Type& other) const - { - return value == other.value; - } - }; +bool _3DScene::is_shader_enabled(wxGLCanvas* canvas) +{ + return s_canvas_mgr.is_shader_enabled(canvas); +} - typedef std::vector<Type> TypesList; +bool _3DScene::is_reload_delayed(wxGLCanvas* canvas) +{ + return s_canvas_mgr.is_reload_delayed(canvas); +} - // colors travels by travel type +void _3DScene::enable_layers_editing(wxGLCanvas* canvas, bool enable) +{ + s_canvas_mgr.enable_layers_editing(canvas, enable); +} - // detects types - TypesList types; - for (const GCodePreviewData::Travel::Polyline& polyline : preview_data.travel.polylines) - { - if (std::find(types.begin(), types.end(), Type(polyline.type)) == types.end()) - types.emplace_back(polyline.type); - } +void _3DScene::enable_warning_texture(wxGLCanvas* canvas, bool enable) +{ + s_canvas_mgr.enable_warning_texture(canvas, enable); +} - // nothing to render, return - if (types.empty()) - return true; +void _3DScene::enable_legend_texture(wxGLCanvas* canvas, bool enable) +{ + s_canvas_mgr.enable_legend_texture(canvas, enable); +} - // creates a new volume for each type - for (Type& type : types) - { - GLVolume* volume = new GLVolume(preview_data.travel.type_colors[type.value].rgba); - if (volume == nullptr) - return false; - else - { - type.volume = volume; - volumes.volumes.emplace_back(volume); - } - } +void _3DScene::enable_picking(wxGLCanvas* canvas, bool enable) +{ + s_canvas_mgr.enable_picking(canvas, enable); +} - // populates volumes - for (const GCodePreviewData::Travel::Polyline& polyline : preview_data.travel.polylines) - { - TypesList::iterator type = std::find(types.begin(), types.end(), Type(polyline.type)); - if (type != types.end()) - { - type->volume->print_zs.push_back(unscale(polyline.polyline.bounding_box().min.z)); - type->volume->offsets.push_back(type->volume->indexed_vertex_array.quad_indices.size()); - type->volume->offsets.push_back(type->volume->indexed_vertex_array.triangle_indices.size()); +void _3DScene::enable_moving(wxGLCanvas* canvas, bool enable) +{ + s_canvas_mgr.enable_moving(canvas, enable); +} - polyline3_to_verts(polyline.polyline, preview_data.travel.width, preview_data.travel.height, *type->volume); - } - } +void _3DScene::enable_gizmos(wxGLCanvas* canvas, bool enable) +{ + s_canvas_mgr.enable_gizmos(canvas, enable); +} - return true; +void _3DScene::enable_shader(wxGLCanvas* canvas, bool enable) +{ + s_canvas_mgr.enable_shader(canvas, enable); } -bool _3DScene::_travel_paths_by_feedrate(const GCodePreviewData& preview_data, GLVolumeCollection& volumes) +void _3DScene::enable_force_zoom_to_bed(wxGLCanvas* canvas, bool enable) { - // Helper structure for feedrate - struct Feedrate - { - float value; - GLVolume* volume; + s_canvas_mgr.enable_force_zoom_to_bed(canvas, enable); +} - explicit Feedrate(float value) - : value(value) - , volume(nullptr) - { - } +void _3DScene::allow_multisample(wxGLCanvas* canvas, bool allow) +{ + s_canvas_mgr.allow_multisample(canvas, allow); +} - bool operator == (const Feedrate& other) const - { - return value == other.value; - } - }; +void _3DScene::zoom_to_bed(wxGLCanvas* canvas) +{ + s_canvas_mgr.zoom_to_bed(canvas); +} - typedef std::vector<Feedrate> FeedratesList; +void _3DScene::zoom_to_volumes(wxGLCanvas* canvas) +{ + s_canvas_mgr.zoom_to_volumes(canvas); +} - // colors travels by feedrate +void _3DScene::select_view(wxGLCanvas* canvas, const std::string& direction) +{ + s_canvas_mgr.select_view(canvas, direction); +} - // detects feedrates - FeedratesList feedrates; - for (const GCodePreviewData::Travel::Polyline& polyline : preview_data.travel.polylines) - { - if (std::find(feedrates.begin(), feedrates.end(), Feedrate(polyline.feedrate)) == feedrates.end()) - feedrates.emplace_back(polyline.feedrate); - } +void _3DScene::set_viewport_from_scene(wxGLCanvas* canvas, wxGLCanvas* other) +{ + s_canvas_mgr.set_viewport_from_scene(canvas, other); +} - // nothing to render, return - if (feedrates.empty()) - return true; +void _3DScene::update_volumes_colors_by_extruder(wxGLCanvas* canvas) +{ + s_canvas_mgr.update_volumes_colors_by_extruder(canvas); +} - // creates a new volume for each feedrate - for (Feedrate& feedrate : feedrates) - { - GLVolume* volume = new GLVolume(preview_data.get_feedrate_color(feedrate.value).rgba); - if (volume == nullptr) - return false; - else - { - feedrate.volume = volume; - volumes.volumes.emplace_back(volume); - } - } +void _3DScene::render(wxGLCanvas* canvas) +{ + s_canvas_mgr.render(canvas); +} - // populates volumes - for (const GCodePreviewData::Travel::Polyline& polyline : preview_data.travel.polylines) - { - FeedratesList::iterator feedrate = std::find(feedrates.begin(), feedrates.end(), Feedrate(polyline.feedrate)); - if (feedrate != feedrates.end()) - { - feedrate->volume->print_zs.push_back(unscale(polyline.polyline.bounding_box().min.z)); - feedrate->volume->offsets.push_back(feedrate->volume->indexed_vertex_array.quad_indices.size()); - feedrate->volume->offsets.push_back(feedrate->volume->indexed_vertex_array.triangle_indices.size()); +std::vector<double> _3DScene::get_current_print_zs(wxGLCanvas* canvas, bool active_only) +{ + return s_canvas_mgr.get_current_print_zs(canvas, active_only); +} - polyline3_to_verts(polyline.polyline, preview_data.travel.width, preview_data.travel.height, *feedrate->volume); - } - } +void _3DScene::set_toolpaths_range(wxGLCanvas* canvas, double low, double high) +{ + s_canvas_mgr.set_toolpaths_range(canvas, low, high); +} - return true; +void _3DScene::register_on_viewport_changed_callback(wxGLCanvas* canvas, void* callback) +{ + s_canvas_mgr.register_on_viewport_changed_callback(canvas, callback); } -bool _3DScene::_travel_paths_by_tool(const GCodePreviewData& preview_data, GLVolumeCollection& volumes, const std::vector<float>& tool_colors) +void _3DScene::register_on_double_click_callback(wxGLCanvas* canvas, void* callback) { - // Helper structure for tool - struct Tool - { - unsigned int value; - GLVolume* volume; + s_canvas_mgr.register_on_double_click_callback(canvas, callback); +} - explicit Tool(unsigned int value) - : value(value) - , volume(nullptr) - { - } +void _3DScene::register_on_right_click_callback(wxGLCanvas* canvas, void* callback) +{ + s_canvas_mgr.register_on_right_click_callback(canvas, callback); +} - bool operator == (const Tool& other) const - { - return value == other.value; - } - }; +void _3DScene::register_on_select_object_callback(wxGLCanvas* canvas, void* callback) +{ + s_canvas_mgr.register_on_select_object_callback(canvas, callback); +} - typedef std::vector<Tool> ToolsList; +void _3DScene::register_on_model_update_callback(wxGLCanvas* canvas, void* callback) +{ + s_canvas_mgr.register_on_model_update_callback(canvas, callback); +} - // colors travels by tool +void _3DScene::register_on_remove_object_callback(wxGLCanvas* canvas, void* callback) +{ + s_canvas_mgr.register_on_remove_object_callback(canvas, callback); +} - // detects tools - ToolsList tools; - for (const GCodePreviewData::Travel::Polyline& polyline : preview_data.travel.polylines) - { - if (std::find(tools.begin(), tools.end(), Tool(polyline.extruder_id)) == tools.end()) - tools.emplace_back(polyline.extruder_id); - } +void _3DScene::register_on_arrange_callback(wxGLCanvas* canvas, void* callback) +{ + s_canvas_mgr.register_on_arrange_callback(canvas, callback); +} - // nothing to render, return - if (tools.empty()) - return true; +void _3DScene::register_on_rotate_object_left_callback(wxGLCanvas* canvas, void* callback) +{ + s_canvas_mgr.register_on_rotate_object_left_callback(canvas, callback); +} - // creates a new volume for each tool - for (Tool& tool : tools) - { - GLVolume* volume = new GLVolume(tool_colors.data() + tool.value * 4); - if (volume == nullptr) - return false; - else - { - tool.volume = volume; - volumes.volumes.emplace_back(volume); - } - } +void _3DScene::register_on_rotate_object_right_callback(wxGLCanvas* canvas, void* callback) +{ + s_canvas_mgr.register_on_rotate_object_right_callback(canvas, callback); +} - // populates volumes - for (const GCodePreviewData::Travel::Polyline& polyline : preview_data.travel.polylines) - { - ToolsList::iterator tool = std::find(tools.begin(), tools.end(), Tool(polyline.extruder_id)); - if (tool != tools.end()) - { - tool->volume->print_zs.push_back(unscale(polyline.polyline.bounding_box().min.z)); - tool->volume->offsets.push_back(tool->volume->indexed_vertex_array.quad_indices.size()); - tool->volume->offsets.push_back(tool->volume->indexed_vertex_array.triangle_indices.size()); +void _3DScene::register_on_scale_object_uniformly_callback(wxGLCanvas* canvas, void* callback) +{ + s_canvas_mgr.register_on_scale_object_uniformly_callback(canvas, callback); +} - polyline3_to_verts(polyline.polyline, preview_data.travel.width, preview_data.travel.height, *tool->volume); - } - } +void _3DScene::register_on_increase_objects_callback(wxGLCanvas* canvas, void* callback) +{ + s_canvas_mgr.register_on_increase_objects_callback(canvas, callback); +} - return true; +void _3DScene::register_on_decrease_objects_callback(wxGLCanvas* canvas, void* callback) +{ + s_canvas_mgr.register_on_decrease_objects_callback(canvas, callback); } -void _3DScene::_load_gcode_retractions(const GCodePreviewData& preview_data, GLVolumeCollection& volumes, bool use_VBOs) +void _3DScene::register_on_instance_moved_callback(wxGLCanvas* canvas, void* callback) { - s_gcode_preview_volume_index.first_volumes.emplace_back(GCodePreviewVolumeIndex::Retraction, 0, (unsigned int)volumes.volumes.size()); + s_canvas_mgr.register_on_instance_moved_callback(canvas, callback); +} - // nothing to render, return - if (preview_data.retraction.positions.empty()) - return; +void _3DScene::register_on_wipe_tower_moved_callback(wxGLCanvas* canvas, void* callback) +{ + s_canvas_mgr.register_on_wipe_tower_moved_callback(canvas, callback); +} - GLVolume* volume = new GLVolume(preview_data.retraction.color.rgba); - if (volume != nullptr) - { - volumes.volumes.emplace_back(volume); +void _3DScene::register_on_enable_action_buttons_callback(wxGLCanvas* canvas, void* callback) +{ + s_canvas_mgr.register_on_enable_action_buttons_callback(canvas, callback); +} - GCodePreviewData::Retraction::PositionsList copy(preview_data.retraction.positions); - std::sort(copy.begin(), copy.end(), [](const GCodePreviewData::Retraction::Position& p1, const GCodePreviewData::Retraction::Position& p2){ return p1.position.z < p2.position.z; }); +void _3DScene::register_on_gizmo_scale_uniformly_callback(wxGLCanvas* canvas, void* callback) +{ + s_canvas_mgr.register_on_gizmo_scale_uniformly_callback(canvas, callback); +} - for (const GCodePreviewData::Retraction::Position& position : copy) - { - volume->print_zs.push_back(unscale(position.position.z)); - volume->offsets.push_back(volume->indexed_vertex_array.quad_indices.size()); - volume->offsets.push_back(volume->indexed_vertex_array.triangle_indices.size()); +static inline int hex_digit_to_int(const char c) +{ + return + (c >= '0' && c <= '9') ? int(c - '0') : + (c >= 'A' && c <= 'F') ? int(c - 'A') + 10 : + (c >= 'a' && c <= 'f') ? int(c - 'a') + 10 : -1; +} - point3_to_verts(position.position, position.width, position.height, *volume); +static inline std::vector<float> parse_colors(const std::vector<std::string> &scolors) +{ + std::vector<float> output(scolors.size() * 4, 1.f); + for (size_t i = 0; i < scolors.size(); ++ i) { + const std::string &scolor = scolors[i]; + const char *c = scolor.data() + 1; + if (scolor.size() == 7 && scolor.front() == '#') { + for (size_t j = 0; j < 3; ++j) { + int digit1 = hex_digit_to_int(*c ++); + int digit2 = hex_digit_to_int(*c ++); + if (digit1 == -1 || digit2 == -1) + break; + output[i * 4 + j] = float(digit1 * 16 + digit2) / 255.f; + } } - - // finalize volumes and sends geometry to gpu - volume->bounding_box = volume->indexed_vertex_array.bounding_box(); - volume->indexed_vertex_array.finalize_geometry(use_VBOs); } + return output; } -void _3DScene::_load_gcode_unretractions(const GCodePreviewData& preview_data, GLVolumeCollection& volumes, bool use_VBOs) +std::vector<int> _3DScene::load_object(wxGLCanvas* canvas, const ModelObject* model_object, int obj_idx, std::vector<int> instance_idxs) { - s_gcode_preview_volume_index.first_volumes.emplace_back(GCodePreviewVolumeIndex::Unretraction, 0, (unsigned int)volumes.volumes.size()); - - // nothing to render, return - if (preview_data.unretraction.positions.empty()) - return; + return s_canvas_mgr.load_object(canvas, model_object, obj_idx, instance_idxs); +} - GLVolume* volume = new GLVolume(preview_data.unretraction.color.rgba); - if (volume != nullptr) - { - volumes.volumes.emplace_back(volume); +std::vector<int> _3DScene::load_object(wxGLCanvas* canvas, const Model* model, int obj_idx) +{ + return s_canvas_mgr.load_object(canvas, model, obj_idx); +} - GCodePreviewData::Retraction::PositionsList copy(preview_data.unretraction.positions); - std::sort(copy.begin(), copy.end(), [](const GCodePreviewData::Retraction::Position& p1, const GCodePreviewData::Retraction::Position& p2){ return p1.position.z < p2.position.z; }); +void _3DScene::reload_scene(wxGLCanvas* canvas, bool force) +{ + s_canvas_mgr.reload_scene(canvas, force); +} - for (const GCodePreviewData::Retraction::Position& position : copy) - { - volume->print_zs.push_back(unscale(position.position.z)); - volume->offsets.push_back(volume->indexed_vertex_array.quad_indices.size()); - volume->offsets.push_back(volume->indexed_vertex_array.triangle_indices.size()); +void _3DScene::load_print_toolpaths(wxGLCanvas* canvas) +{ + s_canvas_mgr.load_print_toolpaths(canvas); +} - point3_to_verts(position.position, position.width, position.height, *volume); - } +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); +} - // finalize volumes and sends geometry to gpu - volume->bounding_box = volume->indexed_vertex_array.bounding_box(); - volume->indexed_vertex_array.finalize_geometry(use_VBOs); - } +void _3DScene::load_wipe_tower_toolpaths(wxGLCanvas* canvas, const std::vector<std::string>& str_tool_colors) +{ + s_canvas_mgr.load_wipe_tower_toolpaths(canvas, str_tool_colors); } -void _3DScene::_update_gcode_volumes_visibility(const GCodePreviewData& preview_data, GLVolumeCollection& volumes) +void _3DScene::load_gcode_preview(wxGLCanvas* canvas, const GCodePreviewData* preview_data, const std::vector<std::string>& str_tool_colors) { - unsigned int size = (unsigned int)s_gcode_preview_volume_index.first_volumes.size(); - for (unsigned int i = 0; i < size; ++i) - { - std::vector<GLVolume*>::iterator begin = volumes.volumes.begin() + s_gcode_preview_volume_index.first_volumes[i].id; - std::vector<GLVolume*>::iterator end = (i + 1 < size) ? volumes.volumes.begin() + s_gcode_preview_volume_index.first_volumes[i + 1].id : volumes.volumes.end(); + s_canvas_mgr.load_gcode_preview(canvas, preview_data, str_tool_colors); +} - for (std::vector<GLVolume*>::iterator it = begin; it != end; ++it) - { - GLVolume* volume = *it; - volume->outside_printer_detection_enabled = false; +void _3DScene::generate_legend_texture(const GCodePreviewData& preview_data, const std::vector<float>& tool_colors) +{ + s_legend_texture.generate(preview_data, tool_colors); +} - switch (s_gcode_preview_volume_index.first_volumes[i].type) - { - case GCodePreviewVolumeIndex::Extrusion: - { - if ((ExtrusionRole)s_gcode_preview_volume_index.first_volumes[i].flag == erCustom) - volume->zoom_to_volumes = false; - - volume->is_active = preview_data.extrusion.is_role_flag_set((ExtrusionRole)s_gcode_preview_volume_index.first_volumes[i].flag); - break; - } - case GCodePreviewVolumeIndex::Travel: - { - volume->is_active = preview_data.travel.is_visible; - volume->zoom_to_volumes = false; - break; - } - case GCodePreviewVolumeIndex::Retraction: - { - volume->is_active = preview_data.retraction.is_visible; - volume->zoom_to_volumes = false; - break; - } - case GCodePreviewVolumeIndex::Unretraction: - { - volume->is_active = preview_data.unretraction.is_visible; - volume->zoom_to_volumes = false; - break; - } - case GCodePreviewVolumeIndex::Shell: - { - volume->is_active = preview_data.shell.is_visible; - volume->color[3] = 0.25f; - volume->zoom_to_volumes = false; - break; - } - default: - { - volume->is_active = false; - volume->zoom_to_volumes = false; - break; - } - } - } - } +unsigned int _3DScene::get_legend_texture_width() +{ + return s_legend_texture.get_texture_width(); } -void _3DScene::_generate_legend_texture(const GCodePreviewData& preview_data, const std::vector<float>& tool_colors) +unsigned int _3DScene::get_legend_texture_height() { - s_legend_texture.generate(preview_data, tool_colors); + return s_legend_texture.get_texture_height(); } -void _3DScene::_load_shells(const Print& print, GLVolumeCollection& volumes, bool use_VBOs) +void _3DScene::reset_legend_texture() { - size_t initial_volumes_count = volumes.volumes.size(); - s_gcode_preview_volume_index.first_volumes.emplace_back(GCodePreviewVolumeIndex::Shell, 0, (unsigned int)initial_volumes_count); + s_legend_texture.reset_texture(); +} - if (print.objects.empty()) - // nothing to render, return - return; +unsigned int _3DScene::finalize_legend_texture() +{ + return s_legend_texture.finalize(); +} - // adds objects' volumes - unsigned int object_id = 0; - for (PrintObject* obj : print.objects) - { - ModelObject* model_obj = obj->model_object(); +unsigned int _3DScene::get_warning_texture_width() +{ + return s_warning_texture.get_texture_width(); +} - std::vector<int> instance_ids(model_obj->instances.size()); - for (int i = 0; i < model_obj->instances.size(); ++i) - { - instance_ids[i] = i; - } +unsigned int _3DScene::get_warning_texture_height() +{ + return s_warning_texture.get_texture_height(); +} - for (ModelInstance* instance : model_obj->instances) - { - volumes.load_object(model_obj, object_id, instance_ids, "object", "object", "object", use_VBOs); - } +void _3DScene::generate_warning_texture(const std::string& msg) +{ + s_warning_texture.generate(msg); +} - ++object_id; - } +void _3DScene::reset_warning_texture() +{ + s_warning_texture.reset_texture(); +} - // adds wipe tower's volume - coordf_t max_z = print.objects[0]->model_object()->get_model()->bounding_box().max.z; - const PrintConfig& config = print.config; - unsigned int extruders_count = config.nozzle_diameter.size(); - if ((extruders_count > 1) && config.single_extruder_multi_material && config.wipe_tower && !config.complete_objects) { - const float width_per_extruder = 15.f; // a simple workaround after wipe_tower_per_color_wipe got obsolete - volumes.load_wipe_tower_preview(1000, config.wipe_tower_x, config.wipe_tower_y, config.wipe_tower_width, width_per_extruder * (extruders_count - 1), max_z, config.wipe_tower_rotation_angle, use_VBOs); - } +unsigned int _3DScene::finalize_warning_texture() +{ + return s_warning_texture.finalize(); } } // namespace Slic3r diff --git a/xs/src/slic3r/GUI/3DScene.hpp b/xs/src/slic3r/GUI/3DScene.hpp index a417f5f9d..9016f984d 100644 --- a/xs/src/slic3r/GUI/3DScene.hpp +++ b/xs/src/slic3r/GUI/3DScene.hpp @@ -6,8 +6,10 @@ #include "../../libslic3r/Line.hpp" #include "../../libslic3r/TriangleMesh.hpp" #include "../../libslic3r/Utils.hpp" +#include "../../slic3r/GUI/GLCanvas3DManager.hpp" class wxBitmap; +class wxWindow; namespace Slic3r { @@ -17,6 +19,11 @@ class Model; class ModelObject; class GCodePreviewData; class DynamicPrintConfig; +class ExtrusionPath; +class ExtrusionMultiPath; +class ExtrusionLoop; +class ExtrusionEntity; +class ExtrusionEntityCollection; // A container for interleaved arrays of 3D vertices and normals, // possibly indexed by triangles and / or quads. @@ -305,9 +312,9 @@ public: // Boolean: Is mouse over this object? bool hover; // Wheter or not this volume has been generated from a modifier - bool is_modifier; + bool is_modifier; // Wheter or not this volume has been generated from the wipe tower - bool is_wipe_tower; + bool is_wipe_tower; // Interleaved triangles & normals with indexed triangles & quads. GLIndexedVertexArray indexed_vertex_array; @@ -437,35 +444,6 @@ private: class _3DScene { - struct GCodePreviewVolumeIndex - { - enum EType - { - Extrusion, - Travel, - Retraction, - Unretraction, - Shell, - Num_Geometry_Types - }; - - struct FirstVolume - { - EType type; - unsigned int flag; - // Index of the first volume in a GLVolumeCollection. - unsigned int id; - - FirstVolume(EType type, unsigned int flag, unsigned int id) : type(type), flag(flag), id(id) {} - }; - - std::vector<FirstVolume> first_volumes; - - void reset() { first_volumes.clear(); } - }; - - static GCodePreviewVolumeIndex s_gcode_preview_volume_index; - class TextureBase { protected: @@ -525,12 +503,106 @@ class _3DScene static LegendTexture s_legend_texture; static WarningTexture s_warning_texture; + static GUI::GLCanvas3DManager s_canvas_mgr; public: - static void _glew_init(); + static void init_gl(); + static std::string get_gl_info(bool format_as_html, bool extensions); + static bool use_VBOs(); + + static bool add_canvas(wxGLCanvas* canvas); + static bool remove_canvas(wxGLCanvas* canvas); + static void remove_all_canvases(); + + static bool init(wxGLCanvas* canvas); + + static void set_as_dirty(wxGLCanvas* canvas); + + static unsigned int get_volumes_count(wxGLCanvas* canvas); + static void reset_volumes(wxGLCanvas* canvas); + static void deselect_volumes(wxGLCanvas* canvas); + static void select_volume(wxGLCanvas* canvas, unsigned int id); + static void update_volumes_selection(wxGLCanvas* canvas, const std::vector<int>& selections); + static bool check_volumes_outside_state(wxGLCanvas* canvas, const DynamicPrintConfig* config); + static bool move_volume_up(wxGLCanvas* canvas, unsigned int id); + static bool move_volume_down(wxGLCanvas* canvas, unsigned int id); + + static void set_objects_selections(wxGLCanvas* canvas, const std::vector<int>& selections); + + static void set_config(wxGLCanvas* canvas, DynamicPrintConfig* config); + static void set_print(wxGLCanvas* canvas, Print* print); + static void set_model(wxGLCanvas* canvas, Model* model); + + static void set_bed_shape(wxGLCanvas* canvas, const Pointfs& shape); + static void set_auto_bed_shape(wxGLCanvas* canvas); + + static BoundingBoxf3 get_volumes_bounding_box(wxGLCanvas* canvas); + + static void set_axes_length(wxGLCanvas* canvas, float length); - static void load_gcode_preview(const Print* print, const GCodePreviewData* preview_data, GLVolumeCollection* volumes, const std::vector<std::string>& str_tool_colors, bool use_VBOs); + static void set_cutting_plane(wxGLCanvas* canvas, float z, const ExPolygons& polygons); + static void set_color_by(wxGLCanvas* canvas, const std::string& value); + static void set_select_by(wxGLCanvas* canvas, const std::string& value); + static void set_drag_by(wxGLCanvas* canvas, const std::string& value); + + static bool is_layers_editing_enabled(wxGLCanvas* canvas); + static bool is_layers_editing_allowed(wxGLCanvas* canvas); + static bool is_shader_enabled(wxGLCanvas* canvas); + + static bool is_reload_delayed(wxGLCanvas* canvas); + + static void enable_layers_editing(wxGLCanvas* canvas, bool enable); + static void enable_warning_texture(wxGLCanvas* canvas, bool enable); + static void enable_legend_texture(wxGLCanvas* canvas, bool enable); + static void enable_picking(wxGLCanvas* canvas, bool enable); + static void enable_moving(wxGLCanvas* canvas, bool enable); + 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 allow_multisample(wxGLCanvas* canvas, bool allow); + + static void zoom_to_bed(wxGLCanvas* canvas); + static void zoom_to_volumes(wxGLCanvas* canvas); + static void select_view(wxGLCanvas* canvas, const std::string& direction); + static void set_viewport_from_scene(wxGLCanvas* canvas, wxGLCanvas* other); + + static void update_volumes_colors_by_extruder(wxGLCanvas* canvas); + + static void render(wxGLCanvas* canvas); + + static std::vector<double> get_current_print_zs(wxGLCanvas* canvas, bool active_only); + static void set_toolpaths_range(wxGLCanvas* canvas, double low, double high); + + static void register_on_viewport_changed_callback(wxGLCanvas* canvas, void* callback); + static void register_on_double_click_callback(wxGLCanvas* canvas, void* callback); + static void register_on_right_click_callback(wxGLCanvas* canvas, void* callback); + static void register_on_select_object_callback(wxGLCanvas* canvas, void* callback); + static void register_on_model_update_callback(wxGLCanvas* canvas, void* callback); + static void register_on_remove_object_callback(wxGLCanvas* canvas, void* callback); + static void register_on_arrange_callback(wxGLCanvas* canvas, void* callback); + static void register_on_rotate_object_left_callback(wxGLCanvas* canvas, void* callback); + static void register_on_rotate_object_right_callback(wxGLCanvas* canvas, void* callback); + static void register_on_scale_object_uniformly_callback(wxGLCanvas* canvas, void* callback); + static void register_on_increase_objects_callback(wxGLCanvas* canvas, void* callback); + static void register_on_decrease_objects_callback(wxGLCanvas* canvas, void* callback); + static void register_on_instance_moved_callback(wxGLCanvas* canvas, void* callback); + static void register_on_wipe_tower_moved_callback(wxGLCanvas* canvas, void* callback); + static void register_on_enable_action_buttons_callback(wxGLCanvas* canvas, void* callback); + static void register_on_gizmo_scale_uniformly_callback(wxGLCanvas* canvas, void* callback); + + static std::vector<int> load_object(wxGLCanvas* canvas, const ModelObject* model_object, int obj_idx, std::vector<int> instance_idxs); + static std::vector<int> load_object(wxGLCanvas* canvas, const Model* model, int obj_idx); + + 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); + + // generates the legend texture in dependence of the current shown view type + static void generate_legend_texture(const GCodePreviewData& preview_data, const std::vector<float>& tool_colors); static unsigned int get_legend_texture_width(); static unsigned int get_legend_texture_height(); @@ -545,42 +617,16 @@ public: static void reset_warning_texture(); static unsigned int finalize_warning_texture(); - static void _load_print_toolpaths( - const Print *print, - GLVolumeCollection *volumes, - const std::vector<std::string> &tool_colors, - bool use_VBOs); - - static void _load_print_object_toolpaths( - const PrintObject *print_object, - GLVolumeCollection *volumes, - const std::vector<std::string> &tool_colors, - bool use_VBOs); - - static void _load_wipe_tower_toolpaths( - const Print *print, - GLVolumeCollection *volumes, - const std::vector<std::string> &tool_colors_str, - bool use_VBOs); - -private: - // generates gcode extrusion paths geometry - static void _load_gcode_extrusion_paths(const GCodePreviewData& preview_data, GLVolumeCollection& volumes, const std::vector<float>& tool_colors, bool use_VBOs); - // generates gcode travel paths geometry - static void _load_gcode_travel_paths(const GCodePreviewData& preview_data, GLVolumeCollection& volumes, const std::vector<float>& tool_colors, bool use_VBOs); - static bool _travel_paths_by_type(const GCodePreviewData& preview_data, GLVolumeCollection& volumes); - static bool _travel_paths_by_feedrate(const GCodePreviewData& preview_data, GLVolumeCollection& volumes); - static bool _travel_paths_by_tool(const GCodePreviewData& preview_data, GLVolumeCollection& volumes, const std::vector<float>& tool_colors); - // generates gcode retractions geometry - static void _load_gcode_retractions(const GCodePreviewData& preview_data, GLVolumeCollection& volumes, bool use_VBOs); - // generates gcode unretractions geometry - static void _load_gcode_unretractions(const GCodePreviewData& preview_data, GLVolumeCollection& volumes, bool use_VBOs); - // sets gcode geometry visibility according to user selection - static void _update_gcode_volumes_visibility(const GCodePreviewData& preview_data, GLVolumeCollection& volumes); - // generates the legend texture in dependence of the current shown view type - static void _generate_legend_texture(const GCodePreviewData& preview_data, const std::vector<float>& tool_colors); - // generates objects and wipe tower geometry - static void _load_shells(const Print& print, GLVolumeCollection& volumes, bool use_VBOs); + static void thick_lines_to_verts(const Lines& lines, const std::vector<double>& widths, const std::vector<double>& heights, bool closed, double top_z, GLVolume& volume); + static void thick_lines_to_verts(const Lines3& lines, const std::vector<double>& widths, const std::vector<double>& heights, bool closed, GLVolume& volume); + static void extrusionentity_to_verts(const ExtrusionPath& extrusion_path, float print_z, GLVolume& volume); + static void extrusionentity_to_verts(const ExtrusionPath& extrusion_path, float print_z, const Point& copy, GLVolume& volume); + static void extrusionentity_to_verts(const ExtrusionLoop& extrusion_loop, float print_z, const Point& copy, GLVolume& volume); + static void extrusionentity_to_verts(const ExtrusionMultiPath& extrusion_multi_path, float print_z, const Point& copy, GLVolume& volume); + static void extrusionentity_to_verts(const ExtrusionEntityCollection& extrusion_entity_collection, float print_z, const Point& copy, GLVolume& volume); + static void extrusionentity_to_verts(const ExtrusionEntity* extrusion_entity, float print_z, const Point& copy, GLVolume& volume); + static void polyline3_to_verts(const Polyline3& polyline, double width, double height, GLVolume& volume); + static void point3_to_verts(const Point3& point, double width, double height, GLVolume& volume); }; } diff --git a/xs/src/slic3r/GUI/ConfigWizard.cpp b/xs/src/slic3r/GUI/ConfigWizard.cpp index aed0c3534..2e315a70b 100644 --- a/xs/src/slic3r/GUI/ConfigWizard.cpp +++ b/xs/src/slic3r/GUI/ConfigWizard.cpp @@ -83,8 +83,11 @@ PrinterPicker::PrinterPicker(wxWindow *parent, const VendorProfile &vendor, cons const auto model_id = model.id; + bool default_variant = true; // Mark the first variant as default in the GUI for (const auto &variant : model.variants) { - const auto label = wxString::Format("%s %s %s", variant.name, _(L("mm")), _(L("nozzle"))); + const auto label = wxString::Format("%s %s %s %s", variant.name, _(L("mm")), _(L("nozzle")), + (default_variant ? _(L("(default)")) : wxString())); + default_variant = false; auto *cbox = new Checkbox(panel, label, model_id, variant.name); const size_t idx = cboxes.size(); cboxes.push_back(cbox); @@ -125,6 +128,14 @@ void PrinterPicker::select_all(bool select) } } +void PrinterPicker::select_one(size_t i, bool select) +{ + if (i < cboxes.size() && cboxes[i]->GetValue() != select) { + cboxes[i]->SetValue(select); + on_checkbox(cboxes[i], select); + } +} + void PrinterPicker::on_checkbox(const Checkbox *cbox, bool checked) { variants_checked += checked ? 1 : -1; @@ -227,6 +238,7 @@ PageWelcome::PageWelcome(ConfigWizard *parent) : AppConfig &appconfig_vendors = this->wizard_p()->appconfig_vendors; printer_picker = new PrinterPicker(this, vendor_prusa->second, appconfig_vendors); + printer_picker->select_one(0, true); // Select the default (first) model/variant on the Prusa vendor printer_picker->Bind(EVT_PRINTER_PICK, [this, &appconfig_vendors](const PrinterPickerEvent &evt) { appconfig_vendors.set_variant(evt.vendor_id, evt.model_id, evt.variant_name, evt.enable); this->on_variant_checked(); @@ -598,10 +610,10 @@ void ConfigWizardIndex::on_paint(wxPaintEvent & evt) static const std::unordered_map<std::string, std::pair<std::string, std::string>> legacy_preset_map {{ { "Original Prusa i3 MK2.ini", std::make_pair("MK2S", "0.4") }, - { "Original Prusa i3 MK2 MM Single Mode.ini", std::make_pair("MK2S", "0.4") }, - { "Original Prusa i3 MK2 MM Single Mode 0.6 nozzle.ini", std::make_pair("MK2S", "0.6") }, - { "Original Prusa i3 MK2 MultiMaterial.ini", std::make_pair("MK2S", "0.4") }, - { "Original Prusa i3 MK2 MultiMaterial 0.6 nozzle.ini", std::make_pair("MK2S", "0.6") }, + { "Original Prusa i3 MK2 MM Single Mode.ini", std::make_pair("MK2SMM", "0.4") }, + { "Original Prusa i3 MK2 MM Single Mode 0.6 nozzle.ini", std::make_pair("MK2SMM", "0.6") }, + { "Original Prusa i3 MK2 MultiMaterial.ini", std::make_pair("MK2SMM", "0.4") }, + { "Original Prusa i3 MK2 MultiMaterial 0.6 nozzle.ini", std::make_pair("MK2SMM", "0.6") }, { "Original Prusa i3 MK2 0.25 nozzle.ini", std::make_pair("MK2S", "0.25") }, { "Original Prusa i3 MK2 0.6 nozzle.ini", std::make_pair("MK2S", "0.6") }, { "Original Prusa i3 MK3.ini", std::make_pair("MK3", "0.4") }, @@ -809,8 +821,8 @@ ConfigWizard::ConfigWizard(wxWindow *parent, RunReason reason) : topsizer->AddSpacer(INDEX_MARGIN); topsizer->Add(p->hscroll, 1, wxEXPAND); - p->btn_prev = new wxButton(this, wxID_BACKWARD); - p->btn_next = new wxButton(this, wxID_FORWARD); + p->btn_prev = new wxButton(this, wxID_NONE, _(L("< &Back"))); + p->btn_next = new wxButton(this, wxID_NONE, _(L("&Next >"))); p->btn_finish = new wxButton(this, wxID_APPLY, _(L("&Finish"))); p->btn_cancel = new wxButton(this, wxID_CANCEL); p->btnsizer->AddStretchSpacer(); @@ -881,9 +893,9 @@ const wxString& ConfigWizard::name() { // A different naming convention is used for the Wizard on Windows vs. OSX & GTK. #if WIN32 - static const wxString config_wizard_name = _(L("Configuration Wizard")); + static const wxString config_wizard_name = L("Configuration Wizard"); #else - static const wxString config_wizard_name = _(L("Configuration Assistant")); + static const wxString config_wizard_name = L("Configuration Assistant"); #endif return config_wizard_name; } diff --git a/xs/src/slic3r/GUI/ConfigWizard_private.hpp b/xs/src/slic3r/GUI/ConfigWizard_private.hpp index 72cb88655..04319a1b4 100644 --- a/xs/src/slic3r/GUI/ConfigWizard_private.hpp +++ b/xs/src/slic3r/GUI/ConfigWizard_private.hpp @@ -56,6 +56,7 @@ struct PrinterPicker: wxPanel PrinterPicker(wxWindow *parent, const VendorProfile &vendor, const AppConfig &appconfig_vendors); void select_all(bool select); + void select_one(size_t i, bool select); void on_checkbox(const Checkbox *cbox, bool checked); }; diff --git a/xs/src/slic3r/GUI/Field.cpp b/xs/src/slic3r/GUI/Field.cpp index 43c9e7db9..85247b41b 100644 --- a/xs/src/slic3r/GUI/Field.cpp +++ b/xs/src/slic3r/GUI/Field.cpp @@ -12,10 +12,20 @@ namespace Slic3r { namespace GUI { wxString double_to_string(double const value) { - int precision = 10 * value - int(10 * value) == 0 ? 1 : 2; - return value - int(value) == 0 ? - wxString::Format(_T("%i"), int(value)) : - wxNumberFormatter::ToString(value, precision, wxNumberFormatter::Style_None); + if (value - int(value) == 0) + return wxString::Format(_T("%i"), int(value)); + else { + int precision = 4; + for (size_t p = 1; p < 4; p++) + { + double cur_val = pow(10, p)*value; + if (cur_val - int(cur_val) == 0) { + precision = p; + break; + } + } + return wxNumberFormatter::ToString(value, precision, wxNumberFormatter::Style_None); + } } void Field::PostInitialize(){ diff --git a/xs/src/slic3r/GUI/FirmwareDialog.cpp b/xs/src/slic3r/GUI/FirmwareDialog.cpp index 8ea9d2d6e..d74743055 100644 --- a/xs/src/slic3r/GUI/FirmwareDialog.cpp +++ b/xs/src/slic3r/GUI/FirmwareDialog.cpp @@ -4,12 +4,14 @@ #include <algorithm> #include <boost/format.hpp> #include <boost/filesystem/path.hpp> +#include <boost/filesystem/fstream.hpp> #include <boost/log/trivial.hpp> #include <wx/app.h> #include <wx/event.h> #include <wx/sizer.h> #include <wx/settings.h> +#include <wx/timer.h> #include <wx/panel.h> #include <wx/button.h> #include <wx/filepicker.h> @@ -36,7 +38,7 @@ namespace Slic3r { enum AvrdudeEvent { AE_MESSAGE, - AE_PRORGESS, + AE_PROGRESS, AE_EXIT, }; @@ -62,7 +64,6 @@ struct FirmwareDialog::priv std::vector<Utils::SerialPortInfo> ports; wxFilePickerCtrl *hex_picker; wxStaticText *txt_status; - wxStaticText *txt_progress; wxGauge *progressbar; wxCollapsiblePane *spoiler; wxTextCtrl *txt_stdout; @@ -72,6 +73,8 @@ struct FirmwareDialog::priv wxString btn_flash_label_ready; wxString btn_flash_label_flashing; + wxTimer timer_pulse; + // This is a shared pointer holding the background AvrDude task // also serves as a status indication (it is set _iff_ the background task is running, otherwise it is reset). AvrDude::Ptr avrdude; @@ -83,13 +86,16 @@ struct FirmwareDialog::priv q(q), btn_flash_label_ready(_(L("Flash!"))), btn_flash_label_flashing(_(L("Cancel"))), + timer_pulse(q), avrdude_config((fs::path(::Slic3r::resources_dir()) / "avrdude" / "avrdude.conf").string()), progress_tasks_done(0), cancelled(false) {} void find_serial_ports(); - void flashing_status(bool flashing, AvrDudeComplete complete = AC_NONE); + void flashing_start(bool flashing_l10n); + void flashing_done(AvrDudeComplete complete); + size_t hex_lang_offset(const wxString &path); void perform_upload(); void cancel(); void on_avrdude(const wxCommandEvent &evt); @@ -116,42 +122,76 @@ void FirmwareDialog::priv::find_serial_ports() } } -void FirmwareDialog::priv::flashing_status(bool value, AvrDudeComplete complete) +void FirmwareDialog::priv::flashing_start(bool flashing_l10n) +{ + txt_stdout->Clear(); + txt_status->SetLabel(_(L("Flashing in progress. Please do not disconnect the printer!"))); + txt_status->SetForegroundColour(GUI::get_label_clr_modified()); + port_picker->Disable(); + btn_rescan->Disable(); + hex_picker->Disable(); + btn_close->Disable(); + btn_flash->SetLabel(btn_flash_label_flashing); + progressbar->SetRange(flashing_l10n ? 500 : 200); // See progress callback below + progressbar->SetValue(0); + progress_tasks_done = 0; + cancelled = false; + timer_pulse.Start(50); +} + +void FirmwareDialog::priv::flashing_done(AvrDudeComplete complete) +{ + auto text_color = wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOWTEXT); + port_picker->Enable(); + btn_rescan->Enable(); + hex_picker->Enable(); + btn_close->Enable(); + btn_flash->SetLabel(btn_flash_label_ready); + txt_status->SetForegroundColour(text_color); + timer_pulse.Stop(); + progressbar->SetValue(progressbar->GetRange()); + + switch (complete) { + case AC_SUCCESS: txt_status->SetLabel(_(L("Flashing succeeded!"))); break; + case AC_FAILURE: txt_status->SetLabel(_(L("Flashing failed. Please see the avrdude log below."))); break; + case AC_CANCEL: txt_status->SetLabel(_(L("Flashing cancelled."))); break; + } +} + +size_t FirmwareDialog::priv::hex_lang_offset(const wxString &path) { - if (value) { - txt_stdout->Clear(); - txt_status->SetLabel(_(L("Flashing in progress. Please do not disconnect the printer!"))); - txt_status->SetForegroundColour(GUI::get_label_clr_modified()); - port_picker->Disable(); - btn_rescan->Disable(); - hex_picker->Disable(); - btn_close->Disable(); - btn_flash->SetLabel(btn_flash_label_flashing); - progressbar->SetRange(200); // See progress callback below - progressbar->SetValue(0); - progress_tasks_done = 0; - cancelled = false; - } else { - auto text_color = wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOWTEXT); - port_picker->Enable(); - btn_rescan->Enable(); - hex_picker->Enable(); - btn_close->Enable(); - btn_flash->SetLabel(btn_flash_label_ready); - txt_status->SetForegroundColour(text_color); - progressbar->SetValue(200); - - switch (complete) { - case AC_SUCCESS: txt_status->SetLabel(_(L("Flashing succeeded!"))); break; - case AC_FAILURE: txt_status->SetLabel(_(L("Flashing failed. Please see the avrdude log below."))); break; - case AC_CANCEL: txt_status->SetLabel(_(L("Flashing cancelled."))); break; + fs::ifstream file(fs::path(path.wx_str())); + if (! file.good()) { + return 0; + } + + static const char *hex_terminator = ":00000001FF\r"; + size_t res = 0; + std::string line; + while (getline(file, line, '\n').good()) { + // Account for LF vs CRLF + if (!line.empty() && line.back() != '\r') { + line.push_back('\r'); + } + + if (line == hex_terminator) { + if (res == 0) { + // This is the first terminator seen, save the position + res = file.tellg(); + } else { + // We've found another terminator, return the offset just after the first one + // which is the start of the second 'section'. + return res; + } } } + + return 0; } void FirmwareDialog::priv::perform_upload() { - auto filename = hex_picker->GetPath(); + auto filename = hex_picker->GetPath(); std::string port = port_picker->GetValue().ToStdString(); int selection = port_picker->GetSelection(); if (selection != -1) { @@ -161,16 +201,32 @@ void FirmwareDialog::priv::perform_upload() } if (filename.IsEmpty() || port.empty()) { return; } - flashing_status(true); + 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 {{ - "-v", + extra_verbose ? "-vvvvv" : "-v", "-p", "atmega2560", + // Using the "Wiring" mode to program Rambo or Einsy, using the STK500v2 protocol (not the STK500). + // The Prusa's avrdude is patched to never send semicolons inside the data packets, as the USB to serial chip + // is flashed with a buggy firmware. "-c", "wiring", "-P", port, - "-b", "115200", // XXX: is this ok to hardcode? + "-b", "115200", // TODO: Allow other rates? Ditto below. "-D", - "-U", (boost::format("flash:w:%1%:i") % filename.ToStdString()).str() + // XXX: Safe mode? + "-U", (boost::format("flash:w:0:%1%:i") % filename_utf8.data()).str(), }}; BOOST_LOG_TRIVIAL(info) << "Invoking avrdude, arguments: " @@ -178,26 +234,51 @@ void FirmwareDialog::priv::perform_upload() return a + ' ' + b; }); - // 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; + 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)); + } + + this->avrdude = avrdude + .on_message(std::move([q, extra_verbose](const char *msg, unsigned /* size */) { + if (extra_verbose) { + BOOST_LOG_TRIVIAL(debug) << "avrdude: " << msg; + } - avrdude = AvrDude() - .sys_config(avrdude_config) - .args(args) - .on_message(std::move([q](const char *msg, unsigned /* size */) { auto evt = new wxCommandEvent(EVT_AVRDUDE, q->GetId()); + auto wxmsg = wxString::FromUTF8(msg); evt->SetExtraLong(AE_MESSAGE); - evt->SetString(msg); + evt->SetString(std::move(wxmsg)); wxQueueEvent(q, evt); })) .on_progress(std::move([q](const char * /* task */, unsigned progress) { auto evt = new wxCommandEvent(EVT_AVRDUDE, q->GetId()); - evt->SetExtraLong(AE_PRORGESS); + evt->SetExtraLong(AE_PROGRESS); evt->SetInt(progress); wxQueueEvent(q, evt); })) - .on_complete(std::move([q](int status) { + .on_complete(std::move([q](int status, size_t /* args_id */) { auto evt = new wxCommandEvent(EVT_AVRDUDE, q->GetId()); evt->SetExtraLong(AE_EXIT); evt->SetInt(status); @@ -224,19 +305,19 @@ void FirmwareDialog::priv::on_avrdude(const wxCommandEvent &evt) txt_stdout->AppendText(evt.GetString()); break; - case AE_PRORGESS: + case AE_PROGRESS: // We try to track overall progress here. - // When uploading the firmware, avrdude first reads a littlebit of status data, - // then performs write, then reading (verification). - // We Pulse() during the first read and combine progress of the latter two tasks. + // Avrdude performs 3 tasks per one memory operation ("-U" arg), + // first of which is reading of status data (very short). + // We use the timer_pulse during the very first task to indicate intialization + // and then display overall progress during the latter tasks. - if (progress_tasks_done == 0) { - progressbar->Pulse(); - } else { + if (progress_tasks_done > 0) { progressbar->SetValue(progress_tasks_done - 100 + evt.GetInt()); } if (evt.GetInt() == 100) { + timer_pulse.Stop(); progress_tasks_done += 100; } @@ -246,7 +327,7 @@ void FirmwareDialog::priv::on_avrdude(const wxCommandEvent &evt) BOOST_LOG_TRIVIAL(info) << "avrdude exit code: " << evt.GetInt(); complete_kind = cancelled ? AC_CANCEL : (evt.GetInt() == 0 ? AC_SUCCESS : AC_FAILURE); - flashing_status(false, complete_kind); + flashing_done(complete_kind); // Make sure the background thread is collected and the AvrDude object reset if (avrdude) { avrdude->join(); } @@ -374,6 +455,8 @@ FirmwareDialog::FirmwareDialog(wxWindow *parent) : } }); + Bind(wxEVT_TIMER, [this](wxTimerEvent &evt) { this->p->progressbar->Pulse(); }); + Bind(EVT_AVRDUDE, [this](wxCommandEvent &evt) { this->p->on_avrdude(evt); }); Bind(wxEVT_CLOSE_WINDOW, [this](wxCloseEvent &evt) { diff --git a/xs/src/slic3r/GUI/GLCanvas3D.cpp b/xs/src/slic3r/GUI/GLCanvas3D.cpp new file mode 100644 index 000000000..adea27fa4 --- /dev/null +++ b/xs/src/slic3r/GUI/GLCanvas3D.cpp @@ -0,0 +1,4346 @@ +#include "GLCanvas3D.hpp" + +#include "../../slic3r/GUI/3DScene.hpp" +#include "../../slic3r/GUI/GLShader.hpp" +#include "../../slic3r/GUI/GUI.hpp" +#include "../../slic3r/GUI/PresetBundle.hpp" +#include "../../slic3r/GUI/GLGizmo.hpp" +#include "../../libslic3r/ClipperUtils.hpp" +#include "../../libslic3r/PrintConfig.hpp" +#include "../../libslic3r/Print.hpp" +#include "../../libslic3r/GCode/PreviewData.hpp" + +#include <GL/glew.h> + +#include <wx/glcanvas.h> +#include <wx/timer.h> + +#include <tbb/parallel_for.h> +#include <tbb/spin_mutex.h> + +#include <boost/log/trivial.hpp> +#include <boost/algorithm/string/predicate.hpp> + +#include <iostream> +#include <float.h> +#include <algorithm> + +static const float TRACKBALLSIZE = 0.8f; +static const float GIMBALL_LOCK_THETA_MAX = 180.0f; +static const float GROUND_Z = -0.02f; + +// phi / theta angles to orient the camera. +static const float VIEW_DEFAULT[2] = { 45.0f, 45.0f }; +static const float VIEW_LEFT[2] = { 90.0f, 90.0f }; +static const float VIEW_RIGHT[2] = { -90.0f, 90.0f }; +static const float VIEW_TOP[2] = { 0.0f, 0.0f }; +static const float VIEW_BOTTOM[2] = { 0.0f, 180.0f }; +static const float VIEW_FRONT[2] = { 0.0f, 90.0f }; +static const float VIEW_REAR[2] = { 180.0f, 90.0f }; + +static const float VARIABLE_LAYER_THICKNESS_BAR_WIDTH = 70.0f; +static const float VARIABLE_LAYER_THICKNESS_RESET_BUTTON_HEIGHT = 22.0f; + +namespace Slic3r { +namespace GUI { + +bool GeometryBuffer::set_from_triangles(const Polygons& triangles, float z, bool generate_tex_coords) +{ + m_vertices.clear(); + m_tex_coords.clear(); + + unsigned int v_size = 9 * (unsigned int)triangles.size(); + unsigned int t_size = 6 * (unsigned int)triangles.size(); + if (v_size == 0) + return false; + + m_vertices = std::vector<float>(v_size, 0.0f); + if (generate_tex_coords) + m_tex_coords = std::vector<float>(t_size, 0.0f); + + float min_x = (float)unscale(triangles[0].points[0].x); + float min_y = (float)unscale(triangles[0].points[0].y); + float max_x = min_x; + float max_y = min_y; + + unsigned int v_coord = 0; + unsigned int t_coord = 0; + for (const Polygon& t : triangles) + { + for (unsigned int v = 0; v < 3; ++v) + { + const Point& p = t.points[v]; + float x = (float)unscale(p.x); + float y = (float)unscale(p.y); + + m_vertices[v_coord++] = x; + m_vertices[v_coord++] = y; + m_vertices[v_coord++] = z; + + if (generate_tex_coords) + { + m_tex_coords[t_coord++] = x; + m_tex_coords[t_coord++] = y; + + min_x = std::min(min_x, x); + max_x = std::max(max_x, x); + min_y = std::min(min_y, y); + max_y = std::max(max_y, y); + } + } + } + + if (generate_tex_coords) + { + float size_x = max_x - min_x; + float size_y = max_y - min_y; + + if ((size_x != 0.0f) && (size_y != 0.0f)) + { + float inv_size_x = 1.0f / size_x; + float inv_size_y = -1.0f / size_y; + for (unsigned int i = 0; i < m_tex_coords.size(); i += 2) + { + m_tex_coords[i] *= inv_size_x; + m_tex_coords[i + 1] *= inv_size_y; + } + } + } + + return true; +} + +bool GeometryBuffer::set_from_lines(const Lines& lines, float z) +{ + m_vertices.clear(); + m_tex_coords.clear(); + + unsigned int size = 6 * (unsigned int)lines.size(); + if (size == 0) + return false; + + m_vertices = std::vector<float>(size, 0.0f); + + unsigned int coord = 0; + for (const Line& l : lines) + { + m_vertices[coord++] = (float)unscale(l.a.x); + m_vertices[coord++] = (float)unscale(l.a.y); + m_vertices[coord++] = z; + m_vertices[coord++] = (float)unscale(l.b.x); + m_vertices[coord++] = (float)unscale(l.b.y); + m_vertices[coord++] = z; + } + + return true; +} + +const float* GeometryBuffer::get_vertices() const +{ + return m_vertices.data(); +} + +const float* GeometryBuffer::get_tex_coords() const +{ + return m_tex_coords.data(); +} + +unsigned int GeometryBuffer::get_vertices_count() const +{ + return (unsigned int)m_vertices.size() / 3; +} + +Size::Size() + : m_width(0) + , m_height(0) +{ +} + +Size::Size(int width, int height) + : m_width(width) + , m_height(height) +{ +} + +int Size::get_width() const +{ + return m_width; +} + +void Size::set_width(int width) +{ + m_width = width; +} + +int Size::get_height() const +{ + return m_height; +} + +void Size::set_height(int height) +{ + m_height = height; +} + +Rect::Rect() + : m_left(0.0f) + , m_top(0.0f) + , m_right(0.0f) + , m_bottom(0.0f) +{ +} + +Rect::Rect(float left, float top, float right, float bottom) + : m_left(left) + , m_top(top) + , m_right(right) + , m_bottom(bottom) +{ +} + +float Rect::get_left() const +{ + return m_left; +} + +void Rect::set_left(float left) +{ + m_left = left; +} + +float Rect::get_top() const +{ + return m_top; +} + +void Rect::set_top(float top) +{ + m_top = top; +} + +float Rect::get_right() const +{ + return m_right; +} + +void Rect::set_right(float right) +{ + m_right = right; +} + +float Rect::get_bottom() const +{ + return m_bottom; +} + +void Rect::set_bottom(float bottom) +{ + m_bottom = bottom; +} + +GLCanvas3D::Camera::Camera() + : type(Ortho) + , zoom(1.0f) + , phi(45.0f) +// , distance(0.0f) + , target(0.0, 0.0, 0.0) + , m_theta(45.0f) +{ +} + +std::string GLCanvas3D::Camera::get_type_as_string() const +{ + switch (type) + { + default: + case Unknown: + return "unknown"; +// case Perspective: +// return "perspective"; + case Ortho: + return "ortho"; + }; +} + +float GLCanvas3D::Camera::get_theta() const +{ + return m_theta; +} + +void GLCanvas3D::Camera::set_theta(float theta) +{ + m_theta = clamp(0.0f, GIMBALL_LOCK_THETA_MAX, theta); +} + +GLCanvas3D::Bed::Bed() + : m_type(Custom) +{ +} + +bool GLCanvas3D::Bed::is_prusa() const +{ + return (m_type == MK2) || (m_type == MK3); +} + +bool GLCanvas3D::Bed::is_custom() const +{ + return m_type == Custom; +} + +const Pointfs& GLCanvas3D::Bed::get_shape() const +{ + return m_shape; +} + +void GLCanvas3D::Bed::set_shape(const Pointfs& shape) +{ + m_shape = shape; + m_type = _detect_type(); + + _calc_bounding_box(); + + ExPolygon poly; + for (const Pointf& p : m_shape) + { + poly.contour.append(Point(scale_(p.x), scale_(p.y))); + } + + _calc_triangles(poly); + + const BoundingBox& bed_bbox = poly.contour.bounding_box(); + _calc_gridlines(poly, bed_bbox); + + m_polygon = offset_ex(poly.contour, (float)bed_bbox.radius() * 1.7f, jtRound, scale_(0.5))[0].contour; +} + +const BoundingBoxf3& GLCanvas3D::Bed::get_bounding_box() const +{ + return m_bounding_box; +} + +bool GLCanvas3D::Bed::contains(const Point& point) const +{ + return m_polygon.contains(point); +} + +Point GLCanvas3D::Bed::point_projection(const Point& point) const +{ + return m_polygon.point_projection(point); +} + +void GLCanvas3D::Bed::render(float theta) const +{ + switch (m_type) + { + case MK2: + { + _render_mk2(theta); + break; + } + case MK3: + { + _render_mk3(theta); + break; + } + default: + case Custom: + { + _render_custom(); + break; + } + } +} + +void GLCanvas3D::Bed::_calc_bounding_box() +{ + m_bounding_box = BoundingBoxf3(); + for (const Pointf& p : m_shape) + { + m_bounding_box.merge(Pointf3(p.x, p.y, 0.0)); + } +} + +void GLCanvas3D::Bed::_calc_triangles(const ExPolygon& poly) +{ + Polygons triangles; + poly.triangulate(&triangles); + + if (!m_triangles.set_from_triangles(triangles, GROUND_Z, m_type != Custom)) + printf("Unable to create bed triangles\n"); +} + +void GLCanvas3D::Bed::_calc_gridlines(const ExPolygon& poly, const BoundingBox& bed_bbox) +{ + Polylines axes_lines; + for (coord_t x = bed_bbox.min.x; x <= bed_bbox.max.x; x += scale_(10.0)) + { + Polyline line; + line.append(Point(x, bed_bbox.min.y)); + line.append(Point(x, bed_bbox.max.y)); + axes_lines.push_back(line); + } + for (coord_t y = bed_bbox.min.y; y <= bed_bbox.max.y; y += scale_(10.0)) + { + Polyline line; + line.append(Point(bed_bbox.min.x, y)); + line.append(Point(bed_bbox.max.x, y)); + axes_lines.push_back(line); + } + + // clip with a slightly grown expolygon because our lines lay on the contours and may get erroneously clipped + Lines gridlines = to_lines(intersection_pl(axes_lines, offset(poly, SCALED_EPSILON))); + + // append bed contours + Lines contour_lines = to_lines(poly); + std::copy(contour_lines.begin(), contour_lines.end(), std::back_inserter(gridlines)); + + if (!m_gridlines.set_from_lines(gridlines, GROUND_Z)) + printf("Unable to create bed grid lines\n"); +} + +GLCanvas3D::Bed::EType GLCanvas3D::Bed::_detect_type() const +{ + EType type = Custom; + + const PresetBundle* bundle = get_preset_bundle(); + if (bundle != nullptr) + { + const Preset* curr = &bundle->printers.get_selected_preset(); + while (curr != nullptr) + { + if (curr->config.has("bed_shape") && _are_equal(m_shape, dynamic_cast<const ConfigOptionPoints*>(curr->config.option("bed_shape"))->values)) + { + if ((curr->vendor != nullptr) && (curr->vendor->name == "Prusa Research")) + { + if (boost::contains(curr->name, "MK2")) + { + type = MK2; + break; + } + else if (boost::contains(curr->name, "MK3")) + { + type = MK3; + break; + } + } + } + + curr = bundle->printers.get_preset_parent(*curr); + } + } + + return type; +} + +void GLCanvas3D::Bed::_render_mk2(float theta) const +{ + std::string filename = resources_dir() + "/icons/bed/mk2_top.png"; + if ((m_top_texture.get_id() == 0) || (m_top_texture.get_source() != filename)) + { + if (!m_top_texture.load_from_file(filename, true)) + { + _render_custom(); + return; + } + } + + filename = resources_dir() + "/icons/bed/mk2_bottom.png"; + if ((m_bottom_texture.get_id() == 0) || (m_bottom_texture.get_source() != filename)) + { + if (!m_bottom_texture.load_from_file(filename, true)) + { + _render_custom(); + return; + } + } + + _render_prusa(theta); +} + +void GLCanvas3D::Bed::_render_mk3(float theta) const +{ + std::string filename = resources_dir() + "/icons/bed/mk3_top.png"; + if ((m_top_texture.get_id() == 0) || (m_top_texture.get_source() != filename)) + { + if (!m_top_texture.load_from_file(filename, true)) + { + _render_custom(); + return; + } + } + + filename = resources_dir() + "/icons/bed/mk3_bottom.png"; + if ((m_bottom_texture.get_id() == 0) || (m_bottom_texture.get_source() != filename)) + { + if (!m_bottom_texture.load_from_file(filename, true)) + { + _render_custom(); + return; + } + } + + _render_prusa(theta); +} + +void GLCanvas3D::Bed::_render_prusa(float theta) const +{ + unsigned int triangles_vcount = m_triangles.get_vertices_count(); + if (triangles_vcount > 0) + { + ::glEnable(GL_DEPTH_TEST); + + ::glEnable(GL_BLEND); + ::glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + + ::glEnable(GL_TEXTURE_2D); + + ::glEnableClientState(GL_VERTEX_ARRAY); + ::glEnableClientState(GL_TEXTURE_COORD_ARRAY); + + if (theta > 90.0f) + ::glFrontFace(GL_CW); + + ::glColor4f(1.0f, 1.0f, 1.0f, 1.0f); + ::glBindTexture(GL_TEXTURE_2D, (theta <= 90.0f) ? (GLuint)m_top_texture.get_id() : (GLuint)m_bottom_texture.get_id()); + ::glVertexPointer(3, GL_FLOAT, 0, (GLvoid*)m_triangles.get_vertices()); + ::glTexCoordPointer(2, GL_FLOAT, 0, (GLvoid*)m_triangles.get_tex_coords()); + ::glDrawArrays(GL_TRIANGLES, 0, (GLsizei)triangles_vcount); + + if (theta > 90.0f) + ::glFrontFace(GL_CCW); + + ::glBindTexture(GL_TEXTURE_2D, 0); + ::glDisableClientState(GL_TEXTURE_COORD_ARRAY); + ::glDisableClientState(GL_VERTEX_ARRAY); + + ::glDisable(GL_TEXTURE_2D); + + ::glDisable(GL_BLEND); + } +} + +void GLCanvas3D::Bed::_render_custom() const +{ + m_top_texture.reset(); + m_bottom_texture.reset(); + + unsigned int triangles_vcount = m_triangles.get_vertices_count(); + if (triangles_vcount > 0) + { + ::glEnable(GL_LIGHTING); + ::glDisable(GL_DEPTH_TEST); + + ::glEnable(GL_BLEND); + ::glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + + ::glEnableClientState(GL_VERTEX_ARRAY); + + ::glColor4f(0.8f, 0.6f, 0.5f, 0.4f); + ::glNormal3d(0.0f, 0.0f, 1.0f); + ::glVertexPointer(3, GL_FLOAT, 0, (GLvoid*)m_triangles.get_vertices()); + ::glDrawArrays(GL_TRIANGLES, 0, (GLsizei)triangles_vcount); + + // draw grid + unsigned int gridlines_vcount = m_gridlines.get_vertices_count(); + + // we need depth test for grid, otherwise it would disappear when looking the object from below + ::glEnable(GL_DEPTH_TEST); + ::glLineWidth(3.0f); + ::glColor4f(0.2f, 0.2f, 0.2f, 0.4f); + ::glVertexPointer(3, GL_FLOAT, 0, (GLvoid*)m_gridlines.get_vertices()); + ::glDrawArrays(GL_LINES, 0, (GLsizei)gridlines_vcount); + + ::glDisableClientState(GL_VERTEX_ARRAY); + + ::glDisable(GL_BLEND); + } +} + +bool GLCanvas3D::Bed::_are_equal(const Pointfs& bed_1, const Pointfs& bed_2) +{ + if (bed_1.size() != bed_2.size()) + return false; + + for (unsigned int i = 0; i < (unsigned int)bed_1.size(); ++i) + { + if (bed_1[i] != bed_2[i]) + return false; + } + + return true; +} + +GLCanvas3D::Axes::Axes() + : length(0.0f) +{ +} + +void GLCanvas3D::Axes::render(bool depth_test) const +{ + ::glDisable(GL_LIGHTING); + if (depth_test) + ::glEnable(GL_DEPTH_TEST); + else + ::glDisable(GL_DEPTH_TEST); + + ::glLineWidth(2.0f); + ::glBegin(GL_LINES); + // draw line for x axis + ::glColor3f(1.0f, 0.0f, 0.0f); + ::glVertex3f((GLfloat)origin.x, (GLfloat)origin.y, (GLfloat)origin.z); + ::glVertex3f((GLfloat)origin.x + length, (GLfloat)origin.y, (GLfloat)origin.z); + // draw line for y axis + ::glColor3f(0.0f, 1.0f, 0.0f); + ::glVertex3f((GLfloat)origin.x, (GLfloat)origin.y, (GLfloat)origin.z); + ::glVertex3f((GLfloat)origin.x, (GLfloat)origin.y + length, (GLfloat)origin.z); + ::glEnd(); + // draw line for Z axis + // (re-enable depth test so that axis is correctly shown when objects are behind it) + if (!depth_test) + ::glEnable(GL_DEPTH_TEST); + + ::glBegin(GL_LINES); + ::glColor3f(0.0f, 0.0f, 1.0f); + ::glVertex3f((GLfloat)origin.x, (GLfloat)origin.y, (GLfloat)origin.z); + ::glVertex3f((GLfloat)origin.x, (GLfloat)origin.y, (GLfloat)origin.z + length); + ::glEnd(); +} + +GLCanvas3D::CuttingPlane::CuttingPlane() + : m_z(-1.0f) +{ +} + +bool GLCanvas3D::CuttingPlane::set(float z, const ExPolygons& polygons) +{ + m_z = z; + + // grow slices in order to display them better + ExPolygons expolygons = offset_ex(polygons, scale_(0.1)); + Lines lines = to_lines(expolygons); + return m_lines.set_from_lines(lines, m_z); +} + +void GLCanvas3D::CuttingPlane::render(const BoundingBoxf3& bb) const +{ + ::glDisable(GL_LIGHTING); + _render_plane(bb); + _render_contour(); +} + +void GLCanvas3D::CuttingPlane::_render_plane(const BoundingBoxf3& bb) const +{ + if (m_z >= 0.0f) + { + ::glDisable(GL_CULL_FACE); + ::glEnable(GL_BLEND); + ::glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + + float margin = 20.0f; + float min_x = bb.min.x - margin; + float max_x = bb.max.x + margin; + float min_y = bb.min.y - margin; + float max_y = bb.max.y + margin; + + ::glBegin(GL_QUADS); + ::glColor4f(0.8f, 0.8f, 0.8f, 0.5f); + ::glVertex3f(min_x, min_y, m_z); + ::glVertex3f(max_x, min_y, m_z); + ::glVertex3f(max_x, max_y, m_z); + ::glVertex3f(min_x, max_y, m_z); + ::glEnd(); + + ::glEnable(GL_CULL_FACE); + ::glDisable(GL_BLEND); + } +} + +void GLCanvas3D::CuttingPlane::_render_contour() const +{ + ::glEnableClientState(GL_VERTEX_ARRAY); + + if (m_z >= 0.0f) + { + unsigned int lines_vcount = m_lines.get_vertices_count(); + + ::glLineWidth(2.0f); + ::glColor3f(0.0f, 0.0f, 0.0f); + ::glVertexPointer(3, GL_FLOAT, 0, (GLvoid*)m_lines.get_vertices()); + ::glDrawArrays(GL_LINES, 0, (GLsizei)lines_vcount); + } + + ::glDisableClientState(GL_VERTEX_ARRAY); +} + +GLCanvas3D::Shader::Shader() + : m_shader(nullptr) +{ +} + +GLCanvas3D::Shader::~Shader() +{ + _reset(); +} + +bool GLCanvas3D::Shader::init(const std::string& vertex_shader_filename, const std::string& fragment_shader_filename) +{ + if (is_initialized()) + return true; + + m_shader = new GLShader(); + if (m_shader != nullptr) + { + if (!m_shader->load_from_file(fragment_shader_filename.c_str(), vertex_shader_filename.c_str())) + { + std::cout << "Compilaton of shader failed:" << std::endl; + std::cout << m_shader->last_error << std::endl; + _reset(); + return false; + } + } + + return true; +} + +bool GLCanvas3D::Shader::is_initialized() const +{ + return (m_shader != nullptr); +} + +bool GLCanvas3D::Shader::start_using() const +{ + if (is_initialized()) + { + m_shader->enable(); + return true; + } + else + return false; +} + +void GLCanvas3D::Shader::stop_using() const +{ + if (m_shader != nullptr) + m_shader->disable(); +} + +void GLCanvas3D::Shader::set_uniform(const std::string& name, float value) const +{ + if (m_shader != nullptr) + m_shader->set_uniform(name.c_str(), value); +} + +const GLShader* GLCanvas3D::Shader::get_shader() const +{ + return m_shader; +} + +void GLCanvas3D::Shader::_reset() +{ + if (m_shader != nullptr) + { + m_shader->release(); + delete m_shader; + m_shader = nullptr; + } +} + +GLCanvas3D::LayersEditing::LayersEditing() + : m_use_legacy_opengl(false) + , m_enabled(false) + , m_z_texture_id(0) + , state(Unknown) + , band_width(2.0f) + , strength(0.005f) + , last_object_id(-1) + , last_z(0.0f) + , last_action(0) +{ +} + +GLCanvas3D::LayersEditing::~LayersEditing() +{ + if (m_z_texture_id != 0) + { + ::glDeleteTextures(1, &m_z_texture_id); + m_z_texture_id = 0; + } +} + +bool GLCanvas3D::LayersEditing::init(const std::string& vertex_shader_filename, const std::string& fragment_shader_filename) +{ + if (!m_shader.init(vertex_shader_filename, fragment_shader_filename)) + return false; + + ::glGenTextures(1, (GLuint*)&m_z_texture_id); + ::glBindTexture(GL_TEXTURE_2D, m_z_texture_id); + ::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP); + ::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP); + ::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + ::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_NEAREST); + ::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, 1); + ::glBindTexture(GL_TEXTURE_2D, 0); + + return true; +} + +bool GLCanvas3D::LayersEditing::is_allowed() const +{ + return !m_use_legacy_opengl && m_shader.is_initialized(); +} + +void GLCanvas3D::LayersEditing::set_use_legacy_opengl(bool use_legacy_opengl) +{ + m_use_legacy_opengl = use_legacy_opengl; +} + +bool GLCanvas3D::LayersEditing::is_enabled() const +{ + return m_enabled; +} + +void GLCanvas3D::LayersEditing::set_enabled(bool enabled) +{ + m_enabled = is_allowed() && enabled; +} + +unsigned int GLCanvas3D::LayersEditing::get_z_texture_id() const +{ + return m_z_texture_id; +} + +void GLCanvas3D::LayersEditing::render(const GLCanvas3D& canvas, const PrintObject& print_object, const GLVolume& volume) const +{ + if (!m_enabled) + return; + + const Rect& bar_rect = get_bar_rect_viewport(canvas); + const Rect& reset_rect = get_reset_rect_viewport(canvas); + + ::glDisable(GL_DEPTH_TEST); + + // The viewport and camera are set to complete view and glOrtho(-$x / 2, $x / 2, -$y / 2, $y / 2, -$depth, $depth), + // where x, y is the window size divided by $self->_zoom. + ::glPushMatrix(); + ::glLoadIdentity(); + + _render_tooltip_texture(canvas, bar_rect, reset_rect); + _render_reset_texture(reset_rect); + _render_active_object_annotations(canvas, volume, print_object, bar_rect); + _render_profile(print_object, bar_rect); + + // Revert the matrices. + ::glPopMatrix(); + + ::glEnable(GL_DEPTH_TEST); +} + +int GLCanvas3D::LayersEditing::get_shader_program_id() const +{ + const GLShader* shader = m_shader.get_shader(); + return (shader != nullptr) ? shader->shader_program_id : -1; +} + +float GLCanvas3D::LayersEditing::get_cursor_z_relative(const GLCanvas3D& canvas) +{ + const Point& mouse_pos = canvas.get_local_mouse_position(); + const Rect& rect = get_bar_rect_screen(canvas); + float x = (float)mouse_pos.x; + float y = (float)mouse_pos.y; + float t = rect.get_top(); + float b = rect.get_bottom(); + + return ((rect.get_left() <= x) && (x <= rect.get_right()) && (t <= y) && (y <= b)) ? + // Inside the bar. + (b - y - 1.0f) / (b - t - 1.0f) : + // Outside the bar. + -1000.0f; +} + +bool GLCanvas3D::LayersEditing::bar_rect_contains(const GLCanvas3D& canvas, float x, float y) +{ + const Rect& rect = get_bar_rect_screen(canvas); + return (rect.get_left() <= x) && (x <= rect.get_right()) && (rect.get_top() <= y) && (y <= rect.get_bottom()); +} + +bool GLCanvas3D::LayersEditing::reset_rect_contains(const GLCanvas3D& canvas, float x, float y) +{ + const Rect& rect = get_reset_rect_screen(canvas); + return (rect.get_left() <= x) && (x <= rect.get_right()) && (rect.get_top() <= y) && (y <= rect.get_bottom()); +} + +Rect GLCanvas3D::LayersEditing::get_bar_rect_screen(const GLCanvas3D& canvas) +{ + const Size& cnv_size = canvas.get_canvas_size(); + float w = (float)cnv_size.get_width(); + float h = (float)cnv_size.get_height(); + + return Rect(w - VARIABLE_LAYER_THICKNESS_BAR_WIDTH, 0.0f, w, h - VARIABLE_LAYER_THICKNESS_RESET_BUTTON_HEIGHT); +} + +Rect GLCanvas3D::LayersEditing::get_reset_rect_screen(const GLCanvas3D& canvas) +{ + const Size& cnv_size = canvas.get_canvas_size(); + float w = (float)cnv_size.get_width(); + float h = (float)cnv_size.get_height(); + + return Rect(w - VARIABLE_LAYER_THICKNESS_BAR_WIDTH, h - VARIABLE_LAYER_THICKNESS_RESET_BUTTON_HEIGHT, w, h); +} + +Rect GLCanvas3D::LayersEditing::get_bar_rect_viewport(const GLCanvas3D& canvas) +{ + const Size& cnv_size = canvas.get_canvas_size(); + float half_w = 0.5f * (float)cnv_size.get_width(); + float half_h = 0.5f * (float)cnv_size.get_height(); + + float zoom = canvas.get_camera_zoom(); + float inv_zoom = (zoom != 0.0f) ? 1.0f / zoom : 0.0f; + + return Rect((half_w - VARIABLE_LAYER_THICKNESS_BAR_WIDTH) * inv_zoom, half_h * inv_zoom, half_w * inv_zoom, (-half_h + VARIABLE_LAYER_THICKNESS_RESET_BUTTON_HEIGHT) * inv_zoom); +} + +Rect GLCanvas3D::LayersEditing::get_reset_rect_viewport(const GLCanvas3D& canvas) +{ + const Size& cnv_size = canvas.get_canvas_size(); + float half_w = 0.5f * (float)cnv_size.get_width(); + float half_h = 0.5f * (float)cnv_size.get_height(); + + float zoom = canvas.get_camera_zoom(); + float inv_zoom = (zoom != 0.0f) ? 1.0f / zoom : 0.0f; + + return Rect((half_w - VARIABLE_LAYER_THICKNESS_BAR_WIDTH) * inv_zoom, (-half_h + VARIABLE_LAYER_THICKNESS_RESET_BUTTON_HEIGHT) * inv_zoom, half_w * inv_zoom, -half_h * inv_zoom); +} + + +bool GLCanvas3D::LayersEditing::_is_initialized() const +{ + return m_shader.is_initialized(); +} + +void GLCanvas3D::LayersEditing::_render_tooltip_texture(const GLCanvas3D& canvas, const Rect& bar_rect, const Rect& reset_rect) const +{ + if (m_tooltip_texture.get_id() == 0) + { + std::string filename = resources_dir() + "/icons/variable_layer_height_tooltip.png"; + if (!m_tooltip_texture.load_from_file(filename, false)) + return; + } + + float zoom = canvas.get_camera_zoom(); + float inv_zoom = (zoom != 0.0f) ? 1.0f / zoom : 0.0f; + float gap = 10.0f * inv_zoom; + + float bar_left = bar_rect.get_left(); + float reset_bottom = reset_rect.get_bottom(); + + float l = bar_left - (float)m_tooltip_texture.get_width() * inv_zoom - gap; + float r = bar_left - gap; + float t = reset_bottom + (float)m_tooltip_texture.get_height() * inv_zoom + gap; + float b = reset_bottom + gap; + + GLTexture::render_texture(m_tooltip_texture.get_id(), l, r, b, t); +} + +void GLCanvas3D::LayersEditing::_render_reset_texture(const Rect& reset_rect) const +{ + if (m_reset_texture.get_id() == 0) + { + std::string filename = resources_dir() + "/icons/variable_layer_height_reset.png"; + if (!m_reset_texture.load_from_file(filename, false)) + return; + } + + GLTexture::render_texture(m_reset_texture.get_id(), reset_rect.get_left(), reset_rect.get_right(), reset_rect.get_bottom(), reset_rect.get_top()); +} + +void GLCanvas3D::LayersEditing::_render_active_object_annotations(const GLCanvas3D& canvas, const GLVolume& volume, const PrintObject& print_object, const Rect& bar_rect) const +{ + float max_z = print_object.model_object()->bounding_box().max.z; + + m_shader.start_using(); + + m_shader.set_uniform("z_to_texture_row", (float)volume.layer_height_texture_z_to_row_id()); + m_shader.set_uniform("z_texture_row_to_normalized", 1.0f / (float)volume.layer_height_texture_height()); + m_shader.set_uniform("z_cursor", max_z * get_cursor_z_relative(canvas)); + m_shader.set_uniform("z_cursor_band_width", band_width); + + GLsizei w = (GLsizei)volume.layer_height_texture_width(); + GLsizei h = (GLsizei)volume.layer_height_texture_height(); + GLsizei half_w = w / 2; + GLsizei half_h = h / 2; + + ::glBindTexture(GL_TEXTURE_2D, m_z_texture_id); + ::glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, w, h, 0, GL_RGBA, GL_UNSIGNED_BYTE, 0); + ::glTexImage2D(GL_TEXTURE_2D, 1, GL_RGBA8, half_w, half_h, 0, GL_RGBA, GL_UNSIGNED_BYTE, 0); + ::glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, w, h, GL_RGBA, GL_UNSIGNED_BYTE, volume.layer_height_texture_data_ptr_level0()); + ::glTexSubImage2D(GL_TEXTURE_2D, 1, 0, 0, half_w, half_h, GL_RGBA, GL_UNSIGNED_BYTE, volume.layer_height_texture_data_ptr_level1()); + + // Render the color bar + float l = bar_rect.get_left(); + float r = bar_rect.get_right(); + float t = bar_rect.get_top(); + float b = bar_rect.get_bottom(); + + ::glBegin(GL_QUADS); + ::glVertex3f(l, b, 0.0f); + ::glVertex3f(r, b, 0.0f); + ::glVertex3f(r, t, max_z); + ::glVertex3f(l, t, max_z); + ::glEnd(); + ::glBindTexture(GL_TEXTURE_2D, 0); + + m_shader.stop_using(); +} + +void GLCanvas3D::LayersEditing::_render_profile(const PrintObject& print_object, const Rect& bar_rect) const +{ + // FIXME show some kind of legend. + + // Get a maximum layer height value. + // FIXME This is a duplicate code of Slicing.cpp. + double layer_height_max = DBL_MAX; + const PrintConfig& print_config = print_object.print()->config; + const std::vector<double>& nozzle_diameters = dynamic_cast<const ConfigOptionFloats*>(print_config.option("nozzle_diameter"))->values; + const std::vector<double>& layer_heights_min = dynamic_cast<const ConfigOptionFloats*>(print_config.option("min_layer_height"))->values; + const std::vector<double>& layer_heights_max = dynamic_cast<const ConfigOptionFloats*>(print_config.option("max_layer_height"))->values; + for (unsigned int i = 0; i < (unsigned int)nozzle_diameters.size(); ++i) + { + double lh_min = (layer_heights_min[i] == 0.0) ? 0.07 : std::max(0.01, layer_heights_min[i]); + double lh_max = (layer_heights_max[i] == 0.0) ? (0.75 * nozzle_diameters[i]) : layer_heights_max[i]; + layer_height_max = std::min(layer_height_max, std::max(lh_min, lh_max)); + } + + // Make the vertical bar a bit wider so the layer height curve does not touch the edge of the bar region. + layer_height_max *= 1.12; + + coordf_t max_z = unscale(print_object.size.z); + double layer_height = dynamic_cast<const ConfigOptionFloat*>(print_object.config.option("layer_height"))->value; + float l = bar_rect.get_left(); + float w = bar_rect.get_right() - l; + float b = bar_rect.get_bottom(); + float t = bar_rect.get_top(); + float h = t - b; + float scale_x = w / (float)layer_height_max; + float scale_y = h / (float)max_z; + float x = l + (float)layer_height * scale_x; + + // Baseline + ::glColor3f(0.0f, 0.0f, 0.0f); + ::glBegin(GL_LINE_STRIP); + ::glVertex2f(x, b); + ::glVertex2f(x, t); + ::glEnd(); + + // Curve + const ModelObject* model_object = print_object.model_object(); + if (model_object->layer_height_profile_valid) + { + const std::vector<coordf_t>& profile = model_object->layer_height_profile; + + ::glColor3f(0.0f, 0.0f, 1.0f); + ::glBegin(GL_LINE_STRIP); + for (unsigned int i = 0; i < profile.size(); i += 2) + { + ::glVertex2f(l + (float)profile[i + 1] * scale_x, b + (float)profile[i] * scale_y); + } + ::glEnd(); + } +} + +const Point GLCanvas3D::Mouse::Drag::Invalid_2D_Point(INT_MAX, INT_MAX); +const Pointf3 GLCanvas3D::Mouse::Drag::Invalid_3D_Point(DBL_MAX, DBL_MAX, DBL_MAX); + +GLCanvas3D::Mouse::Drag::Drag() + : start_position_2D(Invalid_2D_Point) + , start_position_3D(Invalid_3D_Point) + , volume_idx(-1) +{ +} + +GLCanvas3D::Mouse::Mouse() + : dragging(false) + , position(DBL_MAX, DBL_MAX) +{ +} + +void GLCanvas3D::Mouse::set_start_position_2D_as_invalid() +{ + drag.start_position_2D = Drag::Invalid_2D_Point; +} + +void GLCanvas3D::Mouse::set_start_position_3D_as_invalid() +{ + drag.start_position_3D = Drag::Invalid_3D_Point; +} + +bool GLCanvas3D::Mouse::is_start_position_2D_defined() const +{ + return (drag.start_position_2D != Drag::Invalid_2D_Point); +} + +bool GLCanvas3D::Mouse::is_start_position_3D_defined() const +{ + return (drag.start_position_3D != Drag::Invalid_3D_Point); +} + +const float GLCanvas3D::Gizmos::OverlayOffsetX = 10.0f; +const float GLCanvas3D::Gizmos::OverlayGapY = 10.0f; + +GLCanvas3D::Gizmos::Gizmos() + : m_enabled(false) + , m_current(Undefined) + , m_dragging(false) +{ +} + +GLCanvas3D::Gizmos::~Gizmos() +{ + _reset(); +} + +bool GLCanvas3D::Gizmos::init() +{ + GLGizmoBase* gizmo = new GLGizmoScale; + if (gizmo == nullptr) + return false; + + if (!gizmo->init()) + return false; + + m_gizmos.insert(GizmosMap::value_type(Scale, gizmo)); + + gizmo = new GLGizmoRotate; + if (gizmo == nullptr) + { + _reset(); + return false; + } + + if (!gizmo->init()) + { + _reset(); + return false; + } + + m_gizmos.insert(GizmosMap::value_type(Rotate, gizmo)); + + return true; +} + +bool GLCanvas3D::Gizmos::is_enabled() const +{ + return m_enabled; +} + +void GLCanvas3D::Gizmos::set_enabled(bool enable) +{ + m_enabled = enable; +} + +void GLCanvas3D::Gizmos::update_hover_state(const GLCanvas3D& canvas, const Pointf& mouse_pos) +{ + if (!m_enabled) + return; + + float cnv_h = (float)canvas.get_canvas_size().get_height(); + float height = _get_total_overlay_height(); + float top_y = 0.5f * (cnv_h - height); + for (GizmosMap::const_iterator it = m_gizmos.begin(); it != m_gizmos.end(); ++it) + { + if (it->second == nullptr) + continue; + + float tex_size = (float)it->second->get_textures_size(); + float half_tex_size = 0.5f * tex_size; + + // we currently use circular icons for gizmo, so we check the radius + if (it->second->get_state() != GLGizmoBase::On) + { + bool inside = length(Pointf(OverlayOffsetX + half_tex_size, top_y + half_tex_size).vector_to(mouse_pos)) < half_tex_size; + it->second->set_state(inside ? GLGizmoBase::Hover : GLGizmoBase::Off); + } + top_y += (tex_size + OverlayGapY); + } +} + +void GLCanvas3D::Gizmos::update_on_off_state(const GLCanvas3D& canvas, const Pointf& mouse_pos) +{ + if (!m_enabled) + return; + + float cnv_h = (float)canvas.get_canvas_size().get_height(); + float height = _get_total_overlay_height(); + float top_y = 0.5f * (cnv_h - height); + for (GizmosMap::const_iterator it = m_gizmos.begin(); it != m_gizmos.end(); ++it) + { + if (it->second == nullptr) + continue; + + float tex_size = (float)it->second->get_textures_size(); + float half_tex_size = 0.5f * tex_size; + + // we currently use circular icons for gizmo, so we check the radius + if (length(Pointf(OverlayOffsetX + half_tex_size, top_y + half_tex_size).vector_to(mouse_pos)) < half_tex_size) + { + if ((it->second->get_state() == GLGizmoBase::On)) + { + it->second->set_state(GLGizmoBase::Off); + m_current = Undefined; + } + else + { + it->second->set_state(GLGizmoBase::On); + m_current = it->first; + } + } + else + it->second->set_state(GLGizmoBase::Off); + + top_y += (tex_size + OverlayGapY); + } +} + +void GLCanvas3D::Gizmos::reset_all_states() +{ + if (!m_enabled) + return; + + for (GizmosMap::const_iterator it = m_gizmos.begin(); it != m_gizmos.end(); ++it) + { + if (it->second != nullptr) + { + it->second->set_state(GLGizmoBase::Off); + it->second->set_hover_id(-1); + } + } + + m_current = Undefined; +} + +void GLCanvas3D::Gizmos::set_hover_id(int id) +{ + if (!m_enabled) + return; + + for (GizmosMap::const_iterator it = m_gizmos.begin(); it != m_gizmos.end(); ++it) + { + if ((it->second != nullptr) && (it->second->get_state() == GLGizmoBase::On)) + it->second->set_hover_id(id); + } +} + +bool GLCanvas3D::Gizmos::overlay_contains_mouse(const GLCanvas3D& canvas, const Pointf& mouse_pos) const +{ + if (!m_enabled) + return false; + + float cnv_h = (float)canvas.get_canvas_size().get_height(); + float height = _get_total_overlay_height(); + float top_y = 0.5f * (cnv_h - height); + for (GizmosMap::const_iterator it = m_gizmos.begin(); it != m_gizmos.end(); ++it) + { + if (it->second == nullptr) + continue; + + float tex_size = (float)it->second->get_textures_size(); + float half_tex_size = 0.5f * tex_size; + + // we currently use circular icons for gizmo, so we check the radius + if (length(Pointf(OverlayOffsetX + half_tex_size, top_y + half_tex_size).vector_to(mouse_pos)) < half_tex_size) + return true; + + top_y += (tex_size + OverlayGapY); + } + + return false; +} + +bool GLCanvas3D::Gizmos::grabber_contains_mouse() const +{ + if (!m_enabled) + return false; + + GLGizmoBase* curr = _get_current(); + return (curr != nullptr) ? (curr->get_hover_id() != -1) : false; +} + +void GLCanvas3D::Gizmos::update(const Pointf& mouse_pos) +{ + if (!m_enabled) + return; + + GLGizmoBase* curr = _get_current(); + if (curr != nullptr) + curr->update(mouse_pos); +} + +void GLCanvas3D::Gizmos::update_data(float scale) +{ + if (!m_enabled) + return; + + GizmosMap::const_iterator it = m_gizmos.find(Scale); + if (it != m_gizmos.end()) + reinterpret_cast<GLGizmoScale*>(it->second)->set_scale(scale); +} + +bool GLCanvas3D::Gizmos::is_running() const +{ + if (!m_enabled) + return false; + + GLGizmoBase* curr = _get_current(); + return (curr != nullptr) ? (curr->get_state() == GLGizmoBase::On) : false; +} + +bool GLCanvas3D::Gizmos::is_dragging() const +{ + return m_dragging; +} + +void GLCanvas3D::Gizmos::start_dragging() +{ + m_dragging = true; + GLGizmoBase* curr = _get_current(); + if (curr != nullptr) + curr->start_dragging(); +} + +void GLCanvas3D::Gizmos::stop_dragging() +{ + m_dragging = false; +} + +float GLCanvas3D::Gizmos::get_scale() const +{ + if (!m_enabled) + return 1.0f; + + GizmosMap::const_iterator it = m_gizmos.find(Scale); + return (it != m_gizmos.end()) ? reinterpret_cast<GLGizmoScale*>(it->second)->get_scale() : 1.0f; +} + +void GLCanvas3D::Gizmos::render(const GLCanvas3D& canvas, const BoundingBoxf3& box) const +{ + if (!m_enabled) + return; + + ::glDisable(GL_DEPTH_TEST); + + _render_current_gizmo(box); + + ::glPushMatrix(); + ::glLoadIdentity(); + + _render_overlay(canvas); + + ::glPopMatrix(); +} + +void GLCanvas3D::Gizmos::render_current_gizmo_for_picking_pass(const BoundingBoxf3& box) const +{ + if (!m_enabled) + return; + + ::glDisable(GL_DEPTH_TEST); + + GLGizmoBase* curr = _get_current(); + if (curr != nullptr) + curr->render_for_picking(box); +} + +void GLCanvas3D::Gizmos::_reset() +{ + for (GizmosMap::value_type& gizmo : m_gizmos) + { + delete gizmo.second; + gizmo.second = nullptr; + } + + m_gizmos.clear(); +} + +void GLCanvas3D::Gizmos::_render_overlay(const GLCanvas3D& canvas) const +{ + if (m_gizmos.empty()) + return; + + float cnv_w = (float)canvas.get_canvas_size().get_width(); + float zoom = canvas.get_camera_zoom(); + float inv_zoom = (zoom != 0.0f) ? 1.0f / zoom : 0.0f; + + float height = _get_total_overlay_height(); + float top_x = (OverlayOffsetX - 0.5f * cnv_w) * inv_zoom; + float top_y = 0.5f * height * inv_zoom; + float scaled_gap_y = OverlayGapY * inv_zoom; + for (GizmosMap::const_iterator it = m_gizmos.begin(); it != m_gizmos.end(); ++it) + { + float tex_size = (float)it->second->get_textures_size() * inv_zoom; + GLTexture::render_texture(it->second->get_textures_id(), top_x, top_x + tex_size, top_y - tex_size, top_y); + top_y -= (tex_size + scaled_gap_y); + } +} + +void GLCanvas3D::Gizmos::_render_current_gizmo(const BoundingBoxf3& box) const +{ + GLGizmoBase* curr = _get_current(); + if (curr != nullptr) + curr->render(box); +} + +float GLCanvas3D::Gizmos::_get_total_overlay_height() const +{ + float height = 0.0f; + + for (GizmosMap::const_iterator it = m_gizmos.begin(); it != m_gizmos.end(); ++it) + { + height += (float)it->second->get_textures_size(); + if (std::distance(it, m_gizmos.end()) > 1) + height += OverlayGapY; + } + + return height; +} + +GLGizmoBase* GLCanvas3D::Gizmos::_get_current() const +{ + GizmosMap::const_iterator it = m_gizmos.find(m_current); + return (it != m_gizmos.end()) ? it->second : nullptr; +} + +GLCanvas3D::GLCanvas3D(wxGLCanvas* canvas) + : m_canvas(canvas) + , m_context(nullptr) + , m_timer(nullptr) + , m_config(nullptr) + , m_print(nullptr) + , m_model(nullptr) + , m_dirty(true) + , m_initialized(false) + , m_use_VBOs(false) + , m_force_zoom_to_bed_enabled(false) + , m_apply_zoom_to_volumes_filter(false) + , m_hover_volume_id(-1) + , m_warning_texture_enabled(false) + , m_legend_texture_enabled(false) + , m_picking_enabled(false) + , m_moving_enabled(false) + , m_shader_enabled(false) + , m_multisample_allowed(false) + , m_color_by("volume") + , m_select_by("object") + , m_drag_by("instance") + , m_reload_delayed(false) +{ + if (m_canvas != nullptr) + { + m_context = new wxGLContext(m_canvas); + m_timer = new wxTimer(m_canvas); + } +} + +GLCanvas3D::~GLCanvas3D() +{ + reset_volumes(); + + if (m_timer != nullptr) + { + delete m_timer; + m_timer = nullptr; + } + + if (m_context != nullptr) + { + delete m_context; + m_context = nullptr; + } + + _deregister_callbacks(); +} + +bool GLCanvas3D::init(bool useVBOs, bool use_legacy_opengl) +{ + if (m_initialized) + return true; + + if ((m_canvas == nullptr) || (m_context == nullptr)) + return false; + + ::glClearColor(1.0f, 1.0f, 1.0f, 1.0f); + ::glClearDepth(1.0f); + + ::glDepthFunc(GL_LESS); + + ::glEnable(GL_DEPTH_TEST); + ::glEnable(GL_CULL_FACE); + ::glEnable(GL_BLEND); + ::glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + + // Set antialiasing / multisampling + ::glDisable(GL_LINE_SMOOTH); + ::glDisable(GL_POLYGON_SMOOTH); + + // ambient lighting + GLfloat ambient[4] = { 0.3f, 0.3f, 0.3f, 1.0f }; + ::glLightModelfv(GL_LIGHT_MODEL_AMBIENT, ambient); + + ::glEnable(GL_LIGHT0); + ::glEnable(GL_LIGHT1); + + // light from camera + GLfloat specular_cam[4] = { 0.3f, 0.3f, 0.3f, 1.0f }; + ::glLightfv(GL_LIGHT1, GL_SPECULAR, specular_cam); + GLfloat diffuse_cam[4] = { 0.2f, 0.2f, 0.2f, 1.0f }; + ::glLightfv(GL_LIGHT1, GL_DIFFUSE, diffuse_cam); + + // light from above + GLfloat specular_top[4] = { 0.2f, 0.2f, 0.2f, 1.0f }; + ::glLightfv(GL_LIGHT0, GL_SPECULAR, specular_top); + GLfloat diffuse_top[4] = { 0.5f, 0.5f, 0.5f, 1.0f }; + ::glLightfv(GL_LIGHT0, GL_DIFFUSE, diffuse_top); + + // Enables Smooth Color Shading; try GL_FLAT for (lack of) fun. + ::glShadeModel(GL_SMOOTH); + + // A handy trick -- have surface material mirror the color. + ::glColorMaterial(GL_FRONT_AND_BACK, GL_AMBIENT_AND_DIFFUSE); + ::glEnable(GL_COLOR_MATERIAL); + + if (m_multisample_allowed) + ::glEnable(GL_MULTISAMPLE); + + if (useVBOs && !m_shader.init("gouraud.vs", "gouraud.fs")) + return false; + + if (useVBOs && !m_layers_editing.init("variable_layer_height.vs", "variable_layer_height.fs")) + return false; + + m_use_VBOs = useVBOs; + m_layers_editing.set_use_legacy_opengl(use_legacy_opengl); + + // on linux the gl context is not valid until the canvas is not shown on screen + // we defer the geometry finalization of volumes until the first call to render() + if (!m_volumes.empty()) + m_volumes.finalize_geometry(m_use_VBOs); + + if (m_gizmos.is_enabled() && !m_gizmos.init()) + return false; + + m_initialized = true; + + return true; +} + +bool GLCanvas3D::set_current() +{ + if ((m_canvas != nullptr) && (m_context != nullptr)) + return m_canvas->SetCurrent(*m_context); + + return false; +} + +void GLCanvas3D::set_as_dirty() +{ + m_dirty = true; +} + +unsigned int GLCanvas3D::get_volumes_count() const +{ + return (unsigned int)m_volumes.volumes.size(); +} + +void GLCanvas3D::reset_volumes() +{ + + if (!m_volumes.empty()) + { + // ensures this canvas is current + if (!set_current()) + return; + + m_volumes.release_geometry(); + m_volumes.clear(); + m_dirty = true; + } +} + +void GLCanvas3D::deselect_volumes() +{ + for (GLVolume* vol : m_volumes.volumes) + { + if (vol != nullptr) + vol->selected = false; + } +} + +void GLCanvas3D::select_volume(unsigned int id) +{ + if (id < (unsigned int)m_volumes.volumes.size()) + { + GLVolume* vol = m_volumes.volumes[id]; + if (vol != nullptr) + vol->selected = true; + } +} + +void GLCanvas3D::update_volumes_selection(const std::vector<int>& selections) +{ + if (m_model == nullptr) + return; + + for (unsigned int obj_idx = 0; obj_idx < (unsigned int)m_model->objects.size(); ++obj_idx) + { + if ((selections[obj_idx] == 1) && (obj_idx < (unsigned int)m_objects_volumes_idxs.size())) + { + const std::vector<int>& volume_idxs = m_objects_volumes_idxs[obj_idx]; + for (int v : volume_idxs) + { + select_volume(v); + } + } + } +} + +bool GLCanvas3D::check_volumes_outside_state(const DynamicPrintConfig* config) const +{ + return m_volumes.check_outside_state(config); +} + +bool GLCanvas3D::move_volume_up(unsigned int id) +{ + if ((id > 0) && (id < (unsigned int)m_volumes.volumes.size())) + { + std::swap(m_volumes.volumes[id - 1], m_volumes.volumes[id]); + std::swap(m_volumes.volumes[id - 1]->composite_id, m_volumes.volumes[id]->composite_id); + std::swap(m_volumes.volumes[id - 1]->select_group_id, m_volumes.volumes[id]->select_group_id); + std::swap(m_volumes.volumes[id - 1]->drag_group_id, m_volumes.volumes[id]->drag_group_id); + return true; + } + + return false; +} + +bool GLCanvas3D::move_volume_down(unsigned int id) +{ + if ((id >= 0) && (id + 1 < (unsigned int)m_volumes.volumes.size())) + { + std::swap(m_volumes.volumes[id + 1], m_volumes.volumes[id]); + std::swap(m_volumes.volumes[id + 1]->composite_id, m_volumes.volumes[id]->composite_id); + std::swap(m_volumes.volumes[id + 1]->select_group_id, m_volumes.volumes[id]->select_group_id); + std::swap(m_volumes.volumes[id + 1]->drag_group_id, m_volumes.volumes[id]->drag_group_id); + return true; + } + + return false; +} + +void GLCanvas3D::set_objects_selections(const std::vector<int>& selections) +{ + m_objects_selections = selections; +} + +void GLCanvas3D::set_config(DynamicPrintConfig* config) +{ + m_config = config; +} + +void GLCanvas3D::set_print(Print* print) +{ + m_print = print; +} + +void GLCanvas3D::set_model(Model* model) +{ + m_model = model; +} + +void GLCanvas3D::set_bed_shape(const Pointfs& shape) +{ + m_bed.set_shape(shape); + + // Set the origin and size for painting of the coordinate system axes. + m_axes.origin = Pointf3(0.0, 0.0, (coordf_t)GROUND_Z); + set_axes_length(0.3f * (float)m_bed.get_bounding_box().max_size()); + + m_dirty = true; +} + +void GLCanvas3D::set_auto_bed_shape() +{ + // draw a default square bed around object center + const BoundingBoxf3& bbox = volumes_bounding_box(); + coordf_t max_size = bbox.max_size(); + const Pointf3& center = bbox.center(); + + Pointfs bed_shape; + bed_shape.reserve(4); + bed_shape.emplace_back(center.x - max_size, center.y - max_size); + bed_shape.emplace_back(center.x + max_size, center.y - max_size); + bed_shape.emplace_back(center.x + max_size, center.y + max_size); + bed_shape.emplace_back(center.x - max_size, center.y + max_size); + + set_bed_shape(bed_shape); + + // Set the origin for painting of the coordinate system axes. + m_axes.origin = Pointf3(center.x, center.y, (coordf_t)GROUND_Z); +} + +void GLCanvas3D::set_axes_length(float length) +{ + m_axes.length = length; +} + +void GLCanvas3D::set_cutting_plane(float z, const ExPolygons& polygons) +{ + m_cutting_plane.set(z, polygons); +} + +void GLCanvas3D::set_color_by(const std::string& value) +{ + m_color_by = value; +} + +void GLCanvas3D::set_select_by(const std::string& value) +{ + m_select_by = value; +} + +void GLCanvas3D::set_drag_by(const std::string& value) +{ + m_drag_by = value; +} + +float GLCanvas3D::get_camera_zoom() const +{ + return m_camera.zoom; +} + +BoundingBoxf3 GLCanvas3D::volumes_bounding_box() const +{ + BoundingBoxf3 bb; + for (const GLVolume* volume : m_volumes.volumes) + { + if (!m_apply_zoom_to_volumes_filter || ((volume != nullptr) && volume->zoom_to_volumes)) + bb.merge(volume->transformed_bounding_box()); + } + return bb; +} + +bool GLCanvas3D::is_layers_editing_enabled() const +{ + return m_layers_editing.is_enabled(); +} + +bool GLCanvas3D::is_layers_editing_allowed() const +{ + return m_layers_editing.is_allowed(); +} + +bool GLCanvas3D::is_shader_enabled() const +{ + return m_shader_enabled; +} + +bool GLCanvas3D::is_reload_delayed() const +{ + return m_reload_delayed; +} + +void GLCanvas3D::enable_layers_editing(bool enable) +{ + m_layers_editing.set_enabled(enable); +} + +void GLCanvas3D::enable_warning_texture(bool enable) +{ + m_warning_texture_enabled = enable; +} + +void GLCanvas3D::enable_legend_texture(bool enable) +{ + m_legend_texture_enabled = enable; +} + +void GLCanvas3D::enable_picking(bool enable) +{ + m_picking_enabled = enable; +} + +void GLCanvas3D::enable_moving(bool enable) +{ + m_moving_enabled = enable; +} + +void GLCanvas3D::enable_gizmos(bool enable) +{ + m_gizmos.set_enabled(enable); +} + +void GLCanvas3D::enable_shader(bool enable) +{ + m_shader_enabled = enable; +} + +void GLCanvas3D::enable_force_zoom_to_bed(bool enable) +{ + m_force_zoom_to_bed_enabled = enable; +} + +void GLCanvas3D::allow_multisample(bool allow) +{ + m_multisample_allowed = allow; +} + +void GLCanvas3D::zoom_to_bed() +{ + _zoom_to_bounding_box(m_bed.get_bounding_box()); +} + +void GLCanvas3D::zoom_to_volumes() +{ + m_apply_zoom_to_volumes_filter = true; + _zoom_to_bounding_box(volumes_bounding_box()); + m_apply_zoom_to_volumes_filter = false; +} + +void GLCanvas3D::select_view(const std::string& direction) +{ + const float* dir_vec = nullptr; + + if (direction == "iso") + dir_vec = VIEW_DEFAULT; + else if (direction == "left") + dir_vec = VIEW_LEFT; + else if (direction == "right") + dir_vec = VIEW_RIGHT; + else if (direction == "top") + dir_vec = VIEW_TOP; + else if (direction == "bottom") + dir_vec = VIEW_BOTTOM; + else if (direction == "front") + dir_vec = VIEW_FRONT; + else if (direction == "rear") + dir_vec = VIEW_REAR; + + if ((dir_vec != nullptr) && !empty(volumes_bounding_box())) + { + m_camera.phi = dir_vec[0]; + m_camera.set_theta(dir_vec[1]); + + m_on_viewport_changed_callback.call(); + + if (m_canvas != nullptr) + m_canvas->Refresh(); + } +} + +void GLCanvas3D::set_viewport_from_scene(const GLCanvas3D& other) +{ + m_camera.phi = other.m_camera.phi; + m_camera.set_theta(other.m_camera.get_theta()); + m_camera.target = other.m_camera.target; + m_camera.zoom = other.m_camera.zoom; + m_dirty = true; +} + +void GLCanvas3D::update_volumes_colors_by_extruder() +{ + if (m_config != nullptr) + m_volumes.update_colors_by_extruder(m_config); +} + +void GLCanvas3D::render() +{ + if (m_canvas == nullptr) + return; + + if (!_is_shown_on_screen()) + return; + + // ensures this canvas is current and initialized + if (!set_current() || !_3DScene::init(m_canvas)) + return; + + if (m_force_zoom_to_bed_enabled) + _force_zoom_to_bed(); + + _camera_tranform(); + + GLfloat position_cam[4] = { 1.0f, 0.0f, 1.0f, 0.0f }; + ::glLightfv(GL_LIGHT1, GL_POSITION, position_cam); + GLfloat position_top[4] = { -0.5f, -0.5f, 1.0f, 0.0f }; + ::glLightfv(GL_LIGHT0, GL_POSITION, position_top); + + float theta = m_camera.get_theta(); + bool is_custom_bed = m_bed.is_custom(); + + _picking_pass(); + _render_background(); + // untextured bed needs to be rendered before objects + if (is_custom_bed) + { + _render_bed(theta); + // disable depth testing so that axes are not covered by ground + _render_axes(false); + } + _render_objects(); + // textured bed needs to be rendered after objects + if (!is_custom_bed) + { + _render_axes(true); + _render_bed(theta); + } + _render_cutting_plane(); + _render_warning_texture(); + _render_legend_texture(); + _render_gizmo(); + _render_layer_editing_overlay(); + + m_canvas->SwapBuffers(); +} + +std::vector<double> GLCanvas3D::get_current_print_zs(bool active_only) const +{ + return m_volumes.get_current_print_zs(active_only); +} + +void GLCanvas3D::set_toolpaths_range(double low, double high) +{ + m_volumes.set_range(low, high); +} + +std::vector<int> GLCanvas3D::load_object(const ModelObject& model_object, int obj_idx, std::vector<int> instance_idxs) +{ + if (instance_idxs.empty()) + { + for (unsigned int i = 0; i < model_object.instances.size(); ++i) + { + instance_idxs.push_back(i); + } + } + return m_volumes.load_object(&model_object, obj_idx, instance_idxs, m_color_by, m_select_by, m_drag_by, m_use_VBOs && m_initialized); +} + +std::vector<int> GLCanvas3D::load_object(const Model& model, int obj_idx) +{ + if ((0 <= obj_idx) && (obj_idx < (int)model.objects.size())) + { + const ModelObject* model_object = model.objects[obj_idx]; + if (model_object != nullptr) + return load_object(*model_object, obj_idx, std::vector<int>()); + } + + return std::vector<int>(); +} + +void GLCanvas3D::reload_scene(bool force) +{ + if ((m_canvas == nullptr) || (m_config == nullptr) || (m_model == nullptr)) + return; + + reset_volumes(); + + // ensures this canvas is current + if (!set_current()) + return; + + set_bed_shape(dynamic_cast<const ConfigOptionPoints*>(m_config->option("bed_shape"))->values); + + if (!m_canvas->IsShown() && !force) + { + m_reload_delayed = true; + return; + } + + m_reload_delayed = false; + + m_objects_volumes_idxs.clear(); + + for (unsigned int obj_idx = 0; obj_idx < (unsigned int)m_model->objects.size(); ++obj_idx) + { + m_objects_volumes_idxs.push_back(load_object(*m_model, obj_idx)); + } + + update_volumes_selection(m_objects_selections); + + if (m_config->has("nozzle_diameter")) + { + // Should the wipe tower be visualized ? + unsigned int extruders_count = (unsigned int)dynamic_cast<const ConfigOptionFloats*>(m_config->option("nozzle_diameter"))->values.size(); + + bool semm = dynamic_cast<const ConfigOptionBool*>(m_config->option("single_extruder_multi_material"))->value; + bool wt = dynamic_cast<const ConfigOptionBool*>(m_config->option("wipe_tower"))->value; + bool co = dynamic_cast<const ConfigOptionBool*>(m_config->option("complete_objects"))->value; + + if ((extruders_count > 1) && semm && wt && !co) + { + // Height of a print (Show at least a slab) + coordf_t height = std::max(m_model->bounding_box().max.z, 10.0); + + float x = dynamic_cast<const ConfigOptionFloat*>(m_config->option("wipe_tower_x"))->value; + float y = dynamic_cast<const ConfigOptionFloat*>(m_config->option("wipe_tower_y"))->value; + float w = dynamic_cast<const ConfigOptionFloat*>(m_config->option("wipe_tower_width"))->value; + float a = dynamic_cast<const ConfigOptionFloat*>(m_config->option("wipe_tower_rotation_angle"))->value; + + m_volumes.load_wipe_tower_preview(1000, x, y, w, 15.0f * (float)(extruders_count - 1), (float)height, a, m_use_VBOs && m_initialized); + } + } + + update_volumes_colors_by_extruder(); + + // checks for geometry outside the print volume to render it accordingly + if (!m_volumes.empty()) + { + bool contained = m_volumes.check_outside_state(m_config); + if (!contained) + { + enable_warning_texture(true); + _3DScene::generate_warning_texture(L("Detected object outside print volume")); + m_on_enable_action_buttons_callback.call(false); + } + else + { + enable_warning_texture(false); + m_volumes.reset_outside_state(); + _3DScene::reset_warning_texture(); + m_on_enable_action_buttons_callback.call(!m_model->objects.empty()); + } + } + else + { + enable_warning_texture(false); + _3DScene::reset_warning_texture(); + } +} + +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)) + { + // ensures that this canvas is current + if (!set_current()) + return; + + if (m_volumes.empty()) + { + std::vector<float> tool_colors = _parse_colors(str_tool_colors); + + m_gcode_preview_volume_index.reset(); + + _load_gcode_extrusion_paths(preview_data, tool_colors); + _load_gcode_travel_paths(preview_data, tool_colors); + _load_gcode_retractions(preview_data); + _load_gcode_unretractions(preview_data); + + if (m_volumes.empty()) + _3DScene::reset_legend_texture(); + else + { + _3DScene::generate_legend_texture(preview_data, tool_colors); + + // removes empty volumes + m_volumes.volumes.erase(std::remove_if(m_volumes.volumes.begin(), m_volumes.volumes.end(), + [](const GLVolume* volume) { return volume->print_zs.empty(); }), m_volumes.volumes.end()); + + _load_shells(); + } + } + + _update_gcode_volumes_visibility(preview_data); + } +} + +void GLCanvas3D::register_on_viewport_changed_callback(void* callback) +{ + if (callback != nullptr) + m_on_viewport_changed_callback.register_callback(callback); +} + +void GLCanvas3D::register_on_double_click_callback(void* callback) +{ + if (callback != nullptr) + m_on_double_click_callback.register_callback(callback); +} + +void GLCanvas3D::register_on_right_click_callback(void* callback) +{ + if (callback != nullptr) + m_on_right_click_callback.register_callback(callback); +} + +void GLCanvas3D::register_on_select_object_callback(void* callback) +{ + if (callback != nullptr) + m_on_select_object_callback.register_callback(callback); +} + +void GLCanvas3D::register_on_model_update_callback(void* callback) +{ + if (callback != nullptr) + m_on_model_update_callback.register_callback(callback); +} + +void GLCanvas3D::register_on_remove_object_callback(void* callback) +{ + if (callback != nullptr) + m_on_remove_object_callback.register_callback(callback); +} + +void GLCanvas3D::register_on_arrange_callback(void* callback) +{ + if (callback != nullptr) + m_on_arrange_callback.register_callback(callback); +} + +void GLCanvas3D::register_on_rotate_object_left_callback(void* callback) +{ + if (callback != nullptr) + m_on_rotate_object_left_callback.register_callback(callback); +} + +void GLCanvas3D::register_on_rotate_object_right_callback(void* callback) +{ + if (callback != nullptr) + m_on_rotate_object_right_callback.register_callback(callback); +} + +void GLCanvas3D::register_on_scale_object_uniformly_callback(void* callback) +{ + if (callback != nullptr) + m_on_scale_object_uniformly_callback.register_callback(callback); +} + +void GLCanvas3D::register_on_increase_objects_callback(void* callback) +{ + if (callback != nullptr) + m_on_increase_objects_callback.register_callback(callback); +} + +void GLCanvas3D::register_on_decrease_objects_callback(void* callback) +{ + if (callback != nullptr) + m_on_decrease_objects_callback.register_callback(callback); +} + +void GLCanvas3D::register_on_instance_moved_callback(void* callback) +{ + if (callback != nullptr) + m_on_instance_moved_callback.register_callback(callback); +} + +void GLCanvas3D::register_on_wipe_tower_moved_callback(void* callback) +{ + if (callback != nullptr) + m_on_wipe_tower_moved_callback.register_callback(callback); +} + +void GLCanvas3D::register_on_enable_action_buttons_callback(void* callback) +{ + if (callback != nullptr) + m_on_enable_action_buttons_callback.register_callback(callback); +} + +void GLCanvas3D::register_on_gizmo_scale_uniformly_callback(void* callback) +{ + if (callback != nullptr) + m_on_gizmo_scale_uniformly_callback.register_callback(callback); +} + +void GLCanvas3D::bind_event_handlers() +{ + if (m_canvas != nullptr) + { + m_canvas->Bind(wxEVT_SIZE, &GLCanvas3D::on_size, this); + m_canvas->Bind(wxEVT_IDLE, &GLCanvas3D::on_idle, this); + m_canvas->Bind(wxEVT_CHAR, &GLCanvas3D::on_char, this); + m_canvas->Bind(wxEVT_MOUSEWHEEL, &GLCanvas3D::on_mouse_wheel, this); + m_canvas->Bind(wxEVT_TIMER, &GLCanvas3D::on_timer, this); + m_canvas->Bind(wxEVT_LEFT_DOWN, &GLCanvas3D::on_mouse, this); + m_canvas->Bind(wxEVT_LEFT_UP, &GLCanvas3D::on_mouse, this); + m_canvas->Bind(wxEVT_MIDDLE_DOWN, &GLCanvas3D::on_mouse, this); + m_canvas->Bind(wxEVT_MIDDLE_UP, &GLCanvas3D::on_mouse, this); + m_canvas->Bind(wxEVT_RIGHT_DOWN, &GLCanvas3D::on_mouse, this); + m_canvas->Bind(wxEVT_RIGHT_UP, &GLCanvas3D::on_mouse, this); + m_canvas->Bind(wxEVT_MOTION, &GLCanvas3D::on_mouse, this); + m_canvas->Bind(wxEVT_ENTER_WINDOW, &GLCanvas3D::on_mouse, this); + m_canvas->Bind(wxEVT_LEAVE_WINDOW, &GLCanvas3D::on_mouse, this); + m_canvas->Bind(wxEVT_LEFT_DCLICK, &GLCanvas3D::on_mouse, this); + m_canvas->Bind(wxEVT_MIDDLE_DCLICK, &GLCanvas3D::on_mouse, this); + m_canvas->Bind(wxEVT_RIGHT_DCLICK, &GLCanvas3D::on_mouse, this); + m_canvas->Bind(wxEVT_PAINT, &GLCanvas3D::on_paint, this); + m_canvas->Bind(wxEVT_KEY_DOWN, &GLCanvas3D::on_key_down, this); + } +} + +void GLCanvas3D::unbind_event_handlers() +{ + if (m_canvas != nullptr) + { + m_canvas->Unbind(wxEVT_SIZE, &GLCanvas3D::on_size, this); + m_canvas->Unbind(wxEVT_IDLE, &GLCanvas3D::on_idle, this); + m_canvas->Unbind(wxEVT_CHAR, &GLCanvas3D::on_char, this); + m_canvas->Unbind(wxEVT_MOUSEWHEEL, &GLCanvas3D::on_mouse_wheel, this); + m_canvas->Unbind(wxEVT_TIMER, &GLCanvas3D::on_timer, this); + m_canvas->Unbind(wxEVT_LEFT_DOWN, &GLCanvas3D::on_mouse, this); + m_canvas->Unbind(wxEVT_LEFT_UP, &GLCanvas3D::on_mouse, this); + m_canvas->Unbind(wxEVT_MIDDLE_DOWN, &GLCanvas3D::on_mouse, this); + m_canvas->Unbind(wxEVT_MIDDLE_UP, &GLCanvas3D::on_mouse, this); + m_canvas->Unbind(wxEVT_RIGHT_DOWN, &GLCanvas3D::on_mouse, this); + m_canvas->Unbind(wxEVT_RIGHT_UP, &GLCanvas3D::on_mouse, this); + m_canvas->Unbind(wxEVT_MOTION, &GLCanvas3D::on_mouse, this); + m_canvas->Unbind(wxEVT_ENTER_WINDOW, &GLCanvas3D::on_mouse, this); + m_canvas->Unbind(wxEVT_LEAVE_WINDOW, &GLCanvas3D::on_mouse, this); + m_canvas->Unbind(wxEVT_LEFT_DCLICK, &GLCanvas3D::on_mouse, this); + m_canvas->Unbind(wxEVT_MIDDLE_DCLICK, &GLCanvas3D::on_mouse, this); + m_canvas->Unbind(wxEVT_RIGHT_DCLICK, &GLCanvas3D::on_mouse, this); + m_canvas->Unbind(wxEVT_PAINT, &GLCanvas3D::on_paint, this); + m_canvas->Unbind(wxEVT_KEY_DOWN, &GLCanvas3D::on_key_down, this); + } +} + +void GLCanvas3D::on_size(wxSizeEvent& evt) +{ + m_dirty = true; +} + +void GLCanvas3D::on_idle(wxIdleEvent& evt) +{ + if (!m_dirty) + return; + + _refresh_if_shown_on_screen(); +} + +void GLCanvas3D::on_char(wxKeyEvent& evt) +{ + if (evt.HasModifiers()) + evt.Skip(); + else + { + int keyCode = evt.GetKeyCode(); + switch (keyCode - 48) + { + // numerical input + case 0: { select_view("iso"); break; } + case 1: { select_view("top"); break; } + case 2: { select_view("bottom"); break; } + case 3: { select_view("front"); break; } + case 4: { select_view("rear"); break; } + case 5: { select_view("left"); break; } + case 6: { select_view("right"); break; } + default: + { + // text input + switch (keyCode) + { + // key + + case 43: { m_on_increase_objects_callback.call(); break; } + // key - + case 45: { m_on_decrease_objects_callback.call(); break; } + // key A/a + case 65: + case 97: { m_on_arrange_callback.call(); break; } + // key B/b + case 66: + case 98: { zoom_to_bed(); break; } + // key L/l + case 76: + case 108: { m_on_rotate_object_left_callback.call(); break; } + // key R/r + case 82: + case 114: { m_on_rotate_object_right_callback.call(); break; } + // key S/s + case 83: + case 115: { m_on_scale_object_uniformly_callback.call(); break; } + // key Z/z + case 90: + case 122: { zoom_to_volumes(); break; } + default: + { + evt.Skip(); + break; + } + } + } + } + } +} + +void GLCanvas3D::on_mouse_wheel(wxMouseEvent& evt) +{ + // Ignore the wheel events if the middle button is pressed. + if (evt.MiddleIsDown()) + return; + + // Performs layers editing updates, if enabled + if (is_layers_editing_enabled()) + { + int object_idx_selected = _get_first_selected_object_id(); + if (object_idx_selected != -1) + { + // A volume is selected. Test, whether hovering over a layer thickness bar. + if (m_layers_editing.bar_rect_contains(*this, (float)evt.GetX(), (float)evt.GetY())) + { + // Adjust the width of the selection. + m_layers_editing.band_width = std::max(std::min(m_layers_editing.band_width * (1.0f + 0.1f * (float)evt.GetWheelRotation() / (float)evt.GetWheelDelta()), 10.0f), 1.5f); + if (m_canvas != nullptr) + m_canvas->Refresh(); + + return; + } + } + } + + // Calculate the zoom delta and apply it to the current zoom factor + float zoom = (float)evt.GetWheelRotation() / (float)evt.GetWheelDelta(); + zoom = std::max(std::min(zoom, 4.0f), -4.0f) / 10.0f; + zoom = get_camera_zoom() / (1.0f - zoom); + + // Don't allow to zoom too far outside the scene. + float zoom_min = _get_zoom_to_bounding_box_factor(_max_bounding_box()); + if (zoom_min > 0.0f) + zoom = std::max(zoom, zoom_min * 0.8f); + + m_camera.zoom = zoom; + m_on_viewport_changed_callback.call(); + + _refresh_if_shown_on_screen(); +} + +void GLCanvas3D::on_timer(wxTimerEvent& evt) +{ + if (m_layers_editing.state != LayersEditing::Editing) + return; + + _perform_layer_editing_action(); +} + +void GLCanvas3D::on_mouse(wxMouseEvent& evt) +{ + Point pos(evt.GetX(), evt.GetY()); + + int selected_object_idx = _get_first_selected_object_id(); + int layer_editing_object_idx = is_layers_editing_enabled() ? selected_object_idx : -1; + m_layers_editing.last_object_id = layer_editing_object_idx; + bool gizmos_overlay_contains_mouse = m_gizmos.overlay_contains_mouse(*this, m_mouse.position); + + if (evt.Entering()) + { +#if defined(__WXMSW__) || defined(__linux__) + // On Windows and Linux needs focus in order to catch key events + if (m_canvas != nullptr) + m_canvas->SetFocus(); + + m_mouse.set_start_position_2D_as_invalid(); +#endif + } + else if (evt.LeftDClick() && (m_hover_volume_id != -1)) + m_on_double_click_callback.call(); + else if (evt.LeftDown() || evt.RightDown()) + { + // If user pressed left or right button we first check whether this happened + // on a volume or not. + int volume_idx = m_hover_volume_id; + m_layers_editing.state = LayersEditing::Unknown; + if ((layer_editing_object_idx != -1) && m_layers_editing.bar_rect_contains(*this, pos.x, pos.y)) + { + // A volume is selected and the mouse is inside the layer thickness bar. + // Start editing the layer height. + m_layers_editing.state = LayersEditing::Editing; + _perform_layer_editing_action(&evt); + } + else if ((layer_editing_object_idx != -1) && m_layers_editing.reset_rect_contains(*this, pos.x, pos.y)) + { + if (evt.LeftDown()) + { + // A volume is selected and the mouse is inside the reset button. + m_print->get_object(layer_editing_object_idx)->reset_layer_height_profile(); + // Index 2 means no editing, just wait for mouse up event. + m_layers_editing.state = LayersEditing::Completed; + + m_dirty = true; + } + } + else if ((selected_object_idx != -1) && gizmos_overlay_contains_mouse) + { + m_gizmos.update_on_off_state(*this, m_mouse.position); + _update_gizmos_data(); + m_dirty = true; + } + else if ((selected_object_idx != -1) && m_gizmos.grabber_contains_mouse()) + { + _update_gizmos_data(); + m_gizmos.start_dragging(); + m_dirty = true; + } + else + { + // Select volume in this 3D canvas. + // Don't deselect a volume if layer editing is enabled. We want the object to stay selected + // during the scene manipulation. + + if (m_picking_enabled && ((volume_idx != -1) || !is_layers_editing_enabled())) + { + if (volume_idx != -1) + { + deselect_volumes(); + select_volume(volume_idx); + int group_id = m_volumes.volumes[volume_idx]->select_group_id; + if (group_id != -1) + { + for (GLVolume* vol : m_volumes.volumes) + { + if ((vol != nullptr) && (vol->select_group_id == group_id)) + vol->selected = true; + } + } + + if (m_gizmos.is_running()) + _update_gizmos_data(); + + m_dirty = true; + } + } + + // propagate event through callback + if (m_picking_enabled && (volume_idx != -1)) + _on_select(volume_idx); + + if (volume_idx != -1) + { + if (evt.LeftDown() && m_moving_enabled) + { + // The mouse_to_3d gets the Z coordinate from the Z buffer at the screen coordinate pos x, y, + // an converts the screen space coordinate to unscaled object space. + Pointf3 pos3d = (volume_idx == -1) ? Pointf3(DBL_MAX, DBL_MAX) : _mouse_to_3d(pos); + + // Only accept the initial position, if it is inside the volume bounding box. + BoundingBoxf3 volume_bbox = m_volumes.volumes[volume_idx]->transformed_bounding_box(); + volume_bbox.offset(1.0); + if (volume_bbox.contains(pos3d)) + { + // The dragging operation is initiated. + m_mouse.drag.volume_idx = volume_idx; + m_mouse.drag.start_position_3D = pos3d; + // Remember the shift to to the object center.The object center will later be used + // to limit the object placement close to the bed. + m_mouse.drag.volume_center_offset = pos3d.vector_to(volume_bbox.center()); + } + } + else if (evt.RightDown()) + { + // if right clicking on volume, propagate event through callback + if (m_volumes.volumes[volume_idx]->hover) + m_on_right_click_callback.call(pos.x, pos.y); + } + } + } + } + else if (evt.Dragging() && evt.LeftIsDown() && !gizmos_overlay_contains_mouse && (m_layers_editing.state == LayersEditing::Unknown) && (m_mouse.drag.volume_idx != -1)) + { + m_mouse.dragging = true; + + // Get new position at the same Z of the initial click point. + float z0 = 0.0f; + float z1 = 1.0f; + Pointf3 cur_pos = Linef3(_mouse_to_3d(pos, &z0), _mouse_to_3d(pos, &z1)).intersect_plane(m_mouse.drag.start_position_3D.z); + + // Clip the new position, so the object center remains close to the bed. + cur_pos.translate(m_mouse.drag.volume_center_offset); + Point cur_pos2(scale_(cur_pos.x), scale_(cur_pos.y)); + if (!m_bed.contains(cur_pos2)) + { + Point ip = m_bed.point_projection(cur_pos2); + cur_pos.x = unscale(ip.x); + cur_pos.y = unscale(ip.y); + } + cur_pos.translate(m_mouse.drag.volume_center_offset.negative()); + + // Calculate the translation vector. + Vectorf3 vector = m_mouse.drag.start_position_3D.vector_to(cur_pos); + // Get the volume being dragged. + GLVolume* volume = m_volumes.volumes[m_mouse.drag.volume_idx]; + // Get all volumes belonging to the same group, if any. + std::vector<GLVolume*> volumes; + if (volume->drag_group_id == -1) + volumes.push_back(volume); + else + { + for (GLVolume* v : m_volumes.volumes) + { + if ((v != nullptr) && (v->drag_group_id == volume->drag_group_id)) + volumes.push_back(v); + } + } + + // Apply new temporary volume origin and ignore Z. + for (GLVolume* v : volumes) + { + v->origin.translate(vector.x, vector.y, 0.0); + } + + m_mouse.drag.start_position_3D = cur_pos; + + m_dirty = true; + } + else if (evt.Dragging() && m_gizmos.is_dragging()) + { + m_mouse.dragging = true; + + const Pointf3& cur_pos = _mouse_to_bed_3d(pos); + m_gizmos.update(Pointf(cur_pos.x, cur_pos.y)); + + m_on_gizmo_scale_uniformly_callback.call((double)m_gizmos.get_scale()); + m_dirty = true; + } + else if (evt.Dragging() && !gizmos_overlay_contains_mouse) + { + m_mouse.dragging = true; + + if ((m_layers_editing.state != LayersEditing::Unknown) && (layer_editing_object_idx != -1)) + { + if (m_layers_editing.state == LayersEditing::Editing) + _perform_layer_editing_action(&evt); + } + else if (evt.LeftIsDown()) + { + // if dragging over blank area with left button, rotate + if (m_mouse.is_start_position_3D_defined()) + { + const Pointf3& orig = m_mouse.drag.start_position_3D; + m_camera.phi += (((float)pos.x - (float)orig.x) * TRACKBALLSIZE); + m_camera.set_theta(m_camera.get_theta() - ((float)pos.y - (float)orig.y) * TRACKBALLSIZE); + + m_on_viewport_changed_callback.call(); + + m_dirty = true; + } + m_mouse.drag.start_position_3D = Pointf3((coordf_t)pos.x, (coordf_t)pos.y, 0.0); + } + else if (evt.MiddleIsDown() || evt.RightIsDown()) + { + // If dragging over blank area with right button, pan. + if (m_mouse.is_start_position_2D_defined()) + { + // get point in model space at Z = 0 + float z = 0.0f; + const Pointf3& cur_pos = _mouse_to_3d(pos, &z); + Pointf3 orig = _mouse_to_3d(m_mouse.drag.start_position_2D, &z); + Pointf3 camera_target = m_camera.target; + camera_target.translate(orig.vector_to(cur_pos).negative()); + m_camera.target = camera_target; + + m_on_viewport_changed_callback.call(); + + m_dirty = true; + } + + m_mouse.drag.start_position_2D = pos; + } + } + else if (evt.LeftUp() || evt.MiddleUp() || evt.RightUp()) + { + if (m_layers_editing.state != LayersEditing::Unknown) + { + m_layers_editing.state = LayersEditing::Unknown; + _stop_timer(); + + if (layer_editing_object_idx != -1) + m_on_model_update_callback.call(); + } + else if ((m_mouse.drag.volume_idx != -1) && m_mouse.dragging) + { + // get all volumes belonging to the same group, if any + std::vector<int> volume_idxs; + int vol_id = m_mouse.drag.volume_idx; + int group_id = m_volumes.volumes[vol_id]->drag_group_id; + if (group_id == -1) + volume_idxs.push_back(vol_id); + else + { + for (int i = 0; i < (int)m_volumes.volumes.size(); ++i) + { + if (m_volumes.volumes[i]->drag_group_id == group_id) + volume_idxs.push_back(i); + } + } + + _on_move(volume_idxs); + } + else if (!m_mouse.dragging && (m_hover_volume_id == -1) && !gizmos_overlay_contains_mouse && !m_gizmos.is_dragging() && !is_layers_editing_enabled()) + { + // deselect and propagate event through callback + if (m_picking_enabled) + { + deselect_volumes(); + _on_select(-1); + } + } + else if (evt.LeftUp() && m_gizmos.is_dragging()) + { + m_gizmos.stop_dragging(); + } + + m_mouse.drag.volume_idx = -1; + m_mouse.set_start_position_3D_as_invalid(); + m_mouse.set_start_position_2D_as_invalid(); + m_mouse.dragging = false; + } + else if (evt.Moving()) + { + m_mouse.position = Pointf((coordf_t)pos.x, (coordf_t)pos.y); + // Only refresh if picking is enabled, in that case the objects may get highlighted if the mouse cursor hovers over. + if (m_picking_enabled) + m_dirty = true; + } + else + evt.Skip(); +} + +void GLCanvas3D::on_paint(wxPaintEvent& evt) +{ + render(); +} + +void GLCanvas3D::on_key_down(wxKeyEvent& evt) +{ + if (evt.HasModifiers()) + evt.Skip(); + else + { + int key = evt.GetKeyCode(); + if (key == WXK_DELETE) + m_on_remove_object_callback.call(); + else + evt.Skip(); + } +} + +Size GLCanvas3D::get_canvas_size() const +{ + int w = 0; + int h = 0; + + if (m_canvas != nullptr) + m_canvas->GetSize(&w, &h); + + return Size(w, h); +} + +Point GLCanvas3D::get_local_mouse_position() const +{ + if (m_canvas == nullptr) + return Point(); + + wxPoint mouse_pos = m_canvas->ScreenToClient(wxGetMousePosition()); + return Point(mouse_pos.x, mouse_pos.y); +} + +bool GLCanvas3D::_is_shown_on_screen() const +{ + return (m_canvas != nullptr) ? m_canvas->IsShownOnScreen() : false; +} + +void GLCanvas3D::_force_zoom_to_bed() +{ + zoom_to_bed(); + m_force_zoom_to_bed_enabled = false; +} + +void GLCanvas3D::_resize(unsigned int w, unsigned int h) +{ + if ((m_canvas == nullptr) && (m_context == nullptr)) + return; + + // ensures that this canvas is current + set_current(); + ::glViewport(0, 0, w, h); + + ::glMatrixMode(GL_PROJECTION); + ::glLoadIdentity(); + + const BoundingBoxf3& bbox = _max_bounding_box(); + + switch (m_camera.type) + { + case Camera::Ortho: + { + float w2 = w; + float h2 = h; + float two_zoom = 2.0f * get_camera_zoom(); + if (two_zoom != 0.0f) + { + float inv_two_zoom = 1.0f / two_zoom; + w2 *= inv_two_zoom; + h2 *= inv_two_zoom; + } + + // FIXME: calculate a tighter value for depth will improve z-fighting + float depth = 5.0f * (float)bbox.max_size(); + ::glOrtho(-w2, w2, -h2, h2, -depth, depth); + + break; + } +// case Camera::Perspective: +// { +// float bbox_r = (float)bbox.radius(); +// float fov = PI * 45.0f / 180.0f; +// float fov_tan = tan(0.5f * fov); +// float cam_distance = 0.5f * bbox_r / fov_tan; +// m_camera.distance = cam_distance; +// +// float nr = cam_distance - bbox_r * 1.1f; +// float fr = cam_distance + bbox_r * 1.1f; +// if (nr < 1.0f) +// nr = 1.0f; +// +// if (fr < nr + 1.0f) +// fr = nr + 1.0f; +// +// float h2 = fov_tan * nr; +// float w2 = h2 * w / h; +// ::glFrustum(-w2, w2, -h2, h2, nr, fr); +// +// break; +// } + default: + { + throw std::runtime_error("Invalid camera type."); + break; + } + } + + ::glMatrixMode(GL_MODELVIEW); + + m_dirty = false; +} + +BoundingBoxf3 GLCanvas3D::_max_bounding_box() const +{ + BoundingBoxf3 bb = m_bed.get_bounding_box(); + bb.merge(volumes_bounding_box()); + return bb; +} + +BoundingBoxf3 GLCanvas3D::_selected_volumes_bounding_box() const +{ + BoundingBoxf3 bb; + for (const GLVolume* volume : m_volumes.volumes) + { + if ((volume != nullptr) && volume->selected) + bb.merge(volume->transformed_bounding_box()); + } + return bb; +} + +void GLCanvas3D::_zoom_to_bounding_box(const BoundingBoxf3& bbox) +{ + // Calculate the zoom factor needed to adjust viewport to bounding box. + float zoom = _get_zoom_to_bounding_box_factor(bbox); + if (zoom > 0.0f) + { + m_camera.zoom = zoom; + // center view around bounding box center + m_camera.target = bbox.center(); + + m_on_viewport_changed_callback.call(); + + _refresh_if_shown_on_screen(); + } +} + +float GLCanvas3D::_get_zoom_to_bounding_box_factor(const BoundingBoxf3& bbox) const +{ + float max_bb_size = bbox.max_size(); + if (max_bb_size == 0.0f) + return -1.0f; + + // project the bbox vertices on a plane perpendicular to the camera forward axis + // then calculates the vertices coordinate on this plane along the camera xy axes + + // we need the view matrix, we let opengl calculate it (same as done in render()) + _camera_tranform(); + + // get the view matrix back from opengl + GLfloat matrix[16]; + ::glGetFloatv(GL_MODELVIEW_MATRIX, matrix); + + // camera axes + Pointf3 right((coordf_t)matrix[0], (coordf_t)matrix[4], (coordf_t)matrix[8]); + Pointf3 up((coordf_t)matrix[1], (coordf_t)matrix[5], (coordf_t)matrix[9]); + Pointf3 forward((coordf_t)matrix[2], (coordf_t)matrix[6], (coordf_t)matrix[10]); + + Pointf3 bb_min = bbox.min; + Pointf3 bb_max = bbox.max; + Pointf3 bb_center = bbox.center(); + + // bbox vertices in world space + std::vector<Pointf3> vertices; + vertices.reserve(8); + vertices.push_back(bb_min); + vertices.emplace_back(bb_max.x, bb_min.y, bb_min.z); + vertices.emplace_back(bb_max.x, bb_max.y, bb_min.z); + vertices.emplace_back(bb_min.x, bb_max.y, bb_min.z); + vertices.emplace_back(bb_min.x, bb_min.y, bb_max.z); + vertices.emplace_back(bb_max.x, bb_min.y, bb_max.z); + vertices.push_back(bb_max); + vertices.emplace_back(bb_min.x, bb_max.y, bb_max.z); + + coordf_t max_x = 0.0; + coordf_t max_y = 0.0; + + // margin factor to give some empty space around the bbox + coordf_t margin_factor = 1.25; + + for (const Pointf3 v : vertices) + { + // project vertex on the plane perpendicular to camera forward axis + Pointf3 pos(v.x - bb_center.x, v.y - bb_center.y, v.z - bb_center.z); + Pointf3 proj_on_plane = pos - dot(pos, forward) * forward; + + // calculates vertex coordinate along camera xy axes + coordf_t x_on_plane = dot(proj_on_plane, right); + coordf_t y_on_plane = dot(proj_on_plane, up); + + max_x = std::max(max_x, margin_factor * std::abs(x_on_plane)); + max_y = std::max(max_y, margin_factor * std::abs(y_on_plane)); + } + + if ((max_x == 0.0) || (max_y == 0.0)) + return -1.0f; + + max_x *= 2.0; + max_y *= 2.0; + + const Size& cnv_size = get_canvas_size(); + return (float)std::min((coordf_t)cnv_size.get_width() / max_x, (coordf_t)cnv_size.get_height() / max_y); +} + +void GLCanvas3D::_deregister_callbacks() +{ + m_on_viewport_changed_callback.deregister_callback(); + m_on_double_click_callback.deregister_callback(); + m_on_right_click_callback.deregister_callback(); + m_on_select_object_callback.deregister_callback(); + m_on_model_update_callback.deregister_callback(); + m_on_remove_object_callback.deregister_callback(); + m_on_arrange_callback.deregister_callback(); + m_on_rotate_object_left_callback.deregister_callback(); + m_on_rotate_object_right_callback.deregister_callback(); + m_on_scale_object_uniformly_callback.deregister_callback(); + m_on_increase_objects_callback.deregister_callback(); + m_on_decrease_objects_callback.deregister_callback(); + m_on_instance_moved_callback.deregister_callback(); + m_on_wipe_tower_moved_callback.deregister_callback(); + m_on_enable_action_buttons_callback.deregister_callback(); + m_on_gizmo_scale_uniformly_callback.deregister_callback(); +} + +void GLCanvas3D::_mark_volumes_for_layer_height() const +{ + if (m_print == nullptr) + return; + + for (GLVolume* vol : m_volumes.volumes) + { + int object_id = int(vol->select_group_id / 1000000); + int shader_id = m_layers_editing.get_shader_program_id(); + + if (is_layers_editing_enabled() && (shader_id != -1) && vol->selected && + vol->has_layer_height_texture() && (object_id < (int)m_print->objects.size())) + { + vol->set_layer_height_texture_data(m_layers_editing.get_z_texture_id(), shader_id, + m_print->get_object(object_id), _get_layers_editing_cursor_z_relative(), m_layers_editing.band_width); + } + else + vol->reset_layer_height_texture_data(); + } +} + +void GLCanvas3D::_refresh_if_shown_on_screen() +{ + if (_is_shown_on_screen()) + { + const Size& cnv_size = get_canvas_size(); + _resize((unsigned int)cnv_size.get_width(), (unsigned int)cnv_size.get_height()); + if (m_canvas != nullptr) + m_canvas->Refresh(); + } +} + +void GLCanvas3D::_camera_tranform() const +{ + ::glMatrixMode(GL_MODELVIEW); + ::glLoadIdentity(); + + ::glRotatef(-m_camera.get_theta(), 1.0f, 0.0f, 0.0f); // pitch + ::glRotatef(m_camera.phi, 0.0f, 0.0f, 1.0f); // yaw + + Pointf3 neg_target = m_camera.target.negative(); + ::glTranslatef((GLfloat)neg_target.x, (GLfloat)neg_target.y, (GLfloat)neg_target.z); +} + +void GLCanvas3D::_picking_pass() const +{ + const Pointf& pos = m_mouse.position; + + if (m_picking_enabled && !m_mouse.dragging && (pos != Pointf(DBL_MAX, DBL_MAX))) + { + // Render the object for picking. + // FIXME This cannot possibly work in a multi - sampled context as the color gets mangled by the anti - aliasing. + // Better to use software ray - casting on a bounding - box hierarchy. + + if (m_multisample_allowed) + ::glDisable(GL_MULTISAMPLE); + + ::glDisable(GL_LIGHTING); + ::glDisable(GL_BLEND); + + ::glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); + + _render_volumes(true); + m_gizmos.render_current_gizmo_for_picking_pass(_selected_volumes_bounding_box()); + + if (m_multisample_allowed) + ::glEnable(GL_MULTISAMPLE); + + const Size& cnv_size = get_canvas_size(); + + GLubyte color[4]; + ::glReadPixels(pos.x, cnv_size.get_height() - pos.y - 1, 1, 1, GL_RGBA, GL_UNSIGNED_BYTE, (void*)color); + int volume_id = color[0] + color[1] * 256 + color[2] * 256 * 256; + + m_hover_volume_id = -1; + + for (GLVolume* vol : m_volumes.volumes) + { + vol->hover = false; + } + + if (volume_id < (int)m_volumes.volumes.size()) + { + m_hover_volume_id = volume_id; + m_volumes.volumes[volume_id]->hover = true; + int group_id = m_volumes.volumes[volume_id]->select_group_id; + if (group_id != -1) + { + for (GLVolume* vol : m_volumes.volumes) + { + if (vol->select_group_id == group_id) + vol->hover = true; + } + } + m_gizmos.set_hover_id(-1); + } + else + m_gizmos.set_hover_id(254 - (int)color[2]); + + // updates gizmos overlay + if (_get_first_selected_object_id() != -1) + m_gizmos.update_hover_state(*this, pos); + else + m_gizmos.reset_all_states(); + } +} + +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 }; + + ::glDisable(GL_LIGHTING); + + ::glPushMatrix(); + ::glLoadIdentity(); + ::glMatrixMode(GL_PROJECTION); + ::glPushMatrix(); + ::glLoadIdentity(); + + // Draws a bluish bottom to top gradient over the complete screen. + ::glDisable(GL_DEPTH_TEST); + + ::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); + ::glEnd(); + + ::glEnable(GL_DEPTH_TEST); + + ::glPopMatrix(); + ::glMatrixMode(GL_MODELVIEW); + ::glPopMatrix(); +} + +void GLCanvas3D::_render_bed(float theta) const +{ + m_bed.render(theta); +} + +void GLCanvas3D::_render_axes(bool depth_test) const +{ + m_axes.render(depth_test); +} + +void GLCanvas3D::_render_objects() const +{ + if (m_volumes.empty()) + return; + + ::glEnable(GL_LIGHTING); + + if (!m_shader_enabled) + _render_volumes(false); + else if (m_use_VBOs) + { + if (m_picking_enabled) + { + _mark_volumes_for_layer_height(); + + if (m_config != nullptr) + { + const BoundingBoxf3& bed_bb = m_bed.get_bounding_box(); + m_volumes.set_print_box((float)bed_bb.min.x, (float)bed_bb.min.y, 0.0f, (float)bed_bb.max.x, (float)bed_bb.max.y, (float)m_config->opt_float("max_print_height")); + m_volumes.check_outside_state(m_config); + } + // do not cull backfaces to show broken geometry, if any + ::glDisable(GL_CULL_FACE); + } + + m_shader.start_using(); + m_volumes.render_VBOs(); + m_shader.stop_using(); + + if (m_picking_enabled) + ::glEnable(GL_CULL_FACE); + } + else + { + // do not cull backfaces to show broken geometry, if any + if (m_picking_enabled) + ::glDisable(GL_CULL_FACE); + + m_volumes.render_legacy(); + + if (m_picking_enabled) + ::glEnable(GL_CULL_FACE); + } +} + +void GLCanvas3D::_render_cutting_plane() const +{ + m_cutting_plane.render(volumes_bounding_box()); +} + +void GLCanvas3D::_render_warning_texture() const +{ + if (!m_warning_texture_enabled) + return; + + // If the warning texture has not been loaded into the GPU, do it now. + unsigned int tex_id = _3DScene::finalize_warning_texture(); + if (tex_id > 0) + { + unsigned int w = _3DScene::get_warning_texture_width(); + unsigned int h = _3DScene::get_warning_texture_height(); + if ((w > 0) && (h > 0)) + { + ::glDisable(GL_DEPTH_TEST); + ::glPushMatrix(); + ::glLoadIdentity(); + + const Size& cnv_size = get_canvas_size(); + float zoom = get_camera_zoom(); + float inv_zoom = (zoom != 0.0f) ? 1.0f / zoom : 0.0f; + float l = (-0.5f * (float)w) * inv_zoom; + float t = (-0.5f * (float)cnv_size.get_height() + (float)h) * inv_zoom; + float r = l + (float)w * inv_zoom; + float b = t - (float)h * inv_zoom; + + GLTexture::render_texture(tex_id, l, r, b, t); + + ::glPopMatrix(); + ::glEnable(GL_DEPTH_TEST); + } + } +} + +void GLCanvas3D::_render_legend_texture() const +{ + if (!m_legend_texture_enabled) + return; + + // If the legend texture has not been loaded into the GPU, do it now. + unsigned int tex_id = _3DScene::finalize_legend_texture(); + if (tex_id > 0) + { + unsigned int w = _3DScene::get_legend_texture_width(); + unsigned int h = _3DScene::get_legend_texture_height(); + if ((w > 0) && (h > 0)) + { + ::glDisable(GL_DEPTH_TEST); + ::glPushMatrix(); + ::glLoadIdentity(); + + const Size& cnv_size = get_canvas_size(); + float zoom = get_camera_zoom(); + float inv_zoom = (zoom != 0.0f) ? 1.0f / zoom : 0.0f; + float l = (-0.5f * (float)cnv_size.get_width()) * inv_zoom; + float t = (0.5f * (float)cnv_size.get_height()) * inv_zoom; + float r = l + (float)w * inv_zoom; + float b = t - (float)h * inv_zoom; + GLTexture::render_texture(tex_id, l, r, b, t); + + ::glPopMatrix(); + ::glEnable(GL_DEPTH_TEST); + } + } +} + +void GLCanvas3D::_render_layer_editing_overlay() const +{ + if (m_print == nullptr) + return; + + GLVolume* volume = nullptr; + + for (GLVolume* vol : m_volumes.volumes) + { + if ((vol != nullptr) && vol->selected && vol->has_layer_height_texture()) + { + volume = vol; + break; + } + } + + if (volume == nullptr) + return; + + // If the active object was not allocated at the Print, go away.This should only be a momentary case between an object addition / deletion + // and an update by Platter::async_apply_config. + int object_idx = int(volume->select_group_id / 1000000); + if ((int)m_print->objects.size() < object_idx) + return; + + const PrintObject* print_object = m_print->get_object(object_idx); + if (print_object == nullptr) + return; + + m_layers_editing.render(*this, *print_object, *volume); +} + +void GLCanvas3D::_render_volumes(bool fake_colors) const +{ + static const GLfloat INV_255 = 1.0f / 255.0f; + + if (fake_colors) + ::glDisable(GL_LIGHTING); + else + ::glEnable(GL_LIGHTING); + + // do not cull backfaces to show broken geometry, if any + ::glDisable(GL_CULL_FACE); + + ::glEnable(GL_BLEND); + ::glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + + ::glEnableClientState(GL_VERTEX_ARRAY); + ::glEnableClientState(GL_NORMAL_ARRAY); + + unsigned int volume_id = 0; + for (GLVolume* vol : m_volumes.volumes) + { + if (fake_colors) + { + // Object picking mode. Render the object with a color encoding the object index. + unsigned int r = (volume_id & 0x000000FF) >> 0; + unsigned int g = (volume_id & 0x0000FF00) >> 8; + unsigned int b = (volume_id & 0x00FF0000) >> 16; + ::glColor3f((GLfloat)r * INV_255, (GLfloat)g * INV_255, (GLfloat)b * INV_255); + } + else + { + vol->set_render_color(); + ::glColor4f(vol->render_color[0], vol->render_color[1], vol->render_color[2], vol->render_color[3]); + } + + vol->render(); + ++volume_id; + } + + ::glDisableClientState(GL_NORMAL_ARRAY); + ::glDisableClientState(GL_VERTEX_ARRAY); + ::glDisable(GL_BLEND); + + ::glEnable(GL_CULL_FACE); +} + +void GLCanvas3D::_render_gizmo() const +{ + m_gizmos.render(*this, _selected_volumes_bounding_box()); +} + +float GLCanvas3D::_get_layers_editing_cursor_z_relative() const +{ + return m_layers_editing.get_cursor_z_relative(*this); +} + +void GLCanvas3D::_perform_layer_editing_action(wxMouseEvent* evt) +{ + int object_idx_selected = m_layers_editing.last_object_id; + if (object_idx_selected == -1) + return; + + if (m_print == nullptr) + return; + + PrintObject* selected_obj = m_print->get_object(object_idx_selected); + if (selected_obj == nullptr) + return; + + // A volume is selected. Test, whether hovering over a layer thickness bar. + if (evt != nullptr) + { + const Rect& rect = LayersEditing::get_bar_rect_screen(*this); + float b = rect.get_bottom(); + m_layers_editing.last_z = unscale(selected_obj->size.z) * (b - evt->GetY() - 1.0f) / (b - rect.get_top()); + m_layers_editing.last_action = evt->ShiftDown() ? (evt->RightIsDown() ? 3 : 2) : (evt->RightIsDown() ? 0 : 1); + } + + // Mark the volume as modified, so Print will pick its layer height profile ? Where to mark it ? + // Start a timer to refresh the print ? schedule_background_process() ? + // The PrintObject::adjust_layer_height_profile() call adjusts the profile of its associated ModelObject, it does not modify the profile of the PrintObject itself. + selected_obj->adjust_layer_height_profile(m_layers_editing.last_z, m_layers_editing.strength, m_layers_editing.band_width, m_layers_editing.last_action); + + // searches the id of the first volume of the selected object + int volume_idx = 0; + for (int i = 0; i < object_idx_selected; ++i) + { + PrintObject* obj = m_print->get_object(i); + if (obj != nullptr) + { + for (int j = 0; j < (int)obj->region_volumes.size(); ++j) + { + volume_idx += (int)obj->region_volumes[j].size(); + } + } + } + + m_volumes.volumes[volume_idx]->generate_layer_height_texture(selected_obj, 1); + _refresh_if_shown_on_screen(); + + // Automatic action on mouse down with the same coordinate. + _start_timer(); +} + +Pointf3 GLCanvas3D::_mouse_to_3d(const Point& mouse_pos, float* z) +{ + if (m_canvas == nullptr) + return Pointf3(DBL_MAX, DBL_MAX, DBL_MAX); + + _camera_tranform(); + + GLint viewport[4]; + ::glGetIntegerv(GL_VIEWPORT, viewport); + GLdouble modelview_matrix[16]; + ::glGetDoublev(GL_MODELVIEW_MATRIX, modelview_matrix); + GLdouble projection_matrix[16]; + ::glGetDoublev(GL_PROJECTION_MATRIX, projection_matrix); + + GLint y = viewport[3] - (GLint)mouse_pos.y; + GLfloat mouse_z; + if (z == nullptr) + ::glReadPixels((GLint)mouse_pos.x, y, 1, 1, GL_DEPTH_COMPONENT, GL_FLOAT, (void*)&mouse_z); + else + mouse_z = *z; + + GLdouble out_x, out_y, out_z; + ::gluUnProject((GLdouble)mouse_pos.x, (GLdouble)y, (GLdouble)mouse_z, modelview_matrix, projection_matrix, viewport, &out_x, &out_y, &out_z); + return Pointf3((coordf_t)out_x, (coordf_t)out_y, (coordf_t)out_z); +} + +Pointf3 GLCanvas3D::_mouse_to_bed_3d(const Point& mouse_pos) +{ + float z0 = 0.0f; + float z1 = 1.0f; + return Linef3(_mouse_to_3d(mouse_pos, &z0), _mouse_to_3d(mouse_pos, &z1)).intersect_plane(0.0); +} + +void GLCanvas3D::_start_timer() +{ + if (m_timer != nullptr) + m_timer->Start(100, wxTIMER_CONTINUOUS); +} + +void GLCanvas3D::_stop_timer() +{ + if (m_timer != nullptr) + m_timer->Stop(); +} + +int GLCanvas3D::_get_first_selected_object_id() const +{ + if (m_print != nullptr) + { + int objects_count = (int)m_print->objects.size(); + + for (const GLVolume* vol : m_volumes.volumes) + { + 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) + return (object_id >= objects_count) ? -1 : object_id; + } + } + } + return -1; +} + +static inline int hex_digit_to_int(const char c) +{ + return + (c >= '0' && c <= '9') ? int(c - '0') : + (c >= 'A' && c <= 'F') ? int(c - 'A') + 10 : + (c >= 'a' && c <= 'f') ? int(c - 'a') + 10 : -1; +} + +void GLCanvas3D::_load_gcode_extrusion_paths(const GCodePreviewData& preview_data, const std::vector<float>& tool_colors) +{ + // helper functions to select data in dependence of the extrusion view type + struct Helper + { + static float path_filter(GCodePreviewData::Extrusion::EViewType type, const ExtrusionPath& path) + { + switch (type) + { + case GCodePreviewData::Extrusion::FeatureType: + return (float)path.role(); + case GCodePreviewData::Extrusion::Height: + return path.height; + case GCodePreviewData::Extrusion::Width: + return path.width; + case GCodePreviewData::Extrusion::Feedrate: + return path.feedrate; + case GCodePreviewData::Extrusion::VolumetricRate: + return path.feedrate * (float)path.mm3_per_mm; + case GCodePreviewData::Extrusion::Tool: + return (float)path.extruder_id; + default: + return 0.0f; + } + + return 0.0f; + } + + static GCodePreviewData::Color path_color(const GCodePreviewData& data, const std::vector<float>& tool_colors, float value) + { + switch (data.extrusion.view_type) + { + case GCodePreviewData::Extrusion::FeatureType: + return data.get_extrusion_role_color((ExtrusionRole)(int)value); + case GCodePreviewData::Extrusion::Height: + return data.get_height_color(value); + case GCodePreviewData::Extrusion::Width: + return data.get_width_color(value); + case GCodePreviewData::Extrusion::Feedrate: + return data.get_feedrate_color(value); + case GCodePreviewData::Extrusion::VolumetricRate: + return data.get_volumetric_rate_color(value); + case GCodePreviewData::Extrusion::Tool: + { + GCodePreviewData::Color color; + ::memcpy((void*)color.rgba, (const void*)(tool_colors.data() + (unsigned int)value * 4), 4 * sizeof(float)); + return color; + } + default: + return GCodePreviewData::Color::Dummy; + } + + return GCodePreviewData::Color::Dummy; + } + }; + + // Helper structure for filters + struct Filter + { + float value; + ExtrusionRole role; + GLVolume* volume; + + Filter(float value, ExtrusionRole role) + : value(value) + , role(role) + , volume(nullptr) + { + } + + bool operator == (const Filter& other) const + { + if (value != other.value) + return false; + + if (role != other.role) + return false; + + return true; + } + }; + + typedef std::vector<Filter> FiltersList; + size_t initial_volumes_count = m_volumes.volumes.size(); + + // detects filters + FiltersList filters; + for (const GCodePreviewData::Extrusion::Layer& layer : preview_data.extrusion.layers) + { + for (const ExtrusionPath& path : layer.paths) + { + ExtrusionRole role = path.role(); + float path_filter = Helper::path_filter(preview_data.extrusion.view_type, path); + if (std::find(filters.begin(), filters.end(), Filter(path_filter, role)) == filters.end()) + filters.emplace_back(path_filter, role); + } + } + + // nothing to render, return + if (filters.empty()) + return; + + // creates a new volume for each filter + for (Filter& filter : filters) + { + m_gcode_preview_volume_index.first_volumes.emplace_back(GCodePreviewVolumeIndex::Extrusion, (unsigned int)filter.role, (unsigned int)m_volumes.volumes.size()); + GLVolume* volume = new GLVolume(Helper::path_color(preview_data, tool_colors, filter.value).rgba); + if (volume != nullptr) + { + filter.volume = volume; + m_volumes.volumes.emplace_back(volume); + } + else + { + // an error occourred - restore to previous state and return + m_gcode_preview_volume_index.first_volumes.pop_back(); + if (initial_volumes_count != m_volumes.volumes.size()) + { + std::vector<GLVolume*>::iterator begin = m_volumes.volumes.begin() + initial_volumes_count; + std::vector<GLVolume*>::iterator end = m_volumes.volumes.end(); + for (std::vector<GLVolume*>::iterator it = begin; it < end; ++it) + { + GLVolume* volume = *it; + delete volume; + } + m_volumes.volumes.erase(begin, end); + return; + } + } + } + + // populates volumes + for (const GCodePreviewData::Extrusion::Layer& layer : preview_data.extrusion.layers) + { + for (const ExtrusionPath& path : layer.paths) + { + float path_filter = Helper::path_filter(preview_data.extrusion.view_type, path); + FiltersList::iterator filter = std::find(filters.begin(), filters.end(), Filter(path_filter, path.role())); + if (filter != filters.end()) + { + filter->volume->print_zs.push_back(layer.z); + filter->volume->offsets.push_back(filter->volume->indexed_vertex_array.quad_indices.size()); + filter->volume->offsets.push_back(filter->volume->indexed_vertex_array.triangle_indices.size()); + + _3DScene::extrusionentity_to_verts(path, layer.z, *filter->volume); + } + } + } + + // finalize volumes and sends geometry to gpu + if (m_volumes.volumes.size() > initial_volumes_count) + { + for (size_t i = initial_volumes_count; i < m_volumes.volumes.size(); ++i) + { + GLVolume* volume = m_volumes.volumes[i]; + volume->bounding_box = volume->indexed_vertex_array.bounding_box(); + volume->indexed_vertex_array.finalize_geometry(m_use_VBOs && m_initialized); + } + } +} + +void GLCanvas3D::_load_gcode_travel_paths(const GCodePreviewData& preview_data, const std::vector<float>& tool_colors) +{ + size_t initial_volumes_count = m_volumes.volumes.size(); + m_gcode_preview_volume_index.first_volumes.emplace_back(GCodePreviewVolumeIndex::Travel, 0, (unsigned int)initial_volumes_count); + + bool res = true; + switch (preview_data.extrusion.view_type) + { + case GCodePreviewData::Extrusion::Feedrate: + { + res = _travel_paths_by_feedrate(preview_data); + break; + } + case GCodePreviewData::Extrusion::Tool: + { + res = _travel_paths_by_tool(preview_data, tool_colors); + break; + } + default: + { + res = _travel_paths_by_type(preview_data); + break; + } + } + + if (!res) + { + // an error occourred - restore to previous state and return + if (initial_volumes_count != m_volumes.volumes.size()) + { + std::vector<GLVolume*>::iterator begin = m_volumes.volumes.begin() + initial_volumes_count; + std::vector<GLVolume*>::iterator end = m_volumes.volumes.end(); + for (std::vector<GLVolume*>::iterator it = begin; it < end; ++it) + { + GLVolume* volume = *it; + delete volume; + } + m_volumes.volumes.erase(begin, end); + } + + return; + } + + // finalize volumes and sends geometry to gpu + if (m_volumes.volumes.size() > initial_volumes_count) + { + for (size_t i = initial_volumes_count; i < m_volumes.volumes.size(); ++i) + { + GLVolume* volume = m_volumes.volumes[i]; + volume->bounding_box = volume->indexed_vertex_array.bounding_box(); + volume->indexed_vertex_array.finalize_geometry(m_use_VBOs && m_initialized); + } + } +} + +bool GLCanvas3D::_travel_paths_by_type(const GCodePreviewData& preview_data) +{ + // Helper structure for types + struct Type + { + GCodePreviewData::Travel::EType value; + GLVolume* volume; + + explicit Type(GCodePreviewData::Travel::EType value) + : value(value) + , volume(nullptr) + { + } + + bool operator == (const Type& other) const + { + return value == other.value; + } + }; + + typedef std::vector<Type> TypesList; + + // colors travels by travel type + + // detects types + TypesList types; + for (const GCodePreviewData::Travel::Polyline& polyline : preview_data.travel.polylines) + { + if (std::find(types.begin(), types.end(), Type(polyline.type)) == types.end()) + types.emplace_back(polyline.type); + } + + // nothing to render, return + if (types.empty()) + return true; + + // creates a new volume for each type + for (Type& type : types) + { + GLVolume* volume = new GLVolume(preview_data.travel.type_colors[type.value].rgba); + if (volume == nullptr) + return false; + else + { + type.volume = volume; + m_volumes.volumes.emplace_back(volume); + } + } + + // populates volumes + for (const GCodePreviewData::Travel::Polyline& polyline : preview_data.travel.polylines) + { + TypesList::iterator type = std::find(types.begin(), types.end(), Type(polyline.type)); + if (type != types.end()) + { + type->volume->print_zs.push_back(unscale(polyline.polyline.bounding_box().min.z)); + type->volume->offsets.push_back(type->volume->indexed_vertex_array.quad_indices.size()); + type->volume->offsets.push_back(type->volume->indexed_vertex_array.triangle_indices.size()); + + _3DScene::polyline3_to_verts(polyline.polyline, preview_data.travel.width, preview_data.travel.height, *type->volume); + } + } + + return true; +} + +bool GLCanvas3D::_travel_paths_by_feedrate(const GCodePreviewData& preview_data) +{ + // Helper structure for feedrate + struct Feedrate + { + float value; + GLVolume* volume; + + explicit Feedrate(float value) + : value(value) + , volume(nullptr) + { + } + + bool operator == (const Feedrate& other) const + { + return value == other.value; + } + }; + + typedef std::vector<Feedrate> FeedratesList; + + // colors travels by feedrate + + // detects feedrates + FeedratesList feedrates; + for (const GCodePreviewData::Travel::Polyline& polyline : preview_data.travel.polylines) + { + if (std::find(feedrates.begin(), feedrates.end(), Feedrate(polyline.feedrate)) == feedrates.end()) + feedrates.emplace_back(polyline.feedrate); + } + + // nothing to render, return + if (feedrates.empty()) + return true; + + // creates a new volume for each feedrate + for (Feedrate& feedrate : feedrates) + { + GLVolume* volume = new GLVolume(preview_data.get_feedrate_color(feedrate.value).rgba); + if (volume == nullptr) + return false; + else + { + feedrate.volume = volume; + m_volumes.volumes.emplace_back(volume); + } + } + + // populates volumes + for (const GCodePreviewData::Travel::Polyline& polyline : preview_data.travel.polylines) + { + FeedratesList::iterator feedrate = std::find(feedrates.begin(), feedrates.end(), Feedrate(polyline.feedrate)); + if (feedrate != feedrates.end()) + { + feedrate->volume->print_zs.push_back(unscale(polyline.polyline.bounding_box().min.z)); + feedrate->volume->offsets.push_back(feedrate->volume->indexed_vertex_array.quad_indices.size()); + feedrate->volume->offsets.push_back(feedrate->volume->indexed_vertex_array.triangle_indices.size()); + + _3DScene::polyline3_to_verts(polyline.polyline, preview_data.travel.width, preview_data.travel.height, *feedrate->volume); + } + } + + return true; +} + +bool GLCanvas3D::_travel_paths_by_tool(const GCodePreviewData& preview_data, const std::vector<float>& tool_colors) +{ + // Helper structure for tool + struct Tool + { + unsigned int value; + GLVolume* volume; + + explicit Tool(unsigned int value) + : value(value) + , volume(nullptr) + { + } + + bool operator == (const Tool& other) const + { + return value == other.value; + } + }; + + typedef std::vector<Tool> ToolsList; + + // colors travels by tool + + // detects tools + ToolsList tools; + for (const GCodePreviewData::Travel::Polyline& polyline : preview_data.travel.polylines) + { + if (std::find(tools.begin(), tools.end(), Tool(polyline.extruder_id)) == tools.end()) + tools.emplace_back(polyline.extruder_id); + } + + // nothing to render, return + if (tools.empty()) + return true; + + // creates a new volume for each tool + for (Tool& tool : tools) + { + GLVolume* volume = new GLVolume(tool_colors.data() + tool.value * 4); + if (volume == nullptr) + return false; + else + { + tool.volume = volume; + m_volumes.volumes.emplace_back(volume); + } + } + + // populates volumes + for (const GCodePreviewData::Travel::Polyline& polyline : preview_data.travel.polylines) + { + ToolsList::iterator tool = std::find(tools.begin(), tools.end(), Tool(polyline.extruder_id)); + if (tool != tools.end()) + { + tool->volume->print_zs.push_back(unscale(polyline.polyline.bounding_box().min.z)); + tool->volume->offsets.push_back(tool->volume->indexed_vertex_array.quad_indices.size()); + tool->volume->offsets.push_back(tool->volume->indexed_vertex_array.triangle_indices.size()); + + _3DScene::polyline3_to_verts(polyline.polyline, preview_data.travel.width, preview_data.travel.height, *tool->volume); + } + } + + return true; +} + +void GLCanvas3D::_load_gcode_retractions(const GCodePreviewData& preview_data) +{ + m_gcode_preview_volume_index.first_volumes.emplace_back(GCodePreviewVolumeIndex::Retraction, 0, (unsigned int)m_volumes.volumes.size()); + + // nothing to render, return + if (preview_data.retraction.positions.empty()) + return; + + GLVolume* volume = new GLVolume(preview_data.retraction.color.rgba); + if (volume != nullptr) + { + m_volumes.volumes.emplace_back(volume); + + GCodePreviewData::Retraction::PositionsList copy(preview_data.retraction.positions); + std::sort(copy.begin(), copy.end(), [](const GCodePreviewData::Retraction::Position& p1, const GCodePreviewData::Retraction::Position& p2){ return p1.position.z < p2.position.z; }); + + for (const GCodePreviewData::Retraction::Position& position : copy) + { + volume->print_zs.push_back(unscale(position.position.z)); + volume->offsets.push_back(volume->indexed_vertex_array.quad_indices.size()); + volume->offsets.push_back(volume->indexed_vertex_array.triangle_indices.size()); + + _3DScene::point3_to_verts(position.position, position.width, position.height, *volume); + } + + // finalize volumes and sends geometry to gpu + volume->bounding_box = volume->indexed_vertex_array.bounding_box(); + volume->indexed_vertex_array.finalize_geometry(m_use_VBOs && m_initialized); + } +} + +void GLCanvas3D::_load_gcode_unretractions(const GCodePreviewData& preview_data) +{ + m_gcode_preview_volume_index.first_volumes.emplace_back(GCodePreviewVolumeIndex::Unretraction, 0, (unsigned int)m_volumes.volumes.size()); + + // nothing to render, return + if (preview_data.unretraction.positions.empty()) + return; + + GLVolume* volume = new GLVolume(preview_data.unretraction.color.rgba); + if (volume != nullptr) + { + m_volumes.volumes.emplace_back(volume); + + GCodePreviewData::Retraction::PositionsList copy(preview_data.unretraction.positions); + std::sort(copy.begin(), copy.end(), [](const GCodePreviewData::Retraction::Position& p1, const GCodePreviewData::Retraction::Position& p2){ return p1.position.z < p2.position.z; }); + + for (const GCodePreviewData::Retraction::Position& position : copy) + { + volume->print_zs.push_back(unscale(position.position.z)); + volume->offsets.push_back(volume->indexed_vertex_array.quad_indices.size()); + volume->offsets.push_back(volume->indexed_vertex_array.triangle_indices.size()); + + _3DScene::point3_to_verts(position.position, position.width, position.height, *volume); + } + + // finalize volumes and sends geometry to gpu + volume->bounding_box = volume->indexed_vertex_array.bounding_box(); + volume->indexed_vertex_array.finalize_geometry(m_use_VBOs && m_initialized); + } +} + +void GLCanvas3D::_load_shells() +{ + size_t initial_volumes_count = m_volumes.volumes.size(); + m_gcode_preview_volume_index.first_volumes.emplace_back(GCodePreviewVolumeIndex::Shell, 0, (unsigned int)initial_volumes_count); + + if (m_print->objects.empty()) + // nothing to render, return + return; + + // adds objects' volumes + unsigned int object_id = 0; + for (PrintObject* obj : m_print->objects) + { + ModelObject* model_obj = obj->model_object(); + + std::vector<int> instance_ids(model_obj->instances.size()); + for (int i = 0; i < (int)model_obj->instances.size(); ++i) + { + instance_ids[i] = i; + } + + m_volumes.load_object(model_obj, object_id, instance_ids, "object", "object", "object", m_use_VBOs && m_initialized); + + ++object_id; + } + + // adds wipe tower's volume + coordf_t max_z = m_print->objects[0]->model_object()->get_model()->bounding_box().max.z; + const PrintConfig& config = m_print->config; + unsigned int extruders_count = config.nozzle_diameter.size(); + if ((extruders_count > 1) && config.single_extruder_multi_material && config.wipe_tower && !config.complete_objects) { + const float width_per_extruder = 15.0f; // a simple workaround after wipe_tower_per_color_wipe got obsolete + m_volumes.load_wipe_tower_preview(1000, config.wipe_tower_x, config.wipe_tower_y, config.wipe_tower_width, width_per_extruder * (extruders_count - 1), max_z, config.wipe_tower_rotation_angle, m_use_VBOs && m_initialized); + } +} + +void GLCanvas3D::_update_gcode_volumes_visibility(const GCodePreviewData& preview_data) +{ + unsigned int size = (unsigned int)m_gcode_preview_volume_index.first_volumes.size(); + for (unsigned int i = 0; i < size; ++i) + { + std::vector<GLVolume*>::iterator begin = m_volumes.volumes.begin() + m_gcode_preview_volume_index.first_volumes[i].id; + std::vector<GLVolume*>::iterator end = (i + 1 < size) ? m_volumes.volumes.begin() + m_gcode_preview_volume_index.first_volumes[i + 1].id : m_volumes.volumes.end(); + + 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) + { + case GCodePreviewVolumeIndex::Extrusion: + { + if ((ExtrusionRole)m_gcode_preview_volume_index.first_volumes[i].flag == erCustom) + volume->zoom_to_volumes = false; + + volume->is_active = preview_data.extrusion.is_role_flag_set((ExtrusionRole)m_gcode_preview_volume_index.first_volumes[i].flag); + break; + } + case GCodePreviewVolumeIndex::Travel: + { + volume->is_active = preview_data.travel.is_visible; + volume->zoom_to_volumes = false; + break; + } + case GCodePreviewVolumeIndex::Retraction: + { + volume->is_active = preview_data.retraction.is_visible; + volume->zoom_to_volumes = false; + break; + } + case GCodePreviewVolumeIndex::Unretraction: + { + volume->is_active = preview_data.unretraction.is_visible; + volume->zoom_to_volumes = false; + break; + } + case GCodePreviewVolumeIndex::Shell: + { + volume->is_active = preview_data.shell.is_visible; + volume->color[3] = 0.25f; + volume->zoom_to_volumes = false; + break; + } + default: + { + volume->is_active = false; + volume->zoom_to_volumes = false; + break; + } + } + } + } +} + +void GLCanvas3D::_on_move(const std::vector<int>& volume_idxs) +{ + if (m_model == nullptr) + return; + + std::set<std::string> done; // prevent moving instances twice + bool object_moved = false; + Pointf3 wipe_tower_origin(0.0, 0.0, 0.0); + for (int volume_idx : volume_idxs) + { + GLVolume* volume = m_volumes.volumes[volume_idx]; + int obj_idx = volume->object_idx(); + int instance_idx = volume->instance_idx(); + + // prevent moving instances twice + char done_id[64]; + ::sprintf(done_id, "%d_%d", obj_idx, instance_idx); + if (done.find(done_id) != done.end()) + continue; + + done.insert(done_id); + + if (obj_idx < 1000) + { + // Move a regular object. + ModelObject* model_object = m_model->objects[obj_idx]; + model_object->instances[instance_idx]->offset.translate(volume->origin.x, volume->origin.y); + model_object->invalidate_bounding_box(); + object_moved = true; + } + else if (obj_idx == 1000) + // Move a wipe tower proxy. + wipe_tower_origin = volume->origin; + } + + if (object_moved) + m_on_instance_moved_callback.call(); + + if (wipe_tower_origin != Pointf3(0.0, 0.0, 0.0)) + m_on_wipe_tower_moved_callback.call(wipe_tower_origin.x, wipe_tower_origin.y); +} + +void GLCanvas3D::_on_select(int volume_idx) +{ + int id = -1; + if ((volume_idx != -1) && (volume_idx < (int)m_volumes.volumes.size())) + { + if (m_select_by == "volume") + id = m_volumes.volumes[volume_idx]->volume_idx(); + else if (m_select_by == "object") + id = m_volumes.volumes[volume_idx]->object_idx(); + } + m_on_select_object_callback.call(id); +} + +void GLCanvas3D::_update_gizmos_data() +{ + int id = _get_first_selected_object_id(); + if ((id != -1) && (m_model != nullptr)) + { + ModelObject* model_object = m_model->objects[id]; + if (model_object != nullptr) + { + ModelInstance* model_instance = model_object->instances[0]; + if (model_instance != nullptr) + m_gizmos.update_data(model_instance->scaling_factor); + } + } +} + +std::vector<float> GLCanvas3D::_parse_colors(const std::vector<std::string>& colors) +{ + static const float INV_255 = 1.0f / 255.0f; + + std::vector<float> output(colors.size() * 4, 1.0f); + for (size_t i = 0; i < colors.size(); ++i) + { + const std::string& color = colors[i]; + const char* c = color.data() + 1; + if ((color.size() == 7) && (color.front() == '#')) + { + for (size_t j = 0; j < 3; ++j) + { + int digit1 = hex_digit_to_int(*c++); + int digit2 = hex_digit_to_int(*c++); + if ((digit1 == -1) || (digit2 == -1)) + break; + + output[i * 4 + j] = float(digit1 * 16 + digit2) * INV_255; + } + } + } + return output; +} + +} // namespace GUI +} // namespace Slic3r diff --git a/xs/src/slic3r/GUI/GLCanvas3D.hpp b/xs/src/slic3r/GUI/GLCanvas3D.hpp new file mode 100644 index 000000000..b0706a05d --- /dev/null +++ b/xs/src/slic3r/GUI/GLCanvas3D.hpp @@ -0,0 +1,636 @@ +#ifndef slic3r_GLCanvas3D_hpp_ +#define slic3r_GLCanvas3D_hpp_ + +#include "../../slic3r/GUI/3DScene.hpp" +#include "../../slic3r/GUI/GLTexture.hpp" + +class wxTimer; +class wxSizeEvent; +class wxIdleEvent; +class wxKeyEvent; +class wxMouseEvent; +class wxTimerEvent; +class wxPaintEvent; + +namespace Slic3r { + +class GLShader; +class ExPolygon; + +namespace GUI { + +class GLGizmoBase; + +class GeometryBuffer +{ + std::vector<float> m_vertices; + std::vector<float> m_tex_coords; + +public: + bool set_from_triangles(const Polygons& triangles, float z, bool generate_tex_coords); + bool set_from_lines(const Lines& lines, float z); + + const float* get_vertices() const; + const float* get_tex_coords() const; + + unsigned int get_vertices_count() const; +}; + +class Size +{ + int m_width; + int m_height; + +public: + Size(); + Size(int width, int height); + + int get_width() const; + void set_width(int width); + + int get_height() const; + void set_height(int height); +}; + +class Rect +{ + float m_left; + float m_top; + float m_right; + float m_bottom; + +public: + Rect(); + Rect(float left, float top, float right, float bottom); + + float get_left() const; + void set_left(float left); + + float get_top() const; + void set_top(float top); + + float get_right() const; + void set_right(float right); + + float get_bottom() const; + void set_bottom(float bottom); +}; + +class GLCanvas3D +{ + struct GCodePreviewVolumeIndex + { + enum EType + { + Extrusion, + Travel, + Retraction, + Unretraction, + Shell, + Num_Geometry_Types + }; + + struct FirstVolume + { + EType type; + unsigned int flag; + // Index of the first volume in a GLVolumeCollection. + unsigned int id; + + FirstVolume(EType type, unsigned int flag, unsigned int id) : type(type), flag(flag), id(id) {} + }; + + std::vector<FirstVolume> first_volumes; + + void reset() { first_volumes.clear(); } + }; + +public: + struct Camera + { + enum EType : unsigned char + { + Unknown, +// Perspective, + Ortho, + Num_types + }; + + EType type; + float zoom; + float phi; +// float distance; + Pointf3 target; + + private: + float m_theta; + + public: + Camera(); + + std::string get_type_as_string() const; + + float get_theta() const; + void set_theta(float theta); + }; + + class Bed + { + public: + enum EType : unsigned char + { + MK2, + MK3, + Custom, + Num_Types + }; + + private: + EType m_type; + Pointfs m_shape; + BoundingBoxf3 m_bounding_box; + Polygon m_polygon; + GeometryBuffer m_triangles; + GeometryBuffer m_gridlines; + mutable GLTexture m_top_texture; + mutable GLTexture m_bottom_texture; + + public: + Bed(); + + bool is_prusa() const; + bool is_custom() const; + + const Pointfs& get_shape() const; + void set_shape(const Pointfs& shape); + + const BoundingBoxf3& get_bounding_box() const; + bool contains(const Point& point) const; + Point point_projection(const Point& point) const; + + void render(float theta) const; + + private: + void _calc_bounding_box(); + void _calc_triangles(const ExPolygon& poly); + void _calc_gridlines(const ExPolygon& poly, const BoundingBox& bed_bbox); + EType _detect_type() const; + void _render_mk2(float theta) const; + void _render_mk3(float theta) const; + void _render_prusa(float theta) const; + void _render_custom() const; + static bool _are_equal(const Pointfs& bed_1, const Pointfs& bed_2); + }; + + struct Axes + { + Pointf3 origin; + float length; + + Axes(); + + void render(bool depth_test) const; + }; + + class CuttingPlane + { + float m_z; + GeometryBuffer m_lines; + + public: + CuttingPlane(); + + bool set(float z, const ExPolygons& polygons); + + void render(const BoundingBoxf3& bb) const; + + private: + void _render_plane(const BoundingBoxf3& bb) const; + void _render_contour() const; + }; + + class Shader + { + GLShader* m_shader; + + public: + Shader(); + ~Shader(); + + bool init(const std::string& vertex_shader_filename, const std::string& fragment_shader_filename); + + bool is_initialized() const; + + bool start_using() const; + void stop_using() const; + + void set_uniform(const std::string& name, float value) const; + + const GLShader* get_shader() const; + + private: + void _reset(); + }; + + class LayersEditing + { + public: + enum EState : unsigned char + { + Unknown, + Editing, + Completed, + Num_States + }; + + private: + bool m_use_legacy_opengl; + bool m_enabled; + Shader m_shader; + unsigned int m_z_texture_id; + mutable GLTexture m_tooltip_texture; + mutable GLTexture m_reset_texture; + + public: + EState state; + float band_width; + float strength; + int last_object_id; + float last_z; + unsigned int last_action; + + LayersEditing(); + ~LayersEditing(); + + bool init(const std::string& vertex_shader_filename, const std::string& fragment_shader_filename); + + bool is_allowed() const; + void set_use_legacy_opengl(bool use_legacy_opengl); + + bool is_enabled() const; + void set_enabled(bool enabled); + + unsigned int get_z_texture_id() const; + + void render(const GLCanvas3D& canvas, const PrintObject& print_object, const GLVolume& volume) const; + + int get_shader_program_id() const; + + static float get_cursor_z_relative(const GLCanvas3D& canvas); + static bool bar_rect_contains(const GLCanvas3D& canvas, float x, float y); + static bool reset_rect_contains(const GLCanvas3D& canvas, float x, float y); + static Rect get_bar_rect_screen(const GLCanvas3D& canvas); + static Rect get_reset_rect_screen(const GLCanvas3D& canvas); + static Rect get_bar_rect_viewport(const GLCanvas3D& canvas); + static Rect get_reset_rect_viewport(const GLCanvas3D& canvas); + + private: + bool _is_initialized() const; + void _render_tooltip_texture(const GLCanvas3D& canvas, const Rect& bar_rect, const Rect& reset_rect) const; + void _render_reset_texture(const Rect& reset_rect) const; + void _render_active_object_annotations(const GLCanvas3D& canvas, const GLVolume& volume, const PrintObject& print_object, const Rect& bar_rect) const; + void _render_profile(const PrintObject& print_object, const Rect& bar_rect) const; + }; + + struct Mouse + { + struct Drag + { + static const Point Invalid_2D_Point; + static const Pointf3 Invalid_3D_Point; + + Point start_position_2D; + Pointf3 start_position_3D; + Vectorf3 volume_center_offset; + int volume_idx; + + public: + Drag(); + }; + + bool dragging; + Pointf position; + Drag drag; + + Mouse(); + + void set_start_position_2D_as_invalid(); + void set_start_position_3D_as_invalid(); + + bool is_start_position_2D_defined() const; + bool is_start_position_3D_defined() const; + }; + + class Gizmos + { + static const float OverlayOffsetX; + static const float OverlayGapY; + + public: + enum EType : unsigned char + { + Undefined, + Scale, + Rotate, + Num_Types + }; + + private: + bool m_enabled; + typedef std::map<EType, GLGizmoBase*> GizmosMap; + GizmosMap m_gizmos; + EType m_current; + bool m_dragging; + + public: + Gizmos(); + ~Gizmos(); + + bool init(); + + bool is_enabled() const; + void set_enabled(bool enable); + + void update_hover_state(const GLCanvas3D& canvas, const Pointf& mouse_pos); + void update_on_off_state(const GLCanvas3D& canvas, const Pointf& mouse_pos); + void reset_all_states(); + + void set_hover_id(int id); + + bool overlay_contains_mouse(const GLCanvas3D& canvas, const Pointf& mouse_pos) const; + bool grabber_contains_mouse() const; + void update(const Pointf& mouse_pos); + void update_data(float scale); + + bool is_running() const; + bool is_dragging() const; + void start_dragging(); + void stop_dragging(); + + float get_scale() const; + + void render(const GLCanvas3D& canvas, const BoundingBoxf3& box) const; + void render_current_gizmo_for_picking_pass(const BoundingBoxf3& box) const; + + private: + void _reset(); + + void _render_overlay(const GLCanvas3D& canvas) const; + void _render_current_gizmo(const BoundingBoxf3& box) const; + + float _get_total_overlay_height() const; + GLGizmoBase* _get_current() const; + }; + +private: + wxGLCanvas* m_canvas; + wxGLContext* m_context; + wxTimer* m_timer; + Camera m_camera; + Bed m_bed; + Axes m_axes; + CuttingPlane m_cutting_plane; + LayersEditing m_layers_editing; + Shader m_shader; + Mouse m_mouse; + mutable Gizmos m_gizmos; + + mutable GLVolumeCollection m_volumes; + DynamicPrintConfig* m_config; + Print* m_print; + Model* m_model; + + bool m_dirty; + bool m_initialized; + bool m_use_VBOs; + bool m_force_zoom_to_bed_enabled; + bool m_apply_zoom_to_volumes_filter; + mutable int m_hover_volume_id; + bool m_warning_texture_enabled; + bool m_legend_texture_enabled; + bool m_picking_enabled; + bool m_moving_enabled; + bool m_shader_enabled; + bool m_multisample_allowed; + + std::string m_color_by; + std::string m_select_by; + std::string m_drag_by; + + bool m_reload_delayed; + std::vector<std::vector<int>> m_objects_volumes_idxs; + std::vector<int> m_objects_selections; + + GCodePreviewVolumeIndex m_gcode_preview_volume_index; + + PerlCallback m_on_viewport_changed_callback; + PerlCallback m_on_double_click_callback; + PerlCallback m_on_right_click_callback; + PerlCallback m_on_select_object_callback; + PerlCallback m_on_model_update_callback; + PerlCallback m_on_remove_object_callback; + PerlCallback m_on_arrange_callback; + PerlCallback m_on_rotate_object_left_callback; + PerlCallback m_on_rotate_object_right_callback; + PerlCallback m_on_scale_object_uniformly_callback; + PerlCallback m_on_increase_objects_callback; + PerlCallback m_on_decrease_objects_callback; + PerlCallback m_on_instance_moved_callback; + PerlCallback m_on_wipe_tower_moved_callback; + PerlCallback m_on_enable_action_buttons_callback; + PerlCallback m_on_gizmo_scale_uniformly_callback; + +public: + GLCanvas3D(wxGLCanvas* canvas); + ~GLCanvas3D(); + + bool init(bool useVBOs, bool use_legacy_opengl); + + bool set_current(); + + void set_as_dirty(); + + unsigned int get_volumes_count() const; + void reset_volumes(); + void deselect_volumes(); + void select_volume(unsigned int id); + void update_volumes_selection(const std::vector<int>& selections); + bool check_volumes_outside_state(const DynamicPrintConfig* config) const; + bool move_volume_up(unsigned int id); + bool move_volume_down(unsigned int id); + + void set_objects_selections(const std::vector<int>& selections); + + void set_config(DynamicPrintConfig* config); + void set_print(Print* print); + void set_model(Model* model); + + // Set the bed shape to a single closed 2D polygon(array of two element arrays), + // triangulate the bed and store the triangles into m_bed.m_triangles, + // fills the m_bed.m_grid_lines and sets m_bed.m_origin. + // Sets m_bed.m_polygon to limit the object placement. + void set_bed_shape(const Pointfs& shape); + // Used by ObjectCutDialog and ObjectPartsPanel to generate a rectangular ground plane to support the scene objects. + void set_auto_bed_shape(); + + void set_axes_length(float length); + + void set_cutting_plane(float z, const ExPolygons& polygons); + + void set_color_by(const std::string& value); + void set_select_by(const std::string& value); + void set_drag_by(const std::string& value); + + float get_camera_zoom() const; + + BoundingBoxf3 volumes_bounding_box() const; + + bool is_layers_editing_enabled() const; + bool is_layers_editing_allowed() const; + bool is_shader_enabled() const; + + bool is_reload_delayed() const; + + void enable_layers_editing(bool enable); + void enable_warning_texture(bool enable); + void enable_legend_texture(bool enable); + void enable_picking(bool enable); + void enable_moving(bool enable); + void enable_gizmos(bool enable); + void enable_shader(bool enable); + void enable_force_zoom_to_bed(bool enable); + void allow_multisample(bool allow); + + void zoom_to_bed(); + void zoom_to_volumes(); + void select_view(const std::string& direction); + void set_viewport_from_scene(const GLCanvas3D& other); + + void update_volumes_colors_by_extruder(); + + void render(); + + std::vector<double> get_current_print_zs(bool active_only) const; + void set_toolpaths_range(double low, double high); + + std::vector<int> load_object(const ModelObject& model_object, int obj_idx, std::vector<int> instance_idxs); + std::vector<int> load_object(const Model& model, int obj_idx); + + 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 register_on_viewport_changed_callback(void* callback); + void register_on_double_click_callback(void* callback); + void register_on_right_click_callback(void* callback); + void register_on_select_object_callback(void* callback); + void register_on_model_update_callback(void* callback); + void register_on_remove_object_callback(void* callback); + void register_on_arrange_callback(void* callback); + void register_on_rotate_object_left_callback(void* callback); + void register_on_rotate_object_right_callback(void* callback); + void register_on_scale_object_uniformly_callback(void* callback); + void register_on_increase_objects_callback(void* callback); + void register_on_decrease_objects_callback(void* callback); + void register_on_instance_moved_callback(void* callback); + void register_on_wipe_tower_moved_callback(void* callback); + void register_on_enable_action_buttons_callback(void* callback); + void register_on_gizmo_scale_uniformly_callback(void* callback); + + void bind_event_handlers(); + void unbind_event_handlers(); + + void on_size(wxSizeEvent& evt); + void on_idle(wxIdleEvent& evt); + void on_char(wxKeyEvent& evt); + void on_mouse_wheel(wxMouseEvent& evt); + void on_timer(wxTimerEvent& evt); + void on_mouse(wxMouseEvent& evt); + void on_paint(wxPaintEvent& evt); + void on_key_down(wxKeyEvent& evt); + + Size get_canvas_size() const; + Point get_local_mouse_position() const; + +private: + bool _is_shown_on_screen() const; + void _force_zoom_to_bed(); + + void _resize(unsigned int w, unsigned int h); + + BoundingBoxf3 _max_bounding_box() const; + BoundingBoxf3 _selected_volumes_bounding_box() const; + + void _zoom_to_bounding_box(const BoundingBoxf3& bbox); + float _get_zoom_to_bounding_box_factor(const BoundingBoxf3& bbox) const; + + void _deregister_callbacks(); + + void _mark_volumes_for_layer_height() const; + void _refresh_if_shown_on_screen(); + + void _camera_tranform() const; + void _picking_pass() const; + void _render_background() const; + void _render_bed(float theta) const; + void _render_axes(bool depth_test) const; + void _render_objects() const; + void _render_cutting_plane() const; + void _render_warning_texture() const; + void _render_legend_texture() const; + void _render_layer_editing_overlay() const; + void _render_volumes(bool fake_colors) const; + void _render_gizmo() const; + + float _get_layers_editing_cursor_z_relative() const; + void _perform_layer_editing_action(wxMouseEvent* evt = nullptr); + + // Convert the screen space coordinate to an object space coordinate. + // If the Z screen space coordinate is not provided, a depth buffer value is substituted. + Pointf3 _mouse_to_3d(const Point& mouse_pos, float* z = nullptr); + + // Convert the screen space coordinate to world coordinate on the bed. + Pointf3 _mouse_to_bed_3d(const Point& mouse_pos); + + void _start_timer(); + void _stop_timer(); + + int _get_first_selected_object_id() const; + + // generates gcode extrusion paths geometry + void _load_gcode_extrusion_paths(const GCodePreviewData& preview_data, const std::vector<float>& tool_colors); + // generates gcode travel paths geometry + void _load_gcode_travel_paths(const GCodePreviewData& preview_data, const std::vector<float>& tool_colors); + bool _travel_paths_by_type(const GCodePreviewData& preview_data); + bool _travel_paths_by_feedrate(const GCodePreviewData& preview_data); + bool _travel_paths_by_tool(const GCodePreviewData& preview_data, const std::vector<float>& tool_colors); + // generates gcode retractions geometry + void _load_gcode_retractions(const GCodePreviewData& preview_data); + // generates gcode unretractions geometry + void _load_gcode_unretractions(const GCodePreviewData& preview_data); + // generates objects and wipe tower geometry + void _load_shells(); + // sets gcode geometry visibility according to user selection + void _update_gcode_volumes_visibility(const GCodePreviewData& preview_data); + + void _on_move(const std::vector<int>& volume_idxs); + void _on_select(int volume_idx); + + void _update_gizmos_data(); + + static std::vector<float> _parse_colors(const std::vector<std::string>& colors); +}; + +} // namespace GUI +} // namespace Slic3r + +#endif // slic3r_GLCanvas3D_hpp_ diff --git a/xs/src/slic3r/GUI/GLCanvas3DManager.cpp b/xs/src/slic3r/GUI/GLCanvas3DManager.cpp new file mode 100644 index 000000000..ec4ac1606 --- /dev/null +++ b/xs/src/slic3r/GUI/GLCanvas3DManager.cpp @@ -0,0 +1,677 @@ +#include "GLCanvas3DManager.hpp" +#include "../../slic3r/GUI/GUI.hpp" +#include "../../slic3r/GUI/AppConfig.hpp" +#include "../../slic3r/GUI/GLCanvas3D.hpp" + +#include <GL/glew.h> + +#include <boost/algorithm/string/split.hpp> +#include <boost/algorithm/string/classification.hpp> + +#include <wx/glcanvas.h> +#include <wx/timer.h> + +#include <vector> +#include <string> +#include <iostream> + +namespace Slic3r { +namespace GUI { + +GLCanvas3DManager::GLInfo::GLInfo() + : version("") + , glsl_version("") + , vendor("") + , renderer("") +{ +} + +void GLCanvas3DManager::GLInfo::detect() +{ + const char* data = (const char*)::glGetString(GL_VERSION); + if (data != nullptr) + version = data; + + data = (const char*)::glGetString(GL_SHADING_LANGUAGE_VERSION); + if (data != nullptr) + glsl_version = data; + + data = (const char*)::glGetString(GL_VENDOR); + if (data != nullptr) + vendor = data; + + data = (const char*)::glGetString(GL_RENDERER); + if (data != nullptr) + renderer = data; +} + +bool GLCanvas3DManager::GLInfo::is_version_greater_or_equal_to(unsigned int major, unsigned int minor) const +{ + std::vector<std::string> tokens; + boost::split(tokens, version, boost::is_any_of(" "), boost::token_compress_on); + + if (tokens.empty()) + return false; + + std::vector<std::string> numbers; + boost::split(numbers, tokens[0], boost::is_any_of("."), boost::token_compress_on); + + unsigned int gl_major = 0; + unsigned int gl_minor = 0; + + if (numbers.size() > 0) + gl_major = ::atoi(numbers[0].c_str()); + + if (numbers.size() > 1) + gl_minor = ::atoi(numbers[1].c_str()); + + if (gl_major < major) + return false; + else if (gl_major > major) + return true; + else + return gl_minor >= minor; +} + +std::string GLCanvas3DManager::GLInfo::to_string(bool format_as_html, bool extensions) const +{ + std::stringstream out; + + std::string h2_start = format_as_html ? "<b>" : ""; + std::string h2_end = format_as_html ? "</b>" : ""; + std::string b_start = format_as_html ? "<b>" : ""; + std::string b_end = format_as_html ? "</b>" : ""; + std::string line_end = format_as_html ? "<br>" : "\n"; + + out << h2_start << "OpenGL installation" << h2_end << line_end; + out << b_start << "GL version: " << b_end << (version.empty() ? "N/A" : version) << line_end; + out << b_start << "Vendor: " << b_end << (vendor.empty() ? "N/A" : vendor) << line_end; + out << b_start << "Renderer: " << b_end << (renderer.empty() ? "N/A" : renderer) << line_end; + out << b_start << "GLSL version: " << b_end << (glsl_version.empty() ? "N/A" : glsl_version) << line_end; + + if (extensions) + { + std::vector<std::string> extensions_list; + std::string extensions_str = (const char*)::glGetString(GL_EXTENSIONS); + boost::split(extensions_list, extensions_str, boost::is_any_of(" "), boost::token_compress_off); + + if (!extensions_list.empty()) + { + out << h2_start << "Installed extensions:" << h2_end << line_end; + + std::sort(extensions_list.begin(), extensions_list.end()); + for (const std::string& ext : extensions_list) + { + out << ext << line_end; + } + } + } + + return out.str(); +} + +GLCanvas3DManager::GLCanvas3DManager() + : m_current(nullptr) + , m_gl_initialized(false) + , m_use_legacy_opengl(false) + , m_use_VBOs(false) +{ +} + +bool GLCanvas3DManager::add(wxGLCanvas* canvas) +{ + if (canvas == nullptr) + return false; + + if (_get_canvas(canvas) != m_canvases.end()) + return false; + + GLCanvas3D* canvas3D = new GLCanvas3D(canvas); + if (canvas3D == nullptr) + return false; + + canvas3D->bind_event_handlers(); + m_canvases.insert(CanvasesMap::value_type(canvas, canvas3D)); + + return true; +} + +bool GLCanvas3DManager::remove(wxGLCanvas* canvas) +{ + CanvasesMap::iterator it = _get_canvas(canvas); + if (it == m_canvases.end()) + return false; + + it->second->unbind_event_handlers(); + delete it->second; + m_canvases.erase(it); + + return true; +} + +void GLCanvas3DManager::remove_all() +{ + for (CanvasesMap::value_type& item : m_canvases) + { + item.second->unbind_event_handlers(); + delete item.second; + } + m_canvases.clear(); +} + +unsigned int GLCanvas3DManager::count() const +{ + return (unsigned int)m_canvases.size(); +} + +void GLCanvas3DManager::init_gl() +{ + if (!m_gl_initialized) + { + glewInit(); + m_gl_info.detect(); + const AppConfig* config = GUI::get_app_config(); + m_use_legacy_opengl = (config == nullptr) || (config->get("use_legacy_opengl") == "1"); + m_use_VBOs = !m_use_legacy_opengl && m_gl_info.is_version_greater_or_equal_to(2, 0); + m_gl_initialized = true; + } +} + +std::string GLCanvas3DManager::get_gl_info(bool format_as_html, bool extensions) const +{ + return m_gl_info.to_string(format_as_html, extensions); +} + +bool GLCanvas3DManager::use_VBOs() const +{ + return m_use_VBOs; +} + +bool GLCanvas3DManager::init(wxGLCanvas* canvas) +{ + CanvasesMap::const_iterator it = _get_canvas(canvas); + if (it != m_canvases.end()) + return (it->second != nullptr) ? _init(*it->second) : false; + else + return false; +} + +void GLCanvas3DManager::set_as_dirty(wxGLCanvas* canvas) +{ + CanvasesMap::iterator it = _get_canvas(canvas); + if (it != m_canvases.end()) + it->second->set_as_dirty(); +} + +unsigned int GLCanvas3DManager::get_volumes_count(wxGLCanvas* canvas) const +{ + CanvasesMap::const_iterator it = _get_canvas(canvas); + return (it != m_canvases.end()) ? it->second->get_volumes_count() : 0; +} + +void GLCanvas3DManager::reset_volumes(wxGLCanvas* canvas) +{ + CanvasesMap::iterator it = _get_canvas(canvas); + if (it != m_canvases.end()) + it->second->reset_volumes(); +} + +void GLCanvas3DManager::deselect_volumes(wxGLCanvas* canvas) +{ + CanvasesMap::iterator it = _get_canvas(canvas); + if (it != m_canvases.end()) + it->second->deselect_volumes(); +} + +void GLCanvas3DManager::select_volume(wxGLCanvas* canvas, unsigned int id) +{ + CanvasesMap::iterator it = _get_canvas(canvas); + if (it != m_canvases.end()) + it->second->select_volume(id); +} + +void GLCanvas3DManager::update_volumes_selection(wxGLCanvas* canvas, const std::vector<int>& selections) +{ + CanvasesMap::iterator it = _get_canvas(canvas); + if (it != m_canvases.end()) + it->second->update_volumes_selection(selections); +} + +bool GLCanvas3DManager::check_volumes_outside_state(wxGLCanvas* canvas, const DynamicPrintConfig* config) const +{ + CanvasesMap::const_iterator it = _get_canvas(canvas); + return (it != m_canvases.end()) ? it->second->check_volumes_outside_state(config) : false; +} + +bool GLCanvas3DManager::move_volume_up(wxGLCanvas* canvas, unsigned int id) +{ + CanvasesMap::const_iterator it = _get_canvas(canvas); + return (it != m_canvases.end()) ? it->second->move_volume_up(id) : false; +} + +bool GLCanvas3DManager::move_volume_down(wxGLCanvas* canvas, unsigned int id) +{ + CanvasesMap::const_iterator it = _get_canvas(canvas); + return (it != m_canvases.end()) ? it->second->move_volume_down(id) : false; +} + +void GLCanvas3DManager::set_objects_selections(wxGLCanvas* canvas, const std::vector<int>& selections) +{ + CanvasesMap::iterator it = _get_canvas(canvas); + if (it != m_canvases.end()) + it->second->set_objects_selections(selections); +} + +void GLCanvas3DManager::set_config(wxGLCanvas* canvas, DynamicPrintConfig* config) +{ + CanvasesMap::iterator it = _get_canvas(canvas); + if (it != m_canvases.end()) + it->second->set_config(config); +} + +void GLCanvas3DManager::set_print(wxGLCanvas* canvas, Print* print) +{ + CanvasesMap::iterator it = _get_canvas(canvas); + if (it != m_canvases.end()) + it->second->set_print(print); +} + +void GLCanvas3DManager::set_model(wxGLCanvas* canvas, Model* model) +{ + CanvasesMap::iterator it = _get_canvas(canvas); + if (it != m_canvases.end()) + it->second->set_model(model); +} + +void GLCanvas3DManager::set_bed_shape(wxGLCanvas* canvas, const Pointfs& shape) +{ + CanvasesMap::iterator it = _get_canvas(canvas); + if (it != m_canvases.end()) + it->second->set_bed_shape(shape); +} + +void GLCanvas3DManager::set_auto_bed_shape(wxGLCanvas* canvas) +{ + CanvasesMap::iterator it = _get_canvas(canvas); + if (it != m_canvases.end()) + it->second->set_auto_bed_shape(); +} + +BoundingBoxf3 GLCanvas3DManager::get_volumes_bounding_box(wxGLCanvas* canvas) +{ + CanvasesMap::const_iterator it = _get_canvas(canvas); + return (it != m_canvases.end()) ? it->second->volumes_bounding_box() : BoundingBoxf3(); +} + +void GLCanvas3DManager::set_axes_length(wxGLCanvas* canvas, float length) +{ + CanvasesMap::iterator it = _get_canvas(canvas); + if (it != m_canvases.end()) + it->second->set_axes_length(length); +} + +void GLCanvas3DManager::set_cutting_plane(wxGLCanvas* canvas, float z, const ExPolygons& polygons) +{ + CanvasesMap::iterator it = _get_canvas(canvas); + if (it != m_canvases.end()) + it->second->set_cutting_plane(z, polygons); +} + +void GLCanvas3DManager::set_color_by(wxGLCanvas* canvas, const std::string& value) +{ + CanvasesMap::iterator it = _get_canvas(canvas); + if (it != m_canvases.end()) + it->second->set_color_by(value); +} + +void GLCanvas3DManager::set_select_by(wxGLCanvas* canvas, const std::string& value) +{ + CanvasesMap::iterator it = _get_canvas(canvas); + if (it != m_canvases.end()) + it->second->set_select_by(value); +} + +void GLCanvas3DManager::set_drag_by(wxGLCanvas* canvas, const std::string& value) +{ + CanvasesMap::iterator it = _get_canvas(canvas); + if (it != m_canvases.end()) + it->second->set_drag_by(value); +} + +bool GLCanvas3DManager::is_layers_editing_enabled(wxGLCanvas* canvas) const +{ + CanvasesMap::const_iterator it = _get_canvas(canvas); + return (it != m_canvases.end()) ? it->second->is_layers_editing_enabled() : false; +} + +bool GLCanvas3DManager::is_layers_editing_allowed(wxGLCanvas* canvas) const +{ + CanvasesMap::const_iterator it = _get_canvas(canvas); + return (it != m_canvases.end()) ? it->second->is_layers_editing_allowed() : false; +} + +bool GLCanvas3DManager::is_shader_enabled(wxGLCanvas* canvas) const +{ + CanvasesMap::const_iterator it = _get_canvas(canvas); + return (it != m_canvases.end()) ? it->second->is_shader_enabled() : false; +} + +bool GLCanvas3DManager::is_reload_delayed(wxGLCanvas* canvas) const +{ + CanvasesMap::const_iterator it = _get_canvas(canvas); + return (it != m_canvases.end()) ? it->second->is_reload_delayed() : false; +} + +void GLCanvas3DManager::enable_layers_editing(wxGLCanvas* canvas, bool enable) +{ + CanvasesMap::iterator it = _get_canvas(canvas); + if (it != m_canvases.end()) + it->second->enable_layers_editing(enable); +} + +void GLCanvas3DManager::enable_warning_texture(wxGLCanvas* canvas, bool enable) +{ + CanvasesMap::iterator it = _get_canvas(canvas); + if (it != m_canvases.end()) + it->second->enable_warning_texture(enable); +} + +void GLCanvas3DManager::enable_legend_texture(wxGLCanvas* canvas, bool enable) +{ + CanvasesMap::iterator it = _get_canvas(canvas); + if (it != m_canvases.end()) + it->second->enable_legend_texture(enable); +} + +void GLCanvas3DManager::enable_picking(wxGLCanvas* canvas, bool enable) +{ + CanvasesMap::iterator it = _get_canvas(canvas); + if (it != m_canvases.end()) + it->second->enable_picking(enable); +} + +void GLCanvas3DManager::enable_moving(wxGLCanvas* canvas, bool enable) +{ + CanvasesMap::iterator it = _get_canvas(canvas); + if (it != m_canvases.end()) + it->second->enable_moving(enable); +} + +void GLCanvas3DManager::enable_gizmos(wxGLCanvas* canvas, bool enable) +{ + CanvasesMap::iterator it = _get_canvas(canvas); + if (it != m_canvases.end()) + it->second->enable_gizmos(enable); +} + +void GLCanvas3DManager::enable_shader(wxGLCanvas* canvas, bool enable) +{ + CanvasesMap::iterator it = _get_canvas(canvas); + if (it != m_canvases.end()) + it->second->enable_shader(enable); +} + +void GLCanvas3DManager::enable_force_zoom_to_bed(wxGLCanvas* canvas, bool enable) +{ + CanvasesMap::iterator it = _get_canvas(canvas); + if (it != m_canvases.end()) + it->second->enable_force_zoom_to_bed(enable); +} + +void GLCanvas3DManager::allow_multisample(wxGLCanvas* canvas, bool allow) +{ + CanvasesMap::iterator it = _get_canvas(canvas); + if (it != m_canvases.end()) + it->second->allow_multisample(allow); +} + +void GLCanvas3DManager::zoom_to_bed(wxGLCanvas* canvas) +{ + CanvasesMap::iterator it = _get_canvas(canvas); + if (it != m_canvases.end()) + it->second->zoom_to_bed(); +} + +void GLCanvas3DManager::zoom_to_volumes(wxGLCanvas* canvas) +{ + CanvasesMap::iterator it = _get_canvas(canvas); + if (it != m_canvases.end()) + it->second->zoom_to_volumes(); +} + +void GLCanvas3DManager::select_view(wxGLCanvas* canvas, const std::string& direction) +{ + CanvasesMap::iterator it = _get_canvas(canvas); + if (it != m_canvases.end()) + it->second->select_view(direction); +} + +void GLCanvas3DManager::set_viewport_from_scene(wxGLCanvas* canvas, wxGLCanvas* other) +{ + CanvasesMap::iterator it = _get_canvas(canvas); + if (it != m_canvases.end()) + { + CanvasesMap::iterator other_it = _get_canvas(other); + if (other_it != m_canvases.end()) + it->second->set_viewport_from_scene(*other_it->second); + } +} + +void GLCanvas3DManager::update_volumes_colors_by_extruder(wxGLCanvas* canvas) +{ + CanvasesMap::const_iterator it = _get_canvas(canvas); + if (it != m_canvases.end()) + it->second->update_volumes_colors_by_extruder(); +} + +void GLCanvas3DManager::render(wxGLCanvas* canvas) const +{ + CanvasesMap::const_iterator it = _get_canvas(canvas); + if (it != m_canvases.end()) + it->second->render(); +} + +std::vector<double> GLCanvas3DManager::get_current_print_zs(wxGLCanvas* canvas, bool active_only) const +{ + CanvasesMap::const_iterator it = _get_canvas(canvas); + return (it != m_canvases.end()) ? it->second->get_current_print_zs(active_only) : std::vector<double>(); +} + +void GLCanvas3DManager::set_toolpaths_range(wxGLCanvas* canvas, double low, double high) +{ + CanvasesMap::iterator it = _get_canvas(canvas); + if (it != m_canvases.end()) + it->second->set_toolpaths_range(low, high); +} + +std::vector<int> GLCanvas3DManager::load_object(wxGLCanvas* canvas, const ModelObject* model_object, int obj_idx, std::vector<int> instance_idxs) +{ + if (model_object == nullptr) + return std::vector<int>(); + + CanvasesMap::const_iterator it = _get_canvas(canvas); + return (it != m_canvases.end()) ? it->second->load_object(*model_object, obj_idx, instance_idxs) : std::vector<int>(); +} + +std::vector<int> GLCanvas3DManager::load_object(wxGLCanvas* canvas, const Model* model, int obj_idx) +{ + if (model == nullptr) + return std::vector<int>(); + + CanvasesMap::const_iterator it = _get_canvas(canvas); + return (it != m_canvases.end()) ? it->second->load_object(*model, obj_idx) : std::vector<int>(); +} + +void GLCanvas3DManager::reload_scene(wxGLCanvas* canvas, bool force) +{ + CanvasesMap::iterator it = _get_canvas(canvas); + if (it != m_canvases.end()) + it->second->reload_scene(force); +} + +void GLCanvas3DManager::load_print_toolpaths(wxGLCanvas* canvas) +{ + CanvasesMap::iterator it = _get_canvas(canvas); + if (it != m_canvases.end()) + it->second->load_print_toolpaths(); +} + +void GLCanvas3DManager::load_print_object_toolpaths(wxGLCanvas* canvas, const PrintObject* print_object, const std::vector<std::string>& tool_colors) +{ + if (print_object == nullptr) + return; + + CanvasesMap::iterator it = _get_canvas(canvas); + if (it != m_canvases.end()) + it->second->load_print_object_toolpaths(*print_object, tool_colors); +} + +void GLCanvas3DManager::load_wipe_tower_toolpaths(wxGLCanvas* canvas, const std::vector<std::string>& str_tool_colors) +{ + CanvasesMap::iterator it = _get_canvas(canvas); + if (it != m_canvases.end()) + it->second->load_wipe_tower_toolpaths(str_tool_colors); +} + +void GLCanvas3DManager::load_gcode_preview(wxGLCanvas* canvas, const GCodePreviewData* preview_data, const std::vector<std::string>& str_tool_colors) +{ + if (preview_data == nullptr) + return; + + CanvasesMap::iterator it = _get_canvas(canvas); + if (it != m_canvases.end()) + it->second->load_gcode_preview(*preview_data, str_tool_colors); +} + +void GLCanvas3DManager::register_on_viewport_changed_callback(wxGLCanvas* canvas, void* callback) +{ + CanvasesMap::iterator it = _get_canvas(canvas); + if (it != m_canvases.end()) + it->second->register_on_viewport_changed_callback(callback); +} + +void GLCanvas3DManager::register_on_double_click_callback(wxGLCanvas* canvas, void* callback) +{ + CanvasesMap::iterator it = _get_canvas(canvas); + if (it != m_canvases.end()) + it->second->register_on_double_click_callback(callback); +} + +void GLCanvas3DManager::register_on_right_click_callback(wxGLCanvas* canvas, void* callback) +{ + CanvasesMap::iterator it = _get_canvas(canvas); + if (it != m_canvases.end()) + it->second->register_on_right_click_callback(callback); +} + +void GLCanvas3DManager::register_on_select_object_callback(wxGLCanvas* canvas, void* callback) +{ + CanvasesMap::iterator it = _get_canvas(canvas); + if (it != m_canvases.end()) + it->second->register_on_select_object_callback(callback); +} + +void GLCanvas3DManager::register_on_model_update_callback(wxGLCanvas* canvas, void* callback) +{ + CanvasesMap::iterator it = _get_canvas(canvas); + if (it != m_canvases.end()) + it->second->register_on_model_update_callback(callback); +} + +void GLCanvas3DManager::register_on_remove_object_callback(wxGLCanvas* canvas, void* callback) +{ + CanvasesMap::iterator it = _get_canvas(canvas); + if (it != m_canvases.end()) + it->second->register_on_remove_object_callback(callback); +} + +void GLCanvas3DManager::register_on_arrange_callback(wxGLCanvas* canvas, void* callback) +{ + CanvasesMap::iterator it = _get_canvas(canvas); + if (it != m_canvases.end()) + it->second->register_on_arrange_callback(callback); +} + +void GLCanvas3DManager::register_on_rotate_object_left_callback(wxGLCanvas* canvas, void* callback) +{ + CanvasesMap::iterator it = _get_canvas(canvas); + if (it != m_canvases.end()) + it->second->register_on_rotate_object_left_callback(callback); +} + +void GLCanvas3DManager::register_on_rotate_object_right_callback(wxGLCanvas* canvas, void* callback) +{ + CanvasesMap::iterator it = _get_canvas(canvas); + if (it != m_canvases.end()) + it->second->register_on_rotate_object_right_callback(callback); +} + +void GLCanvas3DManager::register_on_scale_object_uniformly_callback(wxGLCanvas* canvas, void* callback) +{ + CanvasesMap::iterator it = _get_canvas(canvas); + if (it != m_canvases.end()) + it->second->register_on_scale_object_uniformly_callback(callback); +} + +void GLCanvas3DManager::register_on_increase_objects_callback(wxGLCanvas* canvas, void* callback) +{ + CanvasesMap::iterator it = _get_canvas(canvas); + if (it != m_canvases.end()) + it->second->register_on_increase_objects_callback(callback); +} + +void GLCanvas3DManager::register_on_decrease_objects_callback(wxGLCanvas* canvas, void* callback) +{ + CanvasesMap::iterator it = _get_canvas(canvas); + if (it != m_canvases.end()) + it->second->register_on_decrease_objects_callback(callback); +} + +void GLCanvas3DManager::register_on_instance_moved_callback(wxGLCanvas* canvas, void* callback) +{ + CanvasesMap::iterator it = _get_canvas(canvas); + if (it != m_canvases.end()) + it->second->register_on_instance_moved_callback(callback); +} + +void GLCanvas3DManager::register_on_wipe_tower_moved_callback(wxGLCanvas* canvas, void* callback) +{ + CanvasesMap::iterator it = _get_canvas(canvas); + if (it != m_canvases.end()) + it->second->register_on_wipe_tower_moved_callback(callback); +} + +void GLCanvas3DManager::register_on_enable_action_buttons_callback(wxGLCanvas* canvas, void* callback) +{ + CanvasesMap::iterator it = _get_canvas(canvas); + if (it != m_canvases.end()) + it->second->register_on_enable_action_buttons_callback(callback); +} + +void GLCanvas3DManager::register_on_gizmo_scale_uniformly_callback(wxGLCanvas* canvas, void* callback) +{ + CanvasesMap::iterator it = _get_canvas(canvas); + if (it != m_canvases.end()) + it->second->register_on_gizmo_scale_uniformly_callback(callback); +} + +GLCanvas3DManager::CanvasesMap::iterator GLCanvas3DManager::_get_canvas(wxGLCanvas* canvas) +{ + return (canvas == nullptr) ? m_canvases.end() : m_canvases.find(canvas); +} + +GLCanvas3DManager::CanvasesMap::const_iterator GLCanvas3DManager::_get_canvas(wxGLCanvas* canvas) const +{ + return (canvas == nullptr) ? m_canvases.end() : m_canvases.find(canvas); +} + +bool GLCanvas3DManager::_init(GLCanvas3D& canvas) +{ + if (!m_gl_initialized) + init_gl(); + + return canvas.init(m_use_VBOs, m_use_legacy_opengl); +} + +} // namespace GUI +} // namespace Slic3r diff --git a/xs/src/slic3r/GUI/GLCanvas3DManager.hpp b/xs/src/slic3r/GUI/GLCanvas3DManager.hpp new file mode 100644 index 000000000..9d9285601 --- /dev/null +++ b/xs/src/slic3r/GUI/GLCanvas3DManager.hpp @@ -0,0 +1,166 @@ +#ifndef slic3r_GLCanvas3DManager_hpp_ +#define slic3r_GLCanvas3DManager_hpp_ + +#include "../../libslic3r/BoundingBox.hpp" + +#include <map> +#include <vector> + +class wxGLCanvas; +class wxGLContext; + +namespace Slic3r { + +class DynamicPrintConfig; +class Print; +class Model; +class ExPolygon; +typedef std::vector<ExPolygon> ExPolygons; +class ModelObject; +class PrintObject; +class GCodePreviewData; + +namespace GUI { + +class GLCanvas3D; + +class GLCanvas3DManager +{ + struct GLInfo + { + std::string version; + std::string glsl_version; + std::string vendor; + std::string renderer; + + GLInfo(); + + void detect(); + bool is_version_greater_or_equal_to(unsigned int major, unsigned int minor) const; + + std::string to_string(bool format_as_html, bool extensions) const; + }; + + typedef std::map<wxGLCanvas*, GLCanvas3D*> CanvasesMap; + + CanvasesMap m_canvases; + wxGLCanvas* m_current; + GLInfo m_gl_info; + bool m_gl_initialized; + bool m_use_legacy_opengl; + bool m_use_VBOs; + +public: + GLCanvas3DManager(); + + bool add(wxGLCanvas* canvas); + bool remove(wxGLCanvas* canvas); + + void remove_all(); + + unsigned int count() const; + + void init_gl(); + std::string get_gl_info(bool format_as_html, bool extensions) const; + + bool use_VBOs() const; + bool layer_editing_allowed() const; + + bool init(wxGLCanvas* canvas); + + void set_as_dirty(wxGLCanvas* canvas); + + unsigned int get_volumes_count(wxGLCanvas* canvas) const; + void reset_volumes(wxGLCanvas* canvas); + void deselect_volumes(wxGLCanvas* canvas); + void select_volume(wxGLCanvas* canvas, unsigned int id); + void update_volumes_selection(wxGLCanvas* canvas, const std::vector<int>& selections); + bool check_volumes_outside_state(wxGLCanvas* canvas, const DynamicPrintConfig* config) const; + bool move_volume_up(wxGLCanvas* canvas, unsigned int id); + bool move_volume_down(wxGLCanvas* canvas, unsigned int id); + + void set_objects_selections(wxGLCanvas* canvas, const std::vector<int>& selections); + + void set_config(wxGLCanvas* canvas, DynamicPrintConfig* config); + void set_print(wxGLCanvas* canvas, Print* print); + void set_model(wxGLCanvas* canvas, Model* model); + + void set_bed_shape(wxGLCanvas* canvas, const Pointfs& shape); + void set_auto_bed_shape(wxGLCanvas* canvas); + + BoundingBoxf3 get_volumes_bounding_box(wxGLCanvas* canvas); + + void set_axes_length(wxGLCanvas* canvas, float length); + + void set_cutting_plane(wxGLCanvas* canvas, float z, const ExPolygons& polygons); + + void set_color_by(wxGLCanvas* canvas, const std::string& value); + void set_select_by(wxGLCanvas* canvas, const std::string& value); + void set_drag_by(wxGLCanvas* canvas, const std::string& value); + + bool is_layers_editing_enabled(wxGLCanvas* canvas) const; + bool is_layers_editing_allowed(wxGLCanvas* canvas) const; + bool is_shader_enabled(wxGLCanvas* canvas) const; + + bool is_reload_delayed(wxGLCanvas* canvas) const; + + void enable_layers_editing(wxGLCanvas* canvas, bool enable); + void enable_warning_texture(wxGLCanvas* canvas, bool enable); + void enable_legend_texture(wxGLCanvas* canvas, bool enable); + void enable_picking(wxGLCanvas* canvas, bool enable); + void enable_moving(wxGLCanvas* canvas, bool enable); + 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 allow_multisample(wxGLCanvas* canvas, bool allow); + + void zoom_to_bed(wxGLCanvas* canvas); + void zoom_to_volumes(wxGLCanvas* canvas); + void select_view(wxGLCanvas* canvas, const std::string& direction); + void set_viewport_from_scene(wxGLCanvas* canvas, wxGLCanvas* other); + + void update_volumes_colors_by_extruder(wxGLCanvas* canvas); + + void render(wxGLCanvas* canvas) const; + + std::vector<double> get_current_print_zs(wxGLCanvas* canvas, bool active_only) const; + void set_toolpaths_range(wxGLCanvas* canvas, double low, double high); + + std::vector<int> load_object(wxGLCanvas* canvas, const ModelObject* model_object, int obj_idx, std::vector<int> instance_idxs); + std::vector<int> load_object(wxGLCanvas* canvas, const Model* model, int obj_idx); + + 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 register_on_viewport_changed_callback(wxGLCanvas* canvas, void* callback); + void register_on_double_click_callback(wxGLCanvas* canvas, void* callback); + void register_on_right_click_callback(wxGLCanvas* canvas, void* callback); + void register_on_select_object_callback(wxGLCanvas* canvas, void* callback); + void register_on_model_update_callback(wxGLCanvas* canvas, void* callback); + void register_on_remove_object_callback(wxGLCanvas* canvas, void* callback); + void register_on_arrange_callback(wxGLCanvas* canvas, void* callback); + void register_on_rotate_object_left_callback(wxGLCanvas* canvas, void* callback); + void register_on_rotate_object_right_callback(wxGLCanvas* canvas, void* callback); + void register_on_scale_object_uniformly_callback(wxGLCanvas* canvas, void* callback); + void register_on_increase_objects_callback(wxGLCanvas* canvas, void* callback); + void register_on_decrease_objects_callback(wxGLCanvas* canvas, void* callback); + void register_on_instance_moved_callback(wxGLCanvas* canvas, void* callback); + void register_on_wipe_tower_moved_callback(wxGLCanvas* canvas, void* callback); + void register_on_enable_action_buttons_callback(wxGLCanvas* canvas, void* callback); + void register_on_gizmo_scale_uniformly_callback(wxGLCanvas* canvas, void* callback); + +private: + CanvasesMap::iterator _get_canvas(wxGLCanvas* canvas); + CanvasesMap::const_iterator _get_canvas(wxGLCanvas* canvas) const; + + bool _init(GLCanvas3D& canvas); +}; + +} // namespace GUI +} // namespace Slic3r + +#endif // slic3r_GLCanvas3DManager_hpp_ diff --git a/xs/src/slic3r/GUI/GLGizmo.cpp b/xs/src/slic3r/GUI/GLGizmo.cpp new file mode 100644 index 000000000..d3aae33e8 --- /dev/null +++ b/xs/src/slic3r/GUI/GLGizmo.cpp @@ -0,0 +1,454 @@ +#include "GLGizmo.hpp" + +#include "../../libslic3r/Utils.hpp" +#include "../../libslic3r/BoundingBox.hpp" + +#include <GL/glew.h> + +#include <iostream> + +namespace Slic3r { +namespace GUI { + +const float GLGizmoBase::Grabber::HalfSize = 2.0f; +const float GLGizmoBase::Grabber::HoverOffset = 0.5f; +const float GLGizmoBase::BaseColor[3] = { 1.0f, 1.0f, 1.0f }; +const float GLGizmoBase::HighlightColor[3] = { 1.0f, 0.38f, 0.0f }; + +GLGizmoBase::Grabber::Grabber() + : center(Pointf(0.0, 0.0)) + , angle_z(0.0f) +{ + color[0] = 1.0f; + color[1] = 1.0f; + color[2] = 1.0f; +} + +void GLGizmoBase::Grabber::render(bool hover) const +{ + float min_x = -HalfSize; + float max_x = +HalfSize; + float min_y = -HalfSize; + float max_y = +HalfSize; + + ::glColor3f((GLfloat)color[0], (GLfloat)color[1], (GLfloat)color[2]); + + float angle_z_in_deg = angle_z * 180.0f / (float)PI; + ::glPushMatrix(); + ::glTranslatef((GLfloat)center.x, (GLfloat)center.y, 0.0f); + ::glRotatef((GLfloat)angle_z_in_deg, 0.0f, 0.0f, 1.0f); + + ::glDisable(GL_CULL_FACE); + ::glBegin(GL_TRIANGLES); + ::glVertex3f((GLfloat)min_x, (GLfloat)min_y, 0.0f); + ::glVertex3f((GLfloat)max_x, (GLfloat)min_y, 0.0f); + ::glVertex3f((GLfloat)max_x, (GLfloat)max_y, 0.0f); + ::glVertex3f((GLfloat)max_x, (GLfloat)max_y, 0.0f); + ::glVertex3f((GLfloat)min_x, (GLfloat)max_y, 0.0f); + ::glVertex3f((GLfloat)min_x, (GLfloat)min_y, 0.0f); + ::glEnd(); + ::glEnable(GL_CULL_FACE); + + if (hover) + { + min_x -= HoverOffset; + max_x += HoverOffset; + min_y -= HoverOffset; + max_y += HoverOffset; + + ::glBegin(GL_LINE_LOOP); + ::glVertex3f((GLfloat)min_x, (GLfloat)min_y, 0.0f); + ::glVertex3f((GLfloat)max_x, (GLfloat)min_y, 0.0f); + ::glVertex3f((GLfloat)max_x, (GLfloat)max_y, 0.0f); + ::glVertex3f((GLfloat)min_x, (GLfloat)max_y, 0.0f); + ::glEnd(); + } + + ::glPopMatrix(); +} + +GLGizmoBase::GLGizmoBase() + : m_state(Off) + , m_hover_id(-1) +{ +} + +GLGizmoBase::~GLGizmoBase() +{ +} + +bool GLGizmoBase::init() +{ + return on_init(); +} + +GLGizmoBase::EState GLGizmoBase::get_state() const +{ + return m_state; +} + +void GLGizmoBase::set_state(GLGizmoBase::EState state) +{ + m_state = state; +} + +unsigned int GLGizmoBase::get_textures_id() const +{ + return m_textures[m_state].get_id(); +} + +int GLGizmoBase::get_textures_size() const +{ + return m_textures[Off].get_width(); +} + +int GLGizmoBase::get_hover_id() const +{ + return m_hover_id; +} + +void GLGizmoBase::set_hover_id(int id) +{ + if (id < (int)m_grabbers.size()) + m_hover_id = id; +} + +void GLGizmoBase::start_dragging() +{ + on_start_dragging(); +} + +void GLGizmoBase::update(const Pointf& mouse_pos) +{ + if (m_hover_id != -1) + on_update(mouse_pos); +} + +void GLGizmoBase::render(const BoundingBoxf3& box) const +{ + on_render(box); +} + +void GLGizmoBase::render_for_picking(const BoundingBoxf3& box) const +{ + on_render_for_picking(box); +} + +void GLGizmoBase::on_start_dragging() +{ +} + +void GLGizmoBase::render_grabbers() const +{ + for (unsigned int i = 0; i < (unsigned int)m_grabbers.size(); ++i) + { + m_grabbers[i].render(m_hover_id == i); + } +} + +const float GLGizmoRotate::Offset = 5.0f; +const unsigned int GLGizmoRotate::CircleResolution = 64; +const unsigned int GLGizmoRotate::AngleResolution = 64; +const unsigned int GLGizmoRotate::ScaleStepsCount = 60; +const float GLGizmoRotate::ScaleStepRad = 2.0f * (float)PI / GLGizmoRotate::ScaleStepsCount; +const unsigned int GLGizmoRotate::ScaleLongEvery = 5; +const float GLGizmoRotate::ScaleLongTooth = 2.0f; +const float GLGizmoRotate::ScaleShortTooth = 1.0f; +const unsigned int GLGizmoRotate::SnapRegionsCount = 8; +const float GLGizmoRotate::GrabberOffset = 5.0f; + +GLGizmoRotate::GLGizmoRotate() + : GLGizmoBase() + , m_angle_z(0.0f) + , m_center(Pointf(0.0, 0.0)) + , m_radius(0.0f) +{ +} + +bool GLGizmoRotate::on_init() +{ + std::string path = resources_dir() + "/icons/overlay/"; + + std::string filename = path + "rotate_off.png"; + if (!m_textures[Off].load_from_file(filename, false)) + return false; + + filename = path + "rotate_hover.png"; + if (!m_textures[Hover].load_from_file(filename, false)) + return false; + + filename = path + "rotate_on.png"; + if (!m_textures[On].load_from_file(filename, false)) + return false; + + m_grabbers.push_back(Grabber()); + + return true; +} + +void GLGizmoRotate::on_update(const Pointf& mouse_pos) +{ + Vectorf orig_dir(1.0, 0.0); + Vectorf new_dir = normalize(mouse_pos - m_center); + coordf_t theta = ::acos(clamp(-1.0, 1.0, dot(new_dir, orig_dir))); + if (cross(orig_dir, new_dir) < 0.0) + theta = 2.0 * (coordf_t)PI - theta; + + if (length(m_center.vector_to(mouse_pos)) < 2.0 * (double)m_radius / 3.0) + { + coordf_t step = 2.0 * (coordf_t)PI / (coordf_t)SnapRegionsCount; + theta = step * (coordf_t)std::round(theta / step); + } + + if (theta == 2.0 * (coordf_t)PI) + theta = 0.0; + + m_angle_z = (float)theta; +} + +void GLGizmoRotate::on_render(const BoundingBoxf3& box) const +{ + ::glDisable(GL_LIGHTING); + ::glDisable(GL_DEPTH_TEST); + + const Pointf3& size = box.size(); + m_center = box.center(); + m_radius = Offset + ::sqrt(sqr(0.5f * size.x) + sqr(0.5f * size.y)); + + ::glLineWidth(2.0f); + ::glColor3fv(BaseColor); + + _render_circle(); + _render_scale(); + _render_snap_radii(); + _render_reference_radius(); + + ::glColor3fv(HighlightColor); + _render_angle_z(); + _render_grabber(); +} + +void GLGizmoRotate::on_render_for_picking(const BoundingBoxf3& box) const +{ + ::glDisable(GL_LIGHTING); + ::glDisable(GL_DEPTH_TEST); + + m_grabbers[0].color[0] = 1.0f; + m_grabbers[0].color[1] = 1.0f; + m_grabbers[0].color[2] = 254.0f / 255.0f; + render_grabbers(); +} + +void GLGizmoRotate::_render_circle() const +{ + ::glBegin(GL_LINE_LOOP); + for (unsigned int i = 0; i < ScaleStepsCount; ++i) + { + float angle = (float)i * ScaleStepRad; + float x = m_center.x + ::cos(angle) * m_radius; + float y = m_center.y + ::sin(angle) * m_radius; + ::glVertex3f((GLfloat)x, (GLfloat)y, 0.0f); + } + ::glEnd(); +} + +void GLGizmoRotate::_render_scale() const +{ + float out_radius_long = m_radius + ScaleLongTooth; + float out_radius_short = m_radius + ScaleShortTooth; + + ::glBegin(GL_LINES); + for (unsigned int i = 0; i < ScaleStepsCount; ++i) + { + float angle = (float)i * ScaleStepRad; + float cosa = ::cos(angle); + float sina = ::sin(angle); + float in_x = m_center.x + cosa * m_radius; + float in_y = m_center.y + sina * m_radius; + float out_x = (i % ScaleLongEvery == 0) ? m_center.x + cosa * out_radius_long : m_center.x + cosa * out_radius_short; + float out_y = (i % ScaleLongEvery == 0) ? m_center.y + sina * out_radius_long : m_center.y + sina * out_radius_short; + ::glVertex3f((GLfloat)in_x, (GLfloat)in_y, 0.0f); + ::glVertex3f((GLfloat)out_x, (GLfloat)out_y, 0.0f); + } + ::glEnd(); +} + +void GLGizmoRotate::_render_snap_radii() const +{ + float step = 2.0f * (float)PI / (float)SnapRegionsCount; + + float in_radius = m_radius / 3.0f; + float out_radius = 2.0f * in_radius; + + ::glBegin(GL_LINES); + for (unsigned int i = 0; i < SnapRegionsCount; ++i) + { + float angle = (float)i * step; + float cosa = ::cos(angle); + float sina = ::sin(angle); + float in_x = m_center.x + cosa * in_radius; + float in_y = m_center.y + sina * in_radius; + float out_x = m_center.x + cosa * out_radius; + float out_y = m_center.y + sina * out_radius; + ::glVertex3f((GLfloat)in_x, (GLfloat)in_y, 0.0f); + ::glVertex3f((GLfloat)out_x, (GLfloat)out_y, 0.0f); + } + ::glEnd(); +} + +void GLGizmoRotate::_render_reference_radius() const +{ + ::glBegin(GL_LINES); + ::glVertex3f((GLfloat)m_center.x, (GLfloat)m_center.y, 0.0f); + ::glVertex3f((GLfloat)m_center.x + m_radius + GrabberOffset, (GLfloat)m_center.y, 0.0f); + ::glEnd(); +} + +void GLGizmoRotate::_render_angle_z() const +{ + float step_angle = m_angle_z / AngleResolution; + float ex_radius = m_radius + GrabberOffset; + + ::glBegin(GL_LINE_STRIP); + for (unsigned int i = 0; i <= AngleResolution; ++i) + { + float angle = (float)i * step_angle; + float x = m_center.x + ::cos(angle) * ex_radius; + float y = m_center.y + ::sin(angle) * ex_radius; + ::glVertex3f((GLfloat)x, (GLfloat)y, 0.0f); + } + ::glEnd(); +} + +void GLGizmoRotate::_render_grabber() const +{ + float grabber_radius = m_radius + GrabberOffset; + m_grabbers[0].center.x = m_center.x + ::cos(m_angle_z) * grabber_radius; + m_grabbers[0].center.y = m_center.y + ::sin(m_angle_z) * grabber_radius; + m_grabbers[0].angle_z = m_angle_z; + + ::glColor3fv(BaseColor); + ::glBegin(GL_LINES); + ::glVertex3f((GLfloat)m_center.x, (GLfloat)m_center.y, 0.0f); + ::glVertex3f((GLfloat)m_grabbers[0].center.x, (GLfloat)m_grabbers[0].center.y, 0.0f); + ::glEnd(); + + ::memcpy((void*)m_grabbers[0].color, (const void*)HighlightColor, 3 * sizeof(float)); + render_grabbers(); +} + +const float GLGizmoScale::Offset = 5.0f; + +GLGizmoScale::GLGizmoScale() + : GLGizmoBase() + , m_scale(1.0f) + , m_starting_scale(1.0f) +{ +} + +float GLGizmoScale::get_scale() const +{ + return m_scale; +} + +void GLGizmoScale::set_scale(float scale) +{ + m_starting_scale = scale; +} + +bool GLGizmoScale::on_init() +{ + std::string path = resources_dir() + "/icons/overlay/"; + + std::string filename = path + "scale_off.png"; + if (!m_textures[Off].load_from_file(filename, false)) + return false; + + filename = path + "scale_hover.png"; + if (!m_textures[Hover].load_from_file(filename, false)) + return false; + + filename = path + "scale_on.png"; + if (!m_textures[On].load_from_file(filename, false)) + return false; + + for (unsigned int i = 0; i < 4; ++i) + { + m_grabbers.push_back(Grabber()); + } + + return true; +} + +void GLGizmoScale::on_start_dragging() +{ + if (m_hover_id != -1) + m_starting_drag_position = m_grabbers[m_hover_id].center; +} + +void GLGizmoScale::on_update(const Pointf& mouse_pos) +{ + Pointf center(0.5 * (m_grabbers[1].center.x + m_grabbers[0].center.x), 0.5 * (m_grabbers[3].center.y + m_grabbers[0].center.y)); + + coordf_t orig_len = length(m_starting_drag_position - center); + coordf_t new_len = length(mouse_pos - center); + coordf_t ratio = (orig_len != 0.0) ? new_len / orig_len : 1.0; + + m_scale = m_starting_scale * (float)ratio; +} + +void GLGizmoScale::on_render(const BoundingBoxf3& box) const +{ + ::glDisable(GL_LIGHTING); + ::glDisable(GL_DEPTH_TEST); + + coordf_t min_x = box.min.x - (coordf_t)Offset; + coordf_t max_x = box.max.x + (coordf_t)Offset; + coordf_t min_y = box.min.y - (coordf_t)Offset; + coordf_t max_y = box.max.y + (coordf_t)Offset; + + m_grabbers[0].center.x = min_x; + m_grabbers[0].center.y = min_y; + m_grabbers[1].center.x = max_x; + m_grabbers[1].center.y = min_y; + m_grabbers[2].center.x = max_x; + m_grabbers[2].center.y = max_y; + m_grabbers[3].center.x = min_x; + m_grabbers[3].center.y = max_y; + + ::glLineWidth(2.0f); + ::glColor3fv(BaseColor); + // draw outline + ::glBegin(GL_LINE_LOOP); + for (unsigned int i = 0; i < 4; ++i) + { + ::glVertex3f((GLfloat)m_grabbers[i].center.x, (GLfloat)m_grabbers[i].center.y, 0.0f); + } + ::glEnd(); + + // draw grabbers + for (unsigned int i = 0; i < 4; ++i) + { + ::memcpy((void*)m_grabbers[i].color, (const void*)HighlightColor, 3 * sizeof(float)); + } + render_grabbers(); +} + +void GLGizmoScale::on_render_for_picking(const BoundingBoxf3& box) const +{ + static const GLfloat INV_255 = 1.0f / 255.0f; + + ::glDisable(GL_LIGHTING); + ::glDisable(GL_DEPTH_TEST); + + for (unsigned int i = 0; i < 4; ++i) + { + m_grabbers[i].color[0] = 1.0f; + m_grabbers[i].color[1] = 1.0f; + m_grabbers[i].color[2] = (254.0f - (float)i) * INV_255; + } + render_grabbers(); +} + +} // namespace GUI +} // namespace Slic3r diff --git a/xs/src/slic3r/GUI/GLGizmo.hpp b/xs/src/slic3r/GUI/GLGizmo.hpp new file mode 100644 index 000000000..2baec8f9b --- /dev/null +++ b/xs/src/slic3r/GUI/GLGizmo.hpp @@ -0,0 +1,145 @@ +#ifndef slic3r_GLGizmo_hpp_ +#define slic3r_GLGizmo_hpp_ + +#include "../../slic3r/GUI/GLTexture.hpp" +#include "../../libslic3r/Point.hpp" + +#include <vector> + +namespace Slic3r { + +class BoundingBoxf3; +class Pointf3; + +namespace GUI { + +class GLGizmoBase +{ +protected: + static const float BaseColor[3]; + static const float HighlightColor[3]; + + struct Grabber + { + static const float HalfSize; + static const float HoverOffset; + + Pointf center; + float angle_z; + float color[3]; + + Grabber(); + void render(bool hover) const; + }; + +public: + enum EState + { + Off, + Hover, + On, + Num_States + }; + +protected: + EState m_state; + // textures are assumed to be square and all with the same size in pixels, no internal check is done + GLTexture m_textures[Num_States]; + int m_hover_id; + mutable std::vector<Grabber> m_grabbers; + +public: + GLGizmoBase(); + virtual ~GLGizmoBase(); + + bool init(); + + EState get_state() const; + void set_state(EState state); + + unsigned int get_textures_id() const; + int get_textures_size() const; + + int get_hover_id() const; + void set_hover_id(int id); + + void start_dragging(); + void update(const Pointf& mouse_pos); + + void render(const BoundingBoxf3& box) const; + void render_for_picking(const BoundingBoxf3& box) const; + +protected: + virtual bool on_init() = 0; + virtual void on_start_dragging(); + virtual void on_update(const Pointf& mouse_pos) = 0; + virtual void on_render(const BoundingBoxf3& box) const = 0; + virtual void on_render_for_picking(const BoundingBoxf3& box) const = 0; + + void render_grabbers() const; +}; + +class GLGizmoRotate : public GLGizmoBase +{ + static const float Offset; + static const unsigned int CircleResolution; + static const unsigned int AngleResolution; + static const unsigned int ScaleStepsCount; + static const float ScaleStepRad; + static const unsigned int ScaleLongEvery; + static const float ScaleLongTooth; + static const float ScaleShortTooth; + static const unsigned int SnapRegionsCount; + static const float GrabberOffset; + + float m_angle_z; + + mutable Pointf m_center; + mutable float m_radius; + +public: + GLGizmoRotate(); + +protected: + virtual bool on_init(); + virtual void on_update(const Pointf& mouse_pos); + virtual void on_render(const BoundingBoxf3& box) const; + virtual void on_render_for_picking(const BoundingBoxf3& box) const; + +private: + void _render_circle() const; + void _render_scale() const; + void _render_snap_radii() const; + void _render_reference_radius() const; + void _render_angle_z() const; + void _render_grabber() const; +}; + +class GLGizmoScale : public GLGizmoBase +{ + static const float Offset; + + float m_scale; + + Pointf m_starting_drag_position; + float m_starting_scale; + +public: + GLGizmoScale(); + + float get_scale() const; + void set_scale(float scale); + +protected: + virtual bool on_init(); + virtual void on_start_dragging(); + virtual void on_update(const Pointf& mouse_pos); + virtual void on_render(const BoundingBoxf3& box) const; + virtual void on_render_for_picking(const BoundingBoxf3& box) const; +}; + +} // namespace GUI +} // namespace Slic3r + +#endif // slic3r_GLGizmo_hpp_ + diff --git a/xs/src/slic3r/GUI/GLShader.cpp b/xs/src/slic3r/GUI/GLShader.cpp index ce9a80f05..903f6c347 100644 --- a/xs/src/slic3r/GUI/GLShader.cpp +++ b/xs/src/slic3r/GUI/GLShader.cpp @@ -2,6 +2,9 @@ #include "GLShader.hpp" +#include "../../libslic3r/Utils.hpp" +#include <boost/nowide/fstream.hpp> + #include <string> #include <utility> #include <assert.h> @@ -22,7 +25,7 @@ inline std::string gl_get_string_safe(GLenum param) return std::string(value ? value : "N/A"); } -bool GLShader::load(const char *fragment_shader, const char *vertex_shader) +bool GLShader::load_from_text(const char *fragment_shader, const char *vertex_shader) { std::string gl_version = gl_get_string_safe(GL_VERSION); int major = atoi(gl_version.c_str()); @@ -123,6 +126,41 @@ bool GLShader::load(const char *fragment_shader, const char *vertex_shader) return true; } +bool GLShader::load_from_file(const char* fragment_shader_filename, const char* vertex_shader_filename) +{ + const std::string& path = resources_dir() + "/shaders/"; + + boost::nowide::ifstream vs(path + std::string(vertex_shader_filename), boost::nowide::ifstream::binary); + if (!vs.good()) + return false; + + vs.seekg(0, vs.end); + int file_length = vs.tellg(); + vs.seekg(0, vs.beg); + std::string vertex_shader(file_length, '\0'); + vs.read(const_cast<char*>(vertex_shader.data()), file_length); + if (!vs.good()) + return false; + + vs.close(); + + boost::nowide::ifstream fs(path + std::string(fragment_shader_filename), boost::nowide::ifstream::binary); + if (!fs.good()) + return false; + + fs.seekg(0, fs.end); + file_length = fs.tellg(); + fs.seekg(0, fs.beg); + std::string fragment_shader(file_length, '\0'); + fs.read(const_cast<char*>(fragment_shader.data()), file_length); + if (!fs.good()) + return false; + + fs.close(); + + return load_from_text(fragment_shader.c_str(), vertex_shader.c_str()); +} + void GLShader::release() { if (this->shader_program_id) { diff --git a/xs/src/slic3r/GUI/GLShader.hpp b/xs/src/slic3r/GUI/GLShader.hpp index d91463f19..032640d8d 100644 --- a/xs/src/slic3r/GUI/GLShader.hpp +++ b/xs/src/slic3r/GUI/GLShader.hpp @@ -16,7 +16,9 @@ public: {} ~GLShader(); - bool load(const char *fragment_shader, const char *vertex_shader); + bool load_from_text(const char *fragment_shader, const char *vertex_shader); + bool load_from_file(const char* fragment_shader_filename, const char* vertex_shader_filename); + void release(); int get_attrib_location(const char *name) const; diff --git a/xs/src/slic3r/GUI/GLTexture.cpp b/xs/src/slic3r/GUI/GLTexture.cpp new file mode 100644 index 000000000..924920bd8 --- /dev/null +++ b/xs/src/slic3r/GUI/GLTexture.cpp @@ -0,0 +1,190 @@ +#include "GLTexture.hpp" + +#include <GL/glew.h> + +#include <wx/image.h> + +#include <boost/filesystem.hpp> + +#include <vector> +#include <algorithm> + +namespace Slic3r { +namespace GUI { + +GLTexture::GLTexture() + : m_id(0) + , m_width(0) + , m_height(0) + , m_source("") +{ +} + +GLTexture::~GLTexture() +{ + reset(); +} + +bool GLTexture::load_from_file(const std::string& filename, bool generate_mipmaps) +{ + reset(); + + if (!boost::filesystem::exists(filename)) + return false; + + // Load a PNG with an alpha channel. + wxImage image; + if (!image.LoadFile(filename, wxBITMAP_TYPE_PNG)) + { + reset(); + return false; + } + + m_width = image.GetWidth(); + m_height = image.GetHeight(); + int n_pixels = m_width * m_height; + + if (n_pixels <= 0) + { + reset(); + return false; + } + + // Get RGB & alpha raw data from wxImage, pack them into an array. + unsigned char* img_rgb = image.GetData(); + if (img_rgb == nullptr) + { + reset(); + return false; + } + + unsigned char* img_alpha = image.GetAlpha(); + + std::vector<unsigned char> data(n_pixels * 4, 0); + for (int i = 0; i < n_pixels; ++i) + { + int data_id = i * 4; + int img_id = i * 3; + data[data_id + 0] = img_rgb[img_id + 0]; + data[data_id + 1] = img_rgb[img_id + 1]; + data[data_id + 2] = img_rgb[img_id + 2]; + data[data_id + 3] = (img_alpha != nullptr) ? img_alpha[i] : 255; + } + + // sends data to gpu + ::glGenTextures(1, &m_id); + ::glBindTexture(GL_TEXTURE_2D, m_id); + ::glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, (GLsizei)m_width, (GLsizei)m_height, 0, GL_RGBA, GL_UNSIGNED_BYTE, (const void*)data.data()); + if (generate_mipmaps) + { + // we manually generate mipmaps because glGenerateMipmap() function is not reliable on all graphics cards + _generate_mipmaps(image); + ::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR); + } + else + { + ::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + ::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, 1); + } + ::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + + ::glBindTexture(GL_TEXTURE_2D, 0); + + m_source = filename; + return true; +} + +void GLTexture::reset() +{ + if (m_id != 0) + ::glDeleteTextures(1, &m_id); + + m_id = 0; + m_width = 0; + m_height = 0; + m_source = ""; +} + +unsigned int GLTexture::get_id() const +{ + return m_id; +} + +int GLTexture::get_width() const +{ + return m_width; +} + +int GLTexture::get_height() const +{ + return m_height; +} + +const std::string& GLTexture::get_source() const +{ + return m_source; +} + +void GLTexture::render_texture(unsigned int tex_id, float left, float right, float bottom, float top) +{ + ::glColor4f(1.0f, 1.0f, 1.0f, 1.0f); + + ::glDisable(GL_LIGHTING); + ::glEnable(GL_BLEND); + ::glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + ::glEnable(GL_TEXTURE_2D); + + ::glBindTexture(GL_TEXTURE_2D, (GLuint)tex_id); + + ::glBegin(GL_QUADS); + ::glTexCoord2d(0.0f, 1.0f); ::glVertex3f(left, bottom, 0.0f); + ::glTexCoord2d(1.0f, 1.0f); ::glVertex3f(right, bottom, 0.0f); + ::glTexCoord2d(1.0f, 0.0f); ::glVertex3f(right, top, 0.0f); + ::glTexCoord2d(0.0f, 0.0f); ::glVertex3f(left, top, 0.0f); + ::glEnd(); + + ::glBindTexture(GL_TEXTURE_2D, 0); + + ::glDisable(GL_TEXTURE_2D); + ::glDisable(GL_BLEND); + ::glEnable(GL_LIGHTING); +} + +void GLTexture::_generate_mipmaps(wxImage& image) +{ + int w = image.GetWidth(); + int h = image.GetHeight(); + GLint level = 0; + std::vector<unsigned char> data(w * h * 4, 0); + + while ((w > 1) && (h > 1)) + { + ++level; + + w = std::max(w / 2, 1); + h = std::max(h / 2, 1); + + int n_pixels = w * h; + + image = image.ResampleBicubic(w, h); + + unsigned char* img_rgb = image.GetData(); + unsigned char* img_alpha = image.GetAlpha(); + + data.resize(n_pixels * 4); + for (int i = 0; i < n_pixels; ++i) + { + int data_id = i * 4; + int img_id = i * 3; + data[data_id + 0] = img_rgb[img_id + 0]; + data[data_id + 1] = img_rgb[img_id + 1]; + data[data_id + 2] = img_rgb[img_id + 2]; + data[data_id + 3] = (img_alpha != nullptr) ? img_alpha[i] : 255; + } + + ::glTexImage2D(GL_TEXTURE_2D, level, GL_RGBA8, (GLsizei)w, (GLsizei)h, 0, GL_RGBA, GL_UNSIGNED_BYTE, (const void*)data.data()); + } +} + +} // namespace GUI +} // namespace Slic3r diff --git a/xs/src/slic3r/GUI/GLTexture.hpp b/xs/src/slic3r/GUI/GLTexture.hpp new file mode 100644 index 000000000..70480c605 --- /dev/null +++ b/xs/src/slic3r/GUI/GLTexture.hpp @@ -0,0 +1,41 @@ +#ifndef slic3r_GLTexture_hpp_ +#define slic3r_GLTexture_hpp_ + +#include <string> + +class wxImage; + +namespace Slic3r { +namespace GUI { + + class GLTexture + { + private: + unsigned int m_id; + int m_width; + int m_height; + std::string m_source; + + public: + GLTexture(); + ~GLTexture(); + + bool load_from_file(const std::string& filename, bool generate_mipmaps); + void reset(); + + unsigned int get_id() const; + int get_width() const; + int get_height() const; + const std::string& get_source() const; + + static void render_texture(unsigned int tex_id, float left, float right, float bottom, float top); + + private: + void _generate_mipmaps(wxImage& image); + }; + +} // namespace GUI +} // namespace Slic3r + +#endif // slic3r_GLTexture_hpp_ + diff --git a/xs/src/slic3r/GUI/GUI.cpp b/xs/src/slic3r/GUI/GUI.cpp index 974c554b6..12af36d19 100644 --- a/xs/src/slic3r/GUI/GUI.cpp +++ b/xs/src/slic3r/GUI/GUI.cpp @@ -56,6 +56,7 @@ #include "../Utils/PresetUpdater.hpp" #include "../Config/Snapshot.hpp" +#include "3DScene.hpp" namespace Slic3r { namespace GUI { @@ -316,10 +317,11 @@ void add_config_menu(wxMenuBar *menu, int event_preferences_changed, int event_l auto local_menu = new wxMenu(); wxWindowID config_id_base = wxWindow::NewControlId((int)ConfigMenuCnt); - const auto config_wizard_tooltip = wxString::Format(_(L("Run %s")), ConfigWizard::name()); + auto config_wizard_name = _(ConfigWizard::name().wx_str()); + const auto config_wizard_tooltip = wxString::Format(_(L("Run %s")), config_wizard_name); // Cmd+, is standard on OS X - what about other operating systems? - local_menu->Append(config_id_base + ConfigMenuWizard, ConfigWizard::name() + dots, config_wizard_tooltip); - local_menu->Append(config_id_base + ConfigMenuSnapshots, _(L("Configuration Snapshots"))+dots, _(L("Inspect / activate configuration snapshots"))); + local_menu->Append(config_id_base + ConfigMenuWizard, config_wizard_name + dots, config_wizard_tooltip); + local_menu->Append(config_id_base + ConfigMenuSnapshots, _(L("Configuration Snapshots"))+dots, _(L("Inspect / activate configuration snapshots"))); local_menu->Append(config_id_base + ConfigMenuTakeSnapshot, _(L("Take Configuration Snapshot")), _(L("Capture a configuration snapshot"))); // local_menu->Append(config_id_base + ConfigMenuUpdate, _(L("Check for updates")), _(L("Check for configuration updates"))); local_menu->AppendSeparator(); @@ -378,6 +380,7 @@ void add_config_menu(wxMenuBar *menu, int event_preferences_changed, int event_l save_language(); show_info(g_wxTabPanel, _(L("Application will be restarted")), _(L("Attention!"))); if (event_language_change > 0) { + _3DScene::remove_all_canvases();// remove all canvas before recreate GUI wxCommandEvent event(event_language_change); g_wxApp->ProcessEvent(event); } @@ -423,7 +426,7 @@ bool check_unsaved_changes() bool config_wizard_startup(bool app_config_exists) { - if (! app_config_exists || g_PresetBundle->has_defauls_only()) { + if (! app_config_exists || g_PresetBundle->printers.size() <= 1) { config_wizard(ConfigWizard::RR_DATA_EMPTY); return true; } else if (g_AppConfig->legacy_datadir()) { diff --git a/xs/src/slic3r/GUI/MsgDialog.hpp b/xs/src/slic3r/GUI/MsgDialog.hpp index 2d570a0bf..ca349eb5c 100644 --- a/xs/src/slic3r/GUI/MsgDialog.hpp +++ b/xs/src/slic3r/GUI/MsgDialog.hpp @@ -28,6 +28,8 @@ struct MsgDialog : wxDialog MsgDialog &operator=(const MsgDialog &) = delete; virtual ~MsgDialog(); + // TODO: refactor with CreateStdDialogButtonSizer usage + protected: enum { CONTENT_WIDTH = 500, diff --git a/xs/src/slic3r/GUI/OptionsGroup.cpp b/xs/src/slic3r/GUI/OptionsGroup.cpp index 57659d03d..629a9f3a0 100644 --- a/xs/src/slic3r/GUI/OptionsGroup.cpp +++ b/xs/src/slic3r/GUI/OptionsGroup.cpp @@ -150,8 +150,15 @@ void OptionsGroup::append_line(const Line& line, wxStaticText** colored_Label/* // Build a label if we have it wxStaticText* label=nullptr; if (label_width != 0) { + long label_style = staticbox ? 0 : wxALIGN_RIGHT; +#ifdef __WXGTK__ + // workaround for correct text align of the StaticBox on Linux + // flags wxALIGN_RIGHT and wxALIGN_CENTRE don't work when Ellipsize flags are _not_ given. + // Text is properly aligned only when Ellipsize is checked. + label_style |= staticbox ? 0 : wxST_ELLIPSIZE_END; +#endif /* __WXGTK__ */ label = new wxStaticText(parent(), wxID_ANY, line.label + (line.label.IsEmpty() ? "" : ":"), - wxDefaultPosition, wxSize(label_width, -1), staticbox ? 0 : wxALIGN_RIGHT); + wxDefaultPosition, wxSize(label_width, -1), label_style); label->SetFont(label_font); label->Wrap(label_width); // avoid a Linux/GTK bug grid_sizer->Add(label, 0, (staticbox ? 0 : wxALIGN_RIGHT | wxRIGHT) | wxALIGN_CENTER_VERTICAL, 5); diff --git a/xs/src/slic3r/GUI/PresetBundle.cpp b/xs/src/slic3r/GUI/PresetBundle.cpp index d36ef7b6f..0a280eee1 100644 --- a/xs/src/slic3r/GUI/PresetBundle.cpp +++ b/xs/src/slic3r/GUI/PresetBundle.cpp @@ -264,36 +264,38 @@ void PresetBundle::load_selections(const AppConfig &config) this->load_installed_printers(config); // Parse the initial print / filament / printer profile names. - std::string initial_print_profile_name = remove_ini_suffix(config.get("presets", "print")); - std::vector<std::string> initial_filament_profile_names; - std::string initial_printer_profile_name = remove_ini_suffix(config.get("presets", "printer")); + std::string initial_print_profile_name = remove_ini_suffix(config.get("presets", "print")); + std::string initial_filament_profile_name = remove_ini_suffix(config.get("presets", "filament")); + std::string initial_printer_profile_name = remove_ini_suffix(config.get("presets", "printer")); + // Activate print / filament / printer profiles from the config. + // If the printer profile enumerated by the config are not visible, select an alternate preset. + // Do not select alternate profiles for the print / filament profiles as those presets + // will be selected by the following call of this->update_compatible_with_printer(true). + prints.select_preset_by_name_strict(initial_print_profile_name); + filaments.select_preset_by_name_strict(initial_filament_profile_name); + printers.select_preset_by_name(initial_printer_profile_name, true); + + // Load the names of the other filament profiles selected for a multi-material printer. auto *nozzle_diameter = dynamic_cast<const ConfigOptionFloats*>(printers.get_selected_preset().config.option("nozzle_diameter")); - size_t num_extruders = nozzle_diameter->values.size(); - initial_filament_profile_names.emplace_back(remove_ini_suffix(config.get("presets", "filament"))); - this->set_filament_preset(0, initial_filament_profile_names.back()); + size_t num_extruders = nozzle_diameter->values.size(); + this->filament_presets = { initial_filament_profile_name }; for (unsigned int i = 1; i < (unsigned int)num_extruders; ++ i) { char name[64]; sprintf(name, "filament_%d", i); if (! config.has("presets", name)) break; - initial_filament_profile_names.emplace_back(remove_ini_suffix(config.get("presets", name))); - this->set_filament_preset(i, initial_filament_profile_names.back()); + this->filament_presets.emplace_back(remove_ini_suffix(config.get("presets", name))); } - - // Activate print / filament / printer profiles from the config. - // If the printer profile enumerated by the config are not visible, select an alternate preset. - // Do not select alternate profiles for the print / filament profiles as those presets - // will be selected by the following call of this->update_compatible_with_printer(true). - prints.select_preset_by_name_strict(initial_print_profile_name); - filaments.select_preset_by_name_strict(initial_filament_profile_names.front()); - printers.select_preset_by_name(initial_printer_profile_name, true); + // Do not define the missing filaments, so that the update_compatible_with_printer() will use the preferred filaments. + this->filament_presets.resize(num_extruders, ""); // Update visibility of presets based on their compatibility with the active printer. // Always try to select a compatible print and filament preset to the current printer preset, // as the application may have been closed with an active "external" preset, which does not // exist. this->update_compatible_with_printer(true); + this->update_multi_material_filament_presets(); } // Export selections (current print, current filaments, current printer) into config.ini @@ -946,9 +948,7 @@ void PresetBundle::update_multi_material_filament_presets() for (size_t i = 0; i < std::min(this->filament_presets.size(), num_extruders); ++ i) this->filament_presets[i] = this->filaments.find_preset(this->filament_presets[i], true)->name; // Append the rest of filament presets. -// if (this->filament_presets.size() < num_extruders) - this->filament_presets.resize(num_extruders, this->filament_presets.empty() ? this->filaments.first_visible().name : this->filament_presets.back()); - + this->filament_presets.resize(num_extruders, this->filament_presets.empty() ? this->filaments.first_visible().name : this->filament_presets.back()); // Now verify if wiping_volumes_matrix has proper size (it is used to deduce number of extruders in wipe tower generator): std::vector<double> old_matrix = this->project_config.option<ConfigOptionFloats>("wiping_volumes_matrix")->values; diff --git a/xs/src/slic3r/GUI/Tab.cpp b/xs/src/slic3r/GUI/Tab.cpp index 6eabc2f47..9e0e4fc27 100644 --- a/xs/src/slic3r/GUI/Tab.cpp +++ b/xs/src/slic3r/GUI/Tab.cpp @@ -40,49 +40,30 @@ void Tab::create_preset_tab(PresetBundle *preset_bundle) m_preset_bundle = preset_bundle; // Vertical sizer to hold the choice menu and the rest of the page. +#ifdef __WXOSX__ + auto *main_sizer = new wxBoxSizer(wxVERTICAL); + main_sizer->SetSizeHints(this); + this->SetSizer(main_sizer); + + // Create additional panel to Fit() it from OnActivate() + // It's needed for tooltip showing on OSX + m_tmp_panel = new wxPanel(this, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxBK_LEFT | wxTAB_TRAVERSAL); + auto panel = m_tmp_panel; + auto sizer = new wxBoxSizer(wxVERTICAL); + m_tmp_panel->SetSizer(sizer); + m_tmp_panel->Layout(); + + main_sizer->Add(m_tmp_panel, 1, wxEXPAND | wxALL, 0); +#else Tab *panel = this; auto *sizer = new wxBoxSizer(wxVERTICAL); sizer->SetSizeHints(panel); panel->SetSizer(sizer); +#endif //__WXOSX__ // preset chooser m_presets_choice = new wxBitmapComboBox(panel, wxID_ANY, "", wxDefaultPosition, wxSize(270, -1), 0, 0,wxCB_READONLY); - /* - m_cc_presets_choice = new wxComboCtrl(panel, wxID_ANY, L(""), wxDefaultPosition, wxDefaultSize, wxCB_READONLY); - wxDataViewTreeCtrlComboPopup* popup = new wxDataViewTreeCtrlComboPopup; - if (popup != nullptr) - { - // FIXME If the following line is removed, the combo box popup list will not react to mouse clicks. - // On the other side, with this line the combo box popup cannot be closed by clicking on the combo button on Windows 10. -// m_cc_presets_choice->UseAltPopupWindow(); -// m_cc_presets_choice->EnablePopupAnimation(false); - m_cc_presets_choice->SetPopupControl(popup); - popup->SetStringValue(from_u8("Text1")); - - popup->Bind(wxEVT_DATAVIEW_SELECTION_CHANGED, [this, popup](wxCommandEvent& evt) - { - auto selected = popup->GetItemText(popup->GetSelection()); - if (selected != _(L("System presets")) && selected != _(L("Default presets"))) - { - m_cc_presets_choice->SetText(selected); - std::string selected_string = selected.ToUTF8().data(); -#ifdef __APPLE__ -#else - select_preset(selected_string); -#endif - } - }); - -// popup->Bind(wxEVT_KEY_DOWN, [popup](wxKeyEvent& evt) { popup->OnKeyEvent(evt); }); -// popup->Bind(wxEVT_KEY_UP, [popup](wxKeyEvent& evt) { popup->OnKeyEvent(evt); }); - - auto icons = new wxImageList(16, 16, true, 1); - popup->SetImageList(icons); - icons->Add(*new wxIcon(from_u8(Slic3r::var("flag-green-icon.png")), wxBITMAP_TYPE_PNG)); - icons->Add(*new wxIcon(from_u8(Slic3r::var("flag-red-icon.png")), wxBITMAP_TYPE_PNG)); - } -*/ auto color = wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW); //buttons @@ -173,37 +154,6 @@ void Tab::create_preset_tab(PresetBundle *preset_bundle) m_hsizer = new wxBoxSizer(wxHORIZONTAL); sizer->Add(m_hsizer, 1, wxEXPAND, 0); - -/* - - - //temporary left vertical sizer - m_left_sizer = new wxBoxSizer(wxVERTICAL); - m_hsizer->Add(m_left_sizer, 0, wxEXPAND | wxLEFT | wxTOP | wxBOTTOM, 3); - - // tree - m_presetctrl = new wxDataViewTreeCtrl(panel, wxID_ANY, wxDefaultPosition, wxSize(200, -1), wxDV_NO_HEADER); - m_left_sizer->Add(m_presetctrl, 1, wxEXPAND); - m_preset_icons = new wxImageList(16, 16, true, 1); - m_presetctrl->SetImageList(m_preset_icons); - m_preset_icons->Add(*new wxIcon(from_u8(Slic3r::var("flag-green-icon.png")), wxBITMAP_TYPE_PNG)); - m_preset_icons->Add(*new wxIcon(from_u8(Slic3r::var("flag-red-icon.png")), wxBITMAP_TYPE_PNG)); - - m_presetctrl->Bind(wxEVT_DATAVIEW_SELECTION_CHANGED, [this](wxCommandEvent& evt) - { - auto selected = m_presetctrl->GetItemText(m_presetctrl->GetSelection()); - if (selected != _(L("System presets")) && selected != _(L("Default presets"))) - { - std::string selected_string = selected.ToUTF8().data(); -#ifdef __APPLE__ -#else - select_preset(selected_string); -#endif - } - }); - -*/ - //left vertical sizer m_left_sizer = new wxBoxSizer(wxVERTICAL); m_hsizer->Add(m_left_sizer, 0, wxEXPAND | wxLEFT | wxTOP | wxBOTTOM, 3); @@ -233,7 +183,7 @@ void Tab::create_preset_tab(PresetBundle *preset_bundle) return; if (selected_item >= 0){ std::string selected_string = m_presets_choice->GetString(selected_item).ToUTF8().data(); - if (selected_string.find_first_of("-------") == 0 + if (selected_string.find("-------") == 0 /*selected_string == "------- System presets -------" || selected_string == "------- User presets -------"*/){ m_presets_choice->SetSelection(m_selected_preset_item); @@ -279,7 +229,12 @@ PageShp Tab::add_options_page(const wxString& title, const std::string& icon, bo } } // Initialize the page. - PageShp page(new Page(this, title, icon_idx)); +#ifdef __WXOSX__ + auto panel = m_tmp_panel; +#else + auto panel = this; +#endif + PageShp page(new Page(panel, title, icon_idx)); page->SetScrollbars(1, 1, 1, 1); page->Hide(); m_hsizer->Add(page.get(), 1, wxEXPAND | wxLEFT, 5); @@ -290,6 +245,18 @@ PageShp Tab::add_options_page(const wxString& title, const std::string& icon, bo return page; } +void Tab::OnActivate() +{ +#ifdef __WXOSX__ + wxWindowUpdateLocker noUpdates(this); + + auto size = GetSizer()->GetSize(); + m_tmp_panel->GetSizer()->SetMinSize(size.x + m_size_move, size.y); + Fit(); + m_size_move *= -1; +#endif // __WXOSX__ +} + void Tab::update_labels_colour() { Freeze(); @@ -487,8 +454,13 @@ void Tab::update_changed_tree_ui() get_sys_and_mod_flags(opt_key, sys_page, modified_page); } } - if (title == _("Dependencies") && name() != "printer"){ - get_sys_and_mod_flags("compatible_printers", sys_page, modified_page); + if (title == _("Dependencies")){ + if (name() != "printer") + get_sys_and_mod_flags("compatible_printers", sys_page, modified_page); + else { + sys_page = m_presets->get_selected_preset_parent() ? true:false; + modified_page = false; + } } for (auto group : page->m_optgroups) { @@ -1248,6 +1220,7 @@ void TabPrint::OnActivate() { m_recommended_thin_wall_thickness_description_line->SetText( from_u8(PresetHints::recommended_thin_wall_thickness(*m_preset_bundle))); + Tab::OnActivate(); } void TabFilament::build() @@ -1405,6 +1378,7 @@ void TabFilament::update() void TabFilament::OnActivate() { m_volumetric_speed_description_line->SetText(from_u8(PresetHints::maximum_volumetric_flow_description(*m_preset_bundle))); + Tab::OnActivate(); } wxSizer* Tab::description_line_widget(wxWindow* parent, ogStaticText* *StaticText) @@ -1518,7 +1492,7 @@ void TabPrinter::build() sizer->Add(btn); btn->Bind(wxEVT_BUTTON, [this, parent](wxCommandEvent e){ - auto sender = new GCodeSender(); + auto sender = Slic3r::make_unique<GCodeSender>(); auto res = sender->connect( m_config->opt_string("serial_port"), m_config->opt_int("serial_speed") @@ -1894,6 +1868,8 @@ void Tab::load_current_preset() m_ttg_non_system = m_presets->get_selected_preset_parent() ? &m_ttg_value_unlock : &m_ttg_white_bullet_ns; m_tt_non_system = m_presets->get_selected_preset_parent() ? &m_tt_value_unlock : &m_ttg_white_bullet_ns; + m_undo_to_sys_btn->Enable(!preset.is_default); + // use CallAfter because some field triggers schedule on_change calls using CallAfter, // and we don't want them to be called after this update_dirty() as they would mark the // preset dirty again @@ -2158,6 +2134,8 @@ void Tab::save_preset(std::string name /*= ""*/) update_tab_ui(); // Update the selection boxes at the platter. on_presets_changed(); + // If current profile is saved, "delete preset" button have to be enabled + m_btn_delete_preset->Enable(true); if (m_name == "printer") static_cast<TabPrinter*>(this)->m_initial_extruders_count = static_cast<TabPrinter*>(this)->m_extruders_count; @@ -2560,28 +2538,33 @@ ConfigOptionsGroupShp Page::new_optgroup(const wxString& title, int noncommon_la if (noncommon_label_width >= 0) optgroup->label_width = noncommon_label_width; - optgroup->m_on_change = [this](t_config_option_key opt_key, boost::any value){ +#ifdef __WXOSX__ + auto tab = GetParent()->GetParent(); +#else + auto tab = GetParent(); +#endif + optgroup->m_on_change = [this, tab](t_config_option_key opt_key, boost::any value){ //! This function will be called from OptionGroup. //! Using of CallAfter is redundant. //! And in some cases it causes update() function to be recalled again //! wxTheApp->CallAfter([this, opt_key, value]() { - static_cast<Tab*>(GetParent())->update_dirty(); - static_cast<Tab*>(GetParent())->on_value_change(opt_key, value); + static_cast<Tab*>(tab)->update_dirty(); + static_cast<Tab*>(tab)->on_value_change(opt_key, value); //! }); }; - optgroup->m_get_initial_config = [this](){ - DynamicPrintConfig config = static_cast<Tab*>(GetParent())->m_presets->get_selected_preset().config; + optgroup->m_get_initial_config = [this, tab](){ + DynamicPrintConfig config = static_cast<Tab*>(tab)->m_presets->get_selected_preset().config; return config; }; - optgroup->m_get_sys_config = [this](){ - DynamicPrintConfig config = static_cast<Tab*>(GetParent())->m_presets->get_selected_preset_parent()->config; + optgroup->m_get_sys_config = [this, tab](){ + DynamicPrintConfig config = static_cast<Tab*>(tab)->m_presets->get_selected_preset_parent()->config; return config; }; - optgroup->have_sys_config = [this](){ - return static_cast<Tab*>(GetParent())->m_presets->get_selected_preset_parent() != nullptr; + optgroup->have_sys_config = [this, tab](){ + return static_cast<Tab*>(tab)->m_presets->get_selected_preset_parent() != nullptr; }; vsizer()->Add(optgroup->sizer, 0, wxEXPAND | wxALL, 10); @@ -2619,14 +2602,24 @@ void SavePresetWindow::accept() if (!m_chosen_name.empty()) { const char* unusable_symbols = "<>:/\\|?*\""; bool is_unusable_symbol = false; + bool is_unusable_postfix = false; + const std::string unusable_postfix = "(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; break; } } + if (m_chosen_name.find(unusable_postfix) != std::string::npos) + is_unusable_postfix = true; + if (is_unusable_symbol) { - show_error(this, _(L("The supplied name is not valid; the following characters are not allowed:"))+" <>:/\\|?*\""); + show_error(this,_(L("The supplied name is not valid;")) + "\n" + + _(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); } else if (m_chosen_name.compare("- default -") == 0) { show_error(this, _(L("The supplied name is not available."))); diff --git a/xs/src/slic3r/GUI/Tab.hpp b/xs/src/slic3r/GUI/Tab.hpp index d6bf2cf43..c755f91f1 100644 --- a/xs/src/slic3r/GUI/Tab.hpp +++ b/xs/src/slic3r/GUI/Tab.hpp @@ -102,6 +102,10 @@ using PageShp = std::shared_ptr<Page>; class Tab: public wxPanel { wxNotebook* m_parent; +#ifdef __WXOSX__ + wxPanel* m_tmp_panel; + int m_size_move = -1; +#endif // __WXOSX__ protected: std::string m_name; const wxString m_title; @@ -118,7 +122,6 @@ protected: wxButton* m_undo_btn; wxButton* m_undo_to_sys_btn; wxButton* m_question_btn; - wxComboCtrl* m_cc_presets_choice; wxDataViewTreeCtrl* m_presetctrl; wxImageList* m_preset_icons; @@ -198,7 +201,7 @@ public: Tab() {} Tab(wxNotebook* parent, const wxString& title, const char* name, bool no_controller) : m_parent(parent), m_title(title), m_name(name), m_no_controller(no_controller) { - Create(parent, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxBK_LEFT | wxTAB_TRAVERSAL); + Create(parent, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxBK_LEFT | wxTAB_TRAVERSAL, name); get_tabs_list().push_back(this); } ~Tab(){ @@ -242,7 +245,7 @@ public: PageShp add_options_page(const wxString& title, const std::string& icon, bool is_extruder_pages = false); - virtual void OnActivate(){} + virtual void OnActivate(); virtual void on_preset_loaded(){} virtual void build() = 0; virtual void update() = 0; diff --git a/xs/src/slic3r/Utils/FixModelByWin10.cpp b/xs/src/slic3r/Utils/FixModelByWin10.cpp new file mode 100644 index 000000000..556035a5b --- /dev/null +++ b/xs/src/slic3r/Utils/FixModelByWin10.cpp @@ -0,0 +1,402 @@ +#ifdef HAS_WIN10SDK + +#ifndef NOMINMAX +# define NOMINMAX +#endif + +#include "FixModelByWin10.hpp" + +#include <atomic> +#include <chrono> +#include <cstdint> +#include <condition_variable> +#include <exception> +#include <string> +#include <thread> + +#include <boost/filesystem.hpp> +#include <boost/nowide/convert.hpp> +#include <boost/nowide/cstdio.hpp> + +#include <roapi.h> +// for ComPtr +#include <wrl/client.h> +// from C:/Program Files (x86)/Windows Kits/10/Include/10.0.17134.0/ +#include <winrt/robuffer.h> +#include <winrt/windows.storage.provider.h> +#include <winrt/windows.graphics.printing3d.h> + +#include "libslic3r/Model.hpp" +#include "libslic3r/Print.hpp" +#include "libslic3r/Format/3mf.hpp" +#include "../GUI/GUI.hpp" +#include "../GUI/PresetBundle.hpp" + +#include <wx/msgdlg.h> +#include <wx/progdlg.h> + +extern "C"{ + // from rapi.h + typedef HRESULT (__stdcall* FunctionRoInitialize)(int); + typedef HRESULT (__stdcall* FunctionRoUninitialize)(); + typedef HRESULT (__stdcall* FunctionRoActivateInstance)(HSTRING activatableClassId, IInspectable **instance); + typedef HRESULT (__stdcall* FunctionRoGetActivationFactory)(HSTRING activatableClassId, REFIID iid, void **factory); + // from winstring.h + typedef HRESULT (__stdcall* FunctionWindowsCreateString)(LPCWSTR sourceString, UINT32 length, HSTRING *string); + typedef HRESULT (__stdcall* FunctionWindowsDelteString)(HSTRING string); +} + +namespace Slic3r { + +HMODULE s_hRuntimeObjectLibrary = nullptr; +FunctionRoInitialize s_RoInitialize = nullptr; +FunctionRoUninitialize s_RoUninitialize = nullptr; +FunctionRoActivateInstance s_RoActivateInstance = nullptr; +FunctionRoGetActivationFactory s_RoGetActivationFactory = nullptr; +FunctionWindowsCreateString s_WindowsCreateString = nullptr; +FunctionWindowsDelteString s_WindowsDeleteString = nullptr; + +bool winrt_load_runtime_object_library() +{ + if (s_hRuntimeObjectLibrary == nullptr) + s_hRuntimeObjectLibrary = LoadLibrary(L"ComBase.dll"); + if (s_hRuntimeObjectLibrary != nullptr) { + s_RoInitialize = (FunctionRoInitialize) GetProcAddress(s_hRuntimeObjectLibrary, "RoInitialize"); + s_RoUninitialize = (FunctionRoUninitialize) GetProcAddress(s_hRuntimeObjectLibrary, "RoUninitialize"); + s_RoActivateInstance = (FunctionRoActivateInstance) GetProcAddress(s_hRuntimeObjectLibrary, "RoActivateInstance"); + s_RoGetActivationFactory = (FunctionRoGetActivationFactory) GetProcAddress(s_hRuntimeObjectLibrary, "RoGetActivationFactory"); + s_WindowsCreateString = (FunctionWindowsCreateString) GetProcAddress(s_hRuntimeObjectLibrary, "WindowsCreateString"); + s_WindowsDeleteString = (FunctionWindowsDelteString) GetProcAddress(s_hRuntimeObjectLibrary, "WindowsDeleteString"); + } + return s_RoInitialize && s_RoUninitialize && s_RoActivateInstance && s_WindowsCreateString && s_WindowsDeleteString; +} + +static HRESULT winrt_activate_instance(const std::wstring &class_name, IInspectable **pinst) +{ + HSTRING hClassName; + HRESULT hr = (*s_WindowsCreateString)(class_name.c_str(), class_name.size(), &hClassName); + if (S_OK != hr) + return hr; + hr = (*s_RoActivateInstance)(hClassName, pinst); + (*s_WindowsDeleteString)(hClassName); + return hr; +} + +template<typename TYPE> +static HRESULT winrt_activate_instance(const std::wstring &class_name, TYPE **pinst) +{ + IInspectable *pinspectable = nullptr; + HRESULT hr = winrt_activate_instance(class_name, &pinspectable); + if (S_OK != hr) + return hr; + hr = pinspectable->QueryInterface(__uuidof(TYPE), (void**)pinst); + pinspectable->Release(); + return hr; +} + +static HRESULT winrt_get_activation_factory(const std::wstring &class_name, REFIID iid, void **pinst) +{ + HSTRING hClassName; + HRESULT hr = (*s_WindowsCreateString)(class_name.c_str(), class_name.size(), &hClassName); + if (S_OK != hr) + return hr; + hr = (*s_RoGetActivationFactory)(hClassName, iid, pinst); + (*s_WindowsDeleteString)(hClassName); + return hr; +} + +template<typename TYPE> +static HRESULT winrt_get_activation_factory(const std::wstring &class_name, TYPE **pinst) +{ + return winrt_get_activation_factory(class_name, __uuidof(TYPE), reinterpret_cast<void**>(pinst)); +} + +// To be called often to test whether to cancel the operation. +typedef std::function<void ()> ThrowOnCancelFn; + +template<typename T> +static AsyncStatus winrt_async_await(const Microsoft::WRL::ComPtr<T> &asyncAction, ThrowOnCancelFn throw_on_cancel, int blocking_tick_ms = 100) +{ + Microsoft::WRL::ComPtr<ABI::Windows::Foundation::IAsyncInfo> asyncInfo; + asyncAction.As(&asyncInfo); + AsyncStatus status; + // Ugly blocking loop until the RepairAsync call finishes. +//FIXME replace with a callback. +// https://social.msdn.microsoft.com/Forums/en-US/a5038fb4-b7b7-4504-969d-c102faa389fb/trying-to-block-an-async-operation-and-wait-for-a-particular-time?forum=vclanguage + for (;;) { + asyncInfo->get_Status(&status); + if (status != AsyncStatus::Started) + return status; + throw_on_cancel(); + ::Sleep(blocking_tick_ms); + } +} + +static HRESULT winrt_open_file_stream( + const std::wstring &path, + ABI::Windows::Storage::FileAccessMode mode, + ABI::Windows::Storage::Streams::IRandomAccessStream **fileStream, + ThrowOnCancelFn throw_on_cancel) +{ + // Get the file factory. + Microsoft::WRL::ComPtr<ABI::Windows::Storage::IStorageFileStatics> fileFactory; + HRESULT hr = winrt_get_activation_factory(L"Windows.Storage.StorageFile", fileFactory.GetAddressOf()); + if (FAILED(hr)) return hr; + + // Open the file asynchronously. + HSTRING hstr_path; + hr = (*s_WindowsCreateString)(path.c_str(), path.size(), &hstr_path); + if (FAILED(hr)) return hr; + Microsoft::WRL::ComPtr<ABI::Windows::Foundation::IAsyncOperation<ABI::Windows::Storage::StorageFile*>> fileOpenAsync; + hr = fileFactory->GetFileFromPathAsync(hstr_path, fileOpenAsync.GetAddressOf()); + if (FAILED(hr)) return hr; + (*s_WindowsDeleteString)(hstr_path); + + // Wait until the file gets open, get the actual file. + AsyncStatus status = winrt_async_await(fileOpenAsync, throw_on_cancel); + Microsoft::WRL::ComPtr<ABI::Windows::Storage::IStorageFile> storageFile; + if (status == AsyncStatus::Completed) { + hr = fileOpenAsync->GetResults(storageFile.GetAddressOf()); + } else { + Microsoft::WRL::ComPtr<ABI::Windows::Foundation::IAsyncInfo> asyncInfo; + hr = fileOpenAsync.As(&asyncInfo); + if (FAILED(hr)) return hr; + HRESULT err; + hr = asyncInfo->get_ErrorCode(&err); + return FAILED(hr) ? hr : err; + } + + Microsoft::WRL::ComPtr<ABI::Windows::Foundation::IAsyncOperation<ABI::Windows::Storage::Streams::IRandomAccessStream*>> fileStreamAsync; + hr = storageFile->OpenAsync(mode, fileStreamAsync.GetAddressOf()); + if (FAILED(hr)) return hr; + + status = winrt_async_await(fileStreamAsync, throw_on_cancel); + if (status == AsyncStatus::Completed) { + hr = fileStreamAsync->GetResults(fileStream); + } else { + Microsoft::WRL::ComPtr<ABI::Windows::Foundation::IAsyncInfo> asyncInfo; + hr = fileStreamAsync.As(&asyncInfo); + if (FAILED(hr)) return hr; + HRESULT err; + hr = asyncInfo->get_ErrorCode(&err); + if (!FAILED(hr)) + hr = err; + } + return hr; +} + +bool is_windows10() +{ + HKEY hKey; + LONG lRes = RegOpenKeyExW(HKEY_LOCAL_MACHINE, L"SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion", 0, KEY_READ, &hKey); + if (lRes == ERROR_SUCCESS) { + WCHAR szBuffer[512]; + DWORD dwBufferSize = sizeof(szBuffer); + lRes = RegQueryValueExW(hKey, L"ProductName", 0, nullptr, (LPBYTE)szBuffer, &dwBufferSize); + if (lRes == ERROR_SUCCESS) + return wcsncmp(szBuffer, L"Windows 10", 10) == 0; + RegCloseKey(hKey); + } + return false; +} + +// Progress function, to be called regularly to update the progress. +typedef std::function<void (const char * /* message */, unsigned /* progress */)> ProgressFn; + +void fix_model_by_win10_sdk(const std::string &path_src, const std::string &path_dst, ProgressFn on_progress, ThrowOnCancelFn throw_on_cancel) +{ + if (! is_windows10()) + throw std::runtime_error("fix_model_by_win10_sdk called on non Windows 10 system"); + + if (! winrt_load_runtime_object_library()) + throw std::runtime_error("Failed to initialize the WinRT library."); + + HRESULT hr = (*s_RoInitialize)(RO_INIT_MULTITHREADED); + { + on_progress(L("Exporting the source model"), 20); + + Microsoft::WRL::ComPtr<ABI::Windows::Storage::Streams::IRandomAccessStream> fileStream; + hr = winrt_open_file_stream(boost::nowide::widen(path_src), ABI::Windows::Storage::FileAccessMode::FileAccessMode_Read, fileStream.GetAddressOf(), throw_on_cancel); + + Microsoft::WRL::ComPtr<ABI::Windows::Graphics::Printing3D::IPrinting3D3MFPackage> printing3d3mfpackage; + hr = winrt_activate_instance(L"Windows.Graphics.Printing3D.Printing3D3MFPackage", printing3d3mfpackage.GetAddressOf()); + + Microsoft::WRL::ComPtr<ABI::Windows::Foundation::IAsyncOperation<ABI::Windows::Graphics::Printing3D::Printing3DModel*>> modelAsync; + hr = printing3d3mfpackage->LoadModelFromPackageAsync(fileStream.Get(), modelAsync.GetAddressOf()); + + AsyncStatus status = winrt_async_await(modelAsync, throw_on_cancel); + Microsoft::WRL::ComPtr<ABI::Windows::Graphics::Printing3D::IPrinting3DModel> model; + if (status == AsyncStatus::Completed) + hr = modelAsync->GetResults(model.GetAddressOf()); + else + throw std::runtime_error(L("Failed loading the input model.")); + + Microsoft::WRL::ComPtr<ABI::Windows::Foundation::Collections::IVector<ABI::Windows::Graphics::Printing3D::Printing3DMesh*>> meshes; + hr = model->get_Meshes(meshes.GetAddressOf()); + unsigned num_meshes = 0; + hr = meshes->get_Size(&num_meshes); + + on_progress(L("Repairing the model by the Netfabb service"), 40); + + Microsoft::WRL::ComPtr<ABI::Windows::Foundation::IAsyncAction> repairAsync; + hr = model->RepairAsync(repairAsync.GetAddressOf()); + status = winrt_async_await(repairAsync, throw_on_cancel); + if (status != AsyncStatus::Completed) + throw std::runtime_error(L("Mesh repair failed.")); + repairAsync->GetResults(); + + on_progress(L("Loading the repaired model"), 60); + + // Verify the number of meshes returned after the repair action. + meshes.Reset(); + hr = model->get_Meshes(meshes.GetAddressOf()); + hr = meshes->get_Size(&num_meshes); + + // Save model to this class' Printing3D3MFPackage. + Microsoft::WRL::ComPtr<ABI::Windows::Foundation::IAsyncAction> saveToPackageAsync; + hr = printing3d3mfpackage->SaveModelToPackageAsync(model.Get(), saveToPackageAsync.GetAddressOf()); + status = winrt_async_await(saveToPackageAsync, throw_on_cancel); + if (status != AsyncStatus::Completed) + throw std::runtime_error(L("Saving mesh into the 3MF container failed.")); + hr = saveToPackageAsync->GetResults(); + + Microsoft::WRL::ComPtr<ABI::Windows::Foundation::IAsyncOperation<ABI::Windows::Storage::Streams::IRandomAccessStream*>> generatorStreamAsync; + hr = printing3d3mfpackage->SaveAsync(generatorStreamAsync.GetAddressOf()); + status = winrt_async_await(generatorStreamAsync, throw_on_cancel); + if (status != AsyncStatus::Completed) + throw std::runtime_error(L("Saving mesh into the 3MF container failed.")); + Microsoft::WRL::ComPtr<ABI::Windows::Storage::Streams::IRandomAccessStream> generatorStream; + hr = generatorStreamAsync->GetResults(generatorStream.GetAddressOf()); + + // Go to the beginning of the stream. + generatorStream->Seek(0); + Microsoft::WRL::ComPtr<ABI::Windows::Storage::Streams::IInputStream> inputStream; + hr = generatorStream.As(&inputStream); + + // Get the buffer factory. + Microsoft::WRL::ComPtr<ABI::Windows::Storage::Streams::IBufferFactory> bufferFactory; + hr = winrt_get_activation_factory(L"Windows.Storage.Streams.Buffer", bufferFactory.GetAddressOf()); + + // Open the destination file. + FILE *fout = boost::nowide::fopen(path_dst.c_str(), "wb"); + + Microsoft::WRL::ComPtr<ABI::Windows::Storage::Streams::IBuffer> buffer; + byte *buffer_ptr; + bufferFactory->Create(65536 * 2048, buffer.GetAddressOf()); + { + Microsoft::WRL::ComPtr<Windows::Storage::Streams::IBufferByteAccess> bufferByteAccess; + buffer.As(&bufferByteAccess); + hr = bufferByteAccess->Buffer(&buffer_ptr); + } + uint32_t length; + hr = buffer->get_Length(&length); + + Microsoft::WRL::ComPtr<ABI::Windows::Foundation::IAsyncOperationWithProgress<ABI::Windows::Storage::Streams::IBuffer*, UINT32>> asyncRead; + for (;;) { + hr = inputStream->ReadAsync(buffer.Get(), 65536 * 2048, ABI::Windows::Storage::Streams::InputStreamOptions_ReadAhead, asyncRead.GetAddressOf()); + status = winrt_async_await(asyncRead, throw_on_cancel); + if (status != AsyncStatus::Completed) + throw std::runtime_error(L("Saving mesh into the 3MF container failed.")); + hr = buffer->get_Length(&length); + if (length == 0) + break; + fwrite(buffer_ptr, length, 1, fout); + } + fclose(fout); + // Here all the COM objects will be released through the ComPtr destructors. + } + (*s_RoUninitialize)(); +} + +class RepairCanceledException : public std::exception { +public: + const char* what() const throw() { return "Model repair has been canceled"; } +}; + +void fix_model_by_win10_sdk_gui(const ModelObject &model_object, const Print &print, Model &result) +{ + std::mutex mutex; + std::condition_variable condition; + std::unique_lock<std::mutex> lock(mutex); + struct Progress { + std::string message; + int percent = 0; + bool updated = false; + } progress; + std::atomic<bool> canceled = false; + std::atomic<bool> finished = false; + + // Open a progress dialog. + wxProgressDialog progress_dialog( + _(L("Model fixing")), + _(L("Exporting model...")), + 100, nullptr, wxPD_AUTO_HIDE | wxPD_APP_MODAL | wxPD_CAN_ABORT); + // Executing the calculation in a background thread, so that the COM context could be created with its own threading model. + // (It seems like wxWidgets initialize the COM contex as single threaded and we need a multi-threaded context). + bool success = false; + auto on_progress = [&mutex, &condition, &progress](const char *msg, unsigned prcnt) { + std::lock_guard<std::mutex> lk(mutex); + progress.message = msg; + progress.percent = prcnt; + progress.updated = true; + condition.notify_all(); + }; + auto worker_thread = boost::thread([&model_object, &print, &result, on_progress, &success, &canceled, &finished]() { + try { + on_progress(L("Exporting the source model"), 0); + boost::filesystem::path path_src = boost::filesystem::temp_directory_path() / boost::filesystem::unique_path(); + path_src += ".3mf"; + Model model; + model.add_object(model_object); + if (! Slic3r::store_3mf(path_src.string().c_str(), &model, const_cast<Print*>(&print), false)) { + boost::filesystem::remove(path_src); + throw std::runtime_error(L("Export of a temporary 3mf file failed")); + } + model.clear_objects(); + model.clear_materials(); + boost::filesystem::path path_dst = boost::filesystem::temp_directory_path() / boost::filesystem::unique_path(); + path_dst += ".3mf"; + fix_model_by_win10_sdk(path_src.string().c_str(), path_dst.string(), on_progress, + [&canceled]() { if (canceled) throw RepairCanceledException(); }); + boost::filesystem::remove(path_src); + PresetBundle bundle; + on_progress(L("Loading the repaired model"), 80); + bool loaded = Slic3r::load_3mf(path_dst.string().c_str(), &bundle, &result); + boost::filesystem::remove(path_dst); + if (! loaded) + throw std::runtime_error(L("Import of the repaired 3mf file failed")); + success = true; + finished = true; + on_progress(L("Model repair finished"), 100); + } catch (RepairCanceledException &ex) { + canceled = true; + finished = true; + on_progress(L("Model repair canceled"), 100); + } catch (std::exception &ex) { + success = false; + finished = true; + on_progress(ex.what(), 100); + } + }); + while (! finished) { + condition.wait_for(lock, std::chrono::milliseconds(500), [&progress]{ return progress.updated; }); + if (! progress_dialog.Update(progress.percent, _(progress.message))) + canceled = true; + progress.updated = false; + } + + if (canceled) { + // Nothing to show. + } else if (success) { + wxMessageDialog dlg(nullptr, _(L("Model repaired successfully")), _(L("Model Repair by the Netfabb service")), wxICON_INFORMATION | wxOK_DEFAULT); + dlg.ShowModal(); + } else { + wxMessageDialog dlg(nullptr, _(L("Model repair failed: \n")) + _(progress.message), _(L("Model Repair by the Netfabb service")), wxICON_ERROR | wxOK_DEFAULT); + dlg.ShowModal(); + } + worker_thread.join(); +} + +} // namespace Slic3r + +#endif /* HAS_WIN10SDK */ diff --git a/xs/src/slic3r/Utils/FixModelByWin10.hpp b/xs/src/slic3r/Utils/FixModelByWin10.hpp new file mode 100644 index 000000000..c148a6970 --- /dev/null +++ b/xs/src/slic3r/Utils/FixModelByWin10.hpp @@ -0,0 +1,26 @@ +#ifndef slic3r_GUI_Utils_FixModelByWin10_hpp_ +#define slic3r_GUI_Utils_FixModelByWin10_hpp_ + +#include <string> + +namespace Slic3r { + +class Model; +class ModelObject; +class Print; + +#ifdef HAS_WIN10SDK + +extern bool is_windows10(); +extern void fix_model_by_win10_sdk_gui(const ModelObject &model_object, const Print &print, Model &result); + +#else /* HAS_WIN10SDK */ + +inline bool is_windows10() { return false; } +inline void fix_model_by_win10_sdk_gui(const ModelObject &, const Print &, Model &) {} + +#endif /* HAS_WIN10SDK */ + +} // namespace Slic3r + +#endif /* slic3r_GUI_Utils_FixModelByWin10_hpp_ */ diff --git a/xs/src/slic3r/Utils/Http.cpp b/xs/src/slic3r/Utils/Http.cpp index 0826284d8..37eb59a00 100644 --- a/xs/src/slic3r/Utils/Http.cpp +++ b/xs/src/slic3r/Utils/Http.cpp @@ -3,13 +3,16 @@ #include <cstdlib> #include <functional> #include <thread> -#include <tuple> +#include <deque> +#include <boost/filesystem/fstream.hpp> #include <boost/format.hpp> #include <curl/curl.h> #include "../../libslic3r/libslic3r.h" +namespace fs = boost::filesystem; + namespace Slic3r { @@ -34,7 +37,11 @@ struct Http::priv ::curl_httppost *form; ::curl_httppost *form_end; ::curl_slist *headerlist; + // Used for reading the body std::string buffer; + // Used for storing file streams added as multipart form parts + // Using a deque here because unlike vector it doesn't ivalidate pointers on insertion + std::deque<fs::ifstream> form_files; size_t limit; bool cancel; @@ -50,6 +57,10 @@ struct Http::priv static size_t writecb(void *data, size_t size, size_t nmemb, void *userp); static int xfercb(void *userp, curl_off_t dltotal, curl_off_t dlnow, curl_off_t ultotal, curl_off_t ulnow); static int xfercb_legacy(void *userp, double dltotal, double dlnow, double ultotal, double ulnow); + static size_t form_file_read_cb(char *buffer, size_t size, size_t nitems, void *userp); + + void form_add_file(const char *name, const fs::path &path, const char* filename); + std::string curl_error(CURLcode curlcode); std::string body_size_error(); void http_perform(); @@ -60,6 +71,7 @@ Http::priv::priv(const std::string &url) : form(nullptr), form_end(nullptr), headerlist(nullptr), + limit(0), cancel(false) { if (curl == nullptr) { @@ -135,6 +147,46 @@ int Http::priv::xfercb_legacy(void *userp, double dltotal, double dlnow, double return xfercb(userp, dltotal, dlnow, ultotal, ulnow); } +size_t Http::priv::form_file_read_cb(char *buffer, size_t size, size_t nitems, void *userp) +{ + auto stream = reinterpret_cast<fs::ifstream*>(userp); + + try { + stream->read(buffer, size * nitems); + } catch (...) { + return CURL_READFUNC_ABORT; + } + + return stream->gcount(); +} + +void Http::priv::form_add_file(const char *name, const fs::path &path, const char* filename) +{ + // We can't use CURLFORM_FILECONTENT, because curl doesn't support Unicode filenames on Windows + // and so we use CURLFORM_STREAM with boost ifstream to read the file. + + if (filename == nullptr) { + filename = path.string().c_str(); + } + + form_files.emplace_back(path, std::ios::in | std::ios::binary); + auto &stream = form_files.back(); + stream.seekg(0, std::ios::end); + size_t size = stream.tellg(); + stream.seekg(0); + + if (filename != nullptr) { + ::curl_formadd(&form, &form_end, + CURLFORM_COPYNAME, name, + CURLFORM_FILENAME, filename, + CURLFORM_CONTENTTYPE, "application/octet-stream", + CURLFORM_STREAM, static_cast<void*>(&stream), + CURLFORM_CONTENTSLENGTH, static_cast<long>(size), + CURLFORM_END + ); + } +} + std::string Http::priv::curl_error(CURLcode curlcode) { return (boost::format("%1% (%2%)") @@ -150,10 +202,10 @@ std::string Http::priv::body_size_error() void Http::priv::http_perform() { - ::curl_easy_setopt(curl, CURLOPT_FAILONERROR, 1L); ::curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L); ::curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, writecb); ::curl_easy_setopt(curl, CURLOPT_WRITEDATA, static_cast<void*>(this)); + ::curl_easy_setopt(curl, CURLOPT_READFUNCTION, form_file_read_cb); ::curl_easy_setopt(curl, CURLOPT_NOPROGRESS, 0L); #if LIBCURL_VERSION_MAJOR >= 7 && LIBCURL_VERSION_MINOR >= 32 @@ -178,23 +230,32 @@ void Http::priv::http_perform() } CURLcode res = ::curl_easy_perform(curl); - long http_status = 0; - ::curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &http_status); if (res != CURLE_OK) { if (res == CURLE_ABORTED_BY_CALLBACK) { - Progress dummyprogress(0, 0, 0, 0); - bool cancel = true; - if (progressfn) { progressfn(dummyprogress, cancel); } + if (cancel) { + // The abort comes from the request being cancelled programatically + Progress dummyprogress(0, 0, 0, 0); + bool cancel = true; + if (progressfn) { progressfn(dummyprogress, cancel); } + } else { + // The abort comes from the CURLOPT_READFUNCTION callback, which means reading file failed + if (errorfn) { errorfn(std::move(buffer), "Error reading file for file upload", 0); } + } } else if (res == CURLE_WRITE_ERROR) { - if (errorfn) { errorfn(std::move(buffer), std::move(body_size_error()), http_status); } + if (errorfn) { errorfn(std::move(buffer), body_size_error(), 0); } } else { - if (errorfn) { errorfn(std::move(buffer), std::move(curl_error(res)), http_status); } + if (errorfn) { errorfn(std::move(buffer), curl_error(res), 0); } }; } else { - if (completefn) { - completefn(std::move(buffer), http_status); + long http_status = 0; + ::curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &http_status); + + if (http_status >= 400) { + if (errorfn) { errorfn(std::move(buffer), std::string(), http_status); } + } else { + if (completefn) { completefn(std::move(buffer), http_status); } } } } @@ -265,17 +326,15 @@ Http& Http::form_add(const std::string &name, const std::string &contents) return *this; } -Http& Http::form_add_file(const std::string &name, const std::string &filename) +Http& Http::form_add_file(const std::string &name, const fs::path &path) { - if (p) { - ::curl_formadd(&p->form, &p->form_end, - CURLFORM_COPYNAME, name.c_str(), - CURLFORM_FILE, filename.c_str(), - CURLFORM_CONTENTTYPE, "application/octet-stream", - CURLFORM_END - ); - } + if (p) { p->form_add_file(name.c_str(), path.c_str(), nullptr); } + return *this; +} +Http& Http::form_add_file(const std::string &name, const fs::path &path, const std::string &filename) +{ + if (p) { p->form_add_file(name.c_str(), path.c_str(), filename.c_str()); } return *this; } diff --git a/xs/src/slic3r/Utils/Http.hpp b/xs/src/slic3r/Utils/Http.hpp index 7ed8196e6..ce4e438ca 100644 --- a/xs/src/slic3r/Utils/Http.hpp +++ b/xs/src/slic3r/Utils/Http.hpp @@ -4,6 +4,7 @@ #include <memory> #include <string> #include <functional> +#include <boost/filesystem/path.hpp> namespace Slic3r { @@ -16,11 +17,11 @@ private: public: struct Progress { - size_t dltotal; - size_t dlnow; - size_t ultotal; - size_t ulnow; - + size_t dltotal; // Total bytes to download + size_t dlnow; // Bytes downloaded so far + size_t ultotal; // Total bytes to upload + size_t ulnow; // Bytes uploaded so far + Progress(size_t dltotal, size_t dlnow, size_t ultotal, size_t ulnow) : dltotal(dltotal), dlnow(dlnow), ultotal(ultotal), ulnow(ulnow) {} @@ -28,11 +29,24 @@ public: typedef std::shared_ptr<Http> Ptr; typedef std::function<void(std::string /* body */, unsigned /* http_status */)> CompleteFn; + + // A HTTP request may fail at various stages of completeness (URL parsing, DNS lookup, TCP connection, ...). + // If the HTTP request could not be made or failed before completion, the `error` arg contains a description + // of the error and `http_status` is zero. + // If the HTTP request was completed but the response HTTP code is >= 400, `error` is empty and `http_status` contains the response code. + // In either case there may or may not be a body. typedef std::function<void(std::string /* body */, std::string /* error */, unsigned /* http_status */)> ErrorFn; + + // See the Progress struct above. + // Writing true to the `cancel` reference cancels the request in progress. typedef std::function<void(Progress, bool& /* cancel */)> ProgressFn; Http(Http &&other); + // Note: strings are expected to be UTF-8-encoded + + // These are the primary constructors that create a HTTP object + // for a GET and a POST request respectively. static Http get(std::string url); static Http post(std::string url); ~Http(); @@ -41,21 +55,43 @@ public: Http& operator=(const Http &) = delete; Http& operator=(Http &&) = delete; + // Sets a maximum size of the data that can be received. + // A value of zero sets the default limit, which is is 5MB. Http& size_limit(size_t sizeLimit); + // Sets a HTTP header field. Http& header(std::string name, const std::string &value); + // Removes a header field. Http& remove_header(std::string name); + // Sets a CA certificate file for usage with HTTPS. This is only supported on some backends, + // specifically, this is supported with OpenSSL and NOT supported with Windows and OS X native certificate store. + // See also ca_file_supported(). Http& ca_file(const std::string &filename); + // Add a HTTP multipart form field Http& form_add(const std::string &name, const std::string &contents); - Http& form_add_file(const std::string &name, const std::string &filename); + // Add a HTTP multipart form file data contents, `name` is the name of the part + Http& form_add_file(const std::string &name, const boost::filesystem::path &path); + // Same as above except also override the file's filename with a custom one + Http& form_add_file(const std::string &name, const boost::filesystem::path &path, const std::string &filename); + // Callback called on HTTP request complete Http& on_complete(CompleteFn fn); + // Callback called on an error occuring at any stage of the requests: Url parsing, DNS lookup, + // TCP connection, HTTP transfer, and finally also when the response indicates an error (status >= 400). + // Therefore, a response body may or may not be present. Http& on_error(ErrorFn fn); + // Callback called on data download/upload prorgess (called fairly frequently). + // See the `Progress` structure for description of the data passed. + // Writing a true-ish value into the cancel reference parameter cancels the request. Http& on_progress(ProgressFn fn); + // Starts performing the request in a background thread Ptr perform(); + // Starts performing the request on the current thread void perform_sync(); + // Cancels a request in progress void cancel(); + // Tells whether current backend supports seting up a CA file using ca_file() static bool ca_file_supported(); private: Http(const std::string &url); diff --git a/xs/src/slic3r/Utils/OctoPrint.cpp b/xs/src/slic3r/Utils/OctoPrint.cpp index e63a16c38..97b4123d4 100644 --- a/xs/src/slic3r/Utils/OctoPrint.cpp +++ b/xs/src/slic3r/Utils/OctoPrint.cpp @@ -1,20 +1,65 @@ #include "OctoPrint.hpp" #include <algorithm> +#include <boost/filesystem/path.hpp> #include <boost/format.hpp> +#include <boost/log/trivial.hpp> #include <wx/frame.h> #include <wx/event.h> #include <wx/progdlg.h> +#include <wx/sizer.h> +#include <wx/stattext.h> +#include <wx/textctrl.h> +#include <wx/checkbox.h> #include "libslic3r/PrintConfig.hpp" #include "slic3r/GUI/GUI.hpp" +#include "slic3r/GUI/MsgDialog.hpp" #include "Http.hpp" +namespace fs = boost::filesystem; + namespace Slic3r { +struct SendDialog : public GUI::MsgDialog +{ + wxTextCtrl *txt_filename; + wxCheckBox *box_print; + + SendDialog(const fs::path &path) : + MsgDialog(nullptr, _(L("Send G-Code to printer")), _(L("Upload to OctoPrint with the following filename:")), wxID_NONE), + txt_filename(new wxTextCtrl(this, wxID_ANY, path.filename().wstring())), + box_print(new wxCheckBox(this, wxID_ANY, _(L("Start printing after upload")))) + { + auto *label_dir_hint = new wxStaticText(this, wxID_ANY, _(L("Use forward slashes ( / ) as a directory separator if needed."))); + label_dir_hint->Wrap(CONTENT_WIDTH); + + content_sizer->Add(txt_filename, 0, wxEXPAND); + content_sizer->Add(label_dir_hint); + content_sizer->AddSpacer(VERT_SPACING); + content_sizer->Add(box_print, 0, wxBOTTOM, 2*VERT_SPACING); + + btn_sizer->Add(CreateStdDialogButtonSizer(wxOK | wxCANCEL)); + + txt_filename->SetFocus(); + wxString stem(path.stem().wstring()); + txt_filename->SetSelection(0, stem.Length()); + + Fit(); + } + + fs::path filename() const { + return fs::path(txt_filename->GetValue().wx_str()); + } + + bool print() const { return box_print->GetValue(); } +}; + + + OctoPrint::OctoPrint(DynamicPrintConfig *config) : host(config->opt_string("octoprint_host")), apikey(config->opt_string("octoprint_apikey")), @@ -27,24 +72,39 @@ bool OctoPrint::test(wxString &msg) const // it is ok to refer to `msg` from within the closure bool res = true; + auto url = make_url("api/version"); + + BOOST_LOG_TRIVIAL(info) << boost::format("Octoprint: Get version at: %1%") % url; - auto url = std::move(make_url("api/version")); auto http = Http::get(std::move(url)); set_auth(http); - http.on_error([&](std::string, std::string error, unsigned status) { + http.on_error([&](std::string body, std::string error, unsigned status) { + BOOST_LOG_TRIVIAL(error) << boost::format("Octoprint: Error getting version: %1%, HTTP %2%, body: `%3%`") % error % status % body; res = false; - msg = format_error(error, status); + msg = format_error(body, error, status); + }) + .on_complete([&](std::string body, unsigned) { + BOOST_LOG_TRIVIAL(debug) << boost::format("Octoprint: Got version: %1%") % body; }) .perform_sync(); return res; } -bool OctoPrint::send_gcode(const std::string &filename, bool print) const +bool OctoPrint::send_gcode(const std::string &filename) const { enum { PROGRESS_RANGE = 1000 }; const auto errortitle = _(L("Error while uploading to the OctoPrint server")); + fs::path filepath(filename); + + SendDialog send_dialog(filepath.filename()); + if (send_dialog.ShowModal() != wxID_OK) { return false; } + + const bool print = send_dialog.print(); + const auto upload_filepath = send_dialog.filename(); + const auto upload_filename = upload_filepath.filename(); + const auto upload_parent_path = upload_filepath.parent_path(); wxProgressDialog progress_dialog( _(L("OctoPrint upload")), @@ -61,15 +121,27 @@ bool OctoPrint::send_gcode(const std::string &filename, bool print) const bool res = true; - auto http = Http::post(std::move(make_url("api/files/local"))); + auto url = make_url("api/files/local"); + + BOOST_LOG_TRIVIAL(info) << boost::format("Octoprint: Uploading file %1% at %2%, filename: %3%, path: %4%, print: %5%") + % filepath.string() + % url + % upload_filename.string() + % upload_parent_path.string() + % print; + + auto http = Http::post(std::move(url)); set_auth(http); http.form_add("print", print ? "true" : "false") - .form_add_file("file", filename) + .form_add("path", upload_parent_path.string()) + .form_add_file("file", filename, upload_filename.string()) .on_complete([&](std::string body, unsigned status) { + BOOST_LOG_TRIVIAL(debug) << boost::format("Octoprint: File uploaded: HTTP %1%: %2%") % status % body; progress_dialog.Update(PROGRESS_RANGE); }) .on_error([&](std::string body, std::string error, unsigned status) { - auto errormsg = wxString::Format("%s: %s", errortitle, format_error(error, status)); + BOOST_LOG_TRIVIAL(error) << boost::format("Octoprint: Error uploading file: %1%, HTTP %2%, body: `%3%`") % error % status % body; + auto errormsg = wxString::Format("%s: %s", errortitle, format_error(body, error, status)); GUI::show_error(&progress_dialog, std::move(errormsg)); res = false; }) @@ -102,24 +174,22 @@ std::string OctoPrint::make_url(const std::string &path) const { if (host.find("http://") == 0 || host.find("https://") == 0) { if (host.back() == '/') { - return std::move((boost::format("%1%%2%") % host % path).str()); + return (boost::format("%1%%2%") % host % path).str(); } else { - return std::move((boost::format("%1%/%2%") % host % path).str()); + return (boost::format("%1%/%2%") % host % path).str(); } } else { - return std::move((boost::format("http://%1%/%2%") % host % path).str()); + return (boost::format("http://%1%/%2%") % host % path).str(); } } -wxString OctoPrint::format_error(std::string error, unsigned status) +wxString OctoPrint::format_error(const std::string &body, const std::string &error, unsigned status) { - const wxString wxerror = error; - if (status != 0) { - return wxString::Format("HTTP %u: %s", status, - (status == 401 ? _(L("Invalid API key")) : wxerror)); + auto wxbody = wxString::FromUTF8(body.data()); + return wxString::Format("HTTP %u: %s", status, wxbody); } else { - return std::move(wxerror); + return wxString::FromUTF8(error.data()); } } diff --git a/xs/src/slic3r/Utils/OctoPrint.hpp b/xs/src/slic3r/Utils/OctoPrint.hpp index 744b4fcc1..1e2098ae3 100644 --- a/xs/src/slic3r/Utils/OctoPrint.hpp +++ b/xs/src/slic3r/Utils/OctoPrint.hpp @@ -17,7 +17,8 @@ public: OctoPrint(DynamicPrintConfig *config); bool test(wxString &curl_msg) const; - bool send_gcode(const std::string &filename, bool print = false) const; + // Send gcode file to octoprint, filename is expected to be in UTF-8 + bool send_gcode(const std::string &filename) const; private: std::string host; std::string apikey; @@ -25,7 +26,7 @@ private: void set_auth(Http &http) const; std::string make_url(const std::string &path) const; - static wxString format_error(std::string error, unsigned status); + static wxString format_error(const std::string &body, const std::string &error, unsigned status); }; diff --git a/xs/src/slic3r/Utils/PresetUpdater.cpp b/xs/src/slic3r/Utils/PresetUpdater.cpp index f34fc4c19..1ce814b89 100644 --- a/xs/src/slic3r/Utils/PresetUpdater.cpp +++ b/xs/src/slic3r/Utils/PresetUpdater.cpp @@ -259,7 +259,7 @@ void PresetUpdater::priv::sync_config(const std::set<VendorProfile> vendors) con } const auto recommended = recommended_it->config_version; - BOOST_LOG_TRIVIAL(debug) << boost::format("New index for vendor: %1%: current version: %2%, recommended version: %3%") + BOOST_LOG_TRIVIAL(debug) << boost::format("Got index for vendor: %1%: current version: %2%, recommended version: %3%") % vendor.name % vendor.config_version.to_string() % recommended.to_string(); @@ -352,20 +352,25 @@ Updates PresetUpdater::priv::get_config_updates() const continue; } - auto path_in_cache = cache_path / (idx.vendor() + ".ini"); - if (! fs::exists(path_in_cache)) { - BOOST_LOG_TRIVIAL(warning) << "Index indicates update, but new bundle not found in cache: " << path_in_cache.string(); - continue; + auto path_src = cache_path / (idx.vendor() + ".ini"); + if (! fs::exists(path_src)) { + auto path_in_rsrc = rsrc_path / (idx.vendor() + ".ini"); + if (! fs::exists(path_in_rsrc)) { + BOOST_LOG_TRIVIAL(warning) << boost::format("Index for vendor %1% indicates update, but bundle found in neither cache nor resources") + % idx.vendor();; + continue; + } else { + path_src = std::move(path_in_rsrc); + } } - const auto cached_vp = VendorProfile::from_ini(path_in_cache, false); - if (cached_vp.config_version == recommended->config_version) { - updates.updates.emplace_back(std::move(path_in_cache), std::move(bundle_path), *recommended); + const auto new_vp = VendorProfile::from_ini(path_src, false); + if (new_vp.config_version == recommended->config_version) { + updates.updates.emplace_back(std::move(path_src), std::move(bundle_path), *recommended); } else { - BOOST_LOG_TRIVIAL(warning) << boost::format("Vendor: %1%: Index indicates update (%2%) but cached bundle has a different version: %3%") + BOOST_LOG_TRIVIAL(warning) << boost::format("Index for vendor %1% indicates update (%2%) but the new bundle was found neither in cache nor resources") % idx.vendor() - % recommended->config_version.to_string() - % cached_vp.config_version.to_string(); + % recommended->config_version.to_string(); } } } @@ -532,15 +537,15 @@ bool PresetUpdater::config_update() const incompats_map.emplace(std::make_pair(std::move(vendor), std::move(restrictions))); } + p->had_config_update = true; // This needs to be done before a dialog is shown because of OnIdle() + CallAfter() in Perl + GUI::MsgDataIncompatible dlg(std::move(incompats_map)); const auto res = dlg.ShowModal(); if (res == wxID_REPLACE) { BOOST_LOG_TRIVIAL(info) << "User wants to re-configure..."; p->perform_updates(std::move(updates)); GUI::ConfigWizard wizard(nullptr, GUI::ConfigWizard::RR_DATA_INCOMPAT); - if (wizard.run(GUI::get_preset_bundle(), this)) { - p->had_config_update = true; - } else { + if (! wizard.run(GUI::get_preset_bundle(), this)) { return false; } } else { @@ -561,6 +566,8 @@ bool PresetUpdater::config_update() const updates_map.emplace(std::make_pair(std::move(vendor), std::move(ver_str))); } + p->had_config_update = true; // Ditto, see above + GUI::MsgUpdateConfig dlg(std::move(updates_map)); const auto res = dlg.ShowModal(); @@ -576,8 +583,6 @@ bool PresetUpdater::config_update() const } else { BOOST_LOG_TRIVIAL(info) << "User refused the update"; } - - p->had_config_update = true; } else { BOOST_LOG_TRIVIAL(info) << "No configuration updates available."; } diff --git a/xs/xsp/BoundingBox.xsp b/xs/xsp/BoundingBox.xsp index df8e6baea..ebeb17822 100644 --- a/xs/xsp/BoundingBox.xsp +++ b/xs/xsp/BoundingBox.xsp @@ -29,6 +29,10 @@ int x_max() %code{% RETVAL = THIS->max.x; %}; int y_min() %code{% RETVAL = THIS->min.y; %}; int y_max() %code{% RETVAL = THIS->max.y; %}; + void set_x_min(double val) %code{% THIS->min.x = val; %}; + void set_x_max(double val) %code{% THIS->max.x = val; %}; + void set_y_min(double val) %code{% THIS->min.y = val; %}; + void set_y_max(double val) %code{% THIS->max.y = val; %}; std::string serialize() %code{% char buf[2048]; sprintf(buf, "%ld,%ld;%ld,%ld", THIS->min.x, THIS->min.y, THIS->max.x, THIS->max.y); RETVAL = buf; %}; bool defined() %code{% RETVAL = THIS->defined; %}; diff --git a/xs/xsp/GCode.xsp b/xs/xsp/GCode.xsp index 36b43271c..c1856ccf7 100644 --- a/xs/xsp/GCode.xsp +++ b/xs/xsp/GCode.xsp @@ -31,7 +31,7 @@ try { THIS->do_export(print, path, preview_data); } catch (std::exception& e) { - croak(e.what()); + croak("%s\n", e.what()); } %}; diff --git a/xs/xsp/GUI.xsp b/xs/xsp/GUI.xsp index 50fffc545..af0612f19 100644 --- a/xs/xsp/GUI.xsp +++ b/xs/xsp/GUI.xsp @@ -4,6 +4,7 @@ #include <xsinit.h> #include "slic3r/GUI/GUI.hpp" #include "slic3r/Utils/ASCIIFolding.hpp" +#include "slic3r/Utils/FixModelByWin10.hpp" #include "slic3r/Utils/Serial.hpp" %} @@ -28,6 +29,9 @@ bool debugged() void break_to_debugger() %code{% Slic3r::GUI::break_to_debugger(); %}; +bool is_windows10() + %code{% RETVAL=Slic3r::is_windows10(); %}; + void set_wxapp(SV *ui) %code%{ Slic3r::GUI::set_wxapp((wxApp*)wxPli_sv_2_object(aTHX_ ui, "Wx::App")); %}; @@ -94,3 +98,6 @@ int get_export_option(SV *ui) void desktop_open_datadir_folder() %code%{ Slic3r::GUI::desktop_open_datadir_folder(); %}; + +void fix_model_by_win10_sdk_gui(ModelObject *model_object_src, Print *print, Model *model_dst) + %code%{ Slic3r::fix_model_by_win10_sdk_gui(*model_object_src, *print, *model_dst); %}; diff --git a/xs/xsp/GUI_3DScene.xsp b/xs/xsp/GUI_3DScene.xsp index 6a907291e..48bd30cd1 100644 --- a/xs/xsp/GUI_3DScene.xsp +++ b/xs/xsp/GUI_3DScene.xsp @@ -8,7 +8,8 @@ GLShader(); ~GLShader(); - bool load(const char *fragment_shader, const char *vertex_shader); + bool load_from_text(const char *fragment_shader, const char *vertex_shader); + bool load_from_file(const char *fragment_shader, const char *vertex_shader); void release(); int get_attrib_location(const char *name) const; @@ -92,9 +93,6 @@ int count() %code{% RETVAL = THIS->volumes.size(); %}; - std::vector<double> get_current_print_zs(bool active_only) - %code{% RETVAL = THIS->get_current_print_zs(active_only); %}; - void set_range(double low, double high); void render_VBOs() const; @@ -155,11 +153,456 @@ GLVolumeCollection::arrayref() %package{Slic3r::GUI::_3DScene}; %{ +std::string +get_gl_info(format_as_html, extensions) + bool format_as_html; + bool extensions; + CODE: + RETVAL = _3DScene::get_gl_info(format_as_html, extensions); + OUTPUT: + RETVAL + +bool +use_VBOs() + CODE: + RETVAL = _3DScene::use_VBOs(); + OUTPUT: + RETVAL + +bool +add_canvas(canvas) + SV *canvas; + CODE: + RETVAL = _3DScene::add_canvas((wxGLCanvas*)wxPli_sv_2_object(aTHX_ canvas, "Wx::GLCanvas")); + OUTPUT: + RETVAL + +bool +remove_canvas(canvas) + SV *canvas; + CODE: + RETVAL = _3DScene::remove_canvas((wxGLCanvas*)wxPli_sv_2_object(aTHX_ canvas, "Wx::GLCanvas")); + OUTPUT: + RETVAL void -_glew_init() +remove_all_canvases() CODE: - _3DScene::_glew_init(); + _3DScene::remove_all_canvases(); + +void +set_as_dirty(canvas) + SV *canvas; + CODE: + _3DScene::set_as_dirty((wxGLCanvas*)wxPli_sv_2_object(aTHX_ canvas, "Wx::GLCanvas")); + +unsigned int +get_volumes_count(canvas) + SV *canvas; + CODE: + RETVAL = _3DScene::get_volumes_count((wxGLCanvas*)wxPli_sv_2_object(aTHX_ canvas, "Wx::GLCanvas")); + OUTPUT: + RETVAL + +void +reset_volumes(canvas) + SV *canvas; + CODE: + _3DScene::reset_volumes((wxGLCanvas*)wxPli_sv_2_object(aTHX_ canvas, "Wx::GLCanvas")); + +void +deselect_volumes(canvas) + SV *canvas; + CODE: + _3DScene::deselect_volumes((wxGLCanvas*)wxPli_sv_2_object(aTHX_ canvas, "Wx::GLCanvas")); + +void +select_volume(canvas, id) + SV *canvas; + unsigned int id; + CODE: + _3DScene::select_volume((wxGLCanvas*)wxPli_sv_2_object(aTHX_ canvas, "Wx::GLCanvas"), id); + +void +update_volumes_selection(canvas, selections) + SV *canvas; + std::vector<int> selections; + CODE: + _3DScene::update_volumes_selection((wxGLCanvas*)wxPli_sv_2_object(aTHX_ canvas, "Wx::GLCanvas"), selections); + +bool +check_volumes_outside_state(canvas, config) + SV *canvas; + DynamicPrintConfig *config; + CODE: + RETVAL = _3DScene::check_volumes_outside_state((wxGLCanvas*)wxPli_sv_2_object(aTHX_ canvas, "Wx::GLCanvas"), config); + OUTPUT: + RETVAL + +bool +move_volume_up(canvas, id) + SV *canvas; + unsigned int id; + CODE: + RETVAL = _3DScene::move_volume_up((wxGLCanvas*)wxPli_sv_2_object(aTHX_ canvas, "Wx::GLCanvas"), id); + OUTPUT: + RETVAL + +bool +move_volume_down(canvas, id) + SV *canvas; + unsigned int id; + CODE: + RETVAL = _3DScene::move_volume_down((wxGLCanvas*)wxPli_sv_2_object(aTHX_ canvas, "Wx::GLCanvas"), id); + OUTPUT: + RETVAL + +void +set_objects_selections(canvas, selections) + SV *canvas; + std::vector<int> selections; + CODE: + _3DScene::set_objects_selections((wxGLCanvas*)wxPli_sv_2_object(aTHX_ canvas, "Wx::GLCanvas"), selections); + +void +set_config(canvas, config) + SV *canvas; + DynamicPrintConfig *config; + CODE: + _3DScene::set_config((wxGLCanvas*)wxPli_sv_2_object(aTHX_ canvas, "Wx::GLCanvas"), config); + +void +set_print(canvas, print) + SV *canvas; + Print *print; + CODE: + _3DScene::set_print((wxGLCanvas*)wxPli_sv_2_object(aTHX_ canvas, "Wx::GLCanvas"), print); + +void +set_model(canvas, model) + SV *canvas; + Model *model; + CODE: + _3DScene::set_model((wxGLCanvas*)wxPli_sv_2_object(aTHX_ canvas, "Wx::GLCanvas"), model); + +void +set_bed_shape(canvas, shape) + SV *canvas; + Pointfs shape; + CODE: + _3DScene::set_bed_shape((wxGLCanvas*)wxPli_sv_2_object(aTHX_ canvas, "Wx::GLCanvas"), shape); + +void +set_auto_bed_shape(canvas) + SV *canvas; + CODE: + _3DScene::set_auto_bed_shape((wxGLCanvas*)wxPli_sv_2_object(aTHX_ canvas, "Wx::GLCanvas")); + +Clone<BoundingBoxf3> +get_volumes_bounding_box(canvas) + SV *canvas; + CODE: + RETVAL = _3DScene::get_volumes_bounding_box((wxGLCanvas*)wxPli_sv_2_object(aTHX_ canvas, "Wx::GLCanvas")); + OUTPUT: + RETVAL + +void +set_axes_length(canvas, length) + SV *canvas; + float length; + CODE: + _3DScene::set_axes_length((wxGLCanvas*)wxPli_sv_2_object(aTHX_ canvas, "Wx::GLCanvas"), length); + +void +set_cutting_plane(canvas, z, polygons) + SV *canvas; + float z; + ExPolygons polygons; + CODE: + _3DScene::set_cutting_plane((wxGLCanvas*)wxPli_sv_2_object(aTHX_ canvas, "Wx::GLCanvas"), z, polygons); + +void +set_color_by(canvas, value) + SV *canvas; + std::string value; + CODE: + _3DScene::set_color_by((wxGLCanvas*)wxPli_sv_2_object(aTHX_ canvas, "Wx::GLCanvas"), value); + +void +set_select_by(canvas, value) + SV *canvas; + std::string value; + CODE: + _3DScene::set_select_by((wxGLCanvas*)wxPli_sv_2_object(aTHX_ canvas, "Wx::GLCanvas"), value); + +void +set_drag_by(canvas, value) + SV *canvas; + std::string value; + CODE: + _3DScene::set_drag_by((wxGLCanvas*)wxPli_sv_2_object(aTHX_ canvas, "Wx::GLCanvas"), value); + +bool +is_layers_editing_enabled(canvas) + SV *canvas; + CODE: + RETVAL = _3DScene::is_layers_editing_enabled((wxGLCanvas*)wxPli_sv_2_object(aTHX_ canvas, "Wx::GLCanvas")); + OUTPUT: + RETVAL + +bool +is_layers_editing_allowed(canvas) + SV *canvas; + CODE: + RETVAL = _3DScene::is_layers_editing_allowed((wxGLCanvas*)wxPli_sv_2_object(aTHX_ canvas, "Wx::GLCanvas")); + OUTPUT: + RETVAL + +bool +is_shader_enabled(canvas) + SV *canvas; + CODE: + RETVAL = _3DScene::is_shader_enabled((wxGLCanvas*)wxPli_sv_2_object(aTHX_ canvas, "Wx::GLCanvas")); + OUTPUT: + RETVAL + +bool +is_reload_delayed(canvas) + SV *canvas; + CODE: + RETVAL = _3DScene::is_reload_delayed((wxGLCanvas*)wxPli_sv_2_object(aTHX_ canvas, "Wx::GLCanvas")); + OUTPUT: + RETVAL + +void +enable_layers_editing(canvas, enable) + SV *canvas; + bool enable; + CODE: + _3DScene::enable_layers_editing((wxGLCanvas*)wxPli_sv_2_object(aTHX_ canvas, "Wx::GLCanvas"), enable); + +void +enable_warning_texture(canvas, enable) + SV *canvas; + bool enable; + CODE: + _3DScene::enable_warning_texture((wxGLCanvas*)wxPli_sv_2_object(aTHX_ canvas, "Wx::GLCanvas"), enable); + +void +enable_legend_texture(canvas, enable) + SV *canvas; + bool enable; + CODE: + _3DScene::enable_legend_texture((wxGLCanvas*)wxPli_sv_2_object(aTHX_ canvas, "Wx::GLCanvas"), enable); + +void +enable_picking(canvas, enable) + SV *canvas; + bool enable; + CODE: + _3DScene::enable_picking((wxGLCanvas*)wxPli_sv_2_object(aTHX_ canvas, "Wx::GLCanvas"), enable); + +void +enable_moving(canvas, enable) + SV *canvas; + bool enable; + CODE: + _3DScene::enable_moving((wxGLCanvas*)wxPli_sv_2_object(aTHX_ canvas, "Wx::GLCanvas"), enable); + +void +enable_gizmos(canvas, enable) + SV *canvas; + bool enable; + CODE: + _3DScene::enable_gizmos((wxGLCanvas*)wxPli_sv_2_object(aTHX_ canvas, "Wx::GLCanvas"), enable); + +void +enable_shader(canvas, enable) + SV *canvas; + bool enable; + CODE: + _3DScene::enable_shader((wxGLCanvas*)wxPli_sv_2_object(aTHX_ canvas, "Wx::GLCanvas"), enable); + +void +enable_force_zoom_to_bed(canvas, enable) + SV *canvas; + bool enable; + CODE: + _3DScene::enable_force_zoom_to_bed((wxGLCanvas*)wxPli_sv_2_object(aTHX_ canvas, "Wx::GLCanvas"), enable); + +void +allow_multisample(canvas, allow) + SV *canvas; + bool allow; + CODE: + _3DScene::allow_multisample((wxGLCanvas*)wxPli_sv_2_object(aTHX_ canvas, "Wx::GLCanvas"), allow); + +void +zoom_to_bed(canvas) + SV *canvas; + CODE: + _3DScene::zoom_to_bed((wxGLCanvas*)wxPli_sv_2_object(aTHX_ canvas, "Wx::GLCanvas")); + +void +zoom_to_volumes(canvas) + SV *canvas; + CODE: + _3DScene::zoom_to_volumes((wxGLCanvas*)wxPli_sv_2_object(aTHX_ canvas, "Wx::GLCanvas")); + +void +select_view(canvas, direction) + SV *canvas; + std::string direction; + CODE: + _3DScene::select_view((wxGLCanvas*)wxPli_sv_2_object(aTHX_ canvas, "Wx::GLCanvas"), direction); + +void +set_viewport_from_scene(canvas, other) + SV *canvas; + SV *other; + CODE: + _3DScene::set_viewport_from_scene((wxGLCanvas*)wxPli_sv_2_object(aTHX_ canvas, "Wx::GLCanvas"), (wxGLCanvas*)wxPli_sv_2_object(aTHX_ other, "Wx::GLCanvas")); + +void +update_volumes_colors_by_extruder(canvas) + SV *canvas; + CODE: + _3DScene::update_volumes_colors_by_extruder((wxGLCanvas*)wxPli_sv_2_object(aTHX_ canvas, "Wx::GLCanvas")); + +void +render(canvas) + SV *canvas; + CODE: + _3DScene::render((wxGLCanvas*)wxPli_sv_2_object(aTHX_ canvas, "Wx::GLCanvas")); + +std::vector<double> +get_current_print_zs(canvas, active_only) + SV *canvas; + bool active_only; + CODE: + RETVAL = _3DScene::get_current_print_zs((wxGLCanvas*)wxPli_sv_2_object(aTHX_ canvas, "Wx::GLCanvas"), active_only); + OUTPUT: + RETVAL + +void +set_toolpaths_range(canvas, low, high) + SV *canvas; + double low; + double high; + CODE: + _3DScene::set_toolpaths_range((wxGLCanvas*)wxPli_sv_2_object(aTHX_ canvas, "Wx::GLCanvas"), low, high); + +void +register_on_viewport_changed_callback(canvas, callback) + SV *canvas; + SV *callback; + CODE: + _3DScene::register_on_viewport_changed_callback((wxGLCanvas*)wxPli_sv_2_object(aTHX_ canvas, "Wx::GLCanvas"), (void*)callback); + +void +register_on_double_click_callback(canvas, callback) + SV *canvas; + SV *callback; + CODE: + _3DScene::register_on_double_click_callback((wxGLCanvas*)wxPli_sv_2_object(aTHX_ canvas, "Wx::GLCanvas"), (void*)callback); + +void +register_on_right_click_callback(canvas, callback) + SV *canvas; + SV *callback; + CODE: + _3DScene::register_on_right_click_callback((wxGLCanvas*)wxPli_sv_2_object(aTHX_ canvas, "Wx::GLCanvas"), (void*)callback); + +void +register_on_select_object_callback(canvas, callback) + SV *canvas; + SV *callback; + CODE: + _3DScene::register_on_select_object_callback((wxGLCanvas*)wxPli_sv_2_object(aTHX_ canvas, "Wx::GLCanvas"), (void*)callback); + +void +register_on_model_update_callback(canvas, callback) + SV *canvas; + SV *callback; + CODE: + _3DScene::register_on_model_update_callback((wxGLCanvas*)wxPli_sv_2_object(aTHX_ canvas, "Wx::GLCanvas"), (void*)callback); + +void +register_on_remove_object_callback(canvas, callback) + SV *canvas; + SV *callback; + CODE: + _3DScene::register_on_remove_object_callback((wxGLCanvas*)wxPli_sv_2_object(aTHX_ canvas, "Wx::GLCanvas"), (void*)callback); + +void +register_on_arrange_callback(canvas, callback) + SV *canvas; + SV *callback; + CODE: + _3DScene::register_on_arrange_callback((wxGLCanvas*)wxPli_sv_2_object(aTHX_ canvas, "Wx::GLCanvas"), (void*)callback); + +void +register_on_rotate_object_left_callback(canvas, callback) + SV *canvas; + SV *callback; + CODE: + _3DScene::register_on_rotate_object_left_callback((wxGLCanvas*)wxPli_sv_2_object(aTHX_ canvas, "Wx::GLCanvas"), (void*)callback); + +void +register_on_rotate_object_right_callback(canvas, callback) + SV *canvas; + SV *callback; + CODE: + _3DScene::register_on_rotate_object_right_callback((wxGLCanvas*)wxPli_sv_2_object(aTHX_ canvas, "Wx::GLCanvas"), (void*)callback); + +void +register_on_scale_object_uniformly_callback(canvas, callback) + SV *canvas; + SV *callback; + CODE: + _3DScene::register_on_scale_object_uniformly_callback((wxGLCanvas*)wxPli_sv_2_object(aTHX_ canvas, "Wx::GLCanvas"), (void*)callback); + +void +register_on_increase_objects_callback(canvas, callback) + SV *canvas; + SV *callback; + CODE: + _3DScene::register_on_increase_objects_callback((wxGLCanvas*)wxPli_sv_2_object(aTHX_ canvas, "Wx::GLCanvas"), (void*)callback); + +void +register_on_decrease_objects_callback(canvas, callback) + SV *canvas; + SV *callback; + CODE: + _3DScene::register_on_decrease_objects_callback((wxGLCanvas*)wxPli_sv_2_object(aTHX_ canvas, "Wx::GLCanvas"), (void*)callback); + +void +register_on_instance_moved_callback(canvas, callback) + SV *canvas; + SV *callback; + CODE: + _3DScene::register_on_instance_moved_callback((wxGLCanvas*)wxPli_sv_2_object(aTHX_ canvas, "Wx::GLCanvas"), (void*)callback); + +void +register_on_wipe_tower_moved_callback(canvas, callback) + SV *canvas; + SV *callback; + CODE: + _3DScene::register_on_wipe_tower_moved_callback((wxGLCanvas*)wxPli_sv_2_object(aTHX_ canvas, "Wx::GLCanvas"), (void*)callback); + +void +register_on_enable_action_buttons_callback(canvas, callback) + SV *canvas; + SV *callback; + CODE: + _3DScene::register_on_enable_action_buttons_callback((wxGLCanvas*)wxPli_sv_2_object(aTHX_ canvas, "Wx::GLCanvas"), (void*)callback); + +void +register_on_gizmo_scale_uniformly_callback(canvas, callback) + SV *canvas; + SV *callback; + CODE: + _3DScene::register_on_gizmo_scale_uniformly_callback((wxGLCanvas*)wxPli_sv_2_object(aTHX_ canvas, "Wx::GLCanvas"), (void*)callback); unsigned int finalize_legend_texture() @@ -218,41 +661,61 @@ reset_warning_texture() CODE: _3DScene::reset_warning_texture(); +std::vector<int> +load_model_object(canvas, model_object, obj_idx, instance_idxs) + SV *canvas; + ModelObject *model_object; + int obj_idx; + std::vector<int> instance_idxs; + CODE: + RETVAL = _3DScene::load_object((wxGLCanvas*)wxPli_sv_2_object(aTHX_ canvas, "Wx::GLCanvas"), model_object, obj_idx, instance_idxs); + OUTPUT: + RETVAL + +std::vector<int> +load_model(canvas, model, obj_idx) + SV *canvas; + Model *model; + int obj_idx; + CODE: + RETVAL = _3DScene::load_object((wxGLCanvas*)wxPli_sv_2_object(aTHX_ canvas, "Wx::GLCanvas"), model, obj_idx); + OUTPUT: + RETVAL + void -_load_print_toolpaths(print, volumes, tool_colors, use_VBOs) - Print *print; - GLVolumeCollection *volumes; - std::vector<std::string> tool_colors; - int use_VBOs; +reload_scene(canvas, force) + SV *canvas; + bool 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(print, volumes, tool_colors, use_VBOs != 0); + _3DScene::load_print_toolpaths((wxGLCanvas*)wxPli_sv_2_object(aTHX_ canvas, "Wx::GLCanvas")); void -_load_print_object_toolpaths(print_object, volumes, tool_colors, use_VBOs) - PrintObject *print_object; - GLVolumeCollection *volumes; +load_print_object_toolpaths(canvas, print_object, tool_colors) + SV *canvas; + PrintObject *print_object; std::vector<std::string> tool_colors; - int use_VBOs; CODE: - _3DScene::_load_print_object_toolpaths(print_object, volumes, tool_colors, use_VBOs != 0); + _3DScene::load_print_object_toolpaths((wxGLCanvas*)wxPli_sv_2_object(aTHX_ canvas, "Wx::GLCanvas"), print_object, tool_colors); void -_load_wipe_tower_toolpaths(print, volumes, tool_colors, use_VBOs) - Print *print; - GLVolumeCollection *volumes; +load_wipe_tower_toolpaths(canvas, tool_colors) + SV *canvas; std::vector<std::string> tool_colors; - int use_VBOs; CODE: - _3DScene::_load_wipe_tower_toolpaths(print, volumes, tool_colors, use_VBOs != 0); + _3DScene::load_wipe_tower_toolpaths((wxGLCanvas*)wxPli_sv_2_object(aTHX_ canvas, "Wx::GLCanvas"), tool_colors); void -load_gcode_preview(print, preview_data, volumes, str_tool_colors, use_VBOs) - Print *print; +load_gcode_preview(canvas, preview_data, str_tool_colors) + SV *canvas; GCodePreviewData *preview_data; - GLVolumeCollection *volumes; std::vector<std::string> str_tool_colors; - int use_VBOs; CODE: - _3DScene::load_gcode_preview(print, preview_data, volumes, str_tool_colors, use_VBOs != 0); + _3DScene::load_gcode_preview((wxGLCanvas*)wxPli_sv_2_object(aTHX_ canvas, "Wx::GLCanvas"), preview_data, str_tool_colors); %} diff --git a/xs/xsp/Print.xsp b/xs/xsp/Print.xsp index ef9c5345f..b53b5e82d 100644 --- a/xs/xsp/Print.xsp +++ b/xs/xsp/Print.xsp @@ -52,6 +52,8 @@ _constant() int region_count() %code%{ RETVAL = THIS->print()->regions.size(); %}; + int region_volumes_count() + %code%{ RETVAL = THIS->region_volumes.size(); %}; Ref<Print> print(); Ref<ModelObject> model_object(); Ref<StaticPrintConfig> config() @@ -119,15 +121,6 @@ _constant() RETVAL.push_back(slicing_params.layer_height); %}; - void adjust_layer_height_profile(coordf_t z, coordf_t layer_thickness_delta, coordf_t band_width, int action) - %code%{ - THIS->update_layer_height_profile(THIS->model_object()->layer_height_profile); - adjust_layer_height_profile( - THIS->slicing_parameters(), THIS->model_object()->layer_height_profile, z, layer_thickness_delta, band_width, LayerHeightEditActionType(action)); - THIS->model_object()->layer_height_profile_valid = true; - THIS->layer_height_profile_valid = false; - %}; - void reset_layer_height_profile(); int ptr() diff --git a/xs/xsp/Utils_OctoPrint.xsp b/xs/xsp/Utils_OctoPrint.xsp index 282a3055d..28610cb01 100644 --- a/xs/xsp/Utils_OctoPrint.xsp +++ b/xs/xsp/Utils_OctoPrint.xsp @@ -9,5 +9,5 @@ OctoPrint(DynamicPrintConfig *config); ~OctoPrint(); - bool send_gcode(std::string filename, bool print = false) const; + bool send_gcode(std::string filename) const; }; |