ENH: add prompt for incompatible filaments and nozzles

jira: STUDIO-12873

Signed-off-by: xun.zhang <xun.zhang@bambulab.com>
Change-Id: Ieb79a35e0609e7687fdcf31742df3a08fedc925b
(cherry picked from commit 1ef32833035629c1b3644d77fdc9c234992090f3)
This commit is contained in:
xun.zhang
2025-06-27 12:00:21 +08:00
committed by Noisyfox
parent 94732b6bad
commit 5a1dc90e8c
8 changed files with 184 additions and 3 deletions

View File

@@ -0,0 +1,34 @@
{
"incompatible_nozzles":{
"Standard":{
"0.2":[
"Bambu PLA Marble",
"Bambu PLA Sparkle",
"Bambu PLA Wood",
"Bambu PLA Galaxy",
"Bambu PETG Translucent"
],
"0.4":[],
"0.6":[
"Bambu PLA Silk+",
"Bambu PLA Silk",
"Bambu PLA Aero",
"Bambu ASA-Aero"
],
"0.8":[
"Bambu PLA Silk+",
"Bambu PLA Silk",
"Bambu PLA Aero",
"Bambu ASA-Aero"
]
},
"High Flow":{
"0.4":[
"Bambu PLA-CF",
"Bambu PETG-CF"
],
"0.6":[],
"0.8":[]
}
}
}

View File

