Ported PrintObject::prepare_infill & combine_infill from Perl to C++.

This commit is contained in:
bubnikv 2017-08-02 14:24:32 +02:00
parent 933d5b261a
commit 777023c7a8
10 changed files with 526 additions and 591 deletions

View file

@ -52,7 +52,6 @@ sub slice {
die "No layers were detected. You might want to repair your STL file(s) or check their size or thickness and retry.\n" die "No layers were detected. You might want to repair your STL file(s) or check their size or thickness and retry.\n"
if !@{$self->layers}; if !@{$self->layers};
$self->set_typed_slices(0);
$self->set_step_done(STEP_SLICE); $self->set_step_done(STEP_SLICE);
} }
@ -81,105 +80,7 @@ sub prepare_infill {
$self->set_step_started(STEP_PREPARE_INFILL); $self->set_step_started(STEP_PREPARE_INFILL);
$self->print->status_cb->(30, "Preparing infill"); $self->print->status_cb->(30, "Preparing infill");
# This will assign a type (top/bottom/internal) to $layerm->slices. $self->_prepare_infill;
# Then the classifcation of $layerm->slices is transfered onto
# the $layerm->fill_surfaces by clipping $layerm->fill_surfaces
# by the cummulative area of the previous $layerm->fill_surfaces.
$self->detect_surfaces_type;
# Mark the object to have the region slices classified (typed, which also means they are split based on whether they are supported, bridging, top layers etc.)
$self->set_typed_slices(1);
# Decide what surfaces are to be filled.
# Here the S_TYPE_TOP / S_TYPE_BOTTOMBRIDGE / S_TYPE_BOTTOM infill is turned to just S_TYPE_INTERNAL if zero top / bottom infill layers are configured.
# Also tiny S_TYPE_INTERNAL surfaces are turned to S_TYPE_INTERNAL_SOLID.
# BOOST_LOG_TRIVIAL(info) << "Preparing fill surfaces...";
$_->prepare_fill_surfaces for map @{$_->regions}, @{$self->layers};
# this will detect bridges and reverse bridges
# and rearrange top/bottom/internal surfaces
# It produces enlarged overlapping bridging areas.
#
# 1) S_TYPE_BOTTOMBRIDGE / S_TYPE_BOTTOM infill is grown by 3mm and clipped by the total infill area. Bridges are detected. The areas may overlap.
# 2) S_TYPE_TOP is grown by 3mm and clipped by the grown bottom areas. The areas may overlap.
# 3) Clip the internal surfaces by the grown top/bottom surfaces.
# 4) Merge surfaces with the same style. This will mostly get rid of the overlaps.
#FIXME This does not likely merge surfaces, which are supported by a material with different colors, but same properties.
$self->process_external_surfaces;
# Add solid fills to ensure the shell vertical thickness.
$self->discover_vertical_shells;
# Debugging output.
if ($SLIC3R_DEBUG_SLICE_PROCESSING) {
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];
$layerm->export_region_slices_to_svg_debug("6_discover_vertical_shells-final");
$layerm->export_region_fill_surfaces_to_svg_debug("6_discover_vertical_shells-final");
} # for each layer
} # for each region
}
# Detect, which fill surfaces are near external layers.
# They will be split in internal and internal-solid surfaces.
# The purpose is to add a configurable number of solid layers to support the TOP surfaces
# and to add a configurable number of solid layers above the BOTTOM / BOTTOMBRIDGE surfaces
# to close these surfaces reliably.
#FIXME Vojtech: Is this a good place to add supporting infills below sloping perimeters?
$self->discover_horizontal_shells;
if ($SLIC3R_DEBUG_SLICE_PROCESSING) {
# Debugging output.
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];
$layerm->export_region_slices_to_svg_debug("7_discover_horizontal_shells-final");
$layerm->export_region_fill_surfaces_to_svg_debug("7_discover_horizontal_shells-final");
} # for each layer
} # for each region
}
# Only active if config->infill_only_where_needed. This step trims the sparse infill,
# so it acts as an internal support. It maintains all other infill types intact.
# Here the internal surfaces and perimeters have to be supported by the sparse infill.
#FIXME The surfaces are supported by a sparse infill, but the sparse infill is only as large as the area to support.
# Likely the sparse infill will not be anchored correctly, so it will not work as intended.
# Also one wishes the perimeters to be supported by a full infill.
$self->clip_fill_surfaces;
if ($SLIC3R_DEBUG_SLICE_PROCESSING) {
# Debugging output.
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];
$layerm->export_region_slices_to_svg_debug("8_clip_surfaces-final");
$layerm->export_region_fill_surfaces_to_svg_debug("8_clip_surfaces-final");
} # for each layer
} # for each region
}
# the following step needs to be done before combination because it may need
# to remove only half of the combined infill
$self->bridge_over_infill;
# combine fill surfaces to honor the "infill every N layers" option
$self->combine_infill;
# Debugging output.
if ($SLIC3R_DEBUG_SLICE_PROCESSING) {
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];
$layerm->export_region_slices_to_svg_debug("9_prepare_infill-final");
$layerm->export_region_fill_surfaces_to_svg_debug("9_prepare_infill-final");
} # for each layer
} # for each region
for (my $i = 0; $i < $self->layer_count; $i++) {
my $layer = $self->get_layer($i);
$layer->export_region_slices_to_svg_debug("9_prepare_infill-final");
$layer->export_region_fill_surfaces_to_svg_debug("9_prepare_infill-final");
} # for each layer
}
$self->set_step_done(STEP_PREPARE_INFILL); $self->set_step_done(STEP_PREPARE_INFILL);
} }
@ -214,430 +115,4 @@ sub generate_support_material {
$self->print->status_cb->(85, $stats); $self->print->status_cb->(85, $stats);
} }
# 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 {
my $self = shift;
return unless $self->config->infill_only_where_needed;
# We only want infill under ceilings; this is almost like an
# internal support material.
# proceed top-down skipping bottom layer
my $upper_internal = [];
for my $layer_id (reverse 1..($self->layer_count - 1)) {
my $layer = $self->get_layer($layer_id);
my $lower_layer = $self->get_layer($layer_id-1);
# detect things that we need to support
my $overhangs = []; # Polygons
# we need to support any solid surface
push @$overhangs, map $_->p,
grep $_->is_solid, map @{$_->fill_surfaces}, @{$layer->regions};
# we also need to support perimeters when there's at least one full
# unsupported loop
{
# get perimeters area as the difference between slices and fill_surfaces
my $perimeters = diff(
[ map @$_, @{$layer->slices} ],
[ map $_->p, map @{$_->fill_surfaces}, @{$layer->regions} ],
);
# only consider the area that is not supported by lower perimeters
$perimeters = intersection(
$perimeters,
[ map $_->p, map @{$_->fill_surfaces}, @{$lower_layer->regions} ],
1,
);
# only consider perimeter areas that are at least one extrusion width thick
#FIXME Offset2 eats out from both sides, while the perimeters are create outside in.
#Should the $pw not be half of the current value?
my $pw = min(map $_->flow(FLOW_ROLE_PERIMETER)->scaled_width, @{$layer->regions});
$perimeters = offset2($perimeters, -$pw, +$pw);
# append such thick perimeters to the areas that need support
push @$overhangs, @$perimeters;
}
# find new internal infill
$upper_internal = my $new_internal = intersection(
[
@$overhangs,
@$upper_internal,
],
[
# our current internal fill boundaries
map $_->p,
grep $_->surface_type == S_TYPE_INTERNAL || $_->surface_type == S_TYPE_INTERNALVOID,
map @{$_->fill_surfaces}, @{$lower_layer->regions}
],
);
# apply new internal infill to regions
foreach my $layerm (@{$lower_layer->regions}) {
my (@internal, @other) = ();
foreach my $surface (map $_->clone, @{$layerm->fill_surfaces}) {
if ($surface->surface_type == S_TYPE_INTERNAL || $surface->surface_type == S_TYPE_INTERNALVOID) {
push @internal, $surface;
} else {
push @other, $surface;
}
}
my @new = map Slic3r::Surface->new(
expolygon => $_,
surface_type => S_TYPE_INTERNAL,
),
@{intersection_ex(
[ map $_->p, @internal ],
$new_internal,
1,
)};
push @other, map Slic3r::Surface->new(
expolygon => $_,
surface_type => S_TYPE_INTERNALVOID,
),
@{diff_ex(
[ map $_->p, @internal ],
$new_internal,
1,
)};
# If there are voids it means that our internal infill is not adjacent to
# perimeters. In this case it would be nice to add a loop around infill to
# make it more robust and nicer. TODO.
$layerm->fill_surfaces->clear;
$layerm->fill_surfaces->append($_) for (@new, @other);
if ($SLIC3R_DEBUG_SLICE_PROCESSING) {
$layerm->export_region_fill_surfaces_to_svg_debug("6_clip_fill_surfaces");
}
}
}
}
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) {
# This is the layer to put the sparse infill in. Mark S_TYPE_INTERNAL surfaces as S_TYPE_INTERNALSOLID or S_TYPE_INTERNALBRIDGE.
# If the sparse infill is not active, the internal surfaces are of type S_TYPE_INTERNAL.
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
# 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 = [
# Surfaces including the area of perimeters. Everything, that is visible from the top / bottom
# (not covered by a layer above / below).
# This does not contain the areas covered by perimeters!
(map $_->p, @{$layerm->slices->filter_by_type($type)}),
# Infill areas (slices without the perimeters).
(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;
NEIGHBOR: for (my $n = ($type == S_TYPE_TOP) ? $i-1 : $i+1;
abs($n - $i) < $solid_layers;
($type == S_TYPE_TOP) ? $n-- : $n++) {
next if $n < 0 || $n >= $self->layer_count;
Slic3r::debugf " looking for neighbors on layer %d...\n", $n;
# Reference to the lower layer of a TOP surface, or an upper layer of a BOTTOM surface.
my $neighbor_layerm = $self->get_layer($n)->regions->[$region_id];
# Reference to the neighbour fill surfaces.
my $neighbor_fill_surfaces = $neighbor_layerm->fill_surfaces;
# Clone because we will use these surfaces even after clearing the collection.
my @neighbor_fill_surfaces = map $_->clone, @$neighbor_fill_surfaces;
# find intersection between neighbor and current layer's surfaces
# intersections have contours and holes
# we update $solid so that we limit the next neighbor layer to the areas that were
# found on this one - in other words, solid shells on one layer (for a given external surface)
# are always a subset of the shells found on the previous shell layer
# this approach allows for DWIM in hollow sloping vases, where we want bottom
# shells to be generated in the base but not in the walls (where there are many
# narrow bottom surfaces): reassigning $solid will consider the 'shadow' of the
# upper perimeter as an obstacle and shell will not be propagated to more upper layers
#FIXME How does it work for S_TYPE_INTERNALBRIDGE? This is set for sparse infill. Likely this does not work.
my $new_internal_solid = $solid = intersection(
$solid,
[ map $_->p, grep { ($_->surface_type == S_TYPE_INTERNAL) || ($_->surface_type == S_TYPE_INTERNALSOLID) } @neighbor_fill_surfaces ],
1,
);
next EXTERNAL if !@$new_internal_solid;
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 $regularized = offset2($new_internal_solid, -$margin, +$margin, JT_MITER, 5);
my $too_narrow = diff(
$new_internal_solid,
$regularized,
1,
);
# Trim the regularized region by the original region.
$new_internal_solid = $solid = intersection(
$new_internal_solid,
$regularized,
) if @$too_narrow;
}
# make sure the new internal solid is wide enough, as it might get collapsed
# when spacing is added in Fill.pm
if ($layerm->region->config->ensure_vertical_shell_thickness) {
# The possible thin sickles of top / bottom surfaces on steeply sloping surfaces touch
# the projections of top / bottom perimeters, therefore they will be sufficiently inflated by
# merging them with the projections of the top / bottom perimeters.
} else {
#FIXME Vojtech: Disable this and you will be sorry.
# https://github.com/prusa3d/Slic3r/issues/26 bottom
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, 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;
}
}
} # foreach my $type (S_TYPE_TOP, S_TYPE_BOTTOM, S_TYPE_BOTTOMBRIDGE)
} # for each layer
} # for each region
# Debugging output.
if ($SLIC3R_DEBUG_SLICE_PROCESSING) {
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];
$layerm->export_region_slices_to_svg_debug("5_discover_horizontal_shells");
$layerm->export_region_fill_surfaces_to_svg_debug("5_discover_horizontal_shells");
} # for each layer
} # for each region
}
}
# combine fill surfaces across layers to honor the "infill every N layers" option
# 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 combine_infill {
my $self = shift;
# define the type used for voids
my %voidtype = (
&S_TYPE_INTERNAL() => S_TYPE_INTERNALVOID,
);
# work on each region separately
for my $region_id (0 .. ($self->print->region_count-1)) {
my $region = $self->print->get_region($region_id);
my $every = $region->config->infill_every_layers;
next unless $every > 1 && $region->config->fill_density > 0;
# limit the number of combined layers to the maximum height allowed by this regions' nozzle
my $nozzle_diameter = min(
$self->print->config->get_at('nozzle_diameter', $region->config->infill_extruder-1),
$self->print->config->get_at('nozzle_diameter', $region->config->solid_infill_extruder-1),
);
# define the combinations
my %combine = (); # layer_idx => number of additional combined lower layers
{
my $current_height = my $layers = 0;
for my $layer_idx (0 .. ($self->layer_count-1)) {
my $layer = $self->get_layer($layer_idx);
next if $layer->id == 0; # skip first print layer (which may not be first layer in array because of raft)
my $height = $layer->height;
# check whether the combination of this layer with the lower layers' buffer
# would exceed max layer height or max combined layer count
if ($current_height + $height >= $nozzle_diameter + epsilon || $layers >= $every) {
# append combination to lower layer
$combine{$layer_idx-1} = $layers;
$current_height = $layers = 0;
}
$current_height += $height;
$layers++;
}
# append lower layers (if any) to uppermost layer
$combine{$self->layer_count-1} = $layers;
}
# loop through layers to which we have assigned layers to combine
for my $layer_idx (sort keys %combine) {
next unless $combine{$layer_idx} > 1;
# get all the LayerRegion objects to be combined
my @layerms = map $self->get_layer($_)->get_region($region_id),
($layer_idx - ($combine{$layer_idx}-1) .. $layer_idx);
# only combine internal infill
for my $type (S_TYPE_INTERNAL) {
# we need to perform a multi-layer intersection, so let's split it in pairs
# initialize the intersection with the candidates of the lowest layer
my $intersection = [ map $_->expolygon, @{$layerms[0]->fill_surfaces->filter_by_type($type)} ];
# start looping from the second layer and intersect the current intersection with it
for my $layerm (@layerms[1 .. $#layerms]) {
$intersection = intersection_ex(
[ map @$_, @$intersection ],
[ map @{$_->expolygon}, @{$layerm->fill_surfaces->filter_by_type($type)} ],
);
}
my $area_threshold = $layerms[0]->infill_area_threshold;
@$intersection = grep $_->area > $area_threshold, @$intersection;
next if !@$intersection;
Slic3r::debugf " combining %d %s regions from layers %d-%d\n",
scalar(@$intersection),
($type == S_TYPE_INTERNAL ? 'internal' : 'internal-solid'),
$layer_idx-($every-1), $layer_idx;
# $intersection now contains the regions that can be combined across the full amount of layers
# so let's remove those areas from all layers
my @intersection_with_clearance = map @{$_->offset(
$layerms[-1]->flow(FLOW_ROLE_SOLID_INFILL)->scaled_width / 2
+ $layerms[-1]->flow(FLOW_ROLE_PERIMETER)->scaled_width / 2
# Because fill areas for rectilinear and honeycomb are grown
# later to overlap perimeters, we need to counteract that too.
+ (($type == S_TYPE_INTERNALSOLID || $region->config->fill_pattern =~ /(rectilinear|grid|line|honeycomb)/)
? $layerms[-1]->flow(FLOW_ROLE_SOLID_INFILL)->scaled_width
: 0)
)}, @$intersection;
foreach my $layerm (@layerms) {
my @this_type = @{$layerm->fill_surfaces->filter_by_type($type)};
my @other_types = map $_->clone, grep $_->surface_type != $type, @{$layerm->fill_surfaces};
my @new_this_type = map Slic3r::Surface->new(expolygon => $_, surface_type => $type),
@{diff_ex(
[ map $_->p, @this_type ],
[ @intersection_with_clearance ],
)};
# apply surfaces back with adjusted depth to the uppermost layer
if ($layerm->layer->id == $self->get_layer($layer_idx)->id) {
push @new_this_type,
map Slic3r::Surface->new(
expolygon => $_,
surface_type => $type,
thickness => sum(map $_->layer->height, @layerms),
thickness_layers => scalar(@layerms),
),
@$intersection;
} else {
# save void surfaces
push @new_this_type,
map Slic3r::Surface->new(expolygon => $_, surface_type => $voidtype{$type}),
@{intersection_ex(
[ map @{$_->expolygon}, @this_type ],
[ @intersection_with_clearance ],
)};
}
$layerm->fill_surfaces->clear;
$layerm->fill_surfaces->append($_) for (@new_this_type, @other_types);
}
}
}
}
}
1; 1;

