-
-
Notifications
You must be signed in to change notification settings - Fork 889
/
support.cpp
1786 lines (1612 loc) · 91.6 KB
/
support.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) 2023 UltiMaker
// CuraEngine is released under the terms of the AGPLv3 or higher
#include "support.h"
#include <cmath> // sqrt, round
#include <deque>
#include <fstream> // ifstream.good()
#include <utility> // pair
#include <range/v3/view/concat.hpp>
#include <range/v3/view/drop.hpp>
#include <range/v3/view/drop_last.hpp>
#include <range/v3/view/enumerate.hpp>
#include <range/v3/view/filter.hpp>
#include <range/v3/view/slice.hpp>
#include <range/v3/view/sliding.hpp>
#include <range/v3/view/take.hpp>
#include <range/v3/view/zip.hpp>
#include <scripta/logger.h>
#include <spdlog/spdlog.h>
#include "Application.h" //To get settings.
#include "ExtruderTrain.h"
#include "SkeletalTrapezoidation.h"
#include "Slice.h"
#include "infill.h"
#include "infill/SierpinskiFillProvider.h"
#include "infill/UniformDensityProvider.h"
#include "progress/Progress.h"
#include "settings/EnumSettings.h" //For EFillMethod.
#include "settings/types/Angle.h" //To compute overhang distance from the angle.
#include "settings/types/Ratio.h"
#include "sliceDataStorage.h"
#include "slicer.h"
#include "utils/Simplify.h"
#include "utils/ThreadPool.h"
#include "utils/linearAlg2D.h"
#include "utils/math.h"
namespace cura
{
bool AreaSupport::handleSupportModifierMesh(SliceDataStorage& storage, const Settings& mesh_settings, const Slicer* slicer)
{
if (! mesh_settings.get<bool>("anti_overhang_mesh") && ! mesh_settings.get<bool>("support_mesh"))
{
return false;
}
enum ModifierType
{
ANTI_OVERHANG,
SUPPORT_DROP_DOWN,
SUPPORT_VANILLA
};
ModifierType modifier_type
= (mesh_settings.get<bool>("anti_overhang_mesh")) ? ANTI_OVERHANG : ((mesh_settings.get<bool>("support_mesh_drop_down")) ? SUPPORT_DROP_DOWN : SUPPORT_VANILLA);
for (LayerIndex layer_nr = 0; layer_nr < slicer->layers.size(); layer_nr++)
{
SupportLayer& support_layer = storage.support.supportLayers[layer_nr];
const SlicerLayer& slicer_layer = slicer->layers[layer_nr];
switch (modifier_type)
{
case ANTI_OVERHANG:
support_layer.anti_overhang.push_back(slicer_layer.polygons_);
break;
case SUPPORT_DROP_DOWN:
support_layer.support_mesh_drop_down.push_back(slicer_layer.polygons_);
break;
case SUPPORT_VANILLA:
support_layer.support_mesh.push_back(slicer_layer.polygons_);
break;
}
}
return true;
}
void AreaSupport::splitGlobalSupportAreasIntoSupportInfillParts(SliceDataStorage& storage, const std::vector<Shape>& global_support_areas_per_layer, unsigned int total_layer_count)
{
if (total_layer_count == 0)
{
return;
}
size_t min_layer = 0;
size_t max_layer = total_layer_count - 1;
const Settings& mesh_group_settings = Application::getInstance().current_slice_->scene.current_mesh_group->settings;
const ExtruderTrain& infill_extruder = mesh_group_settings.get<ExtruderTrain&>("support_infill_extruder_nr");
const EFillMethod support_pattern = infill_extruder.settings_.get<EFillMethod>("support_pattern");
const coord_t support_line_width = infill_extruder.settings_.get<coord_t>("support_line_width");
// The wall line count is used for calculating insets, and we generate support infill patterns within the insets
const size_t wall_line_count = infill_extruder.settings_.get<size_t>("support_wall_count");
// Generate separate support islands
for (LayerIndex layer_nr = 0; layer_nr < total_layer_count - 1; ++layer_nr)
{
unsigned int wall_line_count_this_layer = wall_line_count;
if (layer_nr == 0 && (support_pattern == EFillMethod::LINES || support_pattern == EFillMethod::ZIG_ZAG))
{ // The first layer will be printed with a grid pattern
wall_line_count_this_layer++;
}
const Shape& global_support_areas = global_support_areas_per_layer[layer_nr];
if (global_support_areas.size() == 0 || layer_nr < min_layer || layer_nr > max_layer)
{
// Initialize support_infill_parts empty
storage.support.supportLayers[layer_nr].support_infill_parts.clear();
continue;
}
coord_t support_line_width_here = support_line_width;
if (layer_nr == 0 && mesh_group_settings.get<EPlatformAdhesion>("adhesion_type") != EPlatformAdhesion::RAFT)
{
support_line_width_here *= infill_extruder.settings_.get<Ratio>("initial_layer_line_width_factor");
}
// We don't generate insets and infill area for the parts yet because later the skirt/brim and prime
// tower will remove themselves from the support, so the outlines of the parts can be changed.
const coord_t layer_height = infill_extruder.settings_.get<coord_t>("layer_height");
storage.support.supportLayers[layer_nr]
.fillInfillParts(layer_nr, global_support_areas_per_layer, layer_height, storage.meshes, support_line_width_here, wall_line_count_this_layer);
}
}
void AreaSupport::generateSupportInfillFeatures(SliceDataStorage& storage)
{
AreaSupport::generateGradualSupport(storage);
// combine support infill layers
AreaSupport::combineSupportInfillLayers(storage);
AreaSupport::cleanup(storage);
}
void AreaSupport::generateGradualSupport(SliceDataStorage& storage)
{
//
// # How gradual support infill works:
// The gradual support infill uses the same technic as the gradual infill. Here is an illustration
//
// A part of the model |
// V
// __________________________ <-- each gradual support infill step consists of 2 actual layers
// / | | <-- each step halfs the infill density.
// /_ _ _ _ _ _ _ _ _ _ _ _ _ _ _| <-- infill density 1 x 1/(2^2) (25%)
// / | | | <-- infill density 1 x 1/(2^1) (50%)
// /_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _|
// / | | | | <-- infill density 1 x 1/(2^0) (100%)
// /_____________________________________| <-- the current layer, the layer which we are processing right now.
//
// | 0 | 1 | 2 | 3 | <-- areas with different densities, where densities (or sparsities) are represented by numbers 0, 1, 2, etc.
// more dense ----> less dense
//
// In the example above, it shows a part of the model. For the gradual support infill, we process each layer in the following way:
// -> Separate the generated support areas into isolated islands, and we process those islands one by one
// so that the outlines and the gradual infill areas of an island can be printed together.
//
// -> Calculate a number of layer groups, each group consists of layers that will be infilled with the same density.
// The maximum number of densities is defined by the parameter "max graudual support steps". For example, it the <max steps> is 5,
// It means that we can have at most 5 different densities, each is 1/2 than the other. So, it will looks like below:
// | step | density | description |
// | 0 | 1 / (2^0) | 100% the most dense |
// | 1 | 1 / (2^1) | |
// | 2 | 1 / (2^2) | |
// | 3 | 1 / (2^3) | |
// | 4 | 1 / (2^4) | |
// | 5 | 1 / (2^5) | 3.125% the least dense |
//
// -> With those layer groups, we can project areas with different densities onto the layer we are currently processing.
// Like in the illustration above, you can see 4 different areas with densities ranging from 0 to 3.
//
// -> We save those areas with different densities into the SupportInfillPart data structure, which holds all the support infill
// information of a support island on a specific layer.
//
// -> Note that this function only does the above, which is identifying and storing support infill areas with densities.
// The actual printing part is done in FffGcodeWriter.
//
const Settings& mesh_group_settings = Application::getInstance().current_slice_->scene.current_mesh_group->settings;
const size_t total_layer_count = storage.print_layer_count;
const ExtruderTrain& infill_extruder = mesh_group_settings.get<ExtruderTrain&>("support_infill_extruder_nr");
const coord_t gradual_support_step_height = infill_extruder.settings_.get<coord_t>("gradual_support_infill_step_height");
const size_t max_density_steps = infill_extruder.settings_.get<size_t>("gradual_support_infill_steps");
const coord_t wall_width = infill_extruder.settings_.get<coord_t>("support_line_width");
const bool is_connected = infill_extruder.settings_.get<bool>("zig_zaggify_infill") || infill_extruder.settings_.get<EFillMethod>("infill_pattern") == EFillMethod::ZIG_ZAG;
const Simplify simplifier(infill_extruder.settings_);
// no early-out for this function; it needs to initialize the [infill_area_per_combine_per_density]
double layer_skip_count{ 8.0 }; // skip every so many layers as to ignore small gaps in the model making computation more easy
size_t gradual_support_step_layer_count
= round_divide(gradual_support_step_height, mesh_group_settings.get<coord_t>("layer_height")); // The difference in layer count between consecutive density infill areas.
// make gradual_support_step_height divisable by layer_skip_count
const auto n_skip_steps_per_gradual_step
= std::max(1.0, std::ceil(gradual_support_step_layer_count / layer_skip_count)); // only decrease layer_skip_count to make it a divisor of gradual_support_step_layer_count
layer_skip_count = gradual_support_step_layer_count / n_skip_steps_per_gradual_step;
LayerIndex min_layer = 0;
LayerIndex max_layer = total_layer_count - 1;
// compute different density areas for each support island
for (LayerIndex layer_nr = 0; layer_nr < total_layer_count - 1; layer_nr++)
{
if (layer_nr < min_layer || layer_nr > max_layer)
{
continue;
}
// generate separate support islands and calculate density areas for each island
std::vector<SupportInfillPart>& support_infill_parts = storage.support.supportLayers[layer_nr].support_infill_parts;
for (unsigned int part_idx = 0; part_idx < support_infill_parts.size(); ++part_idx)
{
SupportInfillPart& support_infill_part = support_infill_parts[part_idx];
Shape original_area = support_infill_part.getInfillArea();
if (original_area.empty())
{
continue;
}
// NOTE: This both generates the walls _and_ returns the _actual_ infill area (the one _without_ walls) for use in the rest of the method.
const Shape infill_area = Infill::generateWallToolPaths(
support_infill_part.wall_toolpaths_,
original_area,
support_infill_part.inset_count_to_generate_,
wall_width,
infill_extruder.settings_,
layer_nr,
SectionType::SUPPORT);
const AABB& this_part_boundary_box = support_infill_part.outline_boundary_box_;
// calculate density areas for this island
Shape less_dense_support = infill_area; // one step less dense with each density_step
Shape sum_more_dense; // NOTE: Only used for zig-zag or connected fills.
for (unsigned int density_step = 0; density_step < max_density_steps; ++density_step)
{
LayerIndex actual_min_layer{ layer_nr + density_step * gradual_support_step_layer_count + static_cast<LayerIndex::value_type>(layer_skip_count) };
LayerIndex actual_max_layer{ layer_nr + (density_step + 1) * gradual_support_step_layer_count };
for (double upper_layer_idx = actual_min_layer; upper_layer_idx <= actual_max_layer; upper_layer_idx += layer_skip_count)
{
if (static_cast<unsigned int>(upper_layer_idx) >= total_layer_count)
{
less_dense_support.clear();
break;
}
// compute intersections with relevant upper parts
const std::vector<SupportInfillPart> upper_infill_parts = storage.support.supportLayers[upper_layer_idx].support_infill_parts;
Shape relevant_upper_polygons;
for (unsigned int upper_part_idx = 0; upper_part_idx < upper_infill_parts.size(); ++upper_part_idx)
{
if (support_infill_part.outline_.empty())
{
continue;
}
// we compute intersection based on support infill areas
const AABB& upper_part_boundary_box = upper_infill_parts[upper_part_idx].outline_boundary_box_;
//
// Here we are comparing the **outlines** of the infill areas
//
// legend:
// ^ support roof
// | support wall
// # dense support
// + less dense support
//
// comparing infill comparing with outline (this is our approach)
// ^^^^^^ ^^^^^^ ^^^^^^ ^^^^^^
// ####||^^ ####||^^ ####||^^ ####||^^
// ######||^^ #####||^^ ######||^^ #####||^^
// ++++####|| ++++##||^ ++++++##|| ++++++||^
// ++++++#### +++++##|| ++++++++## +++++++||
//
if (upper_part_boundary_box.hit(this_part_boundary_box))
{
relevant_upper_polygons.push_back(upper_infill_parts[upper_part_idx].outline_);
}
}
less_dense_support = less_dense_support.intersection(relevant_upper_polygons);
}
if (less_dense_support.size() == 0)
{
break;
}
// add new infill_area_per_combine_per_density for the current density
support_infill_part.infill_area_per_combine_per_density_.emplace_back();
std::vector<Shape>& support_area_current_density = support_infill_part.infill_area_per_combine_per_density_.back();
const Shape more_dense_support = infill_area.difference(less_dense_support);
support_area_current_density.push_back(simplifier.polygon(more_dense_support.difference(sum_more_dense)));
if (is_connected)
{
sum_more_dense = sum_more_dense.unionPolygons(more_dense_support);
}
}
support_infill_part.infill_area_per_combine_per_density_.emplace_back();
std::vector<Shape>& support_area_current_density = support_infill_part.infill_area_per_combine_per_density_.back();
support_area_current_density.push_back(simplifier.polygon(infill_area.difference(sum_more_dense)));
assert(support_infill_part.infill_area_per_combine_per_density_.size() != 0 && "support_infill_part.infill_area_per_combine_per_density should now be initialized");
#ifdef DEBUG
for (unsigned int part_i = 0; part_i < support_infill_part.infill_area_per_combine_per_density_.size(); ++part_i)
{
assert(support_infill_part.infill_area_per_combine_per_density_[part_i].size() != 0);
}
#endif // DEBUG
}
}
}
void AreaSupport::combineSupportInfillLayers(SliceDataStorage& storage)
{
const Settings& mesh_group_settings = Application::getInstance().current_slice_->scene.current_mesh_group->settings;
const unsigned int total_layer_count = storage.print_layer_count;
const coord_t layer_height = mesh_group_settings.get<coord_t>("layer_height");
// How many support infill layers to combine to obtain the requested sparse thickness.
const ExtruderTrain& infill_extruder = mesh_group_settings.get<ExtruderTrain&>("support_infill_extruder_nr");
const size_t combine_layers_amount
= std::max(uint64_t(1), round_divide(infill_extruder.settings_.get<coord_t>("support_infill_sparse_thickness"), std::max(layer_height, coord_t(1))));
if (combine_layers_amount <= 1)
{
return;
}
/* We need to round down the layer index we start at to the nearest
divisible index. Otherwise we get some parts that have infill at divisible
layers and some at non-divisible layers. Those layers would then miss each
other. */
size_t min_layer = combine_layers_amount - 1;
min_layer -= min_layer % combine_layers_amount; // Round upwards to the nearest layer divisible by infill_sparse_combine.
size_t max_layer = total_layer_count < storage.support.supportLayers.size() ? total_layer_count : storage.support.supportLayers.size();
max_layer = max_layer - 1;
max_layer -= max_layer % combine_layers_amount; // Round downwards to the nearest layer divisible by infill_sparse_combine.
for (size_t layer_idx = min_layer; layer_idx <= max_layer; layer_idx += combine_layers_amount) // Skip every few layers, but extrude more.
{
if (layer_idx >= storage.support.supportLayers.size())
{
break;
}
SupportLayer& layer = storage.support.supportLayers[layer_idx];
for (unsigned int combine_count_here = 1; combine_count_here < combine_layers_amount; ++combine_count_here)
{
if (layer_idx < combine_count_here)
{
break;
}
size_t lower_layer_idx = layer_idx - combine_count_here;
if (lower_layer_idx < min_layer)
{
break;
}
SupportLayer& lower_layer = storage.support.supportLayers[lower_layer_idx];
for (SupportInfillPart& part : layer.support_infill_parts)
{
if (part.getInfillArea().empty())
{
continue;
}
for (unsigned int density_idx = 0; density_idx < part.infill_area_per_combine_per_density_.size(); ++density_idx)
{ // go over each density of gradual infill (these density areas overlap!)
std::vector<Shape>& infill_area_per_combine = part.infill_area_per_combine_per_density_[density_idx];
Shape result;
for (SupportInfillPart& lower_layer_part : lower_layer.support_infill_parts)
{
if (! part.outline_boundary_box_.hit(lower_layer_part.outline_boundary_box_))
{
continue;
}
Shape intersection = infill_area_per_combine[combine_count_here - 1].intersection(lower_layer_part.getInfillArea()).offset(-200).offset(200);
if (intersection.size() <= 0)
{
continue;
}
result.push_back(intersection); // add area to be thickened
infill_area_per_combine[combine_count_here - 1]
= infill_area_per_combine[combine_count_here - 1].difference(intersection); // remove thickened area from less thick layer here
unsigned int max_lower_density_idx = density_idx;
// Generally: remove only from *same density* areas on layer below
// If there are no same density areas, then it's ok to print them anyway
// Don't remove other density areas
if (density_idx == part.infill_area_per_combine_per_density_.size() - 1)
{
// For the most dense areas on a given layer the density of that area is doubled.
// This means that - if the lower layer has more densities -
// all those lower density lines are included in the most dense of this layer.
// We therefore compare the most dense are on this layer with all densities
// of the lower layer with the same or higher density index
max_lower_density_idx = lower_layer_part.infill_area_per_combine_per_density_.size() - 1;
}
for (unsigned int lower_density_idx = density_idx;
lower_density_idx <= max_lower_density_idx && lower_density_idx < lower_layer_part.infill_area_per_combine_per_density_.size();
lower_density_idx++)
{
std::vector<Shape>& lower_infill_area_per_combine = lower_layer_part.infill_area_per_combine_per_density_[lower_density_idx];
lower_infill_area_per_combine[0]
= lower_infill_area_per_combine[0].difference(intersection); // remove thickened area from lower (single thickness) layer
}
}
infill_area_per_combine.push_back(result);
}
}
}
}
}
void AreaSupport::cleanup(SliceDataStorage& storage)
{
const coord_t support_line_width = Application::getInstance().current_slice_->scene.current_mesh_group->settings.get<coord_t>("support_line_width");
for (LayerIndex layer_nr = 0; layer_nr < storage.support.supportLayers.size(); layer_nr++)
{
SupportLayer& layer = storage.support.supportLayers[layer_nr];
for (unsigned int part_idx = 0; part_idx < layer.support_infill_parts.size(); part_idx++)
{
SupportInfillPart& part = layer.support_infill_parts[part_idx];
bool can_be_removed = true;
if (part.inset_count_to_generate_ > 0)
{
can_be_removed = false;
}
else
{
for (const std::vector<Shape>& infill_area_per_combine_this_density : part.infill_area_per_combine_per_density_)
{
for (const Shape& infill_area_this_combine_this_density : infill_area_per_combine_this_density)
{
// remove small areas which were introduced by rounding errors in comparing the same area on two consecutive layer
if (! infill_area_this_combine_this_density.empty() && infill_area_this_combine_this_density.area() > support_line_width * support_line_width)
{
can_be_removed = false;
break;
}
}
if (! can_be_removed)
{ // break outer loop
break;
}
}
}
if (can_be_removed)
{
part = std::move(layer.support_infill_parts.back());
layer.support_infill_parts.pop_back();
part_idx--;
}
}
}
}
Shape AreaSupport::join(const SliceDataStorage& storage, const Shape& supportLayer_up, Shape& supportLayer_this)
{
Shape joined;
const Settings& infill_settings = Application::getInstance().current_slice_->scene.current_mesh_group->settings.get<ExtruderTrain&>("support_infill_extruder_nr").settings_;
const AngleRadians conical_support_angle = infill_settings.get<AngleRadians>("support_conical_angle");
const coord_t layer_thickness = infill_settings.get<coord_t>("layer_height");
coord_t conical_support_offset;
if (conical_support_angle > 0)
{ // outward ==> wider base than overhang
conical_support_offset = -boundedTan(conical_support_angle) * layer_thickness;
}
else
{ // inward ==> smaller base than overhang
conical_support_offset = boundedTan(-conical_support_angle) * layer_thickness;
}
const bool conical_support = infill_settings.get<bool>("support_conical_enabled") && conical_support_angle != 0;
if (conical_support)
{
const Settings& mesh_group_settings = Application::getInstance().current_slice_->scene.current_mesh_group->settings;
// Don't go outside the build volume.
Shape machine_volume_border;
switch (mesh_group_settings.get<BuildPlateShape>("machine_shape"))
{
case BuildPlateShape::ELLIPTIC:
{
// Construct an ellipse to approximate the build volume.
const coord_t width = storage.machine_size.max_.x_ - storage.machine_size.min_.x_;
const coord_t depth = storage.machine_size.max_.y_ - storage.machine_size.min_.y_;
Polygon border_circle;
constexpr unsigned int circle_resolution = 50;
for (unsigned int i = 0; i < circle_resolution; i++)
{
const AngleRadians angle = TAU * i / circle_resolution;
const Point3LL machine_middle = storage.machine_size.getMiddle();
const coord_t x = machine_middle.x_ + cos(angle) * width / 2;
const coord_t y = machine_middle.y_ + sin(angle) * depth / 2;
border_circle.emplace_back(x, y);
}
machine_volume_border.push_back(border_circle);
break;
}
case BuildPlateShape::RECTANGULAR:
default:
machine_volume_border.push_back(storage.machine_size.flatten().toPolygon());
break;
}
coord_t adhesion_size = 0; // Make sure there is enough room for the platform adhesion around support.
coord_t extra_skirt_line_width = 0;
const std::vector<bool> is_extruder_used = storage.getExtrudersUsed();
for (size_t extruder_nr = 0; extruder_nr < Application::getInstance().current_slice_->scene.extruders.size(); extruder_nr++)
{
if (! is_extruder_used[extruder_nr]) // Unused extruders and the primary adhesion extruder don't generate an extra skirt line.
{
continue;
}
const ExtruderTrain& other_extruder = Application::getInstance().current_slice_->scene.extruders[extruder_nr];
extra_skirt_line_width += other_extruder.settings_.get<coord_t>("skirt_brim_line_width") * other_extruder.settings_.get<Ratio>("initial_layer_line_width_factor");
}
const std::vector<ExtruderTrain*> skirt_brim_extruders = mesh_group_settings.get<std::vector<ExtruderTrain*>>("skirt_brim_extruder_nr");
auto adhesion_width_str{ "brim_width" };
auto adhesion_line_count_str{ "brim_line_count" };
switch (mesh_group_settings.get<EPlatformAdhesion>("adhesion_type"))
{
case EPlatformAdhesion::SKIRT:
adhesion_width_str = "skirt_gap";
adhesion_line_count_str = "skirt_line_count";
[[fallthrough]];
case EPlatformAdhesion::BRIM:
for (ExtruderTrain* skirt_brim_extruder_p : skirt_brim_extruders)
{
ExtruderTrain& skirt_brim_extruder = *skirt_brim_extruder_p;
adhesion_size = std::max(
adhesion_size,
coord_t(
skirt_brim_extruder.settings_.get<coord_t>(adhesion_width_str)
+ skirt_brim_extruder.settings_.get<coord_t>("skirt_brim_line_width")
* (skirt_brim_extruder.settings_.get<size_t>(adhesion_line_count_str) - 1) // - 1 because the line is also included in extra_skirt_line_width
* skirt_brim_extruder.settings_.get<Ratio>("initial_layer_line_width_factor")
+ extra_skirt_line_width));
}
break;
case EPlatformAdhesion::RAFT:
{
adhesion_size = std::max({ mesh_group_settings.get<ExtruderTrain&>("raft_base_extruder_nr").settings_.get<coord_t>("raft_base_margin"),
mesh_group_settings.get<ExtruderTrain&>("raft_interface_extruder_nr").settings_.get<coord_t>("raft_interface_margin"),
mesh_group_settings.get<ExtruderTrain&>("raft_surface_extruder_nr").settings_.get<coord_t>("raft_surface_margin") });
break;
}
case EPlatformAdhesion::NONE:
adhesion_size = 0;
break;
default: // Also use 0.
spdlog::info("Unknown platform adhesion type! Please implement the width of the platform adhesion here.");
break;
}
machine_volume_border = machine_volume_border.offset(-adhesion_size);
const coord_t conical_smallest_breadth = infill_settings.get<coord_t>("support_conical_min_width");
Shape insetted = supportLayer_up.offset(-conical_smallest_breadth / 2);
Shape small_parts = supportLayer_up.difference(insetted.offset(conical_smallest_breadth / 2 + 20));
joined = supportLayer_this.unionPolygons(supportLayer_up.offset(conical_support_offset)).unionPolygons(small_parts).intersection(machine_volume_border);
}
else
{
joined = supportLayer_this.unionPolygons(supportLayer_up);
}
// join different parts
const coord_t join_distance = infill_settings.get<coord_t>("support_join_distance");
if (join_distance > 0)
{
// first offset the layer a little inwards; this way tiny area's will not be joined
// (narrow areas; ergo small areas will be removed in a later step using this same offset)
// this inwards offset is later reversed by increasing the outwards offset
const coord_t min_even_wall_line_width = infill_settings.get<coord_t>("min_even_wall_line_width");
auto half_min_feature_width = min_even_wall_line_width + 10;
joined = joined.offset(-half_min_feature_width)
.offset(join_distance + half_min_feature_width, ClipperLib::jtRound)
.offset(-join_distance, ClipperLib::jtRound)
.unionPolygons(joined);
}
const Simplify simplify(infill_settings);
joined = simplify.polygon(joined);
return joined;
}
void AreaSupport::generateOverhangAreas(SliceDataStorage& storage)
{
for (std::shared_ptr<SliceMeshStorage>& mesh_ptr : storage.meshes)
{
auto& mesh = *mesh_ptr;
if (mesh.settings.get<bool>("infill_mesh") || mesh.settings.get<bool>("anti_overhang_mesh"))
{
continue;
}
// it actually also initializes some buffers that are needed in generateSupport
generateOverhangAreasForMesh(storage, mesh);
}
}
void AreaSupport::generateSupportAreas(SliceDataStorage& storage)
{
std::vector<Shape> global_support_areas_per_layer;
global_support_areas_per_layer.resize(storage.print_layer_count);
int max_layer_nr_support_mesh_filled;
for (max_layer_nr_support_mesh_filled = storage.support.supportLayers.size() - 1; max_layer_nr_support_mesh_filled >= 0; max_layer_nr_support_mesh_filled--)
{
const SupportLayer& support_layer = storage.support.supportLayers[max_layer_nr_support_mesh_filled];
if (support_layer.support_mesh.size() > 0 || support_layer.support_mesh_drop_down.size() > 0)
{
break;
}
}
storage.support.layer_nr_max_filled_layer = max_layer_nr_support_mesh_filled;
for (int layer_nr = 0; layer_nr < max_layer_nr_support_mesh_filled; layer_nr++)
{
SupportLayer& support_layer = storage.support.supportLayers[layer_nr];
support_layer.anti_overhang = support_layer.anti_overhang.unionPolygons();
support_layer.support_mesh_drop_down = support_layer.support_mesh_drop_down.unionPolygons();
support_layer.support_mesh = support_layer.support_mesh.unionPolygons();
}
// initialization of supportAreasPerLayer
if (storage.print_layer_count > storage.support.supportLayers.size())
{ // there might already be anti_overhang_area data or support mesh data in the supportLayers
storage.support.supportLayers.resize(storage.print_layer_count);
}
// generate support areas
bool support_meshes_drop_down_handled = false;
bool support_meshes_handled = false;
const Settings& mesh_group_settings = Application::getInstance().current_slice_->scene.current_mesh_group->settings;
for (unsigned int mesh_idx = 0; mesh_idx < storage.meshes.size(); mesh_idx++)
{
SliceMeshStorage& mesh = *storage.meshes[mesh_idx];
if (mesh.settings.get<bool>("infill_mesh") || mesh.settings.get<bool>("anti_overhang_mesh"))
{
continue;
}
Settings* infill_settings = &storage.meshes[mesh_idx]->settings;
Settings* roof_settings = &storage.meshes[mesh_idx]->settings;
Settings* bottom_settings = &storage.meshes[mesh_idx]->settings;
if (mesh.settings.get<bool>("support_mesh"))
{
if ((mesh.settings.get<bool>("support_mesh_drop_down") && support_meshes_drop_down_handled)
|| (! mesh.settings.get<bool>("support_mesh_drop_down") && support_meshes_handled))
{ // handle all support_mesh and support_mesh_drop_down areas only once
continue;
}
// use extruder train settings rather than the per-object settings of the first support mesh encountered.
// because all support meshes are processed at the same time it doesn't make sense to use the per-object settings of the first support mesh encountered.
// instead we must use the support extruder settings, which is the settings base common to all support meshes.
infill_settings = &mesh_group_settings.get<ExtruderTrain&>("support_infill_extruder_nr").settings_;
roof_settings = &mesh_group_settings.get<ExtruderTrain&>("support_roof_extruder_nr").settings_;
bottom_settings = &mesh_group_settings.get<ExtruderTrain&>("support_bottom_extruder_nr").settings_;
if (mesh.settings.get<bool>("support_mesh_drop_down"))
{
support_meshes_drop_down_handled = true;
}
else
{
support_meshes_handled = true;
}
}
std::vector<Shape> mesh_support_areas_per_layer;
mesh_support_areas_per_layer.resize(storage.print_layer_count, Shape());
generateSupportAreasForMesh(storage, *infill_settings, *roof_settings, *bottom_settings, mesh_idx, storage.print_layer_count, mesh_support_areas_per_layer);
for (size_t layer_idx = 0; layer_idx < storage.print_layer_count; layer_idx++)
{
global_support_areas_per_layer[layer_idx].push_back(mesh_support_areas_per_layer[layer_idx]);
}
}
for (Shape& support_areas : global_support_areas_per_layer)
{
support_areas = support_areas.unionPolygons();
}
// handle support interface
for (auto& mesh : storage.meshes)
{
if (mesh->settings.get<bool>("infill_mesh") || mesh->settings.get<bool>("anti_overhang_mesh"))
{
continue;
}
if (mesh->settings.get<bool>("support_roof_enable"))
{
generateSupportRoof(storage, *mesh, global_support_areas_per_layer);
}
if (mesh->settings.get<bool>("support_bottom_enable"))
{
generateSupportBottom(storage, *mesh, global_support_areas_per_layer);
}
}
// split the global support areas into parts for later gradual support infill generation
AreaSupport::splitGlobalSupportAreasIntoSupportInfillParts(storage, global_support_areas_per_layer, storage.print_layer_count);
precomputeCrossInfillTree(storage);
}
void AreaSupport::precomputeCrossInfillTree(SliceDataStorage& storage)
{
const Settings& mesh_group_settings = Application::getInstance().current_slice_->scene.current_mesh_group->settings;
const ExtruderTrain& infill_extruder = mesh_group_settings.get<ExtruderTrain&>("support_infill_extruder_nr");
const EFillMethod& support_pattern = infill_extruder.settings_.get<EFillMethod>("support_pattern");
if ((support_pattern == EFillMethod::CROSS || support_pattern == EFillMethod::CROSS_3D) && infill_extruder.settings_.get<coord_t>("support_line_distance") > 0)
{
AABB3D aabb;
for (unsigned int mesh_idx = 0; mesh_idx < storage.meshes.size(); mesh_idx++)
{
const SliceMeshStorage& mesh = *storage.meshes[mesh_idx];
if (mesh.settings.get<bool>("infill_mesh") || mesh.settings.get<bool>("anti_overhang_mesh"))
{
continue;
}
Settings& infill_settings = storage.meshes[mesh_idx]->settings;
if (mesh.settings.get<bool>("support_mesh"))
{
// use extruder train settings rather than the per-object settings of the first support mesh encountered.
// because all support meshes are processed at the same time it doesn't make sense to use the per-object settings of the first support mesh encountered.
// instead we must use the support extruder settings, which is the settings base common to all support meshes.
infill_settings = mesh_group_settings.get<ExtruderTrain&>("support_infill_extruder_nr").settings_;
}
const coord_t aabb_expansion = infill_settings.get<coord_t>("support_offset");
AABB3D aabb_here(mesh.bounding_box);
aabb_here.include(aabb_here.min_ - Point3LL(-aabb_expansion, -aabb_expansion, 0));
aabb_here.include(aabb_here.max_ + Point3LL(-aabb_expansion, -aabb_expansion, 0));
aabb.include(aabb_here);
}
std::string cross_subdisivion_spec_image_file = infill_extruder.settings_.get<std::string>("cross_support_density_image");
std::ifstream cross_fs(cross_subdisivion_spec_image_file.c_str());
if (cross_subdisivion_spec_image_file != "" && cross_fs.good())
{
storage.support.cross_fill_provider = std::make_shared<SierpinskiFillProvider>(
aabb,
infill_extruder.settings_.get<coord_t>("support_line_distance"),
infill_extruder.settings_.get<coord_t>("support_line_width"),
cross_subdisivion_spec_image_file);
}
else
{
if (cross_subdisivion_spec_image_file != "")
{
spdlog::error("Cannot find density image: {}.", cross_subdisivion_spec_image_file);
}
storage.support.cross_fill_provider = std::make_shared<SierpinskiFillProvider>(
aabb,
infill_extruder.settings_.get<coord_t>("support_line_distance"),
infill_extruder.settings_.get<coord_t>("support_line_width"));
}
}
}
void AreaSupport::generateOverhangAreasForMesh(SliceDataStorage& storage, SliceMeshStorage& mesh)
{
if (! mesh.settings.get<bool>("support_enable") && ! mesh.settings.get<bool>("support_mesh"))
{
return;
}
// Fill the overhang areas with emptiness first, even if it's a support mesh, so that we can request the areas.
mesh.full_overhang_areas.resize(storage.print_layer_count);
for (size_t layer_idx = 0; layer_idx < storage.print_layer_count; layer_idx++)
{
mesh.overhang_areas.emplace_back();
}
// Don't generate overhang areas for support meshes. They are dropped down automatically if desired.
const bool is_support_modifier = mesh.settings.get<bool>("support_mesh");
if (is_support_modifier)
{
return;
}
// Don't generate overhang areas if the Z distance is higher than the objects we're generating support for.
const coord_t layer_height = Application::getInstance().current_slice_->scene.current_mesh_group->settings.get<coord_t>("layer_height");
const coord_t z_distance_top = mesh.settings.get<coord_t>("support_top_distance");
const size_t z_distance_top_layers = (z_distance_top / layer_height) + 1;
if (z_distance_top_layers + 1 > storage.print_layer_count)
{
return;
}
// Generate points and lines of overhang (for corners pointing downwards, since they don't have an area to support but still need supporting).
const coord_t max_supported_diameter = mesh.settings.get<coord_t>("support_tower_maximum_supported_diameter");
const bool use_towers = mesh.settings.get<bool>("support_use_towers") && max_supported_diameter > 0;
if (use_towers)
{
AreaSupport::detectOverhangPoints(storage, mesh);
}
// Generate the actual areas and store them in the mesh.
cura::parallel_for<size_t>(
1,
storage.print_layer_count,
[&](const size_t layer_idx)
{
std::pair<Shape, Shape> basic_and_full_overhang = computeBasicAndFullOverhang(storage, mesh, layer_idx);
mesh.overhang_areas[layer_idx] = basic_and_full_overhang.first; // Store the results.
mesh.full_overhang_areas[layer_idx] = basic_and_full_overhang.second;
scripta::log("support_basic_overhang_area", basic_and_full_overhang.first, SectionType::SUPPORT, layer_idx);
scripta::log("support_full_overhang_area", basic_and_full_overhang.second, SectionType::SUPPORT, layer_idx);
});
}
Shape AreaSupport::generateVaryingXYDisallowedArea(const SliceMeshStorage& storage, const LayerIndex layer_idx)
{
const auto& mesh_group_settings = Application::getInstance().current_slice_->scene.current_mesh_group->settings;
const Simplify simplify{ mesh_group_settings };
const auto layer_thickness = mesh_group_settings.get<coord_t>("layer_height");
const auto support_distance_top = static_cast<double>(mesh_group_settings.get<coord_t>("support_top_distance"));
const auto support_distance_bot = static_cast<double>(mesh_group_settings.get<coord_t>("support_bottom_distance"));
const auto overhang_angle = mesh_group_settings.get<AngleRadians>("support_angle");
const auto xy_distance = static_cast<double>(mesh_group_settings.get<coord_t>("support_xy_distance"));
constexpr auto close_dist = 20;
Shape layer_current = simplify.polygon(storage.layers[layer_idx].getOutlines().offset(-close_dist).offset(close_dist));
using point_pair_t = std::pair<size_t, double>;
using poly_point_key = std::tuple<unsigned int, unsigned int>;
// We calculate the slope for each point at multiple layers. This is to average out local variations in the
// slope. We need at least two layers to calculate the slope; one above the current layer and one below.
// This is because the bottom layer uses _support_distance_bot_ and the top layer uses _support_distance_top_
// for the z-distance, and we want to take in both these values into account when creating the xy-distance poly.
struct z_delta_poly_t
{
double support_distance;
double delta_z;
Shape layer_delta;
};
std::vector<z_delta_poly_t> z_distances_layer_deltas;
// We only use two compare-layers for the slope calculation. A layer $layer_index_offset$ layers below and
// a layer $layer_index_offset$ layers above the current layer.
const size_t layer_index_offset = 1;
const LayerIndex layer_idx_below{ std::max(LayerIndex{ layer_idx - layer_index_offset }, LayerIndex{ 0 }) };
if (layer_idx_below != layer_idx)
{
const auto layer_below = simplify.polygon(storage.layers[layer_idx_below].getOutlines().offset(-close_dist).offset(close_dist));
z_distances_layer_deltas.emplace_back(z_delta_poly_t{
.support_distance = support_distance_bot,
.delta_z = -static_cast<double>(layer_index_offset * layer_thickness),
.layer_delta = layer_below,
});
}
const LayerIndex layer_idx_above{ std::min(LayerIndex{ layer_idx + layer_index_offset }, LayerIndex{ storage.layers.size() - 1 }) };
if (layer_idx_above != layer_idx)
{
const auto layer_above = simplify.polygon(storage.layers[layer_idx_above].getOutlines().offset(-close_dist).offset(close_dist));
z_distances_layer_deltas.emplace_back(z_delta_poly_t{
.support_distance = support_distance_top,
.delta_z = static_cast<double>(layer_index_offset * layer_thickness),
.layer_delta = layer_above,
});
}
// Initialize the offset_dist_at_point map with all the points in the current layer.
// This map is used to store the variation in X/Y distance at each point, per
// compare-layer. The distances calculated for each layer are averaged to get the
// final X/Y distance.
std::map<poly_point_key, point_pair_t> offset_dist_at_point;
for (auto [current_poly_idx, current_poly] : layer_current | ranges::views::enumerate)
{
for (auto [current_point_idx, current_point] : current_poly | ranges::views::enumerate)
{
offset_dist_at_point.insert({ { current_poly_idx, current_point_idx }, { 0, 0 } });
}
}
for (const auto& z_delta_poly : z_distances_layer_deltas)
{
const auto support_distance = z_delta_poly.support_distance;
const auto delta_z = z_delta_poly.delta_z;
const auto layer_delta = z_delta_poly.layer_delta;
const auto xy_distance_natural = support_distance * boundedTan(overhang_angle);
for (auto [current_poly_idx, current_poly] : layer_current | ranges::views::enumerate)
{
for (auto [current_point_idx, current_point] : current_poly | ranges::views::enumerate)
{
auto min_dist2 = std::numeric_limits<coord_t>::max();
Point2LL min_point;
for (auto delta_poly : layer_delta)
{
constexpr auto window_size = 2;
const auto view
= ranges::views::concat(delta_poly, (delta_poly | ranges::views::take(window_size - 1))) // wrap around to make sure all line segments are included
| ranges::views::sliding(window_size); // sliding window of size 2 to get start/end of line segment
for (auto window : view)
{
const auto delta_point = window[0];
const auto delta_point_next = window[1];
const auto dist2 = LinearAlg2D::getDist2FromLineSegment(delta_point, current_point, delta_point_next);
min_dist2 = std::min(min_dist2, dist2);
}
}
const auto min_dist = std::sqrt(min_dist2);
const auto slope = min_dist / delta_z;
const auto wall_angle = std::atan(std::abs(slope));
const auto ratio = std::max(0.0, std::min(1.0, wall_angle / overhang_angle));
const auto xy_distance_varying = std::lerp(xy_distance, xy_distance_natural, ratio);
const poly_point_key key = { current_poly_idx, current_point_idx };
const auto [n, commutative_offset] = offset_dist_at_point.at(key);
offset_dist_at_point.at(key) = { n + 1, commutative_offset + xy_distance_varying };
}
}
}
std::vector<coord_t> varying_offsets;
for (auto [current_poly_idx, current_poly] : layer_current | ranges::views::enumerate)
{
for (auto [current_point_idx, _current_point] : current_poly | ranges::views::enumerate)
{
const auto [n, commutative_offset] = offset_dist_at_point.at({ current_poly_idx, current_point_idx });
double offset_dist;
if (n == 0)
{
// if there are no offset dists generated for a vertex $p$ this must mean that vertex $p$ was not
// present in any of the delta areas. This can only happen if the areas for the current layer and
// the layer(s) below are perfectly aligned at vertex $p$; the walls at vertex $p$ are vertical.
// As the wall is vertical the xy_distance is taken at vertex $p$.
offset_dist = xy_distance;
}
else
{
// Take average of all dists generated for vertex $p$.
offset_dist = commutative_offset / static_cast<double>(n);
}
varying_offsets.push_back(static_cast<coord_t>(offset_dist));
}
}
const auto smooth_dist = xy_distance / 2.0;
Shape varying_xy_disallowed_areas = layer_current
// offset using the varying offset distances we calculated previously
.offsetMulti(varying_offsets)
// close operation to smooth the x/y disallowed area boundary. With varying xy distances we see some jumps in the boundary.
// As the x/y disallowed areas "cut in" to support the xy-disallowed area may propagate through the support area. If the
// x/y disallowed area is not smoothed boost has trouble generating a voronoi diagram.
.offset(smooth_dist)
.offset(-smooth_dist);
scripta::log("support_varying_xy_disallowed_areas", varying_xy_disallowed_areas, SectionType::SUPPORT, layer_idx);
return varying_xy_disallowed_areas;
}
/*
* Algorithm:
* From top layer to bottom layer:
* - find overhang by looking at the difference between two consecutive layers
* - join with support areas from layer above
* - subtract current layer
* - use the result for the next lower support layer (without doing XY-distance and Z bottom distance, so that a single support beam may move around the model a bit => more
* stability)
* - perform inset using X/Y-distance and bottom Z distance
*
* for support buildplate only: purge all support not connected to build plate
*/
void AreaSupport::generateSupportAreasForMesh(
SliceDataStorage& storage,
const Settings& infill_settings,
const Settings& roof_settings,
const Settings& bottom_settings,
const size_t mesh_idx,
const size_t layer_count,
std::vector<Shape>& support_areas)
{
SliceMeshStorage& mesh = *storage.meshes[mesh_idx];
const ESupportStructure support_structure = mesh.settings.get<ESupportStructure>("support_structure");
const bool is_support_mesh_place_holder
= mesh.settings.get<bool>("support_mesh"); // whether this mesh has empty SliceMeshStorage and this function is now called to only generate support for all support meshes
if ((! mesh.settings.get<bool>("support_enable") || support_structure != ESupportStructure::NORMAL) && ! is_support_mesh_place_holder)
{
return;
}
const Settings& mesh_group_settings = Application::getInstance().current_slice_->scene.current_mesh_group->settings;