harktech: move CFS filament sync into CrealityPrintAgent

Per @SoftFever review on #13752, printer-specific filament sync logic
belongs in the agent rather than in Sidebar. This consolidates the
previously-duplicated code so all CFS-specific work lives in
CrealityPrintAgent.

Changes:
- New: CrealityPrintAgent::sync_filaments_into_ams_list() — static method
  that builds a CrealityPrint host from a printer_cfg, queries CFS slots,
  populates PresetBundle::filament_ams_list, and triggers sync_ams_list().
  GUI-free; returns a result struct (Status + counts + detail) so the
  caller decides what dialog to show.
- New: nested CFSAmsListResult struct describing the five possible
  outcomes (Success / NotCfsCapable / QueryFailed / EmptySlots / NoMatches).
- Removed: Sidebar::sync_filaments_from_creality_cfs() entirely (its body
  is now the agent method).
- Plater.hpp loses the declaration; Plater.cpp dispatches to the agent
  inline within sync_ams_list() and owns only the dialog + post-sync UI
  refresh (combo updates, layout, preset selection, persistence).

Two CFS-related entry points on the agent now coexist:
- fetch_filament_info() — agent-driven path; publishes via AmsTrayData
  and build_ams_payload(). Active when a MachineObject is bound (BBL
  concept, not currently created for Creality LAN hosts).
- sync_filaments_into_ams_list() — explicit-pull path used today by the
  Sidebar's "Sync filaments" button until the K-series MachineObject
  work catches up.

No user-visible behaviour change — same end-to-end flow, the data work
just lives in the agent now.
This commit is contained in:
grant0013
2026-05-20 11:16:35 +00:00
parent ee50931226
commit 37bbdefabc
4 changed files with 221 additions and 180 deletions

View File

