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
This commit is contained in:
Pavel Mikus 2023-01-29 20:08:47 +01:00 committed by Pavel Mikuš
parent 7164ed18a6
commit 66a4b5b82a
7 changed files with 149 additions and 95 deletions

View File

@ -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<std::pair<const PrintObject *, std::vector<std::pair<SupportSpotsGenerator::SupportPointCause, bool>>>> objects_isssues;
for (const PrintObject *object : m_objects) {
std::unordered_set<const ModelObject *> 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::pair<SupportSpotsGenerator::SupportPointCause, bool>, std::vector<const PrintObject *>> 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
{

View File

@ -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;

View File

@ -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;

View File

@ -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.

View File

@ -330,14 +330,14 @@ std::vector<ExtrusionLine> 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 &params)
#endif
}
void raise_alerts_for_issues(const SupportPoints &support_points,
PartialObjects &partial_objects,
std::function<void(PrintStateBase::WarningLevel, SupportPointCause)> alert_fn)
std::vector<std::pair<SupportPointCause, bool>> gather_issues(const SupportPoints &support_points, PartialObjects &partial_objects)
{
std::vector<std::pair<SupportPointCause, bool>> 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;
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;
}
}
if (!unstable_floating_part_added) {
for (const SupportPoint &sp : support_points) {
if (sp.cause == SupportPointCause::UnstableFloatingPart) {
alert_fn(PrintStateBase::WarningLevel::CRITICAL, SupportPointCause::UnstableFloatingPart);
return;
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

View File

@ -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<SupportPoints, PartialObjects> full_search(const PrintObject *po, con
void estimate_supports_malformations(std::vector<SupportLayer *> &layers, float supports_flow_width, const Params &params);
void estimate_malformations(std::vector<Layer *> &layers, const Params &params);
void raise_alerts_for_issues(const SupportPoints &support_points,
PartialObjects &partial_objects,
std::function<void(PrintStateBase::WarningLevel, SupportPointCause)> alert_fn);
// NOTE: the boolean marks if the issue is critical or not for now.
std::vector<std::pair<SupportPointCause, bool>> gather_issues(const SupportPoints &support_points,
PartialObjects &partial_objects);
}} // namespace Slic3r::SupportSpotsGenerator

View File

@ -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<PrintObjectStep>(evt.status.warning_step) == posAlertWhenSupportsNeeded &&
if ((evt.status.flags & PrintBase::SlicingStatus::UPDATE_PRINT_STEP_WARNINGS) &&
static_cast<PrintStep>(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.