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