Allow selecting specific network plugin versions (#11645)
Some checks failed
Build all / Build Linux (push) Waiting to run
Build all / Build Non-Linux (push) Waiting to run
Build all / Unit Tests (push) Blocked by required conditions
Build all / Flatpak (push) Waiting to run
Shellcheck / Shellcheck (push) Has been cancelled

# Description

This PR introduces most features and capabilities needed for future network plugin-related changes, as discussed on Discord. In short bullet points: 
- it allows users to select a specific version of the Bambu network library to use,
- improves hot reloading (in most cases, version changes don't require restarting Orca),
- introduces possibility to add _custom_ variants of supported plugin versions by placing them in the plugin directory with a suffix of `-<name>`,
- adds an button in the "Developer" section of Preferences to hot-reload the network plugin from disk,
- ports plugin error messages from Bambu Studio, including the questionable unsigned app one (rephrased), to make it clear to users why their printer commands don't really execute.

Also introduces CMake 4.x compatibility on macOS, because I was too lazy to install CMake 3 on my computer 😶

# Screenshots/Recordings/Graphs
<img width="494" height="225" alt="image" src="https://github.com/user-attachments/assets/366e8c0f-8b12-4c75-a2db-ba08e7544ecd" />
<img width="223" height="276" alt="image" src="https://github.com/user-attachments/assets/dffec726-b6d5-44d3-a2b3-dd56d2590356" />
<img width="521" height="182" alt="image" src="https://github.com/user-attachments/assets/934ba963-f299-4d20-a107-8375f1f99571" />
<img width="644" height="640" alt="image" src="https://github.com/user-attachments/assets/00385657-04f2-4f36-896e-58265df58ebc" />


## Tests

Tested manually, also added some basic tests to make sure config management behaves correctly.
This commit is contained in:
SoftFever 2026-01-06 00:28:27 +08:00 committed by GitHub
commit 03d9dea12c
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
30 changed files with 1910 additions and 99 deletions

View file

@ -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

View file

@ -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"

View file

@ -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)

6
deps/CMakeLists.txt vendored
View file

@ -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

View file

@ -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}"

View file

@ -12,6 +12,7 @@
#include <utility>
#include <vector>
#include <stdexcept>
#include <sstream>
#include <boost/filesystem/path.hpp>
#include <boost/filesystem/operations.hpp>
@ -134,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);
@ -277,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);
@ -1440,6 +1457,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<std::string> AppConfig::get_skipped_network_versions() const
{
std::vector<std::string> 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");

View file

@ -24,6 +24,12 @@ 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 BAMBU_NETWORK_AGENT_VERSION_LEGACY "01.10.01.01"
#define SUPPORT_DARK_MODE
//#define _MSW_DARK_MODE
@ -347,6 +353,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<std::string> 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<typename T>
bool get_3dmouse_device_numeric_value(const std::string &device_name, const char *parameter_name, T &out) const

View file

@ -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
@ -705,7 +707,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)
@ -723,7 +731,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)

View file

@ -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<std::mutex> 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)
{

View file

@ -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();

View file

@ -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,

View file

@ -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"
@ -207,6 +208,16 @@ void DownloadProgressDialog::update_release_note(std::string release_note, std::
std::unique_ptr<UpgradeNetworkJob> DownloadProgressDialog::make_job() { return std::make_unique<UpgradeNetworkJob>(); }
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

View file

@ -42,6 +42,7 @@
#include <wx/menuitem.h>
#include <wx/filedlg.h>
#include <wx/progdlg.h>
#include <wx/busyinfo.h>
#include <wx/dir.h>
#include <wx/wupdlock.h>
#include <wx/filefn.h>
@ -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,47 @@ 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()) {
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)
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 +1588,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 +1643,247 @@ 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";
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) {
// 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);
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);
// Phase 2: Drain pending CallAfter callbacks (bounded)
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ": Phase 2 - draining callbacks";
drain_pending_events(CALLBACK_DRAIN_TIMEOUT_MS);
// 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;
// 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;
}
// Phase 7: Unload module
if (Slic3r::NetworkAgent::is_network_module_loaded()) {
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;
}
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 +1913,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;
@ -2674,8 +2998,11 @@ bool GUI_App::on_init_inner()
std::map<std::string, std::string> 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) {
@ -2863,78 +3190,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 "<<plugin_folder.string();
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ": create directory " << plugin_folder.string();
boost::filesystem::create_directory(plugin_folder);
}
std::string error_message;
if (boost::filesystem::exists(network_library)) {
CopyFileResult cfr = copy_file(network_library, network_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(network_library_dst, perms);
fs::remove(network_library);
BOOST_LOG_TRIVIAL(info) << __FUNCTION__<< ": Copying network library from" << network_library << " to " << network_library_dst<<" successfully.";
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ": Copying network library from " << network_library << " to " << network_library_dst << " successfully.";
app_config->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);
@ -2945,13 +3297,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) {
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;
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);
__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();
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();
}
}
if (check_networking_version()) {
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ": on_init_network, compatibility version";
auto bambu_source = Slic3r::NetworkAgent::get_bambu_source_entry();
@ -2968,7 +3346,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;
}
@ -3047,6 +3425,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;
}
@ -4887,7 +5283,84 @@ 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 == "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"), wxAPPLY | wxOK);
m_show_error_msgdlg = true;
msg_dlg.ShowModal();
m_show_error_msgdlg = false;
return true;
}
}
else if (msg == "device_cert_installed") {
BOOST_LOG_TRIVIAL(info) << "process_network_msg, device_cert_installed";
@ -4896,7 +5369,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") {
@ -4906,7 +5378,6 @@ bool GUI_App::process_network_msg(std::string dev_id, std::string msg)
obj->update_device_cert_state(false);
}
}
return true;
}

