diff --git a/lib/Slic3r.pm b/lib/Slic3r.pm index 5638dd563..bb3352994 100644 --- a/lib/Slic3r.pm +++ b/lib/Slic3r.pm @@ -50,7 +50,6 @@ use Slic3r::GCode::ArcFitting; use Slic3r::GCode::CoolingBuffer; use Slic3r::GCode::Layer; use Slic3r::GCode::MotionPlanner; -use Slic3r::GCode::OozePrevention; use Slic3r::GCode::PlaceholderParser; use Slic3r::GCode::Reader; use Slic3r::GCode::SpiralVase; diff --git a/lib/Slic3r/GCode.pm b/lib/Slic3r/GCode.pm index 2d39337e3..af17fce4f 100644 --- a/lib/Slic3r/GCode.pm +++ b/lib/Slic3r/GCode.pm @@ -17,9 +17,10 @@ has 'origin' => (is => 'rw', default => sub { Slic3r::Pointf->new }) has 'config' => (is => 'ro', default => sub { Slic3r::Config::Full->new }); has 'writer' => (is => 'ro', default => sub { Slic3r::GCode::Writer->new }); has 'placeholder_parser' => (is => 'rw', default => sub { Slic3r::GCode::PlaceholderParser->new }); -has 'ooze_prevention' => (is => 'rw'); +has 'ooze_prevention' => (is => 'rw', default => sub { Slic3r::GCode::OozePrevention->new }); +has 'wipe' => (is => 'rw', default => sub { Slic3r::GCode::Wipe->new }); +has 'avoid_crossing_perimeters' => (is => 'rw', default => sub { Slic3r::GCode::AvoidCrossingPerimeters->new }); has 'enable_loop_clipping' => (is => 'rw', default => sub {1}); -has 'enable_wipe' => (is => 'rw', default => sub {0}); # at least one extruder has wipe enabled has 'enable_cooling_markers' => (is =>'rw', default => sub {0}); has 'layer_count' => (is => 'ro'); has '_layer_index' => (is => 'rw', default => sub {-1}); # just a counter @@ -27,14 +28,9 @@ has 'layer' => (is => 'rw'); has '_layer_islands' => (is => 'rw'); has '_upper_layer_islands' => (is => 'rw'); has '_seam_position' => (is => 'ro', default => sub { {} }); # $object => pos -has '_external_mp' => (is => 'rw'); -has '_layer_mp' => (is => 'rw'); -has 'new_object' => (is => 'rw', default => sub {0}); # this flag triggers the use of the external configuration space for avoid_crossing_perimeters for the next travel move -has 'straight_once' => (is => 'rw', default => sub {1}); # this flag disables avoid_crossing_perimeters just for the next travel move has 'first_layer' => (is => 'rw', default => sub {0}); # this flag triggers first layer speeds has 'elapsed_time' => (is => 'rw', default => sub {0} ); # seconds has 'last_pos' => (is => 'rw', default => sub { Slic3r::Point->new(0,0) } ); -has '_wipe_path' => (is => 'rw'); sub apply_print_config { my ($self, $print_config) = @_; @@ -49,7 +45,7 @@ sub set_extruders { $self->writer->set_extruders($extruder_ids); # enable wipe path generation if any extruder has wipe enabled - $self->enable_wipe(defined first { $self->config->get_at('wipe', $_) } @$extruder_ids); + $self->wipe->enable(defined first { $self->config->get_at('wipe', $_) } @$extruder_ids); } sub set_origin { @@ -61,16 +57,11 @@ sub set_origin { scale ($self->origin->y - $pointf->y), #- ); $self->last_pos->translate(@translate); - $self->_wipe_path->translate(@translate) if $self->_wipe_path; + $self->wipe->path->translate(@translate) if $self->wipe->path; $self->origin($pointf); } -sub init_external_mp { - my ($self, $islands) = @_; - $self->_external_mp(Slic3r::MotionPlanner->new($islands)); -} - sub preamble { my ($self) = @_; @@ -96,9 +87,9 @@ sub change_layer { $self->_layer_islands($layer->islands); $self->_upper_layer_islands($layer->upper_layer ? $layer->upper_layer->islands : []); if ($self->config->avoid_crossing_perimeters) { - $self->_layer_mp(Slic3r::MotionPlanner->new( + $self->avoid_crossing_perimeters->init_layer_mp( union_ex([ map @$_, @{$layer->slices} ], 1), - )); + ); } my $gcode = ""; @@ -213,7 +204,7 @@ sub extrude_loop { # reset acceleration $gcode .= $self->writer->set_acceleration($self->config->default_acceleration); - $self->_wipe_path($paths[0]->polyline->clone) if $self->enable_wipe; # TODO: don't limit wipe to last path + $self->wipe->path($paths[0]->polyline->clone) if $self->wipe->enable; # TODO: don't limit wipe to last path # make a little move inwards before leaving loop if ($paths[-1]->role == EXTR_ROLE_EXTERNAL_PERIMETER && defined $self->layer && $self->config->perimeters > 1) { @@ -324,9 +315,9 @@ sub _extrude_path { $self->writer->extrusion_axis, $self->config->gcode_comments ? " ; $description" : ""); - if ($self->enable_wipe) { - $self->_wipe_path($path->polyline->clone); - $self->_wipe_path->reverse; + if ($self->wipe->enable) { + $self->wipe->path($path->polyline->clone); + $self->wipe->path->reverse; } } $gcode .= ";_BRIDGE_FAN_END\n" if $path->is_bridge && $self->enable_cooling_markers; @@ -366,25 +357,8 @@ sub travel_to { ) { # Just perform a straight travel move without any retraction. $gcode .= $self->writer->travel_to_xy($self->point_to_gcode($point), $comment); - } elsif ($self->config->avoid_crossing_perimeters && !$self->straight_once) { - # If avoid_crossing_perimeters is enabled and the straight_once flag is not set - # we need to plan a multi-segment travel move inside the configuration space. - if ($self->new_object) { - # If we're moving to a new object we need to use the external configuration space. - $self->new_object(0); - - # represent $point in G-code coordinates - $point = $point->clone; - my $origin = $self->origin; - $point->translate(map scale $_, @$origin); - - # calculate path (external_mp uses G-code coordinates so we set a temporary null origin) - $self->set_origin(Slic3r::Pointf->new(0,0)); - $gcode .= $self->_plan($self->_external_mp, $point, $comment); - $self->set_origin($origin); - } else { - $gcode .= $self->_plan($self->_layer_mp, $point, $comment); - } + } elsif ($self->config->avoid_crossing_perimeters && !$self->avoid_crossing_perimeters->straight_once) { + $gcode .= $self->avoid_crossing_perimeters->travel_to($self, $point, $comment); } else { # If avoid_crossing_perimeters is disabled or the straight_once flag is set, # perform a straight move with a retraction. @@ -393,41 +367,11 @@ sub travel_to { } # Re-allow avoid_crossing_perimeters for the next travel moves - $self->straight_once(0); + $self->avoid_crossing_perimeters->straight_once(0); return $gcode; } -sub _plan { - my ($self, $mp, $point, $comment) = @_; - - my $gcode = ""; - my @travel = @{$mp->shortest_path($self->last_pos, $point)->lines}; - - # if the path is not contained in a single island we need to retract - my $need_retract = !$self->config->only_retract_when_crossing_perimeters; - if (!$need_retract) { - $need_retract = 1; - foreach my $island (@{$self->_upper_layer_islands}) { - # discard the island if at any line is not enclosed in it - next if first { !$island->contains_line($_) } @travel; - # okay, this island encloses the full travel path - $need_retract = 0; - last; - } - } - - # perform the retraction - $gcode .= $self->retract if $need_retract; - - # append the actual path and return - # use G1 because we rely on paths being straight (G0 may make round paths) - $gcode .= join '', - map $self->writer->travel_to_xy($self->point_to_gcode($_->b), $comment), - @travel; - return $gcode; -} - sub retract { my ($self, $toolchange) = @_; @@ -436,48 +380,8 @@ sub retract { my $gcode = ""; # wipe (if it's enabled for this extruder and we have a stored wipe path) - if ($self->config->get_at('wipe', $self->writer->extruder->id) && $self->_wipe_path) { - # Reduce feedrate a bit; travel speed is often too high to move on existing material. - # Too fast = ripping of existing material; too slow = short wipe path, thus more blob. - my $wipe_speed = $self->writer->config->get('travel_speed') * 0.8; - - # get the retraction length - my $length = $toolchange - ? $self->writer->extruder->retract_length_toolchange - : $self->writer->extruder->retract_length; - - if ($length) { - # Calculate how long we need to travel in order to consume the required - # amount of retraction. In other words, how far do we move in XY at $wipe_speed - # for the time needed to consume retract_length at retract_speed? - my $wipe_dist = scale($length / $self->writer->extruder->retract_speed * $wipe_speed); - - # Take the stored wipe path and replace first point with the current actual position - # (they might be different, for example, in case of loop clipping). - my $wipe_path = Slic3r::Polyline->new( - $self->last_pos, - @{$self->_wipe_path}[1..$#{$self->_wipe_path}], - ); - # - $wipe_path->clip_end($wipe_path->length - $wipe_dist); - - # subdivide the retraction in segments - my $retracted = 0; - foreach my $line (@{$wipe_path->lines}) { - my $segment_length = $line->length; - # Reduce retraction length a bit to avoid effective retraction speed to be greater than the configured one - # due to rounding (TODO: test and/or better math for this) - my $dE = $length * ($segment_length / $wipe_dist) * 0.95; - $gcode .= $self->writer->set_speed($wipe_speed*60); - $gcode .= $self->writer->extrude_to_xy( - $self->point_to_gcode($line->b), - -$dE, - 'retract' . ($self->enable_cooling_markers ? ';_WIPE' : ''), - ); - $retracted += $dE; - } - $self->writer->extruder->set_retracted($self->writer->extruder->retracted + $retracted); - } + if ($self->config->get_at('wipe', $self->writer->extruder->id) && $self->wipe->path) { + $gcode .= $self->wipe->wipe($self, $toolchange); } # The parent class will decide whether we need to perform an actual retraction @@ -537,14 +441,196 @@ sub set_extruder { # if ooze prevention is enabled, park current extruder in the nearest # standby point and set it to the standby temperature $gcode .= $self->ooze_prevention->pre_toolchange($self) - if $self->ooze_prevention && defined $self->writer->extruder; + if $self->ooze_prevention->enable && defined $self->writer->extruder; # append the toolchange command $gcode .= $self->writer->toolchange($extruder_id); # set the new extruder to the operating temperature $gcode .= $self->ooze_prevention->post_toolchange($self) - if $self->ooze_prevention; + if $self->ooze_prevention->enable; + + return $gcode; +} + +package Slic3r::GCode::OozePrevention; +use Moo; + +use Slic3r::Geometry qw(scale); + +has 'enable' => (is => 'rw', default => sub { 0 }); +has 'standby_points' => (is => 'rw'); + +sub pre_toolchange { + my ($self, $gcodegen) = @_; + + my $gcode = ""; + + # move to the nearest standby point + if (@{$self->standby_points}) { + my $last_pos = $gcodegen->last_pos->clone; + $last_pos->translate(scale +$gcodegen->origin->x, scale +$gcodegen->origin->y); #)) + my $standby_point = $last_pos->nearest_point($self->standby_points); + $standby_point->translate(scale -$gcodegen->origin->x, scale -$gcodegen->origin->y); #)) + $gcode .= $gcodegen->travel_to($standby_point); + } + + if ($gcodegen->config->standby_temperature_delta != 0) { + my $temp = defined $gcodegen->layer && $gcodegen->layer->id == 0 + ? $gcodegen->config->get_at('first_layer_temperature', $gcodegen->writer->extruder->id) + : $gcodegen->config->get_at('temperature', $gcodegen->writer->extruder->id); + # we assume that heating is always slower than cooling, so no need to block + $gcode .= $gcodegen->writer->set_temperature($temp + $gcodegen->config->standby_temperature_delta, 0); + } + + return $gcode; +} + +sub post_toolchange { + my ($self, $gcodegen) = @_; + + my $gcode = ""; + + if ($gcodegen->config->standby_temperature_delta != 0) { + my $temp = defined $gcodegen->layer && $gcodegen->layer->id == 0 + ? $gcodegen->config->get_at('first_layer_temperature', $gcodegen->writer->extruder->id) + : $gcodegen->config->get_at('temperature', $gcodegen->writer->extruder->id); + $gcode .= $gcodegen->writer->set_temperature($temp, 1); + } + + return $gcode; +} + +package Slic3r::GCode::Wipe; +use Moo; + +use Slic3r::Geometry qw(scale); + +has 'enable' => (is => 'rw', default => sub { 0 }); +has 'path' => (is => 'rw'); + +sub wipe { + my ($self, $gcodegen, $toolchange) = @_; + + my $gcode = ""; + + # Reduce feedrate a bit; travel speed is often too high to move on existing material. + # Too fast = ripping of existing material; too slow = short wipe path, thus more blob. + my $wipe_speed = $gcodegen->writer->config->get('travel_speed') * 0.8; + + # get the retraction length + my $length = $toolchange + ? $gcodegen->writer->extruder->retract_length_toolchange + : $gcodegen->writer->extruder->retract_length; + + if ($length) { + # Calculate how long we need to travel in order to consume the required + # amount of retraction. In other words, how far do we move in XY at $wipe_speed + # for the time needed to consume retract_length at retract_speed? + my $wipe_dist = scale($length / $gcodegen->writer->extruder->retract_speed * $wipe_speed); + + # Take the stored wipe path and replace first point with the current actual position + # (they might be different, for example, in case of loop clipping). + my $wipe_path = Slic3r::Polyline->new( + $gcodegen->last_pos, + @{$self->path}[1..$#{$self->path}], + ); + # + $wipe_path->clip_end($wipe_path->length - $wipe_dist); + + # subdivide the retraction in segments + my $retracted = 0; + foreach my $line (@{$wipe_path->lines}) { + my $segment_length = $line->length; + # Reduce retraction length a bit to avoid effective retraction speed to be greater than the configured one + # due to rounding (TODO: test and/or better math for this) + my $dE = $length * ($segment_length / $wipe_dist) * 0.95; + $gcode .= $gcodegen->writer->set_speed($wipe_speed*60); + $gcode .= $gcodegen->writer->extrude_to_xy( + $gcodegen->point_to_gcode($line->b), + -$dE, + 'retract' . ($gcodegen->enable_cooling_markers ? ';_WIPE' : ''), + ); + $retracted += $dE; + } + $gcodegen->writer->extruder->set_retracted($gcodegen->writer->extruder->retracted + $retracted); + } + + return $gcode; +} + +package Slic3r::GCode::AvoidCrossingPerimeters; +use Moo; + +has '_external_mp' => (is => 'rw'); +has '_layer_mp' => (is => 'rw'); +has 'new_object' => (is => 'rw', default => sub {0}); # this flag triggers the use of the external configuration space for avoid_crossing_perimeters for the next travel move +has 'straight_once' => (is => 'rw', default => sub {1}); # this flag disables avoid_crossing_perimeters just for the next travel move + +sub init_external_mp { + my ($self, $islands) = @_; + $self->_external_mp(Slic3r::MotionPlanner->new($islands)); +} + +sub init_layer_mp { + my ($self, $islands) = @_; + $self->_layer_mp(Slic3r::MotionPlanner->new($islands)); +} + +sub travel_to { + my ($self, $gcodegen, $point, $comment) = @_; + + my $gcode = ""; + + # If avoid_crossing_perimeters is enabled and the straight_once flag is not set + # we need to plan a multi-segment travel move inside the configuration space. + if ($self->new_object) { + # If we're moving to a new object we need to use the external configuration space. + $self->new_object(0); + + # represent $point in G-code coordinates + $point = $point->clone; + my $origin = $gcodegen->origin; + $point->translate(map scale $_, @$origin); + + # calculate path (external_mp uses G-code coordinates so we set a temporary null origin) + $gcodegen->set_origin(Slic3r::Pointf->new(0,0)); + $gcode .= $self->_plan($gcodegen, $self->_external_mp, $point, $comment); + $gcodegen->set_origin($origin); + } else { + $gcode .= $self->_plan($gcodegen, $self->_layer_mp, $point, $comment); + } + + return $gcode; +} + +sub _plan { + my ($self, $gcodegen, $mp, $point, $comment) = @_; + + my $gcode = ""; + my @travel = @{$mp->shortest_path($gcodegen->last_pos, $point)->lines}; + + # if the path is not contained in a single island we need to retract + my $need_retract = !$gcodegen->config->only_retract_when_crossing_perimeters; + if (!$need_retract) { + $need_retract = 1; + foreach my $island (@{$gcodegen->_upper_layer_islands}) { + # discard the island if at any line is not enclosed in it + next if first { !$island->contains_line($_) } @travel; + # okay, this island encloses the full travel path + $need_retract = 0; + last; + } + } + + # perform the retraction + $gcode .= $gcodegen->retract if $need_retract; + + # append the actual path and return + # use G1 because we rely on paths being straight (G0 may make round paths) + $gcode .= join '', + map $gcodegen->writer->travel_to_xy($self->point_to_gcode($_->b), $comment), + @travel; return $gcode; } diff --git a/lib/Slic3r/GCode/Layer.pm b/lib/Slic3r/GCode/Layer.pm index d1d3057fa..af637b167 100644 --- a/lib/Slic3r/GCode/Layer.pm +++ b/lib/Slic3r/GCode/Layer.pm @@ -100,7 +100,7 @@ sub process_layer { } } $self->skirt_done->{$layer->print_z} = 1; - $self->gcodegen->straight_once(1); + $self->gcodegen->avoid_crossing_perimeters->straight_once(1); } # extrude brim @@ -110,11 +110,11 @@ sub process_layer { $gcode .= $self->gcodegen->extrude_loop($_, 'brim', $object->config->support_material_speed) for @{$self->print->brim}; $self->brim_done(1); - $self->gcodegen->straight_once(1); + $self->gcodegen->avoid_crossing_perimeters->straight_once(1); } for my $copy (@$object_copies) { - $self->gcodegen->new_object(1) if ($self->_last_obj_copy // '') ne "$copy"; + $self->gcodegen->avoid_crossing_perimeters->new_object(1) if ($self->_last_obj_copy // '') ne "$copy"; $self->_last_obj_copy("$copy"); $self->gcodegen->set_origin(Slic3r::Pointf->new(map $self->origin->[$_] + unscale $copy->[$_], X,Y)); diff --git a/lib/Slic3r/GCode/OozePrevention.pm b/lib/Slic3r/GCode/OozePrevention.pm deleted file mode 100644 index 940117f0b..000000000 --- a/lib/Slic3r/GCode/OozePrevention.pm +++ /dev/null @@ -1,48 +0,0 @@ -package Slic3r::GCode::OozePrevention; -use Moo; - -use Slic3r::Geometry qw(scale); - -has 'standby_points' => (is => 'rw', required => 1); - -sub pre_toolchange { - my ($self, $gcodegen) = @_; - - my $gcode = ""; - - # move to the nearest standby point - if (@{$self->standby_points}) { - my $last_pos = $gcodegen->last_pos->clone; - $last_pos->translate(scale +$gcodegen->origin->x, scale +$gcodegen->origin->y); #)) - my $standby_point = $last_pos->nearest_point($self->standby_points); - $standby_point->translate(scale -$gcodegen->origin->x, scale -$gcodegen->origin->y); #)) - $gcode .= $gcodegen->travel_to($standby_point); - } - - if ($gcodegen->config->standby_temperature_delta != 0) { - my $temp = defined $gcodegen->layer && $gcodegen->layer->id == 0 - ? $gcodegen->config->get_at('first_layer_temperature', $gcodegen->writer->extruder->id) - : $gcodegen->config->get_at('temperature', $gcodegen->writer->extruder->id); - # we assume that heating is always slower than cooling, so no need to block - $gcode .= $gcodegen->writer->set_temperature($temp + $gcodegen->config->standby_temperature_delta, 0); - } - - return $gcode; -} - -sub post_toolchange { - my ($self, $gcodegen) = @_; - - my $gcode = ""; - - if ($gcodegen->config->standby_temperature_delta != 0) { - my $temp = defined $gcodegen->layer && $gcodegen->layer->id == 0 - ? $gcodegen->config->get_at('first_layer_temperature', $gcodegen->writer->extruder->id) - : $gcodegen->config->get_at('temperature', $gcodegen->writer->extruder->id); - $gcode .= $gcodegen->writer->set_temperature($temp, 1); - } - - return $gcode; -} - -1; diff --git a/lib/Slic3r/Print.pm b/lib/Slic3r/Print.pm index 42c7c7746..06e66476f 100644 --- a/lib/Slic3r/Print.pm +++ b/lib/Slic3r/Print.pm @@ -552,7 +552,7 @@ sub write_gcode { } } } - $gcodegen->init_external_mp(union_ex([ map @$_, @islands ])); + $gcodegen->avoid_crossing_perimeters->init_external_mp(union_ex([ map @$_, @islands ])); } # calculate wiping points if needed @@ -567,10 +567,10 @@ sub write_gcode { } my $convex_hull = convex_hull([ map @$_, @skirts ]); - my $oozeprev = Slic3r::GCode::OozePrevention->new( - standby_points => [ map $_->clone, map @$_, map $_->subdivide(scale 10), @{offset([$convex_hull], scale 3)} ], + $gcodegen->ooze_prevention->enable(1); + $gcodegen->ooze_prevention->standby_points( + [ map $_->clone, map @$_, map $_->subdivide(scale 10), @{offset([$convex_hull], scale 3)} ] ); - $gcodegen->ooze_prevention($oozeprev); } }