From 1afb98ab820e1f0fcb97be1fa9fdebdc46117fc8 Mon Sep 17 00:00:00 2001 From: Ian Chua Date: Mon, 5 Oct 2026 20:29:56 +0800 Subject: [PATCH] feat: add generic N-extruder AMS controls --- src/slic3r/GUI/StatusPanel.cpp | 37 +++ src/slic3r/GUI/StatusPanel.hpp | 1 + src/slic3r/GUI/Widgets/AMSControl.cpp | 391 +++++++++++++++++++++++++- src/slic3r/GUI/Widgets/AMSControl.hpp | 21 ++ src/slic3r/GUI/Widgets/AMSItem.cpp | 150 +++++++++- src/slic3r/GUI/Widgets/AMSItem.hpp | 17 ++ 6 files changed, 596 insertions(+), 21 deletions(-) diff --git a/src/slic3r/GUI/StatusPanel.cpp b/src/slic3r/GUI/StatusPanel.cpp index be1dcf21d0..3564fb9d03 100644 --- a/src/slic3r/GUI/StatusPanel.cpp +++ b/src/slic3r/GUI/StatusPanel.cpp @@ -1797,9 +1797,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)); @@ -3850,6 +3860,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)); + 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; @@ -4901,6 +4930,9 @@ void StatusPanel::on_ams_switch(SimpleEvent &event) { if(obj){ + if (m_ams_nozzle_switch && m_ams_nozzle_switch->IsShown()) + m_ams_nozzle_switch->SetSelection(m_ams_control->GetActiveNozzleId()); + if (wxGetApp().preset_bundle->is_bbl_vendor()) { /*right*/ if (obj->GetExtderSystem()->GetTotalExtderCount() > 0) { @@ -5725,6 +5757,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(); @@ -5825,6 +5858,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(); } @@ -5912,6 +5947,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 fd871b6f78..2e5a625668 100644 --- a/src/slic3r/GUI/StatusPanel.hpp +++ b/src/slic3r/GUI/StatusPanel.hpp @@ -569,6 +569,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; diff --git a/src/slic3r/GUI/Widgets/AMSControl.cpp b/src/slic3r/GUI/Widgets/AMSControl.cpp index 5bab52d42f..70e39fa733 100644 --- a/src/slic3r/GUI/Widgets/AMSControl.cpp +++ b/src/slic3r/GUI/Widgets/AMSControl.cpp @@ -241,7 +241,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); @@ -520,7 +520,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 @@ -604,6 +604,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(); @@ -614,7 +616,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(); @@ -625,16 +627,162 @@ 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_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->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); + + pane.ams_book = new wxSimplebook(pane.page, wxID_ANY, wxDefaultPosition, AMS_CANS_WINDOW_SIZE, 0); + 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; @@ -756,6 +904,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; @@ -984,6 +1134,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{ @@ -998,9 +1159,13 @@ void AMSControl::UpdateAms(const std::string &series_name, m_dev_id = dev_id; if (fresh){ ClearAms(); + // 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 (m_total_ext_count == 1) CreateAmsSingleNozzle(series_name, printer_type); - else if (m_total_ext_count == 2) + else if (m_total_ext_count == 2 || is_bbl_behavior) CreateAmsDoubleNozzle(series_name, printer_type); else CreateAmsMultiNozzle(series_name, printer_type); @@ -1077,7 +1242,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(); } @@ -1113,7 +1278,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)); @@ -1207,6 +1378,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)); @@ -1235,7 +1411,16 @@ void AMSControl::createAmsPanel(wxSimplebook *parent, int &idx, std::vector 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->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); } @@ -1279,7 +1464,18 @@ 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->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); } @@ -1335,6 +1531,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){ @@ -1373,8 +1584,95 @@ 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_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); @@ -1513,6 +1811,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); @@ -1637,6 +1949,67 @@ 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_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 2359327553..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}; @@ -211,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 025360dc7f..a895eeb3e8 100644 --- a/src/slic3r/GUI/Widgets/AMSItem.cpp +++ b/src/slic3r/GUI/Widgets/AMSItem.cpp @@ -168,10 +168,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; @@ -854,7 +858,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) @@ -894,6 +902,12 @@ 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 } @@ -906,10 +920,11 @@ 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); - - wxBoxSizer* m_bitmap_sizer = new wxBoxSizer(wxHORIZONTAL); + 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); @@ -920,12 +935,11 @@ bool AMSextruder::updateNozzleNum(int nozzle_num, const string& series_name) } 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)); } - if (m_nozzle_num == 2) + 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); @@ -933,6 +947,14 @@ 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 { // Generic N Nozzles @@ -950,6 +972,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(); @@ -2359,6 +2384,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; @@ -2388,6 +2452,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; } @@ -2432,8 +2507,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; @@ -2496,12 +2597,24 @@ void AMSRoadDownPart::doRender(wxDC& dc) } } + 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 == 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)); } - dc.DrawLine((left_nozzle_pos.x), (size.y / 2), (left_nozzle_pos.x), (size.y)); + 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)); @@ -2617,6 +2730,19 @@ void AMSRoadDownPart::UpdatePassRoad(AMSPanelPos pos, int len, AMSPassRoadSTEP s 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(); +} + void AMSRoadDownPart::msw_rescale() { Layout(); Fit(); diff --git a/src/slic3r/GUI/Widgets/AMSItem.hpp b/src/slic3r/GUI/Widgets/AMSItem.hpp index 55c0a3ca8d..1a9f09e3bf 100644 --- a/src/slic3r/GUI/Widgets/AMSItem.hpp +++ b/src/slic3r/GUI/Widgets/AMSItem.hpp @@ -480,6 +480,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 +682,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 +704,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 +713,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; }; /*************************************************