From 2fecfc291f3fb6ef52778916ff7fcdc89fb8fa7e Mon Sep 17 00:00:00 2001 From: SoftFever Date: Mon, 13 Jul 2026 00:49:16 +0800 Subject: [PATCH] fix(gui): rack-aware pre-print nozzle checks in the send dialog A print sliced for a nozzle that sits in the hotend rack (but is not mounted) was blocked by the send dialog's mounted-nozzle diameter check, even though the printer fetches the required nozzle itself (#14685). Consolidate the three mounted-nozzle gates (_is_nozzle_data_valid, is_nozzle_type_match, _is_same_nozzle_diameters) into a single CheckErrorExtruderNozzleWithSlicing fed by s_get_slicing_extuder_nozzles, which collects the plate's per-extruder nozzle requirements (hybrid extruders contribute one entry per used sub-nozzle flow). The rack extruder validates against its whole inventory (mounted + rack) with guidance to calibrate the rack, refresh nozzle info, or re-slice, and blocks when toolhead + rack are full (no free slot to stow a nozzle). Other extruders keep the validity/flow/diameter checks against the mounted nozzle. Also add CheckErrorRackStatus, which holds Send while the printer is still reading the rack hotend information, and judge material hardness for the rack extruder per dispatch-mapped nozzle as a non-blocking caution (mounted nozzles keep the blocking gate). --- localization/i18n/OrcaSlicer.pot | 26 +- src/slic3r/GUI/DeviceCore/DevDefs.h | 25 +- src/slic3r/GUI/DeviceCore/DevNozzleSystem.h | 6 +- src/slic3r/GUI/PartPlate.cpp | 11 + src/slic3r/GUI/PartPlate.hpp | 1 + src/slic3r/GUI/PrePrintChecker.cpp | 4 + src/slic3r/GUI/PrePrintChecker.hpp | 4 + src/slic3r/GUI/SelectMachine.cpp | 497 +++++++++++--------- src/slic3r/GUI/SelectMachine.hpp | 7 +- 9 files changed, 350 insertions(+), 231 deletions(-) diff --git a/localization/i18n/OrcaSlicer.pot b/localization/i18n/OrcaSlicer.pot index 5ed348ec14..a72f4d6cd6 100644 --- a/localization/i18n/OrcaSlicer.pot +++ b/localization/i18n/OrcaSlicer.pot @@ -18763,8 +18763,26 @@ msgstr "" msgid "Both" msgstr "" -msgid "" -"The printer has no nozzle matching the slicing file (%s). Please install a " -"matching nozzle on the printer, or set the corresponding printer preset " -"while slicing." +#, possible-c-format, possible-boost-format +msgid "The printer has no nozzle matching the slicing file (%s)." +msgstr "" + +msgid "Please complete the hotend rack setup and try again." +msgstr "" + +msgid "Please refresh the nozzle information and try again." +msgstr "" + +msgid "Please install a matching nozzle in the hotend rack, or set the corresponding printer preset while slicing." +msgstr "" + +msgid "The toolhead and hotend rack are full. Please remove at least one hotend before printing." +msgstr "" + +#, possible-c-format, possible-boost-format +msgid "Refreshing information of hotends(%d/%d)." +msgstr "" + +#, possible-c-format, possible-boost-format +msgid "The hardness of current material (%s) exceeds the hardness of %s(%s). It may cause nozzle wear, leading to material leakage and unstable flow. Please exercise caution when using it." msgstr "" diff --git a/src/slic3r/GUI/DeviceCore/DevDefs.h b/src/slic3r/GUI/DeviceCore/DevDefs.h index 76a1d6843a..4e55f7cbae 100644 --- a/src/slic3r/GUI/DeviceCore/DevDefs.h +++ b/src/slic3r/GUI/DeviceCore/DevDefs.h @@ -157,4 +157,27 @@ enum PrintFromType }; } -};// namespace Slic3r \ No newline at end of file +};// namespace Slic3r + +// A nozzle requirement of the sliced plate (or an installed nozzle's identity), compared in the +// pre-print slicing-vs-installed nozzle checks. +struct NozzleDef +{ + float nozzle_diameter; + Slic3r::NozzleFlowType nozzle_flow_type; + + bool operator==(const NozzleDef& other) const + { + return nozzle_diameter == other.nozzle_diameter && nozzle_flow_type == other.nozzle_flow_type; + } +}; + +template<> struct std::hash +{ + std::size_t operator()(const NozzleDef& v) const noexcept + { + size_t h1 = std::hash{}(v.nozzle_diameter * 1000); + size_t h2 = std::hash{}(v.nozzle_flow_type); + return h1 ^ (h2 + 0x9e3779b9 + (h1 << 6) + (h1 >> 2)); + }; +}; \ No newline at end of file diff --git a/src/slic3r/GUI/DeviceCore/DevNozzleSystem.h b/src/slic3r/GUI/DeviceCore/DevNozzleSystem.h index 088c2f858b..8603baeaaf 100644 --- a/src/slic3r/GUI/DeviceCore/DevNozzleSystem.h +++ b/src/slic3r/GUI/DeviceCore/DevNozzleSystem.h @@ -15,9 +15,9 @@ // 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. +// GetExtruderNozzleInfo (and its ExtruderNozzleInfos aggregate) is intentionally omitted here; +// the SelectMachine slicing-vs-installed nozzle comparison collects its per-extruder NozzleDef +// entries itself (s_get_slicing_extuder_nozzles). namespace Slic3r { diff --git a/src/slic3r/GUI/PartPlate.cpp b/src/slic3r/GUI/PartPlate.cpp index e165248d0a..1f0454c778 100644 --- a/src/slic3r/GUI/PartPlate.cpp +++ b/src/slic3r/GUI/PartPlate.cpp @@ -1919,6 +1919,17 @@ int PartPlate::get_physical_extruder_by_filament_id(const DynamicConfig& g_confi return the_map->values[zero_base_logical_idx]; } +int PartPlate::get_logical_extruder_by_filament_id(const DynamicConfig& g_config, int idx) const +{ + const std::vector& filament_map = get_real_filament_maps(g_config); + if (idx <= 0 || idx > (int)filament_map.size()) + { + return -1; + } + + return filament_map[idx - 1] - 1; +} + std::vector PartPlate::get_used_filaments() { std::vector used_filaments; diff --git a/src/slic3r/GUI/PartPlate.hpp b/src/slic3r/GUI/PartPlate.hpp index 8918742d1d..8f4055706f 100644 --- a/src/slic3r/GUI/PartPlate.hpp +++ b/src/slic3r/GUI/PartPlate.hpp @@ -350,6 +350,7 @@ public: std::vector get_used_filaments(); const std::vector& get_slice_filaments_info() const { return slice_filaments_info; } int get_physical_extruder_by_filament_id(const DynamicConfig& g_config, int idx) const; + int get_logical_extruder_by_filament_id(const DynamicConfig& g_config, int idx) const; bool check_filament_printable(const DynamicPrintConfig & config, wxString& error_message); bool check_tpu_printable_status(const DynamicPrintConfig & config, const std::vector &tpu_filaments); bool check_mixture_of_pla_and_petg(const DynamicPrintConfig & config); diff --git a/src/slic3r/GUI/PrePrintChecker.cpp b/src/slic3r/GUI/PrePrintChecker.cpp index 4344e7170c..a7605c616d 100644 --- a/src/slic3r/GUI/PrePrintChecker.cpp +++ b/src/slic3r/GUI/PrePrintChecker.cpp @@ -58,7 +58,11 @@ std::string PrePrintChecker::get_print_status_info(PrintDialogStatus status) case PrintStatusFilamentWarningHighChamberTempCloseDoor: return "PrintStatusFilamentWarningHighChamberTempCloseDoor"; case PrintStatusFilamentWarningHighChamberTempSoft: return "PrintStatusFilamentWarningHighChamberTempSoft"; case PrintStatusFilamentWarningUnknownHighChamberTempSoft: return "PrintStatusFilamentWarningUnknownHighChamberTempSoft"; + case PrintStatusNozzleNoMatchedHotends: return "PrintStatusNozzleNoMatchedHotends"; + case PrintStatusNozzleRackMaximumInstalled: return "PrintStatusNozzleRackMaximumInstalled"; + case PrintStatusRackReading: return "PrintStatusRackReading"; case PrintStatusWarningExtFilamentNotMatch: return "PrintStatusWarningExtFilamentNotMatch"; + case PrintStatusFilamentWarningNozzleHRC: return "PrintStatusFilamentWarningNozzleHRC"; case PrintStatusReadingFinished: return "PrintStatusReadingFinished"; case PrintStatusSendingCanceled: return "PrintStatusSendingCanceled"; case PrintStatusAmsMappingSuccess: return "PrintStatusAmsMappingSuccess"; diff --git a/src/slic3r/GUI/PrePrintChecker.hpp b/src/slic3r/GUI/PrePrintChecker.hpp index 5996782b1d..621fc34647 100644 --- a/src/slic3r/GUI/PrePrintChecker.hpp +++ b/src/slic3r/GUI/PrePrintChecker.hpp @@ -55,6 +55,8 @@ enum PrintDialogStatus : unsigned int { PrintStatusNozzleDataInvalid, PrintStatusNozzleDiameterMismatch, PrintStatusNozzleTypeMismatch, + PrintStatusNozzleNoMatchedHotends, + PrintStatusNozzleRackMaximumInstalled, PrintStatusRefreshingMachineList, PrintStatusSending, PrintStatusLanModeNoSdcard, @@ -67,6 +69,7 @@ enum PrintDialogStatus : unsigned int { PrintStatusUnsupportedPrinter, PrintStatusRackNozzleMappingWaiting, PrintStatusRackNozzleMappingError, + PrintStatusRackReading, PrintStatusFilaSwitcherError, PrintStatusPrinterErrorEnd, @@ -105,6 +108,7 @@ enum PrintDialogStatus : unsigned int { PrintStatusFilamentWarningHighChamberTempSoft, PrintStatusFilamentWarningUnknownHighChamberTempSoft, PrintStatusWarningExtFilamentNotMatch, + PrintStatusFilamentWarningNozzleHRC, PrintStatusFilamentWarningEnd, PrintStatusWarningEnd,//->end error<- diff --git a/src/slic3r/GUI/SelectMachine.cpp b/src/slic3r/GUI/SelectMachine.cpp index ff169a86ae..cdac1da950 100644 --- a/src/slic3r/GUI/SelectMachine.cpp +++ b/src/slic3r/GUI/SelectMachine.cpp @@ -38,6 +38,7 @@ #include #include #include +#include #include "Plater.hpp" #include "Notebook.hpp" #include "BitmapCache.hpp" @@ -1393,103 +1394,6 @@ void SelectMachineDialog::auto_supply_with_ext(std::vector slots) { } } -bool SelectMachineDialog::is_nozzle_type_match(DevExtderSystem data, wxString& error_message) const { - if (data.GetTotalExtderCount() <= 1 || !wxGetApp().preset_bundle) - return false; - - const auto& project_config = wxGetApp().preset_bundle->project_config; - //check nozzle used - auto used_filaments = wxGetApp().plater()->get_partplate_list().get_curr_plate()->get_used_filaments(); // 1 based - auto filament_maps = wxGetApp().plater()->get_partplate_list().get_curr_plate()->get_real_filament_maps(project_config); // 1 based - // flow type and diameter of every nozzle the sliced plate needs, keyed by logical extruder; - // a hybrid extruder contributes one entry per used sub-nozzle flow - std::multimap> used_extruders_flow; - std::vector used_extruders; // 0 based - for (auto f : used_filaments) { - int filament_extruder = filament_maps[f - 1] - 1; - if (std::find(used_extruders.begin(), used_extruders.end(), filament_extruder) == used_extruders.end()) used_extruders.emplace_back(filament_extruder); - } - - std::sort(used_extruders.begin(), used_extruders.end()); - - auto nozzle_volume_type_opt = dynamic_cast(wxGetApp().preset_bundle->project_config.option("nozzle_volume_type")); - auto nozzle_diameter_opt = wxGetApp().preset_bundle->printers.get_edited_preset().config.option("nozzle_diameter"); - for (auto i = 0; i < used_extruders.size(); i++) { - if (nozzle_volume_type_opt) { - NozzleVolumeType nozzle_volume_type = (NozzleVolumeType) (nozzle_volume_type_opt->get_at(used_extruders[i])); - float preset_diameter = nozzle_diameter_opt ? (float) nozzle_diameter_opt->get_at(used_extruders[i]) : -1.0f; - if (nozzle_volume_type == NozzleVolumeType::nvtHybrid) { - // A hybrid extruder prints with a mix of nozzle flows: collect the flow of each - // physically used nozzle instead of forcing a single one. - auto nozzle_group_res = DevUtilBackend::GetNozzleGroupResult(m_plater); - if (nozzle_group_res) { - for (const auto &nozzle_info : nozzle_group_res->get_used_nozzles_in_extruder(used_extruders[i])) { - float diameter = preset_diameter; - try { diameter = std::stof(nozzle_info.diameter); } catch (const std::exception &) {} - used_extruders_flow.insert({used_extruders[i], {DevNozzle::ToNozzleFlowType(nozzle_info.volume_type), diameter}}); - } - } else { - // A by-object plate with several objects produces no plate-level nozzle grouping, - // so the used flows are unknown; skip the check for this extruder rather than - // blocking the print. - BOOST_LOG_TRIVIAL(warning) << __FUNCTION__ << ": no nozzle group result, flow check skipped for extruder " << used_extruders[i]; - } - } else { - used_extruders_flow.insert({used_extruders[i], {DevNozzle::ToNozzleFlowType(nozzle_volume_type), preset_diameter}}); - } - } - } - - vector map_extruders = {1, 0}; - - const DevNozzleSystem* nozzle_sys = data.Owner() ? data.Owner()->GetNozzleSystem() : nullptr; - const bool has_nozzle_rack = nozzle_sys && nozzle_sys->GetNozzleRack() && nozzle_sys->GetNozzleRack()->IsSupported(); - - //Only when all preset nozzle types and machine nozzle types are exactly the same, return true. - for (auto it = used_extruders_flow.begin(); it != used_extruders_flow.end(); it++) { - // The default two extruders are left, right, but the order of the extruders on the machine is right, left. - int target_machine_nozzle_id = map_extruders[it->first]; - NozzleFlowType used_flow = it->second.first; - - // A nozzle-rack extruder swaps nozzles during the print, so the mounted nozzle is irrelevant: - // the extruder inventory (mounted nozzle + rack) must hold a nozzle of every needed flow instead. - if (has_nozzle_rack && target_machine_nozzle_id == MAIN_EXTRUDER_ID) { - if (nozzle_sys->CollectNozzles(target_machine_nozzle_id, used_flow, it->second.second).empty()) { - error_message = wxString::Format(_L("The printer has no nozzle matching the slicing file (%s). " - "Please install a matching nozzle on the printer, " - "or set the corresponding printer preset while slicing."), - DevNozzle::GetNozzleFlowTypeStr(used_flow)); - return false; - } - continue; - } - - NozzleFlowType machine_flow = data.GetNozzleFlowType(target_machine_nozzle_id); - if (machine_flow == NozzleFlowType::NONE_FLOWTYPE) - continue; // the mounted nozzle is unknown, nothing to compare against - - if (machine_flow != used_flow) { - wxString pos; - if (target_machine_nozzle_id == DEPUTY_EXTRUDER_ID) - { - pos = _L("left nozzle"); - } - else if(target_machine_nozzle_id == MAIN_EXTRUDER_ID) - { - pos = _L("right nozzle"); - } - - error_message = wxString::Format(_L("The nozzle flow setting of %s(%s) doesn't match with the slicing file(%s). " - "Please make sure the nozzle installed matches with settings in printer, " - "then set the corresponding printer preset while slicing."), pos, - DevNozzle::GetNozzleFlowTypeStr(machine_flow), - DevNozzle::GetNozzleFlowTypeStr(used_flow)); - return false; - } - } - return true; -} - int SelectMachineDialog::convert_filament_map_nozzle_id_to_task_nozzle_id(int nozzle_id) const { if (nozzle_id == (int)FilamentMapNozzleId::NOZZLE_LEFT) { @@ -2090,6 +1994,20 @@ void SelectMachineDialog::show_status(PrintDialogStatus status, std::vector _get_used_nozzle_idxes() return used_nozzle_idxes; } - -static bool _is_nozzle_data_valid(MachineObject* obj_, const DevExtderSystem &ext_data) +// On a hotend-rack printer the right extruder swaps to the required nozzle during the print, so +// pre-print checks against its currently mounted nozzle don't apply. Availability of a matching +// nozzle (flow and diameter) is checked against the whole inventory (mounted + rack) in +// CheckErrorExtruderNozzleWithSlicing() instead. +static bool _is_rack_managed_nozzle(const MachineObject* obj, int nozzle_idx) { - if (obj_ == nullptr) return false; - - PresetBundle *preset_bundle = wxGetApp().preset_bundle; - - try { - PartPlate *cur_plate = wxGetApp().plater()->get_partplate_list().get_curr_plate(); - auto used_filament_idxs = cur_plate->get_used_filaments(); /*the index is started from 1*/ - for (int used_filament_idx : used_filament_idxs) - { - int used_nozzle_idx = cur_plate->get_physical_extruder_by_filament_id(preset_bundle->full_config(), used_filament_idx); - if (ext_data.GetNozzleType(used_nozzle_idx) == NozzleType::ntUndefine || - ext_data.GetNozzleDiameter(used_nozzle_idx) <= 0.0f || - ext_data.GetNozzleFlowType(used_nozzle_idx) == NozzleFlowType::NONE_FLOWTYPE) { - return false; - } - } - } catch (const std::exception &) { + if (nozzle_idx != MAIN_EXTRUDER_ID) return false; - } - return true; -} - - -/**************************************************************//* - * @param tag_nozzle_type -- return the mismatch nozzle type - * @param tag_nozzle_diameter -- return the target nozzle_diameter but mismatch - * @return is same or not -/*************************************************************/ -static bool _is_same_nozzle_diameters(MachineObject* obj, float &tag_nozzle_diameter, int& mismatch_nozzle_id) -{ - if (obj == nullptr) return false; - - PresetBundle* preset_bundle = wxGetApp().preset_bundle; - auto opt_nozzle_diameters = preset_bundle->printers.get_edited_preset().config.option("nozzle_diameter"); - if (!opt_nozzle_diameters) - { - return false; - } - - try - { - PartPlate* cur_plate = wxGetApp().plater()->get_partplate_list().get_curr_plate(); - auto used_filament_idxs = cur_plate->get_used_filaments();/*the index is started from 1*/ - for (int used_filament_idx : used_filament_idxs) - { - int used_nozzle_idx = cur_plate->get_physical_extruder_by_filament_id(preset_bundle->full_config(), used_filament_idx); - if (used_nozzle_idx == -1) - { - assert(0); - return false; - } - - tag_nozzle_diameter = float(opt_nozzle_diameters->get_at(used_nozzle_idx)); - auto machine_nozzle_diameter = obj->GetExtderSystem()->GetNozzleDiameter(used_nozzle_idx); - - // Assume matching if diameter is unknown - if (machine_nozzle_diameter == 0.0f) - { - continue; - } - - if (tag_nozzle_diameter != machine_nozzle_diameter) - { - mismatch_nozzle_id = used_nozzle_idx; - return false; - } - } - } - catch (const std::exception&) - { - return false; - } - - return true; + const DevNozzleSystem* nozzle_sys = obj ? obj->GetNozzleSystem() : nullptr; + return nozzle_sys && nozzle_sys->GetNozzleRack() && nozzle_sys->GetNozzleRack()->IsSupported(); } bool SelectMachineDialog::is_nozzle_hrc_matched(const DevExtder* extruder, std::string& filament_type) const @@ -3608,6 +3459,228 @@ static wxString _get_nozzle_name(int total_ext_count, int ext_id) return _L("nozzle"); } +// Nozzle requirements of the sliced plate. key -> physical extruder id, value -> nozzle data. +// For example: +// {0, {0.4, S_FLOW}}, {1, {0.8, H_FLOW}} +// {0, {0.4, S_FLOW}}, {0, {0.4, H_FLOW}} // hybrid +static std::unordered_multimap s_get_slicing_extuder_nozzles() +{ + std::unordered_multimap used_extuder_nozzles; + + PresetBundle* preset_bundle = wxGetApp().preset_bundle; + if (!preset_bundle) { + return used_extuder_nozzles; + } + + PartPlate* cur_plate = wxGetApp().plater()->get_partplate_list().get_curr_plate(); + if (!cur_plate) { + return used_extuder_nozzles; + } + + auto opt_nozzle_diameters = preset_bundle->printers.get_edited_preset().config.option("nozzle_diameter"); + if (!opt_nozzle_diameters) { + return used_extuder_nozzles; + } + + auto nozzle_volume_type_opt = dynamic_cast(preset_bundle->project_config.option("nozzle_volume_type")); + if (!nozzle_volume_type_opt) { + return used_extuder_nozzles; + } + + try { + auto nozzle_group_res = DevUtilBackend::GetNozzleGroupResult(wxGetApp().plater()); + if (nozzle_group_res && nozzle_group_res->is_support_dynamic_nozzle_map() && nozzle_volume_type_opt->values.size() == 2) { + const auto& used_nozzles = nozzle_group_res->get_used_nozzles_in_extruder(); + for (const auto& used_nozzle : used_nozzles) { + NozzleDef nozzle_data; + nozzle_data.nozzle_diameter = std::stof(used_nozzle.diameter); + nozzle_data.nozzle_flow_type = DevNozzle::ToNozzleFlowType(used_nozzle.volume_type); + if (used_nozzle.extruder_id == 0) { + used_extuder_nozzles.insert({ DEPUTY_EXTRUDER_ID, nozzle_data }); + } else if (used_nozzle.extruder_id == 1) { + used_extuder_nozzles.insert({ MAIN_EXTRUDER_ID, nozzle_data }); + } + }; + + return used_extuder_nozzles; + } + + const auto& used_filament_idxs = cur_plate->get_used_filaments(); /*the index is started from 1*/ + for (int used_filament_idx : used_filament_idxs) { + int physical_idx = cur_plate->get_physical_extruder_by_filament_id(preset_bundle->full_config(), used_filament_idx); + int logic_extruder_idx = cur_plate->get_logical_extruder_by_filament_id(preset_bundle->full_config(), used_filament_idx); + if (physical_idx < 0 || logic_extruder_idx < 0) { + assert(0); + continue; + } + + NozzleDef nozzle_data; + nozzle_data.nozzle_diameter = float(opt_nozzle_diameters->get_at(logic_extruder_idx)); + nozzle_data.nozzle_flow_type = NozzleFlowType::S_FLOW;// default value + + auto volume_type = (NozzleVolumeType)nozzle_volume_type_opt->get_at(logic_extruder_idx); + if (volume_type == NozzleVolumeType::nvtHybrid) { + if (used_extuder_nozzles.find(physical_idx) != used_extuder_nozzles.end()) { + continue;// already collected + } + + // A hybrid extruder prints with a mix of nozzle flows: collect the flow of each + // physically used nozzle instead of forcing a single one. + if (nozzle_group_res) { + for (const auto& nozzle_info : nozzle_group_res->get_used_nozzles_in_extruder(logic_extruder_idx)) { + nozzle_data.nozzle_flow_type = DevNozzle::ToNozzleFlowType(nozzle_info.volume_type); + used_extuder_nozzles.insert({ physical_idx, nozzle_data }); + } + } else { + // Orca: a by-object plate with several objects produces no plate-level nozzle + // grouping, so the used flows are unknown; skip the check for this extruder + // rather than blocking the print. + BOOST_LOG_TRIVIAL(warning) << __FUNCTION__ << ": no nozzle group result, nozzle check skipped for extruder " << logic_extruder_idx; + } + + continue; + } + + nozzle_data.nozzle_flow_type = DevNozzle::ToNozzleFlowType(volume_type); + used_extuder_nozzles.insert({ physical_idx, nozzle_data }); + }; + } catch (const std::exception& e) { + BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << "exception: " << e.what(); + } + + return used_extuder_nozzles; +} + +bool SelectMachineDialog::CheckErrorRackStatus(MachineObject* obj_) +{ + if (!obj_) { + return true; + } + + auto rack = obj_->GetNozzleSystem()->GetNozzleRack(); + if (!rack->IsSupported()) { + return true; + } + + if (rack->GetReadingCount() > 0) { + const wxString& msg = wxString::Format(_L("Refreshing information of hotends(%d/%d)."), rack->GetReadingIdx(), rack->GetReadingCount()); + show_status(PrintDialogStatus::PrintStatusRackReading, { msg + " " + _L("Please wait a moment...") }); + return false; + } + + return true; +} + +// Compare the extruder nozzle info between slicing file and installed on printer +bool SelectMachineDialog::CheckErrorExtruderNozzleWithSlicing(MachineObject* obj_) +{ + if (!obj_) { + return false; + } + + const auto& ext_sys = obj_->GetExtderSystem(); + const auto& nozzle_sys = obj_->GetNozzleSystem(); + if (m_print_type == FROM_NORMAL) { + const auto& slicing_ext_nozzles = s_get_slicing_extuder_nozzles(); + for (auto slicing_ext_nozzle : slicing_ext_nozzles) { + int slicing_ext_idx = slicing_ext_nozzle.first; + auto slicing_ext = slicing_ext_nozzle.second; + auto installed_ext_nozzle = nozzle_sys->GetExtNozzle(slicing_ext_idx); + + // No need to check the right extruder's mounted nozzle when using a nozzle rack: the + // extruder swaps nozzles during the print, so the inventory (mounted + rack) must hold + // a matching nozzle instead. + if (slicing_ext_idx == MAIN_EXTRUDER_ID && nozzle_sys->GetNozzleRack()->IsSupported()) { + if (nozzle_sys->CollectNozzles(MAIN_EXTRUDER_ID, slicing_ext.nozzle_flow_type, slicing_ext.nozzle_diameter).empty()) { + wxString slicing_nozzle_str = DevNozzle::GetNozzleFlowTypeStr(slicing_ext.nozzle_flow_type); + if (slicing_ext.nozzle_diameter > 0.0f) + slicing_nozzle_str += wxString::Format(" %.1fmm", slicing_ext.nozzle_diameter); + wxString msg = wxString::Format(_L("The printer has no nozzle matching the slicing file (%s)."), slicing_nozzle_str); + msg += " "; + if (nozzle_sys->GetNozzleRack()->GetCaliStatus() != DevNozzleRack::Rack_CALI_OK) + msg += _L("Please complete the hotend rack setup and try again."); + else if (nozzle_sys->HasUnknownNozzles() || nozzle_sys->HasUnreliableNozzles()) + msg += _L("Please refresh the nozzle information and try again."); + else + msg += _L("Please install a matching nozzle in the hotend rack, or set the corresponding printer preset while slicing."); + + show_status(PrintDialogStatus::PrintStatusNozzleNoMatchedHotends, { msg }); + return false; + } + + // A nozzle swap needs a free rack slot to stow the mounted nozzle first. + if (nozzle_sys->IsRackMaximumInstalled()) { + show_status(PrintDialogStatus::PrintStatusNozzleRackMaximumInstalled, + { _L("The toolhead and hotend rack are full. Please remove at least one hotend before printing.") }); + return false; + } + + continue; + } + + // check nozzle data valid + { + if (installed_ext_nozzle.GetNozzleType() == NozzleType::ntUndefine || + installed_ext_nozzle.GetNozzleDiameter() <= 0.0f) { + show_status(PrintDialogStatus::PrintStatusNozzleDataInvalid); + return false; + } + + if (obj_->is_nozzle_flow_type_supported() && + installed_ext_nozzle.GetNozzleFlowType() == NozzleFlowType::NONE_FLOWTYPE) { + show_status(PrintDialogStatus::PrintStatusNozzleDataInvalid); + return false; + } + } + + // check nozzle flow type + { + if (obj_->is_nozzle_flow_type_supported() && slicing_ext.nozzle_flow_type != installed_ext_nozzle.GetNozzleFlowType()) { + const wxString& pos = _get_nozzle_name(ext_sys->GetTotalExtderCount(), slicing_ext_idx); + const wxString& installed_nozzle_str = installed_ext_nozzle.GetNozzleFlowTypeStr(); + const wxString& slicing_nozzle_str = DevNozzle::GetNozzleFlowTypeStr(slicing_ext.nozzle_flow_type); + wxString error_message = wxString::Format(_L("The nozzle flow setting of %s(%s) doesn't match with the slicing file(%s). " + "Please make sure the nozzle installed matches with settings in printer, " + "then set the corresponding printer preset while slicing."), + pos, installed_nozzle_str, slicing_nozzle_str); + + std::vector params{ error_message }; + params.emplace_back(_L("Tips: If you changed your nozzle of your printer lately, Please go to 'Device -> Printer parts' to change your nozzle setting.")); + show_status(PrintDialogStatus::PrintStatusNozzleMatchInvalid, params); + return false; + } + } + + // check nozzle diameter + { + if (slicing_ext.nozzle_diameter != installed_ext_nozzle.GetNozzleDiameter()) { + std::vector msg_params; + if (ext_sys->GetTotalExtderCount() == 2) { + const wxString& mismatch_nozzle_str = _get_nozzle_name(ext_sys->GetTotalExtderCount(), slicing_ext_idx); + const wxString& nozzle_message = wxString::Format(_L("The %s diameter(%.1fmm) of current printer doesn't match with the slicing file (%.1fmm). " + "Please make sure the nozzle installed matches with settings in printer, then set the " + "corresponding printer preset when slicing."), + mismatch_nozzle_str, installed_ext_nozzle.GetNozzleDiameter(), slicing_ext.nozzle_diameter); + msg_params.emplace_back(nozzle_message); + } else { + const wxString& nozzle_message = wxString::Format(_L("The current nozzle diameter (%.1fmm) doesn't match with the slicing file (%.1fmm). " + "Please make sure the nozzle installed matches with settings in printer, then set the " + "corresponding printer preset when slicing."), + installed_ext_nozzle.GetNozzleDiameter(), slicing_ext.nozzle_diameter); + msg_params.emplace_back(nozzle_message); + } + + msg_params.emplace_back(_L("Tips: If you changed your nozzle of your printer lately, Please go to 'Device -> Printer parts' to change your nozzle setting.")); + show_status(PrintDialogStatus::PrintStatusNozzleDiameterMismatch, msg_params); + return false; + } + } + } + } + + return true; +} + static wxString _get_ext_loc_str(const std::unordered_set& extruders, int total_ext_num) { assert(!extruders.empty()); @@ -3810,59 +3883,24 @@ void SelectMachineDialog::update_show_status(MachineObject* obj_) const auto &full_config = wxGetApp().preset_bundle->full_config(); size_t nozzle_nums = full_config.option("nozzle_diameter")->values.size(); - /*the nozzle type of preset and machine are different*/ - if (nozzle_nums > 1 && m_print_type == FROM_NORMAL) { - if (!_is_nozzle_data_valid(obj_, *obj_->GetExtderSystem())) { - show_status(PrintDialogStatus::PrintStatusNozzleDataInvalid); - return; - } + // Filament Track Switch: warn on a slicing/hardware mismatch, and block Send when dynamic + // nozzle mapping needs a switch that isn't installed or set up. No-op for printers without one. + if (!CheckErrorDynamicSwitchNozzle(obj_)) return; - wxString error_message; - if (!is_nozzle_type_match(*obj_->GetExtderSystem(), error_message)) { - std::vector params{error_message}; - params.emplace_back(_L("Tips: If you changed your nozzle of your printer lately, Please go to 'Device -> Printer parts' to change your nozzle setting.")); - show_status(PrintDialogStatus::PrintStatusNozzleMatchInvalid, params); - return; - } - } + // Block Send while a rack printer is still reading its hotend information. + if (!CheckErrorRackStatus(obj_)) return; + + // Compare the slicing file's nozzle requirements (validity, flow, diameter) against the printer. + if (!CheckErrorExtruderNozzleWithSlicing(obj_)) return; - // check nozzle type and diameter if (m_print_type == PrintFromType::FROM_NORMAL) { - int mismatch_nozzle_id = 0; - float nozzle_diameter = 0; - if (!_is_same_nozzle_diameters(obj_, nozzle_diameter, mismatch_nozzle_id)) - { - std::vector msg_params; - if (obj_->GetExtderSystem()->GetTotalExtderCount() == 2) { - wxString mismatch_nozzle_str; - if (mismatch_nozzle_id == MAIN_EXTRUDER_ID) { - mismatch_nozzle_str = _L("right nozzle"); - } else { - mismatch_nozzle_str = _L("left nozzle"); - } - - const wxString &nozzle_config = wxString::Format(_L("The %s diameter(%.1fmm) of current printer doesn't match with the slicing file (%.1fmm). " - "Please make sure the nozzle installed matches with settings in printer, then set the " - "corresponding printer preset when slicing."), - mismatch_nozzle_str, obj_->GetExtderSystem()->GetNozzleDiameter(mismatch_nozzle_id), nozzle_diameter); - msg_params.emplace_back(nozzle_config); - } else { - const wxString &nozzle_config = wxString::Format(_L("The current nozzle diameter (%.1fmm) doesn't match with the slicing file (%.1fmm). " - "Please make sure the nozzle installed matches with settings in printer, then set the " - "corresponding printer preset when slicing."), - obj_->GetExtderSystem()->GetNozzleDiameter(0), nozzle_diameter); - msg_params.emplace_back(nozzle_config); - } - - msg_params.emplace_back(_L("Tips: If you changed your nozzle of your printer lately, Please go to 'Device -> Printer parts' to change your nozzle setting.")); - show_status(PrintDialogStatus::PrintStatusNozzleDiameterMismatch, msg_params); - return; - } - + // Orca: blocking hardness gate on the mounted nozzles; the rack extruder is instead judged + // per dispatch-mapped nozzle in the blacklist loop below, as a non-blocking caution. const auto &used_nozzle_idxes = _get_used_nozzle_idxes(); for (const auto &extder : obj_->GetExtderSystem()->GetExtruders()) { if (used_nozzle_idxes.count(extder.GetNozzleId()) == 0) { continue; } + if (_is_rack_managed_nozzle(obj_, extder.GetNozzleId())) { continue; } std::string filament_type; if (!is_nozzle_hrc_matched(&extder, filament_type)) { @@ -3875,10 +3913,6 @@ void SelectMachineDialog::update_show_status(MachineObject* obj_) } } - // Filament Track Switch: warn on a slicing/hardware mismatch, and block Send when dynamic - // nozzle mapping needs a switch that isn't installed or set up. No-op for printers without one. - if (!CheckErrorDynamicSwitchNozzle(obj_)) return; - // Rack print-dispatch nozzle mapping (dynamic V1): request/await the printer's mapping and block // Send while it computes. No-op for non-rack printers. if (!CheckErrorSyncNozzleMappingResultV1(obj_)) return; @@ -3968,6 +4002,25 @@ void SelectMachineDialog::update_show_status(MachineObject* obj_) show_status(PrintDialogStatus::PrintStatusHasFilamentInBlackListWarning, {item.info_msg}, item.wiki_url);/** warning check **/ } } + + // A rack-managed nozzle skipped the mounted-nozzle hardness gate above; judge the + // filament's hardness against the nozzle the print-dispatch mapping assigned. Caution + // only: the printer picks the swap target, so Send stays enabled. + if (n < mapped_nozzles.size()) { + const DevNozzle &nozzle = mapped_nozzles[n]; + if (_is_rack_managed_nozzle(obj_, nozzle.GetExtruderId()) && nozzle.GetNozzleType() != NozzleType::ntUndefine) { + const int nozzle_hrc = Print::get_hrc_by_nozzle_type(nozzle.GetNozzleType()); + const int filament_hrc = wxGetApp().preset_bundle->get_required_hrc_by_filament_type(filament_type); + if (abs(filament_hrc) > abs(nozzle_hrc)) { + const int pos_id = nozzle.GetNozzlePosId(); + const wxString nozzle_str = (pos_id < 0x10) ? wxString("R") : wxString::Format("R%d", pos_id - 0x10 + 1); + show_status(PrintDialogStatus::PrintStatusFilamentWarningNozzleHRC, + {wxString::Format(_L("The hardness of current material (%s) exceeds the hardness of %s(%s). It may cause nozzle wear, " + "leading to material leakage and unstable flow. Please exercise caution when using it."), + filament_type, nozzle_str, format_steel_name(nozzle.GetNozzleType()))}); + } + } + } } } diff --git a/src/slic3r/GUI/SelectMachine.hpp b/src/slic3r/GUI/SelectMachine.hpp index f22bbda760..6d310375e0 100644 --- a/src/slic3r/GUI/SelectMachine.hpp +++ b/src/slic3r/GUI/SelectMachine.hpp @@ -508,7 +508,6 @@ public: bool build_nozzles_info(std::string& nozzles_info); bool can_hybrid_mapping(DevExtderSystem data); void auto_supply_with_ext(std::vector slots); - bool is_nozzle_type_match(DevExtderSystem data, wxString& error_message) const; int convert_filament_map_nozzle_id_to_task_nozzle_id(int nozzle_id) const; // Physical nozzle(s) a mapped filament (by filament index / FilamentInfo::id) prints on. @@ -520,6 +519,12 @@ public: // printer. Empty for printers with neither (the card then shows no nozzle row). wxString get_mapped_nozzle_str(int fila_id) const; + // Block Send while a rack printer is still reading its hotend information. + bool CheckErrorRackStatus(MachineObject* obj_); + // Compare the slicing file's nozzle requirements (validity, flow, diameter) against the + // printer; the rack extruder is checked against its whole inventory (mounted + rack). + bool CheckErrorExtruderNozzleWithSlicing(MachineObject* obj_);//return true if no errors + // Rack print-dispatch nozzle mapping (H2C): request the printer's auto-mapping and consume the // result, gating the Send button while the printer computes. Both are no-ops for non-rack printers. bool CheckErrorSyncNozzleMappingResultV1(MachineObject* obj_);