mirror of
https://github.com/OrcaSlicer/OrcaSlicer.git
synced 2026-07-26 12:22:32 +00:00
feat(gui): enable Hybrid nozzle flow for multi-sub-nozzle extruders
An extruder with more than one physical sub-nozzle can hold a mix of Standard and High Flow nozzles. The Flow dropdown now offers Hybrid for such extruders (extruder_max_nozzle_count > 1, nil-guarded); grouping already expands a Hybrid extruder into per-volume nozzle groups from extruder_nozzle_stats. - sidebar Flow combo offers Hybrid only for multi-sub-nozzle extruders - preset lookup treats Hybrid as Standard (presets define no Hybrid variant); variant strings are never fabricated for it - printer tab splits a Hybrid extruder into Standard + High Flow rows, with matching selection-index arithmetic and sync-enable rules - syncing from a printer whose extruder holds mixed nozzle flows now selects Hybrid instead of collapsing to the dominant flow type - send-to-printer flow check: a nozzle-rack extruder validates its nozzle inventory (mounted + rack) against every needed flow instead of comparing only the mounted nozzle; mounted-flow lookup is now per-extruder, fixing an index shift when a nozzle reports no flow - Hybrid is session-only in app config (stored as Standard), so a fresh session starts from concrete flow types Printers whose extruders have a single sub-nozzle (including all dual-extruder machines without a rack) see no new option and identical check behavior.
This commit is contained in:
@@ -1592,9 +1592,10 @@ std::optional<NozzleOption> Sidebar::priv::get_nozzle_options(MachineObject* obj
|
||||
|
||||
if (!nozzle_option->extruder_nozzle_stats.count(extruder_id)) {
|
||||
nozzle_count = 0;
|
||||
// Reset the concrete volume types (Standard/High Flow); Hybrid stays a hidden match-any
|
||||
// sentinel and is left alone. TPU High Flow is a concrete variant shipped on 0.4/0.6
|
||||
// nozzles, so reset it too, but only there (mirrors the High-Flow-skip-for-0.2 guard).
|
||||
// Reset the concrete volume types (Standard/High Flow); Hybrid is not a physical
|
||||
// nozzle type and never appears in the stats. TPU High Flow is a concrete variant
|
||||
// shipped on 0.4/0.6 nozzles, so reset it too, but only there (mirrors the
|
||||
// High-Flow-skip-for-0.2 guard).
|
||||
std::vector<NozzleVolumeType> reset_types{nvtStandard, nvtHighFlow};
|
||||
if (extruder_supports_tpu_high_flow(preset_bundle, extruder_id))
|
||||
reset_types.push_back(nvtTPUHighFlow);
|
||||
@@ -1816,27 +1817,11 @@ bool Sidebar::priv::sync_extruder_list(bool &only_external_material, bool is_man
|
||||
std::optional<NozzleVolumeType> select_type;
|
||||
if (nozzle_option && nozzle_option->extruder_nozzle_stats.count(index)) {
|
||||
const auto &stats = nozzle_option->extruder_nozzle_stats[index];
|
||||
if (stats.size() > 1) {
|
||||
// Orca: for a mixed extruder, keep Hybrid out of the (still-deferred) nozzle-centric slicing
|
||||
// pipeline, so collapse to the dominant concrete volume type (tie -> Standard).
|
||||
// TPU High Flow is shipped on 0.4/0.6 nozzles, so only fold it in there (mirrors the
|
||||
// High-Flow-skip-for-0.2 guard). Uses the device-reported diameter, same predicate as the preset path.
|
||||
int std_cnt = 0, hf_cnt = 0, tpu_hf_cnt = 0;
|
||||
for (const auto &kv : stats) {
|
||||
if (kv.first == nvtStandard) std_cnt = kv.second;
|
||||
else if (kv.first == nvtHighFlow) hf_cnt = kv.second;
|
||||
else if (kv.first == nvtTPUHighFlow) tpu_hf_cnt = kv.second;
|
||||
}
|
||||
if (!nozzle_diameter_supports_tpu_high_flow(nozzle_diameters[extruder_id]))
|
||||
tpu_hf_cnt = 0;
|
||||
select_type = nvtStandard;
|
||||
if (hf_cnt > std_cnt && hf_cnt >= tpu_hf_cnt)
|
||||
select_type = nvtHighFlow;
|
||||
else if (tpu_hf_cnt > std_cnt && tpu_hf_cnt > hf_cnt)
|
||||
select_type = nvtTPUHighFlow;
|
||||
} else {
|
||||
if (stats.size() > 1)
|
||||
// The extruder holds nozzles of several flow types: select the mixed-flow mode.
|
||||
select_type = NozzleVolumeType::nvtHybrid;
|
||||
else
|
||||
select_type = stats.begin()->first;
|
||||
}
|
||||
}
|
||||
if (obj->is_nozzle_flow_type_supported()) {
|
||||
if (obj->GetExtderSystem()->GetNozzleFlowType(index) == NozzleFlowType::NONE_FLOWTYPE) {
|
||||
@@ -3292,8 +3277,12 @@ void Sidebar::update_presets(Preset::Type preset_type)
|
||||
auto type = extruders_def->enum_labels[extruders->values[index]];
|
||||
int select = -1;
|
||||
for (size_t i = 0; i < nozzle_volumes_def->enum_labels.size(); ++i) {
|
||||
if (boost::algorithm::contains(extruder_variants->values[index], type + " " + nozzle_volumes_def->enum_labels[i]) /*||
|
||||
extruder_max_nozzle_count->values[index] > 1 && nozzle_volumes_def->enum_keys_map->at(nozzle_volumes_def->enum_values[i]) == nvtHybrid*/) { // TODO: Orca: Support hybrid
|
||||
// get_at falls back to the first entry when a profile defines no per-extruder value,
|
||||
// so extruders without an explicit sub-nozzle count never offer Hybrid. A nullable-int
|
||||
// nil is INT_MAX (> 1) and would otherwise falsely pass the gate, so exclude it too.
|
||||
if (boost::algorithm::contains(extruder_variants->values[index], type + " " + nozzle_volumes_def->enum_labels[i]) ||
|
||||
extruder_max_nozzle_count->get_at(index) > 1 && extruder_max_nozzle_count->get_at(index) != ConfigOptionIntsNullable::nil_value() &&
|
||||
nozzle_volumes_def->enum_keys_map->at(nozzle_volumes_def->enum_values[i]) == nvtHybrid) {
|
||||
if (nozzle_volumes_def->enum_keys_map->at(nozzle_volumes_def->enum_values[i]) == NozzleVolumeType::nvtHighFlow &&(diameter == "0.2" ||
|
||||
is_skip_high_flow_printer(printer_model)))
|
||||
continue;
|
||||
|
||||
@@ -1401,7 +1401,9 @@ bool SelectMachineDialog::is_nozzle_type_match(DevExtderSystem data, wxString& e
|
||||
//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
|
||||
std::map<int, std::string> used_extruders_flow;
|
||||
// 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;
|
||||
@@ -1411,60 +1413,78 @@ bool SelectMachineDialog::is_nozzle_type_match(DevExtderSystem data, wxString& e
|
||||
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]));
|
||||
if (nozzle_volume_type == NozzleVolumeType::nvtStandard) { used_extruders_flow[used_extruders[i]] = "Standard";}
|
||||
else if (nozzle_volume_type == NozzleVolumeType::nvtTPUHighFlow) { used_extruders_flow[used_extruders[i]] = "TPU High Flow";}
|
||||
else {used_extruders_flow[used_extruders[i]] = "High Flow";}
|
||||
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};
|
||||
|
||||
|
||||
// The default two extruders are left, right, but the order of the extruders on the machine is right, left.
|
||||
std::vector<std::string> flow_type_of_machine;
|
||||
for (const auto& it : data.GetExtruders())
|
||||
{
|
||||
if (it.GetNozzleFlowType() == NozzleFlowType::H_FLOW)
|
||||
{
|
||||
flow_type_of_machine.push_back(L("High Flow"));
|
||||
}
|
||||
else if (it.GetNozzleFlowType() == NozzleFlowType::S_FLOW)
|
||||
{
|
||||
flow_type_of_machine.push_back(L("Standard"));
|
||||
}
|
||||
else if (it.GetNozzleFlowType() == NozzleFlowType::U_FLOW)
|
||||
{
|
||||
flow_type_of_machine.push_back(L("TPU High Flow"));
|
||||
}
|
||||
}
|
||||
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 (std::map<int, std::string>::iterator it = used_extruders_flow.begin(); it!= used_extruders_flow.end(); it++) {
|
||||
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;
|
||||
|
||||
if (target_machine_nozzle_id < flow_type_of_machine.size()) {
|
||||
if (flow_type_of_machine[target_machine_nozzle_id] != used_extruders_flow[it->first]) {
|
||||
|
||||
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,
|
||||
_L(flow_type_of_machine[target_machine_nozzle_id]),
|
||||
_L(used_extruders_flow[it->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;
|
||||
|
||||
@@ -616,26 +616,25 @@ void Tab::parse_extruder_selection(int selection, int &extruder_id, NozzleVolume
|
||||
for (int i = 0; i < extruder_nums; ++i) {
|
||||
NozzleVolumeType volume_type = NozzleVolumeType(nozzle_volumes->values[i]);
|
||||
|
||||
// TODO: Orca: Support hybrid
|
||||
//if (volume_type == NozzleVolumeType::nvtHybrid) {
|
||||
// if (selection == current_index) {
|
||||
// extruder_id = i;
|
||||
// nozzle_type = NozzleVolumeType::nvtStandard;
|
||||
// return;
|
||||
// } else if (selection == current_index + 1) {
|
||||
// extruder_id = i;
|
||||
// nozzle_type = NozzleVolumeType::nvtHighFlow;
|
||||
// return;
|
||||
// }
|
||||
// current_index += 2;
|
||||
//} else {
|
||||
if (volume_type == NozzleVolumeType::nvtHybrid) {
|
||||
if (selection == current_index) {
|
||||
extruder_id = i;
|
||||
nozzle_type = NozzleVolumeType::nvtStandard;
|
||||
return;
|
||||
} else if (selection == current_index + 1) {
|
||||
extruder_id = i;
|
||||
nozzle_type = NozzleVolumeType::nvtHighFlow;
|
||||
return;
|
||||
}
|
||||
current_index += 2;
|
||||
} else {
|
||||
if (selection == current_index) {
|
||||
extruder_id = i;
|
||||
nozzle_type = volume_type;
|
||||
return;
|
||||
}
|
||||
current_index += 1;
|
||||
//}
|
||||
}
|
||||
}
|
||||
|
||||
extruder_id = 0;
|
||||
@@ -651,17 +650,16 @@ int Tab::calculate_selection_index_for_extruder(int extruder_id, NozzleVolumeTyp
|
||||
|
||||
for (int i = 0; i < extruder_nums; ++i) {
|
||||
if (i == extruder_id) {
|
||||
// TODO: Orca: Support hybrid
|
||||
NozzleVolumeType volume_type = NozzleVolumeType(nozzle_volumes->values[i]);
|
||||
/*if (volume_type == NozzleVolumeType::nvtHybrid) {
|
||||
if (volume_type == NozzleVolumeType::nvtHybrid) {
|
||||
return nozzle_type == NozzleVolumeType::nvtHighFlow ? index + 1 : index;
|
||||
} else*/ {
|
||||
} else {
|
||||
return index;
|
||||
}
|
||||
}
|
||||
|
||||
NozzleVolumeType volume_type = NozzleVolumeType(nozzle_volumes->values[i]);
|
||||
index += /*(volume_type == NozzleVolumeType::nvtHybrid) ? 2 :*/ 1;
|
||||
index += (volume_type == NozzleVolumeType::nvtHybrid) ? 2 : 1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
@@ -7557,8 +7555,14 @@ void TabPrinter::set_extruder_volume_type(int extruder_id, NozzleVolumeType type
|
||||
|
||||
//save to app config
|
||||
if (!m_base_preset_name.empty()) {
|
||||
ConfigOptionEnumsGeneric* nozzle_volume_type_option = m_preset_bundle->project_config.option<ConfigOptionEnumsGeneric>("nozzle_volume_type");
|
||||
std::string nozzle_volume_type_str = nozzle_volume_type_option->serialize();
|
||||
// do not save hybrid flow status to config: it depends on the nozzles currently installed
|
||||
// on the connected printer, so it must not be restored in a later session
|
||||
ConfigOptionEnumsGeneric nozzle_volume_type_option = *m_preset_bundle->project_config.option<ConfigOptionEnumsGeneric>("nozzle_volume_type");
|
||||
for (size_t i = 0; i < nozzle_volume_type_option.values.size(); i++) {
|
||||
if (nozzle_volume_type_option.values[i] == (int) nvtHybrid)
|
||||
nozzle_volume_type_option.values[i] = (int) nvtStandard;
|
||||
}
|
||||
std::string nozzle_volume_type_str = nozzle_volume_type_option.serialize();
|
||||
wxGetApp().app_config->save_nozzle_volume_types_to_config(m_base_preset_name, nozzle_volume_type_str);
|
||||
}
|
||||
|
||||
@@ -7888,12 +7892,11 @@ std::vector<wxString> Tab::generate_extruder_options()
|
||||
wxString extruder_name = _L(DevPrinterConfigUtil::get_toolhead_display_name(
|
||||
pt, ext_id, ToolHeadComponent::Nozzle, ToolHeadNameCase::TitleCase, true));
|
||||
NozzleVolumeType volume_type = NozzleVolumeType(nozzle_volumes->values[i]);
|
||||
|
||||
// TODO: Orca: Support hybrid
|
||||
/*if (volume_type == NozzleVolumeType::nvtHybrid) {
|
||||
|
||||
if (volume_type == NozzleVolumeType::nvtHybrid) {
|
||||
options.push_back(wxString::Format(_L("%s: %s"), extruder_name, _L("Standard")));
|
||||
options.push_back(wxString::Format(_L("%s: %s"), extruder_name, _L("High Flow")));
|
||||
} else*/ {
|
||||
} else {
|
||||
wxString volume_name = get_nozzle_volume_type_name(volume_type);
|
||||
options.push_back(wxString::Format(_L("%s: %s"), extruder_name, volume_name));
|
||||
}
|
||||
@@ -7942,36 +7945,35 @@ bool Tab::get_extruder_sync_enable_state(int extruder_id)
|
||||
if (left_nozzle == right_nozzle) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// TODO: Orca: Support hybrid
|
||||
//if (left_nozzle != NozzleVolumeType::nvtHybrid && right_nozzle != NozzleVolumeType::nvtHybrid) {
|
||||
// return false;
|
||||
//}
|
||||
|
||||
//if (left_nozzle == NozzleVolumeType::nvtHybrid && right_nozzle == NozzleVolumeType::nvtHybrid) {
|
||||
// return true;
|
||||
//}
|
||||
if (left_nozzle != NozzleVolumeType::nvtHybrid && right_nozzle != NozzleVolumeType::nvtHybrid) {
|
||||
return false;
|
||||
}
|
||||
|
||||
//// Hybrid rules
|
||||
//auto current_nozzle = get_actual_nozzle_volume_type(extruder_id);
|
||||
//if (left_nozzle != NozzleVolumeType::nvtHybrid) {
|
||||
// if (extruder_id == 0) {
|
||||
// return true;
|
||||
// }
|
||||
// if (extruder_id == 1 && current_nozzle == left_nozzle) {
|
||||
// return true;
|
||||
// }
|
||||
// return false;
|
||||
//}
|
||||
//if (right_nozzle != NozzleVolumeType::nvtHybrid) {
|
||||
// if (extruder_id == 1) {
|
||||
// return true;
|
||||
// }
|
||||
// if (extruder_id == 0 && current_nozzle == right_nozzle) {
|
||||
// return true;
|
||||
// }
|
||||
// return false;
|
||||
//}
|
||||
if (left_nozzle == NozzleVolumeType::nvtHybrid && right_nozzle == NozzleVolumeType::nvtHybrid) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Hybrid rules
|
||||
auto current_nozzle = get_actual_nozzle_volume_type(extruder_id);
|
||||
if (left_nozzle != NozzleVolumeType::nvtHybrid) {
|
||||
if (extruder_id == 0) {
|
||||
return true;
|
||||
}
|
||||
if (extruder_id == 1 && current_nozzle == left_nozzle) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
if (right_nozzle != NozzleVolumeType::nvtHybrid) {
|
||||
if (extruder_id == 1) {
|
||||
return true;
|
||||
}
|
||||
if (extruder_id == 0 && current_nozzle == right_nozzle) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user