1 //Copyright (c) 2019 Ultimaker B.V.
2 //CuraEngine is released under the terms of the AGPLv3 or higher.
3
4 #include <cmath> // sqrt
5 #include <fstream> // debug IO
6
7 #include "Application.h" //To get the communication channel.
8 #include "PrintFeature.h"
9 #include "Slice.h"
10 #include "slicer.h"
11 #include "Weaver.h"
12 #include "communication/Communication.h" //To send layer view data.
13 #include "progress/Progress.h"
14 #include "utils/logoutput.h"
15 #include "settings/AdaptiveLayerHeights.h"
16 #include "settings/types/AngleRadians.h"
17
18 namespace cura
19 {
20
weave(MeshGroup * meshgroup)21 void Weaver::weave(MeshGroup* meshgroup)
22 {
23 wireFrame.meshgroup = meshgroup;
24
25 const coord_t maxz = meshgroup->max().z;
26
27 const Settings& mesh_group_settings = Application::getInstance().current_slice->scene.current_mesh_group->settings;
28 const coord_t initial_layer_thickness = mesh_group_settings.get<coord_t>("layer_height_0");
29 const coord_t connection_height = mesh_group_settings.get<coord_t>("wireframe_height");
30 const size_t layer_count = (maxz - initial_layer_thickness) / connection_height + 1;
31 std::vector<AdaptiveLayer> layer_thicknesses;
32
33 log("Layer count: %i\n", layer_count);
34
35 std::vector<cura::Slicer*> slicerList;
36
37 for(Mesh& mesh : meshgroup->meshes)
38 {
39 constexpr bool variable_layer_heights = false;
40 cura::Slicer* slicer = new cura::Slicer(&mesh, connection_height, layer_count, variable_layer_heights, &layer_thicknesses);
41 slicerList.push_back(slicer);
42 }
43
44 LayerIndex starting_layer_idx;
45 { // find first non-empty layer
46 for (starting_layer_idx = 0; starting_layer_idx < LayerIndex(layer_count); starting_layer_idx++)
47 {
48 Polygons parts;
49 for (cura::Slicer* slicer : slicerList)
50 parts.add(slicer->layers[starting_layer_idx].polygons);
51
52 if (parts.size() > 0)
53 break;
54 }
55 if (starting_layer_idx > 0)
56 {
57 logWarning("First %i layers are empty!\n", starting_layer_idx);
58 }
59 }
60
61 log("Chainifying layers...\n");
62 {
63 int starting_z = -1;
64 for (cura::Slicer* slicer : slicerList)
65 wireFrame.bottom_outline.add(slicer->layers[starting_layer_idx].polygons);
66
67 Application::getInstance().communication->sendPolygons(PrintFeatureType::OuterWall, wireFrame.bottom_outline, 1, 1, 1);
68
69 if (slicerList.empty()) //Wait, there is nothing to slice.
70 {
71 wireFrame.z_bottom = 0;
72 }
73 else
74 {
75 wireFrame.z_bottom = slicerList[0]->layers[starting_layer_idx].z;
76 }
77
78 Point starting_point_in_layer;
79 if (wireFrame.bottom_outline.size() > 0)
80 {
81 starting_point_in_layer = (wireFrame.bottom_outline.max() + wireFrame.bottom_outline.min()) / 2;
82 }
83 else
84 {
85 starting_point_in_layer = (Point(0,0) + meshgroup->max() + meshgroup->min()) / 2;
86 }
87
88 Progress::messageProgressStage(Progress::Stage::INSET_SKIN, nullptr);
89 for (LayerIndex layer_idx = starting_layer_idx + 1; layer_idx < LayerIndex(layer_count); layer_idx++)
90 {
91 Progress::messageProgress(Progress::Stage::INSET_SKIN, layer_idx+1, layer_count); // abuse the progress system of the normal mode of CuraEngine
92
93 Polygons parts1;
94 for (cura::Slicer* slicer : slicerList)
95 parts1.add(slicer->layers[layer_idx].polygons);
96
97
98 Polygons chainified;
99
100 chainify_polygons(parts1, starting_point_in_layer, chainified);
101
102 Application::getInstance().communication->sendPolygons(PrintFeatureType::OuterWall, chainified, 1, 1, 1);
103
104 if (chainified.size() > 0)
105 {
106 if (starting_z == -1) starting_z = slicerList[0]->layers[layer_idx-1].z;
107 wireFrame.layers.emplace_back();
108 WeaveLayer& layer = wireFrame.layers.back();
109
110 layer.z0 = slicerList[0]->layers[layer_idx-1].z - starting_z;
111 layer.z1 = slicerList[0]->layers[layer_idx].z - starting_z;
112
113 layer.supported = chainified;
114
115 starting_point_in_layer = layer.supported.back().back();
116 }
117 }
118 }
119
120 log("Finding horizontal parts...\n");
121 {
122 Progress::messageProgressStage(Progress::Stage::SUPPORT, nullptr);
123 for (unsigned int layer_idx = 0; layer_idx < wireFrame.layers.size(); layer_idx++)
124 {
125 Progress::messageProgress(Progress::Stage::SUPPORT, layer_idx+1, wireFrame.layers.size()); // abuse the progress system of the normal mode of CuraEngine
126
127 WeaveLayer& layer = wireFrame.layers[layer_idx];
128
129 Polygons empty;
130 Polygons& layer_above = (layer_idx+1 < wireFrame.layers.size())? wireFrame.layers[layer_idx+1].supported : empty;
131
132 createHorizontalFill(layer, layer_above);
133 }
134 }
135 // at this point layer.supported still only contains the polygons to be connected
136 // when connecting layers, we further add the supporting polygons created by the roofs
137
138 log("Connecting layers...\n");
139 {
140 Polygons* lower_top_parts = &wireFrame.bottom_outline;
141 int last_z = wireFrame.z_bottom;
142 for (unsigned int layer_idx = 0; layer_idx < wireFrame.layers.size(); layer_idx++) // use top of every layer but the last
143 {
144 WeaveLayer& layer = wireFrame.layers[layer_idx];
145
146 connect_polygons(*lower_top_parts, last_z, layer.supported, layer.z1, layer);
147 layer.supported.add(layer.roofs.roof_outlines);
148 lower_top_parts = &layer.supported;
149
150 last_z = layer.z1;
151 }
152 }
153
154
155 { // roofs:
156 if (!wireFrame.layers.empty()) //If there are no layers, create no roof.
157 {
158 WeaveLayer& top_layer = wireFrame.layers.back();
159 Polygons to_be_supported; // empty for the top layer
160 fillRoofs(top_layer.supported, to_be_supported, -1, top_layer.z1, top_layer.roofs);
161 }
162 }
163
164
165 { // bottom:
166 if (!wireFrame.layers.empty()) //If there are no layers, create no bottom.
167 {
168 Polygons to_be_supported; // is empty for the bottom layer, cause the order of insets doesn't really matter (in a sense everything is to be supported)
169 fillRoofs(wireFrame.bottom_outline, to_be_supported, -1, wireFrame.layers.front().z0, wireFrame.bottom_infill);
170 }
171 }
172
173 }
174
175
176
createHorizontalFill(WeaveLayer & layer,Polygons & layer_above)177 void Weaver::createHorizontalFill(WeaveLayer& layer, Polygons& layer_above)
178 {
179 const coord_t bridgable_dist = Application::getInstance().current_slice->scene.current_mesh_group->settings.get<coord_t>("wireframe_height");
180
181 // Polygons& polys_below = lower_top_parts;
182 Polygons& polys_here = layer.supported;
183 Polygons& polys_above = layer_above;
184
185
186 { // roofs
187 Polygons to_be_supported = polys_above.offset(bridgable_dist);
188 fillRoofs(polys_here, to_be_supported, -1, layer.z1, layer.roofs);
189
190 }
191
192 { // floors
193 Polygons to_be_supported = polys_above.offset(-bridgable_dist);
194 fillFloors(polys_here, to_be_supported, 1, layer.z1, layer.roofs);
195 }
196
197 {// optimize away doubly printed regions (boundaries of holes in layer etc.)
198 for (WeaveRoofPart& inset : layer.roofs.roof_insets)
199 connections2moves(inset);
200 }
201
202 }
203
204
fillRoofs(Polygons & supporting,Polygons & to_be_supported,int direction,int z,WeaveRoof & horizontals)205 void Weaver::fillRoofs(Polygons& supporting, Polygons& to_be_supported, int direction, int z, WeaveRoof& horizontals)
206 {
207 std::vector<WeaveRoofPart>& insets = horizontals.roof_insets;
208
209 if (supporting.size() == 0) return; // no parts to start the roof from!
210
211 Polygons roofs = supporting.difference(to_be_supported);
212
213 const coord_t roof_inset = Application::getInstance().current_slice->scene.current_mesh_group->settings.get<coord_t>("wireframe_roof_inset");
214 roofs = roofs.offset(-roof_inset).offset(roof_inset);
215
216 if (roofs.size() == 0) return;
217
218
219 Polygons roof_outlines;
220 Polygons roof_holes;
221 { // split roofs into outlines and holes
222 std::vector<PolygonsPart> roof_parts = roofs.splitIntoParts();
223 for (PolygonsPart& roof_part : roof_parts)
224 {
225 roof_outlines.add(roof_part[0]);
226 for (unsigned int hole_idx = 1; hole_idx < roof_part.size(); hole_idx++)
227 {
228 roof_holes.add(roof_part[hole_idx]);
229 roof_holes.back().reverse();
230 }
231 }
232 }
233
234
235 Polygons supporting_outlines;
236
237 std::vector<PolygonsPart> supporting_parts = supporting.splitIntoParts();
238 for (PolygonsPart& supporting_part : supporting_parts)
239 supporting_outlines.add(supporting_part[0]); // only add outlines, not the holes
240
241
242
243 Polygons inset1;
244 Polygons last_inset;
245 Polygons last_supported = supporting;
246 for (Polygons inset0 = supporting_outlines; inset0.size() > 0; inset0 = last_inset)
247 {
248 last_inset = inset0.offset(direction * roof_inset, ClipperLib::jtRound);
249 inset1 = last_inset.intersection(roof_outlines); // stay within roof area
250 inset1 = inset1.unionPolygons(roof_holes);// make insets go around holes
251
252 if (inset1.size() == 0) break;
253
254 insets.emplace_back();
255
256 connect(last_supported, z, inset1, z, insets.back());
257
258 inset1 = inset1.remove(roof_holes); // throw away holes which appear in every intersection
259 inset1 = inset1.remove(roof_outlines);// throw away fully filled regions
260
261 last_supported = insets.back().supported; // chainified
262
263 }
264
265
266
267 horizontals.roof_outlines.add(roofs); // TODO just add the new lines, not the lines of the roofs which are already supported ==> make outlines into a connection from which we only print the top, not the connection
268 }
269
fillFloors(Polygons & supporting,Polygons & to_be_supported,int direction,int z,WeaveRoof & horizontals)270 void Weaver::fillFloors(Polygons& supporting, Polygons& to_be_supported, int direction, int z, WeaveRoof& horizontals)
271 {
272 std::vector<WeaveRoofPart>& outsets = horizontals.roof_insets;
273
274 if (to_be_supported.size() == 0) return; // no parts to start the floor from!
275 if (supporting.size() == 0) return; // no parts to start the floor from!
276
277 Polygons floors = to_be_supported.difference(supporting);
278
279 const coord_t roof_inset = Application::getInstance().current_slice->scene.current_mesh_group->settings.get<coord_t>("wireframe_roof_inset");
280 floors = floors.offset(-roof_inset).offset(roof_inset);
281
282 if (floors.size() == 0) return;
283
284
285 std::vector<PolygonsPart> floor_parts = floors.splitIntoParts();
286
287 Polygons floor_outlines;
288 Polygons floor_holes;
289 for (PolygonsPart& floor_part : floor_parts)
290 {
291 floor_outlines.add(floor_part[0]);
292 for (unsigned int hole_idx = 1; hole_idx < floor_part.size(); hole_idx++)
293 {
294 floor_holes.add(floor_part[hole_idx]);
295 //floor_holes.back().reverse();
296 }
297 }
298
299
300 Polygons outset1;
301
302 Polygons last_supported = supporting;
303
304 for (Polygons outset0 = supporting; outset0.size() > 0; outset0 = outset1)
305 {
306 outset1 = outset0.offset(roof_inset * direction, ClipperLib::jtRound).intersection(floors);
307 outset1 = outset1.remove(floor_holes); // throw away holes which appear in every intersection
308 outset1 = outset1.remove(floor_outlines); // throw away holes which appear in every intersection
309
310
311 outsets.emplace_back();
312
313 connect(last_supported, z, outset1, z, outsets.back());
314
315 outset1 = outset1.remove(floor_outlines);// throw away fully filled regions
316
317 last_supported = outsets.back().supported; // chainified
318
319 }
320
321
322 horizontals.roof_outlines.add(floors);
323 }
324
325
connections2moves(WeaveRoofPart & inset)326 void Weaver::connections2moves(WeaveRoofPart& inset)
327 {
328 const coord_t line_width = Application::getInstance().current_slice->scene.current_mesh_group->settings.get<coord_t>("wall_line_width_x");
329
330 constexpr bool include_half_of_last_down = true;
331
332 for (WeaveConnectionPart& part : inset.connections)
333 {
334 std::vector<WeaveConnectionSegment>& segments = part.connection.segments;
335 for (unsigned int idx = 0; idx < part.connection.segments.size(); idx += 2)
336 {
337 WeaveConnectionSegment& segment = segments[idx];
338 assert(segment.segmentType == WeaveSegmentType::UP);
339 Point3 from = (idx == 0)? part.connection.from : segments[idx-1].to;
340 bool skipped = (segment.to - from).vSize2() < line_width * line_width;
341 if (skipped)
342 {
343 unsigned int begin = idx;
344 for (; idx < segments.size(); idx += 2)
345 {
346 WeaveConnectionSegment& segment = segments[idx];
347 assert(segments[idx].segmentType == WeaveSegmentType::UP);
348 Point3 from = (idx == 0)? part.connection.from : segments[idx-1].to;
349 bool skipped = (segment.to - from).vSize2() < line_width * line_width;
350 if (!skipped)
351 {
352 break;
353 }
354 }
355 int end = idx - ((include_half_of_last_down)? 2 : 1);
356 if (idx >= segments.size())
357 segments.erase(segments.begin() + begin, segments.end());
358 else
359 {
360 segments.erase(segments.begin() + begin, segments.begin() + end);
361 if (begin < segments.size())
362 {
363 segments[begin].segmentType = WeaveSegmentType::MOVE;
364 if (include_half_of_last_down)
365 segments[begin+1].segmentType = WeaveSegmentType::DOWN_AND_FLAT;
366 }
367 idx = begin + ((include_half_of_last_down)? 2 : 1);
368 }
369 }
370 }
371 }
372 }
373
connect(Polygons & parts0,int z0,Polygons & parts1,int z1,WeaveConnection & result)374 void Weaver::connect(Polygons& parts0, int z0, Polygons& parts1, int z1, WeaveConnection& result)
375 {
376 // TODO: convert polygons (with outset + difference) such that after printing the first polygon, we can't be in the way of the printed stuff
377 // something like:
378 // for (m > n)
379 // parts[m] = parts[m].difference(parts[n].offset(nozzle_top_diameter))
380 // according to the printing order!
381 //
382 // OR! :
383 //
384 // unify different parts if gap is too small
385
386 Polygons& supported = result.supported;
387
388 if (parts1.size() == 0) return;
389
390 Point& start_close_to = (parts0.size() > 0)? parts0.back().back() : parts1.back().back();
391
392 chainify_polygons(parts1, start_close_to, supported);
393
394 if (parts0.size() == 0) return;
395
396 connect_polygons(parts0, z0, supported, z1, result);
397
398 }
399
400
chainify_polygons(Polygons & parts1,Point start_close_to,Polygons & result)401 void Weaver::chainify_polygons(Polygons& parts1, Point start_close_to, Polygons& result)
402 {
403 const Settings& mesh_group_settings = Application::getInstance().current_slice->scene.current_mesh_group->settings;
404 const coord_t connection_height = mesh_group_settings.get<coord_t>("wireframe_height");
405 const coord_t nozzle_outer_diameter = mesh_group_settings.get<coord_t>("machine_nozzle_tip_outer_diameter"); // ___ ___
406 const AngleRadians nozzle_expansion_angle = mesh_group_settings.get<AngleRadians>("machine_nozzle_expansion_angle"); // \_U_/
407 const coord_t nozzle_clearance = mesh_group_settings.get<coord_t>("wireframe_nozzle_clearance"); // at least line width
408 const coord_t nozzle_top_diameter = tan(nozzle_expansion_angle) * connection_height + nozzle_outer_diameter + nozzle_clearance;
409
410 for (unsigned int prt = 0 ; prt < parts1.size(); prt++)
411 {
412 ConstPolygonRef upperPart = parts1[prt];
413
414 ClosestPolygonPoint closestInPoly = PolygonUtils::findClosest(start_close_to, upperPart);
415
416
417 PolygonRef part_top = result.newPoly();
418
419 GivenDistPoint next_upper;
420 bool found = true;
421 int idx = 0;
422
423 for (Point upper_point = upperPart[closestInPoly.point_idx]; found; upper_point = next_upper.location)
424 {
425 found = PolygonUtils::getNextPointWithDistance(upper_point, nozzle_top_diameter, upperPart, idx, closestInPoly.point_idx, next_upper);
426
427
428 if (!found)
429 {
430 break;
431 }
432
433 part_top.add(upper_point);
434
435 idx = next_upper.pos;
436 }
437 if (part_top.size() > 0)
438 start_close_to = part_top.back();
439 else
440 result.remove(result.size()-1);
441 }
442 }
443
444
connect_polygons(Polygons & supporting,int z0,Polygons & supported,int z1,WeaveConnection & result)445 void Weaver::connect_polygons(Polygons& supporting, int z0, Polygons& supported, int z1, WeaveConnection& result)
446 {
447
448 if (supporting.size() < 1)
449 {
450 std::cerr << "lower layer has zero parts!\n";
451 return;
452 }
453
454 result.z0 = z0;
455 result.z1 = z1;
456
457 std::vector<WeaveConnectionPart>& parts = result.connections;
458
459 for (unsigned int prt = 0 ; prt < supported.size(); prt++)
460 {
461
462 ConstPolygonRef upperPart(supported[prt]);
463
464
465 parts.emplace_back(prt);
466 WeaveConnectionPart& part = parts.back();
467 PolyLine3& connection = part.connection;
468
469 Point3 last_upper;
470 bool firstIter = true;
471
472 for (const Point& upper_point : upperPart)
473 {
474
475 ClosestPolygonPoint lowerPolyPoint = PolygonUtils::findClosest(upper_point, supporting);
476 Point& lower = lowerPolyPoint.location;
477
478 Point3 lower3 = Point3(lower.X, lower.Y, z0);
479 Point3 upper3 = Point3(upper_point.X, upper_point.Y, z1);
480
481
482 if (firstIter)
483 connection.from = lower3;
484 else
485 connection.segments.emplace_back<>(lower3, WeaveSegmentType::DOWN);
486
487 connection.segments.emplace_back<>(upper3, WeaveSegmentType::UP);
488 last_upper = upper3;
489
490 firstIter = false;
491 }
492 }
493 }
494
495
496 }//namespace cura
497