diff --git a/resources/plugins/elegoolink/web/lan_service_web/favicon.ico b/resources/plugins/elegoolink/web/lan_service_web/favicon.ico
new file mode 100644
index 0000000000..0673a251e9
Binary files /dev/null and b/resources/plugins/elegoolink/web/lan_service_web/favicon.ico differ
diff --git a/resources/plugins/elegoolink/web/lan_service_web/index.html b/resources/plugins/elegoolink/web/lan_service_web/index.html
new file mode 100644
index 0000000000..e552aa1e8e
--- /dev/null
+++ b/resources/plugins/elegoolink/web/lan_service_web/index.html
@@ -0,0 +1,265 @@
+
ELEGOO-Create The Future
\ No newline at end of file
diff --git a/src/slic3r/CMakeLists.txt b/src/slic3r/CMakeLists.txt
index 7f5b1e05ba..c22b9b64fd 100644
--- a/src/slic3r/CMakeLists.txt
+++ b/src/slic3r/CMakeLists.txt
@@ -390,6 +390,8 @@ set(SLIC3R_GUI_SOURCES
GUI/Printer/PrinterFileSystem.cpp
GUI/Printer/PrinterFileSystem.h
GUI/PrinterWebView.cpp
+ GUI/PrinterWebViewHandler.cpp
+ GUI/PrinterWebViewHandler.hpp
GUI/PrinterWebView.hpp
GUI/PrintHostDialogs.cpp
GUI/PrintHostDialogs.hpp
diff --git a/src/slic3r/GUI/MainFrame.cpp b/src/slic3r/GUI/MainFrame.cpp
index 7f598c3c62..f4aeae01f6 100644
--- a/src/slic3r/GUI/MainFrame.cpp
+++ b/src/slic3r/GUI/MainFrame.cpp
@@ -46,6 +46,7 @@
#include "Widgets/ProgressDialog.hpp"
#include "BindDialog.hpp"
#include "../Utils/MacDarkMode.hpp"
+#include "../Utils/PrintHost.hpp"
#include
#include
@@ -4162,15 +4163,12 @@ void MainFrame::load_printer_url()
return;
auto cfg = preset_bundle.printers.get_edited_preset().config;
- wxString url = cfg.opt_string("print_host_webui").empty() ? cfg.opt_string("print_host") : cfg.opt_string("print_host_webui");
+ wxString url = from_u8(PrintHost::get_print_host_webui(&cfg));
wxString apikey;
const auto host_type = cfg.option>("host_type")->value;
if (cfg.has("printhost_apikey") && (host_type == htPrusaLink || host_type == htPrusaConnect))
apikey = cfg.opt_string("printhost_apikey");
if (!url.empty()) {
- if (!url.Lower().starts_with("http"))
- url = wxString::Format("http://%s", url);
-
load_printer_url(url, apikey);
}
}
diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp
index be952e7200..6c2254d302 100644
--- a/src/slic3r/GUI/Plater.cpp
+++ b/src/slic3r/GUI/Plater.cpp
@@ -81,6 +81,7 @@
#include "GUI_Utils.hpp"
#include "GUI_Factories.hpp"
#include "wxExtensions.hpp"
+#include "../Utils/PrintHost.hpp"
#include "MainFrame.hpp"
#include "format.hpp"
#include "3DScene.hpp"
@@ -2483,13 +2484,11 @@ void Sidebar::update_all_preset_comboboxes()
p->m_bpButton_ams_filament->Hide();
auto print_btn_type = MainFrame::PrintSelectType::eExportGcode;
- wxString url = cfg.opt_string("print_host_webui").empty() ? cfg.opt_string("print_host") : cfg.opt_string("print_host_webui");
+ wxString url = from_u8(PrintHost::get_print_host_webui(&cfg));
wxString apikey;
if(url.empty())
url = wxString::Format("file://%s/web/orca/missing_connection.html", from_u8(resources_dir()));
else {
- if (!url.Lower().starts_with("http"))
- url = wxString::Format("http://%s", url);
const auto host_type = cfg.option>("host_type")->value;
if (cfg.has("printhost_apikey") && (host_type != htSimplyPrint))
apikey = cfg.opt_string("printhost_apikey");
diff --git a/src/slic3r/GUI/PrintHostDialogs.cpp b/src/slic3r/GUI/PrintHostDialogs.cpp
index 81da98366b..f59ff23e9d 100644
--- a/src/slic3r/GUI/PrintHostDialogs.cpp
+++ b/src/slic3r/GUI/PrintHostDialogs.cpp
@@ -643,7 +643,7 @@ void ElegooPrintHostSendDialog::init() {
auto preset_bundle = wxGetApp().preset_bundle;
auto model_id = preset_bundle->printers.get_edited_preset().get_printer_type(preset_bundle);
- if (!boost::starts_with(model_id, "Elegoo-C")) {
+ if (model_id != "Elegoo-CC" && model_id != "Elegoo-C") {
PrintHostSendDialog::init();
return;
}
diff --git a/src/slic3r/GUI/PrinterWebView.cpp b/src/slic3r/GUI/PrinterWebView.cpp
index 9fd1dc5c46..1dccfab66f 100644
--- a/src/slic3r/GUI/PrinterWebView.cpp
+++ b/src/slic3r/GUI/PrinterWebView.cpp
@@ -1,16 +1,17 @@
#include "PrinterWebView.hpp"
#include "I18N.hpp"
+#include "PrinterWebViewHandler.hpp"
#include "slic3r/GUI/PrinterWebView.hpp"
#include "slic3r/GUI/wxExtensions.hpp"
#include "slic3r/GUI/GUI_App.hpp"
#include "slic3r/GUI/MainFrame.hpp"
#include "libslic3r_version.h"
+#include
#include
#include
#include
-#include
#include
#include
@@ -19,13 +20,17 @@
#include
#endif
-namespace pt = boost::property_tree;
-
namespace Slic3r {
namespace GUI {
PrinterWebView::PrinterWebView(wxWindow *parent)
: wxPanel(parent, wxID_ANY, wxDefaultPosition, wxDefaultSize)
+ , m_browser(nullptr)
+ , m_zoomFactor(100)
+ , m_apikey()
+ , m_apikey_sent(false)
+ , m_url_deferred()
+ , m_handler(std::make_unique(*this))
{
wxBoxSizer* topsizer = new wxBoxSizer(wxVERTICAL);
@@ -47,6 +52,8 @@ PrinterWebView::PrinterWebView(wxWindow *parent)
m_browser->Bind(wxEVT_WEBVIEW_ERROR, &PrinterWebView::OnError, this);
m_browser->Bind(wxEVT_WEBVIEW_LOADED, &PrinterWebView::OnLoaded, this);
+ m_browser->Bind(wxEVT_WEBVIEW_NEWWINDOW, &PrinterWebView::OnNewWindow, this);
+ m_browser->Bind(wxEVT_WEBVIEW_SCRIPT_MESSAGE_RECEIVED, &PrinterWebView::OnScriptMessage, this);
SetSizer(topsizer);
@@ -64,9 +71,6 @@ PrinterWebView::PrinterWebView(wxWindow *parent)
}
*/
- //Zoom
- m_zoomFactor = 100;
-
//Connect the idle events
Bind(wxEVT_CLOSE_WINDOW, &PrinterWebView::OnClose, this);
@@ -76,11 +80,18 @@ PrinterWebView::~PrinterWebView()
{
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << " Start";
SetEvtHandlerEnabled(false);
+ m_handler.reset();
+
+ // Destroy the webview
+ if(m_browser){
+ m_browser->Destroy();
+ m_browser = nullptr;
+ }
+
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << " End";
}
-
void PrinterWebView::load_url(wxString& url, wxString apikey)
{
// this->Show();
@@ -89,6 +100,7 @@ void PrinterWebView::load_url(wxString& url, wxString apikey)
return;
m_apikey = apikey;
m_apikey_sent = false;
+ m_handler = create_printer_webview_handler(*this);
if (this->IsShown()) {
//ORCA: m_url_deferred will be cleared on load success
@@ -190,14 +202,34 @@ void PrinterWebView::OnError(wxWebViewEvent &evt)
BOOST_LOG_TRIVIAL(info) << __FUNCTION__<< boost::format(": error loading page %1% %2% %3% %4%") %evt.GetURL() %evt.GetTarget() %e %evt.GetString();
}
-void PrinterWebView::OnLoaded(wxWebViewEvent &evt)
+void PrinterWebView::OnLoaded(wxWebViewEvent& evt)
{
if (evt.GetURL().IsEmpty())
return;
//ORCA: url loaded successfully, safe to clear
m_url_deferred.clear();
SendAPIKey();
+
+ if (m_handler != nullptr) {
+ m_handler->on_loaded(evt);
+ return;
+ }
}
+void PrinterWebView::OnNewWindow(wxWebViewEvent& evt)
+{
+ const wxString url = evt.GetURL();
+ if (!url.empty())
+ wxLaunchDefaultBrowser(url);
+ evt.Veto();
+}
+
+void PrinterWebView::OnScriptMessage(wxWebViewEvent& evt)
+{
+ if (m_handler != nullptr)
+ m_handler->on_script_message(evt);
+}
+
+
} // GUI
} // Slic3r
diff --git a/src/slic3r/GUI/PrinterWebView.hpp b/src/slic3r/GUI/PrinterWebView.hpp
index 887091dde3..8a62ce70a2 100644
--- a/src/slic3r/GUI/PrinterWebView.hpp
+++ b/src/slic3r/GUI/PrinterWebView.hpp
@@ -25,11 +25,14 @@
#include
#include "wx/textctrl.h"
#include
+#include
namespace Slic3r {
namespace GUI {
+class PrinterWebViewHandler;
+
class PrinterWebView : public wxPanel {
public:
@@ -41,20 +44,24 @@ public:
void OnClose(wxCloseEvent& evt);
void OnError(wxWebViewEvent& evt);
void OnLoaded(wxWebViewEvent& evt);
+ void OnNewWindow(wxWebViewEvent& evt);
+ void OnScriptMessage(wxWebViewEvent& evt);
void reload();
void update_mode();
bool Show(bool show = true) override;
private:
+ friend class PrinterWebViewHandler;
+
void SendAPIKey();
wxWebView* m_browser;
long m_zoomFactor;
wxString m_apikey;
bool m_apikey_sent;
-
wxString m_url_deferred;
+ std::unique_ptr m_handler;
// DECLARE_EVENT_TABLE()
};
diff --git a/src/slic3r/GUI/PrinterWebViewHandler.cpp b/src/slic3r/GUI/PrinterWebViewHandler.cpp
new file mode 100644
index 0000000000..16d63795db
--- /dev/null
+++ b/src/slic3r/GUI/PrinterWebViewHandler.cpp
@@ -0,0 +1,339 @@
+#include "PrinterWebViewHandler.hpp"
+
+#include "I18N.hpp"
+#include "PrinterWebView.hpp"
+#include "slic3r/GUI/GUI_App.hpp"
+#include "slic3r/GUI/Widgets/WebView.hpp"
+#include "slic3r/Utils/PrintHost.hpp"
+#include "libslic3r/Preset.hpp"
+
+#include
+#include
+#include
+#include
+#include
+#include
+
+using json = nlohmann::json;
+
+namespace Slic3r {
+namespace GUI {
+
+PrinterWebViewHandler::PrinterWebViewHandler(PrinterWebView& owner)
+ : m_owner(owner)
+{
+}
+
+PrinterWebViewHandler::~PrinterWebViewHandler() = default;
+
+void PrinterWebViewHandler::on_loaded(wxWebViewEvent &evt)
+{
+}
+
+void PrinterWebViewHandler::on_script_message(wxWebViewEvent &evt)
+{
+}
+
+PrinterWebView& PrinterWebViewHandler::owner() const
+{
+ return m_owner;
+}
+
+wxWebView* PrinterWebViewHandler::browser() const
+{
+ return m_owner.m_browser;
+}
+
+namespace {
+
+DynamicPrintConfig* get_active_printer_config()
+{
+ if (wxGetApp().preset_bundle == nullptr)
+ return nullptr;
+
+ return &wxGetApp().preset_bundle->printers.get_edited_preset().config;
+}
+
+std::string json_string(const json& node, const char* key)
+{
+ auto it = node.find(key);
+ return (it != node.end() && it->is_string()) ? it->get() : std::string();
+}
+
+std::string dump_json(const json& node)
+{
+ return node.dump(-1, ' ', false, json::error_handler_t::replace);
+}
+
+boost::filesystem::path path_from_utf8(const std::string& utf8_path)
+{
+#ifdef _WIN32
+ const wxString wide_path = wxString::FromUTF8(utf8_path.c_str());
+ return boost::filesystem::path(wide_path.ToStdWstring());
+#else
+ return boost::filesystem::path(utf8_path);
+#endif
+}
+
+std::string filename_to_utf8(const boost::filesystem::path& path)
+{
+#ifdef _WIN32
+ const wxString wx_filename(path.filename().c_str());
+ const wxScopedCharBuffer utf8 = wx_filename.ToUTF8();
+ return utf8.data() != nullptr ? std::string(utf8.data()) : std::string();
+#else
+ return path.filename().string();
+#endif
+}
+
+class ElegooPrinterWebViewHandler final : public PrinterWebViewHandler {
+public:
+ explicit ElegooPrinterWebViewHandler(PrinterWebView& owner)
+ : PrinterWebViewHandler(owner)
+ {
+ }
+
+ ~ElegooPrinterWebViewHandler() override
+ {
+ stop_upload = true;
+ if (upload_thread.joinable())
+ upload_thread.join();
+ if (sn_thread.joinable())
+ sn_thread.join();
+ }
+
+ void on_script_message(wxWebViewEvent &evt) override
+ {
+ const wxString message = evt.GetString();
+ if (message.empty())
+ return;
+
+ json root = json::parse(message.ToUTF8().data(), nullptr, false);
+ if (root.is_discarded() || !root.is_object())
+ return;
+
+ std::string request_id = json_string(root, "id");
+ std::string method = json_string(root, "method");
+ json params = root.contains("params") && root["params"].is_object() ? root["params"] : json::object();
+
+ if (method.empty()) {
+ method = json_string(root, "command");
+ if (params.empty() && root.contains("data") && root["data"].is_object())
+ params = root["data"];
+ }
+
+ if (method == "open" || method == "common_openurl") {
+ const std::string url = json_string(params, "url").empty() ? json_string(root, "url") : json_string(params, "url");
+ if (!url.empty())
+ wxLaunchDefaultBrowser(url);
+ if (!request_id.empty())
+ send_ipc_message("response", request_id, method, 0, "success");
+ return;
+ }
+
+ if (method == "upload_file") {
+ handle_upload_request(request_id, method, dump_json(params));
+ return;
+ }
+
+ if (method == "open_file_dialog") {
+ handle_open_file_dialog_request(request_id, method, dump_json(params));
+ return;
+ }
+
+ if (method == "get_sn") {
+ handle_get_sn_request(request_id, method);
+ return;
+ }
+ }
+
+private:
+ void send_ipc_message(const char* type, const std::string& request_id, const std::string& method, int code,
+ const std::string& message, const std::string& data_json = "{}")
+ {
+ if (browser() == nullptr)
+ return;
+
+ json body = json::object();
+ body["type"] = type;
+ if (!request_id.empty())
+ body["id"] = request_id;
+ if (!method.empty())
+ body["method"] = method;
+
+ json data = json::parse(data_json, nullptr, false);
+ if (data.is_discarded())
+ data = json::object();
+ body["data"] = std::move(data);
+
+ if (std::string(type) == "response") {
+ body["code"] = code;
+ body["message"] = message;
+ }
+
+ const wxString payload = wxString::FromUTF8(dump_json(body));
+ const wxString script = "if (typeof HandleStudio === 'function') { HandleStudio(" + payload + "); } else { window.postMessage(" + payload + ", '*'); }";
+ wxGetApp().CallAfter([this, script]() {
+ if (browser() != nullptr)
+ WebView::RunScript(browser(), script);
+ });
+ }
+
+ void handle_upload_request(const std::string& request_id, const std::string& method, const std::string& params_json)
+ {
+ if (upload_in_progress.exchange(true)) {
+ send_ipc_message("response", request_id, method, 1, "Upload already in progress");
+ return;
+ }
+
+ if (upload_thread.joinable())
+ upload_thread.join();
+
+ json params = json::parse(params_json, nullptr, false);
+ if (params.is_discarded())
+ params = json::object();
+
+ std::string file_path = json_string(params, "filePath");
+ std::string file_name = json_string(params, "fileName");
+
+ if (file_path.empty()) {
+ upload_in_progress = false;
+ send_ipc_message("response", request_id, method, 1, "Missing filePath");
+ return;
+ }
+
+ // HTML IPC passes UTF-8 strings; decode explicitly to avoid Windows codepage issues.
+ boost::filesystem::path source_path = path_from_utf8(file_path);
+ if (file_name.empty())
+ file_name = filename_to_utf8(source_path);
+
+ DynamicPrintConfig* config = get_active_printer_config();
+ std::unique_ptr print_host(config == nullptr ? nullptr : PrintHost::get_print_host(config));
+ if (print_host == nullptr) {
+ upload_in_progress = false;
+ send_ipc_message("response", request_id, method, 1, "Could not get a valid Printer Host reference");
+ return;
+ }
+
+ stop_upload = false;
+ upload_thread = std::thread([this, request_id, method, file_path, file_name, source_path, print_host = std::move(print_host)]() mutable {
+ std::string error_message;
+
+ PrintHostUpload upload_data;
+ upload_data.use_3mf = false;
+ upload_data.post_action = PrintHostPostUploadAction::None;
+ upload_data.source_path = source_path;
+ upload_data.upload_path = path_from_utf8(file_name);
+
+ const bool success = print_host->upload(
+ std::move(upload_data),
+ [this, request_id](Http::Progress progress, bool& cancel) {
+ cancel = stop_upload.load();
+ json data = {
+ {"uploadedBytes", static_cast(progress.ulnow)},
+ {"totalBytes", static_cast(progress.ultotal)}
+ };
+ send_ipc_message("event", request_id, "upload_progress", 0, "", dump_json(data));
+ },
+ [&error_message](wxString error) {
+ error_message = error.ToUTF8().data();
+ },
+ [this, request_id](wxString tag, wxString status) {
+ json data = {
+ {"tag", tag.ToUTF8().data()},
+ {"status", status.ToUTF8().data()}
+ };
+ send_ipc_message("event", request_id, "upload_info", 0, "", dump_json(data));
+ });
+
+ upload_in_progress = false;
+
+ if (success) {
+ json data = {
+ {"success", true},
+ {"filePath", file_path},
+ {"fileName", file_name}
+ };
+ send_ipc_message("response", request_id, method, 0, "success", dump_json(data));
+ } else {
+ if (error_message.empty())
+ error_message = "Upload failed";
+ send_ipc_message("response", request_id, method, 1, error_message);
+ }
+ });
+ }
+
+ void handle_open_file_dialog_request(const std::string& request_id, const std::string& method, const std::string& params_json)
+ {
+ json params = json::parse(params_json, nullptr, false);
+ if (params.is_discarded())
+ params = json::object();
+
+ const std::string filter = json_string(params, "filter").empty() ? "All files (*.*)|*.*" : json_string(params, "filter");
+
+ wxWindow* parent = owner().GetParent();
+ if (parent == nullptr)
+ parent = wxGetApp().GetTopWindow();
+
+ wxFileDialog open_file_dialog(parent, _L("Open File"), "", "", wxString::FromUTF8(filter), wxFD_OPEN | wxFD_FILE_MUST_EXIST);
+
+ json data = json::object();
+ data["files"] = json::array();
+ if (open_file_dialog.ShowModal() != wxID_CANCEL)
+ data["files"].push_back(open_file_dialog.GetPath().ToUTF8().data());
+
+ send_ipc_message("response", request_id, method, 0, "success", dump_json(data));
+ }
+
+ void handle_get_sn_request(const std::string& request_id, const std::string& method)
+ {
+ if (sn_request_in_progress.exchange(true)) {
+ send_ipc_message("response", request_id, method, 1, "SN request already in progress");
+ return;
+ }
+
+ if (sn_thread.joinable())
+ sn_thread.join();
+
+ sn_thread = std::thread([this, request_id, method]() {
+ std::string sn;
+
+ DynamicPrintConfig* config = get_active_printer_config();
+ std::unique_ptr print_host(config == nullptr ? nullptr : PrintHost::get_print_host(config));
+ if (print_host != nullptr)
+ sn = print_host->get_sn();
+
+ sn_request_in_progress = false;
+ json data = {
+ {"sn", sn}
+ };
+ send_ipc_message("response", request_id, method, 0, "success", dump_json(data));
+ });
+ }
+
+ std::atomic upload_in_progress { false };
+ std::atomic sn_request_in_progress { false };
+ std::atomic stop_upload { false };
+ std::thread upload_thread;
+ std::thread sn_thread;
+};
+
+} // namespace
+
+std::unique_ptr create_printer_webview_handler(PrinterWebView& owner)
+{
+ auto cfg = get_active_printer_config();
+ if(cfg == nullptr) return nullptr;
+
+ const auto host_type = cfg->option>("host_type")->value;
+ switch (host_type)
+ {
+ case PrintHostType::htElegooLink:
+ return std::make_unique(owner);
+ default:
+ return nullptr;
+ }
+}
+
+} // GUI
+} // Slic3r
\ No newline at end of file
diff --git a/src/slic3r/GUI/PrinterWebViewHandler.hpp b/src/slic3r/GUI/PrinterWebViewHandler.hpp
new file mode 100644
index 0000000000..02da72c75f
--- /dev/null
+++ b/src/slic3r/GUI/PrinterWebViewHandler.hpp
@@ -0,0 +1,36 @@
+#ifndef slic3r_PrinterWebViewHandler_hpp_
+#define slic3r_PrinterWebViewHandler_hpp_
+
+#include
+#include
+#include
+
+class wxWebView;
+
+namespace Slic3r {
+namespace GUI {
+
+class PrinterWebView;
+
+class PrinterWebViewHandler {
+public:
+ explicit PrinterWebViewHandler(PrinterWebView& owner);
+ virtual ~PrinterWebViewHandler();
+
+ virtual void on_loaded(wxWebViewEvent &evt);
+ virtual void on_script_message(wxWebViewEvent &evt);
+
+protected:
+ PrinterWebView& owner() const;
+ wxWebView* browser() const;
+
+private:
+ PrinterWebView& m_owner;
+};
+
+std::unique_ptr create_printer_webview_handler(PrinterWebView& owner);
+
+} // GUI
+} // Slic3r
+
+#endif /* slic3r_PrinterWebViewHandler_hpp_ */
\ No newline at end of file
diff --git a/src/slic3r/Utils/ElegooLink.cpp b/src/slic3r/Utils/ElegooLink.cpp
index 7347641e74..90bbe65226 100644
--- a/src/slic3r/Utils/ElegooLink.cpp
+++ b/src/slic3r/Utils/ElegooLink.cpp
@@ -11,6 +11,7 @@
#include
#include
#include
+#include
#include
#include
#include
@@ -58,6 +59,69 @@ namespace Slic3r {
namespace {
+ constexpr const char* ELEGOO_CC2_DEFAULT_TOKEN = "123456";
+
+ enum class ElegooPrinterType {
+ Other,
+ CC,
+ CC2,
+ };
+
+ ElegooPrinterType classify_printer_model(const std::string& printer_model)
+ {
+ if (!boost::algorithm::starts_with(printer_model, "Elegoo Centauri"))
+ return ElegooPrinterType::Other;
+
+ const auto last_char = printer_model.find_last_not_of(" \t\r\n");
+ if (last_char != std::string::npos && printer_model[last_char] == '2')
+ return ElegooPrinterType::CC2;
+
+ return ElegooPrinterType::CC;
+ }
+
+ std::string get_cc2_token(const std::string& apikey)
+ {
+ return apikey.empty() ? ELEGOO_CC2_DEFAULT_TOKEN : apikey;
+ }
+
+ bool parse_cc2_response(const std::string& body, std::string& error_message, std::string* serial_number = nullptr)
+ {
+ try {
+ pt::ptree root;
+ std::istringstream is(body);
+ pt::read_json(is, root);
+
+ const int error_code = root.get("error_code", -1);
+ if (error_code != 0) {
+ error_message = root.get("message", "Printer returned an error");
+ if (error_message.empty())
+ error_message = "Printer returned an error";
+ error_message += " (" + std::to_string(error_code) + ")";
+ return false;
+ }
+
+ if (serial_number != nullptr) {
+ const auto system_info = root.get_child_optional("system_info");
+ if (!system_info) {
+ error_message = "Missing system_info in response";
+ return false;
+ }
+
+ const auto sn = system_info->get_optional("sn");
+ if (!sn || sn->empty()) {
+ error_message = "Missing printer serial number in response";
+ return false;
+ }
+ *serial_number = *sn;
+ }
+
+ return true;
+ } catch (const std::exception&) {
+ error_message = "Error parsing response";
+ return false;
+ }
+ }
+
std::string get_host_from_url(const std::string& url_in)
{
std::string url = url_in;
@@ -220,15 +284,126 @@ namespace Slic3r {
return ret_val;
}
+ std::string path_to_utf8(const boost::filesystem::path& path)
+ {
+ #ifdef WIN32
+ return boost::nowide::narrow(path.wstring());
+ #else
+ return path.string();
+ #endif
+ }
+
+ std::string filename_to_utf8(const boost::filesystem::path& path)
+ {
+ #ifdef WIN32
+ return boost::nowide::narrow(path.filename().wstring());
+ #else
+ return path.filename().string();
+ #endif
+ }
+
} //namespace
ElegooLink::ElegooLink(DynamicPrintConfig *config):
- OctoPrint(config) {
+ OctoPrint(config), m_printerModel(config->opt_string("printer_model")) {
}
+ std::string ElegooLink::get_print_host_webui(DynamicPrintConfig* config)
+ {
+ if (config == nullptr)
+ return {};
+
+ std::string fallback_webui = config->opt_string("print_host_webui");
+ if (fallback_webui.empty())
+ fallback_webui = config->opt_string("print_host");
+ if (!fallback_webui.empty()) {
+ const bool has_http_scheme = boost::algorithm::istarts_with(fallback_webui, "http");
+ const bool has_file_scheme = boost::algorithm::istarts_with(fallback_webui, "file:");
+
+ if (!has_http_scheme && !has_file_scheme)
+ fallback_webui = "http://" + fallback_webui;
+ }
+
+ const std::string host = config->opt_string("print_host");
+ if (host.empty())
+ return fallback_webui;
+
+ if (classify_printer_model(config->opt_string("printer_model")) != ElegooPrinterType::CC2)
+ return fallback_webui;
+
+ std::string web_path = resources_dir() + "/plugins/elegoolink/web/lan_service_web/index.html";
+ std::replace(web_path.begin(), web_path.end(), '\\', '/');
+ web_path = "file://" + web_path;
+ web_path += "?access_code=" + get_cc2_token(config->opt_string("printhost_apikey"));
+ web_path += "&ip=" + get_host_from_url(host) + "&id=elegoo_123456";
+
+ const std::string lang = GUI::wxGetApp().current_language_code_safe().utf8_string();
+ if (!lang.empty())
+ web_path += "&lang=" + lang;
+
+ if (GUI::get_app_config()->get_bool("developer_mode"))
+ web_path += "&dev=true";
+
+ return web_path;
+ }
+
+ std::string ElegooLink::cc2_token() const
+ {
+ return get_cc2_token(m_apikey);
+ }
+
+ std::string ElegooLink::make_cc2_info_url() const
+ {
+ return make_url("system/info?X-Token=" + escape_string(cc2_token()));
+ }
+
+ std::string ElegooLink::make_cc2_upload_url() const
+ {
+ return make_url("upload");
+ }
+
const char* ElegooLink::get_name() const { return "ElegooLink"; }
+ PrintHostPostUploadActions ElegooLink::get_post_upload_actions() const
+ {
+ if (classify_printer_model(m_printerModel) == ElegooPrinterType::CC2) {
+ return PrintHostPostUploadAction::None;
+ } else {
+ return PrintHostPostUploadAction::StartPrint;
+ }
+ }
+
+ std::string ElegooLink::get_sn() const
+ {
+ if (classify_printer_model(m_printerModel) != ElegooPrinterType::CC2)
+ return "";
+
+ const char* name = get_name();
+ std::string sn;
+ const auto token = cc2_token();
+ auto http = Http::get(make_cc2_info_url());
+ http.timeout_connect(10)
+ .timeout_max(15);
+ http.header("X-Token", token);
+ http.header("Accept", "application/json");
+ http.on_error([&](std::string body, std::string error, unsigned status) {
+ BOOST_LOG_TRIVIAL(error) << boost::format("%1%: Error getting CC2 device info for SN: %2%, HTTP %3%, body: `%4%`") % name % error % status % body;
+ })
+ .on_complete([&](std::string body, unsigned status) {
+ std::string error_message;
+ if (!parse_cc2_response(body, error_message, &sn)) {
+ BOOST_LOG_TRIVIAL(warning) << boost::format("%1%: Failed to parse CC2 SN response, HTTP %2%, reason: %3%") % name % status % error_message;
+ sn.clear();
+ }
+ })
+#ifdef WIN32
+ .ssl_revoke_best_effort(m_ssl_revoke_best_effort)
+#endif // WIN32
+ .perform_sync();
+
+ return sn;
+ }
bool ElegooLink::elegoo_test(wxString& msg) const{
@@ -268,11 +443,54 @@ namespace Slic3r {
return res;
}
bool ElegooLink::test(wxString &curl_msg) const{
- if(OctoPrint::test(curl_msg)){
- return true;
+ switch (classify_printer_model(m_printerModel)) {
+ case ElegooPrinterType::Other:
+ return OctoPrint::test(curl_msg);
+ case ElegooPrinterType::CC2:
+ return elegoo_cc2_test(curl_msg);
+ case ElegooPrinterType::CC:
+ return elegoo_test(curl_msg);
}
- curl_msg="";
- return elegoo_test(curl_msg);
+ return false;
+ }
+
+ bool ElegooLink::elegoo_cc2_test(wxString& msg) const
+ {
+ const char* name = get_name();
+ bool res = true;
+ const auto token = cc2_token();
+ auto url = make_cc2_info_url();
+ auto http = Http::get(std::move(url));
+
+ http.header("X-Token", token);
+ http.header("Accept", "application/json");
+ http.on_error([&](std::string body, std::string error, unsigned status) {
+ BOOST_LOG_TRIVIAL(error) << boost::format("%1%: Error getting CC2 device info: %2%, HTTP %3%, body: `%4%`") % name % error % status % body;
+ res = false;
+ if (status == 401 || status == 403)
+ msg = format_error(body, "Invalid access code", status);
+ else
+ msg = format_error(body, error.empty() ? "CC2 device not detected" : error, status);
+ })
+ .on_complete([&](std::string body, unsigned status) {
+ BOOST_LOG_TRIVIAL(debug) << boost::format("%1%: Got CC2 device info: %2%") % name % body;
+ std::string error_message;
+ std::string serial_number;
+ if (!parse_cc2_response(body, error_message, &serial_number)) {
+ res = false;
+ msg = format_error(body, error_message.empty() ? "CC2 device not detected" : error_message, status);
+ return;
+ }
+ res = true;
+ })
+#ifdef WIN32
+ .ssl_revoke_best_effort(m_ssl_revoke_best_effort)
+ .on_ip_resolve([&](std::string address) {
+ msg = GUI::from_u8(address);
+ })
+#endif // WIN32
+ .perform_sync();
+ return res;
}
#ifdef WIN32
@@ -320,12 +538,53 @@ namespace Slic3r {
}
bool ElegooLink::test_with_resolved_ip(wxString& msg) const
{
- // Elegoo supports both otcoprint and Elegoo link
- if (OctoPrint::test_with_resolved_ip(msg)) {
- return true;
+ switch (classify_printer_model(m_printerModel)) {
+ case ElegooPrinterType::Other:
+ return OctoPrint::test_with_resolved_ip(msg);
+ case ElegooPrinterType::CC2:
+ return elegoo_cc2_test_with_resolved_ip(msg);
+ case ElegooPrinterType::CC:
+ return elegoo_test_with_resolved_ip(msg);
}
- msg = "";
- return elegoo_test_with_resolved_ip(msg);
+ return false;
+ }
+
+ bool ElegooLink::elegoo_cc2_test_with_resolved_ip(wxString& msg) const
+ {
+ const char* name = get_name();
+ bool res = true;
+ const auto token = cc2_token();
+ auto url = substitute_host(make_cc2_info_url(), GUI::into_u8(msg));
+ std::string host_header = get_host_from_url(m_host);
+ auto http = Http::get(url);
+ msg.Clear();
+
+ http.header("Host", host_header);
+ http.header("X-Token", token);
+ http.header("Accept", "application/json");
+ http.on_error([&](std::string body, std::string error, unsigned status) {
+ BOOST_LOG_TRIVIAL(error) << boost::format("%1%: Error getting CC2 device info at %2% : %3%, HTTP %4%, body: `%5%`") % name % url %
+ error % status % body;
+ res = false;
+ if (status == 401 || status == 403)
+ msg = format_error(body, "Invalid access code", status);
+ else
+ msg = format_error(body, error.empty() ? "CC2 device not detected" : error, status);
+ })
+ .on_complete([&](std::string body, unsigned status) {
+ std::string error_message;
+ std::string serial_number;
+ if (!parse_cc2_response(body, error_message, &serial_number)) {
+ res = false;
+ msg = format_error(body, error_message.empty() ? "CC2 device not detected" : error_message, status);
+ return;
+ }
+ res = true;
+ })
+ .ssl_revoke_best_effort(m_ssl_revoke_best_effort)
+ .perform_sync();
+
+ return res;
}
#endif // WIN32
@@ -343,25 +602,31 @@ namespace Slic3r {
#ifdef WIN32
bool ElegooLink::upload_inner_with_resolved_ip(PrintHostUpload upload_data, ProgressFn prorgess_fn, ErrorFn error_fn, InfoFn info_fn, const boost::asio::ip::address& resolved_addr) const
{
- wxString test_msg_or_host_ip = "";
+ const auto printer_type = classify_printer_model(m_printerModel);
+
+ if (printer_type == ElegooPrinterType::Other)
+ return OctoPrint::upload_inner_with_resolved_ip(std::move(upload_data), prorgess_fn, error_fn, info_fn, resolved_addr);
info_fn(L"resolve", boost::nowide::widen(resolved_addr.to_string()));
- // If test fails, test_msg_or_host_ip contains the error message.
- // Otherwise on Windows it contains the resolved IP address of the host.
- // Test_msg already contains resolved ip and will be cleared on start of test().
- test_msg_or_host_ip = GUI::from_u8(resolved_addr.to_string());
- //Elegoo supports both otcoprint and Elegoo link
- if(OctoPrint::test_with_resolved_ip(test_msg_or_host_ip)){
- return OctoPrint::upload_inner_with_host(upload_data, prorgess_fn, error_fn, info_fn);
+
+ if (printer_type == ElegooPrinterType::CC2) {
+ wxString cc2_msg = GUI::from_u8(resolved_addr.to_string());
+ if (!elegoo_cc2_test_with_resolved_ip(cc2_msg)) {
+ error_fn(std::move(cc2_msg));
+ return false;
+ }
+
+ std::string url = substitute_host(make_cc2_upload_url(), resolved_addr.to_string());
+ info_fn(L"resolve", boost::nowide::widen(url));
+ return loopUploadCC2(url, get_host_from_url(m_host), std::move(upload_data), prorgess_fn, error_fn, info_fn);
}
- test_msg_or_host_ip = GUI::from_u8(resolved_addr.to_string());
- if(!elegoo_test_with_resolved_ip(test_msg_or_host_ip)){
- error_fn(std::move(test_msg_or_host_ip));
+ wxString legacy_msg = GUI::from_u8(resolved_addr.to_string());
+ if (!elegoo_test_with_resolved_ip(legacy_msg)) {
+ error_fn(std::move(legacy_msg));
return false;
}
-
std::string url = substitute_host(make_url("uploadFile/upload"), resolved_addr.to_string());
info_fn(L"resolve", boost::nowide::widen(url));
@@ -372,23 +637,46 @@ namespace Slic3r {
bool ElegooLink::upload_inner_with_host(PrintHostUpload upload_data, ProgressFn prorgess_fn, ErrorFn error_fn, InfoFn info_fn) const
{
- // 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;
- //Elegoo supports both otcoprint and Elegoo link
- if(OctoPrint::test(test_msg_or_host_ip)){
- return OctoPrint::upload_inner_with_host(upload_data, prorgess_fn, error_fn, info_fn);
+ const auto printer_type = classify_printer_model(m_printerModel);
+
+ if (printer_type == ElegooPrinterType::Other)
+ return OctoPrint::upload_inner_with_host(std::move(upload_data), prorgess_fn, error_fn, info_fn);
+
+ if (printer_type == ElegooPrinterType::CC2) {
+ wxString cc2_msg;
+ if (!elegoo_cc2_test(cc2_msg)) {
+ error_fn(std::move(cc2_msg));
+ return false;
+ }
+
+ std::string url;
+#ifdef WIN32
+ if (m_host.find("https://") == 0 || cc2_msg.empty() || !GUI::get_app_config()->get_bool("allow_ip_resolve"))
+#endif // _WIN32
+ {
+ url = make_cc2_upload_url();
+ }
+#ifdef WIN32
+ else {
+ info_fn(L"resolve", cc2_msg);
+ url = substitute_host(make_cc2_upload_url(), GUI::into_u8(cc2_msg));
+ BOOST_LOG_TRIVIAL(info) << "CC2 upload address after ip resolve: " << url;
+ }
+#endif // _WIN32
+
+ return loopUploadCC2(url, get_host_from_url(m_host), std::move(upload_data), prorgess_fn, error_fn, info_fn);
}
- test_msg_or_host_ip="";
- if(!elegoo_test(test_msg_or_host_ip)){
- error_fn(std::move(test_msg_or_host_ip));
+
+ wxString legacy_msg;
+ if(!elegoo_test(legacy_msg)){
+ error_fn(std::move(legacy_msg));
return false;
}
std::string url;
#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() || !GUI::get_app_config()->get_bool("allow_ip_resolve"))
+ if (m_host.find("https://") == 0 || legacy_msg.empty() || !GUI::get_app_config()->get_bool("allow_ip_resolve"))
#endif // _WIN32
{
// If https is entered we assume signed ceritificate is being used
@@ -403,8 +691,8 @@ namespace Slic3r {
// 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
- info_fn(L"resolve", test_msg_or_host_ip);
- url = substitute_host(make_url("uploadFile/upload"), GUI::into_u8(test_msg_or_host_ip));
+ info_fn(L"resolve", legacy_msg);
+ url = substitute_host(make_url("uploadFile/upload"), GUI::into_u8(legacy_msg));
BOOST_LOG_TRIVIAL(info) << "Upload address after ip resolve: " << url;
}
#endif // _WIN32
@@ -490,11 +778,11 @@ namespace Slic3r {
bool ElegooLink::loopUpload(std::string url, PrintHostUpload upload_data, ProgressFn progress_fn, ErrorFn error_fn, InfoFn info_fn) const
{
const char* name = get_name();
- const auto upload_filename = upload_data.upload_path.filename().string();
- std::string source_path = upload_data.source_path.string();
+ const auto upload_filename = filename_to_utf8(upload_data.upload_path);
+ std::string source_path = path_to_utf8(upload_data.source_path);
// calc file size
- std::ifstream file(source_path, std::ios::binary | std::ios::ate);
+ boost::nowide::ifstream file(source_path, std::ios::binary | std::ios::ate);
std::streamsize size = file.tellg();
file.close();
const std::string fileSize = std::to_string(size);
@@ -583,6 +871,146 @@ namespace Slic3r {
return res;
}
+ bool ElegooLink::uploadPartCC2(Http& http,
+ const std::string& host_header,
+ const std::string& token,
+ const std::string& md5,
+ const boost::filesystem::path& path,
+ const std::string& filename,
+ size_t filesize,
+ size_t offset,
+ size_t length,
+ ProgressFn prorgess_fn,
+ ErrorFn error_fn) const
+ {
+ const char* name = get_name();
+
+ boost::nowide::ifstream file(path, std::ios::binary);
+ if (!file.is_open()) {
+ error_fn(_L("Failed to open file for upload."));
+ return false;
+ }
+
+ file.seekg(static_cast(offset), std::ios::beg);
+ std::string chunk(length, '\0');
+ file.read(chunk.data(), static_cast(length));
+ if (!file && static_cast(file.gcount()) != length) {
+ error_fn(_L("Failed to read file chunk for upload."));
+ return false;
+ }
+ chunk.resize(static_cast(file.gcount()));
+
+ const size_t end_offset = offset + chunk.size() - 1;
+ const auto range = std::string("bytes ") + std::to_string(offset) + "-" + std::to_string(end_offset) + "/" + std::to_string(filesize);
+
+ bool result = false;
+ http.headers_reset();
+ if (!host_header.empty())
+ http.header("Host", host_header);
+ http.header("Accept", "application/json")
+ .header("Content-Type", "application/octet-stream")
+ .header("Content-Length", std::to_string(chunk.size()))
+ .header("Content-Range", range)
+ .header("X-File-Name", filename)
+ .header("X-File-MD5", md5)
+ .header("X-Token", token)
+ .set_post_body(chunk)
+ .on_complete([&](std::string body, unsigned status) {
+ BOOST_LOG_TRIVIAL(debug) << boost::format("%1%: CC2 chunk uploaded: HTTP %2%: %3%") % name % status % body;
+ std::string error_message;
+ if (!parse_cc2_response(body, error_message)) {
+ error_fn(format_error(body, error_message.empty() ? "CC2 upload failed" : error_message, status));
+ return;
+ }
+ result = true;
+ })
+ .on_error([&](std::string body, std::string error, unsigned status) {
+ BOOST_LOG_TRIVIAL(error) << boost::format("%1%: Error uploading CC2 chunk: %2%, HTTP %3%, body: `%4%`") % name % error % status % body;
+ if (status == 401 || status == 403)
+ error_fn(format_error(body, "Invalid access code", status));
+ else
+ error_fn(format_error(body, error.empty() ? "CC2 upload failed" : error, status));
+ })
+ .on_progress([&](Http::Progress progress, bool& cancel) {
+ if (progress.ultotal == progress.ulnow)
+ return;
+ prorgess_fn(std::move(progress), cancel);
+ if (cancel)
+ BOOST_LOG_TRIVIAL(info) << name << ": CC2 upload canceled";
+ })
+#ifdef WIN32
+ .ssl_revoke_best_effort(m_ssl_revoke_best_effort)
+#endif
+ .perform_sync();
+
+ return result;
+ }
+
+ bool ElegooLink::loopUploadCC2(std::string url,
+ const std::string& host_header,
+ PrintHostUpload upload_data,
+ ProgressFn progress_fn,
+ ErrorFn error_fn,
+ InfoFn info_fn) const
+ {
+ BOOST_LOG_TRIVIAL(info) << get_name() << ": Uploading file to Elegoo CC2";
+ const auto upload_filename = filename_to_utf8(upload_data.upload_path);
+ std::string source_path = path_to_utf8(upload_data.source_path);
+ std::string md5;
+ bbl_calc_md5(source_path, md5);
+ std::transform(md5.begin(), md5.end(), md5.begin(), [](unsigned char ch) { return static_cast(std::tolower(ch)); });
+
+ boost::nowide::ifstream file(source_path, std::ios::binary | std::ios::ate);
+ if (!file.is_open()) {
+ error_fn(_L("Failed to open file for upload."));
+ return false;
+ }
+
+ const std::streamsize size = file.tellg();
+ file.close();
+ if (size <= 0) {
+ error_fn(_L("The file is empty or could not be read."));
+ return false;
+ }
+
+ if (md5.empty()) {
+ error_fn(_L("Failed to calculate file checksum."));
+ return false;
+ }
+
+ const std::string token = cc2_token();
+ const int packageCount = static_cast((size + MAX_UPLOAD_PACKAGE_LENGTH - 1) / MAX_UPLOAD_PACKAGE_LENGTH);
+ auto http = Http::put2(url);
+ http.timeout_connect(30)
+ .timeout_max(180);
+
+ bool res = false;
+ for (int i = 0; i < packageCount; ++i) {
+ const size_t offset = MAX_UPLOAD_PACKAGE_LENGTH * static_cast(i);
+ size_t length = MAX_UPLOAD_PACKAGE_LENGTH;
+ if (i == packageCount - 1 && size % MAX_UPLOAD_PACKAGE_LENGTH > 0)
+ length = static_cast(size % MAX_UPLOAD_PACKAGE_LENGTH);
+
+ res = uploadPartCC2(
+ http, host_header, token, md5, source_path, upload_filename, static_cast(size), offset, length,
+ [size, i, progress_fn](Http::Progress progress, bool& cancel) {
+ const size_t uploaded = static_cast(i) * MAX_UPLOAD_PACKAGE_LENGTH + progress.ulnow;
+ Http::Progress merged(0, 0, static_cast(size), std::min(static_cast(size), uploaded), progress.buffer,
+ progress.upload_spd);
+ progress_fn(merged, cancel);
+ },
+ error_fn);
+ if (!res)
+ break;
+ }
+
+ if (res && upload_data.post_action == PrintHostPostUploadAction::StartPrint)
+ BOOST_LOG_TRIVIAL(info) << get_name() << ": CC2 upload completed; start print is not supported.";
+
+ (void) info_fn;
+ return res;
+ }
+
bool ElegooLink::upload(PrintHostUpload upload_data, ProgressFn prorgess_fn, ErrorFn error_fn, InfoFn info_fn) const
{
#ifndef WIN32
diff --git a/src/slic3r/Utils/ElegooLink.hpp b/src/slic3r/Utils/ElegooLink.hpp
index b0c379c29f..eb1ca7ba26 100644
--- a/src/slic3r/Utils/ElegooLink.hpp
+++ b/src/slic3r/Utils/ElegooLink.hpp
@@ -20,14 +20,16 @@ class ElegooLink : public OctoPrint
public:
ElegooLink(DynamicPrintConfig *config);
~ElegooLink() override = default;
+ static std::string get_print_host_webui(DynamicPrintConfig *config);
const char* get_name() const override;
virtual 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, InfoFn info_fn) const override;
+ std::string get_sn() 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; }
+ PrintHostPostUploadActions get_post_upload_actions() const override;
protected:
#ifdef WIN32
virtual bool upload_inner_with_resolved_ip(PrintHostUpload upload_data, ProgressFn prorgess_fn, ErrorFn error_fn, InfoFn info_fn, const boost::asio::ip::address& resolved_addr) const;
@@ -39,9 +41,9 @@ protected:
virtual bool test_with_resolved_ip(wxString& curl_msg) const override;
bool elegoo_test_with_resolved_ip(wxString& curl_msg) const;
#endif
-
private:
bool elegoo_test(wxString& curl_msg) const;
+ bool elegoo_cc2_test(wxString& curl_msg) const;
bool print(WebSocketClient& client,
std::string timeLapse,
std::string heatedBedLeveling,
@@ -54,6 +56,12 @@ private:
ProgressFn prorgess_fn,
ErrorFn error_fn,
InfoFn info_fn) const;
+ bool loopUploadCC2(std::string url,
+ const std::string& host_header,
+ PrintHostUpload upload_data,
+ ProgressFn prorgess_fn,
+ ErrorFn error_fn,
+ InfoFn info_fn) const;
bool uploadPart(Http &http,
std::string md5,
@@ -66,6 +74,26 @@ private:
ProgressFn prorgess_fn,
ErrorFn error_fn,
InfoFn info_fn) const;
+ bool uploadPartCC2(Http& http,
+ const std::string& host_header,
+ const std::string& token,
+ const std::string& md5,
+ const boost::filesystem::path& path,
+ const std::string& filename,
+ size_t filesize,
+ size_t offset,
+ size_t length,
+ ProgressFn prorgess_fn,
+ ErrorFn error_fn) const;
+
+ std::string cc2_token() const;
+ std::string make_cc2_info_url() const;
+ std::string make_cc2_upload_url() const;
+#ifdef WIN32
+ bool elegoo_cc2_test_with_resolved_ip(wxString& curl_msg) const;
+#endif
+
+ std::string m_printerModel;
};
}
diff --git a/src/slic3r/Utils/Http.cpp b/src/slic3r/Utils/Http.cpp
index f75da63fb5..4f5227167c 100644
--- a/src/slic3r/Utils/Http.cpp
+++ b/src/slic3r/Utils/Http.cpp
@@ -570,6 +570,21 @@ Http& Http::header(std::string name, const std::string &value)
return *this;
}
+Http& Http::headers_reset()
+{
+ if (!p) { return *this; }
+
+ ::curl_slist_free_all(p->headerlist);
+ p->headerlist = nullptr;
+ p->headerlist = curl_slist_append(p->headerlist, "Expect:");
+
+ std::lock_guard l(g_mutex);
+ for (auto it = extra_headers.begin(); it != extra_headers.end(); ++it)
+ this->header(it->first, it->second);
+
+ return *this;
+}
+
Http& Http::remove_header(std::string name)
{
if (p) {
diff --git a/src/slic3r/Utils/Http.hpp b/src/slic3r/Utils/Http.hpp
index b08894ea62..4b6967a454 100644
--- a/src/slic3r/Utils/Http.hpp
+++ b/src/slic3r/Utils/Http.hpp
@@ -109,6 +109,8 @@ public:
Http& set_range(const std::string& range);
// Sets a HTTP header field.
Http& header(std::string name, const std::string &value);
+ // Clears all custom headers and restores default implicit headers.
+ Http& headers_reset();
// Removes a header field.
Http& remove_header(std::string name);
// Authorization by HTTP digest, based on RFC2617.
diff --git a/src/slic3r/Utils/PrintHost.cpp b/src/slic3r/Utils/PrintHost.cpp
index 097d26d4c0..3ed4e07382 100644
--- a/src/slic3r/Utils/PrintHost.cpp
+++ b/src/slic3r/Utils/PrintHost.cpp
@@ -74,6 +74,40 @@ PrintHost* PrintHost::get_print_host(DynamicPrintConfig *config)
}
}
+std::string PrintHost::get_print_host_webui(DynamicPrintConfig* config)
+{
+ if (config == nullptr)
+ return {};
+
+ std::string webui_url;
+ const auto* host_type_opt = config->option>("host_type");
+ const auto host_type = host_type_opt != nullptr ? host_type_opt->value : htOctoPrint;
+
+ switch (host_type) {
+ case htElegooLink: {
+ webui_url = ElegooLink::get_print_host_webui(config);
+ break;
+ }
+ default: break;
+ }
+
+ if (webui_url.empty()) {
+ webui_url = config->opt_string("print_host_webui");
+ if (webui_url.empty())
+ webui_url = config->opt_string("print_host");
+ if (webui_url.empty())
+ return webui_url;
+ }
+
+ const bool has_http_scheme = boost::algorithm::istarts_with(webui_url, "http");
+ const bool has_file_scheme = boost::algorithm::istarts_with(webui_url, "file:");
+
+ if (!has_http_scheme && !has_file_scheme)
+ webui_url = "http://" + webui_url;
+
+ return webui_url;
+}
+
wxString PrintHost::format_error(const std::string &body, const std::string &error, unsigned status) const
{
if (status != 0) {
diff --git a/src/slic3r/Utils/PrintHost.hpp b/src/slic3r/Utils/PrintHost.hpp
index 2b5e300008..2b0611849d 100644
--- a/src/slic3r/Utils/PrintHost.hpp
+++ b/src/slic3r/Utils/PrintHost.hpp
@@ -63,6 +63,12 @@ public:
// 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;
+ /**
+ * Get the serial number for connecting to the printer.
+ * For Elegoo CC2, the device details connection to the printer requires the serial number.
+ * Other print hosts do not need to implement this interface, and it returns an empty string by default.
+ */
+ virtual std::string get_sn() const { return ""; }
// Support for Repetier server multiple groups & printers. Not supported by other print hosts.
// Returns false if not supported. May throw HostNetworkError.
@@ -73,6 +79,7 @@ public:
virtual bool get_storage(wxArrayString& /*storage_path*/, wxArrayString& /*storage_name*/) const { return false; }
static PrintHost* get_print_host(DynamicPrintConfig *config);
+ static std::string get_print_host_webui(DynamicPrintConfig *config);
//Support for cloud webui login
virtual bool is_cloud() const { return false; }