Fix: show all print validation warnings instead of only the last (#14112)

This commit is contained in:
raistlin7447
2026-06-17 23:45:26 -05:00
committed by GitHub
parent 3ffb9585d2
commit a587859e84
11 changed files with 270 additions and 95 deletions

View File

@@ -6058,9 +6058,9 @@ int CLI::run(int argc, char **argv)
} }
(dynamic_cast<Print*>(print))->is_BBL_printer() = is_bbl_vendor_preset; (dynamic_cast<Print*>(print))->is_BBL_printer() = is_bbl_vendor_preset;
StringObjectException warning; std::vector<StringObjectException> warnings;
print_fff->set_check_multi_filaments_compatibility(!allow_mix_temp); print_fff->set_check_multi_filaments_compatibility(!allow_mix_temp);
auto err = print->validate(&warning); auto err = print->validate(&warnings);
if (!err.string.empty()) { if (!err.string.empty()) {
if ((STRING_EXCEPT_LAYER_HEIGHT_EXCEEDS_LIMIT == err.type) && no_check) { if ((STRING_EXCEPT_LAYER_HEIGHT_EXCEEDS_LIMIT == err.type) && no_check) {
BOOST_LOG_TRIVIAL(warning) << "got warnings: "<< err.string << std::endl; BOOST_LOG_TRIVIAL(warning) << "got warnings: "<< err.string << std::endl;
@@ -6094,8 +6094,9 @@ int CLI::run(int argc, char **argv)
flush_and_exit(validate_error); flush_and_exit(validate_error);
} }
} }
else if (!warning.string.empty()) { else if (!warnings.empty()) {
BOOST_LOG_TRIVIAL(warning) << "got warnings: "<< warning.string << std::endl; for (const auto& w : warnings)
BOOST_LOG_TRIVIAL(warning) << "got warnings: "<< w.string << std::endl;
} }
if (print->empty()) { if (print->empty()) {
@@ -6115,8 +6116,14 @@ int CLI::run(int argc, char **argv)
BOOST_LOG_TRIVIAL(info) << "set print's callback to cli_status_callback."; BOOST_LOG_TRIVIAL(info) << "set print's callback to cli_status_callback.";
print->set_status_callback(cli_status_callback); print->set_status_callback(cli_status_callback);
g_cli_callback_mgr.set_plate_info(index+1, (plate_to_slice== 0)?partplate_list.get_plate_count():1); g_cli_callback_mgr.set_plate_info(index+1, (plate_to_slice== 0)?partplate_list.get_plate_count():1);
if (!warning.string.empty()) { if (!warnings.empty()) {
PrintBase::SlicingStatus slicing_status{4, warning.string, 0, 0}; std::string warning_text;
for (const auto& w : warnings) {
if (!warning_text.empty())
warning_text += "\n";
warning_text += w.string;
}
PrintBase::SlicingStatus slicing_status{4, warning_text, 0, 0};
cli_status_callback(slicing_status); cli_status_callback(slicing_status);
} }
else { else {

View File

@@ -1256,9 +1256,22 @@ StringObjectException Print::check_multi_filament_valid(const Print& print)
} }
// Precondition: Print::validate() requires the Print::apply() to be called its invocation. // Precondition: Print::validate() requires the Print::apply() to be called its invocation.
//BBS: refine seq-print validation logic.....FIXME:StringObjectException *warning can only contain one warning, but there might be many warnings, need a vector<StringObjectException> //BBS: refine seq-print validation logic
StringObjectException Print::validate(StringObjectException *warning, Polygons* collison_polygons, std::vector<std::pair<Polygon, float>>* height_polygons) const StringObjectException Print::validate(std::vector<StringObjectException> *warnings, Polygons* collison_polygons, std::vector<std::pair<Polygon, float>>* height_polygons) const
{ {
auto add_warning = [warnings](StringObjectException w) {
w.is_warning = true;
if (warnings != nullptr)
warnings->push_back(std::move(w));
};
auto warn = [&](std::string msg, std::string opt_key = "", const ObjectBase* object = nullptr) {
StringObjectException w;
w.string = std::move(msg);
w.opt_key = std::move(opt_key);
w.object = object;
add_warning(std::move(w));
};
std::vector<unsigned int> extruders = this->extruders(); std::vector<unsigned int> extruders = this->extruders();
unsigned int nozzles = m_config.nozzle_diameter.size(); unsigned int nozzles = m_config.nozzle_diameter.size();
@@ -1273,9 +1286,8 @@ StringObjectException Print::validate(StringObjectException *warning, Polygons*
if (!ret.string.empty()) if (!ret.string.empty())
{ {
ret.type = STRING_EXCEPT_FILAMENTS_DIFFERENT_TEMP; ret.type = STRING_EXCEPT_FILAMENTS_DIFFERENT_TEMP;
if (ret.is_warning && warning != nullptr) { if (ret.is_warning) {
*warning = ret; add_warning(ret);
//return {};
}else }else
return ret; return ret;
} }
@@ -1301,32 +1313,26 @@ StringObjectException Print::validate(StringObjectException *warning, Polygons*
} }
else { else {
//BBS //BBS
auto ret = layered_print_cleareance_valid(*this, warning); StringObjectException layer_warning;
auto ret = layered_print_cleareance_valid(*this, &layer_warning);
if (!ret.string.empty()) { if (!ret.string.empty()) {
ret.type = STRING_EXCEPT_OBJECT_COLLISION_IN_LAYER_PRINT; ret.type = STRING_EXCEPT_OBJECT_COLLISION_IN_LAYER_PRINT;
return ret; return ret;
} }
if (!layer_warning.string.empty())
add_warning(layer_warning);
} }
if (m_config.enable_prime_tower) { if (m_config.enable_prime_tower) {
for (const PrintObject* object : m_objects) { for (const PrintObject* object : m_objects) {
if (object->config().precise_z_height.value && warning != nullptr) { if (object->config().precise_z_height.value) {
StringObjectException warningtemp; warn(L("Enabling both precise Z height and the prime tower may cause slicing errors."), "precise_z_height");
warningtemp.string = L("Enabling both precise Z height and the prime tower may cause slicing errors.");
warningtemp.opt_key = "precise_z_height";
warningtemp.is_warning = true;
*warning = warningtemp;
break; break;
} }
} }
} else { } else {
if (m_config.enable_wrapping_detection && warning!=nullptr) { if (m_config.enable_wrapping_detection)
StringObjectException warningtemp; warn(L("A prime tower is required for clumping detection; otherwise, there may be flaws on the model."), "enable_prime_tower");
warningtemp.string = L("A prime tower is required for clumping detection; otherwise, there may be flaws on the model.");
warningtemp.opt_key = "enable_prime_tower";
warningtemp.is_warning = true;
*warning = warningtemp;
}
} }
if (m_config.spiral_mode) { if (m_config.spiral_mode) {
@@ -1422,8 +1428,7 @@ StringObjectException Print::validate(StringObjectException *warning, Polygons*
if (nozzle_diam - EPSILON > first_nozzle_diam || nozzle_diam + EPSILON < first_nozzle_diam if (nozzle_diam - EPSILON > first_nozzle_diam || nozzle_diam + EPSILON < first_nozzle_diam
|| std::abs((filament_diam - first_filament_diam) / first_filament_diam) > 0.1) { || std::abs((filament_diam - first_filament_diam) / first_filament_diam) > 0.1) {
// return { L("Different nozzle diameters and different filament diameters may not work well when prime tower is enabled. It's very experimental, please proceed with caucious.") }; // return { L("Different nozzle diameters and different filament diameters may not work well when prime tower is enabled. It's very experimental, please proceed with caucious.") };
warning->string = L("Different nozzle diameters and different filament diameters may not work well when the prime tower is enabled. It's very experimental, so please proceed with caution."); warn(L("Different nozzle diameters and different filament diameters may not work well when the prime tower is enabled. It's very experimental, so please proceed with caution."), "nozzle_diameter");
warning->opt_key = "nozzle_diameter";
break; break;
} }
} }
@@ -1580,22 +1585,15 @@ StringObjectException Print::validate(StringObjectException *warning, Polygons*
// Orca: use organic as default // Orca: use organic as default
object->config().support_style == smsDefault) { object->config().support_style == smsDefault) {
if (warning) { // Orca: check the support wall count and the base pattern
// Orca: check the support wall count and the base pattern if (object->config().tree_support_wall_count > 1 &&
if (object->config().tree_support_wall_count > 1 && object->config().support_base_pattern != SupportMaterialPattern::smpNone &&
object->config().support_base_pattern != SupportMaterialPattern::smpNone && object->config().support_base_pattern != SupportMaterialPattern::smpDefault)
object->config().support_base_pattern != SupportMaterialPattern::smpDefault) { warn(L("For Organic supports, two walls are supported only with the Hollow/Default base pattern."), "support_base_pattern");
warning->string = L("For Organic supports, two walls are supported only with the Hollow/Default base pattern.");
warning->opt_key = "support_base_pattern";
}
// Orca: check if the Lightning base pattern selected // Orca: check if the Lightning base pattern selected
if (object->config().support_base_pattern == SupportMaterialPattern::smpLightning) { if (object->config().support_base_pattern == SupportMaterialPattern::smpLightning)
warning->string = L( warn(L("The Lightning base pattern is not supported by this support type; Rectilinear will be used instead."), "support_base_pattern");
"The Lightning base pattern is not supported by this support type; Rectilinear will be used instead.");
warning->opt_key = "support_base_pattern";
}
}
float extrusion_width = std::min( float extrusion_width = std::min(
support_material_flow(object).width(), support_material_flow(object).width(),
@@ -1607,28 +1605,23 @@ StringObjectException Print::validate(StringObjectException *warning, Polygons*
if (object->config().tree_support_branch_diameter_organic < object->config().tree_support_tip_diameter) if (object->config().tree_support_branch_diameter_organic < object->config().tree_support_tip_diameter)
return { L("Organic support branch diameter must not be smaller than support tree tip diameter."), object, "tree_support_branch_diameter_organic" }; return { L("Organic support branch diameter must not be smaller than support tree tip diameter."), object, "tree_support_branch_diameter_organic" };
} }
} else if (object->config().support_base_pattern == SupportMaterialPattern::smpLightning && warning) { } else if (object->config().support_base_pattern == SupportMaterialPattern::smpLightning) {
// Orca: check if the Lightning base pattern selected // Orca: check if the Lightning base pattern selected
warning->string = L("The Lightning base pattern is not supported by this support type; Rectilinear will be used instead."); warn(L("The Lightning base pattern is not supported by this support type; Rectilinear will be used instead."), "support_base_pattern");
warning->opt_key = "support_base_pattern"; } else if (object->config().support_base_pattern == SupportMaterialPattern::smpNone) {
} else if (object->config().support_base_pattern == SupportMaterialPattern::smpNone && warning) {
// Orca: check if the Hollow base pattern selected // Orca: check if the Hollow base pattern selected
warning->string = L("The Hollow base pattern is not supported by this support type; Rectilinear will be used instead."); warn(L("The Hollow base pattern is not supported by this support type; Rectilinear will be used instead."), "support_base_pattern");
warning->opt_key = "support_base_pattern";
} }
} }
// Do we have custom support data that would not be used? // Do we have custom support data that would not be used?
// Notify the user in that case. // Notify the user in that case.
if (! object->has_support() && warning) { if (! object->has_support()) {
for (const ModelVolume* mv : object->model_object()->volumes) { for (const ModelVolume* mv : object->model_object()->volumes) {
bool has_enforcers = mv->is_support_enforcer() || bool has_enforcers = mv->is_support_enforcer() ||
(mv->is_model_part() && mv->supported_facets.has_facets(*mv, EnforcerBlockerType::ENFORCER)); (mv->is_model_part() && mv->supported_facets.has_facets(*mv, EnforcerBlockerType::ENFORCER));
if (has_enforcers) { if (has_enforcers) {
StringObjectException warningtemp; warn(L("Support enforcers are used but support is not enabled. Please enable support."), "", object);
warningtemp.string = L("Support enforcers are used but support is not enabled. Please enable support.");
warningtemp.object = object;
*warning = warningtemp;
break; break;
} }
} }
@@ -1780,7 +1773,11 @@ StringObjectException Print::validate(StringObjectException *warning, Polygons*
} }
// check if print speed/accel/jerk is higher than the maximum speed of the printer // check if print speed/accel/jerk is higher than the maximum speed of the printer
if (warning) { if (warnings) {
// The motion-ability checks are mutually exclusive (gated on warning_key), so collect the
// single one that fires into a local and push it once - separate from the precise-wall and
// shrinkage warnings below.
StringObjectException motion_warning;
try { try {
auto check_motion_ability_object_setting = [&](const std::vector<std::string>& keys_to_check, double limit) -> std::string { auto check_motion_ability_object_setting = [&](const std::vector<std::string>& keys_to_check, double limit) -> std::string {
std::string warning_key; std::string warning_key;
@@ -1811,7 +1808,7 @@ StringObjectException Print::validate(StringObjectException *warning, Polygons*
if (!ignore_jerk_validation) { if (!ignore_jerk_validation) {
if (m_default_object_config.default_jerk == 1 || m_default_object_config.outer_wall_jerk == 1 || if (m_default_object_config.default_jerk == 1 || m_default_object_config.outer_wall_jerk == 1 ||
m_default_object_config.inner_wall_jerk == 1) { m_default_object_config.inner_wall_jerk == 1) {
warning->string = L("Setting the jerk speed too low could lead to artifacts on curved surfaces"); motion_warning.string = L("Setting the jerk speed too low could lead to artifacts on curved surfaces");
if (m_default_object_config.outer_wall_jerk == 1) if (m_default_object_config.outer_wall_jerk == 1)
warning_key = "outer_wall_jerk"; warning_key = "outer_wall_jerk";
else if (m_default_object_config.inner_wall_jerk == 1) else if (m_default_object_config.inner_wall_jerk == 1)
@@ -1819,7 +1816,7 @@ StringObjectException Print::validate(StringObjectException *warning, Polygons*
else else
warning_key = "default_jerk"; warning_key = "default_jerk";
warning->opt_key = warning_key; motion_warning.opt_key = warning_key;
} }
if (warning_key.empty() && m_default_object_config.default_jerk > 0) { if (warning_key.empty() && m_default_object_config.default_jerk > 0) {
@@ -1829,20 +1826,20 @@ StringObjectException Print::validate(StringObjectException *warning, Polygons*
warning_key.clear(); warning_key.clear();
warning_key = check_motion_ability_object_setting(jerk_to_check, max_jerk); warning_key = check_motion_ability_object_setting(jerk_to_check, max_jerk);
if (!warning_key.empty()) { if (!warning_key.empty()) {
warning->string = L( motion_warning.string = L(
"The jerk setting exceeds the printer's maximum jerk (machine_max_jerk_x/machine_max_jerk_y).\n" "The jerk setting exceeds the printer's maximum jerk (machine_max_jerk_x/machine_max_jerk_y).\n"
"Orca will automatically cap the jerk speed to ensure it doesn't surpass the printer's capabilities.\n" "Orca will automatically cap the jerk speed to ensure it doesn't surpass the printer's capabilities.\n"
"You can adjust the maximum jerk setting in your printer's configuration to get higher speeds."); "You can adjust the maximum jerk setting in your printer's configuration to get higher speeds.");
warning->opt_key = warning_key; motion_warning.opt_key = warning_key;
} }
} }
} }
// check junction deviation // check junction deviation
else if (m_default_object_config.default_junction_deviation.value > max_junction_deviation) { else if (m_default_object_config.default_junction_deviation.value > max_junction_deviation) {
warning->string = L( "Junction deviation setting exceeds the printer's maximum value (machine_max_junction_deviation).\n" motion_warning.string = L( "Junction deviation setting exceeds the printer's maximum value (machine_max_junction_deviation).\n"
"Orca will automatically cap the junction deviation to ensure it doesn't surpass the printer's capabilities.\n" "Orca will automatically cap the junction deviation to ensure it doesn't surpass the printer's capabilities.\n"
"You can adjust the machine_max_junction_deviation value in your printer's configuration to get higher limits."); "You can adjust the machine_max_junction_deviation value in your printer's configuration to get higher limits.");
warning->opt_key = "default_junction_deviation"; motion_warning.opt_key = "default_junction_deviation";
} }
// check acceleration // check acceleration
@@ -1877,12 +1874,12 @@ StringObjectException Print::validate(StringObjectException *warning, Polygons*
}; };
warning_key = check_motion_ability_object_setting(accel_to_check, max_accel); warning_key = check_motion_ability_object_setting(accel_to_check, max_accel);
if (!warning_key.empty()) { if (!warning_key.empty()) {
warning->string = L("The acceleration setting exceeds the printer's maximum acceleration " motion_warning.string = L("The acceleration setting exceeds the printer's maximum acceleration "
"(machine_max_acceleration_extruding).\nOrca will " "(machine_max_acceleration_extruding).\nOrca will "
"automatically cap the acceleration speed to ensure it doesn't surpass the printer's " "automatically cap the acceleration speed to ensure it doesn't surpass the printer's "
"capabilities.\nYou can adjust the " "capabilities.\nYou can adjust the "
"machine_max_acceleration_extruding value in your printer's configuration to get higher speeds."); "machine_max_acceleration_extruding value in your printer's configuration to get higher speeds.");
warning->opt_key = warning_key; motion_warning.opt_key = warning_key;
} }
if (support_travel_acc) { if (support_travel_acc) {
const auto max_travel = m_config.machine_max_acceleration_travel.values[0]; const auto max_travel = m_config.machine_max_acceleration_travel.values[0];
@@ -1892,13 +1889,13 @@ StringObjectException Print::validate(StringObjectException *warning, Polygons*
}; };
warning_key = check_motion_ability_object_setting(accel_to_check, max_travel); warning_key = check_motion_ability_object_setting(accel_to_check, max_travel);
if (!warning_key.empty()) { if (!warning_key.empty()) {
warning->string = L( motion_warning.string = L(
"The travel acceleration setting exceeds the printer's maximum travel acceleration " "The travel acceleration setting exceeds the printer's maximum travel acceleration "
"(machine_max_acceleration_travel).\nOrca will " "(machine_max_acceleration_travel).\nOrca will "
"automatically cap the travel acceleration speed to ensure it doesn't surpass the printer's " "automatically cap the travel acceleration speed to ensure it doesn't surpass the printer's "
"capabilities.\nYou can adjust the " "capabilities.\nYou can adjust the "
"machine_max_acceleration_travel value in your printer's configuration to get higher speeds."); "machine_max_acceleration_travel value in your printer's configuration to get higher speeds.");
warning->opt_key = warning_key; motion_warning.opt_key = warning_key;
} }
} }
} }
@@ -1924,19 +1921,17 @@ StringObjectException Print::validate(StringObjectException *warning, Polygons*
// } // }
// check wall sequence and precise outer wall // check wall sequence and precise outer wall
if (m_default_region_config.precise_outer_wall && m_default_region_config.wall_sequence != WallSequence::InnerOuter) { if (m_default_region_config.precise_outer_wall && m_default_region_config.wall_sequence != WallSequence::InnerOuter)
warning->string = L("The precise wall option will be ignored for outer-inner or inner-outer-inner wall sequences."); warn(L("The precise wall option will be ignored for outer-inner or inner-outer-inner wall sequences."), "precise_outer_wall");
warning->opt_key = "precise_outer_wall";
}
} catch (std::exception& e) { } catch (std::exception& e) {
BOOST_LOG_TRIVIAL(warning) << "Orca: validate motion ability failed: " << e.what() << std::endl; BOOST_LOG_TRIVIAL(warning) << "Orca: validate motion ability failed: " << e.what() << std::endl;
} }
if (!motion_warning.string.empty())
add_warning(motion_warning);
} }
if (!this->has_same_shrinkage_compensations()){ if (!this->has_same_shrinkage_compensations())
warning->string = L("Filament shrinkage will not be used because filament shrinkage for the used filaments does not match."); warn(L("Filament shrinkage will not be used because filament shrinkage for the used filaments does not match."));
warning->opt_key = "";
}
return {}; return {};
} }

View File

@@ -932,7 +932,7 @@ public:
} }
// Returns an empty string if valid, otherwise returns an error message. // Returns an empty string if valid, otherwise returns an error message.
StringObjectException validate(StringObjectException *warning = nullptr, Polygons* collison_polygons = nullptr, std::vector<std::pair<Polygon, float>>* height_polygons = nullptr) const override; StringObjectException validate(std::vector<StringObjectException> *warnings = nullptr, Polygons* collison_polygons = nullptr, std::vector<std::pair<Polygon, float>>* height_polygons = nullptr) const override;
double skirt_first_layer_height() const; double skirt_first_layer_height() const;
Flow brim_flow() const; Flow brim_flow() const;
Flow skirt_flow() const; Flow skirt_flow() const;

View File

@@ -32,7 +32,7 @@ struct StringObjectException
std::string string; std::string string;
ObjectBase const *object = nullptr; ObjectBase const *object = nullptr;
std::string opt_key; std::string opt_key;
StringExceptionType type; // warning type for tips StringExceptionType type = STRING_EXCEPT_NOT_DEFINED; // warning type for tips
bool is_warning = false; bool is_warning = false;
std::vector<std::string> params; // warning params for tips std::vector<std::string> params; // warning params for tips
}; };
@@ -396,7 +396,7 @@ public:
// Validate the print, return empty string if valid, return error if process() cannot (or should not) be started. // Validate the print, return empty string if valid, return error if process() cannot (or should not) be started.
//BBS: add more paremeters to validate //BBS: add more paremeters to validate
virtual StringObjectException validate(StringObjectException *warning = nullptr, Polygons* collison_polygons = nullptr, std::vector<std::pair<Polygon, float>>* height_polygons = nullptr) const { return {}; } virtual StringObjectException validate(std::vector<StringObjectException> *warnings = nullptr, Polygons* collison_polygons = nullptr, std::vector<std::pair<Polygon, float>>* height_polygons = nullptr) const { return {}; }
enum ApplyStatus { enum ApplyStatus {
// No change after the Print::apply() call. // No change after the Print::apply() call.

View File

@@ -613,7 +613,7 @@ std::string SLAPrint::output_filename(const std::string &filename_base) const
return this->PrintBase::output_filename(m_print_config.filename_format.value, ".sl1", filename_base, &config); return this->PrintBase::output_filename(m_print_config.filename_format.value, ".sl1", filename_base, &config);
} }
StringObjectException SLAPrint::validate(StringObjectException *exception, Polygons *collison_polygons, std::vector<std::pair<Polygon, float>> *height_polygons) const StringObjectException SLAPrint::validate(std::vector<StringObjectException> *warnings, Polygons *collison_polygons, std::vector<std::pair<Polygon, float>> *height_polygons) const
{ {
for(SLAPrintObject * po : m_objects) { for(SLAPrintObject * po : m_objects) {

View File

@@ -488,7 +488,7 @@ public:
const SLAPrintStatistics& print_statistics() const { return m_print_statistics; } const SLAPrintStatistics& print_statistics() const { return m_print_statistics; }
StringObjectException validate(StringObjectException * warning = nullptr, StringObjectException validate(std::vector<StringObjectException> * warnings = nullptr,
Polygons * collison_polygons = nullptr, Polygons * collison_polygons = nullptr,
std::vector<std::pair<Polygon, float>> *height_polygons = nullptr) const override; std::vector<std::pair<Polygon, float>> *height_polygons = nullptr) const override;

View File

@@ -675,13 +675,13 @@ bool BackgroundSlicingProcess::empty() const
return m_print->empty(); return m_print->empty();
} }
StringObjectException BackgroundSlicingProcess::validate(StringObjectException *warning, Polygons* collison_polygons, std::vector<std::pair<Polygon, float>>* height_polygons) StringObjectException BackgroundSlicingProcess::validate(std::vector<StringObjectException> *warnings, Polygons* collison_polygons, std::vector<std::pair<Polygon, float>>* height_polygons)
{ {
assert(m_print != nullptr); assert(m_print != nullptr);
assert(m_print == m_fff_print); assert(m_print == m_fff_print);
m_fff_print->is_BBL_printer() = wxGetApp().preset_bundle->is_bbl_vendor(); m_fff_print->is_BBL_printer() = wxGetApp().preset_bundle->is_bbl_vendor();
return m_print->validate(warning, collison_polygons, height_polygons); return m_print->validate(warnings, collison_polygons, height_polygons);
} }
// Apply config over the print. Returns false, if the new config values caused any of the already // Apply config over the print. Returns false, if the new config values caused any of the already

View File

@@ -145,7 +145,7 @@ public:
bool empty() const; bool empty() const;
// Validate the print. Returns an empty string if valid, returns an error message if invalid. // Validate the print. Returns an empty string if valid, returns an error message if invalid.
// Call validate before calling start(). // Call validate before calling start().
StringObjectException validate(StringObjectException *warning = nullptr, Polygons* collison_polygons = nullptr, std::vector<std::pair<Polygon, float>>* height_polygons = nullptr); StringObjectException validate(std::vector<StringObjectException> *warnings = nullptr, Polygons* collison_polygons = nullptr, std::vector<std::pair<Polygon, float>>* height_polygons = nullptr);
// Set the export path of the G-code. // Set the export path of the G-code.
// Once the path is set, the G-code // Once the path is set, the G-code

View File

@@ -1005,7 +1005,8 @@ private:
NotificationType::PlaterWarning, NotificationType::PlaterWarning,
NotificationType::ProgressBar, NotificationType::ProgressBar,
NotificationType::PrintHostUpload, NotificationType::PrintHostUpload,
NotificationType::SimplifySuggestion NotificationType::SimplifySuggestion,
NotificationType::ValidateWarning
}; };
//prepared (basic) notifications //prepared (basic) notifications
// non-static so its not loaded too early. If static, the translations wont load correctly. // non-static so its not loaded too early. If static, the translations wont load correctly.