View file

@ -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};
@ -689,6 +690,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();
@ -697,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<std::string, std::string> get_extra_header();
void init_http_extra_header();
@ -715,6 +723,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<Semver> m_last_config_version;
bool m_config_corrupted { false };
std::string m_open_method;

View file

@ -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 <wx/app.h>
#include <wx/button.h>
@ -195,6 +197,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 +414,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 +525,25 @@ void MonitorPanel::jump_to_LiveView()
m_status_info_panel->get_media_play_ctrl()->jump_to_play();
}
void MonitorPanel::update_network_version_footer()
{
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
} // Slic3r

View file

@ -157,6 +157,7 @@ public:
void jump_to_HMS();
void jump_to_LiveView();
void update_network_version_footer();
};

View file

@ -0,0 +1,377 @@
#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 <wx/sizer.h>
#include <wx/stattext.h>
#include <wx/collpane.h>
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<wxBoxSizer*>(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, int>(wxColour(0, 137, 123), StateColor::Pressed),
std::pair<wxColour, int>(wxColour(38, 166, 154), StateColor::Hovered),
std::pair<wxColour, int>(wxColour(0, 150, 136), StateColor::Normal));
StateColor btn_bg_white(
std::pair<wxColour, int>(wxColour(206, 206, 206), StateColor::Pressed),
std::pair<wxColour, int>(wxColour(238, 238, 238), StateColor::Hovered),
std::pair<wxColour, int>(*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<wxBoxSizer*>(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, int>(wxColour(0, 137, 123), StateColor::Pressed),
std::pair<wxColour, int>(wxColour(38, 166, 154), StateColor::Hovered),
std::pair<wxColour, int>(wxColour(0, 150, 136), StateColor::Normal));
StateColor btn_bg_white(
std::pair<wxColour, int>(wxColour(206, 206, 206), StateColor::Pressed),
std::pair<wxColour, int>(wxColour(238, 238, 238), StateColor::Hovered),
std::pair<wxColour, int>(*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);
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);
}
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<int>(m_available_versions.size())) {
return "";
}
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<int>(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;
}
}
}
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, int>(wxColour(0, 137, 123), StateColor::Pressed),
std::pair<wxColour, int>(wxColour(38, 166, 154), StateColor::Hovered),
std::pair<wxColour, int>(wxColour(0, 150, 136), StateColor::Normal));
StateColor btn_bg_white(
std::pair<wxColour, int>(wxColour(206, 206, 206), StateColor::Pressed),
std::pair<wxColour, int>(wxColour(238, 238, 238), StateColor::Hovered),
std::pair<wxColour, int>(*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();
}
}
}

View file

@ -0,0 +1,76 @@
#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 "slic3r/Utils/bambu_networking.hpp"
#include <wx/collpane.h>
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;
std::vector<BBL::NetworkLibraryVersionInfo> m_available_versions;
};
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_

View file

@ -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
@ -884,7 +886,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__
@ -912,7 +913,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"
@ -952,11 +953,6 @@ wxBoxSizer *PreferencesDialog::create_item_checkbox(wxString title, wxString too
//// 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;
}
@ -1420,9 +1416,113 @@ 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));
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();
if (current_version.empty()) {
current_version = BBL::get_latest_network_version();
}
int current_selection = 0;
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 == ver.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)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();
}
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;
// 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()) {
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();
});
g_sizer->Add(m_network_version_sizer);
g_sizer->AddSpacer(FromDIP(10));
sizer_page->Add(g_sizer, 0, wxEXPAND);
@ -1493,6 +1593,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);
@ -1693,7 +1805,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));