@@ -2710,6 +2710,47 @@ int Print::get_hrc_by_nozzle_type(const NozzleType&type)
return 0;
}
std::vector<std::string> Print::get_incompatible_filaments_by_nozzle(const float nozzle_diameter, const std::optional<NozzleVolumeType> nozzle_volume_type)
{
static std::map<std::string, std::map<std::string, std::vector<std::string>>> incompatible_filaments;
if(incompatible_filaments.empty()){
fs::path file_path = fs::path(resources_dir()) / "info" / "nozzle_incompatibles.json";
boost::nowide::ifstream in(file_path.string());
json j;
try {
j = json::parse(in);
for(auto& [volume_type, diameter_list] : j["incompatible_nozzles"].items()) {
for(auto& [diameter, filaments]: diameter_list.items()){
incompatible_filaments[volume_type][diameter] = filaments.get<std::vector<std::string>>();
}
}
}
catch(const json::parse_error& err){
in.close();
BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << ": parse " << file_path.string() << " got a nlohmann::detail::parse_error, reason = " << err.what();
incompatible_filaments[get_nozzle_volume_type_string(NozzleVolumeType::nvtHighFlow)] = {};
incompatible_filaments[get_nozzle_volume_type_string(NozzleVolumeType::nvtStandard)] = {};
}
}
std::ostringstream oss;
oss << std::fixed << std::setprecision(1) << nozzle_diameter;
std::string diameter_str = oss.str();
if(nozzle_volume_type.has_value()){
return incompatible_filaments[get_nozzle_volume_type_string(nozzle_volume_type.value())][diameter_str];
}
std::vector<std::string> incompatible_filaments_list;
for(auto& [volume_type, diameter_list] : incompatible_filaments){
auto iter = diameter_list.find(diameter_str);
if(iter != diameter_list.end()){
append(incompatible_filaments_list, iter->second);
}
}
return incompatible_filaments_list;
}
void Print::finalize_first_layer_convex_hull()
{
append(m_first_layer_convex_hull.points, m_skirt_convex_hull);

View File

@@ -1080,6 +1080,7 @@ public:
Vec2d translate_to_print_space(const Point &point) const;
static FilamentTempType get_filament_temp_type(const std::string& filament_type);
static int get_hrc_by_nozzle_type(const NozzleType& type);
static std::vector<std::string> get_incompatible_filaments_by_nozzle(const float nozzle_diameter, const std::optional<NozzleVolumeType> nozzle_volume_type = std::nullopt);
static FilamentCompatibilityType check_multi_filaments_compatibility(const std::vector<std::string>& filament_types);
// similar to check_multi_filaments_compatibility, but the input is int, and may be negative (means unset)
static bool is_filaments_compatible(const std::vector<int>& types);

View File

@@ -137,6 +137,11 @@ std::string& get_right_extruder_unprintable_text() {
return right_unprintable_text;
}
std::string& get_nozzle_filament_incompatible_text() {
static std::string nozzle_filament_incompatible_text;
return nozzle_filament_incompatible_text;
}
static std::string format_number(float value)
{
std::ostringstream oss;
@@ -3030,15 +3035,19 @@ void GLCanvas3D::reload_scene(bool refresh_immediately, bool force_full_scene_re
//if (printer_technology != ptSLA || !contained_min_one)
// _set_warning_notification(EWarning::SlaSupportsOutside, false);
bool tpu_valid = cur_plate->check_tpu_printable_status(wxGetApp().preset_bundle->full_config(), wxGetApp().preset_bundle->get_used_tpu_filaments(cur_plate->get_extruders(true)));
auto full_config_temp = wxGetApp().preset_bundle->full_config();
bool tpu_valid = cur_plate->check_tpu_printable_status(full_config_temp, wxGetApp().preset_bundle->get_used_tpu_filaments(cur_plate->get_extruders(true)));
_set_warning_notification(EWarning::TPUPrintableError, !tpu_valid);
bool filament_printable = cur_plate->check_filament_printable(wxGetApp().preset_bundle->full_config(), filament_printable_error_msg);
bool filament_printable = cur_plate->check_filament_printable(full_config_temp, filament_printable_error_msg);
_set_warning_notification(EWarning::FilamentPrintableError, !filament_printable);
bool mix_pla_and_petg = cur_plate->check_mixture_of_pla_and_petg(wxGetApp().preset_bundle->full_config());
bool mix_pla_and_petg = cur_plate->check_mixture_of_pla_and_petg(full_config_temp);
_set_warning_notification(EWarning::MixUsePLAAndPETG, !mix_pla_and_petg);
bool filament_nozzle_compatible = cur_plate->check_compatible_of_nozzle_and_filament(full_config_temp, wxGetApp().preset_bundle->filament_presets, get_nozzle_filament_incompatible_text());
_set_warning_notification(EWarning::NozzleFilamentIncompatible, !filament_nozzle_compatible);
bool model_fits = contained_min_one && !m_model->objects.empty() && !partlyOut && object_results.filaments.empty() && tpu_valid && filament_printable;
post_event(Event<bool>(EVT_GLCANVAS_ENABLE_ACTION_BUTTONS, model_fits));
ppl.get_curr_plate()->update_slice_ready_status(model_fits);
@@ -3056,6 +3065,8 @@ void GLCanvas3D::reload_scene(bool refresh_immediately, bool force_full_scene_re
_set_warning_notification(EWarning::PrimeTowerOutside, false);
_set_warning_notification(EWarning::MultiExtruderPrintableError,false);
_set_warning_notification(EWarning::MultiExtruderHeightOutside,false);
_set_warning_notification(EWarning::NozzleFilamentIncompatible,false);
post_event(Event<bool>(EVT_GLCANVAS_ENABLE_ACTION_BUTTONS, false));
}
}
@@ -10105,6 +10116,10 @@ void GLCanvas3D::_set_warning_notification(EWarning warning, bool state)
case EWarning::PrimeTowerOutside:
text = _u8L("The prime tower extends beyond the plate boundary.");
break;
case EWarning::NozzleFilamentIncompatible: {
text = _u8L(get_nozzle_filament_incompatible_text());
break;
}
}
//BBS: this may happened when exit the app, plater is null
if (!wxGetApp().plater())
@@ -10129,6 +10144,14 @@ void GLCanvas3D::_set_warning_notification(EWarning warning, bool state)
else
notification_manager.close_slicing_customize_error_notification(NotificationType::BBLMixUsePLAAndPETG, NotificationLevel::WarningNotificationLevel);
}
else if (warning == EWarning::NozzleFilamentIncompatible){
if(state){
notification_manager.push_slicing_customize_error_notification(NotificationType::BBLNozzleFilamentIncompatible, NotificationLevel::WarningNotificationLevel, text);
}
else{
notification_manager.close_slicing_customize_error_notification(NotificationType::BBLNozzleFilamentIncompatible, NotificationLevel::WarningNotificationLevel);
}
}
else {
if (state)
notification_manager.push_plater_warning_notification(text);

View File

@@ -393,6 +393,7 @@ class GLCanvas3D
FilamentUnPrintableOnFirstLayer,
MixUsePLAAndPETG,
PrimeTowerOutside,
NozzleFilamentIncompatible,
};
class RenderStats

View File

@@ -159,6 +159,7 @@ enum class NotificationType
BBLSliceMultiExtruderHeightOutside,
BBLBedFilamentIncompatible,
BBLMixUsePLAAndPETG,
BBLNozzleFilamentIncompatible,
NotificationTypeCount
};

View File

