diff --git a/MANIFEST b/MANIFEST index f1880e541..2cbf9fc2c 100644 --- a/MANIFEST +++ b/MANIFEST @@ -54,6 +54,7 @@ t/arcs.t t/clean_polylines.t t/clipper.t t/collinear.t +t/dynamic.t t/fill.t t/geometry.t t/polyclip.t diff --git a/lib/Slic3r/ExPolygon.pm b/lib/Slic3r/ExPolygon.pm index e5f96d381..6a3e946c1 100644 --- a/lib/Slic3r/ExPolygon.pm +++ b/lib/Slic3r/ExPolygon.pm @@ -82,6 +82,13 @@ sub safety_offset { ); } +sub noncollapsing_offset_ex { + my $self = shift; + my ($distance, @params) = @_; + + return $self->offset_ex($distance + 1, @params); +} + sub encloses_point { my $self = shift; my ($point) = @_; diff --git a/lib/Slic3r/Fill.pm b/lib/Slic3r/Fill.pm index ccbb5ee8d..4ae1f5415 100644 --- a/lib/Slic3r/Fill.pm +++ b/lib/Slic3r/Fill.pm @@ -178,18 +178,8 @@ sub make_fill { } # add thin fill regions - { - my %args = ( - role => EXTR_ROLE_SOLIDFILL, - flow_spacing => $layer->perimeter_flow->spacing, - ); - push @fills, map { - $_->isa('Slic3r::Polygon') - ? (map $_->pack, Slic3r::ExtrusionLoop->new(polygon => $_, %args)->split_at_first_point) - : Slic3r::ExtrusionPath->pack(polyline => $_, %args), - } @{$layer->thin_fills}; - } - push @fills_ordering_points, map $_->[0], @{$layer->thin_fills}; + push @fills, @{$layer->thin_fills}; + push @fills_ordering_points, map $_->unpack->points->[0], @{$layer->thin_fills}; # organize infill paths using a shortest path search @fills = @{shortest_path([ diff --git a/lib/Slic3r/Fill/Rectilinear.pm b/lib/Slic3r/Fill/Rectilinear.pm index d13321015..223b8ca4b 100644 --- a/lib/Slic3r/Fill/Rectilinear.pm +++ b/lib/Slic3r/Fill/Rectilinear.pm @@ -58,7 +58,7 @@ sub fill_surface { } # connect lines - { + unless ($params{dont_connect}) { my $collection = Slic3r::ExtrusionPath::Collection->new( paths => [ map Slic3r::ExtrusionPath->new(polyline => Slic3r::Polyline->new(@$_), role => -1), @paths ], ); diff --git a/lib/Slic3r/Flow.pm b/lib/Slic3r/Flow.pm index 15e54cfcf..a035b9d37 100644 --- a/lib/Slic3r/Flow.pm +++ b/lib/Slic3r/Flow.pm @@ -57,6 +57,16 @@ sub _build_spacing { return $self->width - &Slic3r::OVERLAP_FACTOR * ($self->width - $min_flow_spacing); } +sub clone { + my $self = shift; + + return (ref $self)->new( + nozzle_diameter => $self->nozzle_diameter, + layer_height => $self->layer_height, + @_, + ); +} + sub _build_scaled_width { my $self = shift; return scale $self->width; diff --git a/lib/Slic3r/Layer/Region.pm b/lib/Slic3r/Layer/Region.pm index 31c785e99..909288dd7 100644 --- a/lib/Slic3r/Layer/Region.pm +++ b/lib/Slic3r/Layer/Region.pm @@ -187,12 +187,14 @@ sub make_perimeters { # offsetting a polygon can result in one or many offset polygons my @new_offsets = (); foreach my $expolygon (@last_offsets) { - my @offsets = map $_->offset_ex(+0.5*$distance), $expolygon->offset_ex(-1.5*$distance); + my @offsets = map $_->offset_ex(+0.5*$distance), $expolygon->noncollapsing_offset_ex(-1.5*$distance); push @new_offsets, @offsets; + # where the above check collapses the expolygon, then there's no room for an inner loop + # and we can extract the gap for later processing my $diff = diff_ex( - [ map @$_, $expolygon->offset_ex(-$distance) ], - [ map @$_, @offsets ], + [ map @$_, $expolygon->offset_ex(-0.5*$distance) ], + [ map @$_, map $_->offset_ex(+0.5*$distance), @offsets ], # should these be offsetted in a single pass? ); push @gaps, grep $_->area >= $gap_area_threshold, @$diff; } @@ -207,14 +209,69 @@ sub make_perimeters { my @fill_boundaries = map $_->offset_ex(-$distance), @last_offsets; $_->simplify(scale &Slic3r::RESOLUTION) for @fill_boundaries; push @{ $self->surfaces }, @fill_boundaries; + } + + # fill gaps + { + my $filler = Slic3r::Fill->new(print => $self->layer->object->print)->filler('rectilinear'); - # detect the small gaps that we need to treat like thin polygons, - # thus generating the skeleton and using it to fill them - push @{ $self->thin_fills }, - map $_->medial_axis($self->perimeter_flow->scaled_width), - @gaps; - Slic3r::debugf " %d gaps filled\n", scalar @{ $self->thin_fills } - if @{ $self->thin_fills }; + my $w = $self->perimeter_flow->width; + my @widths = (1.5 * $w, $w, 0.5 * $w); # worth trying 0.2 too? + foreach my $width (@widths) { + my $flow = $self->perimeter_flow->clone(width => $width); + + # extract the gaps having this width + my @this_width = map $_->offset_ex(+0.5*$flow->scaled_width), + map $_->noncollapsing_offset_ex(-0.5*$flow->scaled_width), + @gaps; + + if (0) { + # fill gaps using dynamic extrusion width, by treating them like thin polygons, + # thus generating the skeleton and using it to fill them + my %path_args = ( + role => EXTR_ROLE_SOLIDFILL, + flow_spacing => $flow->spacing, + ); + push @{ $self->thin_fills }, map { + $_->isa('Slic3r::Polygon') + ? (map $_->pack, Slic3r::ExtrusionLoop->new(polygon => $_, %path_args)->split_at_first_point) # we should keep these as loops + : Slic3r::ExtrusionPath->pack(polyline => $_, %path_args), + } map $_->medial_axis($flow->scaled_width), @this_width; + + Slic3r::debugf " %d gaps filled with extrusion width = %s\n", scalar @this_width, $width + if @{ $self->thin_fills }; + + } else { + # fill gaps using zigzag infill + + # since this is infill, we have to offset by half-extrusion width inwards + my @infill = map $_->offset_ex(-0.5*$flow->scaled_width), @this_width; + + foreach my $expolygon (@infill) { + my @paths = $filler->fill_surface( + Slic3r::Surface->new(expolygon => $expolygon), + density => 1, + flow_spacing => $flow->spacing, + dont_connect => 1, # time-saver + ); + my $params = shift @paths; + + push @{ $self->thin_fills }, + map Slic3r::ExtrusionPath->pack( + polyline => Slic3r::Polyline->new(@$_), + role => EXTR_ROLE_SOLIDFILL, + depth_layers => 1, + flow_spacing => $params->{flow_spacing}, + ), @paths; + } + } + + # check what's left + @gaps = @{diff_ex( + [ map @$_, @gaps ], + [ map @$_, @this_width ], + )}; + } } } diff --git a/t/dynamic.t b/t/dynamic.t new file mode 100644 index 000000000..0b5dc490f --- /dev/null +++ b/t/dynamic.t @@ -0,0 +1,79 @@ +use Test::More; +use strict; +use warnings; + +plan tests => 20; + +BEGIN { + use FindBin; + use lib "$FindBin::Bin/../lib"; +} + +use List::Util qw(first); +use Slic3r; +use Slic3r::Geometry qw(X Y scale epsilon); +use Slic3r::Surface ':types'; + +sub scale_points (@) { map [scale $_->[X], scale $_->[Y]], @_ } + +{ + my $square = Slic3r::ExPolygon->new([ + scale_points [0,0], [10,0], [10,10], [0,10], + ]); + + my @offsets = $square->noncollapsing_offset_ex(- scale 5); + is scalar @offsets, 1, 'non-collapsing offset'; +} + +{ + my $w = 0.7; + local $Slic3r::perimeter_flow = Slic3r::Flow->new( + nozzle_diameter => 0.5, + layer_height => 0.4, + width => $w, + ); + local $Slic3r::Config = Slic3r::Config->new( + perimeters => 3, + ); + + my $make_layer = sub { + my ($width) = @_; + my $layer = Slic3r::Layer->new( + id => 1, + slices => [ + Slic3r::Surface->new( + surface_type => S_TYPE_INTERNAL, + expolygon => Slic3r::ExPolygon->new([ scale_points [0,0], [50,0], [50,$width], [0,$width] ]), + ), + ], + thin_walls => [], + ); + $layer->make_perimeters; + return $layer; + }; + + my %widths = ( + 1 * $w => { perimeters => 1, gaps => 0 }, + 1.3 * $w => { perimeters => 1, gaps => 1, gap_flow_spacing => $Slic3r::perimeter_flow->clone(width => 0.2 * $w)->spacing }, + 1.5 * $w => { perimeters => 1, gaps => 1, gap_flow_spacing => $Slic3r::perimeter_flow->clone(width => 0.5 * $w)->spacing }, + 2 * $w => { perimeters => 1, gaps => 1, gap_flow_spacing => $Slic3r::perimeter_flow->spacing }, + 2.5 * $w => { perimeters => 1, gaps => 1, gap_flow_spacing => $Slic3r::perimeter_flow->clone(width => 1.5 * $w)->spacing }, + 3 * $w => { perimeters => 2, gaps => 0 }, + 4 * $w => { perimeters => 2, gaps => 1, gap_flow_spacing => $Slic3r::perimeter_flow->spacing }, + ); + + foreach my $width (sort keys %widths) { + my $layer = $make_layer->($width); + is scalar @{$layer->perimeters}, $widths{$width}{perimeters}, 'right number of perimeters'; + is scalar @{$layer->thin_fills} ? 1 : 0, $widths{$width}{gaps}, + ($widths{$width}{gaps} ? 'gaps were filled' : 'no gaps detected'); # TODO: we should check the exact number of gaps, but we need a better medial axis algorithm + + my @gaps = map $_->unpack, @{$layer->thin_fills}; + if (@gaps) { + ok +(!first { abs($_->flow_spacing - $widths{$width}{gap_flow_spacing}) > epsilon } @gaps), + 'flow spacing was dynamically adjusted'; + } + } +} + +__END__