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_);