diff --git a/README.markdown b/README.markdown index 8848cab54..7d1fed624 100644 --- a/README.markdown +++ b/README.markdown @@ -38,6 +38,7 @@ Slic3r current key features are: * ability to scale, rotate and duplicate input object; * customizable initial and final GCODE; * support material; +* cooling and fan control; * use different speed for bottom layer and perimeters. Experimental features include: @@ -50,7 +51,6 @@ Roadmap includes the following goals: * output some statistics; * support material for internal perimeters; * new and better GUI; -* cool; * more fill patterns. ## Is it usable already? Any known limitation? @@ -91,6 +91,8 @@ The author is Alessandro Ranellucci (me). Output file name format; all config options enclosed in brackets will be replaced by their values, as well as [input_filename_base] and [input_filename] (default: [input_filename_base].gcode) + --post-process Generated G-code will be processed with the supplied script; + call this more than once to process through multiple scripts. Printer options: --nozzle-diameter Diameter of nozzle in mm (default: 0.5) @@ -150,7 +152,7 @@ The author is Alessandro Ranellucci (me). home X axis [G28 X], disable motors [M84]). --support-material Generate support material for overhangs - Retraction options: + Retraction options: --retract-length Length of retraction in mm when pausing extrusion (default: 1) --retract-speed Speed for retraction in mm/s (default: 30) @@ -161,6 +163,17 @@ The author is Alessandro Ranellucci (me). Only retract before travel moves of this length in mm (default: 2) --retract-lift Lift Z by the given distance in mm when retracting (default: 0) + Cooling options: + --min-fan-speed Minimum fan speed (default: 35%) + --max-fan-speed Maximum fan speed (default: 100%) + --bridge-fan-speed Fan speed to use when bridging (default: 100%) + --fan-below-layer-time Enable fan if layer print time is below this approximate number + of seconds (default: 60) + --slowdown-below-layer-time Slow down if layer print time is below this approximate number + of seconds (default: 15) + --min-print-speed Minimum print speed speed (mm/s, default: 10) + --disable-fan-first-layers Disable fan for the first N layers (default: 1) + Skirt options: --skirts Number of skirts to draw (0+, default: 1) --skirt-distance Distance in mm between innermost skirt and object diff --git a/lib/Slic3r.pm b/lib/Slic3r.pm index 3b57f85c9..3b62ecc9b 100644 --- a/lib/Slic3r.pm +++ b/lib/Slic3r.pm @@ -114,6 +114,15 @@ our $retract_speed = 30; # mm/s our $retract_before_travel = 2; # mm our $retract_lift = 0; # mm +# cooling options +our $min_fan_speed = 35; +our $max_fan_speed = 100; +our $bridge_fan_speed = 100; +our $fan_below_layer_time = 60; +our $slowdown_below_layer_time = 15; +our $min_print_speed = 10; +our $disable_fan_first_layers = 1; + # skirt options our $skirts = 1; our $skirt_distance = 6; # mm diff --git a/lib/Slic3r/Config.pm b/lib/Slic3r/Config.pm index 8ec3c0def..80ba87ea1 100644 --- a/lib/Slic3r/Config.pm +++ b/lib/Slic3r/Config.pm @@ -292,6 +292,43 @@ our $Options = { type => 'f', }, + # cooling options + 'min_fan_speed' => { + label => 'Min fan speed (%)', + cli => 'min-fan-speed=i', + type => 'i', + }, + 'max_fan_speed' => { + label => 'Max fan speed (%)', + cli => 'max-fan-speed=i', + type => 'i', + }, + 'bridge_fan_speed' => { + label => 'Bridge fan speed (%)', + cli => 'bridge-fan-speed=i', + type => 'i', + }, + 'fan_below_layer_time' => { + label => 'Enable fan if layer print time is below (approximate seconds)', + cli => 'fan-below-layer-time=i', + type => 'i', + }, + 'slowdown_below_layer_time' => { + label => 'Slow down if layer print time is below (approximate seconds)', + cli => 'slowdown-below-layer-time=i', + type => 'i', + }, + 'min_print_speed' => { + label => 'Min print speed (mm/s)', + cli => 'min-print-speed=i', + type => 'i', + }, + 'disable_fan_first_layers' => { + label => 'Disable fan for the first N layers', + cli => 'disable-fan-first-layers=i', + type => 'i', + }, + # skirt options 'skirts' => { label => 'Loops', diff --git a/lib/Slic3r/Extruder.pm b/lib/Slic3r/Extruder.pm index e5235e6fc..785dca6ea 100644 --- a/lib/Slic3r/Extruder.pm +++ b/lib/Slic3r/Extruder.pm @@ -1,50 +1,36 @@ package Slic3r::Extruder; use Moo; -use Slic3r::Geometry qw(scale); +use Slic3r::Geometry qw(scale unscale); has 'layer' => (is => 'rw'); has 'shift_x' => (is => 'rw', default => sub {0} ); has 'shift_y' => (is => 'rw', default => sub {0} ); has 'z' => (is => 'rw', default => sub {0} ); -has 'print_feed_rate' => (is => 'rw'); +has 'speed' => (is => 'rw'); has 'extrusion_distance' => (is => 'rw', default => sub {0} ); +has 'elapsed_time' => (is => 'rw', default => sub {0} ); # seconds has 'total_extrusion_length' => (is => 'rw', default => sub {0} ); has 'retracted' => (is => 'rw', default => sub {1} ); # this spits out some plastic at start has 'lifted' => (is => 'rw', default => sub {0} ); has 'last_pos' => (is => 'rw', default => sub { Slic3r::Point->new(0,0) } ); -has 'last_f' => (is => 'rw', default => sub {0}); +has 'last_speed' => (is => 'rw', default => sub {""}); +has 'last_fan_speed' => (is => 'rw', default => sub {0}); has 'dec' => (is => 'ro', default => sub { 3 } ); -# calculate speeds -has 'travel_speed' => ( +# calculate speeds (mm/min) +has 'speeds' => ( is => 'ro', - default => sub { $Slic3r::travel_speed * 60 }, # mm/min -); -has 'perimeter_speed' => ( - is => 'ro', - default => sub { $Slic3r::perimeter_speed * 60 }, # mm/min -); -has 'small_perimeter_speed' => ( - is => 'ro', - default => sub { $Slic3r::small_perimeter_speed * 60 }, # mm/min -); -has 'infill_speed' => ( - is => 'ro', - default => sub { $Slic3r::infill_speed * 60 }, # mm/min -); -has 'solid_infill_speed' => ( - is => 'ro', - default => sub { $Slic3r::solid_infill_speed * 60 }, # mm/min -); -has 'bridge_speed' => ( - is => 'ro', - default => sub { $Slic3r::bridge_speed * 60 }, # mm/min -); -has 'retract_speed' => ( - is => 'ro', - default => sub { $Slic3r::retract_speed * 60 }, # mm/min + default => sub {{ + travel => 60 * $Slic3r::travel_speed, + perimeter => 60 * $Slic3r::perimeter_speed, + small_perimeter => 60 * $Slic3r::small_perimeter_speed, + infill => 60 * $Slic3r::infill_speed, + solid_infill => 60 * $Slic3r::solid_infill_speed, + bridge => 60 * $Slic3r::bridge_speed, + retract => 60 * $Slic3r::retract_speed, + }}, ); use Slic3r::Geometry qw(points_coincide PI X Y); @@ -151,23 +137,32 @@ sub extrude_path { * (4 / (($Slic3r::filament_diameter ** 2) * PI)); # extrude arc or line - $self->print_feed_rate( - $path->role =~ /^(perimeter|skirt|support-material)$/o ? $self->perimeter_speed - : $path->role eq 'small-perimeter' ? $self->small_perimeter_speed - : $path->role eq 'fill' ? $self->infill_speed - : $path->role eq 'solid-fill' ? $self->solid_infill_speed - : $path->role eq 'bridge' ? $self->bridge_speed + $self->speed( + $path->role =~ /^(perimeter|skirt|support-material)$/o ? 'perimeter' + : $path->role eq 'small-perimeter' ? 'small_perimeter' + : $path->role eq 'fill' ? 'infill' + : $path->role eq 'solid-fill' ? 'solid_infill' + : $path->role eq 'bridge' ? 'bridge' : die "Unknown role: " . $path->role ); + my $path_length = 0; if ($path->isa('Slic3r::ExtrusionPath::Arc')) { + $path_length = $path->length; $gcode .= $self->G2_G3($path->points->[-1], $path->orientation, - $path->center, $e * $path->length, $description); + $path->center, $e * $path_length, $description); } else { foreach my $line ($path->lines) { - $gcode .= $self->G1($line->b, undef, $e * $line->length, $description); + my $line_length = $line->length; + $path_length += $line_length; + $gcode .= $self->G1($line->b, undef, $e * $line_length, $description); } } + # TODO: optimize: avoid calculation if cooling is disabled + if (1) { + $self->elapsed_time($self->elapsed_time + (unscale($path_length) / $self->speeds->{$self->last_speed} * 60)); + } + return $gcode; } @@ -179,7 +174,7 @@ sub retract { && !$self->retracted; # prepare moves - $self->print_feed_rate($self->retract_speed); + $self->speed('retract'); my $retract = [undef, undef, -$Slic3r::retract_length, "retract"]; my $lift = ($Slic3r::retract_lift == 0 || defined $params{move_z}) ? undef @@ -229,7 +224,7 @@ sub unretract { $self->lifted(0); } - $self->print_feed_rate($self->retract_speed); + $self->speed('retract'); $gcode .= $self->G0(undef, undef, ($Slic3r::retract_length + $Slic3r::retract_restart_extra), "compensate retraction"); @@ -239,7 +234,7 @@ sub unretract { sub set_acceleration { my $self = shift; my ($acceleration) = @_; - return unless $Slic3r::acceleration; + return "" unless $Slic3r::acceleration; return sprintf "M201 E%s%s\n", $acceleration, ($Slic3r::gcode_comments ? ' ; adjust acceleration' : ''); @@ -248,21 +243,19 @@ sub set_acceleration { sub G0 { my $self = shift; return $self->G1(@_) if !$Slic3r::g0; - return "G0" . $self->G0_G1(@_); + return $self->_G0_G1("G0", @_); } sub G1 { my $self = shift; - return "G1" . $self->G0_G1(@_); + return $self->_G0_G1("G1", @_); } -sub G0_G1 { +sub _G0_G1 { my $self = shift; - my ($point, $z, $e, $comment) = @_; + my ($gcode, $point, $z, $e, $comment) = @_; my $dec = $self->dec; - my $gcode = ""; - if ($point) { $gcode .= sprintf " X%.${dec}f Y%.${dec}f", ($point->x * $Slic3r::resolution) + $self->shift_x, @@ -308,13 +301,19 @@ sub _Gx { : 1; # determine speed - my $speed = ($e ? $self->print_feed_rate : $self->travel_speed) * $speed_multiplier; + my $speed = ($e ? $self->speed : 'travel'); # output speed if it's different from last one used # (goal: reduce gcode size) - if ($speed != $self->last_f) { - $gcode .= sprintf " F%.${dec}f", $speed; - $self->last_f($speed); + my $append_bridge_off = 0; + if ($speed ne $self->last_speed) { + if ($speed eq 'bridge') { + $gcode = "_BRIDGE_FAN_START\n$gcode"; + } elsif ($self->last_speed eq 'bridge') { + $append_bridge_off = 1; + } + $gcode .= sprintf " F%.${dec}f", $self->speeds->{$speed} * $speed_multiplier; + $self->last_speed($speed); } # output extrusion distance @@ -326,6 +325,9 @@ sub _Gx { } $gcode .= sprintf " ; %s", $comment if $comment && $Slic3r::gcode_comments; + if ($append_bridge_off) { + $gcode .= "\n_BRIDGE_FAN_END"; + } return "$gcode\n"; } @@ -336,4 +338,19 @@ sub set_tool { return sprintf "T%d%s\n", $tool, ($Slic3r::gcode_comments ? ' ; change tool' : ''); } +sub set_fan { + my $self = shift; + my ($speed, $dont_save) = @_; + + if ($self->last_fan_speed != $speed || $dont_save) { + $self->last_fan_speed($speed) if !$dont_save; + if ($speed == 0) { + return sprintf "M107%s\n", ($Slic3r::gcode_comments ? ' ; disable fan' : ''); + } else { + return sprintf "M106 S%d%s\n", (255 * $speed / 100), ($Slic3r::gcode_comments ? ' ; enable fan' : ''); + } + } + return ""; +} + 1; diff --git a/lib/Slic3r/GUI/OptionsGroup.pm b/lib/Slic3r/GUI/OptionsGroup.pm index 0f529b772..72f868d4d 100644 --- a/lib/Slic3r/GUI/OptionsGroup.pm +++ b/lib/Slic3r/GUI/OptionsGroup.pm @@ -26,8 +26,9 @@ sub new { foreach my $opt_key (@{$p{options}}) { my $opt = $Slic3r::Config::Options->{$opt_key}; - my $label = Wx::StaticText->new($parent, -1, "$opt->{label}:", Wx::wxDefaultPosition, [180,-1]); - $label->Wrap(180); # needed to avoid Linux/GTK bug + my $label = Wx::StaticText->new($parent, -1, "$opt->{label}:", Wx::wxDefaultPosition, + [$p{label_width} || 180, -1]); + $label->Wrap($p{label_width} || 180); # needed to avoid Linux/GTK bug #set the bold font point size to the same size as all the other labels (for consistency) $bold_font->SetPointSize($label->GetFont()->GetPointSize()); diff --git a/lib/Slic3r/GUI/SkeinPanel.pm b/lib/Slic3r/GUI/SkeinPanel.pm index 8af2c552e..d0fc516e8 100644 --- a/lib/Slic3r/GUI/SkeinPanel.pm +++ b/lib/Slic3r/GUI/SkeinPanel.pm @@ -47,6 +47,11 @@ sub new { title => 'Retraction', options => [qw(retract_length retract_lift retract_speed retract_restart_extra retract_before_travel)], }, + cooling => { + title => 'Cooling', + options => [qw(min_fan_speed max_fan_speed bridge_fan_speed fan_below_layer_time slowdown_below_layer_time min_print_speed disable_fan_first_layers)], + label_width => 300, + }, skirt => { title => 'Skirt', options => [qw(skirts skirt_distance skirt_height)], @@ -101,6 +106,7 @@ sub new { my @tabs = ( $make_tab->([qw(transform accuracy skirt)], [qw(print retract)]), + $make_tab->([qw(cooling)]), $make_tab->([qw(printer filament)], [qw(print_speed speed)]), $make_tab->([qw(gcode)]), $make_tab->([qw(notes)]), @@ -108,10 +114,11 @@ sub new { ); $tabpanel->AddPage($tabs[0], "Print Settings"); - $tabpanel->AddPage($tabs[1], "Printer and Filament"); - $tabpanel->AddPage($tabs[2], "Start/End GCODE"); - $tabpanel->AddPage($tabs[3], "Notes"); - $tabpanel->AddPage($tabs[4], "Advanced"); + $tabpanel->AddPage($tabs[1], "Cooling"); + $tabpanel->AddPage($tabs[2], "Printer and Filament"); + $tabpanel->AddPage($tabs[3], "Start/End GCODE"); + $tabpanel->AddPage($tabs[4], "Notes"); + $tabpanel->AddPage($tabs[5], "Advanced"); my $buttons_sizer; { diff --git a/lib/Slic3r/Print.pm b/lib/Slic3r/Print.pm index 7dbdbeb95..56b794baa 100644 --- a/lib/Slic3r/Print.pm +++ b/lib/Slic3r/Print.pm @@ -612,6 +612,8 @@ sub export_gcode { # set up our extruder object my $extruder = Slic3r::Extruder->new; + my $min_print_speed = 60 * $Slic3r::min_print_speed; + my $dec = $extruder->dec; if ($Slic3r::support_material && $Slic3r::support_material_tool > 0) { print $fh $extruder->set_tool(0); } @@ -621,36 +623,75 @@ sub export_gcode { # go to layer print $fh $extruder->change_layer($layer); + my $layer_gcode = ""; + $extruder->elapsed_time(0); + # extrude skirts $extruder->shift_x($shift[X]); $extruder->shift_y($shift[Y]); - print $fh $extruder->set_acceleration($Slic3r::perimeter_acceleration); - print $fh $extruder->extrude_loop($_, 'skirt') for @{ $layer->skirts }; + $layer_gcode .= $extruder->set_acceleration($Slic3r::perimeter_acceleration); + $layer_gcode .= $extruder->extrude_loop($_, 'skirt') for @{ $layer->skirts }; foreach my $copy (@{$self->copies}) { $extruder->shift_x($shift[X] + unscale $copy->[X]); $extruder->shift_y($shift[Y] + unscale $copy->[Y]); # extrude perimeters - print $fh $extruder->extrude($_, 'perimeter') for @{ $layer->perimeters }; + $layer_gcode .= $extruder->extrude($_, 'perimeter') for @{ $layer->perimeters }; # extrude fills - print $fh $extruder->set_acceleration($Slic3r::infill_acceleration); + $layer_gcode .= $extruder->set_acceleration($Slic3r::infill_acceleration); for my $fill (@{ $layer->fills }) { - print $fh $extruder->extrude_path($_, 'fill') + $layer_gcode .= $extruder->extrude_path($_, 'fill') for $fill->shortest_path($extruder->last_pos); } # extrude support material if ($layer->support_fills) { - print $fh $extruder->set_tool($Slic3r::support_material_tool) + $layer_gcode .= $extruder->set_tool($Slic3r::support_material_tool) if $Slic3r::support_material_tool > 0; - print $fh $extruder->extrude_path($_, 'support material') + $layer_gcode .= $extruder->extrude_path($_, 'support material') for $layer->support_fills->shortest_path($extruder->last_pos); - print $fh $extruder->set_tool(0) + $layer_gcode .= $extruder->set_tool(0) if $Slic3r::support_material_tool > 0; } } + last if !$layer_gcode; + + my $layer_time = $extruder->elapsed_time; + my $fan_speed = 0; + my $speed_factor = 1; + Slic3r::debugf "Layer %d estimated printing time: %d seconds\n", $layer->id, $layer_time; + if ($layer_time < $Slic3r::fan_below_layer_time) { + if ($layer_time < $Slic3r::slowdown_below_layer_time) { + $fan_speed = $Slic3r::max_fan_speed; + $speed_factor = $layer_time / $Slic3r::slowdown_below_layer_time; + } else { + $fan_speed = $Slic3r::max_fan_speed - ($Slic3r::max_fan_speed - $Slic3r::min_fan_speed) + * ($layer_time - $Slic3r::slowdown_below_layer_time) + / ($Slic3r::fan_below_layer_time - $Slic3r::slowdown_below_layer_time); #/ + } + } + Slic3r::debugf " fan = %d%%, speed = %d%%\n", $fan_speed, $speed_factor * 100; + + if ($speed_factor < 1) { + $layer_gcode =~ s/^(?=.*? [XY])(G1 .*?F)(\d+(?:\.\d+)?)/ + my $new_speed = $2 * $speed_factor; + $1 . sprintf("%.${dec}f", $new_speed < $min_print_speed ? $min_print_speed : $new_speed) + /gexm; + } + $fan_speed = 0 if $layer->id < $Slic3r::disable_fan_first_layers; + $layer_gcode = $extruder->set_fan($fan_speed) . $layer_gcode; + + # bridge fan speed + if ($Slic3r::bridge_fan_speed == 0 || $layer->id < $Slic3r::disable_fan_first_layers) { + $layer_gcode =~ s/^_BRIDGE_FAN_(?:START|END)\n//gm; + } else { + $layer_gcode =~ s/^_BRIDGE_FAN_START\n/ $extruder->set_fan($Slic3r::bridge_fan_speed, 1) /gmex; + $layer_gcode =~ s/^_BRIDGE_FAN_END\n/ $extruder->set_fan($fan_speed, 1) /gmex; + } + + print $fh $layer_gcode; } # save statistic data @@ -658,6 +699,7 @@ sub export_gcode { # write end commands to file print $fh $extruder->retract; + print $fh $extruder->set_fan(0); print $fh "M501 ; reset acceleration\n" if $Slic3r::acceleration; print $fh "$Slic3r::end_gcode\n"; diff --git a/slic3r.pl b/slic3r.pl index 3bbd8e8f2..36b2ec495 100755 --- a/slic3r.pl +++ b/slic3r.pl @@ -170,7 +170,7 @@ Usage: slic3r.pl [ OPTIONS ] file.stl home X axis [G28 X], disable motors [M84]). --support-material Generate support material for overhangs - Retraction options: + Retraction options: --retract-length Length of retraction in mm when pausing extrusion (default: $Slic3r::retract_length) --retract-speed Speed for retraction in mm/s (default: $Slic3r::retract_speed) @@ -181,6 +181,17 @@ Usage: slic3r.pl [ OPTIONS ] file.stl Only retract before travel moves of this length in mm (default: $Slic3r::retract_before_travel) --retract-lift Lift Z by the given distance in mm when retracting (default: $Slic3r::retract_lift) + Cooling options: + --min-fan-speed Minimum fan speed (default: $Slic3r::min_fan_speed%) + --max-fan-speed Maximum fan speed (default: $Slic3r::max_fan_speed%) + --bridge-fan-speed Fan speed to use when bridging (default: $Slic3r::bridge_fan_speed%) + --fan-below-layer-time Enable fan if layer print time is below this approximate number + of seconds (default: $Slic3r::fan_below_layer_time) + --slowdown-below-layer-time Slow down if layer print time is below this approximate number + of seconds (default: $Slic3r::slowdown_below_layer_time) + --min-print-speed Minimum print speed speed (mm/s, default: $Slic3r::min_print_speed) + --disable-fan-first-layers Disable fan for the first N layers (default: $Slic3r::disable_fan_first_layers) + Skirt options: --skirts Number of skirts to draw (0+, default: $Slic3r::skirts) --skirt-distance Distance in mm between innermost skirt and object