@@ -3481,14 +3481,64 @@ void Sidebar::load_ams_list(MachineObject* obj)
void Sidebar::sync_ams_list(bool is_from_big_sync_btn)
{
// K-series Creality CFS hosts don't bind a MachineObject (that's a BBL
// cloud-connected-printer concept), so the dispatch below would early-return.
// Route to the explicit-pull CFS sync instead.
// K-series Creality CFS hosts don't bind a MachineObject (BBL cloud-printer
// concept), so the dispatch below would early-return. Delegate to
// CrealityPrintAgent which queries CFS state and populates filament_ams_list
// directly, then surface the result here as the appropriate dialog + refresh.
if (auto* preset_bundle = wxGetApp().preset_bundle) {
const auto* opt = preset_bundle->printers.get_edited_preset().config
.option<ConfigOptionEnum<PrintHostType>>("host_type");
if (opt && opt->value == htCrealityPrint) {
sync_filaments_from_creality_cfs();
const auto& printer_cfg = preset_bundle->printers.get_edited_preset().config;
const auto* host_type_opt = printer_cfg.option<ConfigOptionEnum<PrintHostType>>("host_type");
if (host_type_opt && host_type_opt->value == htCrealityPrint) {
wxBusyCursor cursor;
auto result = CrealityPrintAgent::sync_filaments_into_ams_list(printer_cfg, preset_bundle);
using R = CrealityPrintAgent::CFSAmsListResult;
switch (result.status) {
case R::NotCfsCapable: {
MessageDialog dlg(this,
_L("This printer is not a CFS-capable K-series board, or could not be reached at its configured IP."),
_L("Sync filaments from CFS"), wxOK);
dlg.ShowModal();
return;
}
case R::QueryFailed: {
MessageDialog dlg(this,
_L("Could not read CFS slot information from the printer.") + "\n" + from_u8(result.detail),
_L("Sync filaments from CFS"), wxOK);
dlg.ShowModal();
return;
}
case R::EmptySlots: {
MessageDialog dlg(this,
_L("No loaded filament slots detected on the CFS."),
_L("Sync filaments from CFS"), wxOK);
dlg.ShowModal();
return;
}
case R::NoMatches: {
MessageDialog dlg(this,
_L("CFS slots were read but no compatible filament presets resolved. "
"Enable the relevant filament profiles via the Filaments tickbox in printer settings."),
_L("Sync filaments from CFS"), wxOK);
dlg.ShowModal();
return;
}
case R::Success:
break; // fall through to UI refresh below
}
// Mirror the BBL post-sync refresh sequence.
auto& filament_presets = preset_bundle->filament_presets;
wxGetApp().plater()->on_filament_count_change(result.applied_filament_count);
for (auto& c : p->combos_filament)
c->update();
update_filaments_area_height();
Layout();
wxGetApp().get_tab(Preset::TYPE_FILAMENT)->select_preset(filament_presets[0]);
preset_bundle->export_selections(*wxGetApp().app_config);
update_dynamic_filament_list();
BOOST_LOG_TRIVIAL(warning)
<< "Sidebar::sync_ams_list: CFS sync applied " << result.applied_filament_count << " slot(s)";
return;
}
}
@@ -3733,165 +3783,6 @@ void Sidebar::sync_ams_list(bool is_from_big_sync_btn)
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << "finish pop_finsish_sync_ams_dialog";
}
// Explicit-pull filament sync for Creality K-series printers with CFS.
//
// We can't reuse the BBL path (sync_ams_list above) because it requires a
// MachineObject bound to the device, which is a cloud-connected-printer concept
// that doesn't apply to LAN Moonraker-style hosts like the K2. Instead, we query
// the CFS over the K2's port-9999 WebSocket directly (using the CrealityPrint
// helper added in upstream PR #13291), then route the slot data through the
// existing PresetBundle::sync_ams_list machinery so the filament combo widgets
// get the same correct rebuild as BBL printers.
//
// PresetBundle::sync_ams_list resolves each tray's filament_id against system
// bases (get_preset_base(f) == &f). Combined with the matcher's system-over-
// user tiebreaker, this lands us on the brand-specific shipped preset for the
// loaded spool (e.g. "Hyper PLA @Creality K2 0.4 nozzle") rather than a
// generic-inheriting user copy. We do not override post-sync — local user
// customisations should be applied by the user via the dropdown, not silently
// substituted in.
void Sidebar::sync_filaments_from_creality_cfs()
{
auto* preset_bundle = wxGetApp().preset_bundle;
if (!preset_bundle)
return;
const auto& printer_cfg = preset_bundle->printers.get_edited_preset().config;
const auto* host_type_opt = printer_cfg.option<ConfigOptionEnum<PrintHostType>>("host_type");
if (!host_type_opt || host_type_opt->value != htCrealityPrint) {
BOOST_LOG_TRIVIAL(warning)
<< "sync_filaments_from_creality_cfs: host_type is not crealityprint";
return;
}
wxBusyCursor cursor;
// Build a minimal config for the CrealityPrint helper. We can't pass
// printer_cfg directly because its option set is wider than CrealityPrint's
// constructor expects.
DynamicPrintConfig cfg;
cfg.set_key_value("print_host", new ConfigOptionString(printer_cfg.opt_string("print_host")));
cfg.set_key_value("print_host_webui", new ConfigOptionString(printer_cfg.opt_string("print_host_webui")));
cfg.set_key_value("printhost_cafile", new ConfigOptionString(printer_cfg.opt_string("printhost_cafile")));
cfg.set_key_value("printhost_port", new ConfigOptionString(printer_cfg.opt_string("printhost_port")));
cfg.set_key_value("printhost_apikey", new ConfigOptionString(printer_cfg.opt_string("printhost_apikey")));
cfg.set_key_value("printhost_ssl_ignore_revoke", new ConfigOptionBool(printer_cfg.opt_bool("printhost_ssl_ignore_revoke")));
CrealityPrint host(&cfg);
if (!host.supports_multi_color_print()) {
BOOST_LOG_TRIVIAL(warning)
<< "sync_filaments_from_creality_cfs: " << host.model_name()
<< " is not CFS-capable";
MessageDialog dlg(this,
_L("This printer is not a CFS-capable K-series board, or could not be reached at its configured IP."),
_L("Sync filaments from CFS"), wxOK);
dlg.ShowModal();
return;
}
BOOST_LOG_TRIVIAL(warning)
<< "sync_filaments_from_creality_cfs: querying CFS slots on " << host.model_name();
const std::string response = host.query_boxes_info();
std::vector<CrealityPrintAgent::CFSSlot> slots;
int box_count = 0;
std::string parse_err;
if (!CrealityPrintAgent::parse_cfs_response(response, slots, box_count, parse_err)) {
BOOST_LOG_TRIVIAL(warning)
<< "sync_filaments_from_creality_cfs: CFS query failed (" << parse_err << ")";
MessageDialog dlg(this,
_L("Could not read CFS slot information from the printer.") + "\n" + parse_err,
_L("Sync filaments from CFS"), wxOK);
dlg.ShowModal();
return;
}
if (slots.empty()) {
MessageDialog dlg(this,
_L("No loaded filament slots detected on the CFS."),
_L("Sync filaments from CFS"), wxOK);
dlg.ShowModal();
return;
}
BOOST_LOG_TRIVIAL(warning)
<< "sync_filaments_from_creality_cfs: " << box_count << " CFS box(es), "
<< slots.size() << " loaded slot(s)";
auto& filaments = preset_bundle->filaments;
// Build filament_ams_list — same shape as Sidebar::build_filament_ams_list produces
// for BBL printers, consumed by PresetBundle::sync_ams_list.
// Map key encodes (extruder, ams_id, slot_id) — main extruder uses the 0x10000 prefix.
constexpr int kMainExtruder = 0x10000;
std::map<int, DynamicPrintConfig> new_filament_ams_list;
for (const auto& s : slots) {
const std::string normalized_type = CrealityPrintAgent::normalize_filament_type(s.filament_type);
const std::string matched_id = CrealityPrintAgent::match_filament_preset(
filaments, s.vendor, s.brand_name, normalized_type);
DynamicPrintConfig tray_config;
tray_config.set_key_value("filament_id", new ConfigOptionStrings{matched_id});
tray_config.set_key_value("tag_uid", new ConfigOptionStrings{std::string()});
tray_config.set_key_value("ams_id", new ConfigOptionStrings{std::to_string(s.box_id)});
tray_config.set_key_value("slot_id", new ConfigOptionStrings{std::to_string(s.slot_id)});
tray_config.set_key_value("filament_type", new ConfigOptionStrings{normalized_type});
const std::string tray_name = std::string(1, char('A' + s.box_id)) + std::to_string(s.slot_id + 1);
tray_config.set_key_value("tray_name", new ConfigOptionStrings{tray_name});
tray_config.set_key_value("filament_colour", new ConfigOptionStrings{s.color_hex.empty() ? std::string("#FFFFFF") : s.color_hex});
tray_config.set_key_value("filament_multi_colour", new ConfigOptionStrings{});
tray_config.set_key_value("filament_colour_type", new ConfigOptionStrings{std::string("0")});
tray_config.set_key_value("filament_exist", new ConfigOptionBools{true});
tray_config.set_key_value("filament_slot_placeholder", new ConfigOptionBools{false});
tray_config.set_key_value("filament_is_support", new ConfigOptionBools{false});
const int slot_in_filament_array = s.box_id * 4 + s.slot_id;
const int map_key = kMainExtruder + slot_in_filament_array;
new_filament_ams_list.emplace(map_key, std::move(tray_config));
BOOST_LOG_TRIVIAL(warning)
<< "sync_filaments_from_creality_cfs: slot " << slot_in_filament_array
<< " spool {" << s.vendor << " " << s.brand_name << " (" << normalized_type
<< ")} -> filament_id=\"" << matched_id << "\"";
}
preset_bundle->filament_ams_list = new_filament_ams_list;
std::vector<std::pair<DynamicPrintConfig*, std::string>> unknowns;
std::map<int, AMSMapInfo> empty_maps;
MergeFilamentInfo merge_info;
auto n = preset_bundle->sync_ams_list(unknowns, false /*use_map*/, empty_maps,
false /*enable_append*/, merge_info, false /*color_only*/);
BOOST_LOG_TRIVIAL(warning)
<< "sync_filaments_from_creality_cfs: PresetBundle::sync_ams_list returned " << n;
if (n == 0) {
MessageDialog dlg(this,
_L("CFS slots were read but no compatible filament presets resolved. "
"Enable the relevant filament profiles via the Filaments tickbox in printer settings."),
_L("Sync filaments from CFS"), wxOK);
dlg.ShowModal();
return;
}
auto& filament_presets = preset_bundle->filament_presets;
// Mirror the BBL post-sync refresh sequence.
wxGetApp().plater()->on_filament_count_change(n);
for (auto& c : p->combos_filament)
c->update();
update_filaments_area_height();
Layout();
wxGetApp().get_tab(Preset::TYPE_FILAMENT)->select_preset(filament_presets[0]);
preset_bundle->export_selections(*wxGetApp().app_config);
update_dynamic_filament_list();
BOOST_LOG_TRIVIAL(warning)
<< "sync_filaments_from_creality_cfs: applied " << n << " slot(s)";
}
bool Sidebar::should_show_SEMM_buttons()
{

View File

@@ -193,11 +193,6 @@ public:
void load_ams_list(MachineObject* obj);
std::map<int, DynamicPrintConfig> build_filament_ams_list(MachineObject* obj);
void sync_ams_list(bool is_from_big_sync_btn = false);
// Explicit-pull filament sync for Creality K-series hosts (host_type=crealityprint).
// The BBL-style sync_ams_list path requires a bound MachineObject which is never
// created for LAN Moonraker hosts. sync_ams_list dispatches here when host_type
// is crealityprint, but the function is also usable on its own.
void sync_filaments_from_creality_cfs();
bool sync_extruder_list();
bool need_auto_sync_extruder_list_after_connect_priner(const MachineObject* obj);
void update_sync_status(const MachineObject* obj);

View File

@@ -318,4 +318,121 @@ bool CrealityPrintAgent::fetch_filament_info(std::string dev_id)
return true;
}
CrealityPrintAgent::CFSAmsListResult CrealityPrintAgent::sync_filaments_into_ams_list(
const DynamicPrintConfig& printer_cfg,
PresetBundle* bundle)
{
CFSAmsListResult result;
if (!bundle) {
result.status = CFSAmsListResult::QueryFailed;
result.detail = "no preset bundle";
return result;
}
// Build a minimal config for the CrealityPrint helper. We can't pass printer_cfg
// directly because its option set is wider than CrealityPrint's constructor expects.
DynamicPrintConfig cfg;
cfg.set_key_value("print_host", new ConfigOptionString(printer_cfg.opt_string("print_host")));
cfg.set_key_value("print_host_webui", new ConfigOptionString(printer_cfg.opt_string("print_host_webui")));
cfg.set_key_value("printhost_cafile", new ConfigOptionString(printer_cfg.opt_string("printhost_cafile")));
cfg.set_key_value("printhost_port", new ConfigOptionString(printer_cfg.opt_string("printhost_port")));
cfg.set_key_value("printhost_apikey", new ConfigOptionString(printer_cfg.opt_string("printhost_apikey")));
cfg.set_key_value("printhost_ssl_ignore_revoke", new ConfigOptionBool(printer_cfg.opt_bool("printhost_ssl_ignore_revoke")));
CrealityPrint host(&cfg);
if (!host.supports_multi_color_print()) {
BOOST_LOG_TRIVIAL(warning)
<< "CrealityPrintAgent::sync_filaments_into_ams_list: " << host.model_name()
<< " is not CFS-capable";
result.status = CFSAmsListResult::NotCfsCapable;
return result;
}
BOOST_LOG_TRIVIAL(warning)
<< "CrealityPrintAgent::sync_filaments_into_ams_list: querying CFS slots on "
<< host.model_name();
const std::string response = host.query_boxes_info();
std::vector<CFSSlot> slots;
int box_count = 0;
std::string parse_err;
if (!parse_cfs_response(response, slots, box_count, parse_err)) {
BOOST_LOG_TRIVIAL(warning)
<< "CrealityPrintAgent::sync_filaments_into_ams_list: CFS query failed ("
<< parse_err << ")";
result.status = CFSAmsListResult::QueryFailed;
result.detail = parse_err;
return result;
}
if (slots.empty()) {
result.status = CFSAmsListResult::EmptySlots;
return result;
}
BOOST_LOG_TRIVIAL(warning)
<< "CrealityPrintAgent::sync_filaments_into_ams_list: " << box_count
<< " CFS box(es), " << slots.size() << " loaded slot(s)";
result.box_count = box_count;
result.loaded_slot_count = static_cast<int>(slots.size());
auto& filaments = bundle->filaments;
// Build filament_ams_list — same shape as Sidebar::build_filament_ams_list produces
// for BBL printers, consumed by PresetBundle::sync_ams_list.
// Map key encodes (extruder, ams_id, slot_id) — main extruder uses the 0x10000 prefix.
constexpr int kMainExtruder = 0x10000;
std::map<int, DynamicPrintConfig> new_filament_ams_list;
for (const auto& s : slots) {
const std::string normalized_type = normalize_filament_type(s.filament_type);
const std::string matched_id = match_filament_preset(
filaments, s.vendor, s.brand_name, normalized_type);
DynamicPrintConfig tray_config;
tray_config.set_key_value("filament_id", new ConfigOptionStrings{matched_id});
tray_config.set_key_value("tag_uid", new ConfigOptionStrings{std::string()});
tray_config.set_key_value("ams_id", new ConfigOptionStrings{std::to_string(s.box_id)});
tray_config.set_key_value("slot_id", new ConfigOptionStrings{std::to_string(s.slot_id)});
tray_config.set_key_value("filament_type", new ConfigOptionStrings{normalized_type});
const std::string tray_name = std::string(1, char('A' + s.box_id)) + std::to_string(s.slot_id + 1);
tray_config.set_key_value("tray_name", new ConfigOptionStrings{tray_name});
tray_config.set_key_value("filament_colour", new ConfigOptionStrings{s.color_hex.empty() ? std::string("#FFFFFF") : s.color_hex});
tray_config.set_key_value("filament_multi_colour", new ConfigOptionStrings{});
tray_config.set_key_value("filament_colour_type", new ConfigOptionStrings{std::string("0")});
tray_config.set_key_value("filament_exist", new ConfigOptionBools{true});
tray_config.set_key_value("filament_slot_placeholder", new ConfigOptionBools{false});
tray_config.set_key_value("filament_is_support", new ConfigOptionBools{false});
const int slot_in_filament_array = s.box_id * 4 + s.slot_id;
const int map_key = kMainExtruder + slot_in_filament_array;
new_filament_ams_list.emplace(map_key, std::move(tray_config));
BOOST_LOG_TRIVIAL(warning)
<< "CrealityPrintAgent::sync_filaments_into_ams_list: slot "
<< slot_in_filament_array << " spool {" << s.vendor << " " << s.brand_name
<< " (" << normalized_type << ")} -> filament_id=\"" << matched_id << "\"";
}
bundle->filament_ams_list = new_filament_ams_list;
std::vector<std::pair<DynamicPrintConfig*, std::string>> unknowns;
std::map<int, AMSMapInfo> empty_maps;
MergeFilamentInfo merge_info;
const unsigned int n = bundle->sync_ams_list(unknowns, false /*use_map*/, empty_maps,
false /*enable_append*/, merge_info,
false /*color_only*/);
BOOST_LOG_TRIVIAL(warning)
<< "CrealityPrintAgent::sync_filaments_into_ams_list: PresetBundle::sync_ams_list returned " << n;
result.applied_filament_count = static_cast<int>(n);
if (n == 0) {
result.status = CFSAmsListResult::NoMatches;
return result;
}
result.status = CFSAmsListResult::Success;
return result;
}
} // namespace Slic3r

