Add multi-variant support to the app

This commit is contained in:
SoftFever
2026-09-28 19:08:35 +08:00
parent 2ccabb5695
commit 71bb400e46
27 changed files with 846 additions and 195 deletions
+8 -4
View File
@@ -199,7 +199,8 @@ DynamicPrintConfig PresetBundle::construct_full_config(
if (num_filaments <= 1) { if (num_filaments <= 1) {
// BBS: update filament config related with variants // BBS: update filament config related with variants
DynamicPrintConfig filament_config = in_filament_presets[0].config; DynamicPrintConfig filament_config = in_filament_presets[0].config;
if (apply_extruder && ((extruder_count > 1) || different_extruder)) // Orca: a multi-variant filament resolves its variants on a single-variant printer too.
if (apply_extruder && ((extruder_count > 1) || different_extruder || filament_config.has_multi_variant_filament()))
filament_config.update_values_to_printer_extruders(out, extruder_count, extruder_volume_type_count, nozzle_volume_types, filament_options_with_variant, "", "filament_extruder_variant", 1, filament_maps[0], (NozzleVolumeType)filament_volume_maps[0]); filament_config.update_values_to_printer_extruders(out, extruder_count, extruder_volume_type_count, nozzle_volume_types, filament_options_with_variant, "", "filament_extruder_variant", 1, filament_maps[0], (NozzleVolumeType)filament_volume_maps[0]);
out.apply(filament_config); out.apply(filament_config);
compatible_printers_condition.emplace_back(in_filament_presets[0].compatible_printers_condition()); compatible_printers_condition.emplace_back(in_filament_presets[0].compatible_printers_condition());
@@ -223,7 +224,8 @@ DynamicPrintConfig PresetBundle::construct_full_config(
filament_temp_configs.resize(num_filaments); filament_temp_configs.resize(num_filaments);
for (size_t i = 0; i < num_filaments; ++i) { for (size_t i = 0; i < num_filaments; ++i) {
filament_temp_configs[i] = *(filament_configs[i]); filament_temp_configs[i] = *(filament_configs[i]);
if (apply_extruder && ((extruder_count > 1) || different_extruder)) // Orca: a multi-variant filament resolves its variants on a single-variant printer too.
if (apply_extruder && ((extruder_count > 1) || different_extruder || filament_temp_configs[i].has_multi_variant_filament()))
filament_temp_configs[i].update_values_to_printer_extruders(out, extruder_count, extruder_volume_type_count, nozzle_volume_types, filament_options_with_variant, "", "filament_extruder_variant", 1, filament_maps[i], (NozzleVolumeType)filament_volume_maps[i]); filament_temp_configs[i].update_values_to_printer_extruders(out, extruder_count, extruder_volume_type_count, nozzle_volume_types, filament_options_with_variant, "", "filament_extruder_variant", 1, filament_maps[i], (NozzleVolumeType)filament_volume_maps[i]);
} }
@@ -4664,7 +4666,8 @@ DynamicPrintConfig PresetBundle::full_fff_config(bool apply_extruder, std::optio
if (num_filaments <= 1) { if (num_filaments <= 1) {
//BBS: update filament config related with variants //BBS: update filament config related with variants
DynamicPrintConfig filament_config = this->filaments.get_edited_preset().config; DynamicPrintConfig filament_config = this->filaments.get_edited_preset().config;
if (apply_extruder && ((extruder_count > 1) || different_extruder)) // Orca: a multi-variant filament resolves its variants on a single-variant printer too.
if (apply_extruder && ((extruder_count > 1) || different_extruder || filament_config.has_multi_variant_filament()))
filament_config.update_values_to_printer_extruders(out, extruder_count, extruder_volume_type_count, nozzle_volume_types, filament_options_with_variant, "", "filament_extruder_variant", 1, filament_maps[0], (NozzleVolumeType)filament_volume_maps[0]); filament_config.update_values_to_printer_extruders(out, extruder_count, extruder_volume_type_count, nozzle_volume_types, filament_options_with_variant, "", "filament_extruder_variant", 1, filament_maps[0], (NozzleVolumeType)filament_volume_maps[0]);
out.apply(filament_config); out.apply(filament_config);
compatible_printers_condition.emplace_back(this->filaments.get_edited_preset().compatible_printers_condition()); compatible_printers_condition.emplace_back(this->filaments.get_edited_preset().compatible_printers_condition());
@@ -4758,7 +4761,8 @@ DynamicPrintConfig PresetBundle::full_fff_config(bool apply_extruder, std::optio
filament_temp_configs.resize(num_filaments); filament_temp_configs.resize(num_filaments);
for (size_t i = 0; i < num_filaments; ++i) { for (size_t i = 0; i < num_filaments; ++i) {
filament_temp_configs[i] = *(filament_configs[i]); filament_temp_configs[i] = *(filament_configs[i]);
if (apply_extruder && ((extruder_count > 1) || different_extruder)) // Orca: a multi-variant filament resolves its variants on a single-variant printer too.
if (apply_extruder && ((extruder_count > 1) || different_extruder || filament_temp_configs[i].has_multi_variant_filament()))
filament_temp_configs[i].update_values_to_printer_extruders(out, extruder_count, extruder_volume_type_count, nozzle_volume_types, filament_options_with_variant, "", "filament_extruder_variant", 1, filament_maps[i], (NozzleVolumeType)filament_volume_maps[i]); filament_temp_configs[i].update_values_to_printer_extruders(out, extruder_count, extruder_volume_type_count, nozzle_volume_types, filament_options_with_variant, "", "filament_extruder_variant", 1, filament_maps[i], (NozzleVolumeType)filament_volume_maps[i]);
} }
+5 -2
View File
@@ -3938,6 +3938,9 @@ void Print::update_filament_maps_to_config(std::vector<int> f_maps, std::vector<
{ {
int extruder_count = 1, extruder_volume_type_count = 1; int extruder_count = 1, extruder_volume_type_count = 1;
bool support_multi = m_ori_full_print_config.support_different_extruders(extruder_count); bool support_multi = m_ori_full_print_config.support_different_extruders(extruder_count);
// Orca: resolve the filament variants wherever Print::apply does, a multi-variant filament
// on a single-variant printer included.
const bool expand_filaments = (extruder_count > 1) || support_multi || m_ori_full_print_config.has_multi_variant_filament();
std::vector<std::vector<NozzleVolumeType>> nozzle_volume_types; std::vector<std::vector<NozzleVolumeType>> nozzle_volume_types;
extruder_volume_type_count = m_ori_full_print_config.get_extruder_nozzle_volume_count(extruder_count, nozzle_volume_types); extruder_volume_type_count = m_ori_full_print_config.get_extruder_nozzle_volume_count(extruder_count, nozzle_volume_types);
@@ -3978,7 +3981,7 @@ void Print::update_filament_maps_to_config(std::vector<int> f_maps, std::vector<
m_full_print_config = m_ori_full_print_config; m_full_print_config = m_ori_full_print_config;
std::set<std::string> filament_keys = filament_options_with_variant; std::set<std::string> filament_keys = filament_options_with_variant;
filament_keys.insert("filament_self_index"); filament_keys.insert("filament_self_index");
if ((extruder_count > 1) || support_multi) if (expand_filaments)
m_full_print_config.update_values_to_printer_extruders_for_multiple_filaments(m_full_print_config, extruder_count, extruder_volume_type_count, filament_keys, "filament_self_index", "filament_extruder_variant"); m_full_print_config.update_values_to_printer_extruders_for_multiple_filaments(m_full_print_config, extruder_count, extruder_volume_type_count, filament_keys, "filament_self_index", "filament_extruder_variant");
const std::vector<std::string> &extruder_retract_keys = print_config_def.extruder_retract_keys(); const std::vector<std::string> &extruder_retract_keys = print_config_def.extruder_retract_keys();
@@ -3995,7 +3998,7 @@ void Print::update_filament_maps_to_config(std::vector<int> f_maps, std::vector<
compute_filament_override_value(opt_key, opt_old_machine, opt_new_machine, opt_new_filament, m_full_print_config, print_diff, filament_overrides, m_config.filament_map_2.values); compute_filament_override_value(opt_key, opt_old_machine, opt_new_machine, opt_new_filament, m_full_print_config, print_diff, filament_overrides, m_config.filament_map_2.values);
} }
if ((extruder_count > 1) || support_multi) { if (expand_filaments) {
t_config_option_keys keys(filament_options_with_variant.begin(), filament_options_with_variant.end()); t_config_option_keys keys(filament_options_with_variant.begin(), filament_options_with_variant.end());
keys.push_back("filament_self_index"); keys.push_back("filament_self_index");
m_config.apply_only(m_full_print_config, keys, true); m_config.apply_only(m_full_print_config, keys, true);
+3 -1
View File
@@ -1296,7 +1296,9 @@ Print::ApplyStatus Print::apply(const Model &model, DynamicPrintConfig new_full_
extruder_count, extruder_volume_type_count, filament_keys, extruder_count, extruder_volume_type_count, filament_keys,
"filament_self_index", "filament_extruder_variant", "filament_self_index", "filament_extruder_variant",
&dynamic_slot_indices); &dynamic_slot_indices);
else if ((extruder_count > 1) || different_extruder) // Orca: also on a printer with a single extruder variant once a filament defines several
// (e.g. Standard and High Flow), so each filament takes its variant for that extruder.
else if ((extruder_count > 1) || different_extruder || new_full_config.has_multi_variant_filament())
new_full_config.update_values_to_printer_extruders_for_multiple_filaments(m_ori_full_print_config, extruder_count, extruder_volume_type_count, filament_keys, new_full_config.update_values_to_printer_extruders_for_multiple_filaments(m_ori_full_print_config, extruder_count, extruder_volume_type_count, filament_keys,
"filament_self_index", "filament_extruder_variant"); "filament_self_index", "filament_extruder_variant");
} }
+51 -6
View File
@@ -626,7 +626,8 @@ static const t_config_enum_values s_keys_map_NozzleVolumeType = {
{ "Standard", nvtStandard }, { "Standard", nvtStandard },
{ "High Flow", nvtHighFlow }, { "High Flow", nvtHighFlow },
{ "TPU High Flow", nvtTPUHighFlow }, { "TPU High Flow", nvtTPUHighFlow },
{ "Hybrid", nvtHybrid } { "Hybrid", nvtHybrid },
{ "E3D High Flow", nvtE3DHighFlow }
}; };
CONFIG_OPTION_ENUM_DEFINE_STATIC_MAPS(NozzleVolumeType) CONFIG_OPTION_ENUM_DEFINE_STATIC_MAPS(NozzleVolumeType)
@@ -676,9 +677,34 @@ int get_config_index_base(NozzleVolumeType volume_type, ExtruderType extruder_ty
for (int index = 0; index < int(variant_list.size()); ++index) { for (int index = 0; index < int(variant_list.size()); ++index) {
if (extruder_variant == variant_list[index] && variant_ids_1based[index] == variant_id_1based) { return index; } if (extruder_variant == variant_list[index] && variant_ids_1based[index] == variant_id_1based) { return index; }
} }
// Without this variant, use the id's own first variant (usually Standard), not variant index 0,
// which belongs to the first filament or extruder.
for (int index = 0; index < int(variant_list.size()); ++index) {
if (variant_ids_1based[index] == variant_id_1based) { return index; }
}
return 0; return 0;
} }
std::set<NozzleVolumeType> get_extruder_supported_nozzle_volume_types(const DynamicPrintConfig &printer_config, int extruder_id)
{
std::set<NozzleVolumeType> supported_types;
auto *variant_list = printer_config.option<ConfigOptionStrings>("extruder_variant_list");
auto *extruder_types = printer_config.option<ConfigOptionEnumsGeneric>("extruder_type");
if (!variant_list || !extruder_types || extruder_id < 0 ||
extruder_id >= (int) variant_list->values.size() || extruder_id >= (int) extruder_types->values.size())
return supported_types;
const ExtruderType extruder_type = ExtruderType(extruder_types->values[extruder_id]);
for (NozzleVolumeType volume_type : get_valid_nozzle_volume_type()) {
// An unsupported extruder type yields an empty name, which would match any list.
const std::string variant = get_extruder_variant_string(extruder_type, volume_type);
if (!variant.empty() && variant_list->values[extruder_id].find(variant) != std::string::npos)
supported_types.insert(volume_type);
}
return supported_types;
}
std::string get_nozzle_volume_type_string(NozzleVolumeType nozzle_volume_type) std::string get_nozzle_volume_type_string(NozzleVolumeType nozzle_volume_type)
{ {
if (nozzle_volume_type > nvtMaxNozzleVolumeType) { if (nozzle_volume_type > nvtMaxNozzleVolumeType) {
@@ -5923,15 +5949,18 @@ void PrintConfigDef::init_fff_params()
def->label = "Nozzle Volume Type"; def->label = "Nozzle Volume Type";
def->tooltip = "Nozzle volume type for extruders."; def->tooltip = "Nozzle volume type for extruders.";
def->enum_keys_map = &ConfigOptionEnum<NozzleVolumeType>::get_enum_values(); def->enum_keys_map = &ConfigOptionEnum<NozzleVolumeType>::get_enum_values();
// Order must match the NozzleVolumeType enum values (Standard=0, High Flow=1, Hybrid=2, TPU High Flow=3). // Listed in display order. A position is not the enum value (E3D High Flow is 5, after the reserved 4),
// so map a position to its NozzleVolumeType through enum_keys_map.
def->enum_values.push_back(L("Standard")); def->enum_values.push_back(L("Standard"));
def->enum_values.push_back(L("High Flow")); def->enum_values.push_back(L("High Flow"));
def->enum_values.push_back(L("Hybrid")); def->enum_values.push_back(L("Hybrid"));
def->enum_values.push_back(L("TPU High Flow")); def->enum_values.push_back(L("TPU High Flow"));
def->enum_values.push_back(L("E3D High Flow"));
def->enum_labels.push_back(L("Standard")); def->enum_labels.push_back(L("Standard"));
def->enum_labels.push_back(L("High Flow")); def->enum_labels.push_back(L("High Flow"));
def->enum_labels.push_back(L("Hybrid")); def->enum_labels.push_back(L("Hybrid"));
def->enum_labels.push_back(L("TPU High Flow")); def->enum_labels.push_back(L("TPU High Flow"));
def->enum_labels.push_back(L("E3D High Flow"));
def->mode = comSimple; def->mode = comSimple;
def->set_default_value(new ConfigOptionEnumsGeneric{ NozzleVolumeType::nvtStandard }); def->set_default_value(new ConfigOptionEnumsGeneric{ NozzleVolumeType::nvtStandard });
@@ -5944,10 +5973,12 @@ void PrintConfigDef::init_fff_params()
def->enum_values.push_back(L("High Flow")); def->enum_values.push_back(L("High Flow"));
def->enum_values.push_back(L("Hybrid")); def->enum_values.push_back(L("Hybrid"));
def->enum_values.push_back(L("TPU High Flow")); def->enum_values.push_back(L("TPU High Flow"));
def->enum_values.push_back(L("E3D High Flow"));
def->enum_labels.push_back(L("Standard")); def->enum_labels.push_back(L("Standard"));
def->enum_labels.push_back(L("High Flow")); def->enum_labels.push_back(L("High Flow"));
def->enum_labels.push_back(L("Hybrid")); def->enum_labels.push_back(L("Hybrid"));
def->enum_labels.push_back(L("TPU High Flow")); def->enum_labels.push_back(L("TPU High Flow"));
def->enum_labels.push_back(L("E3D High Flow"));
def->mode = comDevelop; def->mode = comDevelop;
def->set_default_value(new ConfigOptionEnumsGeneric{ NozzleVolumeType::nvtStandard }); def->set_default_value(new ConfigOptionEnumsGeneric{ NozzleVolumeType::nvtStandard });
@@ -5986,7 +6017,7 @@ void PrintConfigDef::init_fff_params()
// Per-nozzle volume type. Forward-compat-only registration with no slicing consumer — nothing in // Per-nozzle volume type. Forward-compat-only registration with no slicing consumer — nothing in
// src/ reads it; the engine resolves per-nozzle volume types from `extruder_nozzle_stats` tokens // src/ reads it; the engine resolves per-nozzle volume types from `extruder_nozzle_stats` tokens
// instead. Kept registered so a project/config carrying it loads without an unknown-option // instead. Kept registered so a project/config carrying it loads without an unknown-option
// substitution warning. Registers Standard/High Flow/TPU High Flow only (no Hybrid). // substitution warning. Registers the physical types only (no Hybrid).
// Internal use only, no translation. // Internal use only, no translation.
def = this->add("extruder_nozzle_volume_type", coEnums); def = this->add("extruder_nozzle_volume_type", coEnums);
def->label = "Extruder nozzle volume type"; def->label = "Extruder nozzle volume type";
@@ -5995,9 +6026,11 @@ void PrintConfigDef::init_fff_params()
def->enum_values.push_back("Standard"); def->enum_values.push_back("Standard");
def->enum_values.push_back("High Flow"); def->enum_values.push_back("High Flow");
def->enum_values.push_back("TPU High Flow"); def->enum_values.push_back("TPU High Flow");
def->enum_values.push_back("E3D High Flow");
def->enum_labels.push_back("Standard"); def->enum_labels.push_back("Standard");
def->enum_labels.push_back("High Flow"); def->enum_labels.push_back("High Flow");
def->enum_labels.push_back("TPU High Flow"); def->enum_labels.push_back("TPU High Flow");
def->enum_labels.push_back("E3D High Flow");
def->mode = comDevelop; def->mode = comDevelop;
def->set_default_value(new ConfigOptionEnumsGeneric{ NozzleVolumeType::nvtStandard }); def->set_default_value(new ConfigOptionEnumsGeneric{ NozzleVolumeType::nvtStandard });
@@ -10070,6 +10103,13 @@ bool DynamicPrintConfig::is_using_different_extruders()
return ret; return ret;
} }
bool DynamicPrintConfig::has_multi_variant_filament() const
{
auto variants = dynamic_cast<const ConfigOptionStrings*>(this->option("filament_extruder_variant"));
auto diameters = dynamic_cast<const ConfigOptionFloats*>(this->option("filament_diameter"));
return variants && diameters && variants->size() > diameters->size();
}
bool DynamicPrintConfig::support_different_extruders(int& extruder_count) const bool DynamicPrintConfig::support_different_extruders(int& extruder_count) const
{ {
std::set<std::string> variant_set; std::set<std::string> variant_set;
@@ -11070,6 +11110,11 @@ void DynamicPrintConfig::update_values_to_printer_extruders_for_multiple_filamen
return; return;
} }
std::vector<int> filament_maps = opt_filament_map->values; std::vector<int> filament_maps = opt_filament_map->values;
auto opt_ids = id_name.empty()? nullptr: dynamic_cast<const ConfigOptionInts*>(this->option(id_name));
// Orca: a map shorter than the filament count must not drop the filaments past its end;
// they take the first extruder.
if (opt_ids && !opt_ids->values.empty())
filament_maps.resize(std::max<size_t>(filament_maps.size(), *std::max_element(opt_ids->values.begin(), opt_ids->values.end())), 1);
size_t filament_count = filament_maps.size(); size_t filament_count = filament_maps.size();
//apply process settings //apply process settings
auto opt_extruder_type = dynamic_cast<const ConfigOptionEnumsGeneric*>(printer_config.option("extruder_type")); auto opt_extruder_type = dynamic_cast<const ConfigOptionEnumsGeneric*>(printer_config.option("extruder_type"));
@@ -11088,7 +11133,6 @@ void DynamicPrintConfig::update_values_to_printer_extruders_for_multiple_filamen
// indexed out of bounds. // indexed out of bounds.
if (opt_filament_volume_maps && opt_filament_volume_maps->values.size() == filament_count) if (opt_filament_volume_maps && opt_filament_volume_maps->values.size() == filament_count)
filament_volume_maps = opt_filament_volume_maps->values; filament_volume_maps = opt_filament_volume_maps->values;
auto opt_ids = id_name.empty()? nullptr: dynamic_cast<const ConfigOptionInts*>(this->option(id_name));
std::vector<int> variant_index; std::vector<int> variant_index;
variant_index.resize(filament_count, -1); variant_index.resize(filament_count, -1);
@@ -11105,9 +11149,10 @@ void DynamicPrintConfig::update_values_to_printer_extruders_for_multiple_filamen
//variant index //variant index
variant_index[f_index] = get_index_for_extruder(f_index+1, id_name, extruder_type, nozzle_volume_type, variant_name); variant_index[f_index] = get_index_for_extruder(f_index+1, id_name, extruder_type, nozzle_volume_type, variant_name);
if (variant_index[f_index] < 0) { if (variant_index[f_index] < 0) {
BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << boost::format(", Line %1%: could not found extruder_type %2%, nozzle_volume_type %3%, filament_index %4%, extruder index %5%") // Orca: a filament need not define every extruder variant (a Direct Drive filament on a
// Bowden printer), so this is not an invalid state: the filament's first variant is used.
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format(", Line %1%: could not found extruder_type %2%, nozzle_volume_type %3%, filament_index %4%, extruder index %5%")
%__LINE__ %s_keys_names_ExtruderType[extruder_type] % s_keys_names_NozzleVolumeType[nozzle_volume_type] % (f_index+1) %filament_maps[f_index]; %__LINE__ %s_keys_names_ExtruderType[extruder_type] % s_keys_names_NozzleVolumeType[nozzle_volume_type] % (f_index+1) %filament_maps[f_index];
assert(false);
//for some updates happens in a invalid state(caused by popup window) //for some updates happens in a invalid state(caused by popup window)
//we need to avoid crash //we need to avoid crash
variant_index[f_index] = 0; variant_index[f_index] = 0;
+17 -3
View File
@@ -30,6 +30,8 @@
namespace Slic3r { namespace Slic3r {
class DynamicPrintConfig;
enum GCodeFlavor : unsigned char { enum GCodeFlavor : unsigned char {
gcfMarlinLegacy, gcfMarlinLegacy,
gcfKlipper, gcfKlipper,
@@ -519,8 +521,10 @@ enum NozzleVolumeType {
// with more than one sub-nozzle (extruder_max_nozzle_count > 1); matched as Standard for // with more than one sub-nozzle (extruder_max_nozzle_count > 1); matched as Standard for
// preset lookup and never emitted in profile variant strings // preset lookup and never emitted in profile variant strings
nvtTPUHighFlow, // physical variant, used on H2D/H2DP 0.4 nozzles only nvtTPUHighFlow, // physical variant, used on H2D/H2DP 0.4 nozzles only
// 4 is reserved: E3D High Flow is 5 in BambuStudio's slice_info and device numbering.
nvtE3DHighFlow = 5, // physical variant, E3D high-flow hotend on 0.4/0.6 nozzles
// Integer values are serialized as raw ints in 3mf plate metadata and device MQTT, so they MUST stay stable. // Integer values are serialized as raw ints in 3mf plate metadata and device MQTT, so they MUST stay stable.
nvtMaxNozzleVolumeType = nvtTPUHighFlow nvtMaxNozzleVolumeType = nvtE3DHighFlow
}; };
enum FilamentMapMode { enum FilamentMapMode {
@@ -558,11 +562,19 @@ static std::set<NozzleVolumeType> get_valid_nozzle_volume_type() {
// Hybrid is not a physical nozzle variant: presets never define it, so it must not // Hybrid is not a physical nozzle variant: presets never define it, so it must not
// produce a variant string. // produce a variant string.
if (t == nvtHybrid) continue; if (t == nvtHybrid) continue;
// Skip the reserved gap between nvtTPUHighFlow (3) and nvtE3DHighFlow (5).
if (i > nvtTPUHighFlow && i < nvtE3DHighFlow) continue;
type.insert(t); type.insert(t);
} }
return type; return type;
} }
// The nozzle volume types the given extruder physically provides, as declared by the printer
// profile's extruder_variant_list. An empty set means the profile could not be read and must be
// treated as "unknown", not as "none". nvtHybrid is never reported: it describes an extruder
// holding a mix of nozzles, not a nozzle the profile can offer.
extern std::set<NozzleVolumeType> get_extruder_supported_nozzle_volume_types(const DynamicPrintConfig &printer_config, int extruder_id);
std::string get_nozzle_volume_type_string(NozzleVolumeType nozzle_volume_type); std::string get_nozzle_volume_type_string(NozzleVolumeType nozzle_volume_type);
static std::string bed_type_to_gcode_string(const BedType type) static std::string bed_type_to_gcode_string(const BedType type)
@@ -699,8 +711,6 @@ CONFIG_OPTION_ENUM_DECLARE_STATIC_MAPS(SurfaceFillOrder)
#undef CONFIG_OPTION_ENUM_DECLARE_STATIC_MAPS #undef CONFIG_OPTION_ENUM_DECLARE_STATIC_MAPS
class DynamicPrintConfig;
// Defines each and every configuration option of Slic3r, including the properties of the GUI dialogs. // Defines each and every configuration option of Slic3r, including the properties of the GUI dialogs.
// Does not store the actual values, but defines default values. // Does not store the actual values, but defines default values.
class PrintConfigDef : public ConfigDef class PrintConfigDef : public ConfigDef
@@ -821,6 +831,10 @@ public:
//BBS //BBS
bool is_using_different_extruders(); bool is_using_different_extruders();
bool support_different_extruders(int& extruder_count) const; bool support_different_extruders(int& extruder_count) const;
// Whether any filament defines more than one variant (filament_extruder_variant longer than
// filament_diameter). Its variants then have to be resolved even on a printer with a single
// extruder variant, which picks the filament's variant of the same variant string.
bool has_multi_variant_filament() const;
// Counts the config slots of a printer: one per (extruder x nozzle volume type) as described by // Counts the config slots of a printer: one per (extruder x nozzle volume type) as described by
// extruder_nozzle_stats, or simply one per extruder when the stats are absent/mismatched. // extruder_nozzle_stats, or simply one per extruder when the stats are absent/mismatched.
// Fills nozzle_volume_types with each extruder's volume types in ascending enum order. // Fills nozzle_volume_types with each extruder's volume types in ascending enum order.
+10 -6
View File
@@ -818,12 +818,14 @@ NewCalibrationHistoryDialog::NewCalibrationHistoryDialog(wxWindow *parent, const
if (support_nozzle_volume(curr_obj)) { if (support_nozzle_volume(curr_obj)) {
Label *nozzle_name_title = new Label(top_panel, _L("Nozzle")); Label *nozzle_name_title = new Label(top_panel, _L("Nozzle"));
m_comboBox_nozzle_type = new ::ComboBox(top_panel, wxID_ANY, wxEmptyString, wxDefaultPosition, NEW_HISTORY_DIALOG_INPUT_SIZE, 0, nullptr, wxCB_READONLY); m_comboBox_nozzle_type = new ::ComboBox(top_panel, wxID_ANY, wxEmptyString, wxDefaultPosition, NEW_HISTORY_DIALOG_INPUT_SIZE, 0, nullptr, wxCB_READONLY);
wxArrayString nozzle_items; // The labels are in display order, not enum order (E3D High Flow is 5 but the fifth label), so each
// item carries its NozzleVolumeType as client data.
const ConfigOptionDef *nozzle_volume_type_def = print_config_def.get("nozzle_volume_type"); const ConfigOptionDef *nozzle_volume_type_def = print_config_def.get("nozzle_volume_type");
if (nozzle_volume_type_def && nozzle_volume_type_def->enum_keys_map) { if (nozzle_volume_type_def && nozzle_volume_type_def->enum_keys_map) {
for (auto item : nozzle_volume_type_def->enum_labels) { nozzle_items.push_back(_L(item)); } for (size_t i = 0; i < nozzle_volume_type_def->enum_labels.size(); ++i)
m_comboBox_nozzle_type->Append(_L(nozzle_volume_type_def->enum_labels[i]), wxNullBitmap,
(void *) (intptr_t) nozzle_volume_type_def->enum_keys_map->at(nozzle_volume_type_def->enum_values[i]));
} }
m_comboBox_nozzle_type->Set(nozzle_items);
m_comboBox_nozzle_type->SetSelection(-1); m_comboBox_nozzle_type->SetSelection(-1);
flex_sizer->Add(nozzle_name_title); flex_sizer->Add(nozzle_name_title);
flex_sizer->Add(m_comboBox_nozzle_type); flex_sizer->Add(m_comboBox_nozzle_type);
@@ -891,7 +893,7 @@ int NewCalibrationHistoryDialog::get_nozzle_combo_id_code() const
void NewCalibrationHistoryDialog::on_select_nozzle_pos(wxCommandEvent &event) void NewCalibrationHistoryDialog::on_select_nozzle_pos(wxCommandEvent &event)
{ {
// Mirror the picked hotend's flow onto the (Orca index-based) nozzle-type combo. // Mirror the picked hotend's flow onto the nozzle-type combo.
if (!curr_obj || !m_comboBox_nozzle_id || !m_comboBox_nozzle_type || !curr_obj->GetNozzleSystem()) if (!curr_obj || !m_comboBox_nozzle_id || !m_comboBox_nozzle_type || !curr_obj->GetNozzleSystem())
return; return;
@@ -902,7 +904,9 @@ void NewCalibrationHistoryDialog::on_select_nozzle_pos(wxCommandEvent &event)
DevNozzle nozzle = curr_obj->GetNozzleSystem()->GetNozzleByPosId(pos); DevNozzle nozzle = curr_obj->GetNozzleSystem()->GetNozzleByPosId(pos);
if (nozzle.IsNormal()) { if (nozzle.IsNormal()) {
NozzleVolumeType volume_type = DevNozzle::ToNozzleVolumeType(nozzle.GetNozzleFlowType()); NozzleVolumeType volume_type = DevNozzle::ToNozzleVolumeType(nozzle.GetNozzleFlowType());
m_comboBox_nozzle_type->SetSelection(static_cast<int>(volume_type)); for (unsigned int i = 0; i < m_comboBox_nozzle_type->GetCount(); ++i)
if (NozzleVolumeType(intptr_t(m_comboBox_nozzle_type->GetClientData(i))) == volume_type)
m_comboBox_nozzle_type->SetSelection(i);
} }
} }
@@ -949,7 +953,7 @@ void NewCalibrationHistoryDialog::on_ok(wxCommandEvent &event)
msg_dlg.ShowModal(); msg_dlg.ShowModal();
return; return;
} }
m_new_result.nozzle_volume_type = NozzleVolumeType(m_comboBox_nozzle_type->GetSelection()); m_new_result.nozzle_volume_type = NozzleVolumeType(intptr_t(m_comboBox_nozzle_type->GetClientData(m_comboBox_nozzle_type->GetSelection())));
} }
auto filament_item = map_filament_items[m_comboBox_filament->GetValue().ToStdString()]; auto filament_item = map_filament_items[m_comboBox_filament->GetValue().ToStdString()];
+32 -33
View File
@@ -803,6 +803,29 @@ void CalibrationPresetPage::create_selection_panel(wxWindow* parent)
#define NOZZLE_LIST_DEFAULT 1 #define NOZZLE_LIST_DEFAULT 1
float nozzle_diameter_list[NOZZLE_LIST_COUNT] = {0.2, 0.4, 0.6, 0.8 }; float nozzle_diameter_list[NOZZLE_LIST_COUNT] = {0.2, 0.4, 0.6, 0.8 };
// The nozzle_volume_type labels are in display order, not enum order (E3D High Flow is 5 but the fifth
// label), so each item carries its NozzleVolumeType as client data and is selected by that value.
static void select_nozzle_volume(ComboBox *combo, NozzleVolumeType volume_type)
{
for (unsigned int i = 0; i < combo->GetCount(); ++i)
if (NozzleVolumeType(intptr_t(combo->GetClientData(i))) == volume_type) {
combo->SetSelection(i);
return;
}
}
static void fill_nozzle_volume_combo(ComboBox *combo)
{
combo->Clear();
const ConfigOptionDef *nozzle_volume_type_def = print_config_def.get("nozzle_volume_type");
if (nozzle_volume_type_def && nozzle_volume_type_def->enum_keys_map) {
for (size_t i = 0; i < nozzle_volume_type_def->enum_labels.size(); ++i)
combo->Append(_L(nozzle_volume_type_def->enum_labels[i]), wxNullBitmap,
(void *) (intptr_t) nozzle_volume_type_def->enum_keys_map->at(nozzle_volume_type_def->enum_values[i]));
}
select_nozzle_volume(combo, NozzleVolumeType::nvtStandard);
}
void CalibrationPresetPage::init_selection_values() void CalibrationPresetPage::init_selection_values()
{ {
// init nozzle diameter and nozzle volume // init nozzle diameter and nozzle volume
@@ -813,15 +836,7 @@ void CalibrationPresetPage::init_selection_values()
} }
m_comboBox_nozzle_dia->SetSelection(NOZZLE_LIST_DEFAULT); m_comboBox_nozzle_dia->SetSelection(NOZZLE_LIST_DEFAULT);
m_comboBox_nozzle_volume->Clear(); fill_nozzle_volume_combo(m_comboBox_nozzle_volume);
const ConfigOptionDef *nozzle_volume_type_def = print_config_def.get("nozzle_volume_type");
if (nozzle_volume_type_def && nozzle_volume_type_def->enum_keys_map) {
for (auto item : nozzle_volume_type_def->enum_labels) {
m_comboBox_nozzle_volume->AppendString(_L(item));
}
}
m_comboBox_nozzle_volume->SetSelection(int(NozzleVolumeType::nvtStandard));
} }
Preset* cur_printer_preset = get_printer_preset(curr_obj, 0.4); Preset* cur_printer_preset = get_printer_preset(curr_obj, 0.4);
@@ -866,15 +881,7 @@ void CalibrationPresetPage::init_selection_values()
} }
m_left_comboBox_nozzle_dia->SetSelection(NOZZLE_LIST_DEFAULT); m_left_comboBox_nozzle_dia->SetSelection(NOZZLE_LIST_DEFAULT);
m_left_comboBox_nozzle_volume->Clear(); fill_nozzle_volume_combo(m_left_comboBox_nozzle_volume);
const ConfigOptionDef *nozzle_volume_type_def = print_config_def.get("nozzle_volume_type");
if (nozzle_volume_type_def && nozzle_volume_type_def->enum_keys_map) {
for (auto item : nozzle_volume_type_def->enum_labels) {
m_left_comboBox_nozzle_volume->AppendString(_L(item));
}
}
m_left_comboBox_nozzle_volume->SetSelection(int(NozzleVolumeType::nvtStandard));
} }
// right // right
@@ -885,15 +892,7 @@ void CalibrationPresetPage::init_selection_values()
} }
m_right_comboBox_nozzle_dia->SetSelection(NOZZLE_LIST_DEFAULT); m_right_comboBox_nozzle_dia->SetSelection(NOZZLE_LIST_DEFAULT);
m_right_comboBox_nozzle_volume->Clear(); fill_nozzle_volume_combo(m_right_comboBox_nozzle_volume);
const ConfigOptionDef *nozzle_volume_type_def = print_config_def.get("nozzle_volume_type");
if (nozzle_volume_type_def && nozzle_volume_type_def->enum_keys_map) {
for (auto item : nozzle_volume_type_def->enum_labels) {
m_right_comboBox_nozzle_volume->AppendString(_L(item));
}
}
m_right_comboBox_nozzle_volume->SetSelection(int(NozzleVolumeType::nvtStandard));
} }
} }
@@ -980,13 +979,13 @@ NozzleVolumeType CalibrationPresetPage::get_nozzle_volume_type(int extruder_id)
if (curr_obj) { if (curr_obj) {
if (curr_obj->is_multi_extruders()) { if (curr_obj->is_multi_extruders()) {
if (extruder_id == LEFT_EXTRUDER_ID) { if (extruder_id == LEFT_EXTRUDER_ID) {
return NozzleVolumeType(m_left_comboBox_nozzle_volume->GetSelection()); return NozzleVolumeType(intptr_t(m_left_comboBox_nozzle_volume->GetClientData(m_left_comboBox_nozzle_volume->GetSelection())));
} else if (extruder_id == RIGHT_EXTRUDER_ID) { } else if (extruder_id == RIGHT_EXTRUDER_ID) {
return NozzleVolumeType(m_right_comboBox_nozzle_volume->GetSelection()); return NozzleVolumeType(intptr_t(m_right_comboBox_nozzle_volume->GetClientData(m_right_comboBox_nozzle_volume->GetSelection())));
} }
} }
else else
return NozzleVolumeType(m_comboBox_nozzle_volume->GetSelection()); return NozzleVolumeType(intptr_t(m_comboBox_nozzle_volume->GetClientData(m_comboBox_nozzle_volume->GetSelection())));
} }
return NozzleVolumeType::nvtStandard; return NozzleVolumeType::nvtStandard;
} }
@@ -2139,7 +2138,7 @@ void CalibrationPresetPage::init_with_machine(MachineObject* obj)
} }
if (obj->GetExtderSystem()->GetNozzleFlowType(i) != NozzleFlowType::NONE_FLOWTYPE) { if (obj->GetExtderSystem()->GetNozzleFlowType(i) != NozzleFlowType::NONE_FLOWTYPE) {
m_left_comboBox_nozzle_volume->SetSelection(int(DevNozzle::ToNozzleVolumeType(obj->GetExtderSystem()->GetNozzleFlowType(i)))); select_nozzle_volume(m_left_comboBox_nozzle_volume, DevNozzle::ToNozzleVolumeType(obj->GetExtderSystem()->GetNozzleFlowType(i)));
} else { } else {
m_left_comboBox_nozzle_volume->SetSelection(0); m_left_comboBox_nozzle_volume->SetSelection(0);
} }
@@ -2159,7 +2158,7 @@ void CalibrationPresetPage::init_with_machine(MachineObject* obj)
} }
if (obj->GetExtderSystem()->GetNozzleFlowType(i) != NozzleFlowType::NONE_FLOWTYPE) { if (obj->GetExtderSystem()->GetNozzleFlowType(i) != NozzleFlowType::NONE_FLOWTYPE) {
m_right_comboBox_nozzle_volume->SetSelection(int(DevNozzle::ToNozzleVolumeType(obj->GetExtderSystem()->GetNozzleFlowType(i)))); select_nozzle_volume(m_right_comboBox_nozzle_volume, DevNozzle::ToNozzleVolumeType(obj->GetExtderSystem()->GetNozzleFlowType(i)));
} else { } else {
m_right_comboBox_nozzle_volume->SetSelection(0); m_right_comboBox_nozzle_volume->SetSelection(0);
} }
@@ -2197,7 +2196,7 @@ void CalibrationPresetPage::init_with_machine(MachineObject* obj)
else { else {
if ((obj->GetExtderSystem()->GetTotalExtderCount() > 0) && (obj->GetExtderSystem()->GetNozzleFlowType(0) != NozzleFlowType::NONE_FLOWTYPE)) if ((obj->GetExtderSystem()->GetTotalExtderCount() > 0) && (obj->GetExtderSystem()->GetNozzleFlowType(0) != NozzleFlowType::NONE_FLOWTYPE))
{ {
m_comboBox_nozzle_volume->SetSelection(int(DevNozzle::ToNozzleVolumeType(obj->GetExtderSystem()->GetNozzleFlowType(0)))); select_nozzle_volume(m_comboBox_nozzle_volume, DevNozzle::ToNozzleVolumeType(obj->GetExtderSystem()->GetNozzleFlowType(0)));
} else { } else {
m_comboBox_nozzle_volume->SetSelection(0); m_comboBox_nozzle_volume->SetSelection(0);
} }
@@ -732,6 +732,7 @@ void CaliPASaveAutoPanel::sync_cali_result_for_multi_extruder(const std::vector<
case NozzleVolumeType::nvtStandard: nozzle_id_str += _L("Standard Flow"); break; case NozzleVolumeType::nvtStandard: nozzle_id_str += _L("Standard Flow"); break;
case NozzleVolumeType::nvtHighFlow: nozzle_id_str += _L("High Flow"); break; case NozzleVolumeType::nvtHighFlow: nozzle_id_str += _L("High Flow"); break;
case NozzleVolumeType::nvtTPUHighFlow: nozzle_id_str += _L("TPU High Flow"); break; case NozzleVolumeType::nvtTPUHighFlow: nozzle_id_str += _L("TPU High Flow"); break;
case NozzleVolumeType::nvtE3DHighFlow: nozzle_id_str += _L("E3D High Flow"); break;
default: break; default: break;
} }
nozzle_id_value->SetLabel(nozzle_id_str); nozzle_id_value->SetLabel(nozzle_id_str);
+2 -1
View File
@@ -36,7 +36,8 @@ static NozzleVolumeType convert_to_nozzle_type(const std::string &str)
return NozzleVolumeType::nvtHighFlow; return NozzleVolumeType::nvtHighFlow;
else if (str[1] == 'U') else if (str[1] == 'U')
return NozzleVolumeType::nvtTPUHighFlow; return NozzleVolumeType::nvtTPUHighFlow;
// Orca: no nvtE3DHighFlow in Orca's NozzleVolumeType; map 'B' to Standard else if (str[1] == 'B')
return NozzleVolumeType::nvtE3DHighFlow;
else else
return NozzleVolumeType::nvtStandard; return NozzleVolumeType::nvtStandard;
} }
@@ -13,7 +13,7 @@
namespace Slic3r namespace Slic3r
{ {
// ---- DevNozzle: flow/volume conversions (Standard / High Flow / TPU High Flow) ---------------------- // ---- DevNozzle: flow/volume conversions (Standard / High Flow / TPU High Flow / E3D High Flow) ------
// Device-reported U_FLOW nozzles map to nvtTPUHighFlow so a synced TPU-HF rack activates the H2C // Device-reported U_FLOW nozzles map to nvtTPUHighFlow so a synced TPU-HF rack activates the H2C
// change_filament_gcode TPU-kit branch. nvtHybrid is a slicer-only sentinel with no device // change_filament_gcode TPU-kit branch. nvtHybrid is a slicer-only sentinel with no device
// representation, so ToNozzleFlowType(nvtHybrid) falls through to NONE_FLOWTYPE. // representation, so ToNozzleFlowType(nvtHybrid) falls through to NONE_FLOWTYPE.
@@ -24,6 +24,7 @@ NozzleFlowType DevNozzle::ToNozzleFlowType(const NozzleVolumeType& type)
case NozzleVolumeType::nvtStandard: return NozzleFlowType::S_FLOW; case NozzleVolumeType::nvtStandard: return NozzleFlowType::S_FLOW;
case NozzleVolumeType::nvtHighFlow: return NozzleFlowType::H_FLOW; case NozzleVolumeType::nvtHighFlow: return NozzleFlowType::H_FLOW;
case NozzleVolumeType::nvtTPUHighFlow: return NozzleFlowType::U_FLOW; case NozzleVolumeType::nvtTPUHighFlow: return NozzleFlowType::U_FLOW;
case NozzleVolumeType::nvtE3DHighFlow: return NozzleFlowType::E_FLOW;
default: return NozzleFlowType::NONE_FLOWTYPE; default: return NozzleFlowType::NONE_FLOWTYPE;
} }
} }
@@ -34,6 +35,7 @@ NozzleVolumeType DevNozzle::ToNozzleVolumeType(const NozzleFlowType& type)
case NozzleFlowType::S_FLOW: return NozzleVolumeType::nvtStandard; case NozzleFlowType::S_FLOW: return NozzleVolumeType::nvtStandard;
case NozzleFlowType::H_FLOW: return NozzleVolumeType::nvtHighFlow; case NozzleFlowType::H_FLOW: return NozzleVolumeType::nvtHighFlow;
case NozzleFlowType::U_FLOW: return NozzleVolumeType::nvtTPUHighFlow; case NozzleFlowType::U_FLOW: return NozzleVolumeType::nvtTPUHighFlow;
case NozzleFlowType::E_FLOW: return NozzleVolumeType::nvtE3DHighFlow;
default: { default: {
BOOST_LOG_TRIVIAL(warning) << __FUNCTION__ << "nozzle flow type None convert to nozzle volume type Standard"; BOOST_LOG_TRIVIAL(warning) << __FUNCTION__ << "nozzle flow type None convert to nozzle volume type Standard";
return NozzleVolumeType::nvtStandard; return NozzleVolumeType::nvtStandard;
@@ -47,6 +49,7 @@ wxString DevNozzle::GetNozzleFlowTypeStr(NozzleFlowType type)
case NozzleFlowType::H_FLOW: return _L("High Flow"); case NozzleFlowType::H_FLOW: return _L("High Flow");
case NozzleFlowType::S_FLOW: return _L("Standard"); case NozzleFlowType::S_FLOW: return _L("Standard");
case NozzleFlowType::U_FLOW: return _L("TPU High Flow"); case NozzleFlowType::U_FLOW: return _L("TPU High Flow");
case NozzleFlowType::E_FLOW: return _L("E3D High Flow");
default: break; default: break;
} }
@@ -61,6 +64,7 @@ std::string DevNozzle::GetNozzleFlowTypeString(NozzleFlowType type)
case NozzleFlowType::H_FLOW: return "High Flow"; case NozzleFlowType::H_FLOW: return "High Flow";
case NozzleFlowType::S_FLOW: return "Standard"; case NozzleFlowType::S_FLOW: return "Standard";
case NozzleFlowType::U_FLOW: return "TPU High Flow"; case NozzleFlowType::U_FLOW: return "TPU High Flow";
case NozzleFlowType::E_FLOW: return "E3D High Flow";
default: return "Unknown"; default: return "Unknown";
} }
} }
@@ -73,6 +77,7 @@ std::string DevNozzle::ToNozzleFlowString(const NozzleFlowType& type)
case NozzleFlowType::S_FLOW: return "Standard"; case NozzleFlowType::S_FLOW: return "Standard";
case NozzleFlowType::H_FLOW: return "High Flow"; case NozzleFlowType::H_FLOW: return "High Flow";
case NozzleFlowType::U_FLOW: return "TPU High Flow"; case NozzleFlowType::U_FLOW: return "TPU High Flow";
case NozzleFlowType::E_FLOW: return "E3D High Flow";
default: return std::string(); default: return std::string();
} }
} }
@@ -408,6 +413,7 @@ static unordered_map<string, NozzleFlowType> _str2_nozzle_flow_type = {
{"X", NozzleFlowType::S_FLOW}, {"X", NozzleFlowType::S_FLOW},
{"E", NozzleFlowType::H_FLOW}, // E3D high-flow {"E", NozzleFlowType::H_FLOW}, // E3D high-flow
{"U", NozzleFlowType::U_FLOW}, // TPU 1.75 high-flow -> nvtTPUHighFlow {"U", NozzleFlowType::U_FLOW}, // TPU 1.75 high-flow -> nvtTPUHighFlow
{"B", NozzleFlowType::E_FLOW}, // E3D High Flow -> nvtE3DHighFlow
}; };
static unordered_map<string, NozzleType> _str2_nozzle_type = { static unordered_map<string, NozzleType> _str2_nozzle_type = {
+6 -5
View File
@@ -12,8 +12,9 @@
// Device flow-type mapping note (nozzle rack): the device U_FLOW value maps to nvtTPUHighFlow // Device flow-type mapping note (nozzle rack): the device U_FLOW value maps to nvtTPUHighFlow
// (TPU High Flow), so a device-synced TPU-HF rack resolves to nvtTPUHighFlow and the H2C // (TPU High Flow), so a device-synced TPU-HF rack resolves to nvtTPUHighFlow and the H2C
// change_filament_gcode TPU-kit branch can activate. nvtHybrid is a slicer-only sentinel with no // change_filament_gcode TPU-kit branch can activate. E_FLOW maps to nvtE3DHighFlow. nvtHybrid is a
// device representation, so it stays out of these device flow-type conversions. // slicer-only sentinel with no device representation, so it stays out of these device flow-type
// conversions.
// //
// GetExtruderNozzleInfo (and its ExtruderNozzleInfos aggregate) is intentionally omitted here; // GetExtruderNozzleInfo (and its ExtruderNozzleInfos aggregate) is intentionally omitted here;
// the SelectMachine slicing-vs-installed nozzle comparison collects its per-extruder NozzleDef // the SelectMachine slicing-vs-installed nozzle comparison collects its per-extruder NozzleDef
@@ -36,13 +37,13 @@ namespace Slic3r
float m_diameter = 0.0f;// unknown until reported by the printer float m_diameter = 0.0f;// unknown until reported by the printer
public: public:
// flow/volume conversions (Standard / High Flow / TPU High Flow) // flow/volume conversions (Standard / High Flow / TPU High Flow / E3D High Flow)
static NozzleFlowType ToNozzleFlowType(const NozzleVolumeType& type); static NozzleFlowType ToNozzleFlowType(const NozzleVolumeType& type);
static NozzleVolumeType ToNozzleVolumeType(const NozzleFlowType& type); static NozzleVolumeType ToNozzleVolumeType(const NozzleFlowType& type);
static wxString GetNozzleFlowTypeStr(NozzleFlowType type); static wxString GetNozzleFlowTypeStr(NozzleFlowType type);
static std::string GetNozzleFlowTypeString(NozzleFlowType type);// untranslated literal ("High Flow"/"Standard"/"TPU High Flow") — the filament blacklist JSON matches these raw strings, so it must NOT use the translated GetNozzleFlowTypeStr (would break non-English locales) static std::string GetNozzleFlowTypeString(NozzleFlowType type);// untranslated literal ("High Flow"/"Standard"/"TPU High Flow"/"E3D High Flow") — the filament blacklist JSON matches these raw strings, so it must NOT use the translated GetNozzleFlowTypeStr (would break non-English locales)
static std::string ToNozzleFlowString(const NozzleFlowType& type);// untranslated "Standard"/"High Flow"/"TPU High Flow" ("" for none) — the raw literal serialized into the get_auto_nozzle_mapping payload static std::string ToNozzleFlowString(const NozzleFlowType& type);// untranslated "Standard"/"High Flow"/"TPU High Flow"/"E3D High Flow" ("" for none) — the raw literal serialized into the get_auto_nozzle_mapping payload
static wxString GetNozzleTypeStr(NozzleType type); static wxString GetNozzleTypeStr(NozzleType type);
public: public:
+6
View File
@@ -347,6 +347,10 @@ static wxString _generate_nozzle_id(NozzleVolumeType nozzle_type, const std::str
nozzle_id += "H"; nozzle_id += "H";
break; break;
} }
case NozzleVolumeType::nvtE3DHighFlow: {
nozzle_id += "B";
break;
}
default: default:
nozzle_id += "H"; nozzle_id += "H";
break; break;
@@ -368,6 +372,8 @@ NozzleVolumeType convert_to_nozzle_type(const std::string &str)
res = NozzleVolumeType::nvtStandard; res = NozzleVolumeType::nvtStandard;
else if (str[1] == 'H') else if (str[1] == 'H')
res = NozzleVolumeType::nvtHighFlow; res = NozzleVolumeType::nvtHighFlow;
else if (str[1] == 'B')
res = NozzleVolumeType::nvtE3DHighFlow;
return res; return res;
} }
+2 -1
View File
@@ -566,7 +566,8 @@ std::vector<int> SeparatedDragDropPanel::GetHighFlowFilaments() const
const int right_eid = 1; const int right_eid = 1;
if (nozzle_volumes->values.size() > right_eid) { if (nozzle_volumes->values.size() > right_eid) {
int volume_type = nozzle_volumes->values[right_eid]; int volume_type = nozzle_volumes->values[right_eid];
if (volume_type == static_cast<int>(NozzleVolumeType::nvtHighFlow)) { if (volume_type == static_cast<int>(NozzleVolumeType::nvtHighFlow) ||
volume_type == static_cast<int>(NozzleVolumeType::nvtE3DHighFlow)) {
return m_unified_panel->GetAllFilaments(); return m_unified_panel->GetAllFilaments();
} }
} }
+7 -1
View File
@@ -185,6 +185,12 @@ std::vector<int> FilamentMapManualPanel::GetFilamentVolumeMaps() const
auto preset_bundle = wxGetApp().preset_bundle; auto preset_bundle = wxGetApp().preset_bundle;
auto proj_config = preset_bundle->project_config; auto proj_config = preset_bundle->project_config;
auto nozzle_volume_values = proj_config.option<ConfigOptionEnumsGeneric>("nozzle_volume_type")->values; auto nozzle_volume_values = proj_config.option<ConfigOptionEnumsGeneric>("nozzle_volume_type")->values;
// The high-flow panel of the right extruder also holds an E3D High Flow nozzle; keep that type
// instead of writing plain High Flow, which the extruder does not have.
const int right_extruder_id = 1;
const bool right_e3d_high_flow = nozzle_volume_values.size() > right_extruder_id &&
nozzle_volume_values[right_extruder_id] == static_cast<int>(NozzleVolumeType::nvtE3DHighFlow);
const int right_high_flow_volume = static_cast<int>(right_e3d_high_flow ? NozzleVolumeType::nvtE3DHighFlow : NozzleVolumeType::nvtHighFlow);
for (int i = 0; i < (int) volume_map.size(); ++i) { for (int i = 0; i < (int) volume_map.size(); ++i) {
int filament_id = i + 1; int filament_id = i + 1;
@@ -199,7 +205,7 @@ std::vector<int> FilamentMapManualPanel::GetFilamentVolumeMaps() const
} }
} }
else if (std::find(right_high_flow_filaments.begin(), right_high_flow_filaments.end(), filament_id) != right_high_flow_filaments.end()) { else if (std::find(right_high_flow_filaments.begin(), right_high_flow_filaments.end(), filament_id) != right_high_flow_filaments.end()) {
volume_map[i] = static_cast<int>(NozzleVolumeType::nvtHighFlow); volume_map[i] = right_high_flow_volume;
} }
else if (std::find(right_standard_filaments.begin(), right_standard_filaments.end(), filament_id) != right_standard_filaments.end()) { else if (std::find(right_standard_filaments.begin(), right_standard_filaments.end(), filament_id) != right_standard_filaments.end()) {
volume_map[i] = static_cast<int>(NozzleVolumeType::nvtStandard); volume_map[i] = static_cast<int>(NozzleVolumeType::nvtStandard);
+274 -54
View File
@@ -605,15 +605,50 @@ private:
bool m_enabled{false}; bool m_enabled{false};
}; };
// The nozzle rows are set in the small body font their "T1" prefix and diameter readout use, so the
// combos of a row drop from the Body_14 they are created with to match, instead of towering over
// the labels next to them.
static void use_nozzle_row_font(ComboBox *combo)
{
// A read-only combo draws its value with the font of the TextInput it is built on, and its
// dropped list carries a font of its own.
combo->SetFont(Label::Body_10);
combo->GetDropDown().SetFont(Label::Body_10);
// A row is as tall as the tags of the variant switch: a line of Body_10 text plus the 3 points of
// padding they put above and below it. TextInput::messureSize() instead sizes a combo as its
// (hidden) text control plus 8, and on macOS that control's best height is a fixed 16 points
// whatever font it carries, so the box would keep the height of the Body_14 it was created with.
// Handing the control the height the row asks for, less those 8 points, keeps messureSize()'s own
// arithmetic landing on the small box however often it re-measures.
const int row_height = combo->GetTextExtent("0.4 mm").y + 2 * combo->FromDIP(3);
auto *text_ctrl = combo->GetTextCtrl();
text_ctrl->SetFont(Label::Body_10);
text_ctrl->SetInitialSize(wxSize(text_ctrl->GetBestSize().x, row_height - 8));
combo->SetMinSize(wxSize(combo->GetMinSize().x, row_height));
combo->SetSize(wxSize(combo->GetSize().x, row_height));
}
struct ExtruderGroup : StaticBox struct ExtruderGroup : StaticBox
{ {
// One nozzle row: the tool prefix ("T1"…"Tn", shown only when the group lists several
// extruders) plus the nozzle readout and flow combo of a single extruder. Rows are appended on
// demand and never destroyed, so an event handler may capture its row index for the lifetime
// of the group.
struct NozzleRow
{
Label * prefix = nullptr;
Label * diameter_label = nullptr; // read-only nozzle diameter of this extruder
ComboBox * flow = nullptr;
};
ExtruderGroup(wxWindow * parent, int index, wxString const &title); ExtruderGroup(wxWindow * parent, int index, wxString const &title);
wxBoxSizer * sizer = nullptr; wxBoxSizer * sizer = nullptr;
HoverLabel * hover_label = nullptr; HoverLabel * hover_label = nullptr;
wxStaticText* ams_label{nullptr}; wxStaticText* ams_label{nullptr};
ScalableButton * btn_edit = nullptr; ScalableButton * btn_edit = nullptr;
ComboBox * combo_diameter = nullptr; ComboBox * combo_diameter = nullptr;
ComboBox * combo_flow = nullptr;
AMSPreview * ams[4] = {nullptr}; AMSPreview * ams[4] = {nullptr};
wxStaticText *ams_not_installed_msg{nullptr}; wxStaticText *ams_not_installed_msg{nullptr};
ScalableButton * btn_up{nullptr}; ScalableButton * btn_up{nullptr};
@@ -627,6 +662,23 @@ struct ExtruderGroup : StaticBox
std::vector<AMSinfo> ams_1; std::vector<AMSinfo> ams_1;
wxString diameter; wxString diameter;
std::vector<NozzleRow> rows; // rows[0] always exists, one row per extruder when multi
size_t nozzle_row_count = 0; // rows currently shown
wxBoxSizer * row_columns[2] = {}; // row i is dealt into column i % 2
int group_index = -1; // ctor index, the extruder a single-row group edits
// Show `count` nozzle rows, one per extruder, each prefixed with T<index> when count > 1.
void SetNozzleRowCount(size_t count);
size_t NozzleRowCount() const { return nozzle_row_count; }
// Nozzle diameter readout of the shown rows, one value per extruder. A listing card cannot edit
// it: the diameter belongs to the machine variant the printer preset selects, not to the extruder.
void SetRowDiameters(const std::vector<double> &diameters);
NozzleRow create_nozzle_row();
// Widths the prefix and diameter labels of the visible rows so that the flow combos of a column
// line up.
void update_row_widths();
void set_ams_count(int n4, int n1) void set_ams_count(int n4, int n1)
{ {
if (n4 == ams_n4 && n1 == ams_n1) if (n4 == ams_n4 && n1 == ams_n1)
@@ -657,7 +709,12 @@ struct ExtruderGroup : StaticBox
btn_up->msw_rescale(); btn_up->msw_rescale();
btn_down->msw_rescale(); btn_down->msw_rescale();
combo_diameter->Rescale(); combo_diameter->Rescale();
combo_flow->Rescale(); for (const NozzleRow &row : rows) {
row.prefix->SetFont(Label::Body_10.Bold());
row.diameter_label->SetFont(Label::Body_10);
row.flow->Rescale();
}
update_row_widths();
for (int i = 0; i < 4; ++i) for (int i = 0; i < 4; ++i)
ams[i]->msw_rescale(); ams[i]->msw_rescale();
} }
@@ -668,6 +725,10 @@ struct ExtruderGroup : StaticBox
hover_label->sys_color_changed(); hover_label->sys_color_changed();
if (btn_edit) if (btn_edit)
btn_edit->SetBackgroundColour(extruder_group_chip_bg()); btn_edit->SetBackgroundColour(extruder_group_chip_bg());
for (const NozzleRow &row : rows) {
row.prefix->SetForegroundColour(StateColor::darkModeColorFor(wxColour("#363636")));
row.diameter_label->SetForegroundColour(StateColor::darkModeColorFor(wxColour("#363636")));
}
Refresh(); Refresh();
} }
}; };
@@ -691,11 +752,12 @@ struct Sidebar::priv
ScalableButton * btn_edit_printer = nullptr; ScalableButton * btn_edit_printer = nullptr;
ScalableButton * btn_connect_printer = nullptr; ScalableButton * btn_connect_printer = nullptr;
// Nozzle diameter // Printer variant switch, titled "Variant": the combo lists the variants (nozzle sizes) of the
// current machine model and picking one switches the printer preset.
StaticBox * panel_nozzle_dia = nullptr; StaticBox * panel_nozzle_dia = nullptr;
Label * label_nozzle_title= nullptr; Label * label_nozzle_title= nullptr;
ComboBox * combo_nozzle_dia = nullptr; ComboBox * combo_nozzle_dia = nullptr;
Label * label_nozzle_type = nullptr; Label * label_nozzle_type = nullptr; // read-only nozzle material, one-row layouts only
// Printer - bed // Printer - bed
StaticBox * panel_printer_bed = nullptr; StaticBox * panel_printer_bed = nullptr;
@@ -809,6 +871,8 @@ struct Sidebar::priv
// otherwise reuses the app_config-cached option when the machine's nozzle config is unchanged. // otherwise reuses the app_config-cached option when the machine's nozzle config is unchanged.
std::optional<NozzleOption> get_nozzle_options(MachineObject* obj, int extruder_count, bool support_multi_nozzle, bool is_manual); std::optional<NozzleOption> get_nozzle_options(MachineObject* obj, int extruder_count, bool support_multi_nozzle, bool is_manual);
bool switch_diameter(bool single); bool switch_diameter(bool single);
// Switch to the printer preset offering `diameter`.
bool switch_diameter_to(const wxString &diameter);
void update_sync_status(const MachineObject* obj); void update_sync_status(const MachineObject* obj);
// Filament Track Switch (H2-family accessory): true only when the connected printer is the // Filament Track Switch (H2-family accessory): true only when the connected printer is the
@@ -918,11 +982,19 @@ void Sidebar::priv::layout_printer(bool isBBL, bool isDual)
int extruder_count = 0; int extruder_count = 0;
const bool has_flow_variant = cfg.support_different_extruders(extruder_count); const bool has_flow_variant = cfg.support_different_extruders(extruder_count);
panel_nozzle_dia->Show(!has_flow_variant); // ORCA: a non-Bambu printer with several extruders lists one nozzle row per tool; Bambu's own
extruder_single_sizer->Show(has_flow_variant); // multi-extruder printers keep the left/right pair above.
const bool multi_extruder_rows = !preset_bundle.is_bbl_vendor() && extruder_count > 1;
// The variant box switches the machine variant. The card below lists the nozzles when there
// are flow variants to choose or several extruders to show; otherwise the box says it all.
panel_nozzle_dia->Show(true);
extruder_single_sizer->Show(has_flow_variant || multi_extruder_rows);
single_extruder->SetNozzleRowCount(multi_extruder_rows ? size_t(extruder_count) : 1);
} else { } else {
panel_nozzle_dia->Show(false); panel_nozzle_dia->Show(false);
extruder_single_sizer->Show(false); extruder_single_sizer->Show(false);
single_extruder->SetNozzleRowCount(1);
} }
// ORCA ensure printer section is visible after changing printer from printer selection dialog // ORCA ensure printer section is visible after changing printer from printer selection dialog
@@ -1319,7 +1391,7 @@ public:
}; };
ExtruderGroup::ExtruderGroup(wxWindow * parent, int index, wxString const &title) ExtruderGroup::ExtruderGroup(wxWindow * parent, int index, wxString const &title)
: StaticBox(parent) : StaticBox(parent), group_index(index)
{ {
SetFont(Label::Body_10); SetFont(Label::Body_10);
SetForegroundColour(wxColour("#CECECE")); SetForegroundColour(wxColour("#CECECE"));
@@ -1332,29 +1404,13 @@ ExtruderGroup::ExtruderGroup(wxWindow * parent, int index, wxString const &title
hover_label = new HoverLabel(this, title); hover_label = new HoverLabel(this, title);
hover_label->SetPosition(wxPoint(FromDIP(PRINTER_PANEL_RADIUS), 0)); // position it without putting in a sizer so it will look like title hover_label->SetPosition(wxPoint(FromDIP(PRINTER_PANEL_RADIUS), 0)); // position it without putting in a sizer so it will look like title
// Nozzle // Nozzle. Only the dual left/right cards show this combo: it is how they ask which nozzle the
// project should print with. Every other card reads the diameter out as text.
auto combo_diameter = new ComboBox(this, wxID_ANY, wxString(""), wxDefaultPosition, wxDefaultSize, 0, nullptr, wxCB_READONLY); auto combo_diameter = new ComboBox(this, wxID_ANY, wxString(""), wxDefaultPosition, wxDefaultSize, 0, nullptr, wxCB_READONLY);
this->combo_diameter = combo_diameter; this->combo_diameter = combo_diameter;
combo_diameter->SetToolTip(_L("Diameter")); combo_diameter->SetToolTip(_L("Diameter"));
combo_diameter->Show(index >= 0);
// Flow use_nozzle_row_font(combo_diameter);
auto combo_flow = new ComboBox(this, wxID_ANY, wxString(""), wxDefaultPosition, wxDefaultSize, 0, nullptr, wxCB_READONLY);
combo_flow->GetDropDown().SetUseContentWidth(true);
combo_flow->Bind(wxEVT_COMBOBOX, [index, combo_flow](wxCommandEvent &evt) {
auto printer_tab = dynamic_cast<TabPrinter *>(wxGetApp().get_tab(Preset::TYPE_PRINTER));
NozzleVolumeType volume_type = NozzleVolumeType(intptr_t(combo_flow->GetClientData(evt.GetInt())));
printer_tab->set_extruder_volume_type(index, volume_type);
auto plater = GUI::wxGetApp().plater();
if (plater) {
// A new Flow type invalidates the per-filament volume choices stored on the
// plates for this extruder; rewrite them so the next apply/grouping sees the
// selected volume instead of a stale one.
plater->update_filament_volume_map(index, static_cast<int>(volume_type));
plater->update_machine_sync_status();
}
});
this->combo_flow = combo_flow;
combo_flow->SetToolTip(_L("Flow"));
// AMS // AMS
auto ams_panel = new wxPanel(this, wxID_ANY); auto ams_panel = new wxPanel(this, wxID_ANY);
@@ -1440,10 +1496,6 @@ ExtruderGroup::ExtruderGroup(wxWindow * parent, int index, wxString const &title
btn_down->Hide(); btn_down->Hide();
wxBoxSizer *vsizer = new wxBoxSizer(wxVERTICAL); wxBoxSizer *vsizer = new wxBoxSizer(wxVERTICAL);
wxBoxSizer *hsizer = new wxBoxSizer(wxHORIZONTAL);
hsizer->Add(combo_diameter, 1, wxRIGHT, FromDIP(5));
hsizer->Add(combo_flow , 1);
vsizer->AddSpacer(FromDIP(16)); // spacing for title and control vsizer->AddSpacer(FromDIP(16)); // spacing for title and control
if (index < 0) { if (index < 0) {
@@ -1452,14 +1504,137 @@ ExtruderGroup::ExtruderGroup(wxWindow * parent, int index, wxString const &title
vsizer->Add(ams_panel, 0, wxEXPAND | wxLEFT | wxRIGHT , FromDIP(5)); vsizer->Add(ams_panel, 0, wxEXPAND | wxLEFT | wxRIGHT , FromDIP(5));
vsizer->AddSpacer(FromDIP(2)); vsizer->AddSpacer(FromDIP(2));
} }
vsizer->Add(hsizer, 0, wxEXPAND | wxLEFT | wxRIGHT | wxBOTTOM, FromDIP(5));
// Nozzle rows. The first one is the only one for a printer whose nozzles are not listed
// individually; a multi-extruder printer grows one row per tool (see SetNozzleRowCount).
// The rows are dealt into two columns, which halves the height of a long tool list; the second
// column empties itself when there is a single row, so a one-row card still spans the width.
auto rows_sizer = new wxBoxSizer(wxHORIZONTAL);
for (wxBoxSizer *&column : row_columns) {
column = new wxBoxSizer(wxVERTICAL);
rows_sizer->Add(column, 1, wxEXPAND);
}
vsizer->Add(rows_sizer, 0, wxEXPAND);
sizer = vsizer; // the floating filament-switch icon positions itself against this card
SetSizer(vsizer); SetSizer(vsizer);
SetNozzleRowCount(1);
Layout(); Layout();
AMSCountPopupWindow::UpdateAMSCount(index < 0 ? 0 : index, this); AMSCountPopupWindow::UpdateAMSCount(index < 0 ? 0 : index, this);
} }
ExtruderGroup::NozzleRow ExtruderGroup::create_nozzle_row()
{
const size_t row_index = rows.size();
NozzleRow row;
row.prefix = new Label(this, Label::Body_10.Bold());
row.prefix->SetForegroundColour(StateColor::darkModeColorFor(wxColour("#363636")));
row.prefix->SetLabelText(wxString::Format("T%d", int(row_index) + 1));
row.prefix->Hide();
row.diameter_label = new Label(this, Label::Body_10);
row.diameter_label->SetForegroundColour(StateColor::darkModeColorFor(wxColour("#363636")));
row.diameter_label->Hide();
auto combo_flow = new ComboBox(this, wxID_ANY, wxString(""), wxDefaultPosition, wxDefaultSize, 0, nullptr, wxCB_READONLY);
combo_flow->GetDropDown().SetUseContentWidth(true);
combo_flow->Bind(wxEVT_COMBOBOX, [this, row_index, combo_flow](wxCommandEvent &evt) {
// A one-row card edits the extruder it was created for, a multi-row card one extruder per row.
const int index = nozzle_row_count > 1 ? int(row_index) : group_index;
auto printer_tab = dynamic_cast<TabPrinter *>(wxGetApp().get_tab(Preset::TYPE_PRINTER));
NozzleVolumeType volume_type = NozzleVolumeType(intptr_t(combo_flow->GetClientData(evt.GetInt())));
printer_tab->set_extruder_volume_type(index, volume_type);
auto plater = GUI::wxGetApp().plater();
if (plater) {
// A new Flow type invalidates the per-filament volume choices stored on the
// plates for this extruder; rewrite them so the next apply/grouping sees the
// selected volume instead of a stale one.
plater->update_filament_volume_map(index, static_cast<int>(volume_type));
plater->update_machine_sync_status();
}
});
combo_flow->SetToolTip(_L("Flow"));
row.flow = combo_flow;
use_nozzle_row_font(combo_flow);
auto row_sizer = new wxBoxSizer(wxHORIZONTAL);
row_sizer->Add(row.prefix, 0, wxALIGN_CENTER_VERTICAL | wxRIGHT, FromDIP(3));
if (row_index == 0)
row_sizer->Add(combo_diameter, 1, wxRIGHT, FromDIP(5));
row_sizer->Add(row.diameter_label, 0, wxALIGN_CENTER_VERTICAL | wxRIGHT, FromDIP(5));
row_sizer->Add(row.flow, 1);
// Row-major: T1 T2 on the first line, T3 T4 on the second, and so on.
row_columns[row_index % 2]->Add(row_sizer, 0, wxEXPAND | wxLEFT | wxRIGHT | wxBOTTOM, FromDIP(5));
return row;
}
void ExtruderGroup::update_row_widths()
{
// Measured from the rows on screen rather than fixed, so no gap is reserved for a "T10" or a
// "0.25 mm" that no row carries while the flow combos of a column still line up.
wxSize prefix_width(0, -1);
wxSize diameter_width(0, -1);
for (const NozzleRow &row : rows) {
if (row.prefix->IsShown())
prefix_width.x = std::max(prefix_width.x, row.prefix->GetTextExtent(row.prefix->GetLabelText()).x);
if (row.diameter_label->IsShown())
diameter_width.x = std::max(diameter_width.x, row.diameter_label->GetTextExtent(row.diameter_label->GetLabelText()).x);
}
for (const NozzleRow &row : rows) {
row.prefix->SetMinSize(prefix_width);
row.diameter_label->SetMinSize(diameter_width);
}
}
void ExtruderGroup::SetRowDiameters(const std::vector<double> &diameters)
{
for (size_t i = 0; i < std::min(nozzle_row_count, diameters.size()); ++i) {
// Spelled like the calibration dialogs spell a nozzle diameter, "0.4 mm".
const wxString text = from_u8(get_diameter_string(diameters[i])) + " mm";
rows[i].diameter_label->SetLabelText(text);
rows[i].diameter_label->SetToolTip(text);
}
update_row_widths();
Layout();
}
void ExtruderGroup::SetNozzleRowCount(size_t count)
{
const size_t row_count = std::min(count, size_t(MAXIMUM_EXTRUDER_NUMBER));
if (row_count == nozzle_row_count)
return;
nozzle_row_count = row_count;
// Every row names its tool as soon as there is more than one, so a single-nozzle card keeps the
// plain "Nozzle" look. A row is shown by showing its widgets: a sizer item holding a sub-sizer
// reports itself as shown while any of its windows is, so hiding the sub-sizer alone would
// leave the row in the layout.
for (size_t i = 0; i < row_count; ++i) {
if (i >= rows.size())
rows.push_back(create_nozzle_row());
rows[i].prefix->Show(row_count > 1);
rows[i].diameter_label->Show(group_index < 0); // the dual left/right cards show combo_diameter
// The flow combo's own visibility is decided with its choices, see update_extruder_variant.
}
for (size_t i = row_count; i < rows.size(); ++i) {
rows[i].prefix->Hide();
rows[i].diameter_label->Hide();
rows[i].flow->Hide();
}
// The rows just changed: re-measure them, then let the card and its containers give it the new room.
update_row_widths();
Layout();
if (wxWindow *parent = GetParent()) {
parent->Layout();
if (wxWindow *grand_parent = parent->GetParent())
grand_parent->Layout();
}
}
void ExtruderGroup::update_ams() void ExtruderGroup::update_ams()
{ {
static AMSinfo info4; static AMSinfo info4;
@@ -1588,9 +1763,19 @@ bool Sidebar::priv::switch_diameter(bool single)
diameter = diameter_left; diameter = diameter_left;
} }
} }
return switch_diameter_to(diameter);
}
bool Sidebar::priv::switch_diameter_to(const wxString &diameter)
{
// ORCA: Check if the selected diameter matches the current nozzle diameter in the config // ORCA: Check if the selected diameter matches the current nozzle diameter in the config
Preset& printer_preset = wxGetApp().preset_bundle->printers.get_edited_preset(); Preset& printer_preset = wxGetApp().preset_bundle->printers.get_edited_preset();
// The combo lists printer variants, and the variant of a mixed-nozzle machine ("0.4+0.6") is no
// single extruder's diameter, so the preset's own variant answers first.
if (printer_preset.config.opt_string("printer_variant") == diameter.ToStdString()) {
return true;
}
auto* nozzle_diameter = dynamic_cast<const ConfigOptionFloats*>(printer_preset.config.option("nozzle_diameter")); auto* nozzle_diameter = dynamic_cast<const ConfigOptionFloats*>(printer_preset.config.option("nozzle_diameter"));
if (nozzle_diameter && nozzle_diameter->size() > 0) { if (nozzle_diameter && nozzle_diameter->size() > 0) {
auto current_nozzle_dia = get_diameter_string(nozzle_diameter->values[0]); auto current_nozzle_dia = get_diameter_string(nozzle_diameter->values[0]);
@@ -2679,7 +2864,8 @@ Sidebar::Sidebar(Plater *parent)
p->combo_nozzle_dia->wxEvtHandler::ProcessEvent(evt); p->combo_nozzle_dia->wxEvtHandler::ProcessEvent(evt);
}); });
p->label_nozzle_title = new Label(p->panel_nozzle_dia, _L("Nozzle"), LB_PROPAGATE_MOUSE_EVENT); // "Variant", not "Nozzle": picking one switches the printer preset (see panel_nozzle_dia).
p->label_nozzle_title = new Label(p->panel_nozzle_dia, _L("Variant"), LB_PROPAGATE_MOUSE_EVENT);
p->label_nozzle_title->SetFont(Label::Body_10); p->label_nozzle_title->SetFont(Label::Body_10);
p->combo_nozzle_dia = new ComboBox(p->panel_nozzle_dia, wxID_ANY, wxString(""), wxDefaultPosition, wxDefaultSize, 0, nullptr, wxCB_READONLY); p->combo_nozzle_dia = new ComboBox(p->panel_nozzle_dia, wxID_ANY, wxString(""), wxDefaultPosition, wxDefaultSize, 0, nullptr, wxCB_READONLY);
@@ -2688,12 +2874,15 @@ Sidebar::Sidebar(Plater *parent)
p->combo_nozzle_dia->SetMinSize(FromDIP(wxSize(PRINTER_PANEL_SIZE.GetWidth() - 4, 26))); // requires a static value in here p->combo_nozzle_dia->SetMinSize(FromDIP(wxSize(PRINTER_PANEL_SIZE.GetWidth() - 4, 26))); // requires a static value in here
p->combo_nozzle_dia->SetMaxSize(FromDIP(wxSize(PRINTER_PANEL_SIZE.GetWidth() - 4, 26))); // using -1 with wxEXPAND has issues p->combo_nozzle_dia->SetMaxSize(FromDIP(wxSize(PRINTER_PANEL_SIZE.GetWidth() - 4, 26))); // using -1 with wxEXPAND has issues
p->combo_nozzle_dia->Bind(wxEVT_COMBOBOX, [this](auto &e) { p->combo_nozzle_dia->Bind(wxEVT_COMBOBOX, [this](auto &e) {
auto evt_combo = (*p->single_extruder).combo_diameter; // This box is the machine-variant selector, so it switches the printer preset directly
evt_combo->SetSelection(e.GetSelection()); // instead of feeding an extruder card's combo. Deferred, because switching preset
wxCommandEvent evt(wxEVT_COMBOBOX, evt_combo->GetId()); // rebuilds this very combo.
evt.SetEventObject(evt_combo); const wxString diameter = p->combo_nozzle_dia->GetValue();
evt.SetInt(e.GetSelection()); p->combo_nozzle_dia->CallAfter([this, diameter]() {
wxPostEvent(evt_combo, evt); p->is_switching_diameter = true;
p->switch_diameter_to(diameter);
p->is_switching_diameter = false;
});
e.Skip(); e.Skip();
}); });
// ORCA paint whole combobox on focus // ORCA paint whole combobox on focus
@@ -3673,8 +3862,16 @@ void Sidebar::update_presets(Preset::Type preset_type)
auto diameters = wxGetApp().preset_bundle->printers.diameters_of_selected_printer(); auto diameters = wxGetApp().preset_bundle->printers.diameters_of_selected_printer();
auto diameter = printer_preset.config.opt_string("printer_variant"); auto diameter = printer_preset.config.opt_string("printer_variant");
auto extruder_max_nozzle_count = printer_preset.config.option<ConfigOptionIntsNullable>("extruder_max_nozzle_count"); auto extruder_max_nozzle_count = printer_preset.config.option<ConfigOptionIntsNullable>("extruder_max_nozzle_count");
auto update_extruder_variant = [printer_model, extruders_def, extruders, nozzle_volumes_def, nozzle_volumes, extruder_variants,diameter,extruder_max_nozzle_count](ExtruderGroup & extruder, int index) { auto update_extruder_variant = [printer_model, extruders_def, extruders, nozzle_volumes_def, nozzle_volumes, extruder_variants,diameter,extruder_max_nozzle_count](ExtruderGroup & extruder, size_t row, int index) {
extruder.combo_flow->Clear(); ComboBox *combo_flow = extruder.rows[row].flow;
combo_flow->Clear();
// A profile may leave the per-extruder lists shorter than the nozzle count (they are
// padded when the printer is loaded, but a hand-written preset need not be).
if (index >= int(extruder_variants->values.size()) || index >= int(extruders->values.size()) ||
index >= int(nozzle_volumes->values.size())) {
combo_flow->Hide();
return;
}
auto type = extruders_def->enum_labels[extruders->values[index]]; auto type = extruders_def->enum_labels[extruders->values[index]];
int select = -1; int select = -1;
for (size_t i = 0; i < nozzle_volumes_def->enum_labels.size(); ++i) { for (size_t i = 0; i < nozzle_volumes_def->enum_labels.size(); ++i) {
@@ -3684,26 +3881,36 @@ void Sidebar::update_presets(Preset::Type preset_type)
if (boost::algorithm::contains(extruder_variants->values[index], type + " " + nozzle_volumes_def->enum_labels[i]) || 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() && (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)) { 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" || auto cur_volume_type = nozzle_volumes_def->enum_keys_map->at(nozzle_volumes_def->enum_values[i]);
// Defensive: profiles restrict E3D to 0.4 / 0.6; keep it out elsewhere.
if (cur_volume_type == NozzleVolumeType::nvtE3DHighFlow && diameter != "0.4" && diameter != "0.6")
continue;
if (cur_volume_type == NozzleVolumeType::nvtHighFlow && (diameter == "0.2" ||
is_skip_high_flow_printer(printer_model))) is_skip_high_flow_printer(printer_model)))
continue; continue;
if (nozzle_volumes->values[index] == i) // The client data is the enum value, not the label position: E3D High Flow is 5
select = extruder.combo_flow->GetCount(); // but sits at position 4, and a position would write an invalid type.
extruder.combo_flow->Append(_L(nozzle_volumes_def->enum_labels[i]), {}, (void*)i); if (nozzle_volumes->values[index] == cur_volume_type)
select = combo_flow->GetCount();
combo_flow->Append(_L(nozzle_volumes_def->enum_labels[i]), {}, (void*)(intptr_t)cur_volume_type);
} }
} }
if (select == -1) if (select == -1)
select = extruder.combo_flow->GetCount() - 1; select = combo_flow->GetCount() - 1;
extruder.combo_flow->SetSelection(select); combo_flow->SetSelection(select);
// No flow variant for this extruder: the diameter keeps the whole row.
combo_flow->Show(combo_flow->GetCount() > 0);
}; };
auto update_extruder_diameter = [&diameters, &nozzle_diameter](int extruder_index,ExtruderGroup & extruder) { auto update_extruder_diameter = [&diameters, &nozzle_diameter](int extruder_index,ExtruderGroup & extruder) {
extruder.combo_diameter->Clear(); extruder.combo_diameter->Clear();
if (extruder_index >= int(nozzle_diameter->values.size()))
return;
int select = -1; int select = -1;
// ORCA get the actual nozzle diameter from printer config // ORCA get the actual nozzle diameter from printer config
auto nozzle_dia = get_diameter_string(nozzle_diameter->values[extruder_index]); auto nozzle_dia = get_diameter_string(nozzle_diameter->values[extruder_index]);
// ORCA try to add nozzle diameter from config if list is empty. fixes blank nozzle combo box when preset has no alias // ORCA try to add nozzle diameter from config if list is empty. fixes blank nozzle combo box when preset has no alias
if(diameters[0].empty() && !nozzle_dia.empty()){ if(!diameters.empty() && diameters[0].empty() && !nozzle_dia.empty()){
diameters[0] = nozzle_dia; diameters[0] = nozzle_dia;
} }
// Orca: Check if the actual nozzle diameter exists in the list, if not add it as a custom option // Orca: Check if the actual nozzle diameter exists in the list, if not add it as a custom option
@@ -3719,14 +3926,15 @@ void Sidebar::update_presets(Preset::Type preset_type)
extruder.diameter = nozzle_dia; extruder.diameter = nozzle_dia;
}; };
auto image_path = get_cur_select_bed_image(); auto image_path = get_cur_select_bed_image();
if (is_dual_extruder) { const bool multi_extruder_rows = p->single_extruder->NozzleRowCount() > 1;
if (is_dual_extruder && !multi_extruder_rows) {
std::string printer_type = printer_preset.get_printer_type(wxGetApp().preset_bundle); std::string printer_type = printer_preset.get_printer_type(wxGetApp().preset_bundle);
p->left_extruder->SetTitle(_L(DevPrinterConfigUtil::get_toolhead_display_name(printer_type, DEPUTY_EXTRUDER_ID, ToolHeadComponent::Nozzle, ToolHeadNameCase::TitleCase))); p->left_extruder->SetTitle(_L(DevPrinterConfigUtil::get_toolhead_display_name(printer_type, DEPUTY_EXTRUDER_ID, ToolHeadComponent::Nozzle, ToolHeadNameCase::TitleCase)));
p->right_extruder->SetTitle(_L(DevPrinterConfigUtil::get_toolhead_display_name(printer_type, MAIN_EXTRUDER_ID, ToolHeadComponent::Nozzle, ToolHeadNameCase::TitleCase))); p->right_extruder->SetTitle(_L(DevPrinterConfigUtil::get_toolhead_display_name(printer_type, MAIN_EXTRUDER_ID, ToolHeadComponent::Nozzle, ToolHeadNameCase::TitleCase)));
AMSCountPopupWindow::UpdateAMSCount(0, p->left_extruder); AMSCountPopupWindow::UpdateAMSCount(0, p->left_extruder);
AMSCountPopupWindow::UpdateAMSCount(1, p->right_extruder); AMSCountPopupWindow::UpdateAMSCount(1, p->right_extruder);
update_extruder_variant(*p->left_extruder, 0); update_extruder_variant(*p->left_extruder, 0, 0);
update_extruder_variant(*p->right_extruder, 1); update_extruder_variant(*p->right_extruder, 0, 1);
//if (!p->is_switching_diameter) { //if (!p->is_switching_diameter) {
update_extruder_diameter(0, *p->left_extruder); update_extruder_diameter(0, *p->left_extruder);
update_extruder_diameter(1, *p->right_extruder); update_extruder_diameter(1, *p->right_extruder);
@@ -3734,15 +3942,27 @@ void Sidebar::update_presets(Preset::Type preset_type)
p->image_printer_bed->SetBitmap(create_scaled_bitmap(image_path, this, PRINTER_THUMBNAIL_SIZE.GetHeight())); p->image_printer_bed->SetBitmap(create_scaled_bitmap(image_path, this, PRINTER_THUMBNAIL_SIZE.GetHeight()));
} else { } else {
AMSCountPopupWindow::UpdateAMSCount(0, p->single_extruder); AMSCountPopupWindow::UpdateAMSCount(0, p->single_extruder);
update_extruder_variant(*p->single_extruder, 0); for (size_t row = 0; row < p->single_extruder->NozzleRowCount(); ++row)
update_extruder_variant(*p->single_extruder, row, int(row));
//if (!p->is_switching_diameter) //if (!p->is_switching_diameter)
update_extruder_diameter(0, *p->single_extruder); update_extruder_diameter(0, *p->single_extruder);
// The card reads the nozzles out, the variant box above is what switches the machine.
p->single_extruder->SetRowDiameters(nozzle_diameter->values);
if (multi_extruder_rows) {
// The hidden left/right cards keep mirroring the first extruders' diameters: the
// device sync path (a connected Bambu printer or a printer agent) still reads them.
update_extruder_diameter(0, *p->left_extruder);
update_extruder_diameter(1, *p->right_extruder);
}
// ORCA sync unified nozzle combo box // ORCA sync unified nozzle combo box
p->combo_nozzle_dia->Clear(); p->combo_nozzle_dia->Clear();
for (size_t i = 0; i < diameters.size(); ++i) for (size_t i = 0; i < diameters.size(); ++i)
p->combo_nozzle_dia->Append(diameters[i], {}); p->combo_nozzle_dia->Append(diameters[i], {});
p->combo_nozzle_dia->SetSelection((*p->single_extruder).combo_diameter->GetSelection()); // Prefer the variant the preset names: a mixed-nozzle machine reads "0.4+0.6", which is
// no single extruder's diameter.
const int variant = p->combo_nozzle_dia->FindString(diameter);
p->combo_nozzle_dia->SetSelection(variant != wxNOT_FOUND ? variant : (*p->single_extruder).combo_diameter->GetSelection());
// ORCA update nozzle type // ORCA update nozzle type
const auto& full_config = wxGetApp().preset_bundle->full_config(); const auto& full_config = wxGetApp().preset_bundle->full_config();
+1
View File
@@ -1793,6 +1793,7 @@ wxString PrinterPartsDialog::GetString(NozzleFlowType nozzle_flow_type) const {
case Slic3r::S_FLOW: return _L("Standard"); case Slic3r::S_FLOW: return _L("Standard");
case Slic3r::H_FLOW: return _L("High flow"); case Slic3r::H_FLOW: return _L("High flow");
case Slic3r::U_FLOW: return _L("TPU High flow"); case Slic3r::U_FLOW: return _L("TPU High flow");
case Slic3r::E_FLOW: return _L("E3D High Flow");
default: break; default: break;
} }
+3
View File
@@ -75,6 +75,9 @@ std::string get_nozzle_volume_type_cloud_string(NozzleVolumeType nozzle_volume_t
else if (nozzle_volume_type == NozzleVolumeType::nvtTPUHighFlow) { else if (nozzle_volume_type == NozzleVolumeType::nvtTPUHighFlow) {
return "tpu_high_flow"; return "tpu_high_flow";
} }
else if (nozzle_volume_type == NozzleVolumeType::nvtE3DHighFlow) {
return "e3d_high_flow";
}
else if (nozzle_volume_type == NozzleVolumeType::nvtHybrid) { else if (nozzle_volume_type == NozzleVolumeType::nvtHybrid) {
// to be supported // to be supported
return "hybrid_flow"; return "hybrid_flow";
+4
View File
@@ -1516,6 +1516,8 @@ bool SyncAmsInfoDialog::is_nozzle_type_match(DevExtderSystem data, wxString &err
used_extruders_flow[used_extruders[i]] = "Standard"; used_extruders_flow[used_extruders[i]] = "Standard";
} else if (nozzle_volume_type == NozzleVolumeType::nvtTPUHighFlow) { } else if (nozzle_volume_type == NozzleVolumeType::nvtTPUHighFlow) {
used_extruders_flow[used_extruders[i]] = "TPU High Flow"; used_extruders_flow[used_extruders[i]] = "TPU High Flow";
} else if (nozzle_volume_type == NozzleVolumeType::nvtE3DHighFlow) {
used_extruders_flow[used_extruders[i]] = "E3D High Flow";
} else { } else {
used_extruders_flow[used_extruders[i]] = "High Flow"; used_extruders_flow[used_extruders[i]] = "High Flow";
} }
@@ -1533,6 +1535,8 @@ bool SyncAmsInfoDialog::is_nozzle_type_match(DevExtderSystem data, wxString &err
flow_type_of_machine.push_back("Standard"); flow_type_of_machine.push_back("Standard");
} else if (it->GetNozzleFlowType() == NozzleFlowType::U_FLOW) { } else if (it->GetNozzleFlowType() == NozzleFlowType::U_FLOW) {
flow_type_of_machine.push_back("TPU High Flow"); flow_type_of_machine.push_back("TPU High Flow");
} else if (it->GetNozzleFlowType() == NozzleFlowType::E_FLOW) {
flow_type_of_machine.push_back("E3D High Flow");
} }
} }
+41 -12
View File
@@ -506,7 +506,7 @@ void Tab::create_preset_tab()
if (dynamic_cast<TabPrinter *>(this) || dynamic_cast<TabPrint *>(this)) { if (dynamic_cast<TabPrinter *>(this) || dynamic_cast<TabPrint *>(this)) {
m_extruder_switch = new MultiSwitchButton(panel); m_extruder_switch = new MultiSwitchButton(panel);
m_extruder_switch->SetMaxSize({em_unit(this) * 40, -1}); m_extruder_switch->SetFitToOptions();
m_extruder_switch->Bind(wxCUSTOMEVT_MULTISWITCH_SELECTION, [this](auto &evt) { m_extruder_switch->Bind(wxCUSTOMEVT_MULTISWITCH_SELECTION, [this](auto &evt) {
evt.Skip(); evt.Skip();
int selection = evt.GetInt(); int selection = evt.GetInt();
@@ -539,7 +539,8 @@ void Tab::create_preset_tab()
auto right_sizer = new wxBoxSizer(wxHORIZONTAL); auto right_sizer = new wxBoxSizer(wxHORIZONTAL);
m_variant_sizer->AddStretchSpacer(1); m_variant_sizer->AddStretchSpacer(1);
m_variant_sizer->Add(m_extruder_switch, 0, wxALIGN_CENTER, 0); // Orca: proportion 1 lets a narrow row squeeze the switch, which then scrolls its buttons.
m_variant_sizer->Add(m_extruder_switch, 1, wxALIGN_CENTER, 0);
m_variant_sizer->Add(right_sizer, 1, wxALIGN_CENTER); m_variant_sizer->Add(right_sizer, 1, wxALIGN_CENTER);
right_sizer->AddStretchSpacer(1); right_sizer->AddStretchSpacer(1);
right_sizer->Add(m_extruder_sync_box, 0, wxALIGN_CENTER | wxRIGHT, m_em_unit); right_sizer->Add(m_extruder_sync_box, 0, wxALIGN_CENTER | wxRIGHT, m_em_unit);
@@ -8172,7 +8173,7 @@ void Tab::update_extruder_variants(int extruder_id, bool reload)
// Orca: a non-Bambu dual-nozzle printer has two extruders but a single variant column, so // Orca: a non-Bambu dual-nozzle printer has two extruders but a single variant column, so
// the nozzle switch and sync button have nothing to act on. Only enable with real variants. // the nozzle switch and sync button have nothing to act on. Only enable with real variants.
if (extruder_nums == 2 && m_preset_bundle->support_different_extruders()) { if (extruder_nums >= 2 && m_preset_bundle->support_different_extruders()) {
auto options = generate_extruder_options(); auto options = generate_extruder_options();
m_extruder_switch->SetOptions(options); m_extruder_switch->SetOptions(options);
@@ -8219,6 +8220,23 @@ void Tab::update_extruder_variants(int extruder_id, bool reload)
} }
} }
// The variant switch tags are the narrowest place a volume type is named, so they abbreviate it;
// the flow combo boxes and the Printer tab's parameter labels keep the full names.
static wxString short_nozzle_volume_name(const std::string &volume_name)
{
// Hybrid has no entry on purpose: it is never a variant string, and the switch lists its two
// sub-nozzle types as separate tags (see generate_extruder_options).
if (volume_name == "Standard")
return "SF";
if (volume_name == "High Flow")
return "HF";
if (volume_name == "TPU High Flow")
return "TPU HF";
if (volume_name == "E3D High Flow")
return "E3D HF";
return from_u8(volume_name);
}
std::vector<wxString> Tab::generate_extruder_options() std::vector<wxString> Tab::generate_extruder_options()
{ {
std::vector<wxString> options; std::vector<wxString> options;
@@ -8259,7 +8277,7 @@ std::vector<wxString> Tab::generate_extruder_options()
nozzle = ""; nozzle = "";
} }
} }
options.push_back(wxString::Format(_L("%s: %s"), _L(drive), _L(nozzle))); options.push_back(wxString::Format(_L("%s: %s"), _L(drive), short_nozzle_volume_name(nozzle)));
} }
return options; return options;
} }
@@ -8273,18 +8291,22 @@ std::vector<wxString> Tab::generate_extruder_options()
} }
std::string pt = m_preset_bundle->printers.get_edited_preset().get_printer_type(m_preset_bundle); std::string pt = m_preset_bundle->printers.get_edited_preset().get_printer_type(m_preset_bundle);
// Orca: the main/deputy toolhead names describe a dual-nozzle printer, where extruder 0 is the
// left (deputy) and extruder 1 the right (main) nozzle. From three extruders on the tools are
// interchangeable, so name them by index instead of repeating one side.
for (int i = 0; i < extruder_nums; ++i) { for (int i = 0; i < extruder_nums; ++i) {
int ext_id = (i == 0) ? DEPUTY_EXTRUDER_ID : MAIN_EXTRUDER_ID; wxString extruder_name = extruder_nums > 2 ? wxString::Format("T%d", i + 1) :
wxString extruder_name = _L(DevPrinterConfigUtil::get_toolhead_display_name( _L(DevPrinterConfigUtil::get_toolhead_display_name(
pt, ext_id, ToolHeadComponent::Nozzle, ToolHeadNameCase::TitleCase, true)); pt, (i == 0) ? DEPUTY_EXTRUDER_ID : MAIN_EXTRUDER_ID,
ToolHeadComponent::Nozzle, ToolHeadNameCase::TitleCase, true));
NozzleVolumeType volume_type = NozzleVolumeType(nozzle_volumes->values[i]); NozzleVolumeType volume_type = NozzleVolumeType(nozzle_volumes->values[i]);
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, short_nozzle_volume_name(get_nozzle_volume_type_string(NozzleVolumeType::nvtStandard))));
options.push_back(wxString::Format(_L("%s: %s"), extruder_name, _L("High Flow"))); options.push_back(wxString::Format(_L("%s: %s"), extruder_name, short_nozzle_volume_name(get_nozzle_volume_type_string(NozzleVolumeType::nvtHighFlow))));
} else { } else {
wxString volume_name = get_nozzle_volume_type_name(volume_type); options.push_back(wxString::Format(_L("%s: %s"), extruder_name,
options.push_back(wxString::Format(_L("%s: %s"), extruder_name, volume_name)); short_nozzle_volume_name(get_nozzle_volume_type_string(volume_type))));
} }
} }
return options; return options;
@@ -8315,7 +8337,10 @@ bool Tab::get_extruder_sync_enable_state(int extruder_id)
Preset& printer_preset = m_preset_bundle->printers.get_edited_preset(); Preset& printer_preset = m_preset_bundle->printers.get_edited_preset();
auto nozzle_volumes = m_preset_bundle->project_config.option<ConfigOptionEnumsGeneric>("nozzle_volume_type"); auto nozzle_volumes = m_preset_bundle->project_config.option<ConfigOptionEnumsGeneric>("nozzle_volume_type");
auto extruders = printer_preset.config.option<ConfigOptionEnumsGeneric>("extruder_type"); auto extruders = printer_preset.config.option<ConfigOptionEnumsGeneric>("extruder_type");
if (nozzle_volumes->values.size() < 2 || extruders->values.size() < 2) { // Orca: every rule below describes the two toolheads of a dual-nozzle printer as left/right.
// A printer with any other extruder count has no single counterpart to copy to, so it gets no
// sync button (a toolchanger would need a target to be chosen).
if (nozzle_volumes->values.size() != 2 || extruders->values.size() != 2) {
return false; return false;
} }
@@ -8456,6 +8481,10 @@ void Tab::sync_excluder()
ExtruderType(extruders->values[extruder_id]), nozzle_type, variant_keys.second, stride); ExtruderType(extruders->values[extruder_id]), nozzle_type, variant_keys.second, stride);
}; };
int active_index = get_current_active_extruder(); int active_index = get_current_active_extruder();
// The button copies to the other toolhead, so it is only offered when that other one exists;
// without this the `1 - active_index` below would index an extruder that is not there.
if (!get_extruder_sync_enable_state(active_index))
return;
auto active_nozzle = get_actual_nozzle_volume_type(active_index); auto active_nozzle = get_actual_nozzle_volume_type(active_index);
int from_index = get_index_for_extruder(active_index, active_nozzle); int from_index = get_index_for_extruder(active_index, active_nozzle);
int dest_index = get_index_for_extruder(1 - active_index, active_nozzle); int dest_index = get_index_for_extruder(1 - active_index, active_nozzle);
+47 -47
View File
@@ -70,8 +70,8 @@ int getExtruderNozzleCountTotal(PresetBundle *preset_bundle, int extruder_id)
// ---- ManualNozzleCountDialog ---------------------------------------------------------------------------------- // ---- ManualNozzleCountDialog ----------------------------------------------------------------------------------
ManualNozzleCountDialog::ManualNozzleCountDialog( ManualNozzleCountDialog::ManualNozzleCountDialog(wxWindow *parent, NozzleVolumeType volume_type, int standard_count, int highflow_count, int e3d_count,
wxWindow *parent, NozzleVolumeType volume_type, int standard_count, int highflow_count, int max_nozzle_count, bool force_no_zero) int max_nozzle_count, bool force_no_zero, const std::set<NozzleVolumeType> &supported_types)
: DPIDialog(parent, wxID_ANY, _L("Set nozzle count"), wxDefaultPosition, wxDefaultSize, wxCAPTION | wxCLOSE_BOX) : DPIDialog(parent, wxID_ANY, _L("Set nozzle count"), wxDefaultPosition, wxDefaultSize, wxCAPTION | wxCLOSE_BOX)
{ {
SetBackgroundColour(*wxWHITE); SetBackgroundColour(*wxWHITE);
@@ -93,43 +93,39 @@ ManualNozzleCountDialog::ManualNozzleCountDialog(
for (int i = 0; i <= max_nozzle_count; ++i) for (int i = 0; i <= max_nozzle_count; ++i)
nozzle_choices.Add(wxString::Format("%d", i)); nozzle_choices.Add(wxString::Format("%d", i));
// A Hybrid extruder mixes Standard and High Flow nozzles, so it gets both count choices; the concrete // A Hybrid extruder mixes several nozzle volume types, so it gets a count choice for each type the
// types get exactly one. // extruder actually has; a concrete type gets exactly its own, which it supports by definition.
if (volume_type == nvtStandard || volume_type == nvtHybrid) { auto shows = [volume_type, &supported_types](NozzleVolumeType type) {
wxBoxSizer *standard_sizer = new wxBoxSizer(wxHORIZONTAL); return volume_type == type || (volume_type == nvtHybrid && (supported_types.empty() || supported_types.count(type) > 0));
auto standard_label = new wxStaticText(content, wxID_ANY, _L(get_nozzle_volume_type_string(nvtStandard)), wxDefaultPosition, wxSize(FromDIP(100), -1)); };
standard_label->SetFont(Label::Body_14); auto add_count_choice = [this, content, choice_sizer, &nozzle_choices](NozzleVolumeType type, int count) {
standard_label->SetForegroundColour(StateColor::darkModeColorFor(wxColour("#363636"))); wxBoxSizer *row_sizer = new wxBoxSizer(wxHORIZONTAL);
standard_sizer->Add(standard_label, 0, wxALIGN_CENTER_VERTICAL); auto label = new wxStaticText(content, wxID_ANY, _L(get_nozzle_volume_type_string(type)), wxDefaultPosition, wxSize(FromDIP(100), -1));
m_standard_choice = new ComboBox(content, wxID_ANY, "", wxDefaultPosition, wxSize(FromDIP(80), -1), 0, nullptr, wxCB_READONLY); label->SetFont(Label::Body_14);
std::vector<wxString>::iterator iter; label->SetForegroundColour(StateColor::darkModeColorFor(wxColour("#363636")));
for (iter = nozzle_choices.begin(); iter != nozzle_choices.end(); iter++) row_sizer->Add(label, 0, wxALIGN_CENTER_VERTICAL);
m_standard_choice->Append(*iter); auto choice = new ComboBox(content, wxID_ANY, "", wxDefaultPosition, wxSize(FromDIP(80), -1), 0, nullptr, wxCB_READONLY);
m_standard_choice->SetSelection(standard_count); for (const wxString &item : nozzle_choices)
standard_sizer->Add(m_standard_choice, 0, wxALIGN_CENTER_VERTICAL | wxLEFT, FromDIP(5)); choice->Append(item);
choice_sizer->Add(standard_sizer, 0, wxTOP | wxRIGHT, FromDIP(15)); choice->SetSelection(count);
} row_sizer->Add(choice, 0, wxALIGN_CENTER_VERTICAL | wxLEFT, FromDIP(5));
if (volume_type == nvtHighFlow || volume_type == nvtHybrid) { choice_sizer->Add(row_sizer, 0, wxTOP | wxRIGHT, FromDIP(15));
wxBoxSizer *highflow_sizer = new wxBoxSizer(wxHORIZONTAL); return choice;
auto highflow_label = new wxStaticText(content, wxID_ANY, _L(get_nozzle_volume_type_string(nvtHighFlow)), wxDefaultPosition, wxSize(FromDIP(100), -1)); };
highflow_label->SetFont(Label::Body_14); if (shows(nvtStandard))
highflow_label->SetForegroundColour(StateColor::darkModeColorFor(wxColour("#363636"))); m_standard_choice = add_count_choice(nvtStandard, standard_count);
highflow_sizer->Add(highflow_label, 0, wxALIGN_CENTER_VERTICAL); if (shows(nvtHighFlow))
m_highflow_choice = new ComboBox(content, wxID_ANY, "", wxDefaultPosition, wxSize(FromDIP(80), -1), 0, nullptr, wxCB_READONLY); m_highflow_choice = add_count_choice(nvtHighFlow, highflow_count);
std::vector<wxString>::iterator iter; if (shows(nvtE3DHighFlow))
for (iter = nozzle_choices.begin(); iter != nozzle_choices.end(); iter++) m_e3d_choice = add_count_choice(nvtE3DHighFlow, e3d_count);
m_highflow_choice->Append(*iter);
m_highflow_choice->SetSelection(highflow_count);
highflow_sizer->Add(m_highflow_choice, 0, wxALIGN_CENTER_VERTICAL | wxLEFT, FromDIP(5));
choice_sizer->Add(highflow_sizer, 0, wxTOP | wxRIGHT, FromDIP(15));
}
m_error_label = new wxStaticText(this, wxID_ANY, ""); m_error_label = new wxStaticText(this, wxID_ANY, "");
m_error_label->SetForegroundColour(wxColour("#E14747")); m_error_label->SetForegroundColour(wxColour("#E14747"));
m_error_label->Hide(); m_error_label->Hide();
auto update_nozzle_error = [this, force_no_zero, content, max_nozzle_count](int standard_count, int highflow_count) { auto update_nozzle_error = [this, force_no_zero, content, max_nozzle_count](wxCommandEvent &e) {
const int total_count = standard_count + highflow_count; // The Hybrid count is the sum over every choice on screen, whatever the extruder's own type.
const int total_count = GetNozzleCount(nvtHybrid);
if (0 < total_count && total_count <= max_nozzle_count && m_error_label->IsShown()) { if (0 < total_count && total_count <= max_nozzle_count && m_error_label->IsShown()) {
m_error_label->Hide(); m_error_label->Hide();
m_confirm_btn->Enable(); m_confirm_btn->Enable();
@@ -145,18 +141,12 @@ ManualNozzleCountDialog::ManualNozzleCountDialog(
Layout(); Layout();
Fit(); Fit();
} }
e.Skip();
}; };
if (m_standard_choice) for (ComboBox *choice : {m_standard_choice, m_highflow_choice, m_e3d_choice})
m_standard_choice->Bind(wxEVT_COMBOBOX, [this, update_nozzle_error](wxCommandEvent &e) { if (choice)
update_nozzle_error(m_standard_choice->GetSelection(), m_highflow_choice ? m_highflow_choice->GetSelection() : 0); choice->Bind(wxEVT_COMBOBOX, update_nozzle_error);
e.Skip();
});
if (m_highflow_choice)
m_highflow_choice->Bind(wxEVT_COMBOBOX, [this, update_nozzle_error](wxCommandEvent &e) {
update_nozzle_error(m_standard_choice ? m_standard_choice->GetSelection() : 0, m_highflow_choice->GetSelection());
e.Skip();
});
content_sizer->Add(nozzle_icon, 0, wxALL | wxALIGN_CENTER_VERTICAL, FromDIP(15)); content_sizer->Add(nozzle_icon, 0, wxALL | wxALIGN_CENTER_VERTICAL, FromDIP(15));
content_sizer->Add(choice_sizer, 0, wxALIGN_CENTRE_VERTICAL); content_sizer->Add(choice_sizer, 0, wxALIGN_CENTRE_VERTICAL);
@@ -181,9 +171,10 @@ int ManualNozzleCountDialog::GetNozzleCount(NozzleVolumeType volume_type) const
return m_standard_choice ? m_standard_choice->GetSelection() : 0; return m_standard_choice ? m_standard_choice->GetSelection() : 0;
if (volume_type == nvtHighFlow) if (volume_type == nvtHighFlow)
return m_highflow_choice ? m_highflow_choice->GetSelection() : 0; return m_highflow_choice ? m_highflow_choice->GetSelection() : 0;
if (volume_type == nvtE3DHighFlow)
return m_e3d_choice ? m_e3d_choice->GetSelection() : 0;
if (volume_type == nvtHybrid) if (volume_type == nvtHybrid)
return (m_standard_choice ? m_standard_choice->GetSelection() : 0) + return GetNozzleCount(nvtStandard) + GetNozzleCount(nvtHighFlow) + GetNozzleCount(nvtE3DHighFlow);
(m_highflow_choice ? m_highflow_choice->GetSelection() : 0);
return 0; return 0;
} }
@@ -318,6 +309,7 @@ void manuallySetNozzleCount(int extruder_id)
const NozzleVolumeType volume_type = NozzleVolumeType(nozzle_volume_type_opt->values[extruder_id]); const NozzleVolumeType volume_type = NozzleVolumeType(nozzle_volume_type_opt->values[extruder_id]);
const int standard_count = getExtruderNozzleCount(preset_bundle, extruder_id, nvtStandard); const int standard_count = getExtruderNozzleCount(preset_bundle, extruder_id, nvtStandard);
const int highflow_count = getExtruderNozzleCount(preset_bundle, extruder_id, nvtHighFlow); const int highflow_count = getExtruderNozzleCount(preset_bundle, extruder_id, nvtHighFlow);
const int e3d_count = getExtruderNozzleCount(preset_bundle, extruder_id, nvtE3DHighFlow);
// Require at least one nozzle for a Hybrid extruder (an empty mix is meaningless) and when the other // Require at least one nozzle for a Hybrid extruder (an empty mix is meaningless) and when the other
// extruder currently has none. // extruder currently has none.
@@ -325,11 +317,19 @@ void manuallySetNozzleCount(int extruder_id)
if (nozzle_volume_type_opt->values.size() > 1) if (nozzle_volume_type_opt->values.size() > 1)
force_no_zero |= getExtruderNozzleCountTotal(preset_bundle, 1 - extruder_id) == 0; force_no_zero |= getExtruderNozzleCountTotal(preset_bundle, 1 - extruder_id) == 0;
ManualNozzleCountDialog dialog(wxGetApp().plater(), volume_type, standard_count, highflow_count, max_nozzle_count->values[extruder_id], force_no_zero); // The printer profile's variant list is the authority on which volume types an extruder provides. An
// empty set means the profile could not be read, and the dialog then skips filtering instead of hiding
// everything.
const std::set<NozzleVolumeType> supported_types =
get_extruder_supported_nozzle_volume_types(preset_bundle->printers.get_edited_preset().config, extruder_id);
ManualNozzleCountDialog dialog(wxGetApp().plater(), volume_type, standard_count, highflow_count, e3d_count,
max_nozzle_count->values[extruder_id], force_no_zero, supported_types);
if (dialog.ShowModal() == wxID_OK) { if (dialog.ShowModal() == wxID_OK) {
if (volume_type == nvtHybrid) { if (volume_type == nvtHybrid) {
setExtruderNozzleCount(preset_bundle, extruder_id, nvtStandard, dialog.GetNozzleCount(nvtStandard), true); setExtruderNozzleCount(preset_bundle, extruder_id, nvtStandard, dialog.GetNozzleCount(nvtStandard), true);
setExtruderNozzleCount(preset_bundle, extruder_id, nvtHighFlow, dialog.GetNozzleCount(nvtHighFlow), false); setExtruderNozzleCount(preset_bundle, extruder_id, nvtHighFlow, dialog.GetNozzleCount(nvtHighFlow), false);
setExtruderNozzleCount(preset_bundle, extruder_id, nvtE3DHighFlow, dialog.GetNozzleCount(nvtE3DHighFlow), false);
} else { } else {
setExtruderNozzleCount(preset_bundle, extruder_id, volume_type, dialog.GetNozzleCount(volume_type), true); setExtruderNozzleCount(preset_bundle, extruder_id, volume_type, dialog.GetNozzleCount(volume_type), true);
} }
+5 -1
View File
@@ -28,6 +28,7 @@
#include <memory> #include <memory>
#include <optional> #include <optional>
#include <set>
#include <unordered_map> #include <unordered_map>
#include <vector> #include <vector>
@@ -64,7 +65,9 @@ struct NozzleOption
class ManualNozzleCountDialog : public DPIDialog class ManualNozzleCountDialog : public DPIDialog
{ {
public: public:
ManualNozzleCountDialog(wxWindow *parent, NozzleVolumeType volume_type, int standard_count, int highflow_count, int max_nozzle_count, bool force_no_zero); // supported_types lists the volume types the extruder physically provides; an empty set means "unknown", in which case no filtering is applied.
ManualNozzleCountDialog(wxWindow *parent, NozzleVolumeType volume_type, int standard_count, int highflow_count, int e3d_count, int max_nozzle_count, bool force_no_zero,
const std::set<NozzleVolumeType> &supported_types);
~ManualNozzleCountDialog() override = default; ~ManualNozzleCountDialog() override = default;
void on_dpi_changed(const wxRect &suggested_rect) override {} void on_dpi_changed(const wxRect &suggested_rect) override {}
int GetNozzleCount(NozzleVolumeType volume_type) const; int GetNozzleCount(NozzleVolumeType volume_type) const;
@@ -72,6 +75,7 @@ public:
private: private:
ComboBox *m_standard_choice{nullptr}; ComboBox *m_standard_choice{nullptr};
ComboBox *m_highflow_choice{nullptr}; ComboBox *m_highflow_choice{nullptr};
ComboBox *m_e3d_choice{nullptr};
Button *m_confirm_btn{nullptr}; Button *m_confirm_btn{nullptr};
wxStaticText *m_error_label{nullptr}; wxStaticText *m_error_label{nullptr};
}; };
+110 -11
View File
@@ -4,6 +4,8 @@
#include "StaticBox.hpp" #include "StaticBox.hpp"
#include "../wxExtensions.hpp" #include "../wxExtensions.hpp"
#include <wx/settings.h>
#include "../GUI_App.hpp" #include "../GUI_App.hpp"
#include "slic3r/GUI/I18N.hpp" #include "slic3r/GUI/I18N.hpp"
#include "../Utils/MacDarkMode.hpp" #include "../Utils/MacDarkMode.hpp"
@@ -620,19 +622,34 @@ MultiSwitchButton::MultiSwitchButton(wxWindow *parent, wxWindowID id, const wxPo
std::make_pair(0x6B6B6B, (int) StateColor::NotChecked), std::make_pair(0x6B6B6B, (int) StateColor::NotChecked),
std::make_pair(0xFFFFFE, (int) StateColor::Normal))) std::make_pair(0xFFFFFE, (int) StateColor::Normal)))
, m_button_radius(10.0) , m_button_radius(10.0)
, m_button_padding(10, 6) , m_button_padding(FromDIP(wxSize(11, 3)))
{ {
SetCornerRadius(m_button_radius); SetCornerRadius(m_button_radius);
SetBorderWidth(0); SetBorderWidth(0);
sizer = new wxBoxSizer(wxHORIZONTAL); // Orca: a switch can hold more buttons than the layout has room for (a toolchanger lists one per
auto *hsizer = new wxBoxSizer(wxVERTICAL); // tool), so they live in a scrolled area: the caller caps the switch at its natural width and
hsizer->Add(sizer, 1, wxEXPAND); // this scrolls horizontally instead of clipping the last buttons.
SetSizer(hsizer); m_scroll = new wxScrolledWindow(this, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxHSCROLL | wxBORDER_NONE);
SetMinSize(wxSize(-1, 20)); m_scroll->SetBackgroundColour(GetBackgroundColour());
// The buttons are a single row, so only the horizontal bar may ever appear: a vertical one would
// eat into the row's height.
m_scroll->ShowScrollbars(wxSHOW_SB_DEFAULT, wxSHOW_SB_NEVER);
m_scroll->EnableScrolling(true, false);
m_scroll->SetScrollRate(FromDIP(10), 0);
sizer = new wxBoxSizer(wxHORIZONTAL);
m_scroll->SetSizer(sizer);
auto *hsizer = new wxBoxSizer(wxVERTICAL);
hsizer->Add(m_scroll, 1, wxEXPAND);
SetSizer(hsizer);
SetMinSize(wxSize(-1, options_height()));
Bind(wxEVT_SIZE, &MultiSwitchButton::on_size, this);
Bind(wxEVT_COMMAND_BUTTON_CLICKED, &MultiSwitchButton::button_clicked, this); Bind(wxEVT_COMMAND_BUTTON_CLICKED, &MultiSwitchButton::button_clicked, this);
SetFont(Label::Body_12); // The tags name a tool and its volume type only, so they stay compact.
SetFont(Label::Body_10);
} }
MultiSwitchButton::~MultiSwitchButton() MultiSwitchButton::~MultiSwitchButton()
@@ -640,10 +657,88 @@ MultiSwitchButton::~MultiSwitchButton()
DeleteAllOptions(); DeleteAllOptions();
} }
int MultiSwitchButton::options_height() const
{
// With no button to measure yet, keep the placeholder height the switch starts with.
return btns.empty() ? FromDIP(20) : btns.front()->GetMinSize().y;
}
void MultiSwitchButton::update_scroll_range()
{
// The scrollbar range is measured against the virtual size, so it has to follow the buttons
// whenever their labels or count change.
m_scroll->InvalidateBestSize();
m_scroll->FitInside();
// A scrolled window reports its min size plus a scrollbar as its best size, never the width of
// the buttons it holds, so the layout has to be given that width explicitly. It is also the
// widest this switch wants to be: a row with less room squeezes it below this and it scrolls.
const wxSize content = sizer->CalcMin();
SetMinSize(wxSize(content.x, options_height() + scrollbar_height(content.x)));
SetMaxSize(m_fit_to_options ? wxSize(content.x, -1) : wxDefaultSize);
InvalidateBestSize();
}
int MultiSwitchButton::scrollbar_height(int options_width) const
{
// The bar is drawn inside the switch, so while the buttons need more width than the row gave us
// the switch has to be taller by the bar's height, or the bar sits on top of the buttons.
const int width = GetClientSize().x;
if (width <= 0 || options_width <= width)
return 0;
const int bar = wxSystemSettings::GetMetric(wxSYS_HSCROLL_Y, this);
return bar > 0 ? bar : 0;
}
void MultiSwitchButton::on_size(wxSizeEvent &evt)
{
evt.Skip();
// The row resized, so the buttons may now overflow it (or no longer fit in it) and the room the
// bar needs changed with that. Width does not depend on height, so this settles in one pass.
const int height = options_height() + scrollbar_height(sizer->CalcMin().x);
if (GetMinSize().y != height) {
SetMinSize(wxSize(GetMinSize().x, height));
// The switch sits in a row of this tab, and the tab in a panel that shares its height
// with the page view, so both have to lay out again for the taller row to get its room.
if (wxWindow *tab = GetParent()) {
tab->Layout();
if (wxWindow *panel = tab->GetParent())
panel->Layout();
}
}
}
void MultiSwitchButton::scroll_option_into_view(Button *btn)
{
const int width = m_scroll->GetClientSize().x;
const int view_x = m_scroll->GetViewStartPixels().x;
if (width <= 0) // not laid out yet: there is no view to scroll
return;
const wxRect rect = btn->GetRect();
const int right = rect.GetRight() + 1;
int target = view_x;
if (rect.x < view_x)
target = rect.x;
else if (right > view_x + width)
target = right - width;
if (target == view_x)
return;
// Scroll() counts scroll units; round up so the whole button ends up inside the view rather
// than a few pixels short of it.
int step = 1;
m_scroll->GetScrollPixelsPerUnit(&step, nullptr);
m_scroll->Scroll((target + step - 1) / step, -1);
}
int MultiSwitchButton::AppendOption(const wxString &option, void *clientData) int MultiSwitchButton::AppendOption(const wxString &option, void *clientData)
{ {
Button *btn = new Button(); Button *btn = new Button();
btn->Create(this, option, "", wxBORDER_NONE); btn->Create(m_scroll, option, "", wxBORDER_NONE);
btn->SetFont(GetFont()); btn->SetFont(GetFont());
btn->SetBackgroundColor(m_bg_color); btn->SetBackgroundColor(m_bg_color);
btn->SetTextColor(m_text_color); btn->SetTextColor(m_text_color);
@@ -654,9 +749,6 @@ int MultiSwitchButton::AppendOption(const wxString &option, void *clientData)
btns.push_back(btn); btns.push_back(btn);
sizer->Add(btn, 1, wxEXPAND | wxALIGN_CENTER_VERTICAL); sizer->Add(btn, 1, wxEXPAND | wxALIGN_CENTER_VERTICAL);
wxSize text_size = btn->GetTextExtent(option);
btn->SetMinSize(wxSize(text_size.x + m_button_padding.x * 2 + 6, -1));
return int(btns.size()) - 1; return int(btns.size()) - 1;
} }
@@ -666,6 +758,7 @@ void MultiSwitchButton::SetOptions(const std::vector<wxString> &options)
for (const auto &option : options) for (const auto &option : options)
AppendOption(option); AppendOption(option);
update_scroll_range();
Layout(); Layout();
Refresh(); Refresh();
} }
@@ -700,6 +793,8 @@ void MultiSwitchButton::SetSelection(int index)
sel = index; sel = index;
update_button_styles(); update_button_styles();
send_selection_event(); send_selection_event();
// The selected button may be scrolled out of sight, e.g. when the tab restores the active tool.
scroll_option_into_view(btns[sel]);
Refresh(); Refresh();
} }
@@ -718,6 +813,7 @@ void MultiSwitchButton::SetOptionText(unsigned int index, const wxString &text)
if (index >= btns.size()) if (index >= btns.size())
return; return;
btns[index]->SetLabel(text); btns[index]->SetLabel(text);
update_scroll_range();
} }
void *MultiSwitchButton::GetOptionData(unsigned int index) const void *MultiSwitchButton::GetOptionData(unsigned int index) const
@@ -769,6 +865,7 @@ void MultiSwitchButton::SetButtonPadding(const wxSize &padding)
m_button_padding = padding; m_button_padding = padding;
for (auto *btn : btns) for (auto *btn : btns)
btn->SetPaddingSize(padding); btn->SetPaddingSize(padding);
update_scroll_range();
Layout(); Layout();
Refresh(); Refresh();
} }
@@ -777,6 +874,8 @@ void MultiSwitchButton::Rescale()
{ {
for (auto *btn : btns) for (auto *btn : btns)
btn->Rescale(); btn->Rescale();
// Rescaling can change how the labels measure, and the scrollbar range follows the buttons.
update_scroll_range();
} }
void MultiSwitchButton::button_clicked(wxCommandEvent &event) void MultiSwitchButton::button_clicked(wxCommandEvent &event)
+18 -2
View File
@@ -7,6 +7,7 @@
#include <vector> #include <vector>
#include <wx/sizer.h> #include <wx/sizer.h>
#include <wx/scrolwin.h>
#include <wx/tglbtn.h> #include <wx/tglbtn.h>
#include "Button.hpp" #include "Button.hpp"
@@ -164,6 +165,10 @@ public:
void SetButtonCornerRadius(double radius); void SetButtonCornerRadius(double radius);
void SetButtonPadding(const wxSize &padding); void SetButtonPadding(const wxSize &padding);
// Keep the switch exactly as wide as the buttons need instead of letting the layout stretch it.
// A layout with less room than that still squeezes it, and it scrolls its buttons then.
void SetFitToOptions(bool fit = true) { m_fit_to_options = fit; update_scroll_range(); }
void Rescale(); void Rescale();
protected: protected:
@@ -173,9 +178,20 @@ protected:
bool send_selection_event(); bool send_selection_event();
private: private:
// Height of the single button row, measured from the buttons themselves.
int options_height() const;
void update_scroll_range();
int scrollbar_height(int options_width) const;
void scroll_option_into_view(Button *btn);
void on_size(wxSizeEvent &evt);
std::vector<Button *> btns; std::vector<Button *> btns;
wxBoxSizer *sizer = nullptr; // The buttons are laid out inside this scrolled area so that a switch holding more options than
int sel = -1; // the layout has room for scrolls instead of clipping its tail.
wxScrolledWindow *m_scroll = nullptr;
wxBoxSizer *sizer = nullptr;
bool m_fit_to_options = false;
int sel = -1;
StateColor m_bg_color; StateColor m_bg_color;
StateColor m_text_color; StateColor m_text_color;
+2
View File
@@ -106,6 +106,8 @@ wxString get_nozzle_volume_type_name(NozzleVolumeType type)
return _L("Hybrid"); return _L("Hybrid");
} else if (NozzleVolumeType::nvtTPUHighFlow == type) { } else if (NozzleVolumeType::nvtTPUHighFlow == type) {
return _L("TPU High Flow"); return _L("TPU High Flow");
} else if (NozzleVolumeType::nvtE3DHighFlow == type) {
return _L("E3D High Flow");
} }
return wxString(); return wxString();
} }
+53
View File
@@ -715,3 +715,56 @@ TEST_CASE("Multi-extruder slice stays in bounds with a short max_layer_height",
REQUIRE_FALSE(print.objects().front()->layers().empty()); REQUIRE_FALSE(print.objects().front()->layers().empty());
} }
// A filament can define several variants (Standard, High Flow). Each filament prints with its
// variant of the extruder's variant string, or with its own first variant when it defines none, on a
// printer listing a single variant as on one listing several.
TEST_CASE("Each filament prints with its variant of the extruder's variant string", "[MultiFilament]")
{
auto [variant_list, nozzle_volume_type, filament, temperature, resolved] = GENERATE(table<std::string, NozzleVolumeType, int, int, std::string>({
{ "Direct Drive Standard", nvtStandard, 1, 211, "211,223" },
{ "Direct Drive Standard", nvtStandard, 2, 223, "211,223" },
{ "Direct Drive High Flow", nvtHighFlow, 1, 239, "239,223" },
{ "Direct Drive High Flow", nvtHighFlow, 2, 223, "239,223" }, // filament 2 defines no High Flow variant
{ "Direct Drive Standard,Direct Drive High Flow", nvtHighFlow, 1, 239, "239,223" },
{ "Direct Drive Standard,Direct Drive High Flow", nvtHighFlow, 2, 223, "239,223" },
}));
DYNAMIC_SECTION(variant_list << " printer, " << get_nozzle_volume_type_string(nozzle_volume_type) << " nozzle, filament " << filament) {
DynamicPrintConfig config = multifilament_config(2, {
{ "extruder_variant_list", variant_list },
// filament 1 defines Standard (211) and High Flow (239), filament 2 Standard (223)
{ "filament_extruder_variant", "Direct Drive Standard;Direct Drive High Flow;Direct Drive Standard" },
{ "filament_self_index", "1,1,2" },
{ "nozzle_temperature", "211,239,223" },
{ "nozzle_temperature_initial_layer", "211,239,223" },
{ "sparse_infill_filament_id", filament },
{ "internal_solid_filament_id", filament },
{ "top_surface_filament_id", filament },
{ "bottom_surface_filament_id", filament },
{ "outer_wall_filament_id", filament },
{ "inner_wall_filament_id", filament },
{ "enable_prime_tower", 0 },
{ "skirt_loops", 0 },
{ "brim_type", "no_brim" },
// custom G-code indexes the per-filament arrays by filament
{ "machine_start_gcode", "; start temperature {nozzle_temperature_initial_layer[initial_extruder]}" },
});
config.option<ConfigOptionEnumsGeneric>("nozzle_volume_type", true)->values = { nozzle_volume_type };
const std::string gcode = slice({ cube(20) }, config);
std::set<int> temperatures;
std::istringstream stream(gcode);
for (std::string line; std::getline(stream, line);) {
if (line.rfind("M104 ", 0) != 0 && line.rfind("M109 ", 0) != 0)
continue;
const size_t s = line.find(" S");
if (s != std::string::npos && std::stoi(line.substr(s + 2)) > 0)
temperatures.insert(std::stoi(line.substr(s + 2)));
}
CHECK(temperatures == std::set<int>{ temperature });
CHECK(gcode.find("; start temperature " + std::to_string(temperature) + "\n") != std::string::npos);
// The config the slice ran with holds one value per filament, as the readers that index
// it by filament (the wipe tower, the filament compatibility check) expect.
CHECK(gcode.find("; nozzle_temperature = " + resolved + "\n") != std::string::npos);
}
}
@@ -120,13 +120,25 @@ TEST_CASE("get_config_index_base resolves (volume type, extruder type, id) to a
REQUIRE(get_config_index_base(nvtHighFlow, etDirectDrive, 2, variant_list, variant_ids) == 3); REQUIRE(get_config_index_base(nvtHighFlow, etDirectDrive, 2, variant_list, variant_ids) == 3);
} }
SECTION("no matching column falls back to slot 0") { SECTION("no matching variant falls back to the id's first variant") {
REQUIRE(get_config_index_base(nvtStandard, etDirectDrive, 3, variant_list, variant_ids) == 0);
REQUIRE(get_config_index_base(nvtStandard, etBowden, 1, variant_list, variant_ids) == 0); REQUIRE(get_config_index_base(nvtStandard, etBowden, 1, variant_list, variant_ids) == 0);
REQUIRE(get_config_index_base(nvtStandard, etBowden, 2, variant_list, variant_ids) == 2);
} }
SECTION("Hybrid is not a preset variant string and falls back to slot 0") { SECTION("an id without any variant falls back to variant index 0") {
REQUIRE(get_config_index_base(nvtHybrid, etDirectDrive, 2, variant_list, variant_ids) == 0); REQUIRE(get_config_index_base(nvtStandard, etDirectDrive, 3, variant_list, variant_ids) == 0);
}
SECTION("Hybrid is not a preset variant string and falls back to the id's first variant") {
REQUIRE(get_config_index_base(nvtHybrid, etDirectDrive, 2, variant_list, variant_ids) == 2);
}
SECTION("a filament without a High Flow variant keeps its own variant, not the first filament's") {
// filament 1 defines Standard and High Flow, filament 2 only Standard
const std::vector<std::string> mixed_list = {"Direct Drive Standard", "Direct Drive High Flow", "Direct Drive Standard"};
const std::vector<int> mixed_ids = {1, 1, 2};
REQUIRE(get_config_index_base(nvtHighFlow, etDirectDrive, 1, mixed_list, mixed_ids) == 1);
REQUIRE(get_config_index_base(nvtHighFlow, etDirectDrive, 2, mixed_list, mixed_ids) == 2);
} }
} }
@@ -512,6 +524,73 @@ TEST_CASE("update_values_to_printer_extruders_for_multiple_filaments resolves pe
Catch::Matchers::Approx(std::vector<double>({-1., -1.}))); Catch::Matchers::Approx(std::vector<double>({-1., -1.})));
REQUIRE(config.option<ConfigOptionFloats>("filament_max_volumetric_speed")->values == std::vector<double>({12., 21.})); REQUIRE(config.option<ConfigOptionFloats>("filament_max_volumetric_speed")->values == std::vector<double>({12., 21.}));
} }
// A single extruder whose only variant is Standard or High Flow; filament 1 defines both
// variants, filament 2 only Standard.
auto make_single_variant_config = [](NozzleVolumeType nozzle_volume_type, const std::string &variant) {
DynamicPrintConfig config;
config.option<ConfigOptionEnumsGeneric>("extruder_type", true)->values = {etDirectDrive};
config.option<ConfigOptionEnumsGeneric>("nozzle_volume_type", true)->values = {nozzle_volume_type};
config.option<ConfigOptionStrings>("extruder_variant_list", true)->values = {variant};
config.option<ConfigOptionFloats>("filament_diameter", true)->values = {1.75, 1.75};
config.option<ConfigOptionInts>("filament_self_index", true)->values = {1, 1, 2};
config.option<ConfigOptionStrings>("filament_extruder_variant", true)->values = {"Direct Drive Standard", "Direct Drive High Flow",
"Direct Drive Standard"};
config.option<ConfigOptionFloats>("filament_max_volumetric_speed", true)->values = {12., 20., 13.};
config.option<ConfigOptionInts>("filament_map", true)->values = {1, 1};
return config;
};
SECTION("a single-variant printer picks each filament's variant of that variant string") {
auto [nozzle_volume_type, variant, speeds] = GENERATE(table<NozzleVolumeType, std::string, std::vector<double>>({
{nvtStandard, "Direct Drive Standard", {12., 13.}},
// filament 2 defines no High Flow variant and keeps its first
{nvtHighFlow, "Direct Drive High Flow", {20., 13.}},
}));
DynamicPrintConfig config = make_single_variant_config(nozzle_volume_type, variant);
int extruder_count = 1;
REQUIRE_FALSE(config.support_different_extruders(extruder_count));
REQUIRE(config.has_multi_variant_filament());
std::vector<std::vector<NozzleVolumeType>> nozzle_volume_types;
int count = config.get_extruder_nozzle_volume_count(extruder_count, nozzle_volume_types);
config.update_values_to_printer_extruders_for_multiple_filaments(config, extruder_count, count, filament_keys,
"filament_self_index", "filament_extruder_variant");
REQUIRE(config.option<ConfigOptionFloats>("filament_max_volumetric_speed")->values == speeds);
REQUIRE(config.option<ConfigOptionInts>("filament_self_index")->values == std::vector<int>({1, 2}));
}
SECTION("a filament map shorter than the filament count keeps every filament") {
DynamicPrintConfig config = make_single_variant_config(nvtStandard, "Direct Drive Standard");
config.option<ConfigOptionInts>("filament_map", true)->values = {1};
std::vector<std::vector<NozzleVolumeType>> nozzle_volume_types;
int extruder_count = 1;
int count = config.get_extruder_nozzle_volume_count(extruder_count, nozzle_volume_types);
config.update_values_to_printer_extruders_for_multiple_filaments(config, extruder_count, count, filament_keys,
"filament_self_index", "filament_extruder_variant");
REQUIRE(config.option<ConfigOptionFloats>("filament_max_volumetric_speed")->values == std::vector<double>({12., 13.}));
REQUIRE(config.option<ConfigOptionInts>("filament_self_index")->values == std::vector<int>({1, 2}));
}
}
TEST_CASE("has_multi_variant_filament is true only when a filament defines more than one variant", "[Config]")
{
DynamicPrintConfig config;
config.option<ConfigOptionFloats>("filament_diameter", true)->values = {1.75, 1.75};
SECTION("one variant per filament reports false") {
config.option<ConfigOptionStrings>("filament_extruder_variant", true)->values = {"Direct Drive Standard", "Bowden Standard"};
REQUIRE_FALSE(config.has_multi_variant_filament());
}
SECTION("a filament with Standard and High Flow variants reports true") {
config.option<ConfigOptionStrings>("filament_extruder_variant", true)->values = {"Direct Drive Standard", "Direct Drive High Flow",
"Direct Drive Standard"};
REQUIRE(config.has_multi_variant_filament());
}
} }
// update_values_from_multi_to_multi_2 walks the DESTINATION PRINTER's variant list while writing // update_values_from_multi_to_multi_2 walks the DESTINATION PRINTER's variant list while writing
@@ -10,11 +10,13 @@ TEST_CASE("convert_to_nvt_type maps extruder variant strings to nozzle volume ty
REQUIRE(convert_to_nvt_type("Direct Drive Standard") == nvtStandard); REQUIRE(convert_to_nvt_type("Direct Drive Standard") == nvtStandard);
REQUIRE(convert_to_nvt_type("Direct Drive High Flow") == nvtHighFlow); REQUIRE(convert_to_nvt_type("Direct Drive High Flow") == nvtHighFlow);
REQUIRE(convert_to_nvt_type("Direct Drive TPU High Flow") == nvtTPUHighFlow); REQUIRE(convert_to_nvt_type("Direct Drive TPU High Flow") == nvtTPUHighFlow);
REQUIRE(convert_to_nvt_type("Direct Drive E3D High Flow") == nvtE3DHighFlow);
} }
SECTION("Bowden variants") { SECTION("Bowden variants") {
REQUIRE(convert_to_nvt_type("Bowden Standard") == nvtStandard); REQUIRE(convert_to_nvt_type("Bowden Standard") == nvtStandard);
REQUIRE(convert_to_nvt_type("Bowden High Flow") == nvtHighFlow); REQUIRE(convert_to_nvt_type("Bowden High Flow") == nvtHighFlow);
REQUIRE(convert_to_nvt_type("Bowden E3D High Flow") == nvtE3DHighFlow);
} }
SECTION("Unparsable strings fall back to hybrid") { SECTION("Unparsable strings fall back to hybrid") {
@@ -29,3 +31,49 @@ TEST_CASE("convert_to_nvt_type maps extruder variant strings to nozzle volume ty
REQUIRE(convert_to_nvt_type(" Bowden Standard") == nvtStandard); REQUIRE(convert_to_nvt_type(" Bowden Standard") == nvtStandard);
} }
} }
TEST_CASE("E3D High Flow is nozzle volume type 5, after the reserved 4", "[Config]")
{
REQUIRE(int(nvtE3DHighFlow) == 5);
REQUIRE(get_valid_nozzle_volume_type() == std::set<NozzleVolumeType>{nvtStandard, nvtHighFlow, nvtTPUHighFlow, nvtE3DHighFlow});
REQUIRE(get_nozzle_volume_type_string(nvtE3DHighFlow) == "E3D High Flow");
REQUIRE(get_extruder_variant_string(etDirectDrive, nvtE3DHighFlow) == "Direct Drive E3D High Flow");
REQUIRE(get_extruder_variant_string(etBowden, nvtE3DHighFlow) == "Bowden E3D High Flow");
SECTION("nozzle_volume_type round-trips it by name") {
DynamicPrintConfig config;
config.set_deserialize_strict("nozzle_volume_type", "Standard,E3D High Flow");
REQUIRE(config.option<ConfigOptionEnumsGeneric>("nozzle_volume_type")->values == std::vector<int>{nvtStandard, nvtE3DHighFlow});
REQUIRE(config.opt_serialize("nozzle_volume_type") == "Standard,E3D High Flow");
}
SECTION("its variant is found by value, not mistaken for High Flow") {
const std::vector<std::string> variant_list = {"Direct Drive Standard", "Direct Drive High Flow", "Direct Drive E3D High Flow"};
const std::vector<int> variant_ids = {1, 1, 1};
REQUIRE(get_config_index_base(nvtE3DHighFlow, etDirectDrive, 1, variant_list, variant_ids) == 2);
REQUIRE(get_config_index_base(nvtHighFlow, etDirectDrive, 1, variant_list, variant_ids) == 1);
}
}
TEST_CASE("get_extruder_supported_nozzle_volume_types reads the extruder's variant list", "[Config]")
{
DynamicPrintConfig config;
// H2C: the left extruder offers E3D High Flow, the right (nozzle rack) extruder does not.
config.set_deserialize_strict("extruder_type", "Direct Drive,Direct Drive");
config.option<ConfigOptionStrings>("extruder_variant_list", true)->values = {
"Direct Drive Standard,Direct Drive High Flow,Direct Drive E3D High Flow",
"Direct Drive Standard,Direct Drive High Flow"};
REQUIRE(get_extruder_supported_nozzle_volume_types(config, 0) == std::set<NozzleVolumeType>{nvtStandard, nvtHighFlow, nvtE3DHighFlow});
REQUIRE(get_extruder_supported_nozzle_volume_types(config, 1) == std::set<NozzleVolumeType>{nvtStandard, nvtHighFlow});
SECTION("an E3D-only list does not report plain High Flow") {
config.option<ConfigOptionStrings>("extruder_variant_list")->values[1] = "Direct Drive Standard,Direct Drive E3D High Flow";
REQUIRE(get_extruder_supported_nozzle_volume_types(config, 1) == std::set<NozzleVolumeType>{nvtStandard, nvtE3DHighFlow});
}
SECTION("an extruder past the profile's lists gives the empty, unknown set") {
REQUIRE(get_extruder_supported_nozzle_volume_types(config, 2).empty());
REQUIRE(get_extruder_supported_nozzle_volume_types(config, -1).empty());
}
}