From 96dd106f61d3dd683fc1acfc19e1fbc25af0204c Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Sat, 17 Nov 2012 10:40:15 +0100 Subject: [PATCH 1/8] Initial work for limiting vibrations --- lib/Slic3r/Config.pm | 8 ++++++++ lib/Slic3r/GUI/Tab.pm | 4 ++++ 2 files changed, 12 insertions(+) diff --git a/lib/Slic3r/Config.pm b/lib/Slic3r/Config.pm index 0bf7d7e953..6969633f82 100644 --- a/lib/Slic3r/Config.pm +++ b/lib/Slic3r/Config.pm @@ -410,6 +410,14 @@ our $Options = { type => 'f', default => 1, }, + 'vibration_limit' => { + label => 'Vibration limit', + tooltip => 'This experimental option will slow down those parts hitting the configured frequency limit. The purpose of limiting vibrations is to avoid mechanical resonance.', + sidetext => 'Hz', + cli => 'vibration-limit=f', + type => 'f', + default => 15, + }, # print options 'perimeters' => { diff --git a/lib/Slic3r/GUI/Tab.pm b/lib/Slic3r/GUI/Tab.pm index e1469053c9..b9c445c597 100644 --- a/lib/Slic3r/GUI/Tab.pm +++ b/lib/Slic3r/GUI/Tab.pm @@ -634,6 +634,10 @@ sub build { }, ], }, + { + title => 'Advanced', + options => [qw(vibration_limit)], + }, ]); $self->add_options_page('Custom G-code', 'cog.png', optgroups => [ From 600e951fd83a422d9b64a6dfa18d697653da25c8 Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Sat, 17 Nov 2012 12:08:19 +0100 Subject: [PATCH 2/8] Completed vibration limit --- README.markdown | 3 ++- lib/Slic3r/Config.pm | 2 +- lib/Slic3r/GCode.pm | 44 ++++++++++++++++++++++++++++++++++++++------ lib/Slic3r/Line.pm | 5 +++++ slic3r.pl | 1 + 5 files changed, 47 insertions(+), 8 deletions(-) diff --git a/README.markdown b/README.markdown index 3d708d7220..4fa28d7a3d 100644 --- a/README.markdown +++ b/README.markdown @@ -263,7 +263,8 @@ The author of the Silk icon set is Mark James. --support-material-extrusion-width Set a different extrusion width for support material --bridge-flow-ratio Multiplier for extrusion when bridging (> 0, default: 1) - + --vibration-limit Experimental frequency limit to avoid resonance (Hz, default: 15) + Multiple extruder options: --extruder-offset Offset of each extruder, if firmware doesn't handle the displacement (can be specified multiple times, default: 0x0) diff --git a/lib/Slic3r/Config.pm b/lib/Slic3r/Config.pm index 6969633f82..49542067b0 100644 --- a/lib/Slic3r/Config.pm +++ b/lib/Slic3r/Config.pm @@ -412,7 +412,7 @@ our $Options = { }, 'vibration_limit' => { label => 'Vibration limit', - tooltip => 'This experimental option will slow down those parts hitting the configured frequency limit. The purpose of limiting vibrations is to avoid mechanical resonance.', + tooltip => 'This experimental option will slow down those moves hitting the configured frequency limit. The purpose of limiting vibrations is to avoid mechanical resonance. Set zero to disable.', sidetext => 'Hz', cli => 'vibration-limit=f', type => 'f', diff --git a/lib/Slic3r/GCode.pm b/lib/Slic3r/GCode.pm index f725ee79d0..8be61946dd 100644 --- a/lib/Slic3r/GCode.pm +++ b/lib/Slic3r/GCode.pm @@ -180,23 +180,55 @@ sub extrude_path { $self->speed( $role_speeds{$path->role} || die "Unknown role: " . $path->role ); my $path_length = 0; if ($path->isa('Slic3r::ExtrusionPath::Arc')) { - $path_length = $path->length; + $path_length = unscale $path->length; $gcode .= $self->G2_G3($path->points->[-1], $path->orientation, $path->center, $e * unscale $path_length, $description); } else { + my @moves = (); + my @last_moves = (); + my $speed_mms = $self->speeds->{$self->speed} / 60; foreach my $line ($path->lines) { - my $line_length = $line->length; + my $line_length = unscale $line->length; $path_length += $line_length; - $gcode .= $self->G1($line->[B], undef, $e * unscale $line_length, $description); + + # apply frequency limit + # http://hydraraptor.blogspot.it/2010/12/frequency-limit.html + if ($Slic3r::Config->vibration_limit && @{ $path->polyline } >= 4) { # optimization: resonance isn't triggered with less than three moves + my $freq = $speed_mms / $line_length; + if ($freq >= $Slic3r::Config->vibration_limit) { + my $vector = $line->vector; + if (@last_moves >= 1) { + if ($vector->[B][X] * $last_moves[-1][B][X] > 0 && $vector->[B][Y] * $last_moves[-1][B][Y] > 0) { + # if both X and Y have the same direction (sign), reset the buffer as there's no shaking + @last_moves = (); + } + } + push @last_moves, $vector; + if (@last_moves >= 3) { + my ($shortest_length) = reverse sort map $_->length, @last_moves; + my $speed = $Slic3r::Config->vibration_limit * unscale $shortest_length; + ###Slic3r::debugf "Reducing speed to %s mm/s (%s Hz vibration detected)\n", $speed, $freq; + $self->speed($speed * 60); + } + } else { + @last_moves = (); + } + } + + push @moves, [ $line->[B], $e * unscale $line_length ]; + } + + foreach my $move (@moves) { + $gcode .= $self->G1($move->[0], undef, $move->[1], $description); } } if ($Slic3r::Config->cooling) { - my $path_time = unscale($path_length) / $self->speeds->{$self->last_speed} * 60; + my $path_time = $path_length / $self->speeds->{$self->last_speed} * 60; if ($self->layer->id == 0) { $path_time = $Slic3r::Config->first_layer_speed =~ /^(\d+(?:\.\d+)?)%$/ ? $path_time / ($1/100) - : unscale($path_length) / $Slic3r::Config->first_layer_speed * 60; + : $path_length / $Slic3r::Config->first_layer_speed * 60; } $self->elapsed_time($self->elapsed_time + $path_time); } @@ -366,7 +398,7 @@ sub _Gx { # apply the speed reduction for print moves on bottom layer my $speed_f = $speed eq 'retract' ? ($self->extruder->retract_speed_mm_min) - : $self->speeds->{$speed}; + : $self->speeds->{$speed} // $speed; if ($e && $self->layer && $self->layer->id == 0 && $comment !~ /retract/) { $speed_f = $Slic3r::Config->first_layer_speed =~ /^(\d+(?:\.\d+)?)%$/ ? ($speed_f * $1/100) diff --git a/lib/Slic3r/Line.pm b/lib/Slic3r/Line.pm index 58c9479d3c..d0d0a3da7e 100644 --- a/lib/Slic3r/Line.pm +++ b/lib/Slic3r/Line.pm @@ -32,6 +32,11 @@ sub length { return Slic3r::Geometry::line_length($self); } +sub vector { + my $self = shift; + return (ref $self)->new([0,0], [map $self->[B][$_] - $self->[A][$_], X,Y]); +} + sub atan { my $self = shift; return Slic3r::Geometry::line_atan($self); diff --git a/slic3r.pl b/slic3r.pl index d540fcae43..6e011ec883 100755 --- a/slic3r.pl +++ b/slic3r.pl @@ -311,6 +311,7 @@ $j --support-material-extrusion-width Set a different extrusion width for support material --bridge-flow-ratio Multiplier for extrusion when bridging (> 0, default: $config->{bridge_flow_ratio}) + --vibration-limit Experimental frequency limit to avoid resonance (Hz, default: $config->{vibration_limit}) Multiple extruder options: --extruder-offset Offset of each extruder, if firmware doesn't handle the displacement From 72007c4f6a2d8f5a9f04be5fce1cab3eeb0ef50c Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Sat, 17 Nov 2012 18:07:13 +0100 Subject: [PATCH 3/8] Rewrite frequency limiting code --- lib/Slic3r/GCode.pm | 134 +++++++++++++++++++++++++++++++++----------- lib/Slic3r/Print.pm | 4 +- 2 files changed, 104 insertions(+), 34 deletions(-) diff --git a/lib/Slic3r/GCode.pm b/lib/Slic3r/GCode.pm index 8be61946dd..9b4de6abac 100644 --- a/lib/Slic3r/GCode.pm +++ b/lib/Slic3r/GCode.pm @@ -184,42 +184,10 @@ sub extrude_path { $gcode .= $self->G2_G3($path->points->[-1], $path->orientation, $path->center, $e * unscale $path_length, $description); } else { - my @moves = (); - my @last_moves = (); - my $speed_mms = $self->speeds->{$self->speed} / 60; foreach my $line ($path->lines) { my $line_length = unscale $line->length; $path_length += $line_length; - - # apply frequency limit - # http://hydraraptor.blogspot.it/2010/12/frequency-limit.html - if ($Slic3r::Config->vibration_limit && @{ $path->polyline } >= 4) { # optimization: resonance isn't triggered with less than three moves - my $freq = $speed_mms / $line_length; - if ($freq >= $Slic3r::Config->vibration_limit) { - my $vector = $line->vector; - if (@last_moves >= 1) { - if ($vector->[B][X] * $last_moves[-1][B][X] > 0 && $vector->[B][Y] * $last_moves[-1][B][Y] > 0) { - # if both X and Y have the same direction (sign), reset the buffer as there's no shaking - @last_moves = (); - } - } - push @last_moves, $vector; - if (@last_moves >= 3) { - my ($shortest_length) = reverse sort map $_->length, @last_moves; - my $speed = $Slic3r::Config->vibration_limit * unscale $shortest_length; - ###Slic3r::debugf "Reducing speed to %s mm/s (%s Hz vibration detected)\n", $speed, $freq; - $self->speed($speed * 60); - } - } else { - @last_moves = (); - } - } - - push @moves, [ $line->[B], $e * unscale $line_length ]; - } - - foreach my $move (@moves) { - $gcode .= $self->G1($move->[0], undef, $move->[1], $description); + $gcode .= $self->G1($line->[B], undef, $e * $line_length, $description); } } @@ -500,4 +468,104 @@ sub set_bed_temperature { return $gcode; } +# http://hydraraptor.blogspot.it/2010/12/frequency-limit.html +sub limit_frequency { + my $self = shift; + my ($gcode) = @_; + + return $gcode if $Slic3r::Config->vibration_limit == 0; + + my $current_gcode = $gcode; + $gcode = ''; + my ($X, $Y, $F); + my @last_moves = (); + my $longest_move; + my $buffer = ''; + my $vibration_limit_min = $Slic3r::Config->vibration_limit * 60; + + my $flush_buffer = sub { + my ($line) = @_; + + if (@last_moves >= 3) { + $buffer =~ s/ F[0-9.]+//g; + my $new_speed = $vibration_limit_min * $longest_move; + $gcode .= "G1 F$new_speed ; limit vibrations\n"; + $gcode .= $buffer; + $gcode .= "G1 F$F; restore previous speed\n"; + } else { + $gcode .= $buffer; + } + $gcode .= "$line\n"; + @last_moves = (); + $buffer = ''; + }; + + my $append_to_buffer = sub { + my ($line, $move, $freq) = @_; + + ###printf "move x = %s, y = %s\n", map $_ // '/', @$move[X,Y]; + ###printf " freq x = %s, y = %s\n", map $_ // '/', @$freq[X,Y]; + + $buffer .= "$line\n"; + push @last_moves, [ map $freq->[$_] ? $_ : undef, @$move ]; + for (grep defined $freq->[$_], X,Y) { + $longest_move = abs($move->[$_]) if $move->[$_] && (!defined $longest_move || abs($move->[$_]) > $longest_move); + } + }; + + foreach my $line (split /\n/, $current_gcode) { + if ($line =~ /^G[01] /) { + my ($x, $y, $f); + $x = $1 if $line =~ /X([0-9.]+)/; + $y = $1 if $line =~ /Y([0-9.]+)/; + $f = $1 if $line =~ /F([0-9.]+)/; + + # calculate the move vector + my @move = ( + (defined $x && $X) ? ($x - $X) : 0, + (defined $y && $Y) ? ($x - $Y) : 0, + ); + + # calculate the frequency (how many times the move can happen in one minute) for each axis + # and only keep it for the axes exceeding the configured limit. + # (undef = infinite) + my @freq = (); + for (X,Y) { + next if !$move[$_]; + my $freq = ($f // $F) / abs($move[$_]); + $freq[$_] = $freq if $freq >= $vibration_limit_min; + } + + # does our move exceed the limit? + if (grep defined $_, @freq) { + # if so, do we have a buffer already? + if (@last_moves) { + # if we have a buffer, compare the direction (for each axis) with the previous one. + if (($move[X] // 0) * ($last_moves[-1][X] // 0) < 0 || ($move[Y] // 0) * ($last_moves[-1][Y] // 0) < 0 && ($f // $F) == $F) { + # this move has opposite direction on at least one axis, and has also same speed: + # we can add it to the buffer + $append_to_buffer->($line, \@move, \@freq); + } else { + $flush_buffer->($line); + } + } else { + # if we have no buffer, store this move inside it + $append_to_buffer->($line, \@move, \@freq); + } + } else { + # if the move does not exceed any limit, flush the buffer and output this line + $flush_buffer->($line); + } + + $X = $x if defined $x; + $Y = $y if defined $y; + $F = $f if defined $f; + } else { + $gcode .= "$line\n"; + } + } + + return $gcode; +} + 1; diff --git a/lib/Slic3r/Print.pm b/lib/Slic3r/Print.pm index 84140e9ee3..dd877f9c51 100644 --- a/lib/Slic3r/Print.pm +++ b/lib/Slic3r/Print.pm @@ -3,7 +3,7 @@ use Moo; use File::Basename qw(basename fileparse); use File::Spec; -use List::Util qw(max first); +use List::Util qw(min max first); use Math::ConvexHull::MonotoneChain qw(convex_hull); use Slic3r::ExtrusionPath ':roles'; use Slic3r::Geometry qw(X Y Z X1 Y1 X2 Y2 MIN PI scale unscale move_points nearest_point); @@ -851,6 +851,8 @@ sub write_gcode { $gcode =~ s/^;_BRIDGE_FAN_END\n/ $gcodegen->set_fan($fan_speed, 1) /gmex; } + $gcode = $gcodegen->limit_frequency($gcode); + return $gcode; }; From f3164594eb2883d9d9cb2ce3bee4dd1455a45b60 Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Sun, 18 Nov 2012 11:33:53 +0100 Subject: [PATCH 4/8] More incomplete work --- lib/Slic3r/GCode.pm | 121 +++++++++++++++++++------------------------- lib/Slic3r/Print.pm | 2 +- 2 files changed, 53 insertions(+), 70 deletions(-) diff --git a/lib/Slic3r/GCode.pm b/lib/Slic3r/GCode.pm index 9b4de6abac..fca6fc8ea5 100644 --- a/lib/Slic3r/GCode.pm +++ b/lib/Slic3r/GCode.pm @@ -1,7 +1,7 @@ package Slic3r::GCode; use Moo; -use List::Util qw(first); +use List::Util qw(min first); use Slic3r::ExtrusionPath ':roles'; use Slic3r::Geometry qw(scale unscale scaled_epsilon points_coincide PI X Y A B); @@ -477,94 +477,77 @@ sub limit_frequency { my $current_gcode = $gcode; $gcode = ''; - my ($X, $Y, $F); - my @last_moves = (); - my $longest_move; - my $buffer = ''; - my $vibration_limit_min = $Slic3r::Config->vibration_limit * 60; + my %last; + my $F; - my $flush_buffer = sub { - my ($line) = @_; - - if (@last_moves >= 3) { - $buffer =~ s/ F[0-9.]+//g; - my $new_speed = $vibration_limit_min * $longest_move; - $gcode .= "G1 F$new_speed ; limit vibrations\n"; - $gcode .= $buffer; - $gcode .= "G1 F$F; restore previous speed\n"; - } else { - $gcode .= $buffer; - } - $gcode .= "$line\n"; - @last_moves = (); - $buffer = ''; - }; - - my $append_to_buffer = sub { - my ($line, $move, $freq) = @_; - - ###printf "move x = %s, y = %s\n", map $_ // '/', @$move[X,Y]; - ###printf " freq x = %s, y = %s\n", map $_ // '/', @$freq[X,Y]; - - $buffer .= "$line\n"; - push @last_moves, [ map $freq->[$_] ? $_ : undef, @$move ]; - for (grep defined $freq->[$_], X,Y) { - $longest_move = abs($move->[$_]) if $move->[$_] && (!defined $longest_move || abs($move->[$_]) > $longest_move); - } - }; + my $min_time = 1 / ($Slic3r::Config->vibration_limit * 60); + my @buffer = (); + my %last_dir = (); + my %time = (); + my @axes = qw(X Y E); foreach my $line (split /\n/, $current_gcode) { if ($line =~ /^G[01] /) { - my ($x, $y, $f); - $x = $1 if $line =~ /X([0-9.]+)/; - $y = $1 if $line =~ /Y([0-9.]+)/; + my %cur; + my $f; + for (@axes) { + $cur{$_} = $1 if $line =~ /$_([0-9.]+)/; + } $f = $1 if $line =~ /F([0-9.]+)/; # calculate the move vector - my @move = ( - (defined $x && $X) ? ($x - $X) : 0, - (defined $y && $Y) ? ($x - $Y) : 0, + my %move = ( + map { $_ => (defined $cur{$_} && defined $last{$_}) ? ($cur{$_} - $last{$_}) : 0 } @axes ); - # calculate the frequency (how many times the move can happen in one minute) for each axis - # and only keep it for the axes exceeding the configured limit. - # (undef = infinite) - my @freq = (); - for (X,Y) { - next if !$move[$_]; - my $freq = ($f // $F) / abs($move[$_]); - $freq[$_] = $freq if $freq >= $vibration_limit_min; - } + # check move directions + my %dir = ( + map { $_ => ($move{$_}) ? ($move{$_} > 0 ? 1 : -1) : undef } @axes + ); - # does our move exceed the limit? - if (grep defined $_, @freq) { - # if so, do we have a buffer already? - if (@last_moves) { - # if we have a buffer, compare the direction (for each axis) with the previous one. - if (($move[X] // 0) * ($last_moves[-1][X] // 0) < 0 || ($move[Y] // 0) * ($last_moves[-1][Y] // 0) < 0 && ($f // $F) == $F) { - # this move has opposite direction on at least one axis, and has also same speed: - # we can add it to the buffer - $append_to_buffer->($line, \@move, \@freq); - } else { - $flush_buffer->($line); + my @slowdown = (); + foreach my $axis (@axes) { + # are we changing direction on this axis? + # (actually: are we going positive while last move was negative?) + if (($last_dir{$axis} // 0) < 0 && ($dir{$axis} // 0) > 0) { + # changing direction on this axis! + if (defined $time{$axis} && $time{$axis} < $min_time) { + # direction change was too fast! we need to slow down + push @slowdown, $time{$axis} / $min_time; } - } else { - # if we have no buffer, store this move inside it - $append_to_buffer->($line, \@move, \@freq); + $time{$axis} = 0; + } elsif ($move{$axis}) { + # not changing direction on this axis + # then just calculate move time and sum it + $time{$axis} //= 0; + $time{$axis} += abs($move{$axis}) / ($f // $F); } + } + if (@slowdown) { + my $factor = min(@slowdown); + printf "SLOWDOWN! (slowdown = %d%%)\n", $factor * 100; + $gcode .= "; START SLOWDOWN ($factor)\n"; + $gcode .= "$_\n" for @buffer; + @buffer = (); + $gcode .= "; END SLOWDOWN\n"; + $gcode .= "$line\n"; } else { - # if the move does not exceed any limit, flush the buffer and output this line - $flush_buffer->($line); + push @buffer, $line; } - $X = $x if defined $x; - $Y = $y if defined $y; + for (@axes) { + $last{$_} = $cur{$_} if defined $cur{$_}; + $last_dir{$_} = $dir{$_} if defined $dir{$_}; + } $F = $f if defined $f; + } else { - $gcode .= "$line\n"; + push @buffer, $line; } } + $gcode .= "$_\n" for @buffer; + return $gcode; } diff --git a/lib/Slic3r/Print.pm b/lib/Slic3r/Print.pm index dd877f9c51..15e02b1c6a 100644 --- a/lib/Slic3r/Print.pm +++ b/lib/Slic3r/Print.pm @@ -3,7 +3,7 @@ use Moo; use File::Basename qw(basename fileparse); use File::Spec; -use List::Util qw(min max first); +use List::Util qw(max first); use Math::ConvexHull::MonotoneChain qw(convex_hull); use Slic3r::ExtrusionPath ':roles'; use Slic3r::Geometry qw(X Y Z X1 Y1 X2 Y2 MIN PI scale unscale move_points nearest_point); From 008633f013db909a95c678dcc90fdb3ac1682561 Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Sun, 18 Nov 2012 12:23:11 +0100 Subject: [PATCH 5/8] Working implementation of frequency limit --- lib/Slic3r/GCode.pm | 71 ++++++++++++++++++++++----------------------- 1 file changed, 35 insertions(+), 36 deletions(-) diff --git a/lib/Slic3r/GCode.pm b/lib/Slic3r/GCode.pm index fca6fc8ea5..42bcd2a32f 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 first); +use List::Util qw(min max first); use Slic3r::ExtrusionPath ':roles'; use Slic3r::Geometry qw(scale unscale scaled_epsilon points_coincide PI X Y A B); @@ -477,14 +477,15 @@ sub limit_frequency { my $current_gcode = $gcode; $gcode = ''; - my %last; - my $F; + + # the following code is inspired by Marlin frequency limit implementation my $min_time = 1 / ($Slic3r::Config->vibration_limit * 60); - my @buffer = (); - my %last_dir = (); - my %time = (); - my @axes = qw(X Y E); + my @axes = qw(X Y); + my %segment_time = (map { $_ => [0,0,0] } @axes); + my %last = (map { $_ => 0 } @axes); + my %last_dir = (map { $_ => 0 } @axes); + my $F; foreach my $line (split /\n/, $current_gcode) { if ($line =~ /^G[01] /) { @@ -502,52 +503,50 @@ sub limit_frequency { # check move directions my %dir = ( - map { $_ => ($move{$_}) ? ($move{$_} > 0 ? 1 : -1) : undef } @axes + map { $_ => ($move{$_}) ? ($move{$_} > 0 ? 1 : -1) : 0 } @axes ); - my @slowdown = (); - foreach my $axis (@axes) { - # are we changing direction on this axis? - # (actually: are we going positive while last move was negative?) - if (($last_dir{$axis} // 0) < 0 && ($dir{$axis} // 0) > 0) { - # changing direction on this axis! - if (defined $time{$axis} && $time{$axis} < $min_time) { - # direction change was too fast! we need to slow down - push @slowdown, $time{$axis} / $min_time; + my $factor = 1; + my $segment_time = abs(max(values %move)) / ($f // $F); + if ($segment_time > 0) { + my %max_segment_time = (); + foreach my $axis (@axes) { + # are we changing direction on this axis? + if ($last_dir{$axis} == $dir{$axis}) { + $segment_time{$axis}[0] += $segment_time; + } else { + @{ $segment_time{$axis} } = ($segment_time, @{ $segment_time{$axis} }[0,1]); } - $time{$axis} = 0; - } elsif ($move{$axis}) { - # not changing direction on this axis - # then just calculate move time and sum it - $time{$axis} //= 0; - $time{$axis} += abs($move{$axis}) / ($f // $F); + + $max_segment_time{$axis} = max($segment_time{$axis}[0], max($segment_time{$axis}[1], $segment_time{$axis}[2])); + } + + my $min_segment_time = min(values %max_segment_time); + if ($min_segment_time < $min_time) { + $factor = $min_segment_time / $min_time; } } - if (@slowdown) { - my $factor = min(@slowdown); - printf "SLOWDOWN! (slowdown = %d%%)\n", $factor * 100; - $gcode .= "; START SLOWDOWN ($factor)\n"; - $gcode .= "$_\n" for @buffer; - @buffer = (); - $gcode .= "; END SLOWDOWN\n"; + + if ($factor == 1) { $gcode .= "$line\n"; } else { - push @buffer, $line; + $line =~ s/ F[0-9.]+//; + my $new_speed = sprintf '%.3f', ($f // $F) * $factor; + $line =~ s/^(G[01]) /$1 F$new_speed /; + $gcode .= "$line\nG1 F" . ($f // $F) . "\n"; } for (@axes) { - $last{$_} = $cur{$_} if defined $cur{$_}; - $last_dir{$_} = $dir{$_} if defined $dir{$_}; + $last{$_} = $cur{$_} if $cur{$_}; + $last_dir{$_} = $dir{$_} if $dir{$_}; } $F = $f if defined $f; } else { - push @buffer, $line; + $gcode .= "$line\n"; } } - $gcode .= "$_\n" for @buffer; - return $gcode; } From 7a87a76391d3ac52f4a69dd5a3231ed3b919ebf0 Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Sun, 18 Nov 2012 15:28:13 +0100 Subject: [PATCH 6/8] Refactor frequency limit to avoid processing G-code --- lib/Slic3r/GCode.pm | 140 ++++++++++++++++++-------------------------- lib/Slic3r/Print.pm | 2 - 2 files changed, 56 insertions(+), 86 deletions(-) diff --git a/lib/Slic3r/GCode.pm b/lib/Slic3r/GCode.pm index 42bcd2a32f..466dfbf4f0 100644 --- a/lib/Slic3r/GCode.pm +++ b/lib/Slic3r/GCode.pm @@ -19,10 +19,16 @@ has 'total_extrusion_length' => (is => 'rw', default => sub {0} ); 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 'last_path' => (is => 'rw'); has 'dec' => (is => 'ro', default => sub { 3 } ); +# used for vibration limit: +has 'last_dir' => (is => 'ro', default => sub { [0,0] }); +has 'segment_time' => (is => 'ro', default => sub { [ [0,0,0], [0,0,0] ] }); + # calculate speeds (mm/min) has 'speeds' => ( is => 'ro', @@ -69,6 +75,7 @@ sub move_z { my $current_z = $self->z; if (!defined $current_z || $current_z != ($z + $self->lifted)) { $gcode .= $self->retract(move_z => $z); + $self->speed('travel'); $gcode .= $self->G0(undef, $z, 0, $comment || ('move to next layer (' . $self->layer->id . ')')) unless ($current_z // -1) != ($self->z // -1); } @@ -148,6 +155,7 @@ sub extrude_path { my $point = Slic3r::Geometry::point_along_segment(@$last_line, $last_line->length + scale $path->flow_spacing); bless $point, 'Slic3r::Point'; $point->rotate(PI/6, $last_line->[B]); + $self->speed('travel'); $gcode .= $self->G0($point, undef, 0, "move inwards before travel"); } } @@ -157,6 +165,7 @@ sub extrude_path { } # go to first point of extrusion path + $self->speed('travel'); $gcode .= $self->G0($path->points->[0], undef, 0, "move to first $description point") if !points_coincide($self->last_pos, $path->points->[0]); @@ -267,6 +276,7 @@ sub unretract { my $gcode = ""; if ($self->lifted) { + $self->speed('travel'); $gcode .= $self->G0(undef, $self->z - $self->lifted, 0, 'restore layer Z'); $self->lifted(0); } @@ -311,10 +321,12 @@ 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]; #** + $speed_factor = $self->_limit_frequency($point); $self->last_pos($point->clone); } if (defined $z && (!defined $self->z || $z != $self->z)) { @@ -322,7 +334,7 @@ sub _G0_G1 { $gcode .= sprintf " Z%.${dec}f", $z; } - return $self->_Gx($gcode, $e, $comment); + return $self->_Gx($gcode, $e, $speed_factor, $comment); } sub G2_G3 { @@ -342,39 +354,45 @@ sub G2_G3 { ($center->[Y] - $self->last_pos->[Y]) * &Slic3r::SCALING_FACTOR; $self->last_pos($point); - return $self->_Gx($gcode, $e, $comment); + return $self->_Gx($gcode, $e, undef, $comment); } sub _Gx { my $self = shift; - my ($gcode, $e, $comment) = @_; + my ($gcode, $e, $speed_factor, $comment) = @_; my $dec = $self->dec; - # determine speed - my $speed = ($e ? $self->speed : 'travel'); - # output speed if it's different from last one used # (goal: reduce gcode size) my $append_bridge_off = 0; - if ($speed ne $self->last_speed) { - if ($speed eq 'bridge') { + my $F; + if ($self->speed ne $self->last_speed) { + if ($self->speed eq 'bridge') { $gcode = ";_BRIDGE_FAN_START\n$gcode"; } elsif ($self->last_speed eq 'bridge') { $append_bridge_off = 1; } # apply the speed reduction for print moves on bottom layer - my $speed_f = $speed eq 'retract' + $F = $self->speed eq 'retract' ? ($self->extruder->retract_speed_mm_min) - : $self->speeds->{$speed} // $speed; + : $self->speeds->{$self->speed} // $self->speed; if ($e && $self->layer && $self->layer->id == 0 && $comment !~ /retract/) { - $speed_f = $Slic3r::Config->first_layer_speed =~ /^(\d+(?:\.\d+)?)%$/ - ? ($speed_f * $1/100) + $F = $Slic3r::Config->first_layer_speed =~ /^(\d+(?:\.\d+)?)%$/ + ? ($F * $1/100) : $Slic3r::Config->first_layer_speed * 60; } - $gcode .= sprintf " F%.${dec}f", $speed_f; - $self->last_speed($speed); + $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; # output extrusion distance if ($e && $Slic3r::Config->extrusion_axis) { @@ -469,85 +487,39 @@ sub set_bed_temperature { } # http://hydraraptor.blogspot.it/2010/12/frequency-limit.html -sub limit_frequency { +# the following implementation is inspired by Marlin code +sub _limit_frequency { my $self = shift; - my ($gcode) = @_; - - return $gcode if $Slic3r::Config->vibration_limit == 0; - - my $current_gcode = $gcode; - $gcode = ''; - - # the following code is inspired by Marlin frequency limit implementation + my ($point) = @_; + return if $Slic3r::Config->vibration_limit == 0; my $min_time = 1 / ($Slic3r::Config->vibration_limit * 60); - my @axes = qw(X Y); - my %segment_time = (map { $_ => [0,0,0] } @axes); - my %last = (map { $_ => 0 } @axes); - my %last_dir = (map { $_ => 0 } @axes); - my $F; - foreach my $line (split /\n/, $current_gcode) { - if ($line =~ /^G[01] /) { - my %cur; - my $f; - for (@axes) { - $cur{$_} = $1 if $line =~ /$_([0-9.]+)/; - } - $f = $1 if $line =~ /F([0-9.]+)/; - - # calculate the move vector - my %move = ( - map { $_ => (defined $cur{$_} && defined $last{$_}) ? ($cur{$_} - $last{$_}) : 0 } @axes - ); - - # check move directions - my %dir = ( - map { $_ => ($move{$_}) ? ($move{$_} > 0 ? 1 : -1) : 0 } @axes - ); - - my $factor = 1; - my $segment_time = abs(max(values %move)) / ($f // $F); - if ($segment_time > 0) { - my %max_segment_time = (); - foreach my $axis (@axes) { - # are we changing direction on this axis? - if ($last_dir{$axis} == $dir{$axis}) { - $segment_time{$axis}[0] += $segment_time; - } else { - @{ $segment_time{$axis} } = ($segment_time, @{ $segment_time{$axis} }[0,1]); - } - - $max_segment_time{$axis} = max($segment_time{$axis}[0], max($segment_time{$axis}[1], $segment_time{$axis}[2])); - } - - my $min_segment_time = min(values %max_segment_time); - if ($min_segment_time < $min_time) { - $factor = $min_segment_time / $min_time; - } - } - - if ($factor == 1) { - $gcode .= "$line\n"; + # 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 $factor = 1; + my $segment_time = abs(max(@move)) / $self->speeds->{$self->speed}; + if ($segment_time > 0) { + my @max_segment_time = (); + foreach my $axis (X,Y) { + if ($self->last_dir->[$axis] == $dir[$axis]) { + $self->segment_time->[$axis][0] += $segment_time; } else { - $line =~ s/ F[0-9.]+//; - my $new_speed = sprintf '%.3f', ($f // $F) * $factor; - $line =~ s/^(G[01]) /$1 F$new_speed /; - $gcode .= "$line\nG1 F" . ($f // $F) . "\n"; + @{ $self->segment_time->[$axis] } = ($segment_time, @{ $self->segment_time->[$axis] }[0,1]); } - - for (@axes) { - $last{$_} = $cur{$_} if $cur{$_}; - $last_dir{$_} = $dir{$_} if $dir{$_}; - } - $F = $f if defined $f; - - } else { - $gcode .= "$line\n"; + $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]; + } + + my $min_segment_time = min(@max_segment_time); + if ($min_segment_time < $min_time) { + $factor = $min_segment_time / $min_time; } } - return $gcode; + return $factor; } 1; diff --git a/lib/Slic3r/Print.pm b/lib/Slic3r/Print.pm index 15e02b1c6a..84140e9ee3 100644 --- a/lib/Slic3r/Print.pm +++ b/lib/Slic3r/Print.pm @@ -851,8 +851,6 @@ sub write_gcode { $gcode =~ s/^;_BRIDGE_FAN_END\n/ $gcodegen->set_fan($fan_speed, 1) /gmex; } - $gcode = $gcodegen->limit_frequency($gcode); - return $gcode; }; From 08700aa942507e9c323887acd183dcb1b2ffd13f Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Sun, 18 Nov 2012 15:42:59 +0100 Subject: [PATCH 7/8] Keep bridge flow unchanged even with the new overlapping spacing --- lib/Slic3r/Fill.pm | 6 ++++-- lib/Slic3r/Flow.pm | 13 +++++++++++++ 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/lib/Slic3r/Fill.pm b/lib/Slic3r/Fill.pm index e578b54940..20756bdd45 100644 --- a/lib/Slic3r/Fill.pm +++ b/lib/Slic3r/Fill.pm @@ -135,8 +135,7 @@ sub make_fill { $filler = $Slic3r::Config->solid_fill_pattern; if ($is_bridge) { $filler = 'rectilinear'; - my $width = sqrt($Slic3r::Config->bridge_flow_ratio * ($layer->infill_flow->nozzle_diameter**2)); - $flow_spacing = $width + &Slic3r::OVERLAP_FACTOR * ($width * PI / 4 - $width); # this should be moved to Flow.pm + $flow_spacing = $layer->infill_flow->bridge_spacing; } elsif ($surface->surface_type == S_TYPE_INTERNALSOLID) { $filler = 'rectilinear'; } @@ -157,6 +156,9 @@ sub make_fill { } my $params = shift @paths; + # ugly hack(tm) to get the right amount of flow (GCode.pm should be fixed) + $params->{flow_spacing} = $layer->infill_flow->bridge_width if $is_bridge; + # save into layer next unless @paths; push @fills, Slic3r::ExtrusionPath::Collection->new( diff --git a/lib/Slic3r/Flow.pm b/lib/Slic3r/Flow.pm index a035b9d371..01992e7887 100644 --- a/lib/Slic3r/Flow.pm +++ b/lib/Slic3r/Flow.pm @@ -8,6 +8,8 @@ has 'layer_height' => (is => 'ro', default => sub { $Slic3r::Config->layer_ has 'width' => (is => 'rwp', builder => 1); has 'spacing' => (is => 'lazy'); +has 'bridge_width' => (is => 'lazy'); +has 'bridge_spacing' => (is => 'lazy'); has 'scaled_width' => (is => 'lazy'); has 'scaled_spacing' => (is => 'lazy'); @@ -67,6 +69,17 @@ sub clone { ); } +sub _build_bridge_width { + my $self = shift; + return sqrt($Slic3r::Config->bridge_flow_ratio * ($self->nozzle_diameter**2)); +} + +sub _build_bridge_spacing { + my $self = shift; + my $width = $self->bridge_width; + return $width + &Slic3r::OVERLAP_FACTOR * ($width * PI / 4 - $width); +} + sub _build_scaled_width { my $self = shift; return scale $self->width; From a66e8e547d067602a77d382643eedb3f109754f4 Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Sun, 18 Nov 2012 17:38:08 +0100 Subject: [PATCH 8/8] Apply Douglas-Peucker to all paths before generating G-code --- lib/Slic3r.pm | 3 ++- lib/Slic3r/ExtrusionPath.pm | 2 +- lib/Slic3r/GCode.pm | 1 + lib/Slic3r/Layer/Region.pm | 2 +- lib/Slic3r/Print.pm | 2 +- 5 files changed, 6 insertions(+), 4 deletions(-) diff --git a/lib/Slic3r.pm b/lib/Slic3r.pm index be105ed108..2cbe4437c3 100644 --- a/lib/Slic3r.pm +++ b/lib/Slic3r.pm @@ -57,7 +57,8 @@ use Slic3r::TriangleMesh; eval "use Slic3r::Build"; use constant SCALING_FACTOR => 0.000001; -use constant RESOLUTION => 0.01; +use constant RESOLUTION => 0.0125; +use constant SCALED_RESOLUTION => RESOLUTION / SCALING_FACTOR; use constant OVERLAP_FACTOR => 1; use constant SMALL_PERIMETER_LENGTH => (6.5 / SCALING_FACTOR) * 2 * PI; use constant LOOP_CLIPPING_LENGTH_OVER_SPACING => 0.15; diff --git a/lib/Slic3r/ExtrusionPath.pm b/lib/Slic3r/ExtrusionPath.pm index bae6e61cda..d392b5c40e 100644 --- a/lib/Slic3r/ExtrusionPath.pm +++ b/lib/Slic3r/ExtrusionPath.pm @@ -15,7 +15,7 @@ use Slic3r::Geometry qw(PI X Y epsilon deg2rad rotate_points); has 'polyline' => ( is => 'rw', required => 1, - handles => [qw(merge_continuous_lines lines length reverse clip_end)], + handles => [qw(merge_continuous_lines lines length reverse clip_end simplify)], ); # height is the vertical thickness of the extrusion expressed in mm diff --git a/lib/Slic3r/GCode.pm b/lib/Slic3r/GCode.pm index 466dfbf4f0..42c6793176 100644 --- a/lib/Slic3r/GCode.pm +++ b/lib/Slic3r/GCode.pm @@ -126,6 +126,7 @@ sub extrude_path { my ($path, $description, $recursive) = @_; $path = $path->unpack if $path->isa('Slic3r::ExtrusionPath::Packed'); + $path->simplify(&Slic3r::SCALED_RESOLUTION); # detect arcs if ($Slic3r::Config->gcode_arcs && !$recursive) { diff --git a/lib/Slic3r/Layer/Region.pm b/lib/Slic3r/Layer/Region.pm index e940d9ec82..3e4854ea1b 100644 --- a/lib/Slic3r/Layer/Region.pm +++ b/lib/Slic3r/Layer/Region.pm @@ -228,7 +228,7 @@ sub make_perimeters { # create one more offset to be used as boundary for fill { my @fill_boundaries = map $_->offset_ex(-$distance), @last_offsets; - $_->simplify(scale &Slic3r::RESOLUTION) for @fill_boundaries; + $_->simplify(&Slic3r::SCALED_RESOLUTION) for @fill_boundaries; push @{ $self->surfaces }, @fill_boundaries; } diff --git a/lib/Slic3r/Print.pm b/lib/Slic3r/Print.pm index 84140e9ee3..1b41ccdbfb 100644 --- a/lib/Slic3r/Print.pm +++ b/lib/Slic3r/Print.pm @@ -341,7 +341,7 @@ sub export_gcode { # simplify slices (both layer and region slices), # we only need the max resolution for perimeters foreach my $layer (map @{$_->layers}, @{$self->objects}) { - $_->simplify(scale &Slic3r::RESOLUTION) + $_->simplify(&Slic3r::SCALED_RESOLUTION) for @{$layer->slices}, (map $_->expolygon, map @{$_->slices}, @{$layer->regions}); }