From 9a51964e98d71795b26af646b7f571ac7d58491c Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Mon, 16 Sep 2013 17:44:30 +0200 Subject: [PATCH] Clean medial axis code and gap fill --- lib/Slic3r/ExPolygon.pm | 78 ++++++--------------- lib/Slic3r/Layer/Region.pm | 139 +++++++++++++++++++------------------ lib/Slic3r/Polygon.pm | 25 +++---- lib/Slic3r/Print.pm | 2 - lib/Slic3r/SVG.pm | 10 +-- 5 files changed, 108 insertions(+), 146 deletions(-) diff --git a/lib/Slic3r/ExPolygon.pm b/lib/Slic3r/ExPolygon.pm index 8f5411fff..1cd8222a9 100644 --- a/lib/Slic3r/ExPolygon.pm +++ b/lib/Slic3r/ExPolygon.pm @@ -105,10 +105,8 @@ sub medial_axis { my $self = shift; my ($width) = @_; - my @self_lines = map $_->lines, @$self; - my $expolygon = $self->clone; my @points = (); - foreach my $polygon (@$expolygon) { + foreach my $polygon (@$self) { { my $p = $polygon->pp; Slic3r::Geometry::polyline_remove_short_segments($p, $width / 2); @@ -117,9 +115,9 @@ sub medial_axis { # subdivide polygon segments so that we don't have anyone of them # being longer than $width / 2 - $polygon->subdivide($width/2); + $polygon = $polygon->subdivide($width/2); - push @points, @$polygon; + push @points, map $_->clone, @$polygon; } my $voronoi = Math::Geometry::Voronoi->new(points => [ map $_->pp, @points ]); @@ -136,67 +134,34 @@ sub medial_axis { my ($a, $b); $a = Slic3r::Point->new(@{$vertices->[$edge->[1]]}); $b = Slic3r::Point->new(@{$vertices->[$edge->[2]]}); - next if !$self->encloses_point_quick($a) || !$self->encloses_point_quick($b); push @skeleton_lines, [$edge->[1], $edge->[2]]; } - - # remove leafs (lines not connected to other lines at one of their endpoints) - { - my %pointmap = (); - $pointmap{$_}++ for map @$_, @skeleton_lines; - @skeleton_lines = grep { - $pointmap{$_->[A]} >= 2 && $pointmap{$_->[B]} >= 2 - } @skeleton_lines; - } return () if !@skeleton_lines; # now walk along the medial axis and build continuos polylines or polygons my @polylines = (); { - # build a map of line endpoints - my %pointmap = (); # point_idx => [line_idx, line_idx ...] - for my $line_idx (0 .. $#skeleton_lines) { - for my $point_idx (@{$skeleton_lines[$line_idx]}) { - $pointmap{$point_idx} ||= []; - push @{$pointmap{$point_idx}}, $line_idx; - } - } - - # build the list of available lines - my %spare_lines = map {$_ => 1} (0 .. $#skeleton_lines); - - CYCLE: while (%spare_lines) { - push @polylines, []; - my $polyline = $polylines[-1]; - - # start from a random line - my $first_line_idx = +(keys %spare_lines)[0]; - delete $spare_lines{$first_line_idx}; - push @$polyline, @{ $skeleton_lines[$first_line_idx] }; - - while (1) { - my $last_point_id = $polyline->[-1]; - my $lines_starting_here = $pointmap{$last_point_id}; - - # remove all the visited lines from the array - shift @$lines_starting_here - while @$lines_starting_here && !$spare_lines{$lines_starting_here->[0]}; - - # do we have a line starting here? - my $next_line_idx = shift @$lines_starting_here; - if (!defined $next_line_idx) { - delete $pointmap{$last_point_id}; - next CYCLE; + my @lines = @skeleton_lines; + push @polylines, [ map @$_, shift @lines ]; + CYCLE: while (@lines) { + for my $i (0..$#lines) { + if ($lines[$i][0] == $polylines[-1][-1]) { + push @{$polylines[-1]}, $lines[$i][1]; + } elsif ($lines[$i][1] == $polylines[-1][-1]) { + push @{$polylines[-1]}, $lines[$i][0]; + } elsif ($lines[$i][1] == $polylines[-1][0]) { + unshift @{$polylines[-1]}, $lines[$i][0]; + } elsif ($lines[$i][0] == $polylines[-1][0]) { + unshift @{$polylines[-1]}, $lines[$i][1]; + } else { + next; } - - # line is not available anymore - delete $spare_lines{$next_line_idx}; - - # add the other point to our polyline and continue walking - push @$polyline, grep $_ ne $last_point_id, @{$skeleton_lines[$next_line_idx]}; + splice @lines, $i, 1; + next CYCLE; } + push @polylines, [ map @$_, shift @lines ]; } } @@ -209,9 +174,8 @@ sub medial_axis { # cleanup $polyline = Slic3r::Geometry::douglas_peucker($polyline, $width / 7); - $polyline = Slic3r::Polyline->new(@$polyline); - if (Slic3r::Geometry::same_point($polyline->first_point, $polyline->last_point)) { + if (Slic3r::Geometry::same_point($polyline->[0], $polyline->[-1])) { next if @$polyline == 2; push @result, Slic3r::Polygon->new(@$polyline[0..$#$polyline-1]); } else { diff --git a/lib/Slic3r/Layer/Region.pm b/lib/Slic3r/Layer/Region.pm index 3ce1603a1..c8c968ca2 100644 --- a/lib/Slic3r/Layer/Region.pm +++ b/lib/Slic3r/Layer/Region.pm @@ -28,10 +28,6 @@ has 'overhang_width' => (is => 'lazy'); # divided by type top/bottom/internal has 'slices' => (is => 'rw', default => sub { Slic3r::Surface::Collection->new }); -# collection of polygons or polylines representing thin walls contained -# in the original geometry -has 'thin_walls' => (is => 'rw', default => sub { Slic3r::ExtrusionPath::Collection->new }); - # collection of extrusion paths/loops filling gaps has 'thin_fills' => (is => 'rw', default => sub { Slic3r::ExtrusionPath::Collection->new }); @@ -101,26 +97,6 @@ sub make_surfaces { expolygons => [ map $_->expolygon, @{$self->slices} ], ); } - - # detect thin walls by offsetting slices by half extrusion inwards - if ($self->config->thin_walls) { - $self->thin_walls([]); - # we use spacing here because there could be a case where - # the slice collapses with width but doesn't collapse with spacing, - # thus causing both perimeters and medial axis to be generated - my $width = $self->perimeter_flow->scaled_spacing; - my $diff = diff_ex( - [ map $_->p, @{$self->slices} ], - offset2([ map $_->p, @{$self->slices} ], -$width*0.5, +$width*0.5), - 1, - ); - - my $area_threshold = $width ** 2; - if (@$diff = grep { $_->area > $area_threshold } @$diff) { - @{$self->thin_walls} = map $_->medial_axis($width), @$diff; - Slic3r::debugf " %d thin walls detected\n", scalar(@{$self->thin_walls}); - } - } } sub _merge_loops { @@ -177,7 +153,7 @@ sub make_perimeters { my @contours = (); # array of Polygons with ccw orientation my @holes = (); # array of Polygons with cw orientation - my @gaps = (); # array of ExPolygons + my @gaps = (); # array of Polygons # we need to process each island separately because we might have different # extra perimeters for each one @@ -205,13 +181,13 @@ sub make_perimeters { # where offset2() collapses the expolygon, then there's no room for an inner loop # and we can extract the gap for later processing if ($Slic3r::Config->gap_fill_speed > 0 && $self->object->config->fill_density > 0) { - my $diff = diff_ex( + my $diff = diff( offset(\@last, -0.5*$spacing), # +2 on the offset here makes sure that Clipper float truncation # won't shrink the clip polygon to be smaller than intended. offset(\@offsets, +0.5*$spacing + 2), ); - push @gaps, (@this_gaps = grep $_->area >= $gap_area_threshold, @$diff); + push @gaps, (@this_gaps = grep abs($_->area) >= $gap_area_threshold, @$diff); } last if !@offsets || $i == $loop_number; @@ -222,7 +198,7 @@ sub make_perimeters { # make sure we don't infill narrow parts that are already gap-filled # (we only consider this surface's gaps to reduce the diff() complexity) - @last = @{diff(\@last, [ map @$_, @this_gaps ])}; + @last = @{diff(\@last, \@this_gaps)}; # create one more offset to be used as boundary for fill # we offset by half the perimeter spacing (to get to the actual infill boundary) @@ -237,8 +213,6 @@ sub make_perimeters { ); } - $self->_fill_gaps(\@gaps); - # find nesting hierarchies separately for contours and holes my $contours_pt = union_pt(\@contours); my $holes_pt = union_pt(\@holes); @@ -302,14 +276,49 @@ sub make_perimeters { # append perimeters $self->perimeters->append(@loops); - # add thin walls as perimeters - push @{ $self->perimeters }, @{Slic3r::ExtrusionPath::Collection->new( - map Slic3r::ExtrusionPath->new( - polyline => ($_->isa('Slic3r::Polygon') ? $_->split_at_first_point : $_->clone), - role => EXTR_ROLE_EXTERNAL_PERIMETER, - flow_spacing => $self->perimeter_flow->spacing, - ), @{ $self->thin_walls } - )->chained_path(0)}; + # detect thin walls by offsetting slices by half extrusion inwards + # and add them as perimeters + if ($self->config->thin_walls) { + # we use spacing here because there could be a case where + # the slice collapses with width but doesn't collapse with spacing, + # thus causing both perimeters and medial axis to be generated + my $width = $self->perimeter_flow->scaled_spacing; + my $diff = diff_ex( + [ map $_->p, @{$self->slices} ], + offset2([ map $_->p, @{$self->slices} ], -$width*0.5, +$width*0.5), + 1, + ); + + my $area_threshold = $width ** 2; + if (@$diff = grep { $_->area > $area_threshold } @$diff) { + my @p = map $_->medial_axis($width), @$diff; + my @paths = (); + for my $p (@p) { + my %params = ( + role => EXTR_ROLE_EXTERNAL_PERIMETER, + flow_spacing => $self->perimeter_flow->spacing, + ); + push @paths, $p->isa('Slic3r::Polygon') + ? Slic3r::ExtrusionLoop->new(polygon => $p, %params) + : Slic3r::ExtrusionPath->new(polyline => $p, %params); + } + + $self->perimeters->append( + map $_->clone, @{Slic3r::ExtrusionPath::Collection->new(@paths)->chained_path(0)} + ); + Slic3r::debugf " %d thin walls detected\n", scalar(@paths) if $Slic3r::debug; + + # in the mean time we subtract thin walls from the detected gaps so that we don't + # reprocess them, causing overlapping thin walls and zigzag. + @gaps = @{diff( + \@gaps, + [ map $_->grow($self->perimeter_flow->scaled_width), @p ], + 1, + )}; + } + } + + $self->_fill_gaps(\@gaps); } sub _fill_gaps { @@ -318,19 +327,15 @@ sub _fill_gaps { return unless @$gaps; + # turn gaps into ExPolygons + $gaps = union_ex($gaps); + my $filler = $self->layer->object->fill_maker->filler('rectilinear'); $filler->layer_id($self->layer->id); # we should probably use this code to handle thin walls and remove that logic from # make_surfaces(), but we need to enable dynamic extrusion width before as we can't # use zigzag for thin walls. - # in the mean time we subtract thin walls from the detected gaps so that we don't - # reprocess them, causing overlapping thin walls and zigzag. - @$gaps = @{diff_ex( - [ map @$_, @$gaps ], - [ map $_->grow($self->perimeter_flow->scaled_width), @{$self->{thin_walls}} ], - 1, - )}; # medial axis-based gap fill should benefit from detection of larger gaps too, so # we could try with 1.5*$w for example, but that doesn't work well for zigzag fill @@ -375,29 +380,27 @@ sub _fill_gaps { flow_spacing => $flow->spacing, ); - push @{ $self->thin_fills }, - map { - $_->simplify($flow->scaled_width/3); - $_; - } - map Slic3r::ExtrusionPath->new( - polyline => Slic3r::Polyline->new(@$_), - role => EXTR_ROLE_GAPFILL, - height => $self->height, - flow_spacing => $params->{flow_spacing}, - ), - # Split polylines into lines so that the chained_path() search - # at the final stage has more freedom and will choose starting - # points closer than last positions. OTOH, this will make such - # search slower. Probably, ExtrusionPath objects should support - # splitting nearby a given position so that we can choose the right - # entry point even in the middle of the path without needing a - # complex, slow, chained_path() search on all segments. TODO. - # Such logic will also avoid all the small travel moves that this - # line-splitting causes, and it will be applicable to other things - # too. - map Slic3r::Polyline->new(@$_)->lines, - @paths; + # Split polylines into lines so that the chained_path() search + # at the final stage has more freedom and will choose starting + # points closer than last positions. OTOH, this will make such + # search slower. Probably, ExtrusionPath objects should support + # splitting nearby a given position so that we can choose the right + # entry point even in the middle of the path without needing a + # complex, slow, chained_path() search on all segments. TODO. + # Such logic will also avoid all the small travel moves that this + # line-splitting causes, and it will be applicable to other things + # too. + my @lines = map @{Slic3r::Polyline->new(@$_)->lines}, @paths; + + @paths = map Slic3r::ExtrusionPath->new( + polyline => Slic3r::Polyline->new(@$_), + role => EXTR_ROLE_GAPFILL, + height => $self->height, + flow_spacing => $params->{flow_spacing}, + ), @lines; + $_->simplify($flow->scaled_width/3) for @paths; + + $self->thin_fills->append(@paths); } } diff --git a/lib/Slic3r/Polygon.pm b/lib/Slic3r/Polygon.pm index a8543cadf..5f6dc892a 100644 --- a/lib/Slic3r/Polygon.pm +++ b/lib/Slic3r/Polygon.pm @@ -51,21 +51,18 @@ sub subdivide { my $self = shift; my ($max_length) = @_; - for (my $i = 0; $i <= $#$self; $i++) { - my $len = Slic3r::Geometry::line_length([ $self->[$i-1], $self->[$i] ]); - my $num_points = int($len / $max_length) - 1; - $num_points++ if $len % $max_length; - - # $num_points is the number of points to add between $i-1 and $i - next if $num_points == -1; - my $spacing = $len / ($num_points + 1); - my @new_points = map Slic3r::Point->new($_), - map Slic3r::Geometry::point_along_segment($self->[$i-1], $self->[$i], $spacing * $_), - 1..$num_points; - - splice @$self, $i, 0, @new_points; - $i += @new_points; + my @points = @$self; + push @points, $points[0]; # append first point as this is a polygon + my @new_points = shift @points; + while (@points) { + while ($new_points[-1]->distance_to($points[0]) > $max_length) { + push @new_points, map Slic3r::Point->new(@$_), + Slic3r::Geometry::point_along_segment($new_points[-1], $points[0], $max_length); + } + push @new_points, shift @points; } + pop @new_points; # remove last point as it coincides with first one + return Slic3r::Polygon->new(@new_points); } # for cw polygons this will return convex points! diff --git a/lib/Slic3r/Print.pm b/lib/Slic3r/Print.pm index 762931fe6..d64abde68 100644 --- a/lib/Slic3r/Print.pm +++ b/lib/Slic3r/Print.pm @@ -571,7 +571,6 @@ sub make_skirt { my @layers = map $object->layers->[$_], 0..min($Slic3r::Config->skirt_height-1, $#{$object->layers}); my @layer_points = ( (map @$_, map @$_, map @{$_->slices}, @layers), - (map @$_, map @{$_->thin_walls}, map @{$_->regions}, @layers), ); if (@{ $object->support_layers }) { my @support_layers = map $object->support_layers->[$_], 0..min($Slic3r::Config->skirt_height-1, $#{$object->support_layers}); @@ -637,7 +636,6 @@ sub make_brim { my $layer0 = $object->layers->[0]; my @object_islands = ( (map $_->contour, @{$layer0->slices}), - (map { $_->isa('Slic3r::Polygon') ? $_ : $_->grow($grow_distance) } map @{$_->thin_walls}, @{$layer0->regions}), ); if (@{ $object->support_layers }) { my $support_layer0 = $object->support_layers->[0]; diff --git a/lib/Slic3r/SVG.pm b/lib/Slic3r/SVG.pm index b700aedcc..40b0c7c9f 100644 --- a/lib/Slic3r/SVG.pm +++ b/lib/Slic3r/SVG.pm @@ -46,7 +46,7 @@ sub output { $arrows = 0; } elsif ($type =~ /^(?:(.+?)_)?expolygons$/) { my $colour = $1; - @$value = map $_->pp, @$value; + $value = [ map $_->pp, @$value ]; my $g = $svg->group( style => { @@ -64,7 +64,7 @@ sub output { } } elsif ($type =~ /^(?:(.+?)_)?(polygon|polyline)s$/) { my ($colour, $method) = ($1, $2); - @$value = map $_->pp, @$value; + $value = [ map $_->pp, @$value ]; my $g = $svg->group( style => { @@ -86,8 +86,8 @@ sub output { } } elsif ($type =~ /^(?:(.+?)_)?points$/) { my $colour = $1 // 'black'; - my $r = $colour eq 'black' ? 5 : 3; - @$value = map $_->pp, @$value; + my $r = $colour eq 'black' ? 1 : 3; + $value = [ map $_->pp, @$value ]; my $g = $svg->group( style => { @@ -105,7 +105,7 @@ sub output { } } elsif ($type =~ /^(?:(.+?)_)?lines$/) { my $colour = $1; - @$value = map $_->pp, @$value; + $value = [ map $_->pp, @$value ]; my $g = $svg->group( style => {