@@ -1787,6 +1787,85 @@ bool PartPlate::check_mixture_of_pla_and_petg(const DynamicPrintConfig &config)
return true;
}
bool PartPlate::check_compatible_of_nozzle_and_filament(const DynamicPrintConfig &config, const std::vector<std::string> &filament_presets, std::string &error_msg)
{
float nozzle_diameter = config.option<ConfigOptionFloatsNullable>("nozzle_diameter")->values[0];
auto volume_type_opt = config.option<ConfigOptionEnumsGeneric>("nozzle_volume_type");
auto get_filament_alias = [](std::string preset_name) -> std::string {
size_t at_pos = preset_name.find('@');
std::string alias = preset_name.substr(0, at_pos);
size_t first = alias.find_first_not_of(' ');
if (first == std::string::npos) return "";
size_t last = alias.find_last_not_of(' ');
return alias.substr(first, last - first + 1);
};
bool with_same_volume_type = std::all_of(volume_type_opt->values.begin(), volume_type_opt->values.end(),
[first_value = volume_type_opt->values[0]](int value) { return value == first_value; });
std::set<std::string> selected_filament_alias;
for (auto &filament_preset : filament_presets) { selected_filament_alias.insert(get_filament_alias(filament_preset)); }
auto get_incompatible_selected = [&](const NozzleVolumeType volume_type) -> std::set<std::string> {
std::vector<std::string> incompatible_filaments = Print::get_incompatible_filaments_by_nozzle(nozzle_diameter, volume_type);
std::set<std::string> ret;
for (auto &filament : selected_filament_alias) {
if (std::find(incompatible_filaments.begin(), incompatible_filaments.end(), filament) != incompatible_filaments.end()) ret.insert(filament);
}
return ret;
};
auto get_nozzle_msg = [](const float nozzle_diameter, const NozzleVolumeType volume_type) -> std::string {
std::ostringstream oss;
oss << std::fixed << std::setprecision(1) << nozzle_diameter;
std::string nozzle_msg = oss.str();
((nozzle_msg += "mm ") += _u8L(get_nozzle_volume_type_string(volume_type))) += _u8L(" nozzle");
return nozzle_msg;
};
auto get_incompatible_filament_msg = [](const std::set<std::string> &incompatible_selected_filaments) -> std::string {
std::string filament_str;
size_t idx = 0;
for (const auto &filament : incompatible_selected_filaments) {
if (idx > 0) filament_str += ',';
filament_str += filament;
++idx;
}
return filament_str;
};
error_msg.clear();
std::set<int> nozzle_volumes(volume_type_opt->values.begin(), volume_type_opt->values.end());
std::map<NozzleVolumeType, std::set<std::string>> incompatible_selected_map;
for (auto volume_type_value : nozzle_volumes) {
NozzleVolumeType volume_type = static_cast<NozzleVolumeType>(volume_type_value);
auto incompatible_selected = get_incompatible_selected(volume_type);
if (!incompatible_selected.empty()) incompatible_selected_map[volume_type] = incompatible_selected;
}
if (incompatible_selected_map.empty()) return true;
if (incompatible_selected_map.size() == 1) {
auto elem = incompatible_selected_map.begin();
NozzleVolumeType volume_type = elem->first;
auto incompatible_selected = elem->second;
error_msg = GUI::format(_L("It is not recommended to print the following filament(s) with %1%: %2%\n"), get_nozzle_msg(nozzle_diameter, volume_type),
get_incompatible_filament_msg(incompatible_selected));
} else {
std::string warning_msg = _u8L("It is not recommended to use the following nozzle and filament combinations:\n");
for (auto &elem : incompatible_selected_map) {
NozzleVolumeType volume_type = elem.first;
auto incompatible_selected = elem.second;
warning_msg += GUI::format(_L("%1% with %2%\n"),get_nozzle_msg(nozzle_diameter, volume_type), get_incompatible_filament_msg(incompatible_selected));
}
error_msg = warning_msg;
}
return false;
}
/*Vec3d PartPlate::calculate_wipe_tower_size(const DynamicPrintConfig &config, const double w, const double wipe_volume, int plate_extruder_size, bool use_global_objects) const
{
Vec3d wipe_tower_size;

View File

@@ -335,6 +335,7 @@ public:
bool check_filament_printable(const DynamicPrintConfig & config, wxString& error_message);
bool check_tpu_printable_status(const DynamicPrintConfig & config, const std::vector<int> &tpu_filaments);
bool check_mixture_of_pla_and_petg(const DynamicPrintConfig & config);
bool check_compatible_of_nozzle_and_filament(const DynamicPrintConfig & config, const std::vector<std::string>& filament_presets, std::string& error_msg);
/* instance related operations*/
//judge whether instance is bound in plate or not