Support for Obico cloud integration (#4116)

support obico (#3)

Add printer support for Obico cloud.

---------

Co-authored-by: zzh <anjingxw@126.com>
This commit is contained in:
Kenneth Jiang 2024-02-27 06:04:51 -08:00 committed by GitHub
parent 047b889ba6
commit 8c941df4e8
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
11 changed files with 519 additions and 2 deletions

View file

@ -102,7 +102,8 @@ static t_config_enum_values s_keys_map_PrintHostType {
{ "flashair", htFlashAir }, { "flashair", htFlashAir },
{ "astrobox", htAstroBox }, { "astrobox", htAstroBox },
{ "repetier", htRepetier }, { "repetier", htRepetier },
{ "mks", htMKS } { "mks", htMKS },
{ "obico", htObico }
}; };
CONFIG_OPTION_ENUM_DEFINE_STATIC_MAPS(PrintHostType) CONFIG_OPTION_ENUM_DEFINE_STATIC_MAPS(PrintHostType)
@ -3038,6 +3039,7 @@ def = this->add("filament_loading_speed", coFloats);
def->enum_values.push_back("astrobox"); def->enum_values.push_back("astrobox");
def->enum_values.push_back("repetier"); def->enum_values.push_back("repetier");
def->enum_values.push_back("mks"); def->enum_values.push_back("mks");
def->enum_values.push_back("obico");
def->enum_labels.push_back("PrusaLink"); def->enum_labels.push_back("PrusaLink");
def->enum_labels.push_back("PrusaConnect"); def->enum_labels.push_back("PrusaConnect");
def->enum_labels.push_back("Octo/Klipper"); def->enum_labels.push_back("Octo/Klipper");
@ -3046,6 +3048,7 @@ def = this->add("filament_loading_speed", coFloats);
def->enum_labels.push_back("AstroBox"); def->enum_labels.push_back("AstroBox");
def->enum_labels.push_back("Repetier"); def->enum_labels.push_back("Repetier");
def->enum_labels.push_back("MKS"); def->enum_labels.push_back("MKS");
def->enum_labels.push_back("Obico");
def->mode = comAdvanced; def->mode = comAdvanced;
def->cli = ConfigOptionDef::nocli; def->cli = ConfigOptionDef::nocli;
def->set_default_value(new ConfigOptionEnum<PrintHostType>(htOctoPrint)); def->set_default_value(new ConfigOptionEnum<PrintHostType>(htOctoPrint));

View file

@ -59,7 +59,7 @@ enum class FuzzySkinType {
}; };
enum PrintHostType { enum PrintHostType {
htPrusaLink, htPrusaConnect, htOctoPrint, htDuet, htFlashAir, htAstroBox, htRepetier, htMKS htPrusaLink, htPrusaConnect, htOctoPrint, htDuet, htFlashAir, htAstroBox, htRepetier, htMKS, htObico
}; };
enum AuthorizationType { enum AuthorizationType {

View file

@ -524,6 +524,10 @@ set(SLIC3R_GUI_SOURCES
GUI/calib_dlg.cpp GUI/calib_dlg.cpp
Utils/CalibUtils.cpp Utils/CalibUtils.cpp
Utils/CalibUtils.hpp Utils/CalibUtils.hpp
GUI/PrinterCloudAuthDialog.cpp
GUI/PrinterCloudAuthDialog.hpp
Utils/Obico.cpp
Utils/Obico.hpp
) )
if (WIN32) if (WIN32)

View file

@ -1,10 +1,12 @@
#include "PhysicalPrinterDialog.hpp" #include "PhysicalPrinterDialog.hpp"
#include "PresetComboBoxes.hpp" #include "PresetComboBoxes.hpp"
#include "PrinterCloudAuthDialog.hpp"
#include <cstddef> #include <cstddef>
#include <vector> #include <vector>
#include <string> #include <string>
#include <boost/algorithm/string.hpp> #include <boost/algorithm/string.hpp>
#include <boost/regex.hpp>
#include <wx/sizer.h> #include <wx/sizer.h>
#include <wx/stattext.h> #include <wx/stattext.h>
@ -124,6 +126,8 @@ void PhysicalPrinterDialog::build_printhost_settings(ConfigOptionsGroup* m_optgr
this->update(); this->update();
if (opt_key == "print_host") if (opt_key == "print_host")
this->update_printhost_buttons(); this->update_printhost_buttons();
if (opt_key == "printhost_port")
this->update_ports();
}; };
m_optgroup->append_single_option_line("host_type"); m_optgroup->append_single_option_line("host_type");
@ -161,12 +165,31 @@ void PhysicalPrinterDialog::build_printhost_settings(ConfigOptionsGroup* m_optgr
show_error(this, text); show_error(this, text);
return; return;
} }
wxString msg; wxString msg;
bool result; bool result;
{ {
// Show a wait cursor during the connection test, as it is blocking UI. // Show a wait cursor during the connection test, as it is blocking UI.
wxBusyCursor wait; wxBusyCursor wait;
result = host->test(msg); result = host->test(msg);
if (!result && host->is_cloud()) {
PrinterCloudAuthDialog dlg(this->GetParent(), host.get());
dlg.ShowModal();
auto api_key = dlg.GetApiKey();
m_config->opt_string("printhost_apikey") = api_key;
result = !api_key.empty();
if (result) {
if (Field* print_host_webui_field = this->m_optgroup->get_field("printhost_apikey"); print_host_webui_field) {
if (TextInput* temp_input = dynamic_cast<TextInput*>(print_host_webui_field->getWindow()); temp_input) {
if (wxTextCtrl* temp = temp_input->GetTextCtrl()) {
temp->SetValue(wxString(api_key));
}
}
}
}
}
} }
if (result) if (result)
show_info(this, host->get_test_ok_msg(), _L("Success!")); show_info(this, host->get_test_ok_msg(), _L("Success!"));
@ -311,6 +334,42 @@ void PhysicalPrinterDialog::build_printhost_settings(ConfigOptionsGroup* m_optgr
update(); update();
} }
void PhysicalPrinterDialog::update_ports() {
const PrinterTechnology tech = Preset::printer_technology(*m_config);
if (tech == ptFFF) {
const auto opt = m_config->option<ConfigOptionEnum<PrintHostType>>("host_type");
if (opt->value == htObico) {
auto build_web_ui = [](DynamicPrintConfig* config) {
auto host = config->opt_string("print_host");
auto port = config->opt_string("printhost_port");
auto api_key = config->opt_string("printhost_apikey");
if (host.empty() || port.empty()) {
return std::string();
}
boost::regex re("\\[(\\d+)\\]");
boost::smatch match;
if (!boost::regex_search(port, match, re))
return std::string();
if (match.size() <= 1) {
return std::string();
}
boost::format urlFormat("%1%/printers/%2%/control");
urlFormat % host % match[1];
return urlFormat.str();
};
auto url = build_web_ui(m_config);
if (Field* print_host_webui_field = m_optgroup->get_field("print_host_webui"); print_host_webui_field) {
if (TextInput* temp_input = dynamic_cast<TextInput*>(print_host_webui_field->getWindow()); temp_input) {
if (wxTextCtrl* temp = temp_input->GetTextCtrl()) {
temp->SetValue(wxString(url));
m_config->opt_string("print_host_webui") = url;
}
}
}
}
}
}
void PhysicalPrinterDialog::update_printhost_buttons() void PhysicalPrinterDialog::update_printhost_buttons()
{ {
std::unique_ptr<PrintHost> host(PrintHost::get_print_host(m_config)); std::unique_ptr<PrintHost> host(PrintHost::get_print_host(m_config));
@ -415,6 +474,12 @@ void PhysicalPrinterDialog::update(bool printer_change)
if (wxTextCtrl* temp = dynamic_cast<wxTextCtrl*>(printhost_field->getWindow()); temp && temp->GetValue() == L"https://connect.prusa3d.com") { if (wxTextCtrl* temp = dynamic_cast<wxTextCtrl*>(printhost_field->getWindow()); temp && temp->GetValue() == L"https://connect.prusa3d.com") {
temp->SetValue(wxString()); temp->SetValue(wxString());
} }
if (TextInput* temp_input = dynamic_cast<TextInput*>(printhost_field->getWindow()); temp_input) {
if (wxTextCtrl* temp = temp_input->GetTextCtrl(); temp &&temp->GetValue() == L"https://app.obico.io") {
temp->SetValue(wxString());
}
}
} }
if (opt->value == htPrusaLink) { // PrusaConnect does NOT allow http digest if (opt->value == htPrusaLink) { // PrusaConnect does NOT allow http digest
m_optgroup->show_field("printhost_authorization_type"); m_optgroup->show_field("printhost_authorization_type");
@ -436,6 +501,18 @@ void PhysicalPrinterDialog::update(bool printer_change)
} }
} }
} }
if (opt->value == htObico) {
supports_multiple_printers = true;
if (Field* printhost_field = m_optgroup->get_field("print_host"); printhost_field) {
if (TextInput* temp_input = dynamic_cast<TextInput*>(printhost_field->getWindow()); temp_input) {
if (wxTextCtrl* temp = temp_input->GetTextCtrl(); temp && temp->GetValue().IsEmpty()) {
temp->SetValue(L"https://app.obico.io");
m_config->opt_string("print_host") = "https://app.obico.io";
}
}
}
}
} }
else { else {
m_optgroup->set_value("host_type", int(PrintHostType::htOctoPrint), false); m_optgroup->set_value("host_type", int(PrintHostType::htOctoPrint), false);

View file

@ -61,6 +61,7 @@ public:
void update_preset_input(); void update_preset_input();
void update_printhost_buttons(); void update_printhost_buttons();
void update_printers(); void update_printers();
void update_ports();
protected: protected:
void on_dpi_changed(const wxRect& suggested_rect) override; void on_dpi_changed(const wxRect& suggested_rect) override;

View file

@ -0,0 +1,105 @@
#include "PrinterCloudAuthDialog.hpp"
#include <wx/sizer.h>
#include <wx/toolbar.h>
#include <wx/textdlg.h>
#include <wx/wx.h>
#include <wx/fileconf.h>
#include <wx/file.h>
#include <wx/wfstream.h>
#include <boost/cast.hpp>
#include <boost/lexical_cast.hpp>
#include <nlohmann/json.hpp>
#include "MainFrame.hpp"
#include <boost/dll.hpp>
#include <sstream>
#include <slic3r/GUI/Widgets/WebView.hpp>
//------------------------------------------
// PrinterCloundAuthDialog
//------------------------------------------
namespace Slic3r { namespace GUI {
PrinterCloudAuthDialog::PrinterCloudAuthDialog(wxWindow* parent, PrintHost* host)
: wxDialog((wxWindow*) (wxGetApp().mainframe), wxID_ANY, "Login"), m_host(host)
{
SetBackgroundColour(*wxWHITE);
// Url
host->get_login_url(m_TargetUrl);
BOOST_LOG_TRIVIAL(info) << "login url = " << m_TargetUrl.ToStdString();
// Create the webview
m_browser = WebView::CreateWebView(this, m_TargetUrl);
if (m_browser == nullptr) {
wxLogError("Could not init m_browser");
return;
}
m_browser->Hide();
m_browser->SetSize(0, 0);
// Connect the webview events
Bind(wxEVT_WEBVIEW_NAVIGATING, &PrinterCloudAuthDialog::OnNavigationRequest, this, m_browser->GetId());
Bind(wxEVT_WEBVIEW_NAVIGATED, &PrinterCloudAuthDialog::OnNavigationComplete, this, m_browser->GetId());
Bind(wxEVT_WEBVIEW_LOADED, &PrinterCloudAuthDialog::OnDocumentLoaded, this, m_browser->GetId());
Bind(wxEVT_WEBVIEW_NEWWINDOW, &PrinterCloudAuthDialog::OnNewWindow, this, m_browser->GetId());
Bind(wxEVT_WEBVIEW_SCRIPT_MESSAGE_RECEIVED, &PrinterCloudAuthDialog::OnScriptMessage, this, m_browser->GetId());
// UI
SetTitle(_L("Login"));
// Set a more sensible size for web browsing
wxSize pSize = FromDIP(wxSize(650, 840));
SetSize(pSize);
int screenheight = wxSystemSettings::GetMetric(wxSYS_SCREEN_Y, NULL);
int screenwidth = wxSystemSettings::GetMetric(wxSYS_SCREEN_X, NULL);
int MaxY = (screenheight - pSize.y) > 0 ? (screenheight - pSize.y) / 2 : 0;
wxPoint tmpPT((screenwidth - pSize.x) / 2, MaxY);
Move(tmpPT);
}
PrinterCloudAuthDialog::~PrinterCloudAuthDialog() {}
void PrinterCloudAuthDialog::OnNavigationRequest(wxWebViewEvent& evt)
{
//todo
}
void PrinterCloudAuthDialog::OnNavigationComplete(wxWebViewEvent& evt)
{
m_browser->Show();
Layout();
//fortest
//WebView::RunScript(m_browser, "window.wx.postMessage('This is a web message')");
}
void PrinterCloudAuthDialog::OnDocumentLoaded(wxWebViewEvent& evt)
{
// todo
}
void PrinterCloudAuthDialog::OnNewWindow(wxWebViewEvent& evt) {
}
void PrinterCloudAuthDialog::OnScriptMessage(wxWebViewEvent& evt)
{
wxString str_input = evt.GetString();
try {
json j = json::parse(into_u8(str_input));
wxString strCmd = j["command"];
if (strCmd == "login_token") {
auto token = j["data"]["token"];
m_host->set_api_key(token);
m_apikey = token;
}
Close();
} catch (std::exception& e) {
wxMessageBox(e.what(), "parse json failed", wxICON_WARNING);
Close();
}
}
}
} // namespace Slic3r::GUI

View file

@ -0,0 +1,51 @@
#ifndef slic3r_GUI_PrinterCloudAuthDialog_hpp_
#define slic3r_GUI_PrinterCloudAuthDialog_hpp_
#include <wx/wx.h>
#include <wx/font.h>
#include <wx/colour.h>
#include <wx/string.h>
#include <wx/sizer.h>
#include <wx/dialog.h>
#include "wx/webview.h"
#if wxUSE_WEBVIEW_IE
#include "wx/msw/webview_ie.h"
#endif
#if wxUSE_WEBVIEW_EDGE
#include "wx/msw/webview_edge.h"
#endif
#include "GUI_Utils.hpp"
#include "PrintHost.hpp"
namespace Slic3r { namespace GUI {
class PrinterCloudAuthDialog : public wxDialog
{
protected:
wxWebView* m_browser;
wxString m_TargetUrl;
wxString m_javascript;
wxString m_response_js;
PrintHost* m_host;
std::string m_apikey;
public:
PrinterCloudAuthDialog(wxWindow* parent, PrintHost* host);
~PrinterCloudAuthDialog();
std::string GetApiKey() { return m_apikey; };
void OnNavigationRequest(wxWebViewEvent& evt);
void OnNavigationComplete(wxWebViewEvent& evt);
void OnDocumentLoaded(wxWebViewEvent& evt);
void OnNewWindow(wxWebViewEvent& evt);
void OnScriptMessage(wxWebViewEvent& evt);
};
}} // namespace Slic3r::GUI
#endif

218
src/slic3r/Utils/Obico.cpp Normal file
View file

@ -0,0 +1,218 @@
#include "Obico.hpp"
#include <algorithm>
#include <sstream>
#include <exception>
#include <boost/format.hpp>
#include <boost/foreach.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 <boost/asio.hpp>
#include <boost/algorithm/string/split.hpp>
#include <boost/nowide/convert.hpp>
#include <curl/curl.h>
#include <wx/progdlg.h>
#include "slic3r/GUI/GUI.hpp"
#include "slic3r/GUI/I18N.hpp"
#include "slic3r/GUI/GUI_App.hpp"
#include "slic3r/GUI/format.hpp"
#include "Http.hpp"
#include "libslic3r/AppConfig.hpp"
#include "Bonjour.hpp"
#include "slic3r/GUI/BonjourDialog.hpp"
namespace fs = boost::filesystem;
namespace pt = boost::property_tree;
namespace Slic3r {
Obico::Obico(DynamicPrintConfig* config) :
m_host(config->opt_string("print_host")),
m_web_ui(config->opt_string("print_host_webui")),
m_cafile(config->opt_string("printhost_cafile")),
m_port(config->opt_string("printhost_port")),
m_apikey(config->opt_string("printhost_apikey")),
m_ssl_revoke_best_effort(config->opt_bool("printhost_ssl_ignore_revoke"))
{}
const char* Obico::get_name() const { return "Obico"; }
void Obico::set_api_key(const std::string auth_api_key) { m_apikey = auth_api_key; }
std::string Obico::get_host() const {
return m_host;
}
void Obico::set_auth(Http& http) const
{
http.header("Authorization", "Bearer " + m_apikey);
if (!m_cafile.empty()) {
http.ca_file(m_cafile);
}
}
bool Obico::get_login_url(wxString& auth_url) const
{
auth_url = make_url("o/authorize?response_type=token&client_id=OrcaSlicer&hide_navbar=true");
return true;
}
wxString Obico::get_test_ok_msg() const { return _(L("Connected to Obico successfully!")); }
wxString Obico::get_test_failed_msg(wxString& msg) const
{
return GUI::format_wxstr("%s: %s", _L("Could not connect to Obico"), msg.Truncate(256));
}
bool Obico::test(wxString& msg) const
{
if (m_apikey.empty()) {
return false;
}
bool res = true;
const char* name = get_name();
auto url = make_url("api/v1/version/");
BOOST_LOG_TRIVIAL(info) << boost::format("%1%: Get version at: %2%") % name % url;
// Here we do not have to add custom "Host" header - the url contains host filled by user and libCurl will set the header by itself.
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;
})
#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;
}
bool Obico::get_printers(wxArrayString& printers) const
{
const char* name = get_name();
bool res = false;
auto url = make_url("api/v1/printers/");
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;
})
.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) {
const auto name = v.second.get<std::string>("name");
const auto port = v.second.get<std::string>("id");
printers.push_back(Slic3r::GUI::from_u8(name + " [" + port + "]"));
}
} catch (const std::exception& err) {
throw HostNetworkError(
GUI::format(_L("Enumeration of host printers failed.\nMessage body: \"%1%\"\nError: \"%2%\""), body, err.what()));
}
res = true;
})
.perform_sync();
return res;
}
PrintHostPostUploadActions Obico::get_post_upload_actions() const {
return PrintHostPostUploadAction::StartPrint;
}
bool Obico::upload(PrintHostUpload upload_data, ProgressFn prorgess_fn, ErrorFn error_fn, InfoFn info_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/v1/g_code_files/");
auto http = Http::post(url); // 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("printer_id", m_port)
.form_add("filename", upload_filename.string())
.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 to %2%: %3%, HTTP %4%, body: `%5%`") % name % url % 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) << name << ": Upload canceled";
res = false;
}
})
#ifdef WIN32
.ssl_revoke_best_effort(m_ssl_revoke_best_effort)
#endif
.perform_sync();
return res;
}
std::string Obico::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();
}
}
}

