Merge branch 'main' into feature/filament_id

Brings in 560 commits (merge base 2026-07-04 .. origin/main af9fd10d7a).
19 conflicts resolved; the other 138 touched files auto-merged.

Conflict resolutions
- Snapmaker/Polymaker (7): main normalised JSON key order in #15039 while this
  branch inserted filament_id/filament_vendor. Took main's key order and kept
  this branch's values, inserting a single filament_id key rather than letting
  git's line merge leave duplicate "from"/"instantiation" keys.
- re3D rPETG @0.8/@1.75 nozzle (2): main renamed these from "re3D Greengate
  rPETG @*" and re-vendored GreenGate3D -> re3D (#15169). Git's rename-aware
  merge produced a file with two filament_vendor keys; took main's version
  wholesale instead. The id is reconciled by the tooling, not by hand.
- re3D rPP (1): kept main's inherits change (fdm_filament_pet -> fdm_filament_pp,
  local filament_type override dropped) and its widened compatible_printers.
  The flattened type is still PP, so the product triple and id OFfHGM1D are
  unchanged.
- re3D Greengate rPETG.json, Afinia {ABS+,ABS,PLA,TPU,Value ABS,Value PLA}.json
  (7 modify/delete): accepted main's deletions. The Afinia ids are not orphaned
  - the surviving @HS presets resolve the same triple and already carry the
  same ids, so nothing is retired.
- .github/workflows/check_profiles.yml: kept both main's new "validate slice"
  step and this branch's tree-wide filament-subtype check.
- tests/libslic3r/CMakeLists.txt: kept both test_filament_id_succession.cpp and
  main's test_preset_diff.cpp.

Verified
- No conflict markers; all 12795 profile JSONs parse with no duplicate keys.
- All branch artifacts (tooling, snapshot, ledger, doc, tests) intact and
  unmodified by the merge.
- The succession-ledger C++ call sites survived main's refactors; audited
  Preset.{cpp,hpp}, PresetBundle.cpp, DeviceManager.cpp, PresetComboBoxes.cpp,
  CaliHistoryDialog.cpp, MoonrakerPrinterAgent.cpp against base/ours/theirs.
- scripts/tests: 115/116 pass.

