From 6002153c7fc9ff6cbbf1bb1af1363213b74cd230 Mon Sep 17 00:00:00 2001 From: supermerill Date: Sat, 23 Nov 2024 17:30:07 +0100 Subject: [PATCH 01/12] fix commit "fix fill ordering by island" (e1dc8) --- src/libslic3r/Fill/Fill.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/libslic3r/Fill/Fill.cpp b/src/libslic3r/Fill/Fill.cpp index 351cd2fc6a2..6e963dc2a40 100644 --- a/src/libslic3r/Fill/Fill.cpp +++ b/src/libslic3r/Fill/Fill.cpp @@ -880,8 +880,9 @@ 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.size()) fillsbypriority.emplace_back(); + assert(priority < fills_by_priority.size()); if (!fills_by_priority[priority][fill_idx]->empty()) { fillsbypriority[priority].push_back(fills_by_priority[priority][fill_idx]); } else { From 1cb65ce7fe2018f8048d5ca69e7dde0856bbaba1 Mon Sep 17 00:00:00 2001 From: supermerill Date: Wed, 13 Nov 2024 14:54:06 +0100 Subject: [PATCH 02/12] 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 72c37af1da3340592c3246e2ddd1324b9f4b9182 Mon Sep 17 00:00:00 2001 From: supermerill Date: Wed, 20 Nov 2024 00:37:06 +0100 Subject: [PATCH 03/12] 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 | 156 +++++++++++++++++--- 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/Surface.cpp | 17 ++- src/libslic3r/SurfaceCollection.cpp | 2 +- 15 files changed, 196 insertions(+), 62 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..b315a45663c 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,20 +562,28 @@ 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); + expolygons.erase(expolygons.begin() + i); --i; } else { if (!expolygons[i].contour.is_counter_clockwise()) { 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/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++; } } } 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 49842817bc4e46cb6cca840967b772abef7c605d Mon Sep 17 00:00:00 2001 From: supermerill Date: Wed, 20 Nov 2024 01:01:43 +0100 Subject: [PATCH 04/12] resolution --- src/libslic3r/Fill/FillPlanePath.cpp | 1 + src/libslic3r/LayerRegion.cpp | 21 +++++++++-- src/libslic3r/PrintObject.cpp | 53 +++++++++++++++++++++++++++- src/libslic3r/PrintObjectSlice.cpp | 4 +-- 4 files changed, 74 insertions(+), 5 deletions(-) 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) { diff --git a/src/libslic3r/LayerRegion.cpp b/src/libslic3r/LayerRegion.cpp index 9e962b015d0..9788f0b28c1 100644 --- a/src/libslic3r/LayerRegion.cpp +++ b/src/libslic3r/LayerRegion.cpp @@ -475,6 +475,22 @@ static Surfaces expand_merge_surfaces( void LayerRegion::process_external_surfaces(const Layer *lower_layer, const Polygons *lower_layer_covered) { using namespace Slic3r::Algorithm; +#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(); + } + assert(area < SCALED_EPSILON * SCALED_EPSILON /** 100*/); + } + } + } +#endif #ifdef SLIC3R_DEBUG_SLICE_PROCESSING export_region_fill_surfaces_to_svg_debug("4_process_external_surfaces-initial"); @@ -889,6 +905,7 @@ void LayerRegion::process_external_surfaces(const Layer *lower_layer, const Poly } Surfaces new_surfaces; + const coord_t scaled_resolution = std::max(SCALED_EPSILON, scale_t(this->layer()->object()->print()->config().resolution.value)); { // Intersect the grown surfaces with the actual fill boundaries. Polygons bottom_polygons = to_polygons(bottom); @@ -913,7 +930,7 @@ void LayerRegion::process_external_surfaces(const Layer *lower_layer, const Poly surfaces_append( new_surfaces, // Don't use a safety offset as fill_boundaries were already united using the safety offset. - intersection_ex(polys, fill_boundaries), + ensure_valid(intersection_ex(polys, fill_boundaries), scaled_resolution), s1); } } @@ -935,7 +952,7 @@ void LayerRegion::process_external_surfaces(const Layer *lower_layer, const Poly } ExPolygons new_expolys = diff_ex(polys, new_polygons); polygons_append(new_polygons, to_polygons(new_expolys)); - surfaces_append(new_surfaces, std::move(new_expolys), s1); + surfaces_append(new_surfaces, ensure_valid(std::move(new_expolys), scaled_resolution), s1); } m_fill_surfaces.surfaces = std::move(new_surfaces); diff --git a/src/libslic3r/PrintObject.cpp b/src/libslic3r/PrintObject.cpp index 5d50c220d0b..d9e10c6ad16 100644 --- a/src/libslic3r/PrintObject.cpp +++ b/src/libslic3r/PrintObject.cpp @@ -454,6 +454,27 @@ void PrintObject::prepare_infill() } this->clean_surfaces(); +#ifdef _DEBUG + //assert each surface is not on top of each other (or almost) + for (size_t region_id = 0; region_id < this->num_printing_regions(); ++region_id) { + for (const Layer *layer : m_layers) { + for (auto &srf : layer->m_regions[region_id]->fill_surfaces().surfaces) { + for (auto &srf2 : layer->m_regions[region_id]->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(); + } + assert(area < SCALED_EPSILON * SCALED_EPSILON /** 100*/); + } + } + } + } + } +#endif + #ifdef SLIC3R_DEBUG_SLICE_PROCESSING for (size_t region_id = 0; region_id < this->num_printing_regions(); ++ region_id) { for (const Layer *layer : m_layers) { @@ -2208,6 +2229,16 @@ void PrintObject::process_external_surfaces() { BOOST_LOG_TRIVIAL(info) << "Processing external surfaces..." << log_memory_info(); +#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 + // Cached surfaces covered by some extrusion, defining regions, over which the from the surfaces one layer higher are allowed to expand. std::vector surfaces_covered; // Is there any printing region, that has zero infill? If so, then we don't want the expansion to be performed over the complete voids, but only @@ -2264,7 +2295,17 @@ void PrintObject::process_external_surfaces() BOOST_LOG_TRIVIAL(debug) << "Collecting surfaces covered with extrusions in parallel - end"; } - for (size_t region_id = 0; region_id < this->num_printing_regions(); ++region_id) { +#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 + + 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) { @@ -2282,6 +2323,16 @@ void PrintObject::process_external_surfaces() BOOST_LOG_TRIVIAL(debug) << "Processing external surfaces for region " << region_id << " in parallel - end"; } +#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 + if (this->has_raft() && ! m_layers.empty()) { // Adjust bridge direction of 1st object layer over raft to be perpendicular to the raft contact layer direction. Layer &layer = *m_layers.front(); 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 828e302fad8248e23f5c887f0561b061ee3f1b7c Mon Sep 17 00:00:00 2001 From: supermerill Date: Sun, 24 Nov 2024 14:57:22 +0100 Subject: [PATCH 05/12] restore ensure_vertical_shell_thickness & external_infill_margin, bridged_infill_margin: * 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 investigation 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 | 131 ++++- 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 | 592 +++++++++++++++++--- src/slic3r/GUI/ConfigManipulation.cpp | 7 +- 13 files changed, 716 insertions(+), 143 deletions(-) diff --git a/resources/ui_layout/default/print.ui b/resources/ui_layout/default/print.ui index 9298f9f337b..df50188c03a 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 @@ -544,8 +545,8 @@ group:Wipe tower end_line 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 @@ -571,7 +572,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 9788f0b28c1..43b589c829b 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; @@ -486,7 +492,8 @@ void LayerRegion::process_external_surfaces(const Layer *lower_layer, const Poly for (auto &expoly : intersect) { area += expoly.area(); } - assert(area < SCALED_EPSILON * SCALED_EPSILON /** 100*/); + // assert(area < SCALED_EPSILON * SCALED_EPSILON /** 100*/); + assert(area < scale_t(1) * scale_t(1)); } } } @@ -497,8 +504,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); @@ -507,18 +514,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)); @@ -527,7 +558,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); { @@ -535,7 +578,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); @@ -551,14 +598,31 @@ void LayerRegion::process_external_surfaces(const Layer *lower_layer, const Poly } 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 + ); 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 + ); + + m_fill_surfaces.remove_types({ + stPosBottom | stDensSolid | stModBridge, + stPosBottom | stDensSolid, + stPosTop | stDensSolid, + stPosInternal | stDensSparse, + stPosInternal | stDensSolid }); +#ifdef _DEBUG + 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, {}); @@ -579,24 +643,43 @@ void LayerRegion::process_external_surfaces(const Layer *lower_layer, const Poly 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(); + } + // assert(area < SCALED_EPSILON * SCALED_EPSILON /** 100*/); + assert(area < scale_t(1) * scale_t(1)); + } + } + } +#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 ed872b06970..23ab27a2be4 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"); @@ -8363,8 +8385,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. @@ -8491,6 +8511,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"; @@ -8882,6 +8913,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::Enabled)); + } + //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 506852601ac..a121b3fd004 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 d9e10c6ad16..3b7e91350c5 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,20 @@ 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)); + } + } + } +#endif // This will assign a type (top/bottom/internal) to $layerm->slices. // Then the classifcation of $layerm->slices is transfered onto @@ -350,6 +367,21 @@ 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)); + } + } + } +#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 +406,66 @@ 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)); + } + } + } +#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)); + } + } + } +#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 +475,75 @@ 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 (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 + + 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 (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 + } // Debugging output. #ifdef SLIC3R_DEBUG_SLICE_PROCESSING @@ -400,27 +556,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 +577,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); @@ -467,7 +612,8 @@ void PrintObject::prepare_infill() for (auto &expoly : intersect) { area += expoly.area(); } - assert(area < SCALED_EPSILON * SCALED_EPSILON /** 100*/); + // assert(area < SCALED_EPSILON * SCALED_EPSILON /** 100*/); + assert(area < scale_t(1) * scale_t(1)); } } } @@ -2129,7 +2275,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); @@ -2225,7 +2371,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(); @@ -2308,15 +2454,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(); @@ -2365,9 +2519,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, @@ -2377,26 +2547,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; @@ -2407,19 +2567,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())); @@ -2441,6 +2631,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; @@ -2457,8 +2654,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)); @@ -2466,20 +2694,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) @@ -2577,7 +2813,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()) { @@ -2587,7 +2825,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 @@ -2614,7 +2853,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()) { @@ -2644,14 +2885,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 */ @@ -2671,24 +2908,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(); @@ -2696,8 +2933,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)); @@ -2726,7 +2966,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; { @@ -4049,6 +4289,7 @@ bool PrintObject::update_layer_height_profile(const ModelObject& model_object, c void PrintObject::discover_horizontal_shells() { BOOST_LOG_TRIVIAL(trace) << "discover_horizontal_shells()"; + coord_t scaled_resolution = std::max(SCALED_EPSILON, scale_t(this->print()->config().resolution.value)); for (size_t region_id = 0; region_id < this->num_printing_regions(); ++region_id) { for (size_t i = 0; i < m_layers.size(); ++i) { @@ -4062,11 +4303,198 @@ 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; + } + for (const Surface &srf : layerm->fill_surfaces().surfaces) { + srf.expolygon.assert_valid(); + } + + // 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; + } + } + for (const Surface &srf : layerm->fill_surfaces().surfaces) { + srf.expolygon.assert_valid(); + } + + // 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 = ensure_valid(union_ex(new_internal_solid), scaled_resolution); + // 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 expolys_internal = diff_ex(to_expolygons(backup.filter_by_type(stPosInternal | stDensSparse)), internal_solid, ApplySafetyOffset::Yes); + ensure_valid(expolys_internal, scaled_resolution); + // assign resulting internal surfaces to layer + neighbor_layerm->set_fill_surfaces().append(expolys_internal, stPosInternal | stDensSparse); + expolygons_append(internal_solid, expolys_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( + ensure_valid(diff_ex(to_expolygons(group), union_ex(internal_solid)), scaled_resolution), + // 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 (const Surface &srf : layerm->fill_surfaces().surfaces) { + srf.expolygon.assert_valid(); + } } // for each layer } // for each region @@ -4082,15 +4510,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); @@ -4123,6 +4554,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 831032365a8..98f2b2269f4 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 0ee74d096b353242bb9f7948f3309d3d269d7301 Mon Sep 17 00:00:00 2001 From: supermerill Date: Sun, 24 Nov 2024 19:46:45 +0100 Subject: [PATCH 06/12] gcode writer: don't write axes that doesn't change --- src/libslic3r/Extruder.cpp | 11 +++ src/libslic3r/Extruder.hpp | 2 + src/libslic3r/GCode/GCodeFormatter.cpp | 10 +++ src/libslic3r/GCode/GCodeFormatter.hpp | 45 +++++++----- src/libslic3r/GCode/GCodeWriter.cpp | 96 +++++++++++++++++++------- src/libslic3r/GCode/GCodeWriter.hpp | 19 ++--- 6 files changed, 129 insertions(+), 54 deletions(-) diff --git a/src/libslic3r/Extruder.cpp b/src/libslic3r/Extruder.cpp index da463527c0d..c7b2e1fb5c2 100644 --- a/src/libslic3r/Extruder.cpp +++ b/src/libslic3r/Extruder.cpp @@ -57,6 +57,17 @@ std::pair Tool::extrude(double dE) return std::make_pair(dE, m_E); } +void Tool::cancel_extrude(double dE) { + dE = m_formatter.quantize_e(dE); + if (m_config->use_relative_e_distances) + m_E = 0.; + else + m_E -= dE; + m_absolute_E -= dE; + if (dE < 0.) + m_retracted += dE; +} + /* This method makes sure the extruder is retracted by the specified amount of filament and returns the amount of filament retracted. If the extruder is already retracted by the same or a greater amount, diff --git a/src/libslic3r/Extruder.hpp b/src/libslic3r/Extruder.hpp index 164c9701954..9b48fca4fa0 100644 --- a/src/libslic3r/Extruder.hpp +++ b/src/libslic3r/Extruder.hpp @@ -41,6 +41,8 @@ class Tool // second - number to emit to G-code: This may be delta for relative mode or a distance from last reset_E() for absolute mode. // They also quantize the E axis to G-code resolution. virtual std::pair extrude(double dE); + // if the extrude fail, this function is called to remove the amount for the internal counters. + virtual void cancel_extrude(double dE); virtual std::pair retract(double retract_length, std::optional restart_extra, std::optional restart_extra_from_toolchange); virtual std::pair unretract(); virtual void reset_retract(); diff --git a/src/libslic3r/GCode/GCodeFormatter.cpp b/src/libslic3r/GCode/GCodeFormatter.cpp index 16ab40af39e..d2a750f7202 100644 --- a/src/libslic3r/GCode/GCodeFormatter.cpp +++ b/src/libslic3r/GCode/GCodeFormatter.cpp @@ -23,6 +23,16 @@ bool GCodeFormatter::emit_xy(const Vec2d &point, std::string &old_x, std::string return !same_point; } +bool GCodeFormatter::emit_z(const double pt_z, std::string &old_z) +{ + char* start_digit = this->emit_axis('Z', pt_z, m_gcode_precision_xyz); + std::string z_str = std::string(start_digit, this->ptr_err.ptr); + bool same_point = (z_str == old_z); + // update str + old_z = z_str; + return !same_point; +} + // return the de that isn't emmited as it's truncated double GCodeFormatter::emit_e(const std::string_view axis, double v) { diff --git a/src/libslic3r/GCode/GCodeFormatter.hpp b/src/libslic3r/GCode/GCodeFormatter.hpp index f09e928c04c..cd0c67ba158 100644 --- a/src/libslic3r/GCode/GCodeFormatter.hpp +++ b/src/libslic3r/GCode/GCodeFormatter.hpp @@ -36,6 +36,11 @@ class GCodeFormatter this->ptr_err.ptr = this->buf; } + virtual void clear() { + this->buf_end = buf + buflen; + this->ptr_err.ptr = this->buf; + } + //GCodeFormatter(const GCodeFormatter &) = delete; //GCodeFormatter &operator=(const GCodeFormatter &) = delete; @@ -74,25 +79,12 @@ class GCodeFormatter } // retunr the pointer to the begining of the digit written (without the axis) + // please don't use it but the other safer methods, if available. char* emit_axis(const char axis, const double v, size_t digits); - - void emit_xy(const Vec2d &point) - { - this->emit_axis('X', point.x(), m_gcode_precision_xyz); - this->emit_axis('Y', point.y(), m_gcode_precision_xyz); - } // update old_x & old_y with new strings. Return false if they are both the same. bool emit_xy(const Vec2d &point, std::string &old_x, std::string &old_y); - - void emit_xyz(const Vec3d &point) - { - this->emit_axis('X', point.x(), m_gcode_precision_xyz); - this->emit_axis('Y', point.y(), m_gcode_precision_xyz); - this->emit_z(point.z()); - } - - void emit_z(const double z) { this->emit_axis('Z', z, m_gcode_precision_xyz); } + bool emit_z(const double z, std::string &old_z); void emit_ij(const Vec2d &point) { @@ -172,27 +164,42 @@ class GCodeG1Formatter : public GCodeFormatter { GCodeG1Formatter(const GCodeG1Formatter&) = delete; GCodeG1Formatter& operator=(const GCodeG1Formatter&) = delete; + + void clear() override { + GCodeFormatter::clear(); + this->buf[0] = 'G'; + this->buf[1] = '1'; + this->ptr_err.ptr += 2; + } }; class GCodeG2G3Formatter : public GCodeFormatter { + const bool m_ccw; public: GCodeG2G3Formatter(int gcode_precision_xyz, int gcode_precision_e, bool ccw) - : GCodeFormatter(gcode_precision_xyz, gcode_precision_e) + : GCodeFormatter(gcode_precision_xyz, gcode_precision_e), m_ccw(ccw) { this->buf[0] = 'G'; - this->buf[1] = ccw ? '3' : '2'; + this->buf[1] = m_ccw ? '3' : '2'; this->ptr_err.ptr += 2; } GCodeG2G3Formatter(const GCodeFormatter &precision, bool ccw) - : GCodeFormatter(precision) + : GCodeFormatter(precision), m_ccw(ccw) { this->buf[0] = 'G'; - this->buf[1] = ccw ? '3' : '2'; + this->buf[1] = m_ccw ? '3' : '2'; this->ptr_err.ptr += 2; } GCodeG2G3Formatter(const GCodeG2G3Formatter&) = delete; GCodeG2G3Formatter& operator=(const GCodeG2G3Formatter&) = delete; + + void clear() override { + GCodeFormatter::clear(); + this->buf[0] = 'G'; + this->buf[1] = m_ccw ? '3' : '2'; + this->ptr_err.ptr += 2; + } }; } /* namespace Slic3r */ diff --git a/src/libslic3r/GCode/GCodeWriter.cpp b/src/libslic3r/GCode/GCodeWriter.cpp index 97be3b0d100..ef16d8a28fc 100644 --- a/src/libslic3r/GCode/GCodeWriter.cpp +++ b/src/libslic3r/GCode/GCodeWriter.cpp @@ -586,14 +586,18 @@ std::string GCodeWriter::travel_arc_to_xy(const Vec2d& point, const Vec2d& cente m_pos.y() = point.y(); GCodeG2G3Formatter w(this->config.gcode_precision_xyz.value, this->config.gcode_precision_e.value, is_ccw); - w.emit_xy(point); + bool has_x_y = w.emit_xy(point, m_pos_str_x, m_pos_str_y); + if (!has_x_y) { + //if point too close to the other, then do not write it, it's useless. + return ""; + } w.emit_ij(center_offset); w.emit_f(travel_speed * 60); w.emit_comment(this->config.gcode_comments, comment); return write_acceleration() + w.string(); } -std::string GCodeWriter::travel_to_xyz(const Vec3d &point, bool is_lift, const double speed, const std::string_view comment) +std::string GCodeWriter::travel_to_xyz(const Vec3d &point, const bool is_lift, const double speed, const std::string_view comment) { assert(std::abs(point.x()) < 120000.); assert(std::abs(point.y()) < 120000.); @@ -640,21 +644,26 @@ std::string GCodeWriter::travel_to_xyz(const Vec3d &point, bool is_lift, const d m_pos = point; GCodeG1Formatter w(this->get_default_gcode_formatter()); - if (!w.emit_xy(point.head<2>(), m_pos_str_x, m_pos_str_y)) { + bool has_x_y = w.emit_xy(point.head<2>(), m_pos_str_x, m_pos_str_y); + if (!has_x_y) { //if point too close to the other, then whatever, as long as the z is different. - // //if point too close to the other, then do not write it, it's useless. - // w = GCodeG1Formatter(this->get_default_gcode_formatter()); + //if point too close to the other, then do not write it, it's useless. + w.clear(); + } + if (config.z_step > SCALING_FACTOR) { + w.m_gcode_precision_xyz = 6; + } + bool has_z = w.emit_z(point.z(), m_pos_str_z); + if (!has_x_y && !has_z) { + //if point too close to the other, no move are needed. + return ""; } - if (config.z_step > SCALING_FACTOR) - w.emit_axis('Z', point.z(), 6); - else - w.emit_z(point.z()); w.emit_f(travel_speed * 60); w.emit_comment(this->config.gcode_comments, comment); return write_acceleration() + w.string(); } -std::string GCodeWriter::travel_to_z(double z, const std::string_view comment) +std::string GCodeWriter::travel_to_z(const double z, const std::string_view comment) { /* If target Z is lower than current Z but higher than nominal Z we don't perform the move but we only adjust the nominal Z by @@ -674,7 +683,7 @@ std::string GCodeWriter::travel_to_z(double z, const std::string_view comment) } -std::string GCodeWriter::get_travel_to_z_gcode(double z, const std::string_view comment) +std::string GCodeWriter::get_travel_to_z_gcode(const double z, const std::string_view comment) { m_pos.z() = z; double speed = this->config.travel_speed_z.value; @@ -682,16 +691,19 @@ std::string GCodeWriter::get_travel_to_z_gcode(double z, const std::string_view speed = this->config.travel_speed.value; GCodeG1Formatter w(this->get_default_gcode_formatter()); - if (config.z_step > SCALING_FACTOR) - w.emit_axis('Z', z, 6); - else - w.emit_z(z); + if (config.z_step > SCALING_FACTOR) { + w.m_gcode_precision_xyz = 6; + } + bool has_z = w.emit_z(z, m_pos_str_z); + if (!has_z) { + return ""; + } w.emit_f(speed * 60.0); w.emit_comment(this->config.gcode_comments, comment); return write_acceleration() + w.string(); } -bool GCodeWriter::will_move_z(double z) const +bool GCodeWriter::will_move_z(const double z) const { /* If target Z is lower than current Z but higher than nominal Z we don't perform an actual Z move. */ @@ -703,7 +715,7 @@ bool GCodeWriter::will_move_z(double z) const return true; } -std::string GCodeWriter::extrude_to_xy(const Vec2d &point, double dE, const std::string_view comment) +std::string GCodeWriter::extrude_to_xy(const Vec2d &point, const double dE, const std::string_view comment) { assert(dE == dE); assert(m_pos.x() != point.x() || m_pos.y() != point.y()); @@ -715,6 +727,7 @@ std::string GCodeWriter::extrude_to_xy(const Vec2d &point, double dE, const std: GCodeG1Formatter w(this->get_default_gcode_formatter()); if (!w.emit_xy(point, m_pos_str_x, m_pos_str_y)) { //if point too close to the other, then do not write it, it's useless. + this->m_tool->cancel_extrude(dE + this->m_de_left); this->m_de_left += dE; return ""; } @@ -727,10 +740,11 @@ std::string GCodeWriter::extrude_to_xy(const Vec2d &point, double dE, const std: return write_acceleration() + w.string(); } +static constexpr const std::array log_10{1, 10, 100, 1000, 10000, 100000, 1000000, 10000000, 100000000, 1000000000}; //BBS: generate G2 or G3 extrude which moves by arc //point is end point which means X and Y axis //center_offset is I and J axis -std::string GCodeWriter::extrude_arc_to_xy(const Vec2d& point, const Vec2d& center_offset, double dE, const bool is_ccw, const std::string_view comment) +std::string GCodeWriter::extrude_arc_to_xy(const Vec2d& point, const Vec2d& center_offset, const double dE, const bool is_ccw, const std::string_view comment) { assert(std::abs(point.x()) < 120000.); assert(std::abs(point.y()) < 120000.); @@ -740,12 +754,19 @@ std::string GCodeWriter::extrude_arc_to_xy(const Vec2d& point, const Vec2d& cent m_pos.x() = point.x(); m_pos.y() = point.y(); - auto [/*double*/ delta_e, /*double*/ e_to_write] = this->m_tool->extrude(dE + this->m_de_left); + auto [/*double*/ delta_e, /*double*/ e_to_write] = this->m_tool->extrude(dE + this->m_de_left); + //note: delta_e is the quantized delta. bool is_extrude = std::abs(delta_e) > 0.00000001; GCodeG2G3Formatter w(this->config.gcode_precision_xyz.value, this->config.gcode_precision_e.value, is_ccw); bool has_x_y = w.emit_xy(point, m_pos_str_x, m_pos_str_y); - assert(has_x_y); + if (!has_x_y) { + //if point too close to the other, then do not write it, it's useless. + this->m_tool->cancel_extrude(dE + this->m_de_left); + this->m_de_left += dE; + return ""; + } + // there is a move, write the arc center. w.emit_ij(center_offset); this->m_de_left += dE - delta_e; if (is_extrude) { @@ -756,7 +777,7 @@ std::string GCodeWriter::extrude_arc_to_xy(const Vec2d& point, const Vec2d& cent return write_acceleration() + w.string(); } -std::string GCodeWriter::extrude_to_xyz(const Vec3d &point, double dE, const std::string_view comment) +std::string GCodeWriter::extrude_to_xyz(const Vec3d &point, const double dE, const std::string_view comment) { assert(std::abs(point.x()) < 120000.); assert(std::abs(point.y()) < 120000.); @@ -769,8 +790,25 @@ std::string GCodeWriter::extrude_to_xyz(const Vec3d &point, double dE, const std bool is_extrude = std::abs(delta_e) > 0.00000001; GCodeG1Formatter w(this->get_default_gcode_formatter()); - w.emit_xy(Vec2d(point.x(), point.y()), m_pos_str_x, m_pos_str_y); - w.emit_z(point.z()); + bool has_x_y = w.emit_xy(Vec2d(point.x(), point.y()), m_pos_str_x, m_pos_str_y); + if (!has_x_y) { + w.clear(); + } + bool has_z = w.emit_z(point.z(), m_pos_str_z); + if (!has_z) { + if (!has_x_y) { + // m_pos has already been updated to the new (but indistinguishable from current one) posiiton + // we return nothing, as it's not worth it. + // just update the missing de. + this->m_tool->cancel_extrude(dE + this->m_de_left); + this->m_de_left += dE; + return ""; + } else { + w.clear(); + // re-write x & y. the m_pos_str_x & m_pos_str_y must stay the same, the return boolan must be 'false' + w.emit_xy(Vec2d(point.x(), point.y()), m_pos_str_x, m_pos_str_y); + } + } this->m_de_left += dE - delta_e; if (is_extrude) { double delta = w.emit_e(m_extrusion_axis, e_to_write); @@ -782,7 +820,7 @@ std::string GCodeWriter::extrude_to_xyz(const Vec3d &point, double dE, const std return write_acceleration() + w.string(); } -std::string GCodeWriter::extrude_arc_to_xyz(const Vec3d& point, const Vec2d& center_offset, double dE, const bool is_ccw, const std::string_view comment) +std::string GCodeWriter::extrude_arc_to_xyz(const Vec3d& point, const Vec2d& center_offset, const double dE, const bool is_ccw, const std::string_view comment) { assert(std::abs(point.x()) < 120000.); assert(std::abs(point.y()) < 120000.); @@ -796,8 +834,14 @@ std::string GCodeWriter::extrude_arc_to_xyz(const Vec3d& point, const Vec2d& cen GCodeG2G3Formatter w(this->config.gcode_precision_xyz.value, this->config.gcode_precision_e.value, is_ccw); bool has_x_y = w.emit_xy(Vec2d(point.x(), point.y()), m_pos_str_x, m_pos_str_y); - assert(has_x_y); - w.emit_z(point.z()); + bool has_z = w.emit_z(point.z(), m_pos_str_z); + if (!has_x_y && !has_z) { + // m_pos has already been updated to the new (but indistinguishable from current one) posiiton + // we return nothing, as it's not worth it. + this->m_tool->cancel_extrude(dE + this->m_de_left); + this->m_de_left += dE; + return ""; + } w.emit_ij(center_offset); this->m_de_left += dE - delta_e; if (is_extrude) { diff --git a/src/libslic3r/GCode/GCodeWriter.hpp b/src/libslic3r/GCode/GCodeWriter.hpp index a9bd2f7ef5c..111918d9812 100644 --- a/src/libslic3r/GCode/GCodeWriter.hpp +++ b/src/libslic3r/GCode/GCodeWriter.hpp @@ -80,15 +80,15 @@ class GCodeWriter { double get_speed_mm_s() const; std::string travel_to_xy(const Vec2d &point, const double speed = 0.0, const std::string_view comment = {}); std::string travel_arc_to_xy(const Vec2d& point, const Vec2d& center_offset, const bool is_ccw, const double speed, const std::string_view comment); - std::string travel_to_xyz(const Vec3d &point, bool is_lift, const double speed = 0.0, const std::string_view comment = {}); - std::string travel_to_z(double z, const std::string_view comment = {}); + std::string travel_to_xyz(const Vec3d &point, const bool is_lift, const double speed = 0.0, const std::string_view comment = {}); + std::string travel_to_z(const double z, const std::string_view comment = {}); // low-level method to force a z travel, disregarding the lift and other thigns. Prefer using "travel_to_z" "lift" and "unlift". - std::string get_travel_to_z_gcode(double z, const std::string_view comment = {}); - bool will_move_z(double z) const; - std::string extrude_to_xy(const Vec2d &point, double dE, const std::string_view comment = {}); - std::string extrude_arc_to_xy(const Vec2d& point, const Vec2d& center_offset, double dE, const bool is_ccw, const std::string_view comment = {}); //BBS: generate G2 or G3 extrude which moves by arc - std::string extrude_arc_to_xyz(const Vec3d& point, const Vec2d& center_offset, double dE, const bool is_ccw, const std::string_view comment = {}); //BBS: generate G2 or G3 extrude which moves by arc - std::string extrude_to_xyz(const Vec3d &point, double dE, const std::string_view comment = {}); + std::string get_travel_to_z_gcode(const double z, const std::string_view comment = {}); + bool will_move_z(const double z) const; + std::string extrude_to_xy(const Vec2d &point, const double dE, const std::string_view comment = {}); + std::string extrude_arc_to_xy(const Vec2d& point, const Vec2d& center_offset, const double dE, const bool is_ccw, const std::string_view comment = {}); //BBS: generate G2 or G3 extrude which moves by arc + std::string extrude_arc_to_xyz(const Vec3d& point, const Vec2d& center_offset, const double dE, const bool is_ccw, const std::string_view comment = {}); //BBS: generate G2 or G3 extrude which moves by arc + std::string extrude_to_xyz(const Vec3d &point, const double dE, const std::string_view comment = {}); std::string retract(bool before_wipe = false); std::string retract_for_toolchange(bool before_wipe = false); std::string unretract(); @@ -155,9 +155,10 @@ class GCodeWriter { // current lift, to remove from m_pos to have the current height. double m_lifted = 0; Vec3d m_pos = Vec3d::Zero(); - // cached string representation of x & y m_pos + // cached string representation of x & y & z m_pos std::string m_pos_str_x; std::string m_pos_str_y; + std::string m_pos_str_z; // stored de that wasn't written, because of the rounding double m_de_left = 0; From 1bcb84da050e000ee3540428530c30d7859029e2 Mon Sep 17 00:00:00 2001 From: supermerill Date: Sun, 24 Nov 2024 20:46:58 +0100 Subject: [PATCH 07/12] debug: store debug_verify_flow_mult --- src/libslic3r/Fill/Fill.cpp | 3 ++- src/libslic3r/Fill/FillConcentric.cpp | 5 ++++- src/libslic3r/Fill/FillRectilinear.cpp | 9 ++++++++- 3 files changed, 14 insertions(+), 3 deletions(-) diff --git a/src/libslic3r/Fill/Fill.cpp b/src/libslic3r/Fill/Fill.cpp index 6e963dc2a40..7d9b2c4af7f 100644 --- a/src/libslic3r/Fill/Fill.cpp +++ b/src/libslic3r/Fill/Fill.cpp @@ -1130,7 +1130,8 @@ void Layer::make_fills(FillAdaptive::Octree* adaptive_fill_octree, FillAdaptive: intersection_ex(ExPolygons{surface_fill.surface.expolygon}, f->no_overlap_expolygons); double real_surface = 0; for(auto &t : temp) real_surface += t.area(); - assert(compute_volume.volume < unscaled(unscaled(surface_fill.surface.area())) * surface_fill.params.layer_height * surface_fill.params.flow_mult + EPSILON); + assert(compute_volume.volume < unscaled(unscaled(surface_fill.surface.area())) * surface_fill.params.layer_height * surface_fill.params.flow_mult + EPSILON + || f->debug_verify_flow_mult <= 0.80001); double area = unscaled(unscaled(real_surface)); if(surface_fill.surface.has_pos_top()) area *= surface_fill.params.config->fill_top_flow_ratio.get_abs_value(1); diff --git a/src/libslic3r/Fill/FillConcentric.cpp b/src/libslic3r/Fill/FillConcentric.cpp index e28232bb1c3..b1292ea0c79 100644 --- a/src/libslic3r/Fill/FillConcentric.cpp +++ b/src/libslic3r/Fill/FillConcentric.cpp @@ -444,9 +444,12 @@ FillConcentricWGapFill::fill_surface_extrusion( mult_flow = 1.3; if (mult_flow < 0.8) mult_flow = 0.8; - BOOST_LOG_TRIVIAL(info) << "concentric Infill (with gapfil) process extrude " << get_volume.volume + BOOST_LOG_TRIVIAL(debug) << "concentric Infill (with gapfil) process extrude " << get_volume.volume << " mm3 for a volume of " << polyline_volume << " mm3 : we mult the flow by " << mult_flow; +#if _DEBUG + this->debug_verify_flow_mult = mult_flow; +#endif } mult_flow *= params.flow_mult; if (mult_flow != 1) { diff --git a/src/libslic3r/Fill/FillRectilinear.cpp b/src/libslic3r/Fill/FillRectilinear.cpp index c252d8a8091..0d86d69b98e 100644 --- a/src/libslic3r/Fill/FillRectilinear.cpp +++ b/src/libslic3r/Fill/FillRectilinear.cpp @@ -3613,15 +3613,22 @@ FillRectilinearWGapFill::fill_surface_extrusion(const Surface *surface, const Fi mult_flow = 1.3; if (mult_flow < 0.8) mult_flow = 0.8; - BOOST_LOG_TRIVIAL(info) << "rectilinear/monotonic Infill (with gapfil) process extrude " + BOOST_LOG_TRIVIAL(debug) << "rectilinear/monotonic Infill (with gapfil) process extrude " << extruded_volume << " mm3 for a volume of " << polyline_volume << " mm3 : we mult the flow by " << mult_flow; +#if _DEBUG + this->debug_verify_flow_mult = mult_flow; +#endif } mult_flow *= params.flow_mult; if (mult_flow != 1) { // apply to extrusions ExtrusionModifyFlow{mult_flow}.set(*coll_nosort); } + } else { +#if _DEBUG + this->debug_verify_flow_mult = -1; +#endif } // === end === From a0894fce29430a3a2679238ceada11dd6000559a Mon Sep 17 00:00:00 2001 From: supermerill Date: Sun, 24 Nov 2024 20:48:51 +0100 Subject: [PATCH 08/12] z_offset rework * now z_offset is added by the gcode writer (and the wieptowerwriter). * all computation is done on the un-offseted z * only the "position" placeholder contains the z offset. * moved some placeholder into the placeholder_parser_process, as they are used everywhere. --- src/libslic3r/GCode.cpp | 101 ++++++------------- src/libslic3r/GCode/GCodeWriter.cpp | 10 +- src/libslic3r/GCode/GCodeWriter.hpp | 2 +- src/libslic3r/GCode/WipeTower.cpp | 8 +- src/libslic3r/GCode/WipeTowerIntegration.cpp | 10 +- src/libslic3r/PrintConfig.hpp | 2 +- 6 files changed, 49 insertions(+), 84 deletions(-) diff --git a/src/libslic3r/GCode.cpp b/src/libslic3r/GCode.cpp index 75ba619db24..9a5f5317a60 100644 --- a/src/libslic3r/GCode.cpp +++ b/src/libslic3r/GCode.cpp @@ -320,9 +320,12 @@ void GCodeGenerator::PlaceholderParserIntegration::init(const PrintConfig &print void GCodeGenerator::PlaceholderParserIntegration::update_from_gcodewriter(const GCodeWriter &writer, const WipeTowerData& wipe_tower_data) { + assert(this->position.size() == 3 && writer.get_position().size() == 3); memcpy(this->position.data(), writer.get_position().data(), sizeof(double) * 3); + // the z_offset is added by the writer when writing the z, we need to add it ourself to have the real z as it's printed in the gcode. + this->position[2] += writer.config.z_offset.value; this->opt_position->set(this->position); - this->opt_position_parser->set(this->position); + this->opt_position_parser->set(this->position); this->opt_zhop->value = writer.get_lift(); if (this->num_extruders > 0) { @@ -343,7 +346,7 @@ void GCodeGenerator::PlaceholderParserIntegration::update_from_gcodewriter(const double wt_vol = 0.; const std::vector>>& wtuf = wipe_tower_data.used_filament_until_layer; if (!wtuf.empty()) { - auto it = std::lower_bound(wtuf.begin(), wtuf.end(), writer.get_position().z(), + auto it = std::lower_bound(wtuf.begin(), wtuf.end(), this->position[2] /*z*/, [](const auto& a, const float& val) { return a.first < val; }); if (it == wtuf.end()) it = wtuf.end() - 1; @@ -1708,8 +1711,6 @@ void GCodeGenerator::_do_export(Print& print_mod, GCodeOutputStream &file, Thumb config.set_key_value("previous_extruder", new ConfigOptionInt(-1)); config.set_key_value("next_extruder", new ConfigOptionInt((int)initial_extruder_id)); config.set_key_value("filament_extruder_id", new ConfigOptionInt((int)initial_extruder_id)); - config.set_key_value("layer_num", new ConfigOptionInt(0)); - config.set_key_value("layer_z", new ConfigOptionFloat(0)); //TODO: if the process is changed, please use the real first layer height start_filament_gcode = this->placeholder_parser_process("start_filament_gcode", m_config.start_filament_gcode.get_at(initial_extruder_id), initial_extruder_id, &config); } std::string start_all_gcode = start_gcode + "\"n" + start_filament_gcode; @@ -1940,9 +1941,9 @@ void GCodeGenerator::_do_export(Print& print_mod, GCodeOutputStream &file, Thumb m_wipe_tower = std::make_unique(print.config(), *print.wipe_tower_data().priming.get(), print.wipe_tower_data().tool_changes, *print.wipe_tower_data().final_purge.get()); // Set position for wipe tower generation. - Vec3d new_position = this->writer().get_position(); - new_position.z() = first_layer_height; - this->writer().update_position(new_position); + file.write(this->writer().travel_to_z(first_layer_height, "Move to first z, for wipe tower")); + m_last_layer_z = first_layer_height; + m_max_layer_z = std::max(m_max_layer_z, this->writer().get_unlifted_position().z()); if (print.config().single_extruder_multi_material_priming) { // TODO: 2.7: check that the preamble_to_put_start_layer has the z-move at first (from m_wipe_tower->prime, I guess) @@ -2011,9 +2012,6 @@ void GCodeGenerator::_do_export(Print& print_mod, GCodeOutputStream &file, Thumb if (initial_extruder_id != (uint16_t)-1) { uint16_t current_extruder_id = m_writer.tool()->id(); DynamicConfig config; - config.set_key_value("layer_num", new ConfigOptionInt(m_layer_index)); - config.set_key_value("layer_z", new ConfigOptionFloat(m_writer.get_position().z() - m_config.z_offset.value)); - config.set_key_value("max_layer_z", new ConfigOptionFloat(m_max_layer_z)); config.set_key_value("filament_extruder_id", new ConfigOptionInt(current_extruder_id)); config.set_key_value("previous_extruder", new ConfigOptionInt(current_extruder_id)); config.set_key_value("next_extruder", new ConfigOptionInt(-1)); @@ -2495,29 +2493,21 @@ std::string GCodeGenerator::placeholder_parser_process( PlaceholderParserIntegration &ppi = m_placeholder_parser_integration; try { - //2.7: check where to add that (in PlaceholderParserIntegration ? ) - //add some config conversion for colors - //auto func_add_colour = [&default_config](std::string key, std::string colour) { - // if (colour.length() == 7) { - // default_config.set_key_value(key, new ConfigOptionInt((int)strtol(colour.substr(1, 6).c_str(), NULL, 16))); - // } - //}; - //if (current_extruder_id >= 0 && current_extruder_id < config().filament_colour.size()) { - // func_add_colour("filament_colour_int", config().filament_colour.get_at(current_extruder_id)); - // func_add_colour("extruder_colour_int", config().extruder_colour.get_at(current_extruder_id)); - //} - //// should be the same as Vec2d gcode_pos = point_to_gcode(m_last_pos); - //Vec3d gcode_pos = this->writer().get_position(); - //default_config.set_key_value("current_position", new ConfigOptionFloats( {gcode_pos.x(), gcode_pos.y(), gcode_pos.z()} )); - //default_config.set_key_value("current_object_position", new ConfigOptionFloats( {m_origin.x(), m_origin.y()} )); - + // add special variables from writer & wipetower ppi.update_from_gcodewriter(m_writer, *this->m_wipe_tower_data); + // add special variables from gcodegenerator + ppi.parser.set("layer_num", new ConfigOptionInt(m_layer_index)); + ppi.parser.set("layer_z", new ConfigOptionFloat(m_layer == nullptr ? m_last_layer_z : m_layer->print_z)); + ppi.parser.set("layer_current_height", new ConfigOptionFloat(m_layer == nullptr ? m_last_height : m_layer->height)); + ppi.parser.set("max_layer_z", new ConfigOptionFloat(m_max_layer_z)); + ppi.parser.set("current_object_position", new ConfigOptionFloats({m_origin.x(), m_origin.y()})); + std::string output = ppi.parser.process(templ, current_extruder_id, config_override, &ppi.output_config, &ppi.context); ppi.validate_output_vector_variables(); if (const std::vector &pos = ppi.opt_position->get_values(); ppi.position != pos) { - // Update G-code writer. - m_writer.update_position({ pos[0], pos[1], pos[2] }); + // Update G-code writer. (without z_offset) + m_writer.update_position_by_lift({ pos[0], pos[1], pos[2] - m_writer.config.z_offset.value}); this->set_last_pos(this->gcode_to_point({pos[0], pos[1]})); } @@ -3129,7 +3119,7 @@ LayerResult GCodeGenerator::process_layer( } // Extract 1st object_layer and support_layer of this set of layers with an equal print_z. - coordf_t print_z = layer.print_z + m_config.z_offset.value; + coordf_t print_z = layer.print_z; bool first_layer = layer_id == 0; uint16_t first_extruder_id = layer_tools.extruders.front(); @@ -3183,21 +3173,18 @@ LayerResult GCodeGenerator::process_layer( + float_to_string_decimal_point(height) + "\n"; // update caches - const double previous_layer_z{m_last_layer_z}; + const double previous_layer_z{m_layer != nullptr ? m_layer->print_z : 0}; + assert(std::abs(previous_layer_z - (m_layer != nullptr ? m_last_layer_z : 0)) < 0.00000001); m_last_layer_z = print_z; - m_max_layer_z = std::max(m_max_layer_z, m_last_layer_z); + m_max_layer_z = std::max(m_max_layer_z, m_last_layer_z); m_last_height = height; m_last_too_small.polyline.clear(); //m_already_unretracted = false; // Set new layer - this will change Z and force a retraction if retract_layer_change is enabled. - assert(std::abs(previous_layer_z - (m_layer != nullptr ? m_layer->print_z : 0)) < 0.00000001); if (! print.config().before_layer_gcode.value.empty()) { DynamicConfig config; config.set_key_value("previous_layer_z", new ConfigOptionFloat(previous_layer_z)); - config.set_key_value("layer_num", new ConfigOptionInt(m_layer_index + 1)); - config.set_key_value("layer_z", new ConfigOptionFloat(print_z)); - config.set_key_value("max_layer_z", new ConfigOptionFloat(m_max_layer_z)); gcode += this->placeholder_parser_process("before_layer_gcode", print.config().before_layer_gcode.value, m_writer.tool()->id(), &config) + "\n"; @@ -3234,9 +3221,6 @@ LayerResult GCodeGenerator::process_layer( if (!first_layer && ! print.config().layer_gcode.value.empty()) { DynamicConfig config; config.set_key_value("previous_layer_z", new ConfigOptionFloat(previous_layer_z)); - config.set_key_value("layer_num", new ConfigOptionInt(m_layer_index)); - config.set_key_value("layer_z", new ConfigOptionFloat(print_z)); - config.set_key_value("max_layer_z", new ConfigOptionFloat(m_max_layer_z)); gcode += this->placeholder_parser_process("layer_gcode", print.config().layer_gcode.value, m_writer.tool()->id(), &config) + "\n"; @@ -3522,9 +3506,7 @@ void GCodeGenerator::process_layer_single_object( if (!print_args.print_instance.print_object.config().object_gcode.value.empty()) { DynamicConfig config; - config.set_key_value("layer_num", new ConfigOptionInt(m_layer_index)); - assert(std::abs(m_writer.get_position().z() - m_config.z_offset.value - m_last_layer_z) < 0.0001); - config.set_key_value("layer_z", new ConfigOptionFloat(m_last_layer_z)); + assert(std::abs(m_writer.get_position().z() - m_last_layer_z) < 0.0001); m_gcode_label_objects_start += this->placeholder_parser_process("object_gcode", print_args.print_instance.print_object.config().object_gcode.value, m_writer.tool()->id(), &config) + "\n"; @@ -3684,10 +3666,7 @@ void GCodeGenerator::emit_milling_commands(std::string& gcode, const ObjectsLaye DynamicConfig config; config.set_key_value("previous_extruder", new ConfigOptionInt((int)current_extruder_filament)); config.set_key_value("next_extruder", new ConfigOptionInt((int)milling_extruder_id)); - config.set_key_value("layer_num", new ConfigOptionInt(m_layer_index)); config.set_key_value("previous_layer_z", new ConfigOptionFloat(previous_print_z)); - config.set_key_value("layer_z", new ConfigOptionFloat(m_layer->print_z)); - config.set_key_value("max_layer_z", new ConfigOptionFloat(m_max_layer_z)); // Process the start_mill_gcode for the new filament. gcode += this->placeholder_parser_process("milling_toolchange_start_gcode", start_mill_gcode, current_extruder_filament, &config); @@ -3718,10 +3697,7 @@ void GCodeGenerator::emit_milling_commands(std::string& gcode, const ObjectsLaye DynamicConfig config; config.set_key_value("previous_extruder", new ConfigOptionInt((int)milling_extruder_id)); config.set_key_value("next_extruder", new ConfigOptionInt((int)current_extruder_filament)); - config.set_key_value("layer_num", new ConfigOptionInt(m_layer_index)); config.set_key_value("previous_layer_z", new ConfigOptionFloat(previous_print_z)); - config.set_key_value("layer_z", new ConfigOptionFloat(m_layer->print_z)); - config.set_key_value("max_layer_z", new ConfigOptionFloat(m_max_layer_z)); // Process the end_mill_gcode for the new filament. gcode += this->placeholder_parser_process("milling_toolchange_start_gcode", end_mill_gcode, current_extruder_filament, &config); @@ -3838,7 +3814,8 @@ std::string GCodeGenerator::preamble() position of our writer object so that any initial lift taking place before the first layer change will raise the extruder from the correct initial Z instead of 0. */ - m_writer.travel_to_z(m_config.z_offset.value); + // now, z_offset is added by m_writer directly. + //m_writer.travel_to_z(m_config.z_offset.value); //as this phony thing skip the acceleration writing, they have to be reset after that for real initialisation at the next move/extrusion m_writer.set_acceleration(0); @@ -3846,7 +3823,6 @@ std::string GCodeGenerator::preamble() } // called by GCodeGenerator::process_layer() -// print_z already has the z_offset std::string GCodeGenerator::change_layer(double print_z) { std::string gcode; if (layer_count() > 0) @@ -5465,8 +5441,6 @@ void GCodeGenerator::set_region_for_extrude(const Print &print, const PrintObjec if (!region_config.region_gcode.value.empty()) { //TODO 2.7: new placeholder_parser_process call DynamicConfig config; - config.set_key_value("layer_num", new ConfigOptionInt(m_layer_index)); - config.set_key_value("layer_z", new ConfigOptionFloat(m_layer == nullptr ? m_last_height : m_layer->print_z)); assert(!m_gcode_label_objects_in_session || !m_gcode_label_objects_start.empty()); m_gcode_label_objects_start += this->placeholder_parser_process("region_gcode", region_config.region_gcode.value, @@ -6694,9 +6668,6 @@ std::string GCodeGenerator::_before_extrude(const ExtrusionPath &path, const std config.set_key_value("next_extrusion_role", new ConfigOptionString(gcode_extrusion_role_to_string(grole))); config.set_key_value("previous_extrusion_role", new ConfigOptionString(gcode_extrusion_role_to_string(m_last_extrusion_role))); config.set_key_value("last_extrusion_role", new ConfigOptionString(gcode_extrusion_role_to_string(m_last_extrusion_role))); - config.set_key_value("layer_num", new ConfigOptionInt(m_layer_index + 1)); - config.set_key_value("layer_z", new ConfigOptionFloat(m_layer == nullptr ? m_last_height : m_layer->print_z)); - config.set_key_value("max_layer_z", new ConfigOptionFloat(m_max_layer_z)); gcode += this->placeholder_parser_process("feature_gcode", m_config.feature_gcode.value, m_writer.tool()->id(), &config) + "\n"; @@ -7708,6 +7679,7 @@ void GCodeGenerator::set_extra_lift(const float previous_print_z, const int laye } std::string GCodeGenerator::toolchange(uint16_t extruder_id, double print_z) { + assert( (print_z == 0 && m_max_layer_z == 0) || (print_z != 0 && m_max_layer_z != 0)); std::string gcode; @@ -7719,10 +7691,7 @@ std::string GCodeGenerator::toolchange(uint16_t extruder_id, double print_z) { DynamicConfig config; config.set_key_value("previous_extruder", new ConfigOptionInt((int)(m_writer.tool() != nullptr ? m_writer.tool()->id() : -1))); config.set_key_value("next_extruder", new ConfigOptionInt((int)extruder_id)); - config.set_key_value("layer_num", new ConfigOptionInt(m_layer_index)); - config.set_key_value("layer_z", new ConfigOptionFloat(print_z)); - config.set_key_value("toolchange_z", new ConfigOptionFloat(print_z)); - config.set_key_value("max_layer_z", new ConfigOptionFloat(m_max_layer_z)); + config.set_key_value("toolchange_z", new ConfigOptionFloat(print_z == 0 ? 0 : (print_z))); toolchange_gcode_parsed = placeholder_parser_process("toolchange_gcode", toolchange_gcode, extruder_id, &config); gcode += toolchange_gcode_parsed; check_add_eol(gcode); @@ -7743,6 +7712,8 @@ std::string GCodeGenerator::toolchange(uint16_t extruder_id, double print_z) { std::string GCodeGenerator::set_extruder(uint16_t extruder_id, double print_z, bool no_toolchange /*=false*/) { + assert( (print_z == 0 && m_max_layer_z == 0) || (print_z != 0 && m_max_layer_z != 0)); + if (!m_writer.need_toolchange(extruder_id)) return ""; @@ -7752,7 +7723,7 @@ std::string GCodeGenerator::set_extruder(uint16_t extruder_id, double print_z, b ensure_end_object_change_labels(gcode); //just for testing - assert(is_approx(this->writer().get_position().z() - m_config.z_offset.value, print_z, EPSILON)); + assert(is_approx(this->writer().get_position().z(), print_z, EPSILON)); // if we are running a single-extruder setup, just set the extruder and return nothing if (!m_writer.multiple_extruders) { @@ -7762,9 +7733,7 @@ std::string GCodeGenerator::set_extruder(uint16_t extruder_id, double print_z, b const std::string &start_filament_gcode = m_config.start_filament_gcode.get_at(extruder_id); if (! start_filament_gcode.empty()) { DynamicConfig config; - config.set_key_value("layer_num", new ConfigOptionInt(m_layer_index)); - config.set_key_value("layer_z", new ConfigOptionFloat(print_z)); - config.set_key_value("max_layer_z", new ConfigOptionFloat(m_max_layer_z)); + assert(is_approx((m_layer == nullptr ? m_last_layer_z : m_layer->print_z), print_z, EPSILON)); config.set_key_value("filament_extruder_id", new ConfigOptionInt(int(extruder_id))); config.set_key_value("previous_extruder", new ConfigOptionInt((int)extruder_id)); config.set_key_value("next_extruder", new ConfigOptionInt((int)extruder_id)); @@ -7798,9 +7767,7 @@ std::string GCodeGenerator::set_extruder(uint16_t extruder_id, double print_z, b const std::string &end_filament_gcode = m_config.end_filament_gcode.get_at(old_extruder_id); if (! end_filament_gcode.empty()) { DynamicConfig config; - config.set_key_value("layer_num", new ConfigOptionInt(m_layer_index)); - config.set_key_value("layer_z", new ConfigOptionFloat(print_z)); - config.set_key_value("max_layer_z", new ConfigOptionFloat(m_max_layer_z)); + assert(is_approx((m_layer == nullptr ? m_last_layer_z : m_layer->print_z), print_z, EPSILON)); config.set_key_value("filament_extruder_id", new ConfigOptionInt(int(old_extruder_id))); config.set_key_value("previous_extruder", new ConfigOptionInt((int)old_extruder_id)); config.set_key_value("next_extruder", new ConfigOptionInt((int)extruder_id)); @@ -7835,9 +7802,7 @@ std::string GCodeGenerator::set_extruder(uint16_t extruder_id, double print_z, b const std::string &start_filament_gcode = m_config.start_filament_gcode.get_at(extruder_id); if (! start_filament_gcode.empty()) { DynamicConfig config; - config.set_key_value("layer_num", new ConfigOptionInt(m_layer_index)); - config.set_key_value("layer_z", new ConfigOptionFloat(print_z)); - config.set_key_value("max_layer_z", new ConfigOptionFloat(m_max_layer_z)); + assert(is_approx((m_layer == nullptr ? m_last_layer_z : m_layer->print_z), print_z, EPSILON)); config.set_key_value("filament_extruder_id", new ConfigOptionInt(int(extruder_id))); config.set_key_value("previous_extruder", new ConfigOptionInt((int)old_extruder_id)); config.set_key_value("next_extruder", new ConfigOptionInt((int)extruder_id)); diff --git a/src/libslic3r/GCode/GCodeWriter.cpp b/src/libslic3r/GCode/GCodeWriter.cpp index ef16d8a28fc..477b5ed585f 100644 --- a/src/libslic3r/GCode/GCodeWriter.cpp +++ b/src/libslic3r/GCode/GCodeWriter.cpp @@ -653,7 +653,7 @@ std::string GCodeWriter::travel_to_xyz(const Vec3d &point, const bool is_lift, c if (config.z_step > SCALING_FACTOR) { w.m_gcode_precision_xyz = 6; } - bool has_z = w.emit_z(point.z(), m_pos_str_z); + bool has_z = w.emit_z(point.z() + this->config.z_offset.value, m_pos_str_z); if (!has_x_y && !has_z) { //if point too close to the other, no move are needed. return ""; @@ -694,7 +694,7 @@ std::string GCodeWriter::get_travel_to_z_gcode(const double z, const std::string if (config.z_step > SCALING_FACTOR) { w.m_gcode_precision_xyz = 6; } - bool has_z = w.emit_z(z, m_pos_str_z); + bool has_z = w.emit_z(z + this->config.z_offset.value, m_pos_str_z); if (!has_z) { return ""; } @@ -794,7 +794,7 @@ std::string GCodeWriter::extrude_to_xyz(const Vec3d &point, const double dE, con if (!has_x_y) { w.clear(); } - bool has_z = w.emit_z(point.z(), m_pos_str_z); + bool has_z = w.emit_z(point.z() + this->config.z_offset.value, m_pos_str_z); if (!has_z) { if (!has_x_y) { // m_pos has already been updated to the new (but indistinguishable from current one) posiiton @@ -834,7 +834,7 @@ std::string GCodeWriter::extrude_arc_to_xyz(const Vec3d& point, const Vec2d& cen GCodeG2G3Formatter w(this->config.gcode_precision_xyz.value, this->config.gcode_precision_e.value, is_ccw); bool has_x_y = w.emit_xy(Vec2d(point.x(), point.y()), m_pos_str_x, m_pos_str_y); - bool has_z = w.emit_z(point.z(), m_pos_str_z); + bool has_z = w.emit_z(point.z() + this->config.z_offset.value, m_pos_str_z); if (!has_x_y && !has_z) { // m_pos has already been updated to the new (but indistinguishable from current one) posiiton // we return nothing, as it's not worth it. @@ -974,7 +974,7 @@ std::string GCodeWriter::unretract() return gcode; } -void GCodeWriter::update_position(const Vec3d &new_pos) +void GCodeWriter::update_position_by_lift(const Vec3d &new_pos) { // move z ==> update lift m_lifted = m_lifted + new_pos.z() - m_pos.z(); diff --git a/src/libslic3r/GCode/GCodeWriter.hpp b/src/libslic3r/GCode/GCodeWriter.hpp index 111918d9812..423674615ac 100644 --- a/src/libslic3r/GCode/GCodeWriter.hpp +++ b/src/libslic3r/GCode/GCodeWriter.hpp @@ -113,7 +113,7 @@ class GCodeWriter { // The new position Z coordinate contains the Z-hop. // GCodeWriter expects the custom script to NOT change print_z, only Z-hop, thus the print_z is maintained // by this function while the current Z-hop accumulator is updated. - void update_position(const Vec3d &new_pos); + void update_position_by_lift(const Vec3d &new_pos); // Returns whether this flavor supports separate print and travel acceleration. static bool supports_separate_travel_acceleration(GCodeFlavor flavor); diff --git a/src/libslic3r/GCode/WipeTower.cpp b/src/libslic3r/GCode/WipeTower.cpp index d667b64ed2e..e6ea0b19386 100644 --- a/src/libslic3r/GCode/WipeTower.cpp +++ b/src/libslic3r/GCode/WipeTower.cpp @@ -841,7 +841,7 @@ std::vector WipeTower::prime( WipeTowerWriter writer(m_layer_height, m_perimeter_width, m_gcode_flavor, m_config->tool_name.get_values(), m_filpar); writer.set_extrusion_flow(m_extrusion_flow) - .set_z(m_z_pos) + .set_z(m_z_pos + m_config->z_offset.value) .set_initial_tool(m_current_tool); // This is the first toolchange - initiate priming @@ -937,7 +937,7 @@ WipeTower::ToolChangeResult WipeTower::tool_change(size_t tool) WipeTowerWriter writer(m_layer_height, m_perimeter_width, m_gcode_flavor, m_config->tool_name.get_values(), m_filpar); writer.set_extrusion_flow(m_extrusion_flow) - .set_z(m_z_pos) + .set_z(m_z_pos + m_config->z_offset.value) .set_initial_tool(m_current_tool) .set_y_shift(m_y_shift + (tool!=(unsigned int)(-1) && (m_current_shape == SHAPE_REVERSED) ? m_layer_info->depth - m_layer_info->toolchanges_depth(): 0.f)) .append(";--------------------\n" @@ -1415,7 +1415,7 @@ WipeTower::ToolChangeResult WipeTower::finish_layer() WipeTowerWriter writer(m_layer_height, m_perimeter_width, m_gcode_flavor, m_config->tool_name.get_values(), m_filpar); writer.set_extrusion_flow(m_extrusion_flow) - .set_z(m_z_pos) + .set_z(m_z_pos + m_config->z_offset.value) .set_initial_tool(m_current_tool) .set_y_shift(m_y_shift - (m_current_shape == SHAPE_REVERSED ? m_layer_info->toolchanges_depth() : 0.f)); @@ -1639,7 +1639,7 @@ WipeTower::ToolChangeResult WipeTower::finish_layer() writer.set_extrusion_flow(brim_flow.mm3_per_mm() / filament_area()) - .set_z(m_z_pos) // Let the writer know the current Z position as a base for Z-hop. + .set_z(m_z_pos + m_config->z_offset.value) // Let the writer know the current Z position as a base for Z-hop. .set_initial_tool(m_current_tool) .append(";-------------------------------------\n" "; CP WIPE TOWER FIRST LAYER BRIM START\n"); diff --git a/src/libslic3r/GCode/WipeTowerIntegration.cpp b/src/libslic3r/GCode/WipeTowerIntegration.cpp index 4e2c299af75..a2e43709a2c 100644 --- a/src/libslic3r/GCode/WipeTowerIntegration.cpp +++ b/src/libslic3r/GCode/WipeTowerIntegration.cpp @@ -14,8 +14,8 @@ static inline Point wipe_tower_point_to_object_point(GCodeGenerator &gcodegen, c std::string WipeTowerIntegration::append_tcr(GCodeGenerator &gcodegen, const WipeTower::ToolChangeResult& tcr, int new_extruder_id, double z) const { - // has previosu pos, or it's first layer. - assert(gcodegen.last_pos_defined() || gcodegen.layer()->lower_layer == nullptr); + // has previous pos, or it's first layer. + assert(gcodegen.last_pos_defined() || gcodegen.layer() == nullptr || gcodegen.layer()->lower_layer == nullptr); if (new_extruder_id != -1 && new_extruder_id != tcr.new_tool) throw Slic3r::InvalidArgument("Error: WipeTowerIntegration::append_tcr was asked to do a toolchange it didn't expect."); @@ -103,7 +103,7 @@ std::string WipeTowerIntegration::append_tcr(GCodeGenerator &gcodegen, const Wip gcodegen.m_wipe.reset_path(); // We don't want wiping on the ramming lines. toolchange_gcode_str = gcodegen.set_extruder(new_extruder_id, tcr.print_z); // TODO: toolchange_z vs print_z if (gcodegen.config().wipe_tower) { - const double retract_to_z = tcr.priming ? tcr.print_z + gcodegen.config().z_offset.value : z; + //const double retract_to_z = tcr.priming ? tcr.print_z : z; deretraction_str += gcodegen.writer().unlift(); deretraction_str += gcodegen.unretract(); } @@ -114,8 +114,8 @@ std::string WipeTowerIntegration::append_tcr(GCodeGenerator &gcodegen, const Wip // Insert the toolchange and deretraction gcode into the generated gcode. boost::replace_first(tcr_rotated_gcode, "[toolchange_gcode_from_wipe_tower_generator]", toolchange_gcode_str); boost::replace_first(tcr_rotated_gcode, "[deretraction_from_wipe_tower_generator]", deretraction_str); - boost::replace_first(tcr_rotated_gcode, "{layer_z}", to_string_nozero(gcodegen.writer().get_position().z(), 4)); - boost::replace_first(tcr_rotated_gcode, "[[toolchange_gcode_disable_linear_advance]]", gcodegen.writer().set_pressure_advance(0)); + boost::replace_first(tcr_rotated_gcode, "{layer_z}", to_string_nozero(gcodegen.writer().get_position().z() + gcodegen.writer().config.z_offset.value, 4)); + boost::replace_first(tcr_rotated_gcode, "[toolchange_gcode_disable_linear_advance]", gcodegen.writer().set_pressure_advance(0)); if (gcodegen.config().filament_pressure_advance.is_enabled(new_extruder_id)) { boost::replace_first(tcr_rotated_gcode, "[toolchange_gcode_enable_linear_advance]", gcodegen.writer().set_pressure_advance(gcodegen.config().filament_pressure_advance.get_at(new_extruder_id))); diff --git a/src/libslic3r/PrintConfig.hpp b/src/libslic3r/PrintConfig.hpp index a121b3fd004..ea23ee94372 100644 --- a/src/libslic3r/PrintConfig.hpp +++ b/src/libslic3r/PrintConfig.hpp @@ -1199,6 +1199,7 @@ PRINT_CONFIG_CLASS_DEFINE( ((ConfigOptionBool, wipe_tower_no_sparse_layers)) ((ConfigOptionFloat, wipe_tower_speed)) ((ConfigOptionFloatOrPercent, wipe_tower_wipe_starting_speed)) + ((ConfigOptionFloat, z_offset)) ((ConfigOptionFloat, z_step)) ((ConfigOptionString, color_change_gcode)) ((ConfigOptionString, pause_print_gcode)) @@ -1335,7 +1336,6 @@ PRINT_CONFIG_CLASS_DERIVED_DEFINE( ((ConfigOptionInt, wipe_tower_extruder)) ((ConfigOptionFloats, wiping_volumes_matrix)) ((ConfigOptionFloats, wiping_volumes_extruders)) - ((ConfigOptionFloat, z_offset)) ((ConfigOptionFloat, init_z_rotate)) ) From b943fbc32274230742ce6ccac0b4424dcf452cdf Mon Sep 17 00:00:00 2001 From: supermerill Date: Mon, 25 Nov 2024 19:20:38 +0100 Subject: [PATCH 09/12] Fix preset compare showing always "false" for booleans --- src/slic3r/GUI/UnsavedChangesDialog.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/slic3r/GUI/UnsavedChangesDialog.cpp b/src/slic3r/GUI/UnsavedChangesDialog.cpp index c6b27cb2cef..5a6430c1d0d 100644 --- a/src/slic3r/GUI/UnsavedChangesDialog.cpp +++ b/src/slic3r/GUI/UnsavedChangesDialog.cpp @@ -1130,7 +1130,7 @@ static wxString get_string_value(std::string opt_key, const DynamicPrintConfig& case coBool: serialized_str = from_u8(option->serialize()); serialized_str.Replace("0", "false"); - serialized_str.Replace("1", "false"); + serialized_str.Replace("1", "true"); serialized_str.Replace("!", "Disabled:"); break; case coBools: { @@ -1140,7 +1140,7 @@ static wxString get_string_value(std::string opt_key, const DynamicPrintConfig& serialized_str = from_u8(option->serialize()); } serialized_str.Replace("0", "false"); - serialized_str.Replace("1", "false"); + serialized_str.Replace("1", "true"); serialized_str.Replace("!", "Disabled:"); break; } From 68233d7a89641deca06a8d874fe30e201493fffa Mon Sep 17 00:00:00 2001 From: supermerill Date: Mon, 25 Nov 2024 22:23:13 +0100 Subject: [PATCH 10/12] Fix ExtrusionEntity can_reverse at initialisation. --- src/libslic3r/ExtrusionEntity.hpp | 62 ++++++++++++++++++++++--------- 1 file changed, 44 insertions(+), 18 deletions(-) diff --git a/src/libslic3r/ExtrusionEntity.hpp b/src/libslic3r/ExtrusionEntity.hpp index 65a70064253..db2a7ca07ef 100644 --- a/src/libslic3r/ExtrusionEntity.hpp +++ b/src/libslic3r/ExtrusionEntity.hpp @@ -183,13 +183,23 @@ class ExtrusionPath : public ExtrusionEntity //ExtrusionPath(ExtrusionRole role) : ExtrusionEntity(true), m_attributes{role} {} ExtrusionPath(const ExtrusionAttributes &attributes, bool can_reverse = true) : ExtrusionEntity(can_reverse), m_attributes(attributes) {} - ExtrusionPath(const ExtrusionPath &rhs, bool can_reverse = true) : ExtrusionEntity(can_reverse), polyline(rhs.polyline), m_attributes(rhs.m_attributes) {} - ExtrusionPath(ExtrusionPath &&rhs, bool can_reverse = true) : ExtrusionEntity(can_reverse), polyline(std::move(rhs.polyline)), m_attributes(rhs.m_attributes) {} + ExtrusionPath(const ExtrusionPath &rhs) : ExtrusionEntity(rhs.m_can_reverse), polyline(rhs.polyline), m_attributes(rhs.m_attributes) {} + ExtrusionPath(ExtrusionPath &&rhs) : ExtrusionEntity(rhs.m_can_reverse), polyline(std::move(rhs.polyline)), m_attributes(rhs.m_attributes) {} ExtrusionPath(const ArcPolyline &polyline, const ExtrusionAttributes &attribs, bool can_reverse = true) : ExtrusionEntity(can_reverse), polyline(polyline), m_attributes(attribs) {} ExtrusionPath(ArcPolyline &&polyline, const ExtrusionAttributes &attribs, bool can_reverse = true) : ExtrusionEntity(can_reverse), polyline(std::move(polyline)), m_attributes(attribs) {} - ExtrusionPath& operator=(const ExtrusionPath &rhs) { this->polyline = rhs.polyline; m_attributes = rhs.m_attributes; return *this; } - ExtrusionPath& operator=(ExtrusionPath &&rhs) { this->polyline = std::move(rhs.polyline); m_attributes = rhs.m_attributes; return *this; } + ExtrusionPath &operator=(const ExtrusionPath &rhs) { + this->m_can_reverse = rhs.m_can_reverse; + this->polyline = rhs.polyline; + m_attributes = rhs.m_attributes; + return *this; + } + ExtrusionPath &operator=(ExtrusionPath &&rhs) { + this->m_can_reverse = rhs.m_can_reverse; + this->polyline = std::move(rhs.polyline); + m_attributes = rhs.m_attributes; + return *this; + } ExtrusionEntity* clone() const override { return new ExtrusionPath(*this); } // Create a new object, initialize it with this object using the move semantics. @@ -280,19 +290,19 @@ class ExtrusionPath3D : public ExtrusionPath { ExtrusionPath3D &operator=(const ExtrusionPath3D &rhs) { - this->m_attributes.role = rhs.role(); - this->m_attributes.mm3_per_mm = rhs.mm3_per_mm(); - this->m_attributes.width = rhs.width(); - this->m_attributes.height = rhs.height(); - this->polyline = rhs.polyline; z_offsets = rhs.z_offsets; return *this; + this->m_can_reverse = rhs.m_can_reverse; + this->m_attributes = rhs.m_attributes; + this->polyline = rhs.polyline; + z_offsets = rhs.z_offsets; + return *this; } ExtrusionPath3D &operator=(ExtrusionPath3D &&rhs) { - this->m_attributes.role = rhs.role(); - this->m_attributes.mm3_per_mm = rhs.mm3_per_mm(); - this->m_attributes.width = rhs.width(); - this->m_attributes.height = rhs.height(); - this->polyline = std::move(rhs.polyline); z_offsets = std::move(rhs.z_offsets); return *this; + this->m_can_reverse = rhs.m_can_reverse; + this->m_attributes = rhs.m_attributes; + this->polyline = std::move(rhs.polyline); + z_offsets = std::move(rhs.z_offsets); + return *this; } virtual ExtrusionPath3D* clone() const override { return new ExtrusionPath3D(*this); } virtual ExtrusionPath3D* clone_move() override { return new ExtrusionPath3D(std::move(*this)); } @@ -327,8 +337,16 @@ class ExtrusionMultiEntity : public ExtrusionEntity { ExtrusionMultiEntity(const std::vector &paths) : paths(paths), ExtrusionEntity(false) {}; ExtrusionMultiEntity(const THING &path): ExtrusionEntity(false) { this->paths.push_back(path); } - ExtrusionMultiEntity& operator=(const ExtrusionMultiEntity &rhs) { this->paths = rhs.paths; return *this; } - ExtrusionMultiEntity& operator=(ExtrusionMultiEntity &&rhs) { this->paths = std::move(rhs.paths); return *this; } + ExtrusionMultiEntity &operator=(const ExtrusionMultiEntity &rhs) { + this->m_can_reverse = rhs.m_can_reverse; + this->paths = rhs.paths; + return *this; + } + ExtrusionMultiEntity &operator=(ExtrusionMultiEntity &&rhs) { + this->m_can_reverse = rhs.m_can_reverse; + this->paths = std::move(rhs.paths); + return *this; + } bool is_loop() const override { return false; } virtual const Point& first_point() const override { return this->paths.front().polyline.front(); } @@ -414,8 +432,16 @@ class ExtrusionMultiPath : public ExtrusionMultiEntity { ExtrusionMultiPath(const ExtrusionPaths &paths) : ExtrusionMultiEntity(paths) {}; ExtrusionMultiPath(const ExtrusionPath &path) :ExtrusionMultiEntity(path) {} - ExtrusionMultiPath& operator=(const ExtrusionMultiPath& rhs) { this->paths = rhs.paths; return *this; } - ExtrusionMultiPath& operator=(ExtrusionMultiPath&& rhs) { this->paths = std::move(rhs.paths); return *this; } + ExtrusionMultiPath &operator=(const ExtrusionMultiPath &rhs) { + this->m_can_reverse = rhs.m_can_reverse; + this->paths = rhs.paths; + return *this; + } + ExtrusionMultiPath &operator=(ExtrusionMultiPath &&rhs) { + this->m_can_reverse = rhs.m_can_reverse; + this->paths = std::move(rhs.paths); + return *this; + } void set_can_reverse(bool can_reverse) { m_can_reverse = can_reverse; } From cdfa045bf3ea7ae50345a2989d5593655caba6b4 Mon Sep 17 00:00:00 2001 From: supermerill Date: Mon, 25 Nov 2024 22:24:17 +0100 Subject: [PATCH 11/12] fix ArcPolyline::split_at when in last segment --- src/libslic3r/Polyline.cpp | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/src/libslic3r/Polyline.cpp b/src/libslic3r/Polyline.cpp index 76adf7ddd7d..96bbc17a85a 100644 --- a/src/libslic3r/Polyline.cpp +++ b/src/libslic3r/Polyline.cpp @@ -862,7 +862,9 @@ void ArcPolyline::clip_end(coordf_t dist) void ArcPolyline::split_at(coordf_t distance, ArcPolyline &p1, ArcPolyline &p2) const { - if(m_path.empty()) return; + if (m_path.empty()) return; + assert(distance > SCALED_EPSILON); + if (distance < SCALED_EPSILON) return; assert(this->is_valid()); p1.m_path.push_back(m_path.front()); size_t idx = 1; @@ -916,6 +918,15 @@ void ArcPolyline::split_at(coordf_t distance, ArcPolyline &p1, ArcPolyline &p2) // increment ++idx; } + assert(!p2.empty()); + if (p2.back() != back()) { + if (p2.size() == 1 || !p2.back().coincides_with_epsilon(back())) { + p2.m_path.push_back(back()); + } else { + // even with arc, the difference is not measurable (less than epsilon) + p2.set_back(back()); + } + } assert(p1.is_valid()); assert(p2.is_valid()); } From effc5df5a109ca667c7cd12a652439d326b6ce43 Mon Sep 17 00:00:00 2001 From: supermerill Date: Mon, 25 Nov 2024 22:24:38 +0100 Subject: [PATCH 12/12] Fix seam_notch --- src/libslic3r/GCode.cpp | 76 +++++++++++++++++++++++++++-------------- 1 file changed, 51 insertions(+), 25 deletions(-) diff --git a/src/libslic3r/GCode.cpp b/src/libslic3r/GCode.cpp index 9a5f5317a60..c601faf4e6a 100644 --- a/src/libslic3r/GCode.cpp +++ b/src/libslic3r/GCode.cpp @@ -4297,8 +4297,12 @@ void GCodeGenerator::seam_notch(const ExtrusionLoop& original_loop, assert(notch_extrusion_start.empty()); assert(notch_extrusion_end.empty()); assert(!building_paths.empty()); + + if (building_paths.size() == 1) + assert(is_full_loop_ccw == Polygon(building_paths.front().polyline.to_polyline().points).is_counter_clockwise()); if (original_loop.role().is_external_perimeter() && building_paths.front().size() > 1 && building_paths.back().size() > 1 - && (this->m_config.seam_notch_all.get_abs_value(1.) > 0 || this->m_config.seam_notch_inner.get_abs_value(1.) > 0 || this->m_config.seam_notch_outer.get_abs_value(1.) > 0)) { + && (this->m_config.seam_notch_all.get_abs_value(1.) > 0 || this->m_config.seam_notch_inner.get_abs_value(1.) > 0 + || this->m_config.seam_notch_outer.get_abs_value(1.) > 0)) { //TODO: check there is at least 4 points coord_t notch_value = 0; //check if applicable to seam_notch_inner @@ -4366,44 +4370,52 @@ void GCodeGenerator::seam_notch(const ExtrusionLoop& original_loop, notch_value = std::min(notch_value, scale_t(building_paths.front().width()) / 2); // found a suitable path, move the seam inner - + if (building_paths.size() == 1) + assert(is_full_loop_ccw == Polygon(building_paths.front().polyline.to_polyline().points).is_counter_clockwise()); + // extract paths from the start coordf_t dist = notch_length; - while (dist > 0) { + while (dist > SCALED_EPSILON) { coordf_t length = building_paths.front().as_polyline().length(); - if(length > dist){ - //found the place to split - notch_extrusion_start.emplace_back(building_paths.front().attributes(), building_paths.front().can_reverse()); + if (length > dist) { + // found the place to split + notch_extrusion_start.emplace_back(building_paths.front().attributes(), + building_paths.front().can_reverse()); ArcPolyline ap2; building_paths.front().as_polyline().split_at(dist, notch_extrusion_start.back().polyline, ap2); building_paths.front().polyline = ap2; dist = 0; - }else{ + } else { notch_extrusion_start.push_back(std::move(building_paths.front())); building_paths.erase(building_paths.begin()); dist -= length; } + assert(notch_extrusion_start.back().polyline.back() == building_paths.front().polyline.front()); } // extract paths from the end dist = notch_length; - while (dist > 0) { + while (dist > SCALED_EPSILON) { coordf_t length = building_paths.back().as_polyline().length(); - if(length > dist){ - //found the place to split - notch_extrusion_end.emplace_back(building_paths.back().attributes(), building_paths.back().can_reverse()); - ArcPolyline ap1; - building_paths.back().as_polyline().split_at(dist, ap1, notch_extrusion_end.back().polyline); - building_paths.back().polyline = ap1; + if (length > dist) { + // found the place to split + notch_extrusion_end.emplace_back(building_paths.back().attributes(), + building_paths.back().can_reverse()); + ArcPolyline ap2; + building_paths.back().polyline.split_at(length - dist, ap2, notch_extrusion_end.back().polyline); + building_paths.back().polyline = ap2; dist = 0; - }else{ + } else { notch_extrusion_end.push_back(std::move(building_paths.back())); - building_paths.erase(building_paths.begin()); + notch_extrusion_end.back().polyline.reverse(); + building_paths.pop_back(); dist -= length; } - //notch_extrusion_end has benn created "in-reverse", I have to put it the right way - std::reverse(notch_extrusion_end.begin(), notch_extrusion_end.end()); + assert(building_paths.back().polyline.back() == notch_extrusion_end.front().polyline.front()); } - + // notch_extrusion_end has benn created "in-reverse", I have to put it the right way + std::reverse(notch_extrusion_end.begin(), notch_extrusion_end.end()); + assert(building_paths.back().polyline.back() == notch_extrusion_end.front().polyline.front()); + //kind of the same as the wipe Point prev_point = notch_extrusion_end.back().first_point(); // second to last point Point end_point = notch_extrusion_end.back().last_point(); @@ -4411,9 +4423,10 @@ void GCodeGenerator::seam_notch(const ExtrusionLoop& original_loop, Point next_point = notch_extrusion_start.front().last_point(); // second point //safeguard : if a ExtrusionRole::ror exist abord; if (next_point == start_point || prev_point == end_point) { - throw Slic3r::SlicingError(_u8L("ExtrusionRole::ror while writing gcode: two points are at the same position. Please send the .3mf project to the dev team for debugging. Extrude loop: seam notch.")); + throw Slic3r::SlicingError(_u8L("ExtrusionRole::error while writing gcode: two points are at the same position. Please send the .3mf project to the dev team for debugging. Extrude loop: seam notch.")); } - if(building_paths.size() == 1) + + if (building_paths.size() == 1) assert(is_full_loop_ccw == Polygon(building_paths.front().polyline.to_polyline().points).is_counter_clockwise()); double angle = PI / 2; if (is_hole_loop ? (is_full_loop_ccw) : (!is_full_loop_ccw)) { @@ -4448,13 +4461,13 @@ void GCodeGenerator::seam_notch(const ExtrusionLoop& original_loop, assert(ccw_angle_old_test(start_point, prev_point, next_point) == abs_angle(angle_ccw( prev_point -start_point,next_point- start_point))); check_angle = abs_angle(angle_ccw( prev_point -start_point,next_point- start_point)); } else { - assert(ccw_angle_old_test(end_point, prev_point, start_point) == abs_angle(angle_ccw( start_point -end_point,prev_point- end_point))); - check_angle = abs_angle(angle_ccw( start_point -end_point,prev_point- end_point)); + assert(is_approx(ccw_angle_old_test(end_point, prev_point, start_point), abs_angle(angle_ccw(prev_point- end_point, start_point -end_point)), EPSILON)); + check_angle = abs_angle(angle_ccw(prev_point- end_point, start_point -end_point)); if ((is_hole_loop ? -check_angle : check_angle) > min_angle) { BOOST_LOG_TRIVIAL(debug) << "notch abord: too big angle\n"; return; } - assert(ccw_angle_old_test(start_point, end_point, next_point) == abs_angle(angle_ccw( end_point - start_point,next_point - start_point))); + assert(is_approx(ccw_angle_old_test(start_point, end_point, next_point), abs_angle(angle_ccw( end_point - start_point,next_point - start_point)), EPSILON)); check_angle = abs_angle(angle_ccw( end_point - start_point,next_point - start_point)); } assert(end_point != start_point); @@ -4480,6 +4493,9 @@ void GCodeGenerator::seam_notch(const ExtrusionLoop& original_loop, path.attributes_mutable().width = path.width() * ratio; path.attributes_mutable().mm3_per_mm = path.mm3_per_mm() * ratio; }; + for (ExtrusionPath &ep : notch_extrusion_start) { + assert(!ep.can_reverse()); + } //TODO change below things to avoid deleting more than one path, or at least preserve their flow @@ -4492,13 +4508,16 @@ void GCodeGenerator::seam_notch(const ExtrusionLoop& original_loop, Point p1 = Line(moved_start, Line(start_point, notch_extrusion_start.front().polyline.front()).midpoint()).midpoint(); Point p2 = Line(p1, building_paths.front().first_point()).midpoint(); p2 = Line(p2, notch_extrusion_start.back().polyline.back()).midpoint(); - ExtrusionPath model = notch_extrusion_start.front(); + ExtrusionPath model(notch_extrusion_start.front()); model.polyline.clear(); notch_extrusion_start.clear(); create_new_extrusion(notch_extrusion_start, model, 0.25f, moved_start, p1); create_new_extrusion(notch_extrusion_start, model, 0.5f, p1, p2); create_new_extrusion(notch_extrusion_start, model, 0.75f, p2, building_paths.front().first_point()); } //else : keep the path as-is + for (ExtrusionPath &ep : notch_extrusion_start) { + assert(!ep.can_reverse()); + } //reduce the flow of the notch path, as it's longer than previously length_temp = notch_length; if (length_temp * length_temp < @@ -4525,6 +4544,9 @@ void GCodeGenerator::seam_notch(const ExtrusionLoop& original_loop, create_new_extrusion(notch_extrusion_end, model, 0.f, p1, moved_end); } //else : keep the path as-is } + for (ExtrusionPath &ep : notch_extrusion_start) { + assert(!ep.can_reverse()); + } } std::string GCodeGenerator::extrude_loop(const ExtrusionLoop &original_loop, const std::string_view description, double speed) @@ -4615,6 +4637,8 @@ std::string GCodeGenerator::extrude_loop(const ExtrusionLoop &original_loop, con //assert(!path.can_reverse() || !is_perimeter(path.role())); //just ensure the perimeter have their direction enforced. path.set_can_reverse(false); } + if (building_paths.size() == 1) + assert(is_full_loop_ccw == Polygon(building_paths.front().polyline.to_polyline().points).is_counter_clockwise()); if (m_enable_loop_clipping && m_writer.tool_is_extruder()) { coordf_t clip_length = scale_(m_config.seam_gap.get_abs_value(m_writer.tool()->id(), nozzle_diam)); if (loop_to_seam.role().is_external_perimeter()) { @@ -4653,6 +4677,8 @@ std::string GCodeGenerator::extrude_loop(const ExtrusionLoop &original_loop, con } } if (building_paths.empty()) return ""; + if (building_paths.size() == 1) + assert(is_full_loop_ccw == Polygon(building_paths.front().polyline.to_polyline().points).is_counter_clockwise()); const ExtrusionPaths& wipe_paths = building_paths; for (const ExtrusionPath &path : wipe_paths)