From a4d4bfff27f83ec207fb3780014cc5c0db29243c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maciej=20Wilczy=C5=84ski?= Date: Sun, 14 Dec 2025 09:30:11 +0100 Subject: [PATCH 1/9] Allow selecting specific network plugin versions --- CMakeLists.txt | 9 + build_release_macos.sh | 31 +- cmake/modules/FindGLEW.cmake | 27 +- deps/CMakeLists.txt | 6 + deps/wxWidgets/wxWidgets.cmake | 9 + src/libslic3r/AppConfig.cpp | 77 +++++ src/libslic3r/AppConfig.hpp | 20 ++ src/slic3r/CMakeLists.txt | 16 +- src/slic3r/GUI/DeviceCore/DevManager.cpp | 19 ++ src/slic3r/GUI/DeviceCore/DevManager.h | 2 +- src/slic3r/GUI/DeviceManager.hpp | 2 + src/slic3r/GUI/DownloadProgressDialog.cpp | 13 +- src/slic3r/GUI/GUI_App.cpp | 381 ++++++++++++++++++++-- src/slic3r/GUI/GUI_App.hpp | 6 + src/slic3r/GUI/Monitor.cpp | 14 + src/slic3r/GUI/Monitor.hpp | 1 + src/slic3r/GUI/NetworkPluginDialog.cpp | 371 +++++++++++++++++++++ src/slic3r/GUI/NetworkPluginDialog.hpp | 74 +++++ src/slic3r/GUI/Preferences.cpp | 112 ++++++- src/slic3r/GUI/Preferences.hpp | 2 + src/slic3r/GUI/Tabbook.cpp | 14 + src/slic3r/GUI/Tabbook.hpp | 7 + src/slic3r/Utils/NetworkAgent.cpp | 160 +++++++-- src/slic3r/Utils/NetworkAgent.hpp | 13 +- src/slic3r/Utils/PresetUpdater.cpp | 2 +- src/slic3r/Utils/bambu_networking.hpp | 31 +- tests/libslic3r/CMakeLists.txt | 1 + tests/libslic3r/test_appconfig.cpp | 45 +++ 28 files changed, 1388 insertions(+), 77 deletions(-) create mode 100644 src/slic3r/GUI/NetworkPluginDialog.cpp create mode 100644 src/slic3r/GUI/NetworkPluginDialog.hpp create mode 100644 tests/libslic3r/test_appconfig.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 162763621c..041c7b17cf 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,3 +1,7 @@ +if(${CMAKE_VERSION} VERSION_GREATER_EQUAL "4.0") + set(CMAKE_POLICY_VERSION_MINIMUM 3.5 CACHE STRING "" FORCE) +endif() + cmake_minimum_required(VERSION 3.13) # Verify that your CMake version is exactly 3.5 series or higher on windows @@ -571,6 +575,7 @@ endif() if(POLICY CMP0167) cmake_policy(SET CMP0167 NEW) endif() +set(Boost_NO_SYSTEM_PATHS TRUE) find_package(Boost 1.83.0 REQUIRED COMPONENTS system filesystem thread log log_setup locale regex chrono atomic date_time iostreams program_options nowide) add_library(boost_libs INTERFACE) @@ -688,6 +693,10 @@ find_package(PNG REQUIRED) set(OpenGL_GL_PREFERENCE "LEGACY") find_package(OpenGL REQUIRED) +if(APPLE AND CMAKE_VERSION VERSION_GREATER_EQUAL "4.0") + set(OPENGL_LIBRARIES "-framework OpenGL" CACHE STRING "OpenGL framework" FORCE) +endif() + set(GLEW_ROOT "${CMAKE_PREFIX_PATH}") message("GLEW_ROOT: ${GLEW_ROOT}") # Find glew or use bundled version diff --git a/build_release_macos.sh b/build_release_macos.sh index e42490f1bd..1999c62b92 100755 --- a/build_release_macos.sh +++ b/build_release_macos.sh @@ -3,7 +3,7 @@ set -e set -o pipefail -while getopts ":dpa:snt:xbc:1h" opt; do +while getopts ":dpa:snt:xbc:1Th" opt; do case "${opt}" in d ) export BUILD_TARGET="deps" @@ -37,6 +37,9 @@ while getopts ":dpa:snt:xbc:1h" opt; do 1 ) export CMAKE_BUILD_PARALLEL_LEVEL=1 ;; + T ) + export BUILD_TESTS="1" + ;; h ) echo "Usage: ./build_release_macos.sh [-d]" echo " -d: Build deps only" echo " -a: Set ARCHITECTURE (arm64 or x86_64 or universal)" @@ -47,6 +50,7 @@ while getopts ":dpa:snt:xbc:1h" opt; do echo " -b: Build without reconfiguring CMake" echo " -c: Set CMake build configuration, default is Release" echo " -1: Use single job for building" + echo " -T: Build and run tests" exit 0 ;; * ) @@ -85,6 +89,15 @@ if [ -z "$OSX_DEPLOYMENT_TARGET" ]; then export OSX_DEPLOYMENT_TARGET="11.3" fi +CMAKE_VERSION=$(cmake --version | head -1 | sed 's/[^0-9]*\([0-9]*\).*/\1/') +if [ "$CMAKE_VERSION" -ge 4 ] 2>/dev/null; then + export CMAKE_POLICY_VERSION_MINIMUM=3.5 + export CMAKE_POLICY_COMPAT="-DCMAKE_POLICY_VERSION_MINIMUM=3.5" + echo "Detected CMake 4.x, adding compatibility flag (env + cmake arg)" +else + export CMAKE_POLICY_COMPAT="" +fi + echo "Build params:" echo " - ARCH: $ARCH" echo " - BUILD_CONFIG: $BUILD_CONFIG" @@ -133,7 +146,8 @@ function build_deps() { -G "${DEPS_CMAKE_GENERATOR}" \ -DCMAKE_BUILD_TYPE="$BUILD_CONFIG" \ -DCMAKE_OSX_ARCHITECTURES:STRING="${_ARCH}" \ - -DCMAKE_OSX_DEPLOYMENT_TARGET="${OSX_DEPLOYMENT_TARGET}" + -DCMAKE_OSX_DEPLOYMENT_TARGET="${OSX_DEPLOYMENT_TARGET}" \ + ${CMAKE_POLICY_COMPAT} fi cmake --build . --config "$BUILD_CONFIG" --target deps ) @@ -170,13 +184,24 @@ function build_slicer() { -G "${SLICER_CMAKE_GENERATOR}" \ -DORCA_TOOLS=ON \ ${ORCA_UPDATER_SIG_KEY:+-DORCA_UPDATER_SIG_KEY="$ORCA_UPDATER_SIG_KEY"} \ + ${BUILD_TESTS:+-DBUILD_TESTS=ON} \ -DCMAKE_BUILD_TYPE="$BUILD_CONFIG" \ -DCMAKE_OSX_ARCHITECTURES="${_ARCH}" \ - -DCMAKE_OSX_DEPLOYMENT_TARGET="${OSX_DEPLOYMENT_TARGET}" + -DCMAKE_OSX_DEPLOYMENT_TARGET="${OSX_DEPLOYMENT_TARGET}" \ + ${CMAKE_POLICY_COMPAT} fi cmake --build . --config "$BUILD_CONFIG" --target "$SLICER_BUILD_TARGET" ) + if [ "1." == "$BUILD_TESTS". ]; then + echo "Running tests for $_ARCH..." + ( + set -x + cd "$PROJECT_BUILD_DIR" + ctest --build-config "$BUILD_CONFIG" --output-on-failure + ) + fi + echo "Verify localization with gettext..." ( cd "$PROJECT_DIR" diff --git a/cmake/modules/FindGLEW.cmake b/cmake/modules/FindGLEW.cmake index 56661ffd33..5dd7b24df1 100644 --- a/cmake/modules/FindGLEW.cmake +++ b/cmake/modules/FindGLEW.cmake @@ -223,8 +223,13 @@ if(NOT TARGET GLEW::glew AND NOT GLEW_USE_STATIC_LIBS) PROPERTIES INTERFACE_INCLUDE_DIRECTORIES "${GLEW_INCLUDE_DIRS}") if(APPLE) - set_target_properties(GLEW::glew - PROPERTIES INTERFACE_LINK_LIBRARIES OpenGL::GL) + if(CMAKE_VERSION VERSION_GREATER_EQUAL "4.0") + set_target_properties(GLEW::glew + PROPERTIES INTERFACE_LINK_LIBRARIES "-framework OpenGL") + else() + set_target_properties(GLEW::glew + PROPERTIES INTERFACE_LINK_LIBRARIES OpenGL::GL) + endif() endif() if(GLEW_SHARED_LIBRARY_RELEASE) @@ -258,8 +263,13 @@ elseif(NOT TARGET GLEW::glew_s AND GLEW_USE_STATIC_LIBS) set_target_properties(GLEW::glew_s PROPERTIES INTERFACE_COMPILE_DEFINITIONS GLEW_STATIC) if(APPLE) - set_target_properties(GLEW::glew_s - PROPERTIES INTERFACE_LINK_LIBRARIES OpenGL::GL) + if(CMAKE_VERSION VERSION_GREATER_EQUAL "4.0") + set_target_properties(GLEW::glew_s + PROPERTIES INTERFACE_LINK_LIBRARIES "-framework OpenGL") + else() + set_target_properties(GLEW::glew_s + PROPERTIES INTERFACE_LINK_LIBRARIES OpenGL::GL) + endif() endif() if(GLEW_STATIC_LIBRARY_RELEASE) @@ -292,8 +302,13 @@ if(NOT TARGET GLEW::GLEW) PROPERTIES INTERFACE_INCLUDE_DIRECTORIES "${GLEW_INCLUDE_DIRS}") if(APPLE) - set_target_properties(GLEW::GLEW - PROPERTIES INTERFACE_LINK_LIBRARIES OpenGL::GL) + if(CMAKE_VERSION VERSION_GREATER_EQUAL "4.0") + set_target_properties(GLEW::GLEW + PROPERTIES INTERFACE_LINK_LIBRARIES "-framework OpenGL") + else() + set_target_properties(GLEW::GLEW + PROPERTIES INTERFACE_LINK_LIBRARIES OpenGL::GL) + endif() endif() if(TARGET GLEW::glew) diff --git a/deps/CMakeLists.txt b/deps/CMakeLists.txt index 13f3945d0c..92e36d0cad 100644 --- a/deps/CMakeLists.txt +++ b/deps/CMakeLists.txt @@ -1,3 +1,7 @@ +if(${CMAKE_VERSION} VERSION_GREATER_EQUAL "4.0") + set(CMAKE_POLICY_VERSION_MINIMUM 3.5 CACHE STRING "" FORCE) +endif() + # # This CMake project downloads, configures and builds OrcaSlicer dependencies on Unix and Windows. # @@ -177,6 +181,7 @@ if (NOT IS_CROSS_COMPILE OR NOT APPLE) DOWNLOAD_DIR ${DEP_DOWNLOAD_DIR}/${projectname} ${_gen} CMAKE_ARGS + -DCMAKE_POLICY_VERSION_MINIMUM=3.5 -DCMAKE_INSTALL_PREFIX:STRING=${DESTDIR} -DCMAKE_MODULE_PATH:STRING=${PROJECT_SOURCE_DIR}/../cmake/modules -DCMAKE_PREFIX_PATH:STRING=${DESTDIR} @@ -221,6 +226,7 @@ else() DOWNLOAD_DIR ${DEP_DOWNLOAD_DIR}/${projectname} ${_gen} CMAKE_ARGS + -DCMAKE_POLICY_VERSION_MINIMUM=3.5 -DCMAKE_INSTALL_PREFIX:STRING=${DESTDIR} -DCMAKE_PREFIX_PATH:STRING=${DESTDIR} -DBUILD_SHARED_LIBS:BOOL=OFF diff --git a/deps/wxWidgets/wxWidgets.cmake b/deps/wxWidgets/wxWidgets.cmake index e45979fb42..3a52d14198 100644 --- a/deps/wxWidgets/wxWidgets.cmake +++ b/deps/wxWidgets/wxWidgets.cmake @@ -24,6 +24,14 @@ else () set(_wx_edge "-DwxUSE_WEBVIEW_EDGE=OFF") endif () +set(_wx_opengl_override "") +if(APPLE AND CMAKE_VERSION VERSION_GREATER_EQUAL "4.0") + set(_wx_opengl_override + -DOPENGL_gl_LIBRARY="-framework OpenGL" + -DOPENGL_glu_LIBRARY="-framework OpenGL" + ) +endif() + orcaslicer_add_cmake_project( wxWidgets GIT_REPOSITORY "https://github.com/SoftFever/Orca-deps-wxWidgets" @@ -31,6 +39,7 @@ orcaslicer_add_cmake_project( DEPENDS ${PNG_PKG} ${ZLIB_PKG} ${EXPAT_PKG} ${JPEG_PKG} ${_wx_flatpak_patch} CMAKE_ARGS + ${_wx_opengl_override} -DwxBUILD_PRECOMP=ON ${_wx_toolkit} "-DCMAKE_DEBUG_POSTFIX:STRING=${_wx_debug_postfix}" diff --git a/src/libslic3r/AppConfig.cpp b/src/libslic3r/AppConfig.cpp index e592155d14..569613cb81 100644 --- a/src/libslic3r/AppConfig.cpp +++ b/src/libslic3r/AppConfig.cpp @@ -12,6 +12,7 @@ #include #include #include +#include #include #include @@ -1427,6 +1428,82 @@ std::string AppConfig::get_nozzle_volume_types_from_config(const std::string& pr return nozzle_volume_types; } +std::string AppConfig::get_network_plugin_version() const +{ + return get(SETTING_NETWORK_PLUGIN_VERSION); +} + +void AppConfig::set_network_plugin_version(const std::string& version) +{ + set(SETTING_NETWORK_PLUGIN_VERSION, version); +} + +std::vector AppConfig::get_skipped_network_versions() const +{ + std::vector result; + std::string skipped = get(SETTING_NETWORK_PLUGIN_SKIPPED_VERSIONS); + if (skipped.empty()) + return result; + + std::stringstream ss(skipped); + std::string version; + while (std::getline(ss, version, ';')) { + if (!version.empty()) + result.push_back(version); + } + return result; +} + +void AppConfig::add_skipped_network_version(const std::string& version) +{ + auto skipped = get_skipped_network_versions(); + if (std::find(skipped.begin(), skipped.end(), version) == skipped.end()) { + skipped.push_back(version); + std::string joined; + for (size_t i = 0; i < skipped.size(); ++i) { + if (i > 0) joined += ";"; + joined += skipped[i]; + } + set(SETTING_NETWORK_PLUGIN_SKIPPED_VERSIONS, joined); + } +} + +bool AppConfig::is_network_version_skipped(const std::string& version) const +{ + auto skipped = get_skipped_network_versions(); + return std::find(skipped.begin(), skipped.end(), version) != skipped.end(); +} + +void AppConfig::clear_skipped_network_versions() +{ + set(SETTING_NETWORK_PLUGIN_SKIPPED_VERSIONS, ""); +} + +bool AppConfig::is_network_update_prompt_disabled() const +{ + return get_bool(SETTING_NETWORK_PLUGIN_UPDATE_DISABLED); +} + +void AppConfig::set_network_update_prompt_disabled(bool disabled) +{ + set_bool(SETTING_NETWORK_PLUGIN_UPDATE_DISABLED, disabled); +} + +bool AppConfig::should_remind_network_update_later() const +{ + return get_bool(SETTING_NETWORK_PLUGIN_REMIND_LATER); +} + +void AppConfig::set_remind_network_update_later(bool remind) +{ + set_bool(SETTING_NETWORK_PLUGIN_REMIND_LATER, remind); +} + +void AppConfig::clear_remind_network_update_later() +{ + set_bool(SETTING_NETWORK_PLUGIN_REMIND_LATER, false); +} + void AppConfig::reset_selections() { auto it = m_storage.find("presets"); diff --git a/src/libslic3r/AppConfig.hpp b/src/libslic3r/AppConfig.hpp index 9307877552..82d87677ac 100644 --- a/src/libslic3r/AppConfig.hpp +++ b/src/libslic3r/AppConfig.hpp @@ -24,6 +24,11 @@ using namespace nlohmann; #define OPTION_PROJECT_LOAD_BEHAVIOUR_ALWAYS_ASK "always_ask" #define OPTION_PROJECT_LOAD_BEHAVIOUR_LOAD_GEOMETRY "load_geometry_only" +#define SETTING_NETWORK_PLUGIN_VERSION "network_plugin_version" +#define SETTING_NETWORK_PLUGIN_SKIPPED_VERSIONS "network_plugin_skipped_versions" +#define SETTING_NETWORK_PLUGIN_UPDATE_DISABLED "network_plugin_update_prompts_disabled" +#define SETTING_NETWORK_PLUGIN_REMIND_LATER "network_plugin_remind_later" + #define SUPPORT_DARK_MODE //#define _MSW_DARK_MODE @@ -347,6 +352,21 @@ public: static const std::string SECTION_MATERIALS; static const std::string SECTION_EMBOSS_STYLE; + std::string get_network_plugin_version() const; + void set_network_plugin_version(const std::string& version); + + std::vector get_skipped_network_versions() const; + void add_skipped_network_version(const std::string& version); + bool is_network_version_skipped(const std::string& version) const; + void clear_skipped_network_versions(); + + bool is_network_update_prompt_disabled() const; + void set_network_update_prompt_disabled(bool disabled); + + bool should_remind_network_update_later() const; + void set_remind_network_update_later(bool remind); + void clear_remind_network_update_later(); + private: template bool get_3dmouse_device_numeric_value(const std::string &device_name, const char *parameter_name, T &out) const diff --git a/src/slic3r/CMakeLists.txt b/src/slic3r/CMakeLists.txt index ca4f9c4123..5529cd04c5 100644 --- a/src/slic3r/CMakeLists.txt +++ b/src/slic3r/CMakeLists.txt @@ -456,6 +456,8 @@ set(SLIC3R_GUI_SOURCES GUI/UnsavedChangesDialog.hpp GUI/UpdateDialogs.cpp GUI/UpdateDialogs.hpp + GUI/NetworkPluginDialog.cpp + GUI/NetworkPluginDialog.hpp GUI/UpgradePanel.cpp GUI/UpgradePanel.hpp GUI/UserManager.cpp @@ -703,7 +705,13 @@ source_group(TREE ${CMAKE_CURRENT_SOURCE_DIR} FILES ${SLIC3R_GUI_SOURCES}) encoding_check(libslic3r_gui) -target_link_libraries(libslic3r_gui libslic3r cereal::cereal imgui imguizmo minilzo GLEW::GLEW OpenGL::GL hidapi ${wxWidgets_LIBRARIES} glfw libcurl OpenSSL::SSL OpenSSL::Crypto noise::noise) +if(APPLE AND CMAKE_VERSION VERSION_GREATER_EQUAL "4.0") + set(_opengl_link_lib "") +else() + set(_opengl_link_lib OpenGL::GL) +endif() + +target_link_libraries(libslic3r_gui libslic3r cereal::cereal imgui imguizmo minilzo GLEW::GLEW ${_opengl_link_lib} hidapi ${wxWidgets_LIBRARIES} glfw libcurl OpenSSL::SSL OpenSSL::Crypto noise::noise) if (MSVC) target_link_libraries(libslic3r_gui Setupapi.lib) @@ -721,7 +729,11 @@ elseif (CMAKE_SYSTEM_NAME STREQUAL "Linux") ${CURL_LIBRARIES} ) elseif (APPLE) - target_link_libraries(libslic3r_gui ${DISKARBITRATION_LIBRARY} "-framework Security") + if(CMAKE_VERSION VERSION_GREATER_EQUAL "4.0") + target_link_libraries(libslic3r_gui ${DISKARBITRATION_LIBRARY} "-framework Security" "-framework OpenGL") + else() + target_link_libraries(libslic3r_gui ${DISKARBITRATION_LIBRARY} "-framework Security") + endif() endif() if (SLIC3R_STATIC) diff --git a/src/slic3r/GUI/DeviceCore/DevManager.cpp b/src/slic3r/GUI/DeviceCore/DevManager.cpp index d19f1aa0dd..5c32f42c2b 100644 --- a/src/slic3r/GUI/DeviceCore/DevManager.cpp +++ b/src/slic3r/GUI/DeviceCore/DevManager.cpp @@ -90,6 +90,25 @@ namespace Slic3r userMachineList.clear(); } + void DeviceManager::set_agent(NetworkAgent* agent) + { + BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ": updating agent for " + << localMachineList.size() << " local and " + << userMachineList.size() << " user machines"; + m_agent = agent; + + std::lock_guard lock(listMutex); + for (auto& it : localMachineList) { + if (it.second) { + it.second->set_agent(agent); + } + } + for (auto& it : userMachineList) { + if (it.second) { + it.second->set_agent(agent); + } + } + } void DeviceManager::EnableMultiMachine(bool enable) { diff --git a/src/slic3r/GUI/DeviceCore/DevManager.h b/src/slic3r/GUI/DeviceCore/DevManager.h index fbc30c0050..5ecb77401d 100644 --- a/src/slic3r/GUI/DeviceCore/DevManager.h +++ b/src/slic3r/GUI/DeviceCore/DevManager.h @@ -40,7 +40,7 @@ public: public: NetworkAgent* get_agent() const { return m_agent; } - void set_agent(NetworkAgent* agent) { m_agent = agent; } + void set_agent(NetworkAgent* agent); void start_refresher(); void stop_refresher(); diff --git a/src/slic3r/GUI/DeviceManager.hpp b/src/slic3r/GUI/DeviceManager.hpp index 112b514ab3..f4362cc3f7 100644 --- a/src/slic3r/GUI/DeviceManager.hpp +++ b/src/slic3r/GUI/DeviceManager.hpp @@ -135,6 +135,8 @@ public: MachineObject(DeviceManager* manager, NetworkAgent* agent, std::string name, std::string id, std::string ip); ~MachineObject(); + void set_agent(NetworkAgent* agent) { m_agent = agent; } + public: enum ActiveState { NotActive, diff --git a/src/slic3r/GUI/DownloadProgressDialog.cpp b/src/slic3r/GUI/DownloadProgressDialog.cpp index 1c4f7a9bfb..76ef7afa92 100644 --- a/src/slic3r/GUI/DownloadProgressDialog.cpp +++ b/src/slic3r/GUI/DownloadProgressDialog.cpp @@ -19,6 +19,7 @@ //#include "ConfigWizard.hpp" #include "wxExtensions.hpp" #include "slic3r/GUI/MainFrame.hpp" +#include "slic3r/GUI/MsgDialog.hpp" #include "GUI_App.hpp" #include "Jobs/BoostThreadWorker.hpp" #include "Jobs/PlaterWorker.hpp" @@ -203,6 +204,16 @@ void DownloadProgressDialog::update_release_note(std::string release_note, std:: std::unique_ptr DownloadProgressDialog::make_job() { return std::make_unique(); } -void DownloadProgressDialog::on_finish() { wxGetApp().restart_networking(); } +void DownloadProgressDialog::on_finish() +{ + if (wxGetApp().hot_reload_network_plugin()) { + return; + } + + MessageDialog dlg(nullptr, + _L("The network plugin was installed but could not be loaded. Please restart the application."), + _L("Restart Required"), wxOK | wxICON_INFORMATION); + dlg.ShowModal(); +} }} // namespace Slic3r::GUI diff --git a/src/slic3r/GUI/GUI_App.cpp b/src/slic3r/GUI/GUI_App.cpp index 27816e2b67..a66092c437 100644 --- a/src/slic3r/GUI/GUI_App.cpp +++ b/src/slic3r/GUI/GUI_App.cpp @@ -42,6 +42,7 @@ #include #include #include +#include #include #include #include @@ -98,6 +99,7 @@ #include "UnsavedChangesDialog.hpp" #include "SavePresetDialog.hpp" #include "PrintHostDialogs.hpp" +#include "NetworkPluginDialog.hpp" #include "DesktopIntegrationDialog.hpp" #include "SendSystemInfoDialog.hpp" #include "ParamsDialog.hpp" @@ -959,15 +961,7 @@ void GUI_App::post_init() m_show_gcode_window = app_config->get_bool("show_gcode_window"); if (m_networking_need_update) { - //updating networking - int ret = updating_bambu_networking(); - if (!ret) { - BOOST_LOG_TRIVIAL(info) << __FUNCTION__<<":networking plugin updated successfully"; - //restart_networking(); - } - else { - BOOST_LOG_TRIVIAL(error) << __FUNCTION__<<":networking plugin updated failed"; - } + show_network_plugin_download_dialog(false); } // Start preset sync after project opened, otherwise we could have preset change during project opening which could cause crash @@ -1177,12 +1171,20 @@ std::string GUI_App::get_plugin_url(std::string name, std::string country_code) { std::string url = get_http_url(country_code); - std::string curr_version = NetworkAgent::use_legacy_network ? BAMBU_NETWORK_AGENT_VERSION_LEGACY : BAMBU_NETWORK_AGENT_VERSION; + std::string curr_version; + if (NetworkAgent::use_legacy_network) { + curr_version = BAMBU_NETWORK_AGENT_VERSION_LEGACY; + } else if (name == "plugins" && app_config) { + std::string user_version = app_config->get_network_plugin_version(); + curr_version = user_version.empty() ? BBL::get_latest_network_version() : user_version; + } else { + curr_version = BBL::get_latest_network_version(); + } + std::string using_version = curr_version.substr(0, 9) + "00"; if (name == "cameratools") using_version = curr_version.substr(0, 6) + "00.00"; url += (boost::format("?slicer/%1%/cloud=%2%") % name % using_version).str(); - //url += (boost::format("?slicer/plugins/cloud=%1%") % "01.01.00.00").str(); return url; } @@ -1413,6 +1415,32 @@ int GUI_App::install_plugin(std::string name, std::string package_name, InstallP return InstallStatusUnzipFailed; } + boost::filesystem::path legacy_lib_path, legacy_lib_backup; + bool had_existing_legacy = false; + if (name == "plugins") { +#if defined(_MSC_VER) || defined(_WIN32) + legacy_lib_path = plugin_folder / (std::string(BAMBU_NETWORK_LIBRARY) + ".dll"); +#elif defined(__WXMAC__) + legacy_lib_path = plugin_folder / (std::string("lib") + std::string(BAMBU_NETWORK_LIBRARY) + ".dylib"); +#else + legacy_lib_path = plugin_folder / (std::string("lib") + std::string(BAMBU_NETWORK_LIBRARY) + ".so"); +#endif + legacy_lib_backup = legacy_lib_path; + legacy_lib_backup += ".backup"; + + if (boost::filesystem::exists(legacy_lib_path)) { + had_existing_legacy = true; + boost::system::error_code ec; + boost::filesystem::rename(legacy_lib_path, legacy_lib_backup, ec); + if (ec) { + BOOST_LOG_TRIVIAL(warning) << "[install_plugin] failed to backup existing legacy library: " << ec.message(); + had_existing_legacy = false; + } else { + BOOST_LOG_TRIVIAL(info) << "[install_plugin] backed up existing legacy library"; + } + } + } + mz_uint num_entries = mz_zip_reader_get_num_files(&archive); mz_zip_archive_file_stat stat; BOOST_LOG_TRIVIAL(error) << boost::format("[install_plugin]: %1%, got %2% files")%__LINE__ %num_entries; @@ -1487,6 +1515,38 @@ int GUI_App::install_plugin(std::string name, std::string package_name, InstallP } close_zip_reader(&archive); + + if (name == "plugins") { + std::string config_version = app_config->get_network_plugin_version(); + if (!config_version.empty() && boost::filesystem::exists(legacy_lib_path)) { +#if defined(_MSC_VER) || defined(_WIN32) + auto versioned_lib = plugin_folder / (std::string(BAMBU_NETWORK_LIBRARY) + "_" + config_version + ".dll"); +#elif defined(__WXMAC__) + auto versioned_lib = plugin_folder / (std::string("lib") + std::string(BAMBU_NETWORK_LIBRARY) + "_" + config_version + ".dylib"); +#else + auto versioned_lib = plugin_folder / (std::string("lib") + std::string(BAMBU_NETWORK_LIBRARY) + "_" + config_version + ".so"); +#endif + BOOST_LOG_TRIVIAL(info) << "[install_plugin] renaming newly extracted " << legacy_lib_path.string() << " to " << versioned_lib.string(); + boost::system::error_code ec; + if (boost::filesystem::exists(versioned_lib)) { + boost::filesystem::remove(versioned_lib, ec); + } + boost::filesystem::rename(legacy_lib_path, versioned_lib, ec); + if (ec) { + BOOST_LOG_TRIVIAL(error) << "[install_plugin] failed to rename to versioned: " << ec.message(); + } + } + + if (had_existing_legacy && boost::filesystem::exists(legacy_lib_backup)) { + BOOST_LOG_TRIVIAL(info) << "[install_plugin] restoring backed up legacy library"; + boost::system::error_code ec; + boost::filesystem::rename(legacy_lib_backup, legacy_lib_path, ec); + if (ec) { + BOOST_LOG_TRIVIAL(warning) << "[install_plugin] failed to restore legacy library backup: " << ec.message(); + } + } + } + { fs::path dir_path(plugin_folder); if (fs::exists(dir_path) && fs::is_directory(dir_path)) { @@ -1519,6 +1579,8 @@ int GUI_App::install_plugin(std::string name, std::string package_name, InstallP } } } + + if (pro_fn) pro_fn(InstallStatusInstallCompleted, 100, cancel); if (name == "plugins") @@ -1572,6 +1634,167 @@ void GUI_App::restart_networking() BOOST_LOG_TRIVIAL(info) << __FUNCTION__<< boost::format(" exit, m_agent=%1%")%m_agent; } +bool GUI_App::hot_reload_network_plugin() +{ + BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ": starting hot reload"; + + wxBusyCursor busy; + wxBusyInfo info(_L("Reloading network plugin..."), mainframe); + wxYield(); + wxWindowDisabler disabler; + + if (mainframe) { + int current_tab = mainframe->m_tabpanel->GetSelection(); + if (current_tab == MainFrame::TabPosition::tpMonitor) { + BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ": navigating away from Monitor tab before unload"; + mainframe->m_tabpanel->SetSelection(MainFrame::TabPosition::tp3DEditor); + } + } + + BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ": stopping sync thread before unload"; + if (m_user_sync_token) { + m_user_sync_token.reset(); + } + if (m_sync_update_thread.joinable()) { + BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ": waiting for sync thread to finish"; + m_sync_update_thread.join(); + BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ": sync thread finished"; + } + + if (m_agent) { + BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ": clearing callbacks and stopping operations"; + + m_agent->set_on_ssdp_msg_fn(nullptr); + m_agent->set_on_user_login_fn(nullptr); + m_agent->set_on_printer_connected_fn(nullptr); + m_agent->set_on_server_connected_fn(nullptr); + m_agent->set_on_http_error_fn(nullptr); + m_agent->set_on_subscribe_failure_fn(nullptr); + m_agent->set_on_message_fn(nullptr); + m_agent->set_on_user_message_fn(nullptr); + m_agent->set_on_local_connect_fn(nullptr); + m_agent->set_on_local_message_fn(nullptr); + m_agent->set_queue_on_main_fn(nullptr); + + m_agent->start_discovery(false, false); + m_agent->disconnect_printer(); + + std::this_thread::sleep_for(std::chrono::milliseconds(500)); + + BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ": destroying network agent"; + delete m_agent; + m_agent = nullptr; + + std::this_thread::sleep_for(std::chrono::milliseconds(300)); + } + + if (Slic3r::NetworkAgent::is_network_module_loaded()) { + BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ": unloading old module"; + int unload_result = Slic3r::NetworkAgent::unload_network_module(); + BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ": unload result = " << unload_result; + std::this_thread::sleep_for(std::chrono::milliseconds(200)); + } + + BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ": calling restart_networking"; + restart_networking(); + BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ": restart_networking returned"; + + std::string loaded_version = Slic3r::NetworkAgent::get_version(); + bool success = m_agent != nullptr && !loaded_version.empty() && loaded_version != "00.00.00.00"; + bool user_logged_in = m_agent && m_agent->is_user_login(); + BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ": after restart_networking, is_user_login = " << user_logged_in + << ", m_agent = " << (m_agent ? "valid" : "null") + << ", version = " << loaded_version; + + if (success && m_agent && m_device_manager) { + BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ": connecting to cloud server"; + m_agent->connect_server(); + BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ": re-subscribing to cloud printers"; + m_device_manager->add_user_subscribe(); + } + + if (mainframe && mainframe->m_monitor) { + mainframe->m_monitor->update_network_version_footer(); + mainframe->m_monitor->set_default(); + BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ": reset monitor panel"; + } + + BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ": hot reload " << (success ? "successful" : "failed"); + return success; +} + +std::string GUI_App::get_latest_network_version() const +{ + return BBL::get_latest_network_version(); +} + +bool GUI_App::has_network_update_available() const +{ + std::string current = Slic3r::NetworkAgent::get_version(); + std::string latest = get_latest_network_version(); + + if (current.empty() || current == "00.00.00.00") + return false; + + return current.substr(0, 8) != latest.substr(0, 8); +} + +void GUI_App::show_network_plugin_download_dialog(bool is_update) +{ + auto load_error = Slic3r::NetworkAgent::get_load_error(); + + NetworkPluginDownloadDialog::Mode mode; + if (load_error.has_error) { + mode = NetworkPluginDownloadDialog::Mode::CorruptedPlugin; + } else if (is_update) { + mode = NetworkPluginDownloadDialog::Mode::UpdateAvailable; + } else { + mode = NetworkPluginDownloadDialog::Mode::MissingPlugin; + } + + std::string current_version = Slic3r::NetworkAgent::get_version(); + + NetworkPluginDownloadDialog dlg(mainframe, mode, current_version, + load_error.message, load_error.technical_details); + + int result = dlg.ShowModal(); + + switch (result) { + case NetworkPluginDownloadDialog::RESULT_DOWNLOAD: + { + std::string selected = dlg.get_selected_version(); + app_config->set_network_plugin_version(selected); + app_config->save(); + + DownloadProgressDialog download_dlg(_L("Downloading Network Plugin")); + download_dlg.ShowModal(); + } + break; + + case NetworkPluginDownloadDialog::RESULT_REMIND_LATER: + app_config->set_remind_network_update_later(true); + app_config->save(); + break; + + case NetworkPluginDownloadDialog::RESULT_SKIP_VERSION: + { + std::string latest = get_latest_network_version(); + app_config->add_skipped_network_version(latest); + app_config->save(); + } + break; + + case NetworkPluginDownloadDialog::RESULT_DONT_ASK: + app_config->set_network_update_prompt_disabled(true); + app_config->save(); + break; + + case NetworkPluginDownloadDialog::RESULT_SKIP: + default: + break; + } +} + void GUI_App::remove_old_networking_plugins() { std::string data_dir_str = data_dir(); @@ -1601,8 +1824,20 @@ bool GUI_App::check_networking_version() if (!network_ver.empty()) { BOOST_LOG_TRIVIAL(info) << "get_network_agent_version=" << network_ver; } - std::string studio_ver = NetworkAgent::use_legacy_network ? BAMBU_NETWORK_AGENT_VERSION_LEGACY : BAMBU_NETWORK_AGENT_VERSION; - if (network_ver.length() >= 8) { + + std::string studio_ver; + if (NetworkAgent::use_legacy_network) { + studio_ver = BAMBU_NETWORK_AGENT_VERSION_LEGACY; + } else if (app_config) { + std::string user_version = app_config->get_network_plugin_version(); + studio_ver = user_version.empty() ? BBL::get_latest_network_version() : user_version; + } else { + studio_ver = BBL::get_latest_network_version(); + } + + BOOST_LOG_TRIVIAL(info) << "check_networking_version: network_ver=" << network_ver << ", expected=" << studio_ver; + + if (network_ver.length() >= 8 && studio_ver.length() >= 8) { if (network_ver.substr(0,8) == studio_ver.substr(0,8)) { m_networking_compatible = true; return true; @@ -2857,78 +3092,103 @@ void GUI_App::copy_network_if_available() { if (app_config->get("update_network_plugin") != "true") return; - std::string network_library, player_library, live555_library, network_library_dst, player_library_dst, live555_library_dst; + std::string data_dir_str = data_dir(); boost::filesystem::path data_dir_path(data_dir_str); auto plugin_folder = data_dir_path / "plugins"; auto cache_folder = data_dir_path / "ota"; std::string changelog_file = cache_folder.string() + "/network_plugins.json"; + + std::string cached_version; + if (boost::filesystem::exists(changelog_file)) { + try { + boost::nowide::ifstream ifs(changelog_file); + json j; + ifs >> j; + if (j.contains("version")) + cached_version = j["version"]; + BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ": cached_version = " << cached_version; + } catch (nlohmann::detail::parse_error& err) { + BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << ": parse " << changelog_file << " failed: " << err.what(); + } + } + + if (cached_version.empty()) { + BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << ": no version found in changelog, aborting copy"; + app_config->set("update_network_plugin", "false"); + return; + } + + std::string network_library, player_library, live555_library, network_library_dst, player_library_dst, live555_library_dst; #if defined(_MSC_VER) || defined(_WIN32) network_library = cache_folder.string() + "/bambu_networking.dll"; - player_library = cache_folder.string() + "/BambuSource.dll"; - live555_library = cache_folder.string() + "/live555.dll"; - network_library_dst = plugin_folder.string() + "/bambu_networking.dll"; - player_library_dst = plugin_folder.string() + "/BambuSource.dll"; + player_library = cache_folder.string() + "/BambuSource.dll"; + live555_library = cache_folder.string() + "/live555.dll"; + network_library_dst = plugin_folder.string() + "/" + std::string(BAMBU_NETWORK_LIBRARY) + "_" + cached_version + ".dll"; + player_library_dst = plugin_folder.string() + "/BambuSource.dll"; live555_library_dst = plugin_folder.string() + "/live555.dll"; #elif defined(__WXMAC__) network_library = cache_folder.string() + "/libbambu_networking.dylib"; player_library = cache_folder.string() + "/libBambuSource.dylib"; live555_library = cache_folder.string() + "/liblive555.dylib"; - network_library_dst = plugin_folder.string() + "/libbambu_networking.dylib"; + network_library_dst = plugin_folder.string() + "/lib" + std::string(BAMBU_NETWORK_LIBRARY) + "_" + cached_version + ".dylib"; player_library_dst = plugin_folder.string() + "/libBambuSource.dylib"; live555_library_dst = plugin_folder.string() + "/liblive555.dylib"; #else network_library = cache_folder.string() + "/libbambu_networking.so"; - player_library = cache_folder.string() + "/libBambuSource.so"; - live555_library = cache_folder.string() + "/liblive555.so"; - network_library_dst = plugin_folder.string() + "/libbambu_networking.so"; - player_library_dst = plugin_folder.string() + "/libBambuSource.so"; + player_library = cache_folder.string() + "/libBambuSource.so"; + live555_library = cache_folder.string() + "/liblive555.so"; + network_library_dst = plugin_folder.string() + "/lib" + std::string(BAMBU_NETWORK_LIBRARY) + "_" + cached_version + ".so"; + player_library_dst = plugin_folder.string() + "/libBambuSource.so"; live555_library_dst = plugin_folder.string() + "/liblive555.so"; #endif - BOOST_LOG_TRIVIAL(info) << __FUNCTION__<< ": checking network_library " << network_library << ", player_library " << player_library; + BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ": checking network_library " << network_library << ", player_library " << player_library; if (!boost::filesystem::exists(plugin_folder)) { - BOOST_LOG_TRIVIAL(info)<< __FUNCTION__ << ": create directory "<set(SETTING_NETWORK_PLUGIN_VERSION, cached_version); + app_config->save(); } if (boost::filesystem::exists(player_library)) { CopyFileResult cfr = copy_file(player_library, player_library_dst, error_message, false); if (cfr != CopyFileResult::SUCCESS) { - BOOST_LOG_TRIVIAL(error) << __FUNCTION__<< ": Copying failed(" << cfr << "): " << error_message; + BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << ": Copying failed(" << cfr << "): " << error_message; return; } static constexpr const auto perms = fs::owner_read | fs::owner_write | fs::group_read | fs::others_read; fs::permissions(player_library_dst, perms); fs::remove(player_library); - BOOST_LOG_TRIVIAL(info) << __FUNCTION__<< ": Copying player library from" << player_library << " to " << player_library_dst<<" successfully."; + BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ": Copying player library from " << player_library << " to " << player_library_dst << " successfully."; } if (boost::filesystem::exists(live555_library)) { CopyFileResult cfr = copy_file(live555_library, live555_library_dst, error_message, false); if (cfr != CopyFileResult::SUCCESS) { - BOOST_LOG_TRIVIAL(error) << __FUNCTION__<< ": Copying failed(" << cfr << "): " << error_message; + BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << ": Copying failed(" << cfr << "): " << error_message; return; } static constexpr const auto perms = fs::owner_read | fs::owner_write | fs::group_read | fs::others_read; fs::permissions(live555_library_dst, perms); fs::remove(live555_library); - BOOST_LOG_TRIVIAL(info) << __FUNCTION__<< ": Copying live555 library from" << live555_library << " to " << live555_library_dst<<" successfully."; + BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ": Copying live555 library from " << live555_library << " to " << live555_library_dst << " successfully."; } if (boost::filesystem::exists(changelog_file)) fs::remove(changelog_file); @@ -2939,13 +3199,39 @@ bool GUI_App::on_init_network(bool try_backup) { bool create_network_agent = false; auto should_load_networking_plugin = app_config->get_bool("installed_networking"); + + std::string config_version = app_config->get_network_plugin_version(); + + if (should_load_networking_plugin && Slic3r::NetworkAgent::legacy_library_exists() && config_version.empty()) { + BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ": migration: legacy library found with no config version, removing and requesting download"; + Slic3r::NetworkAgent::remove_legacy_library(); + m_networking_need_update = true; + return false; + } + if(!should_load_networking_plugin) { BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << "Don't load plugin as installed_networking is false"; } else { - int load_agent_dll = Slic3r::NetworkAgent::initialize_network_module(); + if (config_version.empty()) { + BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ": no version configured, need to download"; + m_networking_need_update = true; + return false; + } + int load_agent_dll = Slic3r::NetworkAgent::initialize_network_module(false, config_version); __retry: if (!load_agent_dll) { BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ": on_init_network, load dll ok"; + + 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) { + 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(); + } + } + if (check_networking_version()) { BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ": on_init_network, compatibility version"; auto bambu_source = Slic3r::NetworkAgent::get_bambu_source_entry(); @@ -2962,7 +3248,7 @@ __retry: if (try_backup) { int result = Slic3r::NetworkAgent::unload_network_module(); BOOST_LOG_TRIVIAL(info) << "on_init_network, version mismatch, unload_network_module, result = " << result; - load_agent_dll = Slic3r::NetworkAgent::initialize_network_module(true); + load_agent_dll = Slic3r::NetworkAgent::initialize_network_module(true, config_version); try_backup = false; goto __retry; } @@ -3041,6 +3327,24 @@ __retry: m_user_manager = new Slic3r::UserManager(); } + if (create_network_agent && m_networking_compatible && !NetworkAgent::use_legacy_network) { + app_config->clear_remind_network_update_later(); + + if (has_network_update_available()) { + std::string latest = get_latest_network_version(); + + bool should_prompt = !app_config->is_network_update_prompt_disabled() + && !app_config->is_network_version_skipped(latest) + && !app_config->should_remind_network_update_later(); + + if (should_prompt) { + CallAfter([this]() { + show_network_plugin_download_dialog(true); + }); + } + } + } + return true; } @@ -4880,6 +5184,17 @@ bool GUI_App::process_network_msg(std::string dev_id, std::string msg) { if (dev_id.empty()) { BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << msg; + + if (msg == "unsigned_studio") { + BOOST_LOG_TRIVIAL(info) << "process_network_msg, unsigned_studio"; + MessageDialog msg_dlg(nullptr, + _L("Bambu Lab has implemented a signature verification check in their network plugin that restricts " + "third-party software from communicating with your printer.\n\n" + "As a result, some printing functions are unavailable in OrcaSlicer."), + _L("Network Plugin Restriction"), wxICON_WARNING | wxOK); + msg_dlg.ShowModal(); + return true; + } } else if (msg == "device_cert_installed") { BOOST_LOG_TRIVIAL(info) << "process_network_msg, device_cert_installed"; diff --git a/src/slic3r/GUI/GUI_App.hpp b/src/slic3r/GUI/GUI_App.hpp index f3900f6128..476dfc6f68 100644 --- a/src/slic3r/GUI/GUI_App.hpp +++ b/src/slic3r/GUI/GUI_App.hpp @@ -686,6 +686,11 @@ public: void restart_networking(); void check_config_updates_from_updater() { check_updates(false); } + void show_network_plugin_download_dialog(bool is_update = false); + bool hot_reload_network_plugin(); + std::string get_latest_network_version() const; + bool has_network_update_available() const; + private: int updating_bambu_networking(); bool on_init_inner(); @@ -712,6 +717,7 @@ private: bool m_init_app_config_from_older { false }; bool m_datadir_redefined { false }; std::string m_older_data_dir_path; + bool m_unsigned_plugin_warning_shown { false }; boost::optional m_last_config_version; bool m_config_corrupted { false }; std::string m_open_method; diff --git a/src/slic3r/GUI/Monitor.cpp b/src/slic3r/GUI/Monitor.cpp index aa1e3e2ea8..fd4249863f 100644 --- a/src/slic3r/GUI/Monitor.cpp +++ b/src/slic3r/GUI/Monitor.cpp @@ -195,6 +195,11 @@ void MonitorPanel::init_tabpanel() m_hms_panel = new HMSPanel(m_tabpanel); m_tabpanel->AddPage(m_hms_panel, _L("Assistant(HMS)"), "", false); + std::string network_ver = Slic3r::NetworkAgent::get_version(); + if (!network_ver.empty()) { + m_tabpanel->SetFooterText(wxString::Format("Network plugin v%s", network_ver)); + } + m_initialized = true; show_status((int)MonitorStatus::MONITOR_NO_PRINTER); } @@ -407,6 +412,7 @@ bool MonitorPanel::Show(bool show) DeviceManager* dev = Slic3r::GUI::wxGetApp().getDeviceManager(); if (show) { start_update(); + update_network_version_footer(); m_refresh_timer->Stop(); m_refresh_timer->SetOwner(this); @@ -517,5 +523,13 @@ void MonitorPanel::jump_to_LiveView() m_status_info_panel->get_media_play_ctrl()->jump_to_play(); } +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)); + } +} + } // GUI } // Slic3r diff --git a/src/slic3r/GUI/Monitor.hpp b/src/slic3r/GUI/Monitor.hpp index a7f7126534..b7502b9d51 100644 --- a/src/slic3r/GUI/Monitor.hpp +++ b/src/slic3r/GUI/Monitor.hpp @@ -157,6 +157,7 @@ public: void jump_to_HMS(); void jump_to_LiveView(); + void update_network_version_footer(); }; diff --git a/src/slic3r/GUI/NetworkPluginDialog.cpp b/src/slic3r/GUI/NetworkPluginDialog.cpp new file mode 100644 index 0000000000..ca1ae3bea2 --- /dev/null +++ b/src/slic3r/GUI/NetworkPluginDialog.cpp @@ -0,0 +1,371 @@ +#include "NetworkPluginDialog.hpp" +#include "I18N.hpp" +#include "GUI_App.hpp" +#include "MainFrame.hpp" +#include "MsgDialog.hpp" +#include "Widgets/Label.hpp" +#include "BitmapCache.hpp" +#include "wxExtensions.hpp" +#include "slic3r/Utils/bambu_networking.hpp" + +#include +#include +#include + +namespace Slic3r { +namespace GUI { + +NetworkPluginDownloadDialog::NetworkPluginDownloadDialog(wxWindow* parent, Mode mode, + const std::string& current_version, + const std::string& error_message, + const std::string& error_details) + : DPIDialog(parent, wxID_ANY, mode == Mode::UpdateAvailable ? + _L("Network Plugin Update Available") : _L("Bambu Network Plugin Required"), + wxDefaultPosition, wxDefaultSize, wxDEFAULT_DIALOG_STYLE) + , m_mode(mode) + , m_error_message(error_message) + , m_error_details(error_details) +{ + SetBackgroundColour(*wxWHITE); + + wxBoxSizer* main_sizer = new wxBoxSizer(wxVERTICAL); + + auto m_line_top = new wxPanel(this, wxID_ANY, wxDefaultPosition, wxSize(-1, 1)); + m_line_top->SetBackgroundColour(wxColour(166, 169, 170)); + main_sizer->Add(m_line_top, 0, wxEXPAND, 0); + main_sizer->Add(0, 0, 0, wxTOP, FromDIP(20)); + + SetSizer(main_sizer); + + if (mode == Mode::UpdateAvailable) { + create_update_available_ui(current_version); + } else { + create_missing_plugin_ui(); + } + Layout(); + Fit(); + CentreOnParent(); + wxGetApp().UpdateDlgDarkUI(this); +} + +void NetworkPluginDownloadDialog::create_missing_plugin_ui() +{ + wxBoxSizer* main_sizer = static_cast(GetSizer()); + + auto* desc = new wxStaticText(this, wxID_ANY, + m_mode == Mode::CorruptedPlugin ? + _L("The Bambu Network Plugin is corrupted or incompatible. Please reinstall it.") : + _L("The Bambu Network Plugin is required for cloud features, printer discovery, and remote printing.")); + desc->SetFont(::Label::Body_13); + desc->Wrap(FromDIP(400)); + main_sizer->Add(desc, 0, wxLEFT | wxRIGHT, FromDIP(25)); + main_sizer->Add(0, 0, 0, wxTOP, FromDIP(15)); + + if (!m_error_message.empty()) { + auto* error_label = new wxStaticText(this, wxID_ANY, + wxString::Format(_L("Error: %s"), wxString::FromUTF8(m_error_message))); + error_label->SetFont(::Label::Body_13); + error_label->SetForegroundColour(wxColour(208, 93, 93)); + error_label->Wrap(FromDIP(400)); + main_sizer->Add(error_label, 0, wxLEFT | wxRIGHT, FromDIP(25)); + main_sizer->Add(0, 0, 0, wxTOP, FromDIP(10)); + + if (!m_error_details.empty()) { + m_details_pane = new wxCollapsiblePane(this, wxID_ANY, _L("Show details")); + auto* pane = m_details_pane->GetPane(); + auto* pane_sizer = new wxBoxSizer(wxVERTICAL); + + auto* details_text = new wxStaticText(pane, wxID_ANY, wxString::FromUTF8(m_error_details)); + details_text->SetFont(wxGetApp().code_font()); + details_text->Wrap(FromDIP(380)); + pane_sizer->Add(details_text, 0, wxALL, FromDIP(10)); + + pane->SetSizer(pane_sizer); + main_sizer->Add(m_details_pane, 0, wxLEFT | wxRIGHT | wxEXPAND, FromDIP(25)); + main_sizer->Add(0, 0, 0, wxTOP, FromDIP(10)); + } + } + + auto* version_label = new wxStaticText(this, wxID_ANY, _L("Version to install:")); + version_label->SetFont(::Label::Body_13); + main_sizer->Add(version_label, 0, wxLEFT | wxRIGHT, FromDIP(25)); + main_sizer->Add(0, 0, 0, wxTOP, FromDIP(5)); + + setup_version_selector(); + main_sizer->Add(m_version_combo, 0, wxLEFT | wxRIGHT | wxEXPAND, FromDIP(25)); + main_sizer->Add(0, 0, 0, wxTOP, FromDIP(20)); + + auto* btn_sizer = new wxBoxSizer(wxHORIZONTAL); + btn_sizer->Add(0, 0, 1, wxEXPAND, 0); + + StateColor btn_bg_green( + std::pair(wxColour(0, 137, 123), StateColor::Pressed), + std::pair(wxColour(38, 166, 154), StateColor::Hovered), + std::pair(wxColour(0, 150, 136), StateColor::Normal)); + + StateColor btn_bg_white( + std::pair(wxColour(206, 206, 206), StateColor::Pressed), + std::pair(wxColour(238, 238, 238), StateColor::Hovered), + std::pair(*wxWHITE, StateColor::Normal)); + + auto* btn_download = new Button(this, _L("Download and Install")); + btn_download->SetBackgroundColor(btn_bg_green); + btn_download->SetBorderColor(*wxWHITE); + btn_download->SetTextColor(*wxWHITE); + btn_download->SetFont(::Label::Body_12); + btn_download->SetMinSize(wxSize(FromDIP(120), FromDIP(24))); + btn_download->Bind(wxEVT_BUTTON, &NetworkPluginDownloadDialog::on_download, this); + btn_sizer->Add(btn_download, 0, wxRIGHT, FromDIP(10)); + + auto* btn_skip = new Button(this, _L("Skip for Now")); + btn_skip->SetBackgroundColor(btn_bg_white); + btn_skip->SetBorderColor(wxColour(38, 46, 48)); + btn_skip->SetFont(::Label::Body_12); + btn_skip->SetMinSize(wxSize(FromDIP(100), FromDIP(24))); + btn_skip->Bind(wxEVT_BUTTON, &NetworkPluginDownloadDialog::on_skip, this); + btn_sizer->Add(btn_skip, 0, wxRIGHT, FromDIP(10)); + + main_sizer->Add(btn_sizer, 0, wxLEFT | wxRIGHT | wxEXPAND, FromDIP(20)); + main_sizer->Add(0, 0, 0, wxBOTTOM, FromDIP(20)); +} + +void NetworkPluginDownloadDialog::create_update_available_ui(const std::string& current_version) +{ + wxBoxSizer* main_sizer = static_cast(GetSizer()); + + auto* desc = new wxStaticText(this, wxID_ANY, + _L("A new version of the Bambu Network Plugin is available.")); + desc->SetFont(::Label::Body_13); + desc->Wrap(FromDIP(400)); + main_sizer->Add(desc, 0, wxLEFT | wxRIGHT, FromDIP(25)); + main_sizer->Add(0, 0, 0, wxTOP, FromDIP(15)); + + auto* version_text = new wxStaticText(this, wxID_ANY, + wxString::Format(_L("Current version: %s"), wxString::FromUTF8(current_version))); + version_text->SetFont(::Label::Body_13); + main_sizer->Add(version_text, 0, wxLEFT | wxRIGHT, FromDIP(25)); + main_sizer->Add(0, 0, 0, wxTOP, FromDIP(10)); + + auto* update_label = new wxStaticText(this, wxID_ANY, _L("Update to version:")); + update_label->SetFont(::Label::Body_13); + main_sizer->Add(update_label, 0, wxLEFT | wxRIGHT, FromDIP(25)); + main_sizer->Add(0, 0, 0, wxTOP, FromDIP(5)); + + setup_version_selector(); + main_sizer->Add(m_version_combo, 0, wxLEFT | wxRIGHT | wxEXPAND, FromDIP(25)); + main_sizer->Add(0, 0, 0, wxTOP, FromDIP(20)); + + auto* btn_sizer = new wxBoxSizer(wxHORIZONTAL); + btn_sizer->Add(0, 0, 1, wxEXPAND, 0); + + StateColor btn_bg_green( + std::pair(wxColour(0, 137, 123), StateColor::Pressed), + std::pair(wxColour(38, 166, 154), StateColor::Hovered), + std::pair(wxColour(0, 150, 136), StateColor::Normal)); + + StateColor btn_bg_white( + std::pair(wxColour(206, 206, 206), StateColor::Pressed), + std::pair(wxColour(238, 238, 238), StateColor::Hovered), + std::pair(*wxWHITE, StateColor::Normal)); + + auto* btn_download = new Button(this, _L("Update Now")); + btn_download->SetBackgroundColor(btn_bg_green); + btn_download->SetBorderColor(*wxWHITE); + btn_download->SetTextColor(*wxWHITE); + btn_download->SetFont(::Label::Body_12); + btn_download->SetMinSize(wxSize(FromDIP(100), FromDIP(24))); + btn_download->Bind(wxEVT_BUTTON, &NetworkPluginDownloadDialog::on_download, this); + btn_sizer->Add(btn_download, 0, wxRIGHT, FromDIP(10)); + + auto* btn_remind = new Button(this, _L("Remind Later")); + btn_remind->SetBackgroundColor(btn_bg_white); + btn_remind->SetBorderColor(wxColour(38, 46, 48)); + btn_remind->SetFont(::Label::Body_12); + btn_remind->SetMinSize(wxSize(FromDIP(100), FromDIP(24))); + btn_remind->Bind(wxEVT_BUTTON, &NetworkPluginDownloadDialog::on_remind_later, this); + btn_sizer->Add(btn_remind, 0, wxRIGHT, FromDIP(10)); + + auto* btn_skip = new Button(this, _L("Skip Version")); + btn_skip->SetBackgroundColor(btn_bg_white); + btn_skip->SetBorderColor(wxColour(38, 46, 48)); + btn_skip->SetFont(::Label::Body_12); + btn_skip->SetMinSize(wxSize(FromDIP(100), FromDIP(24))); + btn_skip->Bind(wxEVT_BUTTON, &NetworkPluginDownloadDialog::on_skip_version, this); + btn_sizer->Add(btn_skip, 0, wxRIGHT, FromDIP(10)); + + auto* btn_dont_ask = new Button(this, _L("Don't Ask Again")); + btn_dont_ask->SetBackgroundColor(btn_bg_white); + btn_dont_ask->SetBorderColor(wxColour(38, 46, 48)); + btn_dont_ask->SetFont(::Label::Body_12); + btn_dont_ask->SetMinSize(wxSize(FromDIP(110), FromDIP(24))); + btn_dont_ask->Bind(wxEVT_BUTTON, &NetworkPluginDownloadDialog::on_dont_ask, this); + btn_sizer->Add(btn_dont_ask, 0, wxRIGHT, FromDIP(10)); + + main_sizer->Add(btn_sizer, 0, wxLEFT | wxRIGHT | wxEXPAND, FromDIP(20)); + main_sizer->Add(0, 0, 0, wxBOTTOM, FromDIP(20)); +} + +void NetworkPluginDownloadDialog::setup_version_selector() +{ + m_version_combo = new ComboBox(this, wxID_ANY, wxEmptyString, + 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_version_combo->Append(label); + } + + m_version_combo->SetSelection(0); +} + +std::string NetworkPluginDownloadDialog::get_selected_version() const +{ + if (!m_version_combo) { + return ""; + } + + int selection = m_version_combo->GetSelection(); + if (selection < 0 || selection >= static_cast(BBL::AVAILABLE_NETWORK_VERSIONS_COUNT)) { + return ""; + } + + return BBL::AVAILABLE_NETWORK_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) { + MessageDialog warn_dlg(this, wxString::FromUTF8(warning), _L("Warning"), wxOK | wxCANCEL | wxICON_WARNING); + if (warn_dlg.ShowModal() != wxID_OK) { + return; + } + } + } + EndModal(RESULT_DOWNLOAD); +} + +void NetworkPluginDownloadDialog::on_skip(wxCommandEvent& evt) +{ + EndModal(RESULT_SKIP); +} + +void NetworkPluginDownloadDialog::on_remind_later(wxCommandEvent& evt) +{ + EndModal(RESULT_REMIND_LATER); +} + +void NetworkPluginDownloadDialog::on_skip_version(wxCommandEvent& evt) +{ + EndModal(RESULT_SKIP_VERSION); +} + +void NetworkPluginDownloadDialog::on_dont_ask(wxCommandEvent& evt) +{ + EndModal(RESULT_DONT_ASK); +} + +void NetworkPluginDownloadDialog::on_dpi_changed(const wxRect& suggested_rect) +{ + Layout(); + Fit(); +} + +NetworkPluginRestartDialog::NetworkPluginRestartDialog(wxWindow* parent) + : DPIDialog(parent, wxID_ANY, _L("Restart Required"), + wxDefaultPosition, wxDefaultSize, wxDEFAULT_DIALOG_STYLE) +{ + SetBackgroundColour(*wxWHITE); + + wxBoxSizer* main_sizer = new wxBoxSizer(wxVERTICAL); + + auto m_line_top = new wxPanel(this, wxID_ANY, wxDefaultPosition, wxSize(-1, 1)); + m_line_top->SetBackgroundColour(wxColour(166, 169, 170)); + main_sizer->Add(m_line_top, 0, wxEXPAND, 0); + main_sizer->Add(0, 0, 0, wxTOP, FromDIP(20)); + + auto* icon_sizer = new wxBoxSizer(wxHORIZONTAL); + auto* icon_bitmap = new wxStaticBitmap(this, wxID_ANY, + create_scaled_bitmap("info", nullptr, 64)); + icon_sizer->Add(icon_bitmap, 0, wxALL, FromDIP(10)); + + auto* text_sizer = new wxBoxSizer(wxVERTICAL); + + auto* desc = new wxStaticText(this, wxID_ANY, + _L("The Bambu Network Plugin has been installed successfully.")); + desc->SetFont(::Label::Body_14); + desc->Wrap(FromDIP(350)); + text_sizer->Add(desc, 0, wxTOP, FromDIP(10)); + text_sizer->Add(0, 0, 0, wxTOP, FromDIP(10)); + + auto* restart_msg = new wxStaticText(this, wxID_ANY, + _L("A restart is required to load the new plugin. Would you like to restart now?")); + restart_msg->SetFont(::Label::Body_13); + restart_msg->Wrap(FromDIP(350)); + text_sizer->Add(restart_msg, 0, wxBOTTOM, FromDIP(10)); + + icon_sizer->Add(text_sizer, 1, wxEXPAND | wxRIGHT, FromDIP(20)); + main_sizer->Add(icon_sizer, 0, wxLEFT | wxRIGHT | wxEXPAND, FromDIP(15)); + main_sizer->Add(0, 0, 0, wxTOP, FromDIP(20)); + + auto* btn_sizer = new wxBoxSizer(wxHORIZONTAL); + btn_sizer->Add(0, 0, 1, wxEXPAND, 0); + + StateColor btn_bg_green( + std::pair(wxColour(0, 137, 123), StateColor::Pressed), + std::pair(wxColour(38, 166, 154), StateColor::Hovered), + std::pair(wxColour(0, 150, 136), StateColor::Normal)); + + StateColor btn_bg_white( + std::pair(wxColour(206, 206, 206), StateColor::Pressed), + std::pair(wxColour(238, 238, 238), StateColor::Hovered), + std::pair(*wxWHITE, StateColor::Normal)); + + auto* btn_restart = new Button(this, _L("Restart Now")); + btn_restart->SetBackgroundColor(btn_bg_green); + btn_restart->SetBorderColor(*wxWHITE); + btn_restart->SetTextColor(*wxWHITE); + btn_restart->SetFont(::Label::Body_12); + btn_restart->SetMinSize(wxSize(FromDIP(100), FromDIP(24))); + btn_restart->Bind(wxEVT_BUTTON, [this](wxCommandEvent&) { + m_restart_now = true; + EndModal(wxID_OK); + }); + btn_sizer->Add(btn_restart, 0, wxRIGHT, FromDIP(10)); + + auto* btn_later = new Button(this, _L("Restart Later")); + btn_later->SetBackgroundColor(btn_bg_white); + btn_later->SetBorderColor(wxColour(38, 46, 48)); + btn_later->SetFont(::Label::Body_12); + btn_later->SetMinSize(wxSize(FromDIP(100), FromDIP(24))); + btn_later->Bind(wxEVT_BUTTON, [this](wxCommandEvent&) { + m_restart_now = false; + EndModal(wxID_CANCEL); + }); + btn_sizer->Add(btn_later, 0, wxRIGHT, FromDIP(10)); + + main_sizer->Add(btn_sizer, 0, wxLEFT | wxRIGHT | wxEXPAND, FromDIP(20)); + main_sizer->Add(0, 0, 0, wxBOTTOM, FromDIP(20)); + + SetSizer(main_sizer); + Layout(); + Fit(); + CentreOnParent(); + wxGetApp().UpdateDlgDarkUI(this); +} + +void NetworkPluginRestartDialog::on_dpi_changed(const wxRect& suggested_rect) +{ + Layout(); + Fit(); +} + +} +} diff --git a/src/slic3r/GUI/NetworkPluginDialog.hpp b/src/slic3r/GUI/NetworkPluginDialog.hpp new file mode 100644 index 0000000000..7e28fe2114 --- /dev/null +++ b/src/slic3r/GUI/NetworkPluginDialog.hpp @@ -0,0 +1,74 @@ +#ifndef slic3r_GUI_NetworkPluginDialog_hpp_ +#define slic3r_GUI_NetworkPluginDialog_hpp_ + +#include "GUI_Utils.hpp" +#include "MsgDialog.hpp" +#include "Widgets/ComboBox.hpp" +#include "Widgets/Button.hpp" +#include + +namespace Slic3r { +namespace GUI { + +class NetworkPluginDownloadDialog : public DPIDialog +{ +public: + enum class Mode { + MissingPlugin, + UpdateAvailable, + CorruptedPlugin + }; + + NetworkPluginDownloadDialog(wxWindow* parent, Mode mode, + const std::string& current_version = "", + const std::string& error_message = "", + const std::string& error_details = ""); + + std::string get_selected_version() const; + + enum ResultCode { + RESULT_DOWNLOAD = wxID_OK, + RESULT_SKIP = wxID_CANCEL, + RESULT_REMIND_LATER = wxID_APPLY, + RESULT_SKIP_VERSION = wxID_IGNORE, + RESULT_DONT_ASK = wxID_ABORT + }; + +protected: + void on_dpi_changed(const wxRect& suggested_rect) override; + +private: + void create_missing_plugin_ui(); + void create_update_available_ui(const std::string& current_version); + void setup_version_selector(); + void on_download(wxCommandEvent& evt); + void on_skip(wxCommandEvent& evt); + void on_remind_later(wxCommandEvent& evt); + void on_skip_version(wxCommandEvent& evt); + void on_dont_ask(wxCommandEvent& evt); + + Mode m_mode; + ComboBox* m_version_combo{nullptr}; + wxCollapsiblePane* m_details_pane{nullptr}; + std::string m_error_message; + std::string m_error_details; +}; + +class NetworkPluginRestartDialog : public DPIDialog +{ +public: + NetworkPluginRestartDialog(wxWindow* parent); + + bool should_restart_now() const { return m_restart_now; } + +protected: + void on_dpi_changed(const wxRect& suggested_rect) override; + +private: + bool m_restart_now{false}; +}; + +} // namespace GUI +} // namespace Slic3r + +#endif // slic3r_GUI_NetworkPluginDialog_hpp_ diff --git a/src/slic3r/GUI/Preferences.cpp b/src/slic3r/GUI/Preferences.cpp index 9cc05d5cf1..e2ee8c36c3 100644 --- a/src/slic3r/GUI/Preferences.cpp +++ b/src/slic3r/GUI/Preferences.cpp @@ -14,6 +14,8 @@ #include "NetworkTestDialog.hpp" #include "Widgets/StaticLine.hpp" #include "Widgets/RadioGroup.hpp" +#include "slic3r/Utils/bambu_networking.hpp" +#include "DownloadProgressDialog.hpp" #ifdef __WINDOWS__ #ifdef _MSW_DARK_MODE @@ -842,7 +844,7 @@ wxBoxSizer *PreferencesDialog::create_item_checkbox(wxString title, wxString too if (param == "enable_high_low_temp_mixed_printing") { if (checkbox->GetValue()) { const wxString warning_title = _L("Bed Temperature Difference Warning"); - const wxString warning_message = + const wxString warning_message = _L("Using filaments with significantly different temperatures may cause:\n" "• Extruder clogging\n" "• Nozzle damage\n" @@ -876,6 +878,14 @@ wxBoxSizer *PreferencesDialog::create_item_checkbox(wxString title, wxString too } } + if (param == "legacy_networking") { + bool legacy_mode = checkbox->GetValue(); + if (m_network_version_sizer != nullptr) { + m_network_version_sizer->Show(!legacy_mode); + m_parent->Layout(); + } + } + e.Skip(); }); @@ -1344,6 +1354,92 @@ void PreferencesDialog::create_items() auto item_legacy_network = create_item_checkbox(_L("Use legacy network plugin"), _L("Disable to use latest network plugin that supports new BambuLab firmwares."), "legacy_networking", _L("(Requires restart)")); g_sizer->Add(item_legacy_network); + m_network_version_sizer = new wxBoxSizer(wxHORIZONTAL); + m_network_version_sizer->AddSpacer(FromDIP(DESIGN_LEFT_MARGIN)); + + auto version_title = new wxStaticText(m_parent, wxID_ANY, _L("Network plugin version"), wxDefaultPosition, DESIGN_TITLE_SIZE, wxST_NO_AUTORESIZE); + version_title->SetForegroundColour(DESIGN_GRAY900_COLOR); + version_title->SetFont(::Label::Body_14); + version_title->SetToolTip(_L("Select the network plugin version to use")); + version_title->Wrap(DESIGN_TITLE_SIZE.x); + m_network_version_sizer->Add(version_title, 0, wxALIGN_CENTER); + + m_network_version_combo = new ::ComboBox(m_parent, wxID_ANY, wxEmptyString, wxDefaultPosition, wxSize(FromDIP(180), -1), 0, nullptr, wxCB_READONLY); + m_network_version_combo->SetFont(::Label::Body_14); + m_network_version_combo->GetDropDown().SetFont(::Label::Body_14); + + 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) { + label += " " + _L("(Latest)"); + } + m_network_version_combo->Append(label); + if (current_version == BBL::AVAILABLE_NETWORK_VERSIONS[i].version) { + current_selection = i; + } + } + + m_network_version_combo->SetSelection(current_selection); + m_network_version_sizer->Add(m_network_version_combo, 0, wxALIGN_CENTER | wxLEFT, FromDIP(5)); + + 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; + std::string old_version = app_config->get_network_plugin_version(); + if (old_version.empty()) { + old_version = BBL::get_latest_network_version(); + } + + app_config->set(SETTING_NETWORK_PLUGIN_VERSION, new_version); + app_config->save(); + + 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 (warn_dlg.ShowModal() != wxID_OK) { + app_config->set(SETTING_NETWORK_PLUGIN_VERSION, old_version); + app_config->save(); + e.Skip(); + return; + } + } + + if (Slic3r::NetworkAgent::versioned_library_exists(new_version)) { + BOOST_LOG_TRIVIAL(info) << "Version " << new_version << " already exists on disk, triggering hot reload"; + if (wxGetApp().hot_reload_network_plugin()) { + MessageDialog dlg(this, _L("Network plugin switched successfully."), _L("Success"), wxOK | wxICON_INFORMATION); + dlg.ShowModal(); + } else { + MessageDialog dlg(this, _L("Failed to load network plugin. Please restart the application."), _L("Restart Required"), wxOK | wxICON_WARNING); + dlg.ShowModal(); + } + } else { + wxString msg = wxString::Format( + _L("You've selected network plugin version %s.\n\nWould you like to download and install this version now?\n\nNote: The application may need to restart after installation."), + wxString::FromUTF8(new_version)); + + MessageDialog dlg(this, msg, _L("Download Network Plugin"), wxYES_NO | wxICON_QUESTION); + if (dlg.ShowModal() == wxID_YES) { + DownloadProgressDialog progress_dlg(_L("Downloading Network Plugin")); + progress_dlg.ShowModal(); + } + } + } + } + e.Skip(); + }); + + bool legacy_mode = app_config->get_bool("legacy_networking"); + m_network_version_sizer->Show(!legacy_mode); + g_sizer->Add(m_network_version_sizer); + g_sizer->AddSpacer(FromDIP(10)); sizer_page->Add(g_sizer, 0, wxEXPAND); @@ -1413,6 +1509,18 @@ void PreferencesDialog::create_items() auto loglevel_combox = create_item_loglevel_combobox(_L("Log Level"), _L("Log Level"), log_level_list); g_sizer->Add(loglevel_combox); + g_sizer->Add(create_item_title(_L("Network Plugin")), 1, wxEXPAND); + auto item_reload_plugin = create_item_button(_L("Network plugin"), _L("Reload"), _L("Reload the network plugin without restarting the application"), "", [this]() { + if (wxGetApp().hot_reload_network_plugin()) { + MessageDialog dlg(this, _L("Network plugin reloaded successfully."), _L("Reload"), wxOK | wxICON_INFORMATION); + dlg.ShowModal(); + } else { + MessageDialog dlg(this, _L("Failed to reload network plugin. Please restart the application."), _L("Reload Failed"), wxOK | wxICON_ERROR); + dlg.ShowModal(); + } + }); + g_sizer->Add(item_reload_plugin); + //// DEVELOPER > Debug #if !BBL_RELEASE_TO_PUBLIC g_sizer->Add(create_item_title(_L("Debug")), 1, wxEXPAND); @@ -1613,7 +1721,7 @@ wxBoxSizer* PreferencesDialog::create_debug_page() bSizer->Add(enable_ssl_for_mqtt, 0, wxTOP, FromDIP(3)); bSizer->Add(enable_ssl_for_ftp, 0, wxTOP, FromDIP(3)); bSizer->Add(item_internal_developer, 0, wxTOP, FromDIP(3)); - bSizer->Add(title_host, 0, wxEXPAND); + bSizer->Add(title_host, 0, wxEXPAND | wxTOP, FromDIP(10)); bSizer->Add(radio_group, 0, wxEXPAND | wxLEFT, FromDIP(DESIGN_LEFT_MARGIN)); bSizer->Add(debug_button, 0, wxALIGN_CENTER_HORIZONTAL | wxTOP, FromDIP(15)); diff --git a/src/slic3r/GUI/Preferences.hpp b/src/slic3r/GUI/Preferences.hpp index c6fa92a498..06d9a46f04 100644 --- a/src/slic3r/GUI/Preferences.hpp +++ b/src/slic3r/GUI/Preferences.hpp @@ -68,6 +68,8 @@ public: ::CheckBox * m_dark_mode_ckeckbox = {nullptr}; ::TextInput *m_backup_interval_textinput = {nullptr}; ::CheckBox * m_legacy_networking_ckeckbox = {nullptr}; + ::ComboBox * m_network_version_combo = {nullptr}; + wxBoxSizer * m_network_version_sizer = {nullptr}; wxString m_developer_mode_def; wxString m_internal_developer_mode_def; diff --git a/src/slic3r/GUI/Tabbook.cpp b/src/slic3r/GUI/Tabbook.cpp index ce26f3c658..db8cd3ba10 100644 --- a/src/slic3r/GUI/Tabbook.cpp +++ b/src/slic3r/GUI/Tabbook.cpp @@ -197,6 +197,20 @@ void TabButtonsListCtrl::SetPaddingSize(const wxSize& size) { } } +void TabButtonsListCtrl::SetFooterText(const wxString& text) +{ + if (!m_footer_text) { + m_footer_text = new wxStaticText(this, wxID_ANY, text); + m_footer_text->SetForegroundColour(wxColour(128, 128, 128)); + m_footer_text->SetFont(Label::Body_10); + int em = em_unit(this); + m_sizer->Add(m_footer_text, 0, wxALIGN_CENTER_HORIZONTAL | wxBOTTOM, em / 2); + } else { + m_footer_text->SetLabel(text); + } + m_sizer->Layout(); +} + //#endif // _WIN32 diff --git a/src/slic3r/GUI/Tabbook.hpp b/src/slic3r/GUI/Tabbook.hpp index 7dd19389de..0cea1b8326 100644 --- a/src/slic3r/GUI/Tabbook.hpp +++ b/src/slic3r/GUI/Tabbook.hpp @@ -32,6 +32,7 @@ public: wxString GetPageText(size_t n) const; const wxSize& GetPaddingSize(size_t n); void SetPaddingSize(const wxSize& size); + void SetFooterText(const wxString& text); TabButton* pageButton; private: @@ -43,6 +44,7 @@ private: int m_selection {-1}; int m_btn_margin; int m_line_margin; + wxStaticText* m_footer_text {nullptr}; }; class Tabbook: public wxBookCtrlBase @@ -261,6 +263,11 @@ public: GetBtnsListCtrl()->Rescale(); } + void SetFooterText(const wxString& text) + { + GetBtnsListCtrl()->SetFooterText(text); + } + void OnNavigationKey(wxNavigationKeyEvent& event) { if (event.IsWindowChange()) { diff --git a/src/slic3r/Utils/NetworkAgent.cpp b/src/slic3r/Utils/NetworkAgent.cpp index 5f19e8f4e2..fc70630419 100644 --- a/src/slic3r/Utils/NetworkAgent.cpp +++ b/src/slic3r/Utils/NetworkAgent.cpp @@ -27,6 +27,7 @@ static void* source_module = NULL; #endif bool NetworkAgent::use_legacy_network = true; +NetworkLibraryLoadError NetworkAgent::s_load_error = {}; typedef int (*func_start_print_legacy)(void *agent, PrintParams_Legacy params, OnUpdateStatusFn update_fn, WasCancelledFn cancel_fn, OnWaitFn wait_fn); typedef int (*func_start_local_print_with_record_legacy)(void *agent, PrintParams_Legacy params, OnUpdateStatusFn update_fn, WasCancelledFn cancel_fn, OnWaitFn wait_fn); @@ -219,10 +220,72 @@ std::string NetworkAgent::get_libpath_in_current_directory(std::string library_n return lib_path; } - -int NetworkAgent::initialize_network_module(bool using_backup) +std::string NetworkAgent::get_versioned_library_path(const std::string& version) { - //int ret = -1; + std::string data_dir_str = data_dir(); + boost::filesystem::path data_dir_path(data_dir_str); + auto plugin_folder = data_dir_path / "plugins"; + +#if defined(_MSC_VER) || defined(_WIN32) + return (plugin_folder / (std::string(BAMBU_NETWORK_LIBRARY) + "_" + version + ".dll")).string(); +#elif defined(__WXMAC__) + return (plugin_folder / (std::string("lib") + std::string(BAMBU_NETWORK_LIBRARY) + "_" + version + ".dylib")).string(); +#else + return (plugin_folder / (std::string("lib") + std::string(BAMBU_NETWORK_LIBRARY) + "_" + version + ".so")).string(); +#endif +} + +bool NetworkAgent::versioned_library_exists(const std::string& version) +{ + if (version.empty()) return false; + std::string path = get_versioned_library_path(version); + return boost::filesystem::exists(path); +} + +bool NetworkAgent::legacy_library_exists() +{ + std::string data_dir_str = data_dir(); + boost::filesystem::path data_dir_path(data_dir_str); + auto plugin_folder = data_dir_path / "plugins"; + +#if defined(_MSC_VER) || defined(_WIN32) + auto legacy_path = plugin_folder / (std::string(BAMBU_NETWORK_LIBRARY) + ".dll"); +#elif defined(__WXMAC__) + auto legacy_path = plugin_folder / (std::string("lib") + std::string(BAMBU_NETWORK_LIBRARY) + ".dylib"); +#else + auto legacy_path = plugin_folder / (std::string("lib") + std::string(BAMBU_NETWORK_LIBRARY) + ".so"); +#endif + return boost::filesystem::exists(legacy_path); +} + +void NetworkAgent::remove_legacy_library() +{ + std::string data_dir_str = data_dir(); + boost::filesystem::path data_dir_path(data_dir_str); + auto plugin_folder = data_dir_path / "plugins"; + +#if defined(_MSC_VER) || defined(_WIN32) + auto legacy_path = plugin_folder / (std::string(BAMBU_NETWORK_LIBRARY) + ".dll"); +#elif defined(__WXMAC__) + auto legacy_path = plugin_folder / (std::string("lib") + std::string(BAMBU_NETWORK_LIBRARY) + ".dylib"); +#else + auto legacy_path = plugin_folder / (std::string("lib") + std::string(BAMBU_NETWORK_LIBRARY) + ".so"); +#endif + + if (boost::filesystem::exists(legacy_path)) { + BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ": removing legacy library at " << legacy_path.string(); + boost::system::error_code ec; + boost::filesystem::remove(legacy_path, ec); + if (ec) { + BOOST_LOG_TRIVIAL(warning) << __FUNCTION__ << ": failed to remove legacy library: " << ec.message(); + } + } +} + +int NetworkAgent::initialize_network_module(bool using_backup, const std::string& version) +{ + clear_load_error(); + std::string library; std::string data_dir_str = data_dir(); boost::filesystem::path data_dir_path(data_dir_str); @@ -232,25 +295,33 @@ int NetworkAgent::initialize_network_module(bool using_backup) plugin_folder = plugin_folder/"backup"; } - //first load the library + if (version.empty()) { + BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << ": version is required but not provided"; + set_load_error( + "Network library version not specified", + "A version must be specified to load the network library", + "" + ); + return -1; + } + #if defined(_MSC_VER) || defined(_WIN32) - library = plugin_folder.string() + "\\" + std::string(BAMBU_NETWORK_LIBRARY) + ".dll"; - wchar_t lib_wstr[128]; + library = plugin_folder.string() + "\\" + std::string(BAMBU_NETWORK_LIBRARY) + "_" + version + ".dll"; + BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ": loading versioned library at " << library; + wchar_t lib_wstr[256]; memset(lib_wstr, 0, sizeof(lib_wstr)); ::MultiByteToWideChar(CP_UTF8, NULL, library.c_str(), strlen(library.c_str())+1, lib_wstr, sizeof(lib_wstr) / sizeof(lib_wstr[0])); netwoking_module = LoadLibrary(lib_wstr); - /*if (!netwoking_module) { - library = std::string(BAMBU_NETWORK_LIBRARY) + ".dll"; - memset(lib_wstr, 0, sizeof(lib_wstr)); - ::MultiByteToWideChar(CP_UTF8, NULL, library.c_str(), strlen(library.c_str()) + 1, lib_wstr, sizeof(lib_wstr) / sizeof(lib_wstr[0])); - netwoking_module = LoadLibrary(lib_wstr); - }*/ if (!netwoking_module) { - BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format(", try load library directly from current directory"); - + BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ": versioned library not found, trying current directory"; std::string library_path = get_libpath_in_current_directory(std::string(BAMBU_NETWORK_LIBRARY)); if (library_path.empty()) { BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format(", can not get path in current directory for %1%") % BAMBU_NETWORK_LIBRARY; + set_load_error( + "Network library not found", + "Could not locate versioned library: " + library, + library + ); return -1; } BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format(", current path %1%")%library_path; @@ -260,28 +331,36 @@ int NetworkAgent::initialize_network_module(bool using_backup) } #else #if defined(__WXMAC__) - library = plugin_folder.string() + "/" + std::string("lib") + std::string(BAMBU_NETWORK_LIBRARY) + ".dylib"; + std::string lib_ext = ".dylib"; #else - library = plugin_folder.string() + "/" + std::string("lib") + std::string(BAMBU_NETWORK_LIBRARY) + ".so"; + std::string lib_ext = ".so"; #endif - printf("loading network module at %s\n", library.c_str()); - netwoking_module = dlopen( library.c_str(), RTLD_LAZY); + + library = plugin_folder.string() + "/" + std::string("lib") + std::string(BAMBU_NETWORK_LIBRARY) + "_" + version + lib_ext; + BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ": loading versioned library at " << library; + + netwoking_module = dlopen(library.c_str(), RTLD_LAZY); if (!netwoking_module) { - /*#if defined(__WXMAC__) - library = std::string("lib") + BAMBU_NETWORK_LIBRARY + ".dylib"; - #else - library = std::string("lib") + BAMBU_NETWORK_LIBRARY + ".so"; - #endif*/ - //netwoking_module = dlopen( library.c_str(), RTLD_LAZY); char* dll_error = dlerror(); - printf("error, dlerror is %s\n", dll_error); - BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format(", error, dlerror is %1%")%dll_error; + BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << ": dlopen failed: " << (dll_error ? dll_error : "unknown error"); + set_load_error( + "Failed to load network library", + dll_error ? std::string(dll_error) : "Unknown dlopen error", + library + ); } printf("after dlopen, network_module is %p\n", netwoking_module); #endif if (!netwoking_module) { BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format(", can not Load Library for %1%")%library; + if (!s_load_error.has_error) { + set_load_error( + "Network library failed to load", + "LoadLibrary/dlopen returned null", + library + ); + } return -1; } BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format(", successfully loaded library %1%, module %2%")%library %netwoking_module; @@ -391,6 +470,12 @@ int NetworkAgent::initialize_network_module(bool using_backup) get_mw_user_preference_ptr = reinterpret_cast(get_network_function("bambu_network_get_mw_user_preference")); get_mw_user_4ulist_ptr = reinterpret_cast(get_network_function("bambu_network_get_mw_user_4ulist")); + if (get_version_ptr) { + std::string version = get_version_ptr(); + printf("network plugin version: %s\n", version.c_str()); + BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ": network plugin version = " << version; + } + return 0; } @@ -515,6 +600,11 @@ int NetworkAgent::unload_network_module() return 0; } +bool NetworkAgent::is_network_module_loaded() +{ + return netwoking_module != nullptr; +} + #if defined(_MSC_VER) || defined(_WIN32) HMODULE NetworkAgent::get_bambu_source_entry() #else @@ -610,6 +700,24 @@ std::string NetworkAgent::get_version() return "00.00.00.00"; } +NetworkLibraryLoadError NetworkAgent::get_load_error() +{ + return s_load_error; +} + +void NetworkAgent::clear_load_error() +{ + s_load_error = NetworkLibraryLoadError{}; +} + +void NetworkAgent::set_load_error(const std::string& message, const std::string& technical_details, const std::string& attempted_path) +{ + s_load_error.has_error = true; + s_load_error.message = message; + s_load_error.technical_details = technical_details; + s_load_error.attempted_path = attempted_path; +} + int NetworkAgent::init_log() { int ret = 0; diff --git a/src/slic3r/Utils/NetworkAgent.hpp b/src/slic3r/Utils/NetworkAgent.hpp index 01d16d660b..e03106068e 100644 --- a/src/slic3r/Utils/NetworkAgent.hpp +++ b/src/slic3r/Utils/NetworkAgent.hpp @@ -116,8 +116,13 @@ class NetworkAgent public: static std::string get_libpath_in_current_directory(std::string library_name); - static int initialize_network_module(bool using_backup = false); + static std::string get_versioned_library_path(const std::string& version); + static bool versioned_library_exists(const std::string& version); + static bool legacy_library_exists(); + static void remove_legacy_library(); + static int initialize_network_module(bool using_backup = false, const std::string& version = ""); static int unload_network_module(); + static bool is_network_module_loaded(); #if defined(_MSC_VER) || defined(_WIN32) static HMODULE get_bambu_source_entry(); #else @@ -126,6 +131,10 @@ public: static std::string get_version(); static void* get_network_function(const char* name); static bool use_legacy_network; + + static NetworkLibraryLoadError get_load_error(); + static void clear_load_error(); + static void set_load_error(const std::string& message, const std::string& technical_details, const std::string& attempted_path); NetworkAgent(std::string log_dir); ~NetworkAgent(); @@ -232,6 +241,8 @@ private: bool enable_track = false; void* network_agent { nullptr }; + static NetworkLibraryLoadError s_load_error; + static func_check_debug_consistent check_debug_consistent_ptr; static func_get_version get_version_ptr; static func_create_agent create_agent_ptr; diff --git a/src/slic3r/Utils/PresetUpdater.cpp b/src/slic3r/Utils/PresetUpdater.cpp index 0af6cd459f..26df92f7d4 100644 --- a/src/slic3r/Utils/PresetUpdater.cpp +++ b/src/slic3r/Utils/PresetUpdater.cpp @@ -849,7 +849,7 @@ void PresetUpdater::priv::sync_plugins(std::string http_url, std::string plugin_ BOOST_LOG_TRIVIAL(info) << "non need to sync plugins for there is no plugins currently."; return; } - std::string curr_version = NetworkAgent::use_legacy_network ? BAMBU_NETWORK_AGENT_VERSION_LEGACY : BAMBU_NETWORK_AGENT_VERSION; + std::string curr_version = NetworkAgent::use_legacy_network ? BAMBU_NETWORK_AGENT_VERSION_LEGACY : BBL::get_latest_network_version(); std::string using_version = curr_version.substr(0, 9) + "00"; std::string cached_version; diff --git a/src/slic3r/Utils/bambu_networking.hpp b/src/slic3r/Utils/bambu_networking.hpp index 710e171c7e..f18d29e0b2 100644 --- a/src/slic3r/Utils/bambu_networking.hpp +++ b/src/slic3r/Utils/bambu_networking.hpp @@ -98,7 +98,6 @@ namespace BBL { #define BAMBU_NETWORK_AGENT_NAME "bambu_network_agent" #define BAMBU_NETWORK_AGENT_VERSION_LEGACY "01.10.01.01" -#define BAMBU_NETWORK_AGENT_VERSION "02.03.00.62" //iot preset type strings #define IOT_PRINTER_TYPE_STRING "printer" @@ -306,6 +305,36 @@ struct CertificateInformation { std::string serial_number; }; +struct NetworkLibraryVersion { + const char* version; + const char* display_name; + const char* url_override; + bool is_latest; + const char* warning; +}; + +static const NetworkLibraryVersion AVAILABLE_NETWORK_VERSIONS[] = { + {"02.03.00.62", "02.03.00.62", nullptr, true, nullptr}, + {"02.01.01.52", "02.01.01.52", nullptr, false, nullptr}, + {"02.00.02.50", "02.00.02.50", nullptr, false, "This version may crash on startup due to Bambu Lab's signature verification."}, +}; + +static const size_t AVAILABLE_NETWORK_VERSIONS_COUNT = sizeof(AVAILABLE_NETWORK_VERSIONS) / sizeof(AVAILABLE_NETWORK_VERSIONS[0]); + +inline const char* get_latest_network_version() { + for (size_t i = 0; i < AVAILABLE_NETWORK_VERSIONS_COUNT; ++i) { + if (AVAILABLE_NETWORK_VERSIONS[i].is_latest) + return AVAILABLE_NETWORK_VERSIONS[i].version; + } + return AVAILABLE_NETWORK_VERSIONS[0].version; +} + +struct NetworkLibraryLoadError { + bool has_error = false; + std::string message; + std::string technical_details; + std::string attempted_path; +}; enum class MessageFlag : int { diff --git a/tests/libslic3r/CMakeLists.txt b/tests/libslic3r/CMakeLists.txt index 265feb660f..51e8e1ea6b 100644 --- a/tests/libslic3r/CMakeLists.txt +++ b/tests/libslic3r/CMakeLists.txt @@ -4,6 +4,7 @@ add_executable(${_TEST_NAME}_tests ${_TEST_NAME}_tests.cpp test_3mf.cpp test_aabbindirect.cpp + test_appconfig.cpp test_clipper_offset.cpp test_clipper_utils.cpp test_config.cpp diff --git a/tests/libslic3r/test_appconfig.cpp b/tests/libslic3r/test_appconfig.cpp new file mode 100644 index 0000000000..59f9d11808 --- /dev/null +++ b/tests/libslic3r/test_appconfig.cpp @@ -0,0 +1,45 @@ +#include + +#include "libslic3r/AppConfig.hpp" + +using namespace Slic3r; + +TEST_CASE("AppConfig network version helpers", "[AppConfig]") { + AppConfig config; + + SECTION("skipped versions starts empty") { + auto skipped = config.get_skipped_network_versions(); + REQUIRE(skipped.empty()); + } + + SECTION("add and check skipped version") { + config.add_skipped_network_version("02.01.01.52"); + REQUIRE(config.is_network_version_skipped("02.01.01.52")); + REQUIRE_FALSE(config.is_network_version_skipped("02.03.00.62")); + } + + SECTION("multiple skipped versions") { + config.add_skipped_network_version("02.01.01.52"); + config.add_skipped_network_version("02.00.02.50"); + + auto skipped = config.get_skipped_network_versions(); + REQUIRE(skipped.size() == 2); + REQUIRE(config.is_network_version_skipped("02.01.01.52")); + REQUIRE(config.is_network_version_skipped("02.00.02.50")); + } + + SECTION("clear skipped versions") { + config.add_skipped_network_version("02.01.01.52"); + config.clear_skipped_network_versions(); + REQUIRE_FALSE(config.is_network_version_skipped("02.01.01.52")); + } + + SECTION("duplicate add is idempotent") { + config.add_skipped_network_version("02.01.01.52"); + config.add_skipped_network_version("02.01.01.52"); + + auto skipped = config.get_skipped_network_versions(); + REQUIRE(skipped.size() == 1); + REQUIRE(config.is_network_version_skipped("02.01.01.52")); + } +} From 0df673b0d3fd9b2d4adb9097fc817f9047dd8668 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maciej=20Wilczy=C5=84ski?= Date: Sun, 14 Dec 2025 15:27:56 +0100 Subject: [PATCH 2/9] Implement support for loading custom plugin binaries --- src/slic3r/GUI/GUI_App.cpp | 3 +- src/slic3r/GUI/Monitor.cpp | 20 +++- src/slic3r/GUI/NetworkPluginDialog.cpp | 26 ++++-- src/slic3r/GUI/NetworkPluginDialog.hpp | 2 + src/slic3r/GUI/Preferences.cpp | 28 ++++-- src/slic3r/GUI/Preferences.hpp | 2 + src/slic3r/Utils/NetworkAgent.cpp | 108 ++++++++++++++++++++++ src/slic3r/Utils/NetworkAgent.hpp | 1 + src/slic3r/Utils/bambu_networking.hpp | 43 +++++++++ tests/libslic3r/CMakeLists.txt | 2 + tests/libslic3r/test_bambu_networking.cpp | 98 ++++++++++++++++++++ 11 files changed, 310 insertions(+), 23 deletions(-) create mode 100644 tests/libslic3r/test_bambu_networking.cpp 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); + } +} From f232c9cf08cfd926237f81373cb0b6bcfdf01bbd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maciej=20Wilczy=C5=84ski?= Date: Sun, 14 Dec 2025 16:49:17 +0100 Subject: [PATCH 3/9] Port all plugin error messages from Studio --- src/slic3r/GUI/GUI_App.cpp | 76 +++++++++++++++++++++++++++++++++++--- src/slic3r/GUI/GUI_App.hpp | 1 + 2 files changed, 71 insertions(+), 6 deletions(-) diff --git a/src/slic3r/GUI/GUI_App.cpp b/src/slic3r/GUI/GUI_App.cpp index ba6a211fb7..9edf8553dc 100644 --- a/src/slic3r/GUI/GUI_App.cpp +++ b/src/slic3r/GUI/GUI_App.cpp @@ -5184,16 +5184,82 @@ void GUI_App::check_new_version_sf(bool show_tips, int by_user) bool GUI_App::process_network_msg(std::string dev_id, std::string msg) { if (dev_id.empty()) { - BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << msg; - - if (msg == "unsigned_studio") { + if (msg == "wait_info") { + BOOST_LOG_TRIVIAL(info) << "process_network_msg, wait_info"; + Slic3r::DeviceManager* dev = Slic3r::GUI::wxGetApp().getDeviceManager(); + if (!dev) + return true; + MachineObject* obj = dev->get_selected_machine(); + if (obj && m_agent) + m_agent->install_device_cert(obj->get_dev_id(), obj->is_lan_mode_printer()); + if (!m_show_error_msgdlg) { + MessageDialog msg_dlg(nullptr, _L("Retrieving printer information, please try again later."), "", wxAPPLY | wxOK); + m_show_error_msgdlg = true; + msg_dlg.ShowModal(); + m_show_error_msgdlg = false; + } + return true; + } + else if (msg == "update_studio") { + BOOST_LOG_TRIVIAL(info) << "process_network_msg, update_studio"; + if (!m_show_error_msgdlg) { + MessageDialog msg_dlg(nullptr, _L("Please try updating OrcaSlicer and then try again."), "", wxAPPLY | wxOK); + m_show_error_msgdlg = true; + msg_dlg.ShowModal(); + m_show_error_msgdlg = false; + } + return true; + } + else if (msg == "update_fixed_studio") { + BOOST_LOG_TRIVIAL(info) << "process_network_msg, update_fixed_studio"; + if (!m_show_error_msgdlg) { + MessageDialog msg_dlg(nullptr, _L("Please try updating OrcaSlicer and then try again."), "", wxAPPLY | wxOK); + m_show_error_msgdlg = true; + msg_dlg.ShowModal(); + m_show_error_msgdlg = false; + } + return true; + } + else if (msg == "cert_expired") { + BOOST_LOG_TRIVIAL(info) << "process_network_msg, cert_expired"; + if (!m_show_error_msgdlg) { + MessageDialog msg_dlg(nullptr, _L("The certificate has expired. Please check the time settings or update OrcaSlicer and try again."), "", wxAPPLY | wxOK); + m_show_error_msgdlg = true; + msg_dlg.ShowModal(); + m_show_error_msgdlg = false; + } + return true; + } + else if (msg == "cert_revoked") { + BOOST_LOG_TRIVIAL(info) << "process_network_msg, cert_revoked"; + if (!m_show_error_msgdlg) { + MessageDialog msg_dlg(nullptr, _L("The certificate is no longer valid and the printing functions are unavailable."), "", wxAPPLY | wxOK); + m_show_error_msgdlg = true; + msg_dlg.ShowModal(); + m_show_error_msgdlg = false; + } + return true; + } + else if (msg == "update_firmware_studio") { + BOOST_LOG_TRIVIAL(info) << "process_network_msg, firmware internal error"; + if (!m_show_error_msgdlg) { + MessageDialog msg_dlg(nullptr, _L("Internal error. Please try upgrading the firmware and OrcaSlicer version. If the issue persists, contact support."), "", wxAPPLY | wxOK); + m_show_error_msgdlg = true; + msg_dlg.ShowModal(); + m_show_error_msgdlg = false; + } + return true; + } + else if (msg == "unsigned_studio") { BOOST_LOG_TRIVIAL(info) << "process_network_msg, unsigned_studio"; MessageDialog msg_dlg(nullptr, _L("Bambu Lab has implemented a signature verification check in their network plugin that restricts " "third-party software from communicating with your printer.\n\n" "As a result, some printing functions are unavailable in OrcaSlicer."), - _L("Network Plugin Restriction"), wxICON_WARNING | wxOK); + _L("Network Plugin Restriction"), wxAPPLY | wxOK); + m_show_error_msgdlg = true; msg_dlg.ShowModal(); + m_show_error_msgdlg = false; return true; } } @@ -5204,7 +5270,6 @@ bool GUI_App::process_network_msg(std::string dev_id, std::string msg) obj->update_device_cert_state(true); } } - return true; } else if (msg == "device_cert_uninstalled") { @@ -5214,7 +5279,6 @@ bool GUI_App::process_network_msg(std::string dev_id, std::string msg) obj->update_device_cert_state(false); } } - return true; } diff --git a/src/slic3r/GUI/GUI_App.hpp b/src/slic3r/GUI/GUI_App.hpp index 476dfc6f68..9827e64fae 100644 --- a/src/slic3r/GUI/GUI_App.hpp +++ b/src/slic3r/GUI/GUI_App.hpp @@ -312,6 +312,7 @@ private: bool m_adding_script_handler { false }; bool m_side_popup_status{false}; bool m_show_http_errpr_msgdlg{false}; + bool m_show_error_msgdlg{false}; wxString m_info_dialog_content; HttpServer m_http_server; bool m_show_gcode_window{true}; From 8d8ab5911d84560516cb473b4f2372274f136ba8 Mon Sep 17 00:00:00 2001 From: SoftFever Date: Fri, 19 Dec 2025 22:16:34 +0800 Subject: [PATCH 4/9] Update build_orca.yml to disable cache miss failure --- .github/workflows/build_orca.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build_orca.yml b/.github/workflows/build_orca.yml index aef491f006..5a315bed74 100644 --- a/.github/workflows/build_orca.yml +++ b/.github/workflows/build_orca.yml @@ -35,7 +35,7 @@ jobs: with: path: ${{ inputs.cache-path }} key: ${{ inputs.cache-key }} - fail-on-cache-miss: true + fail-on-cache-miss: false - uses: lukka/get-cmake@latest with: From 0e5a816318d019734c5c808b4a5762c1a999dfa0 Mon Sep 17 00:00:00 2001 From: SoftFever Date: Sun, 28 Dec 2025 19:57:08 +0800 Subject: [PATCH 5/9] Migrate legacy networking configuration to new network plugin versioning system. Remove legacy networking setting and update related GUI components to reflect the changes. Implement auto-migration for legacy libraries to ensure compatibility with the new versioning scheme. --- src/libslic3r/AppConfig.cpp | 22 ++++++++-- src/libslic3r/AppConfig.hpp | 1 + src/slic3r/GUI/GUI_App.cpp | 14 +++--- src/slic3r/GUI/Preferences.cpp | 34 ++++++--------- src/slic3r/GUI/Preferences.hpp | 1 - src/slic3r/Utils/NetworkAgent.cpp | 62 ++++++++++++++++++++++----- src/slic3r/Utils/bambu_networking.hpp | 3 +- 7 files changed, 93 insertions(+), 44 deletions(-) diff --git a/src/libslic3r/AppConfig.cpp b/src/libslic3r/AppConfig.cpp index ad0a8e5932..4d91275010 100644 --- a/src/libslic3r/AppConfig.cpp +++ b/src/libslic3r/AppConfig.cpp @@ -135,6 +135,25 @@ void AppConfig::set_defaults() if (!get("use_legacy_opengl").empty()) erase("app", "use_legacy_opengl"); + // Migrate legacy_networking boolean to network_plugin_version string + std::string legacy_networking = get("legacy_networking"); + std::string network_version = get("network_plugin_version"); + + if (!legacy_networking.empty()) { + // Old legacy_networking setting exists - migrate it + bool was_legacy = (legacy_networking == "true" || legacy_networking == "1"); + + if (was_legacy && network_version.empty()) { + // User had legacy mode enabled - set to legacy version number + BOOST_LOG_TRIVIAL(info) << "Migrating legacy_networking=true to network_plugin_version=01.10.01.01"; + set("network_plugin_version", BAMBU_NETWORK_AGENT_VERSION_LEGACY); + } + // Note: If was_legacy=false, we leave the version empty and let the GUI layer set it to the latest version + + // Remove the old setting + erase("app", "legacy_networking"); + } + #ifdef __APPLE__ if (get("use_retina_opengl").empty()) set_bool("use_retina_opengl", true); @@ -278,9 +297,6 @@ void AppConfig::set_defaults() if (get("allow_abnormal_storage").empty()) { set_bool("allow_abnormal_storage", false); } - if (get("legacy_networking").empty()) { - set_bool("legacy_networking", false); - } if(get("check_stable_update_only").empty()) { set_bool("check_stable_update_only", false); diff --git a/src/libslic3r/AppConfig.hpp b/src/libslic3r/AppConfig.hpp index 82d87677ac..b521410ce9 100644 --- a/src/libslic3r/AppConfig.hpp +++ b/src/libslic3r/AppConfig.hpp @@ -28,6 +28,7 @@ using namespace nlohmann; #define SETTING_NETWORK_PLUGIN_SKIPPED_VERSIONS "network_plugin_skipped_versions" #define SETTING_NETWORK_PLUGIN_UPDATE_DISABLED "network_plugin_update_prompts_disabled" #define SETTING_NETWORK_PLUGIN_REMIND_LATER "network_plugin_remind_later" +#define BAMBU_NETWORK_AGENT_VERSION_LEGACY "01.10.01.01" #define SUPPORT_DARK_MODE //#define _MSW_DARK_MODE diff --git a/src/slic3r/GUI/GUI_App.cpp b/src/slic3r/GUI/GUI_App.cpp index 9edf8553dc..c0293d9e17 100644 --- a/src/slic3r/GUI/GUI_App.cpp +++ b/src/slic3r/GUI/GUI_App.cpp @@ -2903,8 +2903,11 @@ bool GUI_App::on_init_inner() std::map extra_headers = get_extra_header(); Slic3r::Http::set_extra_headers(extra_headers); - // Orca: select network plugin version - NetworkAgent::use_legacy_network = app_config->get_bool("legacy_networking"); + // Orca: select network plugin version based on configured version string + std::string configured_version = app_config->get_network_plugin_version(); + NetworkAgent::use_legacy_network = (configured_version == BAMBU_NETWORK_AGENT_VERSION_LEGACY); + BOOST_LOG_TRIVIAL(info) << "Network plugin mode: " + << (NetworkAgent::use_legacy_network ? ("legacy (version: " + std::string(BAMBU_NETWORK_AGENT_VERSION_LEGACY) + ")") : ("modern (version: " + configured_version + ")")); // Force legacy network plugin if debugger attached // See https://github.com/bambulab/BambuStudio/issues/6726 /* if (!NetworkAgent::use_legacy_network) { @@ -3202,13 +3205,6 @@ bool GUI_App::on_init_network(bool try_backup) std::string config_version = app_config->get_network_plugin_version(); - if (should_load_networking_plugin && Slic3r::NetworkAgent::legacy_library_exists() && config_version.empty()) { - BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ": migration: legacy library found with no config version, removing and requesting download"; - Slic3r::NetworkAgent::remove_legacy_library(); - m_networking_need_update = true; - return false; - } - if(!should_load_networking_plugin) { BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << "Don't load plugin as installed_networking is false"; } else { diff --git a/src/slic3r/GUI/Preferences.cpp b/src/slic3r/GUI/Preferences.cpp index abcf4d2af4..7dc2329f21 100644 --- a/src/slic3r/GUI/Preferences.cpp +++ b/src/slic3r/GUI/Preferences.cpp @@ -884,7 +884,6 @@ wxBoxSizer *PreferencesDialog::create_item_checkbox(wxString title, wxString too if (pbool) { GUI::wxGetApp().CallAfter([] { GUI::wxGetApp().ShowDownNetPluginDlg(); }); } - if (m_legacy_networking_ckeckbox != nullptr) { m_legacy_networking_ckeckbox->Enable(pbool); } } #endif // __WXMSW__ @@ -946,25 +945,12 @@ wxBoxSizer *PreferencesDialog::create_item_checkbox(wxString title, wxString too } } - if (param == "legacy_networking") { - bool legacy_mode = checkbox->GetValue(); - if (m_network_version_sizer != nullptr) { - m_network_version_sizer->Show(!legacy_mode); - m_parent->Layout(); - } - } - e.Skip(); }); //// for debug mode if (param == "developer_mode") { m_developer_mode_ckeckbox = checkbox; } if (param == "internal_developer_mode") { m_internal_developer_mode_ckeckbox = checkbox; } - if (param == "legacy_networking") { - m_legacy_networking_ckeckbox = checkbox; - bool pbool = app_config->get_bool("installed_networking"); - checkbox->Enable(pbool); - } return m_sizer_checkbox; } @@ -1424,9 +1410,6 @@ void PreferencesDialog::create_items() auto item_enable_plugin = create_item_checkbox(_L("Enable network plugin"), "", "installed_networking"); g_sizer->Add(item_enable_plugin); - - auto item_legacy_network = create_item_checkbox(_L("Use legacy network plugin"), _L("Disable to use latest network plugin that supports new BambuLab firmwares."), "legacy_networking", _L("(Requires restart)")); - g_sizer->Add(item_legacy_network); m_network_version_sizer = new wxBoxSizer(wxHORIZONTAL); m_network_version_sizer->AddSpacer(FromDIP(DESIGN_LEFT_MARGIN)); @@ -1442,7 +1425,10 @@ void PreferencesDialog::create_items() m_network_version_combo->SetFont(::Label::Body_14); m_network_version_combo->GetDropDown().SetFont(::Label::Body_14); - std::string current_version = app_config->get("network_plugin_version"); + std::string current_version = app_config->get_network_plugin_version(); + if (current_version.empty()) { + current_version = BBL::get_latest_network_version(); + } int current_selection = 0; m_available_versions = BBL::get_all_available_versions(); @@ -1485,16 +1471,26 @@ void PreferencesDialog::create_items() if (new_version != old_version) { BOOST_LOG_TRIVIAL(info) << "Network plugin version changed from " << old_version << " to " << new_version; + // Update the use_legacy_network flag immediately + bool is_legacy = (new_version == BAMBU_NETWORK_AGENT_VERSION_LEGACY); + bool was_legacy = (old_version == BAMBU_NETWORK_AGENT_VERSION_LEGACY); + if (is_legacy != was_legacy) { + Slic3r::NetworkAgent::use_legacy_network = is_legacy; + BOOST_LOG_TRIVIAL(info) << "Updated use_legacy_network flag to " << is_legacy; + } + 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(); + Slic3r::NetworkAgent::use_legacy_network = was_legacy; e.Skip(); return; } } + // Check if the selected version already exists on disk if (Slic3r::NetworkAgent::versioned_library_exists(new_version)) { BOOST_LOG_TRIVIAL(info) << "Version " << new_version << " already exists on disk, triggering hot reload"; if (wxGetApp().hot_reload_network_plugin()) { @@ -1520,8 +1516,6 @@ void PreferencesDialog::create_items() e.Skip(); }); - bool legacy_mode = app_config->get_bool("legacy_networking"); - m_network_version_sizer->Show(!legacy_mode); g_sizer->Add(m_network_version_sizer); g_sizer->AddSpacer(FromDIP(10)); diff --git a/src/slic3r/GUI/Preferences.hpp b/src/slic3r/GUI/Preferences.hpp index 53182fd475..5f98174bf2 100644 --- a/src/slic3r/GUI/Preferences.hpp +++ b/src/slic3r/GUI/Preferences.hpp @@ -68,7 +68,6 @@ public: ::CheckBox * m_internal_developer_mode_ckeckbox = {nullptr}; ::CheckBox * m_dark_mode_ckeckbox = {nullptr}; ::TextInput *m_backup_interval_textinput = {nullptr}; - ::CheckBox * m_legacy_networking_ckeckbox = {nullptr}; ::ComboBox * m_network_version_combo = {nullptr}; wxBoxSizer * m_network_version_sizer = {nullptr}; std::vector m_available_versions; diff --git a/src/slic3r/Utils/NetworkAgent.cpp b/src/slic3r/Utils/NetworkAgent.cpp index cbbc645682..8220105f10 100644 --- a/src/slic3r/Utils/NetworkAgent.cpp +++ b/src/slic3r/Utils/NetworkAgent.cpp @@ -241,7 +241,17 @@ bool NetworkAgent::versioned_library_exists(const std::string& version) { if (version.empty()) return false; std::string path = get_versioned_library_path(version); - return boost::filesystem::exists(path); + + // Check if versioned library exists + if (boost::filesystem::exists(path)) return true; + + // For legacy version, also check if unversioned legacy library exists + // (it will be auto-migrated to versioned format when loaded) + if (version == BAMBU_NETWORK_AGENT_VERSION_LEGACY) { + return legacy_library_exists(); + } + + return false; } bool NetworkAgent::legacy_library_exists() @@ -353,9 +363,50 @@ int NetworkAgent::initialize_network_module(bool using_backup, const std::string return -1; } + // Auto-migration: If loading legacy version and versioned library doesn't exist, + // but unversioned legacy library does exist, rename it to versioned format + if (version == BAMBU_NETWORK_AGENT_VERSION_LEGACY) { + boost::filesystem::path versioned_path; + boost::filesystem::path legacy_path; +#if defined(_MSC_VER) || defined(_WIN32) + versioned_path = plugin_folder / (std::string(BAMBU_NETWORK_LIBRARY) + "_" + version + ".dll"); + legacy_path = plugin_folder / (std::string(BAMBU_NETWORK_LIBRARY) + ".dll"); +#elif defined(__WXMAC__) + versioned_path = plugin_folder / (std::string("lib") + std::string(BAMBU_NETWORK_LIBRARY) + "_" + version + ".dylib"); + legacy_path = plugin_folder / (std::string("lib") + std::string(BAMBU_NETWORK_LIBRARY) + ".dylib"); +#else + versioned_path = plugin_folder / (std::string("lib") + std::string(BAMBU_NETWORK_LIBRARY) + "_" + version + ".so"); + legacy_path = plugin_folder / (std::string("lib") + std::string(BAMBU_NETWORK_LIBRARY) + ".so"); +#endif + if (!boost::filesystem::exists(versioned_path) && boost::filesystem::exists(legacy_path)) { + BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ": auto-migrating unversioned legacy library to versioned format"; + + try { + // Rename unversioned to versioned in the same folder (main or backup). + boost::filesystem::rename(legacy_path, versioned_path); + BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ": successfully renamed " << legacy_path.string() << " to " + << versioned_path.string(); + } catch (const std::exception& e) { + BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << ": failed to rename legacy library: " << e.what(); + } + } + } + + // Load versioned library #if defined(_MSC_VER) || defined(_WIN32) library = plugin_folder.string() + "\\" + std::string(BAMBU_NETWORK_LIBRARY) + "_" + version + ".dll"; BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ": loading versioned library at " << library; +#else + #if defined(__WXMAC__) + std::string lib_ext = ".dylib"; + #else + std::string lib_ext = ".so"; + #endif + library = plugin_folder.string() + "/" + std::string("lib") + std::string(BAMBU_NETWORK_LIBRARY) + "_" + version + lib_ext; + BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ": loading versioned library at " << library; +#endif + +#if defined(_MSC_VER) || defined(_WIN32) wchar_t lib_wstr[256]; memset(lib_wstr, 0, sizeof(lib_wstr)); ::MultiByteToWideChar(CP_UTF8, NULL, library.c_str(), strlen(library.c_str())+1, lib_wstr, sizeof(lib_wstr) / sizeof(lib_wstr[0])); @@ -378,15 +429,6 @@ int NetworkAgent::initialize_network_module(bool using_backup, const std::string netwoking_module = LoadLibrary(lib_wstr); } #else - #if defined(__WXMAC__) - std::string lib_ext = ".dylib"; - #else - std::string lib_ext = ".so"; - #endif - - library = plugin_folder.string() + "/" + std::string("lib") + std::string(BAMBU_NETWORK_LIBRARY) + "_" + version + lib_ext; - BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ": loading versioned library at " << library; - netwoking_module = dlopen(library.c_str(), RTLD_LAZY); if (!netwoking_module) { char* dll_error = dlerror(); diff --git a/src/slic3r/Utils/bambu_networking.hpp b/src/slic3r/Utils/bambu_networking.hpp index b9a5b6b225..e9f756e705 100644 --- a/src/slic3r/Utils/bambu_networking.hpp +++ b/src/slic3r/Utils/bambu_networking.hpp @@ -6,6 +6,7 @@ #include #include +#include "libslic3r/AppConfig.hpp" extern std::string g_log_folder; extern std::string g_log_start_time; @@ -98,7 +99,6 @@ namespace BBL { #define BAMBU_NETWORK_LIBRARY "bambu_networking" #define BAMBU_NETWORK_AGENT_NAME "bambu_network_agent" -#define BAMBU_NETWORK_AGENT_VERSION_LEGACY "01.10.01.01" //iot preset type strings #define IOT_PRINTER_TYPE_STRING "printer" @@ -318,6 +318,7 @@ static const NetworkLibraryVersion AVAILABLE_NETWORK_VERSIONS[] = { {"02.03.00.62", "02.03.00.62", nullptr, true, nullptr}, {"02.01.01.52", "02.01.01.52", nullptr, false, nullptr}, {"02.00.02.50", "02.00.02.50", nullptr, false, "This version may crash on startup due to Bambu Lab's signature verification."}, + {BAMBU_NETWORK_AGENT_VERSION_LEGACY, BAMBU_NETWORK_AGENT_VERSION_LEGACY " (legacy)", nullptr, false, "Legacy version - may not support newer firmware features. Only use if you have issues with modern versions."}, }; static const size_t AVAILABLE_NETWORK_VERSIONS_COUNT = sizeof(AVAILABLE_NETWORK_VERSIONS) / sizeof(AVAILABLE_NETWORK_VERSIONS[0]); From 1b782d82bc4c6ab774c20d157391c8636b6f0b2c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maciej=20Wilczy=C5=84ski?= Date: Sun, 4 Jan 2026 15:25:44 +0100 Subject: [PATCH 6/9] Fix fresh install issues --- src/slic3r/GUI/GUI_App.cpp | 12 ++++++++++++ src/slic3r/GUI/StatusPanel.cpp | 2 ++ 2 files changed, 14 insertions(+) diff --git a/src/slic3r/GUI/GUI_App.cpp b/src/slic3r/GUI/GUI_App.cpp index c0293d9e17..8223c913e1 100644 --- a/src/slic3r/GUI/GUI_App.cpp +++ b/src/slic3r/GUI/GUI_App.cpp @@ -1518,6 +1518,12 @@ int GUI_App::install_plugin(std::string name, std::string package_name, InstallP if (name == "plugins") { std::string config_version = app_config->get_network_plugin_version(); + if (config_version.empty()) { + config_version = BBL::get_latest_network_version(); + BOOST_LOG_TRIVIAL(info) << "[install_plugin] config_version was empty, using latest: " << config_version; + app_config->set_network_plugin_version(config_version); + app_config->save(); + } if (!config_version.empty() && boost::filesystem::exists(legacy_lib_path)) { #if defined(_MSC_VER) || defined(_WIN32) auto versioned_lib = plugin_folder / (std::string(BAMBU_NETWORK_LIBRARY) + "_" + config_version + ".dll"); @@ -3211,6 +3217,12 @@ bool GUI_App::on_init_network(bool try_backup) if (config_version.empty()) { BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ": no version configured, need to download"; m_networking_need_update = true; + + if (!m_device_manager) + m_device_manager = new Slic3r::DeviceManager(); + if (!m_user_manager) + m_user_manager = new Slic3r::UserManager(); + return false; } int load_agent_dll = Slic3r::NetworkAgent::initialize_network_module(false, config_version); diff --git a/src/slic3r/GUI/StatusPanel.cpp b/src/slic3r/GUI/StatusPanel.cpp index dc717aca39..8a85e556de 100644 --- a/src/slic3r/GUI/StatusPanel.cpp +++ b/src/slic3r/GUI/StatusPanel.cpp @@ -2503,6 +2503,8 @@ StatusPanel::~StatusPanel() void StatusPanel::init_scaled_buttons() { + if (!m_project_task_panel) return; + m_project_task_panel->init_scaled_buttons(); m_bpButton_z_10->SetMinSize(Z_BUTTON_SIZE); m_bpButton_z_10->SetCornerRadius(0); From 01f5c4caaaf0897389307e9f69346acd24bf71ba Mon Sep 17 00:00:00 2001 From: SoftFever Date: Mon, 5 Jan 2026 23:28:33 +0800 Subject: [PATCH 7/9] Fix an error that AppConfig::save is called from non main thread --- src/libslic3r/AppConfig.cpp | 2 +- src/slic3r/GUI/GUI_App.cpp | 3 +++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/src/libslic3r/AppConfig.cpp b/src/libslic3r/AppConfig.cpp index a5fda9f9cb..4fd9b565b3 100644 --- a/src/libslic3r/AppConfig.cpp +++ b/src/libslic3r/AppConfig.cpp @@ -146,7 +146,7 @@ void AppConfig::set_defaults() if (was_legacy && network_version.empty()) { // User had legacy mode enabled - set to legacy version number BOOST_LOG_TRIVIAL(info) << "Migrating legacy_networking=true to network_plugin_version=01.10.01.01"; - set("network_plugin_version", BAMBU_NETWORK_AGENT_VERSION_LEGACY); + set_network_plugin_version(BAMBU_NETWORK_AGENT_VERSION_LEGACY); } // Note: If was_legacy=false, we leave the version empty and let the GUI layer set it to the latest version diff --git a/src/slic3r/GUI/GUI_App.cpp b/src/slic3r/GUI/GUI_App.cpp index fef82357ae..7e32dab4ed 100644 --- a/src/slic3r/GUI/GUI_App.cpp +++ b/src/slic3r/GUI/GUI_App.cpp @@ -1522,7 +1522,10 @@ int GUI_App::install_plugin(std::string name, std::string package_name, InstallP config_version = BBL::get_latest_network_version(); BOOST_LOG_TRIVIAL(info) << "[install_plugin] config_version was empty, using latest: " << config_version; app_config->set_network_plugin_version(config_version); + GUI::wxGetApp().CallAfter([this] { + if (app_config) app_config->save(); + }); } if (!config_version.empty() && boost::filesystem::exists(legacy_lib_path)) { #if defined(_MSC_VER) || defined(_WIN32) From b908c33eea748b7e8f3fcdba07918d9430d4262e Mon Sep 17 00:00:00 2001 From: SoftFever Date: Mon, 5 Jan 2026 23:29:22 +0800 Subject: [PATCH 8/9] Improve hot reload plugin --- src/slic3r/GUI/GUI_App.cpp | 104 ++++++++++++++++++++++++++++++++----- src/slic3r/GUI/GUI_App.hpp | 2 + 2 files changed, 94 insertions(+), 12 deletions(-) diff --git a/src/slic3r/GUI/GUI_App.cpp b/src/slic3r/GUI/GUI_App.cpp index 7e32dab4ed..fda19112d4 100644 --- a/src/slic3r/GUI/GUI_App.cpp +++ b/src/slic3r/GUI/GUI_App.cpp @@ -1524,7 +1524,7 @@ int GUI_App::install_plugin(std::string name, std::string package_name, InstallP app_config->set_network_plugin_version(config_version); GUI::wxGetApp().CallAfter([this] { if (app_config) - app_config->save(); + app_config->save(); }); } if (!config_version.empty() && boost::filesystem::exists(legacy_lib_path)) { @@ -1643,6 +1643,70 @@ void GUI_App::restart_networking() BOOST_LOG_TRIVIAL(info) << __FUNCTION__<< boost::format(" exit, m_agent=%1%")%m_agent; } +// Network plugin hot reload timeout constants (in milliseconds) +namespace { + constexpr int CALLBACK_DRAIN_TIMEOUT_MS = 200; // Time to drain pending CallAfter callbacks + constexpr int NETWORK_IDLE_TIMEOUT_MS = 500; // Max wait for network operations to complete + constexpr int FINAL_DRAIN_TIMEOUT_MS = 100; // Final event processing before destruction + constexpr int POLL_INTERVAL_MS = 50; // Polling interval for state checks + constexpr int MAX_YIELD_ITERATIONS = 20; // Maximum wxYield calls per drain cycle +} + +// Process pending wx events with bounded iteration count +void GUI_App::drain_pending_events(int timeout_ms) +{ + const auto deadline = std::chrono::steady_clock::now() + + std::chrono::milliseconds(timeout_ms); + int yield_count = 0; + + while (std::chrono::steady_clock::now() < deadline) { + // Process pending events + if (wxTheApp) { + wxTheApp->ProcessPendingEvents(); + } + + // Bounded wxYield to prevent infinite loops + if (yield_count < MAX_YIELD_ITERATIONS) { + wxYield(); + ++yield_count; + } + + std::this_thread::sleep_for(std::chrono::milliseconds(POLL_INTERVAL_MS)); + } +} + +// Wait for network operations to complete with state verification +bool GUI_App::wait_for_network_idle(int timeout_ms) +{ + const auto deadline = std::chrono::steady_clock::now() + + std::chrono::milliseconds(timeout_ms); + + while (std::chrono::steady_clock::now() < deadline) { + if (!m_agent) { + return true; // Agent already gone + } + + // Verify all operations completed + bool server_disconnected = !m_agent->is_server_connected(); + + if (server_disconnected) { + BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ": network is idle"; + return true; + } + + // Process events while waiting + if (wxTheApp) { + wxTheApp->ProcessPendingEvents(); + } + + std::this_thread::sleep_for(std::chrono::milliseconds(POLL_INTERVAL_MS)); + } + + BOOST_LOG_TRIVIAL(warning) << __FUNCTION__ << ": timeout after " << timeout_ms + << "ms, server_connected=" << (m_agent ? m_agent->is_server_connected() : false); + return false; +} + bool GUI_App::hot_reload_network_plugin() { BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ": starting hot reload"; @@ -1671,8 +1735,8 @@ bool GUI_App::hot_reload_network_plugin() } if (m_agent) { - BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ": clearing callbacks and stopping operations"; - + // Phase 1: Clear all callbacks (stops new invocations) + BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ": Phase 1 - clearing callbacks"; m_agent->set_on_ssdp_msg_fn(nullptr); m_agent->set_on_user_login_fn(nullptr); m_agent->set_on_printer_connected_fn(nullptr); @@ -1685,23 +1749,39 @@ bool GUI_App::hot_reload_network_plugin() m_agent->set_on_local_message_fn(nullptr); m_agent->set_queue_on_main_fn(nullptr); - m_agent->start_discovery(false, false); - m_agent->disconnect_printer(); + // Phase 2: Drain pending CallAfter callbacks (bounded) + BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ": Phase 2 - draining callbacks"; + drain_pending_events(CALLBACK_DRAIN_TIMEOUT_MS); - std::this_thread::sleep_for(std::chrono::milliseconds(500)); + // Phase 3: Stop operations and verify return values + BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ": Phase 3 - stopping operations"; + bool discovery_stopped = m_agent->start_discovery(false, false); + int disconnect_result = m_agent->disconnect_printer(); + BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ": discovery_stopped=" << discovery_stopped + << ", disconnect_result=" << disconnect_result; - BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ": destroying network agent"; + // Phase 4: Wait for idle with state verification + BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ": Phase 4 - waiting for idle"; + bool became_idle = wait_for_network_idle(NETWORK_IDLE_TIMEOUT_MS); + if (!became_idle) { + BOOST_LOG_TRIVIAL(warning) << __FUNCTION__ << ": proceeding despite timeout"; + } + + // Phase 5: Final bounded drain before destruction + drain_pending_events(FINAL_DRAIN_TIMEOUT_MS); + + // Phase 6: Destroy agent + BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ": Phase 6 - destroying agent"; delete m_agent; m_agent = nullptr; - - std::this_thread::sleep_for(std::chrono::milliseconds(300)); } + // Phase 7: Unload module if (Slic3r::NetworkAgent::is_network_module_loaded()) { - BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ": unloading old module"; + BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ": Phase 7 - unloading module"; + drain_pending_events(FINAL_DRAIN_TIMEOUT_MS); int unload_result = Slic3r::NetworkAgent::unload_network_module(); - BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ": unload result = " << unload_result; - std::this_thread::sleep_for(std::chrono::milliseconds(200)); + BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ": unload_result=" << unload_result; } BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ": calling restart_networking"; diff --git a/src/slic3r/GUI/GUI_App.hpp b/src/slic3r/GUI/GUI_App.hpp index 7e78003497..b84dc97c54 100644 --- a/src/slic3r/GUI/GUI_App.hpp +++ b/src/slic3r/GUI/GUI_App.hpp @@ -703,6 +703,8 @@ private: void init_networking_callbacks(); void init_app_config(); 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 get_extra_header(); void init_http_extra_header(); From 883c095a53fc66cc6fa269880021c7590a930bb6 Mon Sep 17 00:00:00 2001 From: SoftFever Date: Tue, 6 Jan 2026 00:04:59 +0800 Subject: [PATCH 9/9] Remove the warning message for legacy version to keep it consistent with the original "legacy option." --- src/slic3r/Utils/bambu_networking.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/slic3r/Utils/bambu_networking.hpp b/src/slic3r/Utils/bambu_networking.hpp index e9f756e705..c4eafe6988 100644 --- a/src/slic3r/Utils/bambu_networking.hpp +++ b/src/slic3r/Utils/bambu_networking.hpp @@ -318,7 +318,7 @@ static const NetworkLibraryVersion AVAILABLE_NETWORK_VERSIONS[] = { {"02.03.00.62", "02.03.00.62", nullptr, true, nullptr}, {"02.01.01.52", "02.01.01.52", nullptr, false, nullptr}, {"02.00.02.50", "02.00.02.50", nullptr, false, "This version may crash on startup due to Bambu Lab's signature verification."}, - {BAMBU_NETWORK_AGENT_VERSION_LEGACY, BAMBU_NETWORK_AGENT_VERSION_LEGACY " (legacy)", nullptr, false, "Legacy version - may not support newer firmware features. Only use if you have issues with modern versions."}, + {BAMBU_NETWORK_AGENT_VERSION_LEGACY, BAMBU_NETWORK_AGENT_VERSION_LEGACY " (legacy)", nullptr, false, nullptr}, }; static const size_t AVAILABLE_NETWORK_VERSIONS_COUNT = sizeof(AVAILABLE_NETWORK_VERSIONS) / sizeof(AVAILABLE_NETWORK_VERSIONS[0]);