fix: make the AMS device UI work on non-Bambu printer agents

Resolve the filament list's printer model from the agent-reported model id or the selected printer profile, so the AMS Materials Setting dropdown and its Confirm button work on OrcaSonar. Third-party trays pre-fill and allow editing their temps, and the popup's initial selection no longer overwrites them.

Render explicitly-empty trays as "Empty" instead of "?" using a wire-derived DevAmsTray::is_empty, while keeping the slot editable.

Hide the flow-dynamics K/N controls for non-Bambu agents — tile, dialog, validation and sends — with Bambu behavior unchanged.

Tests cover model resolution, the K/N gate and empty-tray classification.
This commit is contained in:
Lam Wei Lun
2026-09-29 17:47:24 +08:00
parent 799fbee86c
commit 543ca95059
18 changed files with 376 additions and 74 deletions
+20 -6
View File
@@ -4273,9 +4273,21 @@ std::vector<std::vector<DynamicPrintConfig>> PresetBundle::get_extruder_filament
return filament_infos; return filament_infos;
} }
// ORCA TODO: currently, this function assumes the printer name follows the pattern of "<printer_model> <nozzle_diameter>", e.g. std::string PresetBundle::get_printer_model_display_name(const std::string &model_id) const
// printer_type: "Bambu Lab X2D", nozzle_diameter_str: "0.4 nozzle" => printer_name: "Bambu Lab X2D 0.4 nozzle". If the printer name does {
// not follow this pattern, the function may not work correctly. if (model_id.empty())
return {};
for (const auto &vendor_entry : vendors) {
for (const auto &model : vendor_entry.second.models) {
if (model.model_id == model_id)
return model.name;
}
}
return {};
}
// ORCA TODO: this assumes printer names follow "<printer_model> <nozzle_diameter>", e.g.
// "Bambu Lab X2D 0.4 nozzle". Other naming schemes may not resolve correctly.
std::set<std::string> PresetBundle::get_printer_names_by_printer_type_and_nozzle(const std::string &printer_type, std::string nozzle_diameter_str, bool system_only) std::set<std::string> PresetBundle::get_printer_names_by_printer_type_and_nozzle(const std::string &printer_type, std::string nozzle_diameter_str, bool system_only)
{ {
std::set<std::string> printer_names; std::set<std::string> printer_names;
@@ -4297,7 +4309,9 @@ std::set<std::string> PresetBundle::get_printer_names_by_printer_type_and_nozzle
if (printer_it->name.find(nozzle_diameter_str) != std::string::npos) printer_names.insert(printer_it->name); if (printer_it->name.find(nozzle_diameter_str) != std::string::npos) printer_names.insert(printer_it->name);
} }
assert(printer_names.size() == 1); // No match is normal for a connected machine the user has not installed; only an
// ambiguous match is a bug (the caller assumes one preset per model and nozzle).
assert(printer_names.size() <= 1);
for (auto& printer_name : printer_names) { for (auto& printer_name : printer_names) {
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << " " << __LINE__ << " printer name: " << printer_name; BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << " " << __LINE__ << " printer name: " << printer_name;
@@ -4310,8 +4324,8 @@ std::vector<Preset *> PresetBundle::get_filament_presets_for_machine(const std::
const std::string &nozzle_diameter_str, const std::string &nozzle_diameter_str,
bool include_user_presets) bool include_user_presets)
{ {
// Printer model plus nozzle diameter is expected to resolve to a single system printer preset; // Printer model plus nozzle diameter normally resolves to a single system printer preset.
// get_printer_names_by_printer_type_and_nozzle asserts as much in debug builds. // Zero matches is normal for a connected machine the user never installed.
const std::set<std::string> printer_names = get_printer_names_by_printer_type_and_nozzle(printer_type, nozzle_diameter_str); const std::set<std::string> printer_names = get_printer_names_by_printer_type_and_nozzle(printer_type, nozzle_diameter_str);
const Preset *printer = printer_names.empty() ? nullptr : printers.find_preset(*printer_names.begin()); const Preset *printer = printer_names.empty() ? nullptr : printers.find_preset(*printer_names.begin());
if (printer == nullptr) if (printer == nullptr)
+3
View File
@@ -391,6 +391,9 @@ public:
std::vector<std::vector<DynamicPrintConfig>> get_extruder_filament_info() const; std::vector<std::vector<DynamicPrintConfig>> get_extruder_filament_info() const;
// Resolve a vendor model id (an agent-reported machine identity) to the display name
// printer profiles use as printer_model. Empty when no loaded vendor declares it.
std::string get_printer_model_display_name(const std::string &model_id) const;
std::set<std::string> get_printer_names_by_printer_type_and_nozzle(const std::string &printer_type, std::string nozzle_diameter_str, bool system_only = true); std::set<std::string> get_printer_names_by_printer_type_and_nozzle(const std::string &printer_type, std::string nozzle_diameter_str, bool system_only = true);
// Orca: the root filament presets a connected machine can use, resolved with the rule the rest // Orca: the root filament presets a connected machine can use, resolved with the rule the rest
// of the app applies (is_compatible_with_printer): an empty compatible_printers means every // of the app applies (is_compatible_with_printer): an empty compatible_printers means every
+10 -1
View File
@@ -7,6 +7,7 @@
#include "slic3r/GUI/DeviceCore/DevExtruderSystem.h" #include "slic3r/GUI/DeviceCore/DevExtruderSystem.h"
#include "slic3r/GUI/DeviceCore/DevManager.h" #include "slic3r/GUI/DeviceCore/DevManager.h"
#include "slic3r/GUI/DeviceManager.hpp"
#include "slic3r/GUI/MsgDialog.hpp" #include "slic3r/GUI/MsgDialog.hpp"
@@ -1602,8 +1603,16 @@ int AMSDryCtrWin::update_filament_list(DevAms* dev_ams, MachineObject* obj)
stream << std::fixed << std::setprecision(1) << obj->GetExtderSystem()->GetNozzleDiameter(extruder_id); stream << std::fixed << std::setprecision(1) << obj->GetExtderSystem()->GetNozzleDiameter(extruder_id);
std::string nozzle_diameter_str = stream.str(); std::string nozzle_diameter_str = stream.str();
// The connected device's model may not resolve (OrcaSonar's is optional); the
// helper falls back to the selected profile so the list is never empty.
const std::string filament_printer_model = resolve_filament_printer_model(obj->printer_type, preset_bundle);
if (filament_printer_model.empty()) {
BOOST_LOG_TRIVIAL(warning) << __FUNCTION__ << " cannot resolve a printer model for the filament list";
return false;
}
for (Preset *filament_it : preset_bundle->get_filament_presets_for_machine( for (Preset *filament_it : preset_bundle->get_filament_presets_for_machine(
DevPrinterConfigUtil::get_printer_display_name(obj->printer_type), nozzle_diameter_str, obj->is_support_user_preset)) { filament_printer_model, nozzle_diameter_str, obj->is_support_user_preset)) {
if (!filament_id_set.insert(filament_it->filament_id).second) if (!filament_id_set.insert(filament_it->filament_id).second)
continue; continue;
const std::string filament_alias = filaments.get_preset_alias(*filament_it, true); const std::string filament_alias = filaments.get_preset_alias(*filament_it, true);
+78 -42
View File
@@ -431,6 +431,11 @@ void AMSMaterialsSetting::update_filament_editing(bool is_printing)
m_button_confirm->Show(true); m_button_confirm->Show(true);
} }
// A third-party tray owns its temp range; BBL RFID trays keep the read-only preset values.
const bool can_edit = !is_printing || obj->is_support_filament_setting_inprinting;
m_input_nozzle_min->Enable(m_is_third && can_edit);
m_input_nozzle_max->Enable(m_is_third && can_edit);
if (!m_is_third) { if (!m_is_third) {
m_tip_readonly->SetLabelText(wxEmptyString); m_tip_readonly->SetLabelText(wxEmptyString);
m_tip_readonly->Hide(); m_tip_readonly->Hide();
@@ -453,6 +458,8 @@ void AMSMaterialsSetting::update_filament_editing(bool is_printing)
if (m_view_only) { // Orca: view-only (2D laser/cut) — lock every edit control and hide apply/reset if (m_view_only) { // Orca: view-only (2D laser/cut) — lock every edit control and hide apply/reset
m_comboBox_filament->Enable(false); m_comboBox_filament->Enable(false);
m_comboBox_cali_result->Enable(false); m_comboBox_cali_result->Enable(false);
m_input_nozzle_min->Enable(false);
m_input_nozzle_max->Enable(false);
m_input_k_val->Enable(false); m_input_k_val->Enable(false);
m_input_n_val->Enable(false); m_input_n_val->Enable(false);
m_button_confirm->Hide(); m_button_confirm->Hide();
@@ -502,7 +509,7 @@ void AMSMaterialsSetting::on_select_reset(wxCommandEvent& event) {
} }
// set k / n value // set k / n value
if (obj->cali_version <= -1 && obj->get_printer_series() == PrinterSeries::SERIES_P1P) { if (obj->supports_extrusion_cali() && obj->cali_version <= -1 && obj->get_printer_series() == PrinterSeries::SERIES_P1P) {
// set extrusion cali ratio // set extrusion cali ratio
int cali_tray_id = ams_id * 4 + slot_id; int cali_tray_id = ams_id * 4 + slot_id;
@@ -523,7 +530,7 @@ void AMSMaterialsSetting::on_select_reset(wxCommandEvent& event) {
} }
obj->command_extrusion_cali_set(cali_tray_id, "", "", k, n); obj->command_extrusion_cali_set(cali_tray_id, "", "", k, n);
} }
else { else if (obj->supports_extrusion_cali()) {
PACalibIndexInfo select_index_info; PACalibIndexInfo select_index_info;
int tray_id = ams_id * 4 + slot_id; int tray_id = ams_id * 4 + slot_id;
if (is_virtual_tray()) { if (is_virtual_tray()) {
@@ -705,7 +712,7 @@ void AMSMaterialsSetting::on_select_ok(wxCommandEvent &event)
wxString k_text = m_input_k_val->GetTextCtrl()->GetValue(); wxString k_text = m_input_k_val->GetTextCtrl()->GetValue();
wxString n_text = m_input_n_val->GetTextCtrl()->GetValue(); wxString n_text = m_input_n_val->GetTextCtrl()->GetValue();
if (obj->cali_version <= -1 && (obj->get_printer_series() != PrinterSeries::SERIES_X1) && !ExtrusionCalibration::check_k_validation(k_text)) { if (obj->supports_extrusion_cali() && obj->cali_version <= -1 && (obj->get_printer_series() != PrinterSeries::SERIES_X1) && !ExtrusionCalibration::check_k_validation(k_text)) {
wxString k_tips = wxString::Format(_L("Please input a valid value (K in %.1f~%.1f)"), MIN_PA_K_VALUE, MAX_PA_K_VALUE); wxString k_tips = wxString::Format(_L("Please input a valid value (K in %.1f~%.1f)"), MIN_PA_K_VALUE, MAX_PA_K_VALUE);
wxString kn_tips = wxString::Format(_L("Please input a valid value (K in %.1f~%.1f, N in %.1f~%.1f)"), MIN_PA_K_VALUE, MAX_PA_K_VALUE, 0.6, 2.0); wxString kn_tips = wxString::Format(_L("Please input a valid value (K in %.1f~%.1f, N in %.1f~%.1f)"), MIN_PA_K_VALUE, MAX_PA_K_VALUE, 0.6, 2.0);
MessageDialog msg_dlg(nullptr, k_tips, wxEmptyString, wxICON_WARNING | wxOK); MessageDialog msg_dlg(nullptr, k_tips, wxEmptyString, wxICON_WARNING | wxOK);
@@ -735,7 +742,7 @@ void AMSMaterialsSetting::on_select_ok(wxCommandEvent &event)
vt_tray = VIRTUAL_TRAY_DEPUTY_ID; vt_tray = VIRTUAL_TRAY_DEPUTY_ID;
} }
if (obj->cali_version >= 0) { if (obj->supports_extrusion_cali() && obj->cali_version >= 0) {
PACalibIndexInfo select_index_info; PACalibIndexInfo select_index_info;
select_index_info.tray_id = vt_tray; select_index_info.tray_id = vt_tray;
select_index_info.ams_id = ams_id; select_index_info.ams_id = ams_id;
@@ -754,7 +761,7 @@ void AMSMaterialsSetting::on_select_ok(wxCommandEvent &event)
CalibUtils::select_PA_calib_result(select_index_info); CalibUtils::select_PA_calib_result(select_index_info);
} }
else { else if (obj->supports_extrusion_cali()) {
obj->command_extrusion_cali_set(vt_tray, "", "", k, n); obj->command_extrusion_cali_set(vt_tray, "", "", k, n);
} }
} }
@@ -776,7 +783,7 @@ void AMSMaterialsSetting::on_select_ok(wxCommandEvent &event)
; ;
} }
if (obj->cali_version >= 0) { if (obj->supports_extrusion_cali() && obj->cali_version >= 0) {
PACalibIndexInfo select_index_info; PACalibIndexInfo select_index_info;
select_index_info.tray_id = cali_tray_id; select_index_info.tray_id = cali_tray_id;
select_index_info.ams_id = ams_id; select_index_info.ams_id = ams_id;
@@ -795,7 +802,7 @@ void AMSMaterialsSetting::on_select_ok(wxCommandEvent &event)
CalibUtils::select_PA_calib_result(select_index_info); CalibUtils::select_PA_calib_result(select_index_info);
} }
else { else if (obj->supports_extrusion_cali()) {
obj->command_extrusion_cali_set(cali_tray_id, "", "", k, n); obj->command_extrusion_cali_set(cali_tray_id, "", "", k, n);
} }
} }
@@ -891,7 +898,12 @@ bool AMSMaterialsSetting::is_virtual_tray()
void AMSMaterialsSetting::update_widgets() void AMSMaterialsSetting::update_widgets()
{ {
if (obj && obj->get_printer_series() == PrinterSeries::SERIES_X1 && obj->cali_version <= -1) { if (obj && !obj->supports_extrusion_cali()) {
// No printer-side K/N records: do not offer them.
m_panel_normal->Show();
m_panel_kn->Hide();
}
else if (obj && obj->get_printer_series() == PrinterSeries::SERIES_X1 && obj->cali_version <= -1) {
// Low version firmware does not display k value // Low version firmware does not display k value
m_panel_kn->Hide(); m_panel_kn->Hide();
} }
@@ -952,6 +964,9 @@ void AMSMaterialsSetting::Popup(wxString filament, wxString sn, wxString temp_mi
m_input_k_val->GetTextCtrl()->SetValue(k); m_input_k_val->GetTextCtrl()->SetValue(k);
m_input_n_val->GetTextCtrl()->SetValue(n); m_input_n_val->GetTextCtrl()->SetValue(n);
// Tray values are re-supplied by every caller; clear so a previous popup cannot leak in.
m_input_nozzle_min->GetTextCtrl()->SetValue(wxEmptyString);
m_input_nozzle_max->GetTextCtrl()->SetValue(wxEmptyString);
wxArrayString filament_items; wxArrayString filament_items;
wxString bambu_filament_name; wxString bambu_filament_name;
@@ -975,11 +990,14 @@ void AMSMaterialsSetting::Popup(wxString filament, wxString sn, wxString temp_mi
} }
stream << std::fixed << std::setprecision(1) << machine_diameter; stream << std::fixed << std::setprecision(1) << machine_diameter;
std::string nozzle_diameter_str = stream.str(); std::string nozzle_diameter_str = stream.str();
// OrcaSonar's model id is optional, so the device model alone can resolve to nothing;
// the helper falls back to the selected profile so the dropdown is never empty.
const std::string filament_printer_model = resolve_filament_printer_model(obj->printer_type, preset_bundle);
if (preset_bundle) { if (preset_bundle && !filament_printer_model.empty()) {
BOOST_LOG_TRIVIAL(trace) << "system_preset_bundle filament number=" << preset_bundle->filaments.size(); BOOST_LOG_TRIVIAL(trace) << "system_preset_bundle filament number=" << preset_bundle->filaments.size();
for (Preset *filament_it : preset_bundle->get_filament_presets_for_machine( for (Preset *filament_it : preset_bundle->get_filament_presets_for_machine(
DevPrinterConfigUtil::get_printer_display_name(obj->printer_type), nozzle_diameter_str, obj->is_support_user_preset)) { filament_printer_model, nozzle_diameter_str, obj->is_support_user_preset)) {
if (!filament_id_set.insert(filament_it->filament_id).second) if (!filament_id_set.insert(filament_it->filament_id).second)
continue; continue;
const std::string alias = preset_bundle->filaments.get_preset_alias(*filament_it, true); const std::string alias = preset_bundle->filaments.get_preset_alias(*filament_it, true);
@@ -1045,6 +1063,12 @@ void AMSMaterialsSetting::Popup(wxString filament, wxString sn, wxString temp_mi
else { else {
m_comboBox_filament->Show(); m_comboBox_filament->Show();
m_readonly_filament->Hide(); m_readonly_filament->Hide();
// A printer-set tray carries its own temps; the preset selection only fills
// them when the tray has none.
if (!temp_min.IsEmpty() && temp_min != "0")
m_input_nozzle_min->GetTextCtrl()->SetValue(temp_min);
if (!temp_max.IsEmpty() && temp_max != "0")
m_input_nozzle_max->GetTextCtrl()->SetValue(temp_max);
} }
if (obj->cali_version >= 0) { if (obj->cali_version >= 0) {
@@ -1181,7 +1205,12 @@ void AMSMaterialsSetting::Popup(wxString filament, wxString sn, wxString temp_mi
} }
} }
if (filament_items.IsEmpty())
BOOST_LOG_TRIVIAL(warning) << "ams_materials_setting: no filament presets for printer model \""
<< filament_printer_model << "\" at nozzle " << nozzle_diameter_str;
m_comboBox_filament->Set(filament_items); m_comboBox_filament->Set(filament_items);
m_comboBox_from_printer = true;
m_comboBox_filament->SetSelection(selection_idx); m_comboBox_filament->SetSelection(selection_idx);
post_select_event(selection_idx); post_select_event(selection_idx);
@@ -1189,9 +1218,6 @@ void AMSMaterialsSetting::Popup(wxString filament, wxString sn, wxString temp_mi
m_comboBox_filament->SetValue(wxEmptyString); m_comboBox_filament->SetValue(wxEmptyString);
} }
// Set the flag whether to open the filament setting dialog from the device page
m_comboBox_filament->SetClientData(new int(1));
update(); update();
Layout(); Layout();
Fit(); Fit();
@@ -1220,49 +1246,52 @@ void AMSMaterialsSetting::on_select_cali_result(wxCommandEvent &evt)
void AMSMaterialsSetting::on_select_filament(wxCommandEvent &evt) void AMSMaterialsSetting::on_select_filament(wxCommandEvent &evt)
{ {
// Get the flag whether to open the filament setting dialog from the device page // True for the popup's own initial selection: the dialog pre-filled the tray's
int* from_printer = static_cast<int*>(m_comboBox_filament->GetClientData()); // temps, so the matching preset must not overwrite them.
const bool initial_printer_selection = m_comboBox_from_printer;
m_filament_type = ""; m_filament_type = "";
PresetBundle* preset_bundle = wxGetApp().preset_bundle; PresetBundle* preset_bundle = wxGetApp().preset_bundle;
std::string filament_printer_model;
if (preset_bundle) { if (preset_bundle) {
std::ostringstream stream; std::ostringstream stream;
if (obj) { if (obj) {
// Defensive: this dialog is opened only from StatusPanel (BBL-only) today, so the fallback fires // Use the selected profile's nozzle diameter until the connected device reports one.
// only during the brief BBL startup window before firmware reports nozzle info. Without this,
// the "0.0" lookup string returns an empty set and filament lookup yields no results.
float machine_diameter = obj->GetExtderSystem()->GetNozzleDiameter(0); float machine_diameter = obj->GetExtderSystem()->GetNozzleDiameter(0);
if (machine_diameter == 0.0f) { if (machine_diameter == 0.0f) {
const ConfigOption *opt = preset_bundle->printers.get_selected_preset().config.option("nozzle_diameter"); const ConfigOption *opt = preset_bundle->printers.get_selected_preset().config.option("nozzle_diameter");
if (opt) machine_diameter = static_cast<const ConfigOptionFloats *>(opt)->values[0]; if (opt) machine_diameter = static_cast<const ConfigOptionFloats *>(opt)->values[0];
} }
stream << std::fixed << std::setprecision(1) << machine_diameter; stream << std::fixed << std::setprecision(1) << machine_diameter;
filament_printer_model = resolve_filament_printer_model(obj->printer_type, preset_bundle);
} }
std::string nozzle_diameter_str = stream.str(); std::string nozzle_diameter_str = stream.str();
// Resolve the selection against the same list Popup() built the dropdown from, so the two // Resolve the selection against the same list Popup() built the dropdown from, so the two
// halves of the dialog cannot disagree about which filaments this machine can use. // halves of the dialog cannot disagree about which filaments this machine can use.
const std::string selected = m_comboBox_filament->GetValue().ToStdString(); const std::string selected = m_comboBox_filament->GetValue().ToStdString();
if (!selected.empty()) { if (!selected.empty() && !filament_printer_model.empty()) {
const std::string filament_id = map_filament_items[selected].filament_id; const std::string filament_id = map_filament_items[selected].filament_id;
for (Preset *it : preset_bundle->get_filament_presets_for_machine( for (Preset *it : preset_bundle->get_filament_presets_for_machine(
DevPrinterConfigUtil::get_printer_display_name(obj->printer_type), nozzle_diameter_str, obj->is_support_user_preset)) { filament_printer_model, nozzle_diameter_str, obj->is_support_user_preset)) {
if (it->filament_id != filament_id) if (it->filament_id != filament_id)
continue; continue;
// ) if nozzle_temperature_range is found if (!initial_printer_selection) {
ConfigOption* opt_min = it->config.option("nozzle_temperature_range_low"); // ) if nozzle_temperature_range is found
if (opt_min) { ConfigOption* opt_min = it->config.option("nozzle_temperature_range_low");
ConfigOptionInts* opt_min_ints = dynamic_cast<ConfigOptionInts*>(opt_min); if (opt_min) {
if (opt_min_ints) { ConfigOptionInts* opt_min_ints = dynamic_cast<ConfigOptionInts*>(opt_min);
wxString text_nozzle_temp_min = wxString::Format("%d", opt_min_ints->get_at(0)); if (opt_min_ints) {
m_input_nozzle_min->GetTextCtrl()->SetValue(text_nozzle_temp_min); wxString text_nozzle_temp_min = wxString::Format("%d", opt_min_ints->get_at(0));
m_input_nozzle_min->GetTextCtrl()->SetValue(text_nozzle_temp_min);
}
} }
} ConfigOption* opt_max = it->config.option("nozzle_temperature_range_high");
ConfigOption* opt_max = it->config.option("nozzle_temperature_range_high"); if (opt_max) {
if (opt_max) { ConfigOptionInts* opt_max_ints = dynamic_cast<ConfigOptionInts*>(opt_max);
ConfigOptionInts* opt_max_ints = dynamic_cast<ConfigOptionInts*>(opt_max); if (opt_max_ints) {
if (opt_max_ints) { wxString text_nozzle_temp_max = wxString::Format("%d", opt_max_ints->get_at(0));
wxString text_nozzle_temp_max = wxString::Format("%d", opt_max_ints->get_at(0)); m_input_nozzle_max->GetTextCtrl()->SetValue(text_nozzle_temp_max);
m_input_nozzle_max->GetTextCtrl()->SetValue(text_nozzle_temp_max); }
} }
} }
ConfigOption* opt_type = it->config.option("filament_type"); ConfigOption* opt_type = it->config.option("filament_type");
@@ -1301,13 +1330,16 @@ void AMSMaterialsSetting::on_select_filament(wxCommandEvent &evt)
m_button_confirm->Disable(); // ORCA No need to change style m_button_confirm->Disable(); // ORCA No need to change style
m_comboBox_cali_result->Clear(); m_comboBox_cali_result->Clear();
m_comboBox_cali_result->SetValue(wxEmptyString); m_comboBox_cali_result->SetValue(wxEmptyString);
m_input_k_val->GetTextCtrl()->SetValue(wxEmptyString); if (!initial_printer_selection) {
m_input_n_val->GetTextCtrl()->SetValue(wxEmptyString); m_input_k_val->GetTextCtrl()->SetValue(wxEmptyString);
m_comboBox_filament->SetClientData(new int(0)); m_input_n_val->GetTextCtrl()->SetValue(wxEmptyString);
}
m_comboBox_from_printer = false;
return; return;
} }
else { else {
m_button_confirm->Enable(true); // ORCA No need to change style if (!m_view_only)
m_button_confirm->Enable(true); // ORCA No need to change style
} }
//filament id //filament id
@@ -1331,6 +1363,9 @@ void AMSMaterialsSetting::on_select_filament(wxCommandEvent &evt)
} }
} }
if (!ams_filament_id.empty())
m_clr_picker->is_empty(false);
wxArrayString items; wxArrayString items;
m_pa_profile_items.clear(); m_pa_profile_items.clear();
m_comboBox_cali_result->SetValue(wxEmptyString); m_comboBox_cali_result->SetValue(wxEmptyString);
@@ -1386,7 +1421,7 @@ void AMSMaterialsSetting::on_select_filament(wxCommandEvent &evt)
m_comboBox_cali_result->Set(items); m_comboBox_cali_result->Set(items);
if (ams_id == VIRTUAL_TRAY_MAIN_ID || ams_id == VIRTUAL_TRAY_DEPUTY_ID) { if (ams_id == VIRTUAL_TRAY_MAIN_ID || ams_id == VIRTUAL_TRAY_DEPUTY_ID) {
if (from_printer && (*from_printer == 1)) { if (initial_printer_selection) {
for (auto slot : obj->vt_slot) { for (auto slot : obj->vt_slot) {
if (slot.id == std::to_string(ams_id)) if (slot.id == std::to_string(ams_id))
cali_select_idx = CalibUtils::get_selected_calib_idx(m_pa_profile_items, slot.cali_idx); cali_select_idx = CalibUtils::get_selected_calib_idx(m_pa_profile_items, slot.cali_idx);
@@ -1403,10 +1438,11 @@ void AMSMaterialsSetting::on_select_filament(wxCommandEvent &evt)
} }
} }
else { else {
if (from_printer && (*from_printer == 1)) { if (initial_printer_selection) {
DevAmsTray* selected_tray = this->obj->GetFilaSystem()->GetAmsTray(std::to_string(ams_id), std::to_string(slot_id)); DevAmsTray* selected_tray = this->obj->GetFilaSystem()->GetAmsTray(std::to_string(ams_id), std::to_string(slot_id));
if (!selected_tray) if (!selected_tray)
{ {
m_comboBox_from_printer = false;
return; return;
} }
@@ -1437,7 +1473,7 @@ void AMSMaterialsSetting::on_select_filament(wxCommandEvent &evt)
else { else {
if (!ams_filament_id.empty()) { if (!ams_filament_id.empty()) {
//m_input_k_val->GetTextCtrl()->SetValue("0.00"); //m_input_k_val->GetTextCtrl()->SetValue("0.00");
m_input_k_val->Enable(true); m_input_k_val->Enable(!m_view_only);
} }
else { else {
//m_input_k_val->GetTextCtrl()->SetValue("0.00"); //m_input_k_val->GetTextCtrl()->SetValue("0.00");
@@ -1445,7 +1481,7 @@ void AMSMaterialsSetting::on_select_filament(wxCommandEvent &evt)
} }
} }
m_comboBox_filament->SetClientData(new int(0)); m_comboBox_from_printer = false;
} }
void AMSMaterialsSetting::on_dpi_changed(const wxRect &suggested_rect) void AMSMaterialsSetting::on_dpi_changed(const wxRect &suggested_rect)
+4 -1
View File
@@ -122,7 +122,7 @@ public:
std::string ams_filament_id; std::string ams_filament_id;
std::string ams_setting_id; std::string ams_setting_id;
bool m_is_third; bool m_is_third = false;
// Orca: view-only mode (laser/cut). When set, the dialog is inspectable but every // Orca: view-only mode (laser/cut). When set, the dialog is inspectable but every
// editing control is disabled and no command is sent. // editing control is disabled and no command is sent.
bool m_view_only = false; bool m_view_only = false;
@@ -182,6 +182,9 @@ protected:
wxStaticText* m_n_param; wxStaticText* m_n_param;
TextInput* m_input_n_val; TextInput* m_input_n_val;
int m_filament_selection; int m_filament_selection;
// True while the popup's initial, printer-driven selection is being handled; it
// keeps that selection from overwriting the tray's own temps with preset temps.
bool m_comboBox_from_printer = false;
int m_pa_cali_select_id = 0; int m_pa_cali_select_id = 0;
+7 -1
View File
@@ -714,9 +714,15 @@ wxArrayString NewCalibrationHistoryDialog::get_all_filaments(const MachineObject
std::string nozzle_diameter_str = stream.str(); std::string nozzle_diameter_str = stream.str();
if (preset_bundle) { if (preset_bundle) {
// OrcaSonar's model id is optional; the helper falls back to the selected profile
// so the list is never empty.
const std::string filament_printer_model = resolve_filament_printer_model(obj->printer_type, preset_bundle);
if (filament_printer_model.empty())
return filament_items;
BOOST_LOG_TRIVIAL(trace) << "system_preset_bundle filament number=" << preset_bundle->filaments.size(); BOOST_LOG_TRIVIAL(trace) << "system_preset_bundle filament number=" << preset_bundle->filaments.size();
for (Preset *filament_it : preset_bundle->get_filament_presets_for_machine( for (Preset *filament_it : preset_bundle->get_filament_presets_for_machine(
DevPrinterConfigUtil::get_printer_display_name(obj->printer_type), nozzle_diameter_str, obj->is_support_user_preset)) { filament_printer_model, nozzle_diameter_str, obj->is_support_user_preset)) {
if (!filament_id_set.insert(filament_it->filament_id).second) if (!filament_id_set.insert(filament_it->filament_id).second)
continue; continue;
const std::string alias = preset_bundle->filaments.get_preset_alias(*filament_it, true); const std::string alias = preset_bundle->filaments.get_preset_alias(*filament_it, true);
+14 -1
View File
@@ -11,6 +11,8 @@
#include "DevUtil.h" #include "DevUtil.h"
#include "DevUtilBackend.h" #include "DevUtilBackend.h"
#include <algorithm>
using namespace nlohmann; using namespace nlohmann;
namespace Slic3r { namespace Slic3r {
@@ -42,6 +44,15 @@ void DevAmsTray::UpdateColorFromStr(const std::string& color)
} }
} }
void DevAmsTray::UpdateEmptyState(bool material_fields_present)
{
const auto is_zero_or_empty = [](const std::string& value) {
return value.empty() || std::all_of(value.begin(), value.end(), [](char c) { return c == '0'; });
};
is_empty = material_fields_present && setting_id.empty() && m_fila_type.empty() &&
is_zero_or_empty(color) && is_zero_or_empty(tag_uid);
}
void DevAmsTray::reset() void DevAmsTray::reset()
{ {
tag_uid = ""; tag_uid = "";
@@ -63,6 +74,7 @@ void DevAmsTray::reset()
k = 0.0f; k = 0.0f;
n = 0.0f; n = 0.0f;
is_bbl = false; is_bbl = false;
is_empty = false;
hold_count = 0; hold_count = 0;
remain = 0; remain = 0;
} }
@@ -662,7 +674,7 @@ void DevFilaSystemParser::ParseV1_0(const json& jj, MachineObject* obj, DevFilaS
//std::string type = (*tray_it)["tray_type"].get<std::string>(); //std::string type = (*tray_it)["tray_type"].get<std::string>();
std::string type = MachineObject::setting_id_to_type(curr_tray->setting_id, (*tray_it)["tray_type"].get<std::string>()); std::string type = MachineObject::setting_id_to_type(curr_tray->setting_id, (*tray_it)["tray_type"].get<std::string>());
// curr_tray->setting_id is our OF id; GFS00/GFS01 are the printer's own support-filament ids. // curr_tray->setting_id is our OF id; GFS00/GFS01 are the printer's own support-filament ids.
auto* agent = GUI::wxGetApp().getAgent(); auto* agent = wxTheApp != nullptr ? GUI::wxGetApp().getAgent() : nullptr;
const std::string printer_filament_id = agent ? agent->from_orca_filament_id(curr_tray->setting_id) : curr_tray->setting_id; const std::string printer_filament_id = agent ? agent->from_orca_filament_id(curr_tray->setting_id) : curr_tray->setting_id;
if (printer_filament_id == "GFS00") if (printer_filament_id == "GFS00")
{ {
@@ -762,6 +774,7 @@ void DevFilaSystemParser::ParseV1_0(const json& jj, MachineObject* obj, DevFilaS
{ {
curr_tray->remain = -1; curr_tray->remain = -1;
} }
curr_tray->UpdateEmptyState(tray_it->contains("tray_info_idx") && tray_it->contains("tray_type"));
// The tray objects are reused across status updates. Reset this // The tray objects are reused across status updates. Reset this
// state when a previously empty slot receives a filament again. // state when a previously empty slot receives a filament again.
curr_tray->is_slot_placeholder = tray_it->contains("tray_slot_placeholder"); curr_tray->is_slot_placeholder = tray_it->contains("tray_slot_placeholder");
+4 -1
View File
@@ -82,6 +82,7 @@ public:
wxColour wx_color; wxColour wx_color;
bool is_bbl; bool is_bbl;
bool is_exists = false; bool is_exists = false;
bool is_empty = false; // Explicitly reported as having no filament.
bool is_slot_placeholder = false; // Orca: True for empty tray slots from pull-mode agents bool is_slot_placeholder = false; // Orca: True for empty tray slots from pull-mode agents
int hold_count = 0; int hold_count = 0;
int remain = 0; // filament remain: 0 ~ 100 int remain = 0; // filament remain: 0 ~ 100
@@ -90,13 +91,15 @@ public:
// operators // operators
bool operator==(DevAmsTray const& o) const bool operator==(DevAmsTray const& o) const
{ {
return id == o.id && m_fila_type == o.m_fila_type && filament_setting_id == o.filament_setting_id && color == o.color; return id == o.id && m_fila_type == o.m_fila_type && filament_setting_id == o.filament_setting_id &&
color == o.color && is_empty == o.is_empty;
} }
bool operator!=(DevAmsTray const& o) const { return !operator==(o); } bool operator!=(DevAmsTray const& o) const { return !operator==(o); }
// setters // setters
void reset(); void reset();
void UpdateColorFromStr(const std::string& color); void UpdateColorFromStr(const std::string& color);
void UpdateEmptyState(bool material_fields_present);
void set_hold_count() { hold_count = HOLD_COUNT_MAX; } void set_hold_count() { hold_count = HOLD_COUNT_MAX; }
// getter // getter
+41 -1
View File
@@ -374,6 +374,32 @@ NozzleVolumeType convert_to_nozzle_type(const std::string &str)
return res; return res;
} }
std::string resolve_filament_printer_model(const std::string& printer_type, PresetBundle* preset_bundle)
{
if (!preset_bundle)
return {};
// Devices with a shipped printer config (Bambu) resolve directly.
if (const std::string display_name = DevPrinterConfigUtil::get_printer_display_name(printer_type); !display_name.empty())
return display_name;
// A vendor model id the device reported (SSDP modelNumber, manual binding).
if (!printer_type.empty()) {
if (const std::string model_name = preset_bundle->get_printer_model_display_name(printer_type); !model_name.empty())
return model_name;
}
// OrcaSonar's model id is optional; a generic device resolves against the selected profile.
if (const ConfigOptionString* model = preset_bundle->printers.get_selected_preset().config.opt<ConfigOptionString>("printer_model");
model && !model->value.empty()) {
BOOST_LOG_TRIVIAL(info) << "resolve_filament_printer_model: device type \"" << printer_type
<< "\" has no installed model; using the selected profile \"" << model->value << "\"";
return model->value;
}
return {};
}
wxString MachineObject::get_printer_type_display_str() const wxString MachineObject::get_printer_type_display_str() const
{ {
std::string display_name = DevPrinterConfigUtil::get_printer_display_name(printer_type); std::string display_name = DevPrinterConfigUtil::get_printer_display_name(printer_type);
@@ -1759,6 +1785,12 @@ bool MachineObject::orca_ams_command_supported(const char* command) const
return command != nullptr && supported_commands.count(command) != 0; return command != nullptr && supported_commands.count(command) != 0;
} }
bool MachineObject::supports_extrusion_cali() const
{
// Devices with no agent id predate the agent split and keep the Bambu path.
return printer_agent_id.empty() || printer_agent_id == BBL_PRINTER_AGENT_ID;
}
int MachineObject::command_ams_filament_settings(int ams_id, int slot_id, std::string filament_id, std::string setting_id, std::string tray_color, std::string tray_type, int nozzle_temp_min, int nozzle_temp_max) int MachineObject::command_ams_filament_settings(int ams_id, int slot_id, std::string filament_id, std::string setting_id, std::string tray_color, std::string tray_type, int nozzle_temp_min, int nozzle_temp_max)
{ {
// OrcaSonar: writing slot metadata is gated on the filament_slots capability. // OrcaSonar: writing slot metadata is gated on the filament_slots capability.
@@ -2850,6 +2882,7 @@ int MachineObject::local_publish_json(std::string json_str, int qos, int flag)
std::string MachineObject::setting_id_to_type(std::string setting_id, std::string tray_type) std::string MachineObject::setting_id_to_type(std::string setting_id, std::string tray_type)
{ {
std::string type; std::string type;
if (wxTheApp == nullptr) return tray_type;
PresetBundle* preset_bundle = GUI::wxGetApp().preset_bundle; PresetBundle* preset_bundle = GUI::wxGetApp().preset_bundle;
if (preset_bundle) { if (preset_bundle) {
for (auto it = preset_bundle->filaments.begin(); it != preset_bundle->filaments.end(); it++) { for (auto it = preset_bundle->filaments.begin(); it != preset_bundle->filaments.end(); it++) {
@@ -4186,6 +4219,9 @@ int MachineObject::parse_json(std::string tunnel, std::string payload, bool key_
vt_slot[0].setting_id = jj["tray_info_idx"].get<std::string>(); vt_slot[0].setting_id = jj["tray_info_idx"].get<std::string>();
//vt_tray.type = jj["tray_type"].get<std::string>(); //vt_tray.type = jj["tray_type"].get<std::string>();
vt_slot[0].m_fila_type = setting_id_to_type(vt_slot[0].setting_id, jj["tray_type"].get<std::string>()); vt_slot[0].m_fila_type = setting_id_to_type(vt_slot[0].setting_id, jj["tray_type"].get<std::string>());
// The ack carries the whole slot; re-derive empty so the panel flips off
// "Empty" without waiting out the hold.
vt_slot[0].UpdateEmptyState(true);
// delay update // delay update
vt_slot[0].set_hold_count(); vt_slot[0].set_hold_count();
} else { } else {
@@ -4211,6 +4247,9 @@ int MachineObject::parse_json(std::string tunnel, std::string payload, bool key_
tray_it->second->setting_id = jj["tray_info_idx"].get<std::string>(); tray_it->second->setting_id = jj["tray_info_idx"].get<std::string>();
tray_it->second->m_fila_type = setting_id_to_type(tray_it->second->setting_id, jj["tray_type"].get<std::string>()); tray_it->second->m_fila_type = setting_id_to_type(tray_it->second->setting_id, jj["tray_type"].get<std::string>());
// The ack carries the whole slot; re-derive empty so the panel flips off
// "Empty" without waiting out the hold.
tray_it->second->UpdateEmptyState(true);
// delay update // delay update
tray_it->second->set_hold_count(); tray_it->second->set_hold_count();
} else { } else {
@@ -5148,7 +5187,7 @@ DevAmsTray MachineObject::parse_vt_tray(json vtray)
//std::string type = vtray["tray_type"].get<std::string>(); //std::string type = vtray["tray_type"].get<std::string>();
std::string type = setting_id_to_type(vt_tray.setting_id, vtray["tray_type"].get<std::string>()); std::string type = setting_id_to_type(vt_tray.setting_id, vtray["tray_type"].get<std::string>());
// vt_tray.setting_id is our OF id (translated on the way in); the two support ids below are the printer's own. // vt_tray.setting_id is our OF id (translated on the way in); the two support ids below are the printer's own.
auto* agent = GUI::wxGetApp().getAgent(); auto* agent = wxTheApp != nullptr ? GUI::wxGetApp().getAgent() : nullptr;
const std::string printer_filament_id = agent ? agent->from_orca_filament_id(vt_tray.setting_id) : vt_tray.setting_id; const std::string printer_filament_id = agent ? agent->from_orca_filament_id(vt_tray.setting_id) : vt_tray.setting_id;
if (printer_filament_id == "GFS00") { if (printer_filament_id == "GFS00") {
vt_tray.m_fila_type = "PLA-S"; vt_tray.m_fila_type = "PLA-S";
@@ -5243,6 +5282,7 @@ DevAmsTray MachineObject::parse_vt_tray(json vtray)
else { else {
vt_tray.remain = -1; vt_tray.remain = -1;
} }
vt_tray.UpdateEmptyState(vtray.contains("tray_info_idx") && vtray.contains("tray_type"));
} }
return vt_tray; return vt_tray;
+10
View File
@@ -67,6 +67,7 @@ class DeviceErrorDialog; // Previous definitions
class NetworkAgent; class NetworkAgent;
// Orca: ManualPaCaliMethod now provided by DeviceCore/DevCalib.h (enum class) // Orca: ManualPaCaliMethod now provided by DeviceCore/DevCalib.h (enum class)
class PresetBundle;
#define UpgradeNoError 0 #define UpgradeNoError 0
#define UpgradeDownloadFailed -1 #define UpgradeDownloadFailed -1
@@ -102,6 +103,11 @@ struct DevPrintTaskRatingInfo;
// given nozzle diameter (mm), bucketed per nozzle size to mirror the printer firmware. // given nozzle diameter (mm), bucketed per nozzle size to mirror the printer firmware.
bool is_stringing_prone_filament(const std::string& filament_id, float nozzle_diameter); bool is_stringing_prone_filament(const std::string& filament_id, float nozzle_diameter);
// The printer model filament presets are matched against for a connected machine. OrcaSonar's
// model id is optional, so a device with no installed vendor model falls back to the selected
// printer profile rather than resolving to no filament list at all.
std::string resolve_filament_printer_model(const std::string& printer_type, PresetBundle* preset_bundle);
class MachineObject class MachineObject
{ {
private: private:
@@ -799,6 +805,10 @@ public:
// always allow; an Orca device must have advertised the command (unknown // always allow; an Orca device must have advertised the command (unknown
// capability is not support). // capability is not support).
bool orca_ams_command_supported(const char* command) const; bool orca_ams_command_supported(const char* command) const;
// The per-tray flow-dynamics K/N records are a Bambu firmware feature. Other
// agents have no printer-side calibration to read or write, so the AMS UI must
// not offer K/N for them.
bool supports_extrusion_cali() const;
// ams controls // ams controls
//int command_ams_switch(int tray_index, int old_temp = 210, int new_temp = 210); //int command_ams_switch(int tray_index, int old_temp = 210, int new_temp = 210);
int command_ams_change_filament(bool load, std::string ams_id, std::string slot_id, int old_temp = 210, int new_temp = 210, std::optional<int> extruder_id = std::nullopt); int command_ams_change_filament(bool load, std::string ams_id, std::string slot_id, int old_temp = 210, int new_temp = 210, std::optional<int> extruder_id = std::nullopt);
@@ -943,7 +943,11 @@ int ReselectMachineDialog::CaculateSwitcherDistribution(MachineObject* obj, cons
const auto& can = ams.cans[j]; const auto& can = ams.cans[j];
auto id = getTrayID(obj, ams.ams_id, can.can_id); auto id = getTrayID(obj, ams.ams_id, can.can_id);
auto material = can.material_name; auto material = can.material_name;
if (can.material_state == AMSCanType::AMS_CAN_TYPE_THIRDBRAND || if (can.is_empty || can.material_state == AMSCanType::AMS_CAN_TYPE_EMPTY)
{
material = L("Empty");
}
else if (can.material_state == AMSCanType::AMS_CAN_TYPE_THIRDBRAND ||
can.material_state == AMSCanType::AMS_CAN_TYPE_BRAND || can.material_state == AMSCanType::AMS_CAN_TYPE_BRAND ||
can.material_state == AMSCanType::AMS_CAN_TYPE_VIRTUAL) can.material_state == AMSCanType::AMS_CAN_TYPE_VIRTUAL)
{ {
@@ -952,10 +956,6 @@ int ReselectMachineDialog::CaculateSwitcherDistribution(MachineObject* obj, cons
material = L("?"); material = L("?");
} }
} }
if (can.material_state == AMSCanType::AMS_CAN_TYPE_EMPTY)
{
material = "Empty";
}
auto itOK = std::find_if(posOK.begin(), posOK.end(), [&](const trayHelper& tray){ auto itOK = std::find_if(posOK.begin(), posOK.end(), [&](const trayHelper& tray){
auto amsID = std::get<0>(tray); auto amsID = std::get<0>(tray);
+6 -4
View File
@@ -4603,12 +4603,14 @@ void StatusPanel::on_filament_edit(wxCommandEvent &event)
} }
m_filament_setting_dlg->m_is_third = !DevFilaSystem::IsBBL_Filament(tray->tag_uid); m_filament_setting_dlg->m_is_third = !DevFilaSystem::IsBBL_Filament(tray->tag_uid);
// The tray's own temps pre-fill the dialog for every tray type; a BBL tray
// additionally carries the SN and brand.
temp_max = tray->nozzle_temp_max;
temp_min = tray->nozzle_temp_min;
if (!m_filament_setting_dlg->m_is_third) if (!m_filament_setting_dlg->m_is_third)
{ {
sn_number = tray->uuid; sn_number = tray->uuid;
filament = tray->sub_brands; filament = tray->sub_brands;
temp_max = tray->nozzle_temp_max;
temp_min = tray->nozzle_temp_min;
} }
} }
@@ -4669,11 +4671,11 @@ void StatusPanel::on_ext_spool_edit(wxCommandEvent &event)
} }
m_filament_setting_dlg->m_is_third = !DevFilaSystem::IsBBL_Filament(obj->vt_slot[nozzle_index].tag_uid); m_filament_setting_dlg->m_is_third = !DevFilaSystem::IsBBL_Filament(obj->vt_slot[nozzle_index].tag_uid);
temp_max = obj->vt_slot[nozzle_index].nozzle_temp_max;
temp_min = obj->vt_slot[nozzle_index].nozzle_temp_min;
if (!m_filament_setting_dlg->m_is_third) { if (!m_filament_setting_dlg->m_is_third) {
sn_number = obj->vt_slot[nozzle_index].uuid; sn_number = obj->vt_slot[nozzle_index].uuid;
filament = obj->vt_slot[nozzle_index].sub_brands; filament = obj->vt_slot[nozzle_index].sub_brands;
temp_max = obj->vt_slot[nozzle_index].nozzle_temp_max;
temp_min = obj->vt_slot[nozzle_index].nozzle_temp_min;
} }
m_filament_setting_dlg->Move(wxPoint(current_position_x,current_position_y)); m_filament_setting_dlg->Move(wxPoint(current_position_x,current_position_y));
+17 -11
View File
@@ -72,6 +72,7 @@ bool AMSinfo::parse_ams_info(MachineObject *obj, DevAms *ams, bool remain_flag,
Caninfo info; Caninfo info;
// tray is exists // tray is exists
if (it != ams->GetTrays().end() && it->second->is_exists) { if (it != ams->GetTrays().end() && it->second->is_exists) {
info.is_empty = it->second->is_empty;
if (it->second->is_tray_info_ready()) { if (it->second->is_tray_info_ready()) {
info.can_id = it->second->id; info.can_id = it->second->id;
info.ctype = it->second->ctype; info.ctype = it->second->ctype;
@@ -139,6 +140,7 @@ void AMSinfo::parse_ext_info(MachineObject* obj, DevAmsTray tray) {
this->ams_type = AMSModel::EXT_AMS; this->ams_type = AMSModel::EXT_AMS;
Caninfo info; Caninfo info;
info.can_id = std::to_string(0); info.can_id = std::to_string(0);
info.is_empty = tray.is_empty;
this->cans.clear(); this->cans.clear();
if (tray.id == std::to_string(VIRTUAL_TRAY_MAIN_ID)) if (tray.id == std::to_string(VIRTUAL_TRAY_MAIN_ID))
@@ -1101,9 +1103,9 @@ void AMSLib::render_lite_text(wxDC& dc)
dc.SetTextForeground(temp_text_colour); dc.SetTextForeground(temp_text_colour);
auto libsize = GetSize(); auto libsize = GetSize();
if (m_info.material_state == AMSCanType::AMS_CAN_TYPE_THIRDBRAND if (!m_info.is_empty && (m_info.material_state == AMSCanType::AMS_CAN_TYPE_THIRDBRAND
|| m_info.material_state == AMSCanType::AMS_CAN_TYPE_BRAND || m_info.material_state == AMSCanType::AMS_CAN_TYPE_BRAND
|| m_info.material_state == AMSCanType::AMS_CAN_TYPE_VIRTUAL) { || m_info.material_state == AMSCanType::AMS_CAN_TYPE_VIRTUAL)) {
if (m_info.material_name.empty()) { if (m_info.material_name.empty()) {
auto tsize = dc.GetMultiLineTextExtent("?"); auto tsize = dc.GetMultiLineTextExtent("?");
@@ -1151,7 +1153,7 @@ void AMSLib::render_lite_text(wxDC& dc)
} }
} }
if (m_info.material_state == AMSCanType::AMS_CAN_TYPE_EMPTY) { if (m_info.material_state == AMSCanType::AMS_CAN_TYPE_EMPTY || m_info.is_empty) {
auto tsize = dc.GetMultiLineTextExtent(_L("/")); auto tsize = dc.GetMultiLineTextExtent(_L("/"));
auto pot = wxPoint((libsize.x - tsize.x) / 2 + FromDIP(2), (libsize.y - tsize.y) / 2 + FromDIP(3)); auto pot = wxPoint((libsize.x - tsize.x) / 2 + FromDIP(2), (libsize.y - tsize.y) / 2 + FromDIP(3));
dc.DrawText(_L("/"), pot); dc.DrawText(_L("/"), pot);
@@ -1160,8 +1162,12 @@ void AMSLib::render_lite_text(wxDC& dc)
void AMSLib::render_generic_text(wxDC &dc) void AMSLib::render_generic_text(wxDC &dc)
{ {
bool show_k_value = true; // K/N is Bambu firmware's flow-dynamics calibration; agents with no printer-side
if (m_info.material_name.empty()) { // records (OrcaSonar, Moonraker) must not show a synthesized value.
const bool k_supported = !m_obj || m_obj->supports_extrusion_cali();
const bool show_kn = m_show_kn && k_supported;
bool show_k_value = k_supported;
if (!k_supported || m_info.material_name.empty()) {
show_k_value = false; show_k_value = false;
} }
else if (m_info.cali_idx == -1 || (m_obj && (CalibUtils::get_selected_calib_idx(m_obj->pa_calib_tab, m_info.cali_idx) == -1))) { else if (m_info.cali_idx == -1 || (m_obj && (CalibUtils::get_selected_calib_idx(m_obj->pa_calib_tab, m_info.cali_idx) == -1))) {
@@ -1202,9 +1208,9 @@ void AMSLib::render_generic_text(wxDC &dc)
} }
auto libsize = GetSize(); auto libsize = GetSize();
if (m_info.material_state == AMSCanType::AMS_CAN_TYPE_THIRDBRAND if (!m_info.is_empty && (m_info.material_state == AMSCanType::AMS_CAN_TYPE_THIRDBRAND
|| m_info.material_state == AMSCanType::AMS_CAN_TYPE_BRAND || m_info.material_state == AMSCanType::AMS_CAN_TYPE_BRAND
|| m_info.material_state == AMSCanType::AMS_CAN_TYPE_VIRTUAL) { || m_info.material_state == AMSCanType::AMS_CAN_TYPE_VIRTUAL)) {
if (m_info.material_name.empty() /*&& m_info.material_state != AMSCanType::AMS_CAN_TYPE_VIRTUAL*/) { if (m_info.material_name.empty() /*&& m_info.material_state != AMSCanType::AMS_CAN_TYPE_VIRTUAL*/) {
auto tsize = dc.GetMultiLineTextExtent("?"); auto tsize = dc.GetMultiLineTextExtent("?");
@@ -1242,7 +1248,7 @@ void AMSLib::render_generic_text(wxDC &dc)
auto line_top_tsize = dc.GetMultiLineTextExtent(line_top); auto line_top_tsize = dc.GetMultiLineTextExtent(line_top);
auto line_bottom_tsize = dc.GetMultiLineTextExtent(line_bottom); auto line_bottom_tsize = dc.GetMultiLineTextExtent(line_bottom);
if (!m_show_kn) { if (!show_kn) {
auto pot_top = wxPoint((libsize.x - line_top_tsize.x) / 2, (libsize.y - line_top_tsize.y) / 2 - line_top_tsize.y + FromDIP(6)); auto pot_top = wxPoint((libsize.x - line_top_tsize.x) / 2, (libsize.y - line_top_tsize.y) / 2 - line_top_tsize.y + FromDIP(6));
dc.DrawText(line_top, pot_top); dc.DrawText(line_top, pot_top);
@@ -1273,7 +1279,7 @@ void AMSLib::render_generic_text(wxDC &dc)
//draw k&n //draw k&n
if (m_obj && show_k_value) { if (m_obj && show_k_value) {
if (m_show_kn) { if (show_kn) {
wxString str_k = wxString::Format("K %1.3f", m_info.k); wxString str_k = wxString::Format("K %1.3f", m_info.k);
wxString str_n = wxString::Format("N %1.3f", m_info.n); wxString str_n = wxString::Format("N %1.3f", m_info.n);
dc.SetFont(::Label::Body_11); dc.SetFont(::Label::Body_11);
@@ -1284,7 +1290,7 @@ void AMSLib::render_generic_text(wxDC &dc)
} }
} }
if (m_info.material_state == AMSCanType::AMS_CAN_TYPE_EMPTY) { if (m_info.material_state == AMSCanType::AMS_CAN_TYPE_EMPTY || m_info.is_empty) {
auto tsize = dc.GetMultiLineTextExtent(_L("Empty")); auto tsize = dc.GetMultiLineTextExtent(_L("Empty"));
auto pot = wxPoint((libsize.x - tsize.x) / 2, (libsize.y - tsize.y) / 2 + FromDIP(3)); auto pot = wxPoint((libsize.x - tsize.x) / 2, (libsize.y - tsize.y) / 2 + FromDIP(3));
dc.DrawText(_L("Empty"), pot); dc.DrawText(_L("Empty"), pot);
@@ -2781,7 +2787,7 @@ void AMSPreview::doRender(wxDC &dc)
} }
else { else {
wxRect rect(left, (size.y - AMS_ITEM_CUBE_SIZE.y) / 2, AMS_ITEM_CUBE_SIZE.x, AMS_ITEM_CUBE_SIZE.y); wxRect rect(left, (size.y - AMS_ITEM_CUBE_SIZE.y) / 2, AMS_ITEM_CUBE_SIZE.x, AMS_ITEM_CUBE_SIZE.y);
if (iter->material_state == AMSCanType::AMS_CAN_TYPE_EMPTY) { if (iter->material_state == AMSCanType::AMS_CAN_TYPE_EMPTY || iter->is_empty) {
dc.SetPen(wxPen(wxColor(0, 0, 0))); dc.SetPen(wxPen(wxColor(0, 0, 0)));
dc.DrawLine(rect.GetRight() - FromDIP(1), rect.GetTop() + FromDIP(1), rect.GetLeft() + FromDIP(1), rect.GetBottom() - FromDIP(1)); dc.DrawLine(rect.GetRight() - FromDIP(1), rect.GetTop() + FromDIP(1), rect.GetLeft() + FromDIP(1), rect.GetBottom() - FromDIP(1));
} }
+2
View File
@@ -183,6 +183,7 @@ struct Caninfo
wxString material_name; wxString material_name;
wxColour material_colour = {*wxWHITE}; wxColour material_colour = {*wxWHITE};
AMSCanType material_state; AMSCanType material_state;
bool is_empty = false;
int ctype=0; int ctype=0;
int material_remain = 100; int material_remain = 100;
int cali_idx = -1; int cali_idx = -1;
@@ -198,6 +199,7 @@ public:
material_name == other.material_name && material_name == other.material_name &&
material_colour == other.material_colour && material_colour == other.material_colour &&
material_state == other.material_state && material_state == other.material_state &&
is_empty == other.is_empty &&
ctype == other.ctype && ctype == other.ctype &&
material_remain == other.material_remain && material_remain == other.material_remain &&
cali_idx == other.cali_idx && cali_idx == other.cali_idx &&
@@ -1407,6 +1407,23 @@ TEST_CASE("Filaments offered for a machine follow the app's compatibility rule",
} }
} }
// An agent-reported model id (SSDP modelNumber, manual binding) must resolve to the display
// name printer profiles use as printer_model; the AMS dialogs look filaments up by it.
TEST_CASE("Printer model ids resolve to vendor display names", "[Preset][Bundle]")
{
PresetBundle bundle;
VendorProfile qidi("Qidi");
qidi.name = "Qidi";
VendorProfile::PrinterModel model;
model.model_id = "Qidi-Q1Pro";
model.name = "Qidi Q1 Pro";
qidi.models.push_back(model);
bundle.vendors.emplace(qidi.id, qidi);
CHECK(bundle.get_printer_model_display_name("Qidi-Q1Pro") == "Qidi Q1 Pro");
CHECK(bundle.get_printer_model_display_name("unknown-model").empty());
}
namespace { namespace {
+1
View File
@@ -6,6 +6,7 @@ add_executable(${_TEST_NAME}_tests
test_dev_mapping.cpp test_dev_mapping.cpp
test_filament_bitmap_utils.cpp test_filament_bitmap_utils.cpp
test_device_progress.cpp test_device_progress.cpp
test_ams_item.cpp
test_device_manager_integration.cpp test_device_manager_integration.cpp
test_web_media_controller.cpp test_web_media_controller.cpp
test_network_versions.cpp test_network_versions.cpp
+86
View File
@@ -0,0 +1,86 @@
// why: match the GUI include order to avoid rpcndr.h byte/std::byte
// ambiguity in the Windows COM headers.
#ifdef WIN32
#ifndef WIN32_LEAN_AND_MEAN
#define WIN32_LEAN_AND_MEAN
#endif
#ifndef NOMINMAX
#define NOMINMAX
#endif
#include <Windows.h>
#endif
#include <catch2/catch_all.hpp>
#include <wx/timer.h>
#include <nlohmann/json.hpp>
#include "slic3r/GUI/Widgets/AMSItem.hpp"
#include "slic3r/GUI/DeviceCore/DevFilaSystem.h"
using json = nlohmann::json;
using namespace Slic3r;
using namespace Slic3r::GUI;
// A configured slot the wire reports as having no filament (all material fields
// present and empty) is "Empty", not the unknown "?" the UI draws for a tray
// whose info is missing. The distinction must survive into the AMS panel model.
TEST_CASE("Configured empty AMS trays remain distinct from unknown trays", "[AMSItem]")
{
MachineObject machine(nullptr, nullptr, "test", "test-device", "127.0.0.1");
machine.printer_agent_id = "orca";
const json print_json = json::parse(R"({
"ams": {
"ams_exist_bits": "1",
"tray_exist_bits": "3",
"ams": [ { "id": "0", "info": "0001", "tray": [
{ "id": "0", "tag_uid": "0000000000000000", "tray_info_idx": "", "tray_type": "", "tray_color": "00000000" },
{ "id": "1" }
] } ]
}
})");
DevFilaSystemParser::ParseV1_0(print_json, &machine, machine.GetFilaSystem().get(), false);
const auto& ams_list = machine.GetFilaSystem()->GetAmsList();
const auto ams_it = ams_list.find("0");
REQUIRE(ams_it != ams_list.end());
auto* ams = ams_it->second;
REQUIRE(ams != nullptr);
REQUIRE(ams->GetTray("0") != nullptr);
CHECK(ams->GetTray("0")->is_empty);
AMSinfo info;
REQUIRE(info.parse_ams_info(&machine, ams));
REQUIRE(info.cans.size() == 2);
CHECK(info.cans[0].is_empty);
CHECK(info.cans[0].material_state == AMSCanType::AMS_CAN_TYPE_THIRDBRAND);
CHECK_FALSE(info.cans[1].is_empty);
CHECK(info.cans[1].material_state == AMSCanType::AMS_CAN_TYPE_THIRDBRAND);
}
TEST_CASE("Empty external slots remain distinct from unknown slots", "[AMSItem]")
{
MachineObject machine(nullptr, nullptr, "test", "test-device", "127.0.0.1");
machine.printer_agent_id = "orca";
DevAmsTray empty_slot = machine.parse_vt_tray(json::parse(R"({
"id": "255", "tag_uid": "0000000000000000", "tray_info_idx": "", "tray_type": "", "tray_color": "00000000"
})"));
CHECK(empty_slot.is_empty);
AMSinfo empty_info;
empty_info.parse_ext_info(&machine, empty_slot);
CHECK(empty_info.cans[0].is_empty);
CHECK(empty_info.cans[0].material_state == AMSCanType::AMS_CAN_TYPE_VIRTUAL);
DevAmsTray unknown_slot = machine.parse_vt_tray(json::parse(R"({"id": "254"})"));
CHECK_FALSE(unknown_slot.is_empty);
AMSinfo unknown_info;
unknown_info.parse_ext_info(&machine, unknown_slot);
CHECK_FALSE(unknown_info.cans[0].is_empty);
CHECK(unknown_info.cans[0].material_state == AMSCanType::AMS_CAN_TYPE_VIRTUAL);
}
@@ -3,6 +3,8 @@
#include <slic3r/GUI/DeviceCore/DevManager.h> #include <slic3r/GUI/DeviceCore/DevManager.h>
#include <slic3r/GUI/DeviceManager.hpp> #include <slic3r/GUI/DeviceManager.hpp>
#include <libslic3r/AppConfig.hpp> #include <libslic3r/AppConfig.hpp>
#include <libslic3r/PresetBundle.hpp>
#include <libslic3r/PrintConfig.hpp>
#include <slic3r/Utils/NetworkAgent.hpp> #include <slic3r/Utils/NetworkAgent.hpp>
#include <slic3r/Utils/OrcaCloudServiceAgent.hpp> #include <slic3r/Utils/OrcaCloudServiceAgent.hpp>
#include <slic3r/Utils/OrcaPrinterAgent.hpp> #include <slic3r/Utils/OrcaPrinterAgent.hpp>
@@ -273,3 +275,52 @@ TEST_CASE("Orca per-command AMS gate requires fms and the advertised command", "
obj->printer_agent_id = "bbl"; obj->printer_agent_id = "bbl";
CHECK(obj->orca_ams_command_supported("print.anything")); CHECK(obj->orca_ams_command_supported("print.anything"));
} }
// The AMS dialogs resolve their filament list from the connected device's model. OrcaSonar's
// model id is optional (the agent falls back to "orcasonar"), so the resolver must stand in
// with the selected printer profile instead of yielding no model at all.
TEST_CASE("Filament printer model resolution falls back to the selected profile", "[DeviceManager][integration]")
{
PresetBundle bundle;
VendorProfile qidi("Qidi");
qidi.name = "Qidi";
VendorProfile::PrinterModel model;
model.model_id = "Qidi-Q1Pro";
model.name = "Qidi Q1 Pro";
qidi.models.push_back(model);
bundle.vendors.emplace(qidi.id, qidi);
// A vendor model id the device reported resolves through the vendor catalog.
CHECK(resolve_filament_printer_model("Qidi-Q1Pro", &bundle) == "Qidi Q1 Pro");
// The OrcaSonar fallback id has no vendor model; the selected profile stands in.
CHECK(resolve_filament_printer_model("orcasonar", &bundle).empty());
bundle.printers.get_selected_preset().config.set_key_value("printer_model", new ConfigOptionString("Generic Klipper Printer"));
CHECK(resolve_filament_printer_model("orcasonar", &bundle) == "Generic Klipper Printer");
CHECK(resolve_filament_printer_model("", &bundle) == "Generic Klipper Printer");
CHECK(resolve_filament_printer_model("orcasonar", nullptr).empty());
}
// The per-tray K/N records are Bambu firmware's flow-dynamics calibration. Agents with no
// printer-side records must not offer the AMS K/N controls (they would show a synthesized
// default and then refuse to confirm it).
TEST_CASE("Flow-dynamics K/N is offered for Bambu agents only", "[DeviceManager][integration]")
{
MachineObject bbl(nullptr, nullptr, "test", "bbl-device", "127.0.0.1");
bbl.printer_agent_id = "bbl";
CHECK(bbl.supports_extrusion_cali());
MachineObject orca(nullptr, nullptr, "test", "orca-device", "127.0.0.1");
orca.printer_agent_id = "orca";
CHECK_FALSE(orca.supports_extrusion_cali());
MachineObject moonraker(nullptr, nullptr, "test", "moonraker-device", "127.0.0.1");
moonraker.printer_agent_id = "moonraker";
CHECK_FALSE(moonraker.supports_extrusion_cali());
// No agent id predates the agent split and keeps the Bambu path.
MachineObject legacy(nullptr, nullptr, "test", "legacy-device", "127.0.0.1");
CHECK(legacy.supports_extrusion_cali());
}