Print.pm 43 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051
  1. package Slic3r::Print;
  2. use strict;
  3. use warnings;
  4. use File::Basename qw(basename fileparse);
  5. use File::Spec;
  6. use List::Util qw(min max first sum);
  7. use Slic3r::ExtrusionPath ':roles';
  8. use Slic3r::Flow ':roles';
  9. use Slic3r::Geometry qw(X Y Z X1 Y1 X2 Y2 MIN MAX PI scale unscale move_points chained_path
  10. convex_hull);
  11. use Slic3r::Geometry::Clipper qw(diff_ex union_ex union_pt intersection_ex intersection offset
  12. offset2 union union_pt_chained JT_ROUND JT_SQUARE);
  13. use Slic3r::Print::State ':steps';
  14. our $status_cb;
  15. sub new {
  16. # TODO: port PlaceholderParser methods to C++, then its own constructor
  17. # can call them and no need for this new() method at all
  18. my ($class) = @_;
  19. my $self = $class->_new;
  20. $self->placeholder_parser->apply_env_variables;
  21. $self->placeholder_parser->update_timestamp;
  22. return $self;
  23. }
  24. sub set_status_cb {
  25. my ($class, $cb) = @_;
  26. $status_cb = $cb;
  27. }
  28. sub status_cb {
  29. return $status_cb // sub {};
  30. }
  31. sub apply_config {
  32. my ($self, $config) = @_;
  33. $config = $config->clone;
  34. $config->normalize;
  35. # apply variables to placeholder parser
  36. $self->placeholder_parser->apply_config($config);
  37. my $invalidated = 0;
  38. # handle changes to print config
  39. my $print_diff = $self->config->diff($config);
  40. if (@$print_diff) {
  41. $self->config->apply_dynamic($config);
  42. $invalidated = 1
  43. if $self->invalidate_state_by_config_options($print_diff);
  44. }
  45. # handle changes to object config defaults
  46. $self->default_object_config->apply_dynamic($config);
  47. foreach my $object (@{$self->objects}) {
  48. # we don't assume that $config contains a full ObjectConfig,
  49. # so we base it on the current print-wise default
  50. my $new = $self->default_object_config->clone;
  51. $new->apply_dynamic($config);
  52. # we override the new config with object-specific options
  53. my $model_object_config = $object->model_object->config->clone;
  54. $model_object_config->normalize;
  55. $new->apply_dynamic($model_object_config);
  56. # check whether the new config is different from the current one
  57. my $diff = $object->config->diff($new);
  58. if (@$diff) {
  59. $object->config->apply($new);
  60. $invalidated = 1
  61. if $object->invalidate_state_by_config_options($diff);
  62. }
  63. }
  64. # handle changes to regions config defaults
  65. $self->default_region_config->apply_dynamic($config);
  66. # All regions now have distinct settings.
  67. # Check whether applying the new region config defaults we'd get different regions.
  68. my $rearrange_regions = 0;
  69. my @other_region_configs = ();
  70. REGION: foreach my $region_id (0..($self->region_count - 1)) {
  71. my $region = $self->regions->[$region_id];
  72. my @this_region_configs = ();
  73. foreach my $object (@{$self->objects}) {
  74. foreach my $volume_id (@{ $object->get_region_volumes($region_id) }) {
  75. my $volume = $object->model_object->volumes->[$volume_id];
  76. my $new = $self->default_region_config->clone;
  77. {
  78. my $model_object_config = $object->model_object->config->clone;
  79. $model_object_config->normalize;
  80. $new->apply_dynamic($model_object_config);
  81. }
  82. if (defined $volume->material_id) {
  83. my $material_config = $object->model_object->model->get_material($volume->material_id)->config->clone;
  84. $material_config->normalize;
  85. $new->apply_dynamic($material_config);
  86. }
  87. if (defined first { !$_->equals($new) } @this_region_configs) {
  88. # if the new config for this volume differs from the other
  89. # volume configs currently associated to this region, it means
  90. # the region subdivision does not make sense anymore
  91. $rearrange_regions = 1;
  92. last REGION;
  93. }
  94. push @this_region_configs, $new;
  95. if (defined first { $_->equals($new) } @other_region_configs) {
  96. # if the new config for this volume equals any of the other
  97. # volume configs that are not currently associated to this
  98. # region, it means the region subdivision does not make
  99. # sense anymore
  100. $rearrange_regions = 1;
  101. last REGION;
  102. }
  103. # if we're here and the new region config is different from the old
  104. # one, we need to apply the new config and invalidate all objects
  105. # (possible optimization: only invalidate objects using this region)
  106. my $region_config_diff = $region->config->diff($new);
  107. if (@$region_config_diff) {
  108. $region->config->apply($new);
  109. foreach my $o (@{$self->objects}) {
  110. $invalidated = 1
  111. if $o->invalidate_state_by_config_options($region_config_diff);
  112. }
  113. }
  114. }
  115. }
  116. push @other_region_configs, @this_region_configs;
  117. }
  118. if ($rearrange_regions) {
  119. # the current subdivision of regions does not make sense anymore.
  120. # we need to remove all objects and re-add them
  121. my @model_objects = map $_->model_object, @{$self->objects};
  122. $self->clear_objects;
  123. $self->add_model_object($_) for @model_objects;
  124. $invalidated = 1;
  125. }
  126. return $invalidated;
  127. }
  128. sub has_support_material {
  129. my $self = shift;
  130. return (first { $_->config->support_material } @{$self->objects})
  131. || (first { $_->config->raft_layers > 0 } @{$self->objects})
  132. || (first { $_->config->support_material_enforce_layers > 0 } @{$self->objects});
  133. }
  134. # caller is responsible for supplying models whose objects don't collide
  135. # and have explicit instance positions
  136. sub add_model_object {
  137. my $self = shift;
  138. my ($object, $obj_idx) = @_;
  139. my $object_config = $object->config->clone;
  140. $object_config->normalize;
  141. # initialize print object and store it at the given position
  142. my $o;
  143. if (defined $obj_idx) {
  144. $o = $self->set_new_object($obj_idx, $object, $object->raw_bounding_box);
  145. } else {
  146. $o = $self->add_object($object, $object->raw_bounding_box);
  147. }
  148. $o->set_copies([ map Slic3r::Point->new_scale(@{ $_->offset }), @{ $object->instances } ]);
  149. $o->set_layer_height_ranges($object->layer_height_ranges);
  150. # TODO: translate _trigger_copies to C++, then this can be done by
  151. # PrintObject constructor
  152. $o->_trigger_copies;
  153. foreach my $volume_id (0..$#{$object->volumes}) {
  154. my $volume = $object->volumes->[$volume_id];
  155. # get the config applied to this volume: start from our global defaults
  156. my $config = Slic3r::Config::PrintRegion->new;
  157. $config->apply($self->default_region_config);
  158. # override the defaults with per-object config and then with per-material config
  159. $config->apply_dynamic($object_config);
  160. if (defined $volume->material_id) {
  161. my $material_config = $volume->material->config->clone;
  162. $material_config->normalize;
  163. $config->apply_dynamic($material_config);
  164. }
  165. # find an existing print region with the same config
  166. my $region_id;
  167. foreach my $i (0..($self->region_count - 1)) {
  168. my $region = $self->regions->[$i];
  169. if ($config->equals($region->config)) {
  170. $region_id = $i;
  171. last;
  172. }
  173. }
  174. # if no region exists with the same config, create a new one
  175. if (!defined $region_id) {
  176. my $r = $self->add_region();
  177. $r->config->apply($config);
  178. $region_id = $self->region_count - 1;
  179. }
  180. # assign volume to region
  181. $o->add_region_volume($region_id, $volume_id);
  182. }
  183. # apply config to print object
  184. $o->config->apply($self->default_object_config);
  185. $o->config->apply_dynamic($object_config);
  186. }
  187. sub reload_object {
  188. my ($self, $obj_idx) = @_;
  189. # TODO: this method should check whether the per-object config and per-material configs
  190. # have changed in such a way that regions need to be rearranged or we can just apply
  191. # the diff and invalidate something. Same logic as apply_config()
  192. # For now we just re-add all objects since we haven't implemented this incremental logic yet.
  193. # This should also check whether object volumes (parts) have changed.
  194. my @models_objects = map $_->model_object, @{$self->objects};
  195. $self->clear_objects;
  196. $self->add_model_object($_) for @models_objects;
  197. }
  198. sub validate {
  199. my $self = shift;
  200. if ($self->config->complete_objects) {
  201. # check horizontal clearance
  202. {
  203. my @a = ();
  204. foreach my $object (@{$self->objects}) {
  205. # get convex hulls of all meshes assigned to this print object
  206. my @mesh_convex_hulls = map $object->model_object->volumes->[$_]->mesh->convex_hull,
  207. map @$_,
  208. grep defined $_,
  209. @{$object->region_volumes};
  210. # make a single convex hull for all of them
  211. my $convex_hull = convex_hull([ map @$_, @mesh_convex_hulls ]);
  212. # apply the same transformations we apply to the actual meshes when slicing them
  213. $object->model_object->instances->[0]->transform_polygon($convex_hull, 1);
  214. # align object to Z = 0 and apply XY shift
  215. $convex_hull->translate(@{$object->_copies_shift});
  216. # grow convex hull with the clearance margin
  217. ($convex_hull) = @{offset([$convex_hull], scale $self->config->extruder_clearance_radius / 2, 1, JT_ROUND, scale(0.1))};
  218. # now we need that no instance of $convex_hull does not intersect any of the previously checked object instances
  219. for my $copy (@{$object->_shifted_copies}) {
  220. my $p = $convex_hull->clone;
  221. $p->translate(@$copy);
  222. if (@{ intersection(\@a, [$p]) }) {
  223. die "Some objects are too close; your extruder will collide with them.\n";
  224. }
  225. @a = @{union([@a, $p])};
  226. }
  227. }
  228. }
  229. # check vertical clearance
  230. {
  231. my @object_height = ();
  232. foreach my $object (@{$self->objects}) {
  233. my $height = $object->size->z;
  234. push @object_height, $height for @{$object->copies};
  235. }
  236. @object_height = sort { $a <=> $b } @object_height;
  237. # ignore the tallest *copy* (this is why we repeat height for all of them):
  238. # it will be printed as last one so its height doesn't matter
  239. pop @object_height;
  240. if (@object_height && max(@object_height) > scale $self->config->extruder_clearance_height) {
  241. die "Some objects are too tall and cannot be printed without extruder collisions.\n";
  242. }
  243. }
  244. }
  245. if ($self->config->spiral_vase) {
  246. if ((map @{$_->copies}, @{$self->objects}) > 1) {
  247. die "The Spiral Vase option can only be used when printing a single object.\n";
  248. }
  249. if (@{$self->regions} > 1) {
  250. die "The Spiral Vase option can only be used when printing single material objects.\n";
  251. }
  252. }
  253. }
  254. # 0-based indices of used extruders
  255. sub extruders {
  256. my ($self) = @_;
  257. # initialize all extruder(s) we need
  258. my @used_extruders = ();
  259. foreach my $region (@{$self->regions}) {
  260. push @used_extruders,
  261. map $region->config->get("${_}_extruder")-1,
  262. qw(perimeter infill);
  263. }
  264. foreach my $object (@{$self->objects}) {
  265. push @used_extruders,
  266. map $object->config->get("${_}_extruder")-1,
  267. qw(support_material support_material_interface);
  268. }
  269. my %h = map { $_ => 1 } @used_extruders;
  270. return [ sort keys %h ];
  271. }
  272. sub init_extruders {
  273. my $self = shift;
  274. return if $self->step_done(STEP_INIT_EXTRUDERS);
  275. $self->set_step_started(STEP_INIT_EXTRUDERS);
  276. # enforce tall skirt if using ooze_prevention
  277. # FIXME: this is not idempotent (i.e. switching ooze_prevention off will not revert skirt settings)
  278. if ($self->config->ooze_prevention && @{$self->extruders} > 1) {
  279. $self->config->set('skirt_height', -1);
  280. $self->config->set('skirts', 1) if $self->config->skirts == 0;
  281. }
  282. $self->set_step_done(STEP_INIT_EXTRUDERS);
  283. }
  284. # this value is not supposed to be compared with $layer->id
  285. # since they have different semantics
  286. sub total_layer_count {
  287. my $self = shift;
  288. return max(map $_->total_layer_count, @{$self->objects});
  289. }
  290. sub regions_count {
  291. my $self = shift;
  292. return scalar @{$self->regions};
  293. }
  294. sub bounding_box {
  295. my $self = shift;
  296. my @points = ();
  297. foreach my $object (@{$self->objects}) {
  298. foreach my $copy (@{$object->_shifted_copies}) {
  299. push @points,
  300. [ $copy->[X], $copy->[Y] ],
  301. [ $copy->[X] + $object->size->[X], $copy->[Y] + $object->size->[Y] ];
  302. }
  303. }
  304. return Slic3r::Geometry::BoundingBox->new_from_points([ map Slic3r::Point->new(@$_), @points ]);
  305. }
  306. sub size {
  307. my $self = shift;
  308. return $self->bounding_box->size;
  309. }
  310. sub _simplify_slices {
  311. my $self = shift;
  312. my ($distance) = @_;
  313. foreach my $layer (map @{$_->layers}, @{$self->objects}) {
  314. $layer->slices->simplify($distance);
  315. $_->slices->simplify($distance) for @{$layer->regions};
  316. }
  317. }
  318. sub process {
  319. my ($self) = @_;
  320. $_->make_perimeters for @{$self->objects};
  321. $_->infill for @{$self->objects};
  322. $_->generate_support_material for @{$self->objects};
  323. $self->make_skirt;
  324. $self->make_brim; # must come after make_skirt
  325. # time to make some statistics
  326. if (0) {
  327. eval "use Devel::Size";
  328. print "MEMORY USAGE:\n";
  329. printf " meshes = %.1fMb\n", List::Util::sum(map Devel::Size::total_size($_->meshes), @{$self->objects})/1024/1024;
  330. printf " layer slices = %.1fMb\n", List::Util::sum(map Devel::Size::total_size($_->slices), map @{$_->layers}, @{$self->objects})/1024/1024;
  331. printf " region slices = %.1fMb\n", List::Util::sum(map Devel::Size::total_size($_->slices), map @{$_->regions}, map @{$_->layers}, @{$self->objects})/1024/1024;
  332. printf " perimeters = %.1fMb\n", List::Util::sum(map Devel::Size::total_size($_->perimeters), map @{$_->regions}, map @{$_->layers}, @{$self->objects})/1024/1024;
  333. printf " fills = %.1fMb\n", List::Util::sum(map Devel::Size::total_size($_->fills), map @{$_->regions}, map @{$_->layers}, @{$self->objects})/1024/1024;
  334. printf " print object = %.1fMb\n", Devel::Size::total_size($self)/1024/1024;
  335. }
  336. if (0) {
  337. eval "use Slic3r::Test::SectionCut";
  338. Slic3r::Test::SectionCut->new(print => $self)->export_svg("section_cut.svg");
  339. }
  340. }
  341. sub export_gcode {
  342. my $self = shift;
  343. my %params = @_;
  344. # prerequisites
  345. $self->process;
  346. # output everything to a G-code file
  347. my $output_file = $self->expanded_output_filepath($params{output_file});
  348. $self->status_cb->(90, "Exporting G-code" . ($output_file ? " to $output_file" : ""));
  349. $self->write_gcode($params{output_fh} || $output_file);
  350. # run post-processing scripts
  351. if (@{$self->config->post_process}) {
  352. $self->status_cb->(95, "Running post-processing scripts");
  353. $self->config->setenv;
  354. for (@{$self->config->post_process}) {
  355. Slic3r::debugf " '%s' '%s'\n", $_, $output_file;
  356. system($_, $output_file);
  357. }
  358. }
  359. }
  360. sub export_svg {
  361. my $self = shift;
  362. my %params = @_;
  363. # is this needed?
  364. $self->init_extruders;
  365. $_->slice for @{$self->objects};
  366. my $fh = $params{output_fh};
  367. if (!$fh) {
  368. my $output_file = $self->expanded_output_filepath($params{output_file});
  369. $output_file =~ s/\.gcode$/.svg/i;
  370. Slic3r::open(\$fh, ">", $output_file) or die "Failed to open $output_file for writing\n";
  371. print "Exporting to $output_file..." unless $params{quiet};
  372. }
  373. my $print_size = $self->size;
  374. print $fh sprintf <<"EOF", unscale($print_size->[X]), unscale($print_size->[Y]);
  375. <?xml version="1.0" encoding="UTF-8" standalone="yes"?>
  376. <!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.0//EN" "http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd">
  377. <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">
  378. <!--
  379. Generated using Slic3r $Slic3r::VERSION
  380. http://slic3r.org/
  381. -->
  382. EOF
  383. my $print_polygon = sub {
  384. my ($polygon, $type) = @_;
  385. printf $fh qq{ <polygon slic3r:type="%s" points="%s" style="fill: %s" />\n},
  386. $type, (join ' ', map { join ',', map unscale $_, @$_ } @$polygon),
  387. ($type eq 'contour' ? 'white' : 'black');
  388. };
  389. my @layers = sort { $a->print_z <=> $b->print_z }
  390. map { @{$_->layers}, @{$_->support_layers} }
  391. @{$self->objects};
  392. my $layer_id = -1;
  393. my @previous_layer_slices = ();
  394. for my $layer (@layers) {
  395. $layer_id++;
  396. # TODO: remove slic3r:z for raft layers
  397. printf $fh qq{ <g id="layer%d" slic3r:z="%s">\n}, $layer_id, unscale($layer->slice_z);
  398. my @current_layer_slices = ();
  399. # sort slices so that the outermost ones come first
  400. my @slices = sort { $a->contour->contains_point($b->contour->[0]) ? 0 : 1 } @{$layer->slices};
  401. foreach my $copy (@{$layer->object->copies}) {
  402. foreach my $slice (@slices) {
  403. my $expolygon = $slice->clone;
  404. $expolygon->translate(@$copy);
  405. $print_polygon->($expolygon->contour, 'contour');
  406. $print_polygon->($_, 'hole') for @{$expolygon->holes};
  407. push @current_layer_slices, $expolygon;
  408. }
  409. }
  410. # generate support material
  411. if ($self->has_support_material && $layer->id > 0) {
  412. my (@supported_slices, @unsupported_slices) = ();
  413. foreach my $expolygon (@current_layer_slices) {
  414. my $intersection = intersection_ex(
  415. [ map @$_, @previous_layer_slices ],
  416. $expolygon,
  417. );
  418. @$intersection
  419. ? push @supported_slices, $expolygon
  420. : push @unsupported_slices, $expolygon;
  421. }
  422. my @supported_points = map @$_, @$_, @supported_slices;
  423. foreach my $expolygon (@unsupported_slices) {
  424. # look for the nearest point to this island among all
  425. # supported points
  426. my $contour = $expolygon->contour;
  427. my $support_point = $contour->first_point->nearest_point(\@supported_points)
  428. or next;
  429. my $anchor_point = $support_point->nearest_point([ @$contour ]);
  430. printf $fh qq{ <line x1="%s" y1="%s" x2="%s" y2="%s" style="stroke-width: 2; stroke: white" />\n},
  431. map @$_, $support_point, $anchor_point;
  432. }
  433. }
  434. print $fh qq{ </g>\n};
  435. @previous_layer_slices = @current_layer_slices;
  436. }
  437. print $fh "</svg>\n";
  438. close $fh;
  439. print "Done.\n" unless $params{quiet};
  440. }
  441. sub make_skirt {
  442. my $self = shift;
  443. # prerequisites
  444. $_->make_perimeters for @{$self->objects};
  445. $_->infill for @{$self->objects};
  446. $_->generate_support_material for @{$self->objects};
  447. return if $self->step_done(STEP_SKIRT);
  448. $self->set_step_started(STEP_SKIRT);
  449. # since this method must be idempotent, we clear skirt paths *before*
  450. # checking whether we need to generate them
  451. $self->skirt->clear;
  452. if ($self->config->skirts == 0
  453. && (!$self->config->ooze_prevention || @{$self->extruders} == 1)) {
  454. $self->set_step_done(STEP_SKIRT);
  455. return;
  456. }
  457. $self->status_cb->(88, "Generating skirt");
  458. # First off we need to decide how tall the skirt must be.
  459. # The skirt_height option from config is expressed in layers, but our
  460. # object might have different layer heights, so we need to find the print_z
  461. # of the highest layer involved.
  462. # Note that unless skirt_height == -1 (which means it's printed on all layers)
  463. # the actual skirt might not reach this $skirt_height_z value since the print
  464. # order of objects on each layer is not guaranteed and will not generally
  465. # include the thickest object first. It is just guaranteed that a skirt is
  466. # prepended to the first 'n' layers (with 'n' = skirt_height).
  467. # $skirt_height_z in this case is the highest possible skirt height for safety.
  468. my $skirt_height_z = -1;
  469. foreach my $object (@{$self->objects}) {
  470. my $skirt_height = ($self->config->skirt_height == -1)
  471. ? scalar(@{$object->layers})
  472. : min($self->config->skirt_height, scalar(@{$object->layers}));
  473. my $highest_layer = $object->get_layer($skirt_height - 1);
  474. $skirt_height_z = max($skirt_height_z, $highest_layer->print_z);
  475. }
  476. # collect points from all layers contained in skirt height
  477. my @points = ();
  478. foreach my $object (@{$self->objects}) {
  479. my @object_points = ();
  480. # get object layers up to $skirt_height_z
  481. foreach my $layer (@{$object->layers}) {
  482. last if $layer->print_z > $skirt_height_z;
  483. push @object_points, map @$_, map @$_, @{$layer->slices};
  484. }
  485. # get support layers up to $skirt_height_z
  486. foreach my $layer (@{$object->support_layers}) {
  487. last if $layer->print_z > $skirt_height_z;
  488. push @object_points, map @{$_->polyline}, @{$layer->support_fills} if $layer->support_fills;
  489. push @object_points, map @{$_->polyline}, @{$layer->support_interface_fills} if $layer->support_interface_fills;
  490. }
  491. # repeat points for each object copy
  492. foreach my $copy (@{$object->_shifted_copies}) {
  493. my @copy_points = map $_->clone, @object_points;
  494. $_->translate(@$copy) for @copy_points;
  495. push @points, @copy_points;
  496. }
  497. }
  498. return if @points < 3; # at least three points required for a convex hull
  499. # find out convex hull
  500. my $convex_hull = convex_hull(\@points);
  501. my @extruded_length = (); # for each extruder
  502. # skirt may be printed on several layers, having distinct layer heights,
  503. # but loops must be aligned so can't vary width/spacing
  504. # TODO: use each extruder's own flow
  505. my $first_layer_height = $self->objects->[0]->config->get_value('first_layer_height');
  506. my $flow = Slic3r::Flow->new_from_width(
  507. width => ($self->config->first_layer_extrusion_width || $self->regions->[0]->config->perimeter_extrusion_width),
  508. role => FLOW_ROLE_PERIMETER,
  509. nozzle_diameter => $self->config->nozzle_diameter->[0],
  510. layer_height => $first_layer_height,
  511. bridge_flow_ratio => 0,
  512. );
  513. my $spacing = $flow->spacing;
  514. my $mm3_per_mm = $flow->mm3_per_mm;
  515. my @extruders_e_per_mm = ();
  516. my $extruder_idx = 0;
  517. # draw outlines from outside to inside
  518. # loop while we have less skirts than required or any extruder hasn't reached the min length if any
  519. my $distance = scale $self->config->skirt_distance;
  520. for (my $i = $self->config->skirts; $i > 0; $i--) {
  521. $distance += scale $spacing;
  522. my $loop = offset([$convex_hull], $distance, 1, JT_ROUND, scale(0.1))->[0];
  523. $self->skirt->append(Slic3r::ExtrusionLoop->new_from_paths(
  524. Slic3r::ExtrusionPath->new(
  525. polyline => Slic3r::Polygon->new(@$loop)->split_at_first_point,
  526. role => EXTR_ROLE_SKIRT,
  527. mm3_per_mm => $mm3_per_mm,
  528. width => $flow->width,
  529. height => $first_layer_height,
  530. ),
  531. ));
  532. if ($self->config->min_skirt_length > 0) {
  533. $extruded_length[$extruder_idx] ||= 0;
  534. if (!$extruders_e_per_mm[$extruder_idx]) {
  535. my $extruder = Slic3r::Extruder->new($extruder_idx, $self->config);
  536. $extruders_e_per_mm[$extruder_idx] = $extruder->e_per_mm($mm3_per_mm);
  537. }
  538. $extruded_length[$extruder_idx] += unscale $loop->length * $extruders_e_per_mm[$extruder_idx];
  539. $i++ if defined first { ($extruded_length[$_] // 0) < $self->config->min_skirt_length } 0 .. $#{$self->extruders};
  540. if ($extruded_length[$extruder_idx] >= $self->config->min_skirt_length) {
  541. if ($extruder_idx < $#{$self->extruders}) {
  542. $extruder_idx++;
  543. next;
  544. }
  545. }
  546. }
  547. }
  548. $self->skirt->reverse;
  549. $self->set_step_done(STEP_SKIRT);
  550. }
  551. sub make_brim {
  552. my $self = shift;
  553. # prerequisites
  554. $_->make_perimeters for @{$self->objects};
  555. $_->infill for @{$self->objects};
  556. $_->generate_support_material for @{$self->objects};
  557. $self->make_skirt;
  558. return if $self->step_done(STEP_BRIM);
  559. $self->set_step_started(STEP_BRIM);
  560. # since this method must be idempotent, we clear brim paths *before*
  561. # checking whether we need to generate them
  562. $self->brim->clear;
  563. if ($self->config->brim_width == 0) {
  564. $self->set_step_done(STEP_BRIM);
  565. return;
  566. }
  567. $self->status_cb->(88, "Generating brim");
  568. # brim is only printed on first layer and uses support material extruder
  569. my $first_layer_height = $self->objects->[0]->config->get_abs_value('first_layer_height');
  570. my $flow = Slic3r::Flow->new_from_width(
  571. width => ($self->config->first_layer_extrusion_width || $self->regions->[0]->config->perimeter_extrusion_width),
  572. role => FLOW_ROLE_PERIMETER,
  573. nozzle_diameter => $self->config->get_at('nozzle_diameter', $self->objects->[0]->config->support_material_extruder-1),
  574. layer_height => $first_layer_height,
  575. bridge_flow_ratio => 0,
  576. );
  577. my $mm3_per_mm = $flow->mm3_per_mm;
  578. my $grow_distance = $flow->scaled_width / 2;
  579. my @islands = (); # array of polygons
  580. foreach my $obj_idx (0 .. ($self->object_count - 1)) {
  581. my $object = $self->objects->[$obj_idx];
  582. my $layer0 = $object->get_layer(0);
  583. my @object_islands = (
  584. (map $_->contour, @{$layer0->slices}),
  585. );
  586. if (@{ $object->support_layers }) {
  587. my $support_layer0 = $object->support_layers->[0];
  588. push @object_islands,
  589. (map @{$_->polyline->grow($grow_distance)}, @{$support_layer0->support_fills})
  590. if $support_layer0->support_fills;
  591. push @object_islands,
  592. (map @{$_->polyline->grow($grow_distance)}, @{$support_layer0->support_interface_fills})
  593. if $support_layer0->support_interface_fills;
  594. }
  595. foreach my $copy (@{$object->_shifted_copies}) {
  596. push @islands, map { $_->translate(@$copy); $_ } map $_->clone, @object_islands;
  597. }
  598. }
  599. # if brim touches skirt, make it around skirt too
  600. # TODO: calculate actual skirt width (using each extruder's flow in multi-extruder setups)
  601. if ($self->config->skirt_distance + (($self->config->skirts - 1) * $flow->spacing) <= $self->config->brim_width) {
  602. push @islands, map @{$_->polygon->split_at_first_point->grow($grow_distance)}, @{$self->skirt};
  603. }
  604. my @loops = ();
  605. my $num_loops = sprintf "%.0f", $self->config->brim_width / $flow->width;
  606. for my $i (reverse 1 .. $num_loops) {
  607. # JT_SQUARE ensures no vertex is outside the given offset distance
  608. # -0.5 because islands are not represented by their centerlines
  609. # (first offset more, then step back - reverse order than the one used for
  610. # perimeters because here we're offsetting outwards)
  611. push @loops, @{offset2(\@islands, ($i + 0.5) * $flow->scaled_spacing, -1.0 * $flow->scaled_spacing, 100000, JT_SQUARE)};
  612. }
  613. $self->brim->append(map Slic3r::ExtrusionLoop->new_from_paths(
  614. Slic3r::ExtrusionPath->new(
  615. polyline => Slic3r::Polygon->new(@$_)->split_at_first_point,
  616. role => EXTR_ROLE_SKIRT,
  617. mm3_per_mm => $mm3_per_mm,
  618. width => $flow->width,
  619. height => $first_layer_height,
  620. ),
  621. ), reverse @{union_pt_chained(\@loops)});
  622. $self->set_step_done(STEP_BRIM);
  623. }
  624. sub write_gcode {
  625. my $self = shift;
  626. my ($file) = @_;
  627. # open output gcode file if we weren't supplied a file-handle
  628. my $fh;
  629. if (ref $file eq 'IO::Scalar') {
  630. $fh = $file;
  631. } else {
  632. Slic3r::open(\$fh, ">", $file)
  633. or die "Failed to open $file for writing\n";
  634. # enable UTF-8 output since user might have entered Unicode characters in fields like notes
  635. binmode $fh, ':utf8';
  636. }
  637. # write some information
  638. my @lt = localtime;
  639. printf $fh "; generated by Slic3r $Slic3r::VERSION on %04d-%02d-%02d at %02d:%02d:%02d\n\n",
  640. $lt[5] + 1900, $lt[4]+1, $lt[3], $lt[2], $lt[1], $lt[0];
  641. print $fh "; $_\n" foreach split /\R/, $self->config->notes;
  642. print $fh "\n" if $self->config->notes;
  643. my $first_object = $self->objects->[0];
  644. my $layer_height = $first_object->config->layer_height;
  645. for my $region_id (0..$#{$self->regions}) {
  646. printf $fh "; external perimeters extrusion width = %.2fmm\n",
  647. $self->regions->[$region_id]->flow(FLOW_ROLE_EXTERNAL_PERIMETER, $layer_height, 0, 0, undef, $first_object)->width;
  648. printf $fh "; perimeters extrusion width = %.2fmm\n",
  649. $self->regions->[$region_id]->flow(FLOW_ROLE_PERIMETER, $layer_height, 0, 0, undef, $first_object)->width;
  650. printf $fh "; infill extrusion width = %.2fmm\n",
  651. $self->regions->[$region_id]->flow(FLOW_ROLE_INFILL, $layer_height, 0, 0, undef, $first_object)->width;
  652. printf $fh "; solid infill extrusion width = %.2fmm\n",
  653. $self->regions->[$region_id]->flow(FLOW_ROLE_SOLID_INFILL, $layer_height, 0, 0, undef, $first_object)->width;
  654. printf $fh "; top infill extrusion width = %.2fmm\n",
  655. $self->regions->[$region_id]->flow(FLOW_ROLE_TOP_SOLID_INFILL, $layer_height, 0, 0, undef, $first_object)->width;
  656. printf $fh "; support material extrusion width = %.2fmm\n",
  657. $self->objects->[0]->support_material_flow->width
  658. if $self->has_support_material;
  659. printf $fh "; first layer extrusion width = %.2fmm\n",
  660. $self->regions->[$region_id]->flow(FLOW_ROLE_PERIMETER, $layer_height, 0, 1, undef, $self->objects->[0])->width
  661. if $self->regions->[$region_id]->config->first_layer_extrusion_width;
  662. print $fh "\n";
  663. }
  664. # prepare the helper object for replacing placeholders in custom G-code and output filename
  665. $self->placeholder_parser->update_timestamp;
  666. # estimate the total number of layer changes
  667. # TODO: only do this when M73 is enabled
  668. my $layer_count;
  669. if ($self->config->complete_objects) {
  670. $layer_count = sum(map { $_->total_layer_count * @{$_->copies} } @{$self->objects});
  671. } else {
  672. # if sequential printing is not enable, all copies of the same object share the same layer change command(s)
  673. $layer_count = sum(map { $_->total_layer_count } @{$self->objects});
  674. }
  675. # set up our helper object
  676. my $gcodegen = Slic3r::GCode->new(
  677. placeholder_parser => $self->placeholder_parser,
  678. layer_count => $layer_count,
  679. );
  680. $gcodegen->config->apply_print_config($self->config);
  681. $gcodegen->set_extruders($self->extruders, $self->config);
  682. print $fh "G21 ; set units to millimeters\n" if $self->config->gcode_flavor ne 'makerware';
  683. print $fh $gcodegen->set_fan(0, 1) if $self->config->cooling && $self->config->disable_fan_first_layers;
  684. # set bed temperature
  685. if ((my $temp = $self->config->first_layer_bed_temperature) && $self->config->start_gcode !~ /M(?:190|140)/i) {
  686. printf $fh $gcodegen->set_bed_temperature($temp, 1);
  687. }
  688. # set extruder(s) temperature before and after start G-code
  689. my $print_first_layer_temperature = sub {
  690. my ($wait) = @_;
  691. return if $self->config->start_gcode =~ /M(?:109|104)/i;
  692. for my $t (@{$self->extruders}) {
  693. my $temp = $self->config->get_at('first_layer_temperature', $t);
  694. $temp += $self->config->standby_temperature_delta if $self->config->ooze_prevention;
  695. printf $fh $gcodegen->set_temperature($temp, $wait, $t) if $temp > 0;
  696. }
  697. };
  698. $print_first_layer_temperature->(0);
  699. printf $fh "%s\n", $gcodegen->placeholder_parser->process($self->config->start_gcode);
  700. $print_first_layer_temperature->(1);
  701. # set other general things
  702. print $fh "G90 ; use absolute coordinates\n" if $self->config->gcode_flavor ne 'makerware';
  703. if ($self->config->gcode_flavor =~ /^(?:reprap|teacup)$/) {
  704. printf $fh $gcodegen->reset_e;
  705. if ($self->config->use_relative_e_distances) {
  706. print $fh "M83 ; use relative distances for extrusion\n";
  707. } else {
  708. print $fh "M82 ; use absolute distances for extrusion\n";
  709. }
  710. }
  711. # initialize a motion planner for object-to-object travel moves
  712. if ($self->config->avoid_crossing_perimeters) {
  713. my $distance_from_objects = 1;
  714. # compute the offsetted convex hull for each object and repeat it for each copy.
  715. my @islands = ();
  716. foreach my $obj_idx (0 .. ($self->object_count - 1)) {
  717. my $convex_hull = convex_hull([
  718. map @{$_->contour}, map @{$_->slices}, @{$self->objects->[$obj_idx]->layers},
  719. ]);
  720. # discard layers only containing thin walls (offset would fail on an empty polygon)
  721. if (@$convex_hull) {
  722. my $expolygon = Slic3r::ExPolygon->new($convex_hull);
  723. my @island = @{$expolygon->offset_ex(scale $distance_from_objects, 1, JT_SQUARE)};
  724. foreach my $copy (@{ $self->objects->[$obj_idx]->_shifted_copies }) {
  725. push @islands, map { my $c = $_->clone; $c->translate(@$copy); $c } @island;
  726. }
  727. }
  728. }
  729. $gcodegen->external_mp(Slic3r::GCode::MotionPlanner->new(
  730. islands => union_ex([ map @$_, @islands ]),
  731. internal => 0,
  732. ));
  733. }
  734. # calculate wiping points if needed
  735. if ($self->config->ooze_prevention) {
  736. my @skirt_points = map @$_, map @$_, @{$self->skirt};
  737. if (@skirt_points) {
  738. my $outer_skirt = convex_hull(\@skirt_points);
  739. my @skirts = ();
  740. foreach my $extruder_id (@{$self->extruders}) {
  741. push @skirts, my $s = $outer_skirt->clone;
  742. $s->translate(map scale($_), @{$self->config->get_at('extruder_offset', $extruder_id)});
  743. }
  744. my $convex_hull = convex_hull([ map @$_, @skirts ]);
  745. $gcodegen->standby_points([ map $_->clone, map @$_, map $_->subdivide(scale 10), @{offset([$convex_hull], scale 3)} ]);
  746. }
  747. }
  748. # prepare the layer processor
  749. my $layer_gcode = Slic3r::GCode::Layer->new(
  750. print => $self,
  751. gcodegen => $gcodegen,
  752. );
  753. # set initial extruder only after custom start G-code
  754. print $fh $gcodegen->set_extruder($self->extruders->[0]);
  755. # do all objects for each layer
  756. if ($self->config->complete_objects) {
  757. # print objects from the smallest to the tallest to avoid collisions
  758. # when moving onto next object starting point
  759. my @obj_idx = sort { $self->objects->[$a]->size->[Z] <=> $self->objects->[$b]->size->[Z] } 0..($self->object_count - 1);
  760. my $finished_objects = 0;
  761. for my $obj_idx (@obj_idx) {
  762. my $object = $self->objects->[$obj_idx];
  763. for my $copy (@{ $self->objects->[$obj_idx]->_shifted_copies }) {
  764. # move to the origin position for the copy we're going to print.
  765. # this happens before Z goes down to layer 0 again, so that
  766. # no collision happens hopefully.
  767. if ($finished_objects > 0) {
  768. $gcodegen->set_shift(map unscale $copy->[$_], X,Y);
  769. print $fh $gcodegen->retract;
  770. print $fh $gcodegen->G0($object->_copies_shift->negative, undef, 0, $gcodegen->config->travel_speed*60, 'move to origin position for next object');
  771. }
  772. my $buffer = Slic3r::GCode::CoolingBuffer->new(
  773. config => $self->config,
  774. gcodegen => $gcodegen,
  775. );
  776. my @layers = sort { $a->print_z <=> $b->print_z } @{$object->layers}, @{$object->support_layers};
  777. for my $layer (@layers) {
  778. # if we are printing the bottom layer of an object, and we have already finished
  779. # another one, set first layer temperatures. this happens before the Z move
  780. # is triggered, so machine has more time to reach such temperatures
  781. if ($layer->id == 0 && $finished_objects > 0) {
  782. printf $fh $gcodegen->set_bed_temperature($self->config->first_layer_bed_temperature),
  783. if $self->config->first_layer_bed_temperature;
  784. $print_first_layer_temperature->();
  785. }
  786. print $fh $buffer->append(
  787. $layer_gcode->process_layer($layer, [$copy]),
  788. $layer->object->ptr,
  789. $layer->id,
  790. $layer->print_z,
  791. );
  792. }
  793. print $fh $buffer->flush;
  794. $finished_objects++;
  795. }
  796. }
  797. } else {
  798. # order objects using a nearest neighbor search
  799. my @obj_idx = @{chained_path([ map Slic3r::Point->new(@{$_->_shifted_copies->[0]}), @{$self->objects} ])};
  800. # sort layers by Z
  801. my %layers = (); # print_z => [ [layers], [layers], [layers] ] by obj_idx
  802. foreach my $obj_idx (0 .. ($self->object_count - 1)) {
  803. my $object = $self->objects->[$obj_idx];
  804. foreach my $layer (@{$object->layers}, @{$object->support_layers}) {
  805. $layers{ $layer->print_z } ||= [];
  806. $layers{ $layer->print_z }[$obj_idx] ||= [];
  807. push @{$layers{ $layer->print_z }[$obj_idx]}, $layer;
  808. }
  809. }
  810. my $buffer = Slic3r::GCode::CoolingBuffer->new(
  811. config => $self->config,
  812. gcodegen => $gcodegen,
  813. );
  814. foreach my $print_z (sort { $a <=> $b } keys %layers) {
  815. foreach my $obj_idx (@obj_idx) {
  816. foreach my $layer (@{ $layers{$print_z}[$obj_idx] // [] }) {
  817. print $fh $buffer->append(
  818. $layer_gcode->process_layer($layer, $layer->object->_shifted_copies),
  819. $layer->object->ptr . ref($layer), # differentiate $obj_id between normal layers and support layers
  820. $layer->id,
  821. $layer->print_z,
  822. );
  823. }
  824. }
  825. }
  826. print $fh $buffer->flush;
  827. }
  828. # write end commands to file
  829. print $fh $gcodegen->retract if $gcodegen->extruder; # empty prints don't even set an extruder
  830. print $fh $gcodegen->set_fan(0);
  831. printf $fh "%s\n", $gcodegen->placeholder_parser->process($self->config->end_gcode);
  832. $self->total_used_filament(0);
  833. $self->total_extruded_volume(0);
  834. foreach my $extruder_id (@{$self->extruders}) {
  835. my $extruder = $gcodegen->extruders->{$extruder_id};
  836. # the final retraction doesn't really count as "used filament"
  837. my $used_filament = $extruder->absolute_E + $extruder->retract_length;
  838. my $extruded_volume = $extruder->extruded_volume($used_filament);
  839. printf $fh "; filament used = %.1fmm (%.1fcm3)\n",
  840. $used_filament, $extruded_volume/1000;
  841. $self->total_used_filament($self->total_used_filament + $used_filament);
  842. $self->total_extruded_volume($self->total_extruded_volume + $extruded_volume);
  843. }
  844. # append full config
  845. print $fh "\n";
  846. foreach my $config ($self->config, $self->default_object_config, $self->default_region_config) {
  847. foreach my $opt_key (sort @{$config->get_keys}) {
  848. next if $Slic3r::Config::Options->{$opt_key}{shortcut};
  849. printf $fh "; %s = %s\n", $opt_key, $config->serialize($opt_key);
  850. }
  851. }
  852. # close our gcode file
  853. close $fh;
  854. }
  855. # this method will return the supplied input file path after expanding its
  856. # format variables with their values
  857. sub expanded_output_filepath {
  858. my $self = shift;
  859. my ($path) = @_;
  860. return undef if !@{$self->objects};
  861. my $input_file = first { defined $_ } map $_->model_object->input_file, @{$self->objects};
  862. return undef if !defined $input_file;
  863. my $filename = my $filename_base = basename($input_file);
  864. $filename_base =~ s/\.[^.]+$//; # without suffix
  865. my $extra = {
  866. input_filename => $filename,
  867. input_filename_base => $filename_base,
  868. };
  869. if ($path && -d $path) {
  870. # if output path is an existing directory, we take that and append
  871. # the specified filename format
  872. $path = File::Spec->join($path, $self->config->output_filename_format);
  873. } elsif (!$path) {
  874. # if no explicit output file was defined, we take the input
  875. # file directory and append the specified filename format
  876. $path = (fileparse($input_file))[1] . $self->config->output_filename_format;
  877. } else {
  878. # path is a full path to a file so we use it as it is
  879. }
  880. # make sure we use an up-to-date timestamp
  881. $self->placeholder_parser->update_timestamp;
  882. return $self->placeholder_parser->process($path, $extra);
  883. }
  884. # This method assigns extruders to the volumes having a material
  885. # but not having extruders set in the material config.
  886. sub auto_assign_extruders {
  887. my ($self, $model_object) = @_;
  888. # only assign extruders if object has more than one volume
  889. return if @{$model_object->volumes} == 1;
  890. my $extruders = scalar @{ $self->config->nozzle_diameter };
  891. foreach my $i (0..$#{$model_object->volumes}) {
  892. my $volume = $model_object->volumes->[$i];
  893. if (defined $volume->material_id) {
  894. my $material = $model_object->model->get_material($volume->material_id);
  895. my $config = $material->config;
  896. my $extruder_id = $i + 1;
  897. $config->set_ifndef('extruder', $extruder_id);
  898. }
  899. }
  900. }
  901. 1;