diff --git a/src/clipper/clipper.cpp b/src/clipper/clipper.cpp index 4d7a40726..c4b7e8bc2 100644 --- a/src/clipper/clipper.cpp +++ b/src/clipper/clipper.cpp @@ -4109,19 +4109,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); } @@ -4129,7 +4150,7 @@ void ClosedPathsFromPolyTree(const PolyTree& polytree, Paths& paths) void OpenPathsFromPolyTree(PolyTree& polytree, Paths& paths) { - paths.resize(0); + paths.clear(); paths.reserve(polytree.Total()); //Open paths are top level only, so ... for (int i = 0; i < polytree.ChildCount(); ++i) diff --git a/src/clipper/clipper.hpp b/src/clipper/clipper.hpp index 641476c8b..849672a8f 100644 --- a/src/clipper/clipper.hpp +++ b/src/clipper/clipper.hpp @@ -206,6 +206,7 @@ void MinkowskiSum(const Path& pattern, const Paths& paths, Paths& solution, bool void MinkowskiDiff(const Path& poly1, const Path& poly2, Paths& solution); void PolyTreeToPaths(const PolyTree& polytree, Paths& paths); +void PolyTreeToPaths(PolyTree&& polytree, Paths& paths); void ClosedPathsFromPolyTree(const PolyTree& polytree, Paths& paths); void OpenPathsFromPolyTree(PolyTree& polytree, Paths& paths); diff --git a/src/libslic3r/AABBTreeIndirect.hpp b/src/libslic3r/AABBTreeIndirect.hpp index e70e8be39..6d6479508 100644 --- a/src/libslic3r/AABBTreeIndirect.hpp +++ b/src/libslic3r/AABBTreeIndirect.hpp @@ -13,6 +13,7 @@ #include +#include "BoundingBox.hpp" #include "Utils.hpp" // for next_highest_power_of_2() // Definition of the ray intersection hit structure. @@ -217,6 +218,23 @@ using Tree3f = Tree<3, float>; using Tree2d = Tree<2, double>; using Tree3d = Tree<3, double>; +// Wrap a 2D Slic3r own BoundingBox to be passed to Tree::build() and similar +// to build an AABBTree over coord_t 2D bounding boxes. +class BoundingBoxWrapper { +public: + using BoundingBox = Eigen::AlignedBox; + BoundingBoxWrapper(const size_t idx, const Slic3r::BoundingBox &bbox) : + m_idx(idx), + // Inflate the bounding box a bit to account for numerical issues. + m_bbox(bbox.min - Point(SCALED_EPSILON, SCALED_EPSILON), bbox.max + Point(SCALED_EPSILON, SCALED_EPSILON)) {} + size_t idx() const { return m_idx; } + const BoundingBox& bbox() const { return m_bbox; } + Point centroid() const { return ((m_bbox.min().cast() + m_bbox.max().cast()) / 2).cast(); } +private: + size_t m_idx; + BoundingBox m_bbox; +}; + namespace detail { template struct RayIntersector { diff --git a/src/libslic3r/Algorithm/RegionExpansion.cpp b/src/libslic3r/Algorithm/RegionExpansion.cpp new file mode 100644 index 000000000..844cda822 --- /dev/null +++ b/src/libslic3r/Algorithm/RegionExpansion.cpp @@ -0,0 +1,539 @@ +#include "RegionExpansion.hpp" + +#include +#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( + 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(); })); + 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, base_idx)); + } + ++ base_idx; + } + 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, with one exception: The anchor expands into a closed hole. + 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; + } +} + +using AABBTreeBBoxes = AABBTreeIndirect::Tree<2, coord_t>; + +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, + // 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 = idx_boundary_begin; + coord_t idx_src_end; + + { + ClipperLib_Z::Clipper zclipper; + ClipperZUtils::ClipperZIntersectionVisitor visitor(intersections); + zclipper.ZFillFunction(visitor.clipper_callback()); + // as closed contours + zclipper.AddPaths(ClipperZUtils::expolygons_to_zpaths(boundary, idx_boundary_end), ClipperLib_Z::ptClip, true); + // as open contours + std::vector> zsrc_splits; + { + 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); + 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. + AABBTreeBBoxes aabb_tree; + + // 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)); + // 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 && + 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. + 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) + 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, path.front() == path.back() ? ClipperLib::etClosedLine : 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); +} + +// Resulting regions are sorted by boundary id and source id. +std::vector propagate_waves(const WaveSeeds &seeds, const ExPolygons &boundary, const RegionExpansionParameters ¶ms) +{ + 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 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 }); + } + it = it2; + } + 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, + 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) +{ + 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{}); + 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();) { + 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 + 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. + 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())); + } + for (; last < uint32_t(src.size()); ++ last) + out.emplace_back(std::move(src[last])); + return out; +} + +} // Algorithm +} // Slic3r diff --git a/src/libslic3r/Algorithm/RegionExpansion.hpp b/src/libslic3r/Algorithm/RegionExpansion.hpp new file mode 100644 index 000000000..26aab198a --- /dev/null +++ b/src/libslic3r/Algorithm/RegionExpansion.hpp @@ -0,0 +1,114 @@ +#ifndef SRC_LIBSLIC3R_ALGORITHM_REGION_EXPANSION_HPP_ +#define SRC_LIBSLIC3R_ALGORITHM_REGION_EXPANSION_HPP_ + +#include +#include +#include +#include + +namespace Slic3r { +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, + // 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_merge_expolygons(ExPolygons &&src, const ExPolygons &boundary, const RegionExpansionParameters ¶ms); + +} // Algorithm +} // Slic3r + +#endif /* SRC_LIBSLIC3R_ALGORITHM_REGION_EXPANSION_HPP_ */ diff --git a/src/libslic3r/BoundingBox.hpp b/src/libslic3r/BoundingBox.hpp index 6c16d08b7..3c124fe2a 100644 --- a/src/libslic3r/BoundingBox.hpp +++ b/src/libslic3r/BoundingBox.hpp @@ -22,24 +22,9 @@ public: BoundingBoxBase(const PointClass &p1, const PointClass &p2, const PointClass &p3) : min(p1), max(p1), defined(false) { merge(p2); merge(p3); } - template > - BoundingBoxBase(It from, It to) : min(PointClass::Zero()), max(PointClass::Zero()) - { - if (from == to) { - this->defined = false; - // throw Slic3r::InvalidArgument("Empty point set supplied to BoundingBoxBase constructor"); - } else { - auto it = from; - this->min = it->template cast(); - this->max = this->min; - for (++ it; it != to; ++ it) { - auto vec = it->template cast(); - this->min = this->min.cwiseMin(vec); - this->max = this->max.cwiseMax(vec); - } - this->defined = (this->min.x() < this->max.x()) && (this->min.y() < this->max.y()); - } - } + template> + BoundingBoxBase(It from, It to) + { construct(*this, from, to); } BoundingBoxBase(const std::vector &points) : BoundingBoxBase(points.begin(), points.end()) @@ -70,6 +55,30 @@ public: } bool operator==(const BoundingBoxBase &rhs) { return this->min == rhs.min && this->max == rhs.max; } bool operator!=(const BoundingBoxBase &rhs) { return ! (*this == rhs); } + +private: + // to access construct() + friend BoundingBox get_extents(const Points &pts); + friend BoundingBox get_extents(const Points &pts); + + // if IncludeBoundary, then a bounding box is defined even for a single point. + // otherwise a bounding box is only defined if it has a positive area. + // The output bounding box is expected to be set to "undefined" initially. + template> + static void construct(BoundingBoxType &out, It from, It to) + { + if (from != to) { + auto it = from; + out.min = it->template cast(); + out.max = out.min; + for (++ it; it != to; ++ it) { + auto vec = it->template cast(); + out.min = out.min.cwiseMin(vec); + out.max = out.max.cwiseMax(vec); + } + out.defined = IncludeBoundary || (out.min.x() < out.max.x() && out.min.y() < out.max.y()); + } + } }; template diff --git a/src/libslic3r/BridgeDetector.hpp b/src/libslic3r/BridgeDetector.hpp index bc5da9712..cb1827306 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 auto [pc1, pc2] = compute_principal_components(overhang_area); if (pc2 == Vec2f::Zero()) { // overhang may be smaller than resolution. In this case, any direction is ok @@ -91,7 +88,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(); @@ -125,6 +121,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/Brim.cpp b/src/libslic3r/Brim.cpp index 8bd9b2fc2..bdc1a19c2 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 09a7ed65d..098131e00 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 AnyPtr.hpp BoundingBox.cpp BoundingBox.hpp @@ -36,6 +38,7 @@ set(SLIC3R_SOURCES clipper.hpp ClipperUtils.cpp ClipperUtils.hpp + ClipperZUtils.hpp Color.cpp Color.hpp Config.cpp @@ -79,6 +82,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/ClipperUtils.cpp b/src/libslic3r/ClipperUtils.cpp index f9d9f5f49..ed76fc66a 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 @@ -720,6 +718,8 @@ Slic3r::ExPolygons diff_ex(const Slic3r::Surfaces &subject, const Slic3r::Surfac { return _clipper_ex(ClipperLib::ctDifference, ClipperUtils::SurfacesProvider(subject), ClipperUtils::SurfacesProvider(clip), do_safety_offset); } Slic3r::ExPolygons diff_ex(const Slic3r::SurfacesPtr &subject, const Slic3r::Polygons &clip, ApplySafetyOffset do_safety_offset) { return _clipper_ex(ClipperLib::ctDifference, ClipperUtils::SurfacesPtrProvider(subject), ClipperUtils::PolygonsProvider(clip), do_safety_offset); } +Slic3r::ExPolygons diff_ex(const Slic3r::SurfacesPtr &subject, const Slic3r::ExPolygons &clip, ApplySafetyOffset do_safety_offset) + { return _clipper_ex(ClipperLib::ctDifference, ClipperUtils::SurfacesPtrProvider(subject), ClipperUtils::ExPolygonsProvider(clip), do_safety_offset); } Slic3r::ExPolygons intersection_ex(const Slic3r::Polygons &subject, const Slic3r::Polygons &clip, ApplySafetyOffset do_safety_offset) { return _clipper_ex(ClipperLib::ctIntersection, ClipperUtils::PolygonsProvider(subject), ClipperUtils::PolygonsProvider(clip), do_safety_offset); } @@ -1062,7 +1062,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 8f77e6da8..5f495788b 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 @@ -370,6 +373,8 @@ inline Slic3r::Polygons shrink(const Slic3r::Polygons &polygons, const float d { assert(delta > 0); return offset(polygons, -delta, joinType, miterLimit); } inline Slic3r::ExPolygons shrink_ex(const Slic3r::Polygons &polygons, const float delta, ClipperLib::JoinType joinType = DefaultJoinType, double miterLimit = DefaultMiterLimit) { assert(delta > 0); return offset_ex(polygons, -delta, joinType, miterLimit); } +inline Slic3r::ExPolygons shrink_ex(const Slic3r::ExPolygons &polygons, const float delta, ClipperLib::JoinType joinType = DefaultJoinType, double miterLimit = DefaultMiterLimit) + { assert(delta > 0); return offset_ex(polygons, -delta, joinType, miterLimit); } // Wherever applicable, please use the opening() / closing() variants instead, they convey their purpose better. // Input polygons for negative offset shall be "normalized": There must be no overlap / intersections between the input polygons. @@ -430,6 +435,7 @@ Slic3r::ExPolygons diff_ex(const Slic3r::Surfaces &subject, const Slic3r::ExPoly Slic3r::ExPolygons diff_ex(const Slic3r::ExPolygons &subject, const Slic3r::Surfaces &clip, ApplySafetyOffset do_safety_offset = ApplySafetyOffset::No); Slic3r::ExPolygons diff_ex(const Slic3r::Surfaces &subject, const Slic3r::Surfaces &clip, ApplySafetyOffset do_safety_offset = ApplySafetyOffset::No); Slic3r::ExPolygons diff_ex(const Slic3r::SurfacesPtr &subject, const Slic3r::Polygons &clip, ApplySafetyOffset do_safety_offset = ApplySafetyOffset::No); +Slic3r::ExPolygons diff_ex(const Slic3r::SurfacesPtr &subject, const Slic3r::ExPolygons &clip, ApplySafetyOffset do_safety_offset = ApplySafetyOffset::No); Slic3r::Polylines diff_pl(const Slic3r::Polyline &subject, const Slic3r::Polygons &clip); Slic3r::Polylines diff_pl(const Slic3r::Polylines &subject, const Slic3r::Polygons &clip); Slic3r::Polylines diff_pl(const Slic3r::Polyline &subject, const Slic3r::ExPolygon &clip); @@ -601,6 +607,6 @@ Polygons variable_offset_outer(const ExPolygon &expoly, const std::vector> &deltas, double miter_limit = 2.); ExPolygons variable_offset_inner_ex(const ExPolygon &expoly, const std::vector> &deltas, double miter_limit = 2.); -} +} // namespace Slic3r -#endif +#endif // slic3r_ClipperUtils_hpp_ diff --git a/src/libslic3r/ClipperZUtils.hpp b/src/libslic3r/ClipperZUtils.hpp new file mode 100644 index 000000000..4ae78ae23 --- /dev/null +++ b/src/libslic3r/ClipperZUtils.hpp @@ -0,0 +1,143 @@ +#ifndef slic3r_ClipperZUtils_hpp_ +#define slic3r_ClipperZUtils_hpp_ + +#include +#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(); })); + for (const ExPolygon &expoly : src) { + out.emplace_back(to_zpath(expoly.contour.points, base_idx)); + for (const Polygon &hole : expoly.holes) + out.emplace_back(to_zpath(hole.points, base_idx)); + ++ base_idx; + } + 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/Config.hpp b/src/libslic3r/Config.hpp index f29a79e0f..a1013f1fc 100644 --- a/src/libslic3r/Config.hpp +++ b/src/libslic3r/Config.hpp @@ -303,7 +303,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 @@ -847,9 +847,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/Fill/Fill.cpp b/src/libslic3r/Fill/Fill.cpp index e0e2e0ca8..40650c509 100644 --- a/src/libslic3r/Fill/Fill.cpp +++ b/src/libslic3r/Fill/Fill.cpp @@ -15,10 +15,12 @@ #include "FillRectilinear.hpp" #include "FillLightning.hpp" #include "FillConcentric.hpp" - +#include "FillEnsuring.hpp" namespace Slic3r { +static constexpr const float NarrowInfillAreaThresholdMM = 3.f; + struct SurfaceFillParams { // Zero based extruder ID. @@ -159,7 +161,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. @@ -302,6 +305,46 @@ std::vector group_fills(const Layer &layer) } } + // Detect narrow internal solid infill area and use ipEnsuring pattern instead. + { + std::vector narrow_expolygons; + 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(); + 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; } @@ -443,6 +486,14 @@ void Layer::make_fills(FillAdaptive::Octree* adaptive_fill_octree, FillAdaptive: size_t first_object_layer_id = this->object()->get_layer(0)->id(); 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); @@ -452,7 +503,9 @@ void Layer::make_fills(FillAdaptive::Octree* adaptive_fill_octree, FillAdaptive: f->layer_id = this->id() - first_object_layer_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) { auto *lf = dynamic_cast(f.get()); @@ -460,11 +513,10 @@ void Layer::make_fills(FillAdaptive::Octree* adaptive_fill_octree, FillAdaptive: lf->num_raft_layers = this->object()->slicing_parameters().raft_layers(); } - 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 == 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(); } // calculate flow spacing for infill pattern generation @@ -494,7 +546,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 == ipEnsuring; params.layer_height = layerm.layer()->height; for (ExPolygon &expoly : surface_fill.expolygons) { @@ -595,6 +647,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/Fill/FillBase.cpp b/src/libslic3r/Fill/FillBase.cpp index 6a0f41afb..f55420c31 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 @@ -50,6 +51,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 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 5200e1c0d..cf3766758 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 FillEnsuring). + const PrintConfig *print_config = nullptr; + const PrintObjectConfig *print_object_config = nullptr; + public: virtual ~Fill() {} virtual Fill* clone() const = 0; diff --git a/src/libslic3r/Fill/FillConcentric.cpp b/src/libslic3r/Fill/FillConcentric.cpp index 7b005ee35..245947cfe 100644 --- a/src/libslic3r/Fill/FillConcentric.cpp +++ b/src/libslic3r/Fill/FillConcentric.cpp @@ -97,14 +97,8 @@ void FillConcentric::_fill_surface_single(const FillParams ¶ms, continue; ThickPolyline thick_polyline = Arachne::to_thick_polyline(*extrusion); - if (extrusion->is_closed && thick_polyline.points.front() == thick_polyline.points.back() && thick_polyline.width.front() == thick_polyline.width.back()) { - thick_polyline.points.pop_back(); - assert(thick_polyline.points.size() * 2 == thick_polyline.width.size()); - int nearest_idx = nearest_point_index(thick_polyline.points, last_pos); - std::rotate(thick_polyline.points.begin(), thick_polyline.points.begin() + nearest_idx, thick_polyline.points.end()); - std::rotate(thick_polyline.width.begin(), thick_polyline.width.begin() + 2 * nearest_idx, thick_polyline.width.end()); - thick_polyline.points.emplace_back(thick_polyline.points.front()); - } + if (extrusion->is_closed) + thick_polyline.start_at_index(nearest_point_index(thick_polyline.points, last_pos)); thick_polylines_out.emplace_back(std::move(thick_polyline)); last_pos = thick_polylines_out.back().last_point(); } diff --git a/src/libslic3r/Fill/FillConcentric.hpp b/src/libslic3r/Fill/FillConcentric.hpp index 405b7238b..c059cc050 100644 --- a/src/libslic3r/Fill/FillConcentric.hpp +++ b/src/libslic3r/Fill/FillConcentric.hpp @@ -26,11 +26,6 @@ protected: ThickPolylines &thick_polylines_out) override; bool no_sort() const override { return true; } - - const PrintConfig *print_config = nullptr; - const PrintObjectConfig *print_object_config = nullptr; - - friend class Layer; }; } // namespace Slic3r diff --git a/src/libslic3r/Fill/FillEnsuring.cpp b/src/libslic3r/Fill/FillEnsuring.cpp new file mode 100644 index 000000000..0dab6d744 --- /dev/null +++ b/src/libslic3r/Fill/FillEnsuring.cpp @@ -0,0 +1,83 @@ +#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) +{ + 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); + + // 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) { + Point bbox_size = ex_poly.contour.bounding_box().size(); + 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()) { + 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()); + } + } + + return thick_polylines_out; +} + +} // namespace Slic3r diff --git a/src/libslic3r/Fill/FillEnsuring.hpp b/src/libslic3r/Fill/FillEnsuring.hpp new file mode 100644 index 000000000..faa080153 --- /dev/null +++ b/src/libslic3r/Fill/FillEnsuring.hpp @@ -0,0 +1,30 @@ +#ifndef slic3r_FillEnsuring_hpp_ +#define slic3r_FillEnsuring_hpp_ + +#include "FillBase.hpp" +#include "FillRectilinear.hpp" + +namespace Slic3r { + +class FillEnsuring : public FillRectilinear +{ +public: + Fill *clone() const override { return new FillEnsuring(*this); } + ~FillEnsuring() override = default; + Polylines fill_surface(const Surface *surface, const FillParams ¶ms) override { return {}; }; + ThickPolylines fill_surface_arachne(const Surface *surface, const FillParams ¶ms) override; + +protected: + void fill_surface_single_arachne(const Surface &surface, const FillParams ¶ms, ThickPolylines &thick_polylines_out); + + bool no_sort() const override { return true; } + + // PrintRegionConfig is used for computing overlap between boundary contour and inner Rectilinear infill. + const PrintRegionConfig *print_region_config = nullptr; + + friend class Layer; +}; + +} // namespace Slic3r + +#endif // slic3r_FillEnsuring_hpp_ diff --git a/src/libslic3r/Fill/FillRectilinear.hpp b/src/libslic3r/Fill/FillRectilinear.hpp index f5228440d..aa5c014b3 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 diff --git a/src/libslic3r/GCode/CoolingBuffer.cpp b/src/libslic3r/GCode/CoolingBuffer.cpp index 5e521f3c1..b8e37be84 100644 --- a/src/libslic3r/GCode/CoolingBuffer.cpp +++ b/src/libslic3r/GCode/CoolingBuffer.cpp @@ -378,7 +378,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; @@ -497,7 +498,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 @@ -846,7 +848,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/GCode/RetractWhenCrossingPerimeters.cpp b/src/libslic3r/GCode/RetractWhenCrossingPerimeters.cpp index 4be42d763..b834ca518 100644 --- a/src/libslic3r/GCode/RetractWhenCrossingPerimeters.cpp +++ b/src/libslic3r/GCode/RetractWhenCrossingPerimeters.cpp @@ -19,20 +19,7 @@ bool RetractWhenCrossingPerimeters::travel_inside_internal_regions(const Layer & if (surface.is_internal()) m_internal_islands.emplace_back(&surface.expolygon); // Calculate bounding boxes of internal slices. - class BBoxWrapper { - public: - BBoxWrapper(const size_t idx, const BoundingBox &bbox) : - m_idx(idx), - // Inflate the bounding box a bit to account for numerical issues. - m_bbox(bbox.min - Point(SCALED_EPSILON, SCALED_EPSILON), bbox.max + Point(SCALED_EPSILON, SCALED_EPSILON)) {} - size_t idx() const { return m_idx; } - const AABBTree::BoundingBox& bbox() const { return m_bbox; } - Point centroid() const { return ((m_bbox.min().cast() + m_bbox.max().cast()) / 2).cast(); } - private: - size_t m_idx; - AABBTree::BoundingBox m_bbox; - }; - std::vector bboxes; + std::vector bboxes; bboxes.reserve(m_internal_islands.size()); for (size_t i = 0; i < m_internal_islands.size(); ++ i) bboxes.emplace_back(i, get_extents(*m_internal_islands[i])); diff --git a/src/libslic3r/Geometry/MedialAxis.cpp b/src/libslic3r/Geometry/MedialAxis.cpp index 4f614169c..39677c928 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/Layer.cpp b/src/libslic3r/Layer.cpp index 6655b6764..7962f0376 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" @@ -302,48 +302,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/Layer.hpp b/src/libslic3r/Layer.hpp index e9c8ecbac..a59c029b8 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(); @@ -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/LayerRegion.cpp b/src/libslic3r/LayerRegion.cpp index 0f42b2107..0777c5ef7 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 @@ -26,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. @@ -139,6 +140,273 @@ 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; + 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 ++, bridge_expansions.end() }); + 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 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(it_end - it_begin); + for (auto it2 = it_begin; it2 != it_end; ++ it2) + bboxes.emplace_back(get_extents(it2->expolygon.contour)); + // For each bridge anchor of the current source: + for (; it != it_end; ++ it) { + // A grup id for this bridge. + for (auto it2 = std::next(it); it2 != it_end; ++ it2) + if (it->src_id != it2->src_id && + 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. + 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; + Polygons anchor_areas; + for (uint32_t bridge_id = 0; bridge_id < uint32_t(bridges.size()); ++ bridge_id) { + Bridge &bridge = bridges[bridge_id]; +// 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, 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 + // 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, {} }; + 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))); + 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; + // 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_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 + { + 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 +414,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 +433,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 +442,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 +501,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 +529,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 +648,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 +699,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/PerimeterGenerator.cpp b/src/libslic3r/PerimeterGenerator.cpp index c71d95059..ead8298b0 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/PlaceholderParser.cpp b/src/libslic3r/PlaceholderParser.cpp index 059cbdac4..2dd5e8f8e 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); } @@ -912,7 +918,7 @@ namespace client template static void evaluate_index(expr &expr_index, int &output) { - if (expr_index.type != expr::TYPE_INT) + if (expr_index.type() != expr::TYPE_INT) expr_index.throw_exception("Non-integer index is not allowed to address a vector variable."); output = expr_index.i(); } diff --git a/src/libslic3r/PlaceholderParser.hpp b/src/libslic3r/PlaceholderParser.hpp index 6157ffe3c..fc184be77 100644 --- a/src/libslic3r/PlaceholderParser.hpp +++ b/src/libslic3r/PlaceholderParser.hpp @@ -5,6 +5,7 @@ #include #include #include +#include #include #include "PrintConfig.hpp" @@ -38,6 +39,8 @@ public: // Add new ConfigOption values to m_config. void set(const std::string &key, const std::string &value) { this->set(key, new ConfigOptionString(value)); } + void set(const std::string &key, std::string_view value) { this->set(key, new ConfigOptionString(std::string(value))); } + void set(const std::string &key, const char *value) { this->set(key, new ConfigOptionString(value)); } void set(const std::string &key, int value) { this->set(key, new ConfigOptionInt(value)); } void set(const std::string &key, unsigned int value) { this->set(key, int(value)); } void set(const std::string &key, bool value) { this->set(key, new ConfigOptionBool(value)); } diff --git a/src/libslic3r/Point.cpp b/src/libslic3r/Point.cpp index beb496b28..09afcc399 100644 --- a/src/libslic3r/Point.cpp +++ b/src/libslic3r/Point.cpp @@ -84,18 +84,28 @@ Points collect_duplicates(Points pts /* Copy */) return duplicits; } +template BoundingBox get_extents(const Points &pts) { - return BoundingBox(pts); + 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); +// 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)); + 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) { diff --git a/src/libslic3r/Point.hpp b/src/libslic3r/Point.hpp index 7ddbcb20b..d53352f28 100644 --- a/src/libslic3r/Point.hpp +++ b/src/libslic3r/Point.hpp @@ -237,8 +237,20 @@ 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); +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); +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); int nearest_point_index(const Points &points, const Point &pt); diff --git a/src/libslic3r/Polyline.cpp b/src/libslic3r/Polyline.cpp index e2dcabfe1..d805473b5 100644 --- a/src/libslic3r/Polyline.cpp +++ b/src/libslic3r/Polyline.cpp @@ -292,6 +292,19 @@ void ThickPolyline::clip_end(double distance) assert(this->width.size() == (this->points.size() - 1) * 2); } +void ThickPolyline::start_at_index(int index) +{ + assert(index >= 0 && index < this->points.size()); + assert(this->points.front() == this->points.back() && this->width.front() == this->width.back()); + if (index != 0 && index != (this->points.size() - 1) && this->points.front() == this->points.back() && this->width.front() == this->width.back()) { + this->points.pop_back(); + assert(this->points.size() * 2 == this->width.size()); + std::rotate(this->points.begin(), this->points.begin() + index, this->points.end()); + std::rotate(this->width.begin(), this->width.begin() + 2 * index, this->width.end()); + this->points.emplace_back(this->points.front()); + } +} + double Polyline3::length() const { double l = 0; diff --git a/src/libslic3r/Polyline.hpp b/src/libslic3r/Polyline.hpp index 8ca8e4f16..8e362f037 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); @@ -166,6 +185,7 @@ struct ThickPolyline { const Point& first_point() const { return this->points.front(); } const Point& last_point() const { return this->points.back(); } + size_t size() const { return this->points.size(); } bool is_valid() const { return this->points.size() >= 2; } double length() const { return Slic3r::length(this->points); } @@ -179,6 +199,11 @@ struct ThickPolyline { 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); + Points points; std::vector width; std::pair endpoints { false, false }; diff --git a/src/libslic3r/Preset.cpp b/src/libslic3r/Preset.cpp index e2fb57b17..9fa3c7430 100644 --- a/src/libslic3r/Preset.cpp +++ b/src/libslic3r/Preset.cpp @@ -786,7 +786,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/PrintConfig.cpp b/src/libslic3r/PrintConfig.cpp index 3425cfd7a..ba1c7f056 100644 --- a/src/libslic3r/PrintConfig.cpp +++ b/src/libslic3r/PrintConfig.cpp @@ -591,7 +591,7 @@ void PrintConfigDef::init_fff_params() "Fan speeds for overhang sizes in between are calculated via linear interpolation. "); def = this->add("overhang_fan_speed_0", coInts); - def->label = L("speed for 0\% overlap (bridge)"); + def->label = L("speed for 0% overlap (bridge)"); def->tooltip = fan_speed_setting_description; def->sidetext = L("%"); def->min = 0; @@ -600,7 +600,7 @@ void PrintConfigDef::init_fff_params() def->set_default_value(new ConfigOptionInts{0}); def = this->add("overhang_fan_speed_1", coInts); - def->label = L("speed for 25\% overlap"); + def->label = L("speed for 25% overlap"); def->tooltip = fan_speed_setting_description; def->sidetext = L("%"); def->min = 0; @@ -609,7 +609,7 @@ void PrintConfigDef::init_fff_params() def->set_default_value(new ConfigOptionInts{0}); def = this->add("overhang_fan_speed_2", coInts); - def->label = L("speed for 50\% overlap"); + def->label = L("speed for 50% overlap"); def->tooltip = fan_speed_setting_description; def->sidetext = L("%"); def->min = 0; @@ -618,7 +618,7 @@ void PrintConfigDef::init_fff_params() def->set_default_value(new ConfigOptionInts{0}); def = this->add("overhang_fan_speed_3", coInts); - def->label = L("speed for 75\% overlap"); + def->label = L("speed for 75% overlap"); def->tooltip = fan_speed_setting_description; def->sidetext = L("%"); def->min = 0; diff --git a/src/libslic3r/PrintConfig.hpp b/src/libslic3r/PrintConfig.hpp index dde60bee4..df57ab1da 100644 --- a/src/libslic3r/PrintConfig.hpp +++ b/src/libslic3r/PrintConfig.hpp @@ -60,6 +60,7 @@ enum InfillPattern : int { ipRectilinear, ipMonotonic, ipMonotonicLines, ipAlignedRectilinear, ipGrid, ipTriangles, ipStars, ipCubic, ipLine, ipConcentric, ipHoneycomb, ip3DHoneycomb, ipGyroid, ipHilbertCurve, ipArchimedeanChords, ipOctagramSpiral, ipAdaptiveCubic, ipSupportCubic, ipSupportBase, ipLightning, + ipEnsuring, ipCount, }; @@ -162,6 +163,7 @@ CONFIG_OPTION_ENUM_DECLARE_STATIC_MAPS(GCodeThumbnailsFormat) CONFIG_OPTION_ENUM_DECLARE_STATIC_MAPS(ForwardCompatibilitySubstitutionRule) CONFIG_OPTION_ENUM_DECLARE_STATIC_MAPS(PerimeterGeneratorType) + #undef CONFIG_OPTION_ENUM_DECLARE_STATIC_MAPS // Defines each and every confiuration option of Slic3r, including the properties of the GUI dialogs. diff --git a/src/libslic3r/PrintObject.cpp b/src/libslic3r/PrintObject.cpp index 0589b2859..f87741cfa 100644 --- a/src/libslic3r/PrintObject.cpp +++ b/src/libslic3r/PrintObject.cpp @@ -1,6 +1,12 @@ +#include "AABBTreeLines.hpp" +#include "BridgeDetector.hpp" +#include "ExPolygon.hpp" #include "Exception.hpp" +#include "Flow.hpp" #include "KDTreeIndirect.hpp" #include "Point.hpp" +#include "Polygon.hpp" +#include "Polyline.hpp" #include "Print.hpp" #include "BoundingBox.hpp" #include "ClipperUtils.hpp" @@ -28,9 +34,15 @@ #include #include +#include #include +#include +#include +#include +#include #include #include +#include #include #include @@ -60,6 +72,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. @@ -270,6 +284,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. @@ -282,17 +312,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 */ @@ -373,6 +399,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(); @@ -706,15 +733,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. @@ -983,9 +1002,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 */ @@ -1073,7 +1092,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; @@ -1099,6 +1118,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()) { @@ -1124,7 +1145,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]); } } @@ -1171,6 +1194,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. @@ -1192,7 +1218,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(); @@ -1206,14 +1232,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, 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), 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; @@ -1273,19 +1299,19 @@ 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]; - 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, 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), 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) @@ -1315,8 +1341,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); @@ -1420,33 +1446,32 @@ 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 */ // 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()) @@ -1461,31 +1486,36 @@ void PrintObject::discover_vertical_shells() #ifdef SLIC3R_DEBUG_SLICE_PROCESSING 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); - if (shell.empty()) - continue; -#else - // Ensure each region is at least 3x infill line width wide, so it could be filled in. - // float margin = float(infill_line_spacing) * 3.f; - float margin = float(infill_line_spacing) * 1.5f; - // we use a higher miterLimit here to handle areas with acute angles - // in those cases, the default miterLimit would cut the corner and we'd - // get a triangle in $too_narrow; if we grow it below then the shell - // would have a different shape from the external surface and we'd still - // have the same angle, so the next shell would be grown even more and so on. - Polygons too_narrow = diff(shell, opening(shell, margin, ClipperLib::jtMiter, 5.), true); - if (! too_narrow.empty()) { - // grow the collapsing parts and add the extra area to the neighbor layer - // as well as to our original surfaces so that we support this - // additional area in the next shell too - // make sure our grown surfaces don't exceed the fill area - polygons_append(shell, intersection(offset(too_narrow, margin), polygonsInternal)); + ExPolygons regularized_shell; + { + // Open to remove (filter out) regions narrower than a bit less than an infill extrusion line width. + // Such narrow regions are difficult to fill in with a gap fill algorithm (or Arachne), however they are most likely + // not needed for print stability / quality. + const float narrow_ensure_vertical_wall_thickness_region_radius = 0.5f * 0.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; + regularized_shell = shrink_ex(offset2_ex(union_ex(shell), + // Open to remove (filter out) regions narrower than an infill extrusion line width. + -narrow_ensure_vertical_wall_thickness_region_radius, + // Then close gaps narrower than 1.2 * line width, such gaps are difficult to fill in with sparse infill. + narrow_ensure_vertical_wall_thickness_region_radius + narrow_sparse_infill_region_radius, ClipperLib::jtSquare), + // Finally expand the infill a bit to remove tiny gaps between solid infill and the other regions. + narrow_sparse_infill_region_radius - tiny_overlap_radius, ClipperLib::jtSquare); + + // The opening operation may cause scattered tiny drops on the smooth parts of the model, filter them out + regularized_shell.erase(std::remove_if(regularized_shell.begin(), regularized_shell.end(), + [&min_perimeter_infill_spacing](const ExPolygon &p) { + return p.area() < min_perimeter_infill_spacing * scaled(8.0); + }), + regularized_shell.end()); } -#endif - ExPolygons new_internal_solid = intersection_ex(polygonsInternal, shell); + if (regularized_shell.empty()) + continue; + + ExPolygons new_internal_solid = intersection_ex(polygonsInternal, regularized_shell); #ifdef SLIC3R_DEBUG_SLICE_PROCESSING { Slic3r::SVG svg(debug_out_path("discover_vertical_shells-regularized-%d.svg", debug_idx), get_extents(shell_before)); @@ -1500,8 +1530,8 @@ void PrintObject::discover_vertical_shells() #endif /* SLIC3R_DEBUG_SLICE_PROCESSING */ // Trim the internal & internalvoid by the shell. - Slic3r::ExPolygons new_internal = diff_ex(layerm->fill_surfaces().filter_by_type(stInternal), shell); - Slic3r::ExPolygons new_internal_void = diff_ex(layerm->fill_surfaces().filter_by_type(stInternalVoid), shell); + Slic3r::ExPolygons new_internal = diff_ex(layerm->fill_surfaces().filter_by_type(stInternal), regularized_shell); + Slic3r::ExPolygons new_internal_void = diff_ex(layerm->fill_surfaces().filter_by_type(stInternalVoid), regularized_shell); #ifdef SLIC3R_DEBUG_SLICE_PROCESSING { @@ -1512,8 +1542,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); @@ -1525,142 +1554,619 @@ 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 } // 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() { - BOOST_LOG_TRIVIAL(info) << "Bridge over infill..." << log_memory_info(); + BOOST_LOG_TRIVIAL(info) << "Bridge over infill - Start" << log_memory_info(); - 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; + 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; + }; - // 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)); - } + std::unordered_map> bridging_surfaces; - // 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); + tbb::parallel_for(tbb::blocked_range(0, this->layers().size()), [po = this, + &bridging_surfaces](tbb::blocked_range r) { + for (size_t lidx = r.begin(); lidx < r.end(); lidx++) { + const Layer *layer = po->get_layer(lidx); - // 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; + // 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) { + AABBTreeLines::LinesDistancer slice_island_tree{to_lines(layer->lslices[int(&slice - layer->lslices_ex.data())])}; + std::unordered_set regions_to_check; - // 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) + // 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(layer->regions()[island.perimeters.region()]); + if (!island.fill_expolygons_composite()) { + regions_to_check.insert(layer->regions()[island.fill_region_id]); + } else { + for (const auto& r : layer->regions()) { + regions_to_check.insert(r); + } 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. + + for ( const LayerRegion *region : regions_to_check) { + SurfacesPtr region_internal_solids = region->fill_surfaces().filter_by_type(stInternalSolid); + + // 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](const Surface *s) { + if (slice_island_tree.outside(s->expolygon.contour.first_point()) > 0) { + return true; + } + return false; + }), + 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, true).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; + } + + // 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()) { + continue; + }; + + auto region_has_special_infill = [](const LayerRegion *layer_region) { + switch (layer_region->region().config().fill_pattern.value) { + case ipAdaptiveCubic: return true; + case ipSupportCubic: return true; + case ipLightning: return true; + default: return false; + } + }; + + // Gather lower layers sparse infill areas, to depth defined by used bridge flow + Polygons lower_layers_sparse_infill{}; + Polygons special_infill{}; + Polygons not_sparse_infill{}; { - float min_width = float(bridge_flow.scaled_width()) * 3.f; - to_bridge_pp = opening(to_bridge_pp, min_width); + 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) + 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 (const LayerRegion *region : regions_under_to_check) { + 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 (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()); + } + } + } + } + current_links = next_links; + next_links.clear(); + } + + 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 (shrink(lower_layers_sparse_infill, 3.0 * scale_(max_bridge_flow_height[candidates.first])).empty()) { + continue; + } } - - 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))); + + 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) { + bridging_surfaces[candidates.first].emplace_back(candidate, to_polygons(candidate->expolygon), + surface_to_region[candidate], 0); + } 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 - - // 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; + 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()); + } + + // 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(); + }); + + 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))}; } - - $excess -= $self->get_layer($i)->height; + } + + // 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, true); + assert(candidate->surface_type == stInternalSolid); + + 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()); + 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, 0.5 * 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); + 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(std::to_string(lidx) + "candidate", to_lines(candidate->expolygon), to_lines(bridged_area), + to_lines(max_area), (anchors_and_walls)); +#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; + 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); + double angle = lines_tree.get_line(index).orientation(); + if (angle > PI) { + angle -= PI; + } + angle += PI * 0.5; + counted_directions[angle]++; + } + } + } + + 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.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; + 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}; + } + } + bridging_angle = best_dir.first; + if (bridging_angle == 0) { + bridging_angle = 0.001; + } + 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; + } + } + + 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)); + } + }; + + 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{}; + 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)); + 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)}; + +#ifdef DEBUG_BRIDGE_OVER_INFILL + debug_draw(std::to_string(lidx) + "sliced", 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) < 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 (Line §ion : polygon_sections[i]) { + 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(); + }); + 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(), + section.b, + [](const Point &a, const std::pair &b) { + return a.y() < b.first.y(); + }); + if (maybe_upper_anchor != anchors_intersections.end()) { + section.b = maybe_upper_anchor->first; + section.b.y() += flow.scaled_width() * (0.5 + 1.0); + } + } + + for (int section_idx = 0; section_idx < int(polygon_sections[i].size()) - 1; section_idx++) { + 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; + } + } + + 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 + struct TracedPoly + { + std::vector lows; + std::vector highs; + }; + + std::vector current_traced_polys; + 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(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 != 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 + 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(); + traced_poly.highs.clear(); + } + } + + 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 : 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); + new_tp.highs.push_back(segment.b - Point{flow.scaled_spacing() / 2, 0}); + new_tp.highs.push_back(segment.b); + } + } + } + + // 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()); + } + +#ifdef DEBUG_BRIDGE_OVER_INFILL + Lines l{}; + for (const auto &s : polygon_sections) { + l.insert(l.end(), s.begin(), s.end()); + } + 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 + } + + 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); + + 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), + to_lines(max_area), to_lines(expand_area)); +#endif } } - */ -#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(); } - }); + }); + + 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, + &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 = 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()]); + if (!island.fill_expolygons_composite()) { + regions_to_check.insert(layer->regions()[island.fill_region_id]); + } else { + for (LayerRegion *r : layer->regions()) { + regions_to_check.insert(r); + } + break; + } + } + + 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 (LayerRegion *region : regions_to_check) { + 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, {}); + for (const ExPolygon &expoly : diff_ex(surface.expolygon, s.new_polys)) { + 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; + 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()); + 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()); + } + } + } + } + }); + + BOOST_LOG_TRIVIAL(info) << "Bridge over infill - End" << log_memory_info(); + } // void PrintObject::bridge_over_infill() static void clamp_exturder_to_default(ConfigOptionInt &opt, size_t num_extruders) @@ -1913,12 +2419,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 @@ -2101,8 +2606,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/SupportMaterial.cpp b/src/libslic3r/SupportMaterial.cpp index 965a9398f..224216466 100644 --- a/src/libslic3r/SupportMaterial.cpp +++ b/src/libslic3r/SupportMaterial.cpp @@ -4755,4 +4755,3 @@ sub clip_with_shape { */ } // namespace Slic3r - diff --git a/src/libslic3r/SupportSpotsGenerator.cpp b/src/libslic3r/SupportSpotsGenerator.cpp index d45f82db8..5062fe18a 100644 --- a/src/libslic3r/SupportSpotsGenerator.cpp +++ b/src/libslic3r/SupportSpotsGenerator.cpp @@ -901,7 +901,7 @@ std::tuple check_stability(const PrintObject *po, params)) { if (bridge.support_point_generated.has_value()) { reckon_new_support_point(*bridge.support_point_generated, create_support_point_position(bridge.b), - -EPSILON, Vec2f::Zero()); + float(-EPSILON), Vec2f::Zero()); } } } @@ -916,7 +916,7 @@ std::tuple check_stability(const PrintObject *po, params); for (const ExtrusionLine &perim : perims) { if (perim.support_point_generated.has_value()) { - reckon_new_support_point(*perim.support_point_generated, create_support_point_position(perim.b), -EPSILON, + reckon_new_support_point(*perim.support_point_generated, create_support_point_position(perim.b), float(-EPSILON), Vec2f::Zero()); } if (perim.is_external_perimeter()) { diff --git a/src/libslic3r/SupportSpotsGenerator.hpp b/src/libslic3r/SupportSpotsGenerator.hpp index 018b856e0..543b9c92b 100644 --- a/src/libslic3r/SupportSpotsGenerator.hpp +++ b/src/libslic3r/SupportSpotsGenerator.hpp @@ -145,6 +145,7 @@ struct PartialObject using PartialObjects = std::vector; +// Both support points and partial objects are sorted from the lowest z to the highest std::tuple full_search(const PrintObject *po, const PrintTryCancel& cancel_func, const Params ¶ms); void estimate_supports_malformations(std::vector &layers, float supports_flow_width, const Params ¶ms); diff --git a/src/libslic3r/Surface.hpp b/src/libslic3r/Surface.hpp index 4afcdf6f5..1f352e977 100644 --- a/src/libslic3r/Surface.hpp +++ b/src/libslic3r/Surface.hpp @@ -33,40 +33,31 @@ class Surface public: SurfaceType surface_type; ExPolygon expolygon; - double thickness; // in mm - unsigned short thickness_layers; // in layers - double bridge_angle; // in radians, ccw, 0 = East, only 0+ (negative means undefined) - unsigned short extra_perimeters; + double thickness { -1 }; // in mm + unsigned short thickness_layers { 1 }; // in layers + double bridge_angle { -1. }; // in radians, ccw, 0 = East, only 0+ (negative means undefined) + unsigned short extra_perimeters { 0 }; - Surface(const Slic3r::Surface &rhs) - : surface_type(rhs.surface_type), expolygon(rhs.expolygon), - thickness(rhs.thickness), thickness_layers(rhs.thickness_layers), - bridge_angle(rhs.bridge_angle), extra_perimeters(rhs.extra_perimeters) - {}; - - Surface(SurfaceType _surface_type, const ExPolygon &_expolygon) - : surface_type(_surface_type), expolygon(_expolygon), - thickness(-1), thickness_layers(1), bridge_angle(-1), extra_perimeters(0) - {}; - Surface(const Surface &other, const ExPolygon &_expolygon) - : surface_type(other.surface_type), expolygon(_expolygon), - thickness(other.thickness), thickness_layers(other.thickness_layers), - bridge_angle(other.bridge_angle), extra_perimeters(other.extra_perimeters) - {}; - Surface(Surface &&rhs) - : surface_type(rhs.surface_type), expolygon(std::move(rhs.expolygon)), - thickness(rhs.thickness), thickness_layers(rhs.thickness_layers), - bridge_angle(rhs.bridge_angle), extra_perimeters(rhs.extra_perimeters) - {}; - Surface(SurfaceType _surface_type, ExPolygon &&_expolygon) - : surface_type(_surface_type), expolygon(std::move(_expolygon)), - thickness(-1), thickness_layers(1), bridge_angle(-1), extra_perimeters(0) - {}; - Surface(const Surface &other, ExPolygon &&_expolygon) - : surface_type(other.surface_type), expolygon(std::move(_expolygon)), - thickness(other.thickness), thickness_layers(other.thickness_layers), - bridge_angle(other.bridge_angle), extra_perimeters(other.extra_perimeters) - {}; + Surface(const Slic3r::Surface &rhs) : + surface_type(rhs.surface_type), expolygon(rhs.expolygon), + thickness(rhs.thickness), thickness_layers(rhs.thickness_layers), + bridge_angle(rhs.bridge_angle), extra_perimeters(rhs.extra_perimeters) {} + Surface(SurfaceType surface_type, const ExPolygon &expolygon) : + surface_type(surface_type), expolygon(expolygon) {} + Surface(const Surface &templ, const ExPolygon &expolygon) : + surface_type(templ.surface_type), expolygon(expolygon), + thickness(templ.thickness), thickness_layers(templ.thickness_layers), + bridge_angle(templ.bridge_angle), extra_perimeters(templ.extra_perimeters) {} + Surface(Surface &&rhs) : + surface_type(rhs.surface_type), expolygon(std::move(rhs.expolygon)), + thickness(rhs.thickness), thickness_layers(rhs.thickness_layers), + bridge_angle(rhs.bridge_angle), extra_perimeters(rhs.extra_perimeters) {} + Surface(SurfaceType surface_type, ExPolygon &&expolygon) : + surface_type(surface_type), expolygon(std::move(expolygon)) {} + Surface(const Surface &templ, ExPolygon &&expolygon) : + surface_type(templ.surface_type), expolygon(std::move(expolygon)), + thickness(templ.thickness), thickness_layers(templ.thickness_layers), + bridge_angle(templ.bridge_angle), extra_perimeters(templ.extra_perimeters) {} Surface& operator=(const Surface &rhs) { diff --git a/src/libslic3r/SurfaceCollection.cpp b/src/libslic3r/SurfaceCollection.cpp index 10a12b683..fbc30ca63 100644 --- a/src/libslic3r/SurfaceCollection.cpp +++ b/src/libslic3r/SurfaceCollection.cpp @@ -51,16 +51,12 @@ SurfacesPtr SurfaceCollection::filter_by_type(const SurfaceType type) const return ss; } -SurfacesPtr SurfaceCollection::filter_by_types(const SurfaceType *types, int ntypes) const +SurfacesPtr SurfaceCollection::filter_by_types(std::initializer_list types) const { SurfacesPtr ss; for (const Surface &surface : this->surfaces) - for (int i = 0; i < ntypes; ++ i) { - if (surface.surface_type == types[i]) { - ss.push_back(&surface); - break; - } - } + if (std::find(types.begin(), types.end(), surface.surface_type) != types.end()) + ss.push_back(&surface); return ss; } @@ -85,23 +81,15 @@ void SurfaceCollection::keep_type(const SurfaceType type) surfaces.erase(surfaces.begin() + j, surfaces.end()); } -void SurfaceCollection::keep_types(const SurfaceType *types, int ntypes) +void SurfaceCollection::keep_types(std::initializer_list types) { size_t j = 0; - for (size_t i = 0; i < surfaces.size(); ++ i) { - bool keep = false; - for (int k = 0; k < ntypes; ++ k) { - if (surfaces[i].surface_type == types[k]) { - keep = true; - break; - } - } - if (keep) { + for (size_t i = 0; i < surfaces.size(); ++ i) + if (std::find(types.begin(), types.end(), surfaces[i].surface_type) != types.end()) { if (j < i) std::swap(surfaces[i], surfaces[j]); ++ j; } - } if (j < surfaces.size()) surfaces.erase(surfaces.begin() + j, surfaces.end()); } @@ -136,23 +124,15 @@ void SurfaceCollection::remove_type(const SurfaceType type, ExPolygons *polygons surfaces.erase(surfaces.begin() + j, surfaces.end()); } -void SurfaceCollection::remove_types(const SurfaceType *types, int ntypes) +void SurfaceCollection::remove_types(std::initializer_list types) { size_t j = 0; - for (size_t i = 0; i < surfaces.size(); ++ i) { - bool remove = false; - for (int k = 0; k < ntypes; ++ k) { - if (surfaces[i].surface_type == types[k]) { - remove = true; - break; - } - } - if (! remove) { + for (size_t i = 0; i < surfaces.size(); ++ i) + if (std::find(types.begin(), types.end(), surfaces[i].surface_type) == types.end()) { if (j < i) std::swap(surfaces[i], surfaces[j]); ++ j; } - } if (j < surfaces.size()) surfaces.erase(surfaces.begin() + j, surfaces.end()); } diff --git a/src/libslic3r/SurfaceCollection.hpp b/src/libslic3r/SurfaceCollection.hpp index b81808b32..0f62875b2 100644 --- a/src/libslic3r/SurfaceCollection.hpp +++ b/src/libslic3r/SurfaceCollection.hpp @@ -3,6 +3,7 @@ #include "libslic3r.h" #include "Surface.hpp" +#include #include namespace Slic3r { @@ -27,11 +28,11 @@ public: return false; } SurfacesPtr filter_by_type(const SurfaceType type) const; - SurfacesPtr filter_by_types(const SurfaceType *types, int ntypes) const; + SurfacesPtr filter_by_types(std::initializer_list types) const; void keep_type(const SurfaceType type); - void keep_types(const SurfaceType *types, int ntypes); + void keep_types(std::initializer_list types); void remove_type(const SurfaceType type); - void remove_types(const SurfaceType *types, int ntypes); + void remove_types(std::initializer_list types); void filter_by_type(SurfaceType type, Polygons *polygons) const; void remove_type(const SurfaceType type, ExPolygons *polygons); void set_type(SurfaceType type) { diff --git a/src/libslic3r/TreeSupport.cpp b/src/libslic3r/TreeSupport.cpp index 752a45577..2906f6b93 100644 --- a/src/libslic3r/TreeSupport.cpp +++ b/src/libslic3r/TreeSupport.cpp @@ -2421,7 +2421,6 @@ static void merge_influence_areas( 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; // 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); throw_on_cancel(); @@ -4012,8 +4011,12 @@ static indexed_triangle_set 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]; @@ -4078,19 +4081,22 @@ static indexed_triangle_set 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); } diff --git a/src/libslic3r/TriangleMeshSlicer.cpp b/src/libslic3r/TriangleMeshSlicer.cpp index 85e170bd0..96f61ba8d 100644 --- a/src/libslic3r/TriangleMeshSlicer.cpp +++ b/src/libslic3r/TriangleMeshSlicer.cpp @@ -62,6 +62,8 @@ public: // Inherits coord_t x, y }; +#define DEBUG_INTERSECTIONLINE (! defined(NDEBUG) || defined(SLIC3R_DEBUG_SLICE_PROCESSING)) + class IntersectionLine : public Line { public: @@ -119,14 +121,14 @@ public: }; uint32_t flags { 0 }; -#ifndef NDEBUG +#if DEBUG_INTERSECTIONLINE enum class Source { BottomPlane, TopPlane, Slab, }; Source source { Source::BottomPlane }; -#endif // NDEBUG +#endif }; using IntersectionLines = std::vector; @@ -1440,24 +1442,24 @@ static std::vector make_slab_loops( for (const IntersectionLine &l : lines.at_slice[slice_below]) if (l.edge_type != IntersectionLine::FacetEdgeType::Top) { in.emplace_back(l); -#ifndef NDEBUG +#if DEBUG_INTERSECTIONLINE in.back().source = IntersectionLine::Source::BottomPlane; -#endif // NDEBUG +#endif // DEBUG_INTERSECTIONLINE } } { // Edges in between slice_below and slice_above. -#ifndef NDEBUG +#if DEBUG_INTERSECTIONLINE size_t old_size = in.size(); -#endif // NDEBUG +#endif // DEBUG_INTERSECTIONLINE // Edge IDs of end points on in-between lines that touch the layer above are already increased with num_edges. append(in, lines.between_slices[line_idx]); -#ifndef NDEBUG +#if DEBUG_INTERSECTIONLINE for (auto it = in.begin() + old_size; it != in.end(); ++ it) { assert(it->edge_type == IntersectionLine::FacetEdgeType::Slab); it->source = IntersectionLine::Source::Slab; } -#endif // NDEBUG +#endif // DEBUG_INTERSECTIONLINE } if (has_slice_above) { for (const IntersectionLine &lsrc : lines.at_slice[slice_above]) @@ -1470,9 +1472,9 @@ static std::vector make_slab_loops( l.edge_a_id += num_edges; if (l.edge_b_id >= 0) l.edge_b_id += num_edges; -#ifndef NDEBUG +#if DEBUG_INTERSECTIONLINE l.source = IntersectionLine::Source::TopPlane; -#endif // NDEBUG +#endif // DEBUG_INTERSECTIONLINE } } if (! in.empty()) { diff --git a/src/libslic3r/libslic3r.h b/src/libslic3r/libslic3r.h index 2b96994fa..856208c9d 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/t/shells.t b/t/shells.t deleted file mode 100644 index cf6a7dce9..000000000 --- a/t/shells.t +++ /dev/null @@ -1,268 +0,0 @@ -use Test::More tests => 17; -use strict; -use warnings; - -BEGIN { - use FindBin; - use lib "$FindBin::Bin/../lib"; - use local::lib "$FindBin::Bin/../local-lib"; -} - -use List::Util qw(first sum); -use Slic3r; -use Slic3r::Geometry qw(epsilon); -use Slic3r::Test; - -# issue #1161 -{ - my $config = Slic3r::Config::new_from_defaults; - $config->set('layer_height', 0.3); - $config->set('first_layer_height', $config->layer_height); - $config->set('bottom_solid_layers', 0); - $config->set('top_solid_layers', 3); - $config->set('cooling', [ 0 ]); - $config->set('bridge_speed', 99); - $config->set('solid_infill_speed', 99); - $config->set('top_solid_infill_speed', 99); - $config->set('first_layer_speed', '100%'); - $config->set('enable_dynamic_overhang_speeds', 0); # prevent speed alteration - - my $print = Slic3r::Test::init_print('V', config => $config); - my %layers_with_solid_infill = (); # Z => 1 - Slic3r::GCode::Reader->new->parse(Slic3r::Test::gcode($print), sub { - my ($self, $cmd, $args, $info) = @_; - - $layers_with_solid_infill{$self->Z} = 1 - if $info->{extruding} && ($args->{F} // $self->F) == $config->solid_infill_speed*60; - }); - is scalar(map $layers_with_solid_infill{$_}, grep $_ <= 7.2, keys %layers_with_solid_infill), 3, - "correct number of top solid shells is generated in V-shaped object"; -} - -{ - my $config = Slic3r::Config::new_from_defaults; - # we need to check against one perimeter because this test is calibrated - # (shape, extrusion_width) so that perimeters cover the bottom surfaces of - # their lower layer - the test checks that shells are not generated on the - # above layers (thus 'across' the shadow perimeter) - # the test is actually calibrated to leave a narrow bottom region for each - # layer - we test that in case of fill_density = 0 such narrow shells are - # discarded instead of grown - $config->set('perimeters', 1); - $config->set('fill_density', 0); - $config->set('cooling', [ 0 ]); # prevent speed alteration - $config->set('first_layer_speed', '100%'); # prevent speed alteration - $config->set('enable_dynamic_overhang_speeds', 0); # prevent speed alteration - $config->set('layer_height', 0.4); - $config->set('first_layer_height', $config->layer_height); - $config->set('extrusion_width', 0.55); - $config->set('bottom_solid_layers', 3); - $config->set('top_solid_layers', 0); - $config->set('solid_infill_speed', 99); - - my $print = Slic3r::Test::init_print('V', config => $config); - my %layers = (); # Z => 1 - Slic3r::GCode::Reader->new->parse(Slic3r::Test::gcode($print), sub { - my ($self, $cmd, $args, $info) = @_; - $layers{$self->Z} = 1 - if $info->{extruding} && ($args->{F} // $self->F) == $config->solid_infill_speed*60; - }); - is scalar(keys %layers), $config->bottom_solid_layers, - "shells are not propagated across perimeters of the neighbor layer"; -} - -{ - my $config = Slic3r::Config::new_from_defaults; - $config->set('perimeters', 3); - $config->set('cooling', [ 0 ]); # prevent speed alteration - $config->set('first_layer_speed', '100%'); # prevent speed alteration - $config->set('enable_dynamic_overhang_speeds', 0); # prevent speed alteration - $config->set('layer_height', 0.4); - $config->set('first_layer_height', $config->layer_height); - $config->set('bottom_solid_layers', 3); - $config->set('top_solid_layers', 3); - $config->set('solid_infill_speed', 99); - $config->set('top_solid_infill_speed', 99); - $config->set('bridge_speed', 99); - $config->set('filament_diameter', [ 3.0 ]); - $config->set('nozzle_diameter', [ 0.5 ]); - - my $print = Slic3r::Test::init_print('sloping_hole', config => $config); - my %solid_layers = (); # Z => 1 - Slic3r::GCode::Reader->new->parse(Slic3r::Test::gcode($print), sub { - my ($self, $cmd, $args, $info) = @_; - $solid_layers{$self->Z} = 1 - if $info->{extruding} && ($args->{F} // $self->F) == $config->solid_infill_speed*60; - }); - is scalar(keys %solid_layers), $config->bottom_solid_layers + $config->top_solid_layers, - "no superfluous shells are generated"; -} - -{ - my $config = Slic3r::Config::new_from_defaults; - $config->set('perimeters', 1); - $config->set('fill_density', 0); - $config->set('top_solid_layers', 0); - $config->set('spiral_vase', 1); - $config->set('bottom_solid_layers', 0); - $config->set('skirts', 0); - $config->set('first_layer_height', $config->layer_height); - $config->set('start_gcode', ''); - $config->set('temperature', [200]); - $config->set('first_layer_temperature', [205]); - - # TODO: this needs to be tested with a model with sloping edges, where starting - # points of each layer are not aligned - in that case we would test that no - # travel moves are left to move to the new starting point - in a cube, end - # points coincide with next layer starting points (provided there's no clipping) - my $test = sub { - my ($model_name, $description) = @_; - my $print = Slic3r::Test::init_print($model_name, config => $config); - my $travel_moves_after_first_extrusion = 0; - my $started_extruding = 0; - my $first_layer_temperature_set = 0; - my $temperature_set = 0; - my @z_steps = (); - Slic3r::GCode::Reader->new->parse(Slic3r::Test::gcode($print), sub { - my ($self, $cmd, $args, $info) = @_; - - if ($cmd eq 'G1') { - $started_extruding = 1 if $info->{extruding}; - push @z_steps, $info->{dist_Z} - if $started_extruding && $info->{dist_Z} > 0; - $travel_moves_after_first_extrusion++ - if $info->{travel} && $info->{dist_XY} > 0 && $started_extruding && !exists $args->{Z}; - } elsif ($cmd eq 'M104') { - $first_layer_temperature_set = 1 if $args->{S} == 205; - $temperature_set = 1 if $args->{S} == 200; - } - }); - - ok $first_layer_temperature_set, 'first layer temperature is preserved'; - ok $temperature_set, 'temperature is preserved'; - - # we allow one travel move after first extrusion: i.e. when moving to the first - # spiral point after moving to second layer (bottom layer had loop clipping, so - # we're slightly distant from the starting point of the loop) - ok $travel_moves_after_first_extrusion <= 1, "no gaps in spiral vase ($description)"; - ok !(grep { $_ > $config->layer_height + epsilon } @z_steps), "no gaps in Z ($description)"; - }; - - $test->('20mm_cube', 'solid model'); - - $config->set('z_offset', -10); - $test->('20mm_cube', 'solid model with negative z-offset'); - - ### Disabled because the current unreliable medial axis code doesn't - ### always produce valid loops. - ###$test->('40x10', 'hollow model with negative z-offset'); -} - -{ - my $config = Slic3r::Config::new_from_defaults; - $config->set('spiral_vase', 1); - $config->set('perimeters', 1); - $config->set('fill_density', 0); - $config->set('top_solid_layers', 0); - $config->set('bottom_solid_layers', 0); - $config->set('retract_layer_change', [0]); - $config->set('skirts', 0); - $config->set('layer_height', 0.4); - $config->set('first_layer_height', $config->layer_height); - $config->set('start_gcode', ''); -# $config->set('use_relative_e_distances', 1); - $config->validate; - - my $print = Slic3r::Test::init_print('20mm_cube', config => $config); - my $z_moves = 0; - my @this_layer = (); # [ dist_Z, dist_XY ], ... - - my $bottom_layer_not_flat = 0; - my $null_z_moves_not_layer_changes = 0; - my $null_z_moves_not_multiples_of_layer_height = 0; - my $sum_of_partial_z_equals_to_layer_height = 0; - my $all_layer_segments_have_same_slope = 0; - my $horizontal_extrusions = 0; - - Slic3r::GCode::Reader->new->parse(Slic3r::Test::gcode($print), sub { - my ($self, $cmd, $args, $info) = @_; - - if ($cmd eq 'G1') { - if ($z_moves < 2) { - # skip everything up to the second Z move - # (i.e. start of second layer) - if (exists $args->{Z}) { - $z_moves++; - $bottom_layer_not_flat = 1 - if $info->{dist_Z} > 0 && $info->{dist_Z} != $config->layer_height; - } - } elsif ($info->{dist_Z} == 0 && $args->{Z}) { - $null_z_moves_not_layer_changes = 1 - if $info->{dist_XY} != 0; - - # % doesn't work easily with floats - $null_z_moves_not_multiples_of_layer_height = 1 - if abs(($args->{Z} / $config->layer_height) * $config->layer_height - $args->{Z}) > epsilon; - - my $total_dist_XY = sum(map $_->[1], @this_layer); - $sum_of_partial_z_equals_to_layer_height = 1 - if abs(sum(map $_->[0], @this_layer) - $config->layer_height) > - # The first segment on the 2nd layer has extrusion interpolated from zero - # and the 1st segment has such a low extrusion assigned, that it is effectively zero, thus the move - # is considered non-extruding and a higher epsilon is required. - ($z_moves == 2 ? 0.0021 : epsilon); - #printf ("Total height: %f, layer height: %f, good: %d\n", sum(map $_->[0], @this_layer), $config->layer_height, $sum_of_partial_z_equals_to_layer_height); - - foreach my $segment (@this_layer) { - # check that segment's dist_Z is proportioned to its dist_XY - $all_layer_segments_have_same_slope = 1 - if abs($segment->[0]*$total_dist_XY/$config->layer_height - $segment->[1]) > 0.2; - } - - @this_layer = (); - } elsif ($info->{extruding} && $info->{dist_XY} > 0) { - $horizontal_extrusions = 1 - if $info->{dist_Z} == 0; - #printf("Pushing dist_z: %f, dist_xy: %f\n", $info->{dist_Z}, $info->{dist_XY}); - push @this_layer, [ $info->{dist_Z}, $info->{dist_XY} ]; - } - } - }); - ok !$bottom_layer_not_flat, 'bottom layer is flat when using spiral vase'; - ok !$null_z_moves_not_layer_changes, 'null Z moves are layer changes'; - ok !$null_z_moves_not_multiples_of_layer_height, 'null Z moves are multiples of layer height'; - ok !$sum_of_partial_z_equals_to_layer_height, 'sum of partial Z increments equals to a full layer height'; - ok !$all_layer_segments_have_same_slope, 'all layer segments have the same slope'; - ok !$horizontal_extrusions, 'no horizontal extrusions'; -} - -# The current Spiral Vase slicing code removes the holes and all but the largest contours from each slice, -# therefore the following test is no more valid. -#{ -# my $config = Slic3r::Config::new_from_defaults; -# $config->set('perimeters', 1); -# $config->set('fill_density', 0); -# $config->set('top_solid_layers', 0); -# $config->set('spiral_vase', 1); -# $config->set('bottom_solid_layers', 0); -# $config->set('skirts', 0); -# $config->set('first_layer_height', $config->layer_height); -# $config->set('start_gcode', ''); -# -# my $print = Slic3r::Test::init_print('two_hollow_squares', config => $config); -# my $diagonal_moves = 0; -# Slic3r::GCode::Reader->new->parse(Slic3r::Test::gcode($print), sub { -# my ($self, $cmd, $args, $info) = @_; -# -# if ($cmd eq 'G1') { -# if ($info->{extruding} && $info->{dist_XY} > 0) { -# if ($info->{dist_Z} > 0) { -# $diagonal_moves++; -# } -# } -# } -# }); -# is $diagonal_moves, 0, 'no spiral moves on two-island object'; -#} - -__END__ diff --git a/tests/fff_print/test_clipper.cpp b/tests/fff_print/test_clipper.cpp index ea923b48e..596876975 100644 --- a/tests/fff_print/test_clipper.cpp +++ b/tests/fff_print/test_clipper.cpp @@ -1,7 +1,7 @@ #include #include "test_data.hpp" -#include "clipper/clipper_z.hpp" +#include "libslic3r/ClipperZUtils.hpp" #include "libslic3r/clipper.hpp" using namespace Slic3r; @@ -132,3 +132,72 @@ SCENARIO("Clipper Z", "[ClipperZ]") REQUIRE(pt.z() == 1); } +SCENARIO("Intersection with multiple polylines", "[ClipperZ]") +{ + // 1000x1000 CCQ square + ClipperLib_Z::Path clip { { 0, 0, 1 }, { 1000, 0, 1 }, { 1000, 1000, 1 }, { 0, 1000, 1 } }; + // Two lines interseting inside the square above, crossing the bottom edge of the square. + ClipperLib_Z::Path line1 { { +100, -100, 2 }, { +900, +900, 2 } }; + ClipperLib_Z::Path line2 { { +100, +900, 3 }, { +900, -100, 3 } }; + + ClipperLib_Z::Clipper clipper; + ClipperZUtils::ClipperZIntersectionVisitor::Intersections intersections; + ClipperZUtils::ClipperZIntersectionVisitor visitor(intersections); + clipper.ZFillFunction(visitor.clipper_callback()); + clipper.AddPath(line1, ClipperLib_Z::ptSubject, false); + clipper.AddPath(line2, ClipperLib_Z::ptSubject, false); + clipper.AddPath(clip, ClipperLib_Z::ptClip, true); + + ClipperLib_Z::PolyTree polytree; + ClipperLib_Z::Paths paths; + clipper.Execute(ClipperLib_Z::ctIntersection, polytree, ClipperLib_Z::pftNonZero, ClipperLib_Z::pftNonZero); + ClipperLib_Z::PolyTreeToPaths(polytree, paths); + + REQUIRE(paths.size() == 2); + + THEN("First output polyline is a trimmed 2nd line") { + // Intermediate point (intersection) was removed) + REQUIRE(paths.front().size() == 2); + REQUIRE(paths.front().front().z() == 3); + REQUIRE(paths.front().back().z() < 0); + REQUIRE(intersections[- paths.front().back().z() - 1] == std::pair(1, 3)); + } + + THEN("Second output polyline is a trimmed 1st line") { + // Intermediate point (intersection) was removed) + REQUIRE(paths[1].size() == 2); + REQUIRE(paths[1].front().z() < 0); + REQUIRE(paths[1].back().z() == 2); + REQUIRE(intersections[- paths[1].front().z() - 1] == std::pair(1, 2)); + } +} + +SCENARIO("Interseting a closed loop as an open polyline", "[ClipperZ]") +{ + // 1000x1000 CCQ square + ClipperLib_Z::Path clip{ { 0, 0, 1 }, { 1000, 0, 1 }, { 1000, 1000, 1 }, { 0, 1000, 1 } }; + // Two lines interseting inside the square above, crossing the bottom edge of the square. + ClipperLib_Z::Path rect{ { 500, 500, 2}, { 500, 1500, 2 }, { 1500, 1500, 2}, { 500, 1500, 2}, { 500, 500, 2 } }; + + ClipperLib_Z::Clipper clipper; + clipper.AddPath(rect, ClipperLib_Z::ptSubject, false); + clipper.AddPath(clip, ClipperLib_Z::ptClip, true); + + ClipperLib_Z::PolyTree polytree; + ClipperLib_Z::Paths paths; + ClipperZUtils::ClipperZIntersectionVisitor::Intersections intersections; + ClipperZUtils::ClipperZIntersectionVisitor visitor(intersections); + clipper.ZFillFunction(visitor.clipper_callback()); + clipper.Execute(ClipperLib_Z::ctIntersection, polytree, ClipperLib_Z::pftNonZero, ClipperLib_Z::pftNonZero); + ClipperLib_Z::PolyTreeToPaths(std::move(polytree), paths); + + THEN("Open polyline is clipped into two pieces") { + REQUIRE(paths.size() == 2); + REQUIRE(paths.front().size() == 2); + REQUIRE(paths.back().size() == 2); + REQUIRE(paths.front().front().z() == 2); + REQUIRE(paths.back().back().z() == 2); + REQUIRE(paths.front().front().x() == paths.back().back().x()); + REQUIRE(paths.front().front().y() == paths.back().back().y()); + } +} diff --git a/tests/fff_print/test_shells.cpp b/tests/fff_print/test_shells.cpp index df0e8f6bb..d9e5817ca 100644 --- a/tests/fff_print/test_shells.cpp +++ b/tests/fff_print/test_shells.cpp @@ -9,16 +9,14 @@ using namespace Slic3r; SCENARIO("Shells", "[Shells]") { GIVEN("20mm box") { - auto test = [](const DynamicPrintConfig &config){ - std::string gcode = Slic3r::Test::slice({ Slic3r::Test::TestMesh::cube_20x20x20 }, config); - + auto test = [](const DynamicPrintConfig &config){ std::vector zs; std::set layers_with_solid_infill; std::set layers_with_bridge_infill; const double solid_infill_speed = config.opt_float("solid_infill_speed") * 60; const double bridge_speed = config.opt_float("bridge_speed") * 60; GCodeReader parser; - parser.parse_buffer(gcode, + parser.parse_buffer(Slic3r::Test::slice({ Slic3r::Test::TestMesh::cube_20x20x20 }, config), [&zs, &layers_with_solid_infill, &layers_with_bridge_infill, solid_infill_speed, bridge_speed] (Slic3r::GCodeReader &self, const Slic3r::GCodeReader::GCodeLine &line) { @@ -110,3 +108,294 @@ SCENARIO("Shells", "[Shells]") { } } } + +static std::set layers_with_speed(const std::string &gcode, int speed) +{ + std::set out; + GCodeReader parser; + parser.parse_buffer(gcode, [&out, speed](Slic3r::GCodeReader &self, const Slic3r::GCodeReader::GCodeLine &line) { + if (line.extruding(self) && is_approx(line.new_F(self), speed * 60.)) + out.insert(self.z()); + }); + return out; +} + +SCENARIO("Shells (from Perl)", "[Shells]") { + GIVEN("V shape, Slic3r GH #1161") { + int solid_speed = 99; + auto config = Slic3r::DynamicPrintConfig::full_print_config_with({ + { "layer_height", 0.3 }, + { "first_layer_height", 0.3 }, + { "bottom_solid_layers", 0 }, + { "top_solid_layers", 3 }, + // to prevent speeds from being altered + { "cooling", "0" }, + { "bridge_speed", solid_speed }, + { "solid_infill_speed", solid_speed }, + { "top_solid_infill_speed", solid_speed }, + // to prevent speeds from being altered + { "first_layer_speed", "100%" }, + // prevent speed alteration + { "enable_dynamic_overhang_speeds", 0 } + }); + + THEN("correct number of top solid shells is generated in V-shaped object") { + size_t n = 0; + for (auto z : layers_with_speed(Slic3r::Test::slice({TestMesh::V}, config), solid_speed)) + if (z <= 7.2) + ++ n; + REQUIRE(n == 3); + } + } + GIVEN("V shape") { + // we need to check against one perimeter because this test is calibrated + // (shape, extrusion_width) so that perimeters cover the bottom surfaces of + // their lower layer - the test checks that shells are not generated on the + // above layers (thus 'across' the shadow perimeter) + // the test is actually calibrated to leave a narrow bottom region for each + // layer - we test that in case of fill_density = 0 such narrow shells are + // discarded instead of grown + int bottom_solid_layers = 3; + auto config = Slic3r::DynamicPrintConfig::full_print_config_with({ + { "perimeters", 1 }, + { "fill_density", 0 }, + // to prevent speeds from being altered + { "cooling", "0" }, + // to prevent speeds from being altered + { "first_layer_speed", "100%" }, + // prevent speed alteration + { "enable_dynamic_overhang_speeds", 0 }, + { "layer_height", 0.4 }, + { "first_layer_height", 0.4 }, + { "extrusion_width", 0.55 }, + { "bottom_solid_layers", bottom_solid_layers }, + { "top_solid_layers", 0 }, + { "solid_infill_speed", 99 } + }); + THEN("shells are not propagated across perimeters of the neighbor layer") { + std::string gcode = Slic3r::Test::slice({TestMesh::V}, config); + REQUIRE(layers_with_speed(gcode, 99).size() == bottom_solid_layers); + } + } + GIVEN("sloping_hole") { + int bottom_solid_layers = 3; + int top_solid_layers = 3; + int solid_speed = 99; + auto config = Slic3r::DynamicPrintConfig::full_print_config_with({ + { "perimeters", 3 }, + // to prevent speeds from being altered + { "cooling", "0" }, + // to prevent speeds from being altered + { "first_layer_speed", "100%" }, + // prevent speed alteration + { "enable_dynamic_overhang_speeds", 0 }, + { "layer_height", 0.4 }, + { "first_layer_height", 0.4 }, + { "bottom_solid_layers", bottom_solid_layers }, + { "top_solid_layers", top_solid_layers }, + { "solid_infill_speed", solid_speed }, + { "top_solid_infill_speed", solid_speed }, + { "bridge_speed", solid_speed }, + { "filament_diameter", 3. }, + { "nozzle_diameter", 0.5 } + }); + THEN("no superfluous shells are generated") { + std::string gcode = Slic3r::Test::slice({TestMesh::sloping_hole}, config); + REQUIRE(layers_with_speed(gcode, solid_speed).size() == bottom_solid_layers + top_solid_layers); + } + } + GIVEN("20mm_cube, spiral vase") { + double layer_height = 0.3; + auto config = Slic3r::DynamicPrintConfig::full_print_config_with({ + { "perimeters", 1 }, + { "fill_density", 0 }, + { "layer_height", layer_height }, + { "first_layer_height", layer_height }, + { "top_solid_layers", 0 }, + { "spiral_vase", 1 }, + { "bottom_solid_layers", 0 }, + { "skirts", 0 }, + { "start_gcode", "" }, + { "temperature", 200 }, + { "first_layer_temperature", 205} + }); + + // TODO: this needs to be tested with a model with sloping edges, where starting + // points of each layer are not aligned - in that case we would test that no + // travel moves are left to move to the new starting point - in a cube, end + // points coincide with next layer starting points (provided there's no clipping) + auto test = [layer_height](const DynamicPrintConfig &config) { + size_t travel_moves_after_first_extrusion = 0; + bool started_extruding = false; + bool first_layer_temperature_set = false; + bool temperature_set = false; + std::vector z_steps; + GCodeReader parser; + parser.parse_buffer(Slic3r::Test::slice({TestMesh::cube_20x20x20}, config), + [&](Slic3r::GCodeReader &self, const Slic3r::GCodeReader::GCodeLine &line) { + if (line.cmd_is("G1")) { + if (line.extruding(self)) + started_extruding = true; + if (started_extruding) { + if (double dz = line.dist_Z(self); dz > 0) + z_steps.emplace_back(dz); + if (line.travel() && line.dist_XY(self) > 0 && ! line.has(Z)) + ++ travel_moves_after_first_extrusion; + } + } else if (line.cmd_is("M104")) { + int s; + if (line.has_value('S', s)) { + if (s == 205) + first_layer_temperature_set = true; + else if (s == 200) + temperature_set = true; + } + } + }); + THEN("first layer temperature is set") { + REQUIRE(first_layer_temperature_set); + } + THEN("temperature is set") { + REQUIRE(temperature_set); + } + // we allow one travel move after first extrusion: i.e. when moving to the first + // spiral point after moving to second layer (bottom layer had loop clipping, so + // we're slightly distant from the starting point of the loop) + THEN("no gaps in spiral vase") { + REQUIRE(travel_moves_after_first_extrusion <= 1); + } + THEN("no gaps in Z") { + REQUIRE(std::count_if(z_steps.begin(), z_steps.end(), + [&layer_height](auto z_step) { return z_step > layer_height + EPSILON; }) == 0); + } + }; + WHEN("solid model") { + test(config); + } + WHEN("solid model with negative z-offset") { + config.set_deserialize_strict("z_offset", "-10"); + test(config); + } + // Disabled because the current unreliable medial axis code doesn't always produce valid loops. + // $test->('40x10', 'hollow model with negative z-offset'); + } + GIVEN("20mm_cube, spiral vase") { + double layer_height = 0.4; + auto config = Slic3r::DynamicPrintConfig::full_print_config_with({ + { "spiral_vase", 1 }, + { "perimeters", 1 }, + { "fill_density", 0 }, + { "top_solid_layers", 0 }, + { "bottom_solid_layers", 0 }, + { "retract_layer_change", 0 }, + { "skirts", 0 }, + { "layer_height", layer_height }, + { "first_layer_height", layer_height }, + { "start_gcode", "" }, + // { "use_relative_e_distances", 1} + }); + config.validate(); + + std::vector> this_layer; // [ dist_Z, dist_XY ], ... + int z_moves = 0; + bool bottom_layer_not_flat = false; + bool null_z_moves_not_layer_changes = false; + bool null_z_moves_not_multiples_of_layer_height = false; + bool sum_of_partial_z_equals_to_layer_height = false; + bool all_layer_segments_have_same_slope = false; + bool horizontal_extrusions = false; + GCodeReader parser; + parser.parse_buffer(Slic3r::Test::slice({TestMesh::cube_20x20x20}, config), + [&](Slic3r::GCodeReader &self, const Slic3r::GCodeReader::GCodeLine &line) { + if (line.cmd_is("G1")) { + if (z_moves < 2) { + // skip everything up to the second Z move + // (i.e. start of second layer) + if (line.has(Z)) { + ++ z_moves; + if (double dz = line.dist_Z(self); dz > 0 && ! is_approx(dz, layer_height)) + bottom_layer_not_flat = true; + } + } else if (line.dist_Z(self) == 0 && line.has(Z)) { + if (line.dist_XY(self) != 0) + null_z_moves_not_layer_changes = true; + double z = line.new_Z(self); + if (fmod(z + EPSILON, layer_height) > 2 * EPSILON) + null_z_moves_not_multiples_of_layer_height = true; + double total_dist_XY = 0; + double total_dist_Z = 0; + for (auto &seg : this_layer) { + total_dist_Z += seg.first; + total_dist_XY += seg.second; + } + if (std::abs(total_dist_Z - layer_height) > + // The first segment on the 2nd layer has extrusion interpolated from zero + // and the 1st segment has such a low extrusion assigned, that it is effectively zero, thus the move + // is considered non-extruding and a higher epsilon is required. + (z_moves == 2 ? 0.0021 : EPSILON)) + sum_of_partial_z_equals_to_layer_height = true; + //printf("Total height: %f, layer height: %f, good: %d\n", sum(map $_->[0], @this_layer), $config->layer_height, $sum_of_partial_z_equals_to_layer_height); + for (auto &seg : this_layer) + // check that segment's dist_Z is proportioned to its dist_XY + if (std::abs(seg.first * total_dist_XY / layer_height - seg.second) > 0.2) + all_layer_segments_have_same_slope = true; + this_layer.clear(); + } else if (line.extruding(self) && line.dist_XY(self) > 0) { + if (line.dist_Z(self) == 0) + horizontal_extrusions = true; + //printf("Pushing dist_z: %f, dist_xy: %f\n", $info->{dist_Z}, $info->{dist_XY}); + this_layer.emplace_back(line.dist_Z(self), line.dist_XY(self)); + } + } + }); + THEN("bottom layer is flat when using spiral vase") { + REQUIRE(! bottom_layer_not_flat); + } + THEN("null Z moves are layer changes") { + REQUIRE(! null_z_moves_not_layer_changes); + } + THEN("null Z moves are multiples of layer height") { + REQUIRE(! null_z_moves_not_multiples_of_layer_height); + } + THEN("sum of partial Z increments equals to a full layer height") { + REQUIRE(! sum_of_partial_z_equals_to_layer_height); + } + THEN("all layer segments have the same slope") { + REQUIRE(! all_layer_segments_have_same_slope); + } + THEN("no horizontal extrusions") { + REQUIRE(! horizontal_extrusions); + } + } +} + +#if 0 +// The current Spiral Vase slicing code removes the holes and all but the largest contours from each slice, +// therefore the following test is no more valid. +{ + my $config = Slic3r::Config::new_from_defaults; + $config->set('perimeters', 1); + $config->set('fill_density', 0); + $config->set('top_solid_layers', 0); + $config->set('spiral_vase', 1); + $config->set('bottom_solid_layers', 0); + $config->set('skirts', 0); + $config->set('first_layer_height', $config->layer_height); + $config->set('start_gcode', ''); + + my $print = Slic3r::Test::init_print('two_hollow_squares', config => $config); + my $diagonal_moves = 0; + Slic3r::GCode::Reader->new->parse(Slic3r::Test::gcode($print), sub { + my ($self, $cmd, $args, $info) = @_; + + if ($cmd eq 'G1') { + if ($info->{extruding} && $info->{dist_XY} > 0) { + if ($info->{dist_Z} > 0) { + $diagonal_moves++; + } + } + } + }); + is $diagonal_moves, 0, 'no spiral moves on two-island object'; +} +#endif diff --git a/tests/libslic3r/CMakeLists.txt b/tests/libslic3r/CMakeLists.txt index ae2474ad5..971db528c 100644 --- a/tests/libslic3r/CMakeLists.txt +++ b/tests/libslic3r/CMakeLists.txt @@ -23,6 +23,7 @@ add_executable(${_TEST_NAME}_tests test_stl.cpp test_meshboolean.cpp test_marchingsquares.cpp + test_region_expansion.cpp test_timeutils.cpp test_utils.cpp test_voronoi.cpp diff --git a/tests/libslic3r/test_placeholder_parser.cpp b/tests/libslic3r/test_placeholder_parser.cpp index a4b3b7514..8ad6b243f 100644 --- a/tests/libslic3r/test_placeholder_parser.cpp +++ b/tests/libslic3r/test_placeholder_parser.cpp @@ -31,6 +31,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"); } @@ -115,4 +116,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\"")); } } diff --git a/tests/libslic3r/test_polygon.cpp b/tests/libslic3r/test_polygon.cpp index c1e1c3b73..b2608883c 100644 --- a/tests/libslic3r/test_polygon.cpp +++ b/tests/libslic3r/test_polygon.cpp @@ -196,19 +196,6 @@ SCENARIO("Simplify polygon", "[Polygon]") } } } - GIVEN("hole in square") { - // CW oriented - auto hole_in_square = Polygon{ {140, 140}, {140, 160}, {160, 160}, {160, 140} }; - WHEN("simplified") { - Polygons simplified = hole_in_square.simplify(2.); - THEN("hole simplification returns one polygon") { - REQUIRE(simplified.size() == 1); - } - THEN("hole simplification turns cw polygon into ccw polygon") { - REQUIRE(simplified.front().is_counter_clockwise()); - } - } - } } #include "libslic3r/ExPolygon.hpp" diff --git a/tests/libslic3r/test_region_expansion.cpp b/tests/libslic3r/test_region_expansion.cpp new file mode 100644 index 000000000..9f8a6fdc5 --- /dev/null +++ b/tests/libslic3r/test_region_expansion.cpp @@ -0,0 +1,284 @@ +#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))); + } + } + } + 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)))); + } + } + } +}