GCode.pm 27 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700
  1. package Slic3r::GCode;
  2. use Moo;
  3. use List::Util qw(min max first);
  4. use Slic3r::ExtrusionPath ':roles';
  5. use Slic3r::Geometry qw(scale unscale scaled_epsilon points_coincide PI X Y B);
  6. use Slic3r::Geometry::Clipper qw(union_ex);
  7. has 'multiple_extruders' => (is => 'ro', default => sub {0} );
  8. has 'layer_count' => (is => 'ro', required => 1 );
  9. has 'layer' => (is => 'rw');
  10. has 'move_z_callback' => (is => 'rw');
  11. has 'shift_x' => (is => 'rw', default => sub {0} );
  12. has 'shift_y' => (is => 'rw', default => sub {0} );
  13. has 'z' => (is => 'rw');
  14. has 'speed' => (is => 'rw');
  15. has 'external_mp' => (is => 'rw');
  16. has 'layer_mp' => (is => 'rw');
  17. has 'new_object' => (is => 'rw', default => sub {0});
  18. has 'straight_once' => (is => 'rw', default => sub {1});
  19. has 'extruder' => (is => 'rw');
  20. has 'extrusion_distance' => (is => 'rw', default => sub {0} );
  21. has 'elapsed_time' => (is => 'rw', default => sub {0} ); # seconds
  22. has 'total_extrusion_length' => (is => 'rw', default => sub {0} );
  23. has 'lifted' => (is => 'rw', default => sub {0} );
  24. has 'last_pos' => (is => 'rw', default => sub { Slic3r::Point->new(0,0) } );
  25. has 'last_speed' => (is => 'rw', default => sub {""});
  26. has 'last_f' => (is => 'rw', default => sub {""});
  27. has 'last_fan_speed' => (is => 'rw', default => sub {0});
  28. has 'wipe_path' => (is => 'rw');
  29. has 'dec' => (is => 'ro', default => sub { 3 } );
  30. # used for vibration limit:
  31. has 'last_dir' => (is => 'ro', default => sub { [0,0] });
  32. has 'dir_time' => (is => 'ro', default => sub { [0,0] });
  33. # calculate speeds (mm/min)
  34. has 'speeds' => (
  35. is => 'ro',
  36. default => sub {+{
  37. map { $_ => 60 * $Slic3r::Config->get_value("${_}_speed") }
  38. qw(travel perimeter small_perimeter external_perimeter infill
  39. solid_infill top_solid_infill support_material bridge gap_fill retract),
  40. }},
  41. );
  42. # assign speeds to roles
  43. my %role_speeds = (
  44. &EXTR_ROLE_PERIMETER => 'perimeter',
  45. &EXTR_ROLE_EXTERNAL_PERIMETER => 'external_perimeter',
  46. &EXTR_ROLE_OVERHANG_PERIMETER => 'external_perimeter',
  47. &EXTR_ROLE_CONTOUR_INTERNAL_PERIMETER => 'perimeter',
  48. &EXTR_ROLE_FILL => 'infill',
  49. &EXTR_ROLE_SOLIDFILL => 'solid_infill',
  50. &EXTR_ROLE_TOPSOLIDFILL => 'top_solid_infill',
  51. &EXTR_ROLE_BRIDGE => 'bridge',
  52. &EXTR_ROLE_INTERNALBRIDGE => 'solid_infill',
  53. &EXTR_ROLE_SKIRT => 'perimeter',
  54. &EXTR_ROLE_SUPPORTMATERIAL => 'support_material',
  55. &EXTR_ROLE_GAPFILL => 'gap_fill',
  56. );
  57. sub set_shift {
  58. my $self = shift;
  59. my @shift = @_;
  60. # if shift increases (goes towards right), last_pos decreases because it goes towards left
  61. my @translate = (
  62. scale ($self->shift_x - $shift[X]),
  63. scale ($self->shift_y - $shift[Y]),
  64. );
  65. $self->last_pos->translate(@translate);
  66. $self->wipe_path->translate(@translate) if $self->wipe_path;
  67. $self->shift_x($shift[X]);
  68. $self->shift_y($shift[Y]);
  69. }
  70. sub change_layer {
  71. my $self = shift;
  72. my ($layer) = @_;
  73. $self->layer($layer);
  74. if ($Slic3r::Config->avoid_crossing_perimeters) {
  75. $self->layer_mp(Slic3r::GCode::MotionPlanner->new(
  76. islands => union_ex([ map @$_, @{$layer->slices} ], undef, 1),
  77. ));
  78. }
  79. my $gcode = "";
  80. if ($Slic3r::Config->gcode_flavor =~ /^(?:makerbot|sailfish)$/) {
  81. $gcode .= sprintf "M73 P%s%s\n",
  82. int(99 * ($layer->id / ($self->layer_count - 1))),
  83. ($Slic3r::Config->gcode_comments ? ' ; update progress' : '');
  84. }
  85. return $gcode;
  86. }
  87. # this method accepts Z in scaled coordinates
  88. sub move_z {
  89. my $self = shift;
  90. my ($z, $comment) = @_;
  91. $z *= &Slic3r::SCALING_FACTOR;
  92. $z += $Slic3r::Config->z_offset;
  93. my $gcode = "";
  94. my $current_z = $self->z;
  95. if (!defined $current_z || $current_z != ($z + $self->lifted)) {
  96. $gcode .= $self->retract(move_z => $z) if $self->extruder->retract_layer_change;
  97. $self->speed('travel');
  98. $gcode .= $self->G0(undef, $z, 0, $comment || ('move to next layer (' . $self->layer->id . ')'))
  99. unless ($current_z // -1) != ($self->z // -1);
  100. $gcode .= $self->move_z_callback->() if defined $self->move_z_callback;
  101. }
  102. return $gcode;
  103. }
  104. sub extrude {
  105. my $self = shift;
  106. ($_[0]->isa('Slic3r::ExtrusionLoop') || $_[0]->isa('Slic3r::ExtrusionLoop::Packed'))
  107. ? $self->extrude_loop(@_)
  108. : $self->extrude_path(@_);
  109. }
  110. sub extrude_loop {
  111. my $self = shift;
  112. my ($loop, $description) = @_;
  113. # extrude all loops ccw
  114. $loop = $loop->unpack if $loop->isa('Slic3r::ExtrusionLoop::Packed');
  115. my $was_clockwise = $loop->polygon->make_counter_clockwise;
  116. # find the point of the loop that is closest to the current extruder position
  117. # or randomize if requested
  118. my $last_pos = $self->last_pos;
  119. if ($Slic3r::Config->randomize_start && $loop->role == EXTR_ROLE_CONTOUR_INTERNAL_PERIMETER) {
  120. $last_pos = Slic3r::Point->new(scale $Slic3r::Config->print_center->[X], scale $Slic3r::Config->bed_size->[Y]);
  121. $last_pos->rotate(rand(2*PI), $Slic3r::Config->print_center);
  122. }
  123. my $start_index = $loop->nearest_point_index_to($last_pos);
  124. # split the loop at the starting point and make a path
  125. my $extrusion_path = $loop->split_at_index($start_index);
  126. # clip the path to avoid the extruder to get exactly on the first point of the loop;
  127. # if polyline was shorter than the clipping distance we'd get a null polyline, so
  128. # we discard it in that case
  129. $extrusion_path->clip_end(scale $extrusion_path->flow_spacing * &Slic3r::LOOP_CLIPPING_LENGTH_OVER_SPACING);
  130. return '' if !@{$extrusion_path->polyline};
  131. # extrude along the path
  132. my $gcode = $self->extrude_path($extrusion_path, $description);
  133. $self->wipe_path($extrusion_path->polyline);
  134. # make a little move inwards before leaving loop
  135. if ($loop->role == EXTR_ROLE_EXTERNAL_PERIMETER && $Slic3r::Config->perimeters > 1) {
  136. # detect angle between last and first segment
  137. # the side depends on the original winding order of the polygon (left for contours, right for holes)
  138. my @points = $was_clockwise ? (-2, 1) : (1, -2);
  139. my $angle = Slic3r::Geometry::angle3points(@{$extrusion_path->polyline}[0, @points]) / 3;
  140. $angle *= -1 if $was_clockwise;
  141. # create the destination point along the first segment and rotate it
  142. # we make sure we don't exceed the segment length because we don't know
  143. # the rotation of the second segment so we might cross the object boundary
  144. my $first_segment = Slic3r::Line->new(@{$extrusion_path->polyline}[0,1]);
  145. my $distance = min(scale $extrusion_path->flow_spacing, $first_segment->length);
  146. my $point = Slic3r::Geometry::point_along_segment(@$first_segment, $distance);
  147. bless $point, 'Slic3r::Point';
  148. $point->rotate($angle, $extrusion_path->polyline->[0]);
  149. # generate the travel move
  150. $gcode .= $self->travel_to($point, $loop->role, "move inwards before travel");
  151. }
  152. return $gcode;
  153. }
  154. sub extrude_path {
  155. my $self = shift;
  156. my ($path, $description, $recursive) = @_;
  157. $path = $path->unpack if $path->isa('Slic3r::ExtrusionPath::Packed');
  158. $path->simplify(&Slic3r::SCALED_RESOLUTION);
  159. # detect arcs
  160. if ($Slic3r::Config->gcode_arcs && !$recursive) {
  161. my $gcode = "";
  162. foreach my $arc_path ($path->detect_arcs) {
  163. $gcode .= $self->extrude_path($arc_path, $description, 1);
  164. }
  165. return $gcode;
  166. }
  167. # go to first point of extrusion path
  168. my $gcode = "";
  169. $gcode .= $self->travel_to($path->points->[0], $path->role, "move to first $description point");
  170. # compensate retraction
  171. $gcode .= $self->unretract;
  172. # adjust acceleration
  173. my $acceleration;
  174. if ($Slic3r::Config->perimeter_acceleration && $path->is_perimeter) {
  175. $acceleration = $Slic3r::Config->perimeter_acceleration;
  176. } elsif ($Slic3r::Config->infill_acceleration && $path->is_fill) {
  177. $acceleration = $Slic3r::Config->infill_acceleration;
  178. } elsif ($Slic3r::Config->infill_acceleration && $path->is_bridge) {
  179. $acceleration = $Slic3r::Config->bridge_acceleration;
  180. }
  181. $gcode .= $self->set_acceleration($acceleration) if $acceleration;
  182. my $area; # mm^3 of extrudate per mm of tool movement
  183. if ($path->is_bridge) {
  184. my $s = $path->flow_spacing;
  185. $area = ($s**2) * PI/4;
  186. } else {
  187. my $s = $path->flow_spacing;
  188. my $h = $path->height // $self->layer->height;
  189. $area = $self->extruder->mm3_per_mm($s, $h);
  190. }
  191. # calculate extrusion length per distance unit
  192. my $e = $self->extruder->e_per_mm3 * $area;
  193. # set speed
  194. $self->speed( $role_speeds{$path->role} || die "Unknown role: " . $path->role );
  195. if ($path->role == EXTR_ROLE_PERIMETER || $path->role == EXTR_ROLE_EXTERNAL_PERIMETER || $path->role == EXTR_ROLE_CONTOUR_INTERNAL_PERIMETER) {
  196. if (abs($path->length) <= &Slic3r::SMALL_PERIMETER_LENGTH) {
  197. $self->speed('small_perimeter');
  198. }
  199. }
  200. # extrude arc or line
  201. my $path_length = 0;
  202. if ($path->isa('Slic3r::ExtrusionPath::Arc')) {
  203. $path_length = unscale $path->length;
  204. $gcode .= $self->G2_G3($path->points->[-1], $path->orientation,
  205. $path->center, $e * unscale $path_length, $description);
  206. $self->wipe_path(undef);
  207. } else {
  208. foreach my $line ($path->lines) {
  209. my $line_length = unscale $line->length;
  210. $path_length += $line_length;
  211. $gcode .= $self->G1($line->[B], undef, $e * $line_length, $description);
  212. }
  213. $self->wipe_path(Slic3r::Polyline->new([ reverse @{$path->points} ]))
  214. if $self->extruder->wipe;
  215. }
  216. if ($Slic3r::Config->cooling) {
  217. my $path_time = $path_length / $self->speeds->{$self->last_speed} * 60;
  218. if ($self->layer->id == 0) {
  219. $path_time = $Slic3r::Config->first_layer_speed =~ /^(\d+(?:\.\d+)?)%$/
  220. ? $path_time / ($1/100)
  221. : $path_length / $Slic3r::Config->first_layer_speed * 60;
  222. }
  223. $self->elapsed_time($self->elapsed_time + $path_time);
  224. }
  225. # reset acceleration
  226. $gcode .= $self->set_acceleration($Slic3r::Config->default_acceleration)
  227. if $acceleration && $Slic3r::Config->default_acceleration;
  228. return $gcode;
  229. }
  230. sub travel_to {
  231. my $self = shift;
  232. my ($point, $role, $comment) = @_;
  233. my $gcode = "";
  234. my $travel = Slic3r::Line->new($self->last_pos->clone, $point->clone);
  235. # move travel back to original layer coordinates for the island check.
  236. # note that we're only considering the current object's islands, while we should
  237. # build a more complete configuration space
  238. $travel->translate(-$self->shift_x, -$self->shift_y);
  239. if ($travel->length < scale $self->extruder->retract_before_travel
  240. || ($Slic3r::Config->only_retract_when_crossing_perimeters && first { $_->encloses_line($travel, scaled_epsilon) } @{$self->layer->slices})
  241. || ($role == EXTR_ROLE_SUPPORTMATERIAL && $self->layer->support_islands_enclose_line($travel))
  242. ) {
  243. $self->straight_once(0);
  244. $self->speed('travel');
  245. $gcode .= $self->G0($point, undef, 0, $comment || "");
  246. } elsif (!$Slic3r::Config->avoid_crossing_perimeters || $self->straight_once) {
  247. $self->straight_once(0);
  248. $gcode .= $self->retract(travel_to => $point);
  249. $self->speed('travel');
  250. $gcode .= $self->G0($point, undef, 0, $comment || "");
  251. } else {
  252. my $plan = sub {
  253. my $mp = shift;
  254. my $gcode = "";
  255. my @travel = $mp->shortest_path($self->last_pos, $point)->lines;
  256. # if the path is not contained in a single island we need to retract
  257. my $need_retract = !$Slic3r::Config->only_retract_when_crossing_perimeters;
  258. if (!$need_retract) {
  259. $need_retract = 1;
  260. foreach my $slice (@{$self->layer->slices}) {
  261. # discard the island if at any line is not enclosed in it
  262. next if first { !$slice->encloses_line($_, scaled_epsilon) } @travel;
  263. # okay, this island encloses the full travel path
  264. $need_retract = 0;
  265. last;
  266. }
  267. }
  268. # do the retract (the travel_to argument is broken)
  269. $gcode .= $self->retract(travel_to => $point) if $need_retract;
  270. # append the actual path and return
  271. $self->speed('travel');
  272. $gcode .= join '', map $self->G0($_->[B], undef, 0, $comment || ""), @travel;
  273. return $gcode;
  274. };
  275. if ($self->new_object) {
  276. $self->new_object(0);
  277. # represent $point in G-code coordinates
  278. $point = $point->clone;
  279. my @shift = ($self->shift_x, $self->shift_y);
  280. $point->translate(map scale $_, @shift);
  281. # calculate path (external_mp uses G-code coordinates so we temporary need a null shift)
  282. $self->set_shift(0,0);
  283. $gcode .= $plan->($self->external_mp);
  284. $self->set_shift(@shift);
  285. } else {
  286. $gcode .= $plan->($self->layer_mp);
  287. }
  288. }
  289. return $gcode;
  290. }
  291. sub retract {
  292. my $self = shift;
  293. my %params = @_;
  294. # get the retraction length and abort if none
  295. my ($length, $restart_extra, $comment) = $params{toolchange}
  296. ? ($self->extruder->retract_length_toolchange, $self->extruder->retract_restart_extra_toolchange, "retract for tool change")
  297. : ($self->extruder->retract_length, $self->extruder->retract_restart_extra, "retract");
  298. # if we already retracted, reduce the required amount of retraction
  299. $length -= $self->extruder->retracted;
  300. return "" unless $length > 0;
  301. my $gcode = "";
  302. # wipe
  303. my $wipe_path;
  304. if ($self->extruder->wipe && $self->wipe_path) {
  305. $wipe_path = Slic3r::Polyline->new([ $self->last_pos, @{$self->wipe_path}[1..$#{$self->wipe_path}] ])
  306. ->clip_start($self->extruder->scaled_wipe_distance);
  307. }
  308. # prepare moves
  309. my $retract = [undef, undef, -$length, $comment];
  310. my $lift = ($self->extruder->retract_lift == 0 || defined $params{move_z}) && !$self->lifted
  311. ? undef
  312. : [undef, $self->z + $self->extruder->retract_lift, 0, 'lift plate during travel'];
  313. if (($Slic3r::Config->g0 || $Slic3r::Config->gcode_flavor eq 'mach3') && $params{travel_to}) {
  314. $self->speed('travel');
  315. if ($lift) {
  316. # combine lift and retract
  317. $lift->[2] = $retract->[2];
  318. $gcode .= $self->G0(@$lift);
  319. } else {
  320. # combine travel and retract
  321. my $travel = [$params{travel_to}, undef, $retract->[2], "travel and $comment"];
  322. $gcode .= $self->G0(@$travel);
  323. }
  324. } elsif (($Slic3r::Config->g0 || $Slic3r::Config->gcode_flavor eq 'mach3') && defined $params{move_z}) {
  325. # combine Z change and retraction
  326. $self->speed('travel');
  327. my $travel = [undef, $params{move_z}, $retract->[2], "change layer and $comment"];
  328. $gcode .= $self->G0(@$travel);
  329. } else {
  330. # check that we have a positive wipe length
  331. if ($wipe_path && (my $total_wipe_length = $wipe_path->length)) {
  332. $self->speed('travel');
  333. # subdivide the retraction
  334. for (1 .. $#$wipe_path) {
  335. my $segment_length = $wipe_path->[$_-1]->distance_to($wipe_path->[$_]);
  336. $gcode .= $self->G1($wipe_path->[$_], undef, $retract->[2] * ($segment_length / $total_wipe_length), $retract->[3] . ";_WIPE");
  337. }
  338. } else {
  339. $self->speed('retract');
  340. $gcode .= $self->G1(@$retract);
  341. }
  342. if (!$self->lifted) {
  343. $self->speed('travel');
  344. if (defined $params{move_z} && $self->extruder->retract_lift > 0) {
  345. my $travel = [undef, $params{move_z} + $self->extruder->retract_lift, 0, 'move to next layer (' . $self->layer->id . ') and lift'];
  346. $gcode .= $self->G0(@$travel);
  347. $self->lifted($self->extruder->retract_lift);
  348. } elsif ($lift) {
  349. $gcode .= $self->G1(@$lift);
  350. }
  351. }
  352. }
  353. $self->extruder->retracted($self->extruder->retracted + $length);
  354. $self->extruder->restart_extra($restart_extra);
  355. $self->lifted($self->extruder->retract_lift) if $lift;
  356. # reset extrusion distance during retracts
  357. # this makes sure we leave sufficient precision in the firmware
  358. $gcode .= $self->reset_e if $Slic3r::Config->gcode_flavor !~ /^(?:mach3|makerbot)$/;
  359. return $gcode;
  360. }
  361. sub unretract {
  362. my $self = shift;
  363. my $gcode = "";
  364. if ($self->lifted) {
  365. $self->speed('travel');
  366. $gcode .= $self->G0(undef, $self->z - $self->lifted, 0, 'restore layer Z');
  367. $self->lifted(0);
  368. }
  369. my $to_unretract = $self->extruder->retracted + $self->extruder->restart_extra;
  370. if ($to_unretract) {
  371. $self->speed('retract');
  372. $gcode .= $self->G0(undef, undef, $to_unretract, "compensate retraction");
  373. $self->extruder->retracted(0);
  374. $self->extruder->restart_extra(0);
  375. }
  376. return $gcode;
  377. }
  378. sub reset_e {
  379. my $self = shift;
  380. $self->extrusion_distance(0);
  381. return sprintf "G92 %s0%s\n", $Slic3r::Config->extrusion_axis, ($Slic3r::Config->gcode_comments ? ' ; reset extrusion distance' : '')
  382. if $Slic3r::Config->extrusion_axis && !$Slic3r::Config->use_relative_e_distances;
  383. }
  384. sub set_acceleration {
  385. my $self = shift;
  386. my ($acceleration) = @_;
  387. return "" if !$acceleration;
  388. return sprintf "M204 S%s%s\n",
  389. $acceleration, ($Slic3r::Config->gcode_comments ? ' ; adjust acceleration' : '');
  390. }
  391. sub G0 {
  392. my $self = shift;
  393. return $self->G1(@_) if !($Slic3r::Config->g0 || $Slic3r::Config->gcode_flavor eq 'mach3');
  394. return $self->_G0_G1("G0", @_);
  395. }
  396. sub G1 {
  397. my $self = shift;
  398. return $self->_G0_G1("G1", @_);
  399. }
  400. sub _G0_G1 {
  401. my $self = shift;
  402. my ($gcode, $point, $z, $e, $comment) = @_;
  403. my $dec = $self->dec;
  404. if ($point) {
  405. $gcode .= sprintf " X%.${dec}f Y%.${dec}f",
  406. ($point->x * &Slic3r::SCALING_FACTOR) + $self->shift_x - $self->extruder->extruder_offset->[X],
  407. ($point->y * &Slic3r::SCALING_FACTOR) + $self->shift_y - $self->extruder->extruder_offset->[Y]; #**
  408. $gcode = $self->_limit_frequency($point) . $gcode;
  409. $self->last_pos($point->clone);
  410. }
  411. if (defined $z && (!defined $self->z || $z != $self->z)) {
  412. $self->z($z);
  413. $gcode .= sprintf " Z%.${dec}f", $z;
  414. }
  415. return $self->_Gx($gcode, $e, $comment);
  416. }
  417. sub G2_G3 {
  418. my $self = shift;
  419. my ($point, $orientation, $center, $e, $comment) = @_;
  420. my $dec = $self->dec;
  421. my $gcode = $orientation eq 'cw' ? "G2" : "G3";
  422. $gcode .= sprintf " X%.${dec}f Y%.${dec}f",
  423. ($point->x * &Slic3r::SCALING_FACTOR) + $self->shift_x - $self->extruder->extruder_offset->[X],
  424. ($point->y * &Slic3r::SCALING_FACTOR) + $self->shift_y - $self->extruder->extruder_offset->[Y]; #**
  425. # XY distance of the center from the start position
  426. $gcode .= sprintf " I%.${dec}f J%.${dec}f",
  427. ($center->[X] - $self->last_pos->[X]) * &Slic3r::SCALING_FACTOR,
  428. ($center->[Y] - $self->last_pos->[Y]) * &Slic3r::SCALING_FACTOR;
  429. $self->last_pos($point);
  430. return $self->_Gx($gcode, $e, $comment);
  431. }
  432. sub _Gx {
  433. my $self = shift;
  434. my ($gcode, $e, $comment) = @_;
  435. my $dec = $self->dec;
  436. # output speed if it's different from last one used
  437. # (goal: reduce gcode size)
  438. my $append_bridge_off = 0;
  439. my $F;
  440. if ($self->speed ne $self->last_speed) {
  441. if ($self->speed eq 'bridge') {
  442. $gcode = ";_BRIDGE_FAN_START\n$gcode";
  443. } elsif ($self->last_speed eq 'bridge') {
  444. $append_bridge_off = 1;
  445. }
  446. # apply the speed reduction for print moves on bottom layer
  447. $F = $self->speed eq 'retract'
  448. ? ($self->extruder->retract_speed_mm_min)
  449. : $self->speeds->{$self->speed} // $self->speed;
  450. if ($e && $self->layer && $self->layer->id == 0 && $comment !~ /retract/) {
  451. $F = $Slic3r::Config->first_layer_speed =~ /^(\d+(?:\.\d+)?)%$/
  452. ? ($F * $1/100)
  453. : $Slic3r::Config->first_layer_speed * 60;
  454. }
  455. $self->last_speed($self->speed);
  456. $self->last_f($F);
  457. }
  458. $gcode .= sprintf " F%.${dec}f", $F if defined $F;
  459. # output extrusion distance
  460. if ($e && $Slic3r::Config->extrusion_axis) {
  461. $self->extrusion_distance(0) if $Slic3r::Config->use_relative_e_distances;
  462. $self->extrusion_distance($self->extrusion_distance + $e);
  463. $self->total_extrusion_length($self->total_extrusion_length + $e);
  464. $gcode .= sprintf " %s%.5f", $Slic3r::Config->extrusion_axis, $self->extrusion_distance;
  465. }
  466. $gcode .= sprintf " ; %s", $comment if $comment && $Slic3r::Config->gcode_comments;
  467. if ($append_bridge_off) {
  468. $gcode .= "\n;_BRIDGE_FAN_END";
  469. }
  470. return "$gcode\n";
  471. }
  472. sub set_extruder {
  473. my $self = shift;
  474. my ($extruder) = @_;
  475. # return nothing if this extruder was already selected
  476. return "" if (defined $self->extruder) && ($self->extruder->id == $extruder->id);
  477. # if we are running a single-extruder setup, just set the extruder and return nothing
  478. if (!$self->multiple_extruders) {
  479. $self->extruder($extruder);
  480. return "";
  481. }
  482. # trigger retraction on the current extruder (if any)
  483. my $gcode = "";
  484. $gcode .= $self->retract(toolchange => 1) if defined $self->extruder;
  485. # append custom toolchange G-code
  486. if (defined $self->extruder && $Slic3r::Config->toolchange_gcode) {
  487. $gcode .= sprintf "%s\n", $Slic3r::Config->replace_options($Slic3r::Config->toolchange_gcode, {
  488. previous_extruder => $self->extruder->id,
  489. next_extruder => $extruder->id,
  490. });
  491. }
  492. # set the new extruder
  493. $self->extruder($extruder);
  494. my $toolchange_gcode = sprintf "%s%d%s\n",
  495. ($Slic3r::Config->gcode_flavor =~ /^(?:makerbot|sailfish)$/ ? 'M108 T' : 'T'),
  496. $extruder->id,
  497. ($Slic3r::Config->gcode_comments ? ' ; change extruder' : '');
  498. if ($Slic3r::Config->gcode_flavor =~ /^(?:makerbot|sailfish)$/) {
  499. $gcode .= $self->reset_e;
  500. $gcode .= $toolchange_gcode;
  501. } else {
  502. $gcode .= $toolchange_gcode;
  503. $gcode .= $self->reset_e;
  504. }
  505. return $gcode;
  506. }
  507. sub set_fan {
  508. my $self = shift;
  509. my ($speed, $dont_save) = @_;
  510. if ($self->last_fan_speed != $speed || $dont_save) {
  511. $self->last_fan_speed($speed) if !$dont_save;
  512. if ($speed == 0) {
  513. my $code = $Slic3r::Config->gcode_flavor eq 'teacup'
  514. ? 'M106 S0'
  515. : $Slic3r::Config->gcode_flavor =~ /^(?:makerbot|sailfish)$/
  516. ? 'M127'
  517. : 'M107';
  518. return sprintf "$code%s\n", ($Slic3r::Config->gcode_comments ? ' ; disable fan' : '');
  519. } else {
  520. if ($Slic3r::Config->gcode_flavor =~ /^(?:makerbot|sailfish)$/) {
  521. return sprintf "M126%s\n", ($Slic3r::Config->gcode_comments ? ' ; enable fan' : '');
  522. } else {
  523. return sprintf "M106 %s%d%s\n", ($Slic3r::Config->gcode_flavor eq 'mach3' ? 'P' : 'S'),
  524. (255 * $speed / 100), ($Slic3r::Config->gcode_comments ? ' ; enable fan' : '');
  525. }
  526. }
  527. }
  528. return "";
  529. }
  530. sub set_temperature {
  531. my $self = shift;
  532. my ($temperature, $wait, $tool) = @_;
  533. return "" if $wait && $Slic3r::Config->gcode_flavor =~ /^(?:makerbot|sailfish)$/;
  534. my ($code, $comment) = ($wait && $Slic3r::Config->gcode_flavor ne 'teacup')
  535. ? ('M109', 'wait for temperature to be reached')
  536. : ('M104', 'set temperature');
  537. my $gcode = sprintf "$code %s%d %s; $comment\n",
  538. ($Slic3r::Config->gcode_flavor eq 'mach3' ? 'P' : 'S'), $temperature,
  539. (defined $tool && ($self->multiple_extruders || $Slic3r::Config->gcode_flavor =~ /^(?:makerbot|sailfish)$/)) ? "T$tool " : "";
  540. $gcode .= "M116 ; wait for temperature to be reached\n"
  541. if $Slic3r::Config->gcode_flavor eq 'teacup' && $wait;
  542. return $gcode;
  543. }
  544. sub set_bed_temperature {
  545. my $self = shift;
  546. my ($temperature, $wait) = @_;
  547. my ($code, $comment) = ($wait && $Slic3r::Config->gcode_flavor ne 'teacup')
  548. ? (($Slic3r::Config->gcode_flavor =~ /^(?:makerbot|sailfish)$/ ? 'M109' : 'M190'), 'wait for bed temperature to be reached')
  549. : ('M140', 'set bed temperature');
  550. my $gcode = sprintf "$code %s%d ; $comment\n",
  551. ($Slic3r::Config->gcode_flavor eq 'mach3' ? 'P' : 'S'), $temperature;
  552. $gcode .= "M116 ; wait for bed temperature to be reached\n"
  553. if $Slic3r::Config->gcode_flavor eq 'teacup' && $wait;
  554. return $gcode;
  555. }
  556. # http://hydraraptor.blogspot.it/2010/12/frequency-limit.html
  557. sub _limit_frequency {
  558. my $self = shift;
  559. my ($point) = @_;
  560. return '' if $Slic3r::Config->vibration_limit == 0;
  561. my $min_time = 1 / ($Slic3r::Config->vibration_limit * 60); # in minutes
  562. # calculate the move vector and move direction
  563. my $vector = Slic3r::Line->new($self->last_pos, $point)->vector;
  564. my @dir = map { $vector->[B][$_] <=> 0 } X,Y;
  565. my $time = (unscale $vector->length) / $self->speeds->{$self->speed}; # in minutes
  566. if ($time > 0) {
  567. my @pause = ();
  568. foreach my $axis (X,Y) {
  569. if ($dir[$axis] != 0 && $self->last_dir->[$axis] != $dir[$axis]) {
  570. if ($self->last_dir->[$axis] != 0) {
  571. # this axis is changing direction: check whether we need to pause
  572. if ($self->dir_time->[$axis] < $min_time) {
  573. push @pause, ($min_time - $self->dir_time->[$axis]);
  574. }
  575. }
  576. $self->last_dir->[$axis] = $dir[$axis];
  577. $self->dir_time->[$axis] = 0;
  578. }
  579. $self->dir_time->[$axis] += $time;
  580. }
  581. if (@pause) {
  582. return sprintf "G4 P%d\n", max(@pause) * 60 * 1000;
  583. }
  584. }
  585. return '';
  586. }
  587. 1;