2012-06-28 14:44:54 +02:00
package Slic3r::GCode ;
use Moo ;
2014-03-27 00:01:33 +01:00
use List::Util qw(min max first) ;
2014-05-12 21:49:17 +02:00
use Slic3r::ExtrusionLoop ':roles' ;
2012-06-28 14:44:54 +02:00
use Slic3r::ExtrusionPath ':roles' ;
2014-01-02 17:24:23 +01:00
use Slic3r::Flow ':roles' ;
2014-10-25 11:15:12 +02:00
use Slic3r::Geometry qw(epsilon scale unscale PI X Y B) ;
2014-04-29 19:58:58 +02:00
use Slic3r::Geometry::Clipper qw(union_ex offset_ex) ;
2013-06-20 20:11:46 +02:00
use Slic3r::Surface ':types' ;
2012-06-28 14:44:54 +02:00
2014-10-25 10:56:21 +02:00
# Origin of print coordinates expressed in unscaled G-code coordinates.
# This affects the input arguments supplied to the extrude*() and travel_to()
# methods.
has 'origin' => ( is => 'rw' , default => sub { Slic3r::Pointf -> new });
2014-05-13 08:34:21 +02:00
has 'config' => ( is => 'ro' , default => sub { Slic3r::Config::Full -> new });
2014-10-28 21:47:09 +01:00
has 'writer' => ( is => 'ro' , default => sub { Slic3r::GCode::Writer -> new });
2014-03-17 00:39:07 +01:00
has 'placeholder_parser' => ( is => 'rw' , default => sub { Slic3r::GCode::PlaceholderParser -> new });
2014-11-23 15:13:40 +01:00
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 });
2013-07-28 13:39:15 +02:00
has 'enable_loop_clipping' => ( is => 'rw' , default => sub { 1 });
2014-10-28 21:47:09 +01:00
has 'enable_cooling_markers' => ( is => 'rw' , default => sub { 0 });
2014-10-18 17:41:21 +02:00
has 'layer_count' => ( is => 'ro' );
2014-01-11 17:40:09 +01:00
has '_layer_index' => ( is => 'rw' , default => sub { - 1 }); # just a counter
2012-06-28 14:44:54 +02:00
has 'layer' => ( is => 'rw' );
2013-08-09 14:55:36 +02:00
has '_layer_islands' => ( is => 'rw' );
has '_upper_layer_islands' => ( is => 'rw' );
2014-05-24 22:10:28 +02:00
has '_seam_position' => ( is => 'ro' , default => sub { {} }); # $object => pos
2014-10-28 21:47:09 +01:00
has 'first_layer' => ( is => 'rw' , default => sub { 0 }); # this flag triggers first layer speeds
2012-06-28 14:44:54 +02:00
has 'elapsed_time' => ( is => 'rw' , default => sub { 0 } ); # seconds
has 'last_pos' => ( is => 'rw' , default => sub { Slic3r::Point -> new ( 0 , 0 ) } );
2014-10-18 17:41:21 +02:00
sub apply_print_config {
my ( $self , $print_config ) = @_ ;
2014-10-28 21:47:09 +01:00
$self -> writer -> apply_print_config ( $print_config );
2014-10-18 17:41:21 +02:00
$self -> config -> apply_print_config ( $print_config );
}
2014-01-03 18:27:46 +01:00
sub set_extruders {
2014-10-18 17:41:21 +02:00
my ( $self , $extruder_ids ) = @_ ;
2014-01-03 18:27:46 +01:00
2014-10-28 21:47:09 +01:00
$self -> writer -> set_extruders ( $extruder_ids );
2014-03-27 00:01:33 +01:00
2014-10-18 17:41:21 +02:00
# enable wipe path generation if any extruder has wipe enabled
2014-11-23 15:13:40 +01:00
$self -> wipe -> enable ( defined first { $self -> config -> get_at ( 'wipe' , $_ ) } @$extruder_ids );
2014-01-03 18:27:46 +01:00
}
2014-10-25 10:56:21 +02:00
sub set_origin {
my ( $self , $pointf ) = @_ ;
2012-08-23 15:42:58 +02:00
2014-10-25 10:56:21 +02:00
# if origin increases (goes towards right), last_pos decreases because it goes towards left
2013-03-25 23:06:18 +01:00
my @translate = (
2014-10-25 10:56:21 +02:00
scale ( $self -> origin -> x - $pointf -> x ),
scale ( $self -> origin -> y - $pointf -> y ), #-
2012-12-09 18:33:25 +01:00
);
2013-03-25 23:06:18 +01:00
$self -> last_pos -> translate ( @translate );
2014-11-23 15:13:40 +01:00
$self -> wipe -> path -> translate ( @translate ) if $self -> wipe -> path ;
2012-08-23 15:42:58 +02:00
2014-10-25 10:56:21 +02:00
$self -> origin ( $pointf );
2012-08-23 15:42:58 +02:00
}
2014-11-09 19:24:17 +01:00
sub preamble {
my ( $self ) = @_ ;
my $gcode = $self -> writer -> preamble ;
# Perform a *silent* move to z_offset: we need this to initialize the Z
# position of our writer object so that any initial lift taking place
# before the first layer change will raise the extruder from the correct
# initial Z instead of 0.
$self -> writer -> travel_to_z ( $self -> config -> z_offset , '' );
return $gcode ;
}
2012-06-28 14:44:54 +02:00
sub change_layer {
2013-08-27 00:02:24 +02:00
my ( $self , $layer ) = @_ ;
2012-06-28 14:44:54 +02:00
$self -> layer ( $layer );
2014-01-11 17:40:09 +01:00
$self -> _layer_index ( $self -> _layer_index + 1 );
2014-10-28 21:47:09 +01:00
$self -> first_layer ( $layer -> id == 0 );
2013-07-07 19:12:44 +02:00
2013-08-09 14:55:36 +02:00
# avoid computing islands and overhangs if they're not needed
2013-11-11 20:37:06 +01:00
$self -> _layer_islands ( $layer -> islands );
$self -> _upper_layer_islands ( $layer -> upper_layer ? $layer -> upper_layer -> islands : [] );
2014-05-13 08:34:21 +02:00
if ( $self -> config -> avoid_crossing_perimeters ) {
2014-11-23 15:13:40 +01:00
$self -> avoid_crossing_perimeters -> init_layer_mp (
2014-05-13 20:06:01 +02:00
union_ex ([ map @$_ , @ { $layer -> slices } ], 1 ),
2014-11-23 15:13:40 +01:00
);
2012-08-23 15:42:58 +02:00
}
2013-01-17 14:56:31 +01:00
my $gcode = "" ;
2014-10-21 20:16:45 +02:00
if ( defined $self -> layer_count ) {
2014-06-05 16:24:47 +02:00
# TODO: cap this to 99% and add an explicit M73 P100 in the end G-code
2014-11-06 21:06:09 +01:00
$gcode .= $self -> writer -> update_progress ( $self -> _layer_index , $self -> layer_count );
2013-01-17 14:56:31 +01:00
}
2013-08-09 19:46:20 +02:00
2014-10-21 20:16:45 +02:00
my $z = $layer -> print_z + $self -> config -> z_offset ; # in unscaled coordinates
2014-10-28 21:47:09 +01:00
if ( $self -> config -> get_at ( 'retract_layer_change' , $self -> writer -> extruder -> id ) && $self -> writer -> will_move_z ( $z )) {
2014-10-21 20:16:45 +02:00
$gcode .= $self -> retract ;
2012-10-30 10:45:55 +01:00
}
2014-10-28 21:47:09 +01:00
$gcode .= $self -> writer -> travel_to_z ( $z , 'move to next layer (' . $self -> layer -> id . ')' );
2012-06-28 14:44:54 +02:00
return $gcode ;
}
sub extrude {
my $self = shift ;
2013-07-15 16:21:09 +02:00
$_ [ 0 ] -> isa ( 'Slic3r::ExtrusionLoop' )
2012-07-20 14:39:07 +02:00
? $self -> extrude_loop ( @_ )
: $self -> extrude_path ( @_ );
2012-06-28 14:44:54 +02:00
}
sub extrude_loop {
2014-05-18 17:02:18 +02:00
my ( $self , $loop , $description , $speed ) = @_ ;
2012-06-28 14:44:54 +02:00
2014-03-24 20:01:14 +01:00
# make a copy; don't modify the orientation of the original loop object otherwise
# next copies (if any) would not detect the correct orientation
$loop = $loop -> clone ;
2012-06-28 14:44:54 +02:00
# extrude all loops ccw
2013-07-16 17:13:01 +02:00
my $was_clockwise = $loop -> make_counter_clockwise ;
2012-06-28 14:44:54 +02:00
# find the point of the loop that is closest to the current extruder position
# or randomize if requested
my $last_pos = $self -> last_pos ;
2014-05-22 12:28:12 +02:00
if ( $self -> config -> spiral_vase ) {
$loop -> split_at ( $last_pos );
2014-05-24 22:10:28 +02:00
} elsif ( $self -> config -> seam_position eq 'nearest' || $self -> config -> seam_position eq 'aligned' ) {
2014-07-25 12:04:33 +02:00
# simplify polygon in order to skip false positives in concave/convex detection
2014-05-22 19:34:49 +02:00
my $polygon = $loop -> polygon ;
2014-10-28 21:47:09 +01:00
my @simplified = @ { $polygon -> simplify ( scale $self -> config -> get_at ( 'nozzle_diameter' , $self -> writer -> extruder -> id ) / 2 )};
2014-05-22 19:34:49 +02:00
2014-07-25 12:04:33 +02:00
# concave vertices have priority
my @candidates = map @ { $_ -> concave_points ( PI * 4 / 3 )}, @simplified ;
2014-05-22 19:34:49 +02:00
2014-07-25 12:04:33 +02:00
# if no concave points were found, look for convex vertices
@candidates = map @ { $_ -> convex_points ( PI * 2 / 3 )}, @simplified if ! @candidates ;
# retrieve the last start position for this object
2014-10-28 21:47:09 +01:00
my $obj_ptr = 0 ;
2014-07-25 12:04:33 +02:00
if ( defined $self -> layer ) {
$obj_ptr = $self -> layer -> object -> ptr ;
if ( defined $self -> _seam_position -> { $self -> layer -> object }) {
2014-06-10 16:01:57 +02:00
$last_pos = $self -> _seam_position -> { $obj_ptr };
2014-05-22 19:34:49 +02:00
}
}
2014-07-25 12:04:33 +02:00
my $point ;
if ( $self -> config -> seam_position eq 'nearest' ) {
@candidates = @$polygon if ! @candidates ;
$point = $last_pos -> nearest_point ( \ @candidates );
2014-11-08 12:56:14 +01:00
if ( ! $loop -> split_at_vertex ( $point )) {
# On 32-bit Linux, Clipper will change some point coordinates by 1 unit
# while performing simplify_polygons(), thus split_at_vertex() won't
# find them anymore.
$loop -> split_at ( $point );
}
2014-07-25 12:04:33 +02:00
} elsif ( @candidates ) {
my @non_overhang = grep ! $loop -> has_overhang_point ( $_ ), @candidates ;
@candidates = @non_overhang if @non_overhang ;
$point = $last_pos -> nearest_point ( \ @candidates );
2014-11-08 12:56:14 +01:00
if ( ! $loop -> split_at_vertex ( $point )) {
$loop -> split_at ( $point );
}
2014-07-25 12:04:33 +02:00
} else {
$point = $last_pos -> projection_onto_polygon ( $polygon );
$loop -> split_at ( $point );
}
$self -> _seam_position -> { $obj_ptr } = $point ;
2014-05-24 22:10:28 +02:00
} elsif ( $self -> config -> seam_position eq 'random' ) {
2014-05-22 19:34:49 +02:00
if ( $loop -> role == EXTRL_ROLE_CONTOUR_INTERNAL_PERIMETER ) {
my $polygon = $loop -> polygon ;
my $centroid = $polygon -> centroid ;
$last_pos = Slic3r::Point -> new ( $polygon -> bounding_box -> x_max , $centroid -> y ); #))
$last_pos -> rotate ( rand ( 2 * PI ), $centroid );
}
$loop -> split_at ( $last_pos );
2014-05-22 12:28:12 +02:00
}
2012-06-28 14:44:54 +02:00
# clip the path to avoid the extruder to get exactly on the first point of the loop;
# if polyline was shorter than the clipping distance we'd get a null polyline, so
# we discard it in that case
2014-05-08 11:07:37 +02:00
my $clip_length = $self -> enable_loop_clipping
2014-10-28 21:47:09 +01:00
? scale ( $self -> config -> get_at ( 'nozzle_diameter' , $self -> writer -> extruder -> id )) * & Slic3r:: LOOP_CLIPPING_LENGTH_OVER_NOZZLE_DIAMETER
2014-05-08 11:07:37 +02:00
: 0 ;
2012-06-28 14:44:54 +02:00
2014-05-08 11:07:37 +02:00
# get paths
my @paths = @ { $loop -> clip_end ( $clip_length )};
return '' if ! @paths ;
2013-06-20 20:11:46 +02:00
2013-06-21 14:52:35 +02:00
# apply the small perimeter speed
2014-05-13 08:34:21 +02:00
if ( $paths [ 0 ] -> is_perimeter && $loop -> length <= & Slic3r:: SMALL_PERIMETER_LENGTH ) {
2014-05-18 17:02:18 +02:00
$speed // = $self -> config -> get_abs_value ( 'small_perimeter_speed' );
2013-06-21 14:52:35 +02:00
}
2014-05-18 17:02:18 +02:00
$speed // = - 1 ;
2013-06-21 14:52:35 +02:00
2012-06-28 14:44:54 +02:00
# extrude along the path
2014-08-03 16:31:20 +02:00
my $gcode = join '' , map $self -> _extrude_path ( $_ , $description , $speed ), @paths ;
# reset acceleration
2014-10-28 21:47:09 +01:00
$gcode .= $self -> writer -> set_acceleration ( $self -> config -> default_acceleration );
2014-08-03 16:31:20 +02:00
2014-11-23 15:13:40 +01:00
$self -> wipe -> path ( $paths [ 0 ] -> polyline -> clone ) if $self -> wipe -> enable ; # TODO: don't limit wipe to last path
2012-12-05 11:31:35 +01:00
# make a little move inwards before leaving loop
2014-05-13 08:34:21 +02:00
if ( $paths [ - 1 ] -> role == EXTR_ROLE_EXTERNAL_PERIMETER && defined $self -> layer && $self -> config -> perimeters > 1 ) {
2014-05-08 11:07:37 +02:00
my $last_path_polyline = $paths [ - 1 ] -> polyline ;
2012-12-05 11:31:35 +01:00
# detect angle between last and first segment
# the side depends on the original winding order of the polygon (left for contours, right for holes)
my @points = $was_clockwise ? ( - 2 , 1 ) : ( 1 , - 2 );
2014-05-08 11:07:37 +02:00
my $angle = Slic3r::Geometry:: angle3points ( @$last_path_polyline [ 0 , @points ]) / 3 ;
2012-12-05 11:31:35 +01:00
$angle *= - 1 if $was_clockwise ;
# create the destination point along the first segment and rotate it
2013-03-09 17:15:45 +01:00
# we make sure we don't exceed the segment length because we don't know
# the rotation of the second segment so we might cross the object boundary
2014-05-08 11:07:37 +02:00
my $first_segment = Slic3r::Line -> new ( @$last_path_polyline [ 0 , 1 ]);
2014-10-28 21:47:09 +01:00
my $distance = min ( scale ( $self -> config -> get_at ( 'nozzle_diameter' , $self -> writer -> extruder -> id )), $first_segment -> length );
2013-10-27 22:57:25 +01:00
my $point = $first_segment -> point_at ( $distance );
2014-05-08 11:07:37 +02:00
$point -> rotate ( $angle , $last_path_polyline -> first_point );
2012-12-05 11:31:35 +01:00
# generate the travel move
2014-05-08 11:07:37 +02:00
$gcode .= $self -> travel_to ( $point , $paths [ - 1 ] -> role , "move inwards before travel" );
2012-12-05 11:31:35 +01:00
}
return $gcode ;
2012-06-28 14:44:54 +02:00
}
sub extrude_path {
2014-05-13 08:34:21 +02:00
my ( $self , $path , $description , $speed ) = @_ ;
2012-06-28 14:44:54 +02:00
2014-08-03 16:31:20 +02:00
my $gcode = $self -> _extrude_path ( $path , $description , $speed );
# reset acceleration
2014-10-28 21:47:09 +01:00
$gcode .= $self -> writer -> set_acceleration ( $self -> config -> default_acceleration );
2014-08-03 16:31:20 +02:00
return $gcode ;
}
sub _extrude_path {
my ( $self , $path , $description , $speed ) = @_ ;
2012-11-18 17:38:08 +01:00
$path -> simplify ( & Slic3r:: SCALED_RESOLUTION );
2012-06-28 14:44:54 +02:00
# go to first point of extrusion path
2013-03-05 17:30:27 +01:00
my $gcode = "" ;
2013-08-29 01:36:42 +02:00
{
my $first_point = $path -> first_point ;
$gcode .= $self -> travel_to ( $first_point , $path -> role , "move to first $description point" )
if ! defined $self -> last_pos || ! $self -> last_pos -> coincides_with ( $first_point );
}
2012-06-28 14:44:54 +02:00
# compensate retraction
2012-12-21 15:14:44 +01:00
$gcode .= $self -> unretract ;
2012-06-28 14:44:54 +02:00
2013-03-09 20:28:03 +01:00
# adjust acceleration
2014-08-03 16:31:20 +02:00
{
my $acceleration ;
2014-10-28 21:47:09 +01:00
if ( $self -> config -> first_layer_acceleration && $self -> first_layer ) {
2014-08-03 16:31:20 +02:00
$acceleration = $self -> config -> first_layer_acceleration ;
} elsif ( $self -> config -> perimeter_acceleration && $path -> is_perimeter ) {
2014-05-13 08:34:21 +02:00
$acceleration = $self -> config -> perimeter_acceleration ;
} elsif ( $self -> config -> infill_acceleration && $path -> is_fill ) {
$acceleration = $self -> config -> infill_acceleration ;
2014-10-15 00:23:58 +02:00
} elsif ( $self -> config -> bridge_acceleration && $path -> is_bridge ) {
2014-05-13 08:34:21 +02:00
$acceleration = $self -> config -> bridge_acceleration ;
2014-08-03 16:31:20 +02:00
} else {
$acceleration = $self -> config -> default_acceleration ;
2013-08-09 14:30:43 +02:00
}
2014-10-28 21:47:09 +01:00
$gcode .= $self -> writer -> set_acceleration ( $acceleration );
2013-03-09 20:31:09 +01:00
}
2013-03-09 20:28:03 +01:00
2012-07-03 18:05:31 +02:00
# calculate extrusion length per distance unit
2014-10-28 21:47:09 +01:00
my $e_per_mm = $self -> writer -> extruder -> e_per_mm3 * $path -> mm3_per_mm ;
2014-11-09 19:02:45 +01:00
$e_per_mm = 0 if ! $self -> writer -> extrusion_axis ;
2012-06-28 14:44:54 +02:00
2012-12-05 10:47:41 +01:00
# set speed
2014-11-23 19:15:28 +01:00
my $F = $speed // - 1 ;
if ( ! defined $F ) {
if ( $path -> role == EXTR_ROLE_PERIMETER ) {
$F = $self -> config -> get_abs_value ( 'perimeter_speed' );
} elsif ( $path -> role == EXTR_ROLE_EXTERNAL_PERIMETER ) {
$F = $self -> config -> get_abs_value ( 'external_perimeter_speed' );
} elsif ( $path -> role == EXTR_ROLE_OVERHANG_PERIMETER || $path -> role == EXTR_ROLE_BRIDGE ) {
$F = $self -> config -> get_abs_value ( 'bridge_speed' );
} elsif ( $path -> role == EXTR_ROLE_FILL ) {
$F = $self -> config -> get_abs_value ( 'infill_speed' );
} elsif ( $path -> role == EXTR_ROLE_SOLIDFILL ) {
$F = $self -> config -> get_abs_value ( 'solid_infill_speed' );
} elsif ( $path -> role == EXTR_ROLE_TOPSOLIDFILL ) {
$F = $self -> config -> get_abs_value ( 'top_solid_infill_speed' );
} elsif ( $path -> role == EXTR_ROLE_GAPFILL ) {
$F = $self -> config -> get_abs_value ( 'gap_fill_speed' );
} else {
die "Invalid speed" ;
}
2014-05-13 08:34:21 +02:00
}
$F *= 60 ; # convert mm/sec to mm/min
2014-10-28 21:47:09 +01:00
if ( $self -> first_layer ) {
2014-05-13 08:34:21 +02:00
$F = $self -> config -> get_abs_value_over ( 'first_layer_speed' , $F / 60 ) * 60 ;
2013-08-28 18:12:20 +02:00
}
2012-12-05 10:47:41 +01:00
# extrude arc or line
2014-10-28 21:47:09 +01:00
$gcode .= ";_BRIDGE_FAN_START\n" if $path -> is_bridge && $self -> enable_cooling_markers ;
2014-04-01 16:54:57 +03:00
my $path_length = unscale $path -> length ;
2013-08-28 19:50:16 +02:00
{
2014-10-28 21:47:09 +01:00
my $extruder_offset = $self -> config -> get_at ( 'extruder_offset' , $self -> writer -> extruder -> id );
$gcode .= $path -> gcode ( $self -> writer -> extruder , $e_per_mm , $F ,
2014-10-25 10:56:21 +02:00
$self -> origin -> x - $extruder_offset -> x ,
$self -> origin -> y - $extruder_offset -> y , #-
2014-11-09 19:02:45 +01:00
$self -> writer -> extrusion_axis ,
2014-05-13 08:34:21 +02:00
$self -> config -> gcode_comments ? " ; $description" : "" );
2014-04-01 16:54:57 +03:00
2014-11-23 15:13:40 +01:00
if ( $self -> wipe -> enable ) {
$self -> wipe -> path ( $path -> polyline -> clone );
$self -> wipe -> path -> reverse ;
2013-08-29 01:36:42 +02:00
}
2012-06-28 14:44:54 +02:00
}
2014-10-28 21:47:09 +01:00
$gcode .= ";_BRIDGE_FAN_END\n" if $path -> is_bridge && $self -> enable_cooling_markers ;
2013-09-02 22:33:03 +02:00
$self -> last_pos ( $path -> last_point );
2012-06-28 14:44:54 +02:00
2014-05-13 08:34:21 +02:00
if ( $self -> config -> cooling ) {
2013-08-28 18:12:20 +02:00
my $path_time = $path_length / $F * 60 ;
2012-06-28 14:44:54 +02:00
$self -> elapsed_time ( $self -> elapsed_time + $path_time );
}
return $gcode ;
}
2012-08-23 15:42:58 +02:00
sub travel_to {
2013-08-27 00:02:24 +02:00
my ( $self , $point , $role , $comment ) = @_ ;
2012-08-23 15:42:58 +02:00
2012-08-23 21:10:04 +02:00
my $gcode = "" ;
2014-10-27 10:34:51 +01:00
# Define the travel move as a line between current position and the taget point.
# This is expressed in print coordinates, so it will need to be translated by
# $self->origin in order to get G-code coordinates.
2013-07-16 17:13:01 +02:00
my $travel = Slic3r::Line -> new ( $self -> last_pos , $point );
2013-03-05 17:30:27 +01:00
2014-10-27 10:34:51 +01:00
# Skip retraction at all in the following cases:
# - travel length is shorter than the configured threshold
# - user has enabled "Only retract when crossing perimeters" and the travel move is
# contained in a single island of the current layer *and* a single island in the
# upper layer (so that ooze will not be visible)
# - the path that will be extruded after this travel move is a support material
# extrusion and the travel move is contained in a single support material island
2014-10-28 21:47:09 +01:00
if ( $travel -> length < scale $self -> config -> get_at ( 'retract_before_travel' , $self -> writer -> extruder -> id )
2014-05-13 08:34:21 +02:00
|| ( $self -> config -> only_retract_when_crossing_perimeters
2014-07-23 00:57:31 +02:00
&& $self -> config -> fill_density > 0
2013-11-21 14:35:28 +01:00
&& ( first { $_ -> contains_line ( $travel ) } @ { $self -> _upper_layer_islands })
&& ( first { $_ -> contains_line ( $travel ) } @ { $self -> _layer_islands }))
|| ( defined $role && $role == EXTR_ROLE_SUPPORTMATERIAL && ( first { $_ -> contains_line ( $travel ) } @ { $self -> layer -> support_islands }))
2013-03-05 17:30:27 +01:00
) {
2014-10-27 10:34:51 +01:00
# Just perform a straight travel move without any retraction.
2014-10-28 21:47:09 +01:00
$gcode .= $self -> writer -> travel_to_xy ( $self -> point_to_gcode ( $point ), $comment );
2014-11-23 15:13:40 +01:00
} elsif ( $self -> config -> avoid_crossing_perimeters && ! $self -> avoid_crossing_perimeters -> straight_once ) {
$gcode .= $self -> avoid_crossing_perimeters -> travel_to ( $self , $point , $comment );
2014-10-27 10:34:51 +01:00
} else {
# If avoid_crossing_perimeters is disabled or the straight_once flag is set,
# perform a straight move with a retraction.
$gcode .= $self -> retract ;
2014-11-09 19:02:45 +01:00
$gcode .= $self -> writer -> travel_to_xy ( $self -> point_to_gcode ( $point ), $comment || '' );
2012-08-23 15:42:58 +02:00
}
2012-08-23 21:10:04 +02:00
2014-10-27 10:34:51 +01:00
# Re-allow avoid_crossing_perimeters for the next travel moves
2014-11-23 15:13:40 +01:00
$self -> avoid_crossing_perimeters -> straight_once ( 0 );
2014-10-27 10:34:51 +01:00
2012-08-23 21:10:04 +02:00
return $gcode ;
2012-08-23 15:42:58 +02:00
}
2012-06-28 14:44:54 +02:00
sub retract {
2014-10-21 20:16:45 +02:00
my ( $self , $toolchange ) = @_ ;
2012-06-28 14:44:54 +02:00
2014-10-28 21:47:09 +01:00
return "" if ! defined $self -> writer -> extruder ;
2012-08-22 19:11:45 +02:00
2013-03-17 02:22:50 +01:00
my $gcode = "" ;
2014-10-21 20:16:45 +02:00
# wipe (if it's enabled for this extruder and we have a stored wipe path)
2014-11-23 15:13:40 +01:00
if ( $self -> config -> get_at ( 'wipe' , $self -> writer -> extruder -> id ) && $self -> wipe -> path ) {
$gcode .= $self -> wipe -> wipe ( $self , $toolchange );
2013-10-13 16:20:01 +02:00
}
2012-06-28 14:44:54 +02:00
2014-10-21 20:16:45 +02:00
# The parent class will decide whether we need to perform an actual retraction
# (the extruder might be already retracted fully or partially). We call these
# methods even if we performed wipe, since this will ensure the entire retraction
# length is honored in case wipe path was too short.p
2014-10-28 21:47:09 +01:00
$gcode .= $toolchange ? $self -> writer -> retract_for_toolchange : $self -> writer -> retract ;
2014-10-21 20:16:45 +02:00
2014-10-28 21:47:09 +01:00
$gcode .= $self -> writer -> reset_e ;
2014-11-05 01:16:26 +01:00
$gcode .= $self -> writer -> lift
if $self -> writer -> extruder -> retract_length > 0 ;
2012-06-28 14:44:54 +02:00
return $gcode ;
}
sub unretract {
2013-08-27 00:02:24 +02:00
my ( $self ) = @_ ;
2012-08-22 18:57:03 +02:00
2012-06-28 14:44:54 +02:00
my $gcode = "" ;
2014-10-28 21:47:09 +01:00
$gcode .= $self -> writer -> unlift ;
2014-11-09 19:02:45 +01:00
$gcode .= $self -> writer -> unretract ;
2012-06-28 14:44:54 +02:00
return $gcode ;
}
2014-10-21 20:16:45 +02:00
# convert a model-space scaled point into G-code coordinates
sub point_to_gcode {
my ( $self , $point ) = @_ ;
2014-10-28 21:47:09 +01:00
my $extruder_offset = $self -> config -> get_at ( 'extruder_offset' , $self -> writer -> extruder -> id );
2014-10-21 20:16:45 +02:00
return Slic3r::Pointf -> new (
2014-10-25 10:56:21 +02:00
( $point -> x * & Slic3r:: SCALING_FACTOR ) + $self -> origin -> x - $extruder_offset -> x ,
( $point -> y * & Slic3r:: SCALING_FACTOR ) + $self -> origin -> y - $extruder_offset -> y , #**
2014-10-21 20:16:45 +02:00
);
}
2012-09-23 02:40:25 +02:00
sub set_extruder {
2014-01-03 18:27:46 +01:00
my ( $self , $extruder_id ) = @_ ;
2012-06-28 14:44:54 +02:00
2014-10-28 21:47:09 +01:00
return "" if ! $self -> writer -> need_toolchange ( $extruder_id );
2012-08-22 19:20:34 +02:00
2012-08-22 19:47:59 +02:00
# if we are running a single-extruder setup, just set the extruder and return nothing
2014-10-28 21:47:09 +01:00
if ( ! $self -> writer -> multiple_extruders ) {
return $self -> writer -> toolchange ( $extruder_id );
2012-08-22 19:47:59 +02:00
}
2014-10-18 17:41:21 +02:00
# prepend retraction on the current extruder
2014-10-21 20:16:45 +02:00
my $gcode = $self -> retract ( 1 );
2012-08-22 19:47:59 +02:00
2012-12-23 16:29:08 +01:00
# append custom toolchange G-code
2014-10-28 21:47:09 +01:00
if ( defined $self -> writer -> extruder && $self -> config -> toolchange_gcode ) {
2014-05-13 08:34:21 +02:00
$gcode .= sprintf "%s\n" , $self -> placeholder_parser -> process ( $self -> config -> toolchange_gcode , {
2014-10-28 21:47:09 +01:00
previous_extruder => $self -> writer -> extruder -> id ,
2014-01-03 18:27:46 +01:00
next_extruder => $extruder_id ,
2012-12-23 16:29:08 +01:00
});
}
2014-10-18 17:58:14 +02:00
# 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 )
2014-11-23 15:13:40 +01:00
if $self -> ooze_prevention -> enable && defined $self -> writer -> extruder ;
2013-09-18 18:49:19 +02:00
2014-10-18 17:41:21 +02:00
# append the toolchange command
2014-10-28 21:47:09 +01:00
$gcode .= $self -> writer -> toolchange ( $extruder_id );
2012-08-22 19:47:59 +02:00
2013-09-18 18:49:19 +02:00
# set the new extruder to the operating temperature
2014-10-18 17:58:14 +02:00
$gcode .= $self -> ooze_prevention -> post_toolchange ( $self )
2014-11-23 15:13:40 +01:00
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 ;
2012-08-30 23:13:28 +02:00
return $gcode ;
2012-06-28 14:44:54 +02:00
}
1 ;