1 //Copyright (c) 2019 Ultimaker B.V.
2 //CuraEngine is released under the terms of the AGPLv3 or higher.
3 
4 #include <assert.h>
5 #include <cmath>
6 #include <iomanip>
7 #include <stdarg.h>
8 
9 #include "Application.h" //To send layer view data.
10 #include "ExtruderTrain.h"
11 #include "gcodeExport.h"
12 #include "PrintFeature.h"
13 #include "RetractionConfig.h"
14 #include "Slice.h"
15 #include "communication/Communication.h" //To send layer view data.
16 #include "settings/types/LayerIndex.h"
17 #include "utils/Date.h"
18 #include "utils/logoutput.h"
19 #include "utils/string.h" // MMtoStream, PrecisionedDouble
20 #include "WipeScriptConfig.h"
21 
22 namespace cura {
23 
transliterate(const std::string & text)24 std::string transliterate(const std::string& text)
25 {
26     // For now, just replace all non-ascii characters with '?'.
27     // This function can be expaned if we need more complex transliteration.
28     std::ostringstream stream;
29     for (const char& c : text)
30     {
31         stream << static_cast<char>((c >= 0) ? c : '?');
32     }
33     return stream.str();
34 }
35 
GCodeExport()36 GCodeExport::GCodeExport()
37 : output_stream(&std::cout)
38 , currentPosition(0,0,MM2INT(20))
39 , layer_nr(0)
40 , relative_extrusion(false)
41 {
42     *output_stream << std::fixed;
43 
44     current_e_value = 0;
45     current_extruder = 0;
46     current_fan_speed = -1;
47 
48     total_print_times = std::vector<Duration>(static_cast<unsigned char>(PrintFeatureType::NumPrintFeatureTypes), 0.0);
49 
50     currentSpeed = 1;
51     current_print_acceleration = -1;
52     current_travel_acceleration = -1;
53     current_jerk = -1;
54 
55     is_z_hopped = 0;
56     setFlavor(EGCodeFlavor::MARLIN);
57     initial_bed_temp = 0;
58     build_volume_temperature = 0;
59     machine_heated_build_volume = false;
60 
61     fan_number = 0;
62     use_extruder_offset_to_offset_coords = false;
63     machine_name = "";
64     machine_buildplate_type = "";
65     relative_extrusion = false;
66     new_line = "\n";
67 
68     total_bounding_box = AABB3D();
69 }
70 
~GCodeExport()71 GCodeExport::~GCodeExport()
72 {
73 }
74 
preSetup(const size_t start_extruder)75 void GCodeExport::preSetup(const size_t start_extruder)
76 {
77     current_extruder = start_extruder;
78 
79     const Scene& scene = Application::getInstance().current_slice->scene;
80     std::vector<MeshGroup>::iterator mesh_group = scene.current_mesh_group;
81     setFlavor(mesh_group->settings.get<EGCodeFlavor>("machine_gcode_flavor"));
82     use_extruder_offset_to_offset_coords = mesh_group->settings.get<bool>("machine_use_extruder_offset_to_offset_coords");
83     const size_t extruder_count = Application::getInstance().current_slice->scene.extruders.size();
84 
85     for (size_t extruder_nr = 0; extruder_nr < extruder_count; extruder_nr++)
86     {
87         const ExtruderTrain& train = scene.extruders[extruder_nr];
88         setFilamentDiameter(extruder_nr, train.settings.get<coord_t>("material_diameter"));
89 
90         extruder_attr[extruder_nr].last_retraction_prime_speed = train.settings.get<Velocity>("retraction_prime_speed"); // the alternative would be switch_extruder_prime_speed, but dual extrusion might not even be configured...
91         extruder_attr[extruder_nr].fan_number = train.settings.get<size_t>("machine_extruder_cooling_fan_number");
92     }
93 
94     machine_name = mesh_group->settings.get<std::string>("machine_name");
95     machine_buildplate_type = mesh_group->settings.get<std::string>("machine_buildplate_type");
96 
97     relative_extrusion = mesh_group->settings.get<bool>("relative_extrusion");
98     always_write_active_tool = mesh_group->settings.get<bool>("machine_always_write_active_tool");
99 
100     if (flavor == EGCodeFlavor::BFB)
101     {
102         new_line = "\r\n";
103     }
104     else
105     {
106         new_line = "\n";
107     }
108 
109     estimateCalculator.setFirmwareDefaults(mesh_group->settings);
110 }
111 
setInitialAndBuildVolumeTemps(const unsigned int start_extruder_nr)112 void GCodeExport::setInitialAndBuildVolumeTemps(const unsigned int start_extruder_nr)
113 {
114     const Scene& scene = Application::getInstance().current_slice->scene;
115     const size_t extruder_count = Application::getInstance().current_slice->scene.extruders.size();
116     for (size_t extruder_nr = 0; extruder_nr < extruder_count; extruder_nr++)
117     {
118         const ExtruderTrain& train = scene.extruders[extruder_nr];
119 
120         const Temperature print_temp_0 = train.settings.get<Temperature>("material_print_temperature_layer_0");
121         const Temperature print_temp_here = (print_temp_0 != 0)? print_temp_0 : train.settings.get<Temperature>("material_print_temperature");
122         const Temperature temp = (extruder_nr == start_extruder_nr)? print_temp_here : train.settings.get<Temperature>("material_standby_temperature");
123         setInitialTemp(extruder_nr, temp);
124     }
125 
126     initial_bed_temp = scene.current_mesh_group->settings.get<Temperature>("material_bed_temperature_layer_0");
127     machine_heated_build_volume = scene.current_mesh_group->settings.get<bool>("machine_heated_build_volume");
128     build_volume_temperature = machine_heated_build_volume ? scene.current_mesh_group->settings.get<Temperature>("build_volume_temperature") : Temperature(0);
129 }
130 
setInitialTemp(int extruder_nr,double temp)131 void GCodeExport::setInitialTemp(int extruder_nr, double temp)
132 {
133     extruder_attr[extruder_nr].initial_temp = temp;
134     if (flavor == EGCodeFlavor::GRIFFIN || flavor == EGCodeFlavor::ULTIGCODE)
135     {
136         extruder_attr[extruder_nr].currentTemperature = temp;
137     }
138 }
139 
flavorToString(const EGCodeFlavor & flavor) const140 const std::string GCodeExport::flavorToString(const EGCodeFlavor& flavor) const
141 {
142     switch (flavor)
143     {
144         case EGCodeFlavor::BFB:
145             return "BFB";
146         case EGCodeFlavor::MACH3:
147             return "Mach3";
148         case EGCodeFlavor::MAKERBOT:
149             return "Makerbot";
150         case EGCodeFlavor::ULTIGCODE:
151             return "UltiGCode";
152         case EGCodeFlavor::MARLIN_VOLUMATRIC:
153             return "Marlin(Volumetric)";
154         case EGCodeFlavor::GRIFFIN:
155             return "Griffin";
156         case EGCodeFlavor::REPETIER:
157             return "Repetier";
158         case EGCodeFlavor::REPRAP:
159             return "RepRap";
160         case EGCodeFlavor::MARLIN:
161         default:
162             return "Marlin";
163     }
164 }
165 
getFileHeader(const std::vector<bool> & extruder_is_used,const Duration * print_time,const std::vector<double> & filament_used,const std::vector<std::string> & mat_ids)166 std::string GCodeExport::getFileHeader(const std::vector<bool>& extruder_is_used, const Duration* print_time, const std::vector<double>& filament_used, const std::vector<std::string>& mat_ids)
167 {
168     std::ostringstream prefix;
169 
170     const size_t extruder_count = Application::getInstance().current_slice->scene.extruders.size();
171     switch (flavor)
172     {
173     case EGCodeFlavor::GRIFFIN:
174         prefix << ";START_OF_HEADER" << new_line;
175         prefix << ";HEADER_VERSION:0.1" << new_line;
176         prefix << ";FLAVOR:" << flavorToString(flavor) << new_line;
177         prefix << ";GENERATOR.NAME:Cura_SteamEngine" << new_line;
178         prefix << ";GENERATOR.VERSION:" << VERSION << new_line;
179         prefix << ";GENERATOR.BUILD_DATE:" << Date::getDate().toStringDashed() << new_line;
180         prefix << ";TARGET_MACHINE.NAME:" << transliterate(machine_name) << new_line;
181 
182         for (size_t extr_nr = 0; extr_nr < extruder_count; extr_nr++)
183         {
184             if (!extruder_is_used[extr_nr])
185             {
186                 continue;
187             }
188             prefix << ";EXTRUDER_TRAIN." << extr_nr << ".INITIAL_TEMPERATURE:" << extruder_attr[extr_nr].initial_temp << new_line;
189             if (filament_used.size() == extruder_count)
190             {
191                 prefix << ";EXTRUDER_TRAIN." << extr_nr << ".MATERIAL.VOLUME_USED:" << static_cast<int>(filament_used[extr_nr]) << new_line;
192             }
193             if (mat_ids.size() == extruder_count && mat_ids[extr_nr] != "")
194             {
195                 prefix << ";EXTRUDER_TRAIN." << extr_nr << ".MATERIAL.GUID:" << mat_ids[extr_nr] << new_line;
196             }
197             const Settings& extruder_settings = Application::getInstance().current_slice->scene.extruders[extr_nr].settings;
198             prefix << ";EXTRUDER_TRAIN." << extr_nr << ".NOZZLE.DIAMETER:" << extruder_settings.get<double>("machine_nozzle_size") << new_line;
199             prefix << ";EXTRUDER_TRAIN." << extr_nr << ".NOZZLE.NAME:" << extruder_settings.get<std::string>("machine_nozzle_id") << new_line;
200         }
201         prefix << ";BUILD_PLATE.TYPE:" << machine_buildplate_type << new_line;
202         prefix << ";BUILD_PLATE.INITIAL_TEMPERATURE:" << initial_bed_temp << new_line;
203 
204         if (machine_heated_build_volume)
205         {
206             prefix << ";BUILD_VOLUME.TEMPERATURE:" << build_volume_temperature << new_line;
207         }
208 
209         if (print_time)
210         {
211             prefix << ";PRINT.TIME:" << static_cast<int>(*print_time) << new_line;
212         }
213 
214         prefix << ";PRINT.GROUPS:" << Application::getInstance().current_slice->scene.mesh_groups.size() << new_line;
215 
216         if (total_bounding_box.min.x > total_bounding_box.max.x) //We haven't encountered any movement (yet). This probably means we're command-line slicing.
217         {
218             //Put some small default in there.
219             total_bounding_box.min = Point3(0, 0, 0);
220             total_bounding_box.max = Point3(10, 10, 10);
221         }
222         prefix << ";PRINT.SIZE.MIN.X:" << INT2MM(total_bounding_box.min.x) << new_line;
223         prefix << ";PRINT.SIZE.MIN.Y:" << INT2MM(total_bounding_box.min.y) << new_line;
224         prefix << ";PRINT.SIZE.MIN.Z:" << INT2MM(total_bounding_box.min.z) << new_line;
225         prefix << ";PRINT.SIZE.MAX.X:" << INT2MM(total_bounding_box.max.x) << new_line;
226         prefix << ";PRINT.SIZE.MAX.Y:" << INT2MM(total_bounding_box.max.y) << new_line;
227         prefix << ";PRINT.SIZE.MAX.Z:" << INT2MM(total_bounding_box.max.z) << new_line;
228         prefix << ";END_OF_HEADER" << new_line;
229         break;
230     default:
231         prefix << ";FLAVOR:" << flavorToString(flavor) << new_line;
232         prefix << ";TIME:" << ((print_time)? static_cast<int>(*print_time) : 6666) << new_line;
233         if (flavor == EGCodeFlavor::ULTIGCODE)
234         {
235             prefix << ";MATERIAL:" << ((filament_used.size() >= 1)? static_cast<int>(filament_used[0]) : 6666) << new_line;
236             prefix << ";MATERIAL2:" << ((filament_used.size() >= 2)? static_cast<int>(filament_used[1]) : 0) << new_line;
237 
238             prefix << ";NOZZLE_DIAMETER:" << Application::getInstance().current_slice->scene.extruders[0].settings.get<double>("machine_nozzle_size") << new_line;
239         }
240         else if (flavor == EGCodeFlavor::REPRAP || flavor == EGCodeFlavor::MARLIN || flavor == EGCodeFlavor::MARLIN_VOLUMATRIC)
241         {
242             prefix << ";Filament used: ";
243             if (filament_used.size() > 0)
244             {
245                 for (unsigned i = 0; i < filament_used.size(); ++i)
246                 {
247                     if (i > 0)
248                     {
249                         prefix << ", ";
250                     }
251                     if (flavor != EGCodeFlavor::MARLIN_VOLUMATRIC)
252                     {
253                         prefix << filament_used[i] / (1000 * extruder_attr[i].filament_area) << "m";
254                     }
255                     else //Use volumetric filament used.
256                     {
257                         prefix << filament_used[i] << "mm3";
258                     }
259                 }
260             }
261             else
262             {
263                 prefix << "0m";
264             }
265             prefix << new_line;
266             prefix << ";Layer height: " << Application::getInstance().current_slice->scene.current_mesh_group->settings.get<double>("layer_height") << new_line;
267         }
268         prefix << ";MINX:" << INT2MM(total_bounding_box.min.x) << new_line;
269         prefix << ";MINY:" << INT2MM(total_bounding_box.min.y) << new_line;
270         prefix << ";MINZ:" << INT2MM(total_bounding_box.min.z) << new_line;
271         prefix << ";MAXX:" << INT2MM(total_bounding_box.max.x) << new_line;
272         prefix << ";MAXY:" << INT2MM(total_bounding_box.max.y) << new_line;
273         prefix << ";MAXZ:" << INT2MM(total_bounding_box.max.z) << new_line;
274     }
275 
276     return prefix.str();
277 }
278 
279 
setLayerNr(unsigned int layer_nr_)280 void GCodeExport::setLayerNr(unsigned int layer_nr_) {
281     layer_nr = layer_nr_;
282 }
283 
setOutputStream(std::ostream * stream)284 void GCodeExport::setOutputStream(std::ostream* stream)
285 {
286     output_stream = stream;
287     *output_stream << std::fixed;
288 }
289 
getExtruderIsUsed(const int extruder_nr) const290 bool GCodeExport::getExtruderIsUsed(const int extruder_nr) const
291 {
292     assert(extruder_nr >= 0);
293     assert(extruder_nr < MAX_EXTRUDERS);
294     return extruder_attr[extruder_nr].is_used;
295 }
296 
getGcodePos(const coord_t x,const coord_t y,const int extruder_train) const297 Point GCodeExport::getGcodePos(const coord_t x, const coord_t y, const int extruder_train) const
298 {
299     if (use_extruder_offset_to_offset_coords)
300     {
301         const Settings& extruder_settings = Application::getInstance().current_slice->scene.extruders[extruder_train].settings;
302         return Point(x - extruder_settings.get<coord_t>("machine_nozzle_offset_x"), y - extruder_settings.get<coord_t>("machine_nozzle_offset_y"));
303     }
304     else
305     {
306         return Point(x, y);
307     }
308 }
309 
310 
setFlavor(EGCodeFlavor flavor)311 void GCodeExport::setFlavor(EGCodeFlavor flavor)
312 {
313     this->flavor = flavor;
314     if (flavor == EGCodeFlavor::MACH3)
315     {
316         for(int n=0; n<MAX_EXTRUDERS; n++)
317         {
318             extruder_attr[n].extruderCharacter = 'A' + n;
319         }
320     }
321     else
322     {
323         for(int n=0; n<MAX_EXTRUDERS; n++)
324         {
325             extruder_attr[n].extruderCharacter = 'E';
326         }
327     }
328     if (flavor == EGCodeFlavor::ULTIGCODE || flavor == EGCodeFlavor::MARLIN_VOLUMATRIC)
329     {
330         is_volumetric = true;
331     }
332     else
333     {
334         is_volumetric = false;
335     }
336 }
337 
getFlavor() const338 EGCodeFlavor GCodeExport::getFlavor() const
339 {
340     return flavor;
341 }
342 
setZ(int z)343 void GCodeExport::setZ(int z)
344 {
345     current_layer_z = z;
346 }
347 
setFlowRateExtrusionSettings(double max_extrusion_offset,double extrusion_offset_factor)348 void GCodeExport::setFlowRateExtrusionSettings(double max_extrusion_offset, double extrusion_offset_factor)
349 {
350     this->max_extrusion_offset = max_extrusion_offset;
351     this->extrusion_offset_factor = extrusion_offset_factor;
352 }
353 
getPosition() const354 Point3 GCodeExport::getPosition() const
355 {
356     return currentPosition;
357 }
getPositionXY() const358 Point GCodeExport::getPositionXY() const
359 {
360     return Point(currentPosition.x, currentPosition.y);
361 }
362 
getPositionZ() const363 int GCodeExport::getPositionZ() const
364 {
365     return currentPosition.z;
366 }
367 
getExtruderNr() const368 int GCodeExport::getExtruderNr() const
369 {
370     return current_extruder;
371 }
372 
setFilamentDiameter(const size_t extruder,const coord_t diameter)373 void GCodeExport::setFilamentDiameter(const size_t extruder, const coord_t diameter)
374 {
375     const double r = INT2MM(diameter) / 2.0;
376     const double area = M_PI * r * r;
377     extruder_attr[extruder].filament_area = area;
378 }
379 
getCurrentExtrudedVolume() const380 double GCodeExport::getCurrentExtrudedVolume() const
381 {
382     double extrusion_amount = current_e_value;
383     const Settings& extruder_settings = Application::getInstance().current_slice->scene.extruders[current_extruder].settings;
384     if (!extruder_settings.get<bool>("machine_firmware_retract"))
385     { // no E values are changed to perform a retraction
386         extrusion_amount -= extruder_attr[current_extruder].retraction_e_amount_at_e_start; // subtract the increment in E which was used for the first unretraction instead of extrusion
387         extrusion_amount += extruder_attr[current_extruder].retraction_e_amount_current; // add the decrement in E which the filament is behind on extrusion due to the last retraction
388     }
389     if (is_volumetric)
390     {
391         return extrusion_amount;
392     }
393     else
394     {
395         return extrusion_amount * extruder_attr[current_extruder].filament_area;
396     }
397 }
398 
eToMm(double e)399 double GCodeExport::eToMm(double e)
400 {
401     if (is_volumetric)
402     {
403         return e / extruder_attr[current_extruder].filament_area;
404     }
405     else
406     {
407         return e;
408     }
409 }
410 
mm3ToE(double mm3)411 double GCodeExport::mm3ToE(double mm3)
412 {
413     if (is_volumetric)
414     {
415         return mm3;
416     }
417     else
418     {
419         return mm3 / extruder_attr[current_extruder].filament_area;
420     }
421 }
422 
mmToE(double mm)423 double GCodeExport::mmToE(double mm)
424 {
425     if (is_volumetric)
426     {
427         return mm * extruder_attr[current_extruder].filament_area;
428     }
429     else
430     {
431         return mm;
432     }
433 }
434 
eToMm3(double e,size_t extruder)435 double GCodeExport::eToMm3(double e, size_t extruder)
436 {
437     if (is_volumetric)
438     {
439         return e;
440     }
441     else
442     {
443         return e * extruder_attr[extruder].filament_area;
444     }
445 }
446 
getTotalFilamentUsed(size_t extruder_nr)447 double GCodeExport::getTotalFilamentUsed(size_t extruder_nr)
448 {
449     if (extruder_nr == current_extruder)
450         return extruder_attr[extruder_nr].totalFilament + getCurrentExtrudedVolume();
451     return extruder_attr[extruder_nr].totalFilament;
452 }
453 
getTotalPrintTimePerFeature()454 std::vector<Duration> GCodeExport::getTotalPrintTimePerFeature()
455 {
456     return total_print_times;
457 }
458 
getSumTotalPrintTimes()459 double GCodeExport::getSumTotalPrintTimes()
460 {
461     double sum = 0.0;
462     for(double item : getTotalPrintTimePerFeature())
463     {
464         sum += item;
465     }
466     return sum;
467 }
468 
resetTotalPrintTimeAndFilament()469 void GCodeExport::resetTotalPrintTimeAndFilament()
470 {
471     for(size_t i = 0; i < total_print_times.size(); i++)
472     {
473         total_print_times[i] = 0.0;
474     }
475     for(unsigned int e=0; e<MAX_EXTRUDERS; e++)
476     {
477         extruder_attr[e].totalFilament = 0.0;
478         extruder_attr[e].currentTemperature = 0;
479         extruder_attr[e].waited_for_temperature = false;
480     }
481     current_e_value = 0.0;
482     estimateCalculator.reset();
483 }
484 
updateTotalPrintTime()485 void GCodeExport::updateTotalPrintTime()
486 {
487     std::vector<Duration> estimates = estimateCalculator.calculate();
488     for(size_t i = 0; i < estimates.size(); i++)
489     {
490         total_print_times[i] += estimates[i];
491     }
492     estimateCalculator.reset();
493     writeTimeComment(getSumTotalPrintTimes());
494 }
495 
writeComment(const std::string & unsanitized_comment)496 void GCodeExport::writeComment(const std::string& unsanitized_comment)
497 {
498     const std::string comment = transliterate(unsanitized_comment);
499 
500     *output_stream << ";";
501     for (unsigned int i = 0; i < comment.length(); i++)
502     {
503         if (comment[i] == '\n')
504         {
505             *output_stream << new_line << ";";
506         }
507         else
508         {
509             *output_stream << comment[i];
510         }
511     }
512     *output_stream << new_line;
513 }
514 
writeTimeComment(const Duration time)515 void GCodeExport::writeTimeComment(const Duration time)
516 {
517     *output_stream << ";TIME_ELAPSED:" << time << new_line;
518 }
519 
writeTypeComment(const PrintFeatureType & type)520 void GCodeExport::writeTypeComment(const PrintFeatureType& type)
521 {
522     switch (type)
523     {
524         case PrintFeatureType::OuterWall:
525             *output_stream << ";TYPE:WALL-OUTER" << new_line;
526             break;
527         case PrintFeatureType::InnerWall:
528             *output_stream << ";TYPE:WALL-INNER" << new_line;
529             break;
530         case PrintFeatureType::Skin:
531             *output_stream << ";TYPE:SKIN" << new_line;
532             break;
533         case PrintFeatureType::Support:
534             *output_stream << ";TYPE:SUPPORT" << new_line;
535             break;
536         case PrintFeatureType::SkirtBrim:
537             *output_stream << ";TYPE:SKIRT" << new_line;
538             break;
539         case PrintFeatureType::Infill:
540             *output_stream << ";TYPE:FILL" << new_line;
541             break;
542         case PrintFeatureType::SupportInfill:
543             *output_stream << ";TYPE:SUPPORT" << new_line;
544             break;
545         case PrintFeatureType::SupportInterface:
546             *output_stream << ";TYPE:SUPPORT-INTERFACE" << new_line;
547             break;
548         case PrintFeatureType::PrimeTower:
549             *output_stream << ";TYPE:PRIME-TOWER" << new_line;
550             break;
551         case PrintFeatureType::MoveCombing:
552         case PrintFeatureType::MoveRetraction:
553         case PrintFeatureType::NoneType:
554         case PrintFeatureType::NumPrintFeatureTypes:
555             // do nothing
556             break;
557     }
558 }
559 
560 
writeLayerComment(const LayerIndex layer_nr)561 void GCodeExport::writeLayerComment(const LayerIndex layer_nr)
562 {
563     *output_stream << ";LAYER:" << layer_nr << new_line;
564 }
565 
writeLayerCountComment(const size_t layer_count)566 void GCodeExport::writeLayerCountComment(const size_t layer_count)
567 {
568     *output_stream << ";LAYER_COUNT:" << layer_count << new_line;
569 }
570 
writeLine(const char * line)571 void GCodeExport::writeLine(const char* line)
572 {
573     *output_stream << line << new_line;
574 }
575 
writeExtrusionMode(bool set_relative_extrusion_mode)576 void GCodeExport::writeExtrusionMode(bool set_relative_extrusion_mode)
577 {
578     if (set_relative_extrusion_mode)
579     {
580         *output_stream << "M83 ;relative extrusion mode" << new_line;
581     }
582     else
583     {
584         *output_stream << "M82 ;absolute extrusion mode" << new_line;
585     }
586 }
587 
resetExtrusionValue()588 void GCodeExport::resetExtrusionValue()
589 {
590     if (!relative_extrusion)
591     {
592         *output_stream << "G92 " << extruder_attr[current_extruder].extruderCharacter << "0" << new_line;
593     }
594     double current_extruded_volume = getCurrentExtrudedVolume();
595     extruder_attr[current_extruder].totalFilament += current_extruded_volume;
596     for (double& extruded_volume_at_retraction : extruder_attr[current_extruder].extruded_volume_at_previous_n_retractions)
597     { // update the extruded_volume_at_previous_n_retractions only of the current extruder, since other extruders don't extrude the current volume
598         extruded_volume_at_retraction -= current_extruded_volume;
599     }
600     current_e_value = 0.0;
601     extruder_attr[current_extruder].retraction_e_amount_at_e_start = extruder_attr[current_extruder].retraction_e_amount_current;
602 }
603 
writeDelay(const Duration & time_amount)604 void GCodeExport::writeDelay(const Duration& time_amount)
605 {
606     *output_stream << "G4 P" << int(time_amount * 1000) << new_line;
607     estimateCalculator.addTime(time_amount);
608 }
609 
writeTravel(const Point & p,const Velocity & speed)610 void GCodeExport::writeTravel(const Point& p, const Velocity& speed)
611 {
612     writeTravel(Point3(p.X, p.Y, current_layer_z), speed);
613 }
writeExtrusion(const Point & p,const Velocity & speed,double extrusion_mm3_per_mm,PrintFeatureType feature,bool update_extrusion_offset)614 void GCodeExport::writeExtrusion(const Point& p, const Velocity& speed, double extrusion_mm3_per_mm, PrintFeatureType feature, bool update_extrusion_offset)
615 {
616     writeExtrusion(Point3(p.X, p.Y, current_layer_z), speed, extrusion_mm3_per_mm, feature, update_extrusion_offset);
617 }
618 
writeTravel(const Point3 & p,const Velocity & speed)619 void GCodeExport::writeTravel(const Point3& p, const Velocity& speed)
620 {
621     if (flavor == EGCodeFlavor::BFB)
622     {
623         writeMoveBFB(p.x, p.y, p.z + is_z_hopped, speed, 0.0, PrintFeatureType::MoveCombing);
624         return;
625     }
626     writeTravel(p.x, p.y, p.z + is_z_hopped, speed);
627 }
628 
writeExtrusion(const Point3 & p,const Velocity & speed,double extrusion_mm3_per_mm,PrintFeatureType feature,bool update_extrusion_offset)629 void GCodeExport::writeExtrusion(const Point3& p, const Velocity& speed, double extrusion_mm3_per_mm, PrintFeatureType feature, bool update_extrusion_offset)
630 {
631     if (flavor == EGCodeFlavor::BFB)
632     {
633         writeMoveBFB(p.x, p.y, p.z, speed, extrusion_mm3_per_mm, feature);
634         return;
635     }
636     writeExtrusion(p.x, p.y, p.z, speed, extrusion_mm3_per_mm, feature, update_extrusion_offset);
637 }
638 
writeMoveBFB(const int x,const int y,const int z,const Velocity & speed,double extrusion_mm3_per_mm,PrintFeatureType feature)639 void GCodeExport::writeMoveBFB(const int x, const int y, const int z, const Velocity& speed, double extrusion_mm3_per_mm, PrintFeatureType feature)
640 {
641     if (std::isinf(extrusion_mm3_per_mm))
642     {
643         logError("Extrusion rate is infinite!");
644         assert(false && "Infinite extrusion move!");
645         std::exit(1);
646     }
647     if (std::isnan(extrusion_mm3_per_mm))
648     {
649         logError("Extrusion rate is not a number!");
650         assert(false && "NaN extrusion move!");
651         std::exit(1);
652     }
653 
654     double extrusion_per_mm = mm3ToE(extrusion_mm3_per_mm);
655 
656     Point gcode_pos = getGcodePos(x,y, current_extruder);
657 
658     //For Bits From Bytes machines, we need to handle this completely differently. As they do not use E values but RPM values.
659     float fspeed = speed * 60;
660     float rpm = extrusion_per_mm * speed * 60;
661     const float mm_per_rpm = 4.0; //All BFB machines have 4mm per RPM extrusion.
662     rpm /= mm_per_rpm;
663     if (rpm > 0)
664     {
665         if (extruder_attr[current_extruder].retraction_e_amount_current)
666         {
667             if (currentSpeed != double(rpm))
668             {
669                 //fprintf(f, "; %f e-per-mm %d mm-width %d mm/s\n", extrusion_per_mm, lineWidth, speed);
670                 //fprintf(f, "M108 S%0.1f\r\n", rpm);
671                 *output_stream << "M108 S" << PrecisionedDouble{1, rpm} << new_line;
672                 currentSpeed = double(rpm);
673             }
674             //Add M101 or M201 to enable the proper extruder.
675             *output_stream << "M" << int((current_extruder + 1) * 100 + 1) << new_line;
676             extruder_attr[current_extruder].retraction_e_amount_current = 0.0;
677         }
678         //Fix the speed by the actual RPM we are asking, because of rounding errors we cannot get all RPM values, but we have a lot more resolution in the feedrate value.
679         // (Trick copied from KISSlicer, thanks Jonathan)
680         fspeed *= (rpm / (roundf(rpm * 100) / 100));
681 
682         //Increase the extrusion amount to calculate the amount of filament used.
683         Point3 diff = Point3(x,y,z) - getPosition();
684 
685         current_e_value += extrusion_per_mm * diff.vSizeMM();
686     }
687     else
688     {
689         //If we are not extruding, check if we still need to disable the extruder. This causes a retraction due to auto-retraction.
690         if (!extruder_attr[current_extruder].retraction_e_amount_current)
691         {
692             *output_stream << "M103" << new_line;
693             extruder_attr[current_extruder].retraction_e_amount_current = 1.0; // 1.0 used as stub; BFB doesn't use the actual retraction amount; it performs retraction on the firmware automatically
694         }
695     }
696     *output_stream << "G1 X" << MMtoStream{gcode_pos.X} << " Y" << MMtoStream{gcode_pos.Y} << " Z" << MMtoStream{z};
697     *output_stream << " F" << PrecisionedDouble{1, fspeed} << new_line;
698 
699     currentPosition = Point3(x, y, z);
700     estimateCalculator.plan(TimeEstimateCalculator::Position(INT2MM(currentPosition.x), INT2MM(currentPosition.y), INT2MM(currentPosition.z), eToMm(current_e_value)), speed, feature);
701 }
702 
writeTravel(const coord_t & x,const coord_t & y,const coord_t & z,const Velocity & speed)703 void GCodeExport::writeTravel(const coord_t& x, const coord_t& y, const coord_t& z, const Velocity& speed)
704 {
705     if (currentPosition.x == x && currentPosition.y == y && currentPosition.z == z)
706     {
707         return;
708     }
709 
710 #ifdef ASSERT_INSANE_OUTPUT
711     assert(speed < 400 && speed > 1); // normal F values occurring in UM2 gcode (this code should not be compiled for release)
712     assert(currentPosition != no_point3);
713     assert(Point3(x, y, z) != no_point3);
714     assert((Point3(x,y,z) - currentPosition).vSize() < MM2INT(1000)); // no crazy positions (this code should not be compiled for release)
715 #endif //ASSERT_INSANE_OUTPUT
716 
717     const PrintFeatureType travel_move_type = extruder_attr[current_extruder].retraction_e_amount_current ? PrintFeatureType::MoveRetraction : PrintFeatureType::MoveCombing;
718     const int display_width = extruder_attr[current_extruder].retraction_e_amount_current ? MM2INT(0.2) : MM2INT(0.1);
719     const double layer_height = Application::getInstance().current_slice->scene.current_mesh_group->settings.get<double>("layer_height");
720     Application::getInstance().communication->sendLineTo(travel_move_type, Point(x, y), display_width, layer_height, speed);
721 
722     *output_stream << "G0";
723     writeFXYZE(speed, x, y, z, current_e_value, travel_move_type);
724 }
725 
writeExtrusion(const int x,const int y,const int z,const Velocity & speed,const double extrusion_mm3_per_mm,const PrintFeatureType & feature,const bool update_extrusion_offset)726 void GCodeExport::writeExtrusion(const int x, const int y, const int z, const Velocity& speed, const double extrusion_mm3_per_mm, const PrintFeatureType& feature, const bool update_extrusion_offset)
727 {
728     if (currentPosition.x == x && currentPosition.y == y && currentPosition.z == z)
729     {
730         return;
731     }
732 
733 #ifdef ASSERT_INSANE_OUTPUT
734     assert(speed < 400 && speed > 1); // normal F values occurring in UM2 gcode (this code should not be compiled for release)
735     assert(currentPosition != no_point3);
736     assert(Point3(x, y, z) != no_point3);
737     assert((Point3(x,y,z) - currentPosition).vSize() < MM2INT(1000)); // no crazy positions (this code should not be compiled for release)
738     assert(extrusion_mm3_per_mm >= 0.0);
739 #endif //ASSERT_INSANE_OUTPUT
740 
741     if (std::isinf(extrusion_mm3_per_mm))
742     {
743         logError("Extrusion rate is infinite!");
744         assert(false && "Infinite extrusion move!");
745         std::exit(1);
746     }
747 
748     if (std::isnan(extrusion_mm3_per_mm))
749     {
750         logError("Extrusion rate is not a number!");
751         assert(false && "NaN extrusion move!");
752         std::exit(1);
753     }
754 
755     if (extrusion_mm3_per_mm < 0.0)
756     {
757         logWarning("Warning! Negative extrusion move!\n");
758     }
759 
760     double extrusion_per_mm = mm3ToE(extrusion_mm3_per_mm);
761 
762     if (is_z_hopped > 0)
763     {
764         writeZhopEnd();
765     }
766 
767     Point3 diff = Point3(x,y,z) - currentPosition;
768 
769     writeUnretractionAndPrime();
770 
771     //flow rate compensation
772     double extrusion_offset = 0;
773     if (diff.vSizeMM())
774     {
775         extrusion_offset = speed * extrusion_mm3_per_mm * extrusion_offset_factor;
776         if (extrusion_offset > max_extrusion_offset)
777         {
778             extrusion_offset = max_extrusion_offset;
779         }
780     }
781     // write new value of extrusion_offset, which will be remembered.
782     if (update_extrusion_offset && (extrusion_offset != current_e_offset))
783     {
784         current_e_offset = extrusion_offset;
785         *output_stream << ";FLOW_RATE_COMPENSATED_OFFSET = " << current_e_offset << new_line;
786     }
787 
788     extruder_attr[current_extruder].last_e_value_after_wipe += extrusion_per_mm * diff.vSizeMM();
789     double new_e_value = current_e_value + extrusion_per_mm * diff.vSizeMM();
790 
791     *output_stream << "G1";
792     writeFXYZE(speed, x, y, z, new_e_value, feature);
793 }
794 
writeFXYZE(const Velocity & speed,const int x,const int y,const int z,const double e,const PrintFeatureType & feature)795 void GCodeExport::writeFXYZE(const Velocity& speed, const int x, const int y, const int z, const double e, const PrintFeatureType& feature)
796 {
797     if (currentSpeed != speed)
798     {
799         *output_stream << " F" << PrecisionedDouble{1, speed * 60};
800         currentSpeed = speed;
801     }
802 
803     Point gcode_pos = getGcodePos(x, y, current_extruder);
804     total_bounding_box.include(Point3(gcode_pos.X, gcode_pos.Y, z));
805 
806     *output_stream << " X" << MMtoStream{gcode_pos.X} << " Y" << MMtoStream{gcode_pos.Y};
807     if (z != currentPosition.z)
808     {
809         *output_stream << " Z" << MMtoStream{z};
810     }
811     if (e + current_e_offset != current_e_value)
812     {
813         const double output_e = (relative_extrusion)? e + current_e_offset - current_e_value : e + current_e_offset;
814         *output_stream << " " << extruder_attr[current_extruder].extruderCharacter << PrecisionedDouble{5, output_e};
815     }
816     *output_stream << new_line;
817 
818     currentPosition = Point3(x, y, z);
819     current_e_value = e;
820     estimateCalculator.plan(TimeEstimateCalculator::Position(INT2MM(x), INT2MM(y), INT2MM(z), eToMm(e)), speed, feature);
821 }
822 
writeUnretractionAndPrime()823 void GCodeExport::writeUnretractionAndPrime()
824 {
825     const double prime_volume = extruder_attr[current_extruder].prime_volume;
826     const double prime_volume_e = mm3ToE(prime_volume);
827     current_e_value += prime_volume_e;
828     if (extruder_attr[current_extruder].retraction_e_amount_current)
829     {
830         const Settings& extruder_settings = Application::getInstance().current_slice->scene.extruders[current_extruder].settings;
831         if (extruder_settings.get<bool>("machine_firmware_retract"))
832         { // note that BFB is handled differently
833             *output_stream << "G11" << new_line;
834             //Assume default UM2 retraction settings.
835             if (prime_volume != 0)
836             {
837                 const double output_e = (relative_extrusion)? prime_volume_e : current_e_value;
838                 *output_stream << "G1 F" << PrecisionedDouble{1, extruder_attr[current_extruder].last_retraction_prime_speed * 60} << " " << extruder_attr[current_extruder].extruderCharacter << PrecisionedDouble{5, output_e} << new_line;
839                 currentSpeed = extruder_attr[current_extruder].last_retraction_prime_speed;
840             }
841             estimateCalculator.plan(TimeEstimateCalculator::Position(INT2MM(currentPosition.x), INT2MM(currentPosition.y), INT2MM(currentPosition.z), eToMm(current_e_value)), 25.0, PrintFeatureType::MoveRetraction);
842         }
843         else
844         {
845             current_e_value += extruder_attr[current_extruder].retraction_e_amount_current;
846             const double output_e = (relative_extrusion)? extruder_attr[current_extruder].retraction_e_amount_current + prime_volume_e : current_e_value;
847             *output_stream << "G1 F" << PrecisionedDouble{1, extruder_attr[current_extruder].last_retraction_prime_speed * 60} << " " << extruder_attr[current_extruder].extruderCharacter << PrecisionedDouble{5, output_e} << new_line;
848             currentSpeed = extruder_attr[current_extruder].last_retraction_prime_speed;
849             estimateCalculator.plan(TimeEstimateCalculator::Position(INT2MM(currentPosition.x), INT2MM(currentPosition.y), INT2MM(currentPosition.z), eToMm(current_e_value)), currentSpeed, PrintFeatureType::MoveRetraction);
850         }
851     }
852     else if (prime_volume != 0.0)
853     {
854         const double output_e = (relative_extrusion)? prime_volume_e : current_e_value;
855         *output_stream << "G1 F" << PrecisionedDouble{1, extruder_attr[current_extruder].last_retraction_prime_speed * 60} << " " << extruder_attr[current_extruder].extruderCharacter;
856         *output_stream << PrecisionedDouble{5, output_e} << new_line;
857         currentSpeed = extruder_attr[current_extruder].last_retraction_prime_speed;
858         estimateCalculator.plan(TimeEstimateCalculator::Position(INT2MM(currentPosition.x), INT2MM(currentPosition.y), INT2MM(currentPosition.z), eToMm(current_e_value)), currentSpeed, PrintFeatureType::NoneType);
859     }
860     extruder_attr[current_extruder].prime_volume = 0.0;
861 
862     if (getCurrentExtrudedVolume() > 10000.0 && flavor != EGCodeFlavor::BFB && flavor != EGCodeFlavor::MAKERBOT) //According to https://github.com/Ultimaker/CuraEngine/issues/14 having more then 21m of extrusion causes inaccuracies. So reset it every 10m, just to be sure.
863     {
864         resetExtrusionValue();
865     }
866     if (extruder_attr[current_extruder].retraction_e_amount_current)
867     {
868         extruder_attr[current_extruder].retraction_e_amount_current = 0.0;
869     }
870 }
871 
writeRetraction(const RetractionConfig & config,bool force,bool extruder_switch)872 void GCodeExport::writeRetraction(const RetractionConfig& config, bool force, bool extruder_switch)
873 {
874     ExtruderTrainAttributes& extr_attr = extruder_attr[current_extruder];
875 
876     if (flavor == EGCodeFlavor::BFB)//BitsFromBytes does automatic retraction.
877     {
878         if (extruder_switch)
879         {
880             if (!extr_attr.retraction_e_amount_current)
881                 *output_stream << "M103" << new_line;
882 
883             extr_attr.retraction_e_amount_current = 1.0; // 1.0 is a stub; BFB doesn't use the actual retracted amount; retraction is performed by firmware
884         }
885         return;
886     }
887 
888     double old_retraction_e_amount = extr_attr.retraction_e_amount_current;
889     double new_retraction_e_amount = mmToE(config.distance);
890     double retraction_diff_e_amount = old_retraction_e_amount - new_retraction_e_amount;
891     if (std::abs(retraction_diff_e_amount) < 0.000001)
892     {
893         return;
894     }
895 
896     { // handle retraction limitation
897         double current_extruded_volume = getCurrentExtrudedVolume();
898         std::deque<double>& extruded_volume_at_previous_n_retractions = extr_attr.extruded_volume_at_previous_n_retractions;
899         while (extruded_volume_at_previous_n_retractions.size() > config.retraction_count_max && !extruded_volume_at_previous_n_retractions.empty())
900         {
901             // extruder switch could have introduced data which falls outside the retraction window
902             // also the retraction_count_max could have changed between the last retraction and this
903             extruded_volume_at_previous_n_retractions.pop_back();
904         }
905         if (!force && config.retraction_count_max <= 0)
906         {
907             return;
908         }
909         if (!force && extruded_volume_at_previous_n_retractions.size() == config.retraction_count_max
910             && current_extruded_volume < extruded_volume_at_previous_n_retractions.back() + config.retraction_extrusion_window * extr_attr.filament_area)
911         {
912             return;
913         }
914         extruded_volume_at_previous_n_retractions.push_front(current_extruded_volume);
915         if (extruded_volume_at_previous_n_retractions.size() == config.retraction_count_max + 1)
916         {
917             extruded_volume_at_previous_n_retractions.pop_back();
918         }
919     }
920 
921     const Settings& extruder_settings = Application::getInstance().current_slice->scene.extruders[current_extruder].settings;
922     if (extruder_settings.get<bool>("machine_firmware_retract"))
923     {
924         if (extruder_switch && extr_attr.retraction_e_amount_current)
925         {
926             return;
927         }
928         *output_stream << "G10";
929         if (extruder_switch && flavor == EGCodeFlavor::REPETIER)
930         {
931             *output_stream << " S1";
932         }
933         *output_stream << new_line;
934         //Assume default UM2 retraction settings.
935         estimateCalculator.plan(TimeEstimateCalculator::Position(INT2MM(currentPosition.x), INT2MM(currentPosition.y), INT2MM(currentPosition.z), eToMm(current_e_value + retraction_diff_e_amount)), 25, PrintFeatureType::MoveRetraction); // TODO: hardcoded values!
936     }
937     else
938     {
939         double speed = ((retraction_diff_e_amount < 0.0)? config.speed : extr_attr.last_retraction_prime_speed) * 60;
940         current_e_value += retraction_diff_e_amount;
941         const double output_e = (relative_extrusion)? retraction_diff_e_amount : current_e_value;
942         *output_stream << "G1 F" << PrecisionedDouble{1, speed} << " " << extr_attr.extruderCharacter << PrecisionedDouble{5, output_e} << new_line;
943         currentSpeed = speed;
944         estimateCalculator.plan(TimeEstimateCalculator::Position(INT2MM(currentPosition.x), INT2MM(currentPosition.y), INT2MM(currentPosition.z), eToMm(current_e_value)), currentSpeed, PrintFeatureType::MoveRetraction);
945         extr_attr.last_retraction_prime_speed = config.primeSpeed;
946     }
947 
948     extr_attr.retraction_e_amount_current = new_retraction_e_amount; // suppose that for UM2 the retraction amount in the firmware is equal to the provided amount
949     extr_attr.prime_volume += config.prime_volume;
950 
951 }
952 
writeZhopStart(const coord_t hop_height,Velocity speed)953 void GCodeExport::writeZhopStart(const coord_t hop_height, Velocity speed/*= 0*/)
954 {
955     if (hop_height > 0)
956     {
957         if (speed == 0)
958         {
959             const ExtruderTrain& extruder = Application::getInstance().current_slice->scene.extruders[current_extruder];
960             speed = extruder.settings.get<Velocity>("speed_z_hop");
961         }
962         is_z_hopped = hop_height;
963         currentSpeed = speed;
964         *output_stream << "G1 F" << PrecisionedDouble{1, speed * 60} << " Z" << MMtoStream{current_layer_z + is_z_hopped} << new_line;
965         total_bounding_box.includeZ(current_layer_z + is_z_hopped);
966         assert(speed > 0.0 && "Z hop speed should be positive.");
967     }
968 }
969 
writeZhopEnd(Velocity speed)970 void GCodeExport::writeZhopEnd(Velocity speed/*= 0*/)
971 {
972     if (is_z_hopped)
973     {
974         if (speed == 0)
975         {
976             const ExtruderTrain& extruder = Application::getInstance().current_slice->scene.extruders[current_extruder];
977             speed = extruder.settings.get<Velocity>("speed_z_hop");
978         }
979         is_z_hopped = 0;
980         currentPosition.z = current_layer_z;
981         currentSpeed = speed;
982         *output_stream << "G1 F" << PrecisionedDouble{1, speed * 60} << " Z" << MMtoStream{current_layer_z} << new_line;
983         assert(speed > 0.0 && "Z hop speed should be positive.");
984     }
985 }
986 
startExtruder(const size_t new_extruder)987 void GCodeExport::startExtruder(const size_t new_extruder)
988 {
989     extruder_attr[new_extruder].is_used = true;
990     if (new_extruder != current_extruder) // wouldn't be the case on the very first extruder start if it's extruder 0
991     {
992         if (flavor == EGCodeFlavor::MAKERBOT)
993         {
994             *output_stream << "M135 T" << new_extruder << new_line;
995         }
996         else
997         {
998             *output_stream << "T" << new_extruder << new_line;
999         }
1000     }
1001 
1002     current_extruder = new_extruder;
1003 
1004     assert(getCurrentExtrudedVolume() == 0.0 && "Just after an extruder switch we haven't extruded anything yet!");
1005     resetExtrusionValue(); // zero the E value on the new extruder, just to be sure
1006 
1007     const std::string start_code = Application::getInstance().current_slice->scene.extruders[new_extruder].settings.get<std::string>("machine_extruder_start_code");
1008 
1009     if(!start_code.empty())
1010     {
1011         if (relative_extrusion)
1012         {
1013             writeExtrusionMode(false); // ensure absolute extrusion mode is set before the start gcode
1014         }
1015 
1016         writeCode(start_code.c_str());
1017 
1018         if (relative_extrusion)
1019         {
1020             writeExtrusionMode(true); // restore relative extrusion mode
1021         }
1022     }
1023 
1024     Application::getInstance().communication->setExtruderForSend(Application::getInstance().current_slice->scene.extruders[new_extruder]);
1025     Application::getInstance().communication->sendCurrentPosition(getPositionXY());
1026 
1027     //Change the Z position so it gets re-written again. We do not know if the switch code modified the Z position.
1028     currentPosition.z += 1;
1029 
1030     setExtruderFanNumber(new_extruder);
1031 }
1032 
switchExtruder(size_t new_extruder,const RetractionConfig & retraction_config_old_extruder,coord_t perform_z_hop)1033 void GCodeExport::switchExtruder(size_t new_extruder, const RetractionConfig& retraction_config_old_extruder, coord_t perform_z_hop /*= 0*/)
1034 {
1035     if (current_extruder == new_extruder)
1036     {
1037         return;
1038     }
1039 
1040     const Settings& old_extruder_settings = Application::getInstance().current_slice->scene.extruders[current_extruder].settings;
1041     if(old_extruder_settings.get<bool>("retraction_enable"))
1042     {
1043         constexpr bool force = true;
1044         constexpr bool extruder_switch = true;
1045         writeRetraction(retraction_config_old_extruder, force, extruder_switch);
1046     }
1047 
1048     if (perform_z_hop > 0)
1049     {
1050         writeZhopStart(perform_z_hop);
1051     }
1052 
1053     resetExtrusionValue(); // zero the E value on the old extruder, so that the current_e_value is registered on the old extruder
1054 
1055     const std::string end_code = old_extruder_settings.get<std::string>("machine_extruder_end_code");
1056 
1057     if(!end_code.empty())
1058     {
1059         if (relative_extrusion)
1060         {
1061             writeExtrusionMode(false); // ensure absolute extrusion mode is set before the end gcode
1062         }
1063 
1064         writeCode(end_code.c_str());
1065 
1066         if (relative_extrusion)
1067         {
1068             writeExtrusionMode(true); // restore relative extrusion mode
1069         }
1070     }
1071 
1072     startExtruder(new_extruder);
1073 }
1074 
writeCode(const char * str)1075 void GCodeExport::writeCode(const char* str)
1076 {
1077     *output_stream << str << new_line;
1078 }
1079 
writePrimeTrain(const Velocity & travel_speed)1080 void GCodeExport::writePrimeTrain(const Velocity& travel_speed)
1081 {
1082     if (extruder_attr[current_extruder].is_primed)
1083     { // extruder is already primed once!
1084         return;
1085     }
1086     const Settings& extruder_settings = Application::getInstance().current_slice->scene.extruders[current_extruder].settings;
1087     if (extruder_settings.get<bool>("prime_blob_enable"))
1088     { // only move to prime position if we do a blob/poop
1089         // ideally the prime position would be respected whether we do a blob or not,
1090         // but the frontend currently doesn't support a value function of an extruder setting depending on an fdmprinter setting,
1091         // which is needed to automatically ignore the prime position for the printer when blob is disabled
1092         Point3 prime_pos(extruder_settings.get<coord_t>("extruder_prime_pos_x"), extruder_settings.get<coord_t>("extruder_prime_pos_y"), extruder_settings.get<coord_t>("extruder_prime_pos_z"));
1093         if (!extruder_settings.get<bool>("extruder_prime_pos_abs"))
1094         {
1095             // currentPosition.z can be already z hopped
1096             prime_pos += Point3(currentPosition.x, currentPosition.y, current_layer_z);
1097         }
1098         writeTravel(prime_pos, travel_speed);
1099     }
1100 
1101     if (flavor == EGCodeFlavor::GRIFFIN)
1102     {
1103         bool should_correct_z = false;
1104 
1105         std::string command = "G280";
1106         if (!extruder_settings.get<bool>("prime_blob_enable"))
1107         {
1108             command += " S1";  // use S1 to disable prime blob
1109             should_correct_z = true;
1110         }
1111         *output_stream << command << new_line;
1112 
1113         // There was an issue with the S1 strategy parameter, where it would only change the material-position,
1114         //   as opposed to 'be a prime-blob maneuvre without actually printing the prime blob', as we assumed here.
1115         // After a chat, the firmware-team decided to change the S1 strategy behaviour,
1116         //   but since people don't update their firmware at each opportunity, it was decided to fix it here as well.
1117         if (should_correct_z)
1118         {
1119             // Can't output via 'writeTravel', since if this is needed, the value saved for 'current height' will not be correct.
1120             // For similar reasons, this isn't written to the front-end via command-socket.
1121             *output_stream << "G0 Z" << MMtoStream{getPositionZ()} << new_line;
1122         }
1123     }
1124     else
1125     {
1126         // there is no prime gcode for other firmware versions...
1127     }
1128 
1129     extruder_attr[current_extruder].is_primed = true;
1130 }
1131 
setExtruderFanNumber(int extruder)1132 void GCodeExport::setExtruderFanNumber(int extruder)
1133 {
1134     if (extruder_attr[extruder].fan_number != fan_number)
1135     {
1136         fan_number = extruder_attr[extruder].fan_number;
1137         current_fan_speed = -1; // ensure fan speed gcode gets output for this fan
1138     }
1139 }
1140 
writeFanCommand(double speed)1141 void GCodeExport::writeFanCommand(double speed)
1142 {
1143     if (std::abs(current_fan_speed - speed) < 0.1)
1144     {
1145         return;
1146     }
1147     if(flavor == EGCodeFlavor::MAKERBOT)
1148     {
1149         if(speed >= 50)
1150         {
1151             *output_stream << "M126 T0" << new_line; //Makerbot cannot PWM the fan speed...
1152         }
1153         else
1154         {
1155             *output_stream << "M127 T0" << new_line;
1156         }
1157     }
1158     else if (speed > 0)
1159     {
1160         *output_stream << "M106 S" << PrecisionedDouble{1, speed * 255 / 100};
1161         if (fan_number)
1162         {
1163             *output_stream << " P" << fan_number;
1164         }
1165         *output_stream << new_line;
1166     }
1167     else
1168     {
1169         *output_stream << "M107";
1170         if (fan_number)
1171         {
1172             *output_stream << " P" << fan_number;
1173         }
1174         *output_stream << new_line;
1175     }
1176 
1177     current_fan_speed = speed;
1178 }
1179 
writeTemperatureCommand(const size_t extruder,const Temperature & temperature,const bool wait)1180 void GCodeExport::writeTemperatureCommand(const size_t extruder, const Temperature& temperature, const bool wait)
1181 {
1182     const ExtruderTrain& extruder_train = Application::getInstance().current_slice->scene.extruders[extruder];
1183 
1184     if (!extruder_train.settings.get<bool>("machine_nozzle_temp_enabled"))
1185     {
1186         return;
1187     }
1188 
1189     if (extruder_train.settings.get<bool>("machine_extruders_share_heater"))
1190     {
1191         // extruders share a single heater
1192         if (extruder != current_extruder)
1193         {
1194             // ignore all changes to the non-current extruder
1195             return;
1196         }
1197 
1198         // sync all extruders with the change to the current extruder
1199         const size_t extruder_count = Application::getInstance().current_slice->scene.extruders.size();
1200 
1201         for (size_t extruder_nr = 0; extruder_nr < extruder_count; extruder_nr++)
1202         {
1203             if (extruder_nr != extruder)
1204             {
1205                 // only reset the other extruders' waited_for_temperature state when the new temperature
1206                 // is greater than the old temperature
1207                 if (wait || temperature > extruder_attr[extruder_nr].currentTemperature)
1208                 {
1209                     extruder_attr[extruder_nr].waited_for_temperature = wait;
1210                 }
1211                 extruder_attr[extruder_nr].currentTemperature = temperature;
1212             }
1213         }
1214     }
1215 
1216     if ((!wait || extruder_attr[extruder].waited_for_temperature) && extruder_attr[extruder].currentTemperature == temperature)
1217     {
1218         return;
1219     }
1220 
1221     if (wait && flavor != EGCodeFlavor::MAKERBOT)
1222     {
1223         if(flavor == EGCodeFlavor::MARLIN)
1224         {
1225             *output_stream << "M105" << new_line; // get temperatures from the last update, the M109 will not let get the target temperature
1226         }
1227         *output_stream << "M109";
1228         extruder_attr[extruder].waited_for_temperature = true;
1229     }
1230     else
1231     {
1232         *output_stream << "M104";
1233         extruder_attr[extruder].waited_for_temperature = false;
1234     }
1235     if (extruder != current_extruder)
1236     {
1237         *output_stream << " T" << extruder;
1238     }
1239 #ifdef ASSERT_INSANE_OUTPUT
1240     assert(temperature >= 0);
1241 #endif // ASSERT_INSANE_OUTPUT
1242     *output_stream << " S" << PrecisionedDouble{1, temperature} << new_line;
1243     if (extruder != current_extruder && always_write_active_tool)
1244     {
1245         //Some firmwares (ie Smoothieware) change tools every time a "T" command is read - even on a M104 line, so we need to switch back to the active tool.
1246         *output_stream << "T" << current_extruder << new_line;
1247     }
1248     if (wait && flavor == EGCodeFlavor::MAKERBOT)
1249     {
1250         //Makerbot doesn't use M109 for heat-and-wait. Instead, use M104 and then wait using M116.
1251         *output_stream << "M116" << new_line;
1252     }
1253     extruder_attr[extruder].currentTemperature = temperature;
1254 }
1255 
writeBedTemperatureCommand(const Temperature & temperature,const bool wait)1256 void GCodeExport::writeBedTemperatureCommand(const Temperature& temperature, const bool wait)
1257 {
1258     if (flavor == EGCodeFlavor::ULTIGCODE)
1259     { // The UM2 family doesn't support temperature commands (they are fixed in the firmware)
1260         return;
1261     }
1262 
1263     if (wait)
1264     {
1265         if(flavor == EGCodeFlavor::MARLIN)
1266         {
1267             *output_stream << "M140 S"; // set the temperature, it will be used as target temperature from M105
1268             *output_stream << PrecisionedDouble{1, temperature} << new_line;
1269             *output_stream << "M105" << new_line;
1270         }
1271 
1272         *output_stream << "M190 S";
1273     }
1274     else
1275         *output_stream << "M140 S";
1276     *output_stream << PrecisionedDouble{1, temperature} << new_line;
1277 }
1278 
writeBuildVolumeTemperatureCommand(const Temperature & temperature,const bool wait)1279 void GCodeExport::writeBuildVolumeTemperatureCommand(const Temperature& temperature, const bool wait)
1280 {
1281     if (flavor == EGCodeFlavor::ULTIGCODE || flavor == EGCodeFlavor::GRIFFIN)
1282     {
1283         //Ultimaker printers don't support build volume temperature commands.
1284         return;
1285     }
1286     if (wait)
1287     {
1288         *output_stream << "M191 S";
1289     }
1290     else
1291     {
1292         *output_stream << "M141 S";
1293     }
1294     *output_stream << PrecisionedDouble{1, temperature} << new_line;
1295 }
1296 
writePrintAcceleration(const Acceleration & acceleration)1297 void GCodeExport::writePrintAcceleration(const Acceleration& acceleration)
1298 {
1299     switch (getFlavor())
1300     {
1301         case EGCodeFlavor::REPETIER:
1302             if (current_print_acceleration != acceleration)
1303             {
1304                 *output_stream << "M201 X" << PrecisionedDouble{0, acceleration} << " Y" << PrecisionedDouble{0, acceleration} << new_line;
1305             }
1306             break;
1307         case EGCodeFlavor::REPRAP:
1308             if (current_print_acceleration != acceleration)
1309             {
1310                 *output_stream << "M204 P" << PrecisionedDouble{0, acceleration} << new_line;
1311             }
1312             break;
1313         default:
1314             // MARLIN, etc. only have one acceleration for both print and travel
1315             if (current_print_acceleration != acceleration)
1316             {
1317                 *output_stream << "M204 S" << PrecisionedDouble{0, acceleration} << new_line;
1318             }
1319             break;
1320     }
1321     current_print_acceleration = acceleration;
1322     estimateCalculator.setAcceleration(acceleration);
1323 }
1324 
writeTravelAcceleration(const Acceleration & acceleration)1325 void GCodeExport::writeTravelAcceleration(const Acceleration& acceleration)
1326 {
1327     switch (getFlavor())
1328     {
1329         case EGCodeFlavor::REPETIER:
1330             if (current_travel_acceleration != acceleration)
1331             {
1332                 *output_stream << "M202 X" << PrecisionedDouble{0, acceleration} << " Y" << PrecisionedDouble{0, acceleration} << new_line;
1333             }
1334             break;
1335         case EGCodeFlavor::REPRAP:
1336             if (current_travel_acceleration != acceleration)
1337             {
1338                 *output_stream << "M204 T" << PrecisionedDouble{0, acceleration} << new_line;
1339             }
1340             break;
1341         default:
1342             // MARLIN, etc. only have one acceleration for both print and travel
1343             writePrintAcceleration(acceleration);
1344             break;
1345     }
1346     current_travel_acceleration = acceleration;
1347     estimateCalculator.setAcceleration(acceleration);
1348 }
1349 
writeJerk(const Velocity & jerk)1350 void GCodeExport::writeJerk(const Velocity& jerk)
1351 {
1352     if (current_jerk != jerk)
1353     {
1354         switch (getFlavor())
1355         {
1356             case EGCodeFlavor::REPETIER:
1357                 *output_stream << "M207 X" << PrecisionedDouble{2, jerk} << new_line;
1358                 break;
1359             case EGCodeFlavor::REPRAP:
1360                 *output_stream << "M566 X" << PrecisionedDouble{2, jerk * 60} << " Y" << PrecisionedDouble{2, jerk * 60} << new_line;
1361                 break;
1362             default:
1363                 *output_stream << "M205 X" << PrecisionedDouble{2, jerk} << " Y" << PrecisionedDouble{2, jerk} << new_line;
1364                 break;
1365         }
1366         current_jerk = jerk;
1367         estimateCalculator.setMaxXyJerk(jerk);
1368     }
1369 }
1370 
finalize(const char * endCode)1371 void GCodeExport::finalize(const char* endCode)
1372 {
1373     writeFanCommand(0);
1374     writeCode(endCode);
1375     int64_t print_time = getSumTotalPrintTimes();
1376     int mat_0 = getTotalFilamentUsed(0);
1377     log("Print time (s): %d\n", print_time);
1378     log("Print time (hr|min|s): %dh %dm %ds\n", int(print_time / 60 / 60), int((print_time / 60) % 60), int(print_time % 60));
1379     log("Filament (mm^3): %d\n", mat_0);
1380     for(int n=1; n<MAX_EXTRUDERS; n++)
1381         if (getTotalFilamentUsed(n) > 0)
1382             log("Filament%d: %d\n", n + 1, int(getTotalFilamentUsed(n)));
1383     output_stream->flush();
1384 }
1385 
getExtrudedVolumeAfterLastWipe(size_t extruder)1386 double GCodeExport::getExtrudedVolumeAfterLastWipe(size_t extruder)
1387 {
1388     return eToMm3(extruder_attr[extruder].last_e_value_after_wipe, extruder);
1389 }
1390 
ResetLastEValueAfterWipe(size_t extruder)1391 void GCodeExport::ResetLastEValueAfterWipe(size_t extruder)
1392 {
1393     extruder_attr[extruder].last_e_value_after_wipe = 0;
1394 }
1395 
insertWipeScript(const WipeScriptConfig & wipe_config)1396 void GCodeExport::insertWipeScript(const WipeScriptConfig& wipe_config)
1397 {
1398     const Point3 prev_position = currentPosition;
1399     writeComment("WIPE_SCRIPT_BEGIN");
1400 
1401     if (wipe_config.retraction_enable)
1402     {
1403         writeRetraction(wipe_config.retraction_config);
1404     }
1405 
1406     if (wipe_config.hop_enable)
1407     {
1408         writeZhopStart(wipe_config.hop_amount, wipe_config.hop_speed);
1409     }
1410 
1411     writeTravel(Point(wipe_config.brush_pos_x, currentPosition.y), wipe_config.move_speed);
1412     for (size_t i = 0; i < wipe_config.repeat_count; ++i)
1413     {
1414         coord_t x = currentPosition.x + (i % 2 ? -wipe_config.move_distance : wipe_config.move_distance);
1415         writeTravel(Point(x, currentPosition.y), wipe_config.move_speed);
1416     }
1417 
1418     writeTravel(prev_position, wipe_config.move_speed);
1419 
1420     if (wipe_config.hop_enable)
1421     {
1422         writeZhopEnd(wipe_config.hop_speed);
1423     }
1424 
1425     if (wipe_config.retraction_enable)
1426     {
1427         writeUnretractionAndPrime();
1428     }
1429 
1430     if (wipe_config.pause > 0)
1431     {
1432         writeDelay(wipe_config.pause);
1433     }
1434 
1435     writeComment("WIPE_SCRIPT_END");
1436 }
1437 
1438 }//namespace cura
1439 
1440