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-01-02 16:24:23 +00:00
use Slic3r::Flow ':roles' ;
2014-10-25 09:15:12 +00:00
use Slic3r::Geometry qw( epsilon scale unscale PI X Y B ) ;
2014-04-29 17:58:58 +00:00
use Slic3r::Geometry::Clipper qw( union_ex offset_ex ) ;
2013-06-20 18:11:46 +00:00
use Slic3r::Surface ':types' ;
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-10-18 15:58:14 +00:00
has 'ooze_prevention' = > ( is = > 'rw' ) ;
2013-07-28 11:39:15 +00:00
has 'enable_loop_clipping' = > ( is = > 'rw' , default = > sub { 1 } ) ;
2014-01-04 23:36:33 +00:00
has 'enable_wipe' = > ( is = > 'rw' , default = > sub { 0 } ) ; # at least one extruder has wipe enabled
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' ) ;
2014-01-11 16:40:09 +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' ) ;
2013-08-09 12:55:36 +00:00
has '_layer_islands' = > ( is = > 'rw' ) ;
has '_upper_layer_islands' = > ( is = > 'rw' ) ;
2014-05-24 20:10:28 +00:00
has '_seam_position' = > ( is = > 'ro' , default = > sub { { } } ) ; # $object => pos
2014-10-27 09:34:51 +00:00
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
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-25 09:00:08 +00:00
has '_wipe_path' = > ( is = > 'rw' ) ;
2012-06-28 12:44:54 +00:00
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-10-25 08:42:07 +00:00
$ self - > enable_wipe ( 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-10-25 09:00:08 +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-10-27 09:34:51 +00:00
sub init_external_mp {
my ( $ self , $ islands ) = @ _ ;
2014-11-07 13:25:07 +00:00
$ self - > _external_mp ( Slic3r::MotionPlanner - > new ( $ islands ) ) ;
2014-10-27 09:34:51 +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 ) ;
2014-01-11 16:40:09 +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
2013-11-11 19:37:06 +00:00
$ self - > _layer_islands ( $ layer - > islands ) ;
$ self - > _upper_layer_islands ( $ layer - > upper_layer ? $ layer - > upper_layer - > islands : [] ) ;
2014-05-13 06:34:21 +00:00
if ( $ self - > config - > avoid_crossing_perimeters ) {
2014-10-27 09:34:51 +00:00
$ self - > _layer_mp ( Slic3r::MotionPlanner - > new (
2014-05-13 18:06:01 +00:00
union_ex ( [ map @$ _ , @ { $ layer - > slices } ] , 1 ) ,
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 ) {
2014-06-05 14:24:47 +00:00
# TODO: cap this to 99% and add an explicit M73 P100 in the end G-code
2014-11-06 20:06:09 +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
}
2014-10-28 20:47:09 +00:00
$ gcode . = $ self - > writer - > travel_to_z ( $ z , 'move to next layer (' . $ self - > layer - > id . ')' ) ;
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 ;
if ( defined $ self - > _seam_position - > { $ self - > layer - > object } ) {
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
}
2014-05-18 15:02:18 +00:00
$ speed // = - 1 ;
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-05 00:16:26 +00:00
$ self - > _wipe_path ( $ paths [ 0 ] - > polyline - > clone ) if $ self - > enable_wipe ; # 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)
my @ points = $ was_clockwise ? ( - 2 , 1 ) : ( 1 , - 2 ) ;
2014-05-08 09:07:37 +00:00
my $ angle = Slic3r::Geometry:: angle3points ( @$ last_path_polyline [ 0 , @ points ] ) / 3 ;
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-05-08 09:07:37 +00:00
my $ first_segment = Slic3r::Line - > new ( @$ last_path_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-05-08 09:07:37 +00:00
$ point - > rotate ( $ angle , $ last_path_polyline - > first_point ) ;
2012-12-05 10:31:35 +00:00
# generate the travel move
2014-05-08 09:07:37 +00:00
$ gcode . = $ self - > travel_to ( $ point , $ paths [ - 1 ] - > role , "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 ;
} elsif ( $ self - > config - > infill_acceleration && $ path - > is_fill ) {
$ acceleration = $ self - > config - > infill_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-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-05-13 06:34:21 +00:00
my $ F ;
2014-05-18 15:02:18 +00:00
if ( $ path - > role == EXTR_ROLE_PERIMETER ) {
2014-05-13 06:34:21 +00:00
$ 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 {
2014-05-15 13:54:16 +00:00
$ F = $ speed // - 1 ;
2014-05-13 06:34:21 +00:00
die "Invalid speed" if $ F < 0 ; # $speed == -1
}
$ F *= 60 ; # convert mm/sec to mm/min
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
2013-08-28 23:36:42 +00:00
if ( $ self - > enable_wipe ) {
2014-10-25 09:00:08 +00:00
$ 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 ;
}
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
2012-08-23 19:10:04 +00:00
my $ gcode = "" ;
2013-03-05 16:30:27 +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.
my $ travel = Slic3r::Line - > new ( $ self - > last_pos , $ point ) ;
2013-03-05 16:30:27 +00:00
2014-10-27 09:34:51 +00: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 20:47:09 +00:00
if ( $ travel - > length < scale $ self - > config - > get_at ( 'retract_before_travel' , $ self - > writer - > extruder - > id )
2014-05-13 06:34:21 +00:00
|| ( $ self - > config - > only_retract_when_crossing_perimeters
2014-07-22 22:57:31 +00:00
&& $ self - > config - > fill_density > 0
2013-11-21 13:35:28 +00: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 16:30:27 +00:00
) {
2014-10-27 09:34:51 +00:00
# Just perform a straight travel move without any retraction.
2014-10-28 20:47:09 +00:00
$ gcode . = $ self - > writer - > travel_to_xy ( $ self - > point_to_gcode ( $ point ) , $ comment ) ;
2014-10-27 09:34:51 +00:00
} 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.
2012-08-23 19:10:04 +00:00
if ( $ self - > new_object ) {
2014-10-27 09:34:51 +00:00
# If we're moving to a new object we need to use the external configuration space.
2013-01-27 11:48:16 +00:00
$ self - > new_object ( 0 ) ;
# represent $point in G-code coordinates
$ point = $ point - > clone ;
2014-10-25 08:56:21 +00:00
my $ origin = $ self - > origin ;
$ point - > translate ( map scale $ _ , @$ origin ) ;
2013-01-27 11:48:16 +00:00
2014-10-27 09:34:51 +00:00
# calculate path (external_mp uses G-code coordinates so we set a temporary null origin)
2014-10-25 08:56:21 +00:00
$ self - > set_origin ( Slic3r::Pointf - > new ( 0 , 0 ) ) ;
2014-10-27 09:34:51 +00:00
$ gcode . = $ self - > _plan ( $ self - > _external_mp , $ point , $ comment ) ;
2014-10-25 08:56:21 +00:00
$ self - > set_origin ( $ origin ) ;
2012-08-23 19:10:04 +00:00
} else {
2014-10-27 09:34:51 +00:00
$ gcode . = $ self - > _plan ( $ self - > _layer_mp , $ point , $ comment ) ;
2012-08-23 19:10:04 +00:00
}
2014-10-27 09:34:51 +00: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 18:02:45 +00:00
$ gcode . = $ self - > writer - > travel_to_xy ( $ self - > point_to_gcode ( $ point ) , $ comment || '' ) ;
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
$ self - > straight_once ( 0 ) ;
2012-08-23 19:10:04 +00:00
return $ gcode ;
2012-08-23 13:42:58 +00:00
}
2013-05-18 14:57:44 +00:00
sub _plan {
2013-08-26 22:02:24 +00:00
my ( $ self , $ mp , $ point , $ comment ) = @ _ ;
2013-05-18 14:57:44 +00:00
my $ gcode = "" ;
2013-09-06 20:52:56 +00:00
my @ travel = @ { $ mp - > shortest_path ( $ self - > last_pos , $ point ) - > lines } ;
2013-05-18 14:57:44 +00:00
# if the path is not contained in a single island we need to retract
2014-05-13 06:34:21 +00:00
my $ need_retract = ! $ self - > config - > only_retract_when_crossing_perimeters ;
2013-05-18 14:57:44 +00:00
if ( ! $ need_retract ) {
$ need_retract = 1 ;
2013-11-11 19:37:06 +00:00
foreach my $ island ( @ { $ self - > _upper_layer_islands } ) {
2013-05-18 14:57:44 +00:00
# discard the island if at any line is not enclosed in it
2013-11-21 13:35:28 +00:00
next if first { ! $ island - > contains_line ( $ _ ) } @ travel ;
2013-05-18 14:57:44 +00:00
# okay, this island encloses the full travel path
$ need_retract = 0 ;
last ;
}
}
2014-10-21 18:16:45 +00:00
# perform the retraction
2013-10-13 14:20:01 +00:00
$ gcode . = $ self - > retract if $ need_retract ;
2013-05-18 14:57:44 +00:00
# append the actual path and return
2013-06-05 15:12:34 +00:00
# use G1 because we rely on paths being straight (G0 may make round paths)
2014-10-21 18:16:45 +00:00
$ gcode . = join '' ,
2014-10-28 20:47:09 +00:00
map $ self - > writer - > travel_to_xy ( $ self - > point_to_gcode ( $ _ - > b ) , $ comment ) ,
2014-10-21 18:16:45 +00:00
@ travel ;
2013-05-18 14:57:44 +00:00
return $ gcode ;
}
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-10-28 20:47:09 +00:00
if ( $ self - > config - > get_at ( 'wipe' , $ self - > writer - > extruder - > id ) && $ self - > _wipe_path ) {
2014-10-21 18:16:45 +00:00
# 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.
2014-11-09 18:02:45 +00:00
my $ wipe_speed = $ self - > writer - > config - > get ( 'travel_speed' ) * 0.8 ;
2014-10-21 18:16:45 +00:00
# get the retraction length
my $ length = $ toolchange
2014-11-05 00:16:26 +00:00
? $ self - > writer - > extruder - > retract_length_toolchange
: $ self - > writer - > extruder - > retract_length ;
2014-10-21 18:16:45 +00:00
2014-11-05 00:16:26 +00:00
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 ) ;
2014-10-21 18:16:45 +00:00
2014-11-05 00:16:26 +00:00
# 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 } ] ,
2014-10-21 18:16:45 +00:00
) ;
2014-11-05 00:16:26 +00:00
#
$ 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 ) ;
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
if $ self - > writer - > extruder - > retract_length > 0 ;
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-10-28 20:47:09 +00:00
if $ self - > ooze_prevention && 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 )
if $ self - > ooze_prevention ;
2012-08-30 21:13:28 +00:00
return $ gcode ;
2012-06-28 12:44:54 +00:00
}
1 ;