mirror of
https://github.com/SoftFever/OrcaSlicer.git
synced 2025-07-08 07:27:41 -06:00
Support upload g-code to third-party printers
* wip * fix cmake warning * add UI and config options for Moonraker connection * wip: copy whole print host UI from PS * add more needed options * wip 2 * fix string issue on Mac * wip3 * fix cmake warning * working need tweaks * cleanup * support thumbnail * fix DNS resolving issue in Windows * code clean up
This commit is contained in:
parent
82127a92c9
commit
488b1cd8f5
51 changed files with 5000 additions and 17 deletions
22
resources/images/add.svg
Normal file
22
resources/images/add.svg
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<!-- Generator: Adobe Illustrator 24.3.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||||
|
<svg version="1.0" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||||
|
viewBox="0 0 128 128" enable-background="new 0 0 128 128" xml:space="preserve">
|
||||||
|
<g id="ADD">
|
||||||
|
<path fill="#FFFFFF" d="M72.3,117.5H10.5v-75h75v23.27c1.61-0.56,3.28-0.99,5-1.29V41.04l27-27V72.3c1.89,1.71,3.57,3.65,5,5.76V8
|
||||||
|
c0-0.05-0.01-0.1-0.02-0.15c0-0.06-0.01-0.11-0.02-0.17c-0.03-0.22-0.08-0.43-0.15-0.62c0,0,0-0.01,0-0.01c0,0,0,0,0,0
|
||||||
|
c-0.01-0.03-0.03-0.05-0.04-0.08c-0.05-0.11-0.11-0.21-0.17-0.31c-0.03-0.04-0.05-0.08-0.08-0.11c-0.06-0.08-0.13-0.16-0.2-0.24
|
||||||
|
c-0.03-0.03-0.06-0.07-0.09-0.1c-0.09-0.09-0.19-0.17-0.3-0.25c-0.01-0.01-0.02-0.02-0.04-0.03c-0.12-0.08-0.24-0.15-0.38-0.2
|
||||||
|
c-0.04-0.02-0.09-0.03-0.13-0.05c-0.1-0.04-0.2-0.07-0.3-0.09c-0.05-0.01-0.09-0.02-0.14-0.03c-0.15-0.03-0.3-0.05-0.45-0.05H48
|
||||||
|
c-0.57,0-1.12,0.19-1.56,0.55l-40,32c-0.03,0.03-0.06,0.06-0.09,0.09c-0.07,0.06-0.13,0.12-0.19,0.19
|
||||||
|
c-0.05,0.06-0.1,0.12-0.15,0.18c-0.05,0.07-0.09,0.13-0.14,0.2c-0.04,0.07-0.08,0.14-0.12,0.21c-0.03,0.07-0.07,0.15-0.09,0.22
|
||||||
|
c-0.03,0.08-0.05,0.16-0.07,0.24c-0.02,0.08-0.04,0.15-0.05,0.23c-0.01,0.09-0.02,0.18-0.03,0.27c0,0.04-0.01,0.08-0.01,0.13v80
|
||||||
|
c0,1.38,1.12,2.5,2.5,2.5h70.06C75.95,121.07,74.01,119.39,72.3,117.5z M48.88,10.5h65.09l-27,27H15.13L48.88,10.5z"/>
|
||||||
|
<g>
|
||||||
|
<path fill="#ED6B21" d="M96,69.5c-14.61,0-26.5,11.89-26.5,26.5s11.89,26.5,26.5,26.5s26.5-11.89,26.5-26.5S110.61,69.5,96,69.5z
|
||||||
|
M96,117.5c-11.86,0-21.5-9.64-21.5-21.5S84.14,74.5,96,74.5s21.5,9.64,21.5,21.5S107.86,117.5,96,117.5z"/>
|
||||||
|
<path fill="#ED6B21" d="M112,93.5H98.5V80c0-1.38-1.12-2.5-2.5-2.5s-2.5,1.12-2.5,2.5v13.5H80c-1.38,0-2.5,1.12-2.5,2.5
|
||||||
|
s1.12,2.5,2.5,2.5h13.5V112c0,1.38,1.12,2.5,2.5,2.5s2.5-1.12,2.5-2.5V98.5H112c1.38,0,2.5-1.12,2.5-2.5S113.38,93.5,112,93.5z"/>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 1.9 KiB |
19
resources/images/add_copies.svg
Normal file
19
resources/images/add_copies.svg
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<!-- Generator: Adobe Illustrator 23.0.3, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||||
|
<svg version="1.0" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||||
|
viewBox="0 0 16 16" enable-background="new 0 0 16 16" xml:space="preserve">
|
||||||
|
<g id="add_x5F_copies">
|
||||||
|
<g>
|
||||||
|
<path fill="#808080" d="M8,2c3.31,0,6,2.69,6,6s-2.69,6-6,6s-6-2.69-6-6S4.69,2,8,2 M8,1C4.13,1,1,4.13,1,8s3.13,7,7,7s7-3.13,7-7
|
||||||
|
S11.87,1,8,1L8,1z"/>
|
||||||
|
</g>
|
||||||
|
<g>
|
||||||
|
<path fill="#ED6B21" d="M12,8.75H4C3.59,8.75,3.25,8.41,3.25,8S3.59,7.25,4,7.25h8c0.41,0,0.75,0.34,0.75,0.75S12.41,8.75,12,8.75
|
||||||
|
z"/>
|
||||||
|
</g>
|
||||||
|
<g>
|
||||||
|
<path fill="#ED6B21" d="M8,12.75c-0.41,0-0.75-0.34-0.75-0.75V4c0-0.41,0.34-0.75,0.75-0.75S8.75,3.59,8.75,4v8
|
||||||
|
C8.75,12.41,8.41,12.75,8,12.75z"/>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 846 B |
11
resources/images/browse.svg
Normal file
11
resources/images/browse.svg
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<!-- Generator: Adobe Illustrator 23.0.3, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||||
|
<svg version="1.0" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||||
|
viewBox="0 0 16 16" enable-background="new 0 0 16 16" xml:space="preserve">
|
||||||
|
<g id="browse">
|
||||||
|
<path fill="#ED6B21" d="M8.49,2.43c-1.71-1.71-4.49-1.71-6.2,0s-1.71,4.49,0,6.2c1.59,1.59,4.1,1.7,5.82,0.34l1.48,1.48
|
||||||
|
c0,0-0.36,0.36,0,0.73s3.65,3.65,3.65,3.65s0.36,0.36,0.73,0c0.36-0.36,0.73-0.73,0.73-0.73s0.36-0.36,0-0.73s-3.65-3.65-3.65-3.65
|
||||||
|
c-0.36-0.36-0.73,0-0.73,0L8.83,8.25C10.19,6.52,10.08,4.02,8.49,2.43z M8.1,8.25c-1.5,1.5-3.93,1.5-5.43,0s-1.5-3.93,0-5.43
|
||||||
|
s3.93-1.5,5.43,0S9.6,6.75,8.1,8.25z"/>
|
||||||
|
</g>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 777 B |
|
@ -14,7 +14,7 @@
|
||||||
#include "libslic3r.h"
|
#include "libslic3r.h"
|
||||||
#include "LocalesUtils.hpp"
|
#include "LocalesUtils.hpp"
|
||||||
#include "libslic3r/format.hpp"
|
#include "libslic3r/format.hpp"
|
||||||
|
#include "Time.hpp"
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
#include <cstdlib>
|
#include <cstdlib>
|
||||||
#include <chrono>
|
#include <chrono>
|
||||||
|
@ -1352,9 +1352,11 @@ void GCode::_do_export(Print& print, GCodeOutputStream &file, ThumbnailsGenerato
|
||||||
}
|
}
|
||||||
|
|
||||||
//BBS: add plate id into thumbnail render logic
|
//BBS: add plate id into thumbnail render logic
|
||||||
//DoExport::export_thumbnails_to_file(thumbnail_cb, print.get_plate_index(), THUMBNAIL_SIZE,
|
file.write_format("; hack-fix: write fake slicer info here so that Moonraker will extract thumbs.\n");
|
||||||
// [&file](const char* sz) { file.write(sz); },
|
file.write_format("; %s\n\n",std::string(std::string("generated by PrusaSlicer " SLIC3R_VERSION " on " ) + Slic3r::Utils::utc_timestamp()).c_str());
|
||||||
// [&print]() { print.throw_if_canceled(); });
|
DoExport::export_thumbnails_to_file(thumbnail_cb, print.get_plate_index(), { Vec2d(300, 300) },
|
||||||
|
[&file](const char* sz) { file.write(sz); },
|
||||||
|
[&print]() { print.throw_if_canceled(); });
|
||||||
|
|
||||||
|
|
||||||
// Write some terse information on the slicing parameters.
|
// Write some terse information on the slicing parameters.
|
||||||
|
|
|
@ -724,7 +724,13 @@ static std::vector<std::string> s_Preset_printer_options {
|
||||||
"silent_mode",
|
"silent_mode",
|
||||||
// BBS
|
// BBS
|
||||||
"scan_first_layer", "machine_load_filament_time", "machine_unload_filament_time", "machine_pause_gcode",
|
"scan_first_layer", "machine_load_filament_time", "machine_unload_filament_time", "machine_pause_gcode",
|
||||||
"nozzle_type", "auxiliary_fan", "nozzle_volume"
|
"nozzle_type", "auxiliary_fan", "nozzle_volume",
|
||||||
|
//SoftFever
|
||||||
|
"connection_moonraker_url","connection_port", "host_type", "print_host", "printhost_apikey",
|
||||||
|
"printhost_cafile","printhost_port","printhost_authorization_type",
|
||||||
|
"printhost_user",
|
||||||
|
"printhost_password",
|
||||||
|
"printhost_ssl_ignore_revoke"
|
||||||
};
|
};
|
||||||
|
|
||||||
static std::vector<std::string> s_Preset_sla_print_options {
|
static std::vector<std::string> s_Preset_sla_print_options {
|
||||||
|
@ -2189,6 +2195,21 @@ void add_correct_opts_to_diff(const std::string &opt_key, t_config_option_keys&
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// template<class T>
|
||||||
|
// void add_correct_opt_to_diff(const std::string &opt_key, t_config_option_keys& vec, const ConfigBase &other, const ConfigBase &this_c)
|
||||||
|
// {
|
||||||
|
// const T* opt_init = static_cast<const T*>(other.option(opt_key));
|
||||||
|
// const T* opt_cur = static_cast<const T*>(this_c.option(opt_key));
|
||||||
|
// int opt_init_max_id = opt_init->values.size() - 1;
|
||||||
|
// for (int i = 0; i < int(opt_cur->values.size()); i++)
|
||||||
|
// {
|
||||||
|
// int init_id = i <= opt_init_max_id ? i : 0;
|
||||||
|
// if (opt_cur->values[i] != opt_init->values[init_id])
|
||||||
|
// vec.emplace_back(opt_key + "#" + std::to_string(i));
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
|
||||||
// Use deep_diff to correct return of changed options, considering individual options for each extruder.
|
// Use deep_diff to correct return of changed options, considering individual options for each extruder.
|
||||||
inline t_config_option_keys deep_diff(const ConfigBase &config_this, const ConfigBase &config_other)
|
inline t_config_option_keys deep_diff(const ConfigBase &config_this, const ConfigBase &config_other)
|
||||||
{
|
{
|
||||||
|
@ -2212,6 +2233,7 @@ inline t_config_option_keys deep_diff(const ConfigBase &config_this, const Confi
|
||||||
case coBools: add_correct_opts_to_diff<ConfigOptionBools >(opt_key, diff, config_other, config_this); break;
|
case coBools: add_correct_opts_to_diff<ConfigOptionBools >(opt_key, diff, config_other, config_this); break;
|
||||||
case coFloats: add_correct_opts_to_diff<ConfigOptionFloats >(opt_key, diff, config_other, config_this); break;
|
case coFloats: add_correct_opts_to_diff<ConfigOptionFloats >(opt_key, diff, config_other, config_this); break;
|
||||||
case coStrings: add_correct_opts_to_diff<ConfigOptionStrings >(opt_key, diff, config_other, config_this); break;
|
case coStrings: add_correct_opts_to_diff<ConfigOptionStrings >(opt_key, diff, config_other, config_this); break;
|
||||||
|
// case coString: add_correct_opts_to_diff<ConfigOptionString >(opt_key, diff, config_other, config_this); break;
|
||||||
case coPercents:add_correct_opts_to_diff<ConfigOptionPercents >(opt_key, diff, config_other, config_this); break;
|
case coPercents:add_correct_opts_to_diff<ConfigOptionPercents >(opt_key, diff, config_other, config_this); break;
|
||||||
case coPoints: add_correct_opts_to_diff<ConfigOptionPoints >(opt_key, diff, config_other, config_this); break;
|
case coPoints: add_correct_opts_to_diff<ConfigOptionPoints >(opt_key, diff, config_other, config_this); break;
|
||||||
// BBS
|
// BBS
|
||||||
|
@ -2519,6 +2541,16 @@ static std::vector<std::string> s_PhysicalPrinter_opts {
|
||||||
"preset_name", // temporary option to compatibility with older Slicer
|
"preset_name", // temporary option to compatibility with older Slicer
|
||||||
"preset_names",
|
"preset_names",
|
||||||
"printer_technology",
|
"printer_technology",
|
||||||
|
"host_type",
|
||||||
|
"print_host",
|
||||||
|
"printhost_apikey",
|
||||||
|
"printhost_cafile",
|
||||||
|
"printhost_port",
|
||||||
|
"printhost_authorization_type",
|
||||||
|
// HTTP digest authentization (RFC 2617)
|
||||||
|
"printhost_user",
|
||||||
|
"printhost_password",
|
||||||
|
"printhost_ssl_ignore_revoke"
|
||||||
};
|
};
|
||||||
|
|
||||||
const std::vector<std::string>& PhysicalPrinter::printer_options()
|
const std::vector<std::string>& PhysicalPrinter::printer_options()
|
||||||
|
@ -2682,6 +2714,8 @@ void PhysicalPrinterCollection::load_printers(
|
||||||
// see https://github.com/prusa3d/PrusaSlicer/issues/732
|
// see https://github.com/prusa3d/PrusaSlicer/issues/732
|
||||||
boost::filesystem::path dir = boost::filesystem::absolute(boost::filesystem::path(dir_path) / subdir).make_preferred();
|
boost::filesystem::path dir = boost::filesystem::absolute(boost::filesystem::path(dir_path) / subdir).make_preferred();
|
||||||
m_dir_path = dir.string();
|
m_dir_path = dir.string();
|
||||||
|
if(!boost::filesystem::exists(dir))
|
||||||
|
return;
|
||||||
std::string errors_cummulative;
|
std::string errors_cummulative;
|
||||||
// Store the loaded printers into a new vector, otherwise the binary search for already existing presets would be broken.
|
// Store the loaded printers into a new vector, otherwise the binary search for already existing presets would be broken.
|
||||||
std::deque<PhysicalPrinter> printers_loaded;
|
std::deque<PhysicalPrinter> printers_loaded;
|
||||||
|
|
|
@ -1245,8 +1245,10 @@ DynamicPrintConfig PresetBundle::full_config() const
|
||||||
DynamicPrintConfig PresetBundle::full_config_secure() const
|
DynamicPrintConfig PresetBundle::full_config_secure() const
|
||||||
{
|
{
|
||||||
DynamicPrintConfig config = this->full_config();
|
DynamicPrintConfig config = this->full_config();
|
||||||
//BBS example: config.erase("print_host");
|
//FIXME legacy, the keys should not be there after conversion to a Physical Printer profile.
|
||||||
return config;
|
config.erase("print_host");
|
||||||
|
config.erase("printhost_apikey");
|
||||||
|
config.erase("printhost_cafile"); return config;
|
||||||
}
|
}
|
||||||
|
|
||||||
const std::set<std::string> ignore_settings_list ={
|
const std::set<std::string> ignore_settings_list ={
|
||||||
|
|
|
@ -43,6 +43,23 @@ static t_config_enum_values s_keys_map_PrinterTechnology {
|
||||||
};
|
};
|
||||||
CONFIG_OPTION_ENUM_DEFINE_STATIC_MAPS(PrinterTechnology)
|
CONFIG_OPTION_ENUM_DEFINE_STATIC_MAPS(PrinterTechnology)
|
||||||
|
|
||||||
|
static t_config_enum_values s_keys_map_PrintHostType {
|
||||||
|
{ "prusalink", htPrusaLink },
|
||||||
|
{ "octoprint", htOctoPrint },
|
||||||
|
{ "duet", htDuet },
|
||||||
|
{ "flashair", htFlashAir },
|
||||||
|
{ "astrobox", htAstroBox },
|
||||||
|
{ "repetier", htRepetier },
|
||||||
|
{ "mks", htMKS }
|
||||||
|
};
|
||||||
|
CONFIG_OPTION_ENUM_DEFINE_STATIC_MAPS(PrintHostType)
|
||||||
|
|
||||||
|
static t_config_enum_values s_keys_map_AuthorizationType {
|
||||||
|
{ "key", atKeyPassword },
|
||||||
|
{ "user", atUserPassword }
|
||||||
|
};
|
||||||
|
CONFIG_OPTION_ENUM_DEFINE_STATIC_MAPS(AuthorizationType)
|
||||||
|
|
||||||
static t_config_enum_values s_keys_map_GCodeFlavor {
|
static t_config_enum_values s_keys_map_GCodeFlavor {
|
||||||
{ "marlin", gcfMarlinLegacy },
|
{ "marlin", gcfMarlinLegacy },
|
||||||
{ "reprap", gcfRepRapSprinter },
|
{ "reprap", gcfRepRapSprinter },
|
||||||
|
@ -257,6 +274,7 @@ void PrintConfigDef::init_common_params()
|
||||||
|
|
||||||
def = this->add("printable_area", coPoints);
|
def = this->add("printable_area", coPoints);
|
||||||
def->label = L("Printable area");
|
def->label = L("Printable area");
|
||||||
|
|
||||||
//BBS
|
//BBS
|
||||||
def->mode = comDevelop;
|
def->mode = comDevelop;
|
||||||
def->set_default_value(new ConfigOptionPoints{ Vec2d(0, 0), Vec2d(200, 0), Vec2d(200, 200), Vec2d(0, 200) });
|
def->set_default_value(new ConfigOptionPoints{ Vec2d(0, 0), Vec2d(200, 0), Vec2d(200, 200), Vec2d(0, 200) });
|
||||||
|
@ -304,6 +322,98 @@ void PrintConfigDef::init_common_params()
|
||||||
def->mode = comDevelop;
|
def->mode = comDevelop;
|
||||||
def->set_default_value(new ConfigOptionStrings());
|
def->set_default_value(new ConfigOptionStrings());
|
||||||
|
|
||||||
|
//SoftFever
|
||||||
|
def = this->add("connection_moonraker_url", coString);
|
||||||
|
def->label = L("Moonraker URL");
|
||||||
|
//def->tooltip = L("Names of presets related to the physical printer");
|
||||||
|
def->mode = comAdvanced;
|
||||||
|
def->set_default_value(new ConfigOptionString("http://"));
|
||||||
|
|
||||||
|
def = this->add("connection_port", coString);
|
||||||
|
def->label = L("Connection port");
|
||||||
|
//def->tooltip = L("Names of presets related to the physical printer");
|
||||||
|
def->mode = comAdvanced;
|
||||||
|
def->set_default_value(new ConfigOptionString("7125"));
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
def = this->add("print_host", coString);
|
||||||
|
def->label = L("Hostname, IP or URL");
|
||||||
|
def->tooltip = L("Slic3r can upload G-code files to a printer host. This field should contain "
|
||||||
|
"the hostname, IP address or URL of the printer host instance. "
|
||||||
|
"Print host behind HAProxy with basic auth enabled can be accessed by putting the user name and password into the URL "
|
||||||
|
"in the following format: https://username:password@your-octopi-address/");
|
||||||
|
def->mode = comAdvanced;
|
||||||
|
def->cli = ConfigOptionDef::nocli;
|
||||||
|
def->set_default_value(new ConfigOptionString(""));
|
||||||
|
|
||||||
|
def = this->add("printhost_apikey", coString);
|
||||||
|
def->label = L("API Key / Password");
|
||||||
|
def->tooltip = L("Slic3r can upload G-code files to a printer host. This field should contain "
|
||||||
|
"the API Key or the password required for authentication.");
|
||||||
|
def->mode = comAdvanced;
|
||||||
|
def->cli = ConfigOptionDef::nocli;
|
||||||
|
def->set_default_value(new ConfigOptionString(""));
|
||||||
|
|
||||||
|
def = this->add("printhost_port", coString);
|
||||||
|
def->label = L("Printer");
|
||||||
|
def->tooltip = L("Name of the printer");
|
||||||
|
def->gui_type = ConfigOptionDef::GUIType::select_open;
|
||||||
|
def->mode = comAdvanced;
|
||||||
|
def->cli = ConfigOptionDef::nocli;
|
||||||
|
def->set_default_value(new ConfigOptionString(""));
|
||||||
|
|
||||||
|
def = this->add("printhost_cafile", coString);
|
||||||
|
def->label = L("HTTPS CA File");
|
||||||
|
def->tooltip = L("Custom CA certificate file can be specified for HTTPS OctoPrint connections, in crt/pem format. "
|
||||||
|
"If left blank, the default OS CA certificate repository is used.");
|
||||||
|
def->mode = comAdvanced;
|
||||||
|
def->cli = ConfigOptionDef::nocli;
|
||||||
|
def->set_default_value(new ConfigOptionString(""));
|
||||||
|
|
||||||
|
// Options used by physical printers
|
||||||
|
|
||||||
|
def = this->add("printhost_user", coString);
|
||||||
|
def->label = L("User");
|
||||||
|
// def->tooltip = L("");
|
||||||
|
def->mode = comAdvanced;
|
||||||
|
def->cli = ConfigOptionDef::nocli;
|
||||||
|
def->set_default_value(new ConfigOptionString(""));
|
||||||
|
|
||||||
|
def = this->add("printhost_password", coString);
|
||||||
|
def->label = L("Password");
|
||||||
|
// def->tooltip = L("");
|
||||||
|
def->mode = comAdvanced;
|
||||||
|
def->cli = ConfigOptionDef::nocli;
|
||||||
|
def->set_default_value(new ConfigOptionString(""));
|
||||||
|
|
||||||
|
// Only available on Windows.
|
||||||
|
def = this->add("printhost_ssl_ignore_revoke", coBool);
|
||||||
|
def->label = L("Ignore HTTPS certificate revocation checks");
|
||||||
|
def->tooltip = L("Ignore HTTPS certificate revocation checks in case of missing or offline distribution points. "
|
||||||
|
"One may want to enable this option for self signed certificates if connection fails.");
|
||||||
|
def->mode = comAdvanced;
|
||||||
|
def->cli = ConfigOptionDef::nocli;
|
||||||
|
def->set_default_value(new ConfigOptionBool(false));
|
||||||
|
|
||||||
|
def = this->add("preset_names", coStrings);
|
||||||
|
def->label = L("Printer preset names");
|
||||||
|
def->tooltip = L("Names of presets related to the physical printer");
|
||||||
|
def->mode = comAdvanced;
|
||||||
|
def->set_default_value(new ConfigOptionStrings());
|
||||||
|
|
||||||
|
def = this->add("printhost_authorization_type", coEnum);
|
||||||
|
def->label = L("Authorization Type");
|
||||||
|
// def->tooltip = L("");
|
||||||
|
def->enum_keys_map = &ConfigOptionEnum<AuthorizationType>::get_enum_values();
|
||||||
|
def->enum_values.push_back("key");
|
||||||
|
def->enum_values.push_back("user");
|
||||||
|
def->enum_labels.push_back(L("API key"));
|
||||||
|
def->enum_labels.push_back(L("HTTP digest"));
|
||||||
|
def->mode = comAdvanced;
|
||||||
|
def->cli = ConfigOptionDef::nocli;
|
||||||
|
def->set_default_value(new ConfigOptionEnum<AuthorizationType>(atKeyPassword));
|
||||||
|
|
||||||
// temporary workaround for compatibility with older Slicer
|
// temporary workaround for compatibility with older Slicer
|
||||||
{
|
{
|
||||||
def = this->add("preset_name", coString);
|
def = this->add("preset_name", coString);
|
||||||
|
@ -1624,6 +1734,30 @@ void PrintConfigDef::init_fff_params()
|
||||||
def->mode = comDevelop;
|
def->mode = comDevelop;
|
||||||
def->set_default_value(new ConfigOptionFloats { 0.4 });
|
def->set_default_value(new ConfigOptionFloats { 0.4 });
|
||||||
|
|
||||||
|
def = this->add("host_type", coEnum);
|
||||||
|
def->label = L("Host Type");
|
||||||
|
def->tooltip = L("Slic3r can upload G-code files to a printer host. This field must contain "
|
||||||
|
"the kind of the host.");
|
||||||
|
def->enum_keys_map = &ConfigOptionEnum<PrintHostType>::get_enum_values();
|
||||||
|
def->enum_values.push_back("prusalink");
|
||||||
|
def->enum_values.push_back("octoprint");
|
||||||
|
def->enum_values.push_back("duet");
|
||||||
|
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<PrintHostType>(htOctoPrint));
|
||||||
|
|
||||||
|
|
||||||
def = this->add("nozzle_volume", coFloat);
|
def = this->add("nozzle_volume", coFloat);
|
||||||
def->label = L("Nozzle volume");
|
def->label = L("Nozzle volume");
|
||||||
def->tooltip = L("Volume of nozzle between the cutter and the end of nozzle");
|
def->tooltip = L("Volume of nozzle between the cutter and the end of nozzle");
|
||||||
|
|
|
@ -42,6 +42,14 @@ enum class FuzzySkinType {
|
||||||
All,
|
All,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
enum PrintHostType {
|
||||||
|
htPrusaLink, htOctoPrint, htDuet, htFlashAir, htAstroBox, htRepetier, htMKS
|
||||||
|
};
|
||||||
|
|
||||||
|
enum AuthorizationType {
|
||||||
|
atKeyPassword, atUserPassword
|
||||||
|
};
|
||||||
|
|
||||||
#define HAS_LIGHTNING_INFILL 0
|
#define HAS_LIGHTNING_INFILL 0
|
||||||
|
|
||||||
enum InfillPattern : int {
|
enum InfillPattern : int {
|
||||||
|
@ -236,6 +244,9 @@ CONFIG_OPTION_ENUM_DECLARE_STATIC_MAPS(BedType)
|
||||||
CONFIG_OPTION_ENUM_DECLARE_STATIC_MAPS(DraftShield)
|
CONFIG_OPTION_ENUM_DECLARE_STATIC_MAPS(DraftShield)
|
||||||
CONFIG_OPTION_ENUM_DECLARE_STATIC_MAPS(ForwardCompatibilitySubstitutionRule)
|
CONFIG_OPTION_ENUM_DECLARE_STATIC_MAPS(ForwardCompatibilitySubstitutionRule)
|
||||||
|
|
||||||
|
CONFIG_OPTION_ENUM_DECLARE_STATIC_MAPS(PrintHostType)
|
||||||
|
CONFIG_OPTION_ENUM_DECLARE_STATIC_MAPS(AuthorizationType)
|
||||||
|
|
||||||
#undef CONFIG_OPTION_ENUM_DECLARE_STATIC_MAPS
|
#undef CONFIG_OPTION_ENUM_DECLARE_STATIC_MAPS
|
||||||
|
|
||||||
// Defines each and every confiuration option of Slic3r, including the properties of the GUI dialogs.
|
// Defines each and every confiuration option of Slic3r, including the properties of the GUI dialogs.
|
||||||
|
@ -755,6 +766,10 @@ PRINT_CONFIG_CLASS_DEFINE(
|
||||||
//BBS
|
//BBS
|
||||||
((ConfigOptionEnum<NozzleType>, nozzle_type))
|
((ConfigOptionEnum<NozzleType>, nozzle_type))
|
||||||
((ConfigOptionBool, auxiliary_fan))
|
((ConfigOptionBool, auxiliary_fan))
|
||||||
|
//SoftFever
|
||||||
|
((ConfigOptionString, connection_moonraker_url))
|
||||||
|
((ConfigOptionString, connection_port))
|
||||||
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// This object is mapped to Perl as Slic3r::Config::Print.
|
// This object is mapped to Perl as Slic3r::Config::Print.
|
||||||
|
|
|
@ -1106,7 +1106,7 @@ std::string string_printf(const char *format, ...)
|
||||||
|
|
||||||
std::string header_slic3r_generated()
|
std::string header_slic3r_generated()
|
||||||
{
|
{
|
||||||
return std::string(SLIC3R_APP_NAME " " SLIC3R_VERSION);
|
return std::string(SLIC3R_APP_NAME "-SoftFever" " " SLIC3R_VERSION);
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string header_gcodeviewer_generated()
|
std::string header_gcodeviewer_generated()
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
project(minilzo)
|
project(minilzo)
|
||||||
cmake_minimum_required(VERSION 2.6)
|
cmake_minimum_required(VERSION 3.0)
|
||||||
|
|
||||||
add_library(minilzo INTERFACE)
|
add_library(minilzo INTERFACE)
|
||||||
|
|
||||||
|
|
|
@ -28,7 +28,7 @@ endif()
|
||||||
else(Qhull_FOUND)
|
else(Qhull_FOUND)
|
||||||
|
|
||||||
project(qhull)
|
project(qhull)
|
||||||
cmake_minimum_required(VERSION 2.6)
|
cmake_minimum_required(VERSION 3.1)
|
||||||
|
|
||||||
# Define qhull_VERSION in CMakeLists.txt, Makefile, qhull-exports.def, qhull_p-exports.def, qhull_r-exports.def, qhull-warn.pri
|
# Define qhull_VERSION in CMakeLists.txt, Makefile, qhull-exports.def, qhull_p-exports.def, qhull_r-exports.def, qhull-warn.pri
|
||||||
set(qhull_VERSION2 "2015.2 2016/01/18") # not used, See global.c, global_r.c, rbox.c, rbox_r.c
|
set(qhull_VERSION2 "2015.2 2016/01/18") # not used, See global.c, global_r.c, rbox.c, rbox_r.c
|
||||||
|
|
|
@ -177,6 +177,10 @@ set(SLIC3R_GUI_SOURCES
|
||||||
GUI/SavePresetDialog.cpp
|
GUI/SavePresetDialog.cpp
|
||||||
GUI/GUI_Colors.hpp
|
GUI/GUI_Colors.hpp
|
||||||
GUI/GUI_Colors.cpp
|
GUI/GUI_Colors.cpp
|
||||||
|
GUI/PhysicalPrinterDialog.hpp
|
||||||
|
GUI/PhysicalPrinterDialog.cpp
|
||||||
|
GUI/PrintHostDialogs.cpp
|
||||||
|
GUI/PrintHostDialogs.hpp
|
||||||
GUI/GUI_Factories.cpp
|
GUI/GUI_Factories.cpp
|
||||||
GUI/GUI_Factories.hpp
|
GUI/GUI_Factories.hpp
|
||||||
GUI/GUI_ObjectList.cpp
|
GUI/GUI_ObjectList.cpp
|
||||||
|
@ -348,6 +352,8 @@ set(SLIC3R_GUI_SOURCES
|
||||||
GUI/Calibration.cpp
|
GUI/Calibration.cpp
|
||||||
GUI/PrintOptionsDialog.hpp
|
GUI/PrintOptionsDialog.hpp
|
||||||
GUI/PrintOptionsDialog.cpp
|
GUI/PrintOptionsDialog.cpp
|
||||||
|
GUI/BonjourDialog.hpp
|
||||||
|
GUI/BonjourDialog.cpp
|
||||||
Utils/json_diff.hpp
|
Utils/json_diff.hpp
|
||||||
Utils/json_diff.cpp
|
Utils/json_diff.cpp
|
||||||
GUI/KBShortcutsDialog.hpp
|
GUI/KBShortcutsDialog.hpp
|
||||||
|
@ -375,6 +381,22 @@ set(SLIC3R_GUI_SOURCES
|
||||||
Utils/ColorSpaceConvert.cpp
|
Utils/ColorSpaceConvert.cpp
|
||||||
Utils/NetworkAgent.cpp
|
Utils/NetworkAgent.cpp
|
||||||
Utils/NetworkAgent.hpp
|
Utils/NetworkAgent.hpp
|
||||||
|
Utils/OctoPrint.cpp
|
||||||
|
Utils/OctoPrint.hpp
|
||||||
|
Utils/PrintHost.cpp
|
||||||
|
Utils/PrintHost.hpp
|
||||||
|
Utils/Serial.cpp
|
||||||
|
Utils/Serial.hpp
|
||||||
|
Utils/MKS.hpp
|
||||||
|
Utils/MKS.cpp
|
||||||
|
Utils/Duet.cpp
|
||||||
|
Utils/Duet.hpp
|
||||||
|
Utils/FlashAir.cpp
|
||||||
|
Utils/FlashAir.hpp
|
||||||
|
Utils/AstroBox.cpp
|
||||||
|
Utils/AstroBox.hpp
|
||||||
|
Utils/Repetier.cpp
|
||||||
|
Utils/Repetier.hpp
|
||||||
)
|
)
|
||||||
|
|
||||||
if (APPLE)
|
if (APPLE)
|
||||||
|
|
|
@ -227,6 +227,9 @@ void BackgroundSlicingProcess::process_fff()
|
||||||
if (! m_export_path.empty()) {
|
if (! m_export_path.empty()) {
|
||||||
wxQueueEvent(GUI::wxGetApp().mainframe->m_plater, new wxCommandEvent(m_event_export_began_id));
|
wxQueueEvent(GUI::wxGetApp().mainframe->m_plater, new wxCommandEvent(m_event_export_began_id));
|
||||||
finalize_gcode();
|
finalize_gcode();
|
||||||
|
} else if (! m_upload_job.empty()) {
|
||||||
|
wxQueueEvent(GUI::wxGetApp().mainframe->m_plater, new wxCommandEvent(m_event_export_began_id));
|
||||||
|
prepare_upload();
|
||||||
} else {
|
} else {
|
||||||
m_print->set_status(100, _utf8(L("Slicing complete")));
|
m_print->set_status(100, _utf8(L("Slicing complete")));
|
||||||
}
|
}
|
||||||
|
@ -684,6 +687,19 @@ void BackgroundSlicingProcess::schedule_export(const std::string &path, bool exp
|
||||||
m_export_path_on_removable_media = export_path_on_removable_media;
|
m_export_path_on_removable_media = export_path_on_removable_media;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void BackgroundSlicingProcess::schedule_upload(Slic3r::PrintHostJob upload_job)
|
||||||
|
{
|
||||||
|
assert(m_export_path.empty());
|
||||||
|
if (! m_export_path.empty())
|
||||||
|
return;
|
||||||
|
|
||||||
|
// Guard against entering the export step before changing the export path.
|
||||||
|
std::scoped_lock<std::mutex> lock(m_print->state_mutex());
|
||||||
|
this->invalidate_step(bspsGCodeFinalize);
|
||||||
|
m_export_path.clear();
|
||||||
|
m_upload_job = std::move(upload_job);
|
||||||
|
}
|
||||||
|
|
||||||
void BackgroundSlicingProcess::reset_export()
|
void BackgroundSlicingProcess::reset_export()
|
||||||
{
|
{
|
||||||
assert(! this->running());
|
assert(! this->running());
|
||||||
|
@ -800,6 +816,45 @@ void BackgroundSlicingProcess::finalize_gcode()
|
||||||
m_print->set_status(100, (boost::format(_utf8(L("Succeed to export G-code to %1%"))) % export_path).str());
|
m_print->set_status(100, (boost::format(_utf8(L("Succeed to export G-code to %1%"))) % export_path).str());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// A print host upload job has been scheduled, enqueue it to the printhost job queue
|
||||||
|
void BackgroundSlicingProcess::prepare_upload()
|
||||||
|
{
|
||||||
|
// Generate a unique temp path to which the gcode/zip file is copied/exported
|
||||||
|
boost::filesystem::path source_path = boost::filesystem::temp_directory_path()
|
||||||
|
/ boost::filesystem::unique_path("." SLIC3R_APP_KEY ".upload.%%%%-%%%%-%%%%-%%%%");
|
||||||
|
|
||||||
|
if (m_print == m_fff_print) {
|
||||||
|
m_print->set_status(95, _utf8(L("Running post-processing scripts")));
|
||||||
|
std::string error_message;
|
||||||
|
if (copy_file(m_temp_output_path, source_path.string(), error_message) != SUCCESS)
|
||||||
|
throw Slic3r::RuntimeError(_utf8(L("Copying of the temporary G-code to the output G-code failed")));
|
||||||
|
m_upload_job.upload_data.upload_path = m_fff_print->print_statistics().finalize_output_path(m_upload_job.upload_data.upload_path.string());
|
||||||
|
// Make a copy of the source path, as run_post_process_scripts() is allowed to change it when making a copy of the source file
|
||||||
|
// (not here, but when the final target is a file).
|
||||||
|
std::string source_path_str = source_path.string();
|
||||||
|
std::string output_name_str = m_upload_job.upload_data.upload_path.string();
|
||||||
|
if (run_post_process_scripts(source_path_str, false, m_upload_job.printhost->get_name(), output_name_str, m_fff_print->full_print_config()))
|
||||||
|
m_upload_job.upload_data.upload_path = output_name_str;
|
||||||
|
} else {
|
||||||
|
m_upload_job.upload_data.upload_path = m_sla_print->print_statistics().finalize_output_path(m_upload_job.upload_data.upload_path.string());
|
||||||
|
|
||||||
|
ThumbnailsList thumbnails = this->render_thumbnails(
|
||||||
|
ThumbnailsParams{current_print()->full_print_config().option<ConfigOptionPoints>("thumbnails")->values, true, true, true, true});
|
||||||
|
// true, false, true, true); // renders also supports and pad
|
||||||
|
Zipper zipper{source_path.string()};
|
||||||
|
m_sla_archive.export_print(zipper, *m_sla_print, m_upload_job.upload_data.upload_path.string());
|
||||||
|
for (const ThumbnailData& data : thumbnails)
|
||||||
|
if (data.is_valid())
|
||||||
|
write_thumbnail(zipper, data);
|
||||||
|
zipper.finalize();
|
||||||
|
}
|
||||||
|
|
||||||
|
m_print->set_status(100, (boost::format(_utf8(L("Scheduling upload to `%1%`. See Window -> Print Host Upload Queue"))) % m_upload_job.printhost->get_host()).str());
|
||||||
|
|
||||||
|
m_upload_job.upload_data.source_path = std::move(source_path);
|
||||||
|
|
||||||
|
GUI::wxGetApp().printhost_job_queue().enqueue(std::move(m_upload_job));
|
||||||
|
}
|
||||||
// Executed by the background thread, to start a task on the UI thread.
|
// Executed by the background thread, to start a task on the UI thread.
|
||||||
ThumbnailsList BackgroundSlicingProcess::render_thumbnails(const ThumbnailsParams ¶ms)
|
ThumbnailsList BackgroundSlicingProcess::render_thumbnails(const ThumbnailsParams ¶ms)
|
||||||
{
|
{
|
||||||
|
|
|
@ -12,6 +12,7 @@
|
||||||
#include "libslic3r/PrintBase.hpp"
|
#include "libslic3r/PrintBase.hpp"
|
||||||
#include "libslic3r/GCode/ThumbnailData.hpp"
|
#include "libslic3r/GCode/ThumbnailData.hpp"
|
||||||
#include "libslic3r/Format/SL1.hpp"
|
#include "libslic3r/Format/SL1.hpp"
|
||||||
|
#include "slic3r/Utils/PrintHost.hpp"
|
||||||
#include "libslic3r/GCode/GCodeProcessor.hpp"
|
#include "libslic3r/GCode/GCodeProcessor.hpp"
|
||||||
#include "PartPlate.hpp"
|
#include "PartPlate.hpp"
|
||||||
|
|
||||||
|
@ -148,10 +149,14 @@ public:
|
||||||
// Set the export path of the G-code.
|
// Set the export path of the G-code.
|
||||||
// Once the path is set, the G-code
|
// Once the path is set, the G-code
|
||||||
void schedule_export(const std::string &path, bool export_path_on_removable_media);
|
void schedule_export(const std::string &path, bool export_path_on_removable_media);
|
||||||
|
// Set print host upload job data to be enqueued to the PrintHostJobQueue
|
||||||
|
// after current print slicing is complete
|
||||||
|
void schedule_upload(Slic3r::PrintHostJob upload_job);
|
||||||
// Clear m_export_path.
|
// Clear m_export_path.
|
||||||
void reset_export();
|
void reset_export();
|
||||||
// Once the G-code export is scheduled, the apply() methods will do nothing.
|
// Once the G-code export is scheduled, the apply() methods will do nothing.
|
||||||
bool is_export_scheduled() const { return ! m_export_path.empty(); }
|
bool is_export_scheduled() const { return ! m_export_path.empty(); }
|
||||||
|
bool is_upload_scheduled() const { return ! m_upload_job.empty(); }
|
||||||
|
|
||||||
enum State {
|
enum State {
|
||||||
// m_thread is not running yet, or it did not reach the STATE_IDLE yet (it does not wait on the condition yet).
|
// m_thread is not running yet, or it did not reach the STATE_IDLE yet (it does not wait on the condition yet).
|
||||||
|
@ -238,6 +243,9 @@ private:
|
||||||
// but once set, it cannot be re-set.
|
// but once set, it cannot be re-set.
|
||||||
std::string m_export_path;
|
std::string m_export_path;
|
||||||
bool m_export_path_on_removable_media = false;
|
bool m_export_path_on_removable_media = false;
|
||||||
|
// Print host upload job to schedule after slicing is complete, used by schedule_upload(),
|
||||||
|
// empty by default (ie. no upload to schedule)
|
||||||
|
PrintHostJob m_upload_job;
|
||||||
// Thread, on which the background processing is executed. The thread will always be present
|
// Thread, on which the background processing is executed. The thread will always be present
|
||||||
// and ready to execute the slicing process.
|
// and ready to execute the slicing process.
|
||||||
boost::thread m_thread;
|
boost::thread m_thread;
|
||||||
|
@ -276,6 +284,7 @@ private:
|
||||||
// If the background processing stop was requested, throw CanceledException.
|
// If the background processing stop was requested, throw CanceledException.
|
||||||
void throw_if_canceled() const { if (m_print->canceled()) throw CanceledException(); }
|
void throw_if_canceled() const { if (m_print->canceled()) throw CanceledException(); }
|
||||||
void finalize_gcode();
|
void finalize_gcode();
|
||||||
|
void prepare_upload();
|
||||||
// To be executed at the background thread.
|
// To be executed at the background thread.
|
||||||
ThumbnailsList render_thumbnails(const ThumbnailsParams ¶ms);
|
ThumbnailsList render_thumbnails(const ThumbnailsParams ¶ms);
|
||||||
// Execute task from background thread on the UI thread synchronously. Returns true if processed, false if cancelled before executing the task.
|
// Execute task from background thread on the UI thread synchronously. Returns true if processed, false if cancelled before executing the task.
|
||||||
|
|
239
src/slic3r/GUI/BonjourDialog.cpp
Normal file
239
src/slic3r/GUI/BonjourDialog.cpp
Normal file
|
@ -0,0 +1,239 @@
|
||||||
|
#include "slic3r/Utils/Bonjour.hpp" // On Windows, boost needs to be included before wxWidgets headers
|
||||||
|
|
||||||
|
#include "BonjourDialog.hpp"
|
||||||
|
|
||||||
|
#include <set>
|
||||||
|
#include <mutex>
|
||||||
|
|
||||||
|
#include <wx/sizer.h>
|
||||||
|
#include <wx/button.h>
|
||||||
|
#include <wx/listctrl.h>
|
||||||
|
#include <wx/stattext.h>
|
||||||
|
#include <wx/timer.h>
|
||||||
|
#include <wx/wupdlock.h>
|
||||||
|
|
||||||
|
#include "slic3r/GUI/GUI.hpp"
|
||||||
|
#include "slic3r/GUI/GUI_App.hpp"
|
||||||
|
#include "slic3r/GUI/I18N.hpp"
|
||||||
|
#include "slic3r/Utils/Bonjour.hpp"
|
||||||
|
|
||||||
|
namespace Slic3r {
|
||||||
|
|
||||||
|
|
||||||
|
class BonjourReplyEvent : public wxEvent
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
BonjourReply reply;
|
||||||
|
|
||||||
|
BonjourReplyEvent(wxEventType eventType, int winid, BonjourReply &&reply) :
|
||||||
|
wxEvent(winid, eventType),
|
||||||
|
reply(std::move(reply))
|
||||||
|
{}
|
||||||
|
|
||||||
|
virtual wxEvent *Clone() const
|
||||||
|
{
|
||||||
|
return new BonjourReplyEvent(*this);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
wxDEFINE_EVENT(EVT_BONJOUR_REPLY, BonjourReplyEvent);
|
||||||
|
|
||||||
|
wxDECLARE_EVENT(EVT_BONJOUR_COMPLETE, wxCommandEvent);
|
||||||
|
wxDEFINE_EVENT(EVT_BONJOUR_COMPLETE, wxCommandEvent);
|
||||||
|
|
||||||
|
class ReplySet: public std::set<BonjourReply> {};
|
||||||
|
|
||||||
|
struct LifetimeGuard
|
||||||
|
{
|
||||||
|
std::mutex mutex;
|
||||||
|
BonjourDialog *dialog;
|
||||||
|
|
||||||
|
LifetimeGuard(BonjourDialog *dialog) : dialog(dialog) {}
|
||||||
|
};
|
||||||
|
|
||||||
|
BonjourDialog::BonjourDialog(wxWindow *parent, Slic3r::PrinterTechnology tech)
|
||||||
|
: wxDialog(parent, wxID_ANY, _(L("Network lookup")), wxDefaultPosition, wxDefaultSize, wxDEFAULT_DIALOG_STYLE|wxRESIZE_BORDER)
|
||||||
|
, list(new wxListView(this, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxLC_REPORT|wxSIMPLE_BORDER))
|
||||||
|
, replies(new ReplySet)
|
||||||
|
, label(new wxStaticText(this, wxID_ANY, ""))
|
||||||
|
, timer(new wxTimer())
|
||||||
|
, timer_state(0)
|
||||||
|
, tech(tech)
|
||||||
|
{
|
||||||
|
const int em = GUI::wxGetApp().em_unit();
|
||||||
|
list->SetMinSize(wxSize(80 * em, 30 * em));
|
||||||
|
|
||||||
|
wxBoxSizer *vsizer = new wxBoxSizer(wxVERTICAL);
|
||||||
|
|
||||||
|
vsizer->Add(label, 0, wxEXPAND | wxTOP | wxLEFT | wxRIGHT, em);
|
||||||
|
|
||||||
|
list->SetSingleStyle(wxLC_SINGLE_SEL);
|
||||||
|
list->SetSingleStyle(wxLC_SORT_DESCENDING);
|
||||||
|
list->AppendColumn(_(L("Address")), wxLIST_FORMAT_LEFT, 5 * em);
|
||||||
|
list->AppendColumn(_(L("Hostname")), wxLIST_FORMAT_LEFT, 10 * em);
|
||||||
|
list->AppendColumn(_(L("Service name")), wxLIST_FORMAT_LEFT, 20 * em);
|
||||||
|
if (tech == ptFFF) {
|
||||||
|
list->AppendColumn(_(L("OctoPrint version")), wxLIST_FORMAT_LEFT, 5 * em);
|
||||||
|
}
|
||||||
|
|
||||||
|
vsizer->Add(list, 1, wxEXPAND | wxALL, em);
|
||||||
|
|
||||||
|
wxBoxSizer *button_sizer = new wxBoxSizer(wxHORIZONTAL);
|
||||||
|
button_sizer->Add(new wxButton(this, wxID_OK, "OK"), 0, wxALL, em);
|
||||||
|
button_sizer->Add(new wxButton(this, wxID_CANCEL, "Cancel"), 0, wxALL, em);
|
||||||
|
// ^ Note: The Ok/Cancel labels are translated by wxWidgets
|
||||||
|
|
||||||
|
vsizer->Add(button_sizer, 0, wxALIGN_CENTER);
|
||||||
|
SetSizerAndFit(vsizer);
|
||||||
|
|
||||||
|
Bind(EVT_BONJOUR_REPLY, &BonjourDialog::on_reply, this);
|
||||||
|
|
||||||
|
Bind(EVT_BONJOUR_COMPLETE, [this](wxCommandEvent &) {
|
||||||
|
this->timer_state = 0;
|
||||||
|
});
|
||||||
|
|
||||||
|
Bind(wxEVT_TIMER, &BonjourDialog::on_timer, this);
|
||||||
|
GUI::wxGetApp().UpdateDlgDarkUI(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
BonjourDialog::~BonjourDialog()
|
||||||
|
{
|
||||||
|
// Needed bacuse of forward defs
|
||||||
|
}
|
||||||
|
|
||||||
|
bool BonjourDialog::show_and_lookup()
|
||||||
|
{
|
||||||
|
Show(); // Because we need GetId() to work before ShowModal()
|
||||||
|
|
||||||
|
timer->Stop();
|
||||||
|
timer->SetOwner(this);
|
||||||
|
timer_state = 1;
|
||||||
|
timer->Start(1000);
|
||||||
|
on_timer_process();
|
||||||
|
|
||||||
|
// The background thread needs to queue messages for this dialog
|
||||||
|
// and for that it needs a valid pointer to it (mandated by the wxWidgets API).
|
||||||
|
// Here we put the pointer under a shared_ptr and protect it by a mutex,
|
||||||
|
// so that both threads can access it safely.
|
||||||
|
auto dguard = std::make_shared<LifetimeGuard>(this);
|
||||||
|
|
||||||
|
// Note: More can be done here when we support discovery of hosts other than Octoprint and SL1
|
||||||
|
Bonjour::TxtKeys txt_keys { "version", "model" };
|
||||||
|
|
||||||
|
bonjour = Bonjour("octoprint")
|
||||||
|
.set_txt_keys(std::move(txt_keys))
|
||||||
|
.set_retries(3)
|
||||||
|
.set_timeout(4)
|
||||||
|
.on_reply([dguard](BonjourReply &&reply) {
|
||||||
|
std::lock_guard<std::mutex> lock_guard(dguard->mutex);
|
||||||
|
auto dialog = dguard->dialog;
|
||||||
|
if (dialog != nullptr) {
|
||||||
|
auto evt = new BonjourReplyEvent(EVT_BONJOUR_REPLY, dialog->GetId(), std::move(reply));
|
||||||
|
wxQueueEvent(dialog, evt);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.on_complete([dguard]() {
|
||||||
|
std::lock_guard<std::mutex> lock_guard(dguard->mutex);
|
||||||
|
auto dialog = dguard->dialog;
|
||||||
|
if (dialog != nullptr) {
|
||||||
|
auto evt = new wxCommandEvent(EVT_BONJOUR_COMPLETE, dialog->GetId());
|
||||||
|
wxQueueEvent(dialog, evt);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.lookup();
|
||||||
|
|
||||||
|
bool res = ShowModal() == wxID_OK && list->GetFirstSelected() >= 0;
|
||||||
|
{
|
||||||
|
// Tell the background thread the dialog is going away...
|
||||||
|
std::lock_guard<std::mutex> lock_guard(dguard->mutex);
|
||||||
|
dguard->dialog = nullptr;
|
||||||
|
}
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
wxString BonjourDialog::get_selected() const
|
||||||
|
{
|
||||||
|
auto sel = list->GetFirstSelected();
|
||||||
|
return sel >= 0 ? list->GetItemText(sel) : wxString();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Private
|
||||||
|
|
||||||
|
void BonjourDialog::on_reply(BonjourReplyEvent &e)
|
||||||
|
{
|
||||||
|
if (replies->find(e.reply) != replies->end()) {
|
||||||
|
// We already have this reply
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Filter replies based on selected technology
|
||||||
|
const auto model = e.reply.txt_data.find("model");
|
||||||
|
const bool sl1 = model != e.reply.txt_data.end() && model->second == "SL1";
|
||||||
|
if ((tech == ptFFF && sl1) || (tech == ptSLA && !sl1)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
replies->insert(std::move(e.reply));
|
||||||
|
|
||||||
|
auto selected = get_selected();
|
||||||
|
|
||||||
|
wxWindowUpdateLocker freeze_guard(this);
|
||||||
|
(void)freeze_guard;
|
||||||
|
|
||||||
|
list->DeleteAllItems();
|
||||||
|
|
||||||
|
// The whole list is recreated so that we benefit from it already being sorted in the set.
|
||||||
|
// (And also because wxListView's sorting API is bananas.)
|
||||||
|
for (const auto &reply : *replies) {
|
||||||
|
auto item = list->InsertItem(0, reply.full_address);
|
||||||
|
list->SetItem(item, 1, reply.hostname);
|
||||||
|
list->SetItem(item, 2, reply.service_name);
|
||||||
|
|
||||||
|
if (tech == ptFFF) {
|
||||||
|
const auto it = reply.txt_data.find("version");
|
||||||
|
if (it != reply.txt_data.end()) {
|
||||||
|
list->SetItem(item, 3, GUI::from_u8(it->second));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const int em = GUI::wxGetApp().em_unit();
|
||||||
|
|
||||||
|
for (int i = 0; i < list->GetColumnCount(); i++) {
|
||||||
|
list->SetColumnWidth(i, wxLIST_AUTOSIZE);
|
||||||
|
if (list->GetColumnWidth(i) < 10 * em) { list->SetColumnWidth(i, 10 * em); }
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!selected.IsEmpty()) {
|
||||||
|
// Attempt to preserve selection
|
||||||
|
auto hit = list->FindItem(-1, selected);
|
||||||
|
if (hit >= 0) { list->SetItemState(hit, wxLIST_STATE_SELECTED, wxLIST_STATE_SELECTED); }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void BonjourDialog::on_timer(wxTimerEvent &)
|
||||||
|
{
|
||||||
|
on_timer_process();
|
||||||
|
}
|
||||||
|
|
||||||
|
// This is here so the function can be bound to wxEVT_TIMER and also called
|
||||||
|
// explicitly (wxTimerEvent should not be created by user code).
|
||||||
|
void BonjourDialog::on_timer_process()
|
||||||
|
{
|
||||||
|
const auto search_str = _utf8(L("Searching for devices"));
|
||||||
|
|
||||||
|
if (timer_state > 0) {
|
||||||
|
const std::string dots(timer_state, '.');
|
||||||
|
label->SetLabel(GUI::from_u8((boost::format("%1% %2%") % search_str % dots).str()));
|
||||||
|
timer_state = (timer_state) % 3 + 1;
|
||||||
|
} else {
|
||||||
|
label->SetLabel(GUI::from_u8((boost::format("%1%: %2%") % search_str % (_utf8(L("Finished"))+".")).str()));
|
||||||
|
timer->Stop();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
53
src/slic3r/GUI/BonjourDialog.hpp
Normal file
53
src/slic3r/GUI/BonjourDialog.hpp
Normal file
|
@ -0,0 +1,53 @@
|
||||||
|
#ifndef slic3r_BonjourDialog_hpp_
|
||||||
|
#define slic3r_BonjourDialog_hpp_
|
||||||
|
|
||||||
|
#include <memory>
|
||||||
|
|
||||||
|
#include <wx/dialog.h>
|
||||||
|
|
||||||
|
#include "libslic3r/PrintConfig.hpp"
|
||||||
|
|
||||||
|
class wxListView;
|
||||||
|
class wxStaticText;
|
||||||
|
class wxTimer;
|
||||||
|
class wxTimerEvent;
|
||||||
|
|
||||||
|
|
||||||
|
namespace Slic3r {
|
||||||
|
|
||||||
|
class Bonjour;
|
||||||
|
class BonjourReplyEvent;
|
||||||
|
class ReplySet;
|
||||||
|
|
||||||
|
|
||||||
|
class BonjourDialog: public wxDialog
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
BonjourDialog(wxWindow *parent, Slic3r::PrinterTechnology);
|
||||||
|
BonjourDialog(BonjourDialog &&) = delete;
|
||||||
|
BonjourDialog(const BonjourDialog &) = delete;
|
||||||
|
BonjourDialog &operator=(BonjourDialog &&) = delete;
|
||||||
|
BonjourDialog &operator=(const BonjourDialog &) = delete;
|
||||||
|
~BonjourDialog();
|
||||||
|
|
||||||
|
bool show_and_lookup();
|
||||||
|
wxString get_selected() const;
|
||||||
|
private:
|
||||||
|
wxListView *list;
|
||||||
|
std::unique_ptr<ReplySet> replies;
|
||||||
|
wxStaticText *label;
|
||||||
|
std::shared_ptr<Bonjour> bonjour;
|
||||||
|
std::unique_ptr<wxTimer> timer;
|
||||||
|
unsigned timer_state;
|
||||||
|
Slic3r::PrinterTechnology tech;
|
||||||
|
|
||||||
|
void on_reply(BonjourReplyEvent &);
|
||||||
|
void on_timer(wxTimerEvent &);
|
||||||
|
void on_timer_process();
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
|
@ -1225,7 +1225,9 @@ void Choice::set_value(const boost::any& value, bool change_event)
|
||||||
// BBS
|
// BBS
|
||||||
case coEnums: {
|
case coEnums: {
|
||||||
int val = boost::any_cast<int>(value);
|
int val = boost::any_cast<int>(value);
|
||||||
|
if (m_opt_id.compare("host_type") == 0 && val != 0 &&
|
||||||
|
m_opt.enum_values.size() > field->GetCount()) // for case, when PrusaLink isn't used as a HostType
|
||||||
|
val--;
|
||||||
if (m_opt_id == "top_surface_pattern" || m_opt_id == "bottom_surface_pattern" || m_opt_id == "sparse_infill_pattern")
|
if (m_opt_id == "top_surface_pattern" || m_opt_id == "bottom_surface_pattern" || m_opt_id == "sparse_infill_pattern")
|
||||||
{
|
{
|
||||||
std::string key;
|
std::string key;
|
||||||
|
@ -1305,7 +1307,11 @@ boost::any& Choice::get_value()
|
||||||
// BBS
|
// BBS
|
||||||
if (m_opt.type == coEnum || m_opt.type == coEnums)
|
if (m_opt.type == coEnum || m_opt.type == coEnums)
|
||||||
{
|
{
|
||||||
if (m_opt_id == "top_surface_pattern" || m_opt_id == "bottom_surface_pattern" || m_opt_id == "sparse_infill_pattern") {
|
if (m_opt_id.compare("host_type") == 0 && m_opt.enum_values.size() > field->GetCount()) {
|
||||||
|
// for case, when PrusaLink isn't used as a HostType
|
||||||
|
m_value = field->GetSelection()+1;
|
||||||
|
}
|
||||||
|
else if (m_opt_id == "top_surface_pattern" || m_opt_id == "bottom_surface_pattern" || m_opt_id == "sparse_infill_pattern") {
|
||||||
const std::string& key = m_opt.enum_values[field->GetSelection()];
|
const std::string& key = m_opt.enum_values[field->GetSelection()];
|
||||||
m_value = int(ConfigOptionEnum<InfillPattern>::get_enum_values().at(key));
|
m_value = int(ConfigOptionEnum<InfillPattern>::get_enum_values().at(key));
|
||||||
}
|
}
|
||||||
|
|
|
@ -24,6 +24,7 @@ wxDEFINE_EVENT(EVT_GLTOOLBAR_SLICE_PLATE, SimpleEvent);
|
||||||
wxDEFINE_EVENT(EVT_GLTOOLBAR_PRINT_ALL, SimpleEvent);
|
wxDEFINE_EVENT(EVT_GLTOOLBAR_PRINT_ALL, SimpleEvent);
|
||||||
wxDEFINE_EVENT(EVT_GLTOOLBAR_PRINT_PLATE, SimpleEvent);
|
wxDEFINE_EVENT(EVT_GLTOOLBAR_PRINT_PLATE, SimpleEvent);
|
||||||
wxDEFINE_EVENT(EVT_GLTOOLBAR_EXPORT_GCODE, SimpleEvent);
|
wxDEFINE_EVENT(EVT_GLTOOLBAR_EXPORT_GCODE, SimpleEvent);
|
||||||
|
wxDEFINE_EVENT(EVT_GLTOOLBAR_SEND_GCODE, SimpleEvent);
|
||||||
wxDEFINE_EVENT(EVT_GLTOOLBAR_EXPORT_SLICED_FILE, SimpleEvent);
|
wxDEFINE_EVENT(EVT_GLTOOLBAR_EXPORT_SLICED_FILE, SimpleEvent);
|
||||||
wxDEFINE_EVENT(EVT_GLTOOLBAR_PRINT_SELECT, SimpleEvent);
|
wxDEFINE_EVENT(EVT_GLTOOLBAR_PRINT_SELECT, SimpleEvent);
|
||||||
|
|
||||||
|
|
|
@ -24,6 +24,7 @@ wxDECLARE_EVENT(EVT_GLTOOLBAR_SLICE_PLATE, SimpleEvent);
|
||||||
wxDECLARE_EVENT(EVT_GLTOOLBAR_PRINT_ALL, SimpleEvent);
|
wxDECLARE_EVENT(EVT_GLTOOLBAR_PRINT_ALL, SimpleEvent);
|
||||||
wxDECLARE_EVENT(EVT_GLTOOLBAR_PRINT_PLATE, SimpleEvent);
|
wxDECLARE_EVENT(EVT_GLTOOLBAR_PRINT_PLATE, SimpleEvent);
|
||||||
wxDECLARE_EVENT(EVT_GLTOOLBAR_EXPORT_GCODE, SimpleEvent);
|
wxDECLARE_EVENT(EVT_GLTOOLBAR_EXPORT_GCODE, SimpleEvent);
|
||||||
|
wxDECLARE_EVENT(EVT_GLTOOLBAR_SEND_GCODE, SimpleEvent);
|
||||||
wxDECLARE_EVENT(EVT_GLTOOLBAR_EXPORT_SLICED_FILE, SimpleEvent);
|
wxDECLARE_EVENT(EVT_GLTOOLBAR_EXPORT_SLICED_FILE, SimpleEvent);
|
||||||
wxDECLARE_EVENT(EVT_GLTOOLBAR_PRINT_SELECT, SimpleEvent);
|
wxDECLARE_EVENT(EVT_GLTOOLBAR_PRINT_SELECT, SimpleEvent);
|
||||||
|
|
||||||
|
|
|
@ -56,6 +56,7 @@
|
||||||
#include "GLCanvas3D.hpp"
|
#include "GLCanvas3D.hpp"
|
||||||
|
|
||||||
#include "../Utils/PresetUpdater.hpp"
|
#include "../Utils/PresetUpdater.hpp"
|
||||||
|
#include "../Utils/PrintHost.hpp"
|
||||||
#include "../Utils/Process.hpp"
|
#include "../Utils/Process.hpp"
|
||||||
#include "../Utils/MacDarkMode.hpp"
|
#include "../Utils/MacDarkMode.hpp"
|
||||||
#include "../Utils/Http.hpp"
|
#include "../Utils/Http.hpp"
|
||||||
|
@ -70,6 +71,7 @@
|
||||||
#include "NotificationManager.hpp"
|
#include "NotificationManager.hpp"
|
||||||
#include "UnsavedChangesDialog.hpp"
|
#include "UnsavedChangesDialog.hpp"
|
||||||
#include "SavePresetDialog.hpp"
|
#include "SavePresetDialog.hpp"
|
||||||
|
#include "PrintHostDialogs.hpp"
|
||||||
#include "DesktopIntegrationDialog.hpp"
|
#include "DesktopIntegrationDialog.hpp"
|
||||||
#include "SendSystemInfoDialog.hpp"
|
#include "SendSystemInfoDialog.hpp"
|
||||||
#include "ParamsDialog.hpp"
|
#include "ParamsDialog.hpp"
|
||||||
|
@ -2081,6 +2083,8 @@ bool GUI_App::on_init_inner()
|
||||||
|
|
||||||
plater_->init_notification_manager();
|
plater_->init_notification_manager();
|
||||||
|
|
||||||
|
m_printhost_job_queue.reset(new PrintHostJobQueue(mainframe->printhost_queue_dlg()));
|
||||||
|
|
||||||
if (is_gcode_viewer()) {
|
if (is_gcode_viewer()) {
|
||||||
mainframe->update_layout();
|
mainframe->update_layout();
|
||||||
if (plater_ != nullptr)
|
if (plater_ != nullptr)
|
||||||
|
@ -2596,6 +2600,7 @@ void GUI_App::recreate_GUI(const wxString& msg_name)
|
||||||
old_main_frame->Destroy();
|
old_main_frame->Destroy();
|
||||||
|
|
||||||
dlg.Update(80, _L("Loading current presets") + dots);
|
dlg.Update(80, _L("Loading current presets") + dots);
|
||||||
|
m_printhost_job_queue.reset(new PrintHostJobQueue(mainframe->printhost_queue_dlg()));
|
||||||
load_current_presets();
|
load_current_presets();
|
||||||
mainframe->Show(true);
|
mainframe->Show(true);
|
||||||
//mainframe->refresh_plugin_tips();
|
//mainframe->refresh_plugin_tips();
|
||||||
|
@ -4301,6 +4306,34 @@ bool GUI_App::can_load_project()
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool GUI_App::check_print_host_queue()
|
||||||
|
{
|
||||||
|
wxString dirty;
|
||||||
|
std::vector<std::pair<std::string, std::string>> jobs;
|
||||||
|
// Get ongoing jobs from dialog
|
||||||
|
mainframe->m_printhost_queue_dlg->get_active_jobs(jobs);
|
||||||
|
if (jobs.empty())
|
||||||
|
return true;
|
||||||
|
// Show dialog
|
||||||
|
wxString job_string = wxString();
|
||||||
|
for (const auto& job : jobs) {
|
||||||
|
job_string += format_wxstr(" %1% : %2% \n", job.first, job.second);
|
||||||
|
}
|
||||||
|
wxString message;
|
||||||
|
message += _(L("The uploads are still ongoing")) + ":\n\n" + job_string +"\n" + _(L("Stop them and continue anyway?"));
|
||||||
|
//wxMessageDialog dialog(mainframe,
|
||||||
|
MessageDialog dialog(mainframe,
|
||||||
|
message,
|
||||||
|
wxString(SLIC3R_APP_NAME) + " - " + _(L("Ongoing uploads")),
|
||||||
|
wxICON_QUESTION | wxYES_NO | wxNO_DEFAULT);
|
||||||
|
if (dialog.ShowModal() == wxID_YES)
|
||||||
|
return true;
|
||||||
|
|
||||||
|
// TODO: If already shown, bring forward
|
||||||
|
mainframe->m_printhost_queue_dlg->Show();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
bool GUI_App::checked_tab(Tab* tab)
|
bool GUI_App::checked_tab(Tab* tab)
|
||||||
{
|
{
|
||||||
bool ret = true;
|
bool ret = true;
|
||||||
|
|
|
@ -7,11 +7,13 @@
|
||||||
#include "ConfigWizard.hpp"
|
#include "ConfigWizard.hpp"
|
||||||
#include "OpenGLManager.hpp"
|
#include "OpenGLManager.hpp"
|
||||||
#include "libslic3r/Preset.hpp"
|
#include "libslic3r/Preset.hpp"
|
||||||
|
#include "wxExtensions.hpp"
|
||||||
#include "libslic3r/PresetBundle.hpp"
|
#include "libslic3r/PresetBundle.hpp"
|
||||||
#include "slic3r/GUI/DeviceManager.hpp"
|
#include "slic3r/GUI/DeviceManager.hpp"
|
||||||
#include "slic3r/Utils/NetworkAgent.hpp"
|
#include "slic3r/Utils/NetworkAgent.hpp"
|
||||||
#include "slic3r/GUI/WebViewDialog.hpp"
|
#include "slic3r/GUI/WebViewDialog.hpp"
|
||||||
#include "slic3r/GUI/Jobs/UpgradeNetworkJob.hpp"
|
#include "slic3r/GUI/Jobs/UpgradeNetworkJob.hpp"
|
||||||
|
#include "../Utils/PrintHost.hpp"
|
||||||
|
|
||||||
#include <wx/app.h>
|
#include <wx/app.h>
|
||||||
#include <wx/colour.h>
|
#include <wx/colour.h>
|
||||||
|
@ -42,6 +44,7 @@ class AppConfig;
|
||||||
class PresetBundle;
|
class PresetBundle;
|
||||||
class PresetUpdater;
|
class PresetUpdater;
|
||||||
class ModelObject;
|
class ModelObject;
|
||||||
|
// class PrintHostJobQueue;
|
||||||
class Model;
|
class Model;
|
||||||
class DeviceManager;
|
class DeviceManager;
|
||||||
class NetworkAgent;
|
class NetworkAgent;
|
||||||
|
@ -244,6 +247,7 @@ private:
|
||||||
//std::unique_ptr<RemovableDriveManager> m_removable_drive_manager;
|
//std::unique_ptr<RemovableDriveManager> m_removable_drive_manager;
|
||||||
|
|
||||||
std::unique_ptr<ImGuiWrapper> m_imgui;
|
std::unique_ptr<ImGuiWrapper> m_imgui;
|
||||||
|
std::unique_ptr<PrintHostJobQueue> m_printhost_job_queue;
|
||||||
//std::unique_ptr <OtherInstanceMessageHandler> m_other_instance_message_handler;
|
//std::unique_ptr <OtherInstanceMessageHandler> m_other_instance_message_handler;
|
||||||
//std::unique_ptr <wxSingleInstanceChecker> m_single_instance_checker;
|
//std::unique_ptr <wxSingleInstanceChecker> m_single_instance_checker;
|
||||||
//std::string m_instance_hash_string;
|
//std::string m_instance_hash_string;
|
||||||
|
@ -416,6 +420,7 @@ public:
|
||||||
void apply_keeped_preset_modifications();
|
void apply_keeped_preset_modifications();
|
||||||
bool check_and_keep_current_preset_changes(const wxString& caption, const wxString& header, int action_buttons, bool* postponed_apply_of_keeped_changes = nullptr);
|
bool check_and_keep_current_preset_changes(const wxString& caption, const wxString& header, int action_buttons, bool* postponed_apply_of_keeped_changes = nullptr);
|
||||||
bool can_load_project();
|
bool can_load_project();
|
||||||
|
bool check_print_host_queue();
|
||||||
bool checked_tab(Tab* tab);
|
bool checked_tab(Tab* tab);
|
||||||
//BBS: add preset combox re-active logic
|
//BBS: add preset combox re-active logic
|
||||||
void load_current_presets(bool active_preset_combox = false, bool check_printer_presets = true);
|
void load_current_presets(bool active_preset_combox = false, bool check_printer_presets = true);
|
||||||
|
@ -483,6 +488,8 @@ public:
|
||||||
|
|
||||||
ImGuiWrapper* imgui() { return m_imgui.get(); }
|
ImGuiWrapper* imgui() { return m_imgui.get(); }
|
||||||
|
|
||||||
|
PrintHostJobQueue& printhost_job_queue() { return *m_printhost_job_queue.get(); }
|
||||||
|
|
||||||
void open_web_page_localized(const std::string &http_address);
|
void open_web_page_localized(const std::string &http_address);
|
||||||
bool may_switch_to_SLA_preset(const wxString& caption);
|
bool may_switch_to_SLA_preset(const wxString& caption);
|
||||||
bool run_wizard(ConfigWizard::RunReason reason, ConfigWizard::StartPage start_page = ConfigWizard::SP_WELCOME);
|
bool run_wizard(ConfigWizard::RunReason reason, ConfigWizard::StartPage start_page = ConfigWizard::SP_WELCOME);
|
||||||
|
|
|
@ -25,6 +25,7 @@
|
||||||
#include "ProgressStatusBar.hpp"
|
#include "ProgressStatusBar.hpp"
|
||||||
#include "3DScene.hpp"
|
#include "3DScene.hpp"
|
||||||
#include "ParamsDialog.hpp"
|
#include "ParamsDialog.hpp"
|
||||||
|
#include "PrintHostDialogs.hpp"
|
||||||
#include "wxExtensions.hpp"
|
#include "wxExtensions.hpp"
|
||||||
#include "GUI_ObjectList.hpp"
|
#include "GUI_ObjectList.hpp"
|
||||||
#include "Mouse3DController.hpp"
|
#include "Mouse3DController.hpp"
|
||||||
|
@ -149,6 +150,7 @@ wxDEFINE_EVENT(EVT_SYNC_CLOUD_PRESET, SimpleEvent);
|
||||||
|
|
||||||
MainFrame::MainFrame() :
|
MainFrame::MainFrame() :
|
||||||
DPIFrame(NULL, wxID_ANY, "", wxDefaultPosition, wxDefaultSize, BORDERLESS_FRAME_STYLE, "mainframe")
|
DPIFrame(NULL, wxID_ANY, "", wxDefaultPosition, wxDefaultSize, BORDERLESS_FRAME_STYLE, "mainframe")
|
||||||
|
, m_printhost_queue_dlg(new PrintHostQueueDialog(this))
|
||||||
// BBS
|
// BBS
|
||||||
, m_recent_projects(9)
|
, m_recent_projects(9)
|
||||||
, m_settings_dialog(this)
|
, m_settings_dialog(this)
|
||||||
|
@ -399,6 +401,10 @@ DPIFrame(NULL, wxID_ANY, "", wxDefaultPosition, wxDefaultSize, BORDERLESS_FRAME_
|
||||||
BOOST_LOG_TRIVIAL(info) << __FUNCTION__<< "cancelled by close_with_confirm selection";
|
BOOST_LOG_TRIVIAL(info) << __FUNCTION__<< "cancelled by close_with_confirm selection";
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
if (event.CanVeto() && !wxGetApp().check_print_host_queue()) {
|
||||||
|
event.Veto();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
#if 0 // BBS
|
#if 0 // BBS
|
||||||
//if (m_plater != nullptr) {
|
//if (m_plater != nullptr) {
|
||||||
|
@ -1105,6 +1111,17 @@ bool MainFrame::can_export_gcode() const
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool MainFrame::can_send_gcode() const
|
||||||
|
{
|
||||||
|
if (m_plater && !m_plater->model().objects.empty())
|
||||||
|
{
|
||||||
|
auto cfg = wxGetApp().preset_bundle->printers.get_selected_preset().config;
|
||||||
|
if (const auto *print_host_opt = cfg.option<ConfigOptionString>("print_host"); print_host_opt)
|
||||||
|
return !print_host_opt->value.empty();
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
/*bool MainFrame::can_export_gcode_sd() const
|
/*bool MainFrame::can_export_gcode_sd() const
|
||||||
{
|
{
|
||||||
if (m_plater == nullptr)
|
if (m_plater == nullptr)
|
||||||
|
@ -1226,6 +1243,8 @@ wxBoxSizer* MainFrame::create_side_tools()
|
||||||
}
|
}
|
||||||
else if (m_print_select == eExportGcode)
|
else if (m_print_select == eExportGcode)
|
||||||
wxPostEvent(m_plater, SimpleEvent(EVT_GLTOOLBAR_EXPORT_GCODE));
|
wxPostEvent(m_plater, SimpleEvent(EVT_GLTOOLBAR_EXPORT_GCODE));
|
||||||
|
else if (m_print_select == eSendGcode)
|
||||||
|
wxPostEvent(m_plater, SimpleEvent(EVT_GLTOOLBAR_SEND_GCODE));
|
||||||
else if (m_print_select == eExportSlicedFile)
|
else if (m_print_select == eExportSlicedFile)
|
||||||
wxPostEvent(m_plater, SimpleEvent(EVT_GLTOOLBAR_EXPORT_SLICED_FILE));
|
wxPostEvent(m_plater, SimpleEvent(EVT_GLTOOLBAR_EXPORT_SLICED_FILE));
|
||||||
});
|
});
|
||||||
|
@ -1313,6 +1332,17 @@ wxBoxSizer* MainFrame::create_side_tools()
|
||||||
this->Layout();
|
this->Layout();
|
||||||
p->Dismiss();
|
p->Dismiss();
|
||||||
});
|
});
|
||||||
|
SideButton* send_gcode_btn = new SideButton(p, _L("Send sliced file (.gcode)"), "");
|
||||||
|
send_gcode_btn->SetCornerRadius(0);
|
||||||
|
send_gcode_btn->Bind(wxEVT_BUTTON, [this, p](wxCommandEvent&) {
|
||||||
|
m_print_btn->SetLabel(_L("Send Sliced File (.gcode)"));
|
||||||
|
m_print_select = eSendGcode;
|
||||||
|
if (m_print_enable)
|
||||||
|
m_print_enable = get_enable_print_status() && can_send_gcode();
|
||||||
|
m_print_btn->Enable(m_print_enable);
|
||||||
|
this->Layout();
|
||||||
|
p->Dismiss();
|
||||||
|
});
|
||||||
|
|
||||||
#if ENABEL_PRINT_ALL
|
#if ENABEL_PRINT_ALL
|
||||||
p->append_button(print_all_btn);
|
p->append_button(print_all_btn);
|
||||||
|
@ -1320,6 +1350,7 @@ wxBoxSizer* MainFrame::create_side_tools()
|
||||||
p->append_button(print_plate_btn);
|
p->append_button(print_plate_btn);
|
||||||
p->append_button(export_sliced_file_3mf_btn);
|
p->append_button(export_sliced_file_3mf_btn);
|
||||||
p->append_button(export_sliced_file_gcode_btn);
|
p->append_button(export_sliced_file_gcode_btn);
|
||||||
|
p->append_button(send_gcode_btn);
|
||||||
p->Popup(m_print_btn);
|
p->Popup(m_print_btn);
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
|
@ -40,6 +40,7 @@ namespace GUI
|
||||||
{
|
{
|
||||||
|
|
||||||
class Tab;
|
class Tab;
|
||||||
|
class PrintHostQueueDialog;
|
||||||
class Plater;
|
class Plater;
|
||||||
class MainFrame;
|
class MainFrame;
|
||||||
class ParamsDialog;
|
class ParamsDialog;
|
||||||
|
@ -110,6 +111,7 @@ class MainFrame : public DPIFrame
|
||||||
bool can_export_toolpaths() const;
|
bool can_export_toolpaths() const;
|
||||||
bool can_export_supports() const;
|
bool can_export_supports() const;
|
||||||
bool can_export_gcode() const;
|
bool can_export_gcode() const;
|
||||||
|
bool can_send_gcode() const;
|
||||||
//bool can_export_gcode_sd() const;
|
//bool can_export_gcode_sd() const;
|
||||||
//bool can_eject() const;
|
//bool can_eject() const;
|
||||||
bool can_slice() const;
|
bool can_slice() const;
|
||||||
|
@ -171,6 +173,7 @@ class MainFrame : public DPIFrame
|
||||||
ePrintPlate = 1,
|
ePrintPlate = 1,
|
||||||
eExportSlicedFile = 2,
|
eExportSlicedFile = 2,
|
||||||
eExportGcode = 3,
|
eExportGcode = 3,
|
||||||
|
eSendGcode = 4,
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -305,6 +308,7 @@ public:
|
||||||
|
|
||||||
// BBS. Replace title bar and menu bar with top bar.
|
// BBS. Replace title bar and menu bar with top bar.
|
||||||
BBLTopbar* m_topbar{ nullptr };
|
BBLTopbar* m_topbar{ nullptr };
|
||||||
|
PrintHostQueueDialog* printhost_queue_dlg() { return m_printhost_queue_dlg; }
|
||||||
Plater* m_plater { nullptr };
|
Plater* m_plater { nullptr };
|
||||||
//BBS: GUI refactor
|
//BBS: GUI refactor
|
||||||
MonitorPanel* m_monitor{ nullptr };
|
MonitorPanel* m_monitor{ nullptr };
|
||||||
|
@ -321,6 +325,7 @@ public:
|
||||||
SettingsDialog m_settings_dialog;
|
SettingsDialog m_settings_dialog;
|
||||||
DiffPresetDialog diff_dialog;
|
DiffPresetDialog diff_dialog;
|
||||||
wxWindow* m_plater_page{ nullptr };
|
wxWindow* m_plater_page{ nullptr };
|
||||||
|
PrintHostQueueDialog* m_printhost_queue_dlg;
|
||||||
|
|
||||||
// BBS
|
// BBS
|
||||||
mutable int m_print_select{ ePrintAll };
|
mutable int m_print_select{ ePrintAll };
|
||||||
|
|
|
@ -114,9 +114,13 @@ Button* MsgDialog::add_button(wxWindowID btn_id, bool set_focus /*= false*/, con
|
||||||
else if (label.length() >= 5 && label.length() < 8) {
|
else if (label.length() >= 5 && label.length() < 8) {
|
||||||
type = ButtonSizeMiddle;
|
type = ButtonSizeMiddle;
|
||||||
btn->SetMinSize(MSG_DIALOG_MIDDLE_BUTTON_SIZE);
|
btn->SetMinSize(MSG_DIALOG_MIDDLE_BUTTON_SIZE);
|
||||||
|
}
|
||||||
|
else if (label.length() >= 8 && label.length() < 12) {
|
||||||
|
type = ButtonSizeMiddle;
|
||||||
|
btn->SetMinSize(MSG_DIALOG_LONG_BUTTON_SIZE);
|
||||||
} else {
|
} else {
|
||||||
type = ButtonSizeLong;
|
type = ButtonSizeLong;
|
||||||
btn->SetMinSize(MSG_DIALOG_LONG_BUTTON_SIZE);
|
btn->SetMinSize(MSG_DIALOG_LONGER_BUTTON_SIZE);
|
||||||
}
|
}
|
||||||
|
|
||||||
btn->SetCornerRadius(12);
|
btn->SetCornerRadius(12);
|
||||||
|
|
|
@ -29,6 +29,7 @@ enum ButtonSizeType{
|
||||||
#define MSG_DIALOG_BUTTON_SIZE wxSize(FromDIP(58), FromDIP(24))
|
#define MSG_DIALOG_BUTTON_SIZE wxSize(FromDIP(58), FromDIP(24))
|
||||||
#define MSG_DIALOG_MIDDLE_BUTTON_SIZE wxSize(FromDIP(76), FromDIP(24))
|
#define MSG_DIALOG_MIDDLE_BUTTON_SIZE wxSize(FromDIP(76), FromDIP(24))
|
||||||
#define MSG_DIALOG_LONG_BUTTON_SIZE wxSize(FromDIP(90), FromDIP(24))
|
#define MSG_DIALOG_LONG_BUTTON_SIZE wxSize(FromDIP(90), FromDIP(24))
|
||||||
|
#define MSG_DIALOG_LONGER_BUTTON_SIZE wxSize(FromDIP(120), FromDIP(24))
|
||||||
|
|
||||||
|
|
||||||
namespace Slic3r {
|
namespace Slic3r {
|
||||||
|
|
|
@ -983,6 +983,149 @@ void NotificationManager::UpdatedItemsInfoNotification::add_type(InfoItemType ty
|
||||||
update(data);
|
update(data);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//------PrintHostUploadNotification----------------
|
||||||
|
void NotificationManager::PrintHostUploadNotification::init()
|
||||||
|
{
|
||||||
|
ProgressBarNotification::init();
|
||||||
|
if (m_state == EState::NotFading && m_uj_state == UploadJobState::PB_COMPLETED)
|
||||||
|
m_state = EState::Shown;
|
||||||
|
}
|
||||||
|
void NotificationManager::PrintHostUploadNotification::count_spaces()
|
||||||
|
{
|
||||||
|
//determine line width
|
||||||
|
m_line_height = ImGui::CalcTextSize("A").y;
|
||||||
|
|
||||||
|
m_left_indentation = m_line_height;
|
||||||
|
if (m_uj_state == UploadJobState::PB_ERROR) {
|
||||||
|
std::string text;
|
||||||
|
text = (m_data.level == NotificationLevel::ErrorNotificationLevel ? ImGui::ErrorMarker : ImGui::WarningMarker);
|
||||||
|
float picture_width = ImGui::CalcTextSize(text.c_str()).x;
|
||||||
|
m_left_indentation = picture_width + m_line_height / 2;
|
||||||
|
}
|
||||||
|
m_window_width_offset = m_line_height * 6; //(m_has_cancel_button ? 6 : 4);
|
||||||
|
m_window_width = m_line_height * 25;
|
||||||
|
}
|
||||||
|
bool NotificationManager::PrintHostUploadNotification::push_background_color()
|
||||||
|
{
|
||||||
|
|
||||||
|
if (m_uj_state == UploadJobState::PB_ERROR) {
|
||||||
|
ImVec4 backcolor = ImGui::GetStyleColorVec4(ImGuiCol_WindowBg);
|
||||||
|
backcolor.x += 0.3f;
|
||||||
|
push_style_color(ImGuiCol_WindowBg, backcolor, m_state == EState::FadingOut, m_current_fade_opacity);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
void NotificationManager::PrintHostUploadNotification::set_percentage(float percent)
|
||||||
|
{
|
||||||
|
m_percentage = percent;
|
||||||
|
if (percent >= 1.0f) {
|
||||||
|
m_uj_state = UploadJobState::PB_COMPLETED;
|
||||||
|
m_has_cancel_button = false;
|
||||||
|
init();
|
||||||
|
} else if (percent < 0.0f) {
|
||||||
|
error();
|
||||||
|
} else {
|
||||||
|
m_uj_state = UploadJobState::PB_PROGRESS;
|
||||||
|
m_has_cancel_button = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
void NotificationManager::PrintHostUploadNotification::render_bar(ImGuiWrapper& imgui, const float win_size_x, const float win_size_y, const float win_pos_x, const float win_pos_y)
|
||||||
|
{
|
||||||
|
std::string text;
|
||||||
|
switch (m_uj_state) {
|
||||||
|
case Slic3r::GUI::NotificationManager::PrintHostUploadNotification::UploadJobState::PB_PROGRESS:
|
||||||
|
{
|
||||||
|
ProgressBarNotification::render_bar(imgui, win_size_x, win_size_y, win_pos_x, win_pos_y);
|
||||||
|
float uploaded = m_file_size * m_percentage;
|
||||||
|
std::stringstream stream;
|
||||||
|
stream << std::fixed << std::setprecision(2) << (int)(m_percentage * 100) << "% - " << uploaded << " of " << m_file_size << "MB uploaded";
|
||||||
|
text = stream.str();
|
||||||
|
ImGui::SetCursorPosX(m_left_indentation);
|
||||||
|
ImGui::SetCursorPosY(win_size_y / 2 + win_size_y / 6 - (m_multiline ? 0 : m_line_height / 4));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case Slic3r::GUI::NotificationManager::PrintHostUploadNotification::UploadJobState::PB_ERROR:
|
||||||
|
text = _u8L("ERROR");
|
||||||
|
ImGui::SetCursorPosX(m_left_indentation);
|
||||||
|
ImGui::SetCursorPosY(win_size_y / 2 + win_size_y / 6 - (m_multiline ? m_line_height / 4 : m_line_height / 2));
|
||||||
|
break;
|
||||||
|
case Slic3r::GUI::NotificationManager::PrintHostUploadNotification::UploadJobState::PB_CANCELLED:
|
||||||
|
text = _u8L("CANCELED");
|
||||||
|
ImGui::SetCursorPosX(m_left_indentation);
|
||||||
|
ImGui::SetCursorPosY(win_size_y / 2 + win_size_y / 6 - (m_multiline ? m_line_height / 4 : m_line_height / 2));
|
||||||
|
break;
|
||||||
|
case Slic3r::GUI::NotificationManager::PrintHostUploadNotification::UploadJobState::PB_COMPLETED:
|
||||||
|
text = _u8L("COMPLETED");
|
||||||
|
ImGui::SetCursorPosX(m_left_indentation);
|
||||||
|
ImGui::SetCursorPosY(win_size_y / 2 + win_size_y / 6 - (m_multiline ? m_line_height / 4 : m_line_height / 2));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
imgui.text(text.c_str());
|
||||||
|
|
||||||
|
}
|
||||||
|
void NotificationManager::PrintHostUploadNotification::render_left_sign(ImGuiWrapper& imgui)
|
||||||
|
{
|
||||||
|
if (m_uj_state == UploadJobState::PB_ERROR) {
|
||||||
|
std::string text;
|
||||||
|
text = ImGui::ErrorMarker;
|
||||||
|
ImGui::SetCursorPosX(m_line_height / 3);
|
||||||
|
ImGui::SetCursorPosY(m_window_height / 2 - m_line_height);
|
||||||
|
imgui.text(text.c_str());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
void NotificationManager::PrintHostUploadNotification::render_cancel_button(ImGuiWrapper& imgui, const float win_size_x, const float win_size_y, const float win_pos_x, const float win_pos_y)
|
||||||
|
{
|
||||||
|
ImVec2 win_size(win_size_x, win_size_y);
|
||||||
|
ImVec2 win_pos(win_pos_x, win_pos_y);
|
||||||
|
ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(.0f, .0f, .0f, .0f));
|
||||||
|
ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImVec4(.0f, .0f, .0f, .0f));
|
||||||
|
push_style_color(ImGuiCol_Text, ImVec4(1.f, 1.f, 1.f, 1.f), m_state == EState::FadingOut, m_current_fade_opacity);
|
||||||
|
push_style_color(ImGuiCol_TextSelectedBg, ImVec4(0, .75f, .75f, 1.f), m_state == EState::FadingOut, m_current_fade_opacity);
|
||||||
|
ImGui::PushStyleColor(ImGuiCol_ButtonActive, ImVec4(.0f, .0f, .0f, .0f));
|
||||||
|
|
||||||
|
std::string button_text;
|
||||||
|
button_text = ImGui::CancelButton;
|
||||||
|
|
||||||
|
if (ImGui::IsMouseHoveringRect(ImVec2(win_pos.x - m_line_height * 5.f, win_pos.y),
|
||||||
|
ImVec2(win_pos.x - m_line_height * 2.5f, win_pos.y + win_size.y),
|
||||||
|
true))
|
||||||
|
{
|
||||||
|
button_text = ImGui::CancelHoverButton;
|
||||||
|
// tooltip
|
||||||
|
long time_now = wxGetLocalTime();
|
||||||
|
if (m_hover_time > 0 && m_hover_time < time_now) {
|
||||||
|
ImGui::PushStyleColor(ImGuiCol_PopupBg, ImGuiWrapper::COL_WINDOW_BACKGROUND);
|
||||||
|
ImGui::BeginTooltip();
|
||||||
|
imgui.text(_u8L("Cancel upload") + " " + GUI::shortkey_ctrl_prefix() + "T");
|
||||||
|
ImGui::EndTooltip();
|
||||||
|
ImGui::PopStyleColor();
|
||||||
|
}
|
||||||
|
if (m_hover_time == 0)
|
||||||
|
m_hover_time = time_now;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
m_hover_time = 0;
|
||||||
|
|
||||||
|
ImVec2 button_pic_size = ImGui::CalcTextSize(button_text.c_str());
|
||||||
|
ImVec2 button_size(button_pic_size.x * 1.25f, button_pic_size.y * 1.25f);
|
||||||
|
ImGui::SetCursorPosX(win_size.x - m_line_height * 5.0f);
|
||||||
|
ImGui::SetCursorPosY(win_size.y / 2 - button_size.y);
|
||||||
|
if (imgui.button(button_text.c_str(), button_size.x, button_size.y))
|
||||||
|
{
|
||||||
|
wxGetApp().printhost_job_queue().cancel(m_job_id - 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
//invisible large button
|
||||||
|
ImGui::SetCursorPosX(win_size.x - m_line_height * 4.625f);
|
||||||
|
ImGui::SetCursorPosY(0);
|
||||||
|
if (imgui.button(" ", m_line_height * 2.f, win_size.y))
|
||||||
|
{
|
||||||
|
wxGetApp().printhost_job_queue().cancel(m_job_id - 1);
|
||||||
|
}
|
||||||
|
ImGui::PopStyleColor(5);
|
||||||
|
}
|
||||||
//------SlicingProgressNotification
|
//------SlicingProgressNotification
|
||||||
void NotificationManager::SlicingProgressNotification::init()
|
void NotificationManager::SlicingProgressNotification::init()
|
||||||
{
|
{
|
||||||
|
@ -1596,6 +1739,59 @@ void NotificationManager::push_exporting_finished_notification(const std::string
|
||||||
set_slicing_progress_hidden();
|
set_slicing_progress_hidden();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void NotificationManager::push_upload_job_notification(int id, float filesize, const std::string& filename, const std::string& host, float percentage)
|
||||||
|
{
|
||||||
|
// find if upload with same id was not already in notification
|
||||||
|
// done by compare_jon_id not compare_text thus has to be performed here
|
||||||
|
for (std::unique_ptr<PopNotification>& notification : m_pop_notifications) {
|
||||||
|
if (notification->get_type() == NotificationType::PrintHostUpload && dynamic_cast<PrintHostUploadNotification*>(notification.get())->compare_job_id(id)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
std::string text = PrintHostUploadNotification::get_upload_job_text(id, filename, host);
|
||||||
|
NotificationData data{ NotificationType::PrintHostUpload, NotificationLevel::ProgressBarNotificationLevel, 10, text };
|
||||||
|
push_notification_data(std::make_unique<NotificationManager::PrintHostUploadNotification>(data, m_id_provider, m_evt_handler, 0, id, filesize), 0);
|
||||||
|
}
|
||||||
|
void NotificationManager::set_upload_job_notification_percentage(int id, const std::string& filename, const std::string& host, float percentage)
|
||||||
|
{
|
||||||
|
for (std::unique_ptr<PopNotification>& notification : m_pop_notifications) {
|
||||||
|
if (notification->get_type() == NotificationType::PrintHostUpload) {
|
||||||
|
PrintHostUploadNotification* phun = dynamic_cast<PrintHostUploadNotification*>(notification.get());
|
||||||
|
if (phun->compare_job_id(id)) {
|
||||||
|
phun->set_percentage(percentage);
|
||||||
|
wxGetApp().plater()->get_current_canvas3D()->schedule_extra_frame(0);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
void NotificationManager::upload_job_notification_show_canceled(int id, const std::string& filename, const std::string& host)
|
||||||
|
{
|
||||||
|
for (std::unique_ptr<PopNotification>& notification : m_pop_notifications) {
|
||||||
|
if (notification->get_type() == NotificationType::PrintHostUpload) {
|
||||||
|
PrintHostUploadNotification* phun = dynamic_cast<PrintHostUploadNotification*>(notification.get());
|
||||||
|
if (phun->compare_job_id(id)) {
|
||||||
|
phun->cancel();
|
||||||
|
wxGetApp().plater()->get_current_canvas3D()->schedule_extra_frame(0);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
void NotificationManager::upload_job_notification_show_error(int id, const std::string& filename, const std::string& host)
|
||||||
|
{
|
||||||
|
for (std::unique_ptr<PopNotification>& notification : m_pop_notifications) {
|
||||||
|
if (notification->get_type() == NotificationType::PrintHostUpload) {
|
||||||
|
PrintHostUploadNotification* phun = dynamic_cast<PrintHostUploadNotification*>(notification.get());
|
||||||
|
if(phun->compare_job_id(id)) {
|
||||||
|
phun->error();
|
||||||
|
wxGetApp().plater()->get_current_canvas3D()->schedule_extra_frame(0);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void NotificationManager::init_slicing_progress_notification(std::function<bool()> cancel_callback)
|
void NotificationManager::init_slicing_progress_notification(std::function<bool()> cancel_callback)
|
||||||
{
|
{
|
||||||
for (std::unique_ptr<PopNotification>& notification : m_pop_notifications) {
|
for (std::unique_ptr<PopNotification>& notification : m_pop_notifications) {
|
||||||
|
|
|
@ -182,6 +182,11 @@ public:
|
||||||
void stop_delayed_notifications_of_type(const NotificationType type);
|
void stop_delayed_notifications_of_type(const NotificationType type);
|
||||||
// Creates Validate Error notification with a custom text and no fade out.
|
// Creates Validate Error notification with a custom text and no fade out.
|
||||||
void push_validate_error_notification(StringObjectException const & error);
|
void push_validate_error_notification(StringObjectException const & error);
|
||||||
|
// print host upload
|
||||||
|
void push_upload_job_notification(int id, float filesize, const std::string& filename, const std::string& host, float percentage = 0);
|
||||||
|
void set_upload_job_notification_percentage(int id, const std::string& filename, const std::string& host, float percentage);
|
||||||
|
void upload_job_notification_show_canceled(int id, const std::string& filename, const std::string& host);
|
||||||
|
void upload_job_notification_show_error(int id, const std::string& filename, const std::string& host);
|
||||||
// Creates Slicing Error notification with a custom text and no fade out.
|
// Creates Slicing Error notification with a custom text and no fade out.
|
||||||
void push_slicing_error_notification(const std::string& text);
|
void push_slicing_error_notification(const std::string& text);
|
||||||
// Creates Slicing Warning notification with a custom text and no fade out.
|
// Creates Slicing Warning notification with a custom text and no fade out.
|
||||||
|
@ -557,6 +562,48 @@ private:
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
class PrintHostUploadNotification : public ProgressBarNotification
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
enum class UploadJobState
|
||||||
|
{
|
||||||
|
PB_PROGRESS,
|
||||||
|
PB_ERROR,
|
||||||
|
PB_CANCELLED,
|
||||||
|
PB_COMPLETED
|
||||||
|
};
|
||||||
|
PrintHostUploadNotification(const NotificationData& n, NotificationIDProvider& id_provider, wxEvtHandler* evt_handler, float percentage, int job_id, float filesize)
|
||||||
|
:ProgressBarNotification(n, id_provider, evt_handler)
|
||||||
|
, m_job_id(job_id)
|
||||||
|
, m_file_size(filesize)
|
||||||
|
{
|
||||||
|
m_has_cancel_button = true;
|
||||||
|
set_percentage(percentage);
|
||||||
|
}
|
||||||
|
static std::string get_upload_job_text(int id, const std::string& filename, const std::string& host) { return /*"[" + std::to_string(id) + "] " + */filename + " -> " + host; }
|
||||||
|
void set_percentage(float percent) override;
|
||||||
|
void cancel() { m_uj_state = UploadJobState::PB_CANCELLED; m_has_cancel_button = false; }
|
||||||
|
void error() { m_uj_state = UploadJobState::PB_ERROR; m_has_cancel_button = false; init(); }
|
||||||
|
bool compare_job_id(const int other_id) const { return m_job_id == other_id; }
|
||||||
|
bool compare_text(const std::string& text) const override { return false; }
|
||||||
|
protected:
|
||||||
|
void init() override;
|
||||||
|
void count_spaces() override;
|
||||||
|
bool push_background_color() override;
|
||||||
|
void render_bar(ImGuiWrapper& imgui,
|
||||||
|
const float win_size_x, const float win_size_y,
|
||||||
|
const float win_pos_x, const float win_pos_y) override;
|
||||||
|
void render_cancel_button(ImGuiWrapper& imgui,
|
||||||
|
const float win_size_x, const float win_size_y,
|
||||||
|
const float win_pos_x, const float win_pos_y) override;
|
||||||
|
void render_left_sign(ImGuiWrapper& imgui) override;
|
||||||
|
// Identifies job in cancel callback
|
||||||
|
int m_job_id;
|
||||||
|
// Size of uploaded size to be displayed in MB
|
||||||
|
float m_file_size;
|
||||||
|
long m_hover_time{ 0 };
|
||||||
|
UploadJobState m_uj_state{ UploadJobState::PB_PROGRESS };
|
||||||
|
};
|
||||||
class SlicingProgressNotification : public ProgressBarNotification
|
class SlicingProgressNotification : public ProgressBarNotification
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
|
|
401
src/slic3r/GUI/PhysicalPrinterDialog.cpp
Normal file
401
src/slic3r/GUI/PhysicalPrinterDialog.cpp
Normal file
|
@ -0,0 +1,401 @@
|
||||||
|
#include "PhysicalPrinterDialog.hpp"
|
||||||
|
#include "PresetComboBoxes.hpp"
|
||||||
|
|
||||||
|
#include <cstddef>
|
||||||
|
#include <vector>
|
||||||
|
#include <string>
|
||||||
|
#include <boost/algorithm/string.hpp>
|
||||||
|
|
||||||
|
#include <wx/sizer.h>
|
||||||
|
#include <wx/stattext.h>
|
||||||
|
#include <wx/textctrl.h>
|
||||||
|
#include <wx/button.h>
|
||||||
|
#include <wx/statbox.h>
|
||||||
|
#include <wx/wupdlock.h>
|
||||||
|
|
||||||
|
#include "libslic3r/libslic3r.h"
|
||||||
|
#include "libslic3r/PrintConfig.hpp"
|
||||||
|
#include "libslic3r/PresetBundle.hpp"
|
||||||
|
|
||||||
|
#include "GUI.hpp"
|
||||||
|
#include "GUI_App.hpp"
|
||||||
|
#include "MainFrame.hpp"
|
||||||
|
#include "format.hpp"
|
||||||
|
#include "Tab.hpp"
|
||||||
|
#include "wxExtensions.hpp"
|
||||||
|
#include "PrintHostDialogs.hpp"
|
||||||
|
#include "../Utils/ASCIIFolding.hpp"
|
||||||
|
#include "../Utils/PrintHost.hpp"
|
||||||
|
#include "../Utils/FixModelByWin10.hpp"
|
||||||
|
#include "../Utils/UndoRedo.hpp"
|
||||||
|
#include "RemovableDriveManager.hpp"
|
||||||
|
#include "BitmapCache.hpp"
|
||||||
|
#include "BonjourDialog.hpp"
|
||||||
|
#include "MsgDialog.hpp"
|
||||||
|
|
||||||
|
namespace Slic3r {
|
||||||
|
namespace GUI {
|
||||||
|
|
||||||
|
#define BORDER_W 10
|
||||||
|
|
||||||
|
//------------------------------------------
|
||||||
|
// PhysicalPrinterDialog
|
||||||
|
//------------------------------------------
|
||||||
|
|
||||||
|
PhysicalPrinterDialog::PhysicalPrinterDialog(wxWindow* parent) :
|
||||||
|
DPIDialog(parent, wxID_ANY, _L("Physical Printer"), wxDefaultPosition, wxSize(45 * wxGetApp().em_unit(), -1), wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER)
|
||||||
|
{
|
||||||
|
SetFont(wxGetApp().normal_font());
|
||||||
|
#ifndef _WIN32
|
||||||
|
SetBackgroundColour(wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW));
|
||||||
|
#endif
|
||||||
|
|
||||||
|
m_config = &wxGetApp().preset_bundle->printers.get_edited_preset().config;
|
||||||
|
m_optgroup = new ConfigOptionsGroup(this, _L("Print Host upload"), m_config);
|
||||||
|
build_printhost_settings(m_optgroup);
|
||||||
|
|
||||||
|
wxStdDialogButtonSizer* btns = this->CreateStdDialogButtonSizer(wxOK | wxCANCEL);
|
||||||
|
wxButton* btnOK = static_cast<wxButton*>(this->FindWindowById(wxID_OK, this));
|
||||||
|
wxGetApp().UpdateDarkUI(btnOK);
|
||||||
|
btnOK->Bind(wxEVT_BUTTON, &PhysicalPrinterDialog::OnOK, this);
|
||||||
|
|
||||||
|
wxGetApp().UpdateDarkUI(static_cast<wxButton*>(this->FindWindowById(wxID_CANCEL, this)));
|
||||||
|
|
||||||
|
|
||||||
|
wxBoxSizer* topSizer = new wxBoxSizer(wxVERTICAL);
|
||||||
|
|
||||||
|
// topSizer->Add(label_top , 0, wxEXPAND | wxLEFT | wxTOP | wxRIGHT, BORDER_W);
|
||||||
|
topSizer->Add(m_optgroup->sizer , 1, wxEXPAND | wxLEFT | wxTOP | wxRIGHT, BORDER_W);
|
||||||
|
topSizer->Add(btns , 0, wxEXPAND | wxALL, BORDER_W);
|
||||||
|
|
||||||
|
SetSizer(topSizer);
|
||||||
|
topSizer->SetSizeHints(this);
|
||||||
|
this->CenterOnScreen();
|
||||||
|
}
|
||||||
|
|
||||||
|
PhysicalPrinterDialog::~PhysicalPrinterDialog()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
void PhysicalPrinterDialog::build_printhost_settings(ConfigOptionsGroup* m_optgroup)
|
||||||
|
{
|
||||||
|
m_optgroup->m_on_change = [this](t_config_option_key opt_key, boost::any value) {
|
||||||
|
if (opt_key == "host_type" || opt_key == "printhost_authorization_type")
|
||||||
|
this->update();
|
||||||
|
if (opt_key == "print_host")
|
||||||
|
this->update_printhost_buttons();
|
||||||
|
};
|
||||||
|
|
||||||
|
m_optgroup->append_single_option_line("host_type");
|
||||||
|
|
||||||
|
auto create_sizer_with_btn = [](wxWindow* parent, ScalableButton** btn, const std::string& icon_name, const wxString& label) {
|
||||||
|
*btn = new ScalableButton(parent, wxID_ANY, icon_name, label, wxDefaultSize, wxDefaultPosition, wxBU_LEFT | wxBU_EXACTFIT);
|
||||||
|
(*btn)->SetFont(wxGetApp().normal_font());
|
||||||
|
|
||||||
|
auto sizer = new wxBoxSizer(wxHORIZONTAL);
|
||||||
|
sizer->Add(*btn);
|
||||||
|
return sizer;
|
||||||
|
};
|
||||||
|
|
||||||
|
auto printhost_browse = [=](wxWindow* parent)
|
||||||
|
{
|
||||||
|
auto sizer = create_sizer_with_btn(parent, &m_printhost_browse_btn, "browse", _L("Browse") + " " + dots);
|
||||||
|
m_printhost_browse_btn->Bind(wxEVT_BUTTON, [=](wxCommandEvent& e) {
|
||||||
|
BonjourDialog dialog(this, Preset::printer_technology(*m_config));
|
||||||
|
if (dialog.show_and_lookup()) {
|
||||||
|
m_optgroup->set_value("print_host", dialog.get_selected(), true);
|
||||||
|
m_optgroup->get_field("print_host")->field_changed();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return sizer;
|
||||||
|
};
|
||||||
|
|
||||||
|
auto print_host_test = [=](wxWindow* parent) {
|
||||||
|
auto sizer = create_sizer_with_btn(parent, &m_printhost_test_btn, "test", _L("Test"));
|
||||||
|
|
||||||
|
m_printhost_test_btn->Bind(wxEVT_BUTTON, [this](wxCommandEvent& e) {
|
||||||
|
std::unique_ptr<PrintHost> host(PrintHost::get_print_host(m_config));
|
||||||
|
if (!host) {
|
||||||
|
const wxString text = _L("Could not get a valid Printer Host reference");
|
||||||
|
show_error(this, text);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
wxString msg;
|
||||||
|
bool result;
|
||||||
|
{
|
||||||
|
// Show a wait cursor during the connection test, as it is blocking UI.
|
||||||
|
wxBusyCursor wait;
|
||||||
|
result = host->test(msg);
|
||||||
|
}
|
||||||
|
if (result)
|
||||||
|
show_info(this, host->get_test_ok_msg(), _L("Success!"));
|
||||||
|
else
|
||||||
|
show_error(this, host->get_test_failed_msg(msg));
|
||||||
|
});
|
||||||
|
|
||||||
|
return sizer;
|
||||||
|
};
|
||||||
|
|
||||||
|
auto print_host_printers = [this, create_sizer_with_btn](wxWindow* parent) {
|
||||||
|
//add_scaled_button(parent, &m_printhost_port_browse_btn, "browse", _(L("Refresh Printers")), wxBU_LEFT | wxBU_EXACTFIT);
|
||||||
|
auto sizer = create_sizer_with_btn(parent, &m_printhost_port_browse_btn, "browse", _(L("Refresh Printers")));
|
||||||
|
ScalableButton* btn = m_printhost_port_browse_btn;
|
||||||
|
btn->SetFont(Slic3r::GUI::wxGetApp().normal_font());
|
||||||
|
btn->Bind(wxEVT_BUTTON, [this](wxCommandEvent e) { update_printers(); });
|
||||||
|
return sizer;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Set a wider width for a better alignment
|
||||||
|
Option option = m_optgroup->get_option("print_host");
|
||||||
|
option.opt.width = Field::def_width_wider();
|
||||||
|
Line host_line = m_optgroup->create_single_option_line(option);
|
||||||
|
host_line.append_widget(printhost_browse);
|
||||||
|
host_line.append_widget(print_host_test);
|
||||||
|
m_optgroup->append_line(host_line);
|
||||||
|
|
||||||
|
m_optgroup->append_single_option_line("printhost_authorization_type");
|
||||||
|
|
||||||
|
option = m_optgroup->get_option("printhost_apikey");
|
||||||
|
option.opt.width = Field::def_width_wider();
|
||||||
|
m_optgroup->append_single_option_line(option);
|
||||||
|
|
||||||
|
option = m_optgroup->get_option("printhost_port");
|
||||||
|
option.opt.width = Field::def_width_wider();
|
||||||
|
Line port_line = m_optgroup->create_single_option_line(option);
|
||||||
|
port_line.append_widget(print_host_printers);
|
||||||
|
m_optgroup->append_line(port_line);
|
||||||
|
|
||||||
|
const auto ca_file_hint = _u8L("HTTPS CA file is optional. It is only needed if you use HTTPS with a self-signed certificate.");
|
||||||
|
|
||||||
|
if (Http::ca_file_supported()) {
|
||||||
|
option = m_optgroup->get_option("printhost_cafile");
|
||||||
|
option.opt.width = Field::def_width_wider();
|
||||||
|
Line cafile_line = m_optgroup->create_single_option_line(option);
|
||||||
|
|
||||||
|
auto printhost_cafile_browse = [=](wxWindow* parent) {
|
||||||
|
auto sizer = create_sizer_with_btn(parent, &m_printhost_cafile_browse_btn, "browse", _L("Browse") + " " + dots);
|
||||||
|
m_printhost_cafile_browse_btn->Bind(wxEVT_BUTTON, [this, m_optgroup](wxCommandEvent e) {
|
||||||
|
static const auto filemasks = _L("Certificate files (*.crt, *.pem)|*.crt;*.pem|All files|*.*");
|
||||||
|
wxFileDialog openFileDialog(this, _L("Open CA certificate file"), "", "", filemasks, wxFD_OPEN | wxFD_FILE_MUST_EXIST);
|
||||||
|
if (openFileDialog.ShowModal() != wxID_CANCEL) {
|
||||||
|
m_optgroup->set_value("printhost_cafile", openFileDialog.GetPath(), true);
|
||||||
|
m_optgroup->get_field("printhost_cafile")->field_changed();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return sizer;
|
||||||
|
};
|
||||||
|
|
||||||
|
cafile_line.append_widget(printhost_cafile_browse);
|
||||||
|
m_optgroup->append_line(cafile_line);
|
||||||
|
|
||||||
|
Line cafile_hint{ "", "" };
|
||||||
|
cafile_hint.full_width = 1;
|
||||||
|
cafile_hint.widget = [ca_file_hint](wxWindow* parent) {
|
||||||
|
auto txt = new wxStaticText(parent, wxID_ANY, ca_file_hint);
|
||||||
|
auto sizer = new wxBoxSizer(wxHORIZONTAL);
|
||||||
|
sizer->Add(txt);
|
||||||
|
return sizer;
|
||||||
|
};
|
||||||
|
m_optgroup->append_line(cafile_hint);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
|
||||||
|
Line line{ "", "" };
|
||||||
|
line.full_width = 1;
|
||||||
|
|
||||||
|
line.widget = [ca_file_hint](wxWindow* parent) {
|
||||||
|
std::string info = _u8L("HTTPS CA File") + ":\n\t" +
|
||||||
|
(boost::format(_u8L("On this system, %s uses HTTPS certificates from the system Certificate Store or Keychain.")) % SLIC3R_APP_NAME).str() +
|
||||||
|
"\n\t" + _u8L("To use a custom CA file, please import your CA file into Certificate Store / Keychain.");
|
||||||
|
|
||||||
|
//auto txt = new wxStaticText(parent, wxID_ANY, from_u8((boost::format("%1%\n\n\t%2%") % info % ca_file_hint).str()));
|
||||||
|
auto txt = new wxStaticText(parent, wxID_ANY, from_u8((boost::format("%1%\n\t%2%") % info % ca_file_hint).str()));
|
||||||
|
txt->SetFont(wxGetApp().normal_font());
|
||||||
|
auto sizer = new wxBoxSizer(wxHORIZONTAL);
|
||||||
|
sizer->Add(txt, 1, wxEXPAND);
|
||||||
|
return sizer;
|
||||||
|
};
|
||||||
|
m_optgroup->append_line(line);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const std::string& opt_key : std::vector<std::string>{ "printhost_user", "printhost_password" }) {
|
||||||
|
option = m_optgroup->get_option(opt_key);
|
||||||
|
option.opt.width = Field::def_width_wider();
|
||||||
|
m_optgroup->append_single_option_line(option);
|
||||||
|
}
|
||||||
|
|
||||||
|
#ifdef WIN32
|
||||||
|
option = m_optgroup->get_option("printhost_ssl_ignore_revoke");
|
||||||
|
option.opt.width = Field::def_width_wider();
|
||||||
|
m_optgroup->append_single_option_line(option);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
m_optgroup->activate();
|
||||||
|
|
||||||
|
Field* printhost_field = m_optgroup->get_field("print_host");
|
||||||
|
if (printhost_field)
|
||||||
|
{
|
||||||
|
wxTextCtrl* temp = dynamic_cast<wxTextCtrl*>(printhost_field->getWindow());
|
||||||
|
if (temp)
|
||||||
|
temp->Bind(wxEVT_TEXT, ([printhost_field, temp](wxEvent& e)
|
||||||
|
{
|
||||||
|
#ifndef __WXGTK__
|
||||||
|
e.Skip();
|
||||||
|
temp->GetToolTip()->Enable(true);
|
||||||
|
#endif // __WXGTK__
|
||||||
|
// Remove all leading and trailing spaces from the input
|
||||||
|
std::string trimed_str, str = trimed_str = temp->GetValue().ToStdString();
|
||||||
|
boost::trim(trimed_str);
|
||||||
|
if (trimed_str != str)
|
||||||
|
temp->SetValue(trimed_str);
|
||||||
|
|
||||||
|
TextCtrl* field = dynamic_cast<TextCtrl*>(printhost_field);
|
||||||
|
if (field)
|
||||||
|
field->propagate_value();
|
||||||
|
}), temp->GetId());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Always fill in the "printhost_port" combo box from the config and select it.
|
||||||
|
{
|
||||||
|
Choice* choice = dynamic_cast<Choice*>(m_optgroup->get_field("printhost_port"));
|
||||||
|
choice->set_values({ m_config->opt_string("printhost_port") });
|
||||||
|
choice->set_selection();
|
||||||
|
}
|
||||||
|
|
||||||
|
update();
|
||||||
|
}
|
||||||
|
|
||||||
|
void PhysicalPrinterDialog::update_printhost_buttons()
|
||||||
|
{
|
||||||
|
std::unique_ptr<PrintHost> host(PrintHost::get_print_host(m_config));
|
||||||
|
m_printhost_test_btn->Enable(!m_config->opt_string("print_host").empty() && host->can_test());
|
||||||
|
m_printhost_browse_btn->Enable(host->has_auto_discovery());
|
||||||
|
}
|
||||||
|
|
||||||
|
void PhysicalPrinterDialog::update(bool printer_change)
|
||||||
|
{
|
||||||
|
m_optgroup->reload_config();
|
||||||
|
|
||||||
|
const PrinterTechnology tech = Preset::printer_technology(*m_config);
|
||||||
|
// Only offer the host type selection for FFF, for SLA it's always the SL1 printer (at the moment)
|
||||||
|
bool supports_multiple_printers = false;
|
||||||
|
if (tech == ptFFF) {
|
||||||
|
update_host_type(printer_change);
|
||||||
|
const auto opt = m_config->option<ConfigOptionEnum<PrintHostType>>("host_type");
|
||||||
|
m_optgroup->show_field("host_type");
|
||||||
|
if (opt->value == htPrusaLink)
|
||||||
|
{
|
||||||
|
m_optgroup->show_field("printhost_authorization_type");
|
||||||
|
AuthorizationType auth_type = m_config->option<ConfigOptionEnum<AuthorizationType>>("printhost_authorization_type")->value;
|
||||||
|
m_optgroup->show_field("printhost_apikey", auth_type == AuthorizationType::atKeyPassword);
|
||||||
|
for (const char* opt_key : { "printhost_user", "printhost_password" })
|
||||||
|
m_optgroup->show_field(opt_key, auth_type == AuthorizationType::atUserPassword);
|
||||||
|
} else {
|
||||||
|
m_optgroup->hide_field("printhost_authorization_type");
|
||||||
|
m_optgroup->show_field("printhost_apikey", true);
|
||||||
|
for (const std::string& opt_key : std::vector<std::string>{ "printhost_user", "printhost_password" })
|
||||||
|
m_optgroup->hide_field(opt_key);
|
||||||
|
supports_multiple_printers = opt && opt->value == htRepetier;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
m_optgroup->set_value("host_type", int(PrintHostType::htOctoPrint), false);
|
||||||
|
m_optgroup->hide_field("host_type");
|
||||||
|
|
||||||
|
m_optgroup->show_field("printhost_authorization_type");
|
||||||
|
|
||||||
|
AuthorizationType auth_type = m_config->option<ConfigOptionEnum<AuthorizationType>>("printhost_authorization_type")->value;
|
||||||
|
m_optgroup->show_field("printhost_apikey", auth_type == AuthorizationType::atKeyPassword);
|
||||||
|
|
||||||
|
for (const char *opt_key : { "printhost_user", "printhost_password" })
|
||||||
|
m_optgroup->show_field(opt_key, auth_type == AuthorizationType::atUserPassword);
|
||||||
|
}
|
||||||
|
|
||||||
|
m_optgroup->show_field("printhost_port", supports_multiple_printers);
|
||||||
|
m_printhost_port_browse_btn->Show(supports_multiple_printers);
|
||||||
|
|
||||||
|
update_printhost_buttons();
|
||||||
|
|
||||||
|
this->SetSize(this->GetBestSize());
|
||||||
|
this->Layout();
|
||||||
|
}
|
||||||
|
|
||||||
|
void PhysicalPrinterDialog::update_host_type(bool printer_change)
|
||||||
|
{
|
||||||
|
if (m_config == nullptr)
|
||||||
|
return;
|
||||||
|
bool all_presets_are_from_mk3_family = false;
|
||||||
|
Field* ht = m_optgroup->get_field("host_type");
|
||||||
|
|
||||||
|
wxArrayString types;
|
||||||
|
// Append localized enum_labels
|
||||||
|
assert(ht->m_opt.enum_labels.size() == ht->m_opt.enum_values.size());
|
||||||
|
for (size_t i = 0; i < ht->m_opt.enum_labels.size(); i++) {
|
||||||
|
if (ht->m_opt.enum_values[i] == "prusalink" && !all_presets_are_from_mk3_family)
|
||||||
|
continue;
|
||||||
|
types.Add(_(ht->m_opt.enum_labels[i]));
|
||||||
|
}
|
||||||
|
|
||||||
|
Choice* choice = dynamic_cast<Choice*>(ht);
|
||||||
|
choice->set_values(types);
|
||||||
|
auto set_to_choice_and_config = [this, choice](PrintHostType type) {
|
||||||
|
choice->set_value(static_cast<int>(type));
|
||||||
|
m_config->set_key_value("host_type", new ConfigOptionEnum<PrintHostType>(type));
|
||||||
|
};
|
||||||
|
if ((printer_change && all_presets_are_from_mk3_family) || all_presets_are_from_mk3_family)
|
||||||
|
set_to_choice_and_config(htPrusaLink);
|
||||||
|
else if ((printer_change && !all_presets_are_from_mk3_family) || (!all_presets_are_from_mk3_family && m_config->option<ConfigOptionEnum<PrintHostType>>("host_type")->value == htPrusaLink))
|
||||||
|
set_to_choice_and_config(htOctoPrint);
|
||||||
|
else
|
||||||
|
choice->set_value(m_config->option("host_type")->getInt());
|
||||||
|
}
|
||||||
|
|
||||||
|
void PhysicalPrinterDialog::update_printers()
|
||||||
|
{
|
||||||
|
wxBusyCursor wait;
|
||||||
|
|
||||||
|
std::unique_ptr<PrintHost> host(PrintHost::get_print_host(m_config));
|
||||||
|
|
||||||
|
wxArrayString printers;
|
||||||
|
Field *rs = m_optgroup->get_field("printhost_port");
|
||||||
|
try {
|
||||||
|
if (! host->get_printers(printers))
|
||||||
|
printers.clear();
|
||||||
|
} catch (const HostNetworkError &err) {
|
||||||
|
printers.clear();
|
||||||
|
show_error(this, _L("Connection to printers connected via the print host failed.") + "\n\n" + from_u8(err.what()));
|
||||||
|
}
|
||||||
|
Choice *choice = dynamic_cast<Choice*>(rs);
|
||||||
|
choice->set_values(printers);
|
||||||
|
printers.empty() ? rs->disable() : rs->enable();
|
||||||
|
}
|
||||||
|
|
||||||
|
void PhysicalPrinterDialog::on_dpi_changed(const wxRect& suggested_rect)
|
||||||
|
{
|
||||||
|
const int& em = em_unit();
|
||||||
|
|
||||||
|
m_printhost_browse_btn->msw_rescale();
|
||||||
|
m_printhost_test_btn->msw_rescale();
|
||||||
|
if (m_printhost_cafile_browse_btn)
|
||||||
|
m_printhost_cafile_browse_btn->msw_rescale();
|
||||||
|
|
||||||
|
m_optgroup->msw_rescale();
|
||||||
|
|
||||||
|
msw_buttons_rescale(this, em, { wxID_OK, wxID_CANCEL });
|
||||||
|
|
||||||
|
const wxSize& size = wxSize(45 * em, 35 * em);
|
||||||
|
SetMinSize(size);
|
||||||
|
|
||||||
|
Fit();
|
||||||
|
Refresh();
|
||||||
|
}
|
||||||
|
|
||||||
|
void PhysicalPrinterDialog::OnOK(wxEvent& event)
|
||||||
|
{
|
||||||
|
event.Skip();
|
||||||
|
}
|
||||||
|
|
||||||
|
}} // namespace Slic3r::GUI
|
59
src/slic3r/GUI/PhysicalPrinterDialog.hpp
Normal file
59
src/slic3r/GUI/PhysicalPrinterDialog.hpp
Normal file
|
@ -0,0 +1,59 @@
|
||||||
|
#ifndef slic3r_PhysicalPrinterDialog_hpp_
|
||||||
|
#define slic3r_PhysicalPrinterDialog_hpp_
|
||||||
|
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
#include <wx/gdicmn.h>
|
||||||
|
|
||||||
|
#include "libslic3r/Preset.hpp"
|
||||||
|
#include "GUI_Utils.hpp"
|
||||||
|
|
||||||
|
class wxString;
|
||||||
|
class wxTextCtrl;
|
||||||
|
class wxStaticText;
|
||||||
|
class ScalableButton;
|
||||||
|
class wxBoxSizer;
|
||||||
|
|
||||||
|
namespace Slic3r {
|
||||||
|
|
||||||
|
namespace GUI {
|
||||||
|
|
||||||
|
//------------------------------------------
|
||||||
|
// PhysicalPrinterDialog
|
||||||
|
//------------------------------------------
|
||||||
|
|
||||||
|
class ConfigOptionsGroup;
|
||||||
|
class PhysicalPrinterDialog : public DPIDialog
|
||||||
|
{
|
||||||
|
DynamicPrintConfig* m_config { nullptr };
|
||||||
|
ConfigOptionsGroup* m_optgroup { nullptr };
|
||||||
|
|
||||||
|
ScalableButton* m_printhost_browse_btn {nullptr};
|
||||||
|
ScalableButton* m_printhost_test_btn {nullptr};
|
||||||
|
ScalableButton* m_printhost_cafile_browse_btn {nullptr};
|
||||||
|
ScalableButton* m_printhost_client_cert_browse_btn {nullptr};
|
||||||
|
ScalableButton* m_printhost_port_browse_btn {nullptr};
|
||||||
|
|
||||||
|
|
||||||
|
void build_printhost_settings(ConfigOptionsGroup* optgroup);
|
||||||
|
void OnOK(wxEvent& event);
|
||||||
|
|
||||||
|
public:
|
||||||
|
PhysicalPrinterDialog(wxWindow* parent);
|
||||||
|
~PhysicalPrinterDialog();
|
||||||
|
|
||||||
|
void update(bool printer_change = false);
|
||||||
|
void update_host_type(bool printer_change);
|
||||||
|
void update_printhost_buttons();
|
||||||
|
void update_printers();
|
||||||
|
|
||||||
|
protected:
|
||||||
|
void on_dpi_changed(const wxRect& suggested_rect) override;
|
||||||
|
void on_sys_color_changed() override {};
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
} // namespace GUI
|
||||||
|
} // namespace Slic3r
|
||||||
|
|
||||||
|
#endif
|
|
@ -124,6 +124,9 @@
|
||||||
#include "libslic3r/Platform.hpp"
|
#include "libslic3r/Platform.hpp"
|
||||||
#include "nlohmann/json.hpp"
|
#include "nlohmann/json.hpp"
|
||||||
|
|
||||||
|
#include "PhysicalPrinterDialog.hpp"
|
||||||
|
#include "PrintHostDialogs.hpp"
|
||||||
|
|
||||||
using boost::optional;
|
using boost::optional;
|
||||||
namespace fs = boost::filesystem;
|
namespace fs = boost::filesystem;
|
||||||
using Slic3r::_3DScene;
|
using Slic3r::_3DScene;
|
||||||
|
@ -503,12 +506,23 @@ Sidebar::Sidebar(Plater *parent)
|
||||||
combo_printer->edit_btn = edit_btn;
|
combo_printer->edit_btn = edit_btn;
|
||||||
p->combo_printer = combo_printer;
|
p->combo_printer = combo_printer;
|
||||||
|
|
||||||
wxBoxSizer* vsizer_printer = new wxBoxSizer(wxVERTICAL);
|
ScalableButton* connection_btn = new ScalableButton(p->m_panel_printer_content, wxID_ANY, "monitor_signal_strong");
|
||||||
wxBoxSizer* hsizer_printer = new wxBoxSizer(wxHORIZONTAL);
|
connection_btn->SetBackgroundColour(wxColour(255, 255, 255));
|
||||||
|
connection_btn->SetToolTip(_L("Connection"));
|
||||||
|
connection_btn->Bind(wxEVT_BUTTON, [this, combo_printer](wxCommandEvent)
|
||||||
|
{
|
||||||
|
PhysicalPrinterDialog dlg(this->GetParent());
|
||||||
|
dlg.ShowModal();
|
||||||
|
});
|
||||||
|
|
||||||
|
wxBoxSizer *vsizer_printer = new wxBoxSizer(wxVERTICAL);
|
||||||
|
wxBoxSizer *hsizer_printer = new wxBoxSizer(wxHORIZONTAL);
|
||||||
|
|
||||||
hsizer_printer->Add(combo_printer, 1, wxALIGN_CENTER_VERTICAL | wxLEFT, FromDIP(3));
|
hsizer_printer->Add(combo_printer, 1, wxALIGN_CENTER_VERTICAL | wxLEFT, FromDIP(3));
|
||||||
hsizer_printer->Add(edit_btn, 0, wxALIGN_CENTER_VERTICAL | wxLEFT, FromDIP(3));
|
hsizer_printer->Add(edit_btn, 0, wxALIGN_CENTER_VERTICAL | wxLEFT, FromDIP(3));
|
||||||
hsizer_printer->Add(FromDIP(8), 0, 0, 0, 0);
|
hsizer_printer->Add(FromDIP(8), 0, 0, 0, 0);
|
||||||
|
hsizer_printer->Add(connection_btn, 0, wxALIGN_CENTER_VERTICAL | wxLEFT, FromDIP(3));
|
||||||
|
hsizer_printer->Add(FromDIP(8), 0, 0, 0, 0);
|
||||||
vsizer_printer->Add(hsizer_printer, 0, wxEXPAND, 0);
|
vsizer_printer->Add(hsizer_printer, 0, wxEXPAND, 0);
|
||||||
|
|
||||||
// Bed type selection
|
// Bed type selection
|
||||||
|
@ -1718,6 +1732,8 @@ struct Plater::priv
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
void export_gcode(fs::path output_path, bool output_path_on_removable_media);
|
void export_gcode(fs::path output_path, bool output_path_on_removable_media);
|
||||||
|
void export_gcode(fs::path output_path, bool output_path_on_removable_media, PrintHostJob upload_job);
|
||||||
|
|
||||||
void reload_from_disk();
|
void reload_from_disk();
|
||||||
bool replace_volume_with_stl(int object_idx, int volume_idx, const fs::path& new_path, const std::string& snapshot = "");
|
bool replace_volume_with_stl(int object_idx, int volume_idx, const fs::path& new_path, const std::string& snapshot = "");
|
||||||
void replace_with_stl();
|
void replace_with_stl();
|
||||||
|
@ -1774,6 +1790,7 @@ struct Plater::priv
|
||||||
void on_action_print_plate(SimpleEvent&);
|
void on_action_print_plate(SimpleEvent&);
|
||||||
void on_action_print_all(SimpleEvent&);
|
void on_action_print_all(SimpleEvent&);
|
||||||
void on_action_export_gcode(SimpleEvent&);
|
void on_action_export_gcode(SimpleEvent&);
|
||||||
|
void on_action_send_gcode(SimpleEvent&);
|
||||||
void on_action_export_sliced_file(SimpleEvent&);
|
void on_action_export_sliced_file(SimpleEvent&);
|
||||||
void on_action_select_sliced_plate(wxCommandEvent& evt);
|
void on_action_select_sliced_plate(wxCommandEvent& evt);
|
||||||
|
|
||||||
|
@ -2162,6 +2179,7 @@ Plater::priv::priv(Plater *q, MainFrame *main_frame)
|
||||||
q->Bind(EVT_GLTOOLBAR_SELECT_SLICED_PLATE, &priv::on_action_select_sliced_plate, this);
|
q->Bind(EVT_GLTOOLBAR_SELECT_SLICED_PLATE, &priv::on_action_select_sliced_plate, this);
|
||||||
q->Bind(EVT_GLTOOLBAR_PRINT_ALL, &priv::on_action_print_all, this);
|
q->Bind(EVT_GLTOOLBAR_PRINT_ALL, &priv::on_action_print_all, this);
|
||||||
q->Bind(EVT_GLTOOLBAR_EXPORT_GCODE, &priv::on_action_export_gcode, this);
|
q->Bind(EVT_GLTOOLBAR_EXPORT_GCODE, &priv::on_action_export_gcode, this);
|
||||||
|
q->Bind(EVT_GLTOOLBAR_SEND_GCODE, &priv::on_action_send_gcode, this);
|
||||||
q->Bind(EVT_GLTOOLBAR_EXPORT_SLICED_FILE, &priv::on_action_export_sliced_file, this);
|
q->Bind(EVT_GLTOOLBAR_EXPORT_SLICED_FILE, &priv::on_action_export_sliced_file, this);
|
||||||
q->Bind(EVT_GLCANVAS_PLATE_SELECT, &priv::on_plate_selected, this);
|
q->Bind(EVT_GLCANVAS_PLATE_SELECT, &priv::on_plate_selected, this);
|
||||||
q->Bind(EVT_DOWNLOAD_PROJECT, &priv::on_action_download_project, this);
|
q->Bind(EVT_DOWNLOAD_PROJECT, &priv::on_action_download_project, this);
|
||||||
|
@ -3994,7 +4012,38 @@ void Plater::priv::export_gcode(fs::path output_path, bool output_path_on_remova
|
||||||
this->background_process.set_task(PrintBase::TaskParams());
|
this->background_process.set_task(PrintBase::TaskParams());
|
||||||
this->restart_background_process(priv::UPDATE_BACKGROUND_PROCESS_FORCE_EXPORT);
|
this->restart_background_process(priv::UPDATE_BACKGROUND_PROCESS_FORCE_EXPORT);
|
||||||
}
|
}
|
||||||
|
void Plater::priv::export_gcode(fs::path output_path, bool output_path_on_removable_media, PrintHostJob upload_job)
|
||||||
|
{
|
||||||
|
wxCHECK_RET(!(output_path.empty() && upload_job.empty()), "export_gcode: output_path and upload_job empty");
|
||||||
|
|
||||||
|
if (model.objects.empty())
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (background_process.is_export_scheduled()) {
|
||||||
|
GUI::show_error(q, _L("Another export job is currently running."));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// bitmask of UpdateBackgroundProcessReturnState
|
||||||
|
unsigned int state = update_background_process(true);
|
||||||
|
if (state & priv::UPDATE_BACKGROUND_PROCESS_REFRESH_SCENE)
|
||||||
|
view3D->reload_scene(false);
|
||||||
|
|
||||||
|
if ((state & priv::UPDATE_BACKGROUND_PROCESS_INVALID) != 0)
|
||||||
|
return;
|
||||||
|
|
||||||
|
show_warning_dialog = true;
|
||||||
|
if (! output_path.empty()) {
|
||||||
|
background_process.schedule_export(output_path.string(), output_path_on_removable_media);
|
||||||
|
notification_manager->push_delayed_notification(NotificationType::ExportOngoing, []() {return true; }, 1000, 0);
|
||||||
|
} else {
|
||||||
|
background_process.schedule_upload(std::move(upload_job));
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the SLA processing of just a single object's supports is running, restart slicing for the whole object.
|
||||||
|
this->background_process.set_task(PrintBase::TaskParams());
|
||||||
|
this->restart_background_process(priv::UPDATE_BACKGROUND_PROCESS_FORCE_EXPORT);
|
||||||
|
}
|
||||||
unsigned int Plater::priv::update_restart_background_process(bool force_update_scene, bool force_update_preview)
|
unsigned int Plater::priv::update_restart_background_process(bool force_update_scene, bool force_update_preview)
|
||||||
{
|
{
|
||||||
bool switch_print = true;
|
bool switch_print = true;
|
||||||
|
@ -5301,6 +5350,14 @@ void Plater::priv::on_action_export_gcode(SimpleEvent&)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Plater::priv::on_action_send_gcode(SimpleEvent&)
|
||||||
|
{
|
||||||
|
if (q != nullptr) {
|
||||||
|
BOOST_LOG_TRIVIAL(debug) << __FUNCTION__ << ":received export gcode event\n" ;
|
||||||
|
q->send_gcode_legacy();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void Plater::priv::on_action_export_sliced_file(SimpleEvent&)
|
void Plater::priv::on_action_export_sliced_file(SimpleEvent&)
|
||||||
{
|
{
|
||||||
if (q != nullptr) {
|
if (q != nullptr) {
|
||||||
|
@ -8482,7 +8539,53 @@ void Plater::reslice_SLA_until_step(SLAPrintObjectStep step, const ModelObject &
|
||||||
// and let the background processing start.
|
// and let the background processing start.
|
||||||
this->p->restart_background_process(state | priv::UPDATE_BACKGROUND_PROCESS_FORCE_RESTART);
|
this->p->restart_background_process(state | priv::UPDATE_BACKGROUND_PROCESS_FORCE_RESTART);
|
||||||
}
|
}
|
||||||
|
void Plater::send_gcode_legacy(int plate_idx, Export3mfProgressFn proFn)
|
||||||
|
{
|
||||||
|
// if physical_printer is selected, send gcode for this printer
|
||||||
|
// DynamicPrintConfig* physical_printer_config = wxGetApp().preset_bundle->physical_printers.get_selected_printer_config();
|
||||||
|
DynamicPrintConfig* physical_printer_config = &Slic3r::GUI::wxGetApp().preset_bundle->printers.get_edited_preset().config;
|
||||||
|
if (! physical_printer_config || p->model.objects.empty())
|
||||||
|
return;
|
||||||
|
|
||||||
|
PrintHostJob upload_job(physical_printer_config);
|
||||||
|
if (upload_job.empty())
|
||||||
|
return;
|
||||||
|
|
||||||
|
// Obtain default output path
|
||||||
|
fs::path default_output_file;
|
||||||
|
try {
|
||||||
|
// Update the background processing, so that the placeholder parser will get the correct values for the ouput file template.
|
||||||
|
// Also if there is something wrong with the current configuration, a pop-up dialog will be shown and the export will not be performed.
|
||||||
|
unsigned int state = this->p->update_restart_background_process(false, false);
|
||||||
|
if (state & priv::UPDATE_BACKGROUND_PROCESS_INVALID)
|
||||||
|
return;
|
||||||
|
default_output_file = this->p->background_process.output_filepath_for_project(into_path(get_project_filename(".3mf")));
|
||||||
|
} catch (const Slic3r::PlaceholderParserError& ex) {
|
||||||
|
// Show the error with monospaced font.
|
||||||
|
show_error(this, ex.what(), true);
|
||||||
|
return;
|
||||||
|
} catch (const std::exception& ex) {
|
||||||
|
show_error(this, ex.what(), false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
default_output_file = fs::path(Slic3r::fold_utf8_to_ascii(default_output_file.string()));
|
||||||
|
|
||||||
|
// Repetier specific: Query the server for the list of file groups.
|
||||||
|
wxArrayString groups;
|
||||||
|
{
|
||||||
|
wxBusyCursor wait;
|
||||||
|
upload_job.printhost->get_groups(groups);
|
||||||
|
}
|
||||||
|
|
||||||
|
PrintHostSendDialog dlg(default_output_file, upload_job.printhost->get_post_upload_actions(), groups);
|
||||||
|
if (dlg.ShowModal() == wxID_OK) {
|
||||||
|
upload_job.upload_data.upload_path = dlg.filename();
|
||||||
|
upload_job.upload_data.post_action = dlg.post_action();
|
||||||
|
upload_job.upload_data.group = dlg.group();
|
||||||
|
|
||||||
|
p->export_gcode(fs::path(), false, std::move(upload_job));
|
||||||
|
}
|
||||||
|
}
|
||||||
int Plater::send_gcode(int plate_idx, Export3mfProgressFn proFn)
|
int Plater::send_gcode(int plate_idx, Export3mfProgressFn proFn)
|
||||||
{
|
{
|
||||||
int result = 0;
|
int result = 0;
|
||||||
|
|
|
@ -328,6 +328,7 @@ public:
|
||||||
/* -1: send current gcode if not specified
|
/* -1: send current gcode if not specified
|
||||||
* -2: send all gcode to target machine */
|
* -2: send all gcode to target machine */
|
||||||
int send_gcode(int plate_idx = -1, Export3mfProgressFn proFn = nullptr);
|
int send_gcode(int plate_idx = -1, Export3mfProgressFn proFn = nullptr);
|
||||||
|
void send_gcode_legacy(int plate_idx = -1, Export3mfProgressFn proFn = nullptr);
|
||||||
int export_config_3mf(int plate_idx = -1, Export3mfProgressFn proFn = nullptr);
|
int export_config_3mf(int plate_idx = -1, Export3mfProgressFn proFn = nullptr);
|
||||||
//BBS jump to nonitor after print job finished
|
//BBS jump to nonitor after print job finished
|
||||||
void print_job_finished(wxCommandEvent &evt);
|
void print_job_finished(wxCommandEvent &evt);
|
||||||
|
|
518
src/slic3r/GUI/PrintHostDialogs.cpp
Normal file
518
src/slic3r/GUI/PrintHostDialogs.cpp
Normal file
|
@ -0,0 +1,518 @@
|
||||||
|
#include "PrintHostDialogs.hpp"
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
|
#include <iomanip>
|
||||||
|
|
||||||
|
#include <wx/frame.h>
|
||||||
|
#include <wx/progdlg.h>
|
||||||
|
#include <wx/sizer.h>
|
||||||
|
#include <wx/stattext.h>
|
||||||
|
#include <wx/textctrl.h>
|
||||||
|
#include <wx/checkbox.h>
|
||||||
|
#include <wx/button.h>
|
||||||
|
#include <wx/dataview.h>
|
||||||
|
#include <wx/wupdlock.h>
|
||||||
|
#include <wx/debug.h>
|
||||||
|
#include <wx/msgdlg.h>
|
||||||
|
|
||||||
|
#include <boost/log/trivial.hpp>
|
||||||
|
#include <boost/filesystem.hpp>
|
||||||
|
#include <boost/nowide/convert.hpp>
|
||||||
|
|
||||||
|
#include "GUI.hpp"
|
||||||
|
#include "GUI_App.hpp"
|
||||||
|
#include "MsgDialog.hpp"
|
||||||
|
#include "I18N.hpp"
|
||||||
|
#include "MainFrame.hpp"
|
||||||
|
#include "libslic3r/AppConfig.hpp"
|
||||||
|
#include "NotificationManager.hpp"
|
||||||
|
#include "ExtraRenderers.hpp"
|
||||||
|
|
||||||
|
namespace fs = boost::filesystem;
|
||||||
|
|
||||||
|
namespace Slic3r {
|
||||||
|
namespace GUI {
|
||||||
|
|
||||||
|
static const char *CONFIG_KEY_PATH = "printhost_path";
|
||||||
|
static const char *CONFIG_KEY_GROUP = "printhost_group";
|
||||||
|
|
||||||
|
PrintHostSendDialog::PrintHostSendDialog(const fs::path &path, PrintHostPostUploadActions post_actions, const wxArrayString &groups)
|
||||||
|
: MsgDialog(static_cast<wxWindow*>(wxGetApp().mainframe), _L("Send G-Code to printer host"), _L("Upload to Printer Host with the following filename:"),0)
|
||||||
|
, txt_filename(new wxTextCtrl(this, wxID_ANY))
|
||||||
|
, combo_groups(!groups.IsEmpty() ? new wxComboBox(this, wxID_ANY, wxEmptyString, wxDefaultPosition, wxDefaultSize, groups, wxCB_READONLY) : nullptr)
|
||||||
|
, post_upload_action(PrintHostPostUploadAction::None)
|
||||||
|
{
|
||||||
|
#ifdef __APPLE__
|
||||||
|
txt_filename->OSXDisableAllSmartSubstitutions();
|
||||||
|
#endif
|
||||||
|
const AppConfig *app_config = wxGetApp().app_config;
|
||||||
|
|
||||||
|
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 * wxGetApp().em_unit());
|
||||||
|
|
||||||
|
content_sizer->Add(txt_filename, 0, wxEXPAND);
|
||||||
|
content_sizer->Add(label_dir_hint);
|
||||||
|
content_sizer->AddSpacer(VERT_SPACING);
|
||||||
|
|
||||||
|
if (combo_groups != nullptr) {
|
||||||
|
// Repetier specific: Show a selection of file groups.
|
||||||
|
auto *label_group = new wxStaticText(this, wxID_ANY, _L("Group"));
|
||||||
|
content_sizer->Add(label_group);
|
||||||
|
content_sizer->Add(combo_groups, 0, wxBOTTOM, 2*VERT_SPACING);
|
||||||
|
wxString recent_group = from_u8(app_config->get("recent", CONFIG_KEY_GROUP));
|
||||||
|
if (! recent_group.empty())
|
||||||
|
combo_groups->SetValue(recent_group);
|
||||||
|
}
|
||||||
|
|
||||||
|
wxString recent_path = from_u8(app_config->get("recent", CONFIG_KEY_PATH));
|
||||||
|
if (recent_path.Length() > 0 && recent_path[recent_path.Length() - 1] != '/') {
|
||||||
|
recent_path += '/';
|
||||||
|
}
|
||||||
|
const auto recent_path_len = recent_path.Length();
|
||||||
|
recent_path += path.filename().wstring();
|
||||||
|
wxString stem(path.stem().wstring());
|
||||||
|
const auto stem_len = stem.Length();
|
||||||
|
|
||||||
|
txt_filename->SetValue(recent_path);
|
||||||
|
txt_filename->SetFocus();
|
||||||
|
|
||||||
|
m_valid_suffix = recent_path.substr(recent_path.find_last_of('.'));
|
||||||
|
// .gcode suffix control
|
||||||
|
auto validate_path = [this](const wxString &path) -> bool {
|
||||||
|
if (! path.Lower().EndsWith(m_valid_suffix.Lower())) {
|
||||||
|
MessageDialog msg_wingow(this, wxString::Format(_L("Upload filename doesn't end with \"%s\". Do you wish to continue?"), m_valid_suffix), wxString(SLIC3R_APP_NAME), wxYES | wxNO);
|
||||||
|
if (msg_wingow.ShowModal() == wxID_NO)
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
|
||||||
|
auto* btn_upload = add_button(wxID_YES, false, _L("Upload"));
|
||||||
|
btn_upload->Bind(wxEVT_BUTTON, [this, validate_path](wxCommandEvent&) {
|
||||||
|
if (validate_path(txt_filename->GetValue())) {
|
||||||
|
post_upload_action = PrintHostPostUploadAction::None;
|
||||||
|
EndDialog(wxID_OK);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (post_actions.has(PrintHostPostUploadAction::StartPrint)) {
|
||||||
|
auto* btn_print = add_button(wxID_YES, false, _L("Upload and Print"));
|
||||||
|
btn_print->Bind(wxEVT_BUTTON, [this, validate_path](wxCommandEvent&) {
|
||||||
|
if (validate_path(txt_filename->GetValue())) {
|
||||||
|
post_upload_action = PrintHostPostUploadAction::StartPrint;
|
||||||
|
EndDialog(wxID_OK);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (post_actions.has(PrintHostPostUploadAction::StartSimulation)) {
|
||||||
|
// Using wxID_MORE as a button identifier to be different from the other buttons, wxID_MORE has no other meaning here.
|
||||||
|
auto* btn_simulate = add_button(wxID_MORE, false, _L("Upload and Simulate"));
|
||||||
|
btn_simulate->Bind(wxEVT_BUTTON, [this, validate_path](wxCommandEvent&) {
|
||||||
|
if (validate_path(txt_filename->GetValue())) {
|
||||||
|
post_upload_action = PrintHostPostUploadAction::StartSimulation;
|
||||||
|
EndDialog(wxID_OK);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
add_button(wxID_CANCEL,false,"Cancel");
|
||||||
|
finalize();
|
||||||
|
|
||||||
|
#ifdef __linux__
|
||||||
|
// On Linux with GTK2 when text control lose the focus then selection (colored background) disappears but text color stay white
|
||||||
|
// and as a result the text is invisible with light mode
|
||||||
|
// see https://github.com/prusa3d/PrusaSlicer/issues/4532
|
||||||
|
// Workaround: Unselect text selection explicitly on kill focus
|
||||||
|
txt_filename->Bind(wxEVT_KILL_FOCUS, [this](wxEvent& e) {
|
||||||
|
e.Skip();
|
||||||
|
txt_filename->SetInsertionPoint(txt_filename->GetLastPosition());
|
||||||
|
}, txt_filename->GetId());
|
||||||
|
#endif /* __linux__ */
|
||||||
|
|
||||||
|
Bind(wxEVT_SHOW, [=](const wxShowEvent &) {
|
||||||
|
// Another similar case where the function only works with EVT_SHOW + CallAfter,
|
||||||
|
// this time on Mac.
|
||||||
|
CallAfter([=]() {
|
||||||
|
txt_filename->SetSelection(recent_path_len, recent_path_len + stem_len);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
fs::path PrintHostSendDialog::filename() const
|
||||||
|
{
|
||||||
|
return into_path(txt_filename->GetValue());
|
||||||
|
}
|
||||||
|
|
||||||
|
PrintHostPostUploadAction PrintHostSendDialog::post_action() const
|
||||||
|
{
|
||||||
|
return post_upload_action;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string PrintHostSendDialog::group() const
|
||||||
|
{
|
||||||
|
if (combo_groups == nullptr) {
|
||||||
|
return "";
|
||||||
|
} else {
|
||||||
|
wxString group = combo_groups->GetValue();
|
||||||
|
return into_u8(group);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void PrintHostSendDialog::EndModal(int ret)
|
||||||
|
{
|
||||||
|
if (ret == wxID_OK) {
|
||||||
|
// Persist path and print settings
|
||||||
|
wxString path = txt_filename->GetValue();
|
||||||
|
int last_slash = path.Find('/', true);
|
||||||
|
if (last_slash == wxNOT_FOUND)
|
||||||
|
path.clear();
|
||||||
|
else
|
||||||
|
path = path.SubString(0, last_slash);
|
||||||
|
|
||||||
|
AppConfig *app_config = wxGetApp().app_config;
|
||||||
|
app_config->set("recent", CONFIG_KEY_PATH, into_u8(path));
|
||||||
|
|
||||||
|
if (combo_groups != nullptr) {
|
||||||
|
wxString group = combo_groups->GetValue();
|
||||||
|
app_config->set("recent", CONFIG_KEY_GROUP, into_u8(group));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
MsgDialog::EndModal(ret);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
wxDEFINE_EVENT(EVT_PRINTHOST_PROGRESS, PrintHostQueueDialog::Event);
|
||||||
|
wxDEFINE_EVENT(EVT_PRINTHOST_ERROR, PrintHostQueueDialog::Event);
|
||||||
|
wxDEFINE_EVENT(EVT_PRINTHOST_CANCEL, PrintHostQueueDialog::Event);
|
||||||
|
|
||||||
|
PrintHostQueueDialog::Event::Event(wxEventType eventType, int winid, size_t job_id)
|
||||||
|
: wxEvent(winid, eventType)
|
||||||
|
, job_id(job_id)
|
||||||
|
{}
|
||||||
|
|
||||||
|
PrintHostQueueDialog::Event::Event(wxEventType eventType, int winid, size_t job_id, int progress)
|
||||||
|
: wxEvent(winid, eventType)
|
||||||
|
, job_id(job_id)
|
||||||
|
, progress(progress)
|
||||||
|
{}
|
||||||
|
|
||||||
|
PrintHostQueueDialog::Event::Event(wxEventType eventType, int winid, size_t job_id, wxString error)
|
||||||
|
: wxEvent(winid, eventType)
|
||||||
|
, job_id(job_id)
|
||||||
|
, error(std::move(error))
|
||||||
|
{}
|
||||||
|
|
||||||
|
wxEvent *PrintHostQueueDialog::Event::Clone() const
|
||||||
|
{
|
||||||
|
return new Event(*this);
|
||||||
|
}
|
||||||
|
|
||||||
|
PrintHostQueueDialog::PrintHostQueueDialog(wxWindow *parent)
|
||||||
|
: DPIDialog(parent, wxID_ANY, _L("Print host upload queue"), wxDefaultPosition, wxDefaultSize, wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER)
|
||||||
|
, on_progress_evt(this, EVT_PRINTHOST_PROGRESS, &PrintHostQueueDialog::on_progress, this)
|
||||||
|
, on_error_evt(this, EVT_PRINTHOST_ERROR, &PrintHostQueueDialog::on_error, this)
|
||||||
|
, on_cancel_evt(this, EVT_PRINTHOST_CANCEL, &PrintHostQueueDialog::on_cancel, this)
|
||||||
|
{
|
||||||
|
const auto em = GetTextExtent("m").x;
|
||||||
|
|
||||||
|
auto *topsizer = new wxBoxSizer(wxVERTICAL);
|
||||||
|
|
||||||
|
std::vector<int> widths;
|
||||||
|
widths.reserve(6);
|
||||||
|
if (!load_user_data(UDT_COLS, widths)) {
|
||||||
|
widths.clear();
|
||||||
|
for (size_t i = 0; i < 6; i++)
|
||||||
|
widths.push_back(-1);
|
||||||
|
}
|
||||||
|
|
||||||
|
job_list = new wxDataViewListCtrl(this, wxID_ANY);
|
||||||
|
|
||||||
|
// MSW DarkMode: workaround for the selected item in the list
|
||||||
|
auto append_text_column = [this](const wxString& label, int width, wxAlignment align = wxALIGN_LEFT,
|
||||||
|
int flags = wxDATAVIEW_COL_RESIZABLE | wxDATAVIEW_COL_SORTABLE) {
|
||||||
|
#ifdef _WIN32
|
||||||
|
job_list->AppendColumn(new wxDataViewColumn(label, new TextRenderer(), job_list->GetColumnCount(), width, align, flags));
|
||||||
|
#else
|
||||||
|
job_list->AppendTextColumn(label, wxDATAVIEW_CELL_INERT, width, align, flags);
|
||||||
|
#endif
|
||||||
|
};
|
||||||
|
|
||||||
|
// Note: Keep these in sync with Column
|
||||||
|
append_text_column(_L("ID"), widths[0]);
|
||||||
|
job_list->AppendProgressColumn(_L("Progress"), wxDATAVIEW_CELL_INERT, widths[1], wxALIGN_LEFT, wxDATAVIEW_COL_RESIZABLE | wxDATAVIEW_COL_SORTABLE);
|
||||||
|
append_text_column(_L("Status"),widths[2]);
|
||||||
|
append_text_column(_L("Host"), widths[3]);
|
||||||
|
append_text_column(_CTX_utf8(L_CONTEXT("Size", "OfFile"), "OfFile"), widths[4]);
|
||||||
|
append_text_column(_L("Filename"), widths[5]);
|
||||||
|
append_text_column(_L("Error Message"), -1, wxALIGN_CENTER, wxDATAVIEW_COL_HIDDEN);
|
||||||
|
|
||||||
|
auto *btnsizer = new wxBoxSizer(wxHORIZONTAL);
|
||||||
|
btn_cancel = new wxButton(this, wxID_DELETE, _L("Cancel selected"));
|
||||||
|
btn_cancel->Disable();
|
||||||
|
btn_error = new wxButton(this, wxID_ANY, _L("Show error message"));
|
||||||
|
btn_error->Disable();
|
||||||
|
// Note: The label needs to be present, otherwise we get accelerator bugs on Mac
|
||||||
|
auto *btn_close = new wxButton(this, wxID_CANCEL, _L("Close"));
|
||||||
|
btnsizer->Add(btn_cancel, 0, wxRIGHT, SPACING);
|
||||||
|
btnsizer->Add(btn_error, 0);
|
||||||
|
btnsizer->AddStretchSpacer();
|
||||||
|
btnsizer->Add(btn_close);
|
||||||
|
|
||||||
|
topsizer->Add(job_list, 1, wxEXPAND | wxBOTTOM, SPACING);
|
||||||
|
topsizer->Add(btnsizer, 0, wxEXPAND);
|
||||||
|
SetSizer(topsizer);
|
||||||
|
|
||||||
|
wxGetApp().UpdateDlgDarkUI(this);
|
||||||
|
wxGetApp().UpdateDVCDarkUI(job_list);
|
||||||
|
|
||||||
|
std::vector<int> size;
|
||||||
|
SetSize(load_user_data(UDT_SIZE, size) ? wxSize(size[0] * em, size[1] * em) : wxSize(HEIGHT * em, WIDTH * em));
|
||||||
|
|
||||||
|
Bind(wxEVT_SIZE, [this](wxSizeEvent& evt) {
|
||||||
|
OnSize(evt);
|
||||||
|
save_user_data(UDT_SIZE | UDT_POSITION | UDT_COLS);
|
||||||
|
});
|
||||||
|
|
||||||
|
std::vector<int> pos;
|
||||||
|
if (load_user_data(UDT_POSITION, pos))
|
||||||
|
SetPosition(wxPoint(pos[0], pos[1]));
|
||||||
|
|
||||||
|
Bind(wxEVT_MOVE, [this](wxMoveEvent& evt) {
|
||||||
|
save_user_data(UDT_SIZE | UDT_POSITION | UDT_COLS);
|
||||||
|
});
|
||||||
|
|
||||||
|
job_list->Bind(wxEVT_DATAVIEW_SELECTION_CHANGED, [this](wxDataViewEvent&) { on_list_select(); });
|
||||||
|
|
||||||
|
btn_cancel->Bind(wxEVT_BUTTON, [this](wxCommandEvent&) {
|
||||||
|
int selected = job_list->GetSelectedRow();
|
||||||
|
if (selected == wxNOT_FOUND) { return; }
|
||||||
|
|
||||||
|
const JobState state = get_state(selected);
|
||||||
|
if (state < ST_ERROR) {
|
||||||
|
GUI::wxGetApp().printhost_job_queue().cancel(selected);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
btn_error->Bind(wxEVT_BUTTON, [this](wxCommandEvent&) {
|
||||||
|
int selected = job_list->GetSelectedRow();
|
||||||
|
if (selected == wxNOT_FOUND) { return; }
|
||||||
|
GUI::show_error(nullptr, job_list->GetTextValue(selected, COL_ERRORMSG));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void PrintHostQueueDialog::append_job(const PrintHostJob &job)
|
||||||
|
{
|
||||||
|
wxCHECK_RET(!job.empty(), "PrintHostQueueDialog: Attempt to append an empty job");
|
||||||
|
|
||||||
|
wxVector<wxVariant> fields;
|
||||||
|
fields.push_back(wxVariant(wxString::Format("%d", job_list->GetItemCount() + 1)));
|
||||||
|
fields.push_back(wxVariant(0));
|
||||||
|
fields.push_back(wxVariant(_L("Enqueued")));
|
||||||
|
fields.push_back(wxVariant(job.printhost->get_host()));
|
||||||
|
boost::system::error_code ec;
|
||||||
|
boost::uintmax_t size_i = boost::filesystem::file_size(job.upload_data.source_path, ec);
|
||||||
|
std::stringstream stream;
|
||||||
|
if (ec) {
|
||||||
|
stream << "unknown";
|
||||||
|
size_i = 0;
|
||||||
|
BOOST_LOG_TRIVIAL(error) << ec.message();
|
||||||
|
} else
|
||||||
|
stream << std::fixed << std::setprecision(2) << ((float)size_i / 1024 / 1024) << "MB";
|
||||||
|
fields.push_back(wxVariant(stream.str()));
|
||||||
|
fields.push_back(wxVariant(job.upload_data.upload_path.string()));
|
||||||
|
fields.push_back(wxVariant(""));
|
||||||
|
job_list->AppendItem(fields, static_cast<wxUIntPtr>(ST_NEW));
|
||||||
|
// Both strings are UTF-8 encoded.
|
||||||
|
upload_names.emplace_back(job.printhost->get_host(), job.upload_data.upload_path.string());
|
||||||
|
|
||||||
|
wxGetApp().notification_manager()->push_upload_job_notification(job_list->GetItemCount(), (float)size_i / 1024 / 1024, job.upload_data.upload_path.string(), job.printhost->get_host());
|
||||||
|
}
|
||||||
|
|
||||||
|
void PrintHostQueueDialog::on_dpi_changed(const wxRect &suggested_rect)
|
||||||
|
{
|
||||||
|
const int& em = em_unit();
|
||||||
|
|
||||||
|
msw_buttons_rescale(this, em, { wxID_DELETE, wxID_CANCEL, btn_error->GetId() });
|
||||||
|
|
||||||
|
SetMinSize(wxSize(HEIGHT * em, WIDTH * em));
|
||||||
|
|
||||||
|
Fit();
|
||||||
|
Refresh();
|
||||||
|
|
||||||
|
save_user_data(UDT_SIZE | UDT_POSITION | UDT_COLS);
|
||||||
|
}
|
||||||
|
|
||||||
|
void PrintHostQueueDialog::on_sys_color_changed()
|
||||||
|
{
|
||||||
|
#ifdef _WIN32
|
||||||
|
wxGetApp().UpdateDlgDarkUI(this);
|
||||||
|
wxGetApp().UpdateDVCDarkUI(job_list);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
PrintHostQueueDialog::JobState PrintHostQueueDialog::get_state(int idx)
|
||||||
|
{
|
||||||
|
wxCHECK_MSG(idx >= 0 && idx < job_list->GetItemCount(), ST_ERROR, "Out of bounds access to job list");
|
||||||
|
return static_cast<JobState>(job_list->GetItemData(job_list->RowToItem(idx)));
|
||||||
|
}
|
||||||
|
|
||||||
|
void PrintHostQueueDialog::set_state(int idx, JobState state)
|
||||||
|
{
|
||||||
|
wxCHECK_RET(idx >= 0 && idx < job_list->GetItemCount(), "Out of bounds access to job list");
|
||||||
|
job_list->SetItemData(job_list->RowToItem(idx), static_cast<wxUIntPtr>(state));
|
||||||
|
|
||||||
|
switch (state) {
|
||||||
|
case ST_NEW: job_list->SetValue(_L("Enqueued"), idx, COL_STATUS); break;
|
||||||
|
case ST_PROGRESS: job_list->SetValue(_L("Uploading"), idx, COL_STATUS); break;
|
||||||
|
case ST_ERROR: job_list->SetValue(_L("Error"), idx, COL_STATUS); break;
|
||||||
|
case ST_CANCELLING: job_list->SetValue(_L("Cancelling"), idx, COL_STATUS); break;
|
||||||
|
case ST_CANCELLED: job_list->SetValue(_L("Cancelled"), idx, COL_STATUS); break;
|
||||||
|
case ST_COMPLETED: job_list->SetValue(_L("Completed"), idx, COL_STATUS); break;
|
||||||
|
}
|
||||||
|
// This might be ambigous call, but user data needs to be saved time to time
|
||||||
|
save_user_data(UDT_SIZE | UDT_POSITION | UDT_COLS);
|
||||||
|
}
|
||||||
|
|
||||||
|
void PrintHostQueueDialog::on_list_select()
|
||||||
|
{
|
||||||
|
int selected = job_list->GetSelectedRow();
|
||||||
|
if (selected != wxNOT_FOUND) {
|
||||||
|
const JobState state = get_state(selected);
|
||||||
|
btn_cancel->Enable(state < ST_ERROR);
|
||||||
|
btn_error->Enable(state == ST_ERROR);
|
||||||
|
Layout();
|
||||||
|
} else {
|
||||||
|
btn_cancel->Disable();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void PrintHostQueueDialog::on_progress(Event &evt)
|
||||||
|
{
|
||||||
|
wxCHECK_RET(evt.job_id < (size_t)job_list->GetItemCount(), "Out of bounds access to job list");
|
||||||
|
|
||||||
|
if (evt.progress < 100) {
|
||||||
|
set_state(evt.job_id, ST_PROGRESS);
|
||||||
|
job_list->SetValue(wxVariant(evt.progress), evt.job_id, COL_PROGRESS);
|
||||||
|
} else {
|
||||||
|
set_state(evt.job_id, ST_COMPLETED);
|
||||||
|
job_list->SetValue(wxVariant(100), evt.job_id, COL_PROGRESS);
|
||||||
|
}
|
||||||
|
|
||||||
|
on_list_select();
|
||||||
|
|
||||||
|
if (evt.progress > 0)
|
||||||
|
{
|
||||||
|
wxVariant nm, hst;
|
||||||
|
job_list->GetValue(nm, evt.job_id, COL_FILENAME);
|
||||||
|
job_list->GetValue(hst, evt.job_id, COL_HOST);
|
||||||
|
wxGetApp().notification_manager()->set_upload_job_notification_percentage(evt.job_id + 1, boost::nowide::narrow(nm.GetString()), boost::nowide::narrow(hst.GetString()), evt.progress / 100.f);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void PrintHostQueueDialog::on_error(Event &evt)
|
||||||
|
{
|
||||||
|
wxCHECK_RET(evt.job_id < (size_t)job_list->GetItemCount(), "Out of bounds access to job list");
|
||||||
|
|
||||||
|
set_state(evt.job_id, ST_ERROR);
|
||||||
|
|
||||||
|
auto errormsg = from_u8((boost::format("%1%\n%2%") % _utf8(L("Error uploading to print host:")) % std::string(evt.error.ToUTF8())).str());
|
||||||
|
job_list->SetValue(wxVariant(0), evt.job_id, COL_PROGRESS);
|
||||||
|
job_list->SetValue(wxVariant(errormsg), evt.job_id, COL_ERRORMSG); // Stashes the error message into a hidden column for later
|
||||||
|
|
||||||
|
on_list_select();
|
||||||
|
|
||||||
|
GUI::show_error(nullptr, errormsg);
|
||||||
|
|
||||||
|
wxVariant nm, hst;
|
||||||
|
job_list->GetValue(nm, evt.job_id, COL_FILENAME);
|
||||||
|
job_list->GetValue(hst, evt.job_id, COL_HOST);
|
||||||
|
wxGetApp().notification_manager()->upload_job_notification_show_error(evt.job_id + 1, boost::nowide::narrow(nm.GetString()), boost::nowide::narrow(hst.GetString()));
|
||||||
|
}
|
||||||
|
|
||||||
|
void PrintHostQueueDialog::on_cancel(Event &evt)
|
||||||
|
{
|
||||||
|
wxCHECK_RET(evt.job_id < (size_t)job_list->GetItemCount(), "Out of bounds access to job list");
|
||||||
|
|
||||||
|
set_state(evt.job_id, ST_CANCELLED);
|
||||||
|
job_list->SetValue(wxVariant(0), evt.job_id, COL_PROGRESS);
|
||||||
|
|
||||||
|
on_list_select();
|
||||||
|
|
||||||
|
wxVariant nm, hst;
|
||||||
|
job_list->GetValue(nm, evt.job_id, COL_FILENAME);
|
||||||
|
job_list->GetValue(hst, evt.job_id, COL_HOST);
|
||||||
|
wxGetApp().notification_manager()->upload_job_notification_show_canceled(evt.job_id + 1, boost::nowide::narrow(nm.GetString()), boost::nowide::narrow(hst.GetString()));
|
||||||
|
}
|
||||||
|
|
||||||
|
void PrintHostQueueDialog::get_active_jobs(std::vector<std::pair<std::string, std::string>>& ret)
|
||||||
|
{
|
||||||
|
int ic = job_list->GetItemCount();
|
||||||
|
for (int i = 0; i < ic; i++)
|
||||||
|
{
|
||||||
|
auto item = job_list->RowToItem(i);
|
||||||
|
auto data = job_list->GetItemData(item);
|
||||||
|
JobState st = static_cast<JobState>(data);
|
||||||
|
if(st == JobState::ST_NEW || st == JobState::ST_PROGRESS)
|
||||||
|
ret.emplace_back(upload_names[i]);
|
||||||
|
}
|
||||||
|
//job_list->data
|
||||||
|
}
|
||||||
|
void PrintHostQueueDialog::save_user_data(int udt)
|
||||||
|
{
|
||||||
|
const auto em = GetTextExtent("m").x;
|
||||||
|
auto *app_config = wxGetApp().app_config;
|
||||||
|
if (udt & UserDataType::UDT_SIZE) {
|
||||||
|
|
||||||
|
app_config->set("print_host_queue_dialog_height", std::to_string(this->GetSize().x / em));
|
||||||
|
app_config->set("print_host_queue_dialog_width", std::to_string(this->GetSize().y / em));
|
||||||
|
}
|
||||||
|
if (udt & UserDataType::UDT_POSITION)
|
||||||
|
{
|
||||||
|
app_config->set("print_host_queue_dialog_x", std::to_string(this->GetPosition().x));
|
||||||
|
app_config->set("print_host_queue_dialog_y", std::to_string(this->GetPosition().y));
|
||||||
|
}
|
||||||
|
if (udt & UserDataType::UDT_COLS)
|
||||||
|
{
|
||||||
|
for (size_t i = 0; i < job_list->GetColumnCount() - 1; i++)
|
||||||
|
{
|
||||||
|
app_config->set("print_host_queue_dialog_column_" + std::to_string(i), std::to_string(job_list->GetColumn(i)->GetWidth()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
bool PrintHostQueueDialog::load_user_data(int udt, std::vector<int>& vector)
|
||||||
|
{
|
||||||
|
auto* app_config = wxGetApp().app_config;
|
||||||
|
auto hasget = [app_config](const std::string& name, std::vector<int>& vector)->bool {
|
||||||
|
if (app_config->has(name)) {
|
||||||
|
vector.push_back(std::stoi(app_config->get(name)));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
if (udt & UserDataType::UDT_SIZE) {
|
||||||
|
if (!hasget("print_host_queue_dialog_height",vector))
|
||||||
|
return false;
|
||||||
|
if (!hasget("print_host_queue_dialog_width", vector))
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (udt & UserDataType::UDT_POSITION)
|
||||||
|
{
|
||||||
|
if (!hasget("print_host_queue_dialog_x", vector))
|
||||||
|
return false;
|
||||||
|
if (!hasget("print_host_queue_dialog_y", vector))
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (udt & UserDataType::UDT_COLS)
|
||||||
|
{
|
||||||
|
for (size_t i = 0; i < 6; i++)
|
||||||
|
{
|
||||||
|
if (!hasget("print_host_queue_dialog_column_" + std::to_string(i), vector))
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}}
|
130
src/slic3r/GUI/PrintHostDialogs.hpp
Normal file
130
src/slic3r/GUI/PrintHostDialogs.hpp
Normal file
|
@ -0,0 +1,130 @@
|
||||||
|
#ifndef slic3r_PrintHostSendDialog_hpp_
|
||||||
|
#define slic3r_PrintHostSendDialog_hpp_
|
||||||
|
|
||||||
|
#include <set>
|
||||||
|
#include <string>
|
||||||
|
#include <boost/filesystem/path.hpp>
|
||||||
|
|
||||||
|
#include <wx/string.h>
|
||||||
|
#include <wx/event.h>
|
||||||
|
#include <wx/dialog.h>
|
||||||
|
|
||||||
|
#include "GUI_Utils.hpp"
|
||||||
|
#include "MsgDialog.hpp"
|
||||||
|
#include "../Utils/PrintHost.hpp"
|
||||||
|
|
||||||
|
class wxButton;
|
||||||
|
class wxTextCtrl;
|
||||||
|
class wxChoice;
|
||||||
|
class wxComboBox;
|
||||||
|
class wxDataViewListCtrl;
|
||||||
|
|
||||||
|
namespace Slic3r {
|
||||||
|
|
||||||
|
namespace GUI {
|
||||||
|
|
||||||
|
class PrintHostSendDialog : public GUI::MsgDialog
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
PrintHostSendDialog(const boost::filesystem::path &path, PrintHostPostUploadActions post_actions, const wxArrayString& groups);
|
||||||
|
boost::filesystem::path filename() const;
|
||||||
|
PrintHostPostUploadAction post_action() const;
|
||||||
|
std::string group() const;
|
||||||
|
|
||||||
|
virtual void EndModal(int ret) override;
|
||||||
|
private:
|
||||||
|
wxTextCtrl *txt_filename;
|
||||||
|
wxComboBox *combo_groups;
|
||||||
|
PrintHostPostUploadAction post_upload_action;
|
||||||
|
wxString m_valid_suffix;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
class PrintHostQueueDialog : public DPIDialog
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
class Event : public wxEvent
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
size_t job_id;
|
||||||
|
int progress = 0; // in percent
|
||||||
|
wxString error;
|
||||||
|
|
||||||
|
Event(wxEventType eventType, int winid, size_t job_id);
|
||||||
|
Event(wxEventType eventType, int winid, size_t job_id, int progress);
|
||||||
|
Event(wxEventType eventType, int winid, size_t job_id, wxString error);
|
||||||
|
|
||||||
|
virtual wxEvent *Clone() const;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
PrintHostQueueDialog(wxWindow *parent);
|
||||||
|
|
||||||
|
void append_job(const PrintHostJob &job);
|
||||||
|
void get_active_jobs(std::vector<std::pair<std::string, std::string>>& ret);
|
||||||
|
|
||||||
|
virtual bool Show(bool show = true) override
|
||||||
|
{
|
||||||
|
if(!show)
|
||||||
|
save_user_data(UDT_SIZE | UDT_POSITION | UDT_COLS);
|
||||||
|
return DPIDialog::Show(show);
|
||||||
|
}
|
||||||
|
protected:
|
||||||
|
void on_dpi_changed(const wxRect &suggested_rect) override;
|
||||||
|
void on_sys_color_changed() override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
enum Column {
|
||||||
|
COL_ID,
|
||||||
|
COL_PROGRESS,
|
||||||
|
COL_STATUS,
|
||||||
|
COL_HOST,
|
||||||
|
COL_SIZE,
|
||||||
|
COL_FILENAME,
|
||||||
|
COL_ERRORMSG
|
||||||
|
};
|
||||||
|
|
||||||
|
enum JobState {
|
||||||
|
ST_NEW,
|
||||||
|
ST_PROGRESS,
|
||||||
|
ST_ERROR,
|
||||||
|
ST_CANCELLING,
|
||||||
|
ST_CANCELLED,
|
||||||
|
ST_COMPLETED,
|
||||||
|
};
|
||||||
|
|
||||||
|
enum { HEIGHT = 60, WIDTH = 30, SPACING = 5 };
|
||||||
|
|
||||||
|
enum UserDataType{
|
||||||
|
UDT_SIZE = 1,
|
||||||
|
UDT_POSITION = 2,
|
||||||
|
UDT_COLS = 4
|
||||||
|
};
|
||||||
|
|
||||||
|
wxButton *btn_cancel;
|
||||||
|
wxButton *btn_error;
|
||||||
|
wxDataViewListCtrl *job_list;
|
||||||
|
// Note: EventGuard prevents delivery of progress evts to a freed PrintHostQueueDialog
|
||||||
|
EventGuard on_progress_evt;
|
||||||
|
EventGuard on_error_evt;
|
||||||
|
EventGuard on_cancel_evt;
|
||||||
|
|
||||||
|
JobState get_state(int idx);
|
||||||
|
void set_state(int idx, JobState);
|
||||||
|
void on_list_select();
|
||||||
|
void on_progress(Event&);
|
||||||
|
void on_error(Event&);
|
||||||
|
void on_cancel(Event&);
|
||||||
|
// This vector keep adress and filename of uploads. It is used when checking for running uploads during exit.
|
||||||
|
std::vector<std::pair<std::string, std::string>> upload_names;
|
||||||
|
void save_user_data(int);
|
||||||
|
bool load_user_data(int, std::vector<int>&);
|
||||||
|
};
|
||||||
|
|
||||||
|
wxDECLARE_EVENT(EVT_PRINTHOST_PROGRESS, PrintHostQueueDialog::Event);
|
||||||
|
wxDECLARE_EVENT(EVT_PRINTHOST_ERROR, PrintHostQueueDialog::Event);
|
||||||
|
wxDECLARE_EVENT(EVT_PRINTHOST_CANCEL, PrintHostQueueDialog::Event);
|
||||||
|
|
||||||
|
}}
|
||||||
|
|
||||||
|
#endif
|
|
@ -43,6 +43,7 @@
|
||||||
#include "MarkdownTip.hpp"
|
#include "MarkdownTip.hpp"
|
||||||
#include "Search.hpp"
|
#include "Search.hpp"
|
||||||
|
|
||||||
|
// #include "BonjourDialog.hpp"
|
||||||
#ifdef WIN32
|
#ifdef WIN32
|
||||||
#include <commctrl.h>
|
#include <commctrl.h>
|
||||||
#endif // WIN32
|
#endif // WIN32
|
||||||
|
@ -2875,7 +2876,6 @@ void TabPrinter::build_fff()
|
||||||
|
|
||||||
// build_preset_description_line(optgroup.get());
|
// build_preset_description_line(optgroup.get());
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
build_unregular_pages(true);
|
build_unregular_pages(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -547,6 +547,7 @@ private:
|
||||||
std::vector<PageShp> m_pages_fff;
|
std::vector<PageShp> m_pages_fff;
|
||||||
std::vector<PageShp> m_pages_sla;
|
std::vector<PageShp> m_pages_sla;
|
||||||
|
|
||||||
|
wxBoxSizer* m_presets_sizer {nullptr};
|
||||||
public:
|
public:
|
||||||
ScalableButton* m_reset_to_filament_color = nullptr;
|
ScalableButton* m_reset_to_filament_color = nullptr;
|
||||||
|
|
||||||
|
@ -585,6 +586,7 @@ public:
|
||||||
//wxSizer* create_bed_shape_widget(wxWindow* parent);
|
//wxSizer* create_bed_shape_widget(wxWindow* parent);
|
||||||
void cache_extruder_cnt();
|
void cache_extruder_cnt();
|
||||||
bool apply_extruder_cnt_from_cache();
|
bool apply_extruder_cnt_from_cache();
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
class TabSLAMaterial : public Tab
|
class TabSLAMaterial : public Tab
|
||||||
|
|
173
src/slic3r/Utils/AstroBox.cpp
Normal file
173
src/slic3r/Utils/AstroBox.cpp
Normal file
|
@ -0,0 +1,173 @@
|
||||||
|
#include "AstroBox.hpp"
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
|
#include <sstream>
|
||||||
|
#include <exception>
|
||||||
|
#include <boost/format.hpp>
|
||||||
|
#include <boost/log/trivial.hpp>
|
||||||
|
#include <boost/property_tree/ptree.hpp>
|
||||||
|
#include <boost/property_tree/json_parser.hpp>
|
||||||
|
#include <boost/algorithm/string/predicate.hpp>
|
||||||
|
|
||||||
|
#include <wx/progdlg.h>
|
||||||
|
|
||||||
|
#include "libslic3r/PrintConfig.hpp"
|
||||||
|
#include "slic3r/GUI/I18N.hpp"
|
||||||
|
#include "slic3r/GUI/GUI.hpp"
|
||||||
|
#include "Http.hpp"
|
||||||
|
|
||||||
|
|
||||||
|
namespace fs = boost::filesystem;
|
||||||
|
namespace pt = boost::property_tree;
|
||||||
|
|
||||||
|
|
||||||
|
namespace Slic3r {
|
||||||
|
|
||||||
|
AstroBox::AstroBox(DynamicPrintConfig *config) :
|
||||||
|
host(config->opt_string("print_host")),
|
||||||
|
apikey(config->opt_string("printhost_apikey")),
|
||||||
|
cafile(config->opt_string("printhost_cafile"))
|
||||||
|
{}
|
||||||
|
|
||||||
|
const char* AstroBox::get_name() const { return "AstroBox"; }
|
||||||
|
|
||||||
|
bool AstroBox::test(wxString &msg) const
|
||||||
|
{
|
||||||
|
// Since the request is performed synchronously here,
|
||||||
|
// it is ok to refer to `msg` from within the closure
|
||||||
|
|
||||||
|
const char *name = get_name();
|
||||||
|
|
||||||
|
bool res = true;
|
||||||
|
auto url = make_url("api/version");
|
||||||
|
|
||||||
|
BOOST_LOG_TRIVIAL(info) << boost::format("%1%: Get version at: %2%") % name % url;
|
||||||
|
|
||||||
|
auto http = Http::get(std::move(url));
|
||||||
|
set_auth(http);
|
||||||
|
http.on_error([&](std::string body, std::string error, unsigned status) {
|
||||||
|
BOOST_LOG_TRIVIAL(error) << boost::format("%1%: Error getting version: %2%, HTTP %3%, body: `%4%`") % name % error % status % body;
|
||||||
|
res = false;
|
||||||
|
msg = format_error(body, error, status);
|
||||||
|
})
|
||||||
|
.on_complete([&, this](std::string body, unsigned) {
|
||||||
|
BOOST_LOG_TRIVIAL(debug) << boost::format("%1%: Got version: %2%") % name % body;
|
||||||
|
|
||||||
|
try {
|
||||||
|
std::stringstream ss(body);
|
||||||
|
pt::ptree ptree;
|
||||||
|
pt::read_json(ss, ptree);
|
||||||
|
|
||||||
|
if (! ptree.get_optional<std::string>("api")) {
|
||||||
|
res = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto text = ptree.get_optional<std::string>("text");
|
||||||
|
res = validate_version_text(text);
|
||||||
|
if (! res) {
|
||||||
|
msg = GUI::from_u8((boost::format(_utf8(L("Mismatched type of print host: %s"))) % (text ? *text : "AstroBox")).str());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (const std::exception &) {
|
||||||
|
res = false;
|
||||||
|
msg = "Could not parse server response";
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.perform_sync();
|
||||||
|
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
wxString AstroBox::get_test_ok_msg () const
|
||||||
|
{
|
||||||
|
return _(L("Connection to AstroBox works correctly."));
|
||||||
|
}
|
||||||
|
|
||||||
|
wxString AstroBox::get_test_failed_msg (wxString &msg) const
|
||||||
|
{
|
||||||
|
return GUI::from_u8((boost::format("%s: %s\n\n%s")
|
||||||
|
% _utf8(L("Could not connect to AstroBox"))
|
||||||
|
% std::string(msg.ToUTF8())
|
||||||
|
% _utf8(L("Note: AstroBox version at least 1.1.0 is required."))).str());
|
||||||
|
}
|
||||||
|
|
||||||
|
bool AstroBox::upload(PrintHostUpload upload_data, ProgressFn prorgess_fn, ErrorFn error_fn) const
|
||||||
|
{
|
||||||
|
const char *name = get_name();
|
||||||
|
|
||||||
|
const auto upload_filename = upload_data.upload_path.filename();
|
||||||
|
const auto upload_parent_path = upload_data.upload_path.parent_path();
|
||||||
|
|
||||||
|
wxString test_msg;
|
||||||
|
if (! test(test_msg)) {
|
||||||
|
error_fn(std::move(test_msg));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool res = true;
|
||||||
|
|
||||||
|
auto url = make_url("api/files/local");
|
||||||
|
|
||||||
|
BOOST_LOG_TRIVIAL(info) << boost::format("%1%: Uploading file %2% at %3%, filename: %4%, path: %5%, print: %6%")
|
||||||
|
% name
|
||||||
|
% upload_data.source_path
|
||||||
|
% url
|
||||||
|
% upload_filename.string()
|
||||||
|
% upload_parent_path.string()
|
||||||
|
% (upload_data.post_action == PrintHostPostUploadAction::StartPrint ? "true" : "false");
|
||||||
|
|
||||||
|
auto http = Http::post(std::move(url));
|
||||||
|
set_auth(http);
|
||||||
|
http.form_add("print", upload_data.post_action == PrintHostPostUploadAction::StartPrint ? "true" : "false")
|
||||||
|
.form_add("path", upload_parent_path.string()) // XXX: slashes on windows ???
|
||||||
|
.form_add_file("file", upload_data.source_path.string(), upload_filename.string())
|
||||||
|
.on_complete([&](std::string body, unsigned status) {
|
||||||
|
BOOST_LOG_TRIVIAL(debug) << boost::format("%1%: File uploaded: HTTP %2%: %3%") % name % status % body;
|
||||||
|
})
|
||||||
|
.on_error([&](std::string body, std::string error, unsigned status) {
|
||||||
|
BOOST_LOG_TRIVIAL(error) << boost::format("%1%: Error uploading file: %2%, HTTP %3%, body: `%4%`") % name % 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) << "AstroBox: Upload canceled";
|
||||||
|
res = false;
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.perform_sync();
|
||||||
|
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool AstroBox::validate_version_text(const boost::optional<std::string> &version_text) const
|
||||||
|
{
|
||||||
|
return version_text ? boost::starts_with(*version_text, "AstroBox") : true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void AstroBox::set_auth(Http &http) const
|
||||||
|
{
|
||||||
|
http.header("X-Api-Key", apikey);
|
||||||
|
|
||||||
|
if (! cafile.empty()) {
|
||||||
|
http.ca_file(cafile);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string AstroBox::make_url(const std::string &path) const
|
||||||
|
{
|
||||||
|
if (host.find("http://") == 0 || host.find("https://") == 0) {
|
||||||
|
if (host.back() == '/') {
|
||||||
|
return (boost::format("%1%%2%") % host % path).str();
|
||||||
|
} else {
|
||||||
|
return (boost::format("%1%/%2%") % host % path).str();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return (boost::format("http://%1%/%2%") % host % path).str();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
46
src/slic3r/Utils/AstroBox.hpp
Normal file
46
src/slic3r/Utils/AstroBox.hpp
Normal file
|
@ -0,0 +1,46 @@
|
||||||
|
#ifndef slic3r_AstroBox_hpp_
|
||||||
|
#define slic3r_AstroBox_hpp_
|
||||||
|
|
||||||
|
#include <string>
|
||||||
|
#include <wx/string.h>
|
||||||
|
#include <boost/optional.hpp>
|
||||||
|
|
||||||
|
#include "PrintHost.hpp"
|
||||||
|
|
||||||
|
namespace Slic3r {
|
||||||
|
|
||||||
|
class DynamicPrintConfig;
|
||||||
|
class Http;
|
||||||
|
|
||||||
|
class AstroBox : public PrintHost
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
AstroBox(DynamicPrintConfig *config);
|
||||||
|
~AstroBox() 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 true; }
|
||||||
|
bool can_test() const override { return true; }
|
||||||
|
PrintHostPostUploadActions get_post_upload_actions() const override { return PrintHostPostUploadAction::StartPrint; }
|
||||||
|
std::string get_host() const override { return host; }
|
||||||
|
|
||||||
|
protected:
|
||||||
|
bool validate_version_text(const boost::optional<std::string> &version_text) const;
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::string host;
|
||||||
|
std::string apikey;
|
||||||
|
std::string cafile;
|
||||||
|
|
||||||
|
void set_auth(Http &http) const;
|
||||||
|
std::string make_url(const std::string &path) const;
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
286
src/slic3r/Utils/Duet.cpp
Normal file
286
src/slic3r/Utils/Duet.cpp
Normal file
|
@ -0,0 +1,286 @@
|
||||||
|
#include "Duet.hpp"
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
|
#include <ctime>
|
||||||
|
#include <boost/filesystem/path.hpp>
|
||||||
|
#include <boost/format.hpp>
|
||||||
|
#include <boost/log/trivial.hpp>
|
||||||
|
#include <boost/property_tree/ptree.hpp>
|
||||||
|
#include <boost/property_tree/json_parser.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/I18N.hpp"
|
||||||
|
#include "slic3r/GUI/MsgDialog.hpp"
|
||||||
|
#include "Http.hpp"
|
||||||
|
|
||||||
|
namespace fs = boost::filesystem;
|
||||||
|
namespace pt = boost::property_tree;
|
||||||
|
|
||||||
|
namespace Slic3r {
|
||||||
|
|
||||||
|
Duet::Duet(DynamicPrintConfig *config) :
|
||||||
|
host(config->opt_string("print_host")),
|
||||||
|
password(config->opt_string("printhost_apikey"))
|
||||||
|
{}
|
||||||
|
|
||||||
|
const char* Duet::get_name() const { return "Duet"; }
|
||||||
|
|
||||||
|
bool Duet::test(wxString &msg) const
|
||||||
|
{
|
||||||
|
auto connectionType = connect(msg);
|
||||||
|
disconnect(connectionType);
|
||||||
|
|
||||||
|
return connectionType != ConnectionType::error;
|
||||||
|
}
|
||||||
|
|
||||||
|
wxString Duet::get_test_ok_msg () const
|
||||||
|
{
|
||||||
|
return _(L("Connection to Duet works correctly."));
|
||||||
|
}
|
||||||
|
|
||||||
|
wxString Duet::get_test_failed_msg (wxString &msg) const
|
||||||
|
{
|
||||||
|
return GUI::from_u8((boost::format("%s: %s")
|
||||||
|
% _utf8(L("Could not connect to Duet"))
|
||||||
|
% std::string(msg.ToUTF8())).str());
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Duet::upload(PrintHostUpload upload_data, ProgressFn prorgess_fn, ErrorFn error_fn) const
|
||||||
|
{
|
||||||
|
wxString connect_msg;
|
||||||
|
auto connectionType = connect(connect_msg);
|
||||||
|
if (connectionType == ConnectionType::error) {
|
||||||
|
error_fn(std::move(connect_msg));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool res = true;
|
||||||
|
bool dsf = (connectionType == ConnectionType::dsf);
|
||||||
|
|
||||||
|
auto upload_cmd = get_upload_url(upload_data.upload_path.string(), connectionType);
|
||||||
|
BOOST_LOG_TRIVIAL(info) << boost::format("Duet: Uploading file %1%, filepath: %2%, post_action: %3%, command: %4%")
|
||||||
|
% upload_data.source_path
|
||||||
|
% upload_data.upload_path
|
||||||
|
% int(upload_data.post_action)
|
||||||
|
% upload_cmd;
|
||||||
|
|
||||||
|
auto http = (dsf ? Http::put(std::move(upload_cmd)) : Http::post(std::move(upload_cmd)));
|
||||||
|
if (dsf) {
|
||||||
|
http.set_put_body(upload_data.source_path);
|
||||||
|
} else {
|
||||||
|
http.set_post_body(upload_data.source_path);
|
||||||
|
}
|
||||||
|
http.on_complete([&](std::string body, unsigned status) {
|
||||||
|
BOOST_LOG_TRIVIAL(debug) << boost::format("Duet: File uploaded: HTTP %1%: %2%") % status % body;
|
||||||
|
|
||||||
|
int err_code = dsf ? (status == 201 ? 0 : 1) : get_err_code_from_body(body);
|
||||||
|
if (err_code != 0) {
|
||||||
|
BOOST_LOG_TRIVIAL(error) << boost::format("Duet: 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.post_action == PrintHostPostUploadAction::StartPrint) {
|
||||||
|
wxString errormsg;
|
||||||
|
res = start_print(errormsg, upload_data.upload_path.string(), connectionType, false);
|
||||||
|
if (! res) {
|
||||||
|
error_fn(std::move(errormsg));
|
||||||
|
}
|
||||||
|
} else if (upload_data.post_action == PrintHostPostUploadAction::StartSimulation) {
|
||||||
|
wxString errormsg;
|
||||||
|
res = start_print(errormsg, upload_data.upload_path.string(), connectionType, true);
|
||||||
|
if (! res) {
|
||||||
|
error_fn(std::move(errormsg));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.on_error([&](std::string body, std::string error, unsigned status) {
|
||||||
|
BOOST_LOG_TRIVIAL(error) << boost::format("Duet: 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) << "Duet: Upload canceled";
|
||||||
|
res = false;
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.perform_sync();
|
||||||
|
|
||||||
|
disconnect(connectionType);
|
||||||
|
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
Duet::ConnectionType Duet::connect(wxString &msg) const
|
||||||
|
{
|
||||||
|
auto res = ConnectionType::error;
|
||||||
|
auto url = get_connect_url(false);
|
||||||
|
|
||||||
|
auto http = Http::get(std::move(url));
|
||||||
|
http.on_error([&](std::string body, std::string error, unsigned status) {
|
||||||
|
auto dsfUrl = get_connect_url(true);
|
||||||
|
auto dsfHttp = Http::get(std::move(dsfUrl));
|
||||||
|
dsfHttp.on_error([&](std::string body, std::string error, unsigned status) {
|
||||||
|
BOOST_LOG_TRIVIAL(error) << boost::format("Duet: Error connecting: %1%, HTTP %2%, body: `%3%`") % error % status % body;
|
||||||
|
msg = format_error(body, error, status);
|
||||||
|
})
|
||||||
|
.on_complete([&](std::string body, unsigned) {
|
||||||
|
res = ConnectionType::dsf;
|
||||||
|
})
|
||||||
|
.perform_sync();
|
||||||
|
})
|
||||||
|
.on_complete([&](std::string body, unsigned) {
|
||||||
|
BOOST_LOG_TRIVIAL(debug) << boost::format("Duet: Got: %1%") % body;
|
||||||
|
|
||||||
|
int err_code = get_err_code_from_body(body);
|
||||||
|
switch (err_code) {
|
||||||
|
case 0:
|
||||||
|
res = ConnectionType::rrf;
|
||||||
|
break;
|
||||||
|
case 1:
|
||||||
|
msg = format_error(body, L("Wrong password"), 0);
|
||||||
|
break;
|
||||||
|
case 2:
|
||||||
|
msg = format_error(body, L("Could not get resources to create a new connection"), 0);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
msg = format_error(body, L("Unknown error occured"), 0);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
})
|
||||||
|
.perform_sync();
|
||||||
|
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Duet::disconnect(ConnectionType connectionType) const
|
||||||
|
{
|
||||||
|
// we don't need to disconnect from DSF or if it failed anyway
|
||||||
|
if (connectionType != ConnectionType::rrf) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
auto url = (boost::format("%1%rr_disconnect")
|
||||||
|
% get_base_url()).str();
|
||||||
|
|
||||||
|
auto http = Http::get(std::move(url));
|
||||||
|
http.on_error([&](std::string body, std::string error, unsigned status) {
|
||||||
|
// we don't care about it, if disconnect is not working Duet will disconnect automatically after some time
|
||||||
|
BOOST_LOG_TRIVIAL(error) << boost::format("Duet: Error disconnecting: %1%, HTTP %2%, body: `%3%`") % error % status % body;
|
||||||
|
})
|
||||||
|
.perform_sync();
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string Duet::get_upload_url(const std::string &filename, ConnectionType connectionType) const
|
||||||
|
{
|
||||||
|
assert(connectionType != ConnectionType::error);
|
||||||
|
|
||||||
|
if (connectionType == ConnectionType::dsf) {
|
||||||
|
return (boost::format("%1%machine/file/gcodes/%2%")
|
||||||
|
% get_base_url()
|
||||||
|
% Http::url_encode(filename)).str();
|
||||||
|
} else {
|
||||||
|
return (boost::format("%1%rr_upload?name=0:/gcodes/%2%&%3%")
|
||||||
|
% get_base_url()
|
||||||
|
% Http::url_encode(filename)
|
||||||
|
% timestamp_str()).str();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string Duet::get_connect_url(const bool dsfUrl) const
|
||||||
|
{
|
||||||
|
if (dsfUrl) {
|
||||||
|
return (boost::format("%1%machine/status")
|
||||||
|
% get_base_url()).str();
|
||||||
|
} else {
|
||||||
|
return (boost::format("%1%rr_connect?password=%2%&%3%")
|
||||||
|
% get_base_url()
|
||||||
|
% (password.empty() ? "reprap" : password)
|
||||||
|
% timestamp_str()).str();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string Duet::get_base_url() const
|
||||||
|
{
|
||||||
|
if (host.find("http://") == 0 || host.find("https://") == 0) {
|
||||||
|
if (host.back() == '/') {
|
||||||
|
return host;
|
||||||
|
} else {
|
||||||
|
return (boost::format("%1%/") % host).str();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return (boost::format("http://%1%/") % host).str();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string Duet::timestamp_str() const
|
||||||
|
{
|
||||||
|
enum { BUFFER_SIZE = 32 };
|
||||||
|
|
||||||
|
auto t = std::time(nullptr);
|
||||||
|
auto tm = *std::localtime(&t);
|
||||||
|
|
||||||
|
char buffer[BUFFER_SIZE];
|
||||||
|
std::strftime(buffer, BUFFER_SIZE, "time=%Y-%m-%dT%H:%M:%S", &tm);
|
||||||
|
|
||||||
|
return std::string(buffer);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Duet::start_print(wxString &msg, const std::string &filename, ConnectionType connectionType, bool simulationMode) const
|
||||||
|
{
|
||||||
|
assert(connectionType != ConnectionType::error);
|
||||||
|
|
||||||
|
bool res = false;
|
||||||
|
bool dsf = (connectionType == ConnectionType::dsf);
|
||||||
|
|
||||||
|
auto url = dsf
|
||||||
|
? (boost::format("%1%machine/code")
|
||||||
|
% get_base_url()).str()
|
||||||
|
: (boost::format(simulationMode
|
||||||
|
? "%1%rr_gcode?gcode=M37%%20P\"0:/gcodes/%2%\""
|
||||||
|
: "%1%rr_gcode?gcode=M32%%20\"0:/gcodes/%2%\"")
|
||||||
|
% get_base_url()
|
||||||
|
% Http::url_encode(filename)).str();
|
||||||
|
|
||||||
|
auto http = (dsf ? Http::post(std::move(url)) : Http::get(std::move(url)));
|
||||||
|
if (dsf) {
|
||||||
|
http.set_post_body(
|
||||||
|
(boost::format(simulationMode
|
||||||
|
? "M37 P\"0:/gcodes/%1%\""
|
||||||
|
: "M32 \"0:/gcodes/%1%\"")
|
||||||
|
% filename).str()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
http.on_error([&](std::string body, std::string error, unsigned status) {
|
||||||
|
BOOST_LOG_TRIVIAL(error) << boost::format("Duet: Error starting print: %1%, HTTP %2%, body: `%3%`") % error % status % body;
|
||||||
|
msg = format_error(body, error, status);
|
||||||
|
})
|
||||||
|
.on_complete([&](std::string body, unsigned) {
|
||||||
|
BOOST_LOG_TRIVIAL(debug) << boost::format("Duet: Got: %1%") % body;
|
||||||
|
res = true;
|
||||||
|
})
|
||||||
|
.perform_sync();
|
||||||
|
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
int Duet::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<int>("err", 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
48
src/slic3r/Utils/Duet.hpp
Normal file
48
src/slic3r/Utils/Duet.hpp
Normal file
|
@ -0,0 +1,48 @@
|
||||||
|
#ifndef slic3r_Duet_hpp_
|
||||||
|
#define slic3r_Duet_hpp_
|
||||||
|
|
||||||
|
#include <string>
|
||||||
|
#include <wx/string.h>
|
||||||
|
|
||||||
|
#include "PrintHost.hpp"
|
||||||
|
|
||||||
|
namespace Slic3r {
|
||||||
|
|
||||||
|
class DynamicPrintConfig;
|
||||||
|
class Http;
|
||||||
|
|
||||||
|
class Duet : public PrintHost
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
explicit Duet(DynamicPrintConfig *config);
|
||||||
|
~Duet() 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; }
|
||||||
|
PrintHostPostUploadActions get_post_upload_actions() const override { return PrintHostPostUploadAction::StartPrint | PrintHostPostUploadAction::StartSimulation; }
|
||||||
|
std::string get_host() const override { return host; }
|
||||||
|
|
||||||
|
private:
|
||||||
|
enum class ConnectionType { rrf, dsf, error };
|
||||||
|
std::string host;
|
||||||
|
std::string password;
|
||||||
|
|
||||||
|
std::string get_upload_url(const std::string &filename, ConnectionType connectionType) const;
|
||||||
|
std::string get_connect_url(const bool dsfUrl) const;
|
||||||
|
std::string get_base_url() const;
|
||||||
|
std::string timestamp_str() const;
|
||||||
|
ConnectionType connect(wxString &msg) const;
|
||||||
|
void disconnect(ConnectionType connectionType) const;
|
||||||
|
bool start_print(wxString &msg, const std::string &filename, ConnectionType connectionType, bool simulationMode) const;
|
||||||
|
int get_err_code_from_body(const std::string &body) const;
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
229
src/slic3r/Utils/FlashAir.cpp
Normal file
229
src/slic3r/Utils/FlashAir.cpp
Normal file
|
@ -0,0 +1,229 @@
|
||||||
|
#include "FlashAir.hpp"
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
|
#include <ctime>
|
||||||
|
#include <boost/filesystem/path.hpp>
|
||||||
|
#include <boost/format.hpp>
|
||||||
|
#include <boost/log/trivial.hpp>
|
||||||
|
#include <boost/algorithm/string/predicate.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/I18N.hpp"
|
||||||
|
#include "slic3r/GUI/MsgDialog.hpp"
|
||||||
|
#include "Http.hpp"
|
||||||
|
|
||||||
|
namespace fs = boost::filesystem;
|
||||||
|
namespace pt = boost::property_tree;
|
||||||
|
|
||||||
|
namespace Slic3r {
|
||||||
|
|
||||||
|
FlashAir::FlashAir(DynamicPrintConfig *config) :
|
||||||
|
host(config->opt_string("print_host"))
|
||||||
|
{}
|
||||||
|
|
||||||
|
const char* FlashAir::get_name() const { return "FlashAir"; }
|
||||||
|
|
||||||
|
bool FlashAir::test(wxString &msg) const
|
||||||
|
{
|
||||||
|
// Since the request is performed synchronously here,
|
||||||
|
// it is ok to refer to `msg` from within the closure
|
||||||
|
|
||||||
|
const char *name = get_name();
|
||||||
|
|
||||||
|
bool res = false;
|
||||||
|
auto url = make_url("command.cgi", "op", "118");
|
||||||
|
|
||||||
|
BOOST_LOG_TRIVIAL(info) << boost::format("%1%: Get upload enabled at: %2%") % name % url;
|
||||||
|
|
||||||
|
auto http = Http::get(std::move(url));
|
||||||
|
http.on_error([&](std::string body, std::string error, unsigned status) {
|
||||||
|
BOOST_LOG_TRIVIAL(error) << boost::format("%1%: Error getting upload enabled: %2%, HTTP %3%, body: `%4%`") % name % error % status % body;
|
||||||
|
res = false;
|
||||||
|
msg = format_error(body, error, status);
|
||||||
|
})
|
||||||
|
.on_complete([&](std::string body, unsigned) {
|
||||||
|
BOOST_LOG_TRIVIAL(debug) << boost::format("%1%: Got upload enabled: %2%") % name % body;
|
||||||
|
|
||||||
|
res = boost::starts_with(body, "1");
|
||||||
|
if (! res) {
|
||||||
|
msg = _(L("Upload not enabled on FlashAir card."));
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.perform_sync();
|
||||||
|
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
wxString FlashAir::get_test_ok_msg () const
|
||||||
|
{
|
||||||
|
return _(L("Connection to FlashAir works correctly and upload is enabled."));
|
||||||
|
}
|
||||||
|
|
||||||
|
wxString FlashAir::get_test_failed_msg (wxString &msg) const
|
||||||
|
{
|
||||||
|
return GUI::from_u8((boost::format("%s: %s\n%s")
|
||||||
|
% _utf8(L("Could not connect to FlashAir"))
|
||||||
|
% std::string(msg.ToUTF8())
|
||||||
|
% _utf8(L("Note: FlashAir with firmware 2.00.02 or newer and activated upload function is required."))).str());
|
||||||
|
}
|
||||||
|
|
||||||
|
bool FlashAir::upload(PrintHostUpload upload_data, ProgressFn prorgess_fn, ErrorFn error_fn) const
|
||||||
|
{
|
||||||
|
const char *name = get_name();
|
||||||
|
|
||||||
|
const auto upload_filename = upload_data.upload_path.filename();
|
||||||
|
const auto upload_parent_path = upload_data.upload_path.parent_path();
|
||||||
|
wxString test_msg;
|
||||||
|
if (! test(test_msg)) {
|
||||||
|
error_fn(std::move(test_msg));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool res = false;
|
||||||
|
|
||||||
|
std::string strDest = upload_parent_path.string();
|
||||||
|
if (strDest.front()!='/') // Needs a leading / else root uploads fail.
|
||||||
|
{
|
||||||
|
strDest.insert(0,"/");
|
||||||
|
}
|
||||||
|
|
||||||
|
auto urlPrepare = make_url("upload.cgi", "WRITEPROTECT=ON&FTIME", timestamp_str());
|
||||||
|
auto urlSetDir = make_url("upload.cgi","UPDIR",strDest);
|
||||||
|
auto urlUpload = make_url("upload.cgi");
|
||||||
|
|
||||||
|
BOOST_LOG_TRIVIAL(info) << boost::format("%1%: Uploading file %2% at %3% / %4%, filename: %5%")
|
||||||
|
% name
|
||||||
|
% upload_data.source_path
|
||||||
|
% urlPrepare
|
||||||
|
% urlUpload
|
||||||
|
% upload_filename.string();
|
||||||
|
|
||||||
|
// set filetime for upload and make card writeprotect to prevent filesystem damage
|
||||||
|
auto httpPrepare = Http::get(std::move(urlPrepare));
|
||||||
|
httpPrepare.on_error([&](std::string body, std::string error, unsigned status) {
|
||||||
|
BOOST_LOG_TRIVIAL(error) << boost::format("%1%: Error preparing upload: %2%, HTTP %3%, body: `%4%`") % name % error % status % body;
|
||||||
|
error_fn(format_error(body, error, status));
|
||||||
|
res = false;
|
||||||
|
})
|
||||||
|
.on_complete([&, this](std::string body, unsigned) {
|
||||||
|
BOOST_LOG_TRIVIAL(debug) << boost::format("%1%: Got prepare result: %2%") % name % body;
|
||||||
|
res = boost::icontains(body, "SUCCESS");
|
||||||
|
if (! res) {
|
||||||
|
BOOST_LOG_TRIVIAL(error) << boost::format("%1%: Request completed but no SUCCESS message was received.") % name;
|
||||||
|
error_fn(format_error(body, L("Unknown error occured"), 0));
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.perform_sync();
|
||||||
|
|
||||||
|
if(! res ) {
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
// start file upload
|
||||||
|
auto httpDir = Http::get(std::move(urlSetDir));
|
||||||
|
httpDir.on_error([&](std::string body, std::string error, unsigned status) {
|
||||||
|
BOOST_LOG_TRIVIAL(error) << boost::format("%1%: Error setting upload dir: %2%, HTTP %3%, body: `%4%`") % name % error % status % body;
|
||||||
|
error_fn(format_error(body, error, status));
|
||||||
|
res = false;
|
||||||
|
})
|
||||||
|
.on_complete([&, this](std::string body, unsigned) {
|
||||||
|
BOOST_LOG_TRIVIAL(debug) << boost::format("%1%: Got dir select result: %2%") % name % body;
|
||||||
|
res = boost::icontains(body, "SUCCESS");
|
||||||
|
if (! res) {
|
||||||
|
BOOST_LOG_TRIVIAL(error) << boost::format("%1%: Request completed but no SUCCESS message was received.") % name;
|
||||||
|
error_fn(format_error(body, L("Unknown error occured"), 0));
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.perform_sync();
|
||||||
|
|
||||||
|
if(! res ) {
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto http = Http::post(std::move(urlUpload));
|
||||||
|
http.form_add_file("file", upload_data.source_path.string(), upload_filename.string())
|
||||||
|
.on_complete([&](std::string body, unsigned status) {
|
||||||
|
BOOST_LOG_TRIVIAL(debug) << boost::format("%1%: File uploaded: HTTP %2%: %3%") % name % status % body;
|
||||||
|
res = boost::icontains(body, "SUCCESS");
|
||||||
|
if (! res) {
|
||||||
|
BOOST_LOG_TRIVIAL(error) << boost::format("%1%: Request completed but no SUCCESS message was received.") % name;
|
||||||
|
error_fn(format_error(body, L("Unknown error occured"), 0));
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.on_error([&](std::string body, std::string error, unsigned status) {
|
||||||
|
BOOST_LOG_TRIVIAL(error) << boost::format("%1%: Error uploading file: %2%, HTTP %3%, body: `%4%`") % name % 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) << boost::format("%1%: Upload canceled") % name;
|
||||||
|
res = false;
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.perform_sync();
|
||||||
|
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string FlashAir::timestamp_str() const
|
||||||
|
{
|
||||||
|
auto t = std::time(nullptr);
|
||||||
|
auto tm = *std::localtime(&t);
|
||||||
|
|
||||||
|
unsigned long fattime = ((tm.tm_year - 80) << 25) |
|
||||||
|
((tm.tm_mon + 1) << 21) |
|
||||||
|
(tm.tm_mday << 16) |
|
||||||
|
(tm.tm_hour << 11) |
|
||||||
|
(tm.tm_min << 5) |
|
||||||
|
(tm.tm_sec >> 1);
|
||||||
|
|
||||||
|
return (boost::format("%1$#x") % fattime).str();
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string FlashAir::make_url(const std::string &path) const
|
||||||
|
{
|
||||||
|
if (host.find("http://") == 0 || host.find("https://") == 0) {
|
||||||
|
if (host.back() == '/') {
|
||||||
|
return (boost::format("%1%%2%") % host % path).str();
|
||||||
|
} else {
|
||||||
|
return (boost::format("%1%/%2%") % host % path).str();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (host.back() == '/') {
|
||||||
|
return (boost::format("http://%1%%2%") % host % path).str();
|
||||||
|
} else {
|
||||||
|
return (boost::format("http://%1%/%2%") % host % path).str();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string FlashAir::make_url(const std::string &path, const std::string &arg, const std::string &val) const
|
||||||
|
{
|
||||||
|
if (host.find("http://") == 0 || host.find("https://") == 0) {
|
||||||
|
if (host.back() == '/') {
|
||||||
|
return (boost::format("%1%%2%?%3%=%4%") % host % path % arg % val).str();
|
||||||
|
} else {
|
||||||
|
return (boost::format("%1%/%2%?%3%=%4%") % host % path % arg % val).str();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (host.back() == '/') {
|
||||||
|
return (boost::format("http://%1%%2%?%3%=%4%") % host % path % arg % val).str();
|
||||||
|
} else {
|
||||||
|
return (boost::format("http://%1%/%2%?%3%=%4%") % host % path % arg % val).str();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
42
src/slic3r/Utils/FlashAir.hpp
Normal file
42
src/slic3r/Utils/FlashAir.hpp
Normal file
|
@ -0,0 +1,42 @@
|
||||||
|
#ifndef slic3r_FlashAir_hpp_
|
||||||
|
#define slic3r_FlashAir_hpp_
|
||||||
|
|
||||||
|
#include <string>
|
||||||
|
#include <wx/string.h>
|
||||||
|
|
||||||
|
#include "PrintHost.hpp"
|
||||||
|
|
||||||
|
|
||||||
|
namespace Slic3r {
|
||||||
|
|
||||||
|
class DynamicPrintConfig;
|
||||||
|
class Http;
|
||||||
|
|
||||||
|
class FlashAir : public PrintHost
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
FlashAir(DynamicPrintConfig *config);
|
||||||
|
~FlashAir() 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; }
|
||||||
|
PrintHostPostUploadActions get_post_upload_actions() const override { return {}; }
|
||||||
|
std::string get_host() const override { return host; }
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::string host;
|
||||||
|
|
||||||
|
std::string timestamp_str() const;
|
||||||
|
std::string make_url(const std::string &path) const;
|
||||||
|
std::string make_url(const std::string &path, const std::string &arg, const std::string &val) const;
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
150
src/slic3r/Utils/MKS.cpp
Normal file
150
src/slic3r/Utils/MKS.cpp
Normal file
|
@ -0,0 +1,150 @@
|
||||||
|
#include "MKS.hpp"
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
|
#include <ctime>
|
||||||
|
#include <chrono>
|
||||||
|
#include <thread>
|
||||||
|
#include <boost/filesystem/path.hpp>
|
||||||
|
#include <boost/format.hpp>
|
||||||
|
#include <boost/log/trivial.hpp>
|
||||||
|
#include <boost/property_tree/ptree.hpp>
|
||||||
|
#include <boost/property_tree/json_parser.hpp>
|
||||||
|
#include <boost/asio.hpp>
|
||||||
|
#include <boost/algorithm/string.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/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.post_action == PrintHostPostUploadAction::StartPrint)
|
||||||
|
% 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.post_action == PrintHostPostUploadAction::StartPrint) {
|
||||||
|
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<int>("err", 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
} // Slic3r
|
42
src/slic3r/Utils/MKS.hpp
Normal file
42
src/slic3r/Utils/MKS.hpp
Normal file
|
@ -0,0 +1,42 @@
|
||||||
|
#ifndef slic3r_MKS_hpp_
|
||||||
|
#define slic3r_MKS_hpp_
|
||||||
|
|
||||||
|
#include <string>
|
||||||
|
#include <wx/string.h>
|
||||||
|
|
||||||
|
#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; }
|
||||||
|
PrintHostPostUploadActions get_post_upload_actions() const override { return PrintHostPostUploadAction::StartPrint; }
|
||||||
|
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
|
367
src/slic3r/Utils/OctoPrint.cpp
Normal file
367
src/slic3r/Utils/OctoPrint.cpp
Normal file
|
@ -0,0 +1,367 @@
|
||||||
|
#include "OctoPrint.hpp"
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
|
#include <sstream>
|
||||||
|
#include <exception>
|
||||||
|
#include <boost/format.hpp>
|
||||||
|
#include <boost/log/trivial.hpp>
|
||||||
|
#include <boost/property_tree/ptree.hpp>
|
||||||
|
#include <boost/property_tree/json_parser.hpp>
|
||||||
|
#include <boost/algorithm/string/predicate.hpp>
|
||||||
|
|
||||||
|
#include <curl/curl.h>
|
||||||
|
|
||||||
|
#include <wx/progdlg.h>
|
||||||
|
|
||||||
|
#include "slic3r/GUI/GUI.hpp"
|
||||||
|
#include "slic3r/GUI/I18N.hpp"
|
||||||
|
#include "slic3r/GUI/GUI.hpp"
|
||||||
|
#include "Http.hpp"
|
||||||
|
#include "libslic3r/AppConfig.hpp"
|
||||||
|
|
||||||
|
|
||||||
|
namespace fs = boost::filesystem;
|
||||||
|
namespace pt = boost::property_tree;
|
||||||
|
|
||||||
|
|
||||||
|
namespace Slic3r {
|
||||||
|
|
||||||
|
#ifdef WIN32
|
||||||
|
// Workaround for Windows 10/11 mDNS resolve issue, where two mDNS resolves in succession fail.
|
||||||
|
namespace {
|
||||||
|
std::string substitute_host(const std::string& orig_addr, std::string sub_addr)
|
||||||
|
{
|
||||||
|
// put ipv6 into [] brackets
|
||||||
|
if (sub_addr.find(':') != std::string::npos && sub_addr.at(0) != '[')
|
||||||
|
sub_addr = "[" + sub_addr + "]";
|
||||||
|
|
||||||
|
#if 0
|
||||||
|
//URI = scheme ":"["//"[userinfo "@"] host [":" port]] path["?" query]["#" fragment]
|
||||||
|
std::string final_addr = orig_addr;
|
||||||
|
// http
|
||||||
|
size_t double_dash = orig_addr.find("//");
|
||||||
|
size_t host_start = (double_dash == std::string::npos ? 0 : double_dash + 2);
|
||||||
|
// userinfo
|
||||||
|
size_t at = orig_addr.find("@");
|
||||||
|
host_start = (at != std::string::npos && at > host_start ? at + 1 : host_start);
|
||||||
|
// end of host, could be port(:), subpath(/) (could be query(?) or fragment(#)?)
|
||||||
|
// or it will be ']' if address is ipv6 )
|
||||||
|
size_t potencial_host_end = orig_addr.find_first_of(":/", host_start);
|
||||||
|
// if there are more ':' it must be ipv6
|
||||||
|
if (potencial_host_end != std::string::npos && orig_addr[potencial_host_end] == ':' && orig_addr.rfind(':') != potencial_host_end) {
|
||||||
|
size_t ipv6_end = orig_addr.find(']', host_start);
|
||||||
|
// DK: Uncomment and replace orig_addr.length() if we want to allow subpath after ipv6 without [] parentheses.
|
||||||
|
potencial_host_end = (ipv6_end != std::string::npos ? ipv6_end + 1 : orig_addr.length()); //orig_addr.find('/', host_start));
|
||||||
|
}
|
||||||
|
size_t host_end = (potencial_host_end != std::string::npos ? potencial_host_end : orig_addr.length());
|
||||||
|
// now host_start and host_end should mark where to put resolved addr
|
||||||
|
// check host_start. if its nonsense, lets just use original addr (or resolved addr?)
|
||||||
|
if (host_start >= orig_addr.length()) {
|
||||||
|
return final_addr;
|
||||||
|
}
|
||||||
|
final_addr.replace(host_start, host_end - host_start, sub_addr);
|
||||||
|
return final_addr;
|
||||||
|
#else
|
||||||
|
// Using the new CURL API for handling URL. https://everything.curl.dev/libcurl/url
|
||||||
|
// If anything fails, return the input unchanged.
|
||||||
|
std::string out = orig_addr;
|
||||||
|
CURLU *hurl = curl_url();
|
||||||
|
if (hurl) {
|
||||||
|
// Parse the input URL.
|
||||||
|
CURLUcode rc = curl_url_set(hurl, CURLUPART_URL, orig_addr.c_str(), 0);
|
||||||
|
if (rc == CURLUE_OK) {
|
||||||
|
// Replace the address.
|
||||||
|
rc = curl_url_set(hurl, CURLUPART_HOST, sub_addr.c_str(), 0);
|
||||||
|
if (rc == CURLUE_OK) {
|
||||||
|
// Extract a string fromt the CURL URL handle.
|
||||||
|
char *url;
|
||||||
|
rc = curl_url_get(hurl, CURLUPART_URL, &url, 0);
|
||||||
|
if (rc == CURLUE_OK) {
|
||||||
|
out = url;
|
||||||
|
curl_free(url);
|
||||||
|
} else
|
||||||
|
BOOST_LOG_TRIVIAL(error) << "OctoPrint substitute_host: failed to extract the URL after substitution";
|
||||||
|
} else
|
||||||
|
BOOST_LOG_TRIVIAL(error) << "OctoPrint substitute_host: failed to substitute host " << sub_addr << " in URL " << orig_addr;
|
||||||
|
} else
|
||||||
|
BOOST_LOG_TRIVIAL(error) << "OctoPrint substitute_host: failed to parse URL " << orig_addr;
|
||||||
|
curl_url_cleanup(hurl);
|
||||||
|
} else
|
||||||
|
BOOST_LOG_TRIVIAL(error) << "OctoPrint substitute_host: failed to allocate curl_url";
|
||||||
|
return out;
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
} //namespace
|
||||||
|
#endif // WIN32
|
||||||
|
|
||||||
|
OctoPrint::OctoPrint(DynamicPrintConfig *config) :
|
||||||
|
m_host(config->opt_string("print_host")),
|
||||||
|
m_apikey(config->opt_string("printhost_apikey")),
|
||||||
|
m_cafile(config->opt_string("printhost_cafile")),
|
||||||
|
m_ssl_revoke_best_effort(config->opt_bool("printhost_ssl_ignore_revoke"))
|
||||||
|
{}
|
||||||
|
|
||||||
|
const char* OctoPrint::get_name() const { return "OctoPrint"; }
|
||||||
|
|
||||||
|
bool OctoPrint::test(wxString &msg) const
|
||||||
|
{
|
||||||
|
// Since the request is performed synchronously here,
|
||||||
|
// it is ok to refer to `msg` from within the closure
|
||||||
|
|
||||||
|
const char *name = get_name();
|
||||||
|
|
||||||
|
bool res = true;
|
||||||
|
auto url = make_url("api/version");
|
||||||
|
|
||||||
|
BOOST_LOG_TRIVIAL(info) << boost::format("%1%: Get version at: %2%") % name % url;
|
||||||
|
|
||||||
|
auto http = Http::get(std::move(url));
|
||||||
|
set_auth(http);
|
||||||
|
http.on_error([&](std::string body, std::string error, unsigned status) {
|
||||||
|
BOOST_LOG_TRIVIAL(error) << boost::format("%1%: Error getting version: %2%, HTTP %3%, body: `%4%`") % name % error % status % body;
|
||||||
|
res = false;
|
||||||
|
msg = format_error(body, error, status);
|
||||||
|
})
|
||||||
|
.on_complete([&, this](std::string body, unsigned) {
|
||||||
|
BOOST_LOG_TRIVIAL(debug) << boost::format("%1%: Got version: %2%") % name % body;
|
||||||
|
|
||||||
|
try {
|
||||||
|
std::stringstream ss(body);
|
||||||
|
pt::ptree ptree;
|
||||||
|
pt::read_json(ss, ptree);
|
||||||
|
|
||||||
|
if (! ptree.get_optional<std::string>("api")) {
|
||||||
|
res = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto text = ptree.get_optional<std::string>("text");
|
||||||
|
res = validate_version_text(text);
|
||||||
|
if (! res) {
|
||||||
|
msg = GUI::from_u8((boost::format(_utf8(L("Mismatched type of print host: %s"))) % (text ? *text : "OctoPrint")).str());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (const std::exception &) {
|
||||||
|
res = false;
|
||||||
|
msg = "Could not parse server response";
|
||||||
|
}
|
||||||
|
})
|
||||||
|
#ifdef WIN32
|
||||||
|
.ssl_revoke_best_effort(m_ssl_revoke_best_effort)
|
||||||
|
.on_ip_resolve([&](std::string address) {
|
||||||
|
// Workaround for Windows 10/11 mDNS resolve issue, where two mDNS resolves in succession fail.
|
||||||
|
// Remember resolved address to be reused at successive REST API call.
|
||||||
|
msg = GUI::from_u8(address);
|
||||||
|
})
|
||||||
|
#endif // WIN32
|
||||||
|
.perform_sync();
|
||||||
|
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
wxString OctoPrint::get_test_ok_msg () const
|
||||||
|
{
|
||||||
|
return _(L("Connection to OctoPrint works correctly."));
|
||||||
|
}
|
||||||
|
|
||||||
|
wxString OctoPrint::get_test_failed_msg (wxString &msg) const
|
||||||
|
{
|
||||||
|
return GUI::from_u8((boost::format("%s: %s\n\n%s")
|
||||||
|
% _utf8(L("Could not connect to OctoPrint"))
|
||||||
|
% std::string(msg.ToUTF8())
|
||||||
|
% _utf8(L("Note: OctoPrint version at least 1.1.0 is required."))).str());
|
||||||
|
}
|
||||||
|
|
||||||
|
bool OctoPrint::upload(PrintHostUpload upload_data, ProgressFn prorgess_fn, ErrorFn error_fn) const
|
||||||
|
{
|
||||||
|
const char *name = get_name();
|
||||||
|
|
||||||
|
const auto upload_filename = upload_data.upload_path.filename();
|
||||||
|
const auto upload_parent_path = upload_data.upload_path.parent_path();
|
||||||
|
|
||||||
|
// If test fails, test_msg_or_host_ip contains the error message.
|
||||||
|
// Otherwise on Windows it contains the resolved IP address of the host.
|
||||||
|
wxString test_msg_or_host_ip;
|
||||||
|
if (! test(test_msg_or_host_ip)) {
|
||||||
|
error_fn(std::move(test_msg_or_host_ip));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string url;
|
||||||
|
bool res = true;
|
||||||
|
|
||||||
|
#ifdef WIN32
|
||||||
|
// Workaround for Windows 10/11 mDNS resolve issue, where two mDNS resolves in succession fail.
|
||||||
|
if (m_host.find("https://") == 0 || test_msg_or_host_ip.empty())
|
||||||
|
#endif // _WIN32
|
||||||
|
{
|
||||||
|
// If https is entered we assume signed ceritificate is being used
|
||||||
|
// IP resolving will not happen - it could resolve into address not being specified in cert
|
||||||
|
url = make_url("api/files/local");
|
||||||
|
}
|
||||||
|
#ifdef WIN32
|
||||||
|
else {
|
||||||
|
// Workaround for Windows 10/11 mDNS resolve issue, where two mDNS resolves in succession fail.
|
||||||
|
// Curl uses easy_getinfo to get ip address of last successful transaction.
|
||||||
|
// If it got the address use it instead of the stored in "host" variable.
|
||||||
|
// This new address returns in "test_msg_or_host_ip" variable.
|
||||||
|
// Solves troubles of uploades failing with name address.
|
||||||
|
// in original address (m_host) replace host for resolved ip
|
||||||
|
url = substitute_host(make_url("api/files/local"), GUI::into_u8(test_msg_or_host_ip));
|
||||||
|
BOOST_LOG_TRIVIAL(info) << "Upload address after ip resolve: " << url;
|
||||||
|
}
|
||||||
|
#endif // _WIN32
|
||||||
|
|
||||||
|
BOOST_LOG_TRIVIAL(info) << boost::format("%1%: Uploading file %2% at %3%, filename: %4%, path: %5%, print: %6%")
|
||||||
|
% name
|
||||||
|
% upload_data.source_path
|
||||||
|
% url
|
||||||
|
% upload_filename.string()
|
||||||
|
% upload_parent_path.string()
|
||||||
|
% (upload_data.post_action == PrintHostPostUploadAction::StartPrint ? "true" : "false");
|
||||||
|
|
||||||
|
auto http = Http::post(std::move(url));
|
||||||
|
set_auth(http);
|
||||||
|
http.form_add("print", upload_data.post_action == PrintHostPostUploadAction::StartPrint ? "true" : "false")
|
||||||
|
.form_add("path", upload_parent_path.string()) // XXX: slashes on windows ???
|
||||||
|
.form_add_file("file", upload_data.source_path.string(), upload_filename.string())
|
||||||
|
.on_complete([&](std::string body, unsigned status) {
|
||||||
|
BOOST_LOG_TRIVIAL(debug) << boost::format("%1%: File uploaded: HTTP %2%: %3%") % name % status % body;
|
||||||
|
})
|
||||||
|
.on_error([&](std::string body, std::string error, unsigned status) {
|
||||||
|
BOOST_LOG_TRIVIAL(error) << boost::format("%1%: Error uploading file: %2%, HTTP %3%, body: `%4%`") % name % 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) << "Octoprint: Upload canceled";
|
||||||
|
res = false;
|
||||||
|
}
|
||||||
|
})
|
||||||
|
#ifdef WIN32
|
||||||
|
.ssl_revoke_best_effort(m_ssl_revoke_best_effort)
|
||||||
|
#endif
|
||||||
|
.perform_sync();
|
||||||
|
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool OctoPrint::validate_version_text(const boost::optional<std::string> &version_text) const
|
||||||
|
{
|
||||||
|
return version_text ? boost::starts_with(*version_text, "OctoPrint") : true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void OctoPrint::set_auth(Http &http) const
|
||||||
|
{
|
||||||
|
http.header("X-Api-Key", m_apikey);
|
||||||
|
|
||||||
|
if (!m_cafile.empty()) {
|
||||||
|
http.ca_file(m_cafile);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string OctoPrint::make_url(const std::string &path) const
|
||||||
|
{
|
||||||
|
if (m_host.find("http://") == 0 || m_host.find("https://") == 0) {
|
||||||
|
if (m_host.back() == '/') {
|
||||||
|
return (boost::format("%1%%2%") % m_host % path).str();
|
||||||
|
} else {
|
||||||
|
return (boost::format("%1%/%2%") % m_host % path).str();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return (boost::format("http://%1%/%2%") % m_host % path).str();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
SL1Host::SL1Host(DynamicPrintConfig *config) :
|
||||||
|
OctoPrint(config),
|
||||||
|
m_authorization_type(dynamic_cast<const ConfigOptionEnum<AuthorizationType>*>(config->option("printhost_authorization_type"))->value),
|
||||||
|
m_username(config->opt_string("printhost_user")),
|
||||||
|
m_password(config->opt_string("printhost_password"))
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
// SL1Host
|
||||||
|
const char* SL1Host::get_name() const { return "SL1Host"; }
|
||||||
|
|
||||||
|
wxString SL1Host::get_test_ok_msg () const
|
||||||
|
{
|
||||||
|
return _(L("Connection to Prusa SL1 / SL1S works correctly."));
|
||||||
|
}
|
||||||
|
|
||||||
|
wxString SL1Host::get_test_failed_msg (wxString &msg) const
|
||||||
|
{
|
||||||
|
return GUI::from_u8((boost::format("%s: %s")
|
||||||
|
% _utf8(L("Could not connect to Prusa SLA"))
|
||||||
|
% std::string(msg.ToUTF8())).str());
|
||||||
|
}
|
||||||
|
|
||||||
|
bool SL1Host::validate_version_text(const boost::optional<std::string> &version_text) const
|
||||||
|
{
|
||||||
|
return version_text ? boost::starts_with(*version_text, "Prusa SLA") : false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void SL1Host::set_auth(Http &http) const
|
||||||
|
{
|
||||||
|
switch (m_authorization_type) {
|
||||||
|
case atKeyPassword:
|
||||||
|
http.header("X-Api-Key", get_apikey());
|
||||||
|
break;
|
||||||
|
case atUserPassword:
|
||||||
|
http.auth_digest(m_username, m_password);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (! get_cafile().empty()) {
|
||||||
|
http.ca_file(get_cafile());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// PrusaLink
|
||||||
|
PrusaLink::PrusaLink(DynamicPrintConfig* config) :
|
||||||
|
OctoPrint(config),
|
||||||
|
m_authorization_type(dynamic_cast<const ConfigOptionEnum<AuthorizationType>*>(config->option("printhost_authorization_type"))->value),
|
||||||
|
m_username(config->opt_string("printhost_user")),
|
||||||
|
m_password(config->opt_string("printhost_password"))
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
const char* PrusaLink::get_name() const { return "PrusaLink"; }
|
||||||
|
|
||||||
|
wxString PrusaLink::get_test_ok_msg() const
|
||||||
|
{
|
||||||
|
return _(L("Connection to PrusaLink works correctly."));
|
||||||
|
}
|
||||||
|
|
||||||
|
wxString PrusaLink::get_test_failed_msg(wxString& msg) const
|
||||||
|
{
|
||||||
|
return GUI::from_u8((boost::format("%s: %s")
|
||||||
|
% _utf8(L("Could not connect to PrusaLink"))
|
||||||
|
% std::string(msg.ToUTF8())).str());
|
||||||
|
}
|
||||||
|
|
||||||
|
bool PrusaLink::validate_version_text(const boost::optional<std::string>& version_text) const
|
||||||
|
{
|
||||||
|
return version_text ? (boost::starts_with(*version_text, "PrusaLink") || boost::starts_with(*version_text, "OctoPrint")) : false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void PrusaLink::set_auth(Http& http) const
|
||||||
|
{
|
||||||
|
switch (m_authorization_type) {
|
||||||
|
case atKeyPassword:
|
||||||
|
http.header("X-Api-Key", get_apikey());
|
||||||
|
break;
|
||||||
|
case atUserPassword:
|
||||||
|
http.auth_digest(m_username, m_password);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!get_cafile().empty()) {
|
||||||
|
http.ca_file(get_cafile());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
101
src/slic3r/Utils/OctoPrint.hpp
Normal file
101
src/slic3r/Utils/OctoPrint.hpp
Normal file
|
@ -0,0 +1,101 @@
|
||||||
|
#ifndef slic3r_OctoPrint_hpp_
|
||||||
|
#define slic3r_OctoPrint_hpp_
|
||||||
|
|
||||||
|
#include <string>
|
||||||
|
#include <wx/string.h>
|
||||||
|
#include <boost/optional.hpp>
|
||||||
|
|
||||||
|
#include "PrintHost.hpp"
|
||||||
|
#include "libslic3r/PrintConfig.hpp"
|
||||||
|
|
||||||
|
|
||||||
|
namespace Slic3r {
|
||||||
|
|
||||||
|
class DynamicPrintConfig;
|
||||||
|
class Http;
|
||||||
|
|
||||||
|
class OctoPrint : public PrintHost
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
OctoPrint(DynamicPrintConfig *config);
|
||||||
|
~OctoPrint() 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 true; }
|
||||||
|
bool can_test() const override { return true; }
|
||||||
|
PrintHostPostUploadActions get_post_upload_actions() const override { return PrintHostPostUploadAction::StartPrint; }
|
||||||
|
std::string get_host() const override { return m_host; }
|
||||||
|
const std::string& get_apikey() const { return m_apikey; }
|
||||||
|
const std::string& get_cafile() const { return m_cafile; }
|
||||||
|
|
||||||
|
protected:
|
||||||
|
virtual bool validate_version_text(const boost::optional<std::string> &version_text) const;
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::string m_host;
|
||||||
|
std::string m_apikey;
|
||||||
|
std::string m_cafile;
|
||||||
|
bool m_ssl_revoke_best_effort;
|
||||||
|
|
||||||
|
virtual void set_auth(Http &http) const;
|
||||||
|
std::string make_url(const std::string &path) const;
|
||||||
|
};
|
||||||
|
|
||||||
|
class SL1Host: public OctoPrint
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
SL1Host(DynamicPrintConfig *config);
|
||||||
|
~SL1Host() override = default;
|
||||||
|
|
||||||
|
const char* get_name() const override;
|
||||||
|
|
||||||
|
wxString get_test_ok_msg() const override;
|
||||||
|
wxString get_test_failed_msg(wxString &msg) const override;
|
||||||
|
PrintHostPostUploadActions get_post_upload_actions() const override { return {}; }
|
||||||
|
|
||||||
|
protected:
|
||||||
|
bool validate_version_text(const boost::optional<std::string> &version_text) const override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
void set_auth(Http &http) const override;
|
||||||
|
|
||||||
|
// Host authorization type.
|
||||||
|
AuthorizationType m_authorization_type;
|
||||||
|
// username and password for HTTP Digest Authentization (RFC RFC2617)
|
||||||
|
std::string m_username;
|
||||||
|
std::string m_password;
|
||||||
|
};
|
||||||
|
|
||||||
|
class PrusaLink : public OctoPrint
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
PrusaLink(DynamicPrintConfig* config);
|
||||||
|
~PrusaLink() override = default;
|
||||||
|
|
||||||
|
const char* get_name() const override;
|
||||||
|
|
||||||
|
wxString get_test_ok_msg() const override;
|
||||||
|
wxString get_test_failed_msg(wxString& msg) const override;
|
||||||
|
PrintHostPostUploadActions get_post_upload_actions() const override { return PrintHostPostUploadAction::StartPrint; }
|
||||||
|
|
||||||
|
protected:
|
||||||
|
bool validate_version_text(const boost::optional<std::string>& version_text) const override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
void set_auth(Http& http) const override;
|
||||||
|
|
||||||
|
// Host authorization type.
|
||||||
|
AuthorizationType m_authorization_type;
|
||||||
|
// username and password for HTTP Digest Authentization (RFC RFC2617)
|
||||||
|
std::string m_username;
|
||||||
|
std::string m_password;
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
281
src/slic3r/Utils/PrintHost.cpp
Normal file
281
src/slic3r/Utils/PrintHost.cpp
Normal file
|
@ -0,0 +1,281 @@
|
||||||
|
#include "PrintHost.hpp"
|
||||||
|
|
||||||
|
#include <vector>
|
||||||
|
#include <thread>
|
||||||
|
#include <exception>
|
||||||
|
#include <boost/optional.hpp>
|
||||||
|
#include <boost/log/trivial.hpp>
|
||||||
|
#include <boost/filesystem.hpp>
|
||||||
|
|
||||||
|
#include <wx/string.h>
|
||||||
|
#include <wx/app.h>
|
||||||
|
#include <wx/arrstr.h>
|
||||||
|
|
||||||
|
#include "libslic3r/PrintConfig.hpp"
|
||||||
|
#include "libslic3r/Channel.hpp"
|
||||||
|
#include "OctoPrint.hpp"
|
||||||
|
#include "Duet.hpp"
|
||||||
|
#include "FlashAir.hpp"
|
||||||
|
#include "AstroBox.hpp"
|
||||||
|
#include "Repetier.hpp"
|
||||||
|
#include "MKS.hpp"
|
||||||
|
#include "../GUI/PrintHostDialogs.hpp"
|
||||||
|
|
||||||
|
namespace fs = boost::filesystem;
|
||||||
|
using boost::optional;
|
||||||
|
using Slic3r::GUI::PrintHostQueueDialog;
|
||||||
|
|
||||||
|
namespace Slic3r {
|
||||||
|
|
||||||
|
|
||||||
|
PrintHost::~PrintHost() {}
|
||||||
|
|
||||||
|
PrintHost* PrintHost::get_print_host(DynamicPrintConfig *config)
|
||||||
|
{
|
||||||
|
PrinterTechnology tech = ptFFF;
|
||||||
|
|
||||||
|
{
|
||||||
|
const auto opt = config->option<ConfigOptionEnum<PrinterTechnology>>("printer_technology");
|
||||||
|
if (opt != nullptr) {
|
||||||
|
tech = opt->value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (tech == ptFFF) {
|
||||||
|
const auto opt = config->option<ConfigOptionEnum<PrintHostType>>("host_type");
|
||||||
|
const auto host_type = opt != nullptr ? opt->value : htOctoPrint;
|
||||||
|
|
||||||
|
switch (host_type) {
|
||||||
|
case htOctoPrint: return new OctoPrint(config);
|
||||||
|
case htDuet: return new Duet(config);
|
||||||
|
case htFlashAir: return new FlashAir(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 {
|
||||||
|
return new SL1Host(config);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
wxString PrintHost::format_error(const std::string &body, const std::string &error, unsigned status) const
|
||||||
|
{
|
||||||
|
if (status != 0) {
|
||||||
|
auto wxbody = wxString::FromUTF8(body.data());
|
||||||
|
return wxString::Format("HTTP %u: %s", status, wxbody);
|
||||||
|
} else {
|
||||||
|
return wxString::FromUTF8(error.data());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
struct PrintHostJobQueue::priv
|
||||||
|
{
|
||||||
|
// XXX: comment on how bg thread works
|
||||||
|
|
||||||
|
PrintHostJobQueue *q;
|
||||||
|
|
||||||
|
Channel<PrintHostJob> channel_jobs;
|
||||||
|
Channel<size_t> channel_cancels;
|
||||||
|
size_t job_id = 0;
|
||||||
|
int prev_progress = -1;
|
||||||
|
fs::path source_to_remove;
|
||||||
|
|
||||||
|
std::thread bg_thread;
|
||||||
|
bool bg_exit = false;
|
||||||
|
|
||||||
|
PrintHostQueueDialog *queue_dialog;
|
||||||
|
|
||||||
|
priv(PrintHostJobQueue *q) : q(q) {}
|
||||||
|
|
||||||
|
void emit_progress(int progress);
|
||||||
|
void emit_error(wxString error);
|
||||||
|
void emit_cancel(size_t id);
|
||||||
|
void start_bg_thread();
|
||||||
|
void stop_bg_thread();
|
||||||
|
void bg_thread_main();
|
||||||
|
void progress_fn(Http::Progress progress, bool &cancel);
|
||||||
|
void remove_source(const fs::path &path);
|
||||||
|
void remove_source();
|
||||||
|
void perform_job(PrintHostJob the_job);
|
||||||
|
};
|
||||||
|
|
||||||
|
PrintHostJobQueue::PrintHostJobQueue(PrintHostQueueDialog *queue_dialog)
|
||||||
|
: p(new priv(this))
|
||||||
|
{
|
||||||
|
p->queue_dialog = queue_dialog;
|
||||||
|
}
|
||||||
|
|
||||||
|
PrintHostJobQueue::~PrintHostJobQueue()
|
||||||
|
{
|
||||||
|
if (p) { p->stop_bg_thread(); }
|
||||||
|
}
|
||||||
|
|
||||||
|
void PrintHostJobQueue::priv::emit_progress(int progress)
|
||||||
|
{
|
||||||
|
auto evt = new PrintHostQueueDialog::Event(GUI::EVT_PRINTHOST_PROGRESS, queue_dialog->GetId(), job_id, progress);
|
||||||
|
wxQueueEvent(queue_dialog, evt);
|
||||||
|
}
|
||||||
|
|
||||||
|
void PrintHostJobQueue::priv::emit_error(wxString error)
|
||||||
|
{
|
||||||
|
auto evt = new PrintHostQueueDialog::Event(GUI::EVT_PRINTHOST_ERROR, queue_dialog->GetId(), job_id, std::move(error));
|
||||||
|
wxQueueEvent(queue_dialog, evt);
|
||||||
|
}
|
||||||
|
|
||||||
|
void PrintHostJobQueue::priv::emit_cancel(size_t id)
|
||||||
|
{
|
||||||
|
auto evt = new PrintHostQueueDialog::Event(GUI::EVT_PRINTHOST_CANCEL, queue_dialog->GetId(), id);
|
||||||
|
wxQueueEvent(queue_dialog, evt);
|
||||||
|
}
|
||||||
|
|
||||||
|
void PrintHostJobQueue::priv::start_bg_thread()
|
||||||
|
{
|
||||||
|
if (bg_thread.joinable()) { return; }
|
||||||
|
|
||||||
|
std::shared_ptr<priv> p2 = q->p;
|
||||||
|
bg_thread = std::thread([p2]() {
|
||||||
|
p2->bg_thread_main();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void PrintHostJobQueue::priv::stop_bg_thread()
|
||||||
|
{
|
||||||
|
if (bg_thread.joinable()) {
|
||||||
|
bg_exit = true;
|
||||||
|
channel_jobs.push(PrintHostJob()); // Push an empty job to wake up bg_thread in case it's sleeping
|
||||||
|
bg_thread.detach(); // Let the background thread go, it should exit on its own
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void PrintHostJobQueue::priv::bg_thread_main()
|
||||||
|
{
|
||||||
|
// bg thread entry point
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Pick up jobs from the job channel:
|
||||||
|
while (! bg_exit) {
|
||||||
|
auto job = channel_jobs.pop(); // Sleeps in a cond var if there are no jobs
|
||||||
|
if (job.empty()) {
|
||||||
|
// This happens when the thread is being stopped
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
source_to_remove = job.upload_data.source_path;
|
||||||
|
|
||||||
|
BOOST_LOG_TRIVIAL(debug) << boost::format("PrintHostJobQueue/bg_thread: Received job: [%1%]: `%2%` -> `%3%`, cancelled: %4%")
|
||||||
|
% job_id
|
||||||
|
% job.upload_data.upload_path
|
||||||
|
% job.printhost->get_host()
|
||||||
|
% job.cancelled;
|
||||||
|
|
||||||
|
if (! job.cancelled) {
|
||||||
|
perform_job(std::move(job));
|
||||||
|
}
|
||||||
|
|
||||||
|
remove_source();
|
||||||
|
job_id++;
|
||||||
|
}
|
||||||
|
} catch (const std::exception &e) {
|
||||||
|
emit_error(e.what());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Cleanup leftover files, if any
|
||||||
|
remove_source();
|
||||||
|
auto jobs = channel_jobs.lock_rw();
|
||||||
|
for (const PrintHostJob &job : *jobs) {
|
||||||
|
remove_source(job.upload_data.source_path);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void PrintHostJobQueue::priv::progress_fn(Http::Progress progress, bool &cancel)
|
||||||
|
{
|
||||||
|
if (cancel) {
|
||||||
|
// When cancel is true from the start, Http indicates request has been cancelled
|
||||||
|
emit_cancel(job_id);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (bg_exit) {
|
||||||
|
cancel = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (channel_cancels.size_hint() > 0) {
|
||||||
|
// Lock both queues
|
||||||
|
auto cancels = channel_cancels.lock_rw();
|
||||||
|
auto jobs = channel_jobs.lock_rw();
|
||||||
|
|
||||||
|
for (size_t cancel_id : *cancels) {
|
||||||
|
if (cancel_id == job_id) {
|
||||||
|
cancel = true;
|
||||||
|
} else if (cancel_id > job_id) {
|
||||||
|
const size_t idx = cancel_id - job_id - 1;
|
||||||
|
if (idx < jobs->size()) {
|
||||||
|
jobs->at(idx).cancelled = true;
|
||||||
|
BOOST_LOG_TRIVIAL(debug) << boost::format("PrintHostJobQueue: Job id %1% cancelled") % cancel_id;
|
||||||
|
emit_cancel(cancel_id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
cancels->clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (! cancel) {
|
||||||
|
int gui_progress = progress.ultotal > 0 ? 100*progress.ulnow / progress.ultotal : 0;
|
||||||
|
if (gui_progress != prev_progress) {
|
||||||
|
emit_progress(gui_progress);
|
||||||
|
prev_progress = gui_progress;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void PrintHostJobQueue::priv::remove_source(const fs::path &path)
|
||||||
|
{
|
||||||
|
if (! path.empty()) {
|
||||||
|
boost::system::error_code ec;
|
||||||
|
fs::remove(path, ec);
|
||||||
|
if (ec) {
|
||||||
|
BOOST_LOG_TRIVIAL(error) << boost::format("PrintHostJobQueue: Error removing file `%1%`: %2%") % path % ec;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void PrintHostJobQueue::priv::remove_source()
|
||||||
|
{
|
||||||
|
remove_source(source_to_remove);
|
||||||
|
source_to_remove.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
void PrintHostJobQueue::priv::perform_job(PrintHostJob the_job)
|
||||||
|
{
|
||||||
|
emit_progress(0); // Indicate the upload is starting
|
||||||
|
|
||||||
|
bool success = the_job.printhost->upload(std::move(the_job.upload_data),
|
||||||
|
[this](Http::Progress progress, bool &cancel) { this->progress_fn(std::move(progress), cancel); },
|
||||||
|
[this](wxString error) {
|
||||||
|
emit_error(std::move(error));
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
if (success) {
|
||||||
|
emit_progress(100);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void PrintHostJobQueue::enqueue(PrintHostJob job)
|
||||||
|
{
|
||||||
|
p->start_bg_thread();
|
||||||
|
p->queue_dialog->append_job(job);
|
||||||
|
p->channel_jobs.push(std::move(job));
|
||||||
|
}
|
||||||
|
|
||||||
|
void PrintHostJobQueue::cancel(size_t id)
|
||||||
|
{
|
||||||
|
p->channel_cancels.push(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
129
src/slic3r/Utils/PrintHost.hpp
Normal file
129
src/slic3r/Utils/PrintHost.hpp
Normal file
|
@ -0,0 +1,129 @@
|
||||||
|
#ifndef slic3r_PrintHost_hpp_
|
||||||
|
#define slic3r_PrintHost_hpp_
|
||||||
|
|
||||||
|
#include <memory>
|
||||||
|
#include <set>
|
||||||
|
#include <string>
|
||||||
|
#include <functional>
|
||||||
|
#include <boost/filesystem/path.hpp>
|
||||||
|
|
||||||
|
#include <wx/string.h>
|
||||||
|
|
||||||
|
#include <libslic3r/enum_bitmask.hpp>
|
||||||
|
#include "Http.hpp"
|
||||||
|
|
||||||
|
class wxArrayString;
|
||||||
|
|
||||||
|
namespace Slic3r {
|
||||||
|
|
||||||
|
class DynamicPrintConfig;
|
||||||
|
|
||||||
|
enum class PrintHostPostUploadAction {
|
||||||
|
None,
|
||||||
|
StartPrint,
|
||||||
|
StartSimulation
|
||||||
|
};
|
||||||
|
using PrintHostPostUploadActions = enum_bitmask<PrintHostPostUploadAction>;
|
||||||
|
ENABLE_ENUM_BITMASK_OPERATORS(PrintHostPostUploadAction);
|
||||||
|
|
||||||
|
struct PrintHostUpload
|
||||||
|
{
|
||||||
|
boost::filesystem::path source_path;
|
||||||
|
boost::filesystem::path upload_path;
|
||||||
|
|
||||||
|
std::string group;
|
||||||
|
|
||||||
|
PrintHostPostUploadAction post_action { PrintHostPostUploadAction::None };
|
||||||
|
};
|
||||||
|
|
||||||
|
class PrintHost
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
virtual ~PrintHost();
|
||||||
|
|
||||||
|
typedef Http::ProgressFn ProgressFn;
|
||||||
|
typedef std::function<void(wxString /* error */)> ErrorFn;
|
||||||
|
|
||||||
|
virtual const char* get_name() const = 0;
|
||||||
|
|
||||||
|
virtual bool test(wxString &curl_msg) const = 0;
|
||||||
|
virtual wxString get_test_ok_msg () const = 0;
|
||||||
|
virtual wxString get_test_failed_msg (wxString &msg) const = 0;
|
||||||
|
virtual bool upload(PrintHostUpload upload_data, ProgressFn prorgess_fn, ErrorFn error_fn) const = 0;
|
||||||
|
virtual bool has_auto_discovery() const = 0;
|
||||||
|
virtual bool can_test() const = 0;
|
||||||
|
virtual PrintHostPostUploadActions get_post_upload_actions() const = 0;
|
||||||
|
// A print host usually does not support multiple printers, with the exception of Repetier server.
|
||||||
|
virtual bool supports_multiple_printers() const { return false; }
|
||||||
|
virtual std::string get_host() const = 0;
|
||||||
|
|
||||||
|
// Support for Repetier server multiple groups & printers. Not supported by other print hosts.
|
||||||
|
// Returns false if not supported. May throw HostNetworkError.
|
||||||
|
virtual bool get_groups(wxArrayString & /* groups */) const { return false; }
|
||||||
|
virtual bool get_printers(wxArrayString & /* printers */) const { return false; }
|
||||||
|
|
||||||
|
static PrintHost* get_print_host(DynamicPrintConfig *config);
|
||||||
|
|
||||||
|
protected:
|
||||||
|
virtual wxString format_error(const std::string &body, const std::string &error, unsigned status) const;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
struct PrintHostJob
|
||||||
|
{
|
||||||
|
PrintHostUpload upload_data;
|
||||||
|
std::unique_ptr<PrintHost> printhost;
|
||||||
|
bool cancelled = false;
|
||||||
|
|
||||||
|
PrintHostJob() {}
|
||||||
|
PrintHostJob(const PrintHostJob&) = delete;
|
||||||
|
PrintHostJob(PrintHostJob &&other)
|
||||||
|
: upload_data(std::move(other.upload_data))
|
||||||
|
, printhost(std::move(other.printhost))
|
||||||
|
, cancelled(other.cancelled)
|
||||||
|
{}
|
||||||
|
|
||||||
|
PrintHostJob(DynamicPrintConfig *config)
|
||||||
|
: printhost(PrintHost::get_print_host(config))
|
||||||
|
{}
|
||||||
|
|
||||||
|
PrintHostJob& operator=(const PrintHostJob&) = delete;
|
||||||
|
PrintHostJob& operator=(PrintHostJob &&other)
|
||||||
|
{
|
||||||
|
upload_data = std::move(other.upload_data);
|
||||||
|
printhost = std::move(other.printhost);
|
||||||
|
cancelled = other.cancelled;
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool empty() const { return !printhost; }
|
||||||
|
operator bool() const { return !!printhost; }
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
namespace GUI { class PrintHostQueueDialog; }
|
||||||
|
|
||||||
|
class PrintHostJobQueue
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
PrintHostJobQueue(GUI::PrintHostQueueDialog *queue_dialog);
|
||||||
|
PrintHostJobQueue(const PrintHostJobQueue &) = delete;
|
||||||
|
PrintHostJobQueue(PrintHostJobQueue &&other) = delete;
|
||||||
|
~PrintHostJobQueue();
|
||||||
|
|
||||||
|
PrintHostJobQueue& operator=(const PrintHostJobQueue &) = delete;
|
||||||
|
PrintHostJobQueue& operator=(PrintHostJobQueue &&other) = delete;
|
||||||
|
|
||||||
|
void enqueue(PrintHostJob job);
|
||||||
|
void cancel(size_t id);
|
||||||
|
|
||||||
|
private:
|
||||||
|
struct priv;
|
||||||
|
std::shared_ptr<priv> p;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
274
src/slic3r/Utils/Repetier.cpp
Normal file
274
src/slic3r/Utils/Repetier.cpp
Normal file
|
@ -0,0 +1,274 @@
|
||||||
|
#include "Repetier.hpp"
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
|
#include <sstream>
|
||||||
|
#include <exception>
|
||||||
|
#include <boost/foreach.hpp>
|
||||||
|
#include <boost/format.hpp>
|
||||||
|
#include <boost/log/trivial.hpp>
|
||||||
|
#include <boost/property_tree/ptree.hpp>
|
||||||
|
#include <boost/property_tree/json_parser.hpp>
|
||||||
|
#include <boost/algorithm/string/predicate.hpp>
|
||||||
|
|
||||||
|
#include <wx/progdlg.h>
|
||||||
|
|
||||||
|
|
||||||
|
#include "libslic3r/PrintConfig.hpp"
|
||||||
|
#include "slic3r/GUI/I18N.hpp"
|
||||||
|
#include "slic3r/GUI/GUI.hpp"
|
||||||
|
#include "slic3r/GUI/format.hpp"
|
||||||
|
#include "Http.hpp"
|
||||||
|
|
||||||
|
|
||||||
|
namespace fs = boost::filesystem;
|
||||||
|
namespace pt = boost::property_tree;
|
||||||
|
|
||||||
|
|
||||||
|
namespace Slic3r {
|
||||||
|
|
||||||
|
Repetier::Repetier(DynamicPrintConfig *config) :
|
||||||
|
host(config->opt_string("print_host")),
|
||||||
|
apikey(config->opt_string("printhost_apikey")),
|
||||||
|
cafile(config->opt_string("printhost_cafile")),
|
||||||
|
port(config->opt_string("printhost_port"))
|
||||||
|
{}
|
||||||
|
|
||||||
|
const char* Repetier::get_name() const { return "Repetier"; }
|
||||||
|
|
||||||
|
bool Repetier::test(wxString &msg) const
|
||||||
|
{
|
||||||
|
// Since the request is performed synchronously here,
|
||||||
|
// it is ok to refer to `msg` from within the closure
|
||||||
|
|
||||||
|
const char *name = get_name();
|
||||||
|
|
||||||
|
bool res = true;
|
||||||
|
auto url = make_url("printer/info");
|
||||||
|
|
||||||
|
BOOST_LOG_TRIVIAL(info) << boost::format("%1%: List version at: %2%") % name % url;
|
||||||
|
|
||||||
|
auto http = Http::get(std::move(url));
|
||||||
|
set_auth(http);
|
||||||
|
|
||||||
|
http.on_error([&](std::string body, std::string error, unsigned status) {
|
||||||
|
BOOST_LOG_TRIVIAL(error) << boost::format("%1%: Error getting version: %2%, HTTP %3%, body: `%4%`") % name % error % status % body;
|
||||||
|
res = false;
|
||||||
|
msg = format_error(body, error, status);
|
||||||
|
})
|
||||||
|
.on_complete([&, this](std::string body, unsigned) {
|
||||||
|
BOOST_LOG_TRIVIAL(debug) << boost::format("%1%: Got version: %2%") % name % body;
|
||||||
|
|
||||||
|
try {
|
||||||
|
std::stringstream ss(body);
|
||||||
|
pt::ptree ptree;
|
||||||
|
pt::read_json(ss, ptree);
|
||||||
|
|
||||||
|
const auto text = ptree.get_optional<std::string>("name");
|
||||||
|
res = validate_version_text(text);
|
||||||
|
if (! res) {
|
||||||
|
msg = GUI::from_u8((boost::format(_utf8(L("Mismatched type of print host: %s"))) % (text ? *text : "Repetier")).str());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (const std::exception &) {
|
||||||
|
res = false;
|
||||||
|
msg = "Could not parse server response";
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.perform_sync();
|
||||||
|
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
wxString Repetier::get_test_ok_msg () const
|
||||||
|
{
|
||||||
|
return _(L("Connection to Repetier works correctly."));
|
||||||
|
}
|
||||||
|
|
||||||
|
wxString Repetier::get_test_failed_msg (wxString &msg) const
|
||||||
|
{
|
||||||
|
return GUI::from_u8((boost::format("%s: %s\n\n%s")
|
||||||
|
% _utf8(L("Could not connect to Repetier"))
|
||||||
|
% std::string(msg.ToUTF8())
|
||||||
|
% _utf8(L("Note: Repetier version at least 0.90.0 is required."))).str());
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Repetier::upload(PrintHostUpload upload_data, ProgressFn prorgess_fn, ErrorFn error_fn) const
|
||||||
|
{
|
||||||
|
const char *name = get_name();
|
||||||
|
|
||||||
|
const auto upload_filename = upload_data.upload_path.filename();
|
||||||
|
const auto upload_parent_path = upload_data.upload_path.parent_path();
|
||||||
|
|
||||||
|
wxString test_msg;
|
||||||
|
if (! test(test_msg)) {
|
||||||
|
error_fn(std::move(test_msg));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool res = true;
|
||||||
|
|
||||||
|
auto url = upload_data.post_action == PrintHostPostUploadAction::StartPrint
|
||||||
|
? make_url((boost::format("printer/job/%1%") % port).str())
|
||||||
|
: make_url((boost::format("printer/model/%1%") % port).str());
|
||||||
|
|
||||||
|
BOOST_LOG_TRIVIAL(info) << boost::format("%1%: Uploading file %2% at %3%, filename: %4%, path: %5%, print: %6%, group: %7%")
|
||||||
|
% name
|
||||||
|
% upload_data.source_path
|
||||||
|
% url
|
||||||
|
% upload_filename.string()
|
||||||
|
% upload_parent_path.string()
|
||||||
|
% (upload_data.post_action == PrintHostPostUploadAction::StartPrint ? "true" : "false")
|
||||||
|
% upload_data.group;
|
||||||
|
|
||||||
|
auto http = Http::post(std::move(url));
|
||||||
|
set_auth(http);
|
||||||
|
|
||||||
|
if (! upload_data.group.empty() && upload_data.group != _utf8(L("Default"))) {
|
||||||
|
http.form_add("group", upload_data.group);
|
||||||
|
}
|
||||||
|
|
||||||
|
if(upload_data.post_action == PrintHostPostUploadAction::StartPrint) {
|
||||||
|
http.form_add("name", upload_filename.string());
|
||||||
|
}
|
||||||
|
|
||||||
|
http.form_add("a", "upload")
|
||||||
|
.form_add_file("filename", upload_data.source_path.string(), upload_filename.string())
|
||||||
|
.on_complete([&](std::string body, unsigned status) {
|
||||||
|
BOOST_LOG_TRIVIAL(debug) << boost::format("%1%: File uploaded: HTTP %2%: %3%") % name % status % body;
|
||||||
|
})
|
||||||
|
.on_error([&](std::string body, std::string error, unsigned status) {
|
||||||
|
BOOST_LOG_TRIVIAL(error) << boost::format("%1%: Error uploading file: %2%, HTTP %3%, body: `%4%`") % name % 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) << "Repetier: Upload canceled";
|
||||||
|
res = false;
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.perform_sync();
|
||||||
|
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Repetier::validate_version_text(const boost::optional<std::string> &version_text) const
|
||||||
|
{
|
||||||
|
return version_text ? boost::starts_with(*version_text, "Repetier") : true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Repetier::set_auth(Http &http) const
|
||||||
|
{
|
||||||
|
http.header("X-Api-Key", apikey);
|
||||||
|
|
||||||
|
if (! cafile.empty()) {
|
||||||
|
http.ca_file(cafile);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string Repetier::make_url(const std::string &path) const
|
||||||
|
{
|
||||||
|
if (host.find("http://") == 0 || host.find("https://") == 0) {
|
||||||
|
if (host.back() == '/') {
|
||||||
|
return (boost::format("%1%%2%") % host % path).str();
|
||||||
|
} else {
|
||||||
|
return (boost::format("%1%/%2%") % host % path).str();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return (boost::format("http://%1%/%2%") % host % path).str();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Repetier::get_groups(wxArrayString& groups) const
|
||||||
|
{
|
||||||
|
bool res = true;
|
||||||
|
|
||||||
|
const char *name = get_name();
|
||||||
|
auto url = make_url((boost::format("printer/api/%1%") % port).str());
|
||||||
|
|
||||||
|
BOOST_LOG_TRIVIAL(info) << boost::format("%1%: Get groups at: %2%") % name % url;
|
||||||
|
|
||||||
|
auto http = Http::get(std::move(url));
|
||||||
|
set_auth(http);
|
||||||
|
http.form_add("a", "listModelGroups");
|
||||||
|
http.on_error([&](std::string body, std::string error, unsigned status) {
|
||||||
|
BOOST_LOG_TRIVIAL(error) << boost::format("%1%: Error getting version: %2%, HTTP %3%, body: `%4%`") % name % error % status % body;
|
||||||
|
})
|
||||||
|
.on_complete([&](std::string body, unsigned) {
|
||||||
|
BOOST_LOG_TRIVIAL(debug) << boost::format("%1%: Got groups: %2%") % name % body;
|
||||||
|
|
||||||
|
try {
|
||||||
|
std::stringstream ss(body);
|
||||||
|
pt::ptree ptree;
|
||||||
|
pt::read_json(ss, ptree);
|
||||||
|
|
||||||
|
BOOST_FOREACH(boost::property_tree::ptree::value_type &v, ptree.get_child("groupNames.")) {
|
||||||
|
if (v.second.data() == "#") {
|
||||||
|
groups.push_back(_utf8(L("Default")));
|
||||||
|
} else {
|
||||||
|
// Is it safe to assume that the data are utf-8 encoded?
|
||||||
|
groups.push_back(GUI::from_u8(v.second.data()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (const std::exception &) {
|
||||||
|
//msg = "Could not parse server response";
|
||||||
|
res = false;
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.perform_sync();
|
||||||
|
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Repetier::get_printers(wxArrayString& printers) const
|
||||||
|
{
|
||||||
|
const char *name = get_name();
|
||||||
|
|
||||||
|
bool res = true;
|
||||||
|
auto url = make_url("printer/list");
|
||||||
|
|
||||||
|
BOOST_LOG_TRIVIAL(info) << boost::format("%1%: List printers at: %2%") % name % url;
|
||||||
|
|
||||||
|
auto http = Http::get(std::move(url));
|
||||||
|
set_auth(http);
|
||||||
|
|
||||||
|
http.on_error([&](std::string body, std::string error, unsigned status) {
|
||||||
|
BOOST_LOG_TRIVIAL(error) << boost::format("%1%: Error listing printers: %2%, HTTP %3%, body: `%4%`") % name % error % status % body;
|
||||||
|
res = false;
|
||||||
|
})
|
||||||
|
.on_complete([&](std::string body, unsigned http_status) {
|
||||||
|
BOOST_LOG_TRIVIAL(debug) << boost::format("%1%: Got printers: %2%, HTTP status: %3%") % name % body % http_status;
|
||||||
|
|
||||||
|
if (http_status != 200)
|
||||||
|
throw HostNetworkError(GUI::format(_L("HTTP status: %1%\nMessage body: \"%2%\""), http_status, body));
|
||||||
|
|
||||||
|
std::stringstream ss(body);
|
||||||
|
pt::ptree ptree;
|
||||||
|
try {
|
||||||
|
pt::read_json(ss, ptree);
|
||||||
|
} catch (const pt::ptree_error &err) {
|
||||||
|
throw HostNetworkError(GUI::format(_L("Parsing of host response failed.\nMessage body: \"%1%\"\nError: \"%2%\""), body, err.what()));
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto error = ptree.get_optional<std::string>("error");
|
||||||
|
if (error)
|
||||||
|
throw HostNetworkError(*error);
|
||||||
|
|
||||||
|
try {
|
||||||
|
BOOST_FOREACH(boost::property_tree::ptree::value_type &v, ptree.get_child("data.")) {
|
||||||
|
const auto port = v.second.get<std::string>("slug");
|
||||||
|
printers.push_back(Slic3r::GUI::from_u8(port));
|
||||||
|
}
|
||||||
|
} catch (const std::exception &err) {
|
||||||
|
throw HostNetworkError(GUI::format(_L("Enumeration of host printers failed.\nMessage body: \"%1%\"\nError: \"%2%\""), body, err.what()));
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.perform_sync();
|
||||||
|
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
51
src/slic3r/Utils/Repetier.hpp
Normal file
51
src/slic3r/Utils/Repetier.hpp
Normal file
|
@ -0,0 +1,51 @@
|
||||||
|
#ifndef slic3r_Repetier_hpp_
|
||||||
|
#define slic3r_Repetier_hpp_
|
||||||
|
|
||||||
|
#include <string>
|
||||||
|
#include <wx/string.h>
|
||||||
|
#include <boost/optional.hpp>
|
||||||
|
|
||||||
|
#include "PrintHost.hpp"
|
||||||
|
|
||||||
|
namespace Slic3r {
|
||||||
|
|
||||||
|
class DynamicPrintConfig;
|
||||||
|
class Http;
|
||||||
|
|
||||||
|
class Repetier : public PrintHost
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
Repetier(DynamicPrintConfig *config);
|
||||||
|
~Repetier() 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; }
|
||||||
|
PrintHostPostUploadActions get_post_upload_actions() const override { return PrintHostPostUploadAction::StartPrint; }
|
||||||
|
bool supports_multiple_printers() const override { return true; }
|
||||||
|
std::string get_host() const override { return host; }
|
||||||
|
|
||||||
|
bool get_groups(wxArrayString &groups) const override;
|
||||||
|
bool get_printers(wxArrayString &printers) const override;
|
||||||
|
|
||||||
|
protected:
|
||||||
|
virtual bool validate_version_text(const boost::optional<std::string> &version_text) const;
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::string host;
|
||||||
|
std::string apikey;
|
||||||
|
std::string cafile;
|
||||||
|
std::string port;
|
||||||
|
|
||||||
|
void set_auth(Http &http) const;
|
||||||
|
std::string make_url(const std::string &path) const;
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
504
src/slic3r/Utils/Serial.cpp
Normal file
504
src/slic3r/Utils/Serial.cpp
Normal file
|
@ -0,0 +1,504 @@
|
||||||
|
#include "Serial.hpp"
|
||||||
|
|
||||||
|
#include "libslic3r/Exception.hpp"
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
#include <chrono>
|
||||||
|
#include <thread>
|
||||||
|
#include <fstream>
|
||||||
|
#include <exception>
|
||||||
|
#include <stdexcept>
|
||||||
|
|
||||||
|
#include <boost/algorithm/string/predicate.hpp>
|
||||||
|
#include <boost/filesystem.hpp>
|
||||||
|
#include <boost/format.hpp>
|
||||||
|
#include <boost/optional.hpp>
|
||||||
|
|
||||||
|
#if _WIN32
|
||||||
|
#include <Windows.h>
|
||||||
|
#include <Setupapi.h>
|
||||||
|
#include <initguid.h>
|
||||||
|
#include <devguid.h>
|
||||||
|
#include <regex>
|
||||||
|
// Undefine min/max macros incompatible with the standard library
|
||||||
|
// For example, std::numeric_limits<std::streamsize>::max()
|
||||||
|
// produces some weird errors
|
||||||
|
#ifdef min
|
||||||
|
#undef min
|
||||||
|
#endif
|
||||||
|
#ifdef max
|
||||||
|
#undef max
|
||||||
|
#endif
|
||||||
|
#include "boost/nowide/convert.hpp"
|
||||||
|
#pragma comment(lib, "user32.lib")
|
||||||
|
#elif __APPLE__
|
||||||
|
#include <CoreFoundation/CoreFoundation.h>
|
||||||
|
#include <CoreFoundation/CFString.h>
|
||||||
|
#include <IOKit/IOKitLib.h>
|
||||||
|
#include <IOKit/serial/IOSerialKeys.h>
|
||||||
|
#include <IOKit/serial/ioss.h>
|
||||||
|
#include <sys/syslimits.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifndef _WIN32
|
||||||
|
#include <sys/ioctl.h>
|
||||||
|
#include <sys/time.h>
|
||||||
|
#include <sys/unistd.h>
|
||||||
|
#include <sys/select.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if defined(__APPLE__) || defined(__OpenBSD__)
|
||||||
|
#include <termios.h>
|
||||||
|
#elif defined __linux__
|
||||||
|
#include <fcntl.h>
|
||||||
|
#include <asm-generic/ioctls.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
using boost::optional;
|
||||||
|
|
||||||
|
|
||||||
|
namespace Slic3r {
|
||||||
|
namespace Utils {
|
||||||
|
|
||||||
|
static bool looks_like_printer(const std::string &friendly_name)
|
||||||
|
{
|
||||||
|
return friendly_name.find("Original Prusa") != std::string::npos;
|
||||||
|
}
|
||||||
|
|
||||||
|
#if _WIN32
|
||||||
|
void parse_hardware_id(const std::string &hardware_id, SerialPortInfo &spi)
|
||||||
|
{
|
||||||
|
unsigned vid, pid;
|
||||||
|
std::regex pattern("USB\\\\.*VID_([[:xdigit:]]+)&PID_([[:xdigit:]]+).*");
|
||||||
|
std::smatch matches;
|
||||||
|
if (std::regex_match(hardware_id, matches, pattern)) {
|
||||||
|
vid = std::stoul(matches[1].str(), 0, 16);
|
||||||
|
pid = std::stoul(matches[2].str(), 0, 16);
|
||||||
|
spi.id_vendor = vid;
|
||||||
|
spi.id_product = pid;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifdef __linux__
|
||||||
|
optional<std::string> sysfs_tty_prop(const std::string &tty_dev, const std::string &name)
|
||||||
|
{
|
||||||
|
const auto prop_path = (boost::format("/sys/class/tty/%1%/device/../%2%") % tty_dev % name).str();
|
||||||
|
std::ifstream file(prop_path);
|
||||||
|
std::string res;
|
||||||
|
|
||||||
|
std::getline(file, res);
|
||||||
|
if (file.good()) { return res; }
|
||||||
|
else { return boost::none; }
|
||||||
|
}
|
||||||
|
|
||||||
|
optional<unsigned long> sysfs_tty_prop_hex(const std::string &tty_dev, const std::string &name)
|
||||||
|
{
|
||||||
|
auto prop = sysfs_tty_prop(tty_dev, name);
|
||||||
|
if (!prop) { return boost::none; }
|
||||||
|
|
||||||
|
try { return std::stoul(*prop, 0, 16); }
|
||||||
|
catch (const std::exception&) { return boost::none; }
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
std::vector<SerialPortInfo> scan_serial_ports_extended()
|
||||||
|
{
|
||||||
|
std::vector<SerialPortInfo> output;
|
||||||
|
|
||||||
|
#ifdef _WIN32
|
||||||
|
SP_DEVINFO_DATA devInfoData = { 0 };
|
||||||
|
devInfoData.cbSize = sizeof(devInfoData);
|
||||||
|
// Get the tree containing the info for the ports.
|
||||||
|
HDEVINFO hDeviceInfo = SetupDiGetClassDevs(&GUID_DEVCLASS_PORTS, 0, nullptr, DIGCF_PRESENT);
|
||||||
|
if (hDeviceInfo != INVALID_HANDLE_VALUE) {
|
||||||
|
// Iterate over all the devices in the tree.
|
||||||
|
for (int nDevice = 0; SetupDiEnumDeviceInfo(hDeviceInfo, nDevice, &devInfoData); ++ nDevice) {
|
||||||
|
SerialPortInfo port_info;
|
||||||
|
// Get the registry key which stores the ports settings.
|
||||||
|
HKEY hDeviceKey = SetupDiOpenDevRegKey(hDeviceInfo, &devInfoData, DICS_FLAG_GLOBAL, 0, DIREG_DEV, KEY_QUERY_VALUE);
|
||||||
|
if (hDeviceKey) {
|
||||||
|
// Read in the name of the port.
|
||||||
|
wchar_t pszPortName[4096];
|
||||||
|
DWORD dwSize = sizeof(pszPortName);
|
||||||
|
DWORD dwType = 0;
|
||||||
|
if (RegQueryValueEx(hDeviceKey, L"PortName", NULL, &dwType, (LPBYTE)pszPortName, &dwSize) == ERROR_SUCCESS)
|
||||||
|
port_info.port = boost::nowide::narrow(pszPortName);
|
||||||
|
RegCloseKey(hDeviceKey);
|
||||||
|
if (port_info.port.empty())
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Find the size required to hold the device info.
|
||||||
|
DWORD regDataType;
|
||||||
|
DWORD reqSize = 0;
|
||||||
|
SetupDiGetDeviceRegistryProperty(hDeviceInfo, &devInfoData, SPDRP_HARDWAREID, nullptr, nullptr, 0, &reqSize);
|
||||||
|
std::vector<wchar_t> hardware_id(reqSize > 1 ? reqSize : 1);
|
||||||
|
// Now store it in a buffer.
|
||||||
|
if (! SetupDiGetDeviceRegistryProperty(hDeviceInfo, &devInfoData, SPDRP_HARDWAREID, ®DataType, (BYTE*)hardware_id.data(), reqSize, nullptr))
|
||||||
|
continue;
|
||||||
|
parse_hardware_id(boost::nowide::narrow(hardware_id.data()), port_info);
|
||||||
|
|
||||||
|
// Find the size required to hold the friendly name.
|
||||||
|
reqSize = 0;
|
||||||
|
SetupDiGetDeviceRegistryProperty(hDeviceInfo, &devInfoData, SPDRP_FRIENDLYNAME, nullptr, nullptr, 0, &reqSize);
|
||||||
|
std::vector<wchar_t> friendly_name;
|
||||||
|
friendly_name.reserve(reqSize > 1 ? reqSize : 1);
|
||||||
|
// Now store it in a buffer.
|
||||||
|
if (! SetupDiGetDeviceRegistryProperty(hDeviceInfo, &devInfoData, SPDRP_FRIENDLYNAME, nullptr, (BYTE*)friendly_name.data(), reqSize, nullptr)) {
|
||||||
|
port_info.friendly_name = port_info.port;
|
||||||
|
} else {
|
||||||
|
port_info.friendly_name = boost::nowide::narrow(friendly_name.data());
|
||||||
|
port_info.is_printer = looks_like_printer(port_info.friendly_name);
|
||||||
|
}
|
||||||
|
output.emplace_back(std::move(port_info));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#elif __APPLE__
|
||||||
|
// inspired by https://sigrok.org/wiki/Libserialport
|
||||||
|
CFMutableDictionaryRef classes = IOServiceMatching(kIOSerialBSDServiceValue);
|
||||||
|
if (classes != 0) {
|
||||||
|
io_iterator_t iter;
|
||||||
|
if (IOServiceGetMatchingServices(kIOMasterPortDefault, classes, &iter) == KERN_SUCCESS) {
|
||||||
|
io_object_t port;
|
||||||
|
while ((port = IOIteratorNext(iter)) != 0) {
|
||||||
|
CFTypeRef cf_property = IORegistryEntryCreateCFProperty(port, CFSTR(kIOCalloutDeviceKey), kCFAllocatorDefault, 0);
|
||||||
|
if (cf_property) {
|
||||||
|
char path[PATH_MAX];
|
||||||
|
Boolean result = CFStringGetCString((CFStringRef)cf_property, path, sizeof(path), kCFStringEncodingUTF8);
|
||||||
|
CFRelease(cf_property);
|
||||||
|
if (result) {
|
||||||
|
SerialPortInfo port_info;
|
||||||
|
port_info.port = path;
|
||||||
|
|
||||||
|
// Attempt to read out the device friendly name
|
||||||
|
if ((cf_property = IORegistryEntrySearchCFProperty(port, kIOServicePlane,
|
||||||
|
CFSTR("USB Interface Name"), kCFAllocatorDefault,
|
||||||
|
kIORegistryIterateRecursively | kIORegistryIterateParents)) ||
|
||||||
|
(cf_property = IORegistryEntrySearchCFProperty(port, kIOServicePlane,
|
||||||
|
CFSTR("USB Product Name"), kCFAllocatorDefault,
|
||||||
|
kIORegistryIterateRecursively | kIORegistryIterateParents)) ||
|
||||||
|
(cf_property = IORegistryEntrySearchCFProperty(port, kIOServicePlane,
|
||||||
|
CFSTR("Product Name"), kCFAllocatorDefault,
|
||||||
|
kIORegistryIterateRecursively | kIORegistryIterateParents)) ||
|
||||||
|
(cf_property = IORegistryEntryCreateCFProperty(port,
|
||||||
|
CFSTR(kIOTTYDeviceKey), kCFAllocatorDefault, 0))) {
|
||||||
|
// Description limited to 127 char, anything longer would not be user friendly anyway.
|
||||||
|
char description[128];
|
||||||
|
if (CFStringGetCString((CFStringRef)cf_property, description, sizeof(description), kCFStringEncodingUTF8)) {
|
||||||
|
port_info.friendly_name = std::string(description) + " (" + port_info.port + ")";
|
||||||
|
port_info.is_printer = looks_like_printer(port_info.friendly_name);
|
||||||
|
}
|
||||||
|
CFRelease(cf_property);
|
||||||
|
}
|
||||||
|
if (port_info.friendly_name.empty())
|
||||||
|
port_info.friendly_name = port_info.port;
|
||||||
|
|
||||||
|
// Attempt to read out the VID & PID
|
||||||
|
int vid, pid;
|
||||||
|
auto cf_vendor = IORegistryEntrySearchCFProperty(port, kIOServicePlane, CFSTR("idVendor"),
|
||||||
|
kCFAllocatorDefault, kIORegistryIterateRecursively | kIORegistryIterateParents);
|
||||||
|
auto cf_product = IORegistryEntrySearchCFProperty(port, kIOServicePlane, CFSTR("idProduct"),
|
||||||
|
kCFAllocatorDefault, kIORegistryIterateRecursively | kIORegistryIterateParents);
|
||||||
|
if (cf_vendor && cf_product) {
|
||||||
|
if (CFNumberGetValue((CFNumberRef)cf_vendor, kCFNumberIntType, &vid) &&
|
||||||
|
CFNumberGetValue((CFNumberRef)cf_product, kCFNumberIntType, &pid)) {
|
||||||
|
port_info.id_vendor = vid;
|
||||||
|
port_info.id_product = pid;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (cf_vendor) { CFRelease(cf_vendor); }
|
||||||
|
if (cf_product) { CFRelease(cf_product); }
|
||||||
|
|
||||||
|
output.emplace_back(std::move(port_info));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
IOObjectRelease(port);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#else
|
||||||
|
// UNIX / Linux
|
||||||
|
std::initializer_list<const char*> prefixes { "ttyUSB" , "ttyACM", "tty.", "cu.", "rfcomm" };
|
||||||
|
for (auto &dir_entry : boost::filesystem::directory_iterator(boost::filesystem::path("/dev"))) {
|
||||||
|
std::string name = dir_entry.path().filename().string();
|
||||||
|
for (const char *prefix : prefixes) {
|
||||||
|
if (boost::starts_with(name, prefix)) {
|
||||||
|
const auto path = dir_entry.path().string();
|
||||||
|
SerialPortInfo spi;
|
||||||
|
spi.port = path;
|
||||||
|
#ifdef __linux__
|
||||||
|
auto friendly_name = sysfs_tty_prop(name, "product");
|
||||||
|
if (friendly_name) {
|
||||||
|
spi.is_printer = looks_like_printer(*friendly_name);
|
||||||
|
spi.friendly_name = (boost::format("%1% (%2%)") % *friendly_name % path).str();
|
||||||
|
} else {
|
||||||
|
spi.friendly_name = path;
|
||||||
|
}
|
||||||
|
auto vid = sysfs_tty_prop_hex(name, "idVendor");
|
||||||
|
auto pid = sysfs_tty_prop_hex(name, "idProduct");
|
||||||
|
if (vid && pid) {
|
||||||
|
spi.id_vendor = *vid;
|
||||||
|
spi.id_product = *pid;
|
||||||
|
}
|
||||||
|
#else
|
||||||
|
spi.friendly_name = path;
|
||||||
|
#endif
|
||||||
|
output.emplace_back(std::move(spi));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
output.erase(std::remove_if(output.begin(), output.end(),
|
||||||
|
[](const SerialPortInfo &info) {
|
||||||
|
return boost::starts_with(info.port, "Bluetooth") || boost::starts_with(info.port, "FireFly");
|
||||||
|
}),
|
||||||
|
output.end());
|
||||||
|
return output;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<std::string> scan_serial_ports()
|
||||||
|
{
|
||||||
|
std::vector<SerialPortInfo> ports = scan_serial_ports_extended();
|
||||||
|
std::vector<std::string> output;
|
||||||
|
output.reserve(ports.size());
|
||||||
|
for (const SerialPortInfo &spi : ports)
|
||||||
|
output.emplace_back(std::move(spi.port));
|
||||||
|
return output;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// Class Serial
|
||||||
|
|
||||||
|
namespace asio = boost::asio;
|
||||||
|
using boost::system::error_code;
|
||||||
|
|
||||||
|
Serial::Serial(asio::io_service& io_service) :
|
||||||
|
asio::serial_port(io_service)
|
||||||
|
{}
|
||||||
|
|
||||||
|
Serial::Serial(asio::io_service& io_service, const std::string &name, unsigned baud_rate) :
|
||||||
|
asio::serial_port(io_service, name)
|
||||||
|
{
|
||||||
|
set_baud_rate(baud_rate);
|
||||||
|
}
|
||||||
|
|
||||||
|
Serial::~Serial() {}
|
||||||
|
|
||||||
|
void Serial::set_baud_rate(unsigned baud_rate)
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
// This does not support speeds > 115200
|
||||||
|
set_option(boost::asio::serial_port_base::baud_rate(baud_rate));
|
||||||
|
} catch (boost::system::system_error &) {
|
||||||
|
auto handle = native_handle();
|
||||||
|
|
||||||
|
auto handle_errno = [](int retval) {
|
||||||
|
if (retval != 0) {
|
||||||
|
throw Slic3r::RuntimeError(
|
||||||
|
(boost::format("Could not set baud rate: %1%") % strerror(errno)).str()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
#if __APPLE__
|
||||||
|
termios ios;
|
||||||
|
handle_errno(::tcgetattr(handle, &ios));
|
||||||
|
handle_errno(::cfsetspeed(&ios, baud_rate));
|
||||||
|
speed_t newSpeed = baud_rate;
|
||||||
|
handle_errno(::ioctl(handle, IOSSIOSPEED, &newSpeed));
|
||||||
|
handle_errno(::tcsetattr(handle, TCSANOW, &ios));
|
||||||
|
#elif __linux__
|
||||||
|
|
||||||
|
/* The following definitions are kindly borrowed from:
|
||||||
|
/usr/include/asm-generic/termbits.h
|
||||||
|
Unfortunately we cannot just include that one because
|
||||||
|
it would redefine the "struct termios" already defined
|
||||||
|
the <termios.h> already included by Boost.ASIO. */
|
||||||
|
#define K_NCCS 19
|
||||||
|
struct termios2 {
|
||||||
|
tcflag_t c_iflag;
|
||||||
|
tcflag_t c_oflag;
|
||||||
|
tcflag_t c_cflag;
|
||||||
|
tcflag_t c_lflag;
|
||||||
|
cc_t c_line;
|
||||||
|
cc_t c_cc[K_NCCS];
|
||||||
|
speed_t c_ispeed;
|
||||||
|
speed_t c_ospeed;
|
||||||
|
};
|
||||||
|
#define BOTHER CBAUDEX
|
||||||
|
|
||||||
|
termios2 ios;
|
||||||
|
handle_errno(::ioctl(handle, TCGETS2, &ios));
|
||||||
|
ios.c_ispeed = ios.c_ospeed = baud_rate;
|
||||||
|
ios.c_cflag &= ~CBAUD;
|
||||||
|
ios.c_cflag |= BOTHER | CLOCAL | CREAD;
|
||||||
|
ios.c_cc[VMIN] = 1; // Minimum of characters to read, prevents eof errors when 0 bytes are read
|
||||||
|
ios.c_cc[VTIME] = 1;
|
||||||
|
handle_errno(::ioctl(handle, TCSETS2, &ios));
|
||||||
|
|
||||||
|
#elif __OpenBSD__
|
||||||
|
struct termios ios;
|
||||||
|
handle_errno(::tcgetattr(handle, &ios));
|
||||||
|
handle_errno(::cfsetspeed(&ios, baud_rate));
|
||||||
|
handle_errno(::tcsetattr(handle, TCSAFLUSH, &ios));
|
||||||
|
#else
|
||||||
|
throw Slic3r::RuntimeError("Custom baud rates are not currently supported on this OS");
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
void Serial::set_DTR(bool on)
|
||||||
|
{
|
||||||
|
auto handle = native_handle();
|
||||||
|
#if defined(_WIN32) && !defined(__SYMBIAN32__)
|
||||||
|
if (! EscapeCommFunction(handle, on ? SETDTR : CLRDTR)) {
|
||||||
|
throw Slic3r::RuntimeError("Could not set serial port DTR");
|
||||||
|
}
|
||||||
|
#else
|
||||||
|
int status;
|
||||||
|
if (::ioctl(handle, TIOCMGET, &status) == 0) {
|
||||||
|
on ? status |= TIOCM_DTR : status &= ~TIOCM_DTR;
|
||||||
|
if (::ioctl(handle, TIOCMSET, &status) == 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
throw Slic3r::RuntimeError(
|
||||||
|
(boost::format("Could not set serial port DTR: %1%") % strerror(errno)).str()
|
||||||
|
);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
void Serial::reset_line_num()
|
||||||
|
{
|
||||||
|
// See https://github.com/MarlinFirmware/Marlin/wiki/M110
|
||||||
|
write_string("M110 N0\n");
|
||||||
|
m_line_num = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Serial::read_line(unsigned timeout, std::string &line, error_code &ec)
|
||||||
|
{
|
||||||
|
auto& io_service =
|
||||||
|
#if BOOST_VERSION >= 107000
|
||||||
|
//FIXME this is most certainly wrong!
|
||||||
|
(boost::asio::io_context&)this->get_executor().context();
|
||||||
|
#else
|
||||||
|
this->get_io_service();
|
||||||
|
#endif
|
||||||
|
asio::deadline_timer timer(io_service);
|
||||||
|
char c = 0;
|
||||||
|
bool fail = false;
|
||||||
|
|
||||||
|
while (true) {
|
||||||
|
io_service.reset();
|
||||||
|
|
||||||
|
asio::async_read(*this, boost::asio::buffer(&c, 1), [&](const error_code &read_ec, size_t size) {
|
||||||
|
if (ec || size == 0) {
|
||||||
|
fail = true;
|
||||||
|
ec = read_ec; // FIXME: only if operation not aborted
|
||||||
|
}
|
||||||
|
timer.cancel(); // FIXME: ditto
|
||||||
|
});
|
||||||
|
|
||||||
|
if (timeout > 0) {
|
||||||
|
timer.expires_from_now(boost::posix_time::milliseconds(timeout));
|
||||||
|
timer.async_wait([&](const error_code &ec) {
|
||||||
|
// Ignore timer aborts
|
||||||
|
if (!ec) {
|
||||||
|
fail = true;
|
||||||
|
this->cancel();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
io_service.run();
|
||||||
|
|
||||||
|
if (fail) {
|
||||||
|
return false;
|
||||||
|
} else if (c != '\n') {
|
||||||
|
line += c;
|
||||||
|
} else {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Serial::printer_setup()
|
||||||
|
{
|
||||||
|
printer_reset();
|
||||||
|
write_string("\r\r\r\r\r\r\r\r\r\r"); // Gets rid of line noise, if any
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t Serial::write_string(const std::string &str)
|
||||||
|
{
|
||||||
|
// TODO: might be wise to timeout here as well
|
||||||
|
return asio::write(*this, asio::buffer(str));
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Serial::printer_ready_wait(unsigned retries, unsigned timeout)
|
||||||
|
{
|
||||||
|
std::string line;
|
||||||
|
error_code ec;
|
||||||
|
|
||||||
|
for (; retries > 0; retries--) {
|
||||||
|
reset_line_num();
|
||||||
|
|
||||||
|
while (read_line(timeout, line, ec)) {
|
||||||
|
if (line == "ok") {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
line.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
line.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t Serial::printer_write_line(const std::string &line, unsigned line_num)
|
||||||
|
{
|
||||||
|
const auto formatted_line = Utils::Serial::printer_format_line(line, line_num);
|
||||||
|
return write_string(formatted_line);
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t Serial::printer_write_line(const std::string &line)
|
||||||
|
{
|
||||||
|
m_line_num++;
|
||||||
|
return printer_write_line(line, m_line_num);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Serial::printer_reset()
|
||||||
|
{
|
||||||
|
this->set_DTR(false);
|
||||||
|
std::this_thread::sleep_for(std::chrono::milliseconds(200));
|
||||||
|
this->set_DTR(true);
|
||||||
|
std::this_thread::sleep_for(std::chrono::milliseconds(200));
|
||||||
|
this->set_DTR(false);
|
||||||
|
std::this_thread::sleep_for(std::chrono::milliseconds(1000));
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string Serial::printer_format_line(const std::string &line, unsigned line_num)
|
||||||
|
{
|
||||||
|
const auto line_num_str = std::to_string(line_num);
|
||||||
|
|
||||||
|
unsigned checksum = 'N';
|
||||||
|
for (auto c : line_num_str) { checksum ^= c; }
|
||||||
|
checksum ^= ' ';
|
||||||
|
for (auto c : line) { checksum ^= c; }
|
||||||
|
|
||||||
|
return (boost::format("N%1% %2%*%3%\n") % line_num_str % line % checksum).str();
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
} // namespace Utils
|
||||||
|
} // namespace Slic3r
|
97
src/slic3r/Utils/Serial.hpp
Normal file
97
src/slic3r/Utils/Serial.hpp
Normal file
|
@ -0,0 +1,97 @@
|
||||||
|
#ifndef slic3r_GUI_Utils_Serial_hpp_
|
||||||
|
#define slic3r_GUI_Utils_Serial_hpp_
|
||||||
|
|
||||||
|
#include <vector>
|
||||||
|
#include <string>
|
||||||
|
#include <boost/system/error_code.hpp>
|
||||||
|
#include <boost/asio.hpp>
|
||||||
|
|
||||||
|
|
||||||
|
namespace Slic3r {
|
||||||
|
namespace Utils {
|
||||||
|
|
||||||
|
struct SerialPortInfo {
|
||||||
|
std::string port;
|
||||||
|
unsigned id_vendor = -1;
|
||||||
|
unsigned id_product = -1;
|
||||||
|
std::string friendly_name;
|
||||||
|
bool is_printer = false;
|
||||||
|
|
||||||
|
SerialPortInfo() {}
|
||||||
|
SerialPortInfo(std::string port) : port(port), friendly_name(std::move(port)) {}
|
||||||
|
|
||||||
|
bool id_match(unsigned id_vendor, unsigned id_product) const { return id_vendor == this->id_vendor && id_product == this->id_product; }
|
||||||
|
};
|
||||||
|
|
||||||
|
inline bool operator==(const SerialPortInfo &sp1, const SerialPortInfo &sp2)
|
||||||
|
{
|
||||||
|
return
|
||||||
|
sp1.port == sp2.port &&
|
||||||
|
sp1.id_vendor == sp2.id_vendor &&
|
||||||
|
sp1.id_product == sp2.id_product &&
|
||||||
|
sp1.is_printer == sp2.is_printer;
|
||||||
|
}
|
||||||
|
|
||||||
|
extern std::vector<std::string> scan_serial_ports();
|
||||||
|
extern std::vector<SerialPortInfo> scan_serial_ports_extended();
|
||||||
|
|
||||||
|
|
||||||
|
class Serial : public boost::asio::serial_port
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
Serial(boost::asio::io_service &io_service);
|
||||||
|
Serial(boost::asio::io_service &io_service, const std::string &name, unsigned baud_rate);
|
||||||
|
Serial(const Serial &) = delete;
|
||||||
|
Serial &operator=(const Serial &) = delete;
|
||||||
|
~Serial();
|
||||||
|
|
||||||
|
void set_baud_rate(unsigned baud_rate);
|
||||||
|
|
||||||
|
// The Serial implementation is currently in disarray and therefore commented out.
|
||||||
|
// The boost implementation seems to have several problems, such as lack of support
|
||||||
|
// for custom baud rates, few weird implementation bugs and a history of API breakages.
|
||||||
|
// It's questionable whether it solves more problems than causes. Probably not.
|
||||||
|
// TODO: Custom implementation not based on asio.
|
||||||
|
//
|
||||||
|
// As of now, this class is only kept for the purpose of rebooting AVR109,
|
||||||
|
// see FirmwareDialog::priv::avr109_reboot()
|
||||||
|
|
||||||
|
/*
|
||||||
|
void set_DTR(bool on);
|
||||||
|
|
||||||
|
// Resets the line number both internally as well as with the firmware using M110
|
||||||
|
void reset_line_num();
|
||||||
|
|
||||||
|
// Reads a line or times out, the timeout is in milliseconds
|
||||||
|
bool read_line(unsigned timeout, std::string &line, boost::system::error_code &ec);
|
||||||
|
|
||||||
|
// Perform an initial setup for communicating with a printer
|
||||||
|
void printer_setup();
|
||||||
|
|
||||||
|
// Write data from a string
|
||||||
|
size_t write_string(const std::string &str);
|
||||||
|
|
||||||
|
// Attempts to reset the line numer and waits until the printer says "ok"
|
||||||
|
bool printer_ready_wait(unsigned retries, unsigned timeout);
|
||||||
|
|
||||||
|
// Write Marlin-formatted line, with a line number and a checksum
|
||||||
|
size_t printer_write_line(const std::string &line, unsigned line_num);
|
||||||
|
|
||||||
|
// Same as above, but with internally-managed line number
|
||||||
|
size_t printer_write_line(const std::string &line);
|
||||||
|
|
||||||
|
// Toggles DTR to reset the printer
|
||||||
|
void printer_reset();
|
||||||
|
|
||||||
|
// Formats a line Marlin-style, ie. with a sequential number and a checksum
|
||||||
|
static std::string printer_format_line(const std::string &line, unsigned line_num);
|
||||||
|
private:
|
||||||
|
unsigned m_line_num = 0;
|
||||||
|
*/
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
} // Utils
|
||||||
|
} // Slic3r
|
||||||
|
|
||||||
|
#endif /* slic3r_GUI_Utils_Serial_hpp_ */
|
Loading…
Add table
Add a link
Reference in a new issue