From a37d5fca899c6766fd7bdaa13b6bff8a416c4181 Mon Sep 17 00:00:00 2001 From: supermerill Date: Tue, 12 Nov 2024 23:46:32 +0100 Subject: [PATCH 01/13] Fix recreate arc a second time. --- src/libslic3r/ExtrusionEntity.cpp | 10 ++- src/libslic3r/Polyline.cpp | 102 ++++++++++++++++++++---- src/libslic3r/SupportSpotsGenerator.cpp | 6 +- 3 files changed, 95 insertions(+), 23 deletions(-) diff --git a/src/libslic3r/ExtrusionEntity.cpp b/src/libslic3r/ExtrusionEntity.cpp index a32a6f272d8..ff4c55d24fa 100644 --- a/src/libslic3r/ExtrusionEntity.cpp +++ b/src/libslic3r/ExtrusionEntity.cpp @@ -607,9 +607,10 @@ void SimplifyVisitor::use(ExtrusionPath3D& path3D) { } void SimplifyVisitor::use(ExtrusionMultiPath &multipath) { - for (size_t i = 0;i 0 && path.length() < min_path_size) { + assert(!m_last_deleted); path->visit(*this); while (m_last_deleted) { ExtrusionPath *path_merged = nullptr; @@ -620,7 +621,7 @@ void SimplifyVisitor::use(ExtrusionMultiPath &multipath) multipath.paths.erase(multipath.paths.begin() + i); --i; } else if (i + 1 < multipath.size()) { - ExtrusionPath &path_next = multipath.paths[i]; + ExtrusionPath &path_next = multipath.paths[i + 1]; path->polyline.append(path_next.polyline); // erase next multipath.paths.erase(multipath.paths.begin() + i + 1); @@ -651,7 +652,7 @@ void SimplifyVisitor::use(ExtrusionMultiPath3D &multipath3D) multipath3D.paths.erase(multipath3D.paths.begin() + i); --i; } else if (i + 1 < multipath3D.size()) { - ExtrusionPath &path_next = multipath3D.paths[i]; + ExtrusionPath &path_next = multipath3D.paths[i + 1]; path->polyline.append(path_next.polyline); // erase next multipath3D.paths.erase(multipath3D.paths.begin() + i + 1); @@ -682,7 +683,7 @@ void SimplifyVisitor::use(ExtrusionLoop &loop) loop.paths.erase(loop.paths.begin() + i); --i; } else if (i + 1 < loop.paths.size()) { - ExtrusionPath &path_next = loop.paths[i]; + ExtrusionPath &path_next = loop.paths[i + 1]; path->polyline.append(path_next.polyline); // erase next loop.paths.erase(loop.paths.begin() + i + 1); @@ -708,6 +709,7 @@ void SimplifyVisitor::use(ExtrusionEntityCollection &collection) // erase it, without any merge. collection.remove(i); --i; + m_last_deleted = false; } } } diff --git a/src/libslic3r/Polyline.cpp b/src/libslic3r/Polyline.cpp index 40443d431fa..c286bd6418c 100644 --- a/src/libslic3r/Polyline.cpp +++ b/src/libslic3r/Polyline.cpp @@ -671,8 +671,14 @@ void ArcPolyline::append(const ArcPolyline &src) } assert(is_valid()); } -void ArcPolyline::append(ArcPolyline &&src) -{ + +void ArcPolyline::append(ArcPolyline &&src) { + if (src.empty()) { + return; + } + Point pt_back = src.back(); + assert(src.is_valid()); + assert(empty() || is_valid()); this->m_only_strait &= src.m_only_strait; if (m_path.empty()) { m_path = std::move(src.m_path); @@ -681,6 +687,7 @@ void ArcPolyline::append(ArcPolyline &&src) const size_t next_size = m_path.size() + src.m_path.size() - 1; m_path.reserve(next_size); m_path.insert(m_path.end(), std::make_move_iterator(src.m_path.begin() + 1), std::make_move_iterator(src.m_path.end())); + assert(is_valid()); assert(next_size == m_path.size()); } } else { @@ -692,6 +699,7 @@ void ArcPolyline::append(ArcPolyline &&src) assert(next_size == m_path.size()); } assert(is_valid()); + assert(m_path.back().point == pt_back); } void ArcPolyline::translate(const Vector &vector) @@ -855,6 +863,7 @@ void ArcPolyline::clip_end(coordf_t dist) void ArcPolyline::split_at(coordf_t distance, ArcPolyline &p1, ArcPolyline &p2) const { if(m_path.empty()) return; + assert(this->is_valid()); p1.m_path.push_back(m_path.front()); size_t idx = 1; while(distance > 0 && idx < m_path.size()) { @@ -907,6 +916,8 @@ void ArcPolyline::split_at(coordf_t distance, ArcPolyline &p1, ArcPolyline &p2) // increment ++idx; } + assert(p1.is_valid()); + assert(p2.is_valid()); } void ArcPolyline::split_at(Point &point, ArcPolyline &p1, ArcPolyline &p2) const @@ -914,13 +925,14 @@ void ArcPolyline::split_at(Point &point, ArcPolyline &p1, ArcPolyline &p2) const if (this->m_path.empty()) return; - if (this->size() < 2) { + if (this->size() < 2 || this->m_path.back().point.coincides_with_epsilon(point)) { p1 = *this; p2.clear(); return; } + assert(this->is_valid()); - if (this->m_path.front().point == point) { + if (this->m_path.front().point.coincides_with_epsilon(point)) { p1.clear(); p1.append(point); p2 = *this; @@ -1000,6 +1012,51 @@ void ArcPolyline::split_at(Point &point, ArcPolyline &p1, ArcPolyline &p2) const p2.m_only_strait = not_arc(p2); point = result.point; + + if (p1.m_path[p1.size() - 2].point.coincides_with_epsilon(p1.m_path.back().point)) { + if (p1.m_path.back().radius == 0 || + Geometry::ArcWelder::arc_length(p1.m_path[p1.size() - 2].point, p1.m_path.back().point, + p1.m_path.back().radius)) { + // too close to each other + if (p1.m_path.size() == 2) { + if (!p2.empty()) { + // clear first polyline + p2.set_front(p1.front()); + p1.clear(); + } else { + assert(false); + } + } else { + // remove last segment, keep last point + p1.m_path[p1.size() - 2].point = p1.m_path.back().point; + p1.m_path.pop_back(); + } + } + } + if (p2.m_path.front().point.coincides_with_epsilon(p2.m_path[1].point)) { + if (p2.m_path[1].radius == 0 || + Geometry::ArcWelder::arc_length(p2.m_path.front().point, p2.m_path[1].point, + p2.m_path[1].radius)) { + // too close to each other + if (p2.m_path.size() == 2) { + if (!p2.empty()) { + // clear first polyline + p1.set_back(p2.back()); + p2.clear(); + } else { + assert(false); + } + } else { + // remove first segment, keep first point + p2.m_path.erase(p2.m_path.begin() + 1); + } + } + } + + assert(p1.is_valid()); + assert(p2.is_valid()); + assert(p1.front() == this->front()); + assert(p2.back() == this->back()); } bool ArcPolyline::split_at_index(const size_t index, ArcPolyline &p1, ArcPolyline &p2) const @@ -1492,21 +1549,23 @@ void ArcPolyline::make_arc(ArcFittingType with_fitting_arc, coordf_t tolerance, Points pts; Geometry::ArcWelder::Path path; // do only section without arcs - size_t idx_start_path = 0, idx_end_mpath; + size_t idx_end_mpath; + path.push_back(m_path.front()); pts.push_back(m_path.front().point); assert(path.empty() || path.front().radius == 0); for (idx_end_mpath = 1; idx_end_mpath < m_path.size(); ++idx_end_mpath) { - path.push_back(m_path[idx_end_mpath]); if (m_path[idx_end_mpath].radius == 0) { + assert(pts.empty() || !pts.back().coincides_with_epsilon(m_path[idx_end_mpath].point)); pts.push_back(m_path[idx_end_mpath].point); } // if current point is arc, make arc on the strait section before it (if enough points) // or if it's the last point of the path, do it on the last strait section (if enough points) - if (m_path[idx_end_mpath].radius != 0 || idx_end_mpath + 1 >= m_path.size()){ + if (m_path[idx_end_mpath].radius != 0 || idx_end_mpath + 1 >= m_path.size()) { + for(int ii=1;ii 2) { // remove strait sections - path.resize(idx_start_path); assert(path.empty() || path.front().radius == 0); // do arc fitting if (with_fitting_arc == ArcFittingType::Bambu) { @@ -1520,6 +1579,7 @@ void ArcPolyline::make_arc(ArcFittingType with_fitting_arc, coordf_t tolerance, assert(path.empty() || pts[data.start_point_index] == path.back().point); assert(path.empty() || path.back().point.coincides_with_epsilon(pts[data.start_point_index])); for (size_t idx_pts = data.start_point_index + (path.empty() ? 0 : 1); idx_pts < data.end_point_index + 1; ++idx_pts) { + assert(!path.back().point.coincides_with_epsilon(pts[idx_pts])); path.emplace_back(pts[idx_pts], 0, Geometry::ArcWelder::Orientation::Unknown); } } else if (data.path_type == Slic3r::Geometry::EMovePathType::Arc_move_cw || @@ -1531,9 +1591,8 @@ void ArcPolyline::make_arc(ArcFittingType with_fitting_arc, coordf_t tolerance, // now the arc section // point, radius, orientation assert(data.arc_data.radius > 0); - assert(data.arc_data.angle_radians > 0); path.emplace_back(data.arc_data.end_point, - data.arc_data.angle_radians > PI ? -data.arc_data.radius : data.arc_data.radius, + std::abs(data.arc_data.angle_radians) > PI ? -data.arc_data.radius : data.arc_data.radius, data.arc_data.direction == Slic3r::Geometry::ArcDirection::Arc_Dir_CCW ? Geometry::ArcWelder::Orientation::CCW : Geometry::ArcWelder::Orientation::CW); @@ -1575,7 +1634,8 @@ void ArcPolyline::make_arc(ArcFittingType with_fitting_arc, coordf_t tolerance, double ccw_angle2 = ccw_angle; if (ccw_angle < 0) ccw_angle = 2 * PI + ccw_angle; - assert(is_approx(ccw_angle, angle, EPSILON * 10)); + assert(is_approx(ccw_angle, angle, 0.01)); + assert(is_approx(ccw_angle, angle, 0.01)); } prev = seg.point; } @@ -1588,16 +1648,28 @@ void ArcPolyline::make_arc(ArcFittingType with_fitting_arc, coordf_t tolerance, } assert(path.empty() || path.front().radius == 0); } - assert(m_path[idx_end_mpath].point == path.back().point); + //assert(idx_end_mpath > 0 && m_path[idx_end_mpath].point == path.back().point); + } else { + // add strait + assert(path.empty() || path.back().point.coincides_with_epsilon(pts.front())); + for (size_t idx_pts = path.empty() ? 0 : 1; idx_pts < pts.size(); ++idx_pts) { + path.emplace_back(pts[idx_pts], 0, Geometry::ArcWelder::Orientation::Unknown); + } + } + assert(path.back().point == pts.back()); + if (m_path[idx_end_mpath].radius != 0) { + // add arc + path.push_back(m_path[idx_end_mpath]); } - idx_start_path = path.size(); pts.clear(); + pts.push_back(path.back().point); } } // copy new path (may be the same) assert(m_path.front().point == path.front().point && m_path.back().point == path.back().point); m_path = path; this->m_only_strait = not_arc(*this); + assert(is_valid()); } else { auto it_end = douglas_peucker(this->m_path.begin(), this->m_path.end(), this->m_path.begin(), tolerance, [](const Geometry::ArcWelder::Segment &s) { return s.point; }); @@ -1605,8 +1677,8 @@ void ArcPolyline::make_arc(ArcFittingType with_fitting_arc, coordf_t tolerance, size_t new_size = size_t(it_end-this->m_path.begin()); this->m_path.resize(size_t(it_end - this->m_path.begin())); } + assert(is_valid()); } - assert(is_valid()); } bool ArcPolyline::is_valid() const { @@ -1627,7 +1699,7 @@ bool ArcPolyline::is_valid() const { ccw_angle = 2 * PI + ccw_angle; assert(is_approx(ccw_angle, angle, EPSILON)); coordf_t new_length = Slic3r::Geometry::ArcWelder::segment_length(m_path[i - 1], m_path[i]); - assert(is_approx(new_length, m_path[i].length, EPSILON)); + assert(is_approx(new_length, m_path[i].length, SCALED_EPSILON*1.)); //assert(is_approx(coord_t(center.x()), m_path[i].center.x(), SCALED_EPSILON)); //assert(is_approx(coord_t(center.y()), m_path[i].center.y(), SCALED_EPSILON)); //if (first_center == Point(0, 0)) { diff --git a/src/libslic3r/SupportSpotsGenerator.cpp b/src/libslic3r/SupportSpotsGenerator.cpp index 9c6ac9a33eb..2e09bbbf488 100644 --- a/src/libslic3r/SupportSpotsGenerator.cpp +++ b/src/libslic3r/SupportSpotsGenerator.cpp @@ -535,12 +535,10 @@ ObjectPart::ObjectPart( } } void use(const ExtrusionPath &path) override { - assert(!path.as_polyline().has_arc()); - use_polyline(path.as_polyline().to_polyline(), path.width()); + use_polyline(path.as_polyline().to_polyline(path.width()/10), path.width()); } void use(const ExtrusionPath3D &path) override { - assert(!path.as_polyline().has_arc()); - Polyline poly = path.as_polyline().to_polyline(); + Polyline poly = path.as_polyline().to_polyline(path.width()/10); poly.douglas_peucker(SCALED_EPSILON * 2); if(poly.length() > SCALED_EPSILON) use_polyline(std::move(poly), path.width()); From 0f9b5e24c93446e41b8b174506588e4f7d6e74d4 Mon Sep 17 00:00:00 2001 From: supermerill Date: Wed, 13 Nov 2024 13:58:21 +0100 Subject: [PATCH 02/13] strait fix --- src/libslic3r/Polyline.cpp | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/src/libslic3r/Polyline.cpp b/src/libslic3r/Polyline.cpp index c286bd6418c..503d8825050 100644 --- a/src/libslic3r/Polyline.cpp +++ b/src/libslic3r/Polyline.cpp @@ -1271,7 +1271,7 @@ int ArcPolyline::simplify_straits(coordf_t min_tolerance, const int buffer_init) { assert(is_valid()); - + return 0; // incentive to remove odds points float squew[] = { 1, 0.94f, 0.98f, 0.96f, 0.99f, 0.93f, 0.97f, 0.95f}; @@ -1464,15 +1464,15 @@ int ArcPolyline::simplify_straits(coordf_t min_tolerance, assert(idxs[i - 1] < idxs[i]); // remove first point(s) if enough dist - while (buffer_length > min_buffer_length && current_buffer_size > 1) { - idxs.pop_front(); // this erase the idx before the first point. we keep first point idx as a 'previous' - arc.pop_front(); - buffer_length -= line_length.front(); - line_length.pop_front(); - assert(weights.front() > 0); - weights.pop_front(); - --current_buffer_size; - } + //while (buffer_length > min_buffer_length && current_buffer_size > 1) { + // idxs.pop_front(); // this erase the idx before the first point. we keep first point idx as a 'previous' + // arc.pop_front(); + // buffer_length -= line_length.front(); + // line_length.pop_front(); + // assert(weights.front() > 0); + // weights.pop_front(); + // --current_buffer_size; + //} assert(buffer_length <= min_buffer_length || current_buffer_size <= 1); } @@ -1521,14 +1521,14 @@ void ArcPolyline::simplify_straits(const coordf_t min_tolerance, for (size_t idx_pt = 1; idx_pt < this->m_path.size() - 1; ++idx_pt) { // only erase point between two strait segment - if (m_path[idx_pt].radius == 0 && m_path[idx_pt + 1].radius != 0) { + if (m_path[idx_pt].radius == 0 && m_path[idx_pt + 1].radius == 0) { // Get previous & next point Point previous = m_path[idx_pt - 1].point; Point current = m_path[idx_pt].point; Point next = m_path[idx_pt + 1].point; // check deviation coordf_t deviation = Line::distance_to(current, previous, next); - //if devaition is small enough and the distance is too small + //if deviation is small enough and the distance is too small if (deviation < min_tolerance && (min_point_distance_sqr < previous.distance_to_square(current) || min_point_distance_sqr < current.distance_to_square(next))) { From 85de61a7b4cd99b916f3e982bf13aa29d55272aa Mon Sep 17 00:00:00 2001 From: supermerill Date: Wed, 13 Nov 2024 13:43:05 +0100 Subject: [PATCH 03/13] various debug assert --- src/libslic3r/GCode.cpp | 2 +- src/libslic3r/Geometry/ArcWelder.cpp | 35 +++++++++++++++++++++++++--- src/libslic3r/Print.cpp | 1 + 3 files changed, 34 insertions(+), 4 deletions(-) diff --git a/src/libslic3r/GCode.cpp b/src/libslic3r/GCode.cpp index ed0627bce94..75ba619db24 100644 --- a/src/libslic3r/GCode.cpp +++ b/src/libslic3r/GCode.cpp @@ -7225,7 +7225,7 @@ void GCodeGenerator::write_travel_to(std::string &gcode, Polyline& travel, std:: this->writer().set_lift(this->writer().get_position().z() - *m_new_z_target); m_new_z_target.reset(); } - assert(is_approx(this->writer().get_unlifted_position().z(), m_layer->print_z, EPSILON)); + assert(is_approx(this->writer().get_unlifted_position().z(), m_layer->print_z, EPSILON) || comment == "Travel to a Wipe Tower"); } // generate a travel in xyz diff --git a/src/libslic3r/Geometry/ArcWelder.cpp b/src/libslic3r/Geometry/ArcWelder.cpp index fab9b8cded8..77b5e31ec65 100644 --- a/src/libslic3r/Geometry/ArcWelder.cpp +++ b/src/libslic3r/Geometry/ArcWelder.cpp @@ -892,6 +892,14 @@ Path fit_path(const Points &src_in, double tolerance, double fit_circle_percent_ void reverse(Path &path) { +#ifdef _DEBUG + for (size_t i = 1; i < path.size(); i++) { + if (path[i].radius) { + const coordf_t computed_length = segment_length(path[i - 1], path[i]); + assert(is_approx(computed_length, path[i].length, SCALED_EPSILON*1.)); + } + } +#endif if (path.size() > 1) { auto prev = path.begin(); assert(prev->radius == 0); @@ -909,32 +917,53 @@ void reverse(Path &path) path.back().orientation = Orientation::Unknown; std::reverse(path.begin(), path.end()); } +#ifdef _DEBUG for (size_t i = 1; i < path.size(); i++) { - if(path[i].radius) - assert(is_approx(segment_length(path[i-1], path[i]), path[i].length, EPSILON)); + if (path[i].radius) { + const coordf_t computed_length = segment_length(path[i - 1], path[i]); + assert(is_approx(computed_length, path[i].length, SCALED_EPSILON*1.)); + } } +#endif } double clip_start(Path &path, const coordf_t len) { +#ifdef _DEBUG Path ptest1 = path; double rem1 = clip_end(ptest1, len); - + for (size_t i = 1; i < path.size(); i++) { + if (path[i].radius) { + coordf_t new_length = segment_length(path[i - 1], path[i]); + assert(is_approx(new_length, path[i].length, EPSILON)); + } + } +#endif reverse(path); double remaining = clip_end(path, len); reverse(path); +#ifdef _DEBUG + for (size_t i = 1; i < path.size(); i++) { + if (path[i].radius) { + coordf_t new_length = segment_length(path[i - 1], path[i]); + assert(is_approx(new_length, path[i].length, EPSILON)); + } + } +#endif // Return remaining distance to go. return remaining; } double clip_end(Path &path, coordf_t distance) { +#ifdef _DEBUG for (size_t i = 1; i < path.size(); i++) { if (path[i].radius) { coordf_t new_length = segment_length(path[i - 1], path[i]); assert(is_approx(new_length, path[i].length, EPSILON)); } } +#endif while (distance > 0) { const Segment last = path.back(); path.pop_back(); diff --git a/src/libslic3r/Print.cpp b/src/libslic3r/Print.cpp index f4d0c89376d..ef396e98286 100644 --- a/src/libslic3r/Print.cpp +++ b/src/libslic3r/Print.cpp @@ -1208,6 +1208,7 @@ void Print::process() ); // The following step writes to m_shared_regions, it should not run in parallel. + //FIXME: only run it when the support is needed. secondary_status_counter_reset(); for (PrintObject *obj : m_objects) obj->generate_support_spots(); From debba61da06a5db14106d3b3bccd530e8ef35d79 Mon Sep 17 00:00:00 2001 From: supermerill Date: Tue, 29 Oct 2024 16:30:31 +0100 Subject: [PATCH 04/13] bigger wipetower lines --- src/libslic3r/GCode/WipeTower.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libslic3r/GCode/WipeTower.hpp b/src/libslic3r/GCode/WipeTower.hpp index 4093ebe8b88..b2c40ba3028 100644 --- a/src/libslic3r/GCode/WipeTower.hpp +++ b/src/libslic3r/GCode/WipeTower.hpp @@ -274,7 +274,7 @@ class WipeTower SHAPE_REVERSED = -1 }; - const float Width_To_Nozzle_Ratio = 1.25f; // desired line width (oval) in multiples of nozzle diameter - may not be actually neccessary to adjust + const float Width_To_Nozzle_Ratio = 1.75f; // desired line width (oval) in multiples of nozzle diameter - may not be actually neccessary to adjust const float WT_EPSILON = 1e-3f; float filament_area() const { return m_filpar[0].filament_area; // all extruders are assumed to have the same filament diameter at this point From de70519b527484b38e22e54a5ea1bbfe0a41e469 Mon Sep 17 00:00:00 2001 From: supermerill Date: Wed, 13 Nov 2024 14:54:06 +0100 Subject: [PATCH 05/13] fix fillPlanePath (hilbert, etc) --- src/libslic3r/Fill/FillPlanePath.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libslic3r/Fill/FillPlanePath.cpp b/src/libslic3r/Fill/FillPlanePath.cpp index c2790d82271..ece4b2df4c4 100644 --- a/src/libslic3r/Fill/FillPlanePath.cpp +++ b/src/libslic3r/Fill/FillPlanePath.cpp @@ -136,7 +136,7 @@ void FillPlanePath::_fill_surface_single( Polyline mini_polyline; mini_polyline.points.reserve(iend - istart); mini_polyline.points.insert(mini_polyline.points.end(), polyline.points.begin()+istart, polyline.points.begin()+iend); - Polylines polylines = intersection_pl(polylines, expolygon); + Polylines polylines = intersection_pl(mini_polyline, expolygon); if (!polylines.empty()) { assert(!polylines.front().empty()); if (!all_poly.empty() && polylines.front().front().coincides_with_epsilon(all_poly.back().back())) { From 5e4bc5c4bc97cc510368872d81e9cf29ee12bf7c Mon Sep 17 00:00:00 2001 From: supermerill Date: Wed, 20 Nov 2024 00:37:06 +0100 Subject: [PATCH 06/13] fix issue with holes ccw (from simplification that cut a hole in two) --- src/libslic3r/Arrange/ArrangeImpl.hpp | 4 +- src/libslic3r/ClipperUtils.cpp | 2 + src/libslic3r/ExPolygon.cpp | 154 ++++++++++++++++++-- src/libslic3r/ExPolygon.hpp | 15 +- src/libslic3r/Fill/FillPlanePath.cpp | 9 +- src/libslic3r/Fill/FillPlanePath.hpp | 8 +- src/libslic3r/Geometry/ArcWelder.cpp | 2 +- src/libslic3r/Layer.cpp | 11 +- src/libslic3r/LayerRegion.cpp | 4 +- src/libslic3r/MultiMaterialSegmentation.cpp | 4 +- src/libslic3r/PerimeterGenerator.cpp | 12 +- src/libslic3r/Polygon.cpp | 2 + src/libslic3r/ShortestPath.cpp | 10 +- src/libslic3r/SurfaceCollection.cpp | 2 +- 14 files changed, 185 insertions(+), 54 deletions(-) diff --git a/src/libslic3r/Arrange/ArrangeImpl.hpp b/src/libslic3r/Arrange/ArrangeImpl.hpp index 08cc9731905..812f40ea2fb 100644 --- a/src/libslic3r/Arrange/ArrangeImpl.hpp +++ b/src/libslic3r/Arrange/ArrangeImpl.hpp @@ -446,9 +446,9 @@ ArrItem AdvancedItemConverter::get_arritem(const Arrangeable &arrbl, if (simpl_tol > 0.) { - outline = expolygons_simplify(outline, simpl_tol); + expolygons_simplify(outline, simpl_tol); if (!envelope.empty()) - envelope = expolygons_simplify(envelope, simpl_tol); + expolygons_simplify(envelope, simpl_tol); } ArrItem ret; diff --git a/src/libslic3r/ClipperUtils.cpp b/src/libslic3r/ClipperUtils.cpp index b5b24f86350..55a3fcaa85b 100644 --- a/src/libslic3r/ClipperUtils.cpp +++ b/src/libslic3r/ClipperUtils.cpp @@ -211,9 +211,11 @@ static ExPolygons PolyTreeToExPolygons(ClipperLib::PolyTree &&polytree) size_t cnt = expolygons->size(); expolygons->resize(cnt + 1); (*expolygons)[cnt].contour.points = std::move(polynode.Contour); + assert((*expolygons)[cnt].contour.is_counter_clockwise()); (*expolygons)[cnt].holes.resize(polynode.ChildCount()); for (int i = 0; i < polynode.ChildCount(); ++ i) { (*expolygons)[cnt].holes[i].points = std::move(polynode.Childs[i]->Contour); + assert((*expolygons)[cnt].holes[i].is_clockwise()); // Add outer polygons contained by (nested within) holes. for (int j = 0; j < polynode.Childs[i]->ChildCount(); ++ j) PolyTreeToExPolygonsRecursive(std::move(*polynode.Childs[i]->Childs[j]), expolygons); diff --git a/src/libslic3r/ExPolygon.cpp b/src/libslic3r/ExPolygon.cpp index 6e899e4a3a1..9097dc57c23 100644 --- a/src/libslic3r/ExPolygon.cpp +++ b/src/libslic3r/ExPolygon.cpp @@ -183,17 +183,59 @@ bool ExPolygon::overlaps(const ExPolygon &other) const other.contains(this->contour.points.front()); } +// @Deprecated. please don't use it. a simplification can cut a thin isma. void ExPolygon::douglas_peucker(coord_t tolerance) { - contour.douglas_peucker(tolerance); - for (Polygon &hole : holes) - hole.douglas_peucker(tolerance); + assert(false); //deprecated + bool need_union = false; + assert(this->contour.size() < 3 || this->contour.is_counter_clockwise()); + for(auto &hole :this->holes) assert(hole.is_clockwise()); + this->contour.douglas_peucker(tolerance); + for(auto &hole :this->holes) assert(hole.is_clockwise()); + if (this->contour.size() < 3) { + this->clear(); + } else { + if (!this->contour.is_counter_clockwise()) { + this->contour.reverse(); + need_union = true; + } + for(auto &hole :this->holes) assert(hole.is_clockwise()); + for (size_t i_hole = 0; i_hole < this->holes.size(); ++i_hole) { + assert(this->holes[i_hole].size() < 3 || this->holes[i_hole].is_clockwise()); + this->holes[i_hole].douglas_peucker(tolerance); + if (this->holes[i_hole].size() < 3) { + this->holes.erase(this->holes.begin() + i_hole); + --i_hole; + } else { + if (!this->holes[i_hole].is_clockwise()) { + this->holes[i_hole].reverse(); + need_union = true; + } + } + } + } + // do we need to do an union_ex() here? -> it's possible that the new holes cut into the new perimeter, so yes... even if unlikely + + assert_valid(); + if (need_union) { + ExPolygons expolygons = union_ex(expolygons); + assert(expolygons.size() == 1); + if (expolygons.size() > 0) { + //TODO choose biggest + *this = expolygons.front(); + this->douglas_peucker(tolerance); + } else { + clear(); + } + + } + assert_valid(); } void -ExPolygon::simplify_p(coord_t tolerance, Polygons* polygons) const +ExPolygon::simplify_p(coord_t tolerance, Polygons &polygons) const { Polygons pp = this->simplify_p(tolerance); - polygons->insert(polygons->end(), pp.begin(), pp.end()); + polygons.insert(polygons.end(), pp.begin(), pp.end()); } Polygons @@ -223,15 +265,47 @@ ExPolygon::simplify_p(coord_t tolerance) const { Polygon p = this->contour; p.douglas_peucker(tolerance); - if(p.size() > 2) - pp.emplace_back(std::move(p)); + assert(p.is_counter_clockwise()); + if (!p.is_counter_clockwise()) { + p.reverse(); + } + if (p.size() >= 2) { + pp.push_back(std::move(p)); + } } if(pp.empty()) return pp; // holes - for (Polygon p : this->holes) { - p.douglas_peucker(tolerance); - if(p.size() > 2) - pp.emplace_back(std::move(p)); + for (Polygon polygon : this->holes) { + Polygon oldp = polygon; + polygon.douglas_peucker(tolerance); + if (polygon.is_counter_clockwise()) { + + { + static int aodfjiaqsdz = 0; + std::stringstream stri; + stri << "_hourglass_" << (aodfjiaqsdz++) << ".svg"; + SVG svg(stri.str()); + oldp.scale(1000,1000); + polygon.scale(1000,1000); + svg.draw(oldp, "grey"); + svg.draw(oldp.split_at_first_point(), "orange", scale_t(0.05)); + svg.draw(polygon, "black"); + svg.draw(polygon.split_at_first_point(), "red", scale_t(0.04)); + Polygons polys = union_(Polygons{oldp}); + svg.draw(to_polylines(polys), "cyan", scale_t(0.032)); + polys = union_(Polygons{polygon}); + svg.draw(to_polylines(polys), "blue", scale_t(0.025)); + svg.Close(); + } + + assert(false); + } + // if polygon began to be counnter-clockwise, then it means that the fucked up part of the + // hourglass is the only part / dominant part left + if (polygon.is_clockwise() && polygon.size() >= 2) { + // size == 2 => triangle + pp.push_back(std::move(polygon)); + } } // union return simplify_polygons(pp); @@ -240,13 +314,53 @@ ExPolygon::simplify_p(coord_t tolerance) const ExPolygons ExPolygon::simplify(coord_t tolerance) const { - return union_ex(this->simplify_p(tolerance)); + //return union_ex(this->simplify_p(tolerance)); + ExPolygons expolys; + this->simplify(tolerance, expolys); + return expolys; } void -ExPolygon::simplify(coord_t tolerance, ExPolygons* expolygons) const +ExPolygon::simplify(coord_t tolerance, ExPolygons &expolygons) const { - append(*expolygons, this->simplify(tolerance)); + //append(*expolygons, this->simplify(tolerance)); + + bool need_union = false; + assert(this->contour.size() < 3 || this->contour.is_counter_clockwise()); + for(auto &hole :this->holes) assert(hole.is_clockwise()); + expolygons.push_back(*this); + expolygons.back().contour.douglas_peucker(tolerance); + for(auto &hole :expolygons.back().holes) assert(hole.is_clockwise()); + if (expolygons.back().contour.size() < 3) { + expolygons.pop_back(); + } else { + if (!expolygons.back().contour.is_counter_clockwise()) { + expolygons.back().contour.reverse(); + need_union = true; + } + for(auto &hole :expolygons.back().holes) assert(hole.is_clockwise()); + for (size_t i_hole = 0; i_hole < expolygons.back().holes.size(); ++i_hole) { + assert(expolygons.back().holes[i_hole].size() < 3 || expolygons.back().holes[i_hole].is_clockwise()); + expolygons.back().holes[i_hole].douglas_peucker(tolerance); + if (expolygons.back().holes[i_hole].size() < 3) { + expolygons.back().holes.erase(expolygons.back().holes.begin() + i_hole); + --i_hole; + } else { + if (!expolygons.back().holes[i_hole].is_clockwise()) { + expolygons.back().holes[i_hole].reverse(); + need_union = true; + } + } + } + } + // do we need to do an union_ex() here? -> it's possible that the new holes cut into the new perimeter, so yes... even if unlikely + + Slic3r::assert_valid(expolygons); + if (need_union) { + expolygons = union_ex(expolygons); + ensure_valid(expolygons, tolerance); + } + Slic3r::assert_valid(expolygons); } /// remove point that are at SCALED_EPSILON * 2 distance. @@ -448,12 +562,19 @@ bool has_duplicate_points(const ExPolygons &expolys) #endif } -void ensure_valid(ExPolygons &expolygons, coord_t resolution /*= SCALED_EPSILON*/) +void ensure_valid(ExPolygons &expolygons, coord_t resolution /*= SCALED_EPSILON*/) { + expolygons_simplify(expolygons, resolution); +} + +void expolygons_simplify(ExPolygons &expolygons, coord_t resolution) { + for (ExPolygon &poly : expolygons) for(auto &hole :poly.holes) assert(hole.is_clockwise()); bool need_union = false; for (size_t i = 0; i < expolygons.size(); ++i) { assert(expolygons[i].contour.size() < 3 || expolygons[i].contour.is_counter_clockwise()); - expolygons[i].douglas_peucker(resolution); + for(auto &hole :expolygons[i].holes) assert(hole.is_clockwise()); + expolygons[i].contour.douglas_peucker(resolution); + for(auto &hole :expolygons[i].holes) assert(hole.is_clockwise()); if (expolygons[i].contour.size() < 3) { expolygons.erase(expolygons.begin() + i); --i; @@ -462,6 +583,7 @@ void ensure_valid(ExPolygons &expolygons, coord_t resolution /*= SCALED_EPSILON* expolygons[i].contour.reverse(); need_union = true; } + for(auto &hole :expolygons[i].holes) assert(hole.is_clockwise()); for (size_t i_hole = 0; i_hole < expolygons[i].holes.size(); ++i_hole) { assert(expolygons[i].holes[i_hole].size() < 3 || expolygons[i].holes[i_hole].is_clockwise()); expolygons[i].holes[i_hole].douglas_peucker(resolution); diff --git a/src/libslic3r/ExPolygon.hpp b/src/libslic3r/ExPolygon.hpp index dafecda669a..6f8de136871 100644 --- a/src/libslic3r/ExPolygon.hpp +++ b/src/libslic3r/ExPolygon.hpp @@ -78,10 +78,10 @@ class ExPolygon bool overlaps(const ExPolygon &other) const; void douglas_peucker(coord_t tolerance); - void simplify_p(coord_t tolerance, Polygons* polygons) const; + void simplify_p(coord_t tolerance, Polygons &polygons) const; Polygons simplify_p(coord_t tolerance) const; ExPolygons simplify(coord_t tolerance) const; - void simplify(coord_t tolerance, ExPolygons* expolygons) const; + void simplify(coord_t tolerance, ExPolygons &expolygons) const; void remove_point_too_near(const coord_t tolerance); void medial_axis(double max_width, double min_width, ThickPolylines &polylines) const; void medial_axis(double max_width, double min_width, Polylines &polylines) const; @@ -521,14 +521,8 @@ inline bool expolygons_contain(const ExPolygons &expolys, const Point &pt, bool return false; } -inline ExPolygons expolygons_simplify(const ExPolygons &expolys, double tolerance) -{ - ExPolygons out; - out.reserve(expolys.size()); - for (const ExPolygon &exp : expolys) - exp.simplify(tolerance, &out); - return out; -} +// expolygons_simplify will simplify the geometry via douglaspeuker. +void expolygons_simplify(ExPolygons &expolys, coord_t tolerance); // Do expolygons match? If they match, they must have the same topology, // however their contours may be rotated. @@ -545,6 +539,7 @@ bool has_duplicate_points(const ExPolygon &expoly); bool has_duplicate_points(const ExPolygons &expolys); // remove any point that are at epsilon (or resolution) 'distance' (douglas_peuckere algo for now) and all polygons that are too small to be valid +// note: in the future, it may limited to removing points that just to close to other ones. If you want to simplify the geomtry, use expolygons_simplify. void ensure_valid(ExPolygons &expolygons, coord_t resolution = SCALED_EPSILON); ExPolygons ensure_valid(ExPolygons &&expolygons, coord_t resolution = SCALED_EPSILON); ExPolygons ensure_valid(coord_t resolution, ExPolygons &&expolygons); diff --git a/src/libslic3r/Fill/FillPlanePath.cpp b/src/libslic3r/Fill/FillPlanePath.cpp index ece4b2df4c4..0d7ad158a12 100644 --- a/src/libslic3r/Fill/FillPlanePath.cpp +++ b/src/libslic3r/Fill/FillPlanePath.cpp @@ -137,6 +137,7 @@ void FillPlanePath::_fill_surface_single( mini_polyline.points.reserve(iend - istart); mini_polyline.points.insert(mini_polyline.points.end(), polyline.points.begin()+istart, polyline.points.begin()+iend); Polylines polylines = intersection_pl(mini_polyline, expolygon); + ensure_valid(polylines, std::max(SCALED_EPSILON, params.fill_resolution)); if (!polylines.empty()) { assert(!polylines.front().empty()); if (!all_poly.empty() && polylines.front().front().coincides_with_epsilon(all_poly.back().back())) { @@ -166,7 +167,7 @@ void FillPlanePath::_fill_surface_single( // Follow an Archimedean spiral, in polar coordinates: r=a+b\theta template -static void generate_archimedean_chords(coord_t min_x, coord_t min_y, coord_t max_x, coord_t max_y, const double resolution, Output &output) +static void generate_archimedean_chords(coord_t min_x, coord_t min_y, coord_t max_x, coord_t max_y, const coordf_t resolution, Output &output) { // Radius to achieve. coordf_t rmax = std::sqrt(coordf_t(max_x)*coordf_t(max_x)+coordf_t(max_y)*coordf_t(max_y)) * std::sqrt(2.) + 1.5; @@ -187,7 +188,7 @@ static void generate_archimedean_chords(coord_t min_x, coord_t min_y, coord_t ma } } -void FillArchimedeanChords::generate(coord_t min_x, coord_t min_y, coord_t max_x, coord_t max_y, const double resolution, InfillPolylineOutput &output) const +void FillArchimedeanChords::generate(coord_t min_x, coord_t min_y, coord_t max_x, coord_t max_y, const coordf_t resolution, InfillPolylineOutput &output) const { if (output.clips()) generate_archimedean_chords(min_x, min_y, max_x, max_y, resolution, static_cast(output)); @@ -262,7 +263,7 @@ static void generate_hilbert_curve(coord_t min_x, coord_t min_y, coord_t max_x, } } -void FillHilbertCurve::generate(coord_t min_x, coord_t min_y, coord_t max_x, coord_t max_y, const double /* resolution */, InfillPolylineOutput &output) const +void FillHilbertCurve::generate(coord_t min_x, coord_t min_y, coord_t max_x, coord_t max_y, const coordf_t /* resolution */, InfillPolylineOutput &output) const { if (output.clips()) generate_hilbert_curve(min_x, min_y, max_x, max_y, static_cast(output)); @@ -302,7 +303,7 @@ static void generate_octagram_spiral(coord_t min_x, coord_t min_y, coord_t max_x } } -void FillOctagramSpiral::generate(coord_t min_x, coord_t min_y, coord_t max_x, coord_t max_y, const double /* resolution */, InfillPolylineOutput &output) const +void FillOctagramSpiral::generate(coord_t min_x, coord_t min_y, coord_t max_x, coord_t max_y, const coordf_t /* resolution */, InfillPolylineOutput &output) const { if (output.clips()) generate_octagram_spiral(min_x, min_y, max_x, max_y, static_cast(output)); diff --git a/src/libslic3r/Fill/FillPlanePath.hpp b/src/libslic3r/Fill/FillPlanePath.hpp index 9bd3b0f6e32..bbbc890f884 100644 --- a/src/libslic3r/Fill/FillPlanePath.hpp +++ b/src/libslic3r/Fill/FillPlanePath.hpp @@ -59,7 +59,7 @@ class FillPlanePath : public Fill double m_scale_out; }; - virtual void generate(coord_t min_x, coord_t min_y, coord_t max_x, coord_t max_y, const double resolution, InfillPolylineOutput &output) const = 0; + virtual void generate(coord_t min_x, coord_t min_y, coord_t max_x, coord_t max_y, const coordf_t resolution, InfillPolylineOutput &output) const = 0; }; class FillArchimedeanChords : public FillPlanePath @@ -70,7 +70,7 @@ class FillArchimedeanChords : public FillPlanePath protected: bool centered() const override { return true; } - void generate(coord_t min_x, coord_t min_y, coord_t max_x, coord_t max_y, const double resolution, InfillPolylineOutput &output) const override; + void generate(coord_t min_x, coord_t min_y, coord_t max_x, coord_t max_y, const coordf_t resolution, InfillPolylineOutput &output) const override; }; class FillHilbertCurve : public FillPlanePath @@ -81,7 +81,7 @@ class FillHilbertCurve : public FillPlanePath protected: bool centered() const override { return false; } - void generate(coord_t min_x, coord_t min_y, coord_t max_x, coord_t max_y, const double resolution, InfillPolylineOutput &output) const override; + void generate(coord_t min_x, coord_t min_y, coord_t max_x, coord_t max_y, const coordf_t resolution, InfillPolylineOutput &output) const override; }; class FillOctagramSpiral : public FillPlanePath @@ -92,7 +92,7 @@ class FillOctagramSpiral : public FillPlanePath protected: bool centered() const override { return true; } - void generate(coord_t min_x, coord_t min_y, coord_t max_x, coord_t max_y, const double resolution, InfillPolylineOutput &output) const override; + void generate(coord_t min_x, coord_t min_y, coord_t max_x, coord_t max_y, const coordf_t resolution, InfillPolylineOutput &output) const override; }; } // namespace Slic3r diff --git a/src/libslic3r/Geometry/ArcWelder.cpp b/src/libslic3r/Geometry/ArcWelder.cpp index 77b5e31ec65..10b0cfe6206 100644 --- a/src/libslic3r/Geometry/ArcWelder.cpp +++ b/src/libslic3r/Geometry/ArcWelder.cpp @@ -593,7 +593,7 @@ static inline std::optional try_create_arc_impl( double ccw_angle2 = ccw_angle; if (ccw_angle < 0) ccw_angle = 2 * PI + ccw_angle; - assert(is_approx(ccw_angle, angle_test, EPSILON * 10)); + assert(is_approx(ccw_angle, angle_test, 0.01)); #endif } return ret_arc; diff --git a/src/libslic3r/Layer.cpp b/src/libslic3r/Layer.cpp index 785d2f9d89d..9579ca22d2e 100644 --- a/src/libslic3r/Layer.cpp +++ b/src/libslic3r/Layer.cpp @@ -57,11 +57,14 @@ void Layer::make_slices() // optimization: if we only have one region, take its slices slices = to_expolygons(m_regions.front()->slices().surfaces); } else { - Polygons slices_p; - for (LayerRegion *layerm : m_regions) - polygons_append(slices_p, to_polygons(layerm->slices().surfaces)); - slices = union_safety_offset_ex(slices_p); + ExPolygons slices_exp; + for (LayerRegion *layerm : m_regions) { + for (const Surface &srf : layerm->slices().surfaces) srf.expolygon.assert_valid(); + append(slices_exp, to_expolygons(layerm->slices().surfaces)); + } + slices = union_safety_offset_ex(slices_exp); } + for (ExPolygon &poly : slices) for(auto &hole :poly.holes) assert(hole.is_clockwise()); ensure_valid(slices, std::max(scale_t(this->object()->print()->config().resolution), SCALED_EPSILON)); for (ExPolygon &poly : slices) poly.assert_valid(); // lslices are sorted by topological order from outside to inside from the clipper union used above diff --git a/src/libslic3r/LayerRegion.cpp b/src/libslic3r/LayerRegion.cpp index e8dea25e097..9e962b015d0 100644 --- a/src/libslic3r/LayerRegion.cpp +++ b/src/libslic3r/LayerRegion.cpp @@ -105,8 +105,8 @@ void LayerRegion::slices_to_fill_surfaces_clipped(coord_t opening_offset) for (auto const& [srf_type, expoly] : polygons_by_surface) { if (!expoly.empty()) for (ExPolygon& expoly_to_test : intersection_ex(expoly, this->fill_expolygons())) { - expoly_to_test.douglas_peucker(std::max(SCALED_EPSILON, scale_t(this->layer()->object()->print()->config().resolution.value))); - if (!opening_ex({ expoly_to_test }, opening_offset).empty()) { + ExPolygons expolys_to_test = expoly_to_test.simplify(std::max(SCALED_EPSILON, scale_t(this->layer()->object()->print()->config().resolution.value))); + if (!opening_ex(expolys_to_test, opening_offset).empty()) { expoly_to_test.assert_valid(); this->m_fill_surfaces.append({ expoly_to_test }, srf_type); } diff --git a/src/libslic3r/MultiMaterialSegmentation.cpp b/src/libslic3r/MultiMaterialSegmentation.cpp index 9f8d29ce5ce..4ce88163fec 100644 --- a/src/libslic3r/MultiMaterialSegmentation.cpp +++ b/src/libslic3r/MultiMaterialSegmentation.cpp @@ -1277,7 +1277,9 @@ std::vector> multi_material_segmentation_by_painting(con // Such close points sometimes caused that the Voronoi diagram has self-intersecting edges around these vertices. // This consequently leads to issues with the extraction of colored segments by function extract_colored_segments. // Calling expolygons_simplify fixed these issues. - input_expolygons[layer_idx] = remove_duplicates(expolygons_simplify(offset_ex(ex_polygons, -10.f * float(SCALED_EPSILON)), 5 * SCALED_EPSILON), scaled(0.01), PI/6); + input_expolygons[layer_idx] = offset_ex(ex_polygons, -10.f * float(SCALED_EPSILON)); + expolygons_simplify(input_expolygons[layer_idx], 5 * SCALED_EPSILON); + input_expolygons[layer_idx] = remove_duplicates(input_expolygons[layer_idx], scaled(0.01), PI/6); #ifdef MM_SEGMENTATION_DEBUG_INPUT export_processed_input_expolygons_to_svg(debug_out_path("mm-input-%d-%d.svg", layer_idx, iRun), layers[layer_idx]->regions(), input_expolygons[layer_idx]); diff --git a/src/libslic3r/PerimeterGenerator.cpp b/src/libslic3r/PerimeterGenerator.cpp index 6a50cea02ea..78d3ceae196 100644 --- a/src/libslic3r/PerimeterGenerator.cpp +++ b/src/libslic3r/PerimeterGenerator.cpp @@ -3308,7 +3308,7 @@ void PerimeterGenerator::process(// Input: assert_valid(*lower_slices); if (get_resolution(0, false, &srf_to_use) < min_feature / 2) { for (const ExPolygon& expoly : *lower_slices) { - expoly.simplify(min_feature, &simplified_storage); + expoly.simplify(min_feature, simplified_storage); } if (!simplified_storage.empty()) { simplified = &simplified_storage; @@ -3463,7 +3463,7 @@ void PerimeterGenerator::process(// Input: Polygons not_filled_p; coord_t scaled_resolution_infill = scale_t(std::max(params.print_config.resolution.value, params.print_config.resolution_internal / 4)); for (const ExPolygon& ex : surface_process_result.inner_perimeter) - ex.simplify_p(scaled_resolution_infill, ¬_filled_p); + ex.simplify_p(scaled_resolution_infill, not_filled_p); ExPolygons not_filled_exp = union_ex(not_filled_p); // collapse too narrow infill areas coord_t min_perimeter_infill_spacing = (coord_t)(params.get_solid_infill_spacing() * (1. - INSET_OVERLAP_TOLERANCE)); @@ -3740,7 +3740,7 @@ void PerimeterGenerator::processs_no_bridge(const Parameters params, Surfaces& a //simplify to avoid most of artefacts from printing lines. ExPolygons bridgeable_simplified; for (ExPolygon& poly : bridgeable) { - poly.simplify(params.get_perimeter_spacing(), &bridgeable_simplified); + poly.simplify(params.get_perimeter_spacing(), bridgeable_simplified); } bridgeable_simplified = offset2_ex(bridgeable_simplified, -params.get_ext_perimeter_width(), params.get_ext_perimeter_width()); //bridgeable_simplified = intersection_ex(bridgeable_simplified, unsupported_filtered); @@ -4147,7 +4147,7 @@ ProcessSurfaceResult PerimeterGenerator::process_classic(const Parameters & //simplify to avoid most of artefacts from printing lines. ExPolygons bridgeable_simplified; for (const ExPolygon& poly : bridgeable) { - poly.simplify(params.get_perimeter_spacing() / 2, &bridgeable_simplified); + poly.simplify(params.get_perimeter_spacing() / 2, bridgeable_simplified); } //offset by perimeter spacing because the simplify may have reduced it a bit. @@ -4359,7 +4359,7 @@ ProcessSurfaceResult PerimeterGenerator::process_classic(const Parameters & resolution = get_resolution(1, false, &surface); ExPolygons next_onion_temp; for (ExPolygon& exp : next_onion) - exp.simplify((resolution < SCALED_EPSILON ? SCALED_EPSILON : resolution), &next_onion_temp); + exp.simplify((resolution < SCALED_EPSILON ? SCALED_EPSILON : resolution), next_onion_temp); //mask next_onion = intersection_ex(next_onion_temp, last); } @@ -4577,7 +4577,7 @@ ProcessSurfaceResult PerimeterGenerator::process_classic(const Parameters & resolution = get_resolution(perimeter_idx + 1, false, &surface); last.clear(); for (ExPolygon &exp : next_onion) { - exp.simplify((resolution < SCALED_EPSILON ? SCALED_EPSILON : resolution), &last); + exp.simplify((resolution < SCALED_EPSILON ? SCALED_EPSILON : resolution), last); } assert_check_polygons(to_polygons(last)); diff --git a/src/libslic3r/Polygon.cpp b/src/libslic3r/Polygon.cpp index 0feb74d2925..098b7cf4200 100644 --- a/src/libslic3r/Polygon.cpp +++ b/src/libslic3r/Polygon.cpp @@ -60,6 +60,7 @@ Polygon::split_at_index(size_t index) const double Polygon::area(const Points &points) { + // Better than ClipperLib::Area(this->points); ? double a = 0.; if (points.size() >= 3) { Vec2d p1 = points.back().cast(); @@ -69,6 +70,7 @@ double Polygon::area(const Points &points) p1 = p2; } } + assert(is_approx(ClipperLib::Area(points), 0.5 * a, SCALED_EPSILON * 1.)); return 0.5 * a; } diff --git a/src/libslic3r/ShortestPath.cpp b/src/libslic3r/ShortestPath.cpp index 79dc262a2a1..3df24445aa4 100644 --- a/src/libslic3r/ShortestPath.cpp +++ b/src/libslic3r/ShortestPath.cpp @@ -2055,6 +2055,7 @@ Polylines chain_polylines(Polylines &&polylines, const Point *start_near) ++ iRun; svg_draw_polyline_chain("chain_polylines-initial", iRun, polylines); #endif /* DEBUG_SVG_OUTPUT */ + assert_valid(polylines); Polylines out; if (! polylines.empty()) { @@ -2062,9 +2063,12 @@ Polylines chain_polylines(Polylines &&polylines, const Point *start_near) std::vector> ordered = chain_segments_greedy2(segment_end_point, polylines.size(), start_near); out.reserve(polylines.size()); for (auto &segment_and_reversal : ordered) { - out.emplace_back(std::move(polylines[segment_and_reversal.first])); - if (segment_and_reversal.second) - out.back().reverse(); + if (!polylines[segment_and_reversal.first].empty()) { + out.emplace_back(std::move(polylines[segment_and_reversal.first])); + if (segment_and_reversal.second) { + out.back().reverse(); + } + } } if (out.size() > 1 && start_near == nullptr) { improve_ordering_by_two_exchanges_with_segment_flipping(out, start_near != nullptr); diff --git a/src/libslic3r/SurfaceCollection.cpp b/src/libslic3r/SurfaceCollection.cpp index 4766a36b361..618d85bd5c0 100644 --- a/src/libslic3r/SurfaceCollection.cpp +++ b/src/libslic3r/SurfaceCollection.cpp @@ -17,7 +17,7 @@ void SurfaceCollection::simplify(double tolerance) Surfaces ss; for (Surfaces::const_iterator it_s = this->surfaces.begin(); it_s != this->surfaces.end(); ++it_s) { ExPolygons expp; - it_s->expolygon.simplify(tolerance, &expp); + it_s->expolygon.simplify(tolerance, expp); for (ExPolygons::const_iterator it_e = expp.begin(); it_e != expp.end(); ++it_e) { Surface s = *it_s; s.expolygon = *it_e; From e07802b434f9a99fd589bf5df20a2a4f2919d196 Mon Sep 17 00:00:00 2001 From: supermerill Date: Wed, 20 Nov 2024 01:01:43 +0100 Subject: [PATCH 07/13] resolution --- src/libslic3r/Fill/FillPlanePath.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/libslic3r/Fill/FillPlanePath.cpp b/src/libslic3r/Fill/FillPlanePath.cpp index 0d7ad158a12..ec0f322db64 100644 --- a/src/libslic3r/Fill/FillPlanePath.cpp +++ b/src/libslic3r/Fill/FillPlanePath.cpp @@ -142,6 +142,7 @@ void FillPlanePath::_fill_surface_single( assert(!polylines.front().empty()); if (!all_poly.empty() && polylines.front().front().coincides_with_epsilon(all_poly.back().back())) { // it continue the last polyline, so just append to it + all_poly.back().points.pop_back(); append(all_poly.back().points, std::move(polylines.front().points)); //append other polylines if (polylines.size() > 1) { From 765e844f953d310a794d3a95517a5467e993412e Mon Sep 17 00:00:00 2001 From: supermerill Date: Sat, 23 Nov 2024 17:28:34 +0100 Subject: [PATCH 08/13] fix 5e4b --- src/libslic3r/ExPolygon.cpp | 2 +- src/libslic3r/Surface.cpp | 17 ++++++++++------- 2 files changed, 11 insertions(+), 8 deletions(-) diff --git a/src/libslic3r/ExPolygon.cpp b/src/libslic3r/ExPolygon.cpp index 9097dc57c23..b315a45663c 100644 --- a/src/libslic3r/ExPolygon.cpp +++ b/src/libslic3r/ExPolygon.cpp @@ -576,7 +576,7 @@ void expolygons_simplify(ExPolygons &expolygons, coord_t resolution) expolygons[i].contour.douglas_peucker(resolution); for(auto &hole :expolygons[i].holes) assert(hole.is_clockwise()); if (expolygons[i].contour.size() < 3) { - expolygons.erase(expolygons.begin() + i); + expolygons.erase(expolygons.begin() + i); --i; } else { if (!expolygons[i].contour.is_counter_clockwise()) { diff --git a/src/libslic3r/Surface.cpp b/src/libslic3r/Surface.cpp index 8ed8cf421f7..2f32eae6a83 100644 --- a/src/libslic3r/Surface.cpp +++ b/src/libslic3r/Surface.cpp @@ -94,16 +94,19 @@ BoundingBox get_extents(const SurfacesConstPtr &surfaces) void ensure_valid(Surfaces &surfaces, coord_t resolution /*= SCALED_EPSILON*/) { for (size_t i = 0; i < surfaces.size(); ++i) { - surfaces[i].expolygon.douglas_peucker(resolution); - if (surfaces[i].expolygon.contour.size() < 3) { + ExPolygons to_simplify = {surfaces[i].expolygon}; + ensure_valid(to_simplify, resolution); + if (to_simplify.empty()) { surfaces.erase(surfaces.begin() + i); --i; + } else if (to_simplify.size() == 1) { + surfaces[i].expolygon = to_simplify.front(); } else { - for (size_t i_hole = 0; i_hole < surfaces[i].expolygon.holes.size(); ++i_hole) { - if (surfaces[i].expolygon.holes[i_hole].size() < 3) { - surfaces[i].expolygon.holes.erase(surfaces[i].expolygon.holes.begin() + i_hole); - --i_hole; - } + surfaces[i].expolygon = to_simplify.front(); + for (size_t idx = 1; idx < to_simplify.size(); idx++) { + surfaces.insert(surfaces.begin() + i + idx, + Surface{surfaces[i], to_simplify[idx]}); + i++; } } } From 409f7696a950a87fde5738f4277f14ad8f3bd936 Mon Sep 17 00:00:00 2001 From: supermerill Date: Sat, 23 Nov 2024 17:28:47 +0100 Subject: [PATCH 09/13] resolution --- src/libslic3r/PrintObjectSlice.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/libslic3r/PrintObjectSlice.cpp b/src/libslic3r/PrintObjectSlice.cpp index 7fd0bf232bf..d9f832d28f4 100644 --- a/src/libslic3r/PrintObjectSlice.cpp +++ b/src/libslic3r/PrintObjectSlice.cpp @@ -1271,7 +1271,7 @@ Polygon _smooth_curve(Polygon& p, double max_angle, double min_angle_convex, dou pout.points.push_back(p[idx]); //get angles //double angle1 = p[idx].ccw_angle(p.points[idx - 1], p.points[idx + 1]); - assert(ccw_angle_old_test(p[idx], p.points[idx - 1], p.points[idx + 1]) == abs_angle(angle_ccw( p.points[idx - 1] - p[idx],p.points[idx + 1] - p[idx]))); + assert(is_approx(ccw_angle_old_test(p[idx], p.points[idx - 1], p.points[idx + 1]), abs_angle(angle_ccw( p.points[idx - 1] - p[idx],p.points[idx + 1] - p[idx])),EPSILON)); double angle1 = abs_angle(angle_ccw( p.points[idx - 1] - p[idx],p.points[idx + 1] - p[idx])); bool angle1_concave = true; if (angle1 > PI) { @@ -1279,7 +1279,7 @@ Polygon _smooth_curve(Polygon& p, double max_angle, double min_angle_convex, dou angle1_concave = false; } //double angle2 = p[idx + 1].ccw_angle(p.points[idx], p.points[idx + 2]); - assert(ccw_angle_old_test(p[idx + 1], p.points[idx], p.points[idx + 2]) == abs_angle(angle_ccw( p.points[idx] - p[idx + 1],p.points[idx + 2] - p[idx + 1]))); + assert(is_approx(ccw_angle_old_test(p[idx + 1], p.points[idx], p.points[idx + 2]), abs_angle(angle_ccw( p.points[idx] - p[idx + 1],p.points[idx + 2] - p[idx + 1])),EPSILON)); double angle2 = abs_angle(angle_ccw( p.points[idx] - p[idx + 1],p.points[idx + 2] - p[idx + 1])); bool angle2_concave = true; if (angle2 > PI) { From a58ffc31fcd3c52ea62ac17abeda9a55b5dcfd29 Mon Sep 17 00:00:00 2001 From: supermerill Date: Sat, 23 Nov 2024 17:30:07 +0100 Subject: [PATCH 10/13] fix commit "fix fill ordering by island" (e1dc8) --- src/libslic3r/Fill/Fill.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libslic3r/Fill/Fill.cpp b/src/libslic3r/Fill/Fill.cpp index 351cd2fc6a2..9246e331e3f 100644 --- a/src/libslic3r/Fill/Fill.cpp +++ b/src/libslic3r/Fill/Fill.cpp @@ -880,7 +880,7 @@ void Layer::make_fills(FillAdaptive::Octree* adaptive_fill_octree, FillAdaptive: island = &this->lslices_ex.front().islands.front(); } std::vector> &fillsbypriority = island_2_fillsby_priority[island]; - while(fillsbypriority.size() < fills_by_priority[priority].size()) + while(fillsbypriority.size() <= fills_by_priority[priority].size()) fillsbypriority.emplace_back(); if (!fills_by_priority[priority][fill_idx]->empty()) { fillsbypriority[priority].push_back(fills_by_priority[priority][fill_idx]); From 9515ebeb5344acdfb7b377d02fa9d2bdfbfa458c Mon Sep 17 00:00:00 2001 From: supermerill Date: Sun, 24 Nov 2024 14:57:22 +0100 Subject: [PATCH 11/13] ensure_vertical_shell_thickness: * get ps2.9 partial & disabled option. * restore 2.5 process_external_surfaces before discover_vertical_shells * 2.7 and 2.9 option are still with discover_vertical_shells before process_external_surfaces, but this break the anchor margins. * the solid_over_perimeters can affect the anchor where the perimeter are well above -> unwanted. * this is still wip, there is at least a week of investigatino to understand & document how it work. --- resources/ui_layout/default/print.ui | 55 +- src/libslic3r/Algorithm/RegionExpansion.cpp | 10 +- src/libslic3r/Algorithm/RegionExpansion.hpp | 4 +- src/libslic3r/Layer.cpp | 4 + src/libslic3r/Layer.hpp | 1 + src/libslic3r/LayerRegion.cpp | 232 ++++++- src/libslic3r/Preset.cpp | 1 + src/libslic3r/PresetBundle.cpp | 2 + src/libslic3r/Print.hpp | 2 +- src/libslic3r/PrintConfig.cpp | 42 +- src/libslic3r/PrintConfig.hpp | 8 + src/libslic3r/PrintObject.cpp | 713 +++++++++++++++++--- src/slic3r/GUI/ConfigManipulation.cpp | 7 +- 13 files changed, 940 insertions(+), 141 deletions(-) diff --git a/resources/ui_layout/default/print.ui b/resources/ui_layout/default/print.ui index ac4c74540f6..c7e925336b5 100644 --- a/resources/ui_layout/default/print.ui +++ b/resources/ui_layout/default/print.ui @@ -3,10 +3,10 @@ page:Perimeters & Shell:shell group:Vertical shells line:Perimeters setting:label$Contour:width$6:sidetext_width$10:perimeters - setting:label$Holes:width$6:perimeters_hole + setting:label$Holes:width$6:perimeters_hole end_line setting:tags$Simple$Expert$SuSi:script:float:depends$perimeter_spacing$external_perimeter_spacing:label$Wall Thickness:tooltip$Change the perimeter extrusion widths to ensure that there is an exact number of perimeters for this wall thickness value. It won't put the perimeter width below the nozzle diameter, and up to double.\nNote that the value displayed is just a view of the current perimeter thickness, like the info text below. The number of perimeters used to compute this value is one loop, or the custom variable 'wall_thickness_lines' (advanced mode) if defined.\nIf the value is too low, it will revert the widths to the saved value.\nIf the value is set to 0, it will show 0.:s_wall_thickness - setting:perimeter_generator + setting:perimeter_generator setting:spiral_vase recommended_thin_wall_thickness_description group:Horizontal shells @@ -18,9 +18,10 @@ group:Horizontal shells setting:top_solid_min_thickness setting:bottom_solid_min_thickness end_line - top_bottom_shell_thickness_explanation setting:solid_over_perimeters + setting:ensure_vertical_shell_thickness setting:enforce_full_fill_volume + top_bottom_shell_thickness_explanation group:Infill line:Sparse infill pattern setting:tags$Simple:label_width$0:label$_:fill_pattern @@ -59,7 +60,7 @@ group:Avoid crossing perimeters end_line line:Modifiers setting:label_width$12:label$Not on first layer:avoid_crossing_not_first_layer - setting:avoid_crossing_top + setting:avoid_crossing_top end_line setting:avoid_crossing_curled_overhangs group:label_width$12:Overhangs @@ -142,7 +143,7 @@ group:Filtering setting:resolution_internal setting:model_precision setting:slice_closing_radius - setting:bridge_precision + setting:bridge_precision # gcode_resolution group:Modifying slices line:Curve smoothing @@ -169,11 +170,11 @@ group:Modifying slices setting:sidetext_width$5:hole_to_polyhole_threshold setting:hole_to_polyhole_twisted end_line - line:Overhangs cut - setting:overhangs_max_slope - setting:overhangs_bridge_threshold - setting:overhangs_bridge_upper_layers - end_line + line:Overhangs cut + setting:overhangs_max_slope + setting:overhangs_bridge_threshold + setting:overhangs_bridge_upper_layers + end_line group:Other setting:slicing_mode setting:allow_empty_layers @@ -184,7 +185,7 @@ group:title_width$0:Infill setting:tags$Advanced$Expert$Prusa:label_left:label_width$6:label$Sparse:width$8:sidetext_width$1:fill_density setting:tags$Advanced$Expert$Prusa:label_width$0:label$_:fill_pattern setting:label$_:width$18:infill_connection - setting:label$Aligned:fill_aligned_z + setting:label$Aligned:fill_aligned_z end_line line:_ setting:label$Connection length:label_width$25:sidetext_width$7:width$12:infill_anchor_max @@ -226,10 +227,10 @@ group:sidetext_width$5:Infill angle group:sidetext_width$5:Advanced setting:solid_infill_every_layers line:Solid infill if area below - setting:label$From region:solid_infill_below_area - setting:label$From the whole layer:solid_infill_below_layer_area + setting:label$From region:solid_infill_below_area + setting:label$From the whole layer:solid_infill_below_layer_area end_line - setting:solid_infill_below_width + setting:solid_infill_below_width line:Anchor solid infill by X mm setting:label_width$8:width$5:external_infill_margin setting:label_width$6:width$5:bridged_infill_margin @@ -348,12 +349,12 @@ group:Organic supports line:Branch diameter setting:label$_:support_tree_branch_diameter setting:label$Angle:support_tree_branch_diameter_angle - setting:support_tree_tip_diameter + setting:support_tree_tip_diameter end_line - setting:support_tree_branch_diameter_double_wall + setting:support_tree_branch_diameter_double_wall line:Branch - setting:label$Distance:support_tree_branch_distance - setting:label$Density:support_tree_top_rate + setting:label$Distance:support_tree_branch_distance + setting:label$Density:support_tree_top_rate end_line page:Speed:time @@ -374,9 +375,9 @@ group:label_width$8:sidetext_width$7:Speed for print moves line:Bridge speed setting:width$4:bridge_speed setting:width$4:internal_bridge_speed - line:Overhangs speed + line:Overhangs speed setting:width$4:overhangs_speed - setting:overhangs_dynamic_speed + setting:overhangs_dynamic_speed line:Gap fill speed setting:width$4:label$Maximum speed:gap_fill_speed setting:width$4:label$Cap with:sidetext$% of perimeter flow:sidetext_width$20:gap_fill_flow_match_perimeter @@ -448,7 +449,7 @@ group:Extrusion width line:perimeter setting:sidetext_width$10:label$width:perimeter_extrusion_width setting:sidetext_width$10:label_width$15:label$spacing:perimeter_extrusion_spacing - setting:sidetext_width$10:label_width$15:label$even layers:perimeter_extrusion_change_odd_layers + setting:sidetext_width$10:label_width$15:label$even layers:perimeter_extrusion_change_odd_layers end_line line:external perimeter setting:sidetext_width$10:label$width:external_perimeter_extrusion_width @@ -490,9 +491,9 @@ group:Overlap setting:label_width$7:label$External:external_perimeter_overlap setting:label_width$7:label$Gap Fill:gap_fill_overlap end_line - line:Solid infill ovelrap - setting:label$Inside:width$4:solid_infill_overlap - setting:label$Top:width$4:top_solid_infill_overlap + line:Solid infill ovelrap + setting:label$Inside:width$4:solid_infill_overlap + setting:label$Top:width$4:top_solid_infill_overlap end_line line:Bridge lines density setting:label_width$7:bridge_overlap_min @@ -541,8 +542,8 @@ group:Wipe tower setting:wipe_tower_extra_spacing setting:wipe_tower_no_sparse_layers line:Priming - setting:single_extruder_multi_material_priming - setting:priming_position + setting:single_extruder_multi_material_priming + setting:priming_position end_line group:Advanced setting:interface_shells @@ -568,7 +569,7 @@ group:Output file setting:gcode_label_objects setting:full_width:output_filename_format group:Other - gcode_substitutions + gcode_substitutions group:Post-processing script setting:full_width:height$5:post_process post_process_explanation diff --git a/src/libslic3r/Algorithm/RegionExpansion.cpp b/src/libslic3r/Algorithm/RegionExpansion.cpp index 17e8bd9a72a..b58fed6768d 100644 --- a/src/libslic3r/Algorithm/RegionExpansion.cpp +++ b/src/libslic3r/Algorithm/RegionExpansion.cpp @@ -30,15 +30,17 @@ inline double clipper_round_offset_error(double offset, double arc_tolerance) RegionExpansionParameters RegionExpansionParameters::build( // Scaled expansion value - float full_expansion, + coord_t _full_expansion, // Expand by waves of expansion_step size (expansion_step is scaled). - float expansion_step, + coord_t _expansion_step, // Don't take more than max_nr_steps for small expansion_step. size_t max_nr_expansion_steps) { - assert(full_expansion > 0); - assert(expansion_step > 0); + assert(_full_expansion > 0); + assert(_expansion_step > 0); assert(max_nr_expansion_steps > 0); + float full_expansion = float(_full_expansion); + float expansion_step = float(_expansion_step); RegionExpansionParameters out; // Initial expansion of src to make the source regions intersect with boundary regions just a bit. diff --git a/src/libslic3r/Algorithm/RegionExpansion.hpp b/src/libslic3r/Algorithm/RegionExpansion.hpp index 62e32526aa4..407ac1fdda2 100644 --- a/src/libslic3r/Algorithm/RegionExpansion.hpp +++ b/src/libslic3r/Algorithm/RegionExpansion.hpp @@ -33,9 +33,9 @@ struct RegionExpansionParameters static RegionExpansionParameters build( // Scaled expansion value - float full_expansion, + coord_t full_expansion, // Expand by waves of expansion_step size (expansion_step is scaled). - float expansion_step, + coord_t expansion_step, // Don't take more than max_nr_steps for small expansion_step. size_t max_nr_expansion_steps); }; diff --git a/src/libslic3r/Layer.cpp b/src/libslic3r/Layer.cpp index 9579ca22d2e..571749cabd5 100644 --- a/src/libslic3r/Layer.cpp +++ b/src/libslic3r/Layer.cpp @@ -596,6 +596,10 @@ void Layer::restore_untyped_slices() void Layer::restore_untyped_slices_no_extra_perimeters() { restore_untyped_slices(); + for (LayerRegion *lr : m_regions) { + lr->set_fill_surfaces().clear(); + } + // if (layer_needs_raw_backup(this)) { // for (LayerRegion *layerm : m_regions) // if (! layerm->region().config().extra_perimeters.value) diff --git a/src/libslic3r/Layer.hpp b/src/libslic3r/Layer.hpp index ba6ded723d5..027014310ce 100644 --- a/src/libslic3r/Layer.hpp +++ b/src/libslic3r/Layer.hpp @@ -175,6 +175,7 @@ class LayerRegion std::vector &fill_expolygons_ranges); void make_milling_post_process(const SurfaceCollection& slices); void process_external_surfaces(const Layer *lower_layer, const Polygons *lower_layer_covered); + void process_external_surfaces_old(const Layer *lower_layer, const Polygons *lower_layer_covered); double infill_area_threshold() const; // Trim surfaces by trimming polygons. Used by the elephant foot compensation at the 1st layer. void trim_surfaces(const Polygons &trimming_polygons); diff --git a/src/libslic3r/LayerRegion.cpp b/src/libslic3r/LayerRegion.cpp index 9e962b015d0..4fd31668b60 100644 --- a/src/libslic3r/LayerRegion.cpp +++ b/src/libslic3r/LayerRegion.cpp @@ -429,7 +429,11 @@ static Surfaces expand_merge_surfaces( const Algorithm::RegionExpansionParameters &expansion_params_into_sparse_infill, const coordf_t closing_radius, const coord_t scaled_resolution, - const double bridge_angle = -1.) + const double bridge_angle +#ifdef _DEBUG + ,std::string svg_name +#endif + ) { using namespace Slic3r::Algorithm; @@ -437,7 +441,9 @@ static Surfaces expand_merge_surfaces( ExPolygons src = fill_surfaces_extract_expolygons(surfaces, {surface_type}, thickness); if (src.empty()) return {}; - + ExPolygons init_src = src; + ExPolygons init_shells = shells; + ExPolygons sparse_shells = sparse; std::vector expansions = propagate_waves(src, shells, expansion_params_into_solid_infill); bool expanded_into_shells = !expansions.empty(); bool expanded_into_sparse = false; @@ -462,6 +468,24 @@ static Surfaces expand_merge_surfaces( shells = diff_ex(shells, expanded); if (expanded_into_sparse) sparse = diff_ex(sparse, expanded); + +#ifdef _DEBUG + { + static int aodfjiaqsdz = 0; + std::stringstream stri; + stri << svg_name <<"_"<<(aodfjiaqsdz++) << ".svg"; + SVG svg(stri.str()); + for(auto&srf : surfaces) + svg.draw(srf.expolygon, "grey"); + svg.draw(to_polylines(init_src), "green", scale_t(0.15)); + svg.draw(to_polylines(init_shells), "blue", scale_t(0.14)); + svg.draw(to_polylines(sparse_shells), "brown", scale_t(0.13)); + svg.draw(to_polylines(expanded), "purple", scale_t(0.12)); + svg.draw(to_polylines(shells), "cyan", scale_t(0.11)); + svg.draw(to_polylines(sparse), "yellow", scale_t(0.10)); + svg.Close(); + } +#endif Surface templ{ surface_type, {} }; templ.bridge_angle = bridge_angle; @@ -481,8 +505,8 @@ void LayerRegion::process_external_surfaces(const Layer *lower_layer, const Poly #endif /* SLIC3R_DEBUG_SLICE_PROCESSING */ // Width of the perimeters. - float shell_width = 0; - float expansion_min = 0; + coord_t shell_width = 0; + coord_t expansion_min = 0; if (int num_perimeters = this->region().config().perimeters; num_perimeters > 0) { Flow external_perimeter_flow = this->flow(frExternalPerimeter); Flow perimeter_flow = this->flow(frPerimeter); @@ -491,18 +515,42 @@ void LayerRegion::process_external_surfaces(const Layer *lower_layer, const Poly expansion_min = perimeter_flow.scaled_spacing(); } else { // TODO: Maybe there is better solution when printing with zero perimeters, but this works reasonably well, given the situation - shell_width = float(SCALED_EPSILON); - expansion_min = float(SCALED_EPSILON);; + shell_width = 0;//SCALED_EPSILON; + expansion_min = 0;//SCALED_EPSILON; + } + + coord_t expansion_solid = shell_width; + coord_t expansion_bottom_bridge = shell_width; + const bool has_infill = this->region().config().fill_density.value > 0.; + //if no infill, reduce the margin for everything to only the perimeter + if (!has_infill) { + coord_t margin = scale_t(this->region().config().external_infill_margin.get_abs_value(unscaled(shell_width))); + coord_t margin_bridged = scale_t(this->region().config().bridged_infill_margin.get_abs_value(this->flow(frExternalPerimeter).width())); + expansion_solid = std::min(margin, shell_width); + expansion_bottom_bridge = std::min(margin_bridged, shell_width); + } else { + expansion_solid = scale_t(this->region().config().external_infill_margin.get_abs_value(unscaled(shell_width))); + expansion_bottom_bridge = scale_t(this->region().config().bridged_infill_margin.get_abs_value(this->flow(frExternalPerimeter).width())); + } + if (expansion_min <= 0) { + expansion_min = SCALED_EPSILON; + } + if (expansion_solid <= 0) { + expansion_solid = SCALED_EPSILON; } + if (expansion_bottom_bridge <= 0) { + expansion_bottom_bridge = SCALED_EPSILON; + } + expansion_min = std::min(expansion_min, expansion_solid); // Scaled expansions of the respective external surfaces. - float expansion_top = shell_width * sqrt(2.); - float expansion_bottom = expansion_top; - float expansion_bottom_bridge = expansion_top; - // Expand by waves of expansion_step size (expansion_step is scaled), but with no more steps than max_nr_expansion_steps. - static constexpr const float expansion_step = scaled(0.1); + coord_t expansion_top = expansion_solid; + coord_t expansion_bottom = expansion_top; // Don't take more than max_nr_steps for small expansion_step. static constexpr const size_t max_nr_expansion_steps = 5; + // Expand by waves of expansion_step size (expansion_step is scaled), but with no more steps than max_nr_expansion_steps. + coord_t expansion_step = std::max(coord_t(expansion_solid / max_nr_expansion_steps), expansion_min / 2); + //std::min(this->flow(frPerimeter).scaled_width() / 4, expansion_min); // Radius (with added epsilon) to absorb empty regions emering from regularization of ensuring, viz const float narrow_ensure_vertical_wall_thickness_region_radius = 0.5f * 0.65f * min_perimeter_infill_spacing; const coordf_t closing_radius = 0.55f * 0.65f * 1.05f * this->flow(frSolidInfill).scaled_spacing(); const coord_t scaled_resolution = std::max(SCALED_EPSILON, scale_t(this->layer()->object()->print()->config().resolution.value)); @@ -511,7 +559,19 @@ void LayerRegion::process_external_surfaces(const Layer *lower_layer, const Poly double layer_thickness; ExPolygons shells = union_ex(fill_surfaces_extract_expolygons(m_fill_surfaces.surfaces, { stPosInternal | stDensSolid }, layer_thickness)); ExPolygons sparse = union_ex(fill_surfaces_extract_expolygons(m_fill_surfaces.surfaces, { stPosInternal | stDensSparse }, layer_thickness)); + ExPolygons init_shells = shells; + ExPolygons init_sparse = sparse; +#ifdef _DEBUG + for (auto &srf : m_fill_surfaces.surfaces) { + assert(srf.surface_type == (stPosInternal | stDensSolid) || + srf.surface_type == (stPosInternal | stDensSparse) || + srf.surface_type == (stPosInternal | stDensVoid) || + srf.surface_type == (stPosTop | stDensSolid) || + srf.surface_type == (stPosBottom | stDensSolid) || + srf.surface_type == (stPosBottom | stDensSolid | stModBridge)); + } +#endif SurfaceCollection bridges; const auto expansion_params_into_sparse_infill = RegionExpansionParameters::build(expansion_min, expansion_step, max_nr_expansion_steps); { @@ -519,7 +579,11 @@ void LayerRegion::process_external_surfaces(const Layer *lower_layer, const Poly const double custom_angle = this->region().config().bridge_angle.value; const auto expansion_params_into_solid_infill = RegionExpansionParameters::build(expansion_bottom_bridge, expansion_step, max_nr_expansion_steps); if (this->region().config().bridge_angle.is_enabled()) { - bridges.surfaces = expand_merge_surfaces(m_fill_surfaces.surfaces, stPosBottom | stDensSolid | stModBridge, shells, expansion_params_into_solid_infill, sparse, expansion_params_into_sparse_infill, closing_radius, scaled_resolution, Geometry::deg2rad(custom_angle)); + bridges.surfaces = expand_merge_surfaces(m_fill_surfaces.surfaces, stPosBottom | stDensSolid | stModBridge, shells, expansion_params_into_solid_infill, sparse, expansion_params_into_sparse_infill, closing_radius, scaled_resolution, Geometry::deg2rad(custom_angle) +#ifdef _DEBUG + , std::to_string(this->layer()->id()) + "_expand_merge_surfaces_bridge_" +#endif + ); for(auto&srf : bridges.surfaces) srf.expolygon.assert_valid(); } else { bridges.surfaces = expand_bridges_detect_orientations(m_fill_surfaces.surfaces, shells, expansion_params_into_solid_infill, sparse, expansion_params_into_sparse_infill, closing_radius, scaled_resolution); @@ -533,16 +597,90 @@ void LayerRegion::process_external_surfaces(const Layer *lower_layer, const Poly } #endif } +#ifdef _DEBUG + { + static int aodfjiaqsdz = 0; + std::stringstream stri; + stri << this->layer()->id() << "_process_external_surfaces_1_" <<"_"<<(aodfjiaqsdz++) << ".svg"; + SVG svg(stri.str()); + svg.draw(this->layer()->lslices(), "grey"); + svg.draw_outline(to_polygons(init_shells), "black", scale_t(0.15)); + svg.draw_outline(to_polygons(init_sparse), "white", scale_t(0.14)); + svg.draw_outline(to_polygons(shells), "purple", scale_t(0.13)); + svg.draw_outline(to_polygons(sparse), "pink", scale_t(0.12)); + for(auto&srf : bridges.surfaces) + svg.draw_outline(to_polygons(srf.expolygon), "cyan", scale_t(0.11)); + for (const Surface *srf : fill_surfaces().filter_by_types({stPosBottom | stDensSolid})) + svg.draw(to_polylines(srf->expolygon), "blue", scale_t(0.10)); + for (const Surface *srf : fill_surfaces().filter_by_types({stPosTop | stDensSolid})) + svg.draw(to_polylines(srf->expolygon), "red", scale_t(0.09)); + svg.Close(); + } +#endif Surfaces bottoms = expand_merge_surfaces(m_fill_surfaces.surfaces, stPosBottom | stDensSolid, shells, - RegionExpansionParameters::build(expansion_bottom, expansion_step, max_nr_expansion_steps), - sparse, expansion_params_into_sparse_infill, closing_radius, scaled_resolution); + RegionExpansionParameters::build(expansion_bottom, expansion_bottom/max_nr_expansion_steps , max_nr_expansion_steps), + sparse, expansion_params_into_sparse_infill, closing_radius, scaled_resolution, -1 +#ifdef _DEBUG + , std::to_string(this->layer()->id()) + "_expand_merge_surfaces_bot_" +#endif + ); +#ifdef _DEBUG + { + static int aodfjiaqsdz = 0; + std::stringstream stri; + stri << this->layer()->id() << "_process_external_surfaces_2_" <<"_"<<(aodfjiaqsdz++) << ".svg"; + SVG svg(stri.str()); + svg.draw(this->layer()->lslices(), "grey"); + svg.draw_outline(to_polygons(init_shells), "black", scale_t(0.15)); + svg.draw_outline(to_polygons(init_sparse), "white", scale_t(0.14)); + svg.draw_outline(to_polygons(shells), "purple", scale_t(0.13)); + svg.draw_outline(to_polygons(sparse), "pink", scale_t(0.12)); + for(auto&srf : bridges.surfaces) + svg.draw_outline(to_polygons(srf.expolygon), "cyan", scale_t(0.11)); + svg.draw_outline(to_polygons(bottoms), "blue", scale_t(0.10)); + for (const Surface *srf : fill_surfaces().filter_by_types({stPosTop | stDensSolid})) + svg.draw(to_polylines(srf->expolygon), "red", scale_t(0.09)); + for (const Surface *srf : fill_surfaces().filter_by_types({stPosTop | stDensSolid})) + svg.draw(offset_ex(srf->expolygon, expansion_top), "orange"); + svg.Close(); + } +#endif Surfaces tops = expand_merge_surfaces(m_fill_surfaces.surfaces, stPosTop | stDensSolid, shells, - RegionExpansionParameters::build(expansion_top, expansion_step, max_nr_expansion_steps), - sparse, expansion_params_into_sparse_infill, closing_radius, scaled_resolution); - -// m_fill_surfaces.remove_types({ stBottomBridge, stBottom, stTop, stInternal, stInternalSolid }); - m_fill_surfaces.clear(); + RegionExpansionParameters::build(expansion_top, expansion_top / max_nr_expansion_steps, max_nr_expansion_steps), + sparse, expansion_params_into_sparse_infill, closing_radius, scaled_resolution, -1 +#ifdef _DEBUG + , std::to_string(this->layer()->id()) + "_expand_merge_surfaces_top_" +#endif + ); + +#ifdef _DEBUG + { + static int aodfjiaqsdz = 0; + std::stringstream stri; + stri << this->layer()->id() << "_process_external_surfaces_3_" <<"_"<<(aodfjiaqsdz++) << ".svg"; + SVG svg(stri.str()); + svg.draw(this->layer()->lslices(), "grey"); + svg.draw_outline(to_polygons(init_shells), "black", scale_t(0.15)); + svg.draw_outline(to_polygons(init_sparse), "white", scale_t(0.14)); + svg.draw_outline(to_polygons(shells), "purple", scale_t(0.13)); + svg.draw_outline(to_polygons(sparse), "pink", scale_t(0.12)); + for(auto&srf : bridges.surfaces) + svg.draw_outline(to_polygons(srf.expolygon), "cyan", scale_t(0.11)); + svg.draw_outline(to_polygons(bottoms), "blue", scale_t(0.10)); + svg.draw_outline(to_polygons(tops), "red", scale_t(0.09)); + svg.Close(); + } + m_fill_surfaces.remove_types({ + stPosBottom | stDensSolid | stModBridge, + stPosBottom | stDensSolid, + stPosTop | stDensSolid, + stPosInternal | stDensSparse, + stPosInternal | stDensSolid }); + for (auto &srf : m_fill_surfaces.surfaces) { + assert(srf.surface_type == (stPosInternal | stDensVoid)); + } +#endif reserve_more(m_fill_surfaces.surfaces, shells.size() + sparse.size() + bridges.size() + bottoms.size() + tops.size()); { Surface solid_templ(stPosInternal | stDensSolid, {}); @@ -562,25 +700,73 @@ void LayerRegion::process_external_surfaces(const Layer *lower_layer, const Poly m_fill_surfaces.append(std::move(bridges.surfaces)); m_fill_surfaces.append(std::move(bottoms)); m_fill_surfaces.append(std::move(tops)); + +#ifdef _DEBUG + //assert each surface is not on top of each other (or almost) + for (auto &srf : m_fill_surfaces.surfaces) { + for (auto &srf2 : m_fill_surfaces.surfaces) { + if (&srf != &srf2) { + ExPolygons intersect = intersection_ex(srf.expolygon, srf2.expolygon); + intersect = offset2_ex(intersect, -SCALED_EPSILON * 2, SCALED_EPSILON); + double area = 0; + for (auto &expoly : intersect) { + area += expoly.area(); + } + if (area > SCALED_EPSILON * SCALED_EPSILON) { + static int aodfjiaqsdz = 0; + std::stringstream stri; + stri << this->layer()->id() << "_overlapping_fill_surfaces_" <<"_"<<(aodfjiaqsdz++) << ".svg"; + SVG svg(stri.str()); + svg.draw(this->layer()->lslices(), "grey"); + if ((srf.surface_type & stPosTop) == stPosTop) + svg.draw_outline(to_polygons(srf), "red", scale_t(0.05)); + else if ((srf.surface_type & stPosTop) == stPosBottom) + svg.draw_outline(to_polygons(srf), "blue", scale_t(0.05)); + else if (srf.surface_type == (stPosInternal | stDensSparse)) + svg.draw_outline(to_polygons(srf), "brown", scale_t(0.05)); + else if (srf.surface_type == (stPosInternal | stDensSolid)) + svg.draw_outline(to_polygons(srf), "purple", scale_t(0.05)); + else + svg.draw_outline(to_polygons(srf), "green", scale_t(0.05)); + + if ((srf2.surface_type & stPosTop) == stPosTop) + svg.draw_outline(to_polygons(srf2), "orange", scale_t(0.03)); + else if ((srf2.surface_type & stPosTop) == stPosBottom) + svg.draw_outline(to_polygons(srf2), "cryan", scale_t(0.03)); + else if (srf2.surface_type == (stPosInternal | stDensSparse)) + svg.draw_outline(to_polygons(srf2), "yellow", scale_t(0.03)); + else if (srf2.surface_type == (stPosInternal | stDensSolid)) + svg.draw_outline(to_polygons(srf2), "pink", scale_t(0.03)); + else + svg.draw_outline(to_polygons(srf), "teal", scale_t(0.03)); + svg.draw(intersect, "black"); + svg.Close(); + } + assert(area < SCALED_EPSILON * SCALED_EPSILON /** 100*/); + } + } + } +#endif #ifdef SLIC3R_DEBUG_SLICE_PROCESSING export_region_fill_surfaces_to_svg_debug("4_process_external_surfaces-final"); #endif /* SLIC3R_DEBUG_SLICE_PROCESSING */ } -#else +//#else //#define EXTERNAL_SURFACES_OFFSET_PARAMETERS ClipperLib::jtMiter, 3. //#define EXTERNAL_SURFACES_OFFSET_PARAMETERS ClipperLib::jtMiter, 1.5 #define EXTERNAL_SURFACES_OFFSET_PARAMETERS ClipperLib::jtSquare, 0. -void LayerRegion::process_external_surfaces(const Layer *lower_layer, const Polygons *lower_layer_covered) +void LayerRegion::process_external_surfaces_old(const Layer *lower_layer, const Polygons *lower_layer_covered) { coord_t max_margin = 0; if ((this->region().config().perimeters > 0)) { - max_margin = this->flow(frExternalPerimeter).scaled_width() + this->flow(frPerimeter).scaled_spacing() * (this->region().config().perimeters.value - 1); + max_margin = (this->flow(frExternalPerimeter).scaled_width() + this->flow(frPerimeter).scaled_spacing()) /2 + + this->flow(frPerimeter).scaled_spacing() * (this->region().config().perimeters.value - 1); } - const Surfaces &surfaces = this->fill_surfaces.surfaces; + const Surfaces &surfaces = this->m_fill_surfaces.surfaces; const bool has_infill = this->region().config().fill_density.value > 0.; coord_t margin = scale_t(this->region().config().external_infill_margin.get_abs_value(unscaled(max_margin))); coord_t margin_bridged = scale_t(this->region().config().bridged_infill_margin.get_abs_value(this->flow(frExternalPerimeter).width())); diff --git a/src/libslic3r/Preset.cpp b/src/libslic3r/Preset.cpp index eaf37da0303..fc6ae144f57 100644 --- a/src/libslic3r/Preset.cpp +++ b/src/libslic3r/Preset.cpp @@ -482,6 +482,7 @@ static std::vector s_Preset_print_options { "bottom_solid_min_thickness", "solid_over_perimeters", "duplicate_distance", + "ensure_vertical_shell_thickness", "extra_perimeters", "extra_perimeters_odd_layers", "extra_perimeters_on_overhangs", diff --git a/src/libslic3r/PresetBundle.cpp b/src/libslic3r/PresetBundle.cpp index 6b3ff455e6d..0a4ffd36d86 100644 --- a/src/libslic3r/PresetBundle.cpp +++ b/src/libslic3r/PresetBundle.cpp @@ -1504,6 +1504,7 @@ std::pair PresetBundle::load_configbundle( } Preset::normalize(config); // Report configuration fields, which are misplaced into a wrong group. + auto copy = config; std::string incorrect_keys = Preset::remove_invalid_keys(config, *default_config); if (! incorrect_keys.empty()) BOOST_LOG_TRIVIAL(error) << "Error in a Vendor Config Bundle \"" << path << "\": The printer preset \"" << @@ -1632,6 +1633,7 @@ std::pair PresetBundle::load_configbundle( } // Report configuration fields, which are misplaced into a wrong group. + auto copy2 = config; std::string incorrect_keys = Preset::remove_invalid_keys(config, default_config); if (!incorrect_keys.empty()) BOOST_LOG_TRIVIAL(error) << "Error in a Vendor Config Bundle \"" << path << "\": The physical printer \"" << diff --git a/src/libslic3r/Print.hpp b/src/libslic3r/Print.hpp index f85d9de21b4..1b8c5a689dc 100644 --- a/src/libslic3r/Print.hpp +++ b/src/libslic3r/Print.hpp @@ -466,7 +466,7 @@ class PrintObject : public PrintObjectBaseWithState &kvp : options) @@ -1496,6 +1504,20 @@ void PrintConfigDef::init_fff_params() def->mode = comExpert | comPrusa; def->set_default_value(new ConfigOptionBool(true)); + def = this->add("ensure_vertical_shell_thickness", coEnum); + def->label = L("Ensure vertical shell thickness"); + def->category = OptionCategory::perimeter; + def->tooltip = L("Add solid infill near sloping surfaces to guarantee the vertical shell thickness " + "(top+bottom solid layers)."); + def->set_enum({ + { "disabled", L("Disabled (2.5)") }, + { "partial", L("partial (2.9 experimental)") }, + { "enabled", L("Enabled (2.7 experimental)") }, + { "enabled_old", L("Enabled (2.5)") }, + }); + def->mode = comAdvancedE | comPrusa; + def->set_default_value(new ConfigOptionEnum(EnsureVerticalShellThickness::Enabled_old)); + def = this->add("external_infill_margin", coFloatOrPercent); def->label = L("Default"); def->full_label = L("Default infill margin"); @@ -5723,7 +5745,7 @@ void PrintConfigDef::init_fff_params() "\nSet zero to disable."); def->min = 0; def->mode = comAdvancedE | comSuSi; - def->set_default_value(new ConfigOptionInt(2)); + def->set_default_value(new ConfigOptionInt(0)); def = this->add("support_material", coBool); def->label = L("Generate support material"); @@ -8355,8 +8377,6 @@ static std::set PrintConfigDef_ignore = { // Introduced in PrusaSlicer 2.3.0-alpha2, later replaced by automatic calculation based on extrusion width. "wall_add_middle_threshold", "wall_split_middle_threshold", // Replaced by new concentric ensuring in 2.6.0-alpha5 - "ensure_vertical_shell_thickness", - // Disabled in 2.6.0-alpha6, this option is problematic // "infill_only_where_needed", <- ignore only if deactivated "gcode_binary", // Introduced in 2.7.0-alpha1, removed in 2.7.1 (replaced by binary_gcode). "gcode_resolution", // now in printer config. @@ -8483,6 +8503,17 @@ void PrintConfigDef::handle_legacy(t_config_option_key &opt_key, std::string &va if (opt_key == "preset_name") { opt_key = "preset_names"; } + if (opt_key == "ensure_vertical_shell_thickness") { + if (value == "1") { + value = "enabled_old"; + } else if (value == "0") { + value = "disabled"; + } else if (const t_config_enum_values &enum_keys_map = ConfigOptionEnum::get_enum_values(); enum_keys_map.find(value) == enum_keys_map.end()) { + assert(value == "0" || value == "1"); + // Values other than 0/1 are replaced with "partial" for handling values from different slicers. + value = "partial"; + } + } if (opt_key == "seam_travel") { if (value == "1") { opt_key = "seam_travel_cost"; @@ -8874,6 +8905,11 @@ void PrintConfigDef::handle_legacy_composite(DynamicPrintConfig &config, std::ve config.set_key_value("overhangs_dynamic_fan_speed", opt.clone()); } + + if (!config.has("ensure_vertical_shell_thickness") && config.has("perimeters")) { + config.set_key_value("ensure_vertical_shell_thickness", new ConfigOptionEnum(EnsureVerticalShellThickness::Partial)); + } + //if (config.has("thumbnails")) { // std::string extention; // if (config.has("thumbnails_format")) { diff --git a/src/libslic3r/PrintConfig.hpp b/src/libslic3r/PrintConfig.hpp index d6f144975a2..ab775698dc8 100644 --- a/src/libslic3r/PrintConfig.hpp +++ b/src/libslic3r/PrintConfig.hpp @@ -297,6 +297,13 @@ enum ZLiftTop { zltNotTop }; +enum class EnsureVerticalShellThickness { + Disabled, + Partial, + Enabled, + Enabled_old, +}; + #define CONFIG_OPTION_ENUM_DECLARE_STATIC_MAPS(NAME) \ template<> const t_config_enum_names& ConfigOptionEnum::get_enum_names(); \ template<> const t_config_enum_values& ConfigOptionEnum::get_enum_values(); @@ -868,6 +875,7 @@ PRINT_CONFIG_CLASS_DEFINE( ((ConfigOptionFloatOrPercent, default_acceleration)) ((ConfigOptionFloatOrPercent, default_speed)) ((ConfigOptionBool, enforce_full_fill_volume)) + ((ConfigOptionEnum, ensure_vertical_shell_thickness)) ((ConfigOptionFloatOrPercent, external_infill_margin)) ((ConfigOptionFloatOrPercent, external_perimeter_acceleration)) ((ConfigOptionFloatOrPercent, external_perimeter_extrusion_width)) diff --git a/src/libslic3r/PrintObject.cpp b/src/libslic3r/PrintObject.cpp index 5d50c220d0b..1a521936026 100644 --- a/src/libslic3r/PrintObject.cpp +++ b/src/libslic3r/PrintObject.cpp @@ -86,6 +86,7 @@ using namespace std::literals; #define PRINT_OBJECT_TIME_LIMIT_MILLIS(limit) do {} while(false) #endif // PRINT_OBJECT_TIMING +//#define SLIC3R_DEBUG_SLICE_PROCESSING 1 #ifdef SLIC3R_DEBUG_SLICE_PROCESSING #define SLIC3R_DEBUG #endif @@ -94,12 +95,14 @@ using namespace std::literals; // Make assert active if SLIC3R_DEBUG #ifdef SLIC3R_DEBUG +#ifndef _DEBUG #undef NDEBUG #define DEBUG #define _DEBUG #include "SVG.hpp" #undef assert #include +#endif #endif #include "SVG.hpp" @@ -333,6 +336,41 @@ void PrintObject::prepare_infill() m_print->throw_if_canceled(); } } +#ifdef _DEBUG + for (size_t region_id = 0; region_id < this->num_printing_regions(); ++region_id) { + for (const Layer *layer : m_layers) { + for (const Surface &srf : layer->get_region(region_id)->fill_surfaces().surfaces) { + assert(srf.surface_type == (stPosInternal | stDensSolid) || + srf.surface_type == (stPosInternal | stDensSparse) || + srf.surface_type == (stPosInternal | stDensVoid) || + srf.surface_type == (stPosTop | stDensSolid) || + srf.surface_type == (stPosBottom | stDensSolid) || + srf.surface_type == (stPosBottom | stDensSolid | stModBridge)); + } + } + } + for (auto &layer : m_layers) { + static int aodfjiaqsdz = 0; + std::stringstream stri; + stri << layer->id() << "_prepare_infill_1_" <<"_"<<(aodfjiaqsdz++) << ".svg"; + SVG svg(stri.str()); + svg.draw(layer->lslices(), "grey"); + const LayerRegion* region = layer->get_region(0); + for (const Surface *srf : region->fill_surfaces().filter_by_types({stPosInternal | stDensSolid})) + svg.draw(to_polylines(srf->expolygon), "purple", scale_t(0.15)); + for (const Surface *srf : region->fill_surfaces().filter_by_types({stPosInternal | stDensSparse})) + svg.draw(to_polylines(srf->expolygon), "pink", scale_t(0.14)); + for (const Surface *srf : region->fill_surfaces().filter_by_types({stPosInternal | stDensVoid})) + svg.draw(to_polylines(srf->expolygon), "white", scale_t(0.13)); + for (const Surface *srf : region->fill_surfaces().filter_by_types({stPosBottom | stDensSolid | stModBridge})) + svg.draw(to_polylines(srf->expolygon), "cyan", scale_t(0.12)); + for (const Surface *srf : region->fill_surfaces().filter_by_types({stPosBottom | stDensSolid})) + svg.draw(to_polylines(srf->expolygon), "blue", scale_t(0.11)); + for (const Surface *srf : region->fill_surfaces().filter_by_types({stPosTop | stDensSolid})) + svg.draw(to_polylines(srf->expolygon), "red", scale_t(0.10)); + svg.Close(); + } +#endif // This will assign a type (top/bottom/internal) to $layerm->slices. // Then the classifcation of $layerm->slices is transfered onto @@ -350,6 +388,63 @@ void PrintObject::prepare_infill() this->detect_surfaces_type(); m_print->throw_if_canceled(); +#ifdef _DEBUG + for (size_t region_id = 0; region_id < this->num_printing_regions(); ++region_id) { + for (const Layer *layer : m_layers) { + for (const Surface &srf : layer->get_region(region_id)->fill_surfaces().surfaces) { + assert(srf.surface_type == (stPosInternal | stDensSolid) || + srf.surface_type == (stPosInternal | stDensSparse) || + srf.surface_type == (stPosInternal | stDensVoid) || + srf.surface_type == (stPosTop | stDensSolid) || + srf.surface_type == (stPosBottom | stDensSolid) || + srf.surface_type == (stPosBottom | stDensSolid | stModBridge)); + } + } + } + for (auto &layer : m_layers) { + static int aodfjiaqsdz = 0; + std::stringstream stri; + stri << layer->id() << "_prepare_infill_2_" <<"_"<<(aodfjiaqsdz++) << ".svg"; + SVG svg(stri.str()); + svg.draw(layer->lslices(), "grey"); + const LayerRegion* region = layer->get_region(0); + for (const Surface *srf : region->fill_surfaces().filter_by_types({stPosInternal | stDensSolid})) + svg.draw(to_polylines(srf->expolygon), "purple", scale_t(0.15)); + for (const Surface *srf : region->fill_surfaces().filter_by_types({stPosInternal | stDensSparse})) + svg.draw(to_polylines(srf->expolygon), "pink", scale_t(0.14)); + for (const Surface *srf : region->fill_surfaces().filter_by_types({stPosInternal | stDensVoid})) + svg.draw(to_polylines(srf->expolygon), "white", scale_t(0.13)); + for (const Surface *srf : region->fill_surfaces().filter_by_types({stPosBottom | stDensSolid | stModBridge})) + svg.draw(to_polylines(srf->expolygon), "cyan", scale_t(0.12)); + for (const Surface *srf : region->fill_surfaces().filter_by_types({stPosBottom | stDensSolid})) + svg.draw(to_polylines(srf->expolygon), "blue", scale_t(0.11)); + for (const Surface *srf : region->fill_surfaces().filter_by_types({stPosTop | stDensSolid})) + svg.draw(to_polylines(srf->expolygon), "red", scale_t(0.10)); + svg.Close(); + } + for (auto &layer : m_layers) { + static int aodfjiaqsdz = 0; + std::stringstream stri; + stri << layer->id() << "_prepare_infill_2b_" <<"_"<<(aodfjiaqsdz++) << ".svg"; + SVG svg(stri.str()); + svg.draw(layer->lslices(), "grey"); + const LayerRegion* region = layer->get_region(0); + for (const Surface *srf : region->fill_surfaces().filter_by_types({stPosInternal | stDensSolid})) + svg.draw((srf->expolygon), "purple"); + for (const Surface *srf : region->fill_surfaces().filter_by_types({stPosInternal | stDensSparse})) + svg.draw((srf->expolygon), "pink"); + for (const Surface *srf : region->fill_surfaces().filter_by_types({stPosInternal | stDensVoid})) + svg.draw((srf->expolygon), "white"); + for (const Surface *srf : region->fill_surfaces().filter_by_types({stPosBottom | stDensSolid | stModBridge})) + svg.draw((srf->expolygon), "cyan"); + for (const Surface *srf : region->fill_surfaces().filter_by_types({stPosBottom | stDensSolid})) + svg.draw((srf->expolygon), "blue"); + for (const Surface *srf : region->fill_surfaces().filter_by_types({stPosTop | stDensSolid})) + svg.draw((srf->expolygon), "red", scale_t(0.10)); + svg.Close(); + } +#endif + // Decide what surfaces are to be filled. // Here the stTop / stBottomBridge / stBottom infill is turned to just stInternal if zero top / bottom infill layers are configured. // Also tiny stInternal surfaces are turned to stInternalSolid. @@ -374,11 +469,109 @@ void PrintObject::prepare_infill() m_print->throw_if_canceled(); } } + +#ifdef _DEBUG + for (size_t region_id = 0; region_id < this->num_printing_regions(); ++region_id) { + for (const Layer *layer : m_layers) { + for (const Surface &srf : layer->get_region(region_id)->fill_surfaces().surfaces) { + assert(srf.surface_type == (stPosInternal | stDensSolid) || + srf.surface_type == (stPosInternal | stDensSparse) || + srf.surface_type == (stPosInternal | stDensVoid) || + srf.surface_type == (stPosTop | stDensSolid) || + srf.surface_type == (stPosBottom | stDensSolid) || + srf.surface_type == (stPosBottom | stDensSolid | stModBridge)); + } + } + } + for (auto &layer : m_layers) { + static int aodfjiaqsdz = 0; + std::stringstream stri; + stri << layer->id() << "_prepare_infill_3_" <<"_"<<(aodfjiaqsdz++) << ".svg"; + SVG svg(stri.str()); + svg.draw(layer->lslices(), "grey"); + const LayerRegion* region = layer->get_region(0); + for (const Surface *srf : region->fill_surfaces().filter_by_types({stPosInternal | stDensSolid})) + svg.draw(to_polylines(srf->expolygon), "purple", scale_t(0.15)); + for (const Surface *srf : region->fill_surfaces().filter_by_types({stPosInternal | stDensSparse})) + svg.draw(to_polylines(srf->expolygon), "pink", scale_t(0.14)); + for (const Surface *srf : region->fill_surfaces().filter_by_types({stPosInternal | stDensVoid})) + svg.draw(to_polylines(srf->expolygon), "white", scale_t(0.13)); + for (const Surface *srf : region->fill_surfaces().filter_by_types({stPosBottom | stDensSolid | stModBridge})) + svg.draw(to_polylines(srf->expolygon), "cyan", scale_t(0.12)); + for (const Surface *srf : region->fill_surfaces().filter_by_types({stPosBottom | stDensSolid})) + svg.draw(to_polylines(srf->expolygon), "blue", scale_t(0.11)); + for (const Surface *srf : region->fill_surfaces().filter_by_types({stPosTop | stDensSolid})) + svg.draw(to_polylines(srf->expolygon), "red", scale_t(0.10)); + svg.Close(); + } +#endif + EnsureVerticalShellThickness ensure_vertical_shell_thickness = this->default_region_config(this->print()->default_region_config()) + .option>("ensure_vertical_shell_thickness")->value; + if (ensure_vertical_shell_thickness != EnsureVerticalShellThickness::Partial && ensure_vertical_shell_thickness != EnsureVerticalShellThickness::Enabled) { + // this will detect bridges and reverse bridges + // and rearrange top/bottom/internal surfaces + // It produces enlarged overlapping bridging areas. + // + // 1) stBottomBridge / stBottom infill is grown by 3mm and clipped by the total infill area. Bridges are + // detected. The areas may overlap. 2) stTop is grown by 3mm and clipped by the grown bottom areas. The areas + // may overlap. 3) Clip the internal surfaces by the grown top/bottom surfaces. 4) Merge surfaces with the + // same style. This will mostly get rid of the overlaps. + // FIXME This does not likely merge surfaces, which are supported by a material with different colors, but + // same properties. + if (m_print->objects().size() == 1) { + m_print->set_status(30, L("Process external surfaces"), {}, PrintBase::SlicingStatus::SECONDARY_STATE); + } else { + int32_t advancement_count = m_print->secondary_status_counter_increment(15); + m_print->set_status(advancement_count * 100 / m_print->secondary_status_counter_get_max(), + L("Process objects: %s / %s"), + {std::to_string(advancement_count), + std::to_string(m_print->secondary_status_counter_get_max())}, + PrintBase::SlicingStatus::SECONDARY_STATE); + } + this->process_external_surfaces(true /* old*/); + m_print->throw_if_canceled(); +#ifdef _DEBUG + for (size_t region_id = 0; region_id < this->num_printing_regions(); ++region_id) { + for (const Layer *layer : m_layers) { + for (const Surface &srf : layer->get_region(region_id)->fill_surfaces().surfaces) { + assert(srf.surface_type == (stPosInternal | stDensSolid) || + srf.surface_type == (stPosInternal | stDensSparse) || + srf.surface_type == (stPosInternal | stDensVoid) || + srf.surface_type == (stPosTop | stDensSolid) || + srf.surface_type == (stPosBottom | stDensSolid) || + srf.surface_type == (stPosBottom | stDensSolid | stModBridge)); + } + } + } + for (auto &layer : m_layers) { + static int aodfjiaqsdz = 0; + std::stringstream stri; + stri << layer->id() << "_prepare_infill_4_" << "_" << (aodfjiaqsdz++) << ".svg"; + SVG svg(stri.str()); + svg.draw(layer->lslices(), "grey"); + const LayerRegion *region = layer->get_region(0); + for (const Surface *srf : region->fill_surfaces().filter_by_types({stPosInternal | stDensSolid})) + svg.draw(to_polylines(srf->expolygon), "purple", scale_t(0.15)); + for (const Surface *srf : region->fill_surfaces().filter_by_types({stPosInternal | stDensSparse})) + svg.draw(to_polylines(srf->expolygon), "pink", scale_t(0.14)); + for (const Surface *srf : region->fill_surfaces().filter_by_types({stPosInternal | stDensVoid})) + svg.draw(to_polylines(srf->expolygon), "white", scale_t(0.13)); + for (const Surface *srf : + region->fill_surfaces().filter_by_types({stPosBottom | stDensSolid | stModBridge})) + svg.draw(to_polylines(srf->expolygon), "cyan", scale_t(0.12)); + for (const Surface *srf : region->fill_surfaces().filter_by_types({stPosBottom | stDensSolid})) + svg.draw(to_polylines(srf->expolygon), "blue", scale_t(0.11)); + for (const Surface *srf : region->fill_surfaces().filter_by_types({stPosTop | stDensSolid})) + svg.draw(to_polylines(srf->expolygon), "red", scale_t(0.10)); + svg.Close(); + } +#endif + } // Add solid fills to ensure the shell vertical thickness. if (m_print->objects().size() == 1) { - m_print->set_status(30, L("Discover shells"), {}, PrintBase::SlicingStatus::SECONDARY_STATE); + m_print->set_status(45, L("Discover shells"), {}, PrintBase::SlicingStatus::SECONDARY_STATE); } else { int32_t advancement_count = m_print->secondary_status_counter_increment(30); m_print->set_status(advancement_count * 100 / m_print->secondary_status_counter_get_max(), L("Process objects: %s / %s"), @@ -388,6 +581,104 @@ void PrintObject::prepare_infill() } this->discover_vertical_shells(); m_print->throw_if_canceled(); + +#ifdef _DEBUG + for (size_t region_id = 0; region_id < this->num_printing_regions(); ++region_id) { + for (const Layer *layer : m_layers) { + for (const Surface &srf : layer->get_region(region_id)->fill_surfaces().surfaces) { + assert(srf.surface_type == (stPosInternal | stDensSolid) || + srf.surface_type == (stPosInternal | stDensSparse) || + srf.surface_type == (stPosInternal | stDensVoid) || + srf.surface_type == (stPosTop | stDensSolid) || + srf.surface_type == (stPosBottom | stDensSolid) || + srf.surface_type == (stPosBottom | stDensSolid | stModBridge)); + } + } + } + for (auto &layer : m_layers) { + static int aodfjiaqsdz = 0; + std::stringstream stri; + stri << layer->id() << "_prepare_infill_5_" <<"_"<<(aodfjiaqsdz++) << ".svg"; + SVG svg(stri.str()); + svg.draw(layer->lslices(), "grey"); + const LayerRegion* region = layer->get_region(0); + for (const Surface *srf : region->fill_surfaces().filter_by_types({stPosInternal | stDensSolid})) + svg.draw(to_polylines(srf->expolygon), "purple", scale_t(0.15)); + for (const Surface *srf : region->fill_surfaces().filter_by_types({stPosInternal | stDensSparse})) + svg.draw(to_polylines(srf->expolygon), "pink", scale_t(0.14)); + for (const Surface *srf : region->fill_surfaces().filter_by_types({stPosInternal | stDensVoid})) + svg.draw(to_polylines(srf->expolygon), "white", scale_t(0.13)); + for (const Surface *srf : region->fill_surfaces().filter_by_types({stPosBottom | stDensSolid | stModBridge})) + svg.draw(to_polylines(srf->expolygon), "cyan", scale_t(0.12)); + for (const Surface *srf : region->fill_surfaces().filter_by_types({stPosBottom | stDensSolid})) + svg.draw(to_polylines(srf->expolygon), "blue", scale_t(0.11)); + for (const Surface *srf : region->fill_surfaces().filter_by_types({stPosTop | stDensSolid})) + svg.draw(to_polylines(srf->expolygon), "red", scale_t(0.10)); + svg.Close(); + } +#endif + + if (ensure_vertical_shell_thickness == EnsureVerticalShellThickness::Partial || ensure_vertical_shell_thickness == EnsureVerticalShellThickness::Enabled) { + // this will detect bridges and reverse bridges + // and rearrange top/bottom/internal surfaces + // It produces enlarged overlapping bridging areas. + // + // 1) stBottomBridge / stBottom infill is grown by 3mm and clipped by the total infill area. Bridges are + // detected. The areas may overlap. 2) stTop is grown by 3mm and clipped by the grown bottom areas. The areas + // may overlap. 3) Clip the internal surfaces by the grown top/bottom surfaces. 4) Merge surfaces with the + // same style. This will mostly get rid of the overlaps. + // FIXME This does not likely merge surfaces, which are supported by a material with different colors, but + // same properties. + if (m_print->objects().size() == 1) { + m_print->set_status(60, L("Process external surfaces"), {}, PrintBase::SlicingStatus::SECONDARY_STATE); + } else { + int32_t advancement_count = m_print->secondary_status_counter_increment(15); + m_print->set_status(advancement_count * 100 / m_print->secondary_status_counter_get_max(), + L("Process objects: %s / %s"), + {std::to_string(advancement_count), + std::to_string(m_print->secondary_status_counter_get_max())}, + PrintBase::SlicingStatus::SECONDARY_STATE); + } + this->process_external_surfaces(false /*!old = new*/); + m_print->throw_if_canceled(); + +#ifdef _DEBUG + for (size_t region_id = 0; region_id < this->num_printing_regions(); ++region_id) { + for (const Layer *layer : m_layers) { + for (const Surface &srf : layer->get_region(region_id)->fill_surfaces().surfaces) { + assert(srf.surface_type == (stPosInternal | stDensSolid) || + srf.surface_type == (stPosInternal | stDensSparse) || + srf.surface_type == (stPosInternal | stDensVoid) || + srf.surface_type == (stPosTop | stDensSolid) || + srf.surface_type == (stPosBottom | stDensSolid) || + srf.surface_type == (stPosBottom | stDensSolid | stModBridge)); + } + } + } + for (auto &layer : m_layers) { + static int aodfjiaqsdz = 0; + std::stringstream stri; + stri << layer->id() << "_prepare_infill_6_" << "_" << (aodfjiaqsdz++) << ".svg"; + SVG svg(stri.str()); + svg.draw(layer->lslices(), "grey"); + const LayerRegion *region = layer->get_region(0); + for (const Surface *srf : region->fill_surfaces().filter_by_types({stPosInternal | stDensSolid})) + svg.draw(to_polylines(srf->expolygon), "purple", scale_t(0.15)); + for (const Surface *srf : region->fill_surfaces().filter_by_types({stPosInternal | stDensSparse})) + svg.draw(to_polylines(srf->expolygon), "pink", scale_t(0.14)); + for (const Surface *srf : region->fill_surfaces().filter_by_types({stPosInternal | stDensVoid})) + svg.draw(to_polylines(srf->expolygon), "white", scale_t(0.13)); + for (const Surface *srf : + region->fill_surfaces().filter_by_types({stPosBottom | stDensSolid | stModBridge})) + svg.draw(to_polylines(srf->expolygon), "cyan", scale_t(0.12)); + for (const Surface *srf : region->fill_surfaces().filter_by_types({stPosBottom | stDensSolid})) + svg.draw(to_polylines(srf->expolygon), "blue", scale_t(0.11)); + for (const Surface *srf : region->fill_surfaces().filter_by_types({stPosTop | stDensSolid})) + svg.draw(to_polylines(srf->expolygon), "red", scale_t(0.10)); + svg.Close(); + } +#endif + } // Debugging output. #ifdef SLIC3R_DEBUG_SLICE_PROCESSING @@ -400,27 +691,6 @@ void PrintObject::prepare_infill() } // for each region #endif /* SLIC3R_DEBUG_SLICE_PROCESSING */ - // this will detect bridges and reverse bridges - // and rearrange top/bottom/internal surfaces - // It produces enlarged overlapping bridging areas. - // - // 1) stBottomBridge / stBottom infill is grown by 3mm and clipped by the total infill area. Bridges are detected. The areas may overlap. - // 2) stTop is grown by 3mm and clipped by the grown bottom areas. The areas may overlap. - // 3) Clip the internal surfaces by the grown top/bottom surfaces. - // 4) Merge surfaces with the same style. This will mostly get rid of the overlaps. - //FIXME This does not likely merge surfaces, which are supported by a material with different colors, but same properties. - if (m_print->objects().size() == 1) { - m_print->set_status( 60, L("Process external surfaces"), {}, PrintBase::SlicingStatus::SECONDARY_STATE); - } else { - int32_t advancement_count = m_print->secondary_status_counter_increment(15); - m_print->set_status(advancement_count * 100 / m_print->secondary_status_counter_get_max(), L("Process objects: %s / %s"), - {std::to_string(advancement_count), - std::to_string(m_print->secondary_status_counter_get_max())}, - PrintBase::SlicingStatus::SECONDARY_STATE); - } - this->process_external_surfaces(); - m_print->throw_if_canceled(); - // Debugging output. #ifdef SLIC3R_DEBUG_SLICE_PROCESSING for (size_t region_id = 0; region_id < this->num_printing_regions(); ++ region_id) { @@ -442,6 +712,16 @@ void PrintObject::prepare_infill() this->discover_horizontal_shells(); m_print->throw_if_canceled(); +#ifdef _DEBUG + for (size_t region_id = 0; region_id < this->num_printing_regions(); ++region_id) { + for (const Layer *layer : m_layers) { + for (const Surface &srf : layer->m_regions[region_id]->fill_surfaces().surfaces) { + srf.expolygon.assert_valid(); + } + } + } +#endif + //as there is some too thin solid surface, please deleted them and merge all of the surfacesthat are contigous. if (m_print->objects().size() == 1) { m_print->set_status( 75, L("Clean surfaces"), {}, PrintBase::SlicingStatus::SECONDARY_STATE); @@ -2108,7 +2388,7 @@ void PrintObject::detect_surfaces_type() polygons_append(topbottom, to_polygons(bottom)); assert_valid(topbottom); assert_valid(surfaces_prev_expolys); - ExPolygons diff = diff_ex(surfaces_prev_expolys, topbottom); + ExPolygons diff = diff_ex(union_ex(surfaces_prev_expolys), union_ex(topbottom)); ensure_valid(diff, scaled_resolution); assert_valid(diff); surfaces_append(surfaces_out, std::move(diff), stPosInternal | stDensSparse); @@ -2204,7 +2484,7 @@ void PrintObject::detect_surfaces_type() } } -void PrintObject::process_external_surfaces() +void PrintObject::process_external_surfaces(bool old) { BOOST_LOG_TRIVIAL(info) << "Processing external surfaces..." << log_memory_info(); @@ -2267,15 +2547,23 @@ void PrintObject::process_external_surfaces() for (size_t region_id = 0; region_id < this->num_printing_regions(); ++region_id) { BOOST_LOG_TRIVIAL(debug) << "Processing external surfaces for region " << region_id << " in parallel - start"; Slic3r::parallel_for(size_t(0), m_layers.size(), - [this, &surfaces_covered, region_id](const size_t layer_idx) { + [this, &surfaces_covered, region_id, old](const size_t layer_idx) { PRINT_OBJECT_TIME_LIMIT_MILLIS(PRINT_OBJECT_TIME_LIMIT_DEFAULT); m_print->throw_if_canceled(); // BOOST_LOG_TRIVIAL(trace) << "Processing external surface, layer" << m_layers[layer_idx]->print_z; - m_layers[layer_idx]->get_region(int(region_id))->process_external_surfaces( - // lower layer - (layer_idx == 0) ? nullptr : m_layers[layer_idx - 1], - // lower layer polygons with density > 0% - (layer_idx == 0 || surfaces_covered.empty() || surfaces_covered[layer_idx - 1].empty()) ? nullptr : &surfaces_covered[layer_idx - 1]); + if (old) { + m_layers[layer_idx]->get_region(int(region_id))->process_external_surfaces_old( + // lower layer + (layer_idx == 0) ? nullptr : m_layers[layer_idx - 1], + // lower layer polygons with density > 0% + (layer_idx == 0 || surfaces_covered.empty() || surfaces_covered[layer_idx - 1].empty()) ? nullptr : &surfaces_covered[layer_idx - 1]); + } else { + m_layers[layer_idx]->get_region(int(region_id))->process_external_surfaces( + // lower layer + (layer_idx == 0) ? nullptr : m_layers[layer_idx - 1], + // lower layer polygons with density > 0% + (layer_idx == 0 || surfaces_covered.empty() || surfaces_covered[layer_idx - 1].empty()) ? nullptr : &surfaces_covered[layer_idx - 1]); + } } ); m_print->throw_if_canceled(); @@ -2314,9 +2602,25 @@ void PrintObject::discover_vertical_shells() // static constexpr const float top_bottom_expansion_coeff = 1.05f; // Just a tiny fraction of an infill extrusion width to merge neighbor regions reliably. static constexpr const float top_bottom_expansion_coeff = 0.15f; //TODO check if not too little + static constexpr const float top_bottom_max_expansion_coeff = 1.5f + top_bottom_expansion_coeff; if (top_bottom_surfaces_all_regions) { // This is a multi-material print and interface_shells are disabled, meaning that the vertical shell thickness // is calculated over all materials. + // Is the "ensure vertical wall thickness" applicable to any region? + bool has_extra_layers = false; + for (size_t region_id = 0; region_id < this->num_printing_regions(); ++region_id) { + const PrintRegionConfig &config = this->printing_region(region_id).config(); + if (config.ensure_vertical_shell_thickness.value == EnsureVerticalShellThickness::Enabled || + config.ensure_vertical_shell_thickness.value == EnsureVerticalShellThickness::Enabled_old || + config.ensure_vertical_shell_thickness.value == EnsureVerticalShellThickness::Partial) { + has_extra_layers = true; + break; + } + } + if (!has_extra_layers) { + // The "ensure vertical wall thickness" feature is not applicable to any of the regions. Quit. + return; + } BOOST_LOG_TRIVIAL(debug) << "Discovering vertical shells in parallel - start : cache top / bottom"; const size_t num_regions = this->num_printing_regions(); Slic3r::parallel_for(size_t(0), num_layers, @@ -2326,26 +2630,16 @@ void PrintObject::discover_vertical_shells() const Layer& layer = *m_layers[idx_layer]; DiscoverVerticalShellsCacheEntry& cache = cache_top_botom_regions[idx_layer]; // Simulate single set of perimeters over all merged regions. - float perimeter_offset = 0.f; - float perimeter_min_spacing = FLT_MAX; + coord_t perimeter_offset_for_holes = 0; + coord_t perimeter_min_spacing = std::numeric_limits::max(); #ifdef SLIC3R_DEBUG_SLICE_PROCESSING static size_t debug_idx = 0; ++debug_idx; #endif /* SLIC3R_DEBUG_SLICE_PROCESSING */ for (size_t region_id = 0; region_id < num_regions; ++ region_id) { LayerRegion &layerm = *layer.m_regions[region_id]; - float top_bottom_expansion = float(layerm.flow(frSolidInfill).scaled_spacing()) * top_bottom_expansion_coeff; - // Top surfaces. - append(cache.top_surfaces, offset_ex(to_expolygons(layerm.slices().filter_by_type(stPosTop | stDensSolid)), top_bottom_expansion)); - append(cache.top_surfaces, offset_ex(to_expolygons(layerm.fill_surfaces().filter_by_type(stPosTop | stDensSolid)), top_bottom_expansion)); - append(cache.top_fill_surfaces, offset_ex(to_expolygons(layerm.fill_surfaces().filter_by_type(stPosTop | stDensSolid)), top_bottom_expansion)); - append(cache.top_perimeter_surfaces, to_expolygons(layerm.slices().filter_by_type(stPosTop | stDensSolid))); - // Bottom surfaces. - auto surfaces_bottom = { stPosBottom | stDensSolid, stPosBottom | stDensSolid | stModBridge }; - append(cache.bottom_surfaces, offset_ex(to_expolygons(layerm.slices().filter_by_types(surfaces_bottom)), top_bottom_expansion)); - append(cache.bottom_surfaces, offset_ex(to_expolygons(layerm.fill_surfaces().filter_by_types(surfaces_bottom)), top_bottom_expansion)); - append(cache.bottom_fill_surfaces, offset_ex(to_expolygons(layerm.fill_surfaces().filter_by_types(surfaces_bottom)), top_bottom_expansion)); - append(cache.bottom_perimeter_surfaces, to_expolygons(layerm.slices().filter_by_type(stPosTop | stDensSolid))); + coord_t expansion_solid = 0; + coord_t expansion_bottom_bridge = 0; // Calculate the maximum perimeter offset as if the slice was extruded with a single extruder only. // First find the maxium number of perimeters per region slice. unsigned int perimeters = 0; @@ -2356,19 +2650,49 @@ void PrintObject::discover_vertical_shells() if (perimeters > 0) { Flow extflow = layerm.flow(frExternalPerimeter); Flow flow = layerm.flow(frPerimeter); - perimeter_offset = std::max(perimeter_offset, - 0.5f * float(extflow.scaled_width() + extflow.scaled_spacing()) + (float(perimeters) - 1.f) * flow.scaled_spacing()); - perimeter_min_spacing = std::min(perimeter_min_spacing, float(std::min(extflow.scaled_spacing(), flow.scaled_spacing()))); + coord_t current_shell_width = (extflow.scaled_width() + extflow.scaled_spacing()) / 2 + (perimeters - 1) * flow.scaled_spacing(); + perimeter_offset_for_holes = std::max(perimeter_offset_for_holes, current_shell_width); + perimeter_min_spacing = std::min(perimeter_min_spacing, std::min(extflow.scaled_spacing(), flow.scaled_spacing())); + const bool has_infill = layerm.region().config().fill_density.value > 0.; + //if no infill, reduce the margin for everything to only the perimeter + if (!has_infill) { + coord_t margin = scale_t(layerm.region().config().external_infill_margin.get_abs_value(unscaled(current_shell_width))); + coord_t margin_bridged = scale_t(layerm.region().config().bridged_infill_margin.get_abs_value(extflow.width())); + expansion_solid = std::min(margin, current_shell_width); + expansion_bottom_bridge = std::min(margin_bridged, current_shell_width); + } else { + expansion_solid = scale_t(layerm.region().config().external_infill_margin.get_abs_value(unscaled(current_shell_width))); + expansion_bottom_bridge = scale_t(layerm.region().config().bridged_infill_margin.get_abs_value(extflow.width())); + } } + // I'm not sure of what I want to do here. this doesn't really grow the resulting surface, it just helps with merging. + //coord_t top_bottom_expansion = std::max(expansion_solid, coord_t(layerm.flow(frSolidInfill).scaled_spacing() * top_bottom_expansion_coeff)); + //coord_t bridge_expansion = std::max(expansion_bottom_bridge, coord_t(layerm.flow(frSolidInfill).scaled_spacing() * top_bottom_expansion_coeff)); + coord_t top_bottom_expansion = coord_t(layerm.flow(frSolidInfill).scaled_spacing() * top_bottom_expansion_coeff); + coord_t bridge_expansion = coord_t(layerm.flow(frSolidInfill).scaled_spacing() * top_bottom_expansion_coeff); + // Top surfaces. + append(cache.top_surfaces, offset_ex(to_expolygons(layerm.slices().filter_by_type(stPosTop | stDensSolid)), top_bottom_expansion)); + append(cache.top_surfaces, offset_ex(to_expolygons(layerm.fill_surfaces().filter_by_type(stPosTop | stDensSolid)), top_bottom_expansion)); + append(cache.top_fill_surfaces, offset_ex(to_expolygons(layerm.fill_surfaces().filter_by_type(stPosTop | stDensSolid)), top_bottom_expansion)); + append(cache.top_perimeter_surfaces, to_expolygons(layerm.slices().filter_by_type(stPosTop | stDensSolid))); + // Bottom surfaces. + append(cache.bottom_surfaces, offset_ex(to_expolygons(layerm.slices().filter_by_type(stPosBottom | stDensSolid)), top_bottom_expansion)); + append(cache.bottom_surfaces, offset_ex(to_expolygons(layerm.fill_surfaces().filter_by_type(stPosBottom | stDensSolid)), top_bottom_expansion)); + append(cache.bottom_fill_surfaces, offset_ex(to_expolygons(layerm.fill_surfaces().filter_by_type(stPosBottom | stDensSolid)), top_bottom_expansion)); + append(cache.bottom_perimeter_surfaces, to_expolygons(layerm.slices().filter_by_type(stPosTop | stDensSolid))); + append(cache.bottom_surfaces, offset_ex(to_expolygons(layerm.slices().filter_by_type(stPosBottom | stDensSolid | stModBridge)), bridge_expansion)); + append(cache.bottom_surfaces, offset_ex(to_expolygons(layerm.fill_surfaces().filter_by_type(stPosBottom | stDensSolid | stModBridge)), bridge_expansion)); + append(cache.bottom_fill_surfaces, offset_ex(to_expolygons(layerm.fill_surfaces().filter_by_type(stPosBottom | stDensSolid | stModBridge)), top_bottom_expansion)); + // "holes" expolygons_append(cache.holes, layerm.fill_expolygons()); } // Save some computing time by reducing the number of polygons. cache.top_surfaces = union_ex(cache.top_surfaces); cache.bottom_surfaces = union_ex(cache.bottom_surfaces); // For a multi-material print, simulate perimeter / infill split as if only a single extruder has been used for the whole print. - if (perimeter_offset > 0.) { + if (perimeter_offset_for_holes > 0.) { // The layer.lslices are forced to merge by expanding them first. - expolygons_append(cache.holes, offset2_ex(layer.lslices(), 0.3f * perimeter_min_spacing, -perimeter_offset - 0.3f * perimeter_min_spacing)); + expolygons_append(cache.holes, offset2_ex(layer.lslices(), 0.3f * perimeter_min_spacing, -perimeter_offset_for_holes - 0.3f * perimeter_min_spacing)); #ifdef SLIC3R_DEBUG_SLICE_PROCESSING { Slic3r::SVG svg(debug_out_path("discover_vertical_shells-extra-holes-%d.svg", debug_idx), get_extents(layer.lslices())); @@ -2390,6 +2714,13 @@ void PrintObject::discover_vertical_shells() const PrintRegion ®ion = this->printing_region(region_id); + if (region.config().ensure_vertical_shell_thickness.value != EnsureVerticalShellThickness::Enabled && + region.config().ensure_vertical_shell_thickness.value != EnsureVerticalShellThickness::Enabled_old && + region.config().ensure_vertical_shell_thickness.value != EnsureVerticalShellThickness::Partial) { + // This region will be handled by discover_horizontal_shells(). + continue; + } + //solid_over_perimeters value, to remove solid fill where there's only perimeters on multiple layers const int nb_perimeter_layers_for_solid_fill = region.config().solid_over_perimeters.value; const int min_layer_no_solid = region.config().bottom_solid_layers.value - 1; @@ -2406,8 +2737,39 @@ void PrintObject::discover_vertical_shells() m_print->throw_if_canceled(); Layer &layer = *m_layers[idx_layer]; LayerRegion &layerm = *layer.m_regions[region_id]; - float top_bottom_expansion = float(layerm.flow(frSolidInfill).scaled_spacing()) * top_bottom_expansion_coeff; - float max_top_bottom_expansion = float(layerm.flow(frSolidInfill).scaled_spacing()) * (1.5f + top_bottom_expansion_coeff); + // I'm not sure of what I want to do here. this doesn't really grow the resulting surface, it just helps with merging. + // First find the maxium number of perimeters for the region + //coord_t expansion_solid = 0; + //coord_t expansion_bottom_bridge = 0; + //unsigned int perimeters = 0; + //for (const Surface &s : layerm.slices()) + // perimeters = std::max(perimeters, s.extra_perimeters); + //perimeters += layerm.region().config().perimeters.value; + //// Then calculate the infill offset. + //if (perimeters > 0) { + // Flow extflow = layerm.flow(frExternalPerimeter); + // Flow flow = layerm.flow(frPerimeter); + // coord_t current_shell_width = (extflow.scaled_width() + extflow.scaled_spacing()) / 2 + (perimeters - 1) * flow.scaled_spacing(); + // const bool has_infill = layerm.region().config().fill_density.value > 0.; + // //if no infill, reduce the margin for everything to only the perimeter + // if (!has_infill) { + // coord_t margin = scale_t(layerm.region().config().external_infill_margin.get_abs_value(unscaled(current_shell_width))); + // coord_t margin_bridged = scale_t(layerm.region().config().bridged_infill_margin.get_abs_value(extflow.width())); + // expansion_solid = std::min(margin, current_shell_width); + // expansion_bottom_bridge = std::min(margin_bridged, current_shell_width); + // } else { + // expansion_solid = scale_t(layerm.region().config().external_infill_margin.get_abs_value(unscaled(current_shell_width))); + // expansion_bottom_bridge = scale_t(layerm.region().config().bridged_infill_margin.get_abs_value(extflow.width())); + // } + //} + //coord_t top_bottom_expansion = std::max(expansion_solid, coord_t(layerm.flow(frSolidInfill).scaled_spacing() * top_bottom_expansion_coeff)); + //coord_t top_bottom_max_expansion = std::max(expansion_solid, coord_t(layerm.flow(frSolidInfill).scaled_spacing() * top_bottom_max_expansion_coeff)); + //coord_t bridge_expansion = std::max(expansion_bottom_bridge, coord_t(layerm.flow(frSolidInfill).scaled_spacing() * top_bottom_expansion_coeff)); + //coord_t bridge_max_expansion = std::max(expansion_bottom_bridge, coord_t(layerm.flow(frSolidInfill).scaled_spacing() * top_bottom_max_expansion_coeff)); + coord_t top_bottom_expansion = coord_t(layerm.flow(frSolidInfill).scaled_spacing() * top_bottom_expansion_coeff); + coord_t top_bottom_max_expansion = coord_t(layerm.flow(frSolidInfill).scaled_spacing() * top_bottom_max_expansion_coeff); + coord_t bridge_expansion = coord_t(layerm.flow(frSolidInfill).scaled_spacing() * top_bottom_expansion_coeff); + coord_t bridge_max_expansion = coord_t(layerm.flow(frSolidInfill).scaled_spacing() * top_bottom_max_expansion_coeff); // Top surfaces. auto& cache = cache_top_botom_regions[idx_layer]; ExPolygons raw_slice_temp = to_expolygons(layerm.slices().filter_by_type(stPosTop | stDensSolid)); @@ -2415,20 +2777,28 @@ void PrintObject::discover_vertical_shells() cache.top_surfaces = offset_ex(raw_slice_temp, top_bottom_expansion); append(cache.top_surfaces, offset_ex(raw_fill_temp, top_bottom_expansion)); if (nb_perimeter_layers_for_solid_fill != 0) { - //it needs to be activated and we don't check the firs layers, where everything have to be solid. - cache.top_fill_surfaces = offset_ex(raw_fill_temp, max_top_bottom_expansion); + //it needs to be activated and we don't check the first layers, where everything have to be solid. + cache.top_fill_surfaces = offset_ex(raw_fill_temp, top_bottom_max_expansion); cache.top_perimeter_surfaces = raw_slice_temp; } // Bottom surfaces. - const auto surfaces_bottom = { stPosBottom | stDensSolid, stPosBottom | stDensSolid | stModBridge }; - raw_slice_temp = to_expolygons(layerm.slices().filter_by_types(surfaces_bottom)); - raw_fill_temp = to_expolygons(layerm.fill_surfaces().filter_by_types(surfaces_bottom)); + raw_slice_temp = to_expolygons(layerm.slices().filter_by_type(stPosBottom | stDensSolid)); + raw_fill_temp = to_expolygons(layerm.fill_surfaces().filter_by_type(stPosBottom | stDensSolid)); cache.bottom_surfaces = offset_ex(raw_slice_temp, top_bottom_expansion); append(cache.bottom_surfaces, offset_ex(raw_fill_temp, top_bottom_expansion)); if (nb_perimeter_layers_for_solid_fill != 0) { cache.bottom_perimeter_surfaces = raw_slice_temp; - cache.bottom_fill_surfaces = offset_ex(raw_fill_temp, max_top_bottom_expansion); + cache.bottom_fill_surfaces = offset_ex(raw_fill_temp, top_bottom_max_expansion); } + // bridge surfaces. + raw_slice_temp = to_expolygons(layerm.slices().filter_by_type(stPosBottom | stDensSolid | stModBridge)); + raw_fill_temp = to_expolygons(layerm.fill_surfaces().filter_by_type(stPosBottom | stDensSolid | stModBridge)); + append(cache.bottom_surfaces, offset_ex(raw_slice_temp, bridge_expansion)); + append(cache.bottom_surfaces, offset_ex(raw_fill_temp, bridge_expansion)); + if (nb_perimeter_layers_for_solid_fill != 0) { + append(cache.bottom_perimeter_surfaces, raw_slice_temp); + append(cache.bottom_fill_surfaces, offset_ex(raw_fill_temp, bridge_max_expansion)); + } // Holes over all regions. Only collect them once, they are valid for all idx_region iterations. if (cache.holes.empty()) { for (size_t region_id = 0; region_id < layer.regions().size(); ++ region_id) @@ -2526,7 +2896,9 @@ void PrintObject::discover_vertical_shells() ++ i) { at_least_one_top_projected = true; const DiscoverVerticalShellsCacheEntry &cache = cache_top_botom_regions[i]; - combine_holes(cache.holes); + if (region_config.ensure_vertical_shell_thickness.value != EnsureVerticalShellThickness::Partial) { + combine_holes(cache.holes); + } combine_shells(cache.top_surfaces); if (nb_perimeter_layers_for_solid_fill != 0 && (idx_layer > min_layer_no_solid || print_z < min_z_no_solid)) { if (!cache.top_fill_surfaces.empty()) { @@ -2536,7 +2908,8 @@ void PrintObject::discover_vertical_shells() expolygons_append(max_perimeter_shell, cache.top_perimeter_surfaces); max_perimeter_shell = union_ex(max_perimeter_shell); } - } } + } + } if (!at_least_one_top_projected && i < int(cache_top_botom_regions.size())) { // Lets consider this a special case - with only 1 top solid and minimal shell thickness settings, the // boundaries of solid layers are not anchored over/under perimeters, so lets fix it by adding at least one @@ -2563,7 +2936,9 @@ void PrintObject::discover_vertical_shells() -- i) { at_least_one_bottom_projected = true; const DiscoverVerticalShellsCacheEntry &cache = cache_top_botom_regions[i]; - combine_holes(cache.holes); + if (region_config.ensure_vertical_shell_thickness.value != EnsureVerticalShellThickness::Partial) { + combine_holes(cache.holes); + } combine_shells(cache.bottom_surfaces); if (nb_perimeter_layers_for_solid_fill != 0 && (idx_layer > min_layer_no_solid || layer->print_z < min_z_no_solid)) { if (!cache.bottom_fill_surfaces.empty()) { @@ -2593,14 +2968,10 @@ void PrintObject::discover_vertical_shells() { Slic3r::SVG svg(debug_out_path("discover_vertical_shells-perimeters-before-union-%d.svg", debug_idx), get_extents(shell)); svg.draw(shell); - svg.draw_outline(shell, "black", scale_(0.05)); + svg.draw_outline(to_polygons(shell), "black", scale_(0.05)); svg.Close(); } #endif /* SLIC3R_DEBUG_SLICE_PROCESSING */ -#if 0 -// shell = union_(shell, true); - shell = union_(shell, false); -#endif #ifdef SLIC3R_DEBUG_SLICE_PROCESSING shell_ex = union_safety_offset_ex(shell); #endif /* SLIC3R_DEBUG_SLICE_PROCESSING */ @@ -2620,24 +2991,24 @@ void PrintObject::discover_vertical_shells() #ifdef SLIC3R_DEBUG_SLICE_PROCESSING { Slic3r::SVG svg(debug_out_path("discover_vertical_shells-internal-wshell-%d.svg", debug_idx), get_extents(shell)); - svg.draw(layerm->fill_surfaces().filter_by_type(stInternal), "yellow", 0.5); - svg.draw_outline(layerm->fill_surfaces().filter_by_type(stInternal), "black", "blue", scale_(0.05)); + svg.draw(layerm->fill_surfaces().filter_by_type(stPosInternal | stDensSparse), "yellow", 0.5); + svg.draw_outline(layerm->fill_surfaces().filter_by_type(stPosInternal | stDensSparse), "black", "blue", scale_(0.05)); svg.draw(shell_ex, "blue", 0.5); svg.draw_outline(shell_ex, "black", "blue", scale_(0.05)); svg.Close(); } { Slic3r::SVG svg(debug_out_path("discover_vertical_shells-internalvoid-wshell-%d.svg", debug_idx), get_extents(shell)); - svg.draw(layerm->fill_surfaces().filter_by_type(stInternalVoid), "yellow", 0.5); - svg.draw_outline(layerm->fill_surfaces().filter_by_type(stInternalVoid), "black", "blue", scale_(0.05)); + svg.draw(layerm->fill_surfaces().filter_by_type(stPosInternal | stDensVoid), "yellow", 0.5); + svg.draw_outline(layerm->fill_surfaces().filter_by_type(stPosInternal | stDensVoid), "black", "blue", scale_(0.05)); svg.draw(shell_ex, "blue", 0.5); svg.draw_outline(shell_ex, "black", "blue", scale_(0.05)); svg.Close(); } { Slic3r::SVG svg(debug_out_path("discover_vertical_shells-internalsolid-wshell-%d.svg", debug_idx), get_extents(shell)); - svg.draw(layerm->fill_surfaces().filter_by_type(stInternalSolid), "yellow", 0.5); - svg.draw_outline(layerm->fill_surfaces().filter_by_type(stInternalSolid), "black", "blue", scale_(0.05)); + svg.draw(layerm->fill_surfaces().filter_by_type(stPosInternal | stDensSolid), "yellow", 0.5); + svg.draw_outline(layerm->fill_surfaces().filter_by_type(stPosInternal | stDensSolid), "black", "blue", scale_(0.05)); svg.draw(shell_ex, "blue", 0.5); svg.draw_outline(shell_ex, "black", "blue", scale_(0.05)); svg.Close(); @@ -2645,8 +3016,11 @@ void PrintObject::discover_vertical_shells() #endif /* SLIC3R_DEBUG_SLICE_PROCESSING */ // Trim the shells region by the internal & internal void surfaces. - const ExPolygons polygonsInternal = to_expolygons(layerm->fill_surfaces() - .filter_by_types({ stPosInternal | stDensSparse, stPosInternal | stDensVoid, stPosInternal | stDensSolid })); + const ExPolygons polygonsInternal = + union_safety_offset_ex( + to_expolygons(layerm->fill_surfaces().filter_by_types( + { stPosInternal | stDensSparse, stPosInternal | stDensVoid, stPosInternal | stDensSolid })) + ); { shell = intersection_ex(shell, polygonsInternal, ApplySafetyOffset::Yes); expolygons_append(shell, diff_ex(polygonsInternal, holes)); @@ -2675,7 +3049,7 @@ void PrintObject::discover_vertical_shells() // only fills regions, which fit at least a single line. To avoid gaps in the sparse infill, // make sure that this region does not contain parts narrower than the infill spacing width. #ifdef SLIC3R_DEBUG_SLICE_PROCESSING - Polygons shell_before = shell; + ExPolygons shell_before = shell; #endif /* SLIC3R_DEBUG_SLICE_PROCESSING */ ExPolygons regularized_shell; { @@ -4011,11 +4385,188 @@ void PrintObject::discover_horizontal_shells() SurfaceType type = (region_config.fill_density == 100 || region_config.solid_infill_every_layers == 1) ? (stPosInternal | stDensSolid) : (stPosInternal | stDensSolid | stModBridge); - for (Surface& surface : layerm->m_fill_surfaces.surfaces) + for (Surface& surface : layerm->set_fill_surfaces().surfaces) if (surface.surface_type == (stPosInternal | stDensSparse)) surface.surface_type = type; + } + + // If ensure_vertical_shell_thickness, then the rest has already been performed by discover_vertical_shells(). + if (region_config.ensure_vertical_shell_thickness.value != EnsureVerticalShellThickness::Disabled) + continue; + + assert(region_config.ensure_vertical_shell_thickness.value == EnsureVerticalShellThickness::Disabled); + + coordf_t print_z = layer->print_z; + coordf_t bottom_z = layer->bottom_z(); + // 0: topSolid, 1: botSolid, 2: boSolidBridged + for (size_t idx_surface_type = 0; idx_surface_type < 3; ++idx_surface_type) { + m_print->throw_if_canceled(); + SurfaceType type = (idx_surface_type == 0) ? (stPosTop | stDensSolid) : + ((idx_surface_type == 1) ? (stPosBottom | stDensSolid) : + (stPosBottom | stDensSolid | stModBridge)); + int num_solid_layers = ((type & stPosTop) == stPosTop) ? region_config.top_solid_layers.value : region_config.bottom_solid_layers.value; + if (num_solid_layers == 0) + continue; + // Find slices of current type for current layer. + // Use slices instead of fill_surfaces, because they also include the perimeter area, + // which needs to be propagated in shells; we need to grow slices like we did for + // fill_surfaces though. Using both ungrown slices and grown fill_surfaces will + // not work in some situations, as there won't be any grown region in the perimeter + // area (this was seen in a model where the top layer had one extra perimeter, thus + // its fill_surfaces were thinner than the lower layer's infill), however it's the best + // solution so far. Growing the external slices by external_infill_margin will put + // too much solid infill inside nearly-vertical slopes. + + // Surfaces including the area of perimeters. Everything, that is visible from the top / bottom + // (not covered by a layer above / below). + // This does not contain the areas covered by perimeters! + ExPolygons solid; + for (const Surface& surface : layerm->slices().surfaces) + if (surface.surface_type == type) + solid.push_back(surface.expolygon); + // Infill areas (slices without the perimeters). + for (const Surface& surface : layerm->fill_surfaces().surfaces) + if (surface.surface_type == type) + solid.push_back(surface.expolygon); + if (solid.empty()) + continue; + solid = union_ex(solid); + // Slic3r::debugf "Layer %d has %s surfaces\n", $i, (($type & stTop) != 0) ? 'top' : 'bottom'; + + // Scatter top / bottom regions to other layers. Scattering process is inherently serial, it is difficult to parallelize without locking. + for (int n = ((type & stPosTop) == stPosTop) ? int(i) - 1 : int(i) + 1; + + ((type & stPosTop) == stPosTop) ? + (n >= 0 && (int(i) - n < num_solid_layers || + print_z - m_layers[n]->print_z < region_config.top_solid_min_thickness.value - EPSILON)) : + (n < int(m_layers.size()) && (n - int(i) < num_solid_layers || + m_layers[n]->bottom_z() - bottom_z < region_config.bottom_solid_min_thickness.value - EPSILON)); + + ((type & stPosTop) == stPosTop) ? --n : ++n) + { + // Slic3r::debugf " looking for neighbors on layer %d...\n", $n; + // Reference to the lower layer of a TOP surface, or an upper layer of a BOTTOM surface. + LayerRegion* neighbor_layerm = m_layers[n]->regions()[region_id]; + + // find intersection between neighbor and current layer's surfaces + // intersections have contours and holes + // we update $solid so that we limit the next neighbor layer to the areas that were + // found on this one - in other words, solid shells on one layer (for a given external surface) + // are always a subset of the shells found on the previous shell layer + // this approach allows for DWIM in hollow sloping vases, where we want bottom + // shells to be generated in the base but not in the walls (where there are many + // narrow bottom surfaces): reassigning $solid will consider the 'shadow' of the + // upper perimeter as an obstacle and shell will not be propagated to more upper layers + //FIXME How does it work for stInternalBRIDGE? This is set for sparse infill. Likely this does not work. + ExPolygons new_internal_solid; + { + ExPolygons internal; + for (const Surface& surface : neighbor_layerm->fill_surfaces().surfaces) + if (surface.has_pos_internal() && (surface.has_fill_sparse() || surface.has_fill_solid())) + internal.push_back(surface.expolygon); + internal = union_ex(internal); + new_internal_solid = intersection_ex(solid, internal, ApplySafetyOffset::Yes); + } + if (new_internal_solid.empty()) { + // No internal solid needed on this layer. In order to decide whether to continue + // searching on the next neighbor (thus enforcing the configured number of solid + // layers, use different strategies according to configured infill density: + if (region_config.fill_density.value == 0) { + // If user expects the object to be void (for example a hollow sloping vase), + // don't continue the search. In this case, we only generate the external solid + // shell if the object would otherwise show a hole (gap between perimeters of + // the two layers), and internal solid shells are a subset of the shells found + // on each previous layer. + goto EXTERNAL; + } else { + // If we have internal infill, we can generate internal solid shells freely. + continue; + } + } + + if (region_config.fill_density.value == 0 && !m_print->config().spiral_vase.value) { + // if we're printing a hollow object we discard any solid shell thinner + // than a perimeter width, since it's probably just crossing a sloping wall + // and it's not wanted in a hollow print even if it would make sense when + // obeying the solid shell count option strictly (DWIM!) + // (disregard if sprial vase, as it's a completly different process) + float margin = float(neighbor_layerm->flow(frExternalPerimeter).scaled_width()); + ExPolygons too_narrow = diff_ex( + new_internal_solid, + opening(new_internal_solid, margin, margin + ClipperSafetyOffset, jtMiter, 5)); //-+ + // Trim the regularized region by the original region. + if (!too_narrow.empty()) { + solid = new_internal_solid = diff_ex(new_internal_solid, too_narrow); + } + } + + + //merill: this is creating artifacts, and i can't recreate the issue it wants to fix. + + // make sure the new internal solid is wide enough, as it might get collapsed + // when spacing is added in Fill.pm + if(false){ + //FIXME Vojtech: Disable this and you will be sorry. + // https://github.com/prusa3d/PrusaSlicer/issues/26 bottom + float margin = 3.f * layerm->flow(frSolidInfill).scaled_width(); // require at least this size + // we use a higher miterLimit here to handle areas with acute angles + // in those cases, the default miterLimit would cut the corner and we'd + // get a triangle in $too_narrow; if we grow it below then the shell + // would have a different shape from the external surface and we'd still + // have the same angle, so the next shell would be grown even more and so on. + ExPolygons too_narrow = diff_ex( + new_internal_solid, + opening(new_internal_solid, margin, margin + ClipperSafetyOffset, ClipperLib::jtMiter, 5)); // -+ + if (!too_narrow.empty()) { + // grow the collapsing parts and add the extra area to the neighbor layer + // as well as to our original surfaces so that we support this + // additional area in the next shell too + // make sure our grown surfaces don't exceed the fill area + ExPolygons internal; + for (const Surface& surface : neighbor_layerm->fill_surfaces().surfaces) + if (surface.has_pos_internal() && !surface.has_mod_bridge()) + internal.push_back(surface.expolygon); + expolygons_append(new_internal_solid, + intersection_ex( + offset_ex(too_narrow, +margin), //expand_ex + // Discard bridges as they are grown for anchoring and we can't + // remove such anchors. (This may happen when a bridge is being + // anchored onto a wall where little space remains after the bridge + // is grown, and that little space is an internal solid shell so + // it triggers this too_narrow logic.) + union_ex(internal))); + // see https://github.com/prusa3d/PrusaSlicer/pull/3426 + // solid = new_internal_solid; + } + } + + // internal-solid are the union of the existing internal-solid surfaces + // and new ones + SurfaceCollection backup = std::move(neighbor_layerm->set_fill_surfaces()); + expolygons_append(new_internal_solid, to_expolygons(backup.filter_by_type(stPosInternal | stDensSolid))); + ExPolygons internal_solid = union_ex(new_internal_solid); + // assign new internal-solid surfaces to layer + neighbor_layerm->set_fill_surfaces().set(internal_solid, stPosInternal | stDensSolid); + // subtract intersections from layer surfaces to get resulting internal surfaces + //ExPolygons polygons_internal = to_polygons(std::move(internal_solid)); + ExPolygons internal = diff_ex(to_expolygons(backup.filter_by_type(stPosInternal | stDensSparse)), internal_solid, ApplySafetyOffset::Yes); + // assign resulting internal surfaces to layer + neighbor_layerm->set_fill_surfaces().append(internal, stPosInternal | stDensSparse); + expolygons_append(internal_solid, internal); + // assign top and bottom surfaces to layer + backup.keep_types({ stPosTop | stDensSolid, stPosBottom | stDensSolid, stPosBottom | stDensSolid | stModBridge }); + //backup.keep_types_flag(stPosTop | stPosBottom); + std::vector top_bottom_groups; + backup.group(&top_bottom_groups); + for (SurfacesPtr& group : top_bottom_groups) { + neighbor_layerm->set_fill_surfaces().append( + diff_ex(to_expolygons(group), union_ex(internal_solid)), + // Use an existing surface as a template, it carries the bridge angle etc. + *group.front()); + } } - // The rest has already been performed by discover_vertical_shells(). + EXTERNAL:; + } // foreach type (stTop, stBottom, stBottomBridge) } // for each layer } // for each region @@ -4031,15 +4582,18 @@ void PrintObject::discover_horizontal_shells() } // void PrintObject::discover_horizontal_shells() void merge_surfaces(LayerRegion* lregion) { + for (const Surface &srf : lregion->fill_surfaces().surfaces) { + srf.expolygon.assert_valid(); + } coord_t scaled_resolution = std::max(SCALED_EPSILON, scale_t(lregion->layer()->object()->print()->config().resolution.value)); //merge regions with same type (other things are all the same anyway) - std::map< SurfaceType, std::vector> type2srfs; + std::map> type2srfs; for (const Surface& surface : lregion->fill_surfaces().surfaces) { type2srfs[surface.surface_type].push_back(&surface); } bool changed = false; - std::map< SurfaceType, ExPolygons> type2newpolys; + std::map type2newpolys; for (auto& entry : type2srfs) { if (entry.second.size() > 2) { ExPolygons merged = ensure_valid(union_safety_offset_ex(to_expolygons(entry.second)), scaled_resolution); @@ -4072,6 +4626,9 @@ void PrintObject::clean_surfaces() { Slic3r::parallel_for(size_t(0), this->layers().size() - 1, [this](const size_t idx_layer) { for (LayerRegion* lregion : this->layers()[idx_layer]->regions()) { + for (const Surface &srf : lregion->fill_surfaces().surfaces) { + srf.expolygon.assert_valid(); + } coord_t extrusion_width = lregion->flow(frInfill).scaled_width(); merge_surfaces(lregion); // collapse too thin solid surfaces. diff --git a/src/slic3r/GUI/ConfigManipulation.cpp b/src/slic3r/GUI/ConfigManipulation.cpp index cefe2069d6c..ced2da9b245 100644 --- a/src/slic3r/GUI/ConfigManipulation.cpp +++ b/src/slic3r/GUI/ConfigManipulation.cpp @@ -454,9 +454,10 @@ void ConfigManipulation::toggle_print_fff_options(DynamicPrintConfig* config) toggle_field("small_area_infill_flow_compensation", has_solid_infill); bool have_small_area_infill_flow_compensation = has_solid_infill && config->opt_bool("small_area_infill_flow_compensation"); toggle_field("small_area_infill_flow_compensation_model", have_small_area_infill_flow_compensation); - - toggle_field("top_solid_min_thickness", ! has_spiral_vase && has_top_solid_infill); - toggle_field("bottom_solid_min_thickness", ! has_spiral_vase && has_bottom_solid_infill); + + const bool has_ensure_vertical_shell_thickness = config->opt_enum("ensure_vertical_shell_thickness") != EnsureVerticalShellThickness::Disabled; + toggle_field("top_solid_min_thickness", ! has_spiral_vase && has_top_solid_infill && has_ensure_vertical_shell_thickness); + toggle_field("bottom_solid_min_thickness", ! has_spiral_vase && has_bottom_solid_infill && has_ensure_vertical_shell_thickness); //speed for (auto el : { "small_perimeter_min_length", "small_perimeter_max_length" }) From b1bef3511e60c512ab7c13f87b5ad32c08e6122a Mon Sep 17 00:00:00 2001 From: supermerill Date: Sun, 24 Nov 2024 16:00:00 +0100 Subject: [PATCH 12/13] fix bef622 --- src/libslic3r/Polyline.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libslic3r/Polyline.cpp b/src/libslic3r/Polyline.cpp index 503d8825050..76adf7ddd7d 100644 --- a/src/libslic3r/Polyline.cpp +++ b/src/libslic3r/Polyline.cpp @@ -1251,7 +1251,7 @@ Geometry::ArcWelder::Path ArcPolyline::_from_polyline(const Points &poly) path.emplace_back(std::move(point), 0, Geometry::ArcWelder::Orientation::Unknown); return path; } -#pragma UNOPTIMIZE + Geometry::ArcWelder::Path ArcPolyline::_from_polyline(std::initializer_list poly) { Geometry::ArcWelder::Path path; From 8e6523dabbc5aa46a8f548f9aef6f62e15990472 Mon Sep 17 00:00:00 2001 From: supermerill Date: Sun, 24 Nov 2024 16:53:25 +0100 Subject: [PATCH 13/13] fix ensure_verti --- src/libslic3r/PrintConfig.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libslic3r/PrintConfig.cpp b/src/libslic3r/PrintConfig.cpp index 6bffffc39f4..f9fc61c97d4 100644 --- a/src/libslic3r/PrintConfig.cpp +++ b/src/libslic3r/PrintConfig.cpp @@ -8907,7 +8907,7 @@ void PrintConfigDef::handle_legacy_composite(DynamicPrintConfig &config, std::ve if (!config.has("ensure_vertical_shell_thickness") && config.has("perimeters")) { - config.set_key_value("ensure_vertical_shell_thickness", new ConfigOptionEnum(EnsureVerticalShellThickness::Partial)); + config.set_key_value("ensure_vertical_shell_thickness", new ConfigOptionEnum(EnsureVerticalShellThickness::Enabled)); } //if (config.has("thumbnails")) {