GCode.pm 31 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643
  1. package Slic3r::Print::GCode;
  2. use Moo;
  3. has 'print' => (is => 'ro', required => 1, handles => [qw(objects placeholder_parser config)]);
  4. has 'fh' => (is => 'ro', required => 1);
  5. has '_gcodegen' => (is => 'rw');
  6. has '_cooling_buffer' => (is => 'rw');
  7. has '_spiral_vase' => (is => 'rw');
  8. has '_vibration_limit' => (is => 'rw');
  9. has '_arc_fitting' => (is => 'rw');
  10. has '_pressure_regulator' => (is => 'rw');
  11. has '_skirt_done' => (is => 'rw', default => sub { {} }); # print_z => 1
  12. has '_brim_done' => (is => 'rw');
  13. has '_second_layer_things_done' => (is => 'rw');
  14. has '_last_obj_copy' => (is => 'rw');
  15. use List::Util qw(first sum min max);
  16. use Slic3r::Flow ':roles';
  17. use Slic3r::Geometry qw(X Y scale unscale chained_path convex_hull);
  18. use Slic3r::Geometry::Clipper qw(JT_SQUARE union_ex offset);
  19. sub BUILD {
  20. my ($self) = @_;
  21. {
  22. # estimate the total number of layer changes
  23. # TODO: only do this when M73 is enabled
  24. my $layer_count;
  25. if ($self->config->complete_objects) {
  26. $layer_count = sum(map { $_->total_layer_count * @{$_->copies} } @{$self->objects});
  27. } else {
  28. # if sequential printing is not enable, all copies of the same object share the same layer change command(s)
  29. $layer_count = sum(map { $_->total_layer_count } @{$self->objects});
  30. }
  31. # set up our helper object
  32. my $gcodegen = Slic3r::GCode->new(
  33. placeholder_parser => $self->placeholder_parser,
  34. layer_count => $layer_count,
  35. enable_cooling_markers => 1,
  36. );
  37. $self->_gcodegen($gcodegen);
  38. $gcodegen->apply_print_config($self->config);
  39. $gcodegen->set_extruders($self->print->extruders);
  40. # initialize autospeed
  41. {
  42. # get the minimum cross-section used in the print
  43. my @mm3_per_mm = ();
  44. foreach my $object (@{$self->print->objects}) {
  45. foreach my $region_id (0..$#{$self->print->regions}) {
  46. my $region = $self->print->get_region($region_id);
  47. foreach my $layer (@{$object->layers}) {
  48. my $layerm = $layer->get_region($region_id);
  49. if ($region->config->get_abs_value('perimeter_speed') == 0
  50. || $region->config->get_abs_value('small_perimeter_speed') == 0
  51. || $region->config->get_abs_value('external_perimeter_speed') == 0
  52. || $region->config->get_abs_value('bridge_speed') == 0) {
  53. push @mm3_per_mm, $layerm->perimeters->min_mm3_per_mm;
  54. }
  55. if ($region->config->get_abs_value('infill_speed') == 0
  56. || $region->config->get_abs_value('solid_infill_speed') == 0
  57. || $region->config->get_abs_value('top_solid_infill_speed') == 0
  58. || $region->config->get_abs_value('bridge_speed') == 0) {
  59. push @mm3_per_mm, $layerm->fills->min_mm3_per_mm;
  60. }
  61. }
  62. }
  63. if ($object->config->get_abs_value('support_material_speed') == 0
  64. || $object->config->get_abs_value('support_material_interface_speed') == 0) {
  65. foreach my $layer (@{$object->support_layers}) {
  66. push @mm3_per_mm, $layer->support_fills->min_mm3_per_mm;
  67. push @mm3_per_mm, $layer->support_interface_fills->min_mm3_per_mm;
  68. }
  69. }
  70. }
  71. my $min_mm3_per_mm = min(@mm3_per_mm);
  72. if ($min_mm3_per_mm > 0) {
  73. # In order to honor max_print_speed we need to find a target volumetric
  74. # speed that we can use throughout the print. So we define this target
  75. # volumetric speed as the volumetric speed produced by printing the
  76. # smallest cross-section at the maximum speed: any larger cross-section
  77. # will need slower feedrates.
  78. my $volumetric_speed = $min_mm3_per_mm * $self->config->max_print_speed;
  79. # limit such volumetric speed with max_volumetric_speed if set
  80. if ($self->config->max_volumetric_speed > 0) {
  81. $volumetric_speed = min(
  82. $volumetric_speed,
  83. $self->config->max_volumetric_speed,
  84. );
  85. }
  86. $gcodegen->volumetric_speed($volumetric_speed);
  87. }
  88. }
  89. }
  90. $self->_cooling_buffer(Slic3r::GCode::CoolingBuffer->new(
  91. config => $self->config,
  92. gcodegen => $self->_gcodegen,
  93. ));
  94. $self->_spiral_vase(Slic3r::GCode::SpiralVase->new(config => $self->config))
  95. if $self->config->spiral_vase;
  96. $self->_vibration_limit(Slic3r::GCode::VibrationLimit->new(config => $self->config))
  97. if $self->config->vibration_limit != 0;
  98. $self->_arc_fitting(Slic3r::GCode::ArcFitting->new(config => $self->config))
  99. if $self->config->gcode_arcs;
  100. $self->_pressure_regulator(Slic3r::GCode::PressureRegulator->new(config => $self->config))
  101. if $self->config->pressure_advance > 0;
  102. }
  103. sub export {
  104. my ($self) = @_;
  105. my $fh = $self->fh;
  106. my $gcodegen = $self->_gcodegen;
  107. # write some information
  108. my @lt = localtime;
  109. printf $fh "; generated by Slic3r $Slic3r::VERSION on %04d-%02d-%02d at %02d:%02d:%02d\n\n",
  110. $lt[5] + 1900, $lt[4]+1, $lt[3], $lt[2], $lt[1], $lt[0];
  111. print $fh "; $_\n" foreach split /\R/, $self->config->notes;
  112. print $fh "\n" if $self->config->notes;
  113. my $first_object = $self->objects->[0];
  114. my $layer_height = $first_object->config->layer_height;
  115. for my $region_id (0..$#{$self->print->regions}) {
  116. my $region = $self->print->regions->[$region_id];
  117. printf $fh "; external perimeters extrusion width = %.2fmm\n",
  118. $region->flow(FLOW_ROLE_EXTERNAL_PERIMETER, $layer_height, 0, 0, -1, $first_object)->width;
  119. printf $fh "; perimeters extrusion width = %.2fmm\n",
  120. $region->flow(FLOW_ROLE_PERIMETER, $layer_height, 0, 0, -1, $first_object)->width;
  121. printf $fh "; infill extrusion width = %.2fmm\n",
  122. $region->flow(FLOW_ROLE_INFILL, $layer_height, 0, 0, -1, $first_object)->width;
  123. printf $fh "; solid infill extrusion width = %.2fmm\n",
  124. $region->flow(FLOW_ROLE_SOLID_INFILL, $layer_height, 0, 0, -1, $first_object)->width;
  125. printf $fh "; top infill extrusion width = %.2fmm\n",
  126. $region->flow(FLOW_ROLE_TOP_SOLID_INFILL, $layer_height, 0, 0, -1, $first_object)->width;
  127. printf $fh "; support material extrusion width = %.2fmm\n",
  128. $self->objects->[0]->support_material_flow->width
  129. if $self->print->has_support_material;
  130. printf $fh "; first layer extrusion width = %.2fmm\n",
  131. $region->flow(FLOW_ROLE_PERIMETER, $layer_height, 0, 1, -1, $self->objects->[0])->width
  132. if $region->config->first_layer_extrusion_width;
  133. print $fh "\n";
  134. }
  135. # prepare the helper object for replacing placeholders in custom G-code and output filename
  136. $self->placeholder_parser->update_timestamp;
  137. print $fh $gcodegen->writer->set_fan(0, 1)
  138. if $self->config->cooling && $self->config->disable_fan_first_layers;
  139. # set bed temperature
  140. if ((my $temp = $self->config->first_layer_bed_temperature) && $self->config->start_gcode !~ /M(?:190|140)/i) {
  141. printf $fh $gcodegen->writer->set_bed_temperature($temp, 1);
  142. }
  143. # set extruder(s) temperature before and after start G-code
  144. $self->_print_first_layer_temperature(0);
  145. printf $fh "%s\n", $gcodegen->placeholder_parser->process($self->config->start_gcode);
  146. $self->_print_first_layer_temperature(1);
  147. # set other general things
  148. print $fh $gcodegen->preamble;
  149. # initialize a motion planner for object-to-object travel moves
  150. if ($self->config->avoid_crossing_perimeters) {
  151. my $distance_from_objects = scale 1;
  152. # compute the offsetted convex hull for each object and repeat it for each copy.
  153. my @islands_p = ();
  154. foreach my $object (@{$self->objects}) {
  155. # compute the convex hull of the entire object
  156. my $convex_hull = convex_hull([
  157. map @{$_->contour}, map @{$_->slices}, @{$object->layers},
  158. ]);
  159. # discard objects only containing thin walls (offset would fail on an empty polygon)
  160. next if !@$convex_hull;
  161. # grow convex hull by the wanted clearance
  162. my @obj_islands_p = @{offset([$convex_hull], $distance_from_objects, 1, JT_SQUARE)};
  163. # translate convex hull for each object copy and append it to the islands array
  164. foreach my $copy (@{ $object->_shifted_copies }) {
  165. my @copy_islands_p = map $_->clone, @obj_islands_p;
  166. $_->translate(@$copy) for @copy_islands_p;
  167. push @islands_p, @copy_islands_p;
  168. }
  169. }
  170. $gcodegen->avoid_crossing_perimeters->init_external_mp(union_ex(\@islands_p));
  171. }
  172. # calculate wiping points if needed
  173. if ($self->config->ooze_prevention) {
  174. my @skirt_points = map @$_, map @$_, @{$self->print->skirt};
  175. if (@skirt_points) {
  176. my $outer_skirt = convex_hull(\@skirt_points);
  177. my @skirts = ();
  178. foreach my $extruder_id (@{$self->print->extruders}) {
  179. my $extruder_offset = $self->config->get_at('extruder_offset', $extruder_id);
  180. push @skirts, my $s = $outer_skirt->clone;
  181. $s->translate(-scale($extruder_offset->x), -scale($extruder_offset->y)); #)
  182. }
  183. my $convex_hull = convex_hull([ map @$_, @skirts ]);
  184. $gcodegen->ooze_prevention->enable(1);
  185. $gcodegen->ooze_prevention->standby_points(
  186. [ map @{$_->equally_spaced_points(scale 10)}, @{offset([$convex_hull], scale 3)} ]
  187. );
  188. if (0) {
  189. require "Slic3r/SVG.pm";
  190. Slic3r::SVG::output(
  191. "ooze_prevention.svg",
  192. red_polygons => \@skirts,
  193. polygons => [$outer_skirt],
  194. points => $gcodegen->ooze_prevention->standby_points,
  195. );
  196. }
  197. }
  198. }
  199. # set initial extruder only after custom start G-code
  200. print $fh $gcodegen->set_extruder($self->print->extruders->[0]);
  201. # do all objects for each layer
  202. if ($self->config->complete_objects) {
  203. # print objects from the smallest to the tallest to avoid collisions
  204. # when moving onto next object starting point
  205. my @obj_idx = sort { $self->objects->[$a]->size->z <=> $self->objects->[$b]->size->z } 0..($self->print->object_count - 1);
  206. my $finished_objects = 0;
  207. for my $obj_idx (@obj_idx) {
  208. my $object = $self->objects->[$obj_idx];
  209. for my $copy (@{ $self->objects->[$obj_idx]->_shifted_copies }) {
  210. # move to the origin position for the copy we're going to print.
  211. # this happens before Z goes down to layer 0 again, so that
  212. # no collision happens hopefully.
  213. if ($finished_objects > 0) {
  214. $gcodegen->set_origin(Slic3r::Pointf->new(map unscale $copy->[$_], X,Y));
  215. $gcodegen->enable_cooling_markers(0); # we're not filtering these moves through CoolingBuffer
  216. $gcodegen->avoid_crossing_perimeters->use_external_mp_once(1);
  217. print $fh $gcodegen->retract;
  218. print $fh $gcodegen->travel_to(
  219. Slic3r::Point->new(0,0),
  220. undef,
  221. 'move to origin position for next object',
  222. );
  223. $gcodegen->enable_cooling_markers(1);
  224. # disable motion planner when traveling to first object point
  225. $gcodegen->avoid_crossing_perimeters->disable_once(1);
  226. }
  227. my @layers = sort { $a->print_z <=> $b->print_z } @{$object->layers}, @{$object->support_layers};
  228. for my $layer (@layers) {
  229. # if we are printing the bottom layer of an object, and we have already finished
  230. # another one, set first layer temperatures. this happens before the Z move
  231. # is triggered, so machine has more time to reach such temperatures
  232. if ($layer->id == 0 && $finished_objects > 0) {
  233. printf $fh $gcodegen->writer->set_bed_temperature($self->config->first_layer_bed_temperature),
  234. if $self->config->first_layer_bed_temperature;
  235. $self->_print_first_layer_temperature(0);
  236. }
  237. $self->process_layer($layer, [$copy]);
  238. }
  239. $self->flush_filters;
  240. $finished_objects++;
  241. $self->_second_layer_things_done(0);
  242. }
  243. }
  244. } else {
  245. # order objects using a nearest neighbor search
  246. my @obj_idx = @{chained_path([ map Slic3r::Point->new(@{$_->_shifted_copies->[0]}), @{$self->objects} ])};
  247. # sort layers by Z
  248. my %layers = (); # print_z => [ [layers], [layers], [layers] ] by obj_idx
  249. foreach my $obj_idx (0 .. ($self->print->object_count - 1)) {
  250. my $object = $self->objects->[$obj_idx];
  251. foreach my $layer (@{$object->layers}, @{$object->support_layers}) {
  252. $layers{ $layer->print_z } ||= [];
  253. $layers{ $layer->print_z }[$obj_idx] ||= [];
  254. push @{$layers{ $layer->print_z }[$obj_idx]}, $layer;
  255. }
  256. }
  257. foreach my $print_z (sort { $a <=> $b } keys %layers) {
  258. foreach my $obj_idx (@obj_idx) {
  259. foreach my $layer (@{ $layers{$print_z}[$obj_idx] // [] }) {
  260. $self->process_layer($layer, $layer->object->_shifted_copies);
  261. }
  262. }
  263. }
  264. $self->flush_filters;
  265. }
  266. # write end commands to file
  267. print $fh $gcodegen->retract; # TODO: process this retract through PressureRegulator in order to discharge fully
  268. print $fh $gcodegen->writer->set_fan(0);
  269. printf $fh "%s\n", $gcodegen->placeholder_parser->process($self->config->end_gcode);
  270. print $fh $gcodegen->writer->update_progress($gcodegen->layer_count, $gcodegen->layer_count, 1); # 100%
  271. print $fh $gcodegen->writer->postamble;
  272. $self->print->total_used_filament(0);
  273. $self->print->total_extruded_volume(0);
  274. foreach my $extruder (@{$gcodegen->writer->extruders}) {
  275. my $used_filament = $extruder->used_filament;
  276. my $extruded_volume = $extruder->extruded_volume;
  277. printf $fh "; filament used = %.1fmm (%.1fcm3)\n",
  278. $used_filament, $extruded_volume/1000;
  279. $self->print->total_used_filament($self->print->total_used_filament + $used_filament);
  280. $self->print->total_extruded_volume($self->print->total_extruded_volume + $extruded_volume);
  281. }
  282. # append full config
  283. print $fh "\n";
  284. foreach my $config ($self->print->config, $self->print->default_object_config, $self->print->default_region_config) {
  285. foreach my $opt_key (sort @{$config->get_keys}) {
  286. next if $Slic3r::Config::Options->{$opt_key}{shortcut};
  287. printf $fh "; %s = %s\n", $opt_key, $config->serialize($opt_key);
  288. }
  289. }
  290. }
  291. sub _print_first_layer_temperature {
  292. my ($self, $wait) = @_;
  293. return if $self->config->start_gcode =~ /M(?:109|104)/i;
  294. for my $t (@{$self->print->extruders}) {
  295. my $temp = $self->config->get_at('first_layer_temperature', $t);
  296. $temp += $self->config->standby_temperature_delta if $self->config->ooze_prevention;
  297. printf {$self->fh} $self->_gcodegen->writer->set_temperature($temp, $wait, $t) if $temp > 0;
  298. }
  299. }
  300. sub process_layer {
  301. my $self = shift;
  302. my ($layer, $object_copies) = @_;
  303. my $gcode = "";
  304. my $object = $layer->object;
  305. $self->_gcodegen->config->apply_object_config($object->config);
  306. # check whether we're going to apply spiralvase logic
  307. if (defined $self->_spiral_vase) {
  308. $self->_spiral_vase->enable(
  309. ($layer->id > 0 || $self->print->config->brim_width == 0)
  310. && ($layer->id >= $self->print->config->skirt_height && !$self->print->has_infinite_skirt)
  311. && !defined(first { $_->config->bottom_solid_layers > $layer->id } @{$layer->regions})
  312. && !defined(first { $_->perimeters->items_count > 1 } @{$layer->regions})
  313. && !defined(first { $_->fills->items_count > 0 } @{$layer->regions})
  314. );
  315. }
  316. # if we're going to apply spiralvase to this layer, disable loop clipping
  317. $self->_gcodegen->enable_loop_clipping(!defined $self->_spiral_vase || !$self->_spiral_vase->enable);
  318. if (!$self->_second_layer_things_done && $layer->id == 1) {
  319. for my $extruder (@{$self->_gcodegen->writer->extruders}) {
  320. my $temperature = $self->config->get_at('temperature', $extruder->id);
  321. $gcode .= $self->_gcodegen->writer->set_temperature($temperature, 0, $extruder->id)
  322. if $temperature && $temperature != $self->config->get_at('first_layer_temperature', $extruder->id);
  323. }
  324. $gcode .= $self->_gcodegen->writer->set_bed_temperature($self->print->config->bed_temperature)
  325. if $self->print->config->bed_temperature && $self->print->config->bed_temperature != $self->print->config->first_layer_bed_temperature;
  326. $self->_second_layer_things_done(1);
  327. }
  328. # set new layer - this will change Z and force a retraction if retract_layer_change is enabled
  329. $gcode .= $self->_gcodegen->placeholder_parser->process($self->print->config->before_layer_gcode, {
  330. layer_num => $self->_gcodegen->layer_index + 1,
  331. layer_z => $layer->print_z,
  332. }) . "\n" if $self->print->config->before_layer_gcode;
  333. $gcode .= $self->_gcodegen->change_layer($layer); # this will increase $self->_gcodegen->layer_index
  334. $gcode .= $self->_gcodegen->placeholder_parser->process($self->print->config->layer_gcode, {
  335. layer_num => $self->_gcodegen->layer_index,
  336. layer_z => $layer->print_z,
  337. }) . "\n" if $self->print->config->layer_gcode;
  338. # extrude skirt along raft layers and normal object layers
  339. # (not along interlaced support material layers)
  340. if (((values %{$self->_skirt_done}) < $self->print->config->skirt_height || $self->print->has_infinite_skirt)
  341. && !$self->_skirt_done->{$layer->print_z}
  342. && (!$layer->isa('Slic3r::Layer::Support') || $layer->id < $object->config->raft_layers)) {
  343. $self->_gcodegen->set_origin(Slic3r::Pointf->new(0,0));
  344. $self->_gcodegen->avoid_crossing_perimeters->use_external_mp(1);
  345. my @extruder_ids = map { $_->id } @{$self->_gcodegen->writer->extruders};
  346. $gcode .= $self->_gcodegen->set_extruder($extruder_ids[0]);
  347. # skip skirt if we have a large brim
  348. if ($layer->id < $self->print->config->skirt_height || $self->print->has_infinite_skirt) {
  349. my $skirt_flow = $self->print->skirt_flow;
  350. # distribute skirt loops across all extruders
  351. my @skirt_loops = @{$self->print->skirt};
  352. for my $i (0 .. $#skirt_loops) {
  353. # when printing layers > 0 ignore 'min_skirt_length' and
  354. # just use the 'skirts' setting; also just use the current extruder
  355. last if ($layer->id > 0) && ($i >= $self->print->config->skirts);
  356. my $extruder_id = $extruder_ids[($i/@extruder_ids) % @extruder_ids];
  357. $gcode .= $self->_gcodegen->set_extruder($extruder_id)
  358. if $layer->id == 0;
  359. # adjust flow according to this layer's layer height
  360. my $loop = $skirt_loops[$i]->clone;
  361. {
  362. my $layer_skirt_flow = $skirt_flow->clone;
  363. $layer_skirt_flow->set_height($layer->height);
  364. my $mm3_per_mm = $layer_skirt_flow->mm3_per_mm;
  365. foreach my $path (@$loop) {
  366. $path->height($layer->height);
  367. $path->mm3_per_mm($mm3_per_mm);
  368. }
  369. }
  370. $gcode .= $self->_gcodegen->extrude_loop($loop, 'skirt', $object->config->support_material_speed);
  371. }
  372. }
  373. $self->_skirt_done->{$layer->print_z} = 1;
  374. $self->_gcodegen->avoid_crossing_perimeters->use_external_mp(0);
  375. # allow a straight travel move to the first object point if this is the first layer
  376. # (but don't in next layers)
  377. if ($layer->id == 0) {
  378. $self->_gcodegen->avoid_crossing_perimeters->disable_once(1);
  379. }
  380. }
  381. # extrude brim
  382. if (!$self->_brim_done) {
  383. $gcode .= $self->_gcodegen->set_extruder($self->print->regions->[0]->config->perimeter_extruder-1);
  384. $self->_gcodegen->set_origin(Slic3r::Pointf->new(0,0));
  385. $self->_gcodegen->avoid_crossing_perimeters->use_external_mp(1);
  386. $gcode .= $self->_gcodegen->extrude_loop($_, 'brim', $object->config->support_material_speed)
  387. for @{$self->print->brim};
  388. $self->_brim_done(1);
  389. $self->_gcodegen->avoid_crossing_perimeters->use_external_mp(0);
  390. # allow a straight travel move to the first object point
  391. $self->_gcodegen->avoid_crossing_perimeters->disable_once(1);
  392. }
  393. for my $copy (@$object_copies) {
  394. # when starting a new object, use the external motion planner for the first travel move
  395. $self->_gcodegen->avoid_crossing_perimeters->use_external_mp_once(1) if ($self->_last_obj_copy // '') ne "$copy";
  396. $self->_last_obj_copy("$copy");
  397. $self->_gcodegen->set_origin(Slic3r::Pointf->new(map unscale $copy->[$_], X,Y));
  398. # extrude support material before other things because it might use a lower Z
  399. # and also because we avoid travelling on other things when printing it
  400. if ($layer->isa('Slic3r::Layer::Support')) {
  401. if ($layer->support_interface_fills->count > 0) {
  402. $gcode .= $self->_gcodegen->set_extruder($object->config->support_material_interface_extruder-1);
  403. $gcode .= $self->_gcodegen->extrude_path($_, 'support material interface', $object->config->get_abs_value('support_material_interface_speed'))
  404. for @{$layer->support_interface_fills->chained_path_from($self->_gcodegen->last_pos, 0)};
  405. }
  406. if ($layer->support_fills->count > 0) {
  407. $gcode .= $self->_gcodegen->set_extruder($object->config->support_material_extruder-1);
  408. $gcode .= $self->_gcodegen->extrude_path($_, 'support material', $object->config->get_abs_value('support_material_speed'))
  409. for @{$layer->support_fills->chained_path_from($self->_gcodegen->last_pos, 0)};
  410. }
  411. }
  412. # We now define a strategy for building perimeters and fills. The separation
  413. # between regions doesn't matter in terms of printing order, as we follow
  414. # another logic instead:
  415. # - we group all extrusions by extruder so that we minimize toolchanges
  416. # - we start from the last used extruder
  417. # - for each extruder, we group extrusions by island
  418. # - for each island, we extrude perimeters first, unless user set the infill_first
  419. # option
  420. # group extrusions by extruder and then by island
  421. my %by_extruder = (); # extruder_id => [ { perimeters => \@perimeters, infill => \@infill } ]
  422. foreach my $region_id (0..($self->print->region_count-1)) {
  423. my $layerm = $layer->regions->[$region_id] or next;
  424. my $region = $self->print->get_region($region_id);
  425. # process perimeters
  426. {
  427. my $extruder_id = $region->config->perimeter_extruder-1;
  428. foreach my $perimeter_coll (@{$layerm->perimeters}) {
  429. next if $perimeter_coll->empty; # this shouldn't happen but first_point() would fail
  430. # init by_extruder item only if we actually use the extruder
  431. $by_extruder{$extruder_id} //= [];
  432. # $perimeter_coll is an ExtrusionPath::Collection object representing a single slice
  433. for my $i (0 .. $#{$layer->slices}) {
  434. if ($i == $#{$layer->slices}
  435. || $layer->slices->[$i]->contour->contains_point($perimeter_coll->first_point)) {
  436. $by_extruder{$extruder_id}[$i] //= { perimeters => {} };
  437. $by_extruder{$extruder_id}[$i]{perimeters}{$region_id} //= [];
  438. push @{ $by_extruder{$extruder_id}[$i]{perimeters}{$region_id} }, @$perimeter_coll;
  439. last;
  440. }
  441. }
  442. }
  443. }
  444. # process infill
  445. # $layerm->fills is a collection of ExtrusionPath::Collection objects, each one containing
  446. # the ExtrusionPath objects of a certain infill "group" (also called "surface"
  447. # throughout the code). We can redefine the order of such Collections but we have to
  448. # do each one completely at once.
  449. foreach my $fill (@{$layerm->fills}) {
  450. next if $fill->empty; # this shouldn't happen but first_point() would fail
  451. # init by_extruder item only if we actually use the extruder
  452. my $extruder_id = $fill->[0]->is_solid_infill
  453. ? $region->config->solid_infill_extruder-1
  454. : $region->config->infill_extruder-1;
  455. $by_extruder{$extruder_id} //= [];
  456. # $fill is an ExtrusionPath::Collection object
  457. for my $i (0 .. $#{$layer->slices}) {
  458. if ($i == $#{$layer->slices}
  459. || $layer->slices->[$i]->contour->contains_point($fill->first_point)) {
  460. $by_extruder{$extruder_id}[$i] //= { infill => {} };
  461. $by_extruder{$extruder_id}[$i]{infill}{$region_id} //= [];
  462. push @{ $by_extruder{$extruder_id}[$i]{infill}{$region_id} }, $fill;
  463. last;
  464. }
  465. }
  466. }
  467. }
  468. # tweak extruder ordering to save toolchanges
  469. my @extruders = sort keys %by_extruder;
  470. if (@extruders > 1) {
  471. my $last_extruder_id = $self->_gcodegen->writer->extruder->id;
  472. if (exists $by_extruder{$last_extruder_id}) {
  473. @extruders = (
  474. $last_extruder_id,
  475. grep $_ != $last_extruder_id, @extruders,
  476. );
  477. }
  478. }
  479. foreach my $extruder_id (@extruders) {
  480. $gcode .= $self->_gcodegen->set_extruder($extruder_id);
  481. foreach my $island (@{ $by_extruder{$extruder_id} }) {
  482. if ($self->print->config->infill_first) {
  483. $gcode .= $self->_extrude_infill($island->{infill} // {});
  484. $gcode .= $self->_extrude_perimeters($island->{perimeters} // {});
  485. } else {
  486. $gcode .= $self->_extrude_perimeters($island->{perimeters} // {});
  487. $gcode .= $self->_extrude_infill($island->{infill} // {});
  488. }
  489. }
  490. }
  491. }
  492. # apply spiral vase post-processing if this layer contains suitable geometry
  493. # (we must feed all the G-code into the post-processor, including the first
  494. # bottom non-spiral layers otherwise it will mess with positions)
  495. # we apply spiral vase at this stage because it requires a full layer
  496. $gcode = $self->_spiral_vase->process_layer($gcode)
  497. if defined $self->_spiral_vase;
  498. # apply cooling logic; this may alter speeds
  499. $gcode = $self->_cooling_buffer->append(
  500. $gcode,
  501. $layer->object->ptr . ref($layer), # differentiate $obj_id between normal layers and support layers
  502. $layer->id,
  503. $layer->print_z,
  504. ) if defined $self->_cooling_buffer;
  505. print {$self->fh} $self->filter($gcode);
  506. }
  507. sub _extrude_perimeters {
  508. my ($self, $entities_by_region) = @_;
  509. my $gcode = "";
  510. foreach my $region_id (sort keys %$entities_by_region) {
  511. $self->_gcodegen->config->apply_region_config($self->print->get_region($region_id)->config);
  512. $gcode .= $self->_gcodegen->extrude($_, 'perimeter')
  513. for @{ $entities_by_region->{$region_id} };
  514. }
  515. return $gcode;
  516. }
  517. sub _extrude_infill {
  518. my ($self, $entities_by_region) = @_;
  519. my $gcode = "";
  520. foreach my $region_id (sort keys %$entities_by_region) {
  521. $self->_gcodegen->config->apply_region_config($self->print->get_region($region_id)->config);
  522. my $collection = Slic3r::ExtrusionPath::Collection->new(@{ $entities_by_region->{$region_id} });
  523. for my $fill (@{$collection->chained_path_from($self->_gcodegen->last_pos, 0)}) {
  524. if ($fill->isa('Slic3r::ExtrusionPath::Collection')) {
  525. $gcode .= $self->_gcodegen->extrude($_, 'infill')
  526. for @{$fill->chained_path_from($self->_gcodegen->last_pos, 0)};
  527. } else {
  528. $gcode .= $self->_gcodegen->extrude($fill, 'infill') ;
  529. }
  530. }
  531. }
  532. return $gcode;
  533. }
  534. sub flush_filters {
  535. my ($self) = @_;
  536. print {$self->fh} $self->filter($self->_cooling_buffer->flush, 1);
  537. }
  538. sub filter {
  539. my ($self, $gcode, $flush) = @_;
  540. # apply vibration limit if enabled;
  541. # this injects pauses according to time (thus depends on actual speeds)
  542. $gcode = $self->_vibration_limit->process($gcode)
  543. if defined $self->_vibration_limit;
  544. # apply pressure regulation if enabled;
  545. # this depends on actual speeds
  546. $gcode = $self->_pressure_regulator->process($gcode, $flush)
  547. if defined $self->_pressure_regulator;
  548. # apply arc fitting if enabled;
  549. # this does not depend on speeds but changes G1 XY commands into G2/G2 IJ
  550. $gcode = $self->_arc_fitting->process($gcode)
  551. if defined $self->_arc_fitting;
  552. return $gcode;
  553. }
  554. 1;