View file

@ -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 {
@ -67,7 +68,9 @@ 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<BBL::NetworkLibraryVersionInfo> m_available_versions;
wxString m_developer_mode_def;
wxString m_internal_developer_mode_def;

View file

@ -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);

View file

@ -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

View file

@ -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()) {

View file

@ -1,5 +1,7 @@
#include <stdio.h>
#include <stdlib.h>
#include <set>
#include <algorithm>
#if defined(_MSC_VER) || defined(_WIN32)
#include <Windows.h>
#else
@ -27,6 +29,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 +222,128 @@ 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);
// 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()
{
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();
}
}
}
std::vector<std::string> NetworkAgent::scan_plugin_versions()
{
std::vector<std::string> 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();
std::string library;
std::string data_dir_str = data_dir();
boost::filesystem::path data_dir_path(data_dir_str);
@ -232,25 +353,74 @@ 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;
}
// 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)
library = plugin_folder.string() + "\\" + std::string(BAMBU_NETWORK_LIBRARY) + ".dll";
wchar_t lib_wstr[128];
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]));
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;
@ -259,29 +429,28 @@ int NetworkAgent::initialize_network_module(bool using_backup)
netwoking_module = LoadLibrary(lib_wstr);
}
#else
#if defined(__WXMAC__)
library = plugin_folder.string() + "/" + std::string("lib") + std::string(BAMBU_NETWORK_LIBRARY) + ".dylib";
#else
library = plugin_folder.string() + "/" + std::string("lib") + std::string(BAMBU_NETWORK_LIBRARY) + ".so";
#endif
printf("loading network module at %s\n", library.c_str());
netwoking_module = dlopen( library.c_str(), RTLD_LAZY);
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 +560,12 @@ int NetworkAgent::initialize_network_module(bool using_backup)
get_mw_user_preference_ptr = reinterpret_cast<func_get_mw_user_preference>(get_network_function("bambu_network_get_mw_user_preference"));
get_mw_user_4ulist_ptr = reinterpret_cast<func_get_mw_user_4ulist>(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 +690,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 +790,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;
@ -1662,3 +1860,63 @@ int NetworkAgent::get_model_mall_rating_result(int job_id, std::string &rating_r
}
} //namespace
std::vector<BBL::NetworkLibraryVersionInfo> BBL::get_all_available_versions()
{
std::vector<NetworkLibraryVersionInfo> result;
std::set<std::string> known_base_versions;
std::set<std::string> 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<std::string> discovered = Slic3r::NetworkAgent::scan_plugin_versions();
std::vector<std::pair<std::string, std::string>> 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;
}

View file

@ -116,8 +116,14 @@ 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 std::vector<std::string> 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();
#if defined(_MSC_VER) || defined(_WIN32)
static HMODULE get_bambu_source_entry();
#else
@ -126,6 +132,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 +242,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;

View file

@ -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;

View file

@ -4,7 +4,9 @@
#include <string>
#include <functional>
#include <map>
#include <vector>
#include "libslic3r/AppConfig.hpp"
extern std::string g_log_folder;
extern std::string g_log_start_time;
@ -97,8 +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"
#define BAMBU_NETWORK_AGENT_VERSION "02.03.00.62"
//iot preset type strings
#define IOT_PRINTER_TYPE_STRING "printer"
@ -306,6 +306,79 @@ 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."},
{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]);
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 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<NetworkLibraryVersionInfo> get_all_available_versions();
struct NetworkLibraryLoadError {
bool has_error = false;
std::string message;
std::string technical_details;
std::string attempted_path;
};
enum class MessageFlag : int
{

View file

@ -4,6 +4,8 @@ add_executable(${_TEST_NAME}_tests
${_TEST_NAME}_tests.cpp
test_3mf.cpp
test_aabbindirect.cpp
test_appconfig.cpp
test_bambu_networking.cpp
test_clipper_offset.cpp
test_clipper_utils.cpp
test_config.cpp
@ -29,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)

View file

@ -0,0 +1,45 @@
#include <catch2/catch_all.hpp>
#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"));
}
}

View file

@ -0,0 +1,98 @@
#include <catch2/catch_all.hpp>
#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);
}
}