diff options
author | bubnikv <bubnikv@gmail.com> | 2018-06-27 11:33:14 +0300 |
---|---|---|
committer | bubnikv <bubnikv@gmail.com> | 2018-06-27 11:33:14 +0300 |
commit | 68e82a734812551689fe88fae980d9205e8f8edc (patch) | |
tree | b37c688a9489d094c34cabe878181eb01edbd293 /xs | |
parent | 22463343a738f7a36b6d59cd038b77a7ec83d8de (diff) | |
parent | 645cc65d2b340752f390359ce3ed09a49b463588 (diff) |
Merge branch 'master' into amf_activate_existing_presets
Diffstat (limited to 'xs')
-rw-r--r-- | xs/CMakeLists.txt | 10 | ||||
-rw-r--r-- | xs/src/avrdude/arduino.c | 11 | ||||
-rw-r--r-- | xs/src/libslic3r/GCode/WipeTowerPrusaMM.cpp | 19 | ||||
-rw-r--r-- | xs/src/slic3r/GUI/3DScene.cpp | 18 | ||||
-rw-r--r-- | xs/src/slic3r/GUI/3DScene.hpp | 4 | ||||
-rw-r--r-- | xs/src/slic3r/GUI/Field.cpp | 18 | ||||
-rw-r--r-- | xs/src/slic3r/GUI/GLCanvas3D.cpp | 80 | ||||
-rw-r--r-- | xs/src/slic3r/GUI/GLCanvas3D.hpp | 3 | ||||
-rw-r--r-- | xs/src/slic3r/GUI/GLCanvas3DManager.cpp | 86 | ||||
-rw-r--r-- | xs/src/slic3r/GUI/GLCanvas3DManager.hpp | 5 | ||||
-rw-r--r-- | xs/src/slic3r/GUI/GLTexture.cpp | 5 | ||||
-rw-r--r-- | xs/src/slic3r/GUI/GUI.cpp | 2 | ||||
-rw-r--r-- | xs/src/slic3r/GUI/MsgDialog.hpp | 2 | ||||
-rw-r--r-- | xs/src/slic3r/GUI/OptionsGroup.cpp | 9 | ||||
-rw-r--r-- | xs/src/slic3r/GUI/Tab.cpp | 12 | ||||
-rw-r--r-- | xs/src/slic3r/Utils/Http.cpp | 86 | ||||
-rw-r--r-- | xs/src/slic3r/Utils/Http.hpp | 38 | ||||
-rw-r--r-- | xs/src/slic3r/Utils/OctoPrint.cpp | 92 | ||||
-rw-r--r-- | xs/src/slic3r/Utils/OctoPrint.hpp | 5 | ||||
-rw-r--r-- | xs/xsp/BoundingBox.xsp | 4 | ||||
-rw-r--r-- | xs/xsp/GUI_3DScene.xsp | 11 | ||||
-rw-r--r-- | xs/xsp/Utils_OctoPrint.xsp | 2 |
22 files changed, 413 insertions, 109 deletions
diff --git a/xs/CMakeLists.txt b/xs/CMakeLists.txt index 66c1cdd6a..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) @@ -579,6 +580,15 @@ if (SLIC3R_PRUSACONTROL) #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 gl) include(${wxWidgets_USE_FILE}) 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 */ 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"; diff --git a/xs/src/slic3r/GUI/3DScene.cpp b/xs/src/slic3r/GUI/3DScene.cpp index 1879b3082..a499825a4 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; @@ -1753,11 +1754,26 @@ 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); } +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..f45bab276 100644 --- a/xs/src/slic3r/GUI/3DScene.hpp +++ b/xs/src/slic3r/GUI/3DScene.hpp @@ -516,7 +516,11 @@ 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); static unsigned int get_volumes_count(wxGLCanvas* canvas); static void reset_volumes(wxGLCanvas* canvas); 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/GLCanvas3D.cpp b/xs/src/slic3r/GUI/GLCanvas3D.cpp index f9c10017e..2148579e8 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<const ConfigOptionPoints*>(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<const ConfigOptionPoints*>(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); } } @@ -1509,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; } @@ -1525,6 +1533,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(); @@ -1532,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; @@ -1831,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) @@ -1913,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<const ConfigOptionPoints*>(m_config->option("bed_shape"))->values); if (!m_canvas->IsShown() && !force) @@ -1984,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; @@ -2348,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, true)) return; if (m_volumes.empty()) @@ -2666,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 @@ -2762,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; + } } } } @@ -2980,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); @@ -3569,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 c503d1845..237044e83 100644 --- a/xs/src/slic3r/GUI/GLCanvas3D.hpp +++ b/xs/src/slic3r/GUI/GLCanvas3D.hpp @@ -449,9 +449,10 @@ 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(); 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 f288ee456..62d17827a 100644 --- a/xs/src/slic3r/GUI/GLCanvas3DManager.cpp +++ b/xs/src/slic3r/GUI/GLCanvas3DManager.cpp @@ -26,33 +26,23 @@ GLCanvas3DManager::GLInfo::GLInfo() { } -bool GLCanvas3DManager::GLInfo::detect() +void GLCanvas3DManager::GLInfo::detect() { const char* data = (const char*)::glGetString(GL_VERSION); - if (data == nullptr) - return false; - - version = data; + if (data != nullptr) + version = data; data = (const char*)::glGetString(GL_SHADING_LANGUAGE_VERSION); - if (data == nullptr) - return false; - - glsl_version = data; + if (data != nullptr) + glsl_version = data; data = (const char*)::glGetString(GL_VENDOR); - if (data == nullptr) - return false; - - vendor = data; + if (data != nullptr) + vendor = data; data = (const char*)::glGetString(GL_RENDERER); - if (data == nullptr) - return false; - - renderer = data; - - return true; + if (data != nullptr) + renderer = data; } bool GLCanvas3DManager::GLInfo::is_version_greater_or_equal_to(unsigned int major, unsigned int minor) const @@ -94,10 +84,10 @@ std::string GLCanvas3DManager::GLInfo::to_string(bool format_as_html, bool exten 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 << line_end; - out << b_start << "Vendor: " << b_end << vendor << line_end; - out << b_start << "Renderer: " << b_end << renderer << line_end; - out << b_start << "GLSL version: " << b_end << glsl_version << 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) { @@ -125,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) @@ -195,15 +186,11 @@ void GLCanvas3DManager::init_gl() if (!m_gl_initialized) { glewInit(); - if (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; - } - else - throw std::runtime_error(std::string("Unable to initialize OpenGL driver\n")); + 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; } } @@ -226,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); @@ -233,6 +248,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 6989da791..35a1db206 100644 --- a/xs/src/slic3r/GUI/GLCanvas3DManager.hpp +++ b/xs/src/slic3r/GUI/GLCanvas3DManager.hpp @@ -35,7 +35,7 @@ class GLCanvas3DManager GLInfo(); - bool detect(); + 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; @@ -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,7 +70,9 @@ 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); unsigned int get_volumes_count(wxGLCanvas* canvas) const; void reset_volumes(wxGLCanvas* canvas); 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 <wx/image.h> +#include <boost/filesystem.hpp> + #include <vector> #include <algorithm> @@ -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)) diff --git a/xs/src/slic3r/GUI/GUI.cpp b/xs/src/slic3r/GUI/GUI.cpp index 0360d73d0..a46fbc476 100644 --- a/xs/src/slic3r/GUI/GUI.cpp +++ b/xs/src/slic3r/GUI/GUI.cpp @@ -57,6 +57,7 @@ #include "../Utils/PresetUpdater.hpp" #include "../Config/Snapshot.hpp" #include "libslic3r/I18N.hpp" +#include "3DScene.hpp" namespace Slic3r { namespace GUI { @@ -382,6 +383,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); } 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/Tab.cpp b/xs/src/slic3r/GUI/Tab.cpp index 3bfab54b3..f89ddddcc 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."))); diff --git a/xs/src/slic3r/Utils/Http.cpp b/xs/src/slic3r/Utils/Http.cpp index 0826284d8..47021d39f 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(); @@ -135,6 +146,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%)") @@ -154,6 +205,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<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 @@ -183,14 +235,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) { @@ -265,17 +323,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..73656bf88 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) {} @@ -33,6 +34,10 @@ 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); static Http post(std::string url); ~Http(); @@ -41,21 +46,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, `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..86049de16 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) { + 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()); + 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 +121,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.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) { + 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; @@ -102,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 744b4fcc1..a8599faa9 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 &error, unsigned status); }; 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/GUI_3DScene.xsp b/xs/xsp/GUI_3DScene.xsp index 29f35293b..8ee88543d 100644 --- a/xs/xsp/GUI_3DScene.xsp +++ b/xs/xsp/GUI_3DScene.xsp @@ -191,12 +191,23 @@ remove_all_canvases() _3DScene::remove_all_canvases(); void +reset_current_canvas() + CODE: + _3DScene::reset_current_canvas(); + +void set_active(canvas, active) SV *canvas; bool 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; 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; }; |