mirror of
https://github.com/SoftFever/OrcaSlicer.git
synced 2025-07-07 23:17:35 -06:00
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:
parent
1487bdd69c
commit
3b7b10f72f
9 changed files with 370 additions and 28 deletions
|
@ -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,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;
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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");
|
||||
|
|
|
@ -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))
|
||||
|
|
|
@ -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));
|
||||
|
|
|
@ -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");
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue