diff --git a/src/libslic3r/PresetBundle.cpp b/src/libslic3r/PresetBundle.cpp index ac83f5cb1b..12dc8aafbb 100644 --- a/src/libslic3r/PresetBundle.cpp +++ b/src/libslic3r/PresetBundle.cpp @@ -4255,9 +4255,19 @@ std::vector> PresetBundle::get_extruder_filament return filament_infos; } -// ORCA TODO: currently, this function assumes the printer name follows the pattern of " ", e.g. -// 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. +std::string PresetBundle::get_printer_model_display_name(const std::string &model_id) const +{ + 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 " ", e.g. +// "Bambu Lab X2D 0.4 nozzle". Other naming schemes may not resolve correctly. std::set PresetBundle::get_printer_names_by_printer_type_and_nozzle(const std::string &printer_type, std::string nozzle_diameter_str, bool system_only) { std::set printer_names; diff --git a/src/libslic3r/PresetBundle.hpp b/src/libslic3r/PresetBundle.hpp index 5a8f34e706..2499a31b0d 100644 --- a/src/libslic3r/PresetBundle.hpp +++ b/src/libslic3r/PresetBundle.hpp @@ -391,6 +391,7 @@ public: std::vector> get_extruder_filament_info() const; + std::string get_printer_model_display_name(const std::string &model_id) const; std::set 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 // of the app applies (is_compatible_with_printer): an empty compatible_printers means every diff --git a/src/slic3r/GUI/AMSMaterialsSetting.cpp b/src/slic3r/GUI/AMSMaterialsSetting.cpp index f0fdf950a0..2fbf7b2906 100644 --- a/src/slic3r/GUI/AMSMaterialsSetting.cpp +++ b/src/slic3r/GUI/AMSMaterialsSetting.cpp @@ -431,6 +431,10 @@ void AMSMaterialsSetting::update_filament_editing(bool is_printing) m_button_confirm->Show(true); } + 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) { m_tip_readonly->SetLabelText(wxEmptyString); m_tip_readonly->Hide(); @@ -453,6 +457,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 m_comboBox_filament->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_n_val->Enable(false); m_button_confirm->Hide(); @@ -940,6 +946,21 @@ static void _collect_filament_info(const wxString& shown_name, query_filament_types[shown_name] = filament.config.get_filament_type(); } +static std::string sGetFilamentPrinterModel(MachineObject *obj, PresetBundle *preset_bundle) +{ + if (!obj || !preset_bundle) + return {}; + + const wxString device_model = obj->get_printer_type_display_str(); + if (!device_model.empty() && device_model != "OrcaSonar Printer" && device_model != _L("Unknown")) + return device_model.ToStdString(); + + // OrcaSonar's model ID is optional; use the selected profile when it is generic. + const ConfigOption *opt = preset_bundle->printers.get_selected_preset().config.option("printer_model"); + const auto *model = dynamic_cast(opt); + return model ? model->value : std::string(); +} + void AMSMaterialsSetting::Popup(wxString filament, wxString sn, wxString temp_min, wxString temp_max, wxString k, wxString n) { if (!obj) return; @@ -952,6 +973,8 @@ void AMSMaterialsSetting::Popup(wxString filament, wxString sn, wxString temp_mi m_input_k_val->GetTextCtrl()->SetValue(k); m_input_n_val->GetTextCtrl()->SetValue(n); + m_input_nozzle_min->GetTextCtrl()->SetValue(wxEmptyString); + m_input_nozzle_max->GetTextCtrl()->SetValue(wxEmptyString); wxArrayString filament_items; wxString bambu_filament_name; @@ -965,9 +988,7 @@ void AMSMaterialsSetting::Popup(wxString filament, wxString sn, wxString temp_mi map_filament_items.clear(); PresetBundle * preset_bundle = wxGetApp().preset_bundle; std::ostringstream stream; - // Defensive: this dialog is opened only from StatusPanel (BBL-only) today, so the fallback fires - // only during the brief BBL startup window before firmware reports nozzle info. Without this, - // the "0.0" lookup string returns an empty set and the filament dropdown goes blank. + // Use the selected profile's nozzle diameter until the connected device reports one. float machine_diameter = obj->GetExtderSystem()->GetNozzleDiameter(0); if (machine_diameter == 0.0f && preset_bundle) { const ConfigOption *opt = preset_bundle->printers.get_selected_preset().config.option("nozzle_diameter"); @@ -975,11 +996,12 @@ void AMSMaterialsSetting::Popup(wxString filament, wxString sn, wxString temp_mi } stream << std::fixed << std::setprecision(1) << machine_diameter; std::string nozzle_diameter_str = stream.str(); + const std::string filament_printer_model = sGetFilamentPrinterModel(obj, 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(); 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) continue; const std::string alias = preset_bundle->filaments.get_preset_alias(*filament_it, true); @@ -1045,6 +1067,10 @@ void AMSMaterialsSetting::Popup(wxString filament, wxString sn, wxString temp_mi else { m_comboBox_filament->Show(); m_readonly_filament->Hide(); + 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) { @@ -1182,6 +1208,7 @@ void AMSMaterialsSetting::Popup(wxString filament, wxString sn, wxString temp_mi } m_comboBox_filament->Set(filament_items); + m_comboBox_filament->SetClientData(new int(1)); m_comboBox_filament->SetSelection(selection_idx); post_select_event(selection_idx); @@ -1189,9 +1216,6 @@ void AMSMaterialsSetting::Popup(wxString filament, wxString sn, wxString temp_mi 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(); Layout(); Fit(); @@ -1222,15 +1246,14 @@ void AMSMaterialsSetting::on_select_filament(wxCommandEvent &evt) { // Get the flag whether to open the filament setting dialog from the device page int* from_printer = static_cast(m_comboBox_filament->GetClientData()); + const bool initial_printer_selection = from_printer && *from_printer == 1; m_filament_type = ""; PresetBundle* preset_bundle = wxGetApp().preset_bundle; if (preset_bundle) { std::ostringstream stream; if (obj) { - // Defensive: this dialog is opened only from StatusPanel (BBL-only) today, so the fallback fires - // 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. + // Use the selected profile's nozzle diameter until the connected device reports one. float machine_diameter = obj->GetExtderSystem()->GetNozzleDiameter(0); if (machine_diameter == 0.0f) { const ConfigOption *opt = preset_bundle->printers.get_selected_preset().config.option("nozzle_diameter"); @@ -1239,30 +1262,32 @@ void AMSMaterialsSetting::on_select_filament(wxCommandEvent &evt) stream << std::fixed << std::setprecision(1) << machine_diameter; } std::string nozzle_diameter_str = stream.str(); + const std::string filament_printer_model = sGetFilamentPrinterModel(obj, preset_bundle); // 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. 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; 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) continue; - // ) if nozzle_temperature_range is found - ConfigOption* opt_min = it->config.option("nozzle_temperature_range_low"); - if (opt_min) { - ConfigOptionInts* opt_min_ints = dynamic_cast(opt_min); - if (opt_min_ints) { - wxString text_nozzle_temp_min = wxString::Format("%d", opt_min_ints->get_at(0)); - m_input_nozzle_min->GetTextCtrl()->SetValue(text_nozzle_temp_min); + if (!initial_printer_selection) { + ConfigOption* opt_min = it->config.option("nozzle_temperature_range_low"); + if (opt_min) { + ConfigOptionInts* opt_min_ints = dynamic_cast(opt_min); + if (opt_min_ints) { + 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"); - if (opt_max) { - ConfigOptionInts* opt_max_ints = dynamic_cast(opt_max); - if (opt_max_ints) { - wxString text_nozzle_temp_max = wxString::Format("%d", opt_max_ints->get_at(0)); - m_input_nozzle_max->GetTextCtrl()->SetValue(text_nozzle_temp_max); + ConfigOption* opt_max = it->config.option("nozzle_temperature_range_high"); + if (opt_max) { + ConfigOptionInts* opt_max_ints = dynamic_cast(opt_max); + if (opt_max_ints) { + wxString text_nozzle_temp_max = wxString::Format("%d", opt_max_ints->get_at(0)); + m_input_nozzle_max->GetTextCtrl()->SetValue(text_nozzle_temp_max); + } } } ConfigOption* opt_type = it->config.option("filament_type"); @@ -1301,13 +1326,16 @@ void AMSMaterialsSetting::on_select_filament(wxCommandEvent &evt) m_button_confirm->Disable(); // ORCA No need to change style m_comboBox_cali_result->Clear(); m_comboBox_cali_result->SetValue(wxEmptyString); - m_input_k_val->GetTextCtrl()->SetValue(wxEmptyString); - m_input_n_val->GetTextCtrl()->SetValue(wxEmptyString); + if (!initial_printer_selection) { + m_input_k_val->GetTextCtrl()->SetValue(wxEmptyString); + m_input_n_val->GetTextCtrl()->SetValue(wxEmptyString); + } m_comboBox_filament->SetClientData(new int(0)); return; } 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 @@ -1331,6 +1359,9 @@ void AMSMaterialsSetting::on_select_filament(wxCommandEvent &evt) } } + if (!ams_filament_id.empty()) + m_clr_picker->is_empty(false); + wxArrayString items; m_pa_profile_items.clear(); m_comboBox_cali_result->SetValue(wxEmptyString); @@ -1437,7 +1468,7 @@ void AMSMaterialsSetting::on_select_filament(wxCommandEvent &evt) else { if (!ams_filament_id.empty()) { //m_input_k_val->GetTextCtrl()->SetValue("0.00"); - m_input_k_val->Enable(true); + m_input_k_val->Enable(!m_view_only); } else { //m_input_k_val->GetTextCtrl()->SetValue("0.00"); diff --git a/src/slic3r/GUI/DeviceManager.cpp b/src/slic3r/GUI/DeviceManager.cpp index f9c9ed20b2..4d9f056914 100644 --- a/src/slic3r/GUI/DeviceManager.cpp +++ b/src/slic3r/GUI/DeviceManager.cpp @@ -378,16 +378,9 @@ wxString MachineObject::get_printer_type_display_str() const { std::string display_name = DevPrinterConfigUtil::get_printer_display_name(printer_type); - // Bambu printers use m_resource_file_path + "/printers/" + type_str + ".json", which is a semantic that only works for their profiles. - // For any other profile, we can simply consult preset bundle if the model_id exists. - if (display_name.empty()) { - for (const auto& [vendor_id, vendor] : GUI::wxGetApp().preset_bundle->vendors) { - for (const auto& model : vendor.models) { - if (printer_type == model.model_id) - display_name = model.name; - } - } - } + // Bambu names come from Bambu configs; other vendors resolve through preset model IDs. + if (display_name.empty() && GUI::wxGetApp().preset_bundle) + display_name = GUI::wxGetApp().preset_bundle->get_printer_model_display_name(printer_type); if (!display_name.empty()) return display_name; diff --git a/src/slic3r/GUI/StatusPanel.cpp b/src/slic3r/GUI/StatusPanel.cpp index 8a278ec311..712e7155e6 100644 --- a/src/slic3r/GUI/StatusPanel.cpp +++ b/src/slic3r/GUI/StatusPanel.cpp @@ -4593,7 +4593,7 @@ void StatusPanel::on_filament_edit(wxCommandEvent &event) m_filament_setting_dlg->set_ctype(tray->ctype); m_filament_setting_dlg->ams_filament_id = tray->setting_id; - if (m_filament_setting_dlg->ams_filament_id.empty()) + if (tray->is_empty) { m_filament_setting_dlg->set_empty_color(color); } @@ -4608,12 +4608,12 @@ void StatusPanel::on_filament_edit(wxCommandEvent &event) // metadata, and the spec says it must never be interpreted — so // the tag alone must not make their trays read-only. m_filament_setting_dlg->m_is_third = !obj->is_bbl_agent() || !DevFilaSystem::IsBBL_Filament(tray->tag_uid); + temp_max = tray->nozzle_temp_max; + temp_min = tray->nozzle_temp_min; if (!m_filament_setting_dlg->m_is_third) { sn_number = tray->uuid; filament = tray->sub_brands; - temp_max = tray->nozzle_temp_max; - temp_min = tray->nozzle_temp_min; } } @@ -4667,7 +4667,7 @@ void StatusPanel::on_ext_spool_edit(wxCommandEvent &event) } m_filament_setting_dlg->set_ctype(obj->vt_slot[nozzle_index].ctype); - if (m_filament_setting_dlg->ams_filament_id.empty()) { + if (obj->vt_slot[nozzle_index].is_empty) { m_filament_setting_dlg->set_empty_color(color); } else { @@ -4676,11 +4676,11 @@ void StatusPanel::on_ext_spool_edit(wxCommandEvent &event) } m_filament_setting_dlg->m_is_third = !obj->is_bbl_agent() || !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) { sn_number = obj->vt_slot[nozzle_index].uuid; 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)); diff --git a/tests/libslic3r/test_preset_bundle_loading.cpp b/tests/libslic3r/test_preset_bundle_loading.cpp index 2b6952aa92..a1f866e0a2 100644 --- a/tests/libslic3r/test_preset_bundle_loading.cpp +++ b/tests/libslic3r/test_preset_bundle_loading.cpp @@ -1407,6 +1407,21 @@ TEST_CASE("Filaments offered for a machine follow the app's compatibility rule", } } +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 { diff --git a/tests/slic3rutils/test_ams_item.cpp b/tests/slic3rutils/test_ams_item.cpp index d756e32012..c72018919c 100644 --- a/tests/slic3rutils/test_ams_item.cpp +++ b/tests/slic3rutils/test_ams_item.cpp @@ -20,16 +20,17 @@ using namespace Slic3r; using namespace Slic3r::GUI; -TEST_CASE("Configured empty AMS trays remain distinct from unknown trays", "[AMSItem]") +TEST_CASE("Configured empty and partial AMS trays remain distinct from unknown trays", "[AMSItem]") { MachineObject machine(nullptr, nullptr, "test", "test-device", "127.0.0.1"); machine.printer_agent_id = "orca"; machine.parse_json("lan", R"({"print":{"command":"push_status","msg":0,"ams":{ - "ams_exist_bits":"1","tray_exist_bits":"3","ams":[ + "ams_exist_bits":"1","tray_exist_bits":"7","ams":[ {"id":"0","info":"0001","tray":[ {"id":"0","tag_uid":"0000000000000000","tray_info_idx":"","tray_type":"","tray_color":"00000000"}, - {"id":"1"} + {"id":"1"}, + {"id":"2","tag_uid":"0000000000000000","tray_info_idx":"","tray_type":"PLA","tray_color":"FF0000FF"} ]} ] }}})", false); @@ -43,12 +44,14 @@ TEST_CASE("Configured empty AMS trays remain distinct from unknown trays", "[AMS CHECK(ams->GetTray("0")->is_exists); AMSinfo info; REQUIRE(info.parse_ams_info(&machine, ams)); - REQUIRE(info.cans.size() == 2); + REQUIRE(info.cans.size() == 3); 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); + CHECK_FALSE(info.cans[2].is_empty); + CHECK(info.cans[2].material_state == AMSCanType::AMS_CAN_TYPE_THIRDBRAND); } TEST_CASE("Empty external slots remain distinct from unknown slots", "[AMSItem]")