2012-06-28 12:44:54 +00:00
package Slic3r::GCode ;
use Moo ;
2014-03-26 23:01:33 +00:00
use List::Util qw( min max first ) ;
2014-05-12 19:49:17 +00:00
use Slic3r::ExtrusionLoop ':roles' ;
2012-06-28 12:44:54 +00:00
use Slic3r::ExtrusionPath ':roles' ;
2014-10-25 09:15:12 +00:00
use Slic3r::Geometry qw( epsilon scale unscale PI X Y B ) ;
2014-11-23 19:16:51 +00:00
use Slic3r::Geometry::Clipper qw( union_ex ) ;
2012-06-28 12:44:54 +00:00
2014-10-25 08:56:21 +00: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 06:34:21 +00:00
has 'config' = > ( is = > 'ro' , default = > sub { Slic3r::Config::Full - > new } ) ;
2014-10-28 20:47:09 +00:00
has 'writer' = > ( is = > 'ro' , default = > sub { Slic3r::GCode::Writer - > new } ) ;
2014-03-16 23:39:07 +00:00
has 'placeholder_parser' = > ( is = > 'rw' , default = > sub { Slic3r::GCode::PlaceholderParser - > new } ) ;
2014-11-23 14:13:40 +00: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 11:39:15 +00:00
has 'enable_loop_clipping' = > ( is = > 'rw' , default = > sub { 1 } ) ;
2014-10-28 20:47:09 +00:00
has 'enable_cooling_markers' = > ( is = > 'rw' , default = > sub { 0 } ) ;
2014-10-18 15:41:21 +00:00
has 'layer_count' = > ( is = > 'ro' ) ;
2015-05-03 18:18:34 +00:00
has 'layer_index' = > ( is = > 'rw' , default = > sub { - 1 } ) ; # just a counter
2012-06-28 12:44:54 +00:00
has 'layer' = > ( is = > 'rw' ) ;
2014-05-24 20:10:28 +00:00
has '_seam_position' = > ( is = > 'ro' , default = > sub { { } } ) ; # $object => pos
2014-10-28 20:47:09 +00:00
has 'first_layer' = > ( is = > 'rw' , default = > sub { 0 } ) ; # this flag triggers first layer speeds
2012-06-28 12:44:54 +00: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 15:41:21 +00:00
sub apply_print_config {
my ( $ self , $ print_config ) = @ _ ;
2014-10-28 20:47:09 +00:00
$ self - > writer - > apply_print_config ( $ print_config ) ;
2014-10-18 15:41:21 +00:00
$ self - > config - > apply_print_config ( $ print_config ) ;
}
2014-01-03 17:27:46 +00:00
sub set_extruders {
2014-10-18 15:41:21 +00:00
my ( $ self , $ extruder_ids ) = @ _ ;
2014-01-03 17:27:46 +00:00
2014-10-28 20:47:09 +00:00
$ self - > writer - > set_extruders ( $ extruder_ids ) ;
2014-03-26 23:01:33 +00:00
2014-10-18 15:41:21 +00:00
# enable wipe path generation if any extruder has wipe enabled
2014-11-23 14:13:40 +00:00
$ self - > wipe - > enable ( defined first { $ self - > config - > get_at ( 'wipe' , $ _ ) } @$ extruder_ids ) ;
2014-01-03 17:27:46 +00:00
}
2014-10-25 08:56:21 +00:00
sub set_origin {
my ( $ self , $ pointf ) = @ _ ;
2012-08-23 13:42:58 +00:00
2014-10-25 08:56:21 +00:00
# if origin increases (goes towards right), last_pos decreases because it goes towards left
2013-03-25 22:06:18 +00:00
my @ translate = (
2014-10-25 08:56:21 +00:00
scale ( $ self - > origin - > x - $ pointf - > x ) ,
scale ( $ self - > origin - > y - $ pointf - > y ) , #-
2012-12-09 17:33:25 +00:00
) ;
2013-03-25 22:06:18 +00:00
$ self - > last_pos - > translate ( @ translate ) ;
2014-11-23 14:13:40 +00:00
$ self - > wipe - > path - > translate ( @ translate ) if $ self - > wipe - > path ;
2012-08-23 13:42:58 +00:00
2014-10-25 08:56:21 +00:00
$ self - > origin ( $ pointf ) ;
2012-08-23 13:42:58 +00:00
}
2014-11-09 18:24:17 +00: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 12:44:54 +00:00
sub change_layer {
2013-08-26 22:02:24 +00:00
my ( $ self , $ layer ) = @ _ ;
2012-06-28 12:44:54 +00:00
$ self - > layer ( $ layer ) ;
2015-05-03 18:18:34 +00:00
$ self - > layer_index ( $ self - > layer_index + 1 ) ;
2014-10-28 20:47:09 +00:00
$ self - > first_layer ( $ layer - > id == 0 ) ;
2013-07-07 17:12:44 +00:00
2013-08-09 12:55:36 +00:00
# avoid computing islands and overhangs if they're not needed
2014-05-13 06:34:21 +00:00
if ( $ self - > config - > avoid_crossing_perimeters ) {
2014-11-23 14:13:40 +00:00
$ self - > avoid_crossing_perimeters - > init_layer_mp (
2014-05-13 18:06:01 +00:00
union_ex ( [ map @$ _ , @ { $ layer - > slices } ] , 1 ) ,
2014-11-23 14:13:40 +00:00
) ;
2012-08-23 13:42:58 +00:00
}
2013-01-17 13:56:31 +00:00
my $ gcode = "" ;
2014-10-21 18:16:45 +00:00
if ( defined $ self - > layer_count ) {
2015-05-03 18:18:34 +00:00
$ gcode . = $ self - > writer - > update_progress ( $ self - > layer_index , $ self - > layer_count ) ;
2013-01-17 13:56:31 +00:00
}
2013-08-09 17:46:20 +00:00
2014-10-21 18:16:45 +00:00
my $ z = $ layer - > print_z + $ self - > config - > z_offset ; # in unscaled coordinates
2014-10-28 20:47:09 +00:00
if ( $ self - > config - > get_at ( 'retract_layer_change' , $ self - > writer - > extruder - > id ) && $ self - > writer - > will_move_z ( $ z ) ) {
2014-10-21 18:16:45 +00:00
$ gcode . = $ self - > retract ;
2012-10-30 09:45:55 +00:00
}
2015-05-03 18:18:34 +00:00
$ gcode . = $ self - > writer - > travel_to_z ( $ z , 'move to next layer (' . $ self - > layer_index . ')' ) ;
2014-12-24 11:02:42 +00:00
# forget last wiping path as wiping after raising Z is pointless
$ self - > wipe - > path ( undef ) ;
2012-06-28 12:44:54 +00:00
return $ gcode ;
}
sub extrude {
my $ self = shift ;
2013-07-15 14:21:09 +00:00
$ _ [ 0 ] - > isa ( 'Slic3r::ExtrusionLoop' )
2012-07-20 12:39:07 +00:00
? $ self - > extrude_loop ( @ _ )
: $ self - > extrude_path ( @ _ ) ;
2012-06-28 12:44:54 +00:00
}
sub extrude_loop {
2014-05-18 15:02:18 +00:00
my ( $ self , $ loop , $ description , $ speed ) = @ _ ;
2012-06-28 12:44:54 +00:00
2014-03-24 19:01:14 +00: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 12:44:54 +00:00
# extrude all loops ccw
2013-07-16 15:13:01 +00:00
my $ was_clockwise = $ loop - > make_counter_clockwise ;
2012-06-28 12:44:54 +00: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 10:28:12 +00:00
if ( $ self - > config - > spiral_vase ) {
$ loop - > split_at ( $ last_pos ) ;
2014-05-24 20:10:28 +00:00
} elsif ( $ self - > config - > seam_position eq 'nearest' || $ self - > config - > seam_position eq 'aligned' ) {
2014-07-25 10:04:33 +00:00
# simplify polygon in order to skip false positives in concave/convex detection
2014-05-22 17:34:49 +00:00
my $ polygon = $ loop - > polygon ;
2014-10-28 20:47:09 +00:00
my @ simplified = @ { $ polygon - > simplify ( scale $ self - > config - > get_at ( 'nozzle_diameter' , $ self - > writer - > extruder - > id ) / 2 ) } ;
2014-05-22 17:34:49 +00:00
2014-07-25 10:04:33 +00:00
# concave vertices have priority
my @ candidates = map @ { $ _ - > concave_points ( PI * 4 / 3 ) } , @ simplified ;
2014-05-22 17:34:49 +00:00
2014-07-25 10:04:33 +00: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 20:47:09 +00:00
my $ obj_ptr = 0 ;
2014-07-25 10:04:33 +00:00
if ( defined $ self - > layer ) {
$ obj_ptr = $ self - > layer - > object - > ptr ;
2015-01-31 20:45:27 +00:00
if ( defined $ self - > _seam_position - > { $ obj_ptr } ) {
2014-06-10 14:01:57 +00:00
$ last_pos = $ self - > _seam_position - > { $ obj_ptr } ;
2014-05-22 17:34:49 +00:00
}
2014-07-25 10:04:33 +00:00
}
my $ point ;
if ( $ self - > config - > seam_position eq 'nearest' ) {
@ candidates = @$ polygon if ! @ candidates ;
$ point = $ last_pos - > nearest_point ( \ @ candidates ) ;
2014-11-08 11:56:14 +00: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 10:04:33 +00: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 11:56:14 +00:00
if ( ! $ loop - > split_at_vertex ( $ point ) ) {
$ loop - > split_at ( $ point ) ;
}
2014-07-25 10:04:33 +00:00
} else {
$ point = $ last_pos - > projection_onto_polygon ( $ polygon ) ;
$ loop - > split_at ( $ point ) ;
2014-05-22 17:34:49 +00:00
}
2014-07-25 10:04:33 +00:00
$ self - > _seam_position - > { $ obj_ptr } = $ point ;
2014-05-24 20:10:28 +00:00
} elsif ( $ self - > config - > seam_position eq 'random' ) {
2014-05-22 17:34:49 +00: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 10:28:12 +00:00
}
2012-06-28 12:44:54 +00: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 09:07:37 +00:00
my $ clip_length = $ self - > enable_loop_clipping
2014-10-28 20:47:09 +00:00
? scale ( $ self - > config - > get_at ( 'nozzle_diameter' , $ self - > writer - > extruder - > id ) ) * & Slic3r:: LOOP_CLIPPING_LENGTH_OVER_NOZZLE_DIAMETER
2014-05-08 09:07:37 +00:00
: 0 ;
2012-06-28 12:44:54 +00:00
2014-05-08 09:07:37 +00:00
# get paths
my @ paths = @ { $ loop - > clip_end ( $ clip_length ) } ;
return '' if ! @ paths ;
2013-06-20 18:11:46 +00:00
2013-06-21 12:52:35 +00:00
# apply the small perimeter speed
2014-05-13 06:34:21 +00:00
if ( $ paths [ 0 ] - > is_perimeter && $ loop - > length <= & Slic3r:: SMALL_PERIMETER_LENGTH ) {
2014-05-18 15:02:18 +00:00
$ speed // = $ self - > config - > get_abs_value ( 'small_perimeter_speed' ) ;
2013-06-21 12:52:35 +00:00
}
2012-06-28 12:44:54 +00:00
# extrude along the path
2014-08-03 14:31:20 +00:00
my $ gcode = join '' , map $ self - > _extrude_path ( $ _ , $ description , $ speed ) , @ paths ;
# reset acceleration
2014-10-28 20:47:09 +00:00
$ gcode . = $ self - > writer - > set_acceleration ( $ self - > config - > default_acceleration ) ;
2014-08-03 14:31:20 +00:00
2014-11-23 14:13:40 +00:00
$ self - > wipe - > path ( $ paths [ 0 ] - > polyline - > clone ) if $ self - > wipe - > enable ; # TODO: don't limit wipe to last path
2012-12-05 10:31:35 +00:00
# make a little move inwards before leaving loop
2014-05-13 06:34:21 +00:00
if ( $ paths [ - 1 ] - > role == EXTR_ROLE_EXTERNAL_PERIMETER && defined $ self - > layer && $ self - > config - > perimeters > 1 ) {
2014-05-08 09:07:37 +00:00
my $ last_path_polyline = $ paths [ - 1 ] - > polyline ;
2012-12-05 10:31:35 +00: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)
2015-01-08 14:19:56 +00:00
my @ points = ( $ paths [ 0 ] [ 1 ] , $ paths [ - 1 ] [ - 2 ] ) ;
@ points = reverse @ points if $ was_clockwise ;
my $ angle = $ paths [ 0 ] - > first_point - > ccw_angle ( @ points ) / 3 ;
# turn left if contour, turn right if hole
2012-12-05 10:31:35 +00:00
$ angle *= - 1 if $ was_clockwise ;
# create the destination point along the first segment and rotate it
2013-03-09 16:15:45 +00: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-12-25 10:06:42 +00:00
my $ first_segment = Slic3r::Line - > new ( @ { $ paths [ 0 ] - > polyline } [ 0 , 1 ] ) ;
2014-10-28 20:47:09 +00:00
my $ distance = min ( scale ( $ self - > config - > get_at ( 'nozzle_diameter' , $ self - > writer - > extruder - > id ) ) , $ first_segment - > length ) ;
2013-10-27 21:57:25 +00:00
my $ point = $ first_segment - > point_at ( $ distance ) ;
2014-12-25 10:06:42 +00:00
$ point - > rotate ( $ angle , $ first_segment - > a ) ;
2012-12-05 10:31:35 +00:00
# generate the travel move
2014-12-29 16:40:56 +00:00
$ gcode . = $ self - > writer - > travel_to_xy ( $ self - > point_to_gcode ( $ point ) , "move inwards before travel" ) ;
2012-12-05 10:31:35 +00:00
}
return $ gcode ;
2012-06-28 12:44:54 +00:00
}
sub extrude_path {
2014-05-13 06:34:21 +00:00
my ( $ self , $ path , $ description , $ speed ) = @ _ ;
2012-06-28 12:44:54 +00:00
2014-08-03 14:31:20 +00:00
my $ gcode = $ self - > _extrude_path ( $ path , $ description , $ speed ) ;
# reset acceleration
2014-10-28 20:47:09 +00:00
$ gcode . = $ self - > writer - > set_acceleration ( $ self - > config - > default_acceleration ) ;
2014-08-03 14:31:20 +00:00
return $ gcode ;
}
sub _extrude_path {
my ( $ self , $ path , $ description , $ speed ) = @ _ ;
2012-11-18 16:38:08 +00:00
$ path - > simplify ( & Slic3r:: SCALED_RESOLUTION ) ;
2012-06-28 12:44:54 +00:00
# go to first point of extrusion path
2013-03-05 16:30:27 +00:00
my $ gcode = "" ;
2013-08-28 23:36:42 +00: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 12:44:54 +00:00
# compensate retraction
2012-12-21 14:14:44 +00:00
$ gcode . = $ self - > unretract ;
2012-06-28 12:44:54 +00:00
2013-03-09 19:28:03 +00:00
# adjust acceleration
2014-08-03 14:31:20 +00:00
{
my $ acceleration ;
2014-10-28 20:47:09 +00:00
if ( $ self - > config - > first_layer_acceleration && $ self - > first_layer ) {
2014-08-03 14:31:20 +00:00
$ acceleration = $ self - > config - > first_layer_acceleration ;
} elsif ( $ self - > config - > perimeter_acceleration && $ path - > is_perimeter ) {
2014-05-13 06:34:21 +00:00
$ acceleration = $ self - > config - > perimeter_acceleration ;
2014-10-14 22:23:58 +00:00
} elsif ( $ self - > config - > bridge_acceleration && $ path - > is_bridge ) {
2014-05-13 06:34:21 +00:00
$ acceleration = $ self - > config - > bridge_acceleration ;
2014-12-16 23:34:00 +00:00
} elsif ( $ self - > config - > infill_acceleration && $ path - > is_infill ) {
$ acceleration = $ self - > config - > infill_acceleration ;
2014-08-03 14:31:20 +00:00
} else {
$ acceleration = $ self - > config - > default_acceleration ;
2013-08-09 12:30:43 +00:00
}
2014-10-28 20:47:09 +00:00
$ gcode . = $ self - > writer - > set_acceleration ( $ acceleration ) ;
2013-03-09 19:31:09 +00:00
}
2013-03-09 19:28:03 +00:00
2012-07-03 16:05:31 +00:00
# calculate extrusion length per distance unit
2014-10-28 20:47:09 +00:00
my $ e_per_mm = $ self - > writer - > extruder - > e_per_mm3 * $ path - > mm3_per_mm ;
2014-11-09 18:02:45 +00:00
$ e_per_mm = 0 if ! $ self - > writer - > extrusion_axis ;
2012-06-28 12:44:54 +00:00
2012-12-05 09:47:41 +00:00
# set speed
2014-11-23 18:37:59 +00:00
$ speed // = - 1 ;
if ( $ speed == - 1 ) {
2014-11-23 18:15:28 +00:00
if ( $ path - > role == EXTR_ROLE_PERIMETER ) {
2014-11-23 18:37:59 +00:00
$ speed = $ self - > config - > get_abs_value ( 'perimeter_speed' ) ;
2014-11-23 18:15:28 +00:00
} elsif ( $ path - > role == EXTR_ROLE_EXTERNAL_PERIMETER ) {
2014-11-23 18:37:59 +00:00
$ speed = $ self - > config - > get_abs_value ( 'external_perimeter_speed' ) ;
2014-11-23 18:15:28 +00:00
} elsif ( $ path - > role == EXTR_ROLE_OVERHANG_PERIMETER || $ path - > role == EXTR_ROLE_BRIDGE ) {
2014-11-23 18:37:59 +00:00
$ speed = $ self - > config - > get_abs_value ( 'bridge_speed' ) ;
2014-11-23 18:15:28 +00:00
} elsif ( $ path - > role == EXTR_ROLE_FILL ) {
2014-11-23 18:37:59 +00:00
$ speed = $ self - > config - > get_abs_value ( 'infill_speed' ) ;
2014-11-23 18:15:28 +00:00
} elsif ( $ path - > role == EXTR_ROLE_SOLIDFILL ) {
2014-11-23 18:37:59 +00:00
$ speed = $ self - > config - > get_abs_value ( 'solid_infill_speed' ) ;
2014-11-23 18:15:28 +00:00
} elsif ( $ path - > role == EXTR_ROLE_TOPSOLIDFILL ) {
2014-11-23 18:37:59 +00:00
$ speed = $ self - > config - > get_abs_value ( 'top_solid_infill_speed' ) ;
2014-11-23 18:15:28 +00:00
} elsif ( $ path - > role == EXTR_ROLE_GAPFILL ) {
2014-11-23 18:37:59 +00:00
$ speed = $ self - > config - > get_abs_value ( 'gap_fill_speed' ) ;
2014-11-23 18:15:28 +00:00
} else {
die "Invalid speed" ;
}
2014-05-13 06:34:21 +00:00
}
2014-11-23 18:37:59 +00:00
my $ F = $ speed * 60 ; # convert mm/sec to mm/min
2014-05-13 06:34:21 +00:00
2014-10-28 20:47:09 +00:00
if ( $ self - > first_layer ) {
2014-05-13 06:34:21 +00:00
$ F = $ self - > config - > get_abs_value_over ( 'first_layer_speed' , $ F / 60 ) * 60 ;
2013-08-28 16:12:20 +00:00
}
2012-12-05 09:47:41 +00:00
# extrude arc or line
2014-10-28 20:47:09 +00:00
$ gcode . = ";_BRIDGE_FAN_START\n" if $ path - > is_bridge && $ self - > enable_cooling_markers ;
2014-04-01 13:54:57 +00:00
my $ path_length = unscale $ path - > length ;
2013-08-28 17:50:16 +00:00
{
2014-10-28 20:47:09 +00: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 08:56:21 +00:00
$ self - > origin - > x - $ extruder_offset - > x ,
$ self - > origin - > y - $ extruder_offset - > y , #-
2014-11-09 18:02:45 +00:00
$ self - > writer - > extrusion_axis ,
2014-05-13 06:34:21 +00:00
$ self - > config - > gcode_comments ? " ; $description" : "" ) ;
2014-04-01 13:54:57 +00:00
2014-11-23 14:13:40 +00:00
if ( $ self - > wipe - > enable ) {
$ self - > wipe - > path ( $ path - > polyline - > clone ) ;
$ self - > wipe - > path - > reverse ;
2013-08-28 23:36:42 +00:00
}
2012-06-28 12:44:54 +00:00
}
2014-10-28 20:47:09 +00:00
$ gcode . = ";_BRIDGE_FAN_END\n" if $ path - > is_bridge && $ self - > enable_cooling_markers ;
2013-09-02 20:33:03 +00:00
$ self - > last_pos ( $ path - > last_point ) ;
2012-06-28 12:44:54 +00:00
2014-05-13 06:34:21 +00:00
if ( $ self - > config - > cooling ) {
2013-08-28 16:12:20 +00:00
my $ path_time = $ path_length / $ F * 60 ;
2012-06-28 12:44:54 +00:00
$ self - > elapsed_time ( $ self - > elapsed_time + $ path_time ) ;
}
return $ gcode ;
}
2014-12-28 21:09:28 +00:00
# This method accepts $point in print coordinates.
2012-08-23 13:42:58 +00:00
sub travel_to {
2013-08-26 22:02:24 +00:00
my ( $ self , $ point , $ role , $ comment ) = @ _ ;
2012-08-23 13:42:58 +00:00
2014-10-27 09:34:51 +00: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.
2015-01-06 13:47:53 +00:00
my $ travel = Slic3r::Polyline - > new ( $ self - > last_pos , $ point ) ;
# check whether a straight travel move would need retraction
my $ needs_retraction = $ self - > needs_retraction ( $ travel , $ role ) ;
# if a retraction would be needed, try to use avoid_crossing_perimeters to plan a
# multi-hop travel path inside the configuration space
if ( $ needs_retraction
&& $ self - > config - > avoid_crossing_perimeters
&& ! $ self - > avoid_crossing_perimeters - > disable_once ) {
$ travel = $ self - > avoid_crossing_perimeters - > travel_to ( $ self , $ point ) ;
# check again whether the new travel path still needs a retraction
$ needs_retraction = $ self - > needs_retraction ( $ travel , $ role ) ;
2012-08-23 13:42:58 +00:00
}
2012-08-23 19:10:04 +00:00
2014-10-27 09:34:51 +00:00
# Re-allow avoid_crossing_perimeters for the next travel moves
2014-12-16 17:13:38 +00:00
$ self - > avoid_crossing_perimeters - > disable_once ( 0 ) ;
2015-01-06 13:47:53 +00:00
$ self - > avoid_crossing_perimeters - > use_external_mp_once ( 0 ) ;
# generate G-code for the travel move
my $ gcode = "" ;
$ gcode . = $ self - > retract if $ needs_retraction ;
# use G1 because we rely on paths being straight (G0 may make round paths)
$ gcode . = $ self - > writer - > travel_to_xy ( $ self - > point_to_gcode ( $ _ - > b ) , $ comment )
for @ { $ travel - > lines } ;
2014-10-27 09:34:51 +00:00
2012-08-23 19:10:04 +00:00
return $ gcode ;
2012-08-23 13:42:58 +00:00
}
2015-01-06 13:47:53 +00:00
sub needs_retraction {
my ( $ self , $ travel , $ role ) = @ _ ;
if ( $ travel - > length < scale $ self - > config - > get_at ( 'retract_before_travel' , $ self - > writer - > extruder - > id ) ) {
# skip retraction if the move is shorter than the configured threshold
return 0 ;
}
2015-01-06 19:54:32 +00:00
if ( defined $ role && $ role == EXTR_ROLE_SUPPORTMATERIAL && $ self - > layer - > support_islands - > contains_polyline ( $ travel ) ) {
2015-01-06 13:47:53 +00:00
# skip retraction if this is a travel move inside a support material island
return 0 ;
}
if ( $ self - > config - > only_retract_when_crossing_perimeters && defined $ self - > layer ) {
if ( $ self - > config - > fill_density > 0
&& $ self - > layer - > any_internal_region_slice_contains_polyline ( $ travel ) ) {
# skip retraction if travel is contained in an internal slice *and*
# internal infill is enabled (so that stringing is entirely not visible)
return 0 ;
} elsif ( $ self - > layer - > any_bottom_region_slice_contains_polyline ( $ travel )
&& defined $ self - > layer - > upper_layer
&& $ self - > layer - > upper_layer - > slices - > contains_polyline ( $ travel )
&& ( $ self - > config - > bottom_solid_layers >= 2 || $ self - > config - > fill_density > 0 ) ) {
# skip retraction if travel is contained in an *infilled* bottom slice
# but only if it's also covered by an *infilled* upper layer's slice
# so that it's not visible from above (a bottom surface might not have an
# upper slice in case of a thin membrane)
return 0 ;
}
}
# retract if only_retract_when_crossing_perimeters is disabled or doesn't apply
return 1 ;
}
2012-06-28 12:44:54 +00:00
sub retract {
2014-10-21 18:16:45 +00:00
my ( $ self , $ toolchange ) = @ _ ;
2012-06-28 12:44:54 +00:00
2014-10-28 20:47:09 +00:00
return "" if ! defined $ self - > writer - > extruder ;
2012-08-22 17:11:45 +00:00
2013-03-17 01:22:50 +00:00
my $ gcode = "" ;
2014-10-21 18:16:45 +00:00
# wipe (if it's enabled for this extruder and we have a stored wipe path)
2014-11-23 14:13:40 +00:00
if ( $ self - > config - > get_at ( 'wipe' , $ self - > writer - > extruder - > id ) && $ self - > wipe - > path ) {
$ gcode . = $ self - > wipe - > wipe ( $ self , $ toolchange ) ;
2012-06-28 12:44:54 +00:00
}
2014-10-21 18:16:45 +00: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 20:47:09 +00:00
$ gcode . = $ toolchange ? $ self - > writer - > retract_for_toolchange : $ self - > writer - > retract ;
2014-10-21 18:16:45 +00:00
2014-10-28 20:47:09 +00:00
$ gcode . = $ self - > writer - > reset_e ;
2014-11-05 00:16:26 +00:00
$ gcode . = $ self - > writer - > lift
2014-11-23 18:28:18 +00:00
if $ self - > writer - > extruder - > retract_length > 0 || $ self - > config - > use_firmware_retraction ;
2012-06-28 12:44:54 +00:00
return $ gcode ;
}
sub unretract {
2013-08-26 22:02:24 +00:00
my ( $ self ) = @ _ ;
2012-08-22 16:57:03 +00:00
2012-06-28 12:44:54 +00:00
my $ gcode = "" ;
2014-10-28 20:47:09 +00:00
$ gcode . = $ self - > writer - > unlift ;
2014-11-09 18:02:45 +00:00
$ gcode . = $ self - > writer - > unretract ;
2012-06-28 12:44:54 +00:00
return $ gcode ;
}
2014-10-21 18:16:45 +00:00
# convert a model-space scaled point into G-code coordinates
sub point_to_gcode {
my ( $ self , $ point ) = @ _ ;
2014-10-28 20:47:09 +00:00
my $ extruder_offset = $ self - > config - > get_at ( 'extruder_offset' , $ self - > writer - > extruder - > id ) ;
2014-10-21 18:16:45 +00:00
return Slic3r::Pointf - > new (
2014-10-25 08:56:21 +00: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 18:16:45 +00:00
) ;
}
2012-09-23 00:40:25 +00:00
sub set_extruder {
2014-01-03 17:27:46 +00:00
my ( $ self , $ extruder_id ) = @ _ ;
2012-06-28 12:44:54 +00:00
2014-10-28 20:47:09 +00:00
return "" if ! $ self - > writer - > need_toolchange ( $ extruder_id ) ;
2012-08-22 17:47:59 +00:00
# if we are running a single-extruder setup, just set the extruder and return nothing
2014-10-28 20:47:09 +00:00
if ( ! $ self - > writer - > multiple_extruders ) {
return $ self - > writer - > toolchange ( $ extruder_id ) ;
2012-08-22 17:47:59 +00:00
}
2014-10-18 15:41:21 +00:00
# prepend retraction on the current extruder
2014-10-21 18:16:45 +00:00
my $ gcode = $ self - > retract ( 1 ) ;
2012-08-22 17:47:59 +00:00
2012-12-23 15:29:08 +00:00
# append custom toolchange G-code
2014-10-28 20:47:09 +00:00
if ( defined $ self - > writer - > extruder && $ self - > config - > toolchange_gcode ) {
2014-05-13 06:34:21 +00:00
$ gcode . = sprintf "%s\n" , $ self - > placeholder_parser - > process ( $ self - > config - > toolchange_gcode , {
2014-10-28 20:47:09 +00:00
previous_extruder = > $ self - > writer - > extruder - > id ,
2014-01-03 17:27:46 +00:00
next_extruder = > $ extruder_id ,
2012-12-23 15:29:08 +00:00
} ) ;
}
2014-10-18 15:58:14 +00: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 14:13:40 +00:00
if $ self - > ooze_prevention - > enable && defined $ self - > writer - > extruder ;
2013-01-17 14:02:40 +00:00
2014-10-18 15:41:21 +00:00
# append the toolchange command
2014-10-28 20:47:09 +00:00
$ gcode . = $ self - > writer - > toolchange ( $ extruder_id ) ;
2012-08-22 17:20:34 +00:00
2013-09-18 16:49:19 +00:00
# set the new extruder to the operating temperature
2014-10-18 15:58:14 +00:00
$ gcode . = $ self - > ooze_prevention - > post_toolchange ( $ self )
2014-11-23 14:13:40 +00: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 } ) {
2015-01-19 14:30:34 +00:00
# get current position in print coordinates
my $ pos = Slic3r::Point - > new_scale ( @ { $ gcodegen - > writer - > get_position } [ 0 , 1 ] ) ;
my $ standby_point = Slic3r::Pointf - > new_unscale ( @ { $ pos - > nearest_point ( $ self - > standby_points ) } ) ;
# We don't call $gcodegen->travel_to() because we don't need retraction (it was already
# triggered by the caller) nor avoid_crossing_perimeters and also because the coordinates
# of the destination point must not be transformed by origin nor current extruder offset.
$ gcode . = $ gcodegen - > writer - > travel_to_xy ( $ standby_point , 'move to standby position' ) ;
2014-11-23 14:13:40 +00:00
}
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 ,
2015-03-06 09:25:31 +00:00
'wipe and retract' . ( $ gcodegen - > enable_cooling_markers ? ';_WIPE' : '' ) ,
2014-11-23 14:13:40 +00:00
) ;
$ retracted += $ dE ;
}
$ gcodegen - > writer - > extruder - > set_retracted ( $ gcodegen - > writer - > extruder - > retracted + $ retracted ) ;
2015-03-06 09:36:04 +00:00
# prevent wiping again on same path
$ self - > path ( undef ) ;
2014-11-23 14:13:40 +00:00
}
return $ gcode ;
}
package Slic3r::GCode::AvoidCrossingPerimeters ;
use Moo ;
2014-12-16 17:13:38 +00:00
has '_external_mp' = > ( is = > 'rw' ) ;
has '_layer_mp' = > ( is = > 'rw' ) ;
has 'use_external_mp' = > ( is = > 'rw' , default = > sub { 0 } ) ;
has 'use_external_mp_once' = > ( is = > 'rw' , default = > sub { 0 } ) ; # this flag triggers the use of the external configuration space for avoid_crossing_perimeters for the next travel move
2014-11-23 14:13:40 +00:00
2015-01-06 13:47:53 +00:00
# this flag disables avoid_crossing_perimeters just for the next travel move
# we enable it by default for the first travel move in print
has 'disable_once' = > ( is = > 'rw' , default = > sub { 1 } ) ;
2014-11-26 21:46:51 +00:00
2014-11-23 14:13:40 +00:00
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 {
2015-01-06 13:47:53 +00:00
my ( $ self , $ gcodegen , $ point ) = @ _ ;
2014-11-23 14:13:40 +00:00
2014-12-16 17:13:38 +00:00
if ( $ self - > use_external_mp || $ self - > use_external_mp_once ) {
2015-01-06 13:47:53 +00:00
# get current origin set in $gcodegen
# (the one that will be used to translate the G-code coordinates by)
my $ scaled_origin = Slic3r::Point - > new_scale ( @ { $ gcodegen - > origin } ) ;
2014-11-23 14:13:40 +00:00
2015-01-06 13:47:53 +00:00
# represent last_pos in absolute G-code coordinates
my $ last_pos = $ gcodegen - > last_pos - > clone ;
2015-01-06 19:52:36 +00:00
$ last_pos - > translate ( @$ scaled_origin ) ;
2015-01-06 13:47:53 +00:00
# represent $point in absolute G-code coordinates
2014-11-23 14:13:40 +00:00
$ point = $ point - > clone ;
2015-01-06 13:47:53 +00:00
$ point - > translate ( @$ scaled_origin ) ;
# calculate path
my $ travel = $ self - > _external_mp - > shortest_path ( $ last_pos , $ point ) ;
2014-11-23 14:13:40 +00:00
2015-01-06 13:47:53 +00:00
# translate the path back into the shifted coordinate system that $gcodegen
# is currently using for writing coordinates
$ travel - > translate ( @ { $ scaled_origin - > negative } ) ;
return $ travel ;
2014-11-23 14:13:40 +00:00
} else {
2015-01-06 13:47:53 +00:00
return $ self - > _layer_mp - > shortest_path ( $ gcodegen - > last_pos , $ point ) ;
2014-11-23 14:13:40 +00:00
}
2012-06-28 12:44:54 +00:00
}
1 ;