View file

@ -505,13 +505,8 @@ inline ClipperLib::PolyTree _clipper_do_polytree2(const ClipperLib::ClipType cli
ClipperLib::Paths input_clip = Slic3rMultiPoints_to_ClipperPaths(clip); ClipperLib::Paths input_clip = Slic3rMultiPoints_to_ClipperPaths(clip);
// perform safety offset // perform safety offset
if (safety_offset_) { if (safety_offset_)
if (clipType == ClipperLib::ctUnion) { safety_offset((clipType == ClipperLib::ctUnion) ? &input_subject : &input_clip);
safety_offset(&input_subject);
} else {
safety_offset(&input_clip);
}
}
ClipperLib::Clipper clipper; ClipperLib::Clipper clipper;
clipper.AddPaths(input_subject, ClipperLib::ptSubject, true); clipper.AddPaths(input_subject, ClipperLib::ptSubject, true);
@ -528,8 +523,7 @@ inline ClipperLib::PolyTree _clipper_do_polytree2(const ClipperLib::ClipType cli
return retval; return retval;
} }
ClipperLib::PolyTree ClipperLib::PolyTree _clipper_do_pl(const ClipperLib::ClipType clipType, const Polylines &subject,
_clipper_do_pl(const ClipperLib::ClipType clipType, const Polylines &subject,
const Polygons &clip, const ClipperLib::PolyFillType fillType, const Polygons &clip, const ClipperLib::PolyFillType fillType,
const bool safety_offset_) const bool safety_offset_)
{ {
@ -554,33 +548,25 @@ _clipper_do_pl(const ClipperLib::ClipType clipType, const Polylines &subject,
return retval; return retval;
} }
Polygons Polygons _clipper(ClipperLib::ClipType clipType, const Polygons &subject, const Polygons &clip, bool safety_offset_)
_clipper(ClipperLib::ClipType clipType, const Polygons &subject,
const Polygons &clip, bool safety_offset_)
{ {
return ClipperPaths_to_Slic3rPolygons(_clipper_do<ClipperLib::Paths>(clipType, subject, clip, ClipperLib::pftNonZero, safety_offset_)); return ClipperPaths_to_Slic3rPolygons(_clipper_do<ClipperLib::Paths>(clipType, subject, clip, ClipperLib::pftNonZero, safety_offset_));
} }
ExPolygons ExPolygons _clipper_ex(ClipperLib::ClipType clipType, const Polygons &subject, const Polygons &clip, bool safety_offset_)
_clipper_ex(ClipperLib::ClipType clipType, const Polygons &subject,
const Polygons &clip, bool safety_offset_)
{ {
ClipperLib::PolyTree polytree = _clipper_do_polytree2(clipType, subject, clip, ClipperLib::pftNonZero, safety_offset_); ClipperLib::PolyTree polytree = _clipper_do_polytree2(clipType, subject, clip, ClipperLib::pftNonZero, safety_offset_);
return PolyTreeToExPolygons(polytree); return PolyTreeToExPolygons(polytree);
} }
Polylines Polylines _clipper_pl(ClipperLib::ClipType clipType, const Polylines &subject, const Polygons &clip, bool safety_offset_)
_clipper_pl(ClipperLib::ClipType clipType, const Polylines &subject,
const Polygons &clip, bool safety_offset_)
{ {
ClipperLib::Paths output; ClipperLib::Paths output;
ClipperLib::PolyTreeToPaths(_clipper_do_pl(clipType, subject, clip, ClipperLib::pftNonZero, safety_offset_), output); ClipperLib::PolyTreeToPaths(_clipper_do_pl(clipType, subject, clip, ClipperLib::pftNonZero, safety_offset_), output);
return ClipperPaths_to_Slic3rPolylines(output); return ClipperPaths_to_Slic3rPolylines(output);
} }
Polylines Polylines _clipper_pl(ClipperLib::ClipType clipType, const Polygons &subject, const Polygons &clip, bool safety_offset_)
_clipper_pl(ClipperLib::ClipType clipType, const Polygons &subject,
const Polygons &clip, bool safety_offset_)
{ {
// transform input polygons into polylines // transform input polygons into polylines
Polylines polylines; Polylines polylines;

View file

@ -185,32 +185,27 @@ inline Slic3r::Lines intersection_ln(const Slic3r::Line &subject, const Slic3r::
} }
// union // union
inline Slic3r::Polygons inline Slic3r::Polygons union_(const Slic3r::Polygons &subject, bool safety_offset_ = false)
union_(const Slic3r::Polygons &subject, bool safety_offset_ = false)
{ {
return _clipper(ClipperLib::ctUnion, subject, Slic3r::Polygons(), safety_offset_); return _clipper(ClipperLib::ctUnion, subject, Slic3r::Polygons(), safety_offset_);
} }
inline Slic3r::Polygons inline Slic3r::Polygons union_(const Slic3r::Polygons &subject, const Slic3r::Polygons &subject2, bool safety_offset_ = false)
union_(const Slic3r::Polygons &subject, const Slic3r::Polygons &subject2, bool safety_offset_ = false)
{ {
return _clipper(ClipperLib::ctUnion, subject, subject2, safety_offset_); return _clipper(ClipperLib::ctUnion, subject, subject2, safety_offset_);
} }
inline Slic3r::ExPolygons inline Slic3r::ExPolygons union_ex(const Slic3r::Polygons &subject, bool safety_offset_ = false)
union_ex(const Slic3r::Polygons &subject, bool safety_offset_ = false)
{ {
return _clipper_ex(ClipperLib::ctUnion, subject, Slic3r::Polygons(), safety_offset_); return _clipper_ex(ClipperLib::ctUnion, subject, Slic3r::Polygons(), safety_offset_);
} }
inline Slic3r::ExPolygons inline Slic3r::ExPolygons union_ex(const Slic3r::ExPolygons &subject, bool safety_offset_ = false)
union_ex(const Slic3r::ExPolygons &subject, bool safety_offset_ = false)
{ {
return _clipper_ex(ClipperLib::ctUnion, to_polygons(subject), Slic3r::Polygons(), safety_offset_); return _clipper_ex(ClipperLib::ctUnion, to_polygons(subject), Slic3r::Polygons(), safety_offset_);
} }
inline Slic3r::ExPolygons inline Slic3r::ExPolygons union_ex(const Slic3r::Surfaces &subject, bool safety_offset_ = false)
union_ex(const Slic3r::Surfaces &subject, bool safety_offset_ = false)
{ {
return _clipper_ex(ClipperLib::ctUnion, to_polygons(subject), Slic3r::Polygons(), safety_offset_); return _clipper_ex(ClipperLib::ctUnion, to_polygons(subject), Slic3r::Polygons(), safety_offset_);
} }

View file

@ -11,6 +11,7 @@
#include "FillPlanePath.hpp" #include "FillPlanePath.hpp"
#include "FillRectilinear.hpp" #include "FillRectilinear.hpp"
#include "FillRectilinear2.hpp" #include "FillRectilinear2.hpp"
#include "FillRectilinear3.hpp"
namespace Slic3r { namespace Slic3r {
@ -31,7 +32,7 @@ Fill* Fill::new_from_type(const InfillPattern type)
case ipArchimedeanChords: return new FillArchimedeanChords(); case ipArchimedeanChords: return new FillArchimedeanChords();
case ipHilbertCurve: return new FillHilbertCurve(); case ipHilbertCurve: return new FillHilbertCurve();
case ipOctagramSpiral: return new FillOctagramSpiral(); case ipOctagramSpiral: return new FillOctagramSpiral();
default: CONFESS("unknown type"); return NULL; default: CONFESS("unknown type"); return nullptr;
} }
} }
@ -39,7 +40,7 @@ Fill* Fill::new_from_type(const std::string &type)
{ {
static t_config_enum_values enum_keys_map = ConfigOptionEnum<InfillPattern>::get_enum_values(); static t_config_enum_values enum_keys_map = ConfigOptionEnum<InfillPattern>::get_enum_values();
t_config_enum_values::const_iterator it = enum_keys_map.find(type); t_config_enum_values::const_iterator it = enum_keys_map.find(type);
return (it == enum_keys_map.end()) ? NULL : new_from_type(InfillPattern(it->second)); return (it == enum_keys_map.end()) ? nullptr : new_from_type(InfillPattern(it->second));
} }
Polylines Fill::fill_surface(const Surface *surface, const FillParams &params) Polylines Fill::fill_surface(const Surface *surface, const FillParams &params)

