1 //Copyright (c) 2018 Ultimaker B.V.
2 //CuraEngine is released under the terms of the AGPLv3 or higher.
3 
4 #include "ExtruderTrain.h"
5 #include "sliceDataStorage.h"
6 #include "WallsComputation.h"
7 #include "settings/types/Ratio.h"
8 #include "settings/EnumSettings.h"
9 #include "utils/polygonUtils.h"
10 
11 namespace cura {
12 
WallsComputation(const Settings & settings,const LayerIndex layer_nr)13 WallsComputation::WallsComputation(const Settings& settings, const LayerIndex layer_nr)
14 : settings(settings)
15 , layer_nr(layer_nr)
16 {
17 }
18 
19 /*
20  * This function is executed in a parallel region based on layer_nr.
21  * When modifying make sure any changes does not introduce data races.
22  *
23  * generateInsets only reads and writes data for the current layer
24  */
generateInsets(SliceLayerPart * part)25 void WallsComputation::generateInsets(SliceLayerPart* part)
26 {
27     size_t inset_count = settings.get<size_t>("wall_line_count");
28     const bool spiralize = settings.get<bool>("magic_spiralize");
29     if (spiralize && layer_nr < LayerIndex(settings.get<size_t>("initial_bottom_layers")) && ((layer_nr % 2) + 2) % 2 == 1) //Add extra insets every 2 layers when spiralizing. This makes bottoms of cups watertight.
30     {
31         inset_count += 5;
32     }
33     if (settings.get<bool>("alternate_extra_perimeter"))
34     {
35         inset_count += ((layer_nr % 2) + 2) % 2;
36     }
37 
38     if (inset_count == 0)
39     {
40         part->insets.push_back(part->outline);
41         part->print_outline = part->outline;
42         return;
43     }
44 
45     const coord_t wall_0_inset = settings.get<coord_t>("wall_0_inset");
46     coord_t line_width_0 = settings.get<coord_t>("wall_line_width_0");
47     coord_t line_width_x = settings.get<coord_t>("wall_line_width_x");
48     if (layer_nr == 0)
49     {
50         const ExtruderTrain& train_wall_0 = settings.get<ExtruderTrain&>("wall_0_extruder_nr");
51         line_width_0 *= train_wall_0.settings.get<Ratio>("initial_layer_line_width_factor");
52         const ExtruderTrain& train_wall_x = settings.get<ExtruderTrain&>("wall_x_extruder_nr");
53         line_width_x *= train_wall_x.settings.get<Ratio>("initial_layer_line_width_factor");
54     }
55 
56     const bool recompute_outline_based_on_outer_wall =
57         settings.get<bool>("support_enable") &&
58         !settings.get<bool>("fill_outline_gaps");
59     for(size_t i = 0; i < inset_count; i++)
60     {
61         part->insets.push_back(Polygons());
62         if (i == 0)
63         {
64             part->insets[0] = part->outline.offset(-line_width_0 / 2 - wall_0_inset);
65         }
66         else if (i == 1)
67         {
68             part->insets[1] = part->insets[0].offset(-line_width_0 / 2 + wall_0_inset - line_width_x / 2);
69         }
70         else
71         {
72             part->insets[i] = part->insets[i - 1].offset(-line_width_x);
73         }
74 
75         const size_t inset_part_count = part->insets[i].size();
76         constexpr size_t minimum_part_saving = 3; //Only try if the part has more pieces than the previous inset and saves at least this many parts.
77         constexpr coord_t try_smaller = 10; //How many micrometres to inset with the try with a smaller inset.
78         if (inset_part_count > minimum_part_saving + 1 && (i == 0 || (i > 0 && inset_part_count > part->insets[i - 1].size() + minimum_part_saving)))
79         {
80             //Try a different line thickness and see if this fits better, based on these criteria:
81             // - There are fewer parts to the polygon (fits better in slim areas).
82             // - The polygon area is largely unaffected.
83             Polygons alternative_inset;
84             if (i == 0)
85             {
86                 alternative_inset = part->outline.offset(-(line_width_0 - try_smaller) / 2 - wall_0_inset);
87             }
88             else if (i == 1)
89             {
90                 alternative_inset = part->insets[0].offset(-(line_width_0 - try_smaller) / 2 + wall_0_inset - line_width_x / 2);
91             }
92             else
93             {
94                 alternative_inset = part->insets[i - 1].offset(-(line_width_x - try_smaller));
95             }
96             if (alternative_inset.size() < inset_part_count - minimum_part_saving) //Significantly fewer parts (saves more than 3 parts).
97             {
98                 part->insets[i] = alternative_inset;
99             }
100         }
101 
102         //Finally optimize all the polygons. Every point removed saves time in the long run.
103         part->insets[i].simplify();
104         part->insets[i].removeDegenerateVerts();
105         if (i == 0)
106         {
107             if (recompute_outline_based_on_outer_wall)
108             {
109                 part->print_outline = part->insets[0].offset(line_width_0 / 2, ClipperLib::jtSquare);
110             }
111             else
112             {
113                 part->print_outline = part->outline;
114             }
115         }
116         if (part->insets[i].size() < 1)
117         {
118             part->insets.pop_back();
119             break;
120         }
121     }
122 }
123 
124 /*
125  * This function is executed in a parallel region based on layer_nr.
126  * When modifying make sure any changes does not introduce data races.
127  *
128  * generateInsets only reads and writes data for the current layer
129  */
generateInsets(SliceLayer * layer)130 void WallsComputation::generateInsets(SliceLayer* layer)
131 {
132     for(unsigned int partNr = 0; partNr < layer->parts.size(); partNr++)
133     {
134         generateInsets(&layer->parts[partNr]);
135     }
136 
137     const bool remove_parts_with_no_insets = !settings.get<bool>("fill_outline_gaps");
138     //Remove the parts which did not generate an inset. As these parts are too small to print,
139     // and later code can now assume that there is always minimal 1 inset line.
140     for (unsigned int part_idx = 0; part_idx < layer->parts.size(); part_idx++)
141     {
142         if (layer->parts[part_idx].insets.size() == 0 && remove_parts_with_no_insets)
143         {
144             if (part_idx != layer->parts.size() - 1)
145             { // move existing part into part to be deleted
146                 layer->parts[part_idx] = std::move(layer->parts.back());
147             }
148             layer->parts.pop_back(); // always remove last element from array (is more efficient)
149             part_idx -= 1; // check the part we just moved here
150         }
151     }
152 }
153 
154 }//namespace cura
155