From abe56f96dabd658efe5332b910f9fa5ffb561aa9 Mon Sep 17 00:00:00 2001
From: Alessandro Ranellucci <aar@cpan.org>
Date: Mon, 18 Nov 2013 17:06:08 +0100
Subject: [PATCH] Cleaner logic for perimeters, thin walls and gaps. More
 correct results and faster processing

---
 lib/Slic3r/Layer/Region.pm | 143 ++++++++++++++++++-------------------
 1 file changed, 69 insertions(+), 74 deletions(-)

diff --git a/lib/Slic3r/Layer/Region.pm b/lib/Slic3r/Layer/Region.pm
index 48a7cc2a3..312dce32a 100644
--- a/lib/Slic3r/Layer/Region.pm
+++ b/lib/Slic3r/Layer/Region.pm
@@ -145,8 +145,9 @@ sub _merge_loops {
 sub make_perimeters {
     my $self = shift;
     
-    my $perimeter_spacing   = $self->perimeter_flow->scaled_spacing;
-    my $infill_spacing      = $self->solid_infill_flow->scaled_spacing;
+    my $pwidth              = $self->perimeter_flow->scaled_width;
+    my $pspacing            = $self->perimeter_flow->scaled_spacing;
+    my $ispacing            = $self->solid_infill_flow->scaled_spacing;
     my $gap_area_threshold  = $self->perimeter_flow->scaled_width ** 2;
     
     $self->perimeters->clear;
@@ -155,7 +156,8 @@ sub make_perimeters {
     
     my @contours    = ();    # array of Polygons with ccw orientation
     my @holes       = ();    # array of Polygons with cw orientation
-    my @gaps        = ();    # array of Polygons
+    my @thin_walls  = ();    # array of ExPolygons
+    my @gaps        = ();    # array of ExPolygons
     
     # we need to process each island separately because we might have different
     # extra perimeters for each one
@@ -163,44 +165,51 @@ sub make_perimeters {
         # detect how many perimeters must be generated for this island
         my $loop_number = $self->config->perimeters + ($surface->extra_perimeters || 0);
         
-        # generate loops
-        # (one more than necessary so that we can detect gaps even after the desired
-        # number of perimeters has been generated)
         my @last = @{$surface->expolygon};
-        my @this_gaps = ();
-        for my $i (0 .. $loop_number) {
-            # external loop only needs half inset distance
-            my $spacing = ($i == 0)
-                ? $perimeter_spacing / 2
-                : $perimeter_spacing;
-            
-            my @offsets = @{offset2_ex(\@last, -1.5*$spacing,  +0.5*$spacing)};
-            # clone polygons because these ExPolygons will go out of scope very soon
-            my @contours_offsets    = map $_->contour->clone, @offsets;
-            my @holes_offsets       = map $_->clone, map @{$_->holes}, @offsets;
-            @offsets = map $_->clone, (@contours_offsets, @holes_offsets);     # turn @offsets from ExPolygons to Polygons
-            
-            # 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->config->fill_density > 0) {
-                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 abs($_->area) >= $gap_area_threshold, @$diff);
+        my @last_gaps = ();
+        for my $i (1 .. $loop_number) {  # outer loop is 1
+            my @offsets = ();
+            if ($i == 1) {
+                # the minimum thickness of a single loop is:
+                # width/2 + spacing/2 + spacing/2 + width/2
+                @offsets = @{offset2(\@last, -(0.5*$pwidth + 0.5*$pspacing - 1), +(0.5*$pspacing - 1))};
+                
+                # look for thin walls
+                if ($self->config->thin_walls) {
+                    my $diff = diff_ex(
+                        \@last,
+                        offset(\@offsets, +0.5*$pwidth),
+                    );
+                    push @thin_walls, grep abs($_->area) >= $gap_area_threshold, @$diff;
+                }
+            } else {
+                @offsets = @{offset2(\@last, -(1.5*$pspacing - 1), +(0.5*$pspacing - 1))};
+                
+                # look for gaps
+                if ($Slic3r::Config->gap_fill_speed > 0 && $self->config->fill_density > 0) {
+                    my $diff = diff_ex(
+                        offset(\@last, -0.5*$pspacing),
+                        offset(\@offsets, +0.5*$pspacing),
+                    );
+                    push @gaps, @last_gaps = grep abs($_->area) >= $gap_area_threshold, @$diff;
+                }
             }
             
-            last if !@offsets || $i == $loop_number;
-            push @contours, @contours_offsets;
-            push @holes,    @holes_offsets;
+            last if !@offsets;
+            # clone polygons because these ExPolygons will go out of scope very soon
             @last = @offsets;
+            foreach my $polygon (@offsets) {
+                if ($polygon->is_counter_clockwise) {
+                    push @contours, $polygon;
+                } else {
+                    push @holes, $polygon;
+                }
+            }
         }
         
         # 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, \@this_gaps)};
+        @last = @{diff(\@last, \@last_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)
@@ -209,8 +218,8 @@ sub make_perimeters {
         $self->fill_surfaces->append(
             @{offset2_ex(
                 [ map $_->simplify_as_polygons(&Slic3r::SCALED_RESOLUTION), @{union_ex(\@last)} ],
-                -($perimeter_spacing/2 + $infill_spacing/2),
-                +$infill_spacing/2,
+                -($pspacing/2 + $ispacing/2),
+                +$ispacing/2,
             )}
         );
     }
@@ -280,44 +289,33 @@ sub make_perimeters {
     
     # 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)}
+    if (@thin_walls) {
+        my @p = map $_->medial_axis($pspacing), @thin_walls;
+        my @paths = ();
+        for my $p (@p) {
+            my %params = (
+                role            => EXTR_ROLE_EXTERNAL_PERIMETER,
+                flow_spacing    => $self->perimeter_flow->spacing,
             );
-            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,
-            )};
+            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.
+        # Note: this is probably not necessary anymore since we're detecting thin walls
+        # and gaps at the same time
+        @gaps = @{diff_ex(
+            [ map @$_, @gaps ],
+            [ map $_->grow($self->perimeter_flow->scaled_width), @p ],
+            1,
+        )};
     }
     
     $self->_fill_gaps(\@gaps);
@@ -329,9 +327,6 @@ 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);