From d3734aa5ae2acc07a4031b2678cbb07ed6af1efe Mon Sep 17 00:00:00 2001 From: Vojtech Bubnik Date: Thu, 29 Sep 2022 16:51:44 +0200 Subject: [PATCH 01/19] 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 22d4cb4197..ea9b60a477 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/19] 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 518b4b7c35..3094e38b4b 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 641476c8b0..849672a8f9 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 0000000000..2a22fe7858 --- /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 0000000000..bbfcc0a655 --- /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 6c16d08b78..3c124fe2a2 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 061cf1423c..eed003be2c 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 06e32e2a23..bdf057f585 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 4762c3e241..abeec68937 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 65ecfb76a1..a9bd36fe15 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 0000000000..d69b2e28a3 --- /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 35b4a331ab..7b44b8b189 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 c700c45f83..6ca37d7b49 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 beb496b288..794b27c444 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 32dcb82d05..389fa313b5 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 bee5e51ba5..de8f859a52 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 d5a21cf219..2ec12c5294 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 ea923b48e3..5968769752 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 ae2474ad50..971db528cd 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 0000000000..5d83b8e9dd --- /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/19] 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 cf6a7dce9d..0000000000 --- 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 df0e8f6bb6..d9e5817caa 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/19] 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 e70e8be39b..6d6479508f 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 2a22fe7858..4cd4689e6f 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 bbfcc0a655..26aab198a3 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 b11736417e..b63be869df 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 4be42d763a..b834ca5180 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 0f42b21076..d28b64bffb 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 24a4191c15..1b593df267 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 4afcdf6f5c..1f352e9777 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 10a12b6836..fbc30ca63e 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 b81808b329..0f62875b26 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 495bcbc9b7..571451b872 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 5d83b8e9dd..9f8a6fdc5f 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/19] 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 1b593df267..85e70779ce 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/19] 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 4cd4689e6f..83561fc83b 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 d28b64bffb..8413095ce6 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/19] 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 ef3dc32c81..a0425bfee4 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 794b27c444..09afcc3993 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 389fa313b5..e86cdcceda 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/19] 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 83561fc83b..3846c47495 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 d69b2e28a3..31f8228137 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/19] =?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 b38c5c9f88..0bf3c4dde6 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 5200e1c0d7..2a9091d354 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 7b005ee357..245947cfe0 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 405b7238bf..c059cc0500 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 bb93d824be..c39ec8b1aa 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 0a6c976ad6..9f833ea9e5 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 e2dcabfe11..d805473b58 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 de8f859a52..5615841e27 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 7f8d5df121..edfac5f4fa 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/19] 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 c962bbeb01..1093de1f35 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 024ed41a45..e6c47fc7a1 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 8413095ce6..5add6a7eb3 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/19] 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 a0425bfee4..8be3c68e35 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 109c949cd3..16d793f9fa 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 6157ffe3c8..fc184be774 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 abf7308f25..33f5214b0a 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/19] 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 c1e1c3b733..b2608883cb 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/19] 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 31f8228137..dd42e3d66b 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 c39ec8b1aa..40579fa3f9 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 9f833ea9e5..9a022873f7 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 f8e1dc6d75..3b3dff75f8 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 c92796f41f..7036121522 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 5add6a7eb3..c26ed7dfdc 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 842638efcd..2e5871b566 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 b416db634f..975510c93a 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 1f0aa00b23..24db849ef4 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/19] 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 40579fa3f9..b0c1e50966 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/19] 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 bdf057f585..92983a34d4 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 1093de1f35..5eee8706ff 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 0bf3c4dde6..c92462148b 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 2a9091d354..cf37667587 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 0000000000..60b92d16ff --- /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 0000000000..faa0801535 --- /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 b0c1e50966..bb93d824be 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 9a022873f7..3ba5823046 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 edfac5f4fa..a253b95a5f 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/19] 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 60b92d16ff..5e37875430 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/19] 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 5e37875430..a501f97326 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 2e5871b566..51bd25fb39 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 fd752873d3..9b5bc3e670 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 a253b95a5f..c8cab5adaa 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 85e70779ce..b2ce77c7d4 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 cf3b96d803..0e6e4c354a 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/19] 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 3846c47495..844cda8223 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 dd42e3d66b..4ae78ae235 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 c26ed7dfdc..0777c5ef78 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 85e70779ce..7d97ec256b 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 d46094f7fb..af3f4f88ad 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 85e170bd03..96f61ba8db 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/19] 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 5eee8706ff..7ff34d40cf 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 c92462148b..f55420c312 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 bb93d824be..e27017b035 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 3ba5823046..aa5c014b3c 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 7a9ec51c0b..3b7bfddbf9 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 c8cab5adaa..3c737778a4 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,