#include "SelectMachine.hpp" #include "I18N.hpp" #include "libslic3r/Utils.hpp" #include "libslic3r/Thread.hpp" #include "libslic3r/MultiNozzleUtils.hpp" #include "libslic3r/FilamentMixer.hpp" #include "FilamentBitmapUtils.hpp" #include "GUI.hpp" #include "GUI_App.hpp" #include "GUI_Preview.hpp" #include "MainFrame.hpp" #include "format.hpp" #include "Widgets/ProgressDialog.hpp" #include "Widgets/RoundedRectangle.hpp" #include "Widgets/StaticBox.hpp" #include "Widgets/CheckBox.hpp" #include "Widgets/RadioBox.hpp" #include "Widgets/Label.hpp" #include "BackgroundSlicingProcess.hpp" #include "ConnectPrinter.hpp" #include "slic3r/Utils/BBLUtil.hpp" #include "DeviceCore/DevConfig.h" #include "DeviceCore/DevPrintOptions.h" #include "DeviceCore/DevMappingNozzle.h" #include "DeviceCore/DevNozzleSystem.h" #include "DeviceCore/DevExtensionTool.h" #include "DeviceCore/DevExtruderSystem.h" #include "DeviceCore/DevConfigUtil.h" #include "DeviceCore/DevFilaBlackList.h" #include "DeviceCore/DevFilaSystem.h" #include "DeviceCore/DevFilaSwitch.h" #include "DeviceCore/DevInfo.h" #include "DeviceCore/DevStorage.h" #include "DeviceCore/DevUpgrade.h" #include "DeviceCore/DevManager.h" #include "DeviceCore/DevMapping.h" #include "DeviceCore/DevUtilBackend.h" #include "../Utils/BBLUtil.hpp" #include #include #include #include #include #include #include #include "Plater.hpp" #include "Notebook.hpp" #include "BitmapCache.hpp" #include "BindDialog.hpp" // definitions #define S_RACK_NOZZLE_OFFSET_CALI_WARNING _L(\ "Turning off this option will affect print quality if:\n" \ "1. Uncalibrated hotends is used\n" \ "2. The current nozzle, heatbed, or chamber temperature differs from the calibration conditions\n" \ "3. The toolhead hotend was manually removed or installed") #define S_RACK_FLOW_DYNAMICS_CALI_WARNING _L("After modifying this option, the system will reassign the nozzles based on the calibrated dynamic flow calibration coefficient.") #define S_RACK_NOZZLE_NOT_ENOUGH _L("There are not enough available hotends currently.") #define S_RACK_NOZZLE_NO_MATCHED _L("No hotends available.") #define S_RACK_NOZZLE_SUGEEST_CALI _L("Please complete the hotend rack setup and try again.") #define S_RACK_NOZZLE_SUGEEST_REFRESH _L("Please refresh the nozzle information and try again.") #define S_RACK_NOZZLE_SUGEEST_RESLICE _L("Please re-slice.") #define S_RACK_NOZZLE_SUGEEST_RESLICE_FILA _L("Please re-slice to avoid filament waste.") namespace Slic3r { namespace GUI { wxDEFINE_EVENT(EVT_SWITCH_PRINT_OPTION, wxCommandEvent); wxDEFINE_EVENT(EVT_UPDATE_USER_MACHINE_LIST, wxCommandEvent); wxDEFINE_EVENT(EVT_PRINT_JOB_CANCEL, wxCommandEvent); wxDEFINE_EVENT(EVT_CLEAR_IPADDRESS, wxCommandEvent); #define INITIAL_NUMBER_OF_MACHINES 0 #define MACHINE_LIST_REFRESH_INTERVAL 2000 #define WRAP_GAP FromDIP(2) static wxString task_canceled_text = _L("Task canceled"); static int s_nozzle_mapping_last_request_time = 0; std::string get_nozzle_volume_type_cloud_string(NozzleVolumeType nozzle_volume_type) { if (nozzle_volume_type == NozzleVolumeType::nvtStandard) { return "standard_flow"; } else if (nozzle_volume_type == NozzleVolumeType::nvtHighFlow) { return "high_flow"; } else if (nozzle_volume_type == NozzleVolumeType::nvtTPUHighFlow) { return "tpu_high_flow"; } else if (nozzle_volume_type == NozzleVolumeType::nvtE3DHighFlow) { return "e3d_high_flow"; } else if(nozzle_volume_type == NozzleVolumeType::nvtHybrid) { // to be supported return "hybrid_flow"; } else { assert(false); return ""; } } // Map device AMS types to AMS timing-vector indices. // 0 = reserved external-spool slot (use values[0], typically 0s) // >0 = AmsTimeType enum value // -1 = unsupported for AMS timing; resolve_ams_load_unload falls back to legacy times static int to_ams_time_index(DevAmsType type) { switch (type) { case DevAmsType::EXT_SPOOL: return 0; case DevAmsType::AMS: return static_cast(AmsTimeType::Ams); case DevAmsType::AMS_LITE: case DevAmsType::AMS_LITE_MIXED: return static_cast(AmsTimeType::AmsLite); case DevAmsType::N3F: case DevAmsType::N3S: return static_cast(AmsTimeType::N3SF); default: return -1; } } static std::vector build_actual_ams_type_per_filament(const std::vector& mapping, MachineObject* obj) { int filament_count = 0; for (const auto& f : mapping) { if (f.id + 1 > filament_count) filament_count = f.id + 1; } std::vector types(static_cast(filament_count), -1); if (!obj) return types; for (const auto& f : mapping) { if (f.id < 0 || f.id >= filament_count) { continue; } if (f.ams_id.empty() || f.tray_id < 0) { types[f.id] = -1; continue; } if (DevAms* ams = obj->GetFilaSystem()->GetAmsById(f.ams_id)) { types[f.id] = to_ams_time_index(ams->GetAmsType()); } else if (devPrinterUtil::IsVirtualSlot(f.ams_id)) { types[f.id] = to_ams_time_index(DevAmsType::EXT_SPOOL); } else { types[f.id] = -1; } } return types; } std::vector SelectMachineDialog::MACHINE_BED_TYPE_STRING; std::vector SelectMachineDialog::MachineBedTypeString; void SelectMachineDialog::init_machine_bed_types() { if (MACHINE_BED_TYPE_STRING.size() == 0) { MACHINE_BED_TYPE_STRING = {//_L("Auto"), _L("Bambu Cool Plate"), _L("Bamabu Engineering Plate"), _L("Bamabu Smooth PEI Plate") + "/" + _L("High temperature Plate"), _L("Bamabu Textured PEI Plate"), _L("Bambu Cool Plate SuperTack") }; MachineBedTypeString = {//"auto", "pc", "pe", "pei", "pte", "suprtack"}; } } SelectMachineDialog::SelectMachineDialog(Plater *plater) : DPIDialog(static_cast(wxGetApp().mainframe), wxID_ANY, _L("Send print job"), wxDefaultPosition, wxDefaultSize, wxCAPTION | wxCLOSE_BOX) , m_plater(plater), m_export_3mf_cancel(false) , m_mapping_popup(AmsMapingPopup(this)) , m_mapping_tip_popup(AmsMapingTipPopup(this)) , m_mapping_tutorial_popup(AmsTutorialPopup(this)) { m_mapping_popup.EnableExtMappingFilaTypeCheck(false); init_machine_bed_types(); #ifdef __WINDOWS__ SetDoubleBuffered(true); #endif //__WINDOWS__ if (wxGetApp().app_config->get("language") == "zh_CN") { ops_auto.push_back(POItem{"auto", _L("Auto")}); ops_auto.push_back(POItem{"on", _L("On")}); ops_auto.push_back(POItem{"off", _L("Off")}); ops_no_auto.push_back(POItem{"on", _L("On")}); ops_no_auto.push_back(POItem{"off", _L("Off")}); } else { ops_auto.push_back(POItem{"auto", "Auto"}); ops_auto.push_back(POItem{"on", "On"}); ops_auto.push_back(POItem{"off", "Off"}); ops_no_auto.push_back(POItem{"on", "On"}); ops_no_auto.push_back(POItem{"off", "Off"}); } // bind Bind(wxEVT_CLOSE_WINDOW, &SelectMachineDialog::on_cancel, this); for (int i = 0; i < BED_TYPE_COUNT; i++) { m_bedtype_list.push_back(MACHINE_BED_TYPE_STRING[i]); } // font SetFont(wxGetApp().normal_font()); // icon std::string icon_path = (boost::format("%1%/images/BambuStudioTitle.ico") % resources_dir()).str(); SetIcon(wxIcon(encode_path(icon_path.c_str()), wxBITMAP_TYPE_ICO)); Freeze(); SetBackgroundColour(m_colour_def_color); wxBoxSizer* m_scroll_sizer = new wxBoxSizer(wxVERTICAL); m_scroll_area = new wxScrolledWindow(this); m_scroll_area->SetScrollRate(0, 20); m_scroll_area->SetBackgroundColour(m_colour_def_color); m_scroll_area->SetMinSize(wxSize(FromDIP(700), FromDIP(600))); m_scroll_area->SetMaxSize(wxSize(FromDIP(700), FromDIP(600))); m_line_top = new wxPanel(m_scroll_area, wxID_ANY, wxDefaultPosition, wxSize(-1, 1), wxTAB_TRAVERSAL); m_line_top->SetBackgroundColour(wxColour(166, 169, 170)); /*mode switch*/ /*auto m_sizer_mode_switch = new wxBoxSizer(wxHORIZONTAL); m_mode_print = new SendModeSwitchButton(scroll_area, _L("Print"), true); m_mode_send = new SendModeSwitchButton(scroll_area,_L("Save to printer"), false); m_sizer_mode_switch->Add(m_mode_print, 0, wxALIGN_CENTER, 0); m_sizer_mode_switch->Add(0, 0, 0, wxLEFT, FromDIP(8)); m_sizer_mode_switch->Add(m_mode_send,0, wxALIGN_CENTER, 0); m_mode_print->Bind(wxEVT_LEFT_DOWN, [this](auto& e) { m_mode_print->setSelected(true); m_mode_send->setSelected(false); e.Skip(); }); m_mode_send->Bind(wxEVT_LEFT_DOWN, [this](auto& e) { m_mode_print->setSelected(false); m_mode_send->setSelected(true); e.Skip(); });*/ m_basic_panel = new wxPanel(m_scroll_area, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL); m_basic_panel->SetBackgroundColour(*wxWHITE); m_basicl_sizer = new wxBoxSizer(wxHORIZONTAL); /*basic info*/ /*thumbnail*/ auto m_sizer_thumbnail_area = new wxBoxSizer(wxHORIZONTAL); auto m_panel_image = new wxPanel(m_basic_panel, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL); m_panel_image->SetBackgroundColour(m_colour_def_color); m_sizer_thumbnail = new wxBoxSizer(wxHORIZONTAL); m_thumbnailPanel = new ThumbnailPanel(m_panel_image); m_thumbnailPanel->SetSize(wxSize(FromDIP(198), FromDIP(198))); m_thumbnailPanel->SetMinSize(wxSize(FromDIP(198), FromDIP(198))); m_thumbnailPanel->SetMaxSize(wxSize(FromDIP(198), FromDIP(198))); m_thumbnailPanel->SetBackgroundColour(*wxWHITE); m_sizer_thumbnail->Add(m_thumbnailPanel, 0, wxALIGN_CENTER, 0); m_panel_image->SetSizer(m_sizer_thumbnail); m_panel_image->Layout(); m_sizer_thumbnail_area->Add(m_panel_image, 0, wxALIGN_CENTER, 0); m_sizer_thumbnail_area->Layout(); /*basic info right*/ auto sizer_basic_right_info = new wxBoxSizer(wxVERTICAL); /*rename*/ auto sizer_rename = new wxBoxSizer(wxHORIZONTAL); m_rename_switch_panel = new wxSimplebook(m_basic_panel); m_rename_switch_panel->SetBackgroundColour(*wxWHITE); m_rename_switch_panel->SetSize(wxSize(FromDIP(360), FromDIP(25))); m_rename_switch_panel->SetMinSize(wxSize(FromDIP(360), FromDIP(25))); m_rename_switch_panel->SetMaxSize(wxSize(FromDIP(360), FromDIP(25))); m_rename_normal_panel = new wxPanel(m_rename_switch_panel, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL); m_rename_normal_panel->SetBackgroundColour(*wxWHITE); rename_sizer_v = new wxBoxSizer(wxVERTICAL); rename_sizer_h = new wxBoxSizer(wxHORIZONTAL); m_rename_text = new wxStaticText(m_rename_normal_panel, wxID_ANY, wxEmptyString, wxDefaultPosition, wxDefaultSize, wxST_ELLIPSIZE_END); m_rename_text->SetFont(::Label::Head_14); m_rename_text->SetBackgroundColour(*wxWHITE); m_rename_text->SetMaxSize(wxSize(FromDIP(340), -1)); rename_editable = new ScalableBitmap(m_scroll_area, "rename_edit", 20); rename_editable_light = new ScalableBitmap(m_scroll_area, "rename_edit", 20); m_rename_button = new wxStaticBitmap(m_rename_normal_panel, wxID_ANY, rename_editable->bmp(), wxDefaultPosition, wxSize(FromDIP(20), FromDIP(20)), 0); m_rename_button->Bind(wxEVT_ENTER_WINDOW, [this](auto& e) {SetCursor(wxCURSOR_HAND); }); m_rename_button->Bind(wxEVT_LEAVE_WINDOW, [this](auto& e) {SetCursor(wxCURSOR_ARROW); }); rename_sizer_h->Add(m_rename_text, 0, wxALIGN_CENTER|wxTOP, FromDIP(2)); rename_sizer_h->Add(0,0,0,wxLEFT,FromDIP(3)); rename_sizer_h->Add(m_rename_button, 0, wxALIGN_CENTER, 0); rename_sizer_v->Add(rename_sizer_h, 1, wxTOP, 0); m_rename_normal_panel->SetSizer(rename_sizer_v); m_rename_normal_panel->Layout(); rename_sizer_v->Fit(m_rename_normal_panel); auto m_rename_edit_panel = new wxPanel(m_rename_switch_panel, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL); m_rename_edit_panel->SetBackgroundColour(*wxWHITE); auto rename_edit_sizer_v = new wxBoxSizer(wxVERTICAL); m_rename_input = new ::TextInput(m_rename_edit_panel, wxEmptyString, wxEmptyString, wxEmptyString, wxDefaultPosition, wxDefaultSize, wxTE_PROCESS_ENTER); m_rename_input->GetTextCtrl()->SetFont(::Label::Body_13); m_rename_input->SetSize(wxSize(FromDIP(360), FromDIP(24))); m_rename_input->SetMinSize(wxSize(FromDIP(360), FromDIP(24))); m_rename_input->SetMaxSize(wxSize(FromDIP(360), FromDIP(24))); m_rename_input->Bind(wxEVT_TEXT_ENTER, [this](auto& e) {on_rename_enter();}); m_rename_input->Bind(wxEVT_KILL_FOCUS, [this](auto& e) { if (!m_rename_input->HasFocus() && !m_rename_text->HasFocus()) on_rename_enter(); else e.Skip(); }); rename_edit_sizer_v->Add(m_rename_input, 1, wxALIGN_CENTER, 0); m_rename_edit_panel->SetSizer(rename_edit_sizer_v); m_rename_edit_panel->Layout(); rename_edit_sizer_v->Fit(m_rename_edit_panel); m_rename_button->Bind(wxEVT_LEFT_DOWN, &SelectMachineDialog::on_rename_click, this); m_rename_switch_panel->AddPage(m_rename_normal_panel, wxEmptyString, true); m_rename_switch_panel->AddPage(m_rename_edit_panel, wxEmptyString, false); Bind(wxEVT_CHAR_HOOK, [this](wxKeyEvent& e) { if (e.GetKeyCode() == WXK_ESCAPE) { if (m_rename_switch_panel->GetSelection() == 0) { e.Skip(); } else { m_rename_switch_panel->SetSelection(0); m_rename_text->SetLabel(m_current_project_name); m_rename_normal_panel->Layout(); } } else { e.Skip(); } }); /*weight & time*/ wxBoxSizer *m_sizer_basic_weight_time = new wxBoxSizer(wxHORIZONTAL); print_time = new ScalableBitmap(m_scroll_area, "print-time", 18); timeimg = new wxStaticBitmap(m_basic_panel, wxID_ANY, print_time->bmp(), wxDefaultPosition, wxSize(FromDIP(18), FromDIP(18)), 0); m_stext_time = new Label(m_basic_panel, wxEmptyString); m_stext_time->SetFont(Label::Body_13); m_time_estimate_tip = new Label(m_basic_panel, wxString::FromUTF8("●")); m_time_estimate_tip->SetFont(Label::Body_8); m_time_estimate_tip->SetForegroundColour(wxColour("#FF6F00")); m_time_estimate_tip->SetToolTip(_L("The current estimated time has been recalculated based on the actual filament mapping.")); m_time_estimate_tip->Hide(); print_weight = new ScalableBitmap(m_scroll_area, "print-weight", 18); weightimg = new wxStaticBitmap(m_basic_panel, wxID_ANY, print_weight->bmp(), wxDefaultPosition, wxSize(FromDIP(18), FromDIP(18)), 0); m_stext_weight = new Label(m_basic_panel, wxEmptyString); m_stext_weight->SetFont(Label::Body_13); /* save time info */ m_saveTimeText = new Label(m_basic_panel, wxEmptyString); m_saveTimeText->SetForegroundColour(wxColour("#FF6F00")); m_saveTimeText->SetFont(Label::Body_13); m_saveTimeText->Hide(); m_saveTimeText->Bind(wxEVT_LEFT_UP, &SelectMachineDialog::on_reselect_dialog_btn_clicked, this); m_saveTimeText->Bind(wxEVT_ENTER_WINDOW, [this](wxMouseEvent&) { m_saveTimeText->SetCursor(wxCURSOR_HAND); }); m_saveTimeText->Bind(wxEVT_LEAVE_WINDOW, [this](wxMouseEvent&) { m_saveTimeText->SetCursor(wxCURSOR_DEFAULT); }); m_sizer_basic_weight_time->Add(timeimg, 0, wxALIGN_CENTER, 0); m_sizer_basic_weight_time->Add(m_stext_time, 0, wxALIGN_CENTER|wxLEFT, FromDIP(6)); m_sizer_basic_weight_time->Add(m_time_estimate_tip, 0, wxALIGN_TOP | wxLEFT, FromDIP(2)); m_sizer_basic_weight_time->AddSpacer(FromDIP(30)); m_sizer_basic_weight_time->Add(weightimg, 0, wxALIGN_CENTER, 0); m_sizer_basic_weight_time->Add(m_stext_weight, 0, wxALIGN_CENTER|wxLEFT, FromDIP(6)); /*last & next page*/ auto last_plate_sizer = new wxBoxSizer(wxVERTICAL); m_bitmap_last_plate = new wxStaticBitmap(m_basic_panel, wxID_ANY, create_scaled_bitmap("go_last_plate", m_scroll_area, 25), wxDefaultPosition, wxSize(FromDIP(25), FromDIP(25)), 0); m_bitmap_last_plate->Hide(); last_plate_sizer->Add(m_bitmap_last_plate, 0, wxALIGN_CENTER, 0); auto next_plate_sizer = new wxBoxSizer(wxVERTICAL); m_bitmap_next_plate = new wxStaticBitmap(m_basic_panel, wxID_ANY, create_scaled_bitmap("go_next_plate", m_scroll_area, 25), wxDefaultPosition, wxSize(FromDIP(25), FromDIP(25)), 0); m_bitmap_next_plate->Hide(); next_plate_sizer->Add(m_bitmap_next_plate, 0, wxALIGN_CENTER, 0); sizer_rename->Add(m_rename_switch_panel, 0, wxALIGN_CENTER, 0); sizer_rename->Add(0, 0, 0, wxEXPAND, 0); sizer_rename->Add(m_bitmap_last_plate, 0, wxALIGN_CENTER, 0); sizer_rename->Add(m_bitmap_next_plate, 0, wxALIGN_CENTER, 0); m_printer_box = new PrinterInfoBox(m_basic_panel, this); m_text_printer_msg = new PrinterMsgPanel(m_basic_panel, this); m_text_printer_msg->SetMinSize(wxSize(FromDIP(420), -1)); m_text_printer_msg->SetMaxSize(wxSize(FromDIP(420), -1)); m_text_printer_msg->SetBackgroundColour(m_colour_def_color); m_text_printer_msg->SetFont(::Label::Body_13); m_text_printer_msg->Hide(); m_text_printer_msg_tips = new Label(m_basic_panel); m_text_printer_msg_tips->SetMinSize(wxSize(FromDIP(420), FromDIP(24))); m_text_printer_msg_tips->SetMaxSize(wxSize(FromDIP(420), FromDIP(24))); m_text_printer_msg_tips->SetFont(::Label::Body_13); m_text_printer_msg_tips->SetForegroundColour(wxColour(0x6B, 0x6B, 0x6B)); m_text_printer_msg_tips->Hide(); m_text_printer_msg_tips->GetAlignment(); sizer_basic_right_info->Add(sizer_rename, 0, wxTOP, 0); sizer_basic_right_info->Add(0, 0, 0, wxTOP, FromDIP(5)); sizer_basic_right_info->Add(m_sizer_basic_weight_time, 0, wxTOP, 0); sizer_basic_right_info->Add(0, 0, 0, wxTOP, FromDIP(4)); sizer_basic_right_info->Add(m_saveTimeText, 0, wxTOP, 0); sizer_basic_right_info->Add(0, 0, 0, wxTOP, FromDIP(10)); sizer_basic_right_info->Add(m_printer_box, 0, wxTOP, 0); sizer_basic_right_info->Add(0, 0, 0, wxTOP, FromDIP(4)); sizer_basic_right_info->Add(m_text_printer_msg, 0, wxLEFT, 0); sizer_basic_right_info->AddSpacer(FromDIP(10)); sizer_basic_right_info->Add(m_text_printer_msg_tips, 0, wxLEFT, 0); m_basicl_sizer->Add(m_sizer_thumbnail_area, 0, wxLEFT, 0); m_basicl_sizer->Add(0, 0, 0, wxLEFT, FromDIP(8)); m_basicl_sizer->Add(sizer_basic_right_info, 0, wxLEFT, 0); m_basic_panel->SetSizer(m_basicl_sizer); m_basic_panel->Layout(); /*filaments info*/ wxBoxSizer* sizer_split_filament = new wxBoxSizer(wxHORIZONTAL); auto m_stext_filament_title = new Label(m_scroll_area, _L("Filament")); m_stext_filament_title->SetFont(::Label::Head_13); m_stext_filament_title->SetForegroundColour(0x909090); auto m_split_line_filament = new wxPanel(m_scroll_area, wxID_ANY, wxDefaultPosition, wxSize(-1, 1), wxTAB_TRAVERSAL); m_split_line_filament->SetBackgroundColour(0xeeeeee); m_split_line_filament->SetMinSize(wxSize(-1, 1)); m_split_line_filament->SetMaxSize(wxSize(-1, 1)); m_sizer_autorefill = new wxBoxSizer(wxHORIZONTAL); m_ams_backup_tip = new Label(m_scroll_area, _L("Auto Refill")); m_ams_backup_tip->SetFont(::Label::Head_13); m_ams_backup_tip->SetForegroundColour(wxColour("#00AE42")); m_ams_backup_tip->SetBackgroundColour(*wxWHITE); img_ams_backup = new wxStaticBitmap(m_scroll_area, wxID_ANY, create_scaled_bitmap("automatic_material_renewal", this, 16), wxDefaultPosition, wxSize(FromDIP(16), FromDIP(16)), 0); img_ams_backup->SetBackgroundColour(*wxWHITE); m_sizer_autorefill->Add(0, 0, 1, wxEXPAND, 0); m_sizer_autorefill->Add(img_ams_backup, 0, wxALL, FromDIP(3)); m_sizer_autorefill->Add(m_ams_backup_tip, 0, wxTOP, FromDIP(5)); m_ams_backup_tip->Hide(); img_ams_backup->Hide(); m_ams_backup_tip->Bind(wxEVT_ENTER_WINDOW, [this](auto& e) {SetCursor(wxCURSOR_HAND); }); img_ams_backup->Bind(wxEVT_ENTER_WINDOW, [this](auto& e) {SetCursor(wxCURSOR_HAND); }); m_ams_backup_tip->Bind(wxEVT_LEAVE_WINDOW, [this](auto& e) {SetCursor(wxCURSOR_ARROW); }); img_ams_backup->Bind(wxEVT_LEAVE_WINDOW, [this](auto& e) {SetCursor(wxCURSOR_ARROW); }); m_ams_backup_tip->Bind(wxEVT_LEFT_DOWN, [this](auto& e) { if (!m_is_in_sending_mode) { popup_filament_backup(); on_rename_enter(); } }); img_ams_backup->Bind(wxEVT_LEFT_DOWN, [this](auto& e) {if (!m_is_in_sending_mode) popup_filament_backup(); on_rename_enter(); }); sizer_split_filament->Add(0, 0, 0, wxEXPAND, 0); sizer_split_filament->Add(m_stext_filament_title, 0, wxALIGN_CENTER, 0); sizer_split_filament->Add(m_split_line_filament, 1, wxALIGN_CENTER_VERTICAL, 0); sizer_split_filament->Add(m_sizer_autorefill, 0, wxALIGN_CENTER, 0); /*filament area*/ /*1 extruder*/ m_filament_panel = new StaticBox(m_scroll_area); m_filament_panel->SetBackgroundColour(wxColour("#F8F8F8")); m_filament_panel->SetBorderWidth(0); m_filament_panel->SetMinSize(wxSize(FromDIP(637), -1)); m_filament_panel->SetMaxSize(wxSize(FromDIP(637), -1)); m_filament_panel_sizer = new wxBoxSizer(wxVERTICAL); m_sizer_ams_mapping = new wxGridSizer(0, 10, FromDIP(7), FromDIP(7)); m_filament_panel_sizer->Add(m_sizer_ams_mapping, 0, wxEXPAND|wxALL, FromDIP(10)); m_filament_panel->SetSizer(m_filament_panel_sizer); m_filament_panel->Layout(); m_filament_panel->Fit(); /*left & right extruder*/ m_sizer_filament_2extruder = new wxBoxSizer(wxHORIZONTAL); m_filament_left_panel = new StaticBox(m_scroll_area); m_filament_left_panel->SetBackgroundColour(wxColour("#F8F8F8")); m_filament_left_panel->SetBorderWidth(0); m_filament_left_panel->SetMinSize(wxSize(FromDIP(315), -1)); m_filament_left_panel->SetMaxSize(wxSize(FromDIP(315), -1)); m_filament_panel_left_sizer = new wxBoxSizer(wxVERTICAL); auto left_recommend_title_sizer = new wxBoxSizer(wxHORIZONTAL); std::string sm_pt = wxGetApp().preset_bundle->printers.get_edited_preset().get_printer_type(wxGetApp().preset_bundle); m_filament_left_title = new Label(m_filament_left_panel, _L(DevPrinterConfigUtil::get_toolhead_display_name(sm_pt, DEPUTY_EXTRUDER_ID, ToolHeadComponent::Nozzle, ToolHeadNameCase::TitleCase))); m_filament_left_title->SetFont(::Label::Head_13); m_filament_left_title->SetBackgroundColour(wxColour("#F8F8F8")); left_recommend_title_sizer->Add(m_filament_left_title, 0, wxALIGN_CENTER, 0); m_sizer_ams_mapping_left = new wxGridSizer(0, 5, FromDIP(7), FromDIP(7)); m_filament_panel_left_sizer->Add(left_recommend_title_sizer, 0, wxLEFT|wxRIGHT|wxTOP, FromDIP(10)); m_filament_panel_left_sizer->Add(m_sizer_ams_mapping_left, 0, wxEXPAND|wxALL, FromDIP(10)); m_filament_left_panel->SetSizer(m_filament_panel_left_sizer); m_filament_left_panel->Layout(); m_filament_right_panel = new StaticBox(m_scroll_area); m_filament_right_panel->SetBorderWidth(0); m_filament_right_panel->SetBackgroundColour(wxColour("#F8F8F8")); m_filament_right_panel->SetMinSize(wxSize(FromDIP(315), -1)); m_filament_right_panel->SetMaxSize(wxSize(FromDIP(315), -1)); m_filament_panel_right_sizer = new wxBoxSizer(wxVERTICAL); auto right_recommend_title_sizer = new wxBoxSizer(wxHORIZONTAL); m_filament_right_title = new Label(m_filament_right_panel, _L(DevPrinterConfigUtil::get_toolhead_display_name(sm_pt, MAIN_EXTRUDER_ID, ToolHeadComponent::Nozzle, ToolHeadNameCase::TitleCase))); m_filament_right_title->SetFont(::Label::Head_13); m_filament_right_title->SetBackgroundColour(wxColour("#F8F8F8")); right_recommend_title_sizer->Add(m_filament_right_title, 0, wxALIGN_CENTER, 0); m_sizer_ams_mapping_right = new wxGridSizer(0, 5, FromDIP(7), FromDIP(7)); m_filament_panel_right_sizer->Add(right_recommend_title_sizer, 0, wxLEFT|wxRIGHT|wxTOP, FromDIP(10)); m_filament_panel_right_sizer->Add(m_sizer_ams_mapping_right, 0, wxEXPAND|wxALL, FromDIP(10)); m_filament_right_panel->SetSizer(m_filament_panel_right_sizer); m_filament_right_panel->Layout(); m_sizer_filament_2extruder->Add(m_filament_left_panel, 0, wxEXPAND, 0); m_sizer_filament_2extruder->Add( 0, 0, 1, wxEXPAND, 0); m_sizer_filament_2extruder->Add(m_filament_right_panel, 0, wxEXPAND, 0); m_sizer_filament_2extruder->Layout(); m_filament_panel->Hide(); m_statictext_ams_msg = new PrinterMsgPanel(m_scroll_area, this); m_statictext_ams_msg->SetMinSize(wxSize(FromDIP(655), -1)); m_statictext_ams_msg->SetMaxSize(wxSize(FromDIP(655), -1)); m_statictext_ams_msg->SetBackgroundColour(m_colour_def_color); m_statictext_ams_msg->SetFont(::Label::Body_13); m_statictext_ams_msg->Hide(); /*ams mapping suggestions*/ m_link_edit_nozzle = new Label(m_scroll_area, wxEmptyString); m_link_edit_nozzle->SetFont(::Label::Body_13); m_link_edit_nozzle->SetForegroundColour(0x00ae42); m_link_edit_nozzle->SetBackgroundColour(*wxWHITE); m_link_edit_nozzle->Bind(wxEVT_ENTER_WINDOW, [this](auto &e) { SetCursor(wxCURSOR_HAND); }); m_link_edit_nozzle->Bind(wxEVT_LEAVE_WINDOW, [this](auto &e) { SetCursor(wxCURSOR_ARROW); }); m_link_edit_nozzle->SetLabel(_L("Not satisfied with the grouping of filaments? Regroup and slice ->")); m_link_edit_nozzle->Bind(wxEVT_LEFT_DOWN, [this](auto &e) { if (this && this->m_is_in_sending_mode) { return; } EndModal(wxID_CLOSE); Plater * plater = wxGetApp().plater(); wxCommandEvent evt(EVT_OPEN_FILAMENT_MAP_SETTINGS_DIALOG); evt.SetEventObject(plater); evt.SetInt(1); // 1 means from gcode viewer wxPostEvent(plater, evt); }); m_check_ext_change_assist = new CheckBox(m_scroll_area, wxID_ANY); m_check_ext_change_assist->SetValue(false); m_check_ext_change_assist->SetBackgroundColour(*wxWHITE); m_check_ext_change_assist->SetToolTip(_L("Manually change external spool during printing for multi-color printing")); m_check_ext_change_assist->Hide(); m_label_ext_change_assist = new Label(m_scroll_area, _L("Multi-color with external")); m_label_ext_change_assist->Wrap(-1); m_label_ext_change_assist->Hide(); m_label_ext_change_assist->SetMaxSize(wxSize(FromDIP(200), -1)); m_label_ext_change_assist->SetFont(::Label::Body_13); m_label_ext_change_assist->SetBackgroundColour(*wxWHITE); m_label_ext_change_assist->SetToolTip(_L("Manually change external spool during printing for multi-color printing")); wxSizer* suggestion_sizer = new wxBoxSizer(wxHORIZONTAL); suggestion_sizer->Add(m_link_edit_nozzle, 0, wxLEFT, 0); suggestion_sizer->Add(0, 0, 1, wxEXPAND, 0); suggestion_sizer->Add(m_check_ext_change_assist, 0, wxRIGHT, FromDIP(10)); suggestion_sizer->Add(m_label_ext_change_assist, 0, wxRIGHT, 0); m_mapping_sugs_sizer = new wxBoxSizer(wxHORIZONTAL); //auto m_img_mapping_sugs = new wxStaticBitmap(this, wxID_ANY, create_scaled_bitmap("warning", this, 16), wxDefaultPosition, wxSize(FromDIP(16), FromDIP(16))); auto m_txt_mapping_sugs = new Label(m_scroll_area, wxEmptyString); m_txt_mapping_sugs->SetFont(::Label::Body_13); m_txt_mapping_sugs->SetForegroundColour(wxColour(0xFF, 0x6F, 0x00)); m_txt_mapping_sugs->SetMinSize(wxSize(FromDIP(580), -1)); m_txt_mapping_sugs->SetMaxSize(wxSize(FromDIP(580), -1)); m_txt_mapping_sugs->SetBackgroundColour(*wxWHITE); m_txt_mapping_sugs->SetLabel(_L("Your filament grouping method in the sliced file is not optimal.")); //m_mapping_sugs_sizer->Add(m_img_mapping_sugs, 0, wxALIGN_CENTER, 0); m_mapping_sugs_sizer->Add(m_txt_mapping_sugs, 0, wxALIGN_CENTER, 0); m_change_filament_times_sizer = new wxBoxSizer(wxHORIZONTAL); //auto m_img_change_filament_times = new wxStaticBitmap(this, wxID_ANY, create_scaled_bitmap("warning", this, 16), wxDefaultPosition, wxSize(FromDIP(16), FromDIP(16))); m_txt_change_filament_times = new Label(m_scroll_area, wxEmptyString); m_txt_change_filament_times->SetFont(::Label::Body_13); m_txt_change_filament_times->SetMinSize(wxSize(FromDIP(580), -1)); m_txt_change_filament_times->SetMaxSize(wxSize(FromDIP(580), -1)); m_txt_change_filament_times->SetForegroundColour(wxColour(0xFF, 0x6F, 0x00)); m_txt_change_filament_times->SetBackgroundColour(*wxWHITE); m_txt_change_filament_times->SetLabel(wxEmptyString); //m_change_filament_times_sizer->Add(m_img_change_filament_times, 0, wxTOP, FromDIP(2)); m_change_filament_times_sizer->Add(m_txt_change_filament_times, 0, wxTOP, 0); m_warn_when_drying_sizer = new wxBoxSizer(wxHORIZONTAL); m_txt_warn_when_drying = new Label(m_scroll_area, wxEmptyString); m_txt_warn_when_drying->SetFont(::Label::Body_13); m_txt_warn_when_drying->SetForegroundColour(wxColour("#F09A17")); m_txt_warn_when_drying->SetBackgroundColour(*wxWHITE); m_txt_warn_when_drying->SetLabel(_L("To ensure print quality, the drying temperature will be lowered during printing.")); m_warn_when_drying_sizer->Add(m_txt_warn_when_drying, 0, wxTOP, FromDIP(2)); /*Advanced Options*/ wxBoxSizer* sizer_split_options = new wxBoxSizer(wxHORIZONTAL); auto m_split_options_line = new wxPanel(m_scroll_area, wxID_ANY); m_split_options_line->SetBackgroundColour(0xEEEEEE); m_split_options_line->SetSize(wxSize(-1, FromDIP(1))); m_split_options_line->SetMinSize(wxSize(-1, FromDIP(1))); m_split_options_line->SetMaxSize(wxSize(-1, FromDIP(1))); sizer_split_options->Add(0, 0, 0, wxEXPAND, 0); sizer_split_options->Add(m_split_options_line, 1, wxALIGN_CENTER, 0); m_options_other = new wxPanel(m_scroll_area); auto option_timelapse = new PrintOption(m_options_other, _L("Timelapse"), wxEmptyString, ops_no_auto, "timelapse"); // timelapse storage location folder button (shown only when is_support_internal_timelapse) m_timelapse_folder_btn = new ScalableButton(m_options_other, wxID_ANY, "folder-closed", wxEmptyString, wxDefaultSize, wxDefaultPosition, wxBU_EXACTFIT | wxNO_BORDER, true); m_timelapse_folder_btn->SetBackgroundColour(*wxWHITE); m_timelapse_folder_btn->SetToolTip(_L("Select timelapse storage location")); m_timelapse_folder_btn->Hide(); m_timelapse_folder_btn->Bind(wxEVT_BUTTON, [this](wxCommandEvent&) { show_timelapse_folder_popup(); }); m_timelapse_folder_btn->Bind(wxEVT_ENTER_WINDOW, [this](wxMouseEvent& e) { // hover: only switch if popup is not open (popup open = active state) if (!m_timelapse_storage_popup || !m_timelapse_storage_popup->IsShown()) m_timelapse_folder_btn->SetBitmap(create_scaled_bitmap("folder-closed-hover", m_timelapse_folder_btn, 16)); e.Skip(); }); m_timelapse_folder_btn->Bind(wxEVT_LEAVE_WINDOW, [this](wxMouseEvent& e) { // restore normal (popup open state is handled separately) if (!m_timelapse_storage_popup || !m_timelapse_storage_popup->IsShown()) update_timelapse_folder_btn_icon(); e.Skip(); }); auto option_auto_bed_level = new PrintOption( m_options_other, _L("Auto Bed Leveling"), _L("This checks the flatness of heatbed. Leveling makes extruded height uniform.\n*Automatic mode: Run a leveling check(about 10 seconds). Skip if surface is fine."), ops_auto, "bed_leveling"); auto option_flow_dynamics_cali = new PrintOption(m_options_other, _L("Flow Dynamics Calibration"), _L("This process determines the dynamic flow values to improve overall print quality.\n*Automatic mode: Skip if the filament was calibrated recently."), ops_auto, "flow_cali"); option_flow_dynamics_cali->Bind(EVT_SWITCH_PRINT_OPTION, &SelectMachineDialog::on_flow_pa_caliation_option_changed, this); auto option_nozzle_offset_cali_cali = new PrintOption( m_options_other, _L("Nozzle Offset Calibration"), _L("Calibrate nozzle offsets to enhance print quality.\n*Automatic mode: Check for calibration before printing. Skip if unnecessary."), ops_auto, "nozzle_offset_cali" ); option_nozzle_offset_cali_cali->Bind(EVT_SWITCH_PRINT_OPTION, &SelectMachineDialog::on_nozzle_offset_option_changed, this); m_pa_value_panel = new wxPanel(m_options_other); m_pa_value_panel->SetMaxSize(wxSize(FromDIP(315), -1)); m_pa_value_panel->SetMinSize(wxSize(FromDIP(315), -1)); wxBoxSizer *m_pa_value_panel_sizer = new wxBoxSizer(wxHORIZONTAL); m_pa_value_message = new Label(m_pa_value_panel, _L("Nozzles and filaments of the same type share the same PA profile")); m_pa_value_message->SetFont(Label::Body_14); m_pa_value_message->SetBackgroundColour(*wxWHITE); m_pa_value_message->Wrap(FromDIP(243)); m_pa_value_switch = new SwitchButton(m_pa_value_panel); m_pa_value_switch->SetBackgroundColour(*wxWHITE); m_pa_value_switch->SetValue(true); m_pa_value_switch->Bind(wxEVT_TOGGLEBUTTON, &SelectMachineDialog::on_pa_value_switch_changed, this); m_pa_value_tips = new ScalableButton(m_pa_value_panel, wxID_ANY, "icon_qusetion", wxEmptyString, wxDefaultSize, wxDefaultPosition, wxBU_EXACTFIT | wxNO_BORDER, true); m_pa_value_tips->SetBackgroundColour(*wxWHITE); m_pa_value_tips->Bind(wxEVT_BUTTON, [this](wxCommandEvent &e){ std::string language = wxGetApp().app_config->get("language"); wxString region = "en"; if (language.find("zh") == 0) { region = "zh"; } wxString url = wxString::Format("https://wiki.bambulab.com/%s/software/bambu-studio/calibration_pa", region); wxLaunchDefaultBrowser(url); e.Skip(); }); m_pa_value_panel_sizer->Add(m_pa_value_message, 0, wxALIGN_CENTER_VERTICAL | wxRIGHT, FromDIP(4)); m_pa_value_panel_sizer->Add(m_pa_value_tips, 0, wxALIGN_CENTER_VERTICAL); m_pa_value_panel_sizer->Add(0, 0, 1, wxEXPAND); m_pa_value_panel_sizer->Add(m_pa_value_switch, 0, wxALIGN_CENTER_VERTICAL); m_pa_value_panel->SetSizer(m_pa_value_panel_sizer); m_pa_value_panel->Hide(); // insert folder button into timelapse PrintOption's own sizer (after title, before tips) option_timelapse->insert_extra_widget(m_timelapse_folder_btn); auto options_sizer = new wxBoxSizer(wxVERTICAL); m_sizer_options = new wxGridSizer(0, 2, FromDIP(5), FromDIP(10)); m_sizer_options->Add(option_timelapse, 0, wxEXPAND); m_sizer_options->Add(option_auto_bed_level, 0, wxEXPAND); m_sizer_options->Add(option_flow_dynamics_cali, 0, wxEXPAND); m_sizer_options->Add(option_nozzle_offset_cali_cali, 0, wxEXPAND); m_options_line_panel = new wxPanel(m_options_other, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL); m_options_line_panel->SetBackgroundColour(*wxWHITE); wxSizer* m_options_line_sizer = new wxBoxSizer(wxHORIZONTAL); wxSizer* m_options_line_right_sizer = new wxBoxSizer(wxVERTICAL); m_options_line_bmp = new wxStaticBitmap(m_options_line_panel, wxID_ANY, create_scaled_bitmap("warning", m_options_line_panel, 25), wxDefaultPosition, wxSize(FromDIP(25), FromDIP(25)), 0); m_options_line_label = new Label(m_options_line_panel, _L("If the filament/nozzle of the main extruder hasn't changed, the last calibration value will be reused. The auxiliary extruder will use the system default value.")); m_options_line_label->SetBackgroundColour(*wxWHITE); m_options_line_label->SetForegroundColour(wxColour(255, 111, 0)); m_options_line_label->SetFont(Label::Body_14); m_options_line_label->Wrap(FromDIP(630)); m_options_line_close = new Label(m_options_line_panel, _L("Don't show again")); m_options_line_close->SetBackgroundColour(*wxWHITE); m_options_line_close->SetForegroundColour(wxColour(0, 177, 66)); m_options_line_close->SetFont(Label::Body_14); wxFont font = m_options_line_close->GetFont(); font.SetUnderlined(true); m_options_line_close->SetFont(font); m_options_line_close->Bind(wxEVT_LEFT_DOWN, [this](auto &e) { if(wxGetApp().app_config){ wxGetApp().app_config->set("disable_auto_flow_cali_tips", "true"); } m_options_line_panel->Hide(); m_options_other->Layout(); m_options_other->Fit(); }); m_options_line_right_sizer->Add(m_options_line_label, 0, wxEXPAND, 0); m_options_line_right_sizer->Add(m_options_line_close, 0, wxEXPAND, 0); m_options_line_sizer->Add(m_options_line_bmp, 0, wxTOP, 0); m_options_line_sizer->Add(0, 0, 0, wxLEFT, FromDIP(2)); m_options_line_sizer->Add(m_options_line_right_sizer, 0, wxEXPAND, 0); m_options_line_panel->Hide(); m_options_line_panel->SetSizer(m_options_line_sizer); m_options_line_panel->Layout(); m_options_line_panel->Fit(); options_sizer->Add(m_sizer_options, 0, wxEXPAND, 0); options_sizer->Add(0, 0, 1, wxTOP, FromDIP(8)); options_sizer->Add(m_pa_value_panel); options_sizer->Add(0, 0, 1, wxTOP, FromDIP(12)); options_sizer->Add(m_options_line_panel, 0, wxEXPAND, 0); m_checkbox_list_order.push_back(option_timelapse); m_checkbox_list_order.push_back(option_auto_bed_level); m_checkbox_list_order.push_back(option_flow_dynamics_cali); m_checkbox_list_order.push_back(option_nozzle_offset_cali_cali); m_options_other->SetSizer(options_sizer); m_options_other->Layout(); m_options_other->Fit(); m_checkbox_list["timelapse"] = option_timelapse; m_checkbox_list["bed_leveling"] = option_auto_bed_level; m_checkbox_list["flow_cali"] = option_flow_dynamics_cali; m_checkbox_list["nozzle_offset_cali"] = option_nozzle_offset_cali_cali; for (auto print_opt : m_checkbox_list_order) { print_opt->Bind(EVT_SWITCH_PRINT_OPTION, [this](auto& e) { save_option_vals(); e.Skip();}); } option_auto_bed_level->Hide(); option_flow_dynamics_cali->Hide(); option_nozzle_offset_cali_cali->Hide(); m_simplebook = new wxSimplebook(this, wxID_ANY, wxDefaultPosition, SELECT_MACHINE_DIALOG_SIMBOOK_SIZE2, 0); m_simplebook->SetMinSize(SELECT_MACHINE_DIALOG_SIMBOOK_SIZE2); m_simplebook->SetMaxSize(SELECT_MACHINE_DIALOG_SIMBOOK_SIZE2); // perpare mode m_panel_prepare = new wxPanel(m_simplebook, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL); m_panel_prepare->SetBackgroundColour(m_colour_def_color); wxBoxSizer *m_sizer_prepare = new wxBoxSizer(wxHORIZONTAL); wxBoxSizer *m_sizer_pcont = new wxBoxSizer(wxVERTICAL); m_btn_bg_enable = StateColor(std::pair(wxColour(27, 136, 68), StateColor::Pressed), std::pair(wxColour(61, 203, 115), StateColor::Hovered), std::pair(wxColour(0, 174, 66), StateColor::Normal)); m_button_ensure = new Button(m_panel_prepare, _L("Send")); m_button_ensure->SetBackgroundColor(m_btn_bg_enable); m_button_ensure->SetBorderColor(m_btn_bg_enable); m_button_ensure->SetTextColor(StateColor::darkModeColorFor("#FFFFFE")); m_button_ensure->SetMinSize(SELECT_MACHINE_DIALOG_BUTTON_SIZE2); m_button_ensure->SetMinSize(SELECT_MACHINE_DIALOG_BUTTON_SIZE2); m_button_ensure->SetCornerRadius(FromDIP(4)); m_button_ensure->Bind(wxEVT_BUTTON, &SelectMachineDialog::on_ok_btn, this); m_sizer_pcont->Add(0, 0, 1, wxEXPAND, 0); m_sizer_pcont->Add(m_button_ensure, 0,wxALIGN_CENTER, 0); m_sizer_prepare->Add(0, 0, 1, wxTOP, FromDIP(12)); m_sizer_prepare->Add(m_sizer_pcont, 0, wxALIGN_CENTER, 0); m_panel_prepare->SetSizer(m_sizer_prepare); m_panel_prepare->Layout(); m_simplebook->AddPage(m_panel_prepare, wxEmptyString, true); // sending mode m_status_bar = std::make_shared(m_simplebook); m_panel_sending = m_status_bar->get_panel(); m_status_bar->set_status_height_changed_callback([this](int height) { const wxSize size(SELECT_MACHINE_DIALOG_SIMBOOK_SIZE2.x, height); m_simplebook->SetMinSize(size); m_simplebook->SetMaxSize(size); m_simplebook->SetSize(size); m_simplebook->Layout(); Layout(); Fit(); }); m_simplebook->AddPage(m_panel_sending, wxEmptyString, false); // finish mode m_panel_finish = new wxPanel(m_simplebook, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL); m_panel_finish->SetBackgroundColour(wxColour(135, 206, 250)); wxBoxSizer *m_sizer_finish = new wxBoxSizer(wxHORIZONTAL); wxBoxSizer *m_sizer_finish_v = new wxBoxSizer(wxVERTICAL); wxBoxSizer *m_sizer_finish_h = new wxBoxSizer(wxHORIZONTAL); auto imgsize = FromDIP(25); auto completedimg = new wxStaticBitmap(m_panel_finish, wxID_ANY, create_scaled_bitmap("completed", m_panel_finish, 25), wxDefaultPosition, wxSize(imgsize, imgsize), 0); m_sizer_finish_h->Add(completedimg, 0, wxALIGN_CENTER | wxALL, FromDIP(5)); m_statictext_finish = new wxStaticText(m_panel_finish, wxID_ANY, L("send completed"), wxDefaultPosition, wxDefaultSize, 0); m_statictext_finish->Wrap(-1); m_statictext_finish->SetForegroundColour(wxColour(0, 174, 66)); m_sizer_finish_h->Add(m_statictext_finish, 0, wxALIGN_CENTER | wxALL, FromDIP(5)); m_sizer_finish_v->Add(m_sizer_finish_h, 1, wxALIGN_CENTER, 0); m_sizer_finish->Add(m_sizer_finish_v, 1, wxALIGN_CENTER, 0); m_panel_finish->SetSizer(m_sizer_finish); m_panel_finish->Layout(); m_sizer_finish->Fit(m_panel_finish); m_simplebook->AddPage(m_panel_finish, wxEmptyString, false); //show bind failed info m_sw_print_failed_info = new wxScrolledWindow(this, wxID_ANY, wxDefaultPosition, wxSize(SELECT_MACHINE_DIALOG_SIMBOOK_SIZE2.x, FromDIP(125)), wxVSCROLL); m_sw_print_failed_info->SetBackgroundColour(*wxWHITE); m_sw_print_failed_info->SetScrollRate(0, 5); m_sw_print_failed_info->SetMinSize(wxSize(SELECT_MACHINE_DIALOG_SIMBOOK_SIZE2.x, FromDIP(125))); m_sw_print_failed_info->SetMaxSize(wxSize(SELECT_MACHINE_DIALOG_SIMBOOK_SIZE2.x, FromDIP(125))); wxBoxSizer* sizer_print_failed_info = new wxBoxSizer(wxVERTICAL); m_sw_print_failed_info->SetSizer(sizer_print_failed_info); wxBoxSizer* sizer_error_code = new wxBoxSizer(wxHORIZONTAL); wxBoxSizer* sizer_error_desc = new wxBoxSizer(wxHORIZONTAL); wxBoxSizer* sizer_extra_info = new wxBoxSizer(wxHORIZONTAL); auto st_title_error_code = new wxStaticText(m_sw_print_failed_info, wxID_ANY, _L("Error code")); auto st_title_error_code_doc = new wxStaticText(m_sw_print_failed_info, wxID_ANY,": "); m_st_txt_error_code = new Label(m_sw_print_failed_info, wxEmptyString); st_title_error_code->SetForegroundColour(0x909090); st_title_error_code_doc->SetForegroundColour(0x909090); m_st_txt_error_code->SetForegroundColour(0x909090); st_title_error_code->SetFont(::Label::Body_13); st_title_error_code_doc->SetFont(::Label::Body_13); m_st_txt_error_code->SetFont(::Label::Body_13); st_title_error_code->SetMinSize(wxSize(FromDIP(74), -1)); st_title_error_code->SetMaxSize(wxSize(FromDIP(74), -1)); m_st_txt_error_code->SetMinSize(wxSize(FromDIP(500), -1)); m_st_txt_error_code->SetMaxSize(wxSize(FromDIP(500), -1)); sizer_error_code->Add(st_title_error_code, 0, wxALL, 0); sizer_error_code->Add(st_title_error_code_doc, 0, wxALL, 0); sizer_error_code->Add(m_st_txt_error_code, 0, wxALL, 0); auto st_title_error_desc = new wxStaticText(m_sw_print_failed_info, wxID_ANY, wxT("Error desc")); auto st_title_error_desc_doc = new wxStaticText(m_sw_print_failed_info, wxID_ANY,": "); m_st_txt_error_desc = new Label(m_sw_print_failed_info, wxEmptyString); st_title_error_desc->SetForegroundColour(0x909090); st_title_error_desc_doc->SetForegroundColour(0x909090); m_st_txt_error_desc->SetForegroundColour(0x909090); st_title_error_desc->SetFont(::Label::Body_13); st_title_error_desc_doc->SetFont(::Label::Body_13); m_st_txt_error_desc->SetFont(::Label::Body_13); st_title_error_desc->SetMinSize(wxSize(FromDIP(74), -1)); st_title_error_desc->SetMaxSize(wxSize(FromDIP(74), -1)); m_st_txt_error_desc->SetMinSize(wxSize(FromDIP(500), -1)); m_st_txt_error_desc->SetMaxSize(wxSize(FromDIP(500), -1)); sizer_error_desc->Add(st_title_error_desc, 0, wxALL, 0); sizer_error_desc->Add(st_title_error_desc_doc, 0, wxALL, 0); sizer_error_desc->Add(m_st_txt_error_desc, 0, wxALL, 0); auto st_title_extra_info = new wxStaticText(m_sw_print_failed_info, wxID_ANY, wxT("Extra info")); auto st_title_extra_info_doc = new wxStaticText(m_sw_print_failed_info, wxID_ANY, ": "); m_st_txt_extra_info = new Label(m_sw_print_failed_info, wxEmptyString); st_title_extra_info->SetForegroundColour(0x909090); st_title_extra_info_doc->SetForegroundColour(0x909090); m_st_txt_extra_info->SetForegroundColour(0x909090); st_title_extra_info->SetFont(::Label::Body_13); st_title_extra_info_doc->SetFont(::Label::Body_13); m_st_txt_extra_info->SetFont(::Label::Body_13); st_title_extra_info->SetMinSize(wxSize(FromDIP(74), -1)); st_title_extra_info->SetMaxSize(wxSize(FromDIP(74), -1)); m_st_txt_extra_info->SetMinSize(wxSize(FromDIP(500), -1)); m_st_txt_extra_info->SetMaxSize(wxSize(FromDIP(500), -1)); sizer_extra_info->Add(st_title_extra_info, 0, wxALL, 0); sizer_extra_info->Add(st_title_extra_info_doc, 0, wxALL, 0); sizer_extra_info->Add(m_st_txt_extra_info, 0, wxALL, 0); m_link_network_state = new wxHyperlinkCtrl(m_sw_print_failed_info, wxID_ANY,_L("Check the status of current system services"),""); m_link_network_state->SetFont(::Label::Body_12); m_link_network_state->Bind(wxEVT_LEFT_DOWN, [this](auto& e) {wxGetApp().link_to_network_check();}); m_link_network_state->Bind(wxEVT_ENTER_WINDOW, [this](auto& e) {m_link_network_state->SetCursor(wxCURSOR_HAND);}); m_link_network_state->Bind(wxEVT_LEAVE_WINDOW, [this](auto& e) {m_link_network_state->SetCursor(wxCURSOR_ARROW);}); sizer_print_failed_info->Add(m_link_network_state, 0, wxLEFT, 5); sizer_print_failed_info->Add(sizer_error_code, 0, wxLEFT, 5); sizer_print_failed_info->Add(0, 0, 0, wxTOP, FromDIP(3)); sizer_print_failed_info->Add(sizer_error_desc, 0, wxLEFT, 5); sizer_print_failed_info->Add(0, 0, 0, wxTOP, FromDIP(3)); sizer_print_failed_info->Add(sizer_extra_info, 0, wxLEFT, 5); m_scroll_sizer->Add(m_line_top, 0, wxEXPAND, 0); m_scroll_sizer->Add(0, 0, 0, wxTOP, FromDIP(11)); //m_sizer_main->Add(m_sizer_mode_switch, 0, wxALIGN_CENTER, 0); //m_scroll_sizer->Add(0, 0, 0, wxTOP, FromDIP(15)); m_scroll_sizer->AddStretchSpacer(); m_scroll_sizer->Add(m_basic_panel, 0, wxEXPAND|wxLEFT|wxRIGHT, FromDIP(15)); m_scroll_sizer->Add(0, 0, 0, wxTOP, FromDIP(14)); m_scroll_sizer->Add(sizer_split_filament, 1, wxEXPAND|wxLEFT|wxRIGHT, FromDIP(15)); m_scroll_sizer->Add(m_filament_panel, 0, wxALIGN_CENTER|wxLEFT|wxRIGHT, FromDIP(15)); m_scroll_sizer->Add(m_sizer_filament_2extruder, 0, wxEXPAND|wxLEFT|wxRIGHT, FromDIP(15)); m_scroll_sizer->Add(0, 0, 0, wxTOP, FromDIP(6)); m_scroll_sizer->Add(m_statictext_ams_msg, 0, wxLEFT|wxRIGHT, FromDIP(15)); m_scroll_sizer->Add(0, 0, 0, wxTOP, FromDIP(10)); m_scroll_sizer->Add(m_mapping_sugs_sizer, 0, wxLEFT|wxRIGHT, FromDIP(15)); m_scroll_sizer->Add(m_change_filament_times_sizer, 0,wxLEFT|wxRIGHT, FromDIP(15)); // m_scroll_sizer->Add(m_link_edit_nozzle, 0, wxLEFT|wxRIGHT, FromDIP(15)); m_scroll_sizer->Add(suggestion_sizer, 0, wxLEFT|wxRIGHT|wxEXPAND, FromDIP(15)); m_scroll_sizer->Add(0, 0, 0, wxTOP, FromDIP(10)); m_scroll_sizer->Add(m_warn_when_drying_sizer, 0, wxLEFT|wxRIGHT, FromDIP(15)); m_scroll_sizer->Add(sizer_split_options, 1, wxEXPAND|wxLEFT|wxRIGHT, FromDIP(15)); m_scroll_sizer->Add(0, 0, 0, wxTOP, FromDIP(10)); m_scroll_sizer->Add(m_options_other, 0, wxEXPAND|wxLEFT|wxRIGHT, FromDIP(15)); m_scroll_sizer->Add(0, 0, 0, wxTOP, FromDIP(30)); show_print_failed_info(false); m_scroll_area->SetSizer(m_scroll_sizer); wxBoxSizer *sizer_main = new wxBoxSizer(wxVERTICAL); sizer_main->Add(m_scroll_area, 1, wxEXPAND, 0); sizer_main->Add(m_simplebook, 0, wxALIGN_CENTER, 0); sizer_main->Add(0, 0, 0, wxTOP, FromDIP(10)); sizer_main->Add(m_sw_print_failed_info, 0, wxALIGN_CENTER, 0); sizer_main->Add(0, 0, 0, wxTOP, FromDIP(18)); SetSizer(sizer_main); Layout(); Fit(); Thaw(); init_bind(); init_timer(); Centre(wxBOTH); wxGetApp().UpdateDlgDarkUI(this); } void SelectMachineDialog::init_bind() { Bind(wxEVT_TIMER, &SelectMachineDialog::on_timer, this); Bind(EVT_CLEAR_IPADDRESS, &SelectMachineDialog::clear_ip_address_config, this); Bind(EVT_SHOW_ERROR_INFO, [this](auto& e) {show_print_failed_info(true);}); Bind(EVT_UPDATE_USER_MACHINE_LIST, &SelectMachineDialog::update_printer_combobox, this); Bind(EVT_PRINT_JOB_CANCEL, &SelectMachineDialog::on_print_job_cancel, this); Bind(EVT_SET_FINISH_MAPPING, &SelectMachineDialog::on_set_finish_mapping, this); Bind(wxEVT_LEFT_DOWN, [this](auto& e) {check_fcous_state(this);e.Skip();}); Bind(wxEVT_REFRESH_DATA, &SelectMachineDialog::update_best_pos_dialog, this); m_panel_prepare->Bind(wxEVT_LEFT_DOWN, [this](auto& e) {check_fcous_state(this);e.Skip();}); m_basic_panel->Bind(wxEVT_LEFT_DOWN, [this](auto& e) {check_fcous_state(this);e.Skip();}); m_bitmap_last_plate->Bind(wxEVT_ENTER_WINDOW, [this](auto& e) {SetCursor(wxCURSOR_HAND); }); m_bitmap_last_plate->Bind(wxEVT_LEAVE_WINDOW, [this](auto& e) {SetCursor(wxCURSOR_ARROW); }); m_bitmap_next_plate->Bind(wxEVT_ENTER_WINDOW, [this](auto& e) {SetCursor(wxCURSOR_HAND); }); m_bitmap_next_plate->Bind(wxEVT_LEAVE_WINDOW, [this](auto& e) {SetCursor(wxCURSOR_ARROW); }); Bind(EVT_CONNECT_LAN_MODE_PRINT, [this](wxCommandEvent& e) { if (e.GetInt() == 0) { DeviceManager* dev = Slic3r::GUI::wxGetApp().getDeviceManager(); if (!dev) return; MachineObject* obj = dev->get_selected_machine(); if (!obj) return; if (obj->get_dev_id() == e.GetString()) { m_printer_box->SetPrinterName(wxString::FromUTF8(obj->get_dev_name()) + "(LAN)"); } } }); m_bitmap_last_plate->Bind(wxEVT_LEFT_DOWN, [this](auto& e) { if (m_print_plate_idx > 0) { m_print_plate_idx--; update_page_turn_state(true); set_default_from_sdcard(); Layout(); } }); m_bitmap_next_plate->Bind(wxEVT_LEFT_DOWN, [this](auto& e) { if (m_print_plate_idx < (m_print_plate_total - 1)) { m_print_plate_idx++; update_page_turn_state(true); set_default_from_sdcard(); Layout(); } }); m_check_ext_change_assist->Bind(wxEVT_TOGGLEBUTTON, [this](auto &e) { m_ext_change_assist = e.IsChecked(); e.Skip(); }); } void SelectMachineDialog::show_print_failed_info(bool show, int code, wxString description, wxString extra) { if (show) { if (!m_sw_print_failed_info->IsShown()) { m_sw_print_failed_info->Show(true); m_st_txt_error_code->SetLabelText(wxString::Format("%d", m_print_error_code)); m_st_txt_error_desc->SetLabelText( wxGetApp().filter_string(m_print_error_msg)); m_st_txt_extra_info->SetLabelText( wxGetApp().filter_string(m_print_error_extra)); m_st_txt_error_code->Wrap(FromDIP(500)); m_st_txt_error_desc->Wrap(FromDIP(500)); m_st_txt_extra_info->Wrap(FromDIP(500)); } else { m_sw_print_failed_info->Show(false); } Layout(); Fit(); } else { if (!m_sw_print_failed_info->IsShown()) {return;} m_sw_print_failed_info->Show(false); m_st_txt_error_code->SetLabelText(wxEmptyString); m_st_txt_error_desc->SetLabelText(wxEmptyString); m_st_txt_extra_info->SetLabelText(wxEmptyString); Layout(); Fit(); } } void SelectMachineDialog::check_fcous_state(wxWindow* window) { if (window == m_rename_input || window == m_rename_input->GetTextCtrl()) { on_rename_enter(); } auto children = window->GetChildren(); for (auto child : children) { check_fcous_state(child); } } void SelectMachineDialog::popup_filament_backup() { if (m_print_status == PrintStatusUnsupportedPrinter) { return; } DeviceManager* dev = Slic3r::GUI::wxGetApp().getDeviceManager(); if (!dev) return; if (dev->get_selected_machine()/* && dev->get_selected_machine()->filam_bak.size() > 0*/) { AmsReplaceMaterialDialog* m_replace_material_popup = new AmsReplaceMaterialDialog(this); m_replace_material_popup->update_machine_obj(dev->get_selected_machine()); m_replace_material_popup->ShowModal(); } } void SelectMachineDialog::update_select_layout(MachineObject *obj) { update_option_opts(obj); load_option_vals(obj); if (obj && obj->get_printer_arch() == PrinterArch::ARCH_I3) { m_checkbox_list["timelapse"]->setValue("off"); } /*off timelapse on selected for n series by zhimin.zeng*/ save_option_vals(obj); Layout(); Fit(); } void SelectMachineDialog::prepare_mode(bool refresh_button) { Enable_Auto_Refill(true); show_print_failed_info(false); m_is_in_sending_mode = false; if (m_print_job) { m_print_job->join(); } if (wxIsBusy()) wxEndBusyCursor(); if (refresh_button) { EnableEditing(true); Enable_Send_Button(true); } m_status_bar->reset(); if (m_simplebook->GetSelection() != 0) { m_simplebook->SetSelection(0); Layout(); Fit(); } if (m_print_page_mode != PrintPageModePrepare) { m_print_page_mode = PrintPageModePrepare; for (auto it = m_materialList.begin(); it != m_materialList.end(); it++) { it->second->item->enable(); } } } void SelectMachineDialog::sending_mode() { // disable combobox Enable_Auto_Refill(false); EnableEditing(false); m_is_in_sending_mode = true; if (m_simplebook->GetSelection() != 1){ m_simplebook->SetSelection(1); Layout(); Fit(); } if (m_print_page_mode != PrintPageModeSending) { m_print_page_mode = PrintPageModeSending; for (auto it = m_materialList.begin(); it != m_materialList.end(); it++) { it->second->item->disable(); } } } void SelectMachineDialog::finish_mode() { EnableEditing(true); m_print_page_mode = PrintPageModeFinish; m_is_in_sending_mode = false; m_simplebook->SetSelection(2); Layout(); Fit(); } void SelectMachineDialog::sync_ams_mapping_result(const std::vector &result) { if (result.empty()) { BOOST_LOG_TRIVIAL(info) << "ams_mapping result is empty"; for (auto it = m_materialList.begin(); it != m_materialList.end(); it++) { wxString ams_id = "Ext";// wxColour ams_col = wxColour(0xCE, 0xCE, 0xCE); it->second->item->set_ams_info(ams_col, ams_id); it->second->item->set_nozzle_info(get_mapped_nozzle_str(it->first)); } return; } auto total_ext_count = std::optional(); if (auto obj_ = get_current_machine()) { total_ext_count = obj_->GetExtderSystem()->GetTotalExtderCount(); }; for (auto f = result.begin(); f != result.end(); f++) { BOOST_LOG_TRIVIAL(trace) << "ams_mapping f id = " << f->id << ", tray_id = " << f->tray_id << ", color = " << f->color << ", type = " << f->type; MaterialHash::iterator iter = m_materialList.begin(); while (iter != m_materialList.end()) { int id = iter->second->id; Material * item = iter->second; MaterialItem *m = item->item; if (f->id == id) { wxString ams_id; wxColour ams_col; if (f->tray_id >= 0) { ams_id = wxGetApp().transition_tridid(f->tray_id, total_ext_count); if (m_filament_left_panel->IsShown() && m_filament_right_panel->IsShown() && devPrinterUtil::IsVirtualSlot(f->tray_id)) { ams_id = "Ext"; } } else { ams_id = "-"; } if (!f->color.empty()) { ams_col = DevAmsTray::decode_color(f->color); } else { // default color ams_col = wxColour(0xCE, 0xCE, 0xCE); } std::vector cols; for (auto col : f->colors) { cols.push_back(DevAmsTray::decode_color(col)); } m->set_ams_info(ams_col, ams_id,f->ctype, cols); m->set_nozzle_info(get_mapped_nozzle_str(id)); break; } iter++; } } auto tab_index = (MainFrame::TabPosition) dynamic_cast(wxGetApp().tab_panel())->GetSelection(); if (tab_index == MainFrame::TabPosition::tp3DEditor || tab_index == MainFrame::TabPosition::tpPreview) { updata_thumbnail_data_after_connected_printer(); } } void print_ams_mapping_result(std::vector& result) { if (result.empty()) { BOOST_LOG_TRIVIAL(info) << "print_ams_mapping_result: empty"; } char buffer[256]; for (int i = 0; i < result.size(); i++) { ::sprintf(buffer, "print_ams_mapping: F(%02d) -> A(%02d)", result[i].id+1, result[i].tray_id+1); BOOST_LOG_TRIVIAL(info) << std::string(buffer); } } bool SelectMachineDialog::do_ams_mapping(MachineObject *obj_,bool use_ams) { if (!obj_) return false; if (!obj_->HasAms()) { use_ams = false;} /*STUDIO-11971*/ obj_->get_ams_colors(m_cur_colors_in_thumbnail); // try color and type mapping const auto& full_config = wxGetApp().preset_bundle->full_config(); const auto& project_config = wxGetApp().preset_bundle->project_config; size_t nozzle_nums = full_config.option("nozzle_diameter")->values.size(); if (m_print_type == FROM_NORMAL) { m_filaments_map = wxGetApp().plater()->get_partplate_list().get_curr_plate()->get_real_filament_maps(project_config); } int filament_result = 0; std::vector map_opt; //four values: use_left_ams, use_right_ams, use_left_ext, use_right_ext if (nozzle_nums > 1){ //get nozzle property, the extders are same? if (true/*!can_hybrid_mapping(obj_get_extder_data())*/){ std::vector m_ams_mapping_result_left, m_ams_mapping_result_right; std::vector m_filament_left, m_filament_right; std::string filament_map_str; for (auto fila : m_filaments_map) { filament_map_str += std::to_string(fila) + " "; }; BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << "[info]: m_filaments_map(fila2extuder)=[ " << filament_map_str << "]"; for (auto it = m_filaments.begin(); it != m_filaments.end(); it++){ if (it->id < 0 || it->id > m_filaments_map.size()){ BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << "[error]: m_filaments_map is invalid"; return false; } if (it->id < m_filaments_map.size()) { if (m_filaments_map[it->id] == 1) m_filament_left.push_back(*it); else if (m_filaments_map[it->id] == 2) m_filament_right.push_back(*it); } } bool has_left_ams = false, has_right_ams = false; for (const auto& ams_item : obj_->GetFilaSystem()->GetAmsList()) { if (ams_item.second->GetBindedExtruderSet().count(MAIN_EXTRUDER_ID) != 0) { has_right_ams = true; } if (ams_item.second->GetBindedExtruderSet().count(DEPUTY_EXTRUDER_ID) != 0) { has_left_ams = true; } if (has_left_ams && has_right_ams) { break; } } map_opt = {true, false, !has_left_ams, false}; //four values: use_left_ams, use_right_ams, use_left_ext, use_right_ext if (!use_ams) { map_opt[0] = false; map_opt[2] = true; } int result_first = DevMappingUtil::ams_filament_mapping(obj_, m_filament_left, m_ams_mapping_result_left, map_opt); map_opt = { false, true, false, !has_right_ams }; if (!use_ams) { map_opt[1] = false; map_opt[3] = true; } int result_second = DevMappingUtil::ams_filament_mapping(obj_, m_filament_right, m_ams_mapping_result_right, map_opt); //m_ams_mapping_result.clear(); m_ams_mapping_result.resize(m_ams_mapping_result_left.size() + m_ams_mapping_result_right.size()); std::merge(m_ams_mapping_result_left.begin(), m_ams_mapping_result_left.end(), m_ams_mapping_result_right.begin(), m_ams_mapping_result_right.end(), m_ams_mapping_result.begin(), [](const FilamentInfo& f1, const FilamentInfo& f2) { return f1.id < f2.id; // Merge based on age }); filament_result = (result_first && result_second); } //can hybrid mapping else { map_opt = { true, true, true, true }; //four values: use_left_ams, use_right_ams, use_left_ext, use_right_ext if (!use_ams) { map_opt[0] = false; map_opt[1] = false; } filament_result = DevMappingUtil::ams_filament_mapping(obj_, m_filaments, m_ams_mapping_result, map_opt); } //When filaments cannot be matched automatically, whether to use ext for automatic supply //auto_supply_with_ext(obj_->vt_slot); } //single nozzle else { map_opt = { false, true, false, false }; //four values: use_left_ams, use_right_ams, use_left_ext, use_right_ext if (!use_ams) { map_opt[1] = false; map_opt[3] = true; } filament_result = DevMappingUtil::ams_filament_mapping(obj_, m_filaments, m_ams_mapping_result, map_opt); // auto_supply_with_ext(obj_->vt_slot); } if (filament_result == 0) { print_ams_mapping_result(m_ams_mapping_result); std::string ams_array; std::string ams_array2; std::string mapping_info; get_ams_mapping_result(ams_array, ams_array2, mapping_info); if (ams_array.empty()) { reset_ams_material(); if (m_print_type == FROM_NORMAL) { updata_thumbnail_data_after_connected_printer(); // FIX:update cur plate thumbnail } BOOST_LOG_TRIVIAL(info) << "ams_mapping_array=[]"; } else { sync_ams_mapping_result(m_ams_mapping_result); BOOST_LOG_TRIVIAL(info) << "ams_mapping_array=" << ams_array; BOOST_LOG_TRIVIAL(info) << "ams_mapping_array2=" << ams_array2; BOOST_LOG_TRIVIAL(info) << "ams_mapping_info=" << mapping_info; } return DevMappingUtil::is_valid_mapping_result(obj_, m_ams_mapping_result); } else { // do not support ams mapping try to use order mapping bool is_valid = DevMappingUtil::is_valid_mapping_result(obj_, m_ams_mapping_result); if (filament_result != 1 && !is_valid) { //reset invalid result for (int i = 0; i < m_ams_mapping_result.size(); i++) { m_ams_mapping_result[i].tray_id = -1; m_ams_mapping_result[i].distance = 99999; } } sync_ams_mapping_result(m_ams_mapping_result); return is_valid; } return true; } static int s_convert_filament_map_nozzle_id_to_task_nozzle_id(int nozzle_id) { if (nozzle_id == (int)FilamentMapNozzleId::NOZZLE_LEFT) { return (int)CloudTaskNozzleId::NOZZLE_LEFT; } else if (nozzle_id == (int)FilamentMapNozzleId::NOZZLE_RIGHT) { return (int)CloudTaskNozzleId::NOZZLE_RIGHT; } else { /* unsupported nozzle id */ assert(false); return nozzle_id; } } bool SelectMachineDialog::get_ams_mapping_result(std::string &mapping_array_str, std::string& mapping_array_str2, std::string &ams_mapping_info) const { if (m_ams_mapping_result.empty()) return false; bool valid_mapping_result = true; int invalid_count = 0; for (int i = 0; i < m_ams_mapping_result.size(); i++) { if (m_ams_mapping_result[i].tray_id == -1) { valid_mapping_result = false; invalid_count++; } } if (invalid_count == m_ams_mapping_result.size()) { return false; } else { if (m_print_type == FROM_NORMAL) { json mapping_v0_json = json::array(); json mapping_v1_json = json::array(); json mapping_info_json = json::array(); /* get filament maps */ std::vector filament_maps; Plater * plater = wxGetApp().plater(); if (plater) { PartPlate *curr_plate = plater->get_partplate_list().get_curr_plate(); if (curr_plate) { filament_maps = curr_plate->get_filament_maps(); } else { BOOST_LOG_TRIVIAL(error) << "get_ams_mapping_result, curr_plate is nullptr"; } } else { BOOST_LOG_TRIVIAL(error) << "get_ams_mapping_result, plater is nullptr"; } for (int i = 0; i < wxGetApp().preset_bundle->filament_presets.size(); i++) { int tray_id = -1; json mapping_item_v1; mapping_item_v1["ams_id"] = 0xff; mapping_item_v1["slot_id"] = 0xff; json mapping_item; mapping_item["ams"] = tray_id; mapping_item["targetColor"] = ""; mapping_item["filamentId"] = ""; mapping_item["filamentType"] = ""; for (int k = 0; k < m_ams_mapping_result.size(); k++) { if (m_ams_mapping_result[k].id == i) { tray_id = m_ams_mapping_result[k].tray_id; mapping_item["ams"] = tray_id; mapping_item["filamentType"] = m_filaments[k].type; if (i >= 0 && i < wxGetApp().preset_bundle->filament_presets.size()) { auto it = wxGetApp().preset_bundle->filaments.find_preset(wxGetApp().preset_bundle->filament_presets[i]); if (it != nullptr) { mapping_item["filamentId"] = it->filament_id; } } /* nozzle id */ if (i >= 0 && i < filament_maps.size()) { mapping_item["nozzleId"] = s_convert_filament_map_nozzle_id_to_task_nozzle_id(filament_maps[i]); } // convert #RRGGBB to RRGGBBAA mapping_item["sourceColor"] = m_filaments[k].color; mapping_item["targetColor"] = m_ams_mapping_result[k].color; if (tray_id == VIRTUAL_TRAY_MAIN_ID || tray_id == VIRTUAL_TRAY_DEPUTY_ID) { tray_id = -1; } /*new ams mapping data*/ try { if (m_ams_mapping_result[k].ams_id.empty() || m_ams_mapping_result[k].slot_id.empty()) { // invalid case mapping_item_v1["ams_id"] = VIRTUAL_TRAY_MAIN_ID; mapping_item_v1["slot_id"] = VIRTUAL_TRAY_MAIN_ID; } else { mapping_item_v1["ams_id"] = std::stoi(m_ams_mapping_result[k].ams_id); mapping_item_v1["slot_id"] = std::stoi(m_ams_mapping_result[k].slot_id); } } catch (...) {} } } mapping_v0_json.push_back(tray_id); mapping_v1_json.push_back(mapping_item_v1); mapping_info_json.push_back(mapping_item); } mapping_array_str = mapping_v0_json.dump(); mapping_array_str2 = mapping_v1_json.dump(); ams_mapping_info = mapping_info_json.dump(); return valid_mapping_result; } else if(m_print_type == FROM_SDCARD_VIEW){ json mapping_v0_json = json::array(); json mapping_v1_json = json::array(); for (int i = 0; i < m_filaments_map.size(); i++) { int mapping_result = -1; json mapping_item_v1; mapping_item_v1["ams_id"] = 0xff; mapping_item_v1["slot_id"] = 0xff; try { for (int k = 0; k < m_ams_mapping_result.size(); k++) { if (m_ams_mapping_result[k].id == i) { mapping_result = m_ams_mapping_result[k].tray_id; mapping_item_v1["ams_id"] = std::stoi(m_ams_mapping_result[k].ams_id); mapping_item_v1["slot_id"] = std::stoi(m_ams_mapping_result[k].slot_id); } } } catch (...) {} mapping_v0_json.push_back(mapping_result); mapping_v1_json.push_back(mapping_item_v1); mapping_array_str = mapping_v0_json.dump(); mapping_array_str2 = mapping_v1_json.dump(); } return true; } } return true; } bool SelectMachineDialog::build_nozzles_info(std::string& nozzles_info) { /* init nozzles info */ json nozzle_info_json = json::array(); nozzles_info = nozzle_info_json.dump(); PresetBundle* preset_bundle = wxGetApp().preset_bundle; if (!preset_bundle) return false; auto opt_nozzle_diameters = preset_bundle->printers.get_edited_preset().config.option("nozzle_diameter"); if (opt_nozzle_diameters == nullptr) { BOOST_LOG_TRIVIAL(error) << "build_nozzles_info, opt_nozzle_diameters is nullptr"; return false; } auto opt_nozzle_volume_type = preset_bundle->project_config.option("nozzle_volume_type"); if (opt_nozzle_volume_type == nullptr) { BOOST_LOG_TRIVIAL(error) << "build_nozzles_info, opt_nozzle_volume_type is nullptr"; return false; } json nozzle_item; /* only o1d two nozzles has build_nozzles info now */ if (opt_nozzle_diameters->size() != 2) { return false; } for (size_t i = 0; i < opt_nozzle_diameters->size(); i++) { if (i == (size_t)ConfigNozzleIdx::NOZZLE_LEFT) { nozzle_item["id"] = CloudTaskNozzleId::NOZZLE_LEFT; } else if (i == (size_t)ConfigNozzleIdx::NOZZLE_RIGHT) { nozzle_item["id"] = CloudTaskNozzleId::NOZZLE_RIGHT; } else { /* unknown ConfigNozzleIdx */ BOOST_LOG_TRIVIAL(error) << "build_nozzles_info, unknown ConfigNozzleIdx = " << i; assert(false); continue; } nozzle_item["type"] = nullptr; if (i >= 0 && i < opt_nozzle_volume_type->size()) { nozzle_item["flowSize"] = get_nozzle_volume_type_cloud_string((NozzleVolumeType)opt_nozzle_volume_type->get_at(i)); } if (i >= 0 && i < opt_nozzle_diameters->size()) { nozzle_item["diameter"] = opt_nozzle_diameters->get_at(i); } nozzle_info_json.push_back(nozzle_item); } nozzles_info = nozzle_info_json.dump(); return true; } bool SelectMachineDialog::can_hybrid_mapping(MachineObject* obj_) const { return obj_ && obj_->GetFilaSwitch()->IsInstalled(); } //When filaments cannot be matched automatically, whether to use ext for automatic supply void SelectMachineDialog::auto_supply_with_ext(std::vector slots) { if (slots.size() <= 0) return; for (int i = 0; i < m_ams_mapping_result.size(); i++) { auto it = m_ams_mapping_result[i]; if (it.ams_id == "") { DevAmsTray slot(""); if (m_filaments_map[it.id] == 1 && slots.size() > 1) slot = slots[1]; else if (m_filaments_map[it.id] == 2) slot = slots[0]; if (slot.id.empty()) continue; m_ams_mapping_result[i].ams_id = slot.id; m_ams_mapping_result[i].color = slot.color; m_ams_mapping_result[i].type = slot.m_fila_type; m_ams_mapping_result[i].colors = slot.cols; m_ams_mapping_result[i].tray_id = atoi(slot.id.c_str()); m_ams_mapping_result[i].slot_id = "0"; } } } bool SelectMachineDialog::support_filament_mapping_estimate() const { if (m_print_type != FROM_NORMAL) { return false; } // Gate on the preset's own AMS timing config instead of a hard-coded machine name or // nozzle variant: only presets that configure AMS load/unload times set default_ams_type // to a real timing type. Both the default (-1) and the external spool (0) are excluded, // since neither has per-AMS timings to recalculate with. bool filament_mapping_estimate = false; PresetBundle* preset_bundle = wxGetApp().preset_bundle; if (preset_bundle) { const auto& printer_config = preset_bundle->printers.get_edited_preset().config; const auto* sel_opt = printer_config.option("default_ams_type"); const auto& ams_types = get_ams_time_types(); filament_mapping_estimate = sel_opt && std::find(ams_types.begin(), ams_types.end(), sel_opt->value) != ams_types.end(); } return filament_mapping_estimate; } void SelectMachineDialog::update_time_estimate_tip(bool show) { if (m_time_estimate_tip) { const bool show_tip = show && support_filament_mapping_estimate(); const wxString tip = show_tip ? _L("The current estimated time has been recalculated based on the actual filament mapping.") : wxString(); if (m_time_estimate_tip->GetToolTipText() != tip) { m_time_estimate_tip->SetToolTip(tip); } if (m_time_estimate_tip->Show(show_tip)) { if (m_basic_panel) { m_basic_panel->Layout(); } } } } void SelectMachineDialog::refresh_print_time(MachineObject *obj) { PartPlate* plate = m_plater ? m_plater->get_partplate_list().get_curr_plate() : nullptr; const GCodeProcessorResult* slice_result = plate ? plate->get_slice_result() : nullptr; if (m_print_type == FROM_NORMAL && slice_result) { if (slice_result->print_statistics.modes.empty()) { return; } const float sliced_seconds = slice_result->print_statistics.modes[0].time; if (support_filament_mapping_estimate()) { refresh_print_time_ams_mapping(obj, slice_result, sliced_seconds); } else { refresh_print_time_normal(obj, sliced_seconds); update_time_estimate_tip(false); } } else { update_time_estimate_tip(false); m_saveTimeText->Hide(); } } void SelectMachineDialog::set_print_time_display(float sliced_seconds, float display_seconds) { const wxString sliced_label = wxString::Format("%s", short_time(get_time_dhms(sliced_seconds))); const wxString display_label = wxString::Format("%s", short_time(get_time_dhms(display_seconds))); m_stext_time->SetLabel(display_label); update_time_estimate_tip(display_label != sliced_label); } void SelectMachineDialog::refresh_print_time_normal(MachineObject* obj, float sliced_seconds) { float display_seconds = sliced_seconds; const auto save_time = get_filament_change_gap_time(obj); if (save_time.has_value()) { display_seconds += save_time.value(); if (display_seconds < 0) display_seconds = 0; } set_print_time_display(sliced_seconds, display_seconds); update_save_time_hint(obj, save_time); } void SelectMachineDialog::refresh_print_time_ams_mapping(MachineObject* obj, const GCodeProcessorResult* slice_result, float sliced_seconds) { float display_seconds = sliced_seconds; if (PresetBundle* preset_bundle = wxGetApp().preset_bundle) { const auto& printer_config = preset_bundle->printers.get_edited_preset().config; const auto actual_ams_types = build_actual_ams_type_per_filament(m_ams_mapping_result, obj); const auto recalc_result = MultiNozzleUtils::estimate_mapped_print_time( slice_result, &printer_config, actual_ams_types); if (recalc_result.reason == MultiNozzleUtils::FailReason::Ok) display_seconds = std::max(0.f, recalc_result.total_seconds); } set_print_time_display(sliced_seconds, display_seconds); m_saveTimeText->Hide(); } void SelectMachineDialog::update_save_time_hint(MachineObject* obj, const std::optional& save_time) { bool is_all_at_suggest_pos = true; for (const auto& mapping_item : m_ams_mapping_result) { is_all_at_suggest_pos = is_at_suggested_pos(obj, mapping_item.id); if (!is_all_at_suggest_pos) break; } if (save_time.has_value() && save_time.value() >= 1 && !is_all_at_suggest_pos) { wxString text = wxString::Format(_L("Recommended filament arrangement saves %s->"), FormatTime(*save_time)); m_saveTimeText->SetLabel(text); m_saveTimeText->Wrap(-1); m_saveTimeText->Show(); m_basic_panel->Layout(); m_basic_panel->Fit(); } else { m_saveTimeText->Hide(); } } bool SelectMachineDialog::is_ams_drying(MachineObject* obj) { const auto& ams_list = obj->GetFilaSystem()->GetAmsList(); for (auto ams = ams_list.begin(); ams != ams_list.end(); ams++) { if (ams->second->AmsIsDrying()) { return true; } } return false; } bool SelectMachineDialog::is_selected_ams_drying(MachineObject* obj) { if (!obj) return false; // If a UI material is selected, only when that material is mapped to an AMS // and that AMS is currently drying. for (const auto& kv : m_materialList) { Material* mat = kv.second; if (!mat || !mat->item) continue; if (!mat->item->m_selected) continue; // find mapping entry for this material id for (const FilamentInfo& f : m_ams_mapping_result) { if (f.id != mat->id) continue; if (f.ams_id.empty()) return false; auto fila_system = obj->GetFilaSystem(); if (!fila_system) return false; DevAms* dev_ams = fila_system->GetAmsById(f.ams_id); return (dev_ams && dev_ams->AmsIsDrying()); } return false; } return false; } bool SelectMachineDialog::has_bowden_extuder(MachineObject* obj) { if (!obj) return false; Preset* preset = get_printer_preset(obj); if (!preset) return false; auto exrtuder_type_opt = dynamic_cast(preset->config.option("extruder_type")); for (int i = 0; i < exrtuder_type_opt->values.size(); i++) { ExtruderType extruder_type = (ExtruderType)exrtuder_type_opt->values[i]; if (extruder_type == ExtruderType::etBowden) return true; } return false; } void SelectMachineDialog::prepare(int print_plate_idx) { m_print_plate_idx = print_plate_idx; } void SelectMachineDialog::update_print_status_msg() { bool is_ams_update = m_statictext_ams_msg->UpdateInfos(m_pre_print_checker.filamentList); bool is_printer_update = m_text_printer_msg->UpdateInfos(m_pre_print_checker.printerList); if (is_printer_update || is_ams_update) { Layout(); Fit(); } } void SelectMachineDialog::update_print_error_info(int code, std::string msg, std::string extra) { m_print_error_code = code; m_print_error_msg = msg; m_print_error_extra = extra; } bool SelectMachineDialog::check_sdcard_for_timelpase(MachineObject* obj) { if (!obj) return false; // must set to a status if return true if (m_checkbox_list["timelapse"]->IsShown() && m_checkbox_list["timelapse"]->getValue() == "on") { if (obj->GetStorage()->get_sdcard_state() == DevStorage::SdcardState::NO_SDCARD && !obj->m_has_timelapse_kit && !obj->is_support_internal_timelapse) { show_status(PrintDialogStatus::PrintStatusTimelapseNoSdcard); return true; } } return false; } void SelectMachineDialog::show_status(PrintDialogStatus status, std::vector params, wxString wiki_url, prePrintInfoStyle style) { wxString msg; wxString tips; if ( params.size() > 0 ) { msg = params[0]; } else if (params.size() > 1) { tips = params[1]; } if (m_print_status != status) { BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ": update_status: " << status << "(" << PrePrintChecker::get_print_status_info(status) << ")"; } m_print_status = status; // all message if (status == PrintDialogStatus::PrintStatusInit) { Enable_Refresh_Button(true); Enable_Send_Button(false); } else if (status == PrintDialogStatus::PrintStatusNoUserLogin) { Enable_Refresh_Button(true); Enable_Send_Button(false); } else if (status == PrintDialogStatus::PrintStatusReadyToGo) { Enable_Refresh_Button(true); Enable_Send_Button(true); } else if (status == PrintDialogStatus::PrintStatusInvalidPrinter) { Enable_Refresh_Button(true); Enable_Send_Button(false); } else if (status == PrintDialogStatus::PrintStatusConnectingServer) { Enable_Refresh_Button(true); Enable_Send_Button(false); } else if (status == PrintDialogStatus::PrintStatusReading) { Enable_Refresh_Button(true); Enable_Send_Button(false); } else if (status == PrintDialogStatus::PrintStatusReadingTimeout) { Enable_Refresh_Button(true); Enable_Send_Button(false); } else if (status == PrintDialogStatus::PrintStatusModeNotFDM) { Enable_Refresh_Button(true); Enable_Send_Button(false); } else if (status == PrintDialogStatus::PrintStatusInUpgrading) { Enable_Refresh_Button(true); Enable_Send_Button(false); } else if (status == PrintDialogStatus::PrintStatusInSystemPrinting) { Enable_Refresh_Button(true); Enable_Send_Button(false); } else if (status == PrintDialogStatus::PrintStatusInPrinting) { Enable_Refresh_Button(true); Enable_Send_Button(false); } else if (status == PrintDialogStatus::PrintStatusAmsOnSettingup) { Enable_Refresh_Button(true); Enable_Send_Button(false); } else if (status == PrintDialogStatus::PrintStatusAmsMappingInvalid) { Enable_Refresh_Button(true); Enable_Send_Button(false); } else if (status == PrintDialogStatus::PrintStatusAmsMappingMixInvalid) { Enable_Refresh_Button(true); Enable_Send_Button(false); } else if (status == PrintDialogStatus::PrintStatusNozzleDataInvalid) { Enable_Refresh_Button(true); Enable_Send_Button(false); } else if (status == PrintDialogStatus::PrintStatusNozzleMatchInvalid) { Enable_Refresh_Button(true); Enable_Send_Button(false); } else if (status == PrintStatusNozzleDiameterMismatch) { Enable_Refresh_Button(true); Enable_Send_Button(false); } else if (status == PrintStatusNozzleHRCMismatch) { Enable_Refresh_Button(true); Enable_Send_Button(true); } else if (status == PrintStatusColorQuantityExceed) { if (params.size() > 0) { msg = wxString::Format(_L("The current firmware supports a maximum of %s materials. You can either reduce the number of materials to %s or fewer on the Preparation Page, or try updating the firmware. If you are still restricted after the update, please wait for subsequent firmware support."), params[0], params[0]); } Enable_Refresh_Button(true); Enable_Send_Button(false); } else if (status == PrintDialogStatus::PrintStatusAmsMappingU0Invalid) { wxString msg_text; if (params.size() > 1) msg_text = wxString::Format(_L("Filament %s does not match the filament in AMS slot %s. Please update the printer firmware to support AMS slot assignment."), params[0], params[1]); else msg_text = _L("Filament does not match the filament in AMS slot. Please update the printer firmware to support AMS slot assignment."); msg = msg_text; Enable_Refresh_Button(true); Enable_Send_Button(false); } else if (status == PrintDialogStatus::PrintStatusRefreshingMachineList) { Enable_Refresh_Button(false); Enable_Send_Button(false); } else if (status == PrintDialogStatus::PrintStatusSending) { Enable_Refresh_Button(false); Enable_Send_Button(false); sending_mode(); } else if (status == PrintDialogStatus::PrintStatusLanModeNoSdcard) { Enable_Refresh_Button(true); Enable_Send_Button(false); } else if (status == PrintDialogStatus::PrintStatusLanModeSDcardNotAvailable) { Enable_Refresh_Button(true); Enable_Send_Button(false); } else if (status == PrintDialogStatus::PrintStatusNoSdcard) { Enable_Refresh_Button(true); Enable_Send_Button(false); }else if (status == PrintDialogStatus::PrintStatusUnsupportedPrinter) { wxString msg_text; try { DeviceManager* dev = Slic3r::GUI::wxGetApp().getDeviceManager(); if (!dev) return; //source print MachineObject* obj_ = dev->get_selected_machine(); if (obj_ == nullptr) return; auto sourcet_print_name = obj_->get_printer_type_display_str(); sourcet_print_name.Replace(wxT("Bambu Lab "), wxEmptyString); //target print std::string target_model_id; if (m_print_type == PrintFromType::FROM_NORMAL){ PresetBundle* preset_bundle = wxGetApp().preset_bundle; target_model_id = preset_bundle->printers.get_edited_preset().get_printer_type(preset_bundle); } else if (m_print_type == PrintFromType::FROM_SDCARD_VIEW) { if (m_required_data_plate_data_list.size() > 0) { target_model_id = m_required_data_plate_data_list[m_print_plate_idx]->printer_model_id; } } auto target_print_name = wxString(DevPrinterConfigUtil::get_printer_display_name(target_model_id)); target_print_name.Replace(wxT("Bambu Lab "), wxEmptyString); msg_text = wxString::Format(_L("The selected printer (%s) is incompatible with the print file configuration (%s). Please adjust the printer preset in the prepare page or choose a compatible printer on this page."), sourcet_print_name, target_print_name); msg = msg_text; Enable_Refresh_Button(true); Enable_Send_Button(false); } catch (...) { Enable_Refresh_Button(true); Enable_Send_Button(false); } } else if (status == PrintDialogStatus::PrintStatusTimelapseNoSdcard) { //this warning Enable_Refresh_Button(true); Enable_Send_Button(true); } else if (status == PrintDialogStatus::PrintStatusNeedForceUpgrading) { Enable_Refresh_Button(true); Enable_Send_Button(false); } else if (status == PrintDialogStatus::PrintStatusNeedConsistencyUpgrading) { Enable_Refresh_Button(true); Enable_Send_Button(false); } else if (status == PrintDialogStatus::PrintStatusBlankPlate) { Enable_Refresh_Button(true); Enable_Send_Button(false); } else if (status == PrintDialogStatus::PrintStatusNotSupportedPrintAll) { Enable_Refresh_Button(true); Enable_Send_Button(false); } else if (status == PrintDialogStatus::PrintStatusTimelapseWarning) { //this is warning wxString msg_text; PartPlate *plate = m_plater->get_partplate_list().get_curr_plate(); for (auto warning : plate->get_slice_result()->warnings) { if (warning.msg == NOT_GENERATE_TIMELAPSE) { if (warning.error_code == "10014001") { msg_text = _L("When enabling spiral vase mode, machines with I3 structure will not generate timelapse videos."); } else if (warning.error_code == "10014002") { msg_text = _L("The current printer does not support timelapse in Instant Mode when printing By-Object."); } } } msg = msg_text; Enable_Refresh_Button(true); Enable_Send_Button(true); } else if (status == PrintDialogStatus::PrintStatusTimelapseStorageLow) { Enable_Refresh_Button(true); Enable_Send_Button(true); } else if (status == PrintStatusToolHeadCoolingFanWarning) { Enable_Refresh_Button(true); Enable_Send_Button(true); } else if (status == PrintStatusMixAmsAndVtSlotWarning) { Enable_Refresh_Button(true); Enable_Send_Button(true); } else if (status == PrintStatusTPUUnsupportAutoCali) { Enable_Refresh_Button(true); Enable_Send_Button(false); } else if (status == PrintStatusHasFilamentInBlackListError) { Enable_Refresh_Button(true); Enable_Send_Button(false); } else if (status == PrintStatusWarningKvalueNotUsed) { Enable_Refresh_Button(true); Enable_Send_Button(true); } else if (status == PrintStatusHasFilamentInBlackListWarning) { Enable_Refresh_Button(true); Enable_Send_Button(true); } else if (status == PrintStatusFilamentWarningHighChamberTempCloseDoor || status == PrintStatusFilamentWarningHighChamberTemp) { Enable_Refresh_Button(true); Enable_Send_Button(true); } else if (status == PrintDialogStatus::PrintStatusRackReading || status == PrintDialogStatus::PrintStatusNozzleNoMatchedHotends || status == PrintDialogStatus::PrintStatusNozzleRackMaximumInstalled || status == PrintDialogStatus::PrintStatusRackNozzleMappingWaiting) { Enable_Refresh_Button(true); Enable_Send_Button(false); } else if (status == PrintDialogStatus::PrintStatusFilamentWarningHighChamberTempSoft || status == PrintDialogStatus::PrintStatusFilamentWarningUnknownHighChamberTempSoft || status == PrintDialogStatus::PrintStatusHasUnreliableNozzleWarning || status == PrintDialogStatus::PrintStatusRackNozzleNumUnmeetWarning) { Enable_Refresh_Button(true); Enable_Send_Button(true); } else if (status == PrintStatusFilamentWarningRemainNotEnough) { Enable_Refresh_Button(true); Enable_Send_Button(true); } else if (status == PrintStatusSmartNozzleBlobNeedAuto) { // Non-blocking advisory: user can still send the print job. Enable_Refresh_Button(true); Enable_Send_Button(true); } /*enter perpare mode*/ prepare_mode(false); m_pre_print_checker.add(status, msg, tips, wiki_url, style); } void SelectMachineDialog::init_timer() { m_refresh_timer = new wxTimer(); m_refresh_timer->SetOwner(this); } void SelectMachineDialog::on_cancel(wxCloseEvent &event) { if (m_mapping_popup.IsShown()) m_mapping_popup.Dismiss(); if (m_print_job) { if (m_print_job->is_running()) { m_print_job->cancel(); m_print_job->join(); } } this->EndModal(wxID_CANCEL); } bool SelectMachineDialog::is_blocking_printing(MachineObject* obj_) { DeviceManager* dev = Slic3r::GUI::wxGetApp().getDeviceManager(); if (!dev) return true; auto target_model = obj_->printer_type; std::string source_model = ""; if (m_print_type == PrintFromType::FROM_NORMAL) { PresetBundle* preset_bundle = wxGetApp().preset_bundle; source_model = preset_bundle->printers.get_edited_preset().get_printer_type(preset_bundle); }else if (m_print_type == PrintFromType::FROM_SDCARD_VIEW) { if (m_required_data_plate_data_list.size() > 0) { source_model = m_required_data_plate_data_list[m_print_plate_idx]->printer_model_id; } } if (source_model != target_model) { std::vector compatible_machine = obj_->get_compatible_machine(); vector::iterator it = find(compatible_machine.begin(), compatible_machine.end(), source_model); if (it == compatible_machine.end()) { return true; } } return false; } static std::unordered_set _get_used_nozzle_idxes() { std::unordered_set used_nozzle_idxes; DeviceManager *dev = Slic3r::GUI::wxGetApp().getDeviceManager(); if (dev) { MachineObject *obj_ = dev->get_selected_machine(); if (obj_) { try { PresetBundle *preset_bundle = wxGetApp().preset_bundle; PartPlate *cur_plate = wxGetApp().plater()->get_partplate_list().get_curr_plate(); auto used_filament_idxs = cur_plate->get_used_filaments(); /*the index is started from 1*/ for (int used_filament_idx : used_filament_idxs) { int used_nozzle_idx = cur_plate->get_physical_extruder_by_filament_id(preset_bundle->full_config(), used_filament_idx); used_nozzle_idxes.insert(used_nozzle_idx); } } catch (const std::exception &) {} } } return used_nozzle_idxes; } bool SelectMachineDialog::is_nozzle_hrc_matched(const NozzleType& nozzle_type, const std::string& filament_id) const { auto printer_nozzle_hrc = Print::get_hrc_by_nozzle_type(nozzle_type); auto preset_bundle = wxGetApp().preset_bundle; auto filament_nozzle_hrc = preset_bundle->get_required_hrc_by_filament_id(filament_id); return abs(printer_nozzle_hrc) >= abs(filament_nozzle_hrc); } bool SelectMachineDialog::is_same_printer_model() { bool result = true; DeviceManager* dev = Slic3r::GUI::wxGetApp().getDeviceManager(); if (!dev) return result; MachineObject* obj_ = dev->get_selected_machine(); assert(obj_->get_dev_id() == m_printer_last_select); if (obj_ == nullptr) { return result; } PresetBundle* preset_bundle = wxGetApp().preset_bundle; if (preset_bundle && preset_bundle->printers.get_edited_preset().get_printer_type(preset_bundle) != obj_->printer_type) { if ((obj_->is_support_upgrade_kit && obj_->installed_upgrade_kit) && (preset_bundle->printers.get_edited_preset().get_printer_type(preset_bundle) == "C12")) { return true; } BOOST_LOG_TRIVIAL(info) << "printer_model: source = " << preset_bundle->printers.get_edited_preset().get_printer_type(preset_bundle); BOOST_LOG_TRIVIAL(info) << "printer_model: target = " << obj_->printer_type; return false; } if (obj_->is_support_upgrade_kit && obj_->installed_upgrade_kit) { BOOST_LOG_TRIVIAL(info) << "printer_model: source = " << preset_bundle->printers.get_edited_preset().get_printer_type(preset_bundle); BOOST_LOG_TRIVIAL(info) << "printer_model: target = " << obj_->printer_type << " (plus)"; return false; } return true; } void SelectMachineDialog::show_errors(wxString &info) { ConfirmBeforeSendDialog confirm_dlg(this, wxID_ANY, _L("Errors")); confirm_dlg.update_text(info); confirm_dlg.on_show(); } void SelectMachineDialog::on_reselect_dialog_btn_clicked(wxMouseEvent&) { BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ": on_reselect_dialog_btn_clicked"; if (m_best_pos_dialog == nullptr) { m_best_pos_dialog = new ReselectMachineDialog(static_cast(this)); } DeviceManager* dev = Slic3r::GUI::wxGetApp().getDeviceManager(); if (!dev) return; MachineObject* obj = dev->get_selected_machine(); if (!obj) return; auto save_time = get_filament_change_gap_time(obj); wxString text{}; if (save_time.has_value() && save_time.value() >= 1) { text = FormatTime(*save_time); } //key: logicID value: posID std::map best_pos_map; for (const auto& slot : m_ams_mapping_result) { if (!is_at_suggested_pos(obj, slot.id)) { auto pos = get_filament_suggest_pos(obj, slot.id); if (pos.has_value()) { best_pos_map[slot.id] = pos.value(); } } } m_best_pos_dialog->Update(obj, best_pos_map, m_ams_mapping_result, text); m_best_pos_dialog->ShowModal(); } void SelectMachineDialog::on_ok_btn(wxCommandEvent &event) { BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ": on_ok_btn"; bool has_slice_warnings = false; bool is_printing_block = false; DeviceManager* dev = Slic3r::GUI::wxGetApp().getDeviceManager(); if (!dev) return; MachineObject* obj_ = dev->get_selected_machine(); if (!obj_) return; std::vector confirm_text; // check more than one using in same external spool std::unordered_set main_external_spool_filas; std::unordered_set deputy_external_spool_filas; for (const auto& mapping_info : m_ams_mapping_result) { if (mapping_info.ams_id == VIRTUAL_AMS_MAIN_ID_STR){ main_external_spool_filas.insert(mapping_info.filament_id); } else if (mapping_info.ams_id == VIRTUAL_AMS_DEPUTY_ID_STR) { deputy_external_spool_filas.insert(mapping_info.filament_id); } } if (main_external_spool_filas.size() > 1 || deputy_external_spool_filas.size() > 1) { if(!m_ext_change_assist ){ confirm_text.push_back(ConfirmBeforeSendInfo(_L("More than one filament types have been mapped to the same external spool, which may cause printing issues. The printer won't pause during printing."))); has_slice_warnings = true; } } else { //check filaments type in external spool for (const auto& mapping_info : m_ams_mapping_result) { if (mapping_info.ams_id != VIRTUAL_AMS_MAIN_ID_STR && mapping_info.ams_id != VIRTUAL_AMS_DEPUTY_ID_STR) { continue; } const auto& mapped_fila_type = obj_->get_filament_type(mapping_info.ams_id, mapping_info.slot_id); if (!mapped_fila_type.empty() && (mapped_fila_type != mapping_info.type)) { confirm_text.push_back(ConfirmBeforeSendInfo(_L("The filament type setting of external spool is different from the filament in the slicing file."))); has_slice_warnings = true; break; } } } //Check Printer Model Id bool is_same_printer_type = is_same_printer_model(); if (!is_same_printer_type && (m_print_type == PrintFromType::FROM_NORMAL)) { confirm_text.push_back(ConfirmBeforeSendInfo(_L("The printer type selected when generating G-Code is not consistent with the currently selected printer. It is recommended that you use the same printer type for slicing."))); has_slice_warnings = true; } //check blacklist, using physical filament info for (const auto& fila : m_ams_mapping_result) { DevFilaBlacklist::CheckFilamentInfo check_info; check_info.dev_id = obj_->get_dev_id(); check_info.model_id = obj_->printer_type; check_info.ams_id = fila.get_ams_id(); check_info.slot_id = fila.get_slot_id(); auto tray_opt = obj_->get_tray(std::to_string(check_info.ams_id), std::to_string(check_info.slot_id)); if (!tray_opt.has_value()) { assert(false); continue; } check_info.fila_id = tray_opt->get_filament_id(); check_info.fila_type = tray_opt->get_filament_type(); auto option = GUI::wxGetApp().preset_bundle->get_filament_by_filament_id(check_info.fila_id); check_info.fila_name = option ? option->filament_name : ""; check_info.fila_vendor = option ? option->vendor : ""; check_info.has_filament_switch = obj_->GetFilaSwitch()->IsInstalled(); // usage of filament if (m_print_type == PrintFromType::FROM_NORMAL) { if (GCodeProcessorResult* gcode_result = m_plater->background_process().get_current_gcode_result()) { for (auto used_fila : gcode_result->used_filaments) { if (used_fila.filament_id == fila.id) { check_info.used_for_print_support = used_fila.use_for_support; check_info.used_for_print_object = used_fila.use_for_object; break; } }; } }; const auto& nozzle_map = get_mapped_nozzles(fila.id); for (const auto& [pos_id, nozzle] : nozzle_map) { if (nozzle.IsEmpty()) { continue; }; check_info.extruder_id = nozzle.GetExtruderId(); check_info.nozzle_flow = DevNozzle::GetNozzleFlowTypeString(nozzle.GetNozzleFlowType()); check_info.nozzle_diameter = nozzle.GetNozzleDiameter(); // here are only warnings level, the errors have been blocked before const auto& result = DevFilaBlacklist::check_filaments_in_blacklist(check_info); if (const auto& warning_items = result.get_items_by_action("warning"); !warning_items.empty()) { for (auto item : warning_items) { confirm_text.push_back(ConfirmBeforeSendInfo(item.info_msg, item.wiki_url)); has_slice_warnings = true; } } } } PartPlate* plate = m_plater->get_partplate_list().get_curr_plate(); bool has_show_traditional_timelapse_waring = false; for (auto warning : plate->get_slice_result()->warnings) { if (warning.msg == NOT_SUPPORT_TRADITIONAL_TIMELAPSE) { if (!has_show_traditional_timelapse_waring && (m_checkbox_list["timelapse"]->getValue() == "on")) { confirm_text.push_back(ConfirmBeforeSendInfo(Plater::get_slice_warning_string(warning))); has_show_traditional_timelapse_waring = true; has_slice_warnings = true; } } else if (warning.msg == SMOOTH_TIMELAPSE_WITHOUT_PRIME_TOWER) { if (m_checkbox_list["timelapse"]->getValue() == "on") { confirm_text.push_back(ConfirmBeforeSendInfo(Plater::get_slice_warning_string(warning))); has_slice_warnings = true; } } else if (warning.msg == NOT_GENERATE_TIMELAPSE) { continue; } else if(warning.msg == NOZZLE_HRC_CHECKER){ wxString error_info = Plater::get_slice_warning_string(warning); if (error_info.IsEmpty()) { error_info = wxString::Format("%s\n", warning.msg); } confirm_text.push_back(ConfirmBeforeSendInfo(error_info)); has_slice_warnings = true; } } //check for unidentified material for (const auto& fila : m_ams_mapping_result) { if (devPrinterUtil::IsVirtualSlot(fila.ams_id)) { continue;//STUDIO-16294 } auto tray_opt = obj_->get_tray(fila.ams_id, fila.slot_id); if (tray_opt.has_value() && (!tray_opt->is_exists || !tray_opt->is_tray_info_ready())) { has_slice_warnings = true; //has_unknown_filament confirm_text.push_back(ConfirmBeforeSendInfo(_L("There are some unknown filaments in the AMS mappings. Please check whether they are the required filaments. If they are okay, press \"Confirm\" to start printing."))); break; } } if (has_slice_warnings) { ConfirmBeforeSendDialog confirm_dlg(this, wxID_ANY, _L("Warning")); if (is_printing_block) { confirm_dlg.hide_button_ok(); confirm_dlg.edit_cancel_button_txt(_L("Close"), true); } confirm_dlg.Bind(EVT_SECONDARY_CHECK_CONFIRM, [this, &confirm_dlg](wxCommandEvent& e) { confirm_dlg.on_hide(); BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ": confirm_dlg to send print job after warnings."; this->on_send_print(); }); // STUDIO-9580 /* use warning color if there are warning and normal messages* / /* use indexes if there are several messages*/ /* add header and ending if there are several messages or has none block warnings*/ if (confirm_text.size() > 1 || !is_printing_block) { bool has_warning_msg = false; bool has_normal_msg = false; if (confirm_text.size() > 1) { std::unordered_set seen_texts; std::vector unique_texts; for (const auto &item : confirm_text) { if (seen_texts.insert(item.text).second) { unique_texts.push_back(item); } } for (size_t i = 0; i < unique_texts.size(); ++i) { unique_texts[i].text = wxString::Format("%d. %s", i + 1, unique_texts[i].text); if (unique_texts[i].level == ConfirmBeforeSendInfo::InfoLevel::Warning) has_warning_msg = true; else has_normal_msg = true; } confirm_text.swap(unique_texts); } std::vector shown_infos; shown_infos.emplace_back(ConfirmBeforeSendInfo(_L("Please check the following:"))); for (const auto& item : confirm_text) { shown_infos.emplace_back(item); } if (is_printing_block) { shown_infos.emplace_back(ConfirmBeforeSendInfo(_L("Please fix the error above, otherwise printing cannot continue."), wxEmptyString, ConfirmBeforeSendInfo::InfoLevel::Warning)); } else { shown_infos.emplace_back(ConfirmBeforeSendInfo(_L("Please click the \"Continue printing\" button if you still want to proceed with printing."))); } confirm_dlg.update_text(shown_infos, has_warning_msg&& has_normal_msg); } else { confirm_dlg.update_text(confirm_text, false); } if (!is_printing_block) { confirm_dlg.update_btn_label(_L("Continue printing"), _L("Return to modify")); } confirm_dlg.on_show(); BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ": confirm_dlg on_show."; } else { // if machine supports internal timelapse and timelapse is on, check storage first if (obj_->is_support_internal_timelapse && m_checkbox_list["timelapse"]->IsShown() && m_checkbox_list["timelapse"]->getValue() == "on" && !m_timelapse_storage.empty()) { start_timelapse_storage_check(obj_); } else { this->on_send_print(); } } } wxString SelectMachineDialog::format_steel_name(NozzleType type) { if (type == NozzleType::ntHardenedSteel) { return _L("Hardened Steel"); } else if (type == NozzleType::ntStainlessSteel) { return _L("Stainless Steel"); } else if (type == NozzleType::ntTungstenCarbide) { return _L("Tungsten Carbide"); } return _L("Unknown"); } void SelectMachineDialog::EnableEditing(bool enable) { /*project*/ m_rename_button->Enable(enable); /*printer*/ m_printer_box->EnableEditing(enable); /*mapping*/ for (size_t i = 0; i < m_sizer_ams_mapping_left->GetItemCount(); ++i) { wxSizerItem *item = m_sizer_ams_mapping_left->GetItem(i); if (item) {item->GetWindow()->Enable(enable);} } for (size_t i = 0; i < m_sizer_ams_mapping_right->GetItemCount(); ++i) { wxSizerItem *item = m_sizer_ams_mapping_right->GetItem(i); if (item) {item->GetWindow()->Enable(enable);} } for (size_t i = 0; i < m_sizer_ams_mapping->GetItemCount(); ++i) { wxSizerItem *item = m_sizer_ams_mapping->GetItem(i); if (item) { item->GetWindow()->Enable(enable); } } /*mapping link*/ //m_link_edit_nozzle->Enable(enable); /*options*/ for (auto iter : m_checkbox_list) { iter.second->enable(enable); } enable ? m_saveTimeText->Enable() : m_saveTimeText->Disable(); } /*content height > FromDIP(650), make the area scrollable*/ /*content height < FromDIP(650), make the area size as the content*/ void SelectMachineDialog::update_scroll_area_size() { wxSize new_size(FromDIP(700), -1); int height = m_scroll_area->GetSizer()->CalcMin().GetHeight(); if (height < FromDIP(650)) { new_size.SetHeight(height); } else { new_size.SetHeight(FromDIP(650)); } if (m_scroll_area->GetSize() != new_size) { m_scroll_area->SetMaxSize(new_size); m_scroll_area->SetMinSize(new_size); Layout(); Fit(); } } void SelectMachineDialog::update_options_layout() { std::unordered_set shown_options; const wxSizerItemList& children = m_sizer_options->GetChildren(); for (auto item : children) { if (item && item->IsWindow()) { shown_options.insert(item->GetWindow()); } } std::unordered_set toshow_options; for (auto option : m_checkbox_list_order) { if (option->IsShown()) { toshow_options.insert(option); } } /*To show changed, update layout*/ if (shown_options != toshow_options) { m_sizer_options->Clear(); for (auto option : m_checkbox_list_order) { if (option->IsShown()) { m_sizer_options->Add(option, 0, wxEXPAND); } } } } void SelectMachineDialog::update_option_opts(MachineObject *obj) { if (!obj) { for (auto opt : m_checkbox_list) { opt.second->Hide(); } update_options_layout(); return; } /*timelapse*/ m_checkbox_list["timelapse"]->Show(); if (obj->is_support_internal_timelapse) { m_timelapse_folder_btn->Show(); if (m_timelapse_storage.empty()) { m_timelapse_storage = "internal"; } update_timelapse_folder_btn_icon(); } else { m_timelapse_folder_btn->Hide(); m_timelapse_storage.clear(); } /*bed_leveling*/ if (obj->is_support_bed_leveling == 2) { m_checkbox_list["bed_leveling"]->Show(); m_checkbox_list["bed_leveling"]->update_options(ops_auto, _L("This checks the flatness of heatbed. Leveling makes extruded height uniform.\n*Automatic mode: Run a " "leveling check(about 10 seconds). Skip if surface is fine.")); } else if (obj->is_support_bed_leveling == 1) { m_checkbox_list["bed_leveling"]->Show(); m_checkbox_list["bed_leveling"]->update_options(ops_no_auto, _L("This checks the flatness of heatbed. Leveling makes extruded height uniform.")); } else { m_checkbox_list["bed_leveling"]->Hide(); } /*nozzle_offset_cali*/ bool support_cali_nozzle_offset = obj->GetConfig()->SupportCalibrationNozzleOffset(); if (support_cali_nozzle_offset) { m_checkbox_list["nozzle_offset_cali"]->update_options(ops_auto, _L("Calibrate nozzle offsets to enhance print quality.\n*Automatic mode: Check for calibration before printing. Skip if unnecessary.")); } m_checkbox_list["nozzle_offset_cali"]->Show(support_cali_nozzle_offset); /*flow_cali*/ if (obj->is_support_pa_calibration) { if (obj->GetConfig()->SupportCalibrationPA_FlowAuto() && can_support_pa_auto_cali()) { m_checkbox_list["flow_cali"]->update_options(ops_auto, _L("This process determines the dynamic flow values to improve overall print quality.\n*Automatic mode: Skip if the filament was calibrated recently.")); } else { m_checkbox_list["flow_cali"]->update_options(ops_no_auto, _L("This process determines the dynamic flow values to improve overall print quality.")); } } m_checkbox_list["flow_cali"]->Show(obj->is_support_pa_calibration); update_options_layout(); } static bool _HasExt(const std::vector &ams_mapping_result) { if (ams_mapping_result.empty()) { return true; }; for (const auto &info : ams_mapping_result) { if (info.ams_id == VIRTUAL_AMS_MAIN_ID_STR || info.ams_id == VIRTUAL_AMS_DEPUTY_ID_STR && !info.ams_id.empty()) { return true; } } return false; } static bool _HasAms(const std::vector &ams_mapping_result) { for (const auto &info : ams_mapping_result) { if (info.ams_id != VIRTUAL_AMS_MAIN_ID_STR && info.ams_id != VIRTUAL_AMS_DEPUTY_ID_STR && !info.ams_id.empty()) { return true; } } return false; } bool SelectMachineDialog::is_enable_external_change_assist(std::vector& ams_mapping_result) { // if (_HasAms(ams_mapping_result)) return false; std::map v_ams_map; v_ams_map[VIRTUAL_AMS_MAIN_ID_STR] = 0; v_ams_map[VIRTUAL_AMS_DEPUTY_ID_STR] = 0; for(auto info : m_ams_mapping_result){ v_ams_map[info.ams_id]++; } return (v_ams_map[VIRTUAL_AMS_MAIN_ID_STR] > 1) || (v_ams_map[VIRTUAL_AMS_DEPUTY_ID_STR] > 1); } void SelectMachineDialog::timelapse_button_click() { wxDialog dlg(nullptr, wxID_ANY, _L("Timelapse"), wxDefaultPosition, wxDefaultSize, wxDEFAULT_DIALOG_STYLE); dlg.SetBackgroundColour(*wxWHITE); std::string icon_path = (boost::format("%1%/images/BambuStudioTitle.ico") % resources_dir()).str(); dlg.SetIcon(wxIcon(encode_path(icon_path.c_str()), wxBITMAP_TYPE_ICO)); wxBoxSizer* main_sizer = new wxBoxSizer(wxVERTICAL); auto* message = new Label(&dlg, _L("After enabling Timelapse, regardless of whether Instant or Smooth mode is selected, the printer will move the build plate and toolhead before each shot, which may increase the overall print time.")); message->SetFont(Label::Body_14); message->Wrap(FromDIP(360)); main_sizer->Add(message, 0, wxALL, FromDIP(20)); auto timelapse_url = wxString::Format(L"https://wiki.bambulab.com/%s/software/bambu-studio/Timelapse", wxGetApp().current_language_code_safe() == "zh_CN" ? "zh" : "en"); wxHyperlinkCtrl* learn_more = new wxHyperlinkCtrl(&dlg, wxID_ANY, _L("Learn more"), timelapse_url, wxDefaultPosition, wxDefaultSize, wxHL_DEFAULT_STYLE); main_sizer->Add(learn_more, 0, wxLEFT | wxRIGHT | wxBOTTOM, FromDIP(20)); wxBoxSizer* button_sizer = new wxBoxSizer(wxHORIZONTAL); button_sizer->AddStretchSpacer(); wxButton* ok_button = new wxButton(&dlg, wxID_OK, _L("OK"), wxDefaultPosition, wxDefaultSize); button_sizer->Add(ok_button, 0, wxALL, FromDIP(5)); main_sizer->Add(button_sizer, 0, wxEXPAND | wxLEFT | wxRIGHT | wxBOTTOM, FromDIP(15)); dlg.SetSizer(main_sizer); dlg.Fit(); dlg.CenterOnParent(); dlg.ShowModal(); } void SelectMachineDialog::update_timelapse_folder_btn_icon() { if (!m_timelapse_folder_btn) return; // always restore to normal (grey) �� active state is managed by popup open/close m_timelapse_folder_btn->SetBitmap(create_scaled_bitmap("folder-closed", m_timelapse_folder_btn, 16)); m_timelapse_folder_btn->Refresh(); } void SelectMachineDialog::show_timelapse_folder_popup() { if (m_timelapse_storage_popup && m_timelapse_storage_popup->IsShown()) { m_timelapse_storage_popup->Dismiss(); return; } // build popup with rounded corners + light border m_timelapse_storage_popup = new PopupWindow(this, wxBORDER_NONE); m_timelapse_storage_popup->SetBackgroundColour(wxColour(0xF0, 0xF0, 0xF0)); m_timelapse_storage_popup->Bind(wxEVT_PAINT, [this](wxPaintEvent&) { wxPaintDC dc(m_timelapse_storage_popup); auto size = m_timelapse_storage_popup->GetSize(); dc.SetPen(wxPen(wxColour(0xCE, 0xCE, 0xCE))); dc.SetBrush(wxBrush(wxColour(0xF0, 0xF0, 0xF0))); dc.DrawRoundedRectangle(0, 0, size.x, size.y, FromDIP(8)); }); auto* panel = new wxPanel(m_timelapse_storage_popup, wxID_ANY); panel->SetBackgroundColour(wxColour(0xF0, 0xF0, 0xF0)); // horizontal layout: [ Internal] [External] auto* sizer = new wxBoxSizer(wxHORIZONTAL); DeviceManager* dev_popup = wxGetApp().getDeviceManager(); MachineObject* obj_popup = dev_popup ? dev_popup->get_selected_machine() : nullptr; bool has_sdcard = obj_popup && obj_popup->GetStorage()->get_sdcard_state() == DevStorage::SdcardState::HAS_SDCARD_NORMAL; // if external was previously selected but sdcard is now absent, fall back to internal if (!has_sdcard && m_timelapse_storage == "external") m_timelapse_storage = "internal"; // Reuse the themed RadioBox widget (radio_on / radio_off bitmaps) instead of the // native wxRadioButton: the selected state shows a clear filled dot with good // contrast on every platform, matching the storage selector in SendToPrinter. auto make_item = [&](const wxString& label, const std::string& val, bool enabled) { auto* radio = new RadioBox(panel); radio->SetValue(m_timelapse_storage == val); if (enabled) radio->Enable(); else radio->Disable(); auto* text = new Label(panel, Label::Body_14, label); text->SetForegroundColour(enabled ? wxColour(0x5C, 0x5C, 0x5C) : wxColour(0xAC, 0xAC, 0xAC)); if (enabled) { auto on_select = [this, val](wxMouseEvent&) { m_timelapse_storage = val; update_timelapse_folder_btn_icon(); if (m_timelapse_storage_popup) m_timelapse_storage_popup->Dismiss(); DeviceManager* dev = wxGetApp().getDeviceManager(); MachineObject* obj = dev ? dev->get_selected_machine() : nullptr; if (obj) check_timelapse_storage_warning(obj); }; radio->Bind(wxEVT_LEFT_DOWN, on_select); text->Bind(wxEVT_LEFT_DOWN, on_select); } sizer->Add(radio, 0, wxALIGN_CENTER_VERTICAL); sizer->Add(text, 0, wxALIGN_CENTER_VERTICAL | wxLEFT, FromDIP(6)); }; make_item(_L("Internal"), "internal", true); sizer->AddSpacer(FromDIP(16)); make_item(_L("External"), "external", has_sdcard); panel->SetSizer(sizer); panel->Fit(); auto* outer = new wxBoxSizer(wxVERTICAL); outer->Add(panel, 0, wxALL, FromDIP(10)); m_timelapse_storage_popup->SetSizer(outer); m_timelapse_storage_popup->Fit(); // restore normal icon when popup is dismissed m_timelapse_storage_popup->Bind(wxEVT_SHOW, [this](wxShowEvent& e) { if (!e.IsShown()) update_timelapse_folder_btn_icon(); e.Skip(); }); wxPoint pos = m_timelapse_folder_btn->ClientToScreen(wxPoint(0, m_timelapse_folder_btn->GetSize().GetHeight())); m_timelapse_storage_popup->Position(pos, wxSize(0, 0)); // switch to active icon before showing popup m_timelapse_folder_btn->SetBitmap(create_scaled_bitmap("folder-closed-active", m_timelapse_folder_btn, 16)); m_timelapse_folder_btn->Refresh(); m_timelapse_storage_popup->Popup(); } void SelectMachineDialog::check_timelapse_storage_warning(MachineObject* obj) { if (!obj || !obj->is_support_internal_timelapse) return; if (!m_checkbox_list["timelapse"]->IsShown()) return; if (m_checkbox_list["timelapse"]->getValue() != "on") return; if (m_timelapse_storage.empty()) return; if (obj->is_timelapse_storage_low(m_timelapse_storage)) { wxString storage_name = (m_timelapse_storage == "internal") ? _L("Internal") : _L("External"); wxString msg = wxString::Format( _L("%s space less than 10MB. Timelapse may not save properly. You can turn it off or"), storage_name); // show_status sets button state; then directly add with link callback show_status(PrintDialogStatus::PrintStatusTimelapseStorageLow); m_pre_print_checker.add_with_link( PrintDialogStatus::PrintStatusTimelapseStorageLow, msg, _L("Clean up files"), [this]() { navigate_to_timelapse_page(); }, prePrintInfoStyle::Default ); } } void SelectMachineDialog::start_timelapse_storage_check(MachineObject* obj) { if (!obj) { on_send_print(); return; } // get total layer count from slice result m_timelapse_total_layer = 0; if (m_print_type == PrintFromType::FROM_NORMAL) { PartPlate* plate = m_plater->get_partplate_list().get_curr_plate(); if (plate && plate->get_slice_result()) { const auto& modes = plate->get_slice_result()->print_statistics.modes; if (!modes.empty()) m_timelapse_total_layer = (int)modes[0].layers_times.size(); } } obj->timelapse_storage_check_done = false; obj->timelapse_storage_check_result = -1; obj->command_ipcam_check_timelapse_storage(m_timelapse_storage, m_timelapse_total_layer); // start polling timer m_timelapse_check_elapsed_ms = 0; if (!m_timelapse_check_timer) { m_timelapse_check_timer = new wxTimer(this); Bind(wxEVT_TIMER, &SelectMachineDialog::on_timelapse_storage_check_timer, this, m_timelapse_check_timer->GetId()); } m_timelapse_check_timer->Start(m_timelapse_check_interval_ms); } void SelectMachineDialog::on_timelapse_storage_check_timer(wxTimerEvent& /*event*/) { m_timelapse_check_elapsed_ms += m_timelapse_check_interval_ms; DeviceManager* dev = wxGetApp().getDeviceManager(); MachineObject* obj = dev ? dev->get_selected_machine() : nullptr; bool timed_out = m_timelapse_check_elapsed_ms >= m_timelapse_check_timeout_ms; bool done = obj && obj->timelapse_storage_check_done.load(); if (done || timed_out) { m_timelapse_check_timer->Stop(); if (timed_out && !done) { BOOST_LOG_TRIVIAL(warning) << "timelapse storage check timed out, proceeding with print"; on_send_print(); return; } on_timelapse_storage_check_result(); } } void SelectMachineDialog::on_timelapse_storage_check_result() { DeviceManager* dev = wxGetApp().getDeviceManager(); MachineObject* obj = dev ? dev->get_selected_machine() : nullptr; if (!obj) { on_send_print(); return; } // query failed �� ignore, proceed with print if (obj->timelapse_storage_check_result != 0) { BOOST_LOG_TRIVIAL(info) << "timelapse storage check failed (result=" << obj->timelapse_storage_check_result << "), proceeding"; on_send_print(); return; } // space is enough �� proceed if (obj->timelapse_storage_is_enough) { on_send_print(); return; } // space not enough �� show dialog show_timelapse_storage_dialog(obj); } void SelectMachineDialog::show_timelapse_storage_dialog(MachineObject* obj) { bool is_internal = (m_timelapse_storage == "internal"); bool has_video_files = obj->timelapse_storage_file_count >= 0; // internal: 2 buttons (Confirm & Print, Cancel Timelapse) // external + has files: 3 buttons (Confirm & Print, Cancel Timelapse, Clean Up) // external + no files: 1 button (Cancel Timelapse only) bool show_confirm_btn = is_internal || has_video_files; bool show_cleanup_btn = !is_internal && has_video_files; wxString body_text; if (is_internal) body_text = _L("Low internal storage. This timelapse will overwrite the oldest video files."); else if (has_video_files) body_text = _L("Low external storage. This timelapse will overwrite the oldest video files."); else body_text = _L("Insufficient external storage for time-lapse photography. Connect to computer to delete files, or use a larger memory card."); wxDialog dlg(this, wxID_ANY, _L("Storage Space Not Enough"), wxDefaultPosition, wxDefaultSize, wxDEFAULT_DIALOG_STYLE); dlg.SetBackgroundColour(*wxWHITE); auto* main_sizer = new wxBoxSizer(wxVERTICAL); // warning icon + text row auto* msg_sizer = new wxBoxSizer(wxHORIZONTAL); auto* warn_bmp = new wxStaticBitmap(&dlg, wxID_ANY, create_scaled_bitmap("obj_warning", &dlg, 16), wxDefaultPosition, wxSize(FromDIP(16), FromDIP(16))); auto* msg_label = new Label(&dlg, body_text); msg_label->SetFont(Label::Body_14); msg_label->SetForegroundColour(wxColour(0x33, 0x33, 0x33)); msg_label->Wrap(FromDIP(340)); msg_sizer->Add(warn_bmp, 0, wxALIGN_TOP | wxRIGHT, FromDIP(6)); msg_sizer->Add(msg_label, 1, wxEXPAND); main_sizer->Add(msg_sizer, 0, wxALL | wxEXPAND, FromDIP(20)); auto* btn_sizer = new wxBoxSizer(wxVERTICAL); // use int id to distinguish choices: wxID_OK=confirm, wxID_NO=cancel_tl, wxID_CANCEL=cleanup if (show_confirm_btn) { auto* btn_confirm = new Button(&dlg, _L("Confirm & Print")); StateColor confirm_bg(std::pair(wxColour(0, 174, 66), StateColor::Normal)); btn_confirm->SetBackgroundColor(confirm_bg); btn_confirm->SetTextColor(StateColor(std::pair(*wxWHITE, StateColor::Normal))); btn_confirm->Bind(wxEVT_BUTTON, [&dlg](wxCommandEvent&) { dlg.EndModal(wxID_OK); }); btn_sizer->Add(btn_confirm, 0, wxEXPAND | wxBOTTOM, FromDIP(8)); } auto* btn_cancel_tl = new Button(&dlg, _L("Cancel Timelapse & Print")); if (!show_confirm_btn) { StateColor cancel_bg(std::pair(wxColour(0, 174, 66), StateColor::Normal)); btn_cancel_tl->SetBackgroundColor(cancel_bg); btn_cancel_tl->SetTextColor(StateColor(std::pair(*wxWHITE, StateColor::Normal))); } btn_cancel_tl->Bind(wxEVT_BUTTON, [&dlg](wxCommandEvent&) { dlg.EndModal(wxID_NO); }); btn_sizer->Add(btn_cancel_tl, 0, wxEXPAND | (show_cleanup_btn ? wxBOTTOM : 0), FromDIP(8)); const int ID_CLEANUP = wxID_HIGHEST + 1; // distinct from wxID_CANCEL (X button) if (show_cleanup_btn) { auto* btn_cleanup = new Button(&dlg, _L("Clean Up")); btn_cleanup->Bind(wxEVT_BUTTON, [&dlg, ID_CLEANUP](wxCommandEvent&) { dlg.EndModal(ID_CLEANUP); }); btn_sizer->Add(btn_cleanup, 0, wxEXPAND); } main_sizer->Add(btn_sizer, 0, wxLEFT | wxRIGHT | wxBOTTOM | wxEXPAND, FromDIP(20)); dlg.SetSizer(main_sizer); dlg.Fit(); dlg.CenterOnParent(); // ShowModal returns after dialog closes �� handle action outside modal stack // wxID_CANCEL is returned when user clicks X (close button) �� do nothing in that case int result = dlg.ShowModal(); if (result == wxID_OK) { // Confirm & Print obj->command_ipcam_delete_oldest_timelapse(m_timelapse_storage, m_timelapse_total_layer); on_send_print(); } else if (result == wxID_NO) { // Cancel Timelapse & Print m_checkbox_list["timelapse"]->setValue("off"); on_send_print(); } else if (result == ID_CLEANUP) { // Clean Up: close SelectMachineDialog and navigate navigate_to_timelapse_page(); } // wxID_CANCEL (X button): do nothing, user dismissed the dialog } void SelectMachineDialog::navigate_to_timelapse_page() { // EndModal closes the dialog; schedule navigation after the dialog is fully destroyed wxGetApp().CallAfter([]() { auto* main_frame = wxGetApp().mainframe; if (!main_frame) return; // use existing jump_to_monitor to switch to Monitor tab main_frame->jump_to_monitor(); // then switch to Storage (Media) tab inside Monitor auto* monitor = dynamic_cast(main_frame->m_monitor); if (monitor) { auto* tabpanel = monitor->get_tabpanel(); if (tabpanel) { tabpanel->SetSelection(MonitorPanel::PT_MEDIA); } } }); this->EndModal(wxID_CANCEL); } void SelectMachineDialog::load_option_vals(MachineObject *obj) { if (m_is_in_sending_mode) { return;} AppConfig* config = wxGetApp().app_config; if (!config) return; if (!obj) return; // Read saved values from config �� only called once on open / switch machine for (auto item : m_checkbox_list) { PrintOption *opt = item.second; const std::string &val = config->get(obj->printer_type, item.first); // Default to auto or on, when reopen the software in !BBL_RELEASE_TO_PUBLIC #if !BBL_RELEASE_TO_PUBLIC static bool s_time_lapse_option_init = false; if (!s_time_lapse_option_init && item.first == "timelapse") { wxString error_messgae; if (obj->canEnableTimelapse(error_messgae) && !has_timelapse_warning(error_messgae)) { if (opt->contain_opt("auto")) { opt->setValue("auto"); } else if (opt->contain_opt("on")) { opt->setValue("on"); } s_time_lapse_option_init = true; continue; } } #endif if (opt->contain_opt(val)) { opt->setValue(val); } else if (opt->contain_opt("auto")) { opt->setValue("auto"); } else if (opt->contain_opt("on")) { opt->setValue("on"); } } if (obj->is_multi_extruders()) { const bool has_switcher = obj->GetFilaSwitch() && obj->GetFilaSwitch()->IsInstalled(); auto used_nozzle_idxes = _get_used_nozzle_idxes(); if (used_nozzle_idxes.size() >= 2) { m_checkbox_list["nozzle_offset_cali"]->setValue("auto"); } else if (used_nozzle_idxes.size() == 1) { if (has_switcher) { m_checkbox_list["nozzle_offset_cali"]->setValue("auto"); } else { m_checkbox_list["nozzle_offset_cali"]->setValue("off"); } } } update_option_dynamic_state(obj); } void SelectMachineDialog::update_option_dynamic_state(MachineObject *obj) { if (m_is_in_sending_mode) { return;} if (!obj) return; /*STUDIO-9197*/ wxString error_messgae; if (obj->canEnableTimelapse(error_messgae) && !has_timelapse_warning(error_messgae)) { m_checkbox_list["timelapse"]->enable(true); m_checkbox_list["timelapse"]->update_tooltip(wxEmptyString); } else { m_checkbox_list["timelapse"]->enable(false); m_checkbox_list["timelapse"]->setValue("off"); m_checkbox_list["timelapse"]->update_tooltip(error_messgae); } if (obj->is_timelapse_slow_down) { m_checkbox_list["timelapse"]->set_tips_clickable(true, [this]() { timelapse_button_click(); }); } else { m_checkbox_list["timelapse"]->set_tips_clickable(false); } if (obj->GetNozzleSystem()->GetNozzleRack()->IsSupported()) { m_checkbox_list["nozzle_offset_cali"]->update_tooltip_options_area(S_RACK_NOZZLE_OFFSET_CALI_WARNING); } else { m_checkbox_list["nozzle_offset_cali"]->update_tooltip_options_area(wxEmptyString); } bool options_line_ignore = false; if(wxGetApp().app_config){ options_line_ignore = wxGetApp().app_config->get("disable_auto_flow_cali_tips") == "true"; } bool old_options_line_shown = m_options_line_panel->IsShown(); if (m_checkbox_list["flow_cali"]->IsShown() && has_bowden_extuder(obj)) { m_checkbox_list["flow_cali"]->update_tooltip( _L("Auto: If the filament/nozzle of the main extruder hasn't changed, the last calibration value will be reused. The auxiliary extruder will use the system default value.") + "\n" + _L("On: Before each print starts, calibration will be performed for the main extruder. The auxiliary extruder will use the system default value.") + "\n" + _L("Off: Prioritize using the value from your manual flow calibration.")); if (m_checkbox_list["flow_cali"]->getValue() == "auto") { m_options_line_label->SetLabel(_L("If the filament/nozzle of the main extruder hasn't changed, the last calibration value will be reused. The auxiliary extruder " "will use the system default value.")); m_options_line_label->Wrap(FromDIP(630)); m_options_line_panel->Show(!options_line_ignore); } else if (m_checkbox_list["flow_cali"]->getValue() == "on") { m_options_line_label->SetLabel(_L("Before each print starts, calibration will be performed for the main extruder. The auxiliary extruder will use the system default value.")); m_options_line_label->Wrap(FromDIP(630)); m_options_line_panel->Show(!options_line_ignore); } else { m_options_line_panel->Hide(); } } else { m_checkbox_list["flow_cali"]->update_tooltip(_L("This process determines the dynamic flow values to improve overall print quality.\n*Automatic mode: Skip if the filament was calibrated recently.")); m_options_line_panel->Hide(); } bool show_pa = obj->is_support_pa_mode && m_checkbox_list["flow_cali"]->IsShown() && m_checkbox_list["flow_cali"]->getValue() == "off"; bool old_pa_shown = m_pa_value_panel->IsShown(); m_pa_value_panel->Show(show_pa); if (m_options_line_panel->IsShown() != old_options_line_shown || m_pa_value_panel->IsShown() != old_pa_shown) { m_options_other->Layout(); m_options_other->Fit(); } } void SelectMachineDialog::save_option_vals() { DeviceManager* dev_manager = wxGetApp().getDeviceManager(); if (!dev_manager) { return; } MachineObject* obj = dev_manager->get_my_machine(m_printer_last_select); if (obj) save_option_vals(obj); } void SelectMachineDialog::save_option_vals(MachineObject *obj) { AppConfig *config = wxGetApp().app_config; if (!config) return; if (!obj) return; for (auto item : m_checkbox_list) { config->set(obj->printer_type, item.first, item.second->getValue()); } } void SelectMachineDialog::check_tpu_aero_flow_cali(MachineObject* obj) { if (!obj) return; if (!m_checkbox_list["flow_cali"]->IsShown()) return; if (m_ams_mapping_result.empty()) return; auto is_tpu_or_aero = [](const std::string &fila_type) { return fila_type.find("TPU") != std::string::npos || fila_type.find("Aero") != std::string::npos || fila_type.find("AERO") != std::string::npos; }; bool all_tpu_or_aero = true; bool has_mapped = false; for (const auto& fila : m_ams_mapping_result) { if (fila.tray_id < 0) continue; auto tray_opt = obj->get_tray(fila.ams_id, fila.slot_id); if (!tray_opt.has_value()) continue; has_mapped = true; if (!is_tpu_or_aero(tray_opt->get_filament_type())) { all_tpu_or_aero = false; break; } } if (has_mapped && all_tpu_or_aero) { m_checkbox_list["flow_cali"]->setValue("off"); // Immediately update pa_panel visibility �� same as on_flow_pa_caliation_option_changed if (obj->is_support_pa_mode) { m_pa_value_panel->Show(); Layout(); } else { m_pa_value_panel->Hide(); } } } void SelectMachineDialog::Enable_Auto_Refill(bool enable) { if (enable) { m_ams_backup_tip->SetForegroundColour(wxColour("#00AE42")); } else { m_ams_backup_tip->SetForegroundColour(wxColour(0x90, 0x90, 0x90)); } m_ams_backup_tip->Refresh(); } void SelectMachineDialog::on_send_print() { BOOST_LOG_TRIVIAL(info) << "print_job: on_ok to send"; m_is_canceled = false; Enable_Send_Button(false); if (m_mapping_popup.IsShown()) m_mapping_popup.Dismiss(); if (m_print_type == PrintFromType::FROM_NORMAL && m_is_in_sending_mode){ return; } int result = 0; if (m_printer_last_select.empty()) { return; } DeviceManager* dev = Slic3r::GUI::wxGetApp().getDeviceManager(); if (!dev) return; MachineObject* obj_ = dev->get_selected_machine(); assert(obj_->get_dev_id() == m_printer_last_select); if (obj_ == nullptr) { return; } if (!DevMappingUtil::is_valid_mapping_result(obj_, m_ams_mapping_result)) { BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << "print_job: invalid mapping"; return; } BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ", print_job: for send task, current printer id = " << BBLCrossTalk::Crosstalk_DevId(m_printer_last_select) << std::endl; show_status(PrintDialogStatus::PrintStatusSending); m_status_bar->reset(); m_status_bar->set_prog_block(); m_status_bar->set_cancel_callback_fina([this]() { BOOST_LOG_TRIVIAL(info) << "print_job: enter canceled"; if (m_print_job) { if (m_print_job->is_running()) { BOOST_LOG_TRIVIAL(info) << "print_job: canceled"; m_print_job->cancel(); } m_print_job->join(); } m_is_canceled = true; wxCommandEvent* event = new wxCommandEvent(EVT_PRINT_JOB_CANCEL); wxQueueEvent(this, event); }); if (m_is_canceled) { BOOST_LOG_TRIVIAL(info) << "print_job: m_is_canceled"; m_status_bar->set_status_text(task_canceled_text); return; } // enter sending mode sending_mode(); if (m_print_job) { m_print_job->reset_print_stage(); } m_status_bar->enable_cancel_button(); // get ams_mapping_result std::string ams_mapping_array; std::string ams_mapping_array2; std::string ams_mapping_info; get_ams_mapping_result(ams_mapping_array,ams_mapping_array2, ams_mapping_info); BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << "print_job: get_ams_mapping_result begin"; print_ams_mapping_result(m_ams_mapping_result); BOOST_LOG_TRIVIAL(info) << "print_job: ams_mapping_array = " << ams_mapping_array; BOOST_LOG_TRIVIAL(info) << "print_job: ams_mapping_array2 = " << ams_mapping_array2; BOOST_LOG_TRIVIAL(info) << "print_job: ams_mapping_info = " << ams_mapping_info; BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << "print_job: get_ams_mapping_result end"; const bool is_imported_3mf = m_print_type == PrintFromType::FROM_NORMAL && m_plater->using_exported_file(); if (is_imported_3mf) { m_plater->set_print_job_plate_idx(m_print_plate_idx); } else if (m_print_type == PrintFromType::FROM_NORMAL) { result = m_plater->send_gcode(m_print_plate_idx, [this](int export_stage, int current, int total, bool& cancel) { if (this->m_is_canceled) return; bool cancelled = false; wxString msg = _L("Preparing print job"); m_status_bar->update_status(msg, cancelled, 10, true); m_export_3mf_cancel = cancel = cancelled; }); if (m_is_canceled || m_export_3mf_cancel) { BOOST_LOG_TRIVIAL(info) << "print_job: m_export_3mf_cancel or m_is_canceled"; m_status_bar->set_status_text(task_canceled_text); return; } if (result < 0) { wxString msg = _L("Abnormal print file data. Please slice again"); m_status_bar->set_status_text(msg); return; } // export config 3mf if needed if (!obj_->is_lan_mode_printer()) { result = m_plater->export_config_3mf(m_print_plate_idx); if (result < 0) { BOOST_LOG_TRIVIAL(info) << "export_config_3mf failed, result = " << result; return; } } if (m_is_canceled || m_export_3mf_cancel) { BOOST_LOG_TRIVIAL(info) << "print_job: m_export_3mf_cancel or m_is_canceled"; m_status_bar->set_status_text(task_canceled_text); return; } } m_print_job = std::make_shared(m_status_bar, m_plater, m_printer_last_select); if (is_imported_3mf) m_print_job->set_imported_3mf_path(m_plater->get_3mf_filename()); m_print_job->m_dev_ip = obj_->get_dev_ip(); m_print_job->m_ftp_folder = obj_->get_ftp_folder(); m_print_job->m_access_code = obj_->get_access_code(); #if !BBL_RELEASE_TO_PUBLIC m_print_job->m_local_use_ssl_for_ftp = wxGetApp().app_config->get("enable_ssl_for_ftp") == "true" ? true : false; m_print_job->m_local_use_ssl_for_mqtt = wxGetApp().app_config->get("enable_ssl_for_mqtt") == "true" ? true : false; #else m_print_job->m_local_use_ssl_for_ftp = obj_->local_use_ssl_for_ftp; m_print_job->m_local_use_ssl_for_mqtt = obj_->local_use_ssl_for_mqtt; #endif m_print_job->connection_type = obj_->connection_type(); m_print_job->cloud_print_only = obj_->is_support_cloud_print_only; if (m_print_type == PrintFromType::FROM_NORMAL) { BOOST_LOG_TRIVIAL(info) << "print_job: m_print_type = from_normal"; m_print_job->m_print_type = "from_normal"; std::string project_name = m_current_project_name.utf8_string(); if (is_imported_3mf && boost::algorithm::iends_with(project_name, ".gcode")) project_name.erase(project_name.size() - std::string(".gcode").size()); m_print_job->set_project_name(std::move(project_name)); } else if(m_print_type == PrintFromType::FROM_SDCARD_VIEW){ BOOST_LOG_TRIVIAL(info) << "print_job: m_print_type = from_sdcard_view"; m_print_job->m_print_type = "from_sdcard_view"; //m_print_job->connection_type = "lan"; try { m_print_job->m_print_from_sdc_plate_idx = m_required_data_plate_data_list[m_print_plate_idx]->plate_index + 1; m_print_job->set_dst_name(m_required_data_file_path); } catch (...) {} BOOST_LOG_TRIVIAL(info) << "print_job: m_print_plate_idx =" << m_print_job->m_print_from_sdc_plate_idx; auto input_str_arr = wxGetApp().split_str(m_required_data_file_name, ".gcode.3mf"); if (input_str_arr.size() <= 1) { input_str_arr = wxGetApp().split_str(m_required_data_file_name, ".3mf"); if (input_str_arr.size() > 1) { m_print_job->set_project_name(input_str_arr[0]); } } else { m_print_job->set_project_name(input_str_arr[0]); } } m_print_job->task_ams_mapping = ams_mapping_array; m_print_job->task_ams_mapping2 = ams_mapping_array2; m_print_job->task_ams_mapping_info = ams_mapping_info; if (!obj_->get_nozzle_mapping_result()->GetNozzleMappingJson().empty()) { m_print_job->task_nozzle_mapping = obj_->get_nozzle_mapping_result()->GetNozzleMappingJson().dump(); } /* build nozzles info for multi extruders printers */ if (build_nozzles_info(m_print_job->task_nozzles_info)) { BOOST_LOG_TRIVIAL(error) << "build_nozzle_info errors"; } m_print_job->has_sdcard = obj_->GetStorage()->get_sdcard_state() == DevStorage::SdcardState::HAS_SDCARD_NORMAL; m_print_job->could_emmc_print = obj_->is_support_print_with_emmc; bool timelapse_option = m_checkbox_list["timelapse"]->IsShown()?true:false; if (timelapse_option) { timelapse_option = m_checkbox_list["timelapse"]->getValue() == "on"; } if (timelapse_option && obj_->is_support_internal_timelapse && !m_timelapse_storage.empty()) { m_print_job->task_timelapse_use_internal = (m_timelapse_storage == "internal"); } m_print_job->set_print_config( MachineBedTypeString[0], (m_checkbox_list["bed_leveling"]->getValue() == "on"), (m_checkbox_list["flow_cali"]->getValue() == "on"), false, timelapse_option, true, m_ext_change_assist, m_checkbox_list["bed_leveling"]->getValueInt(), m_checkbox_list["flow_cali"]->getValueInt(), m_checkbox_list["nozzle_offset_cali"]->getValueInt(), (m_pa_value_switch->GetValue() ? 0 : 1) ); if (obj_->HasAms()) { bool has_ext = _HasExt(m_ams_mapping_result); bool has_ams = _HasAms(m_ams_mapping_result); if (has_ams && !has_ext) { m_print_job->task_use_ams = true; } else if (has_ext && !has_ams) { m_print_job->task_use_ams = false; } else { m_print_job->task_use_ams = true; } } else { m_print_job->task_use_ams = false; } BOOST_LOG_TRIVIAL(info) << "print_job: timelapse_option = " << timelapse_option; BOOST_LOG_TRIVIAL(info) << "print_job: use_ams = " << m_print_job->task_use_ams; m_print_job->on_success([this]() { finish_mode(); }); m_print_job->on_check_ip_address_fail([this]() { wxCommandEvent* evt = new wxCommandEvent(EVT_CLEAR_IPADDRESS); wxQueueEvent(this, evt); wxGetApp().show_ip_address_enter_dialog(); }); // update ota version NetworkAgent* agent = wxGetApp().getAgent(); if (agent) { std::string dev_ota_str = "dev_ota_ver:" + obj_->get_dev_id(); agent->track_update_property(dev_ota_str, obj_->get_ota_version()); } m_print_job->start(); BOOST_LOG_TRIVIAL(info) << "print_job: start print job"; } void SelectMachineDialog::clear_ip_address_config(wxCommandEvent& e) { prepare_mode(); } void SelectMachineDialog::update_user_machine_list() { NetworkAgent* m_agent = wxGetApp().getAgent(); if (m_agent && m_agent->is_user_login()) { boost::thread get_print_info_thread = Slic3r::create_thread([this, token = std::weak_ptr(m_token)] { NetworkAgent* agent = wxGetApp().getAgent(); unsigned int http_code; std::string body; int result = agent->get_user_print_info(&http_code, &body); CallAfter([token, this, result, body] { if (token.expired()) {return;} if (result == 0) { m_print_info = body; } else { m_print_info = ""; } wxCommandEvent event(EVT_UPDATE_USER_MACHINE_LIST); event.SetEventObject(this); wxPostEvent(this, event); }); }); } else { wxCommandEvent event(EVT_UPDATE_USER_MACHINE_LIST); event.SetEventObject(this); wxPostEvent(this, event); } } void SelectMachineDialog::on_refresh(wxCommandEvent &event) { BOOST_LOG_TRIVIAL(info) << "m_printer_last_select: on_refresh"; show_status(PrintDialogStatus::PrintStatusRefreshingMachineList); update_user_machine_list(); } void SelectMachineDialog::on_set_finish_mapping(wxCommandEvent &evt) { auto selection_data = evt.GetString(); auto selection_data_arr = wxSplit(selection_data.ToStdString(), '|'); //BOOST_LOG_TRIVIAL(info) << "The ams mapping selection result: data is " << selection_data; if (selection_data_arr.size() == 8) { auto ams_colour = wxColour(wxAtoi(selection_data_arr[0]), wxAtoi(selection_data_arr[1]), wxAtoi(selection_data_arr[2]), wxAtoi(selection_data_arr[3])); int old_filament_id = (int) wxAtoi(selection_data_arr[5]); if (m_print_type == PrintFromType::FROM_NORMAL) {//todo:support sd card change_default_normal(old_filament_id, ams_colour); final_deal_edge_pixels_data(m_preview_thumbnail_data); set_default_normal(m_preview_thumbnail_data); // do't reset ams } int ctype = 0; std::vector material_cols; std::vector tray_cols; for (auto mapping_item : m_mapping_popup.m_mapping_item_list) { if (mapping_item->m_tray_data.id == evt.GetInt()) { ctype = mapping_item->m_tray_data.ctype; material_cols = mapping_item->m_tray_data.material_cols; for (auto col : mapping_item->m_tray_data.material_cols) { wxString color = wxString::Format("#%02X%02X%02X%02X", col.Red(), col.Green(), col.Blue(), col.Alpha()); tray_cols.push_back(color.ToStdString()); } break; } } for (auto i = 0; i < m_ams_mapping_result.size(); i++) { if (m_ams_mapping_result[i].id == wxAtoi(selection_data_arr[5])) { m_ams_mapping_result[i].tray_id = evt.GetInt(); auto ams_colour = wxColour(wxAtoi(selection_data_arr[0]), wxAtoi(selection_data_arr[1]), wxAtoi(selection_data_arr[2]), wxAtoi(selection_data_arr[3])); wxString color = wxString::Format("#%02X%02X%02X%02X", ams_colour.Red(), ams_colour.Green(), ams_colour.Blue(), ams_colour.Alpha()); m_ams_mapping_result[i].color = color.ToStdString(); m_ams_mapping_result[i].ctype = ctype; m_ams_mapping_result[i].colors = tray_cols; m_ams_mapping_result[i].ams_id = selection_data_arr[6].ToStdString(); m_ams_mapping_result[i].slot_id = selection_data_arr[7].ToStdString(); if (m_ams_mapping_result[i].ams_id == std::to_string(VIRTUAL_TRAY_MAIN_ID) || m_ams_mapping_result[i].ams_id == std::to_string(VIRTUAL_TRAY_DEPUTY_ID)) { }else if (m_ams_mapping_result[i].ams_id >= std::to_string(0)) { } } BOOST_LOG_TRIVIAL(trace) << "The ams mapping result: id is " << m_ams_mapping_result[i].id << "tray_id is " << m_ams_mapping_result[i].tray_id; } MaterialHash::iterator iter = m_materialList.begin(); while (iter != m_materialList.end()) { Material* item = iter->second; MaterialItem *m = item->item; if (item->id == m_current_filament_id) { auto ams_colour = wxColour(wxAtoi(selection_data_arr[0]), wxAtoi(selection_data_arr[1]), wxAtoi(selection_data_arr[2]), wxAtoi(selection_data_arr[3])); m->set_ams_info(ams_colour, selection_data_arr[4], ctype, material_cols); m->set_nozzle_info(get_mapped_nozzle_str(item->id)); } iter++; } } update_filament_change_count(); // Re-check flow calibration default after manual mapping change DeviceManager* dev_mgr = wxGetApp().getDeviceManager(); MachineObject* obj = dev_mgr ? dev_mgr->get_my_machine(m_printer_last_select) : nullptr; if (obj) { check_tpu_aero_flow_cali(obj); refresh_print_time(obj); } } void SelectMachineDialog::on_print_job_cancel(wxCommandEvent &evt) { BOOST_LOG_TRIVIAL(info) << "print_job: canceled"; if (m_print_job && m_print_job->get_print_stage() == PrintingStageFinished) { wxGetApp().show_dialog(_L("As the print job has been successfully sent, the cancellation will not bring the print job to a halt. If you need to terminate this job, please stop it in 'Device' page.")); } EnableEditing(true); show_status(PrintDialogStatus::PrintStatusInit); // enter prepare mode prepare_mode(); } bool SelectMachineDialog::is_timeout() { if (m_timeout_count > 15 * 1000 / LIST_REFRESH_INTERVAL) { return true; } return false; } int SelectMachineDialog::update_print_required_data(Slic3r::DynamicPrintConfig config, Slic3r::Model model, Slic3r::PlateDataPtrs plate_data_list, std::string file_name, std::string file_path) { m_required_data_plate_data_list.clear(); m_required_data_config = config; m_required_data_model = model; //m_required_data_plate_data_list = plate_data_list; for (auto i = 0; i < plate_data_list.size(); i++) { if (!plate_data_list[i]->gcode_file.empty()) { m_required_data_plate_data_list.push_back(plate_data_list[i]); } } m_required_data_file_name = file_name; m_required_data_file_path = file_path; return m_required_data_plate_data_list.size(); } void SelectMachineDialog::reset_timeout() { m_timeout_count = 0; } static bool _compare_obj_names(MachineObject* obj1, MachineObject* obj2) { return obj1->get_dev_name() < obj2->get_dev_name(); } /******************************************************************* *@note _collect_machine_list *@param dev_manager -- the device manager *@param sorted_machine_objs -- return the sorted machine objects *@param best_one -- return the best one */ /*******************************************************************/ static void _collect_sorted_machines(Slic3r::DeviceManager* dev_manager, std::vector& sorted_machine_objs) { sorted_machine_objs.clear(); if (!dev_manager) { return; } /* Step 1 :Collect the target and compatible types*/ PresetBundle* preset_bundle = wxGetApp().preset_bundle; const std::string& printer_type = preset_bundle->printers.get_edited_preset().get_printer_type(preset_bundle); const auto& compatible_types_list = DevPrinterConfigUtil::get_compatible_machine(printer_type); std::set compatible_types_set(compatible_types_list.begin(), compatible_types_list.end()); /* Step 2: collect different machine list*/ MachineObject* cur_selected_obj = dev_manager->get_selected_machine(); std::vector match_avaliable_list; // match and availiable machines std::vector match_inavaliable_list;// match but inavaliable machines std::vector other_list;// other bound machines auto _collect_machine = [&](MachineObject* obj) { if (obj == cur_selected_obj) { return; } if (obj->printer_type == printer_type) { obj->is_avaliable() ? match_avaliable_list.push_back(obj) : match_inavaliable_list.push_back(obj); } else { other_list.push_back(obj); } }; // collect from user machine list const auto& user_machine_list = dev_manager->get_my_machine_list();// user machine list for (const auto& elem : user_machine_list) { MachineObject* mobj = elem.second; if (mobj && (mobj->is_online() || mobj->is_connected())) { _collect_machine(mobj); } } /* Step 2: Sort the lists*/ std::sort(match_avaliable_list.begin(), match_avaliable_list.end(), _compare_obj_names); std::sort(match_inavaliable_list.begin(), match_inavaliable_list.end(), _compare_obj_names); std::sort(other_list.begin(), other_list.end(), _compare_obj_names); /* Step 3: Get the sorted objects*/ auto _collect_sorted_objs = [](const std::vector& obj_list, std::vector& sorted_machine_objs) { for (auto obj : obj_list) { sorted_machine_objs.push_back(obj); } }; // the shown list, STUDIO-8235 if (cur_selected_obj) { std::vector cur_selected_obj_list{ cur_selected_obj }; _collect_sorted_objs(cur_selected_obj_list, sorted_machine_objs); } _collect_sorted_objs(match_avaliable_list, sorted_machine_objs); _collect_sorted_objs(match_inavaliable_list, sorted_machine_objs); _collect_sorted_objs(other_list, sorted_machine_objs); } void SelectMachineDialog::update_user_printer() { Slic3r::DeviceManager* dev = Slic3r::GUI::wxGetApp().getDeviceManager(); if (!dev) return; // update user print info if (!m_print_info.empty()) { dev->parse_user_print_info(m_print_info); m_print_info = ""; } // update the machine list, and select a default machine _collect_sorted_machines(dev, m_list); m_printer_box->SetPrinters(m_list); if (!m_list.empty()) { m_printer_last_select = m_list.front()->get_dev_id(); m_printer_box->GetPrinterComboBox()->SetSelection(0); wxCommandEvent event(wxEVT_COMBOBOX); event.SetEventObject(m_printer_box->GetPrinterComboBox()); wxPostEvent(m_printer_box->GetPrinterComboBox(), event); } else { m_printer_last_select = ""; update_select_layout(nullptr); m_printer_box->GetPrinterComboBox()->SetTextLabel(""); } BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << "for send task, current printer id = " << BBLCrossTalk::Crosstalk_DevId(m_printer_last_select) << std::endl; update_by_obj(get_current_machine()); } void SelectMachineDialog::on_rename_click(wxMouseEvent& event) { m_is_rename_mode = true; m_rename_input->GetTextCtrl()->SetValue(m_current_project_name); m_rename_switch_panel->SetSelection(1); m_rename_input->GetTextCtrl()->SetFocus(); m_rename_input->GetTextCtrl()->SetInsertionPointEnd(); } void SelectMachineDialog::on_rename_enter() { if (m_is_rename_mode == false){ return; } else { m_is_rename_mode = false; } auto new_file_name = m_rename_input->GetTextCtrl()->GetValue(); wxString temp; int num = 0; for (auto t : new_file_name) { if (t == wxString::FromUTF8("\x20")) { num++; if (num == 1) temp += t; } else { num = 0; temp += t; } } new_file_name = temp; auto m_valid_type = Valid; wxString info_line; const char* unusable_symbols = "<>[]:/\\|?*\""; const std::string unusable_suffix = PresetCollection::get_suffix_modified(); //"(modified)"; for (size_t i = 0; i < std::strlen(unusable_symbols); i++) { if (new_file_name.find_first_of(unusable_symbols[i]) != std::string::npos) { info_line = _L("Name is invalid;") + "\n" + _L("illegal characters:") + " " + unusable_symbols; m_valid_type = NoValid; break; } } if (m_valid_type == Valid && new_file_name.find(unusable_suffix) != std::string::npos) { info_line = _L("Name is invalid;") + "\n" + _L("illegal suffix:") + "\n\t" + from_u8(PresetCollection::get_suffix_modified()); m_valid_type = NoValid; } if (m_valid_type == Valid && new_file_name.empty()) { info_line = _L("The name is not allowed to be empty."); m_valid_type = NoValid; } if (m_valid_type == Valid && new_file_name.find_first_of(' ') == 0) { info_line = _L("The name is not allowed to start with space character."); m_valid_type = NoValid; } if (m_valid_type == Valid && new_file_name.find_last_of(' ') == new_file_name.length() - 1) { info_line = _L("The name is not allowed to end with space character."); m_valid_type = NoValid; } if (m_valid_type == Valid && new_file_name.size() >= 100) { info_line = _L("The name length exceeds the limit."); m_valid_type = NoValid; } if (m_valid_type != Valid) { MessageDialog msg_wingow(nullptr, info_line, "", wxICON_WARNING | wxOK); if (msg_wingow.ShowModal() == wxID_OK) { m_rename_switch_panel->SetSelection(0); m_rename_text->SetLabel(m_current_project_name); m_rename_normal_panel->Layout(); return; } } m_current_project_name = new_file_name; m_rename_switch_panel->SetSelection(0); m_rename_text->SetLabelText(m_current_project_name); m_rename_normal_panel->Layout(); } void SelectMachineDialog::update_printer_combobox(wxCommandEvent &event) { show_status(PrintDialogStatus::PrintStatusInit); update_user_printer(); } void SelectMachineDialog::update_ams_backup(MachineObject* obj_) { bool to_show = obj_ && obj_->GetFilaSystem()->CanShowFilamentBackup() && _HasAms(m_ams_mapping_result); if (to_show != m_ams_backup_tip->IsShown() || to_show != img_ams_backup->IsShown()) { m_ams_backup_tip->Show(to_show); img_ams_backup->Show(to_show); Layout(); Fit(); } } Slic3r::MachineObject* SelectMachineDialog::get_current_machine() const { DeviceManager* dev_ = Slic3r::GUI::wxGetApp().getDeviceManager(); if (!dev_) return nullptr; return dev_->get_my_machine(m_printer_last_select); } void SelectMachineDialog::on_timer(wxTimerEvent &event) { update_by_obj(get_current_machine()); } void SelectMachineDialog::update_by_obj(MachineObject* obj_) { if(!obj_) return; if (obj_->GetExtderSystem()->GetTotalExtderCount() > 1) { change_materialitem_tip(false); /*mapping to both ams and ext, is supported while total_extder_count is 2*/ } else { change_materialitem_tip(true); } m_filament_left_title->SetLabel(_L(DevPrinterConfigUtil::get_toolhead_display_name(obj_->printer_type, DEPUTY_EXTRUDER_ID, ToolHeadComponent::Nozzle, ToolHeadNameCase::TitleCase))); m_filament_right_title->SetLabel(_L(DevPrinterConfigUtil::get_toolhead_display_name(obj_->printer_type, MAIN_EXTRUDER_ID, ToolHeadComponent::Nozzle, ToolHeadNameCase::TitleCase))); update_ams_backup(obj_); update_material_item_pos(obj_); update_option_dynamic_state(obj_); update_show_status(obj_); update_print_status_msg(); //update_scroll_area_size();/*STUDIO-12867 the page maybe blank in some platform. FIXME*/ refresh_print_time(obj_); } void SelectMachineDialog::on_selection_changed(wxCommandEvent &event) { // Reset external multi-color printing option to default value m_ext_change_assist = false; if (m_check_ext_change_assist) { m_check_ext_change_assist->SetValue(false); } /* reset timeout and reading printer info */ m_status_bar->reset(); m_timeout_count = 0; s_nozzle_mapping_last_request_time = 0; m_ams_mapping_result.clear(); m_pre_print_checker.clear(); m_link_edit_nozzle->Show(false); m_mapping_sugs_sizer->Show(false); m_change_filament_times_sizer->Show(false); m_txt_change_filament_times->Show(false); update_print_status_msg(); auto selection = m_printer_box->GetPrinterComboBox()->GetSelection(); DeviceManager* dev = Slic3r::GUI::wxGetApp().getDeviceManager(); if (!dev) return; MachineObject* obj = nullptr; for (int i = 0; i < m_list.size(); i++) { if (i == selection) { //check lan mode machine if (m_list[i]->is_lan_mode_printer() && !m_list[i]->has_access_right()) { ConnectPrinterDialog dlg(wxGetApp().mainframe, wxID_ANY, _L("Input access code")); dlg.set_machine_object(m_list[i]); auto res = dlg.ShowModal(); m_printer_last_select = ""; m_printer_box->GetPrinterComboBox()->SetSelection(-1); m_printer_box->GetPrinterComboBox()->Refresh(); m_printer_box->GetPrinterComboBox()->Update(); } m_printer_last_select = m_list[i]->get_dev_id(); obj = m_list[i]; BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << "for send task, current printer id = " << BBLCrossTalk::Crosstalk_DevId(m_printer_last_select) << std::endl; break; } } if (obj) { obj->command_get_version(); obj->command_request_push_all(); obj->get_nozzle_mapping_result()->SetPlater(m_plater); if (!dev->get_selected_machine()) { dev->set_selected_machine(m_printer_last_select); }else if (dev->get_selected_machine()->get_dev_id() != m_printer_last_select) { dev->set_selected_machine(m_printer_last_select); } // Has changed machine unrecoverably m_check_flag = false; } else { BOOST_LOG_TRIVIAL(error) << "on_selection_changed dev_id not found"; return; } //reset print status clear_nozzle_mapping(); m_text_printer_msg->Clear(); m_statictext_ams_msg->Clear(); show_status(PrintDialogStatus::PrintStatusInit); update_show_status(); update_print_status_msg(); // update update_by_obj(get_current_machine()); } void SelectMachineDialog::update_ams_check(MachineObject *obj) { if (!obj) {return;} if (!obj->is_enable_np) { if (obj->HasAms()) { change_materialitem_tip(false); } else { change_materialitem_tip(true); } }else{ change_materialitem_tip(false); } } void SelectMachineDialog::update_best_pos_dialog(wxCommandEvent &evt) { MachineObject* obj_ = get_current_machine(); if(!obj_) return; update_show_status(obj_); auto save_time = get_filament_change_gap_time(obj_); wxString text{}; if (save_time.has_value() && save_time.value() >= 1) { text = FormatTime(*save_time); } //key: logicID value: posID std::map best_pos_map; for (const auto& slot : m_ams_mapping_result) { if (!is_at_suggested_pos(obj_, slot.id)) { auto pos = get_filament_suggest_pos(obj_, slot.id); if (pos.has_value()) { best_pos_map[slot.id] = pos.value(); } } } m_best_pos_dialog->Update(obj_, best_pos_map, m_ams_mapping_result, text); } void SelectMachineDialog::update_filament_change_count() { /*check filament change times*/ PartPlate * part_plate = m_plater->get_partplate_list().get_curr_plate(); PrintBase * print = nullptr; GCodeResult *gcode_result = nullptr; m_change_filament_times_sizer->Show(false); m_txt_change_filament_times->Show(false); m_mapping_sugs_sizer->Show(false); m_link_edit_nozzle->Show(false); DeviceManager *dev = Slic3r::GUI::wxGetApp().getDeviceManager(); if (!dev) return; MachineObject *obj = dev->get_selected_machine(); if (!obj) return; if (m_print_type == FROM_SDCARD_VIEW) return; std::vector filament_ids; for (auto mr : m_ams_mapping_result) { if (mr.ams_id == std::to_string(VIRTUAL_TRAY_MAIN_ID) || mr.ams_id == std::to_string(VIRTUAL_TRAY_DEPUTY_ID)) { filament_ids.push_back(mr.id); } } /*check nozzle & filament is it the best*/ auto stats = m_plater->get_partplate_list().get_current_fff_print().statistics_by_extruder(); auto best = stats.stats_by_multi_extruder_best; auto curr = stats.stats_by_multi_extruder_curr; int hand_changes_count = curr.flush_filament_change_count - best.flush_filament_change_count; int saving_weight = curr.filament_flush_weight - best.filament_flush_weight; if (obj->GetExtderSystem()->GetTotalExtderCount() > 1) { m_link_edit_nozzle->Show(true); } if (hand_changes_count > 0 || saving_weight > 0) { m_mapping_sugs_sizer->Show(true); m_change_filament_times_sizer->Show(true); m_txt_change_filament_times->Show(true); m_txt_change_filament_times->SetLabel(wxString::Format(_L("Cost %dg filament and %d changes more than optimal grouping."), saving_weight, hand_changes_count)); m_txt_change_filament_times->Wrap(FromDIP(580)); m_txt_change_filament_times->Layout(); } Layout(); Fit(); } static wxString _check_kval_not_default(const MachineObject* obj, const std::vector &mapping_result) { if (!obj) { return wxEmptyString;} wxString ams_names; for (const auto& info : mapping_result) { const auto& tray_info = obj->get_tray(info.ams_id, std::to_string(info.tray_id)); if (!tray_info.has_value()) { continue; } if (tray_info->cali_idx == -1) { continue; /*-1 means default*/ } wxString ams_name; if (info.tray_id == VIRTUAL_TRAY_MAIN_ID) { ams_name = "Right-Ext"; } else if (info.tray_id == VIRTUAL_TRAY_DEPUTY_ID) { ams_name = "Left-Ext"; } else { ams_name = wxGetApp().transition_tridid(info.tray_id); } if (!ams_names.empty()) { ams_names += ", ";} ams_names += ams_name; } return ams_names; } static wxString _get_nozzle_name(int total_ext_count, int ext_id) { if (total_ext_count == 2) { std::string pt = wxGetApp().preset_bundle->printers.get_edited_preset().get_printer_type(wxGetApp().preset_bundle); return _L(DevPrinterConfigUtil::get_toolhead_display_name(pt, ext_id, ToolHeadComponent::Nozzle, ToolHeadNameCase::LowerCase)); } return _L("nozzle"); } static wxString _get_ext_loc_str(const std::unordered_set& extruders, int total_ext_num) { assert(!extruders.empty()); if (total_ext_num == 1) { return _L("extruder"); } else if (total_ext_num == 2) { if (extruders.size() == 2) { return _L("both extruders"); } else if (extruders.size() == 1) { auto iter = extruders.begin(); std::string pt = wxGetApp().preset_bundle->printers.get_edited_preset().get_printer_type(wxGetApp().preset_bundle); return _L(DevPrinterConfigUtil::get_toolhead_display_name(pt, *iter, ToolHeadComponent::Extruder, ToolHeadNameCase::LowerCase)); } } return wxEmptyString; } void SelectMachineDialog::update_show_status(MachineObject* obj_) { m_pre_print_checker.clear(); /*agent check and printer valid check*/ NetworkAgent* agent = Slic3r::GUI::wxGetApp().getAgent(); if (!agent) { show_status(PrintDialogStatus::PrintStatusNoUserLogin); update_ams_check(nullptr); return; } if (!obj_) { show_status(PrintDialogStatus::PrintStatusInvalidPrinter); update_ams_check(nullptr); return; } /*combobox check*/ if (m_print_type == PrintFromType::FROM_SDCARD_VIEW) { m_printer_box->GetPrinterComboBox()->Disable(); } else { if (get_status() == PrintDialogStatus::PrintStatusRefreshingMachineList) m_printer_box->GetPrinterComboBox()->Disable(); else m_printer_box->GetPrinterComboBox()->Enable(); } /*mode check*/ if (get_status() == PrintDialogStatus::PrintStatusRefreshingMachineList || get_status() == PrintDialogStatus::PrintStatusSending) return; /*no valid gcode file*/ if (m_print_type == PrintFromType::FROM_NORMAL) { PartPlate* plate = m_plater->get_partplate_list().get_curr_plate(); if (plate && !plate->is_valid_gcode_file()) { show_status(PrintDialogStatus::PrintStatusBlankPlate); return; } } /** error check **/ /* check cloud machine connections */ if (!obj_->is_lan_mode_printer() && !agent->is_server_connected()) { show_status(PrintDialogStatus::PrintStatusConnectingServer); reset_timeout(); return; } if (!obj_->is_info_ready()) { if (is_timeout()) { m_ams_mapping_result.clear(); sync_ams_mapping_result(m_ams_mapping_result); show_status(PrintDialogStatus::PrintStatusReadingTimeout); } else { m_timeout_count++; show_status(PrintDialogStatus::PrintStatusReading); } return; } reset_timeout(); /*check print all*/ if (!obj_->GetConfig()->SupportPrintAllPlates() && m_print_plate_idx == PLATE_ALL_IDX) { show_status(PrintDialogStatus::PrintStatusNotSupportedPrintAll); return; } if (!m_check_flag && obj_->is_info_ready()) { update_select_layout(obj_); update_ams_check(obj_); m_check_flag = true; } /*do ams mapping if no ams result*/ if (m_ams_mapping_result.empty()) { do_ams_mapping(obj_, true); update_filament_change_count(); check_tpu_aero_flow_cali(obj_); // Disable nozzle offset calibration for by-object printing if (m_checkbox_list["nozzle_offset_cali"]->IsShown()) { auto curr_plate = wxGetApp().plater()->get_partplate_list().get_curr_plate(); if (curr_plate && curr_plate->get_print_seq() == PrintSequence::ByObject) { m_checkbox_list["nozzle_offset_cali"]->setValue("off"); } } } /* multi color external change assist*/ bool is_support_mutile_color = obj_->is_support_ext_change_assist_old || obj_->is_support_ext_change_assist; if (is_support_mutile_color && !m_check_ext_change_assist->IsShown()) { m_check_ext_change_assist->Show(true); m_label_ext_change_assist->Show(true); } else if (!is_support_mutile_color && m_check_ext_change_assist->IsShown()) { m_check_ext_change_assist->Hide(); m_label_ext_change_assist->Hide(); } /*check external change assist*/ if(!m_ams_mapping_result.empty() && is_enable_external_change_assist(m_ams_mapping_result)){ m_check_ext_change_assist->Enable(true); }else{ m_check_ext_change_assist->SetValue(false); m_check_ext_change_assist->Enable(false); } { const bool show_warn_when_drying = is_selected_ams_drying(obj_); const bool is_currently_shown = (m_txt_warn_when_drying != nullptr) ? m_txt_warn_when_drying->IsShown() : false; if (is_currently_shown != show_warn_when_drying) { m_warn_when_drying_sizer->Show(show_warn_when_drying); Layout(); Fit(); } } /*reading done*/ auto upgrade_ptr = obj_->GetUpgrade().lock(); if (upgrade_ptr) { if (upgrade_ptr->IsUpgradeForceUpgrade()) { show_status(PrintDialogStatus::PrintStatusNeedForceUpgrading); return; } if (upgrade_ptr->IsUpgradeConsistencyRequest()) { show_status(PrintStatusNeedConsistencyUpgrading); return; } } if (!obj_->GetInfo()->IsFdmMode()) { show_status(PrintDialogStatus::PrintStatusModeNotFDM); return; } if (is_blocking_printing(obj_)) { show_status(PrintDialogStatus::PrintStatusUnsupportedPrinter); return; } else if (obj_->is_in_upgrading()) { show_status(PrintDialogStatus::PrintStatusInUpgrading); return; } else if (obj_->is_system_printing()) { show_status(PrintDialogStatus::PrintStatusInSystemPrinting); return; } else if (obj_->is_in_printing() || obj_->ams_status_main == AMS_STATUS_MAIN_FILAMENT_CHANGE || obj_->ams_status_main == AMS_STATUS_MAIN_COLD_PULL) { show_status(PrintDialogStatus::PrintStatusInPrinting); return; } else if (!obj_->GetConfig()->SupportPrintWithoutSD() && (obj_->GetStorage()->get_sdcard_state() == DevStorage::SdcardState::NO_SDCARD)) { show_status(PrintDialogStatus::PrintStatusNoSdcard); return; } if (m_print_type != PrintFromType::FROM_SDCARD_VIEW) { const int max_color = obj_->get_max_filament_color_count(); const size_t fila_cnt = wxGetApp().preset_bundle->filament_presets.size(); if (max_color > 0 && fila_cnt > (size_t) max_color) { show_status(PrintDialogStatus::PrintStatusColorQuantityExceed, {wxString::Format("%d", max_color)}); return; } } /*check sdcard when if lan mode printer*/ if (obj_->is_lan_mode_printer()) { if (obj_->GetStorage()->get_sdcard_state() == DevStorage::SdcardState::NO_SDCARD && !obj_->is_support_print_with_emmc) { show_status(PrintDialogStatus::PrintStatusLanModeNoSdcard); return; } else if (obj_->GetStorage()->get_sdcard_state() == DevStorage::SdcardState::HAS_SDCARD_ABNORMAL || obj_->GetStorage()->get_sdcard_state() == DevStorage::SdcardState::HAS_SDCARD_READONLY) { show_status(PrintDialogStatus::PrintStatusLanModeSDcardNotAvailable); return; } } if (!can_support_pa_auto_cali() && m_checkbox_list["flow_cali"]->IsShown()) { if (m_checkbox_list["flow_cali"]->getValue() == "auto") { show_status(PrintDialogStatus::PrintStatusTPUUnsupportAutoCali); if (PrePrintChecker::is_error(PrintDialogStatus::PrintStatusTPUUnsupportAutoCali)) { return; } } if (m_checkbox_list["flow_cali"]->getValue() == "on") { show_status(PrintDialogStatus::PrintStatusTPUUnsupportCaliOn); if (PrePrintChecker::is_error(PrintDialogStatus::PrintStatusTPUUnsupportCaliOn)) { return; } } } if (!CheckErrorDynamicSwitchNozzle(obj_)) { return; } if (!CheckErrorRackStatus(obj_)) { return; } if (!CheckErrorExtruderNozzleWithSlicing(obj_)) { return; } if (!CheckErrorSyncNozzleMappingResultV1(obj_)) { return;// check nozzle mapping result v1 for rack and dynamic switcher } if (obj_->GetFilaSystem()->IsAmsSettingUp()) { show_status(PrintDialogStatus::PrintStatusAmsOnSettingup); return; } if (!CheckErrorWarningFilamentMapping(obj_)) { return; } if (!CheckErrorSyncNozzleMappingResultV0(obj_)) /*check nozzle mapping result for rack*/ { return; } if (!check_sdcard_for_timelpase(obj_) && has_timelapse_warning()) { show_status(PrintDialogStatus::PrintStatusTimelapseWarning); } check_timelapse_storage_warning(obj_); /*STUDIO-10970 check the k value and flow cali option*/ if (m_checkbox_list["flow_cali"]->IsShown() && m_checkbox_list["flow_cali"]->getValue() == "auto") { const auto ¬_default_ams_names = _check_kval_not_default(obj_, m_ams_mapping_result); if (!not_default_ams_names.empty()) { std::vector params{not_default_ams_names}; show_status(PrintDialogStatus::PrintStatusWarningKvalueNotUsed); // return; } } /*Check high temperture slicing*/ if (m_print_type == PrintFromType::FROM_NORMAL) { std::set high_temp_filaments; std::unordered_set known_fila_soften_extruders; std::unordered_set unknown_fila_soften_extruders; auto preset_full_config = wxGetApp().preset_bundle->full_config(); auto chamber_temperatures = preset_full_config.option("chamber_temperatures"); for (const FilamentInfo& item : m_ams_mapping_result) { try { int chamber_temp = chamber_temperatures->values[item.id]; if (chamber_temp >= 40) { high_temp_filaments.insert(item.get_display_filament_type());// high printing chamber temperature } for (const auto& extder : obj_->GetExtderSystem()->GetExtruders()) { // check vitrification if (extder.HasFilamentInExt()) { const auto& fila_id = obj_->get_filament_id(extder.GetSlotNow().ams_id, extder.GetSlotNow().slot_id); auto filament_info = wxGetApp().preset_bundle->get_filament_by_filament_id(fila_id); if (filament_info) { if (filament_info->temperature_vitrification - chamber_temp <= 5) { known_fila_soften_extruders.insert(extder.GetExtId()); } } else { // the minimum temperature_vitrification of the known filaments is 43 degrees if (43 - chamber_temp <= 5) { unknown_fila_soften_extruders.insert(extder.GetExtId()); } } } } } catch (std::exception&) { assert(0); } } if (!high_temp_filaments.empty()) { wxString filament_strs;/*join the filament strs*/ for (auto filament : high_temp_filaments) { filament_strs += filament; if (filament != *high_temp_filaments.rbegin()) { filament_strs += ", "; } } if (obj_->GetConfig()->HasChamber()) { const auto& msg = wxString::Format(_L("[ %s ] requires printing in a high-temperature environment. Reminder to close the door if not already closed."), filament_strs); show_status(PrintDialogStatus::PrintStatusFilamentWarningHighChamberTempCloseDoor, { msg }); if (PrePrintChecker::is_error(PrintDialogStatus::PrintStatusFilamentWarningHighChamberTempCloseDoor)) { return; } } else { const auto& msg = wxString::Format(_L("[ %s ] requires printing in a high-temperature environment."), filament_strs); show_status(PrintDialogStatus::PrintStatusFilamentWarningHighChamberTemp, { msg }); if (PrePrintChecker::is_error(PrintDialogStatus::PrintStatusFilamentWarningHighChamberTemp)) { return; } } } if (!known_fila_soften_extruders.empty()) { const wxString& msg = wxString::Format(_L("The filament on %s may soften. Please unload."), _get_ext_loc_str(known_fila_soften_extruders, obj_->GetExtderSystem()->GetTotalExtderCount())); show_status(PrintDialogStatus::PrintStatusFilamentWarningHighChamberTempSoft, std::vector {msg}); if (PrePrintChecker::is_error(PrintDialogStatus::PrintStatusFilamentWarningHighChamberTempSoft)) { return; } } if (!unknown_fila_soften_extruders.empty()) { const wxString& msg = wxString::Format(_L("The filament on %s is unknown and may soften. Please set filament."), _get_ext_loc_str(unknown_fila_soften_extruders, obj_->GetExtderSystem()->GetTotalExtderCount())); show_status(PrintDialogStatus::PrintStatusFilamentWarningUnknownHighChamberTempSoft, std::vector {msg}); if (PrePrintChecker::is_error(PrintDialogStatus::PrintStatusFilamentWarningUnknownHighChamberTempSoft)) { return; } } } // check extension tool warning UpdateStatusCheckWarning_ExtensionTool(obj_); // check rack nozzle warning CheckWarningRackStatus(obj_); /** normal check **/ show_status(PrintDialogStatus::PrintStatusReadyToGo); } bool SelectMachineDialog::has_timelapse_warning(wxString &msg_text) { PartPlate *plate = m_plater->get_partplate_list().get_curr_plate(); for (auto warning : plate->get_slice_result()->warnings) { if (warning.msg == NOT_GENERATE_TIMELAPSE) { if (warning.error_code == "10014001") { msg_text = _L("When enabling spiral vase mode, machines with I3 structure will not generate timelapse videos."); } else if (warning.error_code == "10014002") { msg_text = _L("The current printer does not support timelapse in Instant Mode when printing By-Object."); } return true; } } return false; } bool SelectMachineDialog::can_support_pa_auto_cali() { DeviceManager *dev = Slic3r::GUI::wxGetApp().getDeviceManager(); if (!dev) return true; MachineObject *obj = dev->get_selected_machine(); if (!obj) return true; std::vector unsupport_auto_cali_filaments = DevPrinterConfigUtil::get_unsupport_auto_cali_filaments(obj->printer_type); if (!unsupport_auto_cali_filaments.empty()) { auto iter = std::find_if(m_filaments.begin(), m_filaments.end(), [&unsupport_auto_cali_filaments](const FilamentInfo &item) { auto iter = std::find(unsupport_auto_cali_filaments.begin(), unsupport_auto_cali_filaments.end(), item.filament_id); return iter != unsupport_auto_cali_filaments.end(); }); return iter == m_filaments.end(); } return true; } void SelectMachineDialog::reset_ams_material() { MaterialHash::iterator iter = m_materialList.begin(); while (iter != m_materialList.end()) { int id = iter->first; Material* item = iter->second; MaterialItem* m = item->item; wxString ams_id = "-"; wxColour ams_col = wxColour(0xEE, 0xEE, 0xEE); m->set_ams_info(ams_col, ams_id); m->set_nozzle_info(get_mapped_nozzle_str(id)); iter++; } } void SelectMachineDialog::Enable_Refresh_Button(bool en) { m_printer_box->EnableRefreshButton(en); } void SelectMachineDialog::Enable_Send_Button(bool en) { if (!en) { if (m_button_ensure->IsEnabled()) { m_button_ensure->Disable(); m_button_ensure->SetBackgroundColor(wxColour(200, 200, 200)); m_button_ensure->SetBorderColor(wxColour(200, 200, 200)); } } else { if (!m_button_ensure->IsEnabled()) { m_button_ensure->Enable(); m_button_ensure->SetBackgroundColor(m_btn_bg_enable); m_button_ensure->SetBorderColor(m_btn_bg_enable); } } } void SelectMachineDialog::on_dpi_changed(const wxRect &suggested_rect) { print_time->msw_rescale(); timeimg->SetBitmap(print_time->bmp()); print_weight->msw_rescale(); weightimg->SetBitmap(print_weight->bmp()); rename_editable->msw_rescale(); rename_editable_light->msw_rescale(); if (ams_mapping_help_icon != nullptr) { ams_mapping_help_icon->msw_rescale(); if (img_amsmapping_tip)img_amsmapping_tip->SetBitmap(ams_mapping_help_icon->bmp()); } m_button_ensure->SetMinSize(SELECT_MACHINE_DIALOG_BUTTON_SIZE2); m_button_ensure->SetCornerRadius(FromDIP(4)); m_status_bar->msw_rescale(); for (auto material1 : m_materialList) { material1.second->item->msw_rescale(); } for(auto opt : m_checkbox_list) { opt.second->msw_rescale(); } m_mapping_popup.msw_rescale(); m_options_line_bmp->SetBitmap(create_scaled_bitmap("warning", m_options_line_panel, 25)); m_statictext_ams_msg->Rescale(); m_text_printer_msg->Rescale(); Fit(); Refresh(); } void SelectMachineDialog::set_default() { if (m_print_type == PrintFromType::FROM_NORMAL) { m_printer_box->SetDefault(false); m_rename_normal_panel->Show(true); } else if (m_print_type == PrintFromType::FROM_SDCARD_VIEW) { m_printer_box->SetDefault(true); m_rename_normal_panel->Show(false); } //project name m_rename_switch_panel->SetSelection(0); wxString filename = m_plater->get_export_gcode_filename("", true, m_print_plate_idx == PLATE_ALL_IDX ? true : false); if (m_print_plate_idx == PLATE_ALL_IDX && filename.empty()) { filename = _L("Untitled"); } if (filename.empty()) { filename = m_plater->get_export_gcode_filename("", true); if (filename.empty()) filename = _L("Untitled"); } fs::path filename_path(filename.c_str()); std::string file_name = filename_path.filename().string(); if (from_u8(file_name).find(_L("Untitled")) != wxString::npos) { PartPlate *part_plate = m_plater->get_partplate_list().get_plate(m_print_plate_idx); if (part_plate) { if (std::vector objects = part_plate->get_objects_on_this_plate(); objects.size() > 0) { file_name = objects[0]->name; for (int i = 1; i < objects.size(); i++) { file_name += (" + " + objects[i]->name); } } if (file_name.size() > 100) { file_name = file_name.substr(0, 97) + "..."; } } } m_current_project_name = wxString::FromUTF8(file_name); // filament area title { std::string sm_init_pt = wxGetApp().preset_bundle->printers.get_edited_preset().get_printer_type(wxGetApp().preset_bundle); m_filament_left_title->SetLabel(_L(DevPrinterConfigUtil::get_toolhead_display_name(sm_init_pt, DEPUTY_EXTRUDER_ID, ToolHeadComponent::Nozzle, ToolHeadNameCase::TitleCase))); m_filament_right_title->SetLabel(_L(DevPrinterConfigUtil::get_toolhead_display_name(sm_init_pt, MAIN_EXTRUDER_ID, ToolHeadComponent::Nozzle, ToolHeadNameCase::TitleCase))); } //unsupported character filter m_current_project_name = from_u8(filter_characters(m_current_project_name.ToUTF8().data(), "<>[]:/\\|?*\"")); m_rename_text->SetLabelText(m_current_project_name); m_rename_normal_panel->Layout(); //clear combobox m_list.clear(); m_printer_last_select = ""; m_print_info = ""; m_mapping_sugs_sizer->Show(false); m_change_filament_times_sizer->Show(false); m_txt_change_filament_times->Show(false); m_warn_when_drying_sizer->Show(false); // rset status bar m_status_bar->reset(); // reset ext_change status if (m_check_ext_change_assist) { m_check_ext_change_assist->SetValue(m_ext_change_assist); } NetworkAgent* agent = wxGetApp().getAgent(); if (agent) { if (agent->is_user_login()) { show_status(PrintDialogStatus::PrintStatusInit); } } m_text_printer_msg->Clear(); m_statictext_ams_msg->Clear(); if (m_print_type == PrintFromType::FROM_NORMAL) { try{ reset_and_sync_ams_list(); } catch (std::exception& e) { BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << e.what(); }; set_default_normal(m_plater->get_partplate_list().get_curr_plate()->thumbnail_data); } else if (m_print_type == PrintFromType::FROM_SDCARD_VIEW) { update_page_turn_state(true); set_default_from_sdcard(); } Layout(); Fit(); } void SelectMachineDialog::change_materialitem_tip(bool no_ams_only_ext) { Slic3r::DeviceManager *dev_ = Slic3r::GUI::wxGetApp().getDeviceManager(); MachineObject *obj_ = dev_->get_selected_machine(); MaterialHash::iterator iter = m_materialList.begin(); while (iter != m_materialList.end()) { int id = iter->first; Material *item = iter->second; if (item) { if (no_ams_only_ext && item->item->m_ams_name == "Ext") { item->item->SetToolTip(wxEmptyString); } else { wxString tip_text; if (item->item->m_match) { if (!item->item->m_mapped_nozzle_str.empty()) tip_text = (_L("Upper half area: Original\nMiddle area: Filament in AMS\nLower half area: Selected nozzle\nAnd you can click it to modify")); else tip_text = _L("Upper half area: Original\nLower half area: Filament in AMS\nAnd you can click it to modify"); } else { tip_text = _L("Unable to automatically match to suitable filament. Please click to manually match."); } if (item->item->GetToolTipText() != tip_text) { item->item->SetToolTip(tip_text); } } } iter++; } } std::vector sCollectPresetFilamentInfo() { std::vector preset_filament_vec; auto preset_bundle = wxGetApp().preset_bundle; if (preset_bundle) { for (auto filament_name : preset_bundle->filament_presets) { for (int f_index = 0; f_index < preset_bundle->filaments.size(); f_index++) { PresetCollection* filament_presets = &wxGetApp().preset_bundle->filaments; Preset* preset = &filament_presets->preset(f_index); int size = preset_bundle->filaments.size(); if (preset && filament_name.compare(preset->name) == 0) { pPresetFilaInfo fila_info; fila_info.filament_id = preset->filament_id; fila_info.setting_id = preset->setting_id; fila_info.filament_type = preset->config.get_filament_type(fila_info.filament_display_type);; auto vendor_opt = dynamic_cast(preset->config.option("filament_vendor")); if (vendor_opt && (vendor_opt->values.size() > 0)) { fila_info.filament_vendor = vendor_opt->values[0]; } preset_filament_vec.push_back(fila_info); break; } } } } assert(preset_bundle->filament_presets.size() == preset_filament_vec.size()); if (preset_bundle->filament_presets.size() != preset_filament_vec.size()) { BOOST_LOG_TRIVIAL(error) << "sCollectPresetFilamentInfo size mismatch!"; } return preset_filament_vec; } void SelectMachineDialog::clear_material_infos() { MaterialHash::iterator iter = m_materialList.begin(); while (iter != m_materialList.end()) { int id = iter->first; Material* item = iter->second; item->item->Destroy(); delete item; iter++; } m_materialList.clear(); m_sizer_ams_mapping->Clear(); m_filaments.clear(); m_filaments_map.clear(); } void SelectMachineDialog::on_material_item_clicked(MaterialItem* item, const std::vector& preset_fila_infos, int used_filament_idx, wxMouseEvent& e) { if (!item->m_enable || !m_check_flag || m_print_status == PrintDialogStatus::PrintStatusUnsupportedPrinter) { return;/*STUDIO-11301*/ } MaterialHash::iterator iter = m_materialList.begin(); while (iter != m_materialList.end()) { int id = iter->first; Material* item = iter->second; MaterialItem* m = item->item; m->on_normal(); iter++; } m_current_filament_id = used_filament_idx; item->on_selected(); auto mouse_pos = ClientToScreen(e.GetPosition()); wxPoint rect = item->ClientToScreen(wxPoint(0, 0)); // update ams data DeviceManager* dev_manager = Slic3r::GUI::wxGetApp().getDeviceManager(); if (!dev_manager) return; MachineObject* obj_ = dev_manager->get_selected_machine(); if (obj_) { if (m_mapping_popup.IsShown()) return; if (preset_fila_infos.size() <= used_filament_idx) return; if (obj_ && obj_->get_dev_id() == m_printer_last_select) { m_mapping_popup.set_parent_item(item); m_mapping_popup.set_current_filament_id(used_filament_idx); m_mapping_popup.set_tag_texture(preset_fila_infos[used_filament_idx].filament_type); m_mapping_popup.set_send_win(this);//fix bug:fisrt click is not valid m_mapping_popup.set_show_type(get_filament_mapping_show_type(obj_, used_filament_idx)); m_mapping_popup.update(obj_, m_ams_mapping_result, use_dynamic_nozzle_map(), m_print_type); m_mapping_popup.Popup(); } } } void SelectMachineDialog::reset_and_sync_ams_list() { // Reset clear_material_infos(); if (!wxGetApp().preset_bundle) { return; } // extruder numbers const auto& full_config = wxGetApp().preset_bundle->full_config(); size_t extruder_nums = full_config.option("nozzle_diameter")->values.size(); // filament info from preset bundle const auto& preset_filament_vec = sCollectPresetFilamentInfo(); // filament map & used filaments const auto& project_config = wxGetApp().preset_bundle->project_config; m_filaments_map = wxGetApp().plater()->get_partplate_list().get_curr_plate()->get_real_filament_maps(project_config); const auto& used_filaments = wxGetApp().plater()->get_partplate_list().get_curr_plate()->get_used_filaments(); bool selected_any = false; MaterialItem *first_enabled = nullptr; int first_enabled_id = -1; int left_sizer_count = 0, right_sizer_count = 0, sizer_count = 0; BitmapCache bmcache; auto dev = wxGetApp().getDeviceManager()->get_selected_machine(); bool has_switcher = dev && dev->GetFilaSwitch()->IsInstalled(); auto filament_color_render_info = wxGetApp().plater()->get_filament_colors_render_info(); auto filament_color_type_info = wxGetApp().plater()->get_filament_color_render_type(); for (auto i = 0; i < used_filaments.size(); i++) { auto used_filament = used_filaments[i] - 1; if (used_filament >= preset_filament_vec.size()) { BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << " used_filament index out of range: " << used_filament; continue; }; const auto& preset_filament = preset_filament_vec[used_filament]; auto colour = wxGetApp().preset_bundle->project_config.opt_string("filament_colour", (unsigned int)used_filament); unsigned char rgb[4]; bmcache.parse_color4(colour, rgb); auto colour_rgb = wxColour((int) rgb[0], (int) rgb[1], (int) rgb[2], (int) rgb[3]); // skip mixed/blended filaments whose filament_map is unset (0) if ((extruder_nums >= 2) && (used_filament < (int)m_filaments_map.size()) && m_filaments_map[used_filament] == 0) { continue; } // create material item MaterialItem* item = nullptr; if (extruder_nums == 2 && !has_switcher) { if (m_filaments_map[used_filament] == 1) { item = new MaterialItem(m_filament_left_panel, colour_rgb, preset_filament.filament_display_type, preset_filament.filament_id); m_sizer_ams_mapping_left->Add(item, 0, wxALL, FromDIP(5)); left_sizer_count++; } else if (m_filaments_map[used_filament] == 2) { item = new MaterialItem(m_filament_right_panel, colour_rgb, preset_filament.filament_display_type, preset_filament.filament_id); m_sizer_ams_mapping_right->Add(item, 0, wxALL, FromDIP(5)); right_sizer_count++; } } else { item = new MaterialItem(m_filament_panel, colour_rgb, preset_filament.filament_display_type, preset_filament.filament_id); m_sizer_ams_mapping->Add(item, 0, wxALL, FromDIP(5)); sizer_count++; } if (!item) { continue; } if (used_filament < filament_color_render_info.size() && used_filament < filament_color_type_info.size()) { auto color_strs = Slic3r::split_string(filament_color_render_info[used_filament], ' '); if (color_strs.size() > 1) { int ctype = (filament_color_type_info[used_filament] == "0") ? 0 : 1; std::vector cols; for (const auto& cs : color_strs) { wxColour c(cs); if (c.IsOk()) cols.push_back(c); } if (cols.size() > 1) item->set_material_cols(ctype, cols); } } item->SetToolTip(_L("Upper half area: Original\nLower half area: Filament in AMS\nAnd you can click it to modify")); if (!selected_any && used_filament == m_current_filament_id && item->m_enable) { item->on_selected(); selected_any = true; } if (!first_enabled && item->m_enable) { first_enabled = item; first_enabled_id = used_filament; } item->Bind(wxEVT_LEFT_UP, [this](wxMouseEvent &e) {}); item->Bind(wxEVT_LEFT_DOWN, [this, item, used_filament, preset_filament_vec](wxMouseEvent &e) { on_material_item_clicked(item, preset_filament_vec, used_filament, e);} ); Material *material_item = new Material(); material_item->id = used_filament; material_item->item = item; m_materialList[i] = material_item; // build for ams mapping FilamentInfo info; info.id = used_filament; info.type = preset_filament.filament_type; info.brand = preset_filament.filament_vendor; info.filament_id = preset_filament.filament_id; info.setting_id = preset_filament.setting_id; info.color = wxString::Format("#%02X%02X%02X%02X", colour_rgb.Red(), colour_rgb.Green(), colour_rgb.Blue(), colour_rgb.Alpha()).ToStdString(); m_filaments.push_back(info); } if (!selected_any && first_enabled) { m_current_filament_id = first_enabled_id; first_enabled->on_selected(); } if (sizer_count > 0) { m_sizer_ams_mapping->SetCols(8); m_sizer_ams_mapping->Layout(); m_filament_panel_sizer->Layout(); } if (left_sizer_count > 0) { m_sizer_ams_mapping_left->SetCols(4); m_sizer_ams_mapping_left->Layout(); m_filament_panel_left_sizer->Layout(); m_filament_left_panel->Layout(); } if (right_sizer_count > 0) { m_sizer_ams_mapping_right->SetCols(4); m_sizer_ams_mapping_right->Layout(); m_filament_panel_right_sizer->Layout(); m_filament_right_panel->Layout(); } m_filament_panel->Show(sizer_count > 0); m_filament_left_panel->Show(left_sizer_count > 0 || right_sizer_count > 0); m_filament_right_panel->Show(left_sizer_count > 0 || right_sizer_count > 0); // reset_ams_material();//show "-" } void SelectMachineDialog::clone_thumbnail_data() { //record preview_colors MaterialHash::iterator iter = m_materialList.begin(); if (m_preview_colors_in_thumbnail.size() != m_materialList.size()) { m_preview_colors_in_thumbnail.resize(m_materialList.size()); } while (iter != m_materialList.end()) { int id = iter->first; Material * item = iter->second; MaterialItem *m = item->item; m_preview_colors_in_thumbnail[id] = m->m_material_coloul; if (item->id < m_cur_colors_in_thumbnail.size()) { m_cur_colors_in_thumbnail[item->id] = m->m_ams_coloul; } else {//exist empty or unrecognized type ams in machine m_cur_colors_in_thumbnail.resize(item->id + 1); m_cur_colors_in_thumbnail[item->id] = m->m_ams_coloul; } iter++; } // Expand color arrays to cover mixed (virtual) slots and compute their blended colors const auto& cfg = wxGetApp().preset_bundle->project_config; size_t total = 0; if (auto* opt = cfg.option("filament_is_mixed")) total = opt->values.size(); size_t target = std::max(total, m_cur_colors_in_thumbnail.size()); if (m_cur_colors_in_thumbnail.size() < target) m_cur_colors_in_thumbnail.resize(target); if (m_preview_colors_in_thumbnail.size() < target) m_preview_colors_in_thumbnail.resize(target); recompute_mixed_slot_colors(m_preview_colors_in_thumbnail, cfg); recompute_mixed_slot_colors(m_cur_colors_in_thumbnail, cfg); //copy data auto &data = m_cur_input_thumbnail_data; m_preview_thumbnail_data.reset(); m_preview_thumbnail_data.set(data.width, data.height); if (data.width > 0 && data.height > 0) { for (unsigned int r = 0; r < data.height; ++r) { unsigned int rr = (data.height - 1 - r) * data.width; for (unsigned int c = 0; c < data.width; ++c) { unsigned char *origin_px = (unsigned char *) data.pixels.data() + 4 * (rr + c); unsigned char *new_px = (unsigned char *) m_preview_thumbnail_data.pixels.data() + 4 * (rr + c); for (size_t i = 0; i < 4; i++) { new_px[i] = origin_px[i]; } } } } //record_edge_pixels_data record_edge_pixels_data(); } void SelectMachineDialog::record_edge_pixels_data() { auto is_not_in_preview_colors = [this](unsigned char r, unsigned char g , unsigned char b , unsigned char a) { for (size_t i = 0; i < m_preview_colors_in_thumbnail.size(); i++) { wxColour render_color = adjust_color_for_render(m_preview_colors_in_thumbnail[i]); if (render_color.Red() == r && render_color.Green() == g && render_color.Blue() == b /*&& render_color.Alpha() == a*/) { return false; } } return true; }; ThumbnailData &data = m_cur_no_light_thumbnail_data; ThumbnailData &origin_data = m_cur_input_thumbnail_data; if (data.width > 0 && data.height > 0 && (data.width == origin_data.width && data.height == origin_data.height) ) { m_edge_pixels.resize(data.width * data.height); for (unsigned int r = 0; r < data.height; ++r) { unsigned int rr = (data.height - 1 - r) * data.width; for (unsigned int c = 0; c < data.width; ++c) { unsigned char *no_light_px = (unsigned char *) data.pixels.data() + 4 * (rr + c); unsigned char *origin_px = (unsigned char *) origin_data.pixels.data() + 4 * (rr + c); m_edge_pixels[r * data.width + c] = false; if (origin_px[3] > 0) { if (is_not_in_preview_colors(no_light_px[0], no_light_px[1], no_light_px[2], origin_px[3])) { m_edge_pixels[r * data.width + c] = true; } } } } } } wxColour SelectMachineDialog::adjust_color_for_render(const wxColour &color) { std::array _temp_color_color = {color.Red() / 255.0f, color.Green() / 255.0f, color.Blue() / 255.0f, color.Alpha() / 255.0f}; auto _temp_color_color_ = adjust_color_for_rendering(_temp_color_color); wxColour render_color((int) (_temp_color_color_[0] * 255.0f), (int) (_temp_color_color_[1] * 255.0f), (int) (_temp_color_color_[2] * 255.0f), (int) (_temp_color_color_[3] * 255.0f)); return render_color; } void SelectMachineDialog::final_deal_edge_pixels_data(ThumbnailData &data) { if (data.width > 0 && data.height > 0 && m_edge_pixels.size() >0 ) { for (unsigned int r = 0; r < data.height; ++r) { unsigned int rr = (data.height - 1 - r) * data.width; bool exist_rr_up = r >= 1 ? true : false; bool exist_rr_down = r <= data.height - 2 ? true : false; unsigned int rr_up = exist_rr_up ? (data.height - 1 - (r - 1)) * data.width : 0; unsigned int rr_down = exist_rr_down ? (data.height - 1 - (r + 1)) * data.width : 0; for (unsigned int c = 0; c < data.width; ++c) { bool exist_c_left = c >= 1 ? true : false; bool exist_c_right = c <= data.width - 2 ? true : false; unsigned int c_left = exist_c_left ? c - 1 : 0; unsigned int c_right = exist_c_right ? c + 1 : 0; unsigned char *cur_px = (unsigned char *) data.pixels.data() + 4 * (rr + c); unsigned char *relational_pxs[8] = {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr}; if (exist_rr_up && exist_c_left) { relational_pxs[0] = (unsigned char *) data.pixels.data() + 4 * (rr_up + c_left); } if (exist_rr_up) { relational_pxs[1] = (unsigned char *) data.pixels.data() + 4 * (rr_up + c); } if (exist_rr_up && exist_c_right) { relational_pxs[2] = (unsigned char *) data.pixels.data() + 4 * (rr_up + c_right); } if (exist_c_left) { relational_pxs[3] = (unsigned char *) data.pixels.data() + 4 * (rr + c_left); } if (exist_c_right) { relational_pxs[4] = (unsigned char *) data.pixels.data() + 4 * (rr + c_right); } if (exist_rr_down && exist_c_left) { relational_pxs[5] = (unsigned char *) data.pixels.data() + 4 * (rr_down + c_left); } if (exist_rr_down) { relational_pxs[6] = (unsigned char *) data.pixels.data() + 4 * (rr_down + c); } if (exist_rr_down && exist_c_right) { relational_pxs[7] = (unsigned char *) data.pixels.data() + 4 * (rr_down + c_right); } if (cur_px[3] > 0 && m_edge_pixels[r * data.width + c]) { int rgba_sum[4] = {0, 0, 0, 0}; int valid_count = 0; for (size_t k = 0; k < 8; k++) { if (relational_pxs[k]) { if (k == 0 && m_edge_pixels[(r - 1) * data.width + c_left]) { continue; } if (k == 1 && m_edge_pixels[(r - 1) * data.width + c]) { continue; } if (k == 2 && m_edge_pixels[(r - 1) * data.width + c_right]) { continue; } if (k == 3 && m_edge_pixels[r * data.width + c_left]) { continue; } if (k == 4 && m_edge_pixels[r * data.width + c_right]) { continue; } if (k == 5 && m_edge_pixels[(r + 1) * data.width + c_left]) { continue; } if (k == 6 && m_edge_pixels[(r + 1) * data.width + c]) { continue; } if (k == 7 && m_edge_pixels[(r + 1) * data.width + c_right]) { continue; } for (size_t m = 0; m < 4; m++) { rgba_sum[m] += relational_pxs[k][m]; } valid_count++; } } if (valid_count > 0) { for (size_t m = 0; m < 4; m++) { cur_px[m] = std::clamp(int(rgba_sum[m] / (float)valid_count), 0, 255); } } } } } } } void SelectMachineDialog::updata_thumbnail_data_after_connected_printer() { // change thumbnail_data ThumbnailData &input_data = m_plater->get_partplate_list().get_curr_plate()->thumbnail_data; ThumbnailData &no_light_data = m_plater->get_partplate_list().get_curr_plate()->no_light_thumbnail_data; if (input_data.width == 0 || input_data.height == 0 || no_light_data.width == 0 || no_light_data.height == 0) { wxGetApp().plater()->update_all_plate_thumbnails(false); } unify_deal_thumbnail_data(input_data, no_light_data); } void SelectMachineDialog::unify_deal_thumbnail_data(ThumbnailData &input_data, ThumbnailData &no_light_data) { if (input_data.width == 0 || input_data.height == 0 || no_light_data.width == 0 || no_light_data.height == 0) { BOOST_LOG_TRIVIAL(error) << "SelectMachineDialog::no_light_data is empty,error"; return; } m_cur_input_thumbnail_data = input_data; m_cur_no_light_thumbnail_data = no_light_data; clone_thumbnail_data(); MaterialHash::iterator iter = m_materialList.begin(); if (m_cur_colors_in_thumbnail.size() > 0) { change_default_normal(-1, wxColour()); final_deal_edge_pixels_data(m_preview_thumbnail_data); set_default_normal(m_preview_thumbnail_data); } else { set_default_normal(input_data); } } void SelectMachineDialog::change_default_normal(int old_filament_id, wxColour temp_ams_color) { if (m_cur_colors_in_thumbnail.size() == 0) { BOOST_LOG_TRIVIAL(error) << "SelectMachineDialog::change_default_normal:error:m_cur_colors_in_thumbnail.size() == 0"; return; } if (old_filament_id >= 0) { if (old_filament_id < m_cur_colors_in_thumbnail.size()) { m_cur_colors_in_thumbnail[old_filament_id] = temp_ams_color; } else { BOOST_LOG_TRIVIAL(error) << "SelectMachineDialog::change_default_normal:error:old_filament_id > m_cur_colors_in_thumbnail.size()"; return; } } // Recompute mixed slot colors after physical slot color change const auto& cfg = wxGetApp().preset_bundle->project_config; recompute_mixed_slot_colors(m_cur_colors_in_thumbnail, cfg); ThumbnailData& data = m_cur_input_thumbnail_data; ThumbnailData& no_light_data = m_cur_no_light_thumbnail_data; if (data.width > 0 && data.height > 0 && data.width == no_light_data.width && data.height == no_light_data.height) { for (unsigned int r = 0; r < data.height; ++r) { unsigned int rr = (data.height - 1 - r) * data.width; for (unsigned int c = 0; c < data.width; ++c) { unsigned char *no_light_px = (unsigned char *) no_light_data.pixels.data() + 4 * (rr + c); unsigned char *origin_px = (unsigned char *) data.pixels.data() + 4 * (rr + c); unsigned char *new_px = (unsigned char *) m_preview_thumbnail_data.pixels.data() + 4 * (rr + c); if (origin_px[3] > 0 && m_edge_pixels[r * data.width + c] == false) { auto filament_id = 255 - no_light_px[3]; if (filament_id >= m_cur_colors_in_thumbnail.size()) { continue; } wxColour temp_ams_color_in_loop = m_cur_colors_in_thumbnail[filament_id]; wxColour ams_color = adjust_color_for_render(temp_ams_color_in_loop); //change color new_px[3] = ams_color.Alpha(); //origin_px[3]; // alpha int origin_rgb = origin_px[0] + origin_px[1] + origin_px[2]; int no_light_px_rgb = no_light_px[0] + no_light_px[1] + no_light_px[2]; unsigned char i = 0; if (origin_rgb >= no_light_px_rgb) {//Brighten up unsigned char cur_single_color = ams_color.Red(); new_px[i] = std::clamp(cur_single_color + (origin_px[i] - no_light_px[i]), 0, 255); i++; cur_single_color = ams_color.Green(); new_px[i] = std::clamp(cur_single_color + (origin_px[i] - no_light_px[i]), 0, 255); i++; cur_single_color = ams_color.Blue(); new_px[i] = std::clamp(cur_single_color + (origin_px[i] - no_light_px[i]), 0, 255); } else {//Dimming float ratio = origin_rgb / (float) no_light_px_rgb; unsigned char cur_single_color = ams_color.Red(); new_px[i] = std::clamp((int)(cur_single_color * ratio), 0, 255); i++; cur_single_color = ams_color.Green(); new_px[i] = std::clamp((int) (cur_single_color * ratio), 0, 255); i++; cur_single_color = ams_color.Blue(); new_px[i] = std::clamp((int) (cur_single_color * ratio), 0, 255); } } } } } else { BOOST_LOG_TRIVIAL(error) << "SelectMachineDialog::change_defa:no_light_data is empty,error"; } } void SelectMachineDialog::set_default_normal(const ThumbnailData &data) { update_page_turn_state(false); if (data.is_valid()) { wxImage image(data.width, data.height); image.InitAlpha(); for (unsigned int r = 0; r < data.height; ++r) { unsigned int rr = (data.height - 1 - r) * data.width; for (unsigned int c = 0; c < data.width; ++c) { unsigned char *px = (unsigned char *) data.pixels.data() + 4 * (rr + c); image.SetRGB((int) c, (int) r, px[0], px[1], px[2]); image.SetAlpha((int) c, (int) r, px[3]); } } image = image.Rescale(FromDIP(198), FromDIP(198)); m_thumbnailPanel->set_thumbnail(image); } m_basic_panel->Layout(); m_basic_panel->Fit(); // disable pei bed DeviceManager *dev_manager = Slic3r::GUI::wxGetApp().getDeviceManager(); if (!dev_manager) return; MachineObject *obj_ = dev_manager->get_selected_machine(); wxSize screenSize = wxGetDisplaySize(); auto dialogSize = this->GetSize(); #ifdef __WINDOWS__ #endif // __WXOSX_MAC__ // basic info auto aprint_stats = m_plater->get_partplate_list().get_current_fff_print().print_statistics(); wxString time; PartPlate *plate = m_plater->get_partplate_list().get_curr_plate(); if (plate) { if (plate->get_slice_result()) { time = wxString::Format("%s", short_time(get_time_dhms(plate->get_slice_result()->print_statistics.modes[0].time))); } } char weight[64]; if (wxGetApp().app_config->get("use_inches") == "1") { ::sprintf(weight, "%.2f oz", aprint_stats.total_weight * 0.035274); } else { ::sprintf(weight, "%.2f g", aprint_stats.total_weight); } m_stext_time->SetLabel(time); m_stext_weight->SetLabel(weight); refresh_print_time(obj_); } wxString SelectMachineDialog::FormatTime(float totalSeconds) { totalSeconds = std::abs(totalSeconds); int secs = static_cast(std::round(totalSeconds)); int hours = secs / 3600; int remaining = secs % 3600; int minutes = remaining / 60; int seconds = remaining % 60; wxString timeStr; if (hours > 0) { timeStr = wxString::Format("%dm%ds", hours * 60 + minutes, seconds); } else if (minutes > 0) { timeStr = wxString::Format("%dm%ds", minutes, seconds); } else { timeStr = wxString::Format("%ds", seconds); } return timeStr; } static wxSize s_get_full_resolution() { wxDisplay display; wxRect screenRect = display.GetGeometry(); return wxSize(screenRect.GetWidth(), screenRect.GetHeight()); } void SelectMachineDialog::set_default_from_sdcard() { DeviceManager *dev_manager = Slic3r::GUI::wxGetApp().getDeviceManager(); if (!dev_manager) return; MachineObject *obj_ = dev_manager->get_selected_machine(); if (!obj_) { return; }; m_printer_box->SetPrinterName(wxString::FromUTF8(obj_->get_dev_name())); m_print_plate_total = m_required_data_plate_data_list.size(); update_page_turn_state(true); ThumbnailData& data = m_required_data_plate_data_list[m_print_plate_idx]->plate_thumbnail; if (data.pixels.size() > 0) { wxMemoryInputStream mis((unsigned char*)data.pixels.data(), data.pixels.size()); wxImage image = wxImage(mis); image = image.Rescale(FromDIP(198), FromDIP(198)); m_thumbnailPanel->set_thumbnail(image); } //for black list std::vector materials; std::vector brands; std::vector display_materials; for (auto i = 0; i < m_required_data_plate_data_list[m_print_plate_idx]->slice_filaments_info.size(); i++) { FilamentInfo fo = m_required_data_plate_data_list[m_print_plate_idx]->slice_filaments_info[i]; display_materials.push_back(fo.type); materials.push_back(fo.type); brands.push_back(fo.brand); } auto opt_nozzle_diameters = m_required_data_config.option("nozzle_diameter"); auto diameters_count = 1; if (opt_nozzle_diameters) { diameters_count = opt_nozzle_diameters->size(); } //std::vector filament_map; m_filaments_map.clear(); if (m_required_data_plate_data_list[m_print_plate_idx]->config.option("filament_map")){ m_filaments_map = m_required_data_plate_data_list[m_print_plate_idx]->config.option("filament_map")->values; } // ams mapping area auto dev = wxGetApp().getDeviceManager()->get_selected_machine(); bool has_switcher = dev && dev->GetFilaSwitch()->IsInstalled(); if (diameters_count > 1 && !has_switcher) { m_filament_left_panel->Show(); m_filament_right_panel->Show(); m_filament_panel->Hide(); m_sizer_ams_mapping_left->SetCols(4); m_sizer_ams_mapping_left->Layout(); m_filament_panel_left_sizer->Layout(); m_filament_left_panel->Layout(); m_sizer_ams_mapping_right->SetCols(4); m_sizer_ams_mapping_right->Layout(); m_filament_panel_right_sizer->Layout(); m_filament_right_panel->Layout(); } else { m_filament_left_panel->Hide(); m_filament_right_panel->Hide(); m_filament_panel->Show(); m_sizer_ams_mapping->SetCols(8); m_sizer_ams_mapping->Layout(); m_filament_panel_sizer->Layout(); } //init MaterialItem MaterialHash::iterator iter = m_materialList.begin(); while (iter != m_materialList.end()) { int id = iter->first; Material* item = iter->second; item->item->Destroy(); delete item; iter++; } m_ams_mapping_result.clear(); m_sizer_ams_mapping->Clear(); m_sizer_ams_mapping_left->Clear(); m_sizer_ams_mapping_right->Clear(); m_materialList.clear(); m_filaments.clear(); bool selected_any = false; MaterialItem *first_enabled = nullptr; int first_enabled_id = -1; auto filament_color_render_info2 = wxGetApp().plater()->get_filament_colors_render_info(); auto filament_color_type_info2 = wxGetApp().plater()->get_filament_color_render_type(); for (auto i = 0; i < m_required_data_plate_data_list[m_print_plate_idx]->slice_filaments_info.size(); i++) { FilamentInfo fo = m_required_data_plate_data_list[m_print_plate_idx]->slice_filaments_info[i]; MaterialItem* item = nullptr; if (diameters_count > 1 && !has_switcher) { if (fo.id < m_filaments_map.size()) { auto nozzle_id = m_filaments_map[fo.id]; if (nozzle_id == 1) { item = new MaterialItem(m_filament_left_panel, wxColour(fo.color), fo.get_display_filament_type(), fo.filament_id); m_sizer_ams_mapping_left->Add(item, 0, wxALL, FromDIP(5)); } else if (nozzle_id == 2) { item = new MaterialItem(m_filament_right_panel, wxColour(fo.color), fo.get_display_filament_type(), fo.filament_id); m_sizer_ams_mapping_right->Add(item, 0, wxALL, FromDIP(5)); } } } else { item = new MaterialItem(m_filament_panel, wxColour(fo.color), fo.get_display_filament_type(), fo.filament_id); m_sizer_ams_mapping->Add(item, 0, wxALL, FromDIP(5)); } if (!item) continue; if (fo.id < (int)filament_color_render_info2.size() && fo.id < (int)filament_color_type_info2.size()) { auto color_strs = Slic3r::split_string(filament_color_render_info2[fo.id], ' '); if (color_strs.size() > 1) { int ctype = (filament_color_type_info2[fo.id] == "0") ? 0 : 1; std::vector cols; for (const auto& cs : color_strs) { wxColour c(cs); if (c.IsOk()) cols.push_back(c); } if (cols.size() > 1) item->set_material_cols(ctype, cols); } } if (!selected_any && fo.id == m_current_filament_id && item->m_enable) { item->on_selected(); selected_any = true; } if (!first_enabled && item->m_enable) { first_enabled = item; first_enabled_id = fo.id; } item->Bind(wxEVT_LEFT_UP, [this, item, materials](wxMouseEvent& e) {}); item->Bind(wxEVT_LEFT_DOWN, [this, obj_, item, materials, diameters_count, fo](wxMouseEvent& e) { if (!item->m_enable) { return; } if (!m_check_flag || m_print_status == PrintDialogStatus::PrintStatusUnsupportedPrinter) { return; } /*STUDIO-11301*/ MaterialHash::iterator iter = m_materialList.begin(); while (iter != m_materialList.end()) { int id = iter->first; Material* item = iter->second; MaterialItem* m = item->item; m->on_normal(); iter++; } try { m_current_filament_id = fo.id; } catch (...) {} item->on_selected(); auto mouse_pos = ClientToScreen(e.GetPosition()); wxPoint rect = item->ClientToScreen(wxPoint(0, 0)); if (m_mapping_popup.IsShown()) { return; }; if (obj_ && obj_->get_dev_id() == m_printer_last_select) { m_mapping_popup.set_show_type(get_filament_mapping_show_type(obj_, m_current_filament_id)); m_mapping_popup.set_parent_item(item); m_mapping_popup.set_current_filament_id(fo.id); m_mapping_popup.set_tag_texture(fo.type); m_mapping_popup.set_send_win(this); m_mapping_popup.update(obj_, m_ams_mapping_result); m_mapping_popup.Popup(); } }); Material* material_item = new Material(); material_item->id = fo.id; material_item->item = item; m_materialList[i] = material_item; // build for ams mapping m_filaments.push_back(fo); } m_basic_panel->Layout(); m_basic_panel->Fit(); wxSize screenSize = wxGetDisplaySize(); auto dialogSize = this->GetSize(); if (!selected_any && first_enabled) { m_current_filament_id = first_enabled_id; first_enabled->on_selected(); } reset_ams_material(); // basic info try { float float_time = std::stof(m_required_data_plate_data_list[m_print_plate_idx]->get_gcode_prediction_str()); double float_weight = std::stof(m_required_data_plate_data_list[m_print_plate_idx]->get_gcode_weight_str()); wxString time; time = wxString::Format("%s", short_time(get_time_dhms(float_time))); char weight[64]; ::sprintf(weight, " %.2f g", float_weight); m_stext_time->SetLabel(time); m_stext_weight->SetLabel(weight); // auto save_time = get_filament_change_gap_time(obj_); refresh_print_time(obj_); } catch (...) {} } void SelectMachineDialog::update_page_turn_state(bool show) { m_bitmap_last_plate->Show(show); m_bitmap_next_plate->Show(show); if (show) { if (m_print_plate_idx <= 0) { m_bitmap_last_plate->Disable(); } else { m_bitmap_last_plate->Enable(); } if ((m_print_plate_idx + 1) >= m_print_plate_total) { m_bitmap_next_plate->Disable(); } else { m_bitmap_next_plate->Enable(); } if (m_print_plate_total == 1) { m_bitmap_last_plate->Show(false); m_bitmap_next_plate->Show(false); } } } void SelectMachineDialog::sys_color_changed() { if (wxGetApp(). dark_mode()) { //rename_button->SetIcon("ams_editable_light"); m_rename_button->SetBitmap(rename_editable_light->bmp()); } else { m_rename_button->SetBitmap(rename_editable->bmp()); } m_rename_button->Refresh(); } bool SelectMachineDialog::Show(bool show) { if (show) { EnableEditing(true); m_options_other->Show(); m_refresh_timer->Start(LIST_REFRESH_INTERVAL); //set a default machine when obj is null if (DeviceManager* dev = Slic3r::GUI::wxGetApp().getDeviceManager()) { if (!dev->get_selected_machine()) { dev->load_last_machine(); } }; } else { m_refresh_timer->Stop(); return DPIDialog::Show(false); } m_ams_mapping_result.clear(); show_status(PrintDialogStatus::PrintStatusInit); PartPlate* curr_plate = m_plater->get_partplate_list().get_curr_plate(); BedType bed_type_value = curr_plate ? curr_plate->get_bed_type(true) : btDefault; const t_config_enum_values &enum_keys_map = ConfigOptionEnum::get_enum_values(); std::string plate_name; for (auto& elem : enum_keys_map) { if (elem.second == bed_type_value) plate_name = elem.first; } m_printer_box->UpdatePlate(plate_name); // set default value when show this dialog wxGetApp().UpdateDlgDarkUI(this); wxGetApp().reset_to_active(); set_default(); update_user_machine_list(); update_by_obj(get_current_machine()); Layout(); Fit(); CenterOnParent(); return DPIDialog::Show(show); } SelectMachineDialog::~SelectMachineDialog() { delete m_refresh_timer; } void SelectMachineDialog::UpdateStatusCheckWarning_ExtensionTool(MachineObject* obj_) { if (!obj_) { return; } if (!DevPrinterConfigUtil::support_print_check_extension_fan_f000_mounted(obj_->printer_type)) { return; } if (auto extension_tool = obj_->GetExtensionTool().lock()) { if (!extension_tool->IsToolTypeFanF000() || !extension_tool->IsMounted() ) { for (const FilamentInfo& item : m_ams_mapping_result) { auto filament_info = wxGetApp().preset_bundle->get_filament_by_filament_id(item.filament_id); if (filament_info && (filament_info->temperature_vitrification <= 50)) { show_status(PrintDialogStatus::PrintStatusToolHeadCoolingFanWarning, { _L("Install toolhead enhanced cooling fan to prevent filament softening.")}, wxEmptyString, prePrintInfoStyle::BtnInstallFanF000); return; } } } } } // return true if no error bool SelectMachineDialog::CheckErrorRackStatus(MachineObject* obj_) { if (!obj_) { return true; } auto rack = obj_->GetNozzleSystem()->GetNozzleRack(); if (!rack->IsSupported()) { return true; } if (rack->GetReadingCount() > 0) { const wxString& msg = wxString::Format(_L("Refreshing information of hotends(%d/%d)."), rack->GetReadingIdx(), rack->GetReadingCount()); show_status(PrintStatusRackReading, { msg + " " + _L("Please wait a moment...")}); return false; } return true; } void SelectMachineDialog::CheckWarningRackStatus(MachineObject* obj_) { if (!obj_) { return; } const auto& nozzle_sys = obj_->GetNozzleSystem(); const auto& rack = nozzle_sys->GetNozzleRack(); if (!rack->IsSupported()) { return; } auto nozzle_group_res = DevUtilBackend::GetNozzleGroupResult(m_plater); if (!nozzle_group_res) { return; } if (m_print_type != FROM_NORMAL) { return;// there are no slicing data when print from sdcard } const std::vector& nozzle_vec = nozzle_group_res->get_used_nozzles_in_extruder(LOGIC_R_EXTRUDER_ID); if (nozzle_vec.empty()) { return;// no need to check if no right nozzles used in slicing } std::unordered_map need_nozzle_map; for (auto slicing_nozzle : nozzle_vec) { try { NozzleDef data; data.nozzle_diameter = boost::lexical_cast(slicing_nozzle.diameter); data.nozzle_flow_type = DevNozzle::ToNozzleFlowType(slicing_nozzle.volume_type); need_nozzle_map[data]++; } catch (const std::exception& e) { assert(0); BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << "exception: " << e.what(); } } for (auto need_nozzle : need_nozzle_map) { auto nozzle_info = need_nozzle.first; const auto& installed_nozzles = nozzle_sys->CollectNozzles(MAIN_EXTRUDER_ID, nozzle_info.nozzle_flow_type, nozzle_info.nozzle_diameter); int installed_count = installed_nozzles.size(); int installed_reliable_count = 0; for (const auto& nozzle : installed_nozzles) { if (nozzle.IsInfoReliable()) { installed_reliable_count++; } } // check if enough nozzles installed if (need_nozzle.second > installed_count) { if (nozzle_sys->GetNozzleRack()->GetCaliStatus() != DevNozzleRack::Rack_CALI_OK) { show_status(PrintStatusRackNozzleNumUnmeetWarning, { (S_RACK_NOZZLE_NOT_ENOUGH + " " + S_RACK_NOZZLE_SUGEEST_CALI) }, wxEmptyString, prePrintInfoStyle::NozzleState); } else if (nozzle_sys->HasUnknownNozzles()) { show_status(PrintStatusRackNozzleNumUnmeetWarning, { (S_RACK_NOZZLE_NOT_ENOUGH + " " + S_RACK_NOZZLE_SUGEEST_REFRESH) }, wxEmptyString, prePrintInfoStyle::NozzleState | prePrintInfoStyle::BtnNozzleRefresh); } else { show_status(PrintStatusRackNozzleNumUnmeetWarning, { (S_RACK_NOZZLE_NOT_ENOUGH + " " + S_RACK_NOZZLE_SUGEEST_RESLICE_FILA) }, wxEmptyString, prePrintInfoStyle::NozzleState); } break; } // check if unreliable nozzle maybe used if (need_nozzle.second > installed_reliable_count && nozzle_sys->HasUnreliableNozzles()) { show_status(PrintStatusHasUnreliableNozzleWarning, {}, wxEmptyString, prePrintInfoStyle::BtnNozzleRefresh | prePrintInfoStyle::BtnConfirmNotShowAgain); } } } // return key-> logic extruder idx, value-> nozzle data // for example: // {0, {0.4, S_FLOW}}, {1, {0.8, H_FLOW}} // {0, {0.4, S_FLOW}}, {0, {0.4, H_FLOW}} // hybrid static std::unordered_multimap s_get_slicing_extuder_nozzles() { std::unordered_multimap used_extuder_nozzles; PresetBundle* preset_bundle = wxGetApp().preset_bundle; if (!preset_bundle) { return used_extuder_nozzles; } PartPlate* cur_plate = wxGetApp().plater()->get_partplate_list().get_curr_plate(); if (!cur_plate) { return used_extuder_nozzles; } auto opt_nozzle_diameters = preset_bundle->printers.get_edited_preset().config.option("nozzle_diameter"); if (!opt_nozzle_diameters) { return used_extuder_nozzles; } auto nozzle_volume_type_opt = dynamic_cast(preset_bundle->project_config.option("nozzle_volume_type")); if (!nozzle_volume_type_opt) { return used_extuder_nozzles; } try { auto nozzle_group_res = DevUtilBackend::GetNozzleGroupResult(wxGetApp().plater()); if (nozzle_group_res && nozzle_group_res->is_support_dynamic_nozzle_map() && nozzle_volume_type_opt->values.size() == 2) { const auto& used_nozzles = nozzle_group_res->get_used_nozzles_in_extruder(); for (const auto& used_nozzle : used_nozzles) { NozzleDef nozzle_data; nozzle_data.nozzle_diameter = s_get_diameter(used_nozzle.diameter); nozzle_data.nozzle_flow_type = DevNozzle::ToNozzleFlowType(used_nozzle.volume_type); if (used_nozzle.extruder_id == 0) { used_extuder_nozzles.insert({ DEPUTY_EXTRUDER_ID, nozzle_data }); } else if (used_nozzle.extruder_id == 1) { used_extuder_nozzles.insert({ MAIN_EXTRUDER_ID, nozzle_data }); } }; return used_extuder_nozzles; } const auto& used_filament_idxs = cur_plate->get_used_filaments(); /*the index is started from 1*/ for (int used_filament_idx : used_filament_idxs) { int physical_idx = cur_plate->get_physical_extruder_by_filament_id(preset_bundle->full_config(), used_filament_idx); int logic_extruder_idx = cur_plate->get_logical_extruder_by_filament_id(preset_bundle->full_config(), used_filament_idx); NozzleDef nozzle_data; nozzle_data.nozzle_diameter = float(opt_nozzle_diameters->get_at(logic_extruder_idx)); nozzle_data.nozzle_flow_type = NozzleFlowType::S_FLOW;// default value auto volume_type = (NozzleVolumeType)nozzle_volume_type_opt->get_at(logic_extruder_idx); if (volume_type != NozzleVolumeType::nvtHybrid) { nozzle_data.nozzle_flow_type = DevNozzle::ToNozzleFlowType(volume_type); } else if (volume_type == NozzleVolumeType::nvtHybrid) { if (used_extuder_nozzles.find(physical_idx) != used_extuder_nozzles.end()){ continue;// already collected } auto nozzle_group_res = DevUtilBackend::GetNozzleGroupResult(wxGetApp().plater()); assert(nozzle_group_res); if (nozzle_group_res) { const auto &nozzle_infos = DevUtilBackend::CollectNozzleInfo(nozzle_group_res.get(), logic_extruder_idx); for (const auto& nozzle_info : nozzle_infos) { nozzle_data.nozzle_flow_type = nozzle_info.first.nozzle_flow_type; used_extuder_nozzles.insert({ physical_idx, nozzle_data }); } continue; } } else { BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << "invalid nozzle volume type: " << int(volume_type); assert(0); } used_extuder_nozzles.insert({ physical_idx, nozzle_data }); }; } catch (const std::exception& e) { BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << "exception: " << e.what(); } return used_extuder_nozzles; } // Compare the extruder nozzle info between slicing file and installed on printer bool SelectMachineDialog::CheckErrorExtruderNozzleWithSlicing(MachineObject* obj_) { if (!obj_) { return false; } const auto& nozzle_sys = obj_->GetNozzleSystem(); if (m_print_type == FROM_NORMAL) { const auto& slicing_ext_nozzles = s_get_slicing_extuder_nozzles(); for (auto slicing_ext_nozzle : slicing_ext_nozzles) { int slicing_ext_idx = slicing_ext_nozzle.first; auto slicing_ext = slicing_ext_nozzle.second; auto installed_ext_nozzle = nozzle_sys->GetExtNozzle(slicing_ext_idx); // no need to check right extruder nozzle when using nozzle rack if (slicing_ext_idx == MAIN_EXTRUDER_ID && nozzle_sys->GetNozzleRack()->IsSupported()) { if (nozzle_sys->CollectNozzles(MAIN_EXTRUDER_ID, slicing_ext.nozzle_flow_type, slicing_ext.nozzle_diameter).empty()) { if (nozzle_sys->GetNozzleRack()->GetCaliStatus() != DevNozzleRack::Rack_CALI_OK) { show_status(PrintDialogStatus::PrintStatusNozzleNoMatchedHotends, { (S_RACK_NOZZLE_NO_MATCHED + " " + S_RACK_NOZZLE_SUGEEST_CALI) }, wxEmptyString, prePrintInfoStyle::NozzleState); } else if (nozzle_sys->HasUnknownNozzles() || nozzle_sys->HasUnreliableNozzles()) { show_status(PrintDialogStatus::PrintStatusNozzleNoMatchedHotends, { (S_RACK_NOZZLE_NO_MATCHED + " " + S_RACK_NOZZLE_SUGEEST_REFRESH) }, wxEmptyString, prePrintInfoStyle::NozzleState | prePrintInfoStyle::BtnNozzleRefresh); } else { show_status(PrintDialogStatus::PrintStatusNozzleNoMatchedHotends, { (S_RACK_NOZZLE_NO_MATCHED + " " + S_RACK_NOZZLE_SUGEEST_RESLICE) }, wxEmptyString, prePrintInfoStyle::NozzleState); } return false; } if (nozzle_sys->IsRackMaximumInstalled()) { show_status(PrintDialogStatus::PrintStatusNozzleRackMaximumInstalled, { _L("The toolhead and hotend rack are full. Please remove at least one hotend before printing.") }, wxEmptyString, prePrintInfoStyle::NozzleState); return false; } continue; } // check nozzle data valid { if (installed_ext_nozzle.GetNozzleType() == NozzleType::ntUndefine || installed_ext_nozzle.GetNozzleDiameter() <= 0.0f) { show_status(PrintDialogStatus::PrintStatusNozzleDataInvalid); return false; } if (obj_->is_nozzle_flow_type_supported() && installed_ext_nozzle.GetNozzleFlowType() == NozzleFlowType::NONE_FLOWTYPE) { show_status(PrintDialogStatus::PrintStatusNozzleDataInvalid); return false; } } // check nozzle diameter and flow type, NozzleStatePanel shows which part differs { const bool diameter_mismatch = slicing_ext.nozzle_diameter != installed_ext_nozzle.GetNozzleDiameter(); const bool flow_mismatch = obj_->is_nozzle_flow_type_supported() && slicing_ext.nozzle_flow_type != installed_ext_nozzle.GetNozzleFlowType(); if (diameter_mismatch || flow_mismatch) { // The two statuses differ for logging only const PrintDialogStatus status = diameter_mismatch ? PrintDialogStatus::PrintStatusNozzleDiameterMismatch : PrintDialogStatus::PrintStatusNozzleMatchInvalid; show_status(status, { _L("The nozzle type doesn't match. Please re-slice.") }, wxEmptyString, prePrintInfoStyle::NozzleState); return false; } } } } return true; } void SelectMachineDialog::on_flow_pa_caliation_option_changed(wxCommandEvent& event) { // update nozzle mapping result if flow pa calibration option changed DeviceManager* dev_ = Slic3r::GUI::wxGetApp().getDeviceManager(); MachineObject* obj_ = dev_ ? dev_->get_my_machine(m_printer_last_select) : nullptr; if (obj_ && m_plater && obj_->GetNozzleSystem()->GetNozzleRack()->IsSupported()) { if (use_dynamic_nozzle_map()) { return; } auto nozzle_group_res = DevUtilBackend::GetNozzleGroupResult(m_plater); if (nozzle_group_res && !nozzle_group_res->get_used_nozzles_in_extruder(LOGIC_R_EXTRUDER_ID).empty()) { if (event.GetString() == "off") { MessageDialog dlg(this, S_RACK_FLOW_DYNAMICS_CALI_WARNING, _L("Info"), wxYES | wxCANCEL | wxICON_INFORMATION); dlg.SetButtonLabel(wxID_YES, _L("Keep it On")); dlg.SetButtonLabel(wxID_CANCEL, _L("Still turn it Off")); int rtn = dlg.ShowModal(); if (rtn == wxID_YES) { m_checkbox_list["flow_cali"]->restoreValue(); save_option_vals(); event.Skip(); return; } // STUDIO-15239 // request nozzle mapping result if right extruder nozzles used in slicing clear_nozzle_mapping(); obj_->get_nozzle_mapping_result()->CtrlGetAutoNozzleMappingV0(m_plater, m_ams_mapping_result, m_checkbox_list["flow_cali"]->getValueInt(), m_pa_value_switch->GetValue() ? 0 : 1); } } } if (obj_ && obj_->is_support_pa_mode) { if (event.GetString() == "off") m_pa_value_panel->Show(); else m_pa_value_panel->Hide(); Layout(); } event.Skip(); } void SelectMachineDialog::on_pa_value_switch_changed(wxCommandEvent &event) { DeviceManager *dev_ = Slic3r::GUI::wxGetApp().getDeviceManager(); MachineObject *obj_ = dev_ ? dev_->get_my_machine(m_printer_last_select) : nullptr; if (obj_ == nullptr) { event.Skip(); return; } if (!obj_->GetNozzleRack()->IsSupported()) { return; } if (use_dynamic_nozzle_map()) { return; } MessageDialog dlg(this, S_RACK_FLOW_DYNAMICS_CALI_WARNING, _L("Info"), wxYES | wxCANCEL | wxICON_INFORMATION); dlg.SetButtonLabel(wxID_YES, _L("OK")); dlg.SetButtonLabel(wxID_CANCEL, _L("Cancel")); int rtn = dlg.ShowModal(); if (rtn == wxID_CANCEL) { m_pa_value_switch->SetValue(!m_pa_value_switch->GetValue()); return; } event.Skip(); clear_nozzle_mapping(); obj_->get_nozzle_mapping_result()->CtrlGetAutoNozzleMappingV0(m_plater, m_ams_mapping_result, m_checkbox_list["flow_cali"]->getValueInt(), m_pa_value_switch->GetValue() ? 0 : 1); } void SelectMachineDialog::on_nozzle_offset_option_changed(wxCommandEvent& event) { DeviceManager* dev_ = Slic3r::GUI::wxGetApp().getDeviceManager(); MachineObject* obj_ = dev_ ? dev_->get_my_machine(m_printer_last_select) : nullptr; if (obj_ == nullptr) { event.Skip(); return; } if (!obj_->GetNozzleSystem()->GetNozzleRack()->IsSupported()) { event.Skip(); return; } if (event.GetString() == "off") { MessageDialog dlg(this, S_RACK_NOZZLE_OFFSET_CALI_WARNING, _L("Info"), wxYES | wxCANCEL | wxICON_INFORMATION); dlg.SetButtonLabel(wxID_YES, _L("Keep it On")); dlg.SetButtonLabel(wxID_CANCEL, _L("Still turn it Off")); int rtn = dlg.ShowModal(); if (rtn == wxID_YES) { m_checkbox_list["nozzle_offset_cali"]->restoreValue(); save_option_vals(); } } event.Skip(); } // key: nozzle pos id, value: nozzle std::map SelectMachineDialog::get_mapped_nozzles(int fila_id) const { std::map nozzle_map; auto obj_ = get_current_machine(); if (!obj_) { return nozzle_map; } int total_ext_count = 0; if (m_print_type == FROM_NORMAL) { const auto& full_config = wxGetApp().preset_bundle->full_config(); const auto opt = full_config.option("nozzle_diameter"); total_ext_count = opt ? opt->values.size() : 0; } else { const auto opt = m_required_data_config.option("nozzle_diameter"); total_ext_count = opt ? opt->values.size() : 0; } if (total_ext_count != obj_->GetExtderSystem()->GetTotalExtderCount()) { BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << ": total_ext_count not match"; return nozzle_map; } if (!obj_->GetNozzleRack()->IsSupported() && !use_dynamic_nozzle_map()) { if (total_ext_count == 1) { nozzle_map[MAIN_EXTRUDER_ID] = obj_->GetNozzleSystem()->GetNozzleByPosId(MAIN_EXTRUDER_ID); } else if (total_ext_count == 2) { if (m_filaments_map.size() > fila_id) { if (m_filaments_map[fila_id] == 1) { nozzle_map[DEPUTY_EXTRUDER_ID] = obj_->GetNozzleSystem()->GetNozzleByPosId(DEPUTY_EXTRUDER_ID); } else if (m_filaments_map[fila_id] == 2) { nozzle_map[MAIN_EXTRUDER_ID] = obj_->GetNozzleSystem()->GetNozzleByPosId(MAIN_EXTRUDER_ID); }; } }; } else { if (m_print_type == FROM_NORMAL) { if (obj_->GetNozzleRack()->IsSupported()) { const auto& nozzle_pos_vec = obj_->get_nozzle_mapping_result()->GetMappedNozzlePosVecByFilaId(fila_id); for (const auto& nozzle_pos : nozzle_pos_vec) { nozzle_map[nozzle_pos] = obj_->GetNozzleSystem()->GetNozzleByPosId(nozzle_pos); } } else { auto nozzle_group_res = DevUtilBackend::GetNozzleGroupResult(m_plater); if (nozzle_group_res) { const auto& nozzles = nozzle_group_res->get_nozzles_for_filament(fila_id); for (const auto& item : nozzles) { if (item.extruder_id == 0) { nozzle_map[DEPUTY_EXTRUDER_ID] = obj_->GetNozzleSystem()->GetNozzleByPosId(DEPUTY_EXTRUDER_ID); } else if (item.extruder_id == 1) { nozzle_map[MAIN_EXTRUDER_ID] = obj_->GetNozzleSystem()->GetNozzleByPosId(MAIN_EXTRUDER_ID); }; }; } } } else { // the mapping is not supported } }; return nozzle_map; } // return empty string if not supported // return ? if not mapped wxString SelectMachineDialog::get_mapped_nozzle_str(int fila_id) { if (m_print_type != FROM_NORMAL) { BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ": from normal"; return wxEmptyString;// there are no slicing data when print from sdcard } auto obj_ = get_current_machine(); if (!obj_) { return wxEmptyString; } if (use_dynamic_nozzle_map() && !obj_->GetFilaSwitch()->IsInstalled()) { BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << ": the obj not have fila switch"; return "?";// the nozzle mapping is not supported if fila switch is not installed } if (obj_->GetFilaSwitch()->IsInstalled() || obj_->GetNozzleRack()->IsSupported()) { if (obj_->GetNozzleRack()->IsSupported()) { return obj_->get_nozzle_mapping_result()->GetMappedNozzlePosStrByFilaId(fila_id); } else { const auto& nozzle_map = get_mapped_nozzles(fila_id); if (nozzle_map.count(MAIN_EXTRUDER_ID) != 0 && nozzle_map.count(DEPUTY_EXTRUDER_ID) != 0) { return "L R"; } else if (nozzle_map.count(MAIN_EXTRUDER_ID) != 0) { return "R"; } else if (nozzle_map.count(DEPUTY_EXTRUDER_ID) != 0) { return "L"; } } return "?"; } else if (obj_->GetExtderSystem()->GetTotalExtderCount() != obj_->GetNozzleSystem()->GetExtNozzleCount()) { // TO BE SUPPORTED } return wxEmptyString; } bool SelectMachineDialog::CheckErrorDynamicSwitchNozzle(MachineObject* obj_) { if (!obj_) { return false; } //if (obj_->GetFilaSwitch()->IsInstalled() && !obj_->GetFilaSwitch()->IsReady()) { // show_status(PrintDialogStatus::PrintStatusFilaSwitcherError, { _L("The Filament Track Switch has not been setup. Please setup it first.") }); // return false; //} if (obj_->is_support_check_track_switch_match_slice_printer && slicing_with_fila_switch() != obj_->GetFilaSwitch()->IsInstalled()) { show_status(PrintDialogStatus::PrintStatusFilaSwitcherSlicingNotMatch, { _L("The Filament Track Switch installed on the printer does not match the slicing file. Please re-slice to avoid print quality issues.") }); } if (!use_dynamic_nozzle_map()) { return true; // no need to check if support dynamic nozzle map } if (!obj_->GetFilaSwitch()->IsInstalled()) { show_status(PrintDialogStatus::PrintStatusFilaSwitcherError, { _L("This print requires a Filament Track Switch. Please install it first.") }); return false; } if (!obj_->GetFilaSwitch()->IsReady()) { show_status(PrintDialogStatus::PrintStatusFilaSwitcherError, { _L("The Filament Track Switch has not been setup. Please setup it first.") }); return false; } return true; } bool SelectMachineDialog::CheckErrorSyncNozzleMappingResultV1(MachineObject* obj_) { if (m_print_type != FROM_NORMAL) { return true;// there are no slicing data when print from sdcard } if (!obj_) { return true; } if (!obj_->GetNozzleRack()->IsSupported()) { return true;// no need to check if not support nozzle rack } if (!use_dynamic_nozzle_map()) { return true; } const auto& obj_nozzle_mapping_ptr = obj_->get_nozzle_mapping_result(); if (!obj_nozzle_mapping_ptr->HasResult()) { if (time(nullptr) - s_nozzle_mapping_last_request_time > 10) { // avoid too many requests int rtn = obj_nozzle_mapping_ptr->CtrlGetAutoNozzleMappingV1(m_plater); if (rtn == 0) { s_nozzle_mapping_last_request_time = time(nullptr); } else { const auto& err_msg = wxString::Format(_L("Failed to send nozzle auto-mapping request to printer { code: %d }. " "Please try to refresh the printer information. " "If it still does not recover, you can try to rebind the printer and check the network connection."), rtn); show_status(PrintDialogStatus::PrintStatusRackNozzleMappingWaiting, { err_msg }); return false; } } show_status(PrintDialogStatus::PrintStatusRackNozzleMappingWaiting, { _L("The printer is calculating nozzle mapping.") + " " + _L("Please wait a moment...") }); return false; } if (obj_nozzle_mapping_ptr->GetResultStr() == "failed") { const auto& err_msg = wxString::Format(_L("Failed to receive nozzle auto-mapping table from printer { msg: %s }. Please refresh the printer information."), obj_nozzle_mapping_ptr->GetMqttReason()); show_status(PrintDialogStatus::PrintStatusRackNozzleMappingError, { err_msg }); return false; } if (obj_nozzle_mapping_ptr->GetResultStr() == "fail") { const wxString& err_msg = wxString::Format(_L("The printer failed to build the nozzle auto-mapping table { code: %d }. Please refreash nozzle information."), obj_nozzle_mapping_ptr->GetErrno()); show_status(PrintDialogStatus::PrintStatusRackNozzleMappingError, { err_msg }); return false; } const auto& mapping_map = obj_->get_nozzle_mapping_result()->GetNozzleMappingMap(); if (!mapping_map.empty()) { // mapping result changed, update related gui if (m_nozzle_mapping_result != mapping_map) { m_nozzle_mapping_result = mapping_map; sync_ams_mapping_result(m_ams_mapping_result); BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ": sync_ams_mapping_result done."; } float flush_waste_base = obj_->get_nozzle_mapping_result()->GetFlushWeightBase(); float flush_waste_current = obj_->get_nozzle_mapping_result()->GetFlushWeightCurrent(); if ((flush_waste_base != -1) && (flush_waste_current != -1) && flush_waste_current > flush_waste_base) { float val = flush_waste_current - flush_waste_base; const wxString& warning_msg = wxString::Format(_L("The current nozzle mapping may produce an extra %0.2f g of waste."), val); show_status(PrintDialogStatus::PrintStatusRackNozzleMappingWarning, { warning_msg }); } return true; } BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << "empty"; const wxString& err_msg = wxString::Format(_L("Failed to receive nozzle auto-mapping table from printer { msg: %s }. Please refresh the printer information."), "empty table"); show_status(PrintDialogStatus::PrintStatusRackNozzleMappingError, { err_msg }); return true; } bool SelectMachineDialog::CheckErrorSyncNozzleMappingResultV0(MachineObject* obj_) { if (!obj_) { return true; } if (!obj_->GetNozzleRack()->IsSupported()){ return true;// no need to check if not support nozzle rack } if (m_print_type != FROM_NORMAL) { return true;// there are no slicing data when print from sdcard } if (use_dynamic_nozzle_map()) { return true; // use V1 nozzle mapping } const auto& obj_nozzle_mapping_ptr = obj_->get_nozzle_mapping_result(); auto nozzle_group_res = DevUtilBackend::GetNozzleGroupResult(m_plater); if (nozzle_group_res && nozzle_group_res->get_used_nozzles_in_extruder(LOGIC_R_EXTRUDER_ID).empty()) { if (obj_nozzle_mapping_ptr->HasResult()) { clear_nozzle_mapping(); } return true;// no need to check if no right nozzles used in slicing } if (!obj_nozzle_mapping_ptr->HasResult()) { if (time(nullptr) - s_nozzle_mapping_last_request_time > 10) { // avoid too many requests int rtn = obj_nozzle_mapping_ptr->CtrlGetAutoNozzleMappingV0(m_plater, m_ams_mapping_result, m_checkbox_list["flow_cali"]->getValueInt(), m_pa_value_switch->GetValue() ? 0 : 1); if (rtn == 0) { s_nozzle_mapping_last_request_time = time(nullptr); } else { const auto& err_msg = wxString::Format(_L("Failed to send nozzle auto-mapping request to printer { code: %d }. " "Please try to refresh the printer information. " "If it still does not recover, you can try to rebind the printer and check the network connection."), rtn); show_status(PrintDialogStatus::PrintStatusRackNozzleMappingWaiting, { err_msg }); return false; } } show_status(PrintDialogStatus::PrintStatusRackNozzleMappingWaiting, { _L("The printer is calculating nozzle mapping.") + " " + _L("Please wait a moment...")}); return false; } if (obj_nozzle_mapping_ptr->GetResultStr() == "failed") { const auto& err_msg = wxString::Format(_L("Failed to receive nozzle auto-mapping table from printer { msg: %s }. Please refresh the printer information."), obj_nozzle_mapping_ptr->GetMqttReason()); show_status(PrintDialogStatus::PrintStatusRackNozzleMappingError, { err_msg }); return false; } if (obj_nozzle_mapping_ptr->GetResultStr() == "fail") { const wxString& err_msg = wxString::Format(_L("The printer failed to build the nozzle auto-mapping table { code: %d }. Please refreash nozzle information."), obj_nozzle_mapping_ptr->GetErrno()); show_status(PrintDialogStatus::PrintStatusRackNozzleMappingError, { err_msg }); return false; } const auto& mapping_map = obj_->get_nozzle_mapping_result()->GetNozzleMappingMap(); if (!mapping_map.empty()) { // mapping result changed, update related gui if (m_nozzle_mapping_result != mapping_map) { m_nozzle_mapping_result = mapping_map; sync_ams_mapping_result(m_ams_mapping_result); BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ": sync_ams_mapping_result done."; } float flush_waste_base = obj_->get_nozzle_mapping_result()->GetFlushWeightBase(); float flush_waste_current = obj_->get_nozzle_mapping_result()->GetFlushWeightCurrent(); if ((flush_waste_base != -1) && (flush_waste_current != -1) && flush_waste_current > flush_waste_base) { float val = flush_waste_current - flush_waste_base; const wxString& warning_msg = wxString::Format(_L("The current nozzle mapping may produce an extra %0.2f g of waste."), val); show_status(PrintDialogStatus::PrintStatusRackNozzleMappingWarning, { warning_msg }); } return true; } BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << "empty"; const wxString& err_msg = wxString::Format(_L("Failed to receive nozzle auto-mapping table from printer { msg: %s }. Please refresh the printer information."), "empty table"); show_status(PrintDialogStatus::PrintStatusRackNozzleMappingError, { err_msg }); return true; } bool SelectMachineDialog::CheckErrorWarningFilamentMapping(MachineObject* obj_) { if (!obj_) { return false; } if (!DevMappingUtil::is_valid_mapping_result(obj_, m_ams_mapping_result)) { show_status(PrintDialogStatus::PrintStatusAmsMappingInvalid); return false; } if (!DevPrinterConfigUtil::support_ams_ext_mix_print(obj_->printer_type)) { bool useAms = _HasAms(m_ams_mapping_result); bool useExt = _HasExt(m_ams_mapping_result); if (useAms && useExt) { show_status(PrintDialogStatus::PrintStatusAmsMappingMixInvalid); return false; } } if (DevPrinterConfigUtil::support_print_check_firmware_for_tpu_left(obj_->printer_type)) { bool has_tpu_left = false; for (const auto& fila : m_ams_mapping_result) { const auto& ams_id_str = std::to_string(fila.get_ams_id()); auto tray_opt = obj_->get_tray(ams_id_str, std::to_string(fila.get_slot_id())); if (!tray_opt.has_value()) { show_status(PrintDialogStatus::PrintStatusAmsMappingInvalid); return false; } if (obj_->GetFilaSystem()->GetExtruderIdByAmsId(ams_id_str) == DEPUTY_EXTRUDER_ID && (tray_opt->get_filament_type() == "TPU")) { has_tpu_left = true; break; } }; bool firmware_support_print_tpu_left = obj_->m_firmware_support_print_tpu_left.value_or(false); if (has_tpu_left && !firmware_support_print_tpu_left) { #ifndef __APPLE__ show_status(PrintDialogStatus::PrintStatusFirmwareNotSupportTpuAtLeft, { _L("Your current firmware version cannot start this print job. Please update to the latest version and try again.") }, wxEmptyString, prePrintInfoStyle::BtnJumpToUpgrade); #else show_status(PrintDialogStatus::PrintStatusFirmwareNotSupportTpuAtLeft, { _L("Your current firmware version cannot start this print job. Please update to the latest version and try again.") }, wxEmptyString); #endif return false; } } /** warning check **/ struct pExtruderStatus { bool has_ams{ false }; bool has_vt_slot{ false }; }; std::map extruder_ams_ext_status; // filaments check for black list for (const auto &fila : m_ams_mapping_result) { DevFilaBlacklist::CheckFilamentInfo check_info; check_info.dev_id = obj_->get_dev_id(); check_info.model_id = obj_->printer_type; check_info.ams_id = fila.get_ams_id(); check_info.slot_id = fila.get_slot_id(); auto tray_opt = obj_->get_tray(std::to_string(check_info.ams_id), std::to_string(check_info.slot_id)); if (!tray_opt.has_value()) { show_status(PrintDialogStatus::PrintStatusAmsMappingInvalid); return false; } check_info.fila_id = tray_opt->get_filament_id(); check_info.fila_type = tray_opt->get_filament_type(); auto option = GUI::wxGetApp().preset_bundle->get_filament_by_filament_id(check_info.fila_id); check_info.fila_name = option ? option->filament_name : ""; check_info.fila_vendor = option ? option->vendor : ""; check_info.has_filament_switch = obj_->GetFilaSwitch()->IsInstalled(); // usage of filament if (m_print_type == PrintFromType::FROM_NORMAL) { if (GCodeProcessorResult *gcode_result = m_plater->background_process().get_current_gcode_result()) { for (auto used_fila : gcode_result->used_filaments) { if (used_fila.filament_id == fila.id) { check_info.used_for_print_support = used_fila.use_for_support; check_info.used_for_print_object = used_fila.use_for_object; break; } }; } }; const auto &nozzle_map = get_mapped_nozzles(fila.id); for (const auto &[pos_id, nozzle] : nozzle_map) { if (nozzle.IsEmpty()) { BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << ": nozzle is not found: " << pos_id; continue; }; if (tray_opt->ams_type != DevAmsType::EXT_SPOOL) { extruder_ams_ext_status[nozzle.GetExtruderId()].has_ams = true; } else { extruder_ams_ext_status[nozzle.GetExtruderId()].has_vt_slot = true; } check_info.extruder_id = nozzle.GetExtruderId(); check_info.nozzle_flow = DevNozzle::GetNozzleFlowTypeString(nozzle.GetNozzleFlowType()); check_info.nozzle_diameter = nozzle.GetNozzleDiameter(); auto result = DevFilaBlacklist::check_filaments_in_blacklist(check_info); if (const auto &prohibition_items = result.get_items_by_action("prohibition"); !prohibition_items.empty()) { for (auto item : prohibition_items) { show_status(PrintDialogStatus::PrintStatusHasFilamentInBlackListError, {item.info_msg}, item.wiki_url); } return false; } if (const auto &warning_items = result.get_items_by_action("warning"); !warning_items.empty()) { for (auto item : warning_items) { show_status(PrintDialogStatus::PrintStatusHasFilamentInBlackListWarning, {item.info_msg}, item.wiki_url); } } // Check nozzle hardness check if (nozzle.GetNozzleType() != NozzleType::ntUndefine && !is_nozzle_hrc_matched(nozzle.GetNozzleType(), check_info.fila_id)) { if (check_info.extruder_id == MAIN_EXTRUDER_ID && obj_->GetNozzleSystem()->GetNozzleRack()->IsSupported()) { std::vector error_msg; int nozzle_pos_id = nozzle.GetNozzlePosId(); const std::string &nozzle_str = (nozzle_pos_id == 0) ? "R" : "R" + std::to_string(nozzle_pos_id - 0x10 + 1); error_msg.emplace_back(wxString::Format(_L("The hardness of current material (%s) exceeds the hardness of %s (%s). It may cause nozzle wear, leading to " "material leakage and unstable flow. Please exercise caution when using it."), wxString::FromUTF8(check_info.fila_name.empty() ? check_info.fila_type : check_info.fila_name), nozzle_str, format_steel_name(nozzle.GetNozzleType()))); show_status(PrintDialogStatus::PrintStatusNozzleHRCMismatch, error_msg); } else { std::vector error_msg; error_msg.emplace_back(wxString::Format(_L("The hardness of current material (%s) exceeds the hardness of %s (%s). It may cause nozzle wear, leading to " "material leakage and unstable flow. Please exercise caution when using it."), wxString::FromUTF8(check_info.fila_name.empty() ? check_info.fila_type : check_info.fila_name), _get_nozzle_name(obj_->GetExtderSystem()->GetTotalExtderCount(), nozzle.GetNozzleId()), format_steel_name(nozzle.GetNozzleType()))); show_status(PrintDialogStatus::PrintStatusNozzleHRCMismatch, error_msg); } } } // The hint only applies when Flow Dynamics Calibration is set to Auto/On; skip it when the option is Off or hidden. const bool flow_cali_auto_or_on = m_checkbox_list["flow_cali"]->IsShown() && m_checkbox_list["flow_cali"]->getValue() != "off"; if (flow_cali_auto_or_on) { const auto& warning_tpu_filaments = DevPrinterConfigUtil::get_value_from_config>(obj_->printer_type, "auto_on_cali_warning_tpu_filaments"); for (const auto& warning_tpu_filament : warning_tpu_filaments) { if (warning_tpu_filament == check_info.fila_id) { show_status(PrintDialogStatus::PrintStatusTPUUnsuggestCali, { _L("If 'Dynamic Flow Calibration' is set to Auto/On, the system will use the manual calibration value or the default value and skip the flow calibration process. You can perform a manual flow calibration for TPU filament on the 'Calibration' page.") }); break; } } } } for (const auto &iter : extruder_ams_ext_status) { if (iter.second.has_ams && iter.second.has_vt_slot) { show_status(PrintDialogStatus::PrintStatusMixAmsAndVtSlotWarning); break; } } CheckWarningFilamentRemain(obj_); std::set cross_extruder_filament_ids; if (!CheckWarningFilamentCrossExtruder(obj_, cross_extruder_filament_ids)) { wxString warning_msg = _L("Some filaments may switch between extruders during printing. Manual K-value calibration cannot be applied throughout the entire print, which " "may affect print quality. Enabling Flow Dynamics Calibration is recommended."); show_status(PrintDialogStatus::PrintStatusFilamentCrossExtruderWarning, { warning_msg }); } // Smart nozzle clumping (wrap) detection: recommend switching to Auto when the project // contains stringing-prone filament and the printer is not already in Auto mode. if (!CheckWarningSmartNozzleBlobAuto(obj_)) { const wxString warning_msg = _L("There is stringing-prone filament in this file. For best print quality, " "we recommend switching nozzle clumping detection to Auto mode."); show_status(PrintDialogStatus::PrintStatusSmartNozzleBlobNeedAuto, {warning_msg}, wxEmptyString, prePrintInfoStyle::BtnSwitchNozzleBlobAuto); } return true; }; bool SelectMachineDialog::IsAllAmsSupportAccurateRemain(MachineObject* obj_) const { if (!obj_) return false; const auto& ams_list = obj_->GetFilaSystem()->GetAmsList(); if (ams_list.empty()) return false; for (const auto& [ams_id, ams] : ams_list) { if (!ams || ams->GetRemainEstimateVersion() != DevAms::RemainEstimateVersion::Accurate) return false; } return true; } // return true don't warning bool SelectMachineDialog::CheckWarningFilamentRemain(MachineObject* obj_) { std::vector filaments_not_enough; // Slots of every insufficient auto refill group. A slot that belongs to no refill group is kept // as a single-element group, so that only groups holding more than one slot are named later. std::vector> insufficient_groups; // Step 1: skip when remain detection is off, not a normal send-print, or AMS cannot report accurate remain. if (!obj_) return true; if (!obj_->GetFilaSystem()->IsDetectRemainEnabled() || m_print_type != PrintFromType::FROM_NORMAL) return true; if (!IsAllAmsSupportAccurateRemain(obj_)) return true; auto full_config = wxGetApp().preset_bundle->full_config(); auto filament_densities = full_config.option("filament_density"); // key: ams_id slot_id value: remain std::map, double> fila_remain_map; //collect fila remain info std::map, FilamentInfo> fila_used_map; std::map, std::vector> fila_ids_in_slot; // all filament ids mapped to the same slot // Step 2: collect remaining weight for every AMS slot that reports remain data. { for (auto ams_item : obj_->GetFilaSystem()->GetAmsList()) { std::string ams_id = ams_item.first; if (ams_item.second) { for (auto tray_item : ams_item.second->GetTrays()) { std::string slot_id = tray_item.first; if (tray_item.second) { std::pair key{ams_id, slot_id}; if (auto weight = tray_item.second->get_filament_remain_weight()) { fila_remain_map[key] = weight.value(); } } } } } } // Step 3: collect used weight per mapped slot from gcode volumes and filament density. for (const auto& fila : m_ams_mapping_result) { if (GCodeProcessorResult* gcode_result = m_plater->background_process().get_current_gcode_result()) { if (filament_densities) { auto densities = filament_densities->values; auto volumes_map = gcode_result->print_statistics.total_volumes_per_extruder; if (volumes_map.find(fila.id) != volumes_map.end() && fila.id >= 0 && fila.id < densities.size()) { std::pair key{fila.ams_id, fila.slot_id}; double used_g = densities[fila.id] * (volumes_map[fila.id] / 1000); // mm^3 -> cm^3 auto used_it = fila_used_map.find(key); if (used_it == fila_used_map.end()) { FilamentInfo info; info.id = fila.id; info.used_g = used_g; fila_used_map[key] = info; } else { used_it->second.used_g += used_g; } fila_ids_in_slot[key].push_back(fila.id); } else { BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << "the list of filament densities can't find "<< fila.id; return true; } } } } { wxString record_used_log; for (auto [key, used] : fila_used_map) { record_used_log += wxString::Format("ams_id: %s, slot_id: %s, fila_id: %d, used: %0.2f;", key.first, key.second, used.id, used.used_g); } BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << "fila_used_map: " << record_used_log; wxString record_remain_log; for (auto& [key, remain] : fila_remain_map) { record_remain_log += wxString::Format("ams_id: %s, slot_id: %s, remain: %0.2f;", key.first, key.second, remain); } BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << "fila_remain_map: " << record_remain_log; } double tolerance = DevAmsTray::get_fila_remain_tolerance(); // Step 4: compare remain vs used for each mapped slot; deduct remain when the slot itself is enough. for (auto& item : fila_used_map) { auto ams_slot_key = item.first; if (fila_remain_map.find(ams_slot_key) == fila_remain_map.end()) continue; auto fila_used = item.second.used_g; auto fila_remain = fila_remain_map[ams_slot_key]; if (fila_remain >= fila_used * (1 - tolerance)) { double remain_to_deduct = fila_used * (1 + tolerance); fila_remain_map[ams_slot_key] = std::max(fila_remain_map[ams_slot_key] - remain_to_deduct, 0.0); } else { // Step 5: the slot itself is short. Resolve its slot id first, Step 6 needs it as well. DevAmsSlotId ams_slot_id; try { ams_slot_id = DevAmsSlotId{stoi(ams_slot_key.first), stoi(ams_slot_key.second)}; } catch(...) { BOOST_LOG_TRIVIAL(warning) << __FUNCTION__ << "invalid ams id: " << ams_slot_key.first << " or slot id: " << ams_slot_key.second; return true; } // When auto refill is on, sum up the remain of the whole refill group before warning. bool checked_refill_group = false; if (obj_->GetFilaSystem()->IsAutoRefillEnabled() && obj_->GetExtderSystem()->HasFilamentBackup()) { auto ams_slot_list = obj_->GetExtderSystem()->GetBackupAmsSlotInGroup(ams_slot_id); if (!ams_slot_list.empty()) { checked_refill_group = true; double total_remain = fila_remain; for (const DevAmsSlotId& item : ams_slot_list) { std::pair backup_key = {std::to_string(item.first), std::to_string(item.second)}; const auto remain_it = fila_remain_map.find(backup_key); if (remain_it != fila_remain_map.end()) total_remain += remain_it->second; } if (total_remain >= fila_used * (1 - tolerance)) { // Group remain is enough: consume this slot first, then backup slots. double remain_to_deduct = fila_used * (1 + tolerance); if (fila_remain > 0) { double deduct = std::min(fila_remain_map[ams_slot_key], remain_to_deduct); fila_remain_map[ams_slot_key] -= deduct; remain_to_deduct -= deduct; } for (const DevAmsSlotId& item : ams_slot_list) { if (remain_to_deduct <= 0) break; std::pair backup_key = {std::to_string(item.first), std::to_string(item.second)}; if (fila_remain_map.find(backup_key) != fila_remain_map.end() && fila_remain_map[backup_key] > 0) { double deduct = std::min(fila_remain_map[backup_key], remain_to_deduct); fila_remain_map[backup_key] -= deduct; remain_to_deduct -= deduct; } } } else { // Group remain is still short: mark mapped filaments and keep the whole group. const auto& slot_ids = fila_ids_in_slot[ams_slot_key]; filaments_not_enough.insert(filaments_not_enough.end(), slot_ids.begin(), slot_ids.end()); std::vector group_slots{ams_slot_id}; group_slots.insert(group_slots.end(), ams_slot_list.begin(), ams_slot_list.end()); insufficient_groups.emplace_back(std::move(group_slots)); BOOST_LOG_TRIVIAL(warning) << __FUNCTION__ << "The filament is not enough in ams: " << ams_slot_key.first << " slot: " << ams_slot_key.second << ", used: " << fila_used << ", available: " << total_remain; } } } // Step 6: the slot belongs to no refill group, keep it as a single-slot group so that // Step 8 falls back to the generic warning message. if (!checked_refill_group) { const auto& slot_ids = fila_ids_in_slot[ams_slot_key]; filaments_not_enough.insert(filaments_not_enough.end(), slot_ids.begin(), slot_ids.end()); insufficient_groups.push_back({ams_slot_id}); BOOST_LOG_TRIVIAL(warning) << __FUNCTION__ << "The filament is not enough in ams slot: " << ams_slot_key.first << " slot: " << ams_slot_key.second << ", used: " << fila_used << ", available: " << fila_remain; } } } // Step 7: paint orange warning border on insufficient mapping cards. for (auto iter : m_materialList) { Material * item = iter.second; int fila_id = item->id; MaterialItem *m = item->item; if(std::find(filaments_not_enough.begin(), filaments_not_enough.end(), fila_id) != filaments_not_enough.end()) { m->on_warning(); } else if (m->is_selected()) { m->on_selected(); } else { m->on_normal(); } } // Step 8: turn the insufficient groups into display names. A group holding more than one slot is // reported as "A1+A3", a standalone slot is reported by its own name. if (!filaments_not_enough.empty()) { auto join_names = [](const std::vector& names, const wxString& separator) { wxString joined; for (const wxString& name : names) { if (!joined.empty()) joined += separator; joined += name; } return joined; }; std::vector slot_names; std::vector group_names; for (const auto& group_slots : insufficient_groups) { std::vector tray_ids; tray_ids.reserve(group_slots.size()); for (const DevAmsSlotId& slot : group_slots) { const int tray_id = obj_->GetFilaSystem()->GetTrayIdByAmsSlotId(slot.first, slot.second); if (tray_id >= 0) tray_ids.emplace_back(tray_id); } if (tray_ids.empty()) continue; std::sort(tray_ids.begin(), tray_ids.end()); std::vector tray_names; tray_names.reserve(tray_ids.size()); for (int tray_id : tray_ids) tray_names.emplace_back(wxGetApp().transition_tridid(tray_id)); std::vector& target_names = tray_names.size() == 1 ? slot_names : group_names; wxString display_name = join_names(tray_names, "+"); if (std::find(target_names.begin(), target_names.end(), display_name) == target_names.end()) target_names.emplace_back(display_name); } wxString warning_msg; if (!slot_names.empty()) { warning_msg = wxString::Format( _L("The filament in %s may be insufficient for this print. Please refill or replace it."), join_names(slot_names, ", ")); } if (!group_names.empty()) { if (!warning_msg.empty()) warning_msg += "\n"; const wxString joined_groups = join_names(group_names, ", "); if (group_names.size() == 1) { warning_msg += wxString::Format( _L("The filament in auto refill group %s may be insufficient for this print. Please refill or replace it."), joined_groups); } else { warning_msg += wxString::Format( _L("The filament in auto refill groups %s may be insufficient for this print. Please refill or replace it."), joined_groups); } } show_status(PrintDialogStatus::PrintStatusFilamentWarningRemainNotEnough, {warning_msg}); } return filaments_not_enough.empty(); } bool SelectMachineDialog::CheckWarningPrintTimeEstimate(MachineObject* obj_, const std::vector& ams_mapping_result) { if (!obj_) return true; for (const auto& fila : ams_mapping_result) { if (fila.ams_id.empty() || devPrinterUtil::IsVirtualSlot(fila.ams_id)) continue; if (DevAms* ams = obj_->GetFilaSystem()->GetAmsById(fila.ams_id)) { DevAmsType ams_type = ams->GetAmsType(); if (ams_type != DevAmsType::AMS_LITE && ams_type != DevAmsType::AMS_LITE_MIXED) { return false; } } } return true; } bool SelectMachineDialog::CheckWarningFilamentCrossExtruder(MachineObject* obj_, std::set& cross_extruder_filament_ids) { cross_extruder_filament_ids.clear(); if (!obj_ || m_print_type != PrintFromType::FROM_NORMAL) return true; if (obj_->GetExtderSystem()->GetTotalExtderCount() != 2) return true; for (const auto& fila : m_ams_mapping_result) { std::set used_extruder_ids; const auto& nozzle_map = get_mapped_nozzles(fila.id); for (const auto& [pos_id, nozzle] : nozzle_map) { if (!nozzle.IsEmpty()) { used_extruder_ids.insert(nozzle.GetExtruderId()); } } if (used_extruder_ids.size() >= 2) { cross_extruder_filament_ids.insert(fila.id); } } return cross_extruder_filament_ids.empty(); } bool SelectMachineDialog::CheckWarningSmartNozzleBlobAuto(MachineObject* obj_) { if (!obj_ || m_print_type != PrintFromType::FROM_NORMAL) return true; // Only relevant when the printer firmware supports the smart wrap detection feature. auto* opts = obj_->GetPrintOptions(); if (!opts) return true; const auto* opt = opts->GetDetectionOption(PrintOptionEnum::Smart_Nozzle_Blob_Detection); if (!opt || !opt->is_support_detect) return true; // Already in Auto (cfg[43:44] == 2): nothing to recommend. if (opt->current_detect_value == 2) return true; // Need at least one stringing-prone filament in the sliced project for any nozzle on // the current printer config. Iterating the cross-product covers single- and // dual-extruder printers without needing per-filament extruder assignment. PresetBundle* preset_bundle = wxGetApp().preset_bundle; if (!preset_bundle) return true; const auto* opt_nozzle_diameters = preset_bundle->printers.get_edited_preset().config.option("nozzle_diameter"); if (!opt_nozzle_diameters || opt_nozzle_diameters->size() == 0) return true; for (const auto& fila : m_filaments) { if (fila.filament_id.empty()) continue; for (size_t i = 0; i < opt_nozzle_diameters->size(); ++i) { const float d = static_cast(opt_nozzle_diameters->get_at(i)); if (Slic3r::is_stringing_prone_filament(fila.filament_id, d)) return false; } } return true; } ShowType SelectMachineDialog::get_filament_mapping_show_type(MachineObject* obj_, int fila_logic_id) const { try { int total_ext_count = get_print_task_total_extruder_count(); if (total_ext_count < 2) { return ShowType::RIGHT; } if (can_hybrid_mapping(obj_)) { return ShowType::LEFT_AND_RIGHT_DYNAMIC; } else if (m_filaments_map.at(fila_logic_id) == 1) { return ShowType::LEFT; } else if (m_filaments_map.at(fila_logic_id) == 2) { return ShowType::RIGHT; } } catch (const std::exception& e) { BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << ": exception: " << e.what(); } return ShowType::LEFT_AND_RIGHT; } std::optional SelectMachineDialog::get_slicing_filament_info(int fila_logic_id) const { for (const auto& fila : m_filaments) { if (fila.id == fila_logic_id) { return fila; } } BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << ": not found for logic id " << fila_logic_id; return std::nullopt; } std::optional SelectMachineDialog::get_mapped_filament_info(int fila_logic_id) const { for (const auto& fila : m_ams_mapping_result) { if (fila.id == fila_logic_id) { return fila; } } BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << ": not found for logic id " << fila_logic_id; return std::nullopt; } void SelectMachineDialog::clear_nozzle_mapping() { m_nozzle_mapping_result.clear(); if (auto obj_ = get_current_machine()) { obj_->get_nozzle_mapping_result()->Clear(); } } bool SelectMachineDialog::slicing_with_fila_switch() const { if (use_dynamic_nozzle_map()) { return true; } if (m_print_type == FROM_NORMAL) { auto has_filament_switcher = wxGetApp().preset_bundle->project_config.option("has_filament_switcher"); if (has_filament_switcher) { return has_filament_switcher->value; } } else if (m_print_type == FROM_SDCARD_VIEW) { if (m_required_data_plate_data_list.size() > m_print_plate_idx) { auto has_filament_switcher = m_required_data_plate_data_list[m_print_plate_idx]->config.option("has_filament_switcher"); if (has_filament_switcher) { return has_filament_switcher->value; } } auto has_filament_switcher = m_required_data_config.option("has_filament_switcher"); if (has_filament_switcher) { return has_filament_switcher->value; } } return false; } bool SelectMachineDialog::use_dynamic_nozzle_map() const { if (m_print_type == FROM_NORMAL) { auto nozzle_group_res = DevUtilBackend::GetNozzleGroupResult(m_plater); if (nozzle_group_res && nozzle_group_res->is_support_dynamic_nozzle_map()) { return true; } } else if (m_print_type == FROM_SDCARD_VIEW) { if (m_required_data_plate_data_list.size() > m_print_plate_idx) { auto dynamic_nozzle_map = m_required_data_plate_data_list[m_print_plate_idx]->config.option("enable_filament_dynamic_map"); if (dynamic_nozzle_map) { return dynamic_nozzle_map->value; } } auto dynamic_nozzle_map = m_required_data_config.option("enable_filament_dynamic_map"); if (dynamic_nozzle_map) { return dynamic_nozzle_map->value; } } return false; } /** * @brief * @param obj_ * @return the estimated time gap, optional. */ std::optional SelectMachineDialog::get_filament_change_gap_time(MachineObject* obj_) const { // return 1000; if (m_print_type != FROM_NORMAL) { return std::nullopt;; } if (!m_plater || !obj_ || !obj_->GetFilaSwitch() || !obj_->GetFilaSwitch()->IsInstalled()) { return std::nullopt; } GCodeProcessorResult* gcode_result = m_plater->background_process().get_current_gcode_result(); auto nozzle_group_res = DevUtilBackend::GetNozzleGroupResult(m_plater); if (!nozzle_group_res || !gcode_result) { return std::nullopt; } std::vector group_of_filaments; const auto& logic_filaments = cast(nozzle_group_res->get_used_filaments()); for (const auto& fila_idx : logic_filaments) { bool found = false; for (const auto& item : m_ams_mapping_result) { if (fila_idx != item.id) { continue; } const auto& ams_item = obj_->GetFilaSystem()->GetAmsById(item.ams_id); if (ams_item && ams_item->GetSwitcherPos().has_value()) { if (ams_item->GetSwitcherPos().value() == DevFilaSwitch::POS_IN_A) { group_of_filaments.push_back(0); found = true; break; } else if (ams_item->GetSwitcherPos().value() == DevFilaSwitch::POS_IN_B) { group_of_filaments.push_back(1); found = true; break; } } else { return std::nullopt; } } if (!found) { return std::nullopt; } } const auto& nozzle_list = nozzle_group_res->get_used_nozzles_in_extruder(); const auto& fila_change_seq = cast(gcode_result->filament_change_sequence); const auto& nozzle_change_seq = cast(gcode_result->nozzle_change_sequence); MultiNozzleUtils::FilamentChangeTimeParams params; const auto& full_config = wxGetApp().preset_bundle->full_config(); const auto& load_time_opt = full_config.option("machine_load_filament_time"); if (load_time_opt) { params.standard_load_time = load_time_opt->value;; } const auto& unload_time_opt = full_config.option("machine_unload_filament_time"); if (unload_time_opt) { params.standard_unload_time = unload_time_opt->value;; } params.selector_load_time = params.standard_load_time * 0.5; params.selector_unload_time = params.standard_unload_time * 0.5; int group_count = group_of_filaments.empty() ? 0 : *std::max_element(group_of_filaments.begin(), group_of_filaments.end()) + 1; std::vector ams_preload_enabled(group_count, obj_->ams_preload_version >= 1); try { return MultiNozzleUtils::calc_filament_change_gap_for_assignment(logic_filaments, nozzle_list, fila_change_seq, nozzle_change_seq, group_of_filaments, params, ams_preload_enabled); } catch (const std::exception& e) { BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << ": exception: " << e.what(); return std::nullopt; } return std::nullopt; } /** * @brief Get suggested switch position for each filament according to slicing result and AMS mapping result. * @return logic filament id as key, and the suggested switch position as value. */ std::map SelectMachineDialog::get_filament_suggest_pos(MachineObject* obj_) const { std::map suggest_pos_map; if (m_print_type != FROM_NORMAL) { return suggest_pos_map; } if (!m_plater || !obj_ || !obj_->GetFilaSwitch()->IsInstalled()) { return suggest_pos_map; } std::map> pos2group; const auto& optimal_assignment = m_plater->background_process().get_current_gcode_result()->optimal_assignment; for (int fila_idx = 0; fila_idx < optimal_assignment.size(); fila_idx++) { if (is_used_filament(fila_idx)) { pos2group[optimal_assignment.at(fila_idx)].insert(fila_idx); } }; std::set in_a_fila_set; std::set in_b_fila_set; for (const auto& item : m_ams_mapping_result) { const auto& ams_item = obj_->GetFilaSystem()->GetAmsById(item.ams_id); if (ams_item && ams_item->GetSwitcherPos().has_value()) { if (ams_item->GetSwitcherPos().value() == DevFilaSwitch::POS_IN_A) { in_a_fila_set.insert(item.id); } else if (ams_item->GetSwitcherPos().value() == DevFilaSwitch::POS_IN_B) { in_b_fila_set.insert(item.id); } } else{ return suggest_pos_map; } } if (pos2group.size() == 1) { if (in_a_fila_set.size() > in_b_fila_set.size()) { for (const auto& item : m_ams_mapping_result) { suggest_pos_map[item.id] = DevFilaSwitch::POS_IN_A; } } else if (in_a_fila_set.size() < in_b_fila_set.size()) { for (const auto& item : m_ams_mapping_result) { suggest_pos_map[item.id] = DevFilaSwitch::POS_IN_B; } } else { for (const auto& item : m_ams_mapping_result) { suggest_pos_map[item.id] = DevFilaSwitch::POS_IN_A; } } } else if (pos2group.size() == 2) { auto iter = pos2group.begin(); const auto& group_1 = iter->second; iter++; const auto& group_2 = iter->second; // compute the offset place group1 to INA, group2 to INB int offset_1 = 0; for (const auto& g1 : group_1) { if (in_a_fila_set.count(g1) == 0) { offset_1++; } } for (const auto& g2 : group_2) { if (in_b_fila_set.count(g2) == 0) { offset_1++; } } // compute the offset place group2 to INA, group1 to INB int offset_2 = 0; for (const auto& g1 : group_1) { if (in_b_fila_set.count(g1) == 0) { offset_2++; } } for (const auto& g2 : group_2) { if (in_a_fila_set.count(g2) == 0) { offset_2++; } } if (offset_1 <= offset_2) { for (const auto& fila_idx : group_1) { suggest_pos_map[fila_idx] = DevFilaSwitch::POS_IN_A; } for (const auto& fila_idx : group_2) { suggest_pos_map[fila_idx] = DevFilaSwitch::POS_IN_B; } } else { for (const auto& fila_idx : group_1) { suggest_pos_map[fila_idx] = DevFilaSwitch::POS_IN_B; } for (const auto& fila_idx : group_2) { suggest_pos_map[fila_idx] = DevFilaSwitch::POS_IN_A; } } } return suggest_pos_map; } std::optional SelectMachineDialog::get_filament_suggest_pos(MachineObject* obj_, int filament_logic_id) const { const auto& suggest_pos_opt = get_filament_suggest_pos(obj_); if (suggest_pos_opt.empty()) { return std::nullopt; } for (const auto& item : m_ams_mapping_result) { if (item.id == filament_logic_id) { if (suggest_pos_opt.count(item.id) != 0) { return suggest_pos_opt.at(item.id); } else { return std::nullopt; } } } return std::nullopt; } bool SelectMachineDialog::is_at_suggested_pos(MachineObject* obj_, int filament_logic_id) const { auto opt = get_filament_suggest_pos(obj_,filament_logic_id); if (!opt.has_value()) { return true; } const auto& mapped_filament_info = get_mapped_filament_info(filament_logic_id); if (mapped_filament_info.has_value()) { const auto& ams_item = obj_->GetFilaSystem()->GetAmsById(mapped_filament_info->ams_id); if (ams_item) { return ams_item->GetSwitcherPos() == opt; } } return true; } int SelectMachineDialog::get_print_task_total_extruder_count() const { if (m_print_type == FROM_NORMAL) { const auto& full_config = wxGetApp().preset_bundle->full_config(); return full_config.option("nozzle_diameter")->values.size(); } else if (m_print_type == FROM_SDCARD_VIEW) { auto opt_nozzle_diameters = m_required_data_config.option("nozzle_diameter"); if (opt_nozzle_diameters) { return opt_nozzle_diameters->size(); } } return 1; } void SelectMachineDialog::update_material_item_pos(MachineObject* obj_) { if (!obj_) { return; } const bool is_single_head = obj_->GetExtderSystem()->GetTotalExtderCount() < 2; const bool has_switcher = obj_->GetFilaSwitch()->IsInstalled(); const bool use_single_panel = is_single_head || has_switcher; bool to_change_pos = false; for (const auto& iter : m_materialList) { const auto& material_id = iter.second->id; const auto& material_item = iter.second->item; if (use_single_panel) { if (!m_sizer_ams_mapping->IsShown(material_item)) { to_change_pos = true; } } else { if (m_filaments_map[material_id] == 1 && !m_sizer_ams_mapping_left->IsShown(material_item)) { to_change_pos = true; } else if (m_filaments_map[material_id] == 2 && !m_sizer_ams_mapping_right->IsShown(material_item)) { to_change_pos = true; } } if (to_change_pos) break; } if (!to_change_pos) { return; } m_sizer_ams_mapping->Clear(false); m_sizer_ams_mapping_left->Clear(false); m_sizer_ams_mapping_right->Clear(false); int sizer_count = 0; int left_sizer_count = 0; int right_sizer_count = 0; for (const auto& iter : m_materialList) { const auto& material_id = iter.second->id; const auto& material_item = iter.second->item; if (use_single_panel) { if (!m_sizer_ams_mapping->IsShown(material_item)) { material_item->Reparent(m_filament_panel); m_sizer_ams_mapping->Add(material_item, 0, wxALL, FromDIP(5)); sizer_count++; } } else { if (m_filaments_map[material_id] == 1) { material_item->Reparent(m_filament_left_panel); m_sizer_ams_mapping_left->Add(material_item, 0, wxALL, FromDIP(5)); left_sizer_count++; } else if(m_filaments_map[material_id] == 2){ material_item->Reparent(m_filament_right_panel); m_sizer_ams_mapping_right->Add(material_item, 0, wxALL, FromDIP(5)); right_sizer_count++; } } } if (sizer_count > 0) { m_sizer_ams_mapping->SetCols(8); m_sizer_ams_mapping->Layout(); m_filament_panel_sizer->Layout(); } if (left_sizer_count > 0) { m_sizer_ams_mapping_left->SetCols(4); m_sizer_ams_mapping_left->Layout(); m_filament_panel_left_sizer->Layout(); m_filament_left_panel->Layout(); } if (right_sizer_count > 0) { m_sizer_ams_mapping_right->SetCols(4); m_sizer_ams_mapping_right->Layout(); m_filament_panel_right_sizer->Layout(); m_filament_right_panel->Layout(); } m_filament_panel->Show(sizer_count > 0); m_filament_left_panel->Show(left_sizer_count > 0 || right_sizer_count > 0); m_filament_right_panel->Show(left_sizer_count > 0 || right_sizer_count > 0); Layout(); } bool SelectMachineDialog::is_used_filament(int fila_logic_id) const { for (const auto& fila : m_ams_mapping_result) { if (fila.id == fila_logic_id) { return true; } } return false; } ThumbnailPanel::ThumbnailPanel(wxWindow *parent, wxWindowID winid, const wxPoint &pos, const wxSize &size) : wxPanel(parent, winid, pos, size) { #ifdef __WINDOWS__ SetDoubleBuffered(true); #endif //__WINDOWS__ SetBackgroundStyle(wxBG_STYLE_CUSTOM); wxBoxSizer *sizer = new wxBoxSizer(wxVERTICAL); m_staticbitmap = new wxStaticBitmap(parent, wxID_ANY, wxNullBitmap, wxDefaultPosition, wxDefaultSize); m_background_bitmap = ScalableBitmap(this,"thumbnail_grid",256); sizer->Add(m_staticbitmap, 1, wxEXPAND, 0); Bind(wxEVT_PAINT, &ThumbnailPanel::OnPaint, this); SetSizer(sizer); Layout(); Fit(); } void ThumbnailPanel::set_thumbnail(wxImage &img) { m_brightness_value = get_brightness_value(img); m_bitmap = img; //Paint the background bitmap to the thumbnail bitmap with wxMemoryDC wxMemoryDC dc; bitmap_with_background.Create(wxSize(m_bitmap.GetWidth(), m_bitmap.GetHeight())); dc.SelectObject(bitmap_with_background); dc.DrawBitmap(m_background_bitmap.bmp(), 0, 0); dc.DrawBitmap(m_bitmap, 0, 0); dc.SelectObject(wxNullBitmap); Refresh(); } void ThumbnailPanel::OnPaint(wxPaintEvent& event) { wxPaintDC dc(this); render(dc); } void ThumbnailPanel::render(wxDC& dc) { if (wxGetApp().dark_mode() && m_brightness_value < SHOW_BACKGROUND_BITMAP_PIXEL_THRESHOLD) { #ifdef __WXMSW__ wxMemoryDC memdc; wxBitmap bmp(GetSize()); memdc.SelectObject(bmp); memdc.DrawBitmap(bitmap_with_background, 0, 0); dc.Blit(0, 0, GetSize().GetWidth(), GetSize().GetHeight(), &memdc, 0, 0); #else dc.DrawBitmap(bitmap_with_background, 0, 0); #endif } else dc.DrawBitmap(m_bitmap, 0, 0); } ThumbnailPanel::~ThumbnailPanel() {} PrintOption::PrintOption(wxWindow *parent, wxString title, wxString tips, std::vector ops, std::string param) : wxPanel(parent, wxID_ANY, wxDefaultPosition, wxDefaultSize) { #ifdef __WINDOWS__ SetDoubleBuffered(true); #endif //__WINDOWS__ Bind(wxEVT_PAINT, &PrintOption::OnPaint, this); SetMinSize(wxSize(-1, FromDIP(28))); SetMaxSize(wxSize(-1, FromDIP(28))); m_ops = ops; m_param = param; m_full_title = title; SetBackgroundColour(StateColor::darkModeColorFor(*wxWHITE)); wxBoxSizer *sizer = new wxBoxSizer(wxHORIZONTAL); m_printoption_title = new Label(this, title); m_printoption_title->SetFont(Label::Body_13); update_title_display(); m_printoption_tips = new ScalableButton(this, wxID_ANY, "icon_qusetion", wxEmptyString, wxDefaultSize, wxDefaultPosition, wxBU_EXACTFIT | wxNO_BORDER, true); m_printoption_tips->SetMinSize(wxSize(FromDIP(18), FromDIP(18))); m_printoption_tips->SetMaxSize(wxSize(FromDIP(18), FromDIP(18))); m_printoption_item = new PrintOptionItem(this, m_ops, param); m_printoption_item->SetFont(Label::Body_13); sizer->Add(m_printoption_title, 0, wxALIGN_LEFT | wxALIGN_CENTER_VERTICAL, 0); sizer->Add(m_printoption_tips, 0, wxLEFT | wxALIGN_LEFT | wxALIGN_CENTER_VERTICAL, FromDIP(2)); sizer->AddStretchSpacer(); sizer->Add(m_printoption_item, 0, wxALIGN_RIGHT | wxALIGN_CENTER_VERTICAL, 0); SetSizer(sizer); Layout(); Fit(); update_tooltip(tips); } void PrintOption::insert_extra_widget(wxWindow* widget) { // insert after title (index 0), before tips (index 1) // sizer layout: [title][tips][stretch][item] // after insert: [title][widget][tips][stretch][item] wxSizer* sizer = GetSizer(); if (!sizer || !widget) return; widget->Reparent(this); sizer->Insert(1, widget, 0, wxALIGN_CENTER_VERTICAL | wxLEFT, FromDIP(4)); Layout(); } void PrintOption::OnPaint(wxPaintEvent &event) { wxPaintDC dc(this); doRender(dc); } void PrintOption::render(wxDC &dc) { #ifdef __WXMSW__ wxSize size = GetSize(); wxMemoryDC memdc; wxBitmap bmp(size.x, size.y); memdc.SelectObject(bmp); memdc.Blit({0, 0}, size, &dc, {0, 0}); { wxGCDC dc2(memdc); doRender(dc2); } memdc.SelectObject(wxNullBitmap); dc.DrawBitmap(bmp, 0, 0); #else doRender(dc); #endif } void PrintOption::doRender(wxDC &dc) { auto size = GetSize(); dc.SetPen(wxPen(*wxTRANSPARENT_PEN)); dc.SetBrush(GetBackgroundColour()); dc.DrawRoundedRectangle(0, 0, size.x, size.y, FromDIP(3)); } void PrintOption::enable(bool en) { if (m_printoption_item->is_enabled() != en) { m_printoption_item->enable(en); if (en) { m_printoption_title->SetForegroundColour(StateColor::darkModeColorFor("#262E30")); } else { m_printoption_title->SetForegroundColour(StateColor::darkModeColorFor(wxColour(144, 144, 144))); } } } void PrintOption::setValue(std::string value) { m_printoption_item->setValue(value); } bool PrintOption::contain_opt(const std::string &opt) const { for (const auto &op : m_ops) { if (op.key == opt) { return true; } } return false; } void PrintOption::update_options(std::vector ops, const wxString &tips) { if (m_ops != ops) { m_ops = ops; m_printoption_item->update_options(ops); } update_tooltip(tips); } void PrintOption::update_tooltip(const wxString &tips) { if (m_printoption_tips->GetToolTipText() != tips) { m_printoption_tips->SetToolTip(tips); } m_printoption_tips->Show(!m_printoption_tips->GetToolTipText().IsEmpty()); } void PrintOption::set_tips_clickable(bool clickable, std::function callback) { m_tips_clickable = clickable; m_tips_click_callback = callback; // Always unbind first to avoid stacking multiple handlers when called repeatedly m_printoption_tips->Unbind(wxEVT_BUTTON, &PrintOption::OnTipsButtonClicked, this); if (clickable) { m_printoption_tips->SetCursor(wxCursor(wxCURSOR_HAND)); m_printoption_tips->Bind(wxEVT_BUTTON, &PrintOption::OnTipsButtonClicked, this); m_printoption_tips->Show(true); } else { m_printoption_tips->SetCursor(wxNullCursor); m_printoption_tips->Show(!m_printoption_tips->GetToolTipText().IsEmpty()); } } void PrintOption::update_tooltip_options_area(const wxString& opt_tips) { if (m_printoption_item->GetToolTipText() != opt_tips) { m_printoption_item->SetToolTip(opt_tips); } } void PrintOption::restoreValue() { m_printoption_item->restoreValue(); } std::string PrintOption::getValue() { return m_printoption_item->getValue(); } int PrintOption::getValueInt() { if (m_printoption_item->getValue() == "off") { return 0; } else if (m_printoption_item->getValue() == "on") { return 1; } else if (m_printoption_item->getValue() == "auto") { return 2; } else { return 2; } } void PrintOption::update_title_display() { if (!m_printoption_title || m_full_title.IsEmpty()) { return; } wxGCDC dc; wxSize titleSize = dc.GetTextExtent(m_full_title); int maxTitleWidth = FromDIP(150); wxString displayTitle = m_full_title; if (titleSize.x > maxTitleWidth) { displayTitle = wxControl::Ellipsize(m_full_title, dc, wxELLIPSIZE_END, maxTitleWidth); m_printoption_title->SetToolTip(m_full_title); } else { m_printoption_title->SetToolTip(wxEmptyString); } m_printoption_title->SetLabel(displayTitle); } void PrintOption::OnTipsButtonClicked(wxCommandEvent &event) { if (m_tips_click_callback) { m_tips_click_callback(); } } void PrintOption::msw_rescale() { m_printoption_item->msw_rescale(); m_printoption_tips->msw_rescale(); update_title_display(); } PrintOptionItem::PrintOptionItem(wxWindow* parent, std::vector ops, std::string param) : wxPanel(parent, wxID_ANY, wxDefaultPosition, wxDefaultSize) { #ifdef __WINDOWS__ SetDoubleBuffered(true); #endif //__WINDOWS__ m_param = param; SetBackgroundColour(PRINT_OPT_ITEM_BG_GRAY); Bind(wxEVT_PAINT, &PrintOptionItem::OnPaint, this); Bind(wxEVT_ENTER_WINDOW, [this](auto& e) { SetCursor(wxCURSOR_HAND); }); Bind(wxEVT_LEAVE_WINDOW, [this](auto& e) { SetCursor(wxCURSOR_ARROW); }); Bind(wxEVT_LEFT_DOWN, &PrintOptionItem::on_left_down, this); m_selected_bk = ScalableBitmap(this, "print_options_bg", 22); m_selected_bk_dark = ScalableBitmap(this, "print_options_bg_dark", 22); m_selected_disabled_bk = ScalableBitmap(this, "print_options_bg_disabled", 22); m_selected_disabled_bk_dark = ScalableBitmap(this, "print_options_bg_disabled_dark", 22); // update the options update_options(ops); } void PrintOptionItem::OnPaint(wxPaintEvent& event) { wxPaintDC dc(this); doRender(dc); } void PrintOptionItem::render(wxDC& dc) { #ifdef __WXMSW__ wxSize size = GetSize(); wxMemoryDC memdc; wxBitmap bmp(size.x, size.y); memdc.SelectObject(bmp); memdc.Blit({ 0, 0 }, size, &dc, { 0, 0 }); { wxGCDC dc2(memdc); doRender(dc2); } memdc.SelectObject(wxNullBitmap); dc.DrawBitmap(bmp, 0, 0); #else doRender(dc); #endif } void PrintOptionItem::on_left_down(wxMouseEvent& evt) { BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ", enable status is " << m_enable; if (!m_enable) { return; } // Use client coordinates directly to avoid DPI/multi-monitor coordinate issues auto local_x = evt.GetPosition().x; auto select_size = GetSize().x / (int) m_ops.size(); int i = 0; m_old_selected_key = selected_key; for (const auto& entry : m_ops) { auto left_edge = i * select_size; auto right_edge = (i + 1) * select_size; if (local_x >= left_edge && local_x < right_edge) { selected_key = entry.key; } i++; } BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ", new select key= " << selected_key; if (selected_key.empty()) /* make some log if not find*/ { BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ", local_x=" << local_x << ", width=" << GetSize().x; int i = 0; for (const auto& entry : m_ops) { auto left_edge = i * select_size; auto right_edge = (i + 1) * select_size; BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ", i= " << i << ", entry.key= " << entry.key << ", left_edge= " << left_edge << ", right_edge= " << right_edge; i++; } } if (m_old_selected_key != selected_key) { wxCommandEvent event(EVT_SWITCH_PRINT_OPTION); event.SetString(selected_key); event.SetEventObject(GetParent()); wxPostEvent(GetParent(), event); } Refresh(); } void PrintOptionItem::doRender(wxDC& dc) { auto size = GetSize(); dc.SetPen(wxPen(*wxTRANSPARENT_PEN)); dc.SetBrush(GetBackgroundColour()); dc.DrawRoundedRectangle(0, 0, size.x, size.y, FromDIP(5)); auto left = FromDIP(4); int selected = 0; for (const auto& entry : m_ops) { if (entry.key == selected_key) { break; } selected++; } /*selected*/ auto selected_left = selected * PRINT_OPT_WIDTH + FromDIP(4); if (m_enable) { if (!wxGetApp().dark_mode()) { dc.DrawBitmap(m_selected_bk.bmp(), selected_left, (size.y - m_selected_bk.GetBmpHeight()) / 2); } else { dc.DrawBitmap(m_selected_bk_dark.bmp(), selected_left, (size.y - m_selected_bk_dark.GetBmpHeight()) / 2); } } else { if (!wxGetApp().dark_mode()) { dc.DrawBitmap(m_selected_disabled_bk.bmp(), selected_left, (size.y - m_selected_disabled_bk.GetBmpHeight()) / 2); } else { dc.DrawBitmap(m_selected_bk_dark.bmp(), selected_left, (size.y - m_selected_bk_dark.GetBmpHeight()) / 2); } } for (auto it = m_ops.begin(); it != m_ops.end(); ++it) { auto text_key = it->key; auto text_value = it->value; if (text_key == selected_key) { const wxColour& clr = m_enable ? StateColor::darkModeColorFor("#00AE42") : StateColor::darkModeColorFor(wxColour(144, 144, 144)); dc.SetPen(wxPen(clr)); dc.SetTextForeground(clr); dc.SetFont(::Label::Body_13); auto text_size = dc.GetTextExtent(text_value); auto text_left = left + (PRINT_OPT_WIDTH - text_size.x) / 2; auto text_top = (size.y - text_size.y) / 2; dc.DrawText(text_value, wxPoint(text_left, text_top)); } else { const wxColour& clr = m_enable ? StateColor::darkModeColorFor("#262E30") : StateColor::darkModeColorFor(wxColour(144, 144, 144)); dc.SetPen(wxPen(clr)); dc.SetTextForeground(clr); dc.SetFont(::Label::Body_13); auto text_size = dc.GetTextExtent(text_value); auto text_left = left + (PRINT_OPT_WIDTH - text_size.x) / 2; auto text_top = (size.y - text_size.y) / 2; dc.DrawText(text_value, wxPoint(text_left, text_top)); } left += PRINT_OPT_WIDTH; } } void PrintOptionItem::setValue(std::string value) { if (selected_key != value) { selected_key = value; Refresh(); } } void PrintOptionItem::msw_rescale() { m_selected_bk.msw_rescale(); m_selected_bk_dark.msw_rescale(); m_selected_disabled_bk.msw_rescale(); m_selected_disabled_bk_dark.msw_rescale(); Refresh(); } SendModeSwitchButton::SendModeSwitchButton(wxWindow *parent, wxString mode, bool sel) : wxPanel(parent, wxID_ANY, wxDefaultPosition, wxDefaultSize) { #ifdef __WINDOWS__ SetDoubleBuffered(true); #endif //__WINDOWS__ is_selected = sel; SetLabel(mode); Bind(wxEVT_PAINT, &SendModeSwitchButton::OnPaint, this); Bind(wxEVT_ENTER_WINDOW, [this](auto &e) { SetCursor(wxCURSOR_HAND); }); Bind(wxEVT_LEAVE_WINDOW, [this](auto &e) { SetCursor(wxCURSOR_ARROW); }); SetBackgroundColour(*wxWHITE); static Slic3r::GUI::BitmapCache cache; m_img_selected = ScalableBitmap(this, "switch_send_mode_on", 28); m_img_unselected = ScalableBitmap(this, "switch_send_mode_off", 28); m_img_selected_tag = ScalableBitmap(this, "switch_send_mode_tag_on", 16); m_img_unselected_tag = ScalableBitmap(this, "switch_send_mode_tag_off", 16); auto tagSize = wxSize(FromDIP(185), FromDIP(28)); SetMinSize(tagSize); SetMaxSize(tagSize); wxGetApp().UpdateDarkUIWin(this); } void SendModeSwitchButton::OnPaint(wxPaintEvent &event) { wxPaintDC dc(this); doRender(dc); } void SendModeSwitchButton::render(wxDC &dc) { #ifdef __WXMSW__ wxSize size = GetSize(); wxMemoryDC memdc; wxBitmap bmp(size.x, size.y); memdc.SelectObject(bmp); memdc.Blit({0, 0}, size, &dc, {0, 0}); { wxGCDC dc2(memdc); doRender(dc2); } memdc.SelectObject(wxNullBitmap); dc.DrawBitmap(bmp, 0, 0); #else doRender(dc); #endif } void SendModeSwitchButton::doRender(wxDC &dc) { dc.SetPen(wxPen(*wxTRANSPARENT_PEN)); dc.SetBrush(wxBrush(*wxTRANSPARENT_BRUSH)); auto size = GetSize(); auto textSize = dc.GetTextExtent(GetLabel()); auto left =(size.x - textSize.x - m_img_selected_tag.GetBmpSize().x - FromDIP(8)) / 2; dc.SetFont(::Label::Head_14); if (is_selected) { dc.DrawBitmap(m_img_selected.bmp(), wxPoint(0, 0)); dc.DrawBitmap(m_img_selected_tag.bmp(), wxPoint(left, (size.y - m_img_selected_tag.GetBmpSize().y) / 2)); dc.SetTextForeground(0x00AE42); }else { dc.DrawBitmap(m_img_unselected.bmp(), wxPoint(0, 0)); dc.DrawBitmap(m_img_unselected_tag.bmp(), wxPoint(left, (size.y - m_img_selected_tag.GetBmpSize().y) / 2)); dc.SetTextForeground(0x5C5C5C); } dc.DrawText(GetLabel(), left + m_img_selected_tag.GetBmpSize().x + FromDIP(8), (size.y - textSize.y) / 2); } void SendModeSwitchButton::on_left_down(wxMouseEvent &evt) { setSelected(!is_selected); } void SendModeSwitchButton::msw_rescale() { m_img_selected.msw_rescale(); m_img_unselected.msw_rescale(); m_img_selected_tag.msw_rescale(); m_img_unselected_tag.msw_rescale(); Refresh(); } void SendModeSwitchButton::setSelected(bool selected) { if (is_selected != selected) { is_selected = selected; Refresh(); } } PrinterInfoBox::PrinterInfoBox(wxWindow* parent, SelectMachineDialog* select_dialog) : StaticBox(parent, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxNO_BORDER), m_select_dialog(select_dialog) { Create(); } void PrinterInfoBox::UpdatePlate(const std::string& plate_name) { if (plate_name.empty()) { m_text_bed_type->Hide(); } else { wxString name; if (plate_name == "Cool Plate") { name = _L("Cool"); m_bed_image->SetBitmap(create_scaled_bitmap("bed_cool", this, 32)); } else if (plate_name == "Engineering Plate") { name = _L("Engineering"); m_bed_image->SetBitmap(create_scaled_bitmap("bed_engineering", this, 32)); } else if (plate_name == "High Temp Plate") { name = _L("High Temp"); m_bed_image->SetBitmap(create_scaled_bitmap("bed_high_templ", this, 32)); } else if (plate_name == "Textured PEI Plate") { name = "PEI"; m_bed_image->SetBitmap(create_scaled_bitmap("bed_texture", this, 32)); } else if (plate_name == "Supertack Plate") { name = _L("Cool(Supertack)"); m_bed_image->SetBitmap(create_scaled_bitmap("bed_cool_supertack", this, 32)); } if (name.length() > 8) { m_text_bed_type->SetFont(Label::Body_9); } else{ m_text_bed_type->SetFont(Label::Body_12); } m_text_bed_type->SetLabelText(name); m_text_bed_type->Show(); } } static wxString _get_tips(MachineObject* obj_) { wxString tips; tips = obj_->get_printer_type_display_str(); wxString ext_diameter; if (obj_->GetExtderSystem()->GetTotalExtderCount() == 1) { ext_diameter += wxString::FromDouble(obj_->GetExtderSystem()->GetNozzleDiameter(0)); ext_diameter += "mm"; } else if (obj_->GetExtderSystem()->GetTotalExtderCount() == 2) { ext_diameter += wxString::FromDouble(obj_->GetExtderSystem()->GetNozzleDiameter(1));//Left ext_diameter += "/"; ext_diameter += wxString::FromDouble(obj_->GetExtderSystem()->GetNozzleDiameter(0)); ext_diameter += "mm"; } else { assert(0); } if (!ext_diameter.empty()) { tips += " "; tips += ext_diameter; } return tips; } void PrinterInfoBox::SetPrinters(const std::vector& sorted_printers) { m_comboBox_printer->Clear(); std::vector drop_items; for (MachineObject* obj : sorted_printers) { wxString shown_dev_name = wxString::FromUTF8(obj->get_dev_name()); if (obj->is_lan_mode_printer()) { shown_dev_name += "(LAN)"; } DropDown::Item drop_item; drop_item.text = shown_dev_name; drop_item.text_static_tips = _get_tips(obj); // update image try { drop_item.icon = create_scaled_bitmap("printer_preview_" + obj->printer_type, this, 32); drop_item.icon_textctrl = create_scaled_bitmap("printer_preview_" + obj->printer_type, this, 52); } catch (const std::exception&) { drop_item.icon = create_scaled_bitmap("printer_preview_BL-P001", this, 32); drop_item.icon_textctrl = create_scaled_bitmap("printer_preview_BL-P001", this, 52); } drop_item.tip = obj->get_printer_type_display_str(); drop_items.emplace_back(drop_item); } m_comboBox_printer->SetItems(drop_items); } void PrinterInfoBox::EnableEditing(bool enable) { m_comboBox_printer->Enable(enable); m_button_refresh->Enable(enable); } void PrinterInfoBox::EnableRefreshButton(bool enable) { if (m_button_refresh->IsEnabled() != enable) { m_button_refresh->Enable(enable); } } void PrinterInfoBox::SetDefault(bool from_sd) { if (!from_sd) { m_comboBox_printer->Show(true); m_button_refresh->Show(true); } else { m_comboBox_printer->Show(true); m_button_refresh->Show(false); } m_comboBox_printer->Clear(); m_comboBox_printer->SetValue(wxEmptyString); m_comboBox_printer->Enable(); } void PrinterInfoBox::Create() { /*printer combobox*/ wxBoxSizer* sizer_split_printer = new wxBoxSizer(wxHORIZONTAL); m_stext_printer_title = new Label(this, _L("Printer"), wxALIGN_TOP); m_stext_printer_title->SetFont(::Label::Head_13); m_stext_printer_title->SetForegroundColour(0x909090); m_button_question = new ScalableButton(this, wxID_ANY, "icon_qusetion", wxEmptyString, wxDefaultSize, wxDefaultPosition, wxBU_EXACTFIT | wxNO_BORDER, true); m_button_question->Bind(wxEVT_BUTTON, &PrinterInfoBox::OnBtnQuestionClicked, this); m_button_question->SetToolTip(_L("Click here if you can't connect to the printer")); m_button_question->SetMinSize(wxSize(FromDIP(18), FromDIP(18))); m_button_question->SetMaxSize(wxSize(FromDIP(18), FromDIP(18))); auto m_split_line = new wxPanel(this, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL); m_split_line->SetBackgroundColour(0xeeeeee); m_split_line->SetMinSize(wxSize(-1, 1)); m_split_line->SetMaxSize(wxSize(-1, 1)); sizer_split_printer->Add(0, 0, 0, wxEXPAND, 0); sizer_split_printer->Add(m_stext_printer_title, 0, wxALIGN_CENTER_VERTICAL, 0); sizer_split_printer->AddSpacer(FromDIP(5)); sizer_split_printer->Add(m_button_question, 0, wxALIGN_CENTER_VERTICAL, 0); sizer_split_printer->Add(m_split_line, 1, wxALIGN_CENTER_VERTICAL, 0); wxBoxSizer* sizer_printer_area = new wxBoxSizer(wxHORIZONTAL); wxBoxSizer* sizer_bed_staticbox = new wxBoxSizer(wxVERTICAL); wxBoxSizer* sizer_printer_staticbox = new wxBoxSizer(wxHORIZONTAL); /*printer area*/ auto printer_staticbox = new StaticBox(this); printer_staticbox->SetMinSize(wxSize(FromDIP(338), FromDIP(68))); printer_staticbox->SetMaxSize(wxSize(FromDIP(338), FromDIP(68))); printer_staticbox->SetBorderColor(wxColour("#CECECE")); m_comboBox_printer = new ComboBox(printer_staticbox, wxID_ANY, "", wxDefaultPosition, wxDefaultSize, 0, nullptr, wxCB_READONLY); m_comboBox_printer->SetBorderWidth(0); m_comboBox_printer->SetMinSize(wxSize(FromDIP(300), FromDIP(60))); m_comboBox_printer->SetMaxSize(wxSize(FromDIP(300), FromDIP(60))); m_comboBox_printer->SetBackgroundColor(*wxWHITE); m_comboBox_printer->Bind(wxEVT_COMBOBOX, &SelectMachineDialog::on_selection_changed, m_select_dialog); m_button_refresh = new ScalableButton(printer_staticbox, wxID_ANY, "refresh_printer", wxEmptyString, wxDefaultSize, wxDefaultPosition, wxBU_EXACTFIT | wxNO_BORDER, true); m_button_refresh->Bind(wxEVT_BUTTON, &SelectMachineDialog::on_refresh, m_select_dialog); sizer_printer_staticbox->Add(0, 0, 0, wxLEFT, FromDIP(7)); sizer_printer_staticbox->Add(m_comboBox_printer, 0, wxALIGN_CENTER, 0); sizer_printer_staticbox->Add(m_button_refresh, 0, wxALIGN_CENTER, 0); printer_staticbox->SetSizer(sizer_printer_staticbox); printer_staticbox->Layout(); printer_staticbox->Fit(); /*bed area*/ auto bed_staticbox = new StaticBox(this); bed_staticbox->SetMinSize(wxSize(FromDIP(98), FromDIP(68))); bed_staticbox->SetMaxSize(wxSize(FromDIP(98), FromDIP(68))); bed_staticbox->SetBorderColor(wxColour("#EEEEEE")); m_bed_image = new wxStaticBitmap(bed_staticbox, wxID_ANY, create_scaled_bitmap("bed_cool", this, 32)); m_bed_image->SetBackgroundColour(*wxWHITE); m_bed_image->SetMinSize(wxSize(FromDIP(32), FromDIP(32))); m_bed_image->SetMaxSize(wxSize(FromDIP(32), FromDIP(32))); m_text_bed_type = new Label(bed_staticbox); m_text_bed_type->SetForegroundColour(wxColour(144, 144, 144)); m_text_bed_type->SetMaxSize(wxSize(FromDIP(80), FromDIP(24))); m_text_bed_type->SetFont(Label::Body_13); sizer_bed_staticbox->Add(0, 0, 0, wxTOP, FromDIP(10)); sizer_bed_staticbox->Add(m_bed_image, 0, wxALIGN_CENTER, 0); sizer_bed_staticbox->AddSpacer(FromDIP(6)); sizer_bed_staticbox->Add(m_text_bed_type, 0, wxALIGN_CENTER, 0); bed_staticbox->SetSizer(sizer_bed_staticbox); bed_staticbox->Layout(); bed_staticbox->Fit(); sizer_printer_area->Add(printer_staticbox, 0, wxALIGN_CENTER, 0); sizer_printer_area->Add(0, 0, 0, wxLEFT, FromDIP(4)); sizer_printer_area->Add(bed_staticbox, 0, wxALIGN_CENTER, 0); wxSizer* main_sizer = new wxBoxSizer(wxVERTICAL); main_sizer->Add(m_text_bed_type, 0, wxTOP, 0); main_sizer->Add(0, 0, 0, wxTOP, FromDIP(15)); main_sizer->Add(sizer_split_printer, 1, wxEXPAND, 0); main_sizer->Add(0, 0, 0, wxTOP, FromDIP(8)); main_sizer->Add(sizer_printer_area, 0, wxTOP, 0); SetSizer(main_sizer); } void PrinterInfoBox::OnBtnQuestionClicked(wxCommandEvent& event) { wxLaunchDefaultBrowser(wxT("https://wiki.bambulab.com/en/software/bambu-studio/failed-to-connect-printer")); } static const wxColour NOZZLE_STATE_PANEL_BG("#F8F8F8"); // Panel geometry from the design, in DIP static constexpr int NOZZLE_STATE_PANEL_WIDTH = 420; static constexpr int NOZZLE_STATE_SIDE_PADDING = 11; static constexpr int NOZZLE_STATE_TB_PADDING = 8; static constexpr int NOZZLE_STATE_COLUMN_WIDTH = 183; static constexpr int NOZZLE_STATE_COLUMN_GAP = 35; static constexpr int NOZZLE_STATE_ROW_SPACING = 3; // SetBackgroundColorNormal() is a no-op until the Normal state exists static void s_set_panel_background(StaticBox* box) { box->SetBackgroundColor(StateColor(NOZZLE_STATE_PANEL_BG)); } NozzleStatePanel::NozzleStatePanel(wxWindow* parent) : StaticBox(parent) { SetBorderWidth(0); SetCornerRadius(FromDIP(8)); s_set_panel_background(this); SetMinSize(wxSize(FromDIP(NOZZLE_STATE_PANEL_WIDTH), -1)); m_sizer = new wxBoxSizer(wxHORIZONTAL); SetSizer(m_sizer); } static Label* s_create_content_label(wxWindow* parent, const wxString& content_str) { Label* label = new Label(parent, content_str); label->SetBackgroundColour(StateColor::darkModeColorFor(NOZZLE_STATE_PANEL_BG)); label->SetForegroundColour(StateColor::darkModeColorFor(WXCOLOUR_GREY700)); label->SetFont(Label::Body_12); return label; } // Rack row: the count gets its own label so that it stays neutral when the row is highlighted static NozzleRowLabels s_create_hcontent(wxWindow* parent, wxSizer* row_sizer, const wxString& ext_loc_str, const NozzleDef& nozzle_info, int count) { wxString content_str; if (!ext_loc_str.IsEmpty()) { content_str += ext_loc_str; content_str += ": "; } content_str += wxString::Format("%0.1fmm", nozzle_info.nozzle_diameter); content_str += " "; content_str += DevNozzle::GetNozzleFlowTypeStr(nozzle_info.nozzle_flow_type); NozzleRowLabels labels; labels.whole = s_create_content_label(parent, content_str); row_sizer->Add(labels.whole, 0, wxALIGN_CENTER_VERTICAL); row_sizer->AddSpacer(parent->FromDIP(4)); row_sizer->Add(s_create_content_label(parent, wxString::Format("x%d", count)), 0, wxALIGN_CENTER_VERTICAL); return labels; } // Row without rack: "L: 0.4mm High Flow", split so that only the mismatching part turns red static NozzleRowLabels s_create_split_hcontent(wxWindow* parent, wxSizer* row_sizer, const wxString& prefix_str, const NozzleDef& nozzle_info) { NozzleRowLabels labels; if (!prefix_str.IsEmpty()) { row_sizer->Add(s_create_content_label(parent, prefix_str + ": "), 0, wxALIGN_CENTER_VERTICAL); } labels.diameter = s_create_content_label(parent, wxString::Format("%0.1fmm", nozzle_info.nozzle_diameter)); row_sizer->Add(labels.diameter, 0, wxALIGN_CENTER_VERTICAL); row_sizer->AddSpacer(parent->FromDIP(4)); labels.flow = s_create_content_label(parent, DevNozzle::GetNozzleFlowTypeStr(nozzle_info.nozzle_flow_type)); row_sizer->Add(labels.flow, 0, wxALIGN_CENTER_VERTICAL); return labels; } static Label* s_create_title_label(wxWindow* parent, const wxString& title_str) { Label* label = new Label(parent, title_str); label->SetBackgroundColour(StateColor::darkModeColorFor(NOZZLE_STATE_PANEL_BG)); label->SetForegroundColour(StateColor::darkModeColorFor(ThemeColor::TextPrimary)); label->SetFont(Label::Head_12); return label; } void NozzleStatePanel::AppendNozzleRows(wxSizer* vbox, int ext_id, bool slicing_side) { const ExtruderNozzleInfos& infos = slicing_side ? m_slicing_nozzles : m_installed_nozzles; const auto ext_iter = infos.find(ext_id); if (ext_iter == infos.end()) { return; } // English initial ("Left" -> "L") keeps the prefix one character in every language, // a single extruder needs no prefix at all wxString ext_name; if (m_total_ext_count > 1) { const std::string& short_name = DevPrinterConfigUtil::get_toolhead_display_name(m_printer_type, ext_id, ToolHeadComponent::Extruder, ToolHeadNameCase::TitleCase, true); if (!short_name.empty()) { ext_name = wxString::FromUTF8(short_name.substr(0, 1)); } } for (const auto& item : ext_iter->second) { vbox->AddSpacer(FromDIP(NOZZLE_STATE_ROW_SPACING)); wxSizer* row_sizer = new wxBoxSizer(wxHORIZONTAL); const NozzleRowLabels labels = m_has_rack ? s_create_hcontent(this, row_sizer, ext_name, item.first, item.second) : s_create_split_hcontent(this, row_sizer, ext_name, item.first); vbox->Add(row_sizer, 0, wxALIGN_LEFT); if (slicing_side) { m_slicing_labels[ext_id][item.first] = labels; } } } void NozzleStatePanel::UpdateGui() { m_sizer->Clear(true); m_slicing_labels.clear(); // Slicing wxSizer* slicing_vbox = new wxBoxSizer(wxVERTICAL); slicing_vbox->SetMinSize(wxSize(FromDIP(NOZZLE_STATE_COLUMN_WIDTH), -1)); slicing_vbox->Add(s_create_title_label(this, _L("Slice file")), 0, wxALIGN_LEFT); AppendNozzleRows(slicing_vbox, DEPUTY_EXTRUDER_ID, true); AppendNozzleRows(slicing_vbox, MAIN_EXTRUDER_ID, true); UpdateLabelColour(); // Installed wxSizer* installed_vbox = new wxBoxSizer(wxVERTICAL); installed_vbox->Add(s_create_title_label(this, _L("The connected printer")), 0, wxALIGN_LEFT); AppendNozzleRows(installed_vbox, DEPUTY_EXTRUDER_ID, false); AppendNozzleRows(installed_vbox, MAIN_EXTRUDER_ID, false); wxPanel* separator = new wxPanel(this); separator->SetMaxSize(wxSize(FromDIP(1), -1)); separator->SetMinSize(wxSize(FromDIP(1), -1)); separator->SetBackgroundColour(StateColor::darkModeColorFor(WXCOLOUR_GREY300)); m_sizer->AddSpacer(FromDIP(NOZZLE_STATE_SIDE_PADDING)); m_sizer->Add(slicing_vbox, 0, wxTOP | wxBOTTOM, FromDIP(NOZZLE_STATE_TB_PADDING)); m_sizer->Add(separator, 0, wxEXPAND | wxTOP | wxBOTTOM, FromDIP(6)); m_sizer->AddSpacer(FromDIP(NOZZLE_STATE_COLUMN_GAP)); m_sizer->Add(installed_vbox, 0, wxTOP | wxBOTTOM, FromDIP(NOZZLE_STATE_TB_PADDING)); m_sizer->AddSpacer(FromDIP(NOZZLE_STATE_SIDE_PADDING)); wxGetApp().UpdateDarkUIWin(this); // UpdateDarkUIWin remaps the colours, re-apply the background s_set_panel_background(this); for (auto child : GetChildren()) { if (child != separator) { child->SetBackgroundColour(StateColor::darkModeColorFor(NOZZLE_STATE_PANEL_BG)); } } Layout(); Fit(); } void NozzleStatePanel::UpdateInfoBy(Plater* plater, MachineObject* obj) { if (plater && obj) { m_printer_type = obj->printer_type; auto ext_sys = obj->GetExtderSystem(); auto nozzle_sys = obj->GetNozzleSystem(); m_total_ext_count = ext_sys->GetTotalExtderCount(); m_has_rack = nozzle_sys->GetNozzleRack()->IsSupported(); if (m_has_rack) { auto nozzle_group_res = DevUtilBackend::GetNozzleGroupResult(plater); if (nozzle_group_res) { ExtruderNozzleInfos slicing_nozzle_infos; slicing_nozzle_infos[MAIN_EXTRUDER_ID] = DevUtilBackend::CollectNozzleInfo(nozzle_group_res.get(), LOGIC_R_EXTRUDER_ID); slicing_nozzle_infos[DEPUTY_EXTRUDER_ID] = DevUtilBackend::CollectNozzleInfo(nozzle_group_res.get(), LOGIC_L_EXTRUDER_ID); UpdateInfo(slicing_nozzle_infos, nozzle_sys->GetExtruderNozzleInfo()); return; } } else { // Keyed by physical extruder id like GetExtruderNozzleInfo(), CollectNozzleInfo() uses logic ids ExtruderNozzleInfos slicing_nozzle_infos; for (const auto& slicing_ext_nozzle : s_get_slicing_extuder_nozzles()) { slicing_nozzle_infos[slicing_ext_nozzle.first][slicing_ext_nozzle.second]++; } if (!slicing_nozzle_infos.empty()) { UpdateInfo(slicing_nozzle_infos, nozzle_sys->GetExtruderNozzleInfo()); return; } } } Show(false); } void NozzleStatePanel::UpdateInfo(const ExtruderNozzleInfos& slicing_nozzle_infos, const ExtruderNozzleInfos& machine_nozzle_infos) { if (m_slicing_nozzles != slicing_nozzle_infos || m_installed_nozzles != machine_nozzle_infos) { m_slicing_nozzles = slicing_nozzle_infos; m_installed_nozzles = machine_nozzle_infos; UpdateGui(); } }; static const NozzleRowLabels* s_find_labels(const std::unordered_map>& labels, int ext_id, const NozzleDef& nozzle_info) { auto iter = labels.find(ext_id); if (iter == labels.end()) { assert(0 && __FUNCTION__); return nullptr; } auto iter2 = iter->second.find(nozzle_info); if (iter2 == iter->second.end()) { assert(0 && __FUNCTION__); return nullptr; } return &iter2->second; } static void s_set_label_colour(Label* label, const wxColour& font_clr) { if (label) { label->SetForegroundColour(StateColor::darkModeColorFor(font_clr)); label->Refresh(); } } void NozzleStatePanel::UpdateLabelColour() { auto installed_count = [this](int ext_id, const NozzleDef& nozzle_info) { auto ext_iter = m_installed_nozzles.find(ext_id); if (ext_iter == m_installed_nozzles.end()) { return 0; } auto iter = ext_iter->second.find(nozzle_info); return (iter == ext_iter->second.end()) ? 0 : iter->second; }; for (const auto& slicing_items : m_slicing_nozzles) { const int ext_id = slicing_items.first; for (const auto& slicing_item : slicing_items.second) { if (slicing_item.second < 1) { continue; } const NozzleRowLabels* labels = s_find_labels(m_slicing_labels, ext_id, slicing_item.first); if (!labels) { continue; } if (m_has_rack) { // Rack: red when unavailable, orange when not enough of them const int installed = installed_count(ext_id, slicing_item.first); if (installed == 0) { s_set_label_colour(labels->whole, ThemeColor::Danger); } else if (slicing_item.second > installed) { s_set_label_colour(labels->whole, ThemeColor::Warning); } else { s_set_label_colour(labels->whole, WXCOLOUR_GREY700); } continue; } // One nozzle per extruder: highlight only the mismatching part bool diameter_matched = false; bool flow_matched = false; auto installed_iter = m_installed_nozzles.find(ext_id); if (installed_iter != m_installed_nozzles.end()) { for (const auto& installed_item : installed_iter->second) { if (installed_item.second < 1) { continue; } if (installed_item.first.nozzle_diameter == slicing_item.first.nozzle_diameter) { diameter_matched = true; } if (installed_item.first.nozzle_flow_type == slicing_item.first.nozzle_flow_type) { flow_matched = true; } } } // Both parts exist but not on the same nozzle: highlight everything if (diameter_matched && flow_matched && installed_count(ext_id, slicing_item.first) == 0) { diameter_matched = false; flow_matched = false; } s_set_label_colour(labels->diameter, diameter_matched ? WXCOLOUR_GREY700 : ThemeColor::Danger); s_set_label_colour(labels->flow, flow_matched ? WXCOLOUR_GREY700 : ThemeColor::Danger); } } } } } // namespace Slic3r::GUI