ENH: boost is_support_necessary's performance

1. Parallelize the majority of overhang detection, leaving only a small
part of sharp tail detection as sequential. This strategy makes
is_support_necessary 10 times faster.
2. Use the overlaps function to detect overlapping, instead of using
intersection().empty()
3. Control the max recursion depth to prevent crashing due to too deep
recursion.

Jira: STUDIO-2445, STUDIO-2458
Change-Id: I35283da3e4a22d7afe251b804ce30b90a9d754df
(cherry picked from commit 1a6fedd1a0c82906f1807234ea1b816247ca6fd7)
This commit is contained in:
arthur.tang 2023-03-14 17:04:42 +08:00 committed by Lane.Wei
parent 6f298ac6f1
commit 6c489808a7
5 changed files with 280 additions and 325 deletions

View File

@ -174,6 +174,17 @@ bool ExPolygon::overlaps(const ExPolygon &other) const
other.contains(this->contour.points.front()); other.contains(this->contour.points.front());
} }
bool overlaps(const ExPolygons& expolys1, const ExPolygons& expolys2)
{
for (const ExPolygon& expoly1 : expolys1) {
for (const ExPolygon& expoly2 : expolys2) {
if (expoly1.overlaps(expoly2))
return true;
}
}
return false;
}
void ExPolygon::simplify_p(double tolerance, Polygons* polygons) const void ExPolygon::simplify_p(double tolerance, Polygons* polygons) const
{ {
Polygons pp = this->simplify_p(tolerance); Polygons pp = this->simplify_p(tolerance);

View File

@ -451,6 +451,8 @@ inline ExPolygons expolygons_simplify(const ExPolygons &expolys, double toleranc
// however their contours may be rotated. // however their contours may be rotated.
bool expolygons_match(const ExPolygon &l, const ExPolygon &r); bool expolygons_match(const ExPolygon &l, const ExPolygon &r);
bool overlaps(const ExPolygons& expolys1, const ExPolygons& expolys2);
BoundingBox get_extents(const ExPolygon &expolygon); BoundingBox get_extents(const ExPolygon &expolygon);
BoundingBox get_extents(const ExPolygons &expolygons); BoundingBox get_extents(const ExPolygons &expolygons);
BoundingBox get_extents_rotated(const ExPolygon &poly, double angle); BoundingBox get_extents_rotated(const ExPolygon &poly, double angle);

View File

@ -460,9 +460,20 @@ void PrintObject::generate_support_material()
m_print->throw_if_canceled(); m_print->throw_if_canceled();
} else { } else {
// BBS: pop a warning if objects have significant amount of overhangs but support material is not enabled // BBS: pop a warning if objects have significant amount of overhangs but support material is not enabled
m_print->set_status(50, L("Checking support necessity"));
typedef std::chrono::high_resolution_clock clock_;
typedef std::chrono::duration<double, std::ratio<1> > second_;
std::chrono::time_point<clock_> t0{ clock_::now() };
SupportNecessaryType sntype = this->is_support_necessary(); SupportNecessaryType sntype = this->is_support_necessary();
double duration{ std::chrono::duration_cast<second_>(clock_::now() - t0).count() };
BOOST_LOG_TRIVIAL(info) << std::fixed << std::setprecision(0)
<< "is_support_necessary took " << (duration / 60) << " minutes and "
<< std::setprecision(3)
<< std::fmod(duration, 60.0) << " seconds." << std::endl;
if (sntype != NoNeedSupp) { if (sntype != NoNeedSupp) {
m_print->set_status(50, L("Checking support necessity"));
if (sntype == SharpTail) { if (sntype == SharpTail) {
std::string warning_message = format(L("It seems object %s has completely floating regions. Please re-orient the object or enable support generation."), std::string warning_message = format(L("It seems object %s has completely floating regions. Please re-orient the object or enable support generation."),
this->model_object()->name); this->model_object()->name);
@ -2655,7 +2666,7 @@ SupportNecessaryType PrintObject::is_support_necessary()
#else #else
TreeSupport tree_support(*this, m_slicing_params); TreeSupport tree_support(*this, m_slicing_params);
tree_support.support_type = SupportType::stTreeAuto; // need to set support type to fully utilize the power of feature detection tree_support.support_type = SupportType::stTreeAuto; // need to set support type to fully utilize the power of feature detection
tree_support.detect_overhangs(); tree_support.detect_overhangs(true);
this->clear_support_layers(); this->clear_support_layers();
if (tree_support.has_sharp_tails) if (tree_support.has_sharp_tails)
return SharpTail; return SharpTail;

View File

@ -12,8 +12,6 @@
#include "I18N.hpp" #include "I18N.hpp"
#include <libnest2d/backends/libslic3r/geometries.hpp> #include <libnest2d/backends/libslic3r/geometries.hpp>
#include "Fill/FillBase.hpp"
#define _L(s) Slic3r::I18N::translate(s) #define _L(s) Slic3r::I18N::translate(s)
#define USE_PLAN_LAYER_HEIGHTS 1 #define USE_PLAN_LAYER_HEIGHTS 1
@ -722,7 +720,7 @@ TreeSupport::TreeSupport(PrintObject& object, const SlicingParameters &slicing_p
#define SUPPORT_SURFACES_OFFSET_PARAMETERS ClipperLib::jtSquare, 0. #define SUPPORT_SURFACES_OFFSET_PARAMETERS ClipperLib::jtSquare, 0.
void TreeSupport::detect_overhangs() void TreeSupport::detect_overhangs(bool detect_first_sharp_tail_only)
{ {
// overhangs are already detected // overhangs are already detected
if (m_object->support_layer_count() >= m_object->layer_count()) if (m_object->support_layer_count() >= m_object->layer_count())
@ -731,10 +729,8 @@ void TreeSupport::detect_overhangs()
// Clear and create Tree Support Layers // Clear and create Tree Support Layers
m_object->clear_support_layers(); m_object->clear_support_layers();
m_object->clear_tree_support_preview_cache(); m_object->clear_tree_support_preview_cache();
create_tree_support_layers(); create_tree_support_layers();
const PrintObjectConfig& config = m_object->config(); const PrintObjectConfig& config = m_object->config();
SupportType stype = support_type; SupportType stype = support_type;
const coordf_t radius_sample_resolution = g_config_tree_support_collision_resolution; const coordf_t radius_sample_resolution = g_config_tree_support_collision_resolution;
@ -750,16 +746,13 @@ void TreeSupport::detect_overhangs()
// a region is considered well supported if the number of layers below it exceeds this threshold // a region is considered well supported if the number of layers below it exceeds this threshold
const int thresh_layers_below = 10 / config.layer_height; const int thresh_layers_below = 10 / config.layer_height;
double obj_height = m_object->size().z(); double obj_height = m_object->size().z();
double threshold_rad = (config.support_threshold_angle.value < EPSILON ? 30 : config.support_threshold_angle.value + 1) * M_PI / 180.;
struct ExPolygonComp {
bool operator()(const ExPolygon& a, const ExPolygon& b) const {
return &a < &b;
}
};
// for small overhang removal // for small overhang removal
struct OverhangCluster { struct OverhangCluster {
std::map<int, const ExPolygon*> layer_overhangs; std::map<int, const ExPolygon*> layer_overhangs;
ExPolygons merged_poly; ExPolygons merged_poly;
BoundingBox merged_bbox;
int min_layer = 1e7; int min_layer = 1e7;
int max_layer = 0; int max_layer = 0;
coordf_t offset = 0; coordf_t offset = 0;
@ -774,6 +767,7 @@ void TreeSupport::detect_overhangs()
merged_poly = union_ex(merged_poly, dilate1); merged_poly = union_ex(merged_poly, dilate1);
min_layer = std::min(min_layer, layer_nr); min_layer = std::min(min_layer, layer_nr);
max_layer = std::max(max_layer, layer_nr); max_layer = std::max(max_layer, layer_nr);
merged_bbox.merge(get_extents(dilate1));
} }
int height() { int height() {
return max_layer - min_layer + 1; return max_layer - min_layer + 1;
@ -786,283 +780,259 @@ void TreeSupport::detect_overhangs()
this->offset = offset; this->offset = offset;
auto dilate1 = offset_ex(region, offset); auto dilate1 = offset_ex(region, offset);
return !intersection_ex(*overhang, dilate1).empty(); BoundingBox bbox = get_extents(dilate1);
if (!merged_bbox.overlap(bbox))
return false;
return overlaps({ *overhang }, dilate1);
}
// it's basically the combination of push_back and intersects, but saves an offset_ex
bool push_back_if_intersects(const ExPolygon& region, int layer_nr, coordf_t offset) {
bool is_intersect = false;
ExPolygons dilate1;
BoundingBox bbox;
do {
if (layer_nr < 1) break;
auto it = layer_overhangs.find(layer_nr - 1);
if (it == layer_overhangs.end()) break;
const ExPolygon* overhang = it->second;
this->offset = offset;
dilate1 = offset_ex(region, offset);
if (dilate1.empty()) break;
bbox = get_extents(dilate1);
if (!merged_bbox.overlap(bbox))
break;
is_intersect = overlaps({ *overhang }, dilate1);
} while (0);
if (is_intersect) {
layer_overhangs.emplace(layer_nr, &region);
merged_poly = union_ex(merged_poly, dilate1);
min_layer = std::min(min_layer, layer_nr);
max_layer = std::max(max_layer, layer_nr);
merged_bbox.merge(bbox);
}
return is_intersect;
} }
}; };
std::vector<OverhangCluster> overhangClusters; std::vector<OverhangCluster> overhangClusters;
std::map<const ExPolygon*, int> overhang2clusterInd;
// for sharp tail detection auto find_and_insert_cluster = [](auto& regionClusters, const ExPolygon& region, int layer_nr, coordf_t offset) {
struct RegionCluster {
std::map<int, const ExPolygon*> layer_regions;
ExPolygon base;
BoundingBox bbox;
int min_layer = 1e7;
int max_layer = 0;
RegionCluster(const ExPolygon* expoly, int layer_nr) {
push_back(expoly, layer_nr);
}
void push_back(const ExPolygon* expoly, int layer_nr) {
if (layer_regions.empty()) {
base = *expoly;
bbox = get_extents(base);
}
layer_regions.emplace(layer_nr, expoly);
bbox.merge(get_extents(*expoly));
min_layer = std::min(min_layer, layer_nr);
max_layer = std::max(max_layer, layer_nr);
}
int height() {
return max_layer - min_layer + 1;
}
bool intersects(const ExPolygon& region, int layer_nr, coordf_t offset) {
if (layer_nr < 1) return false;
auto it = layer_regions.find(layer_nr - 1);
if (it == layer_regions.end()) return false;
const ExPolygon* region_lower = it->second;
return !intersection_ex(*region_lower, region).empty();
}
};
std::vector<RegionCluster> regionClusters;
std::map<const ExPolygon*, int> region2clusterInd;
auto find_and_insert_cluster = [](auto& regionClusters, auto& region2clusterInd, const ExPolygon& region, int layer_nr, coordf_t offset) {
bool found = false; bool found = false;
for (int i = 0; i < regionClusters.size();i++) { for (int i = 0; i < regionClusters.size();i++) {
auto& cluster = regionClusters[i]; auto& cluster = regionClusters[i];
if (cluster.intersects(region, layer_nr, offset)) { if (cluster.push_back_if_intersects(region, layer_nr, offset)) {
cluster.push_back(&region, layer_nr);
region2clusterInd.emplace(&region, i);
found = true; found = true;
break; break;
} }
} }
if (!found) { if (!found) {
regionClusters.emplace_back(&region, layer_nr); regionClusters.emplace_back(&region, layer_nr);
region2clusterInd.emplace(&region, regionClusters.size() - 1);
} }
}; };
// main part of sharptail detections
if (is_tree(stype))
{
double threshold_rad = (config.support_threshold_angle.value < EPSILON ? 30 : config.support_threshold_angle.value+1) * M_PI / 180.;
ExPolygons regions_well_supported;
std::map<ExPolygon, int, ExPolygonComp> region_layers_below;
ExPolygons lower_overhang_dilated;
for (size_t layer_nr = 0; layer_nr < m_object->layer_count(); layer_nr++){
if (m_object->print()->canceled())
break;
if (!is_auto(stype) && layer_nr > enforce_support_layers)
continue;
Layer* layer = m_object->get_layer(layer_nr); if (!is_tree(stype)) return;
for (auto& slice : layer->lslices)
find_and_insert_cluster(regionClusters, region2clusterInd, slice, layer_nr, 0);
if (layer->lower_layer == nullptr) { // main part of overhang detection can be parallel
for (auto& slice : layer->lslices) { tbb::parallel_for(tbb::blocked_range<size_t>(0, m_object->layer_count()),
auto bbox_size = get_extents(slice).size(); [&](const tbb::blocked_range<size_t>& range) {
if (/*slice.area() > area_thresh_well_supported || */ for (size_t layer_nr = range.begin(); layer_nr < range.end(); layer_nr++) {
(bbox_size.x()>length_thresh_well_supported && bbox_size.y()>length_thresh_well_supported)) if (m_object->print()->canceled())
regions_well_supported.emplace_back(slice); break;
else if(g_config_support_sharp_tails){
layer->sharp_tails.push_back(slice); if (!is_auto(stype) && layer_nr > enforce_support_layers)
layer->sharp_tails_height.insert({ &slice, layer->height }); continue;
Layer* layer = m_object->get_layer(layer_nr);
if (layer->lower_layer == nullptr) {
for (auto& slice : layer->lslices) {
auto bbox_size = get_extents(slice).size();
if (!((bbox_size.x() > length_thresh_well_supported && bbox_size.y() > length_thresh_well_supported))
&& g_config_support_sharp_tails) {
layer->sharp_tails.push_back(slice);
layer->sharp_tails_height.insert({ &slice, layer->height });
}
}
continue;
}
Layer* lower_layer = layer->lower_layer;
coordf_t lower_layer_offset = layer_nr < enforce_support_layers ? -0.15 * extrusion_width : (float)lower_layer->height / tan(threshold_rad);
coordf_t support_offset_scaled = scale_(lower_layer_offset);
// Filter out areas whose diameter that is smaller than extrusion_width. Do not use offset2() for this purpose!
ExPolygons lower_polys;
for (const ExPolygon& expoly : lower_layer->lslices) {
if (!offset_ex(expoly, -extrusion_width_scaled / 2).empty()) {
lower_polys.emplace_back(expoly);
}
}
ExPolygons curr_polys;
for (const ExPolygon& expoly : layer->lslices) {
if (!offset_ex(expoly, -extrusion_width_scaled / 2).empty()) {
curr_polys.emplace_back(expoly);
} }
} }
continue;
}
// normal overhang
ExPolygons lower_layer_offseted = offset_ex(lower_polys, support_offset_scaled, SUPPORT_SURFACES_OFFSET_PARAMETERS);
ExPolygons overhang_areas = std::move(diff_ex(curr_polys, lower_layer_offseted));
overhang_areas.erase(std::remove_if(overhang_areas.begin(), overhang_areas.end(),
[extrusion_width_scaled](ExPolygon& area) { return offset_ex(area, -0.1 * extrusion_width_scaled).empty(); }),
overhang_areas.end());
ExPolygons overhangs_sharp_tail;
if (is_auto(stype) && g_config_support_sharp_tails)
{
// BBS detect sharp tail
const ExPolygons& lower_layer_sharptails = lower_layer->sharp_tails;
auto& lower_layer_sharptails_height = lower_layer->sharp_tails_height;
for (ExPolygon& expoly : layer->lslices) {
bool is_sharp_tail = false;
// 1. nothing below
// this is a sharp tail region if it's small but non-ignorable
if (!overlaps({ expoly }, lower_polys)) {
is_sharp_tail = expoly.area() < area_thresh_well_supported && !offset_ex(expoly, -0.5 * extrusion_width_scaled).empty();
}
if (is_sharp_tail) {
ExPolygons overhang = diff_ex({ expoly }, lower_layer->lslices);
layer->sharp_tails.push_back(expoly);
layer->sharp_tails_height.insert({ &expoly, layer->height });
append(overhang_areas, overhang);
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");
#endif
}
}
}
if (max_bridge_length > 0 && overhang_areas.size() > 0) {
// do not break bridge for normal part in TreeHybrid
bool break_bridge = !(config.support_style == smsTreeHybrid && area(overhang_areas) > m_support_params.thresh_big_overhang);
m_object->remove_bridges_from_contacts(lower_layer, layer, extrusion_width_scaled, &overhang_areas, max_bridge_length, break_bridge);
}
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);
}
}
}
); // end tbb::parallel_for
// 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++) {
if (m_object->print()->canceled())
break;
Layer* layer = m_object->get_layer(layer_nr);
SupportLayer* ts_layer = m_object->get_support_layer(layer_nr + m_raft_layers);
Layer* lower_layer = layer->lower_layer; Layer* lower_layer = layer->lower_layer;
coordf_t lower_layer_offset = layer_nr < enforce_support_layers ? -0.15 * extrusion_width : (float)lower_layer->height / tan(threshold_rad); // skip if:
coordf_t support_offset_scaled = scale_(lower_layer_offset); // 1) if the current layer is already detected as sharp tails
// Filter out areas whose diameter that is smaller than extrusion_width. Do not use offset2() for this purpose! // 2) lower layer has no sharp tails
if (!lower_layer || layer->sharp_tails.empty() == false || lower_layer->sharp_tails.empty() == true)
continue;
ExPolygons lower_polys; ExPolygons lower_polys;
for (const ExPolygon& expoly : lower_layer->lslices) { for (const ExPolygon& expoly : lower_layer->lslices) {
if (!offset_ex(expoly, -extrusion_width_scaled / 2).empty()) { if (!offset_ex(expoly, -extrusion_width_scaled / 2).empty()) {
lower_polys.emplace_back(expoly); lower_polys.emplace_back(expoly);
} }
} }
ExPolygons curr_polys;
for (const ExPolygon& expoly : layer->lslices) {
if (!offset_ex(expoly, -extrusion_width_scaled / 2).empty()) {
curr_polys.emplace_back(expoly);
}
}
// normal overhang // BBS detect sharp tail
ExPolygons lower_layer_offseted = offset_ex(lower_polys, support_offset_scaled, SUPPORT_SURFACES_OFFSET_PARAMETERS); const ExPolygons& lower_layer_sharptails = lower_layer->sharp_tails;
ExPolygons overhang_areas = std::move(diff_ex(curr_polys, lower_layer_offseted)); auto& lower_layer_sharptails_height = lower_layer->sharp_tails_height;
for (ExPolygon& expoly : layer->lslices) {
bool is_sharp_tail = false;
float accum_height = layer->height;
do {
// 2. something below
// check whether this is above a sharp tail region.
overhang_areas.erase(std::remove_if(overhang_areas.begin(), overhang_areas.end(), // 2.1 If no sharp tail below, this is considered as common region.
[extrusion_width_scaled](ExPolygon &area) { return offset_ex(area, -0.1 * extrusion_width_scaled).empty(); }), ExPolygons supported_by_lower = intersection_ex({ expoly }, lower_layer_sharptails);
overhang_areas.end()); if (supported_by_lower.empty()) {
is_sharp_tail = false;
break;
}
ExPolygons overhangs_sharp_tail; // 2.2 If sharp tail below, check whether it support this region enough.
if (is_auto(stype) && g_config_support_sharp_tails)
{
#if 0 #if 0
// detect sharp tail and add more supports around // judge by area isn't reliable, failure cases include 45 degree rotated cube
for (auto& lower_region : lower_layer_offseted) { float supported_area = area(supported_by_lower);
auto radius = get_extents(lower_region).radius(); if (supported_area > area_thresh_well_supported) {
auto out_of_well_supported_region = offset_ex(diff_ex({ lower_region }, regions_well_supported), -extrusion_width_scaled); is_sharp_tail = false;
auto bbox_size = get_extents(out_of_well_supported_region).size(); break;
double area_inter = area(intersection_ex({ lower_region }, regions_well_supported));
if ((area_inter==0 ||
((bbox_size.x()> extrusion_width_scaled && bbox_size.y() > extrusion_width_scaled) && area_inter < area_thresh_well_supported ) )
/*&& (obj_height - scale_(layer->slice_z)) > get_extents(lower_region).radius() * 5*/) {
auto lower_region_unoffseted = offset_ex(lower_region, -support_offset_scaled);
if (!lower_region_unoffseted.empty())
lower_region = lower_region_unoffseted.front();
} }
}
if (!lower_layer_offseted.empty()) {
overhangs_sharp_tail = std::move(diff_ex(curr_polys, lower_layer_offseted));
//overhangs_sharp_tail = std::move(offset2_ex(overhangs_sharp_tail, -0.1 * extrusion_width_scaled, 0.1 * extrusion_width_scaled));
overhangs_sharp_tail = diff_ex(overhangs_sharp_tail, overhang_areas);
}
if (!overhangs_sharp_tail.empty()) {
append(layer->sharp_tails, overhangs_sharp_tail);
overhang_areas = union_ex(overhang_areas, overhangs_sharp_tail);
}
#else
// BBS detect sharp tail
const ExPolygons& lower_layer_sharptails = lower_layer->sharp_tails;
auto& lower_layer_sharptails_height = lower_layer->sharp_tails_height;
for (ExPolygon& expoly : layer->lslices) {
bool is_sharp_tail = false;
float accum_height = layer->height;
do {
// 1. nothing below
// this is a sharp tail region if it's small but non-ignorable
if (intersection_ex({expoly}, lower_polys).empty()) {
is_sharp_tail = expoly.area() < area_thresh_well_supported && !offset_ex(expoly,-0.5*extrusion_width_scaled).empty();
break;
}
// 2. something below
// check whether this is above a sharp tail region.
// 2.1 If no sharp tail below, this is considered as common region.
ExPolygons supported_by_lower = intersection_ex({expoly}, lower_layer_sharptails);
if (supported_by_lower.empty()) {
is_sharp_tail = false;
break;
}
// 2.2 If sharp tail below, check whether it support this region enough.
float supported_area = area(supported_by_lower);
BoundingBox bbox = get_extents(supported_by_lower);
#if 0
// judge by area isn't reliable, failure cases include 45 degree rotated cube
if (supported_area > area_thresh_well_supported) {
is_sharp_tail = false;
break;
}
#endif #endif
if (bbox.size().x() > length_thresh_well_supported && bbox.size().y() > length_thresh_well_supported) { BoundingBox bbox = get_extents(supported_by_lower);
is_sharp_tail = false; if (bbox.size().x() > length_thresh_well_supported && bbox.size().y() > length_thresh_well_supported) {
is_sharp_tail = false;
break;
}
// 2.3 check whether sharp tail exceed the max height
for (auto& lower_sharp_tail_height : lower_layer_sharptails_height) {
if (lower_sharp_tail_height.first->overlaps(expoly)) {
accum_height += lower_sharp_tail_height.second;
break; break;
} }
}
if (accum_height >= sharp_tail_max_support_height) {
is_sharp_tail = false;
break;
}
// 2.3 check whether sharp tail exceed the max height // 2.4 if the area grows fast than threshold, it get connected to other part or
for (auto &lower_sharp_tail_height : lower_layer_sharptails_height) { // it has a sharp slop and will be auto supported.
if (!intersection_ex(*lower_sharp_tail_height.first, expoly).empty()) { ExPolygons new_overhang_expolys = diff_ex({ expoly }, lower_layer_sharptails);
accum_height += lower_sharp_tail_height.second; if ((get_extents(new_overhang_expolys).size() - get_extents(lower_layer_sharptails).size()).both_comp(Point(scale_(5), scale_(5)), ">") || !offset_ex(new_overhang_expolys, -5.0 * extrusion_width_scaled).empty()) {
break; is_sharp_tail = false;
} break;
} }
if (accum_height >= sharp_tail_max_support_height) {
is_sharp_tail = false;
break;
}
// 2.4 if the area grows fast than threshold, it get connected to other part or // 2.5 mark the expoly as sharptail
// it has a sharp slop and will be auto supported. is_sharp_tail = true;
ExPolygons new_overhang_expolys = diff_ex({expoly}, lower_layer_sharptails); } while (0);
if ((get_extents(new_overhang_expolys).size()-get_extents(lower_layer_sharptails).size()).both_comp(Point(scale_(5),scale_(5)),">") || !offset_ex(new_overhang_expolys, -5.0 * extrusion_width_scaled).empty()) {
is_sharp_tail = false;
break;
}
// 2.5 mark the expoly as sharptail if (is_sharp_tail) {
is_sharp_tail = true; ExPolygons overhang = diff_ex({ expoly }, lower_layer->lslices);
} while (0); layer->sharp_tails.push_back(expoly);
layer->sharp_tails_height.insert({ &expoly, accum_height });
append(ts_layer->overhang_areas, overhang);
if (is_sharp_tail) { if (!overhang.empty())
ExPolygons overhang = diff_ex({expoly}, lower_layer->lslices); has_sharp_tails = true;
layer->sharp_tails.push_back(expoly);
layer->sharp_tails_height.insert({ &expoly, accum_height });
append(overhang_areas, overhang);
if (!overhang.empty())
has_sharp_tails = true;
#ifdef SUPPORT_TREE_DEBUG_TO_SVG #ifdef SUPPORT_TREE_DEBUG_TO_SVG
SVG svg(get_svg_filename(std::to_string(layer->print_z), "sharp_tail"), m_object->bounding_box()); 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"); if (svg.is_opened()) svg.draw(overhang, "yellow");
#endif #endif
}
}
#endif
}
if (max_bridge_length > 0 && overhang_areas.size()>0) {
// do not break bridge for normal part in TreeHybrid
bool break_bridge = !(config.support_style == smsTreeHybrid && area(overhang_areas) > m_support_params.thresh_big_overhang);
m_object->remove_bridges_from_contacts(lower_layer, layer, extrusion_width_scaled, &overhang_areas, max_bridge_length, break_bridge);
}
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 (is_auto(stype) && g_config_remove_small_overhangs) {
for (auto& overhang : ts_layer->overhang_areas) {
find_and_insert_cluster(overhangClusters, overhang2clusterInd, overhang, layer_nr, extrusion_width_scaled);
}
}
if (is_auto(stype) && /*g_config_support_sharp_tails*/0)
{ // update well supported regions
ExPolygons regions_well_supported2;
// regions intersects with lower regions_well_supported or large support are also well supported
auto inters = intersection_ex(layer->lslices, regions_well_supported);
auto inters2 = intersection_ex(layer->lslices, ts_layer->overhang_areas);
inters.insert(inters.end(), inters2.begin(), inters2.end());
for (auto inter : inters) {
auto bbox_size = get_extents(inter).size();
if (inter.area() >= area_thresh_well_supported
|| (bbox_size.x()>length_thresh_well_supported && bbox_size.y()>length_thresh_well_supported) )
{
auto tmp = offset_ex(inter, support_offset_scaled);
if (!tmp.empty()) {
// if inter is a single line with only 2 valid points, clipper will return empty
regions_well_supported2.emplace_back(std::move(tmp[0]));
}
}
} }
// experimental: regions high enough is also well supported }
for (auto& region : layer->lslices) { }
auto cluster = regionClusters[region2clusterInd[&region]]; }
if (layer_nr - cluster.min_layer > thresh_layers_below)
regions_well_supported2.push_back(region); // group overhang clusters
} for (size_t layer_nr = 0; layer_nr < m_object->layer_count(); layer_nr++) {
if (m_object->print()->canceled())
regions_well_supported = union_ex(regions_well_supported2); break;
#ifdef SUPPORT_TREE_DEBUG_TO_SVG SupportLayer* ts_layer = m_object->get_support_layer(layer_nr + m_raft_layers);
if (!regions_well_supported.empty()) { for (auto& overhang : ts_layer->overhang_areas) {
SVG svg(get_svg_filename(std::to_string(layer->print_z), "regions_well_supported"), m_object->bounding_box()); find_and_insert_cluster(overhangClusters, overhang, layer_nr, extrusion_width_scaled);
if (svg.is_opened())
svg.draw(regions_well_supported, "yellow");
}
#endif
}
} }
} }
@ -1133,7 +1103,7 @@ void TreeSupport::detect_overhangs()
bool is_sharp_tail = false; bool is_sharp_tail = false;
for (size_t layer_id = cluster.min_layer; layer_id <= cluster.max_layer; layer_id++) { for (size_t layer_id = cluster.min_layer; layer_id <= cluster.max_layer; layer_id++) {
Layer* layer = m_object->get_layer(layer_id); Layer* layer = m_object->get_layer(layer_id);
if (!intersection_ex(layer->sharp_tails, cluster.merged_poly).empty()) { if(overlaps(layer->sharp_tails, cluster.merged_poly)) {
is_sharp_tail = true; is_sharp_tail = true;
break; break;
} }
@ -2488,21 +2458,6 @@ void TreeSupport::draw_circles(const std::vector<std::vector<Node*>>& contact_no
char fname[10]; sprintf(fname, "%d_%.2f", layer_nr, ts_layer->print_z); char fname[10]; sprintf(fname, "%d_%.2f", layer_nr, ts_layer->print_z);
draw_contours_and_nodes_to_svg("", base_areas, roof_areas, roof_1st_layer, {}, {}, get_svg_filename(fname, "circles"), {"base", "roof", "roof1st"}); draw_contours_and_nodes_to_svg("", base_areas, roof_areas, roof_1st_layer, {}, {}, get_svg_filename(fname, "circles"), {"base", "roof", "roof1st"});
} }
// export layer & print_z log
std::ofstream draw_circles_layer_out;
draw_circles_layer_out.open("./SVG/layer_heights_draw_circles.txt");
if (draw_circles_layer_out.is_open()) {
for (int layer_nr = m_object->layer_count() - 1; layer_nr > 0; layer_nr--) {
SupportLayer* ts_layer = m_object->get_support_layer(layer_nr + m_raft_layers);
ExPolygons& base_areas = ts_layer->base_areas;
ExPolygons& roof_areas = ts_layer->roof_areas;
ExPolygons& roof_1st_layer = ts_layer->roof_1st_layer;
ExPolygons& floor_areas = ts_layer->floor_areas;
if (base_areas.empty() && roof_areas.empty() && roof_1st_layer.empty()) continue;
draw_circles_layer_out << layer_nr << " " << ts_layer->print_z << " " << ts_layer->height << std::endl;
}
}
#endif // SUPPORT_TREE_DEBUG_TO_SVG #endif // SUPPORT_TREE_DEBUG_TO_SVG
SupportLayerPtrs& ts_layers = m_object->support_layers(); SupportLayerPtrs& ts_layers = m_object->support_layers();
@ -3046,27 +3001,6 @@ void TreeSupport::drop_nodes(std::vector<std::vector<Node*>>& contact_nodes)
delete node; delete node;
} }
to_free_node_set.clear(); to_free_node_set.clear();
// Merge empty contact_nodes layers
#ifdef SUPPORT_TREE_DEBUG_TO_SVG
// export all print_z and layer height into .txt
std::ofstream layer_heights_out;
layer_heights_out.open("./SVG/layer_heights_drop_nodes.txt");
//layer_heights_out.open("layer_heights_out.txt");
if (layer_heights_out.is_open()) {
for (int i = 0; i < layer_heights.size(); i++) {
if (contact_nodes[i].empty()) {
layer_heights_out << 0 << " " << 0 << std::endl;
}
else {
layer_heights_out << contact_nodes[i][0]->print_z << " " << contact_nodes[i][0]->height << std::endl;
}
}
layer_heights_out.close();
}
#endif
} }
void TreeSupport::smooth_nodes(std::vector<std::vector<Node *>> &contact_nodes) void TreeSupport::smooth_nodes(std::vector<std::vector<Node *>> &contact_nodes)
@ -3303,31 +3237,23 @@ std::vector<LayerHeightData> TreeSupport::plan_layer_heights(std::vector<std::ve
} }
} }
for (int i = layer_heights.size() - 1; i >= 0; i--) { // fill in next_layer_nr
if (layer_heights[i].height < EPSILON) continue; int i = layer_heights.size() - 1, j = i;
for (int j = i - 1; j >= 0; j--) { for (; j >= 0; i = j) {
if (layer_heights[i].height < EPSILON) {
j--;
continue;
}
for (j = i - 1; j >= 0; j--) {
if (layer_heights[j].height > EPSILON) { if (layer_heights[j].height > EPSILON) {
layer_heights[i].next_layer_nr = j; layer_heights[i].next_layer_nr = j;
break; break;
} }
} }
BOOST_LOG_TRIVIAL(info) << "plan_layer_heights print_z, height, layer_nr->next_layer_nr: " << layer_heights[i].print_z << " " << layer_heights[i].height << " "
<< i << "->" << layer_heights[i].next_layer_nr << std::endl;
} }
#ifdef SUPPORT_TREE_DEBUG_TO_SVG
// check bounds
if (1)
{
std::ofstream bounds_out;
bounds_out.open("bounds.txt");
if (bounds_out.is_open()) {
for (int i = 0; i < bounds.size(); i++) {
bounds_out << bounds[i] << std::endl;
}
}
}
#endif
for (int i = 0; i < layer_heights.size(); i++) { BOOST_LOG_TRIVIAL(info) << "plan_layer_heights print_z, height: "<< layer_heights[i].print_z << " " << layer_heights[i].height << std::endl; }
return layer_heights; return layer_heights;
} }
@ -3573,11 +3499,11 @@ const ExPolygons& TreeSupportData::get_collision(coordf_t radius, size_t layer_n
return collision; return collision;
} }
const ExPolygons& TreeSupportData::get_avoidance(coordf_t radius, size_t layer_nr) const const ExPolygons& TreeSupportData::get_avoidance(coordf_t radius, size_t layer_nr, int recursions) const
{ {
profiler.tic(); profiler.tic();
radius = ceil_radius(radius); radius = ceil_radius(radius);
RadiusLayerPair key{radius, layer_nr}; RadiusLayerPair key{radius, layer_nr, recursions };
const auto it = m_avoidance_cache.find(key); const auto it = m_avoidance_cache.find(key);
const ExPolygons& avoidance = it != m_avoidance_cache.end() ? it->second : calculate_avoidance(key); const ExPolygons& avoidance = it != m_avoidance_cache.end() ? it->second : calculate_avoidance(key);
@ -3624,22 +3550,20 @@ coordf_t TreeSupportData::ceil_radius(coordf_t radius) const
const ExPolygons& TreeSupportData::calculate_collision(const RadiusLayerPair& key) const const ExPolygons& TreeSupportData::calculate_collision(const RadiusLayerPair& key) const
{ {
const auto& radius = key.first; assert(key.layer_nr < m_layer_outlines.size());
const auto& layer_nr = key.second;
assert(layer_nr < m_layer_outlines.size()); ExPolygons collision_areas = std::move(offset_ex(m_layer_outlines[key.layer_nr], scale_(key.radius)));
ExPolygons collision_areas = std::move(offset_ex(m_layer_outlines[layer_nr], scale_(radius)));
const auto ret = m_collision_cache.insert({ key, std::move(collision_areas) }); const auto ret = m_collision_cache.insert({ key, std::move(collision_areas) });
return ret.first->second; return ret.first->second;
} }
const ExPolygons& TreeSupportData::calculate_avoidance(const RadiusLayerPair& key) const const ExPolygons& TreeSupportData::calculate_avoidance(const RadiusLayerPair& key) const
{ {
const auto& radius = key.first; const auto& radius = key.radius;
const auto& layer_nr = key.second; const auto& layer_nr = key.layer_nr;
std::pair<tbb::concurrent_unordered_map<RadiusLayerPair, ExPolygons, RadiusLayerPairHash>::iterator,bool> ret; std::pair<tbb::concurrent_unordered_map<RadiusLayerPair, ExPolygons, RadiusLayerPairHash>::iterator,bool> ret;
if (/*is_slim*/1) { constexpr auto max_recursion_depth = 100;
if (key.recursions <= max_recursion_depth*2) {
if (layer_nr == 0) { if (layer_nr == 0) {
m_avoidance_cache[key] = get_collision(radius, 0); m_avoidance_cache[key] = get_collision(radius, 0);
return m_avoidance_cache[key]; return m_avoidance_cache[key];
@ -3650,19 +3574,17 @@ const ExPolygons& TreeSupportData::calculate_avoidance(const RadiusLayerPair& ke
// below the current one exists and if not, forcing the calculation of that layer. This may cause another recursion // below the current one exists and if not, forcing the calculation of that layer. This may cause another recursion
// if the layer at 2N below the current one but we won't exceed our limit unless there are N*N uncalculated layers // if the layer at 2N below the current one but we won't exceed our limit unless there are N*N uncalculated layers
// below our current one. // below our current one.
constexpr auto max_recursion_depth = 100;
size_t layer_nr_next = layer_nr; size_t layer_nr_next = layer_nr;
for (int i = 0; i < max_recursion_depth && layer_nr_next>0; i++) { int layers_below;
layer_nr_next = layer_heights[layer_nr_next].next_layer_nr; for (layers_below = 0; layers_below < max_recursion_depth && layer_nr_next > 0; layers_below++) { layer_nr_next = layer_heights[layer_nr_next].next_layer_nr; }
}
// Check if we would exceed the recursion limit by trying to process this layer // Check if we would exceed the recursion limit by trying to process this layer
if (layer_nr >= max_recursion_depth && m_avoidance_cache.find({radius, layer_nr_next}) == m_avoidance_cache.end()) { if (layers_below >= max_recursion_depth && m_avoidance_cache.find({radius, layer_nr_next}) == m_avoidance_cache.end()) {
// Force the calculation of the layer `max_recursion_depth` below our current one, ignoring the result. // Force the calculation of the layer `max_recursion_depth` below our current one, ignoring the result.
get_avoidance(radius, layer_nr_next); get_avoidance(radius, layer_nr_next, key.recursions + 1);
} }
layer_nr_next = layer_heights[layer_nr].next_layer_nr; layer_nr_next = layer_heights[layer_nr].next_layer_nr;
ExPolygons avoidance_areas = std::move(offset_ex(get_avoidance(radius, layer_nr_next), scale_(-m_max_move))); ExPolygons avoidance_areas = std::move(offset_ex(get_avoidance(radius, layer_nr_next, key.recursions+1), scale_(-m_max_move)));
const ExPolygons &collision = get_collision(radius, layer_nr); const ExPolygons &collision = get_collision(radius, layer_nr);
avoidance_areas.insert(avoidance_areas.end(), collision.begin(), collision.end()); avoidance_areas.insert(avoidance_areas.end(), collision.begin(), collision.end());
avoidance_areas = std::move(union_ex(avoidance_areas)); avoidance_areas = std::move(union_ex(avoidance_areas));

View File

@ -83,7 +83,7 @@ public:
* \param layer The layer of interest * \param layer The layer of interest
* \return Polygons object * \return Polygons object
*/ */
const ExPolygons& get_avoidance(coordf_t radius, size_t layer_idx) const; const ExPolygons& get_avoidance(coordf_t radius, size_t layer_idx, int recursions=0) const;
Polygons get_contours(size_t layer_nr) const; Polygons get_contours(size_t layer_nr) const;
Polygons get_contours_with_holes(size_t layer_nr) const; Polygons get_contours_with_holes(size_t layer_nr) const;
@ -94,11 +94,20 @@ private:
/*! /*!
* \brief Convenience typedef for the keys to the caches * \brief Convenience typedef for the keys to the caches
*/ */
using RadiusLayerPair = std::pair<coordf_t, size_t>; struct RadiusLayerPair {
coordf_t radius;
size_t layer_nr;
int recursions;
};
struct RadiusLayerPairEquality {
constexpr bool operator()(const RadiusLayerPair& _Left, const RadiusLayerPair& _Right) const {
return _Left.radius == _Right.radius && _Left.layer_nr == _Right.layer_nr;
}
};
struct RadiusLayerPairHash { struct RadiusLayerPairHash {
size_t operator()(const RadiusLayerPair& elem) const { size_t operator()(const RadiusLayerPair& elem) const {
return std::hash<coord_t>()(elem.first) ^ std::hash<coord_t>()(elem.second * 7919); return std::hash<coord_t>()(elem.radius) ^ std::hash<coord_t>()(elem.layer_nr * 7919);
} }
}; };
@ -168,8 +177,8 @@ public:
* coconut: previously stl::unordered_map is used which seems problematic with tbb::parallel_for. * coconut: previously stl::unordered_map is used which seems problematic with tbb::parallel_for.
* So we change to tbb::concurrent_unordered_map * So we change to tbb::concurrent_unordered_map
*/ */
mutable tbb::concurrent_unordered_map<RadiusLayerPair, ExPolygons, RadiusLayerPairHash> m_collision_cache; mutable tbb::concurrent_unordered_map<RadiusLayerPair, ExPolygons, RadiusLayerPairHash, RadiusLayerPairEquality> m_collision_cache;
mutable tbb::concurrent_unordered_map<RadiusLayerPair, ExPolygons, RadiusLayerPairHash> m_avoidance_cache; mutable tbb::concurrent_unordered_map<RadiusLayerPair, ExPolygons, RadiusLayerPairHash, RadiusLayerPairEquality> m_avoidance_cache;
friend TreeSupport; friend TreeSupport;
}; };
@ -203,7 +212,7 @@ public:
*/ */
void generate(); void generate();
void detect_overhangs(); void detect_overhangs(bool detect_first_sharp_tail_only=false);
enum NodeType { enum NodeType {
eCircle, eCircle,