gcode.t 8.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246
  1. use Test::More tests => 25;
  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 List::Util qw(first);
  10. use Slic3r;
  11. use Slic3r::Geometry qw(scale convex_hull);
  12. use Slic3r::Test;
  13. {
  14. my $gcodegen = Slic3r::GCode->new();
  15. $gcodegen->set_layer_count(1);
  16. $gcodegen->set_origin(Slic3r::Pointf->new(10, 10));
  17. is_deeply $gcodegen->last_pos->arrayref, [scale -10, scale -10], 'last_pos is shifted correctly';
  18. }
  19. {
  20. my $config = Slic3r::Config::new_from_defaults;
  21. $config->set('wipe', [1]);
  22. $config->set('retract_layer_change', [0]);
  23. my $print = Slic3r::Test::init_print('20mm_cube', config => $config);
  24. my $have_wipe = 0;
  25. my @retract_speeds = ();
  26. my $extruded_on_this_layer = 0;
  27. my $wiping_on_new_layer = 0;
  28. Slic3r::GCode::Reader->new->parse(Slic3r::Test::gcode($print), sub {
  29. my ($self, $cmd, $args, $info) = @_;
  30. if ($info->{travel} && $info->{dist_Z}) {
  31. # changing layer
  32. $extruded_on_this_layer = 0;
  33. } elsif ($info->{extruding} && $info->{dist_XY}) {
  34. $extruded_on_this_layer = 1;
  35. } elsif ($info->{retracting} && $info->{dist_XY} > 0) {
  36. $have_wipe = 1;
  37. $wiping_on_new_layer = 1 if !$extruded_on_this_layer;
  38. my $move_time = $info->{dist_XY} / ($args->{F} // $self->F);
  39. push @retract_speeds, abs($info->{dist_E}) / $move_time;
  40. }
  41. });
  42. ok $have_wipe, "wipe";
  43. ok !defined (first { abs($_ - $config->retract_speed->[0]*60) < 5 } @retract_speeds), 'wipe moves don\'t retract faster than configured speed';
  44. ok !$wiping_on_new_layer, 'no wiping after layer change';
  45. }
  46. {
  47. my $config = Slic3r::Config::new_from_defaults;
  48. $config->set('z_offset', 5);
  49. $config->set('start_gcode', '');
  50. my $test = sub {
  51. my ($comment) = @_;
  52. my $print = Slic3r::Test::init_print('20mm_cube', config => $config);
  53. my $moves_below_z_offset = 0;
  54. Slic3r::GCode::Reader->new->parse(Slic3r::Test::gcode($print), sub {
  55. my ($self, $cmd, $args, $info) = @_;
  56. if ($info->{travel} && exists $args->{Z}) {
  57. $moves_below_z_offset++ if $args->{Z} < $config->z_offset;
  58. }
  59. });
  60. is $moves_below_z_offset, 0, "no Z moves below Z offset ($comment)";
  61. };
  62. $test->("no lift");
  63. $config->set('retract_lift', [3]);
  64. $test->("lift < z_offset");
  65. $config->set('retract_lift', [6]);
  66. $test->("lift > z_offset");
  67. }
  68. {
  69. # This tests the following behavior:
  70. # - complete objects does not crash
  71. # - no hard-coded "E" are generated
  72. # - Z moves are correctly generated for both objects
  73. # - no travel moves go outside skirt
  74. # - temperatures are set correctly
  75. my $config = Slic3r::Config::new_from_defaults;
  76. $config->set('gcode_comments', 1);
  77. $config->set('complete_objects', 1);
  78. $config->set('extrusion_axis', 'A');
  79. $config->set('start_gcode', ''); # prevent any default extra Z move
  80. $config->set('layer_height', 0.4);
  81. $config->set('first_layer_height', 0.4);
  82. $config->set('temperature', [200]);
  83. $config->set('first_layer_temperature', [210]);
  84. my $print = Slic3r::Test::init_print('20mm_cube', config => $config, duplicate => 2);
  85. ok my $gcode = Slic3r::Test::gcode($print), "complete_objects";
  86. my @z_moves = ();
  87. my @travel_moves = (); # array of scaled points
  88. my @extrusions = (); # array of scaled points
  89. my @temps = ();
  90. Slic3r::GCode::Reader->new->parse($gcode, sub {
  91. my ($self, $cmd, $args, $info) = @_;
  92. fail 'unexpected E argument' if defined $args->{E};
  93. if (defined $args->{Z}) {
  94. push @z_moves, $args->{Z};
  95. }
  96. if ($info->{dist_XY}) {
  97. if ($info->{extruding} || $args->{A}) {
  98. push @extrusions, Slic3r::Point->new_scale($info->{new_X}, $info->{new_Y});
  99. } else {
  100. push @travel_moves, Slic3r::Point->new_scale($info->{new_X}, $info->{new_Y})
  101. if @extrusions; # skip initial travel move to first skirt point
  102. }
  103. } elsif ($cmd eq 'M104' || $cmd eq 'M109') {
  104. push @temps, $args->{S} if !@temps || $args->{S} != $temps[-1];
  105. }
  106. });
  107. my $layer_count = 20/0.4; # cube is 20mm tall
  108. is scalar(@z_moves), 2*$layer_count, 'complete_objects generates the correct number of Z moves';
  109. is_deeply [ @z_moves[0..($layer_count-1)] ], [ @z_moves[$layer_count..$#z_moves] ], 'complete_objects generates the correct Z moves';
  110. my $convex_hull = convex_hull(\@extrusions);
  111. ok !(defined first { !$convex_hull->contains_point($_) } @travel_moves), 'all travel moves happen within skirt';
  112. is_deeply \@temps, [210, 200, 210, 200, 0], 'expected temperature changes';
  113. }
  114. {
  115. my $config = Slic3r::Config::new_from_defaults;
  116. $config->set('retract_length', [1000000]);
  117. $config->set('use_relative_e_distances', 1);
  118. my $print = Slic3r::Test::init_print('20mm_cube', config => $config);
  119. Slic3r::Test::gcode($print);
  120. ok $print->print->total_used_filament > 0, 'final retraction is not considered in total used filament';
  121. }
  122. {
  123. my $test = sub {
  124. my ($print, $comment) = @_;
  125. my @percent = ();
  126. my $got_100 = 0;
  127. my $extruding_after_100 = 0;
  128. Slic3r::GCode::Reader->new->parse(Slic3r::Test::gcode($print), sub {
  129. my ($self, $cmd, $args, $info) = @_;
  130. if ($cmd eq 'M73') {
  131. push @percent, $args->{P};
  132. $got_100 = 1 if $args->{P} eq '100';
  133. }
  134. if ($info->{extruding} && $got_100) {
  135. $extruding_after_100 = 1;
  136. }
  137. });
  138. # the extruder heater is turned off when M73 P100 is reached
  139. ok !(defined first { $_ > 100 } @percent), "M73 is never given more than 100% ($comment)";
  140. ok !$extruding_after_100, "no extrusions after M73 P100 ($comment)";
  141. };
  142. {
  143. my $config = Slic3r::Config::new_from_defaults;
  144. $config->set('gcode_flavor', 'sailfish');
  145. $config->set('raft_layers', 3);
  146. my $print = Slic3r::Test::init_print('20mm_cube', config => $config);
  147. $test->($print, 'single object');
  148. }
  149. {
  150. my $config = Slic3r::Config::new_from_defaults;
  151. $config->set('gcode_flavor', 'sailfish');
  152. my $print = Slic3r::Test::init_print('20mm_cube', config => $config, duplicate => 2);
  153. $test->($print, 'two copies of single object');
  154. }
  155. {
  156. my $config = Slic3r::Config::new_from_defaults;
  157. $config->set('gcode_flavor', 'sailfish');
  158. my $print = Slic3r::Test::init_print(['20mm_cube','20mm_cube'], config => $config);
  159. $test->($print, 'two objects');
  160. }
  161. {
  162. my $config = Slic3r::Config::new_from_defaults;
  163. $config->set('gcode_flavor', 'sailfish');
  164. my $print = Slic3r::Test::init_print('20mm_cube', config => $config, scale_xyz => [1,1, 1/(20/$config->layer_height) ]);
  165. $test->($print, 'one layer object');
  166. }
  167. }
  168. {
  169. my $config = Slic3r::Config::new_from_defaults;
  170. $config->set('start_gcode', 'START:[input_filename]');
  171. my $print = Slic3r::Test::init_print('20mm_cube', config => $config);
  172. my $gcode = Slic3r::Test::gcode($print);
  173. like $gcode, qr/START:20mm_cube/, '[input_filename] is also available in custom G-code';
  174. }
  175. {
  176. my $config = Slic3r::Config::new_from_defaults;
  177. $config->set('spiral_vase', 1);
  178. my $print = Slic3r::Test::init_print('cube_with_hole', config => $config);
  179. my $spiral = 0;
  180. Slic3r::GCode::Reader->new->parse(Slic3r::Test::gcode($print), sub {
  181. my ($self, $cmd, $args, $info) = @_;
  182. if ($cmd eq 'G1' && exists $args->{E} && exists $args->{Z}) {
  183. $spiral = 1;
  184. }
  185. });
  186. ok !$spiral, 'spiral vase is correctly disabled on layers with multiple loops';
  187. }
  188. {
  189. # Tests that the Repetier flavor produces M201 Xnnn Ynnn for resetting
  190. # acceleration, also that M204 Snnn syntax is not generated.
  191. my $config = Slic3r::Config::new_from_defaults;
  192. $config->set('gcode_flavor', 'repetier');
  193. $config->set('default_acceleration', 1337);
  194. my $print = Slic3r::Test::init_print('cube_with_hole', config => $config);
  195. my $has_accel = 0;
  196. my $has_m204 = 0;
  197. Slic3r::GCode::Reader->new->parse(Slic3r::Test::gcode($print), sub {
  198. my ($self, $cmd, $args, $info) = @_;
  199. if ($cmd eq 'M201' && exists $args->{X} && exists $args->{Y}) {
  200. if ($args->{X} == 1337 && $args->{Y} == 1337) {
  201. $has_accel = 1;
  202. }
  203. }
  204. if ($cmd eq 'M204' && exists $args->{S}) {
  205. $has_m204 = 1;
  206. }
  207. });
  208. ok $has_accel, 'M201 is generated for repetier firmware.';
  209. ok !$has_m204, 'M204 is not generated for repetier firmware';
  210. }
  211. __END__