From d3734aa5ae2acc07a4031b2678cbb07ed6af1efe Mon Sep 17 00:00:00 2001 From: Vojtech Bubnik Date: Thu, 29 Sep 2022 16:51:44 +0200 Subject: [PATCH 001/110] 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 002/110] 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 003/110] 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 004/110] 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 005/110] 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 006/110] 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 007/110] 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 008/110] 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 009/110] =?UTF-8?q?Cherry=20picked=20FillBoundedRectilinea?= =?UTF-8?q?r=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 010/110] 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 011/110] 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 012/110] 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 013/110] 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 014/110] 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 015/110] 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 016/110] 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 017/110] 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 018/110] 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 019/110] 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, From 7f80f7456bcd8ef6e495435ca51587ca75839073 Mon Sep 17 00:00:00 2001 From: Vojtech Bubnik Date: Fri, 17 Feb 2023 15:18:44 +0100 Subject: [PATCH 020/110] Merged with master --- src/libslic3r/SupportMaterial.cpp | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/libslic3r/SupportMaterial.cpp b/src/libslic3r/SupportMaterial.cpp index 836dc8ac97..3215c9d6aa 100644 --- a/src/libslic3r/SupportMaterial.cpp +++ b/src/libslic3r/SupportMaterial.cpp @@ -799,10 +799,6 @@ public: ) { switch (m_style) { - case smsTree: - case smsOrganic: - assert(false); - [[fallthrough]]; case smsGrid: { #ifdef SUPPORT_USE_AGG_RASTERIZER @@ -893,6 +889,10 @@ public: polygons_rotate(out, m_support_angle); return out; } + case smsTree: + case smsOrganic: +// assert(false); + [[fallthrough]]; case smsSnug: // Merge the support polygons by applying morphological closing and inwards smoothing. auto closing_distance = scaled(m_support_material_closing_radius); @@ -1763,7 +1763,7 @@ static inline void fill_contact_layer( #endif // SLIC3R_DEBUG )); // 2) infill polygons, expand them by half the extrusion width + a tiny bit of extra. - bool reduce_interfaces = object_config.support_material_style.value != smsSnug && layer_id > 0 && !slicing_params.soluble_interface; + bool reduce_interfaces = object_config.support_material_style.value == smsGrid && layer_id > 0 && !slicing_params.soluble_interface; if (reduce_interfaces) { // Reduce the amount of dense interfaces: Do not generate dense interfaces below overhangs with 60% overhang of the extrusions. Polygons dense_interface_polygons = diff(overhang_polygons, lower_layer_polygons_for_dense_interface()); @@ -3045,7 +3045,7 @@ std::pair PrintObjectSuppo m_object_config->support_material_interface_extruder.value > 0 && m_print_config->filament_soluble.get_at(m_object_config->support_material_interface_extruder.value - 1) && // Base extruder: Either "print with active extruder" not soluble. (m_object_config->support_material_extruder.value == 0 || ! m_print_config->filament_soluble.get_at(m_object_config->support_material_extruder.value - 1)); - bool snug_supports = m_object_config->support_material_style.value == smsSnug; + bool snug_supports = m_object_config->support_material_style.value != smsGrid; int num_interface_layers_top = m_object_config->support_material_interface_layers; int num_interface_layers_bottom = m_object_config->support_material_bottom_interface_layers; if (num_interface_layers_bottom < 0) @@ -4227,7 +4227,7 @@ void generate_support_toolpaths( } // Insert the raft base layers. - size_t n_raft_layers = size_t(std::max(0, int(slicing_params.raft_layers()) - 1)); + size_t n_raft_layers = std::min(support_layers.size(), std::max(0, int(slicing_params.raft_layers()) - 1)); tbb::parallel_for(tbb::blocked_range(0, n_raft_layers), [&support_layers, &raft_layers, &config, &support_params, &slicing_params, &bbox_object, raft_angle_1st_layer, raft_angle_base, raft_angle_interface, link_max_length_factor] @@ -4356,7 +4356,7 @@ void generate_support_toolpaths( { SupportLayer &support_layer = *support_layers[support_layer_id]; LayerCache &layer_cache = layer_caches[support_layer_id]; - float interface_angle_delta = config.support_material_style.value == smsSnug || config.support_material_style.value == smsTree || config.support_material_style.value == smsOrganic ? + float interface_angle_delta = config.support_material_style.value != smsGrid ? (support_layer.interface_id() & 1) ? float(- M_PI / 4.) : float(+ M_PI / 4.) : 0; From 696b316e2bda294558f802801b4bd7c799714b9c Mon Sep 17 00:00:00 2001 From: PavelMikus Date: Wed, 8 Feb 2023 19:10:58 +0100 Subject: [PATCH 021/110] initial implementation, trying to filter out surfaces that need expansion --- src/libslic3r/BridgeDetector.hpp | 6 ++ src/libslic3r/PrintObject.cpp | 85 ++++++++++++++++++++++++- src/libslic3r/SupportSpotsGenerator.hpp | 1 + 3 files changed, 91 insertions(+), 1 deletion(-) diff --git a/src/libslic3r/BridgeDetector.hpp b/src/libslic3r/BridgeDetector.hpp index bc5da9712f..bb1c1ead3d 100644 --- a/src/libslic3r/BridgeDetector.hpp +++ b/src/libslic3r/BridgeDetector.hpp @@ -126,6 +126,12 @@ inline std::tuple detect_bridging_direction(const Polygons &to_co }; +inline Vec2d detect_internal_bridge_direction() { + //TODO AABB tree, get nearest point on the infill polylines (or perimeters) and do not take duplicates + // this could yield some density map where the more scattered points are more interesting +} + + } #endif diff --git a/src/libslic3r/PrintObject.cpp b/src/libslic3r/PrintObject.cpp index c4a5946e85..ab8fa5dd93 100644 --- a/src/libslic3r/PrintObject.cpp +++ b/src/libslic3r/PrintObject.cpp @@ -27,9 +27,13 @@ #include #include +#include #include +#include +#include #include #include +#include #include #include @@ -1522,7 +1526,86 @@ void PrintObject::discover_vertical_shells() // This method applies bridge flow to the first internal solid layer above sparse infill. void PrintObject::bridge_over_infill() { - BOOST_LOG_TRIVIAL(info) << "Bridge over infill..." << log_memory_info(); + BOOST_LOG_TRIVIAL(info) << "Bridge over infill - Start" << log_memory_info(); + + tbb::parallel_for(tbb::blocked_range(0, this->layers().size()), [po = this](tbb::blocked_range r) { + for (size_t lidx = r.begin(); lidx < r.end(); lidx++) { + + const Layer *layer = po->get_layer(lidx); + + // gather also sparse infill surfaces on this layer, to which we can expand the bridges for anchoring + // gather potential internal bridging surfaces for the current layer + // pair of LayerSlice idx and surfaces. The LayerSlice idx simplifies the processing, since we cannot expand beyond it + std::unordered_map bridging_surface_candidates; + std::unordered_map expansion_space; + std::unordered_map max_bridge_flow_height; + for (const LayerSlice &slice : layer->lslices_ex) { + std::unordered_set regions_to_check; + for (const LayerIsland &island : slice.islands) { + regions_to_check.insert(island.perimeters.region()); + if (!island.fill_expolygons_composite()) { + regions_to_check.insert(island.fill_region_id); + } + } + + for (size_t region_idx : regions_to_check) { + const LayerRegion *region = layer->get_region(region_idx); + auto region_internal_solids = region->fill_surfaces().filter_by_type(stInternalSolid); + if (!region_internal_solids.empty()) { + max_bridge_flow_height[&slice] = std::max(max_bridge_flow_height[&slice], + region->bridging_flow(frSolidInfill).height()); + } + bridging_surface_candidates[&slice].insert(bridging_surface_candidates[&slice].end(), region_internal_solids.begin(), + region_internal_solids.end()); + auto region_sparse_infill = region->fill_surfaces().filter_by_type(stInternal); + expansion_space[&slice].insert(expansion_space[&slice].end(), region_sparse_infill.begin(), region_sparse_infill.end()); + } + } + + for (const std::pair &candidates : bridging_surface_candidates) { + if (candidates.second.empty()) { + continue; + }; + + // Gather lower layers sparse infill areas, to depth defined by used bridge flow + Polygons lower_layers_sparse_infill; + double bottom_z = layer->print_z - max_bridge_flow_height[candidates.first] - EPSILON; + LayerSlice::Links current_links = candidates.first->overlaps_below; + for (auto i = int(lidx) - 1; i >= 0; --i) { + // Stop iterating if layer is lower than bottom_z. + if (po->get_layer(i)->print_z < bottom_z) + break; + for (const auto &link : current_links) { + const LayerSlice& slice_below = po->get_layer(i)->lslices_ex[link.slice_idx]; + for (const LayerRegion *region : po->get_layer(i)->regions()) { + for (const Surface *surface : region->fill_surfaces().filter_by_type(stInternal)) { + Polygons p = to_polygons(surface->expolygon); + lower_layers_sparse_infill.insert(lower_layers_sparse_infill.end(), p.begin(), p.end()); + } + } + } + } + lower_layers_sparse_infill = union_(lower_layers_sparse_infill); + } + + // Now, temporarily fill the previous layer and extract the extrusions. + po->get_layer(lidx)->lower_layer->make_fills(nullptr, nullptr, nullptr); + Polylines lower_layer_polylines; + for (const LayerRegion *region : layer->lower_layer->m_regions) { + for (const ExtrusionEntity *ee : region->fills().entities) { + assert(ee->is_collection()); + auto region_polylines = dynamic_cast(ee)->as_polylines(); + lower_layer_polylines.insert(lower_layer_polylines.end(), region_polylines.begin(), region_polylines.end()); + } + } + } + }); + + BOOST_LOG_TRIVIAL(info) << "Bridge over infill - End" << log_memory_info(); + + + + std::vector sparse_infill_regions; for (size_t region_id = 0; region_id < this->num_printing_regions(); ++ region_id) diff --git a/src/libslic3r/SupportSpotsGenerator.hpp b/src/libslic3r/SupportSpotsGenerator.hpp index 018b856e0c..543b9c92bb 100644 --- a/src/libslic3r/SupportSpotsGenerator.hpp +++ b/src/libslic3r/SupportSpotsGenerator.hpp @@ -145,6 +145,7 @@ struct PartialObject using PartialObjects = std::vector; +// Both support points and partial objects are sorted from the lowest z to the highest std::tuple full_search(const PrintObject *po, const PrintTryCancel& cancel_func, const Params ¶ms); void estimate_supports_malformations(std::vector &layers, float supports_flow_width, const Params ¶ms); From fd81d7a46094de9884f6a3066d796c5ff150e45f Mon Sep 17 00:00:00 2001 From: PavelMikus Date: Thu, 9 Feb 2023 16:41:18 +0100 Subject: [PATCH 022/110] Compute bridging direction. Now what is left is to stretch the bridge lines across infill and cut them to ideal length --- src/libslic3r/BridgeDetector.hpp | 6 -- src/libslic3r/PrintObject.cpp | 165 +++++++++++++++++++++++++------ 2 files changed, 134 insertions(+), 37 deletions(-) diff --git a/src/libslic3r/BridgeDetector.hpp b/src/libslic3r/BridgeDetector.hpp index bb1c1ead3d..bc5da9712f 100644 --- a/src/libslic3r/BridgeDetector.hpp +++ b/src/libslic3r/BridgeDetector.hpp @@ -126,12 +126,6 @@ inline std::tuple detect_bridging_direction(const Polygons &to_co }; -inline Vec2d detect_internal_bridge_direction() { - //TODO AABB tree, get nearest point on the infill polylines (or perimeters) and do not take duplicates - // this could yield some density map where the more scattered points are more interesting -} - - } #endif diff --git a/src/libslic3r/PrintObject.cpp b/src/libslic3r/PrintObject.cpp index ab8fa5dd93..1c9ea6a985 100644 --- a/src/libslic3r/PrintObject.cpp +++ b/src/libslic3r/PrintObject.cpp @@ -1,6 +1,11 @@ +#include "AABBTreeLines.hpp" +#include "BridgeDetector.hpp" +#include "ExPolygon.hpp" #include "Exception.hpp" #include "KDTreeIndirect.hpp" #include "Point.hpp" +#include "Polygon.hpp" +#include "Polyline.hpp" #include "Print.hpp" #include "BoundingBox.hpp" #include "ClipperUtils.hpp" @@ -1530,7 +1535,6 @@ void PrintObject::bridge_over_infill() tbb::parallel_for(tbb::blocked_range(0, this->layers().size()), [po = this](tbb::blocked_range r) { for (size_t lidx = r.begin(); lidx < r.end(); lidx++) { - const Layer *layer = po->get_layer(lidx); // gather also sparse infill surfaces on this layer, to which we can expand the bridges for anchoring @@ -1539,6 +1543,7 @@ void PrintObject::bridge_over_infill() std::unordered_map bridging_surface_candidates; std::unordered_map expansion_space; std::unordered_map max_bridge_flow_height; + std::unordered_map max_bridge_flow_width; for (const LayerSlice &slice : layer->lslices_ex) { std::unordered_set regions_to_check; for (const LayerIsland &island : slice.islands) { @@ -1552,8 +1557,10 @@ void PrintObject::bridge_over_infill() const LayerRegion *region = layer->get_region(region_idx); auto region_internal_solids = region->fill_surfaces().filter_by_type(stInternalSolid); if (!region_internal_solids.empty()) { - max_bridge_flow_height[&slice] = std::max(max_bridge_flow_height[&slice], - region->bridging_flow(frSolidInfill).height()); + max_bridge_flow_height[&slice] = std::max(max_bridge_flow_height[&slice], + region->bridging_flow(frSolidInfill).height()); + max_bridge_flow_width[&slice] = std::max(max_bridge_flow_width[&slice], + region->bridging_flow(frSolidInfill).width()); } bridging_surface_candidates[&slice].insert(bridging_surface_candidates[&slice].end(), region_internal_solids.begin(), region_internal_solids.end()); @@ -1562,33 +1569,15 @@ void PrintObject::bridge_over_infill() } } - for (const std::pair &candidates : bridging_surface_candidates) { - if (candidates.second.empty()) { - continue; - }; - - // Gather lower layers sparse infill areas, to depth defined by used bridge flow - Polygons lower_layers_sparse_infill; - double bottom_z = layer->print_z - max_bridge_flow_height[candidates.first] - EPSILON; - LayerSlice::Links current_links = candidates.first->overlaps_below; - for (auto i = int(lidx) - 1; i >= 0; --i) { - // Stop iterating if layer is lower than bottom_z. - if (po->get_layer(i)->print_z < bottom_z) - break; - for (const auto &link : current_links) { - const LayerSlice& slice_below = po->get_layer(i)->lslices_ex[link.slice_idx]; - for (const LayerRegion *region : po->get_layer(i)->regions()) { - for (const Surface *surface : region->fill_surfaces().filter_by_type(stInternal)) { - Polygons p = to_polygons(surface->expolygon); - lower_layers_sparse_infill.insert(lower_layers_sparse_infill.end(), p.begin(), p.end()); - } - } - } - } - lower_layers_sparse_infill = union_(lower_layers_sparse_infill); + // if there are none briding candidates, exit now, before making infill for the previous layer + if (std::all_of(bridging_surface_candidates.begin(), bridging_surface_candidates.end(), + [](const std::pair &candidates) { return candidates.second.empty(); })) { + continue; } // Now, temporarily fill the previous layer and extract the extrusions. + // TODO - the make_fills function does a lot of work, some of it is not needed (e.g. sorting the paths) + // It would be nice to have a function that only creates the fill polylines, ideally without modifying the global state po->get_layer(lidx)->lower_layer->make_fills(nullptr, nullptr, nullptr); Polylines lower_layer_polylines; for (const LayerRegion *region : layer->lower_layer->m_regions) { @@ -1598,15 +1587,128 @@ void PrintObject::bridge_over_infill() lower_layer_polylines.insert(lower_layer_polylines.end(), region_polylines.begin(), region_polylines.end()); } } - } + + for (const std::pair candidates : bridging_surface_candidates) { + if (candidates.second.empty()) { + continue; + }; + + // Gather lower layers sparse infill areas, to depth defined by used bridge flow + Polygons lower_layers_sparse_infill; + double bottom_z = layer->print_z - max_bridge_flow_height[candidates.first] - EPSILON; + LayerSlice::Links current_links = candidates.first->overlaps_below; + LayerSlice::Links next_links{}; + for (auto i = int(lidx) - 1; i >= 0; --i) { + // Stop iterating if layer is lower than bottom_z. + if (po->get_layer(i)->print_z < bottom_z) + break; + for (const auto &link : current_links) { + const LayerSlice &slice_below = po->get_layer(i)->lslices_ex[link.slice_idx]; + next_links.insert(next_links.end(), slice_below.overlaps_below.begin(), slice_below.overlaps_below.end()); + std::unordered_set regions_under_to_check; + for (const LayerIsland &island : slice_below.islands) { + regions_under_to_check.insert(island.perimeters.region()); + if (!island.fill_expolygons_composite()) { + regions_under_to_check.insert(island.fill_region_id); + } + } + + for (size_t region_idx : regions_under_to_check) { + const LayerRegion *region = layer->get_region(region_idx); + for (const Surface *surface : region->fill_surfaces().filter_by_type(stInternal)) { + Polygons p = to_polygons(surface->expolygon); + lower_layers_sparse_infill.insert(lower_layers_sparse_infill.end(), p.begin(), p.end()); + } + } + } + current_links = next_links; + } + if (lower_layers_sparse_infill.empty()) { + continue; + } + lower_layers_sparse_infill = union_(lower_layers_sparse_infill); + + Polygons expand_area; + for (const Surface *sparse_infill : expansion_space[candidates.first]) { + assert(sparse_infill->surface_type == stInternal); + Polygons a = to_polygons(sparse_infill->expolygon); + expand_area.insert(expand_area.end(), a.begin(), a.end()); + } + + // Lower layers sparse infill sections gathered + // now we can intersected them with bridging surface candidates to get actual areas that need and can accumulate + // bridging. These areas we then expand (within the surrounding sparse infill only!) + // to touch the infill polylines on previous layer. + for (const Surface *candidate : candidates.second) { + assert(candidate->surface_type == stInternalSolid); + Polygons bridged_area = to_polygons(candidate->expolygon); + bridged_area = + intersection(bridged_area, + lower_layers_sparse_infill); // cut off parts which are not over sparse infill - material overflow + + if (bridged_area.empty()) { + continue; + } + + Polygons max_area = expand_area; + max_area.insert(max_area.end(), bridged_area.begin(), bridged_area.end()); + closing(max_area, max_bridge_flow_width[candidates.first]); + + Polylines anchors = intersection_pl(lower_layer_polylines, max_area); + anchors = diff_pl(anchors, shrink(bridged_area, scale_(max_bridge_flow_width[candidates.first]))); + + AABBTreeLines::LinesDistancer anchors_and_walls; + { + Lines tmp = to_lines(anchors); + Lines tmp2 = to_lines(max_area); + tmp.insert(tmp.end(), tmp.begin(), tmp.end()); + anchors_and_walls = AABBTreeLines::LinesDistancer{tmp}; + } + + double bridging_dir = 0; + { + std::vector> directions_with_distances; + for (const Polygon &p : bridged_area) { + for (int point_idx = 0; point_idx < int(p.points.size()) - 1; ++point_idx) { + Vec2d start = p.points[point_idx].cast(); + Vec2d next = p.points[point_idx + 1].cast(); + Vec2d v = next - start; // vector from next to current + double dist_to_next = v.norm(); + v.normalize(); + int lines_count = int(std::ceil(dist_to_next / scaled(3.0))); + float step_size = dist_to_next / lines_count; + for (int i = 0; i < lines_count; ++i) { + Point a(start + v * (i * step_size)); + auto [distance, index, p] = anchors_and_walls.distance_from_lines_extra(a); + const Line& l = anchors_and_walls.get_line(index); + directions_with_distances.emplace_back(l.direction(), unscaled(distance)); + } + } + } + double max_dist = directions_with_distances[0].second; + for (const auto& dir :directions_with_distances) { + max_dist = std::max(max_dist, dir.second); + } + double acc = 0; + for (const auto& dir : directions_with_distances) { + bridging_dir += dir.first * (max_dist - dir.second); + acc += (max_dist - dir.second); + } + bridging_dir /= acc; + } + + + + } // surface iteration end + } // island iteration end + } // layer iteration end }); BOOST_LOG_TRIVIAL(info) << "Bridge over infill - End" << log_memory_info(); +} // void PrintObject::bridge_over_infill() - - - +void a(){ std::vector sparse_infill_regions; for (size_t region_id = 0; region_id < this->num_printing_regions(); ++ region_id) if (const PrintRegion ®ion = this->printing_region(region_id); region.config().fill_density.value < 100) @@ -1731,6 +1833,7 @@ void PrintObject::bridge_over_infill() m_print->throw_if_canceled(); } }); + } // void PrintObject::bridge_over_infill() static void clamp_exturder_to_default(ConfigOptionInt &opt, size_t num_extruders) From ebcedca21198211dac2de17e8c5eb0d1c686dbbf Mon Sep 17 00:00:00 2001 From: PavelMikus Date: Thu, 9 Feb 2023 17:42:39 +0100 Subject: [PATCH 023/110] write down general idea how to continue --- src/libslic3r/PrintObject.cpp | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/libslic3r/PrintObject.cpp b/src/libslic3r/PrintObject.cpp index 1c9ea6a985..61c761be02 100644 --- a/src/libslic3r/PrintObject.cpp +++ b/src/libslic3r/PrintObject.cpp @@ -1681,7 +1681,7 @@ void PrintObject::bridge_over_infill() Point a(start + v * (i * step_size)); auto [distance, index, p] = anchors_and_walls.distance_from_lines_extra(a); const Line& l = anchors_and_walls.get_line(index); - directions_with_distances.emplace_back(l.direction(), unscaled(distance)); + directions_with_distances.emplace_back(PI - l.direction(), unscaled(distance)); } } } @@ -1697,7 +1697,10 @@ void PrintObject::bridge_over_infill() bridging_dir /= acc; } - + //TODO use get_extens_rotated on the bridged_area polygons, generate vertical lines of the box, + // OR maybe get extens of rotated max_area, then fill with vertical lines, make AABB tree rotated for anchors and walls and also + // for bridged area + // then cut off the vertical lines, compose the final polygon, and rotate back } // surface iteration end } // island iteration end From a57f98961e43a7224b80550ccef8c1a97d5a1a7f Mon Sep 17 00:00:00 2001 From: Pavel Mikus Date: Fri, 10 Feb 2023 20:15:12 +0100 Subject: [PATCH 024/110] reconstruction of polygon from vertical slices TODO --- src/libslic3r/PrintObject.cpp | 392 +++++++++++++++++++++------------- 1 file changed, 240 insertions(+), 152 deletions(-) diff --git a/src/libslic3r/PrintObject.cpp b/src/libslic3r/PrintObject.cpp index 61c761be02..13fc926597 100644 --- a/src/libslic3r/PrintObject.cpp +++ b/src/libslic3r/PrintObject.cpp @@ -2,6 +2,7 @@ #include "BridgeDetector.hpp" #include "ExPolygon.hpp" #include "Exception.hpp" +#include "Flow.hpp" #include "KDTreeIndirect.hpp" #include "Point.hpp" #include "Polygon.hpp" @@ -1533,185 +1534,272 @@ void PrintObject::bridge_over_infill() { BOOST_LOG_TRIVIAL(info) << "Bridge over infill - Start" << log_memory_info(); - tbb::parallel_for(tbb::blocked_range(0, this->layers().size()), [po = this](tbb::blocked_range r) { - for (size_t lidx = r.begin(); lidx < r.end(); lidx++) { - const Layer *layer = po->get_layer(lidx); + tbb::parallel_for( + tbb::blocked_range(0, this->layers().size()), + [po = this](tbb::blocked_range r) { + for (size_t lidx = r.begin(); lidx < r.end(); lidx++) { + const Layer *layer = po->get_layer(lidx); - // gather also sparse infill surfaces on this layer, to which we can expand the bridges for anchoring - // gather potential internal bridging surfaces for the current layer - // pair of LayerSlice idx and surfaces. The LayerSlice idx simplifies the processing, since we cannot expand beyond it - std::unordered_map bridging_surface_candidates; - std::unordered_map expansion_space; - std::unordered_map max_bridge_flow_height; - std::unordered_map max_bridge_flow_width; - for (const LayerSlice &slice : layer->lslices_ex) { - std::unordered_set regions_to_check; - for (const LayerIsland &island : slice.islands) { - regions_to_check.insert(island.perimeters.region()); - if (!island.fill_expolygons_composite()) { - regions_to_check.insert(island.fill_region_id); - } - } - - for (size_t region_idx : regions_to_check) { - const LayerRegion *region = layer->get_region(region_idx); - auto region_internal_solids = region->fill_surfaces().filter_by_type(stInternalSolid); - if (!region_internal_solids.empty()) { - max_bridge_flow_height[&slice] = std::max(max_bridge_flow_height[&slice], - region->bridging_flow(frSolidInfill).height()); - max_bridge_flow_width[&slice] = std::max(max_bridge_flow_width[&slice], - region->bridging_flow(frSolidInfill).width()); - } - bridging_surface_candidates[&slice].insert(bridging_surface_candidates[&slice].end(), region_internal_solids.begin(), - region_internal_solids.end()); - auto region_sparse_infill = region->fill_surfaces().filter_by_type(stInternal); - expansion_space[&slice].insert(expansion_space[&slice].end(), region_sparse_infill.begin(), region_sparse_infill.end()); - } - } - - // if there are none briding candidates, exit now, before making infill for the previous layer - if (std::all_of(bridging_surface_candidates.begin(), bridging_surface_candidates.end(), - [](const std::pair &candidates) { return candidates.second.empty(); })) { - continue; - } - - // Now, temporarily fill the previous layer and extract the extrusions. - // TODO - the make_fills function does a lot of work, some of it is not needed (e.g. sorting the paths) - // It would be nice to have a function that only creates the fill polylines, ideally without modifying the global state - po->get_layer(lidx)->lower_layer->make_fills(nullptr, nullptr, nullptr); - Polylines lower_layer_polylines; - for (const LayerRegion *region : layer->lower_layer->m_regions) { - for (const ExtrusionEntity *ee : region->fills().entities) { - assert(ee->is_collection()); - auto region_polylines = dynamic_cast(ee)->as_polylines(); - lower_layer_polylines.insert(lower_layer_polylines.end(), region_polylines.begin(), region_polylines.end()); - } - } - - for (const std::pair candidates : bridging_surface_candidates) { - if (candidates.second.empty()) { - continue; - }; - - // Gather lower layers sparse infill areas, to depth defined by used bridge flow - Polygons lower_layers_sparse_infill; - double bottom_z = layer->print_z - max_bridge_flow_height[candidates.first] - EPSILON; - LayerSlice::Links current_links = candidates.first->overlaps_below; - LayerSlice::Links next_links{}; - for (auto i = int(lidx) - 1; i >= 0; --i) { - // Stop iterating if layer is lower than bottom_z. - if (po->get_layer(i)->print_z < bottom_z) - break; - for (const auto &link : current_links) { - const LayerSlice &slice_below = po->get_layer(i)->lslices_ex[link.slice_idx]; - next_links.insert(next_links.end(), slice_below.overlaps_below.begin(), slice_below.overlaps_below.end()); - std::unordered_set regions_under_to_check; - for (const LayerIsland &island : slice_below.islands) { - regions_under_to_check.insert(island.perimeters.region()); - if (!island.fill_expolygons_composite()) { - regions_under_to_check.insert(island.fill_region_id); - } - } - - for (size_t region_idx : regions_under_to_check) { - const LayerRegion *region = layer->get_region(region_idx); - for (const Surface *surface : region->fill_surfaces().filter_by_type(stInternal)) { - Polygons p = to_polygons(surface->expolygon); - lower_layers_sparse_infill.insert(lower_layers_sparse_infill.end(), p.begin(), p.end()); - } + // gather also sparse infill surfaces on this layer, to which we can expand the bridges for anchoring + // gather potential internal bridging surfaces for the current layer + // pair of LayerSlice idx and surfaces. The LayerSlice idx simplifies the processing, since we cannot expand beyond it + std::unordered_map bridging_surface_candidates; + std::unordered_map expansion_space; + std::unordered_map max_bridge_flow_height; + std::unordered_map surface_to_region; + for (const LayerSlice &slice : layer->lslices_ex) { + std::unordered_set regions_to_check; + for (const LayerIsland &island : slice.islands) { + regions_to_check.insert(island.perimeters.region()); + if (!island.fill_expolygons_composite()) { + regions_to_check.insert(island.fill_region_id); } } - current_links = next_links; + + for (size_t region_idx : regions_to_check) { + const LayerRegion *region = layer->get_region(region_idx); + auto region_internal_solids = region->fill_surfaces().filter_by_type(stInternalSolid); + if (!region_internal_solids.empty()) { + max_bridge_flow_height[&slice] = std::max(max_bridge_flow_height[&slice], + region->bridging_flow(frSolidInfill).height()); + } + for (const Surface *s : region_internal_solids) { + surface_to_region[s] = region; + } + bridging_surface_candidates[&slice].insert(bridging_surface_candidates[&slice].end(), + region_internal_solids.begin(), region_internal_solids.end()); + auto region_sparse_infill = region->fill_surfaces().filter_by_type(stInternal); + expansion_space[&slice].insert(expansion_space[&slice].end(), region_sparse_infill.begin(), + region_sparse_infill.end()); + } } - if (lower_layers_sparse_infill.empty()) { + + // if there are none briding candidates, exit now, before making infill for the previous layer + if (std::all_of(bridging_surface_candidates.begin(), bridging_surface_candidates.end(), + [](const std::pair &candidates) { return candidates.second.empty(); })) { continue; } - lower_layers_sparse_infill = union_(lower_layers_sparse_infill); - Polygons expand_area; - for (const Surface *sparse_infill : expansion_space[candidates.first]) { - assert(sparse_infill->surface_type == stInternal); - Polygons a = to_polygons(sparse_infill->expolygon); - expand_area.insert(expand_area.end(), a.begin(), a.end()); + // Now, temporarily fill the previous layer and extract the extrusions. + // TODO - the make_fills function does a lot of work, some of it is not needed (e.g. sorting the paths) + // It would be nice to have a function that only creates the fill polylines, ideally without modifying the global state + po->get_layer(lidx)->lower_layer->make_fills(nullptr, nullptr, nullptr); + Polylines lower_layer_polylines; + for (const LayerRegion *region : layer->lower_layer->m_regions) { + for (const ExtrusionEntity *ee : region->fills().entities) { + assert(ee->is_collection()); + auto region_polylines = dynamic_cast(ee)->as_polylines(); + lower_layer_polylines.insert(lower_layer_polylines.end(), region_polylines.begin(), region_polylines.end()); + } } - // Lower layers sparse infill sections gathered - // now we can intersected them with bridging surface candidates to get actual areas that need and can accumulate - // bridging. These areas we then expand (within the surrounding sparse infill only!) - // to touch the infill polylines on previous layer. - for (const Surface *candidate : candidates.second) { - assert(candidate->surface_type == stInternalSolid); - Polygons bridged_area = to_polygons(candidate->expolygon); - bridged_area = - intersection(bridged_area, - lower_layers_sparse_infill); // cut off parts which are not over sparse infill - material overflow - - if (bridged_area.empty()) { + for (const std::pair candidates : bridging_surface_candidates) { + if (candidates.second.empty()) { continue; - } + }; - Polygons max_area = expand_area; - max_area.insert(max_area.end(), bridged_area.begin(), bridged_area.end()); - closing(max_area, max_bridge_flow_width[candidates.first]); + // Gather lower layers sparse infill areas, to depth defined by used bridge flow + Polygons lower_layers_sparse_infill; + double bottom_z = layer->print_z - max_bridge_flow_height[candidates.first] - EPSILON; + LayerSlice::Links current_links = candidates.first->overlaps_below; + LayerSlice::Links next_links{}; + for (auto i = int(lidx) - 1; i >= 0; --i) { + // Stop iterating if layer is lower than bottom_z. + if (po->get_layer(i)->print_z < bottom_z) + break; + for (const auto &link : current_links) { + const LayerSlice &slice_below = po->get_layer(i)->lslices_ex[link.slice_idx]; + next_links.insert(next_links.end(), slice_below.overlaps_below.begin(), slice_below.overlaps_below.end()); + std::unordered_set regions_under_to_check; + for (const LayerIsland &island : slice_below.islands) { + regions_under_to_check.insert(island.perimeters.region()); + if (!island.fill_expolygons_composite()) { + regions_under_to_check.insert(island.fill_region_id); + } + } - Polylines anchors = intersection_pl(lower_layer_polylines, max_area); - anchors = diff_pl(anchors, shrink(bridged_area, scale_(max_bridge_flow_width[candidates.first]))); - - AABBTreeLines::LinesDistancer anchors_and_walls; - { - Lines tmp = to_lines(anchors); - Lines tmp2 = to_lines(max_area); - tmp.insert(tmp.end(), tmp.begin(), tmp.end()); - anchors_and_walls = AABBTreeLines::LinesDistancer{tmp}; - } - - double bridging_dir = 0; - { - std::vector> directions_with_distances; - for (const Polygon &p : bridged_area) { - for (int point_idx = 0; point_idx < int(p.points.size()) - 1; ++point_idx) { - Vec2d start = p.points[point_idx].cast(); - Vec2d next = p.points[point_idx + 1].cast(); - Vec2d v = next - start; // vector from next to current - double dist_to_next = v.norm(); - v.normalize(); - int lines_count = int(std::ceil(dist_to_next / scaled(3.0))); - float step_size = dist_to_next / lines_count; - for (int i = 0; i < lines_count; ++i) { - Point a(start + v * (i * step_size)); - auto [distance, index, p] = anchors_and_walls.distance_from_lines_extra(a); - const Line& l = anchors_and_walls.get_line(index); - directions_with_distances.emplace_back(PI - l.direction(), unscaled(distance)); + for (size_t region_idx : regions_under_to_check) { + const LayerRegion *region = layer->get_region(region_idx); + for (const Surface *surface : region->fill_surfaces().filter_by_type(stInternal)) { + Polygons p = to_polygons(surface->expolygon); + lower_layers_sparse_infill.insert(lower_layers_sparse_infill.end(), p.begin(), p.end()); } } } - double max_dist = directions_with_distances[0].second; - for (const auto& dir :directions_with_distances) { - max_dist = std::max(max_dist, dir.second); - } - double acc = 0; - for (const auto& dir : directions_with_distances) { - bridging_dir += dir.first * (max_dist - dir.second); - acc += (max_dist - dir.second); - } - bridging_dir /= acc; + current_links = next_links; + } + if (lower_layers_sparse_infill.empty()) { + continue; + } + lower_layers_sparse_infill = union_(lower_layers_sparse_infill); + + Polygons expand_area; + for (const Surface *sparse_infill : expansion_space[candidates.first]) { + assert(sparse_infill->surface_type == stInternal); + Polygons a = to_polygons(sparse_infill->expolygon); + expand_area.insert(expand_area.end(), a.begin(), a.end()); } - //TODO use get_extens_rotated on the bridged_area polygons, generate vertical lines of the box, - // OR maybe get extens of rotated max_area, then fill with vertical lines, make AABB tree rotated for anchors and walls and also + // Lower layers sparse infill sections gathered + // now we can intersected them with bridging surface candidates to get actual areas that need and can accumulate + // bridging. These areas we then expand (within the surrounding sparse infill only!) + // to touch the infill polylines on previous layer. + for (const Surface *candidate : candidates.second) { + const Flow &flow = surface_to_region[candidate]->bridging_flow(frSolidInfill); + assert(candidate->surface_type == stInternalSolid); + Polygons bridged_area = to_polygons(candidate->expolygon); + bridged_area = + intersection(bridged_area, + lower_layers_sparse_infill); // cut off parts which are not over sparse infill - material overflow + + if (bridged_area.empty()) { + continue; + } + + Polygons max_area = expand_area; + max_area.insert(max_area.end(), bridged_area.begin(), bridged_area.end()); + closing(max_area, flow.scaled_width()); + + Polylines anchors = intersection_pl(lower_layer_polylines, max_area); + anchors = diff_pl(anchors, shrink(bridged_area, flow.scaled_width())); + + Lines anchors_and_walls = to_lines(anchors); + Lines tmp = to_lines(max_area); + tmp.insert(anchors_and_walls.end(), tmp.begin(), tmp.end()); + + double bridging_angle = 0; + { + AABBTreeLines::LinesDistancer lines_tree{anchors_and_walls}; + + std::vector> directions_with_distances; + for (const Polygon &p : bridged_area) { + for (int point_idx = 0; point_idx < int(p.points.size()) - 1; ++point_idx) { + Vec2d start = p.points[point_idx].cast(); + Vec2d next = p.points[point_idx + 1].cast(); + Vec2d v = next - start; // vector from next to current + double dist_to_next = v.norm(); + v.normalize(); + int lines_count = int(std::ceil(dist_to_next / scaled(3.0))); + float step_size = dist_to_next / lines_count; + for (int i = 0; i < lines_count; ++i) { + Point a(start + v * (i * step_size)); + auto [distance, index, p] = lines_tree.distance_from_lines_extra(a); + const Line &l = lines_tree.get_line(index); + directions_with_distances.emplace_back(PI - l.direction(), unscaled(distance)); + } + } + } + double max_dist = directions_with_distances[0].second; + for (const auto &dir : directions_with_distances) { + max_dist = std::max(max_dist, dir.second); + } + double acc = 0; + for (const auto &dir : directions_with_distances) { + bridging_angle += dir.first * (max_dist - dir.second); + acc += (max_dist - dir.second); + } + bridging_angle /= acc; + } + + // TODO maybe get extens of rotated max_area, then fill with vertical lines, make AABB tree rotated for anchors and + // walls and also // for bridged area - // then cut off the vertical lines, compose the final polygon, and rotate back + // then cut off the vertical lines, compose the final polygon, and rotate back + auto lines_rotate = [](Lines &lines, double cos_angle, double sin_angle) { + for (Line &l : lines) { + double ax = double(l.a.x()); + double ay = double(l.a.y()); + l.a.x() = coord_t(round(cos_angle * ax - sin_angle * ay)); + l.a.y() = coord_t(round(cos_angle * ay + sin_angle * ax)); + double bx = double(l.b.x()); + double by = double(l.b.y()); + l.b.x() = coord_t(round(cos_angle * bx - sin_angle * by)); + l.b.y() = coord_t(round(cos_angle * by + sin_angle * bx)); + } + }; + + Polygons expanded_bridged_area{}; + { + polygons_rotate(bridged_area, bridging_angle); + lines_rotate(anchors_and_walls, cos(bridging_angle), sin(bridging_angle)); + BoundingBox bb_x = get_extents(bridged_area); + BoundingBox bb_y = get_extents(anchors_and_walls); + + const size_t n_vlines = (bb_x.max.x() - bb_x.min.x() + flow.scaled_spacing() - 1) / flow.scaled_spacing(); + std::vector vertical_lines(n_vlines); + for (size_t i = 0; i < n_vlines; i++) { + coord_t x = bb_x.min.x() + i * flow.scaled_spacing(); + coord_t y_min = bb_y.min.y() - flow.scaled_spacing(); + coord_t y_max = bb_y.max.y() + flow.scaled_spacing(); + vertical_lines[i].a = Point{x, y_min}; + vertical_lines[i].b = Point{x, y_max}; + } + + auto anchors_and_walls_tree = AABBTreeLines::LinesDistancer{std::move(anchors_and_walls)}; + auto bridged_area_tree = AABBTreeLines::LinesDistancer{to_lines(bridged_area)}; + + std::vector>> polygon_sections(n_vlines); + for (size_t i = 0; i < n_vlines; i++) { + auto area_intersections = bridged_area_tree.intersections_with_line(vertical_lines[i]); + if (area_intersections.size() < 2) { + if (area_intersections.size() > 0) { + polygon_sections[i].emplace_back(area_intersections[0].first, area_intersections[0].first); + } + continue; + } + auto anchors_intersections = anchors_and_walls_tree.intersections_with_line(vertical_lines[i]); + for (const auto &intersection : area_intersections) { + auto high_b = std::upper_bound(anchors_intersections.begin(), anchors_intersections.end(), intersection, + [](const std::pair left, + const std::pair right) { + return left.first.y() > right.first.y(); + }); + Point low, high; + if (high_b == anchors_intersections.end()) { + assert(false); // should not happen + continue; + } else if (high_b == anchors_intersections.begin()) { + low = high_b->first; + high = (++high_b)->first; + } else { + low = (--high_b)->first; + high = high_b->first; + } + + if (polygon_sections[i].size() > 0 && polygon_sections[i].back().second.y() >= low.y()) { + polygon_sections[i].back().second = high; + } else { + polygon_sections[i].emplace_back(low, high); + } + } + } + + //reconstruct polygon from polygon sections + struct TracedPoly { + std::vector lows; + std::vector highs; + }; + + std::vector traced_polys; + for (const auto& layer : polygon_sections) { + for () + } + + + } + } } // surface iteration end } // island iteration end } // layer iteration end - }); + ); BOOST_LOG_TRIVIAL(info) << "Bridge over infill - End" << log_memory_info(); } // void PrintObject::bridge_over_infill() -void a(){ +void a() +{ std::vector sparse_infill_regions; for (size_t region_id = 0; region_id < this->num_printing_regions(); ++ region_id) if (const PrintRegion ®ion = this->printing_region(region_id); region.config().fill_density.value < 100) From 3d158e545e5c8617316d4e287a0af5fd46e458c5 Mon Sep 17 00:00:00 2001 From: PavelMikus Date: Mon, 13 Feb 2023 15:53:08 +0100 Subject: [PATCH 025/110] Debug version, threading disabled for the first part currently and crashing. But core should be finished --- src/libslic3r/PrintObject.cpp | 820 +++++++++++++++++++--------------- 1 file changed, 472 insertions(+), 348 deletions(-) diff --git a/src/libslic3r/PrintObject.cpp b/src/libslic3r/PrintObject.cpp index 13fc926597..b34d4ee89a 100644 --- a/src/libslic3r/PrintObject.cpp +++ b/src/libslic3r/PrintObject.cpp @@ -36,6 +36,7 @@ #include #include #include +#include #include #include #include @@ -1534,398 +1535,521 @@ void PrintObject::bridge_over_infill() { BOOST_LOG_TRIVIAL(info) << "Bridge over infill - Start" << log_memory_info(); - tbb::parallel_for( - tbb::blocked_range(0, this->layers().size()), - [po = this](tbb::blocked_range r) { - for (size_t lidx = r.begin(); lidx < r.end(); lidx++) { - const Layer *layer = po->get_layer(lidx); + struct ModifiedSurface + { + ModifiedSurface(const Surface *original_surface, Polygons new_polys, const LayerRegion *region, double bridge_angle) + : original_surface(original_surface), new_polys(new_polys), region(region), bridge_angle(bridge_angle) + {} + const Surface *original_surface; + Polygons new_polys; + const LayerRegion *region; + double bridge_angle; + }; - // gather also sparse infill surfaces on this layer, to which we can expand the bridges for anchoring - // gather potential internal bridging surfaces for the current layer - // pair of LayerSlice idx and surfaces. The LayerSlice idx simplifies the processing, since we cannot expand beyond it - std::unordered_map bridging_surface_candidates; - std::unordered_map expansion_space; - std::unordered_map max_bridge_flow_height; - std::unordered_map surface_to_region; - for (const LayerSlice &slice : layer->lslices_ex) { - std::unordered_set regions_to_check; - for (const LayerIsland &island : slice.islands) { - regions_to_check.insert(island.perimeters.region()); - if (!island.fill_expolygons_composite()) { - regions_to_check.insert(island.fill_region_id); - } - } + std::unordered_map> expanded_briding_surfaces; - for (size_t region_idx : regions_to_check) { - const LayerRegion *region = layer->get_region(region_idx); - auto region_internal_solids = region->fill_surfaces().filter_by_type(stInternalSolid); - if (!region_internal_solids.empty()) { - max_bridge_flow_height[&slice] = std::max(max_bridge_flow_height[&slice], - region->bridging_flow(frSolidInfill).height()); - } - for (const Surface *s : region_internal_solids) { - surface_to_region[s] = region; - } - bridging_surface_candidates[&slice].insert(bridging_surface_candidates[&slice].end(), - region_internal_solids.begin(), region_internal_solids.end()); - auto region_sparse_infill = region->fill_surfaces().filter_by_type(stInternal); - expansion_space[&slice].insert(expansion_space[&slice].end(), region_sparse_infill.begin(), - region_sparse_infill.end()); + // tbb::parallel_for(tbb::blocked_range(0, this->layers().size()), [po = this, + // &expanded_briding_surfaces](tbb::blocked_range r) { + auto r = tbb::blocked_range{0, this->layer_count()}; + auto po = this; + + for (size_t lidx = r.begin(); lidx < r.end(); lidx++) { + const Layer *layer = po->get_layer(lidx); + + // gather also sparse infill surfaces on this layer, to which we can expand the bridges for anchoring + // gather potential internal bridging surfaces for the current layer + // pair of LayerSlice idx and surfaces. The LayerSlice idx simplifies the processing, since we cannot expand beyond it + std::unordered_map bridging_surface_candidates; + std::unordered_map expansion_space; + std::unordered_map max_bridge_flow_height; + std::unordered_map surface_to_region; + for (const LayerSlice &slice : layer->lslices_ex) { + std::unordered_set regions_to_check; + for (const LayerIsland &island : slice.islands) { + regions_to_check.insert(island.perimeters.region()); + if (!island.fill_expolygons_composite()) { + regions_to_check.insert(island.fill_region_id); } } - // if there are none briding candidates, exit now, before making infill for the previous layer - if (std::all_of(bridging_surface_candidates.begin(), bridging_surface_candidates.end(), - [](const std::pair &candidates) { return candidates.second.empty(); })) { + for (size_t region_idx : regions_to_check) { + const LayerRegion *region = layer->get_region(region_idx); + auto region_internal_solids = region->fill_surfaces().filter_by_type(stInternalSolid); + if (!region_internal_solids.empty()) { + max_bridge_flow_height[&slice] = std::max(max_bridge_flow_height[&slice], + region->bridging_flow(frSolidInfill).height()); + } + for (const Surface *s : region_internal_solids) { + surface_to_region[s] = region; + } + bridging_surface_candidates[&slice].insert(bridging_surface_candidates[&slice].end(), region_internal_solids.begin(), + region_internal_solids.end()); + auto region_sparse_infill = region->fill_surfaces().filter_by_type(stInternal); + expansion_space[&slice].insert(expansion_space[&slice].end(), region_sparse_infill.begin(), region_sparse_infill.end()); + } + } + + // if there are none briding candidates, exit now, before making infill for the previous layer + if (std::all_of(bridging_surface_candidates.begin(), bridging_surface_candidates.end(), + [](const std::pair &candidates) { return candidates.second.empty(); })) { + continue; + } + + // Now, temporarily fill the previous layer and extract the extrusions. + // TODO - the make_fills function does a lot of work, some of it is not needed (e.g. sorting the paths) + // It would be nice to have a function that only creates the fill polylines, ideally without modifying the global state + po->get_layer(lidx)->lower_layer->make_fills(nullptr, nullptr, nullptr); + Polylines lower_layer_polylines; + for (const LayerRegion *region : layer->lower_layer->m_regions) { + for (const ExtrusionEntity *ee : region->fills().entities) { + assert(ee->is_collection()); + auto region_polylines = dynamic_cast(ee)->as_polylines(); + lower_layer_polylines.insert(lower_layer_polylines.end(), region_polylines.begin(), region_polylines.end()); + } + } + + for (const std::pair candidates : bridging_surface_candidates) { + if (candidates.second.empty()) { + continue; + }; + + // Gather lower layers sparse infill areas, to depth defined by used bridge flow + Polygons lower_layers_sparse_infill{}; + double bottom_z = layer->print_z - max_bridge_flow_height[candidates.first] - EPSILON; + LayerSlice::Links current_links = candidates.first->overlaps_below; + LayerSlice::Links next_links{}; + for (int i = int(lidx) - 1; i >= 0; --i) { + // Stop iterating if layer is lower than bottom_z. + if (po->get_layer(i)->print_z < bottom_z) + break; + for (const auto &link : current_links) { + const LayerSlice &slice_below = po->get_layer(i)->lslices_ex[link.slice_idx]; + next_links.insert(next_links.end(), slice_below.overlaps_below.begin(), slice_below.overlaps_below.end()); + std::unordered_set regions_under_to_check; + for (const LayerIsland &island : slice_below.islands) { + regions_under_to_check.insert(island.perimeters.region()); + if (!island.fill_expolygons_composite()) { + regions_under_to_check.insert(island.fill_region_id); + } + } + + for (size_t region_idx : regions_under_to_check) { + const LayerRegion *region = po->get_layer(i)->get_region(region_idx); + for (const Surface *surface : region->fill_surfaces().filter_by_type(stInternal)) { + Polygons p = to_polygons(surface->expolygon); + lower_layers_sparse_infill.insert(lower_layers_sparse_infill.end(), p.begin(), p.end()); + } + } + } + current_links = next_links; + } + if (lower_layers_sparse_infill.empty()) { continue; } + lower_layers_sparse_infill = union_(lower_layers_sparse_infill); - // Now, temporarily fill the previous layer and extract the extrusions. - // TODO - the make_fills function does a lot of work, some of it is not needed (e.g. sorting the paths) - // It would be nice to have a function that only creates the fill polylines, ideally without modifying the global state - po->get_layer(lidx)->lower_layer->make_fills(nullptr, nullptr, nullptr); - Polylines lower_layer_polylines; - for (const LayerRegion *region : layer->lower_layer->m_regions) { - for (const ExtrusionEntity *ee : region->fills().entities) { - assert(ee->is_collection()); - auto region_polylines = dynamic_cast(ee)->as_polylines(); - lower_layer_polylines.insert(lower_layer_polylines.end(), region_polylines.begin(), region_polylines.end()); - } + Polygons expand_area; + for (const Surface *sparse_infill : expansion_space[candidates.first]) { + assert(sparse_infill->surface_type == stInternal); + Polygons a = to_polygons(sparse_infill->expolygon); + expand_area.insert(expand_area.end(), a.begin(), a.end()); } - for (const std::pair candidates : bridging_surface_candidates) { - if (candidates.second.empty()) { + // Lower layers sparse infill sections gathered + // now we can intersected them with bridging surface candidates to get actual areas that need and can accumulate + // bridging. These areas we then expand (within the surrounding sparse infill only!) + // to touch the infill polylines on previous layer. + for (const Surface *candidate : candidates.second) { + const Flow &flow = surface_to_region[candidate]->bridging_flow(frSolidInfill); + assert(candidate->surface_type == stInternalSolid); + Polygons bridged_area = to_polygons(candidate->expolygon); + bridged_area = + intersection(bridged_area, + lower_layers_sparse_infill); // cut off parts which are not over sparse infill - material overflow + + if (bridged_area.empty()) { continue; + } + + Polygons max_area = expand_area; + max_area.insert(max_area.end(), bridged_area.begin(), bridged_area.end()); + closing(max_area, flow.scaled_width()); + + Polylines anchors = intersection_pl(lower_layer_polylines, max_area); + anchors = diff_pl(anchors, shrink(bridged_area, flow.scaled_width())); + + Lines anchors_and_walls = to_lines(anchors); + Lines tmp = to_lines(max_area); + tmp.insert(anchors_and_walls.end(), tmp.begin(), tmp.end()); + + double bridging_angle = 0; + { + AABBTreeLines::LinesDistancer lines_tree{anchors_and_walls}; + + std::vector> directions_with_distances; + for (const Polygon &p : bridged_area) { + for (int point_idx = 0; point_idx < int(p.points.size()) - 1; ++point_idx) { + Vec2d start = p.points[point_idx].cast(); + Vec2d next = p.points[point_idx + 1].cast(); + Vec2d v = next - start; // vector from next to current + double dist_to_next = v.norm(); + v.normalize(); + int lines_count = int(std::ceil(dist_to_next / scaled(3.0))); + float step_size = dist_to_next / lines_count; + for (int i = 0; i < lines_count; ++i) { + Point a = (start + v * (i * step_size)).cast(); + auto [distance, index, p] = lines_tree.distance_from_lines_extra(a); + const Line &l = lines_tree.get_line(index); + directions_with_distances.emplace_back(PI - l.direction(), unscaled(distance)); + } + } + } + double max_dist = directions_with_distances[0].second; + for (const auto &dir : directions_with_distances) { + max_dist = std::max(max_dist, dir.second); + } + double acc = 0; + for (const auto &dir : directions_with_distances) { + bridging_angle += dir.first * (max_dist - dir.second); + acc += (max_dist - dir.second); + } + bridging_angle /= acc; + } + + // TODO maybe get extens of rotated max_area, then fill with vertical lines, make AABB tree rotated for anchors and + // walls and also + // for bridged area + // then cut off the vertical lines, compose the final polygon, and rotate back + auto lines_rotate = [](Lines &lines, double cos_angle, double sin_angle) { + for (Line &l : lines) { + double ax = double(l.a.x()); + double ay = double(l.a.y()); + l.a.x() = coord_t(round(cos_angle * ax - sin_angle * ay)); + l.a.y() = coord_t(round(cos_angle * ay + sin_angle * ax)); + double bx = double(l.b.x()); + double by = double(l.b.y()); + l.b.x() = coord_t(round(cos_angle * bx - sin_angle * by)); + l.b.y() = coord_t(round(cos_angle * by + sin_angle * bx)); + } }; - // Gather lower layers sparse infill areas, to depth defined by used bridge flow - Polygons lower_layers_sparse_infill; - double bottom_z = layer->print_z - max_bridge_flow_height[candidates.first] - EPSILON; - LayerSlice::Links current_links = candidates.first->overlaps_below; - LayerSlice::Links next_links{}; - for (auto i = int(lidx) - 1; i >= 0; --i) { - // Stop iterating if layer is lower than bottom_z. - if (po->get_layer(i)->print_z < bottom_z) - break; - for (const auto &link : current_links) { - const LayerSlice &slice_below = po->get_layer(i)->lslices_ex[link.slice_idx]; - next_links.insert(next_links.end(), slice_below.overlaps_below.begin(), slice_below.overlaps_below.end()); - std::unordered_set regions_under_to_check; - for (const LayerIsland &island : slice_below.islands) { - regions_under_to_check.insert(island.perimeters.region()); - if (!island.fill_expolygons_composite()) { - regions_under_to_check.insert(island.fill_region_id); - } - } + Polygons expanded_bridged_area{}; + { + polygons_rotate(bridged_area, bridging_angle); + lines_rotate(anchors_and_walls, cos(bridging_angle), sin(bridging_angle)); + BoundingBox bb_x = get_extents(bridged_area); + BoundingBox bb_y = get_extents(anchors_and_walls); - for (size_t region_idx : regions_under_to_check) { - const LayerRegion *region = layer->get_region(region_idx); - for (const Surface *surface : region->fill_surfaces().filter_by_type(stInternal)) { - Polygons p = to_polygons(surface->expolygon); - lower_layers_sparse_infill.insert(lower_layers_sparse_infill.end(), p.begin(), p.end()); + const size_t n_vlines = (bb_x.max.x() - bb_x.min.x() + flow.scaled_spacing() - 1) / flow.scaled_spacing(); + std::vector vertical_lines(n_vlines); + for (size_t i = 0; i < n_vlines; i++) { + coord_t x = bb_x.min.x() + i * flow.scaled_spacing(); + coord_t y_min = bb_y.min.y() - flow.scaled_spacing(); + coord_t y_max = bb_y.max.y() + flow.scaled_spacing(); + vertical_lines[i].a = Point{x, y_min}; + vertical_lines[i].b = Point{x, y_max}; + } + + auto anchors_and_walls_tree = AABBTreeLines::LinesDistancer{std::move(anchors_and_walls)}; + auto bridged_area_tree = AABBTreeLines::LinesDistancer{to_lines(bridged_area)}; + + std::vector>> polygon_sections(n_vlines); + for (size_t i = 0; i < n_vlines; i++) { + auto area_intersections = bridged_area_tree.intersections_with_line(vertical_lines[i]); + if (area_intersections.size() < 2) { + if (area_intersections.size() > 0) { + polygon_sections[i].emplace_back(area_intersections[0].first, area_intersections[0].first); + } + continue; + } + auto anchors_intersections = anchors_and_walls_tree.intersections_with_line(vertical_lines[i]); + for (const auto &intersection : area_intersections) { + auto high_b = std::upper_bound(anchors_intersections.begin(), anchors_intersections.end(), intersection, + [](const std::pair left, + const std::pair right) { + return left.first.y() > right.first.y(); + }); + Point low, high; + if (high_b == anchors_intersections.end()) { + assert(false); // should not happen + continue; + } else if (high_b == anchors_intersections.begin()) { + low = high_b->first; + high = (++high_b)->first; + } else { + low = (--high_b)->first; + high = high_b->first; + } + + if (polygon_sections[i].size() > 0 && polygon_sections[i].back().second.y() >= low.y()) { + polygon_sections[i].back().second = high; + } else { + polygon_sections[i].emplace_back(low, high); } } } - current_links = next_links; - } - if (lower_layers_sparse_infill.empty()) { - continue; - } - lower_layers_sparse_infill = union_(lower_layers_sparse_infill); - Polygons expand_area; - for (const Surface *sparse_infill : expansion_space[candidates.first]) { - assert(sparse_infill->surface_type == stInternal); - Polygons a = to_polygons(sparse_infill->expolygon); - expand_area.insert(expand_area.end(), a.begin(), a.end()); - } - - // Lower layers sparse infill sections gathered - // now we can intersected them with bridging surface candidates to get actual areas that need and can accumulate - // bridging. These areas we then expand (within the surrounding sparse infill only!) - // to touch the infill polylines on previous layer. - for (const Surface *candidate : candidates.second) { - const Flow &flow = surface_to_region[candidate]->bridging_flow(frSolidInfill); - assert(candidate->surface_type == stInternalSolid); - Polygons bridged_area = to_polygons(candidate->expolygon); - bridged_area = - intersection(bridged_area, - lower_layers_sparse_infill); // cut off parts which are not over sparse infill - material overflow - - if (bridged_area.empty()) { - continue; - } - - Polygons max_area = expand_area; - max_area.insert(max_area.end(), bridged_area.begin(), bridged_area.end()); - closing(max_area, flow.scaled_width()); - - Polylines anchors = intersection_pl(lower_layer_polylines, max_area); - anchors = diff_pl(anchors, shrink(bridged_area, flow.scaled_width())); - - Lines anchors_and_walls = to_lines(anchors); - Lines tmp = to_lines(max_area); - tmp.insert(anchors_and_walls.end(), tmp.begin(), tmp.end()); - - double bridging_angle = 0; + // reconstruct polygon from polygon sections + struct TracedPoly { - AABBTreeLines::LinesDistancer lines_tree{anchors_and_walls}; - - std::vector> directions_with_distances; - for (const Polygon &p : bridged_area) { - for (int point_idx = 0; point_idx < int(p.points.size()) - 1; ++point_idx) { - Vec2d start = p.points[point_idx].cast(); - Vec2d next = p.points[point_idx + 1].cast(); - Vec2d v = next - start; // vector from next to current - double dist_to_next = v.norm(); - v.normalize(); - int lines_count = int(std::ceil(dist_to_next / scaled(3.0))); - float step_size = dist_to_next / lines_count; - for (int i = 0; i < lines_count; ++i) { - Point a(start + v * (i * step_size)); - auto [distance, index, p] = lines_tree.distance_from_lines_extra(a); - const Line &l = lines_tree.get_line(index); - directions_with_distances.emplace_back(PI - l.direction(), unscaled(distance)); - } - } - } - double max_dist = directions_with_distances[0].second; - for (const auto &dir : directions_with_distances) { - max_dist = std::max(max_dist, dir.second); - } - double acc = 0; - for (const auto &dir : directions_with_distances) { - bridging_angle += dir.first * (max_dist - dir.second); - acc += (max_dist - dir.second); - } - bridging_angle /= acc; - } - - // TODO maybe get extens of rotated max_area, then fill with vertical lines, make AABB tree rotated for anchors and - // walls and also - // for bridged area - // then cut off the vertical lines, compose the final polygon, and rotate back - auto lines_rotate = [](Lines &lines, double cos_angle, double sin_angle) { - for (Line &l : lines) { - double ax = double(l.a.x()); - double ay = double(l.a.y()); - l.a.x() = coord_t(round(cos_angle * ax - sin_angle * ay)); - l.a.y() = coord_t(round(cos_angle * ay + sin_angle * ax)); - double bx = double(l.b.x()); - double by = double(l.b.y()); - l.b.x() = coord_t(round(cos_angle * bx - sin_angle * by)); - l.b.y() = coord_t(round(cos_angle * by + sin_angle * bx)); - } + std::vector lows; + std::vector highs; }; - Polygons expanded_bridged_area{}; - { - polygons_rotate(bridged_area, bridging_angle); - lines_rotate(anchors_and_walls, cos(bridging_angle), sin(bridging_angle)); - BoundingBox bb_x = get_extents(bridged_area); - BoundingBox bb_y = get_extents(anchors_and_walls); + auto segments_overlap = [](coord_t alow, coord_t ahigh, coord_t blow, coord_t bhigh) { + return (alow >= blow && alow <= bhigh) || (ahigh >= blow && ahigh <= bhigh) || + (blow >= alow && blow <= ahigh) || (bhigh >= alow && bhigh <= ahigh); + }; - const size_t n_vlines = (bb_x.max.x() - bb_x.min.x() + flow.scaled_spacing() - 1) / flow.scaled_spacing(); - std::vector vertical_lines(n_vlines); - for (size_t i = 0; i < n_vlines; i++) { - coord_t x = bb_x.min.x() + i * flow.scaled_spacing(); - coord_t y_min = bb_y.min.y() - flow.scaled_spacing(); - coord_t y_max = bb_y.max.y() + flow.scaled_spacing(); - vertical_lines[i].a = Point{x, y_min}; - vertical_lines[i].b = Point{x, y_max}; - } + std::vector current_traced_polys; + for (const auto &layer : polygon_sections) { + std::unordered_set *> used_segments; + for (TracedPoly &traced_poly : current_traced_polys) { + auto maybe_first_overlap = std::upper_bound(layer.begin(), layer.end(), traced_poly.lows.back(), + [](const Point &low, const std::pair &seg) { + return seg.second.y() > low.y(); + }); - auto anchors_and_walls_tree = AABBTreeLines::LinesDistancer{std::move(anchors_and_walls)}; - auto bridged_area_tree = AABBTreeLines::LinesDistancer{to_lines(bridged_area)}; - - std::vector>> polygon_sections(n_vlines); - for (size_t i = 0; i < n_vlines; i++) { - auto area_intersections = bridged_area_tree.intersections_with_line(vertical_lines[i]); - if (area_intersections.size() < 2) { - if (area_intersections.size() > 0) { - polygon_sections[i].emplace_back(area_intersections[0].first, area_intersections[0].first); - } - continue; - } - auto anchors_intersections = anchors_and_walls_tree.intersections_with_line(vertical_lines[i]); - for (const auto &intersection : area_intersections) { - auto high_b = std::upper_bound(anchors_intersections.begin(), anchors_intersections.end(), intersection, - [](const std::pair left, - const std::pair right) { - return left.first.y() > right.first.y(); - }); - Point low, high; - if (high_b == anchors_intersections.end()) { - assert(false); // should not happen - continue; - } else if (high_b == anchors_intersections.begin()) { - low = high_b->first; - high = (++high_b)->first; - } else { - low = (--high_b)->first; - high = high_b->first; - } - - if (polygon_sections[i].size() > 0 && polygon_sections[i].back().second.y() >= low.y()) { - polygon_sections[i].back().second = high; - } else { - polygon_sections[i].emplace_back(low, high); - } + if (maybe_first_overlap != layer.end() && // segment exists + segments_overlap(traced_poly.lows.back().y(), traced_poly.highs.back().y(), + maybe_first_overlap->first.y(), + maybe_first_overlap->second.y())) // segment is overlapping + { + // Overlapping segment. In that case, add it + // to the traced polygon and add segment to used segments + traced_poly.lows.push_back(maybe_first_overlap->first - Point{flow.scaled_spacing() / 2, 0}); + traced_poly.lows.push_back(maybe_first_overlap->first + Point{flow.scaled_spacing() / 2, 0}); + traced_poly.highs.push_back(maybe_first_overlap->second - Point{flow.scaled_spacing() / 2, 0}); + traced_poly.highs.push_back(maybe_first_overlap->second + Point{flow.scaled_spacing() / 2, 0}); + used_segments.insert(&(*maybe_first_overlap)); + } else { + // Zero or multiple overlapping segments. Resolving this is nontrivial, + // so we just close this polygon and maybe open several new. This will hopefully happen much less often + Polygon &new_poly = expanded_bridged_area.emplace_back(std::move(traced_poly.lows)); + new_poly.points.insert(new_poly.points.end(), traced_poly.highs.rbegin(), traced_poly.highs.rend()); + traced_poly.lows.clear(); + traced_poly.highs.clear(); } } - //reconstruct polygon from polygon sections - struct TracedPoly { - std::vector lows; - std::vector highs; - }; - - std::vector traced_polys; - for (const auto& layer : polygon_sections) { - for () + std::remove_if(current_traced_polys.begin(), current_traced_polys.end(), + [](const TracedPoly &tp) { return tp.lows.empty(); }); + + for (const auto &segment : layer) { + if (used_segments.find(&segment) != used_segments.end()) { + TracedPoly &new_tp = current_traced_polys.emplace_back(); + new_tp.lows.push_back(segment.first - Point{flow.scaled_spacing() / 2, 0}); + new_tp.lows.push_back(segment.first + Point{flow.scaled_spacing() / 2, 0}); + new_tp.highs.push_back(segment.second - Point{flow.scaled_spacing() / 2, 0}); + new_tp.highs.push_back(segment.second + Point{flow.scaled_spacing() / 2, 0}); + } } + } - + // add not closed polys + for (TracedPoly &traced_poly : current_traced_polys) { + Polygon &new_poly = expanded_bridged_area.emplace_back(std::move(traced_poly.lows)); + new_poly.points.insert(new_poly.points.end(), traced_poly.highs.rbegin(), traced_poly.highs.rend()); } } - } // surface iteration end - } // island iteration end - } // layer iteration end - ); + expand_area = diff(expand_area, expanded_bridged_area); + + expanded_briding_surfaces[candidates.first].emplace_back(candidate, expanded_bridged_area, surface_to_region[candidate], + bridging_angle); + } + } + } + // }); + + BOOST_LOG_TRIVIAL(info) << "Bridge over infill - Directions and expanded surfaces computed" << log_memory_info(); + + tbb::parallel_for(tbb::blocked_range(0, this->layers().size()), + [po = this, &expanded_briding_surfaces](tbb::blocked_range r) { + for (size_t lidx = r.begin(); lidx < r.end(); lidx++) { + Layer *layer = po->get_layer(lidx); + + for (const LayerSlice &slice : layer->lslices_ex) { + if (const auto &modified_surfaces = expanded_briding_surfaces.find(&slice); + modified_surfaces != expanded_briding_surfaces.end()) { + std::unordered_set regions_to_check; + for (const LayerIsland &island : slice.islands) { + regions_to_check.insert(island.perimeters.region()); + if (!island.fill_expolygons_composite()) { + regions_to_check.insert(island.fill_region_id); + } + } + + Polygons cut_from_infill{}; + for (const auto &surface : modified_surfaces->second) { + cut_from_infill.insert(cut_from_infill.end(), surface.new_polys.begin(), surface.new_polys.end()); + } + + for (size_t region_idx : regions_to_check) { + LayerRegion *region = layer->get_region(region_idx); + Surfaces new_surfaces; + + for (const ModifiedSurface &s : modified_surfaces->second) { + for (Surface &surface : region->m_fill_surfaces.surfaces) { + if (s.original_surface == &surface) { + Surface tmp(surface, {}); + tmp.surface_type = stInternalBridge; + tmp.bridge_angle = s.bridge_angle; + for (const ExPolygon &expoly : union_ex(s.new_polys)) { + new_surfaces.emplace_back(tmp, expoly); + } + surface.clear(); + } else if (surface.surface_type == stInternal) { + Surface tmp(surface, {}); + for (const ExPolygon &expoly : diff_ex(surface.expolygon, cut_from_infill)) { + new_surfaces.emplace_back(tmp, expoly); + } + surface.clear(); + } + } + } + region->m_fill_surfaces.surfaces.insert(region->m_fill_surfaces.surfaces.end(), + new_surfaces.begin(), new_surfaces.end()); + std::remove_if(region->m_fill_surfaces.begin(), region->m_fill_surfaces.end(), + [](const Surface &s) { return s.empty(); }); + } + } + } + } + }); BOOST_LOG_TRIVIAL(info) << "Bridge over infill - End" << log_memory_info(); } // void PrintObject::bridge_over_infill() -void a() -{ - std::vector sparse_infill_regions; - for (size_t region_id = 0; region_id < this->num_printing_regions(); ++ region_id) - if (const PrintRegion ®ion = this->printing_region(region_id); region.config().fill_density.value < 100) - sparse_infill_regions.emplace_back(region_id); - if (this->layer_count() < 2 || sparse_infill_regions.empty()) - return; +// void a() +// { +// std::vector sparse_infill_regions; +// for (size_t region_id = 0; region_id < this->num_printing_regions(); ++ region_id) +// if (const PrintRegion ®ion = this->printing_region(region_id); region.config().fill_density.value < 100) +// sparse_infill_regions.emplace_back(region_id); +// if (this->layer_count() < 2 || sparse_infill_regions.empty()) +// return; - // Collect sum of all internal (sparse infill) regions, because - // 1) layerm->fill_surfaces.will be modified in parallel. - // 2) the parallel loop works on a sum of surfaces over regions anyways, thus collecting the sparse infill surfaces - // up front is an optimization. - std::vector internals; - internals.reserve(this->layer_count()); - for (Layer *layer : m_layers) { - Polygons sum; - for (const LayerRegion *layerm : layer->m_regions) - layerm->fill_surfaces().filter_by_type(stInternal, &sum); - internals.emplace_back(std::move(sum)); - } +// // Collect sum of all internal (sparse infill) regions, because +// // 1) layerm->fill_surfaces.will be modified in parallel. +// // 2) the parallel loop works on a sum of surfaces over regions anyways, thus collecting the sparse infill surfaces +// // up front is an optimization. +// std::vector internals; +// internals.reserve(this->layer_count()); +// for (Layer *layer : m_layers) { +// Polygons sum; +// for (const LayerRegion *layerm : layer->m_regions) +// layerm->fill_surfaces().filter_by_type(stInternal, &sum); +// internals.emplace_back(std::move(sum)); +// } - // Process all regions and layers in parallel. - tbb::parallel_for(tbb::blocked_range(0, sparse_infill_regions.size() * (this->layer_count() - 1), sparse_infill_regions.size()), - [this, &sparse_infill_regions, &internals] - (const tbb::blocked_range &range) { - for (size_t task_id = range.begin(); task_id != range.end(); ++ task_id) { - const size_t layer_id = (task_id / sparse_infill_regions.size()) + 1; - const size_t region_id = sparse_infill_regions[task_id % sparse_infill_regions.size()]; - Layer *layer = this->get_layer(layer_id); - LayerRegion *layerm = layer->m_regions[region_id]; - Flow bridge_flow = layerm->bridging_flow(frSolidInfill); +// // Process all regions and layers in parallel. +// tbb::parallel_for(tbb::blocked_range(0, sparse_infill_regions.size() * (this->layer_count() - 1), sparse_infill_regions.size()), +// [this, &sparse_infill_regions, &internals] +// (const tbb::blocked_range &range) { +// for (size_t task_id = range.begin(); task_id != range.end(); ++ task_id) { +// const size_t layer_id = (task_id / sparse_infill_regions.size()) + 1; +// const size_t region_id = sparse_infill_regions[task_id % sparse_infill_regions.size()]; +// Layer *layer = this->get_layer(layer_id); +// LayerRegion *layerm = layer->m_regions[region_id]; +// Flow bridge_flow = layerm->bridging_flow(frSolidInfill); - // Extract the stInternalSolid surfaces that might be transformed into bridges. - ExPolygons internal_solid; - layerm->m_fill_surfaces.remove_type(stInternalSolid, &internal_solid); - if (internal_solid.empty()) - // No internal solid -> no new bridges for this layer region. - continue; +// // Extract the stInternalSolid surfaces that might be transformed into bridges. +// ExPolygons internal_solid; +// layerm->m_fill_surfaces.remove_type(stInternalSolid, &internal_solid); +// if (internal_solid.empty()) +// // No internal solid -> no new bridges for this layer region. +// continue; - // check whether the lower area is deep enough for absorbing the extra flow - // (for obvious physical reasons but also for preventing the bridge extrudates - // from overflowing in 3D preview) - ExPolygons to_bridge; - { - Polygons to_bridge_pp = to_polygons(internal_solid); - // Iterate through lower layers spanned by bridge_flow. - double bottom_z = layer->print_z - bridge_flow.height() - EPSILON; - for (auto i = int(layer_id) - 1; i >= 0; -- i) { - // Stop iterating if layer is lower than bottom_z. - if (m_layers[i]->print_z < bottom_z) - break; - // Intersect lower sparse infills with the candidate solid surfaces. - to_bridge_pp = intersection(to_bridge_pp, internals[i]); - } - // there's no point in bridging too thin/short regions - //FIXME Vojtech: The offset2 function is not a geometric offset, - // therefore it may create 1) gaps, and 2) sharp corners, which are outside the original contour. - // The gaps will be filled by a separate region, which makes the infill less stable and it takes longer. - { - float min_width = float(bridge_flow.scaled_width()) * 3.f; - to_bridge_pp = opening(to_bridge_pp, min_width); - } +// // check whether the lower area is deep enough for absorbing the extra flow +// // (for obvious physical reasons but also for preventing the bridge extrudates +// // from overflowing in 3D preview) +// ExPolygons to_bridge; +// { +// Polygons to_bridge_pp = to_polygons(internal_solid); +// // Iterate through lower layers spanned by bridge_flow. +// double bottom_z = layer->print_z - bridge_flow.height() - EPSILON; +// for (auto i = int(layer_id) - 1; i >= 0; -- i) { +// // Stop iterating if layer is lower than bottom_z. +// if (m_layers[i]->print_z < bottom_z) +// break; +// // Intersect lower sparse infills with the candidate solid surfaces. +// to_bridge_pp = intersection(to_bridge_pp, internals[i]); +// } +// // there's no point in bridging too thin/short regions +// //FIXME Vojtech: The offset2 function is not a geometric offset, +// // therefore it may create 1) gaps, and 2) sharp corners, which are outside the original contour. +// // The gaps will be filled by a separate region, which makes the infill less stable and it takes longer. +// { +// float min_width = float(bridge_flow.scaled_width()) * 3.f; +// to_bridge_pp = opening(to_bridge_pp, min_width); +// } - if (to_bridge_pp.empty()) { - // Restore internal_solid surfaces. - for (ExPolygon &ex : internal_solid) - layerm->m_fill_surfaces.surfaces.push_back(Surface(stInternalSolid, std::move(ex))); - continue; - } - // convert into ExPolygons - to_bridge = union_ex(to_bridge_pp); - } +// if (to_bridge_pp.empty()) { +// // Restore internal_solid surfaces. +// for (ExPolygon &ex : internal_solid) +// layerm->m_fill_surfaces.surfaces.push_back(Surface(stInternalSolid, std::move(ex))); +// continue; +// } +// // convert into ExPolygons +// to_bridge = union_ex(to_bridge_pp); +// } - #ifdef SLIC3R_DEBUG - printf("Bridging %zu internal areas at layer %zu\n", to_bridge.size(), layer->id()); - #endif +// #ifdef SLIC3R_DEBUG +// printf("Bridging %zu internal areas at layer %zu\n", to_bridge.size(), layer->id()); +// #endif - // compute the remaning internal solid surfaces as difference - ExPolygons not_to_bridge = diff_ex(internal_solid, to_bridge, ApplySafetyOffset::Yes); - to_bridge = intersection_ex(to_bridge, internal_solid, ApplySafetyOffset::Yes); - // build the new collection of fill_surfaces - for (ExPolygon &ex : to_bridge) - layerm->m_fill_surfaces.surfaces.push_back(Surface(stInternalBridge, std::move(ex))); - for (ExPolygon &ex : not_to_bridge) - layerm->m_fill_surfaces.surfaces.push_back(Surface(stInternalSolid, std::move(ex))); - /* - # exclude infill from the layers below if needed - # see discussion at https://github.com/alexrj/Slic3r/issues/240 - # Update: do not exclude any infill. Sparse infill is able to absorb the excess material. - if (0) { - my $excess = $layerm->extruders->{infill}->bridge_flow->width - $layerm->height; - for (my $i = $layer_id-1; $excess >= $self->get_layer($i)->height; $i--) { - Slic3r::debugf " skipping infill below those areas at layer %d\n", $i; - foreach my $lower_layerm (@{$self->get_layer($i)->regions}) { - my @new_surfaces = (); - # subtract the area from all types of surfaces - foreach my $group (@{$lower_layerm->fill_surfaces->group}) { - push @new_surfaces, map $group->[0]->clone(expolygon => $_), - @{diff_ex( - [ map $_->p, @$group ], - [ map @$_, @$to_bridge ], - )}; - push @new_surfaces, map Slic3r::Surface->new( - expolygon => $_, - surface_type => stInternalVoid, - ), @{intersection_ex( - [ map $_->p, @$group ], - [ map @$_, @$to_bridge ], - )}; - } - $lower_layerm->fill_surfaces->clear; - $lower_layerm->fill_surfaces->append($_) for @new_surfaces; - } - - $excess -= $self->get_layer($i)->height; - } - } - */ -#ifdef SLIC3R_DEBUG_SLICE_PROCESSING - layerm->export_region_slices_to_svg_debug("7_bridge_over_infill"); - layerm->export_region_fill_surfaces_to_svg_debug("7_bridge_over_infill"); -#endif /* SLIC3R_DEBUG_SLICE_PROCESSING */ - m_print->throw_if_canceled(); - } - }); +// // compute the remaning internal solid surfaces as difference +// ExPolygons not_to_bridge = diff_ex(internal_solid, to_bridge, ApplySafetyOffset::Yes); +// to_bridge = intersection_ex(to_bridge, internal_solid, ApplySafetyOffset::Yes); +// // build the new collection of fill_surfaces +// for (ExPolygon &ex : to_bridge) +// layerm->m_fill_surfaces.surfaces.push_back(Surface(stInternalBridge, std::move(ex))); +// for (ExPolygon &ex : not_to_bridge) +// layerm->m_fill_surfaces.surfaces.push_back(Surface(stInternalSolid, std::move(ex))); +// /* +// # exclude infill from the layers below if needed +// # see discussion at https://github.com/alexrj/Slic3r/issues/240 +// # Update: do not exclude any infill. Sparse infill is able to absorb the excess material. +// if (0) { +// my $excess = $layerm->extruders->{infill}->bridge_flow->width - $layerm->height; +// for (my $i = $layer_id-1; $excess >= $self->get_layer($i)->height; $i--) { +// Slic3r::debugf " skipping infill below those areas at layer %d\n", $i; +// foreach my $lower_layerm (@{$self->get_layer($i)->regions}) { +// my @new_surfaces = (); +// # subtract the area from all types of surfaces +// foreach my $group (@{$lower_layerm->fill_surfaces->group}) { +// push @new_surfaces, map $group->[0]->clone(expolygon => $_), +// @{diff_ex( +// [ map $_->p, @$group ], +// [ map @$_, @$to_bridge ], +// )}; +// push @new_surfaces, map Slic3r::Surface->new( +// expolygon => $_, +// surface_type => stInternalVoid, +// ), @{intersection_ex( +// [ map $_->p, @$group ], +// [ map @$_, @$to_bridge ], +// )}; +// } +// $lower_layerm->fill_surfaces->clear; +// $lower_layerm->fill_surfaces->append($_) for @new_surfaces; +// } -} // void PrintObject::bridge_over_infill() +// $excess -= $self->get_layer($i)->height; +// } +// } +// */ +// #ifdef SLIC3R_DEBUG_SLICE_PROCESSING +// layerm->export_region_slices_to_svg_debug("7_bridge_over_infill"); +// layerm->export_region_fill_surfaces_to_svg_debug("7_bridge_over_infill"); +// #endif /* SLIC3R_DEBUG_SLICE_PROCESSING */ +// m_print->throw_if_canceled(); +// } +// }); + +// } // void PrintObject::bridge_over_infill() static void clamp_exturder_to_default(ConfigOptionInt &opt, size_t num_extruders) { From dfbea5976e3c13870db7932e72bfcc054a071c39 Mon Sep 17 00:00:00 2001 From: PavelMikus Date: Mon, 13 Feb 2023 17:47:54 +0100 Subject: [PATCH 026/110] bunch of bug fixes, but still does not work --- src/libslic3r/PrintObject.cpp | 79 +++++++++++++++++++++-------------- 1 file changed, 47 insertions(+), 32 deletions(-) diff --git a/src/libslic3r/PrintObject.cpp b/src/libslic3r/PrintObject.cpp index b34d4ee89a..83bd7d3298 100644 --- a/src/libslic3r/PrintObject.cpp +++ b/src/libslic3r/PrintObject.cpp @@ -1680,7 +1680,7 @@ void PrintObject::bridge_over_infill() Lines anchors_and_walls = to_lines(anchors); Lines tmp = to_lines(max_area); - tmp.insert(anchors_and_walls.end(), tmp.begin(), tmp.end()); + anchors_and_walls.insert(anchors_and_walls.end(), tmp.begin(), tmp.end()); double bridging_angle = 0; { @@ -1700,7 +1700,7 @@ void PrintObject::bridge_over_infill() Point a = (start + v * (i * step_size)).cast(); auto [distance, index, p] = lines_tree.distance_from_lines_extra(a); const Line &l = lines_tree.get_line(index); - directions_with_distances.emplace_back(PI - l.direction(), unscaled(distance)); + directions_with_distances.emplace_back(PI - l.direction(), distance); } } } @@ -1713,7 +1713,11 @@ void PrintObject::bridge_over_infill() bridging_angle += dir.first * (max_dist - dir.second); acc += (max_dist - dir.second); } - bridging_angle /= acc; + if (acc <= EPSILON || bridging_angle == 0) { + bridging_angle = 0.01; + } else { + bridging_angle /= acc; + } } // TODO maybe get extens of rotated max_area, then fill with vertical lines, make AABB tree rotated for anchors and @@ -1733,6 +1737,11 @@ void PrintObject::bridge_over_infill() } }; + auto segments_overlap = [](coord_t alow, coord_t ahigh, coord_t blow, coord_t bhigh) { + return (alow >= blow && alow <= bhigh) || (ahigh >= blow && ahigh <= bhigh) || (blow >= alow && blow <= ahigh) || + (bhigh >= alow && bhigh <= ahigh); + }; + Polygons expanded_bridged_area{}; { polygons_rotate(bridged_area, bridging_angle); @@ -1756,37 +1765,48 @@ void PrintObject::bridge_over_infill() std::vector>> polygon_sections(n_vlines); for (size_t i = 0; i < n_vlines; i++) { auto area_intersections = bridged_area_tree.intersections_with_line(vertical_lines[i]); - if (area_intersections.size() < 2) { - if (area_intersections.size() > 0) { - polygon_sections[i].emplace_back(area_intersections[0].first, area_intersections[0].first); + for (int intersection_idx = 0; intersection_idx < int(area_intersections.size()) - 1; intersection_idx++) { + if (!bridged_area_tree.outside( + (area_intersections[intersection_idx].first + area_intersections[intersection_idx + 1].first) / 2)) { + polygon_sections[i].emplace_back(area_intersections[intersection_idx].first, + area_intersections[intersection_idx + 1].first); } - continue; } auto anchors_intersections = anchors_and_walls_tree.intersections_with_line(vertical_lines[i]); - for (const auto &intersection : area_intersections) { - auto high_b = std::upper_bound(anchors_intersections.begin(), anchors_intersections.end(), intersection, - [](const std::pair left, - const std::pair right) { - return left.first.y() > right.first.y(); - }); - Point low, high; - if (high_b == anchors_intersections.end()) { - assert(false); // should not happen - continue; - } else if (high_b == anchors_intersections.begin()) { - low = high_b->first; - high = (++high_b)->first; - } else { - low = (--high_b)->first; - high = high_b->first; + + for (std::pair §ion : polygon_sections[i]) { + auto maybe_below_anchor = std::upper_bound(anchors_intersections.begin(), anchors_intersections.end(), + section.first, + [](const Point &a, const std::pair &b) { + return a.y() < b.first.y(); + }); + if (maybe_below_anchor != anchors_intersections.begin() && + maybe_below_anchor != anchors_intersections.end()) { + section.first = (--maybe_below_anchor)->first; } - if (polygon_sections[i].size() > 0 && polygon_sections[i].back().second.y() >= low.y()) { - polygon_sections[i].back().second = high; - } else { - polygon_sections[i].emplace_back(low, high); + auto maybe_upper_anchor = std::upper_bound(anchors_intersections.begin(), anchors_intersections.end(), + section.second, + [](const Point &a, const std::pair &b) { + return a.y() < b.first.y(); + }); + if (maybe_upper_anchor != anchors_intersections.end()) { + section.second = maybe_upper_anchor->first; } } + + for (int section_idx = 0; section_idx < int(polygon_sections[i].size()) - 1; section_idx++) { + std::pair §ion_a = polygon_sections[i][section_idx]; + std::pair §ion_b = polygon_sections[i][section_idx + 1]; + if (segments_overlap(section_a.first.y(), section_a.second.y(), section_b.first.y(), section_b.second.y())) { + section_b.first = section_a.first.y() < section_b.first.y() ? section_a.first : section_b.first; + section_b.second = section_a.second.y() < section_b.second.y() ? section_b.second : section_a.second; + section_a.first = section_a.second; + } + } + + std::remove_if(polygon_sections[i].begin(), polygon_sections[i].end(), + [](const std::pair &s) { return s.first == s.second; }); } // reconstruct polygon from polygon sections @@ -1796,11 +1816,6 @@ void PrintObject::bridge_over_infill() std::vector highs; }; - auto segments_overlap = [](coord_t alow, coord_t ahigh, coord_t blow, coord_t bhigh) { - return (alow >= blow && alow <= bhigh) || (ahigh >= blow && ahigh <= bhigh) || - (blow >= alow && blow <= ahigh) || (bhigh >= alow && bhigh <= ahigh); - }; - std::vector current_traced_polys; for (const auto &layer : polygon_sections) { std::unordered_set *> used_segments; From df20302eefc6c1c11e862d432caf1043373f513b Mon Sep 17 00:00:00 2001 From: PavelMikus Date: Tue, 14 Feb 2023 11:57:43 +0100 Subject: [PATCH 027/110] various bug fixes debug images --- src/libslic3r/PrintObject.cpp | 102 +++++++++++++++++++++++----------- 1 file changed, 71 insertions(+), 31 deletions(-) diff --git a/src/libslic3r/PrintObject.cpp b/src/libslic3r/PrintObject.cpp index 83bd7d3298..848ce36eac 100644 --- a/src/libslic3r/PrintObject.cpp +++ b/src/libslic3r/PrintObject.cpp @@ -38,6 +38,7 @@ #include #include #include +#include #include #include #include @@ -70,6 +71,8 @@ using namespace std::literals; #include #endif + #include "SVG.hpp" + namespace Slic3r { // Constructor is called from the main thread, therefore all Model / ModelObject / ModelIntance data are valid. @@ -1530,6 +1533,25 @@ void PrintObject::discover_vertical_shells() } // for each region } // void PrintObject::discover_vertical_shells() +#define DEBUG_BRIDGE_OVER_INFILL +#ifdef DEBUG_BRIDGE_OVER_INFILL +template void debug_draw(std::string name, const T& a, const T& b, const T& c, const T& d) +{ + std::vector colors = {"red", "blue", "orange", "green"}; + BoundingBox bbox = get_extents(a); + bbox.merge(get_extents(b)); + bbox.merge(get_extents(c)); + bbox.merge(get_extents(d)); + bbox.offset(scale_(1.)); + ::Slic3r::SVG svg(debug_out_path(name.c_str()).c_str(), bbox); + svg.draw(a, colors[0], scale_(0.3)); + svg.draw(b, colors[1], scale_(0.23)); + svg.draw(c, colors[2], scale_(0.16)); + svg.draw(d, colors[3], scale_(0.10)); + svg.Close(); +} +#endif + // This method applies bridge flow to the first internal solid layer above sparse infill. void PrintObject::bridge_over_infill() { @@ -1682,6 +1704,11 @@ void PrintObject::bridge_over_infill() Lines tmp = to_lines(max_area); anchors_and_walls.insert(anchors_and_walls.end(), tmp.begin(), tmp.end()); +#ifdef DEBUG_BRIDGE_OVER_INFILL + debug_draw("candidate" + std::to_string(lidx), to_lines(candidate->expolygon), to_lines(bridged_area), + to_lines(max_area), (anchors_and_walls)); +#endif + double bridging_angle = 0; { AABBTreeLines::LinesDistancer lines_tree{anchors_and_walls}; @@ -1714,7 +1741,7 @@ void PrintObject::bridge_over_infill() acc += (max_dist - dir.second); } if (acc <= EPSILON || bridging_angle == 0) { - bridging_angle = 0.01; + bridging_angle = 0.001; } else { bridging_angle /= acc; } @@ -1762,51 +1789,56 @@ void PrintObject::bridge_over_infill() auto anchors_and_walls_tree = AABBTreeLines::LinesDistancer{std::move(anchors_and_walls)}; auto bridged_area_tree = AABBTreeLines::LinesDistancer{to_lines(bridged_area)}; - std::vector>> polygon_sections(n_vlines); +#ifdef DEBUG_BRIDGE_OVER_INFILL + debug_draw("sliced" + std::to_string(lidx), to_lines(bridged_area), anchors_and_walls, + vertical_lines, {}); +#endif + + std::vector> polygon_sections(n_vlines); for (size_t i = 0; i < n_vlines; i++) { auto area_intersections = bridged_area_tree.intersections_with_line(vertical_lines[i]); for (int intersection_idx = 0; intersection_idx < int(area_intersections.size()) - 1; intersection_idx++) { - if (!bridged_area_tree.outside( - (area_intersections[intersection_idx].first + area_intersections[intersection_idx + 1].first) / 2)) { + if (bridged_area_tree.outside( + (area_intersections[intersection_idx].first + area_intersections[intersection_idx + 1].first) / 2) < 0) { polygon_sections[i].emplace_back(area_intersections[intersection_idx].first, area_intersections[intersection_idx + 1].first); } } auto anchors_intersections = anchors_and_walls_tree.intersections_with_line(vertical_lines[i]); - for (std::pair §ion : polygon_sections[i]) { + for (Line §ion : polygon_sections[i]) { auto maybe_below_anchor = std::upper_bound(anchors_intersections.begin(), anchors_intersections.end(), - section.first, + section.a, [](const Point &a, const std::pair &b) { return a.y() < b.first.y(); }); if (maybe_below_anchor != anchors_intersections.begin() && maybe_below_anchor != anchors_intersections.end()) { - section.first = (--maybe_below_anchor)->first; + section.a = (--maybe_below_anchor)->first; } auto maybe_upper_anchor = std::upper_bound(anchors_intersections.begin(), anchors_intersections.end(), - section.second, + section.b, [](const Point &a, const std::pair &b) { return a.y() < b.first.y(); }); if (maybe_upper_anchor != anchors_intersections.end()) { - section.second = maybe_upper_anchor->first; + section.b = maybe_upper_anchor->first; } } for (int section_idx = 0; section_idx < int(polygon_sections[i].size()) - 1; section_idx++) { - std::pair §ion_a = polygon_sections[i][section_idx]; - std::pair §ion_b = polygon_sections[i][section_idx + 1]; - if (segments_overlap(section_a.first.y(), section_a.second.y(), section_b.first.y(), section_b.second.y())) { - section_b.first = section_a.first.y() < section_b.first.y() ? section_a.first : section_b.first; - section_b.second = section_a.second.y() < section_b.second.y() ? section_b.second : section_a.second; - section_a.first = section_a.second; + Line §ion_a = polygon_sections[i][section_idx]; + Line §ion_b = polygon_sections[i][section_idx + 1]; + if (segments_overlap(section_a.a.y(), section_a.b.y(), section_b.a.y(), section_b.b.y())) { + section_b.a = section_a.a.y() < section_b.a.y() ? section_a.a : section_b.a; + section_b.b = section_a.b.y() < section_b.b.y() ? section_b.b : section_a.b; + section_a.a = section_a.b; } } std::remove_if(polygon_sections[i].begin(), polygon_sections[i].end(), - [](const std::pair &s) { return s.first == s.second; }); + [](const Line &s) { return s.a == s.b; }); } // reconstruct polygon from polygon sections @@ -1818,24 +1850,23 @@ void PrintObject::bridge_over_infill() std::vector current_traced_polys; for (const auto &layer : polygon_sections) { - std::unordered_set *> used_segments; + std::unordered_set used_segments; for (TracedPoly &traced_poly : current_traced_polys) { auto maybe_first_overlap = std::upper_bound(layer.begin(), layer.end(), traced_poly.lows.back(), - [](const Point &low, const std::pair &seg) { - return seg.second.y() > low.y(); + [](const Point &low, const Line &seg) { + return seg.b.y() > low.y(); }); if (maybe_first_overlap != layer.end() && // segment exists - segments_overlap(traced_poly.lows.back().y(), traced_poly.highs.back().y(), - maybe_first_overlap->first.y(), - maybe_first_overlap->second.y())) // segment is overlapping + segments_overlap(traced_poly.lows.back().y(), traced_poly.highs.back().y(), maybe_first_overlap->a.y(), + maybe_first_overlap->b.y())) // segment is overlapping { // Overlapping segment. In that case, add it // to the traced polygon and add segment to used segments - traced_poly.lows.push_back(maybe_first_overlap->first - Point{flow.scaled_spacing() / 2, 0}); - traced_poly.lows.push_back(maybe_first_overlap->first + Point{flow.scaled_spacing() / 2, 0}); - traced_poly.highs.push_back(maybe_first_overlap->second - Point{flow.scaled_spacing() / 2, 0}); - traced_poly.highs.push_back(maybe_first_overlap->second + Point{flow.scaled_spacing() / 2, 0}); + traced_poly.lows.push_back(maybe_first_overlap->a - Point{flow.scaled_spacing() / 2, 0}); + traced_poly.lows.push_back(maybe_first_overlap->a + Point{flow.scaled_spacing() / 2, 0}); + traced_poly.highs.push_back(maybe_first_overlap->b - Point{flow.scaled_spacing() / 2, 0}); + traced_poly.highs.push_back(maybe_first_overlap->b + Point{flow.scaled_spacing() / 2, 0}); used_segments.insert(&(*maybe_first_overlap)); } else { // Zero or multiple overlapping segments. Resolving this is nontrivial, @@ -1851,12 +1882,12 @@ void PrintObject::bridge_over_infill() [](const TracedPoly &tp) { return tp.lows.empty(); }); for (const auto &segment : layer) { - if (used_segments.find(&segment) != used_segments.end()) { + if (used_segments.find(&segment) == used_segments.end()) { TracedPoly &new_tp = current_traced_polys.emplace_back(); - new_tp.lows.push_back(segment.first - Point{flow.scaled_spacing() / 2, 0}); - new_tp.lows.push_back(segment.first + Point{flow.scaled_spacing() / 2, 0}); - new_tp.highs.push_back(segment.second - Point{flow.scaled_spacing() / 2, 0}); - new_tp.highs.push_back(segment.second + Point{flow.scaled_spacing() / 2, 0}); + new_tp.lows.push_back(segment.a - Point{flow.scaled_spacing() / 2, 0}); + new_tp.lows.push_back(segment.a + Point{flow.scaled_spacing() / 2, 0}); + new_tp.highs.push_back(segment.b - Point{flow.scaled_spacing() / 2, 0}); + new_tp.highs.push_back(segment.b + Point{flow.scaled_spacing() / 2, 0}); } } } @@ -1866,6 +1897,15 @@ void PrintObject::bridge_over_infill() Polygon &new_poly = expanded_bridged_area.emplace_back(std::move(traced_poly.lows)); new_poly.points.insert(new_poly.points.end(), traced_poly.highs.rbegin(), traced_poly.highs.rend()); } + +#ifdef DEBUG_BRIDGE_OVER_INFILL + Lines l{}; + for (const auto &s : polygon_sections) { + l.insert(l.end(), s.begin(), s.end()); + } + debug_draw("reconstructed" + std::to_string(lidx), l, anchors_and_walls_tree.get_lines(), to_lines(expanded_bridged_area), + bridged_area_tree.get_lines()); +#endif } expand_area = diff(expand_area, expanded_bridged_area); From 9f5f03099e3b7d17f4c5a4d720d48e8a42235eed Mon Sep 17 00:00:00 2001 From: PavelMikus Date: Tue, 14 Feb 2023 13:08:24 +0100 Subject: [PATCH 028/110] fixing bridging angle, avoiding too small areas, fixing final area rotation --- src/libslic3r/PrintObject.cpp | 138 +++++++++++++++++++--------------- 1 file changed, 78 insertions(+), 60 deletions(-) diff --git a/src/libslic3r/PrintObject.cpp b/src/libslic3r/PrintObject.cpp index 848ce36eac..b55baa6b0d 100644 --- a/src/libslic3r/PrintObject.cpp +++ b/src/libslic3r/PrintObject.cpp @@ -1597,6 +1597,12 @@ void PrintObject::bridge_over_infill() for (size_t region_idx : regions_to_check) { const LayerRegion *region = layer->get_region(region_idx); auto region_internal_solids = region->fill_surfaces().filter_by_type(stInternalSolid); + + //remove very small solid infills, usually not worth it and many of them may not even contain extrusions in the end. + void(std::remove_if(region_internal_solids.begin(), region_internal_solids.end(), [region](const Surface *s) { + float min_width = float(region->bridging_flow(frSolidInfill).scaled_width()) * 3.f; + return opening_ex({s->expolygon}, min_width).empty(); + })); if (!region_internal_solids.empty()) { max_bridge_flow_height[&slice] = std::max(max_bridge_flow_height[&slice], region->bridging_flow(frSolidInfill).height()); @@ -1724,10 +1730,14 @@ void PrintObject::bridge_over_infill() int lines_count = int(std::ceil(dist_to_next / scaled(3.0))); float step_size = dist_to_next / lines_count; for (int i = 0; i < lines_count; ++i) { - Point a = (start + v * (i * step_size)).cast(); + Point a = (start + v * (i * step_size)).cast(); auto [distance, index, p] = lines_tree.distance_from_lines_extra(a); - const Line &l = lines_tree.get_line(index); - directions_with_distances.emplace_back(PI - l.direction(), distance); + double angle = lines_tree.get_line(index).orientation(); + if (angle > PI) { + angle -= PI; + } + angle += PI; + directions_with_distances.emplace_back(angle, distance); } } } @@ -1770,9 +1780,10 @@ void PrintObject::bridge_over_infill() }; Polygons expanded_bridged_area{}; + double aligning_angle = -bridging_angle + PI; { - polygons_rotate(bridged_area, bridging_angle); - lines_rotate(anchors_and_walls, cos(bridging_angle), sin(bridging_angle)); + polygons_rotate(bridged_area, aligning_angle); + lines_rotate(anchors_and_walls, cos(aligning_angle), sin(aligning_angle)); BoundingBox bb_x = get_extents(bridged_area); BoundingBox bb_y = get_extents(anchors_and_walls); @@ -1903,77 +1914,84 @@ void PrintObject::bridge_over_infill() for (const auto &s : polygon_sections) { l.insert(l.end(), s.begin(), s.end()); } - debug_draw("reconstructed" + std::to_string(lidx), l, anchors_and_walls_tree.get_lines(), to_lines(expanded_bridged_area), - bridged_area_tree.get_lines()); + debug_draw("reconstructed" + std::to_string(lidx), l, anchors_and_walls_tree.get_lines(), + to_lines(expanded_bridged_area), bridged_area_tree.get_lines()); #endif } - expand_area = diff(expand_area, expanded_bridged_area); + polygons_rotate(expanded_bridged_area, -aligning_angle); + expanded_bridged_area = intersection(expanded_bridged_area, max_area); + expand_area = diff(expand_area, expanded_bridged_area); expanded_briding_surfaces[candidates.first].emplace_back(candidate, expanded_bridged_area, surface_to_region[candidate], bridging_angle); + +#ifdef DEBUG_BRIDGE_OVER_INFILL + debug_draw("cadidate_added" + std::to_string(lidx), to_lines(expanded_bridged_area), to_lines(bridged_area), + to_lines(max_area), to_lines(expand_area)); +#endif } } } - // }); + // }); - BOOST_LOG_TRIVIAL(info) << "Bridge over infill - Directions and expanded surfaces computed" << log_memory_info(); + BOOST_LOG_TRIVIAL(info) << "Bridge over infill - Directions and expanded surfaces computed" << log_memory_info(); - tbb::parallel_for(tbb::blocked_range(0, this->layers().size()), - [po = this, &expanded_briding_surfaces](tbb::blocked_range r) { - for (size_t lidx = r.begin(); lidx < r.end(); lidx++) { - Layer *layer = po->get_layer(lidx); + tbb::parallel_for(tbb::blocked_range(0, this->layers().size()), [po = this, + &expanded_briding_surfaces](tbb::blocked_range r) { + for (size_t lidx = r.begin(); lidx < r.end(); lidx++) { + Layer *layer = po->get_layer(lidx); - for (const LayerSlice &slice : layer->lslices_ex) { - if (const auto &modified_surfaces = expanded_briding_surfaces.find(&slice); - modified_surfaces != expanded_briding_surfaces.end()) { - std::unordered_set regions_to_check; - for (const LayerIsland &island : slice.islands) { - regions_to_check.insert(island.perimeters.region()); - if (!island.fill_expolygons_composite()) { - regions_to_check.insert(island.fill_region_id); - } - } + for (const LayerSlice &slice : layer->lslices_ex) { + if (const auto &modified_surfaces = expanded_briding_surfaces.find(&slice); + modified_surfaces != expanded_briding_surfaces.end()) { + std::unordered_set regions_to_check; + for (const LayerIsland &island : slice.islands) { + regions_to_check.insert(island.perimeters.region()); + if (!island.fill_expolygons_composite()) { + regions_to_check.insert(island.fill_region_id); + } + } - Polygons cut_from_infill{}; - for (const auto &surface : modified_surfaces->second) { - cut_from_infill.insert(cut_from_infill.end(), surface.new_polys.begin(), surface.new_polys.end()); - } + Polygons cut_from_infill{}; + for (const auto &surface : modified_surfaces->second) { + cut_from_infill.insert(cut_from_infill.end(), surface.new_polys.begin(), surface.new_polys.end()); + } - for (size_t region_idx : regions_to_check) { - LayerRegion *region = layer->get_region(region_idx); - Surfaces new_surfaces; + for (size_t region_idx : regions_to_check) { + LayerRegion *region = layer->get_region(region_idx); + Surfaces new_surfaces; - for (const ModifiedSurface &s : modified_surfaces->second) { - for (Surface &surface : region->m_fill_surfaces.surfaces) { - if (s.original_surface == &surface) { - Surface tmp(surface, {}); - tmp.surface_type = stInternalBridge; - tmp.bridge_angle = s.bridge_angle; - for (const ExPolygon &expoly : union_ex(s.new_polys)) { - new_surfaces.emplace_back(tmp, expoly); - } - surface.clear(); - } else if (surface.surface_type == stInternal) { - Surface tmp(surface, {}); - for (const ExPolygon &expoly : diff_ex(surface.expolygon, cut_from_infill)) { - new_surfaces.emplace_back(tmp, expoly); - } - surface.clear(); - } - } - } - region->m_fill_surfaces.surfaces.insert(region->m_fill_surfaces.surfaces.end(), - new_surfaces.begin(), new_surfaces.end()); - std::remove_if(region->m_fill_surfaces.begin(), region->m_fill_surfaces.end(), - [](const Surface &s) { return s.empty(); }); - } - } - } - } - }); + for (const ModifiedSurface &s : modified_surfaces->second) { + for (Surface &surface : region->m_fill_surfaces.surfaces) { + if (s.original_surface == &surface) { + Surface tmp(surface, {}); + tmp.surface_type = stInternalBridge; + tmp.bridge_angle = s.bridge_angle; + for (const ExPolygon &expoly : union_ex(s.new_polys)) { + new_surfaces.emplace_back(tmp, expoly); + } + surface.clear(); + } else if (surface.surface_type == stInternal) { + Surface tmp(surface, {}); + for (const ExPolygon &expoly : diff_ex(surface.expolygon, cut_from_infill)) { + new_surfaces.emplace_back(tmp, expoly); + } + surface.clear(); + } + } + } + region->m_fill_surfaces.surfaces.insert(region->m_fill_surfaces.surfaces.end(), new_surfaces.begin(), + new_surfaces.end()); + std::remove_if(region->m_fill_surfaces.begin(), region->m_fill_surfaces.end(), + [](const Surface &s) { return s.empty(); }); + } + } + } + } + }); - BOOST_LOG_TRIVIAL(info) << "Bridge over infill - End" << log_memory_info(); + BOOST_LOG_TRIVIAL(info) << "Bridge over infill - End" << log_memory_info(); } // void PrintObject::bridge_over_infill() From 95ec803a06e879526c71dab0ad3c44d8e19b29b2 Mon Sep 17 00:00:00 2001 From: PavelMikus Date: Tue, 14 Feb 2023 14:06:05 +0100 Subject: [PATCH 029/110] Another bunch of fixes. There is still problem with bridging direction --- src/libslic3r/PrintObject.cpp | 25 ++++++++++++------------- 1 file changed, 12 insertions(+), 13 deletions(-) diff --git a/src/libslic3r/PrintObject.cpp b/src/libslic3r/PrintObject.cpp index b55baa6b0d..21c19c84d8 100644 --- a/src/libslic3r/PrintObject.cpp +++ b/src/libslic3r/PrintObject.cpp @@ -1736,7 +1736,7 @@ void PrintObject::bridge_over_infill() if (angle > PI) { angle -= PI; } - angle += PI; + angle -= PI * 0.5; directions_with_distances.emplace_back(angle, distance); } } @@ -1780,7 +1780,7 @@ void PrintObject::bridge_over_infill() }; Polygons expanded_bridged_area{}; - double aligning_angle = -bridging_angle + PI; + double aligning_angle = -bridging_angle + PI * 0.5; { polygons_rotate(bridged_area, aligning_angle); lines_rotate(anchors_and_walls, cos(aligning_angle), sin(aligning_angle)); @@ -1818,14 +1818,13 @@ void PrintObject::bridge_over_infill() auto anchors_intersections = anchors_and_walls_tree.intersections_with_line(vertical_lines[i]); for (Line §ion : polygon_sections[i]) { - auto maybe_below_anchor = std::upper_bound(anchors_intersections.begin(), anchors_intersections.end(), + auto maybe_below_anchor = std::upper_bound(anchors_intersections.rbegin(), anchors_intersections.rend(), section.a, [](const Point &a, const std::pair &b) { - return a.y() < b.first.y(); + return a.y() > b.first.y(); }); - if (maybe_below_anchor != anchors_intersections.begin() && - maybe_below_anchor != anchors_intersections.end()) { - section.a = (--maybe_below_anchor)->first; + if (maybe_below_anchor != anchors_intersections.rend()) { + section.a = maybe_below_anchor->first; } auto maybe_upper_anchor = std::upper_bound(anchors_intersections.begin(), anchors_intersections.end(), @@ -1848,8 +1847,8 @@ void PrintObject::bridge_over_infill() } } - std::remove_if(polygon_sections[i].begin(), polygon_sections[i].end(), - [](const Line &s) { return s.a == s.b; }); + void(std::remove_if(polygon_sections[i].begin(), polygon_sections[i].end(), + [](const Line &s) { return s.a == s.b; })); } // reconstruct polygon from polygon sections @@ -1889,8 +1888,8 @@ void PrintObject::bridge_over_infill() } } - std::remove_if(current_traced_polys.begin(), current_traced_polys.end(), - [](const TracedPoly &tp) { return tp.lows.empty(); }); + void(std::remove_if(current_traced_polys.begin(), current_traced_polys.end(), + [](const TracedPoly &tp) { return tp.lows.empty(); })); for (const auto &segment : layer) { if (used_segments.find(&segment) == used_segments.end()) { @@ -1983,8 +1982,8 @@ void PrintObject::bridge_over_infill() } region->m_fill_surfaces.surfaces.insert(region->m_fill_surfaces.surfaces.end(), new_surfaces.begin(), new_surfaces.end()); - std::remove_if(region->m_fill_surfaces.begin(), region->m_fill_surfaces.end(), - [](const Surface &s) { return s.empty(); }); + void(std::remove_if(region->m_fill_surfaces.begin(), region->m_fill_surfaces.end(), + [](const Surface &s) { return s.empty(); })); } } } From d843d0d981f91598c25ad375a7629c330d5e5040 Mon Sep 17 00:00:00 2001 From: PavelMikus Date: Tue, 14 Feb 2023 16:06:37 +0100 Subject: [PATCH 030/110] Fixed most of issues, TODO expand by half extrusion width, smoothen sides, crashes --- src/libslic3r/PrintObject.cpp | 17 +++++------------ 1 file changed, 5 insertions(+), 12 deletions(-) diff --git a/src/libslic3r/PrintObject.cpp b/src/libslic3r/PrintObject.cpp index 21c19c84d8..dfa2148e27 100644 --- a/src/libslic3r/PrintObject.cpp +++ b/src/libslic3r/PrintObject.cpp @@ -1570,11 +1570,8 @@ void PrintObject::bridge_over_infill() std::unordered_map> expanded_briding_surfaces; - // tbb::parallel_for(tbb::blocked_range(0, this->layers().size()), [po = this, - // &expanded_briding_surfaces](tbb::blocked_range r) { - auto r = tbb::blocked_range{0, this->layer_count()}; - auto po = this; - + tbb::parallel_for(tbb::blocked_range(0, this->layers().size()), [po = this, + &expanded_briding_surfaces](tbb::blocked_range r) { for (size_t lidx = r.begin(); lidx < r.end(); lidx++) { const Layer *layer = po->get_layer(lidx); @@ -1601,7 +1598,7 @@ void PrintObject::bridge_over_infill() //remove very small solid infills, usually not worth it and many of them may not even contain extrusions in the end. void(std::remove_if(region_internal_solids.begin(), region_internal_solids.end(), [region](const Surface *s) { float min_width = float(region->bridging_flow(frSolidInfill).scaled_width()) * 3.f; - return opening_ex({s->expolygon}, min_width).empty(); + return offset_ex({s->expolygon}, -min_width).empty(); })); if (!region_internal_solids.empty()) { max_bridge_flow_height[&slice] = std::max(max_bridge_flow_height[&slice], @@ -1736,7 +1733,7 @@ void PrintObject::bridge_over_infill() if (angle > PI) { angle -= PI; } - angle -= PI * 0.5; + angle += PI * 0.5; directions_with_distances.emplace_back(angle, distance); } } @@ -1757,10 +1754,6 @@ void PrintObject::bridge_over_infill() } } - // TODO maybe get extens of rotated max_area, then fill with vertical lines, make AABB tree rotated for anchors and - // walls and also - // for bridged area - // then cut off the vertical lines, compose the final polygon, and rotate back auto lines_rotate = [](Lines &lines, double cos_angle, double sin_angle) { for (Line &l : lines) { double ax = double(l.a.x()); @@ -1932,7 +1925,7 @@ void PrintObject::bridge_over_infill() } } } - // }); + }); BOOST_LOG_TRIVIAL(info) << "Bridge over infill - Directions and expanded surfaces computed" << log_memory_info(); From 5e83ecf387a445d05d03aff0f39f29ac20ef40e4 Mon Sep 17 00:00:00 2001 From: PavelMikus Date: Wed, 15 Feb 2023 11:54:51 +0100 Subject: [PATCH 031/110] Fixed crashes, smoothened polygon, some bug fixes --- src/libslic3r/PrintObject.cpp | 78 +++++++++++++++++++++++------------ 1 file changed, 51 insertions(+), 27 deletions(-) diff --git a/src/libslic3r/PrintObject.cpp b/src/libslic3r/PrintObject.cpp index dfa2148e27..8b72c292ab 100644 --- a/src/libslic3r/PrintObject.cpp +++ b/src/libslic3r/PrintObject.cpp @@ -1593,13 +1593,16 @@ void PrintObject::bridge_over_infill() for (size_t region_idx : regions_to_check) { const LayerRegion *region = layer->get_region(region_idx); - auto region_internal_solids = region->fill_surfaces().filter_by_type(stInternalSolid); + SurfacesPtr region_internal_solids = region->fill_surfaces().filter_by_type(stInternalSolid); - //remove very small solid infills, usually not worth it and many of them may not even contain extrusions in the end. - void(std::remove_if(region_internal_solids.begin(), region_internal_solids.end(), [region](const Surface *s) { - float min_width = float(region->bridging_flow(frSolidInfill).scaled_width()) * 3.f; - return offset_ex({s->expolygon}, -min_width).empty(); - })); + // remove very small solid infills, usually not worth it and many of them may not even contain extrusions in the end. + region_internal_solids.erase(std::remove_if(region_internal_solids.begin(), region_internal_solids.end(), + [region](const Surface *s) { + float min_width = + float(region->bridging_flow(frSolidInfill).scaled_width()) * 3.f; + return offset_ex({s->expolygon}, -min_width).empty(); + }), + region_internal_solids.end()); if (!region_internal_solids.empty()) { max_bridge_flow_height[&slice] = std::max(max_bridge_flow_height[&slice], region->bridging_flow(frSolidInfill).height()); @@ -1708,7 +1711,7 @@ void PrintObject::bridge_over_infill() anchors_and_walls.insert(anchors_and_walls.end(), tmp.begin(), tmp.end()); #ifdef DEBUG_BRIDGE_OVER_INFILL - debug_draw("candidate" + std::to_string(lidx), to_lines(candidate->expolygon), to_lines(bridged_area), + debug_draw(std::to_string(lidx) + "candidate", to_lines(candidate->expolygon), to_lines(bridged_area), to_lines(max_area), (anchors_and_walls)); #endif @@ -1794,7 +1797,7 @@ void PrintObject::bridge_over_infill() auto bridged_area_tree = AABBTreeLines::LinesDistancer{to_lines(bridged_area)}; #ifdef DEBUG_BRIDGE_OVER_INFILL - debug_draw("sliced" + std::to_string(lidx), to_lines(bridged_area), anchors_and_walls, + debug_draw(std::to_string(lidx) + "sliced", to_lines(bridged_area), anchors_and_walls, vertical_lines, {}); #endif @@ -1818,6 +1821,7 @@ void PrintObject::bridge_over_infill() }); if (maybe_below_anchor != anchors_intersections.rend()) { section.a = maybe_below_anchor->first; + section.a.y() -= flow.scaled_width() * (0.5 + 1.0); } auto maybe_upper_anchor = std::upper_bound(anchors_intersections.begin(), anchors_intersections.end(), @@ -1827,6 +1831,7 @@ void PrintObject::bridge_over_infill() }); if (maybe_upper_anchor != anchors_intersections.end()) { section.b = maybe_upper_anchor->first; + section.b.y() += flow.scaled_width() * (0.5 + 1.0); } } @@ -1840,8 +1845,9 @@ void PrintObject::bridge_over_infill() } } - void(std::remove_if(polygon_sections[i].begin(), polygon_sections[i].end(), - [](const Line &s) { return s.a == s.b; })); + polygon_sections[i].erase(std::remove_if(polygon_sections[i].begin(), polygon_sections[i].end(), + [](const Line &s) { return s.a == s.b; }), + polygon_sections[i].end()); } // reconstruct polygon from polygon sections @@ -1852,28 +1858,43 @@ void PrintObject::bridge_over_infill() }; std::vector current_traced_polys; - for (const auto &layer : polygon_sections) { + for (const auto &polygon_slice : polygon_sections) { std::unordered_set used_segments; for (TracedPoly &traced_poly : current_traced_polys) { - auto maybe_first_overlap = std::upper_bound(layer.begin(), layer.end(), traced_poly.lows.back(), - [](const Point &low, const Line &seg) { + auto maybe_first_overlap = std::upper_bound(polygon_slice.begin(), polygon_slice.end(), + traced_poly.lows.back(), [](const Point &low, const Line &seg) { return seg.b.y() > low.y(); }); - if (maybe_first_overlap != layer.end() && // segment exists + if (maybe_first_overlap != polygon_slice.end() && // segment exists segments_overlap(traced_poly.lows.back().y(), traced_poly.highs.back().y(), maybe_first_overlap->a.y(), maybe_first_overlap->b.y())) // segment is overlapping { // Overlapping segment. In that case, add it // to the traced polygon and add segment to used segments - traced_poly.lows.push_back(maybe_first_overlap->a - Point{flow.scaled_spacing() / 2, 0}); - traced_poly.lows.push_back(maybe_first_overlap->a + Point{flow.scaled_spacing() / 2, 0}); - traced_poly.highs.push_back(maybe_first_overlap->b - Point{flow.scaled_spacing() / 2, 0}); - traced_poly.highs.push_back(maybe_first_overlap->b + Point{flow.scaled_spacing() / 2, 0}); + if ((traced_poly.lows.back() - maybe_first_overlap->a).cast().squaredNorm() < + 36.0 * double(flow.scaled_spacing()) * flow.scaled_spacing()) { + traced_poly.lows.push_back(maybe_first_overlap->a); + } else { + traced_poly.lows.push_back(traced_poly.lows.back() + Point{flow.scaled_spacing() / 2, 0}); + traced_poly.lows.push_back(maybe_first_overlap->a - Point{flow.scaled_spacing() / 2, 0}); + traced_poly.lows.push_back(maybe_first_overlap->a); + } + + if ((traced_poly.highs.back() - maybe_first_overlap->b).cast().squaredNorm() < + 36.0 * double(flow.scaled_spacing()) * flow.scaled_spacing()) { + traced_poly.highs.push_back(maybe_first_overlap->b); + } else { + traced_poly.highs.push_back(traced_poly.highs.back() + Point{flow.scaled_spacing() / 2, 0}); + traced_poly.highs.push_back(maybe_first_overlap->b - Point{flow.scaled_spacing() / 2, 0}); + traced_poly.highs.push_back(maybe_first_overlap->b); + } used_segments.insert(&(*maybe_first_overlap)); } else { // Zero or multiple overlapping segments. Resolving this is nontrivial, // so we just close this polygon and maybe open several new. This will hopefully happen much less often + traced_poly.lows.push_back(traced_poly.lows.back() + Point{flow.scaled_spacing() / 2, 0}); + traced_poly.highs.push_back(traced_poly.highs.back() + Point{flow.scaled_spacing() / 2, 0}); Polygon &new_poly = expanded_bridged_area.emplace_back(std::move(traced_poly.lows)); new_poly.points.insert(new_poly.points.end(), traced_poly.highs.rbegin(), traced_poly.highs.rend()); traced_poly.lows.clear(); @@ -1881,16 +1902,17 @@ void PrintObject::bridge_over_infill() } } - void(std::remove_if(current_traced_polys.begin(), current_traced_polys.end(), - [](const TracedPoly &tp) { return tp.lows.empty(); })); + current_traced_polys.erase(std::remove_if(current_traced_polys.begin(), current_traced_polys.end(), + [](const TracedPoly &tp) { return tp.lows.empty(); }), + current_traced_polys.end()); - for (const auto &segment : layer) { + for (const auto &segment : polygon_slice) { if (used_segments.find(&segment) == used_segments.end()) { TracedPoly &new_tp = current_traced_polys.emplace_back(); new_tp.lows.push_back(segment.a - Point{flow.scaled_spacing() / 2, 0}); - new_tp.lows.push_back(segment.a + Point{flow.scaled_spacing() / 2, 0}); + new_tp.lows.push_back(segment.a); new_tp.highs.push_back(segment.b - Point{flow.scaled_spacing() / 2, 0}); - new_tp.highs.push_back(segment.b + Point{flow.scaled_spacing() / 2, 0}); + new_tp.highs.push_back(segment.b); } } } @@ -1906,7 +1928,7 @@ void PrintObject::bridge_over_infill() for (const auto &s : polygon_sections) { l.insert(l.end(), s.begin(), s.end()); } - debug_draw("reconstructed" + std::to_string(lidx), l, anchors_and_walls_tree.get_lines(), + debug_draw(std::to_string(lidx) + "reconstructed", l, anchors_and_walls_tree.get_lines(), to_lines(expanded_bridged_area), bridged_area_tree.get_lines()); #endif } @@ -1919,7 +1941,7 @@ void PrintObject::bridge_over_infill() bridging_angle); #ifdef DEBUG_BRIDGE_OVER_INFILL - debug_draw("cadidate_added" + std::to_string(lidx), to_lines(expanded_bridged_area), to_lines(bridged_area), + debug_draw(std::to_string(lidx) + "cadidate_added", to_lines(expanded_bridged_area), to_lines(bridged_area), to_lines(max_area), to_lines(expand_area)); #endif } @@ -1975,8 +1997,10 @@ void PrintObject::bridge_over_infill() } region->m_fill_surfaces.surfaces.insert(region->m_fill_surfaces.surfaces.end(), new_surfaces.begin(), new_surfaces.end()); - void(std::remove_if(region->m_fill_surfaces.begin(), region->m_fill_surfaces.end(), - [](const Surface &s) { return s.empty(); })); + region->m_fill_surfaces.surfaces.erase(std::remove_if(region->m_fill_surfaces.surfaces.begin(), + region->m_fill_surfaces.surfaces.end(), + [](const Surface &s) { return s.empty(); }), + region->m_fill_surfaces.surfaces.end()); } } } From 9054aa74b34405439f535a99a4b9b560b930f5fb Mon Sep 17 00:00:00 2001 From: PavelMikus Date: Wed, 15 Feb 2023 13:32:19 +0100 Subject: [PATCH 032/110] Filter out tiny areas, Fix issue where partial surfaces were cleared and empty when over solid infill --- src/libslic3r/PrintObject.cpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/libslic3r/PrintObject.cpp b/src/libslic3r/PrintObject.cpp index 8b72c292ab..4e91581e4c 100644 --- a/src/libslic3r/PrintObject.cpp +++ b/src/libslic3r/PrintObject.cpp @@ -1695,7 +1695,7 @@ void PrintObject::bridge_over_infill() intersection(bridged_area, lower_layers_sparse_infill); // cut off parts which are not over sparse infill - material overflow - if (bridged_area.empty()) { + if (shrink(bridged_area, 3.0 * flow.scaled_width()).empty()) { continue; } @@ -1980,6 +1980,9 @@ void PrintObject::bridge_over_infill() for (Surface &surface : region->m_fill_surfaces.surfaces) { if (s.original_surface == &surface) { Surface tmp(surface, {}); + for (const ExPolygon &expoly : diff_ex(surface.expolygon, s.new_polys)) { + new_surfaces.emplace_back(tmp, expoly); + } tmp.surface_type = stInternalBridge; tmp.bridge_angle = s.bridge_angle; for (const ExPolygon &expoly : union_ex(s.new_polys)) { From 2b0a7ccb2c3c6aa9f4b57839878ca538a2f71fbf Mon Sep 17 00:00:00 2001 From: PavelMikus Date: Wed, 15 Feb 2023 13:43:20 +0100 Subject: [PATCH 033/110] invalidate posPrepareInfill on pattern change, since that influences anchoring filter out sparse infill with 100 density from bridgeable areas --- src/libslic3r/PrintObject.cpp | 23 +++++++++++------------ 1 file changed, 11 insertions(+), 12 deletions(-) diff --git a/src/libslic3r/PrintObject.cpp b/src/libslic3r/PrintObject.cpp index 4e91581e4c..63ce832d8b 100644 --- a/src/libslic3r/PrintObject.cpp +++ b/src/libslic3r/PrintObject.cpp @@ -718,15 +718,7 @@ bool PrintObject::invalidate_state_by_config_options( || opt_key == "first_layer_extrusion_width") { steps.emplace_back(posInfill); } else if (opt_key == "fill_pattern") { - steps.emplace_back(posInfill); - - const auto *old_fill_pattern = old_config.option>(opt_key); - const auto *new_fill_pattern = new_config.option>(opt_key); - assert(old_fill_pattern && new_fill_pattern); - // We need to recalculate infill surfaces when infill_only_where_needed is enabled, and we are switching from - // the Lightning infill to another infill or vice versa. - if (m_config.infill_only_where_needed && (new_fill_pattern->value == ipLightning || old_fill_pattern->value == ipLightning)) - steps.emplace_back(posPrepareInfill); + steps.emplace_back(posPrepareInfill); } else if (opt_key == "fill_density") { // One likely wants to reslice only when switching between zero infill to simulate boolean difference (subtracting volumes), // normal infill and 100% (solid) infill. @@ -1641,6 +1633,11 @@ void PrintObject::bridge_over_infill() continue; }; + if (expansion_space[candidates.first].empty()){ + // there is no expansion space to which can anchors on this island, skip + continue; + } + // Gather lower layers sparse infill areas, to depth defined by used bridge flow Polygons lower_layers_sparse_infill{}; double bottom_z = layer->print_z - max_bridge_flow_height[candidates.first] - EPSILON; @@ -1663,9 +1660,11 @@ void PrintObject::bridge_over_infill() for (size_t region_idx : regions_under_to_check) { const LayerRegion *region = po->get_layer(i)->get_region(region_idx); - for (const Surface *surface : region->fill_surfaces().filter_by_type(stInternal)) { - Polygons p = to_polygons(surface->expolygon); - lower_layers_sparse_infill.insert(lower_layers_sparse_infill.end(), p.begin(), p.end()); + if (region->region().config().fill_density.value < 100) { + for (const Surface *surface : region->fill_surfaces().filter_by_type(stInternal)) { + Polygons p = to_polygons(surface->expolygon); + lower_layers_sparse_infill.insert(lower_layers_sparse_infill.end(), p.begin(), p.end()); + } } } } From 6521b722749775b9c7809d38a5b454428e75932f Mon Sep 17 00:00:00 2001 From: PavelMikus Date: Wed, 15 Feb 2023 17:23:31 +0100 Subject: [PATCH 034/110] Fix problems with adaptive infills, but the anchoring itself is not used on them Fix briding angles for octagram and hilberts curve --- src/libslic3r/Fill/Fill.cpp | 8 ++++ src/libslic3r/PrintObject.cpp | 72 +++++++++++++++++++++++++++-------- 2 files changed, 64 insertions(+), 16 deletions(-) diff --git a/src/libslic3r/Fill/Fill.cpp b/src/libslic3r/Fill/Fill.cpp index 082cf93ccd..043a2e34ab 100644 --- a/src/libslic3r/Fill/Fill.cpp +++ b/src/libslic3r/Fill/Fill.cpp @@ -442,6 +442,14 @@ void Layer::make_fills(FillAdaptive::Octree* adaptive_fill_octree, FillAdaptive: #endif /* SLIC3R_DEBUG_SLICE_PROCESSING */ for (SurfaceFill &surface_fill : surface_fills) { + //skip patterns for which additional input is nullptr + switch (surface_fill.params.pattern) { + case ipLightning: if (lightning_generator == nullptr) continue; break; + case ipAdaptiveCubic: if (adaptive_fill_octree == nullptr) continue; break; + case ipSupportCubic: if (support_fill_octree == nullptr) continue; break; + default: break; + } + // Create the filler object. std::unique_ptr f = std::unique_ptr(Fill::new_from_type(surface_fill.params.pattern)); f->set_bounding_box(bbox); diff --git a/src/libslic3r/PrintObject.cpp b/src/libslic3r/PrintObject.cpp index 63ce832d8b..f1f377deb5 100644 --- a/src/libslic3r/PrintObject.cpp +++ b/src/libslic3r/PrintObject.cpp @@ -1638,6 +1638,17 @@ void PrintObject::bridge_over_infill() continue; } + auto region_has_anchorable_sparse_infill = [](const LayerRegion* layer_region) { + switch (layer_region->region().config().fill_pattern.value) { + case ipAdaptiveCubic: return false; + case ipSupportCubic: return false; + case ipLightning: return false; + default: break; + } + + return layer_region->region().config().fill_density.value < 100; + }; + // Gather lower layers sparse infill areas, to depth defined by used bridge flow Polygons lower_layers_sparse_infill{}; double bottom_z = layer->print_z - max_bridge_flow_height[candidates.first] - EPSILON; @@ -1660,7 +1671,7 @@ void PrintObject::bridge_over_infill() for (size_t region_idx : regions_under_to_check) { const LayerRegion *region = po->get_layer(i)->get_region(region_idx); - if (region->region().config().fill_density.value < 100) { + if (region_has_anchorable_sparse_infill(region)) { for (const Surface *surface : region->fill_surfaces().filter_by_type(stInternal)) { Polygons p = to_polygons(surface->expolygon); lower_layers_sparse_infill.insert(lower_layers_sparse_infill.end(), p.begin(), p.end()); @@ -1703,7 +1714,7 @@ void PrintObject::bridge_over_infill() closing(max_area, flow.scaled_width()); Polylines anchors = intersection_pl(lower_layer_polylines, max_area); - anchors = diff_pl(anchors, shrink(bridged_area, flow.scaled_width())); + anchors = diff_pl(anchors, bridged_area); Lines anchors_and_walls = to_lines(anchors); Lines tmp = to_lines(max_area); @@ -1716,9 +1727,9 @@ void PrintObject::bridge_over_infill() double bridging_angle = 0; { - AABBTreeLines::LinesDistancer lines_tree{anchors_and_walls}; + AABBTreeLines::LinesDistancer lines_tree{anchors.empty() ? anchors_and_walls : to_lines(anchors)}; - std::vector> directions_with_distances; + std::map counted_directions; for (const Polygon &p : bridged_area) { for (int point_idx = 0; point_idx < int(p.points.size()) - 1; ++point_idx) { Vec2d start = p.points[point_idx].cast(); @@ -1736,23 +1747,52 @@ void PrintObject::bridge_over_infill() angle -= PI; } angle += PI * 0.5; - directions_with_distances.emplace_back(angle, distance); + counted_directions[angle]++; } } } - double max_dist = directions_with_distances[0].second; - for (const auto &dir : directions_with_distances) { - max_dist = std::max(max_dist, dir.second); + + std::pair best_dir{0, 0}; + // sliding window accumulation + for (const auto &dir : counted_directions) { + int score_acc = 0; + double dir_acc = 0; + double window_start_angle = dir.first - PI * 0.2; + double window_end_angle = dir.first + PI * 0.2; + for (auto dirs_window = counted_directions.lower_bound(window_start_angle); + dirs_window != counted_directions.upper_bound(window_end_angle); dirs_window++) { + dir_acc += dirs_window->first * dirs_window->second; + score_acc += dirs_window->second; + } + // current span of directions is 0.5 PI to 1.5 PI (due to the aproach.). Edge values should also account for the + // opposite direction. + if (window_start_angle < 0.5 * PI) { + for (auto dirs_window = counted_directions.lower_bound(1.5 * PI - (0.5 * PI - window_start_angle)); + dirs_window != counted_directions.end(); dirs_window++) { + dir_acc += dirs_window->first * dirs_window->second; + score_acc += dirs_window->second; + } + } + if (window_start_angle > 1.5 * PI) { + for (auto dirs_window = counted_directions.begin(); + dirs_window != counted_directions.upper_bound(window_start_angle - 1.5 * PI); dirs_window++) { + dir_acc += dirs_window->first * dirs_window->second; + score_acc += dirs_window->second; + } + } + + if (score_acc > best_dir.second) { + best_dir = {dir_acc / score_acc, score_acc}; + } } - double acc = 0; - for (const auto &dir : directions_with_distances) { - bridging_angle += dir.first * (max_dist - dir.second); - acc += (max_dist - dir.second); - } - if (acc <= EPSILON || bridging_angle == 0) { + bridging_angle = best_dir.first; + if (bridging_angle == 0) { bridging_angle = 0.001; - } else { - bridging_angle /= acc; + } + switch (surface_to_region[candidate]->region().config().fill_pattern.value) { + case ipHilbertCurve: bridging_angle += 0.25 * PI; break; + case ipOctagramSpiral: bridging_angle += (1.0 / 16.0) * PI; break; + default: break; } } From 59c58397c925ab8fddb9b3d3b5e361e3115daf41 Mon Sep 17 00:00:00 2001 From: PavelMikus Date: Wed, 15 Feb 2023 18:43:41 +0100 Subject: [PATCH 035/110] Fix bug with special infill --- src/libslic3r/PrintObject.cpp | 46 +++++++++++++++++++++++------------ 1 file changed, 31 insertions(+), 15 deletions(-) diff --git a/src/libslic3r/PrintObject.cpp b/src/libslic3r/PrintObject.cpp index f1f377deb5..b7b5571de2 100644 --- a/src/libslic3r/PrintObject.cpp +++ b/src/libslic3r/PrintObject.cpp @@ -1525,7 +1525,7 @@ void PrintObject::discover_vertical_shells() } // for each region } // void PrintObject::discover_vertical_shells() -#define DEBUG_BRIDGE_OVER_INFILL +// #define DEBUG_BRIDGE_OVER_INFILL #ifdef DEBUG_BRIDGE_OVER_INFILL template void debug_draw(std::string name, const T& a, const T& b, const T& c, const T& d) { @@ -1633,24 +1633,18 @@ void PrintObject::bridge_over_infill() continue; }; - if (expansion_space[candidates.first].empty()){ - // there is no expansion space to which can anchors on this island, skip - continue; - } - - auto region_has_anchorable_sparse_infill = [](const LayerRegion* layer_region) { + auto region_has_special_infill = [](const LayerRegion *layer_region) { switch (layer_region->region().config().fill_pattern.value) { - case ipAdaptiveCubic: return false; - case ipSupportCubic: return false; - case ipLightning: return false; - default: break; + case ipAdaptiveCubic: return true; + case ipSupportCubic: return true; + case ipLightning: return true; + default: return false; } - - return layer_region->region().config().fill_density.value < 100; }; // Gather lower layers sparse infill areas, to depth defined by used bridge flow Polygons lower_layers_sparse_infill{}; + Polygons special_infill{}; double bottom_z = layer->print_z - max_bridge_flow_height[candidates.first] - EPSILON; LayerSlice::Links current_links = candidates.first->overlaps_below; LayerSlice::Links next_links{}; @@ -1671,20 +1665,32 @@ void PrintObject::bridge_over_infill() for (size_t region_idx : regions_under_to_check) { const LayerRegion *region = po->get_layer(i)->get_region(region_idx); - if (region_has_anchorable_sparse_infill(region)) { + if (region->region().config().fill_density.value < 100 && !region_has_special_infill(region)) { for (const Surface *surface : region->fill_surfaces().filter_by_type(stInternal)) { Polygons p = to_polygons(surface->expolygon); lower_layers_sparse_infill.insert(lower_layers_sparse_infill.end(), p.begin(), p.end()); } + } else if (region_has_special_infill(region)) { + for (const Surface *surface : region->fill_surfaces().filter_by_type(stInternal)) { + Polygons p = to_polygons(surface->expolygon); + special_infill.insert(special_infill.end(), p.begin(), p.end()); + } } } } current_links = next_links; } + + lower_layers_sparse_infill.insert(lower_layers_sparse_infill.end(), special_infill.begin(), special_infill.end()); + if (lower_layers_sparse_infill.empty()) { continue; } - lower_layers_sparse_infill = union_(lower_layers_sparse_infill); + + if (expansion_space[candidates.first].empty() && special_infill.empty()) { + // there is no expansion space to which can anchors expand on this island, skip + continue; + } Polygons expand_area; for (const Surface *sparse_infill : expansion_space[candidates.first]) { @@ -1714,6 +1720,16 @@ void PrintObject::bridge_over_infill() closing(max_area, flow.scaled_width()); Polylines anchors = intersection_pl(lower_layer_polylines, max_area); + if (!special_infill.empty()) { + auto part_over_special_infill = intersection(special_infill, bridged_area); + auto artificial_boundary = to_polylines(expand(part_over_special_infill, flow.scaled_width())); + anchors.insert(anchors.end(), artificial_boundary.begin(), artificial_boundary.end()); + +#ifdef DEBUG_BRIDGE_OVER_INFILL + debug_draw(std::to_string(lidx) + "special", to_lines(part_over_special_infill), to_lines(artificial_boundary), + to_lines(anchors), to_lines(expand_area)); +#endif + } anchors = diff_pl(anchors, bridged_area); Lines anchors_and_walls = to_lines(anchors); From ce73bce78012f84c0a53fae1cdb64af068b62643 Mon Sep 17 00:00:00 2001 From: PavelMikus Date: Thu, 16 Feb 2023 13:35:59 +0100 Subject: [PATCH 036/110] fixed multiple regions, fixed artefacts in morphological operations --- src/libslic3r/PrintObject.cpp | 134 ++++++++++++++++++++-------------- 1 file changed, 80 insertions(+), 54 deletions(-) diff --git a/src/libslic3r/PrintObject.cpp b/src/libslic3r/PrintObject.cpp index b7b5571de2..12fe10e4c5 100644 --- a/src/libslic3r/PrintObject.cpp +++ b/src/libslic3r/PrintObject.cpp @@ -1575,26 +1575,38 @@ void PrintObject::bridge_over_infill() std::unordered_map max_bridge_flow_height; std::unordered_map surface_to_region; for (const LayerSlice &slice : layer->lslices_ex) { - std::unordered_set regions_to_check; + AABBTreeLines::LinesDistancer slice_island_tree{to_lines(layer->lslices[int(&slice - layer->lslices_ex.data())])}; + std::unordered_set regions_to_check; + + // If there is composite island we have to check all regions on the layer. otherwise, only some regions are needed to be checked for (const LayerIsland &island : slice.islands) { - regions_to_check.insert(island.perimeters.region()); + regions_to_check.insert(layer->regions()[island.perimeters.region()]); if (!island.fill_expolygons_composite()) { - regions_to_check.insert(island.fill_region_id); + regions_to_check.insert(layer->regions()[island.fill_region_id]); + } else { + for (const auto& r : layer->regions()) { + regions_to_check.insert(r); + } + break; } } - for (size_t region_idx : regions_to_check) { - const LayerRegion *region = layer->get_region(region_idx); + for ( const LayerRegion *region : regions_to_check) { SurfacesPtr region_internal_solids = region->fill_surfaces().filter_by_type(stInternalSolid); // remove very small solid infills, usually not worth it and many of them may not even contain extrusions in the end. - region_internal_solids.erase(std::remove_if(region_internal_solids.begin(), region_internal_solids.end(), - [region](const Surface *s) { - float min_width = - float(region->bridging_flow(frSolidInfill).scaled_width()) * 3.f; - return offset_ex({s->expolygon}, -min_width).empty(); - }), - region_internal_solids.end()); + region_internal_solids + .erase(std::remove_if(region_internal_solids.begin(), region_internal_solids.end(), + [slice_island_tree, region](const Surface *s) { + float min_width = float(region->bridging_flow(frSolidInfill).scaled_spacing()) * 3.f; + if (slice_island_tree.distance_from_lines(s->expolygon.contour.first_point()) > + min_width / 3.0) { + return true; + } + return area(offset_ex({s->expolygon}, -min_width)) < + min_width * region->bridging_flow(frSolidInfill).scaled_width(); + }), + region_internal_solids.end()); if (!region_internal_solids.empty()) { max_bridge_flow_height[&slice] = std::max(max_bridge_flow_height[&slice], region->bridging_flow(frSolidInfill).height()); @@ -1645,46 +1657,56 @@ void PrintObject::bridge_over_infill() // Gather lower layers sparse infill areas, to depth defined by used bridge flow Polygons lower_layers_sparse_infill{}; Polygons special_infill{}; - double bottom_z = layer->print_z - max_bridge_flow_height[candidates.first] - EPSILON; - LayerSlice::Links current_links = candidates.first->overlaps_below; - LayerSlice::Links next_links{}; - for (int i = int(lidx) - 1; i >= 0; --i) { - // Stop iterating if layer is lower than bottom_z. - if (po->get_layer(i)->print_z < bottom_z) - break; - for (const auto &link : current_links) { - const LayerSlice &slice_below = po->get_layer(i)->lslices_ex[link.slice_idx]; - next_links.insert(next_links.end(), slice_below.overlaps_below.begin(), slice_below.overlaps_below.end()); - std::unordered_set regions_under_to_check; - for (const LayerIsland &island : slice_below.islands) { - regions_under_to_check.insert(island.perimeters.region()); - if (!island.fill_expolygons_composite()) { - regions_under_to_check.insert(island.fill_region_id); + { + double bottom_z = layer->print_z - max_bridge_flow_height[candidates.first] - EPSILON; + LayerSlice::Links current_links = candidates.first->overlaps_below; + LayerSlice::Links next_links{}; + for (int i = int(lidx) - 1; i >= 0; --i) { + // Stop iterating if layer is lower than bottom_z. + if (po->get_layer(i)->print_z < bottom_z) + break; + for (const auto &link : current_links) { + const LayerSlice &slice_below = po->get_layer(i)->lslices_ex[link.slice_idx]; + next_links.insert(next_links.end(), slice_below.overlaps_below.begin(), slice_below.overlaps_below.end()); + std::unordered_set regions_under_to_check; + for (const LayerIsland &island : slice_below.islands) { + regions_under_to_check.insert(po->get_layer(i)->regions()[island.perimeters.region()]); + if (!island.fill_expolygons_composite()) { + regions_under_to_check.insert(po->get_layer(i)->regions()[island.fill_region_id]); + } else { + for (const auto &r : po->get_layer(i)->regions()) { + regions_under_to_check.insert(r); + } + break; + } } - } - for (size_t region_idx : regions_under_to_check) { - const LayerRegion *region = po->get_layer(i)->get_region(region_idx); - if (region->region().config().fill_density.value < 100 && !region_has_special_infill(region)) { - for (const Surface *surface : region->fill_surfaces().filter_by_type(stInternal)) { - Polygons p = to_polygons(surface->expolygon); - lower_layers_sparse_infill.insert(lower_layers_sparse_infill.end(), p.begin(), p.end()); - } - } else if (region_has_special_infill(region)) { - for (const Surface *surface : region->fill_surfaces().filter_by_type(stInternal)) { - Polygons p = to_polygons(surface->expolygon); - special_infill.insert(special_infill.end(), p.begin(), p.end()); + for (const LayerRegion *region : regions_under_to_check) { + if (region->region().config().fill_density.value < 100 && !region_has_special_infill(region)) { + for (const Surface *surface : region->fill_surfaces().filter_by_type(stInternal)) { + Polygons p = to_polygons(surface->expolygon); + lower_layers_sparse_infill.insert(lower_layers_sparse_infill.end(), p.begin(), p.end()); + } + } else if (region_has_special_infill(region)) { + for (const Surface *surface : region->fill_surfaces().filter_by_type(stInternal)) { + Polygons p = to_polygons(surface->expolygon); + special_infill.insert(special_infill.end(), p.begin(), p.end()); + } } } } + current_links = next_links; } - current_links = next_links; - } - lower_layers_sparse_infill.insert(lower_layers_sparse_infill.end(), special_infill.begin(), special_infill.end()); + lower_layers_sparse_infill = intersection(lower_layers_sparse_infill, + layer->lslices[int(candidates.first - layer->lslices_ex.data())]); + special_infill = intersection(special_infill, layer->lslices[int(candidates.first - layer->lslices_ex.data())]); - if (lower_layers_sparse_infill.empty()) { - continue; + lower_layers_sparse_infill.insert(lower_layers_sparse_infill.end(), special_infill.begin(), special_infill.end()); + + if (lower_layers_sparse_infill.empty()) { + continue; + } } if (expansion_space[candidates.first].empty() && special_infill.empty()) { @@ -1711,18 +1733,18 @@ void PrintObject::bridge_over_infill() intersection(bridged_area, lower_layers_sparse_infill); // cut off parts which are not over sparse infill - material overflow - if (shrink(bridged_area, 3.0 * flow.scaled_width()).empty()) { + if (area(shrink(bridged_area, 3.0 * flow.scaled_spacing())) < 9.0 * flow.scaled_spacing() * flow.scaled_spacing()) { continue; } Polygons max_area = expand_area; max_area.insert(max_area.end(), bridged_area.begin(), bridged_area.end()); - closing(max_area, flow.scaled_width()); + max_area = closing(max_area, flow.scaled_spacing()); Polylines anchors = intersection_pl(lower_layer_polylines, max_area); if (!special_infill.empty()) { auto part_over_special_infill = intersection(special_infill, bridged_area); - auto artificial_boundary = to_polylines(expand(part_over_special_infill, flow.scaled_width())); + auto artificial_boundary = to_polylines(expand(part_over_special_infill, 0.5 * flow.scaled_width())); anchors.insert(anchors.end(), artificial_boundary.begin(), artificial_boundary.end()); #ifdef DEBUG_BRIDGE_OVER_INFILL @@ -1990,11 +2012,11 @@ void PrintObject::bridge_over_infill() polygons_rotate(expanded_bridged_area, -aligning_angle); expanded_bridged_area = intersection(expanded_bridged_area, max_area); + expanded_bridged_area = opening(expanded_bridged_area, flow.scaled_spacing()); expand_area = diff(expand_area, expanded_bridged_area); expanded_briding_surfaces[candidates.first].emplace_back(candidate, expanded_bridged_area, surface_to_region[candidate], bridging_angle); - #ifdef DEBUG_BRIDGE_OVER_INFILL debug_draw(std::to_string(lidx) + "cadidate_added", to_lines(expanded_bridged_area), to_lines(bridged_area), to_lines(max_area), to_lines(expand_area)); @@ -2014,11 +2036,16 @@ void PrintObject::bridge_over_infill() for (const LayerSlice &slice : layer->lslices_ex) { if (const auto &modified_surfaces = expanded_briding_surfaces.find(&slice); modified_surfaces != expanded_briding_surfaces.end()) { - std::unordered_set regions_to_check; + std::unordered_set regions_to_check; for (const LayerIsland &island : slice.islands) { - regions_to_check.insert(island.perimeters.region()); + regions_to_check.insert(layer->regions()[island.perimeters.region()]); if (!island.fill_expolygons_composite()) { - regions_to_check.insert(island.fill_region_id); + regions_to_check.insert(layer->regions()[island.fill_region_id]); + } else { + for (LayerRegion *r : layer->regions()) { + regions_to_check.insert(r); + } + break; } } @@ -2027,9 +2054,8 @@ void PrintObject::bridge_over_infill() cut_from_infill.insert(cut_from_infill.end(), surface.new_polys.begin(), surface.new_polys.end()); } - for (size_t region_idx : regions_to_check) { - LayerRegion *region = layer->get_region(region_idx); - Surfaces new_surfaces; + for (LayerRegion *region : regions_to_check) { + Surfaces new_surfaces; for (const ModifiedSurface &s : modified_surfaces->second) { for (Surface &surface : region->m_fill_surfaces.surfaces) { From 7c603a53e0e30daa42561e9c74ef30c6ae02860d Mon Sep 17 00:00:00 2001 From: PavelMikus Date: Thu, 16 Feb 2023 16:11:23 +0100 Subject: [PATCH 037/110] fix first layer over sparse sometimes not labeled as bridge when there was no expand space --- src/libslic3r/PrintObject.cpp | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/src/libslic3r/PrintObject.cpp b/src/libslic3r/PrintObject.cpp index 12fe10e4c5..d4dcd62139 100644 --- a/src/libslic3r/PrintObject.cpp +++ b/src/libslic3r/PrintObject.cpp @@ -1560,10 +1560,10 @@ void PrintObject::bridge_over_infill() double bridge_angle; }; - std::unordered_map> expanded_briding_surfaces; + std::unordered_map> briding_surfaces; tbb::parallel_for(tbb::blocked_range(0, this->layers().size()), [po = this, - &expanded_briding_surfaces](tbb::blocked_range r) { + &briding_surfaces](tbb::blocked_range r) { for (size_t lidx = r.begin(); lidx < r.end(); lidx++) { const Layer *layer = po->get_layer(lidx); @@ -1710,7 +1710,11 @@ void PrintObject::bridge_over_infill() } if (expansion_space[candidates.first].empty() && special_infill.empty()) { - // there is no expansion space to which can anchors expand on this island, skip + // there is no expansion space to which can anchors expand on this island, add back original polygons and skip the island + for (const Surface *candidate : candidates.second) { + briding_surfaces[candidates.first].emplace_back(candidate, to_polygons(candidate->expolygon), + surface_to_region[candidate], 0); + } continue; } @@ -2015,7 +2019,7 @@ void PrintObject::bridge_over_infill() expanded_bridged_area = opening(expanded_bridged_area, flow.scaled_spacing()); expand_area = diff(expand_area, expanded_bridged_area); - expanded_briding_surfaces[candidates.first].emplace_back(candidate, expanded_bridged_area, surface_to_region[candidate], + briding_surfaces[candidates.first].emplace_back(candidate, expanded_bridged_area, surface_to_region[candidate], bridging_angle); #ifdef DEBUG_BRIDGE_OVER_INFILL debug_draw(std::to_string(lidx) + "cadidate_added", to_lines(expanded_bridged_area), to_lines(bridged_area), @@ -2029,13 +2033,13 @@ void PrintObject::bridge_over_infill() BOOST_LOG_TRIVIAL(info) << "Bridge over infill - Directions and expanded surfaces computed" << log_memory_info(); tbb::parallel_for(tbb::blocked_range(0, this->layers().size()), [po = this, - &expanded_briding_surfaces](tbb::blocked_range r) { + &briding_surfaces](tbb::blocked_range r) { for (size_t lidx = r.begin(); lidx < r.end(); lidx++) { Layer *layer = po->get_layer(lidx); for (const LayerSlice &slice : layer->lslices_ex) { - if (const auto &modified_surfaces = expanded_briding_surfaces.find(&slice); - modified_surfaces != expanded_briding_surfaces.end()) { + if (const auto &modified_surfaces = briding_surfaces.find(&slice); + modified_surfaces != briding_surfaces.end()) { std::unordered_set regions_to_check; for (const LayerIsland &island : slice.islands) { regions_to_check.insert(layer->regions()[island.perimeters.region()]); From 739bee354d599c6c40530e7c2dad39be50789b1c Mon Sep 17 00:00:00 2001 From: PavelMikus Date: Thu, 16 Feb 2023 16:33:34 +0100 Subject: [PATCH 038/110] lower angles span so that briding direction is better --- src/libslic3r/PrintObject.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/libslic3r/PrintObject.cpp b/src/libslic3r/PrintObject.cpp index d4dcd62139..6bf8d0e551 100644 --- a/src/libslic3r/PrintObject.cpp +++ b/src/libslic3r/PrintObject.cpp @@ -1799,8 +1799,8 @@ void PrintObject::bridge_over_infill() for (const auto &dir : counted_directions) { int score_acc = 0; double dir_acc = 0; - double window_start_angle = dir.first - PI * 0.2; - double window_end_angle = dir.first + PI * 0.2; + double window_start_angle = dir.first - PI * 0.1; + double window_end_angle = dir.first + PI * 0.1; for (auto dirs_window = counted_directions.lower_bound(window_start_angle); dirs_window != counted_directions.upper_bound(window_end_angle); dirs_window++) { dir_acc += dirs_window->first * dirs_window->second; From 61e623eda9c9e98c874f230aeab7e2ea1e1d998e Mon Sep 17 00:00:00 2001 From: Pavel Mikus Date: Tue, 21 Feb 2023 16:44:03 +0100 Subject: [PATCH 039/110] fix issues after merge with new ensuring branch --- src/libslic3r/PrintObject.cpp | 70 ++++++++++++++++++----------------- 1 file changed, 37 insertions(+), 33 deletions(-) diff --git a/src/libslic3r/PrintObject.cpp b/src/libslic3r/PrintObject.cpp index 73a48f2a7d..8c1ac78f5a 100644 --- a/src/libslic3r/PrintObject.cpp +++ b/src/libslic3r/PrintObject.cpp @@ -1593,10 +1593,10 @@ void PrintObject::bridge_over_infill() double bridge_angle; }; - std::unordered_map> briding_surfaces; + std::unordered_map> bridging_surfaces; tbb::parallel_for(tbb::blocked_range(0, this->layers().size()), [po = this, - &briding_surfaces](tbb::blocked_range r) { + &bridging_surfaces](tbb::blocked_range r) { for (size_t lidx = r.begin(); lidx < r.end(); lidx++) { const Layer *layer = po->get_layer(lidx); @@ -1628,21 +1628,19 @@ void PrintObject::bridge_over_infill() SurfacesPtr region_internal_solids = region->fill_surfaces().filter_by_type(stInternalSolid); // remove very small solid infills, usually not worth it and many of them may not even contain extrusions in the end. - region_internal_solids - .erase(std::remove_if(region_internal_solids.begin(), region_internal_solids.end(), - [slice_island_tree, region](const Surface *s) { - float min_width = float(region->bridging_flow(frSolidInfill).scaled_spacing()) * 3.f; - if (slice_island_tree.distance_from_lines(s->expolygon.contour.first_point()) > - min_width / 3.0) { - return true; - } - return area(offset_ex({s->expolygon}, -min_width)) < - min_width * region->bridging_flow(frSolidInfill).scaled_width(); - }), - region_internal_solids.end()); + region_internal_solids.erase(std::remove_if(region_internal_solids.begin(), region_internal_solids.end(), + [slice_island_tree, region](const Surface *s) { + float spacing = float( + region->bridging_flow(frSolidInfill, true).scaled_spacing()); + if (slice_island_tree.outside(s->expolygon.contour.first_point()) > 0) { + return true; + } + return offset({s->expolygon}, -3.0 * spacing).empty(); + }), + region_internal_solids.end()); if (!region_internal_solids.empty()) { max_bridge_flow_height[&slice] = std::max(max_bridge_flow_height[&slice], - region->bridging_flow(frSolidInfill).height()); + region->bridging_flow(frSolidInfill, true).height()); } for (const Surface *s : region_internal_solids) { surface_to_region[s] = region; @@ -1688,8 +1686,9 @@ void PrintObject::bridge_over_infill() }; // Gather lower layers sparse infill areas, to depth defined by used bridge flow - Polygons lower_layers_sparse_infill{}; - Polygons special_infill{}; + Polygons lower_layers_sparse_infill{}; + Polygons special_infill{}; + Polygons not_sparse_infill{}; { double bottom_z = layer->print_z - max_bridge_flow_height[candidates.first] - EPSILON; LayerSlice::Links current_links = candidates.first->overlaps_below; @@ -1715,15 +1714,18 @@ void PrintObject::bridge_over_infill() } for (const LayerRegion *region : regions_under_to_check) { - if (region->region().config().fill_density.value < 100 && !region_has_special_infill(region)) { - for (const Surface *surface : region->fill_surfaces().filter_by_type(stInternal)) { - Polygons p = to_polygons(surface->expolygon); + bool has_low_density = region->region().config().fill_density.value < 100; + bool has_special_infill = region_has_special_infill(region); + for (const Surface &surface : region->fill_surfaces()) { + if (surface.surface_type == stInternal && has_low_density && !has_special_infill) { + Polygons p = to_polygons(surface.expolygon); lower_layers_sparse_infill.insert(lower_layers_sparse_infill.end(), p.begin(), p.end()); - } - } else if (region_has_special_infill(region)) { - for (const Surface *surface : region->fill_surfaces().filter_by_type(stInternal)) { - Polygons p = to_polygons(surface->expolygon); + } else if (surface.surface_type == stInternal && has_low_density && has_special_infill) { + Polygons p = to_polygons(surface.expolygon); special_infill.insert(special_infill.end(), p.begin(), p.end()); + } else { + Polygons p = to_polygons(surface.expolygon); + not_sparse_infill.insert(not_sparse_infill.end(), p.begin(), p.end()); } } } @@ -1733,11 +1735,13 @@ void PrintObject::bridge_over_infill() lower_layers_sparse_infill = intersection(lower_layers_sparse_infill, layer->lslices[int(candidates.first - layer->lslices_ex.data())]); + lower_layers_sparse_infill = diff(lower_layers_sparse_infill, not_sparse_infill); special_infill = intersection(special_infill, layer->lslices[int(candidates.first - layer->lslices_ex.data())]); + special_infill = diff(special_infill, not_sparse_infill); lower_layers_sparse_infill.insert(lower_layers_sparse_infill.end(), special_infill.begin(), special_infill.end()); - if (lower_layers_sparse_infill.empty()) { + if (shrink(lower_layers_sparse_infill, 6.0 * scaled(max_bridge_flow_height[candidates.first])).empty()) { continue; } } @@ -1745,7 +1749,7 @@ void PrintObject::bridge_over_infill() if (expansion_space[candidates.first].empty() && special_infill.empty()) { // there is no expansion space to which can anchors expand on this island, add back original polygons and skip the island for (const Surface *candidate : candidates.second) { - briding_surfaces[candidates.first].emplace_back(candidate, to_polygons(candidate->expolygon), + bridging_surfaces[candidates.first].emplace_back(candidate, to_polygons(candidate->expolygon), surface_to_region[candidate], 0); } continue; @@ -1763,14 +1767,14 @@ void PrintObject::bridge_over_infill() // bridging. These areas we then expand (within the surrounding sparse infill only!) // to touch the infill polylines on previous layer. for (const Surface *candidate : candidates.second) { - const Flow &flow = surface_to_region[candidate]->bridging_flow(frSolidInfill); + const Flow &flow = surface_to_region[candidate]->bridging_flow(frSolidInfill, true); assert(candidate->surface_type == stInternalSolid); - Polygons bridged_area = to_polygons(candidate->expolygon); + Polygons bridged_area = expand(to_polygons(candidate->expolygon), flow.scaled_spacing()); bridged_area = intersection(bridged_area, lower_layers_sparse_infill); // cut off parts which are not over sparse infill - material overflow - if (area(shrink(bridged_area, 3.0 * flow.scaled_spacing())) < 9.0 * flow.scaled_spacing() * flow.scaled_spacing()) { + if (shrink(bridged_area, 4.0 * flow.scaled_spacing()).empty()) { continue; } @@ -2052,7 +2056,7 @@ void PrintObject::bridge_over_infill() expanded_bridged_area = opening(expanded_bridged_area, flow.scaled_spacing()); expand_area = diff(expand_area, expanded_bridged_area); - briding_surfaces[candidates.first].emplace_back(candidate, expanded_bridged_area, surface_to_region[candidate], + bridging_surfaces[candidates.first].emplace_back(candidate, expanded_bridged_area, surface_to_region[candidate], bridging_angle); #ifdef DEBUG_BRIDGE_OVER_INFILL debug_draw(std::to_string(lidx) + "cadidate_added", to_lines(expanded_bridged_area), to_lines(bridged_area), @@ -2066,13 +2070,13 @@ void PrintObject::bridge_over_infill() BOOST_LOG_TRIVIAL(info) << "Bridge over infill - Directions and expanded surfaces computed" << log_memory_info(); tbb::parallel_for(tbb::blocked_range(0, this->layers().size()), [po = this, - &briding_surfaces](tbb::blocked_range r) { + &bridging_surfaces](tbb::blocked_range r) { for (size_t lidx = r.begin(); lidx < r.end(); lidx++) { Layer *layer = po->get_layer(lidx); for (const LayerSlice &slice : layer->lslices_ex) { - if (const auto &modified_surfaces = briding_surfaces.find(&slice); - modified_surfaces != briding_surfaces.end()) { + if (const auto &modified_surfaces = bridging_surfaces.find(&slice); + modified_surfaces != bridging_surfaces.end()) { std::unordered_set regions_to_check; for (const LayerIsland &island : slice.islands) { regions_to_check.insert(layer->regions()[island.perimeters.region()]); From 86729aa49946f6cbeabda83932fdd2346982f4ce Mon Sep 17 00:00:00 2001 From: Pavel Mikus Date: Tue, 21 Feb 2023 19:53:39 +0100 Subject: [PATCH 040/110] use vector instead of boost small vector --- src/libslic3r/PrintObject.cpp | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/libslic3r/PrintObject.cpp b/src/libslic3r/PrintObject.cpp index 8c1ac78f5a..1d66492807 100644 --- a/src/libslic3r/PrintObject.cpp +++ b/src/libslic3r/PrintObject.cpp @@ -1690,9 +1690,11 @@ void PrintObject::bridge_over_infill() Polygons special_infill{}; Polygons not_sparse_infill{}; { - double bottom_z = layer->print_z - max_bridge_flow_height[candidates.first] - EPSILON; - LayerSlice::Links current_links = candidates.first->overlaps_below; - LayerSlice::Links next_links{}; + double bottom_z = layer->print_z - max_bridge_flow_height[candidates.first] - EPSILON; + std::vector current_links{}; + current_links.insert(current_links.end(), candidates.first->overlaps_below.begin(), + candidates.first->overlaps_below.end()); + std::vector next_links{}; for (int i = int(lidx) - 1; i >= 0; --i) { // Stop iterating if layer is lower than bottom_z. if (po->get_layer(i)->print_z < bottom_z) From 5656d4ea6dda79cc7d2126744c2f63389bdc86a1 Mon Sep 17 00:00:00 2001 From: PavelMikus Date: Fri, 24 Feb 2023 11:56:24 +0100 Subject: [PATCH 041/110] Fix crashes. Set common bridging angle to touching surfaces, otherwise bad stuff happens. --- src/libslic3r/PrintObject.cpp | 24 ++++++++++++++++++++++-- 1 file changed, 22 insertions(+), 2 deletions(-) diff --git a/src/libslic3r/PrintObject.cpp b/src/libslic3r/PrintObject.cpp index 1d66492807..679d51cc88 100644 --- a/src/libslic3r/PrintObject.cpp +++ b/src/libslic3r/PrintObject.cpp @@ -1671,7 +1671,7 @@ void PrintObject::bridge_over_infill() } } - for (const std::pair candidates : bridging_surface_candidates) { + for (std::pair candidates : bridging_surface_candidates) { if (candidates.second.empty()) { continue; }; @@ -1733,6 +1733,7 @@ void PrintObject::bridge_over_infill() } } current_links = next_links; + next_links.clear(); } lower_layers_sparse_infill = intersection(lower_layers_sparse_infill, @@ -1764,6 +1765,18 @@ void PrintObject::bridge_over_infill() expand_area.insert(expand_area.end(), a.begin(), a.end()); } + // Presort the candidate polygons. This will help choose the same angle for neighbournig surfaces, that would otherwise + // compete over anchoring sparse infill lines, leaving one area unachored + std::sort(candidates.second.begin(), candidates.second.end(), [](const Surface* left, const Surface* right){ + auto a = get_extents(left->expolygon); + auto b = get_extents(right->expolygon); + + if (a.min.x() == b.min.x()) { + return a.min.y() < b.min.y(); + }; + return a.min.x() < b.min.x(); + }); + // Lower layers sparse infill sections gathered // now we can intersected them with bridging surface candidates to get actual areas that need and can accumulate // bridging. These areas we then expand (within the surrounding sparse infill only!) @@ -1807,7 +1820,14 @@ void PrintObject::bridge_over_infill() #endif double bridging_angle = 0; - { + Polygons tmp_expanded_area = expand(bridged_area, 3.0 * flow.scaled_spacing()); + for (const ModifiedSurface& s : bridging_surfaces[candidates.first]) { + if (!intersection(s.new_polys, tmp_expanded_area).empty()) { + bridging_angle = s.bridge_angle; + break; + } + } + if (bridging_angle == 0) { AABBTreeLines::LinesDistancer lines_tree{anchors.empty() ? anchors_and_walls : to_lines(anchors)}; std::map counted_directions; From feb9310fe319962086ca3e5510a867783a133e60 Mon Sep 17 00:00:00 2001 From: PavelMikus Date: Fri, 24 Feb 2023 14:38:08 +0100 Subject: [PATCH 042/110] Support tiny floating islands inside sparse infill. You never know what can grow out of them. --- src/libslic3r/PrintObject.cpp | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/src/libslic3r/PrintObject.cpp b/src/libslic3r/PrintObject.cpp index 679d51cc88..1dbffa5335 100644 --- a/src/libslic3r/PrintObject.cpp +++ b/src/libslic3r/PrintObject.cpp @@ -1627,15 +1627,15 @@ void PrintObject::bridge_over_infill() for ( const LayerRegion *region : regions_to_check) { SurfacesPtr region_internal_solids = region->fill_surfaces().filter_by_type(stInternalSolid); - // remove very small solid infills, usually not worth it and many of them may not even contain extrusions in the end. + // filter out surfaces not from this island... TODO sotre this info in the Z-Graph, so that this filtering is not needed + // NOTE: we are keeping even very small internal ensuring overhangs here. The aim is to later differentiate between expanding wall ensuring regions + // where briding them would be conterproductive, and small ensuring islands that expand into large ones, where bridging is quite necessary region_internal_solids.erase(std::remove_if(region_internal_solids.begin(), region_internal_solids.end(), - [slice_island_tree, region](const Surface *s) { - float spacing = float( - region->bridging_flow(frSolidInfill, true).scaled_spacing()); + [slice_island_tree](const Surface *s) { if (slice_island_tree.outside(s->expolygon.contour.first_point()) > 0) { return true; } - return offset({s->expolygon}, -3.0 * spacing).empty(); + return false; }), region_internal_solids.end()); if (!region_internal_solids.empty()) { @@ -1744,7 +1744,7 @@ void PrintObject::bridge_over_infill() lower_layers_sparse_infill.insert(lower_layers_sparse_infill.end(), special_infill.begin(), special_infill.end()); - if (shrink(lower_layers_sparse_infill, 6.0 * scaled(max_bridge_flow_height[candidates.first])).empty()) { + if (shrink(lower_layers_sparse_infill, 3.0 * scaled(max_bridge_flow_height[candidates.first])).empty()) { continue; } } @@ -1784,12 +1784,16 @@ void PrintObject::bridge_over_infill() for (const Surface *candidate : candidates.second) { const Flow &flow = surface_to_region[candidate]->bridging_flow(frSolidInfill, true); assert(candidate->surface_type == stInternalSolid); - Polygons bridged_area = expand(to_polygons(candidate->expolygon), flow.scaled_spacing()); + Polygons bridged_area = expand(to_polygons(candidate->expolygon), flow.scaled_spacing()); + Polygons infill_region = to_polygons(surface_to_region[candidate]->fill_expolygons()); + bool touches_perimeter = !diff(bridged_area, infill_region).empty(); + bool touches_solid_region_under = !intersection(bridged_area, not_sparse_infill).empty(); + bridged_area = intersection(bridged_area, lower_layers_sparse_infill); // cut off parts which are not over sparse infill - material overflow - if (shrink(bridged_area, 4.0 * flow.scaled_spacing()).empty()) { + if ((touches_perimeter || touches_solid_region_under) && shrink(bridged_area, 5.0 * flow.scaled_spacing()).empty()) { continue; } From e4910381b465b5907e6a3c0914674bd365b7c9d7 Mon Sep 17 00:00:00 2001 From: PavelMikus Date: Fri, 24 Feb 2023 16:47:07 +0100 Subject: [PATCH 043/110] optimize the brdige over infill by extractng only the sparse infill lines from previous layer --- src/libslic3r/Fill/Fill.cpp | 88 +++++++++++++++++++++++++++++++++++ src/libslic3r/Layer.hpp | 1 + src/libslic3r/PrintObject.cpp | 17 ++----- 3 files changed, 93 insertions(+), 13 deletions(-) diff --git a/src/libslic3r/Fill/Fill.cpp b/src/libslic3r/Fill/Fill.cpp index b502a65b43..9716ecc208 100644 --- a/src/libslic3r/Fill/Fill.cpp +++ b/src/libslic3r/Fill/Fill.cpp @@ -643,6 +643,94 @@ void Layer::make_fills(FillAdaptive::Octree* adaptive_fill_octree, FillAdaptive: #endif } +Polylines Layer::generate_sparse_infill_polylines_for_anchoring() const +{ + std::vector surface_fills = group_fills(*this); + const Slic3r::BoundingBox bbox = this->object()->bounding_box(); + const auto resolution = this->object()->print()->config().gcode_resolution.value; + + Polylines sparse_infill_polylines{}; + + for (SurfaceFill &surface_fill : surface_fills) { + // skip patterns for which additional input is nullptr + switch (surface_fill.params.pattern) { + case ipLightning: continue; break; + case ipAdaptiveCubic: continue; break; + case ipSupportCubic: continue; break; + case ipCount: continue; break; + case ipSupportBase: continue; break; + case ipEnsuring: continue; break; + case ipRectilinear: + case ipMonotonic: + case ipMonotonicLines: + case ipAlignedRectilinear: + case ipGrid: + case ipTriangles: + case ipStars: + case ipCubic: + case ipLine: + case ipConcentric: + case ipHoneycomb: + case ip3DHoneycomb: + case ipGyroid: + case ipHilbertCurve: + case ipArchimedeanChords: + case ipOctagramSpiral: break; + } + + // Create the filler object. + std::unique_ptr f = std::unique_ptr(Fill::new_from_type(surface_fill.params.pattern)); + f->set_bounding_box(bbox); + f->layer_id = this->id(); + f->z = this->print_z; + f->angle = surface_fill.params.angle; + // f->adapt_fill_octree = (surface_fill.params.pattern == ipSupportCubic) ? support_fill_octree : adaptive_fill_octree; + f->print_config = &this->object()->print()->config(); + f->print_object_config = &this->object()->config(); + + // calculate flow spacing for infill pattern generation + double link_max_length = 0.; + if (!surface_fill.params.bridge) { +#if 0 + link_max_length = layerm.region()->config().get_abs_value(surface.is_external() ? "external_fill_link_max_length" : "fill_link_max_length", flow.spacing()); +// printf("flow spacing: %f, is_external: %d, link_max_length: %lf\n", flow.spacing(), int(surface.is_external()), link_max_length); +#else + if (surface_fill.params.density > 80.) // 80% + link_max_length = 3. * f->spacing; +#endif + } + + // Maximum length of the perimeter segment linking two infill lines. + f->link_max_length = (coord_t) scale_(link_max_length); + // Used by the concentric infill pattern to clip the loops to create extrusion paths. + f->loop_clipping = coord_t(scale_(surface_fill.params.flow.nozzle_diameter()) * LOOP_CLIPPING_LENGTH_OVER_NOZZLE_DIAMETER); + + LayerRegion &layerm = *m_regions[surface_fill.region_id]; + + // apply half spacing using this flow's own spacing and generate infill + FillParams params; + params.density = float(0.01 * surface_fill.params.density); + params.dont_adjust = false; // surface_fill.params.dont_adjust; + params.anchor_length = surface_fill.params.anchor_length; + params.anchor_length_max = surface_fill.params.anchor_length_max; + params.resolution = resolution; + params.use_arachne = false; + params.layer_height = layerm.layer()->height; + + for (ExPolygon &expoly : surface_fill.expolygons) { + // Spacing is modified by the filler to indicate adjustments. Reset it for each expolygon. + f->spacing = surface_fill.params.spacing; + surface_fill.surface.expolygon = std::move(expoly); + try { + Polylines polylines = f->fill_surface(&surface_fill.surface, params); + sparse_infill_polylines.insert(sparse_infill_polylines.end(), polylines.begin(), polylines.end()); + } catch (InfillFailedException &) {} + } + } + + return sparse_infill_polylines; +} + // Create ironing extrusions over top surfaces. void Layer::make_ironing() { diff --git a/src/libslic3r/Layer.hpp b/src/libslic3r/Layer.hpp index d2bdc1088c..a59c029b8f 100644 --- a/src/libslic3r/Layer.hpp +++ b/src/libslic3r/Layer.hpp @@ -369,6 +369,7 @@ public: // Phony version of make_fills() without parameters for Perl integration only. void make_fills() { this->make_fills(nullptr, nullptr, nullptr); } void make_fills(FillAdaptive::Octree* adaptive_fill_octree, FillAdaptive::Octree* support_fill_octree, FillLightning::Generator* lightning_generator); + Polylines generate_sparse_infill_polylines_for_anchoring() const; void make_ironing(); void export_region_slices_to_svg(const char *path) const; diff --git a/src/libslic3r/PrintObject.cpp b/src/libslic3r/PrintObject.cpp index 1dbffa5335..ca9516d202 100644 --- a/src/libslic3r/PrintObject.cpp +++ b/src/libslic3r/PrintObject.cpp @@ -397,6 +397,7 @@ void PrintObject::infill() this->prepare_infill(); if (this->set_started(posInfill)) { + m_print->set_status(45, L("making infill")); auto [adaptive_fill_octree, support_fill_octree] = this->prepare_adaptive_infill_data(); auto lightning_generator = this->prepare_lightning_infill_data(); @@ -1658,18 +1659,8 @@ void PrintObject::bridge_over_infill() continue; } - // Now, temporarily fill the previous layer and extract the extrusions. - // TODO - the make_fills function does a lot of work, some of it is not needed (e.g. sorting the paths) - // It would be nice to have a function that only creates the fill polylines, ideally without modifying the global state - po->get_layer(lidx)->lower_layer->make_fills(nullptr, nullptr, nullptr); - Polylines lower_layer_polylines; - for (const LayerRegion *region : layer->lower_layer->m_regions) { - for (const ExtrusionEntity *ee : region->fills().entities) { - assert(ee->is_collection()); - auto region_polylines = dynamic_cast(ee)->as_polylines(); - lower_layer_polylines.insert(lower_layer_polylines.end(), region_polylines.begin(), region_polylines.end()); - } - } + // generate sparse infill polylines from lower layers to get anchorable polylines + Polylines lower_layer_polylines = po->get_layer(lidx)->lower_layer->generate_sparse_infill_polylines_for_anchoring(); for (std::pair candidates : bridging_surface_candidates) { if (candidates.second.empty()) { @@ -1786,7 +1777,7 @@ void PrintObject::bridge_over_infill() assert(candidate->surface_type == stInternalSolid); Polygons bridged_area = expand(to_polygons(candidate->expolygon), flow.scaled_spacing()); Polygons infill_region = to_polygons(surface_to_region[candidate]->fill_expolygons()); - bool touches_perimeter = !diff(bridged_area, infill_region).empty(); + bool touches_perimeter = !diff(bridged_area, infill_region).empty(); bool touches_solid_region_under = !intersection(bridged_area, not_sparse_infill).empty(); bridged_area = From 43a155c4762aa0606028b869e22540a67c0b0ab8 Mon Sep 17 00:00:00 2001 From: PavelMikus Date: Mon, 27 Feb 2023 12:52:46 +0100 Subject: [PATCH 044/110] improve the filters of regions that will be turned into bridges --- src/libslic3r/PrintObject.cpp | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/src/libslic3r/PrintObject.cpp b/src/libslic3r/PrintObject.cpp index ca9516d202..b67e68f60d 100644 --- a/src/libslic3r/PrintObject.cpp +++ b/src/libslic3r/PrintObject.cpp @@ -1777,15 +1777,22 @@ void PrintObject::bridge_over_infill() assert(candidate->surface_type == stInternalSolid); Polygons bridged_area = expand(to_polygons(candidate->expolygon), flow.scaled_spacing()); Polygons infill_region = to_polygons(surface_to_region[candidate]->fill_expolygons()); - bool touches_perimeter = !diff(bridged_area, infill_region).empty(); - bool touches_solid_region_under = !intersection(bridged_area, not_sparse_infill).empty(); bridged_area = intersection(bridged_area, lower_layers_sparse_infill); // cut off parts which are not over sparse infill - material overflow - if ((touches_perimeter || touches_solid_region_under) && shrink(bridged_area, 5.0 * flow.scaled_spacing()).empty()) { - continue; + { + Polygons area_without_perimeter_boundary_sections = intersection(bridged_area, + closing(infill_region, flow.scaled_width(), + flow.scaled_width() + + 4.0 * flow.scaled_spacing())); + Polygons and_further_without_solid_supported_sections = diff(area_without_perimeter_boundary_sections, + expand(not_sparse_infill, 4.0 * flow.scaled_spacing())); + + if (and_further_without_solid_supported_sections.empty()) { + continue; + } } Polygons max_area = expand_area; From 0a5a401d3298e4bddd7124ba19be7682c715d9e5 Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Wed, 25 Jan 2023 13:02:42 +0100 Subject: [PATCH 045/110] Add alignment option to arrange settings dialog Also make it work --- src/libslic3r/Arrange.cpp | 8 +++++++- src/libslic3r/Arrange.hpp | 10 ++++++++++ src/slic3r/GUI/GLCanvas3D.cpp | 8 ++++++++ src/slic3r/GUI/GLCanvas3D.hpp | 1 + src/slic3r/GUI/Jobs/ArrangeJob.cpp | 15 +++++++++++++++ 5 files changed, 41 insertions(+), 1 deletion(-) diff --git a/src/libslic3r/Arrange.cpp b/src/libslic3r/Arrange.cpp index e200b7edba..5744a70765 100644 --- a/src/libslic3r/Arrange.cpp +++ b/src/libslic3r/Arrange.cpp @@ -85,7 +85,13 @@ template void fill_config(PConf& pcfg, const ArrangeParams ¶ms) { // Align the arranged pile into the center of the bin - pcfg.alignment = PConf::Alignment::CENTER; + switch (params.alignment) { + case Pivots::Center: pcfg.alignment = PConf::Alignment::CENTER; break; + case Pivots::BottomLeft: pcfg.alignment = PConf::Alignment::BOTTOM_LEFT; break; + case Pivots::BottomRight: pcfg.alignment = PConf::Alignment::BOTTOM_RIGHT; break; + case Pivots::TopLeft: pcfg.alignment = PConf::Alignment::TOP_LEFT; break; + case Pivots::TopRight: pcfg.alignment = PConf::Alignment::TOP_RIGHT; break; + } // Start placing the items from the center of the print bed pcfg.starting_point = PConf::Alignment::CENTER; diff --git a/src/libslic3r/Arrange.hpp b/src/libslic3r/Arrange.hpp index 02d05e3e48..1d2e3fe88b 100644 --- a/src/libslic3r/Arrange.hpp +++ b/src/libslic3r/Arrange.hpp @@ -75,6 +75,10 @@ struct ArrangePolygon { using ArrangePolygons = std::vector; +enum class Pivots { + Center, TopLeft, BottomLeft, BottomRight, TopRight +}; + struct ArrangeParams { /// The minimum distance which is allowed for any @@ -93,6 +97,12 @@ struct ArrangeParams { bool allow_rotations = false; + /// Final alignment of the merged pile after arrangement + Pivots alignment = Pivots::Center; + + /// Starting position hint for the arrangement + Pivots starting_point = Pivots::Center; + /// Progress indicator callback called when an object gets packed. /// The unsigned argument is the number of items remaining to pack. std::function progressind; diff --git a/src/slic3r/GUI/GLCanvas3D.cpp b/src/slic3r/GUI/GLCanvas3D.cpp index 7df8f5b327..dfbd66428c 100644 --- a/src/slic3r/GUI/GLCanvas3D.cpp +++ b/src/slic3r/GUI/GLCanvas3D.cpp @@ -4145,6 +4145,7 @@ bool GLCanvas3D::_render_arrange_menu(float pos_x) std::string dist_key = "min_object_distance"; std::string dist_bed_key = "min_bed_distance"; std::string rot_key = "enable_rotation"; + std::string align_key = "alignment"; std::string postfix; if (ptech == ptSLA) { @@ -4163,6 +4164,7 @@ bool GLCanvas3D::_render_arrange_menu(float pos_x) dist_key += postfix; dist_bed_key += postfix; rot_key += postfix; + align_key += postfix; imgui->text(GUI::format_wxstr(_L("Press %1%left mouse button to enter the exact value"), shortkey_ctrl_prefix())); @@ -4186,6 +4188,12 @@ bool GLCanvas3D::_render_arrange_menu(float pos_x) settings_changed = true; } + if (imgui->combo(_("Alignment"), {"Center", "Top left", "Bottom left", "Bottom right", "Top right", "Random"}, settings.alignment)) { + settings_out.alignment = settings.alignment; + appcfg->set("arrange", align_key.c_str(), std::to_string(settings_out.alignment)); + settings_changed = true; + } + ImGui::Separator(); if (imgui->button(_L("Reset"))) { diff --git a/src/slic3r/GUI/GLCanvas3D.hpp b/src/slic3r/GUI/GLCanvas3D.hpp index f8f5a0efcd..55b403c9f7 100644 --- a/src/slic3r/GUI/GLCanvas3D.hpp +++ b/src/slic3r/GUI/GLCanvas3D.hpp @@ -463,6 +463,7 @@ public: // float distance_sla = 6.; float accuracy = 0.65f; // Unused currently bool enable_rotation = false; + int alignment = 0; }; private: diff --git a/src/slic3r/GUI/Jobs/ArrangeJob.cpp b/src/slic3r/GUI/Jobs/ArrangeJob.cpp index 7873af758d..dd133260eb 100644 --- a/src/slic3r/GUI/Jobs/ArrangeJob.cpp +++ b/src/slic3r/GUI/Jobs/ArrangeJob.cpp @@ -298,6 +298,21 @@ arrangement::ArrangeParams get_arrange_params(Plater *p) params.min_obj_distance = scaled(settings.distance); params.min_bed_distance = scaled(settings.distance_from_bed); + arrangement::Pivots pivot = arrangement::Pivots::Center; + + int pivot_max = static_cast(arrangement::Pivots::TopRight); + if (settings.alignment > pivot_max) { + // means it should be random + std::random_device rd{}; + std::mt19937 rng(rd()); + std::uniform_int_distribution dist(0, pivot_max); + pivot = static_cast(dist(rng)); + } else { + pivot = static_cast(settings.alignment); + } + + params.alignment = pivot; + return params; } From 068134d58b9607a229493e3772dd17bbffff0b9d Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Wed, 25 Jan 2023 13:27:03 +0100 Subject: [PATCH 046/110] Don't show arrange alignment combo if not on a rectangular bed --- src/libslic3r/Arrange.cpp | 6 ++++++ src/libslic3r/Arrange.hpp | 2 ++ src/slic3r/GUI/GLCanvas3D.cpp | 4 +++- 3 files changed, 11 insertions(+), 1 deletion(-) diff --git a/src/libslic3r/Arrange.cpp b/src/libslic3r/Arrange.cpp index 5744a70765..32cfac2ea2 100644 --- a/src/libslic3r/Arrange.cpp +++ b/src/libslic3r/Arrange.cpp @@ -607,6 +607,12 @@ template auto call_with_bed(const Points &bed, Fn &&fn) } } +bool is_box(const Points &bed) +{ + return !bed.empty() && + ((1.0 - poly_area(bed) / area(BoundingBox(bed))) < 1e-3); +} + template<> void arrange(ArrangePolygons & items, const ArrangePolygons &excludes, diff --git a/src/libslic3r/Arrange.hpp b/src/libslic3r/Arrange.hpp index 1d2e3fe88b..e8fdbdff2c 100644 --- a/src/libslic3r/Arrange.hpp +++ b/src/libslic3r/Arrange.hpp @@ -143,6 +143,8 @@ inline void arrange(ArrangePolygons &items, const CircleBed &bed, const ArrangeP inline void arrange(ArrangePolygons &items, const Polygon &bed, const ArrangeParams ¶ms = {}) { arrange(items, {}, bed, params); } inline void arrange(ArrangePolygons &items, const InfiniteBed &bed, const ArrangeParams ¶ms = {}) { arrange(items, {}, bed, params); } +bool is_box(const Points &bed); + }} // namespace Slic3r::arrangement #endif // MODELARRANGE_HPP diff --git a/src/slic3r/GUI/GLCanvas3D.cpp b/src/slic3r/GUI/GLCanvas3D.cpp index dfbd66428c..ba228452c4 100644 --- a/src/slic3r/GUI/GLCanvas3D.cpp +++ b/src/slic3r/GUI/GLCanvas3D.cpp @@ -4188,7 +4188,9 @@ bool GLCanvas3D::_render_arrange_menu(float pos_x) settings_changed = true; } - if (imgui->combo(_("Alignment"), {"Center", "Top left", "Bottom left", "Bottom right", "Top right", "Random"}, settings.alignment)) { + Points bed = m_config ? get_bed_shape(*m_config) : Points{}; + + if (arrangement::is_box(bed) && imgui->combo(_("Alignment"), {"Center", "Top left", "Bottom left", "Bottom right", "Top right", "Random"}, settings.alignment)) { settings_out.alignment = settings.alignment; appcfg->set("arrange", align_key.c_str(), std::to_string(settings_out.alignment)); settings_changed = true; From be0c90041ad0baa098ef7d823f25d7846dd9d9cb Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Wed, 25 Jan 2023 13:37:16 +0100 Subject: [PATCH 047/110] Remember arrangement alignment settings between app sessions --- src/slic3r/GUI/GLCanvas3D.cpp | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/src/slic3r/GUI/GLCanvas3D.cpp b/src/slic3r/GUI/GLCanvas3D.cpp index ba228452c4..8096bc312b 100644 --- a/src/slic3r/GUI/GLCanvas3D.cpp +++ b/src/slic3r/GUI/GLCanvas3D.cpp @@ -999,6 +999,15 @@ void GLCanvas3D::load_arrange_settings() std::string en_rot_sla_str = wxGetApp().app_config->get("arrange", "enable_rotation_sla"); + std::string alignment_fff_str = + wxGetApp().app_config->get("arrange", "alignment_fff"); + + std::string alignment_fff_seqp_str = + wxGetApp().app_config->get("arrange", "alignment_fff_seq_pring"); + + std::string alignment_sla_str = + wxGetApp().app_config->get("arrange", "alignment_sla"); + if (!dist_fff_str.empty()) m_arrange_settings_fff.distance = string_to_float_decimal_point(dist_fff_str); @@ -1025,6 +1034,15 @@ void GLCanvas3D::load_arrange_settings() if (!en_rot_sla_str.empty()) m_arrange_settings_sla.enable_rotation = (en_rot_sla_str == "1" || en_rot_sla_str == "yes"); + + if (!alignment_sla_str.empty()) + m_arrange_settings_sla.alignment = std::stoi(alignment_sla_str); + + if (!alignment_fff_str.empty()) + m_arrange_settings_fff.alignment = std::stoi(alignment_fff_str); + + if (!alignment_fff_seqp_str.empty()) + m_arrange_settings_fff_seq_print.alignment = std::stoi(alignment_fff_seqp_str); } PrinterTechnology GLCanvas3D::current_printer_technology() const From 2a69dda9dad64ad5082c5a10e8cae019ce452e47 Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Wed, 25 Jan 2023 13:58:59 +0100 Subject: [PATCH 048/110] Fix compilation on Linux --- src/slic3r/GUI/Jobs/ArrangeJob.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/slic3r/GUI/Jobs/ArrangeJob.cpp b/src/slic3r/GUI/Jobs/ArrangeJob.cpp index dd133260eb..fa1de2203d 100644 --- a/src/slic3r/GUI/Jobs/ArrangeJob.cpp +++ b/src/slic3r/GUI/Jobs/ArrangeJob.cpp @@ -14,6 +14,7 @@ #include "libnest2d/common.hpp" #include +#include namespace Slic3r { namespace GUI { From aab62994584a93b4ee63eb78f5b0d2accaea9e0a Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Mon, 27 Feb 2023 18:10:14 +0100 Subject: [PATCH 049/110] Show and arrange alignment only for XL (MINI currentl --- src/slic3r/GUI/GLCanvas3D.cpp | 58 ++++++++++++++++++++++-------- src/slic3r/GUI/GLCanvas3D.hpp | 31 +++++++++------- src/slic3r/GUI/Jobs/ArrangeJob.cpp | 6 ++-- src/slic3r/GUI/Jobs/FillBedJob.cpp | 17 ++++----- 4 files changed, 72 insertions(+), 40 deletions(-) diff --git a/src/slic3r/GUI/GLCanvas3D.cpp b/src/slic3r/GUI/GLCanvas3D.cpp index 8096bc312b..dda3f6b4b5 100644 --- a/src/slic3r/GUI/GLCanvas3D.cpp +++ b/src/slic3r/GUI/GLCanvas3D.cpp @@ -999,14 +999,18 @@ void GLCanvas3D::load_arrange_settings() std::string en_rot_sla_str = wxGetApp().app_config->get("arrange", "enable_rotation_sla"); - std::string alignment_fff_str = - wxGetApp().app_config->get("arrange", "alignment_fff"); +// std::string alignment_fff_str = +// wxGetApp().app_config->get("arrange", "alignment_fff"); - std::string alignment_fff_seqp_str = - wxGetApp().app_config->get("arrange", "alignment_fff_seq_pring"); +// std::string alignment_fff_seqp_str = +// wxGetApp().app_config->get("arrange", "alignment_fff_seq_pring"); - std::string alignment_sla_str = - wxGetApp().app_config->get("arrange", "alignment_sla"); +// std::string alignment_sla_str = +// wxGetApp().app_config->get("arrange", "alignment_sla"); + + // Override default alignment and save save/load it to a temporary slot "alignment_xl" + std::string alignment_xl_str = + wxGetApp().app_config->get("arrange", "alignment_xl"); if (!dist_fff_str.empty()) m_arrange_settings_fff.distance = string_to_float_decimal_point(dist_fff_str); @@ -1035,14 +1039,23 @@ void GLCanvas3D::load_arrange_settings() if (!en_rot_sla_str.empty()) m_arrange_settings_sla.enable_rotation = (en_rot_sla_str == "1" || en_rot_sla_str == "yes"); - if (!alignment_sla_str.empty()) - m_arrange_settings_sla.alignment = std::stoi(alignment_sla_str); +// if (!alignment_sla_str.empty()) +// m_arrange_settings_sla.alignment = std::stoi(alignment_sla_str); - if (!alignment_fff_str.empty()) - m_arrange_settings_fff.alignment = std::stoi(alignment_fff_str); +// if (!alignment_fff_str.empty()) +// m_arrange_settings_fff.alignment = std::stoi(alignment_fff_str); - if (!alignment_fff_seqp_str.empty()) - m_arrange_settings_fff_seq_print.alignment = std::stoi(alignment_fff_seqp_str); +// if (!alignment_fff_seqp_str.empty()) +// m_arrange_settings_fff_seq_print.alignment = std::stoi(alignment_fff_seqp_str); + + // Override default alignment and save save/load it to a temporary slot "alignment_xl" + int arr_alignment = static_cast(arrangement::Pivots::BottomLeft); + if (!alignment_xl_str.empty()) + arr_alignment = std::stoi(alignment_xl_str); + + m_arrange_settings_sla.alignment = arr_alignment ; + m_arrange_settings_fff.alignment = arr_alignment ; + m_arrange_settings_fff_seq_print.alignment = arr_alignment ; } PrinterTechnology GLCanvas3D::current_printer_technology() const @@ -1050,6 +1063,21 @@ PrinterTechnology GLCanvas3D::current_printer_technology() const return m_process->current_printer_technology(); } +bool GLCanvas3D::is_arrange_alignment_enabled() +{ + static constexpr const char *ALIGN_ONLY_FOR = "MINI"; + + bool ret = false; + + const Preset &preset = wxGetApp().preset_bundle->get_presets(Preset::TYPE_PRINTER).get_selected_preset(); + + auto *printermodel = PresetUtils::system_printer_model(preset); + if (printermodel) + ret = printermodel->family == ALIGN_ONLY_FOR; + + return ret; +} + GLCanvas3D::GLCanvas3D(wxGLCanvas* canvas, Bed3D &bed) : m_canvas(canvas) , m_context(nullptr) @@ -4152,7 +4180,7 @@ bool GLCanvas3D::_render_arrange_menu(float pos_x) imgui->begin(_L("Arrange options"), ImGuiWindowFlags_NoMove | ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoCollapse); ArrangeSettings settings = get_arrange_settings(); - ArrangeSettings &settings_out = get_arrange_settings(); + ArrangeSettings &settings_out = get_arrange_settings_ref(this); auto &appcfg = wxGetApp().app_config; PrinterTechnology ptech = current_printer_technology(); @@ -4206,9 +4234,9 @@ bool GLCanvas3D::_render_arrange_menu(float pos_x) settings_changed = true; } - Points bed = m_config ? get_bed_shape(*m_config) : Points{}; + Points bed = m_config ? get_bed_shape(*m_config) : Points{}; - if (arrangement::is_box(bed) && imgui->combo(_("Alignment"), {"Center", "Top left", "Bottom left", "Bottom right", "Top right", "Random"}, settings.alignment)) { + if (arrangement::is_box(bed) && settings.alignment >= 0 && imgui->combo(_("Alignment"), {"Center", "Top left", "Bottom left", "Bottom right", "Top right", "Random"}, settings.alignment)) { settings_out.alignment = settings.alignment; appcfg->set("arrange", align_key.c_str(), std::to_string(settings_out.alignment)); settings_changed = true; diff --git a/src/slic3r/GUI/GLCanvas3D.hpp b/src/slic3r/GUI/GLCanvas3D.hpp index 55b403c9f7..20ab6b7338 100644 --- a/src/slic3r/GUI/GLCanvas3D.hpp +++ b/src/slic3r/GUI/GLCanvas3D.hpp @@ -550,8 +550,10 @@ private: PrinterTechnology current_printer_technology() const; + static bool is_arrange_alignment_enabled(); + template - static auto & get_arrange_settings(Self *self) { + static auto & get_arrange_settings_ref(Self *self) { PrinterTechnology ptech = self->current_printer_technology(); auto *ptr = &self->m_arrange_settings_fff; @@ -569,8 +571,22 @@ private: return *ptr; } - ArrangeSettings &get_arrange_settings() { return get_arrange_settings(this); } +public: + ArrangeSettings get_arrange_settings() const { + const ArrangeSettings &settings = get_arrange_settings_ref(this); + ArrangeSettings ret = settings; + if (&settings == &m_arrange_settings_fff_seq_print) { + ret.distance = std::max(ret.distance, + float(min_object_distance(*m_config))); + } + if (!is_arrange_alignment_enabled()) + ret.alignment = -1; + + return ret; + } + +private: void load_arrange_settings(); class SequentialPrintClearance @@ -902,17 +918,6 @@ public: void highlight_toolbar_item(const std::string& item_name); void highlight_gizmo(const std::string& gizmo_name); - ArrangeSettings get_arrange_settings() const { - const ArrangeSettings &settings = get_arrange_settings(this); - ArrangeSettings ret = settings; - if (&settings == &m_arrange_settings_fff_seq_print) { - ret.distance = std::max(ret.distance, - float(min_object_distance(*m_config))); - } - - return ret; - } - // Timestamp for FPS calculation and notification fade-outs. static int64_t timestamp_now() { #ifdef _WIN32 diff --git a/src/slic3r/GUI/Jobs/ArrangeJob.cpp b/src/slic3r/GUI/Jobs/ArrangeJob.cpp index fa1de2203d..2b954108b9 100644 --- a/src/slic3r/GUI/Jobs/ArrangeJob.cpp +++ b/src/slic3r/GUI/Jobs/ArrangeJob.cpp @@ -292,7 +292,7 @@ arrangement::ArrangePolygon get_arrange_poly(ModelInstance *inst, arrangement::ArrangeParams get_arrange_params(Plater *p) { const GLCanvas3D::ArrangeSettings &settings = - static_cast(p->canvas3D())->get_arrange_settings(); + p->canvas3D()->get_arrange_settings(); arrangement::ArrangeParams params; params.allow_rotations = settings.enable_rotation; @@ -302,7 +302,9 @@ arrangement::ArrangeParams get_arrange_params(Plater *p) arrangement::Pivots pivot = arrangement::Pivots::Center; int pivot_max = static_cast(arrangement::Pivots::TopRight); - if (settings.alignment > pivot_max) { + if (settings.alignment < 0) { + pivot = arrangement::Pivots::Center; + } else if (settings.alignment > pivot_max) { // means it should be random std::random_device rd{}; std::mt19937 rng(rd()); diff --git a/src/slic3r/GUI/Jobs/FillBedJob.cpp b/src/slic3r/GUI/Jobs/FillBedJob.cpp index 5eefc14b6f..6a173683f6 100644 --- a/src/slic3r/GUI/Jobs/FillBedJob.cpp +++ b/src/slic3r/GUI/Jobs/FillBedJob.cpp @@ -106,18 +106,15 @@ void FillBedJob::prepare() void FillBedJob::process(Ctl &ctl) { auto statustxt = _u8L("Filling bed"); - ctl.call_on_main_thread([this] { prepare(); }).wait(); + arrangement::ArrangeParams params; + ctl.call_on_main_thread([this, ¶ms] { + prepare(); + params = get_arrange_params(m_plater); + }).wait(); ctl.update_status(0, statustxt); - if (m_object_idx == -1 || m_selected.empty()) return; - - const GLCanvas3D::ArrangeSettings &settings = - static_cast(m_plater->canvas3D())->get_arrange_settings(); - - arrangement::ArrangeParams params; - params.allow_rotations = settings.enable_rotation; - params.min_obj_distance = scaled(settings.distance); - params.min_bed_distance = scaled(settings.distance_from_bed); + if (m_object_idx == -1 || m_selected.empty()) + return; bool do_stop = false; params.stopcondition = [&ctl, &do_stop]() { From 437c0814b4ad4d34f60a82ddd77beb3bb407c53f Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Tue, 28 Feb 2023 09:28:00 +0100 Subject: [PATCH 050/110] Fine tune arrange alignment enabling rules for XL --- src/slic3r/GUI/GLCanvas3D.cpp | 11 +++++------ src/slic3r/GUI/GLCanvas3D.hpp | 2 +- 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/src/slic3r/GUI/GLCanvas3D.cpp b/src/slic3r/GUI/GLCanvas3D.cpp index dda3f6b4b5..97b4d727f6 100644 --- a/src/slic3r/GUI/GLCanvas3D.cpp +++ b/src/slic3r/GUI/GLCanvas3D.cpp @@ -1063,17 +1063,16 @@ PrinterTechnology GLCanvas3D::current_printer_technology() const return m_process->current_printer_technology(); } -bool GLCanvas3D::is_arrange_alignment_enabled() +bool GLCanvas3D::is_arrange_alignment_enabled() const { - static constexpr const char *ALIGN_ONLY_FOR = "MINI"; + static constexpr const char *ALIGN_ONLY_FOR = "XL"; bool ret = false; - const Preset &preset = wxGetApp().preset_bundle->get_presets(Preset::TYPE_PRINTER).get_selected_preset(); + auto *printer_model = m_config->opt("printer_model"); - auto *printermodel = PresetUtils::system_printer_model(preset); - if (printermodel) - ret = printermodel->family == ALIGN_ONLY_FOR; + if (printer_model) + ret = boost::algorithm::contains(printer_model->value, ALIGN_ONLY_FOR); return ret; } diff --git a/src/slic3r/GUI/GLCanvas3D.hpp b/src/slic3r/GUI/GLCanvas3D.hpp index 20ab6b7338..8545cdf51c 100644 --- a/src/slic3r/GUI/GLCanvas3D.hpp +++ b/src/slic3r/GUI/GLCanvas3D.hpp @@ -550,7 +550,7 @@ private: PrinterTechnology current_printer_technology() const; - static bool is_arrange_alignment_enabled(); + bool is_arrange_alignment_enabled() const; template static auto & get_arrange_settings_ref(Self *self) { From b5fc26ab0a5abbaaf37319d1da89c195b547eb79 Mon Sep 17 00:00:00 2001 From: PavelMikus Date: Tue, 28 Feb 2023 15:07:17 +0100 Subject: [PATCH 051/110] Filter out very small esnuring regions --- src/libslic3r/PrintObject.cpp | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/libslic3r/PrintObject.cpp b/src/libslic3r/PrintObject.cpp index af26f175fe..c6dbd7e4c4 100644 --- a/src/libslic3r/PrintObject.cpp +++ b/src/libslic3r/PrintObject.cpp @@ -1479,7 +1479,7 @@ void PrintObject::discover_vertical_shells() // Open to remove (filter out) regions narrower than a bit less than an infill extrusion line width. // Such narrow regions are difficult to fill in with a gap fill algorithm (or Arachne), however they are most likely // not needed for print stability / quality. - const float narrow_ensure_vertical_wall_thickness_region_radius = 0.5f * 0.65f * min_perimeter_infill_spacing; + const float narrow_ensure_vertical_wall_thickness_region_radius = 0.5f * 0.85f * min_perimeter_infill_spacing; // Then close gaps narrower than 1.2 * line width, such gaps are difficult to fill in with sparse infill, // thus they will be merged into the solid infill. const float narrow_sparse_infill_region_radius = 0.5f * 1.2f * min_perimeter_infill_spacing; @@ -1492,6 +1492,11 @@ void PrintObject::discover_vertical_shells() narrow_ensure_vertical_wall_thickness_region_radius + narrow_sparse_infill_region_radius, ClipperLib::jtSquare), // Finally expand the infill a bit to remove tiny gaps between solid infill and the other regions. narrow_sparse_infill_region_radius - tiny_overlap_radius, ClipperLib::jtSquare); + + // The opening operation may cause scattered tiny drops on the smooth parts of the model. + shell.erase(std::remove_if(shell.begin(), shell.end(), [&min_perimeter_infill_spacing](const Polygon& p){ + return p.area() < min_perimeter_infill_spacing * scaled(8.0); + }), shell.end()); } if (shell.empty()) continue; From daaa9669e9c6d559ffd4d190d0442bd61f57c686 Mon Sep 17 00:00:00 2001 From: David Kocik Date: Tue, 21 Feb 2023 14:00:52 +0100 Subject: [PATCH 052/110] File -> Import -> Import Zip Archive --- src/slic3r/GUI/GUI_App.cpp | 13 +++++++++++++ src/slic3r/GUI/GUI_App.hpp | 3 +++ src/slic3r/GUI/MainFrame.cpp | 4 ++++ src/slic3r/GUI/Plater.cpp | 11 +++++++++++ src/slic3r/GUI/Plater.hpp | 1 + 5 files changed, 32 insertions(+) diff --git a/src/slic3r/GUI/GUI_App.cpp b/src/slic3r/GUI/GUI_App.cpp index 520f67aed1..f189aeb473 100644 --- a/src/slic3r/GUI/GUI_App.cpp +++ b/src/slic3r/GUI/GUI_App.cpp @@ -480,6 +480,8 @@ static const FileWildcards file_wildcards_by_type[FT_SIZE] = { /* FT_TEX */ { "Texture"sv, { ".png"sv, ".svg"sv } }, /* FT_SL1 */ { "Masked SLA files"sv, { ".sl1"sv, ".sl1s"sv, ".pwmx"sv } }, + + /* FT_ZIP */ { "Zip files"sv, { ".zip"sv } }, }; #if ENABLE_ALTERNATIVE_FILE_WILDCARDS_GENERATOR @@ -1977,6 +1979,17 @@ void GUI_App::import_model(wxWindow *parent, wxArrayString& input_files) const dialog.GetPaths(input_files); } +void GUI_App::import_zip(wxWindow* parent, wxString& input_file) const +{ + wxFileDialog dialog(parent ? parent : GetTopWindow(), + _L("Choose ZIP file:"), + from_u8(app_config->get_last_dir()), "", + file_wildcards(FT_ZIP), wxFD_OPEN | wxFD_FILE_MUST_EXIST); + + if (dialog.ShowModal() == wxID_OK) + input_file = dialog.GetPath(); +} + void GUI_App::load_gcode(wxWindow* parent, wxString& input_file) const { input_file.Clear(); diff --git a/src/slic3r/GUI/GUI_App.hpp b/src/slic3r/GUI/GUI_App.hpp index 3f8d47fd77..da231bae66 100644 --- a/src/slic3r/GUI/GUI_App.hpp +++ b/src/slic3r/GUI/GUI_App.hpp @@ -73,6 +73,8 @@ enum FileType FT_SL1, + FT_ZIP, + FT_SIZE, }; @@ -252,6 +254,7 @@ public: void keyboard_shortcuts(); void load_project(wxWindow *parent, wxString& input_file) const; void import_model(wxWindow *parent, wxArrayString& input_files) const; + void import_zip(wxWindow* parent, wxString& input_file) const; void load_gcode(wxWindow* parent, wxString& input_file) const; static bool catch_error(std::function cb, const std::string& err); diff --git a/src/slic3r/GUI/MainFrame.cpp b/src/slic3r/GUI/MainFrame.cpp index b656f056ae..ab82c10336 100644 --- a/src/slic3r/GUI/MainFrame.cpp +++ b/src/slic3r/GUI/MainFrame.cpp @@ -1293,6 +1293,10 @@ void MainFrame::init_menubar_as_editor() [this](wxCommandEvent&) { if (m_plater) m_plater->import_sl1_archive(); }, "import_plater", nullptr, [this](){return m_plater != nullptr && m_plater->get_ui_job_worker().is_idle(); }, this); + append_menu_item(import_menu, wxID_ANY, _L("Import ZIP Achive") + dots, _L("Load a zip achive"), + [this](wxCommandEvent&) { if (m_plater) m_plater->import_zip_archive(); }, "import_plater", nullptr, + [this]() {return m_plater != nullptr; }, this); + import_menu->AppendSeparator(); append_menu_item(import_menu, wxID_ANY, _L("Import &Config") + dots + "\tCtrl+L", _L("Load exported configuration file"), [this](wxCommandEvent&) { load_config_file(); }, "import_config", nullptr, diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index e04b05033b..5a948f577b 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -5479,6 +5479,17 @@ void Plater::add_model(bool imperial_units/* = false*/) wxGetApp().mainframe->update_title(); } +void Plater::import_zip_archive() +{ + wxString input_file; + wxGetApp().import_zip(this, input_file); + if (input_file.empty()) + return; + + fs::path path = into_path(input_file); + preview_zip_archive(path); +} + void Plater::import_sl1_archive() { auto &w = get_ui_job_worker(); diff --git a/src/slic3r/GUI/Plater.hpp b/src/slic3r/GUI/Plater.hpp index 3dcca746da..db4349101a 100644 --- a/src/slic3r/GUI/Plater.hpp +++ b/src/slic3r/GUI/Plater.hpp @@ -166,6 +166,7 @@ public: void load_project(); void load_project(const wxString& filename); void add_model(bool imperial_units = false); + void import_zip_archive(); void import_sl1_archive(); void extract_config_from_project(); void load_gcode(); From 6f9e3c24259e63b9631f95307dc787dec2394cb1 Mon Sep 17 00:00:00 2001 From: David Kocik Date: Wed, 1 Mar 2023 12:16:20 +0100 Subject: [PATCH 053/110] Allow XL for PrusaLink and PrusaConnect --- src/slic3r/GUI/PhysicalPrinterDialog.cpp | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/slic3r/GUI/PhysicalPrinterDialog.cpp b/src/slic3r/GUI/PhysicalPrinterDialog.cpp index f67af6dfc3..25f8bea62e 100644 --- a/src/slic3r/GUI/PhysicalPrinterDialog.cpp +++ b/src/slic3r/GUI/PhysicalPrinterDialog.cpp @@ -547,18 +547,19 @@ void PhysicalPrinterDialog::update_host_type(bool printer_change) } link, connect; // allowed models are: all MINI, all MK3 and newer, MK2.5 and MK2.5S auto model_supports_prusalink = [](const std::string& model) { - return model.size() >= 3 && + return model.size() >= 2 && (( boost::starts_with(model, "MK") && model[2] > '2' && model[2] <= '9') || boost::starts_with(model, "MINI") || boost::starts_with(model, "MK2.5") - //|| boost::starts_with(model, "MK2.5S") + || boost::starts_with(model, "XL") ); }; // allowed models are: all MK3/S and MK2.5/S auto model_supports_prusaconnect = [](const std::string& model) { - return model.size() >= 3 && - (boost::starts_with(model, "MK3") + return model.size() >= 2 && + ((boost::starts_with(model, "MK") && model[2] > '2' && model[2] <= '9') || boost::starts_with(model, "MK2.5") + || boost::starts_with(model, "XL") ); }; From 3985250b879e09b637c1bbf4d8960dab9536679c Mon Sep 17 00:00:00 2001 From: YuSanka Date: Wed, 1 Mar 2023 12:18:34 +0100 Subject: [PATCH 054/110] Fix for SPE-1544 - Dowels come from model during camera moving in edit connectors mode --- src/slic3r/GUI/Gizmos/GLGizmoCut.cpp | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp b/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp index a522f5533e..7ce8376ba6 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp @@ -996,6 +996,8 @@ void GLGizmoCut3D::update_raycasters_for_picking_transform() const Vec3d& instance_offset = mo->instances[inst_id]->get_offset(); const double sla_shift = double(m_c->selection_info()->get_sla_shift()); + const bool looking_forward = is_looking_forward(); + for (size_t i = 0; i < connectors.size(); ++i) { const CutConnector& connector = connectors[i]; @@ -1004,9 +1006,8 @@ void GLGizmoCut3D::update_raycasters_for_picking_transform() Vec3d pos = connector.pos + instance_offset; if (connector.attribs.type == CutConnectorType::Dowel && connector.attribs.style == CutConnectorStyle::Prism) { - if (is_looking_forward()) - pos -= static_cast(height - 0.05f) * m_clp_normal; - else + height = 0.05f; + if (!looking_forward) pos += 0.05 * m_clp_normal; } pos[Z] += sla_shift; @@ -2065,6 +2066,8 @@ void GLGizmoCut3D::render_connectors() m_has_invalid_connector = false; m_info_stats.invalidate(); + const bool looking_forward = is_looking_forward(); + for (size_t i = 0; i < connectors.size(); ++i) { const CutConnector& connector = connectors[i]; @@ -2093,20 +2096,19 @@ void GLGizmoCut3D::render_connectors() if (connector.attribs.type == CutConnectorType::Dowel && connector.attribs.style == CutConnectorStyle::Prism) { if (m_connectors_editing) { - if (is_looking_forward()) - pos -= static_cast(height-0.05f) * m_clp_normal; - else + height = 0.05f; + if (!looking_forward) pos += 0.05 * m_clp_normal; } else { - if (is_looking_forward()) + if (looking_forward) pos -= static_cast(height) * m_clp_normal; else pos += static_cast(height) * m_clp_normal; height *= 2; } } - else if (!is_looking_forward()) + else if (!looking_forward) pos += 0.05 * m_clp_normal; const Transform3d view_model_matrix = camera.get_view_matrix() * translation_transform(pos) * m_rotation_m * From e09bdef952c260425f21edb94b20bdc831fea114 Mon Sep 17 00:00:00 2001 From: YuSanka Date: Wed, 1 Mar 2023 12:22:31 +0100 Subject: [PATCH 055/110] Fix for SPE-1543 - "T"- key add text to cut object with plugs/dowels --- src/slic3r/GUI/Gizmos/GLGizmoEmboss.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/slic3r/GUI/Gizmos/GLGizmoEmboss.cpp b/src/slic3r/GUI/Gizmos/GLGizmoEmboss.cpp index 71374de23e..0210de3b5e 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoEmboss.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoEmboss.cpp @@ -885,7 +885,7 @@ void GLGizmoEmboss::on_set_state() priv::get_volume(m_parent.get_selection().get_model()->objects, m_volume_id) == nullptr ) { // reopen gizmo when new object is created GLGizmoBase::m_state = GLGizmoBase::Off; - if (wxGetApp().get_mode() == comSimple) + if (wxGetApp().get_mode() == comSimple || wxGetApp().obj_list()->has_selected_cut_object()) // It's impossible to add a part in simple mode return; // start creating new object From dfe735fb6c4ee38bfd28a7bce97df431ba2ef897 Mon Sep 17 00:00:00 2001 From: David Kocik Date: Wed, 1 Mar 2023 13:33:44 +0100 Subject: [PATCH 056/110] Postpone post_init() until opengl is initialized on all systems. Revert on win --- src/slic3r/GUI/GUI_App.cpp | 13 ++++++------- src/slic3r/GUI/GUI_App.hpp | 2 -- 2 files changed, 6 insertions(+), 9 deletions(-) diff --git a/src/slic3r/GUI/GUI_App.cpp b/src/slic3r/GUI/GUI_App.cpp index f189aeb473..cc60b3cff3 100644 --- a/src/slic3r/GUI/GUI_App.cpp +++ b/src/slic3r/GUI/GUI_App.cpp @@ -887,13 +887,9 @@ wxGLContext* GUI_App::init_glcontext(wxGLCanvas& canvas) bool GUI_App::init_opengl() { -#ifdef __linux__ bool status = m_opengl_mgr.init_gl(); m_opengl_initialized = true; return status; -#else - return m_opengl_mgr.init_gl(); -#endif } // gets path to PrusaSlicer.ini, returns semver from first line comment @@ -1370,12 +1366,15 @@ bool GUI_App::on_init_inner() // An ugly solution to GH #5537 in which GUI_App::init_opengl (normally called from events wxEVT_PAINT // and wxEVT_SET_FOCUS before GUI_App::post_init is called) wasn't called before GUI_App::post_init and OpenGL wasn't initialized. -#ifdef __linux__ - if (! m_post_initialized && m_opengl_initialized) { + // Since issue #9774 Where same problem occured on MacOS Ventura, we decided to have this check on MacOS as well. + +#ifdef __linux__ || __APPLE__ + if (!m_post_initialized && m_opengl_initialized) { #else - if (! m_post_initialized) { + if (!m_post_initialized) { #endif m_post_initialized = true; + #ifdef WIN32 this->mainframe->register_win32_callbacks(); #endif diff --git a/src/slic3r/GUI/GUI_App.hpp b/src/slic3r/GUI/GUI_App.hpp index da231bae66..51900a9b61 100644 --- a/src/slic3r/GUI/GUI_App.hpp +++ b/src/slic3r/GUI/GUI_App.hpp @@ -127,9 +127,7 @@ private: bool m_last_app_conf_lower_version{ false }; EAppMode m_app_mode{ EAppMode::Editor }; bool m_is_recreating_gui{ false }; -#ifdef __linux__ bool m_opengl_initialized{ false }; -#endif wxColour m_color_label_modified; wxColour m_color_label_sys; From 1749f745782cd72ff28e993ada04b3419d24e766 Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Wed, 1 Mar 2023 13:37:29 +0100 Subject: [PATCH 057/110] Fix bug when mini pillar widens and the end radius is too small. only affects SLA default supports fixes SPE-1542 --- src/libslic3r/SLA/SupportTreeUtilsLegacy.hpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/libslic3r/SLA/SupportTreeUtilsLegacy.hpp b/src/libslic3r/SLA/SupportTreeUtilsLegacy.hpp index b504d82fb3..f8e2f3024f 100644 --- a/src/libslic3r/SLA/SupportTreeUtilsLegacy.hpp +++ b/src/libslic3r/SLA/SupportTreeUtilsLegacy.hpp @@ -141,6 +141,7 @@ std::pair create_ground_pillar( if (head_id >= 0) builder.head(head_id).bridge_id = br.id; endp = diffbr->endp; radius = diffbr->end_r; + end_radius = diffbr->end_r; builder.add_junction(endp, radius); non_head = true; dir = diffbr->get_dir(); From 55b3bf879acd987cce5d4553543ccb0979c8ca46 Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Wed, 1 Mar 2023 15:17:43 +0100 Subject: [PATCH 058/110] SPE-1547 - Fixed object shown in preview while it should not --- src/slic3r/GUI/GLCanvas3D.cpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/slic3r/GUI/GLCanvas3D.cpp b/src/slic3r/GUI/GLCanvas3D.cpp index 29ef6e94d1..d0967d3dbc 100644 --- a/src/slic3r/GUI/GLCanvas3D.cpp +++ b/src/slic3r/GUI/GLCanvas3D.cpp @@ -5016,8 +5016,10 @@ void GLCanvas3D::_refresh_if_shown_on_screen() // frequently enough, we call render() here directly when we can. render(); assert(m_initialized); - if (requires_reload_scene) - reload_scene(true); + if (requires_reload_scene) { + if (wxGetApp().plater()->is_view3D_shown()) + reload_scene(true); + } } } From a507504b228725bf056a86aa0ef4c0c1ea299c67 Mon Sep 17 00:00:00 2001 From: YuSanka Date: Wed, 1 Mar 2023 15:33:13 +0100 Subject: [PATCH 059/110] PresetCollection::load_external_preset : Added check for m_idx_selected before call of the get_selected_preset. --- src/libslic3r/Preset.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libslic3r/Preset.cpp b/src/libslic3r/Preset.cpp index eebb4d54f8..55a643ea1f 100644 --- a/src/libslic3r/Preset.cpp +++ b/src/libslic3r/Preset.cpp @@ -879,7 +879,7 @@ std::pair PresetCollection::load_external_preset( // Insert a new profile. Preset &preset = this->load_preset(path, new_name, std::move(cfg), select == LoadAndSelect::Always); preset.is_external = true; - if (&this->get_selected_preset() == &preset) + if (this->m_idx_selected != size_t(-1) && &this->get_selected_preset() == &preset) this->get_edited_preset().is_external = true; return std::make_pair(&preset, false); From f048ff31b75a3d09d05829ae2ff8595525321408 Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Wed, 1 Mar 2023 16:00:30 +0100 Subject: [PATCH 060/110] Add localization and change wording of arrange alignment combo --- src/slic3r/GUI/GLCanvas3D.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/slic3r/GUI/GLCanvas3D.cpp b/src/slic3r/GUI/GLCanvas3D.cpp index 97b4d727f6..f114543f42 100644 --- a/src/slic3r/GUI/GLCanvas3D.cpp +++ b/src/slic3r/GUI/GLCanvas3D.cpp @@ -4235,7 +4235,8 @@ bool GLCanvas3D::_render_arrange_menu(float pos_x) Points bed = m_config ? get_bed_shape(*m_config) : Points{}; - if (arrangement::is_box(bed) && settings.alignment >= 0 && imgui->combo(_("Alignment"), {"Center", "Top left", "Bottom left", "Bottom right", "Top right", "Random"}, settings.alignment)) { + if (arrangement::is_box(bed) && settings.alignment >= 0 && + imgui->combo(_L("Alignment"), {_u8L("Center"), _u8L("Rear left"), _u8L("Front left"), _u8L("Front right"), _u8L("Rear right"), _u8L("Random") }, settings.alignment)) { settings_out.alignment = settings.alignment; appcfg->set("arrange", align_key.c_str(), std::to_string(settings_out.alignment)); settings_changed = true; From a3430a5b518aed84e893241797535ad1d2bdf396 Mon Sep 17 00:00:00 2001 From: PavelMikus Date: Wed, 1 Mar 2023 16:42:57 +0100 Subject: [PATCH 061/110] Completely removed Bounded Rectilinear infill Improved bridge over sparse infill logic - now does not bridge the whole area but only neede part Filtered out tiny regions of ensuring created after bridge_over_sparse infill expanded the regions --- src/libslic3r/Fill/FillEnsuring.cpp | 26 +--------------- src/libslic3r/Preset.cpp | 2 +- src/libslic3r/PrintConfig.cpp | 17 ----------- src/libslic3r/PrintConfig.hpp | 7 ----- src/libslic3r/PrintObject.cpp | 46 ++++++++++++++++------------- src/slic3r/GUI/Tab.cpp | 1 - 6 files changed, 28 insertions(+), 71 deletions(-) diff --git a/src/libslic3r/Fill/FillEnsuring.cpp b/src/libslic3r/Fill/FillEnsuring.cpp index a501f97326..0dab6d7441 100644 --- a/src/libslic3r/Fill/FillEnsuring.cpp +++ b/src/libslic3r/Fill/FillEnsuring.cpp @@ -14,8 +14,6 @@ ThickPolylines FillEnsuring::fill_surface_arachne(const Surface *surface, const assert(this->print_config != nullptr && this->print_object_config != nullptr && this->print_region_config != nullptr); const coord_t scaled_spacing = scaled(this->spacing); - const EnsuringInfillPattern infill_patter = this->print_object_config->ensure_vertical_shell_infill; - const bool is_bounded_rectilinear = infill_patter == EnsuringInfillPattern::eipBoundedRectilinear; // Perform offset. Slic3r::ExPolygons expp = this->overlap != 0. ? offset_ex(surface->expolygon, scaled(this->overlap)) : ExPolygons{surface->expolygon}; @@ -23,7 +21,7 @@ ThickPolylines FillEnsuring::fill_surface_arachne(const Surface *surface, const ThickPolylines thick_polylines_out; for (ExPolygon &ex_poly : expp) { Point bbox_size = ex_poly.contour.bounding_box().size(); - coord_t loops_count = is_bounded_rectilinear ? 1 : std::max(bbox_size.x(), bbox_size.y()) / scaled_spacing + 1; + coord_t loops_count = std::max(bbox_size.x(), bbox_size.y()) / scaled_spacing + 1; Polygons polygons = to_polygons(ex_poly); Arachne::WallToolPaths wall_tool_paths(polygons, scaled_spacing, scaled_spacing, loops_count, 0, params.layer_height, *this->print_object_config, *this->print_config); if (std::vector loops = wall_tool_paths.getToolPaths(); !loops.empty()) { @@ -77,28 +75,6 @@ ThickPolylines FillEnsuring::fill_surface_arachne(const Surface *surface, const if (j < thick_polylines_out.size()) thick_polylines_out.erase(thick_polylines_out.begin() + int(j), thick_polylines_out.end()); } - - if (is_bounded_rectilinear) { - // Remaining infill area will be filled with classic Rectilinear infill. - ExPolygons infill_contour = union_ex(wall_tool_paths.getInnerContour()); - if (offset_ex(infill_contour, -float(scaled_spacing / 2.)).empty()) - infill_contour.clear(); // Infill region is too small, so let's filter it out. - - Polygons pp; - for (ExPolygon &ex : infill_contour) - ex.simplify_p(scaled(params.resolution), &pp); - - // Collapse too narrow infill areas and append them to thick_polylines_out. - const auto min_perimeter_infill_spacing = coord_t(scaled_spacing * (1. - INSET_OVERLAP_TOLERANCE)); - const auto infill_overlap = coord_t(scale_(this->print_region_config->get_abs_value("infill_overlap", this->spacing))); - for (ExPolygon &ex_poly : offset2_ex(union_ex(pp), float(-min_perimeter_infill_spacing / 2.), float(infill_overlap + min_perimeter_infill_spacing / 2.))) { - Polylines polylines; - if (Surface new_surface(*surface, std::move(ex_poly)); !fill_surface_by_lines(&new_surface, params, 0.f, 0.f, polylines)) - BOOST_LOG_TRIVIAL(error) << "FillBoundedRectilinear::fill_surface() failed to fill a region."; - append(thick_polylines_out, to_thick_polylines(std::move(polylines), scaled(this->spacing))); - } - } else - assert(infill_patter == EnsuringInfillPattern::eipConcentric); } return thick_polylines_out; diff --git a/src/libslic3r/Preset.cpp b/src/libslic3r/Preset.cpp index 5306b7d5a7..677a35000c 100644 --- a/src/libslic3r/Preset.cpp +++ b/src/libslic3r/Preset.cpp @@ -463,7 +463,7 @@ static std::vector s_Preset_print_options { "wipe_tower_width", "wipe_tower_rotation_angle", "wipe_tower_brim_width", "wipe_tower_bridging", "single_extruder_multi_material_priming", "mmu_segmented_region_max_width", "wipe_tower_no_sparse_layers", "compatible_printers", "compatible_printers_condition", "inherits", "perimeter_generator", "wall_transition_length", "wall_transition_filter_deviation", "wall_transition_angle", - "wall_distribution_count", "min_feature_size", "min_bead_width", "ensure_vertical_shell_infill" + "wall_distribution_count", "min_feature_size", "min_bead_width" }; static std::vector s_Preset_filament_options { diff --git a/src/libslic3r/PrintConfig.cpp b/src/libslic3r/PrintConfig.cpp index 8d30b96100..878acc10ad 100644 --- a/src/libslic3r/PrintConfig.cpp +++ b/src/libslic3r/PrintConfig.cpp @@ -222,12 +222,6 @@ static t_config_enum_values s_keys_map_PerimeterGeneratorType { }; CONFIG_OPTION_ENUM_DEFINE_STATIC_MAPS(PerimeterGeneratorType) -static t_config_enum_values s_keys_map_EnsuringInfillPattern { - { "bounded_rectilinear", int(EnsuringInfillPattern::eipBoundedRectilinear) }, - { "concentric", int(EnsuringInfillPattern::eipConcentric) } -}; -CONFIG_OPTION_ENUM_DEFINE_STATIC_MAPS(EnsuringInfillPattern) - static void assign_printer_technology_to_unknown(t_optiondef_map &options, PrinterTechnology printer_technology) { for (std::pair &kvp : options) @@ -3220,17 +3214,6 @@ void PrintConfigDef::init_fff_params() def->min = 0; def->set_default_value(new ConfigOptionFloatOrPercent(85, true)); - def = this->add("ensure_vertical_shell_infill", coEnum); - def->label = L("Ensure vertical shell infill"); - def->category = L("Layers and Perimeters"); - def->tooltip = L("Ensure vertical shell infill."); - def->set_enum({ - { "bounded_rectilinear", L("Bounded Rectilinear") }, - { "concentric", L("Concentric") } - }); - def->mode = comAdvanced; - def->set_default_value(new ConfigOptionEnum(EnsuringInfillPattern::eipBoundedRectilinear)); - // Declare retract values for filament profile, overriding the printer's extruder profile. for (const char *opt_key : { // floats diff --git a/src/libslic3r/PrintConfig.hpp b/src/libslic3r/PrintConfig.hpp index 5b4599743f..cfd852ca14 100644 --- a/src/libslic3r/PrintConfig.hpp +++ b/src/libslic3r/PrintConfig.hpp @@ -133,11 +133,6 @@ enum class PerimeterGeneratorType Arachne }; -enum class EnsuringInfillPattern { - eipBoundedRectilinear, - eipConcentric -}; - enum class GCodeThumbnailsFormat { PNG, JPG, QOI }; @@ -167,7 +162,6 @@ CONFIG_OPTION_ENUM_DECLARE_STATIC_MAPS(DraftShield) CONFIG_OPTION_ENUM_DECLARE_STATIC_MAPS(GCodeThumbnailsFormat) CONFIG_OPTION_ENUM_DECLARE_STATIC_MAPS(ForwardCompatibilitySubstitutionRule) CONFIG_OPTION_ENUM_DECLARE_STATIC_MAPS(PerimeterGeneratorType) -CONFIG_OPTION_ENUM_DECLARE_STATIC_MAPS(EnsuringInfillPattern) #undef CONFIG_OPTION_ENUM_DECLARE_STATIC_MAPS @@ -497,7 +491,6 @@ PRINT_CONFIG_CLASS_DEFINE( ((ConfigOptionFloat, brim_width)) ((ConfigOptionBool, dont_support_bridges)) ((ConfigOptionFloat, elefant_foot_compensation)) - ((ConfigOptionEnum, ensure_vertical_shell_infill)) ((ConfigOptionFloatOrPercent, extrusion_width)) ((ConfigOptionFloat, first_layer_acceleration_over_raft)) ((ConfigOptionFloatOrPercent, first_layer_speed_over_raft)) diff --git a/src/libslic3r/PrintObject.cpp b/src/libslic3r/PrintObject.cpp index 19056b6823..dab1984068 100644 --- a/src/libslic3r/PrintObject.cpp +++ b/src/libslic3r/PrintObject.cpp @@ -774,8 +774,7 @@ bool PrintObject::invalidate_state_by_config_options( || opt_key == "wall_transition_angle" || opt_key == "wall_distribution_count" || opt_key == "min_feature_size" - || opt_key == "min_bead_width" - || opt_key == "ensure_vertical_shell_infill") { + || opt_key == "min_bead_width") { steps.emplace_back(posSlice); } else if ( opt_key == "seam_position" @@ -1774,6 +1773,20 @@ void PrintObject::bridge_over_infill() return a.min.x() < b.min.x(); }); + std::unordered_map> infill_and_deep_infill_polygons_per_region; + for (const auto &surface_region : surface_to_region) { + const LayerRegion *r = surface_region.second; + if (infill_and_deep_infill_polygons_per_region.find(r) == infill_and_deep_infill_polygons_per_region.end()) { + const Flow &flow = r->bridging_flow(frSolidInfill, true); + Polygons infill_region = to_polygons(r->fill_expolygons()); + Polygons deep_infill_area = closing(infill_region, scale_(0.01), scale_(0.01) + 4.0 * flow.scaled_spacing()); + Polygons solid_supported_area = expand(not_sparse_infill, 4.0 * flow.scaled_spacing()); + infill_and_deep_infill_polygons_per_region[r] = {closing(infill_region, scale_(0.1)), + intersection(lower_layers_sparse_infill, + diff(deep_infill_area, solid_supported_area))}; + } + } + // Lower layers sparse infill sections gathered // now we can intersected them with bridging surface candidates to get actual areas that need and can accumulate // bridging. These areas we then expand (within the surrounding sparse infill only!) @@ -1781,25 +1794,16 @@ void PrintObject::bridge_over_infill() for (const Surface *candidate : candidates.second) { const Flow &flow = surface_to_region[candidate]->bridging_flow(frSolidInfill, true); assert(candidate->surface_type == stInternalSolid); - Polygons bridged_area = expand(to_polygons(candidate->expolygon), flow.scaled_spacing()); - Polygons infill_region = to_polygons(surface_to_region[candidate]->fill_expolygons()); - bridged_area = - intersection(bridged_area, - lower_layers_sparse_infill); // cut off parts which are not over sparse infill - material overflow - - { - Polygons area_without_perimeter_boundary_sections = intersection(bridged_area, - closing(infill_region, flow.scaled_width(), - flow.scaled_width() + - 4.0 * flow.scaled_spacing())); - Polygons and_further_without_solid_supported_sections = diff(area_without_perimeter_boundary_sections, - expand(not_sparse_infill, 4.0 * flow.scaled_spacing())); - - if (and_further_without_solid_supported_sections.empty()) { - continue; - } + Polygons bridged_area = intersection(expand(to_polygons(candidate->expolygon), flow.scaled_spacing()), + infill_and_deep_infill_polygons_per_region[surface_to_region[candidate]].first); + // cut off parts which are not over sparse infill - material overflow + Polygons worth_bridging = intersection(bridged_area, + infill_and_deep_infill_polygons_per_region[surface_to_region[candidate]].second); + if (worth_bridging.empty()) { + continue; } + bridged_area = intersection(bridged_area, expand(worth_bridging, 5.0 * flow.scaled_spacing())); Polygons max_area = expand_area; max_area.insert(max_area.end(), bridged_area.begin(), bridged_area.end()); @@ -2133,7 +2137,9 @@ void PrintObject::bridge_over_infill() if (s.original_surface == &surface) { Surface tmp(surface, {}); for (const ExPolygon &expoly : diff_ex(surface.expolygon, s.new_polys)) { - new_surfaces.emplace_back(tmp, expoly); + if (expoly.area() > region->flow(frSolidInfill).scaled_width() * scale_(4.0)) { + new_surfaces.emplace_back(tmp, expoly); + } } tmp.surface_type = stInternalBridge; tmp.bridge_angle = s.bridge_angle; diff --git a/src/slic3r/GUI/Tab.cpp b/src/slic3r/GUI/Tab.cpp index 00a0122c32..17ddd63abd 100644 --- a/src/slic3r/GUI/Tab.cpp +++ b/src/slic3r/GUI/Tab.cpp @@ -1428,7 +1428,6 @@ void TabPrint::build() optgroup->append_single_option_line("extra_perimeters", category_path + "extra-perimeters-if-needed"); optgroup->append_single_option_line("extra_perimeters_on_overhangs", category_path + "extra-perimeters-on-overhangs"); optgroup->append_single_option_line("ensure_vertical_shell_thickness", category_path + "ensure-vertical-shell-thickness"); - optgroup->append_single_option_line("ensure_vertical_shell_infill"); optgroup->append_single_option_line("avoid_crossing_curled_overhangs", category_path + "avoid-crossing-curled-overhangs"); optgroup->append_single_option_line("avoid_crossing_perimeters", category_path + "avoid-crossing-perimeters"); optgroup->append_single_option_line("avoid_crossing_perimeters_max_detour", category_path + "avoid_crossing_perimeters_max_detour"); From 61b31bfbc3b8a92993b6894040b2a22cc4e2850d Mon Sep 17 00:00:00 2001 From: David Kocik Date: Thu, 16 Feb 2023 16:30:25 +0100 Subject: [PATCH 062/110] PrusaLink storage names in Upload dialog --- src/slic3r/GUI/Plater.cpp | 7 ++++--- src/slic3r/GUI/PrintHostDialogs.cpp | 19 ++++++++++--------- src/slic3r/GUI/PrintHostDialogs.hpp | 3 ++- src/slic3r/Utils/OctoPrint.cpp | 25 ++++++++++++++----------- src/slic3r/Utils/OctoPrint.hpp | 2 +- src/slic3r/Utils/PrintHost.hpp | 2 +- 6 files changed, 32 insertions(+), 26 deletions(-) diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index 5a948f577b..0951479143 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -6948,18 +6948,19 @@ void Plater::send_gcode() upload_job.printhost->get_groups(groups); } // PrusaLink specific: Query the server for the list of file groups. - wxArrayString storage; + wxArrayString storage_paths; + wxArrayString storage_names; { wxBusyCursor wait; try { - upload_job.printhost->get_storage(storage); + upload_job.printhost->get_storage(storage_paths, storage_names); } catch (const Slic3r::IOError& ex) { show_error(this, ex.what(), false); return; } } - PrintHostSendDialog dlg(default_output_file, upload_job.printhost->get_post_upload_actions(), groups, storage); + PrintHostSendDialog dlg(default_output_file, upload_job.printhost->get_post_upload_actions(), groups, storage_paths, storage_names); if (dlg.ShowModal() == wxID_OK) { upload_job.upload_data.upload_path = dlg.filename(); upload_job.upload_data.post_action = dlg.post_action(); diff --git a/src/slic3r/GUI/PrintHostDialogs.cpp b/src/slic3r/GUI/PrintHostDialogs.cpp index dab033cfbe..982fe99a7a 100644 --- a/src/slic3r/GUI/PrintHostDialogs.cpp +++ b/src/slic3r/GUI/PrintHostDialogs.cpp @@ -38,13 +38,14 @@ static const char *CONFIG_KEY_PATH = "printhost_path"; static const char *CONFIG_KEY_GROUP = "printhost_group"; static const char* CONFIG_KEY_STORAGE = "printhost_storage"; -PrintHostSendDialog::PrintHostSendDialog(const fs::path &path, PrintHostPostUploadActions post_actions, const wxArrayString &groups, const wxArrayString& storage) +PrintHostSendDialog::PrintHostSendDialog(const fs::path &path, PrintHostPostUploadActions post_actions, const wxArrayString &groups, const wxArrayString& storage_paths, const wxArrayString& storage_names) : MsgDialog(static_cast(wxGetApp().mainframe), _L("Send G-Code to printer host"), _L("Upload to Printer Host with the following filename:"), 0) // Set style = 0 to avoid default creation of the "OK" button. // All buttons will be added later in this constructor , txt_filename(new wxTextCtrl(this, wxID_ANY)) , combo_groups(!groups.IsEmpty() ? new wxComboBox(this, wxID_ANY, wxEmptyString, wxDefaultPosition, wxDefaultSize, groups, wxCB_READONLY) : nullptr) - , combo_storage(storage.GetCount() > 1 ? new wxComboBox(this, wxID_ANY, wxEmptyString, wxDefaultPosition, wxDefaultSize, storage, wxCB_READONLY) : nullptr) + , combo_storage(storage_names.GetCount() > 1 ? new wxComboBox(this, wxID_ANY, wxEmptyString, wxDefaultPosition, wxDefaultSize, storage_names, wxCB_READONLY) : nullptr) , post_upload_action(PrintHostPostUploadAction::None) + , m_paths(storage_paths) { #ifdef __APPLE__ txt_filename->OSXDisableAllSmartSubstitutions(); @@ -73,15 +74,15 @@ PrintHostSendDialog::PrintHostSendDialog(const fs::path &path, PrintHostPostUplo auto* label_group = new wxStaticText(this, wxID_ANY, _L("Upload to storage:")); content_sizer->Add(label_group); content_sizer->Add(combo_storage, 0, wxBOTTOM, 2 * VERT_SPACING); - combo_storage->SetValue(storage.front()); + combo_storage->SetValue(storage_names.front()); wxString recent_storage = from_u8(app_config->get("recent", CONFIG_KEY_STORAGE)); if (!recent_storage.empty()) combo_storage->SetValue(recent_storage); - } else if (storage.GetCount() == 1){ + } else if (storage_names.GetCount() == 1){ // PrusaLink specific: Show which storage has been detected. - auto* label_group = new wxStaticText(this, wxID_ANY, _L("Upload to storage: ") + storage.front()); + auto* label_group = new wxStaticText(this, wxID_ANY, _L("Upload to storage: ") + storage_names.front()); content_sizer->Add(label_group); - m_preselected_storage = storage.front(); + m_preselected_storage = storage_paths.front(); } @@ -196,7 +197,9 @@ std::string PrintHostSendDialog::storage() const { if (!combo_storage) return GUI::format("%1%", m_preselected_storage); - return boost::nowide::narrow(combo_storage->GetValue()); + if (combo_storage->GetSelection() < 0 || combo_storage->GetSelection() >= m_paths.size()) + return {}; + return boost::nowide::narrow(m_paths[combo_storage->GetSelection()]); } void PrintHostSendDialog::EndModal(int ret) @@ -226,8 +229,6 @@ void PrintHostSendDialog::EndModal(int ret) MsgDialog::EndModal(ret); } - - wxDEFINE_EVENT(EVT_PRINTHOST_PROGRESS, PrintHostQueueDialog::Event); wxDEFINE_EVENT(EVT_PRINTHOST_ERROR, PrintHostQueueDialog::Event); wxDEFINE_EVENT(EVT_PRINTHOST_CANCEL, PrintHostQueueDialog::Event); diff --git a/src/slic3r/GUI/PrintHostDialogs.hpp b/src/slic3r/GUI/PrintHostDialogs.hpp index 80e2a0f485..b3f5504050 100644 --- a/src/slic3r/GUI/PrintHostDialogs.hpp +++ b/src/slic3r/GUI/PrintHostDialogs.hpp @@ -26,7 +26,7 @@ namespace GUI { class PrintHostSendDialog : public GUI::MsgDialog { public: - PrintHostSendDialog(const boost::filesystem::path &path, PrintHostPostUploadActions post_actions, const wxArrayString& groups, const wxArrayString& storage); + PrintHostSendDialog(const boost::filesystem::path &path, PrintHostPostUploadActions post_actions, const wxArrayString& groups, const wxArrayString& storage_paths, const wxArrayString& storage_names); boost::filesystem::path filename() const; PrintHostPostUploadAction post_action() const; std::string group() const; @@ -40,6 +40,7 @@ private: PrintHostPostUploadAction post_upload_action; wxString m_valid_suffix; wxString m_preselected_storage; + wxArrayString m_paths; }; diff --git a/src/slic3r/Utils/OctoPrint.cpp b/src/slic3r/Utils/OctoPrint.cpp index f769320146..1889a7d369 100644 --- a/src/slic3r/Utils/OctoPrint.cpp +++ b/src/slic3r/Utils/OctoPrint.cpp @@ -273,7 +273,6 @@ bool OctoPrint::test(wxString& msg) const return res; } - wxString OctoPrint::get_test_ok_msg () const { return _(L("Connection to OctoPrint works correctly.")); @@ -685,7 +684,7 @@ bool PrusaLink::test(wxString& msg) const return res; } -bool PrusaLink::get_storage(wxArrayString& output) const +bool PrusaLink::get_storage(wxArrayString& storage_path, wxArrayString& storage_name) const { const char* name = get_name(); @@ -693,10 +692,11 @@ bool PrusaLink::get_storage(wxArrayString& output) const auto url = make_url("api/v1/storage"); wxString error_msg; - struct StorageInfo{ + struct StorageInfo { + wxString path; wxString name; - bool read_only; - long long free_space; + bool read_only = false; + long long free_space = -1; }; std::vector storage; @@ -731,6 +731,7 @@ bool PrusaLink::get_storage(wxArrayString& output) const } // each storage has own subtree of storage_list for (const auto& section : ptree.front().second) { + const auto name = section.second.get_optional("name"); const auto path = section.second.get_optional("path"); const auto space = section.second.get_optional("free_space"); const auto read_only = section.second.get_optional("read_only"); @@ -738,7 +739,8 @@ bool PrusaLink::get_storage(wxArrayString& output) const const auto available = section.second.get_optional("available"); if (path && (!available || *available)) { StorageInfo si; - si.name = boost::nowide::widen(*path); + si.path = boost::nowide::widen(*path); + si.name = name ? boost::nowide::widen(*name) : wxString(); // If read_only is missing, assume it is NOT read only. // si.read_only = read_only ? *read_only : false; // version without "ro" si.read_only = (read_only ? *read_only : (ro ? *ro : false)); @@ -759,16 +761,17 @@ bool PrusaLink::get_storage(wxArrayString& output) const .perform_sync(); for (const auto& si : storage) { - if (!si.read_only && si.free_space > 0) - output.push_back(si.name); + if (!si.read_only && si.free_space > 0) { + storage_path.push_back(si.path); + storage_name.push_back(si.name); + } } - if (res && output.empty()) - { + if (res && storage_path.empty()) { if (!storage.empty()) { // otherwise error_msg is already filled error_msg = L"\n\n" + _L("Storages found:") + L" \n"; for (const auto& si : storage) { - error_msg += si.name + L" : " + (si.read_only ? _L("read only") : _L("no free space")) + L"\n"; + error_msg += si.path + L" : " + (si.read_only ? _L("read only") : _L("no free space")) + L"\n"; } } std::string message = GUI::format(_L("Upload has failed. There is no suitable storage found at %1%.%2%"), m_host, error_msg); diff --git a/src/slic3r/Utils/OctoPrint.hpp b/src/slic3r/Utils/OctoPrint.hpp index fadb5d924b..d11cd9514b 100644 --- a/src/slic3r/Utils/OctoPrint.hpp +++ b/src/slic3r/Utils/OctoPrint.hpp @@ -94,7 +94,7 @@ public: virtual PrintHostPostUploadActions get_post_upload_actions() const override { return PrintHostPostUploadAction::StartPrint; } // gets possible storage to be uploaded to. This allows different printer to have different storage. F.e. local vs sdcard vs usb. - bool get_storage(wxArrayString& /* storage */) const override; + bool get_storage(wxArrayString& storage_path, wxArrayString& storage_name) const override; protected: bool test(wxString& curl_msg) const override; bool validate_version_text(const boost::optional& version_text) const override; diff --git a/src/slic3r/Utils/PrintHost.hpp b/src/slic3r/Utils/PrintHost.hpp index c39f86288e..becaf138b4 100644 --- a/src/slic3r/Utils/PrintHost.hpp +++ b/src/slic3r/Utils/PrintHost.hpp @@ -66,7 +66,7 @@ public: virtual bool get_printers(wxArrayString & /* printers */) const { return false; } // Support for PrusaLink uploading to different storage. Not supported by other print hosts. // Returns false if not supported or fail. - virtual bool get_storage(wxArrayString& /* storage */) const { return false; } + virtual bool get_storage(wxArrayString& /*storage_path*/, wxArrayString& /*storage_name*/) const { return false; } static PrintHost* get_print_host(DynamicPrintConfig *config); From 370073812309a1212c2f8ae5e90dcc79fb98c3eb Mon Sep 17 00:00:00 2001 From: David Kocik Date: Wed, 1 Mar 2023 14:18:52 +0100 Subject: [PATCH 063/110] Accept-Language when getting PrusaLink storage --- src/slic3r/Utils/OctoPrint.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/slic3r/Utils/OctoPrint.cpp b/src/slic3r/Utils/OctoPrint.cpp index 1889a7d369..de3ae943f9 100644 --- a/src/slic3r/Utils/OctoPrint.cpp +++ b/src/slic3r/Utils/OctoPrint.cpp @@ -702,8 +702,12 @@ bool PrusaLink::get_storage(wxArrayString& storage_path, wxArrayString& storage_ BOOST_LOG_TRIVIAL(info) << boost::format("%1%: Get storage at: %2%") % name % url; + wxString wlang = GUI::wxGetApp().current_language_code(); + std::string lang = GUI::format(wlang.SubString(0, 1)); + auto http = Http::get(std::move(url)); set_auth(http); + http.header("Accept-Language", lang); http.on_error([&](std::string body, std::string error, unsigned status) { BOOST_LOG_TRIVIAL(error) << boost::format("%1%: Error getting storage: %2%, HTTP %3%, body: `%4%`") % name % error % status % body; error_msg = L"\n\n" + boost::nowide::widen(error); From dc7373514d6f694cfeb5820501717e242cbd22e6 Mon Sep 17 00:00:00 2001 From: YuSanka Date: Thu, 2 Mar 2023 10:16:09 +0100 Subject: [PATCH 064/110] Fixed localization of the tooltips for settings parameters (follow-up https://github.com/Prusa-Development/PrusaSlicerPrivate/commit/6238595a) --- src/libslic3r/PrintConfig.cpp | 33 +++++---------------------------- src/slic3r/GUI/Field.cpp | 14 +++++--------- src/slic3r/GUI/OptionsGroup.cpp | 19 ++++++++++++++++--- src/slic3r/GUI/OptionsGroup.hpp | 3 +-- 4 files changed, 27 insertions(+), 42 deletions(-) diff --git a/src/libslic3r/PrintConfig.cpp b/src/libslic3r/PrintConfig.cpp index 878acc10ad..76fddebc4d 100644 --- a/src/libslic3r/PrintConfig.cpp +++ b/src/libslic3r/PrintConfig.cpp @@ -3270,9 +3270,6 @@ void PrintConfigDef::init_sla_support_params(const std::string &prefix) { ConfigOptionDef* def; - constexpr const char * pretext_unavailable = L("Unavailable for this method.\n"); - std::string pretext; - def = this->add(prefix + "support_head_front_diameter", coFloat); def->label = L("Pinhead front diameter"); def->category = L("Supports"); @@ -3322,13 +3319,9 @@ void PrintConfigDef::init_sla_support_params(const std::string &prefix) def->mode = comExpert; def->set_default_value(new ConfigOptionPercent(50)); - pretext = ""; - if (prefix == "branching") - pretext = pretext_unavailable; - def = this->add(prefix + "support_max_bridges_on_pillar", coInt); def->label = L("Max bridges on a pillar"); - def->tooltip = pretext + L( + def->tooltip = L( "Maximum number of bridges that can be placed on a pillar. Bridges " "hold support point pinheads and connect to pillars as small branches."); def->min = 0; @@ -3336,14 +3329,10 @@ void PrintConfigDef::init_sla_support_params(const std::string &prefix) def->mode = comExpert; def->set_default_value(new ConfigOptionInt(prefix == "branching" ? 2 : 3)); - pretext = ""; - if (prefix.empty()) - pretext = pretext_unavailable; - def = this->add(prefix + "support_max_weight_on_model", coFloat); def->label = L("Max weight on model"); def->category = L("Supports"); - def->tooltip = pretext + L( + def->tooltip = L( "Maximum weight of sub-trees that terminate on the model instead of the print bed. The weight is the sum of the lenghts of all " "branches emanating from the endpoint."); def->sidetext = L("mm"); @@ -3351,13 +3340,9 @@ void PrintConfigDef::init_sla_support_params(const std::string &prefix) def->mode = comExpert; def->set_default_value(new ConfigOptionFloat(10.)); - pretext = ""; - if (prefix == "branching") - pretext = pretext_unavailable; - def = this->add(prefix + "support_pillar_connection_mode", coEnum); def->label = L("Pillar connection mode"); - def->tooltip = pretext + L("Controls the bridge type between two neighboring pillars." + def->tooltip = L("Controls the bridge type between two neighboring pillars." " Can be zig-zag, cross (double zig-zag) or dynamic which" " will automatically switch between the first two depending" " on the distance of the two pillars."); @@ -3378,11 +3363,7 @@ void PrintConfigDef::init_sla_support_params(const std::string &prefix) def->label = L("Pillar widening factor"); def->category = L("Supports"); - pretext = ""; - if (prefix.empty()) - pretext = pretext_unavailable; - - def->tooltip = pretext + + def->tooltip = L("Merging bridges or pillars into another pillars can " "increase the radius. Zero means no increase, one means " "full increase. The exact amount of increase is unspecified and can " @@ -3449,14 +3430,10 @@ void PrintConfigDef::init_sla_support_params(const std::string &prefix) def->set_default_value(new ConfigOptionFloat(default_val)); - pretext = ""; - if (prefix == "branching") - pretext = pretext_unavailable; - def = this->add(prefix + "support_max_pillar_link_distance", coFloat); def->label = L("Max pillar linking distance"); def->category = L("Supports"); - def->tooltip = pretext + L("The max distance of two pillars to get linked with each other." + def->tooltip = L("The max distance of two pillars to get linked with each other." " A zero value will prohibit pillar cascading."); def->sidetext = L("mm"); def->min = 0; // 0 means no linking diff --git a/src/slic3r/GUI/Field.cpp b/src/slic3r/GUI/Field.cpp index 13a9e6e07c..ffd5a6bc37 100644 --- a/src/slic3r/GUI/Field.cpp +++ b/src/slic3r/GUI/Field.cpp @@ -175,24 +175,20 @@ void Field::on_back_to_sys_value() wxString Field::get_tooltip_text(const wxString& default_string) { - wxString tooltip_text(""); - wxString tooltip = _(m_opt.tooltip); - edit_tooltip(tooltip); + if (m_opt.tooltip.empty()) + return ""; std::string opt_id = m_opt_id; - auto hash_pos = opt_id.find("#"); + auto hash_pos = opt_id.find('#'); if (hash_pos != std::string::npos) { opt_id.replace(hash_pos, 1,"["); opt_id += "]"; } - if (tooltip.length() > 0) - tooltip_text = tooltip + "\n" + _(L("default value")) + "\t: " + + return from_u8(m_opt.tooltip) + "\n" + _L("default value") + "\t: " + (boost::iends_with(opt_id, "_gcode") ? "\n" : "") + default_string + (boost::iends_with(opt_id, "_gcode") ? "" : "\n") + - _(L("parameter name")) + "\t: " + opt_id; - - return tooltip_text; + _L("parameter name") + "\t: " + opt_id; } bool Field::is_matched(const std::string& string, const std::string& pattern) diff --git a/src/slic3r/GUI/OptionsGroup.cpp b/src/slic3r/GUI/OptionsGroup.cpp index 688570f904..e567ac7c66 100644 --- a/src/slic3r/GUI/OptionsGroup.cpp +++ b/src/slic3r/GUI/OptionsGroup.cpp @@ -118,6 +118,20 @@ OptionsGroup::OptionsGroup( wxWindow* _parent, const wxString& title, { } +Option::Option(const ConfigOptionDef& _opt, t_config_option_key id) : opt(_opt), opt_id(id) +{ + if (!opt.tooltip.empty()) { + wxString tooltip; + if (opt.opt_key.rfind("branching", 0) == 0) + tooltip = _L("Unavailable for this method.") + "\n"; + tooltip += _(opt.tooltip); + + edit_tooltip(tooltip); + + opt.tooltip = into_u8(tooltip); + } +} + void Line::clear() { if (near_label_widget_win) @@ -517,9 +531,8 @@ void OptionsGroup::clear(bool destroy_custom_ctrl) Line OptionsGroup::create_single_option_line(const Option& option, const std::string& path/* = std::string()*/) const { - wxString tooltip = _(option.opt.tooltip); - edit_tooltip(tooltip); - Line retval{ _(option.opt.label), tooltip }; + Line retval{ _(option.opt.label), from_u8(option.opt.tooltip) }; + retval.label_path = path; retval.append_option(option); return retval; diff --git a/src/slic3r/GUI/OptionsGroup.hpp b/src/slic3r/GUI/OptionsGroup.hpp index 88d0ff8bf5..f754505dd4 100644 --- a/src/slic3r/GUI/OptionsGroup.hpp +++ b/src/slic3r/GUI/OptionsGroup.hpp @@ -47,8 +47,7 @@ struct Option { return (rhs.opt_id == this->opt_id); } - Option(const ConfigOptionDef& _opt, t_config_option_key id) : - opt(_opt), opt_id(id) {} + Option(const ConfigOptionDef& _opt, t_config_option_key id); }; using t_option = std::unique_ptr