Merge branch 'main' into zaa

This commit is contained in:
SoftFever
2026-05-01 18:04:05 +08:00
committed by GitHub
116 changed files with 9028 additions and 3589 deletions
+10 -8
View File
@@ -285,7 +285,7 @@ void PingCodeBindDialog::on_bind_printer(wxCommandEvent& event)
}
NetworkAgent* agent = wxGetApp().getAgent();
if (agent && agent->is_user_login() && ping_code.length() == PING_CODE_LENGTH) {
if (agent && agent->is_user_login(wxGetApp().get_printer_cloud_provider()) && ping_code.length() == PING_CODE_LENGTH) {
auto result = agent->ping_bind(ping_code.ToStdString());
if(result < 0){
@@ -868,11 +868,12 @@ void BindMachineDialog::on_show(wxShowEvent &event)
m_printer_name->SetLabelText(from_u8(m_machine_info->get_dev_name()));
if (wxGetApp().is_user_login()) {
wxString username_text = from_u8(wxGetApp().getAgent()->get_user_nickname());
const std::string provider = wxGetApp().get_printer_cloud_provider();
if (wxGetApp().is_user_login(provider)) {
wxString username_text = from_u8(wxGetApp().getAgent()->get_user_nickname(provider));
m_user_name->SetLabelText(username_text);
std::string avatar_url = wxGetApp().getAgent()->get_user_avatar();
std::string avatar_url = wxGetApp().getAgent()->get_user_avatar(provider);
Slic3r::Http http = Slic3r::Http::get(avatar_url);
std::string suffix = avatar_url.substr(avatar_url.find_last_of(".") + 1);
http.header("accept", "image/" + suffix)
@@ -1017,7 +1018,7 @@ void UnBindMachineDialog::on_cancel(wxCommandEvent &event)
void UnBindMachineDialog::on_unbind_printer(wxCommandEvent &event)
{
if (!wxGetApp().is_user_login()) {
if (!wxGetApp().is_user_login(wxGetApp().get_printer_cloud_provider())) {
m_status_text->SetLabelText(_L("Please log in first."));
return;
}
@@ -1073,11 +1074,12 @@ void UnBindMachineDialog::on_show(wxShowEvent &event)
m_printer_name->SetLabelText(from_u8(m_machine_info->get_dev_name()));
if (wxGetApp().is_user_login()) {
wxString username_text = from_u8(wxGetApp().getAgent()->get_user_name());
const std::string provider = wxGetApp().get_printer_cloud_provider();
if (wxGetApp().is_user_login(provider)) {
wxString username_text = from_u8(wxGetApp().getAgent()->get_user_name(provider));
m_user_name->SetLabelText(username_text);
std::string avatar_url = wxGetApp().getAgent()->get_user_avatar();
std::string avatar_url = wxGetApp().getAgent()->get_user_avatar(provider);
Slic3r::Http http = Slic3r::Http::get(avatar_url);
std::string suffix = avatar_url.substr(avatar_url.find_last_of(".") + 1);
http.header("accept", "image/" + suffix)
+5 -4
View File
@@ -244,13 +244,14 @@ void SelectMObjectPopup::Popup(wxWindow* WXUNUSED(focus))
m_refresh_timer->Start(MACHINE_LIST_REFRESH_INTERVAL);
}
if (wxGetApp().is_user_login()) {
const std::string provider = wxGetApp().get_printer_cloud_provider();
if (wxGetApp().is_user_login(provider)) {
if (!get_print_info_thread) {
get_print_info_thread = new boost::thread(Slic3r::create_thread([this, token = std::weak_ptr<int>(m_token)] {
get_print_info_thread = new boost::thread(Slic3r::create_thread([this, token = std::weak_ptr<int>(m_token), provider] {
NetworkAgent* agent = wxGetApp().getAgent();
unsigned int http_code;
std::string body;
int result = agent->get_user_print_info(&http_code, &body);
int result = agent->get_user_print_info(&http_code, &body, provider);
wxGetApp().CallAfter([token, this, result, body]() {
if (token.expired()) {return;}
@@ -585,7 +586,7 @@ void CalibrationPanel::update_all() {
// only disconnected server in cloud mode
if (obj->connection_type() != "lan") {
if (m_agent) {
server_status = m_agent->is_server_connected() ? 0 : (int)MONITOR_DISCONNECTED_SERVER;
server_status = m_agent->is_server_connected(wxGetApp().get_printer_cloud_provider()) ? 0 : (int)MONITOR_DISCONNECTED_SERVER;
}
}
show_status((int)MONITOR_DISCONNECTED + server_status);
@@ -1694,7 +1694,7 @@ void CalibrationPresetPage::update_show_status()
MachineObject* obj_ = dev->get_selected_machine();
if (!obj_) {
if (agent->is_user_login()) {
if (agent->is_user_login(wxGetApp().get_printer_cloud_provider())) {
show_status(CaliPresetPageStatus::CaliPresetStatusInvalidPrinter);
}
else {
@@ -1704,7 +1704,7 @@ void CalibrationPresetPage::update_show_status()
}
if (!obj_->is_lan_mode_printer()) {
if (!agent->is_server_connected()) {
if (!agent->is_server_connected(wxGetApp().get_printer_cloud_provider())) {
show_status(CaliPresetPageStatus::CaliPresetStatusConnectingServer);
return;
}
+4 -5
View File
@@ -248,8 +248,7 @@ static bool delete_filament_preset_by_name(std::string delete_preset_name, std::
if (!need_delete_preset) BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format(" can't find delete preset and name: %1%") % delete_preset_name;
if (!need_delete_preset->setting_id.empty()) {
BOOST_LOG_TRIVIAL(info) << "delete preset = " << need_delete_preset->name << ", setting_id = " << need_delete_preset->setting_id;
m_presets.set_sync_info_and_save(need_delete_preset->name, need_delete_preset->setting_id, "delete", 0);
wxGetApp().delete_preset_from_cloud(need_delete_preset->setting_id);
wxGetApp().delete_preset_from_cloud(need_delete_preset->setting_id, need_delete_preset->file);
} else {
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format(" can't preset setting id is empty and name: %1%") % delete_preset_name;
}
@@ -4348,7 +4347,7 @@ void ExportConfigsDialog::data_init()
const std::deque<Preset> &filament_presets = preset_bundle.filaments.get_presets();
for (const Preset &filament_preset : filament_presets) {
if (filament_preset.is_system || filament_preset.is_default || filament_preset.is_project_embedded) continue;
if (!filament_preset.is_user()) continue;
if (filament_preset.is_compatible) {
Preset *new_filament_preset = new Preset(filament_preset);
m_filament_presets[preset_name].push_back(new_filament_preset);
@@ -4357,7 +4356,7 @@ void ExportConfigsDialog::data_init()
const std::deque<Preset> &process_presets = preset_bundle.prints.get_presets();
for (const Preset &process_preset : process_presets) {
if (process_preset.is_system || process_preset.is_default || process_preset.is_project_embedded) continue;
if (!process_preset.is_user()) continue;
if (process_preset.is_compatible) {
Preset *new_prpcess_preset = new Preset(process_preset);
m_process_presets[preset_name].push_back(new_prpcess_preset);
@@ -4371,7 +4370,7 @@ void ExportConfigsDialog::data_init()
}
const std::deque<Preset> &filament_presets = preset_bundle.filaments.get_presets();
for (const Preset &filament_preset : filament_presets) {
if (filament_preset.is_system || filament_preset.is_default) continue;
if (!filament_preset.can_overwrite()) continue;
Preset *new_filament_preset = new Preset(filament_preset);
const Preset *base_filament_preset = preset_bundle.filaments.get_preset_base(*new_filament_preset);
+85
View File
@@ -18,6 +18,28 @@ namespace Slic3r
{
class MachineObject;
/**
* DevAmsTray - Represents a single filament tray/slot in an AMS unit or virtual tray.
*
* Data Population:
* ================
* This class is used in two contexts with different population paths:
*
* 1. AMS Trays (within DevFilaSystem):
* NetworkAgent → MachineObject::parse_json() → DevFilaSystemParser::ParseV1_0()
*
* 2. Virtual Trays (MachineObject::vt_slot for external/manual filament):
* NetworkAgent → MachineObject::parse_json() → MachineObject::parse_vt_tray()
*
* Key Fields Used by build_filament_ams_list():
* - setting_id: Filament preset identifier (tray_info_idx from printer)
* - tag_uid: RFID tag unique ID for identifying filament spools
* - m_fila_type: Filament material type (e.g., "PLA", "ABS", "PETG")
* - color: Hex color string without '#' prefix (e.g., "FF0000")
* - cols: Multi-color component list for gradient/multi-color filaments
* - ctype: Color type indicator
* - is_exists: Whether filament is currently loaded in the tray
*/
class DevAmsTray
{
public:
@@ -83,6 +105,27 @@ public:
static wxColour decode_color(const std::string& color);
};
/**
* DevAms - Represents a single AMS (Automatic Material System) unit.
*
* An AMS unit is a physical hardware component that holds multiple filament trays.
* Different printer models support different AMS variants with varying slot counts
* and capabilities.
*
* Data Population:
* ================
* Populated by DevFilaSystemParser from printer JSON messages received via NetworkAgent.
*
* Key Properties:
* - m_ams_id: Unique identifier for this AMS unit (string, typically "0", "1", etc.)
* - m_ext_id: Which extruder this AMS is connected to (for multi-extruder setups)
* - m_trays: Map of tray IDs to DevAmsTray pointers containing filament data
*
* AMS Type Variants:
* - AMS (type 1): Standard 4-slot AMS with humidity control
* - AMS_LITE (type 2): Simplified version
* - N3F/N3S (types 3,4): Newer variants with different humidity/drying support
*/
class DevAms
{
friend class DevFilaSystemParser;
@@ -146,6 +189,36 @@ private:
int m_left_dry_time = 0;
};
/**
* DevFilaSystem - Central manager for all AMS-related data on a printer.
*
* This class owns and manages the hierarchy of AMS units (DevAms) and their trays (DevAmsTray).
* It provides the primary interface for querying filament/AMS state used by the GUI.
*
* Data Flow Architecture:
* =======================
* Printer Device (sends status via MQTT/LAN)
* ↓
* NetworkAgent (receives JSON, invokes registered callbacks)
* ↓
* MachineObject::parse_json() (delegates to DevFilaSystemParser)
* ↓
* DevFilaSystemParser::ParseV1_0() (populates this DevFilaSystem instance)
* ↓
* GUI functions like build_filament_ams_list() read from here
*
* Key Methods:
* - GetAmsList(): Returns map of all AMS units (ams_id -> DevAms*)
* - GetAmsTray(): Retrieves specific tray by AMS ID and tray ID
* - HasAms(): Checks if any AMS units are connected
*
* Ownership:
* - Owned by MachineObject (m_fila_system member)
* - Owns all DevAms instances which in turn own DevAmsTray instances
*
* Note: This class does NOT directly communicate with NetworkAgent.
* It is a passive data store populated by the parsing layer.
*/
class DevFilaSystem
{
friend class DevFilaSystemParser;
@@ -200,6 +273,18 @@ private:
};// class DevFilaSystem
/**
* DevFilaSystemParser - Parses printer JSON messages to populate DevFilaSystem.
*
* This is the bridge between NetworkAgent's raw JSON data and the structured
* DevFilaSystem/DevAms/DevAmsTray hierarchy.
*
* Called from MachineObject::parse_json() when AMS-related fields are present
* in printer status messages received via MQTT or LAN communication.
*
* @see MachineObject::parse_json() - Entry point for JSON parsing
* @see DevFilaSystem - Target data structure
*/
class DevFilaSystemParser
{
public:
+26 -19
View File
@@ -363,7 +363,7 @@ namespace Slic3r
return obj;
}
int DeviceManager::query_bind_status(std::string& msg)
int DeviceManager::query_bind_status(std::string& msg, const std::string& provider)
{
if (!m_agent)
{
@@ -381,7 +381,7 @@ namespace Slic3r
unsigned int http_code;
std::string http_body;
int result = m_agent->query_bind_status(query_list, &http_code, &http_body);
int result = m_agent->query_bind_status(query_list, &http_code, &http_body, provider);
if (result < 0)
{
@@ -419,9 +419,9 @@ namespace Slic3r
return result;
}
MachineObject* DeviceManager::get_user_machine(std::string dev_id)
MachineObject* DeviceManager::get_user_machine(std::string dev_id, const std::string& provider)
{
if (!m_agent || !m_agent->is_user_login())
if (!m_agent || !m_agent->is_user_login(provider))
{
return nullptr;
}
@@ -442,14 +442,13 @@ namespace Slic3r
return nullptr;
}
void DeviceManager::clean_user_info()
void DeviceManager::clean_user_info(bool keep_local_selection)
{
BOOST_LOG_TRIVIAL(trace) << "DeviceManager::clean_user_info";
// reset selected_machine
selected_machine = "";
local_selected_machine = "";
OnSelectedMachineChanged(selected_machine, "");
const std::string previous_selected_machine = selected_machine;
const bool keep_selected_machine = keep_local_selection &&
!selected_machine.empty() &&
localMachineList.find(selected_machine) != localMachineList.end();
// clean user list
for (auto it = userMachineList.begin(); it != userMachineList.end(); it++)
@@ -462,6 +461,13 @@ namespace Slic3r
}
}
userMachineList.clear();
if (!keep_selected_machine) {
selected_machine = "";
local_selected_machine = "";
}
OnSelectedMachineChanged(previous_selected_machine, selected_machine);
}
bool DeviceManager::set_selected_machine(std::string dev_id)
@@ -566,7 +572,7 @@ namespace Slic3r
{
if (selected_machine.empty()) return nullptr;
MachineObject* obj = get_user_machine(selected_machine);
MachineObject* obj = get_user_machine(selected_machine, GUI::wxGetApp().get_printer_cloud_provider());
if (obj)
return obj;
@@ -683,15 +689,15 @@ namespace Slic3r
return "";
}
void DeviceManager::modify_device_name(std::string dev_id, std::string dev_name)
void DeviceManager::modify_device_name(std::string dev_id, std::string dev_name, const std::string& provider)
{
BOOST_LOG_TRIVIAL(trace) << "modify_device_name";
if (m_agent)
{
int result = m_agent->modify_printer_name(dev_id, dev_name);
int result = m_agent->modify_printer_name(dev_id, dev_name, provider);
if (result == 0)
{
update_user_machine_list_info();
update_user_machine_list_info(provider);
}
}
}
@@ -712,6 +718,7 @@ namespace Slic3r
try
{
json j = json::parse(body);
const std::string provider = GUI::wxGetApp().get_printer_cloud_provider();
#if !BBL_RELEASE_TO_PUBLIC
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ": " << j;
@@ -740,7 +747,7 @@ namespace Slic3r
obj = new MachineObject(this, m_agent, "", "", "");
if (m_agent)
{
obj->set_bind_status(m_agent->get_user_name());
obj->set_bind_status(m_agent->get_user_name(provider));
}
if (obj->get_dev_ip().empty())
@@ -806,14 +813,14 @@ namespace Slic3r
}
}
void DeviceManager::update_user_machine_list_info()
void DeviceManager::update_user_machine_list_info(const std::string& provider)
{
if (!m_agent) return;
BOOST_LOG_TRIVIAL(debug) << "update_user_machine_list_info";
unsigned int http_code;
std::string body;
int result = m_agent->get_user_print_info(&http_code, &body);
int result = m_agent->get_user_print_info(&http_code, &body, provider);
if (result == 0)
{
parse_user_print_info(body);
@@ -906,7 +913,7 @@ namespace Slic3r
}
// do some refresh
if (Slic3r::GUI::wxGetApp().is_user_login())
if (Slic3r::GUI::wxGetApp().is_user_login(Slic3r::GUI::wxGetApp().get_printer_cloud_provider()))
{
m_manager->check_pushing();
try
@@ -922,4 +929,4 @@ namespace Slic3r
// certificate
agent->install_device_cert(obj->get_dev_id(), obj->is_lan_mode_printer());
}
}
}
+5 -5
View File
@@ -65,14 +65,14 @@ public:
std::map<std::string, MachineObject*> get_user_machinelist() const { return userMachineList; }
std::string get_first_online_user_machine() const;
void erase_user_machine(std::string dev_id) { userMachineList.erase(dev_id); }
void clean_user_info();
void clean_user_info(bool keep_local_selection = false);
void load_last_machine();
void update_user_machine_list_info();
void update_user_machine_list_info(const std::string& provider);
void parse_user_print_info(std::string body);
void reload_printer_settings();
MachineObject* get_user_machine(std::string dev_id);
MachineObject* get_user_machine(std::string dev_id, const std::string& provider);
// subscribe
void add_user_subscribe();
@@ -83,11 +83,11 @@ public:
MachineObject* get_my_machine(std::string dev_id);
std::map<std::string, MachineObject*> get_my_machine_list();
std::map<std::string, MachineObject*> get_my_cloud_machine_list();
void modify_device_name(std::string dev_id, std::string dev_name);
void modify_device_name(std::string dev_id, std::string dev_name, const std::string& provider);
/* create machine or update machine properties */
void on_machine_alive(std::string json_str);
int query_bind_status(std::string& msg);
int query_bind_status(std::string& msg, const std::string& provider);
// mutil-device
void EnableMultiMachine(bool enable = true);
+1 -1
View File
@@ -2430,7 +2430,7 @@ bool MachineObject::is_connected()
if (!is_lan_mode_printer()) {
NetworkAgent* m_agent = Slic3r::GUI::wxGetApp().getAgent();
if (m_agent) {
return m_agent->is_server_connected();
return m_agent->is_server_connected(Slic3r::GUI::wxGetApp().get_printer_cloud_provider());
}
}
return true;
+553
View File
@@ -0,0 +1,553 @@
#include "ExportPresetBundleDialog.hpp"
#include <slic3r/GUI/Widgets/WebView.hpp>
#include "GUI_App.hpp"
#include "ConfigWizard.hpp"
#include "I18N.hpp"
#include "GUI_App.hpp"
#include <libslic3r/Config.hpp>
#include <wx/app.h>
#include <wx/event.h>
#include <wx/filename.h>
#include <wx/scrolwin.h>
#include <wx/sizer.h>
#include <libslic3r/PresetBundle.hpp>
#include <wx/string.h>
#include <slic3r/GUI/Widgets/WebView.hpp>
#include <miniz.h>
#include <slic3r/GUI/MsgDialog.hpp>
namespace Slic3r { namespace GUI {
ExportPresetBundleDialog::ExportPresetBundleDialog(
wxWindow* parent, wxWindowID id, const wxString& title, const wxPoint& pos, const wxSize& size, long style)
: DPIDialog(parent, id, _L("ExportPresetBundle"), pos, size, style)
{
SetBackgroundColour(*wxWHITE);
SetMinSize(DESIGN_WINDOW_SIZE);
Init();
wxGetApp().UpdateDlgDarkUI(this);
}
ExportPresetBundleDialog::~ExportPresetBundleDialog()
{
for (std::pair<std::string, Preset*> printer_preset : m_printer_presets) {
Preset* preset = printer_preset.second;
if (preset) {
delete preset;
preset = nullptr;
}
}
}
void ExportPresetBundleDialog::LoadUrl(wxString& url)
{
if (!m_browser)
return;
BOOST_LOG_TRIVIAL(trace) << __FUNCTION__ << " enter, url=" << url.ToStdString();
WebView::LoadUrl(m_browser, url);
m_browser->SetFocus();
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << " exit";
}
void ExportPresetBundleDialog::on_dpi_changed(const wxRect& suggested_rect) { this->Refresh(); }
void ExportPresetBundleDialog::Init()
{
wxString TargetUrl = from_u8(
(boost::filesystem::path(resources_dir()) / "web/dialog/ExportPresetDialog/index.html").make_preferred().string());
wxString strlang = wxGetApp().current_language_code_safe();
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format(", strlang=%1%") % into_u8(strlang);
if (strlang != "")
TargetUrl = wxString::Format("%s?lang=%s", std::string(TargetUrl.mb_str()), strlang);
TargetUrl = "file://" + TargetUrl;
// Create the webview
m_browser = WebView::CreateWebView(this, TargetUrl);
if (m_browser == nullptr) {
wxLogError("Could not init m_browser");
return;
}
wxBoxSizer* topsizer = new wxBoxSizer(wxVERTICAL);
SetTitle(_L("Export Preset Bundle"));
SetSizer(topsizer);
topsizer->Add(m_browser, wxSizerFlags().Expand().Proportion(1));
// Set a more sensible size for web browsing
wxSize pSize = FromDIP(wxSize(820, 660));
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);
Bind(wxEVT_WEBVIEW_SCRIPT_MESSAGE_RECEIVED, &ExportPresetBundleDialog::OnScriptMessage, this, m_browser->GetId());
LoadUrl(TargetUrl);
}
void ExportPresetBundleDialog::RunScript(const wxString& s)
{
if (!m_browser)
return;
WebView::RunScript(m_browser, s);
}
void ExportPresetBundleDialog::OnScriptMessage(wxWebViewEvent& e)
{
try {
wxString strInput = e.GetString();
BOOST_LOG_TRIVIAL(trace) << "ExportPresetBundleDialog::OnScriptMessage;OnRecv:" << strInput.c_str();
json j = json::parse(strInput.utf8_string());
wxString strCmd = j["command"];
BOOST_LOG_TRIVIAL(trace) << "ExportPresetBundleDialog::OnScriptMessage;Command:" << strCmd;
if (strCmd == "close_page") {
this->EndModal(wxID_CANCEL);
} else if (strCmd == "request_export_preset_profile") {
InitExportData();
OnRequestPresets();
} else if (strCmd == "export_local") {
wxFileDialog dlg(this, _L("Save preset bundle"), "", "export.orca_bundle", "Orca Preset Bundle (*.orca_bundle)|*.orca_bundle",
wxFD_SAVE | wxFD_OVERWRITE_PROMPT);
wxString path;
wxString name;
if (dlg.ShowModal() == wxID_OK) {
path = dlg.GetPath();
wxFileName file_name(path);
name = file_name.GetName();
if (file_name.GetExt().empty()) {
file_name.SetExt("orca_bundle");
path = file_name.GetFullPath();
}
}
OnExportData(path, name, j["data"]);
}
} catch (std::exception& e) {
BOOST_LOG_TRIVIAL(trace) << "ExportPresetBundleDialog::OnScriptMessage;Error:" << e.what();
}
}
static std::string get_machine_name(const std::string& preset_name)
{
size_t index_at = preset_name.find_last_of("@");
if (std::string::npos == index_at) {
return "";
} else {
return preset_name.substr(index_at + 1);
}
}
static std::string get_filament_name(std::string& preset_name)
{
size_t index_at = preset_name.find_last_of("@");
if (std::string::npos == index_at) {
return preset_name;
} else {
return preset_name.substr(0, index_at - 1);
}
}
static std::string get_vendor_name(std::string& preset_name)
{
if (preset_name.empty())
return "";
std::string vendor_name = preset_name.substr(preset_name.find_first_not_of(' ')); // remove the name prefix space
size_t index_at = vendor_name.find(" ");
if (std::string::npos == index_at) {
return vendor_name;
} else {
vendor_name = vendor_name.substr(0, index_at);
return vendor_name;
}
}
static std::string get_curr_time(const char* format = "%Y_%m_%d_%H_%M_%S")
{
std::chrono::system_clock::time_point now = std::chrono::system_clock::now();
std::time_t time = std::chrono::system_clock::to_time_t(now);
std::tm local_time = *std::localtime(&time);
std::ostringstream time_stream;
time_stream << std::put_time(&local_time, format);
std::string current_time = time_stream.str();
return current_time;
}
static mz_bool initial_zip_archive(mz_zip_archive& zip_archive, const std::string& file_path)
{
mz_zip_zero_struct(&zip_archive);
mz_bool status;
// Initialize the ZIP file to write to the structure, using memory storage
std::string export_dir = encode_path(file_path.c_str());
status = mz_zip_writer_init_file(&zip_archive, export_dir.c_str(), 0);
return status;
}
void ExportPresetBundleDialog::InitExportData()
{
// Delete the Temp folder
boost::filesystem::path folder(data_dir() + "/" + PRESET_USER_DIR + "/" + "Temp");
if (boost::filesystem::exists(folder))
boost::filesystem::remove_all(folder);
boost::system::error_code ec;
boost::filesystem::path user_folder(data_dir() + "/" + PRESET_USER_DIR);
bool temp_folder_exist = true;
if (!boost::filesystem::exists(user_folder)) {
if (!boost::filesystem::create_directories(user_folder, ec)) {
BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << " create directory failed: " << user_folder << " " << ec.message();
temp_folder_exist = false;
}
}
boost::filesystem::path temp_folder(user_folder / "Temp");
if (!boost::filesystem::exists(temp_folder)) {
if (!boost::filesystem::create_directories(temp_folder, ec)) {
BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << " create directory failed: " << temp_folder << " " << ec.message();
temp_folder_exist = false;
}
}
if (!temp_folder_exist) {
MessageDialog dlg(this, _L("Failed to create temporary folder, please try Export Configs again."),
wxString(SLIC3R_APP_FULL_NAME) + " - " + _L("Info"), wxYES_NO | wxYES_DEFAULT | wxCENTRE);
dlg.ShowModal();
EndModal(wxCANCEL);
}
PresetBundle preset_bundle(*wxGetApp().preset_bundle);
const std::deque<Preset>& printer_presets = preset_bundle.printers.get_presets();
// for all the printers
for (const Preset& printer_preset : printer_presets) {
std::string preset_name = printer_preset.name;
if (!printer_preset.is_visible || printer_preset.is_default || printer_preset.is_project_embedded)
continue;
if (preset_bundle.printers.select_preset_by_name(preset_name, true)) {
preset_bundle.update_compatible(PresetSelectCompatibleType::Always);
const std::deque<Preset>& filament_presets = preset_bundle.filaments.get_presets();
for (const Preset& filament_preset : filament_presets) {
if (!filament_preset.is_user())
continue;
if (filament_preset.is_compatible) {
Preset* new_filament_preset = new Preset(filament_preset);
m_filament_presets[preset_name].push_back(new_filament_preset);
}
}
const std::deque<Preset>& process_presets = preset_bundle.prints.get_presets();
for (const Preset& process_preset : process_presets) {
if (!process_preset.is_user())
continue;
if (process_preset.is_compatible) {
Preset* new_prpcess_preset = new Preset(process_preset);
m_process_presets[preset_name].push_back(new_prpcess_preset);
}
}
// make new and erase sensitive information
Preset* new_printer_preset = new Preset(printer_preset);
if (new_printer_preset->type == Preset::Type::TYPE_PRINTER) {
boost::filesystem::path file_path(data_dir() + "/" + PRESET_USER_DIR + "/" + "Temp" + "/" +
(new_printer_preset->name + ".json"));
new_printer_preset->file = file_path.make_preferred().string();
DynamicPrintConfig& config = new_printer_preset->config;
config.erase("print_host");
config.erase("print_host_webui");
config.erase("printhost_apikey");
config.erase("printhost_cafile");
config.erase("printhost_user");
config.erase("printhost_password");
config.erase("printhost_port");
new_printer_preset->save(nullptr);
}
m_printer_presets[preset_name] = new_printer_preset;
}
}
const std::deque<Preset>& filament_presets = preset_bundle.filaments.get_presets();
for (const Preset& filament_preset : filament_presets) {
if (!filament_preset.can_overwrite())
continue;
Preset* new_filament_preset = new Preset(filament_preset);
const Preset* base_filament_preset = preset_bundle.filaments.get_preset_base(*new_filament_preset);
if (base_filament_preset == nullptr) {
BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << " Failed to find base preset";
continue;
}
std::string filament_preset_name = base_filament_preset->name;
std::string machine_name = get_machine_name(filament_preset_name);
m_filament_name_to_presets[get_filament_name(filament_preset_name)].push_back(
std::make_pair(get_vendor_name(machine_name), new_filament_preset));
}
}
void ExportPresetBundleDialog::OnRequestPresets()
{
PresetBundle* preset_bundle = wxGetApp().preset_bundle;
json res;
res["command"] = "response_export_preset_profile";
res["sequence_id"] = "2000";
res["data"] = json::object();
res["data"]["printers"] = json::array();
res["data"]["filaments"] = json::array();
res["data"]["process"] = json::array();
for (std::pair<std::string, Preset*> preset : m_printer_presets) {
if (preset.second->is_system)
continue;
res["data"]["printers"].push_back(preset.first);
}
for (std::pair<std::string, std::vector<std::pair<std::string, Preset*>>> filament_name_to_preset : m_filament_name_to_presets) {
if (filament_name_to_preset.second.empty())
continue;
res["data"]["filaments"].push_back(filament_name_to_preset.first);
}
for (std::pair<std::string, std::vector<const Preset*>> presets : m_process_presets) {
Preset* printer_preset = preset_bundle->printers.find_preset(presets.first, false);
if (!printer_preset)
continue;
if (!printer_preset->is_system)
continue;
if (preset_bundle->printers.get_preset_base(*printer_preset) != printer_preset)
continue;
for (const Preset* preset : presets.second) {
if (!preset->is_system) {
res["data"]["process"].push_back(preset->name);
break;
}
}
}
wxString strJS = wxString::Format("HandleStudio(%s)", wxString::FromUTF8(res.dump(-1, ' ', false, json::error_handler_t::ignore)));
wxGetApp().CallAfter([this, strJS] { RunScript(strJS); });
}
void ExportPresetBundleDialog::OnExportData(const wxString& path, const wxString& filename, json data)
{
auto get_names = [&](const char* key) {
std::vector<std::string> out;
auto it = data.find(key);
if (it == data.end() || !it->is_array())
return out;
for (const auto& v : *it) {
if (v.is_string())
out.push_back(v.get<std::string>());
}
return out;
};
// JS BuildResultPayload uses: machines / filaments / presets
const auto machine_names = get_names("machines");
const auto filament_names = get_names("filaments");
const auto process_names = get_names("presets"); // or "process" if your JS sends that
std::vector<Preset*> selected_printers;
for (const auto& name : machine_names) {
auto it = m_printer_presets.find(name);
if (it != m_printer_presets.end() && it->second)
selected_printers.push_back(it->second);
}
std::vector<Preset*> selected_filaments;
for (const auto& name : filament_names) {
auto it = m_filament_name_to_presets.find(name); // name -> vector<pair<vendor, Preset*>>
if (it == m_filament_name_to_presets.end())
continue;
for (const auto& vp : it->second) {
if (vp.second)
selected_filaments.push_back(vp.second);
}
}
std::vector<Preset*> selected_processes;
for (const auto& name : process_names) {
if (Preset* p = wxGetApp().preset_bundle->prints.find_preset(name, false))
selected_processes.push_back(p);
}
std::string export_path = into_u8(path);
if (export_path.empty() || "initial_failed" == export_path)
return;
boost::filesystem::path export_file_path = boost::filesystem::path(export_path).make_preferred();
if (export_file_path.extension().empty())
export_file_path += ".orca_bundle";
const boost::filesystem::path export_dir = export_file_path.parent_path();
if (!export_dir.empty() && !boost::filesystem::exists(export_dir)) {
boost::system::error_code ec;
if (!boost::filesystem::create_directories(export_dir, ec)) {
BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << " create directory failed: " << export_dir << " " << ec.message();
return;
}
}
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << " export file path: " << export_file_path.string();
BOOST_LOG_TRIVIAL(info) << "Export printer preset bundle";
json bundle_structure;
NetworkAgent* agent = wxGetApp().getAgent();
std::string clock = get_curr_time();
if (agent) {
bundle_structure["version"] = agent->get_version();
bundle_structure["bundle_id"] = agent->get_user_id() + "_" + std::string(filename.utf8_string()) + "_" + clock;
} else {
bundle_structure["version"] = "";
std::string id;
bundle_structure["bundle_id"] = id + "offline_" + "_" + clock;
}
bundle_structure["bundle_type"] = "printer config bundle";
bundle_structure["printer_preset_name"] = "export";
json printer_config = json::array();
json filament_configs = json::array();
json process_configs = json::array();
mz_zip_archive zip_archive;
mz_bool status = initial_zip_archive(zip_archive, export_file_path.string());
if (MZ_FALSE == status) {
BOOST_LOG_TRIVIAL(info) << "Failed to initialize ZIP archive";
show_export_result(ExportCase::INITIALIZE_FAIL);
}
for (auto& printer : selected_printers) {
boost::filesystem::path printer_file_path = boost::filesystem::path(printer->file);
std::string preset_path = printer_file_path.make_preferred().string();
if (preset_path.empty()) {
BOOST_LOG_TRIVIAL(info) << "Export printer preset: " << printer->name << " skip because of the preset file path is empty.";
continue;
}
// Add a file to the ZIP file
std::string printer_config_file_name = "printer/" + printer_file_path.filename().string();
status = mz_zip_writer_add_file(&zip_archive, printer_config_file_name.c_str(), encode_path(preset_path.c_str()).c_str(), NULL, 0,
MZ_DEFAULT_COMPRESSION);
if (MZ_FALSE == status) {
BOOST_LOG_TRIVIAL(info) << printer->name << " Failed to add file to ZIP archive";
mz_zip_writer_end(&zip_archive);
show_export_result(ExportCase::ADD_FILE_FAIL);
return;
}
printer_config.push_back(printer_config_file_name);
BOOST_LOG_TRIVIAL(info) << "Printer preset json add successful: " << printer->name;
}
for (auto& filament : selected_filaments) {
boost::filesystem::path filament_file_path = boost::filesystem::path(filament->file);
std::string filament_preset_path = filament_file_path.make_preferred().string();
if (filament_preset_path.empty()) {
BOOST_LOG_TRIVIAL(info) << "Export filament preset: " << filament->name << " skip because of the preset file path is empty.";
continue;
}
std::string filament_config_file_name = "filament/" + filament_file_path.filename().string();
status = mz_zip_writer_add_file(&zip_archive, filament_config_file_name.c_str(), encode_path(filament_preset_path.c_str()).c_str(),
NULL, 0, MZ_DEFAULT_COMPRESSION);
if (MZ_FALSE == status) {
BOOST_LOG_TRIVIAL(info) << filament->name << " Failed to add file to ZIP archive";
mz_zip_writer_end(&zip_archive);
show_export_result(ExportCase::ADD_FILE_FAIL);
return;
}
filament_configs.push_back(filament_config_file_name);
BOOST_LOG_TRIVIAL(info) << "Filament preset json add successful.";
}
for (auto& process : selected_processes) {
boost::filesystem::path process_file_path = boost::filesystem::path(process->file);
std::string process_preset_path = process_file_path.make_preferred().string();
if (process_preset_path.empty()) {
BOOST_LOG_TRIVIAL(info) << "Export process preset: " << process->name << " skip because of the preset file path is empty.";
continue;
}
std::string process_config_file_name = "process/" + process_file_path.filename().string();
status = mz_zip_writer_add_file(&zip_archive, process_config_file_name.c_str(), encode_path(process_preset_path.c_str()).c_str(),
NULL, 0, MZ_DEFAULT_COMPRESSION);
if (MZ_FALSE == status) {
BOOST_LOG_TRIVIAL(info) << process->name << " Failed to add file to ZIP archive";
mz_zip_writer_end(&zip_archive);
show_export_result(ExportCase::ADD_FILE_FAIL);
return;
}
process_configs.push_back(process_config_file_name);
BOOST_LOG_TRIVIAL(info) << "Process preset json add successful: ";
}
bundle_structure["printer_config"] = printer_config;
bundle_structure["filament_config"] = filament_configs;
bundle_structure["process_config"] = process_configs;
std::string bundle_structure_str = bundle_structure.dump();
status = mz_zip_writer_add_mem(&zip_archive, BUNDLE_STRUCTURE_JSON_NAME, bundle_structure_str.data(), bundle_structure_str.size(),
MZ_DEFAULT_COMPRESSION);
if (MZ_FALSE == status) {
BOOST_LOG_TRIVIAL(info) << " Failed to add file: " << BUNDLE_STRUCTURE_JSON_NAME;
mz_zip_writer_end(&zip_archive);
show_export_result(ExportCase::ADD_BUNDLE_STRUCTURE_FAIL);
return;
}
BOOST_LOG_TRIVIAL(info) << " Success to add file: " << BUNDLE_STRUCTURE_JSON_NAME;
// Complete writing of ZIP file
mz_bool s = mz_zip_writer_finalize_archive(&zip_archive);
if (MZ_FALSE == s) {
BOOST_LOG_TRIVIAL(info) << "Failed to finalize ZIP archive";
mz_zip_writer_end(&zip_archive);
show_export_result(ExportCase::FINALIZE_FAIL);
return;
}
// Release ZIP file to write structure and related resources
mz_zip_writer_end(&zip_archive);
// if (ExportCase::CASE_COUNT != save_result) return save_result;
BOOST_LOG_TRIVIAL(info) << "ZIP archive created successfully";
}
void ExportPresetBundleDialog::show_export_result(const ExportCase& export_case)
{
MessageDialog* msg_dlg = nullptr;
switch (export_case) {
case ExportCase::INITIALIZE_FAIL:
msg_dlg = new MessageDialog(this, _L("initialize fail"), wxString(SLIC3R_APP_FULL_NAME) + " - " + _L("Info"),
wxYES | wxYES_DEFAULT | wxCENTRE);
break;
case ExportCase::ADD_FILE_FAIL:
msg_dlg = new MessageDialog(this, _L("add file fail"), wxString(SLIC3R_APP_FULL_NAME) + " - " + _L("Info"),
wxYES | wxYES_DEFAULT | wxCENTRE);
break;
case ExportCase::ADD_BUNDLE_STRUCTURE_FAIL:
msg_dlg = new MessageDialog(this, _L("add bundle structure file fail"), wxString(SLIC3R_APP_FULL_NAME) + " - " + _L("Info"),
wxYES | wxYES_DEFAULT | wxCENTRE);
break;
case ExportCase::FINALIZE_FAIL:
msg_dlg = new MessageDialog(this, _L("finalize fail"), wxString(SLIC3R_APP_FULL_NAME) + " - " + _L("Info"),
wxYES | wxYES_DEFAULT | wxCENTRE);
break;
case ExportCase::OPEN_ZIP_WRITTEN_FILE:
msg_dlg = new MessageDialog(this, _L("open zip written fail"), wxString(SLIC3R_APP_FULL_NAME) + " - " + _L("Info"),
wxYES | wxYES_DEFAULT | wxCENTRE);
break;
case ExportCase::EXPORT_SUCCESS:
msg_dlg = new MessageDialog(this, _L("Export successful"), wxString(SLIC3R_APP_FULL_NAME) + " - " + _L("Info"),
wxYES | wxYES_DEFAULT | wxCENTRE);
break;
}
if (msg_dlg) {
msg_dlg->ShowModal();
delete msg_dlg;
msg_dlg = nullptr;
}
}
}} // namespace Slic3r::GUI
@@ -0,0 +1,86 @@
#ifndef slic3r_ExportPresetBundleDialog_hpp_
#define slic3r_ExportPresetBundleDialog_hpp_
#include "GUI.hpp"
#include "GUI_Utils.hpp"
#include "libslic3r/AppConfig.hpp"
#include <slic3r/GUI/GUI.hpp>
#include <wx/dataview.h>
#include <wx/event.h>
#include <wx/language.h>
#include <wx/string.h>
#include <wx/fswatcher.h>
#include <wx/webview.h>
namespace Slic3r { namespace GUI {
#define DESIGN_GRAY900_COLOR wxColour("#363636") // Label color
#define DESIGN_GRAY600_COLOR wxColour("#ACACAC") // Dimmed text color
#define DESIGN_WINDOW_SIZE wxSize(FromDIP(640), FromDIP(640))
#define DESIGN_TITLE_SIZE wxSize(FromDIP(280), -1)
#define DESIGN_COMBOBOX_SIZE wxSize(FromDIP(120), -1)
#define DESIGN_LARGE_COMBOBOX_SIZE wxSize(FromDIP(120), -1)
#define DESIGN_INPUT_SIZE wxSize(FromDIP(120), -1)
#define DESIGN_LEFT_MARGIN 25
#define VERTICAL_GAP_SIZE FromDIP(4)
enum ExportCase {
INITIALIZE_FAIL = 0,
ADD_FILE_FAIL,
ADD_BUNDLE_STRUCTURE_FAIL,
FINALIZE_FAIL,
OPEN_ZIP_WRITTEN_FILE,
EXPORT_CANCEL,
EXPORT_SUCCESS,
CASE_COUNT,
};
class ExportPresetBundleDialog : public Slic3r::GUI::DPIDialog
{
public:
ExportPresetBundleDialog(wxWindow* parent,
wxWindowID id = wxID_ANY,
const wxString& title = wxT(""),
const wxPoint& pos = wxDefaultPosition,
const wxSize& size = wxDefaultSize,
long style = wxSYSTEM_MENU | wxCAPTION | wxCLOSE_BOX | wxMAXIMIZE_BOX);
~ExportPresetBundleDialog();
// Utilities
bool seq_top_layer_only_changed() const { return m_seq_top_layer_only_changed; }
bool recreate_GUI() const { return m_recreate_GUI; }
void on_dpi_changed(const wxRect& suggested_rect) override;
void show_export_result(const ExportCase& e);
void Init();
void InitExportData();
// Webview
void LoadUrl(wxString& url);
void OnScriptMessage(wxWebViewEvent& e);
void RunScript(const wxString& s);
void OnRequestPresets();
void OnExportData(const wxString& path, const wxString& name, json data);
protected:
bool m_seq_top_layer_only_changed{false};
bool m_recreate_GUI{false};
// Webview
wxWebView* m_browser{nullptr};
// Export Preset
std::unordered_map<std::string, Preset*> m_printer_presets; // first: printer name, second: printer presets have same printer name
std::unordered_map<std::string, std::vector<const Preset*>>
m_filament_presets; // first: printer name, second: filament presets have same printer name
std::unordered_map<std::string, std::vector<const Preset*>>
m_process_presets; // first: printer name, second: filament presets have same printer name
std::unordered_map<std::string, std::vector<std::pair<std::string, Preset*>>>
m_filament_name_to_presets; // first: filament name, second presets have same filament name and printer name in vector
};
}} // namespace Slic3r::GUI
#endif
+1 -9
View File
@@ -1,5 +1,6 @@
#include "GUI.hpp"
#include "GUI_App.hpp"
#include "ICloudServiceAgent.hpp"
#include "format.hpp"
#include "I18N.hpp"
@@ -524,15 +525,6 @@ void about()
dlg.ShowModal();
}
void login()
{
//LoginDialog dlg;
//dlg.ShowModal();
ZUserLogin dlg;
dlg.run();
}
void desktop_open_datadir_folder()
{
// Execute command to open a file explorer, platform dependent.
-2
View File
@@ -79,8 +79,6 @@ boost::filesystem::path into_path(const wxString &str);
// Display an About dialog
extern void about();
// Display a Login dialog
extern void login();
// Ask the destop to open the datadir using the default file explorer.
extern void desktop_open_datadir_folder();
// Ask the destop to open one folder
+1097 -254
View File
File diff suppressed because it is too large Load Diff
+61 -25
View File
@@ -6,11 +6,13 @@
#include "ImGuiWrapper.hpp"
#include "ConfigWizard.hpp"
#include "OpenGLManager.hpp"
#include "PresetBundleDialog.hpp"
#include "libslic3r/Preset.hpp"
#include "libslic3r/PresetBundle.hpp"
#include "slic3r/GUI/DeviceManager.hpp"
#include "slic3r/GUI/UserNotification.hpp"
#include "slic3r/Utils/NetworkAgent.hpp"
#include "slic3r/Utils/BBLCloudServiceAgent.hpp"
#include "slic3r/GUI/WebViewDialog.hpp"
#include "slic3r/GUI/WebUserLoginDialog.hpp"
#include "slic3r/GUI/BindDialog.hpp"
@@ -293,13 +295,21 @@ private:
Slic3r::UserManager* m_user_manager { nullptr };
Slic3r::TaskManager* m_task_manager { nullptr };
NetworkAgent* m_agent { nullptr };
std::vector<std::string> need_delete_presets; // store setting ids of preset
std::map<std::string, std::string> need_delete_presets; // store setting ids of preset
std::vector<bool> m_create_preset_blocked { false, false, false, false, false, false }; // excceed limit
bool m_networking_compatible { false };
bool m_networking_need_update { false };
bool m_networking_cancel_update { false };
std::shared_ptr<UpgradeNetworkJob> m_upgrade_network_job;
// ORCA: for installing vendors on the main thread when presets to be synced requires it
// vendor structure is:
// [vendor_name]: { model: variants, model: variants, ... }
// filaments structure is:
// [filament_name]: true/false, ...
std::map<std::string, std::map<std::string, std::set<std::string>>> need_add_vendors;
std::map<std::string, std::string> need_add_filaments;
// login widget
ZUserLogin* login_dlg { nullptr };
@@ -460,27 +470,29 @@ public:
wxString transition_tridid(int trid_id) const;
void ShowUserGuide();
void ShowDownNetPluginDlg();
void ShowUserLogin(bool show = true);
void ShowUserLogin(bool show = true, const std::string& provider = ORCA_CLOUD_PROVIDER);
void ShowOnlyFilament();
//BBS
void request_login(bool show_user_info = false);
bool check_login();
void get_login_info();
bool is_user_login();
// Orca auth
void request_login(bool show_user_info = false, const std::string& provider = ORCA_CLOUD_PROVIDER);
bool check_login(const std::string& provider = ORCA_CLOUD_PROVIDER);
void get_login_info(const std::string& provider = ORCA_CLOUD_PROVIDER);
bool is_user_login(const std::string& provider = ORCA_CLOUD_PROVIDER);
const std::string& get_printer_cloud_provider() const;
void request_user_login(int online_login = 0);
void request_user_handle(int online_login = 0);
void request_user_logout();
int request_user_unbind(std::string dev_id);
void request_user_login(int online_login = 0, const std::string& provider = ORCA_CLOUD_PROVIDER);
void request_user_handle(int online_login = 0, const std::string& provider = ORCA_CLOUD_PROVIDER);
void request_user_logout(const std::string& provider = ORCA_CLOUD_PROVIDER);
void post_logout_to_webview(const std::string& provider);
int request_user_unbind(std::string dev_id, const std::string& provider = ORCA_CLOUD_PROVIDER);
std::string handle_web_request(std::string cmd);
void handle_script_message(std::string msg);
void handle_script_message(std::string msg, const std::string& provider = ORCA_CLOUD_PROVIDER);
void request_model_download(wxString url);
void download_project(std::string project_id);
void request_project_download(std::string project_id);
void request_open_project(std::string project_id);
void request_remove_project(std::string project_id);
void handle_http_error(unsigned int status, std::string body);
void handle_http_error(unsigned int status, std::string body, const std::string& provider = ORCA_CLOUD_PROVIDER);
void on_http_error(wxCommandEvent &evt);
void on_update_machine_list(wxCommandEvent& evt);
void on_user_login(wxCommandEvent &evt);
@@ -507,19 +519,36 @@ public:
void push_notification(const MachineObject* obj, wxString msg, wxString title = wxEmptyString, UserNotificationStyle style = UserNotificationStyle::UNS_NORMAL);
void reload_settings();
void remove_user_presets();
bool maybe_migrate_user_presets_on_login();
// ORCA: functions for loading unloaded vendors to allow for proper inheritance when syncing user presets/bundles
bool check_preset_parent_available(const std::pair<std::string, std::map<std::string, std::string>>& preset_data);
void add_pending_vendor_preset(const std::pair<std::string, std::map<std::string, std::string>>& preset_data);
void load_pending_vendors();
void sync_preset(Preset* preset);
void start_sync_user_preset(bool with_progress_dlg = false);
void stop_sync_user_preset();
void start_http_server();
void start_http_server(int port);
// Bundle subscription sync
void check_bundle_updates();
void sync_bundle(std::string bundle_id, std::string version);
bool unsubscribe_bundle(const std::string& id);
void update_single_bundle(wxCommandEvent& evt);
PresetBundleDialog* m_preset_bundle_dlg{nullptr};
void start_http_server(const std::string& provider = ORCA_CLOUD_PROVIDER);
void start_http_server(int port, const std::string& provider = ORCA_CLOUD_PROVIDER);
void stop_http_server();
void switch_staff_pick(bool on);
void on_show_check_privacy_dlg(int online_login = 0);
void on_show_check_privacy_dlg(int online_login = 0, const std::string& provider = ORCA_CLOUD_PROVIDER);
void show_check_privacy_dlg(wxCommandEvent& evt);
void on_check_privacy_update(wxCommandEvent &evt);
bool check_privacy_update();
void check_privacy_version(int online_login = 0);
void check_privacy_version(int online_login = 0, const std::string& provider = ORCA_CLOUD_PROVIDER);
void check_track_enable();
static bool catch_error(std::function<void()> cb, const std::string& err);
@@ -560,10 +589,13 @@ public:
bool checked_tab(Tab* tab);
//BBS: add preset combox re-active logic
void load_current_presets(bool active_preset_combox = false, bool check_printer_presets = true);
std::vector<std::string> &get_delete_cache_presets();
std::vector<std::string> get_delete_cache_presets_lock();
void delete_preset_from_cloud(std::string setting_id);
std::map<std::string, std::string> &get_delete_cache_presets();
std::map<std::string, std::string> get_delete_cache_presets_lock();
void process_delete_presets();
void delete_preset_from_cloud(std::string setting_id, std::string preset_file_path);
void preset_deleted_from_cloud(std::string setting_id);
void scan_orphaned_info_files();
static std::string extract_setting_id_from_info(const std::string& info_file_path);
wxString filter_string(wxString str);
wxString current_language_code() const { return m_active_language_code.empty() && m_wxLocale ? m_wxLocale->GetCanonicalName() : m_active_language_code; }
@@ -572,7 +604,8 @@ public:
bool is_localized() const { return m_wxLocale->GetLocale() != "English"; }
void open_preferences(size_t open_on_tab = 0, const std::string& highlight_option = std::string());
void open_presetbundledialog(size_t open_on_tab = 0, const std::string& highlight_option = std::string());
void open_exportpresetbundledialog(size_t open_on_tab = 0, const std::string& highlight_option = std::string());
virtual bool OnExceptionInMainLoop() override;
// Calls wxLaunchDefaultBrowser if user confirms in dialog.
bool open_browser_with_warning_dialog(const wxString& url, int flags = 0);
@@ -707,6 +740,10 @@ public:
bool hot_reload_network_plugin();
std::string get_latest_network_version() const;
bool has_network_update_available() const;
// Orca: return the client version to report to Bambu servers. Pinned to
// 01.10.01.50 when the legacy network plugin lacks get_my_token support
// so the auth server stays on the ?access_token= redirect path.
std::string get_bbl_client_version();
private:
int updating_bambu_networking();
@@ -718,10 +755,6 @@ private:
void remove_old_networking_plugins();
void drain_pending_events(int timeout_ms);
bool wait_for_network_idle(int timeout_ms);
//BBS set extra header for http request
std::map<std::string, std::string> get_extra_header();
void init_http_extra_header();
void update_http_extra_header();
bool check_older_app_config(Semver current_version, bool backup);
void copy_older_config();
void window_pos_save(wxTopLevelWindow* window, const std::string &name);
@@ -747,6 +780,9 @@ private:
DECLARE_APP(GUI_App)
wxDECLARE_EVENT(EVT_CONNECT_LAN_MODE_PRINT, wxCommandEvent);
wxDECLARE_EVENT(EVT_UPDATE_PRESET_BUNDLE, wxCommandEvent);
wxDECLARE_EVENT(EVT_UPDATE_BUNDLE_COMPLETE, wxCommandEvent);
bool is_support_filament(int extruder_id, bool strict_check = true);
bool is_soluble_filament(int extruder_id);
+140 -20
View File
@@ -3,6 +3,7 @@
#include "GUI_App.hpp"
#include "slic3r/Utils/Http.hpp"
#include "slic3r/Utils/NetworkAgent.hpp"
#include "slic3r/Utils/BBLNetworkPlugin.hpp"
namespace Slic3r {
namespace GUI {
@@ -60,7 +61,7 @@ void session::read_body()
int nbuffer = 1000;
std::shared_ptr<std::vector<char>> bufptr = std::make_shared<std::vector<char>>(nbuffer);
async_read(socket, boost::asio::buffer(*bufptr, nbuffer),
[this, self](const boost::beast::error_code& e, std::size_t s) { server.stop(self); });
[this, self, bufptr](const boost::beast::error_code& e, std::size_t s) { server.stop(self); });
}
void session::read_next_line()
@@ -145,18 +146,27 @@ void HttpServer::IOServer::stop_all()
HttpServer::HttpServer(boost::asio::ip::port_type port) : port(port) {}
HttpServer::~HttpServer()
{
stop();
}
void HttpServer::start()
{
if (start_http_server)
return;
BOOST_LOG_TRIVIAL(info) << "start_http_service...";
start_http_server = true;
m_http_server_thread = create_thread([this] {
server_ = std::make_unique<IOServer>(*this);
IOServer* io_server = server_.get();
start_http_server = true;
m_http_server_thread = create_thread([io_server] {
set_current_thread_name("http_server");
server_ = std::make_unique<IOServer>(*this);
server_->acceptor.listen();
io_server->acceptor.listen();
server_->do_accept();
io_server->do_accept();
server_->io_service.run();
io_server->io_service.run();
});
}
@@ -164,9 +174,14 @@ void HttpServer::stop()
{
start_http_server = false;
if (server_) {
server_->acceptor.close();
server_->stop_all();
server_->io_service.stop();
IOServer* io_server = server_.get();
boost::asio::post(io_server->io_service, [io_server] {
boost::system::error_code ec;
io_server->acceptor.cancel(ec);
io_server->acceptor.close(ec);
io_server->stop_all();
io_server->io_service.stop();
});
}
if (m_http_server_thread.joinable())
m_http_server_thread.join();
@@ -179,12 +194,20 @@ void HttpServer::set_request_handler(const std::function<std::shared_ptr<Respons
}
std::shared_ptr<HttpServer::Response> HttpServer::bbl_auth_handle_request(const std::string& url)
{
return auth_handle_request(url, BBL_CLOUD_PROVIDER);
}
std::shared_ptr<HttpServer::Response> HttpServer::auth_handle_request(const std::string& url, const std::string& provider)
{
BOOST_LOG_TRIVIAL(info) << "thirdparty_login: get_response";
const std::string auth_code = url_get_param(url, "code");
if (!auth_code.empty()) {
std::string state = url_get_param(url, "state");
std::string state = url_get_param(url, "orca_state");
if (state.empty()) {
state = url_get_param(url, "state"); // fallback
}
NetworkAgent* agent = wxGetApp().getAgent();
if (!agent) {
return std::make_shared<ResponseNotFound>();
@@ -195,10 +218,10 @@ std::shared_ptr<HttpServer::Response> HttpServer::bbl_auth_handle_request(const
payload["data"]["code"] = auth_code;
payload["data"]["state"] = state;
agent->change_user(payload.dump());
const bool login_ok = agent->is_user_login();
agent->change_user(payload.dump(), provider);
const bool login_ok = agent->is_user_login(provider);
if (login_ok) {
wxGetApp().request_user_login(1);
wxGetApp().request_user_login(1, provider);
GUI::wxGetApp().CallAfter([] { wxGetApp().ShowUserLogin(false); });
}
@@ -228,7 +251,7 @@ std::shared_ptr<HttpServer::Response> HttpServer::bbl_auth_handle_request(const
unsigned int http_code;
std::string http_body;
int result = agent->get_my_profile(access_token, &http_code, &http_body);
int result = agent->get_my_profile(access_token, &http_code, &http_body, provider);
if (result == 0) {
std::string user_id;
std::string user_name;
@@ -256,9 +279,9 @@ std::shared_ptr<HttpServer::Response> HttpServer::bbl_auth_handle_request(const
j["data"]["user"]["name"] = user_name;
j["data"]["user"]["account"] = user_account;
j["data"]["user"]["avatar"] = user_avatar;
agent->change_user(j.dump());
if (agent->is_user_login()) {
wxGetApp().request_user_login(1);
agent->change_user(j.dump(), provider);
if (agent->is_user_login(provider)) {
wxGetApp().request_user_login(1, provider);
}
GUI::wxGetApp().CallAfter([] { wxGetApp().ShowUserLogin(false); });
std::string location_str = (boost::format("%1%?result=success") % redirect_url).str();
@@ -268,9 +291,106 @@ std::shared_ptr<HttpServer::Response> HttpServer::bbl_auth_handle_request(const
std::string location_str = (boost::format("%1%?result=fail&error=%2%") % redirect_url % error_str).str();
return std::make_shared<ResponseRedirect>(location_str);
}
} else {
return std::make_shared<ResponseNotFound>();
}
// Ticket-based redirect: Bambu Lab's auth server redirects here after a
// third-party (Google) OAuth so that the access token never travels through
// the URL. We exchange the ticket via the network plugin's get_my_token,
// then run the same get_my_profile + change_user flow as access_token.
// Skip entirely on legacy plugins missing bambu_network_get_my_token —
// those clients pin X-BBL-Client-Version so the server stays on the legacy
// ?access_token= redirect path and never sends ?ticket= here.
const std::string ticket = url_get_param(url, "ticket");
const std::string ticket_redirect_url = url_get_param(url, "redirect_url");
if (!ticket.empty() && !ticket_redirect_url.empty() &&
BBLNetworkPlugin::instance().get_get_my_token() != nullptr) {
BOOST_LOG_TRIVIAL(info) << "thirdparty_login: ticket flow";
NetworkAgent* agent = wxGetApp().getAgent();
if (!agent) {
std::string location_str = (boost::format("%1%?result=fail&error=no_agent") % ticket_redirect_url).str();
return std::make_shared<ResponseRedirect>(location_str);
}
auto fail_redirect = [&ticket_redirect_url](const std::string& reason) {
std::string location_str = (boost::format("%1%?result=fail&error=%2%") % ticket_redirect_url % reason).str();
return std::make_shared<ResponseRedirect>(location_str);
};
unsigned int token_http_code = 0;
std::string token_body;
int token_result = agent->get_my_token(ticket, &token_http_code, &token_body, provider);
if (token_result != 0) {
BOOST_LOG_TRIVIAL(warning) << "thirdparty_login: get_my_token failed, http_code=" << token_http_code;
return fail_redirect("get_my_token_error_" + std::to_string(token_result));
}
std::string access_token;
std::string refresh_token;
std::string expires_in_str;
std::string refresh_expires_in_str;
try {
json token_j = json::parse(token_body);
if (token_j.contains("accessToken"))
access_token = token_j["accessToken"].get<std::string>();
if (token_j.contains("refreshToken"))
refresh_token = token_j["refreshToken"].get<std::string>();
if (token_j.contains("expiresIn"))
expires_in_str = std::to_string(token_j["expiresIn"].get<double>());
if (token_j.contains("refreshExpiresIn"))
refresh_expires_in_str = std::to_string(token_j["refreshExpiresIn"].get<double>());
} catch (...) {
return fail_redirect("token_parse_error");
}
if (access_token.empty()) {
return fail_redirect("token_missing");
}
unsigned int profile_http_code = 0;
std::string profile_body;
int profile_result = agent->get_my_profile(access_token, &profile_http_code, &profile_body, provider);
if (profile_result != 0) {
BOOST_LOG_TRIVIAL(warning) << "thirdparty_login: get_my_profile failed, http_code=" << profile_http_code;
return fail_redirect("get_user_profile_error_" + std::to_string(profile_result));
}
std::string user_id;
std::string user_name;
std::string user_account;
std::string user_avatar;
try {
json user_j = json::parse(profile_body);
if (user_j.contains("uidStr"))
user_id = user_j["uidStr"].get<std::string>();
if (user_j.contains("name"))
user_name = user_j["name"].get<std::string>();
if (user_j.contains("avatar"))
user_avatar = user_j["avatar"].get<std::string>();
if (user_j.contains("account"))
user_account = user_j["account"].get<std::string>();
} catch (...) {
BOOST_LOG_TRIVIAL(warning) << "thirdparty_login: profile JSON parse failed";
}
json j;
j["data"]["refresh_token"] = refresh_token;
j["data"]["token"] = access_token;
j["data"]["expires_in"] = expires_in_str;
j["data"]["refresh_expires_in"] = refresh_expires_in_str;
j["data"]["user"]["uid"] = user_id;
j["data"]["user"]["name"] = user_name;
j["data"]["user"]["account"] = user_account;
j["data"]["user"]["avatar"] = user_avatar;
agent->change_user(j.dump(), provider);
if (agent->is_user_login(provider)) {
wxGetApp().request_user_login(1, provider);
}
GUI::wxGetApp().CallAfter([] { wxGetApp().ShowUserLogin(false); });
std::string location_str = (boost::format("%1%?result=success") % ticket_redirect_url).str();
return std::make_shared<ResponseRedirect>(location_str);
}
return std::make_shared<ResponseNotFound>();
}
void HttpServer::ResponseNotFound::write_response(std::stringstream& ssOut)
+2
View File
@@ -110,6 +110,7 @@ public:
};
HttpServer(boost::asio::ip::port_type port = LOCALHOST_PORT);
~HttpServer();
boost::thread m_http_server_thread;
bool start_http_server = false;
@@ -122,6 +123,7 @@ public:
void set_request_handler(const std::function<std::shared_ptr<Response>(const std::string&)>& m_request_handler);
static std::shared_ptr<Response> bbl_auth_handle_request(const std::string& url);
static std::shared_ptr<Response> auth_handle_request(const std::string& url, const std::string& provider);
private:
class IOServer
+1 -1
View File
@@ -125,7 +125,7 @@ void BindJob::process(Ctl &ctl)
post_fail_event(result_code, result_info);
return;
}
dev->update_user_machine_list_info();
dev->update_user_machine_list_info(wxGetApp().get_printer_cloud_provider());
wxCommandEvent event(EVT_BIND_MACHINE_SUCCESS);
event.SetEventObject(m_event_handle);
+26 -3
View File
@@ -3243,6 +3243,15 @@ void MainFrame::init_menubar_as_editor()
wxGetApp().open_preferences();
},
"", nullptr, []() { return true; }, this);
append_menu_item(
m_topbar->GetTopMenu(), wxID_ANY, _L("Preset Bundle") + "\t", "",
[this](wxCommandEvent &) {
// Orca: Use GUI_App::open_preferences instead of direct call so windows associations are updated on exit
wxGetApp().open_presetbundledialog();
plater()->get_current_canvas3D()->force_set_focus();
},
"", nullptr, []() { return true; }, this);
//m_topbar->AddDropDownMenuItem(preference_item);
//m_topbar->AddDropDownMenuItem(printer_item);
//m_topbar->AddDropDownMenuItem(language_item);
@@ -3343,6 +3352,15 @@ void MainFrame::init_menubar_as_editor()
{return m_plater->is_view3D_shown();; }, this);
#else
// On Mac, the Apple menu ignores non-standard custom items, so add Preset Bundle to the File menu
fileMenu->AppendSeparator();
append_menu_item(
fileMenu, wxID_ANY, _L("Preset Bundle"), "",
[this](wxCommandEvent &) {
wxGetApp().open_presetbundledialog();
plater()->get_current_canvas3D()->force_set_focus();
},
"", nullptr, []() { return true; }, this);
m_menubar->Append(fileMenu, wxString::Format("&%s", _L("File")));
if (editMenu)
m_menubar->Append(editMenu, wxString::Format("&%s", _L("Edit")));
@@ -3645,7 +3663,7 @@ void MainFrame::load_config_file()
// return;
wxFileDialog dlg(this, _L("Select profile to load:"),
!m_last_config.IsEmpty() ? get_dir_name(m_last_config) : wxGetApp().app_config->get_last_dir(),
"config.json", "Config files (*.json;*.zip;*.orca_printer;*.orca_filament)|*.json;*.zip;*.orca_printer;*.orca_filament", wxFD_OPEN | wxFD_MULTIPLE | wxFD_FILE_MUST_EXIST);
"config.json", "Config files (*.json;*.zip;*.orca_printer;*.orca_bundle;*.orca_filament)|*.json;*.zip;*.orca_printer;*.orca_bundle;*.orca_filament", wxFD_OPEN | wxFD_MULTIPLE | wxFD_FILE_MUST_EXIST);
wxArrayString files;
if (dlg.ShowModal() != wxID_OK)
return;
@@ -3662,11 +3680,16 @@ void MainFrame::load_config_file()
int ids[]{wxID_NO, wxID_YES, wxID_NOTOALL, wxID_YESTOALL};
return std::find(ids, ids + 4, res) - ids;
},
ForwardCompatibilitySubstitutionRule::Enable);
ForwardCompatibilitySubstitutionRule::Enable,
*wxGetApp().app_config);
if (!cfiles.empty()) {
wxGetApp().app_config->update_config_dir(get_dir_name(cfiles.back()));
wxGetApp().load_current_presets();
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << " presets has been import,and size is" << cfiles.size();
NetworkAgent* agent = wxGetApp().getAgent();
if (agent) {
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << " user is: " << agent->get_user_id();
}
}
wxGetApp().preset_bundle->update_compatible(PresetSelectCompatibleType::Always);
update_side_preset_ui();
@@ -4213,7 +4236,7 @@ void MainFrame::update_side_preset_ui()
void MainFrame::on_select_default_preset(SimpleEvent& evt)
{
MessageDialog dialog(this,
_L("Do you want to synchronize your personal data from Bambu Cloud?\n"
_L("Do you want to synchronize your personal data from Orca Cloud?\n"
"It contains the following information:\n"
"1. The Process presets\n"
"2. The Filament presets\n"
+1 -1
View File
@@ -362,7 +362,7 @@ void MonitorPanel::update_all()
// only disconnected server in cloud mode
if (obj->connection_type() != "lan") {
if (m_agent) {
server_status = m_agent->is_server_connected() ? 0 : (int)MONITOR_DISCONNECTED_SERVER;
server_status = m_agent->is_server_connected(wxGetApp().get_printer_cloud_provider()) ? 0 : (int)MONITOR_DISCONNECTED_SERVER;
}
}
show_status((int) MONITOR_DISCONNECTED + server_status);
+132
View File
@@ -2231,6 +2231,138 @@ void NotificationManager::push_import_finished_notification(const std::string& p
set_slicing_progress_hidden();
}
// SharedProfilesNotification implementation
void NotificationManager::SharedProfilesNotification::init()
{
PopNotification::init();
// Add one extra line for the hyperlink row ("Browse shared profiles" + "Don't show again")
m_lines_count++;
}
void NotificationManager::SharedProfilesNotification::render_text(ImGuiWrapper& imgui,
const float win_size_x, const float win_size_y,
const float win_pos_x, const float win_pos_y)
{
float x_offset = m_left_indentation;
float shift_y = m_line_height;
float starting_y = m_line_height / 2;
// Render main text line(s)
int last_end = 0;
std::string line;
for (size_t i = 0; i < m_endlines.size(); i++) {
if (m_text1.size() >= m_endlines[i]) {
line = m_text1.substr(last_end, m_endlines[i] - last_end);
last_end = m_endlines[i];
if (m_text1.size() > m_endlines[i])
last_end += (m_text1[m_endlines[i]] == '\n' || m_text1[m_endlines[i]] == ' ' ? 1 : 0);
ImGui::SetCursorPosX(x_offset);
ImGui::SetCursorPosY(starting_y + i * shift_y);
imgui.text(line.c_str());
}
}
// Render "Browse shared profiles" hyperlink on the next line
float hyper_y = starting_y + m_endlines.size() * shift_y;
render_hypertext(imgui, x_offset, hyper_y, m_hypertext);
// Render "Don't show again" hyperlink after the browse link
{
float dont_show_x = x_offset + ImGui::CalcTextSize((m_hypertext + " ").c_str()).x;
std::string dont_show_text = _u8L("Don't show again");
ImVec2 part_size = ImGui::CalcTextSize(dont_show_text.c_str());
// Invisible button
ImGui::SetCursorPosX(dont_show_x - 4);
ImGui::SetCursorPosY(hyper_y - 5);
ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(.0f, .0f, .0f, .0f));
ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImVec4(.0f, .0f, .0f, .0f));
ImGui::PushStyleColor(ImGuiCol_ButtonActive, ImVec4(.0f, .0f, .0f, .0f));
if (imgui.button("##dont_show_btn", part_size.x + 6, part_size.y + 10)) {
wxGetApp().app_config->set_bool("show_shared_profiles_notification", false);
wxGetApp().app_config->save();
close();
}
ImGui::PopStyleColor(3);
// Hover color
ImVec4 color = m_HyperTextColor;
if (ImGui::IsItemHovered(ImGuiHoveredFlags_RectOnly))
color.y += 0.1f;
// Text
push_style_color(ImGuiCol_Text, color, m_state == EState::FadingOut, m_current_fade_opacity);
ImGui::SetCursorPosX(dont_show_x);
ImGui::SetCursorPosY(hyper_y);
imgui.text(dont_show_text.c_str());
ImGui::PopStyleColor();
// Underline
ImVec2 lineEnd = ImGui::GetItemRectMax();
lineEnd.y -= 2;
ImVec2 lineStart = lineEnd;
lineStart.x = ImGui::GetItemRectMin().x;
ImGui::GetWindowDrawList()->AddLine(lineStart, lineEnd,
IM_COL32((int)(color.x * 255), (int)(color.y * 255), (int)(color.z * 255),
(int)(color.w * 255.f * (m_state == EState::FadingOut ? m_current_fade_opacity : 1.f))));
}
}
bool NotificationManager::SharedProfilesNotification::on_text_click()
{
wxLaunchDefaultBrowser(m_explore_url);
return false;
}
void NotificationManager::SharedProfilesNotification::render_hypertext(ImGuiWrapper& imgui,
const float text_x, const float text_y, const std::string text, bool more)
{
// Invisible button
ImVec2 part_size = ImGui::CalcTextSize(text.c_str());
ImGui::SetCursorPosX(text_x - 4);
ImGui::SetCursorPosY(text_y - 5);
ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(.0f, .0f, .0f, .0f));
ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImVec4(.0f, .0f, .0f, .0f));
ImGui::PushStyleColor(ImGuiCol_ButtonActive, ImVec4(.0f, .0f, .0f, .0f));
if (imgui.button("##browse_btn", part_size.x + 6, part_size.y + 10)) {
if (on_text_click()) {
close();
}
}
ImGui::PopStyleColor(3);
// Hover color
ImVec4 HyperColor = m_HyperTextColor;
if (ImGui::IsItemHovered(ImGuiHoveredFlags_RectOnly))
HyperColor.y += 0.1f;
// Text
push_style_color(ImGuiCol_Text, HyperColor, m_state == EState::FadingOut, m_current_fade_opacity);
ImGui::SetCursorPosX(text_x);
ImGui::SetCursorPosY(text_y);
imgui.text(text.c_str());
ImGui::PopStyleColor();
// Underline
ImVec2 lineEnd = ImGui::GetItemRectMax();
lineEnd.y -= 2;
ImVec2 lineStart = lineEnd;
lineStart.x = ImGui::GetItemRectMin().x;
ImGui::GetWindowDrawList()->AddLine(lineStart, lineEnd,
IM_COL32((int)(HyperColor.x * 255), (int)(HyperColor.y * 255), (int)(HyperColor.z * 255),
(int)(HyperColor.w * 255.f * (m_state == EState::FadingOut ? m_current_fade_opacity : 1.f))));
}
void NotificationManager::push_shared_profiles_notification(const std::string& explore_url)
{
close_notification_of_type(NotificationType::OrcaSharedProfilesAvailable);
NotificationData data{ NotificationType::OrcaSharedProfilesAvailable, NotificationLevel::RegularNotificationLevel, 0,
_u8L("Shared profiles may be available for this printer."),
_u8L("Browse shared profiles") };
push_notification_data(std::make_unique<NotificationManager::SharedProfilesNotification>(data, m_id_provider, m_evt_handler, explore_url), 0);
}
void NotificationManager::push_download_URL_progress_notification(size_t id, const std::string& text, std::function<bool(DownloaderUserAction, int)> user_action_callback)
{
// If already exists
+28
View File
@@ -161,6 +161,7 @@ enum class NotificationType
BBLBedFilamentIncompatible,
BBLMixUsePLAAndPETG,
BBLNozzleFilamentIncompatible,
OrcaSharedProfilesAvailable,
NotificationTypeCount
};
@@ -271,6 +272,9 @@ public:
void push_exporting_finished_notification(const std::string& path, const std::string& dir_path, bool on_removable);
void push_import_finished_notification(const std::string& path, const std::string& dir_path, bool on_removable);
// Shared profiles available for selected printer
void push_shared_profiles_notification(const std::string& explore_url);
// Download URL progress notif
void push_download_URL_progress_notification(size_t id, const std::string& text, std::function<bool(DownloaderUserAction, int)> user_action_callback);
void set_download_URL_progress(size_t id, float percentage);
@@ -858,6 +862,30 @@ private:
};
// in SlicingProgressNotification.hpp
class SharedProfilesNotification : public PopNotification
{
public:
SharedProfilesNotification(const NotificationData& n, NotificationIDProvider& id_provider, wxEvtHandler* evt_handler,
const std::string& explore_url)
: PopNotification(n, id_provider, evt_handler)
, m_explore_url(explore_url)
{
m_multiline = true;
}
protected:
void init() override;
void render_text(ImGuiWrapper& imgui,
const float win_size_x, const float win_size_y,
const float win_pos_x, const float win_pos_y) override;
bool on_text_click() override;
void render_hypertext(ImGuiWrapper& imgui,
const float text_x, const float text_y,
const std::string text, bool more = false) override;
std::string m_explore_url;
bool m_dont_show_clicked{ false };
};
class SlicingProgressNotification;
// in HintNotification.hpp
+69 -20
View File
@@ -161,6 +161,8 @@
#include "DailyTips.hpp"
#include "CreatePresetsDialog.hpp"
#include "FileArchiveDialog.hpp"
#include "../Utils/Http.hpp"
#include "../Utils/OrcaCloudServiceAgent.hpp"
#include "StepMeshDialog.hpp"
#include "FilamentMapDialog.hpp"
#include "CloneDialog.hpp"
@@ -5992,11 +5994,24 @@ std::vector<size_t> Plater::priv::load_files(const std::vector<fs::path>& input_
// BBS: version check
Semver app_version = *(Semver::parse(SoftFever_VERSION));
const wxString load_3mf_title = _L("Load 3MF");
const wxString newer_3mf_title = _L("Newer 3MF version");
const wxString bambu_project_title = _L("BambuStudio Project");
const wxString msg_unsupported_geometry = _L("The 3MF is not supported by OrcaSlicer, loading geometry data only.");
const wxString msg_old_orca_geometry = _L("The 3MF file was generated by an old OrcaSlicer version, loading geometry data only.");
const wxString msg_older_geometry = _L("The 3MF file was generated by an older version, loading geometry data only.");
const wxString msg_bambu_geometry = _L("The 3MF file was generated by BambuStudio, loading geometry data only.");
auto log_and_show_3mf_info = [&](const wxString& text, const wxString& title) {
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ":" << __LINE__
<< " "
<< boost::format("3MF import message [%1%]: %2% | file: %3%") % into_u8(title) % into_u8(text) % path.string();
show_info(q, text, title);
};
if (en_3mf_file_type == En3mfType::From_Prusa) {
// do not reset the model config
load_config = false;
if(load_type != LoadType::LoadGeometry)
show_info(q, _L("The 3MF is not supported by OrcaSlicer, loading geometry data only."), _L("Load 3MF"));
if (load_type != LoadType::LoadGeometry)
log_and_show_3mf_info(msg_unsupported_geometry, load_3mf_title);
}
else if (en_3mf_file_type == En3mfType::From_Orca) {
// OrcaSlicer file (has OrcaSlicer tag) - compare file_version with SoftFever_VERSION
@@ -6032,7 +6047,7 @@ std::vector<size_t> Plater::priv::load_files(const std::vector<fs::path>& input_
wxString append = _L("You'd better upgrade your software.\n");
context += "\n\n";
context += append;
show_info(q, context, _L("Newer 3MF version"));
log_and_show_3mf_info(context, newer_3mf_title);
}
else {
//if the minor version is not matched
@@ -6040,13 +6055,13 @@ std::vector<size_t> Plater::priv::load_files(const std::vector<fs::path>& input_
wxString text = wxString::Format(_L("The 3MF file version %s is newer than %s's version %s, we suggest to upgrade your software."),
file_version.to_string_sf(), std::string(SLIC3R_APP_FULL_NAME), app_version.to_string_sf());
text += "\n";
show_info(q, text, _L("Newer 3MF version"));
log_and_show_3mf_info(text, newer_3mf_title);
}
}
}
else if (load_config && config_loaded.empty()) {
load_config = false;
show_info(q, _L("The 3MF file was generated by an old OrcaSlicer version, loading geometry data only."), _L("Load 3MF"));
log_and_show_3mf_info(msg_old_orca_geometry, load_3mf_title);
}
}
else if (en_3mf_file_type == En3mfType::From_BBS) {
@@ -6081,7 +6096,7 @@ std::vector<size_t> Plater::priv::load_files(const std::vector<fs::path>& input_
}
else if (load_config && config_loaded.empty()) {
load_config = false;
show_info(q, _L("The 3MF file was generated by an older version, loading geometry data only."), _L("Load 3MF"));
log_and_show_3mf_info(msg_older_geometry, load_3mf_title);
}
} else {
// BambuStudio project (version > 2.3.2 without OrcaSlicer tag)
@@ -6089,7 +6104,7 @@ std::vector<size_t> Plater::priv::load_files(const std::vector<fs::path>& input_
Semver slic3r_version = *(Semver::parse(SLIC3R_VERSION));
if (load_config && config_loaded.empty()) {
load_config = false;
show_info(q, _L("The 3MF file was generated by BambuStudio, loading geometry data only."), _L("Load 3MF"));
log_and_show_3mf_info(msg_bambu_geometry, load_3mf_title);
}
else if (load_config && (file_version > slic3r_version)) {
// BambuStudio file version is newer than our compatible SLIC3R_VERSION
@@ -6101,23 +6116,32 @@ std::vector<size_t> Plater::priv::load_files(const std::vector<fs::path>& input_
wxString append = _L("You'd better upgrade your software.\n");
context += "\n\n";
context += append;
show_info(q, context, _L("BambuStudio Project"));
log_and_show_3mf_info(context, bambu_project_title);
} else {
wxString text = wxString::Format(_L("The 3MF was created by BambuStudio (version %s), which is newer than the compatible version %s. Some settings may not be fully compatible."),
file_version.to_string(), slic3r_version.to_string());
text += "\n";
show_info(q, text, _L("BambuStudio Project"));
log_and_show_3mf_info(text, bambu_project_title);
}
} else if (load_config) {
// BambuStudio version is older or same as our SLIC3R_VERSION
wxString text = _L("The 3MF was created by BambuStudio. Some settings may differ from OrcaSlicer.");
show_info(q, text, _L("BambuStudio Project"));
log_and_show_3mf_info(text, bambu_project_title);
}
}
}
else if (en_3mf_file_type == En3mfType::From_Other) {
// Generic CAD/other 3MF without slicer metadata: import geometry silently.
if (load_config && config_loaded.empty()) {
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ":" << __LINE__
<< " "
<< boost::format("3MF has no slicer metadata/project config, importing geometry only: %1%") % path.string();
load_config = false;
}
}
else if (load_config && config_loaded.empty()) {
load_config = false;
show_info(q, _L("The 3MF file was generated by an old OrcaSlicer version, loading geometry data only."), _L("Load 3MF"));
log_and_show_3mf_info(msg_old_orca_geometry, load_3mf_title);
}
else if (!load_config) {
// reset config except color
@@ -6207,9 +6231,9 @@ std::vector<size_t> Plater::priv::load_files(const std::vector<fs::path>& input_
config += std::move(config_loaded);
std::map<std::string, std::string> validity = config.validate();
if (!validity.empty()) {
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ":" << __LINE__ << boost::format("Param values in 3mf error: ");
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ":" << __LINE__ << " " << boost::format("Param values in 3mf error: ");
for (std::map<std::string, std::string>::iterator it=validity.begin(); it!=validity.end(); ++it)
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ":" << __LINE__ << boost::format("%1%: %2%")%it->first %it->second;
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ":" << __LINE__ << " " << boost::format("%1%: %2%")%it->first %it->second;
//
NotificationManager *notify_manager = q->get_notification_manager();
std::string error_message = L("Invalid values found in the 3MF:");
@@ -6691,7 +6715,7 @@ std::vector<size_t> Plater::priv::load_files(const std::vector<fs::path>& input_
model_object->center_around_origin(false);
// BBS
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ":" << __LINE__ << boost::format("import 3mf IMPORT_LOAD_MODEL_OBJECTS \n");
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ":" << __LINE__ << " " << boost::format("import 3mf IMPORT_LOAD_MODEL_OBJECTS \n");
wxString msg = wxString::Format("Loading file: %s", from_path(real_filename));
model_idx++;
dlg_cont = dlg.Update(progress_percent, msg);
@@ -9401,12 +9425,17 @@ void Plater::priv::on_select_preset(wxCommandEvent &evt)
//! instead of
//! combo->GetStringSelection().ToUTF8().data());
wxString wx_name = combo->GetString(selection);
// if (preset_type == Preset::TYPE_PRINTER) {
// wx_name = combo->get_preset_item_name(selection); }
std::string preset_name = wxGetApp().preset_bundle->get_preset_name_by_alias(preset_type,
Preset::remove_suffix_modified(wx_name.ToUTF8().data()));
// Prefer the internal preset name stored per combo item to avoid alias collisions
// (e.g. user preset and bundle preset sharing the same display alias).
wxString wx_stored_name = combo->GetItemAlias(selection);
std::string preset_name;
if (!wx_stored_name.empty()) {
preset_name = Preset::remove_suffix_modified(wx_stored_name.ToUTF8().data());
} else {
wxString wx_name = combo->GetString(selection);
preset_name = wxGetApp().preset_bundle->get_preset_name_by_alias(preset_type,
Preset::remove_suffix_modified(wx_name.ToUTF8().data()));
}
if (preset_type == Preset::TYPE_FILAMENT) {
wxGetApp().preset_bundle->set_filament_preset(idx, preset_name);
@@ -9518,6 +9547,26 @@ void Plater::priv::on_select_preset(wxCommandEvent &evt)
for (size_t idx = 0; idx < filament_size; ++idx)
wxGetApp().plater()->sidebar().auto_calc_flushing_volumes(idx);
#endif
// Show shared profiles notification for the newly selected printer
if (wxGetApp().app_config->get_bool("show_shared_profiles_notification"))
{
std::string printer_name = wxGetApp().preset_bundle->printers.get_selected_preset_base().name;
std::string encoded_name = Http::url_encode(printer_name);
std::string cloud_url;
if (auto agent = wxGetApp().getAgent()) {
if (auto orca_agent = std::dynamic_pointer_cast<OrcaCloudServiceAgent>(agent->get_cloud_agent())) {
cloud_url = orca_agent->get_cloud_base_url();
}
}
if (cloud_url.empty())
cloud_url = "https://cloud.orcaslicer.com";
std::string explore_url = cloud_url + "/app/bundles/explore?printers=" + encoded_name;
wxGetApp().plater()->get_notification_manager()->push_shared_profiles_notification(explore_url);
}
}
#ifdef __WXMSW__
+42
View File
@@ -1356,6 +1356,9 @@ void PreferencesDialog::create_items()
auto item_show_splash_scr = create_item_checkbox(_L("Show splash screen"), _L("Show the splash screen during startup."), "show_splash_screen");
g_sizer->Add(item_show_splash_scr);
auto item_shared_profiles = create_item_checkbox(_L("Show shared profiles notification"), _L("Show a notification with a link to browse shared profiles when the selected printer is changed."), "show_shared_profiles_notification");
g_sizer->Add(item_shared_profiles);
#ifdef __linux__
auto item_window_button_pos = create_item_checkbox(_L("Use window buttons on left side"), "", "window_buttons_on_left", _L("(Requires restart)"));
g_sizer->Add(item_window_button_pos);
@@ -1534,6 +1537,45 @@ void PreferencesDialog::create_items()
});
g_sizer->Add(item_network_test);
//// ONLINE > Cloud Providers
g_sizer->Add(create_item_title(_L("Cloud Providers")), 1, wxEXPAND);
{
auto sizer = new wxBoxSizer(wxHORIZONTAL);
sizer->AddSpacer(FromDIP(DESIGN_LEFT_MARGIN));
auto text = new wxStaticText(m_parent, wxID_ANY, _L("Enable Bambu Cloud"),
wxDefaultPosition, DESIGN_TITLE_SIZE, wxST_NO_AUTORESIZE);
text->SetForegroundColour(DESIGN_GRAY900_COLOR);
text->SetFont(::Label::Body_14);
text->SetToolTip(_L("Allow logging into Bambu Cloud alongside Orca Cloud. When enabled, a Bambu login section appears on the homepage."));
text->Wrap(DESIGN_TITLE_SIZE.x);
auto cb = new ::CheckBox(m_parent);
cb->SetValue(app_config->has_cloud_provider(BBL_CLOUD_PROVIDER));
cb->SetToolTip(text->GetToolTipText());
cb->Bind(wxEVT_TOGGLEBUTTON, [this, cb](wxCommandEvent &e) {
e.Skip(); // let CheckBox::update() refresh the bitmap
if (cb->GetValue()) {
app_config->add_cloud_provider(BBL_CLOUD_PROVIDER);
} else {
app_config->remove_cloud_provider(BBL_CLOUD_PROVIDER);
}
app_config->save();
// Update homepage visibility immediately
auto *mainframe = wxGetApp().mainframe;
if (mainframe && mainframe->m_webview)
mainframe->m_webview->SendCloudProvidersInfo();
});
sizer->Add(text, 0, wxALIGN_CENTER | wxTOP | wxBOTTOM, FromDIP(3));
sizer->Add(cb, 0, wxALIGN_CENTER | wxRIGHT | wxLEFT, FromDIP(5));
g_sizer->Add(sizer);
}
//// ONLINE > Update & sync
g_sizer->Add(create_item_title(_L("Update & sync")), 1, wxEXPAND);
+472
View File
@@ -0,0 +1,472 @@
#include "PresetBundleDialog.hpp"
#include "ExportPresetBundleDialog.hpp"
#include "I18N.hpp"
#include "GUI_App.hpp"
#include <libslic3r/Config.hpp>
#include <libslic3r/Thread.hpp>
#include <wx/app.h>
#include <wx/event.h>
#include <wx/filename.h>
#include <wx/scrolwin.h>
#include <wx/sizer.h>
#include <libslic3r/PresetBundle.hpp>
#include <wx/string.h>
#include "MainFrame.hpp"
#include <slic3r/GUI/Widgets/WebView.hpp>
#include <miniz.h>
#include <OrcaCloudServiceAgent.hpp>
#include <wx/event.h>
#include <wx/utils.h>
namespace Slic3r { namespace GUI {
PresetBundleDialog::PresetBundleDialog(
wxWindow* parent, wxWindowID id, const wxString& title, const wxPoint& pos, const wxSize& size, long style)
: DPIDialog(parent, id, _L("PresetBundle"), pos, size, style)
{
wxGetApp().preset_bundle->bundles.PauseRead(); // for the entirety of the preset bundle dialog, we want the update thread to yield.
SetBackgroundColour(*wxWHITE);
SetMinSize(DESIGN_WINDOW_SIZE);
create();
wxGetApp().UpdateDlgDarkUI(this);
m_watcher = new wxFileSystemWatcher();
m_watcher->SetOwner(this);
Bind(wxEVT_FSWATCHER, &PresetBundleDialog::OnFSWatch, this);
Bind(EVT_UPDATE_BUNDLE_COMPLETE, &PresetBundleDialog::OnBundleUpdate, this);
m_watcher->Add(wxFileName(wxGetApp().preset_bundle->dir_user_presets_local.c_str())); // _local
m_watcher->Add(wxFileName(wxGetApp().preset_bundle->dir_user_presets_subscribed.c_str())); // _subscribed
RefreshBundleMap();
StartDialogWorker(); // start worker thread;
}
PresetBundleDialog::~PresetBundleDialog()
{
StopDialogWorker();
wxGetApp().preset_bundle->bundles.UnpauseRead(); // yield for update thread
if (m_watcher) {
m_watcher->RemoveAll();
delete m_watcher;
}
}
void PresetBundleDialog::OnFSWatch(wxFileSystemWatcherEvent& e)
{
GUI::wxGetApp().preset_bundle->load_presets(*app_config, ForwardCompatibilitySubstitutionRule::EnableSilentDisableSystem);
wxGetApp().mainframe->update_side_preset_ui();
// ListBundles();
m_update_bundles.store(true);
e.Skip();
}
void PresetBundleDialog::StartDialogWorker()
{
if (m_dialog_worker_token) {
return;
}
m_dialog_worker_token = std::make_shared<int>(0);
m_dialog_worker_thread = Slic3r::create_thread([this, token = std::weak_ptr<int>(m_dialog_worker_token)] {
while (!token.expired()) {
// after comparing version with the cloud, if there is an update, we will update the rows to reflect
if (m_check_update_pending.exchange(false, std::memory_order_relaxed)) {
if (CheckUpdateCloud()) {
ListBundles();
}
}
if (m_update_bundles.exchange(false, std::memory_order_relaxed)) {
RefreshBundleMap();
ListBundles();
}
boost::this_thread::sleep_for(boost::chrono::milliseconds(500));
}
});
}
// true if b>a
bool PresetBundleDialog::CompareVer(const std::string& a, const std::string& b)
{
// Compare versions using Semver
auto local_version = Semver::parse(a);
auto remote_version = Semver::parse(b);
if (!local_version || !remote_version) {
return false;
}
if (local_version < remote_version) {
return true;
}
return false;
}
bool PresetBundleDialog::CheckUpdateCloud()
{
bool has_update = false;
if (!wxGetApp().getAgent() || !wxGetApp().getAgent()->is_user_login())
return false;
auto orca_agent = std::dynamic_pointer_cast<OrcaCloudServiceAgent>(wxGetApp().getAgent()->get_cloud_agent());
if (!orca_agent)
return false;
BOOST_LOG_TRIVIAL(info) << "Preset Bundle Dialog: checking for bundle updates";
// Fetch all subscribed bundles from cloud
std::vector<std::pair<std::string, std::string>> subscribed_bundles;
std::vector<std::string> notfound;
std::vector<std::string> unauthorized;
int result = orca_agent->get_subscribed_bundles(&subscribed_bundles, notfound, unauthorized);
if (result != 0) {
BOOST_LOG_TRIVIAL(warning) << "Preset Bundle Dialog: failed to fetch subscribed bundles, result=" << result;
return false;
}
// if unauthorized or not found it should be a warning icon to the ui shit
// check bundle copy with the subscribed bundles
for (auto& b : subscribed_bundles) {
if (bundle_copy.find(b.first) != bundle_copy.end()) {
if (CompareVer(bundle_copy[b.first].version, b.second)) {
bundle_copy[b.first].update_available = true;
bundle_copy[b.first].unauthorized = false;
has_update = true;
} else {
// we count it as an update to the UI if we need to update the unauthorized state
if (bundle_copy[b.first].unauthorized) {
bundle_copy[b.first].unauthorized = false;
has_update = true;
}
bundle_copy[b.first].update_available = false;
}
}
}
for (auto& a : unauthorized) {
if (bundle_copy.find(a) != bundle_copy.end()) {
bundle_copy[a].unauthorized = true;
has_update = true;
}
}
return has_update;
}
void PresetBundleDialog::StopDialogWorker()
{
if (!m_dialog_worker_token) {
return;
}
m_dialog_worker_token.reset();
if (m_dialog_worker_thread.joinable()) {
m_dialog_worker_thread.join();
}
}
void PresetBundleDialog::OnBundleUpdate(wxCommandEvent& evt)
{
// const std::string bundle_id = evt.GetString().ToStdString();
m_update_bundles.store(true);
}
void PresetBundleDialog::RefreshBundleMap()
{
wxGetApp().preset_bundle->bundles.ReadLock();
bundle_copy = wxGetApp().preset_bundle->bundles.m_bundles;
wxGetApp().preset_bundle->bundles.ReadUnlock();
}
void PresetBundleDialog::load_url(wxString& url)
{
if (!m_browser)
return;
BOOST_LOG_TRIVIAL(trace) << __FUNCTION__ << " enter, url=" << url.ToStdString();
WebView::LoadUrl(m_browser, url);
m_browser->SetFocus();
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << " exit";
}
void PresetBundleDialog::create()
{
app_config = get_app_config();
wxString TargetUrl = from_u8(
(boost::filesystem::path(resources_dir()) / "web/dialog/PresetBundleDialog/index.html").make_preferred().string());
wxString strlang = wxGetApp().current_language_code_safe();
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format(", strlang=%1%") % into_u8(strlang);
if (strlang != "")
TargetUrl = wxString::Format("%s?lang=%s", std::string(TargetUrl.mb_str()), strlang);
TargetUrl = "file://" + TargetUrl;
wxBoxSizer* topsizer = new wxBoxSizer(wxVERTICAL);
SetTitle(_L("Preset Bundle"));
m_browser = WebView::CreateWebView(this, TargetUrl);
if (m_browser == nullptr) {
wxLogError("Could not init m_browser");
return;
}
SetSizer(topsizer);
topsizer->Add(m_browser, wxSizerFlags().Expand().Proportion(1));
// Set a more sensible size for web browsing
wxSize pSize = FromDIP(wxSize(820, 660));
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);
Bind(wxEVT_WEBVIEW_SCRIPT_MESSAGE_RECEIVED, &PresetBundleDialog::OnScriptMessage, this, m_browser->GetId());
load_url(TargetUrl);
}
bool PresetBundleDialog::DeleteBundleById(const wxString& id)
{
auto* b = wxGetApp().preset_bundle;
if (id.empty()) {
return false;
}
const std::string bundle_id = id.ToStdString();
wxGetApp().preset_bundle->bundles.ReadLock();
auto it = b->bundles.m_bundles.find(bundle_id);
if (it == b->bundles.m_bundles.end()) {
wxGetApp().preset_bundle->bundles.ReadUnlock();
return false;
}
const std::string metadata_path = it->second.path;
const boost::filesystem::path bundle_dir = boost::filesystem::path(metadata_path).parent_path();
const BundleType bundle_type = it->second.bundle_type;
wxGetApp().preset_bundle->bundles.ReadUnlock();
if (bundle_type == BundleType::Subscribed) {
// do unsubscribe before deleting locally
}
wxGetApp().preset_bundle->bundles.WriteLock();
b->bundles.m_bundles.erase(it);
wxGetApp().preset_bundle->bundles.WriteUnlock();
auto remove_from_collection = [&](PresetCollection& c) {
std::vector<std::string> to_delete;
for (const auto& p : c.get_presets()) {
if (p.bundle_id == bundle_id)
to_delete.push_back(p.name);
}
for (const auto& name : to_delete)
c.delete_preset(name);
};
remove_from_collection(b->prints);
remove_from_collection(b->filaments);
remove_from_collection(b->printers);
boost::system::error_code ec;
if (!bundle_dir.empty() && boost::filesystem::exists(bundle_dir))
boost::filesystem::remove_all(bundle_dir, ec);
wxGetApp().preset_bundle->update_compatible(PresetSelectCompatibleType::Always);
return true;
}
bool PresetBundleDialog::UnsubscribeBundleById(const std::string& id) { return wxGetApp().unsubscribe_bundle(id); }
void PresetBundleDialog::on_dpi_changed(const wxRect& suggested_rect) { this->Refresh(); }
void PresetBundleDialog::RunScript(const wxString& s)
{
if (!m_browser)
return;
WebView::RunScript(m_browser, s);
}
void PresetBundleDialog::OnScriptMessage(wxWebViewEvent& e)
{
try {
wxString strInput = e.GetString();
BOOST_LOG_TRIVIAL(trace) << "PresetBundleDialog::OnScriptMessage;OnRecv:" << strInput.c_str();
json j = json::parse(strInput.utf8_string());
wxString strCmd = j["command"];
BOOST_LOG_TRIVIAL(trace) << "PresetBundleDialog::OnScriptMessage;Command:" << strCmd;
if (strCmd == "request_bundles") {
ListBundles();
} else if (strCmd == "refresh_bundles") {
// use the thread to check for updates.
m_check_update_pending.store(true, std::memory_order_relaxed);
} else if (strCmd == "update_bundle") {
std::string id = j["bundle_id"];
auto* evt = new wxCommandEvent(EVT_UPDATE_PRESET_BUNDLE);
evt->SetString(wxString::FromUTF8(id));
wxQueueEvent(&wxGetApp(), evt); // dialog -> GUI_App
} else if (strCmd == "set_auto_update") {
bool enabled = j.value("enabled", false);
// Example persistence location. Adjust key name if you already have one.
app_config->set_bool("preset_bundle_auto_update", enabled ? true : false);
app_config->save();
} else if (strCmd == "close_page") {
this->EndModal(wxID_CANCEL);
} else if (strCmd == "export_page") {
wxGetApp().CallAfter([this]() {
ExportPresetBundleDialog dlg(this);
dlg.ShowModal();
});
} else if (strCmd == "top_row_menu_action") {
if (j["action"] == "open_folder") {
std::string id = j["bundle_id"];
OpenFolder(id);
} else if (j["action"] == "delete_bundle") {
std::string id = j["bundle_id"];
DeleteBundle(id);
} else if (j["action"] == "unsubscribe_bundle") {
std::string id = j["bundle_id"];
UnsubscribeBundle(id);
}
} else if (strCmd == "open_bundle_on_cloud") {
std::string bundle_id = j["bundle_id"];
OpenBundleOnCloud(bundle_id);
}
} catch (std::exception& e) {
BOOST_LOG_TRIVIAL(trace) << "PresetBundleDialog::OnScriptMessage;Error:" << e.what();
}
}
// call on dialog create to populate the js local store
void PresetBundleDialog::ListBundles()
{
json res;
res["command"] = "list_bundles";
res["sequence_id"] = "2000";
res["data"] = json::array();
const auto& all_bundles = bundle_copy;
auto strip_prefix = [](const std::vector<std::string>& names) {
json arr = json::array();
for (const auto& name : names)
arr.push_back(boost::filesystem::path(name).filename().string());
return arr;
};
for (const auto& bundle : all_bundles) {
const auto& metadata = bundle.second;
json temp;
temp["id"] = metadata.id;
temp["name"] = metadata.name;
temp["type"] = metadata.bundle_type == Subscribed ? "Subscribed" : metadata.bundle_type == Local ? "Local" : "Default";
temp["author"] = metadata.author;
temp["version"] = metadata.version;
temp["description"] = metadata.description;
temp["path"] = metadata.path;
temp["printers"] = strip_prefix(metadata.printer_presets);
temp["filaments"] = strip_prefix(metadata.filament_presets);
temp["presets"] = strip_prefix(metadata.print_presets);
temp["update_available"] = metadata.update_available;
temp["unauthorized"] = metadata.unauthorized;
res["auto_update_enabled"] = app_config->get_bool("preset_bundle_auto_update");
res["data"].push_back(std::move(temp));
}
wxString strJS = wxString::Format("HandleStudio(%s)", wxString::FromUTF8(res.dump(-1, ' ', false, json::error_handler_t::ignore)));
wxGetApp().CallAfter([this, strJS] { RunScript(strJS); });
}
void PresetBundleDialog::OpenFolder(const std::string& id)
{
wxGetApp().preset_bundle->bundles.ReadLock();
wxString target = _L(wxGetApp().preset_bundle->bundles.m_bundles.find(id)->second.path);
wxGetApp().preset_bundle->bundles.ReadUnlock();
wxFileName fn(target);
if (fn.FileExists())
target = fn.GetPath();
if (target.empty() || !wxFileName::DirExists(target)) {
wxMessageBox(_L("Bundle folder does not exist."), _L("Open Folder"), wxOK | wxICON_WARNING, this);
return;
}
if (!wxLaunchDefaultApplication(target)) {
wxMessageBox(_L("Failed to open folder."), _L("Open Folder"), wxOK | wxICON_ERROR, this);
}
}
void PresetBundleDialog::DeleteBundle(const std::string& id)
{
if (id.empty())
return;
const int rc = wxMessageBox(_L("Delete selected bundle from folder and all presets loaded from it?"), _L("Delete Bundle"),
wxYES_NO | wxNO_DEFAULT | wxICON_WARNING, this);
if (rc != wxYES)
return;
if (!DeleteBundleById(id)) {
wxMessageBox(_L("Failed to remove bundle."), _L("Remove Bundle"), wxOK | wxICON_ERROR, this);
return;
}
wxGetApp().mainframe->update_side_preset_ui();
}
void PresetBundleDialog::UnsubscribeBundle(const std::string& id)
{
if (id.empty())
return;
const int rc = wxMessageBox(_L("Unsubscribe bundle?"), _L("UnsubscribeBundle"), wxYES_NO | wxNO_DEFAULT | wxICON_WARNING, this);
if (rc != wxYES)
return;
if (!UnsubscribeBundleById(id)) {
wxMessageBox(_L("Failed to unsubscribe bundle."), _L("Unsubscribe Bundle"), wxOK | wxICON_ERROR, this);
return;
}
wxGetApp().mainframe->update_side_preset_ui();
}
void PresetBundleDialog::OpenBundleOnCloud(const std::string& id)
{
if (id.empty())
return;
if (!wxGetApp().getAgent())
return;
auto orca_agent = std::dynamic_pointer_cast<OrcaCloudServiceAgent>(wxGetApp().getAgent()->get_cloud_agent());
if (!orca_agent)
return;
wxLaunchDefaultBrowser(wxString::FromUTF8(orca_agent->get_bundle_url(id)));
}
}} // namespace Slic3r::GUI
+102
View File
@@ -0,0 +1,102 @@
#ifndef slic3r_PresetBundleDialog_hpp_
#define slic3r_PresetBundleDialog_hpp_
#include "GUI.hpp"
#include "GUI_Utils.hpp"
#include "libslic3r/AppConfig.hpp"
#include <boost/thread/detail/thread.hpp>
#include <libslic3r/PresetBundle.hpp>
#include <memory>
#include <slic3r/GUI/GUI.hpp>
#include <string>
#include <unordered_map>
#include <wx/dataview.h>
#include <wx/event.h>
#include <wx/language.h>
#include <wx/string.h>
#include <wx/fswatcher.h>
#include <wx/webview.h>
namespace Slic3r { namespace GUI {
#define DESIGN_GRAY900_COLOR wxColour("#363636") // Label color
#define DESIGN_GRAY600_COLOR wxColour("#ACACAC") // Dimmed text color
#define DESIGN_WINDOW_SIZE wxSize(FromDIP(640), FromDIP(640))
#define DESIGN_TITLE_SIZE wxSize(FromDIP(280), -1)
#define DESIGN_COMBOBOX_SIZE wxSize(FromDIP(120), -1)
#define DESIGN_LARGE_COMBOBOX_SIZE wxSize(FromDIP(120), -1)
#define DESIGN_INPUT_SIZE wxSize(FromDIP(120), -1)
#define DESIGN_LEFT_MARGIN 25
#define VERTICAL_GAP_SIZE FromDIP(4)
class PresetBundleDialog : public Slic3r::GUI::DPIDialog
{
public:
PresetBundleDialog(wxWindow* parent,
wxWindowID id = wxID_ANY,
const wxString& title = wxT(""),
const wxPoint& pos = wxDefaultPosition,
const wxSize& size = wxDefaultSize,
long style = wxSYSTEM_MENU | wxCAPTION | wxCLOSE_BOX | wxMAXIMIZE_BOX);
~PresetBundleDialog();
void create();
bool DeleteBundleById(const wxString& id);
bool UnsubscribeBundleById(const std::string& id);
bool seq_top_layer_only_changed() const { return m_seq_top_layer_only_changed; }
bool recreate_GUI() const { return m_recreate_GUI; }
void on_dpi_changed(const wxRect& suggested_rect) override;
// webview utilities
void load_url(wxString& url);
void ListBundles();
void OpenFolder(const std::string& id);
void DeleteBundle(const std::string& id);
void UnsubscribeBundle(const std::string& id);
void OpenBundleOnCloud(const std::string& id);
void OnPresetBundlePage();
// sends command to webview
void RunScript(const wxString& s);
// webview events
void OnScriptMessage(wxWebViewEvent& e);
void StartDialogWorker();
void StopDialogWorker();
void RefreshBundleMap();
bool CheckUpdateCloud();
void OnBundleUpdate(wxCommandEvent& evt);
// true if b>a
bool CompareVer(const std::string& a, const std::string& b);
protected:
wxFileSystemWatcher* m_watcher = nullptr;
void OnFSWatch(wxFileSystemWatcherEvent& e);
bool m_seq_top_layer_only_changed{false};
bool m_recreate_GUI{false};
// Webview
wxWebView* m_browser{nullptr};
std::unordered_map<std::string, BundleMetadata> bundle_copy;
boost::thread m_dialog_worker_thread;
std::shared_ptr<int> m_dialog_worker_token;
std::atomic<bool> m_check_update_pending{false};
std::atomic<bool> m_update_bundles{false};
private:
Slic3r::AppConfig* app_config;
};
}} // namespace Slic3r::GUI
#endif
+110 -17
View File
@@ -1133,16 +1133,21 @@ void PlaterPresetComboBox::update()
std::map<wxString, wxBitmap*> nonsys_presets;
//BBS: add project embedded presets logic
std::map<wxString, wxBitmap*> project_embedded_presets;
// ORCA: add bundle presets
std::map<wxString, wxBitmap*> bundle_presets;
std::map<wxString, wxBitmap *> system_presets;
std::map<wxString, wxBitmap *> uncompatible_presets;
std::unordered_set<std::string> system_printer_models;
std::map<wxString, wxString> preset_descriptions;
std::map<wxString, std::string> preset_filament_vendors;
std::map<wxString, std::string> preset_filament_types;
std::map<wxString, std::string> preset_filament_names; // ORCA
std::map<wxString, std::string> preset_aliases; // ORCA
std::map<wxString, std::string> preset_bundle_ids;
std::map<wxString, std::string> preset_bundle_names;
//BBS: move system to the end
wxString selected_system_preset;
wxString selected_user_preset;
wxString selected_bundle_preset;
wxString tooltip;
const std::deque<Preset>& presets = m_collection->get_presets();
@@ -1170,7 +1175,21 @@ void PlaterPresetComboBox::update()
}
bool single_bar = false;
wxString name = get_preset_name(preset);
wxString name = preset.name;
preset_aliases[name] = get_preset_name(preset).ToStdString(); // ORCA
// Track bundle names for bundled presets
if (preset.is_from_bundle()) {
m_preset_bundle->bundles.ReadLock();
auto bundle_it = m_preset_bundle->bundles.m_bundles.find(preset.bundle_id);
if (bundle_it != m_preset_bundle->bundles.m_bundles.end()) {
preset_bundle_ids[name] = bundle_it->second.id;
preset_bundle_names[name] = bundle_it->second.name;
}
m_preset_bundle->bundles.ReadUnlock();
}
if (m_type == Preset::TYPE_FILAMENT)
{
#if 0
@@ -1192,7 +1211,6 @@ void PlaterPresetComboBox::update()
if (preset_filament_vendors[name] == "Bambu Lab")
preset_filament_vendors[name] = "Bambu";
preset_filament_types[name] = preset.config.option<ConfigOptionStrings>("filament_type")->values.at(0);
preset_filament_names[name] = name.ToStdString(); // ORCA
//}
}
wxBitmap* bmp = get_bmp(preset);
@@ -1214,6 +1232,7 @@ void PlaterPresetComboBox::update()
name = from_u8(is_selected && preset.is_dirty ? Preset::suffix_modified() + printer_model : printer_model);
if (system_printer_models.count(printer_model) == 0) {
preset_aliases[name] = name.ToStdString(); // ORCA
system_presets.emplace(name, bmp);
system_printer_models.insert(printer_model);
}
@@ -1246,6 +1265,15 @@ void PlaterPresetComboBox::update()
tooltip = wxString::FromUTF8(preset.name.c_str());
}
}
// ORCA: add bundle presets
else if (preset.is_from_bundle())
{
bundle_presets.emplace(name, bmp);
if (is_selected) {
selected_bundle_preset = name;
tooltip = get_tooltip(preset);
}
}
else
{
nonsys_presets.emplace(name, bmp);
@@ -1271,11 +1299,11 @@ void PlaterPresetComboBox::update()
"Bambu PLA Galaxy", "Bambu PLA Metal", "Bambu PLA Marble", "Bambu PETG-CF", "Bambu PETG Translucent", "Bambu ABS-GF"};
std::vector<std::string> first_vendors = {"", "Bambu", "Generic"}; // Empty vendor for non-system presets
std::vector<std::string> first_types = {"PLA", "PETG", "ABS", "TPU"};
auto add_presets = [this, &preset_descriptions, &filament_orders, &preset_filament_vendors, &first_vendors, &preset_filament_types, &preset_filament_names, &first_types, &selected_in_ams]
auto add_presets = [this, &preset_descriptions, &filament_orders, &preset_filament_vendors, &first_vendors, &preset_filament_types, &preset_aliases, &preset_bundle_ids, &preset_bundle_names, &first_types, &selected_in_ams]
(std::map<wxString, wxBitmap *> const &presets, wxString const &selected, std::string const &group, wxString const &groupName) {
if (!presets.empty()) {
set_label_marker(Append(_L(group), wxNullBitmap, DD_ITEM_STYLE_SPLIT_ITEM));
if (m_type == Preset::TYPE_FILAMENT) {
if (m_type == Preset::TYPE_FILAMENT || m_type == Preset::TYPE_PRINTER) {
std::vector<std::map<wxString, wxBitmap *>::value_type const*> list(presets.size(), nullptr);
std::transform(presets.begin(), presets.end(), list.begin(), [](auto & pair) { return &pair; });
bool groupByGroup = group != "System presets";
@@ -1283,7 +1311,7 @@ void PlaterPresetComboBox::update()
// if (GetCount() == 1) Clear();
// else SetString(GetCount() - 1, "");
//}
if (group == "System presets" || group == "Unsupported presets")
if (m_type == Preset::TYPE_FILAMENT && (group == "System presets" || group == "Unsupported presets"))
std::sort(list.begin(), list.end(), [&filament_orders, &preset_filament_vendors, &first_vendors, &preset_filament_types, &first_types](auto *l, auto *r) {
{ // Compare order
auto iter1 = std::find(filament_orders.begin(), filament_orders.end(), l->first);
@@ -1306,10 +1334,11 @@ void PlaterPresetComboBox::update()
return l->first < r->first;
});
// ORCA add sorting support for vendor / type for user presets. also non grouped items
if (groupName == "by_vendor" || groupName == "by_type" || groupName == ""){
auto by = groupName == "by_vendor" ? preset_filament_vendors
if (groupName == "by_bundle" || groupName == "by_vendor" || groupName == "by_type" || groupName == ""){
auto by = groupName == "by_bundle" ? preset_bundle_names
: groupName == "by_vendor" ? preset_filament_vendors
: groupName == "by_type" ? preset_filament_types
: preset_filament_names;
: preset_aliases;
std::sort(list.begin(), list.end(), [&by](auto *l, auto *r) {
auto get_key = [&](auto* item) -> std::pair<bool, std::string> {
std::string str = by.count(item->first) ? by.at(item->first) : "";
@@ -1319,20 +1348,29 @@ void PlaterPresetComboBox::update()
auto [l_valid, l_lower] = get_key(l);
auto [r_valid, r_lower] = get_key(r);
return (l_valid != r_valid) ? l_valid > r_valid
: (l_lower != r_lower) ? l_lower < r_lower
: (l_lower != r_lower) ? l_lower < r_lower
: l->first < r->first;
});
}
bool unsupported = group == "Unsupported presets";
for (auto it : list) {
// ORCA add sorting support for vendor / type for user presets
auto groupName2 = groupName == "by_type" ? (preset_filament_types[it->first].empty() ? _L("Unspecified") : preset_filament_types[it->first])
: groupName == "by_vendor" ? (preset_filament_vendors[it->first].empty() ? _L("Unspecified") : preset_filament_vendors[it->first])
: groupByGroup ? groupName
auto groupName2 = groupName == "by_bundle" ? (preset_bundle_names[it->first].empty() ? _L("Unspecified") : preset_bundle_names[it->first])
: groupName == "by_type" ? (preset_filament_types[it->first].empty() ? _L("Unspecified") : preset_filament_types[it->first])
: groupName == "by_vendor" ? (preset_filament_vendors[it->first].empty() ? _L("Unspecified") : preset_filament_vendors[it->first])
: groupByGroup ? groupName
: preset_filament_vendors[it->first];
int index = Append(it->first, *it->second, groupName2, nullptr, unsupported ? DD_ITEM_STYLE_DISABLED : 0);
int index = groupName == "by_bundle"
? Append(preset_aliases[it->first], *it->second,
from_u8(preset_bundle_ids[it->first]), groupName2, nullptr,
unsupported ? DD_ITEM_STYLE_DISABLED : 0)
: Append(preset_aliases[it->first], *it->second, groupName2, nullptr,
unsupported ? DD_ITEM_STYLE_DISABLED : 0);
SetItemAlias(index, it->first);
if (unsupported)
set_label_marker(index, LABEL_ITEM_DISABLED);
else if (m_type == Preset::TYPE_PRINTER && group == "System presets" )
set_label_marker(index, LABEL_ITEM_PRINTER_MODELS);
SetItemTooltip(index, preset_descriptions[it->first]);
bool is_selected = it->first == selected;
validate_selection(is_selected);
@@ -1342,7 +1380,9 @@ void PlaterPresetComboBox::update()
}
} else {
for (std::map<wxString, wxBitmap *>::const_iterator it = presets.begin(); it != presets.end(); ++it) {
SetItemTooltip(Append(it->first, *it->second), preset_descriptions[it->first]);
int index = Append(preset_aliases[it->first], *it->second);
SetItemAlias(index, it->first);
SetItemTooltip(index, preset_descriptions[it->first]);
if (group == "System presets")
set_label_marker(GetCount() - 1, LABEL_ITEM_PRINTER_MODELS);
validate_selection(it->first == selected);
@@ -1360,6 +1400,9 @@ void PlaterPresetComboBox::update()
: group_filament_presets == "3" ? "by_vendor" // Create sub menus with filament vendor
: ""; // Use without sub menu
add_presets(nonsys_presets, selected_user_preset, L("User presets"), group_filament_presets_by);
// ORCA: add bundle presets with sub-dropdown grouping for filament and printer
auto bundle_group_name = (m_type == Preset::TYPE_FILAMENT || m_type == Preset::TYPE_PRINTER) ? "by_bundle" : "";
add_presets(bundle_presets, selected_bundle_preset, L("Bundle presets"), bundle_group_name);
// BBS: move system to the end
add_presets(system_presets, selected_system_preset, L("System presets"), _L("System"));
add_presets(uncompatible_presets, {}, L("Unsupported presets"), _L("Unsupported") + " ");
@@ -1579,7 +1622,10 @@ void TabPresetComboBox::OnSelect(wxCommandEvent &evt)
wxString TabPresetComboBox::get_preset_name(const Preset& preset)
{
return from_u8(preset.label(true));
if (preset.is_from_bundle())
return from_u8(preset.label(false));
else
return from_u8(preset.label(true));
}
// Update the choice UI from the list of presets.
@@ -1598,7 +1644,12 @@ void TabPresetComboBox::update()
std::map<wxString, std::pair<wxBitmap*, bool>> project_embedded_presets;
//BBS: move system to the end
std::map<wxString, std::pair<wxBitmap*, bool>> system_presets;
// ORCA: add bundle presets
std::map<wxString, std::pair<wxBitmap*, bool>> bundle_presets;
std::map<wxString, wxString> preset_descriptions;
std::map<wxString, std::string> preset_aliases; // ORCA
std::map<wxString, std::string> preset_bundle_ids;
std::map<wxString, std::string> preset_bundle_names;
wxString selected = "";
//BBS: move system to the end
@@ -1625,10 +1676,23 @@ void TabPresetComboBox::update()
wxBitmap* bmp = get_bmp(preset);
assert(bmp);
const wxString name = get_preset_name(preset);
const wxString name = preset.name;
preset_aliases[name] = get_preset_name(preset).ToStdString();
if (preset.is_system)
preset_descriptions.emplace(name, from_u8(preset.description));
// ORCA: Track bundle names for bundled presets
if (preset.is_from_bundle()) {
m_preset_bundle->bundles.ReadLock();
auto bundle_it = m_preset_bundle->bundles.m_bundles.find(preset.bundle_id);
if (bundle_it != m_preset_bundle->bundles.m_bundles.end()) {
preset_bundle_ids[name] = bundle_it->second.id;
preset_bundle_names[name] = bundle_it->second.name;
}
m_preset_bundle->bundles.ReadUnlock();
}
if (preset.is_default || preset.is_system) {
//BBS: move system to the end
system_presets.emplace(name, std::pair<wxBitmap *, bool>(bmp, is_enabled));
@@ -1647,6 +1711,13 @@ void TabPresetComboBox::update()
if (i == idx_selected)
selected = name;
}
// ORCA: add bundle presets
else if (preset.is_from_bundle())
{
bundle_presets.emplace(name, std::pair<wxBitmap*, bool>(bmp, is_enabled));
if (i == idx_selected)
selected = name;
}
else
{
std::pair<wxBitmap*, bool> pair(bmp, is_enabled);
@@ -1680,6 +1751,27 @@ void TabPresetComboBox::update()
set_label_marker(Append(_L("User presets"), wxNullBitmap, DD_ITEM_STYLE_SPLIT_ITEM));
for (std::map<wxString, std::pair<wxBitmap*, bool>>::iterator it = nonsys_presets.begin(); it != nonsys_presets.end(); ++it) {
int item_id = Append(it->first, *it->second.first);
SetItemAlias(item_id, it->first);
SetItemTooltip(item_id, preset_descriptions[it->first]);
bool is_enabled = it->second.second;
if (!is_enabled)
set_label_marker(item_id, LABEL_ITEM_DISABLED);
validate_selection(it->first == selected);
}
}
// ORCA: add bundle presets with sub-dropdown grouping
if (!bundle_presets.empty())
{
set_label_marker(Append(_L("Bundle presets"), wxNullBitmap, DD_ITEM_STYLE_SPLIT_ITEM));
for (std::map<wxString, std::pair<wxBitmap*, bool>>::iterator it = bundle_presets.begin(); it != bundle_presets.end(); ++it) {
// Get bundle name for grouping
wxString bundle_name = _L("Unspecified");
if (preset_bundle_names.count(it->first) > 0 && !preset_bundle_names[it->first].empty()) {
bundle_name = preset_bundle_names[it->first];
}
// Use Append with group parameter for sub-dropdown grouping
int item_id = Append(preset_aliases[it->first], *it->second.first, from_u8(preset_bundle_ids[it->first]), bundle_name);
SetItemAlias(item_id, it->first);
SetItemTooltip(item_id, preset_descriptions[it->first]);
bool is_enabled = it->second.second;
if (!is_enabled)
@@ -1693,6 +1785,7 @@ void TabPresetComboBox::update()
set_label_marker(Append(_L("System presets"), wxNullBitmap, DD_ITEM_STYLE_SPLIT_ITEM));
for (std::map<wxString, std::pair<wxBitmap*, bool>>::iterator it = system_presets.begin(); it != system_presets.end(); ++it) {
int item_id = Append(it->first, *it->second.first);
SetItemAlias(item_id, it->first);
SetItemTooltip(item_id, preset_descriptions[it->first]);
bool is_enabled = it->second.second;
if (!is_enabled)
+3 -5
View File
@@ -36,7 +36,7 @@ SavePresetDialog::Item::Item(Preset::Type type, const std::string &suffix, wxBox
m_presets = tab->get_presets();
const Preset &sel_preset = m_presets->get_selected_preset();
std::string preset_name = sel_preset.is_default ? "Untitled" : sel_preset.is_system ? (boost::format(("%1% - %2%")) % sel_preset.name % suffix).str() : sel_preset.name;
std::string preset_name = sel_preset.is_default ? "Untitled" : sel_preset.is_system ? (boost::format(("%1% - %2%")) % sel_preset.name % suffix).str() : sel_preset.is_from_bundle() && !sel_preset.alias.empty() ? sel_preset.alias : sel_preset.name;
// if name contains extension
if (boost::iends_with(preset_name, ".ini")) {
@@ -162,7 +162,7 @@ void SavePresetDialog::Item::update()
}
const Preset *existing = m_presets->find_preset(m_preset_name, false);
if (m_valid_type == Valid && existing && (existing->is_default || existing->is_system)) {
if (m_valid_type == Valid && existing && !existing->can_overwrite()) {
info_line = _L("Overwriting a system profile is not allowed.");
m_valid_type = NoValid;
}
@@ -230,13 +230,11 @@ void SavePresetDialog::Item::update_valid_bmp()
void SavePresetDialog::Item::accept()
{
if (m_valid_type == Warning) {
// BBS add sync info
auto it = m_presets->find_preset(m_preset_name, false);
Preset &current_preset = *it;
current_preset.sync_info = "delete";
if (!current_preset.setting_id.empty()) {
BOOST_LOG_TRIVIAL(info) << "delete preset = " << current_preset.name << ", setting_id = " << current_preset.setting_id;
wxGetApp().delete_preset_from_cloud(current_preset.setting_id);
wxGetApp().delete_preset_from_cloud(current_preset.setting_id, current_preset.file);
}
m_presets->delete_preset(m_preset_name);
}
+6 -5
View File
@@ -2601,12 +2601,13 @@ void SelectMachineDialog::clear_ip_address_config(wxCommandEvent& e)
void SelectMachineDialog::update_user_machine_list()
{
NetworkAgent* m_agent = wxGetApp().getAgent();
if (m_agent && m_agent->is_user_login()) {
boost::thread get_print_info_thread = Slic3r::create_thread([this, token = std::weak_ptr<int>(m_token)] {
const std::string provider = wxGetApp().get_printer_cloud_provider();
if (m_agent && m_agent->is_user_login(provider)) {
boost::thread get_print_info_thread = Slic3r::create_thread([this, token = std::weak_ptr<int>(m_token), provider] {
NetworkAgent* agent = wxGetApp().getAgent();
unsigned int http_code;
std::string body;
int result = agent->get_user_print_info(&http_code, &body);
int result = agent->get_user_print_info(&http_code, &body, provider);
CallAfter([token, this, result, body] {
if (token.expired()) {return;}
if (result == 0) {
@@ -3233,7 +3234,7 @@ void SelectMachineDialog::update_show_status(MachineObject* obj_)
/** error check **/
/* check cloud machine connections */
if (!obj_->is_lan_mode_printer() && !agent->is_server_connected()) {
if (!obj_->is_lan_mode_printer() && !agent->is_server_connected(wxGetApp().get_printer_cloud_provider())) {
show_status(PrintDialogStatus::PrintStatusConnectingServer);
reset_timeout();
return;
@@ -3770,7 +3771,7 @@ void SelectMachineDialog::set_default()
NetworkAgent* agent = wxGetApp().getAgent();
if (agent) {
if (agent->is_user_login()) {
if (agent->is_user_login(wxGetApp().get_printer_cloud_provider())) {
show_status(PrintDialogStatus::PrintStatusInit);
}
}
+7 -6
View File
@@ -96,7 +96,7 @@ MachineObjectPanel::~MachineObjectPanel() {}
void MachineObjectPanel::show_bind_dialog()
{
if (wxGetApp().is_user_login()) {
if (wxGetApp().is_user_login(wxGetApp().get_printer_cloud_provider())) {
BindMachineDialog dlg;
dlg.update_machine_info(m_info);
dlg.ShowModal();
@@ -387,13 +387,14 @@ void SelectMachinePopup::Popup(wxWindow *WXUNUSED(focus))
m_refresh_timer->Start(MACHINE_LIST_REFRESH_INTERVAL);
}
if (wxGetApp().is_user_login()) {
const std::string provider = wxGetApp().get_printer_cloud_provider();
if (wxGetApp().is_user_login(provider)) {
if (!get_print_info_thread) {
get_print_info_thread = new boost::thread(Slic3r::create_thread([this, token = std::weak_ptr<int>(m_token)] {
get_print_info_thread = new boost::thread(Slic3r::create_thread([this, token = std::weak_ptr<int>(m_token), provider] {
NetworkAgent* agent = wxGetApp().getAgent();
unsigned int http_code;
std::string body;
int result = agent->get_user_print_info(&http_code, &body);
int result = agent->get_user_print_info(&http_code, &body, provider);
CallAfter([token, this, result, body]() {
if (token.expired()) {return;}
if (result == 0) {
@@ -511,7 +512,7 @@ void SelectMachinePopup::update_other_devices()
/* do not show printer bind state is empty */
if (!mobj->is_avaliable()) continue;
if (!wxGetApp().is_user_login() && !mobj->is_lan_mode_printer())
if (!wxGetApp().is_user_login(wxGetApp().get_printer_cloud_provider()) && !mobj->is_lan_mode_printer())
continue;
/* do not show printer in my list */
@@ -983,7 +984,7 @@ void EditDevNameDialog::on_edit_name(wxCommandEvent &e)
auto utf8_str = new_dev_name.ToUTF8();
auto name = std::string(utf8_str.data(), utf8_str.length());
if (m_info)
dev->modify_device_name(m_info->get_dev_id(), name);
dev->modify_device_name(m_info->get_dev_id(), name, wxGetApp().get_printer_cloud_provider());
}
DPIDialog::EndModal(wxID_CLOSE);
}
+8 -6
View File
@@ -1013,12 +1013,13 @@ void SendToPrinterDialog::clear_ip_address_config(wxCommandEvent& e)
void SendToPrinterDialog::update_user_machine_list()
{
NetworkAgent* m_agent = wxGetApp().getAgent();
if (m_agent && m_agent->is_user_login()) {
boost::thread get_print_info_thread = Slic3r::create_thread([this, token = std::weak_ptr<int>(m_token)] {
const std::string provider = wxGetApp().get_printer_cloud_provider();
if (m_agent && m_agent->is_user_login(provider)) {
boost::thread get_print_info_thread = Slic3r::create_thread([this, token = std::weak_ptr<int>(m_token), provider] {
NetworkAgent* agent = wxGetApp().getAgent();
unsigned int http_code;
std::string body;
int result = agent->get_user_print_info(&http_code, &body);
int result = agent->get_user_print_info(&http_code, &body, provider);
CallAfter([token, this, result, body] {
if (token.expired()) {return;}
if (result == 0) {
@@ -1224,11 +1225,12 @@ void SendToPrinterDialog::update_show_status()
DeviceManager* dev = Slic3r::GUI::wxGetApp().getDeviceManager();
if (!agent) return;
if (!dev) return;
const std::string provider = wxGetApp().get_printer_cloud_provider();
MachineObject* obj_ = dev->get_my_machine(m_printer_last_select);
if (!obj_) {
if (agent) {
if (agent->is_user_login()) {
if (agent->is_user_login(provider)) {
show_status(PrintDialogStatus::PrintStatusInvalidPrinter);
}
}
@@ -1237,7 +1239,7 @@ void SendToPrinterDialog::update_show_status()
/* check cloud machine connections */
if (!obj_->is_lan_mode_printer()) {
if (!agent->is_server_connected()) {
if (!agent->is_server_connected(provider)) {
show_status(PrintDialogStatus::PrintStatusConnectingServer);
reset_timeout();
return;
@@ -1564,7 +1566,7 @@ void SendToPrinterDialog::set_default()
NetworkAgent* agent = wxGetApp().getAgent();
if (agent) {
if (agent->is_user_login()) {
if (agent->is_user_login(wxGetApp().get_printer_cloud_provider())) {
show_status(PrintDialogStatus::PrintStatusInit);
}
}
+8 -6
View File
@@ -2054,12 +2054,13 @@ void SyncAmsInfoDialog::Enable_Auto_Refill(bool enable)
void SyncAmsInfoDialog::update_user_machine_list()
{
NetworkAgent *m_agent = wxGetApp().getAgent();
if (m_agent && m_agent->is_user_login()) {
boost::thread get_print_info_thread = Slic3r::create_thread([this, token = std::weak_ptr(m_token)] {
const std::string provider = wxGetApp().get_printer_cloud_provider();
if (m_agent && m_agent->is_user_login(provider)) {
boost::thread get_print_info_thread = Slic3r::create_thread([this, token = std::weak_ptr(m_token), provider] {
NetworkAgent *agent = wxGetApp().getAgent();
unsigned int http_code;
std::string body;
int result = agent->get_user_print_info(&http_code, &body);
int result = agent->get_user_print_info(&http_code, &body, provider);
CallAfter([token, this, result, body] {
if (token.expired()) { return; }
if (result == 0) {
@@ -2307,13 +2308,14 @@ void SyncAmsInfoDialog::update_show_status()
return;
}
if (!dev) return;
const std::string provider = wxGetApp().get_printer_cloud_provider();
// blank plate has no valid gcode file
if (is_must_finish_slice_then_connected_printer()) { return; }
MachineObject * obj_ = dev->get_selected_machine();
if (!obj_) {
if (agent) {
if (agent->is_user_login()) {
if (agent->is_user_login(provider)) {
show_status(PrintDialogStatus::PrintStatusInvalidPrinter);
}
}
@@ -2322,7 +2324,7 @@ void SyncAmsInfoDialog::update_show_status()
/* check cloud machine connections */
if (!obj_->is_lan_mode_printer()) {
if (!agent->is_server_connected()) {
if (!agent->is_server_connected(provider)) {
show_status(PrintDialogStatus::PrintStatusConnectingServer);
reset_timeout();
return;
@@ -2585,7 +2587,7 @@ void SyncAmsInfoDialog::set_default(bool hide_some)
NetworkAgent *agent = wxGetApp().getAgent();
if (agent) {
if (!hide_some) {
if (agent->is_user_login()) {
if (agent->is_user_login(wxGetApp().get_printer_cloud_provider())) {
show_status(PrintDialogStatus::PrintStatusInit);
}
}
+23 -11
View File
@@ -33,6 +33,7 @@
#include "GUI_App.hpp"
#include "GUI_ObjectList.hpp"
#include "slic3r/Utils/PresetUpdater.hpp"
#include "Plater.hpp"
#include "MainFrame.hpp"
#include "format.hpp"
@@ -229,9 +230,17 @@ void Tab::create_preset_tab()
if (m_type == Preset::TYPE_PRINTER && !m_presets_choice->is_selected_physical_printer())
m_preset_bundle->physical_printers.unselect_printer();
// select preset
std::string preset_name = m_presets_choice->GetString(selection).ToUTF8().data();
select_preset(Preset::remove_suffix_modified(preset_name));
// select preset — prefer stored internal name to avoid alias collisions
wxString stored_name = m_presets_choice->GetItemAlias(selection);
std::string preset_name;
if (!stored_name.empty()) {
preset_name = Preset::remove_suffix_modified(stored_name.ToUTF8().data());
} else {
std::string selected_label = Preset::remove_suffix_modified(
m_presets_choice->GetString(selection).ToUTF8().data());
preset_name = m_preset_bundle->get_preset_name_by_alias(m_type, selected_label);
}
select_preset(preset_name);
}
});
}
@@ -2053,6 +2062,12 @@ void Tab::on_presets_changed()
// Check if printer agent needs switching
if (m_type == Preset::TYPE_PRINTER) {
wxGetApp().switch_printer_agent();
// Trigger per-vendor preset update check
const Preset& printer_preset = m_preset_bundle->printers.get_edited_preset();
if (printer_preset.vendor) {
wxGetApp().get_preset_updater()->check_vendor_update(printer_preset.vendor->id);
}
}
bool is_bbl_vendor_preset = m_preset_bundle->is_bbl_vendor();
@@ -5676,7 +5691,7 @@ void Tab::update_btns_enabling()
// and any user preset
const Preset& preset = m_presets->get_edited_preset();
m_btn_delete_preset->Show((m_type == Preset::TYPE_PRINTER && m_preset_bundle->physical_printers.has_selection())
|| (!preset.is_default && !preset.is_system));
|| preset.can_overwrite());
//if (m_btn_edit_ph_printer)
// m_btn_edit_ph_printer->SetToolTip( m_preset_bundle->physical_printers.has_selection() ?
@@ -5857,7 +5872,7 @@ bool Tab::select_preset(
Preset &current_preset = m_presets->get_selected_preset();
// Obtain compatible filament and process presets for printers
if (m_preset_bundle && m_presets->get_preset_base(current_preset) == &current_preset && printer_tab && !current_preset.is_system) {
if (m_preset_bundle && m_presets->get_preset_base(current_preset) == &current_preset && printer_tab && !current_preset.is_system && !current_preset.is_from_bundle()) {
delete_third_printer = true;
for (const Preset &preset : m_preset_bundle->filaments.get_presets()) {
if (preset.is_compatible && !preset.is_default) {
@@ -5865,7 +5880,6 @@ bool Tab::select_preset(
filament_presets.push_front(preset);
else
filament_presets.push_back(preset);
if (!preset.setting_id.empty()) { m_preset_bundle->filaments.set_sync_info_and_save(preset.name, preset.setting_id, "delete", 0); }
}
}
for (const Preset &preset : m_preset_bundle->prints.get_presets()) {
@@ -5874,13 +5888,11 @@ bool Tab::select_preset(
process_presets.push_front(preset);
else
process_presets.push_back(preset);
if (!preset.setting_id.empty()) { m_preset_bundle->filaments.set_sync_info_and_save(preset.name, preset.setting_id, "delete", 0); }
}
}
}
if (!current_preset.setting_id.empty()) {
m_presets->set_sync_info_and_save(current_preset.name, current_preset.setting_id, "delete", 0);
wxGetApp().delete_preset_from_cloud(current_preset.setting_id);
wxGetApp().delete_preset_from_cloud(current_preset.setting_id, current_preset.file);
}
BOOST_LOG_TRIVIAL(info) << "delete preset = " << current_preset.name << ", setting_id = " << current_preset.setting_id;
BOOST_LOG_TRIVIAL(info) << boost::format("will delete current preset...");
@@ -5971,7 +5983,7 @@ bool Tab::select_preset(
for (const Preset &preset : filament_presets) {
if (!preset.setting_id.empty()) {
wxGetApp().delete_preset_from_cloud(preset.setting_id);
wxGetApp().delete_preset_from_cloud(preset.setting_id, preset.file);
}
BOOST_LOG_TRIVIAL(info) << "delete filament preset = " << preset.name << ", setting_id = " << preset.setting_id;
preset_bundle->filaments.delete_preset(preset.name);
@@ -5979,7 +5991,7 @@ bool Tab::select_preset(
for (const Preset &preset : process_presets) {
if (!preset.setting_id.empty()) {
wxGetApp().delete_preset_from_cloud(preset.setting_id);
wxGetApp().delete_preset_from_cloud(preset.setting_id, preset.file);
}
BOOST_LOG_TRIVIAL(info) << "delete print preset = " << preset.name << ", setting_id = " << preset.setting_id;
preset_bundle->prints.delete_preset(preset.name);
+1
View File
@@ -437,6 +437,7 @@ protected:
// return true if cancelled
bool tree_sel_change_delayed(wxCommandEvent& event);
void on_presets_changed();
void update_printer_agent_if_needed();
void build_preset_description_line(ConfigOptionsGroup* optgroup);
void update_preset_description_line();
void update_frequently_changed_parameters();
+2 -2
View File
@@ -1087,7 +1087,7 @@ bool UnsavedChangesDialog::save(PresetCollection* dependent_presets, bool show_s
// for system/default/external presets we should take an edited name
//BBS: add project embedded preset logic and refine is_external
bool save_to_project = false;
if (preset.is_system || preset.is_default) {
if (!preset.can_overwrite()) {
//if (preset.is_system || preset.is_default || preset.is_external) {
SavePresetDialog save_dlg(this, preset.type);
if (save_dlg.ShowModal() != wxID_OK) {
@@ -1114,7 +1114,7 @@ bool UnsavedChangesDialog::save(PresetCollection* dependent_presets, bool show_s
if (tab->supports_printer_technology(printer_technology) && tab->current_preset_is_dirty()) {
const Preset& preset = tab->get_presets()->get_edited_preset();
//BBS: add project embedded preset logic and refine is_external
if (preset.is_system || preset.is_default)
if (!preset.can_overwrite())
//if (preset.is_system || preset.is_default || preset.is_external)
types_for_save.emplace_back(preset.type);
+2 -2
View File
@@ -62,7 +62,7 @@ int UserManager::parse_json(std::string payload)
GUI::MessageDialog msgdialog(nullptr, _L("Log in successful."), "", wxAPPLY | wxOK);
msgdialog.ShowModal();
}
dev->update_user_machine_list_info();
dev->update_user_machine_list_info(GUI::wxGetApp().get_printer_cloud_provider());
dev->set_selected_machine(dev_id);
return 0;
}
@@ -75,4 +75,4 @@ int UserManager::parse_json(std::string payload)
return -1;
}
} // namespace Slic3r
} // namespace Slic3r
+77 -34
View File
@@ -509,7 +509,6 @@ void GuideFrame::OnScriptMessage(wxWebViewEvent &evt)
SaveProfile();
std::string oldregion = m_ProfileJson["region"];
bool bLogin = false;
if (m_Region != oldregion) {
AppConfig* config = GUI::wxGetApp().app_config;
std::string country_code = config->get_country_code();
@@ -517,7 +516,6 @@ void GuideFrame::OnScriptMessage(wxWebViewEvent &evt)
if (agent) {
agent->set_country_code(country_code);
if (wxGetApp().is_user_login()) {
bLogin = true;
agent->user_logout();
}
}
@@ -526,10 +524,7 @@ void GuideFrame::OnScriptMessage(wxWebViewEvent &evt)
this->EndModal(wxID_OK);
if (InstallNetplugin)
GUI::wxGetApp().CallAfter([this] { GUI::wxGetApp().ShowDownNetPluginDlg(); });
if (bLogin)
GUI::wxGetApp().CallAfter([this] { login(); });
GUI::wxGetApp().CallAfter([] { GUI::wxGetApp().ShowDownNetPluginDlg(); });
}
else if (strCmd == "user_guide_cancel") {
this->EndModal(wxID_CANCEL);
@@ -790,11 +785,9 @@ bool GuideFrame::apply_config(AppConfig *app_config, PresetBundle *preset_bundle
!wxGetApp().check_and_keep_current_preset_changes(caption, header, act_btns, &apply_keeped_changes))
return false;
if (install_bundles.size() > 0) {
// Install bundles from resources.
// Don't create snapshot - we've already done that above if applicable.
if (! updater->install_bundles_rsrc(std::move(install_bundles), false))
return false;
// If there are bundles to install, they will be installed by apply_vendor_config
if (!install_bundles.empty()) {
BOOST_LOG_TRIVIAL(info) << "Will install " << install_bundles.size() << " vendor bundles from resources";
} else {
BOOST_LOG_TRIVIAL(info) << "No bundles need to be installed from resource directory";
}
@@ -884,32 +877,82 @@ bool GuideFrame::apply_config(AppConfig *app_config, PresetBundle *preset_bundle
// Not switch filament
//get_first_added_material_preset(AppConfig::SECTION_FILAMENTS, first_added_filament);
//update the app_config
app_config->set_section(AppConfig::SECTION_FILAMENTS, enabled_filaments);
app_config->set_vendors(m_appconfig_new);
// ORCA: functionality moved to PresetBundle::apply_vendor_config; keeping for future reference
// // For each @System filament, check if a vendor-specific override exists
// // in the loaded profiles. If so, replace the @System variant with the
// // override (e.g. replace "Generic ABS @System" with BBL "Generic ABS").
// // When printers from the default bundle are also selected, keep @System
// // too since those printers need it.
// static const std::string system_suffix = " @System";
// auto it_default = enabled_vendors.find(PresetBundle::ORCA_DEFAULT_BUNDLE);
// bool has_default_bundle_printer = it_default != enabled_vendors.end() && !it_default->second.empty();
// bool has_filament_profiles = m_ProfileJson.contains("filament");
if (check_unsaved_preset_changes)
preset_bundle->load_presets(*app_config, ForwardCompatibilitySubstitutionRule::Enable,
{preferred_model, preferred_variant, first_added_filament, std::string()});
// // Check if any non-default vendor has selected printers
// bool has_vendor_printer = false;
// for (const auto& [vendor, models] : enabled_vendors) {
// if (vendor != PresetBundle::ORCA_DEFAULT_BUNDLE && !models.empty()) {
// has_vendor_printer = true;
// break;
// }
// }
// If the active filament is not in the wizard-selected filaments, switch to the first
// compatible wizard-selected filament. This handles the first-run case where load_presets
// falls back to "Generic PLA" even though the user selected a different filament.
bool active_filament_selected = enabled_filaments.empty()
|| enabled_filaments.count(preset_bundle->filament_presets.front()) > 0;
if (!active_filament_selected) {
for (const auto& [filament_name, _] : enabled_filaments) {
const Preset* preset = preset_bundle->filaments.find_preset(filament_name);
if (preset && preset->is_visible && preset->is_compatible) {
preset_bundle->filaments.select_preset_by_name(filament_name, true);
preset_bundle->filament_presets.front() = preset_bundle->filaments.get_selected_preset_name();
break;
}
}
}
// std::map<std::string, std::string> supplemented_filaments;
// for (const auto& [name, value] : enabled_filaments) {
// if (name.size() > system_suffix.size() &&
// name.compare(name.size() - system_suffix.size(), system_suffix.size(), system_suffix) == 0) {
// std::string short_name = name.substr(0, name.size() - system_suffix.size());
// if (has_vendor_printer && has_filament_profiles && m_ProfileJson["filament"].contains(short_name)) {
// supplemented_filaments[short_name] = value;
// BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << " Replacing @System filament: '" << name << "' -> '" << short_name << "'";
// if (has_default_bundle_printer) {
// supplemented_filaments[name] = value;
// BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << " Also keeping '" << name << "' for default bundle printers";
// }
// continue;
// }
// }
// supplemented_filaments[name] = value;
// }
// Update the selections from the compatibilty.
preset_bundle->export_selections(*app_config);
// //update the app_config
// app_config->set_section(AppConfig::SECTION_FILAMENTS, supplemented_filaments);
// app_config->set_vendors(m_appconfig_new);
// if (check_unsaved_preset_changes)
// preset_bundle->load_presets(*app_config, ForwardCompatibilitySubstitutionRule::Enable,
// {preferred_model, preferred_variant, first_added_filament, std::string()});
// // If the active filament is not in the wizard-selected filaments, switch to the first
// // compatible wizard-selected filament. This handles the first-run case where load_presets
// // falls back to "Generic PLA" even though the user selected a different filament.
// bool active_filament_selected = supplemented_filaments.empty()
// || supplemented_filaments.count(preset_bundle->filament_presets.front()) > 0;
// if (!active_filament_selected) {
// for (const auto& [filament_name, _] : supplemented_filaments) {
// const Preset* preset = preset_bundle->filaments.find_preset(filament_name);
// if (preset && preset->is_visible && preset->is_compatible) {
// preset_bundle->filaments.select_preset_by_name(filament_name, true);
// preset_bundle->filament_presets.front() = preset_bundle->filaments.get_selected_preset_name();
// break;
// }
// }
// }
// // Update the selections from the compatibilty.
// preset_bundle->export_selections(*app_config);
BOOST_LOG_TRIVIAL(info) << "calling apply_vendor_config from WebGuideDialog";
// Call the Core library function to apply vendor configuration
// This handles bundle installation, filament @System substitution, AppConfig updates, and preset loading
if (!preset_bundle->apply_vendor_config(
enabled_vendors,
enabled_filaments,
app_config,
true,
preferred_model,
preferred_variant))
return false;
return true;
}
+87 -38
View File
@@ -17,6 +17,8 @@
#include <wx/wfstream.h>
#include <boost/cast.hpp>
#include <boost/asio.hpp>
#include <boost/algorithm/string/replace.hpp>
#include <boost/lexical_cast.hpp>
#include <nlohmann/json.hpp>
@@ -40,13 +42,78 @@ END_EVENT_TABLE()
int ZUserLogin::web_sequence_id = 20000;
ZUserLogin::ZUserLogin() : wxDialog((wxWindow *) (wxGetApp().mainframe), wxID_ANY, "OrcaSlicer")
namespace {
int reserve_loopback_port()
{
try {
boost::asio::io_service io_service;
boost::asio::ip::tcp::acceptor acceptor(io_service, {boost::asio::ip::tcp::v4(), 0});
return static_cast<int>(acceptor.local_endpoint().port());
} catch (...) {
return 0;
}
}
std::string rewrite_loopback_url(std::string url, int port)
{
if (port <= 0) {
return url;
}
const std::string old_port = std::to_string(LOCALHOST_PORT);
const std::string new_port = std::to_string(port);
boost::replace_all(url, std::string(LOCALHOST_URL) + old_port, std::string(LOCALHOST_URL) + new_port);
boost::replace_all(url, "http://127.0.0.1:" + old_port, "http://127.0.0.1:" + new_port);
boost::replace_all(url, "http%3A%2F%2Flocalhost%3A" + old_port, "http%3A%2F%2Flocalhost%3A" + new_port);
boost::replace_all(url, "http%3A%2F%2F127.0.0.1%3A" + old_port, "http%3A%2F%2F127.0.0.1%3A" + new_port);
return url;
}
}
int ZUserLogin::ensure_loopback_port()
{
if (m_loopback_port <= 0) {
m_loopback_port = reserve_loopback_port();
}
int port = m_loopback_port > 0 ? m_loopback_port : LOCALHOST_PORT;
wxGetApp().start_http_server(port, m_cloud_agent->get_id());
return port;
}
ZUserLogin::ZUserLogin(std::shared_ptr<ICloudServiceAgent> cloud_agent)
: wxDialog((wxWindow*) (wxGetApp().mainframe), wxID_ANY, "OrcaSlicer"), m_cloud_agent(cloud_agent)
{
SetBackgroundColour(*wxWHITE);
const auto bblnetwork_enabled =wxGetApp().app_config->get_bool("installed_networking");
// Url
NetworkAgent* agent = wxGetApp().getAgent();
if (!agent && bblnetwork_enabled) {
if (!m_cloud_agent) {
wxBoxSizer* m_sizer_main = new wxBoxSizer(wxVERTICAL);
auto m_line_top = new wxPanel(this, wxID_ANY, wxDefaultPosition, wxSize(-1, 1));
m_line_top->SetBackgroundColour(wxColour(166, 169, 170));
m_sizer_main->Add(m_line_top, 0, wxEXPAND, 0);
auto* m_message = new wxStaticText(this, wxID_ANY,
_L("Cloud agent is not available. Please restart OrcaSlicer and try again."),
wxDefaultPosition, wxDefaultSize, 0);
m_message->SetForegroundColour(*wxBLACK);
m_message->Wrap(FromDIP(360));
m_sizer_main->Add(m_message, 0, wxALIGN_CENTER | wxALL, FromDIP(15));
m_sizer_main->Add(0, 0, 1, wxBOTTOM, 10);
SetSizer(m_sizer_main);
m_sizer_main->SetSizeHints(this);
Layout();
Fit();
CentreOnParent();
wxGetApp().UpdateDlgDarkUI(this);
return;
}
const auto bblnetwork_enabled = wxGetApp().app_config->get_bool("installed_networking");
if (m_cloud_agent->get_id() == BBL_CLOUD_PROVIDER && !bblnetwork_enabled) {
SetBackgroundColour(*wxWHITE);
@@ -74,19 +141,15 @@ ZUserLogin::ZUserLogin() : wxDialog((wxWindow *) (wxGetApp().mainframe), wxID_AN
Layout();
Fit();
CentreOnParent();
}
else {
// Get the login URL from the cloud service agent
} else {
// Get the login URL from the injected cloud service agent
wxString strlang = wxGetApp().current_language_code_safe();
strlang.Replace("_", "-");
TargetUrl = wxString::FromUTF8(agent->get_cloud_login_url(strlang.ToStdString()));
m_networkOk = TargetUrl.StartsWith("file://");
TargetUrl = wxString::FromUTF8(m_cloud_agent->get_cloud_login_url(strlang.ToStdString()));
BOOST_LOG_TRIVIAL(info) << "login url = " << TargetUrl.ToStdString();
m_bbl_user_agent = wxString::Format("BBL-Slicer/v%s", SLIC3R_VERSION);
// set the frame icon
m_bbl_user_agent = wxString::Format("BBL-Slicer/v%s", wxGetApp().get_bbl_client_version());
// Create the webview
m_browser = WebView::CreateWebView(this, TargetUrl);
@@ -97,12 +160,6 @@ ZUserLogin::ZUserLogin() : wxDialog((wxWindow *) (wxGetApp().mainframe), wxID_AN
m_browser->Hide();
m_browser->SetSize(0, 0);
// Log backend information
// wxLogMessage(wxWebView::GetBackendVersionInfo().ToString());
// wxLogMessage("Backend: %s Version: %s",
// m_browser->GetClassInfo()->GetClassName(),wxWebView::GetBackendVersionInfo().ToString());
// wxLogMessage("User Agent: %s", m_browser->GetUserAgent());
// Connect the webview events
Bind(wxEVT_WEBVIEW_NAVIGATING, &ZUserLogin::OnNavigationRequest, this, m_browser->GetId());
Bind(wxEVT_WEBVIEW_NAVIGATED, &ZUserLogin::OnNavigationComplete, this, m_browser->GetId());
@@ -113,19 +170,15 @@ ZUserLogin::ZUserLogin() : wxDialog((wxWindow *) (wxGetApp().mainframe), wxID_AN
Bind(wxEVT_WEBVIEW_FULLSCREEN_CHANGED, &ZUserLogin::OnFullScreenChanged, this, m_browser->GetId());
Bind(wxEVT_WEBVIEW_SCRIPT_MESSAGE_RECEIVED, &ZUserLogin::OnScriptMessage, this, m_browser->GetId());
// Connect the idle events
// Bind(wxEVT_IDLE, &ZUserLogin::OnIdle, this);
// Bind(wxEVT_CLOSE_WINDOW, &ZUserLogin::OnClose, this);
// 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;
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);
}
@@ -221,8 +274,7 @@ void ZUserLogin::OnDocumentLoaded(wxWebViewEvent &evt)
{
// Only notify if the document is the main frame, not a subframe
wxString tmpUrl = evt.GetURL();
NetworkAgent* agent = wxGetApp().getAgent();
std::string strHost = agent->get_cloud_service_host();
std::string strHost = m_cloud_agent->get_cloud_service_host();
if (tmpUrl.StartsWith("file://") || tmpUrl.Contains(strHost)) {
m_networkOk = true;
@@ -270,11 +322,10 @@ void ZUserLogin::OnScriptMessage(wxWebViewEvent &evt)
json j = json::parse(into_u8(str_input));
wxString strCmd = j["command"];
NetworkAgent* agent = wxGetApp().getAgent();
if (agent && strCmd == "get_login_cmd" && agent->get_cloud_agent()) {
if (m_cloud_agent && strCmd == "get_login_cmd") {
// Return login config (backend_url, apikey, pkce)
// WebView handles provider selection internally
std::string login_cmd = agent->build_login_cmd();
std::string login_cmd = m_cloud_agent->build_login_cmd();
m_loopback_port = 0;
try {
json cfg = json::parse(login_cmd);
@@ -329,13 +380,11 @@ void ZUserLogin::OnScriptMessage(wxWebViewEvent &evt)
// Handle message after modal dialog ends to avoid deadlock
// Use wxTheApp->CallAfter to ensure it runs after modal loop exits
wxTheApp->CallAfter([message_json]() {
wxGetApp().handle_script_message(message_json);
});
const auto provider = m_cloud_agent->get_id();
wxTheApp->CallAfter([message_json, provider]() { wxGetApp().handle_script_message(message_json, provider); });
}
else if (strCmd == "get_localhost_url") {
int loopback_port = m_loopback_port > 0 ? m_loopback_port : LOCALHOST_PORT;
wxGetApp().start_http_server(loopback_port);
int loopback_port = ensure_loopback_port();
std::string sequence_id = j["sequence_id"].get<std::string>();
CallAfter([this, sequence_id] {
json ack_j;
@@ -351,8 +400,8 @@ void ZUserLogin::OnScriptMessage(wxWebViewEvent &evt)
else if (strCmd == "thirdparty_login") {
if (j["data"].contains("url")) {
std::string jump_url = j["data"]["url"].get<std::string>();
int loopback_port = m_loopback_port > 0 ? m_loopback_port : LOCALHOST_PORT;
wxGetApp().start_http_server(loopback_port);
int loopback_port = ensure_loopback_port();
jump_url = rewrite_loopback_url(jump_url, loopback_port);
CallAfter([this, jump_url] {
wxString url = wxString::FromUTF8(jump_url);
wxLaunchDefaultBrowser(url);
+6 -1
View File
@@ -28,12 +28,14 @@
#include <wx/tbarbase.h>
#include "wx/textctrl.h"
#include "slic3r/Utils/ICloudServiceAgent.hpp"
namespace Slic3r { namespace GUI {
class ZUserLogin : public wxDialog
{
public:
ZUserLogin();
explicit ZUserLogin(std::shared_ptr<ICloudServiceAgent> cloud_agent);
virtual ~ZUserLogin();
void load_url(wxString &url);
@@ -84,6 +86,9 @@ private:
wxString m_bbl_user_agent;
std::shared_ptr<ICloudServiceAgent> m_cloud_agent;
int ensure_loopback_port();
DECLARE_EVENT_TABLE()
};
+29 -2
View File
@@ -417,8 +417,13 @@ void WebViewPanel::OnClose(wxCloseEvent& evt)
void WebViewPanel::OnFreshLoginStatus(wxTimerEvent &event)
{
auto mainframe = Slic3r::GUI::wxGetApp().mainframe;
if (mainframe && mainframe->m_webview == this)
Slic3r::GUI::wxGetApp().get_login_info();
if (mainframe && mainframe->m_webview == this) {
Slic3r::GUI::wxGetApp().get_login_info(ORCA_CLOUD_PROVIDER);
auto* app_config = Slic3r::GUI::wxGetApp().app_config;
if (app_config && app_config->has_cloud_provider(BBL_CLOUD_PROVIDER)) {
Slic3r::GUI::wxGetApp().get_login_info(BBL_CLOUD_PROVIDER);
}
}
}
void WebViewPanel::SetLoginPanelVisibility(bool bshow)
@@ -509,6 +514,27 @@ void WebViewPanel::ShowNetpluginTip()
RunScript(strJS);
}
void WebViewPanel::SendCloudProvidersInfo()
{
auto* app_config = wxGetApp().app_config;
if (!app_config)
return;
auto providers = app_config->get_cloud_providers();
json j;
j["command"] = "cloud_providers_info";
json data;
json provider_array = json::array();
for (const auto& p : providers) {
provider_array.push_back(p);
}
data["providers"] = provider_array;
j["data"] = data;
wxString strJS = wxString::Format("window.postMessage(%s)", j.dump());
RunScript(strJS);
}
void WebViewPanel::get_design_staffpick(int offset, int limit, std::function<void(std::string)> callback)
{
// auto host = wxGetApp().get_http_url(wxGetApp().app_config->get_country_code(), "v1/design-service/design/staffpick");
@@ -613,6 +639,7 @@ void WebViewPanel::OnDocumentLoaded(wxWebViewEvent& evt)
wxLogMessage("%s", "Document loaded; url='" + evt.GetURL() + "'");
}
UpdateState();
SendCloudProvidersInfo();
}
void WebViewPanel::OnTitleChanged(wxWebViewEvent &evt)
+1
View File
@@ -99,6 +99,7 @@ public:
void OpenModelDetail(std::string id, NetworkAgent *agent);
void SendLoginInfo();
void ShowNetpluginTip();
void SendCloudProvidersInfo();
void get_design_staffpick(int offset, int limit, std::function<void(std::string)> callback);
int get_model_mall_detail_url(std::string *url, std::string id);
+23 -1
View File
@@ -210,9 +210,19 @@ int ComboBox::Append(const wxString &text,
}
int ComboBox::Append(const wxString &text, const wxBitmap &bitmap, const wxString &group, void *clientData, int style)
{
return Append(text, bitmap, group, group, clientData, style);
}
int ComboBox::Append(const wxString &text,
const wxBitmap &bitmap,
const wxString &group_key,
const wxString &group_label,
void *clientData,
int style)
{
auto valid_bit_map = (&bitmap && bitmap.IsOk()) ? bitmap : wxNullBitmap;
Item item{text, wxEmptyString, valid_bit_map, valid_bit_map, clientData, group};
Item item{text, wxEmptyString, valid_bit_map, valid_bit_map, clientData, group_key, group_label};
item.style = style;
items.push_back(item);
SetClientDataType(wxClientData_Void);
@@ -290,6 +300,18 @@ void ComboBox::SetItemTooltip(unsigned int n, wxString const &value) {
if (n == drop.GetSelection()) drop.SetToolTip(value);
}
wxString ComboBox::GetItemAlias(unsigned int n) const
{
if (n >= items.size()) return wxString();
return items[n].alias;
}
void ComboBox::SetItemAlias(unsigned int n, wxString const &value)
{
if (n >= items.size()) return;
items[n].alias = value;
}
wxBitmap ComboBox::GetItemBitmap(unsigned int n) { return items[n].icon; }
void ComboBox::SetItemBitmap(unsigned int n, wxBitmap const &bitmap)
+6
View File
@@ -37,6 +37,12 @@ public:
int Append(const wxString &item, const wxBitmap &bitmap = wxNullBitmap, int item_style = 0);
int Append(const wxString &item, const wxBitmap &bitmap, void *clientData, int item_style = 0);
int Append(const wxString &item, const wxBitmap &bitmap, const wxString &group, void *clientData = nullptr, int item_style = 0);
int Append(const wxString& item,
const wxBitmap& bitmap,
const wxString& group_key,
const wxString& group_label,
void* clientData = nullptr,
int item_style = 0);
int SetItems(const std::vector<DropDown::Item>& the_items);
+27 -27
View File
@@ -340,14 +340,14 @@ void DropDown::render(wxDC &dc)
states2 &= ~StateColor::Enabled;
// Skip by group
if (group.IsEmpty()) {
if (!item.group.IsEmpty()) {
if (groups.find(item.group) != groups.end())
if (!item.group_key.IsEmpty()) {
if (groups.find(item.group_key) != groups.end())
continue;
groups.insert(item.group);
if (!item.group.IsEmpty()) {
groups.insert(item.group_key);
if (!item.group_key.IsEmpty()) {
bool disabled = true;
for (int j = i + 1; j < items.size(); ++j) {
if (items[i].group != item.group && (items[j].style & DD_ITEM_STYLE_DISABLED) == 0) {
if (items[j].group_key == item.group_key && (items[j].style & DD_ITEM_STYLE_DISABLED) == 0) {
disabled = false;
break;
}
@@ -357,7 +357,7 @@ void DropDown::render(wxDC &dc)
}
}
} else {
if (item.group != group)
if (item.group_key != group)
continue;
}
bool is_hover = index == hover_item;
@@ -391,8 +391,8 @@ void DropDown::render(wxDC &dc)
pt.y = rcContent.y;
}
auto text = group.IsEmpty()
? (item.group.IsEmpty() ? item.text : item.group)
: (item.text.StartsWith(group) && !group.EndsWith(' ') ? item.text.substr(group.size()).Trim(false) : item.text);
? (item.group_key.IsEmpty() ? item.text : item.group_label)
: item.text;
if (!text_off && !text.IsEmpty()) {
wxSize tSize = dc.GetMultiLineTextExtent(text);
if (pt.x + tSize.x > rcContent.GetRight()) {
@@ -405,7 +405,7 @@ void DropDown::render(wxDC &dc)
dc.SetFont(GetFont());
dc.SetTextForeground(text_color.colorForStates(states2));
dc.DrawText(text, pt);
if (group.IsEmpty() && !item.group.IsEmpty()) {
if (group.IsEmpty() && !item.group_key.IsEmpty()) {
auto szBmp = arrow_bitmap.GetBmpSize();
pt.x = rcContent.GetRight() - szBmp.x - 5;
pt.y = rcContent.y + (rcContent.height - szBmp.y) / 2;
@@ -430,18 +430,18 @@ int DropDown::hoverIndex()
auto &item = items[i];
// Skip by group
if (group.IsEmpty()) {
if (!item.group.IsEmpty()) {
if (groups.find(item.group) == groups.end())
groups.insert(item.group);
if (!item.group_key.IsEmpty()) {
if (groups.find(item.group_key) == groups.end())
groups.insert(item.group_key);
else
continue;
}
} else {
if (item.group != group)
if (item.group_key != group)
continue;
}
if (++index == hover_item)
return (item.group.IsEmpty() || !group.IsEmpty()) ? i : -i - 2;
return (item.group_key.IsEmpty() || !group.IsEmpty()) ? i : -i - 2;
}
return -1;
}
@@ -455,7 +455,7 @@ int DropDown::selectedItem()
if (count == items.size() && subDropDown == nullptr)
return selection;
auto & sel = items[selection];
if (group.IsEmpty() ? !sel.group.IsEmpty() : sel.group != group)
if (group.IsEmpty() ? !sel.group_key.IsEmpty() : sel.group_key != group)
return -1;
if (selection == 0)
return 0;
@@ -465,14 +465,14 @@ int DropDown::selectedItem()
auto &item = items[i];
// Skip by group
if (group.IsEmpty()) {
if (!item.group.IsEmpty()) {
if (groups.find(item.group) == groups.end())
groups.insert(item.group);
if (!item.group_key.IsEmpty()) {
if (groups.find(item.group_key) == groups.end())
groups.insert(item.group_key);
else
continue;
}
} else {
if (item.group != group)
if (item.group_key != group)
continue;
}
++index;
@@ -493,24 +493,24 @@ void DropDown::messureSize()
auto &item = items[i];
// Skip by group
if (group.IsEmpty()) {
if (!item.group.IsEmpty()) {
if (groups.find(item.group) == groups.end())
groups.insert(item.group);
if (!item.group_key.IsEmpty()) {
if (groups.find(item.group_key) == groups.end())
groups.insert(item.group_key);
else
continue;
}
} else {
if (item.group != group)
if (item.group_key != group)
continue;
}
++count;
wxSize size1;
if (!text_off) {
auto text = group.IsEmpty()
? (item.group.IsEmpty() ? item.text : item.group)
: (item.text.StartsWith(group) && !group.EndsWith(' ') ? item.text.substr(group.size()).Trim(false) : item.text);
? (item.group_key.IsEmpty() ? item.text : item.group_label)
: item.text;
size1 = dc.GetMultiLineTextExtent(text);
if (group.IsEmpty() && !item.group.IsEmpty())
if (group.IsEmpty() && !item.group_key.IsEmpty())
size1.x += 5 + arrow_bitmap.GetBmpWidth();
}
if (item.icon.IsOk()) {
@@ -696,7 +696,7 @@ void DropDown::mouseMove(wxMouseEvent &event)
int index = hoverIndex();
if (index < -1) {
auto & drop = *subDropDown;
drop.group = items[-index - 2].group;
drop.group = items[-index - 2].group_key;
drop.need_sync = true;
drop.messureSize();
drop.autoPosition();
+2 -1
View File
@@ -26,7 +26,8 @@ public:
wxBitmap icon;
wxBitmap icon_textctrl;// display icon for TextInput.eg.PrinterInfoBox
void * data{nullptr};
wxString group{};
wxString group_key{};
wxString group_label{};
wxString alias{};
wxString tip{};
int flag{0};
+3 -3
View File
@@ -276,7 +276,7 @@ wxWebView* WebView::CreateWebView(wxWindow * parent, wxString const & url)
#ifdef __WIN32__
webView->SetUserAgent(wxString::Format("Mozilla/5.0 (Windows NT 10.0; Win64; x64) "
"AppleWebKit/537.36 (KHTML, like Gecko) Chrome/107.0.0.0 Safari/537.36 Edg/107.0.1418.52 BBL-Slicer/v%s (%s) BBL-Language/%s",
SLIC3R_VERSION, Slic3r::GUI::wxGetApp().dark_mode() ? "dark" : "light", language_code.mb_str()));
Slic3r::GUI::wxGetApp().get_bbl_client_version(), Slic3r::GUI::wxGetApp().dark_mode() ? "dark" : "light", language_code.mb_str()));
webView->Create(parent, wxID_ANY, url2, wxDefaultPosition, wxDefaultSize, wxBORDER_NONE);
// We register the wxfs:// protocol for testing purposes
webView->RegisterHandler(wxSharedPtr<wxWebViewHandler>(new wxWebViewArchiveHandler("bbl")));
@@ -294,7 +294,7 @@ wxWebView* WebView::CreateWebView(wxWindow * parent, wxString const & url)
}
webView->Create(parent, wxID_ANY, url2, wxDefaultPosition, wxDefaultSize, wxBORDER_NONE);
webView->SetUserAgent(wxString::Format("Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) BBL-Slicer/v%s (%s) BBL-Language/%s",
SLIC3R_VERSION, Slic3r::GUI::wxGetApp().dark_mode() ? "dark" : "light", language_code.mb_str()));
Slic3r::GUI::wxGetApp().get_bbl_client_version(), Slic3r::GUI::wxGetApp().dark_mode() ? "dark" : "light", language_code.mb_str()));
#endif
#ifdef __WXMAC__
WKWebView * wkWebView = (WKWebView *) webView->GetNativeBackend();
@@ -399,7 +399,7 @@ void WebView::RecreateAll()
language_code = language_code.ToStdString();
for (auto webView : g_webviews) {
webView->SetUserAgent(wxString::Format("Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) BBL-Slicer/v%s (%s) BBL-Language/%s",
SLIC3R_VERSION, dark ? "dark" : "light", language_code.mb_str()));
Slic3r::GUI::wxGetApp().get_bbl_client_version(), dark ? "dark" : "light", language_code.mb_str()));
webView->Reload();
}
}