diff --git a/lib/Slic3r/Print/Object.pm b/lib/Slic3r/Print/Object.pm index 380da13f8..08b6355f5 100644 --- a/lib/Slic3r/Print/Object.pm +++ b/lib/Slic3r/Print/Object.pm @@ -252,181 +252,6 @@ sub _support_material { ); } -sub discover_horizontal_shells { - my $self = shift; - - Slic3r::debugf "==> DISCOVERING HORIZONTAL SHELLS\n"; - - for my $region_id (0 .. ($self->print->region_count-1)) { - for (my $i = 0; $i < $self->layer_count; $i++) { - my $layerm = $self->get_layer($i)->regions->[$region_id]; - - if ($layerm->region->config->solid_infill_every_layers && $layerm->region->config->fill_density > 0 - && ($i % $layerm->region->config->solid_infill_every_layers) == 0) { - 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)}; - } - - EXTERNAL: foreach my $type (S_TYPE_TOP, S_TYPE_BOTTOM, S_TYPE_BOTTOMBRIDGE) { - # find slices of current type for current layer - # use slices instead of fill_surfaces because they also include the perimeter area - # which needs to be propagated in shells; we need to grow slices like we did for - # fill_surfaces though. Using both ungrown slices and grown fill_surfaces will - # not work in some situations, as there won't be any grown region in the perimeter - # area (this was seen in a model where the top layer had one extra perimeter, thus - # its fill_surfaces were thinner than the lower layer's infill), however it's the best - # solution so far. Growing the external slices by EXTERNAL_INFILL_MARGIN will put - # too much solid infill inside nearly-vertical slopes. - my $solid = [ - (map $_->p, @{$layerm->slices->filter_by_type($type)}), - (map $_->p, @{$layerm->fill_surfaces->filter_by_type($type)}), - ]; - next if !@$solid; - Slic3r::debugf "Layer %d has %s surfaces\n", $i, ($type == S_TYPE_TOP) ? 'top' : 'bottom'; - - my $solid_layers = ($type == S_TYPE_TOP) - ? $layerm->region->config->top_solid_layers - : $layerm->region->config->bottom_solid_layers; - - if ($layerm->region->config->min_top_bottom_shell_thickness > 0) { - my $current_shell_thickness = $solid_layers * $self->get_layer($i)->height; - my $minimum_shell_thickness = $layerm->region->config->min_top_bottom_shell_thickness; - - while ($minimum_shell_thickness - $current_shell_thickness > epsilon) { - $solid_layers++; - $current_shell_thickness = $solid_layers * $self->get_layer($i)->height; - } - } - - NEIGHBOR: for (my $n = ($type == S_TYPE_TOP) ? $i-1 : $i+1; - abs($n - $i) <= $solid_layers-1; - ($type == S_TYPE_TOP) ? $n-- : $n++) { - - next if $n < 0 || $n >= $self->layer_count; - Slic3r::debugf " looking for neighbors on layer %d...\n", $n; - - my $neighbor_layerm = $self->get_layer($n)->regions->[$region_id]; - my $neighbor_fill_surfaces = $neighbor_layerm->fill_surfaces; - my @neighbor_fill_surfaces = map $_->clone, @$neighbor_fill_surfaces; # clone because we will use these surfaces even after clearing the collection - - # find intersection between neighbor and current layer's surfaces - # intersections have contours and holes - my $new_internal_solid = intersection( - $solid, - [ map $_->p, grep { ($_->surface_type == S_TYPE_INTERNAL) || ($_->surface_type == S_TYPE_INTERNALSOLID) } @neighbor_fill_surfaces ], - 1, - ); - if (!@$new_internal_solid) { - # No internal solid needed on this layer. In order to decide whether to continue - # searching on the next neighbor (thus enforcing the configured number of solid - # layers, use different strategies according to configured infill density: - if ($layerm->region->config->fill_density == 0) { - # If user expects the object to be void (for example a hollow sloping vase), - # don't continue the search. In this case, we only generate the external solid - # shell if the object would otherwise show a hole (gap between perimeters of - # the two layers), and internal solid shells are a subset of the shells found - # on each previous layer. - next EXTERNAL; - } else { - # If we have internal infill, we can generate internal solid shells freely. - next NEIGHBOR; - } - } - - if ($layerm->region->config->fill_density == 0) { - # if we're printing a hollow object we discard any solid shell thinner - # than a perimeter width, since it's probably just crossing a sloping wall - # and it's not wanted in a hollow print even if it would make sense when - # obeying the solid shell count option strictly (DWIM!) - my $margin = $neighbor_layerm->flow(FLOW_ROLE_EXTERNAL_PERIMETER)->scaled_width; - my $too_narrow = diff( - $new_internal_solid, - offset2($new_internal_solid, -$margin, +$margin, CLIPPER_OFFSET_SCALE, JT_MITER, 5), - 1, - ); - $new_internal_solid = $solid = diff( - $new_internal_solid, - $too_narrow, - ) if @$too_narrow; - } - - # make sure the new internal solid is wide enough, as it might get collapsed - # when spacing is added in Fill.pm - { - my $margin = 3 * $layerm->flow(FLOW_ROLE_SOLID_INFILL)->scaled_width; # require at least this size - # 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. - my $too_narrow = diff( - $new_internal_solid, - offset2($new_internal_solid, -$margin, +$margin, CLIPPER_OFFSET_SCALE, JT_MITER, 5), - 1, - ); - - if (@$too_narrow) { - # 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 - my @grown = @{intersection( - offset($too_narrow, +$margin), - # Discard bridges as they are grown for anchoring and we can't - # remove such anchors. (This may happen when a bridge is being - # anchored onto a wall where little space remains after the bridge - # is grown, and that little space is an internal solid shell so - # it triggers this too_narrow logic.) - [ map $_->p, grep { $_->is_internal && !$_->is_bridge } @neighbor_fill_surfaces ], - )}; - $new_internal_solid = $solid = [ @grown, @$new_internal_solid ]; - } - } - - # internal-solid are the union of the existing internal-solid surfaces - # and new ones - my $internal_solid = union_ex([ - ( map $_->p, grep $_->surface_type == S_TYPE_INTERNALSOLID, @neighbor_fill_surfaces ), - @$new_internal_solid, - ]); - - # subtract intersections from layer surfaces to get resulting internal surfaces - my $internal = diff_ex( - [ map $_->p, grep $_->surface_type == S_TYPE_INTERNAL, @neighbor_fill_surfaces ], - [ map @$_, @$internal_solid ], - 1, - ); - Slic3r::debugf " %d internal-solid and %d internal surfaces found\n", - scalar(@$internal_solid), scalar(@$internal); - - # assign resulting internal surfaces to layer - $neighbor_fill_surfaces->clear; - $neighbor_fill_surfaces->append($_) - for map Slic3r::Surface->new(expolygon => $_, surface_type => S_TYPE_INTERNAL), - @$internal; - - # assign new internal-solid surfaces to layer - $neighbor_fill_surfaces->append($_) - for map Slic3r::Surface->new(expolygon => $_, surface_type => S_TYPE_INTERNALSOLID), - @$internal_solid; - - # assign top and bottom surfaces to layer - foreach my $s (@{Slic3r::Surface::Collection->new(grep { ($_->surface_type == S_TYPE_TOP) || $_->is_bottom } @neighbor_fill_surfaces)->group}) { - my $solid_surfaces = diff_ex( - [ map $_->p, @$s ], - [ map @$_, @$internal_solid, @$internal ], - 1, - ); - $neighbor_fill_surfaces->append($_) - for map $s->[0]->clone(expolygon => $_), @$solid_surfaces; - } - } - } - } - } -} - # combine fill surfaces across layers # 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. diff --git a/xs/src/libslic3r/PrintObject.cpp b/xs/src/libslic3r/PrintObject.cpp index aa3680e62..74f36099e 100644 --- a/xs/src/libslic3r/PrintObject.cpp +++ b/xs/src/libslic3r/PrintObject.cpp @@ -1375,6 +1375,10 @@ PrintObject::generate_support_material() void PrintObject::discover_horizontal_shells() { + #ifdef SLIC3R_DEBUG + std::cout << "==> DISCOVERING HORIZONTAL SHELLS" << std::endl; + #endif + for (size_t region_id = 0U; region_id < _print->regions.size(); ++region_id) { for (size_t i = 0; i < this->layer_count(); ++i) { auto* layerm = this->get_layer(i)->get_region(region_id); @@ -1383,8 +1387,8 @@ PrintObject::discover_horizontal_shells() if (region_config.solid_infill_every_layers() > 0 && region_config.fill_density() > 0 && (i % region_config.solid_infill_every_layers()) == 0) { const auto type = region_config.fill_density() == 100 ? stInternalSolid : stInternalBridge; - // set the surface type to internal for the types - std::for_each(layerm->fill_surfaces.begin(), layerm->fill_surfaces.end(), [type] (Surface& s) { s.surface_type = (s.surface_type == type ? stInternal : s.surface_type); }); + for (auto* s : layerm->fill_surfaces.filter_by_type(stInternal)) + s->surface_type = type; } this->_discover_external_horizontal_shells(layerm, i, region_id); } @@ -1407,22 +1411,24 @@ PrintObject::_discover_external_horizontal_shells(LayerRegion* layerm, const siz // too much solid infill inside nearly-vertical slopes. Polygons solid; - auto tmp = layerm->slices.filter_by_type(type); - polygons_append(solid, tmp); - tmp.clear(); - tmp = layerm->fill_surfaces.filter_by_type(type); - polygons_append(solid, tmp); - - if (solid.size() == 0) continue; - - auto solid_layers = type == stTop ? region_config.top_solid_layers() : region_config.bottom_solid_layers(); + polygons_append(solid, to_polygons(layerm->slices.filter_by_type(type))); + polygons_append(solid, to_polygons(layerm->fill_surfaces.filter_by_type(type))); + if (solid.empty()) continue; + + #ifdef SLIC3R_DEBUG + std::cout << "Layer " << i << " has " << (type == stTop ? "top" : "bottom") << " surfaces" << std::endl; + #endif + + auto solid_layers = type == stTop + ? region_config.top_solid_layers() + : region_config.bottom_solid_layers(); if (region_config.min_top_bottom_shell_thickness() > 0) { - auto current_shell_thick = static_cast(solid_layers) * this->get_layer(i)->height; - const auto min_shell_thick = region_config.min_top_bottom_shell_thickness(); - while (std::abs(min_shell_thick - current_shell_thick) > Slic3r::Geometry::epsilon) { + auto current_shell_thickness = static_cast(solid_layers) * this->get_layer(i)->height; + const auto min_shell_thickness = region_config.min_top_bottom_shell_thickness(); + while (std::abs(min_shell_thickness - current_shell_thickness) > Slic3r::Geometry::epsilon) { solid_layers++; - current_shell_thick = static_cast(solid_layers) * this->get_layer(i)->height; + current_shell_thickness = static_cast(solid_layers) * this->get_layer(i)->height; } } _discover_neighbor_horizontal_shells(layerm, i, region_id, type, solid, solid_layers); @@ -1440,16 +1446,19 @@ PrintObject::_discover_neighbor_horizontal_shells(LayerRegion* layerm, const siz LayerRegion* neighbor_layerm { this->get_layer(n)->get_region(region_id) }; // make a copy so we can use them even after clearing the original collection SurfaceCollection neighbor_fill_surfaces{ neighbor_layerm->fill_surfaces }; + // find intersection between neighbor and current layer's surfaces // intersections have contours and holes - Polygons filtered_poly; - polygons_append(filtered_poly, neighbor_fill_surfaces.filter_by_type({stInternal, stInternalSolid})); - auto new_internal_solid = intersection(solid, filtered_poly , 1 ); - if (new_internal_solid.size() == 0) { + Polygons new_internal_solid = intersection( + solid, + to_polygons(neighbor_fill_surfaces.filter_by_type({stInternal, stInternalSolid})), + true + ); + if (new_internal_solid.empty()) { // No internal solid needed on this layer. In order to decide whether to continue // searching on the next neighbor (thus enforcing the configured number of solid // layers, use different strategies according to configured infill density: - if(region_config.fill_density == 0) { + if (region_config.fill_density == 0) { // If user expects the object to be void (for example a hollow sloping vase), // don't continue the search. In this case, we only generate the external solid // shell if the object would otherwise show a hole (gap between perimeters of @@ -1468,14 +1477,19 @@ PrintObject::_discover_neighbor_horizontal_shells(LayerRegion* layerm, const siz // and it's not wanted in a hollow print even if it would make sense when // obeying the solid shell count option strictly (DWIM!) const auto margin = neighbor_layerm->flow(frExternalPerimeter).scaled_width(); - const auto too_narrow = diff(new_internal_solid, offset2(new_internal_solid, -margin, +margin, CLIPPER_OFFSET_SCALE, ClipperLib::jtMiter, 5), 1); - if (too_narrow.size() > 0) + const auto too_narrow = diff( + new_internal_solid, + offset2(new_internal_solid, -margin, +margin, CLIPPER_OFFSET_SCALE, ClipperLib::jtMiter, 5), + true + ); + if (!too_narrow.empty()) new_internal_solid = solid = diff(new_internal_solid, too_narrow); } // make sure the new internal solid is wide enough, as it might get collapsed // when spacing is added in Slic3r::Fill { + // require at least this size const auto margin = 3 * layerm->flow(frSolidInfill).scaled_width(); // we use a higher miterLimit here to handle areas with acute angles @@ -1483,7 +1497,11 @@ PrintObject::_discover_neighbor_horizontal_shells(LayerRegion* layerm, const siz // 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. - const auto too_narrow = diff(new_internal_solid, offset2(new_internal_solid, -margin, +margin, CLIPPER_OFFSET_SCALE, ClipperLib::jtMiter, 5), 1); + const auto too_narrow = diff( + new_internal_solid, + offset2(new_internal_solid, -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 @@ -1491,10 +1509,10 @@ PrintObject::_discover_neighbor_horizontal_shells(LayerRegion* layerm, const siz // additional area in the next shell too // make sure our grown surfaces don't exceed the fill area - Polygons tmp_internal; - for (auto& s : neighbor_fill_surfaces) { - if (s.is_internal() && !s.is_bridge()) tmp_internal.emplace_back(Polygon(s.expolygon)); - } + Polygons tmp; + for (auto& s : neighbor_fill_surfaces) + if (s.is_internal() && !s.is_bridge()) + append_to(tmp, (Polygons)s); const auto grown = intersection( offset(too_narrow, +margin), // Discard bridges as they are grown for anchoring and we cant @@ -1502,47 +1520,43 @@ PrintObject::_discover_neighbor_horizontal_shells(LayerRegion* layerm, const siz // anchored onto a wall where little space remains after the bridge // is grown, and that little space is an internal solid shell so // it triggers this too_narrow logic.) - tmp_internal + tmp ); - new_internal_solid = solid = diff(grown, new_internal_solid); + append_to(new_internal_solid, grown); + solid = new_internal_solid; } } + // internal-solid are the union of the existing internal-solid surfaces // and new ones - - Polygons tmp_internal { to_polygons(neighbor_fill_surfaces.filter_by_type(stInternalSolid)) }; - polygons_append(tmp_internal, neighbor_fill_surfaces.surfaces); - const auto internal_solid = union_ex(tmp_internal); + Polygons tmp { to_polygons(neighbor_fill_surfaces.filter_by_type(stInternalSolid)) }; + polygons_append(tmp, new_internal_solid); + const auto internal_solid = union_ex(tmp); // subtract intersections from layer surfaces to get resulting internal surfaces - tmp_internal = to_polygons(neighbor_fill_surfaces.filter_by_type(stInternal)); - const auto internal = diff_ex(tmp_internal, to_polygons(internal_solid), 1); + tmp = to_polygons(neighbor_fill_surfaces.filter_by_type(stInternal)); + const auto internal = diff_ex(tmp, to_polygons(internal_solid), 1); // assign resulting internal surfaces to layer - neighbor_fill_surfaces.clear(); - for (const auto& poly : internal) { - neighbor_fill_surfaces.surfaces.emplace_back(Surface(stInternal, poly)); - } + neighbor_layerm->fill_surfaces.clear(); + neighbor_layerm->fill_surfaces.append(internal, stInternal); // assign new internal-solid surfaces to layer - for (const auto& poly : internal_solid) { - neighbor_fill_surfaces.surfaces.emplace_back(Surface(stInternalSolid, poly)); - } + neighbor_layerm->fill_surfaces.append(internal_solid, stInternalSolid); // assign top and bottom surfaces to layer - SurfaceCollection tmp_collection; - for (auto& s : tmp_collection) { - Polygons pp; - append_to(pp, (Polygons)s); - ExPolygons both_solids; - both_solids.reserve(internal_solid.size() + internal.size()); - - both_solids.insert(both_solids.end(), internal_solid.begin(), internal_solid.end()); - both_solids.insert(both_solids.end(), internal.begin(), internal.end()); - - const auto solid_surfaces = diff_ex(pp, to_polygons(both_solids), 1); - for (auto exp : solid_surfaces) - neighbor_fill_surfaces.surfaces.emplace_back(Surface(s.surface_type, exp)); + SurfaceCollection tmp_coll; + for (const auto& s : neighbor_fill_surfaces.surfaces) + if (s.surface_type == stTop || s.is_bottom()) + tmp_coll.append(s); + + for (auto s : tmp_coll.group()) { + Polygons tmp; + append_to(tmp, to_polygons(internal_solid)); + append_to(tmp, to_polygons(internal)); + + const auto solid_surfaces = diff_ex(to_polygons(s), tmp, true); + neighbor_layerm->fill_surfaces.append(solid_surfaces, s.front()->surface_type); } } } diff --git a/xs/src/libslic3r/SurfaceCollection.cpp b/xs/src/libslic3r/SurfaceCollection.cpp index d8bea906b..1fb15d023 100644 --- a/xs/src/libslic3r/SurfaceCollection.cpp +++ b/xs/src/libslic3r/SurfaceCollection.cpp @@ -129,6 +129,12 @@ SurfaceCollection::append(const SurfaceCollection &coll) this->append(coll.surfaces); } +void +SurfaceCollection::append(const Surface &surface) +{ + this->surfaces.push_back(surface); +} + void SurfaceCollection::append(const Surfaces &surfaces) { @@ -209,6 +215,14 @@ SurfaceCollection::keep_types(std::initializer_list types) { } } /* group surfaces by common properties */ +std::vector +SurfaceCollection::group() +{ + std::vector retval; + this->group(&retval); + return retval; +} + void SurfaceCollection::group(std::vector *retval) { diff --git a/xs/src/libslic3r/SurfaceCollection.hpp b/xs/src/libslic3r/SurfaceCollection.hpp index 1eca06012..02b54a463 100644 --- a/xs/src/libslic3r/SurfaceCollection.hpp +++ b/xs/src/libslic3r/SurfaceCollection.hpp @@ -41,6 +41,7 @@ class SurfaceCollection remove_types(types.data(), types.size()); } /// group surfaces by common properties + std::vector group(); void group(std::vector *retval); /// Deletes every surface other than the ones that match the provided type. @@ -64,6 +65,7 @@ class SurfaceCollection void set(Surfaces &&src) { clear(); this->append(std::move(src)); } void append(const SurfaceCollection &coll); + void append(const Surface &surface); void append(const Surfaces &surfaces); void append(const ExPolygons &src, const Surface &templ); void append(const ExPolygons &src, SurfaceType surfaceType); diff --git a/xs/xsp/Print.xsp b/xs/xsp/Print.xsp index 6c49abb3b..40d59837a 100644 --- a/xs/xsp/Print.xsp +++ b/xs/xsp/Print.xsp @@ -129,6 +129,7 @@ _constant() %name{_detect_surfaces_type} void detect_surfaces_type(); void process_external_surfaces(); void bridge_over_infill(); + void discover_horizontal_shells(); void clip_fill_surfaces(); void _slice(); SV* _slice_region(size_t region_id, std::vector z, bool modifier)