Translate filament ids at the printer boundary

Orca content-addresses every system filament, Bambu's included, but a printer,
its AMS and its vendor's cloud know only that vendor's own catalog ids. The
printer agent now translates between the two: outbound MQTT and FTP traffic, the
AMS mapping sent with a print job, and the ids written into a 3mf bound for the
printer all leave in the printer's own ids, while status messages, loaded
projects and SD-card prints arrive in Orca's. An id with no mapping passes
through unchanged, and an agent whose printers already speak Orca's ids
translates nothing at all.

Bambu's map is generated from BambuStudio's own shipped bundle; a missing or
unreadable file leaves every lookup an identity rather than taking the app down.
The profile check validates the map's shape, and profile CI now runs on the paths
that can change it. docs/HLSD/filament_id.md records the places the map
deliberately does not reach.
This commit is contained in:
SoftFever
2026-09-06 20:53:11 +08:00
parent ec207e67a3
commit 4aa0e1d60b
23 changed files with 652 additions and 78 deletions
+115 -2
View File
@@ -1,11 +1,121 @@
#include "BBLPrinterAgent.hpp"
#include "BBLNetworkPlugin.hpp"
#include "NetworkAgentFactory.hpp"
#include "libslic3r/Utils.hpp"
#include <boost/log/trivial.hpp>
#include <boost/nowide/fstream.hpp>
#include <nlohmann/json.hpp>
using json = nlohmann::json;
#include <unordered_map>
namespace Slic3r {
namespace {
// Bambu's own catalog ids for every filament this app ships, Bambu's own included, since Orca
// content-addresses those too. Keyed both ways so each of the four translation entry points
// below is a single lookup. Loaded once per process, on first use. A missing or malformed file
// logs once and leaves both maps empty, so every translation degrades to identity. Same shape
// as DevFilaBlacklist::load_filaments_blacklist_config.
struct BambuFilamentIdMap { std::unordered_map<std::string, std::string> to_bambu, to_orca; };
const BambuFilamentIdMap& bambu_filament_id_map()
{
static const BambuFilamentIdMap map = [] {
BambuFilamentIdMap m;
const std::string path = resources_dir() + "/printers/bambu_filament_ids.json";
try {
boost::nowide::ifstream file(path);
if (!file.is_open()) {
BOOST_LOG_TRIVIAL(warning) << "Bambu filament id map not found, ids pass through untranslated: " << path;
return m;
}
json doc;
file >> doc;
for (const auto& [orca_filament_id, row] : doc.at("filaments").items()) {
const std::string bambu_id = row.at("bambu_id").get<std::string>();
m.to_bambu.emplace(orca_filament_id, bambu_id);
m.to_orca.emplace(bambu_id, orca_filament_id);
}
} catch (const std::exception& e) {
BOOST_LOG_TRIVIAL(error) << "Bambu filament id map unreadable, ids pass through untranslated: " << e.what();
m = {};
}
return m;
}();
return map;
}
// Rewrites every string under "tray_info_idx", "filament_id" or "filamentId", at any depth, in place.
void rewrite_filament_ids(json& j, const std::unordered_map<std::string, std::string>& map)
{
if (j.is_object()) {
for (auto& [key, value] : j.items()) {
if (value.is_string() && (key == "tray_info_idx" || key == "filament_id" || key == "filamentId")) {
auto it = map.find(value.get_ref<const std::string&>());
if (it != map.end())
value = it->second;
} else
rewrite_filament_ids(value, map);
}
} else if (j.is_array())
for (auto& element : j)
rewrite_filament_ids(element, map);
}
// Text that does not parse as JSON, or that mentions none of the id keys, comes back byte-identical.
std::string rewrite_filament_ids(std::string text, const std::unordered_map<std::string, std::string>& map)
{
if (map.empty() || (text.find("tray_info_idx") == std::string::npos && text.find("filament_id") == std::string::npos &&
text.find("filamentId") == std::string::npos))
return text; // nothing to map, skip the parse (moved, not copied)
try {
json j = json::parse(text);
rewrite_filament_ids(j, map);
return j.dump();
} catch (const std::exception&) {
return text; // not JSON: forward as received
}
}
// Wraps an inbound message callback so every Bambu id it delivers arrives already translated.
// A null fn is a deregistration (see GUI_App.cpp's shutdown phase 1 and NetworkAgent::apply_printer_callbacks
// clearing callbacks with {}), and must stay null rather than become a live wrapper around an empty target.
OnMessageFn to_orca_messages(OnMessageFn fn)
{
if (!fn)
return fn;
return [fn = std::move(fn)](std::string dev_id, std::string msg) { fn(std::move(dev_id), BBLPrinterAgent::to_orca_payload(std::move(msg))); };
}
} // namespace
std::string BBLPrinterAgent::to_orca_filament_id(const std::string& printer_filament_id) const
{
const auto& map = bambu_filament_id_map().to_orca;
auto it = map.find(printer_filament_id);
return it != map.end() ? it->second : printer_filament_id;
}
std::string BBLPrinterAgent::from_orca_filament_id(const std::string& orca_filament_id) const
{
const auto& map = bambu_filament_id_map().to_bambu;
auto it = map.find(orca_filament_id);
return it != map.end() ? it->second : orca_filament_id;
}
std::string BBLPrinterAgent::to_orca_payload(std::string json_text)
{
return rewrite_filament_ids(std::move(json_text), bambu_filament_id_map().to_orca);
}
std::string BBLPrinterAgent::from_orca_payload(std::string json_text)
{
return rewrite_filament_ids(std::move(json_text), bambu_filament_id_map().to_bambu);
}
BBLPrinterAgent::BBLPrinterAgent() = default;
BBLPrinterAgent::~BBLPrinterAgent() = default;
@@ -22,6 +132,7 @@ void BBLPrinterAgent::set_cloud_agent(std::shared_ptr<ICloudServiceAgent> cloud)
int BBLPrinterAgent::send_message(std::string dev_id, std::string json_str, int qos, int flag)
{
json_str = from_orca_payload(std::move(json_str));
auto& plugin = BBLNetworkPlugin::instance();
auto agent = plugin.get_agent();
auto func = plugin.get_send_message();
@@ -67,6 +178,7 @@ int BBLPrinterAgent::disconnect_printer()
int BBLPrinterAgent::send_message_to_printer(std::string dev_id, std::string json_str, int qos, int flag)
{
json_str = from_orca_payload(std::move(json_str));
auto& plugin = BBLNetworkPlugin::instance();
auto agent = plugin.get_agent();
auto func = plugin.get_send_message_to_printer();
@@ -321,6 +433,7 @@ int dispatch_start(CurrentFn func, PrintParams& params, const CallbackFns&... ca
auto agent = plugin.get_agent();
if (!func || !agent)
return -1;
params.ams_mapping_info = BBLPrinterAgent::from_orca_payload(std::move(params.ams_mapping_info));
switch (plugin.network_abi()) {
case NetworkAbi::Legacy:
return reinterpret_cast<LegacyFn>(func)(agent, BBLNetworkPlugin::as_legacy(params), callbacks...);
@@ -408,7 +521,7 @@ int BBLPrinterAgent::set_on_message_fn(OnMessageFn fn)
auto agent = plugin.get_agent();
auto func = plugin.get_set_on_message_fn();
if (func && agent) {
return func(agent, fn);
return func(agent, to_orca_messages(std::move(fn)));
}
return -1;
}
@@ -441,7 +554,7 @@ int BBLPrinterAgent::set_on_local_message_fn(OnMessageFn fn)
auto agent = plugin.get_agent();
auto func = plugin.get_set_on_local_message_fn();
if (func && agent) {
return func(agent, fn);
return func(agent, to_orca_messages(std::move(fn)));
}
return -1;
}
+14
View File
@@ -84,6 +84,20 @@ public:
int set_queue_on_main_fn(QueueOnMainFn fn) override;
FilamentSyncMode get_filament_sync_mode() const override;
// Bambu's own catalog ids. Orca content-addresses every system filament, Bambu's included;
// the printer, the AMS and Bambu's cloud know only Bambu's ids, so this agent translates at
// the boundary through resources/printers/bambu_filament_ids.json (generated by
// scripts/update_bambu_filament_ids.py). An id without a map row is returned as it is.
std::string to_orca_filament_id(const std::string& printer_filament_id) const override;
std::string from_orca_filament_id(const std::string& orca_filament_id) const override;
// Rewrite every string under "tray_info_idx", "filament_id" or "filamentId", at any depth,
// in a JSON document (an MQTT payload or PrintParams::ams_mapping_info). Text that does not
// parse, or contains none of the keys, is returned unchanged.
// Taken by value: most outbound traffic carries no filament id and is moved straight back out.
static std::string to_orca_payload(std::string json_text);
static std::string from_orca_payload(std::string json_text);
private:
std::shared_ptr<ICloudServiceAgent> m_cloud_agent;
};
+18 -8
View File
@@ -62,22 +62,29 @@ std::vector<std::string> not_support_auto_pa_cali_filaments = {
void get_default_k_n_value(const std::string &filament_id, float &k, float &n)
{
if (filament_id.compare("GFU01") == 0) {
// filament_id is our OF id; the literals below are the printer's own. An id the agent has
// no mapping for (e.g. a caller still on the old id) passes through unchanged.
auto* agent = wxGetApp().getAgent();
const std::string printer_filament_id = agent ? agent->from_orca_filament_id(filament_id) : filament_id;
if (printer_filament_id.compare("GFU01") == 0) {
/* TPU 95A */
k = 0.25;
n = 1.0;
} else if (filament_id.compare("GFU03") == 0) {
} else if (printer_filament_id.compare("GFU03") == 0) {
/* TPU 90A */
k = 0.35;
n = 1.0;
} else if (filament_id.compare("GFU04") == 0) {
} else if (printer_filament_id.compare("GFU04") == 0) {
/* TPU 85A */
k = 0.65;
n = 1.0;
} else if (filament_id.compare("GFG00") == 0 || filament_id.compare("GFG01") == 0 || filament_id.compare("GFG60") == 0 || filament_id.compare("GFL06") == 0 ||
filament_id.compare("GFL55") == 0 || filament_id.compare("GFG99") == 0 || filament_id.compare("GFG98") == 0 || filament_id.compare("GFG97") == 0 ||
filament_id.compare("GFG50") == 0 || filament_id.compare("GFU02") == 0 || filament_id.compare("GFU98") == 0 || filament_id.compare("GFS00") == 0 ||
filament_id.compare("GFS02") == 0) {
} else if (printer_filament_id.compare("GFG00") == 0 || printer_filament_id.compare("GFG01") == 0 ||
printer_filament_id.compare("GFG60") == 0 || printer_filament_id.compare("GFL06") == 0 ||
printer_filament_id.compare("GFL55") == 0 || printer_filament_id.compare("GFG99") == 0 ||
printer_filament_id.compare("GFG98") == 0 || printer_filament_id.compare("GFG97") == 0 ||
printer_filament_id.compare("GFG50") == 0 || printer_filament_id.compare("GFU02") == 0 ||
printer_filament_id.compare("GFU98") == 0 || printer_filament_id.compare("GFS00") == 0 ||
printer_filament_id.compare("GFS02") == 0) {
/* 0.04 filaments */
k = 0.04;
n = 1.0;
@@ -1393,7 +1400,10 @@ void CalibUtils::calib_retraction(const CalibInfo &calib_info, wxString &error_m
bool CalibUtils::is_support_auto_pa_cali(std::string filament_id)
{
auto iter = std::find(not_support_auto_pa_cali_filaments.begin(), not_support_auto_pa_cali_filaments.end(), filament_id);
// filament_id is our OF id; not_support_auto_pa_cali_filaments holds the printer's own ids.
auto* agent = wxGetApp().getAgent();
const std::string printer_filament_id = agent ? agent->from_orca_filament_id(filament_id) : filament_id;
auto iter = std::find(not_support_auto_pa_cali_filaments.begin(), not_support_auto_pa_cali_filaments.end(), printer_filament_id);
if (iter != not_support_auto_pa_cali_filaments.end()) {
return false;
}
+10
View File
@@ -290,6 +290,16 @@ public:
* Populates the MachineObject's DevFilaSystem with fetched filament data.
*/
virtual bool fetch_filament_info(std::string dev_id) { return false; }
/**
* Translate one filament id across the printer boundary.
*
* Orca content-addresses every system filament; a printer, its AMS and its vendor cloud
* know only that vendor's own catalog ids. An agent whose printers already speak Orca's
* ids leaves them alone, and so does an id with no mapping.
*/
virtual std::string to_orca_filament_id(const std::string& printer_filament_id) const { return printer_filament_id; }
virtual std::string from_orca_filament_id(const std::string& orca_filament_id) const { return orca_filament_id; }
};
} // namespace Slic3r
+14
View File
@@ -929,6 +929,20 @@ bool NetworkAgent::fetch_filament_info(std::string dev_id)
return false;
}
std::string NetworkAgent::to_orca_filament_id(const std::string& printer_filament_id) const
{
if (m_printer_agent)
return m_printer_agent->to_orca_filament_id(printer_filament_id);
return printer_filament_id;
}
std::string NetworkAgent::from_orca_filament_id(const std::string& orca_filament_id) const
{
if (m_printer_agent)
return m_printer_agent->from_orca_filament_id(orca_filament_id);
return orca_filament_id;
}
int NetworkAgent::request_bind_ticket(std::string* ticket)
{
if (m_printer_agent)
+2
View File
@@ -165,6 +165,8 @@ public:
int start_sdcard_print(PrintParams params, OnUpdateStatusFn update_fn, WasCancelledFn cancel_fn);
FilamentSyncMode get_filament_sync_mode() const;
bool fetch_filament_info(std::string dev_id);
std::string to_orca_filament_id(const std::string& printer_filament_id) const;
std::string from_orca_filament_id(const std::string& orca_filament_id) const;
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);