View file

@ -0,0 +1,51 @@
#ifndef slic3r_Obico_hpp_
#define slic3r_Obico_hpp_
#include <string>
#include <wx/string.h>
#include <boost/optional.hpp>
#include <boost/asio/ip/address.hpp>
#include "PrintHost.hpp"
#include "libslic3r/PrintConfig.hpp"
namespace Slic3r {
class DynamicPrintConfig;
class Http;
class Obico : public PrintHost
{
public:
Obico(DynamicPrintConfig* config);
~Obico() override = default;
const char* get_name() const override;
virtual bool can_test() const { return true; };
bool has_auto_discovery() const override { return false; }
bool is_cloud() const override { return true; }
bool get_login_url(wxString& auth_url) const override;
void set_api_key(const std::string auth_api_key) override;
std::string get_host() const override;
wxString get_test_ok_msg() const override;
wxString get_test_failed_msg(wxString& msg) const override;
virtual bool test(wxString& curl_msg) const override;
bool get_printers(wxArrayString& printers) const override;
PrintHostPostUploadActions get_post_upload_actions() const;
bool upload(PrintHostUpload upload_data, ProgressFn prorgess_fn, ErrorFn error_fn, InfoFn info_fn) const override;
protected:
virtual void set_auth(Http& http) const;
private:
std::string m_host;
std::string m_port;
std::string m_apikey;
std::string m_cafile;
std::string m_web_ui;
bool m_ssl_revoke_best_effort;
std::string make_url(const std::string& path) const;
};
} // namespace Slic3r
#endif

