1 //Copyright (c) 2019 Ultimaker B.V.
2 //CuraEngine is released under the terms of the AGPLv3 or higher.
3 
4 #include <gtest/gtest.h>
5 
6 #include "../src/settings/types/LayerIndex.h"
7 #include "../src/utils/Date.h" //To check the Griffin header.
8 #include "../src/Application.h" //To set up a slice with settings.
9 #include "../src/gcodeExport.h" //The unit under test.
10 #include "../src/Slice.h" //To set up a slice with settings.
11 #include "../src/RetractionConfig.h" //For extruder switch tests.
12 #include "../src/WipeScriptConfig.h" //For wipe sciprt tests.
13 #include "arcus/MockCommunication.h" //To prevent calls to any missing Communication class.
14 
15 namespace cura
16 {
17 
18 /*
19  * Fixture that provides a GCodeExport instance in a certain base state.
20  */
21 class GCodeExportTest : public testing::Test
22 {
23 public:
24     /*
25      * An export class to test with.
26      */
27     GCodeExport gcode;
28 
29     /*
30      * A stream to capture the output of the g-code export.
31      */
32     std::stringstream output;
33 
34     /*
35      * Mock away the communication channel where layer data is output by this
36      * class.
37      */
38     MockCommunication* mock_communication;
39 
SetUp()40     void SetUp()
41     {
42         output << std::fixed;
43         gcode.output_stream = &output;
44 
45         //Since GCodeExport doesn't support copying, we have to reset everything in-place.
46         gcode.currentPosition = Point3(0, 0, MM2INT(20));
47         gcode.layer_nr = 0;
48         gcode.current_e_value = 0;
49         gcode.current_e_offset = 0;
50         gcode.current_extruder = 0;
51         gcode.current_fan_speed = -1;
52         gcode.total_print_times = std::vector<Duration>(static_cast<unsigned char>(PrintFeatureType::NumPrintFeatureTypes), 0.0);
53         gcode.currentSpeed = 1;
54         gcode.current_print_acceleration = -1;
55         gcode.current_travel_acceleration = -1;
56         gcode.current_jerk = -1;
57         gcode.is_z_hopped = 0;
58         gcode.setFlavor(EGCodeFlavor::MARLIN);
59         gcode.initial_bed_temp = 0;
60         gcode.fan_number = 0;
61         gcode.total_bounding_box = AABB3D();
62         gcode.current_layer_z = 0;
63         gcode.relative_extrusion = false;
64 
65         gcode.new_line = "\n"; //Not BFB flavour by default.
66         gcode.machine_name = "Your favourite 3D printer";
67         gcode.machine_buildplate_type = "Your favourite build plate";
68 
69         //Set up a scene so that we may request settings.
70         Application::getInstance().current_slice = new Slice(1);
71         mock_communication = new MockCommunication();
72         Application::getInstance().communication = mock_communication;
73     }
74 
TearDown()75     void TearDown()
76     {
77         delete Application::getInstance().current_slice;
78         delete Application::getInstance().communication;
79         Application::getInstance().communication = nullptr;
80     }
81 };
82 
TEST_F(GCodeExportTest,CommentEmpty)83 TEST_F(GCodeExportTest, CommentEmpty)
84 {
85     gcode.writeComment("");
86     EXPECT_EQ(std::string(";\n"), output.str()) << "Semicolon and newline must exist but it must be empty for the rest.";
87 }
88 
TEST_F(GCodeExportTest,CommentSimple)89 TEST_F(GCodeExportTest, CommentSimple)
90 {
91     gcode.writeComment("extrude harder");
92     EXPECT_EQ(std::string(";extrude harder\n"), output.str()) << "Message must be preceded by a semicolon and ends with a newline..";
93 }
94 
TEST_F(GCodeExportTest,CommentMultiLine)95 TEST_F(GCodeExportTest, CommentMultiLine)
96 {
97     gcode.writeComment("If you catch a chinchilla in Chile\n"
98         "And cut off its beard, willy-nilly\n"
99         "You can honestly say\n"
100         "You made on that day\n"
101         "A Chilean chinchilla's chin chilly");
102     EXPECT_EQ(std::string(";If you catch a chinchilla in Chile\n"
103         ";And cut off its beard, willy-nilly\n"
104         ";You can honestly say\n"
105         ";You made on that day\n"
106         ";A Chilean chinchilla's chin chilly\n"), output.str()) << "Each line must be preceded by a semicolon.";
107 }
108 
TEST_F(GCodeExportTest,CommentMultiple)109 TEST_F(GCodeExportTest, CommentMultiple)
110 {
111     gcode.writeComment("Thunderbolt and lightning");
112     gcode.writeComment("Very very frightening me");
113     gcode.writeComment(" - Galileo (1638)");
114     EXPECT_EQ(std::string(";Thunderbolt and lightning\n"
115         ";Very very frightening me\n"
116         "; - Galileo (1638)\n"), output.str()) << "Semicolon before each line, and newline in between.";
117 }
118 
TEST_F(GCodeExportTest,CommentTimeZero)119 TEST_F(GCodeExportTest, CommentTimeZero)
120 {
121     gcode.writeTimeComment(0);
122     EXPECT_EQ(std::string(";TIME_ELAPSED:0.000000\n"), output.str());
123 }
124 
TEST_F(GCodeExportTest,CommentTimeInteger)125 TEST_F(GCodeExportTest, CommentTimeInteger)
126 {
127     gcode.writeTimeComment(42);
128     EXPECT_EQ(std::string(";TIME_ELAPSED:42.000000\n"), output.str()) << "The time must be fixed-radix to the microsecond.";
129 }
130 
TEST_F(GCodeExportTest,CommentTimeFloatRoundingError)131 TEST_F(GCodeExportTest, CommentTimeFloatRoundingError)
132 {
133     gcode.writeTimeComment(0.3);
134     EXPECT_EQ(std::string(";TIME_ELAPSED:0.300000\n"), output.str()) << "Don't output up to the precision of rounding errors.";
135 }
136 
TEST_F(GCodeExportTest,CommentTypeAllTypesCovered)137 TEST_F(GCodeExportTest, CommentTypeAllTypesCovered)
138 {
139     for (PrintFeatureType type = PrintFeatureType(0); type < PrintFeatureType::NumPrintFeatureTypes; type = PrintFeatureType(static_cast<size_t>(type) + 1))
140     {
141         gcode.writeTypeComment(type);
142         if (type == PrintFeatureType::MoveCombing || type == PrintFeatureType::MoveRetraction)
143         {
144             EXPECT_EQ(std::string(""), output.str()) << "Travel moves shouldn't output a type.";
145         }
146         else if (type == PrintFeatureType::NoneType)
147         {
148             EXPECT_EQ(std::string(""), output.str()) << "NoneType shouldn't output a type.";
149         }
150         else
151         {
152             EXPECT_EQ(std::string(";TYPE:"), output.str().substr(0, 6)) << "Type " << static_cast<size_t>(type) << " is not implemented.";
153         }
154         output.str(""); //Reset so that our next measurement is clean again.
155         output << std::fixed;
156     }
157 }
158 
TEST_F(GCodeExportTest,CommentLayer)159 TEST_F(GCodeExportTest, CommentLayer)
160 {
161     gcode.writeLayerComment(9);
162     EXPECT_EQ(std::string(";LAYER:9\n"), output.str()) << "Put the correct prefix and a newline afterwards.";
163 }
164 
TEST_F(GCodeExportTest,CommentLayerNegative)165 TEST_F(GCodeExportTest, CommentLayerNegative)
166 {
167     gcode.writeLayerComment(-3);
168     EXPECT_EQ(std::string(";LAYER:-3\n"), output.str());
169 }
170 
TEST_F(GCodeExportTest,CommentLayerCount)171 TEST_F(GCodeExportTest, CommentLayerCount)
172 {
173     gcode.writeLayerCountComment(5);
174     EXPECT_EQ(std::string(";LAYER_COUNT:5\n"), output.str());
175 }
176 
177 /*
178  * Parameterized test with different numbers of extruders.
179  */
180 class GriffinHeaderTest : public testing::TestWithParam<size_t>
181 {
182 public:
183     /*
184      * An export class to test with.
185      */
186     GCodeExport gcode;
187 
188     /*
189      * A stream to capture the output of the g-code export.
190      */
191     std::stringstream output;
192 
SetUp()193     void SetUp()
194     {
195         output << std::fixed;
196         gcode.output_stream = &output;
197 
198         //Since GCodeExport doesn't support copying, we have to reset everything in-place.
199         gcode.currentPosition = Point3(0, 0, MM2INT(20));
200         gcode.layer_nr = 0;
201         gcode.current_e_value = 0;
202         gcode.current_extruder = 0;
203         gcode.current_fan_speed = -1;
204         gcode.total_print_times = std::vector<Duration>(static_cast<unsigned char>(PrintFeatureType::NumPrintFeatureTypes), 0.0);
205         gcode.currentSpeed = 1;
206         gcode.current_print_acceleration = -1;
207         gcode.current_travel_acceleration = -1;
208         gcode.current_jerk = -1;
209         gcode.is_z_hopped = 0;
210         gcode.setFlavor(EGCodeFlavor::MARLIN);
211         gcode.initial_bed_temp = 0;
212         gcode.fan_number = 0;
213         gcode.total_bounding_box = AABB3D();
214 
215         gcode.new_line = "\n"; //Not BFB flavour by default.
216         gcode.machine_name = "Your favourite 3D printer";
217         gcode.machine_buildplate_type = "Your favourite build plate";
218 
219         //Set up a scene so that we may request settings.
220         Application::getInstance().current_slice = new Slice(0);
221     }
222 
TearDown()223     void TearDown()
224     {
225         delete Application::getInstance().current_slice;
226     }
227 };
228 
TEST_P(GriffinHeaderTest,HeaderGriffinFormat)229 TEST_P(GriffinHeaderTest, HeaderGriffinFormat)
230 {
231     const size_t num_extruders = GetParam();
232     gcode.flavor = EGCodeFlavor::GRIFFIN;
233     for (size_t extruder_index = 0; extruder_index < num_extruders; extruder_index++)
234     {
235         Application::getInstance().current_slice->scene.extruders.emplace_back(extruder_index, nullptr);
236         ExtruderTrain& train = Application::getInstance().current_slice->scene.extruders.back();
237         train.settings.add("machine_nozzle_size", "0.4");
238         train.settings.add("machine_nozzle_id", "TestNozzle");
239     }
240 
241     const std::vector<bool> extruder_is_used(num_extruders, true);
242     std::istringstream result(gcode.getFileHeader(extruder_is_used));
243     std::string token;
244 
245     std::getline(result, token, '\n');
246     EXPECT_EQ(std::string(";START_OF_HEADER"), token);
247     std::getline(result, token, '\n');
248     EXPECT_EQ(std::string(";HEADER_VERSION:"), token.substr(0, 16)); //Actual version doesn't matter in this test.
249     std::getline(result, token, '\n');
250     EXPECT_EQ(std::string(";FLAVOR:Griffin"), token);
251     std::getline(result, token, '\n');
252     EXPECT_EQ(std::string(";GENERATOR.NAME:Cura_SteamEngine"), token);
253     std::getline(result, token, '\n');
254     EXPECT_EQ(std::string(";GENERATOR.VERSION:"), token.substr(0, 19));
255     EXPECT_EQ(std::string("master"), token.substr(19));
256     std::getline(result, token, '\n');
257     EXPECT_EQ(std::string(";GENERATOR.BUILD_DATE:"), token.substr(0, 22));
258     EXPECT_EQ(Date::getDate().toStringDashed(), token.substr(22));
259     std::getline(result, token, '\n');
260     EXPECT_EQ(std::string(";TARGET_MACHINE.NAME:"), token.substr(0, 21));
261     EXPECT_EQ(gcode.machine_name, token.substr(21));
262 
263     for (size_t extruder_nr = 0; extruder_nr < num_extruders; extruder_nr++)
264     {
265         std::getline(result, token, '\n');
266         EXPECT_EQ(std::string(";EXTRUDER_TRAIN."), token.substr(0, 16));
267         EXPECT_EQ(std::to_string(extruder_nr), token.substr(16, 1)); //TODO: Assumes the extruder nr is 1 digit.
268         EXPECT_EQ(std::string(".INITIAL_TEMPERATURE:"), token.substr(17, 21)); //Actual temperature doesn't matter.
269         std::getline(result, token, '\n');
270         EXPECT_EQ(std::string(";EXTRUDER_TRAIN."), token.substr(0, 16));
271         EXPECT_EQ(std::to_string(extruder_nr), token.substr(16, 1)); //TODO: Assumes the extruder nr is 1 digit.
272         EXPECT_EQ(std::string(".NOZZLE.DIAMETER:0.4"), token.substr(17, 20)); //Nozzle size needs to be equal to the machine_nozzle_size setting.
273         std::getline(result, token, '\n');
274         EXPECT_EQ(std::string(";EXTRUDER_TRAIN."), token.substr(0, 16));
275         EXPECT_EQ(std::to_string(extruder_nr), token.substr(16, 1)); //TODO: Assumes the extruder nr is 1 digit.
276         EXPECT_EQ(std::string(".NOZZLE.NAME:TestNozzle"), token.substr(17, 23)); //Nozzle name needs to be equal to the machine_nozzle_id setting.
277     }
278 
279     std::getline(result, token, '\n');
280     EXPECT_EQ(std::string(";BUILD_PLATE.TYPE:"), token.substr(0, 18));
281     EXPECT_EQ(gcode.machine_buildplate_type, token.substr(18));
282     std::getline(result, token, '\n');
283     EXPECT_EQ(std::string(";BUILD_PLATE.INITIAL_TEMPERATURE:"), token.substr(0, 33)); //Actual temperature doesn't matter in this test.
284     std::getline(result, token, '\n');
285     EXPECT_EQ(std::string(";PRINT.GROUPS:0"), token);
286     std::getline(result, token, '\n');
287     EXPECT_EQ(std::string(";PRINT.SIZE.MIN.X:"), token.substr(0, 18)); //Actual bounds don't matter in this test.
288     std::getline(result, token, '\n');
289     EXPECT_EQ(std::string(";PRINT.SIZE.MIN.Y:"), token.substr(0, 18));
290     std::getline(result, token, '\n');
291     EXPECT_EQ(std::string(";PRINT.SIZE.MIN.Z:"), token.substr(0, 18));
292     std::getline(result, token, '\n');
293     EXPECT_EQ(std::string(";PRINT.SIZE.MAX.X:"), token.substr(0, 18));
294     std::getline(result, token, '\n');
295     EXPECT_EQ(std::string(";PRINT.SIZE.MAX.Y:"), token.substr(0, 18));
296     std::getline(result, token, '\n');
297     EXPECT_EQ(std::string(";PRINT.SIZE.MAX.Z:"), token.substr(0, 18));
298     std::getline(result, token, '\n');
299     EXPECT_EQ(std::string(";END_OF_HEADER"), token);
300 }
301 
302 INSTANTIATE_TEST_CASE_P(GriffinHeaderTestInstantiation, GriffinHeaderTest, testing::Values(0, 1, 2, 9));
303 
304 /*
305  * Test the default header generation.
306  */
TEST_F(GCodeExportTest,HeaderUltiGCode)307 TEST_F(GCodeExportTest, HeaderUltiGCode)
308 {
309     gcode.flavor = EGCodeFlavor::ULTIGCODE;
310     constexpr size_t num_extruders = 2;
311     const std::vector<bool> extruder_is_used(num_extruders, true);
312     constexpr Duration print_time = 1337;
313     const std::vector<double> filament_used = {100, 200};
314     for (size_t extruder_index = 0; extruder_index < num_extruders; extruder_index++)
315     {
316         Application::getInstance().current_slice->scene.extruders.emplace_back(extruder_index, nullptr);
317         ExtruderTrain& train = Application::getInstance().current_slice->scene.extruders.back();
318         train.settings.add("machine_nozzle_size", "0.4");
319     }
320     gcode.total_bounding_box = AABB3D(Point3(0, 0, 0), Point3(1000, 1000, 1000));
321 
322     std::string result = gcode.getFileHeader(extruder_is_used, &print_time, filament_used);
323 
324     EXPECT_EQ(result, ";FLAVOR:UltiGCode\n;TIME:1337\n;MATERIAL:100\n;MATERIAL2:200\n;NOZZLE_DIAMETER:0.4\n;MINX:0\n;MINY:0\n;MINZ:0\n;MAXX:1\n;MAXY:1\n;MAXZ:1\n");
325 }
326 
TEST_F(GCodeExportTest,HeaderRepRap)327 TEST_F(GCodeExportTest, HeaderRepRap)
328 {
329     Application::getInstance().current_slice->scene.current_mesh_group->settings.add("layer_height", "0.123");
330     gcode.flavor = EGCodeFlavor::REPRAP;
331     gcode.extruder_attr[0].filament_area = 5.0;
332     gcode.extruder_attr[1].filament_area = 4.0;
333     constexpr size_t num_extruders = 2;
334     const std::vector<bool> extruder_is_used(num_extruders, true);
335     constexpr Duration print_time = 1337;
336     const std::vector<double> filament_used = {100, 200};
337     gcode.total_bounding_box = AABB3D(Point3(0, 0, 0), Point3(1000, 1000, 1000));
338 
339     std::string result = gcode.getFileHeader(extruder_is_used, &print_time, filament_used);
340 
341     EXPECT_EQ(result, ";FLAVOR:RepRap\n;TIME:1337\n;Filament used: 0.02m, 0.05m\n;Layer height: 0.123\n;MINX:0\n;MINY:0\n;MINZ:0\n;MAXX:1\n;MAXY:1\n;MAXZ:1\n");
342 }
343 
TEST_F(GCodeExportTest,HeaderMarlin)344 TEST_F(GCodeExportTest, HeaderMarlin)
345 {
346     Application::getInstance().current_slice->scene.current_mesh_group->settings.add("layer_height", "0.123");
347     gcode.flavor = EGCodeFlavor::MARLIN;
348     gcode.extruder_attr[0].filament_area = 5.0;
349     gcode.extruder_attr[1].filament_area = 4.0;
350     constexpr size_t num_extruders = 2;
351     const std::vector<bool> extruder_is_used(num_extruders, true);
352     constexpr Duration print_time = 1337;
353     const std::vector<double> filament_used = {100, 200};
354     gcode.total_bounding_box = AABB3D(Point3(0, 0, 0), Point3(1000, 1000, 1000));
355 
356     std::string result = gcode.getFileHeader(extruder_is_used, &print_time, filament_used);
357 
358     EXPECT_EQ(result, ";FLAVOR:Marlin\n;TIME:1337\n;Filament used: 0.02m, 0.05m\n;Layer height: 0.123\n;MINX:0\n;MINY:0\n;MINZ:0\n;MAXX:1\n;MAXY:1\n;MAXZ:1\n");
359 }
360 
TEST_F(GCodeExportTest,HeaderMarlinVolumetric)361 TEST_F(GCodeExportTest, HeaderMarlinVolumetric)
362 {
363     Application::getInstance().current_slice->scene.current_mesh_group->settings.add("layer_height", "0.123");
364     gcode.flavor = EGCodeFlavor::MARLIN_VOLUMATRIC;
365     constexpr size_t num_extruders = 2;
366     const std::vector<bool> extruder_is_used(num_extruders, true);
367     constexpr Duration print_time = 1337;
368     const std::vector<double> filament_used = {100, 200};
369     gcode.total_bounding_box = AABB3D(Point3(0, 0, 0), Point3(1000, 1000, 1000));
370 
371     std::string result = gcode.getFileHeader(extruder_is_used, &print_time, filament_used);
372 
373     EXPECT_EQ(result, ";FLAVOR:Marlin(Volumetric)\n;TIME:1337\n;Filament used: 100mm3, 200mm3\n;Layer height: 0.123\n;MINX:0\n;MINY:0\n;MINZ:0\n;MAXX:1\n;MAXY:1\n;MAXZ:1\n");
374 }
375 
376 /*
377  * Test conversion from E values to millimetres and back in the case of a
378  * volumetric printer.
379  */
TEST_F(GCodeExportTest,EVsMmVolumetric)380 TEST_F(GCodeExportTest, EVsMmVolumetric)
381 {
382     constexpr double filament_area = 10.0;
383     gcode.extruder_attr[0].filament_area = filament_area;
384     gcode.is_volumetric = true;
385 
386     constexpr double mm3_input = 15.0;
387     EXPECT_EQ(gcode.mm3ToE(mm3_input), mm3_input) << "Since the E is volumetric and the input mm3 is also volumetric, the output needs to be the same.";
388 
389     EXPECT_EQ(gcode.eToMm(200.0), 200.0 / filament_area) << "Since the E is volumetric but mm is linear, divide by the cross-sectional area of the filament to convert the volume to a length.";
390 
391     constexpr double mm_input = 33.0;
392     EXPECT_EQ(gcode.mmToE(mm_input), mm_input * filament_area) << "Since the input mm is linear but the E output must be volumetric, we need to multiply by the cross-sectional area to convert length to volume.";
393 
394     constexpr double e_input = 100.0;
395     EXPECT_EQ(gcode.eToMm3(e_input, 0), e_input) << "Since the E is volumetric and mm3 is also volumetric, the output needs to be the same.";
396 }
397 
398 /*
399  * Test conversion from E values to millimetres and back in the case where the E
400  * value represents the linear position of the filament.
401  */
TEST_F(GCodeExportTest,EVsMmLinear)402 TEST_F(GCodeExportTest, EVsMmLinear)
403 {
404     constexpr double filament_area = 10.0;
405     gcode.extruder_attr[0].filament_area = filament_area;
406     gcode.is_volumetric = false;
407 
408     EXPECT_EQ(gcode.mmToE(15.0), 15.0) << "Since the E is linear and the input mm is also linear, the output needs to be the same.";
409     EXPECT_EQ(gcode.eToMm(15.0), 15.0) << "Since the E is linear and the output mm is also linear, the output needs to be the same.";
410 
411     for(double x = -1000.0; x < 1000.0; x += 16.0)
412     {
413         EXPECT_DOUBLE_EQ(gcode.mmToE(gcode.eToMm(x)), x) << "Converting back and forth should lead to the same number.";
414     }
415 
416     constexpr double mm3_input = 33.0;
417     EXPECT_EQ(gcode.mm3ToE(mm3_input), mm3_input / filament_area) << "Since the input mm3 is volumetric but the E output must be linear, we need to divide by the cross-sectional area to convert volume to length.";
418 
419     constexpr double e_input = 100.0;
420     EXPECT_EQ(gcode.eToMm3(e_input, 0), e_input * filament_area) << "Since the input E is linear but the output must be volumetric, we need to multiply by cross-sectional area to convert length to volume.";
421 }
422 
423 /*
424  * Switch extruders, with the following special cases:
425  * - No retraction distance.
426  */
TEST_F(GCodeExportTest,SwitchExtruderSimple)427 TEST_F(GCodeExportTest, SwitchExtruderSimple)
428 {
429     Scene& scene = Application::getInstance().current_slice->scene;
430 
431     scene.extruders.emplace_back(0, nullptr);
432     ExtruderTrain& train1 = scene.extruders.back();
433     train1.settings.add("machine_extruder_start_code", ";FIRST EXTRUDER START G-CODE!");
434     train1.settings.add("machine_extruder_end_code", ";FIRST EXTRUDER END G-CODE!");
435     train1.settings.add("machine_firmware_retract", "True");
436     train1.settings.add("retraction_enable", "True");
437     scene.extruders.emplace_back(1, nullptr);
438     ExtruderTrain& train2 = scene.extruders.back();
439     train2.settings.add("machine_extruder_start_code", ";SECOND EXTRUDER START G-CODE!");
440     train2.settings.add("machine_extruder_end_code", ";SECOND EXTRUDER END G-CODE!");
441     train2.settings.add("machine_firmware_retract", "True");
442     train2.settings.add("retraction_enable", "True");
443 
444     RetractionConfig no_retraction;
445     no_retraction.distance = 0;
446 
447     EXPECT_CALL(*mock_communication, setExtruderForSend(testing::_));
448     EXPECT_CALL(*mock_communication, sendCurrentPosition(testing::_));
449     gcode.switchExtruder(1, no_retraction);
450 
451     EXPECT_EQ(std::string("G92 E0\n;FIRST EXTRUDER END G-CODE!\nT1\nG92 E0\n;SECOND EXTRUDER START G-CODE!\n"), output.str());
452 }
453 
TEST_F(GCodeExportTest,WriteZHopStartZero)454 TEST_F(GCodeExportTest, WriteZHopStartZero)
455 {
456     gcode.writeZhopStart(0);
457     EXPECT_EQ(std::string(""), output.str()) << "Zero length z hop shouldn't affect gcode output.";
458 }
459 
TEST_F(GCodeExportTest,WriteZHopStartDefaultSpeed)460 TEST_F(GCodeExportTest, WriteZHopStartDefaultSpeed)
461 {
462     Application::getInstance().current_slice->scene.extruders.emplace_back(0, nullptr);
463     Application::getInstance().current_slice->scene.extruders[gcode.current_extruder].settings.add("speed_z_hop", "1"); //60mm/min.
464     gcode.current_layer_z = 2000;
465     constexpr coord_t hop_height = 3000;
466     gcode.writeZhopStart(hop_height);
467     EXPECT_EQ(std::string("G1 F60 Z5\n"), output.str());
468 }
469 
TEST_F(GCodeExportTest,WriteZHopStartCustomSpeed)470 TEST_F(GCodeExportTest, WriteZHopStartCustomSpeed)
471 {
472     Application::getInstance().current_slice->scene.extruders.emplace_back(0, nullptr);
473     Application::getInstance().current_slice->scene.extruders[gcode.current_extruder].settings.add("speed_z_hop", "1"); //60mm/min.
474     gcode.current_layer_z = 2000;
475     constexpr coord_t hop_height = 3000;
476     constexpr Velocity speed = 4; // 240 mm/min.
477     gcode.writeZhopStart(hop_height, speed);
478     EXPECT_EQ(std::string("G1 F240 Z5\n"), output.str()) << "Custom provided speed should be used.";
479 }
480 
TEST_F(GCodeExportTest,WriteZHopEndZero)481 TEST_F(GCodeExportTest, WriteZHopEndZero)
482 {
483     gcode.is_z_hopped = 0;
484     gcode.writeZhopEnd();
485     EXPECT_EQ(std::string(""), output.str()) << "Zero length z hop shouldn't affect gcode output.";
486 }
487 
TEST_F(GCodeExportTest,WriteZHopEndDefaultSpeed)488 TEST_F(GCodeExportTest, WriteZHopEndDefaultSpeed)
489 {
490     Application::getInstance().current_slice->scene.extruders.emplace_back(0, nullptr);
491     Application::getInstance().current_slice->scene.extruders[gcode.current_extruder].settings.add("speed_z_hop", "1"); //60mm/min.
492     gcode.current_layer_z = 2000;
493     gcode.is_z_hopped = 3000;
494     gcode.writeZhopEnd();
495     EXPECT_EQ(std::string("G1 F60 Z2\n"), output.str());
496 }
497 
TEST_F(GCodeExportTest,WriteZHopEndCustomSpeed)498 TEST_F(GCodeExportTest, WriteZHopEndCustomSpeed)
499 {
500     Application::getInstance().current_slice->scene.extruders.emplace_back(0, nullptr);
501     Application::getInstance().current_slice->scene.extruders[gcode.current_extruder].settings.add("speed_z_hop", "1");
502     gcode.current_layer_z = 2000;
503     gcode.is_z_hopped = 3000;
504     constexpr Velocity speed = 4; // 240 mm/min.
505     gcode.writeZhopEnd(speed);
506     EXPECT_EQ(std::string("G1 F240 Z2\n"), output.str()) << "Custom provided speed should be used.";
507 }
508 
TEST_F(GCodeExportTest,insertWipeScriptSingleMove)509 TEST_F(GCodeExportTest, insertWipeScriptSingleMove)
510 {
511     gcode.currentPosition = Point3(1000, 1000, 1000);
512     gcode.current_layer_z = 1000;
513     gcode.use_extruder_offset_to_offset_coords = false;
514     Application::getInstance().current_slice->scene.current_mesh_group->settings.add("layer_height", "0.2");
515 
516     WipeScriptConfig config;
517     config.retraction_enable = false;
518     config.hop_enable = false;
519     config.brush_pos_x = 2000;
520     config.repeat_count = 1;
521     config.move_distance = 500;
522     config.move_speed = 10;
523     config.pause = 0;
524 
525     EXPECT_CALL(*mock_communication, sendLineTo(testing::_, testing::_, testing::_, testing::_, testing::_)).Times(3);
526     gcode.insertWipeScript(config);
527 
528     std::string token;
529     std::getline(output, token, '\n');
530     EXPECT_EQ(std::string(";WIPE_SCRIPT_BEGIN"), token) << "Wipe script should always start with tag.";
531     std::getline(output, token, '\n');
532     EXPECT_EQ(std::string("G0 F600 X2 Y1"), token) << "Wipe script should go to its position.";
533     std::getline(output, token, '\n');
534     EXPECT_EQ(std::string("G0 X2.5 Y1"), token) << "There should be one wipe move.";
535     std::getline(output, token, '\n');
536     EXPECT_EQ(std::string("G0 X1 Y1"), token) << "Wipe script should return back to position before wipe.";
537     std::getline(output, token, '\n');
538     EXPECT_EQ(std::string(";WIPE_SCRIPT_END"), token) << "Wipe script should always end with tag.";
539 }
540 
TEST_F(GCodeExportTest,insertWipeScriptMultipleMoves)541 TEST_F(GCodeExportTest, insertWipeScriptMultipleMoves)
542 {
543     gcode.currentPosition = Point3(1000, 1000, 1000);
544     gcode.current_layer_z = 1000;
545     gcode.use_extruder_offset_to_offset_coords = false;
546     Application::getInstance().current_slice->scene.current_mesh_group->settings.add("layer_height", "0.2");
547 
548     WipeScriptConfig config;
549     config.retraction_enable = false;
550     config.hop_enable = false;
551     config.brush_pos_x = 2000;
552     config.repeat_count = 4;
553     config.move_distance = 500;
554     config.move_speed = 10;
555     config.pause = 0;
556 
557     EXPECT_CALL(*mock_communication, sendLineTo(testing::_, testing::_, testing::_, testing::_, testing::_)).Times(6);
558     gcode.insertWipeScript(config);
559 
560     std::string token;
561     std::getline(output, token, '\n');
562     EXPECT_EQ(std::string(";WIPE_SCRIPT_BEGIN"), token) << "Wipe script should always start with tag.";
563     std::getline(output, token, '\n');
564     EXPECT_EQ(std::string("G0 F600 X2 Y1"), token) << "Wipe script should go to its position.";
565     std::getline(output, token, '\n');
566     EXPECT_EQ(std::string("G0 X2.5 Y1"), token);
567     std::getline(output, token, '\n');
568     EXPECT_EQ(std::string("G0 X2 Y1"), token);
569     std::getline(output, token, '\n');
570     EXPECT_EQ(std::string("G0 X2.5 Y1"), token);
571     std::getline(output, token, '\n');
572     EXPECT_EQ(std::string("G0 X2 Y1"), token);
573     std::getline(output, token, '\n');
574     EXPECT_EQ(std::string("G0 X1 Y1"), token) << "Wipe script should return back to position before wipe.";
575     std::getline(output, token, '\n');
576     EXPECT_EQ(std::string(";WIPE_SCRIPT_END"), token) << "Wipe script should always end with tag.";
577 }
578 
TEST_F(GCodeExportTest,insertWipeScriptOptionalDelay)579 TEST_F(GCodeExportTest, insertWipeScriptOptionalDelay)
580 {
581     gcode.currentPosition = Point3(1000, 1000, 1000);
582     gcode.current_layer_z = 1000;
583     gcode.use_extruder_offset_to_offset_coords = false;
584     Application::getInstance().current_slice->scene.current_mesh_group->settings.add("layer_height", "0.2");
585 
586     WipeScriptConfig config;
587     config.retraction_enable = false;
588     config.hop_enable = false;
589     config.brush_pos_x = 2000;
590     config.repeat_count = 1;
591     config.move_distance = 500;
592     config.move_speed = 10;
593     config.pause = 1.5; // 1.5 sec = 1500 ms.
594 
595     EXPECT_CALL(*mock_communication, sendLineTo(testing::_, testing::_, testing::_, testing::_, testing::_)).Times(3);
596     gcode.insertWipeScript(config);
597 
598     std::string token;
599     std::getline(output, token, '\n');
600     EXPECT_EQ(std::string(";WIPE_SCRIPT_BEGIN"), token) << "Wipe script should always start with tag.";
601     std::getline(output, token, '\n'); // go to wipe position
602     std::getline(output, token, '\n'); // make wipe move
603     std::getline(output, token, '\n'); // return back
604     std::getline(output, token, '\n');
605     EXPECT_EQ(std::string("G4 P1500"), token) << "Wipe script should make a delay.";
606     std::getline(output, token, '\n');
607     EXPECT_EQ(std::string(";WIPE_SCRIPT_END"), token) << "Wipe script should always end with tag.";
608 }
609 
TEST_F(GCodeExportTest,insertWipeScriptRetractionEnable)610 TEST_F(GCodeExportTest, insertWipeScriptRetractionEnable)
611 {
612     gcode.currentPosition = Point3(1000, 1000, 1000);
613     gcode.current_layer_z = 1000;
614     gcode.current_e_value = 100;
615     gcode.use_extruder_offset_to_offset_coords = false;
616     gcode.is_volumetric = false;
617     gcode.current_extruder = 0;
618     gcode.extruder_attr[0].filament_area = 10.0;
619     gcode.relative_extrusion = false;
620     gcode.currentSpeed = 1;
621     Application::getInstance().current_slice->scene.current_mesh_group->settings.add("layer_height", "0.2");
622     Application::getInstance().current_slice->scene.extruders.emplace_back(0, &Application::getInstance().current_slice->scene.current_mesh_group->settings);
623     Application::getInstance().current_slice->scene.extruders.back().settings.add("machine_firmware_retract", "false");
624 
625     WipeScriptConfig config;
626     config.retraction_enable = true;
627     config.retraction_config.distance = 1;
628     config.retraction_config.speed = 2; // 120 mm/min.
629     config.retraction_config.primeSpeed = 3; // 180 mm/min.
630     config.retraction_config.prime_volume = gcode.extruder_attr[0].filament_area * 4; // 4mm in linear dimensions
631     config.hop_enable = false;
632     config.brush_pos_x = 2000;
633     config.repeat_count = 1;
634     config.move_distance = 500;
635     config.move_speed = 10;
636     config.pause = 0;
637 
638     EXPECT_CALL(*mock_communication, sendLineTo(testing::_, testing::_, testing::_, testing::_, testing::_)).Times(3);
639     gcode.insertWipeScript(config);
640 
641     std::string token;
642     std::getline(output, token, '\n');
643     EXPECT_EQ(std::string(";WIPE_SCRIPT_BEGIN"), token) << "Wipe script should always start with tag.";
644     std::getline(output, token, '\n');
645     EXPECT_EQ(std::string("G1 F120 E99"), token) << "Wipe script should perform retraction with provided speed and retraction distance.";
646     std::getline(output, token, '\n'); // go to wipe position
647     std::getline(output, token, '\n'); // make wipe move
648     std::getline(output, token, '\n'); // return back
649     std::getline(output, token, '\n');
650     EXPECT_EQ(std::string("G1 F180 E104"), token) << "Wipe script should make unretraction with provided speed and extra prime volume.";
651     std::getline(output, token, '\n');
652     EXPECT_EQ(std::string(";WIPE_SCRIPT_END"), token) << "Wipe script should always end with tag.";
653 }
654 
TEST_F(GCodeExportTest,insertWipeScriptHopEnable)655 TEST_F(GCodeExportTest, insertWipeScriptHopEnable)
656 {
657     gcode.currentPosition = Point3(1000, 1000, 1000);
658     gcode.current_layer_z = 1000;
659     gcode.use_extruder_offset_to_offset_coords = false;
660     gcode.currentSpeed = 1;
661     Application::getInstance().current_slice->scene.current_mesh_group->settings.add("layer_height", "0.2");
662 
663     WipeScriptConfig config;
664     config.retraction_enable = false;
665     config.hop_enable = true;
666     config.hop_speed = 2; // 120 mm/min.
667     config.hop_amount = 300;
668     config.brush_pos_x = 2000;
669     config.repeat_count = 1;
670     config.move_distance = 500;
671     config.move_speed = 10;
672     config.pause = 0;
673 
674     EXPECT_CALL(*mock_communication, sendLineTo(testing::_, testing::_, testing::_, testing::_, testing::_)).Times(3);
675     gcode.insertWipeScript(config);
676 
677     std::string token;
678     std::getline(output, token, '\n');
679     EXPECT_EQ(std::string(";WIPE_SCRIPT_BEGIN"), token) << "Wipe script should always start with tag.";
680     std::getline(output, token, '\n');
681     EXPECT_EQ(std::string("G1 F120 Z1.3"), token) << "Wipe script should perform z-hop.";
682     std::getline(output, token, '\n'); // go to wipe position
683     std::getline(output, token, '\n'); // make wipe move
684     std::getline(output, token, '\n'); // return back
685     std::getline(output, token, '\n');
686     EXPECT_EQ(std::string("G1 F120 Z1"), token) << "Wipe script should return z position.";
687     std::getline(output, token, '\n');
688     EXPECT_EQ(std::string(";WIPE_SCRIPT_END"), token) << "Wipe script should always end with tag.";
689 }
690 
691 } //namespace cura
692