View File

@@ -9,23 +9,27 @@
namespace Slic3r {
class PresetCollection;
class PresetBundle;
class DynamicPrintConfig;
// Filament sync for Creality K-series printers with CFS.
//
// Inherits MoonrakerPrinterAgent for all communication / certificates / discovery /
// binding / print-job operations. Overrides fetch_filament_info() to query Creality's
// web-server (port 9999, websocket) for CFS slot state and publish it via the existing
// AmsTrayData / build_ams_payload() path so loaded CFS slots appear in Orca's filament
// UI like a Bambu AMS.
// binding / print-job operations. Owns two related entry points:
//
// * fetch_filament_info() — the agent-driven path; queries CFS and publishes via
// AmsTrayData / build_ams_payload(). Reached when a MachineObject is bound (BBL
// concept; not currently created for Creality LAN hosts).
//
// * sync_filaments_into_ams_list() — the explicit-pull path used by Sidebar's
// "Sync filaments" button. Same data flow up to the parse step, but writes
// directly into PresetBundle::filament_ams_list and triggers sync_ams_list()
// so the UI updates without a MachineObject. Returns a result struct so the
// caller can show appropriate dialogs without the agent touching wxWidgets.
//
// Model detection delegated to CrealityPrint::supports_multi_color_print() (PR #13291).
// For non-CFS K-series boards or when the WS query fails, falls back to the base
// MoonrakerPrinterAgent behaviour.
//
// The CFS-slot parser and preset matcher are also exposed as public statics so the
// Sidebar's manual "Sync from CFS" path can reuse them without going through the
// agent dispatch (which only fires when a MachineObject is bound — a BBL concept that
// doesn't apply to Moonraker-style hosts).
class CrealityPrintAgent final : public MoonrakerPrinterAgent
{
@@ -65,6 +69,40 @@ public:
const std::string& vendor,
const std::string& brand_name,
const std::string& base_type);
// Return status from sync_filaments_into_ams_list(). The agent code is GUI-free
// so the caller (Sidebar) decides what dialog (if any) to show for each case.
struct CFSAmsListResult
{
enum Status {
Success, // bundle->filament_ams_list populated and sync_ams_list applied
NotCfsCapable, // printer model has no CFS, or unreachable at configured IP
QueryFailed, // CFS query returned bad JSON / HTTP error (detail set)
EmptySlots, // CFS responded but reports no loaded slots
NoMatches // slots read but PresetBundle::sync_ams_list returned 0
};
Status status = Success;
std::string detail; // free-text error / extra info, may be empty
int box_count = 0; // number of CFS boxes reported by the printer
int loaded_slot_count = 0; // number of physically loaded slots seen
int applied_filament_count = 0; // result of PresetBundle::sync_ams_list
};
// Explicit-pull entry point for the Sidebar "Sync filaments" button.
//
// Builds a CrealityPrint host from `printer_cfg`, queries CFS slot state over its
// port-9999 WebSocket, populates `bundle->filament_ams_list`, and triggers
// `bundle->sync_ams_list()` so the standard preset-reconciliation path runs. Pure
// data / model work — does not touch wxWidgets; the caller is responsible for
// showing dialogs and refreshing the UI based on the returned Status.
//
// Single source of truth for CFS-to-filament sync (was previously duplicated in
// Sidebar). The agent-driven fetch_filament_info() path still publishes through
// AmsTrayData / build_ams_payload() and will become the primary path once Creality
// K-series hosts have a MachineObject created for them.
static CFSAmsListResult sync_filaments_into_ams_list(
const DynamicPrintConfig& printer_cfg,
PresetBundle* bundle);
};
} // namespace Slic3r