Print.pm 45 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017
  1. package Slic3r::Print;
  2. use Moo;
  3. use File::Basename qw(basename fileparse);
  4. use File::Spec;
  5. use List::Util qw(max first);
  6. use Math::ConvexHull::MonotoneChain qw(convex_hull);
  7. use Slic3r::ExtrusionPath ':roles';
  8. use Slic3r::Geometry qw(X Y Z X1 Y1 X2 Y2 MIN PI scale unscale move_points nearest_point);
  9. use Slic3r::Geometry::Clipper qw(diff_ex union_ex intersection_ex offset JT_ROUND JT_SQUARE);
  10. use Time::HiRes qw(gettimeofday tv_interval);
  11. has 'config' => (is => 'rw', default => sub { Slic3r::Config->new_from_defaults }, trigger => 1);
  12. has 'extra_variables' => (is => 'rw', default => sub {{}});
  13. has 'objects' => (is => 'rw', default => sub {[]});
  14. has 'total_extrusion_length' => (is => 'rw');
  15. has 'processing_time' => (is => 'rw');
  16. has 'extruders' => (is => 'rw', default => sub {[]});
  17. has 'regions' => (is => 'rw', default => sub {[]});
  18. has 'support_material_flow' => (is => 'rw');
  19. has 'first_layer_support_material_flow' => (is => 'rw');
  20. has 'has_support_material' => (is => 'lazy');
  21. # ordered collection of extrusion paths to build skirt loops
  22. has 'skirt' => (
  23. is => 'rw',
  24. #isa => 'ArrayRef[Slic3r::ExtrusionLoop]',
  25. default => sub { [] },
  26. );
  27. # ordered collection of extrusion paths to build a brim
  28. has 'brim' => (
  29. is => 'rw',
  30. #isa => 'ArrayRef[Slic3r::ExtrusionLoop]',
  31. default => sub { [] },
  32. );
  33. sub BUILD {
  34. my $self = shift;
  35. # call this manually because the 'default' coderef doesn't trigger the trigger
  36. $self->_trigger_config;
  37. }
  38. sub _trigger_config {
  39. my $self = shift;
  40. # store config in a handy place
  41. $Slic3r::Config = $self->config;
  42. # legacy with existing config files
  43. $self->config->set('first_layer_height', $self->config->layer_height)
  44. if !$self->config->first_layer_height;
  45. $self->config->set_ifndef('small_perimeter_speed', $self->config->perimeter_speed);
  46. $self->config->set_ifndef('bridge_speed', $self->config->infill_speed);
  47. $self->config->set_ifndef('solid_infill_speed', $self->config->infill_speed);
  48. $self->config->set_ifndef('top_solid_infill_speed', $self->config->solid_infill_speed);
  49. $self->config->set_ifndef('top_solid_layers', $self->config->solid_layers);
  50. $self->config->set_ifndef('bottom_solid_layers', $self->config->solid_layers);
  51. # G-code flavors
  52. $self->config->set('extrusion_axis', 'A') if $self->config->gcode_flavor eq 'mach3';
  53. $self->config->set('extrusion_axis', '') if $self->config->gcode_flavor eq 'no-extrusion';
  54. }
  55. sub _build_has_support_material {
  56. my $self = shift;
  57. return $self->config->support_material
  58. || $self->config->raft_layers > 0
  59. || $self->config->support_material_enforce_layers > 0;
  60. }
  61. sub add_model {
  62. my $self = shift;
  63. my ($model) = @_;
  64. # append/merge materials and preserve a mapping between the original material ID
  65. # and our numeric material index
  66. my %materials = ();
  67. {
  68. my @material_ids = sort keys %{$model->materials};
  69. @material_ids = (0) if !@material_ids;
  70. for (my $i = $self->regions_count; $i < @material_ids; $i++) {
  71. push @{$self->regions}, Slic3r::Print::Region->new;
  72. $materials{$material_ids[$i]} = $#{$self->regions};
  73. }
  74. }
  75. foreach my $object (@{ $model->objects }) {
  76. my @meshes = (); # by region_id
  77. foreach my $volume (@{$object->volumes}) {
  78. # should the object contain multiple volumes of the same material, merge them
  79. my $region_id = defined $volume->material_id ? $materials{$volume->material_id} : 0;
  80. my $mesh = $volume->mesh->clone;
  81. $meshes[$region_id] = $meshes[$region_id]
  82. ? Slic3r::TriangleMesh->merge($meshes[$region_id], $mesh)
  83. : $mesh;
  84. }
  85. foreach my $mesh (@meshes) {
  86. next unless $mesh;
  87. $mesh->check_manifoldness;
  88. if ($object->instances) {
  89. # we ignore the per-instance rotation currently and only
  90. # consider the first one
  91. $mesh->rotate($object->instances->[0]->rotation);
  92. }
  93. $mesh->rotate($Slic3r::Config->rotate);
  94. $mesh->scale($Slic3r::Config->scale / &Slic3r::SCALING_FACTOR);
  95. }
  96. my $complete_mesh = Slic3r::TriangleMesh->merge(grep defined $_, @meshes);
  97. # initialize print object
  98. my $print_object = Slic3r::Print::Object->new(
  99. print => $self,
  100. meshes => [ @meshes ],
  101. size => [ $complete_mesh->size ],
  102. input_file => $object->input_file
  103. );
  104. push @{$self->objects}, $print_object;
  105. # align object to origin
  106. {
  107. my @extents = $complete_mesh->extents;
  108. foreach my $mesh (grep defined $_, @meshes) {
  109. $mesh->move(map -$extents[$_][MIN], X,Y,Z);
  110. }
  111. }
  112. if ($object->instances) {
  113. # replace the default [0,0] instance with the custom ones
  114. @{$print_object->copies} = map [ scale $_->offset->[X], scale $_->offset->[Y] ], @{$object->instances};
  115. }
  116. }
  117. }
  118. sub validate {
  119. my $self = shift;
  120. if ($Slic3r::Config->complete_objects) {
  121. # check horizontal clearance
  122. {
  123. my @a = ();
  124. for my $obj_idx (0 .. $#{$self->objects}) {
  125. my $clearance;
  126. {
  127. my @points = map [ @$_[X,Y] ], map @{$_->vertices}, @{$self->objects->[$obj_idx]->meshes};
  128. my $convex_hull = Slic3r::Polygon->new(convex_hull(\@points));
  129. $clearance = +($convex_hull->offset(scale $Slic3r::Config->extruder_clearance_radius / 2, 1, JT_ROUND))[0];
  130. }
  131. for my $copy (@{$self->objects->[$obj_idx]->copies}) {
  132. my $copy_clearance = $clearance->clone;
  133. $copy_clearance->translate(@$copy);
  134. if (@{ intersection_ex(\@a, [$copy_clearance]) }) {
  135. die "Some objects are too close; your extruder will collide with them.\n";
  136. }
  137. @a = map @$_, @{union_ex([ @a, $copy_clearance ])};
  138. }
  139. }
  140. }
  141. # check vertical clearance
  142. {
  143. my @obj_copies = $self->object_copies;
  144. pop @obj_copies; # ignore the last copy: its height doesn't matter
  145. my $scaled_clearance = scale $Slic3r::Config->extruder_clearance_height;
  146. if (grep { +($_->size)[Z] > $scaled_clearance } map @{$self->objects->[$_->[0]]->meshes}, @obj_copies) {
  147. die "Some objects are too tall and cannot be printed without extruder collisions.\n";
  148. }
  149. }
  150. }
  151. }
  152. sub init_extruders {
  153. my $self = shift;
  154. # map regions to extruders (ghetto mapping for now)
  155. my %extruder_mapping = map { $_ => $_ } 0..$#{$self->regions};
  156. # initialize all extruder(s) we need
  157. my @used_extruders = (
  158. 0,
  159. (map $self->config->get("${_}_extruder")-1, qw(perimeter infill support_material)),
  160. (values %extruder_mapping),
  161. );
  162. for my $extruder_id (keys %{{ map {$_ => 1} @used_extruders }}) {
  163. $self->extruders->[$extruder_id] = Slic3r::Extruder->new(
  164. id => $extruder_id,
  165. map { $_ => $self->config->get($_)->[$extruder_id] // $self->config->get($_)->[0] } #/
  166. @{&Slic3r::Extruder::OPTIONS}
  167. );
  168. }
  169. # calculate regions' flows
  170. for my $region_id (0 .. $#{$self->regions}) {
  171. my $region = $self->regions->[$region_id];
  172. # per-role extruders and flows
  173. for (qw(perimeter infill)) {
  174. $region->extruders->{$_} = ($self->regions_count > 1)
  175. ? $self->extruders->[$extruder_mapping{$region_id}]
  176. : $self->extruders->[$self->config->get("${_}_extruder")-1];
  177. $region->flows->{$_} = $region->extruders->{$_}->make_flow(
  178. width => $self->config->get("${_}_extrusion_width") || $self->config->extrusion_width,
  179. );
  180. $region->first_layer_flows->{$_} = $region->extruders->{$_}->make_flow(
  181. layer_height => $self->config->get_value('first_layer_height'),
  182. width => $self->config->first_layer_extrusion_width,
  183. ) if $self->config->first_layer_extrusion_width;
  184. }
  185. }
  186. # calculate support material flow
  187. if ($self->has_support_material) {
  188. my $extruder = $self->extruders->[$self->config->support_material_extruder-1];
  189. $self->support_material_flow($extruder->make_flow(
  190. width => $self->config->support_material_extrusion_width || $self->config->extrusion_width,
  191. ));
  192. $self->first_layer_support_material_flow($extruder->make_flow(
  193. layer_height => $self->config->get_value('first_layer_height'),
  194. width => $self->config->first_layer_extrusion_width,
  195. ));
  196. }
  197. }
  198. sub object_copies {
  199. my $self = shift;
  200. my @oc = ();
  201. for my $obj_idx (0 .. $#{$self->objects}) {
  202. push @oc, map [ $obj_idx, $_ ], @{$self->objects->[$obj_idx]->copies};
  203. }
  204. return @oc;
  205. }
  206. sub layer_count {
  207. my $self = shift;
  208. return max(map { scalar @{$_->layers} } @{$self->objects});
  209. }
  210. sub regions_count {
  211. my $self = shift;
  212. return scalar @{$self->regions};
  213. }
  214. sub duplicate {
  215. my $self = shift;
  216. if ($Slic3r::Config->duplicate_grid->[X] > 1 || $Slic3r::Config->duplicate_grid->[Y] > 1) {
  217. if (@{$self->objects} > 1) {
  218. die "Grid duplication is not supported with multiple objects\n";
  219. }
  220. my $object = $self->objects->[0];
  221. # generate offsets for copies
  222. my $dist = scale $Slic3r::Config->duplicate_distance;
  223. @{$self->objects->[0]->copies} = ();
  224. for my $x_copy (1..$Slic3r::Config->duplicate_grid->[X]) {
  225. for my $y_copy (1..$Slic3r::Config->duplicate_grid->[Y]) {
  226. push @{$self->objects->[0]->copies}, [
  227. ($object->size->[X] + $dist) * ($x_copy-1),
  228. ($object->size->[Y] + $dist) * ($y_copy-1),
  229. ];
  230. }
  231. }
  232. } elsif ($Slic3r::Config->duplicate > 1) {
  233. foreach my $object (@{$self->objects}) {
  234. @{$object->copies} = map [0,0], 1..$Slic3r::Config->duplicate;
  235. }
  236. $self->arrange_objects;
  237. }
  238. }
  239. sub arrange_objects {
  240. my $self = shift;
  241. my $total_parts = scalar map @{$_->copies}, @{$self->objects};
  242. my $partx = max(map $_->size->[X], @{$self->objects});
  243. my $party = max(map $_->size->[Y], @{$self->objects});
  244. my @positions = Slic3r::Geometry::arrange
  245. ($total_parts, $partx, $party, (map scale $_, @{$Slic3r::Config->bed_size}), scale $Slic3r::Config->min_object_distance, $self->config);
  246. @{$_->copies} = splice @positions, 0, scalar @{$_->copies} for @{$self->objects};
  247. }
  248. sub bounding_box {
  249. my $self = shift;
  250. my @points = ();
  251. foreach my $obj_idx (0 .. $#{$self->objects}) {
  252. my $object = $self->objects->[$obj_idx];
  253. foreach my $copy (@{$self->objects->[$obj_idx]->copies}) {
  254. push @points,
  255. [ $copy->[X], $copy->[Y] ],
  256. [ $copy->[X] + $object->size->[X], $copy->[Y] ],
  257. [ $copy->[X] + $object->size->[X], $copy->[Y] + $object->size->[Y] ],
  258. [ $copy->[X], $copy->[Y] + $object->size->[Y] ];
  259. }
  260. }
  261. return Slic3r::Geometry::bounding_box(\@points);
  262. }
  263. sub size {
  264. my $self = shift;
  265. my @bb = $self->bounding_box;
  266. return [ $bb[X2] - $bb[X1], $bb[Y2] - $bb[Y1] ];
  267. }
  268. sub export_gcode {
  269. my $self = shift;
  270. my %params = @_;
  271. $self->init_extruders;
  272. my $status_cb = $params{status_cb} || sub {};
  273. my $t0 = [gettimeofday];
  274. # skein the STL into layers
  275. # each layer has surfaces with holes
  276. $status_cb->(10, "Processing triangulated mesh");
  277. $_->slice(keep_meshes => $params{keep_meshes}) for @{$self->objects};
  278. # make perimeters
  279. # this will add a set of extrusion loops to each layer
  280. # as well as generate infill boundaries
  281. $status_cb->(20, "Generating perimeters");
  282. $_->make_perimeters for @{$self->objects};
  283. # simplify slices (both layer and region slices),
  284. # we only need the max resolution for perimeters
  285. foreach my $layer (map @{$_->layers}, @{$self->objects}) {
  286. $_->simplify(&Slic3r::SCALED_RESOLUTION)
  287. for @{$layer->slices}, (map $_->expolygon, map @{$_->slices}, @{$layer->regions});
  288. }
  289. # this will assign a type (top/bottom/internal) to $layerm->slices
  290. # and transform $layerm->fill_surfaces from expolygon
  291. # to typed top/bottom/internal surfaces;
  292. $status_cb->(30, "Detecting solid surfaces");
  293. $_->detect_surfaces_type for @{$self->objects};
  294. # decide what surfaces are to be filled
  295. $status_cb->(35, "Preparing infill surfaces");
  296. $_->prepare_fill_surfaces for map @{$_->regions}, map @{$_->layers}, @{$self->objects};
  297. # this will detect bridges and reverse bridges
  298. # and rearrange top/bottom/internal surfaces
  299. $status_cb->(45, "Detect bridges");
  300. $_->process_external_surfaces for map @{$_->regions}, map @{$_->layers}, @{$self->objects};
  301. # detect which fill surfaces are near external layers
  302. # they will be split in internal and internal-solid surfaces
  303. $status_cb->(60, "Generating horizontal shells");
  304. $_->discover_horizontal_shells for @{$self->objects};
  305. $_->clip_fill_surfaces for @{$self->objects};
  306. # the following step needs to be done before combination because it may need
  307. # to remove only half of the combined infill
  308. $_->bridge_over_infill for @{$self->objects};
  309. # combine fill surfaces to honor the "infill every N layers" option
  310. $status_cb->(70, "Combining infill");
  311. $_->combine_infill for @{$self->objects};
  312. # this will generate extrusion paths for each layer
  313. $status_cb->(80, "Infilling layers");
  314. {
  315. my $fill_maker = Slic3r::Fill->new('print' => $self);
  316. Slic3r::parallelize(
  317. items => sub {
  318. my @items = (); # [obj_idx, layer_id]
  319. for my $obj_idx (0 .. $#{$self->objects}) {
  320. for my $region_id (0 .. ($self->regions_count-1)) {
  321. push @items, map [$obj_idx, $_, $region_id], 0..($self->objects->[$obj_idx]->layer_count-1);
  322. }
  323. }
  324. @items;
  325. },
  326. thread_cb => sub {
  327. my $q = shift;
  328. $Slic3r::Geometry::Clipper::clipper = Math::Clipper->new;
  329. my $fills = {};
  330. while (defined (my $obj_layer = $q->dequeue)) {
  331. my ($obj_idx, $layer_id, $region_id) = @$obj_layer;
  332. $fills->{$obj_idx} ||= {};
  333. $fills->{$obj_idx}{$layer_id} ||= {};
  334. $fills->{$obj_idx}{$layer_id}{$region_id} = [
  335. $fill_maker->make_fill($self->objects->[$obj_idx]->layers->[$layer_id]->regions->[$region_id]),
  336. ];
  337. }
  338. return $fills;
  339. },
  340. collect_cb => sub {
  341. my $fills = shift;
  342. foreach my $obj_idx (keys %$fills) {
  343. my $object = $self->objects->[$obj_idx];
  344. foreach my $layer_id (keys %{$fills->{$obj_idx}}) {
  345. my $layer = $object->layers->[$layer_id];
  346. foreach my $region_id (keys %{$fills->{$obj_idx}{$layer_id}}) {
  347. $layer->regions->[$region_id]->fills($fills->{$obj_idx}{$layer_id}{$region_id});
  348. }
  349. }
  350. }
  351. },
  352. no_threads_cb => sub {
  353. foreach my $layerm (map @{$_->regions}, map @{$_->layers}, @{$self->objects}) {
  354. $layerm->fills([ $fill_maker->make_fill($layerm) ]);
  355. }
  356. },
  357. );
  358. }
  359. # generate support material
  360. if ($self->has_support_material) {
  361. $status_cb->(85, "Generating support material");
  362. $_->generate_support_material for @{$self->objects};
  363. }
  364. # free memory (note that support material needs fill_surfaces)
  365. $_->fill_surfaces(undef) for map @{$_->regions}, map @{$_->layers}, @{$self->objects};
  366. # make skirt
  367. $status_cb->(88, "Generating skirt");
  368. $self->make_skirt;
  369. $self->make_brim; # must come after make_skirt
  370. # time to make some statistics
  371. if (0) {
  372. eval "use Devel::Size";
  373. print "MEMORY USAGE:\n";
  374. printf " meshes = %.1fMb\n", List::Util::sum(map Devel::Size::total_size($_->meshes), @{$self->objects})/1024/1024;
  375. printf " layer slices = %.1fMb\n", List::Util::sum(map Devel::Size::total_size($_->slices), map @{$_->layers}, @{$self->objects})/1024/1024;
  376. printf " region slices = %.1fMb\n", List::Util::sum(map Devel::Size::total_size($_->slices), map @{$_->regions}, map @{$_->layers}, @{$self->objects})/1024/1024;
  377. printf " perimeters = %.1fMb\n", List::Util::sum(map Devel::Size::total_size($_->perimeters), map @{$_->regions}, map @{$_->layers}, @{$self->objects})/1024/1024;
  378. printf " fills = %.1fMb\n", List::Util::sum(map Devel::Size::total_size($_->fills), map @{$_->regions}, map @{$_->layers}, @{$self->objects})/1024/1024;
  379. printf " print object = %.1fMb\n", Devel::Size::total_size($self)/1024/1024;
  380. }
  381. # output everything to a G-code file
  382. my $output_file = $self->expanded_output_filepath($params{output_file});
  383. $status_cb->(90, "Exporting G-code" . ($output_file ? " to $output_file" : ""));
  384. $self->write_gcode($params{output_fh} || $output_file);
  385. # run post-processing scripts
  386. if (@{$Slic3r::Config->post_process}) {
  387. $status_cb->(95, "Running post-processing scripts");
  388. $Slic3r::Config->setenv;
  389. for (@{$Slic3r::Config->post_process}) {
  390. Slic3r::debugf " '%s' '%s'\n", $_, $output_file;
  391. system($_, $output_file);
  392. }
  393. }
  394. # output some statistics
  395. unless ($params{quiet}) {
  396. $self->processing_time(tv_interval($t0));
  397. printf "Done. Process took %d minutes and %.3f seconds\n",
  398. int($self->processing_time/60),
  399. $self->processing_time - int($self->processing_time/60)*60;
  400. # TODO: more statistics!
  401. printf "Filament required: %.1fmm (%.1fcm3)\n",
  402. $self->total_extrusion_length, $self->total_extrusion_volume;
  403. }
  404. }
  405. sub export_svg {
  406. my $self = shift;
  407. my %params = @_;
  408. # this shouldn't be needed, but we're currently relying on ->make_surfaces() which
  409. # calls ->perimeter_flow
  410. $self->init_extruders;
  411. $_->slice(keep_meshes => $params{keep_meshes}) for @{$self->objects};
  412. $self->arrange_objects;
  413. my $output_file = $self->expanded_output_filepath($params{output_file});
  414. $output_file =~ s/\.gcode$/.svg/i;
  415. Slic3r::open(\my $fh, ">", $output_file) or die "Failed to open $output_file for writing\n";
  416. print "Exporting to $output_file...";
  417. my $print_size = $self->size;
  418. print $fh sprintf <<"EOF", unscale($print_size->[X]), unscale($print_size->[Y]);
  419. <?xml version="1.0" encoding="UTF-8" standalone="yes"?>
  420. <!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.0//EN" "http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd">
  421. <svg width="%s" height="%s" xmlns="http://www.w3.org/2000/svg" xmlns:svg="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:slic3r="http://slic3r.org/namespaces/slic3r">
  422. <!--
  423. Generated using Slic3r $Slic3r::VERSION
  424. http://slic3r.org/
  425. -->
  426. EOF
  427. my $print_polygon = sub {
  428. my ($polygon, $type) = @_;
  429. printf $fh qq{ <polygon slic3r:type="%s" points="%s" style="fill: %s" />\n},
  430. $type, (join ' ', map { join ',', map unscale $_, @$_ } @$polygon),
  431. ($type eq 'contour' ? 'white' : 'black');
  432. };
  433. my @previous_layer_slices = ();
  434. for my $layer_id (0..$self->layer_count-1) {
  435. my @layers = map $_->layers->[$layer_id], @{$self->objects};
  436. printf $fh qq{ <g id="layer%d" slic3r:z="%s">\n}, $layer_id, unscale +(grep defined $_, @layers)[0]->slice_z;
  437. my @current_layer_slices = ();
  438. for my $obj_idx (0 .. $#{$self->objects}) {
  439. my $layer = $self->objects->[$obj_idx]->layers->[$layer_id] or next;
  440. # sort slices so that the outermost ones come first
  441. my @slices = sort { $a->contour->encloses_point($b->contour->[0]) ? 0 : 1 } @{$layer->slices};
  442. foreach my $copy (@{$self->objects->[$obj_idx]->copies}) {
  443. foreach my $slice (@slices) {
  444. my $expolygon = $slice->clone;
  445. $expolygon->translate(@$copy);
  446. $print_polygon->($expolygon->contour, 'contour');
  447. $print_polygon->($_, 'hole') for $expolygon->holes;
  448. push @current_layer_slices, $expolygon;
  449. }
  450. }
  451. }
  452. # generate support material
  453. if ($self->has_support_material && $layer_id > 0) {
  454. my (@supported_slices, @unsupported_slices) = ();
  455. foreach my $expolygon (@current_layer_slices) {
  456. my $intersection = intersection_ex(
  457. [ map @$_, @previous_layer_slices ],
  458. $expolygon,
  459. );
  460. @$intersection
  461. ? push @supported_slices, $expolygon
  462. : push @unsupported_slices, $expolygon;
  463. }
  464. my @supported_points = map @$_, @$_, @supported_slices;
  465. foreach my $expolygon (@unsupported_slices) {
  466. # look for the nearest point to this island among all
  467. # supported points
  468. my $support_point = nearest_point($expolygon->contour->[0], \@supported_points)
  469. or next;
  470. my $anchor_point = nearest_point($support_point, $expolygon->contour);
  471. printf $fh qq{ <line x1="%s" y1="%s" x2="%s" y2="%s" style="stroke-width: 2; stroke: white" />\n},
  472. map @$_, $support_point, $anchor_point;
  473. }
  474. }
  475. print $fh qq{ </g>\n};
  476. @previous_layer_slices = @current_layer_slices;
  477. }
  478. print $fh "</svg>\n";
  479. close $fh;
  480. print "Done.\n";
  481. }
  482. sub make_skirt {
  483. my $self = shift;
  484. return unless $Slic3r::Config->skirts > 0;
  485. # collect points from all layers contained in skirt height
  486. my @points = ();
  487. foreach my $obj_idx (0 .. $#{$self->objects}) {
  488. my $skirt_height = $Slic3r::Config->skirt_height;
  489. $skirt_height = $self->objects->[$obj_idx]->layer_count if $skirt_height > $self->objects->[$obj_idx]->layer_count;
  490. my @layers = map $self->objects->[$obj_idx]->layers->[$_], 0..($skirt_height-1);
  491. my @layer_points = (
  492. (map @$_, map @$_, map @{$_->slices}, @layers),
  493. (map @$_, map @{$_->thin_walls}, map @{$_->regions}, @layers),
  494. (map @{$_->unpack->polyline}, map @{$_->support_fills->paths}, grep $_->support_fills, @layers),
  495. );
  496. push @points, map move_points($_, @layer_points), @{$self->objects->[$obj_idx]->copies};
  497. }
  498. return if @points < 3; # at least three points required for a convex hull
  499. # find out convex hull
  500. my $convex_hull = convex_hull(\@points);
  501. my @extruded_length = (); # for each extruder
  502. # TODO: use each extruder's own flow
  503. my $spacing = $self->objects->[0]->layers->[0]->regions->[0]->perimeter_flow->spacing;
  504. my $first_layer_height = $Slic3r::Config->get_value('first_layer_height');
  505. my @extruders_e_per_mm = ();
  506. my $extruder_idx = 0;
  507. # draw outlines from outside to inside
  508. # loop while we have less skirts than required or any extruder hasn't reached the min length if any
  509. my $distance = scale $Slic3r::Config->skirt_distance;
  510. for (my $i = $Slic3r::Config->skirts; $i > 0; $i--) {
  511. $distance += scale $spacing;
  512. my ($loop) = Slic3r::Geometry::Clipper::offset([$convex_hull], $distance, 0.0001, JT_ROUND);
  513. push @{$self->skirt}, Slic3r::ExtrusionLoop->pack(
  514. polygon => Slic3r::Polygon->new(@$loop),
  515. role => EXTR_ROLE_SKIRT,
  516. flow_spacing => $spacing,
  517. );
  518. if ($Slic3r::Config->min_skirt_length > 0) {
  519. bless $loop, 'Slic3r::Polygon';
  520. $extruded_length[$extruder_idx] ||= 0;
  521. $extruders_e_per_mm[$extruder_idx] ||= $self->extruders->[$extruder_idx]->e_per_mm($spacing, $first_layer_height);
  522. $extruded_length[$extruder_idx] += unscale $loop->length * $extruders_e_per_mm[$extruder_idx];
  523. $i++ if defined first { ($extruded_length[$_] // 0) < $Slic3r::Config->min_skirt_length } 0 .. $#{$self->extruders};
  524. if ($extruded_length[$extruder_idx] >= $Slic3r::Config->min_skirt_length) {
  525. if ($extruder_idx < $#{$self->extruders}) {
  526. $extruder_idx++;
  527. next;
  528. }
  529. }
  530. }
  531. }
  532. @{$self->skirt} = reverse @{$self->skirt};
  533. }
  534. sub make_brim {
  535. my $self = shift;
  536. return unless $Slic3r::Config->brim_width > 0;
  537. my $flow = $self->objects->[0]->layers->[0]->regions->[0]->perimeter_flow;
  538. my $grow_distance = $flow->scaled_width / 2;
  539. my @islands = (); # array of polygons
  540. foreach my $obj_idx (0 .. $#{$self->objects}) {
  541. my $layer0 = $self->objects->[$obj_idx]->layers->[0];
  542. my @object_islands = (
  543. (map $_->contour, @{$layer0->slices}),
  544. (map { $_->isa('Slic3r::Polygon') ? $_ : $_->grow($grow_distance) } map @{$_->thin_walls}, @{$layer0->regions}),
  545. (map $_->unpack->polyline->grow($grow_distance), map @{$_->support_fills->paths}, grep $_->support_fills, $layer0),
  546. );
  547. foreach my $copy (@{$self->objects->[$obj_idx]->copies}) {
  548. push @islands, map $_->clone->translate(@$copy), @object_islands;
  549. }
  550. }
  551. # if brim touches skirt, make it around skirt too
  552. # TODO: calculate actual skirt width (using each extruder's flow in multi-extruder setups)
  553. if ($Slic3r::Config->skirt_distance + (($Slic3r::Config->skirts - 1) * $flow->spacing) <= $Slic3r::Config->brim_width) {
  554. push @islands, map $_->unpack->split_at_first_point->polyline->grow($grow_distance), @{$self->skirt};
  555. }
  556. my $num_loops = sprintf "%.0f", $Slic3r::Config->brim_width / $flow->width;
  557. for my $i (reverse 1 .. $num_loops) {
  558. # JT_SQUARE ensures no vertex is outside the given offset distance
  559. push @{$self->brim}, Slic3r::ExtrusionLoop->pack(
  560. polygon => Slic3r::Polygon->new($_),
  561. role => EXTR_ROLE_SKIRT,
  562. flow_spacing => $flow->spacing,
  563. ) for Slic3r::Geometry::Clipper::offset(\@islands, ($i - 0.5) * $flow->scaled_spacing, undef, JT_SQUARE); # -0.5 because islands are not represented by their centerlines
  564. # TODO: we need the offset inwards/offset outwards logic to avoid overlapping extrusions
  565. }
  566. }
  567. sub write_gcode {
  568. my $self = shift;
  569. my ($file) = @_;
  570. # open output gcode file if we weren't supplied a file-handle
  571. my $fh;
  572. if (ref $file eq 'IO::Scalar') {
  573. $fh = $file;
  574. } else {
  575. Slic3r::open(\$fh, ">", $file)
  576. or die "Failed to open $file for writing\n";
  577. }
  578. # write some information
  579. my @lt = localtime;
  580. printf $fh "; generated by Slic3r $Slic3r::VERSION on %04d-%02d-%02d at %02d:%02d:%02d\n\n",
  581. $lt[5] + 1900, $lt[4]+1, $lt[3], $lt[2], $lt[1], $lt[0];
  582. print $fh "; $_\n" foreach split /\R/, $Slic3r::Config->notes;
  583. print $fh "\n" if $Slic3r::Config->notes;
  584. for (qw(layer_height perimeters top_solid_layers bottom_solid_layers fill_density perimeter_speed infill_speed travel_speed scale)) {
  585. printf $fh "; %s = %s\n", $_, $Slic3r::Config->$_;
  586. }
  587. for (qw(nozzle_diameter filament_diameter extrusion_multiplier)) {
  588. printf $fh "; %s = %s\n", $_, $Slic3r::Config->$_->[0];
  589. }
  590. printf $fh "; perimeters extrusion width = %.2fmm\n", $self->regions->[0]->flows->{perimeter}->width;
  591. printf $fh "; infill extrusion width = %.2fmm\n", $self->regions->[0]->flows->{infill}->width;
  592. printf $fh "; support material extrusion width = %.2fmm\n", $self->support_material_flow->width
  593. if $self->support_material_flow;
  594. printf $fh "; first layer extrusion width = %.2fmm\n", $self->regions->[0]->first_layer_flows->{perimeter}->width
  595. if $self->regions->[0]->first_layer_flows->{perimeter};
  596. print $fh "\n";
  597. # set up our extruder object
  598. my $gcodegen = Slic3r::GCode->new(
  599. multiple_extruders => (@{$self->extruders} > 1),
  600. layer_count => $self->layer_count,
  601. );
  602. my $min_print_speed = 60 * $Slic3r::Config->min_print_speed;
  603. my $dec = $gcodegen->dec;
  604. print $fh $gcodegen->set_fan(0, 1) if $Slic3r::Config->cooling && $Slic3r::Config->disable_fan_first_layers;
  605. # write start commands to file
  606. printf $fh $gcodegen->set_bed_temperature($Slic3r::Config->first_layer_bed_temperature, 1),
  607. if $Slic3r::Config->first_layer_bed_temperature && $Slic3r::Config->start_gcode !~ /M190/i;
  608. my $print_first_layer_temperature = sub {
  609. for my $t (grep $self->extruders->[$_], 0 .. $#{$Slic3r::Config->first_layer_temperature}) {
  610. printf $fh $gcodegen->set_temperature($self->extruders->[$t]->first_layer_temperature, 0, $t)
  611. if $self->extruders->[$t]->first_layer_temperature;
  612. }
  613. };
  614. $print_first_layer_temperature->();
  615. printf $fh "%s\n", $Slic3r::Config->replace_options($Slic3r::Config->start_gcode);
  616. for my $t (grep $self->extruders->[$_], 0 .. $#{$Slic3r::Config->first_layer_temperature}) {
  617. printf $fh $gcodegen->set_temperature($self->extruders->[$t]->first_layer_temperature, 1, $t)
  618. if $self->extruders->[$t]->first_layer_temperature && $Slic3r::Config->start_gcode !~ /M109/i;
  619. }
  620. print $fh "G90 ; use absolute coordinates\n";
  621. print $fh "G21 ; set units to millimeters\n";
  622. if ($Slic3r::Config->gcode_flavor =~ /^(?:reprap|teacup)$/) {
  623. printf $fh $gcodegen->reset_e;
  624. if ($Slic3r::Config->gcode_flavor =~ /^(?:reprap|makerbot|sailfish)$/) {
  625. if ($Slic3r::Config->use_relative_e_distances) {
  626. print $fh "M83 ; use relative distances for extrusion\n";
  627. } else {
  628. print $fh "M82 ; use absolute distances for extrusion\n";
  629. }
  630. }
  631. }
  632. # calculate X,Y shift to center print around specified origin
  633. my @print_bb = $self->bounding_box;
  634. my @shift = (
  635. $Slic3r::Config->print_center->[X] - (unscale ($print_bb[X2] - $print_bb[X1]) / 2) - unscale $print_bb[X1],
  636. $Slic3r::Config->print_center->[Y] - (unscale ($print_bb[Y2] - $print_bb[Y1]) / 2) - unscale $print_bb[Y1],
  637. );
  638. # initialize a motion planner for object-to-object travel moves
  639. if ($Slic3r::Config->avoid_crossing_perimeters) {
  640. my $distance_from_objects = 1;
  641. # compute the offsetted convex hull for each object and repeat it for each copy.
  642. my @islands = ();
  643. foreach my $obj_idx (0 .. $#{$self->objects}) {
  644. my $convex_hull = convex_hull([
  645. map @{$_->contour}, map @{$_->slices}, @{$self->objects->[$obj_idx]->layers},
  646. ]);
  647. # discard layers only containing thin walls (offset would fail on an empty polygon)
  648. if (@$convex_hull) {
  649. my @island = Slic3r::ExPolygon->new($convex_hull)
  650. ->translate(scale $shift[X], scale $shift[Y])
  651. ->offset_ex(scale $distance_from_objects, 1, JT_SQUARE);
  652. foreach my $copy (@{ $self->objects->[$obj_idx]->copies }) {
  653. push @islands, map $_->clone->translate(@$copy), @island;
  654. }
  655. }
  656. }
  657. $gcodegen->external_mp(Slic3r::GCode::MotionPlanner->new(
  658. islands => union_ex([ map @$_, @islands ]),
  659. no_internal => 1,
  660. ));
  661. }
  662. # prepare the logic to print one layer
  663. my $skirt_done = 0; # count of skirt layers done
  664. my $brim_done = 0;
  665. my $last_obj_copy = "";
  666. my $extrude_layer = sub {
  667. my ($layer_id, $object_copies) = @_;
  668. my $gcode = "";
  669. if ($layer_id == 1) {
  670. for my $t (grep $self->extruders->[$_], 0 .. $#{$Slic3r::Config->temperature}) {
  671. $gcode .= $gcodegen->set_temperature($self->extruders->[$t]->temperature, 0, $t)
  672. if $self->extruders->[$t]->temperature && $self->extruders->[$t]->temperature != $self->extruders->[$t]->first_layer_temperature;
  673. }
  674. $gcode .= $gcodegen->set_bed_temperature($Slic3r::Config->bed_temperature)
  675. if $Slic3r::Config->bed_temperature && $Slic3r::Config->bed_temperature != $Slic3r::Config->first_layer_bed_temperature;
  676. }
  677. # set new layer, but don't move Z as support material contact areas may need an intermediate one
  678. $gcode .= $gcodegen->change_layer($self->objects->[$object_copies->[0][0]]->layers->[$layer_id]);
  679. $gcodegen->elapsed_time(0);
  680. # prepare callback to call as soon as a Z command is generated
  681. $gcodegen->move_z_callback(sub {
  682. $gcodegen->move_z_callback(undef); # circular ref or not?
  683. return "" if !$Slic3r::Config->layer_gcode;
  684. return $Slic3r::Config->replace_options($Slic3r::Config->layer_gcode) . "\n";
  685. });
  686. # extrude skirt
  687. if ($skirt_done < $Slic3r::Config->skirt_height) {
  688. $gcodegen->set_shift(@shift);
  689. $gcode .= $gcodegen->set_extruder($self->extruders->[0]); # move_z requires extruder
  690. $gcode .= $gcodegen->move_z($gcodegen->layer->print_z);
  691. # skip skirt if we have a large brim
  692. if ($layer_id < $Slic3r::Config->skirt_height) {
  693. # distribute skirt loops across all extruders
  694. for my $i (0 .. $#{$self->skirt}) {
  695. # when printing layers > 0 ignore 'min_skirt_length' and
  696. # just use the 'skirts' setting; also just use the current extruder
  697. last if ($layer_id > 0) && ($i >= $Slic3r::Config->skirts);
  698. $gcode .= $gcodegen->set_extruder($self->extruders->[ ($i/@{$self->extruders}) % @{$self->extruders} ])
  699. if $layer_id == 0;
  700. $gcode .= $gcodegen->extrude_loop($self->skirt->[$i], 'skirt');
  701. }
  702. }
  703. $skirt_done++;
  704. $gcodegen->straight_once(1);
  705. }
  706. # extrude brim
  707. if ($layer_id == 0 && !$brim_done) {
  708. $gcode .= $gcodegen->set_extruder($self->extruders->[$Slic3r::Config->support_material_extruder-1]); # move_z requires extruder
  709. $gcode .= $gcodegen->move_z($gcodegen->layer->print_z);
  710. $gcodegen->set_shift(@shift);
  711. $gcode .= $gcodegen->extrude_loop($_, 'brim') for @{$self->brim};
  712. $brim_done = 1;
  713. $gcodegen->straight_once(1);
  714. }
  715. for my $obj_copy (@$object_copies) {
  716. my ($obj_idx, $copy) = @$obj_copy;
  717. $gcodegen->new_object(1) if $last_obj_copy && $last_obj_copy ne "${obj_idx}_${copy}";
  718. $last_obj_copy = "${obj_idx}_${copy}";
  719. my $layer = $self->objects->[$obj_idx]->layers->[$layer_id];
  720. $gcodegen->set_shift(map $shift[$_] + unscale $copy->[$_], X,Y);
  721. # extrude support material before other things because it might use a lower Z
  722. # and also because we avoid travelling on other things when printing it
  723. if ($self->has_support_material) {
  724. $gcode .= $gcodegen->move_z($layer->support_material_contact_z)
  725. if ($layer->support_contact_fills && @{ $layer->support_contact_fills->paths });
  726. $gcode .= $gcodegen->set_extruder($self->extruders->[$Slic3r::Config->support_material_extruder-1]);
  727. if ($layer->support_contact_fills) {
  728. $gcode .= $gcodegen->extrude_path($_, 'support material contact area')
  729. for $layer->support_contact_fills->chained_path($gcodegen->last_pos);
  730. }
  731. $gcode .= $gcodegen->move_z($layer->print_z);
  732. if ($layer->support_fills) {
  733. $gcode .= $gcodegen->extrude_path($_, 'support material')
  734. for $layer->support_fills->chained_path($gcodegen->last_pos);
  735. }
  736. }
  737. # set actual Z - this will force a retraction
  738. $gcode .= $gcodegen->move_z($layer->print_z);
  739. foreach my $region_id (0 .. ($self->regions_count-1)) {
  740. my $layerm = $layer->regions->[$region_id];
  741. my $region = $self->regions->[$region_id];
  742. # extrude perimeters
  743. if (@{ $layerm->perimeters }) {
  744. $gcode .= $gcodegen->set_extruder($region->extruders->{perimeter});
  745. $gcode .= $gcodegen->set_acceleration($Slic3r::Config->perimeter_acceleration);
  746. $gcode .= $gcodegen->extrude($_, 'perimeter') for @{ $layerm->perimeters };
  747. $gcode .= $gcodegen->set_acceleration($Slic3r::Config->default_acceleration)
  748. if $Slic3r::Config->perimeter_acceleration;
  749. }
  750. # extrude fills
  751. if (@{ $layerm->fills }) {
  752. $gcode .= $gcodegen->set_extruder($region->extruders->{infill});
  753. $gcode .= $gcodegen->set_acceleration($Slic3r::Config->infill_acceleration);
  754. for my $fill (@{ $layerm->fills }) {
  755. if ($fill->isa('Slic3r::ExtrusionPath::Collection')) {
  756. $gcode .= $gcodegen->extrude($_, 'fill')
  757. for $fill->chained_path($gcodegen->last_pos);
  758. } else {
  759. $gcode .= $gcodegen->extrude($fill, 'fill') ;
  760. }
  761. }
  762. $gcode .= $gcodegen->set_acceleration($Slic3r::Config->default_acceleration)
  763. if $Slic3r::Config->infill_acceleration;
  764. }
  765. }
  766. }
  767. return if !$gcode;
  768. my $fan_speed = $Slic3r::Config->fan_always_on ? $Slic3r::Config->min_fan_speed : 0;
  769. my $speed_factor = 1;
  770. if ($Slic3r::Config->cooling) {
  771. my $layer_time = $gcodegen->elapsed_time;
  772. Slic3r::debugf "Layer %d estimated printing time: %d seconds\n", $layer_id, $layer_time;
  773. if ($layer_time < $Slic3r::Config->slowdown_below_layer_time) {
  774. $fan_speed = $Slic3r::Config->max_fan_speed;
  775. $speed_factor = $layer_time / $Slic3r::Config->slowdown_below_layer_time;
  776. } elsif ($layer_time < $Slic3r::Config->fan_below_layer_time) {
  777. $fan_speed = $Slic3r::Config->max_fan_speed - ($Slic3r::Config->max_fan_speed - $Slic3r::Config->min_fan_speed)
  778. * ($layer_time - $Slic3r::Config->slowdown_below_layer_time)
  779. / ($Slic3r::Config->fan_below_layer_time - $Slic3r::Config->slowdown_below_layer_time); #/
  780. }
  781. Slic3r::debugf " fan = %d%%, speed = %d%%\n", $fan_speed, $speed_factor * 100;
  782. if ($speed_factor < 1) {
  783. $gcode =~ s/^(?=.*? [XY])(?=.*? E)(?<!;_BRIDGE_FAN_START\n)(G1 .*?F)(\d+(?:\.\d+)?)/
  784. my $new_speed = $2 * $speed_factor;
  785. $1 . sprintf("%.${dec}f", $new_speed < $min_print_speed ? $min_print_speed : $new_speed)
  786. /gexm;
  787. }
  788. $fan_speed = 0 if $layer_id < $Slic3r::Config->disable_fan_first_layers;
  789. }
  790. $gcode = $gcodegen->set_fan($fan_speed) . $gcode;
  791. # bridge fan speed
  792. if (!$Slic3r::Config->cooling || $Slic3r::Config->bridge_fan_speed == 0 || $layer_id < $Slic3r::Config->disable_fan_first_layers) {
  793. $gcode =~ s/^;_BRIDGE_FAN_(?:START|END)\n//gm;
  794. } else {
  795. $gcode =~ s/^;_BRIDGE_FAN_START\n/ $gcodegen->set_fan($Slic3r::Config->bridge_fan_speed, 1) /gmex;
  796. $gcode =~ s/^;_BRIDGE_FAN_END\n/ $gcodegen->set_fan($fan_speed, 1) /gmex;
  797. }
  798. return $gcode;
  799. };
  800. # do all objects for each layer
  801. if ($Slic3r::Config->complete_objects) {
  802. # print objects from the smallest to the tallest to avoid collisions
  803. # when moving onto next object starting point
  804. my @obj_idx = sort { $self->objects->[$a]->layer_count <=> $self->objects->[$b]->layer_count } 0..$#{$self->objects};
  805. my $finished_objects = 0;
  806. for my $obj_idx (@obj_idx) {
  807. for my $copy (@{ $self->objects->[$obj_idx]->copies }) {
  808. # move to the origin position for the copy we're going to print.
  809. # this happens before Z goes down to layer 0 again, so that
  810. # no collision happens hopefully.
  811. if ($finished_objects > 0) {
  812. $gcodegen->set_shift(map $shift[$_] + unscale $copy->[$_], X,Y);
  813. print $fh $gcodegen->retract;
  814. print $fh $gcodegen->G0(Slic3r::Point->new(0,0), undef, 0, 'move to origin position for next object');
  815. }
  816. for my $layer_id (0..$#{$self->objects->[$obj_idx]->layers}) {
  817. # if we are printing the bottom layer of an object, and we have already finished
  818. # another one, set first layer temperatures. this happens before the Z move
  819. # is triggered, so machine has more time to reach such temperatures
  820. if ($layer_id == 0 && $finished_objects > 0) {
  821. printf $fh $gcodegen->set_bed_temperature($Slic3r::Config->first_layer_bed_temperature),
  822. if $Slic3r::Config->first_layer_bed_temperature;
  823. $print_first_layer_temperature->();
  824. }
  825. print $fh $extrude_layer->($layer_id, [[ $obj_idx, $copy ]]);
  826. }
  827. $finished_objects++;
  828. }
  829. }
  830. } else {
  831. for my $layer_id (0..$self->layer_count-1) {
  832. my @object_copies = ();
  833. for my $obj_idx (grep $self->objects->[$_]->layers->[$layer_id], 0..$#{$self->objects}) {
  834. push @object_copies, map [ $obj_idx, $_ ], @{ $self->objects->[$obj_idx]->copies };
  835. }
  836. print $fh $extrude_layer->($layer_id, \@object_copies);
  837. }
  838. }
  839. # save statistic data
  840. $self->total_extrusion_length($gcodegen->total_extrusion_length);
  841. # write end commands to file
  842. print $fh $gcodegen->retract;
  843. print $fh $gcodegen->set_fan(0);
  844. printf $fh "%s\n", $Slic3r::Config->replace_options($Slic3r::Config->end_gcode);
  845. printf $fh "; filament used = %.1fmm (%.1fcm3)\n",
  846. $self->total_extrusion_length, $self->total_extrusion_volume;
  847. if ($Slic3r::Config->gcode_comments) {
  848. # append full config
  849. print $fh "\n";
  850. foreach my $opt_key (sort keys %{$Slic3r::Config}) {
  851. next if $Slic3r::Config::Options->{$opt_key}{shortcut};
  852. next if $Slic3r::Config::Options->{$opt_key}{gui_only};
  853. printf $fh "; %s = %s\n", $opt_key, $Slic3r::Config->serialize($opt_key);
  854. }
  855. }
  856. # close our gcode file
  857. close $fh;
  858. }
  859. sub total_extrusion_volume {
  860. my $self = shift;
  861. return $self->total_extrusion_length * ($self->extruders->[0]->filament_diameter**2) * PI/4 / 1000;
  862. }
  863. # this method will return the supplied input file path after expanding its
  864. # format variables with their values
  865. sub expanded_output_filepath {
  866. my $self = shift;
  867. my ($path, $input_file) = @_;
  868. # if no input file was supplied, take the first one from our objects
  869. $input_file ||= $self->objects->[0]->input_file;
  870. return undef if !defined $input_file;
  871. # if output path is an existing directory, we take that and append
  872. # the specified filename format
  873. $path = File::Spec->join($path, $Slic3r::Config->output_filename_format) if ($path && -d $path);
  874. # if no explicit output file was defined, we take the input
  875. # file directory and append the specified filename format
  876. $path ||= (fileparse($input_file))[1] . $Slic3r::Config->output_filename_format;
  877. my $input_filename = my $input_filename_base = basename($input_file);
  878. $input_filename_base =~ s/\.(?:stl|amf(?:\.xml)?)$//i;
  879. return $Slic3r::Config->replace_options($path, {
  880. input_filename => $input_filename,
  881. input_filename_base => $input_filename_base,
  882. %{ $self->extra_variables },
  883. });
  884. }
  885. 1;