GCode.pm 27 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681
  1. package Slic3r::GCode;
  2. use Moo;
  3. use List::Util qw(min max first);
  4. use Slic3r::ExtrusionLoop ':roles';
  5. use Slic3r::ExtrusionPath ':roles';
  6. use Slic3r::Flow ':roles';
  7. use Slic3r::Geometry qw(epsilon scale unscale scaled_epsilon points_coincide PI X Y B);
  8. use Slic3r::Geometry::Clipper qw(union_ex offset_ex);
  9. use Slic3r::Surface ':types';
  10. has 'config' => (is => 'ro', default => sub { Slic3r::Config::Full->new });
  11. has 'placeholder_parser' => (is => 'rw', default => sub { Slic3r::GCode::PlaceholderParser->new });
  12. has 'standby_points' => (is => 'rw');
  13. has 'enable_loop_clipping' => (is => 'rw', default => sub {1});
  14. has 'enable_wipe' => (is => 'rw', default => sub {0}); # at least one extruder has wipe enabled
  15. has 'layer_count' => (is => 'ro', required => 1 );
  16. has '_layer_index' => (is => 'rw', default => sub {-1}); # just a counter
  17. has 'layer' => (is => 'rw');
  18. has '_layer_islands' => (is => 'rw');
  19. has '_upper_layer_islands' => (is => 'rw');
  20. has '_seal_position' => (is => 'ro', default => sub { {} }); # $object => pos
  21. has 'shift_x' => (is => 'rw', default => sub {0} );
  22. has 'shift_y' => (is => 'rw', default => sub {0} );
  23. has 'z' => (is => 'rw');
  24. has 'extruders' => (is => 'ro', default => sub {{}});
  25. has 'multiple_extruders' => (is => 'rw', default => sub {0});
  26. has 'extruder' => (is => 'rw');
  27. has 'external_mp' => (is => 'rw');
  28. has 'layer_mp' => (is => 'rw');
  29. has 'new_object' => (is => 'rw', default => sub {0});
  30. has 'straight_once' => (is => 'rw', default => sub {1});
  31. has 'elapsed_time' => (is => 'rw', default => sub {0} ); # seconds
  32. has 'lifted' => (is => 'rw', default => sub {0} );
  33. has 'last_pos' => (is => 'rw', default => sub { Slic3r::Point->new(0,0) } );
  34. has 'last_fan_speed' => (is => 'rw', default => sub {0});
  35. has 'wipe_path' => (is => 'rw');
  36. sub set_extruders {
  37. my ($self, $extruder_ids, $print_config) = @_;
  38. foreach my $i (@$extruder_ids) {
  39. $self->extruders->{$i} = my $e = Slic3r::Extruder->new($i, $print_config);
  40. $self->enable_wipe(1) if $e->wipe;
  41. }
  42. # we enable support for multiple extruder if any extruder greater than 0 is used
  43. # (even if prints only uses that one) since we need to output Tx commands
  44. # first extruder has index 0
  45. $self->multiple_extruders(max(@$extruder_ids) > 0);
  46. }
  47. sub set_shift {
  48. my ($self, @shift) = @_;
  49. # if shift increases (goes towards right), last_pos decreases because it goes towards left
  50. my @translate = (
  51. scale ($self->shift_x - $shift[X]),
  52. scale ($self->shift_y - $shift[Y]),
  53. );
  54. $self->last_pos->translate(@translate);
  55. $self->wipe_path->translate(@translate) if $self->wipe_path;
  56. $self->shift_x($shift[X]);
  57. $self->shift_y($shift[Y]);
  58. }
  59. sub change_layer {
  60. my ($self, $layer) = @_;
  61. $self->layer($layer);
  62. $self->_layer_index($self->_layer_index + 1);
  63. # avoid computing islands and overhangs if they're not needed
  64. $self->_layer_islands($layer->islands);
  65. $self->_upper_layer_islands($layer->upper_layer ? $layer->upper_layer->islands : []);
  66. if ($self->config->avoid_crossing_perimeters) {
  67. $self->layer_mp(Slic3r::GCode::MotionPlanner->new(
  68. islands => union_ex([ map @$_, @{$layer->slices} ], 1),
  69. ));
  70. }
  71. my $gcode = "";
  72. if ($self->config->gcode_flavor =~ /^(?:makerware|sailfish)$/) {
  73. $gcode .= sprintf "M73 P%s%s\n",
  74. int(99 * ($self->_layer_index / ($self->layer_count - 1))),
  75. ($self->config->gcode_comments ? ' ; update progress' : '');
  76. }
  77. if ($self->config->first_layer_acceleration) {
  78. if ($layer->id == 0) {
  79. $gcode .= $self->set_acceleration($self->config->first_layer_acceleration);
  80. } elsif ($layer->id == 1) {
  81. $gcode .= $self->set_acceleration($self->config->default_acceleration);
  82. }
  83. }
  84. $gcode .= $self->move_z($layer->print_z);
  85. return $gcode;
  86. }
  87. # this method accepts Z in unscaled coordinates
  88. sub move_z {
  89. my ($self, $z, $comment) = @_;
  90. my $gcode = "";
  91. $z += $self->config->z_offset;
  92. my $current_z = $self->z;
  93. my $nominal_z = defined $current_z ? ($current_z - $self->lifted) : undef;
  94. if (!defined $current_z || $z > $current_z || $z < $nominal_z) {
  95. # we're moving above the current actual Z (so above the lift height of the current
  96. # layer if any) or below the current nominal layer
  97. # in both cases, we're going to the nominal Z of the next layer
  98. $self->lifted(0);
  99. if ($self->extruder->retract_layer_change) {
  100. # this retraction may alter $self->z
  101. $gcode .= $self->retract(move_z => $z);
  102. $current_z = $self->z; # update current z in case retract() changed it
  103. $nominal_z = defined $current_z ? ($current_z - $self->lifted) : undef;
  104. }
  105. $gcode .= $self->G0(undef, $z, 0, $self->config->travel_speed*60, $comment || ('move to next layer (' . $self->layer->id . ')'))
  106. if !defined $current_z || abs($z - $nominal_z) > epsilon;
  107. } elsif ($z < $current_z) {
  108. # we're moving above the current nominal layer height and below the current actual one.
  109. # we're basically advancing to next layer, whose nominal Z is still lower than the previous
  110. # layer Z with lift.
  111. $self->lifted($current_z - $z);
  112. }
  113. return $gcode;
  114. }
  115. sub extrude {
  116. my $self = shift;
  117. $_[0]->isa('Slic3r::ExtrusionLoop')
  118. ? $self->extrude_loop(@_)
  119. : $self->extrude_path(@_);
  120. }
  121. sub extrude_loop {
  122. my ($self, $loop, $description, $speed) = @_;
  123. # make a copy; don't modify the orientation of the original loop object otherwise
  124. # next copies (if any) would not detect the correct orientation
  125. $loop = $loop->clone;
  126. # extrude all loops ccw
  127. my $was_clockwise = $loop->make_counter_clockwise;
  128. # find the point of the loop that is closest to the current extruder position
  129. # or randomize if requested
  130. my $last_pos = $self->last_pos;
  131. if ($self->config->spiral_vase) {
  132. $loop->split_at($last_pos);
  133. } elsif ($self->config->seal_position eq 'nearest' || $self->config->seal_position eq 'aligned') {
  134. my $polygon = $loop->polygon;
  135. my @candidates = @{$polygon->concave_points(PI*4/3)};
  136. @candidates = @{$polygon->convex_points(PI*2/3)} if !@candidates;
  137. @candidates = @{$polygon} if !@candidates;
  138. my @non_overhang = grep !$loop->has_overhang_point($_), @candidates;
  139. @candidates = @non_overhang if @non_overhang;
  140. if ($self->config->seal_position eq 'nearest') {
  141. $loop->split_at_vertex($last_pos->nearest_point(\@candidates));
  142. } elsif ($self->config->seal_position eq 'aligned') {
  143. if (defined $self->layer && defined $self->_seal_position->{$self->layer->object}) {
  144. $last_pos = $self->_seal_position->{$self->layer->object};
  145. }
  146. my $point = $self->_seal_position->{$self->layer->object} = $last_pos->nearest_point(\@candidates);
  147. $loop->split_at_vertex($point);
  148. }
  149. } elsif ($self->config->seal_position eq 'random') {
  150. if ($loop->role == EXTRL_ROLE_CONTOUR_INTERNAL_PERIMETER) {
  151. my $polygon = $loop->polygon;
  152. my $centroid = $polygon->centroid;
  153. $last_pos = Slic3r::Point->new($polygon->bounding_box->x_max, $centroid->y); #))
  154. $last_pos->rotate(rand(2*PI), $centroid);
  155. }
  156. $loop->split_at($last_pos);
  157. }
  158. # clip the path to avoid the extruder to get exactly on the first point of the loop;
  159. # if polyline was shorter than the clipping distance we'd get a null polyline, so
  160. # we discard it in that case
  161. my $clip_length = $self->enable_loop_clipping
  162. ? scale($self->extruder->nozzle_diameter) * &Slic3r::LOOP_CLIPPING_LENGTH_OVER_NOZZLE_DIAMETER
  163. : 0;
  164. # get paths
  165. my @paths = @{$loop->clip_end($clip_length)};
  166. return '' if !@paths;
  167. # apply the small perimeter speed
  168. if ($paths[0]->is_perimeter && $loop->length <= &Slic3r::SMALL_PERIMETER_LENGTH) {
  169. $speed //= $self->config->get_abs_value('small_perimeter_speed');
  170. }
  171. $speed //= -1;
  172. # extrude along the path
  173. my $gcode = join '', map $self->extrude_path($_, $description, $speed), @paths;
  174. $self->wipe_path($paths[-1]->polyline->clone) if $self->enable_wipe; # TODO: don't limit wipe to last path
  175. # make a little move inwards before leaving loop
  176. if ($paths[-1]->role == EXTR_ROLE_EXTERNAL_PERIMETER && defined $self->layer && $self->config->perimeters > 1) {
  177. my $last_path_polyline = $paths[-1]->polyline;
  178. # detect angle between last and first segment
  179. # the side depends on the original winding order of the polygon (left for contours, right for holes)
  180. my @points = $was_clockwise ? (-2, 1) : (1, -2);
  181. my $angle = Slic3r::Geometry::angle3points(@$last_path_polyline[0, @points]) / 3;
  182. $angle *= -1 if $was_clockwise;
  183. # create the destination point along the first segment and rotate it
  184. # we make sure we don't exceed the segment length because we don't know
  185. # the rotation of the second segment so we might cross the object boundary
  186. my $first_segment = Slic3r::Line->new(@$last_path_polyline[0,1]);
  187. my $distance = min(scale($self->extruder->nozzle_diameter), $first_segment->length);
  188. my $point = $first_segment->point_at($distance);
  189. $point->rotate($angle, $last_path_polyline->first_point);
  190. # generate the travel move
  191. $gcode .= $self->travel_to($point, $paths[-1]->role, "move inwards before travel");
  192. }
  193. return $gcode;
  194. }
  195. sub extrude_path {
  196. my ($self, $path, $description, $speed) = @_;
  197. $path->simplify(&Slic3r::SCALED_RESOLUTION);
  198. # go to first point of extrusion path
  199. my $gcode = "";
  200. {
  201. my $first_point = $path->first_point;
  202. $gcode .= $self->travel_to($first_point, $path->role, "move to first $description point")
  203. if !defined $self->last_pos || !$self->last_pos->coincides_with($first_point);
  204. }
  205. # compensate retraction
  206. $gcode .= $self->unretract;
  207. # adjust acceleration
  208. my $acceleration;
  209. if (!$self->config->first_layer_acceleration || $self->layer->id != 0) {
  210. if ($self->config->perimeter_acceleration && $path->is_perimeter) {
  211. $acceleration = $self->config->perimeter_acceleration;
  212. } elsif ($self->config->infill_acceleration && $path->is_fill) {
  213. $acceleration = $self->config->infill_acceleration;
  214. } elsif ($self->config->infill_acceleration && $path->is_bridge) {
  215. $acceleration = $self->config->bridge_acceleration;
  216. }
  217. $gcode .= $self->set_acceleration($acceleration) if $acceleration;
  218. }
  219. # calculate extrusion length per distance unit
  220. my $e = $self->extruder->e_per_mm3 * $path->mm3_per_mm;
  221. $e = 0 if !$self->config->get_extrusion_axis;
  222. # set speed
  223. my $F;
  224. if ($path->role == EXTR_ROLE_PERIMETER) {
  225. $F = $self->config->get_abs_value('perimeter_speed');
  226. } elsif ($path->role == EXTR_ROLE_EXTERNAL_PERIMETER) {
  227. $F = $self->config->get_abs_value('external_perimeter_speed');
  228. } elsif ($path->role == EXTR_ROLE_OVERHANG_PERIMETER || $path->role == EXTR_ROLE_BRIDGE) {
  229. $F = $self->config->get_abs_value('bridge_speed');
  230. } elsif ($path->role == EXTR_ROLE_FILL) {
  231. $F = $self->config->get_abs_value('infill_speed');
  232. } elsif ($path->role == EXTR_ROLE_SOLIDFILL) {
  233. $F = $self->config->get_abs_value('solid_infill_speed');
  234. } elsif ($path->role == EXTR_ROLE_TOPSOLIDFILL) {
  235. $F = $self->config->get_abs_value('top_solid_infill_speed');
  236. } elsif ($path->role == EXTR_ROLE_GAPFILL) {
  237. $F = $self->config->get_abs_value('gap_fill_speed');
  238. } else {
  239. $F = $speed // -1;
  240. die "Invalid speed" if $F < 0; # $speed == -1
  241. }
  242. $F *= 60; # convert mm/sec to mm/min
  243. if ($self->layer->id == 0) {
  244. $F = $self->config->get_abs_value_over('first_layer_speed', $F/60) * 60;
  245. }
  246. # extrude arc or line
  247. $gcode .= ";_BRIDGE_FAN_START\n" if $path->is_bridge;
  248. my $path_length = unscale $path->length;
  249. {
  250. $gcode .= $path->gcode($self->extruder, $e, $F,
  251. $self->shift_x - $self->extruder->extruder_offset->x,
  252. $self->shift_y - $self->extruder->extruder_offset->y, #,,
  253. $self->config->get_extrusion_axis,
  254. $self->config->gcode_comments ? " ; $description" : "");
  255. if ($self->enable_wipe) {
  256. $self->wipe_path($path->polyline->clone);
  257. $self->wipe_path->reverse;
  258. }
  259. }
  260. $gcode .= ";_BRIDGE_FAN_END\n" if $path->is_bridge;
  261. $self->last_pos($path->last_point);
  262. if ($self->config->cooling) {
  263. my $path_time = $path_length / $F * 60;
  264. $self->elapsed_time($self->elapsed_time + $path_time);
  265. }
  266. # reset acceleration
  267. $gcode .= $self->set_acceleration($self->config->default_acceleration)
  268. if $acceleration && $self->config->default_acceleration;
  269. return $gcode;
  270. }
  271. sub travel_to {
  272. my ($self, $point, $role, $comment) = @_;
  273. my $gcode = "";
  274. my $travel = Slic3r::Line->new($self->last_pos, $point);
  275. # move travel back to original layer coordinates for the island check.
  276. # note that we're only considering the current object's islands, while we should
  277. # build a more complete configuration space
  278. $travel->translate(-$self->shift_x, -$self->shift_y);
  279. # skip retraction if the travel move is contained in an island in the current layer
  280. # *and* in an island in the upper layer (so that the ooze will not be visible)
  281. if ($travel->length < scale $self->extruder->retract_before_travel
  282. || ($self->config->only_retract_when_crossing_perimeters
  283. && (first { $_->contains_line($travel) } @{$self->_upper_layer_islands})
  284. && (first { $_->contains_line($travel) } @{$self->_layer_islands}))
  285. || (defined $role && $role == EXTR_ROLE_SUPPORTMATERIAL && (first { $_->contains_line($travel) } @{$self->layer->support_islands}))
  286. ) {
  287. $self->straight_once(0);
  288. $gcode .= $self->G0($point, undef, 0, $self->config->travel_speed*60, $comment || "");
  289. } elsif (!$self->config->avoid_crossing_perimeters || $self->straight_once) {
  290. $self->straight_once(0);
  291. $gcode .= $self->retract;
  292. $gcode .= $self->G0($point, undef, 0, $self->config->travel_speed*60, $comment || "");
  293. } else {
  294. if ($self->new_object) {
  295. $self->new_object(0);
  296. # represent $point in G-code coordinates
  297. $point = $point->clone;
  298. my @shift = ($self->shift_x, $self->shift_y);
  299. $point->translate(map scale $_, @shift);
  300. # calculate path (external_mp uses G-code coordinates so we temporary need a null shift)
  301. $self->set_shift(0,0);
  302. $gcode .= $self->_plan($self->external_mp, $point, $comment);
  303. $self->set_shift(@shift);
  304. } else {
  305. $gcode .= $self->_plan($self->layer_mp, $point, $comment);
  306. }
  307. }
  308. return $gcode;
  309. }
  310. sub _plan {
  311. my ($self, $mp, $point, $comment) = @_;
  312. my $gcode = "";
  313. my @travel = @{$mp->shortest_path($self->last_pos, $point)->lines};
  314. # if the path is not contained in a single island we need to retract
  315. my $need_retract = !$self->config->only_retract_when_crossing_perimeters;
  316. if (!$need_retract) {
  317. $need_retract = 1;
  318. foreach my $island (@{$self->_upper_layer_islands}) {
  319. # discard the island if at any line is not enclosed in it
  320. next if first { !$island->contains_line($_) } @travel;
  321. # okay, this island encloses the full travel path
  322. $need_retract = 0;
  323. last;
  324. }
  325. }
  326. # do the retract (the travel_to argument is broken)
  327. $gcode .= $self->retract if $need_retract;
  328. # append the actual path and return
  329. # use G1 because we rely on paths being straight (G0 may make round paths)
  330. $gcode .= join '', map $self->G1($_->b, undef, 0, $self->config->travel_speed*60, $comment || ""), @travel;
  331. return $gcode;
  332. }
  333. sub retract {
  334. my ($self, %params) = @_;
  335. # get the retraction length and abort if none
  336. my ($length, $restart_extra, $comment) = $params{toolchange}
  337. ? ($self->extruder->retract_length_toolchange, $self->extruder->retract_restart_extra_toolchange, "retract for tool change")
  338. : ($self->extruder->retract_length, $self->extruder->retract_restart_extra, "retract");
  339. # if we already retracted, reduce the required amount of retraction
  340. $length -= $self->extruder->retracted;
  341. return "" unless $length > 0;
  342. my $gcode = "";
  343. # wipe
  344. my $wipe_path;
  345. if ($self->extruder->wipe && $self->wipe_path) {
  346. my @points = @{$self->wipe_path};
  347. $wipe_path = Slic3r::Polyline->new($self->last_pos, @{$self->wipe_path}[1..$#{$self->wipe_path}]);
  348. $wipe_path->clip_end($wipe_path->length - $self->extruder->scaled_wipe_distance($self->config->travel_speed));
  349. }
  350. # prepare moves
  351. my $retract = [undef, undef, -$length, $self->extruder->retract_speed_mm_min, $comment];
  352. my $lift = ($self->config->retract_lift->[0] == 0 || defined $params{move_z}) && !$self->lifted
  353. ? undef
  354. : [undef, $self->z + $self->config->retract_lift->[0], 0, $self->config->travel_speed*60, 'lift plate during travel'];
  355. # check that we have a positive wipe length
  356. if ($wipe_path) {
  357. # subdivide the retraction
  358. my $retracted = 0;
  359. foreach my $line (@{$wipe_path->lines}) {
  360. my $segment_length = $line->length;
  361. # reduce retraction length a bit to avoid effective retraction speed to be greater than the configured one
  362. # due to rounding
  363. my $e = $retract->[2] * ($segment_length / $self->extruder->scaled_wipe_distance($self->config->travel_speed)) * 0.95;
  364. $retracted += $e;
  365. $gcode .= $self->G1($line->b, undef, $e, $self->config->travel_speed*60*0.8, $retract->[3] . ";_WIPE");
  366. }
  367. if ($retracted > $retract->[2]) {
  368. # if we retracted less than we had to, retract the remainder
  369. # TODO: add regression test
  370. $gcode .= $self->G1(undef, undef, $retract->[2] - $retracted, $self->extruder->retract_speed_mm_min, $comment);
  371. }
  372. } elsif ($self->config->use_firmware_retraction) {
  373. $gcode .= "G10 ; retract\n";
  374. } else {
  375. $gcode .= $self->G1(@$retract);
  376. }
  377. if (!$self->lifted) {
  378. if (defined $params{move_z} && $self->config->retract_lift->[0] > 0) {
  379. my $travel = [undef, $params{move_z} + $self->config->retract_lift->[0], 0, $self->config->travel_speed*60, 'move to next layer (' . $self->layer->id . ') and lift'];
  380. $gcode .= $self->G0(@$travel);
  381. $self->lifted($self->config->retract_lift->[0]);
  382. } elsif ($lift) {
  383. $gcode .= $self->G1(@$lift);
  384. }
  385. }
  386. $self->extruder->set_retracted($self->extruder->retracted + $length);
  387. $self->extruder->set_restart_extra($restart_extra);
  388. $self->lifted($self->config->retract_lift->[0]) if $lift;
  389. # reset extrusion distance during retracts
  390. # this makes sure we leave sufficient precision in the firmware
  391. $gcode .= $self->reset_e;
  392. $gcode .= "M103 ; extruder off\n" if $self->config->gcode_flavor eq 'makerware';
  393. return $gcode;
  394. }
  395. sub unretract {
  396. my ($self) = @_;
  397. my $gcode = "";
  398. $gcode .= "M101 ; extruder on\n" if $self->config->gcode_flavor eq 'makerware';
  399. if ($self->lifted) {
  400. $gcode .= $self->G0(undef, $self->z - $self->lifted, 0, $self->config->travel_speed*60, 'restore layer Z');
  401. $self->lifted(0);
  402. }
  403. my $to_unretract = $self->extruder->retracted + $self->extruder->restart_extra;
  404. if ($to_unretract) {
  405. if ($self->config->use_firmware_retraction) {
  406. $gcode .= "G11 ; unretract\n";
  407. } elsif ($self->config->get_extrusion_axis) {
  408. # use G1 instead of G0 because G0 will blend the restart with the previous travel move
  409. $gcode .= sprintf "G1 %s%.5f F%.3f",
  410. $self->config->get_extrusion_axis,
  411. $self->extruder->extrude($to_unretract),
  412. $self->extruder->retract_speed_mm_min;
  413. $gcode .= " ; compensate retraction" if $self->config->gcode_comments;
  414. $gcode .= "\n";
  415. }
  416. $self->extruder->set_retracted(0);
  417. $self->extruder->set_restart_extra(0);
  418. }
  419. return $gcode;
  420. }
  421. sub reset_e {
  422. my ($self) = @_;
  423. return "" if $self->config->gcode_flavor =~ /^(?:mach3|makerware|sailfish)$/;
  424. $self->extruder->set_E(0) if $self->extruder;
  425. return sprintf "G92 %s0%s\n", $self->config->get_extrusion_axis, ($self->config->gcode_comments ? ' ; reset extrusion distance' : '')
  426. if $self->config->get_extrusion_axis && !$self->config->use_relative_e_distances;
  427. }
  428. sub set_acceleration {
  429. my ($self, $acceleration) = @_;
  430. return "" if !$acceleration;
  431. return sprintf "M204 S%s%s\n",
  432. $acceleration, ($self->config->gcode_comments ? ' ; adjust acceleration' : '');
  433. }
  434. sub G0 {
  435. my $self = shift;
  436. return $self->G1(@_) if !($self->config->g0 || $self->config->gcode_flavor eq 'mach3');
  437. return $self->_G0_G1("G0", @_);
  438. }
  439. sub G1 {
  440. my $self = shift;
  441. return $self->_G0_G1("G1", @_);
  442. }
  443. sub _G0_G1 {
  444. my ($self, $gcode, $point, $z, $e, $F, $comment) = @_;
  445. if ($point) {
  446. $gcode .= sprintf " X%.3f Y%.3f",
  447. ($point->x * &Slic3r::SCALING_FACTOR) + $self->shift_x - $self->extruder->extruder_offset->x,
  448. ($point->y * &Slic3r::SCALING_FACTOR) + $self->shift_y - $self->extruder->extruder_offset->y; #**
  449. $self->last_pos($point->clone);
  450. }
  451. if (defined $z && (!defined $self->z || $z != $self->z)) {
  452. $self->z($z);
  453. $gcode .= sprintf " Z%.3f", $z;
  454. }
  455. return $self->_Gx($gcode, $e, $F, $comment);
  456. }
  457. sub _Gx {
  458. my ($self, $gcode, $e, $F, $comment) = @_;
  459. $gcode .= sprintf " F%.3f", $F;
  460. # output extrusion distance
  461. if ($e && $self->config->get_extrusion_axis) {
  462. $gcode .= sprintf " %s%.5f", $self->config->get_extrusion_axis, $self->extruder->extrude($e);
  463. }
  464. $gcode .= " ; $comment" if $comment && $self->config->gcode_comments;
  465. return "$gcode\n";
  466. }
  467. sub set_extruder {
  468. my ($self, $extruder_id) = @_;
  469. # return nothing if this extruder was already selected
  470. return "" if (defined $self->extruder) && ($self->extruder->id == $extruder_id);
  471. # if we are running a single-extruder setup, just set the extruder and return nothing
  472. if (!$self->multiple_extruders) {
  473. $self->extruder($self->extruders->{$extruder_id});
  474. return "";
  475. }
  476. # trigger retraction on the current extruder (if any)
  477. my $gcode = "";
  478. $gcode .= $self->retract(toolchange => 1) if defined $self->extruder;
  479. # append custom toolchange G-code
  480. if (defined $self->extruder && $self->config->toolchange_gcode) {
  481. $gcode .= sprintf "%s\n", $self->placeholder_parser->process($self->config->toolchange_gcode, {
  482. previous_extruder => $self->extruder->id,
  483. next_extruder => $extruder_id,
  484. });
  485. }
  486. # set the current extruder to the standby temperature
  487. if ($self->standby_points && defined $self->extruder) {
  488. # move to the nearest standby point
  489. {
  490. my $last_pos = $self->last_pos->clone;
  491. $last_pos->translate(scale +$self->shift_x, scale +$self->shift_y);
  492. my $standby_point = $last_pos->nearest_point($self->standby_points);
  493. $standby_point->translate(scale -$self->shift_x, scale -$self->shift_y);
  494. $gcode .= $self->travel_to($standby_point);
  495. }
  496. if ($self->config->standby_temperature_delta != 0) {
  497. my $temp = defined $self->layer && $self->layer->id == 0
  498. ? $self->extruder->first_layer_temperature
  499. : $self->extruder->temperature;
  500. # we assume that heating is always slower than cooling, so no need to block
  501. $gcode .= $self->set_temperature($temp + $self->config->standby_temperature_delta, 0);
  502. }
  503. }
  504. # set the new extruder
  505. $self->extruder($self->extruders->{$extruder_id});
  506. $gcode .= sprintf "%s%d%s\n",
  507. ($self->config->gcode_flavor eq 'makerware'
  508. ? 'M135 T'
  509. : $self->config->gcode_flavor eq 'sailfish'
  510. ? 'M108 T'
  511. : 'T'),
  512. $extruder_id,
  513. ($self->config->gcode_comments ? ' ; change extruder' : '');
  514. $gcode .= $self->reset_e;
  515. # set the new extruder to the operating temperature
  516. if ($self->config->ooze_prevention && $self->config->standby_temperature_delta != 0) {
  517. my $temp = defined $self->layer && $self->layer->id == 0
  518. ? $self->extruder->first_layer_temperature
  519. : $self->extruder->temperature;
  520. $gcode .= $self->set_temperature($temp, 1);
  521. }
  522. return $gcode;
  523. }
  524. sub set_fan {
  525. my ($self, $speed, $dont_save) = @_;
  526. if ($self->last_fan_speed != $speed || $dont_save) {
  527. $self->last_fan_speed($speed) if !$dont_save;
  528. if ($speed == 0) {
  529. my $code = $self->config->gcode_flavor eq 'teacup'
  530. ? 'M106 S0'
  531. : $self->config->gcode_flavor =~ /^(?:makerware|sailfish)$/
  532. ? 'M127'
  533. : 'M107';
  534. return sprintf "$code%s\n", ($self->config->gcode_comments ? ' ; disable fan' : '');
  535. } else {
  536. if ($self->config->gcode_flavor =~ /^(?:makerware|sailfish)$/) {
  537. return sprintf "M126%s\n", ($self->config->gcode_comments ? ' ; enable fan' : '');
  538. } else {
  539. return sprintf "M106 %s%d%s\n", ($self->config->gcode_flavor eq 'mach3' ? 'P' : 'S'),
  540. (255 * $speed / 100), ($self->config->gcode_comments ? ' ; enable fan' : '');
  541. }
  542. }
  543. }
  544. return "";
  545. }
  546. sub set_temperature {
  547. my ($self, $temperature, $wait, $tool) = @_;
  548. return "" if $wait && $self->config->gcode_flavor =~ /^(?:makerware|sailfish)$/;
  549. my ($code, $comment) = ($wait && $self->config->gcode_flavor ne 'teacup')
  550. ? ('M109', 'wait for temperature to be reached')
  551. : ('M104', 'set temperature');
  552. my $gcode = sprintf "$code %s%d %s; $comment\n",
  553. ($self->config->gcode_flavor eq 'mach3' ? 'P' : 'S'), $temperature,
  554. (defined $tool && ($self->multiple_extruders || $self->config->gcode_flavor =~ /^(?:makerware|sailfish)$/)) ? "T$tool " : "";
  555. $gcode .= "M116 ; wait for temperature to be reached\n"
  556. if $self->config->gcode_flavor eq 'teacup' && $wait;
  557. return $gcode;
  558. }
  559. sub set_bed_temperature {
  560. my ($self, $temperature, $wait) = @_;
  561. my ($code, $comment) = ($wait && $self->config->gcode_flavor ne 'teacup')
  562. ? (($self->config->gcode_flavor =~ /^(?:makerware|sailfish)$/ ? 'M109' : 'M190'), 'wait for bed temperature to be reached')
  563. : ('M140', 'set bed temperature');
  564. my $gcode = sprintf "$code %s%d ; $comment\n",
  565. ($self->config->gcode_flavor eq 'mach3' ? 'P' : 'S'), $temperature;
  566. $gcode .= "M116 ; wait for bed temperature to be reached\n"
  567. if $self->config->gcode_flavor eq 'teacup' && $wait;
  568. return $gcode;
  569. }
  570. 1;