From 66a4b5b82afaa80f0993c76189eff158082bfcc5 Mon Sep 17 00:00:00 2001 From: Pavel Mikus Date: Sun, 29 Jan 2023 20:08:47 +0100 Subject: [PATCH] Refactored stability alert step from object step into print step, implemented stability issues grouping based on number of issues and objects, maximum of single warning message emitted --- src/libslic3r/Print.cpp | 98 ++++++++++++++++++++++++- src/libslic3r/Print.hpp | 5 +- src/libslic3r/PrintApply.cpp | 2 +- src/libslic3r/PrintObject.cpp | 63 ++-------------- src/libslic3r/SupportSpotsGenerator.cpp | 55 ++++++++------ src/libslic3r/SupportSpotsGenerator.hpp | 15 ++-- src/slic3r/GUI/Plater.cpp | 6 +- 7 files changed, 149 insertions(+), 95 deletions(-) diff --git a/src/libslic3r/Print.cpp b/src/libslic3r/Print.cpp index 6bf468571..736cfb4b2 100644 --- a/src/libslic3r/Print.cpp +++ b/src/libslic3r/Print.cpp @@ -841,8 +841,8 @@ void Print::process() obj->ironing(); for (PrintObject *obj : m_objects) obj->generate_support_spots(); - for (PrintObject *obj : m_objects) - obj->alert_when_supports_needed(); + // check data from previous step, format the error message(s) and send alert to ui + alert_when_supports_needed(); for (PrintObject *obj : m_objects) obj->generate_support_material(); for (PrintObject *obj : m_objects) @@ -1065,6 +1065,8 @@ void Print::_make_skirt() append(m_skirt_convex_hull, std::move(poly.points)); } + + Polygons Print::first_layer_islands() const { Polygons islands; @@ -1117,6 +1119,98 @@ void Print::finalize_first_layer_convex_hull() m_first_layer_convex_hull = Geometry::convex_hull(m_first_layer_convex_hull.points); } +void Print::alert_when_supports_needed() +{ + if (this->set_started(psAlertWhenSupportsNeeded)) { + BOOST_LOG_TRIVIAL(debug) << "psAlertWhenSupportsNeeded - start"; + set_status(69, L("Alert if supports needed")); + + auto issue_to_alert_message = [](SupportSpotsGenerator::SupportPointCause cause, bool critical) { + std::string message; + switch (cause) { + case SupportSpotsGenerator::SupportPointCause::LongBridge: message = L("long bridging extrusions"); break; + case SupportSpotsGenerator::SupportPointCause::FloatingBridgeAnchor: message = L("floating bridge anchors"); break; + case SupportSpotsGenerator::SupportPointCause::FloatingExtrusion: + if (critical) { + message = L("collapsing overhang"); + } else { + message = L("loose extrusions"); + } + break; + case SupportSpotsGenerator::SupportPointCause::SeparationFromBed: message = L("low bed adhesion"); break; + case SupportSpotsGenerator::SupportPointCause::UnstableFloatingPart: message = L("floating object part"); break; + case SupportSpotsGenerator::SupportPointCause::WeakObjectPart: message = L("thin fragile section"); break; + } + + return (critical ? "!" : "") + message; + }; + + // vector of pairs of object and its issues, where each issue is a pair of type and critical flag + std::vector>>> objects_isssues; + + for (const PrintObject *object : m_objects) { + std::unordered_set checked_model_objects; + if (!object->has_support() && checked_model_objects.find(object->model_object()) == checked_model_objects.end()) { + if (object->m_shared_regions->generated_support_points.has_value()) { + SupportSpotsGenerator::SupportPoints supp_points = object->m_shared_regions->generated_support_points->support_points; + SupportSpotsGenerator::PartialObjects partial_objects = object->m_shared_regions->generated_support_points + ->partial_objects; + auto issues = SupportSpotsGenerator::gather_issues(supp_points, partial_objects); + if (issues.size() > 0) { + objects_isssues.emplace_back(object, issues); + } + } + checked_model_objects.emplace(object->model_object()); + } + } + + bool recommend_brim = false; + std::map, std::vector> po_by_support_issues; + for (const auto &obj : objects_isssues) { + for (const auto &issue : obj.second) { + po_by_support_issues[issue].push_back(obj.first); + if (issue.first == SupportSpotsGenerator::SupportPointCause::SeparationFromBed){ + recommend_brim = true; + } + } + } + + auto message = L("Detected print stability issues") + ": \n"; + if (objects_isssues.size() > po_by_support_issues.size()) { + // there are more objects than causes, group by issues + for (const auto &issue : po_by_support_issues) { + message += "\n" + issue_to_alert_message(issue.first.first, issue.first.second) + " >> "; + for (const auto &obj : issue.second) { + message += obj->m_model_object->name + ", "; + } + message.pop_back(); + message.pop_back(); // remove , + message += ".\n"; + } + } else { + // more causes than objects, group by objects + for (const auto &obj : objects_isssues) { + message += "\n" + L("Object") + " " + obj.first->model_object()->name + " << "; + for (const auto &issue : obj.second) { + message += issue_to_alert_message(issue.first, issue.second) + ", "; + } + message.pop_back(); + message.pop_back(); // remove , + message += ".\n"; + } + } + + message += "\n" + L("Consider enabling supports") + (recommend_brim ? (" " + L("and brim")) : "") + "."; + + if (objects_isssues.size() > 0) { + this->active_step_add_warning(PrintStateBase::WarningLevel::NON_CRITICAL, message); + } + + BOOST_LOG_TRIVIAL(debug) << "psAlertWhenSupportsNeeded - end"; + this->set_done(psAlertWhenSupportsNeeded); + } +} + // Wipe tower support. bool Print::has_wipe_tower() const { diff --git a/src/libslic3r/Print.hpp b/src/libslic3r/Print.hpp index b71b98578..3795c2449 100644 --- a/src/libslic3r/Print.hpp +++ b/src/libslic3r/Print.hpp @@ -54,6 +54,7 @@ enum PrintStep : unsigned int { // psToolOrdering is a synonym to psWipeTower, as the Wipe Tower calculates and modifies the ToolOrdering, // while if printing without the Wipe Tower, the ToolOrdering is calculated as well. psToolOrdering = psWipeTower, + psAlertWhenSupportsNeeded, psSkirtBrim, // Last step before G-code export, after this step is finished, the initial extrusion path preview // should be refreshed. @@ -64,7 +65,7 @@ enum PrintStep : unsigned int { enum PrintObjectStep : unsigned int { posSlice, posPerimeters, posPrepareInfill, - posInfill, posIroning, posSupportSpotsSearch, posAlertWhenSupportsNeeded, posSupportMaterial, posEstimateCurledExtrusions, posCount, + posInfill, posIroning, posSupportSpotsSearch, posSupportMaterial, posEstimateCurledExtrusions, posCount, }; // A PrintRegion object represents a group of volumes to print @@ -371,7 +372,6 @@ private: void infill(); void ironing(); void generate_support_spots(); - void alert_when_supports_needed(); void generate_support_material(); void estimate_curled_extrusions(); @@ -609,6 +609,7 @@ private: void _make_skirt(); void _make_wipe_tower(); void finalize_first_layer_convex_hull(); + void alert_when_supports_needed(); // Islands of objects and their supports extruded at the 1st layer. Polygons first_layer_islands() const; diff --git a/src/libslic3r/PrintApply.cpp b/src/libslic3r/PrintApply.cpp index cc0a790d3..e137f0157 100644 --- a/src/libslic3r/PrintApply.cpp +++ b/src/libslic3r/PrintApply.cpp @@ -1335,7 +1335,7 @@ Print::ApplyStatus Print::apply(const Model &model, DynamicPrintConfig new_full_ deleted_objects = true; } if (new_objects || deleted_objects) - update_apply_status(this->invalidate_steps({ psSkirtBrim, psWipeTower, psGCodeExport })); + update_apply_status(this->invalidate_steps({ psAlertWhenSupportsNeeded, psSkirtBrim, psWipeTower, psGCodeExport })); if (new_objects) update_apply_status(false); print_regions_reshuffled = true; diff --git a/src/libslic3r/PrintObject.cpp b/src/libslic3r/PrintObject.cpp index 06137a7ec..da330603f 100644 --- a/src/libslic3r/PrintObject.cpp +++ b/src/libslic3r/PrintObject.cpp @@ -433,57 +433,6 @@ void PrintObject::generate_support_spots() } } -void PrintObject::alert_when_supports_needed() -{ - if (this->set_started(posAlertWhenSupportsNeeded)) { - BOOST_LOG_TRIVIAL(debug) << "posAlertWhenSupportsNeeded - start"; - m_print->set_status(69, L("Alert if supports needed")); - - auto alert_fn = [&](PrintStateBase::WarningLevel level, SupportSpotsGenerator::SupportPointCause cause) { - switch (cause) { - case SupportSpotsGenerator::SupportPointCause::LongBridge: - this->active_step_add_warning(level, L("There are bridges longer than recommended length. Consider adding supports. ") + - (L("Object name")) + ": " + this->model_object()->name); - break; - case SupportSpotsGenerator::SupportPointCause::FloatingBridgeAnchor: - this->active_step_add_warning(level, L("Unsupported bridges will collapse. Supports are needed. ") + (L("Object name")) + - ": " + this->model_object()->name); - break; - case SupportSpotsGenerator::SupportPointCause::FloatingExtrusion: - if (level == PrintStateBase::WarningLevel::CRITICAL) { - this->active_step_add_warning(level, L("Clusters of unsupported extrusions found. Supports are needed. ") + - (L("Object name")) + ": " + this->model_object()->name); - } else { - this->active_step_add_warning(level, L("Some unspported extrusions found. Consider adding supports. ") + - (L("Object name")) + ": " + this->model_object()->name); - } - break; - case SupportSpotsGenerator::SupportPointCause::SeparationFromBed: - this->active_step_add_warning(level, L("Object part may break from the bed. Consider adding brim and/or supports. ") + - (L("Object name")) + ": " + this->model_object()->name); - break; - case SupportSpotsGenerator::SupportPointCause::UnstableFloatingPart: - this->active_step_add_warning(level, L("Floating object parts detected. Supports are needed. ") + (L("Object name")) + - ": " + this->model_object()->name); - break; - case SupportSpotsGenerator::SupportPointCause::WeakObjectPart: - this->active_step_add_warning(level, L("Thin parts of the object may break. Consider adding supports. ") + - (L("Object name")) + ": " + this->model_object()->name); - break; - } - }; - - if (!this->has_support() && this->m_shared_regions->generated_support_points.has_value()) { - SupportSpotsGenerator::SupportPoints supp_points = this->m_shared_regions->generated_support_points->support_points; - SupportSpotsGenerator::PartialObjects partial_objects = this->m_shared_regions->generated_support_points->partial_objects; - SupportSpotsGenerator::raise_alerts_for_issues(supp_points, partial_objects, alert_fn); - } - - BOOST_LOG_TRIVIAL(debug) << "posAlertWhenSupportsNeeded - end"; - this->set_done(posAlertWhenSupportsNeeded); - } -} - void PrintObject::generate_support_material() { if (this->set_started(posSupportMaterial)) { @@ -845,26 +794,26 @@ bool PrintObject::invalidate_step(PrintObjectStep step) // propagate to dependent steps if (step == posPerimeters) { - invalidated |= this->invalidate_steps({ posPrepareInfill, posInfill, posIroning, posSupportSpotsSearch, posAlertWhenSupportsNeeded, posEstimateCurledExtrusions }); + invalidated |= this->invalidate_steps({ posPrepareInfill, posInfill, posIroning, posSupportSpotsSearch, posEstimateCurledExtrusions }); invalidated |= m_print->invalidate_steps({ psSkirtBrim }); } else if (step == posPrepareInfill) { - invalidated |= this->invalidate_steps({ posInfill, posIroning, posSupportSpotsSearch, posAlertWhenSupportsNeeded }); + invalidated |= this->invalidate_steps({ posInfill, posIroning, posSupportSpotsSearch}); } else if (step == posInfill) { - invalidated |= this->invalidate_steps({ posIroning, posSupportSpotsSearch, posAlertWhenSupportsNeeded }); + invalidated |= this->invalidate_steps({ posIroning, posSupportSpotsSearch }); invalidated |= m_print->invalidate_steps({ psSkirtBrim }); } else if (step == posSlice) { invalidated |= this->invalidate_steps({posPerimeters, posPrepareInfill, posInfill, posIroning, posSupportSpotsSearch, - posAlertWhenSupportsNeeded, posSupportMaterial, posEstimateCurledExtrusions}); + posSupportMaterial, posEstimateCurledExtrusions}); invalidated |= m_print->invalidate_steps({ psSkirtBrim }); m_slicing_params.valid = false; - } else if (step == posSupportSpotsSearch) { - invalidated |= posAlertWhenSupportsNeeded; } else if (step == posSupportMaterial) { invalidated |= m_print->invalidate_steps({ psSkirtBrim, }); invalidated |= this->invalidate_steps({ posEstimateCurledExtrusions }); m_slicing_params.valid = false; } + // invalidate alerts step always, since it depends on everything (except supports, but with supports enabled it is skipped anyway.) + invalidated |= m_print->invalidate_step(psAlertWhenSupportsNeeded); // Wipe tower depends on the ordering of extruders, which in turn depends on everything. // It also decides about what the wipe_into_infill / wipe_into_object features will do, // and that too depends on many of the settings. diff --git a/src/libslic3r/SupportSpotsGenerator.cpp b/src/libslic3r/SupportSpotsGenerator.cpp index fb824707c..5c6c2d84b 100644 --- a/src/libslic3r/SupportSpotsGenerator.cpp +++ b/src/libslic3r/SupportSpotsGenerator.cpp @@ -330,14 +330,14 @@ std::vector check_extrusion_entity_stability(const ExtrusionEntit ((1.0f + std::abs(curr_point.curvature)) * (1.0f + std::abs(curr_point.curvature)) * (1.0f + std::abs(curr_point.curvature)))); - if (curr_point.distance > 2.0f * flow_width) { + if (curr_point.distance > 1.2f * flow_width) { line_out.form_quality = 0.8f; bridged_distance += line_len; if (bridged_distance > max_bridge_len) { line_out.support_point_generated = potential_cause; bridged_distance = 0.0f; } - } else if (curr_point.distance > flow_width * (1.0 + std::clamp(curr_point.curvature, -0.30f, 0.20f))) { + } else if (curr_point.distance > flow_width * 0.8f) { bridged_distance += line_len; line_out.form_quality = nearest_prev_layer_line.form_quality - 0.3f; if (line_out.form_quality < 0 && bridged_distance > max_bridge_len) { @@ -554,6 +554,10 @@ public: params.material_yield_strength; float conn_weight_arm = (conn_centroid.head<2>() - mass_centroid.head<2>()).norm(); + if (layer_z - conn_centroid.z() < 30.0) { + conn_weight_arm = 0.0f; // Given that we do not have very good info about the weight distribution between the connection and current layer, + // do not consider the weight until quite far away from the weak connection segment + } float conn_weight_torque = conn_weight_arm * weight * (1.0f - conn_centroid.z() / layer_z) * (1.0f - conn_centroid.z() / layer_z); float conn_movement_arm = std::max(0.0f, mass_centroid.z() - conn_centroid.z()); @@ -1170,26 +1174,30 @@ void estimate_malformations(LayerPtrs &layers, const Params ¶ms) #endif } -void raise_alerts_for_issues(const SupportPoints &support_points, - PartialObjects &partial_objects, - std::function alert_fn) +std::vector> gather_issues(const SupportPoints &support_points, PartialObjects &partial_objects) { + std::vector> result; + // The partial object are most likely sorted from smaller to larger as the print continues, so this should save some sorting time std::reverse(partial_objects.begin(), partial_objects.end()); std::sort(partial_objects.begin(), partial_objects.end(), [](const PartialObject &left, const PartialObject &right) { return left.volume > right.volume; }); - float max_volume_part = partial_objects.front().volume; + float max_volume_part = partial_objects.front().volume; + bool unstable_floating_part_added = false; for (const PartialObject &p : partial_objects) { if (p.volume > max_volume_part / 500.0f && !p.connected_to_bed) { - alert_fn(PrintStateBase::WarningLevel::CRITICAL, SupportPointCause::UnstableFloatingPart); - return; + result.emplace_back(SupportPointCause::UnstableFloatingPart, true); + unstable_floating_part_added = true; + break; } } - for (const SupportPoint &sp : support_points) { - if (sp.cause == SupportPointCause::UnstableFloatingPart) { - alert_fn(PrintStateBase::WarningLevel::CRITICAL, SupportPointCause::UnstableFloatingPart); - return; + if (!unstable_floating_part_added) { + for (const SupportPoint &sp : support_points) { + if (sp.cause == SupportPointCause::UnstableFloatingPart) { + result.emplace_back(SupportPointCause::UnstableFloatingPart, true); + break; + } } } @@ -1215,39 +1223,40 @@ void raise_alerts_for_issues(const SupportPoints } if (score > 5) { if (floating_bridge) { - alert_fn(PrintStateBase::WarningLevel::CRITICAL, SupportPointCause::FloatingBridgeAnchor); + result.emplace_back(SupportPointCause::FloatingBridgeAnchor, true); } else { - alert_fn(PrintStateBase::WarningLevel::CRITICAL, SupportPointCause::FloatingExtrusion); + result.emplace_back(SupportPointCause::FloatingExtrusion, true); } - return; + break; } } for (const SupportPoint &sp : support_points) { if (sp.cause == SupportPointCause::SeparationFromBed) { - alert_fn(PrintStateBase::WarningLevel::NON_CRITICAL, SupportPointCause::SeparationFromBed); - return; + result.emplace_back(SupportPointCause::SeparationFromBed, true); + break; } } for (const SupportPoint &sp : support_points) { if (sp.cause == SupportPointCause::WeakObjectPart) { - alert_fn(PrintStateBase::WarningLevel::NON_CRITICAL, SupportPointCause::WeakObjectPart); - return; + result.emplace_back(SupportPointCause::WeakObjectPart, true); + break; } } if (ext_supp_points.size() > 5) { - alert_fn(PrintStateBase::WarningLevel::NON_CRITICAL, SupportPointCause::FloatingExtrusion); - return; + result.emplace_back(SupportPointCause::FloatingExtrusion, false); } for (const SupportPoint &sp : support_points) { if (sp.cause == SupportPointCause::LongBridge) { - alert_fn(PrintStateBase::WarningLevel::NON_CRITICAL, SupportPointCause::LongBridge); - return; + result.emplace_back(SupportPointCause::LongBridge, false); + break; } } + + return result; } } // namespace SupportSpotsGenerator diff --git a/src/libslic3r/SupportSpotsGenerator.hpp b/src/libslic3r/SupportSpotsGenerator.hpp index 23fb5dad0..b72ea5c75 100644 --- a/src/libslic3r/SupportSpotsGenerator.hpp +++ b/src/libslic3r/SupportSpotsGenerator.hpp @@ -52,7 +52,7 @@ struct Params const float gravity_constant = 9806.65f; // mm/s^2; gravity acceleration on Earth's surface, algorithm assumes that printer is in upwards position. const double filament_density = 1.25e-3f; // g/mm^3 ; Common filaments are very lightweight, so precise number is not that important const double material_yield_strength = 33.0f * 1e6f; // (g*mm/s^2)/mm^2; 33 MPa is yield strength of ABS, which has the lowest yield strength from common materials. - const float standard_extruder_conflict_force = 20.0f * gravity_constant; // force that can occasionally push the model due to various factors (filament leaks, small curling, ... ); + const float standard_extruder_conflict_force = 5.0f * gravity_constant; // force that can occasionally push the model due to various factors (filament leaks, small curling, ... ); const float malformations_additive_conflict_extruder_force = 100.0f * gravity_constant; // for areas with possible high layered curled filaments // MPa * 1e^6 = (g*mm/s^2)/mm^2 = g/(mm*s^2); yield strength of the bed surface @@ -62,19 +62,19 @@ struct Params } if (filament_type == "PLA") { - return 0.018 * 1e6; + return 0.02 * 1e6; } else if (filament_type == "PET" || filament_type == "PETG") { return 0.3 * 1e6; } else if (filament_type == "ABS" || filament_type == "ASA") { return 0.1 * 1e6; //TODO do measurements } else { //PLA default value - defensive approach, PLA has quite low adhesion - return 0.018 * 1e6; + return 0.02 * 1e6; } } //just return PLA adhesion value as value for supports double get_support_spots_adhesion_strength() const { - return 0.018f * 1e6; + return 0.02f * 1e6; } }; @@ -150,9 +150,10 @@ std::tuple full_search(const PrintObject *po, con void estimate_supports_malformations(std::vector &layers, float supports_flow_width, const Params ¶ms); void estimate_malformations(std::vector &layers, const Params ¶ms); -void raise_alerts_for_issues(const SupportPoints &support_points, - PartialObjects &partial_objects, - std::function alert_fn); + +// NOTE: the boolean marks if the issue is critical or not for now. +std::vector> gather_issues(const SupportPoints &support_points, + PartialObjects &partial_objects); }} // namespace Slic3r::SupportSpotsGenerator diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index fee9b3d27..58134a0a3 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -4271,10 +4271,10 @@ void Plater::priv::on_slicing_update(SlicingStatusEvent &evt) this->preview->reload_print(); } - if ((evt.status.flags & PrintBase::SlicingStatus::UPDATE_PRINT_OBJECT_STEP_WARNINGS) && - static_cast(evt.status.warning_step) == posAlertWhenSupportsNeeded && + if ((evt.status.flags & PrintBase::SlicingStatus::UPDATE_PRINT_STEP_WARNINGS) && + static_cast(evt.status.warning_step) == psAlertWhenSupportsNeeded && get_app_config()->get("alert_when_supports_needed") != "1") { - // This alerts are from posAlertWhenSupportsNeeded and the respective app settings is not Enabled, so discard the alerts. + // This alerts are from psAlertWhenSupportsNeeded and the respective app settings is not Enabled, so discard the alerts. } else if (evt.status.flags & (PrintBase::SlicingStatus::UPDATE_PRINT_STEP_WARNINGS | PrintBase::SlicingStatus::UPDATE_PRINT_OBJECT_STEP_WARNINGS)) { // Update notification center with warnings of object_id and its warning_step.