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")); + } +}