From 4460b5ce50c7160cd922972a190c27bf4381ffbd Mon Sep 17 00:00:00 2001 From: bubnikv Date: Thu, 10 Nov 2016 19:23:01 +0100 Subject: [PATCH] re-wrote PrintObject::detect_surfaces_type() to C++, Fixed some cracks in the fill surfaces created by rounding all surfaces inside detect_surface_type(). Fixed https://github.com/prusa3d/Slic3r/issues/12 Bridging-Angle not optimal Extended the "Ensure veritcal wall thickness" mode (merged with the original discover_horizontal_shells function), but this a work in progress. Already Slic3r with "ensure vertical wall thickness" produces less spurious infills inside solids. --- lib/Slic3r/Print/Object.pm | 164 +--------------- xs/src/libslic3r/Layer.cpp | 6 - xs/src/libslic3r/Layer.hpp | 4 +- xs/src/libslic3r/LayerRegion.cpp | 251 +++++++++++++----------- xs/src/libslic3r/PerimeterGenerator.cpp | 6 +- xs/src/libslic3r/Print.hpp | 1 + xs/src/libslic3r/PrintObject.cpp | 205 ++++++++++++++++++- xs/xsp/Print.xsp | 1 + 8 files changed, 345 insertions(+), 293 deletions(-) diff --git a/lib/Slic3r/Print/Object.pm b/lib/Slic3r/Print/Object.pm index 438c4b1ef..ab390fd3a 100644 --- a/lib/Slic3r/Print/Object.pm +++ b/lib/Slic3r/Print/Object.pm @@ -669,167 +669,6 @@ sub _support_material { } } -# This function analyzes slices of a region (SurfaceCollection slices). -# Each slice (instance of Surface) is analyzed, whether it is supported or whether it is the top surface. -# Initially all slices are of type S_TYPE_INTERNAL. -# Slices are compared against the top / bottom slices and regions and classified to the following groups: -# S_TYPE_TOP - Part of a region, which is not covered by any upper layer. This surface will be filled with a top solid infill. -# S_TYPE_BOTTOMBRIDGE - Part of a region, which is not fully supported, but it hangs in the air, or it hangs losely on a support or a raft. -# S_TYPE_BOTTOM - Part of a region, which is not supported by the same region, but it is supported either by another region, or by a soluble interface layer. -# S_TYPE_INTERNAL - Part of a region, which is supported by the same region type. -# If a part of a region is of S_TYPE_BOTTOM and S_TYPE_TOP, the S_TYPE_BOTTOM wins. -sub detect_surfaces_type { - my $self = shift; - Slic3r::debugf "Detecting solid surfaces...\n"; - - for my $region_id (0 .. ($self->print->region_count-1)) { - for my $i (0 .. ($self->layer_count - 1)) { - my $layerm = $self->get_layer($i)->regions->[$region_id]; - - # prepare a reusable subroutine to make surface differences - my $difference = sub { - my ($subject, $clip, $result_type) = @_; - my $diff = diff( - [ map @$_, @$subject ], - [ map @$_, @$clip ], - 1, - ); - - # collapse very narrow parts (using the safety offset in the diff is not enough) - my $offset = $layerm->flow(FLOW_ROLE_EXTERNAL_PERIMETER)->scaled_width / 10; - return map Slic3r::Surface->new(expolygon => $_, surface_type => $result_type), - @{ offset2_ex($diff, -$offset, +$offset) }; - }; - - # comparison happens against the *full* slices (considering all regions) - # unless internal shells are requested - my $upper_layer = $i < $self->layer_count - 1 ? $self->get_layer($i+1) : undef; - my $lower_layer = $i > 0 ? $self->get_layer($i-1) : undef; - - # find top surfaces (difference between current surfaces - # of current layer and upper one) - my @top = (); - if ($upper_layer) { - # Config value $self->config->interface_shells is true, if a support is separated from the object - # by a soluble material (for example a PVA plastic). - my $upper_slices = $self->config->interface_shells - ? [ map $_->expolygon, @{$upper_layer->regions->[$region_id]->slices} ] - : $upper_layer->slices; - - @top = $difference->( - [ map $_->expolygon, @{$layerm->slices} ], - $upper_slices, - S_TYPE_TOP, - ); - } else { - # if no upper layer, all surfaces of this one are solid - # we clone surfaces because we're going to clear the slices collection - @top = map $_->clone, @{$layerm->slices}; - $_->surface_type(S_TYPE_TOP) for @top; - } - - # find bottom surfaces (difference between current surfaces - # of current layer and lower one) - my @bottom = (); - if ($lower_layer) { - # Any surface lying on the void is a true bottom bridge (an overhang) - push @bottom, $difference->( - [ map $_->expolygon, @{$layerm->slices} ], - $lower_layer->slices, - S_TYPE_BOTTOMBRIDGE, - ); - - # If we have soluble support material, don't bridge. The overhang will be squished against a soluble layer separating - # the support from the print. - if ($self->config->support_material && $self->config->support_material_contact_distance == 0) { - $_->surface_type(S_TYPE_BOTTOM) for @bottom; - } - - # if user requested internal shells, we need to identify surfaces - # lying on other slices not belonging to this region - if ($self->config->interface_shells) { - # non-bridging bottom surfaces: any part of this layer lying - # on something else, excluding those lying on our own region - my $supported = intersection_ex( - [ map @{$_->expolygon}, @{$layerm->slices} ], - [ map @$_, @{$lower_layer->slices} ], - ); - push @bottom, $difference->( - $supported, - [ map $_->expolygon, @{$lower_layer->regions->[$region_id]->slices} ], - S_TYPE_BOTTOM, - ); - } - } else { - # if no lower layer, all surfaces of this one are solid - # we clone surfaces because we're going to clear the slices collection - @bottom = map $_->clone, @{$layerm->slices}; - - # if we have raft layers, consider bottom layer as a bridge - # just like any other bottom surface lying on the void - if ($self->config->raft_layers > 0 && $self->config->support_material_contact_distance > 0) { - $_->surface_type(S_TYPE_BOTTOMBRIDGE) for @bottom; - } else { - $_->surface_type(S_TYPE_BOTTOM) for @bottom; - } - } - - # now, if the object contained a thin membrane, we could have overlapping bottom - # and top surfaces; let's do an intersection to discover them and consider them - # as bottom surfaces (to allow for bridge detection) - if (@top && @bottom) { - my $overlapping = intersection_ex([ map $_->p, @top ], [ map $_->p, @bottom ]); - Slic3r::debugf " layer %d contains %d membrane(s)\n", $layerm->layer->id, scalar(@$overlapping) - if $Slic3r::debug; - @top = $difference->([map $_->expolygon, @top], $overlapping, S_TYPE_TOP); - } - - # find internal surfaces (difference between top/bottom surfaces and others) - my @internal = $difference->( - [ map $_->expolygon, @{$layerm->slices} ], - [ map $_->expolygon, @top, @bottom ], - S_TYPE_INTERNAL, - ); - - # save surfaces to layer - $layerm->slices->clear; - $layerm->slices->append($_) for (@bottom, @top, @internal); - - Slic3r::debugf " layer %d has %d bottom, %d top and %d internal surfaces\n", - $layerm->layer->id, scalar(@bottom), scalar(@top), scalar(@internal) if $Slic3r::debug; - - if ($SLIC3R_DEBUG_SLICE_PROCESSING) { - $layerm->export_region_slices_to_svg_debug("detect_surfaces_type-final"); - } - } # for each layer of a region - - # clip surfaces to the fill boundaries - foreach my $layer (@{$self->layers}) { - my $layerm = $layer->regions->[$region_id]; - - # Note: this method should be idempotent, but fill_surfaces gets modified - # in place. However we're now only using its boundaries (which are invariant) - # so we're safe. This guarantees idempotence of prepare_infill() also in case - # that combine_infill() turns some fill_surface into VOID surfaces. - my $fill_boundaries = [ map $_->clone->p, @{$layerm->fill_surfaces} ]; - $layerm->fill_surfaces->clear; - foreach my $surface (@{$layerm->slices}) { - my $intersection = intersection_ex( - [ $surface->p ], - $fill_boundaries, - ); - $layerm->fill_surfaces->append($_) - for map Slic3r::Surface->new(expolygon => $_, surface_type => $surface->surface_type), - @$intersection; - } - - if ($SLIC3R_DEBUG_SLICE_PROCESSING) { - $layerm->export_region_fill_surfaces_to_svg_debug("1_detect_surfaces_type-final"); - } - } # for each layer of a region - } # for each $self->print->region_count -} - # Idempotence of this method is guaranteed by the fact that we don't remove things from # fill_surfaces but we only turn them into VOID surfaces, thus preserving the boundaries. sub clip_fill_surfaces { @@ -953,6 +792,9 @@ sub discover_horizontal_shells { my $type = $layerm->region->config->fill_density == 100 ? S_TYPE_INTERNALSOLID : S_TYPE_INTERNALBRIDGE; $_->surface_type($type) for @{$layerm->fill_surfaces->filter_by_type(S_TYPE_INTERNAL)}; } + + # If ensure_vertical_shell_thickness, then the rest has already been performed by discover_vertical_shells(). + next if ($layerm->region->config->ensure_vertical_shell_thickness); EXTERNAL: foreach my $type (S_TYPE_TOP, S_TYPE_BOTTOM, S_TYPE_BOTTOMBRIDGE) { # find slices of current type for current layer diff --git a/xs/src/libslic3r/Layer.cpp b/xs/src/libslic3r/Layer.cpp index 43a715971..0e1937cbb 100644 --- a/xs/src/libslic3r/Layer.cpp +++ b/xs/src/libslic3r/Layer.cpp @@ -75,12 +75,6 @@ Layer::clear_regions() this->regions.clear(); } -LayerRegion* -Layer::get_region(int idx) -{ - return this->regions.at(idx); -} - LayerRegion* Layer::add_region(PrintRegion* print_region) { diff --git a/xs/src/libslic3r/Layer.hpp b/xs/src/libslic3r/Layer.hpp index f603d1fe5..375d8ac9f 100644 --- a/xs/src/libslic3r/Layer.hpp +++ b/xs/src/libslic3r/Layer.hpp @@ -61,6 +61,7 @@ class LayerRegion Flow flow(FlowRole role, bool bridge = false, double width = -1) const; void merge_slices(); + void slices_to_fill_surfaces_clipped(); void prepare_fill_surfaces(); void make_perimeters(const SurfaceCollection &slices, SurfaceCollection* perimeter_surfaces, SurfaceCollection* fill_surfaces); void process_external_surfaces(const Layer* lower_layer); @@ -108,7 +109,8 @@ public: size_t region_count() const; - LayerRegion* get_region(int idx); + const LayerRegion* get_region(int idx) const { return this->regions.at(idx); } + LayerRegion* get_region(int idx) { return this->regions.at(idx); } LayerRegion* add_region(PrintRegion* print_region); void make_slices(); diff --git a/xs/src/libslic3r/LayerRegion.cpp b/xs/src/libslic3r/LayerRegion.cpp index 23a1bd9b0..115cba61d 100644 --- a/xs/src/libslic3r/LayerRegion.cpp +++ b/xs/src/libslic3r/LayerRegion.cpp @@ -46,12 +46,25 @@ LayerRegion::merge_slices() { ExPolygons expp; // without safety offset, artifacts are generated (GH #2494) - union_(this->slices, &expp, true); + union_(to_polygons(STDMOVE(this->slices.surfaces)), &expp, true); this->slices.surfaces.clear(); - this->slices.surfaces.reserve(expp.size()); - - for (ExPolygons::const_iterator expoly = expp.begin(); expoly != expp.end(); ++expoly) - this->slices.surfaces.push_back(Surface(stInternal, *expoly)); + surfaces_append(this->slices.surfaces, expp, stInternal); +} + +// Fill in layerm->fill_surfaces by trimming the layerm->slices by the cummulative layerm->fill_surfaces. +void LayerRegion::slices_to_fill_surfaces_clipped() +{ + // Note: this method should be idempotent, but fill_surfaces gets modified + // in place. However we're now only using its boundaries (which are invariant) + // so we're safe. This guarantees idempotence of prepare_infill() also in case + // that combine_infill() turns some fill_surface into VOID surfaces. + Polygons fill_boundaries = to_polygons(STDMOVE(this->fill_surfaces)); + this->fill_surfaces.surfaces.clear(); + for (Surfaces::const_iterator surface = this->slices.surfaces.begin(); surface != this->slices.surfaces.end(); ++ surface) + surfaces_append( + this->fill_surfaces.surfaces, + intersection_ex(to_polygons(surface->expolygon), fill_boundaries), + surface->surface_type); } void @@ -102,70 +115,46 @@ LayerRegion::process_external_surfaces(const Layer* lower_layer) export_region_fill_surfaces_to_svg_debug("3_process_external_surfaces-initial"); #endif /* SLIC3R_DEBUG_SLICE_PROCESSING */ -#if 0 - Surfaces bottom; - // For all stBottom && stBottomBridge surfaces: - for (Surfaces::const_iterator surface = surfaces.begin(); surface != surfaces.end(); ++surface) { - if (!surface->is_bottom()) continue; - - ExPolygons grown = offset_ex(surface->expolygon, +margin, EXTERNAL_SURFACES_OFFSET_PARAMETERS); - - /* detect bridge direction before merging grown surfaces otherwise adjacent bridges - would get merged into a single one while they need different directions - also, supply the original expolygon instead of the grown one, because in case - of very thin (but still working) anchors, the grown expolygon would go beyond them */ - double angle = -1; - if (lower_layer != NULL) { - ExPolygons expolygons; - expolygons.push_back(surface->expolygon); - BridgeDetector bd( - expolygons, - lower_layer->slices, - this->flow(frInfill, true, this->layer()->height).scaled_width() - ); - - #ifdef SLIC3R_DEBUG - printf("Processing bridge at layer " PRINTF_ZU ":\n", this->layer()->id(); - #endif - - if (bd.detect_angle()) { - angle = bd.angle; - - if (this->layer()->object()->config.support_material) { - polygons_append(this->bridged, bd.coverage()); - this->unsupported_bridge_edges.append(bd.unsupported_edges()); - } - } - } - - for (ExPolygons::const_iterator it = grown.begin(); it != grown.end(); ++it) { - Surface s = *surface; - s.expolygon = *it; - s.bridge_angle = angle; - bottom.push_back(s); - } - } -#else // 1) Collect bottom and bridge surfaces, each of them grown by a fixed 3mm offset // for better anchoring. // Bottom surfaces, grown. Surfaces bottom; // Bridge surfaces, initialy not grown. Surfaces bridges; - // Bridge expolygons, grown, to be tested for intersection with other bridge regions. - std::vector bridges_grown; - // Bounding boxes of bridges_grown. - std::vector bridge_bboxes; - // For all stBottom && stBottomBridge surfaces: - for (Surfaces::const_iterator surface = surfaces.begin(); surface != surfaces.end(); ++surface) { - if (surface->surface_type == stBottom || lower_layer == NULL) { - // Grown by 3mm. - surfaces_append(bottom, offset_ex(surface->expolygon, float(margin), EXTERNAL_SURFACES_OFFSET_PARAMETERS), *surface); - } else if (surface->surface_type == stBottomBridge) { - bridges.push_back(*surface); - // Grown by 3mm. - bridges_grown.push_back(offset(surface->expolygon, float(margin), EXTERNAL_SURFACES_OFFSET_PARAMETERS)); - bridge_bboxes.push_back(get_extents(bridges_grown.back())); + // Top surfaces, grown. + Surfaces top; + // Internal surfaces, not grown. + Surfaces internal; + // Areas, where an infill of various types (top, bottom, bottom bride, sparse, void) could be placed. + Polygons fill_boundaries; + + // Collect top surfaces and internal surfaces. + // Collect fill_boundaries: If we're slicing with no infill, we can't extend external surfaces over non-existent infill. + // This loop destroys the surfaces (aliasing this->fill_surfaces.surfaces) by moving into top/internal/fill_boundaries! + { + // bottom_polygons are used to trim inflated top surfaces. + fill_boundaries.reserve(number_polygons(surfaces)); + bool has_infill = this->region()->config.fill_density.value > 0.; + for (Surfaces::iterator surface = this->fill_surfaces.surfaces.begin(); surface != this->fill_surfaces.surfaces.end(); ++surface) { + if (surface->surface_type == stTop) { + // Collect the top surfaces, inflate them and trim them by the bottom surfaces. + // This gives the priority to bottom surfaces. + surfaces_append(top, offset_ex(surface->expolygon, float(margin), EXTERNAL_SURFACES_OFFSET_PARAMETERS), *surface); + } else if (surface->surface_type == stBottom || (surface->surface_type == stBottomBridge && lower_layer == NULL)) { + // Grown by 3mm. + surfaces_append(bottom, offset_ex(surface->expolygon, float(margin), EXTERNAL_SURFACES_OFFSET_PARAMETERS), *surface); + } else if (surface->surface_type == stBottomBridge) { + if (! surface->empty()) + bridges.push_back(*surface); + } + bool internal_surface = surface->surface_type != stTop && ! surface->is_bottom(); + if (has_infill || surface->surface_type != stInternal) { + if (internal_surface) + // Make a copy as the following line uses the move semantics. + internal.push_back(*surface); + polygons_append(fill_boundaries, STDMOVE(surface->expolygon)); + } else if (internal_surface) + internal.push_back(STDMOVE(*surface)); } } @@ -176,8 +165,56 @@ LayerRegion::process_external_surfaces(const Layer* lower_layer) } #endif - if (lower_layer != NULL) + if (bridges.empty()) { + fill_boundaries = union_(fill_boundaries, true); + } else + { + // 1) Calculate the inflated bridge regions, each constrained to its island. + ExPolygons fill_boundaries_ex = union_ex(fill_boundaries, true); + std::vector bridges_grown; + std::vector bridge_bboxes; + +#ifdef SLIC3R_DEBUG_SLICE_PROCESSING + { + static int iRun = 0; + SVG svg(debug_out_path("3_process_external_surfaces-fill_regions-%d.svg", iRun ++).c_str(), get_extents(fill_boundaries_ex)); + svg.draw(fill_boundaries_ex); + svg.draw_outline(fill_boundaries_ex, "black", "blue", scale_(0.05)); + svg.Close(); + } + +// export_region_fill_surfaces_to_svg_debug("3_process_external_surfaces-initial"); +#endif /* SLIC3R_DEBUG_SLICE_PROCESSING */ + + { + // Bridge expolygons, grown, to be tested for intersection with other bridge regions. + std::vector fill_boundaries_ex_bboxes = get_extents_vector(fill_boundaries_ex); + bridges_grown.reserve(bridges.size()); + bridge_bboxes.reserve(bridges.size()); + for (size_t i = 0; i < bridges.size(); ++ i) { + // Find the island of this bridge. + const Point pt = bridges[i].expolygon.contour.points.front(); + int idx_island = -1; + for (int j = 0; j < int(fill_boundaries_ex.size()); ++ j) + if (fill_boundaries_ex_bboxes[j].contains(pt) && + fill_boundaries_ex[j].contains(pt)) { + idx_island = j; + break; + } + // Grown by 3mm. + Polygons polys = offset(bridges[i].expolygon, float(margin), EXTERNAL_SURFACES_OFFSET_PARAMETERS); + if (idx_island == -1) { + printf("Bridge did not fall into the source region!\r\n"); + } else { + // Found an island, to which this bridge region belongs. Trim it, + polys = intersection(polys, to_polygons(fill_boundaries_ex[idx_island])); + } + bridge_bboxes.push_back(get_extents(polys)); + bridges_grown.push_back(STDMOVE(polys)); + } + } + // 2) Group the bridge surfaces by overlaps. std::vector bridge_group(bridges.size(), (size_t)-1); size_t n_groups = 0; @@ -195,7 +232,7 @@ LayerRegion::process_external_surfaces(const Layer* lower_layer) if (bridge_group[j] != -1) { // The j'th bridge has been merged with some other bridge before. size_t group_id_new = bridge_group[j]; - for (size_t k = i; k < j; ++ k) + for (size_t k = 0; k < j; ++ k) if (bridge_group[k] == group_id) bridge_group[k] = group_id_new; group_id = group_id_new; @@ -251,6 +288,8 @@ LayerRegion::process_external_surfaces(const Layer* lower_layer) // without safety offset, artifacts are generated (GH #2494) surfaces_append(bottom, union_ex(grown, true), bridges[idx_last]); } + + fill_boundaries = STDMOVE(to_polygons(fill_boundaries_ex)); } #if 0 @@ -260,58 +299,33 @@ LayerRegion::process_external_surfaces(const Layer* lower_layer) } #endif } -#endif - - // Collect top surfaces and internal surfaces. - // Collect fill_boundaries: If we're slicing with no infill, we can't extend external surfaces over non-existent infill. - Surfaces top; - Surfaces internal; - Polygons fill_boundaries; - // This loop destroys the surfaces (aliasing this->fill_surfaces.surfaces) by moving into top/internal/fill_boundaries! - { - // bottom_polygons are used to trim inflated top surfaces. - const Polygons bottom_polygons = to_polygons(bottom); - fill_boundaries.reserve(number_polygons(surfaces)); - bool has_infill = this->region()->config.fill_density.value > 0.; - for (Surfaces::iterator surface = this->fill_surfaces.surfaces.begin(); surface != this->fill_surfaces.surfaces.end(); ++surface) { - if (surface->surface_type == stTop) - // Collect the top surfaces, inflate them and trim them by the bottom surfaces. - // This gives the priority to bottom surfaces. - surfaces_append( - top, - STDMOVE(diff_ex(offset(surface->expolygon, float(margin), EXTERNAL_SURFACES_OFFSET_PARAMETERS), bottom_polygons)), - *surface); // template - bool internal_surface = surface->surface_type != stTop && ! surface->is_bottom(); - if (has_infill || surface->surface_type != stInternal) { - if (internal_surface) - // Make a copy as the following line uses the move semantics. - internal.push_back(*surface); - polygons_append(fill_boundaries, STDMOVE(surface->expolygon)); - } else if (internal_surface) - internal.push_back(STDMOVE(*surface)); - } - } Surfaces new_surfaces; - - // Merge top and bottom in a single collection. - surfaces_append(top, STDMOVE(bottom)); - // Intersect the grown surfaces with the actual fill boundaries. - for (size_t i = 0; i < top.size(); ++ i) { - Surface &s1 = top[i]; - if (s1.empty()) - continue; - Polygons polys; - polygons_append(polys, STDMOVE(s1)); - for (size_t j = i + 1; j < top.size(); ++ j) { - Surface &s2 = top[j]; - if (! s2.empty() && surfaces_could_merge(s1, s2)) - polygons_append(polys, STDMOVE(s2)); + { + // Merge top and bottom in a single collection. + surfaces_append(top, STDMOVE(bottom)); + // Intersect the grown surfaces with the actual fill boundaries. + Polygons bottom_polygons = to_polygons(bottom); + for (size_t i = 0; i < top.size(); ++ i) { + Surface &s1 = top[i]; + if (s1.empty()) + continue; + Polygons polys; + polygons_append(polys, STDMOVE(s1)); + for (size_t j = i + 1; j < top.size(); ++ j) { + Surface &s2 = top[j]; + if (! s2.empty() && surfaces_could_merge(s1, s2)) + polygons_append(polys, STDMOVE(s2)); + } + if (s1.surface_type == stTop) + // Trim the top surfaces by the bottom surfaces. This gives the priority to the bottom surfaces. + polys = diff(polys, bottom_polygons); + surfaces_append( + new_surfaces, + // Don't use a safety offset as fill_boundaries were already united using the safety offset. + STDMOVE(intersection_ex(polys, fill_boundaries, false)), + s1); } - surfaces_append( - new_surfaces, - STDMOVE(intersection_ex(polys, fill_boundaries, true)), - s1); } // Subtract the new top surfaces from the other non-top surfaces and re-add them. @@ -342,6 +356,11 @@ LayerRegion::process_external_surfaces(const Layer* lower_layer) void LayerRegion::prepare_fill_surfaces() { +#ifdef SLIC3R_DEBUG_SLICE_PROCESSING + export_region_slices_to_svg_debug("2_prepare_fill_surfaces-initial"); + export_region_fill_surfaces_to_svg_debug("2_prepare_fill_surfaces-initial"); +#endif /* SLIC3R_DEBUG_SLICE_PROCESSING */ + /* Note: in order to make the psPrepareInfill step idempotent, we should never alter fill_surfaces boundaries on which our idempotency relies since that's the only meaningful information returned by psPerimeters. */ @@ -376,8 +395,8 @@ LayerRegion::prepare_fill_surfaces() } #ifdef SLIC3R_DEBUG_SLICE_PROCESSING - export_region_slices_to_svg_debug("2_prepare_fill_surfaces"); - export_region_fill_surfaces_to_svg_debug("2_prepare_fill_surfaces"); + export_region_slices_to_svg_debug("2_prepare_fill_surfaces-final"); + export_region_fill_surfaces_to_svg_debug("2_prepare_fill_surfaces-final"); #endif /* SLIC3R_DEBUG_SLICE_PROCESSING */ } diff --git a/xs/src/libslic3r/PerimeterGenerator.cpp b/xs/src/libslic3r/PerimeterGenerator.cpp index f39f38cff..594e385d9 100644 --- a/xs/src/libslic3r/PerimeterGenerator.cpp +++ b/xs/src/libslic3r/PerimeterGenerator.cpp @@ -120,7 +120,11 @@ PerimeterGenerator::process() // from the line width of the infill? coord_t distance = (i == 1) ? ext_pspacing2 : pspacing; - if (this->config->thin_walls) { + + //FIXME Vojtech: Why there is a special case for the thin walls? + // Gap fill is active all the time anyway and this is not the outer perimeter. +// if (this->config->thin_walls) { + if (false) { offsets = offset2( last, -(distance + min_spacing/2 - 1), diff --git a/xs/src/libslic3r/Print.hpp b/xs/src/libslic3r/Print.hpp index 7a41af302..30b1f40cc 100644 --- a/xs/src/libslic3r/Print.hpp +++ b/xs/src/libslic3r/Print.hpp @@ -138,6 +138,7 @@ public: bool invalidate_all_steps(); bool has_support_material() const; + void detect_surfaces_type(); void process_external_surfaces(); void discover_vertical_shells(); void bridge_over_infill(); diff --git a/xs/src/libslic3r/PrintObject.cpp b/xs/src/libslic3r/PrintObject.cpp index 34f6fbbb8..64a46c06c 100644 --- a/xs/src/libslic3r/PrintObject.cpp +++ b/xs/src/libslic3r/PrintObject.cpp @@ -310,6 +310,162 @@ PrintObject::has_support_material() const || this->config.support_material_enforce_layers > 0; } +// This function analyzes slices of a region (SurfaceCollection slices). +// Each slice (instance of Surface) is analyzed, whether it is supported or whether it is the top surface. +// Initially all slices are of type S_TYPE_INTERNAL. +// Slices are compared against the top / bottom slices and regions and classified to the following groups: +// S_TYPE_TOP - Part of a region, which is not covered by any upper layer. This surface will be filled with a top solid infill. +// S_TYPE_BOTTOMBRIDGE - Part of a region, which is not fully supported, but it hangs in the air, or it hangs losely on a support or a raft. +// S_TYPE_BOTTOM - Part of a region, which is not supported by the same region, but it is supported either by another region, or by a soluble interface layer. +// S_TYPE_INTERNAL - Part of a region, which is supported by the same region type. +// If a part of a region is of S_TYPE_BOTTOM and S_TYPE_TOP, the S_TYPE_BOTTOM wins. +void PrintObject::detect_surfaces_type() +{ +// Slic3r::debugf "Detecting solid surfaces...\n"; + for (int idx_region = 0; idx_region < this->_print->regions.size(); ++ idx_region) { + // Fill in layerm->fill_surfaces by trimming the layerm->slices by the cummulative layerm->fill_surfaces. + for (int idx_layer = 0; idx_layer < int(this->layer_count()); ++ idx_layer) { + LayerRegion *layerm = this->layers[idx_layer]->get_region(idx_region); + layerm->slices_to_fill_surfaces_clipped(); +#ifdef SLIC3R_DEBUG_SLICE_PROCESSING + layerm->export_region_fill_surfaces_to_svg_debug("1_detect_surfaces_type-initial"); +#endif /* SLIC3R_DEBUG_SLICE_PROCESSING */ + } + + for (int idx_layer = 0; idx_layer < int(this->layer_count()); ++ idx_layer) { + Layer *layer = this->layers[idx_layer]; + LayerRegion *layerm = layer->get_region(idx_region); + // comparison happens against the *full* slices (considering all regions) + // unless internal shells are requested + Layer *upper_layer = idx_layer + 1 < this->layer_count() ? this->get_layer(idx_layer + 1) : NULL; + Layer *lower_layer = idx_layer > 0 ? this->get_layer(idx_layer - 1) : NULL; + // collapse very narrow parts (using the safety offset in the diff is not enough) + float offset = layerm->flow(frExternalPerimeter).scaled_width() / 10.f; + + Polygons layerm_slices_surfaces = to_polygons(layerm->slices.surfaces); + + // find top surfaces (difference between current surfaces + // of current layer and upper one) + Surfaces top; + if (upper_layer) { + // Config value $self->config->interface_shells is true, if a support is separated from the object + // by a soluble material (for example a PVA plastic). + Polygons upper_slices = this->config.interface_shells.value ? + to_polygons(upper_layer->get_region(idx_region)->slices.surfaces) : + to_polygons(upper_layer->slices); + surfaces_append(top, + offset2_ex(diff(layerm_slices_surfaces, upper_slices, true), -offset, offset), + stTop); + } else { + // if no upper layer, all surfaces of this one are solid + // we clone surfaces because we're going to clear the slices collection + top = layerm->slices.surfaces; + for (Surfaces::iterator it = top.begin(); it != top.end(); ++ it) + it->surface_type = stTop; + } + + // find bottom surfaces (difference between current surfaces + // of current layer and lower one) + Surfaces bottom; + if (lower_layer) { + // If we have soluble support material, don't bridge. The overhang will be squished against a soluble layer separating + // the support from the print. + SurfaceType surface_type_bottom = + (this->config.support_material.value && this->config.support_material_contact_distance.value == 0) ? + stBottom : stBottomBridge; + // Any surface lying on the void is a true bottom bridge (an overhang) + surfaces_append( + bottom, + offset2_ex( + diff(layerm_slices_surfaces, to_polygons(lower_layer->slices), true), + -offset, offset), + surface_type_bottom); + // if user requested internal shells, we need to identify surfaces + // lying on other slices not belonging to this region + //FIXME Vojtech: config.internal_shells or config.interface_shells? Is it some legacy code? + // Why shall multiple regions over soluble support be treated specially? + if (this->config.interface_shells.value) { + // non-bridging bottom surfaces: any part of this layer lying + // on something else, excluding those lying on our own region + surfaces_append( + bottom, + offset2_ex( + diff( + intersection(layerm_slices_surfaces, to_polygons(lower_layer->slices)), // supported + to_polygons(lower_layer->get_region(idx_region)->slices.surfaces), + true), + -offset, offset), + stBottom); + } + } else { + // if no lower layer, all surfaces of this one are solid + // we clone surfaces because we're going to clear the slices collection + bottom = layerm->slices.surfaces; + // if we have raft layers, consider bottom layer as a bridge + // just like any other bottom surface lying on the void + SurfaceType surface_type_bottom = + (this->config.raft_layers.value > 0 && this->config.support_material_contact_distance.value > 0) ? + stBottomBridge : stBottom; + for (Surfaces::iterator it = bottom.begin(); it != bottom.end(); ++ it) + it->surface_type = surface_type_bottom; + } + + // now, if the object contained a thin membrane, we could have overlapping bottom + // and top surfaces; let's do an intersection to discover them and consider them + // as bottom surfaces (to allow for bridge detection) + if (! top.empty() && ! bottom.empty()) { +// Polygons overlapping = intersection(to_polygons(top), to_polygons(bottom)); +// Slic3r::debugf " layer %d contains %d membrane(s)\n", $layerm->layer->id, scalar(@$overlapping) +// if $Slic3r::debug; + Polygons top_polygons = to_polygons(STDMOVE(top)); + top.clear(); + surfaces_append(top, +#if 0 + offset2_ex(diff(top_polygons, to_polygons(bottom), true), -offset, offset), +#else + diff_ex(top_polygons, to_polygons(bottom), false), +#endif + stTop); + } + + // save surfaces to layer + layerm->slices.surfaces.clear(); + + // find internal surfaces (difference between top/bottom surfaces and others) + { + Polygons topbottom = to_polygons(top); + polygons_append(topbottom, to_polygons(bottom)); + surfaces_append(layerm->slices.surfaces, +#if 0 + offset2_ex(diff(layerm_slices_surfaces, topbottom, true), -offset, offset), +#else + diff_ex(layerm_slices_surfaces, topbottom, false), +#endif + stInternal); + } + + surfaces_append(layerm->slices.surfaces, STDMOVE(top)); + surfaces_append(layerm->slices.surfaces, STDMOVE(bottom)); + +// Slic3r::debugf " layer %d has %d bottom, %d top and %d internal surfaces\n", +// $layerm->layer->id, scalar(@bottom), scalar(@top), scalar(@internal) if $Slic3r::debug; + +#ifdef SLIC3R_DEBUG_SLICE_PROCESSING + layerm->export_region_slices_to_svg_debug("detect_surfaces_type-final"); +#endif /* SLIC3R_DEBUG_SLICE_PROCESSING */ + } // for each layer of a region + + // Fill in layerm->fill_surfaces by trimming the layerm->slices by the cummulative layerm->fill_surfaces. + for (int idx_layer = 0; idx_layer < int(this->layer_count()); ++ idx_layer) { + LayerRegion *layerm = this->layers[idx_layer]->get_region(idx_region); + layerm->slices_to_fill_surfaces_clipped(); +#ifdef SLIC3R_DEBUG_SLICE_PROCESSING + layerm->export_region_fill_surfaces_to_svg_debug("1_detect_surfaces_type-final"); +#endif /* SLIC3R_DEBUG_SLICE_PROCESSING */ + } // for each layer of a region + } // for each $self->print->region_count +} + void PrintObject::process_external_surfaces() { @@ -367,10 +523,23 @@ PrintObject::discover_vertical_shells() ++ idx; } #endif /* SLIC3R_DEBUG_SLICE_PROCESSING */ + SurfaceType surfaces_bottom[2] = { stBottom, stBottomBridge }; for (int n = (int)idx_layer - layerm->region()->config.bottom_solid_layers + 1; n < (int)idx_layer + layerm->region()->config.top_solid_layers; ++ n) - if (n >= 0 && n < (int)this->layers.size()) - polygons_append(shell, this->layers[n]->perimeter_expolygons.expolygons); - //FIXME Add the top / bottom layerm->slices to the mix! + if (n >= 0 && n < (int)this->layers.size()) { + Layer &neighbor_layer = *this->layers[n]; + LayerRegion &neighbor_region = *neighbor_layer.get_region(int(idx_region)); + polygons_append(shell, neighbor_layer.perimeter_expolygons.expolygons); + if (n > int(idx_layer)) { + // Collect top surfaces. + polygons_append(shell, to_polygons(neighbor_region.slices.filter_by_type(stTop))); + polygons_append(shell, to_polygons(neighbor_region.fill_surfaces.filter_by_type(stTop))); + } + else if (n < int(idx_layer)) { + // Collect bottom and bottom bridge surfaces. + polygons_append(shell, to_polygons(neighbor_region.slices.filter_by_types(surfaces_bottom, 2))); + polygons_append(shell, to_polygons(neighbor_region.fill_surfaces.filter_by_types(surfaces_bottom, 2))); + } + } #ifdef SLIC3R_DEBUG_SLICE_PROCESSING { static size_t idx = 0; @@ -430,25 +599,47 @@ PrintObject::discover_vertical_shells() #endif /* SLIC3R_DEBUG_SLICE_PROCESSING */ // Trim the shells region by the internal & internal void surfaces. - const SurfaceType surfaceTypesInternal[] = { stInternal, stInternalVoid }; + const SurfaceType surfaceTypesInternal[] = { stInternal, stInternalVoid, stInternalSolid }; const Polygons polygonsInternal = to_polygons(layerm->fill_surfaces.filter_by_types(surfaceTypesInternal, 2)); shell = intersection(shell, polygonsInternal, true); if (shell.empty()) continue; + // Append the internal solids, so they will be merged with the new ones. + polygons_append(shell, to_polygons(layerm->fill_surfaces.filter_by_type(stInternalSolid))); + // These regions will be filled by a rectilinear full infill. Currently this type of infill // only fills regions, which fit at least a single line. To avoid gaps in the sparse infill, // make sure that this region does not contain parts narrower than the infill spacing width. - float min_perimeter_infill_spacing = float(infill_line_spacing) * 1.05f; #ifdef SLIC3R_DEBUG_SLICE_PROCESSING Polygons shell_before = shell; #endif /* SLIC3R_DEBUG_SLICE_PROCESSING */ +#if 1 + float min_perimeter_infill_spacing = float(infill_line_spacing) * 1.05f; // Intentionally inflate a bit more than how much the region has been shrunk, // so there will be some overlap between this solid infill and the other infill regions (mainly the sparse infill). shell = offset2(shell, - 0.5f * min_perimeter_infill_spacing, 0.8f * min_perimeter_infill_spacing, CLIPPER_OFFSET_SCALE, ClipperLib::jtSquare); if (shell.empty()) continue; +#else + // Ensure each region is at least 3x infill line width wide, so it could be filled in. +// float margin = float(infill_line_spacing) * 3.f; + float margin = float(infill_line_spacing) * 1.5f; + // we use a higher miterLimit here to handle areas with acute angles + // in those cases, the default miterLimit would cut the corner and we'd + // get a triangle in $too_narrow; if we grow it below then the shell + // would have a different shape from the external surface and we'd still + // have the same angle, so the next shell would be grown even more and so on. + Polygons too_narrow = diff(shell, offset2(shell, -margin, margin, CLIPPER_OFFSET_SCALE, ClipperLib::jtMiter, 5.), true); + if (! too_narrow.empty()) { + // grow the collapsing parts and add the extra area to the neighbor layer + // as well as to our original surfaces so that we support this + // additional area in the next shell too + // make sure our grown surfaces don't exceed the fill area + polygons_append(shell, intersection(offset(too_narrow, margin), polygonsInternal)); + } +#endif ExPolygons new_internal_solid = intersection_ex(polygonsInternal, shell, false); #ifdef SLIC3R_DEBUG_SLICE_PROCESSING { @@ -465,8 +656,6 @@ PrintObject::discover_vertical_shells() #endif /* SLIC3R_DEBUG_SLICE_PROCESSING */ // Trim the internal & internalvoid by the shell. - // Enforce some overlap with the other infill regions. - shell = offset(shell, - 0.25f * min_perimeter_infill_spacing); Slic3r::ExPolygons new_internal = diff_ex( to_polygons(layerm->fill_surfaces.filter_by_type(stInternal)), shell, @@ -489,7 +678,7 @@ PrintObject::discover_vertical_shells() #endif /* SLIC3R_DEBUG_SLICE_PROCESSING */ // Assign resulting internal surfaces to layer. - const SurfaceType surfaceTypesKeep[] = { stTop, stBottom, stBottomBridge, stInternalSolid }; + const SurfaceType surfaceTypesKeep[] = { stTop, stBottom, stBottomBridge }; layerm->fill_surfaces.keep_types(surfaceTypesKeep, sizeof(surfaceTypesKeep)/sizeof(SurfaceType)); layerm->fill_surfaces.append(stInternal , new_internal); layerm->fill_surfaces.append(stInternalVoid , new_internal_void); diff --git a/xs/xsp/Print.xsp b/xs/xsp/Print.xsp index f8b541712..ad90eb32e 100644 --- a/xs/xsp/Print.xsp +++ b/xs/xsp/Print.xsp @@ -107,6 +107,7 @@ _constant() void set_step_started(PrintObjectStep step) %code%{ THIS->state.set_started(step); %}; + void detect_surfaces_type(); void process_external_surfaces(); void discover_vertical_shells(); void bridge_over_infill();