From 1a3497b71daa035092ee7f44d2d1446b4866c451 Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci <aar@cpan.org> Date: Sun, 9 Dec 2012 18:33:25 +0100 Subject: [PATCH] Add tests for vibration limiting and fix implementation. Also includes a fix in set_shift() --- lib/Slic3r/GCode.pm | 65 +++++++++++++------------------ lib/Slic3r/Test.pm | 12 ++++-- t/retraction.t | 6 ++- t/vibrationlimit.t | 95 +++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 136 insertions(+), 42 deletions(-) create mode 100644 t/vibrationlimit.t 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__