forked from Ultimaker/CuraEngine
-
Notifications
You must be signed in to change notification settings - Fork 0
/
FffGcodeWriter.cpp
3069 lines (2705 loc) · 156 KB
/
FffGcodeWriter.cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
//Copyright (c) 2021 Ultimaker B.V.
//CuraEngine is released under the terms of the AGPLv3 or higher.
#include <list>
#include <limits> // numeric_limits
#include "Application.h"
#include "bridge.h"
#include "ExtruderTrain.h"
#include "FffGcodeWriter.h"
#include "FffProcessor.h"
#include "GcodeLayerThreader.h"
#include "infill.h"
#include "InsetOrderOptimizer.h"
#include "LayerPlan.h"
#include "raft.h"
#include "Slice.h"
#include "wallOverlap.h"
#include "communication/Communication.h" //To send layer view data.
#include "progress/Progress.h"
#include "utils/math.h"
#include "utils/orderOptimizer.h"
#define OMP_MAX_ACTIVE_LAYERS_PROCESSED 30 // TODO: hardcoded-value for the max number of layers being in the pipeline while writing away and destroying layers in a multi-threaded context
namespace cura
{
FffGcodeWriter::FffGcodeWriter()
: max_object_height(0)
, layer_plan_buffer(gcode)
{
for (unsigned int extruder_nr = 0; extruder_nr < MAX_EXTRUDERS; extruder_nr++)
{ // initialize all as max layer_nr, so that they get updated to the lowest layer on which they are used.
extruder_prime_layer_nr[extruder_nr] = std::numeric_limits<int>::max();
}
}
void FffGcodeWriter::writeGCode(SliceDataStorage& storage, TimeKeeper& time_keeper)
{
const size_t start_extruder_nr = getStartExtruder(storage);
gcode.preSetup(start_extruder_nr);
Scene& scene = Application::getInstance().current_slice->scene;
if (scene.current_mesh_group == scene.mesh_groups.begin()) //First mesh group.
{
gcode.resetTotalPrintTimeAndFilament();
gcode.setInitialAndBuildVolumeTemps(start_extruder_nr);
}
Application::getInstance().communication->beginGCode();
setConfigFanSpeedLayerTime();
setConfigRetraction(storage);
setConfigWipe(storage);
if (scene.current_mesh_group == scene.mesh_groups.begin())
{
processStartingCode(storage, start_extruder_nr);
}
else
{
processNextMeshGroupCode(storage);
}
size_t total_layers = 0;
for (SliceMeshStorage& mesh : storage.meshes)
{
if (mesh.isPrinted()) //No need to process higher layers if the non-printed meshes are higher than the normal meshes.
{
total_layers = std::max(total_layers, mesh.layers.size());
}
setInfillAndSkinAngles(mesh);
}
setSupportAngles(storage);
gcode.writeLayerCountComment(total_layers);
{ // calculate the mesh order for each extruder
const size_t extruder_count = Application::getInstance().current_slice->scene.extruders.size();
mesh_order_per_extruder.clear(); // Might be not empty in case of sequential printing.
mesh_order_per_extruder.reserve(extruder_count);
for (size_t extruder_nr = 0; extruder_nr < extruder_count; extruder_nr++)
{
mesh_order_per_extruder.push_back(calculateMeshOrder(storage, extruder_nr));
}
}
calculateExtruderOrderPerLayer(storage);
calculatePrimeLayerPerExtruder(storage);
if (scene.current_mesh_group->settings.get<bool>("magic_spiralize"))
{
findLayerSeamsForSpiralize(storage, total_layers);
}
int process_layer_starting_layer_nr = 0;
const bool has_raft = scene.current_mesh_group->settings.get<EPlatformAdhesion>("adhesion_type") == EPlatformAdhesion::RAFT;
if (has_raft)
{
processRaft(storage);
// process filler layers to fill the airgap with helper object (support etc) so that they stick better to the raft.
// only process the filler layers if there is anything to print in them.
for (bool extruder_is_used_in_filler_layers : storage.getExtrudersUsed(-1))
{
if (extruder_is_used_in_filler_layers)
{
process_layer_starting_layer_nr = -Raft::getFillerLayerCount();
break;
}
}
}
const std::function<LayerPlan* (int)>& produce_item =
[&storage, total_layers, this](int layer_nr)
{
LayerPlan& gcode_layer = processLayer(storage, layer_nr, total_layers);
return &gcode_layer;
};
const std::function<void (LayerPlan*)>& consume_item =
[this, total_layers](LayerPlan* gcode_layer)
{
Progress::messageProgress(Progress::Stage::EXPORT, std::max(0, gcode_layer->getLayerNr()) + 1, total_layers);
layer_plan_buffer.handle(*gcode_layer, gcode);
};
const unsigned int max_task_count = OMP_MAX_ACTIVE_LAYERS_PROCESSED;
GcodeLayerThreader<LayerPlan> threader(
process_layer_starting_layer_nr
, static_cast<int>(total_layers)
, produce_item
, consume_item
, max_task_count
);
// process all layers, process buffer for preheating and minimal layer time etc, write layers to gcode:
threader.run();
layer_plan_buffer.flush();
Progress::messageProgressStage(Progress::Stage::FINISH, &time_keeper);
//Store the object height for when we are printing multiple objects, as we need to clear every one of them when moving to the next position.
max_object_height = std::max(max_object_height, storage.model_max.z);
constexpr bool force = true;
gcode.writeRetraction(storage.retraction_config_per_extruder[gcode.getExtruderNr()], force); // retract after finishing each meshgroup
}
unsigned int FffGcodeWriter::findSpiralizedLayerSeamVertexIndex(const SliceDataStorage& storage, const SliceMeshStorage& mesh, const int layer_nr, const int last_layer_nr)
{
const SliceLayer& layer = mesh.layers[layer_nr];
// last_layer_nr will be < 0 until we have processed the first non-empty layer
if (last_layer_nr < 0)
{
// If the user has specified a z-seam location, use the vertex closest to that location for the seam vertex
// in the first layer that has a part with insets. This allows the user to alter the seam start location which
// could be useful if the spiralization has a problem with a particular seam path.
Point seam_pos(0, 0);
if (mesh.settings.get<EZSeamType>("z_seam_type") == EZSeamType::USER_SPECIFIED)
{
seam_pos = mesh.getZSeamHint();
}
return PolygonUtils::findClosest(seam_pos, layer.parts[0].insets[0][0]).point_idx;
}
else
{
// note that the code below doesn't assume that last_layer_nr is one less than layer_nr but the print is going
// to come out pretty weird if that isn't true as it implies that there are empty layers
ConstPolygonRef last_wall = (*storage.spiralize_wall_outlines[last_layer_nr])[0];
ConstPolygonRef wall = layer.parts[0].insets[0][0];
const int n_points = wall.size();
Point last_wall_seam_vertex = last_wall[storage.spiralize_seam_vertex_indices[last_layer_nr]];
// seam_vertex_idx is going to be the index of the seam vertex in the current wall polygon
// initially we choose the vertex that is closest to the seam vertex in the last spiralized layer processed
int seam_vertex_idx = PolygonUtils::findNearestVert(last_wall_seam_vertex, wall);
// now we check that the vertex following the seam vertex is to the left of the seam vertex in the last layer
// and if it isn't, we move forward
if (vSize(last_wall_seam_vertex - wall[seam_vertex_idx]) >= mesh.settings.get<coord_t>("meshfix_maximum_resolution"))
{
// get the inward normal of the last layer seam vertex
Point last_wall_seam_vertex_inward_normal = PolygonUtils::getVertexInwardNormal(last_wall, storage.spiralize_seam_vertex_indices[last_layer_nr]);
// create a vector from the normal so that we can then test the vertex following the candidate seam vertex to make sure it is on the correct side
Point last_wall_seam_vertex_vector = last_wall_seam_vertex + last_wall_seam_vertex_inward_normal;
// now test the vertex following the candidate seam vertex and if it lies to the left of the vector, it's good to use
float a = LinearAlg2D::getAngleLeft(last_wall_seam_vertex_vector, last_wall_seam_vertex, wall[(seam_vertex_idx + 1) % n_points]);
if (a <= 0 || a >= M_PI)
{
// the vertex was not on the left of the vector so move the seam vertex on
seam_vertex_idx = (seam_vertex_idx + 1) % n_points;
a = LinearAlg2D::getAngleLeft(last_wall_seam_vertex_vector, last_wall_seam_vertex, wall[(seam_vertex_idx + 1) % n_points]);
}
}
return seam_vertex_idx;
}
}
void FffGcodeWriter::findLayerSeamsForSpiralize(SliceDataStorage& storage, size_t total_layers)
{
// The spiral has to continue on in an anti-clockwise direction from where the last layer finished, it can't jump backwards
// we track the seam position for each layer and ensure that the seam position for next layer continues in the right direction
storage.spiralize_wall_outlines.assign(total_layers, nullptr); // default is no information available
storage.spiralize_seam_vertex_indices.assign(total_layers, 0);
int last_layer_nr = -1; // layer number of the last non-empty layer processed (for any extruder or mesh)
for (unsigned layer_nr = 0; layer_nr < total_layers; ++layer_nr)
{
bool done_this_layer = false;
// iterate through extruders until we find a mesh that has a part with insets
const std::vector<size_t>& extruder_order = extruder_order_per_layer[layer_nr];
for (unsigned int extruder_idx = 0; !done_this_layer && extruder_idx < extruder_order.size(); ++extruder_idx)
{
const size_t extruder_nr = extruder_order[extruder_idx];
// iterate through this extruder's meshes until we find a part with insets
const std::vector<size_t>& mesh_order = mesh_order_per_extruder[extruder_nr];
for (unsigned int mesh_idx : mesh_order)
{
SliceMeshStorage& mesh = storage.meshes[mesh_idx];
// if this mesh has layer data for this layer process it
if (!done_this_layer && mesh.layers.size() > layer_nr)
{
SliceLayer& layer = mesh.layers[layer_nr];
// if the first part in the layer (if any) has insets, process it
if (layer.parts.size() != 0 && layer.parts[0].insets.size() != 0)
{
// save the seam vertex index for this layer as we need it to determine the seam vertex index for the next layer
storage.spiralize_seam_vertex_indices[layer_nr] = findSpiralizedLayerSeamVertexIndex(storage, mesh, layer_nr, last_layer_nr);
// save the wall outline for this layer so it can be used in the spiralize interpolation calculation
storage.spiralize_wall_outlines[layer_nr] = &layer.parts[0].insets[0];
last_layer_nr = layer_nr;
// ignore any further meshes/extruders for this layer
done_this_layer = true;
}
}
}
}
}
}
void FffGcodeWriter::setConfigFanSpeedLayerTime()
{
for (const ExtruderTrain& train : Application::getInstance().current_slice->scene.extruders)
{
fan_speed_layer_time_settings_per_extruder.emplace_back();
FanSpeedLayerTimeSettings& fan_speed_layer_time_settings = fan_speed_layer_time_settings_per_extruder.back();
fan_speed_layer_time_settings.cool_min_layer_time = train.settings.get<Duration>("cool_min_layer_time");
fan_speed_layer_time_settings.cool_min_layer_time_fan_speed_max = train.settings.get<Duration>("cool_min_layer_time_fan_speed_max");
fan_speed_layer_time_settings.cool_fan_speed_0 = train.settings.get<Ratio>("cool_fan_speed_0") * 100.0;
fan_speed_layer_time_settings.cool_fan_speed_min = train.settings.get<Ratio>("cool_fan_speed_min") * 100.0;
fan_speed_layer_time_settings.cool_fan_speed_max = train.settings.get<Ratio>("cool_fan_speed_max") * 100.0;
fan_speed_layer_time_settings.cool_min_speed = train.settings.get<Velocity>("cool_min_speed");
fan_speed_layer_time_settings.cool_fan_full_layer = train.settings.get<LayerIndex>("cool_fan_full_layer");
if (!train.settings.get<bool>("cool_fan_enabled"))
{
fan_speed_layer_time_settings.cool_fan_speed_0 = 0;
fan_speed_layer_time_settings.cool_fan_speed_min = 0;
fan_speed_layer_time_settings.cool_fan_speed_max = 0;
}
}
}
void FffGcodeWriter::setConfigRetraction(SliceDataStorage& storage)
{
Scene& scene = Application::getInstance().current_slice->scene;
for (size_t extruder_index = 0; extruder_index < scene.extruders.size(); extruder_index++)
{
ExtruderTrain& train = scene.extruders[extruder_index];
RetractionConfig& retraction_config = storage.retraction_config_per_extruder[extruder_index];
retraction_config.distance = (train.settings.get<bool>("retraction_enable")) ? train.settings.get<double>("retraction_amount") : 0; //Retraction distance in mm.
retraction_config.prime_volume = train.settings.get<double>("retraction_extra_prime_amount"); //Extra prime volume in mm^3.
retraction_config.speed = train.settings.get<Velocity>("retraction_retract_speed");
retraction_config.primeSpeed = train.settings.get<Velocity>("retraction_prime_speed");
retraction_config.zHop = train.settings.get<coord_t>("retraction_hop");
retraction_config.retraction_min_travel_distance = train.settings.get<coord_t>("retraction_min_travel");
retraction_config.retraction_extrusion_window = train.settings.get<double>("retraction_extrusion_window"); //Window to count retractions in in mm of extruded filament.
retraction_config.retraction_count_max = train.settings.get<size_t>("retraction_count_max");
RetractionConfig& switch_retraction_config = storage.extruder_switch_retraction_config_per_extruder[extruder_index];
switch_retraction_config.distance = train.settings.get<double>("switch_extruder_retraction_amount"); //Retraction distance in mm.
switch_retraction_config.prime_volume = 0.0;
switch_retraction_config.speed = train.settings.get<Velocity>("switch_extruder_retraction_speed");
switch_retraction_config.primeSpeed = train.settings.get<Velocity>("switch_extruder_prime_speed");
switch_retraction_config.zHop = train.settings.get<coord_t>("retraction_hop_after_extruder_switch_height");
switch_retraction_config.retraction_min_travel_distance = 0; // no limitation on travel distance for an extruder switch retract
switch_retraction_config.retraction_extrusion_window = 99999.9; // so that extruder switch retractions won't affect the retraction buffer (extruded_volume_at_previous_n_retractions)
switch_retraction_config.retraction_count_max = 9999999; // extruder switch retraction is never limited
}
}
void FffGcodeWriter::setConfigWipe(SliceDataStorage& storage)
{
Scene& scene = Application::getInstance().current_slice->scene;
for (size_t extruder_index = 0; extruder_index < scene.extruders.size(); extruder_index++)
{
ExtruderTrain& train = scene.extruders[extruder_index];
WipeScriptConfig& wipe_config = storage.wipe_config_per_extruder[extruder_index];
wipe_config.retraction_enable = train.settings.get<bool>("wipe_retraction_enable");
wipe_config.retraction_config.distance = train.settings.get<double>("wipe_retraction_amount");
wipe_config.retraction_config.speed = train.settings.get<Velocity>("wipe_retraction_retract_speed");
wipe_config.retraction_config.primeSpeed = train.settings.get<Velocity>("wipe_retraction_prime_speed");
wipe_config.retraction_config.prime_volume = train.settings.get<double>("wipe_retraction_extra_prime_amount");
wipe_config.retraction_config.retraction_min_travel_distance = 0;
wipe_config.retraction_config.retraction_extrusion_window = std::numeric_limits<double>::max();
wipe_config.retraction_config.retraction_count_max = std::numeric_limits<size_t>::max();
wipe_config.pause = train.settings.get<Duration>("wipe_pause");
wipe_config.hop_enable = train.settings.get<bool>("wipe_hop_enable");
wipe_config.hop_amount = train.settings.get<coord_t>("wipe_hop_amount");
wipe_config.hop_speed = train.settings.get<Velocity>("wipe_hop_speed");
wipe_config.brush_pos_x = train.settings.get<coord_t>("wipe_brush_pos_x");
wipe_config.repeat_count = train.settings.get<size_t>("wipe_repeat_count");
wipe_config.move_distance = train.settings.get<coord_t>("wipe_move_distance");
wipe_config.move_speed = train.settings.get<Velocity>("speed_travel");
wipe_config.max_extrusion_mm3 = train.settings.get<double>("max_extrusion_before_wipe");
wipe_config.clean_between_layers = train.settings.get<bool>("clean_between_layers");
}
}
unsigned int FffGcodeWriter::getStartExtruder(const SliceDataStorage& storage)
{
const Settings& mesh_group_settings = Application::getInstance().current_slice->scene.current_mesh_group->settings;
size_t start_extruder_nr = mesh_group_settings.get<ExtruderTrain&>("adhesion_extruder_nr").extruder_nr;
if (mesh_group_settings.get<EPlatformAdhesion>("adhesion_type") == EPlatformAdhesion::NONE)
{
if (mesh_group_settings.get<bool>("support_enable") && mesh_group_settings.get<bool>("support_brim_enable"))
{
start_extruder_nr = mesh_group_settings.get<ExtruderTrain&>("support_infill_extruder_nr").extruder_nr;
}
else
{
std::vector<bool> extruder_is_used = storage.getExtrudersUsed();
for (size_t extruder_nr = 0; extruder_nr < extruder_is_used.size(); extruder_nr++)
{
start_extruder_nr = extruder_nr;
if (extruder_is_used[extruder_nr])
{
break;
}
}
}
}
assert(start_extruder_nr < Application::getInstance().current_slice->scene.extruders.size() && "start_extruder_nr must be a valid extruder");
return start_extruder_nr;
}
void FffGcodeWriter::setInfillAndSkinAngles(SliceMeshStorage& mesh)
{
if (mesh.infill_angles.size() == 0)
{
mesh.infill_angles = mesh.settings.get<std::vector<AngleDegrees>>("infill_angles");
if (mesh.infill_angles.size() == 0)
{
// user has not specified any infill angles so use defaults
const EFillMethod infill_pattern = mesh.settings.get<EFillMethod>("infill_pattern");
if (infill_pattern == EFillMethod::CROSS || infill_pattern == EFillMethod::CROSS_3D)
{
mesh.infill_angles.push_back(22); // put most infill lines in between 45 and 0 degrees
}
else
{
mesh.infill_angles.push_back(45); // generally all infill patterns use 45 degrees
if (infill_pattern == EFillMethod::LINES || infill_pattern == EFillMethod::ZIG_ZAG)
{
// lines and zig zag patterns default to also using 135 degrees
mesh.infill_angles.push_back(135);
}
}
}
}
if (mesh.roofing_angles.size() == 0)
{
mesh.roofing_angles = mesh.settings.get<std::vector<AngleDegrees>>("roofing_angles");
if (mesh.roofing_angles.size() == 0)
{
// user has not specified any infill angles so use defaults
mesh.roofing_angles.push_back(45);
mesh.roofing_angles.push_back(135);
}
}
if (mesh.skin_angles.size() == 0)
{
mesh.skin_angles = mesh.settings.get<std::vector<AngleDegrees>>("skin_angles");
if (mesh.skin_angles.size() == 0)
{
// user has not specified any infill angles so use defaults
mesh.skin_angles.push_back(45);
mesh.skin_angles.push_back(135);
}
}
}
void FffGcodeWriter::setSupportAngles(SliceDataStorage& storage)
{
const Settings& mesh_group_settings = Application::getInstance().current_slice->scene.current_mesh_group->settings;
const ExtruderTrain& support_infill_extruder = mesh_group_settings.get<ExtruderTrain&>("support_infill_extruder_nr");
storage.support.support_infill_angles = support_infill_extruder.settings.get<std::vector<AngleDegrees>>("support_infill_angles");
if (storage.support.support_infill_angles.empty())
{
storage.support.support_infill_angles.push_back(0);
}
const ExtruderTrain& support_extruder_nr_layer_0 = mesh_group_settings.get<ExtruderTrain&>("support_extruder_nr_layer_0");
storage.support.support_infill_angles_layer_0 = support_extruder_nr_layer_0.settings.get<std::vector<AngleDegrees>>("support_infill_angles");
if (storage.support.support_infill_angles_layer_0.empty())
{
storage.support.support_infill_angles_layer_0.push_back(0);
}
auto getInterfaceAngles = [&storage](const ExtruderTrain& extruder, const std::string& interface_angles_setting, const EFillMethod pattern, const std::string& interface_height_setting)
{
std::vector<AngleDegrees> angles = extruder.settings.get<std::vector<AngleDegrees>>(interface_angles_setting);
if (angles.empty())
{
if (pattern == EFillMethod::CONCENTRIC)
{
angles.push_back(0); //Concentric has no rotation.
}
else if (pattern == EFillMethod::TRIANGLES)
{
angles.push_back(90); //Triangular support interface shouldn't alternate every layer.
}
else
{
for (const SliceMeshStorage& mesh : storage.meshes)
{
if (mesh.settings.get<coord_t>(interface_height_setting) >= 2 * Application::getInstance().current_slice->scene.current_mesh_group->settings.get<coord_t>("layer_height"))
{
//Some roofs are quite thick.
//Alternate between the two kinds of diagonal: / and \ .
angles.push_back(45);
angles.push_back(135);
}
}
if (angles.empty())
{
angles.push_back(90); //Perpendicular to support lines.
}
}
}
return angles;
};
const ExtruderTrain& roof_extruder = mesh_group_settings.get<ExtruderTrain&>("support_roof_extruder_nr");
storage.support.support_roof_angles = getInterfaceAngles(roof_extruder, "support_roof_angles", roof_extruder.settings.get<EFillMethod>("support_roof_pattern"), "support_roof_height");
const ExtruderTrain& bottom_extruder = mesh_group_settings.get<ExtruderTrain&>("support_bottom_extruder_nr");
storage.support.support_bottom_angles = getInterfaceAngles(bottom_extruder, "support_bottom_angles", bottom_extruder.settings.get<EFillMethod>("support_bottom_pattern"), "support_bottom_height");
}
void FffGcodeWriter::processInitialLayerTemperature(const SliceDataStorage& storage, const size_t start_extruder_nr)
{
std::vector<bool> extruder_is_used = storage.getExtrudersUsed();
Scene& scene = Application::getInstance().current_slice->scene;
const size_t num_extruders = scene.extruders.size();
if (gcode.getFlavor() == EGCodeFlavor::GRIFFIN)
{
ExtruderTrain& train = scene.extruders[start_extruder_nr];
constexpr bool wait = true;
const Temperature print_temp_0 = train.settings.get<Temperature>("material_print_temperature_layer_0");
const Temperature print_temp_here = (print_temp_0 != 0) ? print_temp_0 : train.settings.get<Temperature>("material_print_temperature");
gcode.writeTemperatureCommand(start_extruder_nr, print_temp_here, wait);
}
else if (gcode.getFlavor() != EGCodeFlavor::ULTIGCODE)
{
if (num_extruders > 1 || gcode.getFlavor() == EGCodeFlavor::REPRAP)
{
std::ostringstream tmp;
tmp << "T" << start_extruder_nr;
gcode.writeLine(tmp.str().c_str());
}
if (scene.current_mesh_group->settings.get<bool>("material_bed_temp_prepend"))
{
if (scene.current_mesh_group->settings.get<bool>("machine_heated_bed"))
{
const Temperature bed_temp = scene.current_mesh_group->settings.get<Temperature>("material_bed_temperature_layer_0");
if (bed_temp != 0)
{
gcode.writeBedTemperatureCommand(bed_temp, scene.current_mesh_group->settings.get<bool>("material_bed_temp_wait"));
}
}
}
if (scene.current_mesh_group->settings.get<bool>("material_print_temp_prepend"))
{
for (unsigned extruder_nr = 0; extruder_nr < num_extruders; extruder_nr++)
{
if (extruder_is_used[extruder_nr])
{
const ExtruderTrain& train = scene.extruders[extruder_nr];
Temperature extruder_temp;
if (extruder_nr == start_extruder_nr)
{
const Temperature print_temp_0 = train.settings.get<Temperature>("material_print_temperature_layer_0");
extruder_temp = (print_temp_0 != 0) ? print_temp_0 : train.settings.get<Temperature>("material_print_temperature");
}
else
{
extruder_temp = train.settings.get<Temperature>("material_standby_temperature");
}
gcode.writeTemperatureCommand(extruder_nr, extruder_temp);
}
}
if (scene.current_mesh_group->settings.get<bool>("material_print_temp_wait"))
{
for (unsigned extruder_nr = 0; extruder_nr < num_extruders; extruder_nr++)
{
if (extruder_is_used[extruder_nr])
{
const ExtruderTrain& train = scene.extruders[extruder_nr];
Temperature extruder_temp;
if (extruder_nr == start_extruder_nr)
{
const Temperature print_temp_0 = train.settings.get<Temperature>("material_print_temperature_layer_0");
extruder_temp = (print_temp_0 != 0) ? print_temp_0 : train.settings.get<Temperature>("material_print_temperature");
}
else
{
extruder_temp = train.settings.get<Temperature>("material_standby_temperature");
}
gcode.writeTemperatureCommand(extruder_nr, extruder_temp, true);
}
}
}
}
}
}
void FffGcodeWriter::processStartingCode(const SliceDataStorage& storage, const size_t start_extruder_nr)
{
std::vector<bool> extruder_is_used = storage.getExtrudersUsed();
if (Application::getInstance().communication->isSequential()) //If we must output the g-code sequentially, we must already place the g-code header here even if we don't know the exact time/material usages yet.
{
std::string prefix = gcode.getFileHeader(extruder_is_used);
gcode.writeCode(prefix.c_str());
}
gcode.writeComment("Generated with Cura_SteamEngine " VERSION);
if (gcode.getFlavor() == EGCodeFlavor::GRIFFIN)
{
std::ostringstream tmp;
tmp << "T" << start_extruder_nr;
gcode.writeLine(tmp.str().c_str());
}
else
{
processInitialLayerTemperature(storage, start_extruder_nr);
}
const Settings& mesh_group_settings = Application::getInstance().current_slice->scene.current_mesh_group->settings;
gcode.writeExtrusionMode(false); // ensure absolute extrusion mode is set before the start gcode
gcode.writeCode(mesh_group_settings.get<std::string>("machine_start_gcode").c_str());
// in case of shared nozzle assume that the machine-start gcode reset the extruders as per machine description
if (Application::getInstance().current_slice->scene.settings.get<bool>("machine_extruders_share_nozzle"))
{
for (const ExtruderTrain& train : Application::getInstance().current_slice->scene.extruders)
{
gcode.resetExtruderToPrimed(train.extruder_nr, train.settings.get<double>("machine_extruders_shared_nozzle_initial_retraction"));
}
}
if (mesh_group_settings.get<bool>("machine_heated_build_volume"))
{
gcode.writeBuildVolumeTemperatureCommand(mesh_group_settings.get<Temperature>("build_volume_temperature"));
}
Application::getInstance().communication->sendCurrentPosition(gcode.getPositionXY());
gcode.startExtruder(start_extruder_nr);
if (gcode.getFlavor() == EGCodeFlavor::BFB)
{
gcode.writeComment("enable auto-retraction");
std::ostringstream tmp;
tmp << "M227 S" << (mesh_group_settings.get<coord_t>("retraction_amount") * 2560 / 1000) << " P" << (mesh_group_settings.get<coord_t>("retraction_amount") * 2560 / 1000);
gcode.writeLine(tmp.str().c_str());
}
else if (gcode.getFlavor() == EGCodeFlavor::GRIFFIN)
{ // initialize extruder trains
ExtruderTrain& train = Application::getInstance().current_slice->scene.extruders[start_extruder_nr];
processInitialLayerTemperature(storage, start_extruder_nr);
gcode.writePrimeTrain(train.settings.get<Velocity>("speed_travel"));
extruder_prime_layer_nr[start_extruder_nr] = std::numeric_limits<int>::min(); // set to most negative number so that layer processing never primes this extruder any more.
const RetractionConfig& retraction_config = storage.retraction_config_per_extruder[start_extruder_nr];
gcode.writeRetraction(retraction_config);
}
if (mesh_group_settings.get<bool>("relative_extrusion"))
{
gcode.writeExtrusionMode(true);
}
if (gcode.getFlavor() != EGCodeFlavor::GRIFFIN)
{
if (mesh_group_settings.get<bool>("retraction_enable"))
{
// ensure extruder is zeroed
gcode.resetExtrusionValue();
// retract before first travel move
gcode.writeRetraction(storage.retraction_config_per_extruder[start_extruder_nr]);
}
}
gcode.setExtruderFanNumber(start_extruder_nr);
}
void FffGcodeWriter::processNextMeshGroupCode(const SliceDataStorage& storage)
{
gcode.writeFanCommand(0);
gcode.setZ(max_object_height + 5000);
Application::getInstance().communication->sendCurrentPosition(gcode.getPositionXY());
gcode.writeTravel(gcode.getPositionXY(), Application::getInstance().current_slice->scene.extruders[gcode.getExtruderNr()].settings.get<Velocity>("speed_travel"));
Point start_pos(storage.model_min.x, storage.model_min.y);
gcode.writeTravel(start_pos, Application::getInstance().current_slice->scene.extruders[gcode.getExtruderNr()].settings.get<Velocity>("speed_travel"));
const Settings& mesh_group_settings = Application::getInstance().current_slice->scene.current_mesh_group->settings;
if (mesh_group_settings.get<bool>("machine_heated_bed") && mesh_group_settings.get<Temperature>("material_bed_temperature_layer_0") != 0)
{
const bool wait = mesh_group_settings.get<bool>("material_bed_temp_wait");
gcode.writeBedTemperatureCommand(mesh_group_settings.get<Temperature>("material_bed_temperature_layer_0"), wait);
}
processInitialLayerTemperature(storage, gcode.getExtruderNr());
}
void FffGcodeWriter::processRaft(const SliceDataStorage& storage)
{
Settings& mesh_group_settings = Application::getInstance().current_slice->scene.current_mesh_group->settings;
size_t extruder_nr = mesh_group_settings.get<ExtruderTrain&>("adhesion_extruder_nr").extruder_nr;
const ExtruderTrain& train = Application::getInstance().current_slice->scene.extruders[extruder_nr];
coord_t z = 0;
const LayerIndex initial_raft_layer_nr = -Raft::getTotalExtraLayers();
// some infill config for all lines infill generation below
constexpr int offset_from_poly_outline = 0;
constexpr double fill_overlap = 0; // raft line shouldn't be expanded - there is no boundary polygon printed
constexpr int infill_multiplier = 1; // rafts use single lines
constexpr int extra_infill_shift = 0;
coord_t max_resolution = mesh_group_settings.get<ExtruderTrain&>("adhesion_extruder_nr").settings.get<coord_t>("meshfix_maximum_resolution");
coord_t max_deviation = mesh_group_settings.get<ExtruderTrain&>("adhesion_extruder_nr").settings.get<coord_t>("meshfix_maximum_deviation");
Polygons raft_polygons; // should remain empty, since we only have the lines pattern for the raft...
std::optional<Point> last_planned_position = std::optional<Point>();
unsigned int current_extruder_nr = extruder_nr;
{ // raft base layer
LayerIndex layer_nr = initial_raft_layer_nr;
const coord_t layer_height = train.settings.get<coord_t>("raft_base_thickness");
z += layer_height;
const coord_t comb_offset = train.settings.get<coord_t>("raft_base_line_spacing");
std::vector<FanSpeedLayerTimeSettings> fan_speed_layer_time_settings_per_extruder_raft_base = fan_speed_layer_time_settings_per_extruder; // copy so that we change only the local copy
for (FanSpeedLayerTimeSettings& fan_speed_layer_time_settings : fan_speed_layer_time_settings_per_extruder_raft_base)
{
double regular_fan_speed = train.settings.get<Ratio>("raft_base_fan_speed") * 100.0;
fan_speed_layer_time_settings.cool_fan_speed_min = regular_fan_speed;
fan_speed_layer_time_settings.cool_fan_speed_0 = regular_fan_speed; // ignore initial layer fan speed stuff
}
LayerPlan& gcode_layer = *new LayerPlan(storage, layer_nr, z, layer_height, extruder_nr, fan_speed_layer_time_settings_per_extruder_raft_base, comb_offset, train.settings.get<bool>("raft_base_line_width"), train.settings.get<coord_t>("travel_avoid_distance"));
gcode_layer.setIsInside(true);
gcode_layer.setExtruder(extruder_nr);
Application::getInstance().communication->sendLayerComplete(layer_nr, z, layer_height);
Polygons wall = storage.raftOutline.offset(-gcode_layer.configs_storage.raft_base_config.getLineWidth() / 2);
wall.simplify(max_resolution, max_deviation); //Simplify because of a micron-movement created in corners when insetting a polygon that was offset with round joint type.
gcode_layer.addPolygonsByOptimizer(wall, gcode_layer.configs_storage.raft_base_config);
Polygons raftLines;
double fill_angle = 0;
constexpr bool zig_zaggify_infill = false;
constexpr bool connect_polygons = true; // causes less jerks, so better adhesion
constexpr int wall_line_count = 0;
const Point& infill_origin = Point();
Polygons* perimeter_gaps = nullptr;
constexpr bool connected_zigzags = false;
constexpr bool use_endpieces = true;
constexpr bool skip_some_zags = false;
constexpr int zag_skip_count = 0;
constexpr coord_t pocket_size = 0;
Infill infill_comp(
EFillMethod::LINES, zig_zaggify_infill, connect_polygons, wall, offset_from_poly_outline, gcode_layer.configs_storage.raft_base_config.getLineWidth(), train.settings.get<coord_t>("raft_base_line_spacing"),
fill_overlap, infill_multiplier, fill_angle, z, extra_infill_shift,
max_resolution, max_deviation,
wall_line_count, infill_origin, perimeter_gaps, connected_zigzags, use_endpieces, skip_some_zags, zag_skip_count, pocket_size
);
infill_comp.generate(raft_polygons, raftLines);
gcode_layer.addLinesByOptimizer(raftLines, gcode_layer.configs_storage.raft_base_config, SpaceFillType::Lines);
// When we use raft, we need to make sure that all used extruders for this print will get primed on the first raft layer,
// and then switch back to the original extruder.
std::vector<size_t> extruder_order = getUsedExtrudersOnLayerExcludingStartingExtruder(storage, extruder_nr, layer_nr);
for (const size_t to_be_primed_extruder_nr : extruder_order)
{
setExtruder_addPrime(storage, gcode_layer, to_be_primed_extruder_nr);
current_extruder_nr = to_be_primed_extruder_nr;
}
layer_plan_buffer.handle(gcode_layer, gcode);
last_planned_position = gcode_layer.getLastPlannedPositionOrStartingPosition();
}
{ // raft interface layer
const LayerIndex layer_nr = initial_raft_layer_nr + 1;
const coord_t layer_height = train.settings.get<coord_t>("raft_interface_thickness");
z += layer_height;
const coord_t comb_offset = train.settings.get<coord_t>("raft_interface_line_spacing");
std::vector<FanSpeedLayerTimeSettings> fan_speed_layer_time_settings_per_extruder_raft_interface = fan_speed_layer_time_settings_per_extruder; // copy so that we change only the local copy
for (FanSpeedLayerTimeSettings& fan_speed_layer_time_settings : fan_speed_layer_time_settings_per_extruder_raft_interface)
{
double regular_fan_speed = train.settings.get<Ratio>("raft_interface_fan_speed") * 100.0;
fan_speed_layer_time_settings.cool_fan_speed_min = regular_fan_speed;
fan_speed_layer_time_settings.cool_fan_speed_0 = regular_fan_speed; // ignore initial layer fan speed stuff
}
LayerPlan& gcode_layer = *new LayerPlan(storage, layer_nr, z, layer_height, current_extruder_nr, fan_speed_layer_time_settings_per_extruder_raft_interface, comb_offset, train.settings.get<coord_t>("raft_interface_line_width"), train.settings.get<coord_t>("travel_avoid_distance"));
gcode_layer.setIsInside(true);
gcode_layer.setExtruder(extruder_nr); // reset to extruder number, because we might have primed in the last layer
current_extruder_nr = extruder_nr;
Application::getInstance().communication->sendLayerComplete(layer_nr, z, layer_height);
Polygons raft_outline_path = storage.raftOutline.offset(-gcode_layer.configs_storage.raft_interface_config.getLineWidth() / 2); //Do this manually because of micron-movement created in corners when insetting a polygon that was offset with round joint type.
raft_outline_path.simplify(); //Remove those micron-movements.
constexpr coord_t infill_outline_width = 0;
Polygons raftLines;
int offset_from_poly_outline = 0;
AngleDegrees fill_angle = train.settings.get<size_t>("raft_surface_layers") > 0 ? 45 : 90;
constexpr bool zig_zaggify_infill = true;
constexpr bool connect_polygons = true; // why not?
constexpr int wall_line_count = 0;
const Point& infill_origin = Point();
Polygons* perimeter_gaps = nullptr;
constexpr bool connected_zigzags = false;
constexpr bool use_endpieces = true;
constexpr bool skip_some_zags = false;
constexpr int zag_skip_count = 0;
constexpr coord_t pocket_size = 0;
Infill infill_comp(
EFillMethod::ZIG_ZAG, zig_zaggify_infill, connect_polygons, raft_outline_path, offset_from_poly_outline, infill_outline_width, train.settings.get<coord_t>("raft_interface_line_spacing"),
fill_overlap, infill_multiplier, fill_angle, z, extra_infill_shift,
max_resolution, max_deviation,
wall_line_count, infill_origin, perimeter_gaps, connected_zigzags, use_endpieces, skip_some_zags, zag_skip_count, pocket_size
);
infill_comp.generate(raft_polygons, raftLines);
gcode_layer.addLinesByOptimizer(raftLines, gcode_layer.configs_storage.raft_interface_config, SpaceFillType::Lines, false, 0, 1.0, last_planned_position);
layer_plan_buffer.handle(gcode_layer, gcode);
last_planned_position = gcode_layer.getLastPlannedPositionOrStartingPosition();
}
coord_t layer_height = train.settings.get<coord_t>("raft_surface_thickness");
for (LayerIndex raft_surface_layer = 1; static_cast<size_t>(raft_surface_layer) <= train.settings.get<size_t>("raft_surface_layers"); raft_surface_layer++)
{ // raft surface layers
const LayerIndex layer_nr = initial_raft_layer_nr + 2 + raft_surface_layer - 1; // 2: 1 base layer, 1 interface layer
z += layer_height;
const coord_t comb_offset = train.settings.get<coord_t>("raft_surface_line_spacing");
std::vector<FanSpeedLayerTimeSettings> fan_speed_layer_time_settings_per_extruder_raft_surface = fan_speed_layer_time_settings_per_extruder; // copy so that we change only the local copy
for (FanSpeedLayerTimeSettings& fan_speed_layer_time_settings : fan_speed_layer_time_settings_per_extruder_raft_surface)
{
double regular_fan_speed = train.settings.get<Ratio>("raft_surface_fan_speed") * 100.0;
fan_speed_layer_time_settings.cool_fan_speed_min = regular_fan_speed;
fan_speed_layer_time_settings.cool_fan_speed_0 = regular_fan_speed; // ignore initial layer fan speed stuff
}
LayerPlan& gcode_layer = *new LayerPlan(storage, layer_nr, z, layer_height, extruder_nr, fan_speed_layer_time_settings_per_extruder_raft_surface, comb_offset, train.settings.get<coord_t>("raft_surface_line_width"), train.settings.get<coord_t>("travel_avoid_distance"));
gcode_layer.setIsInside(true);
// make sure that we are using the correct extruder to print raft
gcode_layer.setExtruder(extruder_nr);
current_extruder_nr = extruder_nr;
Application::getInstance().communication->sendLayerComplete(layer_nr, z, layer_height);
Polygons raft_outline_path = storage.raftOutline.offset(-gcode_layer.configs_storage.raft_surface_config.getLineWidth() / 2); //Do this manually because of micron-movement created in corners when insetting a polygon that was offset with round joint type.
raft_outline_path.simplify(); //Remove those micron-movements.
constexpr coord_t infill_outline_width = 0;
Polygons raft_lines;
int offset_from_poly_outline = 0;
AngleDegrees fill_angle = 90 * raft_surface_layer;
constexpr bool zig_zaggify_infill = true;
constexpr size_t wall_line_count = 0;
const Point& infill_origin = Point();
Polygons* perimeter_gaps = nullptr;
constexpr bool connected_zigzags = false;
constexpr bool connect_polygons = false; // midway connections between polygons can make the surface less smooth
constexpr bool use_endpieces = true;
constexpr bool skip_some_zags = false;
constexpr size_t zag_skip_count = 0;
constexpr coord_t pocket_size = 0;
Infill infill_comp(
EFillMethod::ZIG_ZAG, zig_zaggify_infill, connect_polygons, raft_outline_path, offset_from_poly_outline, infill_outline_width, train.settings.get<coord_t>("raft_surface_line_spacing"),
fill_overlap, infill_multiplier, fill_angle, z, extra_infill_shift,
max_resolution, max_deviation,
wall_line_count, infill_origin, perimeter_gaps, connected_zigzags, use_endpieces, skip_some_zags, zag_skip_count, pocket_size
);
infill_comp.generate(raft_polygons, raft_lines);
gcode_layer.addLinesByOptimizer(raft_lines, gcode_layer.configs_storage.raft_surface_config, SpaceFillType::Lines, false, 0, 1.0, last_planned_position);
layer_plan_buffer.handle(gcode_layer, gcode);
}
}
LayerPlan& FffGcodeWriter::processLayer(const SliceDataStorage& storage, LayerIndex layer_nr, const size_t total_layers) const
{
logDebug("GcodeWriter processing layer %i of %i\n", layer_nr, total_layers);
const Settings& mesh_group_settings = Application::getInstance().current_slice->scene.current_mesh_group->settings;
coord_t layer_thickness = mesh_group_settings.get<coord_t>("layer_height");
coord_t z;
bool include_helper_parts = true;
if (layer_nr < 0)
{
#ifdef DEBUG
assert(mesh_group_settings.get<EPlatformAdhesion>("adhesion_type") == EPlatformAdhesion::RAFT && "negative layer_number means post-raft, pre-model layer!");
#endif // DEBUG
const int filler_layer_count = Raft::getFillerLayerCount();
layer_thickness = Raft::getFillerLayerHeight();
z = Raft::getTotalThickness() + (filler_layer_count + layer_nr + 1) * layer_thickness;
}
else
{
z = storage.meshes[0].layers[layer_nr].printZ; // stub default
// find printZ of first actual printed mesh
for (const SliceMeshStorage& mesh : storage.meshes)
{
if (layer_nr >= static_cast<int>(mesh.layers.size())
|| mesh.settings.get<bool>("support_mesh")
|| mesh.settings.get<bool>("anti_overhang_mesh")
|| mesh.settings.get<bool>("cutting_mesh")
|| mesh.settings.get<bool>("infill_mesh"))
{
continue;
}
z = mesh.layers[layer_nr].printZ;
layer_thickness = mesh.layers[layer_nr].thickness;
break;
}
if (layer_nr == 0)
{
if (mesh_group_settings.get<EPlatformAdhesion>("adhesion_type") == EPlatformAdhesion::RAFT)
{
include_helper_parts = false;
}
}
}
const Scene& scene = Application::getInstance().current_slice->scene;
#pragma omp critical
Application::getInstance().communication->sendLayerComplete(layer_nr, z, layer_thickness);
coord_t avoid_distance = 0; // minimal avoid distance is zero
const std::vector<bool> extruder_is_used = storage.getExtrudersUsed();
for (size_t extruder_nr = 0; extruder_nr < scene.extruders.size(); extruder_nr++)
{
if (extruder_is_used[extruder_nr])
{
const ExtruderTrain& extruder = scene.extruders[extruder_nr];
if (extruder.settings.get<bool>("travel_avoid_other_parts"))
{
avoid_distance = std::max(avoid_distance, extruder.settings.get<coord_t>("travel_avoid_distance"));
}
}
}
coord_t max_inner_wall_width = 0;
for (const SliceMeshStorage& mesh : storage.meshes)
{
max_inner_wall_width = std::max(max_inner_wall_width, mesh.settings.get<coord_t>((mesh.settings.get<size_t>("wall_line_count") > 1) ? "wall_line_width_x" : "wall_line_width_0"));
if (layer_nr == 0)
{
const ExtruderTrain& train = mesh.settings.get<ExtruderTrain&>((mesh.settings.get<size_t>("wall_line_count") > 1) ? "wall_0_extruder_nr" : "wall_x_extruder_nr");
max_inner_wall_width *= train.settings.get<Ratio>("initial_layer_line_width_factor");
}
}
const coord_t comb_offset_from_outlines = max_inner_wall_width * 2;
assert(static_cast<LayerIndex>(extruder_order_per_layer_negative_layers.size()) + layer_nr >= 0 && "Layer numbers shouldn't get more negative than there are raft/filler layers");
const std::vector<size_t>& extruder_order =
(layer_nr < 0) ?
extruder_order_per_layer_negative_layers[extruder_order_per_layer_negative_layers.size() + layer_nr]
:
extruder_order_per_layer[layer_nr];
const coord_t first_outer_wall_line_width = scene.extruders[extruder_order.front()].settings.get<coord_t>("wall_line_width_0");
LayerPlan& gcode_layer = *new LayerPlan(storage, layer_nr, z, layer_thickness, extruder_order.front(), fan_speed_layer_time_settings_per_extruder, comb_offset_from_outlines, first_outer_wall_line_width, avoid_distance);
if (include_helper_parts && layer_nr == 0)
{ // process the skirt or the brim of the starting extruder.
int extruder_nr = gcode_layer.getExtruder();
if (storage.skirt_brim[extruder_nr].size() > 0)
{
processSkirtBrim(storage, gcode_layer, extruder_nr);
}
}
if (include_helper_parts)
{ // handle shield(s) first in a layer so that chances are higher that the other nozzle is wiped (for the ooze shield)
processOozeShield(storage, gcode_layer);
processDraftShield(storage, gcode_layer);
}
const size_t support_roof_extruder_nr = mesh_group_settings.get<ExtruderTrain&>("support_roof_extruder_nr").extruder_nr;
const size_t support_bottom_extruder_nr = mesh_group_settings.get<ExtruderTrain&>("support_bottom_extruder_nr").extruder_nr;
const size_t support_infill_extruder_nr = (layer_nr <= 0) ? mesh_group_settings.get<ExtruderTrain&>("support_extruder_nr_layer_0").extruder_nr : mesh_group_settings.get<ExtruderTrain&>("support_infill_extruder_nr").extruder_nr;
bool disable_path_optimisation = false;
for (const size_t& extruder_nr : extruder_order)
{
if (include_helper_parts
&& (extruder_nr == support_infill_extruder_nr || extruder_nr == support_roof_extruder_nr || extruder_nr == support_bottom_extruder_nr))
{
addSupportToGCode(storage, gcode_layer, extruder_nr);
}
if (layer_nr >= 0)
{
const std::vector<size_t>& mesh_order = mesh_order_per_extruder[extruder_nr];
for (size_t mesh_idx : mesh_order)
{
const SliceMeshStorage& mesh = storage.meshes[mesh_idx];
const PathConfigStorage::MeshPathConfigs& mesh_config = gcode_layer.configs_storage.mesh_configs[mesh_idx];
if (mesh.settings.get<ESurfaceMode>("magic_mesh_surface_mode") == ESurfaceMode::SURFACE)
{
assert(extruder_nr == mesh.settings.get<ExtruderTrain&>("wall_0_extruder_nr").extruder_nr && "mesh surface mode should always only be printed with the outer wall extruder!");
addMeshLayerToGCode_meshSurfaceMode(storage, mesh, mesh_config, gcode_layer);
}
else
{
addMeshLayerToGCode(storage, mesh, extruder_nr, mesh_config, gcode_layer);
}
}
}
// ensure we print the prime tower with this extruder, because the next layer begins with this extruder!
// If this is not performed, the next layer might get two extruder switches...
setExtruder_addPrime(storage, gcode_layer, extruder_nr);
}
if (include_helper_parts)
{ // add prime tower if it hasn't already been added
const size_t prev_extruder = gcode_layer.getExtruder(); // most likely the same extruder as we are extruding with now
if (gcode_layer.getLayerNr() != 0 || storage.primeTower.extruder_order[0] == prev_extruder)
{
addPrimeTower(storage, gcode_layer, prev_extruder);
}
}
if (!disable_path_optimisation)
{
gcode_layer.optimizePaths(gcode.getPositionXY());
}
return gcode_layer;
}
bool FffGcodeWriter::getExtruderNeedPrimeBlobDuringFirstLayer(const SliceDataStorage& storage, const size_t extruder_nr) const
{
bool need_prime_blob = false;