mirror of
https://github.com/OrcaSlicer/OrcaSlicer.git
synced 2026-09-29 20:01:26 +00:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
769c885052 | ||
|
|
77248a7e9d | ||
|
|
e68694dbaf | ||
|
|
72cfe71b81 |
@@ -1019,14 +1019,9 @@ if (UNIX AND NOT APPLE)
|
|||||||
find_package(GTK${SLIC3R_GTK} REQUIRED)
|
find_package(GTK${SLIC3R_GTK} REQUIRED)
|
||||||
pkg_check_modules(LIBSECRET REQUIRED libsecret-1)
|
pkg_check_modules(LIBSECRET REQUIRED libsecret-1)
|
||||||
pkg_check_modules(webkit2gtk REQUIRED webkit2gtk-4.1)
|
pkg_check_modules(webkit2gtk REQUIRED webkit2gtk-4.1)
|
||||||
if (FLATPAK)
|
find_package(X11 REQUIRED)
|
||||||
# I don't know why this is needed, but for whatever reason slic3r isn't
|
|
||||||
# linking to X11 and webkit2gtk. force it.
|
|
||||||
find_package(X11 REQUIRED)
|
|
||||||
target_link_libraries(libslic3r_gui ${X11_LIBRARIES} ${webkit2gtk_LIBRARIES})
|
|
||||||
endif()
|
|
||||||
target_include_directories(libslic3r_gui SYSTEM PRIVATE ${GTK${SLIC3R_GTK}_INCLUDE_DIRS} ${LIBSECRET_INCLUDE_DIRS} ${webkit2gtk_INCLUDE_DIRS})
|
target_include_directories(libslic3r_gui SYSTEM PRIVATE ${GTK${SLIC3R_GTK}_INCLUDE_DIRS} ${LIBSECRET_INCLUDE_DIRS} ${webkit2gtk_INCLUDE_DIRS})
|
||||||
target_link_libraries(libslic3r_gui ${GTK${SLIC3R_GTK}_LIBRARIES} fontconfig ${LIBSECRET_LIBRARIES})
|
target_link_libraries(libslic3r_gui ${GTK${SLIC3R_GTK}_LIBRARIES} fontconfig ${LIBSECRET_LIBRARIES} ${webkit2gtk_LIBRARIES} ${X11_LIBRARIES})
|
||||||
|
|
||||||
# Propagate GDK backend detection results as compile definitions so that
|
# Propagate GDK backend detection results as compile definitions so that
|
||||||
# LinuxDisplayBackend.cpp can include the right GDK headers.
|
# LinuxDisplayBackend.cpp can include the right GDK headers.
|
||||||
|
|||||||
@@ -14,6 +14,9 @@
|
|||||||
#include <boost/algorithm/string/predicate.hpp>
|
#include <boost/algorithm/string/predicate.hpp>
|
||||||
#include <boost/any.hpp>
|
#include <boost/any.hpp>
|
||||||
|
|
||||||
|
#include <wx/filename.h>
|
||||||
|
#include <wx/filesys.h>
|
||||||
|
|
||||||
#if __APPLE__
|
#if __APPLE__
|
||||||
#import <IOKit/pwr_mgt/IOPMLib.h>
|
#import <IOKit/pwr_mgt/IOPMLib.h>
|
||||||
#elif _WIN32
|
#elif _WIN32
|
||||||
@@ -531,6 +534,11 @@ boost::filesystem::path into_path(const wxString &str)
|
|||||||
return boost::filesystem::path(str.wx_str());
|
return boost::filesystem::path(str.wx_str());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
wxString file_url_from_path(const boost::filesystem::path &path)
|
||||||
|
{
|
||||||
|
return wxFileSystem::FileNameToURL(wxFileName(from_path(path)));
|
||||||
|
}
|
||||||
|
|
||||||
void about()
|
void about()
|
||||||
{
|
{
|
||||||
AboutDialog dlg;
|
AboutDialog dlg;
|
||||||
|
|||||||
@@ -76,6 +76,9 @@ std::string into_u8(const wxString &str);
|
|||||||
wxString from_path(const boost::filesystem::path &path);
|
wxString from_path(const boost::filesystem::path &path);
|
||||||
// boost path from wxString
|
// boost path from wxString
|
||||||
boost::filesystem::path into_path(const wxString &str);
|
boost::filesystem::path into_path(const wxString &str);
|
||||||
|
// file:// URL of a local path, percent-encoded so characters such as '#', '%' and '?' stay part of the path.
|
||||||
|
// Append any query or fragment to the result.
|
||||||
|
wxString file_url_from_path(const boost::filesystem::path &path);
|
||||||
|
|
||||||
// Display an About dialog
|
// Display an About dialog
|
||||||
extern void about();
|
extern void about();
|
||||||
|
|||||||
@@ -98,10 +98,7 @@ void MarkdownTip::LoadStyle()
|
|||||||
ph /= "tooltip/styled.html";
|
ph /= "tooltip/styled.html";
|
||||||
_data_dir = false;
|
_data_dir = false;
|
||||||
}
|
}
|
||||||
auto url = ph.string();
|
_tipView->LoadURL(file_url_from_path(ph));
|
||||||
std::replace(url.begin(), url.end(), '\\', '/');
|
|
||||||
url = "file:///" + url;
|
|
||||||
_tipView->LoadURL(from_u8(url));
|
|
||||||
_lastTip.clear();
|
_lastTip.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -3672,7 +3672,7 @@ void Sidebar::update_all_preset_comboboxes()
|
|||||||
wxString url = from_u8(PrintHost::get_print_host_webui(&cfg));
|
wxString url = from_u8(PrintHost::get_print_host_webui(&cfg));
|
||||||
wxString apikey;
|
wxString apikey;
|
||||||
if(url.empty())
|
if(url.empty())
|
||||||
url = wxString::Format("file://%s/web/orca/missing_connection.html", from_u8(resources_dir()));
|
url = file_url_from_path(boost::filesystem::path(resources_dir()) / "web/orca/missing_connection.html");
|
||||||
else {
|
else {
|
||||||
const auto host_type = cfg.option<ConfigOptionEnum<PrintHostType>>("host_type")->value;
|
const auto host_type = cfg.option<ConfigOptionEnum<PrintHostType>>("host_type")->value;
|
||||||
if (cfg.has("printhost_apikey") && (host_type != htSimplyPrint))
|
if (cfg.has("printhost_apikey") && (host_type != htSimplyPrint))
|
||||||
|
|||||||
@@ -57,9 +57,7 @@ PrivacyUpdateDialog::PrivacyUpdateDialog(wxWindow* parent, wxWindowID id, const
|
|||||||
|
|
||||||
fs::path ph(resources_dir());
|
fs::path ph(resources_dir());
|
||||||
ph /= "tooltip/privacyupdate.html";
|
ph /= "tooltip/privacyupdate.html";
|
||||||
m_host_url = ph.string();
|
m_host_url = into_u8(file_url_from_path(ph));
|
||||||
std::replace(m_host_url.begin(), m_host_url.end(), '\\', '/');
|
|
||||||
m_host_url = "file:///" + m_host_url;
|
|
||||||
m_vebview_release_note->LoadURL(from_u8(m_host_url));
|
m_vebview_release_note->LoadURL(from_u8(m_host_url));
|
||||||
m_sizer_right->Add(m_vebview_release_note, 0, wxEXPAND | wxRIGHT | wxLEFT, FromDIP(15));
|
m_sizer_right->Add(m_vebview_release_note, 0, wxEXPAND | wxRIGHT | wxLEFT, FromDIP(15));
|
||||||
|
|
||||||
|
|||||||
@@ -45,10 +45,10 @@ const std::vector<std::string> license_list = {
|
|||||||
ProjectPanel::ProjectPanel(wxWindow *parent, wxWindowID id, const wxPoint &pos, const wxSize &size, long style) : wxPanel(parent, id, pos, size, style)
|
ProjectPanel::ProjectPanel(wxWindow *parent, wxWindowID id, const wxPoint &pos, const wxSize &size, long style) : wxPanel(parent, id, pos, size, style)
|
||||||
{
|
{
|
||||||
SetBackgroundColour(*wxWHITE);
|
SetBackgroundColour(*wxWHITE);
|
||||||
m_project_home_url = wxString::Format("file://%s/web/model/index.html", from_u8(resources_dir()));
|
m_project_home_url = file_url_from_path(boost::filesystem::path(resources_dir()) / "web/model/index.html");
|
||||||
wxString strlang = wxGetApp().current_language_code_safe();
|
wxString strlang = wxGetApp().current_language_code_safe();
|
||||||
if (strlang != "")
|
if (strlang != "")
|
||||||
m_project_home_url = wxString::Format("file://%s/web/model/index.html?lang=%s", from_u8(resources_dir()), strlang);
|
m_project_home_url += "?lang=" + strlang;
|
||||||
|
|
||||||
wxBoxSizer* main_sizer = new wxBoxSizer(wxVERTICAL);
|
wxBoxSizer* main_sizer = new wxBoxSizer(wxVERTICAL);
|
||||||
|
|
||||||
|
|||||||
@@ -510,7 +510,7 @@ void UpdateVersionDialog::update_version_info(wxString release_note, wxString ve
|
|||||||
out_buf->append(text, size);
|
out_buf->append(text, size);
|
||||||
}, (void*) &html_source, MD_DIALECT_GITHUB | MD_FLAG_STRIKETHROUGH | MD_FLAG_WIKILINKS, 0);
|
}, (void*) &html_source, MD_DIALECT_GITHUB | MD_FLAG_STRIKETHROUGH | MD_FLAG_WIKILINKS, 0);
|
||||||
html_source.append("</body></html>");
|
html_source.append("</body></html>");
|
||||||
m_vebview_release_note->LoadURL("file://" + (boost::filesystem::path (resources_dir()) / "web/guide/0/index.html").string());
|
m_vebview_release_note->LoadURL(file_url_from_path(boost::filesystem::path(resources_dir()) / "web/guide/0/index.html"));
|
||||||
|
|
||||||
SetMinSize(GetSize());
|
SetMinSize(GetSize());
|
||||||
SetMaxSize(GetSize());
|
SetMaxSize(GetSize());
|
||||||
|
|||||||
@@ -31,14 +31,15 @@ int UserManager::parse_json(std::string payload)
|
|||||||
{
|
{
|
||||||
bool restored_json = false;
|
bool restored_json = false;
|
||||||
json j;
|
json j;
|
||||||
json j_pre = json::parse(payload);
|
|
||||||
if (j_pre.empty()) {
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
//bind/unbind
|
//bind/unbind
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
json j_pre = json::parse(payload);
|
||||||
|
if (j_pre.empty()) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
if (j_pre.contains("bind")) {
|
if (j_pre.contains("bind")) {
|
||||||
if (j_pre["bind"].contains("command")) {
|
if (j_pre["bind"].contains("command")) {
|
||||||
|
|
||||||
|
|||||||
@@ -38,9 +38,7 @@ DownPluginFrame::DownPluginFrame(GUI_App *pGUI) : wxDialog((wxWindow *) (pGUI->m
|
|||||||
|
|
||||||
// set the frame icon
|
// set the frame icon
|
||||||
wxBoxSizer *topsizer = new wxBoxSizer(wxVERTICAL);
|
wxBoxSizer *topsizer = new wxBoxSizer(wxVERTICAL);
|
||||||
wxString TargetUrl = from_u8((boost::filesystem::path(resources_dir()) / "web/guide/6/index.html").make_preferred().string());
|
wxString TargetUrl = file_url_from_path(boost::filesystem::path(resources_dir()) / "web/guide/6/index.html");
|
||||||
|
|
||||||
TargetUrl = "file://" + TargetUrl;
|
|
||||||
|
|
||||||
// Create the webview
|
// Create the webview
|
||||||
m_browser = WebView::CreateWebView(this, TargetUrl);
|
m_browser = WebView::CreateWebView(this, TargetUrl);
|
||||||
|
|||||||
@@ -218,37 +218,38 @@ wxString GuideFrame::SetStartPage(GuidePage startpage, bool load)
|
|||||||
m_page = startpage;
|
m_page = startpage;
|
||||||
BOOST_LOG_TRIVIAL(info) << __FUNCTION__<< boost::format(" enter, load=%1%, start_page=%2%")%load%int(startpage);
|
BOOST_LOG_TRIVIAL(info) << __FUNCTION__<< boost::format(" enter, load=%1%, start_page=%2%")%load%int(startpage);
|
||||||
//wxLogMessage("GUIDE: webpage_1 %s", (boost::filesystem::path(resources_dir()) / "web\\guide\\1\\index.html").make_preferred().string().c_str() );
|
//wxLogMessage("GUIDE: webpage_1 %s", (boost::filesystem::path(resources_dir()) / "web\\guide\\1\\index.html").make_preferred().string().c_str() );
|
||||||
wxString TargetUrl = from_u8( (boost::filesystem::path(resources_dir()) / "web/guide/0/index.html?target=1").make_preferred().string() );
|
const wxString guide_url = file_url_from_path(boost::filesystem::path(resources_dir()) / "web/guide/0/index.html");
|
||||||
|
wxString TargetUrl = guide_url + "?target=1";
|
||||||
//wxLogMessage("GUIDE: webpage_2 %s", TargetUrl.mb_str());
|
//wxLogMessage("GUIDE: webpage_2 %s", TargetUrl.mb_str());
|
||||||
|
|
||||||
if (startpage == BBL_WELCOME){
|
if (startpage == BBL_WELCOME){
|
||||||
SetTitle(_L("Setup Wizard"));
|
SetTitle(_L("Setup Wizard"));
|
||||||
TargetUrl = from_u8((boost::filesystem::path(resources_dir()) / "web/guide/0/index.html?target=1").make_preferred().string());
|
TargetUrl = guide_url + "?target=1";
|
||||||
} else if (startpage == BBL_REGION) {
|
} else if (startpage == BBL_REGION) {
|
||||||
SetTitle(_L("Setup Wizard"));
|
SetTitle(_L("Setup Wizard"));
|
||||||
TargetUrl = from_u8((boost::filesystem::path(resources_dir()) / "web/guide/0/index.html?target=11").make_preferred().string());
|
TargetUrl = guide_url + "?target=11";
|
||||||
} else if (startpage == BBL_MODELS) {
|
} else if (startpage == BBL_MODELS) {
|
||||||
SetTitle(_L("Setup Wizard"));
|
SetTitle(_L("Setup Wizard"));
|
||||||
TargetUrl = from_u8((boost::filesystem::path(resources_dir()) / "web/guide/0/index.html?target=21").make_preferred().string());
|
TargetUrl = guide_url + "?target=21";
|
||||||
} else if (startpage == BBL_FILAMENTS) {
|
} else if (startpage == BBL_FILAMENTS) {
|
||||||
SetTitle(_L("Setup Wizard"));
|
SetTitle(_L("Setup Wizard"));
|
||||||
|
|
||||||
int nSize = m_ProfileJson["model"].size();
|
int nSize = m_ProfileJson["model"].size();
|
||||||
|
|
||||||
if (nSize>0)
|
if (nSize>0)
|
||||||
TargetUrl = from_u8((boost::filesystem::path(resources_dir()) / "web/guide/0/index.html?target=22").make_preferred().string());
|
TargetUrl = guide_url + "?target=22";
|
||||||
else
|
else
|
||||||
TargetUrl = from_u8((boost::filesystem::path(resources_dir()) / "web/guide/0/index.html?target=21").make_preferred().string());
|
TargetUrl = guide_url + "?target=21";
|
||||||
} else if (startpage == BBL_FILAMENT_ONLY) {
|
} else if (startpage == BBL_FILAMENT_ONLY) {
|
||||||
SetTitle("");
|
SetTitle("");
|
||||||
TargetUrl = from_u8((boost::filesystem::path(resources_dir()) / "web/guide/0/index.html?target=23").make_preferred().string());
|
TargetUrl = guide_url + "?target=23";
|
||||||
} else if (startpage == BBL_MODELS_ONLY) {
|
} else if (startpage == BBL_MODELS_ONLY) {
|
||||||
SetTitle("");
|
SetTitle("");
|
||||||
TargetUrl = from_u8((boost::filesystem::path(resources_dir()) / "web/guide/0/index.html?target=24").make_preferred().string());
|
TargetUrl = guide_url + "?target=24";
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
SetTitle(_L("Setup Wizard"));
|
SetTitle(_L("Setup Wizard"));
|
||||||
TargetUrl = from_u8((boost::filesystem::path(resources_dir()) / "web/guide/0/index.html?target=21").make_preferred().string());
|
TargetUrl = guide_url + "?target=21";
|
||||||
}
|
}
|
||||||
|
|
||||||
wxString strlang = wxGetApp().current_language_code_safe();
|
wxString strlang = wxGetApp().current_language_code_safe();
|
||||||
@@ -256,7 +257,6 @@ wxString GuideFrame::SetStartPage(GuidePage startpage, bool load)
|
|||||||
if (strlang != "")
|
if (strlang != "")
|
||||||
TargetUrl = wxString::Format("%s&lang=%s", w2s(TargetUrl), strlang);
|
TargetUrl = wxString::Format("%s&lang=%s", w2s(TargetUrl), strlang);
|
||||||
|
|
||||||
TargetUrl = "file://" + TargetUrl;
|
|
||||||
if (load)
|
if (load)
|
||||||
load_url(TargetUrl);
|
load_url(TargetUrl);
|
||||||
|
|
||||||
@@ -1284,7 +1284,7 @@ bool GuideFrame::BuildProfileJson(const PresetBundle& bundle, bool require_all_r
|
|||||||
entry["vendor"] = vp.id;
|
entry["vendor"] = vp.id;
|
||||||
entry["nozzle_diameter"] = nozzle_str;
|
entry["nozzle_diameter"] = nozzle_str;
|
||||||
entry["materials"] = materials_str;
|
entry["materials"] = materials_str;
|
||||||
entry["cover"] = cover_path.string();
|
entry["cover"] = into_u8(file_url_from_path(cover_path));
|
||||||
entry["nozzle_selected"] = "";
|
entry["nozzle_selected"] = "";
|
||||||
entry["sub_path"] = "";
|
entry["sub_path"] = "";
|
||||||
m_ProfileJson["model"].push_back(entry);
|
m_ProfileJson["model"].push_back(entry);
|
||||||
@@ -1732,7 +1732,7 @@ int GuideFrame::LoadProfileFamily(std::string strVendor, std::string strFilePath
|
|||||||
cover_path = (boost::filesystem::absolute(boost::filesystem::path(resources_dir()) / "/web/image/printer/") / cover_file)
|
cover_path = (boost::filesystem::absolute(boost::filesystem::path(resources_dir()) / "/web/image/printer/") / cover_file)
|
||||||
.make_preferred();
|
.make_preferred();
|
||||||
}
|
}
|
||||||
OneModel["cover"] = cover_path.string();
|
OneModel["cover"] = into_u8(file_url_from_path(cover_path));
|
||||||
|
|
||||||
OneModel["nozzle_selected"] = "";
|
OneModel["nozzle_selected"] = "";
|
||||||
|
|
||||||
|
|||||||
@@ -599,7 +599,7 @@ void ZUserLogin::OnScriptResponseMessage(wxCommandEvent &WXUNUSED(evt))
|
|||||||
|
|
||||||
bool ZUserLogin::ShowErrorPage()
|
bool ZUserLogin::ShowErrorPage()
|
||||||
{
|
{
|
||||||
wxString ErrortUrl = from_u8((boost::filesystem::path(resources_dir()) / "web\\login\\error.html").make_preferred().string());
|
wxString ErrortUrl = file_url_from_path(boost::filesystem::path(resources_dir()) / "web/login/error.html");
|
||||||
load_url(ErrortUrl);
|
load_url(ErrortUrl);
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
|
|||||||
@@ -36,10 +36,10 @@ namespace GUI {
|
|||||||
WebViewPanel::WebViewPanel(wxWindow *parent)
|
WebViewPanel::WebViewPanel(wxWindow *parent)
|
||||||
: wxPanel(parent, wxID_ANY, wxDefaultPosition, wxDefaultSize)
|
: wxPanel(parent, wxID_ANY, wxDefaultPosition, wxDefaultSize)
|
||||||
{
|
{
|
||||||
wxString url = wxString::Format("file://%s/web/homepage/index.html", from_u8(resources_dir()));
|
wxString url = file_url_from_path(boost::filesystem::path(resources_dir()) / "web/homepage/index.html");
|
||||||
wxString strlang = wxGetApp().current_language_code_safe();
|
wxString strlang = wxGetApp().current_language_code_safe();
|
||||||
if (strlang != "")
|
if (strlang != "")
|
||||||
url = wxString::Format("file://%s/web/homepage/index.html?lang=%s", from_u8(resources_dir()), strlang);
|
url += "?lang=" + strlang;
|
||||||
|
|
||||||
wxBoxSizer* topsizer = new wxBoxSizer(wxVERTICAL);
|
wxBoxSizer* topsizer = new wxBoxSizer(wxVERTICAL);
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
#include "MultiNozzleSync.hpp"
|
#include "MultiNozzleSync.hpp"
|
||||||
|
|
||||||
|
#include "../GUI.hpp"
|
||||||
#include "../GUI_App.hpp"
|
#include "../GUI_App.hpp"
|
||||||
#include "../I18N.hpp"
|
#include "../I18N.hpp"
|
||||||
#include "../Plater.hpp"
|
#include "../Plater.hpp"
|
||||||
@@ -21,8 +22,6 @@
|
|||||||
#include <set>
|
#include <set>
|
||||||
|
|
||||||
#include <wx/choice.h>
|
#include <wx/choice.h>
|
||||||
#include <wx/filename.h>
|
|
||||||
#include <wx/filesys.h>
|
|
||||||
#include <wx/sizer.h>
|
#include <wx/sizer.h>
|
||||||
#include <wx/stattext.h>
|
#include <wx/stattext.h>
|
||||||
|
|
||||||
@@ -641,8 +640,7 @@ NozzleListTable::NozzleListTable(wxWindow* parent) : wxPanel(parent,wxID_ANY,wxD
|
|||||||
m_web_view->AddScriptMessageHandler("nozzleListTable");
|
m_web_view->AddScriptMessageHandler("nozzleListTable");
|
||||||
m_web_view->EnableContextMenu(false);
|
m_web_view->EnableContextMenu(false);
|
||||||
fs::path filepath = fs::path(resources_dir()) / "web/flush/NozzleListTable.html";
|
fs::path filepath = fs::path(resources_dir()) / "web/flush/NozzleListTable.html";
|
||||||
wxFileName fn(wxString::FromUTF8(filepath.string()));
|
wxString url = file_url_from_path(filepath);
|
||||||
wxString url = wxFileSystem::FileNameToURL(fn);
|
|
||||||
m_web_view->LoadURL(url);
|
m_web_view->LoadURL(url);
|
||||||
|
|
||||||
auto sizer = new wxBoxSizer(wxVERTICAL);
|
auto sizer = new wxBoxSizer(wxVERTICAL);
|
||||||
|
|||||||
@@ -48,20 +48,19 @@ constexpr char ORCA_BRIDGE_JS[] = R"JS(
|
|||||||
|
|
||||||
wxString bootstrap_url()
|
wxString bootstrap_url()
|
||||||
{
|
{
|
||||||
return wxString("file://") + from_u8((boost::filesystem::path(resources_dir()) / BOOTSTRAP_PAGE).make_preferred().string());
|
return file_url_from_path(boost::filesystem::path(resources_dir()) / BOOTSTRAP_PAGE);
|
||||||
}
|
}
|
||||||
|
|
||||||
wxString content_base_url()
|
wxString content_base_url()
|
||||||
{
|
{
|
||||||
const std::string dir = (boost::filesystem::path(resources_dir()) / "web").make_preferred().string();
|
return file_url_from_path(boost::filesystem::path(resources_dir()) / "web") + "/";
|
||||||
return wxString("file://") + from_u8(dir) + "/";
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool is_content_url(const wxString& url)
|
bool is_content_url(const wxString& url)
|
||||||
{
|
{
|
||||||
// The web view reports the URL it parsed, which escapes anything the resources path holds
|
// The web view reports the URL it parsed, which may escape the resources path differently
|
||||||
// (a space, a non-ASCII character), while content_base_url() is the raw path.
|
// from content_base_url().
|
||||||
return wxURI::Unescape(url.BeforeFirst('#')) == content_base_url();
|
return wxURI::Unescape(url.BeforeFirst('#')) == wxURI::Unescape(content_base_url());
|
||||||
}
|
}
|
||||||
|
|
||||||
const char* orca_bridge_script() { return ORCA_BRIDGE_JS; }
|
const char* orca_bridge_script() { return ORCA_BRIDGE_JS; }
|
||||||
|
|||||||
@@ -192,7 +192,7 @@ bool WebViewHostDialog::create_webview(const std::string& resource_path,
|
|||||||
|
|
||||||
wxString WebViewHostDialog::build_resource_url(const std::string& resource_path) const
|
wxString WebViewHostDialog::build_resource_url(const std::string& resource_path) const
|
||||||
{
|
{
|
||||||
wxString target_url = from_u8((boost::filesystem::path(resources_dir()) / resource_path).make_preferred().string());
|
wxString target_url = file_url_from_path(boost::filesystem::path(resources_dir()) / resource_path);
|
||||||
|
|
||||||
if (append_language_to_url()) {
|
if (append_language_to_url()) {
|
||||||
const wxString lang = wxGetApp().current_language_code_safe();
|
const wxString lang = wxGetApp().current_language_code_safe();
|
||||||
@@ -202,7 +202,7 @@ wxString WebViewHostDialog::build_resource_url(const std::string& resource_path)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return wxString("file://") + target_url;
|
return target_url;
|
||||||
}
|
}
|
||||||
|
|
||||||
void WebViewHostDialog::load_url(const wxString& url)
|
void WebViewHostDialog::load_url(const wxString& url)
|
||||||
|
|||||||
@@ -464,7 +464,7 @@ WipingDialog::WipingDialog(wxWindow* parent, const int max_flush_volume) :
|
|||||||
wxString filepath_str = from_path(filepath);
|
wxString filepath_str = from_path(filepath);
|
||||||
wxFileName fn(filepath_str);
|
wxFileName fn(filepath_str);
|
||||||
if(fn.FileExists()) {
|
if(fn.FileExists()) {
|
||||||
wxString url = wxFileSystem::FileNameToURL(fn);
|
wxString url = file_url_from_path(filepath);
|
||||||
BOOST_LOG_TRIVIAL(debug) << __FUNCTION__<< "File exists and load url " << url.ToStdString();
|
BOOST_LOG_TRIVIAL(debug) << __FUNCTION__<< "File exists and load url " << url.ToStdString();
|
||||||
m_webview->LoadURL(url);
|
m_webview->LoadURL(url);
|
||||||
BOOST_LOG_TRIVIAL(debug) << __FUNCTION__<< "Successfully loaded url: " << url.ToStdString();
|
BOOST_LOG_TRIVIAL(debug) << __FUNCTION__<< "Successfully loaded url: " << url.ToStdString();
|
||||||
|
|||||||
@@ -274,13 +274,4 @@ bool Duet::start_print(wxString &msg, const std::string &filename, ConnectionTyp
|
|||||||
return res;
|
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);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -40,7 +40,6 @@ private:
|
|||||||
ConnectionType connect(wxString &msg) const;
|
ConnectionType connect(wxString &msg) const;
|
||||||
void disconnect(ConnectionType connectionType) const;
|
void disconnect(ConnectionType connectionType) const;
|
||||||
bool start_print(wxString &msg, const std::string &filename, ConnectionType connectionType, bool simulationMode) 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;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -146,15 +146,6 @@ bool ESP3D::start_print(wxString& msg, const std::string& filename) const
|
|||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
int ESP3D::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);
|
|
||||||
}
|
|
||||||
|
|
||||||
// ESP3D only accepts 8.3 filenames else it crashes marlin and other undefined behaviour
|
// ESP3D only accepts 8.3 filenames else it crashes marlin and other undefined behaviour
|
||||||
std::string ESP3D::get_short_name(const std::string& filename) const
|
std::string ESP3D::get_short_name(const std::string& filename) const
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -33,7 +33,6 @@ private:
|
|||||||
std::string m_console_port;
|
std::string m_console_port;
|
||||||
|
|
||||||
bool start_print(wxString& msg, 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;
|
|
||||||
std::string get_short_name(const std::string& filename) const;
|
std::string get_short_name(const std::string& filename) const;
|
||||||
std::string format_command(const std::string& path, const std::string& arg, const std::string& val) const;
|
std::string format_command(const std::string& path, const std::string& arg, const std::string& val) const;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -340,9 +340,7 @@ namespace Slic3r {
|
|||||||
if (classify_printer_model(config->opt_string("printer_model")) != ElegooPrinterType::CC2)
|
if (classify_printer_model(config->opt_string("printer_model")) != ElegooPrinterType::CC2)
|
||||||
return fallback_webui;
|
return fallback_webui;
|
||||||
|
|
||||||
std::string web_path = resources_dir() + "/web/elegoolink/lan_service_web/index.html";
|
std::string web_path = GUI::into_u8(GUI::file_url_from_path(boost::filesystem::path(resources_dir()) / "web/elegoolink/lan_service_web/index.html"));
|
||||||
std::replace(web_path.begin(), web_path.end(), '\\', '/');
|
|
||||||
web_path = "file://" + web_path;
|
|
||||||
const std::string token = get_cc2_token(config->opt_string("printhost_apikey"));
|
const std::string token = get_cc2_token(config->opt_string("printhost_apikey"));
|
||||||
const std::string host_ip = Http::get_host_header_value(host);
|
const std::string host_ip = Http::get_host_header_value(host);
|
||||||
|
|
||||||
|
|||||||
@@ -510,12 +510,22 @@ bool Flashforge::fetch_material_slots(std::vector<FlashforgeMaterialSlot>& slots
|
|||||||
if (!request_local_api_json("detail", json{{"serialNumber", m_serial_number}, {"checkCode", m_check_code}}.dump(), body, msg))
|
if (!request_local_api_json("detail", json{{"serialNumber", m_serial_number}, {"checkCode", m_check_code}}.dump(), body, msg))
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
const auto parsed = json::parse(body, nullptr, false, true);
|
if (!parse_material_slots(body, slots, supports_material_station)) {
|
||||||
if (parsed.is_discarded()) {
|
|
||||||
msg = _(L("Flashforge returned an invalid JSON response."));
|
msg = _(L("Flashforge returned an invalid JSON response."));
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Flashforge::parse_material_slots(const std::string& body, std::vector<FlashforgeMaterialSlot>& slots, bool* supports_material_station)
|
||||||
|
{
|
||||||
|
slots.clear();
|
||||||
|
|
||||||
|
const auto parsed = json::parse(body, nullptr, false, true);
|
||||||
|
if (parsed.is_discarded())
|
||||||
|
return false;
|
||||||
|
|
||||||
const auto& detail = parsed.contains("detail") ? parsed["detail"] : parsed;
|
const auto& detail = parsed.contains("detail") ? parsed["detail"] : parsed;
|
||||||
const auto& station = detail.contains("matlStationInfo") ? detail["matlStationInfo"] :
|
const auto& station = detail.contains("matlStationInfo") ? detail["matlStationInfo"] :
|
||||||
detail.contains("MatlStationInfo") ? detail["MatlStationInfo"] : json();
|
detail.contains("MatlStationInfo") ? detail["MatlStationInfo"] : json();
|
||||||
@@ -542,12 +552,21 @@ bool Flashforge::fetch_material_slots(std::vector<FlashforgeMaterialSlot>& slots
|
|||||||
if (supports_material_station != nullptr)
|
if (supports_material_station != nullptr)
|
||||||
*supports_material_station = reports_material_station;
|
*supports_material_station = reports_material_station;
|
||||||
|
|
||||||
|
// Fields are read leniently: firmware may send numbers as strings or flags as numbers.
|
||||||
for (const auto& slot : slot_infos) {
|
for (const auto& slot : slot_infos) {
|
||||||
|
if (!slot.is_object())
|
||||||
|
continue;
|
||||||
FlashforgeMaterialSlot info;
|
FlashforgeMaterialSlot info;
|
||||||
info.slot_id = slot.value("slotId", static_cast<int>(slots.size()) + 1);
|
info.slot_id = static_cast<int>(slots.size()) + 1;
|
||||||
info.has_filament = slot.value("hasFilament", false);
|
if (const auto it = slot.find("slotId"); it != slot.end())
|
||||||
info.material_name = slot.value("materialName", std::string());
|
try_parse_json_int(*it, info.slot_id);
|
||||||
info.material_color = slot.value("materialColor", std::string());
|
int has_filament = 0;
|
||||||
|
if (const auto it = slot.find("hasFilament"); it != slot.end() && try_parse_json_int(*it, has_filament))
|
||||||
|
info.has_filament = has_filament != 0;
|
||||||
|
if (const auto it = slot.find("materialName"); it != slot.end() && it->is_string())
|
||||||
|
info.material_name = it->get<std::string>();
|
||||||
|
if (const auto it = slot.find("materialColor"); it != slot.end() && it->is_string())
|
||||||
|
info.material_color = it->get<std::string>();
|
||||||
slots.emplace_back(std::move(info));
|
slots.emplace_back(std::move(info));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -670,13 +689,4 @@ std::string Flashforge::extract_host_name() const
|
|||||||
return out;
|
return out;
|
||||||
}
|
}
|
||||||
|
|
||||||
int Flashforge::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);
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace Slic3r
|
} // namespace Slic3r
|
||||||
|
|||||||
@@ -45,6 +45,8 @@ public:
|
|||||||
PrintHostPostUploadActions get_post_upload_actions() const override { return PrintHostPostUploadAction::StartPrint; }
|
PrintHostPostUploadActions get_post_upload_actions() const override { return PrintHostPostUploadAction::StartPrint; }
|
||||||
std::string get_host() const override { return m_host; }
|
std::string get_host() const override { return m_host; }
|
||||||
bool fetch_material_slots(std::vector<FlashforgeMaterialSlot>& slots, bool* supports_material_station, wxString& msg) const;
|
bool fetch_material_slots(std::vector<FlashforgeMaterialSlot>& slots, bool* supports_material_station, wxString& msg) const;
|
||||||
|
// Parses a local API "detail" reply. Returns false when the body is not valid JSON.
|
||||||
|
static bool parse_material_slots(const std::string& body, std::vector<FlashforgeMaterialSlot>& slots, bool* supports_material_station);
|
||||||
static bool discover_printers(std::vector<FlashforgeDiscoveredPrinter>& printers, wxString& msg, int timeout_ms = 10000, int idle_timeout_ms = 1500, int max_retries = 3);
|
static bool discover_printers(std::vector<FlashforgeDiscoveredPrinter>& printers, wxString& msg, int timeout_ms = 10000, int idle_timeout_ms = 1500, int max_retries = 3);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
@@ -68,7 +70,6 @@ private:
|
|||||||
bool request_local_api_json(const std::string& path, const std::string& body, std::string& response_body, wxString& error_msg) const;
|
bool request_local_api_json(const std::string& path, const std::string& body, std::string& response_body, wxString& error_msg) const;
|
||||||
std::string make_http_url(const std::string& path) const;
|
std::string make_http_url(const std::string& path) const;
|
||||||
std::string extract_host_name() const;
|
std::string extract_host_name() const;
|
||||||
int get_err_code_from_body(const std::string &body) const;
|
|
||||||
bool connect(wxString& msg) const;
|
bool connect(wxString& msg) const;
|
||||||
bool start_print(wxString& msg, const std::string& filename) const;
|
bool start_print(wxString& msg, const std::string& filename) const;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -141,13 +141,4 @@ bool MKS::start_print(wxString& msg, const std::string& filename) const
|
|||||||
return ret;
|
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
|
} // Slic3r
|
||||||
|
|||||||
@@ -34,7 +34,6 @@ private:
|
|||||||
|
|
||||||
std::string get_upload_url(const std::string& filename) const;
|
std::string get_upload_url(const std::string& filename) const;
|
||||||
bool start_print(wxString& msg, 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;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,10 +3,13 @@
|
|||||||
#include <vector>
|
#include <vector>
|
||||||
#include <thread>
|
#include <thread>
|
||||||
#include <exception>
|
#include <exception>
|
||||||
|
#include <sstream>
|
||||||
#include <boost/optional.hpp>
|
#include <boost/optional.hpp>
|
||||||
#include <boost/log/trivial.hpp>
|
#include <boost/log/trivial.hpp>
|
||||||
#include <boost/filesystem.hpp>
|
#include <boost/filesystem.hpp>
|
||||||
#include <nlohmann/json.hpp>
|
#include <nlohmann/json.hpp>
|
||||||
|
#include <boost/property_tree/ptree.hpp>
|
||||||
|
#include <boost/property_tree/json_parser.hpp>
|
||||||
|
|
||||||
#include <wx/string.h>
|
#include <wx/string.h>
|
||||||
#include <wx/app.h>
|
#include <wx/app.h>
|
||||||
@@ -172,6 +175,20 @@ std::string moonraker_error_reason(const std::string &body)
|
|||||||
|
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
|
int PrintHost::get_err_code_from_body(const std::string &body)
|
||||||
|
{
|
||||||
|
boost::property_tree::ptree root;
|
||||||
|
std::istringstream iss(body);
|
||||||
|
try {
|
||||||
|
boost::property_tree::read_json(iss, root);
|
||||||
|
} catch (const std::exception &ex) {
|
||||||
|
BOOST_LOG_TRIVIAL(error) << "PrintHost: response is not valid JSON: " << ex.what();
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
return root.get<int>("err", 0);
|
||||||
|
}
|
||||||
|
|
||||||
wxString PrintHost::format_error(const std::string &body, const std::string &error, unsigned status) const
|
wxString PrintHost::format_error(const std::string &body, const std::string &error, unsigned status) const
|
||||||
{
|
{
|
||||||
if (status != 0) {
|
if (status != 0) {
|
||||||
@@ -415,12 +432,10 @@ void PrintHostJobQueue::priv::remove_source()
|
|||||||
source_to_remove.clear();
|
source_to_remove.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
void PrintHostJobQueue::priv::perform_job(PrintHostJob the_job)
|
bool PrintHostJobQueue::upload_job(PrintHostJob &job, PrintHost::ProgressFn progress_fn, PrintHost::ErrorFn error_fn, PrintHost::InfoFn info_fn)
|
||||||
{
|
{
|
||||||
emit_progress(0); // Indicate the upload is starting
|
|
||||||
|
|
||||||
// Captured before upload_data is moved into upload() below.
|
// Captured before upload_data is moved into upload() below.
|
||||||
const std::string upload_filename = the_job.upload_data.source_path.filename().string();
|
const std::string upload_filename = job.upload_data.source_path.filename().string();
|
||||||
|
|
||||||
{
|
{
|
||||||
LifecycleEventContext ctx;
|
LifecycleEventContext ctx;
|
||||||
@@ -429,19 +444,37 @@ void PrintHostJobQueue::priv::perform_job(PrintHostJob the_job)
|
|||||||
fire_lifecycle_event(LifecycleEvent::UploadStarted, ctx);
|
fire_lifecycle_event(LifecycleEvent::UploadStarted, ctx);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool success = the_job.printhost->upload(std::move(the_job.upload_data),
|
bool success = false;
|
||||||
[this](Http::Progress progress, bool &cancel) { this->progress_fn(std::move(progress), cancel); },
|
std::string error;
|
||||||
[this](wxString error) { this->error_fn(std::move(error)); },
|
// A throwing upload must not stop the worker, or later jobs would stay queued forever.
|
||||||
[this](wxString tag, wxString host) { this->info_fn(std::move(tag), std::move(host)); }
|
try {
|
||||||
);
|
success = job.printhost->upload(std::move(job.upload_data), std::move(progress_fn), error_fn, std::move(info_fn));
|
||||||
|
} catch (const std::exception &e) {
|
||||||
|
error = e.what();
|
||||||
|
error_fn(error);
|
||||||
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
LifecycleEventContext ctx;
|
LifecycleEventContext ctx;
|
||||||
ctx.name = upload_filename;
|
ctx.name = upload_filename;
|
||||||
ctx.code = success ? LifecycleEvtCode::Ok : LifecycleEvtCode::Error;
|
ctx.code = success ? LifecycleEvtCode::Ok : LifecycleEvtCode::Error;
|
||||||
|
ctx.msg = error;
|
||||||
fire_lifecycle_event(LifecycleEvent::UploadFinished, ctx);
|
fire_lifecycle_event(LifecycleEvent::UploadFinished, ctx);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return success;
|
||||||
|
}
|
||||||
|
|
||||||
|
void PrintHostJobQueue::priv::perform_job(PrintHostJob the_job)
|
||||||
|
{
|
||||||
|
emit_progress(0); // Indicate the upload is starting
|
||||||
|
|
||||||
|
bool success = PrintHostJobQueue::upload_job(the_job,
|
||||||
|
[this](Http::Progress progress, bool &cancel) { this->progress_fn(std::move(progress), cancel); },
|
||||||
|
[this](wxString error) { this->error_fn(std::move(error)); },
|
||||||
|
[this](wxString tag, wxString host) { this->info_fn(std::move(tag), std::move(host)); }
|
||||||
|
);
|
||||||
|
|
||||||
if (success) {
|
if (success) {
|
||||||
emit_progress(100);
|
emit_progress(100);
|
||||||
if (the_job.switch_to_device_tab) {
|
if (the_job.switch_to_device_tab) {
|
||||||
|
|||||||
@@ -87,6 +87,8 @@ public:
|
|||||||
|
|
||||||
static PrintHost* get_print_host(DynamicPrintConfig *config);
|
static PrintHost* get_print_host(DynamicPrintConfig *config);
|
||||||
static std::string get_print_host_webui(DynamicPrintConfig *config);
|
static std::string get_print_host_webui(DynamicPrintConfig *config);
|
||||||
|
// Reads the "err" field of a JSON reply, 0 when absent. Returns -1 when the body is not valid JSON.
|
||||||
|
static int get_err_code_from_body(const std::string &body);
|
||||||
|
|
||||||
//Support for cloud webui login
|
//Support for cloud webui login
|
||||||
virtual bool is_cloud() const { return false; }
|
virtual bool is_cloud() const { return false; }
|
||||||
@@ -150,6 +152,10 @@ public:
|
|||||||
void enqueue(PrintHostJob job);
|
void enqueue(PrintHostJob job);
|
||||||
void cancel(size_t id);
|
void cancel(size_t id);
|
||||||
|
|
||||||
|
// Uploads the job, firing UploadStarted and a matching UploadFinished. An exception thrown by
|
||||||
|
// the upload is reported through error_fn and makes the upload fail.
|
||||||
|
static bool upload_job(PrintHostJob &job, PrintHost::ProgressFn progress_fn, PrintHost::ErrorFn error_fn, PrintHost::InfoFn info_fn);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
struct priv;
|
struct priv;
|
||||||
std::shared_ptr<priv> p;
|
std::shared_ptr<priv> p;
|
||||||
|
|||||||
@@ -654,13 +654,4 @@ bool UltiMaker::start_print(wxString &msg, const std::string &filename, Connecti
|
|||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
int UltiMaker::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);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -64,7 +64,6 @@ private:
|
|||||||
void set_auth(Http& http) const;
|
void set_auth(Http& http) const;
|
||||||
void disconnect(ConnectionType connectionType) const;
|
void disconnect(ConnectionType connectionType) const;
|
||||||
bool start_print(wxString &msg, const std::string &filename, ConnectionType connectionType) const;
|
bool start_print(wxString &msg, const std::string &filename, ConnectionType connectionType) const;
|
||||||
int get_err_code_from_body(const std::string &body) const;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -27,6 +27,8 @@ add_executable(${_TEST_NAME}_tests
|
|||||||
test_plugin_cloud_metadata.cpp
|
test_plugin_cloud_metadata.cpp
|
||||||
test_plugin_audit.cpp
|
test_plugin_audit.cpp
|
||||||
test_shortcuts.cpp
|
test_shortcuts.cpp
|
||||||
|
test_file_url.cpp
|
||||||
|
test_user_manager.cpp
|
||||||
../fff_print/test_helpers.cpp
|
../fff_print/test_helpers.cpp
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,74 @@
|
|||||||
|
#include <catch2/catch_test_macros.hpp>
|
||||||
|
#include <catch2/generators/catch_generators.hpp>
|
||||||
|
|
||||||
|
#include "slic3r/GUI/GUI.hpp"
|
||||||
|
|
||||||
|
#include <boost/filesystem/path.hpp>
|
||||||
|
|
||||||
|
#include <wx/uri.h>
|
||||||
|
|
||||||
|
using namespace Slic3r::GUI;
|
||||||
|
|
||||||
|
#ifndef _WIN32
|
||||||
|
TEST_CASE("A file URL keeps characters special to URLs in its path", "[FileUrl]")
|
||||||
|
{
|
||||||
|
const std::string path = GENERATE(as<std::string>{},
|
||||||
|
"/opt/test#dir/resources/web/homepage/index.html",
|
||||||
|
"/opt/test%20x/resources/web/homepage/index.html",
|
||||||
|
"/opt/Orca Slicer/resources/web/homepage/index.html",
|
||||||
|
"/opt/what?/resources/web/homepage/index.html",
|
||||||
|
"/home/Jos\xC3\xA9/\xE8\xB5\x84\xE6\xBA\x90/resources/web/homepage/index.html");
|
||||||
|
|
||||||
|
const wxString url = file_url_from_path(boost::filesystem::path(path));
|
||||||
|
CAPTURE(path, url.utf8_string());
|
||||||
|
|
||||||
|
// WebView::CreateWebView() and WebView::LoadUrl() re-parse the URL before loading it.
|
||||||
|
const wxURI uri(wxURI(url).BuildURI());
|
||||||
|
CHECK(uri.GetScheme() == "file");
|
||||||
|
CHECK(!uri.HasQuery());
|
||||||
|
CHECK(!uri.HasFragment());
|
||||||
|
CHECK(wxURI::Unescape(uri.GetPath()).utf8_string() == path);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("A file URL of a plain path is the path behind file://", "[FileUrl]")
|
||||||
|
{
|
||||||
|
const std::string path = "/opt/OrcaSlicer/resources/web/homepage/index.html";
|
||||||
|
CHECK(file_url_from_path(boost::filesystem::path(path)) == "file://" + path);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("A query appended to a file URL stays separate from its path", "[FileUrl]")
|
||||||
|
{
|
||||||
|
const std::string path = "/opt/test#dir%20x/resources/web/guide/0/index.html";
|
||||||
|
const wxURI uri(file_url_from_path(boost::filesystem::path(path)) + "?target=21&lang=de");
|
||||||
|
CHECK(wxURI::Unescape(uri.GetPath()).utf8_string() == path);
|
||||||
|
CHECK(uri.GetQuery() == "target=21&lang=de");
|
||||||
|
CHECK(!uri.HasFragment());
|
||||||
|
}
|
||||||
|
#else
|
||||||
|
TEST_CASE("A file URL of a Windows path has a drive letter and forward slashes", "[FileUrl]")
|
||||||
|
{
|
||||||
|
const auto [path, url] = GENERATE(table<std::wstring, std::string>({
|
||||||
|
{ L"C:\\Program Files\\OrcaSlicer\\resources\\web\\homepage\\index.html",
|
||||||
|
"file:///C:/Program%20Files/OrcaSlicer/resources/web/homepage/index.html" },
|
||||||
|
{ L"D:\\#OneDrive\\OrcaSlicer\\resources\\web\\guide\\0\\index.html",
|
||||||
|
"file:///D:/%23OneDrive/OrcaSlicer/resources/web/guide/0/index.html" },
|
||||||
|
{ L"D:\\100%\\OrcaSlicer\\resources\\web\\homepage\\index.html",
|
||||||
|
"file:///D:/100%25/OrcaSlicer/resources/web/homepage/index.html" },
|
||||||
|
// Callers join the resources directory with a forward-slash relative path.
|
||||||
|
{ L"D:\\#OneDrive\\OrcaSlicer\\resources/web/homepage/index.html",
|
||||||
|
"file:///D:/%23OneDrive/OrcaSlicer/resources/web/homepage/index.html" },
|
||||||
|
{ L"\\\\server\\share\\OrcaSlicer\\resources\\web\\homepage\\index.html",
|
||||||
|
"file://server/share/OrcaSlicer/resources/web/homepage/index.html" },
|
||||||
|
}));
|
||||||
|
CHECK(file_url_from_path(boost::filesystem::path(path)).utf8_string() == url);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("A query appended to a Windows file URL stays separate from its path", "[FileUrl]")
|
||||||
|
{
|
||||||
|
const wxURI uri(file_url_from_path(boost::filesystem::path(L"D:\\#OneDrive\\OrcaSlicer\\resources\\web\\guide\\0\\index.html")) +
|
||||||
|
"?target=21&lang=de");
|
||||||
|
CHECK(wxURI::Unescape(uri.GetPath()) == "/D:/#OneDrive/OrcaSlicer/resources/web/guide/0/index.html");
|
||||||
|
CHECK(uri.GetQuery() == "target=21&lang=de");
|
||||||
|
CHECK(!uri.HasFragment());
|
||||||
|
}
|
||||||
|
#endif
|
||||||
@@ -16,6 +16,8 @@
|
|||||||
|
|
||||||
#include <string>
|
#include <string>
|
||||||
|
|
||||||
|
#include <wx/uri.h>
|
||||||
|
|
||||||
namespace py = pybind11;
|
namespace py = pybind11;
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
@@ -210,23 +212,33 @@ TEST_CASE("A reloaded plugin page is recognised by its base URL, fragment aside"
|
|||||||
{
|
{
|
||||||
using namespace Slic3r::GUI::web_hosting;
|
using namespace Slic3r::GUI::web_hosting;
|
||||||
|
|
||||||
// A resources path holding a space, which the web view reports escaped.
|
// A resources path holding a space, which the web view may report escaped differently.
|
||||||
const Slic3r::ScopedResourcesDir resources("web content check");
|
const Slic3r::ScopedResourcesDir resources("web content check");
|
||||||
|
|
||||||
// The swapped-in page, then after an in-page anchor and a reload.
|
// The swapped-in page, then after an in-page anchor and a reload.
|
||||||
CHECK(is_content_url(content_base_url()));
|
CHECK(is_content_url(content_base_url()));
|
||||||
CHECK(is_content_url(content_base_url() + "#tab2"));
|
CHECK(is_content_url(content_base_url() + "#tab2"));
|
||||||
wxString escaped = content_base_url();
|
const wxString unescaped = wxURI::Unescape(content_base_url());
|
||||||
escaped.Replace(" ", "%20");
|
REQUIRE(unescaped != content_base_url());
|
||||||
REQUIRE(escaped != content_base_url());
|
CHECK(is_content_url(unescaped));
|
||||||
CHECK(is_content_url(escaped));
|
CHECK(is_content_url(unescaped + "#tab2"));
|
||||||
CHECK(is_content_url(escaped + "#tab2"));
|
|
||||||
// A page the plugin linked to keeps its own URL and must be left alone.
|
// A page the plugin linked to keeps its own URL and must be left alone.
|
||||||
CHECK_FALSE(is_content_url(content_base_url() + "guide.html"));
|
CHECK_FALSE(is_content_url(content_base_url() + "guide.html"));
|
||||||
CHECK_FALSE(is_content_url("https://example.com/"));
|
CHECK_FALSE(is_content_url("https://example.com/"));
|
||||||
CHECK_FALSE(is_content_url(""));
|
CHECK_FALSE(is_content_url(""));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TEST_CASE("A reloaded plugin page is recognised when the resources path holds a '#'", "[PluginHost]")
|
||||||
|
{
|
||||||
|
using namespace Slic3r::GUI::web_hosting;
|
||||||
|
|
||||||
|
const Slic3r::ScopedResourcesDir resources("web#content check");
|
||||||
|
|
||||||
|
CHECK(is_content_url(content_base_url()));
|
||||||
|
CHECK(is_content_url(content_base_url() + "#tab2"));
|
||||||
|
CHECK_FALSE(is_content_url(content_base_url() + "guide.html"));
|
||||||
|
}
|
||||||
|
|
||||||
TEST_CASE("Plugin host API exposes model geometry and structure to Python", "[PluginHost][Python]")
|
TEST_CASE("Plugin host API exposes model geometry and structure to Python", "[PluginHost][Python]")
|
||||||
{
|
{
|
||||||
using Catch::Matchers::WithinAbs;
|
using Catch::Matchers::WithinAbs;
|
||||||
|
|||||||
@@ -1,8 +1,15 @@
|
|||||||
#include <catch2/catch_all.hpp>
|
#include <catch2/catch_all.hpp>
|
||||||
|
|
||||||
|
#include <memory>
|
||||||
|
#include <stdexcept>
|
||||||
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
#include <nlohmann/json.hpp>
|
#include <nlohmann/json.hpp>
|
||||||
|
|
||||||
|
#include "libslic3r/LifecycleEvents.hpp"
|
||||||
#include "slic3r/Utils/PrintHost.hpp"
|
#include "slic3r/Utils/PrintHost.hpp"
|
||||||
|
#include "slic3r/Utils/Flashforge.hpp"
|
||||||
|
|
||||||
using namespace Slic3r;
|
using namespace Slic3r;
|
||||||
|
|
||||||
@@ -24,6 +31,41 @@ public:
|
|||||||
std::string get_host() const override { return {}; }
|
std::string get_host() const override { return {}; }
|
||||||
};
|
};
|
||||||
|
|
||||||
|
class ThrowingPrintHost : public TestPrintHost
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
bool upload(PrintHostUpload, ProgressFn, ErrorFn, InfoFn) const override { throw std::runtime_error("reply could not be read"); }
|
||||||
|
};
|
||||||
|
|
||||||
|
struct UploadEvents
|
||||||
|
{
|
||||||
|
std::vector<LifecycleEvent> events;
|
||||||
|
std::vector<LifecycleEvtCode> codes;
|
||||||
|
std::vector<std::string> errors;
|
||||||
|
bool uploaded{false};
|
||||||
|
|
||||||
|
explicit UploadEvents(std::unique_ptr<PrintHost> host)
|
||||||
|
{
|
||||||
|
set_lifecycle_hook_fn([this](LifecycleEvent event, const LifecycleEventContext& ctx) {
|
||||||
|
events.push_back(event);
|
||||||
|
codes.push_back(ctx.code);
|
||||||
|
});
|
||||||
|
|
||||||
|
PrintHostJob job;
|
||||||
|
job.printhost = std::move(host);
|
||||||
|
job.upload_data.source_path = "plate.gcode";
|
||||||
|
try {
|
||||||
|
uploaded = PrintHostJobQueue::upload_job(job, [](Http::Progress, bool&) {},
|
||||||
|
[this](wxString error) { errors.push_back(error.ToStdString()); },
|
||||||
|
[](wxString, wxString) {});
|
||||||
|
} catch (...) {
|
||||||
|
set_lifecycle_hook_fn(nullptr);
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
set_lifecycle_hook_fn(nullptr);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
std::string format_error(const std::string& body, const std::string& error, unsigned status)
|
std::string format_error(const std::string& body, const std::string& error, unsigned status)
|
||||||
{
|
{
|
||||||
return TestPrintHost().format_error(body, error, status).ToStdString();
|
return TestPrintHost().format_error(body, error, status).ToStdString();
|
||||||
@@ -46,6 +88,14 @@ std::string moonraker_error(int code, const std::string& message, const std::str
|
|||||||
constexpr const char* k_busy_file_403 =
|
constexpr const char* k_busy_file_403 =
|
||||||
R"JSON({"error": {"code": 403, "message": "Forbidden", "traceback": "Traceback (most recent call last):\n\n File \"/home/lava/moonraker/moonraker/components/file_manager/file_manager.py\", line 1017, in _finish_gcode_upload\n can_start = self._handle_operation_check(check_path)\n ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n\nmoonraker.utils.exceptions.ServerError: File currently in use\n\nDuring handling of the above exception, another exception occurred:\n\nTraceback (most recent call last):\n\n File \"/home/lava/moonraker/moonraker/components/application.py\", line 1069, in post\n raise tornado.web.HTTPError(\ntornado.web.HTTPError: HTTP 403: Forbidden (File is loaded, upload not permitted)\n"}})JSON";
|
R"JSON({"error": {"code": 403, "message": "Forbidden", "traceback": "Traceback (most recent call last):\n\n File \"/home/lava/moonraker/moonraker/components/file_manager/file_manager.py\", line 1017, in _finish_gcode_upload\n can_start = self._handle_operation_check(check_path)\n ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n\nmoonraker.utils.exceptions.ServerError: File currently in use\n\nDuring handling of the above exception, another exception occurred:\n\nTraceback (most recent call last):\n\n File \"/home/lava/moonraker/moonraker/components/application.py\", line 1069, in post\n raise tornado.web.HTTPError(\ntornado.web.HTTPError: HTTP 403: Forbidden (File is loaded, upload not permitted)\n"}})JSON";
|
||||||
|
|
||||||
|
// Replies a print host can send instead of JSON: a proxy or login page, nothing, a cut-off body.
|
||||||
|
const std::vector<std::string> non_json_replies = {
|
||||||
|
"<html><body>proxy login required</body></html>",
|
||||||
|
"",
|
||||||
|
"{\"err\":",
|
||||||
|
"{\"detail\":{\"matlStationInfo\":{\"slotInfos\":[{\"slotId\":1,",
|
||||||
|
};
|
||||||
|
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
TEST_CASE("A Klipper upload error shows its reason instead of a Python traceback", "[PrintHost][Regression]")
|
TEST_CASE("A Klipper upload error shows its reason instead of a Python traceback", "[PrintHost][Regression]")
|
||||||
@@ -211,3 +261,114 @@ TEST_CASE("Error bodies that are not a Moonraker envelope are left unchanged", "
|
|||||||
CHECK(format_error("", "curl:Could not connect", 0) == "curl:Could not connect");
|
CHECK(format_error("", "curl:Could not connect", 0) == "curl:Could not connect");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TEST_CASE("Print host error code is read from a JSON reply", "[PrintHost]")
|
||||||
|
{
|
||||||
|
CHECK(PrintHost::get_err_code_from_body(R"({"err":0})") == 0);
|
||||||
|
CHECK(PrintHost::get_err_code_from_body(R"({"err":2})") == 2);
|
||||||
|
CHECK(PrintHost::get_err_code_from_body(R"({"status":"ok"})") == 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("Print host error code reports a reply that is not JSON as an error", "[PrintHost]")
|
||||||
|
{
|
||||||
|
const std::string body = GENERATE(from_range(non_json_replies));
|
||||||
|
int err = 0;
|
||||||
|
REQUIRE_NOTHROW(err = PrintHost::get_err_code_from_body(body));
|
||||||
|
CHECK(err != 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("Print host error code tolerates a wrongly typed err field", "[PrintHost]")
|
||||||
|
{
|
||||||
|
const std::string body = GENERATE(as<std::string>{}, R"({"err":"busy"})", R"({"err":{"code":1}})", R"([1,2])");
|
||||||
|
CHECK_NOTHROW(PrintHost::get_err_code_from_body(body));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("Flashforge material slots are read from a well-formed reply", "[PrintHost][Flashforge]")
|
||||||
|
{
|
||||||
|
const std::string body = R"({"code":0,"detail":{"hasMatlStation":true,"matlStationInfo":{"slotCnt":2,"slotInfos":[
|
||||||
|
{"slotId":1,"hasFilament":true,"materialName":"PLA","materialColor":"#FFFFFF"},
|
||||||
|
{"slotId":2,"hasFilament":false,"materialName":"","materialColor":""}]}}})";
|
||||||
|
|
||||||
|
std::vector<FlashforgeMaterialSlot> slots;
|
||||||
|
bool supports_station = false;
|
||||||
|
REQUIRE(Flashforge::parse_material_slots(body, slots, &supports_station));
|
||||||
|
CHECK(supports_station);
|
||||||
|
REQUIRE(slots.size() == 2);
|
||||||
|
CHECK(slots[0].slot_id == 1);
|
||||||
|
CHECK(slots[0].has_filament);
|
||||||
|
CHECK(slots[0].material_name == "PLA");
|
||||||
|
CHECK(slots[0].material_color == "#FFFFFF");
|
||||||
|
CHECK(slots[1].slot_id == 2);
|
||||||
|
CHECK_FALSE(slots[1].has_filament);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("Flashforge material slots accept numbers as strings and flags as numbers", "[PrintHost][Flashforge]")
|
||||||
|
{
|
||||||
|
const std::string body = R"({"detail":{"matlStationInfo":{"slotInfos":[
|
||||||
|
{"slotId":"3","hasFilament":1,"materialName":null,"materialColor":7}]}}})";
|
||||||
|
|
||||||
|
std::vector<FlashforgeMaterialSlot> slots;
|
||||||
|
REQUIRE_NOTHROW(Flashforge::parse_material_slots(body, slots, nullptr));
|
||||||
|
REQUIRE(slots.size() == 1);
|
||||||
|
CHECK(slots[0].slot_id == 3);
|
||||||
|
CHECK(slots[0].has_filament);
|
||||||
|
CHECK(slots[0].material_name.empty());
|
||||||
|
CHECK(slots[0].material_color.empty());
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("Flashforge material slots skip entries that are not objects", "[PrintHost][Flashforge]")
|
||||||
|
{
|
||||||
|
const std::string body = R"({"detail":{"matlStationInfo":{"slotInfos":[5,"slot",null,[],
|
||||||
|
{"slotId":4,"hasFilament":true,"materialName":"PETG"}]}}})";
|
||||||
|
|
||||||
|
std::vector<FlashforgeMaterialSlot> slots;
|
||||||
|
REQUIRE_NOTHROW(Flashforge::parse_material_slots(body, slots, nullptr));
|
||||||
|
REQUIRE(slots.size() == 1);
|
||||||
|
CHECK(slots[0].slot_id == 4);
|
||||||
|
CHECK(slots[0].material_name == "PETG");
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("Flashforge material slots tolerate slot info that is not a list", "[PrintHost][Flashforge]")
|
||||||
|
{
|
||||||
|
const std::string body = GENERATE(as<std::string>{},
|
||||||
|
R"({"detail":{"matlStationInfo":{"slotInfos":5}}})",
|
||||||
|
R"({"detail":{"matlStationInfo":{"slotInfos":"none"}}})",
|
||||||
|
R"({"detail":{"matlStationInfo":7}})",
|
||||||
|
R"({"detail":"offline"})");
|
||||||
|
|
||||||
|
std::vector<FlashforgeMaterialSlot> slots;
|
||||||
|
bool ok = false;
|
||||||
|
REQUIRE_NOTHROW(ok = Flashforge::parse_material_slots(body, slots, nullptr));
|
||||||
|
CHECK(ok);
|
||||||
|
CHECK(slots.empty());
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("Flashforge material slots reject a reply that is not JSON", "[PrintHost][Flashforge]")
|
||||||
|
{
|
||||||
|
const std::string body = GENERATE(from_range(non_json_replies));
|
||||||
|
std::vector<FlashforgeMaterialSlot> slots;
|
||||||
|
bool ok = true;
|
||||||
|
REQUIRE_NOTHROW(ok = Flashforge::parse_material_slots(body, slots, nullptr));
|
||||||
|
CHECK_FALSE(ok);
|
||||||
|
CHECK(slots.empty());
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("An upload that throws still finishes with an error", "[PrintHost][LifecycleEvents]")
|
||||||
|
{
|
||||||
|
UploadEvents run(std::make_unique<ThrowingPrintHost>());
|
||||||
|
|
||||||
|
CHECK_FALSE(run.uploaded);
|
||||||
|
CHECK(run.errors == std::vector<std::string>{"reply could not be read"});
|
||||||
|
CHECK(run.events == std::vector<LifecycleEvent>{LifecycleEvent::UploadStarted, LifecycleEvent::UploadFinished});
|
||||||
|
CHECK(run.codes == std::vector<LifecycleEvtCode>{LifecycleEvtCode::Ok, LifecycleEvtCode::Error});
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("A successful upload finishes without an error", "[PrintHost][LifecycleEvents]")
|
||||||
|
{
|
||||||
|
UploadEvents run(std::make_unique<TestPrintHost>());
|
||||||
|
|
||||||
|
CHECK(run.uploaded);
|
||||||
|
CHECK(run.errors.empty());
|
||||||
|
CHECK(run.events == std::vector<LifecycleEvent>{LifecycleEvent::UploadStarted, LifecycleEvent::UploadFinished});
|
||||||
|
CHECK(run.codes == std::vector<LifecycleEvtCode>{LifecycleEvtCode::Ok, LifecycleEvtCode::Ok});
|
||||||
|
}
|
||||||
|
|||||||
@@ -0,0 +1,23 @@
|
|||||||
|
#include <catch2/catch_all.hpp>
|
||||||
|
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
#include "slic3r/GUI/UserManager.hpp"
|
||||||
|
|
||||||
|
using namespace Slic3r;
|
||||||
|
|
||||||
|
TEST_CASE("User message that is not JSON is rejected without throwing", "[UserManager]")
|
||||||
|
{
|
||||||
|
const std::string payload = GENERATE(as<std::string>{}, "not json", "", "<html></html>", "{\"bind\":");
|
||||||
|
UserManager manager;
|
||||||
|
int result = 0;
|
||||||
|
REQUIRE_NOTHROW(result = manager.parse_json(payload));
|
||||||
|
CHECK(result == -1);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("User message without a successful bind is ignored", "[UserManager]")
|
||||||
|
{
|
||||||
|
const std::string payload = GENERATE(as<std::string>{}, "{}", R"({"bind":{"command":"unbind"}})", R"({"bind":"bind"})", "[1]");
|
||||||
|
UserManager manager;
|
||||||
|
CHECK(manager.parse_json(payload) == -1);
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user