diff --git a/src/slic3r/GUI/DeviceCore/DevDefs.h b/src/slic3r/GUI/DeviceCore/DevDefs.h index 6332d6b109..34469c7d43 100644 --- a/src/slic3r/GUI/DeviceCore/DevDefs.h +++ b/src/slic3r/GUI/DeviceCore/DevDefs.h @@ -163,8 +163,8 @@ public: ~devPrinterUtil() = delete; public: - static bool IsVirtualSlot(int ams_id) { return (ams_id == VIRTUAL_TRAY_MAIN_ID || ams_id == VIRTUAL_TRAY_DEPUTY_ID);} - static bool IsVirtualSlot(const std::string& ams_id) { return (ams_id == VIRTUAL_AMS_MAIN_ID_STR || ams_id == VIRTUAL_AMS_DEPUTY_ID_STR); } + static bool IsVirtualSlot(int ams_id); + static bool IsVirtualSlot(const std::string& ams_id); }; namespace GUI @@ -200,4 +200,4 @@ template<> struct std::hash }; // key(extruder_id) -> { key1(nozzle type info), val1( number of the nozzle type)} -using ExtruderNozzleInfos = std::unordered_map>; \ No newline at end of file +using ExtruderNozzleInfos = std::unordered_map>; diff --git a/src/slic3r/GUI/DeviceCore/DevFilaSystem.cpp b/src/slic3r/GUI/DeviceCore/DevFilaSystem.cpp index 14880a6ef3..6e26a5f5d0 100644 --- a/src/slic3r/GUI/DeviceCore/DevFilaSystem.cpp +++ b/src/slic3r/GUI/DeviceCore/DevFilaSystem.cpp @@ -34,6 +34,25 @@ using namespace nlohmann; namespace Slic3r { + +bool devPrinterUtil::IsVirtualSlot(int ams_id) +{ + if (wxTheApp == nullptr || GUI::wxGetApp().preset_bundle == nullptr || GUI::wxGetApp().preset_bundle->is_bbl_vendor()) + return ams_id == VIRTUAL_TRAY_MAIN_ID || ams_id == VIRTUAL_TRAY_DEPUTY_ID; + + const int extruder_count = GUI::wxGetApp().preset_bundle->get_printer_extruder_count(); + return ams_id >= VIRTUAL_TRAY_MAIN_ID - extruder_count + 1 && ams_id <= VIRTUAL_TRAY_MAIN_ID; +} + +bool devPrinterUtil::IsVirtualSlot(const std::string& ams_id) +{ + try { + return IsVirtualSlot(std::stoi(ams_id)); + } catch (...) { + return false; + } +} + static int _hex_digit_to_int(const char c) { return (c >= '0' && c <= '9') ? c - '0' : (c >= 'A' && c <= 'F') ? c - 'A' + 10 : (c >= 'a' && c <= 'f') ? c - 'a' + 10 : -1; } wxColour DevAmsTray::decode_color(const std::string &color) @@ -212,7 +231,7 @@ int DevAms::GetSlotCount() const return 1; } - return 1; + return static_cast(m_trays.size()); } DevAmsTray* DevAms::GetTray(const std::string& tray_id) const @@ -303,13 +322,21 @@ int DevFilaSystem::GetExtruderIdByAmsId(const std::string& ams_id) const { return it->second->GetExtruderId(); } - else if (stoi(ams_id) == VIRTUAL_TRAY_MAIN_ID) - { - return MAIN_EXTRUDER_ID; + + const bool is_bbl_vendor = wxTheApp != nullptr && GUI::wxGetApp().preset_bundle != nullptr && GUI::wxGetApp().preset_bundle->is_bbl_vendor(); + if (!is_bbl_vendor && GetOwner()) { + for (const auto& tray : GetOwner()->vt_slot) { + if (tray.id == ams_id) + return VIRTUAL_TRAY_MAIN_ID - std::stoi(ams_id); + } } - else if (stoi(ams_id) == VIRTUAL_TRAY_DEPUTY_ID) - { + + if (is_bbl_vendor && ams_id == VIRTUAL_AMS_MAIN_ID_STR) + return MAIN_EXTRUDER_ID; + if (is_bbl_vendor && ams_id == VIRTUAL_AMS_DEPUTY_ID_STR) return DEPUTY_EXTRUDER_ID; + if (!is_bbl_vendor && devPrinterUtil::IsVirtualSlot(ams_id)) { + return VIRTUAL_TRAY_MAIN_ID - std::stoi(ams_id); } assert(false && __FUNCTION__); @@ -326,9 +353,22 @@ std::string DevFilaSystem::GetNozzleFlowStringByAmsId(const std::string& ams_id) std::map DevFilaSystem::GetTrayIndexMap() { std::map tray_id_map; - tray_id_map[VIRTUAL_TRAY_MAIN_ID] = DevAmsSlotId{VIRTUAL_TRAY_MAIN_ID, 0}; - tray_id_map[VIRTUAL_TRAY_DEPUTY_ID] = DevAmsSlotId{VIRTUAL_TRAY_DEPUTY_ID, 0}; + const bool is_bbl_vendor = wxTheApp != nullptr && GUI::wxGetApp().preset_bundle != nullptr && GUI::wxGetApp().preset_bundle->is_bbl_vendor(); + if (is_bbl_vendor) { + tray_id_map[VIRTUAL_TRAY_MAIN_ID] = DevAmsSlotId{VIRTUAL_TRAY_MAIN_ID, 0}; + tray_id_map[VIRTUAL_TRAY_DEPUTY_ID] = DevAmsSlotId{VIRTUAL_TRAY_DEPUTY_ID, 0}; + } else if (GetOwner()) { + for (const auto& tray : GetOwner()->vt_slot) { + try { + const int tray_id = std::stoi(tray.id); + tray_id_map[tray_id] = DevAmsSlotId{tray_id, 0}; + } catch (...) { + BOOST_LOG_TRIVIAL(warning) << __FUNCTION__ << " invalid virtual tray id: " << tray.id; + } + } + } + int generic_tray_index = 0; for (auto& [ams_id, ams_item] : GetAmsList()) { for (auto &[slot_id, slot_item] : ams_item->GetTrays()) { if (ams_item && slot_item) { @@ -336,9 +376,11 @@ std::map DevFilaSystem::GetTrayIndexMap() int ams_id_int = stoi(ams_id); int slot_id_int = stoi(slot_id); int tray_index = -1; - if (ams_item->GetAmsType() == DevAms::N3S) { + if (!is_bbl_vendor) { + tray_index = generic_tray_index + slot_id_int; + } else if (ams_item->GetAmsType() == DevAms::N3S) { tray_index = ams_id_int; - } else if(ams_item->GetAmsType() == DevAms::AMS_LITE && ams_item->IsAmsLiteMixed()) { + } else if (ams_item->GetAmsType() == DevAms::AMS_LITE && ams_item->IsAmsLiteMixed()) { tray_index = 24 + slot_id_int; } else { tray_index = (ams_id_int * 4 + slot_id_int); @@ -349,6 +391,8 @@ std::map DevFilaSystem::GetTrayIndexMap() } } } + if (!is_bbl_vendor) + generic_tray_index += static_cast(ams_item->GetTrays().size()); } return tray_id_map; @@ -914,6 +958,8 @@ void DevFilaSystemParser::ParseAgentFilament(const json& data, MachineObject* ob } ams->m_exist = true; + ams->m_binded_extruder_set = {ext_id}; + ams->m_binded_switcher_pos.reset(); ams->m_current_temperature = u.value("temperature", (float) INVALID_AMS_TEMPERATURE); ams->m_humidity_percent = u.value("humidity_percent", -1); ams->m_left_dry_time = u.value("dry_time_min", 0); @@ -996,16 +1042,22 @@ void DevFilaSystemParser::ParseAgentFilament(const json& data, MachineObject* ob } // --- external / direct spools -> obj->vt_slot --- - // extruder 0 -> main virtual slot, extruder >0 -> deputy. + // Keep one virtual slot per extruder. The legacy two-extruder mapping uses + // 255 for extruder 0 and 254 for extruder 1; continue that sequence for + // additional extruders (253, 252, ...). if (obj && data.contains("external") && data["external"].is_array()) { + std::map external_slots; obj->vt_slot.clear(); for (const auto& e : data["external"]) { if (!e.is_object()) continue; const int ext = e.value("extruder", MAIN_EXTRUDER_ID); - const int vt_id = (ext == MAIN_EXTRUDER_ID) ? VIRTUAL_TRAY_MAIN_ID : VIRTUAL_TRAY_DEPUTY_ID; + if (ext < MAIN_EXTRUDER_ID || ext > VIRTUAL_TRAY_MAIN_ID) + continue; + + const int vt_id = VIRTUAL_TRAY_MAIN_ID - ext; DevAmsTray tray(std::to_string(vt_id)); tray.is_exists = e.value("loaded", false); tray.m_fila_type = e.value("material", std::string()); @@ -1014,8 +1066,11 @@ void DevFilaSystemParser::ParseAgentFilament(const json& data, MachineObject* ob tray.nozzle_temp_min = std::to_string(e.value("nozzle_temp_min", 0)); tray.nozzle_temp_max = std::to_string(e.value("nozzle_temp_max", 0)); tray.remain = e.value("remain_percent", -1); - obj->vt_slot.push_back(tray); + external_slots.insert_or_assign(ext, std::move(tray)); } + + for (auto& entry : external_slots) + obj->vt_slot.push_back(std::move(entry.second)); } } diff --git a/src/slic3r/GUI/DeviceCore/DevFilaSystem.h b/src/slic3r/GUI/DeviceCore/DevFilaSystem.h index 32edc06a32..ee9079a5c4 100644 --- a/src/slic3r/GUI/DeviceCore/DevFilaSystem.h +++ b/src/slic3r/GUI/DeviceCore/DevFilaSystem.h @@ -349,7 +349,7 @@ public: DevAmsTray* GetAmsTray(const std::string& ams_id, const std::string& tray_id) const; void CollectAmsColors(std::vector& ams_colors) const; - // Map a linear tray index -> {ams_id, slot_id}. Includes the two virtual (external-spool) trays, + // Map a linear tray index -> {ams_id, slot_id}. Includes virtual external-spool trays, // N3S single-slot units, and the A2L/N9 AMS-Lite-mixed layout (trays 24-27). std::map GetTrayIndexMap(); @@ -434,4 +434,4 @@ struct DevFilamentDryingPreset float filament_dev_ams_drying_heat_distortion_temperature = 0.0f; }; -}// namespace Slic3r \ No newline at end of file +}// namespace Slic3r diff --git a/src/slic3r/GUI/DeviceCore/DevNozzleSystem.cpp b/src/slic3r/GUI/DeviceCore/DevNozzleSystem.cpp index 70eec0f795..304d84097f 100644 --- a/src/slic3r/GUI/DeviceCore/DevNozzleSystem.cpp +++ b/src/slic3r/GUI/DeviceCore/DevNozzleSystem.cpp @@ -27,6 +27,7 @@ #include #include #include +#include "slic3r/GUI/GUI_App.hpp" namespace Slic3r { @@ -174,34 +175,48 @@ DevFirmwareVersionInfo DevNozzle::GetFirmwareInfo() const int DevNozzle::GetLogicExtruderId() const { int total_ext_count = GetTotalExtruderCount(); - if (total_ext_count == 1) { - return LOGIC_UNIQUE_EXTRUDER_ID; - } else if (total_ext_count == 2) { - if (AtLeftExtruder()) { - return LOGIC_L_EXTRUDER_ID; - } else if (AtRightExtruder()) { - return LOGIC_R_EXTRUDER_ID; + + if (GUI::wxGetApp().preset_bundle->is_bbl_vendor()) { + if (total_ext_count == 1) { + return LOGIC_UNIQUE_EXTRUDER_ID; + } else if (total_ext_count == 2) { + if (AtLeftExtruder()) { + return LOGIC_L_EXTRUDER_ID; + } else if (AtRightExtruder()) { + return LOGIC_R_EXTRUDER_ID; + } } + + assert(0); + return LOGIC_UNIQUE_EXTRUDER_ID; } - assert(0); - return LOGIC_UNIQUE_EXTRUDER_ID; + // For some reason, BBL's logical extruder ID is inverted: + // physical extruder id = 0 (MAIN_EXTRUDER_ID) vs logical extruder id = 1 (LOGIC_R_EXTRUDER_ID) + // Likely because logical id reads from left to right (left = 0, right = 1) + // For generic N extruders, this inversion does not apply. + if (IsOnRack()) return INVALID_EXTRUDER_ID; + return m_nozzle_id; } int DevNozzle::GetExtruderId() const { - int total_ext_count = GetTotalExtruderCount(); - if (total_ext_count == 1) { - return MAIN_EXTRUDER_ID; - } else if (total_ext_count == 2) { - if (AtRightExtruder()) { + if (GUI::wxGetApp().preset_bundle->is_bbl_vendor()) { + int total_ext_count = GetTotalExtruderCount(); + if (total_ext_count == 1) { return MAIN_EXTRUDER_ID; - } else if (AtLeftExtruder()) { - return DEPUTY_EXTRUDER_ID; + } else if (total_ext_count == 2) { + if (AtRightExtruder()) { + return MAIN_EXTRUDER_ID; + } else if (AtLeftExtruder()) { + return DEPUTY_EXTRUDER_ID; + } } + + return MAIN_EXTRUDER_ID; } - return MAIN_EXTRUDER_ID; + return m_nozzle_id; } bool DevNozzle::AtLeftExtruder() const diff --git a/src/slic3r/GUI/DeviceManager.cpp b/src/slic3r/GUI/DeviceManager.cpp index 392ff4587b..59953e75ff 100644 --- a/src/slic3r/GUI/DeviceManager.cpp +++ b/src/slic3r/GUI/DeviceManager.cpp @@ -1273,19 +1273,24 @@ int MachineObject::get_bed_temperature_limit() bool MachineObject::is_filament_installed() { - if (m_extder_system->GetTotalExtderCount() > 0) { - // right//or single - auto ext = m_extder_system->m_extders[MAIN_EXTRUDER_ID]; - if (ext.m_ext_has_filament) { + // if (m_extder_system->GetTotalExtderCount() > 0) { + // // right//or single + // auto ext = m_extder_system->m_extders[MAIN_EXTRUDER_ID]; + // if (ext.m_ext_has_filament) { + // return true; + // } + // } + // /*left*/ + // if (m_extder_system->GetTotalExtderCount() > 1) { + // auto ext = m_extder_system->m_extders[DEPUTY_EXTRUDER_ID]; + // if (ext.m_ext_has_filament) { + // return true; + // } + // } + + for (auto& ext : m_extder_system->m_extders) { + if (ext.m_ext_has_filament) return true; - } - } - /*left*/ - if (m_extder_system->GetTotalExtderCount() > 1) { - auto ext = m_extder_system->m_extders[DEPUTY_EXTRUDER_ID]; - if (ext.m_ext_has_filament) { - return true; - } } return false; } @@ -5262,16 +5267,17 @@ DevAmsTray MachineObject::parse_vt_tray(json vtray) bool MachineObject::contains_tray(const std::string &ams_id, const std::string &tray_id) const { - if (ams_id != VIRTUAL_AMS_MAIN_ID_STR && ams_id != VIRTUAL_AMS_DEPUTY_ID_STR) { - + const bool is_bbl_vendor = wxTheApp != nullptr && GUI::wxGetApp().preset_bundle != nullptr && GUI::wxGetApp().preset_bundle->is_bbl_vendor(); + if (is_bbl_vendor && ams_id != VIRTUAL_AMS_MAIN_ID_STR && ams_id != VIRTUAL_AMS_DEPUTY_ID_STR) return m_fila_system->GetAmsTray(ams_id, tray_id) != nullptr; - } else { + + if (!is_bbl_vendor || ams_id == VIRTUAL_AMS_MAIN_ID_STR || ams_id == VIRTUAL_AMS_DEPUTY_ID_STR) { for (const auto& tray : vt_slot) { - if (tray.id == ams_id) { return true; } + if (tray.id == ams_id) return true; } } - return false; + return !is_bbl_vendor && m_fila_system->GetAmsTray(ams_id, tray_id) != nullptr; } DevAmsTray MachineObject::get_tray(const std::string &ams_id, const std::string &tray_id) const @@ -5281,13 +5287,19 @@ DevAmsTray MachineObject::get_tray(const std::string &ams_id, const std::string return DevAmsTray(tray_id); } - if (ams_id != VIRTUAL_AMS_MAIN_ID_STR && ams_id != VIRTUAL_AMS_DEPUTY_ID_STR) { + const bool is_bbl_vendor = wxTheApp != nullptr && GUI::wxGetApp().preset_bundle != nullptr && GUI::wxGetApp().preset_bundle->is_bbl_vendor(); + if (is_bbl_vendor && ams_id != VIRTUAL_AMS_MAIN_ID_STR && ams_id != VIRTUAL_AMS_DEPUTY_ID_STR) { auto tray = m_fila_system->GetAmsTray(ams_id, tray_id); - if (tray) { return *tray;}; + if (tray) return *tray; } - else { + else if (!is_bbl_vendor || ams_id == VIRTUAL_AMS_MAIN_ID_STR || ams_id == VIRTUAL_AMS_DEPUTY_ID_STR) { for (const auto &tray : vt_slot) { - if (tray.id == ams_id) { return tray; } + if (tray.id == ams_id) return tray; + } + + if (!is_bbl_vendor) { + auto tray = m_fila_system->GetAmsTray(ams_id, tray_id); + if (tray) return *tray; } } diff --git a/src/slic3r/GUI/StatusPanel.cpp b/src/slic3r/GUI/StatusPanel.cpp index c720494dff..e9f05548e5 100644 --- a/src/slic3r/GUI/StatusPanel.cpp +++ b/src/slic3r/GUI/StatusPanel.cpp @@ -75,6 +75,8 @@ #include #include #include +#include +#include #include #include #include @@ -288,7 +290,22 @@ void ExtruderImage::msw_rescale() void ExtruderImage::setExtruderCount(int nozzle_num) { - m_nozzle_num = nozzle_num; + const int normalized_count = std::max(0, nozzle_num); + if (m_nozzle_num == normalized_count && m_multi_extruder_states.size() == static_cast(normalized_count)) + return; + + m_nozzle_num = normalized_count; + m_multi_extruder_states.resize(static_cast(m_nozzle_num), ExtruderState::EMPTY_LOAD); + Refresh(); +} + +void ExtruderImage::setGenericNozzleDisplay(bool enabled) +{ + if (m_generic_nozzle_display == enabled) + return; + + m_generic_nozzle_display = enabled; + Refresh(); } void ExtruderImage::setExtruderUsed(std::string loc) @@ -303,14 +320,36 @@ void ExtruderImage::setExtruderUsed(std::string loc) Refresh(); } +void ExtruderImage::setExtruderUsed(int nozzle_idx) +{ + if (current_nozzle_idx == nozzle_idx) + return; + + current_nozzle_idx = nozzle_idx; + Refresh(); +} + void ExtruderImage::update(ExtruderState single_state) { m_single_ext_state = single_state; + Refresh(); } void ExtruderImage::update(ExtruderState right_state, ExtruderState left_state) { m_left_ext_state = left_state; m_right_ext_state = right_state; + Refresh(); +} + +void ExtruderImage::update(ExtruderState state, int idx) { + if (idx < 0 || m_multi_extruder_states.size() <= static_cast(idx)) { + BOOST_LOG_TRIVIAL(warning) << __FUNCTION__ << ": Index exceeded number of extruders."; + return; + } + if (m_multi_extruder_states[idx] == state) + return; + + m_multi_extruder_states[idx] = state; } void ExtruderImage::paintEvent(wxPaintEvent& evt) @@ -345,6 +384,50 @@ void ExtruderImage::doRender(wxDC& dc) auto size = GetSize(); //dc.DrawRectangle(0, FromDIP(5), size.x, size.y - FromDIP(5) - FromDIP(2)); + if (m_generic_nozzle_display) { + const wxSize source_size = m_left_extruder_active_filled->GetBmpSize(); + const int count = std::min(m_nozzle_num, static_cast(m_multi_extruder_states.size())); + if (count <= 0 || size.x <= 0 || size.y <= 0 || source_size.x <= 0 || source_size.y <= 0) + return; + + double best_scale = 0.0; + int columns = 1; + int rows = count; + for (int candidate_columns = 1; candidate_columns <= count; ++candidate_columns) { + const int candidate_rows = (count + candidate_columns - 1) / candidate_columns; + const double scale = std::min(static_cast(size.x) / candidate_columns / source_size.x, + static_cast(size.y) / candidate_rows / source_size.y); + if (scale > best_scale) { + best_scale = scale; + columns = candidate_columns; + rows = candidate_rows; + } + } + + const int icon_width = std::max(1, static_cast(source_size.x * best_scale)); + const int icon_height = std::max(1, static_cast(source_size.y * best_scale)); + const int cell_width = size.x / columns; + const int cell_height = size.y / rows; + for (int i = 0; i < count; ++i) { + const ExtruderState state = m_multi_extruder_states[i]; + const bool filled = state == ExtruderState::FILLED_LOAD || state == ExtruderState::FILLED_UNLOAD; + const bool selected = i == current_nozzle_idx; + ScalableBitmap* icon = nullptr; + if (selected) + icon = filled ? m_left_extruder_active_filled : m_left_extruder_active_empty; + else + icon = filled ? m_left_extruder_unactive_filled : m_left_extruder_unactive_empty; + + const int col = i % columns; + const int row = i / columns; + const int x = col * cell_width + (cell_width - icon_width) / 2; + const int y = row * cell_height + (cell_height - icon_height) / 2; + const wxImage scaled_image = icon->bmp().ConvertToImage().Scale(icon_width, icon_height, wxIMAGE_QUALITY_HIGH); + dc.DrawBitmap(wxBitmap(scaled_image), x, y, true); + } + return; + } + auto pot = wxPoint(size.x / 2, (size.y - m_pipe_filled_load->GetBmpSize().y - m_left_extruder_active_filled->GetBmpSize().y) / 2); if (m_nozzle_num >= 2){ @@ -1702,9 +1785,19 @@ wxBoxSizer *StatusBasePanel::create_machine_control_page(wxWindow *parent) m_ams_rack_switch->Hide(); m_ams_rack_switch->Bind(wxCUSTOMEVT_SWITCH_POS, &StatusBasePanel::on_ams_rack_switch, this); + m_ams_nozzle_switch = new MultiSwitchButton(parent); + m_ams_nozzle_switch->SetFitToOptions(); + m_ams_nozzle_switch->Hide(); + m_ams_nozzle_switch->Bind(wxCUSTOMEVT_MULTISWITCH_SELECTION, [this](wxCommandEvent &event) { + if (m_ams_control) + m_ams_control->SelectNozzle(event.GetInt()); + event.Skip(); + }); + bSizer_control->Add(0, 0, 0, wxTOP, FromDIP(8)); bSizer_control->Add(temp_axis_slot, 0, wxALIGN_CENTER|wxLEFT|wxRIGHT, FromDIP(8)); bSizer_control->Add(m_ams_rack_switch, 0, wxALIGN_CENTRE|wxTOP, FromDIP(6)); + bSizer_control->Add(m_ams_nozzle_switch, 0, wxALIGN_CENTRE|wxTOP, FromDIP(6)); bSizer_control->Add(0, 0, 0, wxTOP, FromDIP(6)); bSizer_control->Add(ams_rack_sizer, 0, wxALIGN_CENTER|wxLEFT|wxRIGHT, FromDIP(8)); bSizer_control->Add(0, 0, 0, wxTOP, FromDIP(6)); @@ -1777,32 +1870,14 @@ wxBoxSizer *StatusBasePanel::create_temp_control(wxWindow *parent) { auto sizer = new wxBoxSizer(wxVERTICAL); - wxWindowID nozzle_id = wxWindow::NewControlId(); - m_tempCtrl_nozzle = new TempInput(parent, nozzle_id, TEMP_BLANK_STR, TempInputType::TEMP_OF_NORMAL_TYPE, TEMP_BLANK_STR, wxString("monitor_nozzle_temp"), - wxString("monitor_nozzle_temp_active"), wxDefaultPosition, wxDefaultSize, wxALIGN_CENTER); - m_tempCtrl_nozzle->SetMinSize(TEMP_CTRL_MIN_SIZE_ALIGN_TWO_ICON); - m_tempCtrl_nozzle->AddTemp(0); // zero is default temp - m_tempCtrl_nozzle->SetMinTemp(20); - m_tempCtrl_nozzle->SetMaxTemp(300); - m_tempCtrl_nozzle->SetBorderWidth(FromDIP(2)); + m_nozzle_temp_control_id = wxWindow::NewControlId(); + m_temp_nozzle_parent = parent; + m_temp_nozzle_sizer = sizer; - StateColor tempinput_text_colour(std::make_pair(DISCONNECT_TEXT_COL, (int) StateColor::Disabled), std::make_pair(NORMAL_TEXT_COL, (int) StateColor::Normal)); - StateColor tempinput_border_colour(std::make_pair(*wxWHITE, (int)StateColor::Disabled), std::make_pair(BUTTON_HOVER_COL, (int)StateColor::Focused), - std::make_pair(BUTTON_HOVER_COL, (int)StateColor::Hovered), std::make_pair(*wxWHITE, (int)StateColor::Normal)); - - m_tempCtrl_nozzle->SetTextColor(tempinput_text_colour); - m_tempCtrl_nozzle->SetBorderColor(tempinput_border_colour); - - m_tempCtrl_nozzle_deputy = new TempInput(parent, nozzle_id, TEMP_BLANK_STR, TempInputType::TEMP_OF_NORMAL_TYPE, TEMP_BLANK_STR, wxString("monitor_nozzle_temp"), wxString("monitor_nozzle_temp_active"), - wxDefaultPosition, wxDefaultSize, wxALIGN_CENTER); - m_tempCtrl_nozzle_deputy->SetMinSize(TEMP_CTRL_MIN_SIZE_ALIGN_TWO_ICON); - m_tempCtrl_nozzle_deputy->AddTemp(0); // zero is default temp - m_tempCtrl_nozzle_deputy->SetMinTemp(20); - m_tempCtrl_nozzle_deputy->SetMaxTemp(300); - m_tempCtrl_nozzle_deputy->SetBorderWidth(FromDIP(2)); - - m_tempCtrl_nozzle_deputy->SetTextColor(tempinput_text_colour); - m_tempCtrl_nozzle_deputy->SetBorderColor(tempinput_border_colour); + // Keep the two legacy controls for Bambu's main/deputy nozzle presentation. Generic + // printers use the grow-on-demand pool below, so these do not limit their nozzle count. + m_tempCtrl_nozzle = create_nozzle_temp_control(parent, m_nozzle_temp_control_id); + m_tempCtrl_nozzle_deputy = create_nozzle_temp_control(parent, m_nozzle_temp_control_id); sizer->Add(m_tempCtrl_nozzle_deputy, 0, wxEXPAND | wxALL, 1); sizer->Add(m_tempCtrl_nozzle, 0, wxEXPAND | wxALL, 1); @@ -1821,8 +1896,7 @@ wxBoxSizer *StatusBasePanel::create_temp_control(wxWindow *parent) m_tempCtrl_bed->SetMaxTemp(bed_temp_range[1]); m_tempCtrl_bed->SetMinSize(TEMP_CTRL_MIN_SIZE_ALIGN_ONE_ICON); m_tempCtrl_bed->SetBorderWidth(FromDIP(2)); - m_tempCtrl_bed->SetTextColor(tempinput_text_colour); - m_tempCtrl_bed->SetBorderColor(tempinput_border_colour); + set_temp_input_colors(m_tempCtrl_bed); sizer->Add(m_tempCtrl_bed, 0, wxEXPAND | wxALL, 1); auto line = new StaticLine(parent); @@ -1838,8 +1912,7 @@ wxBoxSizer *StatusBasePanel::create_temp_control(wxWindow *parent) m_tempCtrl_chamber->SetMaxTemp(default_champer_temp_max); m_tempCtrl_chamber->SetMinSize(TEMP_CTRL_MIN_SIZE_ALIGN_ONE_ICON); m_tempCtrl_chamber->SetBorderWidth(FromDIP(2)); - m_tempCtrl_chamber->SetTextColor(tempinput_text_colour); - m_tempCtrl_chamber->SetBorderColor(tempinput_border_colour); + set_temp_input_colors(m_tempCtrl_chamber); sizer->Add(m_tempCtrl_chamber, 0, wxEXPAND | wxALL, 1); m_misc_ctrl_sizer = create_misc_control(parent); @@ -1847,6 +1920,73 @@ wxBoxSizer *StatusBasePanel::create_temp_control(wxWindow *parent) return sizer; } +TempInput* StatusBasePanel::create_nozzle_temp_control(wxWindow* parent, wxWindowID id) +{ + auto* temp_ctrl = new TempInput(parent, id, TEMP_BLANK_STR, TempInputType::TEMP_OF_NORMAL_TYPE, TEMP_BLANK_STR, + wxString("monitor_nozzle_temp"), wxString("monitor_nozzle_temp_active"), + wxDefaultPosition, wxDefaultSize, wxALIGN_CENTER); + temp_ctrl->SetMinSize(TEMP_CTRL_MIN_SIZE_ALIGN_TWO_ICON); + temp_ctrl->AddTemp(0); // zero is default temp + temp_ctrl->SetMinTemp(20); + temp_ctrl->SetMaxTemp(300); + temp_ctrl->SetBorderWidth(FromDIP(2)); + + set_temp_input_colors(temp_ctrl); + return temp_ctrl; +} + +void StatusBasePanel::set_temp_input_colors(TempInput* temp_ctrl) +{ + StateColor tempinput_text_colour(std::make_pair(DISCONNECT_TEXT_COL, (int)StateColor::Disabled), + std::make_pair(NORMAL_TEXT_COL, (int)StateColor::Normal)); + StateColor tempinput_border_colour(std::make_pair(*wxWHITE, (int)StateColor::Disabled), + std::make_pair(BUTTON_HOVER_COL, (int)StateColor::Focused), + std::make_pair(BUTTON_HOVER_COL, (int)StateColor::Hovered), + std::make_pair(*wxWHITE, (int)StateColor::Normal)); + temp_ctrl->SetTextColor(tempinput_text_colour); + temp_ctrl->SetBorderColor(tempinput_border_colour); +} + +void StatusBasePanel::ensure_nozzle_temp_controls(size_t count) +{ + if (!m_temp_nozzle_parent || !m_temp_nozzle_sizer) + return; + + bool changed = count != m_temp_nozzle_active_count; + while (m_tempCtrl_nozzles.size() < count) { + TempInput* temp_ctrl = create_nozzle_temp_control(m_temp_nozzle_parent, m_nozzle_temp_control_id); + temp_ctrl->Hide(); + m_temp_nozzle_sizer->Insert(m_tempCtrl_nozzles.size(), temp_ctrl, 0, wxEXPAND | wxALL, 1); + m_tempCtrl_nozzles.push_back(temp_ctrl); + m_temp_nozzle_timeouts.push_back(0); + changed = true; + } + + const wxSize nozzle_size = count >= 2 ? TEMP_CTRL_MIN_SIZE_ALIGN_TWO_ICON : TEMP_CTRL_MIN_SIZE_ALIGN_ONE_ICON; + for (size_t i = 0; i < m_tempCtrl_nozzles.size(); ++i) { + const bool show = i < count; + if (m_tempCtrl_nozzles[i]->IsShown() != show) { + m_tempCtrl_nozzles[i]->Show(show); + changed = true; + } + if (m_tempCtrl_nozzles[i]->GetMinSize() != nozzle_size) { + m_tempCtrl_nozzles[i]->SetMinSize(nozzle_size); + changed = true; + } + } + + if (count < m_temp_nozzle_active_count) { + for (size_t i = count; i < m_temp_nozzle_timeouts.size(); ++i) + m_temp_nozzle_timeouts[i] = 0; + } + m_temp_nozzle_active_count = count; + + if (changed) { + m_temp_nozzle_parent->Layout(); + Layout(); + } +} + wxBoxSizer *StatusBasePanel::create_misc_control(wxWindow *parent) { wxBoxSizer *sizer = new wxBoxSizer(wxVERTICAL); @@ -1931,13 +2071,25 @@ wxBoxSizer *StatusBasePanel::create_misc_control(wxWindow *parent) void StatusBasePanel::reset_temp_misc_control() { // reset temp string - m_tempCtrl_nozzle->SetIconNormal(); - m_tempCtrl_nozzle->SetLabel(TEMP_BLANK_STR); - m_tempCtrl_nozzle->GetTextCtrl()->SetValue(TEMP_BLANK_STR); + if (wxGetApp().preset_bundle->is_bbl_vendor()) { + m_tempCtrl_nozzle->SetIconNormal(); + m_tempCtrl_nozzle->SetLabel(TEMP_BLANK_STR); + m_tempCtrl_nozzle->GetTextCtrl()->SetValue(TEMP_BLANK_STR); - m_tempCtrl_nozzle_deputy->SetIconNormal(); - m_tempCtrl_nozzle_deputy->SetLabel(TEMP_BLANK_STR); - m_tempCtrl_nozzle_deputy->GetTextCtrl()->SetValue(TEMP_BLANK_STR); + m_tempCtrl_nozzle_deputy->SetIconNormal(); + m_tempCtrl_nozzle_deputy->SetLabel(TEMP_BLANK_STR); + m_tempCtrl_nozzle_deputy->GetTextCtrl()->SetValue(TEMP_BLANK_STR); + + m_tempCtrl_nozzle->Enable(true); + m_tempCtrl_nozzle_deputy->Enable(true); + } else { + for (auto& tempCtrl : m_tempCtrl_nozzles) { + tempCtrl->SetIconNormal(); + tempCtrl->SetLabel(TEMP_BLANK_STR); + tempCtrl->GetTextCtrl()->SetValue(TEMP_BLANK_STR); + tempCtrl->Enable(true); + } + } m_tempCtrl_bed->SetIconNormal(); m_tempCtrl_bed->SetLabel(TEMP_BLANK_STR); @@ -1947,8 +2099,6 @@ void StatusBasePanel::reset_temp_misc_control() m_tempCtrl_chamber->SetLabel(TEMP_BLANK_STR); m_tempCtrl_chamber->GetTextCtrl()->SetValue(TEMP_BLANK_STR); - m_tempCtrl_nozzle->Enable(true); - m_tempCtrl_nozzle_deputy->Enable(true); m_tempCtrl_chamber->Enable(true); m_tempCtrl_bed->Enable(true); @@ -2060,6 +2210,9 @@ wxBoxSizer *StatusBasePanel::create_extruder_control(wxWindow *parent) m_nozzle_btn_panel = new SwitchBoard(panel, _L_CONTEXT("Left", "Nozzle position"), _L_CONTEXT("Right", "Nozzle position"), wxSize(FromDIP(126), FromDIP(26))); m_nozzle_btn_panel->SetAutoDisableWhenSwitch(); + m_generic_nozzle_selector = new wxChoice(panel, wxID_ANY, wxDefaultPosition, wxSize(FromDIP(126), FromDIP(26))); + m_generic_nozzle_selector->SetMinSize(wxSize(FromDIP(126), FromDIP(26))); + m_generic_nozzle_selector->Hide(); m_bpButton_e_10 = new Button(panel, "", "monitor_extruder_up", 0, 22); // Orca Dont scale icon size m_bpButton_e_10->SetBorderWidth(2); @@ -2092,6 +2245,7 @@ wxBoxSizer *StatusBasePanel::create_extruder_control(wxWindow *parent) bSizer_e_ctrl->Add(0, 0, 0, wxTOP, FromDIP(15)); bSizer_e_ctrl->Add(m_nozzle_btn_panel, 0, wxALIGN_CENTER_HORIZONTAL, 0); + bSizer_e_ctrl->Add(m_generic_nozzle_selector, 0, wxALIGN_CENTER_HORIZONTAL, 0); bSizer_e_ctrl->Add(0, 0, 0, wxTOP, FromDIP(15)); bSizer_e_ctrl->Add(m_bpButton_e_10, 0, wxALIGN_CENTER_HORIZONTAL, 0); bSizer_e_ctrl->Add(0, 0, 0, wxTOP, FromDIP(7)); @@ -2514,9 +2668,43 @@ StatusPanel::StatusPanel(wxWindow *parent, wxWindowID id, const wxPoint &pos, co add_build_step([this] { wire_controls(); }); } +void StatusPanel::sync_nozzle_temp_controls(size_t count) +{ + const size_t old_size = m_tempCtrl_nozzles.size(); + ensure_nozzle_temp_controls(count); + + bool layout_changed = false; + if (m_tempCtrl_nozzle->IsShown()) { + m_tempCtrl_nozzle->Hide(); + layout_changed = true; + } + if (m_tempCtrl_nozzle_deputy->IsShown()) { + m_tempCtrl_nozzle_deputy->Hide(); + layout_changed = true; + } + if (layout_changed) { + m_temp_nozzle_parent->Layout(); + Layout(); + } + + for (size_t i = old_size; i < m_tempCtrl_nozzles.size(); ++i) { + m_tempCtrl_nozzles[i]->Connect(wxEVT_KILL_FOCUS, wxFocusEventHandler(StatusPanel::on_nozzle_temp_kill_focus), NULL, this); + m_tempCtrl_nozzles[i]->Connect(wxEVT_SET_FOCUS, wxFocusEventHandler(StatusPanel::on_nozzle_temp_set_focus), NULL, this); + } +} + void StatusPanel::wire_controls() { init_scaled_buttons(); + const bool is_bbl_vendor = wxGetApp().preset_bundle->is_bbl_vendor(); + if (!is_bbl_vendor) + sync_nozzle_temp_controls(obj ? obj->GetExtderSystem()->GetTotalExtderCount() : 0); + if (is_bbl_vendor) + m_generic_nozzle_selector->Hide(); + else + m_nozzle_btn_panel->Hide(); + m_nozzle_btn_panel->GetParent()->Layout(); + m_buttons.push_back(m_bpButton_z_10); m_buttons.push_back(m_bpButton_z_1); m_buttons.push_back(m_bpButton_z_down_1); @@ -2546,13 +2734,24 @@ void StatusPanel::wire_controls() int id = e.GetInt(); if (id == m_tempCtrl_bed->GetType()) { on_set_bed_temp(); - } else if (id == m_tempCtrl_nozzle->GetType()) { - if (e.GetString() == wxString::Format("%d", MAIN_EXTRUDER_ID)) { - on_set_nozzle_temp(MAIN_EXTRUDER_ID); - } else if (e.GetString() == wxString::Format("%d", DEPUTY_EXTRUDER_ID)) { - on_set_nozzle_temp(DEPUTY_EXTRUDER_ID); + } else if (id == m_nozzle_temp_control_id) { + if (wxGetApp().preset_bundle->is_bbl_vendor()) { + if (e.GetString() == wxString::Format("%d", MAIN_EXTRUDER_ID)) { + on_set_nozzle_temp(MAIN_EXTRUDER_ID); + } else if (e.GetString() == wxString::Format("%d", DEPUTY_EXTRUDER_ID)) { + on_set_nozzle_temp(DEPUTY_EXTRUDER_ID); + } else { + on_set_nozzle_temp(UNIQUE_EXTRUDER_ID);//there is only one nozzle + } } else { - on_set_nozzle_temp(UNIQUE_EXTRUDER_ID);//there is only one nozzle + // N-extruder widgets all share TEMP_OF_NORMAL_TYPE, so e.GetString() can't + // disambiguate them; identify the one that fired by object identity instead. + for (size_t i = 0; i < m_tempCtrl_nozzles.size(); ++i) { + if (m_tempCtrl_nozzles[i] == e.GetEventObject()) { + on_set_nozzle_temp((int)i); + break; + } + } } } else if (id == m_tempCtrl_chamber->GetType()) { if (!m_tempCtrl_chamber->IsOnChanging()) { @@ -2577,10 +2776,12 @@ void StatusPanel::wire_controls() m_setting_button->Connect(wxEVT_LEFT_DCLICK, wxMouseEventHandler(StatusPanel::on_camera_enter), NULL, this); m_tempCtrl_bed->Connect(wxEVT_KILL_FOCUS, wxFocusEventHandler(StatusPanel::on_bed_temp_kill_focus), NULL, this); m_tempCtrl_bed->Connect(wxEVT_SET_FOCUS, wxFocusEventHandler(StatusPanel::on_bed_temp_set_focus), NULL, this); - m_tempCtrl_nozzle->Connect(wxEVT_KILL_FOCUS, wxFocusEventHandler(StatusPanel::on_nozzle_temp_kill_focus), NULL, this); - m_tempCtrl_nozzle->Connect(wxEVT_SET_FOCUS, wxFocusEventHandler(StatusPanel::on_nozzle_temp_set_focus), NULL, this); - m_tempCtrl_nozzle_deputy->Connect(wxEVT_KILL_FOCUS, wxFocusEventHandler(StatusPanel::on_nozzle_temp_kill_focus), NULL, this); - m_tempCtrl_nozzle_deputy->Connect(wxEVT_SET_FOCUS, wxFocusEventHandler(StatusPanel::on_nozzle_temp_set_focus), NULL, this); + if (wxGetApp().preset_bundle->is_bbl_vendor()) { + m_tempCtrl_nozzle->Connect(wxEVT_KILL_FOCUS, wxFocusEventHandler(StatusPanel::on_nozzle_temp_kill_focus), NULL, this); + m_tempCtrl_nozzle->Connect(wxEVT_SET_FOCUS, wxFocusEventHandler(StatusPanel::on_nozzle_temp_set_focus), NULL, this); + m_tempCtrl_nozzle_deputy->Connect(wxEVT_KILL_FOCUS, wxFocusEventHandler(StatusPanel::on_nozzle_temp_kill_focus), NULL, this); + m_tempCtrl_nozzle_deputy->Connect(wxEVT_SET_FOCUS, wxFocusEventHandler(StatusPanel::on_nozzle_temp_set_focus), NULL, this); + } m_tempCtrl_chamber->Connect(wxEVT_KILL_FOCUS, wxFocusEventHandler(StatusPanel::on_cham_temp_kill_focus), NULL, this); m_tempCtrl_chamber->Connect(wxEVT_SET_FOCUS, wxFocusEventHandler(StatusPanel::on_cham_temp_set_focus), NULL, this); m_switch_lamp->Connect(wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler(StatusPanel::on_lamp_switch), NULL, this); @@ -2600,6 +2801,7 @@ void StatusPanel::wire_controls() m_bpButton_e_10->Connect(wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler(StatusPanel::on_axis_ctrl_e_up_10), NULL, this); m_bpButton_e_down_10->Connect(wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler(StatusPanel::on_axis_ctrl_e_down_10), NULL, this); m_nozzle_btn_panel->Connect(wxCUSTOMEVT_SWITCH_POS, wxCommandEventHandler(StatusPanel::on_nozzle_selected), NULL, this); + m_generic_nozzle_selector->Connect(wxEVT_CHOICE, wxCommandEventHandler(StatusPanel::on_nozzle_selected), NULL, this); Bind(EVT_AMS_EXTRUSION_CALI, &StatusPanel::on_filament_extrusion_cali, this); Bind(EVT_AMS_LOAD, &StatusPanel::on_ams_load, this); @@ -2641,11 +2843,19 @@ StatusPanel::~StatusPanel() m_setting_button->Disconnect(wxEVT_LEFT_DCLICK, wxMouseEventHandler(StatusPanel::on_camera_enter), NULL, this); m_tempCtrl_bed->Disconnect(wxEVT_KILL_FOCUS, wxFocusEventHandler(StatusPanel::on_bed_temp_kill_focus), NULL, this); m_tempCtrl_bed->Disconnect(wxEVT_SET_FOCUS, wxFocusEventHandler(StatusPanel::on_bed_temp_set_focus), NULL, this); - m_tempCtrl_nozzle->Disconnect(wxEVT_KILL_FOCUS, wxFocusEventHandler(StatusPanel::on_nozzle_temp_kill_focus), NULL, this); - m_tempCtrl_nozzle->Disconnect(wxEVT_SET_FOCUS, wxFocusEventHandler(StatusPanel::on_nozzle_temp_set_focus), NULL, this); - m_tempCtrl_nozzle_deputy->Disconnect(wxEVT_KILL_FOCUS, wxFocusEventHandler(StatusPanel::on_nozzle_temp_kill_focus), NULL, this); - m_tempCtrl_nozzle_deputy->Disconnect(wxEVT_SET_FOCUS, wxFocusEventHandler(StatusPanel::on_nozzle_temp_set_focus), NULL, this); + if (wxGetApp().preset_bundle->is_bbl_vendor()) { + m_tempCtrl_nozzle->Disconnect(wxEVT_KILL_FOCUS, wxFocusEventHandler(StatusPanel::on_nozzle_temp_kill_focus), NULL, this); + m_tempCtrl_nozzle->Disconnect(wxEVT_SET_FOCUS, wxFocusEventHandler(StatusPanel::on_nozzle_temp_set_focus), NULL, this); + + m_tempCtrl_nozzle_deputy->Disconnect(wxEVT_KILL_FOCUS, wxFocusEventHandler(StatusPanel::on_nozzle_temp_kill_focus), NULL, this); + m_tempCtrl_nozzle_deputy->Disconnect(wxEVT_SET_FOCUS, wxFocusEventHandler(StatusPanel::on_nozzle_temp_set_focus), NULL, this); + } else { + for (auto& tempCtrl : m_tempCtrl_nozzles) { + tempCtrl->Disconnect(wxEVT_KILL_FOCUS, wxFocusEventHandler(StatusPanel::on_nozzle_temp_kill_focus), NULL, this); + tempCtrl->Disconnect(wxEVT_SET_FOCUS, wxFocusEventHandler(StatusPanel::on_nozzle_temp_set_focus), NULL, this); + } + } m_switch_lamp->Disconnect(wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler(StatusPanel::on_lamp_switch), NULL, this); /*m_switch_nozzle_fan->Disconnect(wxEVT_COMMAND_TOGGLEBUTTON_CLICKED, wxCommandEventHandler(StatusPanel::on_nozzle_fan_switch), NULL, this); @@ -2664,6 +2874,7 @@ StatusPanel::~StatusPanel() m_bpButton_e_10->Disconnect(wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler(StatusPanel::on_axis_ctrl_e_up_10), NULL, this); m_bpButton_e_down_10->Disconnect(wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler(StatusPanel::on_axis_ctrl_e_down_10), NULL, this); m_nozzle_btn_panel->Disconnect(wxCUSTOMEVT_SWITCH_POS, wxCommandEventHandler(StatusPanel::on_nozzle_selected), NULL, this); + m_generic_nozzle_selector->Disconnect(wxEVT_CHOICE, wxCommandEventHandler(StatusPanel::on_nozzle_selected), NULL, this); m_switch_speed->Disconnect(wxEVT_LEFT_DOWN, wxCommandEventHandler(StatusPanel::on_switch_speed), NULL, this); m_calibration_btn->Disconnect(wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler(StatusPanel::on_start_calibration), NULL, this); m_options_btn->Disconnect(wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler(StatusPanel::on_show_print_options), NULL, this); @@ -2921,6 +3132,7 @@ void StatusPanel::update(MachineObject *obj) if (!obj || !obj->is_info_ready()) { m_nozzle_btn_panel->Disable(); + m_generic_nozzle_selector->Disable(); return; } @@ -3115,8 +3327,14 @@ void StatusPanel::show_printing_status(bool ctrl_area, bool temp_area) } if (!temp_area) { - m_tempCtrl_nozzle->Enable(false); - m_tempCtrl_nozzle_deputy->Enable(false); + if (wxGetApp().preset_bundle->is_bbl_vendor()) { + m_tempCtrl_nozzle->Enable(false); + m_tempCtrl_nozzle_deputy->Enable(false); + } else { + for (auto& tempCtrl : m_tempCtrl_nozzles) + tempCtrl->Enable(false); + } + m_tempCtrl_bed->Enable(false); m_tempCtrl_chamber->Enable(false); m_switch_speed->Enable(false); @@ -3127,8 +3345,14 @@ void StatusPanel::show_printing_status(bool ctrl_area, bool temp_area) m_switch_cham_fan->Enable(false);*/ m_switch_fan->Enable(false); } else { - m_tempCtrl_nozzle->Enable(); - m_tempCtrl_nozzle_deputy->Enable(); + if (wxGetApp().preset_bundle->is_bbl_vendor()) { + m_tempCtrl_nozzle->Enable(); + m_tempCtrl_nozzle_deputy->Enable(); + } else { + for (auto& tempCtrl : m_tempCtrl_nozzles) + tempCtrl->Enable(); + } + m_tempCtrl_bed->Enable(); m_tempCtrl_chamber->Enable(); m_switch_speed->Enable(); @@ -3145,6 +3369,10 @@ void StatusPanel::update_temp_ctrl(MachineObject *obj) { if (!obj) return; + const int nozzle_num = obj->GetExtderSystem()->GetTotalExtderCount(); + if (!wxGetApp().preset_bundle->is_bbl_vendor()) + sync_nozzle_temp_controls(nozzle_num); + DevBed* bed = obj->GetBed(); int bed_cur_temp = bed->GetBedTemp(); int bed_target_temp = bed->GetBedTempTarget(); @@ -3157,11 +3385,18 @@ void StatusPanel::update_temp_ctrl(MachineObject *obj) m_tempCtrl_bed->SetMaxTemp(limit); if (obj->nozzle_temp_range.size() >= 2) { - m_tempCtrl_nozzle->SetMinTemp(obj->nozzle_temp_range[0]); - m_tempCtrl_nozzle->SetMaxTemp(obj->nozzle_temp_range[1]); + if (wxGetApp().preset_bundle->is_bbl_vendor()) { + m_tempCtrl_nozzle->SetMinTemp(obj->nozzle_temp_range[0]); + m_tempCtrl_nozzle->SetMaxTemp(obj->nozzle_temp_range[1]); - m_tempCtrl_nozzle_deputy->SetMinTemp(obj->nozzle_temp_range[0]); - m_tempCtrl_nozzle_deputy->SetMaxTemp(obj->nozzle_temp_range[1]); + m_tempCtrl_nozzle_deputy->SetMinTemp(obj->nozzle_temp_range[0]); + m_tempCtrl_nozzle_deputy->SetMaxTemp(obj->nozzle_temp_range[1]); + } else { + for (auto& tempCtrl : m_tempCtrl_nozzles) { + tempCtrl->SetMinTemp(obj->nozzle_temp_range[0]); + tempCtrl->SetMaxTemp(obj->nozzle_temp_range[1]); + } + } } // update temprature if not input temp target @@ -3178,77 +3413,143 @@ void StatusPanel::update_temp_ctrl(MachineObject *obj) } bool to_update_layout = false; - int nozzle_num = obj->GetExtderSystem()->GetTotalExtderCount(); - if (nozzle_num == 1) - { - m_tempCtrl_nozzle->SetCurrTemp(obj->GetExtderSystem()->GetNozzleTempCurrent(MAIN_EXTRUDER_ID)); - m_tempCtrl_nozzle->SetCurrType(TEMP_OF_NORMAL_TYPE); - m_tempCtrl_nozzle_deputy->SetCurrType(TEMP_OF_NORMAL_TYPE); - m_tempCtrl_nozzle_deputy->SetLabel(TEMP_BLANK_STR); - m_tempCtrl_nozzle_deputy->Hide(); - - if (m_tempCtrl_nozzle->GetMinSize() != TEMP_CTRL_MIN_SIZE_ALIGN_ONE_ICON) - { - to_update_layout = true; - m_tempCtrl_nozzle->SetMinSize(TEMP_CTRL_MIN_SIZE_ALIGN_ONE_ICON); + if (wxGetApp().preset_bundle->is_bbl_vendor()) { + for (auto& tempCtrl : m_tempCtrl_nozzles) { + if (tempCtrl->IsShown()) { + tempCtrl->Hide(); + to_update_layout = true; + } } - } - else if (nozzle_num == 2) - { - m_tempCtrl_nozzle->SetCurrType(TEMP_OF_MAIN_NOZZLE_TYPE); - m_tempCtrl_nozzle->SetCurrTemp(obj->GetExtderSystem()->GetNozzleTempCurrent(MAIN_EXTRUDER_ID)); - m_tempCtrl_nozzle->Show(); - m_tempCtrl_nozzle_deputy->SetCurrType(TEMP_OF_DEPUTY_NOZZLE_TYPE); - m_tempCtrl_nozzle_deputy->SetCurrTemp(obj->GetExtderSystem()->GetNozzleTempCurrent(DEPUTY_EXTRUDER_ID)); - m_tempCtrl_nozzle_deputy->Show(); - - if (m_tempCtrl_nozzle->GetMinSize() != TEMP_CTRL_MIN_SIZE_ALIGN_TWO_ICON) + if (nozzle_num == 1) { - to_update_layout = true; - m_tempCtrl_nozzle->SetMinSize(TEMP_CTRL_MIN_SIZE_ALIGN_TWO_ICON); - } - } + m_tempCtrl_nozzle->SetCurrTemp(obj->GetExtderSystem()->GetNozzleTempCurrent(MAIN_EXTRUDER_ID)); + m_tempCtrl_nozzle->SetCurrType(TEMP_OF_NORMAL_TYPE); - if (m_temp_nozzle_timeout > 0) { - m_temp_nozzle_timeout--; - } else { - if (!nozzle_temp_input) { - auto main_extder = obj->GetExtderSystem()->GetExtderById(MAIN_EXTRUDER_ID); - if (main_extder) + if (!m_tempCtrl_nozzle->IsShown()) { + m_tempCtrl_nozzle->Show(); + to_update_layout = true; + } + + m_tempCtrl_nozzle_deputy->SetCurrType(TEMP_OF_NORMAL_TYPE); + m_tempCtrl_nozzle_deputy->SetLabel(TEMP_BLANK_STR); + if (m_tempCtrl_nozzle_deputy->IsShown()) { + m_tempCtrl_nozzle_deputy->Hide(); + to_update_layout = true; + } + + if (m_tempCtrl_nozzle->GetMinSize() != TEMP_CTRL_MIN_SIZE_ALIGN_ONE_ICON) { - m_tempCtrl_nozzle->SetTagTemp(main_extder->GetTargetTemp()); - m_tempCtrl_nozzle->SetCurrTemp((int)main_extder->GetCurrentTemp()); - if (main_extder->GetTargetTemp() - main_extder->GetCurrentTemp() > TEMP_THRESHOLD_VAL) + to_update_layout = true; + m_tempCtrl_nozzle->SetMinSize(TEMP_CTRL_MIN_SIZE_ALIGN_ONE_ICON); + } + } + else if (nozzle_num == 2) + { + m_tempCtrl_nozzle->SetCurrType(TEMP_OF_MAIN_NOZZLE_TYPE); + m_tempCtrl_nozzle->SetCurrTemp(obj->GetExtderSystem()->GetNozzleTempCurrent(MAIN_EXTRUDER_ID)); + if (!m_tempCtrl_nozzle->IsShown()) { + m_tempCtrl_nozzle->Show(); + to_update_layout = true; + } + + m_tempCtrl_nozzle_deputy->SetCurrType(TEMP_OF_DEPUTY_NOZZLE_TYPE); + m_tempCtrl_nozzle_deputy->SetCurrTemp(obj->GetExtderSystem()->GetNozzleTempCurrent(DEPUTY_EXTRUDER_ID)); + if (!m_tempCtrl_nozzle_deputy->IsShown()) { + m_tempCtrl_nozzle_deputy->Show(); + to_update_layout = true; + } + + if (m_tempCtrl_nozzle->GetMinSize() != TEMP_CTRL_MIN_SIZE_ALIGN_TWO_ICON) + { + to_update_layout = true; + m_tempCtrl_nozzle->SetMinSize(TEMP_CTRL_MIN_SIZE_ALIGN_TWO_ICON); + } + } + } + else + { + if (m_tempCtrl_nozzle->IsShown()) { + m_tempCtrl_nozzle->Hide(); + to_update_layout = true; + } + if (m_tempCtrl_nozzle_deputy->IsShown()) { + m_tempCtrl_nozzle_deputy->Hide(); + to_update_layout = true; + } + for (size_t i = 0; i < static_cast(nozzle_num); ++i) + { + m_tempCtrl_nozzles[i]->SetCurrTemp(obj->GetExtderSystem()->GetNozzleTempCurrent((int)i)); + m_tempCtrl_nozzles[i]->SetCurrType(TEMP_OF_NORMAL_TYPE); + } + } + + if (wxGetApp().preset_bundle->is_bbl_vendor()) { + if (m_temp_nozzle_timeout > 0) { + m_temp_nozzle_timeout--; + } else { + if (!nozzle_temp_input) { + auto main_extder = obj->GetExtderSystem()->GetExtderById(MAIN_EXTRUDER_ID); + if (main_extder) { - m_tempCtrl_nozzle->SetIconActive(); + m_tempCtrl_nozzle->SetTagTemp(main_extder->GetTargetTemp()); + m_tempCtrl_nozzle->SetCurrTemp((int)main_extder->GetCurrentTemp()); + if (main_extder->GetTargetTemp() - main_extder->GetCurrentTemp() > TEMP_THRESHOLD_VAL) + { + m_tempCtrl_nozzle->SetIconActive(); + } + else + { + m_tempCtrl_nozzle->SetIconNormal(); + } } - else + } + } + + if (m_temp_nozzle_deputy_timeout > 0) { + m_temp_nozzle_deputy_timeout--; + } + else { + if (!nozzle_temp_input && nozzle_num >= 2) { + auto deputy_extder = obj->GetExtderSystem()->GetExtderById(DEPUTY_EXTRUDER_ID); + if (deputy_extder) { - m_tempCtrl_nozzle->SetIconNormal(); + m_tempCtrl_nozzle_deputy->SetTagTemp(deputy_extder->GetTargetTemp()); + m_tempCtrl_nozzle_deputy->SetCurrTemp((int)deputy_extder->GetCurrentTemp()); + if (deputy_extder->GetTargetTemp() - deputy_extder->GetCurrentTemp() > TEMP_THRESHOLD_VAL) + { + m_tempCtrl_nozzle_deputy->SetIconActive(); + } + else + { + m_tempCtrl_nozzle_deputy->SetIconNormal(); + } } } } } - - if (m_temp_nozzle_deputy_timeout > 0) { - m_temp_nozzle_deputy_timeout--; - } else { - if (!nozzle_temp_input && nozzle_num >= 2) { - auto deputy_extder = obj->GetExtderSystem()->GetExtderById(DEPUTY_EXTRUDER_ID); - if (deputy_extder) - { - m_tempCtrl_nozzle_deputy->SetTagTemp(deputy_extder->GetTargetTemp()); - m_tempCtrl_nozzle_deputy->SetCurrTemp((int)deputy_extder->GetCurrentTemp()); - if (deputy_extder->GetTargetTemp() - deputy_extder->GetCurrentTemp() > TEMP_THRESHOLD_VAL) - { - m_tempCtrl_nozzle_deputy->SetIconActive(); - } - else - { - m_tempCtrl_nozzle_deputy->SetIconNormal(); + assert(m_temp_nozzle_timeouts.size() == m_tempCtrl_nozzles.size()); + for (size_t i = 0; i < static_cast(nozzle_num); ++i) { + if (m_temp_nozzle_timeouts[i] > 0) { + m_temp_nozzle_timeouts[i]--; + } else { + if (!nozzle_temp_input) { + auto extder = obj->GetExtderSystem()->GetExtderById(i); + if (extder) + { + m_tempCtrl_nozzles[i]->SetTagTemp(extder->GetTargetTemp()); + m_tempCtrl_nozzles[i]->SetCurrTemp((int)extder->GetCurrentTemp()); + if (extder->GetTargetTemp() - extder->GetCurrentTemp() > TEMP_THRESHOLD_VAL) + { + m_tempCtrl_nozzles[i]->SetIconActive(); + } + else + { + m_tempCtrl_nozzles[i]->SetIconNormal(); + } + } } } } @@ -3321,65 +3622,91 @@ void StatusPanel::update_misc_ctrl(MachineObject *obj) /*extder*/ auto extder_system = obj->GetExtderSystem(); m_nozzle_num = extder_system->GetTotalExtderCount(); - int select_index = m_nozzle_num - 1; + const bool is_bbl_vendor = wxGetApp().preset_bundle->is_bbl_vendor(); + const bool bbl_selector_was_shown = m_nozzle_btn_panel->IsShown(); + const bool generic_selector_was_shown = m_generic_nozzle_selector->IsShown(); + if (m_nozzle_num <= 0) { + m_nozzle_btn_panel->Hide(); + m_generic_nozzle_selector->Hide(); + m_extruder_book->SetSelection(0); + } else if (is_bbl_vendor) { + // Preserve Bambu's existing main/deputy presentation and left/right selector. + const int display_num = std::min(m_nozzle_num, static_cast(m_extruderImage.size())); + const int select_index = display_num - 1; + ExtruderImage* image = m_extruderImage[select_index]; + image->setGenericNozzleDisplay(false); + image->setExtruderCount(m_nozzle_num); - if (m_nozzle_num >= 2) { - m_extruder_book->SetSelection(m_nozzle_num); + if (m_nozzle_num >= 2) { + m_extruder_book->SetSelection(display_num); + m_nozzle_btn_panel->Show(); + m_generic_nozzle_selector->Hide(); - /*style*/ - m_nozzle_btn_panel->Show(); - m_extruderImage[select_index]->setExtruderCount(m_nozzle_num); + if (extder_system->GetTotalExtderSize() > 1) { + image->update(get_extder_shown_state(extder_system->HasFilamentInExt(0)), + get_extder_shown_state(extder_system->HasFilamentInExt(1))); + } - if (obj->GetExtderSystem()->GetTotalExtderSize() > 1) - { - m_extruderImage[select_index]->update(get_extder_shown_state(obj->GetExtderSystem()->HasFilamentInExt(0)), - get_extder_shown_state(obj->GetExtderSystem()->HasFilamentInExt(1))); - } + if (extder_system->GetCurrentExtderId() == 0xf) { + image->setExtruderUsed(""); + m_nozzle_btn_panel->updateState(""); + } else if (extder_system->GetCurrentExtderId() == MAIN_EXTRUDER_ID) { + image->setExtruderUsed("right"); + m_nozzle_btn_panel->updateState("right"); + } else if (extder_system->GetCurrentExtderId() == DEPUTY_EXTRUDER_ID) { + image->setExtruderUsed("left"); + m_nozzle_btn_panel->updateState("left"); + } - /*current*/ - /*update when extder position changed or the machine changed*/ - if (obj->GetExtderSystem()->GetCurrentExtderId() == 0xf) - { - m_extruderImage[select_index]->setExtruderUsed(""); - m_nozzle_btn_panel->updateState(""); - } - else if (obj->GetExtderSystem()->GetCurrentExtderId() == MAIN_EXTRUDER_ID) - { - m_extruderImage[select_index]->setExtruderUsed("right"); - m_nozzle_btn_panel->updateState("right"); - } - else if (obj->GetExtderSystem()->GetCurrentExtderId() == DEPUTY_EXTRUDER_ID) - { - m_extruderImage[select_index]->setExtruderUsed("left"); - m_nozzle_btn_panel->updateState("left"); - } - - m_nozzle_btn_panel->SetClientData(obj); - - /*enable status*/ - /* Can do switch while printing pause STUDIO-9789*/ - if ((obj->is_in_printing() && !obj->is_in_printing_pause()) || - obj->ams_status_main == AMS_STATUS_MAIN_FILAMENT_CHANGE || - obj->targ_nozzle_id_from_pc != INVALID_EXTRUDER_ID) - { - m_nozzle_btn_panel->Disable(); - } - else - { - m_nozzle_btn_panel->Enable(); + m_nozzle_btn_panel->SetClientData(obj); + if ((obj->is_in_printing() && !obj->is_in_printing_pause()) || + obj->ams_status_main == AMS_STATUS_MAIN_FILAMENT_CHANGE || + obj->targ_nozzle_id_from_pc != INVALID_EXTRUDER_ID) + m_nozzle_btn_panel->Disable(); + else + m_nozzle_btn_panel->Enable(); + } else { + m_nozzle_btn_panel->Hide(); + m_generic_nozzle_selector->Hide(); + m_extruder_book->SetSelection(display_num); + if (extder_system->GetTotalExtderSize() > 0) + image->update(get_extder_shown_state(extder_system->HasFilamentInExt(0))); } } else { m_nozzle_btn_panel->Hide(); - m_extruder_book->SetSelection(m_nozzle_num); - m_extruderImage[select_index]->setExtruderCount(m_nozzle_num); + m_extruder_book->SetSelection(1); + ExtruderImage* image = m_extruderImage.front(); + image->setGenericNozzleDisplay(true); + image->setExtruderCount(m_nozzle_num); - if (extder_system->GetTotalExtderSize() > 0) - { - ExtruderState shown_state = get_extder_shown_state(extder_system->HasFilamentInExt(0)); - m_extruderImage[select_index]->update(shown_state); + const int current_nozzle_id = extder_system->GetCurrentExtderId(); + const int selected_nozzle_id = current_nozzle_id >= 0 && current_nozzle_id < m_nozzle_num ? current_nozzle_id : -1; + image->setExtruderUsed(selected_nozzle_id); + for (int i = 0; i < m_nozzle_num; ++i) + image->update(get_extder_shown_state(extder_system->HasFilamentInExt(i)), i); + image->Refresh(); + + if (m_generic_nozzle_selector_count != m_nozzle_num) { + m_generic_nozzle_selector->Clear(); + for (int i = 0; i < m_nozzle_num; ++i) + m_generic_nozzle_selector->Append(wxString::Format("%s %d", _L("Toolhead"), i + 1)); + m_generic_nozzle_selector_count = m_nozzle_num; } + m_generic_nozzle_selector->SetSelection(selected_nozzle_id); + m_generic_nozzle_selector->Show(m_nozzle_num > 1); + + if ((obj->is_in_printing() && !obj->is_in_printing_pause()) || + obj->ams_status_main == AMS_STATUS_MAIN_FILAMENT_CHANGE || + obj->targ_nozzle_id_from_pc != INVALID_EXTRUDER_ID) + m_generic_nozzle_selector->Disable(); + else + m_generic_nozzle_selector->Enable(); } + if (bbl_selector_was_shown != m_nozzle_btn_panel->IsShown() || + generic_selector_was_shown != m_generic_nozzle_selector->IsShown()) + m_nozzle_btn_panel->GetParent()->Layout(); + /*switch extder*/ m_extruder_switching_status->updateBy(obj); m_extruder_label->Show(!m_extruder_switching_status->has_content_shown());/*hide the label if there are shown infos from m_extruder_switching_status*/ @@ -3521,6 +3848,25 @@ void StatusPanel::update_ams(MachineObject *obj) // must select a current can m_ams_control->UpdateAms(obj->get_printer_series_str(), obj->printer_type, ams_info, ext_info, *obj->GetExtderSystem(), obj->get_dev_id(), obj, false); + + const bool show_generic_nozzle_switch = + obj->GetExtderSystem()->GetTotalExtderCount() > 2 && + !wxGetApp().preset_bundle->is_bbl_vendor(); + if (show_generic_nozzle_switch) { + std::vector nozzle_options; + const int nozzle_count = obj->GetExtderSystem()->GetTotalExtderCount(); + if (m_ams_nozzle_switch->GetCount() != static_cast(nozzle_count)) { + nozzle_options.reserve(nozzle_count); + for (int nozzle_id = 0; nozzle_id < nozzle_count; ++nozzle_id) + nozzle_options.push_back(wxString::Format(_L("T%d"), nozzle_id + 1)); + m_ams_nozzle_switch->SetOptions(nozzle_options); + } + m_ams_nozzle_switch->SetSelection(m_ams_control->GetActiveNozzleId()); + m_ams_nozzle_switch->Show(); + } else { + m_ams_nozzle_switch->Hide(); + } + m_ams_nozzle_switch->GetParent()->Layout(); m_ams_control->UpdateAmsDryControl(obj); last_tray_exist_bits = obj->tray_exist_bits; @@ -3557,34 +3903,50 @@ void StatusPanel::update_ams(MachineObject *obj) // m_ams_control->SetExtruder(obj->is_filament_at_extruder(), obj->m_extder_data.extders[MAIN_NOZZLE_ID].snow.ams_id, obj->m_extder_data.extders[MAIN_NOZZLE_ID].snow.slot_id); //} else { + if (wxGetApp().preset_bundle->is_bbl_vendor()) { /*right*/ - if (obj->GetExtderSystem()->GetTotalExtderCount() > 0) { - auto ext = obj->GetExtderSystem()->GetExtderById(MAIN_EXTRUDER_ID); - if (ext->HasFilamentInExt()) { - if (ext->GetSlotNow().ams_id == std::to_string(VIRTUAL_TRAY_MAIN_ID) || ext->GetSlotNow().ams_id == std::to_string(VIRTUAL_TRAY_DEPUTY_ID)) { - m_ams_control->SetAmsStep(ext->GetSlotNow().ams_id, "0", AMSPassRoadType::AMS_ROAD_TYPE_LOAD, AMSPassRoadSTEP::AMS_ROAD_STEP_COMBO_LOAD_STEP3); + if (obj->GetExtderSystem()->GetTotalExtderCount() > 0) { + auto ext = obj->GetExtderSystem()->GetExtderById(MAIN_EXTRUDER_ID); + if (ext->HasFilamentInExt()) { + if (ext->GetSlotNow().ams_id == std::to_string(VIRTUAL_TRAY_MAIN_ID) || ext->GetSlotNow().ams_id == std::to_string(VIRTUAL_TRAY_DEPUTY_ID)) { + m_ams_control->SetAmsStep(ext->GetSlotNow().ams_id, "0", AMSPassRoadType::AMS_ROAD_TYPE_LOAD, AMSPassRoadSTEP::AMS_ROAD_STEP_COMBO_LOAD_STEP3); + } else { + m_ams_control->SetAmsStep(ext->GetSlotNow().ams_id, ext->GetSlotNow().slot_id, AMSPassRoadType::AMS_ROAD_TYPE_LOAD, AMSPassRoadSTEP::AMS_ROAD_STEP_COMBO_LOAD_STEP2); + } } else { - m_ams_control->SetAmsStep(ext->GetSlotNow().ams_id, ext->GetSlotNow().slot_id, AMSPassRoadType::AMS_ROAD_TYPE_LOAD, AMSPassRoadSTEP::AMS_ROAD_STEP_COMBO_LOAD_STEP2); + m_ams_control->SetAmsStep(ext->GetSlotNow().ams_id, ext->GetSlotNow().slot_id, AMSPassRoadType::AMS_ROAD_TYPE_UNLOAD, AMSPassRoadSTEP::AMS_ROAD_STEP_NONE); } - } else { - m_ams_control->SetAmsStep(ext->GetSlotNow().ams_id, ext->GetSlotNow().slot_id, AMSPassRoadType::AMS_ROAD_TYPE_UNLOAD, AMSPassRoadSTEP::AMS_ROAD_STEP_NONE); + m_ams_control->SetExtruder(ext->HasFilamentInExt(), MAIN_EXTRUDER_ID, ext->GetSlotNow().ams_id, ext->GetSlotNow().slot_id); } - m_ams_control->SetExtruder(ext->HasFilamentInExt(), MAIN_EXTRUDER_ID, ext->GetSlotNow().ams_id, ext->GetSlotNow().slot_id); - } - /*left*/ - if (obj->GetExtderSystem()->GetTotalExtderCount() > 1) { - auto ext = obj->GetExtderSystem()->GetExtderById(DEPUTY_EXTRUDER_ID); - if (ext->HasFilamentInExt()) { - if (ext->GetSlotNow().ams_id == std::to_string(VIRTUAL_TRAY_MAIN_ID) || ext->GetSlotNow().ams_id == std::to_string(VIRTUAL_TRAY_DEPUTY_ID)) { - m_ams_control->SetAmsStep(ext->GetSlotNow().ams_id, "0", AMSPassRoadType::AMS_ROAD_TYPE_LOAD, AMSPassRoadSTEP::AMS_ROAD_STEP_COMBO_LOAD_STEP3); + /*left*/ + if (obj->GetExtderSystem()->GetTotalExtderCount() > 1) { + auto ext = obj->GetExtderSystem()->GetExtderById(DEPUTY_EXTRUDER_ID); + if (ext->HasFilamentInExt()) { + if (ext->GetSlotNow().ams_id == std::to_string(VIRTUAL_TRAY_MAIN_ID) || ext->GetSlotNow().ams_id == std::to_string(VIRTUAL_TRAY_DEPUTY_ID)) { + m_ams_control->SetAmsStep(ext->GetSlotNow().ams_id, "0", AMSPassRoadType::AMS_ROAD_TYPE_LOAD, AMSPassRoadSTEP::AMS_ROAD_STEP_COMBO_LOAD_STEP3); + } else { + m_ams_control->SetAmsStep(ext->GetSlotNow().ams_id, ext->GetSlotNow().slot_id, AMSPassRoadType::AMS_ROAD_TYPE_LOAD, AMSPassRoadSTEP::AMS_ROAD_STEP_COMBO_LOAD_STEP2); + } } else { - m_ams_control->SetAmsStep(ext->GetSlotNow().ams_id, ext->GetSlotNow().slot_id, AMSPassRoadType::AMS_ROAD_TYPE_LOAD, AMSPassRoadSTEP::AMS_ROAD_STEP_COMBO_LOAD_STEP2); + m_ams_control->SetAmsStep(ext->GetSlotNow().ams_id, ext->GetSlotNow().slot_id, AMSPassRoadType::AMS_ROAD_TYPE_UNLOAD, AMSPassRoadSTEP::AMS_ROAD_STEP_NONE); } - } else { - m_ams_control->SetAmsStep(ext->GetSlotNow().ams_id, ext->GetSlotNow().slot_id, AMSPassRoadType::AMS_ROAD_TYPE_UNLOAD, AMSPassRoadSTEP::AMS_ROAD_STEP_NONE); + m_ams_control->SetExtruder(ext->HasFilamentInExt(), DEPUTY_EXTRUDER_ID, ext->GetSlotNow().ams_id, ext->GetSlotNow().slot_id); + } + } else { + /*all extruders, in id order*/ + for (const auto& ext : obj->GetExtderSystem()->GetExtruders()) { + if (ext.HasFilamentInExt()) { + if (ext.GetSlotNow().ams_id == std::to_string(VIRTUAL_TRAY_MAIN_ID) || ext.GetSlotNow().ams_id == std::to_string(VIRTUAL_TRAY_DEPUTY_ID)) { + m_ams_control->SetAmsStep(ext.GetSlotNow().ams_id, "0", AMSPassRoadType::AMS_ROAD_TYPE_LOAD, AMSPassRoadSTEP::AMS_ROAD_STEP_COMBO_LOAD_STEP3); + } else { + m_ams_control->SetAmsStep(ext.GetSlotNow().ams_id, ext.GetSlotNow().slot_id, AMSPassRoadType::AMS_ROAD_TYPE_LOAD, AMSPassRoadSTEP::AMS_ROAD_STEP_COMBO_LOAD_STEP2); + } + } else { + m_ams_control->SetAmsStep(ext.GetSlotNow().ams_id, ext.GetSlotNow().slot_id, AMSPassRoadType::AMS_ROAD_TYPE_UNLOAD, AMSPassRoadSTEP::AMS_ROAD_STEP_NONE); + } + m_ams_control->SetExtruder(ext.HasFilamentInExt(), ext.GetExtId(), ext.GetSlotNow().ams_id, ext.GetSlotNow().slot_id); } - m_ams_control->SetExtruder(ext->HasFilamentInExt(), DEPUTY_EXTRUDER_ID, ext->GetSlotNow().ams_id, ext->GetSlotNow().slot_id); } update_filament_loading_panel(obj); @@ -4291,33 +4653,49 @@ void StatusPanel::on_set_nozzle_temp(int nozzle_id) try { long nozzle_temp; - if (nozzle_id == MAIN_EXTRUDER_ID) { - wxString str = m_tempCtrl_nozzle->GetTextCtrl()->GetValue(); - if (str.ToLong(&nozzle_temp) && obj) { - set_hold_count(m_temp_nozzle_timeout); - if (nozzle_temp > m_tempCtrl_nozzle->get_max_temp()) { - nozzle_temp = m_tempCtrl_nozzle->get_max_temp(); - m_tempCtrl_nozzle->SetTagTemp(wxString::Format("%d", nozzle_temp)); - m_tempCtrl_nozzle->Warning(false); + if (wxGetApp().preset_bundle->is_bbl_vendor()) { + if (nozzle_id == MAIN_EXTRUDER_ID) { + wxString str = m_tempCtrl_nozzle->GetTextCtrl()->GetValue(); + if (str.ToLong(&nozzle_temp) && obj) { + set_hold_count(m_temp_nozzle_timeout); + if (nozzle_temp > m_tempCtrl_nozzle->get_max_temp()) { + nozzle_temp = m_tempCtrl_nozzle->get_max_temp(); + m_tempCtrl_nozzle->SetTagTemp(wxString::Format("%d", nozzle_temp)); + m_tempCtrl_nozzle->Warning(false); + } + if (m_tempCtrl_nozzle->GetCurrType() == TempInputType::TEMP_OF_NORMAL_TYPE) { + obj->command_set_nozzle(nozzle_temp); + } else { + obj->command_set_nozzle_new(MAIN_EXTRUDER_ID, nozzle_temp); + } } - if (m_tempCtrl_nozzle->GetCurrType() == TempInputType::TEMP_OF_NORMAL_TYPE) { - obj->command_set_nozzle(nozzle_temp); - } else { - obj->command_set_nozzle_new(MAIN_EXTRUDER_ID, nozzle_temp); + } + + if (nozzle_id == DEPUTY_EXTRUDER_ID) { + wxString str = m_tempCtrl_nozzle_deputy->GetTextCtrl()->GetValue(); + if (str.ToLong(&nozzle_temp) && obj) { + set_hold_count(m_temp_nozzle_deputy_timeout); + if (nozzle_temp > m_tempCtrl_nozzle_deputy->get_max_temp()) { + nozzle_temp = m_tempCtrl_nozzle_deputy->get_max_temp(); + m_tempCtrl_nozzle_deputy->SetTagTemp(wxString::Format("%d", nozzle_temp)); + m_tempCtrl_nozzle_deputy->Warning(false); + } + obj->command_set_nozzle_new(DEPUTY_EXTRUDER_ID, nozzle_temp); } } } - - if (nozzle_id == DEPUTY_EXTRUDER_ID) { - wxString str = m_tempCtrl_nozzle_deputy->GetTextCtrl()->GetValue(); + else if (nozzle_id >= 0 && nozzle_id < (int)m_tempCtrl_nozzles.size() && + nozzle_id < obj->GetExtderSystem()->GetTotalExtderCount()) { + assert(m_tempCtrl_nozzles.size() == m_temp_nozzle_timeouts.size()); + wxString str = m_tempCtrl_nozzles[nozzle_id]->GetTextCtrl()->GetValue(); if (str.ToLong(&nozzle_temp) && obj) { - set_hold_count(m_temp_nozzle_deputy_timeout); - if (nozzle_temp > m_tempCtrl_nozzle_deputy->get_max_temp()) { - nozzle_temp = m_tempCtrl_nozzle_deputy->get_max_temp(); - m_tempCtrl_nozzle_deputy->SetTagTemp(wxString::Format("%d", nozzle_temp)); - m_tempCtrl_nozzle_deputy->Warning(false); + set_hold_count(m_temp_nozzle_timeouts[nozzle_id]); + if (nozzle_temp > m_tempCtrl_nozzles[nozzle_id]->get_max_temp()) { + nozzle_temp = m_tempCtrl_nozzles[nozzle_id]->get_max_temp(); + m_tempCtrl_nozzles[nozzle_id]->SetTagTemp(wxString::Format("%d", nozzle_temp)); + m_tempCtrl_nozzles[nozzle_id]->Warning(false); } - obj->command_set_nozzle_new(DEPUTY_EXTRUDER_ID, nozzle_temp); + obj->command_set_nozzle_new(nozzle_id, nozzle_temp); } } } catch (...) { @@ -4401,26 +4779,44 @@ void StatusPanel::on_ams_load_curr() return; } - // Filament Track Switch is calibrated: the load can go to either extruder, so ask the - // user which. Record each extruder's currently-loaded slot so the dialog can grey out a - // side that already holds this filament. - std::vector> extruderSlots(2, {"", ""}); - if (auto ext = obj->GetExtderSystem()->GetExtderById(MAIN_EXTRUDER_ID); ext.has_value()) - { - if (ext->HasFilamentInExt()) + // Filament Track Switch is calibrated: the load can go to either/any extruder, so ask + // the user which. Record each extruder's currently-loaded slot so the dialog can grey + // out a side that already holds this filament. + // NOTE: FeedDirectionDialog/SetExtruderMapping (Widgets/AMSItem.{hpp,cpp}) take the real + // extruder count in the non-BBL path now, but that class's own internals haven't been + // audited for N > 2 -- this only fixes what's local to StatusPanel.cpp. + int extruder_count; + std::vector> extruderSlots; + if (wxGetApp().preset_bundle->is_bbl_vendor()) { + extruder_count = 2; + extruderSlots.assign(2, {"", ""}); + if (auto ext = obj->GetExtderSystem()->GetExtderById(MAIN_EXTRUDER_ID); ext.has_value()) { - extruderSlots[MAIN_EXTRUDER_ID] = {ext->GetSlotNow().ams_id, ext->GetSlotNow().slot_id}; + if (ext->HasFilamentInExt()) + { + extruderSlots[MAIN_EXTRUDER_ID] = {ext->GetSlotNow().ams_id, ext->GetSlotNow().slot_id}; + } } - } - if (auto ext = obj->GetExtderSystem()->GetExtderById(DEPUTY_EXTRUDER_ID); ext.has_value()) - { - if (ext->HasFilamentInExt()) + if (auto ext = obj->GetExtderSystem()->GetExtderById(DEPUTY_EXTRUDER_ID); ext.has_value()) { - extruderSlots[DEPUTY_EXTRUDER_ID] = {ext->GetSlotNow().ams_id, ext->GetSlotNow().slot_id}; + if (ext->HasFilamentInExt()) + { + extruderSlots[DEPUTY_EXTRUDER_ID] = {ext->GetSlotNow().ams_id, ext->GetSlotNow().slot_id}; + } + } + } else { + extruder_count = obj->GetExtderSystem()->GetTotalExtderCount(); + extruderSlots.assign(extruder_count, {"", ""}); + for (const auto& ext : obj->GetExtderSystem()->GetExtruders()) + { + if (ext.HasFilamentInExt()) + { + extruderSlots[ext.GetExtId()] = {ext.GetSlotNow().ams_id, ext.GetSlotNow().slot_id}; + } } } - FeedDirectionDialog dialog(nullptr, 2, obj->printer_type); + FeedDirectionDialog dialog(nullptr, extruder_count, obj->printer_type); dialog.SetExtruderMapping(obj, curr_ams_id, curr_can_id, extruderSlots); auto rtn = dialog.ShowModal(); @@ -4522,34 +4918,53 @@ void StatusPanel::on_ams_switch(SimpleEvent &event) { if(obj){ - /*right*/ - if (obj->GetExtderSystem()->GetTotalExtderCount() > 0) { - auto ext = obj->GetExtderSystem()->GetExtderById(MAIN_EXTRUDER_ID); - if (ext->HasFilamentInExt()) { - if (ext->GetSlotNow().ams_id == std::to_string(VIRTUAL_TRAY_MAIN_ID) || ext->GetSlotNow().ams_id == std::to_string(VIRTUAL_TRAY_DEPUTY_ID)) { - m_ams_control->SetAmsStep(ext->GetSlotNow().ams_id, "0", AMSPassRoadType::AMS_ROAD_TYPE_LOAD, AMSPassRoadSTEP::AMS_ROAD_STEP_COMBO_LOAD_STEP3); - } else { - m_ams_control->SetAmsStep(ext->GetSlotNow().ams_id, ext->GetSlotNow().slot_id, AMSPassRoadType::AMS_ROAD_TYPE_LOAD, AMSPassRoadSTEP::AMS_ROAD_STEP_COMBO_LOAD_STEP2); - } - } else { - m_ams_control->SetAmsStep(ext->GetSlotNow().ams_id, ext->GetSlotNow().slot_id, AMSPassRoadType::AMS_ROAD_TYPE_UNLOAD, AMSPassRoadSTEP::AMS_ROAD_STEP_NONE); - } - m_ams_control->SetExtruder(ext->HasFilamentInExt(), MAIN_EXTRUDER_ID, ext->GetSlotNow().ams_id, ext->GetSlotNow().slot_id); - } + if (m_ams_nozzle_switch && m_ams_nozzle_switch->IsShown()) + m_ams_nozzle_switch->SetSelection(m_ams_control->GetActiveNozzleId()); - /*left*/ - if (obj->GetExtderSystem()->GetTotalExtderCount() > 1) { - auto ext = obj->GetExtderSystem()->GetExtruders()[DEPUTY_EXTRUDER_ID]; - if (ext.HasFilamentInExt()) { - if (ext.GetSlotNow().ams_id == std::to_string(VIRTUAL_TRAY_MAIN_ID) || ext.GetSlotNow().ams_id == std::to_string(VIRTUAL_TRAY_DEPUTY_ID)) { - m_ams_control->SetAmsStep(ext.GetSlotNow().ams_id, "0", AMSPassRoadType::AMS_ROAD_TYPE_LOAD, AMSPassRoadSTEP::AMS_ROAD_STEP_COMBO_LOAD_STEP3); + if (wxGetApp().preset_bundle->is_bbl_vendor()) { + /*right*/ + if (obj->GetExtderSystem()->GetTotalExtderCount() > 0) { + auto ext = obj->GetExtderSystem()->GetExtderById(MAIN_EXTRUDER_ID); + if (ext->HasFilamentInExt()) { + if (ext->GetSlotNow().ams_id == std::to_string(VIRTUAL_TRAY_MAIN_ID) || ext->GetSlotNow().ams_id == std::to_string(VIRTUAL_TRAY_DEPUTY_ID)) { + m_ams_control->SetAmsStep(ext->GetSlotNow().ams_id, "0", AMSPassRoadType::AMS_ROAD_TYPE_LOAD, AMSPassRoadSTEP::AMS_ROAD_STEP_COMBO_LOAD_STEP3); + } else { + m_ams_control->SetAmsStep(ext->GetSlotNow().ams_id, ext->GetSlotNow().slot_id, AMSPassRoadType::AMS_ROAD_TYPE_LOAD, AMSPassRoadSTEP::AMS_ROAD_STEP_COMBO_LOAD_STEP2); + } } else { - m_ams_control->SetAmsStep(ext.GetSlotNow().ams_id, ext.GetSlotNow().slot_id, AMSPassRoadType::AMS_ROAD_TYPE_LOAD, AMSPassRoadSTEP::AMS_ROAD_STEP_COMBO_LOAD_STEP2); + m_ams_control->SetAmsStep(ext->GetSlotNow().ams_id, ext->GetSlotNow().slot_id, AMSPassRoadType::AMS_ROAD_TYPE_UNLOAD, AMSPassRoadSTEP::AMS_ROAD_STEP_NONE); } - } else { - m_ams_control->SetAmsStep(ext.GetSlotNow().ams_id, ext.GetSlotNow().slot_id, AMSPassRoadType::AMS_ROAD_TYPE_UNLOAD, AMSPassRoadSTEP::AMS_ROAD_STEP_NONE); + m_ams_control->SetExtruder(ext->HasFilamentInExt(), MAIN_EXTRUDER_ID, ext->GetSlotNow().ams_id, ext->GetSlotNow().slot_id); + } + + /*left*/ + if (obj->GetExtderSystem()->GetTotalExtderCount() > 1) { + auto ext = obj->GetExtderSystem()->GetExtderById(DEPUTY_EXTRUDER_ID); + if (ext->HasFilamentInExt()) { + if (ext->GetSlotNow().ams_id == std::to_string(VIRTUAL_TRAY_MAIN_ID) || ext->GetSlotNow().ams_id == std::to_string(VIRTUAL_TRAY_DEPUTY_ID)) { + m_ams_control->SetAmsStep(ext->GetSlotNow().ams_id, "0", AMSPassRoadType::AMS_ROAD_TYPE_LOAD, AMSPassRoadSTEP::AMS_ROAD_STEP_COMBO_LOAD_STEP3); + } else { + m_ams_control->SetAmsStep(ext->GetSlotNow().ams_id, ext->GetSlotNow().slot_id, AMSPassRoadType::AMS_ROAD_TYPE_LOAD, AMSPassRoadSTEP::AMS_ROAD_STEP_COMBO_LOAD_STEP2); + } + } else { + m_ams_control->SetAmsStep(ext->GetSlotNow().ams_id, ext->GetSlotNow().slot_id, AMSPassRoadType::AMS_ROAD_TYPE_UNLOAD, AMSPassRoadSTEP::AMS_ROAD_STEP_NONE); + } + m_ams_control->SetExtruder(ext->HasFilamentInExt(), DEPUTY_EXTRUDER_ID, ext->GetSlotNow().ams_id, ext->GetSlotNow().slot_id); + } + } else { + /*all extruders, in id order*/ + for (const auto& ext : obj->GetExtderSystem()->GetExtruders()) { + if (ext.HasFilamentInExt()) { + if (ext.GetSlotNow().ams_id == std::to_string(VIRTUAL_TRAY_MAIN_ID) || ext.GetSlotNow().ams_id == std::to_string(VIRTUAL_TRAY_DEPUTY_ID)) { + m_ams_control->SetAmsStep(ext.GetSlotNow().ams_id, "0", AMSPassRoadType::AMS_ROAD_TYPE_LOAD, AMSPassRoadSTEP::AMS_ROAD_STEP_COMBO_LOAD_STEP3); + } else { + m_ams_control->SetAmsStep(ext.GetSlotNow().ams_id, ext.GetSlotNow().slot_id, AMSPassRoadType::AMS_ROAD_TYPE_LOAD, AMSPassRoadSTEP::AMS_ROAD_STEP_COMBO_LOAD_STEP2); + } + } else { + m_ams_control->SetAmsStep(ext.GetSlotNow().ams_id, ext.GetSlotNow().slot_id, AMSPassRoadType::AMS_ROAD_TYPE_UNLOAD, AMSPassRoadSTEP::AMS_ROAD_STEP_NONE); + } + m_ams_control->SetExtruder(ext.HasFilamentInExt(), ext.GetExtId(), ext.GetSlotNow().ams_id, ext.GetSlotNow().slot_id); } - m_ams_control->SetExtruder(ext.HasFilamentInExt(), DEPUTY_EXTRUDER_ID, ext.GetSlotNow().ams_id, ext.GetSlotNow().slot_id); } } } @@ -5127,23 +5542,42 @@ void StatusPanel::on_xyz_abs(wxCommandEvent &event) void StatusPanel::on_nozzle_selected(wxCommandEvent &event) { + const bool is_bbl_vendor = wxGetApp().preset_bundle->is_bbl_vendor(); if (obj) { - - /*Enable switch head while printing is paused STUDIO-9789*/ - if ((obj->is_in_printing() && !obj->is_in_printing_pause()) || obj->ams_status_main == AMS_STATUS_MAIN_FILAMENT_CHANGE) { - MessageDialog dlg(nullptr, _L("The printer is busy with another print job."), _L("Error"), wxICON_WARNING | wxOK); - dlg.ShowModal(); + const int nozzle_id = event.GetInt(); + const auto extder_system = obj->GetExtderSystem(); + const int current_nozzle_id = extder_system->GetCurrentExtderId(); + const int current_selection = current_nozzle_id >= 0 && current_nozzle_id < extder_system->GetTotalExtderCount() + ? current_nozzle_id + : wxNOT_FOUND; + const bool nozzle_change_in_progress = !is_bbl_vendor && obj->targ_nozzle_id_from_pc != INVALID_EXTRUDER_ID; + if (!is_bbl_vendor && (nozzle_id < 0 || nozzle_id >= extder_system->GetTotalExtderCount())) { + m_generic_nozzle_selector->SetSelection(current_selection); return; } - auto nozzle_id = event.GetInt(); + /*Enable switch head while printing is paused STUDIO-9789*/ + if ((obj->is_in_printing() && !obj->is_in_printing_pause()) || + obj->ams_status_main == AMS_STATUS_MAIN_FILAMENT_CHANGE || nozzle_change_in_progress) { + MessageDialog dlg(nullptr, _L("The printer is busy with another print job."), _L("Error"), wxICON_WARNING | wxOK); + dlg.ShowModal(); + if (!is_bbl_vendor) + m_generic_nozzle_selector->SetSelection(current_selection); + return; + } + + if (!is_bbl_vendor) + m_generic_nozzle_selector->Disable(); if (obj->GetCtrl()->command_select_extruder(nozzle_id) == 0) { return; } } - m_nozzle_btn_panel->Enable(); + if (is_bbl_vendor) + m_nozzle_btn_panel->Enable(); + else + m_generic_nozzle_selector->Enable(); } void StatusPanel::on_show_print_options(wxCommandEvent& event) @@ -5255,6 +5689,8 @@ void StatusPanel::set_default() m_switch_lamp_timeout = 0; m_temp_nozzle_timeout = 0; m_temp_nozzle_deputy_timeout = 0; + for (auto& timeout : m_temp_nozzle_timeouts) + timeout = 0; m_temp_bed_timeout = 0; m_temp_chamber_timeout = 0; m_switch_nozzle_fan_timeout = 0; @@ -5262,6 +5698,12 @@ void StatusPanel::set_default() m_switch_cham_fan_timeout = 0; m_show_ams_group = false; m_show_filament_group = false; + m_generic_nozzle_selector->Hide(); + m_nozzle_btn_panel->Hide(); + m_generic_nozzle_selector->Clear(); + m_generic_nozzle_selector_count = 0; + m_extruder_book->SetSelection(0); + m_nozzle_btn_panel->GetParent()->Layout(); reset_printing_values(); m_bitmap_timelapse_img->Hide(); @@ -5286,6 +5728,7 @@ void StatusPanel::set_default() m_ams_control->Reset(); m_ams_rack_switch->updateState("left"); m_ams_rack_switch->Hide(); + m_ams_nozzle_switch->Hide(); m_panel_nozzle_rack->Hide(); m_scale_panel->Hide(); m_filament_load_box->Hide(); @@ -5386,6 +5829,8 @@ void StatusPanel::on_sys_color_changed() m_bitmap_speed_active.msw_rescale(); m_switch_speed->SetImages(m_bitmap_speed, m_bitmap_speed); m_ams_control->msw_rescale(); + if (m_ams_nozzle_switch) + m_ams_nozzle_switch->Rescale(); rescale_camera_icons(); } @@ -5414,10 +5859,17 @@ void StatusPanel::msw_rescale() m_bpButton_xy->Rescale(); auto size = TEMP_CTRL_MIN_SIZE_ALIGN_ONE_ICON; if (obj && obj->GetExtderSystem()->GetTotalExtderCount() >= 2) size = TEMP_CTRL_MIN_SIZE_ALIGN_TWO_ICON; - m_tempCtrl_nozzle->SetMinSize(size); - m_tempCtrl_nozzle->Rescale(); - m_tempCtrl_nozzle_deputy->SetMinSize(size); - m_tempCtrl_nozzle_deputy->Rescale(); + if (wxGetApp().preset_bundle->is_bbl_vendor()) { + m_tempCtrl_nozzle->SetMinSize(size); + m_tempCtrl_nozzle->Rescale(); + m_tempCtrl_nozzle_deputy->SetMinSize(size); + m_tempCtrl_nozzle_deputy->Rescale(); + } else { + for (auto& tempCtrl : m_tempCtrl_nozzles) { + tempCtrl->SetMinSize(size); + tempCtrl->Rescale(); + } + } m_line_nozzle->SetSize(wxSize(-1, FromDIP(1))); m_tempCtrl_bed->SetMinSize(size); m_tempCtrl_bed->Rescale(); @@ -5466,6 +5918,8 @@ void StatusPanel::msw_rescale() m_extruder_switching_status->msw_rescale(); m_ams_control->msw_rescale(); + if (m_ams_nozzle_switch) + m_ams_nozzle_switch->Rescale(); m_panel_nozzle_rack->Rescale(); // m_filament_step->Rescale(); diff --git a/src/slic3r/GUI/StatusPanel.hpp b/src/slic3r/GUI/StatusPanel.hpp index 4a17aff571..141764df04 100644 --- a/src/slic3r/GUI/StatusPanel.hpp +++ b/src/slic3r/GUI/StatusPanel.hpp @@ -1,6 +1,8 @@ #ifndef slic3r_StatusPanel_hpp_ #define slic3r_StatusPanel_hpp_ +#include + #include "libslic3r/ProjectTask.hpp" #include "DeviceManager.hpp" #include "MonitorPage.hpp" @@ -66,6 +68,7 @@ #include "StagedBuild.hpp" class StepIndicator; +class wxChoice; #define COMMAND_TIMEOUT 5 @@ -140,28 +143,35 @@ class ExtruderImage : public wxWindow ScalableBitmap *m_left_extruder_active_empty; ScalableBitmap *m_left_extruder_unactive_filled; ScalableBitmap *m_left_extruder_unactive_empty; + ScalableBitmap *m_right_extruder_active_filled; ScalableBitmap *m_right_extruder_active_empty; ScalableBitmap *m_right_extruder_unactive_filled; ScalableBitmap *m_right_extruder_unactive_empty; - + ScalableBitmap *m_extruder_single_nozzle_empty_load; ScalableBitmap *m_extruder_single_nozzle_empty_unload; ScalableBitmap *m_extruder_single_nozzle_filled_load; ScalableBitmap *m_extruder_single_nozzle_filled_unload; - + ExtruderState m_left_ext_state = {ExtruderState::EMPTY_LOAD}; ExtruderState m_right_ext_state = {ExtruderState::EMPTY_LOAD}; ExtruderState m_single_ext_state = {ExtruderState::EMPTY_LOAD}; + std::vector m_multi_extruder_states; + bool m_generic_nozzle_display{false}; public: void update(int nozzle_num, int nozzle_id); void update(ExtruderState single_state); void update(ExtruderState right_state, ExtruderState left_state); + void update(ExtruderState state, int idx); + void msw_rescale(); void setExtruderCount(int nozzle_num); + void setGenericNozzleDisplay(bool enabled); void setExtruderUsed(std::string loc); + void setExtruderUsed(int nozzle_idx); void paintEvent(wxPaintEvent &evt); void render(wxDC &dc); @@ -495,6 +505,8 @@ protected: std::vector m_extruderImage; SwitchBoard * m_nozzle_btn_panel; + wxChoice* m_generic_nozzle_selector{nullptr}; + int m_generic_nozzle_selector_count{0}; wxStaticText * m_text_tasklist_caption; @@ -507,10 +519,18 @@ protected: wxBoxSizer * m_misc_ctrl_sizer; StaticBox* m_fan_panel; StaticLine * m_line_nozzle; - TempInput* m_tempCtrl_nozzle; + wxWindowID m_nozzle_temp_control_id{wxID_ANY}; + wxWindow* m_temp_nozzle_parent{nullptr}; + wxBoxSizer* m_temp_nozzle_sizer{nullptr}; + size_t m_temp_nozzle_active_count{0}; + TempInput* m_tempCtrl_nozzle{nullptr}; int m_temp_nozzle_timeout{ 0 }; - TempInput* m_tempCtrl_nozzle_deputy; + TempInput* m_tempCtrl_nozzle_deputy{nullptr}; int m_temp_nozzle_deputy_timeout{ 0 }; + + std::vector m_tempCtrl_nozzles; + std::vector m_temp_nozzle_timeouts; + TempInput * m_tempCtrl_bed; int m_temp_bed_timeout {0}; TempInput * m_tempCtrl_chamber; @@ -550,6 +570,7 @@ protected: /* AMS control box <-> live nozzle-rack panel toggle (rack printers only) */ SwitchBoard* m_ams_rack_switch{ nullptr }; + MultiSwitchButton* m_ams_nozzle_switch{ nullptr }; AMSControl* m_ams_control; StaticBox* m_ams_control_box; @@ -624,6 +645,9 @@ public: wxBoxSizer *create_temp_axis_group(wxWindow *parent); wxBoxSizer *create_temp_control(wxWindow *parent); + TempInput* create_nozzle_temp_control(wxWindow *parent, wxWindowID id); + void set_temp_input_colors(TempInput* temp_ctrl); + void ensure_nozzle_temp_controls(size_t count); wxBoxSizer *create_misc_control(wxWindow *parent); wxBoxSizer *create_axis_control(wxWindow *parent); wxPanel *create_bed_control(wxWindow *parent); @@ -654,6 +678,7 @@ private: friend class MonitorPanel; void wire_controls(); bool load_thumbnail_from_url(const wxString &url, MachineObject *obj); + void sync_nozzle_temp_controls(size_t count); protected: std::shared_ptr m_slice_info_popup; diff --git a/src/slic3r/GUI/Widgets/AMSControl.cpp b/src/slic3r/GUI/Widgets/AMSControl.cpp index c5015c9ddc..8a899fc233 100644 --- a/src/slic3r/GUI/Widgets/AMSControl.cpp +++ b/src/slic3r/GUI/Widgets/AMSControl.cpp @@ -244,7 +244,7 @@ AMSControl::AMSControl(wxWindow *parent, wxWindowID id, const wxPoint &pos, cons m_sizer_ams_body->Add(m_sizer_ams_area_right, wxALIGN_CENTER, 0); m_sizer_body->Add(m_sizer_ams_items, 0, wxALIGN_CENTER, 0); - m_sizer_body->Add(0, 0, 1, wxEXPAND | wxTOP, FromDIP(10)); + m_sizer_ams_gap = m_sizer_body->Add(0, 0, 1, wxEXPAND | wxTOP, FromDIP(10)); m_sizer_body->Add(m_sizer_ams_body, 0, wxALIGN_CENTER, 0); m_sizer_body->Add(m_sizer_down_road, 0, wxALIGN_CENTER, 0); m_sizer_body->Add(m_sizer_ams_option, 0, wxEXPAND, 0); @@ -371,7 +371,7 @@ bool AMSControl::IsAmsInRightPanel(std::string ams_id) { return false; } } - else{ + else if (m_total_ext_count == 1){ for (auto id : m_item_ids[MAIN_EXTRUDER_ID]){ if (id == ams_id){ return true; @@ -379,6 +379,10 @@ bool AMSControl::IsAmsInRightPanel(std::string ams_id) { } return false; } + else { + // Generic N Nozzles + } + return false; } void AMSControl::AmsSelectedSwitch(wxCommandEvent& event) { @@ -519,7 +523,7 @@ void AMSControl::msw_rescale() m_button_ams_setting_press.msw_rescale(); m_button_ams_setting->SetBitmap(m_button_ams_setting_normal.bmp()); - m_extruder->msw_rescale(); + if (m_extruder) m_extruder->msw_rescale(); if (m_button_extruder_feed) m_button_extruder_feed->Rescale(); // ORCA if (m_button_extruder_back) m_button_extruder_back->Rescale(); // ORCA @@ -603,6 +607,8 @@ void AMSControl::CreateAms() void AMSControl::ClearAms() { + m_sizer_ams_items->Clear(false); + m_simplebook_ams_right->DeleteAllPages(); m_simplebook_ams_left->DeleteAllPages(); m_simplebook_ams_right->DestroyChildren(); @@ -613,7 +619,7 @@ void AMSControl::ClearAms() { m_simplebook_ams_left->Refresh(); for (auto it : m_ams_preview_list) { - delete it.second; + if (it.second) it.second->Destroy(); } m_ams_preview_list.clear(); m_ext_image_list.clear(); @@ -624,12 +630,167 @@ void AMSControl::ClearAms() { m_ams_item_list.clear(); m_sizer_prv_right->Clear(); m_sizer_prv_left->Clear(); - m_item_ids = { {}, {} }; + + if (m_nozzle_book) { + m_nozzle_book->Destroy(); + m_nozzle_book = nullptr; + } else { + for (auto &pane : m_nozzle_panes) { + if (pane.preview_panel) pane.preview_panel->Destroy(); + if (pane.ams_book) pane.ams_book->Destroy(); + } + } + m_item_ids.assign(std::max(m_total_ext_count, 2), {}); + m_nozzle_panes.clear(); + m_active_nozzle_id = 0; + m_current_ams.clear(); + m_current_show_ams_left.clear(); + m_current_show_ams_right.clear(); pair_id.clear(); } +void AMSControl::restore_legacy_ams_layout() +{ + m_sizer_ams_items->Clear(false); + m_sizer_ams_items->SetOrientation(wxHORIZONTAL); + m_sizer_body->GetItem(m_sizer_ams_items)->SetFlag(wxALIGN_CENTER); + m_sizer_ams_items->Add(m_panel_prv_left, 0, wxLEFT | wxRIGHT, FromDIP(5)); + m_sizer_ams_items->Add(m_panel_prv_right, 0, wxLEFT | wxRIGHT, FromDIP(5)); + + m_panel_prv_left->Show(); + m_panel_prv_right->Show(); + m_simplebook_ams_left->Show(); + m_simplebook_ams_right->Show(); + m_panel_down_road->Show(); + m_sizer_body->Show(m_sizer_ams_body, true); + m_sizer_option_mid->Show(m_extruder, true); + m_down_road->SetSingleSideLayout(false); + m_sizer_ams_gap->SetProportion(1); + m_sizer_ams_gap->SetBorder(FromDIP(10)); + m_down_road->SetNozzleCount(m_total_ext_count); +} + +void AMSControl::CreateAmsMultiNozzle(const std::string &series_name, const std::string &printer_type) { + const size_t pane_count = static_cast(std::max(m_total_ext_count, 0)); + m_nozzle_panes.resize(pane_count); + m_active_nozzle_id = 0; + + m_sizer_ams_items->Clear(false); + m_sizer_ams_items->SetOrientation(wxVERTICAL); + m_sizer_body->GetItem(m_sizer_ams_items)->SetFlag(wxALIGN_CENTER); + m_sizer_body->Show(m_sizer_ams_body, false); + m_sizer_ams_gap->SetProportion(0); + m_sizer_ams_gap->SetBorder(0); + + m_panel_prv_left->Hide(); + m_panel_prv_right->Hide(); + m_simplebook_ams_left->Hide(); + m_simplebook_ams_right->Hide(); + m_panel_down_road->Show(); + m_sizer_option_mid->Show(m_extruder, true); + m_switcher->Hide(); + m_down_road->SetNozzleCount(1); + m_down_road->SetSingleSideLayout(true, AMSPanelPos::LEFT_PANEL); + m_down_road->UpdateLeft(1, AMSRoadShowMode::AMS_ROAD_MODE_SINGLE); + m_down_road->UpdateRight(1, AMSRoadShowMode::AMS_ROAD_MODE_NONE); + m_extruder->updateNozzleNum(1, series_name); + + m_nozzle_book = new wxSimplebook(m_amswin, wxID_ANY, wxDefaultPosition, wxDefaultSize, 0); + m_nozzle_book->SetMinSize(wxSize(FromDIP(264), -1)); + m_nozzle_book->SetBackgroundColour(StateColor::darkModeColorFor(AMS_CONTROL_DEF_BLOCK_BK_COLOUR)); + m_sizer_ams_items->Add(m_nozzle_book, 0, wxALIGN_CENTER); + + for (size_t nozzle_id = 0; nozzle_id < pane_count; ++nozzle_id) { + auto &pane = m_nozzle_panes[nozzle_id]; + // A generic page is one H2C side. Keep the left-side geometry as the + // canonical single-nozzle presentation; the road and AMS item code + // still use the same side-specific paths as the legacy layout. + pane.panel_pos = AMSPanelPos::LEFT_PANEL; + pane.page = new wxPanel(m_nozzle_book, wxID_ANY); + pane.page->SetMinSize(wxSize(FromDIP(264), -1)); + pane.page->SetBackgroundColour(StateColor::darkModeColorFor(AMS_CONTROL_DEF_BLOCK_BK_COLOUR)); + pane.page_sizer = new wxBoxSizer(wxVERTICAL); + pane.page->SetSizer(pane.page_sizer); + + pane.preview_panel = new wxScrolledWindow(pane.page, wxID_ANY); + pane.preview_panel->SetScrollRate(10, 0); + pane.preview_panel->SetSize(AMS_ITEMS_PANEL_SIZE); + pane.preview_panel->SetMinSize(AMS_ITEMS_PANEL_SIZE); + pane.preview_panel->SetBackgroundColour(StateColor::darkModeColorFor(AMS_CONTROL_DEF_BLOCK_BK_COLOUR)); + + pane.preview_sizer = new wxBoxSizer(wxHORIZONTAL); + pane.preview_panel->SetSizer(pane.preview_sizer); + + // Generic lane cards grow to their calculated width for 1-8 lanes. Keep the + // book at the legacy minimum, but allow the page to expand to that width. + pane.ams_book = new wxSimplebook(pane.page, wxID_ANY, wxDefaultPosition, wxDefaultSize, 0); + pane.ams_book->SetMinSize(wxSize(FromDIP(264), -1)); + pane.ams_book->SetBackgroundColour(StateColor::darkModeColorFor(AMS_CONTROL_DEF_BLOCK_BK_COLOUR)); + pane.ams_area = new wxBoxSizer(wxHORIZONTAL); + pane.ams_area->Add(pane.ams_book, 0, wxALIGN_CENTER, 0); + + pane.page_sizer->Add(pane.preview_panel, 0, wxALIGN_CENTER | wxTOP, FromDIP(5)); + pane.page_sizer->Add(pane.ams_area, 0, wxALIGN_CENTER | wxTOP, FromDIP(10)); + + m_nozzle_book->AddPage(pane.page, wxEmptyString, nozzle_id == 0); + } + + auto add_info = [this, series_name, printer_type, pane_count](AMSinfo info) { + if (pane_count == 0) return; + + info.nozzle_id = std::clamp(info.nozzle_id, 0, static_cast(pane_count) - 1); + AddAmsPreview(info, info.ams_type); + if (info.ams_type == AMSModel::EXT_AMS && info.cans.size() == 1) + AddAms({info}, series_name, printer_type, m_nozzle_panes[info.nozzle_id].panel_pos); + else + AddAms(info); + }; + + for (const auto &info : m_ams_info) + add_info(info); + + for (auto &info : m_ext_info) { + const int virtual_tray_id = std::atoi(info.ams_id.c_str()); + if (virtual_tray_id <= VIRTUAL_TRAY_MAIN_ID && + virtual_tray_id >= VIRTUAL_TRAY_MAIN_ID - static_cast(pane_count) + 1) + info.nozzle_id = VIRTUAL_TRAY_MAIN_ID - virtual_tray_id; + add_info(info); + } + + for (auto &pane : m_nozzle_panes) { + if (pane.ams_book && pane.ams_book->GetPageCount() > 0) + pane.ams_book->SetSelection(0); + if (pane.preview_sizer) pane.preview_sizer->Layout(); + if (pane.preview_panel) pane.preview_panel->FitInside(); + if (pane.ams_book) pane.ams_book->Layout(); + if (pane.page) pane.page->Layout(); + + if (!pane.current_ams.empty()) { + auto preview = m_ams_preview_list.find(pane.current_ams); + if (preview != m_ams_preview_list.end() && preview->second) + preview->second->OnSelected(); + } + } + + if (m_nozzle_book && pane_count > 0) { + m_nozzle_book->SetSelection(0); + m_current_ams = m_nozzle_panes[0].current_ams; + if (!m_current_ams.empty()) + SwitchAms(m_current_ams); + else { + m_down_road->UpdateLeft(1, AMSRoadShowMode::AMS_ROAD_MODE_NONE); + m_down_road->UpdateRight(1, AMSRoadShowMode::AMS_ROAD_MODE_NONE); + } + } + m_amswin->Layout(); + m_amswin->Fit(); + // Generic N Nozzles +} + void AMSControl::CreateAmsDoubleNozzle(const std::string &series_name, const std::string &printer_type) { + restore_legacy_ams_layout(); + std::vector single_info_left; std::vector single_info_right; @@ -751,6 +912,8 @@ void AMSControl::CreateAmsDoubleNozzle(const std::string &series_name, const std void AMSControl::CreateAmsSingleNozzle(const std::string &series_name, const std::string &printer_type) { + restore_legacy_ams_layout(); + std::vectorm_item_nums{0,0}; std::vector single_info; @@ -979,6 +1142,17 @@ void AMSControl::UpdateAms(const std::string &series_name, if (m_ams_info[i].routes_to_main_extruder() != ams_info[i].routes_to_main_extruder()) { fresh = true; } + if (m_ams_info[i].nozzle_id != ams_info[i].nozzle_id) { + fresh = true; + } + } + + for (int i = 0; i < static_cast(m_ext_info.size()); ++i) { + if (m_ext_info[i].ams_id != ext_info[i].ams_id || + m_ext_info[i].nozzle_id != ext_info[i].nozzle_id || + m_ext_info[i].ext_type != ext_info[i].ext_type) { + fresh = true; + } } } else{ @@ -993,11 +1167,19 @@ void AMSControl::UpdateAms(const std::string &series_name, m_dev_id = dev_id; if (fresh){ ClearAms(); - if (m_total_ext_count >= 2){ - CreateAmsDoubleNozzle(series_name, printer_type); - }else{ - CreateAmsSingleNozzle(series_name, printer_type); + // Keep the established Bambu left/right AMS presentation. Generic N-nozzle + // tabs are for non-Bambu printers; the current checkout exposes this behavior + // through PresetBundle::is_bbl_vendor(). + const bool is_bbl_behavior = wxGetApp().preset_bundle && wxGetApp().preset_bundle->is_bbl_vendor(); + if (is_bbl_behavior) { + if (m_total_ext_count == 1) + CreateAmsSingleNozzle(series_name, printer_type); + else if (m_total_ext_count == 2) + CreateAmsDoubleNozzle(series_name, printer_type); } + else + CreateAmsMultiNozzle(series_name, printer_type); + SetSize(wxSize(FromDIP(578), -1)); SetMinSize(wxSize(FromDIP(578), -1)); Layout(); @@ -1070,7 +1252,7 @@ void AMSControl::UpdateAms(const std::string &series_name, } /*update ams extruder*/ - if (m_extruder->updateNozzleNum(m_total_ext_count, series_name)) + if (m_total_ext_count <= 2 && m_extruder->updateNozzleNum(m_total_ext_count, series_name)) { m_amswin->Layout(); } @@ -1106,7 +1288,13 @@ void AMSControl::AddAmsPreview(AMSinfo info, AMSModel type) { AMSPreview *ams_prv = nullptr; - if (info.routes_to_main_extruder()) + if (m_total_ext_count > 2) { + const int nozzle_id = std::clamp(info.nozzle_id, 0, m_total_ext_count - 1); + auto &pane = m_nozzle_panes[nozzle_id]; + ams_prv = new AMSPreview(pane.preview_panel, wxID_ANY, info, type); + pane.preview_sizer->Add(ams_prv, 0, wxALIGN_CENTER | wxLEFT, FromDIP(6)); + } + else if (info.routes_to_main_extruder()) { ams_prv = new AMSPreview(m_panel_prv_right, wxID_ANY, info, type); m_sizer_prv_right->Add(ams_prv, 0, wxALIGN_CENTER | wxLEFT, FromDIP(6)); @@ -1129,6 +1317,40 @@ void AMSControl::AddAmsPreview(AMSinfo info, AMSModel type) void AMSControl::createAms(wxSimplebook* parent, int& idx, AMSinfo info, AMSPanelPos pos) { auto ams_item = new AmsItem(parent, info, info.ams_type, pos); parent->InsertPage(idx, ams_item, wxEmptyString, true); + + // Generic AMS cards may be wider than the legacy 264 DIP page. Propagate + // that calculated width through the page and simplebook so 5-8 lanes are + // laid out fully instead of being clipped by the old minimum width. + if (info.ams_type == AMSModel::GENERIC_AMS) { + const int required_width = ams_item->GetMinSize().x; + if (required_width > 0) { + const int page_width = std::max(parent->GetMinSize().x, required_width); + parent->SetMinSize(wxSize(page_width, -1)); + if (auto *page = parent->GetParent()) { + page->SetMinSize(wxSize(std::max(page->GetMinSize().x, page_width), -1)); + if (auto *nozzle_book = page->GetParent()) + nozzle_book->SetMinSize(wxSize(std::max(nozzle_book->GetMinSize().x, page_width), -1)); + } + } + // Above eight lanes, AmsItem is a fixed-minimum viewport over a wider + // lane strip. Expand the viewport through its page's sizers rather + // than propagating the strip's virtual width into the card/book. + const bool generic_lane_layout = !wxGetApp().preset_bundle || !wxGetApp().preset_bundle->is_bbl_vendor(); + if (generic_lane_layout && info.cans.size() > 8) { + if (auto *page = parent->GetParent()) { + auto pane = std::find_if(m_nozzle_panes.begin(), m_nozzle_panes.end(), + [page](const NozzleAmsPane &candidate) { return candidate.page == page; }); + if (pane != m_nozzle_panes.end()) { + pane->ams_area->GetItem(pane->ams_book)->SetProportion(1); + pane->ams_area->GetItem(pane->ams_book)->SetFlag(wxEXPAND); + pane->page_sizer->GetItem(pane->ams_area)->SetFlag(wxEXPAND | wxTOP); + m_sizer_ams_items->GetItem(m_nozzle_book)->SetFlag(wxEXPAND); + m_sizer_body->GetItem(m_sizer_ams_items)->SetFlag(wxEXPAND); + m_amswin->Layout(); + } + } + } + } ams_item->set_selection(idx); idx++; @@ -1153,7 +1375,10 @@ AMSRoadShowMode AMSControl::findFirstMode(AMSPanelPos pos) { if (item->second->get_ams_model() == AMSModel::EXT_AMS && item->second->get_ext_type() == AMSModelOriginType::LITE_EXT) return AMSRoadShowMode::AMS_ROAD_MODE_AMS_LITE; return AMSRoadShowMode::AMS_ROAD_MODE_FOUR; } - else{ + else if (item->second->get_can_count() > GENERIC_AMS_SLOT_NUM) { + return AMSRoadShowMode::AMS_ROAD_MODE_GENERIC; + } + else { for (auto ids : pair_id){ if (ids.first == ams_id || ids.second == ams_id){ return AMSRoadShowMode::AMS_ROAD_MODE_DOUBLE; @@ -1200,6 +1425,11 @@ void AMSControl::createAmsPanel(wxSimplebook *parent, int &idx, std::vectorAdd(ams1, 0, wxALIGN_CENTER_HORIZONTAL, 0); book_sizer->Add(ams1, 0, wxLEFT, (book_panel->GetSize().x - ams1->GetSize().x) / 2); } + else if (pos == AMSPanelPos::SINGLE_PANEL) { + book_sizer->AddStretchSpacer(1); + book_sizer->Add(ams1, 0, wxALIGN_CENTER_VERTICAL, 0); + book_sizer->AddStretchSpacer(1); + } else{ auto ext_image = new AMSExtImage(book_panel, pos, m_total_ext_count, false); book_sizer->Add(ams1, 0, wxLEFT, FromDIP(30)); @@ -1228,7 +1458,17 @@ void AMSControl::createAmsPanel(wxSimplebook *parent, int &idx, std::vector 1){ + if (m_total_ext_count > 2) { + const int nozzle_id = std::clamp(info.nozzle_id, 0, m_total_ext_count - 1); + auto &pane = m_nozzle_panes[nozzle_id]; + pane.item_ids.push_back(info.ams_id); + m_item_ids[nozzle_id].push_back(info.ams_id); + if (pane.current_ams.empty()) pane.current_ams = info.ams_id; + createAms(pane.ams_book, pane.page_index, info, AMSPanelPos::SINGLE_PANEL); + pane.ams_book->Fit(); + pane.ams_book->Layout(); + } + else if (m_total_ext_count == 2){ if (info.routes_to_main_extruder()){ createAms(m_simplebook_ams_right, m_right_page_index, info, AMSPanelPos::RIGHT_PANEL); } @@ -1239,6 +1479,9 @@ void AMSControl::AddAms(AMSinfo info, AMSPanelPos pos) else if (m_total_ext_count == 1){ createAms(m_simplebook_ams_left, m_left_page_index, info, AMSPanelPos::LEFT_PANEL); } + else { + // Generic N Nozzles + } m_simplebook_ams_left->Layout(); m_simplebook_ams_right->Layout(); m_simplebook_ams_left->Refresh(); @@ -1269,7 +1512,19 @@ void AMSControl::AddAms(std::vector single_info, const std::string &ser if (single_info.size() <= 0){ return; } - if (m_total_ext_count == 2) { + if (m_total_ext_count > 2) { + const int nozzle_id = std::clamp(single_info.front().nozzle_id, 0, m_total_ext_count - 1); + auto &pane = m_nozzle_panes[nozzle_id]; + for (const auto &info : single_info) { + pane.item_ids.push_back(info.ams_id); + m_item_ids[nozzle_id].push_back(info.ams_id); + } + if (pane.current_ams.empty()) pane.current_ams = single_info.front().ams_id; + createAmsPanel(pane.ams_book, pane.page_index, single_info, series_name, printer_type, AMSPanelPos::SINGLE_PANEL, m_total_ext_count); + pane.ams_book->Fit(); + pane.ams_book->Layout(); + } + else if (m_total_ext_count == 2) { if (single_info[0].routes_to_main_extruder()) { createAmsPanel(m_simplebook_ams_right, m_right_page_index, single_info, series_name, printer_type, AMSPanelPos::RIGHT_PANEL, m_total_ext_count); } @@ -1285,6 +1540,9 @@ void AMSControl::AddAms(std::vector single_info, const std::string &ser createAmsPanel(m_simplebook_ams_left, m_left_page_index, single_info, series_name, printer_type, AMSPanelPos::LEFT_PANEL, m_total_ext_count); } } + else { + // Generic N Nozzles + } m_simplebook_ams_left->Layout(); m_simplebook_ams_right->Layout(); @@ -1322,6 +1580,21 @@ void AMSControl::AddAms(std::vector single_info, const std::string &ser void AMSControl::AddAmsPreview(std::vectorsingle_info, AMSPanelPos pos) { if (single_info.size() <= 0) return; + if (m_total_ext_count > 2) { + const int nozzle_id = std::clamp(single_info.front().nozzle_id, 0, m_total_ext_count - 1); + auto &pane = m_nozzle_panes[nozzle_id]; + for (const auto &info : single_info) { + auto *ams_prv = new AMSPreview(pane.preview_panel, wxID_ANY, info, info.ams_type); + pane.preview_sizer->Add(ams_prv, 0, wxALIGN_CENTER | wxLEFT, FromDIP(6)); + ams_prv->Bind(wxEVT_LEFT_DOWN, [this, ams_prv](wxMouseEvent &e) { + SwitchAms(ams_prv->get_ams_id()); + e.Skip(); + }); + m_ams_preview_list[info.ams_id] = ams_prv; + } + return; + } + AMSPreview* ams_prv = nullptr; AMSPreview* ams_prv2 = nullptr; if (pos == AMSPanelPos::RIGHT_PANEL){ @@ -1360,8 +1633,97 @@ void AMSControl::AddAmsPreview(std::vectorsingle_info, AMSPanelPos pos) } } +void AMSControl::SelectNozzle(int nozzle_id) +{ + if (nozzle_id < 0 || nozzle_id >= static_cast(m_nozzle_panes.size())) + return; + + if (m_active_nozzle_id == nozzle_id && + (!m_nozzle_book || m_nozzle_book->GetSelection() == nozzle_id)) + return; + + m_active_nozzle_id = nozzle_id; + if (m_nozzle_book && m_nozzle_book->GetSelection() != nozzle_id) + m_nozzle_book->SetSelection(nozzle_id); + m_current_ams = m_nozzle_panes[nozzle_id].current_ams; + if (!m_current_ams.empty()) { + SwitchAms(m_current_ams); + return; + } + if (m_down_road) { + m_down_road->UpdateLeft(1, AMSRoadShowMode::AMS_ROAD_MODE_NONE); + m_down_road->UpdateRight(1, AMSRoadShowMode::AMS_ROAD_MODE_NONE); + } + if (m_extruder) + m_extruder->OnAmsLoading(false, 0); + post_event(SimpleEvent(EVT_AMS_SWITCH)); +} + void AMSControl::SwitchAms(std::string ams_id) { + if (m_total_ext_count > 2) { + NozzleAmsPane *pane = nullptr; + int nozzle_id = -1; + for (int index = 0; index < static_cast(m_nozzle_panes.size()); ++index) { + auto &candidate = m_nozzle_panes[index]; + if (std::find(candidate.item_ids.begin(), candidate.item_ids.end(), ams_id) != candidate.item_ids.end()) { + pane = &candidate; + nozzle_id = index; + break; + } + } + if (!pane) return; + + m_active_nozzle_id = nozzle_id; + if (m_nozzle_book && m_nozzle_book->GetSelection() != nozzle_id) + m_nozzle_book->SetSelection(nozzle_id); + + pane->current_ams = ams_id; + m_current_ams = ams_id; + m_current_select = ams_id; + + for (const auto &id : pane->item_ids) { + auto preview = m_ams_preview_list.find(id); + if (preview == m_ams_preview_list.end() || !preview->second) continue; + if (id == ams_id) preview->second->OnSelected(); + else preview->second->UnSelected(); + } + + auto item = m_ams_item_list.find(ams_id); + if (item != m_ams_item_list.end() && item->second) { + if (pane->ams_book) + pane->ams_book->SetSelection(item->second->get_selection()); + + AMSRoadShowMode road_mode = AMSRoadShowMode::AMS_ROAD_MODE_SINGLE; + if (item->second->get_can_count() == GENERIC_AMS_SLOT_NUM) + road_mode = AMSRoadShowMode::AMS_ROAD_MODE_FOUR; + else if (item->second->get_can_count() > GENERIC_AMS_SLOT_NUM) + road_mode = AMSRoadShowMode::AMS_ROAD_MODE_GENERIC; + else if (item->second->get_ams_model() == AMSModel::N3S_AMS) + road_mode = AMSRoadShowMode::AMS_ROAD_MODE_SINGLE_N3S; + else if (item->second->get_ams_model() == AMSModel::AMS_LITE || + (item->second->get_ams_model() == AMSModel::EXT_AMS && + item->second->get_ext_type() == AMSModelOriginType::LITE_EXT)) + road_mode = AMSRoadShowMode::AMS_ROAD_MODE_AMS_LITE; + + if (m_down_road) { + if (pane->panel_pos == AMSPanelPos::LEFT_PANEL) { + m_down_road->UpdateLeft(1, road_mode); + m_down_road->UpdateRight(1, AMSRoadShowMode::AMS_ROAD_MODE_NONE); + } else { + m_down_road->UpdateLeft(1, AMSRoadShowMode::AMS_ROAD_MODE_NONE); + m_down_road->UpdateRight(1, road_mode); + } + m_down_road->UpdatePassRoad(pane->panel_pos, -1, AMSPassRoadSTEP::AMS_ROAD_STEP_NONE); + } + if (m_extruder) + m_extruder->OnAmsLoading(false, 0); + } + + post_event(SimpleEvent(EVT_AMS_SWITCH)); + return; + } + if(ams_id == m_current_show_ams_left || ams_id == m_current_show_ams_right){return;} bool is_in_right = IsAmsInRightPanel(ams_id); @@ -1432,13 +1794,17 @@ void AMSControl::SwitchAms(std::string ams_id) else { AMSRoadShowMode mode = AMSRoadShowMode::AMS_ROAD_MODE_SINGLE; - if (item->get_ams_model() == AMSModel::N3S_AMS) + if (item->get_can_count() > GENERIC_AMS_SLOT_NUM) + mode = AMSRoadShowMode::AMS_ROAD_MODE_GENERIC; + else if (item->get_ams_model() == AMSModel::N3S_AMS) mode = AMSRoadShowMode::AMS_ROAD_MODE_SINGLE_N3S; - for (auto it : pair_id) { - if (it.first == ams_id || it.second == ams_id) { - mode = AMSRoadShowMode::AMS_ROAD_MODE_DOUBLE; - break; + if (item->get_can_count() <= GENERIC_AMS_SLOT_NUM) { + for (auto it : pair_id) { + if (it.first == ams_id || it.second == ams_id) { + mode = AMSRoadShowMode::AMS_ROAD_MODE_DOUBLE; + break; + } } } pos == AMSPanelPos::LEFT_PANEL ? m_down_road->UpdateLeft(m_total_ext_count, mode) @@ -1500,6 +1866,20 @@ void AMSControl::SetExtruder(bool on_off, int nozzle_id, std::string ams_id, std AmsItem *item = nullptr; if (m_ams_item_list.find(ams_id) != m_ams_item_list.end()) { item = m_ams_item_list[ams_id]; } + if (m_total_ext_count > 2) { + if (nozzle_id < 0 || nozzle_id >= static_cast(m_nozzle_panes.size())) + return; + + if (!m_extruder) + return; + + if (on_off && item) + m_extruder->OnAmsLoading(true, 0, item->GetTagColr(slot_id)); + else + m_extruder->OnAmsLoading(false, 0); + return; + } + if (on_off && item) { auto col = item->GetTagColr(slot_id); m_extruder->OnAmsLoading(true, nozzle_id, col); @@ -1624,6 +2004,69 @@ void AMSControl::SetAmsStep(std::string ams_id, std::string canid, AMSPassRoadTy else{ return; } + + if (m_total_ext_count > 2) { + const int nozzle_id = std::clamp(ams->get_nozzle_id(), 0, m_total_ext_count - 1); + if (nozzle_id >= static_cast(m_nozzle_panes.size())) + return; + auto &pane = m_nozzle_panes[nozzle_id]; + AMSRoadShowMode road_mode = AMSRoadShowMode::AMS_ROAD_MODE_SINGLE; + if (ams->get_can_count() == GENERIC_AMS_SLOT_NUM) + road_mode = AMSRoadShowMode::AMS_ROAD_MODE_FOUR; + else if (ams->get_can_count() > GENERIC_AMS_SLOT_NUM) + road_mode = AMSRoadShowMode::AMS_ROAD_MODE_GENERIC; + else if (ams->get_ams_model() == AMSModel::N3S_AMS) + road_mode = AMSRoadShowMode::AMS_ROAD_MODE_SINGLE_N3S; + else if (ams->get_ams_model() == AMSModel::AMS_LITE || + (ams->get_ams_model() == AMSModel::EXT_AMS && ams->get_ext_type() == AMSModelOriginType::LITE_EXT)) + road_mode = AMSRoadShowMode::AMS_ROAD_MODE_AMS_LITE; + + if (can_index >= 0 && can_index < static_cast(info.cans.size()) && m_down_road) + m_down_road->SetPassRoadColour(pane.panel_pos == AMSPanelPos::LEFT_PANEL, + info.cans[can_index].material_colour); + if (m_down_road) { + if (pane.panel_pos == AMSPanelPos::LEFT_PANEL) { + m_down_road->UpdateLeft(1, road_mode); + m_down_road->UpdateRight(1, AMSRoadShowMode::AMS_ROAD_MODE_NONE); + } else { + m_down_road->UpdateLeft(1, AMSRoadShowMode::AMS_ROAD_MODE_NONE); + m_down_road->UpdateRight(1, road_mode); + } + } + + int road_length = 0; + + if (step == AMSPassRoadSTEP::AMS_ROAD_STEP_NONE) { + ams->SetAmsStep(ams_id, canid, type, AMSPassRoadSTEP::AMS_ROAD_STEP_NONE); + if (m_down_road) + m_down_road->UpdatePassRoad(pane.panel_pos, -1, AMSPassRoadSTEP::AMS_ROAD_STEP_NONE); + if (m_extruder) + m_extruder->OnAmsLoading(false, 0); + } + else if (step == AMSPassRoadSTEP::AMS_ROAD_STEP_COMBO_LOAD_STEP1) { + ams->SetAmsStep(ams_id, canid, type, AMSPassRoadSTEP::AMS_ROAD_STEP_1); + if (m_down_road) + m_down_road->UpdatePassRoad(pane.panel_pos, road_length, AMSPassRoadSTEP::AMS_ROAD_STEP_1); + if (m_extruder) + m_extruder->OnAmsLoading(false, 0); + } + else if (step == AMSPassRoadSTEP::AMS_ROAD_STEP_COMBO_LOAD_STEP2) { + ams->SetAmsStep(ams_id, canid, type, AMSPassRoadSTEP::AMS_ROAD_STEP_2); + if (m_down_road) + m_down_road->UpdatePassRoad(pane.panel_pos, road_length, AMSPassRoadSTEP::AMS_ROAD_STEP_2); + if (m_extruder) + m_extruder->OnAmsLoading(true, 0, ams->GetTagColr(canid)); + } + else if (step == AMSPassRoadSTEP::AMS_ROAD_STEP_COMBO_LOAD_STEP3) { + ams->SetAmsStep(ams_id, canid, type, AMSPassRoadSTEP::AMS_ROAD_STEP_3); + if (m_down_road) + m_down_road->UpdatePassRoad(pane.panel_pos, road_length, AMSPassRoadSTEP::AMS_ROAD_STEP_3); + if (m_extruder) + m_extruder->OnAmsLoading(true, 0, ams->GetTagColr(canid)); + } + return; + } + if (can_index >= 0 && can_index < info.cans.size()) { m_down_road->SetPassRoadColour(left, info.cans[can_index].material_colour); diff --git a/src/slic3r/GUI/Widgets/AMSControl.hpp b/src/slic3r/GUI/Widgets/AMSControl.hpp index f4c83667c2..d771ea6174 100644 --- a/src/slic3r/GUI/Widgets/AMSControl.hpp +++ b/src/slic3r/GUI/Widgets/AMSControl.hpp @@ -47,6 +47,20 @@ public: void on_retry(); protected: + struct NozzleAmsPane + { + wxPanel* page{nullptr}; + wxBoxSizer* page_sizer{nullptr}; + wxScrolledWindow* preview_panel{nullptr}; + wxBoxSizer* preview_sizer{nullptr}; + wxSimplebook* ams_book{nullptr}; + wxBoxSizer* ams_area{nullptr}; + AMSPanelPos panel_pos{AMSPanelPos::LEFT_PANEL}; + std::vector item_ids; + std::string current_ams; + int page_index{0}; + }; + std::string m_current_ams; std::string m_current_slot_left; std::string m_current_slot_right; @@ -63,6 +77,9 @@ protected: std::string m_dev_id; std::vector> m_item_ids{ {}, {} }; + std::vector m_nozzle_panes; + wxSimplebook* m_nozzle_book{nullptr}; + int m_active_nozzle_id{0}; std::vector> pair_id; int m_total_ext_count = 1; @@ -103,6 +120,7 @@ protected: wxSimplebook *m_simplebook_ams_left{nullptr}; wxSimplebook *m_simplebook_ams_right{ nullptr }; wxSimplebook *m_simplebook_bottom{nullptr}; + wxSizerItem *m_sizer_ams_gap{nullptr}; wxPanel *m_panel_down_road{ nullptr }; int m_left_page_index = 0; int m_right_page_index = 0; @@ -156,6 +174,7 @@ public: void createAms(wxSimplebook* parent, int& idx, AMSinfo info, AMSPanelPos pos); void createAmsPanel(wxSimplebook *parent, int &idx, std::vector infos, const std::string &series_name, const std::string &printer_type, AMSPanelPos pos, int total_ext_num); AMSRoadShowMode findFirstMode(AMSPanelPos pos); + void restore_legacy_ams_layout(); AMSModel m_ams_model{AMSModel::EXT_AMS}; AMSModel m_ext_model{AMSModel::EXT_AMS}; @@ -183,6 +202,7 @@ public: void UpdatePassRoad(string ams_id, AMSPassRoadType type, AMSPassRoadSTEP step); void CreateAms(); + void CreateAmsMultiNozzle(const std::string& series_name, const std::string& printer_type); void CreateAmsDoubleNozzle(const std::string &series_name, const std::string& printer_type); void CreateAmsSingleNozzle(const std::string &series_name, const std::string &printer_type); void ClearAms(); @@ -210,6 +230,8 @@ public: void SetExtruder(bool on_off, int nozzle_id, std::string ams_id, std::string slot_id); void SetAmsStep(std::string ams_id, std::string canid, AMSPassRoadType type, AMSPassRoadSTEP step); void SwitchAms(std::string ams_id); + void SelectNozzle(int nozzle_id); + int GetActiveNozzleId() const { return m_active_nozzle_id; } void msw_rescale(); void on_filament_load(wxCommandEvent &event); diff --git a/src/slic3r/GUI/Widgets/AMSItem.cpp b/src/slic3r/GUI/Widgets/AMSItem.cpp index ef3643e531..757894916d 100644 --- a/src/slic3r/GUI/Widgets/AMSItem.cpp +++ b/src/slic3r/GUI/Widgets/AMSItem.cpp @@ -42,6 +42,7 @@ #include #include #include +#include #include "CalibUtils.hpp" #include "slic3r/GUI/wxExtensions.hpp" @@ -76,6 +77,48 @@ namespace Slic3r { namespace GUI { //#define AMS_SINGLE_CAN_SIZE wxSize(FromDIP(78), 144) #define AMS_CANS_WINDOW_SIZE wxSize(FromDIP(264), FromDIP(174)) #define AMS_SINGLE_CAN_SIZE wxSize(FromDIP(78), FromDIP(174)) + +int generic_ams_layout_width(wxWindow *window, int lane_count) +{ + const int base_width = window->FromDIP(264); + const int lane_width = window->FromDIP(52); + const int lane_gap = window->FromDIP(4); + if (lane_count <= 4) + return base_width; + + // Lane centers are distributed as width * (index + 1) / (count + 1). + // Keep at least one card width plus the requested gap between centers. + return std::max(base_width, (lane_count + 1) * (lane_width + lane_gap)); +} + +std::vector generic_ams_lane_centers(int lane_count, int width) +{ + std::vector centers; + if (lane_count <= 0) + return centers; + + centers.reserve(lane_count); + for (int lane = 0; lane < lane_count; ++lane) + centers.push_back(width * (lane + 1) / (lane_count + 1)); + return centers; +} + +int generic_ams_preview_width(wxWindow *window, int lane_count) +{ + const int base_width = window->FromDIP(52); + const int lane_width = window->FromDIP(9); + const int lane_gap = window->FromDIP(4); + if (lane_count <= 0) + return base_width; + + return std::max(base_width, lane_count * lane_width + (lane_count + 1) * lane_gap); +} + +bool use_generic_ams_layout(AMSModel model) +{ + return model == AMSModel::GENERIC_AMS && + (!wxGetApp().preset_bundle || !wxGetApp().preset_bundle->is_bbl_vendor()); +} bool AMSinfo::parse_ams_info(MachineObject *obj, DevAms *ams, bool remain_flag, bool humidity_flag) { if (!ams) return false; @@ -170,10 +213,14 @@ void AMSinfo::parse_ext_info(MachineObject* obj, DevAmsTray tray) { info.can_id = std::to_string(0); this->cans.clear(); - if (tray.id == std::to_string(VIRTUAL_TRAY_MAIN_ID)) + try { + const int virtual_tray_id = std::stoi(tray.id); + if (virtual_tray_id >= 0 && virtual_tray_id <= VIRTUAL_TRAY_MAIN_ID) + this->nozzle_id = VIRTUAL_TRAY_MAIN_ID - virtual_tray_id; + } + catch (...) { this->nozzle_id = 0; - else if (tray.id == std::to_string(VIRTUAL_TRAY_DEPUTY_ID)) - this->nozzle_id = 1; + } if (tray.is_tray_info_ready()) { info.ctype = tray.ctype; @@ -856,7 +903,11 @@ AMSextruder::AMSextruder(wxWindow *parent, wxWindowID id, int nozzle_num, const void AMSextruder::TurnOff() { - m_left_extruder->TurnOff(); + if (m_left_extruder) m_left_extruder->TurnOff(); + if (m_right_extruder) m_right_extruder->TurnOff(); + for (auto *extruder : m_nozzle_extruders) { + if (extruder) extruder->TurnOff(); + } } void AMSextruder::create(wxWindow *parent, wxWindowID id, const wxPoint &pos, const wxSize &size, int nozzle_num) @@ -882,7 +933,7 @@ void AMSextruder::OnAmsLoading(bool load, int nozzle_id, wxColour col /*= AMS_CO m_left_extruder->OnAmsLoading(load, col); if (load) m_current_colur_deputy = col; } - else if (m_nozzle_num > 1){ + else if (m_nozzle_num == 2){ if (nozzle_id == MAIN_EXTRUDER_ID) { m_right_extruder->OnAmsLoading(load, col); if (m_current_colur != col){ @@ -896,6 +947,15 @@ void AMSextruder::OnAmsLoading(bool load, int nozzle_id, wxColour col /*= AMS_CO } } } + else if (m_nozzle_num > 2) { + if (nozzle_id >= 0 && nozzle_id < static_cast(m_nozzle_extruders.size())) { + m_nozzle_extruders[nozzle_id]->OnAmsLoading(load, col); + if (load) m_current_colur = col; + } + } + else { + // Generic N Nozzles + } } /*return true if something is updated*/ @@ -905,12 +965,26 @@ bool AMSextruder::updateNozzleNum(int nozzle_num, const string& series_name) m_series_name = series_name; m_nozzle_num = nozzle_num; this->DestroyChildren(); + m_left_extruder = nullptr; + m_right_extruder = nullptr; + m_nozzle_extruders.clear(); - m_right_extruder = new AMSextruderImage(this, wxID_ANY, "right_nozzle", AMS_EXTRUDER_DOUBLE_NOZZLE_BITMAP_SIZE); + m_bitmap_sizer = new wxBoxSizer(wxHORIZONTAL); + if (m_nozzle_num == 1) { + if (MachineObject::is_series_n(m_series_name)) { + m_left_extruder = new AMSextruderImage(this, wxID_ANY, "single_nozzle_n", AMS_EXTRUDER_SINGLE_NOZZLE_N_SIZE); + } else if (MachineObject::is_series_x(m_series_name) || MachineObject::is_series_p(m_series_name)) { + m_left_extruder = new AMSextruderImage(this, wxID_ANY, "single_nozzle_xp", AMS_EXTRUDER_SINGLE_NOZZLE_XP_SIZE); + } else { + m_left_extruder = new AMSextruderImage(this, wxID_ANY, "single_nozzle_xp", AMS_EXTRUDER_SINGLE_NOZZLE_XP_SIZE); + } - wxBoxSizer* m_bitmap_sizer = new wxBoxSizer(wxHORIZONTAL); - if (m_nozzle_num >= 2) + m_left_extruder->setShowState(true); + m_bitmap_sizer->Add(m_left_extruder, 0, wxALIGN_LEFT | wxALIGN_TOP, 0); + } + else if (m_nozzle_num == 2) { + m_right_extruder = new AMSextruderImage(this, wxID_ANY, "right_nozzle", AMS_EXTRUDER_DOUBLE_NOZZLE_BITMAP_SIZE); m_left_extruder = new AMSextruderImage(this, wxID_ANY, "left_nozzle", AMS_EXTRUDER_DOUBLE_NOZZLE_BITMAP_SIZE); m_left_extruder->setShowState(true); m_right_extruder->setShowState(true); @@ -918,25 +992,17 @@ bool AMSextruder::updateNozzleNum(int nozzle_num, const string& series_name) m_bitmap_sizer->Add(m_right_extruder, 0, wxLEFT | wxALIGN_TOP, FromDIP(2)); m_bitmap_sizer->AddSpacer(2); } + else if (m_nozzle_num > 2) { + for (int nozzle_id = 0; nozzle_id < m_nozzle_num; ++nozzle_id) { + auto *extruder = new AMSextruderImage(this, wxID_ANY, "single_nozzle_n", AMS_EXTRUDER_SINGLE_NOZZLE_N_SIZE); + extruder->setShowState(true); + m_nozzle_extruders.push_back(extruder); + m_bitmap_sizer->Add(extruder, 0, wxALIGN_LEFT | wxALIGN_TOP, 0); + } + } else { - if (MachineObject::is_series_n(m_series_name)) - { - m_left_extruder = new AMSextruderImage(this, wxID_ANY, "single_nozzle_n", AMS_EXTRUDER_SINGLE_NOZZLE_N_SIZE); - } - else if(MachineObject::is_series_x(m_series_name) || MachineObject::is_series_p(m_series_name)) - { - m_left_extruder = new AMSextruderImage(this, wxID_ANY, "single_nozzle_xp", AMS_EXTRUDER_SINGLE_NOZZLE_XP_SIZE); - } - else - { - m_left_extruder = new AMSextruderImage(this, wxID_ANY, "single_nozzle_xp", AMS_EXTRUDER_SINGLE_NOZZLE_XP_SIZE); - } - - m_left_extruder->setShowState(true); - m_right_extruder->setShowState(false); - m_bitmap_sizer->Add(m_left_extruder, 0, wxALIGN_LEFT | wxALIGN_TOP, 0); - m_bitmap_sizer->Add(m_right_extruder, 0, wxLEFT | wxALIGN_TOP, FromDIP(3)); + // Generic N Nozzles } SetSizer(m_bitmap_sizer); @@ -951,6 +1017,9 @@ void AMSextruder::msw_rescale() //m_amsSextruder->msw_rescale(); if (m_left_extruder) m_left_extruder->msw_rescale(); if (m_right_extruder) m_right_extruder->msw_rescale(); + for (auto *extruder : m_nozzle_extruders) { + if (extruder) extruder->msw_rescale(); + } Layout(); Update(); Refresh(); @@ -2123,7 +2192,10 @@ AMSRoadUpPart::AMSRoadUpPart(wxWindow* parent, wxWindowID id, AMSinfo info, AMSM m_ams_model = model; if (m_ams_model == AMSModel::GENERIC_AMS){ - create(parent, id, pos, wxSize(FromDIP(264), FromDIP(34))); + const int width = use_generic_ams_layout(m_ams_model) + ? generic_ams_layout_width(this, static_cast(m_amsinfo.cans.size())) + : FromDIP(264); + create(parent, id, pos, wxSize(width, FromDIP(34))); } else{ create(parent, id, pos, wxSize(FromDIP(78), FromDIP(34))); @@ -2235,6 +2307,38 @@ void AMSRoadUpPart::doRender(wxDC& dc) dc.SetPen(wxPen(AMS_CONTROL_GRAY500, 2, wxPENSTYLE_SOLID)); dc.SetBrush(wxBrush(*wxTRANSPARENT_BRUSH)); + if (use_generic_ams_layout(m_ams_model)) { + const int lane_count = static_cast(m_amsinfo.cans.size()); + if (lane_count <= 0) + return; + + const auto lane_centers = generic_ams_lane_centers(lane_count, size.x); + const int height = FromDIP(21); + for (const int lane_center : lane_centers) + dc.DrawLine(lane_center, 0, lane_center, height); + + if (lane_centers.size() > 1) + dc.DrawLine(lane_centers.front(), height, lane_centers.back(), height); + dc.DrawLine(size.x / 2, height, size.x / 2, size.y); + + if (m_load_step != AMSPassRoadSTEP::AMS_ROAD_STEP_NONE && + m_load_slot_index >= 0 && m_load_slot_index < lane_count) { + const int lane_center = lane_centers[m_load_slot_index]; + dc.SetPen(wxPen(_get_diff_clr(this, m_amsinfo.cans[m_load_slot_index].material_colour), 4, wxPENSTYLE_SOLID)); + dc.DrawLine(lane_center, 0, lane_center, height); + dc.DrawLine(std::min(lane_center, size.x / 2), height, + std::max(lane_center, size.x / 2), height); + if (m_load_step == AMSPassRoadSTEP::AMS_ROAD_STEP_2 || + m_load_step == AMSPassRoadSTEP::AMS_ROAD_STEP_3) + dc.DrawLine(size.x / 2, height, size.x / 2, size.y); + } + + dc.SetPen(*wxTRANSPARENT_PEN); + dc.SetBrush(wxBrush(wxColour(194, 194, 194))); + dc.DrawRectangle(size.x / 2 - FromDIP(14), height - FromDIP(5), FromDIP(28), FromDIP(10)); + return; + } + if ((m_ams_model == N3S_AMS || m_ams_model == EXT_AMS) && m_amsinfo.cans.size() != 4){ dc.DrawLine(((float)size.x / 2), (0), ((float)size.x / 2), (size.y)); if (m_load_step == AMSPassRoadSTEP::AMS_ROAD_STEP_2 || m_load_step == AMSPassRoadSTEP::AMS_ROAD_STEP_3){ @@ -2360,6 +2464,45 @@ void AMSRoadDownPart::UpdateRight(int nozzle_num, AMSRoadShowMode mode) Refresh(); } +void AMSRoadDownPart::SetNozzleCount(int nozzle_num) +{ + const int new_nozzle_num = std::max(1, nozzle_num); + if (m_nozzle_num == new_nozzle_num && + (new_nozzle_num <= 2 || static_cast(m_generic_road_states.size()) == new_nozzle_num)) { + return; + } + + m_nozzle_num = new_nozzle_num; + if (m_nozzle_num > 2) + m_generic_road_states.resize(m_nozzle_num); + else + m_generic_road_states.clear(); + Refresh(); +} + +void AMSRoadDownPart::SetSingleSideLayout(bool enabled, AMSPanelPos pos) +{ + if (m_single_side_layout == enabled && m_single_side_pos == pos) + return; + + m_single_side_layout = enabled; + m_single_side_pos = pos; + Refresh(); +} + +void AMSRoadDownPart::UpdateNozzle(int nozzle_id, AMSRoadShowMode mode) +{ + if (nozzle_id < 0) return; + if (nozzle_id >= static_cast(m_generic_road_states.size())) + m_generic_road_states.resize(nozzle_id + 1); + + auto &state = m_generic_road_states[nozzle_id]; + if (state.mode == mode && m_nozzle_num > 2) return; + state.mode = mode; + m_nozzle_num = std::max(m_nozzle_num, nozzle_id + 1); + Refresh(); +} + void AMSRoadDownPart::OnVamsLoading(bool load, wxColour col /*= AMS_CONTROL_GRAY500*/) { /*m_vams_loading = load; @@ -2389,6 +2532,17 @@ void AMSRoadDownPart::SetPassRoadColour(bool left, wxColour col) Refresh(); } +void AMSRoadDownPart::SetPassRoadColour(int nozzle_id, wxColour col) +{ + if (nozzle_id < 0) return; + if (nozzle_id >= static_cast(m_generic_road_states.size())) + m_generic_road_states.resize(nozzle_id + 1); + + if (m_generic_road_states[nozzle_id].road_color == col) return; + m_generic_road_states[nozzle_id].road_color = col; + Refresh(); +} + void AMSRoadDownPart::SetShowMode(AMSRoadShowMode left_mode, AMSRoadShowMode right_mode) { if (m_left_rode_mode == left_mode && m_right_rode_mode == right_mode) { return; } @@ -2433,8 +2587,34 @@ void AMSRoadDownPart::doRender(wxDC& dc) /*if (m_road_color.Alpha() == 0) { dc.SetPen(wxPen(*wxWHITE, m_passroad_width, wxPENSTYLE_SOLID)); } else { dc.SetPen(wxPen(m_road_color, m_passroad_width, wxPENSTYLE_SOLID)); }*/ dc.SetPen(wxPen(AMS_CONTROL_GRAY500, 2, wxPENSTYLE_SOLID)); + + if (m_nozzle_num > 2) { + const int nozzle_count = std::min(m_nozzle_num, static_cast(m_generic_road_states.size())); + for (int nozzle_id = 0; nozzle_id < nozzle_count; ++nozzle_id) { + const auto &state = m_generic_road_states[nozzle_id]; + if (state.mode == AMSRoadShowMode::AMS_ROAD_MODE_NONE) continue; + + const int x = size.x * (nozzle_id + 1) / (nozzle_count + 1); + dc.SetPen(wxPen(AMS_CONTROL_GRAY500, 2, wxPENSTYLE_SOLID)); + dc.DrawLine(x, size.y / 2, x, size.y); + + if (state.road_length > 0 && + (state.pass_road_step == AMSPassRoadSTEP::AMS_ROAD_STEP_2 || + state.pass_road_step == AMSPassRoadSTEP::AMS_ROAD_STEP_3)) { + dc.SetPen(wxPen(_get_diff_clr(this, state.road_color), 4, wxPENSTYLE_SOLID)); + dc.DrawLine(x - FromDIP(state.road_length), size.y / 2, x, size.y / 2); + } + } + return; + } + auto xpos = left_nozzle_pos.x; - if (m_left_rode_mode == AMSRoadShowMode::AMS_ROAD_MODE_NONE || m_right_rode_mode == AMSRoadShowMode::AMS_ROAD_MODE_NONE){ + if (m_single_side_layout) { + dc.DrawLine(size.x / 2, 0, size.x / 2, size.y); + } + else if ( + (m_left_rode_mode == AMSRoadShowMode::AMS_ROAD_MODE_NONE || + m_right_rode_mode == AMSRoadShowMode::AMS_ROAD_MODE_NONE)){ auto length = 50; if (m_left_rode_mode == AMSRoadShowMode::AMS_ROAD_MODE_AMS_LITE || m_right_rode_mode == AMSRoadShowMode::AMS_ROAD_MODE_AMS_LITE) length = -13; @@ -2465,6 +2645,9 @@ void AMSRoadDownPart::doRender(wxDC& dc) case AMSRoadShowMode::AMS_ROAD_MODE_AMS_LITE: dc.DrawLine(left_nozzle_pos.x, 0, left_nozzle_pos.x, size.y); break; + case AMSRoadShowMode::AMS_ROAD_MODE_GENERIC: + // Generic N-lane geometry is intentionally left unimplemented. + break; default: break; } @@ -2492,23 +2675,41 @@ void AMSRoadDownPart::doRender(wxDC& dc) dc.DrawLine(left_nozzle_pos.x, (size.y / 2), left_nozzle_pos.x + FromDIP(145), (size.y / 2)); dc.DrawLine(left_nozzle_pos.x + FromDIP(145), 0, left_nozzle_pos.x + FromDIP(145), (size.y / 2)); break; + case AMSRoadShowMode::AMS_ROAD_MODE_GENERIC: + // Generic N-lane geometry is intentionally left unimplemented. + break; default: break; } } + if (m_single_side_layout) { + if (m_pass_road_left_step == AMSPassRoadSTEP::AMS_ROAD_STEP_2 || + m_pass_road_left_step == AMSPassRoadSTEP::AMS_ROAD_STEP_3) { + dc.SetPen(wxPen(_get_diff_clr(this, m_road_color[1]), 4, wxPENSTYLE_SOLID)); + dc.DrawLine(size.x / 2, 0, size.x / 2, size.y); + } + return; + } + if (m_right_rode_mode != AMSRoadShowMode::AMS_ROAD_MODE_AMS_LITE){ - if (m_nozzle_num == 2) { + if (m_nozzle_num == 1) { + if (m_right_rode_mode != AMSRoadShowMode::AMS_ROAD_MODE_NONE && m_left_rode_mode != AMSRoadShowMode::AMS_ROAD_MODE_NONE) { + dc.DrawLine((left_nozzle_pos.x), (size.y / 2), (right_nozzle_pos.x), (size.y / 2)); + } + const auto &nozzle_pos = m_single_side_layout && m_single_side_pos == AMSPanelPos::RIGHT_PANEL + ? right_nozzle_pos + : left_nozzle_pos; + dc.DrawLine(nozzle_pos.x, (size.y / 2), nozzle_pos.x, (size.y)); + } + else if (m_nozzle_num == 2) { /*dc.DrawLine(FromDIP(left_nozzle_pos.x), FromDIP(size.y / 2), FromDIP(left_nozzle_pos.x), FromDIP(size.y)); dc.DrawLine(FromDIP(right_nozzle_pos.x), FromDIP(size.y / 2), FromDIP(right_nozzle_pos.x), FromDIP(size.y));*/ dc.DrawLine((left_nozzle_pos.x), (size.y / 2), (left_nozzle_pos.x), (size.y)); dc.DrawLine((right_nozzle_pos.x), (size.y / 2), (right_nozzle_pos.x), (size.y)); } else { - if (m_right_rode_mode != AMSRoadShowMode::AMS_ROAD_MODE_NONE && m_left_rode_mode != AMSRoadShowMode::AMS_ROAD_MODE_NONE) { - dc.DrawLine((left_nozzle_pos.x), (size.y / 2), (right_nozzle_pos.x), (size.y / 2)); - } - dc.DrawLine((left_nozzle_pos.x), (size.y / 2), (left_nozzle_pos.x), (size.y)); + // Generic N Nozzles } } @@ -2533,10 +2734,16 @@ void AMSRoadDownPart::doRender(wxDC& dc) dc.DrawLine(xpos, size.y / 2, xpos, size.y);*/ int x = left_nozzle_pos.x; int len = m_right_road_length; - if (m_nozzle_num == 2) { + if (m_nozzle_num == 1) { + // Keep the single-nozzle path anchored at the left nozzle. + } + else if (m_nozzle_num == 2) { x = right_nozzle_pos.x; len = len - 14; } + else { + // Generic N Nozzles + } dc.DrawLine(((x)), (size.y / 2), x + FromDIP(len), (size.y / 2)); dc.DrawLine(x + FromDIP(len), (0), x + FromDIP(len), (size.y / 2)); dc.DrawLine((x), (size.y / 2), (x), (size.y)); @@ -2544,10 +2751,16 @@ void AMSRoadDownPart::doRender(wxDC& dc) else{ int x = left_nozzle_pos.x; int len = m_right_road_length; - if (m_nozzle_num == 2) { + if (m_nozzle_num == 1) { + // Keep the single-nozzle path anchored at the left nozzle. + } + else if (m_nozzle_num == 2) { x = right_nozzle_pos.x; len = len - 14; } + else { + // Generic N Nozzles + } dc.DrawLine(((x)), (size.y / 2), x + FromDIP(len), (size.y / 2)); dc.DrawLine(x + FromDIP(len), (0), x + FromDIP(len), (size.y / 2)); dc.DrawLine((x), (size.y / 2), (x), (size.y)); @@ -2571,7 +2784,7 @@ void AMSRoadDownPart::doRender(wxDC& dc) } void AMSRoadDownPart::UpdatePassRoad(AMSPanelPos pos, int len, AMSPassRoadSTEP step) { - if (m_nozzle_num >= 2){ + if (m_nozzle_num == 2){ if (pos == AMSPanelPos::LEFT_PANEL){ if (m_left_road_length == len && m_pass_road_left_step == step){ return; } m_left_road_length = len;; @@ -2583,7 +2796,7 @@ void AMSRoadDownPart::UpdatePassRoad(AMSPanelPos pos, int len, AMSPassRoadSTEP s m_pass_road_right_step = step; } } - else{ + else if (m_nozzle_num == 1){ if (pos == AMSPanelPos::LEFT_PANEL) { if (m_left_road_length == len && m_pass_road_left_step == step && m_right_road_length == -1) { return; } m_left_road_length = len; @@ -2597,6 +2810,22 @@ void AMSRoadDownPart::UpdatePassRoad(AMSPanelPos pos, int len, AMSPassRoadSTEP s m_pass_road_right_step = step; } } + else { + // Generic N Nozzles + } + Refresh(); +} + +void AMSRoadDownPart::UpdatePassRoad(int nozzle_id, int len, AMSPassRoadSTEP step) +{ + if (nozzle_id < 0) return; + if (nozzle_id >= static_cast(m_generic_road_states.size())) + m_generic_road_states.resize(nozzle_id + 1); + + auto &state = m_generic_road_states[nozzle_id]; + if (state.road_length == len && state.pass_road_step == step) return; + state.road_length = len; + state.pass_road_step = step; Refresh(); } @@ -2615,7 +2844,10 @@ AMSPreview::AMSPreview(wxWindow* parent, wxWindowID id, AMSinfo amsinfo, AMSMode { wxWindow::Create(parent, id, pos); if (itemType == AMSModel::GENERIC_AMS || itemType == AMSModel::AMS_LITE || itemType == AMSModel::N3F_AMS) { - create(parent, id, pos, AMS_PREV_FOUR_SIZE); + if (itemType == AMSModel::GENERIC_AMS && use_generic_ams_layout(itemType)) + create(parent, id, pos, wxSize(generic_ams_preview_width(this, static_cast(amsinfo.cans.size())), AMS_PREV_FOUR_SIZE.y)); + else + create(parent, id, pos, AMS_PREV_FOUR_SIZE); } else { create(parent, id, pos, AMS_PREV_SINGLE_SIZE); @@ -2650,7 +2882,13 @@ void AMSPreview::UpdateInfo(AMSinfo amsinfo) m_amsinfo = amsinfo; m_ams_item_type = amsinfo.ams_type; - if (m_ams_item_type == AMSModel::GENERIC_AMS || m_ams_item_type == AMSModel::AMS_LITE || m_ams_item_type == AMSModel::N3F_AMS) { + if (m_ams_item_type == AMSModel::GENERIC_AMS && use_generic_ams_layout(m_ams_item_type)) { + const wxSize generic_size(generic_ams_preview_width(this, static_cast(m_amsinfo.cans.size())), AMS_PREV_FOUR_SIZE.y); + SetSize(generic_size); + SetMinSize(generic_size); + SetMaxSize(generic_size); + } + else if (m_ams_item_type == AMSModel::GENERIC_AMS || m_ams_item_type == AMSModel::AMS_LITE || m_ams_item_type == AMSModel::N3F_AMS) { SetMinSize(AMS_PREV_FOUR_SIZE); SetMaxSize(AMS_PREV_FOUR_SIZE); } else { @@ -2758,8 +2996,41 @@ void AMSPreview::doRender(wxDC &dc) dc.DrawRoundedRectangle(0, 0, size.x, size.y, FromDIP(3)); auto left = 0; - //four slot - if (m_ams_item_type != AMSModel::EXT_AMS && m_ams_item_type != AMSModel::N3S_AMS){ + // Generic AMS previews use the same normalized lane centers as the card row and connector. + if (m_ams_item_type == AMSModel::GENERIC_AMS && use_generic_ams_layout(m_ams_item_type)) { + const int lane_count = static_cast(m_amsinfo.cans.size()); + const auto lane_centers = generic_ams_lane_centers(lane_count, size.x); + const int cube_width = FromDIP(9); + const int cube_height = FromDIP(14); + + for (int lane = 0; lane < lane_count; ++lane) { + const Caninfo &can = m_amsinfo.cans[lane]; + const int cube_left = lane_centers[lane] - cube_width / 2; + const int cube_top = (size.y - cube_height) / 2; + wxRect rect(cube_left, cube_top, cube_width, cube_height); + + if (can.material_colour.Alpha() == 0) { + if (wxGetApp().dark_mode()) + dc.DrawBitmap(m_ts_bitmap_cube_dark.bmp(), cube_left - FromDIP(1), (size.y - m_ts_bitmap_cube_dark.GetBmpHeight()) / 2); + else + dc.DrawBitmap(m_ts_bitmap_cube.bmp(), cube_left - FromDIP(1), (size.y - m_ts_bitmap_cube.GetBmpHeight()) / 2); + } + else if (can.material_state == AMSCanType::AMS_CAN_TYPE_EMPTY) { + 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)); + } + else { + wxColour color = can.material_colour; + change_the_opacity(color); + dc.SetPen(wxPen(*wxTRANSPARENT_PEN)); + dc.SetBrush(wxBrush(color)); + dc.DrawRoundedRectangle(rect, 2); + } + } + } + // four slot + else if (m_ams_item_type != AMSModel::EXT_AMS && m_ams_item_type != AMSModel::N3S_AMS){ left = FromDIP(8); for (std::vector::iterator iter = m_amsinfo.cans.begin(); iter != m_amsinfo.cans.end(); iter++) { @@ -3158,10 +3429,19 @@ AmsItem::AmsItem(wxWindow *parent,AMSinfo info, AMSModel model, AMSPanelPos pos m_panel_pos = pos; if (m_ams_model == AMSModel::GENERIC_AMS){ - wxWindow::Create(parent, wxID_ANY, wxDefaultPosition, AMS_CANS_WINDOW_SIZE); - SetSize(AMS_CANS_WINDOW_SIZE); - SetMinSize(AMS_CANS_WINDOW_SIZE); - SetMaxSize(AMS_CANS_WINDOW_SIZE); + const int lane_count = static_cast(m_info.cans.size()); + const int width = use_generic_ams_layout(m_ams_model) && lane_count <= 8 + ? generic_ams_layout_width(this, lane_count) + : FromDIP(264); + const wxSize generic_size(width, FromDIP(174)); + wxWindow::Create(parent, wxID_ANY, wxDefaultPosition, generic_size); + SetSize(generic_size); + SetMinSize(generic_size); + // For large lane counts the card is a viewport, not the lane strip. Let + // its parent allocate the available width; the scrolled child retains + // the wider virtual content size. + if (!(use_generic_ams_layout(m_ams_model) && lane_count > 8)) + SetMaxSize(generic_size); } else{ wxWindow::Create(parent, wxID_ANY, wxDefaultPosition, AMS_SINGLE_CAN_SIZE); @@ -3187,12 +3467,43 @@ void AmsItem::create(wxWindow *parent) if (m_ams_model != AMSModel::AMS_LITE) { sizer_can = new wxBoxSizer(wxHORIZONTAL); sizer_item = new wxBoxSizer(wxVERTICAL); - auto it = m_info.cans.begin(); - for (; it != m_info.cans.end(); it++) { - AddCan(*it, m_can_count, m_info.cans.size(), sizer_can); + const bool generic_layout = use_generic_ams_layout(m_ams_model); + const int lane_count = static_cast(m_info.cans.size()); + const int content_width = generic_layout ? generic_ams_layout_width(this, lane_count) : 0; + const bool scroll_lanes = generic_layout && lane_count > 8; + const auto lane_centers = generic_layout + ? generic_ams_lane_centers(lane_count, content_width) + : std::vector(); + + m_can_parent = this; + if (scroll_lanes) { + m_can_scroll = new wxScrolledWindow(this, wxID_ANY); + m_can_scroll->SetScrollRate(FromDIP(10), 0); + m_can_scroll->SetMinSize(wxSize(FromDIP(264), FromDIP(143))); + m_can_scroll->SetBackgroundColour(StateColor::darkModeColorFor(AMS_CONTROL_DEF_LIB_BK_COLOUR)); + m_can_scroll_sizer = new wxBoxSizer(wxVERTICAL); + m_can_scroll->SetSizer(m_can_scroll_sizer); + m_can_parent = m_can_scroll; + } + + int previous_right = 0; + for (int can_index = 0; can_index < lane_count; ++can_index) { + if (generic_layout) { + const int left = lane_centers[can_index] - FromDIP(26); + sizer_can->AddSpacer(std::max(0, left - previous_right)); + AddCan(m_info.cans[can_index], m_can_count, m_info.cans.size(), sizer_can); + previous_right = left + FromDIP(52); + } + else { + AddCan(m_info.cans[can_index], m_can_count, m_info.cans.size(), sizer_can); + } m_can_count++; } - it = m_info.cans.begin(); + if (generic_layout) + sizer_can->AddSpacer(std::max(0, content_width - previous_right)); + if (generic_layout) + sizer_can->SetMinSize(wxSize(content_width, -1)); + //auto road_panel = new wxWindow(this, wxID_ANY); //auto road_panel = new wxPanel(this, wxID_ANY); //road_panel->SetSize(AMS_CAN_ROAD_SIZE); @@ -3207,11 +3518,18 @@ void AmsItem::create(wxWindow *parent) sizer_item->Add(m_ext_image, 0, wxALIGN_CENTER, 0); } } - m_panel_road = new AMSRoadUpPart(this, wxID_ANY, m_info, m_ams_model); + m_panel_road = new AMSRoadUpPart(m_can_parent, wxID_ANY, m_info, m_ams_model); - sizer_item->Add(sizer_can, 0, wxALIGN_CENTER_HORIZONTAL, 0); - //sizer_item->Add(m_panel_road, 0, wxALIGN_CENTER_HORIZONTAL, 0); - sizer_item->Add(m_panel_road, 1, wxEXPAND); + if (scroll_lanes) { + m_can_scroll_sizer->Add(sizer_can, 0, wxALIGN_LEFT, 0); + m_can_scroll_sizer->Add(m_panel_road, 0, wxALIGN_LEFT, 0); + sizer_item->Add(m_can_scroll, 1, wxEXPAND); + } + else { + sizer_item->Add(sizer_can, 0, wxALIGN_CENTER_HORIZONTAL, 0); + //sizer_item->Add(m_panel_road, 0, wxALIGN_CENTER_HORIZONTAL, 0); + sizer_item->Add(m_panel_road, 1, wxEXPAND); + } SetSizer(sizer_item); } @@ -3235,13 +3553,17 @@ void AmsItem::create(wxWindow *parent) Layout(); Fit(); + if (m_can_scroll) { + m_can_scroll->Layout(); + m_can_scroll->FitInside(); + } Thaw(); //Refresh(); } void AmsItem::AddCan(Caninfo caninfo, int canindex, int maxcan, wxBoxSizer* sizer) { - auto amscan = new wxWindow(this, wxID_ANY); + auto amscan = new wxWindow(m_can_parent ? m_can_parent : this, wxID_ANY); amscan->SetSize(wxSize(FromDIP(52), FromDIP(109))); amscan->SetMinSize(wxSize(FromDIP(52), FromDIP(109))); @@ -3322,7 +3644,8 @@ void AmsItem::AddCan(Caninfo caninfo, int canindex, int maxcan, wxBoxSizer* size amscan->Layout(); amscan->Fit(); - sizer->Add(amscan, 0, wxUP | wxLEFT | wxRIGHT, FromDIP(5)); + const int margin = use_generic_ams_layout(m_ams_model) ? 0 : FromDIP(5); + sizer->Add(amscan, 0, wxUP | wxLEFT | wxRIGHT, margin); m_can_lib_list[caninfo.can_id] = m_panel_lib; //m_can_road_list[caninfo.can_id] = m_panel_road; @@ -3447,6 +3770,10 @@ void AmsItem::UpdateInfo(AMSinfo info) } Layout(); + if (m_can_scroll) { + m_can_scroll->Layout(); + m_can_scroll->FitInside(); + } } void AmsItem::SetDefSelectCan() diff --git a/src/slic3r/GUI/Widgets/AMSItem.hpp b/src/slic3r/GUI/Widgets/AMSItem.hpp index 55c0a3ca8d..8d50084228 100644 --- a/src/slic3r/GUI/Widgets/AMSItem.hpp +++ b/src/slic3r/GUI/Widgets/AMSItem.hpp @@ -30,6 +30,7 @@ #include #include #include +#include #include "slic3r/GUI/DeviceCore/DevFilaSwitch.h" // Orca: DevFilaSwitch::SwitchPos for inlet-aware AMS placement @@ -96,6 +97,7 @@ enum class AMSRoadShowMode : int { AMS_ROAD_MODE_SINGLE, AMS_ROAD_MODE_SINGLE_N3S, AMS_ROAD_MODE_AMS_LITE, + AMS_ROAD_MODE_GENERIC, AMS_ROAD_MODE_NONE }; @@ -480,6 +482,7 @@ public: //AMSextruderImage *m_amsSextruder{nullptr}; AMSextruderImage* m_left_extruder = nullptr; AMSextruderImage* m_right_extruder = nullptr; + std::vector m_nozzle_extruders; AMSextruder(wxWindow *parent, wxWindowID id, int nozzle_num, const wxPoint &pos = wxDefaultPosition, const wxSize &size = wxDefaultSize); ~AMSextruder(); @@ -681,11 +684,16 @@ public: // void Update(AMSRoadDownPartMode nozzle, AMSRoadShowMode left_mode, AMSRoadShowMode right_mode, int left_len, int right_len); void UpdateLeft(int nozzle_num, AMSRoadShowMode mode); void UpdateRight(int nozzle_num, AMSRoadShowMode mode); + void SetNozzleCount(int nozzle_num); + void SetSingleSideLayout(bool enabled, AMSPanelPos pos = AMSPanelPos::LEFT_PANEL); + void UpdateNozzle(int nozzle_id, AMSRoadShowMode mode); void OnVamsLoading(bool load, wxColour col = AMS_CONTROL_GRAY500); void SetPassRoadColour(bool left, wxColour col); + void SetPassRoadColour(int nozzle_id, wxColour col); void SetShowMode(AMSRoadShowMode left_mode, AMSRoadShowMode right_mode); void UpdatePassRoad(AMSPanelPos pos, int len, AMSPassRoadSTEP step); + void UpdatePassRoad(int nozzle_id, int len, AMSPassRoadSTEP step); void paintEvent(wxPaintEvent& evt); void render(wxDC& dc); @@ -698,6 +706,8 @@ private: AMSRoadShowMode m_left_rode_mode = {AMSRoadShowMode::AMS_ROAD_MODE_FOUR}; AMSRoadShowMode m_right_rode_mode = {AMSRoadShowMode::AMS_ROAD_MODE_FOUR}; bool m_selected = {false}; + bool m_single_side_layout = {false}; + AMSPanelPos m_single_side_pos = {AMSPanelPos::LEFT_PANEL}; int m_left_road_length = {-1}; int m_right_road_length = {-1}; @@ -705,6 +715,15 @@ private: AMSPassRoadSTEP m_pass_road_right_step = {AMSPassRoadSTEP::AMS_ROAD_STEP_NONE}; std::map m_road_color; + + struct GenericRoadState + { + AMSRoadShowMode mode{AMSRoadShowMode::AMS_ROAD_MODE_NONE}; + int road_length{-1}; + AMSPassRoadSTEP pass_road_step{AMSPassRoadSTEP::AMS_ROAD_STEP_NONE}; + wxColour road_color{AMS_CONTROL_GRAY500}; + }; + std::vector m_generic_road_states; }; /************************************************* @@ -864,6 +883,9 @@ private: std::map m_can_lib_list; //std::map m_can_road_list; + wxScrolledWindow* m_can_scroll = { nullptr }; + wxBoxSizer* m_can_scroll_sizer = { nullptr }; + wxWindow* m_can_parent = { nullptr }; AMSRoadUpPart* m_panel_road = { nullptr }; std::map m_can_refresh_list; AMSHumidity* m_humidity = { nullptr }; diff --git a/src/slic3r/GUI/Widgets/MultiNozzleSync.cpp b/src/slic3r/GUI/Widgets/MultiNozzleSync.cpp index 2fe66de8de..b26bcf80f4 100644 --- a/src/slic3r/GUI/Widgets/MultiNozzleSync.cpp +++ b/src/slic3r/GUI/Widgets/MultiNozzleSync.cpp @@ -866,14 +866,21 @@ void MultiNozzleStatusTable::UpdateRackInfo(std::weak_ptr rack) bool has_right = false; for (auto& elem : nozzles_in_extruder) { auto& nozzle = elem.second; - int extruder_id = nozzle.AtLeftExtruder() ? 0 : 1; - if (nozzle.AtRightExtruder()) - has_right = true; + + int extruder_id{}; + if (wxGetApp().preset_bundle->is_bbl_vendor()) { + extruder_id = nozzle.AtLeftExtruder() ? 0 : 1; + if (nozzle.AtRightExtruder()) + has_right = true; + } + else + extruder_id = nozzle.GetExtruderId(); NozzleVolumeType volume_type = DevNozzle::ToNozzleVolumeType(nozzle.m_nozzle_flow); m_badge->SetExtruderInfo(extruder_id, format_diameter_to_str(nozzle.GetNozzleDiameter()), volume_type); } + // TODO: Update for N extruders m_badge->SetExtruderValid(has_right); } } diff --git a/src/slic3r/GUI/Widgets/TempInput.cpp b/src/slic3r/GUI/Widgets/TempInput.cpp index d75d8cecd7..418f1a5620 100644 --- a/src/slic3r/GUI/Widgets/TempInput.cpp +++ b/src/slic3r/GUI/Widgets/TempInput.cpp @@ -184,6 +184,9 @@ void TempInput::SetFinish() wxCommandEvent event(wxCUSTOMEVT_SET_TEMP_FINISH); event.SetInt(temp_type); event.SetString(wxString::Format("%d", m_input_type)); + // N-extruder temp controls all share TEMP_OF_NORMAL_TYPE, so the string payload above can't + // tell them apart; carry widget identity so the listener can find which one fired. + event.SetEventObject(this); wxPostEvent(this->GetParent(), event); } diff --git a/src/slic3r/Utils/MoonrakerPrinterAgent.cpp b/src/slic3r/Utils/MoonrakerPrinterAgent.cpp index 96570b656b..e949ae79f5 100644 --- a/src/slic3r/Utils/MoonrakerPrinterAgent.cpp +++ b/src/slic3r/Utils/MoonrakerPrinterAgent.cpp @@ -1057,6 +1057,25 @@ int MoonrakerPrinterAgent::handle_request(const std::string& dev_id, const std:: } } + // Tool selection is printer-specific G-code. Moonraker forwards the + // conventional T command to Klipper, where the printer can + // implement logical selection or a physical toolhead attach macro. + if (cmd == "select_extruder") { + if (!json["print"].contains("extruder_index") || !json["print"]["extruder_index"].is_number_integer()) { + BOOST_LOG_TRIVIAL(error) << "MoonrakerPrinterAgent: select_extruder missing integer extruder_index, full json: " << json_str; + return BAMBU_NETWORK_ERR_INVALID_RESULT; + } + + const int extruder_idx = json["print"]["extruder_index"].get(); + if (extruder_idx < 0) { + BOOST_LOG_TRIVIAL(error) << "MoonrakerPrinterAgent: select_extruder received negative extruder_index: " << extruder_idx; + return BAMBU_NETWORK_ERR_INVALID_RESULT; + } + + send_gcode_async(dev_id, "T" + std::to_string(extruder_idx)); + return BAMBU_NETWORK_SUCCESS; + } + if (cmd == "home") { send_gcode_async(dev_id, "G28"); return BAMBU_NETWORK_SUCCESS; @@ -1153,7 +1172,7 @@ bool MoonrakerPrinterAgent::query_printer_status(const std::string& base_url, nlohmann::json& status, std::string& error) const { - std::string url = join_url(base_url, "/printer/objects/query?print_stats&virtual_sdcard&extruder&heater_bed&fan"); + std::string url = join_url(base_url, "/printer/objects/query?print_stats&virtual_sdcard&toolhead&extruder&heater_bed&fan"); std::string response_body; bool success = false; @@ -1566,11 +1585,12 @@ void MoonrakerPrinterAgent::run_status_stream(std::string dev_id, std::string ba } for (const auto& name : this->available_objects) { - if (name == "extruder" || name.rfind("extruder", 0) == 0) { + const bool is_main_extruder = name == "extruder"; + const bool is_additional_extruder = + name.rfind("extruder", 0) == 0 && name.size() > 8 && + std::all_of(name.begin() + 8, name.end(), [](unsigned char c) { return std::isdigit(c) != 0; }); + if (is_main_extruder || is_additional_extruder) { subscribe_objects.insert(name); - if (name == "extruder") { - break; - } } } } else { @@ -1866,6 +1886,81 @@ nlohmann::json MoonrakerPrinterAgent::build_print_payload_locked() const } } + // MachineObject::parse_new_info() only parses device.extruder when the four + // legacy new-protocol fields are present. Moonraker has no equivalents, so + // empty values act as compatibility markers without enabling any features. + std::map extruder_status; + for (const auto& item : status_cache.items()) { + int extruder_id = -1; + if (item.key() == "extruder") { + extruder_id = 0; + } else if (item.key().rfind("extruder", 0) == 0 && item.key().size() > 8 && + std::all_of(item.key().begin() + 8, item.key().end(), [](unsigned char c) { return std::isdigit(c) != 0; })) { + try { + extruder_id = std::stoi(item.key().substr(8)); + } catch (...) { + extruder_id = -1; + } + } + if (extruder_id >= 0 && item.value().is_object()) { + extruder_status.emplace(extruder_id, &item.value()); + } + } + + if (!extruder_status.empty()) { + auto& device_extruder = payload["print"]["device"]["extruder"]; + + int current_extruder_id = 0; + if (status_cache.contains("toolhead") && status_cache["toolhead"].is_object() && + status_cache["toolhead"].contains("extruder") && status_cache["toolhead"]["extruder"].is_string()) { + const std::string active_extruder = status_cache["toolhead"]["extruder"].get(); + if (active_extruder != "extruder" && active_extruder.rfind("extruder", 0) == 0 && active_extruder.size() > 8 && + std::all_of(active_extruder.begin() + 8, active_extruder.end(), [](unsigned char c) { return std::isdigit(c) != 0; })) { + try { + const int parsed_id = std::stoi(active_extruder.substr(8)); + if (parsed_id >= 0 && parsed_id < 16 && extruder_status.count(parsed_id) != 0) { + current_extruder_id = parsed_id; + } + } catch (...) { + // Keep the main extruder as the fallback for malformed status data. + } + } + } + + const int extruder_count = std::min(static_cast(extruder_status.size()), 0xF); + device_extruder["state"] = extruder_count | (current_extruder_id << 4); + device_extruder["info"] = nlohmann::json::array(); + + auto encoded_temperature = [](const nlohmann::json& status, const char* key) { + if (!status.contains(key) || !status[key].is_number()) { + return 0; + } + const double temperature = status[key].get(); + return std::clamp(static_cast(temperature + 0.5), 0, 65535); + }; + + for (const auto& [extruder_id, status] : extruder_status) { + const int current_temperature = encoded_temperature(*status, "temperature"); + const int target_temperature = encoded_temperature(*status, "target"); + device_extruder["info"].push_back({ + {"id", extruder_id}, + {"filam_bak", nlohmann::json::array()}, + {"info", 1 << 3}, // The configured Moonraker extruder has a toolhead. + {"temp", current_temperature | (target_temperature << 16)}, + {"spre", 0}, + {"snow", 0}, + {"star", 0}, + {"stat", 0}, + {"hnow", 0}, + }); + } + + payload["print"]["cfg"] = ""; + payload["print"]["fun"] = ""; + payload["print"]["aux"] = ""; + payload["print"]["stat"] = ""; + } + if (status_cache.contains("heater_bed") && status_cache["heater_bed"].is_object()) { const auto& bed = status_cache["heater_bed"]; if (bed.contains("temperature") && bed["temperature"].is_number()) { diff --git a/tests/slic3rutils/test_dev_mapping.cpp b/tests/slic3rutils/test_dev_mapping.cpp index 9d9d1a7e2a..cf93605113 100644 --- a/tests/slic3rutils/test_dev_mapping.cpp +++ b/tests/slic3rutils/test_dev_mapping.cpp @@ -17,6 +17,7 @@ #include "slic3r/GUI/DeviceCore/DevDefs.h" #include "libslic3r/ProjectTask.hpp" #include +#include #include #include @@ -65,6 +66,108 @@ TEST_CASE("AMS tray placeholder state follows the latest status", "[DevFilaSyste CHECK_FALSE(tray->is_slot_placeholder); } +TEST_CASE("Agent filament slots preserve every extruder and AMS binding", "[DevFilaSystem]") +{ + MachineObject obj(nullptr, nullptr, "test", "test_dev", "127.0.0.1"); + + const json filament = json::parse(R"({ + "units": [ + { "id": "0", "extruder": 0, "slots": [ + { "index": 0, "loaded": true, "material": "PLA", "color": "FF5733FF" }, + { "index": 1, "loaded": true, "material": "PLA", "color": "33C1FFFF" }, + { "index": 2, "loaded": true, "material": "PLA", "color": "8D33FFFF" }, + { "index": 3, "loaded": false, "material": "", "color": "00000000" } + ] }, + { "id": "1", "extruder": 1, "slots": [ + { "index": 0, "loaded": true, "material": "ASA", "color": "33C1FFFF" }, + { "index": 1, "loaded": true, "material": "ASA", "color": "8D33FFFF" }, + { "index": 2, "loaded": true, "material": "ASA", "color": "FF5733FF" }, + { "index": 3, "loaded": false, "material": "", "color": "00000000" } + ] }, + { "id": "2", "extruder": 2, "slots": [ + { "index": 0, "loaded": true, "material": "ABS", "color": "8D33FFFF" }, + { "index": 1, "loaded": true, "material": "ABS", "color": "FF5733FF" }, + { "index": 2, "loaded": true, "material": "ABS", "color": "33C1FFFF" }, + { "index": 3, "loaded": false, "material": "", "color": "00000000" } + ] }, + { "id": "3", "extruder": 3, "slots": [ + { "index": 0, "loaded": true, "material": "PETG", "color": "FF5733FF" }, + { "index": 1, "loaded": true, "material": "PETG", "color": "33C1FFFF" }, + { "index": 2, "loaded": true, "material": "PETG", "color": "8D33FFFF" }, + { "index": 3, "loaded": false, "material": "", "color": "00000000" } + ] } + ], + "external": [ + { "extruder": 3, "loaded": true, "material": "PETG", "preset_id": "P3" }, + { "extruder": 0, "loaded": true, "material": "PLA", "preset_id": "P0" }, + { "extruder": 2, "loaded": false, "material": "ABS", "preset_id": "P2" }, + { "extruder": 1, "loaded": true, "material": "ASA", "preset_id": "P1" } + ] + })"); + + DevFilaSystemParser::ParseAgentFilament(filament, &obj, obj.GetFilaSystem().get()); + + REQUIRE(obj.vt_slot.size() == 4); + CHECK(obj.vt_slot[0].id == "255"); + CHECK(obj.vt_slot[1].id == "254"); + CHECK(obj.vt_slot[2].id == "253"); + CHECK(obj.vt_slot[3].id == "252"); + CHECK(obj.vt_slot[0].setting_id == "P0"); + CHECK(obj.vt_slot[1].setting_id == "P1"); + CHECK(obj.vt_slot[2].setting_id == "P2"); + CHECK(obj.vt_slot[3].setting_id == "P3"); + + REQUIRE(obj.GetFilaSystem()->GetAmsCount() == 4); + for (int extruder = 0; extruder < 4; ++extruder) { + const auto& ams = obj.GetFilaSystem()->GetAmsList().at(std::to_string(extruder)); + CHECK(ams->GetExtruderId() == extruder); + CHECK(ams->GetBindedExtruderSet() == std::set{extruder}); + REQUIRE(ams->GetTrays().size() == 4); + CHECK(ams->GetTrays().at("0")->color != ams->GetTrays().at("1")->color); + CHECK(ams->GetTrays().at("1")->color != ams->GetTrays().at("2")->color); + CHECK_FALSE(ams->GetTrays().at("3")->is_exists); + } + + CHECK(obj.contains_tray("253", "0")); + CHECK(obj.get_tray("253", "0").setting_id == "P2"); + CHECK(obj.GetFilaSystem()->GetExtruderIdByAmsId("253") == 2); + + const auto tray_index_map = obj.GetFilaSystem()->GetTrayIndexMap(); + REQUIRE(tray_index_map.count(252) == 1); + CHECK(tray_index_map.at(252).first == 252); + CHECK(tray_index_map.at(252).second == 0); +} + +TEST_CASE("Generic AMS tray index map uses cumulative lane counts", "[DevMapping]") +{ + MachineObject obj(nullptr, nullptr, "test", "test_dev", "127.0.0.1"); + + json filament = { {"units", json::array()} }; + for (int ams_id = 0; ams_id < 2; ++ams_id) { + json unit = { + {"id", std::to_string(ams_id)}, + {"extruder", ams_id}, + {"slots", json::array()} + }; + for (int slot_id = 0; slot_id < 5; ++slot_id) + unit["slots"].push_back({{"index", slot_id}, {"loaded", false}}); + filament["units"].push_back(std::move(unit)); + } + + DevFilaSystemParser::ParseAgentFilament(filament, &obj, obj.GetFilaSystem().get()); + + const auto tray_index_map = obj.GetFilaSystem()->GetTrayIndexMap(); + REQUIRE(tray_index_map.size() == 10); + CHECK(tray_index_map.at(0).first == 0); + CHECK(tray_index_map.at(0).second == 0); + CHECK(tray_index_map.at(4).first == 0); + CHECK(tray_index_map.at(4).second == 4); + CHECK(tray_index_map.at(5).first == 1); + CHECK(tray_index_map.at(5).second == 0); + CHECK(tray_index_map.at(9).first == 1); + CHECK(tray_index_map.at(9).second == 4); +} + TEST_CASE("Switch-bound AMS trays map to the left extruder", "[DevMapping]") { MachineObject obj(nullptr, nullptr, "test", "test_dev", "127.0.0.1");