Welcome to mirror list, hosted at ThFree Co, Russian Federation.

github.com/prusa3d/PrusaSlicer.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
path: root/xs
diff options
context:
space:
mode:
Diffstat (limited to 'xs')
-rw-r--r--xs/CMakeLists.txt49
-rw-r--r--xs/lib/Slic3r/XS.pm3
-rw-r--r--xs/src/avrdude/arduino.c56
-rw-r--r--xs/src/avrdude/avrdude-slic3r.cpp48
-rw-r--r--xs/src/avrdude/avrdude-slic3r.hpp23
-rw-r--r--xs/src/avrdude/avrpart.c10
-rw-r--r--xs/src/avrdude/fileio.c69
-rw-r--r--xs/src/avrdude/libavrdude.h7
-rw-r--r--xs/src/avrdude/main.c4
-rw-r--r--xs/src/avrdude/ser_posix.c4
-rw-r--r--xs/src/avrdude/stk500.c123
-rw-r--r--xs/src/avrdude/stk500v2.c17
-rw-r--r--xs/src/avrdude/update.c37
-rw-r--r--xs/src/libslic3r/BoundingBox.cpp8
-rw-r--r--xs/src/libslic3r/BoundingBox.hpp1
-rw-r--r--xs/src/libslic3r/Format/3mf.cpp20
-rw-r--r--xs/src/libslic3r/Format/AMF.cpp20
-rw-r--r--xs/src/libslic3r/GCode/WipeTowerPrusaMM.cpp19
-rw-r--r--xs/src/libslic3r/Model.cpp5
-rw-r--r--xs/src/libslic3r/Model.hpp11
-rw-r--r--xs/src/libslic3r/Point.hpp5
-rw-r--r--xs/src/libslic3r/Polygon.cpp2
-rw-r--r--xs/src/libslic3r/Print.hpp2
-rw-r--r--xs/src/libslic3r/PrintObject.cpp9
-rw-r--r--xs/src/libslic3r/Utils.hpp11
-rw-r--r--xs/src/libslic3r/libslic3r.h2
-rw-r--r--xs/src/libslic3r/utils.cpp52
-rw-r--r--xs/src/slic3r/GUI/3DScene.cpp1241
-rw-r--r--xs/src/slic3r/GUI/3DScene.hpp184
-rw-r--r--xs/src/slic3r/GUI/ConfigWizard.cpp30
-rw-r--r--xs/src/slic3r/GUI/ConfigWizard_private.hpp1
-rw-r--r--xs/src/slic3r/GUI/Field.cpp18
-rw-r--r--xs/src/slic3r/GUI/FirmwareDialog.cpp191
-rw-r--r--xs/src/slic3r/GUI/GLCanvas3D.cpp4346
-rw-r--r--xs/src/slic3r/GUI/GLCanvas3D.hpp636
-rw-r--r--xs/src/slic3r/GUI/GLCanvas3DManager.cpp677
-rw-r--r--xs/src/slic3r/GUI/GLCanvas3DManager.hpp166
-rw-r--r--xs/src/slic3r/GUI/GLGizmo.cpp454
-rw-r--r--xs/src/slic3r/GUI/GLGizmo.hpp145
-rw-r--r--xs/src/slic3r/GUI/GLShader.cpp40
-rw-r--r--xs/src/slic3r/GUI/GLShader.hpp4
-rw-r--r--xs/src/slic3r/GUI/GLTexture.cpp190
-rw-r--r--xs/src/slic3r/GUI/GLTexture.hpp41
-rw-r--r--xs/src/slic3r/GUI/GUI.cpp11
-rw-r--r--xs/src/slic3r/GUI/MsgDialog.hpp2
-rw-r--r--xs/src/slic3r/GUI/OptionsGroup.cpp9
-rw-r--r--xs/src/slic3r/GUI/PresetBundle.cpp38
-rw-r--r--xs/src/slic3r/GUI/Tab.cpp155
-rw-r--r--xs/src/slic3r/GUI/Tab.hpp9
-rw-r--r--xs/src/slic3r/Utils/FixModelByWin10.cpp402
-rw-r--r--xs/src/slic3r/Utils/FixModelByWin10.hpp26
-rw-r--r--xs/src/slic3r/Utils/Http.cpp99
-rw-r--r--xs/src/slic3r/Utils/Http.hpp48
-rw-r--r--xs/src/slic3r/Utils/OctoPrint.cpp102
-rw-r--r--xs/src/slic3r/Utils/OctoPrint.hpp5
-rw-r--r--xs/src/slic3r/Utils/PresetUpdater.cpp37
-rw-r--r--xs/xsp/BoundingBox.xsp4
-rw-r--r--xs/xsp/GCode.xsp2
-rw-r--r--xs/xsp/GUI.xsp7
-rw-r--r--xs/xsp/GUI_3DScene.xsp517
-rw-r--r--xs/xsp/Print.xsp11
-rw-r--r--xs/xsp/Utils_OctoPrint.xsp2
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 &copy, GLVolume &volume)
+void _3DScene::extrusionentity_to_verts(const ExtrusionPath &extrusion_path, float print_z, const Point &copy, 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 &copy, GLVolume &volume)
+void _3DScene::extrusionentity_to_verts(const ExtrusionLoop &extrusion_loop, float print_z, const Point &copy, 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 &copy, GLVolume &volume)
+void _3DScene::extrusionentity_to_verts(const ExtrusionMultiPath &extrusion_multi_path, float print_z, const Point &copy, 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 &copy, GLVolume &volume);
-
-static inline void extrusionentity_to_verts(const ExtrusionEntityCollection &extrusion_entity_collection, float print_z, const Point &copy, GLVolume &volume)
+void _3DScene::extrusionentity_to_verts(const ExtrusionEntityCollection &extrusion_entity_collection, float print_z, const Point &copy, 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 &copy, GLVolume &volume)
+void _3DScene::extrusionentity_to_verts(const ExtrusionEntity *extrusion_entity, float print_z, const Point &copy, 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 &copy: *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 &copy : *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;
};