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).
This commit is contained in:
SoftFever
2026-07-13 00:49:16 +08:00
parent e98750e713
commit 2fecfc291f
9 changed files with 350 additions and 231 deletions

View File

@@ -157,4 +157,27 @@ enum PrintFromType
};
}
};// namespace Slic3r
};// 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<NozzleDef>
{
std::size_t operator()(const NozzleDef& v) const noexcept
{
size_t h1 = std::hash<int>{}(v.nozzle_diameter * 1000);
size_t h2 = std::hash<int>{}(v.nozzle_flow_type);
return h1 ^ (h2 + 0x9e3779b9 + (h1 << 6) + (h1 >> 2));
};
};

View File

@@ -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
{

View File

@@ -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<int>& 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<int> PartPlate::get_used_filaments()
{
std::vector<int> used_filaments;

View File

@@ -350,6 +350,7 @@ public:
std::vector<int> get_used_filaments();
const std::vector<FilamentInfo>& 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<int> &tpu_filaments);
bool check_mixture_of_pla_and_petg(const DynamicPrintConfig & config);

View File

@@ -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";

View File

@@ -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<-

View File

@@ -38,6 +38,7 @@
#include <wx/mstream.h>
#include <miniz.h>
#include <algorithm>
#include <unordered_map>
#include "Plater.hpp"
#include "Notebook.hpp"
#include "BitmapCache.hpp"
@@ -1393,103 +1394,6 @@ void SelectMachineDialog::auto_supply_with_ext(std::vector<DevAmsTray> 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<int, std::pair<NozzleFlowType, float>> used_extruders_flow;
std::vector<int> 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<const ConfigOptionEnumsGeneric *>(wxGetApp().preset_bundle->project_config.option("nozzle_volume_type"));
auto nozzle_diameter_opt = wxGetApp().preset_bundle->printers.get_edited_preset().config.option<ConfigOptionFloats>("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<int> 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<wxSt
// Advisory slicing/hardware mismatch: allow Send.
Enable_Refresh_Button(true);
Enable_Send_Button(true);
} else if (status == PrintDialogStatus::PrintStatusFilamentWarningNozzleHRC) {
// Hardness caution against a rack-mapped nozzle: allow Send.
Enable_Refresh_Button(true);
Enable_Send_Button(true);
} else if (status == PrintDialogStatus::PrintStatusNozzleNoMatchedHotends) {
Enable_Refresh_Button(true);
Enable_Send_Button(false);
} else if (status == PrintDialogStatus::PrintStatusNozzleRackMaximumInstalled) {
Enable_Refresh_Button(true);
Enable_Send_Button(false);
} else if (status == PrintDialogStatus::PrintStatusRackReading) {
// Rack hotend info is being read: block Send until it finishes.
Enable_Refresh_Button(true);
Enable_Send_Button(false);
}
/*enter perpare mode*/
@@ -2165,84 +2083,17 @@ static std::unordered_set<int> _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<ConfigOptionFloats>("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<int, NozzleDef> s_get_slicing_extuder_nozzles()
{
std::unordered_multimap<int, NozzleDef> 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<ConfigOptionFloats>("nozzle_diameter");
if (!opt_nozzle_diameters) {
return used_extuder_nozzles;
}
auto nozzle_volume_type_opt = dynamic_cast<const ConfigOptionEnumsGeneric*>(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<wxString> 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<wxString> 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<int>& 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<ConfigOptionFloats>("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<wxString> 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<wxString> 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()))});
}
}
}
}
}

View File

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