From 0aa224ffadd948af69506f511dfc3545e852384e Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Sat, 25 Aug 2012 14:23:46 +0200 Subject: [PATCH 1/2] Dynamic extrusion width for better gap filling --- MANIFEST | 1 + lib/Slic3r/ExPolygon.pm | 7 ++++ lib/Slic3r/Fill.pm | 14 ++------ lib/Slic3r/Flow.pm | 10 ++++++ lib/Slic3r/Layer.pm | 46 +++++++++++++++++++----- t/dynamic.t | 79 +++++++++++++++++++++++++++++++++++++++++ 6 files changed, 136 insertions(+), 21 deletions(-) create mode 100644 t/dynamic.t diff --git a/MANIFEST b/MANIFEST index 19de72498..64d93f368 100644 --- a/MANIFEST +++ b/MANIFEST @@ -51,6 +51,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 5f9d23577..ed572d694 100644 --- a/lib/Slic3r/ExPolygon.pm +++ b/lib/Slic3r/ExPolygon.pm @@ -91,6 +91,13 @@ sub offset_ex { return @{ union_ex(\@offsets) }; } +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 e30eec65f..8068bb87e 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/Flow.pm b/lib/Slic3r/Flow.pm index 9c01dd30c..5e8f23a2b 100644 --- a/lib/Slic3r/Flow.pm +++ b/lib/Slic3r/Flow.pm @@ -55,4 +55,14 @@ 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, + @_, + ); +} + 1; diff --git a/lib/Slic3r/Layer.pm b/lib/Slic3r/Layer.pm index fc33cf420..9794e0ed9 100644 --- a/lib/Slic3r/Layer.pm +++ b/lib/Slic3r/Layer.pm @@ -225,12 +225,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; } @@ -245,14 +247,40 @@ 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 using dynamic extrusion width + { # 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(scale $self->perimeter_flow->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, 0.2 * $w); + foreach my $width (@widths) { + my $scaled_width = scale $width; + + # extract the gaps having this width + my @this_width = map $_->offset_ex(+0.5*$scaled_width), map $_->noncollapsing_offset_ex(-0.5*$scaled_width), @gaps; + + # fill them + my %path_args = ( + role => EXTR_ROLE_SOLIDFILL, + flow_spacing => $self->perimeter_flow->clone(width => $width)->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($scaled_width), @this_width; + + Slic3r::debugf " %d gaps filled with extrusion width = %s\n", scalar @this_width, $width + if @{ $self->thin_fills }; + + # 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__ From 6beaf5e5979ed592fc751c93a646393430e73c52 Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Wed, 24 Oct 2012 16:17:09 +0200 Subject: [PATCH 2/2] New faster algorithm for filling gaps, while we work on a new medial axis implementation --- lib/Slic3r/Fill/Rectilinear.pm | 2 +- lib/Slic3r/Layer/Region.pm | 67 ++++++++++++++++++++++++---------- 2 files changed, 49 insertions(+), 20 deletions(-) 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/Layer/Region.pm b/lib/Slic3r/Layer/Region.pm index e6cbff4c4..a7ca7c157 100644 --- a/lib/Slic3r/Layer/Region.pm +++ b/lib/Slic3r/Layer/Region.pm @@ -211,31 +211,60 @@ sub make_perimeters { push @{ $self->surfaces }, @fill_boundaries; } - # fill gaps using dynamic extrusion width + # fill gaps { - # detect the small gaps that we need to treat like thin polygons, - # thus generating the skeleton and using it to fill them + my $filler = Slic3r::Fill->new(print => $self->layer->object->print)->filler('rectilinear'); + my $w = $self->perimeter_flow->width; - my @widths = (1.5 * $w, $w, 0.5 * $w, 0.2 * $w); + my @widths = (1.5 * $w, $w, 0.5 * $w); # worth trying 0.2 too? foreach my $width (@widths) { - my $scaled_width = scale $width; + my $flow = $self->perimeter_flow->clone(width => $width); # extract the gaps having this width - my @this_width = map $_->offset_ex(+0.5*$scaled_width), map $_->noncollapsing_offset_ex(-0.5*$scaled_width), @gaps; + my @this_width = map $_->offset_ex(+0.5*$flow->scaled_width), + map $_->noncollapsing_offset_ex(-0.5*$flow->scaled_width), + @gaps; - # fill them - my %path_args = ( - role => EXTR_ROLE_SOLIDFILL, - flow_spacing => $self->perimeter_flow->clone(width => $width)->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($scaled_width), @this_width; - - Slic3r::debugf " %d gaps filled with extrusion width = %s\n", scalar @this_width, $width - if @{ $self->thin_fills }; + 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(