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