1 //Copyright (c) 2018 Ultimaker B.V.
2 //CuraEngine is released under the terms of the AGPLv3 or higher.
3 
4 #include <algorithm>
5 #include <limits>
6 
7 #include "Application.h" //To get settings.
8 #include "ExtruderTrain.h"
9 #include "gcodeExport.h"
10 #include "infill.h"
11 #include "LayerPlan.h"
12 #include "PrimeTower.h"
13 #include "PrintFeature.h"
14 #include "raft.h"
15 #include "Scene.h"
16 #include "Slice.h"
17 #include "sliceDataStorage.h"
18 
19 #define CIRCLE_RESOLUTION 32 //The number of vertices in each circle.
20 
21 
22 namespace cura
23 {
24 
PrimeTower()25 PrimeTower::PrimeTower()
26 : wipe_from_middle(false)
27 {
28     const Scene& scene = Application::getInstance().current_slice->scene;
29     enabled = scene.current_mesh_group->settings.get<bool>("prime_tower_enable")
30            && scene.current_mesh_group->settings.get<coord_t>("prime_tower_min_volume") > 10
31            && scene.current_mesh_group->settings.get<coord_t>("prime_tower_size") > 10;
32 
33     extruder_count = scene.extruders.size();
34     extruder_order.resize(extruder_count);
35     for (unsigned int extruder_nr = 0; extruder_nr < extruder_count; extruder_nr++)
36     {
37         extruder_order[extruder_nr] = extruder_nr; //Start with default order, then sort.
38     }
39     //Sort from high adhesion to low adhesion.
40     const Scene* scene_pointer = &scene; //Communicate to lambda via pointer to prevent copy.
41     std::stable_sort(extruder_order.begin(), extruder_order.end(), [scene_pointer](const unsigned int& extruder_nr_a, const unsigned int& extruder_nr_b) -> bool
42     {
43         const Ratio adhesion_a = scene_pointer->extruders[extruder_nr_a].settings.get<Ratio>("material_adhesion_tendency");
44         const Ratio adhesion_b = scene_pointer->extruders[extruder_nr_b].settings.get<Ratio>("material_adhesion_tendency");
45         return adhesion_a < adhesion_b;
46     });
47 }
48 
generateGroundpoly()49 void PrimeTower::generateGroundpoly()
50 {
51     if (!enabled)
52     {
53         return;
54     }
55 
56     const Settings& mesh_group_settings = Application::getInstance().current_slice->scene.current_mesh_group->settings;
57     const coord_t tower_size = mesh_group_settings.get<coord_t>("prime_tower_size");
58 
59     const Settings& brim_extruder_settings = mesh_group_settings.get<ExtruderTrain&>("adhesion_extruder_nr").settings;
60     const bool has_raft = (mesh_group_settings.get<EPlatformAdhesion>("adhesion_type") == EPlatformAdhesion::RAFT);
61     const bool has_prime_brim = mesh_group_settings.get<bool>("prime_tower_brim_enable");
62     const coord_t offset = (has_raft || ! has_prime_brim) ? 0 :
63         brim_extruder_settings.get<size_t>("brim_line_count") *
64         brim_extruder_settings.get<coord_t>("skirt_brim_line_width") *
65         brim_extruder_settings.get<Ratio>("initial_layer_line_width_factor");
66 
67     PolygonRef p = outer_poly.newPoly();
68     int tower_distance = 0;
69     const coord_t x = mesh_group_settings.get<coord_t>("prime_tower_position_x") - offset;
70     const coord_t y = mesh_group_settings.get<coord_t>("prime_tower_position_y") - offset;
71     const coord_t tower_radius = tower_size / 2;
72     for (unsigned int i = 0; i < CIRCLE_RESOLUTION; i++)
73     {
74         const double angle = (double) i / CIRCLE_RESOLUTION * 2 * M_PI; //In radians.
75         p.add(Point(x - tower_radius + tower_distance + cos(angle) * tower_radius,
76                     y + tower_radius + tower_distance + sin(angle) * tower_radius));
77     }
78     middle = Point(x - tower_size / 2, y + tower_size / 2);
79 
80     post_wipe_point = Point(x + tower_distance - tower_size / 2, y + tower_distance + tower_size / 2);
81 
82     outer_poly_first_layer = outer_poly.offset(offset);
83 }
84 
generatePaths(const SliceDataStorage & storage)85 void PrimeTower::generatePaths(const SliceDataStorage& storage)
86 {
87     enabled &= storage.max_print_height_second_to_last_extruder >= 0; //Maybe it turns out that we don't need a prime tower after all because there are no layer switches.
88     if (enabled)
89     {
90         generatePaths_denseInfill();
91         generateStartLocations();
92     }
93 }
94 
generatePaths_denseInfill()95 void PrimeTower::generatePaths_denseInfill()
96 {
97     const Scene& scene = Application::getInstance().current_slice->scene;
98     const Settings& mesh_group_settings = scene.current_mesh_group->settings;
99     const coord_t layer_height = mesh_group_settings.get<coord_t>("layer_height");
100     pattern_per_extruder.resize(extruder_count);
101 
102     coord_t cumulative_inset = 0; //Each tower shape is going to be printed inside the other. This is the inset we're doing for each extruder.
103     for (size_t extruder_nr : extruder_order)
104     {
105         const coord_t line_width = scene.extruders[extruder_nr].settings.get<coord_t>("prime_tower_line_width");
106         const coord_t required_volume = scene.extruders[extruder_nr].settings.get<double>("prime_tower_min_volume") * 1000000000; //To cubic microns.
107         const Ratio flow = scene.extruders[extruder_nr].settings.get<Ratio>("prime_tower_flow");
108         coord_t current_volume = 0;
109         ExtrusionMoves& pattern = pattern_per_extruder[extruder_nr];
110 
111         //Create the walls of the prime tower.
112         unsigned int wall_nr = 0;
113         for (; current_volume < required_volume; wall_nr++)
114         {
115             //Create a new polygon with an offset from the outer polygon.
116             Polygons polygons = outer_poly.offset(-cumulative_inset - wall_nr * line_width - line_width / 2);
117             pattern.polygons.add(polygons);
118             current_volume += polygons.polygonLength() * line_width * layer_height * flow;
119             if (polygons.empty()) //Don't continue. We won't ever reach the required volume because it doesn't fit.
120             {
121                 break;
122             }
123         }
124         cumulative_inset += wall_nr * line_width;
125 
126         //Generate the pattern for the first layer.
127         coord_t line_width_layer0 = line_width;
128         if (mesh_group_settings.get<EPlatformAdhesion>("adhesion_type") != EPlatformAdhesion::RAFT)
129         {
130             line_width_layer0 *= scene.extruders[extruder_nr].settings.get<Ratio>("initial_layer_line_width_factor");
131         }
132         pattern_per_extruder_layer0.emplace_back();
133 
134         ExtrusionMoves& pattern_layer0 = pattern_per_extruder_layer0.back();
135 
136         // Generate a concentric infill pattern in the form insets for the prime tower's first layer instead of using
137         // the infill pattern because the infill pattern tries to connect polygons in different insets which causes the
138         // first layer of the prime tower to not stick well.
139         Polygons inset = outer_poly.offset(-line_width_layer0 / 2);
140         while (!inset.empty())
141         {
142             pattern_layer0.polygons.add(inset);
143             inset = inset.offset(-line_width_layer0);
144         }
145     }
146 }
147 
generateStartLocations()148 void PrimeTower::generateStartLocations()
149 {
150     // Evenly spread out a number of dots along the prime tower's outline. This is done for the complete outline,
151     // so use the same start and end segments for this.
152     PolygonsPointIndex segment_start = PolygonsPointIndex(&outer_poly, 0, 0);
153     PolygonsPointIndex segment_end = segment_start;
154 
155     PolygonUtils::spreadDots(segment_start, segment_end, number_of_prime_tower_start_locations, prime_tower_start_locations);
156 }
157 
addToGcode(const SliceDataStorage & storage,LayerPlan & gcode_layer,const int prev_extruder,const int new_extruder) const158 void PrimeTower::addToGcode(const SliceDataStorage& storage, LayerPlan& gcode_layer, const int prev_extruder, const int new_extruder) const
159 {
160     if (!enabled)
161     {
162         return;
163     }
164     if (gcode_layer.getPrimeTowerIsPlanned(new_extruder))
165     { // don't print the prime tower if it has been printed already with this extruder.
166         return;
167     }
168 
169     const LayerIndex layer_nr = gcode_layer.getLayerNr();
170     if (layer_nr > storage.max_print_height_second_to_last_extruder + 1)
171     {
172         return;
173     }
174 
175     bool post_wipe = Application::getInstance().current_slice->scene.extruders[prev_extruder].settings.get<bool>("prime_tower_wipe_enabled");
176 
177     // Do not wipe on the first layer, we will generate non-hollow prime tower there for better bed adhesion.
178     if (prev_extruder == new_extruder || layer_nr == 0)
179     {
180         post_wipe = false;
181     }
182 
183     // Go to the start location if it's not the first layer
184     if (layer_nr != 0)
185     {
186         gotoStartLocation(gcode_layer, new_extruder);
187     }
188 
189     addToGcode_denseInfill(gcode_layer, new_extruder);
190 
191     // post-wipe:
192     if (post_wipe)
193     {
194         //Make sure we wipe the old extruder on the prime tower.
195         const Settings& previous_settings = Application::getInstance().current_slice->scene.extruders[prev_extruder].settings;
196         const Point previous_nozzle_offset = Point(previous_settings.get<coord_t>("machine_nozzle_offset_x"), previous_settings.get<coord_t>("machine_nozzle_offset_y"));
197         const Settings& new_settings = Application::getInstance().current_slice->scene.extruders[new_extruder].settings;
198         const Point new_nozzle_offset = Point(new_settings.get<coord_t>("machine_nozzle_offset_x"), new_settings.get<coord_t>("machine_nozzle_offset_y"));
199         gcode_layer.addTravel(post_wipe_point - previous_nozzle_offset + new_nozzle_offset);
200     }
201 
202     gcode_layer.setPrimeTowerIsPlanned(new_extruder);
203 }
204 
addToGcode_denseInfill(LayerPlan & gcode_layer,const size_t extruder_nr) const205 void PrimeTower::addToGcode_denseInfill(LayerPlan& gcode_layer, const size_t extruder_nr) const
206 {
207     const ExtrusionMoves& pattern = (gcode_layer.getLayerNr() == -static_cast<LayerIndex>(Raft::getFillerLayerCount()))
208         ? pattern_per_extruder_layer0[extruder_nr]
209         : pattern_per_extruder[extruder_nr];
210 
211     const GCodePathConfig& config = gcode_layer.configs_storage.prime_tower_config_per_extruder[extruder_nr];
212 
213     gcode_layer.addPolygonsByOptimizer(pattern.polygons, config);
214     gcode_layer.addLinesByOptimizer(pattern.lines, config, SpaceFillType::Lines);
215 }
216 
subtractFromSupport(SliceDataStorage & storage)217 void PrimeTower::subtractFromSupport(SliceDataStorage& storage)
218 {
219     const Polygons outside_polygon = outer_poly.getOutsidePolygons();
220     AABB outside_polygon_boundary_box(outside_polygon);
221     for(size_t layer = 0; layer <= (size_t)storage.max_print_height_second_to_last_extruder + 1 && layer < storage.support.supportLayers.size(); layer++)
222     {
223         SupportLayer& support_layer = storage.support.supportLayers[layer];
224         // take the differences of the support infill parts and the prime tower area
225         support_layer.excludeAreasFromSupportInfillAreas(outside_polygon, outside_polygon_boundary_box);
226     }
227 }
228 
gotoStartLocation(LayerPlan & gcode_layer,const int extruder_nr) const229 void PrimeTower::gotoStartLocation(LayerPlan& gcode_layer, const int extruder_nr) const
230 {
231     int current_start_location_idx = ((((extruder_nr + 1) * gcode_layer.getLayerNr()) % number_of_prime_tower_start_locations)
232             + number_of_prime_tower_start_locations) % number_of_prime_tower_start_locations;
233 
234     const ClosestPolygonPoint wipe_location = prime_tower_start_locations[current_start_location_idx];
235 
236     const ExtruderTrain& train = Application::getInstance().current_slice->scene.extruders[extruder_nr];
237     const coord_t inward_dist = train.settings.get<coord_t>("machine_nozzle_size") * 3 / 2 ;
238     const coord_t start_dist = train.settings.get<coord_t>("machine_nozzle_size") * 2;
239     const Point prime_end = PolygonUtils::moveInsideDiagonally(wipe_location, inward_dist);
240     const Point outward_dir = wipe_location.location - prime_end;
241     const Point prime_start = wipe_location.location + normal(outward_dir, start_dist);
242 
243     gcode_layer.addTravel(prime_start);
244 }
245 
246 }//namespace cura
247