From 409fae6183f8fcd84ec60857bae5c8880103520f Mon Sep 17 00:00:00 2001 From: Vojtech Bubnik Date: Wed, 2 Nov 2022 12:59:31 +0100 Subject: [PATCH] WIP Refactoring of Layers: LayerIslands filled in with perimeter extrusions, gap fill extrusions and fill regions. --- src/libslic3r/BoundingBox.hpp | 2 + src/libslic3r/ClipperUtils.cpp | 2 + src/libslic3r/ClipperUtils.hpp | 1 + src/libslic3r/ExPolygon.cpp | 12 + src/libslic3r/ExPolygon.hpp | 4 + src/libslic3r/Layer.cpp | 351 +++++++++--- src/libslic3r/Layer.hpp | 184 +++++-- src/libslic3r/LayerRegion.cpp | 70 ++- src/libslic3r/PerimeterGenerator.cpp | 794 +++++++++++++-------------- src/libslic3r/PerimeterGenerator.hpp | 4 +- src/libslic3r/Polygon.cpp | 20 + src/libslic3r/Polygon.hpp | 4 + tests/fff_print/test_perimeters.cpp | 3 +- 13 files changed, 891 insertions(+), 560 deletions(-) diff --git a/src/libslic3r/BoundingBox.hpp b/src/libslic3r/BoundingBox.hpp index 1d6cd4ef1..5e3d3e323 100644 --- a/src/libslic3r/BoundingBox.hpp +++ b/src/libslic3r/BoundingBox.hpp @@ -187,6 +187,8 @@ public: friend BoundingBox get_extents_rotated(const Points &points, double angle); }; +using BoundingBoxes = std::vector; + class BoundingBox3 : public BoundingBox3Base { public: diff --git a/src/libslic3r/ClipperUtils.cpp b/src/libslic3r/ClipperUtils.cpp index 650cfca15..1f83309f4 100644 --- a/src/libslic3r/ClipperUtils.cpp +++ b/src/libslic3r/ClipperUtils.cpp @@ -432,6 +432,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 50d96142d..89d05260c 100644 --- a/src/libslic3r/ClipperUtils.hpp +++ b/src/libslic3r/ClipperUtils.hpp @@ -326,6 +326,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); inline Slic3r::Polygons union_safety_offset (const Slic3r::Polygons &polygons) { return offset (polygons, ClipperSafetyOffset); } inline Slic3r::Polygons union_safety_offset (const Slic3r::ExPolygons &expolygons) { return offset (expolygons, ClipperSafetyOffset); } diff --git a/src/libslic3r/ExPolygon.cpp b/src/libslic3r/ExPolygon.cpp index 7e22127cd..2224f991e 100644 --- a/src/libslic3r/ExPolygon.cpp +++ b/src/libslic3r/ExPolygon.cpp @@ -306,6 +306,18 @@ Lines ExPolygon::lines() const return lines; } +// Do expolygons match? If they match, they must have the same topology, +// however their contours may be rotated. +bool expolygons_match(const ExPolygon &l, const ExPolygon &r) +{ + if (l.holes.size() != r.holes.size() || ! polygons_match(l.contour, r.contour)) + return false; + for (size_t hole_idx = 0; hole_idx < l.holes.size(); ++ hole_idx) + if (! polygons_match(l.holes[hole_idx], r.holes[hole_idx])) + return false; + return true; +} + BoundingBox get_extents(const ExPolygon &expolygon) { return get_extents(expolygon.contour); diff --git a/src/libslic3r/ExPolygon.hpp b/src/libslic3r/ExPolygon.hpp index def2be85a..a0e4f272f 100644 --- a/src/libslic3r/ExPolygon.hpp +++ b/src/libslic3r/ExPolygon.hpp @@ -391,6 +391,10 @@ inline ExPolygons expolygons_simplify(const ExPolygons &expolys, double toleranc return out; } +// Do expolygons match? If they match, they must have the same topology, +// however their contours may be rotated. +bool expolygons_match(const ExPolygon &l, const ExPolygon &r); + BoundingBox get_extents(const ExPolygon &expolygon); BoundingBox get_extents(const ExPolygons &expolygons); BoundingBox get_extents_rotated(const ExPolygon &poly, double angle); diff --git a/src/libslic3r/Layer.cpp b/src/libslic3r/Layer.cpp index 82c82372b..1561a2869 100644 --- a/src/libslic3r/Layer.cpp +++ b/src/libslic3r/Layer.cpp @@ -380,90 +380,279 @@ void Layer::make_perimeters() BOOST_LOG_TRIVIAL(trace) << "Generating perimeters for layer " << this->id(); // keep track of regions whose perimeters we have already generated - std::vector done(m_regions.size(), false); - - LayerRegionPtrs layerms; - for (LayerRegionPtrs::iterator layerm = m_regions.begin(); layerm != m_regions.end(); ++ layerm) - if ((*layerm)->slices().empty()) { - (*layerm)->m_perimeters.clear(); - (*layerm)->m_fills.clear(); - (*layerm)->m_thin_fills.clear(); - } else { - size_t region_id = layerm - m_regions.begin(); - if (done[region_id]) - continue; - BOOST_LOG_TRIVIAL(trace) << "Generating perimeters for layer " << this->id() << ", region " << region_id; - done[region_id] = true; - const PrintRegionConfig &config = (*layerm)->region().config(); - - // find compatible regions - layerms.clear(); - layerms.push_back(*layerm); - for (LayerRegionPtrs::const_iterator it = layerm + 1; it != m_regions.end(); ++it) - if (! (*it)->slices().empty()) { - LayerRegion* other_layerm = *it; - const PrintRegionConfig &other_config = other_layerm->region().config(); - if (config.perimeter_extruder == other_config.perimeter_extruder - && config.perimeters == other_config.perimeters - && config.perimeter_speed == other_config.perimeter_speed - && config.external_perimeter_speed == other_config.external_perimeter_speed - && (config.gap_fill_enabled ? config.gap_fill_speed.value : 0.) == - (other_config.gap_fill_enabled ? other_config.gap_fill_speed.value : 0.) - && config.overhangs == other_config.overhangs - && config.opt_serialize("perimeter_extrusion_width") == other_config.opt_serialize("perimeter_extrusion_width") - && config.thin_walls == other_config.thin_walls - && config.external_perimeters_first == other_config.external_perimeters_first - && config.infill_overlap == other_config.infill_overlap - && config.fuzzy_skin == other_config.fuzzy_skin - && config.fuzzy_skin_thickness == other_config.fuzzy_skin_thickness - && config.fuzzy_skin_point_dist == other_config.fuzzy_skin_point_dist) - { - other_layerm->m_perimeters.clear(); - other_layerm->m_fills.clear(); - other_layerm->m_thin_fills.clear(); - layerms.push_back(other_layerm); - done[it - m_regions.begin()] = true; - } - } - - ExPolygons fill_expolygons; - if (layerms.size() == 1) { // optimization - (*layerm)->m_fill_expolygons.clear(); - (*layerm)->m_fill_surfaces.clear(); - (*layerm)->make_perimeters((*layerm)->slices(), fill_expolygons); - (*layerm)->m_fill_expolygons = std::move(fill_expolygons); - } else { - SurfaceCollection new_slices; - // Use the region with highest infill rate, as the make_perimeters() function below decides on the gap fill based on the infill existence. - LayerRegion *layerm_config = layerms.front(); - { - // group slices (surfaces) according to number of extra perimeters - std::map slices; // extra_perimeters => [ surface, surface... ] - for (LayerRegion *layerm : layerms) { - for (const Surface &surface : layerm->slices()) - slices[surface.extra_perimeters].emplace_back(surface); - if (layerm->region().config().fill_density > layerm_config->region().config().fill_density) - layerm_config = layerm; - layerm->m_fill_surfaces.clear(); - layerm->m_fill_expolygons.clear(); - } - // merge the surfaces assigned to each group - for (std::pair &surfaces_with_extra_perimeters : slices) - new_slices.append(offset_ex(surfaces_with_extra_perimeters.second, ClipperSafetyOffset), surfaces_with_extra_perimeters.second.front()); - } - // make perimeters - layerm_config->make_perimeters(new_slices, fill_expolygons); - // assign fill_surfaces to each layer - if (! fill_expolygons.empty()) { - // Separate the fill surfaces. - for (LayerRegion *l : layerms) - l->m_fill_expolygons = intersection_ex(l->slices().surfaces, fill_expolygons); - } - } - } + std::vector done(m_regions.size(), false); + std::vector layer_region_ids; + std::vector> perimeter_and_gapfill_ranges; + ExPolygons fill_expolygons; + std::vector fill_expolygons_ranges; + SurfacesPtr surfaces_to_merge; + SurfacesPtr surfaces_to_merge_temp; + + auto layer_region_reset_perimeters = [](LayerRegion &layerm) { + layerm.m_perimeters.clear(); + layerm.m_fills.clear(); + layerm.m_thin_fills.clear(); + layerm.m_fill_expolygons.clear(); + layerm.m_fill_expolygons_bboxes.clear(); + layerm.m_fill_expolygons_composite.clear(); + layerm.m_fill_expolygons_composite_bboxes.clear(); + }; + + for (LayerRegionPtrs::iterator layerm = m_regions.begin(); layerm != m_regions.end(); ++ layerm) + if (size_t region_id = layerm - m_regions.begin(); ! done[region_id]) { + layer_region_reset_perimeters(**layerm); + if (! (*layerm)->slices().empty()) { + BOOST_LOG_TRIVIAL(trace) << "Generating perimeters for layer " << this->id() << ", region " << region_id; + done[region_id] = true; + const PrintRegionConfig &config = (*layerm)->region().config(); + + perimeter_and_gapfill_ranges.clear(); + fill_expolygons.clear(); + fill_expolygons_ranges.clear(); + surfaces_to_merge.clear(); + + // find compatible regions + layer_region_ids.clear(); + layer_region_ids.push_back(region_id); + for (LayerRegionPtrs::const_iterator it = layerm + 1; it != m_regions.end(); ++it) + if (! (*it)->slices().empty()) { + LayerRegion* other_layerm = *it; + const PrintRegionConfig &other_config = other_layerm->region().config(); + if (config.perimeter_extruder == other_config.perimeter_extruder + && config.perimeters == other_config.perimeters + && config.perimeter_speed == other_config.perimeter_speed + && config.external_perimeter_speed == other_config.external_perimeter_speed + && (config.gap_fill_enabled ? config.gap_fill_speed.value : 0.) == + (other_config.gap_fill_enabled ? other_config.gap_fill_speed.value : 0.) + && config.overhangs == other_config.overhangs + && config.opt_serialize("perimeter_extrusion_width") == other_config.opt_serialize("perimeter_extrusion_width") + && config.thin_walls == other_config.thin_walls + && config.external_perimeters_first == other_config.external_perimeters_first + && config.infill_overlap == other_config.infill_overlap + && config.fuzzy_skin == other_config.fuzzy_skin + && config.fuzzy_skin_thickness == other_config.fuzzy_skin_thickness + && config.fuzzy_skin_point_dist == other_config.fuzzy_skin_point_dist) + { + layer_region_reset_perimeters(*other_layerm); + layer_region_ids.push_back(it - m_regions.begin()); + done[it - m_regions.begin()] = true; + } + } + + if (layer_region_ids.size() == 1) { // optimization + (*layerm)->make_perimeters((*layerm)->slices(), perimeter_and_gapfill_ranges, fill_expolygons, fill_expolygons_ranges); + this->sort_perimeters_into_islands((*layerm)->slices(), region_id, perimeter_and_gapfill_ranges, std::move(fill_expolygons), fill_expolygons_ranges, layer_region_ids); + } else { + SurfaceCollection new_slices; + // Use the region with highest infill rate, as the make_perimeters() function below decides on the gap fill based on the infill existence. + LayerRegion *layerm_config = m_regions[layer_region_ids.front()]; + { + // Merge slices (surfaces) according to number of extra perimeters. + for (uint32_t region_id : layer_region_ids) { + LayerRegion &layerm = *m_regions[region_id]; + for (const Surface &surface : layerm.slices()) + surfaces_to_merge.emplace_back(&surface); + if (layerm.region().config().fill_density > layerm_config->region().config().fill_density) + layerm_config = &layerm; + } + std::sort(surfaces_to_merge.begin(), surfaces_to_merge.end(), [](const Surface *l, const Surface *r){ return l->extra_perimeters < r->extra_perimeters; }); + for (size_t i = 0; i < surfaces_to_merge.size();) { + size_t j = i; + const Surface &first = *surfaces_to_merge[i]; + size_t extra_perimeters = first.extra_perimeters; + for (; j < surfaces_to_merge.size() && surfaces_to_merge[j]->extra_perimeters == extra_perimeters; ++ j) ; + if (i + 1 == j) + // Nothing to merge, just copy. + new_slices.surfaces.emplace_back(*surfaces_to_merge[i]); + else { + surfaces_to_merge_temp.assign(surfaces_to_merge.begin() + i, surfaces_to_merge.begin() + j); + new_slices.append(offset_ex(surfaces_to_merge_temp, ClipperSafetyOffset), first); + } + i = j; + } + } + // make perimeters + layerm_config->make_perimeters(new_slices, perimeter_and_gapfill_ranges, fill_expolygons, fill_expolygons_ranges); + this->sort_perimeters_into_islands(new_slices, region_id, perimeter_and_gapfill_ranges, std::move(fill_expolygons), fill_expolygons_ranges, layer_region_ids); + } + } + } BOOST_LOG_TRIVIAL(trace) << "Generating perimeters for layer " << this->id() << " - Done"; } +void Layer::sort_perimeters_into_islands( + // Slices for which perimeters and fill_expolygons were just created. + // The slices may have been created by merging multiple source slices with the same perimeter parameters. + const SurfaceCollection &slices, + // Region where the perimeters, gap fills and fill expolygons are stored. + const uint32_t region_id, + // Perimeters and gap fills produced by the perimeter generator for the slices, + // sorted by the source slices. + const std::vector> &perimeter_and_gapfill_ranges, + // Fill expolygons produced for all source slices above. + ExPolygons &&fill_expolygons, + // Fill expolygon ranges sorted by the source slices. + const std::vector &fill_expolygons_ranges, + // If the current layer consists of multiple regions, then the fill_expolygons above are split by the source LayerRegion surfaces. + const std::vector &layer_region_ids) +{ + for (LayerSlice &lslice : this->lslices_ex) + lslice.islands.clear(); + + LayerRegion &this_layer_region = *m_regions[region_id]; + + // Bounding boxes of fill_expolygons. + BoundingBoxes fill_expolygons_bboxes; + fill_expolygons_bboxes.reserve(fill_expolygons.size()); + for (const ExPolygon &expolygon : fill_expolygons) + fill_expolygons_bboxes.emplace_back(get_extents(expolygon)); + + // Map of source fill_expolygon into region and fill_expolygon of that region. + // -1: not set + std::vector> map_expolygon_to_region_and_fill; + + // assign fill_surfaces to each layer + if (! fill_expolygons.empty()) { + if (layer_region_ids.size() == 1) { + this_layer_region.m_fill_expolygons = std::move(fill_expolygons); + this_layer_region.m_fill_expolygons_bboxes = std::move(fill_expolygons_bboxes); + } else { + // Sort the bounding boxes lexicographically. + std::vector fill_expolygons_bboxes_sorted(fill_expolygons_bboxes.size()); + std::iota(fill_expolygons_bboxes_sorted.begin(), fill_expolygons_bboxes_sorted.end(), 0); + std::sort(fill_expolygons_bboxes_sorted.begin(), fill_expolygons_bboxes_sorted.end(), [&fill_expolygons_bboxes](uint32_t lhs, uint32_t rhs){ + const BoundingBox &bbl = fill_expolygons_bboxes[lhs]; + const BoundingBox &bbr = fill_expolygons_bboxes[rhs]; + return bbl.min < bbr.min || (bbl.min == bbr.min && bbl.max < bbr.max); + }); + map_expolygon_to_region_and_fill.assign(fill_expolygons.size(), std::make_pair(-1, -1)); + for (uint32_t region_idx : layer_region_ids) { + LayerRegion &l = *m_regions[region_idx]; + l.m_fill_expolygons = intersection_ex(l.slices().surfaces, fill_expolygons); + l.m_fill_expolygons_bboxes.reserve(l.fill_expolygons().size()); + for (const ExPolygon &expolygon : l.fill_expolygons()) { + BoundingBox bbox = get_extents(expolygon); + l.m_fill_expolygons_bboxes.emplace_back(bbox); + auto it_bbox = std::lower_bound(fill_expolygons_bboxes_sorted.begin(), fill_expolygons_bboxes_sorted.end(), bbox, [&fill_expolygons_bboxes](uint32_t lhs, const BoundingBox &bbr){ + const BoundingBox &bbl = fill_expolygons_bboxes[lhs]; + return bbl.min < bbr.min || (bbl.min == bbr.min && bbl.max < bbr.max); + }); + if (it_bbox != fill_expolygons_bboxes_sorted.end()) + if (uint32_t fill_id = *it_bbox; fill_expolygons_bboxes[fill_id] == bbox) { + // With a very high probability the two expolygons match exactly. Confirm that. + if (expolygons_match(expolygon, fill_expolygons[fill_id])) { + std::pair &ref = map_expolygon_to_region_and_fill[fill_id]; + // Only one expolygon produced by intersection with LayerRegion surface may match an expolygon of fill_expolygons. + assert(ref.first == -1); + ref.first = region_idx; + ref.second = int(&expolygon - l.fill_expolygons().data()); + } + } + } + } + } + } + + // Traverse the slices in an increasing order of bounding box size, so that the islands inside another islands are tested first, + // so we can just test a point inside ExPolygon::contour and we may skip testing the holes. + auto point_inside_surface = [this](const size_t lslice_idx, const Point &point) { + const BoundingBox &bbox = this->lslices_ex[lslice_idx].bbox; + return point.x() >= bbox.min.x() && point.x() < bbox.max.x() && + point.y() >= bbox.min.y() && point.y() < bbox.max.y() && + this->lslices[lslice_idx].contour.contains(point); + }; + + // Take one sample point for each source slice, to be used to sort source slices into layer slices. + // source slice index + its sample. + std::vector> perimeter_slices_queue; + perimeter_slices_queue.reserve(slices.size()); + for (uint32_t islice = 0; islice < uint32_t(slices.size()); ++ islice) { + const std::pair &extrusions = perimeter_and_gapfill_ranges[islice]; + Point sample; + bool sample_set = false; + if (! extrusions.first.empty()) { + sample = this_layer_region.perimeters().entities[*extrusions.first.begin()]->first_point(); + sample_set = true; + } else if (! extrusions.second.empty()) { + sample = this_layer_region.thin_fills().entities[*extrusions.second.begin()]->first_point(); + sample_set = true; + } else if (const ExPolygonRange &fill_expolygon_range = fill_expolygons_ranges[islice]; ! fill_expolygons.empty()) { + for (uint32_t iexpoly : fill_expolygon_range) + if (const ExPolygon &expoly = fill_expolygons[iexpoly]; ! expoly.empty()) { + sample = expoly.contour.points.front(); + sample_set = true; + break; + } + } + if (sample_set) + perimeter_slices_queue.emplace_back(islice, sample); + } + + // Sort perimeter extrusions, thin fill extrusions and fill expolygons into islands. + std::vector region_fill_sorted_last; + for (int lslice_idx = int(this->lslices_ex.size()) - 1; lslice_idx >= 0 && ! perimeter_slices_queue.empty(); -- lslice_idx) { + for (auto it_source_slice = perimeter_slices_queue.begin(); it_source_slice != perimeter_slices_queue.end(); ++ it_source_slice) + if (point_inside_surface(lslice_idx, it_source_slice->second)) { + this->lslices_ex[lslice_idx].islands.push_back({}); + LayerIsland &island = this->lslices_ex[lslice_idx].islands.back(); + const uint32_t source_slice_idx = it_source_slice->first; + island.perimeters = LayerExtrusionRange(region_id, perimeter_and_gapfill_ranges[source_slice_idx].first); + island.thin_fills = perimeter_and_gapfill_ranges[source_slice_idx].second; + if (ExPolygonRange fill_range = fill_expolygons_ranges[source_slice_idx]; ! fill_range.empty()) { + if (layer_region_ids.size() == 1) { + // Layer island is made of one fill region only. + island.fill_expolygons = fill_range; + island.fill_region_id = region_id; + } else { + // Check whether the fill expolygons of this island were split into multiple regions. + island.fill_region_id = LayerIsland::fill_region_composite_id; + for (uint32_t fill_idx : fill_range) { + const std::pair &kvp = map_expolygon_to_region_and_fill[fill_idx]; + if (kvp.first == -1 || (island.fill_region_id != -1 && island.fill_region_id != kvp.second)) { + island.fill_region_id = LayerIsland::fill_region_composite_id; + break; + } else + island.fill_region_id = kvp.second; + } + if (island.fill_expolygons_composite()) { + // They were split, thus store the unsplit "composite" expolygons into the region of perimeters. + auto begin = uint32_t(this_layer_region.fill_expolygons_composite().size()); + this_layer_region.m_fill_expolygons_composite.reserve(this_layer_region.fill_expolygons_composite().size() + fill_range.size()); + std::move(fill_expolygons.begin() + *fill_range.begin(), fill_expolygons.begin() + *fill_range.end(), std::back_inserter(this_layer_region.m_fill_expolygons_composite)); + this_layer_region.m_fill_expolygons_composite_bboxes.insert(this_layer_region.m_fill_expolygons_composite_bboxes.end(), + fill_expolygons_bboxes.begin() + *fill_range.begin(), fill_expolygons_bboxes.begin() + *fill_range.end()); + island.fill_expolygons = ExPolygonRange(begin, uint32_t(this_layer_region.fill_expolygons_composite().size())); + } else { + if (region_fill_sorted_last.empty()) + region_fill_sorted_last.assign(m_regions.size(), 0); + uint32_t &last = region_fill_sorted_last[island.fill_region_id]; + // They were not split and they belong to the same region. + // Sort the region m_fill_expolygons to a continuous span. + uint32_t begin = last; + LayerRegion &layerm = *m_regions[island.fill_region_id]; + for (uint32_t fill_id : fill_range) { + uint32_t region_fill_id = map_expolygon_to_region_and_fill[fill_id].second; + assert(region_fill_id >= last); + if (region_fill_id > last) { + std::swap(layerm.m_fill_expolygons[region_fill_id], layerm.m_fill_expolygons[last]); + std::swap(layerm.m_fill_expolygons_bboxes[region_fill_id], layerm.m_fill_expolygons_bboxes[last]); + } + ++ last; + } + island.fill_expolygons = ExPolygonRange(begin, last); + } + } + } + if (std::next(it_source_slice) != perimeter_slices_queue.end()) { + // Remove the current slice & point pair from the queue. + *it_source_slice = perimeter_slices_queue.back(); + perimeter_slices_queue.pop_back(); + } + break; + } + } +} + void Layer::export_region_slices_to_svg(const char *path) const { BoundingBox bbox; diff --git a/src/libslic3r/Layer.hpp b/src/libslic3r/Layer.hpp index 0fda3a6b2..137f35e5c 100644 --- a/src/libslic3r/Layer.hpp +++ b/src/libslic3r/Layer.hpp @@ -28,6 +28,71 @@ namespace FillLightning { class Generator; }; +// Range of indices, providing support for range based loops. +template +class IndexRange +{ +private: + // Just a bare minimum functionality iterator required by range-for loop. + template + class IteratorType { + public: + T operator*() const { return m_idx; } + bool operator!=(const IteratorType &rhs) const { return m_idx != rhs.m_idx; } + void operator++() { ++ m_idx; } + private: + friend class IndexRange; + IteratorType(T idx) : m_idx(idx) {} + T m_idx; + }; + // Index of the first extrusion in LayerRegion. + T m_begin { 0 }; + // Index of the last extrusion in LayerRegion. + T m_end { 0 }; + +public: + IndexRange(T ibegin, T iend) : m_begin(ibegin), m_end(iend) {} + IndexRange() = default; + + using Iterator = IteratorType; + + Iterator begin() const { assert(m_begin <= m_end); return Iterator(m_begin); }; + Iterator end() const { assert(m_begin <= m_end); return Iterator(m_end); }; + + bool empty() const { assert(m_begin <= m_end); return m_begin >= m_end; } + T size() const { assert(m_begin <= m_end); return m_end - m_begin; } +}; + +using ExtrusionRange = IndexRange; +using ExPolygonRange = IndexRange; + +// Range of extrusions, referencing the source region by an index. +class LayerExtrusionRange : public ExtrusionRange +{ +public: + LayerExtrusionRange(uint32_t iregion, uint32_t ibegin, uint32_t iend) : m_region(iregion), ExtrusionRange(ibegin, iend) {} + LayerExtrusionRange(uint32_t iregion, ExtrusionRange extrusion_range) : m_region(iregion), ExtrusionRange(extrusion_range) {} + LayerExtrusionRange() = default; + + // Index of LayerRegion in Layer. + uint32_t region() const { return m_region; }; + +private: + // Index of LayerRegion in Layer. + uint32_t m_region { 0 }; +}; + +// Most likely one LayerIsland will be filled with maximum one fill type. +static constexpr const size_t LayerExtrusionRangesStaticSize = 1; +using LayerExtrusionRanges = +#ifdef NDEBUG + // To reduce memory allocation in release mode. + boost::container::small_vector; +#else // NDEBUG + // To ease debugging. + std::vector; +#endif // NDEBUG + class LayerRegion { public: @@ -39,7 +104,16 @@ public: // divided by type top/bottom/internal [[nodiscard]] const SurfaceCollection& slices() const { return m_slices; } + // Unspecified fill polygons, used for overhang detection ("ensure vertical wall thickness feature") + // and for re-starting of infills. [[nodiscard]] const ExPolygons& fill_expolygons() const { return m_fill_expolygons; } + // and their bounding boxes + [[nodiscard]] const BoundingBoxes& fill_expolygons_bboxes() const { return m_fill_expolygons_bboxes; } + // Storage for fill regions produced for a single LayerIsland, of which infill splits into multiple islands. + // Not used for a plain single material print with no infill modifiers. + [[nodiscard]] const ExPolygons& fill_expolygons_composite() const { return m_fill_expolygons_composite; } + // and their bounding boxes + [[nodiscard]] const BoundingBoxes& fill_expolygons_composite_bboxes() const { return m_fill_expolygons_composite_bboxes; } // collection of surfaces generated by slicing the original geometry // divided by type top/bottom/internal @@ -67,7 +141,17 @@ public: void slices_to_fill_surfaces_clipped(); void prepare_fill_surfaces(); - void make_perimeters(const SurfaceCollection &slices, ExPolygons &fill_expolygons); + // Produce perimeter extrusions, gap fill extrusions and fill polygons for input slices. + void make_perimeters( + // Input slices for which the perimeters, gap fills and fill expolygons are to be generated. + const SurfaceCollection &slices, + // Ranges of perimeter extrusions and gap fill extrusions per suface, referencing + // newly created extrusions stored at this LayerRegion. + std::vector> &perimeter_and_gapfill_ranges, + // All fill areas produced for all input slices above. + ExPolygons &fill_expolygons, + // Ranges of fill areas above per input slice. + std::vector &fill_expolygons_ranges); 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. @@ -116,11 +200,18 @@ private: // Unspecified fill polygons, used for overhang detection ("ensure vertical wall thickness feature") // and for re-starting of infills. ExPolygons m_fill_expolygons; + // and their bounding boxes + BoundingBoxes m_fill_expolygons_bboxes; + // Storage for fill regions produced for a single LayerIsland, of which infill splits into multiple islands. + // Not used for a plain single material print with no infill modifiers. + ExPolygons m_fill_expolygons_composite; + // and their bounding boxes + BoundingBoxes m_fill_expolygons_composite_bboxes; - // collection of surfaces for infill generation + // Collection of surfaces for infill generation, created by splitting m_slices by m_fill_expolygons. SurfaceCollection m_fill_surfaces; - // collection of extrusion paths/loops filling gaps + // Collection of extrusion paths/loops filling gaps // These fills are generated by the perimeter generator. // They are not printed on their own, but they are copied to this->fills during infill generation. ExtrusionEntityCollection m_thin_fills; @@ -141,66 +232,31 @@ private: // Polygons bridged; }; -// Range of two indices, providing support for range based loops. -template -class IndexRange -{ -public: - IndexRange(T ibegin, T iend) : m_begin(ibegin), m_end(iend) {} - IndexRange() = default; - - T begin() const { assert(m_begin <= m_end); return m_begin; }; - T end() const { assert(m_begin <= m_end); return m_end; }; - - bool empty() const { assert(m_begin <= m_end); return m_begin >= m_end; } - T size() const { assert(m_begin <= m_end); return m_end - m_begin; } - -private: - // Index of the first extrusion in LayerRegion. - T m_begin { 0 }; - // Index of the last extrusion in LayerRegion. - T m_end { 0 }; -}; - -class LayerExtrusionRange : public IndexRange -{ -public: - LayerExtrusionRange(uint32_t iregion, uint32_t ibegin, uint32_t iend) : m_region(iregion), IndexRange(ibegin, iend) {} - LayerExtrusionRange() = default; - - // Index of LayerRegion in Layer. - uint32_t region() const { return m_region; }; - -private: - // Index of LayerRegion in Layer. - uint32_t m_region { 0 }; -}; - -static constexpr const size_t LayerExtrusionRangesStaticSize = 1; -using LayerExtrusionRanges = -#ifdef NDEBUG - // To reduce memory allocation in release mode. - boost::container::small_vector; -#else // NDEBUG - // To ease debugging. - std::vector; -#endif // NDEBUG - -using ExPolygonRange = IndexRange(); - // LayerSlice contains one or more LayerIsland objects, // each LayerIsland containing a set of perimeter extrusions extruded with one particular PrintRegionConfig parameters // and one or multiple struct LayerIsland { +private: + friend class Layer; + static constexpr const uint32_t fill_region_composite_id = std::numeric_limits::max(); + +public: // Perimeter extrusions in LayerRegion belonging to this island. LayerExtrusionRange perimeters; + // Thin fills of the same region as perimeters. Generated by classic perimeter generator, while Arachne puts them into perimeters. + ExtrusionRange thin_fills; // Infill + gapfill extrusions in LayerRegion belonging to this island. LayerExtrusionRanges fills; - // Region that is to be filled with the fills above. + // Region that is to be filled with the fills above (thin fills, regular fills). + // Pointing to either LayerRegion::fill_expolygons() or LayerRegion::fill_expolygons_composite() + // based on this->fill_expolygons_composite() flag. ExPolygonRange fill_expolygons; + // Index of LayerRegion with LayerRegion::fill_expolygons() if not fill_expolygons_composite(). + uint32_t fill_region_id; + bool fill_expolygons_composite() const { return this->fill_region_id == fill_region_composite_id; } // Centroid of this island used for path planning. - Point centroid; +// Point centroid; bool has_extrusions() const { return ! this->perimeters.empty() || ! this->fills.empty(); } }; @@ -215,7 +271,7 @@ using LayerIslands = std::vector; #endif // NDEBUG -// +// One connected island of a layer. LayerSlice may consist of one or more LayerIslands. struct LayerSlice { struct Link { @@ -235,6 +291,10 @@ struct LayerSlice BoundingBox bbox; Links overlaps_above; Links overlaps_below; + // One island for each region or region set that generates its own perimeters. + // For multi-material prints or prints with regions of different perimeter parameters, + // a LayerSlice may be split into multiple LayerIslands. + // For most prints there will be just one island. LayerIslands islands; bool has_extrusions() const { for (const LayerIsland &island : islands) if (island.has_extrusions()) return true; return false; } @@ -324,6 +384,22 @@ protected: virtual ~Layer(); private: + void sort_perimeters_into_islands( + // Slices for which perimeters and fill_expolygons were just created. + // The slices may have been created by merging multiple source slices with the same perimeter parameters. + const SurfaceCollection &slices, + // Region where the perimeters, gap fills and fill expolygons are stored. + const uint32_t region_id, + // Perimeters and gap fills produced by the perimeter generator for the slices, + // sorted by the source slices. + const std::vector> &perimeter_and_gapfill_ranges, + // Fill expolygons produced for all source slices above. + ExPolygons &&fill_expolygons, + // Fill expolygon ranges sorted by the source slices. + const std::vector &fill_expolygons_ranges, + // If the current layer consists of multiple regions, then the fill_expolygons above are split by the source LayerRegion surfaces. + const std::vector &layer_region_ids); + // Sequential index of layer, 0-based, offsetted by number of raft layers. size_t m_id; PrintObject *m_object; @@ -337,7 +413,7 @@ public: // Used to suppress retraction if moving for a support extrusion over these support_islands. ExPolygons support_islands; // Slightly inflated bounding boxes of the above, for faster intersection query. - std::vector support_islands_bboxes; + BoundingBoxes support_islands_bboxes; // Extrusion paths for the support base and for the support interface and contacts. ExtrusionEntityCollection support_fills; diff --git a/src/libslic3r/LayerRegion.cpp b/src/libslic3r/LayerRegion.cpp index 4b346647b..0c50fce27 100644 --- a/src/libslic3r/LayerRegion.cpp +++ b/src/libslic3r/LayerRegion.cpp @@ -59,11 +59,26 @@ void LayerRegion::slices_to_fill_surfaces_clipped() } } -void LayerRegion::make_perimeters(const SurfaceCollection &slices, ExPolygons &fill_expolygons) +// Produce perimeter extrusions, gap fill extrusions and fill polygons for input slices. +void LayerRegion::make_perimeters( + // Input slices for which the perimeters, gap fills and fill expolygons are to be generated. + const SurfaceCollection &slices, + // Ranges of perimeter extrusions and gap fill extrusions per suface, referencing + // newly created extrusions stored at this LayerRegion. + std::vector> &perimeter_and_gapfill_ranges, + // All fill areas produced for all input slices above. + ExPolygons &fill_expolygons, + // Ranges of fill areas above per input slice. + std::vector &fill_expolygons_ranges) { m_perimeters.clear(); m_thin_fills.clear(); + perimeter_and_gapfill_ranges.reserve(perimeter_and_gapfill_ranges.size() + slices.size()); + // There may be more expolygons produced per slice, thus this reserve is conservative. + fill_expolygons.reserve(fill_expolygons.size() + slices.size()); + fill_expolygons_ranges.reserve(fill_expolygons_ranges.size() + slices.size()); + const PrintConfig &print_config = this->layer()->object()->print()->config(); const PrintRegionConfig ®ion_config = this->region().config(); // This needs to be in sync with PrintObject::_slice() slicing_mode_normal_below_layer! @@ -90,28 +105,37 @@ void LayerRegion::make_perimeters(const SurfaceCollection &slices, ExPolygons &f // Cache for offsetted lower_slices Polygons lower_layer_polygons_cache; - if (this->layer()->object()->config().perimeter_generator.value == PerimeterGeneratorType::Arachne && !spiral_vase) - PerimeterGenerator::process_arachne( - // input: - params, - &slices, - lower_slices, - lower_layer_polygons_cache, - // output: - m_perimeters, - m_thin_fills, - fill_expolygons); - else - PerimeterGenerator::process_classic( - // input: - params, - &slices, - lower_slices, - lower_layer_polygons_cache, - // output: - m_perimeters, - m_thin_fills, - fill_expolygons); + for (const Surface &surface : slices) { + auto perimeters_begin = uint32_t(m_perimeters.size()); + auto gap_fills_begin = uint32_t(m_thin_fills.size()); + auto fill_expolygons_begin = uint32_t(fill_expolygons.size()); + if (this->layer()->object()->config().perimeter_generator.value == PerimeterGeneratorType::Arachne && !spiral_vase) + PerimeterGenerator::process_arachne( + // input: + params, + surface, + lower_slices, + lower_layer_polygons_cache, + // output: + m_perimeters, + m_thin_fills, + fill_expolygons); + else + PerimeterGenerator::process_classic( + // input: + params, + surface, + lower_slices, + lower_layer_polygons_cache, + // output: + m_perimeters, + m_thin_fills, + fill_expolygons); + perimeter_and_gapfill_ranges.emplace_back( + ExtrusionRange{ perimeters_begin, uint32_t(m_perimeters.size()) }, + ExtrusionRange{ gap_fills_begin, uint32_t(m_thin_fills.size()) }); + fill_expolygons_ranges.emplace_back(ExtrusionRange{ fill_expolygons_begin, uint32_t(fill_expolygons.size()) }); + } } //#define EXTERNAL_SURFACES_OFFSET_PARAMETERS ClipperLib::jtMiter, 3. diff --git a/src/libslic3r/PerimeterGenerator.cpp b/src/libslic3r/PerimeterGenerator.cpp index 27bc0e9d7..92759238e 100644 --- a/src/libslic3r/PerimeterGenerator.cpp +++ b/src/libslic3r/PerimeterGenerator.cpp @@ -601,7 +601,7 @@ static void export_perimeters_to_svg(const std::string &path, const Polygons &co void PerimeterGenerator::process_arachne( // Inputs: const Parameters ¶ms, - const SurfaceCollection *slices, + const Surface &surface, const ExPolygons *lower_slices, // Cache: Polygons &lower_slices_polygons_cache, @@ -633,202 +633,200 @@ void PerimeterGenerator::process_arachne( // we need to process each island separately because we might have different // extra perimeters for each one - for (const Surface &surface : slices->surfaces) { - // detect how many perimeters must be generated for this island - int loop_number = params.config.perimeters + surface.extra_perimeters - 1; // 0-indexed loops - ExPolygons last = offset_ex(surface.expolygon.simplify_p(params.scaled_resolution), - float(ext_perimeter_width / 2. - ext_perimeter_spacing / 2.)); - Polygons last_p = to_polygons(last); + // detect how many perimeters must be generated for this island + int loop_number = params.config.perimeters + surface.extra_perimeters - 1; // 0-indexed loops + ExPolygons last = offset_ex(surface.expolygon.simplify_p(params.scaled_resolution), - float(ext_perimeter_width / 2. - ext_perimeter_spacing / 2.)); + Polygons last_p = to_polygons(last); - Arachne::WallToolPaths wallToolPaths(last_p, ext_perimeter_spacing, perimeter_spacing, coord_t(loop_number + 1), 0, params.layer_height, params.object_config, params.print_config); - std::vector perimeters = wallToolPaths.getToolPaths(); - loop_number = int(perimeters.size()) - 1; + Arachne::WallToolPaths wallToolPaths(last_p, ext_perimeter_spacing, perimeter_spacing, coord_t(loop_number + 1), 0, params.layer_height, params.object_config, params.print_config); + std::vector perimeters = wallToolPaths.getToolPaths(); + loop_number = int(perimeters.size()) - 1; #ifdef ARACHNE_DEBUG - { - static int iRun = 0; - export_perimeters_to_svg(debug_out_path("arachne-perimeters-%d-%d.svg", layer_id, iRun++), to_polygons(last), perimeters, union_ex(wallToolPaths.getInnerContour())); - } + { + static int iRun = 0; + export_perimeters_to_svg(debug_out_path("arachne-perimeters-%d-%d.svg", layer_id, iRun++), to_polygons(last), perimeters, union_ex(wallToolPaths.getInnerContour())); + } #endif - // All closed ExtrusionLine should have the same the first and the last point. - // But in rare cases, Arachne produce ExtrusionLine marked as closed but without - // equal the first and the last point. - assert([&perimeters = std::as_const(perimeters)]() -> bool { - for (const Arachne::VariableWidthLines &perimeter : perimeters) - for (const Arachne::ExtrusionLine &el : perimeter) - if (el.is_closed && el.junctions.front().p != el.junctions.back().p) - return false; - return true; - }()); + // All closed ExtrusionLine should have the same the first and the last point. + // But in rare cases, Arachne produce ExtrusionLine marked as closed but without + // equal the first and the last point. + assert([&perimeters = std::as_const(perimeters)]() -> bool { + for (const Arachne::VariableWidthLines &perimeter : perimeters) + for (const Arachne::ExtrusionLine &el : perimeter) + if (el.is_closed && el.junctions.front().p != el.junctions.back().p) + return false; + return true; + }()); - int start_perimeter = int(perimeters.size()) - 1; - int end_perimeter = -1; - int direction = -1; + int start_perimeter = int(perimeters.size()) - 1; + int end_perimeter = -1; + int direction = -1; - if (params.config.external_perimeters_first) { - start_perimeter = 0; - end_perimeter = int(perimeters.size()); - direction = 1; - } - - std::vector all_extrusions; - for (int perimeter_idx = start_perimeter; perimeter_idx != end_perimeter; perimeter_idx += direction) { - if (perimeters[perimeter_idx].empty()) - continue; - for (Arachne::ExtrusionLine &wall : perimeters[perimeter_idx]) - all_extrusions.emplace_back(&wall); - } - - // Find topological order with constraints from extrusions_constrains. - std::vector blocked(all_extrusions.size(), 0); // Value indicating how many extrusions it is blocking (preceding extrusions) an extrusion. - std::vector> blocking(all_extrusions.size()); // Each extrusion contains a vector of extrusions that are blocked by this extrusion. - std::unordered_map map_extrusion_to_idx; - for (size_t idx = 0; idx < all_extrusions.size(); idx++) - map_extrusion_to_idx.emplace(all_extrusions[idx], idx); - - auto extrusions_constrains = Arachne::WallToolPaths::getRegionOrder(all_extrusions, params.config.external_perimeters_first); - for (auto [before, after] : extrusions_constrains) { - auto after_it = map_extrusion_to_idx.find(after); - ++blocked[after_it->second]; - blocking[map_extrusion_to_idx.find(before)->second].emplace_back(after_it->second); - } - - std::vector processed(all_extrusions.size(), false); // Indicate that the extrusion was already processed. - Point current_position = all_extrusions.empty() ? Point::Zero() : all_extrusions.front()->junctions.front().p; // Some starting position. - std::vector ordered_extrusions; // To store our result in. At the end we'll std::swap. - ordered_extrusions.reserve(all_extrusions.size()); - - while (ordered_extrusions.size() < all_extrusions.size()) { - size_t best_candidate = 0; - double best_distance_sqr = std::numeric_limits::max(); - bool is_best_closed = false; - - std::vector available_candidates; - for (size_t candidate = 0; candidate < all_extrusions.size(); ++candidate) { - if (processed[candidate] || blocked[candidate]) - continue; // Not a valid candidate. - available_candidates.push_back(candidate); - } - - std::sort(available_candidates.begin(), available_candidates.end(), [&all_extrusions](const size_t a_idx, const size_t b_idx) -> bool { - return all_extrusions[a_idx]->is_closed < all_extrusions[b_idx]->is_closed; - }); - - for (const size_t candidate_path_idx : available_candidates) { - auto& path = all_extrusions[candidate_path_idx]; - - if (path->junctions.empty()) { // No vertices in the path. Can't find the start position then or really plan it in. Put that at the end. - if (best_distance_sqr == std::numeric_limits::max()) { - best_candidate = candidate_path_idx; - is_best_closed = path->is_closed; - } - continue; - } - - const Point candidate_position = path->junctions.front().p; - double distance_sqr = (current_position - candidate_position).cast().norm(); - if (distance_sqr < best_distance_sqr) { // Closer than the best candidate so far. - if (path->is_closed || (!path->is_closed && best_distance_sqr != std::numeric_limits::max()) || (!path->is_closed && !is_best_closed)) { - best_candidate = candidate_path_idx; - best_distance_sqr = distance_sqr; - is_best_closed = path->is_closed; - } - } - } - - auto &best_path = all_extrusions[best_candidate]; - ordered_extrusions.push_back({best_path, best_path->is_contour(), false}); - processed[best_candidate] = true; - for (size_t unlocked_idx : blocking[best_candidate]) - blocked[unlocked_idx]--; - - if(!best_path->junctions.empty()) { //If all paths were empty, the best path is still empty. We don't upate the current position then. - if(best_path->is_closed) - current_position = best_path->junctions[0].p; //We end where we started. - else - current_position = best_path->junctions.back().p; //Pick the other end from where we started. - } - } - - if (params.layer_id > 0 && params.config.fuzzy_skin != FuzzySkinType::None) { - std::vector closed_loop_extrusions; - for (PerimeterGeneratorArachneExtrusion &extrusion : ordered_extrusions) - if (extrusion.extrusion->inset_idx == 0) { - if (extrusion.extrusion->is_closed && params.config.fuzzy_skin == FuzzySkinType::External) { - closed_loop_extrusions.emplace_back(&extrusion); - } else { - extrusion.fuzzify = true; - } - } - - if (params.config.fuzzy_skin == FuzzySkinType::External) { - ClipperLib_Z::Paths loops_paths; - loops_paths.reserve(closed_loop_extrusions.size()); - for (const auto &cl_extrusion : closed_loop_extrusions) { - assert(cl_extrusion->extrusion->junctions.front() == cl_extrusion->extrusion->junctions.back()); - size_t loop_idx = &cl_extrusion - &closed_loop_extrusions.front(); - ClipperLib_Z::Path loop_path; - loop_path.reserve(cl_extrusion->extrusion->junctions.size() - 1); - for (auto junction_it = cl_extrusion->extrusion->junctions.begin(); junction_it != std::prev(cl_extrusion->extrusion->junctions.end()); ++junction_it) - loop_path.emplace_back(junction_it->p.x(), junction_it->p.y(), loop_idx); - loops_paths.emplace_back(loop_path); - } - - ClipperLib_Z::Clipper clipper; - clipper.AddPaths(loops_paths, ClipperLib_Z::ptSubject, true); - ClipperLib_Z::PolyTree loops_polytree; - clipper.Execute(ClipperLib_Z::ctUnion, loops_polytree, ClipperLib_Z::pftEvenOdd, ClipperLib_Z::pftEvenOdd); - - for (const ClipperLib_Z::PolyNode *child_node : loops_polytree.Childs) { - // The whole contour must have the same index. - coord_t polygon_idx = child_node->Contour.front().z(); - bool has_same_idx = std::all_of(child_node->Contour.begin(), child_node->Contour.end(), - [&polygon_idx](const ClipperLib_Z::IntPoint &point) -> bool { return polygon_idx == point.z(); }); - if (has_same_idx) - closed_loop_extrusions[polygon_idx]->fuzzify = true; - } - } - } - - if (ExtrusionEntityCollection extrusion_coll = traverse_extrusions(params, lower_slices_polygons_cache, ordered_extrusions); !extrusion_coll.empty()) - out_loops.append(extrusion_coll); - - ExPolygons infill_contour = union_ex(wallToolPaths.getInnerContour()); - const coord_t spacing = (perimeters.size() == 1) ? ext_perimeter_spacing2 : perimeter_spacing; - if (offset_ex(infill_contour, -float(spacing / 2.)).empty()) - infill_contour.clear(); // Infill region is too small, so let's filter it out. - - // create one more offset to be used as boundary for fill - // we offset by half the perimeter spacing (to get to the actual infill boundary) - // and then we offset back and forth by half the infill spacing to only consider the - // non-collapsing regions - coord_t inset = - (loop_number < 0) ? 0 : - (loop_number == 0) ? - // one loop - ext_perimeter_spacing: - // two or more loops? - perimeter_spacing; - - inset = coord_t(scale_(params.config.get_abs_value("infill_overlap", unscale(inset)))); - Polygons pp; - for (ExPolygon &ex : infill_contour) - ex.simplify_p(params.scaled_resolution, &pp); - // collapse too narrow infill areas - const auto min_perimeter_infill_spacing = coord_t(solid_infill_spacing * (1. - INSET_OVERLAP_TOLERANCE)); - // append infill areas to fill_surfaces - append(out_fill_expolygons, - offset2_ex( - union_ex(pp), - float(- min_perimeter_infill_spacing / 2.), - float(inset + min_perimeter_infill_spacing / 2.))); + if (params.config.external_perimeters_first) { + start_perimeter = 0; + end_perimeter = int(perimeters.size()); + direction = 1; } + + std::vector all_extrusions; + for (int perimeter_idx = start_perimeter; perimeter_idx != end_perimeter; perimeter_idx += direction) { + if (perimeters[perimeter_idx].empty()) + continue; + for (Arachne::ExtrusionLine &wall : perimeters[perimeter_idx]) + all_extrusions.emplace_back(&wall); + } + + // Find topological order with constraints from extrusions_constrains. + std::vector blocked(all_extrusions.size(), 0); // Value indicating how many extrusions it is blocking (preceding extrusions) an extrusion. + std::vector> blocking(all_extrusions.size()); // Each extrusion contains a vector of extrusions that are blocked by this extrusion. + std::unordered_map map_extrusion_to_idx; + for (size_t idx = 0; idx < all_extrusions.size(); idx++) + map_extrusion_to_idx.emplace(all_extrusions[idx], idx); + + auto extrusions_constrains = Arachne::WallToolPaths::getRegionOrder(all_extrusions, params.config.external_perimeters_first); + for (auto [before, after] : extrusions_constrains) { + auto after_it = map_extrusion_to_idx.find(after); + ++blocked[after_it->second]; + blocking[map_extrusion_to_idx.find(before)->second].emplace_back(after_it->second); + } + + std::vector processed(all_extrusions.size(), false); // Indicate that the extrusion was already processed. + Point current_position = all_extrusions.empty() ? Point::Zero() : all_extrusions.front()->junctions.front().p; // Some starting position. + std::vector ordered_extrusions; // To store our result in. At the end we'll std::swap. + ordered_extrusions.reserve(all_extrusions.size()); + + while (ordered_extrusions.size() < all_extrusions.size()) { + size_t best_candidate = 0; + double best_distance_sqr = std::numeric_limits::max(); + bool is_best_closed = false; + + std::vector available_candidates; + for (size_t candidate = 0; candidate < all_extrusions.size(); ++candidate) { + if (processed[candidate] || blocked[candidate]) + continue; // Not a valid candidate. + available_candidates.push_back(candidate); + } + + std::sort(available_candidates.begin(), available_candidates.end(), [&all_extrusions](const size_t a_idx, const size_t b_idx) -> bool { + return all_extrusions[a_idx]->is_closed < all_extrusions[b_idx]->is_closed; + }); + + for (const size_t candidate_path_idx : available_candidates) { + auto& path = all_extrusions[candidate_path_idx]; + + if (path->junctions.empty()) { // No vertices in the path. Can't find the start position then or really plan it in. Put that at the end. + if (best_distance_sqr == std::numeric_limits::max()) { + best_candidate = candidate_path_idx; + is_best_closed = path->is_closed; + } + continue; + } + + const Point candidate_position = path->junctions.front().p; + double distance_sqr = (current_position - candidate_position).cast().norm(); + if (distance_sqr < best_distance_sqr) { // Closer than the best candidate so far. + if (path->is_closed || (!path->is_closed && best_distance_sqr != std::numeric_limits::max()) || (!path->is_closed && !is_best_closed)) { + best_candidate = candidate_path_idx; + best_distance_sqr = distance_sqr; + is_best_closed = path->is_closed; + } + } + } + + auto &best_path = all_extrusions[best_candidate]; + ordered_extrusions.push_back({best_path, best_path->is_contour(), false}); + processed[best_candidate] = true; + for (size_t unlocked_idx : blocking[best_candidate]) + blocked[unlocked_idx]--; + + if(!best_path->junctions.empty()) { //If all paths were empty, the best path is still empty. We don't upate the current position then. + if(best_path->is_closed) + current_position = best_path->junctions[0].p; //We end where we started. + else + current_position = best_path->junctions.back().p; //Pick the other end from where we started. + } + } + + if (params.layer_id > 0 && params.config.fuzzy_skin != FuzzySkinType::None) { + std::vector closed_loop_extrusions; + for (PerimeterGeneratorArachneExtrusion &extrusion : ordered_extrusions) + if (extrusion.extrusion->inset_idx == 0) { + if (extrusion.extrusion->is_closed && params.config.fuzzy_skin == FuzzySkinType::External) { + closed_loop_extrusions.emplace_back(&extrusion); + } else { + extrusion.fuzzify = true; + } + } + + if (params.config.fuzzy_skin == FuzzySkinType::External) { + ClipperLib_Z::Paths loops_paths; + loops_paths.reserve(closed_loop_extrusions.size()); + for (const auto &cl_extrusion : closed_loop_extrusions) { + assert(cl_extrusion->extrusion->junctions.front() == cl_extrusion->extrusion->junctions.back()); + size_t loop_idx = &cl_extrusion - &closed_loop_extrusions.front(); + ClipperLib_Z::Path loop_path; + loop_path.reserve(cl_extrusion->extrusion->junctions.size() - 1); + for (auto junction_it = cl_extrusion->extrusion->junctions.begin(); junction_it != std::prev(cl_extrusion->extrusion->junctions.end()); ++junction_it) + loop_path.emplace_back(junction_it->p.x(), junction_it->p.y(), loop_idx); + loops_paths.emplace_back(loop_path); + } + + ClipperLib_Z::Clipper clipper; + clipper.AddPaths(loops_paths, ClipperLib_Z::ptSubject, true); + ClipperLib_Z::PolyTree loops_polytree; + clipper.Execute(ClipperLib_Z::ctUnion, loops_polytree, ClipperLib_Z::pftEvenOdd, ClipperLib_Z::pftEvenOdd); + + for (const ClipperLib_Z::PolyNode *child_node : loops_polytree.Childs) { + // The whole contour must have the same index. + coord_t polygon_idx = child_node->Contour.front().z(); + bool has_same_idx = std::all_of(child_node->Contour.begin(), child_node->Contour.end(), + [&polygon_idx](const ClipperLib_Z::IntPoint &point) -> bool { return polygon_idx == point.z(); }); + if (has_same_idx) + closed_loop_extrusions[polygon_idx]->fuzzify = true; + } + } + } + + if (ExtrusionEntityCollection extrusion_coll = traverse_extrusions(params, lower_slices_polygons_cache, ordered_extrusions); !extrusion_coll.empty()) + out_loops.append(extrusion_coll); + + ExPolygons infill_contour = union_ex(wallToolPaths.getInnerContour()); + const coord_t spacing = (perimeters.size() == 1) ? ext_perimeter_spacing2 : perimeter_spacing; + if (offset_ex(infill_contour, -float(spacing / 2.)).empty()) + infill_contour.clear(); // Infill region is too small, so let's filter it out. + + // create one more offset to be used as boundary for fill + // we offset by half the perimeter spacing (to get to the actual infill boundary) + // and then we offset back and forth by half the infill spacing to only consider the + // non-collapsing regions + coord_t inset = + (loop_number < 0) ? 0 : + (loop_number == 0) ? + // one loop + ext_perimeter_spacing: + // two or more loops? + perimeter_spacing; + + inset = coord_t(scale_(params.config.get_abs_value("infill_overlap", unscale(inset)))); + Polygons pp; + for (ExPolygon &ex : infill_contour) + ex.simplify_p(params.scaled_resolution, &pp); + // collapse too narrow infill areas + const auto min_perimeter_infill_spacing = coord_t(solid_infill_spacing * (1. - INSET_OVERLAP_TOLERANCE)); + // append infill areas to fill_surfaces + append(out_fill_expolygons, + offset2_ex( + union_ex(pp), + float(- min_perimeter_infill_spacing / 2.), + float(inset + min_perimeter_infill_spacing / 2.))); } void PerimeterGenerator::process_classic( // Inputs: const Parameters ¶ms, - const SurfaceCollection *slices, + const Surface &surface, const ExPolygons *lower_slices, // Cache: Polygons &lower_slices_polygons_cache, @@ -875,230 +873,228 @@ void PerimeterGenerator::process_classic( // we need to process each island separately because we might have different // extra perimeters for each one - for (const Surface &surface : slices->surfaces) { - // detect how many perimeters must be generated for this island - int loop_number = params.config.perimeters + surface.extra_perimeters - 1; // 0-indexed loops - ExPolygons last = union_ex(surface.expolygon.simplify_p(params.scaled_resolution)); - ExPolygons gaps; - if (loop_number >= 0) { - // In case no perimeters are to be generated, loop_number will equal to -1. - std::vector contours(loop_number+1); // depth => loops - std::vector holes(loop_number+1); // depth => loops - ThickPolylines thin_walls; - // we loop one time more than needed in order to find gaps after the last perimeter was applied - for (int i = 0;; ++ i) { // outer loop is 0 - // Calculate next onion shell of perimeters. - ExPolygons offsets; - if (i == 0) { - // the minimum thickness of a single loop is: - // ext_width/2 + ext_spacing/2 + spacing/2 + width/2 - offsets = params.config.thin_walls ? - offset2_ex( - last, - - float(ext_perimeter_width / 2. + ext_min_spacing / 2. - 1), - + float(ext_min_spacing / 2. - 1)) : - offset_ex(last, - float(ext_perimeter_width / 2.)); - // look for thin walls - if (params.config.thin_walls) { - // the following offset2 ensures almost nothing in @thin_walls is narrower than $min_width - // (actually, something larger than that still may exist due to mitering or other causes) - coord_t min_width = coord_t(scale_(params.ext_perimeter_flow.nozzle_diameter() / 3)); - ExPolygons expp = opening_ex( - // medial axis requires non-overlapping geometry - diff_ex(last, offset(offsets, float(ext_perimeter_width / 2.) + ClipperSafetyOffset)), - float(min_width / 2.)); - // the maximum thickness of our thin wall area is equal to the minimum thickness of a single loop - for (ExPolygon &ex : expp) - ex.medial_axis(ext_perimeter_width + ext_perimeter_spacing2, min_width, &thin_walls); - } - if (params.spiral_vase && offsets.size() > 1) { - // Remove all but the largest area polygon. - keep_largest_contour_only(offsets); - } - } else { - //FIXME Is this offset correct if the line width of the inner perimeters differs - // from the line width of the infill? - coord_t distance = (i == 1) ? ext_perimeter_spacing2 : perimeter_spacing; - offsets = params.config.thin_walls ? - // This path will ensure, that the perimeters do not overfill, as in - // prusa3d/Slic3r GH #32, but with the cost of rounding the perimeters - // excessively, creating gaps, which then need to be filled in by the not very - // reliable gap fill algorithm. - // Also the offset2(perimeter, -x, x) may sometimes lead to a perimeter, which is larger than - // the original. - offset2_ex(last, - - float(distance + min_spacing / 2. - 1.), - float(min_spacing / 2. - 1.)) : - // If "detect thin walls" is not enabled, this paths will be entered, which - // leads to overflows, as in prusa3d/Slic3r GH #32 - offset_ex(last, - float(distance)); - // look for gaps - if (has_gap_fill) - // not using safety offset here would "detect" very narrow gaps - // (but still long enough to escape the area threshold) that gap fill - // won't be able to fill but we'd still remove from infill area - append(gaps, diff_ex( - offset(last, - float(0.5 * distance)), - offset(offsets, float(0.5 * distance + 10)))); // safety offset + // detect how many perimeters must be generated for this island + int loop_number = params.config.perimeters + surface.extra_perimeters - 1; // 0-indexed loops + ExPolygons last = union_ex(surface.expolygon.simplify_p(params.scaled_resolution)); + ExPolygons gaps; + if (loop_number >= 0) { + // In case no perimeters are to be generated, loop_number will equal to -1. + std::vector contours(loop_number+1); // depth => loops + std::vector holes(loop_number+1); // depth => loops + ThickPolylines thin_walls; + // we loop one time more than needed in order to find gaps after the last perimeter was applied + for (int i = 0;; ++ i) { // outer loop is 0 + // Calculate next onion shell of perimeters. + ExPolygons offsets; + if (i == 0) { + // the minimum thickness of a single loop is: + // ext_width/2 + ext_spacing/2 + spacing/2 + width/2 + offsets = params.config.thin_walls ? + offset2_ex( + last, + - float(ext_perimeter_width / 2. + ext_min_spacing / 2. - 1), + + float(ext_min_spacing / 2. - 1)) : + offset_ex(last, - float(ext_perimeter_width / 2.)); + // look for thin walls + if (params.config.thin_walls) { + // the following offset2 ensures almost nothing in @thin_walls is narrower than $min_width + // (actually, something larger than that still may exist due to mitering or other causes) + coord_t min_width = coord_t(scale_(params.ext_perimeter_flow.nozzle_diameter() / 3)); + ExPolygons expp = opening_ex( + // medial axis requires non-overlapping geometry + diff_ex(last, offset(offsets, float(ext_perimeter_width / 2.) + ClipperSafetyOffset)), + float(min_width / 2.)); + // the maximum thickness of our thin wall area is equal to the minimum thickness of a single loop + for (ExPolygon &ex : expp) + ex.medial_axis(ext_perimeter_width + ext_perimeter_spacing2, min_width, &thin_walls); } - if (offsets.empty()) { - // Store the number of loops actually generated. - loop_number = i - 1; - // No region left to be filled in. - last.clear(); - break; - } else if (i > loop_number) { - // If i > loop_number, we were looking just for gaps. - break; + if (params.spiral_vase && offsets.size() > 1) { + // Remove all but the largest area polygon. + keep_largest_contour_only(offsets); } - { - const bool fuzzify_contours = params.config.fuzzy_skin != FuzzySkinType::None && i == 0 && params.layer_id > 0; - const bool fuzzify_holes = fuzzify_contours && params.config.fuzzy_skin == FuzzySkinType::All; - 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); + } else { + //FIXME Is this offset correct if the line width of the inner perimeters differs + // from the line width of the infill? + coord_t distance = (i == 1) ? ext_perimeter_spacing2 : perimeter_spacing; + offsets = params.config.thin_walls ? + // This path will ensure, that the perimeters do not overfill, as in + // prusa3d/Slic3r GH #32, but with the cost of rounding the perimeters + // excessively, creating gaps, which then need to be filled in by the not very + // reliable gap fill algorithm. + // Also the offset2(perimeter, -x, x) may sometimes lead to a perimeter, which is larger than + // the original. + offset2_ex(last, + - float(distance + min_spacing / 2. - 1.), + float(min_spacing / 2. - 1.)) : + // If "detect thin walls" is not enabled, this paths will be entered, which + // leads to overflows, as in prusa3d/Slic3r GH #32 + offset_ex(last, - float(distance)); + // look for gaps + if (has_gap_fill) + // not using safety offset here would "detect" very narrow gaps + // (but still long enough to escape the area threshold) that gap fill + // won't be able to fill but we'd still remove from infill area + append(gaps, diff_ex( + offset(last, - float(0.5 * distance)), + offset(offsets, float(0.5 * distance + 10)))); // safety offset + } + if (offsets.empty()) { + // Store the number of loops actually generated. + loop_number = i - 1; + // No region left to be filled in. + last.clear(); + break; + } else if (i > loop_number) { + // If i > loop_number, we were looking just for gaps. + break; + } + { + const bool fuzzify_contours = params.config.fuzzy_skin != FuzzySkinType::None && i == 0 && params.layer_id > 0; + const bool fuzzify_holes = fuzzify_contours && params.config.fuzzy_skin == FuzzySkinType::All; + 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); - 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); - } + 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); } } - last = std::move(offsets); - if (i == loop_number && (! has_gap_fill || params.config.fill_density.value == 0)) { - // The last run of this loop is executed to collect gaps for gap fill. - // As the gap fill is either disabled or not - break; - } } - - // nest loops: holes first - for (int d = 0; d <= loop_number; ++ d) { - PerimeterGeneratorLoops &holes_d = holes[d]; - // loop through all holes having depth == d - for (int i = 0; i < (int)holes_d.size(); ++ i) { - const PerimeterGeneratorLoop &loop = holes_d[i]; - // find the hole loop that contains this one, if any - for (int t = d + 1; t <= loop_number; ++ t) { - for (int j = 0; j < (int)holes[t].size(); ++ j) { - PerimeterGeneratorLoop &candidate_parent = holes[t][j]; - if (candidate_parent.polygon.contains(loop.polygon.first_point())) { - candidate_parent.children.push_back(loop); - holes_d.erase(holes_d.begin() + i); - -- i; - goto NEXT_LOOP; - } - } - } - // if no hole contains this hole, find the contour loop that contains it - for (int t = loop_number; t >= 0; -- t) { - for (int j = 0; j < (int)contours[t].size(); ++ j) { - PerimeterGeneratorLoop &candidate_parent = contours[t][j]; - if (candidate_parent.polygon.contains(loop.polygon.first_point())) { - candidate_parent.children.push_back(loop); - holes_d.erase(holes_d.begin() + i); - -- i; - goto NEXT_LOOP; - } - } - } - NEXT_LOOP: ; - } + last = std::move(offsets); + if (i == loop_number && (! has_gap_fill || params.config.fill_density.value == 0)) { + // The last run of this loop is executed to collect gaps for gap fill. + // As the gap fill is either disabled or not + break; } - // nest contour loops - for (int d = loop_number; d >= 1; -- d) { - PerimeterGeneratorLoops &contours_d = contours[d]; - // loop through all contours having depth == d - for (int i = 0; i < (int)contours_d.size(); ++ i) { - const PerimeterGeneratorLoop &loop = contours_d[i]; - // find the contour loop that contains it - for (int t = d - 1; t >= 0; -- t) { - for (size_t j = 0; j < contours[t].size(); ++ j) { - PerimeterGeneratorLoop &candidate_parent = contours[t][j]; - if (candidate_parent.polygon.contains(loop.polygon.first_point())) { - candidate_parent.children.push_back(loop); - contours_d.erase(contours_d.begin() + i); - -- i; - goto NEXT_CONTOUR; - } - } - } - NEXT_CONTOUR: ; - } - } - // at this point, all loops should be in contours[0] - ExtrusionEntityCollection entities = traverse_loops(params, lower_slices_polygons_cache, contours.front(), thin_walls); - // if brim will be printed, reverse the order of perimeters so that - // we continue inwards after having finished the brim - // TODO: add test for perimeter order - if (params.config.external_perimeters_first || - (params.layer_id == 0 && params.object_config.brim_width.value > 0)) - entities.reverse(); - // append perimeters for this slice as a collection - if (! entities.empty()) - out_loops.append(entities); - } // for each loop of an island - - // fill gaps - if (! gaps.empty()) { - // collapse - double min = 0.2 * perimeter_width * (1 - INSET_OVERLAP_TOLERANCE); - double max = 2. * perimeter_spacing; - ExPolygons gaps_ex = diff_ex( - //FIXME offset2 would be enough and cheaper. - opening_ex(gaps, float(min / 2.)), - offset2_ex(gaps, - float(max / 2.), float(max / 2. + ClipperSafetyOffset))); - ThickPolylines polylines; - for (const ExPolygon &ex : gaps_ex) - ex.medial_axis(max, min, &polylines); - if (! polylines.empty()) { - ExtrusionEntityCollection gap_fill; - variable_width(polylines, erGapFill, params.solid_infill_flow, gap_fill.entities); - /* Make sure we don't infill narrow parts that are already gap-filled - (we only consider this surface's gaps to reduce the diff() complexity). - Growing actual extrusions ensures that gaps not filled by medial axis - are not subtracted from fill surfaces (they might be too short gaps - that medial axis skips but infill might join with other infill regions - and use zigzag). */ - //FIXME Vojtech: This grows by a rounded extrusion width, not by line spacing, - // therefore it may cover the area, but no the volume. - last = diff_ex(last, gap_fill.polygons_covered_by_width(10.f)); - out_gap_fill.append(std::move(gap_fill.entities)); - } } - // create one more offset to be used as boundary for fill - // we offset by half the perimeter spacing (to get to the actual infill boundary) - // and then we offset back and forth by half the infill spacing to only consider the - // non-collapsing regions - coord_t inset = - (loop_number < 0) ? 0 : - (loop_number == 0) ? - // one loop - ext_perimeter_spacing / 2 : - // two or more loops? - perimeter_spacing / 2; - // only apply infill overlap if we actually have one perimeter - if (inset > 0) - inset -= coord_t(scale_(params.config.get_abs_value("infill_overlap", unscale(inset + solid_infill_spacing / 2)))); - // simplify infill contours according to resolution - Polygons pp; - for (ExPolygon &ex : last) - ex.simplify_p(params.scaled_resolution, &pp); - // collapse too narrow infill areas - coord_t min_perimeter_infill_spacing = coord_t(solid_infill_spacing * (1. - INSET_OVERLAP_TOLERANCE)); - // append infill areas to fill_surfaces - append(out_fill_expolygons, - offset2_ex( - union_ex(pp), - float(- inset - min_perimeter_infill_spacing / 2.), - float(min_perimeter_infill_spacing / 2.))); - } // for each island + // nest loops: holes first + for (int d = 0; d <= loop_number; ++ d) { + PerimeterGeneratorLoops &holes_d = holes[d]; + // loop through all holes having depth == d + for (int i = 0; i < (int)holes_d.size(); ++ i) { + const PerimeterGeneratorLoop &loop = holes_d[i]; + // find the hole loop that contains this one, if any + for (int t = d + 1; t <= loop_number; ++ t) { + for (int j = 0; j < (int)holes[t].size(); ++ j) { + PerimeterGeneratorLoop &candidate_parent = holes[t][j]; + if (candidate_parent.polygon.contains(loop.polygon.first_point())) { + candidate_parent.children.push_back(loop); + holes_d.erase(holes_d.begin() + i); + -- i; + goto NEXT_LOOP; + } + } + } + // if no hole contains this hole, find the contour loop that contains it + for (int t = loop_number; t >= 0; -- t) { + for (int j = 0; j < (int)contours[t].size(); ++ j) { + PerimeterGeneratorLoop &candidate_parent = contours[t][j]; + if (candidate_parent.polygon.contains(loop.polygon.first_point())) { + candidate_parent.children.push_back(loop); + holes_d.erase(holes_d.begin() + i); + -- i; + goto NEXT_LOOP; + } + } + } + NEXT_LOOP: ; + } + } + // nest contour loops + for (int d = loop_number; d >= 1; -- d) { + PerimeterGeneratorLoops &contours_d = contours[d]; + // loop through all contours having depth == d + for (int i = 0; i < (int)contours_d.size(); ++ i) { + const PerimeterGeneratorLoop &loop = contours_d[i]; + // find the contour loop that contains it + for (int t = d - 1; t >= 0; -- t) { + for (size_t j = 0; j < contours[t].size(); ++ j) { + PerimeterGeneratorLoop &candidate_parent = contours[t][j]; + if (candidate_parent.polygon.contains(loop.polygon.first_point())) { + candidate_parent.children.push_back(loop); + contours_d.erase(contours_d.begin() + i); + -- i; + goto NEXT_CONTOUR; + } + } + } + NEXT_CONTOUR: ; + } + } + // at this point, all loops should be in contours[0] + ExtrusionEntityCollection entities = traverse_loops(params, lower_slices_polygons_cache, contours.front(), thin_walls); + // if brim will be printed, reverse the order of perimeters so that + // we continue inwards after having finished the brim + // TODO: add test for perimeter order + if (params.config.external_perimeters_first || + (params.layer_id == 0 && params.object_config.brim_width.value > 0)) + entities.reverse(); + // append perimeters for this slice as a collection + if (! entities.empty()) + out_loops.append(entities); + } // for each loop of an island + + // fill gaps + if (! gaps.empty()) { + // collapse + double min = 0.2 * perimeter_width * (1 - INSET_OVERLAP_TOLERANCE); + double max = 2. * perimeter_spacing; + ExPolygons gaps_ex = diff_ex( + //FIXME offset2 would be enough and cheaper. + opening_ex(gaps, float(min / 2.)), + offset2_ex(gaps, - float(max / 2.), float(max / 2. + ClipperSafetyOffset))); + ThickPolylines polylines; + for (const ExPolygon &ex : gaps_ex) + ex.medial_axis(max, min, &polylines); + if (! polylines.empty()) { + ExtrusionEntityCollection gap_fill; + variable_width(polylines, erGapFill, params.solid_infill_flow, gap_fill.entities); + /* Make sure we don't infill narrow parts that are already gap-filled + (we only consider this surface's gaps to reduce the diff() complexity). + Growing actual extrusions ensures that gaps not filled by medial axis + are not subtracted from fill surfaces (they might be too short gaps + that medial axis skips but infill might join with other infill regions + and use zigzag). */ + //FIXME Vojtech: This grows by a rounded extrusion width, not by line spacing, + // therefore it may cover the area, but no the volume. + last = diff_ex(last, gap_fill.polygons_covered_by_width(10.f)); + out_gap_fill.append(std::move(gap_fill.entities)); + } + } + + // create one more offset to be used as boundary for fill + // we offset by half the perimeter spacing (to get to the actual infill boundary) + // and then we offset back and forth by half the infill spacing to only consider the + // non-collapsing regions + coord_t inset = + (loop_number < 0) ? 0 : + (loop_number == 0) ? + // one loop + ext_perimeter_spacing / 2 : + // two or more loops? + perimeter_spacing / 2; + // only apply infill overlap if we actually have one perimeter + if (inset > 0) + inset -= coord_t(scale_(params.config.get_abs_value("infill_overlap", unscale(inset + solid_infill_spacing / 2)))); + // simplify infill contours according to resolution + Polygons pp; + for (ExPolygon &ex : last) + ex.simplify_p(params.scaled_resolution, &pp); + // collapse too narrow infill areas + coord_t min_perimeter_infill_spacing = coord_t(solid_infill_spacing * (1. - INSET_OVERLAP_TOLERANCE)); + // append infill areas to fill_surfaces + append(out_fill_expolygons, + offset2_ex( + union_ex(pp), + float(- inset - min_perimeter_infill_spacing / 2.), + float(min_perimeter_infill_spacing / 2.))); } } diff --git a/src/libslic3r/PerimeterGenerator.hpp b/src/libslic3r/PerimeterGenerator.hpp index de52dd89a..33d735a3e 100644 --- a/src/libslic3r/PerimeterGenerator.hpp +++ b/src/libslic3r/PerimeterGenerator.hpp @@ -67,7 +67,7 @@ private: void process_classic( // Inputs: const Parameters ¶ms, - const SurfaceCollection *slices, + const Surface &surface, const ExPolygons *lower_slices, // Cache: Polygons &lower_slices_polygons_cache, @@ -82,7 +82,7 @@ void process_classic( void process_arachne( // Inputs: const Parameters ¶ms, - const SurfaceCollection *slices, + const Surface &surface, const ExPolygons *lower_slices, // Cache: Polygons &lower_slices_polygons_cache, diff --git a/src/libslic3r/Polygon.cpp b/src/libslic3r/Polygon.cpp index bf0abd4fa..361234bd2 100644 --- a/src/libslic3r/Polygon.cpp +++ b/src/libslic3r/Polygon.cpp @@ -516,6 +516,26 @@ void remove_collinear(Polygons &polys) remove_collinear(poly); } +// Do polygons match? If they match, they must have the same topology, +// however their contours may be rotated. +bool polygons_match(const Polygon &l, const Polygon &r) +{ + if (l.size() != r.size()) + return false; + auto it_l = std::find(l.points.begin(), l.points.end(), r.points.front()); + if (it_l == l.points.end()) + return false; + auto it_r = r.points.begin(); + for (; it_l != l.points.end(); ++ it_l, ++ it_r) + if (*it_l != *it_r) + return false; + it_l = l.points.begin(); + for (; it_r != r.points.end(); ++ it_l, ++ it_r) + if (*it_l != *it_r) + return false; + return true; +} + bool contains(const Polygons &polygons, const Point &p, bool border_result) { int poly_count_inside = 0; diff --git a/src/libslic3r/Polygon.hpp b/src/libslic3r/Polygon.hpp index 12d457c37..a17b91188 100644 --- a/src/libslic3r/Polygon.hpp +++ b/src/libslic3r/Polygon.hpp @@ -250,6 +250,10 @@ inline Polygons to_polygons(std::vector &&paths) return out; } +// Do polygons match? If they match, they must have the same topology, +// however their contours may be rotated. +bool polygons_match(const Polygon &l, const Polygon &r); + // Returns true if inside. Returns border_result if on boundary. bool contains(const Polygons& polygons, const Point& p, bool border_result = true); diff --git a/tests/fff_print/test_perimeters.cpp b/tests/fff_print/test_perimeters.cpp index decdb4802..3c817be00 100644 --- a/tests/fff_print/test_perimeters.cpp +++ b/tests/fff_print/test_perimeters.cpp @@ -55,6 +55,7 @@ SCENARIO("Perimeter nesting", "[Perimeters]") static_cast(config), false); // spiral_vase Polygons lower_layer_polygons_cache; + for (const Surface &surface : slices) // FIXME Lukas H.: Disable this test for Arachne because it is failing and needs more investigation. // if (config.perimeter_generator == PerimeterGeneratorType::Arachne) // PerimeterGenerator::process_arachne(); @@ -62,7 +63,7 @@ SCENARIO("Perimeter nesting", "[Perimeters]") PerimeterGenerator::process_classic( // input: perimeter_generator_params, - &slices, + surface, nullptr, // cache: lower_layer_polygons_cache,