Print.pm 43 KB

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