ENH: improve cantilever detection's speed and accuracy

Cantilever detection should be done layer by layer, instead of using
merged_poly of clusters.

Jira: STUDIO-2198

Change-Id: Ifea017fc4c13425a944ee80f7dedb603a8e14cc2
(cherry picked from commit 0803a439a458ffeb9c6d91e90344a5b78794a6bd)
This commit is contained in:
Arthur 2023-04-03 20:49:25 +08:00 committed by Lane.Wei
parent 74e1ba7963
commit 3d710609d9
4 changed files with 94 additions and 117 deletions

View file

@ -2657,7 +2657,7 @@ SupportNecessaryType PrintObject::is_support_necessary()
this->clear_support_layers();
if (tree_support.has_sharp_tails)
return SharpTail;
else if (tree_support.has_cantilever && tree_support.max_cantilevel_dist > cantilevel_dist_thresh)
else if (tree_support.has_cantilever && tree_support.max_cantilever_dist > cantilevel_dist_thresh)
return Cantilever;
#endif
return NoNeedSupp;

View file

@ -1680,7 +1680,30 @@ static inline ExPolygons detect_overhangs(
} // for each layer.region
}
return union_ex(overhang_polygons);
ExPolygons overhang_areas = union_ex(overhang_polygons);
// check cantilever
if (layer.lower_layer) {
for (ExPolygon& poly : overhang_areas) {
float fw = float(layer.regions().front()->flow(frExternalPerimeter).scaled_width());
auto cluster_boundary_ex = intersection_ex(poly, offset_ex(layer.lower_layer->lslices, scale_(0.5)));
Polygons cluster_boundary = to_polygons(cluster_boundary_ex);
if (cluster_boundary.empty()) continue;
double dist_max = 0;
for (auto& pt : poly.contour.points) {
double dist_pt = std::numeric_limits<double>::max();
for (auto& ply : cluster_boundary) {
double d = ply.distance_to(pt);
dist_pt = std::min(dist_pt, d);
}
dist_max = std::max(dist_max, dist_pt);
}
if (dist_max > scale_(3)) { // is cantilever if the farmost point is larger than 3mm away from base
layer.cantilevers.emplace_back(poly);
}
}
}
return overhang_areas;
}
// Tuple: overhang_polygons, contact_polygons, enforcer_polygons, no_interface_offset
@ -2154,19 +2177,21 @@ struct OverhangCluster {
}
};
static void add_overhang(std::vector<OverhangCluster>& clusters, ExPolygon* overhang, int layer_nr, coordf_t offset_scaled) {
static OverhangCluster* add_overhang(std::vector<OverhangCluster>& clusters, ExPolygon* overhang, int layer_nr, coordf_t offset_scaled) {
OverhangCluster* cluster = nullptr;
bool found = false;
for (int i = 0; i < clusters.size(); i++) {
auto& cluster = clusters[i];
if (cluster.intersects(*overhang, layer_nr)) {
cluster.insert(overhang, layer_nr);
found = true;
auto cluster_i = &clusters[i];
if (cluster_i->intersects(*overhang, layer_nr)) {
cluster_i->insert(overhang, layer_nr);
cluster = cluster_i;
break;
}
}
if (!found) {
clusters.emplace_back(overhang, layer_nr, offset_scaled);
if (!cluster) {
cluster = &clusters.emplace_back(overhang, layer_nr, offset_scaled);
}
return cluster;
};
// Generate top contact layers supporting overhangs.
@ -2328,13 +2353,15 @@ PrintObjectSupportMaterial::MyLayersPtr PrintObjectSupportMaterial::top_contact_
std::set<ExPolygon*> removed_overhang;
for (size_t layer_id = layer_id_start; layer_id < num_layers; layer_id++) {
const Layer* layer = object.get_layer(layer_id);
for (auto& overhang : overhangs_per_layers[layer_id]) {
add_overhang(clusters, &overhang, layer_id, fw_scaled);
OverhangCluster* cluster = add_overhang(clusters, &overhang, layer_id, fw_scaled);
if (overlaps({ overhang }, layer->cantilevers))
cluster->is_cantilever = true;
}
}
for (OverhangCluster& cluster : clusters) {
// 3. check whether the small overhang is sharp tail
cluster.is_sharp_tail = false;
for (size_t layer_id = cluster.min_layer; layer_id <= cluster.max_layer; layer_id++) {
@ -2344,33 +2371,6 @@ PrintObjectSupportMaterial::MyLayersPtr PrintObjectSupportMaterial::top_contact_
break;
}
}
if (cluster.is_sharp_tail)
continue;
if (!cluster.is_sharp_tail) {
// 4. check whether the overhang cluster is cantilever (far awary from main body)
const Layer* layer = object.get_layer(cluster.min_layer);
if (layer->lower_layer == NULL) continue;
Layer* lower_layer = layer->lower_layer;
auto cluster_boundary = intersection(cluster.merged_overhangs_dilated, offset(lower_layer->lslices, scale_(0.5)));
if (cluster_boundary.empty()) continue;
double dist_max = 0;
Points cluster_pts;
for (auto& poly : cluster.merged_overhangs_dilated)
append(cluster_pts, poly.contour.points);
for (auto& pt : cluster_pts) {
double dist_pt = std::numeric_limits<double>::max();
for (auto& poly : cluster_boundary) {
double d = poly.distance_to(pt);
dist_pt = std::min(dist_pt, d);
}
dist_max = std::max(dist_max, dist_pt);
}
if (dist_max > scale_(3)) {
cluster.is_cantilever = true;
continue;
}
}
if (!cluster.is_sharp_tail && !cluster.is_cantilever) {
// 2. check overhang cluster size is small

View file

@ -824,22 +824,25 @@ void TreeSupport::detect_overhangs(bool detect_first_sharp_tail_only)
};
std::vector<OverhangCluster> overhangClusters;
auto find_and_insert_cluster = [](auto& regionClusters, const ExPolygon& region, int layer_nr, coordf_t offset) {
bool found = false;
for (int i = 0; i < regionClusters.size();i++) {
auto& cluster = regionClusters[i];
if (cluster.push_back_if_intersects(region, layer_nr, offset)) {
found = true;
auto find_and_insert_cluster = [](auto &regionClusters, const ExPolygon &region, int layer_nr, coordf_t offset) {
OverhangCluster *cluster = nullptr;
for (int i = 0; i < regionClusters.size(); i++) {
auto cluster_i = &regionClusters[i];
if (cluster_i->push_back_if_intersects(region, layer_nr, offset)) {
cluster = cluster_i;
break;
}
}
if (!found) {
regionClusters.emplace_back(&region, layer_nr);
if (!cluster) {
cluster = &regionClusters.emplace_back(&region, layer_nr);
}
return cluster;
};
if (!is_tree(stype)) return;
max_cantilever_dist = 0;
// main part of overhang detection can be parallel
tbb::parallel_for(tbb::blocked_range<size_t>(0, m_object->layer_count()),
[&](const tbb::blocked_range<size_t>& range) {
@ -912,26 +915,43 @@ void TreeSupport::detect_overhangs(bool detect_first_sharp_tail_only)
if (!overhang.empty())
has_sharp_tails = true;
#ifdef SUPPORT_TREE_DEBUG_TO_SVG
SVG svg(get_svg_filename(std::to_string(layer->print_z), "sharp_tail"), m_object->bounding_box());
if (svg.is_opened()) {
svg.draw(overhang, "yellow");
svg.draw(lower_layer->lslices, "red");
}
#endif
}
}
}
SupportLayer* ts_layer = m_object->get_support_layer(layer_nr + m_raft_layers);
for (ExPolygon& poly : overhang_areas) {
if (!offset_ex(poly, -0.1 * extrusion_width_scaled).empty())
ts_layer->overhang_areas.emplace_back(poly);
if (offset_ex(poly, -0.1 * extrusion_width_scaled).empty()) continue;
ts_layer->overhang_areas.emplace_back(poly);
// check cantilever
{
auto cluster_boundary_ex = intersection_ex(poly, offset_ex(lower_layer->lslices, scale_(0.5)));
Polygons cluster_boundary = to_polygons(cluster_boundary_ex);
if (cluster_boundary.empty()) continue;
double dist_max = 0;
for (auto& pt : poly.contour.points) {
double dist_pt = std::numeric_limits<double>::max();
for (auto& ply : cluster_boundary) {
double d = ply.distance_to(pt);
dist_pt = std::min(dist_pt, d);
}
dist_max = std::max(dist_max, dist_pt);
}
if (dist_max > scale_(3)) { // is cantilever if the farmost point is larger than 3mm away from base
max_cantilever_dist = std::max(max_cantilever_dist, dist_max);
layer->cantilevers.emplace_back(poly);
BOOST_LOG_TRIVIAL(debug) << "found a cantilever cluster. layer_nr=" << layer_nr << dist_max;
has_cantilever = true;
}
}
}
}
}
); // end tbb::parallel_for
BOOST_LOG_TRIVIAL(info) << "max_cantilever_dist=" << max_cantilever_dist;
// check if the sharp tails should be extended higher
if (is_auto(stype) && g_config_support_sharp_tails && !detect_first_sharp_tail_only) {
for (size_t layer_nr = 0; layer_nr < m_object->layer_count(); layer_nr++) {
@ -1026,8 +1046,11 @@ void TreeSupport::detect_overhangs(bool detect_first_sharp_tail_only)
if (m_object->print()->canceled())
break;
SupportLayer* ts_layer = m_object->get_support_layer(layer_nr + m_raft_layers);
Layer* layer = m_object->get_layer(layer_nr);
for (auto& overhang : ts_layer->overhang_areas) {
find_and_insert_cluster(overhangClusters, overhang, layer_nr, extrusion_width_scaled);
OverhangCluster* cluster = find_and_insert_cluster(overhangClusters, overhang, layer_nr, extrusion_width_scaled);
if (overlaps({ overhang },layer->cantilevers))
cluster->is_cantilever = true;
}
}
@ -1035,57 +1058,19 @@ void TreeSupport::detect_overhangs(bool detect_first_sharp_tail_only)
auto blockers = m_object->slice_support_blockers();
m_object->project_and_append_custom_facets(false, EnforcerBlockerType::ENFORCER, enforcers);
m_object->project_and_append_custom_facets(false, EnforcerBlockerType::BLOCKER, blockers);
// check whether the overhang cluster is sharp tail or cantilever
max_cantilevel_dist = 0;
for (auto& cluster : overhangClusters) {
// 3. check whether the small overhang is sharp tail
cluster.is_sharp_tail = false;
for (size_t layer_id = cluster.min_layer; layer_id <= cluster.max_layer; layer_id++) {
Layer* layer = m_object->get_layer(layer_id);
if (overlaps(layer->sharp_tails, cluster.merged_poly)) {
cluster.is_sharp_tail = true;
break;
}
}
if (cluster.is_sharp_tail) continue;
// check whether the overhang cluster is cantilever (far awary from main body)
Layer* layer = m_object->get_layer(cluster.min_layer);
if (layer->lower_layer == NULL) continue;
Layer* lower_layer = layer->lower_layer;
auto cluster_boundary = intersection(cluster.merged_poly, offset(lower_layer->lslices, scale_(0.5)));
if (cluster_boundary.empty()) continue;
double dist_max = 0;
Points cluster_pts;
for (auto& poly : cluster.merged_poly)
append(cluster_pts, poly.contour.points);
for (auto& pt : cluster_pts) {
double dist_pt = std::numeric_limits<double>::max();
for (auto& poly : cluster_boundary) {
double d = poly.distance_to(pt);
dist_pt = std::min(dist_pt, d);
}
dist_max = std::max(dist_max, dist_pt);
}
if (dist_max > scale_(3)) { // this cluster is cantilever if the farmost point is larger than 3mm away from base
for (auto it = cluster.layer_overhangs.begin(); it != cluster.layer_overhangs.end(); it++) {
int layer_nr = it->first;
auto p_overhang = it->second;
m_object->get_layer(layer_nr)->cantilevers.emplace_back(*p_overhang);
}
max_cantilevel_dist = std::max(max_cantilevel_dist, dist_max);
cluster.is_cantilever = true;
}
}
BOOST_LOG_TRIVIAL(info) << "max_cantilevel_dist=" << max_cantilevel_dist;
if (is_auto(stype) && g_config_remove_small_overhangs) {
if (blockers.size() < m_object->layer_count())
blockers.resize(m_object->layer_count());
for (auto& cluster : overhangClusters) {
// 4. check whether the overhang cluster is cantilever or sharp tail
if (cluster.is_cantilever || cluster.is_sharp_tail) continue;
// 3. check whether the small overhang is sharp tail
cluster.is_sharp_tail = false;
for (size_t layer_id = cluster.min_layer; layer_id <= cluster.max_layer; layer_id++) {
Layer* layer = m_object->get_layer(layer_id);
if (overlaps(layer->sharp_tails, cluster.merged_poly)) {
cluster.is_sharp_tail = true;
break;
}
}
if (!cluster.is_sharp_tail && !cluster.is_cantilever) {
// 2. check overhang cluster size is smaller than 3.0 * fw_scaled
@ -1111,15 +1096,6 @@ void TreeSupport::detect_overhangs(bool detect_first_sharp_tail_only)
int layer_nr = it->first;
auto p_overhang = it->second;
blockers[layer_nr].push_back(p_overhang->contour);
// auto dilate1 = offset_ex(*p_overhang, extrusion_width_scaled);
// auto erode1 = offset_ex(*p_overhang, -extrusion_width_scaled);
// Layer* layer = m_object->get_layer(layer_nr);
// auto inter_with_others = intersection_ex(dilate1, diff_ex(layer->lslices, *p_overhang));
//// the following cases are small overhangs:
//// 1) overhang is single line (erode1.empty()==true)
//// 2) overhang is not island (intersects with others)
// if (erode1.empty() && !inter_with_others.empty())
// blockers[layer_nr].push_back(p_overhang->contour);
}
}
}
@ -1133,6 +1109,7 @@ void TreeSupport::detect_overhangs(bool detect_first_sharp_tail_only)
auto layer = m_object->get_layer(layer_nr);
auto lower_layer = layer->lower_layer;
if (support_critical_regions_only) {
ts_layer->overhang_areas.clear();
if (lower_layer == nullptr)
ts_layer->overhang_areas = layer->sharp_tails;
else
@ -1177,7 +1154,7 @@ void TreeSupport::detect_overhangs(bool detect_first_sharp_tail_only)
if (layer->overhang_areas.empty())
continue;
SVG svg(get_svg_filename(std::to_string(layer->print_z), "overhang_areas"), m_object->bounding_box());
SVG svg(format("SVG/overhang_areas_%s.svg", layer->print_z), m_object->bounding_box());
if (svg.is_opened()) {
svg.draw_outline(m_object->get_layer(layer->id())->lslices, "yellow");
svg.draw(layer->overhang_areas, "red");

View file

@ -381,7 +381,7 @@ public:
bool has_overhangs = false;
bool has_sharp_tails = false;
bool has_cantilever = false;
double max_cantilevel_dist = 0;
double max_cantilever_dist = 0;
SupportType support_type;
SupportMaterialStyle support_style;