diff --git a/lib/Slic3r/GCode.pm b/lib/Slic3r/GCode.pm
index 6a45b61cc..72bc89fa1 100644
--- a/lib/Slic3r/GCode.pm
+++ b/lib/Slic3r/GCode.pm
@@ -1,7 +1,7 @@
 package Slic3r::GCode;
 use Moo;
 
-use List::Util qw(min max first);
+use List::Util qw(max first);
 use Slic3r::ExtrusionPath ':roles';
 use Slic3r::Geometry qw(scale unscale scaled_epsilon points_coincide PI X Y B);
 
@@ -20,14 +20,12 @@ has 'lifted'             => (is => 'rw', default => sub {0} );
 has 'last_pos'           => (is => 'rw', default => sub { Slic3r::Point->new(0,0) } );
 has 'last_speed'         => (is => 'rw', default => sub {""});
 has 'last_f'             => (is => 'rw', default => sub {""});
-has 'force_f'            => (is => 'rw', default => sub {0});
 has 'last_fan_speed'     => (is => 'rw', default => sub {0});
 has 'dec'                => (is => 'ro', default => sub { 3 } );
 
 # used for vibration limit:
-has 'limit_frequency'    => (is => 'rw', default => sub { 0 });
 has 'last_dir'           => (is => 'ro', default => sub { [0,0] });
-has 'segment_time'       => (is => 'ro', default => sub { [ [0,0,0], [0,0,0] ] });
+has 'dir_time'           => (is => 'ro', default => sub { [0,0] });
 
 # calculate speeds (mm/min)
 has 'speeds' => (
@@ -57,10 +55,13 @@ sub set_shift {
     my $self = shift;
     my @shift = @_;
     
+    $self->last_pos->translate(
+        scale ($shift[X] - $self->shift_x),
+        scale ($shift[Y] - $self->shift_y),
+    );
+    
     $self->shift_x($shift[X]);
     $self->shift_y($shift[Y]);
-    
-    $self->last_pos->translate(map -(scale $_), @shift);
 }
 
 # this method accepts Z in scaled coordinates
@@ -174,8 +175,6 @@ sub extrude_path {
         }
     }
     
-    $self->limit_frequency($path->role != EXTR_ROLE_PERIMETER && $path->role != EXTR_ROLE_EXTERNAL_PERIMETER && $path->role != EXTR_ROLE_CONTOUR_INTERNAL_PERIMETER);
-    
     # go to first point of extrusion path
     $self->speed('travel');
     $gcode .= $self->G0($path->points->[0], undef, 0, "move to first $description point")