View file

@ -35,9 +35,7 @@ public:
Point(int _x, int _y): x(_x), y(_y) {}; Point(int _x, int _y): x(_x), y(_y) {};
Point(long long _x, long long _y): x(coord_t(_x)), y(coord_t(_y)) {}; // for Clipper Point(long long _x, long long _y): x(coord_t(_x)), y(coord_t(_y)) {}; // for Clipper
Point(double x, double y); Point(double x, double y);
static Point new_scale(coordf_t x, coordf_t y) { static Point new_scale(coordf_t x, coordf_t y) { return Point(coord_t(scale_(x)), coord_t(scale_(y))); }
return Point(scale_(x), scale_(y));
};
bool operator==(const Point& rhs) const { return this->x == rhs.x && this->y == rhs.y; } bool operator==(const Point& rhs) const { return this->x == rhs.x && this->y == rhs.y; }
bool operator!=(const Point& rhs) const { return ! (*this == rhs); } bool operator!=(const Point& rhs) const { return ! (*this == rhs); }
@ -185,6 +183,7 @@ class Point3 : public Point
public: public:
coord_t z; coord_t z;
explicit Point3(coord_t _x = 0, coord_t _y = 0, coord_t _z = 0): Point(_x, _y), z(_z) {}; explicit Point3(coord_t _x = 0, coord_t _y = 0, coord_t _z = 0): Point(_x, _y), z(_z) {};
static Point3 new_scale(coordf_t x, coordf_t y, coordf_t z) { return Point3(coord_t(scale_(x)), coord_t(scale_(y)), coord_t(scale_(z))); }
}; };
std::ostream& operator<<(std::ostream &stm, const Pointf &pointf); std::ostream& operator<<(std::ostream &stm, const Pointf &pointf);