Known follow-up: assign_filament_ids.py --check reports 243 errors, all from
filament data main added in the last month (BBL/addnorth, Qidi X5/Plus 5,
Snapmaker U1, re3D). The single failing unit test is the live-tree conformance
test asserting that count is zero. Reconciliation lands separately.
This commit is contained in:
SoftFever
2026-08-08 02:05:37 +08:00
2224 changed files with 2621810 additions and 1287330 deletions
+192 -57
View File
@@ -17,6 +17,20 @@ namespace Slic3r {
#define BAMBU_SOURCE_LIBRARY "BambuSource"
namespace {
// Named in the load log: the bound generation is what ties a crash report to an ABI choice.
// The label is the whitelist row's series, so it can never drift from the dispatch table.
const char* network_abi_name(NetworkAbi abi)
{
for (size_t i = 0; i < AVAILABLE_NETWORK_VERSIONS_COUNT; ++i)
if (AVAILABLE_NETWORK_VERSIONS[i].abi == abi)
return AVAILABLE_NETWORK_VERSIONS[i].version;
return "unsupported";
}
} // namespace
// ============================================================================
// Singleton Implementation
// ============================================================================
@@ -46,8 +60,7 @@ BBLNetworkPlugin::BBLNetworkPlugin() = default;
BBLNetworkPlugin::~BBLNetworkPlugin()
{
destroy_agent();
unload();
unload(); // unload() destroys the agent first (see the note there)
}
// ============================================================================
@@ -101,17 +114,18 @@ int BBLNetworkPlugin::initialize(bool using_backup, const std::string& version)
}
}
// Load versioned library
// Load versioned library. In the normal plugins folder a bare series (02.08.01) resolves to
// whatever same-series build is actually on disk (see resolve_library_path); the backup
// folder keeps the exact versioned name.
#if defined(_MSC_VER) || defined(_WIN32)
library = plugin_folder.string() + "\\" + std::string(BAMBU_NETWORK_LIBRARY) + "_" + version + ".dll";
std::string versioned_name = std::string(BAMBU_NETWORK_LIBRARY) + "_" + version + ".dll";
#elif defined(__WXMAC__)
std::string versioned_name = std::string("lib") + std::string(BAMBU_NETWORK_LIBRARY) + "_" + version + ".dylib";
#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;
std::string versioned_name = std::string("lib") + std::string(BAMBU_NETWORK_LIBRARY) + "_" + version + ".so";
#endif
library = using_backup ? (plugin_folder / versioned_name).string()
: resolve_library_path(version);
#if defined(_MSC_VER) || defined(_WIN32)
wchar_t lib_wstr[256];
@@ -162,19 +176,20 @@ int BBLNetworkPlugin::initialize(bool using_backup, const std::string& version)
// Load all function pointers
load_all_function_pointers();
// Sync legacy network flag from loaded plugin
m_use_legacy_network = is_legacy_version(version);
// Key the generation on the library that actually loaded, not the version asked for:
// resolve_library_path() serves any same-series build. m_get_version is read directly, not
// via get_version(), which would substitute the "00.00.00.00" sentinel and pick no generation.
const std::string loaded_version = m_get_version ? m_get_version() : std::string();
m_network_abi = network_plugin_abi(loaded_version.empty() ? version : loaded_version);
std::string loaded_version;
if (m_get_version) {
loaded_version = m_get_version();
if (!loaded_version.empty()) {
m_use_legacy_network = is_legacy_version(loaded_version);
}
// A library reporting a series this build has no ABI for stays loaded but uncallable -
// check_networking_version() then reports it as incompatible and offers the update flow.
if (m_network_abi == NetworkAbi::Unsupported) {
BOOST_LOG_TRIVIAL(warning) << "BBLNetworkPlugin::initialize: no ABI for version "
<< (loaded_version.empty() ? version : loaded_version) << ", plug-in calls are disabled";
}
BOOST_LOG_TRIVIAL(info) << "BBLNetworkPlugin::initialize: legacy_mode="
<< (m_use_legacy_network ? "true" : "false")
BOOST_LOG_TRIVIAL(info) << "BBLNetworkPlugin::initialize: abi=" << network_abi_name(m_network_abi)
<< ", library=" << library
<< ", version=" << (loaded_version.empty() ? "unknown" : loaded_version)
<< ", send_message=" << (m_send_message ? "loaded" : "null")
@@ -187,6 +202,12 @@ int BBLNetworkPlugin::initialize(bool using_backup, const std::string& version)
int BBLNetworkPlugin::unload()
{
// Orca: destroy the plugin agent while its creating DLL is still loaded, so the void* handle
// never dangles into freed memory. A stale m_agent surviving the unload makes create_agent()
// short-circuit on has_agent() after a hot reload, and the next call into the freshly loaded
// DLL dereferences the old-DLL handle -> access violation.
destroy_agent();
UnloadFTModule();
#if defined(_MSC_VER) || defined(_WIN32)
@@ -211,7 +232,9 @@ int BBLNetworkPlugin::unload()
clear_all_function_pointers();
m_use_legacy_network = false;
// Safe to reset only because every pointer was nulled just above and every dispatcher is
// guarded on a non-null pointer, so no stale generation is reachable.
m_network_abi = NetworkAbi::Unsupported;
return 0;
}
@@ -250,6 +273,7 @@ std::string BBLNetworkPlugin::get_version() const
void* BBLNetworkPlugin::create_agent(const std::string& log_dir)
{
if (m_agent) {
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ": reusing existing agent " << m_agent;
return m_agent;
}
@@ -257,6 +281,7 @@ void* BBLNetworkPlugin::create_agent(const std::string& log_dir)
m_agent = m_create_agent(log_dir);
}
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ": created agent " << m_agent;
return m_agent;
}
@@ -375,12 +400,33 @@ std::string BBLNetworkPlugin::get_versioned_library_path(const std::string& vers
#endif
}
std::string BBLNetworkPlugin::resolve_library_path(const std::string& version)
{
std::string exact = get_versioned_library_path(version);
if (boost::filesystem::exists(exact))
return exact;
// A bare series (02.08.01) is physically present only as a specific build (02.08.01.53) when
// the startup file-rename was skipped or failed. Resolve to the newest same-series build
// actually on disk. Custom and legacy names are exact and never resolved.
if (is_series_managed_version(version)) {
const std::string series = network_plugin_series(version);
std::string best;
for (const auto& v : scan_plugin_versions())
if (is_series_managed_version(v) && network_plugin_series(v) == series && (best.empty() || v > best))
best = v;
if (!best.empty())
return get_versioned_library_path(best);
}
return exact; // nonexistent -> caller downloads
}
bool BBLNetworkPlugin::versioned_library_exists(const std::string& version)
{
if (version.empty()) return false;
std::string path = get_versioned_library_path(version);
if (boost::filesystem::exists(path)) return true;
if (boost::filesystem::exists(resolve_library_path(version))) return true;
if (is_legacy_version(version)) {
return legacy_library_exists();
@@ -491,7 +537,7 @@ void BBLNetworkPlugin::set_load_error(const std::string& message,
}
// ============================================================================
// Legacy Helper
// ABI Conversion Helpers
// ============================================================================
PrintParams_Legacy BBLNetworkPlugin::as_legacy(PrintParams& param)
@@ -535,6 +581,57 @@ PrintParams_Legacy BBLNetworkPlugin::as_legacy(PrintParams& param)
return l;
}
// Every PrintParams field except the four the 02.08.01 series added
// (task_timelapse_use_internal, extruder_cali_manual_mode, svc_context, slicer_uid).
PrintParams_0203 BBLNetworkPlugin::as_0203(PrintParams& param)
{
PrintParams_0203 p;
p.dev_id = std::move(param.dev_id);
p.task_name = std::move(param.task_name);
p.project_name = std::move(param.project_name);
p.preset_name = std::move(param.preset_name);
p.filename = std::move(param.filename);
p.config_filename = std::move(param.config_filename);
p.plate_index = param.plate_index;
p.ftp_folder = std::move(param.ftp_folder);
p.ftp_file = std::move(param.ftp_file);
p.ftp_file_md5 = std::move(param.ftp_file_md5);
p.nozzle_mapping = std::move(param.nozzle_mapping);
p.ams_mapping = std::move(param.ams_mapping);
p.ams_mapping2 = std::move(param.ams_mapping2);
p.ams_mapping_info = std::move(param.ams_mapping_info);
p.nozzles_info = std::move(param.nozzles_info);
p.connection_type = std::move(param.connection_type);
p.comments = std::move(param.comments);
p.origin_profile_id = param.origin_profile_id;
p.stl_design_id = param.stl_design_id;
p.origin_model_id = std::move(param.origin_model_id);
p.print_type = std::move(param.print_type);
p.dst_file = std::move(param.dst_file);
p.dev_name = std::move(param.dev_name);
p.dev_ip = std::move(param.dev_ip);
p.use_ssl_for_ftp = param.use_ssl_for_ftp;
p.use_ssl_for_mqtt = param.use_ssl_for_mqtt;
p.username = std::move(param.username);
p.password = std::move(param.password);
p.task_bed_leveling = param.task_bed_leveling;
p.task_flow_cali = param.task_flow_cali;
p.task_vibration_cali = param.task_vibration_cali;
p.task_layer_inspect = param.task_layer_inspect;
p.task_record_timelapse = param.task_record_timelapse;
p.task_use_ams = param.task_use_ams;
p.task_bed_type = std::move(param.task_bed_type);
p.extra_options = std::move(param.extra_options);
p.auto_bed_leveling = param.auto_bed_leveling;
p.auto_flow_cali = param.auto_flow_cali;
p.auto_offset_cali = param.auto_offset_cali;
p.task_ext_change_assist = param.task_ext_change_assist;
p.try_emmc_print = param.try_emmc_print;
return p;
}
// ============================================================================
// Function Pointer Loading
// ============================================================================
@@ -639,6 +736,20 @@ void BBLNetworkPlugin::load_all_function_pointers()
m_get_model_mall_rating_result = reinterpret_cast<func_get_model_mall_rating_result>(get_function("bambu_network_get_model_mall_rating"));
m_get_mw_user_preference = reinterpret_cast<func_get_mw_user_preference>(get_function("bambu_network_get_mw_user_preference"));
m_get_mw_user_4ulist = reinterpret_cast<func_get_mw_user_4ulist>(get_function("bambu_network_get_mw_user_4ulist"));
// Bound late; anything a generation does not export resolves to null so callers no-op.
// See the typedefs for which generation introduced each of these.
m_set_on_user_login_fn = reinterpret_cast<func_set_on_user_login_fn>(get_function("bambu_network_set_on_user_login_fn"));
m_get_studio_info_url = reinterpret_cast<func_get_studio_info_url>(get_function("bambu_network_get_studio_info_url"));
m_report_consent = reinterpret_cast<func_report_consent>(get_function("bambu_network_report_consent"));
m_get_camera_url_for_golive = reinterpret_cast<func_get_camera_url_for_golive>(get_function("bambu_network_get_camera_url_for_golive"));
m_get_hms_snapshot = reinterpret_cast<func_get_hms_snapshot>(get_function("bambu_network_get_hms_snapshot"));
m_get_filament_spools = reinterpret_cast<func_get_filament_spools>(get_function("bambu_network_get_filament_spools"));
m_create_filament_spool = reinterpret_cast<func_create_filament_spool>(get_function("bambu_network_create_filament_spool"));
m_update_filament_spool = reinterpret_cast<func_update_filament_spool>(get_function("bambu_network_update_filament_spool"));
m_delete_filament_spools = reinterpret_cast<func_delete_filament_spools>(get_function("bambu_network_delete_filament_spools"));
m_get_filament_config = reinterpret_cast<func_get_filament_config>(get_function("bambu_network_get_filament_config"));
m_sync_ams_filaments = reinterpret_cast<func_sync_ams_filaments>(get_function("bambu_network_sync_ams_filaments"));
}
void BBLNetworkPlugin::clear_all_function_pointers()
@@ -741,65 +852,89 @@ void BBLNetworkPlugin::clear_all_function_pointers()
m_get_model_mall_rating_result = nullptr;
m_get_mw_user_preference = nullptr;
m_get_mw_user_4ulist = nullptr;
m_set_on_user_login_fn = nullptr;
m_get_studio_info_url = nullptr;
m_report_consent = nullptr;
m_get_camera_url_for_golive = nullptr;
m_get_hms_snapshot = nullptr;
m_get_filament_spools = nullptr;
m_create_filament_spool = nullptr;
m_update_filament_spool = nullptr;
m_delete_filament_spools = nullptr;
m_get_filament_config = nullptr;
m_sync_ams_filaments = nullptr;
}
std::vector<NetworkLibraryVersionInfo> get_all_available_versions()
{
// get_version() reports the "00.00.00.00" sentinel when nothing is loaded; resolve
// that here so the list builder only ever sees a real version or an empty string.
const BBLNetworkPlugin& plugin = BBLNetworkPlugin::instance();
return get_all_available_versions(plugin.is_loaded() ? plugin.get_version() : std::string());
}
std::vector<NetworkLibraryVersionInfo> get_all_available_versions(const std::string& loaded_version)
{
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 = BBLNetworkPlugin::scan_plugin_versions();
std::vector<std::pair<std::string, std::string>> suffixed_versions;
// A managed build (pure dotted-numeric AA.BB.CC[.DD]) is represented by its series entry
// above - the OTA-installed 02.08.01.53 and a bare 02.08.01 both collapse into the single
// 02.08.01 row. Only a custom-named build a user dropped in (02.08.01_custom, ..-dev) earns
// its own row, and only when its series is one this build can actually load. The part past
// the series is stored as the "suffix" so the entry sorts and renders nested under it.
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())
if (is_series_managed_version(version))
continue;
if (known_base_versions.count(base) == 0)
if (!is_supported_network_version(version))
continue;
suffixed_versions.emplace_back(base, version);
const std::string series = network_plugin_series(version);
const std::string sfx = version.size() > series.size() ? version.substr(series.size()) : version;
result.push_back(NetworkLibraryVersionInfo::from_discovered(version, series, sfx));
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;
// Newest first. Version components are fixed-width and zero-padded, so a plain
// string compare orders them numerically, and the legacy series sorts last on its
// own. Suffixed dev builds sort directly under the base version they build on.
std::sort(result.begin(), result.end(),
[](const NetworkLibraryVersionInfo& a, const NetworkLibraryVersionInfo& b) {
if (a.base_version != b.base_version) return a.base_version > b.base_version;
return a.suffix < b.suffix;
});
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));
const std::string loaded_series = network_plugin_series(loaded_version);
for (auto& info : result) {
// A managed (series) entry matches when a managed build of the same series is loaded -
// the loaded plug-in reports its full build (02.08.01.53) but the row is the series.
// Custom and legacy entries match their exact reported version.
info.is_loaded = !loaded_version.empty() &&
(info.version == loaded_version ||
(is_series_managed_version(info.version) && is_series_managed_version(loaded_version) &&
network_plugin_series(info.version) == loaded_series));
info.is_latest = false;
}
// "(Latest)" goes on the highest full version in the list, which after the sort is
// simply the first entry without a dev suffix - an OTA-installed build can be newer
// than the newest whitelisted entry. get_latest_network_version() intentionally
// keeps returning the static whitelist default, which drives the download and
// update-check decisions.
auto latest = std::find_if(result.begin(), result.end(),
[](const NetworkLibraryVersionInfo& info) { return info.suffix.empty(); });
if (latest != result.end())
latest->is_latest = true;
return result;
}
+64 -8
View File
@@ -68,7 +68,7 @@ typedef std::string (*func_build_login_info)(void *agent);
typedef int (*func_ping_bind)(void *agent, std::string ping_code);
typedef int (*func_bind_detect)(void *agent, std::string dev_ip, std::string sec_link, detectResult& detect);
typedef int (*func_set_server_callback)(void *agent, OnServerErrFn fn);
typedef int (*func_bind)(void *agent, std::string dev_ip, std::string dev_id, std::string sec_link, std::string timezone, bool improved, OnUpdateStatusFn update_fn);
typedef int (*func_bind)(void *agent, std::string dev_ip, std::string dev_id, std::string dev_model, std::string sec_link, std::string timezone, bool improved, OnUpdateStatusFn update_fn);
typedef int (*func_unbind)(void *agent, std::string dev_id);
typedef std::string (*func_get_bambulab_host)(void *agent);
typedef std::string (*func_get_user_selected_machine)(void *agent);
@@ -119,7 +119,7 @@ typedef int (*func_get_model_mall_rating_result)(void *agent, int job_id, std::s
typedef int (*func_get_mw_user_preference)(void *agent, std::function<void(std::string)> callback);
typedef int (*func_get_mw_user_4ulist)(void *agent, int seed, int limit, std::function<void(std::string)> callback);
// Legacy function pointer types (for older DLL versions)
// Legacy function pointer types (for the 01.10.01 DLL)
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);
typedef int (*func_start_send_gcode_to_sdcard_legacy)(void *agent, PrintParams_Legacy params, OnUpdateStatusFn update_fn, WasCancelledFn cancel_fn, OnWaitFn wait_fn);
@@ -128,6 +128,34 @@ typedef int (*func_start_sdcard_print_legacy)(void* agent, PrintParams_Legacy pa
typedef int (*func_send_message_legacy)(void* agent, std::string dev_id, std::string json_str, int qos);
typedef int (*func_send_message_to_printer_legacy)(void* agent, std::string dev_id, std::string json_str, int qos);
// 02.03.00 function pointer types. Only PrintParams differs from the current ABI; send_message
// and send_message_to_printer already take the flag argument in this series.
typedef int (*func_start_print_0203)(void *agent, PrintParams_0203 params, OnUpdateStatusFn update_fn, WasCancelledFn cancel_fn, OnWaitFn wait_fn);
typedef int (*func_start_local_print_with_record_0203)(void *agent, PrintParams_0203 params, OnUpdateStatusFn update_fn, WasCancelledFn cancel_fn, OnWaitFn wait_fn);
typedef int (*func_start_send_gcode_to_sdcard_0203)(void *agent, PrintParams_0203 params, OnUpdateStatusFn update_fn, WasCancelledFn cancel_fn, OnWaitFn wait_fn);
typedef int (*func_start_local_print_0203)(void *agent, PrintParams_0203 params, OnUpdateStatusFn update_fn, WasCancelledFn cancel_fn);
typedef int (*func_start_sdcard_print_0203)(void* agent, PrintParams_0203 params, OnUpdateStatusFn update_fn, WasCancelledFn cancel_fn);
// bind() gained dev_model in 02.08.01; the legacy and 02.03.00 series share the older form.
typedef int (*func_bind_pre0208)(void *agent, std::string dev_ip, std::string dev_id, std::string sec_link, std::string timezone, bool improved, OnUpdateStatusFn update_fn);
// Exported by every supported generation, but only bound here.
typedef int (*func_set_on_user_login_fn)(void *agent, OnUserLoginFn fn);
typedef std::string (*func_get_studio_info_url)(void *agent);
// Present since 02.03.00, null on the legacy plugin.
typedef int (*func_report_consent)(void *agent, std::string expand);
// Added by the 02.08.01.52 plugin ABI (null on older plugins).
typedef int (*func_get_camera_url_for_golive)(void *agent, std::string dev_id, std::string sdev_id, std::function<void(std::string)> callback);
typedef int (*func_get_hms_snapshot)(void *agent, std::string& dev_id, std::string& file_name, std::function<void(std::string, int)> callback);
typedef int (*func_get_filament_spools)(void *agent, FilamentQueryParams params, std::string* http_body);
typedef int (*func_create_filament_spool)(void *agent, std::string request_body, std::string* http_body);
typedef int (*func_update_filament_spool)(void *agent, std::string spool_id, std::string request_body, std::string* http_body);
typedef int (*func_delete_filament_spools)(void *agent, FilamentDeleteParams params, std::string* http_body);
typedef int (*func_get_filament_config)(void *agent, std::string* http_body);
typedef int (*func_sync_ams_filaments)(void *agent, AmsSyncParams params, std::string* http_body);
/**
* BBLNetworkPlugin - Singleton managing the Bambu Lab network DLL.
*
@@ -247,6 +275,9 @@ public:
static std::string get_libpath_in_current_directory(const std::string& library_name);
static std::string get_versioned_library_path(const std::string& version);
// Path to load for a stored version: the exact file if present, else - for a bare series
// whose file the startup rename did not produce - the newest same-series build on disk.
static std::string resolve_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();
@@ -263,12 +294,13 @@ public:
const std::string& attempted_path);
// ========================================================================
// Legacy Network Flag
// Plug-in ABI Generation
// ========================================================================
static bool is_legacy_version(const std::string& version) { return version == BAMBU_NETWORK_AGENT_VERSION_LEGACY; }
bool use_legacy_network() const { return m_use_legacy_network; }
void set_use_legacy_network(bool legacy) { m_use_legacy_network = legacy; }
// The generation the loaded library speaks - what every call must dispatch on.
NetworkAbi network_abi() const { return m_network_abi; }
bool use_legacy_network() const { return m_network_abi == NetworkAbi::Legacy; }
// ========================================================================
// Function Pointer Accessors
@@ -372,12 +404,25 @@ public:
func_get_model_mall_rating_result get_get_model_mall_rating_result() const { return m_get_model_mall_rating_result; }
func_get_mw_user_preference get_get_mw_user_preference() const { return m_get_mw_user_preference; }
func_get_mw_user_4ulist get_get_mw_user_4ulist() const { return m_get_mw_user_4ulist; }
func_set_on_user_login_fn get_set_on_user_login_fn() const { return m_set_on_user_login_fn; }
func_get_studio_info_url get_get_studio_info_url() const { return m_get_studio_info_url; }
func_report_consent get_report_consent() const { return m_report_consent; }
func_get_camera_url_for_golive get_get_camera_url_for_golive() const { return m_get_camera_url_for_golive; }
func_get_hms_snapshot get_get_hms_snapshot() const { return m_get_hms_snapshot; }
func_get_filament_spools get_get_filament_spools() const { return m_get_filament_spools; }
func_create_filament_spool get_create_filament_spool() const { return m_create_filament_spool; }
func_update_filament_spool get_update_filament_spool() const { return m_update_filament_spool; }
func_delete_filament_spools get_delete_filament_spools() const { return m_delete_filament_spools; }
func_get_filament_config get_get_filament_config() const { return m_get_filament_config; }
func_sync_ams_filaments get_sync_ams_filaments() const { return m_sync_ams_filaments; }
// ========================================================================
// Legacy Helper
// ABI Conversion Helpers
// ========================================================================
// Both move out of `param`, so convert only inside the branch that will actually run.
static PrintParams_Legacy as_legacy(PrintParams& param);
static PrintParams_0203 as_0203(PrintParams& param);
private:
// Singleton instance pointer (heap-allocated for explicit lifetime control)
@@ -404,8 +449,8 @@ private:
// Load error state
NetworkLibraryLoadError m_load_error;
// Legacy network compatibility flag
bool m_use_legacy_network{false};
// ABI generation of the currently loaded library
NetworkAbi m_network_abi{NetworkAbi::Unsupported};
// Function pointers
func_check_debug_consistent m_check_debug_consistent{nullptr};
@@ -506,6 +551,17 @@ private:
func_get_model_mall_rating_result m_get_model_mall_rating_result{nullptr};
func_get_mw_user_preference m_get_mw_user_preference{nullptr};
func_get_mw_user_4ulist m_get_mw_user_4ulist{nullptr};
func_set_on_user_login_fn m_set_on_user_login_fn{nullptr};
func_get_studio_info_url m_get_studio_info_url{nullptr};
func_report_consent m_report_consent{nullptr};
func_get_camera_url_for_golive m_get_camera_url_for_golive{nullptr};
func_get_hms_snapshot m_get_hms_snapshot{nullptr};
func_get_filament_spools m_get_filament_spools{nullptr};
func_create_filament_spool m_create_filament_spool{nullptr};
func_update_filament_spool m_update_filament_spool{nullptr};
func_delete_filament_spools m_delete_filament_spools{nullptr};
func_get_filament_config m_get_filament_config{nullptr};
func_sync_ams_filaments m_sync_ams_filaments{nullptr};
};
} // namespace Slic3r
+83 -66
View File
@@ -26,11 +26,19 @@ int BBLPrinterAgent::send_message(std::string dev_id, std::string json_str, int
auto agent = plugin.get_agent();
auto func = plugin.get_send_message();
if (func && agent) {
if (plugin.use_legacy_network()) {
// Only the legacy plug-in lacks `flag`; 02.03.00 already takes it, and routing that
// series through the legacy form would silently drop MessageFlag sign/encrypt.
switch (plugin.network_abi()) {
case NetworkAbi::Legacy: {
auto legacy_func = reinterpret_cast<func_send_message_legacy>(func);
return legacy_func(agent, dev_id, json_str, qos);
return legacy_func(agent, std::move(dev_id), std::move(json_str), qos);
}
case NetworkAbi::V0203:
case NetworkAbi::Current:
return func(agent, std::move(dev_id), std::move(json_str), qos, flag);
default:
return -1;
}
return func(agent, dev_id, json_str, qos, flag);
}
return -1;
}
@@ -63,11 +71,17 @@ int BBLPrinterAgent::send_message_to_printer(std::string dev_id, std::string jso
auto agent = plugin.get_agent();
auto func = plugin.get_send_message_to_printer();
if (func && agent) {
if (plugin.use_legacy_network()) {
switch (plugin.network_abi()) {
case NetworkAbi::Legacy: {
auto legacy_func = reinterpret_cast<func_send_message_to_printer_legacy>(func);
return legacy_func(agent, dev_id, json_str, qos);
return legacy_func(agent, std::move(dev_id), std::move(json_str), qos);
}
case NetworkAbi::V0203:
case NetworkAbi::Current:
return func(agent, std::move(dev_id), std::move(json_str), qos, flag);
default:
return -1;
}
return func(agent, dev_id, json_str, qos, flag);
}
return -1;
}
@@ -138,13 +152,25 @@ int BBLPrinterAgent::bind_detect(std::string dev_ip, std::string sec_link, detec
return -1;
}
int BBLPrinterAgent::bind(std::string dev_ip, std::string dev_id, std::string sec_link, std::string timezone, bool improved, OnUpdateStatusFn update_fn)
int BBLPrinterAgent::bind(std::string dev_ip, std::string dev_id, std::string dev_model, std::string sec_link, std::string timezone, bool improved, OnUpdateStatusFn update_fn)
{
auto& plugin = BBLNetworkPlugin::instance();
auto agent = plugin.get_agent();
auto func = plugin.get_bind();
if (func && agent) {
return func(agent, dev_ip, dev_id, sec_link, timezone, improved, update_fn);
// dev_model was added in 02.08.01. Passing it to a plug-in that takes the 7-argument
// form shifts every following argument, so the older generations get the older call.
switch (plugin.network_abi()) {
case NetworkAbi::Legacy:
case NetworkAbi::V0203: {
auto older_func = reinterpret_cast<func_bind_pre0208>(func);
return older_func(agent, dev_ip, dev_id, sec_link, timezone, improved, update_fn);
}
case NetworkAbi::Current:
return func(agent, dev_ip, dev_id, dev_model, sec_link, timezone, improved, update_fn);
default:
return -1;
}
}
return -1;
}
@@ -171,6 +197,19 @@ int BBLPrinterAgent::request_bind_ticket(std::string* ticket)
return -1;
}
int BBLPrinterAgent::get_hms_snapshot(std::string dev_id, std::string file_name, std::function<void(std::string, int)> callback)
{
auto& plugin = BBLNetworkPlugin::instance();
auto agent = plugin.get_agent();
auto func = plugin.get_get_hms_snapshot();
// dev_id/file_name are passed as lvalues to bind the plugin's std::string& params.
// A null func (older plugin without this symbol) falls through to -1 so callers degrade gracefully.
if (func && agent) {
return func(agent, dev_id, file_name, callback);
}
return -1;
}
int BBLPrinterAgent::set_server_callback(OnServerErrFn fn)
{
auto& plugin = BBLNetworkPlugin::instance();
@@ -268,84 +307,62 @@ AgentInfo BBLPrinterAgent::get_agent_info_static()
// Print Job Operations
// ============================================================================
int BBLPrinterAgent::start_print(PrintParams params, OnUpdateStatusFn update_fn, WasCancelledFn cancel_fn, OnWaitFn wait_fn)
namespace {
// Shared dispatcher for the start_* operations, whose params layout differs per generation.
// The per-generation typedefs are template arguments so a swapped pair fails to compile
// (each arm's converted params must match the casted signature). Each arm converts and calls
// in one step: as_legacy()/as_0203() move out of `params` and their prvalue result lands in
// the by-value ABI argument without another copy; the Current arm moves `params` outright.
template <typename LegacyFn, typename Fn0203, typename CurrentFn, typename... CallbackFns>
int dispatch_start(CurrentFn func, PrintParams& params, const CallbackFns&... callbacks)
{
auto& plugin = BBLNetworkPlugin::instance();
auto agent = plugin.get_agent();
auto func = plugin.get_start_print();
if (func && agent) {
if (plugin.use_legacy_network()) {
auto legacy_func = reinterpret_cast<func_start_print_legacy>(func);
auto legacy_params = BBLNetworkPlugin::as_legacy(params);
return legacy_func(agent, legacy_params, update_fn, cancel_fn, wait_fn);
}
return func(agent, params, update_fn, cancel_fn, wait_fn);
if (!func || !agent)
return -1;
switch (plugin.network_abi()) {
case NetworkAbi::Legacy:
return reinterpret_cast<LegacyFn>(func)(agent, BBLNetworkPlugin::as_legacy(params), callbacks...);
case NetworkAbi::V0203:
return reinterpret_cast<Fn0203>(func)(agent, BBLNetworkPlugin::as_0203(params), callbacks...);
case NetworkAbi::Current:
return func(agent, std::move(params), callbacks...);
default:
return -1;
}
return -1;
}
} // namespace
int BBLPrinterAgent::start_print(PrintParams params, OnUpdateStatusFn update_fn, WasCancelledFn cancel_fn, OnWaitFn wait_fn)
{
return dispatch_start<func_start_print_legacy, func_start_print_0203>(
BBLNetworkPlugin::instance().get_start_print(), params, update_fn, cancel_fn, wait_fn);
}
int BBLPrinterAgent::start_local_print_with_record(PrintParams params, OnUpdateStatusFn update_fn, WasCancelledFn cancel_fn, OnWaitFn wait_fn)
{
auto& plugin = BBLNetworkPlugin::instance();
auto agent = plugin.get_agent();
auto func = plugin.get_start_local_print_with_record();
if (func && agent) {
if (plugin.use_legacy_network()) {
auto legacy_func = reinterpret_cast<func_start_local_print_with_record_legacy>(func);
auto legacy_params = BBLNetworkPlugin::as_legacy(params);
return legacy_func(agent, legacy_params, update_fn, cancel_fn, wait_fn);
}
return func(agent, params, update_fn, cancel_fn, wait_fn);
}
return -1;
return dispatch_start<func_start_local_print_with_record_legacy, func_start_local_print_with_record_0203>(
BBLNetworkPlugin::instance().get_start_local_print_with_record(), params, update_fn, cancel_fn, wait_fn);
}
int BBLPrinterAgent::start_send_gcode_to_sdcard(PrintParams params, OnUpdateStatusFn update_fn, WasCancelledFn cancel_fn, OnWaitFn wait_fn)
{
auto& plugin = BBLNetworkPlugin::instance();
auto agent = plugin.get_agent();
auto func = plugin.get_start_send_gcode_to_sdcard();
if (func && agent) {
if (plugin.use_legacy_network()) {
auto legacy_func = reinterpret_cast<func_start_send_gcode_to_sdcard_legacy>(func);
auto legacy_params = BBLNetworkPlugin::as_legacy(params);
return legacy_func(agent, legacy_params, update_fn, cancel_fn, wait_fn);
}
return func(agent, params, update_fn, cancel_fn, wait_fn);
}
return -1;
return dispatch_start<func_start_send_gcode_to_sdcard_legacy, func_start_send_gcode_to_sdcard_0203>(
BBLNetworkPlugin::instance().get_start_send_gcode_to_sdcard(), params, update_fn, cancel_fn, wait_fn);
}
int BBLPrinterAgent::start_local_print(PrintParams params, OnUpdateStatusFn update_fn, WasCancelledFn cancel_fn)
{
auto& plugin = BBLNetworkPlugin::instance();
auto agent = plugin.get_agent();
auto func = plugin.get_start_local_print();
if (func && agent) {
if (plugin.use_legacy_network()) {
auto legacy_func = reinterpret_cast<func_start_local_print_legacy>(func);
auto legacy_params = BBLNetworkPlugin::as_legacy(params);
return legacy_func(agent, legacy_params, update_fn, cancel_fn);
}
return func(agent, params, update_fn, cancel_fn);
}
return -1;
return dispatch_start<func_start_local_print_legacy, func_start_local_print_0203>(
BBLNetworkPlugin::instance().get_start_local_print(), params, update_fn, cancel_fn);
}
int BBLPrinterAgent::start_sdcard_print(PrintParams params, OnUpdateStatusFn update_fn, WasCancelledFn cancel_fn)
{
auto& plugin = BBLNetworkPlugin::instance();
auto agent = plugin.get_agent();
auto func = plugin.get_start_sdcard_print();
if (func && agent) {
if (plugin.use_legacy_network()) {
auto legacy_func = reinterpret_cast<func_start_sdcard_print_legacy>(func);
auto legacy_params = BBLNetworkPlugin::as_legacy(params);
return legacy_func(agent, legacy_params, update_fn, cancel_fn);
}
return func(agent, params, update_fn, cancel_fn);
}
return -1;
return dispatch_start<func_start_sdcard_print_legacy, func_start_sdcard_print_0203>(
BBLNetworkPlugin::instance().get_start_sdcard_print(), params, update_fn, cancel_fn);
}
// ============================================================================
+2 -1
View File
@@ -42,9 +42,10 @@ public:
// Binding
int ping_bind(std::string ping_code) override;
int bind_detect(std::string dev_ip, std::string sec_link, detectResult& detect) override;
int bind(std::string dev_ip, std::string dev_id, std::string sec_link, std::string timezone, bool improved, OnUpdateStatusFn update_fn) override;
int bind(std::string dev_ip, std::string dev_id, std::string dev_model, std::string sec_link, std::string timezone, bool improved, OnUpdateStatusFn update_fn) override;
int unbind(std::string dev_id) override;
int request_bind_ticket(std::string* ticket) override;
int get_hms_snapshot(std::string dev_id, std::string file_name, std::function<void(std::string, int)> callback) override;
int set_server_callback(OnServerErrFn fn) override;
// Machine Selection
+1 -1
View File
@@ -871,7 +871,7 @@ void Bonjour::priv::lookup_perform()
std::vector<LookupSocket*> sockets;
// resolve intefaces - from PR#6646
// resolve interfaces - from PR#6646
std::vector<boost::asio::ip::address> interfaces;
asio::ip::udp::resolver resolver(*io_service);
boost::system::error_code ec;
+59 -18
View File
@@ -7,6 +7,7 @@
#include "../GUI/PartPlate.hpp"
#include "libslic3r/CutUtils.hpp"
#include "libslic3r/ClipperUtils.hpp"
#include "libslic3r/Utils.hpp"
#include "libslic3r/Model.hpp"
#include "slic3r/GUI/Jobs/BoostThreadWorker.hpp"
@@ -27,11 +28,19 @@ const double MIN_PA_K_VALUE = 0.0;
const double MAX_PA_K_VALUE = 2.0;
std::unique_ptr<Worker> CalibUtils::print_worker;
wxString wxstr_temp_dir = fs::path(fs::temp_directory_path() / "calib").wstring();
static const std::string temp_dir = wxstr_temp_dir.utf8_string();
static const std::string temp_gcode_path = temp_dir + "/temp.gcode";
static const std::string path = temp_dir + "/test.3mf";
static const std::string config_3mf_path = temp_dir + "/test_config.3mf";
// Built lazily so temporary_dir() is read on first use, after set_temporary_dir()
// has run at startup (it isolates the temp root per user to avoid cross-user collisions).
static const std::string& calib_temp_dir()
{
static const std::string dir = temporary_dir() + "/calib";
return dir;
}
static std::string calib_temp_file(const std::string& name) { return calib_temp_dir() + "/" + name; }
static const std::string gcode_filename = "temp.gcode";
static const std::string model_filename = "test.3mf";
static const std::string config_filename = "test_config.3mf";
static std::string MachineBedTypeString[7] = {
"auto",
@@ -85,6 +94,10 @@ wxString get_nozzle_volume_type_name(NozzleVolumeType type)
return _L("Standard");
} else if (NozzleVolumeType::nvtHighFlow == type) {
return _L("High Flow");
} else if (NozzleVolumeType::nvtHybrid == type) {
return _L("Hybrid");
} else if (NozzleVolumeType::nvtTPUHighFlow == type) {
return _L("TPU High Flow");
}
return wxString();
}
@@ -1252,6 +1265,19 @@ void CalibUtils::calib_VFA(const CalibInfo &calib_info, wxString &error_message)
DynamicPrintConfig filament_config = calib_info.filament_prest->config;
DynamicPrintConfig printer_config = calib_info.printer_prest->config;
const ConfigOptionFloats* nozzle_diameter_config = printer_config.option<ConfigOptionFloats>("nozzle_diameter");
size_t nozzle_id = static_cast<size_t>(std::max(params.extruder_id, 0));
double nozzle_diameter = vfa_base_nozzle_diameter;
if (nozzle_diameter_config && !nozzle_diameter_config->values.empty()) {
nozzle_id = std::min(nozzle_id, nozzle_diameter_config->values.size() - 1);
nozzle_diameter = nozzle_diameter_config->values[nozzle_id];
}
if (nozzle_diameter <= 0.0)
nozzle_diameter = vfa_base_nozzle_diameter;
// Resolved layer height: use the (possibly auto-adjusted) value if provided, else default to nozzle/2.
double layer_height = params.vfa_layer_height > 0.0 ? params.vfa_layer_height : nozzle_diameter / 2.0;
filament_config.set_key_value("slow_down_layer_time", new ConfigOptionInts{0});
filament_config.set_key_value("filament_max_volumetric_speed", new ConfigOptionFloats{200});
filament_config.set_key_value("curr_bed_type", new ConfigOptionEnum<BedType>(calib_info.bed_type));
@@ -1267,13 +1293,18 @@ void CalibUtils::calib_VFA(const CalibInfo &calib_info, wxString &error_message)
print_config.set_key_value("sparse_infill_density", new ConfigOptionPercent(0));
print_config.set_key_value("overhang_reverse", new ConfigOptionBool(false));
print_config.set_key_value("spiral_mode", new ConfigOptionBool(true));
print_config.set_key_value("initial_layer_print_height", new ConfigOptionFloat(layer_height));
model.objects[0]->config.set_key_value("layer_height", new ConfigOptionFloat(layer_height));
model.objects[0]->config.set_key_value("brim_type", new ConfigOptionEnum<BrimType>(btOuterOnly));
model.objects[0]->config.set_key_value("brim_width", new ConfigOptionFloat(3.0));
model.objects[0]->config.set_key_value("brim_object_gap", new ConfigOptionFloat(0.0));
// cut upper
// cut upper (on the unscaled model, using the base block height); the scaling below keeps the physical
// block height (vfa_layers_per_block * layer_height) in sync with the speed stepping in GCode::process_layer.
// Subtract EPSILON (as the temperature tower does) so the cut lands just below the flat block surface instead
// of exactly on it, which would otherwise add a degenerate extra layer.
auto obj_bb = model.objects[0]->bounding_box_exact();
auto height = 5 * ((params.end - params.start) / params.step + 1);
auto height = vfa_base_block_height * ((params.end - params.start) / params.step + 1) - EPSILON;
if (height < obj_bb.size().z()) {
cut_model(model, height, ModelObjectCutAttribute::KeepLower);
}
@@ -1282,6 +1313,13 @@ void CalibUtils::calib_VFA(const CalibInfo &calib_info, wxString &error_message)
return;
}
// XY scales with the nozzle; Z scales so each base block becomes vfa_layers_per_block layers of layer_height.
const double xy_scale = nozzle_diameter / vfa_base_nozzle_diameter;
const double z_scale = (vfa_layers_per_block * layer_height) / vfa_base_block_height;
if (std::abs(xy_scale - 1.0) > EPSILON || std::abs(z_scale - 1.0) > EPSILON)
model.objects[0]->scale(xy_scale, xy_scale, z_scale);
model.objects[0]->ensure_on_bed();
DynamicPrintConfig full_config;
full_config.apply(FullPrintConfig::defaults());
full_config.apply(print_config);
@@ -1290,7 +1328,10 @@ void CalibUtils::calib_VFA(const CalibInfo &calib_info, wxString &error_message)
init_multi_extruder_params_for_cali(full_config, calib_info);
process_and_store_3mf(&model, full_config, params, error_message);
// Pass the resolved layer height on so the GCode speed stepping matches the geometry.
Calib_Params store_params = params;
store_params.vfa_layer_height = layer_height;
process_and_store_3mf(&model, full_config, store_params, error_message);
if (!error_message.empty())
return;
@@ -1599,7 +1640,7 @@ bool CalibUtils::process_and_store_3mf(Model *model, const DynamicPrintConfig &f
part_plate->update_slice_result_valid_state(true);
gcode_result->reset();
fff_print->export_gcode(temp_gcode_path, gcode_result, nullptr);
fff_print->export_gcode(calib_temp_file(gcode_filename), gcode_result, nullptr);
std::vector<ThumbnailData*> thumbnails;
PlateDataPtrs plate_data_list;
@@ -1618,7 +1659,7 @@ bool CalibUtils::process_and_store_3mf(Model *model, const DynamicPrintConfig &f
}
for (auto plate_data : plate_data_list) {
plate_data->gcode_file = temp_gcode_path;
plate_data->gcode_file = calib_temp_file(gcode_filename);
plate_data->is_sliced_valid = true;
plate_data->printer_model_id = obj_->printer_type;
FilamentInfo& filament_info = plate_data->slice_filaments_info.front();
@@ -1681,7 +1722,7 @@ bool CalibUtils::process_and_store_3mf(Model *model, const DynamicPrintConfig &f
}
StoreParams store_params;
store_params.path = path.c_str();
store_params.path = calib_temp_file(model_filename);
store_params.model = model;
store_params.plate_data_list = plate_data_list;
store_params.config = &new_print_config;
@@ -1695,7 +1736,7 @@ bool CalibUtils::process_and_store_3mf(Model *model, const DynamicPrintConfig &f
bool success = Slic3r::store_bbs_3mf(store_params);
store_params.strategy = SaveStrategy::Silence | SaveStrategy::SplitModel | SaveStrategy::WithSliceInfo | SaveStrategy::SkipAuxiliary;
store_params.path = config_3mf_path.c_str();
store_params.path = calib_temp_file(config_filename);
success = Slic3r::store_bbs_3mf(store_params);
release_PlateData_list(plate_data_list);
@@ -1784,9 +1825,9 @@ void CalibUtils::send_to_print(const CalibInfo &calib_info, wxString &error_mess
PrintPrepareData job_data;
job_data.is_from_plater = false;
job_data.plate_idx = 0;
job_data._3mf_config_path = config_3mf_path;
job_data._3mf_path = path;
job_data._temp_path = temp_dir;
job_data._3mf_config_path = calib_temp_file(config_filename);
job_data._3mf_path = calib_temp_file(model_filename);
job_data._temp_path = calib_temp_dir();
PlateListData plate_data;
plate_data.is_valid = true;
@@ -1889,9 +1930,9 @@ void CalibUtils::send_to_print(const std::vector<CalibInfo> &calib_infos, wxStri
PrintPrepareData job_data;
job_data.is_from_plater = false;
job_data.plate_idx = 0;
job_data._3mf_config_path = config_3mf_path;
job_data._3mf_path = path;
job_data._temp_path = temp_dir;
job_data._3mf_config_path = calib_temp_file(config_filename);
job_data._3mf_path = calib_temp_file(model_filename);
job_data._temp_path = calib_temp_dir();
PlateListData plate_data;
plate_data.is_valid = true;
+1 -1
View File
@@ -1162,7 +1162,7 @@ namespace Slic3r {
switch(ack){
case ElegooLinkStartPrintAck::SDCP_PRINT_CTRL_ACK_BUSY:
{
error_message =_L("The printer is busy, Please check the device page for the file and try to start printing again.");
error_message =_L("The printer is busy, please check the device page for the file and try to start printing again.");
break;
}
case ElegooLinkStartPrintAck::SDCP_PRINT_CTRL_ACK_NOT_FOUND:
+16 -1
View File
@@ -2,6 +2,13 @@
#define __I_PRINTER_AGENT_HPP__
#include "bambu_networking.hpp"
// why: these extend the BAMBU_NETWORK_* return space rather than opening a new one - the value
// flows through the same int domain callers already compare against BAMBU_NETWORK_SUCCESS.
// They live here and not in bambu_networking.hpp because that file is a vendor header replaced
// wholesale by header-sync commits (see c09252ce11), which would silently clobber them.
// -70xx is free: the vendor occupies -1..-25 and -10xx through -60xx.
#define ORCA_NETWORK_ERR_CMD_NOT_SUPPORTED -7010 // no translation exists for this command
#define ORCA_NETWORK_ERR_CAP_NOT_AVAILABLE -7020 // a translation exists; this printer lacks the capability
#include <string>
#include <memory>
@@ -48,6 +55,8 @@ enum class FilamentSyncMode {
*
* Implementations:
* - OrcaPrinterAgent: Stub implementation (printer ops not yet supported)
* - PrinterAgentPluginCapability: Python printer-agent plugin capability that
* implements IPrinterAgent directly and is handed out as the live agent
* - BBLPrinterAgent: Wrapper around Bambu Lab's proprietary DLL
*
* Token Access:
@@ -127,7 +136,7 @@ public:
/**
* Execute the multi-stage printer binding workflow.
*/
virtual int bind(std::string dev_ip, std::string dev_id, std::string sec_link, std::string timezone, bool improved, OnUpdateStatusFn update_fn) = 0;
virtual int bind(std::string dev_ip, std::string dev_id, std::string dev_model, std::string sec_link, std::string timezone, bool improved, OnUpdateStatusFn update_fn) = 0;
/**
* Remove the association between account and printer.
@@ -139,6 +148,12 @@ public:
*/
virtual int request_bind_ticket(std::string* ticket) = 0;
/**
* Fetch the cloud snapshot image captured at a print failure.
* Returns 0 if the request was dispatched; the image body arrives via callback(body, http_status).
*/
virtual int get_hms_snapshot(std::string dev_id, std::string file_name, std::function<void(std::string, int)> callback) = 0;
/**
* Register callback for fatal HTTP errors.
*/
+12 -2
View File
@@ -230,10 +230,11 @@ int MoonrakerPrinterAgent::bind_detect(std::string dev_ip, std::string sec_link,
}
int MoonrakerPrinterAgent::bind(
std::string dev_ip, std::string dev_id, std::string sec_link, std::string timezone, bool improved, OnUpdateStatusFn update_fn)
std::string dev_ip, std::string dev_id, std::string dev_model, std::string sec_link, std::string timezone, bool improved, OnUpdateStatusFn update_fn)
{
(void) dev_ip;
(void) dev_id;
(void) dev_model;
(void) sec_link;
(void) timezone;
(void) improved;
@@ -254,6 +255,15 @@ int MoonrakerPrinterAgent::request_bind_ticket(std::string* ticket)
return BAMBU_NETWORK_SUCCESS;
}
int MoonrakerPrinterAgent::get_hms_snapshot(std::string dev_id, std::string file_name, std::function<void(std::string, int)> callback)
{
// No BBL cloud snapshot source; report failure so the caller falls back.
(void) dev_id;
(void) file_name;
(void) callback;
return -1;
}
int MoonrakerPrinterAgent::set_server_callback(OnServerErrFn fn)
{
std::lock_guard<std::recursive_mutex> lock(state_mutex);
@@ -537,7 +547,7 @@ void MoonrakerPrinterAgent::build_ams_payload(int ams_count, int max_lane_index,
print_json["ams"] = ams_json;
// Call the parser to populate DevFilaSystem
DevFilaSystemParser::ParseV1_0(print_json, obj, obj->GetFilaSystem(), false);
DevFilaSystemParser::ParseV1_0(print_json, obj, obj->GetFilaSystem().get(), false);
BOOST_LOG_TRIVIAL(info) << "MoonrakerPrinterAgent::build_ams_payload: Parsed " << trays.size() << " trays";
// Set printer_type so update_sync_status() can match it against the preset's printer type.
+2 -1
View File
@@ -42,9 +42,10 @@ public:
// Binding
int ping_bind(std::string ping_code) override;
int bind_detect(std::string dev_ip, std::string sec_link, detectResult& detect) override;
int bind(std::string dev_ip, std::string dev_id, std::string sec_link, std::string timezone, bool improved, OnUpdateStatusFn update_fn) override;
int bind(std::string dev_ip, std::string dev_id, std::string dev_model, std::string sec_link, std::string timezone, bool improved, OnUpdateStatusFn update_fn) override;
int unbind(std::string dev_id) override;
int request_bind_ticket(std::string* ticket) override;
int get_hms_snapshot(std::string dev_id, std::string file_name, std::function<void(std::string, int)> callback) override;
int set_server_callback(OnServerErrFn fn) override;
// Machine Selection
+13 -7
View File
@@ -115,15 +115,11 @@ void NetworkAgent::add_cloud_agent(const std::string& provider, std::shared_ptr<
void NetworkAgent::set_printer_agent(std::shared_ptr<IPrinterAgent> printer_agent)
{
if (!printer_agent) {
return;
}
// Disconnect all callbacks from the old agent
auto old_printer_agent = m_printer_agent;
m_printer_agent = std::move(printer_agent);
m_printer_agent_id = m_printer_agent->get_agent_info().id;
m_printer_agent_id = m_printer_agent ? m_printer_agent->get_agent_info().id : "";
// Disconnect the old agent's connections/threads.
if (old_printer_agent && old_printer_agent != m_printer_agent) {
@@ -131,6 +127,9 @@ void NetworkAgent::set_printer_agent(std::shared_ptr<IPrinterAgent> printer_agen
apply_printer_callbacks(old_printer_agent, {});
}
if (!m_printer_agent)
return;
apply_printer_callbacks(m_printer_agent, m_printer_callbacks);
}
@@ -824,10 +823,10 @@ int NetworkAgent::bind_detect(std::string dev_ip, std::string sec_link, detectRe
}
int NetworkAgent::bind(
std::string dev_ip, std::string dev_id, std::string sec_link, std::string timezone, bool improved, OnUpdateStatusFn update_fn)
std::string dev_ip, std::string dev_id, std::string dev_model, std::string sec_link, std::string timezone, bool improved, OnUpdateStatusFn update_fn)
{
if (m_printer_agent)
return m_printer_agent->bind(dev_ip, dev_id, sec_link, timezone, improved, update_fn);
return m_printer_agent->bind(dev_ip, dev_id, dev_model, sec_link, timezone, improved, update_fn);
return -1;
}
@@ -937,4 +936,11 @@ int NetworkAgent::request_bind_ticket(std::string* ticket)
return -1;
}
int NetworkAgent::get_hms_snapshot(std::string dev_id, std::string file_name, std::function<void(std::string, int)> callback)
{
if (m_printer_agent)
return m_printer_agent->get_hms_snapshot(dev_id, file_name, callback);
return -1;
}
} // namespace Slic3r
+2 -1
View File
@@ -150,7 +150,7 @@ public:
bool start_discovery(bool start, bool sending);
int ping_bind(std::string ping_code);
int bind_detect(std::string dev_ip, std::string sec_link, detectResult& detect);
int bind(std::string dev_ip, std::string dev_id, std::string sec_link, std::string timezone, bool improved, OnUpdateStatusFn update_fn);
int bind(std::string dev_ip, std::string dev_id, std::string dev_model, std::string sec_link, std::string timezone, bool improved, OnUpdateStatusFn update_fn);
int unbind(std::string dev_id);
std::string get_user_selected_machine();
int set_user_selected_machine(std::string dev_id);
@@ -166,6 +166,7 @@ public:
FilamentSyncMode get_filament_sync_mode() const;
bool fetch_filament_info(std::string dev_id);
int request_bind_ticket(std::string* ticket);
int get_hms_snapshot(std::string dev_id, std::string file_name, std::function<void(std::string, int)> callback);
private:
struct PrinterCallbacks {
+302 -25
View File
@@ -6,16 +6,29 @@
#include "QidiPrinterAgent.hpp"
#include "SnapmakerPrinterAgent.hpp"
#include "MoonrakerPrinterAgent.hpp"
#include "slic3r/plugin/PluginManager.hpp"
#include "slic3r/plugin/pluginTypes/printerAgent/PrinterAgentPluginCapability.hpp"
#include "CrealityPrintAgent.hpp"
#include <algorithm>
#include <boost/log/trivial.hpp>
#include <chrono>
#include <map>
#include <mutex>
#include <utility>
#include <slic3r/GUI/GUI_App.hpp>
#include <slic3r/plugin/PluginDescriptor.hpp>
#include <slic3r/plugin/PythonPluginInterface.hpp>
namespace Slic3r {
namespace {
static std::mutex s_registry_mutex;
struct PythonPrinterAgentRegistrationToken {};
static std::map<std::pair<std::string, std::string>, std::shared_ptr<PythonPrinterAgentRegistrationToken>>
s_python_printer_agent_registration_tokens;
std::map<std::string, PrinterAgentInfo>& get_printer_agents()
{
static std::map<std::string, PrinterAgentInfo> agents;
@@ -28,22 +41,32 @@ std::map<std::string, std::shared_ptr<IPrinterAgent>>& get_printer_agent_cache()
return cache;
}
std::map<std::pair<std::string, std::string>, std::string>& get_python_printer_agent_ids()
{
static std::map<std::pair<std::string, std::string>, std::string> capability_to_agent_id;
return capability_to_agent_id;
}
std::string plugin_printer_agent_full_ref(const PluginDescriptor& descriptor, const std::string& capability_name)
{
const std::string identity = descriptor.is_cloud_plugin() ? descriptor.name : descriptor.plugin_key;
return identity + ';' + descriptor.cloud_uuid() + ';' + capability_name;
}
// Helper to register a printer agent type with the standard factory pattern.
// AgentTypes that take a log_dir constructor arg use the default; BBLPrinterAgent
// (no log_dir) is registered separately.
template<typename T>
void register_agent()
template<typename T> void register_agent()
{
auto info = T::get_agent_info_static();
NetworkAgentFactory::register_printer_agent(
info.id, info.name,
[](std::shared_ptr<ICloudServiceAgent> cloud_agent,
const std::string& log_dir) -> std::shared_ptr<IPrinterAgent> {
auto agent = std::make_shared<T>(log_dir);
if (cloud_agent)
agent->set_cloud_agent(cloud_agent);
return agent;
});
NetworkAgentFactory::register_printer_agent(info.id, info.name,
[](std::shared_ptr<ICloudServiceAgent> cloud_agent,
const std::string& log_dir) -> std::shared_ptr<IPrinterAgent> {
auto agent = std::make_shared<T>(log_dir);
if (cloud_agent)
agent->set_cloud_agent(cloud_agent);
return agent;
});
}
} // anonymous namespace
@@ -51,48 +74,62 @@ void register_agent()
bool NetworkAgentFactory::register_printer_agent(const std::string& id, const std::string& display_name, PrinterAgentFactory factory)
{
std::lock_guard<std::mutex> lock(s_registry_mutex);
auto& agents = get_printer_agents();
auto& agents = get_printer_agents();
return agents.emplace(id, PrinterAgentInfo(id, display_name, std::move(factory))).second;
}
bool NetworkAgentFactory::is_printer_agent_registered(const std::string& id)
{
std::lock_guard<std::mutex> lock(s_registry_mutex);
auto& agents = get_printer_agents();
auto& agents = get_printer_agents();
return agents.find(id) != agents.end();
}
const PrinterAgentInfo* NetworkAgentFactory::get_printer_agent_info(const std::string& id)
{
std::lock_guard<std::mutex> lock(s_registry_mutex);
auto& agents = get_printer_agents();
auto it = agents.find(id);
auto& agents = get_printer_agents();
auto it = agents.find(id);
return (it != agents.end()) ? &it->second : nullptr;
}
std::string NetworkAgentFactory::get_printer_agent_plugin_identifier(const std::string& id)
{
std::lock_guard<std::mutex> lock(s_registry_mutex);
auto& agents = get_printer_agents();
auto it = agents.find(id);
return (it != agents.end() && it->second.is_plugin()) ? it->second.plugin_identifier : std::string();
}
std::vector<PrinterAgentInfo> NetworkAgentFactory::get_registered_printer_agents()
{
std::lock_guard<std::mutex> lock(s_registry_mutex);
auto& agents = get_printer_agents();
std::lock_guard<std::mutex> lock(s_registry_mutex);
auto& agents = get_printer_agents();
std::vector<PrinterAgentInfo> result;
result.reserve(agents.size());
for (const auto& pair : agents) {
result.push_back(pair.second);
if (!pair.second.is_plugin())
result.push_back(pair.second);
}
for (const auto& pair : agents) {
if (pair.second.is_plugin())
result.push_back(pair.second);
}
return result;
}
std::shared_ptr<IPrinterAgent> NetworkAgentFactory::create_printer_agent_by_id(const std::string& id,
std::shared_ptr<IPrinterAgent> NetworkAgentFactory::create_printer_agent_by_id(const std::string& id,
std::shared_ptr<ICloudServiceAgent> cloud_agent,
const std::string& log_dir)
const std::string& log_dir)
{
std::lock_guard<std::mutex> lock(s_registry_mutex);
// Check cache first
auto& cache = get_printer_agent_cache();
auto cache_it = cache.find(id);
auto& cache = get_printer_agent_cache();
auto cache_it = cache.find(id);
if (cache_it != cache.end()) {
BOOST_LOG_TRIVIAL(info) << "Reusing cached printer agent: " << id;
if (cloud_agent)
@@ -102,7 +139,7 @@ std::shared_ptr<IPrinterAgent> NetworkAgentFactory::create_printer_agent_by_id(c
// Not cached — create via factory
auto& agents = get_printer_agents();
auto it = agents.find(id);
auto it = agents.find(id);
if (it == agents.end()) {
BOOST_LOG_TRIVIAL(warning) << "Unknown printer agent ID: " << id;
@@ -120,7 +157,7 @@ std::shared_ptr<IPrinterAgent> NetworkAgentFactory::create_printer_agent_by_id(c
void NetworkAgentFactory::clear_printer_agent_cache()
{
std::lock_guard<std::mutex> lock(s_registry_mutex);
auto& cache = get_printer_agent_cache();
auto& cache = get_printer_agent_cache();
for (auto& pair : cache) {
if (pair.second)
pair.second->disconnect_printer();
@@ -139,7 +176,8 @@ void NetworkAgentFactory::register_all_agents()
// for K-series boards with CFS support.
register_agent<MoonrakerPrinterAgent>();
// BBLPrinterAgent takes no constructor args, so register manually
// Keep BBL as a built-in option. Python printer-agent plugins with the
// same AgentInfo ID are listed separately under the plugin registry key.
{
auto info = BBLPrinterAgent::get_agent_info_static();
register_printer_agent(info.id, info.name,
@@ -189,4 +227,243 @@ std::unique_ptr<NetworkAgent> create_agent_from_config(const std::string& log_di
return agent;
}
void NetworkAgentFactory::register_python_plugin(const std::string& plugin_key)
{
for (const auto& capability : PluginManager::instance().get_plugin_capabilities(plugin_key, PluginCapabilityType::PrinterConnection,
/*only_enabled=*/true))
register_python_printer_agent(plugin_key, capability->name());
}
void NetworkAgentFactory::register_python_printer_agent(const std::string& plugin_key, const std::string& capability_name)
{
PluginManager& plugin_manager = PluginManager::instance();
auto cap = plugin_manager.get_plugin_capability({PluginCapabilityType::PrinterConnection, capability_name, plugin_key},
/*only_enabled=*/false);
if (!cap) {
BOOST_LOG_TRIVIAL(warning) << "Printer-agent capability '" << capability_name << "' not found for plugin '" << plugin_key << "'";
return;
}
PluginDescriptor descriptor;
if (!plugin_manager.try_get_plugin_descriptor(plugin_key, descriptor)) {
BOOST_LOG_TRIVIAL(warning) << "Could not find descriptor for plugin key: '" << plugin_key;
return;
}
if (!cap->is_enabled()) {
BOOST_LOG_TRIVIAL(warning) << "Printer-agent capability '" << capability_name << "' is disabled for plugin '" << plugin_key << "'";
return;
}
auto plugin = std::dynamic_pointer_cast<PrinterAgentPluginCapability>(cap);
if (!plugin) {
BOOST_LOG_TRIVIAL(warning) << "Loaded plugin capability '" << capability_name << "' is not a printer-agent plugin";
return;
}
auto info = plugin->get_agent_info();
if (info.id.empty()) {
BOOST_LOG_TRIVIAL(warning) << "Printer-agent plugin '" << capability_name << "' returned an empty agent ID";
return;
}
const auto capability_key = std::make_pair(plugin_key, capability_name);
std::shared_ptr<PythonPrinterAgentRegistrationToken> registration_token;
{
std::lock_guard<std::mutex> lock(s_registry_mutex);
auto token_it = std::find_if(s_python_printer_agent_registration_tokens.begin(), s_python_printer_agent_registration_tokens.end(),
[&plugin_key, &capability_name](const auto& entry) {
return entry.first.first == plugin_key && entry.first.second == capability_name;
});
if (token_it == s_python_printer_agent_registration_tokens.end()) {
registration_token = std::make_shared<PythonPrinterAgentRegistrationToken>();
s_python_printer_agent_registration_tokens.emplace(capability_key, registration_token);
} else {
registration_token = token_it->second;
}
}
const std::string plugin_full_ref = plugin_printer_agent_full_ref(descriptor, capability_name);
std::weak_ptr<PrinterAgentPluginCapability> weak_plugin = plugin;
PrinterAgentFactory factory = [weak_plugin](std::shared_ptr<ICloudServiceAgent> cloud_agent,
const std::string& /*log_dir*/) -> std::shared_ptr<IPrinterAgent> {
// The capability implements IPrinterAgent directly, so it is handed out as the
// live agent (no adapter); the Python plugin services the calls.
auto plugin = weak_plugin.lock();
if (!plugin)
return nullptr;
if (cloud_agent)
plugin->set_cloud_agent(cloud_agent);
return plugin;
};
// Python work above may allow disable/unload/reload to replace this capability.
// Re-resolve without holding the registry mutex to avoid lock inversion and reentrancy.
// only_enabled defaults to true here, so a capability that changed identity (reload) or was
// merely disabled both surface the same way: current_cap no longer equals cap.
auto current_cap = plugin_manager.get_plugin_capability({PluginCapabilityType::PrinterConnection, capability_name, plugin_key});
if (current_cap != cap) {
BOOST_LOG_TRIVIAL(debug) << "Printer-agent capability '" << capability_name << "' from plugin '" << plugin_key
<< "' changed or was disabled during registration";
return;
}
std::shared_ptr<IPrinterAgent> cached_agent;
{
std::lock_guard<std::mutex> lock(s_registry_mutex);
auto token_it = std::find_if(s_python_printer_agent_registration_tokens.begin(), s_python_printer_agent_registration_tokens.end(),
[&plugin_key, &capability_name](const auto& entry) {
return entry.first.first == plugin_key && entry.first.second == capability_name;
});
const bool cap_still_enabled = cap->is_enabled();
if (token_it == s_python_printer_agent_registration_tokens.end() || token_it->second != registration_token ||
!cap_still_enabled) {
BOOST_LOG_TRIVIAL(debug) << "Printer-agent capability '" << capability_name << "' from plugin '" << plugin_key
<< "' was disabled before registry commit";
return;
}
auto& python_agent_ids = get_python_printer_agent_ids();
for (const auto& pair : python_agent_ids) {
if (pair.first != capability_key && pair.second == info.id) {
BOOST_LOG_TRIVIAL(warning) << "Printer-agent plugin '" << capability_name << "' uses duplicate agent ID '" << info.id
<< "' already registered by capability '" << pair.first.second << "' from plugin '"
<< pair.first.first << "'";
return;
}
}
auto plugin_it = python_agent_ids.find(capability_key);
if (plugin_it != python_agent_ids.end() && plugin_it->second != info.id) {
const std::string old_agent_id = plugin_it->second;
get_printer_agents().erase(old_agent_id);
auto& cache = get_printer_agent_cache();
auto cache_it = cache.find(old_agent_id);
if (cache_it != cache.end()) {
cached_agent = std::move(cache_it->second);
cache.erase(cache_it);
}
}
auto& agents = get_printer_agents();
auto agent_it = agents.find(info.id);
if (agent_it != agents.end() && agent_it->second.plugin_identifier != plugin_full_ref) {
BOOST_LOG_TRIVIAL(warning) << "Printer-agent plugin '" << capability_name << "' uses agent ID '" << info.id
<< "' already registered by '" << agent_it->second.display_name << "'";
return;
}
agents.insert_or_assign(info.id, PrinterAgentInfo(info.id, info.name, plugin_full_ref, std::move(factory)));
python_agent_ids[capability_key] = info.id;
auto& cache = get_printer_agent_cache();
auto cache_it = cache.find(info.id);
if (cache_it != cache.end()) {
cached_agent = std::move(cache_it->second);
cache.erase(cache_it);
}
}
if (cached_agent)
cached_agent->disconnect_printer();
BOOST_LOG_TRIVIAL(info) << "Registered printer-agent plugin '" << capability_name << "' with agent ID '" << info.id
<< "' and plugin ref '" << plugin_full_ref << "'";
}
void NetworkAgentFactory::deregister_python_plugin(const std::string& plugin_key)
{
std::vector<std::string> capability_names;
{
std::lock_guard<std::mutex> lock(s_registry_mutex);
for (const auto& [capability_key, token] : s_python_printer_agent_registration_tokens) {
(void) token;
if (capability_key.first == plugin_key)
capability_names.push_back(capability_key.second);
}
for (const auto& [capability_key, agent_id] : get_python_printer_agent_ids()) {
(void) agent_id;
if (capability_key.first == plugin_key &&
std::find(capability_names.begin(), capability_names.end(), capability_key.second) == capability_names.end())
capability_names.push_back(capability_key.second);
}
}
for (const std::string& capability_name : capability_names)
deregister_python_printer_agent(plugin_key, capability_name);
if (capability_names.empty())
BOOST_LOG_TRIVIAL(debug) << "Python printer-agent plugin '" << plugin_key << "' has no registered capabilities";
}
void NetworkAgentFactory::deregister_python_printer_agent(const std::string& plugin_key, const std::string& capability_name)
{
std::string agent_id;
std::shared_ptr<IPrinterAgent> cached_agent;
{
std::lock_guard<std::mutex> lock(s_registry_mutex);
auto token_it = std::find_if(s_python_printer_agent_registration_tokens.begin(),
s_python_printer_agent_registration_tokens.end(),
[&plugin_key, &capability_name](const auto& entry) {
return entry.first.first == plugin_key &&
entry.first.second == capability_name;
});
if (token_it != s_python_printer_agent_registration_tokens.end())
s_python_printer_agent_registration_tokens.erase(token_it);
auto& python_agent_ids = get_python_printer_agent_ids();
auto id_it = std::find_if(python_agent_ids.begin(), python_agent_ids.end(),
[&plugin_key, &capability_name](const auto& entry) {
return entry.first.first == plugin_key &&
entry.first.second == capability_name;
});
if (id_it == python_agent_ids.end()) {
BOOST_LOG_TRIVIAL(debug) << "Python printer-agent capability '" << capability_name << "' from plugin '"
<< plugin_key << "' is not registered";
return;
}
agent_id = id_it->second;
python_agent_ids.erase(id_it);
auto& cache = get_printer_agent_cache();
auto cache_it = cache.find(agent_id);
if (cache_it != cache.end()) {
cached_agent = std::move(cache_it->second);
cache.erase(cache_it);
}
get_printer_agents().erase(agent_id);
}
if (cached_agent)
cached_agent->disconnect_printer();
// The GUI may still own the active capability separately from the factory cache. Clear that
// handle before the plugin module is torn down, otherwise NetworkAgent can retain a Python
// object whose implementation is about to be unloaded.
if (!agent_id.empty() && wxTheApp) {
NetworkAgent* network_agent = GUI::wxGetApp().getAgent();
if (network_agent) {
const auto active_agent = network_agent->get_printer_agent();
if (active_agent && active_agent->get_agent_info().id == agent_id)
network_agent->set_printer_agent(nullptr);
}
}
BOOST_LOG_TRIVIAL(info) << "Deregistered Python printer-agent capability '" << capability_name << "' from plugin '"
<< plugin_key << "' with agent ID '" << agent_id << "'";
}
} // namespace Slic3r
+24 -6
View File
@@ -24,13 +24,19 @@ using PrinterAgentFactory =
// Information about a registered printer agent
struct PrinterAgentInfo
{
std::string id; // e.g., "orca", "bbl"
std::string id; // Registry/config key, e.g. "orca" or a plugin AgentInfo::id
std::string display_name; // e.g., "Orca Native", "Bambu Lab"
std::string plugin_identifier; // Empty for built-ins, otherwise <plugin_key>;<uuid>;<capability_name>
PrinterAgentFactory factory; // Function to create the agent
bool is_plugin() const { return !plugin_identifier.empty(); }
PrinterAgentInfo(const std::string& id_, const std::string& display_name_, PrinterAgentFactory factory_)
: id(id_), display_name(display_name_), factory(std::move(factory_))
{}
PrinterAgentInfo(const std::string& id_, const std::string& display_name_, const std::string& plugin_identifier, PrinterAgentFactory factory_)
: id(id_), display_name(display_name_), plugin_identifier(plugin_identifier), factory(std::move(factory_))
{}
};
/**
@@ -80,27 +86,32 @@ public:
static bool register_printer_agent(const std::string& id, const std::string& display_name, PrinterAgentFactory factory);
/**
* Check if an agent ID is registered
* Check if an agent registry/config key is registered
*/
static bool is_printer_agent_registered(const std::string& id);
/**
* Get info about a registered agent
* Get info about a registered agent by registry/config key
*/
static const PrinterAgentInfo* get_printer_agent_info(const std::string& id);
/**
* Return the full plugin reference for a plugin-backed printer agent ID, or empty for built-ins.
*/
static std::string get_printer_agent_plugin_identifier(const std::string& id);
/**
* Get all registered printer agents (for UI population)
*/
static std::vector<PrinterAgentInfo> get_registered_printer_agents();
/**
* Create a printer agent by ID (using registry)
* Create a printer agent by registry/config key
*
* Returns a cached instance if one exists for the given ID, otherwise
* Returns a cached instance if one exists for the given key, otherwise
* creates a new agent via the registered factory and caches it.
*
* @param id Agent ID to create
* @param id Agent registry/config key to create
* @param cloud_agent Cloud agent for token access
* @param log_dir Directory for log files
* @return Shared pointer to IPrinterAgent, or nullptr if ID not found
@@ -148,6 +159,13 @@ public:
return nullptr;
}
// Plugin printer agents
static void register_python_plugin(const std::string& plugin_key);
static void deregister_python_plugin(const std::string& plugin_key);
static void register_python_printer_agent(const std::string& plugin_key, const std::string& capability_name);
static void deregister_python_printer_agent(const std::string& plugin_key, const std::string& capability_name);
private:
// Factory is not instantiable
NetworkAgentFactory() = delete;
File diff suppressed because it is too large Load Diff
+44 -1
View File
@@ -2,14 +2,17 @@
#define __ORCA_CLOUD_SERVICE_AGENT_HPP__
#include "ICloudServiceAgent.hpp"
#include <cstdlib>
#include <string>
#include <map>
#include <mutex>
#include <memory>
#include <atomic>
#include <chrono>
#include <functional>
#include <memory>
#include <thread>
#include <unordered_map>
#include <vector>
#include <nlohmann/json.hpp>
class wxSecretStore;
@@ -19,6 +22,22 @@ namespace Slic3r {
// Forward declarations
class AppConfig;
struct BundleMetadata;
struct PluginDescriptor;
struct PluginChangelog;
struct PluginDownloadData
{
std::string plugin_id;
std::string download_link;
std::string requested_os;
std::string returned_os;
};
struct PluginDownloadNotFound
{
std::string id;
std::string reason;
};
// Outcome of a token-refresh attempt: decides whether a 401 should log the user
// out (AuthRejected) or be treated as a recoverable condition (Transient).
@@ -36,6 +55,15 @@ namespace auth_constants {
constexpr const char* LOGOUT_PATH = "/auth/v1/logout";
} // namespace auth_constants
// Names of the on-disk credential files inside the config dir.
namespace secret_constants {
// Encrypted refresh-token fallback file, written when wxSecretStore is unavailable.
// Also seeded into PluginAuditManager's denied-filename registry (install_hook), which
// additionally covers the ".tmp" staging file the token is written to before its atomic
// rename, so plugins can read neither.
constexpr const char* USER_SECRET_FILENAME = "orca_refresh_token.sec";
} // namespace secret_constants
// ============================================================================
// Sync Protocol Data Structures (per Orca Cloud Sync Protocol Specification)
// ============================================================================
@@ -270,6 +298,21 @@ public:
int get_subscribed_bundles(std::vector<std::pair<std::string, std::string>>* bundles,std::vector<std::string>& notfound, std::vector<std::string>& unauthorized);
int get_shared_bundle(const std::string& bundle_id, std::map<std::string, std::map<std::string, std::string>>* presets, BundleMetadata* bundle_metadata);
// ========================================================================
// Plugins API
// ========================================================================
int fetch_subscribed_manifests_into_descriptors(std::vector<PluginDescriptor>& descriptors, std::vector<std::string>& not_found, std::vector<std::string>& unauthorized);
int fetch_mine_manifests_into_descriptors(std::vector<PluginDescriptor>& descriptors);
int get_plugin_download_url(const std::string& uuid,
const std::string& requested_version,
std::vector<PluginDownloadData>& data,
std::vector<PluginDownloadNotFound>& not_found,
std::vector<std::string>& unauthorized);
std::string get_plugin_url(const std::string& sharing_token) const;
int subscribe_plugin(const std::string& plugin_uuid);
int unsubscribe_plugins(const std::vector<std::string>& plugin_uuids);
int fetch_plugin_changelogs(const std::vector<std::string>& uuids, std::unordered_map<std::string, std::vector<PluginChangelog>>& changelog);
// ========================================================================
// Additional Public Methods - Auth
// ========================================================================
+10 -1
View File
@@ -78,7 +78,7 @@ int OrcaPrinterAgent::bind_detect(std::string dev_ip, std::string sec_link, dete
}
int OrcaPrinterAgent::bind(
std::string dev_ip, std::string dev_id, std::string sec_link, std::string timezone, bool improved, OnUpdateStatusFn update_fn)
std::string dev_ip, std::string dev_id, std::string dev_model, std::string sec_link, std::string timezone, bool improved, OnUpdateStatusFn update_fn)
{
return BAMBU_NETWORK_SUCCESS;
}
@@ -95,6 +95,15 @@ int OrcaPrinterAgent::request_bind_ticket(std::string* ticket)
return BAMBU_NETWORK_SUCCESS;
}
int OrcaPrinterAgent::get_hms_snapshot(std::string dev_id, std::string file_name, std::function<void(std::string, int)> callback)
{
// No BBL cloud snapshot source; report failure so the caller falls back.
(void) dev_id;
(void) file_name;
(void) callback;
return -1;
}
int OrcaPrinterAgent::set_server_callback(OnServerErrFn fn)
{
std::lock_guard<std::mutex> lock(state_mutex);
+2 -1
View File
@@ -42,9 +42,10 @@ public:
// Binding
int ping_bind(std::string ping_code) override;
int bind_detect(std::string dev_ip, std::string sec_link, detectResult& detect) override;
int bind(std::string dev_ip, std::string dev_id, std::string sec_link, std::string timezone, bool improved, OnUpdateStatusFn update_fn) override;
int bind(std::string dev_ip, std::string dev_id, std::string dev_model, std::string sec_link, std::string timezone, bool improved, OnUpdateStatusFn update_fn) override;
int unbind(std::string dev_id) override;
int request_bind_ticket(std::string* ticket) override;
int get_hms_snapshot(std::string dev_id, std::string file_name, std::function<void(std::string, int)> callback) override;
int set_server_callback(OnServerErrFn fn) override;
// Machine Selection
+67 -119
View File
@@ -1,12 +1,14 @@
#include "PresetUpdater.hpp"
#include <algorithm>
#include <boost/filesystem/directory.hpp>
#include <boost/filesystem/operations.hpp>
#include <boost/filesystem/path.hpp>
#include <boost/nowide/fstream.hpp>
#include <functional>
#include <atomic>
#include <mutex>
#include <set>
#include <string>
#include <thread>
#include <unordered_map>
#include <ostream>
@@ -19,6 +21,7 @@
#include <boost/lexical_cast.hpp>
#include <boost/log/trivial.hpp>
#include <vector>
#include <wx/app.h>
#include <wx/msgdlg.h>
@@ -204,7 +207,6 @@ struct PresetUpdater::priv
// Per-vendor update checking
std::set<std::string> checked_vendors;
std::mutex vendor_check_mutex;
std::vector<std::thread> vendor_check_threads;
std::atomic<bool> vendor_check_cancel{false};
@@ -221,10 +223,10 @@ struct PresetUpdater::priv
priv();
void set_download_prefs(AppConfig *app_config);
bool get_file(const std::string &url, const fs::path &target_path) const;
//BBS: refine preset update logic
bool get_file(const std::string &url, const fs::path &target_path) const;
//BBS: refine preset update logic
bool extract_file(const fs::path &source_path, const fs::path &dest_path = {});
void prune_tmps() const;
void prune_tmp(const std::string& vendor_id) const;
void sync_version() const;
void parse_version_string(const std::string& body) const;
void sync_resources(std::string http_url, std::map<std::string, Resource> &resources, bool check_patch = false, std::string current_version="", std::string changelog_file="");
@@ -371,14 +373,14 @@ bool PresetUpdater::priv::extract_file(const fs::path &source_path, const fs::pa
return true;
}
// Remove leftover paritally downloaded files, if any.
void PresetUpdater::priv::prune_tmps() const
// Remove a leftover partial archive for the vendor about to be synchronized.
void PresetUpdater::priv::prune_tmp(const std::string& vendor_id) const
{
for (auto &dir_entry : boost::filesystem::directory_iterator(cache_path))
if (is_plain_file(dir_entry) && dir_entry.path().extension() == TMP_EXTENSION) {
BOOST_LOG_TRIVIAL(debug) << "[Orca Updater]remove old cached files: " << dir_entry.path().string();
fs::remove(dir_entry.path());
}
boost::system::error_code ec;
const fs::path tmp_path = cache_path / (vendor_id + TMP_EXTENSION);
fs::remove(tmp_path, ec);
if (ec)
BOOST_LOG_TRIVIAL(warning) << "[Orca Updater]failed to remove " << tmp_path.string() << ": " << ec.message();
}
//BBS: refine the Preset Updater logic
@@ -782,9 +784,9 @@ void PresetUpdater::priv::sync_tooltip(std::string http_url, std::string languag
// return true means there are plugins files
bool PresetUpdater::priv::get_cached_plugins_version(std::string& cached_version, bool &force)
{
std::string data_dir_str = data_dir();
boost::filesystem::path data_dir_path(data_dir_str);
auto cache_folder = data_dir_path / "ota";
// The OTA plugin cache lives in its own ota/plugins subfolder; the update dialog
// (Plater::priv::update_plugin_when_launch) reads the changelog from the same place.
auto cache_folder = cache_path / "plugins";
std::string network_library, player_library, live555_library;
bool has_plugins = false;
@@ -838,6 +840,25 @@ void PresetUpdater::priv::sync_plugins(std::string http_url, std::string plugin_
}
std::string curr_version = GUI::wxGetApp().use_legacy_network_plugin() ? BAMBU_NETWORK_AGENT_VERSION_LEGACY : get_latest_network_version();
std::string using_version = curr_version.substr(0, 9) + "00";
auto cache_plugin_folder = cache_path / "plugins";
// Orca: drop leftovers from the old flat ota/ cache layout (pre ota/plugins) so the
// stale files cannot linger forever after this layout migration.
{
#if defined(_MSC_VER) || defined(_WIN32)
const char* legacy_names[] = {"bambu_networking.dll", "BambuSource.dll", "live555.dll", "network_plugins.json"};
#elif defined(__WXMAC__)
const char* legacy_names[] = {"libbambu_networking.dylib", "libBambuSource.dylib", "liblive555.dylib", "network_plugins.json"};
#else
const char* legacy_names[] = {"libbambu_networking.so", "libBambuSource.so", "liblive555.so", "network_plugins.json"};
#endif
for (const char* name : legacy_names) {
boost::system::error_code ec;
auto legacy_file = cache_path / name;
if (boost::filesystem::exists(legacy_file, ec))
boost::filesystem::remove(legacy_file, ec);
}
}
std::string cached_version;
bool force_upgrade = false;
@@ -868,64 +889,11 @@ void PresetUpdater::priv::sync_plugins(std::string http_url, std::string plugin_
}
if (need_delete_cache) {
std::string data_dir_str = data_dir();
boost::filesystem::path data_dir_path(data_dir_str);
auto cache_folder = data_dir_path / "ota";
#if defined(_MSC_VER) || defined(_WIN32)
auto network_library = cache_folder / "bambu_networking.dll";
auto player_library = cache_folder / "BambuSource.dll";
auto live555_library = cache_folder / "live555.dll";
#elif defined(__WXMAC__)
auto network_library = cache_folder / "libbambu_networking.dylib";
auto player_library = cache_folder / "libBambuSource.dylib";
auto live555_library = cache_folder / "liblive555.dylib";
#else
auto network_library = cache_folder / "libbambu_networking.so";
auto player_library = cache_folder / "libBambuSource.so";
auto live555_library = cache_folder / "liblive555.so";
#endif
auto changelog_file = cache_folder / "network_plugins.json";
if (boost::filesystem::exists(network_library))
{
BOOST_LOG_TRIVIAL(info) << "[remove_old_networking_plugins] remove the file "<<network_library.string();
try {
fs::remove(network_library);
} catch (...) {
BOOST_LOG_TRIVIAL(error) << "Failed removing the plugins file " << network_library.string();
}
}
if (boost::filesystem::exists(player_library))
{
BOOST_LOG_TRIVIAL(info) << "[remove_old_networking_plugins] remove the file "<<player_library.string();
try {
fs::remove(player_library);
} catch (...) {
BOOST_LOG_TRIVIAL(error) << "Failed removing the plugins file " << player_library.string();
}
}
if (boost::filesystem::exists(live555_library))
{
BOOST_LOG_TRIVIAL(info) << "[remove_old_networking_plugins] remove the file " << live555_library.string();
try {
fs::remove(live555_library);
} catch (...) {
BOOST_LOG_TRIVIAL(error) << "Failed removing the plugins file " << live555_library.string();
}
}
if (boost::filesystem::exists(changelog_file))
{
BOOST_LOG_TRIVIAL(info) << "[remove_old_networking_plugins] remove the file "<<changelog_file.string();
try {
fs::remove(changelog_file);
} catch (...) {
BOOST_LOG_TRIVIAL(error) << "Failed removing the plugins file " << changelog_file.string();
}
BOOST_LOG_TRIVIAL(info) << "[remove_old_networking_plugins] remove the plugins directory " << cache_plugin_folder.string();
try {
fs::remove_all(cache_plugin_folder);
} catch (...) {
BOOST_LOG_TRIVIAL(error) << "Failed removing the plugins directory " << cache_plugin_folder.string();
}
}
}
@@ -943,7 +911,7 @@ void PresetUpdater::priv::sync_plugins(std::string http_url, std::string plugin_
try {
std::map<std::string, Resource> resources
{
{"slicer/plugins/cloud", { using_version, "", "", false, cache_path.string(), {"plugins"}}}
{"slicer/plugins/cloud", { using_version, "", "", false, cache_plugin_folder.string()}}
};
sync_resources(http_url, resources, true, plugin_version, "network_plugins.json");
}
@@ -1094,10 +1062,9 @@ void PresetUpdater::priv::check_installed_vendor_profiles() const
Semver resource_ver = get_version_from_json(file_path);
Semver vendor_ver = get_version_from_json(path_in_vendor.string());
bool version_match = ((resource_ver.maj() == vendor_ver.maj()) && (resource_ver.min() == vendor_ver.min()));
if (!version_match || (vendor_ver < resource_ver)) {
BOOST_LOG_TRIVIAL(info) << "[Orca Updater]:found vendor "<<vendor_name<<" newer version "<<resource_ver.to_string() <<" from resource, old version "<<vendor_ver.to_string();
if (vendor_ver < resource_ver) {
BOOST_LOG_TRIVIAL(info) << "[Orca Updater]:found vendor " << vendor_name << " newer version "
<< resource_ver.to_string() << " from resource, old version " << vendor_ver.to_string();
bundles.insert(vendor_name);
}
}
@@ -1339,49 +1306,29 @@ PresetUpdater::~PresetUpdater()
//BBS: change directories by design
//BBS: refine the preset updater logic
void PresetUpdater::sync(std::string http_url, std::string language, std::string plugin_version, PresetBundle *preset_bundle)
void PresetUpdater::sync(std::string http_url, std::string language, std::string plugin_version, PresetBundle * /*preset_bundle*/)
{
//p->set_download_prefs(GUI::wxGetApp().app_config);
if (!p->enabled_version_check && !p->enabled_config_update) { return; }
// Copy the whole vendors data for use in the background thread
// Unfortunatelly as of C++11, it needs to be copied again
// into the closure (but perhaps the compiler can elide this).
VendorMap vendors = preset_bundle ? preset_bundle->vendors : VendorMap{};
// Determine active vendor before entering the thread
std::string active_vendor;
if (preset_bundle) {
const Preset& printer = preset_bundle->printers.get_edited_preset();
if (printer.vendor)
active_vendor = printer.vendor->id;
}
p->thread = std::thread([this, vendors, active_vendor, http_url, language, plugin_version]() {
this->p->prune_tmps();
if (p->cancel)
return;
this->p->sync_version();
if (p->cancel)
return;
// Per-vendor config check for the active vendor at startup
if (!active_vendor.empty() && !vendors.empty()) {
this->p->sync_vendor_config(active_vendor);
if (p->cancel)
return;
{
std::lock_guard<std::mutex> lock(this->p->vendor_check_mutex);
this->p->checked_vendors.insert(active_vendor);
}
}
if (p->cancel)
return;
this->p->sync_plugins(http_url, plugin_version);
this->p->sync_printer_config(http_url);
//if (p->cancel)
// return;
//remove the tooltip currently
//this->p->sync_tooltip(http_url, language);
p->thread = std::thread([this, http_url, language, plugin_version]() {
try {
this->p->sync_version();
if (p->cancel)
return;
// Vendor profile updates are triggered by check_vendor_update()
// after the startup printer preset has been restored.
this->p->sync_plugins(http_url, plugin_version);
this->p->sync_printer_config(http_url);
//if (p->cancel)
// return;
//remove the tooltip currently
//this->p->sync_tooltip(http_url, language);
} catch (const std::exception &e) {
BOOST_LOG_TRIVIAL(error) << "[Orca Updater] background sync failed: " << e.what();
} catch (...) {
BOOST_LOG_TRIVIAL(error) << "[Orca Updater] background sync failed with an unknown exception";
}
});
}
@@ -1390,16 +1337,17 @@ void PresetUpdater::check_vendor_update(const std::string& vendor_id)
if (!p->enabled_config_update) return;
if (vendor_id.empty()) return;
std::lock_guard<std::mutex> lock(p->vendor_check_mutex);
if (!p->checked_vendors.insert(vendor_id).second)
return;
p->vendor_check_threads.emplace_back([this, vendor_id]() {
try {
this->p->prune_tmp(vendor_id);
this->p->sync_vendor_config(vendor_id);
} catch (const std::exception& e) {
BOOST_LOG_TRIVIAL(error) << "[Orca Updater] vendor update failed for " << vendor_id << ": " << e.what();
} catch (...) {
BOOST_LOG_TRIVIAL(error) << "[Orca Updater] vendor update failed for " << vendor_id << " with an unknown exception";
}
});
}
+1 -1
View File
@@ -107,7 +107,7 @@ std::optional<RaycastManager::Hit> RaycastManager::first_hit(const Vec3d& point,
const AABBMesh *hit_mesh = nullptr;
double hit_squared_distance = 0.;
int hit_face = -1;
Vec3d hit_world;
Vec3d hit_world { Vec3d::Zero() };
const Transform3d *hit_tramsformation = nullptr;
const TrKey *hit_key = nullptr;
+180 -7
View File
@@ -128,6 +128,7 @@ typedef std::function<void(std::string topic)> GetSubscribeFailureFn;
typedef std::function<void(int status, int code, std::string msg)> OnUpdateStatusFn;
typedef std::function<bool()> WasCancelledFn;
typedef std::function<bool(int status, std::string job_info)> OnWaitFn;
typedef std::function<void(int online_login, bool login)> OnUserLoginFn;
// local callbacks
typedef std::function<void(std::string dev_info_json_str)> OnMsgArrivedFn;
// queue call to main thread
@@ -229,8 +230,11 @@ struct PrintParams_Legacy {
std::string extra_options;
};
/* print job*/
struct PrintParams {
/* print job, as the 02.03.00 series expects it. The 02.08.01 series inserted
task_timelapse_use_internal, extruder_cali_manual_mode, svc_context and slicer_uid into
PrintParams; two of them sit mid-struct, so an older plug-in misreads every field from
task_use_ams onwards if handed the current layout. Rebuild it with as_0203() instead. */
struct PrintParams_0203 {
/* basic info */
std::string dev_id;
std::string task_name;
@@ -279,6 +283,60 @@ struct PrintParams {
bool try_emmc_print;
};
/* print job*/
struct PrintParams {
/* basic info */
std::string dev_id;
std::string task_name;
std::string project_name;
std::string preset_name;
std::string filename;
std::string config_filename;
int plate_index;
std::string ftp_folder;
std::string ftp_file;
std::string ftp_file_md5;
std::string nozzle_mapping;
std::string ams_mapping;
std::string ams_mapping2;
std::string ams_mapping_info;
std::string nozzles_info;
std::string connection_type;
std::string comments;
int origin_profile_id = 0;
int stl_design_id = 0;
std::string origin_model_id;
std::string print_type;
std::string dst_file;
std::string dev_name;
/* access options */
std::string dev_ip;
bool use_ssl_for_ftp;
bool use_ssl_for_mqtt;
std::string username;
std::string password;
/*user options */
bool task_bed_leveling; /* bed leveling of task */
bool task_flow_cali; /* flow calibration of task */
bool task_vibration_cali; /* vibration calibration of task */
bool task_layer_inspect; /* first layer inspection of task */
bool task_record_timelapse; /* record timelapse of task */
bool task_timelapse_use_internal;
bool task_use_ams;
std::string task_bed_type;
std::string extra_options;
int auto_bed_leveling{ 0 };
int auto_flow_cali{ 0 };
int auto_offset_cali{ 0 };
int extruder_cali_manual_mode{ -1 };
bool task_ext_change_assist;
bool try_emmc_print;
std::string svc_context;
std::string slicer_uid;
};
struct TaskQueryParams
{
std::string dev_id;
@@ -287,6 +345,48 @@ struct TaskQueryParams
int limit = 20;
};
struct FilamentQueryParams
{
std::string category;
std::string status;
std::string spool_id;
std::string rfid;
int offset = 0;
int limit = 20;
};
struct FilamentDeleteParams
{
std::vector<std::string> ids;
std::vector<std::string> rfids;
};
struct AmsSyncItem {
std::string RFID;
std::string filamentVendor;
std::string filamentType;
std::string filamentName;
std::string filamentId;
bool isSupport = false;
std::string color;
int colorType = 0;
std::vector<std::string> colors;
int netWeight = 0;
int totalNetWeight = 0;
std::string trayIdName;
std::string note;
std::string amsSn;
std::string slotId;
int amsId = 0;
int amsType = 0;
bool createNew = false;
};
struct AmsSyncParams {
std::string devId;
std::vector<AmsSyncItem> items;
};
struct PublishParams {
std::string project_name;
std::string project_3mf_file;
@@ -304,19 +404,34 @@ struct CertificateInformation {
std::string serial_number;
};
// The plug-in ABI generation a library speaks. Generations differ in by-value struct layouts and
// function signatures, so a call must go through the matching typedefs (see BBLPrinterAgent) -
// the wrong one corrupts the stack rather than failing cleanly.
enum class NetworkAbi {
Unsupported, // no generation in this build can call it - never dispatch through it
Legacy, // 01.10.01: PrintParams_Legacy; send_message/send_message_to_printer take no flag
V0203, // 02.03.00: PrintParams_0203; bind takes no dev_model
Current, // 02.08.01: the layouts and signatures this build declares directly
};
struct NetworkLibraryVersion {
const char* version;
const char* display_name;
const char* url_override;
bool is_latest;
const char* warning;
NetworkAbi abi;
};
// Every row names the generation that can call it, so a series can never be offered without a
// host-side ABI for it. Series with no generation - 02.01.01, 02.00.02 and older - must stay out;
// is_supported_network_version() is the gate that keeps them from loading.
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},
{"02.08.01", "02.08.01", nullptr, true, nullptr, NetworkAbi::Current},
{"02.03.00", "02.03.00", nullptr, false,
"An older plug-in series. Features that need newer plug-in support, such as print-failure "
"snapshots in the device error dialog, are unavailable.", NetworkAbi::V0203},
{BAMBU_NETWORK_AGENT_VERSION_LEGACY, BAMBU_NETWORK_AGENT_VERSION_LEGACY " (legacy)", nullptr, false, nullptr, NetworkAbi::Legacy},
};
static const size_t AVAILABLE_NETWORK_VERSIONS_COUNT = sizeof(AVAILABLE_NETWORK_VERSIONS) / sizeof(AVAILABLE_NETWORK_VERSIONS[0]);
@@ -329,6 +444,45 @@ inline const char* get_latest_network_version() {
return AVAILABLE_NETWORK_VERSIONS[0].version;
}
// The AA.BB.CC series of a modern version string - the plug-in's stored identity. The 4th
// component is only which build of the series happens to be installed and is read live from
// the loaded plug-in for display. Legacy keeps its exact string (the shim matches exactly).
inline std::string network_plugin_series(const std::string& version) {
if (version.empty() || version == BAMBU_NETWORK_AGENT_VERSION_LEGACY)
return version;
return version.size() >= 8 ? version.substr(0, 8) : version;
}
// Index of the whitelist entry that can load this version: an exact match, or a build of the same
// AA.BB.CC series as a non-legacy entry (the ABI is stable within a series, and the OTA sync only
// installs same-series updates). Legacy matches exactly only - a sibling build of that series
// would come through the modern layout. AVAILABLE_NETWORK_VERSIONS_COUNT when nothing matches.
inline size_t find_network_version_index(const std::string& version) {
const std::string series = network_plugin_series(version);
for (size_t i = 0; i < AVAILABLE_NETWORK_VERSIONS_COUNT; ++i) {
const std::string base = AVAILABLE_NETWORK_VERSIONS[i].version;
if (version == base)
return i;
if (base == BAMBU_NETWORK_AGENT_VERSION_LEGACY)
continue;
if (series == base)
return i;
}
return AVAILABLE_NETWORK_VERSIONS_COUNT;
}
// True when a whitelisted series can load the version through an ABI this build implements.
inline bool is_supported_network_version(const std::string& version) {
return find_network_version_index(version) < AVAILABLE_NETWORK_VERSIONS_COUNT;
}
// The generation to call a loaded library through. Unsupported for anything the load gate rejects,
// so a mislabelled library reaches no plug-in call instead of a layout it does not share.
inline NetworkAbi network_plugin_abi(const std::string& version) {
const size_t i = find_network_version_index(version);
return i < AVAILABLE_NETWORK_VERSIONS_COUNT ? AVAILABLE_NETWORK_VERSIONS[i].abi : NetworkAbi::Unsupported;
}
struct NetworkLibraryVersionInfo {
std::string version;
std::string base_version;
@@ -338,6 +492,10 @@ struct NetworkLibraryVersionInfo {
bool is_latest;
std::string warning;
bool is_discovered;
// Whether this is the build currently loaded in this session. Deliberately not
// "present on disk": switching versions leaves the previous library in place, so
// an on-disk test marks every version ever selected.
bool is_loaded = false;
static NetworkLibraryVersionInfo from_static(const NetworkLibraryVersion& v) {
return {
@@ -346,7 +504,7 @@ struct NetworkLibraryVersionInfo {
"",
v.display_name,
v.url_override ? v.url_override : "",
v.is_latest,
false, // assigned by get_all_available_versions() once the list is sorted
v.warning ? v.warning : "",
false
};
@@ -369,7 +527,22 @@ inline std::string extract_suffix(const std::string& full_version) {
return (pos == std::string::npos) ? "" : full_version.substr(pos + 1);
}
// True when the version is a pure dotted-numeric build (AA.BB.CC or AA.BB.CC.DD) whose identity
// collapses to its series - the managed/OTA build. Legacy and any custom-named build
// (02.08.01_custom, 02.08.01.52-dev) are NOT managed: they are genuinely distinct files kept
// under their own name and never folded into the series entry.
inline bool is_series_managed_version(const std::string& version) {
if (version.empty() || version == BAMBU_NETWORK_AGENT_VERSION_LEGACY)
return false;
return version.find_first_not_of("0123456789.") == std::string::npos;
}
// Selectable versions, newest first. Marks the entry matching the plug-in currently
// loaded in this session.
std::vector<NetworkLibraryVersionInfo> get_all_available_versions();
// Same list, resolving is_loaded against an explicitly supplied version rather than the
// live plug-in. Pass an empty string for "nothing loaded".
std::vector<NetworkLibraryVersionInfo> get_all_available_versions(const std::string& loaded_version);
struct NetworkLibraryLoadError {
bool has_error = false;
@@ -0,0 +1,274 @@
#include "CustomWidgetsPlugin.hpp"
#include "slic3r/GUI/Widgets/Button.hpp"
#include "slic3r/GUI/Widgets/CheckBox.hpp"
#include "slic3r/GUI/Widgets/TextInput.hpp"
#include "slic3r/GUI/Widgets/SwitchButton.hpp"
#include "slic3r/GUI/Widgets/ProgressBar.hpp"
#include "slic3r/GUI/Widgets/Label.hpp"
#include "slic3r/GUI/Widgets/LabeledStaticBox.hpp"
#include <wx/window.h>
#include <wx/tglbtn.h>
wxString CustomWidgetsPlugin::GetName() const
{
return "OrcaCustomWidgets";
}
bool CustomWidgetsPlugin::CanProvideProperties(wxClassInfo* info)
{
return info->IsKindOf(CLASSINFO(wxWindow));
}
wxVector<wxInspector::PropertyDef> CustomWidgetsPlugin::GetProperties(
wxInspector::InspectableObject& obj)
{
wxVector<wxInspector::PropertyDef> props;
wxWindow* win = obj.AsWindow();
if (!win) return props;
if (auto* btn = dynamic_cast<Button*>(win))
addButtonProps(btn, props);
if (auto* cb = dynamic_cast<CheckBox*>(win))
addCheckBoxProps(cb, props);
if (auto* ti = dynamic_cast<TextInput*>(win))
addTextInputProps(ti, props);
if (auto* sb = dynamic_cast<SwitchButton*>(win))
addSwitchButtonProps(sb, props);
if (auto* pb = dynamic_cast<ProgressBar*>(win))
addProgressBarProps(pb, props);
if (auto* lbl = dynamic_cast<Label*>(win))
addLabelProps(lbl, props);
if (auto* lsb = dynamic_cast<LabeledStaticBox*>(win))
addLabeledStaticBoxProps(lsb, props);
return props;
}
void CustomWidgetsPlugin::addButtonProps(Button* btn,
wxVector<wxInspector::PropertyDef>& props)
{
using namespace wxInspector;
wxVector<wxString> styleChoices;
styleChoices.push_back("Regular");
styleChoices.push_back("Confirm");
styleChoices.push_back("Alert");
styleChoices.push_back("Disabled");
auto styleToStr = [](ButtonStyle s) -> wxString {
switch (s) {
case ButtonStyle::Regular: return "Regular";
case ButtonStyle::Confirm: return "Confirm";
case ButtonStyle::Alert: return "Alert";
case ButtonStyle::Disabled: return "Disabled";
}
return "Regular";
};
props.push_back({"Button Style", "Orca Button", PropertyType::Choice,
styleToStr(btn->GetStyle()), false, styleChoices,
[btn, styleToStr]() { return styleToStr(btn->GetStyle()); },
[btn](const wxString& v) {
ButtonStyle s = ButtonStyle::Regular;
if (v == "Confirm") s = ButtonStyle::Confirm;
else if (v == "Alert") s = ButtonStyle::Alert;
else if (v == "Disabled") s = ButtonStyle::Disabled;
btn->SetStyle(s, btn->GetType());
return true;
}});
wxVector<wxString> typeChoices;
typeChoices.push_back("Compact");
typeChoices.push_back("Window");
typeChoices.push_back("Choice");
typeChoices.push_back("Parameter");
typeChoices.push_back("Icon");
typeChoices.push_back("Expanded");
auto typeToStr = [](ButtonType t) -> wxString {
switch (t) {
case ButtonType::Compact: return "Compact";
case ButtonType::Window: return "Window";
case ButtonType::Choice: return "Choice";
case ButtonType::Parameter: return "Parameter";
case ButtonType::Icon: return "Icon";
case ButtonType::Expanded: return "Expanded";
}
return "Compact";
};
props.push_back({"Button Type", "Orca Button", PropertyType::Choice,
typeToStr(btn->GetType()), false, typeChoices,
[btn, typeToStr]() { return typeToStr(btn->GetType()); },
[btn](const wxString& v) {
ButtonType t = ButtonType::Compact;
if (v == "Window") t = ButtonType::Window;
else if (v == "Choice") t = ButtonType::Choice;
else if (v == "Parameter") t = ButtonType::Parameter;
else if (v == "Icon") t = ButtonType::Icon;
else if (v == "Expanded") t = ButtonType::Expanded;
btn->SetStyle(btn->GetStyle(), t);
return true;
}});
props.push_back({"Selected", "Orca Button", PropertyType::Boolean,
btn->IsSelected() ? "true" : "false", false, {},
[btn]() { return btn->IsSelected() ? "true" : "false"; },
[btn](const wxString& v) {
btn->SetSelected(v == "true");
btn->Refresh();
return true;
}});
}
void CustomWidgetsPlugin::addCheckBoxProps(CheckBox* cb,
wxVector<wxInspector::PropertyDef>& props)
{
using namespace wxInspector;
props.push_back({"Half Checked", "Orca CheckBox", PropertyType::Boolean,
cb->IsHalfChecked() ? "true" : "false", false, {},
[cb]() { return cb->IsHalfChecked() ? "true" : "false"; },
[cb](const wxString& v) {
cb->SetHalfChecked(v == "true");
return true;
}});
}
void CustomWidgetsPlugin::addTextInputProps(TextInput* ti,
wxVector<wxInspector::PropertyDef>& props)
{
using namespace wxInspector;
props.push_back({"Label", "Orca TextInput", PropertyType::String,
ti->GetLabel(), false, {},
[ti]() { return ti->GetLabel(); },
[ti](const wxString& v) { ti->SetLabel(v); return true; }});
props.push_back({"Text Value", "Orca TextInput", PropertyType::String,
ti->GetTextCtrl()->GetValue(), false, {},
[ti]() { return ti->GetTextCtrl()->GetValue(); },
[ti](const wxString& v) { ti->GetTextCtrl()->SetValue(v); return true; }});
props.push_back({"Corner Radius", "Orca TextInput", PropertyType::Integer,
wxString::Format("%d", ti->GetCornerRadius()), false, {},
[ti]() { return wxString::Format("%d", ti->GetCornerRadius()); },
[ti](const wxString& v) {
long val;
if (!v.ToLong(&val)) return false;
ti->SetCornerRadius((double) val);
ti->Refresh();
return true;
}});
}
void CustomWidgetsPlugin::addSwitchButtonProps(SwitchButton* sb,
wxVector<wxInspector::PropertyDef>& props)
{
using namespace wxInspector;
props.push_back({"Value", "Orca SwitchButton", PropertyType::Boolean,
sb->GetValue() ? "true" : "false", false, {},
[sb]() { return sb->GetValue() ? "true" : "false"; },
[sb](const wxString& v) {
sb->SetValue(v == "true");
return true;
}});
}
void CustomWidgetsPlugin::addProgressBarProps(ProgressBar* pb,
wxVector<wxInspector::PropertyDef>& props)
{
using namespace wxInspector;
props.push_back({"Proportion", "Orca ProgressBar", PropertyType::String,
wxString::Format("%.2f", pb->m_proportion), false, {},
[pb]() { return wxString::Format("%.2f", pb->m_proportion); },
[pb](const wxString& v) {
double val;
if (wxSscanf(v, "%lf", &val) != 1) return false;
pb->m_proportion = val;
pb->Refresh();
return true;
}});
props.push_back({"Show Number", "Orca ProgressBar", PropertyType::Boolean,
pb->m_shownumber ? "true" : "false", false, {},
[pb]() { return pb->m_shownumber ? "true" : "false"; },
[pb](const wxString& v) {
pb->m_shownumber = (v == "true");
pb->Refresh();
return true;
}});
}
void CustomWidgetsPlugin::addLabelProps(Label* lbl,
wxVector<wxInspector::PropertyDef>& props)
{
using namespace wxInspector;
bool isHyperlink = (lbl->GetWindowStyleFlag() & LB_HYPERLINK) != 0;
props.push_back({"Is Hyperlink", "Orca Label", PropertyType::Boolean,
isHyperlink ? "true" : "false", true, {},
[lbl]() {
return (lbl->GetWindowStyleFlag() & LB_HYPERLINK) ? "true" : "false";
},
nullptr});
props.push_back({"Font Point Size", "Orca Label", PropertyType::ReadOnly,
wxString::Format("%d", lbl->GetFont().GetPointSize()), true, {},
[lbl]() {
return wxString::Format("%d", lbl->GetFont().GetPointSize());
},
nullptr});
}
void CustomWidgetsPlugin::addLabeledStaticBoxProps(LabeledStaticBox* lsb,
wxVector<wxInspector::PropertyDef>& props)
{
using namespace wxInspector;
props.push_back({"Corner Radius", "LabeledStaticBox", PropertyType::Integer,
wxString::Format("%d", lsb->GetCornerRadius()), false, {},
[lsb]() { return wxString::Format("%d", lsb->GetCornerRadius()); },
[lsb](const wxString& v) {
long val;
if (!v.ToLong(&val)) return false;
lsb->SetCornerRadius((int) val);
return true;
}});
props.push_back({"Border Width", "LabeledStaticBox", PropertyType::Integer,
wxString::Format("%d", lsb->GetBorderWidth()), false, {},
[lsb]() { return wxString::Format("%d", lsb->GetBorderWidth()); },
[lsb](const wxString& v) {
long val;
if (!v.ToLong(&val)) return false;
lsb->SetBorderWidth((int) val);
return true;
}});
// Border Color: display as hex string
wxColour bc = lsb->GetBorderColor().colorForStates(0);
props.push_back({"Border Color", "LabeledStaticBox", PropertyType::String,
bc.GetAsString(wxC2S_HTML_SYNTAX), false, {},
[lsb]() {
return lsb->GetBorderColor()
.colorForStates(0)
.GetAsString(wxC2S_HTML_SYNTAX);
},
[lsb](const wxString& v) {
wxColour c(v);
if (!c.IsOk()) return false;
lsb->SetBorderColor(StateColor(c));
return true;
}});
props.push_back({"Scale", "LabeledStaticBox", PropertyType::ReadOnly,
wxString::Format("%.2f", lsb->GetScale()), true, {},
[lsb]() { return wxString::Format("%.2f", lsb->GetScale()); },
nullptr});
}
@@ -0,0 +1,30 @@
#pragma once
#include <wx/inspector/plugin.h>
class CustomWidgetsPlugin : public wxInspector::wxInspectorPlugin
{
public:
wxString GetName() const override;
bool CanProvideProperties(wxClassInfo* info) override;
wxVector<wxInspector::PropertyDef> GetProperties(
wxInspector::InspectableObject& obj) override;
private:
void addButtonProps(class Button* btn,
wxVector<wxInspector::PropertyDef>& props);
void addCheckBoxProps(class CheckBox* cb,
wxVector<wxInspector::PropertyDef>& props);
void addTextInputProps(class TextInput* ti,
wxVector<wxInspector::PropertyDef>& props);
void addSwitchButtonProps(class SwitchButton* sb,
wxVector<wxInspector::PropertyDef>& props);
void addProgressBarProps(class ProgressBar* pb,
wxVector<wxInspector::PropertyDef>& props);
void addLabelProps(class Label* lbl,
wxVector<wxInspector::PropertyDef>& props);
void addLabeledStaticBoxProps(class LabeledStaticBox* lsb,
wxVector<wxInspector::PropertyDef>& props);
};
@@ -0,0 +1,82 @@
#include "DPIAwarePlugin.hpp"
#include "slic3r/GUI/GUI_Utils.hpp" // DPIFrame, DPIDialog, DPIAware<P>
#include <wx/window.h>
#include <wx/crt.h>
namespace {
template<typename T>
void addDPIProps(T* dpi, wxVector<wxInspector::PropertyDef>& props)
{
using namespace wxInspector;
props.push_back({"Scale Factor", "DPI Scaling", PropertyType::String,
wxString::Format("%.2f", dpi->scale_factor()), false, {},
[dpi]() { return wxString::Format("%.2f", dpi->scale_factor()); },
[dpi](const wxString& v) {
double val;
if (wxSscanf(v, "%lf", &val) != 1) return false;
dpi->set_scale_factor((float) val);
return true;
}});
props.push_back({"Prev Scale Factor", "DPI Scaling", PropertyType::String,
wxString::Format("%.2f", dpi->prev_scale_factor()), false, {},
[dpi]() { return wxString::Format("%.2f", dpi->prev_scale_factor()); },
[dpi](const wxString& v) {
double val;
if (wxSscanf(v, "%lf", &val) != 1) return false;
dpi->set_prev_scale_factor((float) val);
return true;
}});
props.push_back({"EM Unit", "DPI Scaling", PropertyType::Integer,
wxString::Format("%d", dpi->em_unit()), false, {},
[dpi]() { return wxString::Format("%d", dpi->em_unit()); },
[dpi](const wxString& v) {
long val;
if (!v.ToLong(&val)) return false;
dpi->set_em_unit((int) val);
return true;
}});
props.push_back({"Normal Font", "DPI Scaling", PropertyType::ReadOnly,
dpi->normal_font().GetNativeFontInfoDesc(), true, {},
[dpi]() { return dpi->normal_font().GetNativeFontInfoDesc(); },
nullptr});
props.push_back({"Force Rescale", "DPI Scaling", PropertyType::Boolean,
dpi->force_rescale() ? "true" : "false", true, {},
[dpi]() { return dpi->force_rescale() ? "true" : "false"; },
nullptr});
}
} // anonymous namespace
wxString DPIAwarePlugin::GetName() const
{
return "OrcaDPIAware";
}
bool DPIAwarePlugin::CanProvideProperties(wxClassInfo* info)
{
return info->IsKindOf(CLASSINFO(wxWindow));
}
wxVector<wxInspector::PropertyDef> DPIAwarePlugin::GetProperties(
wxInspector::InspectableObject& obj)
{
wxVector<wxInspector::PropertyDef> props;
wxWindow* win = obj.AsWindow();
if (!win) return props;
if (auto* frame = dynamic_cast<Slic3r::GUI::DPIFrame*>(win)) {
addDPIProps(frame, props);
} else if (auto* dlg = dynamic_cast<Slic3r::GUI::DPIDialog*>(win)) {
addDPIProps(dlg, props);
}
return props;
}
@@ -0,0 +1,14 @@
#pragma once
#include <wx/inspector/plugin.h>
class DPIAwarePlugin : public wxInspector::wxInspectorPlugin
{
public:
wxString GetName() const override;
bool CanProvideProperties(wxClassInfo* info) override;
wxVector<wxInspector::PropertyDef> GetProperties(
wxInspector::InspectableObject& obj) override;
};
@@ -0,0 +1,14 @@
#pragma once
#include "DPIAwarePlugin.hpp"
#include "CustomWidgetsPlugin.hpp"
#include <wx/inspector/inspector.h>
inline void RegisterOrcaInspectorPlugins()
{
static DPIAwarePlugin dpiaware;
static CustomWidgetsPlugin customWidgets;
wxInspector::RegisterPlugin(&dpiaware);
wxInspector::RegisterPlugin(&customWidgets);
}