@@ -338,14 +337,11 @@ sub _G0_G1 {
     my ($gcode, $point, $z, $e, $comment) = @_;
     my $dec = $self->dec;
     
-    my $speed_factor;
     if ($point) {
         $gcode .= sprintf " X%.${dec}f Y%.${dec}f", 
             ($point->x * &Slic3r::SCALING_FACTOR) + $self->shift_x - $self->extruder->extruder_offset->[X], 
             ($point->y * &Slic3r::SCALING_FACTOR) + $self->shift_y - $self->extruder->extruder_offset->[Y]; #**
-        if ($self->limit_frequency) {
-            $gcode = $self->_limit_frequency($point) . $gcode;
-        }
+        $gcode = $self->_limit_frequency($point) . $gcode;
         $self->last_pos($point->clone);
     }
     if (defined $z && (!defined $self->z || $z != $self->z)) {
@@ -353,7 +349,7 @@ sub _G0_G1 {
         $gcode .= sprintf " Z%.${dec}f", $z;
     }
     
-    return $self->_Gx($gcode, $e, $speed_factor, $comment);
+    return $self->_Gx($gcode, $e, $comment);
 }
 
 sub G2_G3 {
@@ -373,12 +369,12 @@ sub G2_G3 {
         ($center->[Y] - $self->last_pos->[Y]) * &Slic3r::SCALING_FACTOR;
     
     $self->last_pos($point);
-    return $self->_Gx($gcode, $e, undef, $comment);
+    return $self->_Gx($gcode, $e, $comment);
 }
 
 sub _Gx {
     my $self = shift;
-    my ($gcode, $e, $speed_factor, $comment) = @_;
+    my ($gcode, $e, $comment) = @_;
     my $dec = $self->dec;
     
     # output speed if it's different from last one used
@@ -403,13 +399,6 @@ sub _Gx {
         }
         $self->last_speed($self->speed);
         $self->last_f($F);
-        $F *= $speed_factor // 1;
-    } elsif (defined $speed_factor && $speed_factor != 1) {
-        $gcode .= sprintf " F%.${dec}f", ($self->last_f * $speed_factor);
-        $self->force_f(1);  # next move will need explicit F
-    } elsif ($self->force_f) {
-        $gcode .= sprintf " F%.${dec}f", $self->last_f;
-        $self->force_f(0);
     }
     $gcode .= sprintf " F%.${dec}f", $F if defined $F;
     
@@ -509,7 +498,6 @@ sub set_bed_temperature {
 }
 
 # http://hydraraptor.blogspot.it/2010/12/frequency-limit.html
-# the following implementation is inspired by Marlin code
 sub _limit_frequency {
     my $self = shift;
     my ($point) = @_;
@@ -518,25 +506,28 @@ sub _limit_frequency {
     my $min_time = 1 / ($Slic3r::Config->vibration_limit * 60);  # in minutes
     
     # calculate the move vector and move direction
-    my @move = map unscale $_, @{ Slic3r::Line->new($self->last_pos, $point)->vector->[B] };
-    my @dir = map { $move[$_] ? (($move[$_] > 0) ? 1 : -1) : 0 } X,Y;
+    my $vector = Slic3r::Line->new($self->last_pos, $point)->vector;
+    my @dir = map { $vector->[B][$_] <=> 0 } X,Y;
     
-    my $segment_time = abs(max(@move)) / $self->speeds->{$self->speed};  # in minutes
-    if ($segment_time > 0) {
-        my @max_segment_time = ();
+    my $time = (unscale $vector->length) / $self->speeds->{$self->speed};  # in minutes
+    if ($time > 0) {
+        my @pause = ();
         foreach my $axis (X,Y) {
-            if ($self->last_dir->[$axis] == $dir[$axis]) {
-                $self->segment_time->[$axis][0] += $segment_time;
-            } else {
-                @{ $self->segment_time->[$axis] } = ($segment_time, @{ $self->segment_time->[$axis] }[0,1]);
+            if ($dir[$axis] != 0 && $self->last_dir->[$axis] != $dir[$axis]) {
+                if ($self->last_dir->[$axis] != 0) {
+                    # this axis is changing direction: check whether we need to pause
+                    if ($self->dir_time->[$axis] < $min_time) {
+                        push @pause, ($min_time - $self->dir_time->[$axis]);
+                    }
+                }
+                $self->last_dir->[$axis] = $dir[$axis];
+                $self->dir_time->[$axis] = 0;
             }
-            $max_segment_time[$axis] = max($self->segment_time->[$axis][0], max($self->segment_time->[$axis][1], $self->segment_time->[$axis][2]));
-            $self->last_dir->[$axis] = $dir[$axis] if $dir[$axis];
+            $self->dir_time->[$axis] += $time;
         }
         
-        my $min_segment_time = min(@max_segment_time);
-        if ($min_segment_time < $min_time) {
-            return sprintf "G4 P%d\n", ($min_time - $min_segment_time) * 60 * 1000;
+        if (@pause) {
+            return sprintf "G4 P%d\n", max(@pause) * 60 * 1000;
         }
     }
     
diff --git a/lib/Slic3r/Test.pm b/lib/Slic3r/Test.pm
index ca95d5de4..7f5f4776a 100644
--- a/lib/Slic3r/Test.pm
+++ b/lib/Slic3r/Test.pm
@@ -5,15 +5,21 @@ use warnings;
 use IO::Scalar;
 use Slic3r::Geometry qw(epsilon);
 
+my %cuboids = (
+    '20mm_cube' => [20,20,20],
+    '2x20x10'   => [2, 20,10],
+);
+
 sub init_print {
     my ($model_name, %params) = @_;
     
     my $model = Slic3r::Model->new;
     {
         my ($vertices, $facets);
-        if ($model_name eq '20mm_cube') {
+        if ($cuboids{$model_name}) {
+            my ($x, $y, $z) = @{ $cuboids{$model_name} };
             $vertices = [
-                [10,10,-10], [10,-10,-10], [-10,-10,-10], [-10,10,-10], [10,10,10], [-10,10,10], [-10,-10,10], [10,-10,10],
+                [$x,$y,0], [$x,0,0], [0,0,0], [0,$y,0], [$x,$y,$z], [0,$y,$z], [0,0,$z], [$x,0,$z],
             ];
             $facets = [
                 [0,1,2], [0,2,3], [4,5,6], [4,6,7], [0,4,7], [0,7,1], [1,7,6], [1,6,2], [2,6,5], [2,5,3], [4,0,3], [4,3,5],
@@ -65,6 +71,7 @@ sub parse {
     my ($cb) = @_;
     
     foreach my $line (split /\n/, $self->gcode) {
+        print "$line\n" if $Verbose || $ENV{SLIC3R_TESTS_GCODE};
         $line =~ s/\s*;(.*)//; # strip comment
         next if $line eq '';
         my $comment = $1;
@@ -95,7 +102,6 @@ sub parse {
         }
         
         # run callback
-        printf "$line\n" if $Verbose;
         $cb->($self, $command, \%args, \%info);
         
         # update coordinates
diff --git a/t/retraction.t b/t/retraction.t
index c1381c671..9c4506e8f 100644
--- a/t/retraction.t
+++ b/t/retraction.t
@@ -1,4 +1,4 @@
-use Test::More tests => 6;
+use Test::More tests => 9;
 use strict;
 use warnings;
 
@@ -77,7 +77,9 @@ my $retract_tests = sub {
 };
 
 $retract_tests->('');
+$config->set('duplicate', 2);
+$retract_tests->(' (duplicate)');
 $config->set('g0', 1);
-$retract_tests->(' (G0)');
+$retract_tests->(' (G0 and duplicate)');
 
 __END__
diff --git a/t/vibrationlimit.t b/t/vibrationlimit.t
new file mode 100644
index 000000000..b45409560
--- /dev/null
+++ b/t/vibrationlimit.t
@@ -0,0 +1,95 @@
+use Test::More tests => 9;
+use strict;
+use warnings;
+
+BEGIN {
+    use FindBin;
+    use lib "$FindBin::Bin/../lib";
+}
+
+use Slic3r;
+use Slic3r::Geometry qw(epsilon);
+use Slic3r::Test;
+
+my $config = Slic3r::Config->new_from_defaults;
+
+# tolerance, in minutes
+# (our time estimation differs from the internal one because of decimals truncation)
+my $epsilon = 0.002;
+
+my $test = sub {
+    my ($conf) = @_;
+    $conf ||= $config;
+    
+    my $print = Slic3r::Test::init_print('2x20x10', config => $conf);
+    
+    my $min_time        = 1 / ($conf->vibration_limit * 60);  # minimum time between direction changes in minutes
+    my %dir             = (X => 0, Y => 0);
+    my %dir_time        = (X => 0, Y => 0);
+    my %dir_sleep_time  = (X => 0, Y => 0);
+    my $last_cmd_pause  = 0;
+    Slic3r::Test::GCodeReader->new(gcode => Slic3r::Test::gcode($print))->parse(sub {
+        my ($self, $cmd, $args, $info) = @_;
+        
+        if ($cmd !~ /^G[01]$/) {
+            if ($cmd eq 'G4') {
+                $last_cmd_pause = (($args->{P} // 0) / 1000 + ($args->{S} // 0)) / 60;  # in minutes
+                $dir_sleep_time{$_} += $last_cmd_pause for qw(X Y);
+                $last_cmd_pause -= $epsilon;  # error builds up
+            }
+            return;
+        }
+        
+        # Z moves are slow enough that we can consider any vibration interrupted
+        if ($info->{dist_Z}) {
+            $dir_time{$_} += 99999 for qw(X Y);
+            $last_cmd_pause = 0;
+            return;
+        } elsif ($info->{dist_E} != 0 && $info->{dist_XY} == 0) {
+            my $time = abs($info->{dist_E}) / ($args->{F} // $self->F);  # in minutes
+            $dir_time{$_} += $time for qw(X Y);
+            $last_cmd_pause = 0;
+            return;
+        }
+        
+        # compute move time (this assumes that the speed is XY-bound, which happens very likely)
+        my $time = abs($info->{dist_XY}) / ($args->{F} // $self->F);  # in minutes
+        
+        my $one_axis_would_trigger_limit_without_pause = 0;
+        foreach my $axis (qw(X Y)) {
+            # are we changing direction on this axis?
+            my $dir = $info->{"dist_$axis"} <=> 0;
+            if ($dir != 0 && $dir{$axis} != $dir) {
+                # this move changes direction on this axis
+                if ($dir{$axis} != 0) {
+                    if (($dir_time{$axis} + $dir_sleep_time{$axis}) < ($min_time - $epsilon)) {
+                        fail 'vibration limit exceeded';
+                    }
+                    $one_axis_would_trigger_limit_without_pause = 1
+                        if ($dir_time{$axis} - $last_cmd_pause) < $min_time;
+                }
+                $dir{$axis}             = $dir;
+                $dir_time{$axis}        = 0;
+                $dir_sleep_time{$axis}  = 0;
+            }
+            $dir_time{$axis} += $time;
+        }
+        fail 'no unnecessary pauses are added'
+            if $last_cmd_pause > $epsilon && !$one_axis_would_trigger_limit_without_pause;
+        
+        $last_cmd_pause = 0;
+    });
+    
+    1;
+};
+
+$config->set('gcode_comments', 1);
+$config->set('perimeters', 1);
+foreach my $frequency (5, 10, 15) {
+    foreach my $gapfillspeed (20, 50, 100) {
+        $config->set('vibration_limit', $frequency);
+        ok $test->(), "vibrations limited to ${frequency}Hz (gap fill speed = ${gapfillspeed} mm/s)";
+    }
+}
+
+__END__