From 64061267c84c98e6508bdb4b7e7ad4ef288c864d Mon Sep 17 00:00:00 2001
From: Alessandro Ranellucci <aar@cpan.org>
Date: Mon, 22 Dec 2014 16:47:35 +0100
Subject: [PATCH] Align infill across layers regardless of first-layer-specific
 extrusion width. Includes a good internal API refactoring and a fix to 3D
 honeycomb flow

---
 lib/Slic3r/Fill.pm             | 52 ++++++++++++++++++++++++++++------
 lib/Slic3r/Fill/3DHoneycomb.pm | 15 ++++------
 lib/Slic3r/Fill/Base.pm        |  5 ++++
 lib/Slic3r/Fill/Concentric.pm  | 16 +++--------
 lib/Slic3r/Fill/Honeycomb.pm   |  6 ++--
 lib/Slic3r/Fill/PlanePath.pm   |  5 ++--
 lib/Slic3r/Fill/Rectilinear.pm | 12 ++------
 xs/src/libslic3r/Flow.cpp      |  2 +-
 8 files changed, 67 insertions(+), 46 deletions(-)

diff --git a/lib/Slic3r/Fill.pm b/lib/Slic3r/Fill.pm
index 780717586..fb2dfe858 100644
--- a/lib/Slic3r/Fill.pm
+++ b/lib/Slic3r/Fill.pm
@@ -195,28 +195,66 @@ sub make_fill {
             next SURFACE unless $density > 0;
         }
         
+        # get filler object
+        my $f = $self->filler($filler);
+        
+        # calculate the actual flow we'll be using for this infill
         my $h = $surface->thickness == -1 ? $layerm->height : $surface->thickness;
         my $flow = $layerm->region->flow(
             $role,
             $h,
-            $is_bridge,
+            $is_bridge || $f->use_bridge_flow,
             $layerm->id == 0,
             -1,
             $layerm->object,
         );
         
-        my $f = $self->filler($filler);
+        # calculate flow spacing for infill pattern generation
+        my $using_internal_flow = 0;
+        if (!$is_solid && !$is_bridge) {
+            # it's internal infill, so we can calculate a generic flow spacing 
+            # for all layers, for avoiding the ugly effect of
+            # misaligned infill on first layer because of different extrusion width and
+            # layer height
+            my $internal_flow = $layerm->region->flow(
+                FLOW_ROLE_INFILL,
+                $layerm->object->config->layer_height,  # TODO: handle infill_every_layers?
+                0,  # no bridge
+                0,  # no first layer
+                -1, # auto width
+                $layerm->object,
+            );
+            $f->spacing($internal_flow->spacing);
+            $using_internal_flow = 1;
+        } else {
+            $f->spacing($flow->spacing);
+        }
+        
         $f->layer_id($layerm->id);
         $f->z($layerm->print_z);
         $f->angle(deg2rad($layerm->config->fill_angle));