View file

@ -191,6 +191,7 @@ public:
void _slice(); void _slice();
std::string _fix_slicing_errors(); std::string _fix_slicing_errors();
void _simplify_slices(double distance); void _simplify_slices(double distance);
void _prepare_infill();
bool has_support_material() const; bool has_support_material() const;
void detect_surfaces_type(); void detect_surfaces_type();
void process_external_surfaces(); void process_external_surfaces();
@ -198,6 +199,9 @@ public:
void bridge_over_infill(); void bridge_over_infill();
void _make_perimeters(); void _make_perimeters();
void _infill(); void _infill();
void clip_fill_surfaces();
void discover_horizontal_shells();
void combine_infill();
void _generate_support_material(); void _generate_support_material();
private: private:

View file

@ -68,7 +68,7 @@ PrintConfigDef::PrintConfigDef()
def = this->add("bridge_angle", coFloat); def = this->add("bridge_angle", coFloat);
def->label = "Bridging angle"; def->label = "Bridging angle";
def->category = "Infill"; def->category = "Infill";
def->tooltip = "Bridging angle override. If left to zero, the bridging angle will be calculated automatically. Otherwise the provided angle will be used for all bridges, use 180° for zero angle."; def->tooltip = "Bridging angle override. If left to zero, the bridging angle will be calculated automatically. Otherwise the provided angle will be used for all bridges. Use 180° for zero angle.";
def->sidetext = "°"; def->sidetext = "°";
def->cli = "bridge-angle=f"; def->cli = "bridge-angle=f";
def->min = 0; def->min = 0;

View file