View File

@@ -4625,6 +4625,7 @@ struct Plater::priv
} }
void process_validation_warning(StringObjectException const &warning) const; void process_validation_warning(StringObjectException const &warning) const;
void process_validation_warnings(const std::vector<StringObjectException> &warnings) const;
bool background_processing_enabled() const { bool background_processing_enabled() const {
#ifdef SUPPORT_BACKGROUND_PROCESSING #ifdef SUPPORT_BACKGROUND_PROCESSING
@@ -7949,6 +7950,15 @@ void Plater::priv::process_validation_warning(StringObjectException const &warni
} }
} }
void Plater::priv::process_validation_warnings(const std::vector<StringObjectException> &warnings) const
{
// ValidateWarning stacks by text (m_multiple_types), so clear the stale set before re-adding.
notification_manager->close_notification_of_type(NotificationType::ValidateWarning);
for (const StringObjectException &warning : warnings)
if (!warning.string.empty())
process_validation_warning(warning);
}
// Update background processing thread from the current config and Model. // Update background processing thread from the current config and Model.
// Returns a bitmask of UpdateBackgroundProcessReturnState. // Returns a bitmask of UpdateBackgroundProcessReturnState.
@@ -8029,14 +8039,14 @@ unsigned int Plater::priv::update_background_process(bool force_validation, bool
// The state of the Print changed, and it is non-zero. Let's validate it and give the user feedback on errors. // The state of the Print changed, and it is non-zero. Let's validate it and give the user feedback on errors.
//BBS: add is_warning logic //BBS: add is_warning logic
StringObjectException warning; std::vector<StringObjectException> warnings;
//BBS: refine seq-print logic //BBS: refine seq-print logic
Polygons polygons; Polygons polygons;
std::vector<std::pair<Polygon, float>> height_polygons; std::vector<std::pair<Polygon, float>> height_polygons;
StringObjectException err = background_process.validate(&warning, &polygons, &height_polygons); StringObjectException err = background_process.validate(&warnings, &polygons, &height_polygons);
// update string by type // update string by type
q->post_process_string_object_exception(err); q->post_process_string_object_exception(err);
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format(": validate err=%1%, warning=%2%")%err.string%warning.string; BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format(": validate err=%1%, warnings=%2%")%err.string%warnings.size();
if (err.string.empty()) { if (err.string.empty()) {
this->partplate_list.get_curr_plate()->update_apply_result_invalid(false); this->partplate_list.get_curr_plate()->update_apply_result_invalid(false);
@@ -8047,9 +8057,7 @@ unsigned int Plater::priv::update_background_process(bool force_validation, bool
if (invalidated != Print::APPLY_STATUS_UNCHANGED && background_processing_enabled()) if (invalidated != Print::APPLY_STATUS_UNCHANGED && background_processing_enabled())
return_state |= UPDATE_BACKGROUND_PROCESS_RESTART; return_state |= UPDATE_BACKGROUND_PROCESS_RESTART;
// Pass a warning from validation and either show a notification, process_validation_warnings(warnings);
// or hide the old one.
process_validation_warning(warning);
if (printer_technology == ptFFF) { if (printer_technology == ptFFF) {
view3D->get_canvas3d()->reset_sequential_print_clearance(); view3D->get_canvas3d()->reset_sequential_print_clearance();
view3D->get_canvas3d()->set_as_dirty(); view3D->get_canvas3d()->set_as_dirty();
@@ -8062,7 +8070,7 @@ unsigned int Plater::priv::update_background_process(bool force_validation, bool
// Show error as notification. // Show error as notification.
notification_manager->push_validate_error_notification(err); notification_manager->push_validate_error_notification(err);
//also update the warnings //also update the warnings
process_validation_warning(warning); process_validation_warnings(warnings);
return_state |= UPDATE_BACKGROUND_PROCESS_INVALID; return_state |= UPDATE_BACKGROUND_PROCESS_INVALID;
if (printer_technology == ptFFF) { if (printer_technology == ptFFF) {
const Print* print = background_process.fff_print(); const Print* print = background_process.fff_print();
@@ -17693,14 +17701,14 @@ void Plater::validate_current_plate(bool& model_fits, bool& validate_error)
if (p->printer_technology == ptFFF) { if (p->printer_technology == ptFFF) {
//std::string plater_text = _u8L("An object is laid over the boundary of plate or exceeds the height limit.\n" //std::string plater_text = _u8L("An object is laid over the boundary of plate or exceeds the height limit.\n"
// "Please solve the problem by moving it totally on or off the plate, and confirming that the height is within the build volume.");; // "Please solve the problem by moving it totally on or off the plate, and confirming that the height is within the build volume.");;
StringObjectException warning; std::vector<StringObjectException> warnings;
Polygons polygons; Polygons polygons;
std::vector<std::pair<Polygon, float>> height_polygons; std::vector<std::pair<Polygon, float>> height_polygons;
p->background_process.fff_print()->set_check_multi_filaments_compatibility(wxGetApp().app_config->get("enable_high_low_temp_mixed_printing") == "false"); p->background_process.fff_print()->set_check_multi_filaments_compatibility(wxGetApp().app_config->get("enable_high_low_temp_mixed_printing") == "false");
StringObjectException err = p->background_process.validate(&warning, &polygons, &height_polygons); StringObjectException err = p->background_process.validate(&warnings, &polygons, &height_polygons);
// update string by type // update string by type
post_process_string_object_exception(err); post_process_string_object_exception(err);
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format(": validate err=%1%, warning=%2%, model_fits %3%")%err.string%warning.string %model_fits; BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format(": validate err=%1%, warnings=%2%, model_fits %3%")%err.string%warnings.size() %model_fits;
if (err.string.empty()) { if (err.string.empty()) {
p->partplate_list.get_curr_plate()->update_apply_result_invalid(false); p->partplate_list.get_curr_plate()->update_apply_result_invalid(false);
@@ -17708,9 +17716,7 @@ void Plater::validate_current_plate(bool& model_fits, bool& validate_error)
p->notification_manager->close_notification_of_type(NotificationType::ValidateError); p->notification_manager->close_notification_of_type(NotificationType::ValidateError);
p->notification_manager->bbl_close_3mf_warn_notification(); p->notification_manager->bbl_close_3mf_warn_notification();
// Pass a warning from validation and either show a notification, p->process_validation_warnings(warnings);
// or hide the old one.
p->process_validation_warning(warning);
p->view3D->get_canvas3d()->reset_sequential_print_clearance(); p->view3D->get_canvas3d()->reset_sequential_print_clearance();
p->view3D->get_canvas3d()->set_as_dirty(); p->view3D->get_canvas3d()->set_as_dirty();
p->view3D->get_canvas3d()->request_extra_frame(); p->view3D->get_canvas3d()->request_extra_frame();
@@ -17720,7 +17726,7 @@ void Plater::validate_current_plate(bool& model_fits, bool& validate_error)
p->partplate_list.get_curr_plate()->update_apply_result_invalid(true); p->partplate_list.get_curr_plate()->update_apply_result_invalid(true);
// Show error as notification. // Show error as notification.
p->notification_manager->push_validate_error_notification(err); p->notification_manager->push_validate_error_notification(err);
p->process_validation_warning(warning); p->process_validation_warnings(warnings);
//model_fits = false; //model_fits = false;
validate_error = true; validate_error = true;
p->view3D->get_canvas3d()->set_sequential_print_clearance_visible(true); p->view3D->get_canvas3d()->set_sequential_print_clearance_visible(true);

View File

@@ -3,9 +3,12 @@
#include "libslic3r/libslic3r.h" #include "libslic3r/libslic3r.h"
#include "libslic3r/Print.hpp" #include "libslic3r/Print.hpp"
#include "libslic3r/Layer.hpp" #include "libslic3r/Layer.hpp"
#include "libslic3r/Model.hpp"
#include "test_data.hpp" #include "test_data.hpp"
#include <algorithm>
using namespace Slic3r; using namespace Slic3r;
using namespace Slic3r::Test; using namespace Slic3r::Test;
@@ -103,3 +106,166 @@ SCENARIO("Print: Brim generation", "[Print]") {
} }
} }
} }
// ---------------------------------------------------------------------------
// Print::validate() warning collection
//
// validate() returns its warnings in a vector. The warning paths deliberately
// differ in how many entries they produce; these tests pin down each behaviour:
// * independent checks -> stack (one entry each)
// * motion-ability -> coalesce into one (mutually exclusive, gated)
// * clumping detection -> one independent warning
// * layered clearance -> many collisions concatenated into one entry
// * null warnings pointer -> no-op, no crash, no blocking error
// ---------------------------------------------------------------------------
namespace {
// Build `n` 20mm cubes (spread apart, or stacked at the origin when `overlap`) into
// `model`/`print` and apply `config`, leaving the print ready to validate(). No slicing needed.
void build_cubes(Slic3r::Model& model, Slic3r::Print& print,
DynamicPrintConfig config, int n, bool overlap)
{
config.set_key_value("layer_change_gcode", new ConfigOptionString("G92 E0\n")); // validate() relative-E reset
for (int i = 0; i < n; ++i) {
ModelObject* object = model.add_object();
object->add_volume(Slic3r::Test::mesh(TestMesh::cube_20x20x20));
ModelInstance* inst = object->add_instance();
inst->set_offset(Vec3d(overlap ? 0.0 : i * 60.0, 0.0, 0.0));
}
for (ModelObject* mo : model.objects) {
mo->ensure_on_bed();
print.auto_assign_extruders(mo);
}
print.apply(model, config);
}
// Build cubes and run validate(), collecting warnings; returns the blocking error.
StringObjectException validate_cubes(const DynamicPrintConfig& config,
std::vector<StringObjectException>& warnings,
int n = 1, bool overlap = false)
{
Slic3r::Model model;
Slic3r::Print print;
build_cubes(model, print, config, n, overlap);
return print.validate(&warnings);
}
size_t count_opt_key(const std::vector<StringObjectException>& warnings, const std::string& key)
{
return std::count_if(warnings.begin(), warnings.end(),
[&](const StringObjectException& w) { return w.opt_key == key; });
}
// Make `default_acceleration` exceed the machine's extruding-acceleration limit.
void trigger_acceleration_warning(DynamicPrintConfig& c)
{
c.set_key_value("machine_max_acceleration_extruding", new ConfigOptionFloats{ 100. });
c.set_key_value("default_acceleration", new ConfigOptionFloat(100000.));
}
// Make `default_jerk` exceed the machine's jerk limit (junction deviation off so
// the jerk check is not skipped).
void trigger_jerk_warning(DynamicPrintConfig& c)
{
c.set_key_value("machine_max_junction_deviation", new ConfigOptionFloats{ 0. });
c.set_key_value("machine_max_jerk_x", new ConfigOptionFloats{ 1. });
c.set_key_value("machine_max_jerk_y", new ConfigOptionFloats{ 1. });
c.set_key_value("default_jerk", new ConfigOptionFloat(9999.));
}
// Precise outer wall is ignored unless the wall sequence is inner-outer.
void trigger_precise_wall_warning(DynamicPrintConfig& c)
{
c.set_key_value("precise_outer_wall", new ConfigOptionBool(true));
c.set_key_value("wall_sequence", new ConfigOptionEnum<WallSequence>(WallSequence::OuterInner));
}
} // namespace
TEST_CASE("Print::validate stacks independent warnings", "[Print][validate]")
{
// Two unrelated checks (region precise-wall + machine acceleration) must each
// contribute their own entry.
DynamicPrintConfig config = DynamicPrintConfig::full_print_config();
trigger_precise_wall_warning(config);
trigger_acceleration_warning(config);
std::vector<StringObjectException> warnings;
StringObjectException err = validate_cubes(config, warnings);
CHECK(err.string.empty());
CHECK(warnings.size() >= 2);
CHECK(count_opt_key(warnings, "precise_outer_wall") == 1); // jump-to key is preserved
for (const auto& w : warnings)
CHECK(w.is_warning); // every collected entry is a warning
}
TEST_CASE("Print::validate coalesces motion-ability warnings into one", "[Print][validate]")
{
// The jerk/junction/acceleration checks are mutually exclusive (gated on a shared
// key), so adding a second motion trigger must NOT add a second warning.
DynamicPrintConfig accel_only = DynamicPrintConfig::full_print_config();
trigger_acceleration_warning(accel_only);
std::vector<StringObjectException> w_accel;
CHECK(validate_cubes(accel_only, w_accel).string.empty());
DynamicPrintConfig accel_and_jerk = DynamicPrintConfig::full_print_config();
trigger_acceleration_warning(accel_and_jerk);
trigger_jerk_warning(accel_and_jerk);
std::vector<StringObjectException> w_both;
CHECK(validate_cubes(accel_and_jerk, w_both).string.empty());
CHECK(w_accel.size() >= 1);
CHECK(w_both.size() == w_accel.size()); // the extra motion trigger collapses into the same warning
}
TEST_CASE("Print::validate reports the clumping-detection warning", "[Print][validate]")
{
// A distinct single-shot path: clumping/wrapping detection without a prime tower warns
// (and carries the enable_prime_tower jump-to key). enable_prime_tower must be off, as
// the warning lives in the no-prime-tower branch.
DynamicPrintConfig config = DynamicPrintConfig::full_print_config();
config.set_key_value("enable_prime_tower", new ConfigOptionBool(false));
config.set_key_value("enable_wrapping_detection", new ConfigOptionBool(true));
std::vector<StringObjectException> warnings;
StringObjectException err = validate_cubes(config, warnings);
CHECK(err.string.empty());
CHECK(count_opt_key(warnings, "enable_prime_tower") == 1);
}
TEST_CASE("Print::validate concatenates layered-clearance collisions into one warning", "[Print][validate]")
{
// In by-layer mode, layered_print_cleareance_valid folds every too-close pair into a
// single warning entry (newline-joined), unlike the per-check stacking above. Isolate
// that entry by type so unrelated default-config warnings don't affect the assertion.
DynamicPrintConfig config = DynamicPrintConfig::full_print_config();
std::vector<StringObjectException> warnings;
StringObjectException err = validate_cubes(config, warnings, /*n=*/3, /*overlap=*/true);
CHECK(err.string.empty());
auto is_layered = [](const StringObjectException& w) {
return w.type == STRING_EXCEPT_OBJECT_COLLISION_IN_LAYER_PRINT; };
REQUIRE(std::count_if(warnings.begin(), warnings.end(), is_layered) == 1); // 3 objects, 2 collisions, 1 entry
auto it = std::find_if(warnings.begin(), warnings.end(), is_layered);
CHECK(it->string.find('\n') != std::string::npos); // the collisions were concatenated
}
TEST_CASE("Print::validate tolerates a null warnings pointer", "[Print][validate]")
{
// Callers may pass no warnings sink: a warning-producing config must not crash
// and must still return without a blocking error.
DynamicPrintConfig config = DynamicPrintConfig::full_print_config();
trigger_precise_wall_warning(config);
trigger_acceleration_warning(config);
Slic3r::Model model;
Slic3r::Print print;
build_cubes(model, print, config, /*n=*/1, /*overlap=*/false);
StringObjectException err = print.validate(); // warnings == nullptr
CHECK(err.string.empty());
}