Print.pm 47 KB

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