From 20f1f3d2b417aaa79314fb84e33c2862d05d2c7e Mon Sep 17 00:00:00 2001
From: Alessandro Ranellucci <aar@cpan.org>
Date: Mon, 23 Dec 2013 20:12:39 +0100
Subject: [PATCH] Fix regression preventing raft from being generated for the
 entire object footprint. #1614 #1567

---
 lib/Slic3r/Print/SupportMaterial.pm | 121 +++++++++++++++-------------
 lib/Slic3r/Test.pm                  |   1 +
 t/support.t                         |  39 ++++++++-
 3 files changed, 104 insertions(+), 57 deletions(-)

diff --git a/lib/Slic3r/Print/SupportMaterial.pm b/lib/Slic3r/Print/SupportMaterial.pm
index 9cc19a644..d4fb29221 100644
--- a/lib/Slic3r/Print/SupportMaterial.pm
+++ b/lib/Slic3r/Print/SupportMaterial.pm
@@ -82,6 +82,10 @@ sub contact_area {
     my %contact  = ();  # contact_z => [ polygons ]
     my %overhang = ();  # contact_z => [ polygons ] - this stores the actual overhang supported by each contact layer
     for my $layer_id (0 .. $#{$object->layers}) {
+        # note $layer_id might != $layer->id when raft_layers > 0
+        # so $layer_id == 0 means first object layer
+        # and $layer->id == 0 means first print layer (including raft)
+        
         if ($self->config->raft_layers == 0) {
             next if $layer_id == 0;
         } elsif (!$self->config->support_material) {
@@ -90,69 +94,76 @@ sub contact_area {
             last if $layer_id > 0;
         }
         my $layer = $object->layers->[$layer_id];
-        my $lower_layer = $object->layers->[$layer_id-1];
         
         # detect overhangs and contact areas needed to support them
         my (@overhang, @contact) = ();
-        foreach my $layerm (@{$layer->regions}) {
-            my $fw = $layerm->perimeter_flow->scaled_width;
-            my $diff;
+        if ($layer_id == 0) {
+            # this is the first object layer, so we're here just to get the object
+            # footprint for the raft
+            push @overhang, map $_->clone, map @$_, @{$layer->slices};
+            push @contact, @{offset(\@overhang, scale +MARGIN)};
+        } else {
+            my $lower_layer = $object->layers->[$layer_id-1];
+            foreach my $layerm (@{$layer->regions}) {
+                my $fw = $layerm->perimeter_flow->scaled_width;
+                my $diff;
             
-            # If a threshold angle was specified, use a different logic for detecting overhangs.
-            if (defined $threshold_rad
-                || $layer_id < $self->config->support_material_enforce_layers
-                || $self->config->raft_layers > 0) {
-                my $d = defined $threshold_rad
-                    ? scale $lower_layer->height * ((cos $threshold_rad) / (sin $threshold_rad))
-                    : 0;
+                # If a threshold angle was specified, use a different logic for detecting overhangs.
+                if (defined $threshold_rad
+                    || $layer_id < $self->config->support_material_enforce_layers
+                    || $self->config->raft_layers > 0) {
+                    my $d = defined $threshold_rad
+                        ? scale $lower_layer->height * ((cos $threshold_rad) / (sin $threshold_rad))
+                        : 0;
                 
-                $diff = diff(
-                    offset([ map $_->p, @{$layerm->slices} ], -$d),
-                    [ map @$_, @{$lower_layer->slices} ],
-                );
-                
-                # only enforce spacing from the object ($fw/2) if the threshold angle
-                # is not too high: in that case, $d will be very small (as we need to catch
-                # very short overhangs), and such contact area would be eaten by the
-                # enforced spacing, resulting in high threshold angles to be almost ignored
-                $diff = diff(
-                    offset($diff, $d - $fw/2),
-                    [ map @$_, @{$lower_layer->slices} ],
-                ) if $d > $fw/2;
-            } else {
-                $diff = diff(
-                    offset([ map $_->p, @{$layerm->slices} ], -$fw/2),
-                    [ map @$_, @{$lower_layer->slices} ],
-                );
-                
-                # collapse very tiny spots
-                $diff = offset2($diff, -$fw/10, +$fw/10);
-                
-                # $diff now contains the ring or stripe comprised between the boundary of 
-                # lower slices and the centerline of the last perimeter in this overhanging layer.
-                # Void $diff means that there's no upper perimeter whose centerline is
-                # outside the lower slice boundary, thus no overhang
-            }
-            
-            # TODO: this is the place to remove bridged areas
-            
-            next if !@$diff;
-            push @overhang, @$diff;  # NOTE: this is not the full overhang as it misses the outermost half of the perimeter width!
-            
-            # Let's define the required contact area by using a max gap of half the upper 
-            # extrusion width and extending the area according to the configured margin.
-            # We increment the area in steps because we don't want our support to overflow
-            # on the other side of the object (if it's very thin).
-            {
-                my @slices_margin = @{offset([ map @$_, @{$lower_layer->slices} ], $fw/2)};
-                for ($fw/2, map {scale MARGIN_STEP} 1..(MARGIN / MARGIN_STEP)) {
                     $diff = diff(
-                        offset($diff, $_),
-                        \@slices_margin,
+                        offset([ map $_->p, @{$layerm->slices} ], -$d),
+                        [ map @$_, @{$lower_layer->slices} ],
                     );
+                
+                    # only enforce spacing from the object ($fw/2) if the threshold angle
+                    # is not too high: in that case, $d will be very small (as we need to catch
+                    # very short overhangs), and such contact area would be eaten by the
+                    # enforced spacing, resulting in high threshold angles to be almost ignored
+                    $diff = diff(
+                        offset($diff, $d - $fw/2),
+                        [ map @$_, @{$lower_layer->slices} ],
+                    ) if $d > $fw/2;
+                } else {
+                    $diff = diff(
+                        offset([ map $_->p, @{$layerm->slices} ], -$fw/2),
+                        [ map @$_, @{$lower_layer->slices} ],
+                    );
+                
+                    # collapse very tiny spots
+                    $diff = offset2($diff, -$fw/10, +$fw/10);
+                
+                    # $diff now contains the ring or stripe comprised between the boundary of 
+                    # lower slices and the centerline of the last perimeter in this overhanging layer.
+                    # Void $diff means that there's no upper perimeter whose centerline is
+                    # outside the lower slice boundary, thus no overhang
                 }
+            
+                # TODO: this is the place to remove bridged areas
+            
+                next if !@$diff;
+                push @overhang, @$diff;  # NOTE: this is not the full overhang as it misses the outermost half of the perimeter width!
+            
+                # Let's define the required contact area by using a max gap of half the upper 
+                # extrusion width and extending the area according to the configured margin.
+                # We increment the area in steps because we don't want our support to overflow
+                # on the other side of the object (if it's very thin).
+                {
+                    my @slices_margin = @{offset([ map @$_, @{$lower_layer->slices} ], $fw/2)};
+                    for ($fw/2, map {scale MARGIN_STEP} 1..(MARGIN / MARGIN_STEP)) {
+                        $diff = diff(
+                            offset($diff, $_),
+                            \@slices_margin,
+                        );
+                    }
+                }
+                push @contact, @$diff;
             }
-            push @contact, @$diff;
         }
         next if !@contact;
         
@@ -177,7 +188,7 @@ sub contact_area {
                 require "Slic3r/SVG.pm";
                 Slic3r::SVG::output("contact_" . $contact_z . ".svg",
                     expolygons      => union_ex(\@contact),
-                    red_expolygons  => \@overhang,
+                    red_expolygons  => union_ex(\@overhang),
                 );
             }
         }
diff --git a/lib/Slic3r/Test.pm b/lib/Slic3r/Test.pm
index bc3ad1128..6e630d5c9 100644
--- a/lib/Slic3r/Test.pm
+++ b/lib/Slic3r/Test.pm
@@ -107,6 +107,7 @@ sub init_print {
     
     $model_name = [$model_name] if ref($model_name) ne 'ARRAY';
     for my $model (map model($_, %params), @$model_name) {
+        die "Unknown model in test" if !defined $model;
         $model->arrange_objects($config);
         $model->center_instances_around_point($config->print_center);
         $print->add_model_object($_) for @{$model->objects};
diff --git a/t/support.t b/t/support.t
index 768ddc095..b7d1810df 100644
--- a/t/support.t
+++ b/t/support.t
@@ -1,4 +1,4 @@
-use Test::More tests => 13;
+use Test::More tests => 14;
 use strict;
 use warnings;
 
@@ -9,7 +9,8 @@ BEGIN {
 
 use List::Util qw(first);
 use Slic3r;
-use Slic3r::Geometry qw(epsilon);
+use Slic3r::Geometry qw(epsilon scale);
+use Slic3r::Geometry::Clipper qw(diff);
 use Slic3r::Test;
 
 {
@@ -88,4 +89,38 @@ use Slic3r::Test;
     });
 }
 
+{
+    my $config = Slic3r::Config->new_from_defaults;
+    $config->set('skirts', 0);
+    $config->set('raft_layers', 3);
+    $config->set('support_material_extrusion_width', 0.6);
+    $config->set('first_layer_extrusion_width', '100%');
+    my $print = Slic3r::Test::init_print('20mm_cube', config => $config);
+    
+    my $layer_id = 0;
+    my @raft = my @first_object_layer = ();
+    Slic3r::GCode::Reader->new->parse(Slic3r::Test::gcode($print), sub {
+        my ($self, $cmd, $args, $info) = @_;
+        
+        if ($info->{extruding} && $info->{dist_XY} > 0) {
+            if ($layer_id <= $config->raft_layers) {
+                # this is a raft layer or the first object layer
+                my $line = Slic3r::Line->new_scale([ $self->X, $self->Y ], [ $info->{new_X}, $info->{new_Y} ]);
+                my @path = $line->grow(scale($config->support_material_extrusion_width/2));
+                if ($layer_id < $config->raft_layers) {
+                    # this is a raft layer
+                    push @raft, @path;
+                } else {
+                    push @first_object_layer, @path;
+                }
+            }
+        } elsif ($cmd eq 'G1' && $info->{dist_Z} > 0) {
+            $layer_id++;
+        }
+    });
+    
+    ok !@{diff(\@first_object_layer, \@raft)},
+        'first object layer is completely supported by raft';
+}
+
 __END__