View file

@ -20,6 +20,7 @@
#include "Repetier.hpp" #include "Repetier.hpp"
#include "MKS.hpp" #include "MKS.hpp"
#include "../GUI/PrintHostDialogs.hpp" #include "../GUI/PrintHostDialogs.hpp"
#include "Obico.hpp"
namespace fs = boost::filesystem; namespace fs = boost::filesystem;
using boost::optional; using boost::optional;
@ -54,6 +55,7 @@ PrintHost* PrintHost::get_print_host(DynamicPrintConfig *config)
case htPrusaLink: return new PrusaLink(config); case htPrusaLink: return new PrusaLink(config);
case htPrusaConnect: return new PrusaConnect(config); case htPrusaConnect: return new PrusaConnect(config);
case htMKS: return new MKS(config); case htMKS: return new MKS(config);
case htObico: return new Obico(config);
default: return nullptr; default: return nullptr;
} }
} else { } else {

View file

@ -70,6 +70,11 @@ public:
static PrintHost* get_print_host(DynamicPrintConfig *config); static PrintHost* get_print_host(DynamicPrintConfig *config);
//Support for cloud webui login
virtual bool is_cloud() const { return false; }
virtual bool get_login_url(wxString& auth_url) const { return false; }
virtual void set_api_key(const std::string auth_api_key) {}
protected: protected:
virtual wxString format_error(const std::string &body, const std::string &error, unsigned status) const; virtual wxString format_error(const std::string &body, const std::string &error, unsigned status) const;
}; };