diff --git a/src/libslic3r/BridgeDetector.cpp b/src/libslic3r/BridgeDetector.cpp index 09a2c2dc3b..321e066a8a 100644 --- a/src/libslic3r/BridgeDetector.cpp +++ b/src/libslic3r/BridgeDetector.cpp @@ -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 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::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,26 +331,65 @@ 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; - for (const Line &supported_line : intersection_ln(trapezoid.lines(), anchors)) - if (supported_line.length() >= this->spacing) - ++ n_supported; - if (n_supported >= 2) + 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; + } 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)); + } } } } @@ -312,7 +399,7 @@ Polygons BridgeDetector::coverage(double angle) const 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; diff --git a/src/libslic3r/BridgeDetector.hpp b/src/libslic3r/BridgeDetector.hpp index c87b4e2cec..6025f9d3e0 100644 --- a/src/libslic3r/BridgeDetector.hpp +++ b/src/libslic3r/BridgeDetector.hpp @@ -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; diff --git a/src/libslic3r/PerimeterGenerator.cpp b/src/libslic3r/PerimeterGenerator.cpp index 2e8d3af0fd..c9b1d5d980 100644 --- a/src/libslic3r/PerimeterGenerator.cpp +++ b/src/libslic3r/PerimeterGenerator.cpp @@ -18,6 +18,8 @@ #include "Clipper2Utils.hpp" #include "Arachne/WallToolPaths.hpp" #include "Geometry/ConvexHull.hpp" +#include "ExPolygonCollection.hpp" +#include "Geometry.hpp" #include #include @@ -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 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 diff --git a/src/libslic3r/PerimeterGenerator.hpp b/src/libslic3r/PerimeterGenerator.hpp index 424d6bc375..e9da632386 100644 --- a/src/libslic3r/PerimeterGenerator.hpp +++ b/src/libslic3r/PerimeterGenerator.hpp @@ -82,6 +82,7 @@ private: std::map 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; diff --git a/src/libslic3r/Preset.cpp b/src/libslic3r/Preset.cpp index 2652f603ed..c9a315085c 100644 --- a/src/libslic3r/Preset.cpp +++ b/src/libslic3r/Preset.cpp @@ -772,7 +772,7 @@ static std::vector 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", diff --git a/src/libslic3r/PrintConfig.cpp b/src/libslic3r/PrintConfig.cpp index 5bff9c7285..45a7f926e9 100644 --- a/src/libslic3r/PrintConfig.cpp +++ b/src/libslic3r/PrintConfig.cpp @@ -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 &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::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(chbNone)); + def = this->add("overhang_reverse_threshold", coFloatOrPercent); def->label = L("Reverse threshold"); def->full_label = L("Overhang reversal threshold"); diff --git a/src/libslic3r/PrintConfig.hpp b/src/libslic3r/PrintConfig.hpp index f894a24ca8..db53a9b781 100644 --- a/src/libslic3r/PrintConfig.hpp +++ b/src/libslic3r/PrintConfig.hpp @@ -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, counterbole_hole_bridging)) ((ConfigOptionEnum, wall_sequence)) ((ConfigOptionBool, is_infill_first)) ((ConfigOptionBool, small_area_infill_flow_compensation)) diff --git a/src/libslic3r/PrintObject.cpp b/src/libslic3r/PrintObject.cpp index d3a206123f..4812579f02 100644 --- a/src/libslic3r/PrintObject.cpp +++ b/src/libslic3r/PrintObject.cpp @@ -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)); diff --git a/src/slic3r/GUI/Tab.cpp b/src/slic3r/GUI/Tab.cpp index 9e23aec99a..df83a14c37 100644 --- a/src/slic3r/GUI/Tab.cpp +++ b/src/slic3r/GUI/Tab.cpp @@ -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");