diff --git a/src/libslic3r/BoundingBox.hpp b/src/libslic3r/BoundingBox.hpp index 5e3d3e323f..ecc36de7b2 100644 --- a/src/libslic3r/BoundingBox.hpp +++ b/src/libslic3r/BoundingBox.hpp @@ -244,6 +244,39 @@ auto cast(const BoundingBox3Base &b) b.max.template cast()}; } +// Distance of a point to a bounding box. Zero inside and on the boundary, positive outside. +inline double bbox_point_distance(const BoundingBox &bbox, const Point &pt) +{ + if (pt.x() < bbox.min.x()) + return pt.y() < bbox.min.y() ? (bbox.min - pt).cast().norm() : + pt.y() > bbox.max.y() ? (Point(bbox.min.x(), bbox.max.y()) - pt).cast().norm() : + double(bbox.min.x() - pt.x()); + else if (pt.x() > bbox.max.x()) + return pt.y() < bbox.min.y() ? (Point(bbox.max.x(), bbox.min.y()) - pt).cast().norm() : + pt.y() > bbox.max.y() ? (bbox.max - pt).cast().norm() : + double(pt.x() - bbox.max.x()); + else + return pt.y() < bbox.min.y() ? bbox.min.y() - pt.y() : + pt.y() > bbox.max.y() ? pt.y() - bbox.max.y() : + coord_t(0); +} + +inline double bbox_point_distance_squared(const BoundingBox &bbox, const Point &pt) +{ + if (pt.x() < bbox.min.x()) + return pt.y() < bbox.min.y() ? (bbox.min - pt).cast().squaredNorm() : + pt.y() > bbox.max.y() ? (Point(bbox.min.x(), bbox.max.y()) - pt).cast().squaredNorm() : + Slic3r::sqr(double(bbox.min.x() - pt.x())); + else if (pt.x() > bbox.max.x()) + return pt.y() < bbox.min.y() ? (Point(bbox.max.x(), bbox.min.y()) - pt).cast().squaredNorm() : + pt.y() > bbox.max.y() ? (bbox.max - pt).cast().squaredNorm() : + Slic3r::sqr(pt.x() - bbox.max.x()); + else + return Slic3r::sqr(pt.y() < bbox.min.y() ? bbox.min.y() - pt.y() : + pt.y() > bbox.max.y() ? pt.y() - bbox.max.y() : + coord_t(0)); +} + } // namespace Slic3r // Serialization through the Cereal library diff --git a/src/libslic3r/ExPolygon.cpp b/src/libslic3r/ExPolygon.cpp index 2224f991ea..651be062a9 100644 --- a/src/libslic3r/ExPolygon.cpp +++ b/src/libslic3r/ExPolygon.cpp @@ -117,6 +117,26 @@ ExPolygon::has_boundary_point(const Point &point) const return false; } +// Projection of a point onto the polygon. +Point ExPolygon::point_projection(const Point &point) const +{ + if (this->holes.empty()) { + return this->contour.point_projection(point); + } else { + double dist_min2 = std::numeric_limits::max(); + Point closest_pt_min; + for (size_t i = 0; i < this->num_contours(); ++ i) { + Point closest_pt = this->contour_or_hole(i).point_projection(point); + double d2 = (closest_pt - point).cast().squaredNorm(); + if (d2 < dist_min2) { + dist_min2 = d2; + closest_pt_min = closest_pt; + } + } + return closest_pt_min; + } +} + bool ExPolygon::overlaps(const ExPolygon &other) const { #if 0 diff --git a/src/libslic3r/ExPolygon.hpp b/src/libslic3r/ExPolygon.hpp index a0e4f272f5..a319d003c3 100644 --- a/src/libslic3r/ExPolygon.hpp +++ b/src/libslic3r/ExPolygon.hpp @@ -53,6 +53,8 @@ public: bool contains(const Point &point) const; bool contains_b(const Point &point) const; bool has_boundary_point(const Point &point) const; + // Projection of a point onto the polygon. + Point point_projection(const Point &point) const; // Does this expolygon overlap another expolygon? // Either the ExPolygons intersect, or one is fully inside the other, diff --git a/src/libslic3r/Fill/Fill.cpp b/src/libslic3r/Fill/Fill.cpp index 2526006923..0a54ae1fa2 100644 --- a/src/libslic3r/Fill/Fill.cpp +++ b/src/libslic3r/Fill/Fill.cpp @@ -322,6 +322,92 @@ void export_group_fills_to_svg(const char *path, const std::vector } #endif +static void insert_fills_into_islands(Layer &layer, uint32_t fill_region_id, uint32_t fill_begin, uint32_t fill_end) +{ + if (fill_begin < fill_end) { + // Sort the extrusion range into its LayerIsland. + // 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 = [&layer](const size_t lslice_idx, const Point &point) { + const BoundingBox &bbox = layer.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() && + layer.lslices[lslice_idx].contour.contains(point); + }; + Point point = layer.get_region(fill_region_id)->fills().entities[fill_begin]->first_point(); + int lslice_idx = int(layer.lslices_ex.size()) - 1; + for (; lslice_idx >= 0; -- lslice_idx) + if (point_inside_surface(lslice_idx, point)) + break; + assert(lslice_idx >= 0); + if (lslice_idx >= 0) { + LayerSlice &lslice = layer.lslices_ex[lslice_idx]; + // Find an island. + LayerIsland *island = nullptr; + if (lslice.islands.size() == 1) { + // Cool, just save the extrusions in there. + island = &lslice.islands.front(); + } else { + // The infill was created for one of the infills. + // In case of ironing, the infill may not fall into any of the infill expolygons either. + // In case of some numerical error, the infill may not fall into any of the infill expolygons either. + // 1) Try an exact test, it should be cheaper than a closest region test. + for (LayerIsland &li : lslice.islands) { + const BoundingBoxes &bboxes = li.fill_expolygons_composite() ? + layer.get_region(li.perimeters.region())->fill_expolygons_composite_bboxes() : + layer.get_region(li.fill_region_id)->fill_expolygons_bboxes(); + const ExPolygons &expolygons = li.fill_expolygons_composite() ? + layer.get_region(li.perimeters.region())->fill_expolygons_composite() : + layer.get_region(li.fill_region_id)->fill_expolygons(); + for (uint32_t fill_expolygon_id : li.fill_expolygons) + if (bboxes[fill_expolygon_id].contains(point) && expolygons[fill_expolygon_id].contains(point)) { + island = &li; + goto found; + } + } + // 2) Find closest fill_expolygon, branch and bound by distance to bounding box. + { + struct Island { + uint32_t island_idx; + uint32_t expolygon_idx; + double distance2; + }; + std::vector islands_sorted; + for (uint32_t island_idx = 0; island_idx < uint32_t(lslice.islands.size()); ++ island_idx) { + const LayerIsland &li = lslice.islands[island_idx]; + const BoundingBoxes &bboxes = li.fill_expolygons_composite() ? + layer.get_region(li.perimeters.region())->fill_expolygons_composite_bboxes() : + layer.get_region(li.fill_region_id)->fill_expolygons_bboxes(); + for (uint32_t fill_expolygon_id : li.fill_expolygons) + islands_sorted.push_back({ island_idx, fill_expolygon_id, bbox_point_distance_squared(bboxes[fill_expolygon_id], point) }); + } + std::sort(islands_sorted.begin(), islands_sorted.end(), [](auto &l, auto &r){ return l.distance2 < r.distance2; }); + auto dist_min2 = std::numeric_limits::max(); + for (uint32_t sorted_bbox_idx = 0; sorted_bbox_idx < uint32_t(islands_sorted.size()); ++ sorted_bbox_idx) { + const Island &isl = islands_sorted[sorted_bbox_idx]; + if (isl.distance2 > dist_min2) + // Branch & bound condition. + break; + LayerIsland &li = lslice.islands[isl.island_idx]; + const ExPolygons &expolygons = li.fill_expolygons_composite() ? + layer.get_region(li.perimeters.region())->fill_expolygons_composite() : + layer.get_region(li.fill_region_id)->fill_expolygons(); + double d2 = (expolygons[isl.expolygon_idx].point_projection(point) - point).cast().squaredNorm(); + if (d2 < dist_min2) { + dist_min2 = d2; + island = &li; + } + } + } + found:; + } + assert(island); + if (island) + island->fills.push_back(LayerExtrusionRange{ fill_region_id, { fill_begin, fill_end }}); + } + } +} + // friend to Layer void Layer::make_fills(FillAdaptive::Octree* adaptive_fill_octree, FillAdaptive::Octree* support_fill_octree, FillLightning::Generator* lightning_generator) { @@ -382,6 +468,8 @@ void Layer::make_fills(FillAdaptive::Octree* adaptive_fill_octree, FillAdaptive: // Used by the concentric infill pattern to clip the loops to create extrusion paths. f->loop_clipping = coord_t(scale_(surface_fill.params.flow.nozzle_diameter()) * LOOP_CLIPPING_LENGTH_OVER_NOZZLE_DIAMETER); + LayerRegion &layerm = *m_regions[surface_fill.region_id]; + // apply half spacing using this flow's own spacing and generate infill FillParams params; params.density = float(0.01 * surface_fill.params.density); @@ -390,7 +478,7 @@ void Layer::make_fills(FillAdaptive::Octree* adaptive_fill_octree, FillAdaptive: params.anchor_length_max = surface_fill.params.anchor_length_max; params.resolution = resolution; params.use_arachne = perimeter_generator == PerimeterGeneratorType::Arachne && surface_fill.params.pattern == ipConcentric; - params.layer_height = m_regions[surface_fill.region_id]->layer()->height; + params.layer_height = layerm.layer()->height; for (ExPolygon &expoly : surface_fill.expolygons) { // Spacing is modified by the filler to indicate adjustments. Reset it for each expolygon. @@ -421,7 +509,8 @@ void Layer::make_fills(FillAdaptive::Octree* adaptive_fill_octree, FillAdaptive: } // Save into layer. ExtrusionEntityCollection* eec = nullptr; - m_regions[surface_fill.region_id]->m_fills.entities.push_back(eec = new ExtrusionEntityCollection()); + auto fill_begin = uint32_t(layerm.fills().size()); + layerm.m_fills.entities.push_back(eec = new ExtrusionEntityCollection()); // Only concentric fills are not sorted. eec->no_sort = f->no_sort(); if (params.use_arachne) { @@ -445,10 +534,14 @@ void Layer::make_fills(FillAdaptive::Octree* adaptive_fill_octree, FillAdaptive: surface_fill.params.extrusion_role, flow_mm3_per_mm, float(flow_width), surface_fill.params.flow.height()); } + insert_fills_into_islands(*this, uint32_t(surface_fill.region_id), fill_begin, uint32_t(layerm.fills().size())); } } } + //FIXME Don't copy thin fill extrusions into fills, just use these thin fill extrusions + // from the G-code export directly. +#if 0 // add thin fill regions // Unpacks the collection, creates multiple collections per path. // The path type could be ExtrusionPath, ExtrusionLoop or ExtrusionEntityCollection. @@ -459,6 +552,7 @@ void Layer::make_fills(FillAdaptive::Octree* adaptive_fill_octree, FillAdaptive: layerm->m_fills.entities.push_back(&collection); collection.entities.push_back(thin_fill->clone()); } +#endif #ifndef NDEBUG for (LayerRegion *layerm : m_regions) @@ -519,7 +613,8 @@ void Layer::make_ironing() this->angle == rhs.angle; } - LayerRegion *layerm = nullptr; + LayerRegion *layerm; + uint32_t region_id; // IdeaMaker: ironing // ironing flowrate (5% percent) @@ -539,8 +634,8 @@ void Layer::make_ironing() std::vector by_extruder; double default_layer_height = this->object()->config().layer_height; - for (LayerRegion *layerm : m_regions) - if (! layerm->slices().empty()) { + for (uint32_t region_id = 0; region_id < uint32_t(this->regions().size()); ++region_id) + if (LayerRegion *layerm = this->get_region(region_id); ! layerm->slices().empty()) { IroningParams ironing_params; const PrintRegionConfig &config = layerm->region().config(); if (config.ironing && @@ -564,6 +659,7 @@ void Layer::make_ironing() ironing_params.speed = config.ironing_speed; ironing_params.angle = config.fill_angle * M_PI / 180.; ironing_params.layerm = layerm; + ironing_params.region_id = region_id; by_extruder.emplace_back(ironing_params); } } @@ -659,6 +755,7 @@ void Layer::make_ironing() } if (! polylines.empty()) { // Save into layer. + auto fill_begin = uint32_t(ironing_params.layerm->fills().size()); ExtrusionEntityCollection *eec = nullptr; ironing_params.layerm->m_fills.entities.push_back(eec = new ExtrusionEntityCollection()); // Don't sort the ironing infill lines as they are monotonicly ordered. @@ -667,6 +764,7 @@ void Layer::make_ironing() eec->entities, std::move(polylines), erIroning, flow_mm3_per_mm, extrusion_width, float(extrusion_height)); + insert_fills_into_islands(*this, ironing_params.region_id, fill_begin, uint32_t(ironing_params.layerm->fills().size())); } } diff --git a/src/libslic3r/Layer.hpp b/src/libslic3r/Layer.hpp index 137f35e5ca..d7d0fbffbb 100644 --- a/src/libslic3r/Layer.hpp +++ b/src/libslic3r/Layer.hpp @@ -32,35 +32,33 @@ namespace FillLightning { 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; + // Just a bare minimum functionality iterator required by range-for loop. + class Iterator { + public: + T operator*() const { return m_idx; } + bool operator!=(const Iterator &rhs) const { return m_idx != rhs.m_idx; } + void operator++() { ++ m_idx; } + private: + friend class IndexRange; + Iterator(T idx) : m_idx(idx) {} + T m_idx; + }; 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; } + +private: + // Index of the first extrusion in LayerRegion. + T m_begin { 0 }; + // Index of the last extrusion in LayerRegion. + T m_end { 0 }; }; using ExtrusionRange = IndexRange; @@ -70,7 +68,6 @@ using ExPolygonRange = IndexRange; 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;