diff --git a/src/slic3r/GUI/GUI_App.cpp b/src/slic3r/GUI/GUI_App.cpp index a66092c437..ba6a211fb7 100644 --- a/src/slic3r/GUI/GUI_App.cpp +++ b/src/slic3r/GUI/GUI_App.cpp @@ -3225,7 +3225,8 @@ __retry: std::string loaded_version = Slic3r::NetworkAgent::get_version(); if (app_config && !loaded_version.empty() && loaded_version != "00.00.00.00") { std::string config_version = app_config->get_network_plugin_version(); - if (config_version != loaded_version) { + std::string config_base = BBL::extract_base_version(config_version); + if (config_base != loaded_version) { BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ": syncing config version from " << config_version << " to loaded " << loaded_version; app_config->set(SETTING_NETWORK_PLUGIN_VERSION, loaded_version); app_config->save(); diff --git a/src/slic3r/GUI/Monitor.cpp b/src/slic3r/GUI/Monitor.cpp index fd4249863f..02b2306ff3 100644 --- a/src/slic3r/GUI/Monitor.cpp +++ b/src/slic3r/GUI/Monitor.cpp @@ -1,6 +1,8 @@ #include "Tab.hpp" #include "libslic3r/Utils.hpp" #include "libslic3r/Model.hpp" +#include "libslic3r/AppConfig.hpp" +#include "slic3r/Utils/bambu_networking.hpp" #include #include @@ -525,10 +527,22 @@ void MonitorPanel::jump_to_LiveView() void MonitorPanel::update_network_version_footer() { - std::string network_ver = Slic3r::NetworkAgent::get_version(); - if (!network_ver.empty()) { - m_tabpanel->SetFooterText(wxString::Format("Network plugin v%s", network_ver)); + std::string binary_version = Slic3r::NetworkAgent::get_version(); + if (binary_version.empty()) + return; + + std::string configured_version = wxGetApp().app_config->get_network_plugin_version(); + std::string suffix = BBL::extract_suffix(configured_version); + std::string configured_base = BBL::extract_base_version(configured_version); + + wxString footer_text; + if (!suffix.empty() && configured_base == binary_version) { + footer_text = wxString::Format("Network plugin v%s (%s)", binary_version, suffix); + } else { + footer_text = wxString::Format("Network plugin v%s", binary_version); } + + m_tabpanel->SetFooterText(footer_text); } } // GUI diff --git a/src/slic3r/GUI/NetworkPluginDialog.cpp b/src/slic3r/GUI/NetworkPluginDialog.cpp index ca1ae3bea2..5c1e158748 100644 --- a/src/slic3r/GUI/NetworkPluginDialog.cpp +++ b/src/slic3r/GUI/NetworkPluginDialog.cpp @@ -211,11 +211,17 @@ void NetworkPluginDownloadDialog::setup_version_selector() wxDefaultPosition, wxSize(FromDIP(380), FromDIP(28)), 0, nullptr, wxCB_READONLY); m_version_combo->SetFont(::Label::Body_13); - for (size_t i = 0; i < BBL::AVAILABLE_NETWORK_VERSIONS_COUNT; ++i) { - const auto& ver = BBL::AVAILABLE_NETWORK_VERSIONS[i]; - wxString label = wxString::FromUTF8(ver.display_name); - if (ver.is_latest) { - label += wxString(" ") + _L("(Latest)"); + m_available_versions = BBL::get_all_available_versions(); + for (size_t i = 0; i < m_available_versions.size(); ++i) { + const auto& ver = m_available_versions[i]; + wxString label; + if (!ver.suffix.empty()) { + label = wxString::FromUTF8("\xE2\x94\x94 ") + wxString::FromUTF8(ver.display_name); + } else { + label = wxString::FromUTF8(ver.display_name); + if (ver.is_latest) { + label += wxString(" ") + _L("(Latest)"); + } } m_version_combo->Append(label); } @@ -230,19 +236,19 @@ std::string NetworkPluginDownloadDialog::get_selected_version() const } int selection = m_version_combo->GetSelection(); - if (selection < 0 || selection >= static_cast(BBL::AVAILABLE_NETWORK_VERSIONS_COUNT)) { + if (selection < 0 || selection >= static_cast(m_available_versions.size())) { return ""; } - return BBL::AVAILABLE_NETWORK_VERSIONS[selection].version; + return m_available_versions[selection].version; } void NetworkPluginDownloadDialog::on_download(wxCommandEvent& evt) { int selection = m_version_combo ? m_version_combo->GetSelection() : 0; - if (selection >= 0 && selection < static_cast(BBL::AVAILABLE_NETWORK_VERSIONS_COUNT)) { - const char* warning = BBL::AVAILABLE_NETWORK_VERSIONS[selection].warning; - if (warning) { + if (selection >= 0 && selection < static_cast(m_available_versions.size())) { + const std::string& warning = m_available_versions[selection].warning; + if (!warning.empty()) { MessageDialog warn_dlg(this, wxString::FromUTF8(warning), _L("Warning"), wxOK | wxCANCEL | wxICON_WARNING); if (warn_dlg.ShowModal() != wxID_OK) { return; diff --git a/src/slic3r/GUI/NetworkPluginDialog.hpp b/src/slic3r/GUI/NetworkPluginDialog.hpp index 7e28fe2114..88d4a3813c 100644 --- a/src/slic3r/GUI/NetworkPluginDialog.hpp +++ b/src/slic3r/GUI/NetworkPluginDialog.hpp @@ -5,6 +5,7 @@ #include "MsgDialog.hpp" #include "Widgets/ComboBox.hpp" #include "Widgets/Button.hpp" +#include "slic3r/Utils/bambu_networking.hpp" #include namespace Slic3r { @@ -52,6 +53,7 @@ private: wxCollapsiblePane* m_details_pane{nullptr}; std::string m_error_message; std::string m_error_details; + std::vector m_available_versions; }; class NetworkPluginRestartDialog : public DPIDialog diff --git a/src/slic3r/GUI/Preferences.cpp b/src/slic3r/GUI/Preferences.cpp index e2ee8c36c3..b595cbd04f 100644 --- a/src/slic3r/GUI/Preferences.cpp +++ b/src/slic3r/GUI/Preferences.cpp @@ -1371,13 +1371,23 @@ void PreferencesDialog::create_items() std::string current_version = app_config->get("network_plugin_version"); int current_selection = 0; - for (size_t i = 0; i < BBL::AVAILABLE_NETWORK_VERSIONS_COUNT; i++) { - wxString label = wxString::FromUTF8(BBL::AVAILABLE_NETWORK_VERSIONS[i].display_name); - if (BBL::AVAILABLE_NETWORK_VERSIONS[i].is_latest) { + m_available_versions = BBL::get_all_available_versions(); + + for (size_t i = 0; i < m_available_versions.size(); i++) { + const auto& ver = m_available_versions[i]; + wxString label; + + if (!ver.suffix.empty()) { + label = wxString::FromUTF8("\xE2\x94\x94 ") + wxString::FromUTF8(ver.display_name); + } else { + label = wxString::FromUTF8(ver.display_name); + } + + if (ver.is_latest) { label += " " + _L("(Latest)"); } m_network_version_combo->Append(label); - if (current_version == BBL::AVAILABLE_NETWORK_VERSIONS[i].version) { + if (current_version == ver.version) { current_selection = i; } } @@ -1387,8 +1397,9 @@ void PreferencesDialog::create_items() m_network_version_combo->GetDropDown().Bind(wxEVT_COMBOBOX, [this](wxCommandEvent& e) { int selection = e.GetSelection(); - if (selection >= 0 && selection < (int)BBL::AVAILABLE_NETWORK_VERSIONS_COUNT) { - std::string new_version = BBL::AVAILABLE_NETWORK_VERSIONS[selection].version; + if (selection >= 0 && selection < (int)m_available_versions.size()) { + const auto& selected_ver = m_available_versions[selection]; + std::string new_version = selected_ver.version; std::string old_version = app_config->get_network_plugin_version(); if (old_version.empty()) { old_version = BBL::get_latest_network_version(); @@ -1400,9 +1411,8 @@ void PreferencesDialog::create_items() if (new_version != old_version) { BOOST_LOG_TRIVIAL(info) << "Network plugin version changed from " << old_version << " to " << new_version; - const char* warning = BBL::AVAILABLE_NETWORK_VERSIONS[selection].warning; - if (warning) { - MessageDialog warn_dlg(this, wxString::FromUTF8(warning), _L("Warning"), wxOK | wxCANCEL | wxICON_WARNING); + if (!selected_ver.warning.empty()) { + MessageDialog warn_dlg(this, wxString::FromUTF8(selected_ver.warning), _L("Warning"), wxOK | wxCANCEL | wxICON_WARNING); if (warn_dlg.ShowModal() != wxID_OK) { app_config->set(SETTING_NETWORK_PLUGIN_VERSION, old_version); app_config->save(); diff --git a/src/slic3r/GUI/Preferences.hpp b/src/slic3r/GUI/Preferences.hpp index 06d9a46f04..90c3af1dee 100644 --- a/src/slic3r/GUI/Preferences.hpp +++ b/src/slic3r/GUI/Preferences.hpp @@ -13,6 +13,7 @@ #include "Widgets/CheckBox.hpp" #include "Widgets/TextInput.hpp" #include "Widgets/TabCtrl.hpp" +#include "slic3r/Utils/bambu_networking.hpp" namespace Slic3r { namespace GUI { @@ -70,6 +71,7 @@ public: ::CheckBox * m_legacy_networking_ckeckbox = {nullptr}; ::ComboBox * m_network_version_combo = {nullptr}; wxBoxSizer * m_network_version_sizer = {nullptr}; + std::vector m_available_versions; wxString m_developer_mode_def; wxString m_internal_developer_mode_def; diff --git a/src/slic3r/Utils/NetworkAgent.cpp b/src/slic3r/Utils/NetworkAgent.cpp index fc70630419..cbbc645682 100644 --- a/src/slic3r/Utils/NetworkAgent.cpp +++ b/src/slic3r/Utils/NetworkAgent.cpp @@ -1,5 +1,7 @@ #include #include +#include +#include #if defined(_MSC_VER) || defined(_WIN32) #include #else @@ -282,6 +284,52 @@ void NetworkAgent::remove_legacy_library() } } +std::vector NetworkAgent::scan_plugin_versions() +{ + std::vector discovered_versions; + std::string data_dir_str = data_dir(); + boost::filesystem::path plugin_folder = boost::filesystem::path(data_dir_str) / "plugins"; + + if (!boost::filesystem::is_directory(plugin_folder)) { + return discovered_versions; + } + +#if defined(_MSC_VER) || defined(_WIN32) + std::string prefix = std::string(BAMBU_NETWORK_LIBRARY) + "_"; + std::string extension = ".dll"; +#elif defined(__WXMAC__) + std::string prefix = std::string("lib") + std::string(BAMBU_NETWORK_LIBRARY) + "_"; + std::string extension = ".dylib"; +#else + std::string prefix = std::string("lib") + std::string(BAMBU_NETWORK_LIBRARY) + "_"; + std::string extension = ".so"; +#endif + + boost::system::error_code ec; + for (auto& entry : boost::filesystem::directory_iterator(plugin_folder, ec)) { + if (ec) { + BOOST_LOG_TRIVIAL(warning) << __FUNCTION__ << ": error iterating directory: " << ec.message(); + break; + } + if (!boost::filesystem::is_regular_file(entry.status())) + continue; + + std::string filename = entry.path().filename().string(); + + if (filename.rfind(prefix, 0) != 0) + continue; + if (filename.size() <= extension.size() || + filename.compare(filename.size() - extension.size(), extension.size(), extension) != 0) + continue; + + std::string version = filename.substr(prefix.size(), + filename.size() - prefix.size() - extension.size()); + discovered_versions.push_back(version); + } + + return discovered_versions; +} + int NetworkAgent::initialize_network_module(bool using_backup, const std::string& version) { clear_load_error(); @@ -1770,3 +1818,63 @@ int NetworkAgent::get_model_mall_rating_result(int job_id, std::string &rating_r } } //namespace + +std::vector BBL::get_all_available_versions() +{ + std::vector result; + std::set known_base_versions; + std::set all_known_versions; + + for (size_t i = 0; i < AVAILABLE_NETWORK_VERSIONS_COUNT; ++i) { + result.push_back(NetworkLibraryVersionInfo::from_static(AVAILABLE_NETWORK_VERSIONS[i])); + known_base_versions.insert(AVAILABLE_NETWORK_VERSIONS[i].version); + all_known_versions.insert(AVAILABLE_NETWORK_VERSIONS[i].version); + } + + std::vector discovered = Slic3r::NetworkAgent::scan_plugin_versions(); + + std::vector> suffixed_versions; + + for (const auto& version : discovered) { + if (all_known_versions.count(version) > 0) + continue; + + std::string base = extract_base_version(version); + std::string suffix = extract_suffix(version); + + if (suffix.empty()) + continue; + + if (known_base_versions.count(base) == 0) + continue; + + suffixed_versions.emplace_back(base, version); + all_known_versions.insert(version); + } + + std::sort(suffixed_versions.begin(), suffixed_versions.end(), + [](const auto& a, const auto& b) { + if (a.first != b.first) return a.first > b.first; + return a.second < b.second; + }); + + for (const auto& [base, full] : suffixed_versions) { + size_t insert_pos = 0; + for (size_t i = 0; i < result.size(); ++i) { + if (result[i].base_version == base) { + insert_pos = i + 1; + while (insert_pos < result.size() && + result[insert_pos].base_version == base) { + ++insert_pos; + } + break; + } + } + + std::string sfx = extract_suffix(full); + result.insert(result.begin() + insert_pos, + NetworkLibraryVersionInfo::from_discovered(full, base, sfx)); + } + + return result; +} diff --git a/src/slic3r/Utils/NetworkAgent.hpp b/src/slic3r/Utils/NetworkAgent.hpp index e03106068e..50bb35474e 100644 --- a/src/slic3r/Utils/NetworkAgent.hpp +++ b/src/slic3r/Utils/NetworkAgent.hpp @@ -120,6 +120,7 @@ public: static bool versioned_library_exists(const std::string& version); static bool legacy_library_exists(); static void remove_legacy_library(); + static std::vector scan_plugin_versions(); static int initialize_network_module(bool using_backup = false, const std::string& version = ""); static int unload_network_module(); static bool is_network_module_loaded(); diff --git a/src/slic3r/Utils/bambu_networking.hpp b/src/slic3r/Utils/bambu_networking.hpp index f18d29e0b2..b9a5b6b225 100644 --- a/src/slic3r/Utils/bambu_networking.hpp +++ b/src/slic3r/Utils/bambu_networking.hpp @@ -4,6 +4,7 @@ #include #include #include +#include extern std::string g_log_folder; extern std::string g_log_start_time; @@ -329,6 +330,48 @@ inline const char* get_latest_network_version() { return AVAILABLE_NETWORK_VERSIONS[0].version; } +struct NetworkLibraryVersionInfo { + std::string version; + std::string base_version; + std::string suffix; + std::string display_name; + std::string url_override; + bool is_latest; + std::string warning; + bool is_discovered; + + static NetworkLibraryVersionInfo from_static(const NetworkLibraryVersion& v) { + return { + v.version, + v.version, + "", + v.display_name, + v.url_override ? v.url_override : "", + v.is_latest, + v.warning ? v.warning : "", + false + }; + } + + static NetworkLibraryVersionInfo from_discovered(const std::string& full_version, + const std::string& base, + const std::string& sfx) { + return {full_version, base, sfx, full_version, "", false, "", true}; + } +}; + +inline std::string extract_base_version(const std::string& full_version) { + auto pos = full_version.find('-'); + return (pos == std::string::npos) ? full_version : full_version.substr(0, pos); +} + +inline std::string extract_suffix(const std::string& full_version) { + auto pos = full_version.find('-'); + return (pos == std::string::npos) ? "" : full_version.substr(pos + 1); +} + +std::vector get_all_available_versions(); + struct NetworkLibraryLoadError { bool has_error = false; std::string message; diff --git a/tests/libslic3r/CMakeLists.txt b/tests/libslic3r/CMakeLists.txt index 51e8e1ea6b..38ff543336 100644 --- a/tests/libslic3r/CMakeLists.txt +++ b/tests/libslic3r/CMakeLists.txt @@ -5,6 +5,7 @@ add_executable(${_TEST_NAME}_tests test_3mf.cpp test_aabbindirect.cpp test_appconfig.cpp + test_bambu_networking.cpp test_clipper_offset.cpp test_clipper_utils.cpp test_config.cpp @@ -30,6 +31,7 @@ if (TARGET OpenVDB::openvdb) endif() target_link_libraries(${_TEST_NAME}_tests test_common libslic3r Catch2::Catch2WithMain) +target_include_directories(${_TEST_NAME}_tests PRIVATE ${CMAKE_SOURCE_DIR}/src) set_property(TARGET ${_TEST_NAME}_tests PROPERTY FOLDER "tests") if (WIN32) diff --git a/tests/libslic3r/test_bambu_networking.cpp b/tests/libslic3r/test_bambu_networking.cpp new file mode 100644 index 0000000000..c15f3b3d39 --- /dev/null +++ b/tests/libslic3r/test_bambu_networking.cpp @@ -0,0 +1,98 @@ +#include + +#include "slic3r/Utils/bambu_networking.hpp" + +using namespace BBL; + +TEST_CASE("extract_base_version", "[BambuNetworking]") { + SECTION("version without suffix returns unchanged") { + REQUIRE(extract_base_version("02.03.00.62") == "02.03.00.62"); + REQUIRE(extract_base_version("01.00.00.00") == "01.00.00.00"); + } + + SECTION("version with suffix returns base only") { + REQUIRE(extract_base_version("02.03.00.62-mod") == "02.03.00.62"); + REQUIRE(extract_base_version("02.03.00.62-patched") == "02.03.00.62"); + REQUIRE(extract_base_version("02.03.00.62-test-build") == "02.03.00.62"); + } + + SECTION("empty string returns empty") { + REQUIRE(extract_base_version("") == ""); + } + + SECTION("suffix only returns empty") { + REQUIRE(extract_base_version("-mod") == ""); + } +} + +TEST_CASE("extract_suffix", "[BambuNetworking]") { + SECTION("version without suffix returns empty") { + REQUIRE(extract_suffix("02.03.00.62") == ""); + REQUIRE(extract_suffix("01.00.00.00") == ""); + } + + SECTION("version with suffix returns suffix without dash") { + REQUIRE(extract_suffix("02.03.00.62-mod") == "mod"); + REQUIRE(extract_suffix("02.03.00.62-patched") == "patched"); + } + + SECTION("version with multiple dashes returns everything after first dash") { + REQUIRE(extract_suffix("02.03.00.62-test-build") == "test-build"); + } + + SECTION("empty string returns empty") { + REQUIRE(extract_suffix("") == ""); + } + + SECTION("suffix only returns suffix without leading dash") { + REQUIRE(extract_suffix("-mod") == "mod"); + } +} + +TEST_CASE("NetworkLibraryVersionInfo::from_static", "[BambuNetworking]") { + SECTION("converts static version info correctly") { + NetworkLibraryVersion static_ver{"02.03.00.62", "02.03.00.62", nullptr, true, nullptr}; + auto info = NetworkLibraryVersionInfo::from_static(static_ver); + + REQUIRE(info.version == "02.03.00.62"); + REQUIRE(info.base_version == "02.03.00.62"); + REQUIRE(info.suffix == ""); + REQUIRE(info.display_name == "02.03.00.62"); + REQUIRE(info.url_override == ""); + REQUIRE(info.is_latest == true); + REQUIRE(info.warning == ""); + REQUIRE(info.is_discovered == false); + } + + SECTION("handles version with warning") { + NetworkLibraryVersion static_ver{"02.00.02.50", "02.00.02.50", nullptr, false, "This is a warning"}; + auto info = NetworkLibraryVersionInfo::from_static(static_ver); + + REQUIRE(info.version == "02.00.02.50"); + REQUIRE(info.is_latest == false); + REQUIRE(info.warning == "This is a warning"); + REQUIRE(info.is_discovered == false); + } + + SECTION("handles version with url override") { + NetworkLibraryVersion static_ver{"02.01.01.52", "02.01.01.52", "https://custom.url/plugin.zip", false, nullptr}; + auto info = NetworkLibraryVersionInfo::from_static(static_ver); + + REQUIRE(info.url_override == "https://custom.url/plugin.zip"); + } +} + +TEST_CASE("NetworkLibraryVersionInfo::from_discovered", "[BambuNetworking]") { + SECTION("creates discovered version info correctly") { + auto info = NetworkLibraryVersionInfo::from_discovered("02.03.00.62-mod", "02.03.00.62", "mod"); + + REQUIRE(info.version == "02.03.00.62-mod"); + REQUIRE(info.base_version == "02.03.00.62"); + REQUIRE(info.suffix == "mod"); + REQUIRE(info.display_name == "02.03.00.62-mod"); + REQUIRE(info.url_override == ""); + REQUIRE(info.is_latest == false); + REQUIRE(info.warning == ""); + REQUIRE(info.is_discovered == true); + } +}