Port "No Unsupported Perimeters" feature from SS (#3189)

* first impl

* Properly handle extra bridges in `detect_surfaces_type()`

* Pass `perimeter_spacing` and `ext_perimeter_width` as parameters instead of instance property

* Make `process_no_bridge()` private

* Attempt to run `process_no_bridge()` in arachne

* Update `BridgeDetector::coverage` to give us more precise bridge coverage

Co-authored-by: supermerill <merill@free.fr>

* Fix bridge infill margin scaling

* Rename the option name as well as add tooltip

---------

Co-authored-by: Noisyfox <timemanager.rick@gmail.com>
Co-authored-by: supermerill <merill@free.fr>
Co-authored-by: SoftFever <softfeverever@gmail.com>
This commit is contained in:
Ocraftyone 2024-01-28 05:12:55 -05:00 committed by GitHub
parent 1487bdd69c
commit 3b7b10f72f
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
9 changed files with 370 additions and 28 deletions

View file

@ -272,8 +272,56 @@ static void get_trapezoids2(const ExPolygon &expoly, Polygons* polygons, double
polygon.rotate(-(PI/2 - angle), Point(0,0));
}
// Coverage is currently only used by the unit tests. It is extremely slow and unreliable!
Polygons BridgeDetector::coverage(double angle) const
void get_trapezoids3_half(const ExPolygon& expoly, Polygons* polygons, float spacing)
{
// get all points of this ExPolygon
Points pp = to_points(expoly);
if (pp.empty()) return;
// build our bounding box
BoundingBox bb(pp);
// get all x coordinates
coord_t min_x = pp[0].x(), max_x = pp[0].x();
std::vector<coord_t> xx;
for (Points::const_iterator p = pp.begin(); p != pp.end(); ++p) {
if (min_x > p->x()) min_x = p->x();
if (max_x < p->x()) max_x = p->x();
}
for (coord_t x = min_x; x < max_x - (coord_t)(spacing / 2); x += (coord_t)spacing) {
xx.push_back(x);
}
xx.push_back(max_x);
//std::sort(xx.begin(), xx.end());
// find trapezoids by looping from first to next-to-last coordinate
for (std::vector<coord_t>::const_iterator x = xx.begin(); x != xx.end() - 1; ++x) {
coord_t next_x = *(x + 1);
if (*x == next_x) continue;
// build rectangle
Polygon poly;
poly.points.resize(4);
poly[0].x() = *x + (coord_t)spacing / 4;
poly[0].y() = bb.min(1);
poly[1].x() = next_x - (coord_t)spacing / 4;
poly[1].y() = bb.min(1);
poly[2].x() = next_x - (coord_t)spacing / 4;
poly[2].y() = bb.max(1);
poly[3].x() = *x + (coord_t)spacing / 4;
poly[3].y() = bb.max(1);
// intersect with this expolygon
// append results to return value
polygons_append(*polygons, intersection(Polygons{ poly }, to_polygons(expoly)));
}
}
Polygons BridgeDetector::coverage(double angle, bool precise) const
{
if (angle == -1)
angle = this->angle;
@ -283,36 +331,75 @@ Polygons BridgeDetector::coverage(double angle) const
if (angle != -1) {
// Get anchors, convert them to Polygons and rotate them.
Polygons anchors = to_polygons(this->_anchor_regions);
polygons_rotate(anchors, PI/2.0 - angle);
polygons_rotate(anchors, PI / 2.0 - angle);
//same for region which do not need bridging
//Polygons supported_area = diff(this->lower_slices.expolygons, this->_anchor_regions, true);
//polygons_rotate(anchors, PI / 2.0 - angle);
for (ExPolygon expolygon : this->expolygons) {
// Clone our expolygon and rotate it so that we work with vertical lines.
expolygon.rotate(PI/2.0 - angle);
expolygon.rotate(PI / 2.0 - angle);
// Outset the bridge expolygon by half the amount we used for detecting anchors;
// we'll use this one to generate our trapezoids and be sure that their vertices
// are inside the anchors and not on their contours leading to false negatives.
for (ExPolygon &expoly : offset_ex(expolygon, 0.5f * float(this->spacing))) {
// Compute trapezoids according to a vertical orientation
Polygons trapezoids;
get_trapezoids2(expoly, &trapezoids, PI/2.0);
for (const Polygon &trapezoid : trapezoids) {
// not nice, we need a more robust non-numeric check
if (!precise) get_trapezoids2(expoly, &trapezoids, PI / 2);
else get_trapezoids3_half(expoly, &trapezoids, float(this->spacing));
for (Polygon &trapezoid : trapezoids) {
size_t n_supported = 0;
if (!precise) {
// not nice, we need a more robust non-numeric check
// imporvment 1: take into account when we go in the supported area.
for (const Line &supported_line : intersection_ln(trapezoid.lines(), anchors))
if (supported_line.length() >= this->spacing)
++ n_supported;
if (n_supported >= 2)
++n_supported;
} else {
Polygons intersects = intersection(Polygons{trapezoid}, anchors);
n_supported = intersects.size();
if (n_supported >= 2) {
// trim it to not allow to go outside of the intersections
BoundingBox center_bound = intersects[0].bounding_box();
coord_t min_y = center_bound.center()(1), max_y = center_bound.center()(1);
for (Polygon &poly_bound : intersects) {
center_bound = poly_bound.bounding_box();
if (min_y > center_bound.center()(1)) min_y = center_bound.center()(1);
if (max_y < center_bound.center()(1)) max_y = center_bound.center()(1);
}
coord_t min_x = trapezoid[0](0), max_x = trapezoid[0](0);
for (Point &p : trapezoid.points) {
if (min_x > p(0)) min_x = p(0);
if (max_x < p(0)) max_x = p(0);
}
//add what get_trapezoids3 has removed (+EPSILON)
min_x -= (this->spacing / 4 + 1);
max_x += (this->spacing / 4 + 1);
coord_t mid_x = (min_x + max_x) / 2;
for (Point &p : trapezoid.points) {
if (p(1) < min_y) p(1) = min_y;
if (p(1) > max_y) p(1) = max_y;
if (p(0) > min_x && p(0) < mid_x) p(0) = min_x;
if (p(0) < max_x && p(0) > mid_x) p(0) = max_x;
}
}
}
if (n_supported >= 2) {
//add it
covered.push_back(std::move(trapezoid));
}
}
}
}
// Unite the trapezoids before rotation, as the rotation creates tiny gaps and intersections between the trapezoids
// instead of exact overlaps.
covered = union_(covered);
// Intersect trapezoids with actual bridge area to remove extra margins and append it to result.
polygons_rotate(covered, -(PI/2.0 - angle));
covered = intersection(this->expolygons, covered);
//covered = intersection(this->expolygons, covered);
#if 0
{
my @lines = map @{$_->lines}, @$trapezoids;

View file

@ -43,8 +43,7 @@ public:
BridgeDetector(const ExPolygons &_expolygons, const ExPolygons &_lower_slices, coord_t _extrusion_width);
// If bridge_direction_override != 0, then the angle is used instead of auto-detect.
bool detect_angle(double bridge_direction_override = 0.);
// Coverage is currently only used by the unit tests. It is extremely slow and unreliable!
Polygons coverage(double angle = -1) const;
Polygons coverage(double angle = -1, bool precise = true) const;
void unsupported_edges(double angle, Polylines* unsupported) const;
Polylines unsupported_edges(double angle = -1) const;

View file

@ -18,6 +18,8 @@
#include "Clipper2Utils.hpp"
#include "Arachne/WallToolPaths.hpp"
#include "Geometry/ConvexHull.hpp"
#include "ExPolygonCollection.hpp"
#include "Geometry.hpp"
#include <cmath>
#include <cassert>
@ -1494,16 +1496,18 @@ void PerimeterGenerator::process_classic()
// we need to process each island separately because we might have different
// extra perimeters for each one
Surfaces all_surfaces = this->slices->surfaces;
process_no_bridge(all_surfaces, perimeter_spacing, ext_perimeter_width);
// BBS: don't simplify too much which influence arc fitting when export gcode if arc_fitting is enabled
double surface_simplify_resolution = (print_config->enable_arc_fitting && this->config->fuzzy_skin == FuzzySkinType::None) ? 0.2 * m_scaled_resolution : m_scaled_resolution;
//BBS: reorder the surface to reduce the travel time
ExPolygons surface_exp;
for (const Surface &surface : this->slices->surfaces)
for (const Surface &surface : all_surfaces)
surface_exp.push_back(surface.expolygon);
std::vector<size_t> surface_order = chain_expolygons(surface_exp);
for (size_t order_idx = 0; order_idx < surface_order.size(); order_idx++) {
const Surface &surface = this->slices->surfaces[surface_order[order_idx]];
const Surface &surface = all_surfaces[surface_order[order_idx]];
// detect how many perimeters must be generated for this island
int loop_number = this->config->wall_loops + surface.extra_perimeters - 1; // 0-indexed loops
int sparse_infill_density = this->config->sparse_infill_density.value;
@ -1904,6 +1908,218 @@ void PerimeterGenerator::add_infill_contour_for_arachne( ExPolygons infil
append(*this->fill_no_overlap, offset2_ex(union_ex(inner_pp), float(-min_perimeter_infill_spacing / 2.), float(+min_perimeter_infill_spacing / 2.)));
}
// Orca: sacrificial bridge layer algorithm ported from SuperSlicer
void PerimeterGenerator::process_no_bridge(Surfaces& all_surfaces, coord_t perimeter_spacing, coord_t ext_perimeter_width)
{
//store surface for bridge infill to avoid unsupported perimeters (but the first one, this one is always good)
if (this->config->counterbole_hole_bridging != chbNone
&& this->lower_slices != NULL && !this->lower_slices->empty()) {
const coordf_t bridged_infill_margin = scale_(BRIDGE_INFILL_MARGIN);
for (size_t surface_idx = 0; surface_idx < all_surfaces.size(); surface_idx++) {
Surface* surface = &all_surfaces[surface_idx];
ExPolygons last = { surface->expolygon };
//compute our unsupported surface
ExPolygons unsupported = diff_ex(last, *this->lower_slices, ApplySafetyOffset::Yes);
if (!unsupported.empty()) {
//remove small overhangs
ExPolygons unsupported_filtered = offset2_ex(unsupported, double(-perimeter_spacing), double(perimeter_spacing));
if (!unsupported_filtered.empty()) {
//to_draw.insert(to_draw.end(), last.begin(), last.end());
//extract only the useful part of the lower layer. The safety offset is really needed here.
ExPolygons support = diff_ex(last, unsupported, ApplySafetyOffset::Yes);
if (!unsupported.empty()) {
//only consider the part that can be bridged (really, by the bridge algorithm)
//first, separate into islands (ie, each ExPlolygon)
int numploy = 0;
//only consider the bottom layer that intersect unsupported, to be sure it's only on our island.
ExPolygonCollection lower_island(support);
//a detector per island
ExPolygons bridgeable;
for (ExPolygon unsupported : unsupported_filtered) {
BridgeDetector detector{ unsupported,
lower_island.expolygons,
perimeter_spacing };
if (detector.detect_angle(Geometry::deg2rad(this->config->bridge_angle.value)))
expolygons_append(bridgeable, union_ex(detector.coverage(-1, true)));
}
if (!bridgeable.empty()) {
//check if we get everything or just the bridgeable area
if (/*this->config->counterbole_hole_bridging.value == chbNoPeri || */this->config->counterbole_hole_bridging.value == chbFilled) {
//we bridge everything, even the not-bridgeable bits
for (size_t i = 0; i < unsupported_filtered.size();) {
ExPolygon& poly_unsupp = *(unsupported_filtered.begin() + i);
Polygons contour_simplified = poly_unsupp.contour.simplify(perimeter_spacing);
ExPolygon poly_unsupp_bigger = poly_unsupp;
Polygons contour_bigger = offset(poly_unsupp_bigger.contour, bridged_infill_margin);
if (contour_bigger.size() == 1) poly_unsupp_bigger.contour = contour_bigger[0];
//check convex, has some bridge, not overhang
if (contour_simplified.size() == 1 && contour_bigger.size() == 1 && contour_simplified[0].concave_points().size() == 0
&& intersection_ex(bridgeable, ExPolygons{ poly_unsupp }).size() > 0
&& diff_ex(ExPolygons{ poly_unsupp_bigger }, union_ex(last, offset_ex(bridgeable, bridged_infill_margin + perimeter_spacing / 2)), ApplySafetyOffset::Yes).size() == 0
) {
//ok, keep it
i++;
} else {
unsupported_filtered.erase(unsupported_filtered.begin() + i);
}
}
unsupported_filtered = intersection_ex(last,
offset2_ex(unsupported_filtered, double(-perimeter_spacing / 2), double(bridged_infill_margin + perimeter_spacing / 2)));
if (this->config->counterbole_hole_bridging.value == chbFilled) {
for (ExPolygon& expol : unsupported_filtered) {
//check if the holes won't be covered by the upper layer
//TODO: if we want to do that, we must modify the geometry before making perimeters.
//if (this->upper_slices != nullptr && !this->upper_slices->expolygons.empty()) {
// for (Polygon &poly : expol.holes) poly.make_counter_clockwise();
// float perimeterwidth = this->config->perimeters == 0 ? 0 : (this->ext_perimeter_flow.scaled_width() + (this->config->perimeters - 1) + this->perimeter_flow.scaled_spacing());
// std::cout << "test upper slices with perimeterwidth=" << perimeterwidth << "=>" << offset_ex(this->upper_slices->expolygons, -perimeterwidth).size();
// if (intersection(Polygons() = { expol.holes }, to_polygons(offset_ex(this->upper_slices->expolygons, -this->ext_perimeter_flow.scaled_width() / 2))).empty()) {
// std::cout << " EMPTY";
// expol.holes.clear();
// } else {
// }
// std::cout << "\n";
//} else {
expol.holes.clear();
//}
//detect inside volume
for (size_t surface_idx_other = 0; surface_idx_other < all_surfaces.size(); surface_idx_other++) {
if (surface_idx == surface_idx_other) continue;
if (intersection_ex(ExPolygons() = { expol }, ExPolygons() = { all_surfaces[surface_idx_other].expolygon }).size() > 0) {
//this means that other_surf was inside an expol holes
//as we removed them, we need to add a new one
ExPolygons new_poly = offset2_ex(ExPolygons{ all_surfaces[surface_idx_other].expolygon }, double(-bridged_infill_margin - perimeter_spacing), double(perimeter_spacing));
if (new_poly.size() == 1) {
all_surfaces[surface_idx_other].expolygon = new_poly[0];
expol.holes.push_back(new_poly[0].contour);
expol.holes.back().make_clockwise();
} else {
for (size_t idx = 0; idx < new_poly.size(); idx++) {
Surface new_surf = all_surfaces[surface_idx_other];
new_surf.expolygon = new_poly[idx];
all_surfaces.push_back(new_surf);
expol.holes.push_back(new_poly[idx].contour);
expol.holes.back().make_clockwise();
}
all_surfaces.erase(all_surfaces.begin() + surface_idx_other);
if (surface_idx_other < surface_idx) {
surface_idx--;
surface = &all_surfaces[surface_idx];
}
surface_idx_other--;
}
}
}
}
}
//TODO: add other polys as holes inside this one (-margin)
} else if (/*this->config->counterbole_hole_bridging.value == chbBridgesOverhangs || */this->config->counterbole_hole_bridging.value == chbBridges) {
//simplify to avoid most of artefacts from printing lines.
ExPolygons bridgeable_simplified;
for (ExPolygon& poly : bridgeable) {
poly.simplify(perimeter_spacing, &bridgeable_simplified);
}
bridgeable_simplified = offset2_ex(bridgeable_simplified, -ext_perimeter_width, ext_perimeter_width);
//bridgeable_simplified = intersection_ex(bridgeable_simplified, unsupported_filtered);
//offset by perimeter spacing because the simplify may have reduced it a bit.
//it's not dangerous as it will be intersected by 'unsupported' later
//FIXME: add overlap in this->fill_surfaces->append
//FIXME: it overlap inside unsuppported not-bridgeable area!
//bridgeable_simplified = offset2_ex(bridgeable_simplified, (double)-perimeter_spacing, (double)perimeter_spacing * 2);
//ExPolygons unbridgeable = offset_ex(diff_ex(unsupported, bridgeable_simplified), perimeter_spacing * 3 / 2);
//ExPolygons unbridgeable = intersection_ex(unsupported, diff_ex(unsupported_filtered, offset_ex(bridgeable_simplified, ext_perimeter_width / 2)));
//unbridgeable = offset2_ex(unbridgeable, -ext_perimeter_width, ext_perimeter_width);
// if (this->config->counterbole_hole_bridging.value == chbBridges) {
ExPolygons unbridgeable = unsupported_filtered;
for (ExPolygon& expol : unbridgeable)
expol.holes.clear();
unbridgeable = diff_ex(unbridgeable, bridgeable_simplified);
unbridgeable = offset2_ex(unbridgeable, -ext_perimeter_width * 2, ext_perimeter_width * 2);
ExPolygons bridges_temp = offset2_ex(intersection_ex(last, diff_ex(unsupported_filtered, unbridgeable), ApplySafetyOffset::Yes), -ext_perimeter_width / 4, ext_perimeter_width / 4);
//remove the overhangs section from the surface polygons
ExPolygons reference = last;
last = diff_ex(last, unsupported_filtered);
//ExPolygons no_bridge = diff_ex(offset_ex(unbridgeable, ext_perimeter_width * 3 / 2), last);
//bridges_temp = diff_ex(bridges_temp, no_bridge);
coordf_t offset_to_do = bridged_infill_margin;
bool first = true;
unbridgeable = diff_ex(unbridgeable, offset_ex(bridges_temp, ext_perimeter_width));
while (offset_to_do > ext_perimeter_width * 1.5) {
unbridgeable = offset2_ex(unbridgeable, -ext_perimeter_width / 4, ext_perimeter_width * 2.25, ClipperLib::jtSquare);
bridges_temp = diff_ex(bridges_temp, unbridgeable);
bridges_temp = offset_ex(bridges_temp, ext_perimeter_width, ClipperLib::jtMiter, 6.);
unbridgeable = diff_ex(unbridgeable, offset_ex(bridges_temp, ext_perimeter_width));
offset_to_do -= ext_perimeter_width;
first = false;
}
unbridgeable = offset_ex(unbridgeable, ext_perimeter_width + offset_to_do, ClipperLib::jtSquare);
bridges_temp = diff_ex(bridges_temp, unbridgeable);
unsupported_filtered = offset_ex(bridges_temp, offset_to_do);
unsupported_filtered = intersection_ex(unsupported_filtered, reference);
// } else {
// ExPolygons unbridgeable = intersection_ex(unsupported, diff_ex(unsupported_filtered, offset_ex(bridgeable_simplified, ext_perimeter_width / 2)));
// unbridgeable = offset2_ex(unbridgeable, -ext_perimeter_width, ext_perimeter_width);
// unsupported_filtered = unbridgeable;
// ////put the bridge area inside the unsupported_filtered variable
// //unsupported_filtered = intersection_ex(last,
// // diff_ex(
// // offset_ex(bridgeable_simplified, (double)perimeter_spacing / 2),
// // unbridgeable
// // )
// // );
// }
} else {
unsupported_filtered.clear();
}
} else {
unsupported_filtered.clear();
}
}
if (!unsupported_filtered.empty()) {
//add this directly to the infill list.
// this will avoid to throw wrong offsets into a good polygons
this->fill_surfaces->append(
unsupported_filtered,
stInternal);
// store the results
last = diff_ex(last, unsupported_filtered, ApplySafetyOffset::Yes);
//remove "thin air" polygons (note: it assumes that all polygons below will be extruded)
for (int i = 0; i < last.size(); i++) {
if (intersection_ex(support, ExPolygons() = { last[i] }).empty()) {
this->fill_surfaces->append(
ExPolygons() = { last[i] },
stInternal);
last.erase(last.begin() + i);
i--;
}
}
}
}
}
if (last.size() == 0) {
all_surfaces.erase(all_surfaces.begin() + surface_idx);
surface_idx--;
} else {
surface->expolygon = last[0];
for (size_t idx = 1; idx < last.size(); idx++) {
all_surfaces.emplace_back(*surface, last[idx]);
}
}
}
}
}
// Thanks, Cura developers, for implementing an algorithm for generating perimeters with variable width (Arachne) that is based on the paper
// "A framework for adaptive width control of dense contour-parallel toolpaths in fused deposition modeling"
void PerimeterGenerator::process_arachne()
@ -1932,12 +2148,14 @@ void PerimeterGenerator::process_arachne()
m_lower_slices_polygons = offset(*this->lower_slices, float(scale_(+nozzle_diameter / 2)));
}
Surfaces all_surfaces = this->slices->surfaces;
process_no_bridge(all_surfaces, perimeter_spacing, ext_perimeter_width);
// BBS: don't simplify too much which influence arc fitting when export gcode if arc_fitting is enabled
double surface_simplify_resolution = (print_config->enable_arc_fitting && this->config->fuzzy_skin == FuzzySkinType::None) ? 0.2 * m_scaled_resolution : m_scaled_resolution;
// we need to process each island separately because we might have different
// extra perimeters for each one
for (const Surface& surface : this->slices->surfaces) {
for (const Surface& surface : all_surfaces) {
coord_t bead_width_0 = ext_perimeter_spacing;
// detect how many perimeters must be generated for this island
int loop_number = this->config->wall_loops + surface.extra_perimeters - 1; // 0-indexed loops

View file

@ -82,6 +82,7 @@ private:
std::map<int, Polygons> generate_lower_polygons_series(float width);
void split_top_surfaces(const ExPolygons &orig_polygons, ExPolygons &top_fills, ExPolygons &non_top_polygons, ExPolygons &fill_clip) const;
void apply_extra_perimeters(ExPolygons& infill_area);
void process_no_bridge(Surfaces& all_surfaces, coord_t perimeter_spacing, coord_t ext_perimeter_width);
private:
bool m_spiral_vase;

View file

@ -772,7 +772,7 @@ static std::vector<std::string> s_Preset_print_options {
"top_shell_layers", "top_shell_thickness", "bottom_shell_layers", "bottom_shell_thickness",
"extra_perimeters_on_overhangs", "ensure_vertical_shell_thickness","reduce_wall_solid_infill", "reduce_crossing_wall", "detect_thin_wall", "detect_overhang_wall", "overhang_reverse", "overhang_reverse_threshold","overhang_reverse_internal_only",
"seam_position", "staggered_inner_seams", "wall_sequence", "is_infill_first", "sparse_infill_density", "sparse_infill_pattern", "top_surface_pattern", "bottom_surface_pattern",
"infill_direction",
"infill_direction", "counterbole_hole_bridging",
"minimum_sparse_infill_area", "reduce_infill_retraction","internal_solid_infill_pattern","gap_fill_target",
"ironing_type", "ironing_pattern", "ironing_flow", "ironing_speed", "ironing_spacing", "ironing_angle",
"max_travel_detour_distance",

View file

@ -395,6 +395,13 @@ static const t_config_enum_values s_keys_map_GCodeThumbnailsFormat = {
};
CONFIG_OPTION_ENUM_DEFINE_STATIC_MAPS(GCodeThumbnailsFormat)
static const t_config_enum_values s_keys_map_CounterboleHoleBridgingOption{
{ "none", chbNone },
{ "partiallybridge", chbBridges },
{ "sacrificiallayer", chbFilled },
};
CONFIG_OPTION_ENUM_DEFINE_STATIC_MAPS(CounterboleHoleBridgingOption)
static void assign_printer_technology_to_unknown(t_optiondef_map &options, PrinterTechnology printer_technology)
{
for (std::pair<const t_config_option_key, ConfigOptionDef> &kvp : options)
@ -942,6 +949,24 @@ void PrintConfigDef::init_fff_params()
def->mode = comAdvanced;
def->set_default_value(new ConfigOptionBool(false));
def = this->add("counterbole_hole_bridging", coEnum);
def->label = L("Bridge counterbole holes");
def->category = L("Quality");
def->tooltip = L(
"This option creates bridges for counterbore holes, allowing them to be printed without support. Available modes include:\n"
"1. None: No bridge is created.\n"
"2. Partially Bridged: Only a part of the unsupported area will be bridged.\n"
"3. Sacrificial Layer: A full sacrificial bridge layer is created.");
def->mode = comAdvanced;
def->enum_keys_map = &ConfigOptionEnum<CounterboleHoleBridgingOption>::get_enum_values();
def->enum_values.emplace_back("none");
def->enum_values.emplace_back("partiallybridge");
def->enum_values.emplace_back("sacrificiallayer");
def->enum_labels.emplace_back(L("None"));
def->enum_labels.emplace_back(L("Partially bridged"));
def->enum_labels.emplace_back(L("Sacrificial layer"));
def->set_default_value(new ConfigOptionEnum<CounterboleHoleBridgingOption>(chbNone));
def = this->add("overhang_reverse_threshold", coFloatOrPercent);
def->label = L("Reverse threshold");
def->full_label = L("Overhang reversal threshold");

View file

@ -293,6 +293,10 @@ enum class GCodeThumbnailsFormat {
PNG, JPG, QOI, BTT_TFT, ColPic
};
enum CounterboleHoleBridgingOption {
chbNone, chbBridges, chbFilled
};
static std::string bed_type_to_gcode_string(const BedType type)
{
std::string type_str;
@ -376,7 +380,7 @@ CONFIG_OPTION_ENUM_DECLARE_STATIC_MAPS(BedType)
CONFIG_OPTION_ENUM_DECLARE_STATIC_MAPS(DraftShield)
CONFIG_OPTION_ENUM_DECLARE_STATIC_MAPS(ForwardCompatibilitySubstitutionRule)
CONFIG_OPTION_ENUM_DECLARE_STATIC_MAPS(GCodeThumbnailsFormat)
CONFIG_OPTION_ENUM_DECLARE_STATIC_MAPS(CounterboleHoleBridgingOption)
CONFIG_OPTION_ENUM_DECLARE_STATIC_MAPS(PrintHostType)
CONFIG_OPTION_ENUM_DECLARE_STATIC_MAPS(AuthorizationType)
CONFIG_OPTION_ENUM_DECLARE_STATIC_MAPS(PerimeterGeneratorType)
@ -924,7 +928,7 @@ PRINT_CONFIG_CLASS_DEFINE(
((ConfigOptionBool, overhang_reverse))
((ConfigOptionBool, overhang_reverse_internal_only))
((ConfigOptionFloatOrPercent, overhang_reverse_threshold))
((ConfigOptionEnum<CounterboleHoleBridgingOption>, counterbole_hole_bridging))
((ConfigOptionEnum<WallSequence>, wall_sequence))
((ConfigOptionBool, is_infill_first))
((ConfigOptionBool, small_area_infill_flow_compensation))

View file

@ -1283,13 +1283,19 @@ void PrintObject::detect_surfaces_type()
// collapse very narrow parts (using the safety offset in the diff is not enough)
float offset = layerm->flow(frExternalPerimeter).scaled_width() / 10.f;
ExPolygons layerm_slices_surfaces = to_expolygons(layerm->slices.surfaces);
// no_perimeter_full_bridge allow to put bridges where there are nothing, hence adding area to slice, that's why we need to start from the result of PerimeterGenerator.
if (layerm->region().config().counterbole_hole_bridging.value == chbFilled) {
layerm_slices_surfaces = union_ex(layerm_slices_surfaces, to_expolygons(layerm->fill_surfaces.surfaces));
}
// find top surfaces (difference between current surfaces
// of current layer and upper one)
Surfaces top;
if (upper_layer) {
ExPolygons upper_slices = interface_shells ?
diff_ex(layerm->slices.surfaces, upper_layer->m_regions[region_id]->slices.surfaces, ApplySafetyOffset::Yes) :
diff_ex(layerm->slices.surfaces, upper_layer->lslices, ApplySafetyOffset::Yes);
diff_ex(layerm_slices_surfaces, upper_layer->m_regions[region_id]->slices.surfaces, ApplySafetyOffset::Yes) :
diff_ex(layerm_slices_surfaces, upper_layer->lslices, ApplySafetyOffset::Yes);
surfaces_append(top, opening_ex(upper_slices, offset), stTop);
} else {
// if no upper layer, all surfaces of this one are solid
@ -1308,14 +1314,14 @@ void PrintObject::detect_surfaces_type()
to_polygons(lower_layer->get_region(region_id)->slices.surfaces) :
to_polygons(lower_layer->slices);
surfaces_append(bottom,
opening_ex(diff(layerm->slices.surfaces, lower_slices, true), offset),
opening_ex(diff(layerm_slices_surfaces, lower_slices, true), offset),
surface_type_bottom_other);
#else
// Any surface lying on the void is a true bottom bridge (an overhang)
surfaces_append(
bottom,
opening_ex(
diff_ex(layerm->slices.surfaces, lower_layer->lslices, ApplySafetyOffset::Yes),
diff_ex(layerm_slices_surfaces, lower_layer->lslices, ApplySafetyOffset::Yes),
offset),
surface_type_bottom_other);
// if user requested internal shells, we need to identify surfaces
@ -1327,7 +1333,7 @@ void PrintObject::detect_surfaces_type()
bottom,
opening_ex(
diff_ex(
intersection(layerm->slices.surfaces, lower_layer->lslices), // supported
intersection(layerm_slices_surfaces, lower_layer->lslices), // supported
lower_layer->m_regions[region_id]->slices.surfaces,
ApplySafetyOffset::Yes),
offset),
@ -1372,13 +1378,14 @@ void PrintObject::detect_surfaces_type()
surfaces_backup = std::move(surfaces_out);
surfaces_out.clear();
}
const Surfaces &surfaces_prev = interface_shells ? layerm->slices.surfaces : surfaces_backup;
//const Surfaces &surfaces_prev = interface_shells ? layerm->slices.surfaces : surfaces_backup;
const ExPolygons& surfaces_prev_expolys = interface_shells ? layerm_slices_surfaces : to_expolygons(surfaces_backup);
// find internal surfaces (difference between top/bottom surfaces and others)
{
Polygons topbottom = to_polygons(top);
polygons_append(topbottom, to_polygons(bottom));
surfaces_append(surfaces_out, diff_ex(surfaces_prev, topbottom), stInternal);
surfaces_append(surfaces_out, diff_ex(surfaces_prev_expolys, topbottom), stInternal);
}
surfaces_append(surfaces_out, std::move(top));

View file

@ -2023,6 +2023,7 @@ void TabPrint::build()
optgroup->append_single_option_line("thick_bridges");
optgroup->append_single_option_line("thick_internal_bridges");
optgroup->append_single_option_line("dont_filter_internal_bridges");
optgroup->append_single_option_line("counterbole_hole_bridging","counterbole-hole-bridging");
optgroup = page->new_optgroup(L("Overhangs"), L"param_advanced");
optgroup->append_single_option_line("detect_overhang_wall");