1 #include "ExtrusionEntity.hpp"
2 #include "ExtrusionEntityCollection.hpp"
3 #include "ExPolygonCollection.hpp"
4 #include "ClipperUtils.hpp"
5 #include "Extruder.hpp"
6 #include "Flow.hpp"
7 #include <cmath>
8 #include <limits>
9 #include <sstream>
10 
11 #define L(s) (s)
12 
13 namespace Slic3r {
14 
intersect_expolygons(const ExPolygonCollection & collection,ExtrusionEntityCollection * retval) const15 void ExtrusionPath::intersect_expolygons(const ExPolygonCollection &collection, ExtrusionEntityCollection* retval) const
16 {
17     this->_inflate_collection(intersection_pl((Polylines)polyline, to_polygons(collection.expolygons)), retval);
18 }
19 
subtract_expolygons(const ExPolygonCollection & collection,ExtrusionEntityCollection * retval) const20 void ExtrusionPath::subtract_expolygons(const ExPolygonCollection &collection, ExtrusionEntityCollection* retval) const
21 {
22     this->_inflate_collection(diff_pl((Polylines)this->polyline, to_polygons(collection.expolygons)), retval);
23 }
24 
clip_end(double distance)25 void ExtrusionPath::clip_end(double distance)
26 {
27     this->polyline.clip_end(distance);
28 }
29 
simplify(double tolerance)30 void ExtrusionPath::simplify(double tolerance)
31 {
32     this->polyline.simplify(tolerance);
33 }
34 
length() const35 double ExtrusionPath::length() const
36 {
37     return this->polyline.length();
38 }
39 
_inflate_collection(const Polylines & polylines,ExtrusionEntityCollection * collection) const40 void ExtrusionPath::_inflate_collection(const Polylines &polylines, ExtrusionEntityCollection* collection) const
41 {
42     for (const Polyline &polyline : polylines)
43         collection->entities.emplace_back(new ExtrusionPath(polyline, *this));
44 }
45 
polygons_covered_by_width(Polygons & out,const float scaled_epsilon) const46 void ExtrusionPath::polygons_covered_by_width(Polygons &out, const float scaled_epsilon) const
47 {
48     polygons_append(out, offset(this->polyline, float(scale_(this->width/2)) + scaled_epsilon));
49 }
50 
polygons_covered_by_spacing(Polygons & out,const float scaled_epsilon) const51 void ExtrusionPath::polygons_covered_by_spacing(Polygons &out, const float scaled_epsilon) const
52 {
53     // Instantiating the Flow class to get the line spacing.
54     // Don't know the nozzle diameter, setting to zero. It shall not matter it shall be optimized out by the compiler.
55     Flow flow(this->width, this->height, 0.f, is_bridge(this->role()));
56     polygons_append(out, offset(this->polyline, 0.5f * float(flow.scaled_spacing()) + scaled_epsilon));
57 }
58 
reverse()59 void ExtrusionMultiPath::reverse()
60 {
61     for (ExtrusionPath &path : this->paths)
62         path.reverse();
63     std::reverse(this->paths.begin(), this->paths.end());
64 }
65 
length() const66 double ExtrusionMultiPath::length() const
67 {
68     double len = 0;
69     for (const ExtrusionPath &path : this->paths)
70         len += path.polyline.length();
71     return len;
72 }
73 
polygons_covered_by_width(Polygons & out,const float scaled_epsilon) const74 void ExtrusionMultiPath::polygons_covered_by_width(Polygons &out, const float scaled_epsilon) const
75 {
76     for (const ExtrusionPath &path : this->paths)
77         path.polygons_covered_by_width(out, scaled_epsilon);
78 }
79 
polygons_covered_by_spacing(Polygons & out,const float scaled_epsilon) const80 void ExtrusionMultiPath::polygons_covered_by_spacing(Polygons &out, const float scaled_epsilon) const
81 {
82     for (const ExtrusionPath &path : this->paths)
83         path.polygons_covered_by_spacing(out, scaled_epsilon);
84 }
85 
min_mm3_per_mm() const86 double ExtrusionMultiPath::min_mm3_per_mm() const
87 {
88     double min_mm3_per_mm = std::numeric_limits<double>::max();
89     for (const ExtrusionPath &path : this->paths)
90         min_mm3_per_mm = std::min(min_mm3_per_mm, path.mm3_per_mm);
91     return min_mm3_per_mm;
92 }
93 
as_polyline() const94 Polyline ExtrusionMultiPath::as_polyline() const
95 {
96     Polyline out;
97     if (! paths.empty()) {
98         size_t len = 0;
99         for (size_t i_path = 0; i_path < paths.size(); ++ i_path) {
100             assert(! paths[i_path].polyline.points.empty());
101             assert(i_path == 0 || paths[i_path - 1].polyline.points.back() == paths[i_path].polyline.points.front());
102             len += paths[i_path].polyline.points.size();
103         }
104         // The connecting points between the segments are equal.
105         len -= paths.size() - 1;
106         assert(len > 0);
107         out.points.reserve(len);
108         out.points.push_back(paths.front().polyline.points.front());
109         for (size_t i_path = 0; i_path < paths.size(); ++ i_path)
110             out.points.insert(out.points.end(), paths[i_path].polyline.points.begin() + 1, paths[i_path].polyline.points.end());
111     }
112     return out;
113 }
114 
make_clockwise()115 bool ExtrusionLoop::make_clockwise()
116 {
117     bool was_ccw = this->polygon().is_counter_clockwise();
118     if (was_ccw) this->reverse();
119     return was_ccw;
120 }
121 
make_counter_clockwise()122 bool ExtrusionLoop::make_counter_clockwise()
123 {
124     bool was_cw = this->polygon().is_clockwise();
125     if (was_cw) this->reverse();
126     return was_cw;
127 }
128 
reverse()129 void ExtrusionLoop::reverse()
130 {
131     for (ExtrusionPath &path : this->paths)
132         path.reverse();
133     std::reverse(this->paths.begin(), this->paths.end());
134 }
135 
polygon() const136 Polygon ExtrusionLoop::polygon() const
137 {
138     Polygon polygon;
139     for (const ExtrusionPath &path : this->paths) {
140         // for each polyline, append all points except the last one (because it coincides with the first one of the next polyline)
141         polygon.points.insert(polygon.points.end(), path.polyline.points.begin(), path.polyline.points.end()-1);
142     }
143     return polygon;
144 }
145 
length() const146 double ExtrusionLoop::length() const
147 {
148     double len = 0;
149     for (const ExtrusionPath &path : this->paths)
150         len += path.polyline.length();
151     return len;
152 }
153 
split_at_vertex(const Point & point)154 bool ExtrusionLoop::split_at_vertex(const Point &point)
155 {
156     for (ExtrusionPaths::iterator path = this->paths.begin(); path != this->paths.end(); ++path) {
157         int idx = path->polyline.find_point(point);
158         if (idx != -1) {
159             if (this->paths.size() == 1) {
160                 // just change the order of points
161                 path->polyline.points.insert(path->polyline.points.end(), path->polyline.points.begin() + 1, path->polyline.points.begin() + idx + 1);
162                 path->polyline.points.erase(path->polyline.points.begin(), path->polyline.points.begin() + idx);
163             } else {
164                 // new paths list starts with the second half of current path
165                 ExtrusionPaths new_paths;
166                 new_paths.reserve(this->paths.size() + 1);
167                 {
168                     ExtrusionPath p = *path;
169                     p.polyline.points.erase(p.polyline.points.begin(), p.polyline.points.begin() + idx);
170                     if (p.polyline.is_valid()) new_paths.push_back(p);
171                 }
172 
173                 // then we add all paths until the end of current path list
174                 new_paths.insert(new_paths.end(), path+1, this->paths.end());  // not including this path
175 
176                 // then we add all paths since the beginning of current list up to the previous one
177                 new_paths.insert(new_paths.end(), this->paths.begin(), path);  // not including this path
178 
179                 // finally we add the first half of current path
180                 {
181                     ExtrusionPath p = *path;
182                     p.polyline.points.erase(p.polyline.points.begin() + idx + 1, p.polyline.points.end());
183                     if (p.polyline.is_valid()) new_paths.push_back(p);
184                 }
185                 // we can now override the old path list with the new one and stop looping
186                 std::swap(this->paths, new_paths);
187             }
188             return true;
189         }
190     }
191     return false;
192 }
193 
194 // Splitting an extrusion loop, possibly made of multiple segments, some of the segments may be bridging.
split_at(const Point & point,bool prefer_non_overhang)195 void ExtrusionLoop::split_at(const Point &point, bool prefer_non_overhang)
196 {
197     if (this->paths.empty())
198         return;
199 
200     // Find the closest path and closest point belonging to that path. Avoid overhangs, if asked for.
201     size_t path_idx = 0;
202     Point  p;
203     {
204         double min = std::numeric_limits<double>::max();
205         Point  p_non_overhang;
206         size_t path_idx_non_overhang = 0;
207         double min_non_overhang = std::numeric_limits<double>::max();
208         for (const ExtrusionPath &path : this->paths) {
209             Point p_tmp = point.projection_onto(path.polyline);
210             double dist = (p_tmp - point).cast<double>().norm();
211             if (dist < min) {
212                 p = p_tmp;
213                 min = dist;
214                 path_idx = &path - &this->paths.front();
215             }
216             if (prefer_non_overhang && ! is_bridge(path.role()) && dist < min_non_overhang) {
217                 p_non_overhang = p_tmp;
218                 min_non_overhang = dist;
219                 path_idx_non_overhang = &path - &this->paths.front();
220             }
221         }
222         if (prefer_non_overhang && min_non_overhang != std::numeric_limits<double>::max()) {
223             // Only apply the non-overhang point if there is one.
224             path_idx = path_idx_non_overhang;
225             p        = p_non_overhang;
226         }
227     }
228 
229     // now split path_idx in two parts
230     const ExtrusionPath &path = this->paths[path_idx];
231     ExtrusionPath p1(path.role(), path.mm3_per_mm, path.width, path.height);
232     ExtrusionPath p2(path.role(), path.mm3_per_mm, path.width, path.height);
233     path.polyline.split_at(p, &p1.polyline, &p2.polyline);
234 
235     if (this->paths.size() == 1) {
236         if (! p1.polyline.is_valid())
237             std::swap(this->paths.front().polyline.points, p2.polyline.points);
238         else if (! p2.polyline.is_valid())
239             std::swap(this->paths.front().polyline.points, p1.polyline.points);
240         else {
241             p2.polyline.points.insert(p2.polyline.points.end(), p1.polyline.points.begin() + 1, p1.polyline.points.end());
242             std::swap(this->paths.front().polyline.points, p2.polyline.points);
243         }
244     } else {
245         // install the two paths
246         this->paths.erase(this->paths.begin() + path_idx);
247         if (p2.polyline.is_valid()) this->paths.insert(this->paths.begin() + path_idx, p2);
248         if (p1.polyline.is_valid()) this->paths.insert(this->paths.begin() + path_idx, p1);
249     }
250 
251     // split at the new vertex
252     this->split_at_vertex(p);
253 }
254 
clip_end(double distance,ExtrusionPaths * paths) const255 void ExtrusionLoop::clip_end(double distance, ExtrusionPaths* paths) const
256 {
257     *paths = this->paths;
258 
259     while (distance > 0 && !paths->empty()) {
260         ExtrusionPath &last = paths->back();
261         double len = last.length();
262         if (len <= distance) {
263             paths->pop_back();
264             distance -= len;
265         } else {
266             last.polyline.clip_end(distance);
267             break;
268         }
269     }
270 }
271 
has_overhang_point(const Point & point) const272 bool ExtrusionLoop::has_overhang_point(const Point &point) const
273 {
274     for (const ExtrusionPath &path : this->paths) {
275         int pos = path.polyline.find_point(point);
276         if (pos != -1) {
277             // point belongs to this path
278             // we consider it overhang only if it's not an endpoint
279             return (is_bridge(path.role()) && pos > 0 && pos != (int)(path.polyline.points.size())-1);
280         }
281     }
282     return false;
283 }
284 
polygons_covered_by_width(Polygons & out,const float scaled_epsilon) const285 void ExtrusionLoop::polygons_covered_by_width(Polygons &out, const float scaled_epsilon) const
286 {
287     for (const ExtrusionPath &path : this->paths)
288         path.polygons_covered_by_width(out, scaled_epsilon);
289 }
290 
polygons_covered_by_spacing(Polygons & out,const float scaled_epsilon) const291 void ExtrusionLoop::polygons_covered_by_spacing(Polygons &out, const float scaled_epsilon) const
292 {
293     for (const ExtrusionPath &path : this->paths)
294         path.polygons_covered_by_spacing(out, scaled_epsilon);
295 }
296 
min_mm3_per_mm() const297 double ExtrusionLoop::min_mm3_per_mm() const
298 {
299     double min_mm3_per_mm = std::numeric_limits<double>::max();
300     for (const ExtrusionPath &path : this->paths)
301         min_mm3_per_mm = std::min(min_mm3_per_mm, path.mm3_per_mm);
302     return min_mm3_per_mm;
303 }
304 
305 
role_to_string(ExtrusionRole role)306 std::string ExtrusionEntity::role_to_string(ExtrusionRole role)
307 {
308     switch (role) {
309         case erNone                         : return L("Unknown");
310         case erPerimeter                    : return L("Perimeter");
311         case erExternalPerimeter            : return L("External perimeter");
312         case erOverhangPerimeter            : return L("Overhang perimeter");
313         case erInternalInfill               : return L("Internal infill");
314         case erSolidInfill                  : return L("Solid infill");
315         case erTopSolidInfill               : return L("Top solid infill");
316         case erIroning                      : return L("Ironing");
317         case erBridgeInfill                 : return L("Bridge infill");
318         case erGapFill                      : return L("Gap fill");
319         case erSkirt                        : return L("Skirt");
320         case erSupportMaterial              : return L("Support material");
321         case erSupportMaterialInterface     : return L("Support material interface");
322         case erWipeTower                    : return L("Wipe tower");
323         case erCustom                       : return L("Custom");
324         case erMixed                        : return L("Mixed");
325         default                             : assert(false);
326     }
327     return "";
328 }
329 
string_to_role(const std::string_view role)330 ExtrusionRole ExtrusionEntity::string_to_role(const std::string_view role)
331 {
332     if (role == L("Perimeter"))
333         return erPerimeter;
334     else if (role == L("External perimeter"))
335         return erExternalPerimeter;
336     else if (role == L("Overhang perimeter"))
337         return erOverhangPerimeter;
338     else if (role == L("Internal infill"))
339         return erInternalInfill;
340     else if (role == L("Solid infill"))
341         return erSolidInfill;
342     else if (role == L("Top solid infill"))
343         return erTopSolidInfill;
344     else if (role == L("Ironing"))
345         return erIroning;
346     else if (role == L("Bridge infill"))
347         return erBridgeInfill;
348     else if (role == L("Gap fill"))
349         return erGapFill;
350     else if (role == L("Skirt"))
351         return erSkirt;
352     else if (role == L("Support material"))
353         return erSupportMaterial;
354     else if (role == L("Support material interface"))
355         return erSupportMaterialInterface;
356     else if (role == L("Wipe tower"))
357         return erWipeTower;
358     else if (role == L("Custom"))
359         return erCustom;
360     else if (role == L("Mixed"))
361         return erMixed;
362     else
363         return erNone;
364 }
365 
366 }
367