@ -3,6 +3,7 @@
#include "ClipperUtils.hpp" #include "ClipperUtils.hpp"
#include "Geometry.hpp" #include "Geometry.hpp"
#include "SupportMaterial.hpp" #include "SupportMaterial.hpp"
#include "Surface.hpp"
#include <utility> #include <utility>
#include <boost/log/trivial.hpp> #include <boost/log/trivial.hpp>
@ -32,8 +33,8 @@
namespace Slic3r { namespace Slic3r {
PrintObject::PrintObject(Print* print, ModelObject* model_object, const BoundingBoxf3 &modobj_bbox) PrintObject::PrintObject(Print* print, ModelObject* model_object, const BoundingBoxf3 &modobj_bbox) :
: typed_slices(false), typed_slices(false),
_print(print), _print(print),
_model_object(model_object), _model_object(model_object),
layer_height_profile_valid(false) layer_height_profile_valid(false)
@ -46,12 +47,10 @@ PrintObject::PrintObject(Print* print, ModelObject* model_object, const Bounding
// don't assume it's already aligned and we don't alter the original position in model. // don't assume it's already aligned and we don't alter the original position in model.
// We store the XY translation so that we can place copies correctly in the output G-code // We store the XY translation so that we can place copies correctly in the output G-code
// (copies are expressed in G-code coordinates and this translation is not publicly exposed). // (copies are expressed in G-code coordinates and this translation is not publicly exposed).
this->_copies_shift = Point( this->_copies_shift = Point::new_scale(modobj_bbox.min.x, modobj_bbox.min.y);
scale_(modobj_bbox.min.x), scale_(modobj_bbox.min.y));
// Scale the object size and store it // Scale the object size and store it
Pointf3 size = modobj_bbox.size(); Pointf3 size = modobj_bbox.size();
this->size = Point3(scale_(size.x), scale_(size.y), scale_(size.z)); this->size = Point3::new_scale(size.x, size.y, size.z);
} }
this->reload_model_instances(); this->reload_model_instances();
@ -283,6 +282,105 @@ bool PrintObject::has_support_material() const
|| this->config.support_material_enforce_layers > 0; || this->config.support_material_enforce_layers > 0;
} }
void PrintObject::_prepare_infill()
{
// This will assign a type (top/bottom/internal) to $layerm->slices.
// Then the classifcation of $layerm->slices is transfered onto
// the $layerm->fill_surfaces by clipping $layerm->fill_surfaces
// by the cummulative area of the previous $layerm->fill_surfaces.
this->detect_surfaces_type();
// Decide what surfaces are to be filled.
// Here the S_TYPE_TOP / S_TYPE_BOTTOMBRIDGE / S_TYPE_BOTTOM infill is turned to just S_TYPE_INTERNAL if zero top / bottom infill layers are configured.
// Also tiny S_TYPE_INTERNAL surfaces are turned to S_TYPE_INTERNAL_SOLID.
BOOST_LOG_TRIVIAL(info) << "Preparing fill surfaces...";
for (auto *layer : this->layers)
for (auto *region : layer->regions)
region->prepare_fill_surfaces();
// this will detect bridges and reverse bridges
// and rearrange top/bottom/internal surfaces
// It produces enlarged overlapping bridging areas.
//
// 1) S_TYPE_BOTTOMBRIDGE / S_TYPE_BOTTOM infill is grown by 3mm and clipped by the total infill area. Bridges are detected. The areas may overlap.
// 2) S_TYPE_TOP is grown by 3mm and clipped by the grown bottom areas. The areas may overlap.
// 3) Clip the internal surfaces by the grown top/bottom surfaces.
// 4) Merge surfaces with the same style. This will mostly get rid of the overlaps.
//FIXME This does not likely merge surfaces, which are supported by a material with different colors, but same properties.
this->process_external_surfaces();
// Add solid fills to ensure the shell vertical thickness.
this->discover_vertical_shells();
// Debugging output.
#ifdef SLIC3R_DEBUG_SLICE_PROCESSING
for (size_t region_id = 0; region_id < this->print()->regions.size(); ++ region_id)
for (const Layer *layer : this->layers) {
LayerRegion *layerm = layer->regions[region_id];
layerm->export_region_slices_to_svg_debug("6_discover_vertical_shells-final");
layerm->export_region_fill_surfaces_to_svg_debug("6_discover_vertical_shells-final");
} // for each layer
} // for each region
#endif /* SLIC3R_DEBUG_SLICE_PROCESSING */
// Detect, which fill surfaces are near external layers.
// They will be split in internal and internal-solid surfaces.
// The purpose is to add a configurable number of solid layers to support the TOP surfaces
// and to add a configurable number of solid layers above the BOTTOM / BOTTOMBRIDGE surfaces
// to close these surfaces reliably.
//FIXME Vojtech: Is this a good place to add supporting infills below sloping perimeters?
this->discover_horizontal_shells();
#ifdef SLIC3R_DEBUG_SLICE_PROCESSING
for (size_t region_id = 0; region_id < this->print()->regions.size(); ++ region_id)
for (const Layer *layer : this->layers) {
LayerRegion *layerm = layer->regions[region_id];
layerm->export_region_slices_to_svg_debug("7_discover_horizontal_shells-final");
layerm->export_region_fill_surfaces_to_svg_debug("7_discover_horizontal_shells-final");
} // for each layer
} // for each region
#endif /* SLIC3R_DEBUG_SLICE_PROCESSING */
// Only active if config->infill_only_where_needed. This step trims the sparse infill,
// so it acts as an internal support. It maintains all other infill types intact.
// Here the internal surfaces and perimeters have to be supported by the sparse infill.
//FIXME The surfaces are supported by a sparse infill, but the sparse infill is only as large as the area to support.
// Likely the sparse infill will not be anchored correctly, so it will not work as intended.
// Also one wishes the perimeters to be supported by a full infill.
this->clip_fill_surfaces();
#ifdef SLIC3R_DEBUG_SLICE_PROCESSING
for (size_t region_id = 0; region_id < this->print()->regions.size(); ++ region_id)
for (const Layer *layer : this->layers) {
LayerRegion *layerm = layer->regions[region_id];
layerm->export_region_slices_to_svg_debug("8_clip_surfaces-final");
layerm->export_region_fill_surfaces_to_svg_debug("8_clip_surfaces-final");
} // for each layer
} // for each region
#endif /* SLIC3R_DEBUG_SLICE_PROCESSING */
// the following step needs to be done before combination because it may need
// to remove only half of the combined infill
this->bridge_over_infill();
// combine fill surfaces to honor the "infill every N layers" option
this->combine_infill();
#ifdef SLIC3R_DEBUG_SLICE_PROCESSING
for (size_t region_id = 0; region_id < this->print()->regions.size(); ++ region_id)
for (const Layer *layer : this->layers) {
LayerRegion *layerm = layer->regions[region_id];
layerm->export_region_slices_to_svg_debug("9_prepare_infill-final");
layerm->export_region_fill_surfaces_to_svg_debug("9_prepare_infill-final");
} // for each layer
} // for each region
for (const Layer *layer : this->layers) {
layer->export_region_slices_to_svg_debug("9_prepare_infill-final");
layer->export_region_fill_surfaces_to_svg_debug("9_prepare_infill-final");
} // for each layer
#endif /* SLIC3R_DEBUG_SLICE_PROCESSING */
}
// This function analyzes slices of a region (SurfaceCollection slices). // This function analyzes slices of a region (SurfaceCollection slices).
// Each region slice (instance of Surface) is analyzed, whether it is supported or whether it is the top surface. // Each region slice (instance of Surface) is analyzed, whether it is supported or whether it is the top surface.
// Initially all slices are of type stInternal. // Initially all slices are of type stInternal.
@ -334,8 +432,8 @@ void PrintObject::detect_surfaces_type()
LayerRegion *layerm = layer->get_region(idx_region); LayerRegion *layerm = layer->get_region(idx_region);
// comparison happens against the *full* slices (considering all regions) // comparison happens against the *full* slices (considering all regions)
// unless internal shells are requested // unless internal shells are requested
Layer *upper_layer = idx_layer + 1 < this->layer_count() ? this->get_layer(idx_layer + 1) : nullptr; Layer *upper_layer = (idx_layer + 1 < this->layer_count()) ? this->layers[idx_layer + 1] : nullptr;
Layer *lower_layer = idx_layer > 0 ? this->get_layer(idx_layer - 1) : nullptr; Layer *lower_layer = (idx_layer > 0) ? this->layers[idx_layer - 1] : nullptr;
// collapse very narrow parts (using the safety offset in the diff is not enough) // collapse very narrow parts (using the safety offset in the diff is not enough)
float offset = layerm->flow(frExternalPerimeter).scaled_width() / 10.f; float offset = layerm->flow(frExternalPerimeter).scaled_width() / 10.f;
@ -473,7 +571,10 @@ void PrintObject::detect_surfaces_type()
} // for each layer of a region } // for each layer of a region
}); });
BOOST_LOG_TRIVIAL(debug) << "Detecting solid surfaces for region " << idx_region << " - clipping in parallel - end"; BOOST_LOG_TRIVIAL(debug) << "Detecting solid surfaces for region " << idx_region << " - clipping in parallel - end";
} // for each $self->print->region_count } // for each this->print->region_count
// Mark the object to have the region slices classified (typed, which also means they are split based on whether they are supported, bridging, top layers etc.)
this->typed_slices = true;
} }
void PrintObject::process_external_surfaces() void PrintObject::process_external_surfaces()
@ -669,7 +770,6 @@ void PrintObject::discover_vertical_shells()
ExPolygons shell_ex; ExPolygons shell_ex;
#endif /* SLIC3R_DEBUG_SLICE_PROCESSING */ #endif /* SLIC3R_DEBUG_SLICE_PROCESSING */
float min_perimeter_infill_spacing = float(infill_line_spacing) * 1.05f; float min_perimeter_infill_spacing = float(infill_line_spacing) * 1.05f;
if (1)
{ {
PROFILE_BLOCK(discover_vertical_shells_region_layer_collect); PROFILE_BLOCK(discover_vertical_shells_region_layer_collect);
#if 0 #if 0
@ -699,7 +799,6 @@ void PrintObject::discover_vertical_shells()
for (int n = (int)idx_layer - n_extra_bottom_layers; n <= (int)idx_layer + n_extra_top_layers; ++ n) for (int n = (int)idx_layer - n_extra_bottom_layers; n <= (int)idx_layer + n_extra_top_layers; ++ n)
if (n >= 0 && n < (int)this->layers.size()) { if (n >= 0 && n < (int)this->layers.size()) {
Layer &neighbor_layer = *this->layers[n]; Layer &neighbor_layer = *this->layers[n];
LayerRegion &neighbor_region = *neighbor_layer.get_region(int(idx_region));
const DiscoverVerticalShellsCacheEntry &cache = cache_top_botom_regions[n]; const DiscoverVerticalShellsCacheEntry &cache = cache_top_botom_regions[n];
if (hole_first) { if (hole_first) {
hole_first = false; hole_first = false;
@ -880,8 +979,7 @@ void PrintObject::discover_vertical_shells()
/* This method applies bridge flow to the first internal solid layer above /* This method applies bridge flow to the first internal solid layer above
sparse infill */ sparse infill */
void void PrintObject::bridge_over_infill()
PrintObject::bridge_over_infill()
{ {
BOOST_LOG_TRIVIAL(info) << "Bridge over infill..."; BOOST_LOG_TRIVIAL(info) << "Bridge over infill...";
@ -906,7 +1004,7 @@ PrintObject::bridge_over_infill()
if (layer_it == this->layers.begin()) continue; if (layer_it == this->layers.begin()) continue;
Layer* layer = *layer_it; Layer* layer = *layer_it;
LayerRegion* layerm = layer->get_region(region_id); LayerRegion* layerm = layer->regions[region_id];
// extract the stInternalSolid surfaces that might be transformed into bridges // extract the stInternalSolid surfaces that might be transformed into bridges
Polygons internal_solid; Polygons internal_solid;
@ -921,7 +1019,7 @@ PrintObject::bridge_over_infill()
// iterate through lower layers spanned by bridge_flow // iterate through lower layers spanned by bridge_flow
double bottom_z = layer->print_z - bridge_flow.height; double bottom_z = layer->print_z - bridge_flow.height;
for (int i = (layer_it - this->layers.begin()) - 1; i >= 0; --i) { for (int i = int(layer_it - this->layers.begin()) - 1; i >= 0; --i) {
const Layer* lower_layer = this->layers[i]; const Layer* lower_layer = this->layers[i];
// stop iterating if layer is lower than bottom_z // stop iterating if layer is lower than bottom_z
@ -941,7 +1039,7 @@ PrintObject::bridge_over_infill()
// therefore it may create 1) gaps, and 2) sharp corners, which are outside the original contour. // therefore it may create 1) gaps, and 2) sharp corners, which are outside the original contour.
// The gaps will be filled by a separate region, which makes the infill less stable and it takes longer. // The gaps will be filled by a separate region, which makes the infill less stable and it takes longer.
{ {
double min_width = bridge_flow.scaled_width() * 3; float min_width = float(bridge_flow.scaled_width()) * 3.f;
to_bridge_pp = offset2(to_bridge_pp, -min_width, +min_width); to_bridge_pp = offset2(to_bridge_pp, -min_width, +min_width);
} }
@ -1069,6 +1167,8 @@ void PrintObject::_slice()
{ {
BOOST_LOG_TRIVIAL(info) << "Slicing objects..."; BOOST_LOG_TRIVIAL(info) << "Slicing objects...";
this->typed_slices = false;
#if 0 #if 0
// Disable parallelization for debugging purposes. // Disable parallelization for debugging purposes.
static tbb::task_scheduler_init *tbb_init = nullptr; static tbb::task_scheduler_init *tbb_init = nullptr;
@ -1221,7 +1321,7 @@ std::vector<ExPolygons> PrintObject::_slice_region(size_t region_id, const std::
// consider the first one // consider the first one
this->model_object()->instances.front()->transform_mesh(&mesh, true); this->model_object()->instances.front()->transform_mesh(&mesh, true);
// align mesh to Z = 0 (it should be already aligned actually) and apply XY shift // align mesh to Z = 0 (it should be already aligned actually) and apply XY shift
mesh.translate(- unscale(this->_copies_shift.x), - unscale(this->_copies_shift.y), -this->model_object()->bounding_box().min.z); mesh.translate(- float(unscale(this->_copies_shift.x)), - float(unscale(this->_copies_shift.y)), -float(this->model_object()->bounding_box().min.z));
// perform actual slicing // perform actual slicing
TriangleMeshSlicer mslicer(&mesh); TriangleMeshSlicer mslicer(&mesh);
mslicer.slice(z, &layers); mslicer.slice(z, &layers);
@ -1462,6 +1562,387 @@ void PrintObject::_infill()
this->state.set_done(posInfill); this->state.set_done(posInfill);
} }
// Only active if config->infill_only_where_needed. This step trims the sparse infill,
// so it acts as an internal support. It maintains all other infill types intact.
// Here the internal surfaces and perimeters have to be supported by the sparse infill.
//FIXME The surfaces are supported by a sparse infill, but the sparse infill is only as large as the area to support.
// Likely the sparse infill will not be anchored correctly, so it will not work as intended.
// Also one wishes the perimeters to be supported by a full infill.
// 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.
void PrintObject::clip_fill_surfaces()
{
if (! this->config.infill_only_where_needed.value ||
! std::any_of(this->print()->regions.begin(), this->print()->regions.end(),
[](const PrintRegion *region) { return region->config.fill_density > 0; }))
return;
// We only want infill under ceilings; this is almost like an
// internal support material.
// Proceed top-down, skipping the bottom layer.
Polygons upper_internal;
for (int layer_id = int(this->layers.size()) - 1; layer_id > 0; -- layer_id) {
Layer *layer = this->layers[layer_id];
Layer *lower_layer = this->layers[layer_id - 1];
// Detect things that we need to support.
// Cummulative slices.
Polygons slices;
for (const ExPolygon &expoly : layer->slices.expolygons)
polygons_append(slices, to_polygons(expoly));
// Cummulative fill surfaces.
Polygons fill_surfaces;
// Solid surfaces to be supported.
Polygons overhangs;
for (const LayerRegion *layerm : layer->regions)
for (const Surface &surface : layerm->fill_surfaces.surfaces) {
Polygons polygons = to_polygons(surface.expolygon);
if (surface.is_solid())
polygons_append(overhangs, polygons);
polygons_append(fill_surfaces, std::move(polygons));
}
Polygons lower_layer_fill_surfaces;
Polygons lower_layer_internal_surfaces;
for (const LayerRegion *layerm : lower_layer->regions)
for (const Surface &surface : layerm->fill_surfaces.surfaces) {
Polygons polygons = to_polygons(surface.expolygon);
if (surface.surface_type == stInternal || surface.surface_type == stInternalVoid)
polygons_append(lower_layer_internal_surfaces, polygons);
polygons_append(lower_layer_fill_surfaces, std::move(polygons));
}
// We also need to support perimeters when there's at least one full unsupported loop
{
// Get perimeters area as the difference between slices and fill_surfaces
// Only consider the area that is not supported by lower perimeters
Polygons perimeters = intersection(diff(slices, fill_surfaces), lower_layer_fill_surfaces);
// Only consider perimeter areas that are at least one extrusion width thick.
//FIXME Offset2 eats out from both sides, while the perimeters are create outside in.
//Should the pw not be half of the current value?
float pw = FLT_MAX;
for (const LayerRegion *layerm : layer->regions)
pw = std::min<float>(pw, layerm->flow(frPerimeter).scaled_width());
// Append such thick perimeters to the areas that need support
polygons_append(overhangs, offset2(perimeters, -pw, +pw));
}
// Find new internal infill.
polygons_append(overhangs, std::move(upper_internal));
upper_internal = intersection(overhangs, lower_layer_internal_surfaces);
// Apply new internal infill to regions.
for (LayerRegion *layerm : lower_layer->regions) {
if (layerm->region()->config.fill_density.value == 0)
continue;
SurfaceType internal_surface_types[] = { stInternal, stInternalVoid };
Polygons internal;
for (Surface &surface : layerm->fill_surfaces.surfaces)
if (surface.surface_type == stInternal || surface.surface_type == stInternalVoid)
polygons_append(internal, std::move(surface.expolygon));
layerm->fill_surfaces.remove_types(internal_surface_types, 2);
layerm->fill_surfaces.append(intersection_ex(internal, upper_internal, true), stInternal);
layerm->fill_surfaces.append(diff_ex (internal, upper_internal, true), stInternalVoid);
// If there are voids it means that our internal infill is not adjacent to
// perimeters. In this case it would be nice to add a loop around infill to
// make it more robust and nicer. TODO.
#ifdef SLIC3R_DEBUG_SLICE_PROCESSING
layerm->export_region_fill_surfaces_to_svg_debug("6_clip_fill_surfaces");
#endif
}
}
}
void PrintObject::discover_horizontal_shells()
{
BOOST_LOG_TRIVIAL(trace) << "discover_horizontal_shells()";
for (size_t region_id = 0; region_id < this->print()->regions.size(); ++ region_id) {
for (int i = 0; i < int(this->layers.size()); ++ i) {
LayerRegion *layerm = this->layers[i]->regions[region_id];
PrintRegionConfig &region_config = layerm->region()->config;
if (region_config.solid_infill_every_layers.value > 0 && region_config.fill_density.value > 0 &&
(i % region_config.solid_infill_every_layers) == 0) {
// Insert a solid internal layer. Mark stInternal surfaces as stInternalSolid or stInternalBridge.
SurfaceType type = (region_config.fill_density == 100) ? stInternalSolid : stInternalBridge;
for (Surface &surface : layerm->fill_surfaces.surfaces)
if (surface.surface_type == stInternal)
surface.surface_type = type;
}
// If ensure_vertical_shell_thickness, then the rest has already been performed by discover_vertical_shells().
if (region_config.ensure_vertical_shell_thickness.value)
continue;
for (int idx_surface_type = 0; idx_surface_type < 3; ++ idx_surface_type) {
SurfaceType type = (idx_surface_type == 0) ? stTop : (idx_surface_type == 1) ? stBottom : stBottomBridge;
// 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.
// Surfaces including the area of perimeters. Everything, that is visible from the top / bottom
// (not covered by a layer above / below).
// This does not contain the areas covered by perimeters!
Polygons solid;
for (const Surface &surface : layerm->slices.surfaces)
if (surface.surface_type == type)
polygons_append(solid, to_polygons(surface.expolygon));
// Infill areas (slices without the perimeters).
for (const Surface &surface : layerm->fill_surfaces.surfaces)
if (surface.surface_type == type)
polygons_append(solid, to_polygons(surface.expolygon));
if (solid.empty())
continue;
// Slic3r::debugf "Layer %d has %s surfaces\n", $i, ($type == S_TYPE_TOP) ? 'top' : 'bottom';
size_t solid_layers = (type == stTop) ? region_config.top_solid_layers.value : region_config.bottom_solid_layers.value;
for (int n = (type == stTop) ? i-1 : i+1; std::abs(n - i) < solid_layers; (type == stTop) ? -- n : ++ n) {
if (n < 0 || n >= int(this->layers.size()))
continue;
// Slic3r::debugf " looking for neighbors on layer %d...\n", $n;
// Reference to the lower layer of a TOP surface, or an upper layer of a BOTTOM surface.
LayerRegion *neighbor_layerm = this->layers[n]->regions[region_id];
// find intersection between neighbor and current layer's surfaces
// intersections have contours and holes
// we update $solid so that we limit the next neighbor layer to the areas that were
// found on this one - in other words, solid shells on one layer (for a given external surface)
// are always a subset of the shells found on the previous shell layer
// this approach allows for DWIM in hollow sloping vases, where we want bottom
// shells to be generated in the base but not in the walls (where there are many
// narrow bottom surfaces): reassigning $solid will consider the 'shadow' of the
// upper perimeter as an obstacle and shell will not be propagated to more upper layers
//FIXME How does it work for S_TYPE_INTERNALBRIDGE? This is set for sparse infill. Likely this does not work.
Polygons new_internal_solid;
{
Polygons internal;
for (const Surface &surface : neighbor_layerm->fill_surfaces.surfaces)
if (surface.surface_type == stInternal || surface.surface_type == stInternalSolid)
polygons_append(internal, to_polygons(surface.expolygon));
new_internal_solid = intersection(solid, internal, 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.value == 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.
goto EXTERNAL;
} else {
// If we have internal infill, we can generate internal solid shells freely.
continue;
}
}
if (region_config.fill_density.value == 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!)
float margin = float(neighbor_layerm->flow(frExternalPerimeter).scaled_width());
Polygons too_narrow = diff(
new_internal_solid,
offset2(new_internal_solid, -margin, +margin, jtMiter, 5),
true);
// Trim the regularized region by the original region.
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 Fill.pm
{
//FIXME Vojtech: Disable this and you will be sorry.
// https://github.com/prusa3d/Slic3r/issues/26 bottom
float margin = 3.f * layerm->flow(frSolidInfill).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.
Polygons too_narrow = diff(
new_internal_solid,
offset2(new_internal_solid, -margin, +margin, 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 internal;
for (const Surface &surface : neighbor_layerm->fill_surfaces.surfaces)
if (surface.is_internal() && !surface.is_bridge())
polygons_append(internal, to_polygons(surface.expolygon));
polygons_append(new_internal_solid,
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.)
internal));
solid = new_internal_solid;
}
}
// internal-solid are the union of the existing internal-solid surfaces
// and new ones
SurfaceCollection backup = std::move(neighbor_layerm->fill_surfaces);
polygons_append(new_internal_solid, to_polygons(backup.filter_by_type(stInternalSolid)));
ExPolygons internal_solid = union_ex(new_internal_solid, false);
// assign new internal-solid surfaces to layer
neighbor_layerm->fill_surfaces.set(internal_solid, stInternalSolid);
// subtract intersections from layer surfaces to get resulting internal surfaces
Polygons polygons_internal = to_polygons(std::move(internal_solid));
ExPolygons internal = diff_ex(
to_polygons(backup.filter_by_type(stInternal)),
polygons_internal,
true);
// assign resulting internal surfaces to layer
neighbor_layerm->fill_surfaces.append(internal, stInternal);
polygons_append(polygons_internal, to_polygons(std::move(internal)));
// assign top and bottom surfaces to layer
SurfaceType surface_types_solid[] = { stTop, stBottom, stBottomBridge };
backup.keep_types(surface_types_solid, 3);
std::vector<SurfacesPtr> top_bottom_groups;
backup.group(&top_bottom_groups);
for (SurfacesPtr &group : top_bottom_groups)
neighbor_layerm->fill_surfaces.append(
diff_ex(to_polygons(group), polygons_internal),
group.front()->surface_type);
}
EXTERNAL:;
} // foreach type (stTop, stBottom, stBottomBridge)
} // for each layer
} // for each region
#ifdef SLIC3R_DEBUG_SLICE_PROCESSING
for (size_t region_id = 0; region_id < this->print()->regions.size(); ++ region_id)
for (const Layer *layer : this->layers) {
const LayerRegion *layerm = layer->regions[region_id];
layerm->export_region_slices_to_svg_debug("5_discover_horizontal_shells");
layerm->export_region_fill_surfaces_to_svg_debug("5_discover_horizontal_shells");
} // for each layer
} // for each region
#endif /* SLIC3R_DEBUG_SLICE_PROCESSING */
}
// combine fill surfaces across layers to honor the "infill every N layers" option
// 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.
void PrintObject::combine_infill()
{
// Work on each region separately.
for (size_t region_id = 0; region_id < this->print()->regions.size(); ++ region_id) {
const PrintRegion *region = this->print()->regions[region_id];
const int every = region->config.infill_every_layers.value;
if (every < 2 || region->config.fill_density == 0.)
continue;
// Limit the number of combined layers to the maximum height allowed by this regions' nozzle.
//FIXME limit the layer height to max_layer_height
double nozzle_diameter = std::min(
this->print()->config.nozzle_diameter.get_at(region->config.infill_extruder.value - 1),
this->print()->config.nozzle_diameter.get_at(region->config.solid_infill_extruder.value - 1));
// define the combinations
std::vector<size_t> combine(this->layers.size(), 0);
{
double current_height = 0.;
size_t num_layers = 0;
for (size_t layer_idx = 0; layer_idx < this->layers.size(); ++ layer_idx) {
const Layer *layer = this->layers[layer_idx];
if (layer->id() == 0)
// Skip first print layer (which may not be first layer in array because of raft).
continue;
// Check whether the combination of this layer with the lower layers' buffer
// would exceed max layer height or max combined layer count.
if (current_height + layer->height >= nozzle_diameter + EPSILON || num_layers >= every) {
// Append combination to lower layer.
combine[layer_idx - 1] = num_layers;
current_height = 0.;
num_layers = 0;
}
current_height += layer->height;
++ num_layers;
}
// Append lower layers (if any) to uppermost layer.
combine[this->layers.size() - 1] = num_layers;
}
// loop through layers to which we have assigned layers to combine
for (size_t layer_idx = 0; layer_idx < this->layers.size(); ++ layer_idx) {
size_t num_layers = combine[layer_idx];
if (num_layers <= 1)
continue;
// Get all the LayerRegion objects to be combined.
std::vector<LayerRegion*> layerms;
layerms.reserve(num_layers);
for (size_t i = layer_idx + 1 - num_layers; i <= layer_idx; ++ i)
layerms.emplace_back(this->layers[i]->regions[region_id]);
// We need to perform a multi-layer intersection, so let's split it in pairs.
// Initialize the intersection with the candidates of the lowest layer.
ExPolygons intersection = to_expolygons(layerms.front()->fill_surfaces.filter_by_type(stInternal));
// Start looping from the second layer and intersect the current intersection with it.
for (size_t i = 1; i < layerms.size(); ++ i)
intersection = intersection_ex(
to_polygons(intersection),
to_polygons(layerms[i]->fill_surfaces.filter_by_type(stInternal)),
false);
double area_threshold = layerms.front()->infill_area_threshold();
if (! intersection.empty() && area_threshold > 0.)
intersection.erase(std::remove_if(intersection.begin(), intersection.end(),
[area_threshold](const ExPolygon &expoly) { return expoly.area() <= area_threshold; }),
intersection.end());
if (intersection.empty())
continue;
// Slic3r::debugf " combining %d %s regions from layers %d-%d\n",
// scalar(@$intersection),
// ($type == S_TYPE_INTERNAL ? 'internal' : 'internal-solid'),
// $layer_idx-($every-1), $layer_idx;
// intersection now contains the regions that can be combined across the full amount of layers,
// so let's remove those areas from all layers.
Polygons intersection_with_clearance;
intersection_with_clearance.reserve(intersection.size());
float clearance_offset =
0.5f * layerms.back()->flow(frPerimeter).scaled_width() +
// Because fill areas for rectilinear and honeycomb are grown
// later to overlap perimeters, we need to counteract that too.
((region->config.fill_pattern == ipRectilinear ||
region->config.fill_pattern == ipGrid ||
region->config.fill_pattern == ipLine ||
region->config.fill_pattern == ipHoneycomb) ? 1.5f : 0.5f) *
layerms.back()->flow(frSolidInfill).scaled_width();
for (ExPolygon &expoly : intersection)
polygons_append(intersection_with_clearance, offset(expoly, clearance_offset));
for (LayerRegion *layerm : layerms) {
Polygons internal = to_polygons(layerm->fill_surfaces.filter_by_type(stInternal));
layerm->fill_surfaces.remove_type(stInternal);
layerm->fill_surfaces.append(diff_ex(internal, intersection_with_clearance, false), stInternal);
if (layerm == layerms.back()) {
// Apply surfaces back with adjusted depth to the uppermost layer.
Surface templ(stInternal, ExPolygon());
templ.thickness = 0.;
for (LayerRegion *layerm2 : layerms)
templ.thickness += layerm2->layer()->height;
templ.thickness_layers = (unsigned short)layerms.size();
layerm->fill_surfaces.append(intersection, templ);
} else {
// Save void surfaces.
layerm->fill_surfaces.append(
intersection_ex(internal, intersection_with_clearance, false),
stInternalVoid);
}
}
}
}
}
void PrintObject::_generate_support_material() void PrintObject::_generate_support_material()
{ {
PrintObjectSupportMaterial support_material(this, PrintObject::slicing_parameters()); PrintObjectSupportMaterial support_material(this, PrintObject::slicing_parameters());

View file

@ -13,8 +13,8 @@ public:
Surfaces surfaces; Surfaces surfaces;
SurfaceCollection() {}; SurfaceCollection() {};
SurfaceCollection(const Surfaces &_surfaces) SurfaceCollection(const Surfaces &surfaces) : surfaces(surfaces) {};
: surfaces(_surfaces) {}; SurfaceCollection(Surfaces &&surfaces) : surfaces(std::move(surfaces)) {};
operator Polygons() const; operator Polygons() const;
operator ExPolygons() const; operator ExPolygons() const;
void simplify(double tolerance); void simplify(double tolerance);

View file

@ -65,11 +65,6 @@ _constant()
%code%{ RETVAL = &THIS->size; %}; %code%{ RETVAL = &THIS->size; %};
Clone<BoundingBox> bounding_box(); Clone<BoundingBox> bounding_box();
bool typed_slices()
%code%{ RETVAL = THIS->typed_slices; %};
void set_typed_slices(bool value)
%code%{ THIS->typed_slices = value; %};
Points _shifted_copies() Points _shifted_copies()
%code%{ RETVAL = THIS->_shifted_copies; %}; %code%{ RETVAL = THIS->_shifted_copies; %};
void set_shifted_copies(Points value) void set_shifted_copies(Points value)
@ -108,10 +103,9 @@ _constant()
void _slice(); void _slice();
std::string _fix_slicing_errors(); std::string _fix_slicing_errors();
void _simplify_slices(double distance); void _simplify_slices(double distance);
void _prepare_infill();
void detect_surfaces_type(); void detect_surfaces_type();
void process_external_surfaces(); void process_external_surfaces();
void discover_vertical_shells();
void bridge_over_infill();
void _make_perimeters(); void _make_perimeters();
void _infill(); void _infill();
void _generate_support_material(); void _generate_support_material();