#include "FilamentGroupPopup.hpp" #include "GUI_App.hpp" #include "MsgDialog.hpp" #include "wx/dcgraph.h" #include "I18N.hpp" #include "PartPlate.hpp" namespace Slic3r { namespace GUI { static const wxColour LabelEnableColor = wxColour("#262E30"); static const wxColour LabelDisableColor = wxColour("#ACACAC"); static const wxColour GreyColor = wxColour("#6B6B6B"); static const wxColour GreenColor = wxColour("#00AE42"); static const wxColour BackGroundColor = wxColour("#FFFFFF"); static bool should_pop_up() { const auto &preset_bundle = wxGetApp().preset_bundle; const auto &full_config = preset_bundle->full_config(); const auto nozzle_diameters = full_config.option("nozzle_diameter"); return nozzle_diameters->size() > 1; } static FilamentMapMode get_prefered_map_mode() { const static std::map enum_keys_map = ConfigOptionEnum::get_enum_values(); auto &app_config = wxGetApp().app_config; std::string mode_str = app_config->get("prefered_filament_map_mode"); auto iter = enum_keys_map.find(mode_str); if (iter == enum_keys_map.end()) { BOOST_LOG_TRIVIAL(warning) << __FUNCTION__ << boost::format("Could not get prefered_filament_map_mode from app config, use AutoForFlsuh mode"); return FilamentMapMode::fmmAutoForFlush; } return FilamentMapMode(iter->second); } static void set_prefered_map_mode(FilamentMapMode mode) { const static std::vector enum_values = ConfigOptionEnum::get_enum_names(); auto &app_config = wxGetApp().app_config; std::string mode_str; if (mode < enum_values.size()) mode_str = enum_values[mode]; if (mode_str.empty()) BOOST_LOG_TRIVIAL(warning) << __FUNCTION__ << boost::format("Set empty prefered_filament_map_mode to app config"); app_config->set("prefered_filament_map_mode", mode_str); } bool play_dual_extruder_slice_video() { const wxString video_url = "https://e.bambulab.com/t?c=HDB24RlwSmt77YFH"; if (wxLaunchDefaultBrowser(video_url)) { BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format("Video is being played using the system's default browser."); return true; } BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << boost::format("launch system's default browser failed"); return false; } bool play_dual_extruder_print_tpu_video() { const wxString video_url = "https://e.bambulab.com/t?c=fwWqpBg37Liel92N"; if (wxLaunchDefaultBrowser(video_url)){ BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format("Print Tpu Video is being played using the system's default browser."); return true; } BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << boost::format("launch system's default browser failed"); return false; } bool open_filament_group_wiki() { const wxString wiki_url = "https://e.bambulab.com/t?c=mOkvsXkJ9pldGYp9"; if (wxLaunchDefaultBrowser(wiki_url)) { BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format("Wiki is being displayed using the system's default browser."); return true; } BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << boost::format("launch system's default browser failed"); return false; } void FilamentGroupPopup::CreateBmps() { checked_bmp = create_scaled_bitmap("map_mode_on", nullptr, 16);; unchecked_bmp = create_scaled_bitmap("map_mode_off", nullptr, 16); disabled_bmp = create_scaled_bitmap("map_mode_disabled", nullptr, 16); checked_hover_bmp = create_scaled_bitmap("map_mode_on_hovered", nullptr, 16); unchecked_hover_bmp = create_scaled_bitmap("map_mode_off_hovered", nullptr, 16); } FilamentGroupPopup::FilamentGroupPopup(wxWindow *parent) : PopupWindow(parent, wxBORDER_NONE | wxPU_CONTAINS_CONTROLS) { const wxString AutoForFlushLabel = _L("Filament-Saving Mode"); const wxString AutoForMatchLabel = _L("Convenience Mode"); const wxString ManualLabel = _L("Custom Mode"); const wxString AutoForFlushDetail = _L("Generates filament grouping for the left and right nozzles based on the most filament-saving principles to minimize waste"); const wxString AutoForMatchDetail = _L("Generates filament grouping for the left and right nozzles based on the printer's actual filament status, reducing the need for manual filament adjustment"); const wxString ManualDetail = _L("Manually assign filament to the left or right nozzle"); const wxString AutoForFlushDesp = ""; //_L("(Post-slicing arrangement)"); const wxString ManualDesp = ""; const wxString AutoForMatchDesp = "";// _L("(Pre-slicing arrangement)"); wxBoxSizer *top_sizer = new wxBoxSizer(wxVERTICAL); const int horizontal_margin = FromDIP(16); const int vertical_margin = FromDIP(15); const int vertical_padding = FromDIP(12); const int ratio_spacing = FromDIP(4); SetBackgroundColour(BackGroundColor); radio_btns.resize(ButtonType::btCount); button_labels.resize(ButtonType::btCount); button_desps.resize(ButtonType::btCount); detail_infos.resize(ButtonType::btCount); //global_mode_tags.resize(ButtonType::btCount); std::vector btn_texts = {AutoForFlushLabel, AutoForMatchLabel, ManualLabel}; std::vector btn_desps = {AutoForFlushDesp, AutoForMatchDesp, ManualDesp}; std::vector mode_details = {AutoForFlushDetail, AutoForMatchDetail, ManualDetail}; top_sizer->AddSpacer(vertical_margin); CreateBmps(); for (size_t idx = 0; idx < ButtonType::btCount; ++idx) { wxBoxSizer *button_sizer = new wxBoxSizer(wxHORIZONTAL); radio_btns[idx] = new wxBitmapButton(this, wxID_ANY, unchecked_bmp, wxDefaultPosition, wxDefaultSize, wxNO_BORDER); radio_btns[idx]->SetBackgroundColour(BackGroundColor); button_labels[idx] = new Label(this, btn_texts[idx]); button_labels[idx]->SetBackgroundColour(BackGroundColor); button_labels[idx]->SetForegroundColour(LabelEnableColor); button_labels[idx]->SetFont(Label::Body_14); button_desps[idx] = new Label(this, btn_desps[idx]); button_desps[idx]->SetBackgroundColour(BackGroundColor); button_desps[idx]->SetForegroundColour(LabelEnableColor); button_desps[idx]->SetFont(Label::Body_14); #if 0 global_mode_tags[idx] = new wxBitmapButton(this, wxID_ANY, global_tag_bmp, wxDefaultPosition, wxDefaultSize, wxNO_BORDER); global_mode_tags[idx]->SetBackgroundColour(BackGroundColor); global_mode_tags[idx]->SetToolTip(_L("Global settings")); #endif button_sizer->Add(radio_btns[idx], 0, wxALIGN_CENTER); button_sizer->AddSpacer(ratio_spacing); button_sizer->Add(button_labels[idx], 0, wxALIGN_CENTER); button_sizer->Add(button_desps[idx], 0, wxALIGN_CENTER); //button_sizer->AddSpacer(ratio_spacing); //button_sizer->Add(global_mode_tags[idx], 0, wxALIGN_CENTER); wxBoxSizer *label_sizer = new wxBoxSizer(wxHORIZONTAL); detail_infos[idx] = new Label(this, mode_details[idx]); detail_infos[idx]->SetBackgroundColour(BackGroundColor); detail_infos[idx]->SetForegroundColour(GreyColor); detail_infos[idx]->SetFont(Label::Body_12); detail_infos[idx]->Wrap(FromDIP(320)); label_sizer->AddSpacer(radio_btns[idx]->GetRect().width + ratio_spacing); label_sizer->Add(detail_infos[idx], 1, wxEXPAND | wxALIGN_CENTER_VERTICAL); top_sizer->Add(button_sizer, 0, wxLEFT | wxRIGHT, horizontal_margin); top_sizer->Add(label_sizer, 0, wxLEFT | wxRIGHT, horizontal_margin); top_sizer->AddSpacer(vertical_padding); radio_btns[idx]->Bind(wxEVT_LEFT_DOWN, [this, idx](auto &) { OnRadioBtn(idx);}); radio_btns[idx]->Bind(wxEVT_ENTER_WINDOW, [this, idx](auto &) { UpdateButtonStatus(idx); }); radio_btns[idx]->Bind(wxEVT_LEAVE_WINDOW, [this](auto &) { UpdateButtonStatus(); }); button_labels[idx]->Bind(wxEVT_LEFT_DOWN, [this, idx](auto &) { OnRadioBtn(idx);}); button_labels[idx]->Bind(wxEVT_ENTER_WINDOW, [this, idx](auto &) { UpdateButtonStatus(idx); }); button_labels[idx]->Bind(wxEVT_LEAVE_WINDOW, [this](auto &) { UpdateButtonStatus(); }); } { wxBoxSizer *button_sizer = new wxBoxSizer(wxHORIZONTAL); auto* video_sizer = new wxBoxSizer(wxHORIZONTAL); video_link = new wxStaticText(this, wxID_ANY, _L("Video tutorial")); video_link->SetBackgroundColour(BackGroundColor); video_link->SetForegroundColour(GreenColor); video_link->SetFont(Label::Body_12.Underlined()); video_link->SetCursor(wxCursor(wxCURSOR_HAND)); video_link->Bind(wxEVT_LEFT_DOWN, [](wxMouseEvent&) { play_dual_extruder_slice_video(); wxGetApp().app_config->set("play_slicing_video", "false"); }); video_sizer->Add(video_link, 0, wxALIGN_CENTER | wxALL, FromDIP(3)); button_sizer->Add(video_sizer, 0, wxLEFT, horizontal_margin); button_sizer->AddStretchSpacer(); auto* wiki_sizer = new wxBoxSizer(wxHORIZONTAL); wiki_link = new wxStaticText(this, wxID_ANY, _L("Learn more")); wiki_link->SetBackgroundColour(BackGroundColor); wiki_link->SetForegroundColour(GreenColor); wiki_link->SetFont(Label::Body_12.Underlined()); wiki_link->SetCursor(wxCursor(wxCURSOR_HAND)); wiki_link->Bind(wxEVT_LEFT_DOWN, [](wxMouseEvent&) { open_filament_group_wiki(); }); wiki_sizer->Add(wiki_link, 0, wxALIGN_CENTER | wxALL, FromDIP(3)); button_sizer->Add(wiki_sizer, 0, wxLEFT, horizontal_margin); top_sizer->Add(button_sizer, 0, wxEXPAND | wxLEFT | wxRIGHT, horizontal_margin); } top_sizer->AddSpacer(vertical_margin); SetSizerAndFit(top_sizer); m_mode = get_prefered_map_mode(); m_timer = new wxTimer(this); GUI::wxGetApp().UpdateDarkUIWin(this); Bind(wxEVT_PAINT, &FilamentGroupPopup::OnPaint, this); Bind(wxEVT_TIMER, &FilamentGroupPopup::OnTimer, this); Bind(wxEVT_ENTER_WINDOW, &FilamentGroupPopup::OnEnterWindow, this); Bind(wxEVT_LEAVE_WINDOW, &FilamentGroupPopup::OnLeaveWindow, this); } void FilamentGroupPopup::DrawRoundedCorner(int radius) { #ifdef __WIN32__ HWND hwnd = GetHWND(); if (hwnd) { HRGN hrgn = CreateRoundRectRgn(0, 0, GetRect().GetWidth(), GetRect().GetHeight(), radius, radius); SetWindowRgn(hwnd, hrgn, FALSE); SetWindowLong(hwnd, GWL_EXSTYLE, GetWindowLong(hwnd, GWL_EXSTYLE) | WS_EX_LAYERED); SetLayeredWindowAttributes(hwnd, 0, 0, LWA_COLORKEY); } #endif } void FilamentGroupPopup::Init() { static bool is_dark_mode = wxGetApp().dark_mode(); if (is_dark_mode != wxGetApp().dark_mode()) { CreateBmps(); is_dark_mode = wxGetApp().dark_mode(); } const wxString AutoForMatchDesp = "";// _L("(Pre-slicing arrangement)"); const wxString MachineSyncTip = _L("(Sync with printer)"); if (m_connected) { button_labels[ButtonType::btForMatch]->SetForegroundColour(LabelEnableColor); button_desps[ButtonType::btForMatch]->SetForegroundColour(LabelEnableColor); detail_infos[ButtonType::btForMatch]->SetForegroundColour(GreyColor); radio_btns[ButtonType::btForMatch]->SetBitmap(unchecked_bmp); button_desps[ButtonType::btForMatch]->SetLabel(AutoForMatchDesp); } else { button_labels[ButtonType::btForMatch]->SetForegroundColour(LabelDisableColor); button_desps[ButtonType::btForMatch]->SetForegroundColour(LabelDisableColor); detail_infos[ButtonType::btForMatch]->SetForegroundColour(LabelDisableColor); radio_btns[ButtonType::btForMatch]->SetBitmap(disabled_bmp); button_desps[ButtonType::btForMatch]->SetLabel(MachineSyncTip); } m_mode = GetFilamentMapMode(); if (m_mode == fmmAutoForMatch && !m_connected) { SetFilamentMapMode(fmmAutoForFlush); m_mode = fmmAutoForFlush; } else if (m_slice_all) { // reset the filament map mode in slice all mode SetFilamentMapMode(m_mode); } UpdateButtonStatus(); GUI::wxGetApp().UpdateDarkUIWin(this); } void FilamentGroupPopup::tryPopup(Plater* plater,PartPlate* partplate,bool slice_all) { if (should_pop_up()) { bool connect_status = plater->get_machine_sync_status(); this->partplate_ref = partplate; this->plater_ref = plater; this->m_sync_plate = true; this->m_slice_all = slice_all; if (m_active) { if (m_connected != connect_status) { Init(); } m_connected = connect_status; ResetTimer(); } else { m_connected = connect_status; m_active = true; Init(); ResetTimer(); DrawRoundedCorner(16); PopupWindow::Popup(); } } } FilamentMapMode FilamentGroupPopup::GetFilamentMapMode() const { const auto& proj_config = wxGetApp().preset_bundle->project_config; if (m_sync_plate) return partplate_ref->get_real_filament_map_mode(proj_config); return plater_ref->get_global_filament_map_mode(); } void FilamentGroupPopup::SetFilamentMapMode(const FilamentMapMode mode) { if (m_sync_plate) { if (m_slice_all) { auto plate_list = plater_ref->get_partplate_list().get_plate_list(); for (int i = 0; i < plate_list.size(); ++i) { plate_list[i]->set_filament_map_mode(mode); } } else { partplate_ref->set_filament_map_mode(mode); } return; } plater_ref->set_global_filament_map_mode(mode); } void FilamentGroupPopup::tryClose() { StartTimer(); } void FilamentGroupPopup::OnPaint(wxPaintEvent&) { DrawRoundedCorner(16); } void FilamentGroupPopup::StartTimer() { m_timer->StartOnce(300); } void FilamentGroupPopup::ResetTimer() { if (m_timer->IsRunning()) { m_timer->Stop(); } } void FilamentGroupPopup::OnRadioBtn(int idx) { if (mode_list.at(idx) == FilamentMapMode::fmmAutoForMatch && !m_connected) return; if (m_mode != mode_list.at(idx)) { m_mode = mode_list.at(idx); SetFilamentMapMode(m_mode); plater_ref->update(); UpdateButtonStatus(m_mode); } } void FilamentGroupPopup::OnTimer(wxTimerEvent &event) { Dismiss(); } void FilamentGroupPopup::Dismiss() { m_active = false; PopupWindow::Dismiss(); m_timer->Stop(); } void FilamentGroupPopup::OnLeaveWindow(wxMouseEvent &) { wxPoint pos = this->ScreenToClient(wxGetMousePosition()); if (this->GetClientRect().Contains(pos)) return; StartTimer(); } void FilamentGroupPopup::OnEnterWindow(wxMouseEvent &) { ResetTimer(); } void FilamentGroupPopup::UpdateButtonStatus(int hover_idx) { for (int i = 0; i < ButtonType::btCount; ++i) { #if 0 // do not display global mode tag if (mode_list.at(i) == global_mode) global_mode_tags[i]->Show(); else global_mode_tags[i]->Hide(); #endif if (ButtonType::btForMatch == i && !m_connected) { button_labels[i]->SetFont(Label::Body_14); continue; } // process checked and unchecked status if (mode_list.at(i) == m_mode) { if (i == hover_idx) radio_btns[i]->SetBitmap(checked_hover_bmp); else radio_btns[i]->SetBitmap(checked_bmp); button_labels[i]->SetFont(Label::Head_14); } else { if (i == hover_idx) radio_btns[i]->SetBitmap(unchecked_hover_bmp); else radio_btns[i]->SetBitmap(unchecked_bmp); button_labels[i]->SetFont(Label::Body_14); } } Layout(); Fit(); } }} // namespace Slic3r::GUI