diff --git a/src/slic3r/GUI/DeviceCore/CMakeLists.txt b/src/slic3r/GUI/DeviceCore/CMakeLists.txt index 57fd747228..e06cbe0abe 100644 --- a/src/slic3r/GUI/DeviceCore/CMakeLists.txt +++ b/src/slic3r/GUI/DeviceCore/CMakeLists.txt @@ -28,6 +28,8 @@ list(APPEND SLIC3R_GUI_SOURCES GUI/DeviceCore/DevFilaSystem.h GUI/DeviceCore/DevFilaSystem.cpp GUI/DeviceCore/DevFilaSystemCtrl.cpp + GUI/DeviceCore/DevFilaSwitch.h + GUI/DeviceCore/DevFilaSwitch.cpp GUI/DeviceCore/DevFirmware.h GUI/DeviceCore/DevFirmware.cpp GUI/DeviceCore/DevPrintOptions.h @@ -47,10 +49,17 @@ list(APPEND SLIC3R_GUI_SOURCES GUI/DeviceCore/DevManager.cpp GUI/DeviceCore/DevMapping.h GUI/DeviceCore/DevMapping.cpp + GUI/DeviceCore/DevMappingNozzle.h + GUI/DeviceCore/DevMappingNozzle.cpp GUI/DeviceCore/DevNozzleSystem.h GUI/DeviceCore/DevNozzleSystem.cpp + GUI/DeviceCore/DevNozzleRack.h + GUI/DeviceCore/DevNozzleRack.cpp + GUI/DeviceCore/DevNozzleRackCtrl.cpp GUI/DeviceCore/DevUtil.h GUI/DeviceCore/DevUtil.cpp + GUI/DeviceCore/DevUtilBackend.h + GUI/DeviceCore/DevUtilBackend.cpp ) set(SLIC3R_GUI_SOURCES ${SLIC3R_GUI_SOURCES} PARENT_SCOPE) \ No newline at end of file diff --git a/src/slic3r/GUI/DeviceCore/DevConfigUtil.cpp b/src/slic3r/GUI/DeviceCore/DevConfigUtil.cpp index 14fa87c127..03c5c0587d 100644 --- a/src/slic3r/GUI/DeviceCore/DevConfigUtil.cpp +++ b/src/slic3r/GUI/DeviceCore/DevConfigUtil.cpp @@ -87,6 +87,28 @@ std::string DevPrinterConfigUtil::get_printer_ext_img(const std::string& type_st return (vec.size() > pos) ? vec[pos] : std::string(); }; +std::string DevPrinterConfigUtil::get_filament_load_img(const std::string &type_str, int ext_id, bool has_nozzle_rack) +{ + if (has_nozzle_rack) + { + const auto &rack_vec = get_value_from_config>(type_str, "filament_load_image_nozzle_rack"); + if (!rack_vec.empty()) + { + if (ext_id >= 0 && static_cast(ext_id) < rack_vec.size()) + { + return rack_vec[ext_id]; + } + return rack_vec[0]; + } + } + const auto &vec = get_value_from_config>(type_str, "filament_load_image"); + if (ext_id >= 0 && static_cast(ext_id) < vec.size()) + { + return vec[ext_id]; + } + return vec.empty() ? std::string() : vec[0]; +} + std::string DevPrinterConfigUtil::get_fan_text(const std::string& type_str, const std::string& key) { std::vector filaments; diff --git a/src/slic3r/GUI/DeviceCore/DevConfigUtil.h b/src/slic3r/GUI/DeviceCore/DevConfigUtil.h index 48b458768c..f8f9b21199 100644 --- a/src/slic3r/GUI/DeviceCore/DevConfigUtil.h +++ b/src/slic3r/GUI/DeviceCore/DevConfigUtil.h @@ -74,6 +74,7 @@ public: static std::string get_printer_use_ams_type(std::string type_str) { return get_value_from_config(type_str, "use_ams_type"); } static std::string get_printer_ams_img(const std::string& type_str) { return get_value_from_config(type_str, "printer_use_ams_image"); } static std::string get_printer_ext_img(const std::string& type_str, int pos);//printer_ext_image + static std::string get_filament_load_img(const std::string &type_str, int ext_id, bool has_nozzle_rack = false); /*fan*/ static std::string get_fan_text(const std::string& type_str, const std::string& key); diff --git a/src/slic3r/GUI/DeviceCore/DevDefs.h b/src/slic3r/GUI/DeviceCore/DevDefs.h index 468ed0184a..204064645c 100644 --- a/src/slic3r/GUI/DeviceCore/DevDefs.h +++ b/src/slic3r/GUI/DeviceCore/DevDefs.h @@ -10,6 +10,7 @@ #pragma once #include +#include enum PrinterArch { @@ -47,21 +48,44 @@ enum AmsStatusMain #define VIRTUAL_AMS_MAIN_ID_STR "255" #define VIRTUAL_AMS_DEPUTY_ID_STR "254" +// Tray index offset for the AMS-Lite-mixed unit (A2L / N9). Its 4 trays occupy +// global tray indices 24..27. +#define AMS_LITE_MIXED_TRAY_INDEX_OFFSET 24 + #define INVALID_AMS_TEMPERATURE std::numeric_limits::min() +// (ams_id, slot_id) pair. +using DevAmsSlotId = std::pair; + /* Extruder*/ #define MAIN_EXTRUDER_ID 0 #define DEPUTY_EXTRUDER_ID 1 #define UNIQUE_EXTRUDER_ID MAIN_EXTRUDER_ID #define INVALID_EXTRUDER_ID -1 +/* Logical extruder ids (multi-nozzle). */ +#define LOGIC_UNIQUE_EXTRUDER_ID 0 +#define LOGIC_L_EXTRUDER_ID 0 +#define LOGIC_R_EXTRUDER_ID 1 + /* Nozzle*/ enum NozzleFlowType { NONE_FLOWTYPE, S_FLOW, - H_FLOW + H_FLOW, + U_FLOW, // TPU 1.75 High Flow (device-reported; maps to nvtTPUHighFlow) +}; + +// Discrete nozzle diameters reported by the device. +enum NozzleDiameterType : int +{ + NONE_DIAMETER_TYPE, + NOZZLE_DIAMETER_0_2, + NOZZLE_DIAMETER_0_4, + NOZZLE_DIAMETER_0_6, + NOZZLE_DIAMETER_0_8 }; /*Print speed*/ @@ -96,4 +120,16 @@ public: static bool IsVirtualSlot(const std::string& ams_id) { return (ams_id == VIRTUAL_AMS_MAIN_ID_STR || ams_id == VIRTUAL_AMS_DEPUTY_ID_STR); } }; +namespace GUI +{ +// Print source. Defined here (rather than in SelectMachine.hpp) so the shared device-mapping +// GUI headers — AmsMappingPopup, wgtDeviceNozzleSelect — can name it without pulling in +// SelectMachine.hpp, which would create an include cycle. +enum PrintFromType +{ + FROM_NORMAL, + FROM_SDCARD_VIEW, +}; +} + };// namespace Slic3r \ No newline at end of file diff --git a/src/slic3r/GUI/DeviceCore/DevExtruderSystem.cpp b/src/slic3r/GUI/DeviceCore/DevExtruderSystem.cpp index 6c96d85496..7f49f4ab3a 100644 --- a/src/slic3r/GUI/DeviceCore/DevExtruderSystem.cpp +++ b/src/slic3r/GUI/DeviceCore/DevExtruderSystem.cpp @@ -1,6 +1,7 @@ #include #include "DevExtruderSystem.h" #include "DevNozzleSystem.h" +#include "DevFilaSystem.h" // complete type for GetFilaSystem()->GetTrayIndexMap() in GetBackupAmsSlotInGroup // TODO: remove this include #include "slic3r/GUI/DeviceManager.hpp" @@ -48,17 +49,43 @@ namespace Slic3r NozzleType DevExtder::GetNozzleType() const { - return system->Owner()->GetNozzleSystem()->GetNozzle(m_current_nozzle_id).m_nozzle_type; + return system->Owner()->GetNozzleSystem()->GetExtNozzle(m_current_nozzle_id).m_nozzle_type; } NozzleFlowType DevExtder::GetNozzleFlowType() const { - return system->Owner()->GetNozzleSystem()->GetNozzle(m_current_nozzle_id).m_nozzle_flow; + return system->Owner()->GetNozzleSystem()->GetExtNozzle(m_current_nozzle_id).m_nozzle_flow; } float DevExtder::GetNozzleDiameter() const { - return system->Owner()->GetNozzleSystem()->GetNozzle(m_current_nozzle_id).m_diameter; + return system->Owner()->GetNozzleSystem()->GetExtNozzle(m_current_nozzle_id).m_diameter; + } + + std::unordered_map DevExtder::GetBackupStatus(unsigned int fila_back_group) + { + std::unordered_map trayid_group; + for (int i = 0; i < 16; i++) + { + if (fila_back_group & (1 << i)) + { + trayid_group[i] = true; + } + } + + for (int j = 16; j <= 23; j++)/* single ams is from 128*/ + { + if (fila_back_group & (1 << j)) { + trayid_group[128 + j - 16] = true; + } + } + + for (int i = 24; i <= 27; i++) /* ams lite for N9*/ + { + if (fila_back_group & (1 << i)) { trayid_group[i] = true; } + } + + return trayid_group; } DevExtderSystem::DevExtderSystem(MachineObject* obj) @@ -143,6 +170,24 @@ namespace Slic3r return false; } + std::vector DevExtderSystem::GetBackupAmsSlotInGroup(const DevAmsSlotId& ams_slot_id) + { + std::map tray_map = Owner()->GetFilaSystem()->GetTrayIndexMap(); + + std::vector backup_group; + for (const auto& extruder : m_extders) { + for (int fila_backup : extruder.GetFilamBackup()) { + for (auto [tray_id, is_valid] : DevExtder::GetBackupStatus(fila_backup)) { + if (is_valid && tray_map.find(tray_id) != tray_map.end() && tray_map[tray_id] != ams_slot_id) { + backup_group.emplace_back(tray_map[tray_id]); + } + } + } + } + + return backup_group; + } + void ExtderSystemParser::ParseV1_0(const nlohmann::json& print_json, DevExtderSystem* system) { if (system->GetTotalExtderCount() != 1) diff --git a/src/slic3r/GUI/DeviceCore/DevExtruderSystem.h b/src/slic3r/GUI/DeviceCore/DevExtruderSystem.h index f4b174ba02..5e1b93174e 100644 --- a/src/slic3r/GUI/DeviceCore/DevExtruderSystem.h +++ b/src/slic3r/GUI/DeviceCore/DevExtruderSystem.h @@ -1,5 +1,6 @@ #pragma once #include +#include #include "libslic3r/CommonDefs.hpp" #include "slic3r/Utils/json_diff.hpp" @@ -66,6 +67,10 @@ public: bool HasFilamBackup() const { return !m_filam_bak.empty(); } std::vector GetFilamBackup() const { return m_filam_bak; } + // Decode a filament-backup bit group into { tray_id -> valid }. Bits 0-15 map directly to tray ids, + // bits 16-23 to single-AMS tray ids 128+, bits 24-27 to AMS-Lite-mixed (N9) tray ids 24-27. + static std::unordered_map GetBackupStatus(unsigned int fila_back_group); + // ams binding on current extruder const DevAmsSlotInfo& GetSlotPre() const { return m_spre; } const DevAmsSlotInfo& GetSlotNow() const { return m_snow; } @@ -168,6 +173,11 @@ public: bool HasFilamentBackup() const; bool HasFilamentInExt(int exter_id) { return GetExtderById(exter_id) ? GetExtderById(exter_id)->HasFilamentInExt() : false; } + // List the other AMS slots that back up the given slot within the same filament-backup group, + // across all extruders. Uses DevFilaSystem::GetTrayIndexMap() to translate backup bits (incl. the + // A2L/N9 AMS-Lite-mixed trays 24-27) into ams/slot ids. + std::vector GetBackupAmsSlotInGroup(const DevAmsSlotId& ams_slot_id); + protected: void AddExtder(const DevExtder& ext) { m_extders[ext.GetExtId()] = ext; }; diff --git a/src/slic3r/GUI/DeviceCore/DevFilaBlackList.cpp b/src/slic3r/GUI/DeviceCore/DevFilaBlackList.cpp index d2febcdec1..e75622720c 100644 --- a/src/slic3r/GUI/DeviceCore/DevFilaBlackList.cpp +++ b/src/slic3r/GUI/DeviceCore/DevFilaBlackList.cpp @@ -1,8 +1,12 @@ #include +#include +#include + #include "DevFilaBlackList.h" #include "DevFilaSystem.h" #include "DevManager.h" +#include "DevConfigUtil.h" #include "libslic3r/Utils.hpp" @@ -73,64 +77,97 @@ static std::string _get_filament_name_from_ams(int ams_id, int slot_id) } // moved from tao.wang and zhimin.zeng -void check_filaments(std::string model_id, - std::string tag_vendor, - std::string tag_type, - int ams_id, - int slot_id, - std::string tag_name, - bool& in_blacklist, - std::string& ac, - wxString& info, - wxString& wiki_url) +// Struct-in / struct-out, accumulate-all parser: every matching rule is pushed into +// result.action_items[action] and scanning continues (rather than stopping at the first match). +void check_filaments(const DevFilaBlacklist::CheckFilamentInfo& check_info, DevFilaBlacklist::CheckResult& result) { + std::string tag_type = check_info.fila_type; + std::string tag_name = check_info.fila_name; + std::string tag_vendor = check_info.fila_vendor; + std::string tag_calib_mode = check_info.calib_mode; + + // Orca: the print-send and calibration consumers do not populate fila_name; recover it from the + // selected AMS slot so name / name_suffix rules keep matching. if (tag_name.empty()) { - tag_name = _get_filament_name_from_ams(ams_id, slot_id); + tag_name = _get_filament_name_from_ams(check_info.ams_id, check_info.slot_id); } - in_blacklist = false; std::transform(tag_vendor.begin(), tag_vendor.end(), tag_vendor.begin(), ::tolower); std::transform(tag_type.begin(), tag_type.end(), tag_type.begin(), ::tolower); std::transform(tag_name.begin(), tag_name.end(), tag_name.begin(), ::tolower); + std::transform(tag_calib_mode.begin(), tag_calib_mode.end(), tag_calib_mode.begin(), ::tolower); DevFilaBlacklist::load_filaments_blacklist_config(); if (DevFilaBlacklist::filaments_blacklist.contains("blacklist")) { for (auto filament_item : DevFilaBlacklist::filaments_blacklist["blacklist"]) { - - std::string vendor = filament_item.contains("vendor") ? filament_item["vendor"].get() : ""; - std::string type = filament_item.contains("type") ? filament_item["type"].get() : ""; - std::string type_suffix = filament_item.contains("type_suffix") ? filament_item["type_suffix"].get() : ""; - std::string name = filament_item.contains("name") ? filament_item["name"].get() : ""; - std::string slot = filament_item.contains("slot") ? filament_item["slot"].get() : ""; - std::vector model_ids = filament_item.contains("model_id") ? filament_item["model_id"].get>() : std::vector(); std::string action = filament_item.contains("action") ? filament_item["action"].get() : ""; std::string description = filament_item.contains("description") ? filament_item["description"].get() : ""; + // blacklist items + std::string vendor = filament_item.contains("vendor") ? filament_item["vendor"].get() : ""; + std::string type = filament_item.contains("type") ? filament_item["type"].get() : ""; + std::vector types = filament_item.contains("types") ? filament_item["types"].get>() : std::vector(); + std::string type_suffix = filament_item.contains("type_suffix") ? filament_item["type_suffix"].get() : ""; + std::string name_suffix = filament_item.contains("name_suffix") ? filament_item["name_suffix"].get() : ""; + std::string name = filament_item.contains("name") ? filament_item["name"].get() : ""; + std::string slot = filament_item.contains("slot") ? filament_item["slot"].get() : ""; + std::optional used_for_print_support = filament_item.contains("used_for_print_support") ? filament_item["used_for_print_support"].get() : std::optional(); + std::optional used_for_print_object = filament_item.contains("used_for_print_object") ? filament_item["used_for_print_object"].get() : std::optional(); + std::vector model_ids = filament_item.contains("model_id") ? filament_item["model_id"].get>() : std::vector(); + std::vector extruder_ids = filament_item.contains("extruder_id") ? filament_item["extruder_id"].get>() : std::vector(); + std::vector nozzle_flows = filament_item.contains("nozzle_flows") ? filament_item["nozzle_flows"].get>() : std::vector(); + std::string calib_mode = filament_item.contains("calib_mode") ? filament_item["calib_mode"].get() : ""; + // check model id - if (!model_ids.empty() && std::find(model_ids.begin(), model_ids.end(), model_id) == model_ids.end()) { continue; } + if (!model_ids.empty() && std::find(model_ids.begin(), model_ids.end(), check_info.model_id) == model_ids.end()) { continue; } + + // A rule keyed on nozzle_flows/nozzle_diameters only matches when check_info carries real + // per-nozzle context (threaded by the print-send consumer). Consumers that leave + // nozzle_flow/nozzle_diameter unset (AMS edit, calibration, and the no-nozzle-context + // fallback) never satisfy the non-empty match below, so those rules stay dormant there. + + // check nozzle flows + if (!nozzle_flows.empty() && std::find(nozzle_flows.begin(), nozzle_flows.end(), check_info.nozzle_flow.value_or("")) == nozzle_flows.end()) { continue; } + + // check nozzle diameter + const std::vector nozzle_diameters = filament_item.contains("nozzle_diameters") ? filament_item["nozzle_diameters"].get>() : std::vector(); + if (!nozzle_diameters.empty() && check_info.nozzle_diameter.has_value() && + std::find(nozzle_diameters.begin(), nozzle_diameters.end(), check_info.nozzle_diameter.value()) == nozzle_diameters.end()) { + continue; + } // check vendor std::transform(vendor.begin(), vendor.end(), vendor.begin(), ::tolower); if (!vendor.empty()) { - if ((vendor == "bambu lab" && (tag_vendor == vendor)) || - (vendor == "third party" && (tag_vendor != "bambu lab"))) - { - // Do nothing - } - else + if (!((vendor == "bambu lab" && tag_vendor == "bambu lab") || + (vendor == "third party" && tag_vendor != "bambu lab"))) { continue; } } + // check calib mode + std::transform(calib_mode.begin(), calib_mode.end(), calib_mode.begin(), ::tolower); + if (!calib_mode.empty() && calib_mode != tag_calib_mode) { continue; } + // check type std::transform(type.begin(), type.end(), type.begin(), ::tolower); if (!type.empty() && (type != tag_type)) { continue; } + // check types (plural): item matches if tag_type is any of the listed types. + if (!types.empty()) + { + auto it = std::find_if(types.begin(), types.end(), [&tag_type](std::string ttype) { + std::transform(ttype.begin(), ttype.end(), ttype.begin(), ::tolower); + return ttype == tag_type; + }); + if (it == types.end()) { continue; } + } + // check type suffix std::transform(type_suffix.begin(), type_suffix.end(), type_suffix.begin(), ::tolower); if (!type_suffix.empty()) @@ -143,10 +180,31 @@ void check_filaments(std::string model_id, std::transform(name.begin(), name.end(), name.begin(), ::tolower); if (!name.empty() && (name != tag_name)) { continue; } + // check name suffix + std::transform(name_suffix.begin(), name_suffix.end(), name_suffix.begin(), ::tolower); + if (!name_suffix.empty()) + { + if (tag_name.length() < name_suffix.length()) { continue; } + if ((tag_name.substr(tag_name.length() - name_suffix.length()) != name_suffix)) { continue; } + } + + // check filament used for print support + if (used_for_print_support.has_value() && used_for_print_support != check_info.used_for_print_support) { continue; } + + // check filament used for print object + if (used_for_print_object.has_value() && used_for_print_object != check_info.used_for_print_object) { continue; } + + // check filament switch + if (filament_item.contains("has_filament_switch")) + { + bool has_filament_switch = filament_item["has_filament_switch"].get(); + if (check_info.has_filament_switch != has_filament_switch) { continue; } + } + // check loc if (!slot.empty()) { - bool is_virtual_slot = devPrinterUtil::IsVirtualSlot(ams_id); + bool is_virtual_slot = devPrinterUtil::IsVirtualSlot(check_info.ams_id); bool check_virtual_slot = (slot == "ext"); bool check_ams_slot = (slot == "ams"); if (is_virtual_slot && !check_virtual_slot) @@ -159,71 +217,115 @@ void check_filaments(std::string model_id, } } - if (GUI::wxGetApp().app_config->get("skip_ams_blacklist_check") == "true") { - action = "warning"; + // check extruder id + if (!extruder_ids.empty() && check_info.extruder_id.has_value() && + std::find(extruder_ids.begin(), extruder_ids.end(), check_info.extruder_id.value()) == extruder_ids.end()) { + continue; } - in_blacklist = true; - ac = action; - info = _L(description); - wiki_url = filament_item.contains("wiki") ? filament_item["wiki"].get() : ""; - return; + // white items: a matched rule is suppressed when the filament is whitelisted + { + // contains match + std::set white_names = filament_item.contains("white_names") ? filament_item["white_names"].get>() : std::set(); + if (!white_names.empty() && !tag_name.empty()) + { + auto it = std::find_if(white_names.begin(), white_names.end(), [&tag_name](std::string white_name) { + std::transform(white_name.begin(), white_name.end(), white_name.begin(), ::tolower); + return tag_name.find(white_name) != std::string::npos; + }); + if (it != white_names.end()) { continue; } + } + } + { + // equal match + std::set white_fila_ids = filament_item.contains("white_fila_ids") ? filament_item["white_fila_ids"].get>() : std::set(); + if (!white_fila_ids.empty() && !check_info.fila_id.empty()) + { + auto it = std::find_if(white_fila_ids.begin(), white_fila_ids.end(), [&check_info](const std::string& white_fila_id) { + return white_fila_id == check_info.fila_id; + }); + if (it != white_fila_ids.end()) { continue; } + } + } + + // the item is matched: accumulate it (do not stop scanning) + DevFilaBlacklist::CheckResultItem result_item; + result_item.action = action; + result_item.wiki_url = filament_item.contains("wiki") ? filament_item["wiki"].get() : ""; + + if (description == "%s has a risk of nozzle clogging when using 0.4mm high-flow nozzles. Use with caution.") { + result_item.info_msg = wxString::Format(_L(description), check_info.fila_name); + } else if (description == "%s filaments are hard and brittle and could break in AMS, and there is also a risk of nozzle clogging when using 0.4mm high-flow nozzles. Use with caution.") { + result_item.info_msg = wxString::Format(_L(description), check_info.fila_type); + } else if (description == "%s has a risk of nozzle clogging when using 0.4, 0.6, 0.8mm high-flow nozzles. Use with caution.") { + result_item.info_msg = wxString::Format(_L(description), check_info.fila_name); + } else if (description == "%s may fail to load or unload due to the Filament Track Switch. If you wish to continue.") { + result_item.info_msg = wxString::Format(_L(description), check_info.fila_name); + } else { + result_item.info_msg = _L(description); + } + + result.action_items[result_item.action].push_back(result_item); + continue; // Error in description L("TPU is not supported by AMS."); L("AMS does not support 'Bambu Lab PET-CF'."); + L("The current filament doesn't support the E3D high-flow nozzle and can't be used."); + L("The current filament doesn't support the TPU high-flow nozzle and can't be used."); + L("Auto dynamic flow calibration is not supported for TPU filament."); + L("Bambu TPU 85A is not supported for printing with 0.4 mm Standard or High Flow nozzles."); // Warning in description + L("How to feed TPU filament."); + L("How to feed TPU filament on X2D."); L("Please cold pull before printing TPU to avoid clogging. You may use cold pull maintenance on the printer."); L("Damp PVA will become flexible and get stuck inside AMS, please take care to dry it before use."); L("Damp PVA is flexible and may get stuck in extruder. Dry it before use."); L("The rough surface of PLA Glow can accelerate wear on the AMS system, particularly on the internal components of the AMS Lite."); + L("PLA Glow may wear the AMS first stage feeder. Use an external spool instead."); L("CF/GF filaments are hard and brittle, it's easy to break or get stuck in AMS, please use with caution."); L("PPS-CF is brittle and could break in bended PTFE tube above Toolhead."); L("PPA-CF is brittle and could break in bended PTFE tube above Toolhead."); + L("Default settings may affect print quality. Adjust as needed for best results."); + L("%s has a risk of nozzle clogging when using 0.4mm high-flow nozzles. Use with caution."); + L("%s filaments are hard and brittle and could break in AMS, and there is also a risk of nozzle clogging when using 0.4mm high-flow nozzles. Use with caution."); + L("%s has a risk of nozzle clogging when using 0.4, 0.6, 0.8mm high-flow nozzles. Use with caution."); + L("%s may fail to load or unload due to the Filament Track Switch. If you wish to continue."); } } } - -void DevFilaBlacklist::check_filaments_in_blacklist(std::string model_id, - std::string tag_vendor, - std::string tag_type, - const std::string& filament_id, - int ams_id, - int slot_id, - std::string tag_name, - bool& in_blacklist, - std::string& ac, - wxString& info) +// Extends the prohibition-only bit check with a 4-level filament_extruder_compatibility model +// (0 printable / 1 error / 2 critical warning / 3 warning), bowden-extruder message variants and +// toolhead display names for the "%s extruder" name. +// +// Orca: several deliberate divergences preserve H2D dual-extruder behaviour when the compatibility +// key is absent (default 0 = printable): +// - The extruder is resolved from the ams_id rather than a threaded check_info.extruder_id with an +// early-return: extruder_id is only threaded on the print-send consumer, so falling back on +// ams_id keeps the bit-check firing on the AMS-edit / calibration paths. +// - The prohibition bit-check keeps check_info.fila_type for its "%s", preserving the exact H2D +// AMS-edit message that ships today. The new 4-level messages use the fila_name fallback since +// they stay inert until a filament declares the compatibility key (only X2D ships it). +// - is_bowden_extruder is assigned correctly here so the Bowden message variants actually fire. +void check_filaments_printable(const DevFilaBlacklist::CheckFilamentInfo& check_info, DevFilaBlacklist::CheckResult& result) { - wxString wiki_url; - check_filaments_in_blacklist_url(model_id, tag_vendor, tag_type, filament_id, ams_id, slot_id, tag_name, in_blacklist, ac, info, wiki_url); -} + DeviceManager* dev = Slic3r::GUI::wxGetApp().getDeviceManager(); + if (!dev) { return; } + MachineObject* obj = dev->get_selected_machine(); + if (obj == nullptr || !obj->is_multi_extruders()) { return; } -bool check_filaments_printable(const std::string &tag_vendor, const std::string &tag_type, const std::string& filament_id, int ams_id, bool &in_blacklist, std::string &ac, wxString &info) -{ - DeviceManager *dev = Slic3r::GUI::wxGetApp().getDeviceManager(); - if (!dev) { - return true; - } + Preset* printer_preset = GUI::get_printer_preset(obj); + if (!printer_preset) { return; } - MachineObject *obj = dev->get_selected_machine(); - if (obj == nullptr || !obj->is_multi_extruders()) { - return true; - } + ConfigOptionInts* physical_extruder_map_op = dynamic_cast(printer_preset->config.option("physical_extruder_map")); + if (!physical_extruder_map_op) { return; } - Preset *printer_preset = GUI::get_printer_preset(obj); - if (!printer_preset) - return true; - - ConfigOptionInts *physical_extruder_map_op = dynamic_cast(printer_preset->config.option("physical_extruder_map")); - if (!physical_extruder_map_op) - return true; std::vector physical_extruder_maps = physical_extruder_map_op->values; - int obj_extruder_id = obj->get_extruder_id_by_ams_id(std::to_string(ams_id)); + int obj_extruder_id = obj->get_extruder_id_by_ams_id(std::to_string(check_info.ams_id)); int extruder_idx = obj_extruder_id; for (int index = 0; index < physical_extruder_maps.size(); ++index) { if (physical_extruder_maps[index] == obj_extruder_id) { @@ -232,32 +334,85 @@ bool check_filaments_printable(const std::string &tag_vendor, const std::string } } - PresetBundle *preset_bundle = GUI::wxGetApp().preset_bundle; - std::optional filament_info = preset_bundle->get_filament_by_filament_id(filament_id, printer_preset->name); - if (filament_info.has_value() && !(filament_info->filament_printable >> extruder_idx & 1)) { - wxString extruder_name = extruder_idx == 0 ? _L("left") : _L("right"); - ac = "prohibition"; - info = wxString::Format(_L("%s is not supported by %s extruder."), tag_type, extruder_name); - in_blacklist = true; - return false; + PresetBundle* preset_bundle = GUI::wxGetApp().preset_bundle; + std::optional filament_info = preset_bundle->get_filament_by_filament_id(check_info.fila_id, printer_preset->name); + if (!filament_info.has_value()) { return; } + + // Physical extruder 0 -> deputy (left), 1 -> main (right); the toolhead display name replaces the + // hard-coded left/right labels (falls back to "left"/"right" for printers without an override). + int bl_ext_id = (extruder_idx == 0) ? DEPUTY_EXTRUDER_ID : MAIN_EXTRUDER_ID; + wxString extruder_name = _L(DevPrinterConfigUtil::get_toolhead_display_name( + obj->printer_type, bl_ext_id, ToolHeadComponent::Extruder, ToolHeadNameCase::LowerCase, true)); + + // Prohibition-only bit check; keeps fila_type in the "%s" to preserve current behaviour. + if (!(filament_info->filament_printable >> extruder_idx & 1)) { + DevFilaBlacklist::CheckResultItem item; + item.action = "prohibition"; + item.info_msg = wxString::Format(_L("%s is not supported by %s extruder."), check_info.fila_type, extruder_name); + result.action_items[item.action].push_back(item); + return; } - return true; + // Is this a bowden extruder? (drives the message variant for compatibility warnings.) + bool is_bowden_extruder = false; + auto extruder_type_opt = dynamic_cast(printer_preset->config.option("extruder_type")); + if (extruder_type_opt && (int) extruder_type_opt->values.size() > extruder_idx) { + ExtruderType extruder_type = (ExtruderType) extruder_type_opt->values[extruder_idx]; + is_bowden_extruder = (extruder_type == ExtruderType::etBowden); + } + + // Compatibility levels: 0 = printable, 1 = error, 2 = critical warning, 3 = warning (4-7 reserved). + std::string fila_name = check_info.fila_name.empty() ? check_info.fila_type : check_info.fila_name; + int compatible_val = filament_info->get_extruder_compatibility(extruder_idx); + if (compatible_val == 0) { + // printable, nothing to report + } else if (compatible_val == 1) { + DevFilaBlacklist::CheckResultItem item; + item.action = "prohibition"; + item.info_msg = wxString::Format(_L("%s is not supported by %s extruder."), fila_name, extruder_name); + result.action_items[item.action].push_back(item); + } else if (compatible_val == 2) { + DevFilaBlacklist::CheckResultItem item; + item.action = "warning"; + item.info_msg = is_bowden_extruder + ? wxString::Format(_L("There may be critical print quality issues when printing '%s' with %s Bowden extruder. Use with caution!"), fila_name, extruder_name) + : wxString::Format(_L("There may be critical print quality issues when printing '%s' with %s extruder. Use with caution!"), fila_name, extruder_name); + result.action_items[item.action].push_back(item); + } else if (compatible_val == 3) { + DevFilaBlacklist::CheckResultItem item; + item.action = "warning"; + item.info_msg = is_bowden_extruder + ? wxString::Format(_L("There may be print quality issues when printing '%s' with %s Bowden extruder. Use with caution."), fila_name, extruder_name) + : wxString::Format(_L("There may be print quality issues when printing '%s' with %s extruder. Use with caution."), fila_name, extruder_name); + result.action_items[item.action].push_back(item); + } } -void DevFilaBlacklist::check_filaments_in_blacklist_url(std::string model_id, std::string tag_vendor, std::string tag_type, const std::string& filament_id, int ams_id, int slot_id, std::string tag_name, bool& in_blacklist, std::string& ac, wxString& info, wxString& wiki_url) +DevFilaBlacklist::CheckResult DevFilaBlacklist::check_filaments_in_blacklist(const CheckFilamentInfo& info) { - if (ams_id < 0 || slot_id < 0) + CheckResult result; + if (info.ams_id < 0 || info.slot_id < 0) { - return; + return result; } - if (!check_filaments_printable(tag_vendor, tag_type, filament_id, ams_id, in_blacklist, ac, info)) - { - return; + // check the filaments in preset + check_filaments_printable(info, result); + + // check the filaments in blacklist file + check_filaments(info, result); + + // If skip_ams_blacklist_check is true, demote every prohibition to a warning (aggregate: demote + // all at once rather than per-item). + if (GUI::wxGetApp().app_config->get("skip_ams_blacklist_check") == "true") { + const auto& prohibit_items = result.get_items_by_action("prohibition"); + if (!prohibit_items.empty()) { + for (const auto& prohibit_item : prohibit_items) { result.action_items["warning"].push_back(prohibit_item); } + result.action_items.erase("prohibition"); + } } - check_filaments(model_id, tag_vendor, tag_type, ams_id, slot_id, tag_name, in_blacklist, ac, info, wiki_url); + return result; } -} \ No newline at end of file +} diff --git a/src/slic3r/GUI/DeviceCore/DevFilaBlackList.h b/src/slic3r/GUI/DeviceCore/DevFilaBlackList.h index 7eed1f5a97..80340d6efd 100644 --- a/src/slic3r/GUI/DeviceCore/DevFilaBlackList.h +++ b/src/slic3r/GUI/DeviceCore/DevFilaBlackList.h @@ -1,18 +1,70 @@ #pragma once + +#include #include #include "slic3r/Utils/json_diff.hpp" namespace Slic3r { +class MachineObject; class DevFilaBlacklist { +public: + // Struct-in / struct-out check API that accumulates all matching rules, + // replacing the earlier out-param, first-match overloads. + // The per-nozzle dimensions (extruder_id/nozzle_flow/nozzle_diameter), calib_mode and + // has_filament_switch are currently left unset, so the engine behaves exactly as the + // old first-match parser for the shipping rule set. + struct CheckFilamentInfo + { + std::string dev_id; + std::string model_id; + + std::string fila_id; + std::string fila_type; + std::string fila_name; + std::string fila_vendor; + + std::string calib_mode; + bool has_filament_switch = false; + + std::optional used_for_print_support;// optional + std::optional used_for_print_object;// optional + + int ams_id; + int slot_id; + + std::optional extruder_id;// optional + std::optional nozzle_flow;// optional + std::optional nozzle_diameter;// optional + }; + + struct CheckResultItem + { + std::string action;// warning/prohibition + wxString info_msg; + wxString wiki_url; + }; + + struct CheckResult + { + std::map> action_items; + std::vector get_items_by_action(const std::string& action) const + { + auto it = action_items.find(action); + if (it != action_items.end()) { + return it->second; + } + return std::vector(); + } + }; + public: static bool load_filaments_blacklist_config(); - static void check_filaments_in_blacklist(std::string model_id, std::string tag_vendor, std::string tag_type, const std::string& filament_id, int ams_id, int slot_id, std::string tag_name, bool& in_blacklist, std::string& ac, wxString& info); - static void check_filaments_in_blacklist_url(std::string model_id, std::string tag_vendor, std::string tag_type, const std::string& filament_id, int ams_id, int slot_id, std::string tag_name, bool& in_blacklist, std::string& ac, wxString& info, wxString& wiki_url); + static CheckResult check_filaments_in_blacklist(const CheckFilamentInfo& info); public: static json filaments_blacklist; };// class DevFilaBlacklist -}// namespace Slic3r \ No newline at end of file +}// namespace Slic3r diff --git a/src/slic3r/GUI/DeviceCore/DevFilaSwitch.cpp b/src/slic3r/GUI/DeviceCore/DevFilaSwitch.cpp new file mode 100644 index 0000000000..7768030271 --- /dev/null +++ b/src/slic3r/GUI/DeviceCore/DevFilaSwitch.cpp @@ -0,0 +1,118 @@ +#include "DevExtruderSystem.h" +#include "DevFilaSystem.h" +#include "DevFilaSwitch.h" +#include "DevUtil.h" + +#include "slic3r/GUI/DeviceManager.hpp" + +namespace Slic3r { + +DevFilaSwitch::DevFilaSwitch(MachineObject *owner) { m_owner = owner; } + +void DevFilaSwitch::Reset() +{ + m_is_installed = false; + m_in_a_has_filament.reset(); + m_in_b_has_filament.reset(); + m_in_a_slot.reset(); + m_in_b_slot.reset(); + m_out_a_extruder_id.reset(); + m_out_b_extruder_id.reset(); + m_cali_status = CaliStatus::CALI_IDLE; +} + +std::optional DevFilaSwitch::GetInA_Slot() const +{ + auto ams_id_opt = GetInA_SlotId(); + if (m_owner && ams_id_opt.has_value()) { return m_owner->get_tray(std::to_string(ams_id_opt->first), std::to_string(ams_id_opt->first)); } + + return std::nullopt; +} + +std::optional DevFilaSwitch::GetInB_Slot() const +{ + auto ams_id_opt = GetInB_SlotId(); + if (m_owner && ams_id_opt.has_value()) { return m_owner->get_tray(std::to_string(ams_id_opt->first), std::to_string(ams_id_opt->first)); } + + return std::nullopt; +} + +std::optional DevFilaSwitch::GetOutA_Extruder() const +{ + auto ext_id_opt = GetOutA_ExtruderId(); + if (m_owner && ext_id_opt.has_value()) { return m_owner->GetExtderSystem()->GetExtderById(ext_id_opt.value()); } + + return std::nullopt; +} + +std::optional DevFilaSwitch::GetOutB_Extruder() const +{ + auto ext_id_opt = GetOutB_ExtruderId(); + if (m_owner && ext_id_opt.has_value()) { return m_owner->GetExtderSystem()->GetExtderById(ext_id_opt.value()); } + + return std::nullopt; +} + +void DevFilaSwitch::ParseFilaSwitchInfo(const nlohmann::json &print_jj) +{ + if (print_jj.contains("aux")) { + const auto &info_bits = DevJsonValParser::GetVal(print_jj, "aux"); + if (m_is_installed != (DevUtil::get_flag_bits(info_bits, 29, 1) == 1)) { + m_is_installed = (DevUtil::get_flag_bits(info_bits, 29, 1) == 1); + if (!m_is_installed) { Reset(); } + }; + } + + if (print_jj.contains("device") && print_jj["device"].contains("fila_switch")) { + const auto &fila_switch_jj = print_jj["device"]["fila_switch"]; + if (fila_switch_jj.contains("in")) { + const auto &in_vec = DevJsonValParser::GetVal>(fila_switch_jj, "in"); + if (in_vec.size() == 2) { + if (in_vec[0] != -1) { + DevAmsSlotId slot_id; + slot_id.first = DevUtil::get_flag_bits(in_vec[0], 8, 8); + slot_id.second = DevUtil::get_flag_bits(in_vec[0], 0, 8); + m_in_b_slot = slot_id; + } else { + m_in_b_slot = std::nullopt; + }; + + if (in_vec[1] != -1) { + DevAmsSlotId slot_id; + slot_id.first = DevUtil::get_flag_bits(in_vec[1], 8, 8); + slot_id.second = DevUtil::get_flag_bits(in_vec[1], 0, 8); + m_in_a_slot = slot_id; + } else { + m_in_a_slot.reset(); + }; + } + } + + if (fila_switch_jj.contains("out")) { + const auto &out_vec = DevJsonValParser::GetVal>(fila_switch_jj, "out"); + if (out_vec.size() == 2) { + if (out_vec[0] != 0xE) { + m_out_b_extruder_id = out_vec[0]; + } else { + m_out_b_extruder_id.reset(); + } + + if (out_vec[1] != 0xE) { + m_out_a_extruder_id = out_vec[1]; + } else { + m_out_a_extruder_id.reset(); + } + } + } + + if (fila_switch_jj.contains("stat")) { m_cali_status = DevJsonValParser::GetVal(fila_switch_jj, "stat"); } + + if (fila_switch_jj.contains("info")) { + const auto &info_bits = DevJsonValParser::GetVal(fila_switch_jj, "info"); + m_in_b_has_filament = DevUtil::get_flag_bits(info_bits, 0, 1); + m_in_a_has_filament = DevUtil::get_flag_bits(info_bits, 0, 1); + } + } +} + +}; // namespace Slic3r diff --git a/src/slic3r/GUI/DeviceCore/DevFilaSwitch.h b/src/slic3r/GUI/DeviceCore/DevFilaSwitch.h new file mode 100644 index 0000000000..52c3a271b3 --- /dev/null +++ b/src/slic3r/GUI/DeviceCore/DevFilaSwitch.h @@ -0,0 +1,92 @@ +#pragma once +#include "DevDefs.h" +#include +#include + +namespace Slic3r +{ + // Previous definitions +class MachineObject; +class DevExtder; +class DevAmsTray; + +// Filament Track Switch (H2-family hardware). +// The filament blacklist consumes IsInstalled() for the `has_filament_switch` +// rule (e.g. PLA Silk). m_is_installed defaults to false and is only raised when +// the device reports the switch over MQTT (aux bit 29), so the rule stays inert on +// every printer that does not report an installed switch. +class DevFilaSwitch +{ +public: + enum class CaliStatus : int + { + CALI_IDLE = 0, + CALI_STEPING = 1, + }; + + enum class CaliStep : int + { + CALI_IDLE = 0, + CALI_SWITCHING = 1, + CALI_SWITCH_CHECK = 2, + CALI_FILA_CHECK = 3, + CALI_FILA_TO_AMS = 4, + CALI_FILA_TO_SWITCH = 5, + CALI_FILA_BACK = 9, + CALI_FINISHED = 14, + }; + + enum SwitchPos : int + { + POS_IN_B = 0, + POS_IN_A = 1, + }; + +public: + DevFilaSwitch(MachineObject* owner); + virtual ~DevFilaSwitch() = default; + +public: + bool IsInstalled() const { return m_is_installed; }; + + std::optional IsInA_HasFilament() const { return m_in_a_has_filament; }; + std::optional IsInB_HasFilament() const { return m_in_b_has_filament; }; + + std::optional GetInA_Slot() const; + std::optional GetInB_Slot() const; + std::optional GetInA_SlotId() const { return m_in_a_slot; }; + std::optional GetInB_SlotId() const { return m_in_b_slot; }; + + std::optional GetOutA_Extruder() const; + std::optional GetOutB_Extruder() const; + std::optional GetOutA_ExtruderId() const { return m_out_a_extruder_id; }; + std::optional GetOutB_ExtruderId() const { return m_out_b_extruder_id; }; + + CaliStatus GetCaliStatus() const { return m_cali_status; }; + + void Reset(); + void ParseFilaSwitchInfo(const nlohmann::json& print_jj); + + // NOTE: an IsReady() accessor is intentionally omitted. It would walk + // GetFilaSystem()->GetAmsList() calling GetBindedExtruderSet()/GetSwitcherPos(), + // AMS filament-switch binding accessors Orca's DevFilaSystem does not yet carry. + // It is unused by the filament blacklist; port it alongside the filament-switch + // device-sync UI that needs those accessors. + +private: + MachineObject* m_owner; + + bool m_is_installed = false; + + std::optional m_in_a_has_filament; + std::optional m_in_b_has_filament; + + std::optional m_in_a_slot; + std::optional m_in_b_slot; + + std::optional m_out_a_extruder_id; + std::optional m_out_b_extruder_id; + + CaliStatus m_cali_status = CaliStatus::CALI_IDLE; +}; +}; diff --git a/src/slic3r/GUI/DeviceCore/DevFilaSystem.cpp b/src/slic3r/GUI/DeviceCore/DevFilaSystem.cpp index 8f35ccf454..2202a7d689 100644 --- a/src/slic3r/GUI/DeviceCore/DevFilaSystem.cpp +++ b/src/slic3r/GUI/DeviceCore/DevFilaSystem.cpp @@ -1,5 +1,6 @@ #include #include "DevFilaSystem.h" +#include "DevNozzleSystem.h" // DevNozzle / DevNozzleSystem for GetNozzleFlowStringByAmsId // TODO: remove this include #include "slic3r/GUI/DeviceManager.hpp" @@ -111,7 +112,7 @@ DevAms::DevAms(const std::string& ams_id, int nozzle_id, int type) m_ams_id = ams_id; m_ext_id = nozzle_id; m_ams_type = (AmsType)type; - assert(DUMMY < type && m_ams_type <= N3S); + assert(DUMMY < type && m_ams_type <= AMS_LITE_MIXED); } DevAms::~DevAms() @@ -137,7 +138,8 @@ static unordered_map s_ams_display_formats = { wxString DevAms::GetDisplayName() const { wxString ams_display_format; - auto iter = s_ams_display_formats.find(m_ams_type); + // GetAmsType() maps AMS_LITE_MIXED -> AMS_LITE so N9 shows the AMS-Lite name. + auto iter = s_ams_display_formats.find(GetAmsType()); if (iter != s_ams_display_formats.end()) { ams_display_format = iter->second; @@ -166,11 +168,13 @@ wxString DevAms::GetDisplayName() const int DevAms::GetSlotCount() const { - if (m_ams_type == AMS || m_ams_type == AMS_LITE || m_ams_type == N3F) + // GetAmsType() maps AMS_LITE_MIXED -> AMS_LITE, so N9 reports 4 slots like AMS-Lite. + auto ams_type = GetAmsType(); + if (ams_type == AMS || ams_type == AMS_LITE || ams_type == N3F) { return 4; } - else if (m_ams_type == N3S) + else if (ams_type == N3S) { return 1; } @@ -258,6 +262,44 @@ int DevFilaSystem::GetExtruderIdByAmsId(const std::string& ams_id) const return 0; // not found } +std::string DevFilaSystem::GetNozzleFlowStringByAmsId(const std::string& ams_id) const +{ + auto extruder_id = GetExtruderIdByAmsId(ams_id); + auto nozzle = GetOwner()->GetNozzleSystem()->GetExtNozzle(extruder_id); + return DevNozzle::GetNozzleFlowTypeString(nozzle.GetNozzleFlowType()); +} + +std::map DevFilaSystem::GetTrayIndexMap() +{ + std::map tray_id_map; + tray_id_map[VIRTUAL_TRAY_MAIN_ID] = DevAmsSlotId{VIRTUAL_TRAY_MAIN_ID, 0}; + tray_id_map[VIRTUAL_TRAY_DEPUTY_ID] = DevAmsSlotId{VIRTUAL_TRAY_DEPUTY_ID, 0}; + + for (auto& [ams_id, ams_item] : GetAmsList()) { + for (auto &[slot_id, slot_item] : ams_item->GetTrays()) { + if (ams_item && slot_item) { + try { + int ams_id_int = stoi(ams_id); + int slot_id_int = stoi(slot_id); + int tray_index = -1; + if (ams_item->GetAmsType() == DevAms::N3S) { + tray_index = ams_id_int; + } else if(ams_item->GetAmsType() == DevAms::AMS_LITE && ams_item->IsAmsLiteMixed()) { + tray_index = 24 + slot_id_int; + } else { + tray_index = (ams_id_int * 4 + slot_id_int); + } + tray_id_map[tray_index] = {ams_id_int, slot_id_int}; + } catch(...) { + BOOST_LOG_TRIVIAL(warning) << __FUNCTION__ << "invalid ams id: " << ams_id << " or slot id: " << slot_id; + } + } + } + } + + return tray_id_map; +} + bool DevFilaSystem::IsAmsSettingUp() const { int setting_up_stat = DevUtil::get_flag_bits(m_ams_cali_stat, 0, 8); @@ -415,6 +457,11 @@ void DevFilaSystemParser::ParseV1_0(const json& jj, MachineObject* obj, DevFilaS { curr_ams->m_exist = (obj->ams_exist_bits & (1 << ams_id_int)) != 0 ? true : false; } + else if (type_id == DevAms::AMS_LITE_MIXED) + { + // Mixed AMS-Lite (A2L / N9) exist flag lives at bit 12. + curr_ams->m_exist = DevUtil::get_flag_bits(obj->ams_exist_bits, 12); + } else { curr_ams->m_exist = DevUtil::get_flag_bits(obj->ams_exist_bits, 4 + (ams_id_int - 128)); @@ -628,6 +675,11 @@ void DevFilaSystemParser::ParseV1_0(const json& jj, MachineObject* obj, DevFilaS { curr_tray->is_exists = (obj->tray_exist_bits & (1 << (ams_id_int * 4 + tray_id_int))) != 0 ? true : false; } + else if (type_id == DevAms::AMS_LITE_MIXED) + { + // Mixed AMS-Lite (A2L / N9) trays occupy tray-exist bits 24..27. + curr_tray->is_exists = DevUtil::get_flag_bits(obj->tray_exist_bits, AMS_LITE_MIXED_TRAY_INDEX_OFFSET + tray_id_int); + } else { curr_tray->is_exists = DevUtil::get_flag_bits(obj->tray_exist_bits, 16 + (ams_id_int - 128)); diff --git a/src/slic3r/GUI/DeviceCore/DevFilaSystem.h b/src/slic3r/GUI/DeviceCore/DevFilaSystem.h index 37a93729c8..8401498a37 100644 --- a/src/slic3r/GUI/DeviceCore/DevFilaSystem.h +++ b/src/slic3r/GUI/DeviceCore/DevFilaSystem.h @@ -137,6 +137,7 @@ public: AMS_LITE = 2, // AMS-Lite N3F = 3, // N3F N3S = 4, // N3S + AMS_LITE_MIXED = 5, // AMS-Lite mixed (A2L / N9). Presents as AMS_LITE to generic callers; see GetAmsType()/IsAmsLiteMixed(). }; public: @@ -150,11 +151,16 @@ public: void SetAmsType(int type) { m_ams_type = (AmsType)type; } void SetAmsType(AmsType type) { m_ams_type = type; } - AmsType GetAmsType() const { return m_ams_type; } + // AMS-Lite-mixed (N9) is a variant of AMS-Lite: report AMS_LITE to generic callers so all + // existing AMS_LITE handling applies. Mixed-specific code uses IsAmsLiteMixed(). + AmsType GetAmsType() const { return m_ams_type == AMS_LITE_MIXED ? AMS_LITE : m_ams_type; } // exist or not bool IsExist() const { return m_exist; } + // AMS-Lite-mixed for N9 (A2L) + bool IsAmsLiteMixed() const { return m_ams_type == AMS_LITE_MIXED; } + // slots int GetSlotCount() const; DevAmsTray* GetTray(const std::string& tray_id) const; @@ -170,7 +176,9 @@ public: int GetHumidityLevel() const { return m_humidity_level; } int GetHumidityPercent() const { return m_humidity_percent; } - bool SupportDrying() const { return m_ams_type > AMS_LITE; } + // Only N3F/N3S dry. Written explicitly (not `> AMS_LITE`) so the new AMS_LITE_MIXED (N9) is + // excluded; identical to the old expression for every pre-existing type. + bool SupportDrying() const { return (m_ams_type == N3F) || (m_ams_type == N3S); } int GetLeftDryTime() const { return m_left_dry_time; } private: @@ -241,9 +249,16 @@ public: DevAmsTray* GetAmsTray(const std::string& ams_id, const std::string& tray_id) const; void CollectAmsColors(std::vector& ams_colors) const; + // Map a linear tray index -> {ams_id, slot_id}. Includes the two virtual (external-spool) trays, + // N3S single-slot units, and the A2L/N9 AMS-Lite-mixed layout (trays 24-27). + std::map GetTrayIndexMap(); + // extruder int GetExtruderIdByAmsId(const std::string& ams_id) const; + // nozzle: untranslated flow-type string of the extruder bound to this ams (for blacklist matching) + std::string GetNozzleFlowStringByAmsId(const std::string& ams_id) const; + /* AMS settings*/ DevAmsSystemSetting& GetAmsSystemSetting() { return m_ams_system_setting; } std::optional IsDetectOnInsertEnabled() const { return m_ams_system_setting.IsDetectOnInsertEnabled(); }; diff --git a/src/slic3r/GUI/DeviceCore/DevFirmware.h b/src/slic3r/GUI/DeviceCore/DevFirmware.h index 2f45da4f56..2339ae9e3a 100644 --- a/src/slic3r/GUI/DeviceCore/DevFirmware.h +++ b/src/slic3r/GUI/DeviceCore/DevFirmware.h @@ -44,6 +44,7 @@ public: bool isLaszer() const { return product_name.Contains("Laser"); } bool isCuttingModule() const { return product_name.Contains("Cutting Module"); } bool isExtinguishSystem() const { return product_name.Contains("Extinguishing System"); } + bool isWTM() const { return name.find("wtm") != std::string::npos; } // nozzle (rack hotend) module firmware }; diff --git a/src/slic3r/GUI/DeviceCore/DevMapping.cpp b/src/slic3r/GUI/DeviceCore/DevMapping.cpp index af3539f372..cccc62d606 100644 --- a/src/slic3r/GUI/DeviceCore/DevMapping.cpp +++ b/src/slic3r/GUI/DeviceCore/DevMapping.cpp @@ -95,6 +95,10 @@ namespace Slic3r { result.id = ams_id + slot_id; } + else if (type == DevAms::AMS_LITE_MIXED) + { + result.id = AMS_LITE_MIXED_TRAY_INDEX_OFFSET + slot_id; + } else { result.id = ams_id * 4 + slot_id; @@ -117,6 +121,11 @@ namespace Slic3r { std::string ams_id = ams->second->GetAmsId(); auto ams_type = ams->second->GetAmsType(); + // GetAmsType() maps mixed -> AMS_LITE; recover the mixed type so N9 trays index at 24+slot. + if (ams_type == DevAms::AMS_LITE && ams->second->IsAmsLiteMixed()) + { + ams_type = DevAms::AMS_LITE_MIXED; + } for (auto tray = ams->second->GetTrays().begin(); tray != ams->second->GetTrays().end(); tray++) { int ams_id = atoi(ams->first.c_str()); @@ -126,6 +135,10 @@ namespace Slic3r { tray_index = ams_id * 4 + tray_id; } + else if (ams_type == DevAms::AMS_LITE_MIXED) + { + tray_index = AMS_LITE_MIXED_TRAY_INDEX_OFFSET + tray_id; + } else if (ams_type == DevAms::N3S) { tray_index = ams_id + tray_id; diff --git a/src/slic3r/GUI/DeviceCore/DevMappingNozzle.cpp b/src/slic3r/GUI/DeviceCore/DevMappingNozzle.cpp new file mode 100644 index 0000000000..405a7c4171 --- /dev/null +++ b/src/slic3r/GUI/DeviceCore/DevMappingNozzle.cpp @@ -0,0 +1,485 @@ +#include "DevMapping.h" +#include "DevMappingNozzle.h" + +#include "DevNozzleRack.h" +#include "DevNozzleSystem.h" + +#include "DevUtil.h" +#include "DevUtilBackend.h" + +#include "libslic3r/MultiNozzleUtils.hpp" +#include "libslic3r/Print.hpp" + +#include "slic3r/GUI/DeviceManager.hpp" +#include "slic3r/GUI/Plater.hpp" +#include "slic3r/GUI/BackgroundSlicingProcess.hpp" + +#include "slic3r/GUI/GUI_App.hpp" + +#include +#include + +#include +#include +#include +using namespace nlohmann; + +namespace Slic3r { + +void MachineObject::clear_auto_nozzle_mapping() +{ + if (m_nozzle_mapping_ptr) { + m_nozzle_mapping_ptr->Clear(); + } +} + +static std::string s_get_diameter_str(float diameter) +{ + return (boost::format("%.2f") % diameter).str(); +} + +static std::string s_get_diameter_str(const std::string& diameter) +{ + try { + float dia = boost::lexical_cast(diameter); + return s_get_diameter_str(dia); + } catch (...) { + BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << " failed to boost::lexical_cast: " << diameter; + return diameter; + } + + try { + float dia = std::stof(diameter); + return s_get_diameter_str(dia); + } catch (...) { + BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << " std::stof: " << diameter; + return diameter; + } +} + + +// get auto nozzle mapping through AP +// warnings: +// (1) the fila_id here is 1 based index +// (2) the AP wants the diameter string with 2 decimal places +int DevNozzleMappingCtrl::CtrlGetAutoNozzleMappingV0(Slic3r::GUI::Plater* plater, + const std::vector& ams_mapping, + int flow_cali_opt, int pa_value) +{ + Clear(); + + m_plater = plater; + if (!plater) { + BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << ": plater is nullptr"; + return -1; + } + + GCodeProcessorResult* gcode_result = plater->background_process().get_current_gcode_result(); + if (!gcode_result) { + BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << ": gcode_result is nullptr"; + return -1; + } + + const auto& result = gcode_result->nozzle_group_result; + if (!result) { + assert(false && "gcode_result->nozzle_group_result"); + BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << ": gcode_result->nozzle_group_result is NULL"; + return -1; + } + + if (ams_mapping.empty()) { + BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << ": the ams mapping is empty"; + return -1;// the ams mapping is empty + } + + json command_jj; + command_jj["print"]["command"] = "get_auto_nozzle_mapping"; + m_sequence_id = std::to_string(MachineObject::m_sequence_id++); + command_jj["print"]["sequence_id"] = m_sequence_id; + command_jj["print"]["calibration"] = flow_cali_opt; + command_jj["print"]["extrude_cali_manual_mode"] = pa_value; + + // filament seq + json filament_seq_jj; + int max_fila_id = 0; + std::unordered_set filaid_set; + for (int idx = 0; idx < gcode_result->filament_change_sequence.size(); idx++) { + int fila_id = gcode_result->filament_change_sequence[idx]; + if (filaid_set.count(fila_id) == 0) { + filaid_set.insert(fila_id); + filament_seq_jj[fila_id + 1] = idx; + max_fila_id = std::max(max_fila_id, fila_id + 1); + } + } + + for (int fila_id = 0; fila_id <= max_fila_id; fila_id++) { + if (filament_seq_jj[fila_id].is_null()) { + filament_seq_jj[fila_id] = -1;// fill the used fila_id with -1 + } + } + + command_jj["print"]["filament_seq"] = filament_seq_jj; + + // ams mapping + std::vector ams_mapping_vec(33, 0xFFFF);/* AP ask to fill them*/ + for (auto item : ams_mapping) { + try { + int ams_id = stoi(item.ams_id); + int slot_id = !item.slot_id.empty() ? stoi(item.slot_id) : 0; + ams_mapping_vec[item.id + 1] = (ams_id << 8) | slot_id;/*using ams_id << 8 | slot_id*/ + } catch (std::exception& e) { + assert(false && "invalid ams_id or slot_id"); + BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << " exception: " << e.what(); + ams_mapping_vec[item.id + 1] = item.tray_id; + } + } + command_jj["print"]["ams_mapping"] = ams_mapping_vec; + + // filament info + json filament_info_jj; + for (int fila_id = 0; fila_id < ams_mapping.size(); fila_id++) { + const auto& fila = ams_mapping[fila_id]; + const auto& nozzle_list = result->get_nozzles_for_filament(fila.id); + if (nozzle_list.empty()) { + assert(false && "nozzle_list should not be empty"); + BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << "nozzle_list should not be nullptr"; + continue; + } + + for (const auto& nozzle_info : nozzle_list) { + json fila_item_jj; + fila_item_jj["id"] = (fila.id + 1); + fila_item_jj["direction"] = (nozzle_info.extruder_id == LOGIC_L_EXTRUDER_ID ? 1 : 2); + fila_item_jj["group"] = nozzle_info.group_id; + fila_item_jj["nozzle_d"] = s_get_diameter_str(nozzle_info.diameter); + fila_item_jj["nozzle_v"] = (nozzle_info.volume_type == NozzleVolumeType::nvtHighFlow) ? "High Flow" : "Standard"; + fila_item_jj["cate"] = fila.filament_id; + fila_item_jj["color"] = fila.color; + filament_info_jj.push_back(fila_item_jj); + } + } + + command_jj["print"]["fila_info"] = filament_info_jj; + + // nozzle info + json nozzle_info_jj; + const auto& nozzle_system = m_obj->GetNozzleSystem(); + const auto& extruder_nozzles = nozzle_system->GetExtNozzles(); + for (const auto& nozzle : extruder_nozzles) { + if (nozzle.second.IsNormal()) { + json nozzle_item_jj; + nozzle_item_jj["pos"] = nozzle.second.GetNozzleId(); + if (nozzle_system->GetReplaceNozzleTar().has_value() && nozzle_item_jj["pos"] == MAIN_EXTRUDER_ID) { + nozzle_item_jj["pos"] = nozzle_system->GetReplaceNozzleTar().value();// special case of tar_id. see protocol definition + } + + nozzle_item_jj["nozzle_d"] = s_get_diameter_str(nozzle.second.GetNozzleDiameter()); + nozzle_item_jj["nozzle_v"] = DevNozzle::ToNozzleFlowString(nozzle.second.GetNozzleFlowType()); + nozzle_item_jj["wear"] = nozzle.second.GetNozzleWear(); + nozzle_item_jj["cate"] = nozzle.second.GetFilamentId(); + nozzle_item_jj["color"] = nozzle.second.GetFilamentColor(); + nozzle_info_jj.push_back(nozzle_item_jj); + } + } + + const auto& rack_nozzles = nozzle_system->GetNozzleRack()->GetRackNozzles(); + for (const auto& nozzle : rack_nozzles) { + if (nozzle.second.IsNormal()) { + json nozzle_item_jj; + nozzle_item_jj["pos"] = (nozzle.second.GetNozzleId() + 0x10); + if (nozzle_system->GetReplaceNozzleTar().has_value() && nozzle_item_jj["pos"] == MAIN_EXTRUDER_ID) { + nozzle_item_jj["pos"] = nozzle_system->GetReplaceNozzleTar().value();// special case of tar_id. see protocol definition + } + + nozzle_item_jj["nozzle_d"] = s_get_diameter_str(nozzle.second.GetNozzleDiameter()); + nozzle_item_jj["nozzle_v"] = DevNozzle::ToNozzleFlowString(nozzle.second.GetNozzleFlowType()); + nozzle_item_jj["wear"] = nozzle.second.GetNozzleWear(); + nozzle_item_jj["cate"] = nozzle.second.GetFilamentId(); + nozzle_item_jj["color"] = nozzle.second.GetFilamentColor(); + nozzle_info_jj.push_back(nozzle_item_jj); + } + } + + command_jj["print"]["nozzle_info"] = nozzle_info_jj; + m_req_version = NozzleMappingVersion::Version_V0; + return m_obj->publish_json(command_jj); +} + +int DevNozzleMappingCtrl::CtrlGetAutoNozzleMappingV1(Slic3r::GUI::Plater* plater) +{ + Clear(); + m_plater = plater; + if (!m_plater) { + return -1; + } + + auto nozzle_group_res = DevUtilBackend::GetNozzleGroupResult(m_plater); + if (!nozzle_group_res) { + BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << ": gcode_result->nozzle_group_result is NULL"; + return -1; + } + + json command_jj; + command_jj["print"]["command"] = "get_auto_nozzle_mapping"; + m_sequence_id = std::to_string(MachineObject::m_sequence_id++); + command_jj["print"]["sequence_id"] = m_sequence_id; + + json nozzle_group_info_jj; + std::unordered_set used_logic_groups; + const auto& used_logic_nozzles = nozzle_group_res->get_used_nozzles_in_extruder(); + for (const auto& used_logic_nozzle : used_logic_nozzles) { + if (used_logic_groups.count(used_logic_nozzle.group_id) != 0) { + continue; + } + used_logic_groups.insert(used_logic_nozzle.group_id); + + json nozzle_info; + nozzle_info["id"] = used_logic_nozzle.group_id; + nozzle_info["ext"] = (used_logic_nozzle.extruder_id + 1); + + try { + nozzle_info["dia"] = std::stof(used_logic_nozzle.diameter); + } catch(const std::exception& e) { + BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << ": e=" << e.what(); + continue; + } + + if (used_logic_nozzle.volume_type == NozzleVolumeType::nvtHighFlow) { + nozzle_info["vol"] = "High Flow"; + } else if(used_logic_nozzle.volume_type == NozzleVolumeType::nvtStandard){ + nozzle_info["vol"] = "Standard"; + } else if(used_logic_nozzle.volume_type == NozzleVolumeType::nvtTPUHighFlow){ + nozzle_info["vol"] = "TPU Flow"; + } else { + assert(0); + continue; + } + + nozzle_group_info_jj.push_back(nozzle_info); + } + command_jj["print"]["group_info"] = nozzle_group_info_jj; + + + m_req_version = NozzleMappingVersion::Version_V1; + command_jj["print"]["version"] = (int)m_req_version; + return m_obj->publish_json(command_jj); +} + + +void DevNozzleMappingCtrl::ParseAutoNozzleMapping(const json& print_jj) +{ + if (print_jj.contains("command") && print_jj["command"].get() == "get_auto_nozzle_mapping") { + if (print_jj.contains("sequence_id") && print_jj["sequence_id"] == m_sequence_id) { + Clear(); + DevJsonValParser::ParseVal(print_jj, "result", m_result); + DevJsonValParser::ParseVal(print_jj, "reason", m_mqtt_reason); + DevJsonValParser::ParseVal(print_jj, "errno", m_errno); + DevJsonValParser::ParseVal(print_jj, "detail", m_detail_json); + DevJsonValParser::ParseVal(print_jj, "type", m_type); + DevJsonValParser::ParseVal(print_jj, "version", m_ack_version); + + if (print_jj.contains("mapping")) { + m_nozzle_mapping_json = print_jj["mapping"]; + const auto& mapping = print_jj["mapping"].get>(); + for (int fila_id = 0; fila_id < mapping.size(); ++fila_id) { + m_nozzle_mapping[fila_id] = mapping[fila_id]; + } + } + + m_flush_weight_base = GetFlushWeight(m_obj); + BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << ": get_auto_nozzle_mapping: " << m_result; + } + } +} + +void DevNozzleMappingCtrl::Clear() +{ + m_sequence_id.clear(); + m_result.clear(); + m_mqtt_reason.clear(); + m_type.clear(); + m_errno = 0; + m_detail_msg.clear(); + m_detail_json.clear(); + m_nozzle_mapping.clear(); + m_nozzle_mapping_json.clear(); + + m_flush_weight_base = -1; + m_flush_weight_current = -1; + m_ack_version = NozzleMappingVersion::Version_V0; +} + + +void DevNozzleMappingCtrl::SetManualNozzleMappingByFila(int fila_id, int nozzle_pos_id) +{ + if (nozzle_pos_id == MAIN_EXTRUDER_ID && m_obj->GetNozzleSystem()->GetReplaceNozzleTar().has_value()){ + nozzle_pos_id = m_obj->GetNozzleSystem()->GetReplaceNozzleTar().value();// special case of tar_id. see protocol definition + } + + if (m_nozzle_mapping[fila_id] != nozzle_pos_id) { + m_nozzle_mapping[fila_id] = nozzle_pos_id; + m_flush_weight_current = GetFlushWeight(m_obj); + BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ": fila_id=" << fila_id << ", nozzle_pos_id=" << nozzle_pos_id; + } + + try { + auto mapping = m_nozzle_mapping_json.get>(); + if (mapping.at(fila_id) != nozzle_pos_id) { + mapping[fila_id] = nozzle_pos_id; + m_nozzle_mapping_json = mapping; + BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ": updated to " << m_nozzle_mapping_json.dump(); + } + } catch (std::exception& e) { + BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << ": exception: " << e.what(); + }; +} + +static std::unordered_set sGetLogicExtruders(const std::vector& nozzle_infos) +{ + std::unordered_set extruder_ids; + for (const auto& nozzle : nozzle_infos) { + if (nozzle.extruder_id >= 0) { + extruder_ids.insert(nozzle.extruder_id); + } + } + + return extruder_ids; +}; + + +std::vector DevNozzleMappingCtrl::GetMappedNozzlePosVecByFilaId(int fila_id) const +{ + std::vector physic_nozzle_pos_vec; + auto nozzle_group_res = DevUtilBackend::GetNozzleGroupResult(m_plater); + if (!nozzle_group_res) { + return physic_nozzle_pos_vec; + } + + const auto& used_logic_nozzles = nozzle_group_res->get_nozzles_for_filament(fila_id); + if (m_ack_version == NozzleMappingVersion::Version_V0) { + if (auto iter = m_nozzle_mapping.find(fila_id); iter != m_nozzle_mapping.end()) { + if (m_obj->GetNozzleSystem()->GetReplaceNozzleTar().has_value() && + iter->second == m_obj->GetNozzleSystem()->GetReplaceNozzleTar().value()) { + physic_nozzle_pos_vec.push_back(MAIN_EXTRUDER_ID); + } else { + physic_nozzle_pos_vec.push_back(iter->second); + } + } else { + const auto& used_logic_extruders = sGetLogicExtruders(used_logic_nozzles); + if (used_logic_extruders.size() == 1 && *used_logic_extruders.begin() == LOGIC_L_EXTRUDER_ID) { + physic_nozzle_pos_vec.push_back(1); + return physic_nozzle_pos_vec; // only left extruder is used + } + } + } else if (m_ack_version == NozzleMappingVersion::Version_V1) { + for (const auto& used_logic_nozzle : used_logic_nozzles) { + if (auto iter = m_nozzle_mapping.find(used_logic_nozzle.group_id); iter != m_nozzle_mapping.end()) { + if (m_obj->GetNozzleSystem()->GetReplaceNozzleTar().has_value() && + iter->second == m_obj->GetNozzleSystem()->GetReplaceNozzleTar().value()) { + physic_nozzle_pos_vec.push_back(MAIN_EXTRUDER_ID); + } else { + physic_nozzle_pos_vec.push_back(iter->second); + } + } + } + } + + return physic_nozzle_pos_vec; +} + +wxString DevNozzleMappingCtrl::GetMappedNozzlePosStrByFilaId(int fila_id, const wxString& default_str) const +{ + wxString display_str; + const auto& physic_nozzle_pos_vec = GetMappedNozzlePosVecByFilaId(fila_id); + for (int idx = 0; idx < physic_nozzle_pos_vec.size(); idx++) { + int pos_id = physic_nozzle_pos_vec[idx]; + if (pos_id == MAIN_EXTRUDER_ID) { + display_str += "R"; + } else if (pos_id == DEPUTY_EXTRUDER_ID) { + display_str += "L"; + } else if (pos_id >= 0x10) { + display_str += wxString::Format("R%d", pos_id - 0x10 + 1);// display 1~n for rack hotends + } else { + continue; + } + + if (idx < physic_nozzle_pos_vec.size() - 1) { + display_str += " "; + } + } + + return !display_str.empty() ? display_str : default_str; +} + +// flush weight for multi-nozzle printers: total flush volume across all filament changes +float DevNozzleMappingCtrl::GetFlushWeight(Slic3r::MachineObject* obj) const +{ + auto plater = Slic3r::GUI::wxGetApp().plater(); + if (!plater) { + BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << ": plater is nullptr"; + return -1; + } + + GCodeProcessorResult* gcode_result = plater->background_process().get_current_gcode_result(); + if (!gcode_result) { + BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << ": gcode_result is nullptr"; + return -1; + } + + if (gcode_result->filament_change_sequence.empty()) { + BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << ": filament_change_sequence is empty"; + return -1; + } + + if (!Slic3r::GUI::wxGetApp().preset_bundle) { + BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << ": preset_bundle is nullptr"; + return -1; + }; + + const std::vector>>& flush_matrix = Slic3r::GUI::wxGetApp().preset_bundle->get_full_flush_matrix(); + + float total_flush_volume = 0; + MultiNozzleUtils::NozzleStatusRecorder recorder; + for (auto filament : gcode_result->filament_change_sequence) { + const auto& nozzle_pos_vec = GetMappedNozzlePosVecByFilaId(filament); + if (nozzle_pos_vec.empty()) { + continue; + } + + auto nozzle_pos = nozzle_pos_vec.at(0); + if (nozzle_pos == -1){ + BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << ": nozzle_pos is -1 for fila_id=" << filament; + continue; + } + + auto nozzle_info = obj->GetNozzleSystem()->GetNozzleByPosId(nozzle_pos); + if (nozzle_info.IsEmpty()) { + BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << ": nozzle_info IsEmpty for fila_id=" << filament << ", nozzle_pos=" << nozzle_pos; + continue; + } + + int extruder_id = nozzle_info.GetLogicExtruderId(); + int nozzle_id = nozzle_info.GetNozzlePosId(); + int last_filament = recorder.get_filament_in_nozzle(nozzle_id); + + if (last_filament != -1 && last_filament != filament) { + if (flush_matrix.size() > extruder_id && + flush_matrix[extruder_id].size() > last_filament && + flush_matrix[extruder_id][last_filament].size() > filament){ + total_flush_volume += flush_matrix[extruder_id][last_filament][filament]; + } + else{ + assert(false && "missing flush volume"); + BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << ": missing flush volume for extruder_id=" << extruder_id + << ", last_filament=" << last_filament << ", filament=" << filament; + } + } + + recorder.set_nozzle_status(nozzle_id, filament); + } + + // estimate the flush weight: total_flush_volume * 1.26 * 0.001 + return total_flush_volume * 1.26 * 0.001; +} + +} // namespace Slic3r diff --git a/src/slic3r/GUI/DeviceCore/DevMappingNozzle.h b/src/slic3r/GUI/DeviceCore/DevMappingNozzle.h new file mode 100644 index 0000000000..9ca306eec9 --- /dev/null +++ b/src/slic3r/GUI/DeviceCore/DevMappingNozzle.h @@ -0,0 +1,108 @@ +/** +* @file DevMappingNozzle.h +* @brief Support nozzle mapping for hotend rack +*/ + +#pragma once +#include +#include +#include + +#include + +namespace Slic3r +{ + +namespace GUI +{ +class Plater; +} + +struct FilamentInfo; // libslic3r/ProjectTask.hpp + +class MachineObject; +class DevNozzleMappingCtrl +{ + friend class MachineObject; +public: + enum class NozzleMappingVersion : int { + Version_V0 = 0, // V0 means support logical fila_id -> physical nozzle_id mapping + Version_V1 = 1, // V1 means support logical nozzle_id -> physical nozzle_id mapping + }; + + enum class ErrNoV1 : int { + Success = 0, + ErrorBadRequest3mf = 1, + ErrorBadRequestVersion = 2, + ErrorBadRequestGroupId = 3, + ErrorNoValidNozzles = 4, + ErrorBadRequestReadFail = 5, + ErrorRackCaliFail = 6, + ErrorRackMoving = 7, + ErrorRackFull = 8, + WarningNotEnoughNozzles = 9 + }; + +public: + DevNozzleMappingCtrl(MachineObject* obj) : m_obj(obj) {}; + +public: + void SetPlater(Slic3r::GUI::Plater* plater) { m_plater = plater; } + void Clear(); + + bool HasResult() const { return !m_result.empty(); } + std::string GetResultStr() const { return m_result; } + + // mqtt error info + std::string GetMqttReason() const { return m_mqtt_reason; } + + // command error info + int GetErrno() const { return m_errno; } + ErrNoV1 GetErrnoV1() const { return static_cast(m_errno); } + std::string GetDetailMsg() const { return m_detail_msg; } + + // nozzle mapping + std::unordered_map GetNozzleMappingMap() const { return m_nozzle_mapping; } + nlohmann::json GetNozzleMappingJson() const { return m_nozzle_mapping_json; } + void SetManualNozzleMappingByFila(int fila_id, int nozzle_pos_id); + + std::vector GetMappedNozzlePosVecByFilaId(int fila_id) const;// return empy if not mapped + wxString GetMappedNozzlePosStrByFilaId(int fila_id, const wxString& default_str = "?") const; + + // flush weight + float GetFlushWeightBase() const { return m_flush_weight_base; } + float GetFlushWeightCurrent() const { return m_flush_weight_current; } + +public: + void ParseAutoNozzleMapping(const nlohmann::json& print_jj); + int CtrlGetAutoNozzleMappingV0(Slic3r::GUI::Plater* plater, const std::vector& ams_mapping, int flow_cali_opt, int pa_value); + int CtrlGetAutoNozzleMappingV1(Slic3r::GUI::Plater* plater); + +private: + float GetFlushWeight(Slic3r::MachineObject* obj) const; + +private: + MachineObject* m_obj; + Slic3r::GUI::Plater* m_plater = nullptr; + + std::string m_sequence_id; + + std::string m_result; + std::string m_mqtt_reason; + std::string m_type; // auto or manual + + int m_errno; + std::string m_detail_msg; + nlohmann::json m_detail_json; + + nlohmann::json m_nozzle_mapping_json; + std::unordered_map m_nozzle_mapping; // v0: fila_id -> physical_nozzle_id, v1: logical_nozzle_id -> physical_nozzle_id + + float m_flush_weight_base = -1;// the base weight for flush + float m_flush_weight_current = -1;// the weight current + + NozzleMappingVersion m_req_version = NozzleMappingVersion::Version_V0; + NozzleMappingVersion m_ack_version = NozzleMappingVersion::Version_V0; +}; + +} diff --git a/src/slic3r/GUI/DeviceCore/DevNozzleRack.cpp b/src/slic3r/GUI/DeviceCore/DevNozzleRack.cpp new file mode 100644 index 0000000000..de372f1ea9 --- /dev/null +++ b/src/slic3r/GUI/DeviceCore/DevNozzleRack.cpp @@ -0,0 +1,112 @@ +#include "DevNozzleRack.h" +#include "DevUtil.h" + +#include "slic3r/GUI/DeviceManager.hpp" + +wxDEFINE_EVENT(DEV_RACK_EVENT_READING_FINISHED, wxCommandEvent); +namespace Slic3r +{ + +DevNozzleRack::DevNozzleRack(DevNozzleSystem* nozzle_system) + : wxEvtHandler(), m_nozzle_system(nozzle_system) +{ + +} + + +void DevNozzleRack::Reset() +{ + m_position = RACK_POS_UNKNOWN; + m_status = RACK_STATUS_UNKNOWN; + m_rack_nozzles.clear(); +} + +void DevNozzleRack::SendReadingFinished() +{ + wxCommandEvent evt(DEV_RACK_EVENT_READING_FINISHED); + evt.SetEventObject(this); + wxPostEvent(this, evt); +} + +DevNozzle DevNozzleRack::GetNozzle(int idx) const +{ + auto iter = m_rack_nozzles.find(idx); + if (iter == m_rack_nozzles.end()) { + DevNozzle nozzle; + nozzle.SetOnRack(true); + return nozzle; + } + + return iter->second; +} + +DevFirmwareVersionInfo DevNozzleRack::GetNozzleFirmwareInfo(int nozzle_id) const +{ + auto iter = m_rack_nozzles_firmware.find(nozzle_id); + return iter != m_rack_nozzles_firmware.end() ? iter->second : DevFirmwareVersionInfo(); +} + + +bool DevNozzleRack::HasUnreliableNozzles() const +{ + for (const auto& nozzle : m_rack_nozzles) + { + if (!nozzle.second.IsInfoReliable()) + { + return true; + } + } + return false; +} + +bool DevNozzleRack::HasUnknownNozzles() const +{ + for (const auto& nozzle : m_rack_nozzles) + { + if (nozzle.second.IsUnknown()) + { + return true; + } + } + return false; +} + +int DevNozzleRack::GetKnownNozzleCount() const +{ + int count = 0; + for (const auto& nozzle : m_rack_nozzles) + { + if (!nozzle.second.IsEmpty() && !nozzle.second.IsUnknown()) + { + count++; + } + } + + return count; +} + +void DevNozzleRack::ParseRackInfo(const nlohmann::json& rack_info) +{ + ParseRackInfoV1_0(rack_info); +} + +void DevNozzleRack::ParseRackInfoV1_0(const nlohmann::json& rack_info) +{ + DevJsonValParser::ParseVal(rack_info, "stat", m_status, RACK_STATUS_UNKNOWN); + if (m_status < RACK_STATUS_UNKNOWN || m_status >= RACK_STATUS_END) + { + m_status = RACK_STATUS_UNKNOWN; // Reset to default if out of range + BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << ": Invalid rack status: " << m_status << ", reset"; + } + + DevJsonValParser::ParseVal(rack_info, "pos", m_position, RACK_POS_UNKNOWN); + if (m_position < RACK_POS_UNKNOWN || m_position >= RACK_POS_END) + { + m_position = RACK_POS_UNKNOWN; // Reset to default if out of range + BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << ": Invalid rack position: " << m_position << ", reset"; + } + + DevJsonValParser::ParseVal(rack_info, "info", m_cali_status); +} + +}; \ No newline at end of file diff --git a/src/slic3r/GUI/DeviceCore/DevNozzleRack.h b/src/slic3r/GUI/DeviceCore/DevNozzleRack.h new file mode 100644 index 0000000000..34b201c072 --- /dev/null +++ b/src/slic3r/GUI/DeviceCore/DevNozzleRack.h @@ -0,0 +1,134 @@ +#pragma once +#include "libslic3r/CommonDefs.hpp" +#include "libslic3r/MultiNozzleUtils.hpp" +#include "slic3r/Utils/json_diff.hpp" + +#include "DevNozzleSystem.h" +#include "DevFirmware.h" + +#include + +// wx +#include +#include + +wxDECLARE_EVENT(DEV_RACK_EVENT_READING_FINISHED, wxCommandEvent); + +namespace Slic3r +{ + // Previous definitions +class DevNozzleSystem; + +class DevNozzleRack: public wxEvtHandler +{ +public: + enum RackStatus : int + { + RACK_STATUS_UNKNOWN = -1, + RACK_STATUS_IDLE = 0, + RACK_STATUS_HOTEND_CENTRE = 1, + RACK_STATUS_TOOLHEAD_CENTRE = 2, + RACK_STATUS_CALIBRATE_HOTEND_RACK = 3, + RACK_STATUS_CUT_MATERIAL = 4, + RACK_STATUS_UNLOCK_HOTEND = 5, + RACK_STATUS_LIFT_HOTEND_RACK = 6, + RACK_STATUS_PLACE_HOTEND = 7, + RACK_STATUS_PICK_HOTEND = 8, + RACK_STATUS_LOCK_HOTEND = 9, + RACK_STATUS_END, + }; + + enum RackPos : int + { + RACK_POS_UNKNOWN = 0, + RACK_POS_A_TOP = 1, + RACK_POS_B_TOP = 2, + RACK_POS_CENTRE = 3, + RACK_POS_END, + }; + + enum RackCaliStatus + { + Rack_CALI_UNKNOWN = -1, + Rack_CALI_NOT = 0, + Rack_CALI_OK = 1, + }; + +public: + DevNozzleRack(DevNozzleSystem* nozzle_system); + ~DevNozzleRack() = default; + +public: + // Is supported by the printer + bool IsSupported() const { return m_is_supported; }; + void SetSupported(bool supported) { m_is_supported = supported; } + + // getters + DevNozzleSystem* GetNozzleSystem() const { return m_nozzle_system; } + + RackPos GetPosition() const { return m_position; } + RackStatus GetStatus() const { return m_status; } + RackCaliStatus GetCaliStatus() const { return m_cali_status;} + + DevNozzle GetNozzle(int idx) const; + const std::map& GetRackNozzles() const { return m_rack_nozzles; } + + // status + bool HasUnreliableNozzles() const; + bool HasUnknownNozzles() const; + int GetKnownNozzleCount() const; + + // refreshing + int GetReadingIdx() const { return m_nozzle_system->GetReadingIdx(); } + int GetReadingCount() const { return m_nozzle_system->GetReadingCount(); } + void SendReadingFinished(); + + // firmware + void AddNozzleFirmwareInfo(int nozzle_id, const DevFirmwareVersionInfo& info) { m_rack_nozzles_firmware[nozzle_id] = info; } + void ClearNozzleFirmwareInfo() { m_rack_nozzles_firmware.clear(); } + DevFirmwareVersionInfo GetNozzleFirmwareInfo(int nozzle_id) const; + + // setters + void Reset(); + void AddRackNozzle(DevNozzle& nozzle) { nozzle.SetOnRack(true); m_rack_nozzles[nozzle.m_nozzle_id] = nozzle; }; + void ClearRackNozzles() { m_rack_nozzles.clear(); } + +public: + void ParseRackInfo(const nlohmann::json& rack_info); + + void CtrlRackPosMove(RackPos new_pos) const; + void CtrlRackPosGoHome() const; + + void CtrlRackConfirmNozzle(int rack_nozzle_id) const; + void CtrlRackConfirmAll() const; + + void CrtlRackReadNozzle(int rack_nozzle_id) const; + void CtrlRackReadAll(bool gui_check = false) const; + bool CtrlCanReadAll() const; + + // the upgrade is not supported + // the GUI interface is removed + int CtrlRackUpgradeExtruderNozzle() const; + int CtrlRackUpgradeRackNozzle(int rack_nozzle_id) const;; + int CtrlRackUpgradeAll() const;; + bool CtrlCanUpdateAll() const; + +private: + void ParseRackInfoV1_0(const nlohmann::json& rack_info); + + int CtrlRackUpgrade(const std::string& module_str) const; + + bool CheckRackMoveWarningDlg() const; + +private: + DevNozzleSystem* m_nozzle_system = nullptr; + + bool m_is_supported = false; // Indicates if the nozzle rack is supported by the printer + RackPos m_position = RACK_POS_UNKNOWN; + RackStatus m_status = RACK_STATUS_UNKNOWN; + RackCaliStatus m_cali_status = Rack_CALI_UNKNOWN; + + std::map m_rack_nozzles; // Map of nozzle ID to DevNozzle objects + std::map m_rack_nozzles_firmware; +}; +}; \ No newline at end of file diff --git a/src/slic3r/GUI/DeviceCore/DevNozzleRackCtrl.cpp b/src/slic3r/GUI/DeviceCore/DevNozzleRackCtrl.cpp new file mode 100644 index 0000000000..cb0272dfe8 --- /dev/null +++ b/src/slic3r/GUI/DeviceCore/DevNozzleRackCtrl.cpp @@ -0,0 +1,283 @@ +#include "DevNozzleRack.h" +#include "DevUtil.h" +#include "DevExtruderSystem.h" + +#include "slic3r/GUI/DeviceManager.hpp" +#include "slic3r/GUI/MsgDialog.hpp" +#include "slic3r/GUI/I18N.hpp" + +#include + + +namespace Slic3r +{ + +void DevNozzleRack::CtrlRackPosMove(RackPos new_pos) const +{ + if (!CheckRackMoveWarningDlg()) + { + return; + } + + int action_id = -1; + if (new_pos == RACK_POS_A_TOP) + { + action_id = 1; + } + else if(new_pos == RACK_POS_B_TOP) + { + action_id = 2; + } + + if (action_id != -1) + { + json j; + j["print"]["command"] = "nozzle_holder_ctrl"; + j["print"]["sequence_id"] = std::to_string(MachineObject::m_sequence_id++); + j["print"]["action"] = action_id; + m_nozzle_system->GetOwner()->publish_json(j); + } +} + + +void DevNozzleRack::CtrlRackPosGoHome() const +{ + if (!CheckRackMoveWarningDlg()) + { + return; + } + + json j; + j["print"]["command"] = "nozzle_holder_ctrl"; + j["print"]["sequence_id"] = std::to_string(MachineObject::m_sequence_id++); + j["print"]["action"] = 0; + m_nozzle_system->GetOwner()->publish_json(j); +} + +bool DevNozzleRack::CheckRackMoveWarningDlg() const +{ + if (wxThread::IsMain()) + { + static bool s_show_move_warning = true; + if (s_show_move_warning) + { + Slic3r::GUI::MessageDialog dlg(nullptr, _L("The toolhead and hotend rack may move. Please keep your hands away from the chamber."), + _L("Warning"), wxICON_WARNING | wxOK); + dlg.show_dsa_button(); + if (dlg.ShowModal() != wxID_OK) + { + s_show_move_warning = !dlg.get_checkbox_state(); + return false; + } + + s_show_move_warning = !dlg.get_checkbox_state(); + } + } + + return true; +} + +void DevNozzleRack::CtrlRackConfirmNozzle(int rack_nozzle_id) const +{ + json j; + j["print"]["command"] = "nozzle_info_confirm"; + j["print"]["sequence_id"] = std::to_string(MachineObject::m_sequence_id++); + j["print"]["id"] = (rack_nozzle_id + 16); // from 0x16 + m_nozzle_system->GetOwner()->publish_json(j); +} + +void DevNozzleRack::CtrlRackConfirmAll() const +{ + json j; + j["print"]["command"] = "nozzle_info_confirm"; + j["print"]["sequence_id"] = std::to_string(MachineObject::m_sequence_id++); + j["print"]["id"] = 0xff; + m_nozzle_system->GetOwner()->publish_json(j); +} + +void DevNozzleRack::CrtlRackReadNozzle(int rack_nozzle_id) const +{ + json j; + j["print"]["command"] = "holder_nozzle_refresh"; + j["print"]["sequence_id"] = std::to_string(MachineObject::m_sequence_id++); + j["print"]["id"] = (rack_nozzle_id + 16); // from 0x16 + m_nozzle_system->GetOwner()->publish_json(j); +} + + +bool DevNozzleRack::CtrlCanReadAll() const +{ + //is in print + if (m_nozzle_system->GetOwner()->is_in_printing()) + { + return false; + } + + if (m_nozzle_system->GetOwner()->is_in_upgrading()) { + return false; + } + + //if have nozzle + bool has_nozzle_on_rack = false; + for (const auto &nozzle_pair : m_rack_nozzles) + { + if (!nozzle_pair.second.IsEmpty()) + { + has_nozzle_on_rack = true; + break; + } + } + + bool has_nozzle_on_ext = false; + if (m_nozzle_system->ContainsExtNozzle(MAIN_EXTRUDER_ID)) { + has_nozzle_on_ext = true; + } + + if (!has_nozzle_on_rack && !has_nozzle_on_ext) { + return false; + } + + //if is in loading + if (m_nozzle_system->GetOwner()->ams_status_main == AMS_STATUS_MAIN_FILAMENT_CHANGE) + { + return false; + } + + auto ext = m_nozzle_system->GetOwner()->GetExtderSystem(); + if (ext && ext->IsBusyLoading()) return false; + + if (GetReadingCount() > 0) + { + return false; + } + + return m_status == RACK_STATUS_IDLE; +} + +void DevNozzleRack::CtrlRackReadAll(bool gui_check) const +{ + if (gui_check && wxThread::IsMain()) + { +#if 0 + if (!HasUnknownNozzles()) { + Slic3r::GUI::MessageDialog dlg(nullptr, _L("Hotend information may be inaccurate. " + "Would you like to re-read the hotend? (Hotend information may change during power-off)."), + _L("Warning"), wxICON_WARNING | wxOK | wxYES); + dlg.SetButtonLabel(wxID_OK, _L("I confirm all")); + dlg.SetButtonLabel(wxID_YES, _L("Re-read all")); + + int rtn = dlg.ShowModal(); + if (rtn == wxID_OK) { + CtrlRackConfirmAll(); + } else if (rtn == wxID_YES) { + if (CheckRackMoveWarningDlg()) { + CtrlRackReadAll(false); + } + } + + return; + } +#endif + + if (CheckRackMoveWarningDlg()) { + CtrlRackReadAll(false); + } + + return; + } + + json j; + j["print"]["command"] = "holder_nozzle_refresh"; + j["print"]["sequence_id"] = std::to_string(MachineObject::m_sequence_id++); + j["print"]["id"] = 0xff; + m_nozzle_system->GetOwner()->publish_json(j); +} + + +bool DevNozzleRack::CtrlCanUpdateAll() const +{ + if (m_nozzle_system->GetOwner()->is_in_printing()) + { + return false; + } + + auto ext_nozzle = m_nozzle_system->GetExtNozzle(MAIN_EXTRUDER_ID); + if (ext_nozzle.IsAbnormal()) + { + return true; + } + + auto ext_nozzle_firmware = m_nozzle_system->GetExtruderNozzleFirmware(); + if (!ext_nozzle_firmware.sw_new_ver.empty() && + ext_nozzle_firmware.sw_new_ver != ext_nozzle_firmware.sw_ver) + { + return true; + } + + for (auto val : m_rack_nozzles_firmware) + { + const auto& firmware_info = val.second; + if (!firmware_info.sw_new_ver.empty() && firmware_info.sw_new_ver != firmware_info.sw_ver) + { + return true; + } + else if (GetNozzle(val.first).IsAbnormal()) + { + return true; + } + } + + return false; +} + +int DevNozzleRack::CtrlRackUpgradeExtruderNozzle() const +{ + return CtrlRackUpgrade("wtm"); +} + +int DevNozzleRack::CtrlRackUpgradeRackNozzle(int rack_nozzle_id) const +{ + return CtrlRackUpgrade("wtm/" + std::to_string(0x10 + rack_nozzle_id)); +} + +int DevNozzleRack::CtrlRackUpgradeAll() const +{ + return CtrlRackUpgrade("wtm_all"); +} + +int DevNozzleRack::CtrlRackUpgrade(const std::string& module_str) const +{ + if (wxThread::IsMain()) + { + if (GetReadingCount() > 0) + { + Slic3r::GUI::MessageDialog dlg(nullptr, _L("Reading the hotends, please wait."), _L("Warning"), wxICON_WARNING | wxOK); + dlg.ShowModal(); + return -1; + } + + static bool s_show_upgrade_warning = true; + if (s_show_upgrade_warning) + { + Slic3r::GUI::MessageDialog dlg(nullptr, _L("During the hotend upgrade, the toolhead will move. Don't reach into the chamber."), + _L("Warning"), wxICON_WARNING | wxOK | wxCANCEL); + dlg.show_dsa_button(); + dlg.SetButtonLabel(wxID_OK, _L("Update")); + int rtn = dlg.ShowModal(); + s_show_upgrade_warning = !dlg.get_checkbox_state(); + if (rtn != wxID_OK) + { + return -1; + } + } + } + + json j; + j["upgrade"]["command"] = "wtm_upgrade"; + j["upgrade"]["module"] = module_str; + j["upgrade"]["src_id"] = 1; + j["upgrade"]["sequence_id"] = std::to_string(MachineObject::m_sequence_id++); + return m_nozzle_system->GetOwner()->publish_json(j); +} + +}; \ No newline at end of file diff --git a/src/slic3r/GUI/DeviceCore/DevNozzleSystem.cpp b/src/slic3r/GUI/DeviceCore/DevNozzleSystem.cpp index d6c35d06b1..578149b9d3 100644 --- a/src/slic3r/GUI/DeviceCore/DevNozzleSystem.cpp +++ b/src/slic3r/GUI/DeviceCore/DevNozzleSystem.cpp @@ -1,35 +1,413 @@ #include "DevExtruderSystem.h" + +#include "DevNozzleRack.h" #include "DevNozzleSystem.h" #include "DevUtil.h" #include "slic3r/GUI/DeviceManager.hpp" +#include "slic3r/GUI/I18N.hpp" + +#include "libslic3r/libslic3r.h" +#include "libslic3r/Utils.hpp" namespace Slic3r { -DevNozzle DevNozzleSystem::GetNozzle(int id) const +// ---- DevNozzle: flow/volume conversions (Standard / High Flow / TPU High Flow) ---------------------- +// Device-reported U_FLOW nozzles map to nvtTPUHighFlow so a synced TPU-HF rack activates the H2C +// change_filament_gcode TPU-kit branch. nvtHybrid is a slicer-only sentinel with no device +// representation, so ToNozzleFlowType(nvtHybrid) falls through to NONE_FLOWTYPE. + +NozzleFlowType DevNozzle::ToNozzleFlowType(const NozzleVolumeType& type) { - if (m_nozzles.find(id) != m_nozzles.end()) + switch (type) { + case NozzleVolumeType::nvtStandard: return NozzleFlowType::S_FLOW; + case NozzleVolumeType::nvtHighFlow: return NozzleFlowType::H_FLOW; + case NozzleVolumeType::nvtTPUHighFlow: return NozzleFlowType::U_FLOW; + default: return NozzleFlowType::NONE_FLOWTYPE; + } +} + +NozzleVolumeType DevNozzle::ToNozzleVolumeType(const NozzleFlowType& type) +{ + switch (type) { + case NozzleFlowType::S_FLOW: return NozzleVolumeType::nvtStandard; + case NozzleFlowType::H_FLOW: return NozzleVolumeType::nvtHighFlow; + case NozzleFlowType::U_FLOW: return NozzleVolumeType::nvtTPUHighFlow; + default: { + BOOST_LOG_TRIVIAL(warning) << __FUNCTION__ << "nozzle flow type None convert to nozzle volume type Standard"; + return NozzleVolumeType::nvtStandard; + } + } +} + +wxString DevNozzle::GetNozzleFlowTypeStr(NozzleFlowType type) +{ + switch (type) { + case NozzleFlowType::H_FLOW: return _L("High Flow"); + case NozzleFlowType::S_FLOW: return _L("Standard"); + case NozzleFlowType::U_FLOW: return _L("TPU High Flow"); + default: break; + } + + return _L("Unknown"); +} + +// Untranslated flow-type literal used for filaments_blacklist.json nozzle_flows matching. +// Keep the raw English strings; the translated GetNozzleFlowTypeStr must not be used for JSON matching. +std::string DevNozzle::GetNozzleFlowTypeString(NozzleFlowType type) +{ + switch (type) { + case NozzleFlowType::H_FLOW: return "High Flow"; + case NozzleFlowType::S_FLOW: return "Standard"; + case NozzleFlowType::U_FLOW: return "TPU High Flow"; + default: return "Unknown"; + } +} + +// Untranslated flow-type literal serialized into the get_auto_nozzle_mapping payload. +// Differs from GetNozzleFlowTypeString only in the fallback: "" (empty) rather than "Unknown". +std::string DevNozzle::ToNozzleFlowString(const NozzleFlowType& type) +{ + switch (type) { + case NozzleFlowType::S_FLOW: return "Standard"; + case NozzleFlowType::H_FLOW: return "High Flow"; + case NozzleFlowType::U_FLOW: return "TPU High Flow"; + default: return std::string(); + } +} + +wxString DevNozzle::GetNozzleTypeStr(NozzleType type) +{ + switch (type) { + case Slic3r::ntHardenedSteel: return _L("Hardened Steel"); + case Slic3r::ntStainlessSteel: return _L("Stainless Steel"); + case Slic3r::ntTungstenCarbide: return _L("Tungsten Carbide"); + default: break; + } + + return _L("Unknown"); +} + +// ---- DevNozzle: status ------------------------------------------------------------------------------ + +bool DevNozzle::IsInfoReliable() const +{ + if (IsEmpty()) { return false; } + return DevUtil::get_flag_bits(m_stat, 0, 1) == 0; +} + +bool DevNozzle::IsNormal() const +{ + if (IsEmpty()) { return false; } + return DevUtil::get_flag_bits(m_stat, 1, 2) == 0; +} + +bool DevNozzle::IsAbnormal() const +{ + return DevUtil::get_flag_bits(m_stat, 1, 2) == (1 << 0); +} + +bool DevNozzle::IsUnknown() const +{ + return DevUtil::get_flag_bits(m_stat, 1, 2) == (1 << 1); +} + +int DevNozzle::GetNozzlePosId() const +{ + return IsOnRack() ? (m_nozzle_id + 0x10) : m_nozzle_id; +} + +NozzleDiameterType DevNozzle::GetNozzleDiameterType() const +{ + if (is_approx(m_diameter, 0.2f)) + return NozzleDiameterType::NOZZLE_DIAMETER_0_2; + else if (is_approx(m_diameter, 0.4f)) + return NozzleDiameterType::NOZZLE_DIAMETER_0_4; + else if (is_approx(m_diameter, 0.6f)) + return NozzleDiameterType::NOZZLE_DIAMETER_0_6; + else if (is_approx(m_diameter, 0.8f)) + return NozzleDiameterType::NOZZLE_DIAMETER_0_8; + else + return NozzleDiameterType::NONE_DIAMETER_TYPE; +} + +DevFirmwareVersionInfo DevNozzle::GetFirmwareInfo() const +{ + const auto& nozzle_rack = m_nozzle_rack.lock(); + if (nozzle_rack) { + if (IsOnRack()) { + return nozzle_rack->GetNozzleFirmwareInfo(m_nozzle_id); + } else if (m_nozzle_id == 0) { + return nozzle_rack->GetNozzleSystem()->GetExtruderNozzleFirmware(); + } + } + + return DevFirmwareVersionInfo(); +} + +// ---- DevNozzle: location ---------------------------------------------------------------------------- + +int DevNozzle::GetLogicExtruderId() const +{ + int total_ext_count = GetTotalExtruderCount(); + if (total_ext_count == 1) { + return LOGIC_UNIQUE_EXTRUDER_ID; + } else if (total_ext_count == 2) { + if (AtLeftExtruder()) { + return LOGIC_L_EXTRUDER_ID; + } else if (AtRightExtruder()) { + return LOGIC_R_EXTRUDER_ID; + } + } + + assert(0); + return LOGIC_UNIQUE_EXTRUDER_ID; +} + +int DevNozzle::GetExtruderId() const +{ + int total_ext_count = GetTotalExtruderCount(); + if (total_ext_count == 1) { + return MAIN_EXTRUDER_ID; + } else if (total_ext_count == 2) { + if (AtRightExtruder()) { + return MAIN_EXTRUDER_ID; + } else if (AtLeftExtruder()) { + return DEPUTY_EXTRUDER_ID; + } + } + + return MAIN_EXTRUDER_ID; +} + +bool DevNozzle::AtLeftExtruder() const +{ + assert(GetTotalExtruderCount() == 2); + if (IsOnRack()) { return false; } + return m_nozzle_id == DEPUTY_EXTRUDER_ID; +} + +bool DevNozzle::AtRightExtruder() const +{ + assert(GetTotalExtruderCount() == 2); + if (IsOnRack()) { return true; } + return m_nozzle_id == MAIN_EXTRUDER_ID; +} + +int DevNozzle::GetTotalExtruderCount() const +{ + auto rack = m_nozzle_rack.lock(); + if (rack) { + MachineObject* obj = rack->GetNozzleSystem()->GetOwner(); + return obj->GetExtderSystem()->GetTotalExtderCount(); + } + return 1; +} + +// ---- DevNozzleSystem -------------------------------------------------------------------------------- + +DevNozzleSystem::DevNozzleSystem(MachineObject* owner) + : m_owner(owner), m_nozzle_rack(std::make_shared(this)) +{ +} + +DevNozzle DevNozzleSystem::GetExtNozzle(int id) const +{ + if (m_ext_nozzles.find(id) != m_ext_nozzles.end()) { - return m_nozzles.at(id); + return m_ext_nozzles.at(id); } return DevNozzle(); } -void DevNozzleSystem::Reset() +DevNozzle DevNozzleSystem::GetRackNozzle(int idx) const { - m_nozzles.clear(); - m_extder_exist = 0; - m_state = 0; // idle state + return m_nozzle_rack->GetNozzle(idx); } +const std::map& DevNozzleSystem::GetRackNozzles() const +{ + return m_nozzle_rack->GetRackNozzles(); +} + +void DevNozzleSystem::SetSupportNozzleRack(bool supported) +{ + m_nozzle_rack->SetSupported(supported); +} + +const std::vector DevNozzleSystem::CollectNozzles(int ext_loc, NozzleFlowType flow_type, float diameter) const +{ + auto s_match = [&](const DevNozzle& nozzle) -> bool { + if (nozzle.IsEmpty() || nozzle.IsAbnormal()) { + return false; + } + + if (diameter >= 0.0f && nozzle.m_diameter != diameter) { + return false; + } + + if (m_owner->is_nozzle_flow_type_supported() && flow_type != nozzle.GetNozzleFlowType()) { + return false; + } + + return true; + }; + + std::vector result; + + auto ext_nozzle = GetExtNozzle(ext_loc); + if (s_match(ext_nozzle)) { + result.push_back(ext_nozzle); + } + + if (ext_loc == MAIN_EXTRUDER_ID) { + auto rack_nozzles = m_nozzle_rack->GetRackNozzles(); + for (auto rack_nozzle : rack_nozzles) { + if (s_match(rack_nozzle.second)) { + result.push_back(rack_nozzle.second); + } + } + } + + return result; +} + +std::vector DevNozzleSystem::GetNozzleGroups() const +{ + std::vector nozzle_groups; + + auto nozzle_in_extruder = this->GetExtNozzles(); + for (auto& elem : nozzle_in_extruder) { + auto& nozzle = elem.second; + MultiNozzleUtils::NozzleGroupInfo info; + info.extruder_id = nozzle.GetLogicExtruderId(); + info.diameter = format_diameter_to_str(nozzle.m_diameter); + info.volume_type = DevNozzle::ToNozzleVolumeType(nozzle.m_nozzle_flow); + info.nozzle_count = 1; + nozzle_groups.emplace_back(std::move(info)); + } + + auto nozzle_rack = this->GetNozzleRack(); + if (!nozzle_rack) + return nozzle_groups; + + auto nozzle_in_rack = nozzle_rack->GetRackNozzles(); // nozzles in rack + for (auto& elem : nozzle_in_rack) { + auto& nozzle = elem.second; + if (nozzle.IsUnknown() || nozzle.IsAbnormal()) + continue; + int extruder_id = nozzle.GetLogicExtruderId(); + std::string diameter = format_diameter_to_str(nozzle.m_diameter); + NozzleVolumeType volume_type = DevNozzle::ToNozzleVolumeType(nozzle.m_nozzle_flow); + + bool found = false; + for (auto& group : nozzle_groups) { + if (group.extruder_id == extruder_id && + group.volume_type == volume_type && + group.diameter == diameter) { + found = true; + group.nozzle_count += 1; + break; + } + } + if (!found) + nozzle_groups.emplace_back(diameter, volume_type, extruder_id, 1); + } + return nozzle_groups; +} + +bool DevNozzleSystem::IsRackMaximumInstalled() const +{ + auto ext_nozzle = GetExtNozzle(MAIN_EXTRUDER_ID); + if (ext_nozzle.IsEmpty()) { + return false; + } + + const auto& rack_nozzles = m_nozzle_rack->GetRackNozzles(); + if (rack_nozzles.size() < 6) { + return false; + } + + for (auto item : rack_nozzles) { + if (item.second.IsEmpty()) { + return false; + } + } + + return true; +} + +bool DevNozzleSystem::HasUnreliableNozzles() const +{ + for (auto nozzle : m_ext_nozzles) { + if (!nozzle.second.IsInfoReliable()) { + return true; + } + } + + if (m_nozzle_rack->HasUnreliableNozzles()) { + return true; + } + + return false; +} + +bool DevNozzleSystem::HasUnknownNozzles() const +{ + for (auto nozzle : m_ext_nozzles) { + if (nozzle.second.IsUnknown()) { + return true; + } + } + + if (m_nozzle_rack->HasUnknownNozzles()) { + return true; + } + + return false; +} + +void DevNozzleSystem::AddFirmwareInfoWTM(const DevFirmwareVersionInfo& info) +{ + static const std::string s_wtm_prefix = "wtm/"; + auto pos = info.name.find(s_wtm_prefix); + if (pos == std::string::npos) { + m_ext_nozzle_firmware_info = info; + } else { + try { + auto str = info.name.substr(s_wtm_prefix.size()); // remove "wtm/" prefix + int rack_nozzle_id = std::stoi(str) - 0x10; // rack nozzle IDs start from 0x10 + m_nozzle_rack->AddNozzleFirmwareInfo(rack_nozzle_id, info); + } catch (const std::exception& e) { + BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << ": Invalid nozzle ID in firmware name: " + << info.name << ", error: " << e.what(); + } + } +} + +void DevNozzleSystem::ClearFirmwareInfoWTM() +{ + m_ext_nozzle_firmware_info = DevFirmwareVersionInfo(); + m_nozzle_rack->ClearNozzleFirmwareInfo(); +} + +void DevNozzleSystem::ClearNozzles() +{ + m_ext_nozzles.clear(); + m_nozzle_rack->ClearRackNozzles(); +} + +// ---- parsing ---------------------------------------------------------------------------------------- static unordered_map _str2_nozzle_flow_type = { {"S", NozzleFlowType::S_FLOW}, {"H", NozzleFlowType::H_FLOW}, {"A", NozzleFlowType::S_FLOW}, - {"X", NozzleFlowType::S_FLOW} + {"X", NozzleFlowType::S_FLOW}, + {"E", NozzleFlowType::H_FLOW}, // E3D high-flow + {"U", NozzleFlowType::U_FLOW}, // TPU 1.75 high-flow -> nvtTPUHighFlow }; static unordered_map _str2_nozzle_type = { @@ -57,6 +435,11 @@ static void s_parse_nozzle_type(const std::string& nozzle_type_str, DevNozzle& n nozzle.m_nozzle_type = _str2_nozzle_type[type_str]; } } + else if (nozzle_type_str == "N/A") + { + nozzle.m_nozzle_type = NozzleType::ntUndefine; + nozzle.m_nozzle_flow = NozzleFlowType::NONE_FLOWTYPE; + } } @@ -66,8 +449,9 @@ void DevNozzleSystemParser::ParseV1_0(const nlohmann::json& nozzletype_json, std::optional flag_e3d) { //Since both the old and new protocols push data. - // assert(system->m_nozzles.size() < 2); + // assert(system->m_ext_nozzles.size() < 2); DevNozzle nozzle; + nozzle.SetRack(system->GetNozzleRack()); nozzle.m_nozzle_id = 0; nozzle.m_nozzle_flow = NozzleFlowType::S_FLOW; // default flow type @@ -112,32 +496,88 @@ void DevNozzleSystemParser::ParseV1_0(const nlohmann::json& nozzletype_json, } } - system->m_nozzles[nozzle.m_nozzle_id] = nozzle; + system->m_ext_nozzles[nozzle.m_nozzle_id] = nozzle; } -void DevNozzleSystemParser::ParseV2_0(const json& nozzle_json, DevNozzleSystem* system) +void DevNozzleSystemParser::ParseV2_0(const json& device_json, DevNozzleSystem* system) { - system->Reset(); - - if (nozzle_json.contains("exist")) + if (device_json.contains("nozzle")) { - system->m_extder_exist = DevUtil::get_flag_bits(nozzle_json["exist"].get(), 0, 16); + const json& nozzle_json = device_json["nozzle"]; + if (nozzle_json.contains("exist")) + { + system->m_extder_exist = DevUtil::get_flag_bits(nozzle_json["exist"].get(), 0, 16); + } + + if (nozzle_json.contains("state")) + { + system->m_state_0_4 = DevUtil::get_flag_bits(nozzle_json["state"].get(), 0, 4); + int new_reading_idx = DevUtil::get_flag_bits(nozzle_json["state"].get(), 8, 4); + int new_reading_count = DevUtil::get_flag_bits(nozzle_json["state"].get(), 4, 4); + if (system->m_reading_count != new_reading_count || system->m_reading_idx != new_reading_idx) + { + system->m_reading_idx = new_reading_idx; + system->m_reading_count = new_reading_count; + if (system->m_reading_count == 0) + { + system->m_nozzle_rack->SendReadingFinished(); + } + } + } + + // replace-nozzle state: a rack nozzle standing in for the toolhead position. + // Absent for every non-rack printer's "nozzle" block => both stay std::nullopt (inert). + if (nozzle_json.contains("src_id")) + { + system->m_replace_nozzle_src = std::make_optional(nozzle_json["src_id"].get()); + } + + if (nozzle_json.contains("tar_id")) + { + system->m_replace_nozzle_tar = std::make_optional(nozzle_json["tar_id"].get()); + } + + system->ClearNozzles(); + for (auto it = nozzle_json["info"].begin(); it != nozzle_json["info"].end(); it++) + { + DevNozzle nozzle_obj; + nozzle_obj.SetRack(system->GetNozzleRack()); + + const auto& njon = it.value(); + nozzle_obj.m_nozzle_id = DevUtil::get_hex_bits(njon["id"].get(), 0); + nozzle_obj.m_diameter = njon["diameter"].get(); + s_parse_nozzle_type(njon["type"].get(), nozzle_obj); + if (njon.contains("stat"))/*maybe not contains*/ + { + nozzle_obj.SetStatus(njon["stat"].get()); + } + + DevJsonValParser::ParseVal(njon, "fila_id", nozzle_obj.m_fila_id); + DevJsonValParser::ParseVal(njon, "wear", nozzle_obj.m_wear); + if (njon.contains("p_t"))/*maybe not contains*/ + { + nozzle_obj.m_nozzle_print_time = njon["p_t"].get(); + } + if (njon.contains("color_m"))/*maybe not contains*/ + { + nozzle_obj.m_filament_clr = njon["color_m"].get(); + } + + if (DevUtil::get_hex_bits(njon["id"].get(), 1) == 1) + { + system->m_nozzle_rack->AddRackNozzle(nozzle_obj); + } + else + { + system->m_ext_nozzles[nozzle_obj.m_nozzle_id] = nozzle_obj; + } + } } - if (nozzle_json.contains("state")) + if (device_json.contains("holder")) { - system->m_state = DevUtil::get_flag_bits(nozzle_json["state"].get(), 0, 4); - } - - for (auto it = nozzle_json["info"].begin(); it != nozzle_json["info"].end(); it++) - { - DevNozzle nozzle_obj; - const auto& njon = it.value(); - nozzle_obj.m_nozzle_id = njon["id"].get(); - nozzle_obj.m_diameter = njon["diameter"].get(); - s_parse_nozzle_type(njon["type"].get(), nozzle_obj); - system->m_nozzles[nozzle_obj.m_nozzle_id] = nozzle_obj; + system->m_nozzle_rack->ParseRackInfo(device_json["holder"]); } } -} \ No newline at end of file +}; diff --git a/src/slic3r/GUI/DeviceCore/DevNozzleSystem.h b/src/slic3r/GUI/DeviceCore/DevNozzleSystem.h index db786cf966..088c2f858b 100644 --- a/src/slic3r/GUI/DeviceCore/DevNozzleSystem.h +++ b/src/slic3r/GUI/DeviceCore/DevNozzleSystem.h @@ -1,28 +1,112 @@ #pragma once +#include "DevDefs.h" +#include "DevFirmware.h" + #include "libslic3r/CommonDefs.hpp" +#include "libslic3r/MultiNozzleUtils.hpp" #include "slic3r/Utils/json_diff.hpp" #include #include +#include + +// Device flow-type mapping note (nozzle rack): the device U_FLOW value maps to nvtTPUHighFlow +// (TPU High Flow), so a device-synced TPU-HF rack resolves to nvtTPUHighFlow and the H2C +// change_filament_gcode TPU-kit branch can activate. nvtHybrid is a slicer-only sentinel with no +// device representation, so it stays out of these device flow-type conversions. +// +// GetExtruderNozzleInfo (and its ExtruderNozzleInfos / NozzleDef type pair) is intentionally +// omitted here; it belongs with the SelectMachine slicing-vs-installed nozzle-info comparison +// that is its only consumer. namespace Slic3r { // Previous definitions class MachineObject; + class DevNozzleRack; struct DevNozzle { + friend class DevNozzleSystemParser; + + public: int m_nozzle_id = -1; NozzleFlowType m_nozzle_flow = NozzleFlowType::S_FLOW;// 0-common 1-high flow NozzleType m_nozzle_type = NozzleType::ntUndefine;// 0-stainless_steel 1-hardened_steel 5-tungsten_carbide float m_diameter = 0.0f;// unknown until reported by the printer + + public: + // flow/volume conversions (Standard / High Flow / TPU High Flow) + static NozzleFlowType ToNozzleFlowType(const NozzleVolumeType& type); + static NozzleVolumeType ToNozzleVolumeType(const NozzleFlowType& type); + + static wxString GetNozzleFlowTypeStr(NozzleFlowType type); + static std::string GetNozzleFlowTypeString(NozzleFlowType type);// untranslated literal ("High Flow"/"Standard"/"TPU High Flow") — the filament blacklist JSON matches these raw strings, so it must NOT use the translated GetNozzleFlowTypeStr (would break non-English locales) + static std::string ToNozzleFlowString(const NozzleFlowType& type);// untranslated "Standard"/"High Flow"/"TPU High Flow" ("" for none) — the raw literal serialized into the get_auto_nozzle_mapping payload + static wxString GetNozzleTypeStr(NozzleType type); + + public: + bool IsEmpty() const { return m_nozzle_id < 0; } + + void SetRack(const std::weak_ptr& rack) { m_nozzle_rack = rack; } + + int GetNozzleId() const { return m_nozzle_id; } + int GetNozzlePosId() const;// physical position id: rack nozzle -> id + 0x10, else id + NozzleType GetNozzleType() const { return m_nozzle_type; } + NozzleFlowType GetNozzleFlowType() const { return m_nozzle_flow; } + NozzleDiameterType GetNozzleDiameterType() const; + float GetNozzleDiameter() const { return m_diameter; } + float GetNozzleWear() const { return m_wear; } + int GetNozzlePrintTime() const { return m_nozzle_print_time; } + + // firmware (rack hotend WTM / extruder nozzle firmware) + DevFirmwareVersionInfo GetFirmwareInfo() const; + + // display + wxString GetNozzleDiameterStr() const { return wxString::Format("%.1f mm", m_diameter); } + wxString GetNozzleFlowTypeStr() const { return GetNozzleFlowTypeStr(m_nozzle_flow); } + wxString GetNozzleTypeStr() const { return GetNozzleTypeStr(m_nozzle_type); } + + std::string GetFilamentId() const { return m_fila_id; } + std::string GetFilamentColor() const { return m_filament_clr; } + + // location + bool AtLeftExtruder() const; + bool AtRightExtruder() const; + + int GetLogicExtruderId() const;// warning: logical extruder id + int GetExtruderId() const;// warning: physical extruder id + + /* holder nozzle */ + bool IsOnRack() const { return m_on_rack; } + bool IsInfoReliable() const; + + bool IsNormal() const; + bool IsAbnormal() const; + bool IsUnknown() const; + + void SetOnRack(bool on_rack) { m_on_rack = on_rack; } + void SetStatus(int stat) { m_stat = stat; } + + private: + int GetTotalExtruderCount() const; + + private: + bool m_on_rack = false; + + int m_stat = 0; + float m_wear = 0.0f; + + std::string m_fila_id; // main material + std::string m_filament_clr; // main color + + std::weak_ptr m_nozzle_rack; // weak pointer to the nozzle rack + int m_nozzle_print_time{0}; }; class DevNozzleSystem { friend class DevNozzleSystemParser; - public: - DevNozzleSystem(MachineObject* owner) : m_owner(owner) {} private: enum Status : int { @@ -31,27 +115,81 @@ namespace Slic3r }; public: - bool ContainsNozzle(int id) const { return m_nozzles.find(id) != m_nozzles.end(); } - DevNozzle GetNozzle(int id) const; - const std::map& GetNozzles() const { return m_nozzles;} - bool IsRefreshing() const { return m_state == 1; } + DevNozzleSystem(MachineObject* owner); + ~DevNozzleSystem() = default; + + public: + MachineObject* GetOwner() const { return m_owner; } + + // nozzle by position id: pos_id < 0x10 -> extruder nozzle, else rack nozzle at (pos_id - 0x10) + DevNozzle GetNozzleByPosId(int pos_id) const { return pos_id < 0x10 ? GetExtNozzle(pos_id) : GetRackNozzle(pos_id - 0x10); } + + // nozzles on extruder + bool ContainsExtNozzle(int id) const { return m_ext_nozzles.find(id) != m_ext_nozzles.end(); } + DevNozzle GetExtNozzle(int id) const; + const std::map& GetExtNozzles() const { return m_ext_nozzles; } + int GetExtNozzleCount() const { return (int) m_ext_nozzles.size(); } + + // nozzles on rack + void SetSupportNozzleRack(bool supported); + std::shared_ptr GetNozzleRack() const { return m_nozzle_rack; } + DevNozzle GetRackNozzle(int idx) const; + const std::map& GetRackNozzles() const; + + // nozzles on extruder and rack + bool IsRackMaximumInstalled() const;// true when the main extruder + all 6 rack slots hold nozzles + + // grouping (drives the MultiNozzleSyncDialog options) + const std::vector CollectNozzles(int ext_loc, NozzleFlowType flow_type, float diameter = -1.0f) const; + std::vector GetNozzleGroups() const; + + bool IsIdle() const { return m_state_0_4 == NOZZLE_SYSTEM_IDLE; } + bool IsRefreshing() const { return m_state_0_4 == NOZZLE_SYSTEM_REFRESHING; } + + bool HasUnreliableNozzles() const;// any extruder or rack nozzle whose reported info is not reliable + bool HasUnknownNozzles() const; // any extruder or rack nozzle of unknown state + + /* reading */ + int GetReadingIdx() const { return m_reading_idx; } + int GetReadingCount() const { return m_reading_count; } + + /* firmware */ + void AddFirmwareInfoWTM(const DevFirmwareVersionInfo& info);// route a "wtm/" module version to the rack nozzle, else to the extruder nozzle + void ClearFirmwareInfoWTM(); + DevFirmwareVersionInfo GetExtruderNozzleFirmware() const { return m_ext_nozzle_firmware_info; } + + /* replace nozzle (device reports src/tar position while a rack nozzle stands in for the toolhead) */ + std::optional GetReplaceNozzleSrc() const { return m_replace_nozzle_src; } + std::optional GetReplaceNozzleTar() const { return m_replace_nozzle_tar; } private: - void Reset(); + void ClearNozzles(); private: MachineObject* m_owner = nullptr; - int m_extder_exist = 0; //0- none exist 1-exist, unused - int m_state = 0; //0-idle 1-checking, unused - std::map m_nozzles; + int m_extder_exist = 0; //0- none exist 1-exist, unused + int m_state_0_4 = 0; //0-idle 1-refreshing + std::optional m_replace_nozzle_src; // replace nozzle source position (device-reported) + std::optional m_replace_nozzle_tar; // replace nozzle target position (device-reported) + + /* refreshing */ + int m_reading_idx = 0; + int m_reading_count = 0; + + // nozzles on extruder + std::map m_ext_nozzles; + DevFirmwareVersionInfo m_ext_nozzle_firmware_info; + + // nozzles on rack + std::shared_ptr m_nozzle_rack; }; class DevNozzleSystemParser { public: static void ParseV1_0(const nlohmann::json& nozzletype_json, const nlohmann::json& diameter_json, DevNozzleSystem* system, std::optional flag_e3d); - static void ParseV2_0(const json& nozzle_json, DevNozzleSystem* system); + static void ParseV2_0(const json& device_json, DevNozzleSystem* system); }; -}; \ No newline at end of file +}; diff --git a/src/slic3r/GUI/DeviceCore/DevUtil.h b/src/slic3r/GUI/DeviceCore/DevUtil.h index 4ba309b18c..fcf912b622 100644 --- a/src/slic3r/GUI/DeviceCore/DevUtil.h +++ b/src/slic3r/GUI/DeviceCore/DevUtil.h @@ -28,6 +28,10 @@ public: static int get_flag_bits(std::string str, int start, int count = 1); static int get_flag_bits(int num, int start, int count = 1, int base = 10); + // Extract the hex digit at position `pos` (0 = least-significant). + // eg. get_hex_bits(16, 1, 10) = 1 + static int get_hex_bits(int num, int pos, int input_num_base = 10) { return get_flag_bits(num, pos * 4, 4, input_num_base); } + static float string_to_float(const std::string& str_value); static std::string convertToIp(long long ip); diff --git a/src/slic3r/GUI/DeviceCore/DevUtilBackend.cpp b/src/slic3r/GUI/DeviceCore/DevUtilBackend.cpp new file mode 100644 index 0000000000..4d909bf68f --- /dev/null +++ b/src/slic3r/GUI/DeviceCore/DevUtilBackend.cpp @@ -0,0 +1,18 @@ +#include "DevUtilBackend.h" + +#include "slic3r/GUI/BackgroundSlicingProcess.hpp" +#include "slic3r/GUI/Plater.hpp" + +namespace Slic3r +{ + +std::shared_ptr DevUtilBackend::GetNozzleGroupResult(Slic3r::GUI::Plater* plater) +{ + if (plater && plater->background_process().get_current_gcode_result()) { + return plater->background_process().get_current_gcode_result()->nozzle_group_result; + } + + return nullptr; +} + +}; // namespace Slic3r diff --git a/src/slic3r/GUI/DeviceCore/DevUtilBackend.h b/src/slic3r/GUI/DeviceCore/DevUtilBackend.h new file mode 100644 index 0000000000..54839d9315 --- /dev/null +++ b/src/slic3r/GUI/DeviceCore/DevUtilBackend.h @@ -0,0 +1,32 @@ +/** + * @file DevUtilBackend.h + * @brief Static helpers bridging the backend (slicer/preset) to the device GUI. + * + * Scope (H2C nozzle rack): exposes only GetNozzleGroupResult — the accessor the print-dispatch + * nozzle-mapping V1 request and result-resolve helpers read. Related helpers (per-nozzle info + * collection via the ExtruderNozzleInfos/NozzleDef type pair, and the drying-preset lookup) are + * not implemented here and left to the consuming code. + */ + +#pragma once + +#include "libslic3r/MultiNozzleUtils.hpp" + +#include + +namespace Slic3r +{ +namespace GUI { class Plater; } + +class DevUtilBackend +{ +public: + DevUtilBackend() = delete; + + // for rack: the slicer's per-filament -> logical-nozzle grouping for the current plate, read off + // the post-slice GCodeProcessorResult (plater->background_process().get_current_gcode_result()). + // Returns nullptr when there is no plater / no current result. + static std::shared_ptr GetNozzleGroupResult(Slic3r::GUI::Plater* plater); +}; + +}; // namespace Slic3r