From f8bbfad1523e2630a7f34de969c02f45c0bd5e06 Mon Sep 17 00:00:00 2001 From: Vojtech Kral Date: Wed, 20 Jun 2018 14:08:41 +0200 Subject: [PATCH 01/14] avrdude: Line noise prevention on MK3 xflash boot --- xs/src/avrdude/arduino.c | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/xs/src/avrdude/arduino.c b/xs/src/avrdude/arduino.c index fc9f4571f..5a9cb465e 100644 --- a/xs/src/avrdude/arduino.c +++ b/xs/src/avrdude/arduino.c @@ -99,7 +99,7 @@ static int prusa_init_external_flash(PROGRAMMER * pgm) 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\n", progname); + avrdude_message(MSG_INFO, "%s: prusa_init_external_flash(): MK3 printer emitted incorrect start code: `%*s`\n", progname, recv_size, buffer); return -1; } @@ -116,7 +116,7 @@ static int prusa_init_external_flash(PROGRAMMER * pgm) 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 start code\n", progname); + avrdude_message(MSG_INFO, "%s: prusa_init_external_flash(): MK3 printer emitted incorrect cfm code: `%*s`\n", progname, recv_size, buffer); return -1; } @@ -140,6 +140,13 @@ 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 */ From b386f52accc85907c9c69a35e7948382f674333c Mon Sep 17 00:00:00 2001 From: Enrico Turri Date: Thu, 21 Jun 2018 09:54:43 +0200 Subject: [PATCH 02/14] Removed error dialog when texture file does not exist --- xs/src/slic3r/GUI/GLTexture.cpp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/xs/src/slic3r/GUI/GLTexture.cpp b/xs/src/slic3r/GUI/GLTexture.cpp index 593362e54..924920bd8 100644 --- a/xs/src/slic3r/GUI/GLTexture.cpp +++ b/xs/src/slic3r/GUI/GLTexture.cpp @@ -4,6 +4,8 @@ #include +#include + #include #include @@ -27,6 +29,9 @@ bool GLTexture::load_from_file(const std::string& filename, bool generate_mipmap { reset(); + if (!boost::filesystem::exists(filename)) + return false; + // Load a PNG with an alpha channel. wxImage image; if (!image.LoadFile(filename, wxBITMAP_TYPE_PNG)) From 80e4155cbc46cf04e69a880880ed5c189cf4771e Mon Sep 17 00:00:00 2001 From: Enrico Turri Date: Thu, 21 Jun 2018 10:24:57 +0200 Subject: [PATCH 03/14] Bed textures selection using config inheritance --- xs/src/slic3r/GUI/GLCanvas3D.cpp | 25 ++++++++++++++++++------- 1 file changed, 18 insertions(+), 7 deletions(-) diff --git a/xs/src/slic3r/GUI/GLCanvas3D.cpp b/xs/src/slic3r/GUI/GLCanvas3D.cpp index f9c10017e..d3153bdb4 100644 --- a/xs/src/slic3r/GUI/GLCanvas3D.cpp +++ b/xs/src/slic3r/GUI/GLCanvas3D.cpp @@ -405,16 +405,27 @@ GLCanvas3D::Bed::EType GLCanvas3D::Bed::_detect_type() const const PresetBundle* bundle = get_preset_bundle(); if (bundle != nullptr) { - const Preset& curr = bundle->printers.get_selected_preset(); - if (curr.config.has("bed_shape") && _are_equal(m_shape, dynamic_cast(curr.config.option("bed_shape"))->values)) + const Preset* curr = &bundle->printers.get_selected_preset(); + while (curr != nullptr) { - if ((curr.vendor != nullptr) && (curr.vendor->name == "Prusa Research")) + if (curr->config.has("bed_shape") && _are_equal(m_shape, dynamic_cast(curr->config.option("bed_shape"))->values)) { - if (boost::contains(curr.name, "MK2")) - type = MK2; - else if (boost::contains(curr.name, "MK3")) - type = MK3; + 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); } } From a3a8333d203606d2c8f404e9e5e7957c6105d1db Mon Sep 17 00:00:00 2001 From: Enrico Turri Date: Thu, 21 Jun 2018 10:43:01 +0200 Subject: [PATCH 04/14] Force update when changing selected printer using the GUI --- lib/Slic3r/GUI/Plater.pm | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/lib/Slic3r/GUI/Plater.pm b/lib/Slic3r/GUI/Plater.pm index e15b8c34c..f5ef10e9c 100644 --- a/lib/Slic3r/GUI/Plater.pm +++ b/lib/Slic3r/GUI/Plater.pm @@ -53,7 +53,7 @@ sub new { bed_shape complete_objects extruder_clearance_radius skirts skirt_distance brim_width variable_layer_height serial_port serial_speed octoprint_host octoprint_apikey octoprint_cafile nozzle_diameter single_extruder_multi_material wipe_tower wipe_tower_x wipe_tower_y wipe_tower_width - wipe_tower_rotation_angle extruder_colour filament_colour max_print_height + wipe_tower_rotation_angle extruder_colour filament_colour max_print_height printer_model )]); # C++ Slic3r::Model with Perl extensions in Slic3r/Model.pm $self->{model} = Slic3r::Model->new; @@ -1886,6 +1886,9 @@ sub on_config_change { $self->{preview3D}->set_number_extruders(scalar(@{$extruder_colors})); } elsif ($opt_key eq 'max_print_height') { $update_scheduled = 1; + } elsif ($opt_key eq 'printer_model') { + # update to force bed selection (for texturing) + $update_scheduled = 1; } } From 1ba81655e2685c40b5c345684868e8d6d96cee2b Mon Sep 17 00:00:00 2001 From: Vojtech Kral Date: Wed, 23 May 2018 12:47:39 +0200 Subject: [PATCH 05/14] Octoprint: Add a dialog for setting the filename/path and a "print now" option cf. #880, #245, #55, #87 --- xs/src/slic3r/GUI/MsgDialog.hpp | 2 + xs/src/slic3r/Utils/Http.cpp | 39 +++++++++++---- xs/src/slic3r/Utils/Http.hpp | 35 ++++++++++--- xs/src/slic3r/Utils/OctoPrint.cpp | 81 +++++++++++++++++++++++++++++-- xs/src/slic3r/Utils/OctoPrint.hpp | 2 +- xs/xsp/Utils_OctoPrint.xsp | 2 +- 6 files changed, 140 insertions(+), 21 deletions(-) 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/Utils/Http.cpp b/xs/src/slic3r/Utils/Http.cpp index 0826284d8..adc8ac816 100644 --- a/xs/src/slic3r/Utils/Http.cpp +++ b/xs/src/slic3r/Utils/Http.cpp @@ -50,6 +50,9 @@ 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); + + void form_add_file(const char *name, const char *path, const char* filename); + std::string curl_error(CURLcode curlcode); std::string body_size_error(); void http_perform(); @@ -135,6 +138,26 @@ int Http::priv::xfercb_legacy(void *userp, double dltotal, double dlnow, double return xfercb(userp, dltotal, dlnow, ultotal, ulnow); } +void Http::priv::form_add_file(const char *name, const char *path, const char* filename) +{ + if (filename != nullptr) { + ::curl_formadd(&form, &form_end, + CURLFORM_COPYNAME, name, + CURLFORM_FILE, path, + CURLFORM_FILENAME, filename, + CURLFORM_CONTENTTYPE, "application/octet-stream", + CURLFORM_END + ); + } else { + ::curl_formadd(&form, &form_end, + CURLFORM_COPYNAME, name, + CURLFORM_FILE, path, + CURLFORM_CONTENTTYPE, "application/octet-stream", + CURLFORM_END + ); + } +} + std::string Http::priv::curl_error(CURLcode curlcode) { return (boost::format("%1% (%2%)") @@ -265,17 +288,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 std::string &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 std::string &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..2b8126334 100644 --- a/xs/src/slic3r/Utils/Http.hpp +++ b/xs/src/slic3r/Utils/Http.hpp @@ -16,11 +16,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) {} @@ -33,6 +33,8 @@ public: Http(Http &&other); + // 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 +43,42 @@ public: Http& operator=(const Http &) = delete; Http& operator=(Http &&) = delete; + // Sets a maximum size of the data that can be received. The default 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 + Http& form_add_file(const std::string &name, const std::string &path); + // Same as above except also override the file's filename with a custom one + Http& form_add_file(const std::string &name, const std::string &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..1794c6024 100644 --- a/xs/src/slic3r/Utils/OctoPrint.cpp +++ b/xs/src/slic3r/Utils/OctoPrint.cpp @@ -1,20 +1,66 @@ #include "OctoPrint.hpp" #include +#include #include +#include #include #include #include +#include +#include +#include +#include #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().string())), + 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(); + txt_filename->SetSelection(0, path.stem().size()); + + Fit(); + } + + fs::path filename() const { + // The buffer object that utf8_str() returns may just point to data owned by the source string + // so we need to copy the string in any case to be on the safe side. + return fs::path(txt_filename->GetValue().utf8_str().data()); + } + + 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 +73,39 @@ bool OctoPrint::test(wxString &msg) const // it is ok to refer to `msg` from within the closure bool res = true; - auto url = std::move(make_url("api/version")); + + BOOST_LOG_TRIVIAL(info) << boost::format("Octoprint: Get version at: %1%") % url; + auto http = Http::get(std::move(url)); set_auth(http); http.on_error([&](std::string, std::string error, unsigned status) { + BOOST_LOG_TRIVIAL(error) << boost::format("Octoprint: Error getting version: %1% (HTTP %2%)") % error % status; res = false; msg = format_error(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().string()); + 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,14 +122,26 @@ 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 + % url + % upload_filename + % upload_parent_path + % 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) { + BOOST_LOG_TRIVIAL(error) << boost::format("Octoprint: Error uploading file: %1% (HTTP %2%)") % error % status; auto errormsg = wxString::Format("%s: %s", errortitle, format_error(error, status)); GUI::show_error(&progress_dialog, std::move(errormsg)); res = false; diff --git a/xs/src/slic3r/Utils/OctoPrint.hpp b/xs/src/slic3r/Utils/OctoPrint.hpp index 744b4fcc1..e278c8141 100644 --- a/xs/src/slic3r/Utils/OctoPrint.hpp +++ b/xs/src/slic3r/Utils/OctoPrint.hpp @@ -17,7 +17,7 @@ public: OctoPrint(DynamicPrintConfig *config); bool test(wxString &curl_msg) const; - bool send_gcode(const std::string &filename, bool print = false) const; + bool send_gcode(const std::string &filename) const; private: std::string host; std::string apikey; 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; }; From 9ee10a877938e371b9200601c53b69b78886d015 Mon Sep 17 00:00:00 2001 From: Vojtech Kral Date: Tue, 12 Jun 2018 12:32:03 +0200 Subject: [PATCH 06/14] Octoprint: Fix unicode support --- xs/src/slic3r/Utils/Http.cpp | 72 +++++++++++++++++++++++-------- xs/src/slic3r/Utils/Http.hpp | 9 ++-- xs/src/slic3r/Utils/OctoPrint.cpp | 31 +++++++------ xs/src/slic3r/Utils/OctoPrint.hpp | 3 +- 4 files changed, 77 insertions(+), 38 deletions(-) diff --git a/xs/src/slic3r/Utils/Http.cpp b/xs/src/slic3r/Utils/Http.cpp index adc8ac816..7c4126633 100644 --- a/xs/src/slic3r/Utils/Http.cpp +++ b/xs/src/slic3r/Utils/Http.cpp @@ -3,13 +3,16 @@ #include #include #include -#include +#include +#include #include #include #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 form_files; size_t limit; bool cancel; @@ -50,8 +57,9 @@ 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 char *path, const char* filename); + 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(); @@ -138,21 +146,42 @@ int Http::priv::xfercb_legacy(void *userp, double dltotal, double dlnow, double return xfercb(userp, dltotal, dlnow, ultotal, ulnow); } -void Http::priv::form_add_file(const char *name, const char *path, const char* filename) +size_t Http::priv::form_file_read_cb(char *buffer, size_t size, size_t nitems, void *userp) { + auto stream = reinterpret_cast(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(); + } + + fs::ifstream stream(path, std::ios::in | std::ios::binary); + stream.seekg(0, std::ios::end); + size_t size = stream.tellg(); + stream.seekg(0); + form_files.push_back(std::move(stream)); + auto stream_ptr = &form_files.back(); + if (filename != nullptr) { ::curl_formadd(&form, &form_end, CURLFORM_COPYNAME, name, - CURLFORM_FILE, path, CURLFORM_FILENAME, filename, CURLFORM_CONTENTTYPE, "application/octet-stream", - CURLFORM_END - ); - } else { - ::curl_formadd(&form, &form_end, - CURLFORM_COPYNAME, name, - CURLFORM_FILE, path, - CURLFORM_CONTENTTYPE, "application/octet-stream", + CURLFORM_STREAM, static_cast(stream_ptr), + CURLFORM_CONTENTSLENGTH, static_cast(size), CURLFORM_END ); } @@ -177,6 +206,7 @@ void Http::priv::http_perform() ::curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L); ::curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, writecb); ::curl_easy_setopt(curl, CURLOPT_WRITEDATA, static_cast(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 @@ -206,14 +236,20 @@ void Http::priv::http_perform() 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", http_status); } + } } 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(), http_status); } } else { - if (errorfn) { errorfn(std::move(buffer), std::move(curl_error(res)), http_status); } + if (errorfn) { errorfn(std::move(buffer), curl_error(res), http_status); } }; } else { if (completefn) { @@ -288,13 +324,13 @@ 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 &path) +Http& Http::form_add_file(const std::string &name, const fs::path &path) { 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 std::string &path, const std::string &filename) +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 2b8126334..73656bf88 100644 --- a/xs/src/slic3r/Utils/Http.hpp +++ b/xs/src/slic3r/Utils/Http.hpp @@ -4,6 +4,7 @@ #include #include #include +#include namespace Slic3r { @@ -33,6 +34,8 @@ public: 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); @@ -55,10 +58,10 @@ public: Http& ca_file(const std::string &filename); // Add a HTTP multipart form field Http& form_add(const std::string &name, const std::string &contents); - // Add a HTTP multipart form file data contents - Http& form_add_file(const std::string &name, const std::string &path); + // 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 std::string &path, const std::string &filename); + 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); diff --git a/xs/src/slic3r/Utils/OctoPrint.cpp b/xs/src/slic3r/Utils/OctoPrint.cpp index 1794c6024..86049de16 100644 --- a/xs/src/slic3r/Utils/OctoPrint.cpp +++ b/xs/src/slic3r/Utils/OctoPrint.cpp @@ -31,7 +31,7 @@ struct SendDialog : public GUI::MsgDialog 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().string())), + 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."))); @@ -45,15 +45,14 @@ struct SendDialog : public GUI::MsgDialog btn_sizer->Add(CreateStdDialogButtonSizer(wxOK | wxCANCEL)); txt_filename->SetFocus(); - txt_filename->SetSelection(0, path.stem().size()); + wxString stem(path.stem().wstring()); + txt_filename->SetSelection(0, stem.Length()); Fit(); } fs::path filename() const { - // The buffer object that utf8_str() returns may just point to data owned by the source string - // so we need to copy the string in any case to be on the safe side. - return fs::path(txt_filename->GetValue().utf8_str().data()); + return fs::path(txt_filename->GetValue().wx_str()); } bool print() const { return box_print->GetValue(); } @@ -73,7 +72,7 @@ bool OctoPrint::test(wxString &msg) const // it is ok to refer to `msg` from within the closure bool res = true; - auto url = std::move(make_url("api/version")); + auto url = make_url("api/version"); BOOST_LOG_TRIVIAL(info) << boost::format("Octoprint: Get version at: %1%") % url; @@ -99,7 +98,7 @@ bool OctoPrint::send_gcode(const std::string &filename) const const auto errortitle = _(L("Error while uploading to the OctoPrint server")); fs::path filepath(filename); - SendDialog send_dialog(filepath.filename().string()); + SendDialog send_dialog(filepath.filename()); if (send_dialog.ShowModal() != wxID_OK) { return false; } const bool print = send_dialog.print(); @@ -125,10 +124,10 @@ bool OctoPrint::send_gcode(const std::string &filename) const 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 + % filepath.string() % url - % upload_filename - % upload_parent_path + % upload_filename.string() + % upload_parent_path.string() % print; auto http = Http::post(std::move(url)); @@ -175,24 +174,24 @@ 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 &error, unsigned status) { - const wxString wxerror = error; + auto wxerror = wxString::FromUTF8(error.data()); if (status != 0) { return wxString::Format("HTTP %u: %s", status, (status == 401 ? _(L("Invalid API key")) : wxerror)); } else { - return std::move(wxerror); + return wxerror; } } diff --git a/xs/src/slic3r/Utils/OctoPrint.hpp b/xs/src/slic3r/Utils/OctoPrint.hpp index e278c8141..a8599faa9 100644 --- a/xs/src/slic3r/Utils/OctoPrint.hpp +++ b/xs/src/slic3r/Utils/OctoPrint.hpp @@ -17,6 +17,7 @@ public: OctoPrint(DynamicPrintConfig *config); bool test(wxString &curl_msg) 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; @@ -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 &error, unsigned status); }; From 7cf39227076fbb04fd9b99d7313b8d265e34c47c Mon Sep 17 00:00:00 2001 From: Vojtech Kral Date: Thu, 21 Jun 2018 11:41:08 +0200 Subject: [PATCH 07/14] Http: Fix nowide fstream usage --- xs/src/slic3r/Utils/Http.cpp | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/xs/src/slic3r/Utils/Http.cpp b/xs/src/slic3r/Utils/Http.cpp index 7c4126633..47021d39f 100644 --- a/xs/src/slic3r/Utils/Http.cpp +++ b/xs/src/slic3r/Utils/Http.cpp @@ -41,7 +41,7 @@ struct Http::priv 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 form_files; + std::deque form_files; size_t limit; bool cancel; @@ -168,19 +168,18 @@ void Http::priv::form_add_file(const char *name, const fs::path &path, const cha filename = path.string().c_str(); } - fs::ifstream stream(path, std::ios::in | std::ios::binary); + 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); - form_files.push_back(std::move(stream)); - auto stream_ptr = &form_files.back(); if (filename != nullptr) { ::curl_formadd(&form, &form_end, CURLFORM_COPYNAME, name, CURLFORM_FILENAME, filename, CURLFORM_CONTENTTYPE, "application/octet-stream", - CURLFORM_STREAM, static_cast(stream_ptr), + CURLFORM_STREAM, static_cast(&stream), CURLFORM_CONTENTSLENGTH, static_cast(size), CURLFORM_END ); From e622401599f715b741ffc2ce4eeaa00fc1f222ec Mon Sep 17 00:00:00 2001 From: Lukas Matena Date: Thu, 21 Jun 2018 14:05:07 +0200 Subject: [PATCH 08/14] Wipe tower bugfix - the tower was printed incorrectly when the rotation angle was close to 90 degrees --- xs/src/libslic3r/GCode/WipeTowerPrusaMM.cpp | 19 ++++++++----------- 1 file changed, 8 insertions(+), 11 deletions(-) diff --git a/xs/src/libslic3r/GCode/WipeTowerPrusaMM.cpp b/xs/src/libslic3r/GCode/WipeTowerPrusaMM.cpp index 6486f2917..fbde83754 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(dx) > EPSILON) + m_gcode += set_format_X(rot.x); + + if (std::abs(dy) > 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"; From 2a03b90d2b8d772309409382f84f8b23947640e4 Mon Sep 17 00:00:00 2001 From: Enrico Turri Date: Fri, 22 Jun 2018 16:06:37 +0200 Subject: [PATCH 09/14] Forced render of 3D scene when changing tab in plater's notebook --- lib/Slic3r/GUI/Plater.pm | 4 ++++ xs/src/slic3r/GUI/3DScene.cpp | 5 +++++ xs/src/slic3r/GUI/3DScene.hpp | 1 + xs/src/slic3r/GUI/GLCanvas3D.cpp | 5 +++++ xs/src/slic3r/GUI/GLCanvas3D.hpp | 1 + xs/src/slic3r/GUI/GLCanvas3DManager.cpp | 7 +++++++ xs/src/slic3r/GUI/GLCanvas3DManager.hpp | 1 + xs/xsp/GUI_3DScene.xsp | 6 ++++++ 8 files changed, 30 insertions(+) diff --git a/lib/Slic3r/GUI/Plater.pm b/lib/Slic3r/GUI/Plater.pm index f5ef10e9c..f4ba05bdb 100644 --- a/lib/Slic3r/GUI/Plater.pm +++ b/lib/Slic3r/GUI/Plater.pm @@ -206,6 +206,8 @@ sub new { Slic3r::GUI::_3DScene::set_active($self->{canvas3D}, 0); Slic3r::GUI::_3DScene::enable_legend_texture($self->{preview3D}->canvas, 1); $self->{preview3D}->load_print(1); + # sets the canvas as dirty to force a render at the 1st idle event (wxWidgets IsShownOnScreen() is buggy and cannot be used reliably) + Slic3r::GUI::_3DScene::set_as_dirty($self->{preview3D}->canvas); } else { Slic3r::GUI::_3DScene::enable_legend_texture($self->{preview3D}->canvas, 0); } @@ -218,6 +220,8 @@ sub new { Slic3r::GUI::_3DScene::set_objects_selections($self->{canvas3D}, \@$selections); Slic3r::GUI::_3DScene::reload_scene($self->{canvas3D}, 1); } + # sets the canvas as dirty to force a render at the 1st idle event (wxWidgets IsShownOnScreen() is buggy and cannot be used reliably) + Slic3r::GUI::_3DScene::set_as_dirty($self->{canvas3D}); } else { $preview->OnActivate if $preview->can('OnActivate'); } diff --git a/xs/src/slic3r/GUI/3DScene.cpp b/xs/src/slic3r/GUI/3DScene.cpp index 1879b3082..a2a310f3c 100644 --- a/xs/src/slic3r/GUI/3DScene.cpp +++ b/xs/src/slic3r/GUI/3DScene.cpp @@ -1758,6 +1758,11 @@ void _3DScene::set_active(wxGLCanvas* canvas, bool active) s_canvas_mgr.set_active(canvas, active); } +void _3DScene::set_as_dirty(wxGLCanvas* canvas) +{ + s_canvas_mgr.set_as_dirty(canvas); +} + unsigned int _3DScene::get_volumes_count(wxGLCanvas* canvas) { return s_canvas_mgr.get_volumes_count(canvas); diff --git a/xs/src/slic3r/GUI/3DScene.hpp b/xs/src/slic3r/GUI/3DScene.hpp index c6a166397..c317dba24 100644 --- a/xs/src/slic3r/GUI/3DScene.hpp +++ b/xs/src/slic3r/GUI/3DScene.hpp @@ -517,6 +517,7 @@ public: static bool init(wxGLCanvas* canvas); static void set_active(wxGLCanvas* canvas, bool active); + static void set_as_dirty(wxGLCanvas* canvas); static unsigned int get_volumes_count(wxGLCanvas* canvas); static void reset_volumes(wxGLCanvas* canvas); diff --git a/xs/src/slic3r/GUI/GLCanvas3D.cpp b/xs/src/slic3r/GUI/GLCanvas3D.cpp index d3153bdb4..6f23af5a3 100644 --- a/xs/src/slic3r/GUI/GLCanvas3D.cpp +++ b/xs/src/slic3r/GUI/GLCanvas3D.cpp @@ -1536,6 +1536,11 @@ void GLCanvas3D::set_active(bool active) m_active = active; } +void GLCanvas3D::set_as_dirty() +{ + m_dirty = true; +} + unsigned int GLCanvas3D::get_volumes_count() const { return (unsigned int)m_volumes.volumes.size(); diff --git a/xs/src/slic3r/GUI/GLCanvas3D.hpp b/xs/src/slic3r/GUI/GLCanvas3D.hpp index c503d1845..18011f497 100644 --- a/xs/src/slic3r/GUI/GLCanvas3D.hpp +++ b/xs/src/slic3r/GUI/GLCanvas3D.hpp @@ -452,6 +452,7 @@ public: bool set_current(); void set_active(bool active); + void set_as_dirty(); unsigned int get_volumes_count() const; void reset_volumes(); diff --git a/xs/src/slic3r/GUI/GLCanvas3DManager.cpp b/xs/src/slic3r/GUI/GLCanvas3DManager.cpp index bdca73854..81efe4408 100644 --- a/xs/src/slic3r/GUI/GLCanvas3DManager.cpp +++ b/xs/src/slic3r/GUI/GLCanvas3DManager.cpp @@ -219,6 +219,13 @@ void GLCanvas3DManager::set_active(wxGLCanvas* canvas, bool active) it->second->set_active(active); } +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); diff --git a/xs/src/slic3r/GUI/GLCanvas3DManager.hpp b/xs/src/slic3r/GUI/GLCanvas3DManager.hpp index 3092925d3..8726eb0b1 100644 --- a/xs/src/slic3r/GUI/GLCanvas3DManager.hpp +++ b/xs/src/slic3r/GUI/GLCanvas3DManager.hpp @@ -70,6 +70,7 @@ public: bool init(wxGLCanvas* canvas); void set_active(wxGLCanvas* canvas, bool active); + void set_as_dirty(wxGLCanvas* canvas); unsigned int get_volumes_count(wxGLCanvas* canvas) const; void reset_volumes(wxGLCanvas* canvas); diff --git a/xs/xsp/GUI_3DScene.xsp b/xs/xsp/GUI_3DScene.xsp index 29f35293b..d46c06120 100644 --- a/xs/xsp/GUI_3DScene.xsp +++ b/xs/xsp/GUI_3DScene.xsp @@ -197,6 +197,12 @@ set_active(canvas, active) CODE: _3DScene::set_active((wxGLCanvas*)wxPli_sv_2_object(aTHX_ canvas, "Wx::GLCanvas"), active); +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; From 7d15a15e1443003681cb33753f2d01dbbc2a2666 Mon Sep 17 00:00:00 2001 From: Enrico Turri Date: Mon, 25 Jun 2018 08:48:36 +0200 Subject: [PATCH 10/14] Fix of #999 --- xs/src/slic3r/GUI/3DScene.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/xs/src/slic3r/GUI/3DScene.cpp b/xs/src/slic3r/GUI/3DScene.cpp index a2a310f3c..feae9312e 100644 --- a/xs/src/slic3r/GUI/3DScene.cpp +++ b/xs/src/slic3r/GUI/3DScene.cpp @@ -433,6 +433,7 @@ std::vector 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; From 362a9d8edad4ba02ea476f18428b2dd1a12f5fe6 Mon Sep 17 00:00:00 2001 From: YuSanka Date: Mon, 25 Jun 2018 11:40:40 +0200 Subject: [PATCH 11/14] Fixed bug #995 --- xs/src/slic3r/GUI/Tab.cpp | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/xs/src/slic3r/GUI/Tab.cpp b/xs/src/slic3r/GUI/Tab.cpp index 6eabc2f47..94f8cc3ea 100644 --- a/xs/src/slic3r/GUI/Tab.cpp +++ b/xs/src/slic3r/GUI/Tab.cpp @@ -2619,14 +2619,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."))); From 318212f47594df0b2b081c90ac00a144c64639b6 Mon Sep 17 00:00:00 2001 From: YuSanka Date: Mon, 25 Jun 2018 12:27:42 +0200 Subject: [PATCH 12/14] Fixed crashing-bug after language changing (detected under Debug) --- xs/src/slic3r/GUI/GUI.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/xs/src/slic3r/GUI/GUI.cpp b/xs/src/slic3r/GUI/GUI.cpp index e2f3925fc..c1f8adaf1 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 { @@ -378,6 +379,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); } From 515502e685e8eeafd1c7b45ca44d09abdecfb689 Mon Sep 17 00:00:00 2001 From: YuSanka Date: Mon, 25 Jun 2018 13:32:28 +0200 Subject: [PATCH 13/14] Fixed #994. Print double-type values according to "full" value instead of 2 digits after point --- xs/src/slic3r/GUI/Field.cpp | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) 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(){ From 40b327eb116392b80835d28ef43778212a7a14a1 Mon Sep 17 00:00:00 2001 From: Enrico Turri Date: Mon, 25 Jun 2018 15:17:13 +0200 Subject: [PATCH 14/14] Fix of interaction between opengl contexts and main page notebook --- lib/Slic3r/GUI/Plater.pm | 20 +++++----- lib/Slic3r/GUI/Plater/2D.pm | 6 ++- xs/src/slic3r/GUI/3DScene.cpp | 10 +++++ xs/src/slic3r/GUI/3DScene.hpp | 3 ++ xs/src/slic3r/GUI/GLCanvas3D.cpp | 50 ++++++++++++++++++------- xs/src/slic3r/GUI/GLCanvas3D.hpp | 2 +- xs/src/slic3r/GUI/GLCanvas3DManager.cpp | 29 ++++++++++++++ xs/src/slic3r/GUI/GLCanvas3DManager.hpp | 2 + xs/xsp/GUI_3DScene.xsp | 5 +++ 9 files changed, 99 insertions(+), 28 deletions(-) diff --git a/lib/Slic3r/GUI/Plater.pm b/lib/Slic3r/GUI/Plater.pm index f4ba05bdb..e6b2fca79 100644 --- a/lib/Slic3r/GUI/Plater.pm +++ b/lib/Slic3r/GUI/Plater.pm @@ -187,6 +187,7 @@ sub new { if ($Slic3r::GUI::have_OpenGL) { $self->{preview3D} = Slic3r::GUI::Plater::3DPreview->new($self->{preview_notebook}, $self->{print}, $self->{gcode_preview_data}, $self->{config}); Slic3r::GUI::_3DScene::set_active($self->{preview3D}->canvas, 0); + Slic3r::GUI::_3DScene::enable_legend_texture($self->{preview3D}->canvas, 1); Slic3r::GUI::_3DScene::register_on_viewport_changed_callback($self->{preview3D}->canvas, sub { Slic3r::GUI::_3DScene::set_viewport_from_scene($self->{canvas3D}, $self->{preview3D}->canvas); }); $self->{preview_notebook}->AddPage($self->{preview3D}, L('Preview')); $self->{preview3D_page_idx} = $self->{preview_notebook}->GetPageCount-1; @@ -200,19 +201,18 @@ sub new { EVT_NOTEBOOK_PAGE_CHANGED($self, $self->{preview_notebook}, sub { my $preview = $self->{preview_notebook}->GetCurrentPage; - if ($preview == $self->{preview3D}) - { + if (($preview != $self->{preview3D}) && ($preview != $self->{canvas3D})) { + Slic3r::GUI::_3DScene::set_active($self->{preview3D}->canvas, 0); + Slic3r::GUI::_3DScene::set_active($self->{canvas3D}, 0); + Slic3r::GUI::_3DScene::reset_current_canvas(); + $preview->OnActivate if $preview->can('OnActivate'); + } elsif ($preview == $self->{preview3D}) { Slic3r::GUI::_3DScene::set_active($self->{preview3D}->canvas, 1); Slic3r::GUI::_3DScene::set_active($self->{canvas3D}, 0); - Slic3r::GUI::_3DScene::enable_legend_texture($self->{preview3D}->canvas, 1); - $self->{preview3D}->load_print(1); + $self->{preview3D}->load_print; # sets the canvas as dirty to force a render at the 1st idle event (wxWidgets IsShownOnScreen() is buggy and cannot be used reliably) Slic3r::GUI::_3DScene::set_as_dirty($self->{preview3D}->canvas); - } else { - Slic3r::GUI::_3DScene::enable_legend_texture($self->{preview3D}->canvas, 0); - } - - if ($preview == $self->{canvas3D}) { + } elsif ($preview == $self->{canvas3D}) { Slic3r::GUI::_3DScene::set_active($self->{canvas3D}, 1); Slic3r::GUI::_3DScene::set_active($self->{preview3D}->canvas, 0); if (Slic3r::GUI::_3DScene::is_reload_delayed($self->{canvas3D})) { @@ -222,8 +222,6 @@ sub new { } # sets the canvas as dirty to force a render at the 1st idle event (wxWidgets IsShownOnScreen() is buggy and cannot be used reliably) Slic3r::GUI::_3DScene::set_as_dirty($self->{canvas3D}); - } else { - $preview->OnActivate if $preview->can('OnActivate'); } }); diff --git a/lib/Slic3r/GUI/Plater/2D.pm b/lib/Slic3r/GUI/Plater/2D.pm index ad8f54ddb..83c2a5021 100644 --- a/lib/Slic3r/GUI/Plater/2D.pm +++ b/lib/Slic3r/GUI/Plater/2D.pm @@ -231,8 +231,10 @@ sub mouse_event { } $self->Refresh; } elsif ($event->LeftUp) { - $self->{on_instances_moved}->() - if $self->{drag_object}; + if ($self->{drag_object}) { + $self->{on_instances_moved}->(); + Slic3r::GUI::_3DScene::reset_current_canvas(); + } $self->{drag_start_pos} = undef; $self->{drag_object} = undef; $self->SetCursor(wxSTANDARD_CURSOR); diff --git a/xs/src/slic3r/GUI/3DScene.cpp b/xs/src/slic3r/GUI/3DScene.cpp index feae9312e..a499825a4 100644 --- a/xs/src/slic3r/GUI/3DScene.cpp +++ b/xs/src/slic3r/GUI/3DScene.cpp @@ -1754,6 +1754,16 @@ bool _3DScene::init(wxGLCanvas* canvas) return s_canvas_mgr.init(canvas); } +bool _3DScene::set_current(wxGLCanvas* canvas, bool force) +{ + return s_canvas_mgr.set_current(canvas, force); +} + +void _3DScene::reset_current_canvas() +{ + s_canvas_mgr.set_current(nullptr, false); +} + void _3DScene::set_active(wxGLCanvas* canvas, bool active) { s_canvas_mgr.set_active(canvas, active); diff --git a/xs/src/slic3r/GUI/3DScene.hpp b/xs/src/slic3r/GUI/3DScene.hpp index c317dba24..f45bab276 100644 --- a/xs/src/slic3r/GUI/3DScene.hpp +++ b/xs/src/slic3r/GUI/3DScene.hpp @@ -516,6 +516,9 @@ public: static bool init(wxGLCanvas* canvas); + static bool set_current(wxGLCanvas* canvas, bool force); + static void reset_current_canvas(); + static void set_active(wxGLCanvas* canvas, bool active); static void set_as_dirty(wxGLCanvas* canvas); diff --git a/xs/src/slic3r/GUI/GLCanvas3D.cpp b/xs/src/slic3r/GUI/GLCanvas3D.cpp index 6f23af5a3..f5db97731 100644 --- a/xs/src/slic3r/GUI/GLCanvas3D.cpp +++ b/xs/src/slic3r/GUI/GLCanvas3D.cpp @@ -1520,13 +1520,10 @@ bool GLCanvas3D::init(bool useVBOs, bool use_legacy_opengl) return true; } -bool GLCanvas3D::set_current() +bool GLCanvas3D::set_current(bool force) { - if ((m_canvas != nullptr) && (m_context != nullptr)) - { - m_canvas->SetCurrent(*m_context); - return true; - } + if ((force || m_active) && (m_canvas != nullptr) && (m_context != nullptr)) + return m_canvas->SetCurrent(*m_context); return false; } @@ -1548,8 +1545,13 @@ unsigned int GLCanvas3D::get_volumes_count() const void GLCanvas3D::reset_volumes() { - if (set_current()) + + if (!m_volumes.empty()) { + // ensures this canvas is current + if ((m_canvas == nullptr) || !_3DScene::set_current(m_canvas, true)) + return; + m_volumes.release_geometry(); m_volumes.clear(); m_dirty = true; @@ -1847,8 +1849,8 @@ void GLCanvas3D::render() if (!_is_shown_on_screen()) return; - // ensures that the proper context is selected and that this canvas is initialized - if (!set_current() || !_3DScene::init(m_canvas)) + // ensures this canvas is current and initialized + if (!_3DScene::set_current(m_canvas, false) || !_3DScene::init(m_canvas)) return; if (m_force_zoom_to_bed_enabled) @@ -1929,6 +1931,11 @@ void GLCanvas3D::reload_scene(bool force) return; reset_volumes(); + + // ensures this canvas is current + if (!_3DScene::set_current(m_canvas, true)) + return; + set_bed_shape(dynamic_cast(m_config->option("bed_shape"))->values); if (!m_canvas->IsShown() && !force) @@ -2000,6 +2007,10 @@ void GLCanvas3D::reload_scene(bool force) void GLCanvas3D::load_print_toolpaths() { + // ensures this canvas is current + if (!_3DScene::set_current(m_canvas, true)) + return; + if (m_print == nullptr) return; @@ -2364,8 +2375,8 @@ void GLCanvas3D::load_gcode_preview(const GCodePreviewData& preview_data, const { if ((m_canvas != nullptr) && (m_print != nullptr)) { - // ensures that the proper context is selected - if (!set_current()) + // ensures that this canvas is current + if (!_3DScene::set_current(m_canvas, false)) return; if (m_volumes.empty()) @@ -2682,7 +2693,11 @@ void GLCanvas3D::on_mouse(wxMouseEvent& evt) #endif } else if (evt.LeftDClick() && (m_hover_volume_id != -1)) + { + m_active = false; m_on_double_click_callback.call(); + m_active = true; + } else if (evt.LeftDown() || evt.RightDown()) { // If user pressed left or right button we first check whether this happened @@ -2778,7 +2793,11 @@ void GLCanvas3D::on_mouse(wxMouseEvent& evt) { // if right clicking on volume, propagate event through callback if (m_volumes.volumes[volume_idx]->hover) + { + m_active = false; m_on_right_click_callback.call(pos.x, pos.y); + m_active = true; + } } } } @@ -2996,10 +3015,11 @@ void GLCanvas3D::_force_zoom_to_bed() void GLCanvas3D::_resize(unsigned int w, unsigned int h) { - if (m_context == nullptr) + if ((m_canvas == nullptr) && (m_context == nullptr)) return; - set_current(); + // ensures that this canvas is current + _3DScene::set_current(m_canvas, false); ::glViewport(0, 0, w, h); ::glMatrixMode(GL_PROJECTION); @@ -3585,9 +3605,11 @@ void GLCanvas3D::_perform_layer_editing_action(wxMouseEvent* evt) Pointf3 GLCanvas3D::_mouse_to_3d(const Point& mouse_pos, float* z) { - if (!set_current()) + 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]; diff --git a/xs/src/slic3r/GUI/GLCanvas3D.hpp b/xs/src/slic3r/GUI/GLCanvas3D.hpp index 18011f497..237044e83 100644 --- a/xs/src/slic3r/GUI/GLCanvas3D.hpp +++ b/xs/src/slic3r/GUI/GLCanvas3D.hpp @@ -449,7 +449,7 @@ public: bool init(bool useVBOs, bool use_legacy_opengl); - bool set_current(); + bool set_current(bool force); void set_active(bool active); void set_as_dirty(); diff --git a/xs/src/slic3r/GUI/GLCanvas3DManager.cpp b/xs/src/slic3r/GUI/GLCanvas3DManager.cpp index 81efe4408..62d17827a 100644 --- a/xs/src/slic3r/GUI/GLCanvas3DManager.cpp +++ b/xs/src/slic3r/GUI/GLCanvas3DManager.cpp @@ -115,6 +115,7 @@ std::string GLCanvas3DManager::GLInfo::to_string(bool format_as_html, bool exten GLCanvas3DManager::GLCanvas3DManager() : m_context(nullptr) + , m_current(nullptr) , m_gl_initialized(false) , m_use_legacy_opengl(false) , m_use_VBOs(false) @@ -212,6 +213,34 @@ bool GLCanvas3DManager::init(wxGLCanvas* canvas) return false; } +bool GLCanvas3DManager::set_current(wxGLCanvas* canvas, bool force) +{ + // given canvas is already current, return + if (m_current == canvas) + return true; + + if (canvas == nullptr) + { + m_current = nullptr; + return true; + } + + // set given canvas as current + CanvasesMap::iterator it = _get_canvas(canvas); + if (it != m_canvases.end()) + { + bool res = it->second->set_current(force); + if (res) + { + m_current = canvas; + return true; + } + } + + m_current = nullptr; + return false; +} + void GLCanvas3DManager::set_active(wxGLCanvas* canvas, bool active) { CanvasesMap::iterator it = _get_canvas(canvas); diff --git a/xs/src/slic3r/GUI/GLCanvas3DManager.hpp b/xs/src/slic3r/GUI/GLCanvas3DManager.hpp index 8726eb0b1..35a1db206 100644 --- a/xs/src/slic3r/GUI/GLCanvas3DManager.hpp +++ b/xs/src/slic3r/GUI/GLCanvas3DManager.hpp @@ -45,6 +45,7 @@ class GLCanvas3DManager wxGLContext* m_context; CanvasesMap m_canvases; + wxGLCanvas* m_current; GLInfo m_gl_info; bool m_gl_initialized; bool m_use_legacy_opengl; @@ -69,6 +70,7 @@ public: bool init(wxGLCanvas* canvas); + bool set_current(wxGLCanvas* canvas, bool force); void set_active(wxGLCanvas* canvas, bool active); void set_as_dirty(wxGLCanvas* canvas); diff --git a/xs/xsp/GUI_3DScene.xsp b/xs/xsp/GUI_3DScene.xsp index d46c06120..8ee88543d 100644 --- a/xs/xsp/GUI_3DScene.xsp +++ b/xs/xsp/GUI_3DScene.xsp @@ -190,6 +190,11 @@ remove_all_canvases() CODE: _3DScene::remove_all_canvases(); +void +reset_current_canvas() + CODE: + _3DScene::reset_current_canvas(); + void set_active(canvas, active) SV *canvas;