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