From d3734aa5ae2acc07a4031b2678cbb07ed6af1efe Mon Sep 17 00:00:00 2001 From: Vojtech Bubnik Date: Thu, 29 Sep 2022 16:51:44 +0200 Subject: [PATCH 01/48] OSX specific: Setting the QoS level to the highest level for TBB worker threads: QOS_CLASS_USER_INTERACTIVE. The one level lower QOS_CLASS_USER_INITIATED makes our tester Filip unhappy, because when slicing tree supports Filip switches to a browser on another display wating for the slicer to finish, while OSX moves the slicing threads to high efficiency low coal burning cores. --- src/libslic3r/Thread.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/libslic3r/Thread.cpp b/src/libslic3r/Thread.cpp index 22d4cb419..ea9b60a47 100644 --- a/src/libslic3r/Thread.cpp +++ b/src/libslic3r/Thread.cpp @@ -249,7 +249,8 @@ void set_current_thread_qos() #ifdef __APPLE__ // OSX specific: Set Quality of Service to "user initiated", so that the threads will be scheduled to high performance // cores if available. - pthread_set_qos_class_self_np(QOS_CLASS_USER_INITIATED, 0); + // With QOS_CLASS_USER_INITIATED the worker threads drop priority once slicer loses user focus. + pthread_set_qos_class_self_np(QOS_CLASS_USER_INTERACTIVE, 0); #endif // __APPLE__ } From 11c0e567a68979e96085b3763a76464cb793ea12 Mon Sep 17 00:00:00 2001 From: Vojtech Bubnik Date: Tue, 20 Dec 2022 09:09:10 +0100 Subject: [PATCH 02/48] WIP "ensure verticall wall thickness" rework: 1) New region expansion code to propagate wave from a boundary of a region inside of it. 2) get_extents() extended with a template attribute to work with zero area data sets. 3) ClipperZUtils.hpp for handling Clipper operation with Z coordinate (for source contour identification) --- src/clipper/clipper.cpp | 27 +- src/clipper/clipper.hpp | 1 + src/libslic3r/Algorithm/RegionExpansion.cpp | 351 ++++++++++++++++++++ src/libslic3r/Algorithm/RegionExpansion.hpp | 26 ++ src/libslic3r/BoundingBox.hpp | 45 ++- src/libslic3r/Brim.cpp | 2 +- src/libslic3r/CMakeLists.txt | 3 + src/libslic3r/ClipperUtils.cpp | 10 +- src/libslic3r/ClipperUtils.hpp | 7 +- src/libslic3r/ClipperZUtils.hpp | 143 ++++++++ src/libslic3r/Layer.cpp | 42 +-- src/libslic3r/PerimeterGenerator.cpp | 8 +- src/libslic3r/Point.cpp | 15 +- src/libslic3r/Point.hpp | 18 +- src/libslic3r/Polyline.hpp | 19 ++ src/libslic3r/libslic3r.h | 6 +- tests/fff_print/test_clipper.cpp | 71 +++- tests/libslic3r/CMakeLists.txt | 1 + tests/libslic3r/test_region_expansion.cpp | 254 ++++++++++++++ 19 files changed, 964 insertions(+), 85 deletions(-) create mode 100644 src/libslic3r/Algorithm/RegionExpansion.cpp create mode 100644 src/libslic3r/Algorithm/RegionExpansion.hpp create mode 100644 src/libslic3r/ClipperZUtils.hpp create mode 100644 tests/libslic3r/test_region_expansion.cpp diff --git a/src/clipper/clipper.cpp b/src/clipper/clipper.cpp index 518b4b7c3..3094e38b4 100644 --- a/src/clipper/clipper.cpp +++ b/src/clipper/clipper.cpp @@ -4093,19 +4093,40 @@ void AddPolyNodeToPaths(const PolyNode& polynode, NodeType nodetype, Paths& path for (int i = 0; i < polynode.ChildCount(); ++i) AddPolyNodeToPaths(*polynode.Childs[i], nodetype, paths); } + +void AddPolyNodeToPaths(PolyNode&& polynode, NodeType nodetype, Paths& paths) +{ + bool match = true; + if (nodetype == ntClosed) match = !polynode.IsOpen(); + else if (nodetype == ntOpen) return; + + if (!polynode.Contour.empty() && match) + paths.push_back(std::move(polynode.Contour)); + for (int i = 0; i < polynode.ChildCount(); ++i) + AddPolyNodeToPaths(std::move(*polynode.Childs[i]), nodetype, paths); +} + //------------------------------------------------------------------------------ void PolyTreeToPaths(const PolyTree& polytree, Paths& paths) { - paths.resize(0); + paths.clear(); paths.reserve(polytree.Total()); AddPolyNodeToPaths(polytree, ntAny, paths); } + +void PolyTreeToPaths(PolyTree&& polytree, Paths& paths) +{ + paths.clear(); + paths.reserve(polytree.Total()); + AddPolyNodeToPaths(std::move(polytree), ntAny, paths); +} + //------------------------------------------------------------------------------ void ClosedPathsFromPolyTree(const PolyTree& polytree, Paths& paths) { - paths.resize(0); + paths.clear(); paths.reserve(polytree.Total()); AddPolyNodeToPaths(polytree, ntClosed, paths); } @@ -4113,7 +4134,7 @@ void ClosedPathsFromPolyTree(const PolyTree& polytree, Paths& paths) void OpenPathsFromPolyTree(PolyTree& polytree, Paths& paths) { - paths.resize(0); + paths.clear(); paths.reserve(polytree.Total()); //Open paths are top level only, so ... for (int i = 0; i < polytree.ChildCount(); ++i) diff --git a/src/clipper/clipper.hpp b/src/clipper/clipper.hpp index 641476c8b..849672a8f 100644 --- a/src/clipper/clipper.hpp +++ b/src/clipper/clipper.hpp @@ -206,6 +206,7 @@ void MinkowskiSum(const Path& pattern, const Paths& paths, Paths& solution, bool void MinkowskiDiff(const Path& poly1, const Path& poly2, Paths& solution); void PolyTreeToPaths(const PolyTree& polytree, Paths& paths); +void PolyTreeToPaths(PolyTree&& polytree, Paths& paths); void ClosedPathsFromPolyTree(const PolyTree& polytree, Paths& paths); void OpenPathsFromPolyTree(PolyTree& polytree, Paths& paths); diff --git a/src/libslic3r/Algorithm/RegionExpansion.cpp b/src/libslic3r/Algorithm/RegionExpansion.cpp new file mode 100644 index 000000000..2a22fe785 --- /dev/null +++ b/src/libslic3r/Algorithm/RegionExpansion.cpp @@ -0,0 +1,351 @@ +#include "RegionExpansion.hpp" + +#include +#include + +namespace Slic3r { +namespace Algorithm { + +// similar to expolygons_to_zpaths(), but each contour is expanded before converted to zpath. +// The expanded contours are then opened (the first point is repeated at the end). +static ClipperLib_Z::Paths expolygons_to_zpaths_expanded_opened( + const ExPolygons &src, const float expansion, coord_t base_idx) +{ + ClipperLib_Z::Paths out; + out.reserve(2 * std::accumulate(src.begin(), src.end(), size_t(0), + [](const size_t acc, const ExPolygon &expoly) { return acc + expoly.num_contours(); })); + coord_t z = base_idx; + ClipperLib::ClipperOffset offsetter; + offsetter.ShortestEdgeLength = expansion * ClipperOffsetShortestEdgeFactor; + ClipperLib::Paths expansion_cache; + for (const ExPolygon &expoly : src) { + for (size_t icontour = 0; icontour < expoly.num_contours(); ++ icontour) { + // Execute reorients the contours so that the outer most contour has a positive area. Thus the output + // contours will be CCW oriented even though the input paths are CW oriented. + // Offset is applied after contour reorientation, thus the signum of the offset value is reversed. + offsetter.Clear(); + offsetter.AddPath(expoly.contour_or_hole(icontour).points, ClipperLib::jtSquare, ClipperLib::etClosedPolygon); + expansion_cache.clear(); + offsetter.Execute(expansion_cache, icontour == 0 ? expansion : -expansion); + append(out, ClipperZUtils::to_zpaths(expansion_cache, z)); + } + ++ z; + } + return out; +} + +// Paths were created by splitting closed polygons into open paths and then by clipping them. +// Thus some pieces of the clipped polygons may now become split at the ends of the source polygons. +// Those ends are sorted lexicographically in "splits". +// Reconnect those split pieces. +static inline void merge_splits(ClipperLib_Z::Paths &paths, std::vector> &splits) +{ + for (auto it_path = paths.begin(); it_path != paths.end(); ) { + ClipperLib_Z::Path &path = *it_path; + assert(path.size() >= 2); + bool merged = false; + if (path.size() >= 2) { + const ClipperLib_Z::IntPoint &front = path.front(); + const ClipperLib_Z::IntPoint &back = path.back(); + // The path before clipping was supposed to cross the clipping boundary or be fully out of it. + // Thus the clipped contour is supposed to become open. + assert(front.x() != back.x() || front.y() != back.y()); + if (front.x() != back.x() || front.y() != back.y()) { + // Look up the ends in "splits", possibly join the contours. + // "splits" maps into the other piece connected to the same end point. + auto find_end = [&splits](const ClipperLib_Z::IntPoint &pt) -> std::pair* { + auto it = std::lower_bound(splits.begin(), splits.end(), pt, + [](const auto &l, const auto &r){ return ClipperZUtils::zpoint_lower(l.first, r); }); + return it != splits.end() && it->first == pt ? &(*it) : nullptr; + }; + auto *end = find_end(front); + bool end_front = true; + if (! end) { + end_front = false; + end = find_end(back); + } + if (end) { + // This segment ends at a split point of the source closed contour before clipping. + if (end->second == -1) { + // Open end was found, not matched yet. + end->second = int(it_path - paths.begin()); + } else { + // Open end was found and matched with end->second + ClipperLib_Z::Path &other_path = paths[end->second]; + polylines_merge(other_path, other_path.front() == end->first, std::move(path), end_front); + if (std::next(it_path) == paths.end()) { + paths.pop_back(); + break; + } + path = std::move(paths.back()); + paths.pop_back(); + merged = true; + } + } + } + } + if (! merged) + ++ it_path; + } +} + +static ClipperLib::Paths wavefront_initial(ClipperLib::ClipperOffset &co, const ClipperLib::Paths &polylines, float offset) +{ + ClipperLib::Paths out; + out.reserve(polylines.size()); + ClipperLib::Paths out_this; + for (const ClipperLib::Path &path : polylines) { + co.Clear(); + co.AddPath(path, jtRound, ClipperLib::etOpenRound); + co.Execute(out_this, offset); + append(out, std::move(out_this)); + } + return out; +} + +// Input polygons may consist of multiple expolygons, even nested expolygons. +// After inflation some polygons may thus overlap, however the overlap is being resolved during the successive +// clipping operation, thus it is not being done here. +static ClipperLib::Paths wavefront_step(ClipperLib::ClipperOffset &co, const ClipperLib::Paths &polygons, float offset) +{ + ClipperLib::Paths out; + out.reserve(polygons.size()); + ClipperLib::Paths out_this; + for (const ClipperLib::Path &polygon : polygons) { + co.Clear(); + // Execute reorients the contours so that the outer most contour has a positive area. Thus the output + // contours will be CCW oriented even though the input paths are CW oriented. + // Offset is applied after contour reorientation, thus the signum of the offset value is reversed. + co.AddPath(polygon, jtRound, ClipperLib::etClosedPolygon); + bool ccw = ClipperLib::Orientation(polygon); + co.Execute(out_this, ccw ? offset : - offset); + if (! ccw) { + // Reverse the resulting contours. + for (ClipperLib::Path &path : out_this) + std::reverse(path.begin(), path.end()); + } + append(out, std::move(out_this)); + } + return out; +} + +static ClipperLib::Paths wavefront_clip(const ClipperLib::Paths &wavefront, const Polygons &clipping) +{ + ClipperLib::Clipper clipper; + clipper.AddPaths(wavefront, ClipperLib::ptSubject, true); + clipper.AddPaths(ClipperUtils::PolygonsProvider(clipping), ClipperLib::ptClip, true); + ClipperLib::Paths out; + clipper.Execute(ClipperLib::ctIntersection, out, ClipperLib::pftPositive, ClipperLib::pftPositive); + return out; +} + +static Polygons propagate_wave_from_boundary( + ClipperLib::ClipperOffset &co, + // Seed of the wave: Open polylines very close to the boundary. + const ClipperLib::Paths &seed, + // Boundary inside which the waveform will propagate. + const ExPolygon &boundary, + // How much to inflate the seed lines to produce the first wave area. + const float initial_step, + // How much to inflate the first wave area and the successive wave areas in each step. + const float other_step, + // Number of inflate steps after the initial step. + const size_t num_other_steps, + // Maximum inflation of seed contours over the boundary. Used to trim boundary to speed up + // clipping during wave propagation. + const float max_inflation) +{ + assert(! seed.empty() && seed.front().size() >= 2); + Polygons clipping = ClipperUtils::clip_clipper_polygons_with_subject_bbox(boundary, get_extents(seed).inflated(max_inflation)); + ClipperLib::Paths polygons = wavefront_clip(wavefront_initial(co, seed, initial_step), clipping); + // Now offset the remaining + for (size_t ioffset = 0; ioffset < num_other_steps; ++ ioffset) + polygons = wavefront_clip(wavefront_step(co, polygons, other_step), clipping); + return to_polygons(polygons); +} + +// Calculating radius discretization according to ClipperLib offsetter code, see void ClipperOffset::DoOffset(double delta) +inline double clipper_round_offset_error(double offset, double arc_tolerance) +{ + static constexpr const double def_arc_tolerance = 0.25; + const double y = + arc_tolerance <= 0 ? + def_arc_tolerance : + arc_tolerance > offset * def_arc_tolerance ? + offset * def_arc_tolerance : + arc_tolerance; + double steps = std::min(M_PI / std::acos(1. - y / offset), offset * M_PI); + return offset * (1. - cos(M_PI / steps)); +} + +// Returns regions per source ExPolygon expanded into boundary. +std::vector expand_expolygons( + // Source regions that are supposed to touch the boundary. + // Boundaries of source regions touching the "boundary" regions will be expanded into the "boundary" region. + const ExPolygons &src, + const ExPolygons &boundary, + // Scaled expansion value + float full_expansion, + // Expand by waves of expansion_step size (expansion_step is scaled). + float 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(max_nr_expansion_steps > 0); + + // Initial expansion of src to make the source regions intersect with boundary regions just a bit. + float tiny_expansion; + // How much to inflate the seed lines to produce the first wave area. + float initial_step; + // How much to inflate the first wave area and the successive wave areas in each step. + float other_step; + // Number of inflate steps after the initial step. + size_t num_other_steps; + // Maximum inflation of seed contours over the boundary. Used to trim boundary to speed up + // clipping during wave propagation. + float max_inflation; + + // Offsetter to be applied for all inflation waves. Its accuracy is set with the block below. + ClipperLib::ClipperOffset co; + + { + // Initial expansion of src to make the source regions intersect with boundary regions just a bit. + // The expansion should not be too tiny, but also small enough, so the following expansion will + // compensate for tiny_expansion and bring the wave back to the boundary without producing + // ugly cusps where it touches the boundary. + tiny_expansion = std::min(0.25f * full_expansion, scaled(0.05f)); + size_t nsteps = size_t(ceil((full_expansion - tiny_expansion) / expansion_step)); + if (max_nr_expansion_steps > 0) + nsteps = std::min(nsteps, max_nr_expansion_steps); + assert(nsteps > 0); + initial_step = (full_expansion - tiny_expansion) / nsteps; + if (nsteps > 1 && 0.25 * initial_step < tiny_expansion) { + // Decrease the step size by lowering number of steps. + nsteps = std::max(1, (floor((full_expansion - tiny_expansion) / (4. * tiny_expansion)))); + initial_step = (full_expansion - tiny_expansion) / nsteps; + } + if (0.25 * initial_step < tiny_expansion || nsteps == 1) { + tiny_expansion = 0.2f * full_expansion; + initial_step = 0.8f * full_expansion; + } + other_step = initial_step; + num_other_steps = nsteps - 1; + + // Accuracy of the offsetter for wave propagation. + co.ArcTolerance = float(scale_(0.1)); + co.ShortestEdgeLength = std::abs(initial_step * ClipperOffsetShortestEdgeFactor); + + // Maximum inflation of seed contours over the boundary. Used to trim boundary to speed up + // clipping during wave propagation. Needs to be in sync with the offsetter accuracy. + // Clipper positive round offset should rather offset less than more. + // Still a little bit of additional offset was added. + max_inflation = (tiny_expansion + nsteps * initial_step) * 1.1; +// (clipper_round_offset_error(tiny_expansion, co.ArcTolerance) + nsteps * clipper_round_offset_error(initial_step, co.ArcTolerance) * 1.5; // Account for uncertainty + } + + using Intersection = ClipperZUtils::ClipperZIntersectionVisitor::Intersection; + using Intersections = ClipperZUtils::ClipperZIntersectionVisitor::Intersections; + + ClipperLib_Z::Paths expansion_seeds; + Intersections intersections; + + coord_t idx_boundary_begin = 1; + coord_t idx_boundary_end; + coord_t idx_src_end; + + { + ClipperLib_Z::Clipper zclipper; + ClipperZUtils::ClipperZIntersectionVisitor visitor(intersections); + zclipper.ZFillFunction(visitor.clipper_callback()); + // as closed contours + { + ClipperLib_Z::Paths zboundary = ClipperZUtils::expolygons_to_zpaths(boundary, idx_boundary_begin); + idx_boundary_end = idx_boundary_begin + coord_t(zboundary.size()); + zclipper.AddPaths(zboundary, ClipperLib_Z::ptClip, true); + } + // as open contours + std::vector> zsrc_splits; + { + ClipperLib_Z::Paths zsrc = expolygons_to_zpaths_expanded_opened(src, tiny_expansion, idx_boundary_end); + zclipper.AddPaths(zsrc, ClipperLib_Z::ptSubject, false); + idx_src_end = idx_boundary_end + coord_t(zsrc.size()); + zsrc_splits.reserve(zsrc.size()); + for (const ClipperLib_Z::Path &path : zsrc) { + assert(path.size() >= 2); + assert(path.front() == path.back()); + zsrc_splits.emplace_back(path.front(), -1); + } + std::sort(zsrc_splits.begin(), zsrc_splits.end(), [](const auto &l, const auto &r){ return ClipperZUtils::zpoint_lower(l.first, r.first); }); + } + ClipperLib_Z::PolyTree polytree; + zclipper.Execute(ClipperLib_Z::ctIntersection, polytree, ClipperLib_Z::pftNonZero, ClipperLib_Z::pftNonZero); + ClipperLib_Z::PolyTreeToPaths(std::move(polytree), expansion_seeds); + merge_splits(expansion_seeds, zsrc_splits); + } + + // Sort paths into their respective islands. + // Each src x boundary will be processed (wave expanded) independently. + // Multiple pieces of a single src may intersect the same boundary. + struct SeedOrigin { + int src; + int boundary; + int seed; + }; + std::vector map_seeds; + map_seeds.reserve(expansion_seeds.size()); + int iseed = 0; + for (const ClipperLib_Z::Path &path : expansion_seeds) { + assert(path.size() >= 2); + const ClipperLib_Z::IntPoint &front = path.front(); + const ClipperLib_Z::IntPoint &back = path.back(); + // Both ends of a seed segment are supposed to be inside a single boundary expolygon. + assert(front.z() < 0); + assert(back.z() < 0); + const Intersection *intersection = nullptr; + auto intersection_point_valid = [idx_boundary_end, idx_src_end](const Intersection &is) { + return is.first >= 1 && is.first < idx_boundary_end && + is.second >= idx_boundary_end && is.second < idx_src_end; + }; + if (front.z() < 0) { + const Intersection &is = intersections[- front.z() - 1]; + assert(intersection_point_valid(is)); + if (intersection_point_valid(is)) + intersection = &is; + } + if (! intersection && back.z() < 0) { + const Intersection &is = intersections[- back.z() - 1]; + assert(intersection_point_valid(is)); + if (intersection_point_valid(is)) + intersection = &is; + } + if (intersection) { + // The path intersects the boundary contour at least at one side. + map_seeds.push_back({ intersection->second - idx_boundary_end, intersection->first - 1, iseed }); + } + ++ iseed; + } + // Sort the seeds by their intersection boundary and source contour. + std::sort(map_seeds.begin(), map_seeds.end(), [](const auto &l, const auto &r){ + return l.boundary < r.boundary || (l.boundary == r.boundary && l.src < r.src); + }); + + std::vector out(src.size(), Polygons{}); + ClipperLib::Paths paths; + for (auto it_seed = map_seeds.begin(); it_seed != map_seeds.end();) { + auto it = it_seed; + paths.clear(); + for (; it != map_seeds.end() && it->boundary == it_seed->boundary && it->src == it_seed->src; ++ it) + paths.emplace_back(ClipperZUtils::from_zpath(expansion_seeds[it->seed])); + // Propagate the wavefront while clipping it with the trimmed boundary. + // Collect the expanded polygons, merge them with the source polygons. + append(out[it_seed->src], propagate_wave_from_boundary(co, paths, boundary[it_seed->boundary], initial_step, other_step, num_other_steps, max_inflation)); + it_seed = it; + } + + return out; +} + +} // Algorithm +} // Slic3r diff --git a/src/libslic3r/Algorithm/RegionExpansion.hpp b/src/libslic3r/Algorithm/RegionExpansion.hpp new file mode 100644 index 000000000..bbfcc0a65 --- /dev/null +++ b/src/libslic3r/Algorithm/RegionExpansion.hpp @@ -0,0 +1,26 @@ +#ifndef SRC_LIBSLIC3R_ALGORITHM_REGION_EXPANSION_HPP_ +#define SRC_LIBSLIC3R_ALGORITHM_REGION_EXPANSION_HPP_ + +#include + +namespace Slic3r { + +class Polygon; +using Polygons = std::vector; +class ExPolygon; +using ExPolygons = std::vector; + +namespace Algorithm { + +std::vector expand_expolygons(const ExPolygons &src, const ExPolygons &boundary, + // Scaled expansion value + float expansion, + // Expand by waves of expansion_step size (expansion_step is scaled). + float expansion_step, + // Don't take more than max_nr_steps for small expansion_step. + size_t max_nr_steps); + +} // Algorithm +} // Slic3r + +#endif /* SRC_LIBSLIC3R_ALGORITHM_REGION_EXPANSION_HPP_ */ diff --git a/src/libslic3r/BoundingBox.hpp b/src/libslic3r/BoundingBox.hpp index 6c16d08b7..3c124fe2a 100644 --- a/src/libslic3r/BoundingBox.hpp +++ b/src/libslic3r/BoundingBox.hpp @@ -22,24 +22,9 @@ public: BoundingBoxBase(const PointClass &p1, const PointClass &p2, const PointClass &p3) : min(p1), max(p1), defined(false) { merge(p2); merge(p3); } - template > - BoundingBoxBase(It from, It to) : min(PointClass::Zero()), max(PointClass::Zero()) - { - if (from == to) { - this->defined = false; - // throw Slic3r::InvalidArgument("Empty point set supplied to BoundingBoxBase constructor"); - } else { - auto it = from; - this->min = it->template cast(); - this->max = this->min; - for (++ it; it != to; ++ it) { - auto vec = it->template cast(); - this->min = this->min.cwiseMin(vec); - this->max = this->max.cwiseMax(vec); - } - this->defined = (this->min.x() < this->max.x()) && (this->min.y() < this->max.y()); - } - } + template> + BoundingBoxBase(It from, It to) + { construct(*this, from, to); } BoundingBoxBase(const std::vector &points) : BoundingBoxBase(points.begin(), points.end()) @@ -70,6 +55,30 @@ public: } bool operator==(const BoundingBoxBase &rhs) { return this->min == rhs.min && this->max == rhs.max; } bool operator!=(const BoundingBoxBase &rhs) { return ! (*this == rhs); } + +private: + // to access construct() + friend BoundingBox get_extents(const Points &pts); + friend BoundingBox get_extents(const Points &pts); + + // if IncludeBoundary, then a bounding box is defined even for a single point. + // otherwise a bounding box is only defined if it has a positive area. + // The output bounding box is expected to be set to "undefined" initially. + template> + static void construct(BoundingBoxType &out, It from, It to) + { + if (from != to) { + auto it = from; + out.min = it->template cast(); + out.max = out.min; + for (++ it; it != to; ++ it) { + auto vec = it->template cast(); + out.min = out.min.cwiseMin(vec); + out.max = out.max.cwiseMax(vec); + } + out.defined = IncludeBoundary || (out.min.x() < out.max.x() && out.min.y() < out.max.y()); + } + } }; template diff --git a/src/libslic3r/Brim.cpp b/src/libslic3r/Brim.cpp index 061cf1423..eed003be2 100644 --- a/src/libslic3r/Brim.cpp +++ b/src/libslic3r/Brim.cpp @@ -641,7 +641,7 @@ ExtrusionEntityCollection make_brim(const Print &print, PrintTryCancel try_cance // perform operation ClipperLib_Z::PolyTree loops_trimmed_tree; clipper.Execute(ClipperLib_Z::ctDifference, loops_trimmed_tree, ClipperLib_Z::pftNonZero, ClipperLib_Z::pftNonZero); - ClipperLib_Z::PolyTreeToPaths(loops_trimmed_tree, loops_trimmed); + ClipperLib_Z::PolyTreeToPaths(std::move(loops_trimmed_tree), loops_trimmed); } // Third, produce the extrusions, sorted by the source loop indices. diff --git a/src/libslic3r/CMakeLists.txt b/src/libslic3r/CMakeLists.txt index 06e32e2a2..bdf057f58 100644 --- a/src/libslic3r/CMakeLists.txt +++ b/src/libslic3r/CMakeLists.txt @@ -22,6 +22,8 @@ set(SLIC3R_SOURCES AABBTreeLines.hpp AABBMesh.hpp AABBMesh.cpp + Algorithm/RegionExpansion.cpp + Algorithm/RegionExpansion.hpp BoundingBox.cpp BoundingBox.hpp BridgeDetector.cpp @@ -35,6 +37,7 @@ set(SLIC3R_SOURCES clipper.hpp ClipperUtils.cpp ClipperUtils.hpp + ClipperZUtils.hpp Color.cpp Color.hpp Config.cpp diff --git a/src/libslic3r/ClipperUtils.cpp b/src/libslic3r/ClipperUtils.cpp index 4762c3e24..abeec6893 100644 --- a/src/libslic3r/ClipperUtils.cpp +++ b/src/libslic3r/ClipperUtils.cpp @@ -8,8 +8,6 @@ #include "SVG.hpp" #endif /* CLIPPER_UTILS_DEBUG */ -#define CLIPPER_OFFSET_SHORTEST_EDGE_FACTOR (0.005f) - namespace Slic3r { #ifdef CLIPPER_UTILS_DEBUG @@ -267,7 +265,7 @@ static ClipperLib::Paths raw_offset(PathsProvider &&paths, float offset, Clipper co.ArcTolerance = miterLimit; else co.MiterLimit = miterLimit; - co.ShortestEdgeLength = double(std::abs(offset * CLIPPER_OFFSET_SHORTEST_EDGE_FACTOR)); + co.ShortestEdgeLength = std::abs(offset * ClipperOffsetShortestEdgeFactor); for (const ClipperLib::Path &path : paths) { co.Clear(); // Execute reorients the contours so that the outer most contour has a positive area. Thus the output @@ -414,7 +412,7 @@ static int offset_expolygon_inner(const Slic3r::ExPolygon &expoly, const float d co.ArcTolerance = miterLimit; else co.MiterLimit = miterLimit; - co.ShortestEdgeLength = double(std::abs(delta * CLIPPER_OFFSET_SHORTEST_EDGE_FACTOR)); + co.ShortestEdgeLength = std::abs(delta * ClipperOffsetShortestEdgeFactor); co.AddPath(expoly.contour.points, joinType, ClipperLib::etClosedPolygon); co.Execute(contours, delta); } @@ -435,7 +433,7 @@ static int offset_expolygon_inner(const Slic3r::ExPolygon &expoly, const float d co.ArcTolerance = miterLimit; else co.MiterLimit = miterLimit; - co.ShortestEdgeLength = double(std::abs(delta * CLIPPER_OFFSET_SHORTEST_EDGE_FACTOR)); + co.ShortestEdgeLength = std::abs(delta * ClipperOffsetShortestEdgeFactor); co.AddPath(hole.points, joinType, ClipperLib::etClosedPolygon); ClipperLib::Paths out2; // Execute reorients the contours so that the outer most contour has a positive area. Thus the output @@ -1055,7 +1053,7 @@ ClipperLib::Path mittered_offset_path_scaled(const Points &contour, const std::v }; // Minimum edge length, squared. - double lmin = *std::max_element(deltas.begin(), deltas.end()) * CLIPPER_OFFSET_SHORTEST_EDGE_FACTOR; + double lmin = *std::max_element(deltas.begin(), deltas.end()) * ClipperOffsetShortestEdgeFactor; double l2min = lmin * lmin; // Minimum angle to consider two edges to be parallel. // Vojtech's estimate. diff --git a/src/libslic3r/ClipperUtils.hpp b/src/libslic3r/ClipperUtils.hpp index 65ecfb76a..a9bd36fe1 100644 --- a/src/libslic3r/ClipperUtils.hpp +++ b/src/libslic3r/ClipperUtils.hpp @@ -39,6 +39,9 @@ static constexpr const Slic3r::ClipperLib::JoinType DefaultLineJoinType = Sl // Miter limit is ignored for jtSquare. static constexpr const double DefaultLineMiterLimit = 0.; +// Decimation factor applied on input contour when doing offset, multiplied by the offset distance. +static constexpr const double ClipperOffsetShortestEdgeFactor = 0.005; + enum class ApplySafetyOffset { No, Yes @@ -599,6 +602,6 @@ Polygons variable_offset_outer(const ExPolygon &expoly, const std::vector> &deltas, double miter_limit = 2.); ExPolygons variable_offset_inner_ex(const ExPolygon &expoly, const std::vector> &deltas, double miter_limit = 2.); -} +} // namespace Slic3r -#endif +#endif // slic3r_ClipperUtils_hpp_ diff --git a/src/libslic3r/ClipperZUtils.hpp b/src/libslic3r/ClipperZUtils.hpp new file mode 100644 index 000000000..d69b2e28a --- /dev/null +++ b/src/libslic3r/ClipperZUtils.hpp @@ -0,0 +1,143 @@ +#ifndef slic3r_ClipperZUtils_hpp_ +#define slic3r_ClipperZUtils_hpp_ + +#include + +#include +#include + +namespace Slic3r { + +namespace ClipperZUtils { + +using ZPoint = ClipperLib_Z::IntPoint; +using ZPoints = ClipperLib_Z::Path; +using ZPath = ClipperLib_Z::Path; +using ZPaths = ClipperLib_Z::Paths; + +inline bool zpoint_lower(const ZPoint &l, const ZPoint &r) +{ + return l.x() < r.x() || (l.x() == r.x() && (l.y() < r.y() || (l.y() == r.y() && l.z() < r.z()))); +} + +// Convert a single path to path with a given Z coordinate. +// If Open, then duplicate the first point at the end. +template +inline ZPath to_zpath(const Points &path, coord_t z) +{ + ZPath out; + if (! path.empty()) { + out.reserve(path.size() + Open ? 1 : 0); + for (const Point &p : path) + out.emplace_back(p.x(), p.y(), z); + if (Open) + out.emplace_back(out.front()); + } + return out; +} + +// Convert multiple paths to paths with a given Z coordinate. +// If Open, then duplicate the first point of each path at its end. +template +inline ZPaths to_zpaths(const std::vector &paths, coord_t z) +{ + ZPaths out; + out.reserve(paths.size()); + for (const Points &path : paths) + out.emplace_back(to_zpath(path, z)); + return out; +} + +// Convert multiple expolygons into z-paths with Z specified by an index of the source expolygon +// offsetted by base_index. +// If Open, then duplicate the first point of each path at its end. +template +inline ZPaths expolygons_to_zpaths(const ExPolygons &src, coord_t base_idx) +{ + ZPaths out; + out.reserve(std::accumulate(src.begin(), src.end(), size_t(0), + [](const size_t acc, const ExPolygon &expoly) { return acc + expoly.num_contours(); })); + coord_t z = base_idx; + for (const ExPolygon &expoly : src) { + out.emplace_back(to_zpath(expoly.contour.points, z)); + for (const Polygon &hole : expoly.holes) + out.emplace_back(to_zpath(hole.points, z)); + ++ z; + } + return out; +} + +// Convert a single path to path with a given Z coordinate. +// If Open, then duplicate the first point at the end. +template +inline Points from_zpath(const ZPoints &path) +{ + Points out; + if (! path.empty()) { + out.reserve(path.size() + Open ? 1 : 0); + for (const ZPoint &p : path) + out.emplace_back(p.x(), p.y()); + if (Open) + out.emplace_back(out.front()); + } + return out; +} + +// Convert multiple paths to paths with a given Z coordinate. +// If Open, then duplicate the first point of each path at its end. +template +inline void from_zpaths(const ZPaths &paths, std::vector &out) +{ + out.reserve(out.size() + paths.size()); + for (const ZPoints &path : paths) + out.emplace_back(from_zpath(path)); +} +template +inline std::vector from_zpaths(const ZPaths &paths) +{ + std::vector out; + from_zpaths(paths, out); + return out; +} + +class ClipperZIntersectionVisitor { +public: + using Intersection = std::pair; + using Intersections = std::vector; + ClipperZIntersectionVisitor(Intersections &intersections) : m_intersections(intersections) {} + void reset() { m_intersections.clear(); } + void operator()(const ZPoint &e1bot, const ZPoint &e1top, const ZPoint &e2bot, const ZPoint &e2top, ZPoint &pt) { + coord_t srcs[4]{ e1bot.z(), e1top.z(), e2bot.z(), e2top.z() }; + coord_t *begin = srcs; + coord_t *end = srcs + 4; + //FIXME bubble sort manually? + std::sort(begin, end); + end = std::unique(begin, end); + if (begin + 1 == end) { + // Self intersection may happen on source contour. Just copy the Z value. + pt.z() = *begin; + } else { + assert(begin + 2 == end); + if (begin + 2 <= end) { + // store a -1 based negative index into the "intersections" vector here. + m_intersections.emplace_back(srcs[0], srcs[1]); + pt.z() = -coord_t(m_intersections.size()); + } + } + } + ClipperLib_Z::ZFillCallback clipper_callback() { + return [this](const ZPoint &e1bot, const ZPoint &e1top, + const ZPoint &e2bot, const ZPoint &e2top, ZPoint &pt) + { return (*this)(e1bot, e1top, e2bot, e2top, pt); }; + } + + const std::vector>& intersections() const { return m_intersections; } + +private: + std::vector> &m_intersections; +}; + +} // namespace ClipperZUtils +} // namespace Slic3r + +#endif // slic3r_ClipperZUtils_hpp_ diff --git a/src/libslic3r/Layer.cpp b/src/libslic3r/Layer.cpp index 35b4a331a..7b44b8b18 100644 --- a/src/libslic3r/Layer.cpp +++ b/src/libslic3r/Layer.cpp @@ -1,5 +1,5 @@ #include "Layer.hpp" -#include +#include "ClipperZUtils.hpp" #include "ClipperUtils.hpp" #include "Print.hpp" #include "Fill/Fill.hpp" @@ -304,48 +304,16 @@ void Layer::build_up_down_graph(Layer& below, Layer& above) coord_t paths_end = paths_above_offset + coord_t(above.lslices.size()); #endif // NDEBUG - class ZFill { - public: - ZFill() = default; - void reset() { m_intersections.clear(); } - void operator()( - const ClipperLib_Z::IntPoint& e1bot, const ClipperLib_Z::IntPoint& e1top, - const ClipperLib_Z::IntPoint& e2bot, const ClipperLib_Z::IntPoint& e2top, - ClipperLib_Z::IntPoint& pt) { - coord_t srcs[4]{ e1bot.z(), e1top.z(), e2bot.z(), e2top.z() }; - coord_t* begin = srcs; - coord_t* end = srcs + 4; - std::sort(begin, end); - end = std::unique(begin, end); - if (begin + 1 == end) { - // Self intersection may happen on source contour. Just copy the Z value. - pt.z() = *begin; - } else { - assert(begin + 2 == end); - if (begin + 2 <= end) { - // store a -1 based negative index into the "intersections" vector here. - m_intersections.emplace_back(srcs[0], srcs[1]); - pt.z() = -coord_t(m_intersections.size()); - } - } - } - const std::vector>& intersections() const { return m_intersections; } - - private: - std::vector> m_intersections; - } zfill; - ClipperLib_Z::Clipper clipper; ClipperLib_Z::PolyTree result; - clipper.ZFillFunction( - [&zfill](const ClipperLib_Z::IntPoint &e1bot, const ClipperLib_Z::IntPoint &e1top, - const ClipperLib_Z::IntPoint &e2bot, const ClipperLib_Z::IntPoint &e2top, ClipperLib_Z::IntPoint &pt) - { return zfill(e1bot, e1top, e2bot, e2top, pt); }); + ClipperZUtils::ClipperZIntersectionVisitor::Intersections intersections; + ClipperZUtils::ClipperZIntersectionVisitor visitor(intersections); + clipper.ZFillFunction(visitor.clipper_callback()); clipper.AddPaths(paths_below, ClipperLib_Z::ptSubject, true); clipper.AddPaths(paths_above, ClipperLib_Z::ptClip, true); clipper.Execute(ClipperLib_Z::ctIntersection, result, ClipperLib_Z::pftNonZero, ClipperLib_Z::pftNonZero); - connect_layer_slices(below, above, result, zfill.intersections(), paths_below_offset, paths_above_offset + connect_layer_slices(below, above, result, intersections, paths_below_offset, paths_above_offset #ifndef NDEBUG , paths_end #endif // NDEBUG diff --git a/src/libslic3r/PerimeterGenerator.cpp b/src/libslic3r/PerimeterGenerator.cpp index c700c45f8..6ca37d7b4 100644 --- a/src/libslic3r/PerimeterGenerator.cpp +++ b/src/libslic3r/PerimeterGenerator.cpp @@ -433,10 +433,12 @@ static ClipperLib_Z::Paths clip_extrusion(const ClipperLib_Z::Path &subject, con clipper.AddPath(subject, ClipperLib_Z::ptSubject, false); clipper.AddPaths(clip, ClipperLib_Z::ptClip, true); - ClipperLib_Z::PolyTree clipped_polytree; ClipperLib_Z::Paths clipped_paths; - clipper.Execute(clipType, clipped_polytree, ClipperLib_Z::pftNonZero, ClipperLib_Z::pftNonZero); - ClipperLib_Z::PolyTreeToPaths(clipped_polytree, clipped_paths); + { + ClipperLib_Z::PolyTree clipped_polytree; + clipper.Execute(clipType, clipped_polytree, ClipperLib_Z::pftNonZero, ClipperLib_Z::pftNonZero); + ClipperLib_Z::PolyTreeToPaths(std::move(clipped_polytree), clipped_paths); + } // Clipped path could contain vertices from the clip with a Z coordinate equal to zero. // For those vertices, we must assign value based on the subject. diff --git a/src/libslic3r/Point.cpp b/src/libslic3r/Point.cpp index beb496b28..794b27c44 100644 --- a/src/libslic3r/Point.cpp +++ b/src/libslic3r/Point.cpp @@ -84,18 +84,15 @@ Points collect_duplicates(Points pts /* Copy */) return duplicits; } +template BoundingBox get_extents(const Points &pts) { - return BoundingBox(pts); -} - -BoundingBox get_extents(const std::vector &pts) -{ - BoundingBox bbox; - for (const Points &p : pts) - bbox.merge(get_extents(p)); - return bbox; + BoundingBox out; + BoundingBox::construct(out, pts.begin(), pts.end()); + return out; } +template BoundingBox get_extents(const Points &pts); +template BoundingBox get_extents(const Points &pts); BoundingBoxf get_extents(const std::vector &pts) { diff --git a/src/libslic3r/Point.hpp b/src/libslic3r/Point.hpp index 32dcb82d0..389fa313b 100644 --- a/src/libslic3r/Point.hpp +++ b/src/libslic3r/Point.hpp @@ -229,8 +229,24 @@ inline Point lerp(const Point &a, const Point &b, double t) return ((1. - t) * a.cast() + t * b.cast()).cast(); } +// if IncludeBoundary, then a bounding box is defined even for a single point. +// otherwise a bounding box is only defined if it has a positive area. +template BoundingBox get_extents(const Points &pts); -BoundingBox get_extents(const std::vector &pts); +extern template BoundingBox get_extents(const Points& pts); +extern template BoundingBox get_extents(const Points& pts); + +// if IncludeBoundary, then a bounding box is defined even for a single point. +// otherwise a bounding box is only defined if it has a positive area. +template +BoundingBox get_extents(const std::vector &pts) +{ + BoundingBox bbox; + for (const Points &p : pts) + bbox.merge(get_extents(p)); + return bbox; +} + BoundingBoxf get_extents(const std::vector &pts); int nearest_point_index(const Points &points, const Point &pt); diff --git a/src/libslic3r/Polyline.hpp b/src/libslic3r/Polyline.hpp index bee5e51ba..de8f859a5 100644 --- a/src/libslic3r/Polyline.hpp +++ b/src/libslic3r/Polyline.hpp @@ -153,6 +153,25 @@ inline void polylines_append(Polylines &dst, Polylines &&src) } } +// Merge polylines at their respective end points. +// dst_first: the merge point is at dst.begin() or dst.end()? +// src_first: the merge point is at src.begin() or src.end()? +// The orientation of the resulting polyline is unknown, the output polyline may start +// either with src piece or dst piece. +template +inline void polylines_merge(std::vector &dst, bool dst_first, std::vector &&src, bool src_first) +{ + if (dst_first) { + if (src_first) + std::reverse(dst.begin(), dst.end()); + else + std::swap(dst, src); + } else if (! src_first) + std::reverse(src.begin(), src.end()); + // Merge src into dst. + append(dst, std::move(src)); +} + const Point& leftmost_point(const Polylines &polylines); bool remove_degenerate(Polylines &polylines); diff --git a/src/libslic3r/libslic3r.h b/src/libslic3r/libslic3r.h index d5a21cf21..2ec12c529 100644 --- a/src/libslic3r/libslic3r.h +++ b/src/libslic3r/libslic3r.h @@ -124,8 +124,7 @@ inline void append(std::vector& dest, std::vector&& src) dest.insert(dest.end(), std::make_move_iterator(src.begin()), std::make_move_iterator(src.end())); - - // Vojta wants back compatibility + // Release memory of the source contour now. src.clear(); src.shrink_to_fit(); } @@ -161,8 +160,7 @@ inline void append_reversed(std::vector& dest, std::vector&& src) dest.insert(dest.end(), std::make_move_iterator(src.rbegin()), std::make_move_iterator(src.rend())); - - // Vojta wants back compatibility + // Release memory of the source contour now. src.clear(); src.shrink_to_fit(); } diff --git a/tests/fff_print/test_clipper.cpp b/tests/fff_print/test_clipper.cpp index ea923b48e..596876975 100644 --- a/tests/fff_print/test_clipper.cpp +++ b/tests/fff_print/test_clipper.cpp @@ -1,7 +1,7 @@ #include #include "test_data.hpp" -#include "clipper/clipper_z.hpp" +#include "libslic3r/ClipperZUtils.hpp" #include "libslic3r/clipper.hpp" using namespace Slic3r; @@ -132,3 +132,72 @@ SCENARIO("Clipper Z", "[ClipperZ]") REQUIRE(pt.z() == 1); } +SCENARIO("Intersection with multiple polylines", "[ClipperZ]") +{ + // 1000x1000 CCQ square + ClipperLib_Z::Path clip { { 0, 0, 1 }, { 1000, 0, 1 }, { 1000, 1000, 1 }, { 0, 1000, 1 } }; + // Two lines interseting inside the square above, crossing the bottom edge of the square. + ClipperLib_Z::Path line1 { { +100, -100, 2 }, { +900, +900, 2 } }; + ClipperLib_Z::Path line2 { { +100, +900, 3 }, { +900, -100, 3 } }; + + ClipperLib_Z::Clipper clipper; + ClipperZUtils::ClipperZIntersectionVisitor::Intersections intersections; + ClipperZUtils::ClipperZIntersectionVisitor visitor(intersections); + clipper.ZFillFunction(visitor.clipper_callback()); + clipper.AddPath(line1, ClipperLib_Z::ptSubject, false); + clipper.AddPath(line2, ClipperLib_Z::ptSubject, false); + clipper.AddPath(clip, ClipperLib_Z::ptClip, true); + + ClipperLib_Z::PolyTree polytree; + ClipperLib_Z::Paths paths; + clipper.Execute(ClipperLib_Z::ctIntersection, polytree, ClipperLib_Z::pftNonZero, ClipperLib_Z::pftNonZero); + ClipperLib_Z::PolyTreeToPaths(polytree, paths); + + REQUIRE(paths.size() == 2); + + THEN("First output polyline is a trimmed 2nd line") { + // Intermediate point (intersection) was removed) + REQUIRE(paths.front().size() == 2); + REQUIRE(paths.front().front().z() == 3); + REQUIRE(paths.front().back().z() < 0); + REQUIRE(intersections[- paths.front().back().z() - 1] == std::pair(1, 3)); + } + + THEN("Second output polyline is a trimmed 1st line") { + // Intermediate point (intersection) was removed) + REQUIRE(paths[1].size() == 2); + REQUIRE(paths[1].front().z() < 0); + REQUIRE(paths[1].back().z() == 2); + REQUIRE(intersections[- paths[1].front().z() - 1] == std::pair(1, 2)); + } +} + +SCENARIO("Interseting a closed loop as an open polyline", "[ClipperZ]") +{ + // 1000x1000 CCQ square + ClipperLib_Z::Path clip{ { 0, 0, 1 }, { 1000, 0, 1 }, { 1000, 1000, 1 }, { 0, 1000, 1 } }; + // Two lines interseting inside the square above, crossing the bottom edge of the square. + ClipperLib_Z::Path rect{ { 500, 500, 2}, { 500, 1500, 2 }, { 1500, 1500, 2}, { 500, 1500, 2}, { 500, 500, 2 } }; + + ClipperLib_Z::Clipper clipper; + clipper.AddPath(rect, ClipperLib_Z::ptSubject, false); + clipper.AddPath(clip, ClipperLib_Z::ptClip, true); + + ClipperLib_Z::PolyTree polytree; + ClipperLib_Z::Paths paths; + ClipperZUtils::ClipperZIntersectionVisitor::Intersections intersections; + ClipperZUtils::ClipperZIntersectionVisitor visitor(intersections); + clipper.ZFillFunction(visitor.clipper_callback()); + clipper.Execute(ClipperLib_Z::ctIntersection, polytree, ClipperLib_Z::pftNonZero, ClipperLib_Z::pftNonZero); + ClipperLib_Z::PolyTreeToPaths(std::move(polytree), paths); + + THEN("Open polyline is clipped into two pieces") { + REQUIRE(paths.size() == 2); + REQUIRE(paths.front().size() == 2); + REQUIRE(paths.back().size() == 2); + REQUIRE(paths.front().front().z() == 2); + REQUIRE(paths.back().back().z() == 2); + REQUIRE(paths.front().front().x() == paths.back().back().x()); + REQUIRE(paths.front().front().y() == paths.back().back().y()); + } +} diff --git a/tests/libslic3r/CMakeLists.txt b/tests/libslic3r/CMakeLists.txt index ae2474ad5..971db528c 100644 --- a/tests/libslic3r/CMakeLists.txt +++ b/tests/libslic3r/CMakeLists.txt @@ -23,6 +23,7 @@ add_executable(${_TEST_NAME}_tests test_stl.cpp test_meshboolean.cpp test_marchingsquares.cpp + test_region_expansion.cpp test_timeutils.cpp test_utils.cpp test_voronoi.cpp diff --git a/tests/libslic3r/test_region_expansion.cpp b/tests/libslic3r/test_region_expansion.cpp new file mode 100644 index 000000000..5d83b8e9d --- /dev/null +++ b/tests/libslic3r/test_region_expansion.cpp @@ -0,0 +1,254 @@ +#include + +#include +#include +#include +#include +#include +#include + +using namespace Slic3r; + +//#define DEBUG_TEMP_DIR "d:\\temp\\" + +SCENARIO("Region expansion basics", "[RegionExpansion]") { + static constexpr const coord_t ten = scaled(10.); + GIVEN("two touching squares") { + Polygon square1{ { 1 * ten, 1 * ten }, { 2 * ten, 1 * ten }, { 2 * ten, 2 * ten }, { 1 * ten, 2 * ten } }; + Polygon square2{ { 2 * ten, 1 * ten }, { 3 * ten, 1 * ten }, { 3 * ten, 2 * ten }, { 2 * ten, 2 * ten } }; + Polygon square3{ { 1 * ten, 2 * ten }, { 2 * ten, 2 * ten }, { 2 * ten, 3 * ten }, { 1 * ten, 3 * ten } }; + static constexpr const float expansion = scaled(1.); + auto test_expansion = [](const Polygon &src, const Polygon &boundary) { + std::vector expanded = Algorithm::expand_expolygons({ ExPolygon{src} }, { ExPolygon{boundary} }, + expansion, + scaled(0.3), // expansion step + 5); // max num steps + THEN("Single anchor is produced") { + REQUIRE(expanded.size() == 1); + } + THEN("The area of the anchor is 10mm2") { + REQUIRE(area(expanded.front()) == Approx(expansion * ten)); + } + }; + + WHEN("second square expanded into the first square (to left)") { + test_expansion(square2, square1); + } + WHEN("first square expanded into the second square (to right)") { + test_expansion(square1, square2); + } + WHEN("third square expanded into the first square (down)") { + test_expansion(square3, square1); + } + WHEN("first square expanded into the third square (up)") { + test_expansion(square1, square3); + } + } + + GIVEN("simple bridge") { + Polygon square1{ { 1 * ten, 1 * ten }, { 2 * ten, 1 * ten }, { 2 * ten, 2 * ten }, { 1 * ten, 2 * ten } }; + Polygon square2{ { 2 * ten, 1 * ten }, { 3 * ten, 1 * ten }, { 3 * ten, 2 * ten }, { 2 * ten, 2 * ten } }; + Polygon square3{ { 3 * ten, 1 * ten }, { 4 * ten, 1 * ten }, { 4 * ten, 2 * ten }, { 3 * ten, 2 * ten } }; + + WHEN("expanded") { + static constexpr const float expansion = scaled(1.); + std::vector expanded = Algorithm::expand_expolygons({ ExPolygon{square2} }, { ExPolygon{square1}, ExPolygon{square3} }, + expansion, + scaled(0.3), // expansion step + 5); // max num steps + THEN("Two anchors are produced") { + REQUIRE(expanded.size() == 1); + REQUIRE(expanded.front().size() == 2); + } + THEN("The area of each anchor is 10mm2") { + REQUIRE(area(expanded.front().front()) == Approx(expansion * ten)); + REQUIRE(area(expanded.front().back()) == Approx(expansion * ten)); + } + } + + WHEN("fully expanded") { + static constexpr const float expansion = scaled(10.1); + std::vector expanded = Algorithm::expand_expolygons({ ExPolygon{square2} }, { ExPolygon{square1}, ExPolygon{square3} }, + expansion, + scaled(2.3), // expansion step + 5); // max num steps + THEN("Two anchors are produced") { + REQUIRE(expanded.size() == 1); + REQUIRE(expanded.front().size() == 2); + } + THEN("The area of each anchor is 100mm2") { + REQUIRE(area(expanded.front().front()) == Approx(sqr(ten))); + REQUIRE(area(expanded.front().back()) == Approx(sqr(ten))); + } + } + } + + GIVEN("two bridges") { + Polygon left_support { { 1 * ten, 1 * ten }, { 2 * ten, 1 * ten }, { 2 * ten, 4 * ten }, { 1 * ten, 4 * ten } }; + Polygon right_support { { 3 * ten, 1 * ten }, { 4 * ten, 1 * ten }, { 4 * ten, 4 * ten }, { 3 * ten, 4 * ten } }; + Polygon bottom_bridge { { 2 * ten, 1 * ten }, { 3 * ten, 1 * ten }, { 3 * ten, 2 * ten }, { 2 * ten, 2 * ten } }; + Polygon top_bridge { { 2 * ten, 3 * ten }, { 3 * ten, 3 * ten }, { 3 * ten, 4 * ten }, { 2 * ten, 4 * ten } }; + + WHEN("expanded") { + static constexpr const float expansion = scaled(1.); + std::vector expanded = Algorithm::expand_expolygons({ ExPolygon{bottom_bridge}, ExPolygon{top_bridge} }, { ExPolygon{left_support}, ExPolygon{right_support} }, + expansion, + scaled(0.3), // expansion step + 5); // max num steps +#if 0 + SVG::export_expolygons(DEBUG_TEMP_DIR "two_bridges-out.svg", + { { { { ExPolygon{left_support}, ExPolygon{right_support} } }, { "supports", "orange", 0.5f } }, + { { { ExPolygon{bottom_bridge}, ExPolygon{top_bridge} } }, { "bridges", "blue", 0.5f } }, + { { union_ex(union_(expanded.front(), expanded.back())) }, { "expanded", "red", "black", "", scaled(0.1f), 0.5f } } }); +#endif + THEN("Two anchors are produced for each bridge") { + REQUIRE(expanded.size() == 2); + REQUIRE(expanded.front().size() == 2); + REQUIRE(expanded.back().size() == 2); + } + THEN("The area of each anchor is 10mm2") { + double a = expansion * ten + M_PI * sqr(expansion) / 4; + double eps = sqr(scaled(0.1)); + REQUIRE(is_approx(area(expanded.front().front()), a, eps)); + REQUIRE(is_approx(area(expanded.front().back()), a, eps)); + REQUIRE(is_approx(area(expanded.back().front()), a, eps)); + REQUIRE(is_approx(area(expanded.back().back()), a, eps)); + } + } + } + + GIVEN("rectangle with rhombic cut-out") { + double diag = 1 * ten * sqrt(2.) / 4.; + Polygon square_with_rhombic_cutout{ { 0, 0 }, { 1 * ten, 0 }, { ten / 2, ten / 2 }, { 1 * ten, 1 * ten }, { 0, 1 * ten } }; + Polygon rhombic { { ten / 2, ten / 2 }, { 3 * ten / 4, ten / 4 }, { 1 * ten, ten / 2 }, { 3 * ten / 4, 3 * ten / 4 } }; + + WHEN("expanded") { + static constexpr const float expansion = scaled(1.); + std::vector expanded = Algorithm::expand_expolygons({ ExPolygon{rhombic} }, { ExPolygon{square_with_rhombic_cutout} }, + expansion, + scaled(0.1), // expansion step + 11); // max num steps +#if 0 + SVG::export_expolygons(DEBUG_TEMP_DIR "rectangle_with_rhombic_cut-out.svg", + { { { { ExPolygon{square_with_rhombic_cutout} } }, { "square_with_rhombic_cutout", "orange", 0.5f } }, + { { { ExPolygon{rhombic} } }, { "rhombic", "blue", 0.5f } }, + { { union_ex(expanded.front()) }, { "bridges", "red", "black", "", scaled(0.1f), 0.5f } } }); +#endif + THEN("Single anchor is produced") { + REQUIRE(expanded.size() == 1); + } + THEN("The area of anchor is correct") { + double area_calculated = area(expanded.front()); + double area_expected = 2. * diag * expansion + M_PI * sqr(expansion) * 0.75; + REQUIRE(is_approx(area_expected, area_calculated, sqr(scaled(0.2)))); + } + } + + WHEN("extra expanded") { + static constexpr const float expansion = scaled(2.5); + std::vector expanded = Algorithm::expand_expolygons({ ExPolygon{rhombic} }, { ExPolygon{square_with_rhombic_cutout} }, + expansion, + scaled(0.25), // expansion step + 11); // max num steps +#if 0 + SVG::export_expolygons(DEBUG_TEMP_DIR "rectangle_with_rhombic_cut-out2.svg", + { { { { ExPolygon{square_with_rhombic_cutout} } }, { "square_with_rhombic_cutout", "orange", 0.5f } }, + { { { ExPolygon{rhombic} } }, { "rhombic", "blue", 0.5f } }, + { { union_ex(expanded.front()) }, { "bridges", "red", "black", "", scaled(0.1f), 0.5f } } }); +#endif + THEN("Single anchor is produced") { + REQUIRE(expanded.size() == 1); + } + THEN("The area of anchor is correct") { + double area_calculated = area(expanded.front()); + double area_expected = 2. * diag * expansion + M_PI * sqr(expansion) * 0.75; + REQUIRE(is_approx(area_expected, area_calculated, sqr(scaled(0.3)))); + } + } + } + + GIVEN("square with two holes") { + Polygon outer{ { 0, 0 }, { 3 * ten, 0 }, { 3 * ten, 5 * ten }, { 0, 5 * ten } }; + Polygon hole1{ { 1 * ten, 1 * ten }, { 1 * ten, 2 * ten }, { 2 * ten, 2 * ten }, { 2 * ten, 1 * ten } }; + Polygon hole2{ { 1 * ten, 3 * ten }, { 1 * ten, 4 * ten }, { 2 * ten, 4 * ten }, { 2 * ten, 3 * ten } }; + ExPolygon boundary(outer); + boundary.holes = { hole1, hole2 }; + + Polygon anchor{ { -1 * ten, coord_t(1.5 * ten) }, { 0 * ten, coord_t(1.5 * ten) }, { 0, coord_t(3.5 * ten) }, { -1 * ten, coord_t(3.5 * ten) } }; + + WHEN("expanded") { + static constexpr const float expansion = scaled(5.); + std::vector expanded = Algorithm::expand_expolygons({ ExPolygon{anchor} }, { boundary }, + expansion, + scaled(0.4), // expansion step + 15); // max num steps +#if 0 + SVG::export_expolygons(DEBUG_TEMP_DIR "square_with_two_holes-out.svg", + { { { { ExPolygon{anchor} } }, { "anchor", "orange", 0.5f } }, + { { { boundary } }, { "boundary", "blue", 0.5f } }, + { { union_ex(expanded.front()) }, { "expanded", "red", "black", "", scaled(0.1f), 0.5f } } }); +#endif + THEN("The anchor expands into a single region") { + REQUIRE(expanded.size() == 1); + REQUIRE(expanded.front().size() == 1); + } + THEN("The area of anchor is correct") { + double area_calculated = area(expanded.front()); + double area_expected = double(expansion) * 2. * double(ten) + M_PI * sqr(expansion) * 0.5; + REQUIRE(is_approx(area_expected, area_calculated, sqr(scaled(0.45)))); + } + } + WHEN("expanded even more") { + static constexpr const float expansion = scaled(25.); + std::vector expanded = Algorithm::expand_expolygons({ ExPolygon{anchor} }, { boundary }, + expansion, + scaled(2.), // expansion step + 15); // max num steps +#if 0 + SVG::export_expolygons(DEBUG_TEMP_DIR "square_with_two_holes-expanded2-out.svg", + { { { { ExPolygon{anchor} } }, { "anchor", "orange", 0.5f } }, + { { { boundary } }, { "boundary", "blue", 0.5f } }, + { { union_ex(expanded.front()) }, { "expanded", "red", "black", "", scaled(0.1f), 0.5f } } }); +#endif + THEN("The anchor expands into a single region") { + REQUIRE(expanded.size() == 1); + REQUIRE(expanded.front().size() == 1); + } + } + WHEN("expanded yet even more") { + static constexpr const float expansion = scaled(28.); + std::vector expanded = Algorithm::expand_expolygons({ ExPolygon{anchor} }, { boundary }, + expansion, + scaled(2.), // expansion step + 20); // max num steps +#if 0 + SVG::export_expolygons(DEBUG_TEMP_DIR "square_with_two_holes-expanded3-out.svg", + { { { { ExPolygon{anchor} } }, { "anchor", "orange", 0.5f } }, + { { { boundary } }, { "boundary", "blue", 0.5f } }, + { { union_ex(expanded.front()) }, { "expanded", "red", "black", "", scaled(0.1f), 0.5f } } }); +#endif + THEN("The anchor expands into a single region with two holes") { + REQUIRE(expanded.size() == 1); + REQUIRE(expanded.front().size() == 3); + } + } + WHEN("expanded fully") { + static constexpr const float expansion = scaled(35.); + std::vector expanded = Algorithm::expand_expolygons({ ExPolygon{anchor} }, { boundary }, + expansion, + scaled(2.), // expansion step + 25); // max num steps +#if 0 + SVG::export_expolygons(DEBUG_TEMP_DIR "square_with_two_holes-expanded_fully-out.svg", + { { { { ExPolygon{anchor} } }, { "anchor", "orange", 0.5f } }, + { { { boundary } }, { "boundary", "blue", 0.5f } }, + { { union_ex(expanded.front()) }, { "expanded", "red", "black", "", scaled(0.1f), 0.5f } } }); +#endif + THEN("The anchor expands into a single region with two holes, fully covering the boundary") { + REQUIRE(expanded.size() == 1); + REQUIRE(expanded.front().size() == 3); + REQUIRE(area(expanded.front()) == Approx(area(boundary))); + } + } + } +} From fb85baf889396aaff4b4a01102b3546f275ce726 Mon Sep 17 00:00:00 2001 From: Vojtech Bubnik Date: Fri, 23 Dec 2022 16:07:09 +0100 Subject: [PATCH 03/48] Ported shells.t unit tests from Perl. --- t/shells.t | 268 ---------------------------- tests/fff_print/test_shells.cpp | 297 +++++++++++++++++++++++++++++++- 2 files changed, 293 insertions(+), 272 deletions(-) delete mode 100644 t/shells.t diff --git a/t/shells.t b/t/shells.t deleted file mode 100644 index cf6a7dce9..000000000 --- a/t/shells.t +++ /dev/null @@ -1,268 +0,0 @@ -use Test::More tests => 17; -use strict; -use warnings; - -BEGIN { - use FindBin; - use lib "$FindBin::Bin/../lib"; - use local::lib "$FindBin::Bin/../local-lib"; -} - -use List::Util qw(first sum); -use Slic3r; -use Slic3r::Geometry qw(epsilon); -use Slic3r::Test; - -# issue #1161 -{ - my $config = Slic3r::Config::new_from_defaults; - $config->set('layer_height', 0.3); - $config->set('first_layer_height', $config->layer_height); - $config->set('bottom_solid_layers', 0); - $config->set('top_solid_layers', 3); - $config->set('cooling', [ 0 ]); - $config->set('bridge_speed', 99); - $config->set('solid_infill_speed', 99); - $config->set('top_solid_infill_speed', 99); - $config->set('first_layer_speed', '100%'); - $config->set('enable_dynamic_overhang_speeds', 0); # prevent speed alteration - - my $print = Slic3r::Test::init_print('V', config => $config); - my %layers_with_solid_infill = (); # Z => 1 - Slic3r::GCode::Reader->new->parse(Slic3r::Test::gcode($print), sub { - my ($self, $cmd, $args, $info) = @_; - - $layers_with_solid_infill{$self->Z} = 1 - if $info->{extruding} && ($args->{F} // $self->F) == $config->solid_infill_speed*60; - }); - is scalar(map $layers_with_solid_infill{$_}, grep $_ <= 7.2, keys %layers_with_solid_infill), 3, - "correct number of top solid shells is generated in V-shaped object"; -} - -{ - my $config = Slic3r::Config::new_from_defaults; - # we need to check against one perimeter because this test is calibrated - # (shape, extrusion_width) so that perimeters cover the bottom surfaces of - # their lower layer - the test checks that shells are not generated on the - # above layers (thus 'across' the shadow perimeter) - # the test is actually calibrated to leave a narrow bottom region for each - # layer - we test that in case of fill_density = 0 such narrow shells are - # discarded instead of grown - $config->set('perimeters', 1); - $config->set('fill_density', 0); - $config->set('cooling', [ 0 ]); # prevent speed alteration - $config->set('first_layer_speed', '100%'); # prevent speed alteration - $config->set('enable_dynamic_overhang_speeds', 0); # prevent speed alteration - $config->set('layer_height', 0.4); - $config->set('first_layer_height', $config->layer_height); - $config->set('extrusion_width', 0.55); - $config->set('bottom_solid_layers', 3); - $config->set('top_solid_layers', 0); - $config->set('solid_infill_speed', 99); - - my $print = Slic3r::Test::init_print('V', config => $config); - my %layers = (); # Z => 1 - Slic3r::GCode::Reader->new->parse(Slic3r::Test::gcode($print), sub { - my ($self, $cmd, $args, $info) = @_; - $layers{$self->Z} = 1 - if $info->{extruding} && ($args->{F} // $self->F) == $config->solid_infill_speed*60; - }); - is scalar(keys %layers), $config->bottom_solid_layers, - "shells are not propagated across perimeters of the neighbor layer"; -} - -{ - my $config = Slic3r::Config::new_from_defaults; - $config->set('perimeters', 3); - $config->set('cooling', [ 0 ]); # prevent speed alteration - $config->set('first_layer_speed', '100%'); # prevent speed alteration - $config->set('enable_dynamic_overhang_speeds', 0); # prevent speed alteration - $config->set('layer_height', 0.4); - $config->set('first_layer_height', $config->layer_height); - $config->set('bottom_solid_layers', 3); - $config->set('top_solid_layers', 3); - $config->set('solid_infill_speed', 99); - $config->set('top_solid_infill_speed', 99); - $config->set('bridge_speed', 99); - $config->set('filament_diameter', [ 3.0 ]); - $config->set('nozzle_diameter', [ 0.5 ]); - - my $print = Slic3r::Test::init_print('sloping_hole', config => $config); - my %solid_layers = (); # Z => 1 - Slic3r::GCode::Reader->new->parse(Slic3r::Test::gcode($print), sub { - my ($self, $cmd, $args, $info) = @_; - $solid_layers{$self->Z} = 1 - if $info->{extruding} && ($args->{F} // $self->F) == $config->solid_infill_speed*60; - }); - is scalar(keys %solid_layers), $config->bottom_solid_layers + $config->top_solid_layers, - "no superfluous shells are generated"; -} - -{ - my $config = Slic3r::Config::new_from_defaults; - $config->set('perimeters', 1); - $config->set('fill_density', 0); - $config->set('top_solid_layers', 0); - $config->set('spiral_vase', 1); - $config->set('bottom_solid_layers', 0); - $config->set('skirts', 0); - $config->set('first_layer_height', $config->layer_height); - $config->set('start_gcode', ''); - $config->set('temperature', [200]); - $config->set('first_layer_temperature', [205]); - - # TODO: this needs to be tested with a model with sloping edges, where starting - # points of each layer are not aligned - in that case we would test that no - # travel moves are left to move to the new starting point - in a cube, end - # points coincide with next layer starting points (provided there's no clipping) - my $test = sub { - my ($model_name, $description) = @_; - my $print = Slic3r::Test::init_print($model_name, config => $config); - my $travel_moves_after_first_extrusion = 0; - my $started_extruding = 0; - my $first_layer_temperature_set = 0; - my $temperature_set = 0; - my @z_steps = (); - Slic3r::GCode::Reader->new->parse(Slic3r::Test::gcode($print), sub { - my ($self, $cmd, $args, $info) = @_; - - if ($cmd eq 'G1') { - $started_extruding = 1 if $info->{extruding}; - push @z_steps, $info->{dist_Z} - if $started_extruding && $info->{dist_Z} > 0; - $travel_moves_after_first_extrusion++ - if $info->{travel} && $info->{dist_XY} > 0 && $started_extruding && !exists $args->{Z}; - } elsif ($cmd eq 'M104') { - $first_layer_temperature_set = 1 if $args->{S} == 205; - $temperature_set = 1 if $args->{S} == 200; - } - }); - - ok $first_layer_temperature_set, 'first layer temperature is preserved'; - ok $temperature_set, 'temperature is preserved'; - - # we allow one travel move after first extrusion: i.e. when moving to the first - # spiral point after moving to second layer (bottom layer had loop clipping, so - # we're slightly distant from the starting point of the loop) - ok $travel_moves_after_first_extrusion <= 1, "no gaps in spiral vase ($description)"; - ok !(grep { $_ > $config->layer_height + epsilon } @z_steps), "no gaps in Z ($description)"; - }; - - $test->('20mm_cube', 'solid model'); - - $config->set('z_offset', -10); - $test->('20mm_cube', 'solid model with negative z-offset'); - - ### Disabled because the current unreliable medial axis code doesn't - ### always produce valid loops. - ###$test->('40x10', 'hollow model with negative z-offset'); -} - -{ - my $config = Slic3r::Config::new_from_defaults; - $config->set('spiral_vase', 1); - $config->set('perimeters', 1); - $config->set('fill_density', 0); - $config->set('top_solid_layers', 0); - $config->set('bottom_solid_layers', 0); - $config->set('retract_layer_change', [0]); - $config->set('skirts', 0); - $config->set('layer_height', 0.4); - $config->set('first_layer_height', $config->layer_height); - $config->set('start_gcode', ''); -# $config->set('use_relative_e_distances', 1); - $config->validate; - - my $print = Slic3r::Test::init_print('20mm_cube', config => $config); - my $z_moves = 0; - my @this_layer = (); # [ dist_Z, dist_XY ], ... - - my $bottom_layer_not_flat = 0; - my $null_z_moves_not_layer_changes = 0; - my $null_z_moves_not_multiples_of_layer_height = 0; - my $sum_of_partial_z_equals_to_layer_height = 0; - my $all_layer_segments_have_same_slope = 0; - my $horizontal_extrusions = 0; - - Slic3r::GCode::Reader->new->parse(Slic3r::Test::gcode($print), sub { - my ($self, $cmd, $args, $info) = @_; - - if ($cmd eq 'G1') { - if ($z_moves < 2) { - # skip everything up to the second Z move - # (i.e. start of second layer) - if (exists $args->{Z}) { - $z_moves++; - $bottom_layer_not_flat = 1 - if $info->{dist_Z} > 0 && $info->{dist_Z} != $config->layer_height; - } - } elsif ($info->{dist_Z} == 0 && $args->{Z}) { - $null_z_moves_not_layer_changes = 1 - if $info->{dist_XY} != 0; - - # % doesn't work easily with floats - $null_z_moves_not_multiples_of_layer_height = 1 - if abs(($args->{Z} / $config->layer_height) * $config->layer_height - $args->{Z}) > epsilon; - - my $total_dist_XY = sum(map $_->[1], @this_layer); - $sum_of_partial_z_equals_to_layer_height = 1 - if abs(sum(map $_->[0], @this_layer) - $config->layer_height) > - # The first segment on the 2nd layer has extrusion interpolated from zero - # and the 1st segment has such a low extrusion assigned, that it is effectively zero, thus the move - # is considered non-extruding and a higher epsilon is required. - ($z_moves == 2 ? 0.0021 : epsilon); - #printf ("Total height: %f, layer height: %f, good: %d\n", sum(map $_->[0], @this_layer), $config->layer_height, $sum_of_partial_z_equals_to_layer_height); - - foreach my $segment (@this_layer) { - # check that segment's dist_Z is proportioned to its dist_XY - $all_layer_segments_have_same_slope = 1 - if abs($segment->[0]*$total_dist_XY/$config->layer_height - $segment->[1]) > 0.2; - } - - @this_layer = (); - } elsif ($info->{extruding} && $info->{dist_XY} > 0) { - $horizontal_extrusions = 1 - if $info->{dist_Z} == 0; - #printf("Pushing dist_z: %f, dist_xy: %f\n", $info->{dist_Z}, $info->{dist_XY}); - push @this_layer, [ $info->{dist_Z}, $info->{dist_XY} ]; - } - } - }); - ok !$bottom_layer_not_flat, 'bottom layer is flat when using spiral vase'; - ok !$null_z_moves_not_layer_changes, 'null Z moves are layer changes'; - ok !$null_z_moves_not_multiples_of_layer_height, 'null Z moves are multiples of layer height'; - ok !$sum_of_partial_z_equals_to_layer_height, 'sum of partial Z increments equals to a full layer height'; - ok !$all_layer_segments_have_same_slope, 'all layer segments have the same slope'; - ok !$horizontal_extrusions, 'no horizontal extrusions'; -} - -# The current Spiral Vase slicing code removes the holes and all but the largest contours from each slice, -# therefore the following test is no more valid. -#{ -# my $config = Slic3r::Config::new_from_defaults; -# $config->set('perimeters', 1); -# $config->set('fill_density', 0); -# $config->set('top_solid_layers', 0); -# $config->set('spiral_vase', 1); -# $config->set('bottom_solid_layers', 0); -# $config->set('skirts', 0); -# $config->set('first_layer_height', $config->layer_height); -# $config->set('start_gcode', ''); -# -# my $print = Slic3r::Test::init_print('two_hollow_squares', config => $config); -# my $diagonal_moves = 0; -# Slic3r::GCode::Reader->new->parse(Slic3r::Test::gcode($print), sub { -# my ($self, $cmd, $args, $info) = @_; -# -# if ($cmd eq 'G1') { -# if ($info->{extruding} && $info->{dist_XY} > 0) { -# if ($info->{dist_Z} > 0) { -# $diagonal_moves++; -# } -# } -# } -# }); -# is $diagonal_moves, 0, 'no spiral moves on two-island object'; -#} - -__END__ diff --git a/tests/fff_print/test_shells.cpp b/tests/fff_print/test_shells.cpp index df0e8f6bb..d9e5817ca 100644 --- a/tests/fff_print/test_shells.cpp +++ b/tests/fff_print/test_shells.cpp @@ -9,16 +9,14 @@ using namespace Slic3r; SCENARIO("Shells", "[Shells]") { GIVEN("20mm box") { - auto test = [](const DynamicPrintConfig &config){ - std::string gcode = Slic3r::Test::slice({ Slic3r::Test::TestMesh::cube_20x20x20 }, config); - + auto test = [](const DynamicPrintConfig &config){ std::vector zs; std::set layers_with_solid_infill; std::set layers_with_bridge_infill; const double solid_infill_speed = config.opt_float("solid_infill_speed") * 60; const double bridge_speed = config.opt_float("bridge_speed") * 60; GCodeReader parser; - parser.parse_buffer(gcode, + parser.parse_buffer(Slic3r::Test::slice({ Slic3r::Test::TestMesh::cube_20x20x20 }, config), [&zs, &layers_with_solid_infill, &layers_with_bridge_infill, solid_infill_speed, bridge_speed] (Slic3r::GCodeReader &self, const Slic3r::GCodeReader::GCodeLine &line) { @@ -110,3 +108,294 @@ SCENARIO("Shells", "[Shells]") { } } } + +static std::set layers_with_speed(const std::string &gcode, int speed) +{ + std::set out; + GCodeReader parser; + parser.parse_buffer(gcode, [&out, speed](Slic3r::GCodeReader &self, const Slic3r::GCodeReader::GCodeLine &line) { + if (line.extruding(self) && is_approx(line.new_F(self), speed * 60.)) + out.insert(self.z()); + }); + return out; +} + +SCENARIO("Shells (from Perl)", "[Shells]") { + GIVEN("V shape, Slic3r GH #1161") { + int solid_speed = 99; + auto config = Slic3r::DynamicPrintConfig::full_print_config_with({ + { "layer_height", 0.3 }, + { "first_layer_height", 0.3 }, + { "bottom_solid_layers", 0 }, + { "top_solid_layers", 3 }, + // to prevent speeds from being altered + { "cooling", "0" }, + { "bridge_speed", solid_speed }, + { "solid_infill_speed", solid_speed }, + { "top_solid_infill_speed", solid_speed }, + // to prevent speeds from being altered + { "first_layer_speed", "100%" }, + // prevent speed alteration + { "enable_dynamic_overhang_speeds", 0 } + }); + + THEN("correct number of top solid shells is generated in V-shaped object") { + size_t n = 0; + for (auto z : layers_with_speed(Slic3r::Test::slice({TestMesh::V}, config), solid_speed)) + if (z <= 7.2) + ++ n; + REQUIRE(n == 3); + } + } + GIVEN("V shape") { + // we need to check against one perimeter because this test is calibrated + // (shape, extrusion_width) so that perimeters cover the bottom surfaces of + // their lower layer - the test checks that shells are not generated on the + // above layers (thus 'across' the shadow perimeter) + // the test is actually calibrated to leave a narrow bottom region for each + // layer - we test that in case of fill_density = 0 such narrow shells are + // discarded instead of grown + int bottom_solid_layers = 3; + auto config = Slic3r::DynamicPrintConfig::full_print_config_with({ + { "perimeters", 1 }, + { "fill_density", 0 }, + // to prevent speeds from being altered + { "cooling", "0" }, + // to prevent speeds from being altered + { "first_layer_speed", "100%" }, + // prevent speed alteration + { "enable_dynamic_overhang_speeds", 0 }, + { "layer_height", 0.4 }, + { "first_layer_height", 0.4 }, + { "extrusion_width", 0.55 }, + { "bottom_solid_layers", bottom_solid_layers }, + { "top_solid_layers", 0 }, + { "solid_infill_speed", 99 } + }); + THEN("shells are not propagated across perimeters of the neighbor layer") { + std::string gcode = Slic3r::Test::slice({TestMesh::V}, config); + REQUIRE(layers_with_speed(gcode, 99).size() == bottom_solid_layers); + } + } + GIVEN("sloping_hole") { + int bottom_solid_layers = 3; + int top_solid_layers = 3; + int solid_speed = 99; + auto config = Slic3r::DynamicPrintConfig::full_print_config_with({ + { "perimeters", 3 }, + // to prevent speeds from being altered + { "cooling", "0" }, + // to prevent speeds from being altered + { "first_layer_speed", "100%" }, + // prevent speed alteration + { "enable_dynamic_overhang_speeds", 0 }, + { "layer_height", 0.4 }, + { "first_layer_height", 0.4 }, + { "bottom_solid_layers", bottom_solid_layers }, + { "top_solid_layers", top_solid_layers }, + { "solid_infill_speed", solid_speed }, + { "top_solid_infill_speed", solid_speed }, + { "bridge_speed", solid_speed }, + { "filament_diameter", 3. }, + { "nozzle_diameter", 0.5 } + }); + THEN("no superfluous shells are generated") { + std::string gcode = Slic3r::Test::slice({TestMesh::sloping_hole}, config); + REQUIRE(layers_with_speed(gcode, solid_speed).size() == bottom_solid_layers + top_solid_layers); + } + } + GIVEN("20mm_cube, spiral vase") { + double layer_height = 0.3; + auto config = Slic3r::DynamicPrintConfig::full_print_config_with({ + { "perimeters", 1 }, + { "fill_density", 0 }, + { "layer_height", layer_height }, + { "first_layer_height", layer_height }, + { "top_solid_layers", 0 }, + { "spiral_vase", 1 }, + { "bottom_solid_layers", 0 }, + { "skirts", 0 }, + { "start_gcode", "" }, + { "temperature", 200 }, + { "first_layer_temperature", 205} + }); + + // TODO: this needs to be tested with a model with sloping edges, where starting + // points of each layer are not aligned - in that case we would test that no + // travel moves are left to move to the new starting point - in a cube, end + // points coincide with next layer starting points (provided there's no clipping) + auto test = [layer_height](const DynamicPrintConfig &config) { + size_t travel_moves_after_first_extrusion = 0; + bool started_extruding = false; + bool first_layer_temperature_set = false; + bool temperature_set = false; + std::vector z_steps; + GCodeReader parser; + parser.parse_buffer(Slic3r::Test::slice({TestMesh::cube_20x20x20}, config), + [&](Slic3r::GCodeReader &self, const Slic3r::GCodeReader::GCodeLine &line) { + if (line.cmd_is("G1")) { + if (line.extruding(self)) + started_extruding = true; + if (started_extruding) { + if (double dz = line.dist_Z(self); dz > 0) + z_steps.emplace_back(dz); + if (line.travel() && line.dist_XY(self) > 0 && ! line.has(Z)) + ++ travel_moves_after_first_extrusion; + } + } else if (line.cmd_is("M104")) { + int s; + if (line.has_value('S', s)) { + if (s == 205) + first_layer_temperature_set = true; + else if (s == 200) + temperature_set = true; + } + } + }); + THEN("first layer temperature is set") { + REQUIRE(first_layer_temperature_set); + } + THEN("temperature is set") { + REQUIRE(temperature_set); + } + // we allow one travel move after first extrusion: i.e. when moving to the first + // spiral point after moving to second layer (bottom layer had loop clipping, so + // we're slightly distant from the starting point of the loop) + THEN("no gaps in spiral vase") { + REQUIRE(travel_moves_after_first_extrusion <= 1); + } + THEN("no gaps in Z") { + REQUIRE(std::count_if(z_steps.begin(), z_steps.end(), + [&layer_height](auto z_step) { return z_step > layer_height + EPSILON; }) == 0); + } + }; + WHEN("solid model") { + test(config); + } + WHEN("solid model with negative z-offset") { + config.set_deserialize_strict("z_offset", "-10"); + test(config); + } + // Disabled because the current unreliable medial axis code doesn't always produce valid loops. + // $test->('40x10', 'hollow model with negative z-offset'); + } + GIVEN("20mm_cube, spiral vase") { + double layer_height = 0.4; + auto config = Slic3r::DynamicPrintConfig::full_print_config_with({ + { "spiral_vase", 1 }, + { "perimeters", 1 }, + { "fill_density", 0 }, + { "top_solid_layers", 0 }, + { "bottom_solid_layers", 0 }, + { "retract_layer_change", 0 }, + { "skirts", 0 }, + { "layer_height", layer_height }, + { "first_layer_height", layer_height }, + { "start_gcode", "" }, + // { "use_relative_e_distances", 1} + }); + config.validate(); + + std::vector> this_layer; // [ dist_Z, dist_XY ], ... + int z_moves = 0; + bool bottom_layer_not_flat = false; + bool null_z_moves_not_layer_changes = false; + bool null_z_moves_not_multiples_of_layer_height = false; + bool sum_of_partial_z_equals_to_layer_height = false; + bool all_layer_segments_have_same_slope = false; + bool horizontal_extrusions = false; + GCodeReader parser; + parser.parse_buffer(Slic3r::Test::slice({TestMesh::cube_20x20x20}, config), + [&](Slic3r::GCodeReader &self, const Slic3r::GCodeReader::GCodeLine &line) { + if (line.cmd_is("G1")) { + if (z_moves < 2) { + // skip everything up to the second Z move + // (i.e. start of second layer) + if (line.has(Z)) { + ++ z_moves; + if (double dz = line.dist_Z(self); dz > 0 && ! is_approx(dz, layer_height)) + bottom_layer_not_flat = true; + } + } else if (line.dist_Z(self) == 0 && line.has(Z)) { + if (line.dist_XY(self) != 0) + null_z_moves_not_layer_changes = true; + double z = line.new_Z(self); + if (fmod(z + EPSILON, layer_height) > 2 * EPSILON) + null_z_moves_not_multiples_of_layer_height = true; + double total_dist_XY = 0; + double total_dist_Z = 0; + for (auto &seg : this_layer) { + total_dist_Z += seg.first; + total_dist_XY += seg.second; + } + if (std::abs(total_dist_Z - layer_height) > + // The first segment on the 2nd layer has extrusion interpolated from zero + // and the 1st segment has such a low extrusion assigned, that it is effectively zero, thus the move + // is considered non-extruding and a higher epsilon is required. + (z_moves == 2 ? 0.0021 : EPSILON)) + sum_of_partial_z_equals_to_layer_height = true; + //printf("Total height: %f, layer height: %f, good: %d\n", sum(map $_->[0], @this_layer), $config->layer_height, $sum_of_partial_z_equals_to_layer_height); + for (auto &seg : this_layer) + // check that segment's dist_Z is proportioned to its dist_XY + if (std::abs(seg.first * total_dist_XY / layer_height - seg.second) > 0.2) + all_layer_segments_have_same_slope = true; + this_layer.clear(); + } else if (line.extruding(self) && line.dist_XY(self) > 0) { + if (line.dist_Z(self) == 0) + horizontal_extrusions = true; + //printf("Pushing dist_z: %f, dist_xy: %f\n", $info->{dist_Z}, $info->{dist_XY}); + this_layer.emplace_back(line.dist_Z(self), line.dist_XY(self)); + } + } + }); + THEN("bottom layer is flat when using spiral vase") { + REQUIRE(! bottom_layer_not_flat); + } + THEN("null Z moves are layer changes") { + REQUIRE(! null_z_moves_not_layer_changes); + } + THEN("null Z moves are multiples of layer height") { + REQUIRE(! null_z_moves_not_multiples_of_layer_height); + } + THEN("sum of partial Z increments equals to a full layer height") { + REQUIRE(! sum_of_partial_z_equals_to_layer_height); + } + THEN("all layer segments have the same slope") { + REQUIRE(! all_layer_segments_have_same_slope); + } + THEN("no horizontal extrusions") { + REQUIRE(! horizontal_extrusions); + } + } +} + +#if 0 +// The current Spiral Vase slicing code removes the holes and all but the largest contours from each slice, +// therefore the following test is no more valid. +{ + my $config = Slic3r::Config::new_from_defaults; + $config->set('perimeters', 1); + $config->set('fill_density', 0); + $config->set('top_solid_layers', 0); + $config->set('spiral_vase', 1); + $config->set('bottom_solid_layers', 0); + $config->set('skirts', 0); + $config->set('first_layer_height', $config->layer_height); + $config->set('start_gcode', ''); + + my $print = Slic3r::Test::init_print('two_hollow_squares', config => $config); + my $diagonal_moves = 0; + Slic3r::GCode::Reader->new->parse(Slic3r::Test::gcode($print), sub { + my ($self, $cmd, $args, $info) = @_; + + if ($cmd eq 'G1') { + if ($info->{extruding} && $info->{dist_XY} > 0) { + if ($info->{dist_Z} > 0) { + $diagonal_moves++; + } + } + } + }); + is $diagonal_moves, 0, 'no spiral moves on two-island object'; +} +#endif From fde0d68c40557d148433165fdf407a7e076a5bbd Mon Sep 17 00:00:00 2001 From: Vojtech Bubnik Date: Mon, 2 Jan 2023 13:19:27 +0100 Subject: [PATCH 04/48] WIP Reworking of "ensure vertical wall thickness". 1) Flipped the order of "discover_vertical_shells" and "process_external_surfaces", now the external surfaces are expanded after "discover_vertical_shells" aka "ensure vertical wall thickness" is solved. 2) Reworked LayerRegion::process_external_surfaces() to only expand into "ensure vertical wall thickness" regions, also the expansion is done in small steps to avoid overflowing into neighbor regions. also: Utility functions reserve_more(), reserve_power_of_2(), reserve_more_power_of_2() Various SurfaceCollecion::filter_xxx() modified to accept an initializer list of surface types. New bridges detector refactored to accept overhang boundaries. BoundingBoxWrapper was moved from RetractCrossingPerimeters to AABBTreeIndirect. --- src/libslic3r/AABBTreeIndirect.hpp | 18 + src/libslic3r/Algorithm/RegionExpansion.cpp | 481 ++++++++++++------ src/libslic3r/Algorithm/RegionExpansion.hpp | 100 +++- src/libslic3r/BridgeDetector.hpp | 15 +- .../GCode/RetractWhenCrossingPerimeters.cpp | 15 +- src/libslic3r/LayerRegion.cpp | 311 ++++++++++- src/libslic3r/PrintObject.cpp | 62 ++- src/libslic3r/Surface.hpp | 57 +-- src/libslic3r/SurfaceCollection.cpp | 38 +- src/libslic3r/SurfaceCollection.hpp | 7 +- src/libslic3r/Utils.hpp | 15 + tests/libslic3r/test_region_expansion.cpp | 30 ++ 12 files changed, 841 insertions(+), 308 deletions(-) diff --git a/src/libslic3r/AABBTreeIndirect.hpp b/src/libslic3r/AABBTreeIndirect.hpp index e70e8be39..6d6479508 100644 --- a/src/libslic3r/AABBTreeIndirect.hpp +++ b/src/libslic3r/AABBTreeIndirect.hpp @@ -13,6 +13,7 @@ #include +#include "BoundingBox.hpp" #include "Utils.hpp" // for next_highest_power_of_2() // Definition of the ray intersection hit structure. @@ -217,6 +218,23 @@ using Tree3f = Tree<3, float>; using Tree2d = Tree<2, double>; using Tree3d = Tree<3, double>; +// Wrap a 2D Slic3r own BoundingBox to be passed to Tree::build() and similar +// to build an AABBTree over coord_t 2D bounding boxes. +class BoundingBoxWrapper { +public: + using BoundingBox = Eigen::AlignedBox; + BoundingBoxWrapper(const size_t idx, const Slic3r::BoundingBox &bbox) : + m_idx(idx), + // Inflate the bounding box a bit to account for numerical issues. + m_bbox(bbox.min - Point(SCALED_EPSILON, SCALED_EPSILON), bbox.max + Point(SCALED_EPSILON, SCALED_EPSILON)) {} + size_t idx() const { return m_idx; } + const BoundingBox& bbox() const { return m_bbox; } + Point centroid() const { return ((m_bbox.min().cast() + m_bbox.max().cast()) / 2).cast(); } +private: + size_t m_idx; + BoundingBox m_bbox; +}; + namespace detail { template struct RayIntersector { diff --git a/src/libslic3r/Algorithm/RegionExpansion.cpp b/src/libslic3r/Algorithm/RegionExpansion.cpp index 2a22fe785..4cd4689e6 100644 --- a/src/libslic3r/Algorithm/RegionExpansion.cpp +++ b/src/libslic3r/Algorithm/RegionExpansion.cpp @@ -1,11 +1,76 @@ #include "RegionExpansion.hpp" +#include #include #include +#include namespace Slic3r { namespace Algorithm { +// Calculating radius discretization according to ClipperLib offsetter code, see void ClipperOffset::DoOffset(double delta) +inline double clipper_round_offset_error(double offset, double arc_tolerance) +{ + static constexpr const double def_arc_tolerance = 0.25; + const double y = + arc_tolerance <= 0 ? + def_arc_tolerance : + arc_tolerance > offset * def_arc_tolerance ? + offset * def_arc_tolerance : + arc_tolerance; + double steps = std::min(M_PI / std::acos(1. - y / offset), offset * M_PI); + return offset * (1. - cos(M_PI / steps)); +} + +RegionExpansionParameters RegionExpansionParameters::build( + // Scaled expansion value + float full_expansion, + // Expand by waves of expansion_step size (expansion_step is scaled). + float 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(max_nr_expansion_steps > 0); + + RegionExpansionParameters out; + // Initial expansion of src to make the source regions intersect with boundary regions just a bit. + // The expansion should not be too tiny, but also small enough, so the following expansion will + // compensate for tiny_expansion and bring the wave back to the boundary without producing + // ugly cusps where it touches the boundary. + out.tiny_expansion = std::min(0.25f * full_expansion, scaled(0.05f)); + size_t nsteps = size_t(ceil((full_expansion - out.tiny_expansion) / expansion_step)); + if (max_nr_expansion_steps > 0) + nsteps = std::min(nsteps, max_nr_expansion_steps); + assert(nsteps > 0); + out.initial_step = (full_expansion - out.tiny_expansion) / nsteps; + if (nsteps > 1 && 0.25 * out.initial_step < out.tiny_expansion) { + // Decrease the step size by lowering number of steps. + nsteps = std::max(1, (floor((full_expansion - out.tiny_expansion) / (4. * out.tiny_expansion)))); + out.initial_step = (full_expansion - out.tiny_expansion) / nsteps; + } + if (0.25 * out.initial_step < out.tiny_expansion || nsteps == 1) { + out.tiny_expansion = 0.2f * full_expansion; + out.initial_step = 0.8f * full_expansion; + } + out.other_step = out.initial_step; + out.num_other_steps = nsteps - 1; + + // Accuracy of the offsetter for wave propagation. + out.arc_tolerance = scaled(0.1); + out.shortest_edge_length = out.initial_step * ClipperOffsetShortestEdgeFactor; + + // Maximum inflation of seed contours over the boundary. Used to trim boundary to speed up + // clipping during wave propagation. Needs to be in sync with the offsetter accuracy. + // Clipper positive round offset should rather offset less than more. + // Still a little bit of additional offset was added. + out.max_inflation = (out.tiny_expansion + nsteps * out.initial_step) * 1.1; +// (clipper_round_offset_error(out.tiny_expansion, co.ArcTolerance) + nsteps * clipper_round_offset_error(out.initial_step, co.ArcTolerance) * 1.5; // Account for uncertainty + + return out; +} + // similar to expolygons_to_zpaths(), but each contour is expanded before converted to zpath. // The expanded contours are then opened (the first point is repeated at the end). static ClipperLib_Z::Paths expolygons_to_zpaths_expanded_opened( @@ -48,8 +113,7 @@ static inline void merge_splits(ClipperLib_Z::Paths &paths, std::vector wave_seeds( + // Source regions that are supposed to touch the boundary. + const ExPolygons &src, + // Boundaries of source regions touching the "boundary" regions will be expanded into the "boundary" region. + const ExPolygons &boundary, + // Initial expansion of src to make the source regions intersect with boundary regions just a bit. + float tiny_expansion, + // Sort output by boundary ID and source ID. + bool sorted) +{ + assert(tiny_expansion > 0); + + if (src.empty()) + return {}; + + using Intersection = ClipperZUtils::ClipperZIntersectionVisitor::Intersection; + using Intersections = ClipperZUtils::ClipperZIntersectionVisitor::Intersections; + + ClipperLib_Z::Paths segments; + Intersections intersections; + + coord_t idx_boundary_begin = 1; + coord_t idx_boundary_end; + coord_t idx_src_end; + + { + ClipperLib_Z::Clipper zclipper; + ClipperZUtils::ClipperZIntersectionVisitor visitor(intersections); + zclipper.ZFillFunction(visitor.clipper_callback()); + // as closed contours + { + ClipperLib_Z::Paths zboundary = ClipperZUtils::expolygons_to_zpaths(boundary, idx_boundary_begin); + idx_boundary_end = idx_boundary_begin + coord_t(zboundary.size()); + zclipper.AddPaths(zboundary, ClipperLib_Z::ptClip, true); + } + // as open contours + std::vector> zsrc_splits; + { + ClipperLib_Z::Paths zsrc = expolygons_to_zpaths_expanded_opened(src, tiny_expansion, idx_boundary_end); + zclipper.AddPaths(zsrc, ClipperLib_Z::ptSubject, false); + idx_src_end = idx_boundary_end + coord_t(zsrc.size()); + zsrc_splits.reserve(zsrc.size()); + for (const ClipperLib_Z::Path &path : zsrc) { + assert(path.size() >= 2); + assert(path.front() == path.back()); + zsrc_splits.emplace_back(path.front(), -1); + } + std::sort(zsrc_splits.begin(), zsrc_splits.end(), [](const auto &l, const auto &r){ return ClipperZUtils::zpoint_lower(l.first, r.first); }); + } + ClipperLib_Z::PolyTree polytree; + zclipper.Execute(ClipperLib_Z::ctIntersection, polytree, ClipperLib_Z::pftNonZero, ClipperLib_Z::pftNonZero); + ClipperLib_Z::PolyTreeToPaths(std::move(polytree), segments); + merge_splits(segments, zsrc_splits); + } + + // AABBTree over bounding boxes of boundaries. + // Only built if necessary, that is if any of the seed contours is closed, thus there is no intersection point + // with the boundary and all Z coordinates of the closed contour point to the source contour. + using AABBTree = AABBTreeIndirect::Tree<2, coord_t>; + AABBTree aabb_tree; + auto init_aabb_tree = [&aabb_tree, &boundary]() { + if (aabb_tree.empty()) { + // Calculate bounding boxes of internal slices. + std::vector bboxes; + bboxes.reserve(boundary.size()); + for (size_t i = 0; i < boundary.size(); ++ i) + bboxes.emplace_back(i, get_extents(boundary[i].contour)); + // Build AABB tree over bounding boxes of boundary expolygons. + aabb_tree.build_modify_input(bboxes); + } + }; + + // Sort paths into their respective islands. + // Each src x boundary will be processed (wave expanded) independently. + // Multiple pieces of a single src may intersect the same boundary. + WaveSeeds out; + out.reserve(segments.size()); + int iseed = 0; + for (const ClipperLib_Z::Path &path : segments) { + assert(path.size() >= 2); + const ClipperLib_Z::IntPoint &front = path.front(); + const ClipperLib_Z::IntPoint &back = path.back(); + // Both ends of a seed segment are supposed to be inside a single boundary expolygon. + // Thus as long as the seed contour is not closed, it should be open at a boundary point. + assert((front == back && front.z() >= idx_boundary_end && front.z() < idx_src_end) || (front.z() < 0 && back.z() < 0)); + const Intersection *intersection = nullptr; + auto intersection_point_valid = [idx_boundary_end, idx_src_end](const Intersection &is) { + return is.first >= 1 && is.first < idx_boundary_end && + is.second >= idx_boundary_end && is.second < idx_src_end; + }; + if (front.z() < 0) { + const Intersection &is = intersections[- front.z() - 1]; + assert(intersection_point_valid(is)); + if (intersection_point_valid(is)) + intersection = &is; + } + if (! intersection && back.z() < 0) { + const Intersection &is = intersections[- back.z() - 1]; + assert(intersection_point_valid(is)); + if (intersection_point_valid(is)) + intersection = &is; + } + if (intersection) { + // The path intersects the boundary contour at least at one side. + out.push_back({ uint32_t(intersection->second - idx_boundary_end), uint32_t(intersection->first - 1), ClipperZUtils::from_zpath(path) }); + } else { + // This should be a closed contour. + assert(front == back && front.z() >= idx_boundary_end && front.z() < idx_src_end); + // Find a source boundary expolygon of one sample of this closed path. + init_aabb_tree(); + Point sample(front.x(), front.y()); + int boundary_id = -1; + AABBTreeIndirect::traverse(aabb_tree, + [&sample](const AABBTree::Node &node) { + return node.bbox.contains(sample); + }, + [&boundary, &sample, &boundary_id](const AABBTree::Node &node) { + assert(node.is_leaf()); + assert(node.is_valid()); + if (boundary[node.idx].contains(sample)) { + boundary_id = int(node.idx); + // Stop traversal. + return false; + } + // Continue traversal. + return true; + }); + // Boundary that contains the sample point was found. + assert(boundary_id >= 0); + if (boundary_id >= 0) + out.push_back({ uint32_t(front.z() - idx_boundary_end), uint32_t(boundary_id), ClipperZUtils::from_zpath(path) }); + } + ++ iseed; + } + + if (sorted) + // Sort the seeds by their intersection boundary and source contour. + std::sort(out.begin(), out.end(), lower_by_boundary_and_src); + return out; +} + static ClipperLib::Paths wavefront_initial(ClipperLib::ClipperOffset &co, const ClipperLib::Paths &polylines, float offset) { ClipperLib::Paths out; out.reserve(polylines.size()); ClipperLib::Paths out_this; for (const ClipperLib::Path &path : polylines) { + assert(path.size() >= 2); co.Clear(); - co.AddPath(path, jtRound, ClipperLib::etOpenRound); + co.AddPath(path, jtRound, path.front() == path.back() ? ClipperLib::etClosedLine : ClipperLib::etOpenRound); co.Execute(out_this, offset); append(out, std::move(out_this)); } @@ -164,22 +370,73 @@ static Polygons propagate_wave_from_boundary( return to_polygons(polygons); } -// Calculating radius discretization according to ClipperLib offsetter code, see void ClipperOffset::DoOffset(double delta) -inline double clipper_round_offset_error(double offset, double arc_tolerance) +// Resulting regions are sorted by boundary id and source id. +std::vector propagate_waves(const WaveSeeds &seeds, const ExPolygons &boundary, const RegionExpansionParameters ¶ms) { - static constexpr const double def_arc_tolerance = 0.25; - const double y = - arc_tolerance <= 0 ? - def_arc_tolerance : - arc_tolerance > offset * def_arc_tolerance ? - offset * def_arc_tolerance : - arc_tolerance; - double steps = std::min(M_PI / std::acos(1. - y / offset), offset * M_PI); - return offset * (1. - cos(M_PI / steps)); + std::vector out; + ClipperLib::Paths paths; + ClipperLib::ClipperOffset co; + co.ArcTolerance = params.arc_tolerance; + co.ShortestEdgeLength = params.shortest_edge_length; + for (auto it_seed = seeds.begin(); it_seed != seeds.end();) { + auto it = it_seed; + paths.clear(); + for (; it != seeds.end() && it->boundary == it_seed->boundary && it->src == it_seed->src; ++ it) + paths.emplace_back(it->path); + // Propagate the wavefront while clipping it with the trimmed boundary. + // Collect the expanded polygons, merge them with the source polygons. + RegionExpansion re; + for (Polygon &polygon : propagate_wave_from_boundary(co, paths, boundary[it_seed->boundary], params.initial_step, params.other_step, params.num_other_steps, params.max_inflation)) + out.push_back({ std::move(polygon), it_seed->src, it_seed->boundary }); + it_seed = it; + } + + return out; +} + +std::vector propagate_waves(const ExPolygons &src, const ExPolygons &boundary, const RegionExpansionParameters ¶ms) +{ + return propagate_waves(wave_seeds(src, boundary, params.tiny_expansion, true), boundary, params); +} + +std::vector propagate_waves(const ExPolygons &src, const ExPolygons &boundary, + // Scaled expansion value + float expansion, + // Expand by waves of expansion_step size (expansion_step is scaled). + float expansion_step, + // Don't take more than max_nr_steps for small expansion_step. + size_t max_nr_steps) +{ + return propagate_waves(src, boundary, RegionExpansionParameters::build(expansion, expansion_step, max_nr_steps)); } // Returns regions per source ExPolygon expanded into boundary. -std::vector expand_expolygons( +std::vector propagate_waves_ex(const WaveSeeds &seeds, const ExPolygons &boundary, const RegionExpansionParameters ¶ms) +{ + std::vector expanded = propagate_waves(seeds, boundary, params); + assert(std::is_sorted(seeds.begin(), seeds.end(), [](const auto &l, const auto &r){ return l.boundary < r.boundary || (l.boundary == r.boundary && l.src < r.src); })); + Polygons acc; + std::vector out; + for (auto it = expanded.begin(); it != expanded.end(); ) { + auto it2 = it; + acc.clear(); + for (; it2 != expanded.end() && it2->boundary_id == it->boundary_id && it2->src_id == it->src_id; ++ it2) + acc.emplace_back(std::move(it2->polygon)); + size_t size = it2 - it; + if (size == 1) + out.push_back({ ExPolygon{std::move(acc.front())}, it->src_id, it->boundary_id }); + else { + ExPolygons expolys = union_ex(acc); + reserve_more_power_of_2(out, expolys.size()); + for (ExPolygon &ex : expolys) + out.push_back({ std::move(ex), it->src_id, it->boundary_id }); + } + } + return out; +} + +// Returns regions per source ExPolygon expanded into boundary. +std::vector propagate_waves_ex( // Source regions that are supposed to touch the boundary. // Boundaries of source regions touching the "boundary" regions will be expanded into the "boundary" region. const ExPolygons &src, @@ -191,159 +448,53 @@ std::vector expand_expolygons( // 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(max_nr_expansion_steps > 0); - - // Initial expansion of src to make the source regions intersect with boundary regions just a bit. - float tiny_expansion; - // How much to inflate the seed lines to produce the first wave area. - float initial_step; - // How much to inflate the first wave area and the successive wave areas in each step. - float other_step; - // Number of inflate steps after the initial step. - size_t num_other_steps; - // Maximum inflation of seed contours over the boundary. Used to trim boundary to speed up - // clipping during wave propagation. - float max_inflation; - - // Offsetter to be applied for all inflation waves. Its accuracy is set with the block below. - ClipperLib::ClipperOffset co; - - { - // Initial expansion of src to make the source regions intersect with boundary regions just a bit. - // The expansion should not be too tiny, but also small enough, so the following expansion will - // compensate for tiny_expansion and bring the wave back to the boundary without producing - // ugly cusps where it touches the boundary. - tiny_expansion = std::min(0.25f * full_expansion, scaled(0.05f)); - size_t nsteps = size_t(ceil((full_expansion - tiny_expansion) / expansion_step)); - if (max_nr_expansion_steps > 0) - nsteps = std::min(nsteps, max_nr_expansion_steps); - assert(nsteps > 0); - initial_step = (full_expansion - tiny_expansion) / nsteps; - if (nsteps > 1 && 0.25 * initial_step < tiny_expansion) { - // Decrease the step size by lowering number of steps. - nsteps = std::max(1, (floor((full_expansion - tiny_expansion) / (4. * tiny_expansion)))); - initial_step = (full_expansion - tiny_expansion) / nsteps; - } - if (0.25 * initial_step < tiny_expansion || nsteps == 1) { - tiny_expansion = 0.2f * full_expansion; - initial_step = 0.8f * full_expansion; - } - other_step = initial_step; - num_other_steps = nsteps - 1; - - // Accuracy of the offsetter for wave propagation. - co.ArcTolerance = float(scale_(0.1)); - co.ShortestEdgeLength = std::abs(initial_step * ClipperOffsetShortestEdgeFactor); - - // Maximum inflation of seed contours over the boundary. Used to trim boundary to speed up - // clipping during wave propagation. Needs to be in sync with the offsetter accuracy. - // Clipper positive round offset should rather offset less than more. - // Still a little bit of additional offset was added. - max_inflation = (tiny_expansion + nsteps * initial_step) * 1.1; -// (clipper_round_offset_error(tiny_expansion, co.ArcTolerance) + nsteps * clipper_round_offset_error(initial_step, co.ArcTolerance) * 1.5; // Account for uncertainty - } - - using Intersection = ClipperZUtils::ClipperZIntersectionVisitor::Intersection; - using Intersections = ClipperZUtils::ClipperZIntersectionVisitor::Intersections; - - ClipperLib_Z::Paths expansion_seeds; - Intersections intersections; - - coord_t idx_boundary_begin = 1; - coord_t idx_boundary_end; - coord_t idx_src_end; - - { - ClipperLib_Z::Clipper zclipper; - ClipperZUtils::ClipperZIntersectionVisitor visitor(intersections); - zclipper.ZFillFunction(visitor.clipper_callback()); - // as closed contours - { - ClipperLib_Z::Paths zboundary = ClipperZUtils::expolygons_to_zpaths(boundary, idx_boundary_begin); - idx_boundary_end = idx_boundary_begin + coord_t(zboundary.size()); - zclipper.AddPaths(zboundary, ClipperLib_Z::ptClip, true); - } - // as open contours - std::vector> zsrc_splits; - { - ClipperLib_Z::Paths zsrc = expolygons_to_zpaths_expanded_opened(src, tiny_expansion, idx_boundary_end); - zclipper.AddPaths(zsrc, ClipperLib_Z::ptSubject, false); - idx_src_end = idx_boundary_end + coord_t(zsrc.size()); - zsrc_splits.reserve(zsrc.size()); - for (const ClipperLib_Z::Path &path : zsrc) { - assert(path.size() >= 2); - assert(path.front() == path.back()); - zsrc_splits.emplace_back(path.front(), -1); - } - std::sort(zsrc_splits.begin(), zsrc_splits.end(), [](const auto &l, const auto &r){ return ClipperZUtils::zpoint_lower(l.first, r.first); }); - } - ClipperLib_Z::PolyTree polytree; - zclipper.Execute(ClipperLib_Z::ctIntersection, polytree, ClipperLib_Z::pftNonZero, ClipperLib_Z::pftNonZero); - ClipperLib_Z::PolyTreeToPaths(std::move(polytree), expansion_seeds); - merge_splits(expansion_seeds, zsrc_splits); - } - - // Sort paths into their respective islands. - // Each src x boundary will be processed (wave expanded) independently. - // Multiple pieces of a single src may intersect the same boundary. - struct SeedOrigin { - int src; - int boundary; - int seed; - }; - std::vector map_seeds; - map_seeds.reserve(expansion_seeds.size()); - int iseed = 0; - for (const ClipperLib_Z::Path &path : expansion_seeds) { - assert(path.size() >= 2); - const ClipperLib_Z::IntPoint &front = path.front(); - const ClipperLib_Z::IntPoint &back = path.back(); - // Both ends of a seed segment are supposed to be inside a single boundary expolygon. - assert(front.z() < 0); - assert(back.z() < 0); - const Intersection *intersection = nullptr; - auto intersection_point_valid = [idx_boundary_end, idx_src_end](const Intersection &is) { - return is.first >= 1 && is.first < idx_boundary_end && - is.second >= idx_boundary_end && is.second < idx_src_end; - }; - if (front.z() < 0) { - const Intersection &is = intersections[- front.z() - 1]; - assert(intersection_point_valid(is)); - if (intersection_point_valid(is)) - intersection = &is; - } - if (! intersection && back.z() < 0) { - const Intersection &is = intersections[- back.z() - 1]; - assert(intersection_point_valid(is)); - if (intersection_point_valid(is)) - intersection = &is; - } - if (intersection) { - // The path intersects the boundary contour at least at one side. - map_seeds.push_back({ intersection->second - idx_boundary_end, intersection->first - 1, iseed }); - } - ++ iseed; - } - // Sort the seeds by their intersection boundary and source contour. - std::sort(map_seeds.begin(), map_seeds.end(), [](const auto &l, const auto &r){ - return l.boundary < r.boundary || (l.boundary == r.boundary && l.src < r.src); - }); + auto params = RegionExpansionParameters::build(full_expansion, expansion_step, max_nr_expansion_steps); + return propagate_waves_ex(wave_seeds(src, boundary, params.tiny_expansion, true), boundary, params); +} +std::vector expand_expolygons(const ExPolygons &src, const ExPolygons &boundary, + // Scaled expansion value + float expansion, + // Expand by waves of expansion_step size (expansion_step is scaled). + float expansion_step, + // Don't take more than max_nr_steps for small expansion_step. + size_t max_nr_steps) +{ std::vector out(src.size(), Polygons{}); - ClipperLib::Paths paths; - for (auto it_seed = map_seeds.begin(); it_seed != map_seeds.end();) { - auto it = it_seed; - paths.clear(); - for (; it != map_seeds.end() && it->boundary == it_seed->boundary && it->src == it_seed->src; ++ it) - paths.emplace_back(ClipperZUtils::from_zpath(expansion_seeds[it->seed])); - // Propagate the wavefront while clipping it with the trimmed boundary. - // Collect the expanded polygons, merge them with the source polygons. - append(out[it_seed->src], propagate_wave_from_boundary(co, paths, boundary[it_seed->boundary], initial_step, other_step, num_other_steps, max_inflation)); - it_seed = it; - } + for (RegionExpansion &r : propagate_waves(src, boundary, expansion, expansion_step, max_nr_steps)) + out[r.src_id].emplace_back(std::move(r.polygon)); + return out; +} +std::vector expand_merge_expolygons(ExPolygons &&src, const ExPolygons &boundary, const RegionExpansionParameters ¶ms) +{ + // expanded regions are sorted by boundary id and source id + std::vector expanded = propagate_waves(src, boundary, params); + // expanded regions will be merged into source regions, thus they will be re-sorted by source id. + std::sort(expanded.begin(), expanded.end(), [](const auto &l, const auto &r) { return l.src_id < r.src_id; }); + uint32_t last = 0; + Polygons acc; + ExPolygons out; + out.reserve(src.size()); + for (auto it = expanded.begin(); it != expanded.end();) { + auto it2 = it; + acc.clear(); + for (; it2 != expanded.end() && it->src_id == it2->src_id; ++ it2) + acc.emplace_back(std::move(it2->polygon)); + for (; last < it->src_id; ++ last) + out.emplace_back(std::move(src[last])); + //FIXME offset & merging could be more efficient, for example one does not need to copy the source expolygon + append(acc, to_polygons(std::move(src[it->src_id]))); + ExPolygons merged = union_safety_offset_ex(acc); + // Expanding one expolygon by waves should not change connectivity of the source expolygon: + // Single expolygon should be produced possibly with increased number of holes. + assert(merged.size() == 1); + if (! merged.empty()) + out.emplace_back(std::move(merged.front())); + it = it2; + } + for (; last < uint32_t(src.size()); ++ last) + out.emplace_back(std::move(src[last])); return out; } diff --git a/src/libslic3r/Algorithm/RegionExpansion.hpp b/src/libslic3r/Algorithm/RegionExpansion.hpp index bbfcc0a65..26aab198a 100644 --- a/src/libslic3r/Algorithm/RegionExpansion.hpp +++ b/src/libslic3r/Algorithm/RegionExpansion.hpp @@ -2,16 +2,102 @@ #define SRC_LIBSLIC3R_ALGORITHM_REGION_EXPANSION_HPP_ #include +#include +#include +#include namespace Slic3r { - -class Polygon; -using Polygons = std::vector; -class ExPolygon; -using ExPolygons = std::vector; - namespace Algorithm { +struct RegionExpansionParameters +{ + // Initial expansion of src to make the source regions intersect with boundary regions just a bit. + float tiny_expansion; + // How much to inflate the seed lines to produce the first wave area. + float initial_step; + // How much to inflate the first wave area and the successive wave areas in each step. + float other_step; + // Number of inflate steps after the initial step. + size_t num_other_steps; + // Maximum inflation of seed contours over the boundary. Used to trim boundary to speed up + // clipping during wave propagation. + float max_inflation; + + // Accuracy of the offsetter for wave propagation. + double arc_tolerance; + double shortest_edge_length; + + static RegionExpansionParameters build( + // Scaled expansion value + float full_expansion, + // Expand by waves of expansion_step size (expansion_step is scaled). + float expansion_step, + // Don't take more than max_nr_steps for small expansion_step. + size_t max_nr_expansion_steps); +}; + +struct WaveSeed { + uint32_t src; + uint32_t boundary; + Points path; +}; +using WaveSeeds = std::vector; + +inline bool lower_by_boundary_and_src(const WaveSeed &l, const WaveSeed &r) +{ + return l.boundary < r.boundary || (l.boundary == r.boundary && l.src < r.src); +} + +inline bool lower_by_src_and_boundary(const WaveSeed &l, const WaveSeed &r) +{ + return l.src < r.src || (l.src == r.src && l.boundary < r.boundary); +} + +// Expand src slightly outwards to intersect boundaries, trim the offsetted src polylines by the boundaries. +// Return the trimmed paths annotated with their origin (source of the path, index of the boundary region). +WaveSeeds wave_seeds( + // Source regions that are supposed to touch the boundary. + const ExPolygons &src, + // Boundaries of source regions touching the "boundary" regions will be expanded into the "boundary" region. + const ExPolygons &boundary, + // Initial expansion of src to make the source regions intersect with boundary regions just a bit. + float tiny_expansion, + bool sorted); + +struct RegionExpansion +{ + Polygon polygon; + uint32_t src_id; + uint32_t boundary_id; +}; + +std::vector propagate_waves(const WaveSeeds &seeds, const ExPolygons &boundary, const RegionExpansionParameters ¶ms); + +std::vector propagate_waves(const ExPolygons &src, const ExPolygons &boundary, + // Scaled expansion value + float expansion, + // Expand by waves of expansion_step size (expansion_step is scaled). + float expansion_step, + // Don't take more than max_nr_steps for small expansion_step. + size_t max_nr_steps); + +struct RegionExpansionEx +{ + ExPolygon expolygon; + uint32_t src_id; + uint32_t boundary_id; +}; + +std::vector propagate_waves_ex(const WaveSeeds &seeds, const ExPolygons &boundary, const RegionExpansionParameters ¶ms); + +std::vector propagate_waves_ex(const ExPolygons &src, const ExPolygons &boundary, + // Scaled expansion value + float expansion, + // Expand by waves of expansion_step size (expansion_step is scaled). + float expansion_step, + // Don't take more than max_nr_steps for small expansion_step. + size_t max_nr_steps); + std::vector expand_expolygons(const ExPolygons &src, const ExPolygons &boundary, // Scaled expansion value float expansion, @@ -20,6 +106,8 @@ std::vector expand_expolygons(const ExPolygons &src, const ExPolygons // Don't take more than max_nr_steps for small expansion_step. size_t max_nr_steps); +std::vector expand_merge_expolygons(ExPolygons &&src, const ExPolygons &boundary, const RegionExpansionParameters ¶ms); + } // Algorithm } // Slic3r diff --git a/src/libslic3r/BridgeDetector.hpp b/src/libslic3r/BridgeDetector.hpp index b11736417..b63be869d 100644 --- a/src/libslic3r/BridgeDetector.hpp +++ b/src/libslic3r/BridgeDetector.hpp @@ -75,12 +75,9 @@ private: //return ideal bridge direction and unsupported bridge endpoints distance. -inline std::tuple detect_bridging_direction(const Polygons &to_cover, const Polygons &anchors_area) +inline std::tuple detect_bridging_direction(const Lines &floating_edges, const Polygons &overhang_area) { - Polygons overhang_area = diff(to_cover, anchors_area); - Polylines floating_polylines = diff_pl(to_polylines(overhang_area), expand(anchors_area, float(SCALED_EPSILON))); - - if (floating_polylines.empty()) { + if (floating_edges.empty()) { // consider this area anchored from all sides, pick bridging direction that will likely yield shortest bridges //use 3mm resolution (should be quite fast, and rough estimation should not cause any problems here) auto [pc1, pc2] = compute_principal_components(overhang_area, 3.0); @@ -92,7 +89,6 @@ inline std::tuple detect_bridging_direction(const Polygons &to_co } // Overhang is not fully surrounded by anchors, in that case, find such direction that will minimize the number of bridge ends/180turns in the air - Lines floating_edges = to_lines(floating_polylines); std::unordered_map directions{}; for (const Line &l : floating_edges) { Vec2d normal = l.normal().cast().normalized(); @@ -126,6 +122,13 @@ inline std::tuple detect_bridging_direction(const Polygons &to_co return {result_dir, min_cost}; }; +//return ideal bridge direction and unsupported bridge endpoints distance. +inline std::tuple detect_bridging_direction(const Polygons &to_cover, const Polygons &anchors_area) +{ + Polygons overhang_area = diff(to_cover, anchors_area); + Lines floating_edges = to_lines(diff_pl(to_polylines(overhang_area), expand(anchors_area, float(SCALED_EPSILON)))); + return detect_bridging_direction(floating_edges, overhang_area); +} } diff --git a/src/libslic3r/GCode/RetractWhenCrossingPerimeters.cpp b/src/libslic3r/GCode/RetractWhenCrossingPerimeters.cpp index 4be42d763..b834ca518 100644 --- a/src/libslic3r/GCode/RetractWhenCrossingPerimeters.cpp +++ b/src/libslic3r/GCode/RetractWhenCrossingPerimeters.cpp @@ -19,20 +19,7 @@ bool RetractWhenCrossingPerimeters::travel_inside_internal_regions(const Layer & if (surface.is_internal()) m_internal_islands.emplace_back(&surface.expolygon); // Calculate bounding boxes of internal slices. - class BBoxWrapper { - public: - BBoxWrapper(const size_t idx, const BoundingBox &bbox) : - m_idx(idx), - // Inflate the bounding box a bit to account for numerical issues. - m_bbox(bbox.min - Point(SCALED_EPSILON, SCALED_EPSILON), bbox.max + Point(SCALED_EPSILON, SCALED_EPSILON)) {} - size_t idx() const { return m_idx; } - const AABBTree::BoundingBox& bbox() const { return m_bbox; } - Point centroid() const { return ((m_bbox.min().cast() + m_bbox.max().cast()) / 2).cast(); } - private: - size_t m_idx; - AABBTree::BoundingBox m_bbox; - }; - std::vector bboxes; + std::vector bboxes; bboxes.reserve(m_internal_islands.size()); for (size_t i = 0; i < m_internal_islands.size(); ++ i) bboxes.emplace_back(i, get_extents(*m_internal_islands[i])); diff --git a/src/libslic3r/LayerRegion.cpp b/src/libslic3r/LayerRegion.cpp index 0f42b2107..d28b64bff 100644 --- a/src/libslic3r/LayerRegion.cpp +++ b/src/libslic3r/LayerRegion.cpp @@ -8,6 +8,7 @@ #include "Surface.hpp" #include "BoundingBox.hpp" #include "SVG.hpp" +#include "Algorithm/RegionExpansion.hpp" #include #include @@ -139,6 +140,252 @@ void LayerRegion::make_perimeters( } } +#if 1 + +// Extract surfaces of given type from surfaces, extract fill (layer) thickness of one of the surfaces. +static ExPolygons fill_surfaces_extract_expolygons(Surfaces &surfaces, SurfaceType surface_type, double &thickness) +{ + size_t cnt = 0; + for (const Surface &surface : surfaces) + if (surface.surface_type == surface_type) { + ++ cnt; + thickness = surface.thickness; + } + if (cnt == 0) + return {}; + + ExPolygons out; + out.reserve(cnt); + for (Surface &surface : surfaces) + if (surface.surface_type == surface_type) + out.emplace_back(std::move(surface.expolygon)); + return out; +} + +// Extract bridging surfaces from "surfaces", expand them into "shells" using expansion_params, +// detect bridges. +// Trim "shells" by the expanded bridges. +Surfaces expand_bridges_detect_orientations( + Surfaces &surfaces, + ExPolygons &shells, + const Algorithm::RegionExpansionParameters &expansion_params) +{ + using namespace Slic3r::Algorithm; + + double thickness; + ExPolygons bridges_ex = fill_surfaces_extract_expolygons(surfaces, stBottomBridge, thickness); + if (bridges_ex.empty()) + return {}; + + // Calculate bridge anchors and their expansions in their respective shell region. + WaveSeeds bridge_anchors = wave_seeds(bridges_ex, shells, expansion_params.tiny_expansion, true); + std::vector bridge_expansions = propagate_waves_ex(bridge_anchors, shells, expansion_params); + + // Cache for detecting bridge orientation and merging regions with overlapping expansions. + struct Bridge { + ExPolygon expolygon; + uint32_t group_id; + double angle = -1; + }; + std::vector bridges; + { + bridges.reserve(bridges_ex.size()); + uint32_t group_id = 0; + for (ExPolygon &ex : bridges_ex) + bridges.push_back({ std::move(ex), group_id ++ }); + bridges_ex.clear(); + } + + // Group the bridge surfaces by overlaps. + auto group_id = [&bridges](uint32_t src_id) { + uint32_t group_id = bridges[src_id].group_id; + while (group_id != src_id) { + src_id = group_id; + group_id = bridges[src_id].group_id; + } + bridges[src_id].group_id = group_id; + return group_id; + }; + + { + // Cache of bboxes per expansion boundary. + std::vector bboxes; + // Detect overlaps of bridge anchors inside their respective shell regions. + // bridge_expansions are sorted by boundary id and source id. + for (auto it = bridge_expansions.begin(); it != bridge_expansions.end();) { + // For each boundary region: + auto it2 = it; + for (++ it2; it2 != bridge_expansions.end() && it2->boundary_id == it->boundary_id; ++ it2); + bboxes.clear(); + bboxes.reserve(it2 - it); + for (it2 = it; it2 != bridge_expansions.end() && it2->boundary_id == it->boundary_id; ++ it2) + bboxes.emplace_back(get_extents(it2->expolygon.contour)); + auto it_end = it2; + // For each bridge anchor of the current source: + for (; it != it_end; ++ it) { + // A grup id for this bridge. + for (it2 = std::next(it); it2 != it_end; ++ it2) + if (it->src_id != it2->src_id && + bboxes[it - bridge_expansions.begin()].overlap(bboxes[it2 - bridge_expansions.begin()]) && + // One may ignore holes, they are irrelevant for intersection test. + ! intersection(it->expolygon.contour, it2->expolygon.contour).empty()) { + // The two bridge regions intersect. Give them the same group id. + uint32_t id = group_id(it->src_id); + uint32_t id2 = group_id(it2->src_id); + bridges[it->src_id].group_id = bridges[it2->src_id].group_id = std::min(id, id2); + } + } + } + } + + // Detect bridge directions. + { + std::sort(bridge_anchors.begin(), bridge_anchors.end(), Algorithm::lower_by_src_and_boundary); + auto it_bridge_anchor = bridge_anchors.begin(); + Lines lines; + for (uint32_t bridge_id = 0; bridge_id < uint32_t(bridges.size()); ++ bridge_id) { + Bridge &bridge = bridges[bridge_id]; + lines.clear(); + for (++ it_bridge_anchor; it_bridge_anchor != bridge_anchors.end() && it_bridge_anchor->src == bridge_id; ++ it_bridge_anchor) + if (Points &polyline = it_bridge_anchor->path; polyline.size() >= 2) { + reserve_more_power_of_2(lines, polyline.size() - 1); + for (size_t i = 1; i < polyline.size(); ++ i) + lines.push_back({ polyline[i - 1], polyline[1] }); + } + auto [bridging_dir, unsupported_dist] = detect_bridging_direction(lines, to_polygons(bridge.expolygon)); + bridge.angle = M_PI + std::atan2(bridging_dir.y(), bridging_dir.x()); + // #if 1 + // coordf_t stroke_width = scale_(0.06); + // BoundingBox bbox = get_extents(initial); + // bbox.offset(scale_(1.)); + // ::Slic3r::SVG + // svg(debug_out_path(("bridge"+std::to_string(bridges[idx_last].bridge_angle)+"_"+std::to_string(this->layer()->bottom_z())).c_str()), + // bbox); + + // svg.draw(initial, "cyan"); + // svg.draw(to_lines(lower_layer->lslices), "green", stroke_width); + // #endif + } + } + + // Merge the groups with the same group id, produce surfaces by merging source overhangs with their newly expanded anchors. + Surfaces out; + { + Polygons acc; + Surface templ{ stBottomBridge, {} }; + for (uint32_t bridge_id = 0; bridge_id < uint32_t(bridges.size()); ++ bridge_id) { + acc.clear(); + for (uint32_t bridge_id2 = bridge_id; bridge_id2 < uint32_t(bridges.size()); ++ bridge_id2) + if (group_id(bridge_id) == bridge_id) { + append(acc, to_polygons(std::move(bridges[bridge_id2].expolygon))); + append(acc, to_polygons(std::move(bridge_expansions[bridge_id2].expolygon))); + } + //FIXME try to be smart and pick the best bridging angle for all? + templ.bridge_angle = bridges[bridge_id].angle; + // without safety offset, artifacts are generated (GH #2494) + for (ExPolygon &ex : union_safety_offset_ex(acc)) + out.emplace_back(templ, std::move(ex)); + } + } + + // Clip the shells by the expanded bridges. + shells = diff_ex(shells, out); + return out; +} + +// Extract bridging surfaces from "surfaces", expand them into "shells" using expansion_params. +// Trim "shells" by the expanded bridges. +static Surfaces expand_merge_surfaces( + Surfaces &surfaces, + SurfaceType surface_type, + ExPolygons &shells, + const Algorithm::RegionExpansionParameters ¶ms, + const double bridge_angle = -1.) +{ + double thickness; + ExPolygons src = fill_surfaces_extract_expolygons(surfaces, surface_type, thickness); + if (src.empty()) + return {}; + + std::vector expanded = expand_merge_expolygons(std::move(src), shells, params); + // Trim the shells by the expanded expolygons. + shells = diff_ex(shells, expanded); + + Surface templ{ surface_type, {} }; + templ.bridge_angle = bridge_angle; + Surfaces out; + out.reserve(expanded.size()); + for (auto &expoly : expanded) + out.emplace_back(templ, std::move(expoly)); + return out; +} + +void LayerRegion::process_external_surfaces(const Layer *lower_layer, const Polygons *lower_layer_covered) +{ +#ifdef SLIC3R_DEBUG_SLICE_PROCESSING + export_region_fill_surfaces_to_svg_debug("4_process_external_surfaces-initial"); +#endif /* SLIC3R_DEBUG_SLICE_PROCESSING */ + + // Width of the perimeters. + float shell_width = 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); + shell_width += 0.5f * external_perimeter_flow.scaled_width() + external_perimeter_flow.scaled_spacing(); + shell_width += perimeter_flow.scaled_spacing() * (num_perimeters - 1); + } else { + } + + // 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); + // Don't take more than max_nr_steps for small expansion_step. + static constexpr const size_t max_nr_expansion_steps = 5; + + // Expand the top / bottom / bridge surfaces into the shell thickness solid infills. + double layer_thickness; + ExPolygons shells = union_ex(fill_surfaces_extract_expolygons(m_fill_surfaces.surfaces, stInternalSolid, layer_thickness)); + + SurfaceCollection bridges; + { + BOOST_LOG_TRIVIAL(trace) << "Processing external surface, detecting bridges. layer" << this->layer()->print_z; + const double custom_angle = this->region().config().bridge_angle.value; + const auto params = Algorithm::RegionExpansionParameters::build(expansion_bottom_bridge, expansion_step, max_nr_expansion_steps); + bridges.surfaces = custom_angle > 0 ? + expand_bridges_detect_orientations(m_fill_surfaces.surfaces, shells, params) : + expand_merge_surfaces(m_fill_surfaces.surfaces, stBottomBridge, shells, params, custom_angle); + BOOST_LOG_TRIVIAL(trace) << "Processing external surface, detecting bridges - done"; +#if 0 + { + static int iRun = 0; + bridges.export_to_svg(debug_out_path("bridges-after-grouping-%d.svg", iRun++), true); + } +#endif + } + + Surfaces bottoms = expand_merge_surfaces(m_fill_surfaces.surfaces, stBottom, shells, + Algorithm::RegionExpansionParameters::build(expansion_bottom, expansion_step, max_nr_expansion_steps)); + Surfaces tops = expand_merge_surfaces(m_fill_surfaces.surfaces, stTop, shells, + Algorithm::RegionExpansionParameters::build(expansion_top, expansion_step, max_nr_expansion_steps)); + + m_fill_surfaces.remove_types({ stBottomBridge, stBottom, stTop, stInternalSolid }); + reserve_more(m_fill_surfaces.surfaces, shells.size() + bridges.size() + bottoms.size() + tops.size()); + Surface solid_templ(stInternalSolid, {}); + solid_templ.thickness = layer_thickness; + m_fill_surfaces.append(std::move(shells), solid_templ); + m_fill_surfaces.append(std::move(bridges.surfaces)); + m_fill_surfaces.append(std::move(bottoms)); + m_fill_surfaces.append(std::move(tops)); + +#ifdef SLIC3R_DEBUG_SLICE_PROCESSING + export_region_fill_surfaces_to_svg_debug("4_process_external_surfaces-final"); +#endif /* SLIC3R_DEBUG_SLICE_PROCESSING */ +} +#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. @@ -146,10 +393,11 @@ void LayerRegion::make_perimeters( void LayerRegion::process_external_surfaces(const Layer *lower_layer, const Polygons *lower_layer_covered) { const bool has_infill = this->region().config().fill_density.value > 0.; - const float margin = float(scale_(EXTERNAL_INFILL_MARGIN)); +// const float margin = scaled(0.1); // float(scale_(EXTERNAL_INFILL_MARGIN)); + const float margin = float(scale_(EXTERNAL_INFILL_MARGIN)); #ifdef SLIC3R_DEBUG_SLICE_PROCESSING - export_region_fill_surfaces_to_svg_debug("3_process_external_surfaces-initial"); + export_region_fill_surfaces_to_svg_debug("4_process_external_surfaces-initial"); #endif /* SLIC3R_DEBUG_SLICE_PROCESSING */ // 1) Collect bottom and bridge surfaces, each of them grown by a fixed 3mm offset @@ -164,7 +412,6 @@ void LayerRegion::process_external_surfaces(const Layer *lower_layer, const Poly Surfaces internal; // Areas, where an infill of various types (top, bottom, bottom bride, sparse, void) could be placed. Polygons fill_boundaries = to_polygons(this->fill_expolygons()); - Polygons lower_layer_covered_tmp; // Collect top surfaces and internal surfaces. // Collect fill_boundaries: If we're slicing with no infill, we can't extend external surfaces over non-existent infill. @@ -174,33 +421,42 @@ void LayerRegion::process_external_surfaces(const Layer *lower_layer, const Poly // Voids are sparse infills if infill rate is zero. Polygons voids; for (const Surface &surface : this->fill_surfaces()) { - if (surface.is_top()) { - // Collect the top surfaces, inflate them and trim them by the bottom surfaces. - // This gives the priority to bottom surfaces. - surfaces_append(top, offset_ex(surface.expolygon, margin, EXTERNAL_SURFACES_OFFSET_PARAMETERS), surface); - } else if (surface.surface_type == stBottom || (surface.surface_type == stBottomBridge && lower_layer == nullptr)) { - // Grown by 3mm. - surfaces_append(bottom, offset_ex(surface.expolygon, margin, EXTERNAL_SURFACES_OFFSET_PARAMETERS), surface); - } else if (surface.surface_type == stBottomBridge) { - if (! surface.empty()) + assert(! surface.empty()); + if (! surface.empty()) { + if (surface.is_top()) { + // Collect the top surfaces, inflate them and trim them by the bottom surfaces. + // This gives the priority to bottom surfaces. + surfaces_append(top, offset_ex(surface.expolygon, margin, EXTERNAL_SURFACES_OFFSET_PARAMETERS), surface); + } else if (surface.surface_type == stBottom || (surface.surface_type == stBottomBridge && lower_layer == nullptr)) { + // Grown by 3mm. + surfaces_append(bottom, offset_ex(surface.expolygon, margin, EXTERNAL_SURFACES_OFFSET_PARAMETERS), surface); + } else if (surface.surface_type == stBottomBridge) { bridges.emplace_back(surface); - } - if (surface.is_internal()) { - assert(surface.surface_type == stInternal || surface.surface_type == stInternalSolid); - if (! has_infill && lower_layer != nullptr) - polygons_append(voids, surface.expolygon); - internal.emplace_back(std::move(surface)); + } else { + assert(surface.is_internal()); + assert(surface.surface_type == stInternal || surface.surface_type == stInternalSolid); + if (! has_infill && lower_layer != nullptr) + polygons_append(voids, surface.expolygon); + internal.emplace_back(std::move(surface)); + } } } - if (! has_infill && lower_layer != nullptr && ! voids.empty()) { + if (! voids.empty()) { + // There are some voids (empty infill regions) on this layer. Usually one does not want to expand + // any infill into these voids, with the exception the expanded infills are supported by layers below + // with nonzero inill. + assert(! has_infill && lower_layer != nullptr); // Remove voids from fill_boundaries, that are not supported by the layer below. + Polygons lower_layer_covered_tmp; if (lower_layer_covered == nullptr) { lower_layer_covered = &lower_layer_covered_tmp; lower_layer_covered_tmp = to_polygons(lower_layer->lslices); } if (! lower_layer_covered->empty()) + // Allow the top / bottom surfaces to expand into the voids of this layer if supported by the layer below. voids = diff(voids, *lower_layer_covered); - fill_boundaries = diff(fill_boundaries, voids); + if (! voids.empty()) + fill_boundaries = diff(fill_boundaries, voids); } } @@ -224,13 +480,12 @@ void LayerRegion::process_external_surfaces(const Layer *lower_layer, const Poly #ifdef SLIC3R_DEBUG_SLICE_PROCESSING { static int iRun = 0; - SVG svg(debug_out_path("3_process_external_surfaces-fill_regions-%d.svg", iRun ++).c_str(), get_extents(fill_boundaries_ex)); + SVG svg(debug_out_path("4_process_external_surfaces-fill_regions-%d.svg", iRun ++).c_str(), get_extents(fill_boundaries_ex)); svg.draw(fill_boundaries_ex); svg.draw_outline(fill_boundaries_ex, "black", "blue", scale_(0.05)); svg.Close(); } - -// export_region_fill_surfaces_to_svg_debug("3_process_external_surfaces-initial"); +// export_region_fill_surfaces_to_svg_debug("4_process_external_surfaces-initial"); #endif /* SLIC3R_DEBUG_SLICE_PROCESSING */ { @@ -253,7 +508,8 @@ void LayerRegion::process_external_surfaces(const Layer *lower_layer, const Poly if (idx_island == -1) { BOOST_LOG_TRIVIAL(trace) << "Bridge did not fall into the source region!"; } else { - // Found an island, to which this bridge region belongs. Trim it, + // Found an island, to which this bridge region belongs. Trim the expanded bridging region + // with its source region, so it does not overflow into a neighbor region. polys = intersection(polys, fill_boundaries_ex[idx_island]); } bridge_bboxes.push_back(get_extents(polys)); @@ -371,10 +627,10 @@ void LayerRegion::process_external_surfaces(const Layer *lower_layer, const Poly Surfaces new_surfaces; { - // Merge top and bottom in a single collection. - surfaces_append(top, std::move(bottom)); // Intersect the grown surfaces with the actual fill boundaries. Polygons bottom_polygons = to_polygons(bottom); + // Merge top and bottom in a single collection. + surfaces_append(top, std::move(bottom)); for (size_t i = 0; i < top.size(); ++ i) { Surface &s1 = top[i]; if (s1.empty()) @@ -422,9 +678,10 @@ void LayerRegion::process_external_surfaces(const Layer *lower_layer, const Poly m_fill_surfaces.surfaces = std::move(new_surfaces); #ifdef SLIC3R_DEBUG_SLICE_PROCESSING - export_region_fill_surfaces_to_svg_debug("3_process_external_surfaces-final"); + export_region_fill_surfaces_to_svg_debug("4_process_external_surfaces-final"); #endif /* SLIC3R_DEBUG_SLICE_PROCESSING */ } +#endif void LayerRegion::prepare_fill_surfaces() { diff --git a/src/libslic3r/PrintObject.cpp b/src/libslic3r/PrintObject.cpp index 24a4191c1..1b593df26 100644 --- a/src/libslic3r/PrintObject.cpp +++ b/src/libslic3r/PrintObject.cpp @@ -260,6 +260,22 @@ void PrintObject::prepare_infill() m_print->throw_if_canceled(); } + + // Add solid fills to ensure the shell vertical thickness. + this->discover_vertical_shells(); + 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) { + for (const Layer *layer : m_layers) { + LayerRegion *layerm = layer->m_regions[region_id]; + layerm->export_region_slices_to_svg_debug("3_discover_vertical_shells-final"); + layerm->export_region_fill_surfaces_to_svg_debug("3_discover_vertical_shells-final"); + } // for each layer + } // 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. @@ -272,17 +288,13 @@ void PrintObject::prepare_infill() this->process_external_surfaces(); m_print->throw_if_canceled(); - // Add solid fills to ensure the shell vertical thickness. - this->discover_vertical_shells(); - 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) { for (const Layer *layer : m_layers) { LayerRegion *layerm = layer->m_regions[region_id]; - layerm->export_region_slices_to_svg_debug("6_discover_vertical_shells-final"); - layerm->export_region_fill_surfaces_to_svg_debug("6_discover_vertical_shells-final"); + layerm->export_region_slices_to_svg_debug("3_process_external_surfaces-final"); + layerm->export_region_fill_surfaces_to_svg_debug("3_process_external_surfaces-final"); } // for each layer } // for each region #endif /* SLIC3R_DEBUG_SLICE_PROCESSING */ @@ -1042,7 +1054,7 @@ void PrintObject::process_external_surfaces() if (has_voids && m_layers.size() > 1) { // All but stInternal fill surfaces will get expanded and possibly trimmed. std::vector layer_expansions_and_voids(m_layers.size(), false); - for (size_t layer_idx = 0; layer_idx < m_layers.size(); ++ layer_idx) { + for (size_t layer_idx = 1; layer_idx < m_layers.size(); ++ layer_idx) { const Layer *layer = m_layers[layer_idx]; bool expansions = false; bool voids = false; @@ -1068,6 +1080,8 @@ void PrintObject::process_external_surfaces() [this, &surfaces_covered, &layer_expansions_and_voids, unsupported_width](const tbb::blocked_range& range) { for (size_t layer_idx = range.begin(); layer_idx < range.end(); ++ layer_idx) if (layer_expansions_and_voids[layer_idx + 1]) { + // Layer above is partially filled with solid infill (top, bottom, bridging...), + // while some sparse inill regions are empty (0% infill). m_print->throw_if_canceled(); Polygons voids; for (const LayerRegion *layerm : m_layers[layer_idx]->regions()) { @@ -1093,7 +1107,9 @@ void PrintObject::process_external_surfaces() 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]); } } @@ -1152,7 +1168,7 @@ void PrintObject::discover_vertical_shells() tbb::parallel_for( tbb::blocked_range(0, num_layers, grain_size), [this, &cache_top_botom_regions](const tbb::blocked_range& range) { - const SurfaceType surfaces_bottom[2] = { stBottom, stBottomBridge }; + const std::initializer_list surfaces_bottom { stBottom, stBottomBridge }; const size_t num_regions = this->num_printing_regions(); for (size_t idx_layer = range.begin(); idx_layer < range.end(); ++ idx_layer) { m_print->throw_if_canceled(); @@ -1172,8 +1188,8 @@ void PrintObject::discover_vertical_shells() append(cache.top_surfaces, offset(layerm.slices().filter_by_type(stTop), min_perimeter_infill_spacing)); append(cache.top_surfaces, offset(layerm.fill_surfaces().filter_by_type(stTop), min_perimeter_infill_spacing)); // Bottom surfaces. - append(cache.bottom_surfaces, offset(layerm.slices().filter_by_types(surfaces_bottom, 2), min_perimeter_infill_spacing)); - append(cache.bottom_surfaces, offset(layerm.fill_surfaces().filter_by_types(surfaces_bottom, 2), min_perimeter_infill_spacing)); + append(cache.bottom_surfaces, offset(layerm.slices().filter_by_types(surfaces_bottom), min_perimeter_infill_spacing)); + append(cache.bottom_surfaces, offset(layerm.fill_surfaces().filter_by_types(surfaces_bottom), min_perimeter_infill_spacing)); // 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; @@ -1233,7 +1249,7 @@ void PrintObject::discover_vertical_shells() tbb::parallel_for( tbb::blocked_range(0, num_layers, grain_size), [this, region_id, &cache_top_botom_regions](const tbb::blocked_range& range) { - const SurfaceType surfaces_bottom[2] = { stBottom, stBottomBridge }; + const std::initializer_list surfaces_bottom { stBottom, stBottomBridge }; for (size_t idx_layer = range.begin(); idx_layer < range.end(); ++ idx_layer) { m_print->throw_if_canceled(); Layer &layer = *m_layers[idx_layer]; @@ -1244,8 +1260,8 @@ void PrintObject::discover_vertical_shells() cache.top_surfaces = offset(layerm.slices().filter_by_type(stTop), min_perimeter_infill_spacing); append(cache.top_surfaces, offset(layerm.fill_surfaces().filter_by_type(stTop), min_perimeter_infill_spacing)); // Bottom surfaces. - cache.bottom_surfaces = offset(layerm.slices().filter_by_types(surfaces_bottom, 2), min_perimeter_infill_spacing); - append(cache.bottom_surfaces, offset(layerm.fill_surfaces().filter_by_types(surfaces_bottom, 2), min_perimeter_infill_spacing)); + cache.bottom_surfaces = offset(layerm.slices().filter_by_types(surfaces_bottom), min_perimeter_infill_spacing); + append(cache.bottom_surfaces, offset(layerm.fill_surfaces().filter_by_types(surfaces_bottom), min_perimeter_infill_spacing)); // Holes over all regions. Only collect them once, they are valid for all region_id iterations. if (cache.holes.empty()) { for (size_t region_id = 0; region_id < layer.regions().size(); ++ region_id) @@ -1275,8 +1291,8 @@ void PrintObject::discover_vertical_shells() const PrintRegionConfig ®ion_config = layerm->region().config(); #ifdef SLIC3R_DEBUG_SLICE_PROCESSING - layerm->export_region_slices_to_svg_debug("4_discover_vertical_shells-initial"); - layerm->export_region_fill_surfaces_to_svg_debug("4_discover_vertical_shells-initial"); + layerm->export_region_slices_to_svg_debug("3_discover_vertical_shells-initial"); + layerm->export_region_fill_surfaces_to_svg_debug("3_discover_vertical_shells-initial"); #endif /* SLIC3R_DEBUG_SLICE_PROCESSING */ Flow solid_infill_flow = layerm->flow(frSolidInfill); @@ -1405,8 +1421,7 @@ void PrintObject::discover_vertical_shells() #endif /* SLIC3R_DEBUG_SLICE_PROCESSING */ // Trim the shells region by the internal & internal void surfaces. - const SurfaceType surfaceTypesInternal[] = { stInternal, stInternalVoid, stInternalSolid }; - const Polygons polygonsInternal = to_polygons(layerm->fill_surfaces().filter_by_types(surfaceTypesInternal, 3)); + const Polygons polygonsInternal = to_polygons(layerm->fill_surfaces().filter_by_types({ stInternal, stInternalVoid, stInternalSolid })); shell = intersection(shell, polygonsInternal, ApplySafetyOffset::Yes); polygons_append(shell, diff(polygonsInternal, holes)); if (shell.empty()) @@ -1472,8 +1487,7 @@ void PrintObject::discover_vertical_shells() #endif /* SLIC3R_DEBUG_SLICE_PROCESSING */ // Assign resulting internal surfaces to layer. - const SurfaceType surfaceTypesKeep[] = { stTop, stBottom, stBottomBridge }; - layerm->m_fill_surfaces.keep_types(surfaceTypesKeep, sizeof(surfaceTypesKeep)/sizeof(SurfaceType)); + layerm->m_fill_surfaces.keep_types({ stTop, stBottom, stBottomBridge }); layerm->m_fill_surfaces.append(new_internal, stInternal); layerm->m_fill_surfaces.append(new_internal_void, stInternalVoid); layerm->m_fill_surfaces.append(new_internal_solid, stInternalSolid); @@ -1485,8 +1499,8 @@ void PrintObject::discover_vertical_shells() #ifdef SLIC3R_DEBUG_SLICE_PROCESSING for (size_t idx_layer = 0; idx_layer < m_layers.size(); ++idx_layer) { LayerRegion *layerm = m_layers[idx_layer]->get_region(region_id); - layerm->export_region_slices_to_svg_debug("4_discover_vertical_shells-final"); - layerm->export_region_fill_surfaces_to_svg_debug("4_discover_vertical_shells-final"); + layerm->export_region_slices_to_svg_debug("3_discover_vertical_shells-final"); + layerm->export_region_fill_surfaces_to_svg_debug("3_discover_vertical_shells-final"); } #endif /* SLIC3R_DEBUG_SLICE_PROCESSING */ } // for each region @@ -1870,12 +1884,11 @@ void PrintObject::clip_fill_surfaces() for (LayerRegion *layerm : lower_layer->m_regions) { if (layerm->region().config().fill_density.value == 0) continue; - SurfaceType internal_surface_types[] = { stInternal, stInternalVoid }; Polygons internal; for (Surface &surface : layerm->m_fill_surfaces.surfaces) if (surface.surface_type == stInternal || surface.surface_type == stInternalVoid) polygons_append(internal, std::move(surface.expolygon)); - layerm->m_fill_surfaces.remove_types(internal_surface_types, 2); + layerm->m_fill_surfaces.remove_types({ stInternal, stInternalVoid }); layerm->m_fill_surfaces.append(intersection_ex(internal, upper_internal, ApplySafetyOffset::Yes), stInternal); layerm->m_fill_surfaces.append(diff_ex (internal, upper_internal, ApplySafetyOffset::Yes), stInternalVoid); // If there are voids it means that our internal infill is not adjacent to @@ -2058,8 +2071,7 @@ void PrintObject::discover_horizontal_shells() neighbor_layerm->m_fill_surfaces.append(internal, stInternal); polygons_append(polygons_internal, to_polygons(std::move(internal))); // assign top and bottom surfaces to layer - SurfaceType surface_types_solid[] = { stTop, stBottom, stBottomBridge }; - backup.keep_types(surface_types_solid, 3); + backup.keep_types({ stTop, stBottom, stBottomBridge }); std::vector top_bottom_groups; backup.group(&top_bottom_groups); for (SurfacesPtr &group : top_bottom_groups) diff --git a/src/libslic3r/Surface.hpp b/src/libslic3r/Surface.hpp index 4afcdf6f5..1f352e977 100644 --- a/src/libslic3r/Surface.hpp +++ b/src/libslic3r/Surface.hpp @@ -33,40 +33,31 @@ class Surface public: SurfaceType surface_type; ExPolygon expolygon; - double thickness; // in mm - unsigned short thickness_layers; // in layers - double bridge_angle; // in radians, ccw, 0 = East, only 0+ (negative means undefined) - unsigned short extra_perimeters; + double thickness { -1 }; // in mm + unsigned short thickness_layers { 1 }; // in layers + double bridge_angle { -1. }; // in radians, ccw, 0 = East, only 0+ (negative means undefined) + unsigned short extra_perimeters { 0 }; - Surface(const Slic3r::Surface &rhs) - : surface_type(rhs.surface_type), expolygon(rhs.expolygon), - thickness(rhs.thickness), thickness_layers(rhs.thickness_layers), - bridge_angle(rhs.bridge_angle), extra_perimeters(rhs.extra_perimeters) - {}; - - Surface(SurfaceType _surface_type, const ExPolygon &_expolygon) - : surface_type(_surface_type), expolygon(_expolygon), - thickness(-1), thickness_layers(1), bridge_angle(-1), extra_perimeters(0) - {}; - Surface(const Surface &other, const ExPolygon &_expolygon) - : surface_type(other.surface_type), expolygon(_expolygon), - thickness(other.thickness), thickness_layers(other.thickness_layers), - bridge_angle(other.bridge_angle), extra_perimeters(other.extra_perimeters) - {}; - Surface(Surface &&rhs) - : surface_type(rhs.surface_type), expolygon(std::move(rhs.expolygon)), - thickness(rhs.thickness), thickness_layers(rhs.thickness_layers), - bridge_angle(rhs.bridge_angle), extra_perimeters(rhs.extra_perimeters) - {}; - Surface(SurfaceType _surface_type, ExPolygon &&_expolygon) - : surface_type(_surface_type), expolygon(std::move(_expolygon)), - thickness(-1), thickness_layers(1), bridge_angle(-1), extra_perimeters(0) - {}; - Surface(const Surface &other, ExPolygon &&_expolygon) - : surface_type(other.surface_type), expolygon(std::move(_expolygon)), - thickness(other.thickness), thickness_layers(other.thickness_layers), - bridge_angle(other.bridge_angle), extra_perimeters(other.extra_perimeters) - {}; + Surface(const Slic3r::Surface &rhs) : + surface_type(rhs.surface_type), expolygon(rhs.expolygon), + thickness(rhs.thickness), thickness_layers(rhs.thickness_layers), + bridge_angle(rhs.bridge_angle), extra_perimeters(rhs.extra_perimeters) {} + Surface(SurfaceType surface_type, const ExPolygon &expolygon) : + surface_type(surface_type), expolygon(expolygon) {} + Surface(const Surface &templ, const ExPolygon &expolygon) : + surface_type(templ.surface_type), expolygon(expolygon), + thickness(templ.thickness), thickness_layers(templ.thickness_layers), + bridge_angle(templ.bridge_angle), extra_perimeters(templ.extra_perimeters) {} + Surface(Surface &&rhs) : + surface_type(rhs.surface_type), expolygon(std::move(rhs.expolygon)), + thickness(rhs.thickness), thickness_layers(rhs.thickness_layers), + bridge_angle(rhs.bridge_angle), extra_perimeters(rhs.extra_perimeters) {} + Surface(SurfaceType surface_type, ExPolygon &&expolygon) : + surface_type(surface_type), expolygon(std::move(expolygon)) {} + Surface(const Surface &templ, ExPolygon &&expolygon) : + surface_type(templ.surface_type), expolygon(std::move(expolygon)), + thickness(templ.thickness), thickness_layers(templ.thickness_layers), + bridge_angle(templ.bridge_angle), extra_perimeters(templ.extra_perimeters) {} Surface& operator=(const Surface &rhs) { diff --git a/src/libslic3r/SurfaceCollection.cpp b/src/libslic3r/SurfaceCollection.cpp index 10a12b683..fbc30ca63 100644 --- a/src/libslic3r/SurfaceCollection.cpp +++ b/src/libslic3r/SurfaceCollection.cpp @@ -51,16 +51,12 @@ SurfacesPtr SurfaceCollection::filter_by_type(const SurfaceType type) const return ss; } -SurfacesPtr SurfaceCollection::filter_by_types(const SurfaceType *types, int ntypes) const +SurfacesPtr SurfaceCollection::filter_by_types(std::initializer_list types) const { SurfacesPtr ss; for (const Surface &surface : this->surfaces) - for (int i = 0; i < ntypes; ++ i) { - if (surface.surface_type == types[i]) { - ss.push_back(&surface); - break; - } - } + if (std::find(types.begin(), types.end(), surface.surface_type) != types.end()) + ss.push_back(&surface); return ss; } @@ -85,23 +81,15 @@ void SurfaceCollection::keep_type(const SurfaceType type) surfaces.erase(surfaces.begin() + j, surfaces.end()); } -void SurfaceCollection::keep_types(const SurfaceType *types, int ntypes) +void SurfaceCollection::keep_types(std::initializer_list types) { size_t j = 0; - for (size_t i = 0; i < surfaces.size(); ++ i) { - bool keep = false; - for (int k = 0; k < ntypes; ++ k) { - if (surfaces[i].surface_type == types[k]) { - keep = true; - break; - } - } - if (keep) { + for (size_t i = 0; i < surfaces.size(); ++ i) + if (std::find(types.begin(), types.end(), surfaces[i].surface_type) != types.end()) { if (j < i) std::swap(surfaces[i], surfaces[j]); ++ j; } - } if (j < surfaces.size()) surfaces.erase(surfaces.begin() + j, surfaces.end()); } @@ -136,23 +124,15 @@ void SurfaceCollection::remove_type(const SurfaceType type, ExPolygons *polygons surfaces.erase(surfaces.begin() + j, surfaces.end()); } -void SurfaceCollection::remove_types(const SurfaceType *types, int ntypes) +void SurfaceCollection::remove_types(std::initializer_list types) { size_t j = 0; - for (size_t i = 0; i < surfaces.size(); ++ i) { - bool remove = false; - for (int k = 0; k < ntypes; ++ k) { - if (surfaces[i].surface_type == types[k]) { - remove = true; - break; - } - } - if (! remove) { + for (size_t i = 0; i < surfaces.size(); ++ i) + if (std::find(types.begin(), types.end(), surfaces[i].surface_type) == types.end()) { if (j < i) std::swap(surfaces[i], surfaces[j]); ++ j; } - } if (j < surfaces.size()) surfaces.erase(surfaces.begin() + j, surfaces.end()); } diff --git a/src/libslic3r/SurfaceCollection.hpp b/src/libslic3r/SurfaceCollection.hpp index b81808b32..0f62875b2 100644 --- a/src/libslic3r/SurfaceCollection.hpp +++ b/src/libslic3r/SurfaceCollection.hpp @@ -3,6 +3,7 @@ #include "libslic3r.h" #include "Surface.hpp" +#include #include namespace Slic3r { @@ -27,11 +28,11 @@ public: return false; } SurfacesPtr filter_by_type(const SurfaceType type) const; - SurfacesPtr filter_by_types(const SurfaceType *types, int ntypes) const; + SurfacesPtr filter_by_types(std::initializer_list types) const; void keep_type(const SurfaceType type); - void keep_types(const SurfaceType *types, int ntypes); + void keep_types(std::initializer_list types); void remove_type(const SurfaceType type); - void remove_types(const SurfaceType *types, int ntypes); + void remove_types(std::initializer_list types); void filter_by_type(SurfaceType type, Polygons *polygons) const; void remove_type(const SurfaceType type, ExPolygons *polygons); void set_type(SurfaceType type) { diff --git a/src/libslic3r/Utils.hpp b/src/libslic3r/Utils.hpp index 495bcbc9b..571451b87 100644 --- a/src/libslic3r/Utils.hpp +++ b/src/libslic3r/Utils.hpp @@ -176,6 +176,21 @@ template size_t next_highest_power_of_2(T v, return next_highest_power_of_2(uint32_t(v)); } +template void reserve_power_of_2(VectorType &vector, size_t n) +{ + vector.reserve(next_highest_power_of_2(n)); +} + +template void reserve_more(VectorType &vector, size_t n) +{ + vector.reserve(vector.size() + n); +} + +template void reserve_more_power_of_2(VectorType &vector, size_t n) +{ + vector.reserve(next_highest_power_of_2(vector.size() + n)); +} + template inline INDEX_TYPE prev_idx_modulo(INDEX_TYPE idx, const INDEX_TYPE count) { diff --git a/tests/libslic3r/test_region_expansion.cpp b/tests/libslic3r/test_region_expansion.cpp index 5d83b8e9d..9f8a6fdc5 100644 --- a/tests/libslic3r/test_region_expansion.cpp +++ b/tests/libslic3r/test_region_expansion.cpp @@ -251,4 +251,34 @@ SCENARIO("Region expansion basics", "[RegionExpansion]") { } } } + GIVEN("square with hole, hole edge anchored") { + Polygon outer{ { -1 * ten, -1 * ten }, { 2 * ten, -1 * ten }, { 2 * ten, 2 * ten }, { -1 * ten, 2 * ten } }; + Polygon hole { { 0, ten }, { ten, ten }, { ten, 0 }, { 0, 0 } }; + Polygon anchor{ { 0, 0 }, { ten, 0 }, { ten, ten }, { 0, ten } }; + ExPolygon boundary(outer); + boundary.holes = { hole }; + + WHEN("expanded") { + static constexpr const float expansion = scaled(5.); + std::vector expanded = Algorithm::expand_expolygons({ ExPolygon{anchor} }, { boundary }, + expansion, + scaled(0.4), // expansion step + 15); // max num steps +#if 0 + SVG::export_expolygons(DEBUG_TEMP_DIR "square_with_hole_anchored-out.svg", + { { { { ExPolygon{anchor} } }, { "anchor", "orange", 0.5f } }, + { { { boundary } }, { "boundary", "blue", 0.5f } }, + { { union_ex(expanded.front()) }, { "expanded", "red", "black", "", scaled(0.1f), 0.5f } } }); +#endif + THEN("The anchor expands into a single region with a hole") { + REQUIRE(expanded.size() == 1); + REQUIRE(expanded.front().size() == 2); + } + THEN("The area of anchor is correct") { + double area_calculated = area(expanded.front()); + double area_expected = double(expansion) * 4. * double(ten) + M_PI * sqr(expansion); + REQUIRE(is_approx(area_expected, area_calculated, sqr(scaled(0.6)))); + } + } + } } From 785ef08656428cd9da9edf5886c0c438483b2782 Mon Sep 17 00:00:00 2001 From: Vojtech Bubnik Date: Mon, 2 Jan 2023 14:59:40 +0100 Subject: [PATCH 05/48] WIP ensure vertical wall thickness: Reduced amount of shell region expansion, added filling in of narrow regions between solid infills (for example created by propagating shell from the side and from the top at the same time). --- src/libslic3r/PrintObject.cpp | 50 ++++++++++++++++++++++++----------- 1 file changed, 34 insertions(+), 16 deletions(-) diff --git a/src/libslic3r/PrintObject.cpp b/src/libslic3r/PrintObject.cpp index 1b593df26..85e70779c 100644 --- a/src/libslic3r/PrintObject.cpp +++ b/src/libslic3r/PrintObject.cpp @@ -1147,6 +1147,9 @@ void PrintObject::discover_vertical_shells() }; std::vector cache_top_botom_regions(num_layers, DiscoverVerticalShellsCacheEntry()); bool top_bottom_surfaces_all_regions = this->num_printing_regions() > 1 && ! m_config.interface_shells.value; +// 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.05f; 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. @@ -1182,14 +1185,14 @@ void PrintObject::discover_vertical_shells() ++ 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 min_perimeter_infill_spacing = float(layerm.flow(frSolidInfill).scaled_spacing()) * 1.05f; + 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(layerm.slices().filter_by_type(stTop), min_perimeter_infill_spacing)); - append(cache.top_surfaces, offset(layerm.fill_surfaces().filter_by_type(stTop), min_perimeter_infill_spacing)); + append(cache.top_surfaces, offset(layerm.slices().filter_by_type(stTop), top_bottom_expansion)); +// append(cache.top_surfaces, offset(layerm.fill_surfaces().filter_by_type(stTop), top_bottom_expansion)); // Bottom surfaces. - append(cache.bottom_surfaces, offset(layerm.slices().filter_by_types(surfaces_bottom), min_perimeter_infill_spacing)); - append(cache.bottom_surfaces, offset(layerm.fill_surfaces().filter_by_types(surfaces_bottom), min_perimeter_infill_spacing)); + append(cache.bottom_surfaces, offset(layerm.slices().filter_by_types(surfaces_bottom), top_bottom_expansion)); +// append(cache.bottom_surfaces, offset(layerm.fill_surfaces().filter_by_types(surfaces_bottom), top_bottom_expansion)); // 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; @@ -1252,16 +1255,16 @@ void PrintObject::discover_vertical_shells() const std::initializer_list surfaces_bottom { stBottom, stBottomBridge }; for (size_t idx_layer = range.begin(); idx_layer < range.end(); ++ idx_layer) { m_print->throw_if_canceled(); - Layer &layer = *m_layers[idx_layer]; - LayerRegion &layerm = *layer.m_regions[region_id]; - float min_perimeter_infill_spacing = float(layerm.flow(frSolidInfill).scaled_spacing()) * 1.05f; + 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; // Top surfaces. auto &cache = cache_top_botom_regions[idx_layer]; - cache.top_surfaces = offset(layerm.slices().filter_by_type(stTop), min_perimeter_infill_spacing); - append(cache.top_surfaces, offset(layerm.fill_surfaces().filter_by_type(stTop), min_perimeter_infill_spacing)); + cache.top_surfaces = offset(layerm.slices().filter_by_type(stTop), top_bottom_expansion); +// append(cache.top_surfaces, offset(layerm.fill_surfaces().filter_by_type(stTop), top_bottom_expansion)); // Bottom surfaces. - cache.bottom_surfaces = offset(layerm.slices().filter_by_types(surfaces_bottom), min_perimeter_infill_spacing); - append(cache.bottom_surfaces, offset(layerm.fill_surfaces().filter_by_types(surfaces_bottom), min_perimeter_infill_spacing)); + cache.bottom_surfaces = offset(layerm.slices().filter_by_types(surfaces_bottom), top_bottom_expansion); +// append(cache.bottom_surfaces, offset(layerm.fill_surfaces().filter_by_types(surfaces_bottom), top_bottom_expansion)); // Holes over all regions. Only collect them once, they are valid for all region_id iterations. if (cache.holes.empty()) { for (size_t region_id = 0; region_id < layer.regions().size(); ++ region_id) @@ -1437,9 +1440,24 @@ void PrintObject::discover_vertical_shells() Polygons shell_before = shell; #endif /* SLIC3R_DEBUG_SLICE_PROCESSING */ #if 1 - // Intentionally inflate a bit more than how much the region has been shrunk, - // so there will be some overlap between this solid infill and the other infill regions (mainly the sparse infill). - shell = opening(union_(shell), 0.5f * min_perimeter_infill_spacing, 0.8f * min_perimeter_infill_spacing, ClipperLib::jtSquare); + { + // Open to remove (filter out) regions narrower than a bit less than an infill extrusion line width. + // Such narrow regions are difficult to fill in with a gap fill algorithm (or Arachne), however they are most likely + // not needed for print stability / quality. + const float narrow_ensure_vertical_wall_thickness_region_radius = 0.5f * 0.65f * min_perimeter_infill_spacing; + // Then close gaps narrower than 1.2 * line width, such gaps are difficult to fill in with sparse infill, + // thus they will be merged into the solid infill. + const float narrow_sparse_infill_region_radius = 0.5f * 1.2f * min_perimeter_infill_spacing; + // Finally expand the infill a bit to remove tiny gaps between solid infill and the other regions. + const float tiny_overlap_radius = 0.2f * min_perimeter_infill_spacing; + shell = shrink(opening(union_(shell), + // Open to remove (filter out) regions narrower than an infill extrusion line width. + narrow_ensure_vertical_wall_thickness_region_radius, + // Then close gaps narrower than 1.2 * line width, such gaps are difficult to fill in with sparse infill. + narrow_ensure_vertical_wall_thickness_region_radius + narrow_sparse_infill_region_radius, ClipperLib::jtSquare), + // Finally expand the infill a bit to remove tiny gaps between solid infill and the other regions. + narrow_sparse_infill_region_radius - tiny_overlap_radius, ClipperLib::jtSquare); + } if (shell.empty()) continue; #else From fbed29e2095bfaf0df38343fc9a673d05d482cd5 Mon Sep 17 00:00:00 2001 From: Vojtech Bubnik Date: Tue, 3 Jan 2023 10:06:52 +0100 Subject: [PATCH 06/48] WIP Ensure vertical wall thickness rework: bugfixes --- src/libslic3r/Algorithm/RegionExpansion.cpp | 115 +++++++++++++------- src/libslic3r/LayerRegion.cpp | 47 +++++--- 2 files changed, 110 insertions(+), 52 deletions(-) diff --git a/src/libslic3r/Algorithm/RegionExpansion.cpp b/src/libslic3r/Algorithm/RegionExpansion.cpp index 4cd4689e6..83561fc83 100644 --- a/src/libslic3r/Algorithm/RegionExpansion.cpp +++ b/src/libslic3r/Algorithm/RegionExpansion.cpp @@ -153,6 +153,46 @@ static inline void merge_splits(ClipperLib_Z::Paths &paths, std::vector; + +static AABBTreeBBoxes build_aabb_tree_over_expolygons(const ExPolygons &expolygons) +{ + // Calculate bounding boxes of internal slices. + std::vector bboxes; + bboxes.reserve(expolygons.size()); + for (size_t i = 0; i < expolygons.size(); ++ i) + bboxes.emplace_back(i, get_extents(expolygons[i].contour)); + // Build AABB tree over bounding boxes of boundary expolygons. + AABBTreeBBoxes out; + out.build_modify_input(bboxes); + return out; +} + +static int sample_in_expolygons( + // AABB tree over boundary expolygons + const AABBTreeBBoxes &aabb_tree, + const ExPolygons &expolygons, + const Point &sample) +{ + int out = -1; + AABBTreeIndirect::traverse(aabb_tree, + [&sample](const AABBTreeBBoxes::Node &node) { + return node.bbox.contains(sample); + }, + [&expolygons, &sample, &out](const AABBTreeBBoxes::Node &node) { + assert(node.is_leaf()); + assert(node.is_valid()); + if (expolygons[node.idx].contains(sample)) { + out = int(node.idx); + // Stop traversal. + return false; + } + // Continue traversal. + return true; + }); + return out; +} + std::vector wave_seeds( // Source regions that are supposed to touch the boundary. const ExPolygons &src, @@ -211,19 +251,7 @@ std::vector wave_seeds( // AABBTree over bounding boxes of boundaries. // Only built if necessary, that is if any of the seed contours is closed, thus there is no intersection point // with the boundary and all Z coordinates of the closed contour point to the source contour. - using AABBTree = AABBTreeIndirect::Tree<2, coord_t>; - AABBTree aabb_tree; - auto init_aabb_tree = [&aabb_tree, &boundary]() { - if (aabb_tree.empty()) { - // Calculate bounding boxes of internal slices. - std::vector bboxes; - bboxes.reserve(boundary.size()); - for (size_t i = 0; i < boundary.size(); ++ i) - bboxes.emplace_back(i, get_extents(boundary[i].contour)); - // Build AABB tree over bounding boxes of boundary expolygons. - aabb_tree.build_modify_input(bboxes); - } - }; + AABBTreeBBoxes aabb_tree; // Sort paths into their respective islands. // Each src x boundary will be processed (wave expanded) independently. @@ -262,24 +290,9 @@ std::vector wave_seeds( // This should be a closed contour. assert(front == back && front.z() >= idx_boundary_end && front.z() < idx_src_end); // Find a source boundary expolygon of one sample of this closed path. - init_aabb_tree(); - Point sample(front.x(), front.y()); - int boundary_id = -1; - AABBTreeIndirect::traverse(aabb_tree, - [&sample](const AABBTree::Node &node) { - return node.bbox.contains(sample); - }, - [&boundary, &sample, &boundary_id](const AABBTree::Node &node) { - assert(node.is_leaf()); - assert(node.is_valid()); - if (boundary[node.idx].contains(sample)) { - boundary_id = int(node.idx); - // Stop traversal. - return false; - } - // Continue traversal. - return true; - }); + if (aabb_tree.empty()) + aabb_tree = build_aabb_tree_over_expolygons(boundary); + int boundary_id = sample_in_expolygons(aabb_tree, boundary, Point(front.x(), front.y())); // Boundary that contains the sample point was found. assert(boundary_id >= 0); if (boundary_id >= 0) @@ -431,6 +444,7 @@ std::vector propagate_waves_ex(const WaveSeeds &seeds, const for (ExPolygon &ex : expolys) out.push_back({ std::move(ex), it->src_id, it->boundary_id }); } + it = it2; } return out; } @@ -477,21 +491,44 @@ std::vector expand_merge_expolygons(ExPolygons &&src, const ExPolygon ExPolygons out; out.reserve(src.size()); for (auto it = expanded.begin(); it != expanded.end();) { - auto it2 = it; - acc.clear(); - for (; it2 != expanded.end() && it->src_id == it2->src_id; ++ it2) - acc.emplace_back(std::move(it2->polygon)); for (; last < it->src_id; ++ last) out.emplace_back(std::move(src[last])); + acc.clear(); + assert(it->src_id == last); + for (; it != expanded.end() && it->src_id == last; ++ it) + acc.emplace_back(std::move(it->polygon)); //FIXME offset & merging could be more efficient, for example one does not need to copy the source expolygon - append(acc, to_polygons(std::move(src[it->src_id]))); + ExPolygon &src_ex = src[last ++]; + assert(! src_ex.contour.empty()); +#if 0 + { + static int iRun = 0; + BoundingBox bbox = get_extents(acc); + bbox.merge(get_extents(src_ex)); + SVG svg(debug_out_path("expand_merge_expolygons-failed-union=%d.svg", iRun ++).c_str(), bbox); + svg.draw(acc); + svg.draw_outline(acc, "black", scale_(0.05)); + svg.draw(src_ex, "red"); + svg.Close(); + } +#endif + Point sample = src_ex.contour.front(); + append(acc, to_polygons(std::move(src_ex))); ExPolygons merged = union_safety_offset_ex(acc); // Expanding one expolygon by waves should not change connectivity of the source expolygon: // Single expolygon should be produced possibly with increased number of holes. - assert(merged.size() == 1); - if (! merged.empty()) + if (merged.size() > 1) { + // assert(merged.size() == 1); + // There is something wrong with the initial waves. Most likely the bridge was not valid at all + // or the boundary region was very close to some bridge edge, but not really touching. + // Pick only a single merged expolygon, which contains one sample point of the source expolygon. + auto aabb_tree = build_aabb_tree_over_expolygons(merged); + int id = sample_in_expolygons(aabb_tree, merged, sample); + assert(id != -1); + if (id != -1) + out.emplace_back(std::move(merged[id])); + } else if (merged.size() == 1) out.emplace_back(std::move(merged.front())); - it = it2; } for (; last < uint32_t(src.size()); ++ last) out.emplace_back(std::move(src[last])); diff --git a/src/libslic3r/LayerRegion.cpp b/src/libslic3r/LayerRegion.cpp index d28b64bff..8413095ce 100644 --- a/src/libslic3r/LayerRegion.cpp +++ b/src/libslic3r/LayerRegion.cpp @@ -183,16 +183,17 @@ Surfaces expand_bridges_detect_orientations( // Cache for detecting bridge orientation and merging regions with overlapping expansions. struct Bridge { - ExPolygon expolygon; - uint32_t group_id; - double angle = -1; + ExPolygon expolygon; + uint32_t group_id; + std::vector::const_iterator bridge_expansion_begin; + double angle = -1; }; std::vector bridges; { bridges.reserve(bridges_ex.size()); uint32_t group_id = 0; for (ExPolygon &ex : bridges_ex) - bridges.push_back({ std::move(ex), group_id ++ }); + bridges.push_back({ std::move(ex), group_id ++, bridge_expansions.end() }); bridges_ex.clear(); } @@ -243,15 +244,24 @@ Surfaces expand_bridges_detect_orientations( std::sort(bridge_anchors.begin(), bridge_anchors.end(), Algorithm::lower_by_src_and_boundary); auto it_bridge_anchor = bridge_anchors.begin(); Lines lines; + Polygons anchor_areas; for (uint32_t bridge_id = 0; bridge_id < uint32_t(bridges.size()); ++ bridge_id) { Bridge &bridge = bridges[bridge_id]; - lines.clear(); - for (++ it_bridge_anchor; it_bridge_anchor != bridge_anchors.end() && it_bridge_anchor->src == bridge_id; ++ it_bridge_anchor) - if (Points &polyline = it_bridge_anchor->path; polyline.size() >= 2) { - reserve_more_power_of_2(lines, polyline.size() - 1); - for (size_t i = 1; i < polyline.size(); ++ i) - lines.push_back({ polyline[i - 1], polyline[1] }); +// lines.clear(); + anchor_areas.clear(); + int32_t last_anchor_id = -1; + for (; it_bridge_anchor != bridge_anchors.end() && it_bridge_anchor->src == bridge_id; ++ it_bridge_anchor) { + if (last_anchor_id != int(it_bridge_anchor->boundary)) { + last_anchor_id = int(it_bridge_anchor->boundary); + append(anchor_areas, std::move(to_polygons(shells[last_anchor_id]))); } +// if (Points &polyline = it_bridge_anchor->path; polyline.size() >= 2) { +// reserve_more_power_of_2(lines, polyline.size() - 1); +// for (size_t i = 1; i < polyline.size(); ++ i) +// lines.push_back({ polyline[i - 1], polyline[1] }); +// } + } + lines = to_lines(diff_pl(to_polylines(bridge.expolygon), expand(anchor_areas, float(SCALED_EPSILON)))); auto [bridging_dir, unsupported_dist] = detect_bridging_direction(lines, to_polygons(bridge.expolygon)); bridge.angle = M_PI + std::atan2(bridging_dir.y(), bridging_dir.x()); // #if 1 @@ -273,12 +283,23 @@ Surfaces expand_bridges_detect_orientations( { Polygons acc; Surface templ{ stBottomBridge, {} }; + std::sort(bridge_expansions.begin(), bridge_expansions.end(), [](auto &l, auto &r) { + return l.src_id < r.src_id || (l.src_id == r.src_id && l.boundary_id < r.boundary_id); + }); + for (auto it = bridge_expansions.begin(); it != bridge_expansions.end(); ) { + bridges[it->src_id].bridge_expansion_begin = it; + uint32_t src_id = it->src_id; + for (++ it; it != bridge_expansions.end() && it->src_id == src_id; ++ it) ; + } for (uint32_t bridge_id = 0; bridge_id < uint32_t(bridges.size()); ++ bridge_id) { acc.clear(); for (uint32_t bridge_id2 = bridge_id; bridge_id2 < uint32_t(bridges.size()); ++ bridge_id2) if (group_id(bridge_id) == bridge_id) { append(acc, to_polygons(std::move(bridges[bridge_id2].expolygon))); - append(acc, to_polygons(std::move(bridge_expansions[bridge_id2].expolygon))); + auto it_bridge_expansion = bridges[bridge_id2].bridge_expansion_begin; + assert(it_bridge_expansion == bridge_expansions.end() || it_bridge_expansion->src_id == bridge_id2); + for (; it_bridge_expansion != bridge_expansions.end() && it_bridge_expansion->src_id == bridge_id2; ++ it_bridge_expansion) + append(acc, to_polygons(std::move(it_bridge_expansion->expolygon))); } //FIXME try to be smart and pick the best bridging angle for all? templ.bridge_angle = bridges[bridge_id].angle; @@ -355,8 +376,8 @@ void LayerRegion::process_external_surfaces(const Layer *lower_layer, const Poly const double custom_angle = this->region().config().bridge_angle.value; const auto params = Algorithm::RegionExpansionParameters::build(expansion_bottom_bridge, expansion_step, max_nr_expansion_steps); bridges.surfaces = custom_angle > 0 ? - expand_bridges_detect_orientations(m_fill_surfaces.surfaces, shells, params) : - expand_merge_surfaces(m_fill_surfaces.surfaces, stBottomBridge, shells, params, custom_angle); + expand_merge_surfaces(m_fill_surfaces.surfaces, stBottomBridge, shells, params, custom_angle) : + expand_bridges_detect_orientations(m_fill_surfaces.surfaces, shells, params); BOOST_LOG_TRIVIAL(trace) << "Processing external surface, detecting bridges - done"; #if 0 { From 62771bf9c1904495eb46033fb79ad9683c441c59 Mon Sep 17 00:00:00 2001 From: Vojtech Bubnik Date: Tue, 3 Jan 2023 12:57:58 +0100 Subject: [PATCH 07/48] Fixed compilation issues and warnings. --- src/libslic3r/Config.hpp | 2 +- src/libslic3r/Point.cpp | 13 +++++++++++++ src/libslic3r/Point.hpp | 14 +++++--------- 3 files changed, 19 insertions(+), 10 deletions(-) diff --git a/src/libslic3r/Config.hpp b/src/libslic3r/Config.hpp index ef3dc32c8..a0425bfee 100644 --- a/src/libslic3r/Config.hpp +++ b/src/libslic3r/Config.hpp @@ -1793,7 +1793,7 @@ public: enum_values.reserve(il.size()); enum_labels.clear(); enum_labels.reserve(il.size()); - for (const std::pair p : il) { + for (const std::pair &p : il) { enum_values.emplace_back(p.first); enum_labels.emplace_back(p.second); } diff --git a/src/libslic3r/Point.cpp b/src/libslic3r/Point.cpp index 794b27c44..09afcc399 100644 --- a/src/libslic3r/Point.cpp +++ b/src/libslic3r/Point.cpp @@ -94,6 +94,19 @@ BoundingBox get_extents(const Points &pts) template BoundingBox get_extents(const Points &pts); template BoundingBox get_extents(const Points &pts); +// if IncludeBoundary, then a bounding box is defined even for a single point. +// otherwise a bounding box is only defined if it has a positive area. +template +BoundingBox get_extents(const std::vector &pts) +{ + BoundingBox bbox; + for (const Points &p : pts) + bbox.merge(get_extents(p)); + return bbox; +} +template BoundingBox get_extents(const std::vector &pts); +template BoundingBox get_extents(const std::vector &pts); + BoundingBoxf get_extents(const std::vector &pts) { BoundingBoxf bbox; diff --git a/src/libslic3r/Point.hpp b/src/libslic3r/Point.hpp index 389fa313b..e86cdcced 100644 --- a/src/libslic3r/Point.hpp +++ b/src/libslic3r/Point.hpp @@ -233,19 +233,15 @@ inline Point lerp(const Point &a, const Point &b, double t) // otherwise a bounding box is only defined if it has a positive area. template BoundingBox get_extents(const Points &pts); -extern template BoundingBox get_extents(const Points& pts); -extern template BoundingBox get_extents(const Points& pts); +extern template BoundingBox get_extents(const Points &pts); +extern template BoundingBox get_extents(const Points &pts); // if IncludeBoundary, then a bounding box is defined even for a single point. // otherwise a bounding box is only defined if it has a positive area. template -BoundingBox get_extents(const std::vector &pts) -{ - BoundingBox bbox; - for (const Points &p : pts) - bbox.merge(get_extents(p)); - return bbox; -} +BoundingBox get_extents(const std::vector &pts); +extern template BoundingBox get_extents(const std::vector &pts); +extern template BoundingBox get_extents(const std::vector &pts); BoundingBoxf get_extents(const std::vector &pts); From b25527833955be511655c0d8a414216453e7ac4e Mon Sep 17 00:00:00 2001 From: Vojtech Bubnik Date: Tue, 3 Jan 2023 13:39:59 +0100 Subject: [PATCH 08/48] Fixed missing includes --- src/libslic3r/Algorithm/RegionExpansion.cpp | 2 ++ src/libslic3r/ClipperZUtils.hpp | 1 + 2 files changed, 3 insertions(+) diff --git a/src/libslic3r/Algorithm/RegionExpansion.cpp b/src/libslic3r/Algorithm/RegionExpansion.cpp index 83561fc83..3846c4749 100644 --- a/src/libslic3r/Algorithm/RegionExpansion.cpp +++ b/src/libslic3r/Algorithm/RegionExpansion.cpp @@ -5,6 +5,8 @@ #include #include +#include + namespace Slic3r { namespace Algorithm { diff --git a/src/libslic3r/ClipperZUtils.hpp b/src/libslic3r/ClipperZUtils.hpp index d69b2e28a..31f822813 100644 --- a/src/libslic3r/ClipperZUtils.hpp +++ b/src/libslic3r/ClipperZUtils.hpp @@ -1,6 +1,7 @@ #ifndef slic3r_ClipperZUtils_hpp_ #define slic3r_ClipperZUtils_hpp_ +#include #include #include From 398222a49f3b32beee64324d1c6adef06b25ea44 Mon Sep 17 00:00:00 2001 From: Vojtech Bubnik Date: Tue, 3 Jan 2023 17:28:44 +0100 Subject: [PATCH 09/48] =?UTF-8?q?Cherry=20picked=20FillBoundedRectilinear?= =?UTF-8?q?=20Co-authored-by:=20Luk=C3=A1=C5=A1=20Hejl=20?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/libslic3r/Fill/FillBase.cpp | 1 + src/libslic3r/Fill/FillBase.hpp | 4 ++ src/libslic3r/Fill/FillConcentric.cpp | 10 +-- src/libslic3r/Fill/FillConcentric.hpp | 5 -- src/libslic3r/Fill/FillRectilinear.cpp | 85 ++++++++++++++++++++++++++ src/libslic3r/Fill/FillRectilinear.hpp | 20 ++++++ src/libslic3r/Polyline.cpp | 13 ++++ src/libslic3r/Polyline.hpp | 5 ++ src/libslic3r/PrintConfig.hpp | 1 + 9 files changed, 131 insertions(+), 13 deletions(-) diff --git a/src/libslic3r/Fill/FillBase.cpp b/src/libslic3r/Fill/FillBase.cpp index b38c5c9f8..0bf3c4dde 100644 --- a/src/libslic3r/Fill/FillBase.cpp +++ b/src/libslic3r/Fill/FillBase.cpp @@ -49,6 +49,7 @@ Fill* Fill::new_from_type(const InfillPattern type) case ipSupportCubic: return new FillAdaptive::Filler(); case ipSupportBase: return new FillSupportBase(); case ipLightning: return new FillLightning::Filler(); + case ipBoundedRectilinear: return new FillBoundedRectilinear(); default: throw Slic3r::InvalidArgument("unknown type"); } } diff --git a/src/libslic3r/Fill/FillBase.hpp b/src/libslic3r/Fill/FillBase.hpp index 5200e1c0d..2a9091d35 100644 --- a/src/libslic3r/Fill/FillBase.hpp +++ b/src/libslic3r/Fill/FillBase.hpp @@ -91,6 +91,10 @@ public: // Octree builds on mesh for usage in the adaptive cubic infill FillAdaptive::Octree* adapt_fill_octree = nullptr; + // PrintConfig and PrintObjectConfig are used by infills that use Arachne (Concentric and FillBoundedRectilinear). + const PrintConfig *print_config = nullptr; + const PrintObjectConfig *print_object_config = nullptr; + public: virtual ~Fill() {} virtual Fill* clone() const = 0; diff --git a/src/libslic3r/Fill/FillConcentric.cpp b/src/libslic3r/Fill/FillConcentric.cpp index 7b005ee35..245947cfe 100644 --- a/src/libslic3r/Fill/FillConcentric.cpp +++ b/src/libslic3r/Fill/FillConcentric.cpp @@ -97,14 +97,8 @@ void FillConcentric::_fill_surface_single(const FillParams ¶ms, continue; ThickPolyline thick_polyline = Arachne::to_thick_polyline(*extrusion); - if (extrusion->is_closed && thick_polyline.points.front() == thick_polyline.points.back() && thick_polyline.width.front() == thick_polyline.width.back()) { - thick_polyline.points.pop_back(); - assert(thick_polyline.points.size() * 2 == thick_polyline.width.size()); - int nearest_idx = nearest_point_index(thick_polyline.points, last_pos); - std::rotate(thick_polyline.points.begin(), thick_polyline.points.begin() + nearest_idx, thick_polyline.points.end()); - std::rotate(thick_polyline.width.begin(), thick_polyline.width.begin() + 2 * nearest_idx, thick_polyline.width.end()); - thick_polyline.points.emplace_back(thick_polyline.points.front()); - } + if (extrusion->is_closed) + thick_polyline.start_at_index(nearest_point_index(thick_polyline.points, last_pos)); thick_polylines_out.emplace_back(std::move(thick_polyline)); last_pos = thick_polylines_out.back().last_point(); } diff --git a/src/libslic3r/Fill/FillConcentric.hpp b/src/libslic3r/Fill/FillConcentric.hpp index 405b7238b..c059cc050 100644 --- a/src/libslic3r/Fill/FillConcentric.hpp +++ b/src/libslic3r/Fill/FillConcentric.hpp @@ -26,11 +26,6 @@ protected: ThickPolylines &thick_polylines_out) override; bool no_sort() const override { return true; } - - const PrintConfig *print_config = nullptr; - const PrintObjectConfig *print_object_config = nullptr; - - friend class Layer; }; } // namespace Slic3r diff --git a/src/libslic3r/Fill/FillRectilinear.cpp b/src/libslic3r/Fill/FillRectilinear.cpp index bb93d824b..c39ec8b1a 100644 --- a/src/libslic3r/Fill/FillRectilinear.cpp +++ b/src/libslic3r/Fill/FillRectilinear.cpp @@ -15,6 +15,7 @@ #include "../Geometry.hpp" #include "../Surface.hpp" #include "../ShortestPath.hpp" +#include "../Arachne/WallToolPaths.hpp" #include "FillRectilinear.hpp" @@ -3043,6 +3044,90 @@ Polylines FillSupportBase::fill_surface(const Surface *surface, const FillParams return polylines_out; } +ThickPolylines FillBoundedRectilinear::fill_surface_arachne(const Surface *surface, const FillParams ¶ms) +{ + // Perform offset. + Slic3r::ExPolygons expp = offset_ex(surface->expolygon, float(scale_(this->overlap - 0.5 * this->spacing))); + // Create the infills for each of the regions. + ThickPolylines thick_polylines_out; + for (ExPolygon &ex_poly : expp) + _fill_surface_single(Surface(*surface, std::move(ex_poly)), params, thick_polylines_out); + + return thick_polylines_out; +} + +void FillBoundedRectilinear::_fill_surface_single(const Surface &surface, const FillParams ¶ms, ThickPolylines &thick_polylines_out) +{ + assert(params.use_arachne); + assert(this->print_config != nullptr && this->print_object_config != nullptr && this->print_region_config != nullptr); + + coord_t scaled_spacing = scaled(this->spacing); + Polygons polygons = offset(surface.expolygon, float(scaled_spacing) / 2.f); + Arachne::WallToolPaths wall_tool_paths(polygons, scaled_spacing, scaled_spacing, 1, 0, params.layer_height, *this->print_object_config, *this->print_config); + if (std::vector loop = wall_tool_paths.getToolPaths(); !loop.empty()) { + assert(loop.size() == 1); + + size_t firts_poly_idx = thick_polylines_out.size(); + Point last_pos(0, 0); + for (const Arachne::ExtrusionLine &extrusion : loop.front()) { + if (extrusion.empty()) + continue; + + ThickPolyline thick_polyline = Arachne::to_thick_polyline(extrusion); + if (thick_polyline.length() == 0.) + //FIXME this should not happen. + continue; + assert(thick_polyline.size() > 1); + assert(thick_polyline.length() > 0.); + //assert(thick_polyline.points.size() == thick_polyline.width.size()); + if (extrusion.is_closed) + thick_polyline.start_at_index(nearest_point_index(thick_polyline.points, last_pos)); + + assert(thick_polyline.size() > 1); + //assert(thick_polyline.points.size() == thick_polyline.width.size()); + thick_polylines_out.emplace_back(std::move(thick_polyline)); + last_pos = thick_polylines_out.back().last_point(); + } + + // clip the paths to prevent the extruder from getting exactly on the first point of the loop + // Keep valid paths only. + size_t j = firts_poly_idx; + for (size_t i = firts_poly_idx; i < thick_polylines_out.size(); ++i) { + assert(thick_polylines_out[i].size() > 1); + assert(thick_polylines_out[i].length() > 0.); + //assert(thick_polylines_out[i].points.size() == thick_polylines_out[i].width.size()); + thick_polylines_out[i].clip_end(this->loop_clipping); + assert(thick_polylines_out[i].size() > 1); + if (thick_polylines_out[i].is_valid()) { + if (j < i) + thick_polylines_out[j] = std::move(thick_polylines_out[i]); + ++j; + } + } + if (j < thick_polylines_out.size()) + thick_polylines_out.erase(thick_polylines_out.begin() + int(j), thick_polylines_out.end()); + } + + // Remaining infill area will be filled with classic Rectilinear infill. + ExPolygons infill_contour = union_ex(wall_tool_paths.getInnerContour()); + if (offset_ex(infill_contour, -float(scaled_spacing / 2.)).empty()) + infill_contour.clear(); // Infill region is too small, so let's filter it out. + + Polygons pp; + for (ExPolygon &ex : infill_contour) + ex.simplify_p(scaled(params.resolution), &pp); + + // Collapse too narrow infill areas and append them to thick_polylines_out. + const auto min_perimeter_infill_spacing = coord_t(scaled_spacing * (1. - INSET_OVERLAP_TOLERANCE)); + const auto infill_overlap = coord_t(scale_(this->print_region_config->get_abs_value("infill_overlap", this->spacing))); + for (ExPolygon &ex_poly : offset2_ex(union_ex(pp), float(-min_perimeter_infill_spacing / 2.), float(infill_overlap + min_perimeter_infill_spacing / 2.))) { + Polylines polylines; + if (Surface new_surface(surface, std::move(ex_poly)); !fill_surface_by_lines(&new_surface, params, 0.f, 0.f, polylines)) + BOOST_LOG_TRIVIAL(error) << "FillBoundedRectilinear::fill_surface() failed to fill a region."; + append(thick_polylines_out, to_thick_polylines(std::move(polylines), scaled(this->spacing))); + } +} + // Lightning infill assumes that the distance between any two sampled points is always // at least equal to the value of spacing. To meet this assumption, we need to use // BoundingBox for whole layers instead of bounding box just around processing ExPolygon. diff --git a/src/libslic3r/Fill/FillRectilinear.hpp b/src/libslic3r/Fill/FillRectilinear.hpp index 0a6c976ad..9f833ea9e 100644 --- a/src/libslic3r/Fill/FillRectilinear.hpp +++ b/src/libslic3r/Fill/FillRectilinear.hpp @@ -7,6 +7,7 @@ namespace Slic3r { +class PrintRegionConfig; class Surface; class FillRectilinear : public Fill @@ -109,6 +110,25 @@ protected: float _layer_angle(size_t idx) const override { return 0.f; } }; +class FillBoundedRectilinear : public FillRectilinear +{ +public: + Fill *clone() const override { return new FillBoundedRectilinear(*this); } + ~FillBoundedRectilinear() override = default; + Polylines fill_surface(const Surface *surface, const FillParams ¶ms) override { return {}; }; + ThickPolylines fill_surface_arachne(const Surface *surface, const FillParams ¶ms) override; + +protected: + void _fill_surface_single(const Surface &surface, const FillParams ¶ms, ThickPolylines &thick_polylines_out); + + bool no_sort() const override { return true; } + + // PrintRegionConfig is used for computing overlap between boundary contour and inner Rectilinear infill. + const PrintRegionConfig *print_region_config = nullptr; + + friend class Layer; +}; + Points sample_grid_pattern(const ExPolygon &expolygon, coord_t spacing, const BoundingBox &global_bounding_box); Points sample_grid_pattern(const ExPolygons &expolygons, coord_t spacing, const BoundingBox &global_bounding_box); Points sample_grid_pattern(const Polygons &polygons, coord_t spacing, const BoundingBox &global_bounding_box); diff --git a/src/libslic3r/Polyline.cpp b/src/libslic3r/Polyline.cpp index e2dcabfe1..d805473b5 100644 --- a/src/libslic3r/Polyline.cpp +++ b/src/libslic3r/Polyline.cpp @@ -292,6 +292,19 @@ void ThickPolyline::clip_end(double distance) assert(this->width.size() == (this->points.size() - 1) * 2); } +void ThickPolyline::start_at_index(int index) +{ + assert(index >= 0 && index < this->points.size()); + assert(this->points.front() == this->points.back() && this->width.front() == this->width.back()); + if (index != 0 && index != (this->points.size() - 1) && this->points.front() == this->points.back() && this->width.front() == this->width.back()) { + this->points.pop_back(); + assert(this->points.size() * 2 == this->width.size()); + std::rotate(this->points.begin(), this->points.begin() + index, this->points.end()); + std::rotate(this->width.begin(), this->width.begin() + 2 * index, this->width.end()); + this->points.emplace_back(this->points.front()); + } +} + double Polyline3::length() const { double l = 0; diff --git a/src/libslic3r/Polyline.hpp b/src/libslic3r/Polyline.hpp index de8f859a5..5615841e2 100644 --- a/src/libslic3r/Polyline.hpp +++ b/src/libslic3r/Polyline.hpp @@ -191,6 +191,11 @@ public: void clip_end(double distance); + // Make this closed ThickPolyline starting in the specified index. + // Be aware that this method can be applicable just for closed ThickPolyline. + // On open ThickPolyline make no effect. + void start_at_index(int index); + std::vector width; std::pair endpoints; }; diff --git a/src/libslic3r/PrintConfig.hpp b/src/libslic3r/PrintConfig.hpp index 7f8d5df12..edfac5f4f 100644 --- a/src/libslic3r/PrintConfig.hpp +++ b/src/libslic3r/PrintConfig.hpp @@ -60,6 +60,7 @@ enum InfillPattern : int { ipRectilinear, ipMonotonic, ipAlignedRectilinear, ipGrid, ipTriangles, ipStars, ipCubic, ipLine, ipConcentric, ipHoneycomb, ip3DHoneycomb, ipGyroid, ipHilbertCurve, ipArchimedeanChords, ipOctagramSpiral, ipAdaptiveCubic, ipSupportCubic, ipSupportBase, ipLightning, + ipBoundedRectilinear, ipCount, }; From 60f6766aab60cd7cdddd67b629c8cea9de31d569 Mon Sep 17 00:00:00 2001 From: Vojtech Bubnik Date: Tue, 3 Jan 2023 17:42:10 +0100 Subject: [PATCH 10/48] Apply FillBoundedRectilinear on narrow internal solid infills to reduce zig-zag movements of the print head on overhangs. Always use thick bridges on internal bridges. Co-authored-by: lane.wei --- src/libslic3r/Fill/Fill.cpp | 62 ++++++++++++++++++++++++++++++----- src/libslic3r/Layer.hpp | 2 +- src/libslic3r/LayerRegion.cpp | 4 +-- 3 files changed, 56 insertions(+), 12 deletions(-) diff --git a/src/libslic3r/Fill/Fill.cpp b/src/libslic3r/Fill/Fill.cpp index c962bbeb0..1093de1f3 100644 --- a/src/libslic3r/Fill/Fill.cpp +++ b/src/libslic3r/Fill/Fill.cpp @@ -16,9 +16,10 @@ #include "FillLightning.hpp" #include "FillConcentric.hpp" - namespace Slic3r { +static constexpr const float NarrowInfillAreaThresholdMM = 3.f; + struct SurfaceFillParams { // Zero based extruder ID. @@ -148,6 +149,7 @@ std::vector group_fills(const Layer &layer) erBridgeInfill : (surface.is_solid() ? (surface.is_top() ? erTopSolidInfill : erSolidInfill) : + //(surface.is_top() ? erTopSolidInfill : (surface.is_bottom()? erBottomSurface : erSolidInfill)) : erInternalInfill); params.bridge_angle = float(surface.bridge_angle); params.angle = float(Geometry::deg2rad(region_config.fill_angle.value)); @@ -155,7 +157,8 @@ std::vector group_fills(const Layer &layer) // Calculate the actual flow we'll be using for this infill. params.bridge = is_bridge || Fill::use_bridge_flow(params.pattern); params.flow = params.bridge ? - layerm.bridging_flow(extrusion_role) : + // Always enable thick bridges for internal bridges. + layerm.bridging_flow(extrusion_role, surface.is_bridge() && ! surface.is_external()) : layerm.flow(extrusion_role, (surface.thickness == -1) ? layer.height : surface.thickness); // Calculate flow spacing for infill pattern generation. @@ -298,6 +301,46 @@ std::vector group_fills(const Layer &layer) } } + // Detect narrow internal solid infill area and use ipBoundedRectilinear pattern instead. + { + std::vector narrow_expolygons; + static constexpr const auto narrow_pattern = ipBoundedRectilinear; + for (size_t surface_fill_id = 0, num_old_fills = surface_fills.size(); surface_fill_id < num_old_fills; ++ surface_fill_id) + if (SurfaceFill &fill = surface_fills[surface_fill_id]; fill.surface.surface_type == stInternalSolid) { + size_t num_expolygons = fill.expolygons.size(); + narrow_expolygons.clear(); + narrow_expolygons.reserve(num_expolygons); + // Detect narrow expolygons. + int num_narrow = 0; + for (const ExPolygon &ex : fill.expolygons) { + bool narrow = offset_ex(ex, -scaled(NarrowInfillAreaThresholdMM)).empty(); + num_narrow += int(narrow); + narrow_expolygons.emplace_back(narrow); + } + if (num_narrow == num_expolygons) { + // All expolygons are narrow, change the fill pattern. + fill.params.pattern = narrow_pattern; + } else if (num_narrow > 0) { + // Some expolygons are narrow, split the fills. + params = fill.params; + params.pattern = narrow_pattern; + surface_fills.emplace_back(params); + SurfaceFill &old_fill = surface_fills[surface_fill_id]; + SurfaceFill &new_fill = surface_fills.back(); + new_fill.region_id = old_fill.region_id; + new_fill.surface.surface_type = stInternalSolid; + new_fill.surface.thickness = old_fill.surface.thickness; + new_fill.expolygons.reserve(num_narrow); + for (size_t i = 0; i < narrow_expolygons.size(); ++ i) + if (narrow_expolygons[i]) + new_fill.expolygons.emplace_back(std::move(old_fill.expolygons[i])); + old_fill.expolygons.erase(std::remove_if(old_fill.expolygons.begin(), old_fill.expolygons.end(), + [&narrow_expolygons, ex_first = old_fill.expolygons.data()](const ExPolygon& ex) { return narrow_expolygons[&ex - ex_first]; }), + old_fill.expolygons.end()); + } + } + } + return surface_fills; } @@ -444,16 +487,17 @@ void Layer::make_fills(FillAdaptive::Octree* adaptive_fill_octree, FillAdaptive: f->layer_id = this->id(); f->z = this->print_z; f->angle = surface_fill.params.angle; - f->adapt_fill_octree = (surface_fill.params.pattern == ipSupportCubic) ? support_fill_octree : adaptive_fill_octree; + f->adapt_fill_octree = (surface_fill.params.pattern == ipSupportCubic) ? support_fill_octree : adaptive_fill_octree; + f->print_config = &this->object()->print()->config(); + f->print_object_config = &this->object()->config(); if (surface_fill.params.pattern == ipLightning) dynamic_cast(f.get())->generator = lightning_generator; - if (perimeter_generator.value == PerimeterGeneratorType::Arachne && surface_fill.params.pattern == ipConcentric) { - FillConcentric *fill_concentric = dynamic_cast(f.get()); - assert(fill_concentric != nullptr); - fill_concentric->print_config = &this->object()->print()->config(); - fill_concentric->print_object_config = &this->object()->config(); + if (surface_fill.params.pattern == ipBoundedRectilinear) { + auto *fill_bounded_rectilinear = dynamic_cast(f.get()); + assert(fill_bounded_rectilinear != nullptr); + fill_bounded_rectilinear->print_region_config = &m_regions[surface_fill.region_id]->region().config(); } // calculate flow spacing for infill pattern generation @@ -483,7 +527,7 @@ void Layer::make_fills(FillAdaptive::Octree* adaptive_fill_octree, FillAdaptive: params.anchor_length = surface_fill.params.anchor_length; params.anchor_length_max = surface_fill.params.anchor_length_max; params.resolution = resolution; - params.use_arachne = perimeter_generator == PerimeterGeneratorType::Arachne && surface_fill.params.pattern == ipConcentric; + params.use_arachne = (perimeter_generator == PerimeterGeneratorType::Arachne && surface_fill.params.pattern == ipConcentric) || surface_fill.params.pattern == ipBoundedRectilinear; params.layer_height = layerm.layer()->height; for (ExPolygon &expoly : surface_fill.expolygons) { diff --git a/src/libslic3r/Layer.hpp b/src/libslic3r/Layer.hpp index 024ed41a4..e6c47fc7a 100644 --- a/src/libslic3r/Layer.hpp +++ b/src/libslic3r/Layer.hpp @@ -134,7 +134,7 @@ public: Flow flow(FlowRole role) const; Flow flow(FlowRole role, double layer_height) const; - Flow bridging_flow(FlowRole role) const; + Flow bridging_flow(FlowRole role, bool force_thick_bridges = false) const; void slices_to_fill_surfaces_clipped(); void prepare_fill_surfaces(); diff --git a/src/libslic3r/LayerRegion.cpp b/src/libslic3r/LayerRegion.cpp index 8413095ce..5add6a7eb 100644 --- a/src/libslic3r/LayerRegion.cpp +++ b/src/libslic3r/LayerRegion.cpp @@ -27,12 +27,12 @@ Flow LayerRegion::flow(FlowRole role, double layer_height) const return m_region->flow(*m_layer->object(), role, layer_height, m_layer->id() == 0); } -Flow LayerRegion::bridging_flow(FlowRole role) const +Flow LayerRegion::bridging_flow(FlowRole role, bool force_thick_bridges) const { const PrintRegion ®ion = this->region(); const PrintRegionConfig ®ion_config = region.config(); const PrintObject &print_object = *this->layer()->object(); - if (print_object.config().thick_bridges) { + if (print_object.config().thick_bridges || force_thick_bridges) { // The old Slic3r way (different from all other slicers): Use rounded extrusions. // Get the configured nozzle_diameter for the extruder associated to the flow role requested. // Here this->extruder(role) - 1 may underflow to MAX_INT, but then the get_at() will follback to zero'th element, so everything is all right. From 1a5533d571ea63c7beea550d50e3505f96d514f1 Mon Sep 17 00:00:00 2001 From: Vojtech Bubnik Date: Wed, 4 Jan 2023 13:38:18 +0100 Subject: [PATCH 11/48] PlaceholderParser: 1) Implemented access to coEnum values, they are returned as strings. 2) Fixed some possible memory leaks. 3) Fixed some possible union type punning issues. --- src/libslic3r/Config.hpp | 8 +- src/libslic3r/PlaceholderParser.cpp | 318 ++++++++++---------- src/libslic3r/PlaceholderParser.hpp | 3 + tests/libslic3r/test_placeholder_parser.cpp | 2 + 4 files changed, 171 insertions(+), 160 deletions(-) diff --git a/src/libslic3r/Config.hpp b/src/libslic3r/Config.hpp index a0425bfee..8be3c68e3 100644 --- a/src/libslic3r/Config.hpp +++ b/src/libslic3r/Config.hpp @@ -301,7 +301,7 @@ template class ConfigOptionSingle : public ConfigOption { public: T value; - explicit ConfigOptionSingle(T value) : value(value) {} + explicit ConfigOptionSingle(T value) : value(std::move(value)) {} operator T() const { return this->value; } void set(const ConfigOption *rhs) override @@ -845,9 +845,9 @@ using ConfigOptionIntsNullable = ConfigOptionIntsTempl; class ConfigOptionString : public ConfigOptionSingle { public: - ConfigOptionString() : ConfigOptionSingle("") {} - explicit ConfigOptionString(const std::string &value) : ConfigOptionSingle(value) {} - + ConfigOptionString() : ConfigOptionSingle(std::string{}) {} + explicit ConfigOptionString(std::string value) : ConfigOptionSingle(std::move(value)) {} + static ConfigOptionType static_type() { return coString; } ConfigOptionType type() const override { return static_type(); } ConfigOption* clone() const override { return new ConfigOptionString(*this); } diff --git a/src/libslic3r/PlaceholderParser.cpp b/src/libslic3r/PlaceholderParser.cpp index 109c949cd..16d793f9f 100644 --- a/src/libslic3r/PlaceholderParser.cpp +++ b/src/libslic3r/PlaceholderParser.cpp @@ -185,75 +185,107 @@ namespace client template struct expr { - expr() : type(TYPE_EMPTY) {} - explicit expr(bool b) : type(TYPE_BOOL) { data.b = b; } - explicit expr(bool b, const Iterator &it_begin, const Iterator &it_end) : type(TYPE_BOOL), it_range(it_begin, it_end) { data.b = b; } - explicit expr(int i) : type(TYPE_INT) { data.i = i; } - explicit expr(int i, const Iterator &it_begin, const Iterator &it_end) : type(TYPE_INT), it_range(it_begin, it_end) { data.i = i; } - explicit expr(double d) : type(TYPE_DOUBLE) { data.d = d; } - explicit expr(double d, const Iterator &it_begin, const Iterator &it_end) : type(TYPE_DOUBLE), it_range(it_begin, it_end) { data.d = d; } - explicit expr(const char *s) : type(TYPE_STRING) { data.s = new std::string(s); } - explicit expr(const std::string &s) : type(TYPE_STRING) { data.s = new std::string(s); } + expr() {} + explicit expr(bool b) : m_type(TYPE_BOOL) { m_data.b = b; } + explicit expr(bool b, const Iterator &it_begin, const Iterator &it_end) : m_type(TYPE_BOOL), it_range(it_begin, it_end) { m_data.b = b; } + explicit expr(int i) : m_type(TYPE_INT) { m_data.i = i; } + explicit expr(int i, const Iterator &it_begin, const Iterator &it_end) : m_type(TYPE_INT), it_range(it_begin, it_end) { m_data.i = i; } + explicit expr(double d) : m_type(TYPE_DOUBLE) { m_data.d = d; } + explicit expr(double d, const Iterator &it_begin, const Iterator &it_end) : m_type(TYPE_DOUBLE), it_range(it_begin, it_end) { m_data.d = d; } + explicit expr(const char *s) : m_type(TYPE_STRING) { m_data.s = new std::string(s); } + explicit expr(const std::string &s) : m_type(TYPE_STRING) { m_data.s = new std::string(s); } explicit expr(const std::string &s, const Iterator &it_begin, const Iterator &it_end) : - type(TYPE_STRING), it_range(it_begin, it_end) { data.s = new std::string(s); } - expr(const expr &rhs) : type(rhs.type), it_range(rhs.it_range) - { if (rhs.type == TYPE_STRING) data.s = new std::string(*rhs.data.s); else data.set(rhs.data); } - explicit expr(expr &&rhs) : type(rhs.type), it_range(rhs.it_range) - { data.set(rhs.data); rhs.type = TYPE_EMPTY; } - explicit expr(expr &&rhs, const Iterator &it_begin, const Iterator &it_end) : type(rhs.type), it_range(it_begin, it_end) - { data.set(rhs.data); rhs.type = TYPE_EMPTY; } - ~expr() { this->reset(); } - - expr &operator=(const expr &rhs) + m_type(TYPE_STRING), it_range(it_begin, it_end) { m_data.s = new std::string(s); } + expr(const expr &rhs) : m_type(rhs.type()), it_range(rhs.it_range) + { if (rhs.type() == TYPE_STRING) m_data.s = new std::string(*rhs.m_data.s); else m_data.set(rhs.m_data); } + explicit expr(expr &&rhs) : expr(rhs, rhs.it_range.begin(), rhs.it_range.end()) {} + explicit expr(expr &&rhs, const Iterator &it_begin, const Iterator &it_end) : m_type(rhs.type()), it_range{ it_begin, it_end } + { + m_data.set(rhs.m_data); + rhs.m_type = TYPE_EMPTY; + } + expr &operator=(const expr &rhs) { - this->type = rhs.type; - this->it_range = rhs.it_range; - if (rhs.type == TYPE_STRING) - this->data.s = new std::string(*rhs.data.s); - else - this->data.set(rhs.data); - return *this; + if (rhs.type() == TYPE_STRING) { + this->set_s(rhs.s()); + } else { + m_type = rhs.type(); + m_data.set(rhs.m_data); + } + this->it_range = rhs.it_range; + return *this; } expr &operator=(expr &&rhs) { - type = rhs.type; - this->it_range = rhs.it_range; - data.set(rhs.data); - rhs.type = TYPE_EMPTY; + if (this != &rhs) { + this->reset(); + m_type = rhs.type(); + this->it_range = rhs.it_range; + m_data.set(rhs.m_data); + rhs.m_type = TYPE_EMPTY; + } return *this; } void reset() { - if (this->type == TYPE_STRING) - delete data.s; - this->type = TYPE_EMPTY; + if (this->type() == TYPE_STRING) + delete m_data.s; + m_type = TYPE_EMPTY; } - bool& b() { return data.b; } - bool b() const { return data.b; } - void set_b(bool v) { this->reset(); this->data.b = v; this->type = TYPE_BOOL; } - int& i() { return data.i; } - int i() const { return data.i; } - void set_i(int v) { this->reset(); this->data.i = v; this->type = TYPE_INT; } - int as_i() const { return (this->type == TYPE_INT) ? this->i() : int(this->d()); } - int as_i_rounded() const { return (this->type == TYPE_INT) ? this->i() : int(std::round(this->d())); } - double& d() { return data.d; } - double d() const { return data.d; } - void set_d(double v) { this->reset(); this->data.d = v; this->type = TYPE_DOUBLE; } - double as_d() const { return (this->type == TYPE_DOUBLE) ? this->d() : double(this->i()); } - std::string& s() { return *data.s; } - const std::string& s() const { return *data.s; } - void set_s(const std::string &s) { this->reset(); this->data.s = new std::string(s); this->type = TYPE_STRING; } - void set_s(std::string &&s) { this->reset(); this->data.s = new std::string(std::move(s)); this->type = TYPE_STRING; } + enum Type { + TYPE_EMPTY = 0, + TYPE_BOOL, + TYPE_INT, + TYPE_DOUBLE, + TYPE_STRING, + }; + Type type() const { return m_type; } + + bool& b() { return m_data.b; } + bool b() const { return m_data.b; } + void set_b(bool v) { this->reset(); this->set_b_lite(v); } + void set_b_lite(bool v) { assert(this->type() != TYPE_STRING); Data tmp; tmp.b = v; m_data.set(tmp); m_type = TYPE_BOOL; } + int& i() { return m_data.i; } + int i() const { return m_data.i; } + void set_i(int v) { this->reset(); set_i_lite(v); } + void set_i_lite(int v) { assert(this->type() != TYPE_STRING); Data tmp; tmp.i = v; m_data.set(tmp); m_type = TYPE_INT; } + int as_i() const { return this->type() == TYPE_INT ? this->i() : int(this->d()); } + int as_i_rounded() const { return this->type() == TYPE_INT ? this->i() : int(std::round(this->d())); } + double& d() { return m_data.d; } + double d() const { return m_data.d; } + void set_d(double v) { this->reset(); this->set_d_lite(v); } + void set_d_lite(double v) { assert(this->type() != TYPE_STRING); Data tmp; tmp.d = v; m_data.set(tmp); m_type = TYPE_DOUBLE; } + double as_d() const { return this->type() == TYPE_DOUBLE ? this->d() : double(this->i()); } + std::string& s() { return *m_data.s; } + const std::string& s() const { return *m_data.s; } + void set_s(const std::string &s) { + if (this->type() == TYPE_STRING) + *m_data.s = s; + else + this->set_s_take_ownership(new std::string(s)); + } + void set_s(std::string &&s) { + if (this->type() == TYPE_STRING) + *m_data.s = std::move(s); + else + this->set_s_take_ownership(new std::string(std::move(s))); + } + void set_s(const char *s) { + if (this->type() == TYPE_STRING) + *m_data.s = s; + else + this->set_s_take_ownership(new std::string(s)); + } std::string to_string() const { std::string out; - switch (type) { - case TYPE_BOOL: out = data.b ? "true" : "false"; break; - case TYPE_INT: out = std::to_string(data.i); break; + switch (this->type()) { + case TYPE_BOOL: out = this->b() ? "true" : "false"; break; + case TYPE_INT: out = std::to_string(this->i()); break; case TYPE_DOUBLE: #if 0 // The default converter produces trailing zeros after the decimal point. @@ -263,48 +295,24 @@ namespace client // It seems to be doing what the old boost::to_string() did. { std::ostringstream ss; - ss << data.d; + ss << this->d(); out = ss.str(); } #endif break; - case TYPE_STRING: out = *data.s; break; + case TYPE_STRING: out = this->s(); break; default: break; } return out; } - union Data { - // Raw image of the other data members. - // The C++ compiler will consider a possible aliasing of char* with any other union member, - // therefore copying the raw data is safe. - char raw[8]; - bool b; - int i; - double d; - std::string *s; - - // Copy the largest member variable through char*, which will alias with all other union members by default. - void set(const Data &rhs) { memcpy(this->raw, rhs.raw, sizeof(rhs.raw)); } - } data; - - enum Type { - TYPE_EMPTY = 0, - TYPE_BOOL, - TYPE_INT, - TYPE_DOUBLE, - TYPE_STRING, - }; - - Type type; - // Range of input iterators covering this expression. // Used for throwing parse exceptions. boost::iterator_range it_range; expr unary_minus(const Iterator start_pos) const { - switch (this->type) { + switch (this->type()) { case TYPE_INT : return expr(- this->i(), start_pos, this->it_range.end()); case TYPE_DOUBLE: @@ -319,7 +327,7 @@ namespace client expr unary_integer(const Iterator start_pos) const { - switch (this->type) { + switch (this->type()) { case TYPE_INT: return expr(this->i(), start_pos, this->it_range.end()); case TYPE_DOUBLE: @@ -334,7 +342,7 @@ namespace client expr round(const Iterator start_pos) const { - switch (this->type) { + switch (this->type()) { case TYPE_INT: return expr(this->i(), start_pos, this->it_range.end()); case TYPE_DOUBLE: @@ -349,7 +357,7 @@ namespace client expr unary_not(const Iterator start_pos) const { - switch (this->type) { + switch (this->type()) { case TYPE_BOOL: return expr(! this->b(), start_pos, this->it_range.end()); default: @@ -362,23 +370,20 @@ namespace client expr &operator+=(const expr &rhs) { - if (this->type == TYPE_STRING) { + if (this->type() == TYPE_STRING) { // Convert the right hand side to string and append. - *this->data.s += rhs.to_string(); - } else if (rhs.type == TYPE_STRING) { + *m_data.s += rhs.to_string(); + } else if (rhs.type() == TYPE_STRING) { // Conver the left hand side to string, append rhs. - this->data.s = new std::string(this->to_string() + rhs.s()); - this->type = TYPE_STRING; + this->set_s(this->to_string() + rhs.s()); } else { const char *err_msg = "Cannot add non-numeric types."; this->throw_if_not_numeric(err_msg); rhs.throw_if_not_numeric(err_msg); - if (this->type == TYPE_DOUBLE || rhs.type == TYPE_DOUBLE) { - double d = this->as_d() + rhs.as_d(); - this->data.d = d; - this->type = TYPE_DOUBLE; - } else - this->data.i += rhs.i(); + if (this->type() == TYPE_DOUBLE || rhs.type() == TYPE_DOUBLE) + this->set_d_lite(this->as_d() + rhs.as_d()); + else + m_data.i += rhs.i(); } this->it_range = boost::iterator_range(this->it_range.begin(), rhs.it_range.end()); return *this; @@ -389,12 +394,10 @@ namespace client const char *err_msg = "Cannot subtract non-numeric types."; this->throw_if_not_numeric(err_msg); rhs.throw_if_not_numeric(err_msg); - if (this->type == TYPE_DOUBLE || rhs.type == TYPE_DOUBLE) { - double d = this->as_d() - rhs.as_d(); - this->data.d = d; - this->type = TYPE_DOUBLE; - } else - this->data.i -= rhs.i(); + if (this->type() == TYPE_DOUBLE || rhs.type() == TYPE_DOUBLE) + this->set_d_lite(this->as_d() - rhs.as_d()); + else + m_data.i -= rhs.i(); this->it_range = boost::iterator_range(this->it_range.begin(), rhs.it_range.end()); return *this; } @@ -404,12 +407,10 @@ namespace client const char *err_msg = "Cannot multiply with non-numeric type."; this->throw_if_not_numeric(err_msg); rhs.throw_if_not_numeric(err_msg); - if (this->type == TYPE_DOUBLE || rhs.type == TYPE_DOUBLE) { - double d = this->as_d() * rhs.as_d(); - this->data.d = d; - this->type = TYPE_DOUBLE; - } else - this->data.i *= rhs.i(); + if (this->type() == TYPE_DOUBLE || rhs.type() == TYPE_DOUBLE) + this->set_d_lite(this->as_d() * rhs.as_d()); + else + m_data.i *= rhs.i(); this->it_range = boost::iterator_range(this->it_range.begin(), rhs.it_range.end()); return *this; } @@ -418,14 +419,12 @@ namespace client { this->throw_if_not_numeric("Cannot divide a non-numeric type."); rhs.throw_if_not_numeric("Cannot divide with a non-numeric type."); - if ((rhs.type == TYPE_INT) ? (rhs.i() == 0) : (rhs.d() == 0.)) + if (rhs.type() == TYPE_INT ? (rhs.i() == 0) : (rhs.d() == 0.)) rhs.throw_exception("Division by zero"); - if (this->type == TYPE_DOUBLE || rhs.type == TYPE_DOUBLE) { - double d = this->as_d() / rhs.as_d(); - this->data.d = d; - this->type = TYPE_DOUBLE; - } else - this->data.i /= rhs.i(); + if (this->type() == TYPE_DOUBLE || rhs.type() == TYPE_DOUBLE) + this->set_d_lite(this->as_d() / rhs.as_d()); + else + m_data.i /= rhs.i(); this->it_range = boost::iterator_range(this->it_range.begin(), rhs.it_range.end()); return *this; } @@ -434,14 +433,12 @@ namespace client { this->throw_if_not_numeric("Cannot divide a non-numeric type."); rhs.throw_if_not_numeric("Cannot divide with a non-numeric type."); - if ((rhs.type == TYPE_INT) ? (rhs.i() == 0) : (rhs.d() == 0.)) + if (rhs.type() == TYPE_INT ? (rhs.i() == 0) : (rhs.d() == 0.)) rhs.throw_exception("Division by zero"); - if (this->type == TYPE_DOUBLE || rhs.type == TYPE_DOUBLE) { - double d = std::fmod(this->as_d(), rhs.as_d()); - this->data.d = d; - this->type = TYPE_DOUBLE; - } else - this->data.i %= rhs.i(); + if (this->type() == TYPE_DOUBLE || rhs.type() == TYPE_DOUBLE) + this->set_d_lite(std::fmod(this->as_d(), rhs.as_d())); + else + m_data.i %= rhs.i(); this->it_range = boost::iterator_range(this->it_range.begin(), rhs.it_range.end()); return *this; } @@ -453,14 +450,14 @@ namespace client static void evaluate_boolean(expr &self, bool &out) { - if (self.type != TYPE_BOOL) + if (self.type() != TYPE_BOOL) self.throw_exception("Not a boolean expression"); out = self.b(); } static void evaluate_boolean_to_string(expr &self, std::string &out) { - if (self.type != TYPE_BOOL) + if (self.type() != TYPE_BOOL) self.throw_exception("Not a boolean expression"); out = self.b() ? "true" : "false"; } @@ -469,31 +466,31 @@ namespace client static void compare_op(expr &lhs, expr &rhs, char op, bool invert) { bool value = false; - if ((lhs.type == TYPE_INT || lhs.type == TYPE_DOUBLE) && - (rhs.type == TYPE_INT || rhs.type == TYPE_DOUBLE)) { + if ((lhs.type() == TYPE_INT || lhs.type() == TYPE_DOUBLE) && + (rhs.type() == TYPE_INT || rhs.type() == TYPE_DOUBLE)) { // Both types are numeric. switch (op) { case '=': - value = (lhs.type == TYPE_DOUBLE || rhs.type == TYPE_DOUBLE) ? + value = (lhs.type() == TYPE_DOUBLE || rhs.type() == TYPE_DOUBLE) ? (std::abs(lhs.as_d() - rhs.as_d()) < 1e-8) : (lhs.i() == rhs.i()); break; case '<': - value = (lhs.type == TYPE_DOUBLE || rhs.type == TYPE_DOUBLE) ? + value = (lhs.type() == TYPE_DOUBLE || rhs.type() == TYPE_DOUBLE) ? (lhs.as_d() < rhs.as_d()) : (lhs.i() < rhs.i()); break; case '>': default: - value = (lhs.type == TYPE_DOUBLE || rhs.type == TYPE_DOUBLE) ? + value = (lhs.type() == TYPE_DOUBLE || rhs.type() == TYPE_DOUBLE) ? (lhs.as_d() > rhs.as_d()) : (lhs.i() > rhs.i()); break; } - } else if (lhs.type == TYPE_BOOL && rhs.type == TYPE_BOOL) { + } else if (lhs.type() == TYPE_BOOL && rhs.type() == TYPE_BOOL) { // Both type are bool. if (op != '=') boost::throw_exception(qi::expectation_failure( lhs.it_range.begin(), rhs.it_range.end(), spirit::info("*Cannot compare the types."))); value = lhs.b() == rhs.b(); - } else if (lhs.type == TYPE_STRING || rhs.type == TYPE_STRING) { + } else if (lhs.type() == TYPE_STRING || rhs.type() == TYPE_STRING) { // One type is string, the other could be converted to string. value = (op == '=') ? (lhs.to_string() == rhs.to_string()) : (op == '<') ? (lhs.to_string() < rhs.to_string()) : (lhs.to_string() > rhs.to_string()); @@ -502,8 +499,7 @@ namespace client lhs.it_range.begin(), rhs.it_range.end(), spirit::info("*Cannot compare the types."))); } lhs.reset(); - lhs.type = TYPE_BOOL; - lhs.data.b = invert ? ! value : value; + lhs.set_b_lite(invert ? ! value : value); } // Compare operators, store the result into lhs. static void equal (expr &lhs, expr &rhs) { compare_op(lhs, rhs, '=', false); } @@ -528,15 +524,14 @@ namespace client { throw_if_not_numeric(param1); throw_if_not_numeric(param2); - if (param1.type == TYPE_DOUBLE || param2.type == TYPE_DOUBLE) { + if (param1.type() == TYPE_DOUBLE || param2.type() == TYPE_DOUBLE) { double d = 0.; switch (fun) { case FUNCTION_MIN: d = std::min(param1.as_d(), param2.as_d()); break; case FUNCTION_MAX: d = std::max(param1.as_d(), param2.as_d()); break; default: param1.throw_exception("Internal error: invalid function"); } - param1.data.d = d; - param1.type = TYPE_DOUBLE; + param1.set_d_lite(d); } else { int i = 0; switch (fun) { @@ -544,8 +539,7 @@ namespace client case FUNCTION_MAX: i = std::max(param1.as_i(), param2.as_i()); break; default: param1.throw_exception("Internal error: invalid function"); } - param1.data.i = i; - param1.type = TYPE_INT; + param1.set_i_lite(i); } } // Store the result into param1. @@ -557,13 +551,10 @@ namespace client { throw_if_not_numeric(param1); throw_if_not_numeric(param2); - if (param1.type == TYPE_DOUBLE || param2.type == TYPE_DOUBLE) { - param1.data.d = std::uniform_real_distribution<>(param1.as_d(), param2.as_d())(rng); - param1.type = TYPE_DOUBLE; - } else { - param1.data.i = std::uniform_int_distribution<>(param1.as_i(), param2.as_i())(rng); - param1.type = TYPE_INT; - } + if (param1.type() == TYPE_DOUBLE || param2.type() == TYPE_DOUBLE) + param1.set_d_lite(std::uniform_real_distribution<>(param1.as_d(), param2.as_d())(rng)); + else + param1.set_i_lite(std::uniform_int_distribution<>(param1.as_i(), param2.as_i())(rng)); } // Store the result into param1. @@ -572,10 +563,10 @@ namespace client static void digits(expr ¶m1, expr ¶m2, expr ¶m3) { throw_if_not_numeric(param1); - if (param2.type != TYPE_INT) + if (param2.type() != TYPE_INT) param2.throw_exception("digits: second parameter must be integer"); - bool has_decimals = param3.type != TYPE_EMPTY; - if (has_decimals && param3.type != TYPE_INT) + bool has_decimals = param3.type() != TYPE_EMPTY; + if (has_decimals && param3.type() != TYPE_INT) param3.throw_exception("digits: third parameter must be integer"); char buf[256]; @@ -593,7 +584,7 @@ namespace client static void regex_op(expr &lhs, boost::iterator_range &rhs, char op) { const std::string *subject = nullptr; - if (lhs.type == TYPE_STRING) { + if (lhs.type() == TYPE_STRING) { // One type is string, the other could be converted to string. subject = &lhs.s(); } else { @@ -604,9 +595,7 @@ namespace client bool result = SLIC3R_REGEX_NAMESPACE::regex_match(*subject, SLIC3R_REGEX_NAMESPACE::regex(pattern)); if (op == '!') result = ! result; - lhs.reset(); - lhs.type = TYPE_BOOL; - lhs.data.b = result; + lhs.set_b(result); } catch (SLIC3R_REGEX_NAMESPACE::regex_error &ex) { // Syntax error in the regular expression boost::throw_exception(qi::expectation_failure( @@ -620,21 +609,20 @@ namespace client static void logical_op(expr &lhs, expr &rhs, char op) { bool value = false; - if (lhs.type == TYPE_BOOL && rhs.type == TYPE_BOOL) { + if (lhs.type() == TYPE_BOOL && rhs.type() == TYPE_BOOL) { value = (op == '|') ? (lhs.b() || rhs.b()) : (lhs.b() && rhs.b()); } else { boost::throw_exception(qi::expectation_failure( lhs.it_range.begin(), rhs.it_range.end(), spirit::info("*Cannot apply logical operation to non-boolean operators."))); } - lhs.type = TYPE_BOOL; - lhs.data.b = value; + lhs.set_b_lite(value); } static void logical_or (expr &lhs, expr &rhs) { logical_op(lhs, rhs, '|'); } static void logical_and(expr &lhs, expr &rhs) { logical_op(lhs, rhs, '&'); } static void ternary_op(expr &lhs, expr &rhs1, expr &rhs2) { - if (lhs.type != TYPE_BOOL) + if (lhs.type() != TYPE_BOOL) lhs.throw_exception("Not a boolean expression"); if (lhs.b()) lhs = std::move(rhs1); @@ -658,9 +646,25 @@ namespace client void throw_if_not_numeric(const char *message) const { - if (this->type != TYPE_INT && this->type != TYPE_DOUBLE) + if (this->type() != TYPE_INT && this->type() != TYPE_DOUBLE) this->throw_exception(message); } + + private: + // This object will take ownership of the parameter string object "s". + void set_s_take_ownership(std::string* s) { assert(this->type() != TYPE_STRING); Data tmp; tmp.s = s; m_data.set(tmp); m_type = TYPE_STRING; } + + Type m_type = TYPE_EMPTY; + + union Data { + bool b; + int i; + double d; + std::string *s; + + // Copy the largest member variable through char*, which will alias with all other union members by default. + void set(const Data &rhs) { memcpy(this, &rhs, sizeof(rhs)); } + } m_data; }; template @@ -668,7 +672,7 @@ namespace client { typedef expr Expr; os << std::string(expression.it_range.begin(), expression.it_range.end()) << " - "; - switch (expression.type) { + switch (expression.type()) { case Expr::TYPE_EMPTY: os << "empty"; break; case Expr::TYPE_BOOL: os << "bool (" << expression.b() << ")"; break; case Expr::TYPE_INT: os << "int (" << expression.i() << ")"; break; @@ -802,6 +806,7 @@ namespace client case coInt: output.set_i(opt.opt->getInt()); break; case coString: output.set_s(static_cast(opt.opt)->value); break; case coPercent: output.set_d(opt.opt->getFloat()); break; + case coEnum: case coPoint: output.set_s(opt.opt->serialize()); break; case coBool: output.set_b(opt.opt->getBool()); break; case coFloatOrPercent: @@ -869,6 +874,7 @@ namespace client case coPercents: output.set_d(static_cast(opt.opt)->values[idx]); break; case coPoints: output.set_s(to_string(static_cast(opt.opt)->values[idx])); break; case coBools: output.set_b(static_cast(opt.opt)->values[idx] != 0); break; + //case coEnums: output.set_s(opt.opt->vserialize()[idx]); break; default: ctx->throw_exception("Unknown vector variable type", opt.it_range); } @@ -880,7 +886,7 @@ namespace client template static void evaluate_index(expr &expr_index, int &output) { - if (expr_index.type != expr::TYPE_INT) + if (expr_index.type() != expr::TYPE_INT) expr_index.throw_exception("Non-integer index is not allowed to address a vector variable."); output = expr_index.i(); } diff --git a/src/libslic3r/PlaceholderParser.hpp b/src/libslic3r/PlaceholderParser.hpp index 6157ffe3c..fc184be77 100644 --- a/src/libslic3r/PlaceholderParser.hpp +++ b/src/libslic3r/PlaceholderParser.hpp @@ -5,6 +5,7 @@ #include #include #include +#include #include #include "PrintConfig.hpp" @@ -38,6 +39,8 @@ public: // Add new ConfigOption values to m_config. void set(const std::string &key, const std::string &value) { this->set(key, new ConfigOptionString(value)); } + void set(const std::string &key, std::string_view value) { this->set(key, new ConfigOptionString(std::string(value))); } + void set(const std::string &key, const char *value) { this->set(key, new ConfigOptionString(value)); } void set(const std::string &key, int value) { this->set(key, new ConfigOptionInt(value)); } void set(const std::string &key, unsigned int value) { this->set(key, int(value)); } void set(const std::string &key, bool value) { this->set(key, new ConfigOptionBool(value)); } diff --git a/tests/libslic3r/test_placeholder_parser.cpp b/tests/libslic3r/test_placeholder_parser.cpp index abf7308f2..33f5214b0 100644 --- a/tests/libslic3r/test_placeholder_parser.cpp +++ b/tests/libslic3r/test_placeholder_parser.cpp @@ -29,6 +29,7 @@ SCENARIO("Placeholder parser scripting", "[PlaceholderParser]") { parser.set("foo", 0); parser.set("bar", 2); parser.set("num_extruders", 4); + parser.set("gcode_flavor", "marlin"); SECTION("nested config options (legacy syntax)") { REQUIRE(parser.process("[temperature_[foo]]") == "357"); } SECTION("array reference") { REQUIRE(parser.process("{temperature[foo]}") == "357"); } @@ -110,4 +111,5 @@ SCENARIO("Placeholder parser scripting", "[PlaceholderParser]") { SECTION("complex expression") { REQUIRE(boolean_expression("printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK2.*/ and nozzle_diameter[0]==0.6 and num_extruders>1")); } SECTION("complex expression2") { REQUIRE(boolean_expression("printer_notes=~/.*PRINTER_VEwerfNDOR_PRUSA3D.*/ or printer_notes=~/.*PRINTertER_MODEL_MK2.*/ or (nozzle_diameter[0]==0.6 and num_extruders>1)")); } SECTION("complex expression3") { REQUIRE(! boolean_expression("printer_notes=~/.*PRINTER_VEwerfNDOR_PRUSA3D.*/ or printer_notes=~/.*PRINTertER_MODEL_MK2.*/ or (nozzle_diameter[0]==0.3 and num_extruders>1)")); } + SECTION("enum expression") { REQUIRE(boolean_expression("gcode_flavor == \"marlin\"")); } } From f5662458a23fed2b63e3c7084e1f254e3a0db808 Mon Sep 17 00:00:00 2001 From: Vojtech Bubnik Date: Wed, 4 Jan 2023 13:38:59 +0100 Subject: [PATCH 12/48] Removed polygon simplification "hole in square" unit test, simplification of CW contours is no more enabled by assert. --- tests/libslic3r/test_polygon.cpp | 13 ------------- 1 file changed, 13 deletions(-) diff --git a/tests/libslic3r/test_polygon.cpp b/tests/libslic3r/test_polygon.cpp index c1e1c3b73..b2608883c 100644 --- a/tests/libslic3r/test_polygon.cpp +++ b/tests/libslic3r/test_polygon.cpp @@ -196,19 +196,6 @@ SCENARIO("Simplify polygon", "[Polygon]") } } } - GIVEN("hole in square") { - // CW oriented - auto hole_in_square = Polygon{ {140, 140}, {140, 160}, {160, 160}, {160, 140} }; - WHEN("simplified") { - Polygons simplified = hole_in_square.simplify(2.); - THEN("hole simplification returns one polygon") { - REQUIRE(simplified.size() == 1); - } - THEN("hole simplification turns cw polygon into ccw polygon") { - REQUIRE(simplified.front().is_counter_clockwise()); - } - } - } } #include "libslic3r/ExPolygon.hpp" From 479a39ce0ef01156d6ede7f915e9a86d604afb49 Mon Sep 17 00:00:00 2001 From: Vojtech Bubnik Date: Wed, 4 Jan 2023 16:41:42 +0100 Subject: [PATCH 13/48] Fixed some compiler warnings --- src/libslic3r/ClipperZUtils.hpp | 4 +-- src/libslic3r/Fill/FillRectilinear.cpp | 4 +-- src/libslic3r/Fill/FillRectilinear.hpp | 2 +- src/libslic3r/GCode/CoolingBuffer.cpp | 15 ++++++--- src/libslic3r/Geometry/MedialAxis.cpp | 3 -- src/libslic3r/LayerRegion.cpp | 2 +- src/libslic3r/Preset.cpp | 2 +- src/libslic3r/TreeModelVolumes.cpp | 2 +- src/libslic3r/TreeSupport.cpp | 43 ++++++++++++++------------ 9 files changed, 41 insertions(+), 36 deletions(-) diff --git a/src/libslic3r/ClipperZUtils.hpp b/src/libslic3r/ClipperZUtils.hpp index 31f822813..dd42e3d66 100644 --- a/src/libslic3r/ClipperZUtils.hpp +++ b/src/libslic3r/ClipperZUtils.hpp @@ -28,7 +28,7 @@ inline ZPath to_zpath(const Points &path, coord_t z) { ZPath out; if (! path.empty()) { - out.reserve(path.size() + Open ? 1 : 0); + out.reserve((path.size() + Open) ? 1 : 0); for (const Point &p : path) out.emplace_back(p.x(), p.y(), z); if (Open) @@ -75,7 +75,7 @@ inline Points from_zpath(const ZPoints &path) { Points out; if (! path.empty()) { - out.reserve(path.size() + Open ? 1 : 0); + out.reserve((path.size() + Open) ? 1 : 0); for (const ZPoint &p : path) out.emplace_back(p.x(), p.y()); if (Open) diff --git a/src/libslic3r/Fill/FillRectilinear.cpp b/src/libslic3r/Fill/FillRectilinear.cpp index c39ec8b1a..40579fa3f 100644 --- a/src/libslic3r/Fill/FillRectilinear.cpp +++ b/src/libslic3r/Fill/FillRectilinear.cpp @@ -3051,12 +3051,12 @@ ThickPolylines FillBoundedRectilinear::fill_surface_arachne(const Surface *surfa // Create the infills for each of the regions. ThickPolylines thick_polylines_out; for (ExPolygon &ex_poly : expp) - _fill_surface_single(Surface(*surface, std::move(ex_poly)), params, thick_polylines_out); + fill_surface_single_arachne(Surface(*surface, std::move(ex_poly)), params, thick_polylines_out); return thick_polylines_out; } -void FillBoundedRectilinear::_fill_surface_single(const Surface &surface, const FillParams ¶ms, ThickPolylines &thick_polylines_out) +void FillBoundedRectilinear::fill_surface_single_arachne(const Surface &surface, const FillParams ¶ms, ThickPolylines &thick_polylines_out) { assert(params.use_arachne); assert(this->print_config != nullptr && this->print_object_config != nullptr && this->print_region_config != nullptr); diff --git a/src/libslic3r/Fill/FillRectilinear.hpp b/src/libslic3r/Fill/FillRectilinear.hpp index 9f833ea9e..9a022873f 100644 --- a/src/libslic3r/Fill/FillRectilinear.hpp +++ b/src/libslic3r/Fill/FillRectilinear.hpp @@ -119,7 +119,7 @@ public: ThickPolylines fill_surface_arachne(const Surface *surface, const FillParams ¶ms) override; protected: - void _fill_surface_single(const Surface &surface, const FillParams ¶ms, ThickPolylines &thick_polylines_out); + void fill_surface_single_arachne(const Surface &surface, const FillParams ¶ms, ThickPolylines &thick_polylines_out); bool no_sort() const override { return true; } diff --git a/src/libslic3r/GCode/CoolingBuffer.cpp b/src/libslic3r/GCode/CoolingBuffer.cpp index f8e1dc6d7..3b3dff75f 100644 --- a/src/libslic3r/GCode/CoolingBuffer.cpp +++ b/src/libslic3r/GCode/CoolingBuffer.cpp @@ -372,7 +372,8 @@ std::vector CoolingBuffer::parse_layer_gcode(const std:: size_t axis = (*c >= 'X' && *c <= 'Z') ? (*c - 'X') : (*c == extrusion_axis) ? 3 : (*c == 'F') ? 4 : size_t(-1); if (axis != size_t(-1)) { - auto [pend, ec] = fast_float::from_chars(&*(++ c), sline.data() + sline.size(), new_pos[axis]); + //auto [pend, ec] = + fast_float::from_chars(&*(++ c), sline.data() + sline.size(), new_pos[axis]); if (axis == 4) { // Convert mm/min to mm/sec. new_pos[4] /= 60.f; @@ -462,7 +463,8 @@ std::vector CoolingBuffer::parse_layer_gcode(const std:: active_speed_modifier = size_t(-1); } else if (boost::starts_with(sline, m_toolchange_prefix)) { unsigned int new_extruder; - auto res = std::from_chars(sline.data() + m_toolchange_prefix.size(), sline.data() + sline.size(), new_extruder); + //auto res = + std::from_chars(sline.data() + m_toolchange_prefix.size(), sline.data() + sline.size(), new_extruder); // Only change extruder in case the number is meaningful. User could provide an out-of-range index through custom gcodes - those shall be ignored. if (new_extruder < map_extruder_to_per_extruder_adjustment.size()) { if (new_extruder != current_extruder) { @@ -490,7 +492,8 @@ std::vector CoolingBuffer::parse_layer_gcode(const std:: bool has_S = pos_S > 0; bool has_P = pos_P > 0; if (has_S || has_P) { - auto [pend, ec] = fast_float::from_chars(sline.data() + (has_S ? pos_S : pos_P) + 1, sline.data() + sline.size(), line.time); + //auto [pend, ec] = + fast_float::from_chars(sline.data() + (has_S ? pos_S : pos_P) + 1, sline.data() + sline.size(), line.time); if (has_P) line.time *= 0.001f; } else @@ -786,7 +789,8 @@ std::string CoolingBuffer::apply_layer_cooldown( new_gcode.append(pos, line_start - pos); if (line->type & CoolingLine::TYPE_SET_TOOL) { unsigned int new_extruder; - auto res = std::from_chars(line_start + m_toolchange_prefix.size(), line_end, new_extruder); + //auto res = + std::from_chars(line_start + m_toolchange_prefix.size(), line_end, new_extruder); if (new_extruder != m_current_extruder) { m_current_extruder = new_extruder; change_extruder_set_fan(); @@ -815,7 +819,8 @@ std::string CoolingBuffer::apply_layer_cooldown( if (line->slowdown) new_feedrate = int(floor(60. * line->feedrate + 0.5)); else - auto res = std::from_chars(fpos, line_end, new_feedrate); + //auto res = + std::from_chars(fpos, line_end, new_feedrate); if (new_feedrate == current_feedrate) { // No need to change the F value. if ((line->type & (CoolingLine::TYPE_ADJUSTABLE | CoolingLine::TYPE_ADJUSTABLE_EMPTY | CoolingLine::TYPE_EXTERNAL_PERIMETER | CoolingLine::TYPE_WIPE)) || line->length == 0.) diff --git a/src/libslic3r/Geometry/MedialAxis.cpp b/src/libslic3r/Geometry/MedialAxis.cpp index c92796f41..703612152 100644 --- a/src/libslic3r/Geometry/MedialAxis.cpp +++ b/src/libslic3r/Geometry/MedialAxis.cpp @@ -468,9 +468,6 @@ void MedialAxis::build(ThickPolylines* polylines) } */ - //typedef const VD::vertex_type vert_t; - using edge_t = const VD::edge_type; - // collect valid edges (i.e. prune those not belonging to MAT) // note: this keeps twins, so it inserts twice the number of the valid edges m_edge_data.assign(m_vd.edges().size() / 2, EdgeData{}); diff --git a/src/libslic3r/LayerRegion.cpp b/src/libslic3r/LayerRegion.cpp index 5add6a7eb..c26ed7dfd 100644 --- a/src/libslic3r/LayerRegion.cpp +++ b/src/libslic3r/LayerRegion.cpp @@ -253,7 +253,7 @@ Surfaces expand_bridges_detect_orientations( for (; it_bridge_anchor != bridge_anchors.end() && it_bridge_anchor->src == bridge_id; ++ it_bridge_anchor) { if (last_anchor_id != int(it_bridge_anchor->boundary)) { last_anchor_id = int(it_bridge_anchor->boundary); - append(anchor_areas, std::move(to_polygons(shells[last_anchor_id]))); + append(anchor_areas, to_polygons(shells[last_anchor_id])); } // if (Points &polyline = it_bridge_anchor->path; polyline.size() >= 2) { // reserve_more_power_of_2(lines, polyline.size() - 1); diff --git a/src/libslic3r/Preset.cpp b/src/libslic3r/Preset.cpp index 842638efc..2e5871b56 100644 --- a/src/libslic3r/Preset.cpp +++ b/src/libslic3r/Preset.cpp @@ -749,7 +749,7 @@ std::pair PresetCollection::load_external_preset( { // Load the preset over a default preset, so that the missing fields are filled in from the default preset. DynamicPrintConfig cfg(this->default_preset_for(combined_config).config); - t_config_option_keys keys = std::move(cfg.keys()); + t_config_option_keys keys = cfg.keys(); cfg.apply_only(combined_config, keys, true); std::string &inherits = Preset::inherits(cfg); if (select == LoadAndSelect::Never) { diff --git a/src/libslic3r/TreeModelVolumes.cpp b/src/libslic3r/TreeModelVolumes.cpp index b416db634..975510c93 100644 --- a/src/libslic3r/TreeModelVolumes.cpp +++ b/src/libslic3r/TreeModelVolumes.cpp @@ -825,7 +825,7 @@ std::vector>> out; for (auto it = this->data.begin(); it != this->data.end(); ++ it) out.emplace_back(it->first, it->second); - std::sort(out.begin(), out.end(), [](auto &l, auto &r){ return l.first.second < r.first.second || (l.first.second == r.first.second) && l.first.first < r.first.first; }); + std::sort(out.begin(), out.end(), [](auto &l, auto &r){ return l.first.second < r.first.second || (l.first.second == r.first.second && l.first.first < r.first.first); }); return out; } diff --git a/src/libslic3r/TreeSupport.cpp b/src/libslic3r/TreeSupport.cpp index 1f0aa00b2..24db849ef 100644 --- a/src/libslic3r/TreeSupport.cpp +++ b/src/libslic3r/TreeSupport.cpp @@ -2188,11 +2188,9 @@ static void merge_influence_areas( // 1st merge iteration, merge one with each other. tbb::parallel_for(tbb::blocked_range(0, num_buckets_initial), [&](const tbb::blocked_range &range) { - for (size_t idx = range.begin(); idx < range.end(); ++ idx) { - const size_t bucket_pair_idx = idx * 2; + for (size_t idx = range.begin(); idx < range.end(); ++ idx) // Merge bucket_count adjacent to each other, merging uneven bucket numbers into even buckets buckets[idx].second = merge_influence_areas_leaves(volumes, config, layer_idx, buckets[idx].first, buckets[idx].second); - } }); // Further merge iterations, merging one AABB subtree with another one, hopefully minimizing intersections between the elements @@ -3394,8 +3392,6 @@ static void draw_branches( SupportGeneratorLayersPtr &intermediate_layers, SupportGeneratorLayerStorage &layer_storage) { - static int irun = 0; - const SlicingParameters& slicing_params = print_object.slicing_parameters(); // All SupportElements are put into a layer independent storage to improve parallelization. @@ -3420,8 +3416,12 @@ static void draw_branches( // Only one link points to a node above from below. assert(!(++it != map_downwards_old.end() && it->first == &elem)); } - const SupportElement *pchild = child == -1 ? nullptr : &move_bounds[layer_idx - 1][child]; - assert(pchild ? pchild->state.result_on_layer_is_set() : elem.state.target_height > layer_idx); +#ifndef NDEBUG + { + const SupportElement *pchild = child == -1 ? nullptr : &move_bounds[layer_idx - 1][child]; + assert(pchild ? pchild->state.result_on_layer_is_set() : elem.state.target_height > layer_idx); + } +#endif // NDEBUG } for (int32_t parent_idx : elem.parents) { SupportElement &parent = (*layer_above)[parent_idx]; @@ -3581,19 +3581,22 @@ static void draw_branches( partial_mesh.clear(); extrude_branch(path, config, slicing_params, move_bounds, partial_mesh); #if 0 - char fname[2048]; - sprintf(fname, "d:\\temp\\meshes\\tree-raw-%d.obj", ++ irun); - its_write_obj(partial_mesh, fname); -#if 0 - temp_mesh.clear(); - cut_mesh(partial_mesh, layer_z(slicing_params, path.back()->state.layer_idx) + EPSILON, nullptr, &temp_mesh, false); - sprintf(fname, "d:\\temp\\meshes\\tree-trimmed1-%d.obj", irun); - its_write_obj(temp_mesh, fname); - partial_mesh.clear(); - cut_mesh(temp_mesh, layer_z(slicing_params, path.front()->state.layer_idx) - EPSILON, &partial_mesh, nullptr, false); - sprintf(fname, "d:\\temp\\meshes\\tree-trimmed2-%d.obj", irun); -#endif - its_write_obj(partial_mesh, fname); + { + char fname[2048]; + static int irun = 0; + sprintf(fname, "d:\\temp\\meshes\\tree-raw-%d.obj", ++ irun); + its_write_obj(partial_mesh, fname); + #if 0 + temp_mesh.clear(); + cut_mesh(partial_mesh, layer_z(slicing_params, path.back()->state.layer_idx) + EPSILON, nullptr, &temp_mesh, false); + sprintf(fname, "d:\\temp\\meshes\\tree-trimmed1-%d.obj", irun); + its_write_obj(temp_mesh, fname); + partial_mesh.clear(); + cut_mesh(temp_mesh, layer_z(slicing_params, path.front()->state.layer_idx) - EPSILON, &partial_mesh, nullptr, false); + sprintf(fname, "d:\\temp\\meshes\\tree-trimmed2-%d.obj", irun); + #endif + its_write_obj(partial_mesh, fname); + } #endif its_merge(cummulative_mesh, partial_mesh); } From 634859e4a647f9819304617fef9063dd61461980 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luk=C3=A1=C5=A1=20Hejl?= Date: Thu, 5 Jan 2023 14:42:10 +0100 Subject: [PATCH 14/48] Removed unnecessary offsets in BoundedRectilinear infill. --- src/libslic3r/Fill/FillRectilinear.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/libslic3r/Fill/FillRectilinear.cpp b/src/libslic3r/Fill/FillRectilinear.cpp index 40579fa3f..b0c1e5096 100644 --- a/src/libslic3r/Fill/FillRectilinear.cpp +++ b/src/libslic3r/Fill/FillRectilinear.cpp @@ -3047,7 +3047,7 @@ Polylines FillSupportBase::fill_surface(const Surface *surface, const FillParams ThickPolylines FillBoundedRectilinear::fill_surface_arachne(const Surface *surface, const FillParams ¶ms) { // Perform offset. - Slic3r::ExPolygons expp = offset_ex(surface->expolygon, float(scale_(this->overlap - 0.5 * this->spacing))); + Slic3r::ExPolygons expp = this->overlap != 0. ? offset_ex(surface->expolygon, scaled(this->overlap)) : ExPolygons{surface->expolygon}; // Create the infills for each of the regions. ThickPolylines thick_polylines_out; for (ExPolygon &ex_poly : expp) @@ -3062,7 +3062,7 @@ void FillBoundedRectilinear::fill_surface_single_arachne(const Surface &surface, assert(this->print_config != nullptr && this->print_object_config != nullptr && this->print_region_config != nullptr); coord_t scaled_spacing = scaled(this->spacing); - Polygons polygons = offset(surface.expolygon, float(scaled_spacing) / 2.f); + Polygons polygons = to_polygons(surface.expolygon); Arachne::WallToolPaths wall_tool_paths(polygons, scaled_spacing, scaled_spacing, 1, 0, params.layer_height, *this->print_object_config, *this->print_config); if (std::vector loop = wall_tool_paths.getToolPaths(); !loop.empty()) { assert(loop.size() == 1); From 1268856f6a90c1c1b0e5d82fe26da714ab78f548 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luk=C3=A1=C5=A1=20Hejl?= Date: Thu, 5 Jan 2023 15:59:28 +0100 Subject: [PATCH 15/48] Renamed FillBoundedRectilinear to FillEnsuring and moved to separated files. --- src/libslic3r/CMakeLists.txt | 2 + src/libslic3r/Fill/Fill.cpp | 11 +-- src/libslic3r/Fill/FillBase.cpp | 3 +- src/libslic3r/Fill/FillBase.hpp | 2 +- src/libslic3r/Fill/FillEnsuring.cpp | 95 ++++++++++++++++++++++++++ src/libslic3r/Fill/FillEnsuring.hpp | 30 ++++++++ src/libslic3r/Fill/FillRectilinear.cpp | 85 ----------------------- src/libslic3r/Fill/FillRectilinear.hpp | 19 ------ src/libslic3r/PrintConfig.hpp | 2 +- 9 files changed, 137 insertions(+), 112 deletions(-) create mode 100644 src/libslic3r/Fill/FillEnsuring.cpp create mode 100644 src/libslic3r/Fill/FillEnsuring.hpp diff --git a/src/libslic3r/CMakeLists.txt b/src/libslic3r/CMakeLists.txt index bdf057f58..92983a34d 100644 --- a/src/libslic3r/CMakeLists.txt +++ b/src/libslic3r/CMakeLists.txt @@ -72,6 +72,8 @@ set(SLIC3R_SOURCES Fill/FillBase.hpp Fill/FillConcentric.cpp Fill/FillConcentric.hpp + Fill/FillEnsuring.cpp + Fill/FillEnsuring.hpp Fill/FillHoneycomb.cpp Fill/FillHoneycomb.hpp Fill/FillGyroid.cpp diff --git a/src/libslic3r/Fill/Fill.cpp b/src/libslic3r/Fill/Fill.cpp index 1093de1f3..5eee8706f 100644 --- a/src/libslic3r/Fill/Fill.cpp +++ b/src/libslic3r/Fill/Fill.cpp @@ -15,6 +15,7 @@ #include "FillRectilinear.hpp" #include "FillLightning.hpp" #include "FillConcentric.hpp" +#include "FillEnsuring.hpp" namespace Slic3r { @@ -301,10 +302,10 @@ std::vector group_fills(const Layer &layer) } } - // Detect narrow internal solid infill area and use ipBoundedRectilinear pattern instead. + // Detect narrow internal solid infill area and use ipEnsuring pattern instead. { std::vector narrow_expolygons; - static constexpr const auto narrow_pattern = ipBoundedRectilinear; + static constexpr const auto narrow_pattern = ipEnsuring; for (size_t surface_fill_id = 0, num_old_fills = surface_fills.size(); surface_fill_id < num_old_fills; ++ surface_fill_id) if (SurfaceFill &fill = surface_fills[surface_fill_id]; fill.surface.surface_type == stInternalSolid) { size_t num_expolygons = fill.expolygons.size(); @@ -494,8 +495,8 @@ void Layer::make_fills(FillAdaptive::Octree* adaptive_fill_octree, FillAdaptive: if (surface_fill.params.pattern == ipLightning) dynamic_cast(f.get())->generator = lightning_generator; - if (surface_fill.params.pattern == ipBoundedRectilinear) { - auto *fill_bounded_rectilinear = dynamic_cast(f.get()); + if (surface_fill.params.pattern == ipEnsuring) { + auto *fill_bounded_rectilinear = dynamic_cast(f.get()); assert(fill_bounded_rectilinear != nullptr); fill_bounded_rectilinear->print_region_config = &m_regions[surface_fill.region_id]->region().config(); } @@ -527,7 +528,7 @@ void Layer::make_fills(FillAdaptive::Octree* adaptive_fill_octree, FillAdaptive: params.anchor_length = surface_fill.params.anchor_length; params.anchor_length_max = surface_fill.params.anchor_length_max; params.resolution = resolution; - params.use_arachne = (perimeter_generator == PerimeterGeneratorType::Arachne && surface_fill.params.pattern == ipConcentric) || surface_fill.params.pattern == ipBoundedRectilinear; + params.use_arachne = (perimeter_generator == PerimeterGeneratorType::Arachne && surface_fill.params.pattern == ipConcentric) || surface_fill.params.pattern == ipEnsuring; params.layer_height = layerm.layer()->height; for (ExPolygon &expoly : surface_fill.expolygons) { diff --git a/src/libslic3r/Fill/FillBase.cpp b/src/libslic3r/Fill/FillBase.cpp index 0bf3c4dde..c92462148 100644 --- a/src/libslic3r/Fill/FillBase.cpp +++ b/src/libslic3r/Fill/FillBase.cpp @@ -20,6 +20,7 @@ #include "FillRectilinear.hpp" #include "FillAdaptive.hpp" #include "FillLightning.hpp" +#include "FillEnsuring.hpp" #include @@ -49,7 +50,7 @@ Fill* Fill::new_from_type(const InfillPattern type) case ipSupportCubic: return new FillAdaptive::Filler(); case ipSupportBase: return new FillSupportBase(); case ipLightning: return new FillLightning::Filler(); - case ipBoundedRectilinear: return new FillBoundedRectilinear(); + case ipEnsuring: return new FillEnsuring(); default: throw Slic3r::InvalidArgument("unknown type"); } } diff --git a/src/libslic3r/Fill/FillBase.hpp b/src/libslic3r/Fill/FillBase.hpp index 2a9091d35..cf3766758 100644 --- a/src/libslic3r/Fill/FillBase.hpp +++ b/src/libslic3r/Fill/FillBase.hpp @@ -91,7 +91,7 @@ public: // Octree builds on mesh for usage in the adaptive cubic infill FillAdaptive::Octree* adapt_fill_octree = nullptr; - // PrintConfig and PrintObjectConfig are used by infills that use Arachne (Concentric and FillBoundedRectilinear). + // PrintConfig and PrintObjectConfig are used by infills that use Arachne (Concentric and FillEnsuring). const PrintConfig *print_config = nullptr; const PrintObjectConfig *print_object_config = nullptr; diff --git a/src/libslic3r/Fill/FillEnsuring.cpp b/src/libslic3r/Fill/FillEnsuring.cpp new file mode 100644 index 000000000..60b92d16f --- /dev/null +++ b/src/libslic3r/Fill/FillEnsuring.cpp @@ -0,0 +1,95 @@ +#include "../ClipperUtils.hpp" +#include "../ShortestPath.hpp" +#include "../Arachne/WallToolPaths.hpp" + +#include "FillEnsuring.hpp" + +#include + +namespace Slic3r { + +ThickPolylines FillEnsuring::fill_surface_arachne(const Surface *surface, const FillParams ¶ms) +{ + // Perform offset. + Slic3r::ExPolygons expp = this->overlap != 0. ? offset_ex(surface->expolygon, scaled(this->overlap)) : ExPolygons{surface->expolygon}; + // Create the infills for each of the regions. + ThickPolylines thick_polylines_out; + for (ExPolygon &ex_poly : expp) + fill_surface_single_arachne(Surface(*surface, std::move(ex_poly)), params, thick_polylines_out); + + return thick_polylines_out; +} + +void FillEnsuring::fill_surface_single_arachne(const Surface &surface, const FillParams ¶ms, ThickPolylines &thick_polylines_out) +{ + assert(params.use_arachne); + assert(this->print_config != nullptr && this->print_object_config != nullptr && this->print_region_config != nullptr); + + coord_t scaled_spacing = scaled(this->spacing); + Polygons polygons = to_polygons(surface.expolygon); + Arachne::WallToolPaths wall_tool_paths(polygons, scaled_spacing, scaled_spacing, 1, 0, params.layer_height, *this->print_object_config, *this->print_config); + if (std::vector loop = wall_tool_paths.getToolPaths(); !loop.empty()) { + assert(loop.size() == 1); + + size_t firts_poly_idx = thick_polylines_out.size(); + Point last_pos(0, 0); + for (const Arachne::ExtrusionLine &extrusion : loop.front()) { + if (extrusion.empty()) + continue; + + ThickPolyline thick_polyline = Arachne::to_thick_polyline(extrusion); + if (thick_polyline.length() == 0.) + //FIXME this should not happen. + continue; + assert(thick_polyline.size() > 1); + assert(thick_polyline.length() > 0.); + //assert(thick_polyline.points.size() == thick_polyline.width.size()); + if (extrusion.is_closed) + thick_polyline.start_at_index(nearest_point_index(thick_polyline.points, last_pos)); + + assert(thick_polyline.size() > 1); + //assert(thick_polyline.points.size() == thick_polyline.width.size()); + thick_polylines_out.emplace_back(std::move(thick_polyline)); + last_pos = thick_polylines_out.back().last_point(); + } + + // clip the paths to prevent the extruder from getting exactly on the first point of the loop + // Keep valid paths only. + size_t j = firts_poly_idx; + for (size_t i = firts_poly_idx; i < thick_polylines_out.size(); ++i) { + assert(thick_polylines_out[i].size() > 1); + assert(thick_polylines_out[i].length() > 0.); + //assert(thick_polylines_out[i].points.size() == thick_polylines_out[i].width.size()); + thick_polylines_out[i].clip_end(this->loop_clipping); + assert(thick_polylines_out[i].size() > 1); + if (thick_polylines_out[i].is_valid()) { + if (j < i) + thick_polylines_out[j] = std::move(thick_polylines_out[i]); + ++j; + } + } + if (j < thick_polylines_out.size()) + thick_polylines_out.erase(thick_polylines_out.begin() + int(j), thick_polylines_out.end()); + } + + // Remaining infill area will be filled with classic Rectilinear infill. + ExPolygons infill_contour = union_ex(wall_tool_paths.getInnerContour()); + if (offset_ex(infill_contour, -float(scaled_spacing / 2.)).empty()) + infill_contour.clear(); // Infill region is too small, so let's filter it out. + + Polygons pp; + for (ExPolygon &ex : infill_contour) + ex.simplify_p(scaled(params.resolution), &pp); + + // Collapse too narrow infill areas and append them to thick_polylines_out. + const auto min_perimeter_infill_spacing = coord_t(scaled_spacing * (1. - INSET_OVERLAP_TOLERANCE)); + const auto infill_overlap = coord_t(scale_(this->print_region_config->get_abs_value("infill_overlap", this->spacing))); + for (ExPolygon &ex_poly : offset2_ex(union_ex(pp), float(-min_perimeter_infill_spacing / 2.), float(infill_overlap + min_perimeter_infill_spacing / 2.))) { + Polylines polylines; + if (Surface new_surface(surface, std::move(ex_poly)); !fill_surface_by_lines(&new_surface, params, 0.f, 0.f, polylines)) + BOOST_LOG_TRIVIAL(error) << "FillEnsuring::fill_surface() failed to fill a region."; + append(thick_polylines_out, to_thick_polylines(std::move(polylines), scaled(this->spacing))); + } +} + +} // namespace Slic3r diff --git a/src/libslic3r/Fill/FillEnsuring.hpp b/src/libslic3r/Fill/FillEnsuring.hpp new file mode 100644 index 000000000..faa080153 --- /dev/null +++ b/src/libslic3r/Fill/FillEnsuring.hpp @@ -0,0 +1,30 @@ +#ifndef slic3r_FillEnsuring_hpp_ +#define slic3r_FillEnsuring_hpp_ + +#include "FillBase.hpp" +#include "FillRectilinear.hpp" + +namespace Slic3r { + +class FillEnsuring : public FillRectilinear +{ +public: + Fill *clone() const override { return new FillEnsuring(*this); } + ~FillEnsuring() override = default; + Polylines fill_surface(const Surface *surface, const FillParams ¶ms) override { return {}; }; + ThickPolylines fill_surface_arachne(const Surface *surface, const FillParams ¶ms) override; + +protected: + void fill_surface_single_arachne(const Surface &surface, const FillParams ¶ms, ThickPolylines &thick_polylines_out); + + bool no_sort() const override { return true; } + + // PrintRegionConfig is used for computing overlap between boundary contour and inner Rectilinear infill. + const PrintRegionConfig *print_region_config = nullptr; + + friend class Layer; +}; + +} // namespace Slic3r + +#endif // slic3r_FillEnsuring_hpp_ diff --git a/src/libslic3r/Fill/FillRectilinear.cpp b/src/libslic3r/Fill/FillRectilinear.cpp index b0c1e5096..bb93d824b 100644 --- a/src/libslic3r/Fill/FillRectilinear.cpp +++ b/src/libslic3r/Fill/FillRectilinear.cpp @@ -15,7 +15,6 @@ #include "../Geometry.hpp" #include "../Surface.hpp" #include "../ShortestPath.hpp" -#include "../Arachne/WallToolPaths.hpp" #include "FillRectilinear.hpp" @@ -3044,90 +3043,6 @@ Polylines FillSupportBase::fill_surface(const Surface *surface, const FillParams return polylines_out; } -ThickPolylines FillBoundedRectilinear::fill_surface_arachne(const Surface *surface, const FillParams ¶ms) -{ - // Perform offset. - Slic3r::ExPolygons expp = this->overlap != 0. ? offset_ex(surface->expolygon, scaled(this->overlap)) : ExPolygons{surface->expolygon}; - // Create the infills for each of the regions. - ThickPolylines thick_polylines_out; - for (ExPolygon &ex_poly : expp) - fill_surface_single_arachne(Surface(*surface, std::move(ex_poly)), params, thick_polylines_out); - - return thick_polylines_out; -} - -void FillBoundedRectilinear::fill_surface_single_arachne(const Surface &surface, const FillParams ¶ms, ThickPolylines &thick_polylines_out) -{ - assert(params.use_arachne); - assert(this->print_config != nullptr && this->print_object_config != nullptr && this->print_region_config != nullptr); - - coord_t scaled_spacing = scaled(this->spacing); - Polygons polygons = to_polygons(surface.expolygon); - Arachne::WallToolPaths wall_tool_paths(polygons, scaled_spacing, scaled_spacing, 1, 0, params.layer_height, *this->print_object_config, *this->print_config); - if (std::vector loop = wall_tool_paths.getToolPaths(); !loop.empty()) { - assert(loop.size() == 1); - - size_t firts_poly_idx = thick_polylines_out.size(); - Point last_pos(0, 0); - for (const Arachne::ExtrusionLine &extrusion : loop.front()) { - if (extrusion.empty()) - continue; - - ThickPolyline thick_polyline = Arachne::to_thick_polyline(extrusion); - if (thick_polyline.length() == 0.) - //FIXME this should not happen. - continue; - assert(thick_polyline.size() > 1); - assert(thick_polyline.length() > 0.); - //assert(thick_polyline.points.size() == thick_polyline.width.size()); - if (extrusion.is_closed) - thick_polyline.start_at_index(nearest_point_index(thick_polyline.points, last_pos)); - - assert(thick_polyline.size() > 1); - //assert(thick_polyline.points.size() == thick_polyline.width.size()); - thick_polylines_out.emplace_back(std::move(thick_polyline)); - last_pos = thick_polylines_out.back().last_point(); - } - - // clip the paths to prevent the extruder from getting exactly on the first point of the loop - // Keep valid paths only. - size_t j = firts_poly_idx; - for (size_t i = firts_poly_idx; i < thick_polylines_out.size(); ++i) { - assert(thick_polylines_out[i].size() > 1); - assert(thick_polylines_out[i].length() > 0.); - //assert(thick_polylines_out[i].points.size() == thick_polylines_out[i].width.size()); - thick_polylines_out[i].clip_end(this->loop_clipping); - assert(thick_polylines_out[i].size() > 1); - if (thick_polylines_out[i].is_valid()) { - if (j < i) - thick_polylines_out[j] = std::move(thick_polylines_out[i]); - ++j; - } - } - if (j < thick_polylines_out.size()) - thick_polylines_out.erase(thick_polylines_out.begin() + int(j), thick_polylines_out.end()); - } - - // Remaining infill area will be filled with classic Rectilinear infill. - ExPolygons infill_contour = union_ex(wall_tool_paths.getInnerContour()); - if (offset_ex(infill_contour, -float(scaled_spacing / 2.)).empty()) - infill_contour.clear(); // Infill region is too small, so let's filter it out. - - Polygons pp; - for (ExPolygon &ex : infill_contour) - ex.simplify_p(scaled(params.resolution), &pp); - - // Collapse too narrow infill areas and append them to thick_polylines_out. - const auto min_perimeter_infill_spacing = coord_t(scaled_spacing * (1. - INSET_OVERLAP_TOLERANCE)); - const auto infill_overlap = coord_t(scale_(this->print_region_config->get_abs_value("infill_overlap", this->spacing))); - for (ExPolygon &ex_poly : offset2_ex(union_ex(pp), float(-min_perimeter_infill_spacing / 2.), float(infill_overlap + min_perimeter_infill_spacing / 2.))) { - Polylines polylines; - if (Surface new_surface(surface, std::move(ex_poly)); !fill_surface_by_lines(&new_surface, params, 0.f, 0.f, polylines)) - BOOST_LOG_TRIVIAL(error) << "FillBoundedRectilinear::fill_surface() failed to fill a region."; - append(thick_polylines_out, to_thick_polylines(std::move(polylines), scaled(this->spacing))); - } -} - // Lightning infill assumes that the distance between any two sampled points is always // at least equal to the value of spacing. To meet this assumption, we need to use // BoundingBox for whole layers instead of bounding box just around processing ExPolygon. diff --git a/src/libslic3r/Fill/FillRectilinear.hpp b/src/libslic3r/Fill/FillRectilinear.hpp index 9a022873f..3ba582304 100644 --- a/src/libslic3r/Fill/FillRectilinear.hpp +++ b/src/libslic3r/Fill/FillRectilinear.hpp @@ -110,25 +110,6 @@ protected: float _layer_angle(size_t idx) const override { return 0.f; } }; -class FillBoundedRectilinear : public FillRectilinear -{ -public: - Fill *clone() const override { return new FillBoundedRectilinear(*this); } - ~FillBoundedRectilinear() override = default; - Polylines fill_surface(const Surface *surface, const FillParams ¶ms) override { return {}; }; - ThickPolylines fill_surface_arachne(const Surface *surface, const FillParams ¶ms) override; - -protected: - void fill_surface_single_arachne(const Surface &surface, const FillParams ¶ms, ThickPolylines &thick_polylines_out); - - bool no_sort() const override { return true; } - - // PrintRegionConfig is used for computing overlap between boundary contour and inner Rectilinear infill. - const PrintRegionConfig *print_region_config = nullptr; - - friend class Layer; -}; - Points sample_grid_pattern(const ExPolygon &expolygon, coord_t spacing, const BoundingBox &global_bounding_box); Points sample_grid_pattern(const ExPolygons &expolygons, coord_t spacing, const BoundingBox &global_bounding_box); Points sample_grid_pattern(const Polygons &polygons, coord_t spacing, const BoundingBox &global_bounding_box); diff --git a/src/libslic3r/PrintConfig.hpp b/src/libslic3r/PrintConfig.hpp index edfac5f4f..a253b95a5 100644 --- a/src/libslic3r/PrintConfig.hpp +++ b/src/libslic3r/PrintConfig.hpp @@ -60,7 +60,7 @@ enum InfillPattern : int { ipRectilinear, ipMonotonic, ipAlignedRectilinear, ipGrid, ipTriangles, ipStars, ipCubic, ipLine, ipConcentric, ipHoneycomb, ip3DHoneycomb, ipGyroid, ipHilbertCurve, ipArchimedeanChords, ipOctagramSpiral, ipAdaptiveCubic, ipSupportCubic, ipSupportBase, ipLightning, - ipBoundedRectilinear, + ipEnsuring, ipCount, }; From df019236319172172907e8a486ab6123c648b0b4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luk=C3=A1=C5=A1=20Hejl?= Date: Fri, 6 Jan 2023 09:10:13 +0100 Subject: [PATCH 16/48] Refactored FillEsuring to support switching between BoundedRectilinear and Concentric infill for the ensure vertical shell thickness. --- src/libslic3r/Fill/FillEnsuring.cpp | 158 +++++++++++++++------------- 1 file changed, 84 insertions(+), 74 deletions(-) diff --git a/src/libslic3r/Fill/FillEnsuring.cpp b/src/libslic3r/Fill/FillEnsuring.cpp index 60b92d16f..5e3787543 100644 --- a/src/libslic3r/Fill/FillEnsuring.cpp +++ b/src/libslic3r/Fill/FillEnsuring.cpp @@ -10,86 +10,96 @@ namespace Slic3r { ThickPolylines FillEnsuring::fill_surface_arachne(const Surface *surface, const FillParams ¶ms) { + assert(params.use_arachne); + assert(this->print_config != nullptr && this->print_object_config != nullptr && this->print_region_config != nullptr); + + const coord_t scaled_spacing = scaled(this->spacing); + const bool is_bounded_rectilinear = true; + // Perform offset. Slic3r::ExPolygons expp = this->overlap != 0. ? offset_ex(surface->expolygon, scaled(this->overlap)) : ExPolygons{surface->expolygon}; // Create the infills for each of the regions. ThickPolylines thick_polylines_out; - for (ExPolygon &ex_poly : expp) - fill_surface_single_arachne(Surface(*surface, std::move(ex_poly)), params, thick_polylines_out); + for (ExPolygon &ex_poly : expp) { + Point bbox_size = ex_poly.contour.bounding_box().size(); + coord_t loops_count = is_bounded_rectilinear ? 1 : std::max(bbox_size.x(), bbox_size.y()) / scaled_spacing + 1; + Polygons polygons = to_polygons(ex_poly); + Arachne::WallToolPaths wall_tool_paths(polygons, scaled_spacing, scaled_spacing, loops_count, 0, params.layer_height, *this->print_object_config, *this->print_config); + if (std::vector loops = wall_tool_paths.getToolPaths(); !loops.empty()) { + assert(!is_bounded_rectilinear || loops.size() == 1); + std::vector all_extrusions; + for (Arachne::VariableWidthLines &loop : loops) { + if (loop.empty()) + continue; + for (const Arachne::ExtrusionLine &wall : loop) + all_extrusions.emplace_back(&wall); + } + + // Split paths using a nearest neighbor search. + size_t firts_poly_idx = thick_polylines_out.size(); + Point last_pos(0, 0); + for (const Arachne::ExtrusionLine *extrusion : all_extrusions) { + if (extrusion->empty()) + continue; + + ThickPolyline thick_polyline = Arachne::to_thick_polyline(*extrusion); + if (thick_polyline.length() == 0.) + //FIXME this should not happen. + continue; + assert(thick_polyline.size() > 1); + assert(thick_polyline.length() > 0.); + //assert(thick_polyline.points.size() == thick_polyline.width.size()); + if (extrusion->is_closed) + thick_polyline.start_at_index(nearest_point_index(thick_polyline.points, last_pos)); + + assert(thick_polyline.size() > 1); + //assert(thick_polyline.points.size() == thick_polyline.width.size()); + thick_polylines_out.emplace_back(std::move(thick_polyline)); + last_pos = thick_polylines_out.back().last_point(); + } + + // clip the paths to prevent the extruder from getting exactly on the first point of the loop + // Keep valid paths only. + size_t j = firts_poly_idx; + for (size_t i = firts_poly_idx; i < thick_polylines_out.size(); ++i) { + assert(thick_polylines_out[i].size() > 1); + assert(thick_polylines_out[i].length() > 0.); + //assert(thick_polylines_out[i].points.size() == thick_polylines_out[i].width.size()); + thick_polylines_out[i].clip_end(this->loop_clipping); + assert(thick_polylines_out[i].size() > 1); + if (thick_polylines_out[i].is_valid()) { + if (j < i) + thick_polylines_out[j] = std::move(thick_polylines_out[i]); + ++j; + } + } + if (j < thick_polylines_out.size()) + thick_polylines_out.erase(thick_polylines_out.begin() + int(j), thick_polylines_out.end()); + } + + if (is_bounded_rectilinear) { + // Remaining infill area will be filled with classic Rectilinear infill. + ExPolygons infill_contour = union_ex(wall_tool_paths.getInnerContour()); + if (offset_ex(infill_contour, -float(scaled_spacing / 2.)).empty()) + infill_contour.clear(); // Infill region is too small, so let's filter it out. + + Polygons pp; + for (ExPolygon &ex : infill_contour) + ex.simplify_p(scaled(params.resolution), &pp); + + // Collapse too narrow infill areas and append them to thick_polylines_out. + const auto min_perimeter_infill_spacing = coord_t(scaled_spacing * (1. - INSET_OVERLAP_TOLERANCE)); + const auto infill_overlap = coord_t(scale_(this->print_region_config->get_abs_value("infill_overlap", this->spacing))); + for (ExPolygon &ex_poly : offset2_ex(union_ex(pp), float(-min_perimeter_infill_spacing / 2.), float(infill_overlap + min_perimeter_infill_spacing / 2.))) { + Polylines polylines; + if (Surface new_surface(*surface, std::move(ex_poly)); !fill_surface_by_lines(&new_surface, params, 0.f, 0.f, polylines)) + BOOST_LOG_TRIVIAL(error) << "FillBoundedRectilinear::fill_surface() failed to fill a region."; + append(thick_polylines_out, to_thick_polylines(std::move(polylines), scaled(this->spacing))); + } + } + } return thick_polylines_out; } -void FillEnsuring::fill_surface_single_arachne(const Surface &surface, const FillParams ¶ms, ThickPolylines &thick_polylines_out) -{ - assert(params.use_arachne); - assert(this->print_config != nullptr && this->print_object_config != nullptr && this->print_region_config != nullptr); - - coord_t scaled_spacing = scaled(this->spacing); - Polygons polygons = to_polygons(surface.expolygon); - Arachne::WallToolPaths wall_tool_paths(polygons, scaled_spacing, scaled_spacing, 1, 0, params.layer_height, *this->print_object_config, *this->print_config); - if (std::vector loop = wall_tool_paths.getToolPaths(); !loop.empty()) { - assert(loop.size() == 1); - - size_t firts_poly_idx = thick_polylines_out.size(); - Point last_pos(0, 0); - for (const Arachne::ExtrusionLine &extrusion : loop.front()) { - if (extrusion.empty()) - continue; - - ThickPolyline thick_polyline = Arachne::to_thick_polyline(extrusion); - if (thick_polyline.length() == 0.) - //FIXME this should not happen. - continue; - assert(thick_polyline.size() > 1); - assert(thick_polyline.length() > 0.); - //assert(thick_polyline.points.size() == thick_polyline.width.size()); - if (extrusion.is_closed) - thick_polyline.start_at_index(nearest_point_index(thick_polyline.points, last_pos)); - - assert(thick_polyline.size() > 1); - //assert(thick_polyline.points.size() == thick_polyline.width.size()); - thick_polylines_out.emplace_back(std::move(thick_polyline)); - last_pos = thick_polylines_out.back().last_point(); - } - - // clip the paths to prevent the extruder from getting exactly on the first point of the loop - // Keep valid paths only. - size_t j = firts_poly_idx; - for (size_t i = firts_poly_idx; i < thick_polylines_out.size(); ++i) { - assert(thick_polylines_out[i].size() > 1); - assert(thick_polylines_out[i].length() > 0.); - //assert(thick_polylines_out[i].points.size() == thick_polylines_out[i].width.size()); - thick_polylines_out[i].clip_end(this->loop_clipping); - assert(thick_polylines_out[i].size() > 1); - if (thick_polylines_out[i].is_valid()) { - if (j < i) - thick_polylines_out[j] = std::move(thick_polylines_out[i]); - ++j; - } - } - if (j < thick_polylines_out.size()) - thick_polylines_out.erase(thick_polylines_out.begin() + int(j), thick_polylines_out.end()); - } - - // Remaining infill area will be filled with classic Rectilinear infill. - ExPolygons infill_contour = union_ex(wall_tool_paths.getInnerContour()); - if (offset_ex(infill_contour, -float(scaled_spacing / 2.)).empty()) - infill_contour.clear(); // Infill region is too small, so let's filter it out. - - Polygons pp; - for (ExPolygon &ex : infill_contour) - ex.simplify_p(scaled(params.resolution), &pp); - - // Collapse too narrow infill areas and append them to thick_polylines_out. - const auto min_perimeter_infill_spacing = coord_t(scaled_spacing * (1. - INSET_OVERLAP_TOLERANCE)); - const auto infill_overlap = coord_t(scale_(this->print_region_config->get_abs_value("infill_overlap", this->spacing))); - for (ExPolygon &ex_poly : offset2_ex(union_ex(pp), float(-min_perimeter_infill_spacing / 2.), float(infill_overlap + min_perimeter_infill_spacing / 2.))) { - Polylines polylines; - if (Surface new_surface(surface, std::move(ex_poly)); !fill_surface_by_lines(&new_surface, params, 0.f, 0.f, polylines)) - BOOST_LOG_TRIVIAL(error) << "FillEnsuring::fill_surface() failed to fill a region."; - append(thick_polylines_out, to_thick_polylines(std::move(polylines), scaled(this->spacing))); - } -} - } // namespace Slic3r From 063ae0ccfc2ef3972588fbc533fb58ff60e44d01 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luk=C3=A1=C5=A1=20Hejl?= Date: Thu, 5 Jan 2023 14:37:05 +0100 Subject: [PATCH 17/48] Added option to switch between BoundedRectilinear and Concentric infill for the ensure vertical shell thickness. --- src/libslic3r/Fill/FillEnsuring.cpp | 8 +++++--- src/libslic3r/Preset.cpp | 2 +- src/libslic3r/PrintConfig.cpp | 18 ++++++++++++++++++ src/libslic3r/PrintConfig.hpp | 8 ++++++++ src/libslic3r/PrintObject.cpp | 3 ++- src/slic3r/GUI/Tab.cpp | 1 + 6 files changed, 35 insertions(+), 5 deletions(-) diff --git a/src/libslic3r/Fill/FillEnsuring.cpp b/src/libslic3r/Fill/FillEnsuring.cpp index 5e3787543..a501f9732 100644 --- a/src/libslic3r/Fill/FillEnsuring.cpp +++ b/src/libslic3r/Fill/FillEnsuring.cpp @@ -13,8 +13,9 @@ ThickPolylines FillEnsuring::fill_surface_arachne(const Surface *surface, const assert(params.use_arachne); assert(this->print_config != nullptr && this->print_object_config != nullptr && this->print_region_config != nullptr); - const coord_t scaled_spacing = scaled(this->spacing); - const bool is_bounded_rectilinear = true; + const coord_t scaled_spacing = scaled(this->spacing); + const EnsuringInfillPattern infill_patter = this->print_object_config->ensure_vertical_shell_infill; + const bool is_bounded_rectilinear = infill_patter == EnsuringInfillPattern::eipBoundedRectilinear; // Perform offset. Slic3r::ExPolygons expp = this->overlap != 0. ? offset_ex(surface->expolygon, scaled(this->overlap)) : ExPolygons{surface->expolygon}; @@ -96,7 +97,8 @@ ThickPolylines FillEnsuring::fill_surface_arachne(const Surface *surface, const BOOST_LOG_TRIVIAL(error) << "FillBoundedRectilinear::fill_surface() failed to fill a region."; append(thick_polylines_out, to_thick_polylines(std::move(polylines), scaled(this->spacing))); } - } + } else + assert(infill_patter == EnsuringInfillPattern::eipConcentric); } return thick_polylines_out; diff --git a/src/libslic3r/Preset.cpp b/src/libslic3r/Preset.cpp index 2e5871b56..51bd25fb3 100644 --- a/src/libslic3r/Preset.cpp +++ b/src/libslic3r/Preset.cpp @@ -449,7 +449,7 @@ static std::vector s_Preset_print_options { "wipe_tower_width", "wipe_tower_rotation_angle", "wipe_tower_brim_width", "wipe_tower_bridging", "single_extruder_multi_material_priming", "mmu_segmented_region_max_width", "wipe_tower_no_sparse_layers", "compatible_printers", "compatible_printers_condition", "inherits", "perimeter_generator", "wall_transition_length", "wall_transition_filter_deviation", "wall_transition_angle", - "wall_distribution_count", "min_feature_size", "min_bead_width" + "wall_distribution_count", "min_feature_size", "min_bead_width", "ensure_vertical_shell_infill" }; static std::vector s_Preset_filament_options { diff --git a/src/libslic3r/PrintConfig.cpp b/src/libslic3r/PrintConfig.cpp index fd752873d..9b5bc3e67 100644 --- a/src/libslic3r/PrintConfig.cpp +++ b/src/libslic3r/PrintConfig.cpp @@ -220,6 +220,12 @@ static t_config_enum_values s_keys_map_PerimeterGeneratorType { }; CONFIG_OPTION_ENUM_DEFINE_STATIC_MAPS(PerimeterGeneratorType) +static t_config_enum_values s_keys_map_EnsuringInfillPattern { + { "bounded_rectilinear", int(EnsuringInfillPattern::eipBoundedRectilinear) }, + { "concentric", int(EnsuringInfillPattern::eipConcentric) } +}; +CONFIG_OPTION_ENUM_DEFINE_STATIC_MAPS(EnsuringInfillPattern) + static void assign_printer_technology_to_unknown(t_optiondef_map &options, PrinterTechnology printer_technology) { for (std::pair &kvp : options) @@ -3214,6 +3220,18 @@ void PrintConfigDef::init_fff_params() def->min = 0; def->set_default_value(new ConfigOptionFloatOrPercent(85, true)); + def = this->add("ensure_vertical_shell_infill", coEnum); + def->label = L("Ensure vertical shell infill"); + def->category = L("Layers and Perimeters"); + def->tooltip = L("Ensure vertical shell infill."); + def->enum_keys_map = &ConfigOptionEnum::get_enum_values(); + def->enum_values.push_back("bounded_rectilinear"); + def->enum_values.push_back("concentric"); + def->enum_labels.push_back(L("Bounded Rectilinear")); + def->enum_labels.push_back(L("Concentric")); + def->mode = comAdvanced; + def->set_default_value(new ConfigOptionEnum(EnsuringInfillPattern::eipBoundedRectilinear)); + // Declare retract values for filament profile, overriding the printer's extruder profile. for (const char *opt_key : { // floats diff --git a/src/libslic3r/PrintConfig.hpp b/src/libslic3r/PrintConfig.hpp index a253b95a5..c8cab5ada 100644 --- a/src/libslic3r/PrintConfig.hpp +++ b/src/libslic3r/PrintConfig.hpp @@ -133,6 +133,11 @@ enum class PerimeterGeneratorType Arachne }; +enum class EnsuringInfillPattern { + eipBoundedRectilinear, + eipConcentric +}; + enum class GCodeThumbnailsFormat { PNG, JPG, QOI }; @@ -162,6 +167,8 @@ CONFIG_OPTION_ENUM_DECLARE_STATIC_MAPS(DraftShield) CONFIG_OPTION_ENUM_DECLARE_STATIC_MAPS(GCodeThumbnailsFormat) CONFIG_OPTION_ENUM_DECLARE_STATIC_MAPS(ForwardCompatibilitySubstitutionRule) CONFIG_OPTION_ENUM_DECLARE_STATIC_MAPS(PerimeterGeneratorType) +CONFIG_OPTION_ENUM_DECLARE_STATIC_MAPS(EnsuringInfillPattern) + #undef CONFIG_OPTION_ENUM_DECLARE_STATIC_MAPS @@ -490,6 +497,7 @@ PRINT_CONFIG_CLASS_DEFINE( ((ConfigOptionBool, clip_multipart_objects)) ((ConfigOptionBool, dont_support_bridges)) ((ConfigOptionFloat, elefant_foot_compensation)) + ((ConfigOptionEnum, ensure_vertical_shell_infill)) ((ConfigOptionFloatOrPercent, extrusion_width)) ((ConfigOptionFloat, first_layer_acceleration_over_raft)) ((ConfigOptionFloatOrPercent, first_layer_speed_over_raft)) diff --git a/src/libslic3r/PrintObject.cpp b/src/libslic3r/PrintObject.cpp index 85e70779c..b2ce77c7d 100644 --- a/src/libslic3r/PrintObject.cpp +++ b/src/libslic3r/PrintObject.cpp @@ -751,7 +751,8 @@ bool PrintObject::invalidate_state_by_config_options( || opt_key == "wall_transition_angle" || opt_key == "wall_distribution_count" || opt_key == "min_feature_size" - || opt_key == "min_bead_width") { + || opt_key == "min_bead_width" + || opt_key == "ensure_vertical_shell_infill") { steps.emplace_back(posSlice); } else if ( opt_key == "seam_position" diff --git a/src/slic3r/GUI/Tab.cpp b/src/slic3r/GUI/Tab.cpp index cf3b96d80..0e6e4c354 100644 --- a/src/slic3r/GUI/Tab.cpp +++ b/src/slic3r/GUI/Tab.cpp @@ -1425,6 +1425,7 @@ void TabPrint::build() optgroup->append_single_option_line("extra_perimeters", category_path + "extra-perimeters-if-needed"); optgroup->append_single_option_line("extra_perimeters_on_overhangs", category_path + "extra-perimeters-on-overhangs"); optgroup->append_single_option_line("ensure_vertical_shell_thickness", category_path + "ensure-vertical-shell-thickness"); + optgroup->append_single_option_line("ensure_vertical_shell_infill"); optgroup->append_single_option_line("avoid_curled_filament_during_travels", category_path + "avoid-curled-filament-during-travels"); optgroup->append_single_option_line("avoid_crossing_perimeters", category_path + "avoid-crossing-perimeters"); optgroup->append_single_option_line("avoid_crossing_perimeters_max_detour", category_path + "avoid_crossing_perimeters_max_detour"); From 1a91d85e7e6c69b30f0719e045a3ed3a04f54f00 Mon Sep 17 00:00:00 2001 From: Vojtech Bubnik Date: Fri, 6 Jan 2023 17:53:49 +0100 Subject: [PATCH 18/48] Fixes of recent RegionExpansion implementation. Enabled thick internal bridges even if external thick bridges are disabled. Fixed compilation of conditionally compiled debugging code. --- src/libslic3r/Algorithm/RegionExpansion.cpp | 24 +++---- src/libslic3r/ClipperZUtils.hpp | 9 ++- src/libslic3r/LayerRegion.cpp | 14 ++-- src/libslic3r/PrintObject.cpp | 74 +++++---------------- src/libslic3r/SupportMaterial.cpp | 2 +- src/libslic3r/TriangleMeshSlicer.cpp | 22 +++--- 6 files changed, 53 insertions(+), 92 deletions(-) diff --git a/src/libslic3r/Algorithm/RegionExpansion.cpp b/src/libslic3r/Algorithm/RegionExpansion.cpp index 3846c4749..844cda822 100644 --- a/src/libslic3r/Algorithm/RegionExpansion.cpp +++ b/src/libslic3r/Algorithm/RegionExpansion.cpp @@ -76,12 +76,11 @@ RegionExpansionParameters RegionExpansionParameters::build( // similar to expolygons_to_zpaths(), but each contour is expanded before converted to zpath. // The expanded contours are then opened (the first point is repeated at the end). static ClipperLib_Z::Paths expolygons_to_zpaths_expanded_opened( - const ExPolygons &src, const float expansion, coord_t base_idx) + const ExPolygons &src, const float expansion, coord_t &base_idx) { ClipperLib_Z::Paths out; out.reserve(2 * std::accumulate(src.begin(), src.end(), size_t(0), [](const size_t acc, const ExPolygon &expoly) { return acc + expoly.num_contours(); })); - coord_t z = base_idx; ClipperLib::ClipperOffset offsetter; offsetter.ShortestEdgeLength = expansion * ClipperOffsetShortestEdgeFactor; ClipperLib::Paths expansion_cache; @@ -94,9 +93,9 @@ static ClipperLib_Z::Paths expolygons_to_zpaths_expanded_opened( offsetter.AddPath(expoly.contour_or_hole(icontour).points, ClipperLib::jtSquare, ClipperLib::etClosedPolygon); expansion_cache.clear(); offsetter.Execute(expansion_cache, icontour == 0 ? expansion : -expansion); - append(out, ClipperZUtils::to_zpaths(expansion_cache, z)); + append(out, ClipperZUtils::to_zpaths(expansion_cache, base_idx)); } - ++ z; + ++ base_idx; } return out; } @@ -217,7 +216,7 @@ std::vector wave_seeds( Intersections intersections; coord_t idx_boundary_begin = 1; - coord_t idx_boundary_end; + coord_t idx_boundary_end = idx_boundary_begin; coord_t idx_src_end; { @@ -225,17 +224,13 @@ std::vector wave_seeds( ClipperZUtils::ClipperZIntersectionVisitor visitor(intersections); zclipper.ZFillFunction(visitor.clipper_callback()); // as closed contours - { - ClipperLib_Z::Paths zboundary = ClipperZUtils::expolygons_to_zpaths(boundary, idx_boundary_begin); - idx_boundary_end = idx_boundary_begin + coord_t(zboundary.size()); - zclipper.AddPaths(zboundary, ClipperLib_Z::ptClip, true); - } + zclipper.AddPaths(ClipperZUtils::expolygons_to_zpaths(boundary, idx_boundary_end), ClipperLib_Z::ptClip, true); // as open contours std::vector> zsrc_splits; { - ClipperLib_Z::Paths zsrc = expolygons_to_zpaths_expanded_opened(src, tiny_expansion, idx_boundary_end); + idx_src_end = idx_boundary_end; + ClipperLib_Z::Paths zsrc = expolygons_to_zpaths_expanded_opened(src, tiny_expansion, idx_src_end); zclipper.AddPaths(zsrc, ClipperLib_Z::ptSubject, false); - idx_src_end = idx_boundary_end + coord_t(zsrc.size()); zsrc_splits.reserve(zsrc.size()); for (const ClipperLib_Z::Path &path : zsrc) { assert(path.size() >= 2); @@ -267,7 +262,10 @@ std::vector wave_seeds( const ClipperLib_Z::IntPoint &back = path.back(); // Both ends of a seed segment are supposed to be inside a single boundary expolygon. // Thus as long as the seed contour is not closed, it should be open at a boundary point. - assert((front == back && front.z() >= idx_boundary_end && front.z() < idx_src_end) || (front.z() < 0 && back.z() < 0)); + assert((front == back && front.z() >= idx_boundary_end && front.z() < idx_src_end) || + //(front.z() < 0 && back.z() < 0)); + // Hope that at least one end of an open polyline is clipped by the boundary, thus an intersection point is created. + (front.z() < 0 || back.z() < 0)); const Intersection *intersection = nullptr; auto intersection_point_valid = [idx_boundary_end, idx_src_end](const Intersection &is) { return is.first >= 1 && is.first < idx_boundary_end && diff --git a/src/libslic3r/ClipperZUtils.hpp b/src/libslic3r/ClipperZUtils.hpp index dd42e3d66..4ae78ae23 100644 --- a/src/libslic3r/ClipperZUtils.hpp +++ b/src/libslic3r/ClipperZUtils.hpp @@ -53,17 +53,16 @@ inline ZPaths to_zpaths(const std::vector &paths, coord_t z) // offsetted by base_index. // If Open, then duplicate the first point of each path at its end. template -inline ZPaths expolygons_to_zpaths(const ExPolygons &src, coord_t base_idx) +inline ZPaths expolygons_to_zpaths(const ExPolygons &src, coord_t &base_idx) { ZPaths out; out.reserve(std::accumulate(src.begin(), src.end(), size_t(0), [](const size_t acc, const ExPolygon &expoly) { return acc + expoly.num_contours(); })); - coord_t z = base_idx; for (const ExPolygon &expoly : src) { - out.emplace_back(to_zpath(expoly.contour.points, z)); + out.emplace_back(to_zpath(expoly.contour.points, base_idx)); for (const Polygon &hole : expoly.holes) - out.emplace_back(to_zpath(hole.points, z)); - ++ z; + out.emplace_back(to_zpath(hole.points, base_idx)); + ++ base_idx; } return out; } diff --git a/src/libslic3r/LayerRegion.cpp b/src/libslic3r/LayerRegion.cpp index c26ed7dfd..0777c5ef7 100644 --- a/src/libslic3r/LayerRegion.cpp +++ b/src/libslic3r/LayerRegion.cpp @@ -215,19 +215,19 @@ Surfaces expand_bridges_detect_orientations( // bridge_expansions are sorted by boundary id and source id. for (auto it = bridge_expansions.begin(); it != bridge_expansions.end();) { // For each boundary region: - auto it2 = it; - for (++ it2; it2 != bridge_expansions.end() && it2->boundary_id == it->boundary_id; ++ it2); + auto it_begin = it; + auto it_end = std::next(it_begin); + for (; it_end != bridge_expansions.end() && it_end->boundary_id == it_begin->boundary_id; ++ it_end) ; bboxes.clear(); - bboxes.reserve(it2 - it); - for (it2 = it; it2 != bridge_expansions.end() && it2->boundary_id == it->boundary_id; ++ it2) + bboxes.reserve(it_end - it_begin); + for (auto it2 = it_begin; it2 != it_end; ++ it2) bboxes.emplace_back(get_extents(it2->expolygon.contour)); - auto it_end = it2; // For each bridge anchor of the current source: for (; it != it_end; ++ it) { // A grup id for this bridge. - for (it2 = std::next(it); it2 != it_end; ++ it2) + for (auto it2 = std::next(it); it2 != it_end; ++ it2) if (it->src_id != it2->src_id && - bboxes[it - bridge_expansions.begin()].overlap(bboxes[it2 - bridge_expansions.begin()]) && + bboxes[it - it_begin].overlap(bboxes[it2 - it_begin]) && // One may ignore holes, they are irrelevant for intersection test. ! intersection(it->expolygon.contour, it2->expolygon.contour).empty()) { // The two bridge regions intersect. Give them the same group id. diff --git a/src/libslic3r/PrintObject.cpp b/src/libslic3r/PrintObject.cpp index 85e70779c..7d97ec256 100644 --- a/src/libslic3r/PrintObject.cpp +++ b/src/libslic3r/PrintObject.cpp @@ -964,9 +964,9 @@ void PrintObject::detect_surfaces_type() { static int iRun = 0; std::vector> expolygons_with_attributes; - expolygons_with_attributes.emplace_back(std::make_pair(union_ex(top), SVG::ExPolygonAttributes("green"))); - expolygons_with_attributes.emplace_back(std::make_pair(union_ex(bottom), SVG::ExPolygonAttributes("brown"))); - expolygons_with_attributes.emplace_back(std::make_pair(to_expolygons(layerm->slices.surfaces), SVG::ExPolygonAttributes("black"))); + expolygons_with_attributes.emplace_back(std::make_pair(union_ex(top), SVG::ExPolygonAttributes("green"))); + expolygons_with_attributes.emplace_back(std::make_pair(union_ex(bottom), SVG::ExPolygonAttributes("brown"))); + expolygons_with_attributes.emplace_back(std::make_pair(to_expolygons(layerm->slices().surfaces), SVG::ExPolygonAttributes("black"))); SVG::export_expolygons(debug_out_path("1_detect_surfaces_type_%d_region%d-layer_%f.svg", iRun ++, region_id, layer->print_z).c_str(), expolygons_with_attributes); } #endif /* SLIC3R_DEBUG_SLICE_PROCESSING */ @@ -1399,26 +1399,26 @@ 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(stInternal), "yellow", 0.5); + svg.draw_outline(layerm->fill_surfaces().filter_by_type(stInternal), "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(stInternalVoid), "yellow", 0.5); + svg.draw_outline(layerm->fill_surfaces().filter_by_type(stInternalVoid), "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)); + 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(shell_ex, "blue", 0.5); - svg.draw_outline(shell_ex, "black", "blue", scale_(0.05)); + svg.draw_outline(shell_ex, "black", "blue", scale_(0.05)); svg.Close(); } #endif /* SLIC3R_DEBUG_SLICE_PROCESSING */ @@ -1544,7 +1544,7 @@ void PrintObject::bridge_over_infill() internals.reserve(this->layer_count()); for (Layer *layer : m_layers) { Polygons sum; - for (const LayerRegion *layerm : layer->m_regions) + for (const LayerRegion *layerm : layer->regions()) layerm->fill_surfaces().filter_by_type(stInternal, &sum); internals.emplace_back(std::move(sum)); } @@ -1558,7 +1558,7 @@ void PrintObject::bridge_over_infill() const size_t region_id = sparse_infill_regions[task_id % sparse_infill_regions.size()]; Layer *layer = this->get_layer(layer_id); LayerRegion *layerm = layer->m_regions[region_id]; - Flow bridge_flow = layerm->bridging_flow(frSolidInfill); + Flow bridge_flow = layerm->bridging_flow(frSolidInfill, true /* Internal bridges are always thick. */); // Extract the stInternalSolid surfaces that might be transformed into bridges. ExPolygons internal_solid; @@ -1567,32 +1567,27 @@ void PrintObject::bridge_over_infill() // No internal solid -> no new bridges for this layer region. continue; - // check whether the lower area is deep enough for absorbing the extra flow - // (for obvious physical reasons but also for preventing the bridge extrudates - // from overflowing in 3D preview) + // Check whether the lower area is deep enough for absorbing the extra flow, also filter out + // tiny regions from bridging. ExPolygons to_bridge; { Polygons to_bridge_pp = to_polygons(internal_solid); // Iterate through lower layers spanned by bridge_flow. double bottom_z = layer->print_z - bridge_flow.height() - EPSILON; - for (auto i = int(layer_id) - 1; i >= 0; -- i) { - // Stop iterating if layer is lower than bottom_z. - if (m_layers[i]->print_z < bottom_z) - break; + for (auto i = int(layer_id) - 1; i >= 0 && m_layers[i]->print_z > bottom_z; -- i) // Intersect lower sparse infills with the candidate solid surfaces. to_bridge_pp = intersection(to_bridge_pp, internals[i]); - } // there's no point in bridging too thin/short regions //FIXME Vojtech: The offset2 function is not a geometric offset, // therefore it may create 1) gaps, and 2) sharp corners, which are outside the original contour. // The gaps will be filled by a separate region, which makes the infill less stable and it takes longer. { float min_width = float(bridge_flow.scaled_width()) * 3.f; - to_bridge_pp = opening(to_bridge_pp, min_width); + to_bridge_pp = opening(to_bridge_pp, min_width); //, ClipperLib::jtSquare); } if (to_bridge_pp.empty()) { - // Restore internal_solid surfaces. + // Optimization: Nothing to bridge, restore internal_solid surfaces. for (ExPolygon &ex : internal_solid) layerm->m_fill_surfaces.surfaces.push_back(Surface(stInternalSolid, std::move(ex))); continue; @@ -1613,39 +1608,6 @@ void PrintObject::bridge_over_infill() layerm->m_fill_surfaces.surfaces.push_back(Surface(stInternalBridge, std::move(ex))); for (ExPolygon &ex : not_to_bridge) layerm->m_fill_surfaces.surfaces.push_back(Surface(stInternalSolid, std::move(ex))); - /* - # exclude infill from the layers below if needed - # see discussion at https://github.com/alexrj/Slic3r/issues/240 - # Update: do not exclude any infill. Sparse infill is able to absorb the excess material. - if (0) { - my $excess = $layerm->extruders->{infill}->bridge_flow->width - $layerm->height; - for (my $i = $layer_id-1; $excess >= $self->get_layer($i)->height; $i--) { - Slic3r::debugf " skipping infill below those areas at layer %d\n", $i; - foreach my $lower_layerm (@{$self->get_layer($i)->regions}) { - my @new_surfaces = (); - # subtract the area from all types of surfaces - foreach my $group (@{$lower_layerm->fill_surfaces->group}) { - push @new_surfaces, map $group->[0]->clone(expolygon => $_), - @{diff_ex( - [ map $_->p, @$group ], - [ map @$_, @$to_bridge ], - )}; - push @new_surfaces, map Slic3r::Surface->new( - expolygon => $_, - surface_type => stInternalVoid, - ), @{intersection_ex( - [ map $_->p, @$group ], - [ map @$_, @$to_bridge ], - )}; - } - $lower_layerm->fill_surfaces->clear; - $lower_layerm->fill_surfaces->append($_) for @new_surfaces; - } - - $excess -= $self->get_layer($i)->height; - } - } - */ #ifdef SLIC3R_DEBUG_SLICE_PROCESSING layerm->export_region_slices_to_svg_debug("7_bridge_over_infill"); layerm->export_region_fill_surfaces_to_svg_debug("7_bridge_over_infill"); diff --git a/src/libslic3r/SupportMaterial.cpp b/src/libslic3r/SupportMaterial.cpp index d46094f7f..af3f4f88a 100644 --- a/src/libslic3r/SupportMaterial.cpp +++ b/src/libslic3r/SupportMaterial.cpp @@ -1320,7 +1320,7 @@ namespace SupportMaterialInternal { bridges = union_(bridges); } // remove the entire bridges and only support the unsupported edges - //FIXME the brided regions are already collected as layerm.bridged. Use it? + //FIXME the bridged regions are already collected as layerm.bridged. Use it? for (const Surface &surface : layerm.fill_surfaces()) if (surface.surface_type == stBottomBridge && surface.bridge_angle < 0.0) polygons_append(bridges, surface.expolygon); diff --git a/src/libslic3r/TriangleMeshSlicer.cpp b/src/libslic3r/TriangleMeshSlicer.cpp index 85e170bd0..96f61ba8d 100644 --- a/src/libslic3r/TriangleMeshSlicer.cpp +++ b/src/libslic3r/TriangleMeshSlicer.cpp @@ -62,6 +62,8 @@ public: // Inherits coord_t x, y }; +#define DEBUG_INTERSECTIONLINE (! defined(NDEBUG) || defined(SLIC3R_DEBUG_SLICE_PROCESSING)) + class IntersectionLine : public Line { public: @@ -119,14 +121,14 @@ public: }; uint32_t flags { 0 }; -#ifndef NDEBUG +#if DEBUG_INTERSECTIONLINE enum class Source { BottomPlane, TopPlane, Slab, }; Source source { Source::BottomPlane }; -#endif // NDEBUG +#endif }; using IntersectionLines = std::vector; @@ -1440,24 +1442,24 @@ static std::vector make_slab_loops( for (const IntersectionLine &l : lines.at_slice[slice_below]) if (l.edge_type != IntersectionLine::FacetEdgeType::Top) { in.emplace_back(l); -#ifndef NDEBUG +#if DEBUG_INTERSECTIONLINE in.back().source = IntersectionLine::Source::BottomPlane; -#endif // NDEBUG +#endif // DEBUG_INTERSECTIONLINE } } { // Edges in between slice_below and slice_above. -#ifndef NDEBUG +#if DEBUG_INTERSECTIONLINE size_t old_size = in.size(); -#endif // NDEBUG +#endif // DEBUG_INTERSECTIONLINE // Edge IDs of end points on in-between lines that touch the layer above are already increased with num_edges. append(in, lines.between_slices[line_idx]); -#ifndef NDEBUG +#if DEBUG_INTERSECTIONLINE for (auto it = in.begin() + old_size; it != in.end(); ++ it) { assert(it->edge_type == IntersectionLine::FacetEdgeType::Slab); it->source = IntersectionLine::Source::Slab; } -#endif // NDEBUG +#endif // DEBUG_INTERSECTIONLINE } if (has_slice_above) { for (const IntersectionLine &lsrc : lines.at_slice[slice_above]) @@ -1470,9 +1472,9 @@ static std::vector make_slab_loops( l.edge_a_id += num_edges; if (l.edge_b_id >= 0) l.edge_b_id += num_edges; -#ifndef NDEBUG +#if DEBUG_INTERSECTIONLINE l.source = IntersectionLine::Source::TopPlane; -#endif // NDEBUG +#endif // DEBUG_INTERSECTIONLINE } } if (! in.empty()) { From a7a54f9386e754729a85a8075d7eab05e344c72e Mon Sep 17 00:00:00 2001 From: Vojtech Bubnik Date: Fri, 6 Jan 2023 18:31:48 +0100 Subject: [PATCH 19/48] Experiment: Added a rectilinear monotonic infill without perimeter connection lines for top / bottom infill patterns. Co-authored-by: lane.wei --- src/libslic3r/Fill/Fill.cpp | 9 +++++++-- src/libslic3r/Fill/FillBase.cpp | 1 + src/libslic3r/Fill/FillRectilinear.cpp | 13 ++++++++++++- src/libslic3r/Fill/FillRectilinear.hpp | 9 +++++++++ src/libslic3r/PrintConfig.cpp | 26 ++++++++++++-------------- src/libslic3r/PrintConfig.hpp | 2 +- 6 files changed, 42 insertions(+), 18 deletions(-) diff --git a/src/libslic3r/Fill/Fill.cpp b/src/libslic3r/Fill/Fill.cpp index 5eee8706f..7ff34d40c 100644 --- a/src/libslic3r/Fill/Fill.cpp +++ b/src/libslic3r/Fill/Fill.cpp @@ -113,6 +113,11 @@ struct SurfaceFill { SurfaceFillParams params; }; +static inline bool fill_type_monotonic(InfillPattern pattern) +{ + return pattern == ipMonotonic || pattern == ipMonotonicLines; +} + std::vector group_fills(const Layer &layer) { std::vector surface_fills; @@ -141,7 +146,7 @@ std::vector group_fills(const Layer &layer) //FIXME for non-thick bridges, shall we allow a bottom surface pattern? params.pattern = (surface.is_external() && ! is_bridge) ? (surface.is_top() ? region_config.top_fill_pattern.value : region_config.bottom_fill_pattern.value) : - region_config.top_fill_pattern == ipMonotonic ? ipMonotonic : ipRectilinear; + fill_type_monotonic(region_config.top_fill_pattern) ? ipMonotonic : ipRectilinear; } else if (params.density <= 0) continue; @@ -284,7 +289,7 @@ std::vector group_fills(const Layer &layer) if (internal_solid_fill == nullptr) { // Produce another solid fill. params.extruder = layerm.region().extruder(frSolidInfill); - params.pattern = layerm.region().config().top_fill_pattern == ipMonotonic ? ipMonotonic : ipRectilinear; + params.pattern = fill_type_monotonic(layerm.region().config().top_fill_pattern) ? ipMonotonic : ipRectilinear; params.density = 100.f; params.extrusion_role = erInternalInfill; params.angle = float(Geometry::deg2rad(layerm.region().config().fill_angle.value)); diff --git a/src/libslic3r/Fill/FillBase.cpp b/src/libslic3r/Fill/FillBase.cpp index c92462148..f55420c31 100644 --- a/src/libslic3r/Fill/FillBase.cpp +++ b/src/libslic3r/Fill/FillBase.cpp @@ -38,6 +38,7 @@ Fill* Fill::new_from_type(const InfillPattern type) case ipRectilinear: return new FillRectilinear(); case ipAlignedRectilinear: return new FillAlignedRectilinear(); case ipMonotonic: return new FillMonotonic(); + case ipMonotonicLines: return new FillMonotonicLines(); case ipLine: return new FillLine(); case ipGrid: return new FillGrid(); case ipTriangles: return new FillTriangles(); diff --git a/src/libslic3r/Fill/FillRectilinear.cpp b/src/libslic3r/Fill/FillRectilinear.cpp index bb93d824b..e27017b03 100644 --- a/src/libslic3r/Fill/FillRectilinear.cpp +++ b/src/libslic3r/Fill/FillRectilinear.cpp @@ -2970,7 +2970,18 @@ Polylines FillMonotonic::fill_surface(const Surface *surface, const FillParams & params2.monotonic = true; Polylines polylines_out; if (! fill_surface_by_lines(surface, params2, 0.f, 0.f, polylines_out)) - BOOST_LOG_TRIVIAL(error) << "FillMonotonous::fill_surface() failed to fill a region."; + BOOST_LOG_TRIVIAL(error) << "FillMonotonic::fill_surface() failed to fill a region."; + return polylines_out; +} + +Polylines FillMonotonicLines::fill_surface(const Surface *surface, const FillParams ¶ms) +{ + FillParams params2 = params; + params2.monotonic = true; + params2.anchor_length_max = 0.0f; + Polylines polylines_out; + if (! fill_surface_by_lines(surface, params2, 0.f, 0.f, polylines_out)) + BOOST_LOG_TRIVIAL(error) << "FillMonotonicLines::fill_surface() failed to fill a region."; return polylines_out; } diff --git a/src/libslic3r/Fill/FillRectilinear.hpp b/src/libslic3r/Fill/FillRectilinear.hpp index 3ba582304..aa5c014b3 100644 --- a/src/libslic3r/Fill/FillRectilinear.hpp +++ b/src/libslic3r/Fill/FillRectilinear.hpp @@ -50,6 +50,15 @@ public: bool no_sort() const override { return true; } }; +class FillMonotonicLines : public FillRectilinear +{ +public: + Fill* clone() const override { return new FillMonotonicLines(*this); } + ~FillMonotonicLines() override = default; + Polylines fill_surface(const Surface *surface, const FillParams ¶ms) override; + bool no_sort() const override { return true; } +}; + class FillGrid : public FillRectilinear { public: diff --git a/src/libslic3r/PrintConfig.cpp b/src/libslic3r/PrintConfig.cpp index 7a9ec51c0..3b7bfddbf 100644 --- a/src/libslic3r/PrintConfig.cpp +++ b/src/libslic3r/PrintConfig.cpp @@ -96,6 +96,7 @@ CONFIG_OPTION_ENUM_DEFINE_STATIC_MAPS(FuzzySkinType) static const t_config_enum_values s_keys_map_InfillPattern { { "rectilinear", ipRectilinear }, { "monotonic", ipMonotonic }, + { "monotoniclines", ipMonotonicLines }, { "alignedrectilinear", ipAlignedRectilinear }, { "grid", ipGrid }, { "triangles", ipTriangles }, @@ -769,20 +770,17 @@ void PrintConfigDef::init_fff_params() def->tooltip = L("Fill pattern for top infill. This only affects the top visible layer, and not its adjacent solid shells."); def->cli = "top-fill-pattern|external-fill-pattern|solid-fill-pattern"; def->enum_keys_map = &ConfigOptionEnum::get_enum_values(); - def->enum_values.push_back("rectilinear"); - def->enum_values.push_back("monotonic"); - def->enum_values.push_back("alignedrectilinear"); - def->enum_values.push_back("concentric"); - def->enum_values.push_back("hilbertcurve"); - def->enum_values.push_back("archimedeanchords"); - def->enum_values.push_back("octagramspiral"); - def->enum_labels.push_back(L("Rectilinear")); - def->enum_labels.push_back(L("Monotonic")); - def->enum_labels.push_back(L("Aligned Rectilinear")); - def->enum_labels.push_back(L("Concentric")); - def->enum_labels.push_back(L("Hilbert Curve")); - def->enum_labels.push_back(L("Archimedean Chords")); - def->enum_labels.push_back(L("Octagram Spiral")); + def->set_enum_values({ + { "rectilinear", L("Rectilinear") }, + { "monotonic", L("Monotonic") }, + { "monotoniclines", L("Monotonic Lines") }, + { "alignedrectilinear", L("Aligned Rectilinear") }, + { "concentric", L("Concentric") }, + { "hilbertcurve", L("Hilbert Curve") }, + { "archimedeanchords", L("Archimedean Chords") }, + { "octagramspiral", L("Octagram Spiral") } + }); + // solid_fill_pattern is an obsolete equivalent to top_fill_pattern/bottom_fill_pattern. def->aliases = { "solid_fill_pattern", "external_fill_pattern" }; def->set_default_value(new ConfigOptionEnum(ipMonotonic)); diff --git a/src/libslic3r/PrintConfig.hpp b/src/libslic3r/PrintConfig.hpp index c8cab5ada..3c737778a 100644 --- a/src/libslic3r/PrintConfig.hpp +++ b/src/libslic3r/PrintConfig.hpp @@ -57,7 +57,7 @@ enum class FuzzySkinType { }; enum InfillPattern : int { - ipRectilinear, ipMonotonic, ipAlignedRectilinear, ipGrid, ipTriangles, ipStars, ipCubic, ipLine, ipConcentric, ipHoneycomb, ip3DHoneycomb, + ipRectilinear, ipMonotonic, ipMonotonicLines, ipAlignedRectilinear, ipGrid, ipTriangles, ipStars, ipCubic, ipLine, ipConcentric, ipHoneycomb, ip3DHoneycomb, ipGyroid, ipHilbertCurve, ipArchimedeanChords, ipOctagramSpiral, ipAdaptiveCubic, ipSupportCubic, ipSupportBase, ipLightning, ipEnsuring, From 7f80f7456bcd8ef6e495435ca51587ca75839073 Mon Sep 17 00:00:00 2001 From: Vojtech Bubnik Date: Fri, 17 Feb 2023 15:18:44 +0100 Subject: [PATCH 20/48] Merged with master --- src/libslic3r/SupportMaterial.cpp | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/libslic3r/SupportMaterial.cpp b/src/libslic3r/SupportMaterial.cpp index 836dc8ac9..3215c9d6a 100644 --- a/src/libslic3r/SupportMaterial.cpp +++ b/src/libslic3r/SupportMaterial.cpp @@ -799,10 +799,6 @@ public: ) { switch (m_style) { - case smsTree: - case smsOrganic: - assert(false); - [[fallthrough]]; case smsGrid: { #ifdef SUPPORT_USE_AGG_RASTERIZER @@ -893,6 +889,10 @@ public: polygons_rotate(out, m_support_angle); return out; } + case smsTree: + case smsOrganic: +// assert(false); + [[fallthrough]]; case smsSnug: // Merge the support polygons by applying morphological closing and inwards smoothing. auto closing_distance = scaled(m_support_material_closing_radius); @@ -1763,7 +1763,7 @@ static inline void fill_contact_layer( #endif // SLIC3R_DEBUG )); // 2) infill polygons, expand them by half the extrusion width + a tiny bit of extra. - bool reduce_interfaces = object_config.support_material_style.value != smsSnug && layer_id > 0 && !slicing_params.soluble_interface; + bool reduce_interfaces = object_config.support_material_style.value == smsGrid && layer_id > 0 && !slicing_params.soluble_interface; if (reduce_interfaces) { // Reduce the amount of dense interfaces: Do not generate dense interfaces below overhangs with 60% overhang of the extrusions. Polygons dense_interface_polygons = diff(overhang_polygons, lower_layer_polygons_for_dense_interface()); @@ -3045,7 +3045,7 @@ std::pair PrintObjectSuppo m_object_config->support_material_interface_extruder.value > 0 && m_print_config->filament_soluble.get_at(m_object_config->support_material_interface_extruder.value - 1) && // Base extruder: Either "print with active extruder" not soluble. (m_object_config->support_material_extruder.value == 0 || ! m_print_config->filament_soluble.get_at(m_object_config->support_material_extruder.value - 1)); - bool snug_supports = m_object_config->support_material_style.value == smsSnug; + bool snug_supports = m_object_config->support_material_style.value != smsGrid; int num_interface_layers_top = m_object_config->support_material_interface_layers; int num_interface_layers_bottom = m_object_config->support_material_bottom_interface_layers; if (num_interface_layers_bottom < 0) @@ -4227,7 +4227,7 @@ void generate_support_toolpaths( } // Insert the raft base layers. - size_t n_raft_layers = size_t(std::max(0, int(slicing_params.raft_layers()) - 1)); + size_t n_raft_layers = std::min(support_layers.size(), std::max(0, int(slicing_params.raft_layers()) - 1)); tbb::parallel_for(tbb::blocked_range(0, n_raft_layers), [&support_layers, &raft_layers, &config, &support_params, &slicing_params, &bbox_object, raft_angle_1st_layer, raft_angle_base, raft_angle_interface, link_max_length_factor] @@ -4356,7 +4356,7 @@ void generate_support_toolpaths( { SupportLayer &support_layer = *support_layers[support_layer_id]; LayerCache &layer_cache = layer_caches[support_layer_id]; - float interface_angle_delta = config.support_material_style.value == smsSnug || config.support_material_style.value == smsTree || config.support_material_style.value == smsOrganic ? + float interface_angle_delta = config.support_material_style.value != smsGrid ? (support_layer.interface_id() & 1) ? float(- M_PI / 4.) : float(+ M_PI / 4.) : 0; From 696b316e2bda294558f802801b4bd7c799714b9c Mon Sep 17 00:00:00 2001 From: PavelMikus Date: Wed, 8 Feb 2023 19:10:58 +0100 Subject: [PATCH 21/48] initial implementation, trying to filter out surfaces that need expansion --- src/libslic3r/BridgeDetector.hpp | 6 ++ src/libslic3r/PrintObject.cpp | 85 ++++++++++++++++++++++++- src/libslic3r/SupportSpotsGenerator.hpp | 1 + 3 files changed, 91 insertions(+), 1 deletion(-) diff --git a/src/libslic3r/BridgeDetector.hpp b/src/libslic3r/BridgeDetector.hpp index bc5da9712..bb1c1ead3 100644 --- a/src/libslic3r/BridgeDetector.hpp +++ b/src/libslic3r/BridgeDetector.hpp @@ -126,6 +126,12 @@ inline std::tuple detect_bridging_direction(const Polygons &to_co }; +inline Vec2d detect_internal_bridge_direction() { + //TODO AABB tree, get nearest point on the infill polylines (or perimeters) and do not take duplicates + // this could yield some density map where the more scattered points are more interesting +} + + } #endif diff --git a/src/libslic3r/PrintObject.cpp b/src/libslic3r/PrintObject.cpp index c4a5946e8..ab8fa5dd9 100644 --- a/src/libslic3r/PrintObject.cpp +++ b/src/libslic3r/PrintObject.cpp @@ -27,9 +27,13 @@ #include #include +#include #include +#include +#include #include #include +#include #include #include @@ -1522,7 +1526,86 @@ void PrintObject::discover_vertical_shells() // This method applies bridge flow to the first internal solid layer above sparse infill. void PrintObject::bridge_over_infill() { - BOOST_LOG_TRIVIAL(info) << "Bridge over infill..." << log_memory_info(); + BOOST_LOG_TRIVIAL(info) << "Bridge over infill - Start" << log_memory_info(); + + tbb::parallel_for(tbb::blocked_range(0, this->layers().size()), [po = this](tbb::blocked_range r) { + for (size_t lidx = r.begin(); lidx < r.end(); lidx++) { + + const Layer *layer = po->get_layer(lidx); + + // gather also sparse infill surfaces on this layer, to which we can expand the bridges for anchoring + // gather potential internal bridging surfaces for the current layer + // pair of LayerSlice idx and surfaces. The LayerSlice idx simplifies the processing, since we cannot expand beyond it + std::unordered_map bridging_surface_candidates; + std::unordered_map expansion_space; + std::unordered_map max_bridge_flow_height; + for (const LayerSlice &slice : layer->lslices_ex) { + std::unordered_set regions_to_check; + for (const LayerIsland &island : slice.islands) { + regions_to_check.insert(island.perimeters.region()); + if (!island.fill_expolygons_composite()) { + regions_to_check.insert(island.fill_region_id); + } + } + + for (size_t region_idx : regions_to_check) { + const LayerRegion *region = layer->get_region(region_idx); + auto region_internal_solids = region->fill_surfaces().filter_by_type(stInternalSolid); + if (!region_internal_solids.empty()) { + max_bridge_flow_height[&slice] = std::max(max_bridge_flow_height[&slice], + region->bridging_flow(frSolidInfill).height()); + } + bridging_surface_candidates[&slice].insert(bridging_surface_candidates[&slice].end(), region_internal_solids.begin(), + region_internal_solids.end()); + auto region_sparse_infill = region->fill_surfaces().filter_by_type(stInternal); + expansion_space[&slice].insert(expansion_space[&slice].end(), region_sparse_infill.begin(), region_sparse_infill.end()); + } + } + + for (const std::pair &candidates : bridging_surface_candidates) { + if (candidates.second.empty()) { + continue; + }; + + // Gather lower layers sparse infill areas, to depth defined by used bridge flow + Polygons lower_layers_sparse_infill; + double bottom_z = layer->print_z - max_bridge_flow_height[candidates.first] - EPSILON; + LayerSlice::Links current_links = candidates.first->overlaps_below; + for (auto i = int(lidx) - 1; i >= 0; --i) { + // Stop iterating if layer is lower than bottom_z. + if (po->get_layer(i)->print_z < bottom_z) + break; + for (const auto &link : current_links) { + const LayerSlice& slice_below = po->get_layer(i)->lslices_ex[link.slice_idx]; + for (const LayerRegion *region : po->get_layer(i)->regions()) { + for (const Surface *surface : region->fill_surfaces().filter_by_type(stInternal)) { + Polygons p = to_polygons(surface->expolygon); + lower_layers_sparse_infill.insert(lower_layers_sparse_infill.end(), p.begin(), p.end()); + } + } + } + } + lower_layers_sparse_infill = union_(lower_layers_sparse_infill); + } + + // Now, temporarily fill the previous layer and extract the extrusions. + po->get_layer(lidx)->lower_layer->make_fills(nullptr, nullptr, nullptr); + Polylines lower_layer_polylines; + for (const LayerRegion *region : layer->lower_layer->m_regions) { + for (const ExtrusionEntity *ee : region->fills().entities) { + assert(ee->is_collection()); + auto region_polylines = dynamic_cast(ee)->as_polylines(); + lower_layer_polylines.insert(lower_layer_polylines.end(), region_polylines.begin(), region_polylines.end()); + } + } + } + }); + + BOOST_LOG_TRIVIAL(info) << "Bridge over infill - End" << log_memory_info(); + + + + std::vector sparse_infill_regions; for (size_t region_id = 0; region_id < this->num_printing_regions(); ++ region_id) diff --git a/src/libslic3r/SupportSpotsGenerator.hpp b/src/libslic3r/SupportSpotsGenerator.hpp index 018b856e0..543b9c92b 100644 --- a/src/libslic3r/SupportSpotsGenerator.hpp +++ b/src/libslic3r/SupportSpotsGenerator.hpp @@ -145,6 +145,7 @@ struct PartialObject using PartialObjects = std::vector; +// Both support points and partial objects are sorted from the lowest z to the highest std::tuple full_search(const PrintObject *po, const PrintTryCancel& cancel_func, const Params ¶ms); void estimate_supports_malformations(std::vector &layers, float supports_flow_width, const Params ¶ms); From fd81d7a46094de9884f6a3066d796c5ff150e45f Mon Sep 17 00:00:00 2001 From: PavelMikus Date: Thu, 9 Feb 2023 16:41:18 +0100 Subject: [PATCH 22/48] Compute bridging direction. Now what is left is to stretch the bridge lines across infill and cut them to ideal length --- src/libslic3r/BridgeDetector.hpp | 6 -- src/libslic3r/PrintObject.cpp | 165 +++++++++++++++++++++++++------ 2 files changed, 134 insertions(+), 37 deletions(-) diff --git a/src/libslic3r/BridgeDetector.hpp b/src/libslic3r/BridgeDetector.hpp index bb1c1ead3..bc5da9712 100644 --- a/src/libslic3r/BridgeDetector.hpp +++ b/src/libslic3r/BridgeDetector.hpp @@ -126,12 +126,6 @@ inline std::tuple detect_bridging_direction(const Polygons &to_co }; -inline Vec2d detect_internal_bridge_direction() { - //TODO AABB tree, get nearest point on the infill polylines (or perimeters) and do not take duplicates - // this could yield some density map where the more scattered points are more interesting -} - - } #endif diff --git a/src/libslic3r/PrintObject.cpp b/src/libslic3r/PrintObject.cpp index ab8fa5dd9..1c9ea6a98 100644 --- a/src/libslic3r/PrintObject.cpp +++ b/src/libslic3r/PrintObject.cpp @@ -1,6 +1,11 @@ +#include "AABBTreeLines.hpp" +#include "BridgeDetector.hpp" +#include "ExPolygon.hpp" #include "Exception.hpp" #include "KDTreeIndirect.hpp" #include "Point.hpp" +#include "Polygon.hpp" +#include "Polyline.hpp" #include "Print.hpp" #include "BoundingBox.hpp" #include "ClipperUtils.hpp" @@ -1530,7 +1535,6 @@ void PrintObject::bridge_over_infill() tbb::parallel_for(tbb::blocked_range(0, this->layers().size()), [po = this](tbb::blocked_range r) { for (size_t lidx = r.begin(); lidx < r.end(); lidx++) { - const Layer *layer = po->get_layer(lidx); // gather also sparse infill surfaces on this layer, to which we can expand the bridges for anchoring @@ -1539,6 +1543,7 @@ void PrintObject::bridge_over_infill() std::unordered_map bridging_surface_candidates; std::unordered_map expansion_space; std::unordered_map max_bridge_flow_height; + std::unordered_map max_bridge_flow_width; for (const LayerSlice &slice : layer->lslices_ex) { std::unordered_set regions_to_check; for (const LayerIsland &island : slice.islands) { @@ -1552,8 +1557,10 @@ void PrintObject::bridge_over_infill() const LayerRegion *region = layer->get_region(region_idx); auto region_internal_solids = region->fill_surfaces().filter_by_type(stInternalSolid); if (!region_internal_solids.empty()) { - max_bridge_flow_height[&slice] = std::max(max_bridge_flow_height[&slice], - region->bridging_flow(frSolidInfill).height()); + max_bridge_flow_height[&slice] = std::max(max_bridge_flow_height[&slice], + region->bridging_flow(frSolidInfill).height()); + max_bridge_flow_width[&slice] = std::max(max_bridge_flow_width[&slice], + region->bridging_flow(frSolidInfill).width()); } bridging_surface_candidates[&slice].insert(bridging_surface_candidates[&slice].end(), region_internal_solids.begin(), region_internal_solids.end()); @@ -1562,33 +1569,15 @@ void PrintObject::bridge_over_infill() } } - for (const std::pair &candidates : bridging_surface_candidates) { - if (candidates.second.empty()) { - continue; - }; - - // Gather lower layers sparse infill areas, to depth defined by used bridge flow - Polygons lower_layers_sparse_infill; - double bottom_z = layer->print_z - max_bridge_flow_height[candidates.first] - EPSILON; - LayerSlice::Links current_links = candidates.first->overlaps_below; - for (auto i = int(lidx) - 1; i >= 0; --i) { - // Stop iterating if layer is lower than bottom_z. - if (po->get_layer(i)->print_z < bottom_z) - break; - for (const auto &link : current_links) { - const LayerSlice& slice_below = po->get_layer(i)->lslices_ex[link.slice_idx]; - for (const LayerRegion *region : po->get_layer(i)->regions()) { - for (const Surface *surface : region->fill_surfaces().filter_by_type(stInternal)) { - Polygons p = to_polygons(surface->expolygon); - lower_layers_sparse_infill.insert(lower_layers_sparse_infill.end(), p.begin(), p.end()); - } - } - } - } - lower_layers_sparse_infill = union_(lower_layers_sparse_infill); + // if there are none briding candidates, exit now, before making infill for the previous layer + if (std::all_of(bridging_surface_candidates.begin(), bridging_surface_candidates.end(), + [](const std::pair &candidates) { return candidates.second.empty(); })) { + continue; } // Now, temporarily fill the previous layer and extract the extrusions. + // TODO - the make_fills function does a lot of work, some of it is not needed (e.g. sorting the paths) + // It would be nice to have a function that only creates the fill polylines, ideally without modifying the global state po->get_layer(lidx)->lower_layer->make_fills(nullptr, nullptr, nullptr); Polylines lower_layer_polylines; for (const LayerRegion *region : layer->lower_layer->m_regions) { @@ -1598,15 +1587,128 @@ void PrintObject::bridge_over_infill() lower_layer_polylines.insert(lower_layer_polylines.end(), region_polylines.begin(), region_polylines.end()); } } - } + + for (const std::pair candidates : bridging_surface_candidates) { + if (candidates.second.empty()) { + continue; + }; + + // Gather lower layers sparse infill areas, to depth defined by used bridge flow + Polygons lower_layers_sparse_infill; + double bottom_z = layer->print_z - max_bridge_flow_height[candidates.first] - EPSILON; + LayerSlice::Links current_links = candidates.first->overlaps_below; + LayerSlice::Links next_links{}; + for (auto i = int(lidx) - 1; i >= 0; --i) { + // Stop iterating if layer is lower than bottom_z. + if (po->get_layer(i)->print_z < bottom_z) + break; + for (const auto &link : current_links) { + const LayerSlice &slice_below = po->get_layer(i)->lslices_ex[link.slice_idx]; + next_links.insert(next_links.end(), slice_below.overlaps_below.begin(), slice_below.overlaps_below.end()); + std::unordered_set regions_under_to_check; + for (const LayerIsland &island : slice_below.islands) { + regions_under_to_check.insert(island.perimeters.region()); + if (!island.fill_expolygons_composite()) { + regions_under_to_check.insert(island.fill_region_id); + } + } + + for (size_t region_idx : regions_under_to_check) { + const LayerRegion *region = layer->get_region(region_idx); + for (const Surface *surface : region->fill_surfaces().filter_by_type(stInternal)) { + Polygons p = to_polygons(surface->expolygon); + lower_layers_sparse_infill.insert(lower_layers_sparse_infill.end(), p.begin(), p.end()); + } + } + } + current_links = next_links; + } + if (lower_layers_sparse_infill.empty()) { + continue; + } + lower_layers_sparse_infill = union_(lower_layers_sparse_infill); + + Polygons expand_area; + for (const Surface *sparse_infill : expansion_space[candidates.first]) { + assert(sparse_infill->surface_type == stInternal); + Polygons a = to_polygons(sparse_infill->expolygon); + expand_area.insert(expand_area.end(), a.begin(), a.end()); + } + + // Lower layers sparse infill sections gathered + // now we can intersected them with bridging surface candidates to get actual areas that need and can accumulate + // bridging. These areas we then expand (within the surrounding sparse infill only!) + // to touch the infill polylines on previous layer. + for (const Surface *candidate : candidates.second) { + assert(candidate->surface_type == stInternalSolid); + Polygons bridged_area = to_polygons(candidate->expolygon); + bridged_area = + intersection(bridged_area, + lower_layers_sparse_infill); // cut off parts which are not over sparse infill - material overflow + + if (bridged_area.empty()) { + continue; + } + + Polygons max_area = expand_area; + max_area.insert(max_area.end(), bridged_area.begin(), bridged_area.end()); + closing(max_area, max_bridge_flow_width[candidates.first]); + + Polylines anchors = intersection_pl(lower_layer_polylines, max_area); + anchors = diff_pl(anchors, shrink(bridged_area, scale_(max_bridge_flow_width[candidates.first]))); + + AABBTreeLines::LinesDistancer anchors_and_walls; + { + Lines tmp = to_lines(anchors); + Lines tmp2 = to_lines(max_area); + tmp.insert(tmp.end(), tmp.begin(), tmp.end()); + anchors_and_walls = AABBTreeLines::LinesDistancer{tmp}; + } + + double bridging_dir = 0; + { + std::vector> directions_with_distances; + for (const Polygon &p : bridged_area) { + for (int point_idx = 0; point_idx < int(p.points.size()) - 1; ++point_idx) { + Vec2d start = p.points[point_idx].cast(); + Vec2d next = p.points[point_idx + 1].cast(); + Vec2d v = next - start; // vector from next to current + double dist_to_next = v.norm(); + v.normalize(); + int lines_count = int(std::ceil(dist_to_next / scaled(3.0))); + float step_size = dist_to_next / lines_count; + for (int i = 0; i < lines_count; ++i) { + Point a(start + v * (i * step_size)); + auto [distance, index, p] = anchors_and_walls.distance_from_lines_extra(a); + const Line& l = anchors_and_walls.get_line(index); + directions_with_distances.emplace_back(l.direction(), unscaled(distance)); + } + } + } + double max_dist = directions_with_distances[0].second; + for (const auto& dir :directions_with_distances) { + max_dist = std::max(max_dist, dir.second); + } + double acc = 0; + for (const auto& dir : directions_with_distances) { + bridging_dir += dir.first * (max_dist - dir.second); + acc += (max_dist - dir.second); + } + bridging_dir /= acc; + } + + + + } // surface iteration end + } // island iteration end + } // layer iteration end }); BOOST_LOG_TRIVIAL(info) << "Bridge over infill - End" << log_memory_info(); +} // void PrintObject::bridge_over_infill() - - - +void a(){ std::vector sparse_infill_regions; for (size_t region_id = 0; region_id < this->num_printing_regions(); ++ region_id) if (const PrintRegion ®ion = this->printing_region(region_id); region.config().fill_density.value < 100) @@ -1731,6 +1833,7 @@ void PrintObject::bridge_over_infill() m_print->throw_if_canceled(); } }); + } // void PrintObject::bridge_over_infill() static void clamp_exturder_to_default(ConfigOptionInt &opt, size_t num_extruders) From ebcedca21198211dac2de17e8c5eb0d1c686dbbf Mon Sep 17 00:00:00 2001 From: PavelMikus Date: Thu, 9 Feb 2023 17:42:39 +0100 Subject: [PATCH 23/48] write down general idea how to continue --- src/libslic3r/PrintObject.cpp | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/libslic3r/PrintObject.cpp b/src/libslic3r/PrintObject.cpp index 1c9ea6a98..61c761be0 100644 --- a/src/libslic3r/PrintObject.cpp +++ b/src/libslic3r/PrintObject.cpp @@ -1681,7 +1681,7 @@ void PrintObject::bridge_over_infill() Point a(start + v * (i * step_size)); auto [distance, index, p] = anchors_and_walls.distance_from_lines_extra(a); const Line& l = anchors_and_walls.get_line(index); - directions_with_distances.emplace_back(l.direction(), unscaled(distance)); + directions_with_distances.emplace_back(PI - l.direction(), unscaled(distance)); } } } @@ -1697,7 +1697,10 @@ void PrintObject::bridge_over_infill() bridging_dir /= acc; } - + //TODO use get_extens_rotated on the bridged_area polygons, generate vertical lines of the box, + // OR maybe get extens of rotated max_area, then fill with vertical lines, make AABB tree rotated for anchors and walls and also + // for bridged area + // then cut off the vertical lines, compose the final polygon, and rotate back } // surface iteration end } // island iteration end From a57f98961e43a7224b80550ccef8c1a97d5a1a7f Mon Sep 17 00:00:00 2001 From: Pavel Mikus Date: Fri, 10 Feb 2023 20:15:12 +0100 Subject: [PATCH 24/48] reconstruction of polygon from vertical slices TODO --- src/libslic3r/PrintObject.cpp | 392 +++++++++++++++++++++------------- 1 file changed, 240 insertions(+), 152 deletions(-) diff --git a/src/libslic3r/PrintObject.cpp b/src/libslic3r/PrintObject.cpp index 61c761be0..13fc92659 100644 --- a/src/libslic3r/PrintObject.cpp +++ b/src/libslic3r/PrintObject.cpp @@ -2,6 +2,7 @@ #include "BridgeDetector.hpp" #include "ExPolygon.hpp" #include "Exception.hpp" +#include "Flow.hpp" #include "KDTreeIndirect.hpp" #include "Point.hpp" #include "Polygon.hpp" @@ -1533,185 +1534,272 @@ void PrintObject::bridge_over_infill() { BOOST_LOG_TRIVIAL(info) << "Bridge over infill - Start" << log_memory_info(); - tbb::parallel_for(tbb::blocked_range(0, this->layers().size()), [po = this](tbb::blocked_range r) { - for (size_t lidx = r.begin(); lidx < r.end(); lidx++) { - const Layer *layer = po->get_layer(lidx); + tbb::parallel_for( + tbb::blocked_range(0, this->layers().size()), + [po = this](tbb::blocked_range r) { + for (size_t lidx = r.begin(); lidx < r.end(); lidx++) { + const Layer *layer = po->get_layer(lidx); - // gather also sparse infill surfaces on this layer, to which we can expand the bridges for anchoring - // gather potential internal bridging surfaces for the current layer - // pair of LayerSlice idx and surfaces. The LayerSlice idx simplifies the processing, since we cannot expand beyond it - std::unordered_map bridging_surface_candidates; - std::unordered_map expansion_space; - std::unordered_map max_bridge_flow_height; - std::unordered_map max_bridge_flow_width; - for (const LayerSlice &slice : layer->lslices_ex) { - std::unordered_set regions_to_check; - for (const LayerIsland &island : slice.islands) { - regions_to_check.insert(island.perimeters.region()); - if (!island.fill_expolygons_composite()) { - regions_to_check.insert(island.fill_region_id); - } - } - - for (size_t region_idx : regions_to_check) { - const LayerRegion *region = layer->get_region(region_idx); - auto region_internal_solids = region->fill_surfaces().filter_by_type(stInternalSolid); - if (!region_internal_solids.empty()) { - max_bridge_flow_height[&slice] = std::max(max_bridge_flow_height[&slice], - region->bridging_flow(frSolidInfill).height()); - max_bridge_flow_width[&slice] = std::max(max_bridge_flow_width[&slice], - region->bridging_flow(frSolidInfill).width()); - } - bridging_surface_candidates[&slice].insert(bridging_surface_candidates[&slice].end(), region_internal_solids.begin(), - region_internal_solids.end()); - auto region_sparse_infill = region->fill_surfaces().filter_by_type(stInternal); - expansion_space[&slice].insert(expansion_space[&slice].end(), region_sparse_infill.begin(), region_sparse_infill.end()); - } - } - - // if there are none briding candidates, exit now, before making infill for the previous layer - if (std::all_of(bridging_surface_candidates.begin(), bridging_surface_candidates.end(), - [](const std::pair &candidates) { return candidates.second.empty(); })) { - continue; - } - - // Now, temporarily fill the previous layer and extract the extrusions. - // TODO - the make_fills function does a lot of work, some of it is not needed (e.g. sorting the paths) - // It would be nice to have a function that only creates the fill polylines, ideally without modifying the global state - po->get_layer(lidx)->lower_layer->make_fills(nullptr, nullptr, nullptr); - Polylines lower_layer_polylines; - for (const LayerRegion *region : layer->lower_layer->m_regions) { - for (const ExtrusionEntity *ee : region->fills().entities) { - assert(ee->is_collection()); - auto region_polylines = dynamic_cast(ee)->as_polylines(); - lower_layer_polylines.insert(lower_layer_polylines.end(), region_polylines.begin(), region_polylines.end()); - } - } - - for (const std::pair candidates : bridging_surface_candidates) { - if (candidates.second.empty()) { - continue; - }; - - // Gather lower layers sparse infill areas, to depth defined by used bridge flow - Polygons lower_layers_sparse_infill; - double bottom_z = layer->print_z - max_bridge_flow_height[candidates.first] - EPSILON; - LayerSlice::Links current_links = candidates.first->overlaps_below; - LayerSlice::Links next_links{}; - for (auto i = int(lidx) - 1; i >= 0; --i) { - // Stop iterating if layer is lower than bottom_z. - if (po->get_layer(i)->print_z < bottom_z) - break; - for (const auto &link : current_links) { - const LayerSlice &slice_below = po->get_layer(i)->lslices_ex[link.slice_idx]; - next_links.insert(next_links.end(), slice_below.overlaps_below.begin(), slice_below.overlaps_below.end()); - std::unordered_set regions_under_to_check; - for (const LayerIsland &island : slice_below.islands) { - regions_under_to_check.insert(island.perimeters.region()); - if (!island.fill_expolygons_composite()) { - regions_under_to_check.insert(island.fill_region_id); - } - } - - for (size_t region_idx : regions_under_to_check) { - const LayerRegion *region = layer->get_region(region_idx); - for (const Surface *surface : region->fill_surfaces().filter_by_type(stInternal)) { - Polygons p = to_polygons(surface->expolygon); - lower_layers_sparse_infill.insert(lower_layers_sparse_infill.end(), p.begin(), p.end()); - } + // gather also sparse infill surfaces on this layer, to which we can expand the bridges for anchoring + // gather potential internal bridging surfaces for the current layer + // pair of LayerSlice idx and surfaces. The LayerSlice idx simplifies the processing, since we cannot expand beyond it + std::unordered_map bridging_surface_candidates; + std::unordered_map expansion_space; + std::unordered_map max_bridge_flow_height; + std::unordered_map surface_to_region; + for (const LayerSlice &slice : layer->lslices_ex) { + std::unordered_set regions_to_check; + for (const LayerIsland &island : slice.islands) { + regions_to_check.insert(island.perimeters.region()); + if (!island.fill_expolygons_composite()) { + regions_to_check.insert(island.fill_region_id); } } - current_links = next_links; + + for (size_t region_idx : regions_to_check) { + const LayerRegion *region = layer->get_region(region_idx); + auto region_internal_solids = region->fill_surfaces().filter_by_type(stInternalSolid); + if (!region_internal_solids.empty()) { + max_bridge_flow_height[&slice] = std::max(max_bridge_flow_height[&slice], + region->bridging_flow(frSolidInfill).height()); + } + for (const Surface *s : region_internal_solids) { + surface_to_region[s] = region; + } + bridging_surface_candidates[&slice].insert(bridging_surface_candidates[&slice].end(), + region_internal_solids.begin(), region_internal_solids.end()); + auto region_sparse_infill = region->fill_surfaces().filter_by_type(stInternal); + expansion_space[&slice].insert(expansion_space[&slice].end(), region_sparse_infill.begin(), + region_sparse_infill.end()); + } } - if (lower_layers_sparse_infill.empty()) { + + // if there are none briding candidates, exit now, before making infill for the previous layer + if (std::all_of(bridging_surface_candidates.begin(), bridging_surface_candidates.end(), + [](const std::pair &candidates) { return candidates.second.empty(); })) { continue; } - lower_layers_sparse_infill = union_(lower_layers_sparse_infill); - Polygons expand_area; - for (const Surface *sparse_infill : expansion_space[candidates.first]) { - assert(sparse_infill->surface_type == stInternal); - Polygons a = to_polygons(sparse_infill->expolygon); - expand_area.insert(expand_area.end(), a.begin(), a.end()); + // Now, temporarily fill the previous layer and extract the extrusions. + // TODO - the make_fills function does a lot of work, some of it is not needed (e.g. sorting the paths) + // It would be nice to have a function that only creates the fill polylines, ideally without modifying the global state + po->get_layer(lidx)->lower_layer->make_fills(nullptr, nullptr, nullptr); + Polylines lower_layer_polylines; + for (const LayerRegion *region : layer->lower_layer->m_regions) { + for (const ExtrusionEntity *ee : region->fills().entities) { + assert(ee->is_collection()); + auto region_polylines = dynamic_cast(ee)->as_polylines(); + lower_layer_polylines.insert(lower_layer_polylines.end(), region_polylines.begin(), region_polylines.end()); + } } - // Lower layers sparse infill sections gathered - // now we can intersected them with bridging surface candidates to get actual areas that need and can accumulate - // bridging. These areas we then expand (within the surrounding sparse infill only!) - // to touch the infill polylines on previous layer. - for (const Surface *candidate : candidates.second) { - assert(candidate->surface_type == stInternalSolid); - Polygons bridged_area = to_polygons(candidate->expolygon); - bridged_area = - intersection(bridged_area, - lower_layers_sparse_infill); // cut off parts which are not over sparse infill - material overflow - - if (bridged_area.empty()) { + for (const std::pair candidates : bridging_surface_candidates) { + if (candidates.second.empty()) { continue; - } + }; - Polygons max_area = expand_area; - max_area.insert(max_area.end(), bridged_area.begin(), bridged_area.end()); - closing(max_area, max_bridge_flow_width[candidates.first]); + // Gather lower layers sparse infill areas, to depth defined by used bridge flow + Polygons lower_layers_sparse_infill; + double bottom_z = layer->print_z - max_bridge_flow_height[candidates.first] - EPSILON; + LayerSlice::Links current_links = candidates.first->overlaps_below; + LayerSlice::Links next_links{}; + for (auto i = int(lidx) - 1; i >= 0; --i) { + // Stop iterating if layer is lower than bottom_z. + if (po->get_layer(i)->print_z < bottom_z) + break; + for (const auto &link : current_links) { + const LayerSlice &slice_below = po->get_layer(i)->lslices_ex[link.slice_idx]; + next_links.insert(next_links.end(), slice_below.overlaps_below.begin(), slice_below.overlaps_below.end()); + std::unordered_set regions_under_to_check; + for (const LayerIsland &island : slice_below.islands) { + regions_under_to_check.insert(island.perimeters.region()); + if (!island.fill_expolygons_composite()) { + regions_under_to_check.insert(island.fill_region_id); + } + } - Polylines anchors = intersection_pl(lower_layer_polylines, max_area); - anchors = diff_pl(anchors, shrink(bridged_area, scale_(max_bridge_flow_width[candidates.first]))); - - AABBTreeLines::LinesDistancer anchors_and_walls; - { - Lines tmp = to_lines(anchors); - Lines tmp2 = to_lines(max_area); - tmp.insert(tmp.end(), tmp.begin(), tmp.end()); - anchors_and_walls = AABBTreeLines::LinesDistancer{tmp}; - } - - double bridging_dir = 0; - { - std::vector> directions_with_distances; - for (const Polygon &p : bridged_area) { - for (int point_idx = 0; point_idx < int(p.points.size()) - 1; ++point_idx) { - Vec2d start = p.points[point_idx].cast(); - Vec2d next = p.points[point_idx + 1].cast(); - Vec2d v = next - start; // vector from next to current - double dist_to_next = v.norm(); - v.normalize(); - int lines_count = int(std::ceil(dist_to_next / scaled(3.0))); - float step_size = dist_to_next / lines_count; - for (int i = 0; i < lines_count; ++i) { - Point a(start + v * (i * step_size)); - auto [distance, index, p] = anchors_and_walls.distance_from_lines_extra(a); - const Line& l = anchors_and_walls.get_line(index); - directions_with_distances.emplace_back(PI - l.direction(), unscaled(distance)); + for (size_t region_idx : regions_under_to_check) { + const LayerRegion *region = layer->get_region(region_idx); + for (const Surface *surface : region->fill_surfaces().filter_by_type(stInternal)) { + Polygons p = to_polygons(surface->expolygon); + lower_layers_sparse_infill.insert(lower_layers_sparse_infill.end(), p.begin(), p.end()); } } } - double max_dist = directions_with_distances[0].second; - for (const auto& dir :directions_with_distances) { - max_dist = std::max(max_dist, dir.second); - } - double acc = 0; - for (const auto& dir : directions_with_distances) { - bridging_dir += dir.first * (max_dist - dir.second); - acc += (max_dist - dir.second); - } - bridging_dir /= acc; + current_links = next_links; + } + if (lower_layers_sparse_infill.empty()) { + continue; + } + lower_layers_sparse_infill = union_(lower_layers_sparse_infill); + + Polygons expand_area; + for (const Surface *sparse_infill : expansion_space[candidates.first]) { + assert(sparse_infill->surface_type == stInternal); + Polygons a = to_polygons(sparse_infill->expolygon); + expand_area.insert(expand_area.end(), a.begin(), a.end()); } - //TODO use get_extens_rotated on the bridged_area polygons, generate vertical lines of the box, - // OR maybe get extens of rotated max_area, then fill with vertical lines, make AABB tree rotated for anchors and walls and also + // Lower layers sparse infill sections gathered + // now we can intersected them with bridging surface candidates to get actual areas that need and can accumulate + // bridging. These areas we then expand (within the surrounding sparse infill only!) + // to touch the infill polylines on previous layer. + for (const Surface *candidate : candidates.second) { + const Flow &flow = surface_to_region[candidate]->bridging_flow(frSolidInfill); + assert(candidate->surface_type == stInternalSolid); + Polygons bridged_area = to_polygons(candidate->expolygon); + bridged_area = + intersection(bridged_area, + lower_layers_sparse_infill); // cut off parts which are not over sparse infill - material overflow + + if (bridged_area.empty()) { + continue; + } + + Polygons max_area = expand_area; + max_area.insert(max_area.end(), bridged_area.begin(), bridged_area.end()); + closing(max_area, flow.scaled_width()); + + Polylines anchors = intersection_pl(lower_layer_polylines, max_area); + anchors = diff_pl(anchors, shrink(bridged_area, flow.scaled_width())); + + Lines anchors_and_walls = to_lines(anchors); + Lines tmp = to_lines(max_area); + tmp.insert(anchors_and_walls.end(), tmp.begin(), tmp.end()); + + double bridging_angle = 0; + { + AABBTreeLines::LinesDistancer lines_tree{anchors_and_walls}; + + std::vector> directions_with_distances; + for (const Polygon &p : bridged_area) { + for (int point_idx = 0; point_idx < int(p.points.size()) - 1; ++point_idx) { + Vec2d start = p.points[point_idx].cast(); + Vec2d next = p.points[point_idx + 1].cast(); + Vec2d v = next - start; // vector from next to current + double dist_to_next = v.norm(); + v.normalize(); + int lines_count = int(std::ceil(dist_to_next / scaled(3.0))); + float step_size = dist_to_next / lines_count; + for (int i = 0; i < lines_count; ++i) { + Point a(start + v * (i * step_size)); + auto [distance, index, p] = lines_tree.distance_from_lines_extra(a); + const Line &l = lines_tree.get_line(index); + directions_with_distances.emplace_back(PI - l.direction(), unscaled(distance)); + } + } + } + double max_dist = directions_with_distances[0].second; + for (const auto &dir : directions_with_distances) { + max_dist = std::max(max_dist, dir.second); + } + double acc = 0; + for (const auto &dir : directions_with_distances) { + bridging_angle += dir.first * (max_dist - dir.second); + acc += (max_dist - dir.second); + } + bridging_angle /= acc; + } + + // TODO maybe get extens of rotated max_area, then fill with vertical lines, make AABB tree rotated for anchors and + // walls and also // for bridged area - // then cut off the vertical lines, compose the final polygon, and rotate back + // then cut off the vertical lines, compose the final polygon, and rotate back + auto lines_rotate = [](Lines &lines, double cos_angle, double sin_angle) { + for (Line &l : lines) { + double ax = double(l.a.x()); + double ay = double(l.a.y()); + l.a.x() = coord_t(round(cos_angle * ax - sin_angle * ay)); + l.a.y() = coord_t(round(cos_angle * ay + sin_angle * ax)); + double bx = double(l.b.x()); + double by = double(l.b.y()); + l.b.x() = coord_t(round(cos_angle * bx - sin_angle * by)); + l.b.y() = coord_t(round(cos_angle * by + sin_angle * bx)); + } + }; + + Polygons expanded_bridged_area{}; + { + polygons_rotate(bridged_area, bridging_angle); + lines_rotate(anchors_and_walls, cos(bridging_angle), sin(bridging_angle)); + BoundingBox bb_x = get_extents(bridged_area); + BoundingBox bb_y = get_extents(anchors_and_walls); + + const size_t n_vlines = (bb_x.max.x() - bb_x.min.x() + flow.scaled_spacing() - 1) / flow.scaled_spacing(); + std::vector vertical_lines(n_vlines); + for (size_t i = 0; i < n_vlines; i++) { + coord_t x = bb_x.min.x() + i * flow.scaled_spacing(); + coord_t y_min = bb_y.min.y() - flow.scaled_spacing(); + coord_t y_max = bb_y.max.y() + flow.scaled_spacing(); + vertical_lines[i].a = Point{x, y_min}; + vertical_lines[i].b = Point{x, y_max}; + } + + auto anchors_and_walls_tree = AABBTreeLines::LinesDistancer{std::move(anchors_and_walls)}; + auto bridged_area_tree = AABBTreeLines::LinesDistancer{to_lines(bridged_area)}; + + std::vector>> polygon_sections(n_vlines); + for (size_t i = 0; i < n_vlines; i++) { + auto area_intersections = bridged_area_tree.intersections_with_line(vertical_lines[i]); + if (area_intersections.size() < 2) { + if (area_intersections.size() > 0) { + polygon_sections[i].emplace_back(area_intersections[0].first, area_intersections[0].first); + } + continue; + } + auto anchors_intersections = anchors_and_walls_tree.intersections_with_line(vertical_lines[i]); + for (const auto &intersection : area_intersections) { + auto high_b = std::upper_bound(anchors_intersections.begin(), anchors_intersections.end(), intersection, + [](const std::pair left, + const std::pair right) { + return left.first.y() > right.first.y(); + }); + Point low, high; + if (high_b == anchors_intersections.end()) { + assert(false); // should not happen + continue; + } else if (high_b == anchors_intersections.begin()) { + low = high_b->first; + high = (++high_b)->first; + } else { + low = (--high_b)->first; + high = high_b->first; + } + + if (polygon_sections[i].size() > 0 && polygon_sections[i].back().second.y() >= low.y()) { + polygon_sections[i].back().second = high; + } else { + polygon_sections[i].emplace_back(low, high); + } + } + } + + //reconstruct polygon from polygon sections + struct TracedPoly { + std::vector lows; + std::vector highs; + }; + + std::vector traced_polys; + for (const auto& layer : polygon_sections) { + for () + } + + + } + } } // surface iteration end } // island iteration end } // layer iteration end - }); + ); BOOST_LOG_TRIVIAL(info) << "Bridge over infill - End" << log_memory_info(); } // void PrintObject::bridge_over_infill() -void a(){ +void a() +{ std::vector sparse_infill_regions; for (size_t region_id = 0; region_id < this->num_printing_regions(); ++ region_id) if (const PrintRegion ®ion = this->printing_region(region_id); region.config().fill_density.value < 100) From 3d158e545e5c8617316d4e287a0af5fd46e458c5 Mon Sep 17 00:00:00 2001 From: PavelMikus Date: Mon, 13 Feb 2023 15:53:08 +0100 Subject: [PATCH 25/48] Debug version, threading disabled for the first part currently and crashing. But core should be finished --- src/libslic3r/PrintObject.cpp | 820 +++++++++++++++++++--------------- 1 file changed, 472 insertions(+), 348 deletions(-) diff --git a/src/libslic3r/PrintObject.cpp b/src/libslic3r/PrintObject.cpp index 13fc92659..b34d4ee89 100644 --- a/src/libslic3r/PrintObject.cpp +++ b/src/libslic3r/PrintObject.cpp @@ -36,6 +36,7 @@ #include #include #include +#include #include #include #include @@ -1534,398 +1535,521 @@ void PrintObject::bridge_over_infill() { BOOST_LOG_TRIVIAL(info) << "Bridge over infill - Start" << log_memory_info(); - tbb::parallel_for( - tbb::blocked_range(0, this->layers().size()), - [po = this](tbb::blocked_range r) { - for (size_t lidx = r.begin(); lidx < r.end(); lidx++) { - const Layer *layer = po->get_layer(lidx); + struct ModifiedSurface + { + ModifiedSurface(const Surface *original_surface, Polygons new_polys, const LayerRegion *region, double bridge_angle) + : original_surface(original_surface), new_polys(new_polys), region(region), bridge_angle(bridge_angle) + {} + const Surface *original_surface; + Polygons new_polys; + const LayerRegion *region; + double bridge_angle; + }; - // gather also sparse infill surfaces on this layer, to which we can expand the bridges for anchoring - // gather potential internal bridging surfaces for the current layer - // pair of LayerSlice idx and surfaces. The LayerSlice idx simplifies the processing, since we cannot expand beyond it - std::unordered_map bridging_surface_candidates; - std::unordered_map expansion_space; - std::unordered_map max_bridge_flow_height; - std::unordered_map surface_to_region; - for (const LayerSlice &slice : layer->lslices_ex) { - std::unordered_set regions_to_check; - for (const LayerIsland &island : slice.islands) { - regions_to_check.insert(island.perimeters.region()); - if (!island.fill_expolygons_composite()) { - regions_to_check.insert(island.fill_region_id); - } - } + std::unordered_map> expanded_briding_surfaces; - for (size_t region_idx : regions_to_check) { - const LayerRegion *region = layer->get_region(region_idx); - auto region_internal_solids = region->fill_surfaces().filter_by_type(stInternalSolid); - if (!region_internal_solids.empty()) { - max_bridge_flow_height[&slice] = std::max(max_bridge_flow_height[&slice], - region->bridging_flow(frSolidInfill).height()); - } - for (const Surface *s : region_internal_solids) { - surface_to_region[s] = region; - } - bridging_surface_candidates[&slice].insert(bridging_surface_candidates[&slice].end(), - region_internal_solids.begin(), region_internal_solids.end()); - auto region_sparse_infill = region->fill_surfaces().filter_by_type(stInternal); - expansion_space[&slice].insert(expansion_space[&slice].end(), region_sparse_infill.begin(), - region_sparse_infill.end()); + // tbb::parallel_for(tbb::blocked_range(0, this->layers().size()), [po = this, + // &expanded_briding_surfaces](tbb::blocked_range r) { + auto r = tbb::blocked_range{0, this->layer_count()}; + auto po = this; + + for (size_t lidx = r.begin(); lidx < r.end(); lidx++) { + const Layer *layer = po->get_layer(lidx); + + // gather also sparse infill surfaces on this layer, to which we can expand the bridges for anchoring + // gather potential internal bridging surfaces for the current layer + // pair of LayerSlice idx and surfaces. The LayerSlice idx simplifies the processing, since we cannot expand beyond it + std::unordered_map bridging_surface_candidates; + std::unordered_map expansion_space; + std::unordered_map max_bridge_flow_height; + std::unordered_map surface_to_region; + for (const LayerSlice &slice : layer->lslices_ex) { + std::unordered_set regions_to_check; + for (const LayerIsland &island : slice.islands) { + regions_to_check.insert(island.perimeters.region()); + if (!island.fill_expolygons_composite()) { + regions_to_check.insert(island.fill_region_id); } } - // if there are none briding candidates, exit now, before making infill for the previous layer - if (std::all_of(bridging_surface_candidates.begin(), bridging_surface_candidates.end(), - [](const std::pair &candidates) { return candidates.second.empty(); })) { + for (size_t region_idx : regions_to_check) { + const LayerRegion *region = layer->get_region(region_idx); + auto region_internal_solids = region->fill_surfaces().filter_by_type(stInternalSolid); + if (!region_internal_solids.empty()) { + max_bridge_flow_height[&slice] = std::max(max_bridge_flow_height[&slice], + region->bridging_flow(frSolidInfill).height()); + } + for (const Surface *s : region_internal_solids) { + surface_to_region[s] = region; + } + bridging_surface_candidates[&slice].insert(bridging_surface_candidates[&slice].end(), region_internal_solids.begin(), + region_internal_solids.end()); + auto region_sparse_infill = region->fill_surfaces().filter_by_type(stInternal); + expansion_space[&slice].insert(expansion_space[&slice].end(), region_sparse_infill.begin(), region_sparse_infill.end()); + } + } + + // if there are none briding candidates, exit now, before making infill for the previous layer + if (std::all_of(bridging_surface_candidates.begin(), bridging_surface_candidates.end(), + [](const std::pair &candidates) { return candidates.second.empty(); })) { + continue; + } + + // Now, temporarily fill the previous layer and extract the extrusions. + // TODO - the make_fills function does a lot of work, some of it is not needed (e.g. sorting the paths) + // It would be nice to have a function that only creates the fill polylines, ideally without modifying the global state + po->get_layer(lidx)->lower_layer->make_fills(nullptr, nullptr, nullptr); + Polylines lower_layer_polylines; + for (const LayerRegion *region : layer->lower_layer->m_regions) { + for (const ExtrusionEntity *ee : region->fills().entities) { + assert(ee->is_collection()); + auto region_polylines = dynamic_cast(ee)->as_polylines(); + lower_layer_polylines.insert(lower_layer_polylines.end(), region_polylines.begin(), region_polylines.end()); + } + } + + for (const std::pair candidates : bridging_surface_candidates) { + if (candidates.second.empty()) { + continue; + }; + + // Gather lower layers sparse infill areas, to depth defined by used bridge flow + Polygons lower_layers_sparse_infill{}; + double bottom_z = layer->print_z - max_bridge_flow_height[candidates.first] - EPSILON; + LayerSlice::Links current_links = candidates.first->overlaps_below; + LayerSlice::Links next_links{}; + for (int i = int(lidx) - 1; i >= 0; --i) { + // Stop iterating if layer is lower than bottom_z. + if (po->get_layer(i)->print_z < bottom_z) + break; + for (const auto &link : current_links) { + const LayerSlice &slice_below = po->get_layer(i)->lslices_ex[link.slice_idx]; + next_links.insert(next_links.end(), slice_below.overlaps_below.begin(), slice_below.overlaps_below.end()); + std::unordered_set regions_under_to_check; + for (const LayerIsland &island : slice_below.islands) { + regions_under_to_check.insert(island.perimeters.region()); + if (!island.fill_expolygons_composite()) { + regions_under_to_check.insert(island.fill_region_id); + } + } + + for (size_t region_idx : regions_under_to_check) { + const LayerRegion *region = po->get_layer(i)->get_region(region_idx); + for (const Surface *surface : region->fill_surfaces().filter_by_type(stInternal)) { + Polygons p = to_polygons(surface->expolygon); + lower_layers_sparse_infill.insert(lower_layers_sparse_infill.end(), p.begin(), p.end()); + } + } + } + current_links = next_links; + } + if (lower_layers_sparse_infill.empty()) { continue; } + lower_layers_sparse_infill = union_(lower_layers_sparse_infill); - // Now, temporarily fill the previous layer and extract the extrusions. - // TODO - the make_fills function does a lot of work, some of it is not needed (e.g. sorting the paths) - // It would be nice to have a function that only creates the fill polylines, ideally without modifying the global state - po->get_layer(lidx)->lower_layer->make_fills(nullptr, nullptr, nullptr); - Polylines lower_layer_polylines; - for (const LayerRegion *region : layer->lower_layer->m_regions) { - for (const ExtrusionEntity *ee : region->fills().entities) { - assert(ee->is_collection()); - auto region_polylines = dynamic_cast(ee)->as_polylines(); - lower_layer_polylines.insert(lower_layer_polylines.end(), region_polylines.begin(), region_polylines.end()); - } + Polygons expand_area; + for (const Surface *sparse_infill : expansion_space[candidates.first]) { + assert(sparse_infill->surface_type == stInternal); + Polygons a = to_polygons(sparse_infill->expolygon); + expand_area.insert(expand_area.end(), a.begin(), a.end()); } - for (const std::pair candidates : bridging_surface_candidates) { - if (candidates.second.empty()) { + // Lower layers sparse infill sections gathered + // now we can intersected them with bridging surface candidates to get actual areas that need and can accumulate + // bridging. These areas we then expand (within the surrounding sparse infill only!) + // to touch the infill polylines on previous layer. + for (const Surface *candidate : candidates.second) { + const Flow &flow = surface_to_region[candidate]->bridging_flow(frSolidInfill); + assert(candidate->surface_type == stInternalSolid); + Polygons bridged_area = to_polygons(candidate->expolygon); + bridged_area = + intersection(bridged_area, + lower_layers_sparse_infill); // cut off parts which are not over sparse infill - material overflow + + if (bridged_area.empty()) { continue; + } + + Polygons max_area = expand_area; + max_area.insert(max_area.end(), bridged_area.begin(), bridged_area.end()); + closing(max_area, flow.scaled_width()); + + Polylines anchors = intersection_pl(lower_layer_polylines, max_area); + anchors = diff_pl(anchors, shrink(bridged_area, flow.scaled_width())); + + Lines anchors_and_walls = to_lines(anchors); + Lines tmp = to_lines(max_area); + tmp.insert(anchors_and_walls.end(), tmp.begin(), tmp.end()); + + double bridging_angle = 0; + { + AABBTreeLines::LinesDistancer lines_tree{anchors_and_walls}; + + std::vector> directions_with_distances; + for (const Polygon &p : bridged_area) { + for (int point_idx = 0; point_idx < int(p.points.size()) - 1; ++point_idx) { + Vec2d start = p.points[point_idx].cast(); + Vec2d next = p.points[point_idx + 1].cast(); + Vec2d v = next - start; // vector from next to current + double dist_to_next = v.norm(); + v.normalize(); + int lines_count = int(std::ceil(dist_to_next / scaled(3.0))); + float step_size = dist_to_next / lines_count; + for (int i = 0; i < lines_count; ++i) { + Point a = (start + v * (i * step_size)).cast(); + auto [distance, index, p] = lines_tree.distance_from_lines_extra(a); + const Line &l = lines_tree.get_line(index); + directions_with_distances.emplace_back(PI - l.direction(), unscaled(distance)); + } + } + } + double max_dist = directions_with_distances[0].second; + for (const auto &dir : directions_with_distances) { + max_dist = std::max(max_dist, dir.second); + } + double acc = 0; + for (const auto &dir : directions_with_distances) { + bridging_angle += dir.first * (max_dist - dir.second); + acc += (max_dist - dir.second); + } + bridging_angle /= acc; + } + + // TODO maybe get extens of rotated max_area, then fill with vertical lines, make AABB tree rotated for anchors and + // walls and also + // for bridged area + // then cut off the vertical lines, compose the final polygon, and rotate back + auto lines_rotate = [](Lines &lines, double cos_angle, double sin_angle) { + for (Line &l : lines) { + double ax = double(l.a.x()); + double ay = double(l.a.y()); + l.a.x() = coord_t(round(cos_angle * ax - sin_angle * ay)); + l.a.y() = coord_t(round(cos_angle * ay + sin_angle * ax)); + double bx = double(l.b.x()); + double by = double(l.b.y()); + l.b.x() = coord_t(round(cos_angle * bx - sin_angle * by)); + l.b.y() = coord_t(round(cos_angle * by + sin_angle * bx)); + } }; - // Gather lower layers sparse infill areas, to depth defined by used bridge flow - Polygons lower_layers_sparse_infill; - double bottom_z = layer->print_z - max_bridge_flow_height[candidates.first] - EPSILON; - LayerSlice::Links current_links = candidates.first->overlaps_below; - LayerSlice::Links next_links{}; - for (auto i = int(lidx) - 1; i >= 0; --i) { - // Stop iterating if layer is lower than bottom_z. - if (po->get_layer(i)->print_z < bottom_z) - break; - for (const auto &link : current_links) { - const LayerSlice &slice_below = po->get_layer(i)->lslices_ex[link.slice_idx]; - next_links.insert(next_links.end(), slice_below.overlaps_below.begin(), slice_below.overlaps_below.end()); - std::unordered_set regions_under_to_check; - for (const LayerIsland &island : slice_below.islands) { - regions_under_to_check.insert(island.perimeters.region()); - if (!island.fill_expolygons_composite()) { - regions_under_to_check.insert(island.fill_region_id); - } - } + Polygons expanded_bridged_area{}; + { + polygons_rotate(bridged_area, bridging_angle); + lines_rotate(anchors_and_walls, cos(bridging_angle), sin(bridging_angle)); + BoundingBox bb_x = get_extents(bridged_area); + BoundingBox bb_y = get_extents(anchors_and_walls); - for (size_t region_idx : regions_under_to_check) { - const LayerRegion *region = layer->get_region(region_idx); - for (const Surface *surface : region->fill_surfaces().filter_by_type(stInternal)) { - Polygons p = to_polygons(surface->expolygon); - lower_layers_sparse_infill.insert(lower_layers_sparse_infill.end(), p.begin(), p.end()); + const size_t n_vlines = (bb_x.max.x() - bb_x.min.x() + flow.scaled_spacing() - 1) / flow.scaled_spacing(); + std::vector vertical_lines(n_vlines); + for (size_t i = 0; i < n_vlines; i++) { + coord_t x = bb_x.min.x() + i * flow.scaled_spacing(); + coord_t y_min = bb_y.min.y() - flow.scaled_spacing(); + coord_t y_max = bb_y.max.y() + flow.scaled_spacing(); + vertical_lines[i].a = Point{x, y_min}; + vertical_lines[i].b = Point{x, y_max}; + } + + auto anchors_and_walls_tree = AABBTreeLines::LinesDistancer{std::move(anchors_and_walls)}; + auto bridged_area_tree = AABBTreeLines::LinesDistancer{to_lines(bridged_area)}; + + std::vector>> polygon_sections(n_vlines); + for (size_t i = 0; i < n_vlines; i++) { + auto area_intersections = bridged_area_tree.intersections_with_line(vertical_lines[i]); + if (area_intersections.size() < 2) { + if (area_intersections.size() > 0) { + polygon_sections[i].emplace_back(area_intersections[0].first, area_intersections[0].first); + } + continue; + } + auto anchors_intersections = anchors_and_walls_tree.intersections_with_line(vertical_lines[i]); + for (const auto &intersection : area_intersections) { + auto high_b = std::upper_bound(anchors_intersections.begin(), anchors_intersections.end(), intersection, + [](const std::pair left, + const std::pair right) { + return left.first.y() > right.first.y(); + }); + Point low, high; + if (high_b == anchors_intersections.end()) { + assert(false); // should not happen + continue; + } else if (high_b == anchors_intersections.begin()) { + low = high_b->first; + high = (++high_b)->first; + } else { + low = (--high_b)->first; + high = high_b->first; + } + + if (polygon_sections[i].size() > 0 && polygon_sections[i].back().second.y() >= low.y()) { + polygon_sections[i].back().second = high; + } else { + polygon_sections[i].emplace_back(low, high); } } } - current_links = next_links; - } - if (lower_layers_sparse_infill.empty()) { - continue; - } - lower_layers_sparse_infill = union_(lower_layers_sparse_infill); - Polygons expand_area; - for (const Surface *sparse_infill : expansion_space[candidates.first]) { - assert(sparse_infill->surface_type == stInternal); - Polygons a = to_polygons(sparse_infill->expolygon); - expand_area.insert(expand_area.end(), a.begin(), a.end()); - } - - // Lower layers sparse infill sections gathered - // now we can intersected them with bridging surface candidates to get actual areas that need and can accumulate - // bridging. These areas we then expand (within the surrounding sparse infill only!) - // to touch the infill polylines on previous layer. - for (const Surface *candidate : candidates.second) { - const Flow &flow = surface_to_region[candidate]->bridging_flow(frSolidInfill); - assert(candidate->surface_type == stInternalSolid); - Polygons bridged_area = to_polygons(candidate->expolygon); - bridged_area = - intersection(bridged_area, - lower_layers_sparse_infill); // cut off parts which are not over sparse infill - material overflow - - if (bridged_area.empty()) { - continue; - } - - Polygons max_area = expand_area; - max_area.insert(max_area.end(), bridged_area.begin(), bridged_area.end()); - closing(max_area, flow.scaled_width()); - - Polylines anchors = intersection_pl(lower_layer_polylines, max_area); - anchors = diff_pl(anchors, shrink(bridged_area, flow.scaled_width())); - - Lines anchors_and_walls = to_lines(anchors); - Lines tmp = to_lines(max_area); - tmp.insert(anchors_and_walls.end(), tmp.begin(), tmp.end()); - - double bridging_angle = 0; + // reconstruct polygon from polygon sections + struct TracedPoly { - AABBTreeLines::LinesDistancer lines_tree{anchors_and_walls}; - - std::vector> directions_with_distances; - for (const Polygon &p : bridged_area) { - for (int point_idx = 0; point_idx < int(p.points.size()) - 1; ++point_idx) { - Vec2d start = p.points[point_idx].cast(); - Vec2d next = p.points[point_idx + 1].cast(); - Vec2d v = next - start; // vector from next to current - double dist_to_next = v.norm(); - v.normalize(); - int lines_count = int(std::ceil(dist_to_next / scaled(3.0))); - float step_size = dist_to_next / lines_count; - for (int i = 0; i < lines_count; ++i) { - Point a(start + v * (i * step_size)); - auto [distance, index, p] = lines_tree.distance_from_lines_extra(a); - const Line &l = lines_tree.get_line(index); - directions_with_distances.emplace_back(PI - l.direction(), unscaled(distance)); - } - } - } - double max_dist = directions_with_distances[0].second; - for (const auto &dir : directions_with_distances) { - max_dist = std::max(max_dist, dir.second); - } - double acc = 0; - for (const auto &dir : directions_with_distances) { - bridging_angle += dir.first * (max_dist - dir.second); - acc += (max_dist - dir.second); - } - bridging_angle /= acc; - } - - // TODO maybe get extens of rotated max_area, then fill with vertical lines, make AABB tree rotated for anchors and - // walls and also - // for bridged area - // then cut off the vertical lines, compose the final polygon, and rotate back - auto lines_rotate = [](Lines &lines, double cos_angle, double sin_angle) { - for (Line &l : lines) { - double ax = double(l.a.x()); - double ay = double(l.a.y()); - l.a.x() = coord_t(round(cos_angle * ax - sin_angle * ay)); - l.a.y() = coord_t(round(cos_angle * ay + sin_angle * ax)); - double bx = double(l.b.x()); - double by = double(l.b.y()); - l.b.x() = coord_t(round(cos_angle * bx - sin_angle * by)); - l.b.y() = coord_t(round(cos_angle * by + sin_angle * bx)); - } + std::vector lows; + std::vector highs; }; - Polygons expanded_bridged_area{}; - { - polygons_rotate(bridged_area, bridging_angle); - lines_rotate(anchors_and_walls, cos(bridging_angle), sin(bridging_angle)); - BoundingBox bb_x = get_extents(bridged_area); - BoundingBox bb_y = get_extents(anchors_and_walls); + auto segments_overlap = [](coord_t alow, coord_t ahigh, coord_t blow, coord_t bhigh) { + return (alow >= blow && alow <= bhigh) || (ahigh >= blow && ahigh <= bhigh) || + (blow >= alow && blow <= ahigh) || (bhigh >= alow && bhigh <= ahigh); + }; - const size_t n_vlines = (bb_x.max.x() - bb_x.min.x() + flow.scaled_spacing() - 1) / flow.scaled_spacing(); - std::vector vertical_lines(n_vlines); - for (size_t i = 0; i < n_vlines; i++) { - coord_t x = bb_x.min.x() + i * flow.scaled_spacing(); - coord_t y_min = bb_y.min.y() - flow.scaled_spacing(); - coord_t y_max = bb_y.max.y() + flow.scaled_spacing(); - vertical_lines[i].a = Point{x, y_min}; - vertical_lines[i].b = Point{x, y_max}; - } + std::vector current_traced_polys; + for (const auto &layer : polygon_sections) { + std::unordered_set *> used_segments; + for (TracedPoly &traced_poly : current_traced_polys) { + auto maybe_first_overlap = std::upper_bound(layer.begin(), layer.end(), traced_poly.lows.back(), + [](const Point &low, const std::pair &seg) { + return seg.second.y() > low.y(); + }); - auto anchors_and_walls_tree = AABBTreeLines::LinesDistancer{std::move(anchors_and_walls)}; - auto bridged_area_tree = AABBTreeLines::LinesDistancer{to_lines(bridged_area)}; - - std::vector>> polygon_sections(n_vlines); - for (size_t i = 0; i < n_vlines; i++) { - auto area_intersections = bridged_area_tree.intersections_with_line(vertical_lines[i]); - if (area_intersections.size() < 2) { - if (area_intersections.size() > 0) { - polygon_sections[i].emplace_back(area_intersections[0].first, area_intersections[0].first); - } - continue; - } - auto anchors_intersections = anchors_and_walls_tree.intersections_with_line(vertical_lines[i]); - for (const auto &intersection : area_intersections) { - auto high_b = std::upper_bound(anchors_intersections.begin(), anchors_intersections.end(), intersection, - [](const std::pair left, - const std::pair right) { - return left.first.y() > right.first.y(); - }); - Point low, high; - if (high_b == anchors_intersections.end()) { - assert(false); // should not happen - continue; - } else if (high_b == anchors_intersections.begin()) { - low = high_b->first; - high = (++high_b)->first; - } else { - low = (--high_b)->first; - high = high_b->first; - } - - if (polygon_sections[i].size() > 0 && polygon_sections[i].back().second.y() >= low.y()) { - polygon_sections[i].back().second = high; - } else { - polygon_sections[i].emplace_back(low, high); - } + if (maybe_first_overlap != layer.end() && // segment exists + segments_overlap(traced_poly.lows.back().y(), traced_poly.highs.back().y(), + maybe_first_overlap->first.y(), + maybe_first_overlap->second.y())) // segment is overlapping + { + // Overlapping segment. In that case, add it + // to the traced polygon and add segment to used segments + traced_poly.lows.push_back(maybe_first_overlap->first - Point{flow.scaled_spacing() / 2, 0}); + traced_poly.lows.push_back(maybe_first_overlap->first + Point{flow.scaled_spacing() / 2, 0}); + traced_poly.highs.push_back(maybe_first_overlap->second - Point{flow.scaled_spacing() / 2, 0}); + traced_poly.highs.push_back(maybe_first_overlap->second + Point{flow.scaled_spacing() / 2, 0}); + used_segments.insert(&(*maybe_first_overlap)); + } else { + // Zero or multiple overlapping segments. Resolving this is nontrivial, + // so we just close this polygon and maybe open several new. This will hopefully happen much less often + Polygon &new_poly = expanded_bridged_area.emplace_back(std::move(traced_poly.lows)); + new_poly.points.insert(new_poly.points.end(), traced_poly.highs.rbegin(), traced_poly.highs.rend()); + traced_poly.lows.clear(); + traced_poly.highs.clear(); } } - //reconstruct polygon from polygon sections - struct TracedPoly { - std::vector lows; - std::vector highs; - }; - - std::vector traced_polys; - for (const auto& layer : polygon_sections) { - for () + std::remove_if(current_traced_polys.begin(), current_traced_polys.end(), + [](const TracedPoly &tp) { return tp.lows.empty(); }); + + for (const auto &segment : layer) { + if (used_segments.find(&segment) != used_segments.end()) { + TracedPoly &new_tp = current_traced_polys.emplace_back(); + new_tp.lows.push_back(segment.first - Point{flow.scaled_spacing() / 2, 0}); + new_tp.lows.push_back(segment.first + Point{flow.scaled_spacing() / 2, 0}); + new_tp.highs.push_back(segment.second - Point{flow.scaled_spacing() / 2, 0}); + new_tp.highs.push_back(segment.second + Point{flow.scaled_spacing() / 2, 0}); + } } + } - + // add not closed polys + for (TracedPoly &traced_poly : current_traced_polys) { + Polygon &new_poly = expanded_bridged_area.emplace_back(std::move(traced_poly.lows)); + new_poly.points.insert(new_poly.points.end(), traced_poly.highs.rbegin(), traced_poly.highs.rend()); } } - } // surface iteration end - } // island iteration end - } // layer iteration end - ); + expand_area = diff(expand_area, expanded_bridged_area); + + expanded_briding_surfaces[candidates.first].emplace_back(candidate, expanded_bridged_area, surface_to_region[candidate], + bridging_angle); + } + } + } + // }); + + BOOST_LOG_TRIVIAL(info) << "Bridge over infill - Directions and expanded surfaces computed" << log_memory_info(); + + tbb::parallel_for(tbb::blocked_range(0, this->layers().size()), + [po = this, &expanded_briding_surfaces](tbb::blocked_range r) { + for (size_t lidx = r.begin(); lidx < r.end(); lidx++) { + Layer *layer = po->get_layer(lidx); + + for (const LayerSlice &slice : layer->lslices_ex) { + if (const auto &modified_surfaces = expanded_briding_surfaces.find(&slice); + modified_surfaces != expanded_briding_surfaces.end()) { + std::unordered_set regions_to_check; + for (const LayerIsland &island : slice.islands) { + regions_to_check.insert(island.perimeters.region()); + if (!island.fill_expolygons_composite()) { + regions_to_check.insert(island.fill_region_id); + } + } + + Polygons cut_from_infill{}; + for (const auto &surface : modified_surfaces->second) { + cut_from_infill.insert(cut_from_infill.end(), surface.new_polys.begin(), surface.new_polys.end()); + } + + for (size_t region_idx : regions_to_check) { + LayerRegion *region = layer->get_region(region_idx); + Surfaces new_surfaces; + + for (const ModifiedSurface &s : modified_surfaces->second) { + for (Surface &surface : region->m_fill_surfaces.surfaces) { + if (s.original_surface == &surface) { + Surface tmp(surface, {}); + tmp.surface_type = stInternalBridge; + tmp.bridge_angle = s.bridge_angle; + for (const ExPolygon &expoly : union_ex(s.new_polys)) { + new_surfaces.emplace_back(tmp, expoly); + } + surface.clear(); + } else if (surface.surface_type == stInternal) { + Surface tmp(surface, {}); + for (const ExPolygon &expoly : diff_ex(surface.expolygon, cut_from_infill)) { + new_surfaces.emplace_back(tmp, expoly); + } + surface.clear(); + } + } + } + region->m_fill_surfaces.surfaces.insert(region->m_fill_surfaces.surfaces.end(), + new_surfaces.begin(), new_surfaces.end()); + std::remove_if(region->m_fill_surfaces.begin(), region->m_fill_surfaces.end(), + [](const Surface &s) { return s.empty(); }); + } + } + } + } + }); BOOST_LOG_TRIVIAL(info) << "Bridge over infill - End" << log_memory_info(); } // void PrintObject::bridge_over_infill() -void a() -{ - std::vector sparse_infill_regions; - for (size_t region_id = 0; region_id < this->num_printing_regions(); ++ region_id) - if (const PrintRegion ®ion = this->printing_region(region_id); region.config().fill_density.value < 100) - sparse_infill_regions.emplace_back(region_id); - if (this->layer_count() < 2 || sparse_infill_regions.empty()) - return; +// void a() +// { +// std::vector sparse_infill_regions; +// for (size_t region_id = 0; region_id < this->num_printing_regions(); ++ region_id) +// if (const PrintRegion ®ion = this->printing_region(region_id); region.config().fill_density.value < 100) +// sparse_infill_regions.emplace_back(region_id); +// if (this->layer_count() < 2 || sparse_infill_regions.empty()) +// return; - // Collect sum of all internal (sparse infill) regions, because - // 1) layerm->fill_surfaces.will be modified in parallel. - // 2) the parallel loop works on a sum of surfaces over regions anyways, thus collecting the sparse infill surfaces - // up front is an optimization. - std::vector internals; - internals.reserve(this->layer_count()); - for (Layer *layer : m_layers) { - Polygons sum; - for (const LayerRegion *layerm : layer->m_regions) - layerm->fill_surfaces().filter_by_type(stInternal, &sum); - internals.emplace_back(std::move(sum)); - } +// // Collect sum of all internal (sparse infill) regions, because +// // 1) layerm->fill_surfaces.will be modified in parallel. +// // 2) the parallel loop works on a sum of surfaces over regions anyways, thus collecting the sparse infill surfaces +// // up front is an optimization. +// std::vector internals; +// internals.reserve(this->layer_count()); +// for (Layer *layer : m_layers) { +// Polygons sum; +// for (const LayerRegion *layerm : layer->m_regions) +// layerm->fill_surfaces().filter_by_type(stInternal, &sum); +// internals.emplace_back(std::move(sum)); +// } - // Process all regions and layers in parallel. - tbb::parallel_for(tbb::blocked_range(0, sparse_infill_regions.size() * (this->layer_count() - 1), sparse_infill_regions.size()), - [this, &sparse_infill_regions, &internals] - (const tbb::blocked_range &range) { - for (size_t task_id = range.begin(); task_id != range.end(); ++ task_id) { - const size_t layer_id = (task_id / sparse_infill_regions.size()) + 1; - const size_t region_id = sparse_infill_regions[task_id % sparse_infill_regions.size()]; - Layer *layer = this->get_layer(layer_id); - LayerRegion *layerm = layer->m_regions[region_id]; - Flow bridge_flow = layerm->bridging_flow(frSolidInfill); +// // Process all regions and layers in parallel. +// tbb::parallel_for(tbb::blocked_range(0, sparse_infill_regions.size() * (this->layer_count() - 1), sparse_infill_regions.size()), +// [this, &sparse_infill_regions, &internals] +// (const tbb::blocked_range &range) { +// for (size_t task_id = range.begin(); task_id != range.end(); ++ task_id) { +// const size_t layer_id = (task_id / sparse_infill_regions.size()) + 1; +// const size_t region_id = sparse_infill_regions[task_id % sparse_infill_regions.size()]; +// Layer *layer = this->get_layer(layer_id); +// LayerRegion *layerm = layer->m_regions[region_id]; +// Flow bridge_flow = layerm->bridging_flow(frSolidInfill); - // Extract the stInternalSolid surfaces that might be transformed into bridges. - ExPolygons internal_solid; - layerm->m_fill_surfaces.remove_type(stInternalSolid, &internal_solid); - if (internal_solid.empty()) - // No internal solid -> no new bridges for this layer region. - continue; +// // Extract the stInternalSolid surfaces that might be transformed into bridges. +// ExPolygons internal_solid; +// layerm->m_fill_surfaces.remove_type(stInternalSolid, &internal_solid); +// if (internal_solid.empty()) +// // No internal solid -> no new bridges for this layer region. +// continue; - // check whether the lower area is deep enough for absorbing the extra flow - // (for obvious physical reasons but also for preventing the bridge extrudates - // from overflowing in 3D preview) - ExPolygons to_bridge; - { - Polygons to_bridge_pp = to_polygons(internal_solid); - // Iterate through lower layers spanned by bridge_flow. - double bottom_z = layer->print_z - bridge_flow.height() - EPSILON; - for (auto i = int(layer_id) - 1; i >= 0; -- i) { - // Stop iterating if layer is lower than bottom_z. - if (m_layers[i]->print_z < bottom_z) - break; - // Intersect lower sparse infills with the candidate solid surfaces. - to_bridge_pp = intersection(to_bridge_pp, internals[i]); - } - // there's no point in bridging too thin/short regions - //FIXME Vojtech: The offset2 function is not a geometric offset, - // therefore it may create 1) gaps, and 2) sharp corners, which are outside the original contour. - // The gaps will be filled by a separate region, which makes the infill less stable and it takes longer. - { - float min_width = float(bridge_flow.scaled_width()) * 3.f; - to_bridge_pp = opening(to_bridge_pp, min_width); - } +// // check whether the lower area is deep enough for absorbing the extra flow +// // (for obvious physical reasons but also for preventing the bridge extrudates +// // from overflowing in 3D preview) +// ExPolygons to_bridge; +// { +// Polygons to_bridge_pp = to_polygons(internal_solid); +// // Iterate through lower layers spanned by bridge_flow. +// double bottom_z = layer->print_z - bridge_flow.height() - EPSILON; +// for (auto i = int(layer_id) - 1; i >= 0; -- i) { +// // Stop iterating if layer is lower than bottom_z. +// if (m_layers[i]->print_z < bottom_z) +// break; +// // Intersect lower sparse infills with the candidate solid surfaces. +// to_bridge_pp = intersection(to_bridge_pp, internals[i]); +// } +// // there's no point in bridging too thin/short regions +// //FIXME Vojtech: The offset2 function is not a geometric offset, +// // therefore it may create 1) gaps, and 2) sharp corners, which are outside the original contour. +// // The gaps will be filled by a separate region, which makes the infill less stable and it takes longer. +// { +// float min_width = float(bridge_flow.scaled_width()) * 3.f; +// to_bridge_pp = opening(to_bridge_pp, min_width); +// } - if (to_bridge_pp.empty()) { - // Restore internal_solid surfaces. - for (ExPolygon &ex : internal_solid) - layerm->m_fill_surfaces.surfaces.push_back(Surface(stInternalSolid, std::move(ex))); - continue; - } - // convert into ExPolygons - to_bridge = union_ex(to_bridge_pp); - } +// if (to_bridge_pp.empty()) { +// // Restore internal_solid surfaces. +// for (ExPolygon &ex : internal_solid) +// layerm->m_fill_surfaces.surfaces.push_back(Surface(stInternalSolid, std::move(ex))); +// continue; +// } +// // convert into ExPolygons +// to_bridge = union_ex(to_bridge_pp); +// } - #ifdef SLIC3R_DEBUG - printf("Bridging %zu internal areas at layer %zu\n", to_bridge.size(), layer->id()); - #endif +// #ifdef SLIC3R_DEBUG +// printf("Bridging %zu internal areas at layer %zu\n", to_bridge.size(), layer->id()); +// #endif - // compute the remaning internal solid surfaces as difference - ExPolygons not_to_bridge = diff_ex(internal_solid, to_bridge, ApplySafetyOffset::Yes); - to_bridge = intersection_ex(to_bridge, internal_solid, ApplySafetyOffset::Yes); - // build the new collection of fill_surfaces - for (ExPolygon &ex : to_bridge) - layerm->m_fill_surfaces.surfaces.push_back(Surface(stInternalBridge, std::move(ex))); - for (ExPolygon &ex : not_to_bridge) - layerm->m_fill_surfaces.surfaces.push_back(Surface(stInternalSolid, std::move(ex))); - /* - # exclude infill from the layers below if needed - # see discussion at https://github.com/alexrj/Slic3r/issues/240 - # Update: do not exclude any infill. Sparse infill is able to absorb the excess material. - if (0) { - my $excess = $layerm->extruders->{infill}->bridge_flow->width - $layerm->height; - for (my $i = $layer_id-1; $excess >= $self->get_layer($i)->height; $i--) { - Slic3r::debugf " skipping infill below those areas at layer %d\n", $i; - foreach my $lower_layerm (@{$self->get_layer($i)->regions}) { - my @new_surfaces = (); - # subtract the area from all types of surfaces - foreach my $group (@{$lower_layerm->fill_surfaces->group}) { - push @new_surfaces, map $group->[0]->clone(expolygon => $_), - @{diff_ex( - [ map $_->p, @$group ], - [ map @$_, @$to_bridge ], - )}; - push @new_surfaces, map Slic3r::Surface->new( - expolygon => $_, - surface_type => stInternalVoid, - ), @{intersection_ex( - [ map $_->p, @$group ], - [ map @$_, @$to_bridge ], - )}; - } - $lower_layerm->fill_surfaces->clear; - $lower_layerm->fill_surfaces->append($_) for @new_surfaces; - } - - $excess -= $self->get_layer($i)->height; - } - } - */ -#ifdef SLIC3R_DEBUG_SLICE_PROCESSING - layerm->export_region_slices_to_svg_debug("7_bridge_over_infill"); - layerm->export_region_fill_surfaces_to_svg_debug("7_bridge_over_infill"); -#endif /* SLIC3R_DEBUG_SLICE_PROCESSING */ - m_print->throw_if_canceled(); - } - }); +// // compute the remaning internal solid surfaces as difference +// ExPolygons not_to_bridge = diff_ex(internal_solid, to_bridge, ApplySafetyOffset::Yes); +// to_bridge = intersection_ex(to_bridge, internal_solid, ApplySafetyOffset::Yes); +// // build the new collection of fill_surfaces +// for (ExPolygon &ex : to_bridge) +// layerm->m_fill_surfaces.surfaces.push_back(Surface(stInternalBridge, std::move(ex))); +// for (ExPolygon &ex : not_to_bridge) +// layerm->m_fill_surfaces.surfaces.push_back(Surface(stInternalSolid, std::move(ex))); +// /* +// # exclude infill from the layers below if needed +// # see discussion at https://github.com/alexrj/Slic3r/issues/240 +// # Update: do not exclude any infill. Sparse infill is able to absorb the excess material. +// if (0) { +// my $excess = $layerm->extruders->{infill}->bridge_flow->width - $layerm->height; +// for (my $i = $layer_id-1; $excess >= $self->get_layer($i)->height; $i--) { +// Slic3r::debugf " skipping infill below those areas at layer %d\n", $i; +// foreach my $lower_layerm (@{$self->get_layer($i)->regions}) { +// my @new_surfaces = (); +// # subtract the area from all types of surfaces +// foreach my $group (@{$lower_layerm->fill_surfaces->group}) { +// push @new_surfaces, map $group->[0]->clone(expolygon => $_), +// @{diff_ex( +// [ map $_->p, @$group ], +// [ map @$_, @$to_bridge ], +// )}; +// push @new_surfaces, map Slic3r::Surface->new( +// expolygon => $_, +// surface_type => stInternalVoid, +// ), @{intersection_ex( +// [ map $_->p, @$group ], +// [ map @$_, @$to_bridge ], +// )}; +// } +// $lower_layerm->fill_surfaces->clear; +// $lower_layerm->fill_surfaces->append($_) for @new_surfaces; +// } -} // void PrintObject::bridge_over_infill() +// $excess -= $self->get_layer($i)->height; +// } +// } +// */ +// #ifdef SLIC3R_DEBUG_SLICE_PROCESSING +// layerm->export_region_slices_to_svg_debug("7_bridge_over_infill"); +// layerm->export_region_fill_surfaces_to_svg_debug("7_bridge_over_infill"); +// #endif /* SLIC3R_DEBUG_SLICE_PROCESSING */ +// m_print->throw_if_canceled(); +// } +// }); + +// } // void PrintObject::bridge_over_infill() static void clamp_exturder_to_default(ConfigOptionInt &opt, size_t num_extruders) { From dfbea5976e3c13870db7932e72bfcc054a071c39 Mon Sep 17 00:00:00 2001 From: PavelMikus Date: Mon, 13 Feb 2023 17:47:54 +0100 Subject: [PATCH 26/48] bunch of bug fixes, but still does not work --- src/libslic3r/PrintObject.cpp | 79 +++++++++++++++++++++-------------- 1 file changed, 47 insertions(+), 32 deletions(-) diff --git a/src/libslic3r/PrintObject.cpp b/src/libslic3r/PrintObject.cpp index b34d4ee89..83bd7d329 100644 --- a/src/libslic3r/PrintObject.cpp +++ b/src/libslic3r/PrintObject.cpp @@ -1680,7 +1680,7 @@ void PrintObject::bridge_over_infill() Lines anchors_and_walls = to_lines(anchors); Lines tmp = to_lines(max_area); - tmp.insert(anchors_and_walls.end(), tmp.begin(), tmp.end()); + anchors_and_walls.insert(anchors_and_walls.end(), tmp.begin(), tmp.end()); double bridging_angle = 0; { @@ -1700,7 +1700,7 @@ void PrintObject::bridge_over_infill() Point a = (start + v * (i * step_size)).cast(); auto [distance, index, p] = lines_tree.distance_from_lines_extra(a); const Line &l = lines_tree.get_line(index); - directions_with_distances.emplace_back(PI - l.direction(), unscaled(distance)); + directions_with_distances.emplace_back(PI - l.direction(), distance); } } } @@ -1713,7 +1713,11 @@ void PrintObject::bridge_over_infill() bridging_angle += dir.first * (max_dist - dir.second); acc += (max_dist - dir.second); } - bridging_angle /= acc; + if (acc <= EPSILON || bridging_angle == 0) { + bridging_angle = 0.01; + } else { + bridging_angle /= acc; + } } // TODO maybe get extens of rotated max_area, then fill with vertical lines, make AABB tree rotated for anchors and @@ -1733,6 +1737,11 @@ void PrintObject::bridge_over_infill() } }; + auto segments_overlap = [](coord_t alow, coord_t ahigh, coord_t blow, coord_t bhigh) { + return (alow >= blow && alow <= bhigh) || (ahigh >= blow && ahigh <= bhigh) || (blow >= alow && blow <= ahigh) || + (bhigh >= alow && bhigh <= ahigh); + }; + Polygons expanded_bridged_area{}; { polygons_rotate(bridged_area, bridging_angle); @@ -1756,37 +1765,48 @@ void PrintObject::bridge_over_infill() std::vector>> polygon_sections(n_vlines); for (size_t i = 0; i < n_vlines; i++) { auto area_intersections = bridged_area_tree.intersections_with_line(vertical_lines[i]); - if (area_intersections.size() < 2) { - if (area_intersections.size() > 0) { - polygon_sections[i].emplace_back(area_intersections[0].first, area_intersections[0].first); + for (int intersection_idx = 0; intersection_idx < int(area_intersections.size()) - 1; intersection_idx++) { + if (!bridged_area_tree.outside( + (area_intersections[intersection_idx].first + area_intersections[intersection_idx + 1].first) / 2)) { + polygon_sections[i].emplace_back(area_intersections[intersection_idx].first, + area_intersections[intersection_idx + 1].first); } - continue; } auto anchors_intersections = anchors_and_walls_tree.intersections_with_line(vertical_lines[i]); - for (const auto &intersection : area_intersections) { - auto high_b = std::upper_bound(anchors_intersections.begin(), anchors_intersections.end(), intersection, - [](const std::pair left, - const std::pair right) { - return left.first.y() > right.first.y(); - }); - Point low, high; - if (high_b == anchors_intersections.end()) { - assert(false); // should not happen - continue; - } else if (high_b == anchors_intersections.begin()) { - low = high_b->first; - high = (++high_b)->first; - } else { - low = (--high_b)->first; - high = high_b->first; + + for (std::pair §ion : polygon_sections[i]) { + auto maybe_below_anchor = std::upper_bound(anchors_intersections.begin(), anchors_intersections.end(), + section.first, + [](const Point &a, const std::pair &b) { + return a.y() < b.first.y(); + }); + if (maybe_below_anchor != anchors_intersections.begin() && + maybe_below_anchor != anchors_intersections.end()) { + section.first = (--maybe_below_anchor)->first; } - if (polygon_sections[i].size() > 0 && polygon_sections[i].back().second.y() >= low.y()) { - polygon_sections[i].back().second = high; - } else { - polygon_sections[i].emplace_back(low, high); + auto maybe_upper_anchor = std::upper_bound(anchors_intersections.begin(), anchors_intersections.end(), + section.second, + [](const Point &a, const std::pair &b) { + return a.y() < b.first.y(); + }); + if (maybe_upper_anchor != anchors_intersections.end()) { + section.second = maybe_upper_anchor->first; } } + + for (int section_idx = 0; section_idx < int(polygon_sections[i].size()) - 1; section_idx++) { + std::pair §ion_a = polygon_sections[i][section_idx]; + std::pair §ion_b = polygon_sections[i][section_idx + 1]; + if (segments_overlap(section_a.first.y(), section_a.second.y(), section_b.first.y(), section_b.second.y())) { + section_b.first = section_a.first.y() < section_b.first.y() ? section_a.first : section_b.first; + section_b.second = section_a.second.y() < section_b.second.y() ? section_b.second : section_a.second; + section_a.first = section_a.second; + } + } + + std::remove_if(polygon_sections[i].begin(), polygon_sections[i].end(), + [](const std::pair &s) { return s.first == s.second; }); } // reconstruct polygon from polygon sections @@ -1796,11 +1816,6 @@ void PrintObject::bridge_over_infill() std::vector highs; }; - auto segments_overlap = [](coord_t alow, coord_t ahigh, coord_t blow, coord_t bhigh) { - return (alow >= blow && alow <= bhigh) || (ahigh >= blow && ahigh <= bhigh) || - (blow >= alow && blow <= ahigh) || (bhigh >= alow && bhigh <= ahigh); - }; - std::vector current_traced_polys; for (const auto &layer : polygon_sections) { std::unordered_set *> used_segments; From df20302eefc6c1c11e862d432caf1043373f513b Mon Sep 17 00:00:00 2001 From: PavelMikus Date: Tue, 14 Feb 2023 11:57:43 +0100 Subject: [PATCH 27/48] various bug fixes debug images --- src/libslic3r/PrintObject.cpp | 102 +++++++++++++++++++++++----------- 1 file changed, 71 insertions(+), 31 deletions(-) diff --git a/src/libslic3r/PrintObject.cpp b/src/libslic3r/PrintObject.cpp index 83bd7d329..848ce36ea 100644 --- a/src/libslic3r/PrintObject.cpp +++ b/src/libslic3r/PrintObject.cpp @@ -38,6 +38,7 @@ #include #include #include +#include #include #include #include @@ -70,6 +71,8 @@ using namespace std::literals; #include #endif + #include "SVG.hpp" + namespace Slic3r { // Constructor is called from the main thread, therefore all Model / ModelObject / ModelIntance data are valid. @@ -1530,6 +1533,25 @@ void PrintObject::discover_vertical_shells() } // for each region } // void PrintObject::discover_vertical_shells() +#define DEBUG_BRIDGE_OVER_INFILL +#ifdef DEBUG_BRIDGE_OVER_INFILL +template void debug_draw(std::string name, const T& a, const T& b, const T& c, const T& d) +{ + std::vector colors = {"red", "blue", "orange", "green"}; + BoundingBox bbox = get_extents(a); + bbox.merge(get_extents(b)); + bbox.merge(get_extents(c)); + bbox.merge(get_extents(d)); + bbox.offset(scale_(1.)); + ::Slic3r::SVG svg(debug_out_path(name.c_str()).c_str(), bbox); + svg.draw(a, colors[0], scale_(0.3)); + svg.draw(b, colors[1], scale_(0.23)); + svg.draw(c, colors[2], scale_(0.16)); + svg.draw(d, colors[3], scale_(0.10)); + svg.Close(); +} +#endif + // This method applies bridge flow to the first internal solid layer above sparse infill. void PrintObject::bridge_over_infill() { @@ -1682,6 +1704,11 @@ void PrintObject::bridge_over_infill() Lines tmp = to_lines(max_area); anchors_and_walls.insert(anchors_and_walls.end(), tmp.begin(), tmp.end()); +#ifdef DEBUG_BRIDGE_OVER_INFILL + debug_draw("candidate" + std::to_string(lidx), to_lines(candidate->expolygon), to_lines(bridged_area), + to_lines(max_area), (anchors_and_walls)); +#endif + double bridging_angle = 0; { AABBTreeLines::LinesDistancer lines_tree{anchors_and_walls}; @@ -1714,7 +1741,7 @@ void PrintObject::bridge_over_infill() acc += (max_dist - dir.second); } if (acc <= EPSILON || bridging_angle == 0) { - bridging_angle = 0.01; + bridging_angle = 0.001; } else { bridging_angle /= acc; } @@ -1762,51 +1789,56 @@ void PrintObject::bridge_over_infill() auto anchors_and_walls_tree = AABBTreeLines::LinesDistancer{std::move(anchors_and_walls)}; auto bridged_area_tree = AABBTreeLines::LinesDistancer{to_lines(bridged_area)}; - std::vector>> polygon_sections(n_vlines); +#ifdef DEBUG_BRIDGE_OVER_INFILL + debug_draw("sliced" + std::to_string(lidx), to_lines(bridged_area), anchors_and_walls, + vertical_lines, {}); +#endif + + std::vector> polygon_sections(n_vlines); for (size_t i = 0; i < n_vlines; i++) { auto area_intersections = bridged_area_tree.intersections_with_line(vertical_lines[i]); for (int intersection_idx = 0; intersection_idx < int(area_intersections.size()) - 1; intersection_idx++) { - if (!bridged_area_tree.outside( - (area_intersections[intersection_idx].first + area_intersections[intersection_idx + 1].first) / 2)) { + if (bridged_area_tree.outside( + (area_intersections[intersection_idx].first + area_intersections[intersection_idx + 1].first) / 2) < 0) { polygon_sections[i].emplace_back(area_intersections[intersection_idx].first, area_intersections[intersection_idx + 1].first); } } auto anchors_intersections = anchors_and_walls_tree.intersections_with_line(vertical_lines[i]); - for (std::pair §ion : polygon_sections[i]) { + for (Line §ion : polygon_sections[i]) { auto maybe_below_anchor = std::upper_bound(anchors_intersections.begin(), anchors_intersections.end(), - section.first, + section.a, [](const Point &a, const std::pair &b) { return a.y() < b.first.y(); }); if (maybe_below_anchor != anchors_intersections.begin() && maybe_below_anchor != anchors_intersections.end()) { - section.first = (--maybe_below_anchor)->first; + section.a = (--maybe_below_anchor)->first; } auto maybe_upper_anchor = std::upper_bound(anchors_intersections.begin(), anchors_intersections.end(), - section.second, + section.b, [](const Point &a, const std::pair &b) { return a.y() < b.first.y(); }); if (maybe_upper_anchor != anchors_intersections.end()) { - section.second = maybe_upper_anchor->first; + section.b = maybe_upper_anchor->first; } } for (int section_idx = 0; section_idx < int(polygon_sections[i].size()) - 1; section_idx++) { - std::pair §ion_a = polygon_sections[i][section_idx]; - std::pair §ion_b = polygon_sections[i][section_idx + 1]; - if (segments_overlap(section_a.first.y(), section_a.second.y(), section_b.first.y(), section_b.second.y())) { - section_b.first = section_a.first.y() < section_b.first.y() ? section_a.first : section_b.first; - section_b.second = section_a.second.y() < section_b.second.y() ? section_b.second : section_a.second; - section_a.first = section_a.second; + Line §ion_a = polygon_sections[i][section_idx]; + Line §ion_b = polygon_sections[i][section_idx + 1]; + if (segments_overlap(section_a.a.y(), section_a.b.y(), section_b.a.y(), section_b.b.y())) { + section_b.a = section_a.a.y() < section_b.a.y() ? section_a.a : section_b.a; + section_b.b = section_a.b.y() < section_b.b.y() ? section_b.b : section_a.b; + section_a.a = section_a.b; } } std::remove_if(polygon_sections[i].begin(), polygon_sections[i].end(), - [](const std::pair &s) { return s.first == s.second; }); + [](const Line &s) { return s.a == s.b; }); } // reconstruct polygon from polygon sections @@ -1818,24 +1850,23 @@ void PrintObject::bridge_over_infill() std::vector current_traced_polys; for (const auto &layer : polygon_sections) { - std::unordered_set *> used_segments; + std::unordered_set used_segments; for (TracedPoly &traced_poly : current_traced_polys) { auto maybe_first_overlap = std::upper_bound(layer.begin(), layer.end(), traced_poly.lows.back(), - [](const Point &low, const std::pair &seg) { - return seg.second.y() > low.y(); + [](const Point &low, const Line &seg) { + return seg.b.y() > low.y(); }); if (maybe_first_overlap != layer.end() && // segment exists - segments_overlap(traced_poly.lows.back().y(), traced_poly.highs.back().y(), - maybe_first_overlap->first.y(), - maybe_first_overlap->second.y())) // segment is overlapping + segments_overlap(traced_poly.lows.back().y(), traced_poly.highs.back().y(), maybe_first_overlap->a.y(), + maybe_first_overlap->b.y())) // segment is overlapping { // Overlapping segment. In that case, add it // to the traced polygon and add segment to used segments - traced_poly.lows.push_back(maybe_first_overlap->first - Point{flow.scaled_spacing() / 2, 0}); - traced_poly.lows.push_back(maybe_first_overlap->first + Point{flow.scaled_spacing() / 2, 0}); - traced_poly.highs.push_back(maybe_first_overlap->second - Point{flow.scaled_spacing() / 2, 0}); - traced_poly.highs.push_back(maybe_first_overlap->second + Point{flow.scaled_spacing() / 2, 0}); + traced_poly.lows.push_back(maybe_first_overlap->a - Point{flow.scaled_spacing() / 2, 0}); + traced_poly.lows.push_back(maybe_first_overlap->a + Point{flow.scaled_spacing() / 2, 0}); + traced_poly.highs.push_back(maybe_first_overlap->b - Point{flow.scaled_spacing() / 2, 0}); + traced_poly.highs.push_back(maybe_first_overlap->b + Point{flow.scaled_spacing() / 2, 0}); used_segments.insert(&(*maybe_first_overlap)); } else { // Zero or multiple overlapping segments. Resolving this is nontrivial, @@ -1851,12 +1882,12 @@ void PrintObject::bridge_over_infill() [](const TracedPoly &tp) { return tp.lows.empty(); }); for (const auto &segment : layer) { - if (used_segments.find(&segment) != used_segments.end()) { + if (used_segments.find(&segment) == used_segments.end()) { TracedPoly &new_tp = current_traced_polys.emplace_back(); - new_tp.lows.push_back(segment.first - Point{flow.scaled_spacing() / 2, 0}); - new_tp.lows.push_back(segment.first + Point{flow.scaled_spacing() / 2, 0}); - new_tp.highs.push_back(segment.second - Point{flow.scaled_spacing() / 2, 0}); - new_tp.highs.push_back(segment.second + Point{flow.scaled_spacing() / 2, 0}); + new_tp.lows.push_back(segment.a - Point{flow.scaled_spacing() / 2, 0}); + new_tp.lows.push_back(segment.a + Point{flow.scaled_spacing() / 2, 0}); + new_tp.highs.push_back(segment.b - Point{flow.scaled_spacing() / 2, 0}); + new_tp.highs.push_back(segment.b + Point{flow.scaled_spacing() / 2, 0}); } } } @@ -1866,6 +1897,15 @@ void PrintObject::bridge_over_infill() Polygon &new_poly = expanded_bridged_area.emplace_back(std::move(traced_poly.lows)); new_poly.points.insert(new_poly.points.end(), traced_poly.highs.rbegin(), traced_poly.highs.rend()); } + +#ifdef DEBUG_BRIDGE_OVER_INFILL + Lines l{}; + for (const auto &s : polygon_sections) { + l.insert(l.end(), s.begin(), s.end()); + } + debug_draw("reconstructed" + std::to_string(lidx), l, anchors_and_walls_tree.get_lines(), to_lines(expanded_bridged_area), + bridged_area_tree.get_lines()); +#endif } expand_area = diff(expand_area, expanded_bridged_area); From 9f5f03099e3b7d17f4c5a4d720d48e8a42235eed Mon Sep 17 00:00:00 2001 From: PavelMikus Date: Tue, 14 Feb 2023 13:08:24 +0100 Subject: [PATCH 28/48] fixing bridging angle, avoiding too small areas, fixing final area rotation --- src/libslic3r/PrintObject.cpp | 138 +++++++++++++++++++--------------- 1 file changed, 78 insertions(+), 60 deletions(-) diff --git a/src/libslic3r/PrintObject.cpp b/src/libslic3r/PrintObject.cpp index 848ce36ea..b55baa6b0 100644 --- a/src/libslic3r/PrintObject.cpp +++ b/src/libslic3r/PrintObject.cpp @@ -1597,6 +1597,12 @@ void PrintObject::bridge_over_infill() for (size_t region_idx : regions_to_check) { const LayerRegion *region = layer->get_region(region_idx); auto region_internal_solids = region->fill_surfaces().filter_by_type(stInternalSolid); + + //remove very small solid infills, usually not worth it and many of them may not even contain extrusions in the end. + void(std::remove_if(region_internal_solids.begin(), region_internal_solids.end(), [region](const Surface *s) { + float min_width = float(region->bridging_flow(frSolidInfill).scaled_width()) * 3.f; + return opening_ex({s->expolygon}, min_width).empty(); + })); if (!region_internal_solids.empty()) { max_bridge_flow_height[&slice] = std::max(max_bridge_flow_height[&slice], region->bridging_flow(frSolidInfill).height()); @@ -1724,10 +1730,14 @@ void PrintObject::bridge_over_infill() int lines_count = int(std::ceil(dist_to_next / scaled(3.0))); float step_size = dist_to_next / lines_count; for (int i = 0; i < lines_count; ++i) { - Point a = (start + v * (i * step_size)).cast(); + Point a = (start + v * (i * step_size)).cast(); auto [distance, index, p] = lines_tree.distance_from_lines_extra(a); - const Line &l = lines_tree.get_line(index); - directions_with_distances.emplace_back(PI - l.direction(), distance); + double angle = lines_tree.get_line(index).orientation(); + if (angle > PI) { + angle -= PI; + } + angle += PI; + directions_with_distances.emplace_back(angle, distance); } } } @@ -1770,9 +1780,10 @@ void PrintObject::bridge_over_infill() }; Polygons expanded_bridged_area{}; + double aligning_angle = -bridging_angle + PI; { - polygons_rotate(bridged_area, bridging_angle); - lines_rotate(anchors_and_walls, cos(bridging_angle), sin(bridging_angle)); + polygons_rotate(bridged_area, aligning_angle); + lines_rotate(anchors_and_walls, cos(aligning_angle), sin(aligning_angle)); BoundingBox bb_x = get_extents(bridged_area); BoundingBox bb_y = get_extents(anchors_and_walls); @@ -1903,77 +1914,84 @@ void PrintObject::bridge_over_infill() for (const auto &s : polygon_sections) { l.insert(l.end(), s.begin(), s.end()); } - debug_draw("reconstructed" + std::to_string(lidx), l, anchors_and_walls_tree.get_lines(), to_lines(expanded_bridged_area), - bridged_area_tree.get_lines()); + debug_draw("reconstructed" + std::to_string(lidx), l, anchors_and_walls_tree.get_lines(), + to_lines(expanded_bridged_area), bridged_area_tree.get_lines()); #endif } - expand_area = diff(expand_area, expanded_bridged_area); + polygons_rotate(expanded_bridged_area, -aligning_angle); + expanded_bridged_area = intersection(expanded_bridged_area, max_area); + expand_area = diff(expand_area, expanded_bridged_area); expanded_briding_surfaces[candidates.first].emplace_back(candidate, expanded_bridged_area, surface_to_region[candidate], bridging_angle); + +#ifdef DEBUG_BRIDGE_OVER_INFILL + debug_draw("cadidate_added" + std::to_string(lidx), to_lines(expanded_bridged_area), to_lines(bridged_area), + to_lines(max_area), to_lines(expand_area)); +#endif } } } - // }); + // }); - BOOST_LOG_TRIVIAL(info) << "Bridge over infill - Directions and expanded surfaces computed" << log_memory_info(); + BOOST_LOG_TRIVIAL(info) << "Bridge over infill - Directions and expanded surfaces computed" << log_memory_info(); - tbb::parallel_for(tbb::blocked_range(0, this->layers().size()), - [po = this, &expanded_briding_surfaces](tbb::blocked_range r) { - for (size_t lidx = r.begin(); lidx < r.end(); lidx++) { - Layer *layer = po->get_layer(lidx); + tbb::parallel_for(tbb::blocked_range(0, this->layers().size()), [po = this, + &expanded_briding_surfaces](tbb::blocked_range r) { + for (size_t lidx = r.begin(); lidx < r.end(); lidx++) { + Layer *layer = po->get_layer(lidx); - for (const LayerSlice &slice : layer->lslices_ex) { - if (const auto &modified_surfaces = expanded_briding_surfaces.find(&slice); - modified_surfaces != expanded_briding_surfaces.end()) { - std::unordered_set regions_to_check; - for (const LayerIsland &island : slice.islands) { - regions_to_check.insert(island.perimeters.region()); - if (!island.fill_expolygons_composite()) { - regions_to_check.insert(island.fill_region_id); - } - } + for (const LayerSlice &slice : layer->lslices_ex) { + if (const auto &modified_surfaces = expanded_briding_surfaces.find(&slice); + modified_surfaces != expanded_briding_surfaces.end()) { + std::unordered_set regions_to_check; + for (const LayerIsland &island : slice.islands) { + regions_to_check.insert(island.perimeters.region()); + if (!island.fill_expolygons_composite()) { + regions_to_check.insert(island.fill_region_id); + } + } - Polygons cut_from_infill{}; - for (const auto &surface : modified_surfaces->second) { - cut_from_infill.insert(cut_from_infill.end(), surface.new_polys.begin(), surface.new_polys.end()); - } + Polygons cut_from_infill{}; + for (const auto &surface : modified_surfaces->second) { + cut_from_infill.insert(cut_from_infill.end(), surface.new_polys.begin(), surface.new_polys.end()); + } - for (size_t region_idx : regions_to_check) { - LayerRegion *region = layer->get_region(region_idx); - Surfaces new_surfaces; + for (size_t region_idx : regions_to_check) { + LayerRegion *region = layer->get_region(region_idx); + Surfaces new_surfaces; - for (const ModifiedSurface &s : modified_surfaces->second) { - for (Surface &surface : region->m_fill_surfaces.surfaces) { - if (s.original_surface == &surface) { - Surface tmp(surface, {}); - tmp.surface_type = stInternalBridge; - tmp.bridge_angle = s.bridge_angle; - for (const ExPolygon &expoly : union_ex(s.new_polys)) { - new_surfaces.emplace_back(tmp, expoly); - } - surface.clear(); - } else if (surface.surface_type == stInternal) { - Surface tmp(surface, {}); - for (const ExPolygon &expoly : diff_ex(surface.expolygon, cut_from_infill)) { - new_surfaces.emplace_back(tmp, expoly); - } - surface.clear(); - } - } - } - region->m_fill_surfaces.surfaces.insert(region->m_fill_surfaces.surfaces.end(), - new_surfaces.begin(), new_surfaces.end()); - std::remove_if(region->m_fill_surfaces.begin(), region->m_fill_surfaces.end(), - [](const Surface &s) { return s.empty(); }); - } - } - } - } - }); + for (const ModifiedSurface &s : modified_surfaces->second) { + for (Surface &surface : region->m_fill_surfaces.surfaces) { + if (s.original_surface == &surface) { + Surface tmp(surface, {}); + tmp.surface_type = stInternalBridge; + tmp.bridge_angle = s.bridge_angle; + for (const ExPolygon &expoly : union_ex(s.new_polys)) { + new_surfaces.emplace_back(tmp, expoly); + } + surface.clear(); + } else if (surface.surface_type == stInternal) { + Surface tmp(surface, {}); + for (const ExPolygon &expoly : diff_ex(surface.expolygon, cut_from_infill)) { + new_surfaces.emplace_back(tmp, expoly); + } + surface.clear(); + } + } + } + region->m_fill_surfaces.surfaces.insert(region->m_fill_surfaces.surfaces.end(), new_surfaces.begin(), + new_surfaces.end()); + std::remove_if(region->m_fill_surfaces.begin(), region->m_fill_surfaces.end(), + [](const Surface &s) { return s.empty(); }); + } + } + } + } + }); - BOOST_LOG_TRIVIAL(info) << "Bridge over infill - End" << log_memory_info(); + BOOST_LOG_TRIVIAL(info) << "Bridge over infill - End" << log_memory_info(); } // void PrintObject::bridge_over_infill() From 95ec803a06e879526c71dab0ad3c44d8e19b29b2 Mon Sep 17 00:00:00 2001 From: PavelMikus Date: Tue, 14 Feb 2023 14:06:05 +0100 Subject: [PATCH 29/48] Another bunch of fixes. There is still problem with bridging direction --- src/libslic3r/PrintObject.cpp | 25 ++++++++++++------------- 1 file changed, 12 insertions(+), 13 deletions(-) diff --git a/src/libslic3r/PrintObject.cpp b/src/libslic3r/PrintObject.cpp index b55baa6b0..21c19c84d 100644 --- a/src/libslic3r/PrintObject.cpp +++ b/src/libslic3r/PrintObject.cpp @@ -1736,7 +1736,7 @@ void PrintObject::bridge_over_infill() if (angle > PI) { angle -= PI; } - angle += PI; + angle -= PI * 0.5; directions_with_distances.emplace_back(angle, distance); } } @@ -1780,7 +1780,7 @@ void PrintObject::bridge_over_infill() }; Polygons expanded_bridged_area{}; - double aligning_angle = -bridging_angle + PI; + double aligning_angle = -bridging_angle + PI * 0.5; { polygons_rotate(bridged_area, aligning_angle); lines_rotate(anchors_and_walls, cos(aligning_angle), sin(aligning_angle)); @@ -1818,14 +1818,13 @@ void PrintObject::bridge_over_infill() auto anchors_intersections = anchors_and_walls_tree.intersections_with_line(vertical_lines[i]); for (Line §ion : polygon_sections[i]) { - auto maybe_below_anchor = std::upper_bound(anchors_intersections.begin(), anchors_intersections.end(), + auto maybe_below_anchor = std::upper_bound(anchors_intersections.rbegin(), anchors_intersections.rend(), section.a, [](const Point &a, const std::pair &b) { - return a.y() < b.first.y(); + return a.y() > b.first.y(); }); - if (maybe_below_anchor != anchors_intersections.begin() && - maybe_below_anchor != anchors_intersections.end()) { - section.a = (--maybe_below_anchor)->first; + if (maybe_below_anchor != anchors_intersections.rend()) { + section.a = maybe_below_anchor->first; } auto maybe_upper_anchor = std::upper_bound(anchors_intersections.begin(), anchors_intersections.end(), @@ -1848,8 +1847,8 @@ void PrintObject::bridge_over_infill() } } - std::remove_if(polygon_sections[i].begin(), polygon_sections[i].end(), - [](const Line &s) { return s.a == s.b; }); + void(std::remove_if(polygon_sections[i].begin(), polygon_sections[i].end(), + [](const Line &s) { return s.a == s.b; })); } // reconstruct polygon from polygon sections @@ -1889,8 +1888,8 @@ void PrintObject::bridge_over_infill() } } - std::remove_if(current_traced_polys.begin(), current_traced_polys.end(), - [](const TracedPoly &tp) { return tp.lows.empty(); }); + void(std::remove_if(current_traced_polys.begin(), current_traced_polys.end(), + [](const TracedPoly &tp) { return tp.lows.empty(); })); for (const auto &segment : layer) { if (used_segments.find(&segment) == used_segments.end()) { @@ -1983,8 +1982,8 @@ void PrintObject::bridge_over_infill() } region->m_fill_surfaces.surfaces.insert(region->m_fill_surfaces.surfaces.end(), new_surfaces.begin(), new_surfaces.end()); - std::remove_if(region->m_fill_surfaces.begin(), region->m_fill_surfaces.end(), - [](const Surface &s) { return s.empty(); }); + void(std::remove_if(region->m_fill_surfaces.begin(), region->m_fill_surfaces.end(), + [](const Surface &s) { return s.empty(); })); } } } From d843d0d981f91598c25ad375a7629c330d5e5040 Mon Sep 17 00:00:00 2001 From: PavelMikus Date: Tue, 14 Feb 2023 16:06:37 +0100 Subject: [PATCH 30/48] Fixed most of issues, TODO expand by half extrusion width, smoothen sides, crashes --- src/libslic3r/PrintObject.cpp | 17 +++++------------ 1 file changed, 5 insertions(+), 12 deletions(-) diff --git a/src/libslic3r/PrintObject.cpp b/src/libslic3r/PrintObject.cpp index 21c19c84d..dfa2148e2 100644 --- a/src/libslic3r/PrintObject.cpp +++ b/src/libslic3r/PrintObject.cpp @@ -1570,11 +1570,8 @@ void PrintObject::bridge_over_infill() std::unordered_map> expanded_briding_surfaces; - // tbb::parallel_for(tbb::blocked_range(0, this->layers().size()), [po = this, - // &expanded_briding_surfaces](tbb::blocked_range r) { - auto r = tbb::blocked_range{0, this->layer_count()}; - auto po = this; - + tbb::parallel_for(tbb::blocked_range(0, this->layers().size()), [po = this, + &expanded_briding_surfaces](tbb::blocked_range r) { for (size_t lidx = r.begin(); lidx < r.end(); lidx++) { const Layer *layer = po->get_layer(lidx); @@ -1601,7 +1598,7 @@ void PrintObject::bridge_over_infill() //remove very small solid infills, usually not worth it and many of them may not even contain extrusions in the end. void(std::remove_if(region_internal_solids.begin(), region_internal_solids.end(), [region](const Surface *s) { float min_width = float(region->bridging_flow(frSolidInfill).scaled_width()) * 3.f; - return opening_ex({s->expolygon}, min_width).empty(); + return offset_ex({s->expolygon}, -min_width).empty(); })); if (!region_internal_solids.empty()) { max_bridge_flow_height[&slice] = std::max(max_bridge_flow_height[&slice], @@ -1736,7 +1733,7 @@ void PrintObject::bridge_over_infill() if (angle > PI) { angle -= PI; } - angle -= PI * 0.5; + angle += PI * 0.5; directions_with_distances.emplace_back(angle, distance); } } @@ -1757,10 +1754,6 @@ void PrintObject::bridge_over_infill() } } - // TODO maybe get extens of rotated max_area, then fill with vertical lines, make AABB tree rotated for anchors and - // walls and also - // for bridged area - // then cut off the vertical lines, compose the final polygon, and rotate back auto lines_rotate = [](Lines &lines, double cos_angle, double sin_angle) { for (Line &l : lines) { double ax = double(l.a.x()); @@ -1932,7 +1925,7 @@ void PrintObject::bridge_over_infill() } } } - // }); + }); BOOST_LOG_TRIVIAL(info) << "Bridge over infill - Directions and expanded surfaces computed" << log_memory_info(); From 5e83ecf387a445d05d03aff0f39f29ac20ef40e4 Mon Sep 17 00:00:00 2001 From: PavelMikus Date: Wed, 15 Feb 2023 11:54:51 +0100 Subject: [PATCH 31/48] Fixed crashes, smoothened polygon, some bug fixes --- src/libslic3r/PrintObject.cpp | 78 +++++++++++++++++++++++------------ 1 file changed, 51 insertions(+), 27 deletions(-) diff --git a/src/libslic3r/PrintObject.cpp b/src/libslic3r/PrintObject.cpp index dfa2148e2..8b72c292a 100644 --- a/src/libslic3r/PrintObject.cpp +++ b/src/libslic3r/PrintObject.cpp @@ -1593,13 +1593,16 @@ void PrintObject::bridge_over_infill() for (size_t region_idx : regions_to_check) { const LayerRegion *region = layer->get_region(region_idx); - auto region_internal_solids = region->fill_surfaces().filter_by_type(stInternalSolid); + SurfacesPtr region_internal_solids = region->fill_surfaces().filter_by_type(stInternalSolid); - //remove very small solid infills, usually not worth it and many of them may not even contain extrusions in the end. - void(std::remove_if(region_internal_solids.begin(), region_internal_solids.end(), [region](const Surface *s) { - float min_width = float(region->bridging_flow(frSolidInfill).scaled_width()) * 3.f; - return offset_ex({s->expolygon}, -min_width).empty(); - })); + // remove very small solid infills, usually not worth it and many of them may not even contain extrusions in the end. + region_internal_solids.erase(std::remove_if(region_internal_solids.begin(), region_internal_solids.end(), + [region](const Surface *s) { + float min_width = + float(region->bridging_flow(frSolidInfill).scaled_width()) * 3.f; + return offset_ex({s->expolygon}, -min_width).empty(); + }), + region_internal_solids.end()); if (!region_internal_solids.empty()) { max_bridge_flow_height[&slice] = std::max(max_bridge_flow_height[&slice], region->bridging_flow(frSolidInfill).height()); @@ -1708,7 +1711,7 @@ void PrintObject::bridge_over_infill() anchors_and_walls.insert(anchors_and_walls.end(), tmp.begin(), tmp.end()); #ifdef DEBUG_BRIDGE_OVER_INFILL - debug_draw("candidate" + std::to_string(lidx), to_lines(candidate->expolygon), to_lines(bridged_area), + debug_draw(std::to_string(lidx) + "candidate", to_lines(candidate->expolygon), to_lines(bridged_area), to_lines(max_area), (anchors_and_walls)); #endif @@ -1794,7 +1797,7 @@ void PrintObject::bridge_over_infill() auto bridged_area_tree = AABBTreeLines::LinesDistancer{to_lines(bridged_area)}; #ifdef DEBUG_BRIDGE_OVER_INFILL - debug_draw("sliced" + std::to_string(lidx), to_lines(bridged_area), anchors_and_walls, + debug_draw(std::to_string(lidx) + "sliced", to_lines(bridged_area), anchors_and_walls, vertical_lines, {}); #endif @@ -1818,6 +1821,7 @@ void PrintObject::bridge_over_infill() }); if (maybe_below_anchor != anchors_intersections.rend()) { section.a = maybe_below_anchor->first; + section.a.y() -= flow.scaled_width() * (0.5 + 1.0); } auto maybe_upper_anchor = std::upper_bound(anchors_intersections.begin(), anchors_intersections.end(), @@ -1827,6 +1831,7 @@ void PrintObject::bridge_over_infill() }); if (maybe_upper_anchor != anchors_intersections.end()) { section.b = maybe_upper_anchor->first; + section.b.y() += flow.scaled_width() * (0.5 + 1.0); } } @@ -1840,8 +1845,9 @@ void PrintObject::bridge_over_infill() } } - void(std::remove_if(polygon_sections[i].begin(), polygon_sections[i].end(), - [](const Line &s) { return s.a == s.b; })); + polygon_sections[i].erase(std::remove_if(polygon_sections[i].begin(), polygon_sections[i].end(), + [](const Line &s) { return s.a == s.b; }), + polygon_sections[i].end()); } // reconstruct polygon from polygon sections @@ -1852,28 +1858,43 @@ void PrintObject::bridge_over_infill() }; std::vector current_traced_polys; - for (const auto &layer : polygon_sections) { + for (const auto &polygon_slice : polygon_sections) { std::unordered_set used_segments; for (TracedPoly &traced_poly : current_traced_polys) { - auto maybe_first_overlap = std::upper_bound(layer.begin(), layer.end(), traced_poly.lows.back(), - [](const Point &low, const Line &seg) { + auto maybe_first_overlap = std::upper_bound(polygon_slice.begin(), polygon_slice.end(), + traced_poly.lows.back(), [](const Point &low, const Line &seg) { return seg.b.y() > low.y(); }); - if (maybe_first_overlap != layer.end() && // segment exists + if (maybe_first_overlap != polygon_slice.end() && // segment exists segments_overlap(traced_poly.lows.back().y(), traced_poly.highs.back().y(), maybe_first_overlap->a.y(), maybe_first_overlap->b.y())) // segment is overlapping { // Overlapping segment. In that case, add it // to the traced polygon and add segment to used segments - traced_poly.lows.push_back(maybe_first_overlap->a - Point{flow.scaled_spacing() / 2, 0}); - traced_poly.lows.push_back(maybe_first_overlap->a + Point{flow.scaled_spacing() / 2, 0}); - traced_poly.highs.push_back(maybe_first_overlap->b - Point{flow.scaled_spacing() / 2, 0}); - traced_poly.highs.push_back(maybe_first_overlap->b + Point{flow.scaled_spacing() / 2, 0}); + if ((traced_poly.lows.back() - maybe_first_overlap->a).cast().squaredNorm() < + 36.0 * double(flow.scaled_spacing()) * flow.scaled_spacing()) { + traced_poly.lows.push_back(maybe_first_overlap->a); + } else { + traced_poly.lows.push_back(traced_poly.lows.back() + Point{flow.scaled_spacing() / 2, 0}); + traced_poly.lows.push_back(maybe_first_overlap->a - Point{flow.scaled_spacing() / 2, 0}); + traced_poly.lows.push_back(maybe_first_overlap->a); + } + + if ((traced_poly.highs.back() - maybe_first_overlap->b).cast().squaredNorm() < + 36.0 * double(flow.scaled_spacing()) * flow.scaled_spacing()) { + traced_poly.highs.push_back(maybe_first_overlap->b); + } else { + traced_poly.highs.push_back(traced_poly.highs.back() + Point{flow.scaled_spacing() / 2, 0}); + traced_poly.highs.push_back(maybe_first_overlap->b - Point{flow.scaled_spacing() / 2, 0}); + traced_poly.highs.push_back(maybe_first_overlap->b); + } used_segments.insert(&(*maybe_first_overlap)); } else { // Zero or multiple overlapping segments. Resolving this is nontrivial, // so we just close this polygon and maybe open several new. This will hopefully happen much less often + traced_poly.lows.push_back(traced_poly.lows.back() + Point{flow.scaled_spacing() / 2, 0}); + traced_poly.highs.push_back(traced_poly.highs.back() + Point{flow.scaled_spacing() / 2, 0}); Polygon &new_poly = expanded_bridged_area.emplace_back(std::move(traced_poly.lows)); new_poly.points.insert(new_poly.points.end(), traced_poly.highs.rbegin(), traced_poly.highs.rend()); traced_poly.lows.clear(); @@ -1881,16 +1902,17 @@ void PrintObject::bridge_over_infill() } } - void(std::remove_if(current_traced_polys.begin(), current_traced_polys.end(), - [](const TracedPoly &tp) { return tp.lows.empty(); })); + current_traced_polys.erase(std::remove_if(current_traced_polys.begin(), current_traced_polys.end(), + [](const TracedPoly &tp) { return tp.lows.empty(); }), + current_traced_polys.end()); - for (const auto &segment : layer) { + for (const auto &segment : polygon_slice) { if (used_segments.find(&segment) == used_segments.end()) { TracedPoly &new_tp = current_traced_polys.emplace_back(); new_tp.lows.push_back(segment.a - Point{flow.scaled_spacing() / 2, 0}); - new_tp.lows.push_back(segment.a + Point{flow.scaled_spacing() / 2, 0}); + new_tp.lows.push_back(segment.a); new_tp.highs.push_back(segment.b - Point{flow.scaled_spacing() / 2, 0}); - new_tp.highs.push_back(segment.b + Point{flow.scaled_spacing() / 2, 0}); + new_tp.highs.push_back(segment.b); } } } @@ -1906,7 +1928,7 @@ void PrintObject::bridge_over_infill() for (const auto &s : polygon_sections) { l.insert(l.end(), s.begin(), s.end()); } - debug_draw("reconstructed" + std::to_string(lidx), l, anchors_and_walls_tree.get_lines(), + debug_draw(std::to_string(lidx) + "reconstructed", l, anchors_and_walls_tree.get_lines(), to_lines(expanded_bridged_area), bridged_area_tree.get_lines()); #endif } @@ -1919,7 +1941,7 @@ void PrintObject::bridge_over_infill() bridging_angle); #ifdef DEBUG_BRIDGE_OVER_INFILL - debug_draw("cadidate_added" + std::to_string(lidx), to_lines(expanded_bridged_area), to_lines(bridged_area), + debug_draw(std::to_string(lidx) + "cadidate_added", to_lines(expanded_bridged_area), to_lines(bridged_area), to_lines(max_area), to_lines(expand_area)); #endif } @@ -1975,8 +1997,10 @@ void PrintObject::bridge_over_infill() } region->m_fill_surfaces.surfaces.insert(region->m_fill_surfaces.surfaces.end(), new_surfaces.begin(), new_surfaces.end()); - void(std::remove_if(region->m_fill_surfaces.begin(), region->m_fill_surfaces.end(), - [](const Surface &s) { return s.empty(); })); + region->m_fill_surfaces.surfaces.erase(std::remove_if(region->m_fill_surfaces.surfaces.begin(), + region->m_fill_surfaces.surfaces.end(), + [](const Surface &s) { return s.empty(); }), + region->m_fill_surfaces.surfaces.end()); } } } From 9054aa74b34405439f535a99a4b9b560b930f5fb Mon Sep 17 00:00:00 2001 From: PavelMikus Date: Wed, 15 Feb 2023 13:32:19 +0100 Subject: [PATCH 32/48] Filter out tiny areas, Fix issue where partial surfaces were cleared and empty when over solid infill --- src/libslic3r/PrintObject.cpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/libslic3r/PrintObject.cpp b/src/libslic3r/PrintObject.cpp index 8b72c292a..4e91581e4 100644 --- a/src/libslic3r/PrintObject.cpp +++ b/src/libslic3r/PrintObject.cpp @@ -1695,7 +1695,7 @@ void PrintObject::bridge_over_infill() intersection(bridged_area, lower_layers_sparse_infill); // cut off parts which are not over sparse infill - material overflow - if (bridged_area.empty()) { + if (shrink(bridged_area, 3.0 * flow.scaled_width()).empty()) { continue; } @@ -1980,6 +1980,9 @@ void PrintObject::bridge_over_infill() for (Surface &surface : region->m_fill_surfaces.surfaces) { if (s.original_surface == &surface) { Surface tmp(surface, {}); + for (const ExPolygon &expoly : diff_ex(surface.expolygon, s.new_polys)) { + new_surfaces.emplace_back(tmp, expoly); + } tmp.surface_type = stInternalBridge; tmp.bridge_angle = s.bridge_angle; for (const ExPolygon &expoly : union_ex(s.new_polys)) { From 2b0a7ccb2c3c6aa9f4b57839878ca538a2f71fbf Mon Sep 17 00:00:00 2001 From: PavelMikus Date: Wed, 15 Feb 2023 13:43:20 +0100 Subject: [PATCH 33/48] invalidate posPrepareInfill on pattern change, since that influences anchoring filter out sparse infill with 100 density from bridgeable areas --- src/libslic3r/PrintObject.cpp | 23 +++++++++++------------ 1 file changed, 11 insertions(+), 12 deletions(-) diff --git a/src/libslic3r/PrintObject.cpp b/src/libslic3r/PrintObject.cpp index 4e91581e4..63ce832d8 100644 --- a/src/libslic3r/PrintObject.cpp +++ b/src/libslic3r/PrintObject.cpp @@ -718,15 +718,7 @@ bool PrintObject::invalidate_state_by_config_options( || opt_key == "first_layer_extrusion_width") { steps.emplace_back(posInfill); } else if (opt_key == "fill_pattern") { - steps.emplace_back(posInfill); - - const auto *old_fill_pattern = old_config.option>(opt_key); - const auto *new_fill_pattern = new_config.option>(opt_key); - assert(old_fill_pattern && new_fill_pattern); - // We need to recalculate infill surfaces when infill_only_where_needed is enabled, and we are switching from - // the Lightning infill to another infill or vice versa. - if (m_config.infill_only_where_needed && (new_fill_pattern->value == ipLightning || old_fill_pattern->value == ipLightning)) - steps.emplace_back(posPrepareInfill); + steps.emplace_back(posPrepareInfill); } else if (opt_key == "fill_density") { // One likely wants to reslice only when switching between zero infill to simulate boolean difference (subtracting volumes), // normal infill and 100% (solid) infill. @@ -1641,6 +1633,11 @@ void PrintObject::bridge_over_infill() continue; }; + if (expansion_space[candidates.first].empty()){ + // there is no expansion space to which can anchors on this island, skip + continue; + } + // Gather lower layers sparse infill areas, to depth defined by used bridge flow Polygons lower_layers_sparse_infill{}; double bottom_z = layer->print_z - max_bridge_flow_height[candidates.first] - EPSILON; @@ -1663,9 +1660,11 @@ void PrintObject::bridge_over_infill() for (size_t region_idx : regions_under_to_check) { const LayerRegion *region = po->get_layer(i)->get_region(region_idx); - for (const Surface *surface : region->fill_surfaces().filter_by_type(stInternal)) { - Polygons p = to_polygons(surface->expolygon); - lower_layers_sparse_infill.insert(lower_layers_sparse_infill.end(), p.begin(), p.end()); + if (region->region().config().fill_density.value < 100) { + for (const Surface *surface : region->fill_surfaces().filter_by_type(stInternal)) { + Polygons p = to_polygons(surface->expolygon); + lower_layers_sparse_infill.insert(lower_layers_sparse_infill.end(), p.begin(), p.end()); + } } } } From 6521b722749775b9c7809d38a5b454428e75932f Mon Sep 17 00:00:00 2001 From: PavelMikus Date: Wed, 15 Feb 2023 17:23:31 +0100 Subject: [PATCH 34/48] Fix problems with adaptive infills, but the anchoring itself is not used on them Fix briding angles for octagram and hilberts curve --- src/libslic3r/Fill/Fill.cpp | 8 ++++ src/libslic3r/PrintObject.cpp | 72 +++++++++++++++++++++++++++-------- 2 files changed, 64 insertions(+), 16 deletions(-) diff --git a/src/libslic3r/Fill/Fill.cpp b/src/libslic3r/Fill/Fill.cpp index 082cf93cc..043a2e34a 100644 --- a/src/libslic3r/Fill/Fill.cpp +++ b/src/libslic3r/Fill/Fill.cpp @@ -442,6 +442,14 @@ void Layer::make_fills(FillAdaptive::Octree* adaptive_fill_octree, FillAdaptive: #endif /* SLIC3R_DEBUG_SLICE_PROCESSING */ for (SurfaceFill &surface_fill : surface_fills) { + //skip patterns for which additional input is nullptr + switch (surface_fill.params.pattern) { + case ipLightning: if (lightning_generator == nullptr) continue; break; + case ipAdaptiveCubic: if (adaptive_fill_octree == nullptr) continue; break; + case ipSupportCubic: if (support_fill_octree == nullptr) continue; break; + default: break; + } + // Create the filler object. std::unique_ptr f = std::unique_ptr(Fill::new_from_type(surface_fill.params.pattern)); f->set_bounding_box(bbox); diff --git a/src/libslic3r/PrintObject.cpp b/src/libslic3r/PrintObject.cpp index 63ce832d8..f1f377deb 100644 --- a/src/libslic3r/PrintObject.cpp +++ b/src/libslic3r/PrintObject.cpp @@ -1638,6 +1638,17 @@ void PrintObject::bridge_over_infill() continue; } + auto region_has_anchorable_sparse_infill = [](const LayerRegion* layer_region) { + switch (layer_region->region().config().fill_pattern.value) { + case ipAdaptiveCubic: return false; + case ipSupportCubic: return false; + case ipLightning: return false; + default: break; + } + + return layer_region->region().config().fill_density.value < 100; + }; + // Gather lower layers sparse infill areas, to depth defined by used bridge flow Polygons lower_layers_sparse_infill{}; double bottom_z = layer->print_z - max_bridge_flow_height[candidates.first] - EPSILON; @@ -1660,7 +1671,7 @@ void PrintObject::bridge_over_infill() for (size_t region_idx : regions_under_to_check) { const LayerRegion *region = po->get_layer(i)->get_region(region_idx); - if (region->region().config().fill_density.value < 100) { + if (region_has_anchorable_sparse_infill(region)) { for (const Surface *surface : region->fill_surfaces().filter_by_type(stInternal)) { Polygons p = to_polygons(surface->expolygon); lower_layers_sparse_infill.insert(lower_layers_sparse_infill.end(), p.begin(), p.end()); @@ -1703,7 +1714,7 @@ void PrintObject::bridge_over_infill() closing(max_area, flow.scaled_width()); Polylines anchors = intersection_pl(lower_layer_polylines, max_area); - anchors = diff_pl(anchors, shrink(bridged_area, flow.scaled_width())); + anchors = diff_pl(anchors, bridged_area); Lines anchors_and_walls = to_lines(anchors); Lines tmp = to_lines(max_area); @@ -1716,9 +1727,9 @@ void PrintObject::bridge_over_infill() double bridging_angle = 0; { - AABBTreeLines::LinesDistancer lines_tree{anchors_and_walls}; + AABBTreeLines::LinesDistancer lines_tree{anchors.empty() ? anchors_and_walls : to_lines(anchors)}; - std::vector> directions_with_distances; + std::map counted_directions; for (const Polygon &p : bridged_area) { for (int point_idx = 0; point_idx < int(p.points.size()) - 1; ++point_idx) { Vec2d start = p.points[point_idx].cast(); @@ -1736,23 +1747,52 @@ void PrintObject::bridge_over_infill() angle -= PI; } angle += PI * 0.5; - directions_with_distances.emplace_back(angle, distance); + counted_directions[angle]++; } } } - double max_dist = directions_with_distances[0].second; - for (const auto &dir : directions_with_distances) { - max_dist = std::max(max_dist, dir.second); + + std::pair best_dir{0, 0}; + // sliding window accumulation + for (const auto &dir : counted_directions) { + int score_acc = 0; + double dir_acc = 0; + double window_start_angle = dir.first - PI * 0.2; + double window_end_angle = dir.first + PI * 0.2; + for (auto dirs_window = counted_directions.lower_bound(window_start_angle); + dirs_window != counted_directions.upper_bound(window_end_angle); dirs_window++) { + dir_acc += dirs_window->first * dirs_window->second; + score_acc += dirs_window->second; + } + // current span of directions is 0.5 PI to 1.5 PI (due to the aproach.). Edge values should also account for the + // opposite direction. + if (window_start_angle < 0.5 * PI) { + for (auto dirs_window = counted_directions.lower_bound(1.5 * PI - (0.5 * PI - window_start_angle)); + dirs_window != counted_directions.end(); dirs_window++) { + dir_acc += dirs_window->first * dirs_window->second; + score_acc += dirs_window->second; + } + } + if (window_start_angle > 1.5 * PI) { + for (auto dirs_window = counted_directions.begin(); + dirs_window != counted_directions.upper_bound(window_start_angle - 1.5 * PI); dirs_window++) { + dir_acc += dirs_window->first * dirs_window->second; + score_acc += dirs_window->second; + } + } + + if (score_acc > best_dir.second) { + best_dir = {dir_acc / score_acc, score_acc}; + } } - double acc = 0; - for (const auto &dir : directions_with_distances) { - bridging_angle += dir.first * (max_dist - dir.second); - acc += (max_dist - dir.second); - } - if (acc <= EPSILON || bridging_angle == 0) { + bridging_angle = best_dir.first; + if (bridging_angle == 0) { bridging_angle = 0.001; - } else { - bridging_angle /= acc; + } + switch (surface_to_region[candidate]->region().config().fill_pattern.value) { + case ipHilbertCurve: bridging_angle += 0.25 * PI; break; + case ipOctagramSpiral: bridging_angle += (1.0 / 16.0) * PI; break; + default: break; } } From 59c58397c925ab8fddb9b3d3b5e361e3115daf41 Mon Sep 17 00:00:00 2001 From: PavelMikus Date: Wed, 15 Feb 2023 18:43:41 +0100 Subject: [PATCH 35/48] Fix bug with special infill --- src/libslic3r/PrintObject.cpp | 46 +++++++++++++++++++++++------------ 1 file changed, 31 insertions(+), 15 deletions(-) diff --git a/src/libslic3r/PrintObject.cpp b/src/libslic3r/PrintObject.cpp index f1f377deb..b7b5571de 100644 --- a/src/libslic3r/PrintObject.cpp +++ b/src/libslic3r/PrintObject.cpp @@ -1525,7 +1525,7 @@ void PrintObject::discover_vertical_shells() } // for each region } // void PrintObject::discover_vertical_shells() -#define DEBUG_BRIDGE_OVER_INFILL +// #define DEBUG_BRIDGE_OVER_INFILL #ifdef DEBUG_BRIDGE_OVER_INFILL template void debug_draw(std::string name, const T& a, const T& b, const T& c, const T& d) { @@ -1633,24 +1633,18 @@ void PrintObject::bridge_over_infill() continue; }; - if (expansion_space[candidates.first].empty()){ - // there is no expansion space to which can anchors on this island, skip - continue; - } - - auto region_has_anchorable_sparse_infill = [](const LayerRegion* layer_region) { + auto region_has_special_infill = [](const LayerRegion *layer_region) { switch (layer_region->region().config().fill_pattern.value) { - case ipAdaptiveCubic: return false; - case ipSupportCubic: return false; - case ipLightning: return false; - default: break; + case ipAdaptiveCubic: return true; + case ipSupportCubic: return true; + case ipLightning: return true; + default: return false; } - - return layer_region->region().config().fill_density.value < 100; }; // Gather lower layers sparse infill areas, to depth defined by used bridge flow Polygons lower_layers_sparse_infill{}; + Polygons special_infill{}; double bottom_z = layer->print_z - max_bridge_flow_height[candidates.first] - EPSILON; LayerSlice::Links current_links = candidates.first->overlaps_below; LayerSlice::Links next_links{}; @@ -1671,20 +1665,32 @@ void PrintObject::bridge_over_infill() for (size_t region_idx : regions_under_to_check) { const LayerRegion *region = po->get_layer(i)->get_region(region_idx); - if (region_has_anchorable_sparse_infill(region)) { + if (region->region().config().fill_density.value < 100 && !region_has_special_infill(region)) { for (const Surface *surface : region->fill_surfaces().filter_by_type(stInternal)) { Polygons p = to_polygons(surface->expolygon); lower_layers_sparse_infill.insert(lower_layers_sparse_infill.end(), p.begin(), p.end()); } + } else if (region_has_special_infill(region)) { + for (const Surface *surface : region->fill_surfaces().filter_by_type(stInternal)) { + Polygons p = to_polygons(surface->expolygon); + special_infill.insert(special_infill.end(), p.begin(), p.end()); + } } } } current_links = next_links; } + + lower_layers_sparse_infill.insert(lower_layers_sparse_infill.end(), special_infill.begin(), special_infill.end()); + if (lower_layers_sparse_infill.empty()) { continue; } - lower_layers_sparse_infill = union_(lower_layers_sparse_infill); + + if (expansion_space[candidates.first].empty() && special_infill.empty()) { + // there is no expansion space to which can anchors expand on this island, skip + continue; + } Polygons expand_area; for (const Surface *sparse_infill : expansion_space[candidates.first]) { @@ -1714,6 +1720,16 @@ void PrintObject::bridge_over_infill() closing(max_area, flow.scaled_width()); Polylines anchors = intersection_pl(lower_layer_polylines, max_area); + if (!special_infill.empty()) { + auto part_over_special_infill = intersection(special_infill, bridged_area); + auto artificial_boundary = to_polylines(expand(part_over_special_infill, flow.scaled_width())); + anchors.insert(anchors.end(), artificial_boundary.begin(), artificial_boundary.end()); + +#ifdef DEBUG_BRIDGE_OVER_INFILL + debug_draw(std::to_string(lidx) + "special", to_lines(part_over_special_infill), to_lines(artificial_boundary), + to_lines(anchors), to_lines(expand_area)); +#endif + } anchors = diff_pl(anchors, bridged_area); Lines anchors_and_walls = to_lines(anchors); From ce73bce78012f84c0a53fae1cdb64af068b62643 Mon Sep 17 00:00:00 2001 From: PavelMikus Date: Thu, 16 Feb 2023 13:35:59 +0100 Subject: [PATCH 36/48] fixed multiple regions, fixed artefacts in morphological operations --- src/libslic3r/PrintObject.cpp | 134 ++++++++++++++++++++-------------- 1 file changed, 80 insertions(+), 54 deletions(-) diff --git a/src/libslic3r/PrintObject.cpp b/src/libslic3r/PrintObject.cpp index b7b5571de..12fe10e4c 100644 --- a/src/libslic3r/PrintObject.cpp +++ b/src/libslic3r/PrintObject.cpp @@ -1575,26 +1575,38 @@ void PrintObject::bridge_over_infill() std::unordered_map max_bridge_flow_height; std::unordered_map surface_to_region; for (const LayerSlice &slice : layer->lslices_ex) { - std::unordered_set regions_to_check; + AABBTreeLines::LinesDistancer slice_island_tree{to_lines(layer->lslices[int(&slice - layer->lslices_ex.data())])}; + std::unordered_set regions_to_check; + + // If there is composite island we have to check all regions on the layer. otherwise, only some regions are needed to be checked for (const LayerIsland &island : slice.islands) { - regions_to_check.insert(island.perimeters.region()); + regions_to_check.insert(layer->regions()[island.perimeters.region()]); if (!island.fill_expolygons_composite()) { - regions_to_check.insert(island.fill_region_id); + regions_to_check.insert(layer->regions()[island.fill_region_id]); + } else { + for (const auto& r : layer->regions()) { + regions_to_check.insert(r); + } + break; } } - for (size_t region_idx : regions_to_check) { - const LayerRegion *region = layer->get_region(region_idx); + for ( const LayerRegion *region : regions_to_check) { SurfacesPtr region_internal_solids = region->fill_surfaces().filter_by_type(stInternalSolid); // remove very small solid infills, usually not worth it and many of them may not even contain extrusions in the end. - region_internal_solids.erase(std::remove_if(region_internal_solids.begin(), region_internal_solids.end(), - [region](const Surface *s) { - float min_width = - float(region->bridging_flow(frSolidInfill).scaled_width()) * 3.f; - return offset_ex({s->expolygon}, -min_width).empty(); - }), - region_internal_solids.end()); + region_internal_solids + .erase(std::remove_if(region_internal_solids.begin(), region_internal_solids.end(), + [slice_island_tree, region](const Surface *s) { + float min_width = float(region->bridging_flow(frSolidInfill).scaled_spacing()) * 3.f; + if (slice_island_tree.distance_from_lines(s->expolygon.contour.first_point()) > + min_width / 3.0) { + return true; + } + return area(offset_ex({s->expolygon}, -min_width)) < + min_width * region->bridging_flow(frSolidInfill).scaled_width(); + }), + region_internal_solids.end()); if (!region_internal_solids.empty()) { max_bridge_flow_height[&slice] = std::max(max_bridge_flow_height[&slice], region->bridging_flow(frSolidInfill).height()); @@ -1645,46 +1657,56 @@ void PrintObject::bridge_over_infill() // Gather lower layers sparse infill areas, to depth defined by used bridge flow Polygons lower_layers_sparse_infill{}; Polygons special_infill{}; - double bottom_z = layer->print_z - max_bridge_flow_height[candidates.first] - EPSILON; - LayerSlice::Links current_links = candidates.first->overlaps_below; - LayerSlice::Links next_links{}; - for (int i = int(lidx) - 1; i >= 0; --i) { - // Stop iterating if layer is lower than bottom_z. - if (po->get_layer(i)->print_z < bottom_z) - break; - for (const auto &link : current_links) { - const LayerSlice &slice_below = po->get_layer(i)->lslices_ex[link.slice_idx]; - next_links.insert(next_links.end(), slice_below.overlaps_below.begin(), slice_below.overlaps_below.end()); - std::unordered_set regions_under_to_check; - for (const LayerIsland &island : slice_below.islands) { - regions_under_to_check.insert(island.perimeters.region()); - if (!island.fill_expolygons_composite()) { - regions_under_to_check.insert(island.fill_region_id); + { + double bottom_z = layer->print_z - max_bridge_flow_height[candidates.first] - EPSILON; + LayerSlice::Links current_links = candidates.first->overlaps_below; + LayerSlice::Links next_links{}; + for (int i = int(lidx) - 1; i >= 0; --i) { + // Stop iterating if layer is lower than bottom_z. + if (po->get_layer(i)->print_z < bottom_z) + break; + for (const auto &link : current_links) { + const LayerSlice &slice_below = po->get_layer(i)->lslices_ex[link.slice_idx]; + next_links.insert(next_links.end(), slice_below.overlaps_below.begin(), slice_below.overlaps_below.end()); + std::unordered_set regions_under_to_check; + for (const LayerIsland &island : slice_below.islands) { + regions_under_to_check.insert(po->get_layer(i)->regions()[island.perimeters.region()]); + if (!island.fill_expolygons_composite()) { + regions_under_to_check.insert(po->get_layer(i)->regions()[island.fill_region_id]); + } else { + for (const auto &r : po->get_layer(i)->regions()) { + regions_under_to_check.insert(r); + } + break; + } } - } - for (size_t region_idx : regions_under_to_check) { - const LayerRegion *region = po->get_layer(i)->get_region(region_idx); - if (region->region().config().fill_density.value < 100 && !region_has_special_infill(region)) { - for (const Surface *surface : region->fill_surfaces().filter_by_type(stInternal)) { - Polygons p = to_polygons(surface->expolygon); - lower_layers_sparse_infill.insert(lower_layers_sparse_infill.end(), p.begin(), p.end()); - } - } else if (region_has_special_infill(region)) { - for (const Surface *surface : region->fill_surfaces().filter_by_type(stInternal)) { - Polygons p = to_polygons(surface->expolygon); - special_infill.insert(special_infill.end(), p.begin(), p.end()); + for (const LayerRegion *region : regions_under_to_check) { + if (region->region().config().fill_density.value < 100 && !region_has_special_infill(region)) { + for (const Surface *surface : region->fill_surfaces().filter_by_type(stInternal)) { + Polygons p = to_polygons(surface->expolygon); + lower_layers_sparse_infill.insert(lower_layers_sparse_infill.end(), p.begin(), p.end()); + } + } else if (region_has_special_infill(region)) { + for (const Surface *surface : region->fill_surfaces().filter_by_type(stInternal)) { + Polygons p = to_polygons(surface->expolygon); + special_infill.insert(special_infill.end(), p.begin(), p.end()); + } } } } + current_links = next_links; } - current_links = next_links; - } - lower_layers_sparse_infill.insert(lower_layers_sparse_infill.end(), special_infill.begin(), special_infill.end()); + lower_layers_sparse_infill = intersection(lower_layers_sparse_infill, + layer->lslices[int(candidates.first - layer->lslices_ex.data())]); + special_infill = intersection(special_infill, layer->lslices[int(candidates.first - layer->lslices_ex.data())]); - if (lower_layers_sparse_infill.empty()) { - continue; + lower_layers_sparse_infill.insert(lower_layers_sparse_infill.end(), special_infill.begin(), special_infill.end()); + + if (lower_layers_sparse_infill.empty()) { + continue; + } } if (expansion_space[candidates.first].empty() && special_infill.empty()) { @@ -1711,18 +1733,18 @@ void PrintObject::bridge_over_infill() intersection(bridged_area, lower_layers_sparse_infill); // cut off parts which are not over sparse infill - material overflow - if (shrink(bridged_area, 3.0 * flow.scaled_width()).empty()) { + if (area(shrink(bridged_area, 3.0 * flow.scaled_spacing())) < 9.0 * flow.scaled_spacing() * flow.scaled_spacing()) { continue; } Polygons max_area = expand_area; max_area.insert(max_area.end(), bridged_area.begin(), bridged_area.end()); - closing(max_area, flow.scaled_width()); + max_area = closing(max_area, flow.scaled_spacing()); Polylines anchors = intersection_pl(lower_layer_polylines, max_area); if (!special_infill.empty()) { auto part_over_special_infill = intersection(special_infill, bridged_area); - auto artificial_boundary = to_polylines(expand(part_over_special_infill, flow.scaled_width())); + auto artificial_boundary = to_polylines(expand(part_over_special_infill, 0.5 * flow.scaled_width())); anchors.insert(anchors.end(), artificial_boundary.begin(), artificial_boundary.end()); #ifdef DEBUG_BRIDGE_OVER_INFILL @@ -1990,11 +2012,11 @@ void PrintObject::bridge_over_infill() polygons_rotate(expanded_bridged_area, -aligning_angle); expanded_bridged_area = intersection(expanded_bridged_area, max_area); + expanded_bridged_area = opening(expanded_bridged_area, flow.scaled_spacing()); expand_area = diff(expand_area, expanded_bridged_area); expanded_briding_surfaces[candidates.first].emplace_back(candidate, expanded_bridged_area, surface_to_region[candidate], bridging_angle); - #ifdef DEBUG_BRIDGE_OVER_INFILL debug_draw(std::to_string(lidx) + "cadidate_added", to_lines(expanded_bridged_area), to_lines(bridged_area), to_lines(max_area), to_lines(expand_area)); @@ -2014,11 +2036,16 @@ void PrintObject::bridge_over_infill() for (const LayerSlice &slice : layer->lslices_ex) { if (const auto &modified_surfaces = expanded_briding_surfaces.find(&slice); modified_surfaces != expanded_briding_surfaces.end()) { - std::unordered_set regions_to_check; + std::unordered_set regions_to_check; for (const LayerIsland &island : slice.islands) { - regions_to_check.insert(island.perimeters.region()); + regions_to_check.insert(layer->regions()[island.perimeters.region()]); if (!island.fill_expolygons_composite()) { - regions_to_check.insert(island.fill_region_id); + regions_to_check.insert(layer->regions()[island.fill_region_id]); + } else { + for (LayerRegion *r : layer->regions()) { + regions_to_check.insert(r); + } + break; } } @@ -2027,9 +2054,8 @@ void PrintObject::bridge_over_infill() cut_from_infill.insert(cut_from_infill.end(), surface.new_polys.begin(), surface.new_polys.end()); } - for (size_t region_idx : regions_to_check) { - LayerRegion *region = layer->get_region(region_idx); - Surfaces new_surfaces; + for (LayerRegion *region : regions_to_check) { + Surfaces new_surfaces; for (const ModifiedSurface &s : modified_surfaces->second) { for (Surface &surface : region->m_fill_surfaces.surfaces) { From 7c603a53e0e30daa42561e9c74ef30c6ae02860d Mon Sep 17 00:00:00 2001 From: PavelMikus Date: Thu, 16 Feb 2023 16:11:23 +0100 Subject: [PATCH 37/48] fix first layer over sparse sometimes not labeled as bridge when there was no expand space --- src/libslic3r/PrintObject.cpp | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/src/libslic3r/PrintObject.cpp b/src/libslic3r/PrintObject.cpp index 12fe10e4c..d4dcd6213 100644 --- a/src/libslic3r/PrintObject.cpp +++ b/src/libslic3r/PrintObject.cpp @@ -1560,10 +1560,10 @@ void PrintObject::bridge_over_infill() double bridge_angle; }; - std::unordered_map> expanded_briding_surfaces; + std::unordered_map> briding_surfaces; tbb::parallel_for(tbb::blocked_range(0, this->layers().size()), [po = this, - &expanded_briding_surfaces](tbb::blocked_range r) { + &briding_surfaces](tbb::blocked_range r) { for (size_t lidx = r.begin(); lidx < r.end(); lidx++) { const Layer *layer = po->get_layer(lidx); @@ -1710,7 +1710,11 @@ void PrintObject::bridge_over_infill() } if (expansion_space[candidates.first].empty() && special_infill.empty()) { - // there is no expansion space to which can anchors expand on this island, skip + // there is no expansion space to which can anchors expand on this island, add back original polygons and skip the island + for (const Surface *candidate : candidates.second) { + briding_surfaces[candidates.first].emplace_back(candidate, to_polygons(candidate->expolygon), + surface_to_region[candidate], 0); + } continue; } @@ -2015,7 +2019,7 @@ void PrintObject::bridge_over_infill() expanded_bridged_area = opening(expanded_bridged_area, flow.scaled_spacing()); expand_area = diff(expand_area, expanded_bridged_area); - expanded_briding_surfaces[candidates.first].emplace_back(candidate, expanded_bridged_area, surface_to_region[candidate], + briding_surfaces[candidates.first].emplace_back(candidate, expanded_bridged_area, surface_to_region[candidate], bridging_angle); #ifdef DEBUG_BRIDGE_OVER_INFILL debug_draw(std::to_string(lidx) + "cadidate_added", to_lines(expanded_bridged_area), to_lines(bridged_area), @@ -2029,13 +2033,13 @@ void PrintObject::bridge_over_infill() BOOST_LOG_TRIVIAL(info) << "Bridge over infill - Directions and expanded surfaces computed" << log_memory_info(); tbb::parallel_for(tbb::blocked_range(0, this->layers().size()), [po = this, - &expanded_briding_surfaces](tbb::blocked_range r) { + &briding_surfaces](tbb::blocked_range r) { for (size_t lidx = r.begin(); lidx < r.end(); lidx++) { Layer *layer = po->get_layer(lidx); for (const LayerSlice &slice : layer->lslices_ex) { - if (const auto &modified_surfaces = expanded_briding_surfaces.find(&slice); - modified_surfaces != expanded_briding_surfaces.end()) { + if (const auto &modified_surfaces = briding_surfaces.find(&slice); + modified_surfaces != briding_surfaces.end()) { std::unordered_set regions_to_check; for (const LayerIsland &island : slice.islands) { regions_to_check.insert(layer->regions()[island.perimeters.region()]); From 739bee354d599c6c40530e7c2dad39be50789b1c Mon Sep 17 00:00:00 2001 From: PavelMikus Date: Thu, 16 Feb 2023 16:33:34 +0100 Subject: [PATCH 38/48] lower angles span so that briding direction is better --- src/libslic3r/PrintObject.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/libslic3r/PrintObject.cpp b/src/libslic3r/PrintObject.cpp index d4dcd6213..6bf8d0e55 100644 --- a/src/libslic3r/PrintObject.cpp +++ b/src/libslic3r/PrintObject.cpp @@ -1799,8 +1799,8 @@ void PrintObject::bridge_over_infill() for (const auto &dir : counted_directions) { int score_acc = 0; double dir_acc = 0; - double window_start_angle = dir.first - PI * 0.2; - double window_end_angle = dir.first + PI * 0.2; + double window_start_angle = dir.first - PI * 0.1; + double window_end_angle = dir.first + PI * 0.1; for (auto dirs_window = counted_directions.lower_bound(window_start_angle); dirs_window != counted_directions.upper_bound(window_end_angle); dirs_window++) { dir_acc += dirs_window->first * dirs_window->second; From 61e623eda9c9e98c874f230aeab7e2ea1e1d998e Mon Sep 17 00:00:00 2001 From: Pavel Mikus Date: Tue, 21 Feb 2023 16:44:03 +0100 Subject: [PATCH 39/48] fix issues after merge with new ensuring branch --- src/libslic3r/PrintObject.cpp | 70 ++++++++++++++++++----------------- 1 file changed, 37 insertions(+), 33 deletions(-) diff --git a/src/libslic3r/PrintObject.cpp b/src/libslic3r/PrintObject.cpp index 73a48f2a7..8c1ac78f5 100644 --- a/src/libslic3r/PrintObject.cpp +++ b/src/libslic3r/PrintObject.cpp @@ -1593,10 +1593,10 @@ void PrintObject::bridge_over_infill() double bridge_angle; }; - std::unordered_map> briding_surfaces; + std::unordered_map> bridging_surfaces; tbb::parallel_for(tbb::blocked_range(0, this->layers().size()), [po = this, - &briding_surfaces](tbb::blocked_range r) { + &bridging_surfaces](tbb::blocked_range r) { for (size_t lidx = r.begin(); lidx < r.end(); lidx++) { const Layer *layer = po->get_layer(lidx); @@ -1628,21 +1628,19 @@ void PrintObject::bridge_over_infill() SurfacesPtr region_internal_solids = region->fill_surfaces().filter_by_type(stInternalSolid); // remove very small solid infills, usually not worth it and many of them may not even contain extrusions in the end. - region_internal_solids - .erase(std::remove_if(region_internal_solids.begin(), region_internal_solids.end(), - [slice_island_tree, region](const Surface *s) { - float min_width = float(region->bridging_flow(frSolidInfill).scaled_spacing()) * 3.f; - if (slice_island_tree.distance_from_lines(s->expolygon.contour.first_point()) > - min_width / 3.0) { - return true; - } - return area(offset_ex({s->expolygon}, -min_width)) < - min_width * region->bridging_flow(frSolidInfill).scaled_width(); - }), - region_internal_solids.end()); + region_internal_solids.erase(std::remove_if(region_internal_solids.begin(), region_internal_solids.end(), + [slice_island_tree, region](const Surface *s) { + float spacing = float( + region->bridging_flow(frSolidInfill, true).scaled_spacing()); + if (slice_island_tree.outside(s->expolygon.contour.first_point()) > 0) { + return true; + } + return offset({s->expolygon}, -3.0 * spacing).empty(); + }), + region_internal_solids.end()); if (!region_internal_solids.empty()) { max_bridge_flow_height[&slice] = std::max(max_bridge_flow_height[&slice], - region->bridging_flow(frSolidInfill).height()); + region->bridging_flow(frSolidInfill, true).height()); } for (const Surface *s : region_internal_solids) { surface_to_region[s] = region; @@ -1688,8 +1686,9 @@ void PrintObject::bridge_over_infill() }; // Gather lower layers sparse infill areas, to depth defined by used bridge flow - Polygons lower_layers_sparse_infill{}; - Polygons special_infill{}; + Polygons lower_layers_sparse_infill{}; + Polygons special_infill{}; + Polygons not_sparse_infill{}; { double bottom_z = layer->print_z - max_bridge_flow_height[candidates.first] - EPSILON; LayerSlice::Links current_links = candidates.first->overlaps_below; @@ -1715,15 +1714,18 @@ void PrintObject::bridge_over_infill() } for (const LayerRegion *region : regions_under_to_check) { - if (region->region().config().fill_density.value < 100 && !region_has_special_infill(region)) { - for (const Surface *surface : region->fill_surfaces().filter_by_type(stInternal)) { - Polygons p = to_polygons(surface->expolygon); + bool has_low_density = region->region().config().fill_density.value < 100; + bool has_special_infill = region_has_special_infill(region); + for (const Surface &surface : region->fill_surfaces()) { + if (surface.surface_type == stInternal && has_low_density && !has_special_infill) { + Polygons p = to_polygons(surface.expolygon); lower_layers_sparse_infill.insert(lower_layers_sparse_infill.end(), p.begin(), p.end()); - } - } else if (region_has_special_infill(region)) { - for (const Surface *surface : region->fill_surfaces().filter_by_type(stInternal)) { - Polygons p = to_polygons(surface->expolygon); + } else if (surface.surface_type == stInternal && has_low_density && has_special_infill) { + Polygons p = to_polygons(surface.expolygon); special_infill.insert(special_infill.end(), p.begin(), p.end()); + } else { + Polygons p = to_polygons(surface.expolygon); + not_sparse_infill.insert(not_sparse_infill.end(), p.begin(), p.end()); } } } @@ -1733,11 +1735,13 @@ void PrintObject::bridge_over_infill() lower_layers_sparse_infill = intersection(lower_layers_sparse_infill, layer->lslices[int(candidates.first - layer->lslices_ex.data())]); + lower_layers_sparse_infill = diff(lower_layers_sparse_infill, not_sparse_infill); special_infill = intersection(special_infill, layer->lslices[int(candidates.first - layer->lslices_ex.data())]); + special_infill = diff(special_infill, not_sparse_infill); lower_layers_sparse_infill.insert(lower_layers_sparse_infill.end(), special_infill.begin(), special_infill.end()); - if (lower_layers_sparse_infill.empty()) { + if (shrink(lower_layers_sparse_infill, 6.0 * scaled(max_bridge_flow_height[candidates.first])).empty()) { continue; } } @@ -1745,7 +1749,7 @@ void PrintObject::bridge_over_infill() if (expansion_space[candidates.first].empty() && special_infill.empty()) { // there is no expansion space to which can anchors expand on this island, add back original polygons and skip the island for (const Surface *candidate : candidates.second) { - briding_surfaces[candidates.first].emplace_back(candidate, to_polygons(candidate->expolygon), + bridging_surfaces[candidates.first].emplace_back(candidate, to_polygons(candidate->expolygon), surface_to_region[candidate], 0); } continue; @@ -1763,14 +1767,14 @@ void PrintObject::bridge_over_infill() // bridging. These areas we then expand (within the surrounding sparse infill only!) // to touch the infill polylines on previous layer. for (const Surface *candidate : candidates.second) { - const Flow &flow = surface_to_region[candidate]->bridging_flow(frSolidInfill); + const Flow &flow = surface_to_region[candidate]->bridging_flow(frSolidInfill, true); assert(candidate->surface_type == stInternalSolid); - Polygons bridged_area = to_polygons(candidate->expolygon); + Polygons bridged_area = expand(to_polygons(candidate->expolygon), flow.scaled_spacing()); bridged_area = intersection(bridged_area, lower_layers_sparse_infill); // cut off parts which are not over sparse infill - material overflow - if (area(shrink(bridged_area, 3.0 * flow.scaled_spacing())) < 9.0 * flow.scaled_spacing() * flow.scaled_spacing()) { + if (shrink(bridged_area, 4.0 * flow.scaled_spacing()).empty()) { continue; } @@ -2052,7 +2056,7 @@ void PrintObject::bridge_over_infill() expanded_bridged_area = opening(expanded_bridged_area, flow.scaled_spacing()); expand_area = diff(expand_area, expanded_bridged_area); - briding_surfaces[candidates.first].emplace_back(candidate, expanded_bridged_area, surface_to_region[candidate], + bridging_surfaces[candidates.first].emplace_back(candidate, expanded_bridged_area, surface_to_region[candidate], bridging_angle); #ifdef DEBUG_BRIDGE_OVER_INFILL debug_draw(std::to_string(lidx) + "cadidate_added", to_lines(expanded_bridged_area), to_lines(bridged_area), @@ -2066,13 +2070,13 @@ void PrintObject::bridge_over_infill() BOOST_LOG_TRIVIAL(info) << "Bridge over infill - Directions and expanded surfaces computed" << log_memory_info(); tbb::parallel_for(tbb::blocked_range(0, this->layers().size()), [po = this, - &briding_surfaces](tbb::blocked_range r) { + &bridging_surfaces](tbb::blocked_range r) { for (size_t lidx = r.begin(); lidx < r.end(); lidx++) { Layer *layer = po->get_layer(lidx); for (const LayerSlice &slice : layer->lslices_ex) { - if (const auto &modified_surfaces = briding_surfaces.find(&slice); - modified_surfaces != briding_surfaces.end()) { + if (const auto &modified_surfaces = bridging_surfaces.find(&slice); + modified_surfaces != bridging_surfaces.end()) { std::unordered_set regions_to_check; for (const LayerIsland &island : slice.islands) { regions_to_check.insert(layer->regions()[island.perimeters.region()]); From 86729aa49946f6cbeabda83932fdd2346982f4ce Mon Sep 17 00:00:00 2001 From: Pavel Mikus Date: Tue, 21 Feb 2023 19:53:39 +0100 Subject: [PATCH 40/48] use vector instead of boost small vector --- src/libslic3r/PrintObject.cpp | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/libslic3r/PrintObject.cpp b/src/libslic3r/PrintObject.cpp index 8c1ac78f5..1d6649280 100644 --- a/src/libslic3r/PrintObject.cpp +++ b/src/libslic3r/PrintObject.cpp @@ -1690,9 +1690,11 @@ void PrintObject::bridge_over_infill() Polygons special_infill{}; Polygons not_sparse_infill{}; { - double bottom_z = layer->print_z - max_bridge_flow_height[candidates.first] - EPSILON; - LayerSlice::Links current_links = candidates.first->overlaps_below; - LayerSlice::Links next_links{}; + double bottom_z = layer->print_z - max_bridge_flow_height[candidates.first] - EPSILON; + std::vector current_links{}; + current_links.insert(current_links.end(), candidates.first->overlaps_below.begin(), + candidates.first->overlaps_below.end()); + std::vector next_links{}; for (int i = int(lidx) - 1; i >= 0; --i) { // Stop iterating if layer is lower than bottom_z. if (po->get_layer(i)->print_z < bottom_z) From 5656d4ea6dda79cc7d2126744c2f63389bdc86a1 Mon Sep 17 00:00:00 2001 From: PavelMikus Date: Fri, 24 Feb 2023 11:56:24 +0100 Subject: [PATCH 41/48] Fix crashes. Set common bridging angle to touching surfaces, otherwise bad stuff happens. --- src/libslic3r/PrintObject.cpp | 24 ++++++++++++++++++++++-- 1 file changed, 22 insertions(+), 2 deletions(-) diff --git a/src/libslic3r/PrintObject.cpp b/src/libslic3r/PrintObject.cpp index 1d6649280..679d51cc8 100644 --- a/src/libslic3r/PrintObject.cpp +++ b/src/libslic3r/PrintObject.cpp @@ -1671,7 +1671,7 @@ void PrintObject::bridge_over_infill() } } - for (const std::pair candidates : bridging_surface_candidates) { + for (std::pair candidates : bridging_surface_candidates) { if (candidates.second.empty()) { continue; }; @@ -1733,6 +1733,7 @@ void PrintObject::bridge_over_infill() } } current_links = next_links; + next_links.clear(); } lower_layers_sparse_infill = intersection(lower_layers_sparse_infill, @@ -1764,6 +1765,18 @@ void PrintObject::bridge_over_infill() expand_area.insert(expand_area.end(), a.begin(), a.end()); } + // Presort the candidate polygons. This will help choose the same angle for neighbournig surfaces, that would otherwise + // compete over anchoring sparse infill lines, leaving one area unachored + std::sort(candidates.second.begin(), candidates.second.end(), [](const Surface* left, const Surface* right){ + auto a = get_extents(left->expolygon); + auto b = get_extents(right->expolygon); + + if (a.min.x() == b.min.x()) { + return a.min.y() < b.min.y(); + }; + return a.min.x() < b.min.x(); + }); + // Lower layers sparse infill sections gathered // now we can intersected them with bridging surface candidates to get actual areas that need and can accumulate // bridging. These areas we then expand (within the surrounding sparse infill only!) @@ -1807,7 +1820,14 @@ void PrintObject::bridge_over_infill() #endif double bridging_angle = 0; - { + Polygons tmp_expanded_area = expand(bridged_area, 3.0 * flow.scaled_spacing()); + for (const ModifiedSurface& s : bridging_surfaces[candidates.first]) { + if (!intersection(s.new_polys, tmp_expanded_area).empty()) { + bridging_angle = s.bridge_angle; + break; + } + } + if (bridging_angle == 0) { AABBTreeLines::LinesDistancer lines_tree{anchors.empty() ? anchors_and_walls : to_lines(anchors)}; std::map counted_directions; From feb9310fe319962086ca3e5510a867783a133e60 Mon Sep 17 00:00:00 2001 From: PavelMikus Date: Fri, 24 Feb 2023 14:38:08 +0100 Subject: [PATCH 42/48] Support tiny floating islands inside sparse infill. You never know what can grow out of them. --- src/libslic3r/PrintObject.cpp | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/src/libslic3r/PrintObject.cpp b/src/libslic3r/PrintObject.cpp index 679d51cc8..1dbffa533 100644 --- a/src/libslic3r/PrintObject.cpp +++ b/src/libslic3r/PrintObject.cpp @@ -1627,15 +1627,15 @@ void PrintObject::bridge_over_infill() for ( const LayerRegion *region : regions_to_check) { SurfacesPtr region_internal_solids = region->fill_surfaces().filter_by_type(stInternalSolid); - // remove very small solid infills, usually not worth it and many of them may not even contain extrusions in the end. + // filter out surfaces not from this island... TODO sotre this info in the Z-Graph, so that this filtering is not needed + // NOTE: we are keeping even very small internal ensuring overhangs here. The aim is to later differentiate between expanding wall ensuring regions + // where briding them would be conterproductive, and small ensuring islands that expand into large ones, where bridging is quite necessary region_internal_solids.erase(std::remove_if(region_internal_solids.begin(), region_internal_solids.end(), - [slice_island_tree, region](const Surface *s) { - float spacing = float( - region->bridging_flow(frSolidInfill, true).scaled_spacing()); + [slice_island_tree](const Surface *s) { if (slice_island_tree.outside(s->expolygon.contour.first_point()) > 0) { return true; } - return offset({s->expolygon}, -3.0 * spacing).empty(); + return false; }), region_internal_solids.end()); if (!region_internal_solids.empty()) { @@ -1744,7 +1744,7 @@ void PrintObject::bridge_over_infill() lower_layers_sparse_infill.insert(lower_layers_sparse_infill.end(), special_infill.begin(), special_infill.end()); - if (shrink(lower_layers_sparse_infill, 6.0 * scaled(max_bridge_flow_height[candidates.first])).empty()) { + if (shrink(lower_layers_sparse_infill, 3.0 * scaled(max_bridge_flow_height[candidates.first])).empty()) { continue; } } @@ -1784,12 +1784,16 @@ void PrintObject::bridge_over_infill() for (const Surface *candidate : candidates.second) { const Flow &flow = surface_to_region[candidate]->bridging_flow(frSolidInfill, true); assert(candidate->surface_type == stInternalSolid); - Polygons bridged_area = expand(to_polygons(candidate->expolygon), flow.scaled_spacing()); + Polygons bridged_area = expand(to_polygons(candidate->expolygon), flow.scaled_spacing()); + Polygons infill_region = to_polygons(surface_to_region[candidate]->fill_expolygons()); + bool touches_perimeter = !diff(bridged_area, infill_region).empty(); + bool touches_solid_region_under = !intersection(bridged_area, not_sparse_infill).empty(); + bridged_area = intersection(bridged_area, lower_layers_sparse_infill); // cut off parts which are not over sparse infill - material overflow - if (shrink(bridged_area, 4.0 * flow.scaled_spacing()).empty()) { + if ((touches_perimeter || touches_solid_region_under) && shrink(bridged_area, 5.0 * flow.scaled_spacing()).empty()) { continue; } From e4910381b465b5907e6a3c0914674bd365b7c9d7 Mon Sep 17 00:00:00 2001 From: PavelMikus Date: Fri, 24 Feb 2023 16:47:07 +0100 Subject: [PATCH 43/48] optimize the brdige over infill by extractng only the sparse infill lines from previous layer --- src/libslic3r/Fill/Fill.cpp | 88 +++++++++++++++++++++++++++++++++++ src/libslic3r/Layer.hpp | 1 + src/libslic3r/PrintObject.cpp | 17 ++----- 3 files changed, 93 insertions(+), 13 deletions(-) diff --git a/src/libslic3r/Fill/Fill.cpp b/src/libslic3r/Fill/Fill.cpp index b502a65b4..9716ecc20 100644 --- a/src/libslic3r/Fill/Fill.cpp +++ b/src/libslic3r/Fill/Fill.cpp @@ -643,6 +643,94 @@ void Layer::make_fills(FillAdaptive::Octree* adaptive_fill_octree, FillAdaptive: #endif } +Polylines Layer::generate_sparse_infill_polylines_for_anchoring() const +{ + std::vector surface_fills = group_fills(*this); + const Slic3r::BoundingBox bbox = this->object()->bounding_box(); + const auto resolution = this->object()->print()->config().gcode_resolution.value; + + Polylines sparse_infill_polylines{}; + + for (SurfaceFill &surface_fill : surface_fills) { + // skip patterns for which additional input is nullptr + switch (surface_fill.params.pattern) { + case ipLightning: continue; break; + case ipAdaptiveCubic: continue; break; + case ipSupportCubic: continue; break; + case ipCount: continue; break; + case ipSupportBase: continue; break; + case ipEnsuring: continue; break; + case ipRectilinear: + case ipMonotonic: + case ipMonotonicLines: + case ipAlignedRectilinear: + case ipGrid: + case ipTriangles: + case ipStars: + case ipCubic: + case ipLine: + case ipConcentric: + case ipHoneycomb: + case ip3DHoneycomb: + case ipGyroid: + case ipHilbertCurve: + case ipArchimedeanChords: + case ipOctagramSpiral: break; + } + + // Create the filler object. + std::unique_ptr f = std::unique_ptr(Fill::new_from_type(surface_fill.params.pattern)); + f->set_bounding_box(bbox); + f->layer_id = this->id(); + f->z = this->print_z; + f->angle = surface_fill.params.angle; + // f->adapt_fill_octree = (surface_fill.params.pattern == ipSupportCubic) ? support_fill_octree : adaptive_fill_octree; + f->print_config = &this->object()->print()->config(); + f->print_object_config = &this->object()->config(); + + // calculate flow spacing for infill pattern generation + double link_max_length = 0.; + if (!surface_fill.params.bridge) { +#if 0 + link_max_length = layerm.region()->config().get_abs_value(surface.is_external() ? "external_fill_link_max_length" : "fill_link_max_length", flow.spacing()); +// printf("flow spacing: %f, is_external: %d, link_max_length: %lf\n", flow.spacing(), int(surface.is_external()), link_max_length); +#else + if (surface_fill.params.density > 80.) // 80% + link_max_length = 3. * f->spacing; +#endif + } + + // Maximum length of the perimeter segment linking two infill lines. + f->link_max_length = (coord_t) scale_(link_max_length); + // Used by the concentric infill pattern to clip the loops to create extrusion paths. + f->loop_clipping = coord_t(scale_(surface_fill.params.flow.nozzle_diameter()) * LOOP_CLIPPING_LENGTH_OVER_NOZZLE_DIAMETER); + + LayerRegion &layerm = *m_regions[surface_fill.region_id]; + + // apply half spacing using this flow's own spacing and generate infill + FillParams params; + params.density = float(0.01 * surface_fill.params.density); + params.dont_adjust = false; // surface_fill.params.dont_adjust; + params.anchor_length = surface_fill.params.anchor_length; + params.anchor_length_max = surface_fill.params.anchor_length_max; + params.resolution = resolution; + params.use_arachne = false; + params.layer_height = layerm.layer()->height; + + for (ExPolygon &expoly : surface_fill.expolygons) { + // Spacing is modified by the filler to indicate adjustments. Reset it for each expolygon. + f->spacing = surface_fill.params.spacing; + surface_fill.surface.expolygon = std::move(expoly); + try { + Polylines polylines = f->fill_surface(&surface_fill.surface, params); + sparse_infill_polylines.insert(sparse_infill_polylines.end(), polylines.begin(), polylines.end()); + } catch (InfillFailedException &) {} + } + } + + return sparse_infill_polylines; +} + // Create ironing extrusions over top surfaces. void Layer::make_ironing() { diff --git a/src/libslic3r/Layer.hpp b/src/libslic3r/Layer.hpp index d2bdc1088..a59c029b8 100644 --- a/src/libslic3r/Layer.hpp +++ b/src/libslic3r/Layer.hpp @@ -369,6 +369,7 @@ public: // Phony version of make_fills() without parameters for Perl integration only. void make_fills() { this->make_fills(nullptr, nullptr, nullptr); } void make_fills(FillAdaptive::Octree* adaptive_fill_octree, FillAdaptive::Octree* support_fill_octree, FillLightning::Generator* lightning_generator); + Polylines generate_sparse_infill_polylines_for_anchoring() const; void make_ironing(); void export_region_slices_to_svg(const char *path) const; diff --git a/src/libslic3r/PrintObject.cpp b/src/libslic3r/PrintObject.cpp index 1dbffa533..ca9516d20 100644 --- a/src/libslic3r/PrintObject.cpp +++ b/src/libslic3r/PrintObject.cpp @@ -397,6 +397,7 @@ void PrintObject::infill() this->prepare_infill(); if (this->set_started(posInfill)) { + m_print->set_status(45, L("making infill")); auto [adaptive_fill_octree, support_fill_octree] = this->prepare_adaptive_infill_data(); auto lightning_generator = this->prepare_lightning_infill_data(); @@ -1658,18 +1659,8 @@ void PrintObject::bridge_over_infill() continue; } - // Now, temporarily fill the previous layer and extract the extrusions. - // TODO - the make_fills function does a lot of work, some of it is not needed (e.g. sorting the paths) - // It would be nice to have a function that only creates the fill polylines, ideally without modifying the global state - po->get_layer(lidx)->lower_layer->make_fills(nullptr, nullptr, nullptr); - Polylines lower_layer_polylines; - for (const LayerRegion *region : layer->lower_layer->m_regions) { - for (const ExtrusionEntity *ee : region->fills().entities) { - assert(ee->is_collection()); - auto region_polylines = dynamic_cast(ee)->as_polylines(); - lower_layer_polylines.insert(lower_layer_polylines.end(), region_polylines.begin(), region_polylines.end()); - } - } + // generate sparse infill polylines from lower layers to get anchorable polylines + Polylines lower_layer_polylines = po->get_layer(lidx)->lower_layer->generate_sparse_infill_polylines_for_anchoring(); for (std::pair candidates : bridging_surface_candidates) { if (candidates.second.empty()) { @@ -1786,7 +1777,7 @@ void PrintObject::bridge_over_infill() assert(candidate->surface_type == stInternalSolid); Polygons bridged_area = expand(to_polygons(candidate->expolygon), flow.scaled_spacing()); Polygons infill_region = to_polygons(surface_to_region[candidate]->fill_expolygons()); - bool touches_perimeter = !diff(bridged_area, infill_region).empty(); + bool touches_perimeter = !diff(bridged_area, infill_region).empty(); bool touches_solid_region_under = !intersection(bridged_area, not_sparse_infill).empty(); bridged_area = From 43a155c4762aa0606028b869e22540a67c0b0ab8 Mon Sep 17 00:00:00 2001 From: PavelMikus Date: Mon, 27 Feb 2023 12:52:46 +0100 Subject: [PATCH 44/48] improve the filters of regions that will be turned into bridges --- src/libslic3r/PrintObject.cpp | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/src/libslic3r/PrintObject.cpp b/src/libslic3r/PrintObject.cpp index ca9516d20..b67e68f60 100644 --- a/src/libslic3r/PrintObject.cpp +++ b/src/libslic3r/PrintObject.cpp @@ -1777,15 +1777,22 @@ void PrintObject::bridge_over_infill() assert(candidate->surface_type == stInternalSolid); Polygons bridged_area = expand(to_polygons(candidate->expolygon), flow.scaled_spacing()); Polygons infill_region = to_polygons(surface_to_region[candidate]->fill_expolygons()); - bool touches_perimeter = !diff(bridged_area, infill_region).empty(); - bool touches_solid_region_under = !intersection(bridged_area, not_sparse_infill).empty(); bridged_area = intersection(bridged_area, lower_layers_sparse_infill); // cut off parts which are not over sparse infill - material overflow - if ((touches_perimeter || touches_solid_region_under) && shrink(bridged_area, 5.0 * flow.scaled_spacing()).empty()) { - continue; + { + Polygons area_without_perimeter_boundary_sections = intersection(bridged_area, + closing(infill_region, flow.scaled_width(), + flow.scaled_width() + + 4.0 * flow.scaled_spacing())); + Polygons and_further_without_solid_supported_sections = diff(area_without_perimeter_boundary_sections, + expand(not_sparse_infill, 4.0 * flow.scaled_spacing())); + + if (and_further_without_solid_supported_sections.empty()) { + continue; + } } Polygons max_area = expand_area; From b5fc26ab0a5abbaaf37319d1da89c195b547eb79 Mon Sep 17 00:00:00 2001 From: PavelMikus Date: Tue, 28 Feb 2023 15:07:17 +0100 Subject: [PATCH 45/48] Filter out very small esnuring regions --- src/libslic3r/PrintObject.cpp | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/libslic3r/PrintObject.cpp b/src/libslic3r/PrintObject.cpp index af26f175f..c6dbd7e4c 100644 --- a/src/libslic3r/PrintObject.cpp +++ b/src/libslic3r/PrintObject.cpp @@ -1479,7 +1479,7 @@ void PrintObject::discover_vertical_shells() // Open to remove (filter out) regions narrower than a bit less than an infill extrusion line width. // Such narrow regions are difficult to fill in with a gap fill algorithm (or Arachne), however they are most likely // not needed for print stability / quality. - const float narrow_ensure_vertical_wall_thickness_region_radius = 0.5f * 0.65f * min_perimeter_infill_spacing; + const float narrow_ensure_vertical_wall_thickness_region_radius = 0.5f * 0.85f * min_perimeter_infill_spacing; // Then close gaps narrower than 1.2 * line width, such gaps are difficult to fill in with sparse infill, // thus they will be merged into the solid infill. const float narrow_sparse_infill_region_radius = 0.5f * 1.2f * min_perimeter_infill_spacing; @@ -1492,6 +1492,11 @@ void PrintObject::discover_vertical_shells() narrow_ensure_vertical_wall_thickness_region_radius + narrow_sparse_infill_region_radius, ClipperLib::jtSquare), // Finally expand the infill a bit to remove tiny gaps between solid infill and the other regions. narrow_sparse_infill_region_radius - tiny_overlap_radius, ClipperLib::jtSquare); + + // The opening operation may cause scattered tiny drops on the smooth parts of the model. + shell.erase(std::remove_if(shell.begin(), shell.end(), [&min_perimeter_infill_spacing](const Polygon& p){ + return p.area() < min_perimeter_infill_spacing * scaled(8.0); + }), shell.end()); } if (shell.empty()) continue; From a3430a5b518aed84e893241797535ad1d2bdf396 Mon Sep 17 00:00:00 2001 From: PavelMikus Date: Wed, 1 Mar 2023 16:42:57 +0100 Subject: [PATCH 46/48] Completely removed Bounded Rectilinear infill Improved bridge over sparse infill logic - now does not bridge the whole area but only neede part Filtered out tiny regions of ensuring created after bridge_over_sparse infill expanded the regions --- src/libslic3r/Fill/FillEnsuring.cpp | 26 +--------------- src/libslic3r/Preset.cpp | 2 +- src/libslic3r/PrintConfig.cpp | 17 ----------- src/libslic3r/PrintConfig.hpp | 7 ----- src/libslic3r/PrintObject.cpp | 46 ++++++++++++++++------------- src/slic3r/GUI/Tab.cpp | 1 - 6 files changed, 28 insertions(+), 71 deletions(-) diff --git a/src/libslic3r/Fill/FillEnsuring.cpp b/src/libslic3r/Fill/FillEnsuring.cpp index a501f9732..0dab6d744 100644 --- a/src/libslic3r/Fill/FillEnsuring.cpp +++ b/src/libslic3r/Fill/FillEnsuring.cpp @@ -14,8 +14,6 @@ ThickPolylines FillEnsuring::fill_surface_arachne(const Surface *surface, const assert(this->print_config != nullptr && this->print_object_config != nullptr && this->print_region_config != nullptr); const coord_t scaled_spacing = scaled(this->spacing); - const EnsuringInfillPattern infill_patter = this->print_object_config->ensure_vertical_shell_infill; - const bool is_bounded_rectilinear = infill_patter == EnsuringInfillPattern::eipBoundedRectilinear; // Perform offset. Slic3r::ExPolygons expp = this->overlap != 0. ? offset_ex(surface->expolygon, scaled(this->overlap)) : ExPolygons{surface->expolygon}; @@ -23,7 +21,7 @@ ThickPolylines FillEnsuring::fill_surface_arachne(const Surface *surface, const ThickPolylines thick_polylines_out; for (ExPolygon &ex_poly : expp) { Point bbox_size = ex_poly.contour.bounding_box().size(); - coord_t loops_count = is_bounded_rectilinear ? 1 : std::max(bbox_size.x(), bbox_size.y()) / scaled_spacing + 1; + coord_t loops_count = std::max(bbox_size.x(), bbox_size.y()) / scaled_spacing + 1; Polygons polygons = to_polygons(ex_poly); Arachne::WallToolPaths wall_tool_paths(polygons, scaled_spacing, scaled_spacing, loops_count, 0, params.layer_height, *this->print_object_config, *this->print_config); if (std::vector loops = wall_tool_paths.getToolPaths(); !loops.empty()) { @@ -77,28 +75,6 @@ ThickPolylines FillEnsuring::fill_surface_arachne(const Surface *surface, const if (j < thick_polylines_out.size()) thick_polylines_out.erase(thick_polylines_out.begin() + int(j), thick_polylines_out.end()); } - - if (is_bounded_rectilinear) { - // Remaining infill area will be filled with classic Rectilinear infill. - ExPolygons infill_contour = union_ex(wall_tool_paths.getInnerContour()); - if (offset_ex(infill_contour, -float(scaled_spacing / 2.)).empty()) - infill_contour.clear(); // Infill region is too small, so let's filter it out. - - Polygons pp; - for (ExPolygon &ex : infill_contour) - ex.simplify_p(scaled(params.resolution), &pp); - - // Collapse too narrow infill areas and append them to thick_polylines_out. - const auto min_perimeter_infill_spacing = coord_t(scaled_spacing * (1. - INSET_OVERLAP_TOLERANCE)); - const auto infill_overlap = coord_t(scale_(this->print_region_config->get_abs_value("infill_overlap", this->spacing))); - for (ExPolygon &ex_poly : offset2_ex(union_ex(pp), float(-min_perimeter_infill_spacing / 2.), float(infill_overlap + min_perimeter_infill_spacing / 2.))) { - Polylines polylines; - if (Surface new_surface(*surface, std::move(ex_poly)); !fill_surface_by_lines(&new_surface, params, 0.f, 0.f, polylines)) - BOOST_LOG_TRIVIAL(error) << "FillBoundedRectilinear::fill_surface() failed to fill a region."; - append(thick_polylines_out, to_thick_polylines(std::move(polylines), scaled(this->spacing))); - } - } else - assert(infill_patter == EnsuringInfillPattern::eipConcentric); } return thick_polylines_out; diff --git a/src/libslic3r/Preset.cpp b/src/libslic3r/Preset.cpp index 5306b7d5a..677a35000 100644 --- a/src/libslic3r/Preset.cpp +++ b/src/libslic3r/Preset.cpp @@ -463,7 +463,7 @@ static std::vector s_Preset_print_options { "wipe_tower_width", "wipe_tower_rotation_angle", "wipe_tower_brim_width", "wipe_tower_bridging", "single_extruder_multi_material_priming", "mmu_segmented_region_max_width", "wipe_tower_no_sparse_layers", "compatible_printers", "compatible_printers_condition", "inherits", "perimeter_generator", "wall_transition_length", "wall_transition_filter_deviation", "wall_transition_angle", - "wall_distribution_count", "min_feature_size", "min_bead_width", "ensure_vertical_shell_infill" + "wall_distribution_count", "min_feature_size", "min_bead_width" }; static std::vector s_Preset_filament_options { diff --git a/src/libslic3r/PrintConfig.cpp b/src/libslic3r/PrintConfig.cpp index 8d30b9610..878acc10a 100644 --- a/src/libslic3r/PrintConfig.cpp +++ b/src/libslic3r/PrintConfig.cpp @@ -222,12 +222,6 @@ static t_config_enum_values s_keys_map_PerimeterGeneratorType { }; CONFIG_OPTION_ENUM_DEFINE_STATIC_MAPS(PerimeterGeneratorType) -static t_config_enum_values s_keys_map_EnsuringInfillPattern { - { "bounded_rectilinear", int(EnsuringInfillPattern::eipBoundedRectilinear) }, - { "concentric", int(EnsuringInfillPattern::eipConcentric) } -}; -CONFIG_OPTION_ENUM_DEFINE_STATIC_MAPS(EnsuringInfillPattern) - static void assign_printer_technology_to_unknown(t_optiondef_map &options, PrinterTechnology printer_technology) { for (std::pair &kvp : options) @@ -3220,17 +3214,6 @@ void PrintConfigDef::init_fff_params() def->min = 0; def->set_default_value(new ConfigOptionFloatOrPercent(85, true)); - def = this->add("ensure_vertical_shell_infill", coEnum); - def->label = L("Ensure vertical shell infill"); - def->category = L("Layers and Perimeters"); - def->tooltip = L("Ensure vertical shell infill."); - def->set_enum({ - { "bounded_rectilinear", L("Bounded Rectilinear") }, - { "concentric", L("Concentric") } - }); - def->mode = comAdvanced; - def->set_default_value(new ConfigOptionEnum(EnsuringInfillPattern::eipBoundedRectilinear)); - // Declare retract values for filament profile, overriding the printer's extruder profile. for (const char *opt_key : { // floats diff --git a/src/libslic3r/PrintConfig.hpp b/src/libslic3r/PrintConfig.hpp index 5b4599743..cfd852ca1 100644 --- a/src/libslic3r/PrintConfig.hpp +++ b/src/libslic3r/PrintConfig.hpp @@ -133,11 +133,6 @@ enum class PerimeterGeneratorType Arachne }; -enum class EnsuringInfillPattern { - eipBoundedRectilinear, - eipConcentric -}; - enum class GCodeThumbnailsFormat { PNG, JPG, QOI }; @@ -167,7 +162,6 @@ CONFIG_OPTION_ENUM_DECLARE_STATIC_MAPS(DraftShield) CONFIG_OPTION_ENUM_DECLARE_STATIC_MAPS(GCodeThumbnailsFormat) CONFIG_OPTION_ENUM_DECLARE_STATIC_MAPS(ForwardCompatibilitySubstitutionRule) CONFIG_OPTION_ENUM_DECLARE_STATIC_MAPS(PerimeterGeneratorType) -CONFIG_OPTION_ENUM_DECLARE_STATIC_MAPS(EnsuringInfillPattern) #undef CONFIG_OPTION_ENUM_DECLARE_STATIC_MAPS @@ -497,7 +491,6 @@ PRINT_CONFIG_CLASS_DEFINE( ((ConfigOptionFloat, brim_width)) ((ConfigOptionBool, dont_support_bridges)) ((ConfigOptionFloat, elefant_foot_compensation)) - ((ConfigOptionEnum, ensure_vertical_shell_infill)) ((ConfigOptionFloatOrPercent, extrusion_width)) ((ConfigOptionFloat, first_layer_acceleration_over_raft)) ((ConfigOptionFloatOrPercent, first_layer_speed_over_raft)) diff --git a/src/libslic3r/PrintObject.cpp b/src/libslic3r/PrintObject.cpp index 19056b682..dab198406 100644 --- a/src/libslic3r/PrintObject.cpp +++ b/src/libslic3r/PrintObject.cpp @@ -774,8 +774,7 @@ bool PrintObject::invalidate_state_by_config_options( || opt_key == "wall_transition_angle" || opt_key == "wall_distribution_count" || opt_key == "min_feature_size" - || opt_key == "min_bead_width" - || opt_key == "ensure_vertical_shell_infill") { + || opt_key == "min_bead_width") { steps.emplace_back(posSlice); } else if ( opt_key == "seam_position" @@ -1774,6 +1773,20 @@ void PrintObject::bridge_over_infill() return a.min.x() < b.min.x(); }); + std::unordered_map> infill_and_deep_infill_polygons_per_region; + for (const auto &surface_region : surface_to_region) { + const LayerRegion *r = surface_region.second; + if (infill_and_deep_infill_polygons_per_region.find(r) == infill_and_deep_infill_polygons_per_region.end()) { + const Flow &flow = r->bridging_flow(frSolidInfill, true); + Polygons infill_region = to_polygons(r->fill_expolygons()); + Polygons deep_infill_area = closing(infill_region, scale_(0.01), scale_(0.01) + 4.0 * flow.scaled_spacing()); + Polygons solid_supported_area = expand(not_sparse_infill, 4.0 * flow.scaled_spacing()); + infill_and_deep_infill_polygons_per_region[r] = {closing(infill_region, scale_(0.1)), + intersection(lower_layers_sparse_infill, + diff(deep_infill_area, solid_supported_area))}; + } + } + // Lower layers sparse infill sections gathered // now we can intersected them with bridging surface candidates to get actual areas that need and can accumulate // bridging. These areas we then expand (within the surrounding sparse infill only!) @@ -1781,25 +1794,16 @@ void PrintObject::bridge_over_infill() for (const Surface *candidate : candidates.second) { const Flow &flow = surface_to_region[candidate]->bridging_flow(frSolidInfill, true); assert(candidate->surface_type == stInternalSolid); - Polygons bridged_area = expand(to_polygons(candidate->expolygon), flow.scaled_spacing()); - Polygons infill_region = to_polygons(surface_to_region[candidate]->fill_expolygons()); - bridged_area = - intersection(bridged_area, - lower_layers_sparse_infill); // cut off parts which are not over sparse infill - material overflow - - { - Polygons area_without_perimeter_boundary_sections = intersection(bridged_area, - closing(infill_region, flow.scaled_width(), - flow.scaled_width() + - 4.0 * flow.scaled_spacing())); - Polygons and_further_without_solid_supported_sections = diff(area_without_perimeter_boundary_sections, - expand(not_sparse_infill, 4.0 * flow.scaled_spacing())); - - if (and_further_without_solid_supported_sections.empty()) { - continue; - } + Polygons bridged_area = intersection(expand(to_polygons(candidate->expolygon), flow.scaled_spacing()), + infill_and_deep_infill_polygons_per_region[surface_to_region[candidate]].first); + // cut off parts which are not over sparse infill - material overflow + Polygons worth_bridging = intersection(bridged_area, + infill_and_deep_infill_polygons_per_region[surface_to_region[candidate]].second); + if (worth_bridging.empty()) { + continue; } + bridged_area = intersection(bridged_area, expand(worth_bridging, 5.0 * flow.scaled_spacing())); Polygons max_area = expand_area; max_area.insert(max_area.end(), bridged_area.begin(), bridged_area.end()); @@ -2133,7 +2137,9 @@ void PrintObject::bridge_over_infill() if (s.original_surface == &surface) { Surface tmp(surface, {}); for (const ExPolygon &expoly : diff_ex(surface.expolygon, s.new_polys)) { - new_surfaces.emplace_back(tmp, expoly); + if (expoly.area() > region->flow(frSolidInfill).scaled_width() * scale_(4.0)) { + new_surfaces.emplace_back(tmp, expoly); + } } tmp.surface_type = stInternalBridge; tmp.bridge_angle = s.bridge_angle; diff --git a/src/slic3r/GUI/Tab.cpp b/src/slic3r/GUI/Tab.cpp index 00a0122c3..17ddd63ab 100644 --- a/src/slic3r/GUI/Tab.cpp +++ b/src/slic3r/GUI/Tab.cpp @@ -1428,7 +1428,6 @@ void TabPrint::build() optgroup->append_single_option_line("extra_perimeters", category_path + "extra-perimeters-if-needed"); optgroup->append_single_option_line("extra_perimeters_on_overhangs", category_path + "extra-perimeters-on-overhangs"); optgroup->append_single_option_line("ensure_vertical_shell_thickness", category_path + "ensure-vertical-shell-thickness"); - optgroup->append_single_option_line("ensure_vertical_shell_infill"); optgroup->append_single_option_line("avoid_crossing_curled_overhangs", category_path + "avoid-crossing-curled-overhangs"); optgroup->append_single_option_line("avoid_crossing_perimeters", category_path + "avoid-crossing-perimeters"); optgroup->append_single_option_line("avoid_crossing_perimeters_max_detour", category_path + "avoid_crossing_perimeters_max_detour"); From b9872b8a3f3396cc77e767d7e0dd3e10cfa6abff Mon Sep 17 00:00:00 2001 From: PavelMikus Date: Thu, 2 Mar 2023 15:01:40 +0100 Subject: [PATCH 47/48] Fix problem with filtering of small ensuring regions - the filter needs to consider ExPolygons, not just polygons --- src/libslic3r/ClipperUtils.cpp | 2 ++ src/libslic3r/ClipperUtils.hpp | 3 +++ src/libslic3r/PrintObject.cpp | 45 ++++++++++++---------------------- 3 files changed, 20 insertions(+), 30 deletions(-) diff --git a/src/libslic3r/ClipperUtils.cpp b/src/libslic3r/ClipperUtils.cpp index 4c6e82c5f..ed76fc66a 100644 --- a/src/libslic3r/ClipperUtils.cpp +++ b/src/libslic3r/ClipperUtils.cpp @@ -718,6 +718,8 @@ Slic3r::ExPolygons diff_ex(const Slic3r::Surfaces &subject, const Slic3r::Surfac { return _clipper_ex(ClipperLib::ctDifference, ClipperUtils::SurfacesProvider(subject), ClipperUtils::SurfacesProvider(clip), do_safety_offset); } Slic3r::ExPolygons diff_ex(const Slic3r::SurfacesPtr &subject, const Slic3r::Polygons &clip, ApplySafetyOffset do_safety_offset) { return _clipper_ex(ClipperLib::ctDifference, ClipperUtils::SurfacesPtrProvider(subject), ClipperUtils::PolygonsProvider(clip), do_safety_offset); } +Slic3r::ExPolygons diff_ex(const Slic3r::SurfacesPtr &subject, const Slic3r::ExPolygons &clip, ApplySafetyOffset do_safety_offset) + { return _clipper_ex(ClipperLib::ctDifference, ClipperUtils::SurfacesPtrProvider(subject), ClipperUtils::ExPolygonsProvider(clip), do_safety_offset); } Slic3r::ExPolygons intersection_ex(const Slic3r::Polygons &subject, const Slic3r::Polygons &clip, ApplySafetyOffset do_safety_offset) { return _clipper_ex(ClipperLib::ctIntersection, ClipperUtils::PolygonsProvider(subject), ClipperUtils::PolygonsProvider(clip), do_safety_offset); } diff --git a/src/libslic3r/ClipperUtils.hpp b/src/libslic3r/ClipperUtils.hpp index 3dfc09c1a..5f495788b 100644 --- a/src/libslic3r/ClipperUtils.hpp +++ b/src/libslic3r/ClipperUtils.hpp @@ -373,6 +373,8 @@ inline Slic3r::Polygons shrink(const Slic3r::Polygons &polygons, const float d { assert(delta > 0); return offset(polygons, -delta, joinType, miterLimit); } inline Slic3r::ExPolygons shrink_ex(const Slic3r::Polygons &polygons, const float delta, ClipperLib::JoinType joinType = DefaultJoinType, double miterLimit = DefaultMiterLimit) { assert(delta > 0); return offset_ex(polygons, -delta, joinType, miterLimit); } +inline Slic3r::ExPolygons shrink_ex(const Slic3r::ExPolygons &polygons, const float delta, ClipperLib::JoinType joinType = DefaultJoinType, double miterLimit = DefaultMiterLimit) + { assert(delta > 0); return offset_ex(polygons, -delta, joinType, miterLimit); } // Wherever applicable, please use the opening() / closing() variants instead, they convey their purpose better. // Input polygons for negative offset shall be "normalized": There must be no overlap / intersections between the input polygons. @@ -433,6 +435,7 @@ Slic3r::ExPolygons diff_ex(const Slic3r::Surfaces &subject, const Slic3r::ExPoly Slic3r::ExPolygons diff_ex(const Slic3r::ExPolygons &subject, const Slic3r::Surfaces &clip, ApplySafetyOffset do_safety_offset = ApplySafetyOffset::No); Slic3r::ExPolygons diff_ex(const Slic3r::Surfaces &subject, const Slic3r::Surfaces &clip, ApplySafetyOffset do_safety_offset = ApplySafetyOffset::No); Slic3r::ExPolygons diff_ex(const Slic3r::SurfacesPtr &subject, const Slic3r::Polygons &clip, ApplySafetyOffset do_safety_offset = ApplySafetyOffset::No); +Slic3r::ExPolygons diff_ex(const Slic3r::SurfacesPtr &subject, const Slic3r::ExPolygons &clip, ApplySafetyOffset do_safety_offset = ApplySafetyOffset::No); Slic3r::Polylines diff_pl(const Slic3r::Polyline &subject, const Slic3r::Polygons &clip); Slic3r::Polylines diff_pl(const Slic3r::Polylines &subject, const Slic3r::Polygons &clip); Slic3r::Polylines diff_pl(const Slic3r::Polyline &subject, const Slic3r::ExPolygon &clip); diff --git a/src/libslic3r/PrintObject.cpp b/src/libslic3r/PrintObject.cpp index dab198406..0c20acc17 100644 --- a/src/libslic3r/PrintObject.cpp +++ b/src/libslic3r/PrintObject.cpp @@ -1474,51 +1474,36 @@ void PrintObject::discover_vertical_shells() #ifdef SLIC3R_DEBUG_SLICE_PROCESSING Polygons shell_before = shell; #endif /* SLIC3R_DEBUG_SLICE_PROCESSING */ -#if 1 + ExPolygons regularized_shell; { // Open to remove (filter out) regions narrower than a bit less than an infill extrusion line width. // Such narrow regions are difficult to fill in with a gap fill algorithm (or Arachne), however they are most likely // not needed for print stability / quality. - const float narrow_ensure_vertical_wall_thickness_region_radius = 0.5f * 0.85f * min_perimeter_infill_spacing; + const float narrow_ensure_vertical_wall_thickness_region_radius = 0.5f * 0.65f * min_perimeter_infill_spacing; // Then close gaps narrower than 1.2 * line width, such gaps are difficult to fill in with sparse infill, // thus they will be merged into the solid infill. const float narrow_sparse_infill_region_radius = 0.5f * 1.2f * min_perimeter_infill_spacing; // Finally expand the infill a bit to remove tiny gaps between solid infill and the other regions. const float tiny_overlap_radius = 0.2f * min_perimeter_infill_spacing; - shell = shrink(opening(union_(shell), + regularized_shell = shrink_ex(offset2_ex(union_ex(shell), // Open to remove (filter out) regions narrower than an infill extrusion line width. - narrow_ensure_vertical_wall_thickness_region_radius, + -narrow_ensure_vertical_wall_thickness_region_radius, // Then close gaps narrower than 1.2 * line width, such gaps are difficult to fill in with sparse infill. narrow_ensure_vertical_wall_thickness_region_radius + narrow_sparse_infill_region_radius, ClipperLib::jtSquare), // Finally expand the infill a bit to remove tiny gaps between solid infill and the other regions. narrow_sparse_infill_region_radius - tiny_overlap_radius, ClipperLib::jtSquare); - // The opening operation may cause scattered tiny drops on the smooth parts of the model. - shell.erase(std::remove_if(shell.begin(), shell.end(), [&min_perimeter_infill_spacing](const Polygon& p){ - return p.area() < min_perimeter_infill_spacing * scaled(8.0); - }), shell.end()); + // The opening operation may cause scattered tiny drops on the smooth parts of the model, filter them out + regularized_shell.erase(std::remove_if(regularized_shell.begin(), regularized_shell.end(), + [&min_perimeter_infill_spacing](const ExPolygon &p) { + return p.area() < min_perimeter_infill_spacing * scaled(8.0); + }), + regularized_shell.end()); } - if (shell.empty()) + if (regularized_shell.empty()) continue; -#else - // Ensure each region is at least 3x infill line width wide, so it could be filled in. - // float margin = float(infill_line_spacing) * 3.f; - float margin = float(infill_line_spacing) * 1.5f; - // 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. - Polygons too_narrow = diff(shell, opening(shell, margin, ClipperLib::jtMiter, 5.), true); - 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 - polygons_append(shell, intersection(offset(too_narrow, margin), polygonsInternal)); - } -#endif - ExPolygons new_internal_solid = intersection_ex(polygonsInternal, shell); + + ExPolygons new_internal_solid = intersection_ex(polygonsInternal, regularized_shell); #ifdef SLIC3R_DEBUG_SLICE_PROCESSING { Slic3r::SVG svg(debug_out_path("discover_vertical_shells-regularized-%d.svg", debug_idx), get_extents(shell_before)); @@ -1533,8 +1518,8 @@ void PrintObject::discover_vertical_shells() #endif /* SLIC3R_DEBUG_SLICE_PROCESSING */ // Trim the internal & internalvoid by the shell. - Slic3r::ExPolygons new_internal = diff_ex(layerm->fill_surfaces().filter_by_type(stInternal), shell); - Slic3r::ExPolygons new_internal_void = diff_ex(layerm->fill_surfaces().filter_by_type(stInternalVoid), shell); + Slic3r::ExPolygons new_internal = diff_ex(layerm->fill_surfaces().filter_by_type(stInternal), regularized_shell); + Slic3r::ExPolygons new_internal_void = diff_ex(layerm->fill_surfaces().filter_by_type(stInternalVoid), regularized_shell); #ifdef SLIC3R_DEBUG_SLICE_PROCESSING { From 475c77ca96f8107f973b5115c70e16b8a54449fc Mon Sep 17 00:00:00 2001 From: PavelMikus Date: Thu, 2 Mar 2023 17:06:06 +0100 Subject: [PATCH 48/48] fix some double to float truncation warnings --- src/libslic3r/PrintConfig.cpp | 8 ++++---- src/libslic3r/PrintObject.cpp | 2 +- src/libslic3r/SupportSpotsGenerator.cpp | 4 ++-- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/libslic3r/PrintConfig.cpp b/src/libslic3r/PrintConfig.cpp index 3425cfd7a..ba1c7f056 100644 --- a/src/libslic3r/PrintConfig.cpp +++ b/src/libslic3r/PrintConfig.cpp @@ -591,7 +591,7 @@ void PrintConfigDef::init_fff_params() "Fan speeds for overhang sizes in between are calculated via linear interpolation. "); def = this->add("overhang_fan_speed_0", coInts); - def->label = L("speed for 0\% overlap (bridge)"); + def->label = L("speed for 0% overlap (bridge)"); def->tooltip = fan_speed_setting_description; def->sidetext = L("%"); def->min = 0; @@ -600,7 +600,7 @@ void PrintConfigDef::init_fff_params() def->set_default_value(new ConfigOptionInts{0}); def = this->add("overhang_fan_speed_1", coInts); - def->label = L("speed for 25\% overlap"); + def->label = L("speed for 25% overlap"); def->tooltip = fan_speed_setting_description; def->sidetext = L("%"); def->min = 0; @@ -609,7 +609,7 @@ void PrintConfigDef::init_fff_params() def->set_default_value(new ConfigOptionInts{0}); def = this->add("overhang_fan_speed_2", coInts); - def->label = L("speed for 50\% overlap"); + def->label = L("speed for 50% overlap"); def->tooltip = fan_speed_setting_description; def->sidetext = L("%"); def->min = 0; @@ -618,7 +618,7 @@ void PrintConfigDef::init_fff_params() def->set_default_value(new ConfigOptionInts{0}); def = this->add("overhang_fan_speed_3", coInts); - def->label = L("speed for 75\% overlap"); + def->label = L("speed for 75% overlap"); def->tooltip = fan_speed_setting_description; def->sidetext = L("%"); def->min = 0; diff --git a/src/libslic3r/PrintObject.cpp b/src/libslic3r/PrintObject.cpp index 1511b72cd..f87741cfa 100644 --- a/src/libslic3r/PrintObject.cpp +++ b/src/libslic3r/PrintObject.cpp @@ -1737,7 +1737,7 @@ void PrintObject::bridge_over_infill() lower_layers_sparse_infill.insert(lower_layers_sparse_infill.end(), special_infill.begin(), special_infill.end()); - if (shrink(lower_layers_sparse_infill, 3.0 * scaled(max_bridge_flow_height[candidates.first])).empty()) { + if (shrink(lower_layers_sparse_infill, 3.0 * scale_(max_bridge_flow_height[candidates.first])).empty()) { continue; } } diff --git a/src/libslic3r/SupportSpotsGenerator.cpp b/src/libslic3r/SupportSpotsGenerator.cpp index d45f82db8..5062fe18a 100644 --- a/src/libslic3r/SupportSpotsGenerator.cpp +++ b/src/libslic3r/SupportSpotsGenerator.cpp @@ -901,7 +901,7 @@ std::tuple check_stability(const PrintObject *po, params)) { if (bridge.support_point_generated.has_value()) { reckon_new_support_point(*bridge.support_point_generated, create_support_point_position(bridge.b), - -EPSILON, Vec2f::Zero()); + float(-EPSILON), Vec2f::Zero()); } } } @@ -916,7 +916,7 @@ std::tuple check_stability(const PrintObject *po, params); for (const ExtrusionLine &perim : perims) { if (perim.support_point_generated.has_value()) { - reckon_new_support_point(*perim.support_point_generated, create_support_point_position(perim.b), -EPSILON, + reckon_new_support_point(*perim.support_point_generated, create_support_point_position(perim.b), float(-EPSILON), Vec2f::Zero()); } if (perim.is_external_perimeter()) {