-        my ($params, @polylines) = $f->fill_surface(
+        $f->loop_clipping(scale($flow->nozzle_diameter) * &Slic3r::LOOP_CLIPPING_LENGTH_OVER_NOZZLE_DIAMETER);
+        my @polylines = $f->fill_surface(
             $surface,
             density         => $density/100,
-            flow            => $flow,
             layer_height    => $h,
         );
         next unless @polylines;
         
+        # calculate actual flow from spacing (which might have been adjusted by the infill
+        # pattern generator)
+        if ($using_internal_flow) {
+            # if we used the internal flow we're not doing a solid infill
+            # so we can safely ignore the slight variation that might have
+            # been applied to $f->flow_spacing
+        } else {
+            $flow = Slic3r::Flow->new_from_spacing(
+                spacing         => $f->spacing,
+                nozzle_diameter => $flow->nozzle_diameter,
+                layer_height    => $h,
+                bridge          => $is_bridge || $f->use_bridge_flow,
+            );
+        }
         my $mm3_per_mm = $flow->mm3_per_mm;
         
         # save into layer
@@ -224,18 +262,16 @@ sub make_fill {
             my $role = $is_bridge ? EXTR_ROLE_BRIDGE
                 : $is_solid ? (($surface->surface_type == S_TYPE_TOP) ? EXTR_ROLE_TOPSOLIDFILL : EXTR_ROLE_SOLIDFILL)
                 : EXTR_ROLE_FILL;
-    
-            my $extrusion_height = $is_bridge ? $flow->width : $h;
             
             push @fills, my $collection = Slic3r::ExtrusionPath::Collection->new;
-            $collection->no_sort($params->{no_sort});
+            $collection->no_sort($f->no_sort);
             $collection->append(
                 map Slic3r::ExtrusionPath->new(
                     polyline    => $_,
                     role        => $role,
                     mm3_per_mm  => $mm3_per_mm,
                     width       => $flow->width,
-                    height      => $extrusion_height,
+                    height      => $flow->height,
                 ), @polylines,
             );
         }
diff --git a/lib/Slic3r/Fill/3DHoneycomb.pm b/lib/Slic3r/Fill/3DHoneycomb.pm
index bb37d0ea7..256437707 100644
--- a/lib/Slic3r/Fill/3DHoneycomb.pm
+++ b/lib/Slic3r/Fill/3DHoneycomb.pm
@@ -7,22 +7,17 @@ use POSIX qw(ceil fmod);
 use Slic3r::Geometry qw(scale scaled_epsilon);
 use Slic3r::Geometry::Clipper qw(intersection_pl);
 
+# require bridge flow since most of this pattern hangs in air
+sub use_bridge_flow { 1 }
+
 sub fill_surface {
     my ($self, $surface, %params) = @_;
     
-    # use bridge flow since most of this pattern hangs in air
-    my $flow = Slic3r::Flow->new(
-        width               => $params{flow}->width,
-        height              => $params{flow}->height,
-        nozzle_diameter     => $params{flow}->nozzle_diameter,
-        bridge              => 1,
-    );
-    
     my $expolygon = $surface->expolygon;
     my $bb = $expolygon->bounding_box;
     my $size = $bb->size;
     
-    my $distance = $flow->scaled_spacing / $params{density};
+    my $distance = scale($self->spacing) / $params{density};
     
     # align bounding box to a multiple of our honeycomb grid
     {
@@ -71,7 +66,7 @@ sub fill_surface {
     }
     
     # TODO: return ExtrusionLoop objects to get better chained paths
-    return { flow => $flow}, @polylines;
+    return @polylines;
 }
 
 
diff --git a/lib/Slic3r/Fill/Base.pm b/lib/Slic3r/Fill/Base.pm
index 34b47243b..75c8e03e6 100644
--- a/lib/Slic3r/Fill/Base.pm
+++ b/lib/Slic3r/Fill/Base.pm
@@ -4,6 +4,8 @@ use Moo;
 has 'layer_id'            => (is => 'rw');
 has 'z'                   => (is => 'rw'); # in unscaled coordinates
 has 'angle'               => (is => 'rw'); # in radians, ccw, 0 = East
+has 'spacing'             => (is => 'rw'); # in unscaled coordinates
+has 'loop_clipping'       => (is => 'rw', default => sub { 0 }); # in scaled coordinates
 has 'bounding_box'        => (is => 'ro', required => 0);  # Slic3r::Geometry::BoundingBox object
 
 sub adjust_solid_spacing {
@@ -17,6 +19,9 @@ sub adjust_solid_spacing {
     return $params{distance} + $extra_space / ($number_of_lines - 1);
 }
 
+sub no_sort { 0 }
+sub use_bridge_flow { 0 }
+
 
 package Slic3r::Fill::WithDirection;
 use Moo::Role;
diff --git a/lib/Slic3r/Fill/Concentric.pm b/lib/Slic3r/Fill/Concentric.pm
index f122bdbd5..17f3fa225 100644
--- a/lib/Slic3r/Fill/Concentric.pm
+++ b/lib/Slic3r/Fill/Concentric.pm
@@ -15,22 +15,15 @@ sub fill_surface {
     my $expolygon = $surface->expolygon;
     my $bounding_box = $expolygon->bounding_box;
     
-    my $flow = $params{flow};
-    my $min_spacing = $flow->scaled_spacing;
+    my $min_spacing = scale($self->spacing);
     my $distance = $min_spacing / $params{density};
     
-    my $flow_spacing = $flow->spacing;
     if ($params{density} == 1 && !$params{dont_adjust}) {
         $distance = $self->adjust_solid_spacing(
             width       => $bounding_box->size->[X],
             distance    => $distance,
         );
-        $flow = Slic3r::Flow->new_from_spacing(
-            spacing             => unscale($distance),
-            nozzle_diameter     => $flow->nozzle_diameter,
-            layer_height        => ($params{layer_height} or die "No layer_height supplied to fill_surface()"),
-            bridge              => $flow->bridge,
-        );
+        $self->spacing(unscale $distance);
     }
     
     # compensate the overlap which is good for rectilinear but harmful for concentric
@@ -54,12 +47,11 @@ sub fill_surface {
     }
     
     # clip the paths to prevent the extruder from getting exactly on the first point of the loop
-    my $clip_length = scale($flow->nozzle_diameter) * &Slic3r::LOOP_CLIPPING_LENGTH_OVER_NOZZLE_DIAMETER;
-    $_->clip_end($clip_length) for @paths;
+    $_->clip_end($self->loop_clipping) for @paths;
     @paths = grep $_->is_valid, @paths;  # remove empty paths (too short, thus eaten by clipping)
     
     # TODO: return ExtrusionLoop objects to get better chained paths
-    return { flow => $flow, no_sort => 1 }, @paths;
+    return @paths;
 }
 
 1;
diff --git a/lib/Slic3r/Fill/Honeycomb.pm b/lib/Slic3r/Fill/Honeycomb.pm
index 08c0b0710..0fb47db4d 100644
--- a/lib/Slic3r/Fill/Honeycomb.pm
+++ b/lib/Slic3r/Fill/Honeycomb.pm
@@ -18,11 +18,11 @@ sub fill_surface {
     my $rotate_vector = $self->infill_direction($surface);
     
     # cache hexagons math
-    my $cache_id = sprintf "d%s_s%s", $params{density}, $params{flow}->spacing;
+    my $cache_id = sprintf "d%s_s%s", $params{density}, $self->spacing;
     my $m;
     if (!($m = $self->cache->{$cache_id})) {
         $m = $self->cache->{$cache_id} = {};
-        my $min_spacing = $params{flow}->scaled_spacing;
+        my $min_spacing = scale($self->spacing);
         $m->{distance} = $min_spacing / $params{density};
         $m->{hex_side} = $m->{distance} / (sqrt(3)/2);
         $m->{hex_width} = $m->{distance} * 2;  # $m->{hex_width} == $m->{hex_side} * sqrt(3);
@@ -123,7 +123,7 @@ sub fill_surface {
         )};
     }
     
-    return { flow => $params{flow} }, @paths;
+    return @paths;
 }
 
 1;
diff --git a/lib/Slic3r/Fill/PlanePath.pm b/lib/Slic3r/Fill/PlanePath.pm
index 3f7e1118f..556835ec4 100644
--- a/lib/Slic3r/Fill/PlanePath.pm
+++ b/lib/Slic3r/Fill/PlanePath.pm
@@ -21,8 +21,7 @@ sub fill_surface {
     my $rotate_vector = $self->infill_direction($surface);
     $self->rotate_points($expolygon, $rotate_vector);
     
-    my $flow = $params{flow};
-    my $distance_between_lines = $flow->scaled_spacing / $params{density} * $self->multiplier;
+    my $distance_between_lines = scale($self->spacing) / $params{density} * $self->multiplier;
     
     # align infill across layers using the object's bounding box
     my $bb_polygon = $self->bounding_box->polygon;
@@ -75,7 +74,7 @@ sub fill_surface {
     $_->translate(@{$translate->negative}) for @paths;
     $self->rotate_points_back(\@paths, $rotate_vector);
     
-    return { flow => $flow }, @paths;
+    return @paths;
 }
 
 
diff --git a/lib/Slic3r/Fill/Rectilinear.pm b/lib/Slic3r/Fill/Rectilinear.pm
index 8e3eee3a8..ef459ab9d 100644
--- a/lib/Slic3r/Fill/Rectilinear.pm
+++ b/lib/Slic3r/Fill/Rectilinear.pm
@@ -18,8 +18,7 @@ sub fill_surface {
     my $rotate_vector = $self->infill_direction($surface);
     $self->rotate_points($expolygon, $rotate_vector);
     
-    my $flow                = $params{flow} or die "No flow supplied to fill_surface()";
-    my $min_spacing         = $flow->scaled_spacing;
+    my $min_spacing         = scale($self->spacing);
     my $line_spacing        = $min_spacing / $params{density};
     my $line_oscillation    = $line_spacing - $min_spacing;
     my $is_line_pattern     = $self->isa('Slic3r::Fill::Line');
@@ -31,12 +30,7 @@ sub fill_surface {
             width       => $bounding_box->size->[X],
             distance    => $line_spacing,
         );
-        $flow = Slic3r::Flow->new_from_spacing(
-            spacing             => unscale($line_spacing),
-            nozzle_diameter     => $flow->nozzle_diameter,
-            layer_height        => ($params{layer_height} or die "No layer_height supplied to fill_surface()"),
-            bridge              => $flow->bridge,
-        );
+        $self->spacing(unscale $line_spacing);
     } else {
         # extend bounding box so that our pattern will be aligned with other layers
         $bounding_box->merge_point(Slic3r::Point->new(
@@ -105,7 +99,7 @@ sub fill_surface {
     # paths must be rotated back
     $self->rotate_points_back(\@polylines, $rotate_vector);
     
-    return { flow => $flow }, @polylines;
+    return @polylines;
 }
 
 1;
diff --git a/xs/src/libslic3r/Flow.cpp b/xs/src/libslic3r/Flow.cpp
index 7f87f146e..b9af18184 100644
--- a/xs/src/libslic3r/Flow.cpp
+++ b/xs/src/libslic3r/Flow.cpp
@@ -13,7 +13,7 @@ Flow::new_from_config_width(FlowRole role, const ConfigOptionFloatOrPercent &wid
     float w;
     if (bridge_flow_ratio > 0) {
         // if bridge flow was requested, calculate bridge width
-        w = Flow::_bridge_width(nozzle_diameter, bridge_flow_ratio);
+        height = w = Flow::_bridge_width(nozzle_diameter, bridge_flow_ratio);
     } else if (!width.percent && width.value == 0) {
         // if user left option to 0, calculate a sane default width
         w = Flow::_auto_width(role, nozzle_diameter, height);