diff --git a/src/libslic3r/Flow.hpp b/src/libslic3r/Flow.hpp index 516490b35..4b0a846ad 100644 --- a/src/libslic3r/Flow.hpp +++ b/src/libslic3r/Flow.hpp @@ -42,14 +42,20 @@ public: bool bridge; Flow(float _w, float _h, float _nd, bool _bridge = false) : - width(_w), height(_h), nozzle_diameter(_nd), bridge(_bridge) {}; + width(_w), height(_h), nozzle_diameter(_nd), bridge(_bridge) {} float spacing() const; float spacing(const Flow &other) const; double mm3_per_mm() const; - coord_t scaled_width() const { return coord_t(scale_(this->width)); }; - coord_t scaled_spacing() const { return coord_t(scale_(this->spacing())); }; - coord_t scaled_spacing(const Flow &other) const { return coord_t(scale_(this->spacing(other))); }; + coord_t scaled_width() const { return coord_t(scale_(this->width)); } + coord_t scaled_spacing() const { return coord_t(scale_(this->spacing())); } + coord_t scaled_spacing(const Flow &other) const { return coord_t(scale_(this->spacing(other))); } + + // Elephant foot compensation spacing to be used to detect narrow parts, where the elephant foot compensation cannot be applied. + // To be used on frExternalPerimeter only. + // Enable some perimeter squish (see INSET_OVERLAP_TOLERANCE). + // Here an overlap of 0.2x external perimeter spacing is allowed for by the elephant foot compensation. + coord_t scaled_elephant_foot_spacing() const { return coord_t(0.5f * float(this->scaled_width() + 0.6f * this->scaled_spacing())); } static Flow new_from_config_width(FlowRole role, const ConfigOptionFloatOrPercent &width, float nozzle_diameter, float height, float bridge_flow_ratio); // Create a flow from the spacing of extrusion lines. diff --git a/src/libslic3r/Layer.cpp b/src/libslic3r/Layer.cpp index fa5d29692..31baaed33 100644 --- a/src/libslic3r/Layer.cpp +++ b/src/libslic3r/Layer.cpp @@ -65,6 +65,7 @@ void Layer::make_slices() this->slices.expolygons.push_back(std::move(slices[i])); } +// Merge typed slices into untyped slices. This method is used to revert the effects of detect_surfaces_type() called for posPrepareInfill. void Layer::merge_slices() { if (m_regions.size() == 1) { @@ -78,6 +79,24 @@ void Layer::merge_slices() } } +ExPolygons Layer::merged(float offset_scaled) const +{ + assert(offset_scaled >= 0.f); + // If no offset is set, apply EPSILON offset before union, and revert it afterwards. + float offset_scaled2 = 0; + if (offset_scaled == 0.f) { + offset_scaled = float( EPSILON); + offset_scaled2 = float(- EPSILON); + } + Polygons polygons; + for (LayerRegion *layerm : m_regions) + append(polygons, offset(to_expolygons(layerm->slices.surfaces), offset_scaled)); + ExPolygons out = union_ex(polygons); + if (offset_scaled2 != 0.f) + out = offset_ex(out, offset_scaled2); + return out; +} + // Here the perimeters are created cummulatively for all layer regions sharing the same parameters influencing the perimeters. // The perimeter paths and the thin fills (ExtrusionEntityCollection) are assigned to the first compatible layer region. // The resulting fill surface is split back among the originating regions. diff --git a/src/libslic3r/Layer.hpp b/src/libslic3r/Layer.hpp index 78897a2db..6297b49d0 100644 --- a/src/libslic3r/Layer.hpp +++ b/src/libslic3r/Layer.hpp @@ -63,7 +63,12 @@ public: void prepare_fill_surfaces(); void make_perimeters(const SurfaceCollection &slices, SurfaceCollection* fill_surfaces); void process_external_surfaces(const Layer* lower_layer); - double infill_area_threshold() const; + double infill_area_threshold() const; + // Trim surfaces by trimming polygons. Used by the elephant foot compensation at the 1st layer. + void trim_surfaces(const Polygons &trimming_polygons); + // Single elephant foot compensation step, used by the elephant foor compensation at the 1st layer. + // Trim surfaces by trimming polygons (shrunk by an elephant foot compensation step), but don't shrink narrow parts so much that no perimeter would fit. + void elephant_foot_compensation_step(const float elephant_foot_compensation_perimeter_step, const Polygons &trimming_polygons); void export_region_slices_to_svg(const char *path) const; void export_region_fill_surfaces_to_svg(const char *path) const; @@ -117,7 +122,10 @@ public: // Test whether whether there are any slices assigned to this layer. bool empty() const; void make_slices(); + // Merge typed slices into untyped slices. This method is used to revert the effects of detect_surfaces_type() called for posPrepareInfill. void merge_slices(); + // Slices merged into islands, to be used by the elephant foot compensation to trim the individual surfaces with the shrunk merged slices. + ExPolygons merged(float offset) const; template bool any_internal_region_slice_contains(const T &item) const { for (const LayerRegion *layerm : m_regions) if (layerm->slices.any_internal_contains(item)) return true; return false; diff --git a/src/libslic3r/LayerRegion.cpp b/src/libslic3r/LayerRegion.cpp index 6f70fba65..913ba76a6 100644 --- a/src/libslic3r/LayerRegion.cpp +++ b/src/libslic3r/LayerRegion.cpp @@ -74,7 +74,7 @@ void LayerRegion::make_perimeters(const SurfaceCollection &slices, SurfaceCollec // Cummulative sum of polygons over all the regions. g.lower_slices = &this->layer()->lower_layer->slices; - g.layer_id = this->layer()->id(); + g.layer_id = (int)this->layer()->id(); g.ext_perimeter_flow = this->flow(frExternalPerimeter); g.overhang_flow = this->region()->flow(frPerimeter, -1, true, false, -1, *this->layer()->object()); g.solid_infill_flow = this->flow(frSolidInfill); @@ -385,6 +385,28 @@ double LayerRegion::infill_area_threshold() const return ss*ss; } +void LayerRegion::trim_surfaces(const Polygons &trimming_polygons) +{ +#ifndef NDEBUG + for (const Surface &surface : this->slices.surfaces) + assert(surface.surface_type == stInternal); +#endif /* NDEBUG */ + this->slices.set(intersection_ex(to_polygons(std::move(this->slices.surfaces)), trimming_polygons, false), stInternal); +} + +void LayerRegion::elephant_foot_compensation_step(const float elephant_foot_compensation_perimeter_step, const Polygons &trimming_polygons) +{ +#ifndef NDEBUG + for (const Surface &surface : this->slices.surfaces) + assert(surface.surface_type == stInternal); +#endif /* NDEBUG */ + ExPolygons slices_expolygons = to_expolygons(std::move(this->slices.surfaces)); + Polygons slices_polygons = to_polygons(slices_expolygons); + Polygons tmp = intersection(slices_polygons, trimming_polygons, false); + append(tmp, diff(slices_polygons, offset(offset_ex(slices_expolygons, -elephant_foot_compensation_perimeter_step), elephant_foot_compensation_perimeter_step))); + this->slices.set(std::move(union_ex(tmp)), stInternal); +} + void LayerRegion::export_region_slices_to_svg(const char *path) const { BoundingBox bbox; diff --git a/src/libslic3r/PrintObject.cpp b/src/libslic3r/PrintObject.cpp index 5e46bb753..07fecdfc6 100644 --- a/src/libslic3r/PrintObject.cpp +++ b/src/libslic3r/PrintObject.cpp @@ -1551,32 +1551,89 @@ end: Layer *layer = m_layers[layer_id]; // Apply size compensation and perform clipping of multi-part objects. float delta = float(scale_(m_config.xy_size_compensation.value)); + float elephant_foot_compensation = 0.f; if (layer_id == 0) - delta -= float(scale_(m_config.elefant_foot_compensation.value)); - bool scale = delta != 0.f; - bool clip = m_config.clip_multipart_objects.value || delta > 0.f; + elephant_foot_compensation = float(scale_(m_config.elefant_foot_compensation.value)); if (layer->m_regions.size() == 1) { - if (scale) { + // Optimized version for a single region layer. + if (layer_id == 0) { + if (delta > elephant_foot_compensation) { + delta -= elephant_foot_compensation; + elephant_foot_compensation = 0.f; + } else if (delta > 0) + elephant_foot_compensation -= delta; + } + if (delta != 0.f || elephant_foot_compensation > 0.f) { // Single region, growing or shrinking. LayerRegion *layerm = layer->m_regions.front(); - layerm->slices.set(offset_ex(to_expolygons(std::move(layerm->slices.surfaces)), delta), stInternal); + // Apply the XY compensation. + ExPolygons expolygons = (delta == 0.f) ? + to_expolygons(std::move(layerm->slices.surfaces)) : + offset_ex(to_expolygons(std::move(layerm->slices.surfaces)), delta); + // Apply the elephant foot compensation. + if (elephant_foot_compensation > 0) { + float elephant_foot_spacing = layerm->flow(frExternalPerimeter).scaled_elephant_foot_spacing(); + float external_perimeter_nozzle = scale_(this->print()->config().nozzle_diameter.get_at(layerm->region()->config().perimeter_extruder.value - 1)); + // Apply the elephant foot compensation by steps of 1/10 nozzle diameter. + float steps = std::ceil(elephant_foot_compensation / (0.1f * external_perimeter_nozzle)); + size_t nsteps = size_t(steps); + float step = elephant_foot_compensation / steps; + for (size_t i = 0; i < nsteps; ++ i) { + Polygons tmp = offset(expolygons, - step); + append(tmp, diff(to_polygons(expolygons), offset(offset_ex(expolygons, -elephant_foot_spacing - step), elephant_foot_spacing + step))); + expolygons = union_ex(tmp); + } + } + layerm->slices.set(std::move(expolygons), stInternal); } - } else if (scale || clip) { - // Multiple regions, growing, shrinking or just clipping one region by the other. - // When clipping the regions, priority is given to the first regions. - Polygons processed; - for (size_t region_id = 0; region_id < layer->m_regions.size(); ++ region_id) { - LayerRegion *layerm = layer->m_regions[region_id]; - ExPolygons slices = to_expolygons(std::move(layerm->slices.surfaces)); - if (scale) - slices = offset_ex(slices, delta); - if (region_id > 0 && clip) - // Trim by the slices of already processed regions. - slices = diff_ex(to_polygons(std::move(slices)), processed); - if (clip && region_id + 1 < layer->m_regions.size()) - // Collect the already processed regions to trim the to be processed regions. - polygons_append(processed, slices); - layerm->slices.set(std::move(slices), stInternal); + } else { + bool upscale = delta > 0.f; + bool downscale = delta < 0.f || elephant_foot_compensation > 0.f; + bool clip = m_config.clip_multipart_objects.value; + if (upscale || clip) { + // Multiple regions, growing or just clipping one region by the other. + // When clipping the regions, priority is given to the first regions. + Polygons processed; + for (size_t region_id = 0; region_id < layer->m_regions.size(); ++ region_id) { + LayerRegion *layerm = layer->m_regions[region_id]; + ExPolygons slices = to_expolygons(std::move(layerm->slices.surfaces)); + if (upscale) + slices = offset_ex(std::move(slices), delta); + if (region_id > 0 && clip) + // Trim by the slices of already processed regions. + slices = diff_ex(to_polygons(std::move(slices)), processed); + if (clip && (region_id + 1 < layer->m_regions.size())) + // Collect the already processed regions to trim the to be processed regions. + polygons_append(processed, slices); + layerm->slices.set(std::move(slices), stInternal); + } + } + if (delta < 0.f) { + // Apply the negative XY compensation. + Polygons trimming = offset(layer->merged(EPSILON), delta - EPSILON); + for (size_t region_id = 0; region_id < layer->m_regions.size(); ++ region_id) + layer->m_regions[region_id]->trim_surfaces(trimming); + } + if (elephant_foot_compensation > 0.f) { + // Apply the elephant foot compensation. + std::vector elephant_foot_spacing; + elephant_foot_spacing.reserve(layer->m_regions.size()); + float external_perimeter_nozzle = 0.f; + for (size_t region_id = 0; region_id < layer->m_regions.size(); ++ region_id) { + LayerRegion *layerm = layer->m_regions[region_id]; + elephant_foot_spacing.emplace_back(layerm->flow(frExternalPerimeter).scaled_elephant_foot_spacing()); + external_perimeter_nozzle += scale_(this->print()->config().nozzle_diameter.get_at(layerm->region()->config().perimeter_extruder.value - 1)); + } + external_perimeter_nozzle /= (float)layer->m_regions.size(); + // Apply the elephant foot compensation by steps of 1/10 nozzle diameter. + float steps = std::ceil(elephant_foot_compensation / (0.1f * external_perimeter_nozzle)); + size_t nsteps = size_t(steps); + float step = elephant_foot_compensation / steps; + for (size_t i = 0; i < nsteps; ++ i) { + Polygons trimming_polygons = offset(layer->merged(EPSILON), - step - EPSILON); + for (size_t region_id = 0; region_id < layer->m_regions.size(); ++ region_id) + layer->m_regions[region_id]->elephant_foot_compensation_step(elephant_foot_spacing[region_id] + step, trimming_polygons); + } } } // Merge all regions' slices to get islands, chain them by a shortest path.