perimeters.t 24 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444
  1. use Test::More tests => 59;
  2. use strict;
  3. use warnings;
  4. BEGIN {
  5. use FindBin;
  6. use lib "$FindBin::Bin/../lib";
  7. use local::lib "$FindBin::Bin/../local-lib";
  8. }
  9. use Slic3r::ExtrusionLoop ':roles';
  10. use Slic3r::ExtrusionPath ':roles';
  11. use List::Util qw(first);
  12. use Slic3r;
  13. use Slic3r::Flow ':roles';
  14. use Slic3r::Geometry qw(PI scale unscale);
  15. use Slic3r::Geometry::Clipper qw(union_ex diff union offset);
  16. use Slic3r::Surface ':types';
  17. use Slic3r::Test;
  18. {
  19. my $flow = Slic3r::Flow->new(
  20. width => 1,
  21. height => 1,
  22. nozzle_diameter => 1,
  23. );
  24. my $config = Slic3r::Config->new;
  25. my $test = sub {
  26. my ($expolygons, %expected) = @_;
  27. my $slices = Slic3r::Surface::Collection->new;
  28. $slices->append(Slic3r::Surface->new(
  29. surface_type => S_TYPE_INTERNAL,
  30. expolygon => $_,
  31. )) for @$expolygons;
  32. my ($region_config, $object_config, $print_config, $loops, $gap_fill, $fill_surfaces);
  33. my $g = Slic3r::Layer::PerimeterGenerator->new(
  34. # input:
  35. $slices,
  36. 1, # layer height
  37. $flow,
  38. ($region_config = Slic3r::Config::PrintRegion->new),
  39. ($object_config = Slic3r::Config::PrintObject->new),
  40. ($print_config = Slic3r::Config::Print->new),
  41. # output:
  42. ($loops = Slic3r::ExtrusionPath::Collection->new),
  43. ($gap_fill = Slic3r::ExtrusionPath::Collection->new),
  44. ($fill_surfaces = Slic3r::Surface::Collection->new),
  45. );
  46. $g->config->apply_dynamic($config);
  47. $g->process;
  48. is scalar(@$loops),
  49. scalar(@$expolygons), 'expected number of collections';
  50. ok !defined(first { !$_->isa('Slic3r::ExtrusionPath::Collection') } @$loops),
  51. 'everything is returned as collections';
  52. my $flattened_loops = $loops->flatten;
  53. my @loops = @$flattened_loops;
  54. is scalar(@loops),
  55. $expected{total}, 'expected number of loops';
  56. is scalar(grep $_->role == EXTR_ROLE_EXTERNAL_PERIMETER, map @$_, @loops),
  57. $expected{external}, 'expected number of external loops';
  58. is_deeply [ map { ($_->role == EXTR_ROLE_EXTERNAL_PERIMETER) || 0 } map @$_, @loops ],
  59. $expected{ext_order}, 'expected external order';
  60. is scalar(grep $_->loop_role == EXTRL_ROLE_CONTOUR_INTERNAL_PERIMETER, @loops),
  61. $expected{cinternal}, 'expected number of internal contour loops';
  62. is scalar(grep $_->polygon->is_counter_clockwise, @loops),
  63. $expected{ccw}, 'expected number of ccw loops';
  64. is_deeply [ map $_->polygon->is_counter_clockwise, @loops ],
  65. $expected{ccw_order}, 'expected ccw/cw order';
  66. if ($expected{nesting}) {
  67. foreach my $nesting (@{ $expected{nesting} }) {
  68. for my $i (1..$#$nesting) {
  69. ok $loops[$nesting->[$i-1]]->polygon->contains_point($loops[$nesting->[$i]]->first_point),
  70. 'expected nesting order';
  71. }
  72. }
  73. }
  74. };
  75. $config->set('perimeters', 3);
  76. $test->(
  77. [
  78. Slic3r::ExPolygon->new(
  79. Slic3r::Polygon->new_scale([0,0], [100,0], [100,100], [0,100]),
  80. ),
  81. ],
  82. total => 3,
  83. external => 1,
  84. ext_order => [0,0,1],
  85. cinternal => 1,
  86. ccw => 3,
  87. ccw_order => [1,1,1],
  88. nesting => [ [2,1,0] ],
  89. );
  90. $test->(
  91. [
  92. Slic3r::ExPolygon->new(
  93. Slic3r::Polygon->new_scale([0,0], [100,0], [100,100], [0,100]),
  94. Slic3r::Polygon->new_scale([40,40], [40,60], [60,60], [60,40]),
  95. ),
  96. ],
  97. total => 6,
  98. external => 2,
  99. ext_order => [0,0,1,0,0,1],
  100. cinternal => 1,
  101. ccw => 3,
  102. ccw_order => [0,0,0,1,1,1],
  103. nesting => [ [5,4,3,0,1,2] ],
  104. );
  105. $test->(
  106. [
  107. Slic3r::ExPolygon->new(
  108. Slic3r::Polygon->new_scale([0,0], [200,0], [200,200], [0,200]),
  109. Slic3r::Polygon->new_scale([20,20], [20,180], [180,180], [180,20]),
  110. ),
  111. # nested:
  112. Slic3r::ExPolygon->new(
  113. Slic3r::Polygon->new_scale([50,50], [150,50], [150,150], [50,150]),
  114. Slic3r::Polygon->new_scale([80,80], [80,120], [120,120], [120,80]),
  115. ),
  116. ],
  117. total => 4*3,
  118. external => 4,
  119. ext_order => [0,0,1,0,0,1,0,0,1,0,0,1],
  120. cinternal => 2,
  121. ccw => 2*3,
  122. ccw_order => [0,0,0,1,1,1,0,0,0,1,1,1],
  123. );
  124. $config->set('perimeters', 2);
  125. $test->(
  126. [
  127. Slic3r::ExPolygon->new(
  128. Slic3r::Polygon->new_scale([0,0], [50,0], [50,50], [0,50]),
  129. Slic3r::Polygon->new_scale([7.5,7.5], [7.5,12.5], [12.5,12.5], [12.5,7.5]),
  130. Slic3r::Polygon->new_scale([7.5,17.5], [7.5,22.5], [12.5,22.5], [12.5,17.5]),
  131. Slic3r::Polygon->new_scale([7.5,27.5], [7.5,32.5], [12.5,32.5], [12.5,27.5]),
  132. Slic3r::Polygon->new_scale([7.5,37.5], [7.5,42.5], [12.5,42.5], [12.5,37.5]),
  133. Slic3r::Polygon->new_scale([17.5,7.5], [17.5,12.5], [22.5,12.5], [22.5,7.5]),
  134. ),
  135. ],
  136. total => 12,
  137. external => 6,
  138. ext_order => [0,1,0,1,0,1,0,1,0,1,0,1],
  139. cinternal => 1,
  140. ccw => 2,
  141. ccw_order => [0,0,0,0,0,0,0,0,0,0,1,1],
  142. nesting => [ [0,1],[2,3],[4,5],[6,7],[8,9] ],
  143. );
  144. }
  145. {
  146. my $config = Slic3r::Config::new_from_defaults;
  147. $config->set('skirts', 0);
  148. $config->set('fill_density', 0);
  149. $config->set('perimeters', 3);
  150. $config->set('top_solid_layers', 0);
  151. $config->set('bottom_solid_layers', 0);
  152. $config->set('cooling', [ 0 ]); # to prevent speeds from being altered
  153. $config->set('first_layer_speed', '100%'); # to prevent speeds from being altered
  154. {
  155. my $print = Slic3r::Test::init_print('overhang', config => $config);
  156. my $has_cw_loops = 0;
  157. my $cur_loop;
  158. Slic3r::GCode::Reader->new->parse(Slic3r::Test::gcode($print), sub {
  159. my ($self, $cmd, $args, $info) = @_;
  160. if ($info->{extruding} && $info->{dist_XY} > 0) {
  161. $cur_loop ||= [ [$self->X, $self->Y] ];
  162. push @$cur_loop, [ @$info{qw(new_X new_Y)} ];
  163. } elsif ($cmd ne 'M73') { # skips remaining time lines (M73)
  164. if ($cur_loop) {
  165. $has_cw_loops = 1 if Slic3r::Polygon->new(@$cur_loop)->is_clockwise;
  166. $cur_loop = undef;
  167. }
  168. }
  169. });
  170. ok !$has_cw_loops, 'all perimeters extruded ccw';
  171. }
  172. foreach my $model (qw(cube_with_hole cube_with_concave_hole)) {
  173. $config->set('external_perimeter_speed', 68);
  174. my $print = Slic3r::Test::init_print(
  175. $model,
  176. config => $config,
  177. duplicate => 2, # we test two copies to make sure ExtrusionLoop objects are not modified in-place (the second object would not detect cw loops and thus would calculate wrong inwards moves)
  178. );
  179. my $has_cw_loops = my $has_outwards_move = my $starts_on_convex_point = 0;
  180. my $cur_loop;
  181. my %external_loops = (); # print_z => count of external loops
  182. Slic3r::GCode::Reader->new->parse(Slic3r::Test::gcode($print), sub {
  183. my ($self, $cmd, $args, $info) = @_;
  184. if ($info->{extruding} && $info->{dist_XY} > 0) {
  185. $cur_loop ||= [ [$self->X, $self->Y] ];
  186. push @$cur_loop, [ @$info{qw(new_X new_Y)} ];
  187. } elsif ($cmd ne 'M73') { # skips remaining time lines (M73)
  188. if ($cur_loop) {
  189. $has_cw_loops = 1 if Slic3r::Polygon->new_scale(@$cur_loop)->is_clockwise;
  190. if ($self->F == $config->external_perimeter_speed*60) {
  191. my $move_dest = Slic3r::Point->new_scale(@$info{qw(new_X new_Y)});
  192. # reset counter for second object
  193. $external_loops{$self->Z} = 0
  194. if defined($external_loops{$self->Z}) && $external_loops{$self->Z} == 2;
  195. $external_loops{$self->Z}++;
  196. my $is_contour = $external_loops{$self->Z} == 2;
  197. my $is_hole = $external_loops{$self->Z} == 1;
  198. my $loop = Slic3r::Polygon->new_scale(@$cur_loop);
  199. my $loop_contains_point = $loop->contains_point($move_dest);
  200. $has_outwards_move = 1
  201. if (!$loop_contains_point && $is_contour) # contour should include destination
  202. || ($loop_contains_point && $is_hole); # hole should not
  203. if ($model eq 'cube_with_concave_hole') {
  204. # check that loop starts at a concave vertex
  205. my $ccw_angle = $loop->first_point->ccw_angle(@$loop[-2,1]);
  206. my $convex = ($ccw_angle > PI); # whether the angle on the *right* side is convex
  207. $starts_on_convex_point = 1
  208. if ($convex && $is_contour) || (!$convex && $is_hole);
  209. }
  210. }
  211. $cur_loop = undef;
  212. }
  213. }
  214. });
  215. ok !$has_cw_loops, 'all perimeters extruded ccw';
  216. ok !$has_outwards_move, 'move inwards after completing external loop';
  217. ok !$starts_on_convex_point, 'loops start on concave point if any';
  218. }
  219. {
  220. $config->set('perimeters', 1);
  221. $config->set('perimeter_speed', 77);
  222. $config->set('external_perimeter_speed', 66);
  223. $config->set('bridge_speed', 99);
  224. $config->set('cooling', [ 1 ]);
  225. $config->set('fan_below_layer_time', [ 0 ]);
  226. $config->set('slowdown_below_layer_time', [ 0 ]);
  227. $config->set('bridge_fan_speed', [ 100 ]);
  228. $config->set('bridge_flow_ratio', 33); # arbitrary value
  229. $config->set('overhangs', 1);
  230. my $print = Slic3r::Test::init_print('overhang', config => $config);
  231. my %layer_speeds = (); # print Z => [ speeds ]
  232. my $fan_speed = 0;
  233. my $bridge_mm_per_mm = ($config->nozzle_diameter->[0]**2) / ($config->filament_diameter->[0]**2) * $config->bridge_flow_ratio;
  234. Slic3r::GCode::Reader->new->parse(Slic3r::Test::gcode($print), sub {
  235. my ($self, $cmd, $args, $info) = @_;
  236. $fan_speed = 0 if $cmd eq 'M107';
  237. $fan_speed = $args->{S} if $cmd eq 'M106';
  238. if ($info->{extruding} && $info->{dist_XY} > 0) {
  239. $layer_speeds{$self->Z} ||= {};
  240. $layer_speeds{$self->Z}{my $feedrate = $args->{F} // $self->F} = 1;
  241. fail 'wrong speed found'
  242. if $feedrate != $config->perimeter_speed*60
  243. && $feedrate != $config->external_perimeter_speed*60
  244. && $feedrate != $config->bridge_speed*60;
  245. if ($feedrate == $config->bridge_speed*60) {
  246. fail 'printing overhang but fan is not enabled or running at wrong speed'
  247. if $fan_speed != 255;
  248. my $mm_per_mm = $info->{dist_E} / $info->{dist_XY};
  249. fail 'wrong bridge flow' if abs($mm_per_mm - $bridge_mm_per_mm) > 0.01;
  250. } else {
  251. fail 'fan is running when not supposed to'
  252. if $fan_speed > 0;
  253. }
  254. }
  255. });
  256. is scalar(grep { keys %$_ > 1 } values %layer_speeds), 1,
  257. 'only overhang layer has more than one speed';
  258. }
  259. }
  260. {
  261. my $config = Slic3r::Config::new_from_defaults;
  262. $config->set('skirts', 0);
  263. $config->set('perimeters', 3);
  264. $config->set('layer_height', 0.4);
  265. $config->set('first_layer_height', 0.35);
  266. $config->set('extra_perimeters', 1);
  267. $config->set('cooling', [ 0 ]); # to prevent speeds from being altered
  268. $config->set('first_layer_speed', '100%'); # to prevent speeds from being altered
  269. $config->set('perimeter_speed', 99);
  270. $config->set('external_perimeter_speed', 99);
  271. $config->set('small_perimeter_speed', 99);
  272. $config->set('thin_walls', 0);
  273. my $print = Slic3r::Test::init_print('ipadstand', config => $config);
  274. my %perimeters = (); # z => number of loops
  275. my $in_loop = 0;
  276. Slic3r::GCode::Reader->new->parse(Slic3r::Test::gcode($print), sub {
  277. my ($self, $cmd, $args, $info) = @_;
  278. if ($info->{extruding} && $info->{dist_XY} > 0 && ($args->{F} // $self->F) == $config->perimeter_speed*60) {
  279. $perimeters{$self->Z}++ if !$in_loop;
  280. $in_loop = 1;
  281. } elsif ($cmd ne 'M73') { # skips remaining time lines (M73)
  282. $in_loop = 0;
  283. }
  284. });
  285. ok !(grep { $_ % $config->perimeters } values %perimeters), 'no superfluous extra perimeters';
  286. }
  287. {
  288. my $config = Slic3r::Config::new_from_defaults;
  289. $config->set('nozzle_diameter', [0.4]);
  290. $config->set('perimeters', 2);
  291. $config->set('perimeter_extrusion_width', 0.4);
  292. $config->set('external_perimeter_extrusion_width', 0.4);
  293. $config->set('infill_extrusion_width', 0.53);
  294. $config->set('solid_infill_extrusion_width', 0.53);
  295. # we just need a pre-filled Print object
  296. my $print = Slic3r::Test::init_print('20mm_cube', config => $config);
  297. # override a layer's slices
  298. my $expolygon = Slic3r::ExPolygon->new([[-71974463,-139999376],[-71731792,-139987456],[-71706544,-139985616],[-71682119,-139982639],[-71441248,-139946912],[-71417487,-139942895],[-71379384,-139933984],[-71141800,-139874480],[-71105247,-139862895],[-70873544,-139779984],[-70838592,-139765856],[-70614943,-139660064],[-70581783,-139643567],[-70368368,-139515680],[-70323751,-139487872],[-70122160,-139338352],[-70082399,-139306639],[-69894800,-139136624],[-69878679,-139121327],[-69707992,-138933008],[-69668575,-138887343],[-69518775,-138685359],[-69484336,-138631632],[-69356423,-138418207],[-69250040,-138193296],[-69220920,-138128976],[-69137992,-137897168],[-69126095,-137860255],[-69066568,-137622608],[-69057104,-137582511],[-69053079,-137558751],[-69017352,-137317872],[-69014392,-137293456],[-69012543,-137268207],[-68999369,-137000000],[-63999999,-137000000],[-63705947,-136985551],[-63654984,-136977984],[-63414731,-136942351],[-63364756,-136929840],[-63129151,-136870815],[-62851950,-136771631],[-62585807,-136645743],[-62377483,-136520895],[-62333291,-136494415],[-62291908,-136463728],[-62096819,-136319023],[-62058644,-136284432],[-61878676,-136121328],[-61680968,-135903184],[-61650275,-135861807],[-61505591,-135666719],[-61354239,-135414191],[-61332211,-135367615],[-61228359,-135148063],[-61129179,-134870847],[-61057639,-134585262],[-61014451,-134294047],[-61000000,-134000000],[-61000000,-107999999],[-61014451,-107705944],[-61057639,-107414736],[-61129179,-107129152],[-61228359,-106851953],[-61354239,-106585808],[-61505591,-106333288],[-61680967,-106096816],[-61878675,-105878680],[-62096820,-105680967],[-62138204,-105650279],[-62333292,-105505591],[-62585808,-105354239],[-62632384,-105332207],[-62851951,-105228360],[-62900463,-105211008],[-63129152,-105129183],[-63414731,-105057640],[-63705947,-105014448],[-63999999,-105000000],[-68999369,-105000000],[-69012543,-104731792],[-69014392,-104706544],[-69017352,-104682119],[-69053079,-104441248],[-69057104,-104417487],[-69066008,-104379383],[-69125528,-104141799],[-69137111,-104105248],[-69220007,-103873544],[-69234136,-103838591],[-69339920,-103614943],[-69356415,-103581784],[-69484328,-103368367],[-69512143,-103323752],[-69661647,-103122160],[-69693352,-103082399],[-69863383,-102894800],[-69878680,-102878679],[-70066999,-102707992],[-70112656,-102668576],[-70314648,-102518775],[-70368367,-102484336],[-70581783,-102356424],[-70806711,-102250040],[-70871040,-102220919],[-71102823,-102137992],[-71139752,-102126095],[-71377383,-102066568],[-71417487,-102057104],[-71441248,-102053079],[-71682119,-102017352],[-71706535,-102014392],[-71731784,-102012543],[-71974456,-102000624],[-71999999,-102000000],[-104000000,-102000000],[-104025536,-102000624],[-104268207,-102012543],[-104293455,-102014392],[-104317880,-102017352],[-104558751,-102053079],[-104582512,-102057104],[-104620616,-102066008],[-104858200,-102125528],[-104894751,-102137111],[-105126455,-102220007],[-105161408,-102234136],[-105385056,-102339920],[-105418215,-102356415],[-105631632,-102484328],[-105676247,-102512143],[-105877839,-102661647],[-105917600,-102693352],[-106105199,-102863383],[-106121320,-102878680],[-106292007,-103066999],[-106331424,-103112656],[-106481224,-103314648],[-106515663,-103368367],[-106643575,-103581783],[-106749959,-103806711],[-106779080,-103871040],[-106862007,-104102823],[-106873904,-104139752],[-106933431,-104377383],[-106942896,-104417487],[-106946920,-104441248],[-106982648,-104682119],[-106985607,-104706535],[-106987456,-104731784],[-107000630,-105000000],[-112000000,-105000000],[-112294056,-105014448],[-112585264,-105057640],[-112870848,-105129184],[-112919359,-105146535],[-113148048,-105228360],[-113194624,-105250392],[-113414191,-105354239],[-113666711,-105505591],[-113708095,-105536279],[-113903183,-105680967],[-114121320,-105878679],[-114319032,-106096816],[-114349720,-106138200],[-114494408,-106333288],[-114645760,-106585808],[-114667792,-106632384],[-114771640,-106851952],[-114788991,-106900463],[-114870815,-107129151],[-114942359,-107414735],[-114985551,-107705943],[-115000000,-107999999],[-115000000,-134000000],[-114985551,-134294048],[-114942359,-134585263],[-114870816,-134870847],[-114853464,-134919359],[-114771639,-135148064],[-114645759,-135414192],[-114494407,-135666720],[-114319031,-135903184],[-114121320,-136121327],[-114083144,-136155919],[-113903184,-136319023],[-113861799,-136349712],[-113666711,-136494416],[-113458383,-136619264],[-113414192,-136645743],[-113148049,-136771631],[-112870848,-136870815],[-112820872,-136883327],[-112585264,-136942351],[-112534303,-136949920],[-112294056,-136985551],[-112000000,-137000000],[-107000630,-137000000],[-106987456,-137268207],[-106985608,-137293440],[-106982647,-137317872],[-106946920,-137558751],[-106942896,-137582511],[-106933991,-137620624],[-106874471,-137858208],[-106862888,-137894751],[-106779992,-138126463],[-106765863,-138161424],[-106660080,-138385055],[-106643584,-138418223],[-106515671,-138631648],[-106487855,-138676256],[-106338352,-138877839],[-106306647,-138917600],[-106136616,-139105199],[-106121320,-139121328],[-105933000,-139291999],[-105887344,-139331407],[-105685351,-139481232],[-105631632,-139515663],[-105418216,-139643567],[-105193288,-139749951],[-105128959,-139779072],[-104897175,-139862016],[-104860247,-139873904],[-104622616,-139933423],[-104582511,-139942896],[-104558751,-139946912],[-104317880,-139982656],[-104293463,-139985616],[-104268216,-139987456],[-104025544,-139999376],[-104000000,-140000000],[-71999999,-140000000]],[[-105000000,-138000000],[-105000000,-104000000],[-71000000,-104000000],[-71000000,-138000000]],[[-69000000,-132000000],[-69000000,-110000000],[-64991180,-110000000],[-64991180,-132000000]],[[-111008824,-132000000],[-111008824,-110000000],[-107000000,-110000000],[-107000000,-132000000]]);
  299. my $object = $print->print->objects->[0];
  300. $object->slice;
  301. my $layer = $object->get_layer(1);
  302. my $layerm = $layer->regions->[0];
  303. $layerm->slices->clear;
  304. $layerm->slices->append(Slic3r::Surface->new(surface_type => S_TYPE_INTERNAL, expolygon => $expolygon));
  305. # make perimeters
  306. $layer->make_perimeters;
  307. # compute the covered area
  308. my $pflow = $layerm->flow(FLOW_ROLE_PERIMETER);
  309. my $iflow = $layerm->flow(FLOW_ROLE_INFILL);
  310. my $covered_by_perimeters = union_ex([
  311. (map @{$_->polygon->split_at_first_point->grow($pflow->scaled_width/2)}, map @$_, @{$layerm->perimeters}),
  312. ]);
  313. my $covered_by_infill = union_ex([
  314. (map $_->p, @{$layerm->fill_surfaces}),
  315. (map @{$_->polyline->grow($iflow->scaled_width/2)}, @{$layerm->thin_fills}),
  316. ]);
  317. # compute the non covered area
  318. my $non_covered = diff(
  319. [ map @{$_->expolygon}, @{$layerm->slices} ],
  320. [ map @$_, (@$covered_by_perimeters, @$covered_by_infill) ],
  321. );
  322. if (0) {
  323. printf "max non covered = %f\n", List::Util::max(map unscale unscale $_->area, @$non_covered);
  324. require "Slic3r/SVG.pm";
  325. Slic3r::SVG::output(
  326. "gaps.svg",
  327. expolygons => [ map $_->expolygon, @{$layerm->slices} ],
  328. red_expolygons => union_ex([ map @$_, (@$covered_by_perimeters, @$covered_by_infill) ]),
  329. green_expolygons => union_ex($non_covered),
  330. no_arrows => 1,
  331. polylines => [
  332. map $_->polygon->split_at_first_point, map @$_, @{$layerm->perimeters},
  333. ],
  334. );
  335. }
  336. ok !(defined first { $_->area > ($iflow->scaled_width**2) } @$non_covered), 'no gap between perimeters and infill';
  337. }
  338. {
  339. my $config = Slic3r::Config::new_from_defaults;
  340. $config->set('skirts', 0);
  341. $config->set('perimeters', 3);
  342. $config->set('layer_height', 0.4);
  343. $config->set('bridge_speed', 99);
  344. $config->set('fill_density', 0); # to prevent bridging over sparse infill
  345. $config->set('overhangs', 1);
  346. $config->set('cooling', [ 0 ]); # to prevent speeds from being altered
  347. $config->set('first_layer_speed', '100%'); # to prevent speeds from being altered
  348. my $test = sub {
  349. my ($print) = @_;
  350. my %z_with_bridges = (); # z => 1
  351. Slic3r::GCode::Reader->new->parse(Slic3r::Test::gcode($print), sub {
  352. my ($self, $cmd, $args, $info) = @_;
  353. if ($info->{extruding} && $info->{dist_XY} > 0) {
  354. $z_with_bridges{$self->Z} = 1 if ($args->{F} // $self->F) == $config->bridge_speed*60;
  355. }
  356. });
  357. return scalar keys %z_with_bridges;
  358. };
  359. ok $test->(Slic3r::Test::init_print('V', config => $config)) == 1,
  360. 'no overhangs printed with bridge speed'; # except for the two internal solid layers above void
  361. ok $test->(Slic3r::Test::init_print('V', config => $config, scale_xyz => [3,1,1])) > 2,
  362. 'overhangs printed with bridge speed';
  363. }
  364. {
  365. my $config = Slic3r::Config::new_from_defaults;
  366. $config->set('seam_position', 'random');
  367. my $print = Slic3r::Test::init_print('20mm_cube', config => $config);
  368. ok Slic3r::Test::gcode($print), 'successful generation of G-code with seam_position = random';
  369. }
  370. {
  371. my $test = sub {
  372. my ($model_name) = @_;
  373. my $config = Slic3r::Config::new_from_defaults;
  374. $config->set('seam_position', 'aligned');
  375. $config->set('skirts', 0);
  376. $config->set('perimeters', 1);
  377. $config->set('fill_density', 0);
  378. $config->set('top_solid_layers', 0);
  379. $config->set('bottom_solid_layers', 0);
  380. $config->set('retract_layer_change', [0]);
  381. my $was_extruding = 0;
  382. my @seam_points = ();
  383. my $print = Slic3r::Test::init_print($model_name, config => $config);
  384. Slic3r::GCode::Reader->new->parse(my $gcode = Slic3r::Test::gcode($print), sub {
  385. my ($self, $cmd, $args, $info) = @_;
  386. if ($info->{extruding}) {
  387. if (!$was_extruding) {
  388. push @seam_points, Slic3r::Point->new_scale($self->X, $self->Y);
  389. }
  390. $was_extruding = 1;
  391. } elsif ($cmd ne 'M73') { # skips remaining time lines (M73)
  392. $was_extruding = 0;
  393. }
  394. });
  395. my @dist = map unscale($_), map $seam_points[$_]->distance_to($seam_points[$_+1]), 0..($#seam_points-1);
  396. ok !(defined first { $_ > 3 } @dist), 'seam is aligned';
  397. };
  398. $test->('20mm_cube');
  399. $test->('small_dorito');
  400. }
  401. __END__