diff --git a/src/libslic3r/Algorithm/LineSplit.cpp b/src/libslic3r/Algorithm/LineSplit.cpp new file mode 100644 index 0000000000..c532e08349 --- /dev/null +++ b/src/libslic3r/Algorithm/LineSplit.cpp @@ -0,0 +1,316 @@ +#include "LineSplit.hpp" + +#include "AABBTreeLines.hpp" +#include "SVG.hpp" +#include "Utils.hpp" + +//#define DEBUG_SPLIT_LINE + +namespace Slic3r { +namespace Algorithm { + +#ifdef DEBUG_SPLIT_LINE +static std::atomic g_dbg_id = 0; +#endif + +// Z for points from clip polygon +static constexpr auto CLIP_IDX = std::numeric_limits::max(); + +static void cb_split_line(const ClipperZUtils::ZPoint& e1bot, + const ClipperZUtils::ZPoint& e1top, + const ClipperZUtils::ZPoint& e2bot, + const ClipperZUtils::ZPoint& e2top, + ClipperZUtils::ZPoint& pt) +{ + coord_t zs[4]{e1bot.z(), e1top.z(), e2bot.z(), e2top.z()}; + std::sort(zs, zs + 4); + pt.z() = -(zs[0] + 1); +} + +static bool is_src(const ClipperZUtils::ZPoint& p) { return p.z() >= 0 && p.z() != CLIP_IDX; } +static bool is_clip(const ClipperZUtils::ZPoint& p) { return p.z() == CLIP_IDX; } +static bool is_new(const ClipperZUtils::ZPoint& p) { return p.z() < 0; } +static size_t to_src_idx(const ClipperZUtils::ZPoint& p) +{ + assert(!is_clip(p)); + if (is_src(p)) { + return p.z(); + } else { + return -p.z() - 1; + } +} + +static Point to_point(const ClipperZUtils::ZPoint& p) { return {p.x(), p.y()}; } + +using SplitNode = std::vector; + +// Note: p cannot be one of the line end +static bool point_on_line(const Point& p, const Line& l) +{ + // Check collinear + const auto d1 = l.b - l.a; + const auto d2 = p - l.a; + if (d1.x() * d2.y() != d1.y() * d2.x()) { + return false; + } + + // Make sure p is in between line.a and line.b + if (l.a.x() != l.b.x()) + return (p.x() > l.a.x()) == (p.x() < l.b.x()); + else + return (p.y() > l.a.y()) == (p.y() < l.b.y()); +} + +SplittedLine do_split_line(const ClipperZUtils::ZPath& path, const ExPolygons& clip, bool closed) +{ + assert(path.size() > 1); +#ifdef DEBUG_SPLIT_LINE + const auto dbg_path_points = ClipperZUtils::from_zpath(path); + BoundingBox dbg_bbox = get_extents(clip); + dbg_bbox.merge(get_extents(dbg_path_points)); + dbg_bbox.offset(scale_(1.)); + const std::uint32_t dbg_id = g_dbg_id++; + { + ::Slic3r::SVG svg(debug_out_path("do_split_line_%d_input.svg", dbg_id).c_str(), dbg_bbox); + svg.draw(clip, "red", 0.5); + svg.draw_outline(clip, "red"); + svg.draw(Polyline{dbg_path_points}); + svg.draw(dbg_path_points); + svg.Close(); + } +#endif + + ClipperZUtils::ZPaths intersections; + // Perform an intersection + { + // Convert clip polygon to closed contours + ClipperZUtils::ZPaths clip_path; + for (const auto& exp : clip) { + clip_path.emplace_back(ClipperZUtils::to_zpath(exp.contour.points, CLIP_IDX)); + for (const Polygon& hole : exp.holes) + clip_path.emplace_back(ClipperZUtils::to_zpath(hole.points, CLIP_IDX)); + } + + ClipperLib_Z::Clipper zclipper; + zclipper.PreserveCollinear(true); + zclipper.ZFillFunction(cb_split_line); + zclipper.AddPaths(clip_path, ClipperLib_Z::ptClip, true); + zclipper.AddPath(path, ClipperLib_Z::ptSubject, false); + ClipperLib_Z::PolyTree polytree; + zclipper.Execute(ClipperLib_Z::ctIntersection, polytree, ClipperLib_Z::pftNonZero, ClipperLib_Z::pftNonZero); + ClipperLib_Z::PolyTreeToPaths(std::move(polytree), intersections); + } + if (intersections.empty()) { + return {}; + } + +#ifdef DEBUG_SPLIT_LINE + { + int i = 0; + for (const auto& segment : intersections) { + ::Slic3r::SVG svg(debug_out_path("do_split_line_%d_seg_%d.svg", dbg_id, i).c_str(), dbg_bbox); + svg.draw(clip, "red", 0.5); + svg.draw_outline(clip, "red"); + const auto segment_points = ClipperZUtils::from_zpath(segment); + svg.draw(Polyline{segment_points}); + for (const ClipperZUtils::ZPoint& p : segment) { + const auto z = p.z(); + if (is_new(p)) { + svg.draw(to_point(p), "yellow"); + } else if (is_clip(p)) { + svg.draw(to_point(p), "red"); + } else { + svg.draw(to_point(p), "black"); + } + } + svg.Close(); + i++; + } + } +#endif + + // Connect the intersection back to the remaining loop + std::vector split_chain; + { + // AABBTree over source paths. + // Only built if necessary, that is if any of the clipped segment has first point came from clip polygon, + // and we need to find out which source edge that point came from. + AABBTreeLines::LinesDistancer aabb_tree; + const auto resolve_clip_point = [&path, &aabb_tree](ClipperZUtils::ZPoint& zp) { + if (!is_clip(zp)) { + return; + } + + if (aabb_tree.get_lines().empty()) { + Lines lines; + lines.reserve(path.size() - 1); + for (auto it = path.begin() + 1; it != path.end(); ++it) { + lines.emplace_back(to_point(it[-1]), to_point(*it)); + } + aabb_tree = AABBTreeLines::LinesDistancer(lines); + } + + const Point p = to_point(zp); + const auto possible_edges = aabb_tree.all_lines_in_radius(p, SCALED_EPSILON); + assert(!possible_edges.empty()); + for (const size_t l : possible_edges) { + // Check if the point is on the line + const Line line(to_point(path[l]), to_point(path[l + 1])); + if (p == line.a) { + zp.z() = path[l].z(); + break; + } + if (p == line.b) { + zp.z() = path[l + 1].z(); + break; + } + if (point_on_line(p, line)) { + zp.z() = -(path[l].z() + 1); + break; + } + } + if (is_clip(zp)) { + // Too bad! Couldn't find the src edge, so we just pick the first one and hope it works + zp.z() = -(path[possible_edges[0]].z() + 1); + } + }; + + split_chain.assign(path.size(), {}); + for (ClipperZUtils::ZPath& segment : intersections) { + assert(segment.size() >= 2); + // Resolve all clip points + std::for_each(segment.begin(), segment.end(), resolve_clip_point); + + // Ensure the point order in segment + std::sort(segment.begin(), segment.end(), [&path](const ClipperZUtils::ZPoint& a, const ClipperZUtils::ZPoint& b) -> bool { + if (is_new(a) && is_new(b) && a.z() == b.z()) { + // Make sure a point is closer to the src point than b + const auto src = to_point(path[-a.z() - 1]); + return (to_point(a) - src).squaredNorm() < (to_point(b) - src).squaredNorm(); + } + const auto a_idx = to_src_idx(a); + const auto b_idx = to_src_idx(b); + if (a_idx == b_idx) { + // On same line, prefer the src point first + return is_src(a); + } else { + return a_idx < b_idx; + } + }); + + // Chain segment back to the original path + ClipperZUtils::ZPoint& front = segment.front(); + const ClipperZUtils::ZPoint* previous_src_point; + if (is_src(front)) { + // The segment starts with a point from src path, which means apart from the last point, + // all other points on this segment should come from the src path or the clip polygon + + // Connect the segment to the src path + auto& node = split_chain[front.z()]; + node.insert(node.begin(), &segment); + + previous_src_point = &front; + } else if (is_new(front)) { + const auto id = -front.z() - 1; // Get the src path index + const ClipperZUtils::ZPoint& src_p = path[id]; // Get the corresponding src point + const auto dist2 = (front - src_p).block<2, 1>(0,0).squaredNorm(); // Distance between the src point and current point + // Find the place on the src line that current point should lie on + auto& node = split_chain[id]; + auto it = std::find_if(node.begin(), node.end(), [dist2, &src_p](const ClipperZUtils::ZPath* p) { + const ClipperZUtils::ZPoint& p_front = p->front(); + if (is_src(p_front)) { + return false; + } + + const auto dist2_2 = (p_front - src_p).block<2, 1>(0, 0).squaredNorm(); + return dist2_2 > dist2; + }); + // Insert this split + node.insert(it, &segment); + + previous_src_point = &src_p; + } else { + assert(false); + } + + // Once we figured out the start point, we can then normalize the remaining points on the segment + for (ClipperZUtils::ZPoint& p : segment) { + assert(!is_new(p) || p == front || p == segment.back()); // Only the first and last point can be a new intersection + if (is_src(p)) { + previous_src_point = &p; + } else if (is_clip(p)) { + // Treat point from clip polygon as new point + p.z() = -(previous_src_point->z() + 1); + } + } + } + } + + // Now we reconstruct the final path by connecting splits + SplittedLine result; + size_t idx = 0; + while (idx < split_chain.size()) { + const ClipperZUtils::ZPoint& p = path[idx]; + const auto& node = split_chain[idx]; + if (node.empty()) { + result.emplace_back(to_point(p), false, idx); + idx++; + } else { + if (!is_src(node.front()->front())) { + const auto& last = result.back(); + if (result.empty() || last.get_src_index() != to_src_idx(p)) { + result.emplace_back(to_point(p), false, idx); + } + } + for (const auto segment : node) { + for (const ClipperZUtils::ZPoint& sp : *segment) { + assert(!is_clip(sp.z())); + result.emplace_back(to_point(sp), true, sp.z()); + } + result.back().clipped = false; // Mark the end of the clipped line + } + + // Determine the next start point + const auto back = result.back().src_idx; + if (back < 0) { + auto next_idx = -back - 1; + if (next_idx == idx) { + next_idx++; + } else if (split_chain[next_idx].empty()) { + next_idx++; + } + idx = next_idx; + } else { + result.pop_back(); + idx = back; + } + } + } + + +#ifdef DEBUG_SPLIT_LINE + { + ::Slic3r::SVG svg(debug_out_path("do_split_line_%d_result.svg", dbg_id).c_str(), dbg_bbox); + svg.draw(clip, "red", 0.5); + svg.draw_outline(clip, "red"); + for (auto it = result.begin() + 1; it != result.end(); ++it) { + const auto& a = *(it - 1); + const auto& b = *it; + const bool clipped = a.clipped; + const Line l(a.p, b.p); + svg.draw(l, clipped ? "yellow" : "black"); + } + svg.Close(); + } +#endif + + if (closed) { + // Remove last point which was duplicated + result.pop_back(); + } + + return result; +} + +} // Algorithm +} // Slic3r diff --git a/src/libslic3r/Algorithm/LineSplit.hpp b/src/libslic3r/Algorithm/LineSplit.hpp new file mode 100644 index 0000000000..102ed62bd9 --- /dev/null +++ b/src/libslic3r/Algorithm/LineSplit.hpp @@ -0,0 +1,71 @@ +#ifndef SRC_LIBSLIC3R_ALGORITHM_LINE_SPLIT_HPP_ +#define SRC_LIBSLIC3R_ALGORITHM_LINE_SPLIT_HPP_ + +#include "clipper2/clipper.core.h" +#include "ClipperZUtils.hpp" + +namespace Slic3r { +namespace Algorithm { + +struct SplitLineJunction +{ + Point p; + + // true if the line between this point and the next point is inside the clip polygon (or on the edge of the clip polygon) + bool clipped; + + // Index from the original input. + // - If this junction is presented in the source polygon/polyline, this is the index of the point with in the source; + // - if this point in a new point that caused by the intersection, this will be -(1+index of the first point of the source line involved in this intersection); + // - if this junction came from the clip polygon, it will be treated as new point. + int64_t src_idx; + + SplitLineJunction(const Point& p, bool clipped, int64_t src_idx) + : p(p) + , clipped(clipped) + , src_idx(src_idx) {} + + bool is_src() const { return src_idx >= 0; } + size_t get_src_index() const + { + if (is_src()) { + return src_idx; + } else { + return -src_idx - 1; + } + } +}; + +using SplittedLine = std::vector; + +SplittedLine do_split_line(const ClipperZUtils::ZPath& path, const ExPolygons& clip, bool closed); + +// Return the splitted line, or empty if no intersection found +template +SplittedLine split_line(const PathType& path, const ExPolygons& clip, bool closed) +{ + if (path.empty()) { + return {}; + } + + // Convert the input path into an open ZPath + ClipperZUtils::ZPath p; + p.reserve(path.size() + closed ? 1 : 0); + ClipperLib_Z::cInt z = 0; + for (const auto& point : path) { + p.emplace_back(point.x(), point.y(), z); + z++; + } + if (closed) { + // duplicate the first point at the end to make a closed path open + p.emplace_back(p.front()); + p.back().z() = z; + } + + return do_split_line(p, clip, closed); +} + +} // Algorithm +} // Slic3r + +#endif /* SRC_LIBSLIC3R_ALGORITHM_LINE_SPLIT_HPP_ */ diff --git a/src/libslic3r/Arachne/utils/ExtrusionJunction.hpp b/src/libslic3r/Arachne/utils/ExtrusionJunction.hpp index 146525185f..942f828b3b 100644 --- a/src/libslic3r/Arachne/utils/ExtrusionJunction.hpp +++ b/src/libslic3r/Arachne/utils/ExtrusionJunction.hpp @@ -40,6 +40,10 @@ struct ExtrusionJunction ExtrusionJunction(const Point p, const coord_t w, const coord_t perimeter_index); bool operator==(const ExtrusionJunction& other) const; + + coord_t x() const { return p.x(); } + coord_t y() const { return p.y(); } + coord_t z() const { return w; } }; inline Point operator-(const ExtrusionJunction& a, const ExtrusionJunction& b) diff --git a/src/libslic3r/Arachne/utils/ExtrusionLine.hpp b/src/libslic3r/Arachne/utils/ExtrusionLine.hpp index ee783fbeba..8438a4c003 100644 --- a/src/libslic3r/Arachne/utils/ExtrusionLine.hpp +++ b/src/libslic3r/Arachne/utils/ExtrusionLine.hpp @@ -194,27 +194,8 @@ struct ExtrusionLine double area() const; }; -static inline Slic3r::ThickPolyline to_thick_polyline(const Arachne::ExtrusionLine &line_junctions) -{ - assert(line_junctions.size() >= 2); - Slic3r::ThickPolyline out; - out.points.emplace_back(line_junctions.front().p); - out.width.emplace_back(line_junctions.front().w); - out.points.emplace_back(line_junctions[1].p); - out.width.emplace_back(line_junctions[1].w); - - auto it_prev = line_junctions.begin() + 1; - for (auto it = line_junctions.begin() + 2; it != line_junctions.end(); ++it) { - out.points.emplace_back(it->p); - out.width.emplace_back(it_prev->w); - out.width.emplace_back(it->w); - it_prev = it; - } - - return out; -} - -static inline Slic3r::ThickPolyline to_thick_polyline(const ClipperLib_Z::Path &path) +template +static inline Slic3r::ThickPolyline to_thick_polyline(const PathType &path) { assert(path.size() >= 2); Slic3r::ThickPolyline out; diff --git a/src/libslic3r/CMakeLists.txt b/src/libslic3r/CMakeLists.txt index 52b38af4f8..d3c08bacc6 100644 --- a/src/libslic3r/CMakeLists.txt +++ b/src/libslic3r/CMakeLists.txt @@ -30,6 +30,8 @@ set(lisbslic3r_sources AABBTreeLines.hpp AABBMesh.hpp AABBMesh.cpp + Algorithm/LineSplit.hpp + Algorithm/LineSplit.cpp Algorithm/PathSorting.hpp Algorithm/RegionExpansion.hpp Algorithm/RegionExpansion.cpp diff --git a/src/libslic3r/ClipperUtils.cpp b/src/libslic3r/ClipperUtils.cpp index dc91e39d10..b9a63e37e7 100644 --- a/src/libslic3r/ClipperUtils.cpp +++ b/src/libslic3r/ClipperUtils.cpp @@ -557,6 +557,8 @@ Slic3r::ExPolygons offset_ex(const Slic3r::ExPolygons &expolygons, const float d { return PolyTreeToExPolygons(expolygons_offset_pt(expolygons, delta, joinType, miterLimit)); } Slic3r::ExPolygons offset_ex(const Slic3r::Surfaces &surfaces, const float delta, ClipperLib::JoinType joinType, double miterLimit) { return PolyTreeToExPolygons(expolygons_offset_pt(surfaces, delta, joinType, miterLimit)); } +Slic3r::ExPolygons offset_ex(const Slic3r::SurfacesPtr &surfaces, const float delta, ClipperLib::JoinType joinType, double miterLimit) + { return PolyTreeToExPolygons(expolygons_offset_pt(surfaces, delta, joinType, miterLimit)); } Polygons offset2(const ExPolygons &expolygons, const float delta1, const float delta2, ClipperLib::JoinType joinType, double miterLimit) { diff --git a/src/libslic3r/ClipperUtils.hpp b/src/libslic3r/ClipperUtils.hpp index 49648db679..167449dc21 100644 --- a/src/libslic3r/ClipperUtils.hpp +++ b/src/libslic3r/ClipperUtils.hpp @@ -344,6 +344,7 @@ Slic3r::ExPolygons offset_ex(const Slic3r::Polygons &polygons, const float delta Slic3r::ExPolygons offset_ex(const Slic3r::ExPolygon &expolygon, const float delta, ClipperLib::JoinType joinType = DefaultJoinType, double miterLimit = DefaultMiterLimit); Slic3r::ExPolygons offset_ex(const Slic3r::ExPolygons &expolygons, const float delta, ClipperLib::JoinType joinType = DefaultJoinType, double miterLimit = DefaultMiterLimit); Slic3r::ExPolygons offset_ex(const Slic3r::Surfaces &surfaces, const float delta, ClipperLib::JoinType joinType = DefaultJoinType, double miterLimit = DefaultMiterLimit); +Slic3r::ExPolygons offset_ex(const Slic3r::SurfacesPtr &surfaces, const float delta, ClipperLib::JoinType joinType = DefaultJoinType, double miterLimit = DefaultMiterLimit); // BBS inline Slic3r::ExPolygons offset_ex(const Slic3r::Polygon &polygon, const float delta, ClipperLib::JoinType joinType = DefaultJoinType, double miterLimit = DefaultMiterLimit) { diff --git a/src/libslic3r/Layer.cpp b/src/libslic3r/Layer.cpp index ddb608901f..cb98c6e6b5 100644 --- a/src/libslic3r/Layer.cpp +++ b/src/libslic3r/Layer.cpp @@ -182,10 +182,6 @@ void Layer::make_perimeters() && config.detect_thin_wall == other_config.detect_thin_wall && config.infill_wall_overlap == other_config.infill_wall_overlap && config.top_bottom_infill_wall_overlap == other_config.top_bottom_infill_wall_overlap - && config.fuzzy_skin == other_config.fuzzy_skin - && config.fuzzy_skin_thickness == other_config.fuzzy_skin_thickness - && config.fuzzy_skin_point_distance == other_config.fuzzy_skin_point_distance - && config.fuzzy_skin_first_layer == other_config.fuzzy_skin_first_layer && config.seam_slope_type == other_config.seam_slope_type && config.seam_slope_conditional == other_config.seam_slope_conditional && config.scarf_angle_threshold == other_config.scarf_angle_threshold @@ -208,7 +204,7 @@ void Layer::make_perimeters() if (layerms.size() == 1) { // optimization (*layerm)->fill_surfaces.surfaces.clear(); - (*layerm)->make_perimeters((*layerm)->slices, &(*layerm)->fill_surfaces, &(*layerm)->fill_no_overlap_expolygons); + (*layerm)->make_perimeters((*layerm)->slices, {*layerm}, &(*layerm)->fill_surfaces, &(*layerm)->fill_no_overlap_expolygons); (*layerm)->fill_expolygons = to_expolygons((*layerm)->fill_surfaces.surfaces); } else { SurfaceCollection new_slices; @@ -232,7 +228,7 @@ void Layer::make_perimeters() SurfaceCollection fill_surfaces; //BBS ExPolygons fill_no_overlap; - layerm_config->make_perimeters(new_slices, &fill_surfaces, &fill_no_overlap); + layerm_config->make_perimeters(new_slices, layerms, &fill_surfaces, &fill_no_overlap); // assign fill_surfaces to each layer if (!fill_surfaces.surfaces.empty()) { diff --git a/src/libslic3r/Layer.hpp b/src/libslic3r/Layer.hpp index 13ae774ff9..a277aca29e 100644 --- a/src/libslic3r/Layer.hpp +++ b/src/libslic3r/Layer.hpp @@ -78,7 +78,7 @@ public: void slices_to_fill_surfaces_clipped(); void prepare_fill_surfaces(); //BBS - void make_perimeters(const SurfaceCollection &slices, SurfaceCollection* fill_surfaces, ExPolygons* fill_no_overlap); + void make_perimeters(const SurfaceCollection &slices, const LayerRegionPtrs &compatible_regions, SurfaceCollection* fill_surfaces, ExPolygons* fill_no_overlap); void process_external_surfaces(const Layer *lower_layer, const Polygons *lower_layer_covered); double infill_area_threshold() const; // Trim surfaces by trimming polygons. Used by the elephant foot compensation at the 1st layer. diff --git a/src/libslic3r/LayerRegion.cpp b/src/libslic3r/LayerRegion.cpp index 58cfcea950..ef5d325f56 100644 --- a/src/libslic3r/LayerRegion.cpp +++ b/src/libslic3r/LayerRegion.cpp @@ -68,7 +68,7 @@ void LayerRegion::slices_to_fill_surfaces_clipped() } } -void LayerRegion::make_perimeters(const SurfaceCollection &slices, SurfaceCollection* fill_surfaces, ExPolygons* fill_no_overlap) +void LayerRegion::make_perimeters(const SurfaceCollection &slices, const LayerRegionPtrs &compatible_regions, SurfaceCollection* fill_surfaces, ExPolygons* fill_no_overlap) { this->perimeters.clear(); this->thin_fills.clear(); @@ -85,6 +85,7 @@ void LayerRegion::make_perimeters(const SurfaceCollection &slices, SurfaceCollec PerimeterGenerator g( // input: &slices, + &compatible_regions, this->layer()->height, this->flow(frPerimeter), ®ion_config, diff --git a/src/libslic3r/PerimeterGenerator.cpp b/src/libslic3r/PerimeterGenerator.cpp index 498b6e5e43..70c6f1eb4e 100644 --- a/src/libslic3r/PerimeterGenerator.cpp +++ b/src/libslic3r/PerimeterGenerator.cpp @@ -20,6 +20,8 @@ #include #include #include "libslic3r/AABBTreeLines.hpp" +#include "Print.hpp" +#include "Algorithm/LineSplit.hpp" static const int overhang_sampling_number = 6; static const double narrow_loop_length_threshold = 10; static const double min_degree_gap = 0.1; @@ -29,6 +31,8 @@ static const int max_overhang_degree = overhang_sampling_number - 1; //we think it's small detail area and will generate smaller line width for it static constexpr double SMALLER_EXT_INSET_OVERLAP_TOLERANCE = 0.22; +//#define DEBUG_FUZZY + namespace Slic3r { // Produces a random value between 0 and 1. Thread-safe. @@ -51,13 +55,11 @@ public: bool is_smaller_width_perimeter; // Depth in the hierarchy. External perimeter has depth = 0. An external perimeter could be both a contour and a hole. unsigned short depth; - // Should this contur be fuzzyfied on path generation? - bool fuzzify; // Children contour, may be both CCW and CW oriented (outer contours or holes). std::vector children; - PerimeterGeneratorLoop(const Polygon &polygon, unsigned short depth, bool is_contour, bool fuzzify, bool is_small_width_perimeter = false) : - polygon(polygon), is_contour(is_contour), is_smaller_width_perimeter(is_small_width_perimeter), depth(depth), fuzzify(fuzzify) {} + PerimeterGeneratorLoop(const Polygon &polygon, unsigned short depth, bool is_contour, bool is_small_width_perimeter = false) : + polygon(polygon), is_contour(is_contour), is_smaller_width_perimeter(is_small_width_perimeter), depth(depth) {} // External perimeter. It may be CCW or CW oriented (outer contour or hole contour). bool is_external() const { return this->depth == 0; } // An island, which may have holes, but it does not have another internal island. @@ -65,23 +67,30 @@ public: }; // Thanks Cura developers for this function. -static void fuzzy_polygon(Polygon &poly, double fuzzy_skin_thickness, double fuzzy_skin_point_distance) +static void fuzzy_polyline(Points& poly, bool closed, const FuzzySkinConfig& cfg) { - const double min_dist_between_points = fuzzy_skin_point_distance * 3. / 4.; // hardcoded: the point distance may vary between 3/4 and 5/4 the supplied value - const double range_random_point_dist = fuzzy_skin_point_distance / 2.; + const double min_dist_between_points = cfg.point_distance * 3. / 4.; // hardcoded: the point distance may vary between 3/4 and 5/4 the supplied value + const double range_random_point_dist = cfg.point_distance / 2.; double dist_left_over = random_value() * (min_dist_between_points / 2.); // the distance to be traversed on the line before making the first new point - Point* p0 = &poly.points.back(); + Point* p0 = &poly.back(); Points out; - out.reserve(poly.points.size()); - for (Point &p1 : poly.points) - { // 'a' is the (next) new point between p0 and p1 + out.reserve(poly.size()); + for (Point &p1 : poly) + { + if (!closed) { + // Skip the first point for open path + closed = true; + p0 = &p1; + continue; + } + // 'a' is the (next) new point between p0 and p1 Vec2d p0p1 = (p1 - *p0).cast(); double p0p1_size = p0p1.norm(); double p0pa_dist = dist_left_over; for (; p0pa_dist < p0p1_size; p0pa_dist += min_dist_between_points + random_value() * range_random_point_dist) { - double r = random_value() * (fuzzy_skin_thickness * 2.) - fuzzy_skin_thickness; + double r = random_value() * (cfg.thickness * 2.) - cfg.thickness; out.emplace_back(*p0 + (p0p1 * (p0pa_dist / p0p1_size) + perp(p0p1).cast().normalized() * r).cast()); } dist_left_over = p0pa_dist - p0p1_size; @@ -95,14 +104,14 @@ static void fuzzy_polygon(Polygon &poly, double fuzzy_skin_thickness, double fuz -- point_idx; } if (out.size() >= 3) - poly.points = std::move(out); + poly = std::move(out); } // Thanks Cura developers for this function. -static void fuzzy_extrusion_line(Arachne::ExtrusionLine& ext_lines, double fuzzy_skin_thickness, double fuzzy_skin_point_dist) +static void fuzzy_extrusion_line(std::vector& ext_lines, const FuzzySkinConfig& cfg) { - const double min_dist_between_points = fuzzy_skin_point_dist * 3. / 4.; // hardcoded: the point distance may vary between 3/4 and 5/4 the supplied value - const double range_random_point_dist = fuzzy_skin_point_dist / 2.; + const double min_dist_between_points = cfg.point_distance * 3. / 4.; // hardcoded: the point distance may vary between 3/4 and 5/4 the supplied value + const double range_random_point_dist = cfg.point_distance / 2.; double dist_left_over = random_value() * (min_dist_between_points / 2.); // the distance to be traversed on the line before making the first new point auto* p0 = &ext_lines.front(); @@ -119,7 +128,7 @@ static void fuzzy_extrusion_line(Arachne::ExtrusionLine& ext_lines, double fuzzy double p0p1_size = p0p1.norm(); double p0pa_dist = dist_left_over; for (; p0pa_dist < p0p1_size; p0pa_dist += min_dist_between_points + random_value() * range_random_point_dist) { - double r = random_value() * (fuzzy_skin_thickness * 2.) - fuzzy_skin_thickness; + double r = random_value() * (cfg.thickness * 2.) - cfg.thickness; out.emplace_back(p0->p + (p0p1 * (p0pa_dist / p0p1_size) + perp(p0p1).cast().normalized() * r).cast(), p1.w, p1.perimeter_index); } dist_left_over = p0pa_dist - p0p1_size; @@ -138,7 +147,7 @@ static void fuzzy_extrusion_line(Arachne::ExtrusionLine& ext_lines, double fuzzy out.front().p = out.back().p; if (out.size() >= 3) - ext_lines.junctions = std::move(out); + ext_lines = std::move(out); } using PerimeterGeneratorLoops = std::vector; @@ -453,6 +462,24 @@ static bool detect_steep_overhang(const PrintRegionConfig *config, return false; } +static bool should_fuzzify(const FuzzySkinConfig& config, const int layer_id, const size_t loop_idx, const bool is_contour) +{ + const auto fuzziy_type = config.type; + + if (fuzziy_type == FuzzySkinType::None) { + return false; + } + if (!config.fuzzy_first_layer && layer_id <= 0) { + // Do not fuzzy first layer unless told to + return false; + } + + const bool fuzzify_contours = loop_idx == 0 || fuzziy_type == FuzzySkinType::AllWalls; + const bool fuzzify_holes = fuzzify_contours && (fuzziy_type == FuzzySkinType::All || fuzziy_type == FuzzySkinType::AllWalls); + + return is_contour ? fuzzify_contours : fuzzify_holes; +} + static ExtrusionEntityCollection traverse_loops(const PerimeterGenerator &perimeter_generator, const PerimeterGeneratorLoops &loops, ThickPolylines &thin_walls, bool &steep_overhang_contour, bool &steep_overhang_hole) { @@ -480,9 +507,6 @@ static ExtrusionEntityCollection traverse_loops(const PerimeterGenerator &perime } else { loop_role = loop.is_contour? elrDefault : elrHole; } - - // detect overhanging/bridging perimeters - ExtrusionPaths paths; // BBS: get lower polygons series, width, mm3_per_mm const std::vector *lower_polygons_series; @@ -510,24 +534,113 @@ static ExtrusionEntityCollection traverse_loops(const PerimeterGenerator &perime extrusion_mm3_per_mm = perimeter_generator.mm3_per_mm(); extrusion_width = perimeter_generator.perimeter_flow.width(); } + const Polygon& polygon = *([&perimeter_generator, &loop, &fuzzified]() ->const Polygon* { + const auto& regions = perimeter_generator.regions_by_fuzzify; + if (regions.size() == 1) { // optimization + const auto& config = regions.begin()->first; + const bool fuzzify = should_fuzzify(config, perimeter_generator.layer_id, loop.depth, loop.is_contour); + if (!fuzzify) { + return &loop.polygon; + } + fuzzified = loop.polygon; + fuzzy_polyline(fuzzified.points, true, config); + return &fuzzified; + } - const Polygon &polygon = loop.fuzzify ? fuzzified : loop.polygon; - if (loop.fuzzify) { + // Find all affective regions + std::vector> fuzzified_regions; + fuzzified_regions.reserve(regions.size()); + for (const auto & region : regions) { + if (should_fuzzify(region.first, perimeter_generator.layer_id, loop.depth, loop.is_contour)) { + fuzzified_regions.emplace_back(region.first, region.second); + } + } + if (fuzzified_regions.empty()) { + return &loop.polygon; + } + +#ifdef DEBUG_FUZZY + { + int i = 0; + for (const auto & r : fuzzified_regions) { + BoundingBox bbox = get_extents(perimeter_generator.slices->surfaces); + bbox.offset(scale_(1.)); + ::Slic3r::SVG svg(debug_out_path("fuzzy_traverse_loops_%d_%d_%d_region_%d.svg", perimeter_generator.layer_id, loop.is_contour ? 0 : 1, loop.depth, i).c_str(), bbox); + svg.draw_outline(perimeter_generator.slices->surfaces); + svg.draw_outline(loop.polygon, "green"); + svg.draw(r.second, "red", 0.5); + svg.draw_outline(r.second, "red"); + svg.Close(); + i++; + } + } +#endif + + // Split the loops into lines with different config, and fuzzy them separately fuzzified = loop.polygon; - fuzzy_polygon(fuzzified, scaled(perimeter_generator.config->fuzzy_skin_thickness.value), scaled(perimeter_generator.config->fuzzy_skin_point_distance.value)); - } + for (const auto& r : fuzzified_regions) { + const auto splitted = Algorithm::split_line(fuzzified, r.second, true); + if (splitted.empty()) { + // No intersection, skip + continue; + } + + // Fuzzy splitted polygon + if (std::all_of(splitted.begin(), splitted.end(), [](const Algorithm::SplitLineJunction& j) { return j.clipped; })) { + // The entire polygon is fuzzified + fuzzy_polyline(fuzzified.points, true, r.first); + } else { + Points segment; + segment.reserve(splitted.size()); + fuzzified.points.clear(); + + const auto fuzzy_current_segment = [&segment, &fuzzified, &r]() { + fuzzified.points.push_back(segment.front()); + const auto back = segment.back(); + fuzzy_polyline(segment, false, r.first); + fuzzified.points.insert(fuzzified.points.end(), segment.begin(), segment.end()); + fuzzified.points.push_back(back); + segment.clear(); + }; + + for (const auto& p : splitted) { + if (p.clipped) { + segment.push_back(p.p); + } else { + if (segment.empty()) { + fuzzified.points.push_back(p.p); + } else { + segment.push_back(p.p); + fuzzy_current_segment(); + } + } + } + if (!segment.empty()) { + // Close the loop + segment.push_back(splitted.front().p); + fuzzy_current_segment(); + } + } + } + + return &fuzzified; + }()); + + ExtrusionPaths paths; if (perimeter_generator.config->detect_overhang_wall && perimeter_generator.layer_id > perimeter_generator.object_config->raft_layers) { + // detect overhanging/bridging perimeters + // get non 100% overhang paths by intersecting this loop with the grown lower slices // prepare grown lower layer slices for overhang detection BoundingBox bbox(polygon.points); bbox.offset(SCALED_EPSILON); // Always reverse extrusion if use fuzzy skin: https://github.com/SoftFever/OrcaSlicer/pull/2413#issuecomment-1769735357 - if (overhangs_reverse && perimeter_generator.config->fuzzy_skin != FuzzySkinType::None) { + if (overhangs_reverse && perimeter_generator.has_fuzzy_skin) { if (loop.is_contour) { steep_overhang_contour = true; - } else if (perimeter_generator.config->fuzzy_skin != FuzzySkinType::External) { + } else if (perimeter_generator.has_fuzzy_hole) { steep_overhang_hole = true; } } @@ -548,7 +661,7 @@ static ExtrusionEntityCollection traverse_loops(const PerimeterGenerator &perime remain_polines = diff_pl({polygon}, lower_polygons_series_clipped); - bool detect_overhang_degree = perimeter_generator.config->overhang_speed_classic && perimeter_generator.config->enable_overhang_speed && perimeter_generator.config->fuzzy_skin == FuzzySkinType::None; + bool detect_overhang_degree = perimeter_generator.config->overhang_speed_classic && perimeter_generator.config->enable_overhang_speed && !perimeter_generator.has_fuzzy_skin; if (!detect_overhang_degree) { if (!inside_polines.empty()) @@ -762,8 +875,6 @@ struct PerimeterGeneratorArachneExtrusion Arachne::ExtrusionLine* extrusion = nullptr; // Indicates if closed ExtrusionLine is a contour or a hole. Used it only when ExtrusionLine is a closed loop. bool is_contour = false; - // Should this extrusion be fuzzyfied on path generation? - bool fuzzify = false; }; @@ -866,8 +977,77 @@ static ExtrusionEntityCollection traverse_extrusions(const PerimeterGenerator& p const bool is_external = extrusion->inset_idx == 0; ExtrusionRole role = is_external ? erExternalPerimeter : erPerimeter; - if (pg_extrusion.fuzzify) - fuzzy_extrusion_line(*extrusion, scaled(perimeter_generator.config->fuzzy_skin_thickness.value), scaled(perimeter_generator.config->fuzzy_skin_point_distance.value)); + const auto& regions = perimeter_generator.regions_by_fuzzify; + const bool is_contour = !extrusion->is_closed || pg_extrusion.is_contour; + if (regions.size() == 1) { // optimization + const auto& config = regions.begin()->first; + const bool fuzzify = should_fuzzify(config, perimeter_generator.layer_id, extrusion->inset_idx, is_contour); + if (fuzzify) + fuzzy_extrusion_line(extrusion->junctions, config); + } else { + // Find all affective regions + std::vector> fuzzified_regions; + fuzzified_regions.reserve(regions.size()); + for (const auto& region : regions) { + if (should_fuzzify(region.first, perimeter_generator.layer_id, extrusion->inset_idx, is_contour)) { + fuzzified_regions.emplace_back(region.first, region.second); + } + } + if (!fuzzified_regions.empty()) { + // Split the loops into lines with different config, and fuzzy them separately + for (const auto& r : fuzzified_regions) { + const auto splitted = Algorithm::split_line(*extrusion, r.second, false); + if (splitted.empty()) { + // No intersection, skip + continue; + } + + // Fuzzy splitted extrusion + if (std::all_of(splitted.begin(), splitted.end(), [](const Algorithm::SplitLineJunction& j) { return j.clipped; })) { + // The entire polygon is fuzzified + fuzzy_extrusion_line(extrusion->junctions, r.first); + } else { + const auto current_ext = extrusion->junctions; + std::vector segment; + segment.reserve(current_ext.size()); + extrusion->junctions.clear(); + + const auto fuzzy_current_segment = [&segment, extrusion, &r]() { + extrusion->junctions.push_back(segment.front()); + const auto back = segment.back(); + fuzzy_extrusion_line(segment, r.first); + extrusion->junctions.insert(extrusion->junctions.end(), segment.begin(), segment.end()); + extrusion->junctions.push_back(back); + segment.clear(); + }; + + const auto to_ex_junction = [¤t_ext](const Algorithm::SplitLineJunction& j) -> Arachne::ExtrusionJunction { + Arachne::ExtrusionJunction res = current_ext[j.get_src_index()]; + if (!j.is_src()) { + res.p = j.p; + } + return res; + }; + + for (const auto& p : splitted) { + if (p.clipped) { + segment.push_back(to_ex_junction(p)); + } else { + if (segment.empty()) { + extrusion->junctions.push_back(to_ex_junction(p)); + } else { + segment.push_back(to_ex_junction(p)); + fuzzy_current_segment(); + } + } + } + if (!segment.empty()) { + fuzzy_current_segment(); + } + } + } + } + } ExtrusionPaths paths; // detect overhanging/bridging perimeters @@ -904,10 +1084,10 @@ static ExtrusionEntityCollection traverse_extrusions(const PerimeterGenerator& p is_external ? perimeter_generator.ext_perimeter_flow : perimeter_generator.perimeter_flow); // Always reverse extrusion if use fuzzy skin: https://github.com/SoftFever/OrcaSlicer/pull/2413#issuecomment-1769735357 - if (overhangs_reverse && perimeter_generator.config->fuzzy_skin != FuzzySkinType::None) { + if (overhangs_reverse && perimeter_generator.has_fuzzy_skin) { if (pg_extrusion.is_contour) { steep_overhang_contour = true; - } else if (perimeter_generator.config->fuzzy_skin != FuzzySkinType::External) { + } else if (perimeter_generator.has_fuzzy_hole) { steep_overhang_hole = true; } } @@ -938,7 +1118,7 @@ static ExtrusionEntityCollection traverse_extrusions(const PerimeterGenerator& p } } - if (perimeter_generator.config->overhang_speed_classic && perimeter_generator.config->enable_overhang_speed && perimeter_generator.config->fuzzy_skin == FuzzySkinType::None) { + if (perimeter_generator.config->overhang_speed_classic && perimeter_generator.config->enable_overhang_speed && !perimeter_generator.has_fuzzy_skin) { Flow flow = is_external ? perimeter_generator.ext_perimeter_flow : perimeter_generator.perimeter_flow; std::map> clipper_serise; @@ -1031,7 +1211,7 @@ static ExtrusionEntityCollection traverse_extrusions(const PerimeterGenerator& p chain_and_reorder_extrusion_paths(paths, &start_point); - if (perimeter_generator.config->enable_overhang_speed && perimeter_generator.config->fuzzy_skin == FuzzySkinType::None) { + if (perimeter_generator.config->enable_overhang_speed && !perimeter_generator.has_fuzzy_skin) { // BBS: filter the speed smooth_overhang_level(paths); } @@ -1642,8 +1822,48 @@ static void reorient_perimeters(ExtrusionEntityCollection &entities, bool steep_ } } +static void group_region_by_fuzzify(PerimeterGenerator& g) +{ + g.regions_by_fuzzify.clear(); + g.has_fuzzy_skin = false; + g.has_fuzzy_hole = false; + + std::unordered_map regions; + for (auto region : *g.compatible_regions) { + const auto& region_config = region->region().config(); + const FuzzySkinConfig cfg{ + region_config.fuzzy_skin, + scaled(region_config.fuzzy_skin_thickness.value), + scaled(region_config.fuzzy_skin_point_distance.value), + region_config.fuzzy_skin_first_layer + }; + auto& surfaces = regions[cfg]; + for (const auto& surface : region->slices.surfaces) { + surfaces.push_back(&surface); + } + + if (cfg.type != FuzzySkinType::None) { + g.has_fuzzy_skin = true; + if (cfg.type != FuzzySkinType::External) { + g.has_fuzzy_hole = true; + } + } + } + + if (regions.size() == 1) { // optimization + g.regions_by_fuzzify[regions.begin()->first] = {}; + return; + } + + for (auto& it : regions) { + g.regions_by_fuzzify[it.first] = offset_ex(it.second, ClipperSafetyOffset); + } +} + void PerimeterGenerator::process_classic() { + group_region_by_fuzzify(*this); + // other perimeters m_mm3_per_mm = this->perimeter_flow.mm3_per_mm(); coord_t perimeter_width = this->perimeter_flow.scaled_width(); @@ -1716,7 +1936,7 @@ void PerimeterGenerator::process_classic() process_no_bridge(all_surfaces, perimeter_spacing, ext_perimeter_width); // BBS: don't simplify too much which influence arc fitting when export gcode if arc_fitting is enabled - double surface_simplify_resolution = (print_config->enable_arc_fitting && this->config->fuzzy_skin == FuzzySkinType::None) ? 0.2 * m_scaled_resolution : m_scaled_resolution; + double surface_simplify_resolution = (print_config->enable_arc_fitting && !this->has_fuzzy_skin) ? 0.2 * m_scaled_resolution : m_scaled_resolution; //BBS: reorder the surface to reduce the travel time ExPolygons surface_exp; for (const Surface &surface : all_surfaces) @@ -1845,32 +2065,29 @@ void PerimeterGenerator::process_classic() break; } { - const bool fuzzify_layer = (this->config->fuzzy_skin_first_layer || this->layer_id>0) && this->config->fuzzy_skin != FuzzySkinType::None; - const bool fuzzify_contours = fuzzify_layer && (i == 0 || this->config->fuzzy_skin == FuzzySkinType::AllWalls); - const bool fuzzify_holes = fuzzify_contours && (this->config->fuzzy_skin == FuzzySkinType::All || this->config->fuzzy_skin == FuzzySkinType::AllWalls); for (const ExPolygon& expolygon : offsets) { // Outer contour may overlap with an inner contour, // inner contour may overlap with another inner contour, // outer contour may overlap with itself. //FIXME evaluate the overlaps, annotate each point with an overlap depth, // compensate for the depth of intersection. - contours[i].emplace_back(expolygon.contour, i, true, fuzzify_contours); + contours[i].emplace_back(expolygon.contour, i, true); if (!expolygon.holes.empty()) { holes[i].reserve(holes[i].size() + expolygon.holes.size()); for (const Polygon& hole : expolygon.holes) - holes[i].emplace_back(hole, i, false, fuzzify_holes); + holes[i].emplace_back(hole, i, false); } } //BBS: save perimeter loop which use smaller width if (i == 0) { for (const ExPolygon& expolygon : offsets_with_smaller_width) { - contours[i].emplace_back(PerimeterGeneratorLoop(expolygon.contour, i, true, fuzzify_contours, true)); + contours[i].emplace_back(PerimeterGeneratorLoop(expolygon.contour, i, true, true)); if (!expolygon.holes.empty()) { holes[i].reserve(holes[i].size() + expolygon.holes.size()); for (const Polygon& hole : expolygon.holes) - holes[i].emplace_back(PerimeterGeneratorLoop(hole, i, false, fuzzify_contours, true)); + holes[i].emplace_back(PerimeterGeneratorLoop(hole, i, false, true)); } } } @@ -2607,6 +2824,8 @@ void bringContoursToFront(std::vector& order // "A framework for adaptive width control of dense contour-parallel toolpaths in fused deposition modeling" void PerimeterGenerator::process_arachne() { + group_region_by_fuzzify(*this); + // other perimeters m_mm3_per_mm = this->perimeter_flow.mm3_per_mm(); coord_t perimeter_spacing = this->perimeter_flow.scaled_spacing(); @@ -2635,7 +2854,7 @@ void PerimeterGenerator::process_arachne() process_no_bridge(all_surfaces, perimeter_spacing, ext_perimeter_width); // BBS: don't simplify too much which influence arc fitting when export gcode if arc_fitting is enabled - double surface_simplify_resolution = (print_config->enable_arc_fitting && this->config->fuzzy_skin == FuzzySkinType::None) ? 0.2 * m_scaled_resolution : m_scaled_resolution; + double surface_simplify_resolution = (print_config->enable_arc_fitting && !this->has_fuzzy_skin) ? 0.2 * m_scaled_resolution : m_scaled_resolution; // we need to process each island separately because we might have different // extra perimeters for each one for (const Surface& surface : all_surfaces) { @@ -2845,7 +3064,7 @@ void PerimeterGenerator::process_arachne() } auto& best_path = all_extrusions[best_candidate]; - ordered_extrusions.push_back({ best_path, best_path->is_contour(), false }); + ordered_extrusions.push_back({ best_path, best_path->is_contour() }); processed[best_candidate] = true; for (size_t unlocked_idx : blocking[best_candidate]) blocked[unlocked_idx]--; @@ -2857,21 +3076,6 @@ void PerimeterGenerator::process_arachne() current_position = best_path->junctions.back().p; //Pick the other end from where we started. } } - const bool fuzzify_layer = (this->config->fuzzy_skin_first_layer || this->layer_id>0) && this->config->fuzzy_skin != FuzzySkinType::None; - if (fuzzify_layer) { - for (PerimeterGeneratorArachneExtrusion& extrusion : ordered_extrusions) { - if (this->config->fuzzy_skin == FuzzySkinType::AllWalls) { - extrusion.fuzzify = true; - } else if (extrusion.extrusion->inset_idx == 0) { - if (extrusion.extrusion->is_closed && this->config->fuzzy_skin == FuzzySkinType::External) { - extrusion.fuzzify = extrusion.is_contour; - } - else { - extrusion.fuzzify = true; - } - } - } - } // printf("New Layer: Layer ID %d\n",layer_id); //debug - new layer if (this->config->wall_sequence == WallSequence::InnerOuterInner && layer_id > 0) { // only enable inner outer inner algorithm after first layer diff --git a/src/libslic3r/PerimeterGenerator.hpp b/src/libslic3r/PerimeterGenerator.hpp index c50f3b7faa..733920e310 100644 --- a/src/libslic3r/PerimeterGenerator.hpp +++ b/src/libslic3r/PerimeterGenerator.hpp @@ -3,17 +3,50 @@ #include "libslic3r.h" #include +#include "Layer.hpp" #include "Flow.hpp" #include "Polygon.hpp" #include "PrintConfig.hpp" #include "SurfaceCollection.hpp" +namespace Slic3r { +struct FuzzySkinConfig +{ + FuzzySkinType type; + coord_t thickness; + coord_t point_distance; + bool fuzzy_first_layer; + + bool operator==(const FuzzySkinConfig& r) const + { + return type == r.type && thickness == r.thickness && point_distance == r.point_distance && fuzzy_first_layer == r.fuzzy_first_layer; + } + + bool operator!=(const FuzzySkinConfig& r) const { return !(*this == r); } +}; +} + +namespace std { +template<> struct hash +{ + size_t operator()(const Slic3r::FuzzySkinConfig& c) const noexcept + { + std::size_t seed = std::hash{}(c.type); + boost::hash_combine(seed, std::hash{}(c.thickness)); + boost::hash_combine(seed, std::hash{}(c.point_distance)); + boost::hash_combine(seed, std::hash{}(c.fuzzy_first_layer)); + return seed; + } +}; +} // namespace std + namespace Slic3r { class PerimeterGenerator { public: // Inputs: const SurfaceCollection *slices; + const LayerRegionPtrs *compatible_regions; const ExPolygons *upper_slices; const ExPolygons *lower_slices; double layer_height; @@ -41,10 +74,14 @@ public: std::pair m_external_overhang_dist_boundary; std::pair m_smaller_external_overhang_dist_boundary; + bool has_fuzzy_skin = false; + bool has_fuzzy_hole = false; + std::unordered_map regions_by_fuzzify; PerimeterGenerator( // Input: - const SurfaceCollection* slices, + const SurfaceCollection* slices, + const LayerRegionPtrs *compatible_regions, double layer_height, Flow flow, const PrintRegionConfig* config, @@ -60,7 +97,7 @@ public: SurfaceCollection* fill_surfaces, //BBS ExPolygons* fill_no_overlap) - : slices(slices), upper_slices(nullptr), lower_slices(nullptr), layer_height(layer_height), + : slices(slices), compatible_regions(compatible_regions), upper_slices(nullptr), lower_slices(nullptr), layer_height(layer_height), layer_id(-1), perimeter_flow(flow), ext_perimeter_flow(flow), overhang_flow(flow), solid_infill_flow(flow), config(config), object_config(object_config), print_config(print_config),