diff --git a/deps/wxWidgets/wxWidgets.cmake b/deps/wxWidgets/wxWidgets.cmake index c993d8948..73d841014 100644 --- a/deps/wxWidgets/wxWidgets.cmake +++ b/deps/wxWidgets/wxWidgets.cmake @@ -13,7 +13,7 @@ prusaslicer_add_cmake_project(wxWidgets # GIT_REPOSITORY "https://github.com/prusa3d/wxWidgets" # GIT_TAG tm_cross_compile #${_wx_git_tag} URL https://github.com/prusa3d/wxWidgets/archive/refs/heads/v3.1.4-patched.zip - URL_HASH SHA256=21ed12eb5c215b00999f0374af652be0a6f785df10d18d0dfec8d81ed4abaea3 + URL_HASH SHA256=ed36a2159c781cce07b06378664e683ebd8cb2f51914aba9acd3bfca3d63d7d3 DEPENDS ${PNG_PKG} ${ZLIB_PKG} ${EXPAT_PKG} dep_TIFF dep_JPEG CMAKE_ARGS -DwxBUILD_PRECOMP=ON diff --git a/src/libslic3r/PrintConfig.cpp b/src/libslic3r/PrintConfig.cpp index 2e0e2ff18..7af71fe30 100644 --- a/src/libslic3r/PrintConfig.cpp +++ b/src/libslic3r/PrintConfig.cpp @@ -72,7 +72,8 @@ static t_config_enum_values s_keys_map_PrintHostType { { "duet", htDuet }, { "flashair", htFlashAir }, { "astrobox", htAstroBox }, - { "repetier", htRepetier } + { "repetier", htRepetier }, + { "mks", htMKS } }; CONFIG_OPTION_ENUM_DEFINE_STATIC_MAPS(PrintHostType) @@ -1854,12 +1855,14 @@ void PrintConfigDef::init_fff_params() def->enum_values.push_back("flashair"); def->enum_values.push_back("astrobox"); def->enum_values.push_back("repetier"); + def->enum_values.push_back("mks"); def->enum_labels.push_back("PrusaLink"); def->enum_labels.push_back("OctoPrint"); def->enum_labels.push_back("Duet"); def->enum_labels.push_back("FlashAir"); def->enum_labels.push_back("AstroBox"); def->enum_labels.push_back("Repetier"); + def->enum_labels.push_back("MKS"); def->mode = comAdvanced; def->cli = ConfigOptionDef::nocli; def->set_default_value(new ConfigOptionEnum(htOctoPrint)); diff --git a/src/libslic3r/PrintConfig.hpp b/src/libslic3r/PrintConfig.hpp index 68dbd68d3..657f34ad1 100644 --- a/src/libslic3r/PrintConfig.hpp +++ b/src/libslic3r/PrintConfig.hpp @@ -44,7 +44,7 @@ enum class MachineLimitsUsage { }; enum PrintHostType { - htPrusaLink, htOctoPrint, htDuet, htFlashAir, htAstroBox, htRepetier + htPrusaLink, htOctoPrint, htDuet, htFlashAir, htAstroBox, htRepetier, htMKS }; enum AuthorizationType { diff --git a/src/libslic3r/PrintObjectSlice.cpp b/src/libslic3r/PrintObjectSlice.cpp index 84b212938..4b91714e5 100644 --- a/src/libslic3r/PrintObjectSlice.cpp +++ b/src/libslic3r/PrintObjectSlice.cpp @@ -379,11 +379,7 @@ static std::vector> slices_to_regions( int j = i; bool merged = false; ExPolygons &expolygons = temp_slices[i].expolygons; - for (++ j; - j < int(temp_slices.size()) && - temp_slices[i].region_id == temp_slices[j].region_id && - (clip_multipart_objects || temp_slices[i].volume_id == temp_slices[j].volume_id); - ++ j) + for (++ j; j < int(temp_slices.size()) && temp_slices[i].region_id == temp_slices[j].region_id; ++ j) if (ExPolygons &expolygons2 = temp_slices[j].expolygons; ! expolygons2.empty()) { if (expolygons.empty()) { expolygons = std::move(expolygons2); @@ -392,7 +388,10 @@ static std::vector> slices_to_regions( merged = true; } } - if (merged) + // Don't unite the regions if ! clip_multipart_objects. In that case it is user's responsibility + // to handle region overlaps. Indeed, one may intentionally let the regions overlap to produce crossing perimeters + // for example. + if (merged && clip_multipart_objects) expolygons = closing_ex(expolygons, float(scale_(EPSILON))); slices_by_region[temp_slices[i].region_id][z_idx] = std::move(expolygons); i = j; diff --git a/src/slic3r/CMakeLists.txt b/src/slic3r/CMakeLists.txt index f0c2338e9..94b8bae6f 100644 --- a/src/slic3r/CMakeLists.txt +++ b/src/slic3r/CMakeLists.txt @@ -237,6 +237,10 @@ set(SLIC3R_GUI_SOURCES Utils/UndoRedo.hpp Utils/HexFile.cpp Utils/HexFile.hpp + Utils/TCPConsole.cpp + Utils/TCPConsole.hpp + Utils/MKS.cpp + Utils/MKS.hpp ) if (APPLE) diff --git a/src/slic3r/GUI/GUI_App.cpp b/src/slic3r/GUI/GUI_App.cpp index 80c3709cf..4253f26c2 100644 --- a/src/slic3r/GUI/GUI_App.cpp +++ b/src/slic3r/GUI/GUI_App.cpp @@ -12,6 +12,7 @@ #include #include #include +#include #include #include #include @@ -97,6 +98,8 @@ #include #endif +using namespace std::literals; + namespace Slic3r { namespace GUI { @@ -471,39 +474,86 @@ static bool run_updater_win() } #endif //_WIN32 +struct FileWildcards { + std::string_view title; + std::vector file_extensions; +}; + +static const FileWildcards file_wildcards_by_type[FT_SIZE] = { + /* FT_STL */ { "STL files"sv, { ".stl"sv } }, + /* FT_OBJ */ { "OBJ files"sv, { ".obj"sv } }, + /* FT_AMF */ { "AMF files"sv, { ".amf"sv, ".zip.amf"sv, ".xml"sv } }, + /* FT_3MF */ { "3MF files"sv, { ".3mf"sv } }, + /* FT_GCODE */ { "G-code files"sv, { ".gcode"sv, ".gco"sv, ".g"sv, ".ngc"sv } }, + /* FT_MODEL */ { "Known files"sv, { ".stl"sv, ".obj"sv, ".3mf"sv, ".amf"sv, ".zip.amf"sv, ".xml"sv } }, + /* FT_PROJECT */ { "Project files"sv, { ".3mf"sv, ".amf"sv, ".zip.amf"sv } }, + /* FT_GALLERY */ { "Known files"sv, { ".stl"sv, ".obj"sv } }, + + /* FT_INI */ { "INI files"sv, { ".ini"sv } }, + /* FT_SVG */ { "SVG files"sv, { ".svg"sv } }, + + /* FT_TEX */ { "Texture"sv, { ".png"sv, ".svg"sv } }, + + /* FT_SL1 */ { "Masked SLA files"sv, { ".sl1"sv, ".sl1s"sv } }, +}; + +// This function produces a Win32 file dialog file template mask to be consumed by wxWidgets on all platforms. +// The function accepts a custom extension parameter. If the parameter is provided, the custom extension +// will be added as a fist to the list. This is important for a "file save" dialog on OSX, which strips +// an extension from the provided initial file name and substitutes it with the default extension (the first one in the template). wxString file_wildcards(FileType file_type, const std::string &custom_extension) { - static const std::string defaults[FT_SIZE] = { - /* FT_STL */ "STL files (*.stl)|*.stl;*.STL", - /* FT_OBJ */ "OBJ files (*.obj)|*.obj;*.OBJ", - /* FT_AMF */ "AMF files (*.amf)|*.zip.amf;*.amf;*.AMF;*.xml;*.XML", - /* FT_3MF */ "3MF files (*.3mf)|*.3mf;*.3MF;", - /* FT_GCODE */ "G-code files (*.gcode, *.gco, *.g, *.ngc)|*.gcode;*.GCODE;*.gco;*.GCO;*.g;*.G;*.ngc;*.NGC", - /* FT_MODEL */ "Known files (*.stl, *.obj, *.amf, *.xml, *.3mf, *.prusa)|*.stl;*.STL;*.obj;*.OBJ;*.amf;*.AMF;*.xml;*.XML;*.3mf;*.3MF", - /* FT_PROJECT */ "Project files (*.3mf, *.amf)|*.3mf;*.3MF;*.amf;*.AMF", - /* FT_GALLERY */ "Known files (*.stl, *.obj)|*.stl;*.STL;*.obj;*.OBJ", + const FileWildcards data = file_wildcards_by_type[file_type]; + std::string title; + std::string mask; + std::string custom_ext_lower; - /* FT_INI */ "INI files (*.ini)|*.ini;*.INI", - /* FT_SVG */ "SVG files (*.svg)|*.svg;*.SVG", - - /* FT_TEX */ "Texture (*.png, *.svg)|*.png;*.PNG;*.svg;*.SVG", - - /* FT_SL1 */ "Masked SLA files (*.sl1, *.sl1s)|*.sl1;*.SL1;*.sl1s;*.SL1S", - // Workaround for OSX file picker, for some reason it always saves with the 1st extension. - /* FT_SL1S */ "Masked SLA files (*.sl1s, *.sl1)|*.sl1s;*.SL1S;*.sl1;*.SL1", - }; - - std::string out = defaults[file_type]; if (! custom_extension.empty()) { - // Find the custom extension in the template. - if (out.find(std::string("*") + custom_extension + ",") == std::string::npos && out.find(std::string("*") + custom_extension + ")") == std::string::npos) { - // The custom extension was not found in the template. - // Append the custom extension to the wildcards, so that the file dialog would not add the default extension to it. - boost::replace_first(out, ")|", std::string(", *") + custom_extension + ")|"); - out += std::string(";*") + custom_extension; + // Generate an extension into the title mask and into the list of extensions. + custom_ext_lower = custom_extension; + boost::to_lower(custom_ext_lower); + std::string custom_ext_upper = custom_extension; + boost::to_upper(custom_ext_upper); + if (custom_ext_lower == custom_extension) { + // Add a lower case version. + title = std::string("*") + custom_ext_lower; + mask = title; + // Add an upper case version. + mask += ";*"; + mask += custom_ext_upper; + } else if (custom_ext_upper == custom_extension) { + // Add an upper case version. + title = std::string("*") + custom_ext_upper; + mask = title; + // Add a lower case version. + mask += ";*"; + mask += custom_ext_lower; + } else { + // Add the mixed case version only. + title = std::string("*") + custom_extension; + mask = title; } } - return from_u8(out); + + for (const std::string_view ext : data.file_extensions) + // Only add an extension if it was not added first as the custom extension. + if (ext != custom_ext_lower) { + if (title.empty()) { + title = "*"; + title += ext; + mask = title; + } else { + title += ", *"; + title += ext; + mask += ";*"; + mask += ext; + } + mask += ";*"; + std::string ext_upper{ ext }; + boost::to_upper(ext_upper); + mask += ext_upper; + } + return GUI::format("%s (%s)|%s", data.title, title, mask); } static std::string libslic3r_translate_callback(const char *s) { return wxGetTranslation(wxString(s, wxConvUTF8)).utf8_str().data(); } diff --git a/src/slic3r/GUI/GUI_App.hpp b/src/slic3r/GUI/GUI_App.hpp index 95ac8c025..9e8e913f6 100644 --- a/src/slic3r/GUI/GUI_App.hpp +++ b/src/slic3r/GUI/GUI_App.hpp @@ -66,13 +66,11 @@ enum FileType FT_TEX, FT_SL1, - // Workaround for OSX file picker, for some reason it always saves with the 1st extension. - FT_SL1S, FT_SIZE, }; -extern wxString file_wildcards(FileType file_type, const std::string &custom_extension = std::string()); +extern wxString file_wildcards(FileType file_type, const std::string &custom_extension = std::string{}); enum ConfigMenuIDs { ConfigMenuWizard, diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index 4cdb91053..628aaa4d2 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -5719,7 +5719,7 @@ void Plater::export_gcode(bool prefer_removable) wxFileDialog dlg(this, (printer_technology() == ptFFF) ? _L("Save G-code file as:") : _L("Save SL1 / SL1S file as:"), start_dir, from_path(default_output_file.filename()), - GUI::file_wildcards((printer_technology() == ptFFF) ? FT_GCODE : boost::iequals(ext, ".sl1s") ? FT_SL1S : FT_SL1, ext), + GUI::file_wildcards((printer_technology() == ptFFF) ? FT_GCODE : FT_SL1, ext), wxFD_SAVE | wxFD_OVERWRITE_PROMPT ); if (dlg.ShowModal() == wxID_OK) { diff --git a/src/slic3r/GUI/Preferences.cpp b/src/slic3r/GUI/Preferences.cpp index 4359d600b..d6422bbad 100644 --- a/src/slic3r/GUI/Preferences.cpp +++ b/src/slic3r/GUI/Preferences.cpp @@ -481,14 +481,18 @@ void PreferencesDialog::build(size_t selected_tab) option = Option(def, "dark_color_mode"); m_optgroup_dark_mode->append_single_option_line(option); - def.label = L("Use system menu for application"); - def.type = coBool; - def.tooltip = L("If enabled, application will use the standart Windows system menu,\n" - "but on some combination od display scales it can look ugly. " - "If disabled, old UI will be used."); - def.set_default_value(new ConfigOptionBool{ app_config->get("sys_menu_enabled") == "1" }); - option = Option(def, "sys_menu_enabled"); - m_optgroup_dark_mode->append_single_option_line(option); + if (wxPlatformInfo::Get().GetOSMajorVersion() >= 10) // Use system menu just for Window newer then Windows 10 + // Use menu with ownerdrawn items by default on systems older then Windows 10 + { + def.label = L("Use system menu for application"); + def.type = coBool; + def.tooltip = L("If enabled, application will use the standart Windows system menu,\n" + "but on some combination od display scales it can look ugly. " + "If disabled, old UI will be used."); + def.set_default_value(new ConfigOptionBool{ app_config->get("sys_menu_enabled") == "1" }); + option = Option(def, "sys_menu_enabled"); + m_optgroup_dark_mode->append_single_option_line(option); + } activate_options_tab(m_optgroup_dark_mode); } diff --git a/src/slic3r/Utils/MKS.cpp b/src/slic3r/Utils/MKS.cpp new file mode 100644 index 000000000..2188a8f68 --- /dev/null +++ b/src/slic3r/Utils/MKS.cpp @@ -0,0 +1,150 @@ +#include "MKS.hpp" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include "libslic3r/PrintConfig.hpp" +#include "slic3r/GUI/GUI.hpp" +#include "slic3r/GUI/I18N.hpp" +#include "slic3r/GUI/MsgDialog.hpp" +#include "Http.hpp" + +namespace fs = boost::filesystem; +namespace pt = boost::property_tree; + +namespace Slic3r { + +MKS::MKS(DynamicPrintConfig* config) : + m_host(config->opt_string("print_host")), m_console_port("8080") +{} + +const char* MKS::get_name() const { return "MKS"; } + +bool MKS::test(wxString& msg) const +{ + Utils::TCPConsole console(m_host, m_console_port); + + console.enqueue_cmd("M105"); + bool ret = console.run_queue(); + + if (!ret) + msg = wxString::FromUTF8(console.error_message().c_str()); + + return ret; +} + +wxString MKS::get_test_ok_msg() const +{ + return _(L("Connection to MKS works correctly.")); +} + +wxString MKS::get_test_failed_msg(wxString& msg) const +{ + return GUI::from_u8((boost::format("%s: %s") + % _utf8(L("Could not connect to MKS")) + % std::string(msg.ToUTF8())).str()); +} + +bool MKS::upload(PrintHostUpload upload_data, ProgressFn prorgess_fn, ErrorFn error_fn) const +{ + bool res = true; + + auto upload_cmd = get_upload_url(upload_data.upload_path.string()); + BOOST_LOG_TRIVIAL(info) << boost::format("MKS: Uploading file %1%, filepath: %2%, print: %3%, command: %4%") + % upload_data.source_path + % upload_data.upload_path + % upload_data.start_print + % upload_cmd; + + auto http = Http::post(std::move(upload_cmd)); + http.set_post_body(upload_data.source_path); + + http.on_complete([&](std::string body, unsigned status) { + BOOST_LOG_TRIVIAL(debug) << boost::format("MKS: File uploaded: HTTP %1%: %2%") % status % body; + + int err_code = get_err_code_from_body(body); + if (err_code != 0) { + BOOST_LOG_TRIVIAL(error) << boost::format("MKS: Request completed but error code was received: %1%") % err_code; + error_fn(format_error(body, L("Unknown error occured"), 0)); + res = false; + } + else if (upload_data.start_print) { + wxString errormsg; + res = start_print(errormsg, upload_data.upload_path.string()); + if (!res) { + error_fn(std::move(errormsg)); + } + } + }) + .on_error([&](std::string body, std::string error, unsigned status) { + BOOST_LOG_TRIVIAL(error) << boost::format("MKS: Error uploading file: %1%, HTTP %2%, body: `%3%`") % error % status % body; + error_fn(format_error(body, error, status)); + res = false; + }) + .on_progress([&](Http::Progress progress, bool& cancel) { + prorgess_fn(std::move(progress), cancel); + if (cancel) { + // Upload was canceled + BOOST_LOG_TRIVIAL(info) << "MKS: Upload canceled"; + res = false; + } + }).perform_sync(); + + + return res; +} + +std::string MKS::get_upload_url(const std::string& filename) const +{ + return (boost::format("http://%1%/upload?X-Filename=%2%") + % m_host + % Http::url_encode(filename)).str(); +} + +bool MKS::start_print(wxString& msg, const std::string& filename) const +{ + // For some reason printer firmware does not want to respond on gcode commands immediately after file upload. + // So we just introduce artificial delay to workaround it. + // TODO: Inspect reasons + std::this_thread::sleep_for(std::chrono::milliseconds(1500)); + + Utils::TCPConsole console(m_host, m_console_port); + + console.enqueue_cmd(std::string("M23 ") + filename); + console.enqueue_cmd("M24"); + + bool ret = console.run_queue(); + + if (!ret) + msg = wxString::FromUTF8(console.error_message().c_str()); + + return ret; +} + +int MKS::get_err_code_from_body(const std::string& body) const +{ + pt::ptree root; + std::istringstream iss(body); // wrap returned json to istringstream + pt::read_json(iss, root); + + return root.get("err", 0); +} + +} // Slic3r diff --git a/src/slic3r/Utils/MKS.hpp b/src/slic3r/Utils/MKS.hpp new file mode 100644 index 000000000..7564b7f81 --- /dev/null +++ b/src/slic3r/Utils/MKS.hpp @@ -0,0 +1,42 @@ +#ifndef slic3r_MKS_hpp_ +#define slic3r_MKS_hpp_ + +#include +#include + +#include "PrintHost.hpp" +#include "TCPConsole.hpp" + +namespace Slic3r { +class DynamicPrintConfig; +class Http; + +class MKS : public PrintHost +{ +public: + explicit MKS(DynamicPrintConfig* config); + ~MKS() override = default; + + const char* get_name() const override; + + bool test(wxString& curl_msg) const override; + wxString get_test_ok_msg() const override; + wxString get_test_failed_msg(wxString& msg) const override; + bool upload(PrintHostUpload upload_data, ProgressFn prorgess_fn, ErrorFn error_fn) const override; + bool has_auto_discovery() const override { return false; } + bool can_test() const override { return true; } + bool can_start_print() const override { return true; } + std::string get_host() const override { return m_host; } + +private: + std::string m_host; + std::string m_console_port; + + std::string get_upload_url(const std::string& filename) const; + bool start_print(wxString& msg, const std::string& filename) const; + int get_err_code_from_body(const std::string& body) const; +}; + +} + +#endif diff --git a/src/slic3r/Utils/PrintHost.cpp b/src/slic3r/Utils/PrintHost.cpp index 53200a4c9..86f6101b6 100644 --- a/src/slic3r/Utils/PrintHost.cpp +++ b/src/slic3r/Utils/PrintHost.cpp @@ -18,6 +18,7 @@ #include "FlashAir.hpp" #include "AstroBox.hpp" #include "Repetier.hpp" +#include "MKS.hpp" #include "../GUI/PrintHostDialogs.hpp" namespace fs = boost::filesystem; @@ -51,6 +52,7 @@ PrintHost* PrintHost::get_print_host(DynamicPrintConfig *config) case htAstroBox: return new AstroBox(config); case htRepetier: return new Repetier(config); case htPrusaLink: return new PrusaLink(config); + case htMKS: return new MKS(config); default: return nullptr; } } else { diff --git a/src/slic3r/Utils/TCPConsole.cpp b/src/slic3r/Utils/TCPConsole.cpp new file mode 100644 index 000000000..a4f4bc21e --- /dev/null +++ b/src/slic3r/Utils/TCPConsole.cpp @@ -0,0 +1,206 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include "TCPConsole.hpp" + +using boost::asio::steady_timer; +using boost::asio::ip::tcp; + +namespace Slic3r { +namespace Utils { + +void TCPConsole::transmit_next_command() +{ + if (m_cmd_queue.empty()) { + m_io_context.stop(); + return; + } + + std::string cmd = m_cmd_queue.front(); + m_cmd_queue.pop_front(); + + BOOST_LOG_TRIVIAL(debug) << boost::format("TCPConsole: transmitting '%3%' to %1%:%2%") + % m_host_name + % m_port_name + % cmd; + + m_send_buffer = cmd + m_newline; + + set_deadline_in(m_write_timeout); + boost::asio::async_write( + m_socket, + boost::asio::buffer(m_send_buffer), + boost::bind(&TCPConsole::handle_write, this, _1, _2) + ); +} + +void TCPConsole::wait_next_line() +{ + set_deadline_in(m_read_timeout); + boost::asio::async_read_until( + m_socket, + m_recv_buffer, + m_newline, + boost::bind(&TCPConsole::handle_read, this, _1, _2) + ); +} + +// TODO: Use std::optional here +std::string TCPConsole::extract_next_line() +{ + char linebuf[1024]; + std::istream is(&m_recv_buffer); + is.getline(linebuf, sizeof(linebuf)); + return is.good() ? linebuf : std::string{}; +} + +void TCPConsole::handle_read( + const boost::system::error_code& ec, + std::size_t bytes_transferred) +{ + m_error_code = ec; + + if (ec) { + BOOST_LOG_TRIVIAL(error) << boost::format("TCPConsole: Can't read from %1%:%2%: %3%") + % m_host_name + % m_port_name + % ec.message(); + + m_io_context.stop(); + } + else { + std::string line = extract_next_line(); + boost::trim(line); + + BOOST_LOG_TRIVIAL(debug) << boost::format("TCPConsole: received '%3%' from %1%:%2%") + % m_host_name + % m_port_name + % line; + + boost::to_lower(line); + + if (line == m_done_string) + transmit_next_command(); + else + wait_next_line(); + } +} + +void TCPConsole::handle_write( + const boost::system::error_code& ec, + std::size_t) +{ + m_error_code = ec; + if (ec) { + BOOST_LOG_TRIVIAL(error) << boost::format("TCPConsole: Can't write to %1%:%2%: %3%") + % m_host_name + % m_port_name + % ec.message(); + + m_io_context.stop(); + } + else { + wait_next_line(); + } +} + +void TCPConsole::handle_connect(const boost::system::error_code& ec) +{ + m_error_code = ec; + + if (ec) { + BOOST_LOG_TRIVIAL(error) << boost::format("TCPConsole: Can't connect to %1%:%2%: %3%") + % m_host_name + % m_port_name + % ec.message(); + + m_io_context.stop(); + } + else { + m_is_connected = true; + BOOST_LOG_TRIVIAL(info) << boost::format("TCPConsole: connected to %1%:%2%") + % m_host_name + % m_port_name; + + transmit_next_command(); + } +} + +void TCPConsole::set_deadline_in(std::chrono::steady_clock::duration d) +{ + m_deadline = std::chrono::steady_clock::now() + d; +} +bool TCPConsole::is_deadline_over() const +{ + return m_deadline < std::chrono::steady_clock::now(); +} + +bool TCPConsole::run_queue() +{ + auto now = std::chrono::steady_clock::now(); + try { + // TODO: Add more resets and initializations after previous run (reset() method?..) + set_deadline_in(m_connect_timeout); + m_is_connected = false; + m_io_context.restart(); + + auto endpoints = m_resolver.resolve(m_host_name, m_port_name); + + m_socket.async_connect(endpoints->endpoint(), + boost::bind(&TCPConsole::handle_connect, this, _1) + ); + + // Loop until we get any reasonable result. Negative result is also result. + // TODO: Rewrite to more graceful way using deadlime_timer + bool timeout = false; + while (!(timeout = is_deadline_over()) && !m_io_context.stopped()) { + if (m_error_code) { + m_io_context.stop(); + } + m_io_context.run_for(boost::asio::chrono::milliseconds(100)); + } + + // Override error message if timeout is set + if (timeout) + m_error_code = make_error_code(boost::asio::error::timed_out); + + // Socket is not closed automatically by boost + m_socket.close(); + + if (m_error_code) { + // We expect that message is logged in handler + return false; + } + + // It's expected to have empty queue after successful exchange + if (!m_cmd_queue.empty()) { + BOOST_LOG_TRIVIAL(error) << "TCPConsole: command queue is not empty after end of exchange"; + return false; + } + } + catch (std::exception& e) + { + BOOST_LOG_TRIVIAL(error) << boost::format("TCPConsole: Exception while talking with %1%:%2%: %3%") + % m_host_name + % m_port_name + % e.what(); + + return false; + } + + return true; +} + +} // namespace Utils +} // namespace Slic3r diff --git a/src/slic3r/Utils/TCPConsole.hpp b/src/slic3r/Utils/TCPConsole.hpp new file mode 100644 index 000000000..7c0e1d290 --- /dev/null +++ b/src/slic3r/Utils/TCPConsole.hpp @@ -0,0 +1,91 @@ +#ifndef slic3r_Utils_TCPConsole_hpp_ +#define slic3r_Utils_TCPConsole_hpp_ + +#include +#include +#include +#include +#include +#include + +namespace Slic3r { +namespace Utils { + +using boost::asio::ip::tcp; + +class TCPConsole +{ +public: + TCPConsole() : m_resolver(m_io_context), m_socket(m_io_context) { set_defaults(); } + TCPConsole(const std::string& host_name, const std::string& port_name) : m_resolver(m_io_context), m_socket(m_io_context) + { set_defaults(); set_remote(host_name, port_name); } + ~TCPConsole() = default; + + void set_defaults() + { + m_newline = "\n"; + m_done_string = "ok"; + m_connect_timeout = std::chrono::milliseconds(5000); + m_write_timeout = std::chrono::milliseconds(10000); + m_read_timeout = std::chrono::milliseconds(10000); + } + + void set_line_delimiter(const std::string& newline) { + m_newline = newline; + } + void set_command_done_string(const std::string& done_string) { + m_done_string = done_string; + } + + void set_remote(const std::string& host_name, const std::string& port_name) + { + m_host_name = host_name; + m_port_name = port_name; + } + + bool enqueue_cmd(const std::string& cmd) { + // TODO: Add multithread protection to queue + m_cmd_queue.push_back(cmd); + return true; + } + + bool run_queue(); + std::string error_message() const { return m_error_code.message(); } + +private: + void handle_connect(const boost::system::error_code& ec); + void handle_read(const boost::system::error_code& ec, std::size_t bytes_transferred); + void handle_write(const boost::system::error_code& ec, std::size_t bytes_transferred); + + void transmit_next_command(); + void wait_next_line(); + std::string extract_next_line(); + + void set_deadline_in(std::chrono::steady_clock::duration); + bool is_deadline_over() const; + + std::string m_host_name; + std::string m_port_name; + std::string m_newline; + std::string m_done_string; + std::chrono::steady_clock::duration m_connect_timeout; + std::chrono::steady_clock::duration m_write_timeout; + std::chrono::steady_clock::duration m_read_timeout; + + std::deque m_cmd_queue; + + boost::asio::io_context m_io_context; + tcp::resolver m_resolver; + tcp::socket m_socket; + boost::asio::streambuf m_recv_buffer; + std::string m_send_buffer; + + bool m_is_connected; + boost::system::error_code m_error_code; + std::chrono::steady_clock::time_point m_deadline; +}; + +} // Utils +} // Slic3r + +#endif